diff options
Diffstat (limited to 'pyload')
965 files changed, 159990 insertions, 0 deletions
diff --git a/pyload/Core.py b/pyload/Core.py new file mode 100755 index 000000000..0de19abbf --- /dev/null +++ b/pyload/Core.py @@ -0,0 +1,682 @@ +# -*- coding: utf-8 -*- +# @author: RaNaN, mkaay, sebnapi, spoob, vuolter +# @version: v0.4.10 + +from __future__ import with_statement + +import logging +import logging.handlers +import os +import signal +import subprocess +import sys +import traceback + +import __builtin__ +import pyload +import pyload.utils.pylgettext as gettext + +from codecs import getwriter +from getopt import getopt, GetoptError +from imp import find_module +from os import _exit, execl, getcwd, makedirs, remove, sep, walk, chdir, close +from os.path import exists, join +from sys import argv, executable, exit +from time import time, sleep + +from pyload import remote +from pyload.database import DatabaseBackend, FileHandler +from pyload.config.Parser import ConfigParser +from pyload.manager.Account import AccountManager +from pyload.manager.Captcha import CaptchaManager +from pyload.manager.Event import PullManager +from pyload.manager.Plugin import PluginManager +from pyload.manager.Remote import RemoteManager +from pyload.manager.event.Scheduler import Scheduler +from pyload.manager.thread.Server import WebServer +from pyload.network.JsEngine import JsEngine +from pyload.network.RequestFactory import RequestFactory +from pyload.utils import freeSpace, formatSize + + +# TODO List +# - configurable auth system ldap/mysql +# - cron job like sheduler +class Core(object): + """pyLoad Core, one tool to rule them all... (the filehosters) :D""" + + + def __init__(self): + self.doDebug = False + self.running = False + self.daemon = False + self.remote = True + self.arg_links = [] + self.pidfile = "pyload.pid" + self.deleteLinks = False #: will delete links on startup + + if len(argv) > 1: + try: + options, args = getopt(argv[1:], 'vchdusqp:', + ["version", "clear", "clean", "help", "debug", "user", + "setup", "configdir=", "changedir", "daemon", + "quit", "status", "no-remote","pidfile="]) + + for option, argument in options: + if option in ("-v", "--version"): + print "pyLoad", pyload.__version__ + exit() + elif option in ("-p", "--pidfile"): + self.pidfile = argument + elif option == "--daemon": + self.daemon = True + elif option in ("-c", "--clear"): + self.deleteLinks = True + elif option in ("-h", "--help"): + self.print_help() + exit() + elif option in ("-d", "--debug"): + self.doDebug = True + elif option in ("-u", "--user"): + from pyload.config.Setup import SetupAssistant as Setup + + self.config = ConfigParser() + s = Setup(self.config) + s.set_user() + exit() + elif option in ("-s", "--setup"): + from pyload.config.Setup import SetupAssistant as Setup + + self.config = ConfigParser() + s = Setup(self.config) + s.start() + exit() + elif option == "--changedir": + from pyload.config.Setup import SetupAssistant as Setup + + self.config = ConfigParser() + s = Setup(self.config) + s.conf_path(True) + exit() + elif option in ("-q", "--quit"): + self.quitInstance() + exit() + elif option == "--status": + pid = self.isAlreadyRunning() + if self.isAlreadyRunning(): + print pid + exit(0) + else: + print "false" + exit(1) + elif option == "--clean": + self.cleanTree() + exit() + elif option == "--no-remote": + self.remote = False + + except GetoptError: + print 'Unknown Argument(s) "%s"' % " ".join(argv[1:]) + self.print_help() + exit() + + + def print_help(self): + print + print "pyLoad v%s 2008-2015 the pyLoad Team" % pyload.__version__ + print + if sys.argv[0].endswith(".py"): + print "Usage: python pyload.py [options]" + else: + print "Usage: pyload [options]" + print + print "<Options>" + print " -v, --version", " " * 10, "Print version to terminal" + print " -c, --clear", " " * 12, "Delete all saved packages/links" + # print " -a, --add=<link/list>", " " * 2, "Add the specified links" + print " -u, --user", " " * 13, "Manages users" + print " -d, --debug", " " * 12, "Enable debug mode" + print " -s, --setup", " " * 12, "Run Setup Assistant" + print " --configdir=<dir>", " " * 6, "Run with <dir> as config directory" + print " -p, --pidfile=<file>", " " * 3, "Set pidfile to <file>" + print " --changedir", " " * 12, "Change config dir permanently" + print " --daemon", " " * 15, "Daemonmize after start" + print " --no-remote", " " * 12, "Disable remote access (saves RAM)" + print " --status", " " * 15, "Display pid if running or False" + print " --clean", " " * 16, "Remove .pyc/.pyo files" + print " -q, --quit", " " * 13, "Quit running pyLoad instance" + print " -h, --help", " " * 13, "Display this help screen" + print + + + def toggle_pause(self): + if self.threadManager.pause: + self.threadManager.pause = False + return False + elif not self.threadManager.pause: + self.threadManager.pause = True + return True + + + def quit(self, a, b): + self.shutdown() + self.log.info(_("Received Quit signal")) + _exit(1) + + + def writePidFile(self): + self.deletePidFile() + pid = os.getpid() + with open(self.pidfile, "wb") as f: + f.write(str(pid)) + + + def deletePidFile(self): + if self.checkPidFile(): + self.log.debug("Deleting old pidfile %s" % self.pidfile) + os.reshutil.move(self.pidfile) + + + def checkPidFile(self): + """ return pid as int or 0""" + if os.path.isfile(self.pidfile): + with open(self.pidfile, "rb") as f: + pid = f.read().strip() + if pid: + pid = int(pid) + return pid + + return 0 + + + def isAlreadyRunning(self): + pid = self.checkPidFile() + if not pid or os.name == "nt": + return False + try: + os.kill(pid, 0) #: 0 - default signal (does nothing) + except Exception: + return 0 + + return pid + + + def quitInstance(self): + if os.name == "nt": + print "Not supported on windows." + return + + pid = self.isAlreadyRunning() + if not pid: + print "No pyLoad running." + return + + try: + os.kill(pid, 3) #: SIGUIT + + t = time() + print "waiting for pyLoad to quit" + + while exists(self.pidfile) and t + 10 > time(): + sleep(0.25) + + if not exists(self.pidfile): + print "pyLoad successfully stopped" + else: + os.kill(pid, 9) #: SIGKILL + print "pyLoad did not respond" + print "Kill signal was send to process with id %s" % pid + + except Exception: + print "Error quitting pyLoad" + + + def cleanTree(self): + for path, dirs, files in walk(self.path("")): + for f in files: + if not f.endswith(".pyo") and not f.endswith(".pyc"): + continue + + if "_25" in f or "_26" in f or "_27" in f: + continue + + print join(path, f) + reshutil.move(join(path, f)) + + + def start(self, rpc=True, web=True): + """ starts the fun :D """ + + self.version = pyload.__version__ + + if not exists("pyload.conf"): + from pyload.config.Setup import SetupAssistant as Setup + + print "This is your first start, running configuration assistent now." + self.config = ConfigParser() + s = Setup(self.config) + res = False + try: + res = s.start() + except SystemExit: + pass + except KeyboardInterrupt: + print "\nSetup interrupted" + except Exception: + res = False + traceback.print_exc() + print "Setup failed" + if not res: + reshutil.move("pyload.conf") + + exit() + + try: signal.signal(signal.SIGQUIT, self.quit) + except Exception: + pass + + self.config = ConfigParser() + + gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None]) + translation = gettext.translation("pyLoad", self.path("locale"), + languages=[self.config.get("general", "language"), "en"], fallback=True) + translation.install(True) + + self.debug = self.doDebug or self.config.get("general", "debug_mode") + self.remote &= self.config.get("remote", "activated") + + pid = self.isAlreadyRunning() + if pid: + print _("pyLoad already running with pid %s") % pid + exit() + + if os.name != "nt" and self.config.get("general", "renice"): + os.system("renice %d %d" % (self.config.get("general", "renice"), os.getpid())) + + if self.config.get("permission", "change_group"): + if os.name != "nt": + try: + from grp import getgrnam + + group = getgrnam(self.config.get("permission", "group")) + os.setgid(group[2]) + except Exception, e: + print _("Failed changing group: %s") % e + + if self.config.get("permission", "change_user"): + if os.name != "nt": + try: + from pwd import getpwnam + + user = getpwnam(self.config.get("permission", "user")) + os.setuid(user[2]) + except Exception, e: + print _("Failed changing user: %s") % e + + self.check_file(self.config.get("log", "log_folder"), _("folder for logs"), True) + + if self.debug: + self.init_logger(logging.DEBUG) #: logging level + else: + self.init_logger(logging.INFO) #: logging level + + self.do_kill = False + self.do_restart = False + self.shuttedDown = False + + self.log.info(_("Starting") + " pyLoad %s" % pyload.__version__) + self.log.info(_("Using home directory: %s") % getcwd()) + + self.writePidFile() + + #@TODO refractor + + remote.activated = self.remote + self.log.debug("Remote activated: %s" % self.remote) + + self.check_install("Crypto", _("pycrypto to decode container files")) + # img = self.check_install("Image", _("Python Image Library (PIL) for captcha reading")) + # self.check_install("pycurl", _("pycurl to download any files"), True, True) + self.check_file("tmp", _("folder for temporary files"), True) + # tesser = self.check_install("tesseract", _("tesseract for captcha reading"), False) if os.name != "nt" else True + + self.captcha = True #: checks seems to fail, although tesseract is available + + self.check_file(self.config.get("general", "download_folder"), _("folder for downloads"), True) + + if self.config.get("ssl", "activated"): + self.check_install("OpenSSL", _("OpenSSL for secure connection")) + + self.setupDB() + if self.config.oldRemoteData: + self.log.info(_("Moving old user config to DB")) + self.db.addUser(self.config.oldRemoteData['username'], self.config.oldRemoteData['password']) + + self.log.info(_("Please check your logindata with ./pyload.py -u")) + + if self.deleteLinks: + self.log.info(_("All links removed")) + self.db.purgeLinks() + + self.requestFactory = RequestFactory(self) + __builtin__.pyreq = self.requestFactory + + self.lastClientConnected = 0 + + # later imported because they would trigger api import, and remote value not set correctly + from pyload import api + from pyload.manager.Addon import AddonManager + from pyload.manager.Thread import ThreadManager + + if api.activated != self.remote: + self.log.warning("Import error: API remote status not correct.") + + self.api = api.Api(self) + + self.scheduler = Scheduler(self) + + # hell yeah, so many important managers :D + self.pluginManager = PluginManager(self) + self.pullManager = PullManager(self) + self.accountManager = AccountManager(self) + self.threadManager = ThreadManager(self) + self.captchaManager = CaptchaManager(self) + self.addonManager = AddonManager(self) + self.remoteManager = RemoteManager(self) + + self.js = JsEngine(self) + + self.log.info(_("Downloadtime: %s") % self.api.isTimeDownload()) + + if rpc: + self.remoteManager.startBackends() + + if web: + self.init_webserver() + + spaceLeft = freeSpace(self.config.get("general", "download_folder")) + + self.log.info(_("Free space: %s") % formatSize(spaceLeft)) + + self.config.save() #: save so config files gets filled + + link_file = join(pypath, "links.txt") + + if exists(link_file): + with open(link_file, "rb") as f: + if f.read().strip(): + self.api.addPackage("links.txt", [link_file], 1) + + link_file = "links.txt" + if exists(link_file): + with open(link_file, "rb") as f: + if f.read().strip(): + self.api.addPackage("links.txt", [link_file], 1) + + # self.scheduler.addJob(0, self.accountManager.getAccountInfos) + self.log.info(_("Activating Accounts...")) + self.accountManager.getAccountInfos() + + self.threadManager.pause = False + self.running = True + + self.log.info(_("Activating Plugins...")) + self.addonManager.coreReady() + + self.log.info(_("pyLoad is up and running")) + + locals().clear() + + while True: + sleep(2) + if self.do_restart: + self.log.info(_("restarting pyLoad")) + self.restart() + if self.do_kill: + self.shutdown() + self.log.info(_("pyLoad quits")) + self.removeLogger() + _exit(0) #@TODO thrift blocks shutdown + + self.threadManager.work() + self.scheduler.work() + + + def setupDB(self): + self.db = DatabaseBackend(self) #: the backend + self.db.setup() + + self.files = FileHandler(self) + self.db.manager = self.files #: ugly? + + + def init_webserver(self): + if self.config.get("webui", "activated"): + self.webserver = WebServer(self) + self.webserver.start() + + + def init_logger(self, level): + self.log = logging.getLogger("log") + self.log.setLevel(level) + + date_fmt = "%Y-%m-%d %H:%M:%S" + fh_fmt = "%(asctime)s %(levelname)-8s %(message)s" + + fh_frm = logging.Formatter(fh_fmt, date_fmt) #: file handler formatter + console_frm = fh_frm #: console formatter did not use colors as default + + # Console formatter with colors + if self.config.get("log", "color_console"): + import colorlog + + if self.config.get("log", "console_mode") == "label": + c_fmt = "%(asctime)s %(log_color)s%(bold)s%(white)s %(levelname)-8s%(reset)s %(message)s" + clr = { + 'DEBUG' : "bg_cyan" , + 'INFO' : "bg_green" , + 'WARNING' : "bg_yellow", + 'ERROR' : "bg_red" , + 'CRITICAL': "bg_purple", + } + + else: + c_fmt = "%(log_color)s%(asctime)s %(levelname)-8s %(message)s" + clr = { + 'DEBUG' : "cyan" , + 'WARNING' : "yellow", + 'ERROR' : "red" , + 'CRITICAL': "purple", + } + + console_frm = colorlog.ColoredFormatter(c_fmt, date_fmt, clr) + + # Set console formatter + console = logging.StreamHandler(sys.stdout) + console.setFormatter(console_frm) + self.log.addHandler(console) + + log_folder = self.config.get("log", "log_folder") + if not exists(log_folder): + makedirs(log_folder, 0700) + + # Set file handler formatter + if self.config.get("log", "file_log"): + if self.config.get("log", "log_rotate"): + file_handler = logging.handlers.RotatingFileHandler(join(log_folder, 'log.txt'), + maxBytes=self.config.get("log", "log_size") * 1024, + backupCount=int(self.config.get("log", "log_count")), + encoding="utf8") + else: + file_handler = logging.FileHandler(join(log_folder, 'log.txt'), encoding="utf8") + + file_handler.setFormatter(fh_frm) + self.log.addHandler(file_handler) + + + def removeLogger(self): + for h in list(self.log.handlers): + self.log.removeHandler(h) + h.close() + + + def check_install(self, check_name, legend, python=True, essential=False): + """check wether needed tools are installed""" + try: + if python: + find_module(check_name) + else: + pipe = subprocess.PIPE + subprocess.Popen(check_name, stdout=pipe, stderr=pipe) + + return True + except Exception: + if essential: + self.log.info(_("Install %s") % legend) + exit() + + return False + + + def check_file(self, check_names, description="", folder=False, empty=True, essential=False, quiet=False): + """check wether needed files exists""" + tmp_names = [] + if not type(check_names) == list: + tmp_names.append(check_names) + else: + tmp_names.extend(check_names) + file_created = True + file_exists = True + for tmp_name in tmp_names: + if not exists(tmp_name): + file_exists = False + if empty: + try: + if folder: + tmp_name = tmp_name.replace("/", sep) + makedirs(tmp_name) + else: + open(tmp_name, "w") + except Exception: + file_created = False + else: + file_created = False + + if not file_exists and not quiet: + if file_created: + # self.log.info( _("%s created") % description ) + pass + else: + if not empty: + self.log.warning( + _("could not find %(desc)s: %(name)s") % {"desc": description, "name": tmp_name}) + else: + print _("could not create %(desc)s: %(name)s") % {"desc": description, "name": tmp_name} + if essential: + exit() + + + def isClientConnected(self): + return (self.lastClientConnected + 30) > time() + + + def restart(self): + self.shutdown() + chdir(owd) + # close some open fds + for i in xrange(3, 50): + try: + close(i) + except Exception: + pass + + execl(executable, executable, *sys.argv) + _exit(0) + + + def shutdown(self): + self.log.info(_("shutting down...")) + try: + if self.config.get("webui", "activated") and hasattr(self, "webserver"): + self.webserver.quit() + + for thread in list(self.threadManager.threads): + thread.put("quit") + pyfiles = self.files.cache.values() + + for pyfile in pyfiles: + pyfile.abortDownload() + + self.addonManager.coreExiting() + + except Exception: + if self.debug: + traceback.print_exc() + self.log.info(_("error while shutting down")) + + finally: + self.files.syncSave() + self.shuttedDown = True + + self.deletePidFile() + + + def path(self, *args): + return join(pypath, *args) + + +def deamon(): + try: + pid = os.fork() + if pid > 0: + sys.exit(0) + except OSError, e: + print >> sys.stderr, "fork #1 failed: %d (%s)" % (e.errno, e.strerror) + sys.exit(1) + + # decouple from parent environment + os.setsid() + os.umask(0) + + # do second fork + try: + pid = os.fork() + if pid > 0: + # exit from second parent, print eventual PID before + print "Daemon PID %d" % pid + sys.exit(0) + except OSError, e: + print >> sys.stderr, "fork #2 failed: %d (%s)" % (e.errno, e.strerror) + sys.exit(1) + + # Iterate through and close some file descriptors. + for fd in xrange(0, 3): + try: + os.close(fd) + except OSError: # ERROR, fd wasn't open to begin with (ignored) + pass + + os.open(os.devnull, os.O_RDWR) # standard input (0) + os.dup2(0, 1) # standard output (1) + os.dup2(0, 2) + + pyload_core = Core() + pyload_core.start() + + +def main(): + if "--daemon" in sys.argv: + deamon() + else: + pyload_core = Core() + try: + pyload_core.start() + except KeyboardInterrupt: + pyload_core.shutdown() + pyload_core.log.info(_("killed pyLoad from Terminal")) + pyload_core.removeLogger() + _exit(1) + +# And so it begins... +if __name__ == "__main__": + main() diff --git a/pyload/__init__.py b/pyload/__init__.py new file mode 100644 index 000000000..7f5ac0667 --- /dev/null +++ b/pyload/__init__.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +import __builtin__ + +import os +import platform +import sys + +from codecs import getwriter + +from pyload.utils import get_console_encoding + + +__all__ = ["__status_code__", "__status__", "__version_info__", "__version__", "__author_name__", "__author_mail__", "__license__"] + +__version_info__ = (0, 4, 10) +__version__ = '.'.join(map(str, __version_info__)) + +__status_code__ = 4 +__status__ = {1: "Planning", + 2: "Pre-Alpha", + 3: "Alpha", + 4: "Beta", + 5: "Production/Stable", + 6: "Mature", + 7: "Inactive"}[__status_code__] #: PyPI Development Status Classifiers + +__description__ = "Fast, lightweight and full featured download manager" + +__license__ = "GNU General Public License v3" + +__website__ = "http://pyload.org" + +__authors__ = [("Marius" , "mkaay@mkaay.de" ), + ("RaNaN" , "Mast3rRaNaN@hotmail.de"), + ("Stefano" , "l.stickell@yahoo.it" ), + ("Walter Purcaro", "vuolter@gmail.com" ), + ("himbrr" , "himbrr@himbrr.ws" ), + ("sebnapi" , "" ), + ("spoob" , "spoob@gmx.de" ), + ("zoidberg10" , "zoidberg@mujmail.cz" )] + + +################################# InitHomeDir ################################# + +__builtin__.owd = os.path.abspath("") #: original working directory +__builtin__.homedir = os.path.expanduser("~") +__builtin__.rootdir = os.path.abspath(os.path.join(__file__, "..")) +__builtin__.configdir = "" +__builtin__.pypath = os.path.abspath(os.path.join(rootdir, "..")) + + +if "64" in platform.machine(): + sys.path.append(os.path.join(pypath, "lib64")) +sys.path.append(os.path.join(pypath, "lib", "Python", "Lib")) +sys.path.append(os.path.join(pypath, "lib")) + + +sys.stdout = getwriter(get_console_encoding(sys.stdout.encoding))(sys.stdout, errors="replace") + +if homedir == "~" and os.name == "nt": + import ctypes + + CSIDL_APPDATA = 26 + + _SHGetFolderPath = ctypes.windll.shell32.SHGetFolderPathW + _SHGetFolderPath.argtypes = [ctypes.wintypes.HWND, + ctypes.c_int, + ctypes.wintypes.HANDLE, + ctypes.wintypes.DWORD, + ctypes.wintypes.LPCWSTR] + + path_buf = ctypes.wintypes.create_unicode_buffer(ctypes.wintypes.MAX_PATH) + + _SHGetFolderPath(0, CSIDL_APPDATA, 0, 0, path_buf) + + __builtin__.homedir = path_buf.value + +try: + p = os.path.join(rootdir, "config", "configdir") + + with open(p, "rb") as f: + configdir = f.read().strip() + +except IOError: + if os.name == "posix": + configdir = os.path.join(homedir, ".pyload") + else: + configdir = os.path.join(homedir, "pyload") + +try: + if not os.path.exists(configdir): + os.makedirs(configdir, 0700) + + os.chdir(configdir) + +except IOError, e: + print >> sys.stderr, "configdir init failed: %d (%s)" % (e.errno, e.strerror) + sys.exit(1) + +else: + __builtin__.configdir = configdir diff --git a/pyload/api/__init__.py b/pyload/api/__init__.py new file mode 100644 index 000000000..442e9ef95 --- /dev/null +++ b/pyload/api/__init__.py @@ -0,0 +1,1052 @@ +# -*- coding: utf-8 -*- +# @author: RaNaN + +from __future__ import with_statement + +from base64 import standard_b64encode +from os.path import join +from time import time +import re + +from urlparse import urlparse + +from pyload.datatype.File import PyFile +from pyload.utils.packagetools import parseNames +from pyload.network.RequestFactory import getURL +from pyload.remote import activated +from pyload.utils import compare_time, freeSpace, safe_filename + +if activated: + try: + from thrift.protocol import TBase + from pyload.remote.thriftbackend.thriftgen.pyload.ttypes import * + from pyload.remote.thriftbackend.thriftgen.pyload.Pyload import Iface + + BaseObject = TBase + + except ImportError: + from pyload.api.types import * + + print "Thrift not imported" + +else: + from pyload.api.types import * + +# contains function names mapped to their permissions +# unlisted functions are for admins only +permMap = {} + + +# decorator only called on init, never initialized, so has no effect on runtime +def permission(bits): + class _Dec(object): + + def __new__(cls, func, *args, **kwargs): + permMap[func.__name__] = bits + return func + return _Dec + + +urlmatcher = re.compile(r"((https?|ftps?|xdcc|sftp):((//)|(\\\\))+[\w\d:#@%/;$()~_?\+\-=\\\.&\[\]\|]*)", re.IGNORECASE) + + +class PERMS(object): + ALL = 0 #: requires no permission, but login + ADD = 1 #: can add packages + DELETE = 2 #: can delete packages + STATUS = 4 #: see and change server status + LIST = 16 #: see queue and collector + MODIFY = 32 #: moddify some attribute of downloads + DOWNLOAD = 64 #: can download from webinterface + SETTINGS = 128 #: can access settings + ACCOUNTS = 256 #: can access accounts + LOGS = 512 #: can see server logs + + +class ROLE(object): + ADMIN = 0 #: admin has all permissions implicit + USER = 1 + + +def has_permission(userperms, perms): + # bytewise or perms before if needed + return perms == (userperms & perms) + + +class Api(Iface): + """ + **pyLoads API** + + This is accessible either internal via core.api or via thrift backend. + + see Thrift specification file remote/thriftbackend/pyload.thrift\ + for information about data structures and what methods are usuable with rpc. + + Most methods requires specific permissions, please look at the source code if you need to know.\ + These can be configured via webinterface. + Admin user have all permissions, and are the only ones who can access the methods with no specific permission. + """ + EXTERNAL = Iface #: let the json api know which methods are external + + + def __init__(self, core): + self.core = core + + + def _convertPyFile(self, p): + fdata = FileData(p['id'], p['url'], p['name'], p['plugin'], p['size'], + p['format_size'], p['status'], p['statusmsg'], + p['package'], p['error'], p['order']) + return fdata + + + def _convertConfigFormat(self, c): + sections = {} + for sectionName, sub in c.iteritems(): + section = ConfigSection(sectionName, sub['desc']) + items = [] + for key, data in sub.iteritems(): + if key in ("desc", "outline"): + continue + item = ConfigItem() + item.name = key + item.description = data['desc'] + item.value = str(data['value']) if not isinstance(data['value'], basestring) else data['value'] + item.type = data['type'] + items.append(item) + section.items = items + sections[sectionName] = section + if "outline" in sub: + section.outline = sub['outline'] + return sections + + + @permission(PERMS.SETTINGS) + def getConfigValue(self, category, option, section="core"): + """Retrieve config value. + + :param category: name of category, or plugin + :param option: config option + :param section: 'plugin' or 'core' + :return: config value as string + """ + if section == "core": + value = self.core.config[category][option] + else: + value = self.core.config.getPlugin(category, option) + return str(value) + + + @permission(PERMS.SETTINGS) + def setConfigValue(self, category, option, value, section="core"): + """Set new config value. + + :param section: + :param option: + :param value: new config value + :param section: 'plugin' or 'core + """ + self.core.addonManager.dispatchEvent("config-changed", category, option, value, section) + if section == "core": + self.core.config[category][option] = value + if option in ("limit_speed", "max_speed"): #: not so nice to update the limit + self.core.requestFactory.updateBucket() + elif section == "plugin": + self.core.config.setPlugin(category, option, value) + + + @permission(PERMS.SETTINGS) + def getConfig(self): + """Retrieves complete config of core. + + :return: list of `ConfigSection` + """ + return self._convertConfigFormat(self.core.config.config) + + + def getConfigDict(self): + """Retrieves complete config in dict format, not for RPC. + + :return: dict + """ + return self.core.config.config + + + @permission(PERMS.SETTINGS) + def getPluginConfig(self): + """Retrieves complete config for all plugins. + + :return: list of `ConfigSection` + """ + return self._convertConfigFormat(self.core.config.plugin) + + + def getPluginConfigDict(self): + """Plugin config as dict, not for RPC. + + :return: dict + """ + return self.core.config.plugin + + + @permission(PERMS.STATUS) + def pauseServer(self): + """Pause server: Tt wont start any new downloads, but nothing gets aborted.""" + self.core.threadManager.pause = True + + + @permission(PERMS.STATUS) + def unpauseServer(self): + """Unpause server: New Downloads will be started.""" + self.core.threadManager.pause = False + + + @permission(PERMS.STATUS) + def togglePause(self): + """Toggle pause state. + + :return: new pause state + """ + self.core.threadManager.pause ^= True + return self.core.threadManager.pause + + + @permission(PERMS.STATUS) + def toggleReconnect(self): + """Toggle reconnect activation. + + :return: new reconnect state + """ + self.core.config['reconnect']['activated'] ^= True + return self.core.config.get("reconnect", "activated") + + + @permission(PERMS.LIST) + def statusServer(self): + """Some general information about the current status of pyLoad. + + :return: `ServerStatus` + """ + serverStatus = ServerStatus(self.core.threadManager.pause, len(self.core.threadManager.processingIds()), + self.core.files.getQueueCount(), self.core.files.getFileCount(), 0, + not self.core.threadManager.pause and self.isTimeDownload(), + self.core.config.get("reconnect", "activated") and self.isTimeReconnect()) + for pyfile in [x.active for x in self.core.threadManager.threads if x.active and isinstance(x.active, PyFile)]: + serverStatus.speed += pyfile.getSpeed() #: bytes/s + return serverStatus + + + @permission(PERMS.STATUS) + def freeSpace(self): + """Available free space at download directory in bytes""" + return freeSpace(self.core.config.get("general", "download_folder")) + + + @permission(PERMS.ALL) + def getServerVersion(self): + """pyLoad Core version """ + return self.core.version + + + def kill(self): + """Clean way to quit pyLoad""" + self.core.do_kill = True + + + def restart(self): + """Restart pyload core""" + self.core.do_restart = True + + + @permission(PERMS.LOGS) + def getLog(self, offset=0): + """Returns most recent log entries. + + :param offset: line offset + :return: List of log entries + """ + filename = join(self.core.config.get("log", "log_folder"), 'log.txt') + try: + with open(filename, "r") as fh: + lines = fh.readlines() + if offset >= len(lines): + return [] + return lines[offset:] + except Exception: + return ['No log available'] + + + @permission(PERMS.STATUS) + def isTimeDownload(self): + """Checks if pyload will start new downloads according to time in config. + + :return: bool + """ + start = self.core.config.get("downloadTime", "start").split(":") + end = self.core.config.get("downloadTime", "end").split(":") + return compare_time(start, end) + + + @permission(PERMS.STATUS) + def isTimeReconnect(self): + """Checks if pyload will try to make a reconnect + + :return: bool + """ + start = self.core.config.get("reconnect", "startTime").split(":") + end = self.core.config.get("reconnect", "endTime").split(":") + return compare_time(start, end) and self.core.config.get("reconnect", "activated") + + + @permission(PERMS.LIST) + def statusDownloads(self): + """ Status off all currently running downloads. + + :return: list of `DownloadStatus` + """ + data = [] + for pyfile in self.core.threadManager.getActiveFiles(): + if not isinstance(pyfile, PyFile): + continue + data.append(DownloadInfo( + pyfile.id, pyfile.name, pyfile.getSpeed(), pyfile.getETA(), pyfile.formatETA(), + pyfile.getBytesLeft(), pyfile.getSize(), pyfile.formatSize(), pyfile.getPercent(), + pyfile.status, pyfile.getStatusName(), pyfile.formatWait(), + pyfile.waitUntil, pyfile.packageid, pyfile.package().name, pyfile.pluginname)) + return data + + + @permission(PERMS.ADD) + def addPackage(self, name, links, dest=Destination.Queue): + """Adds a package, with links to desired destination. + + :param name: name of the new package + :param links: list of urls + :param dest: `Destination` + :return: package id of the new package + """ + if self.core.config.get("general", "folder_per_package"): + folder = urlparse(name).path.split("/")[-1] + else: + folder = "" + + folder = safe_filename(folder) + + pid = self.core.files.addPackage(name, folder, dest) + + self.core.files.addLinks(links, pid) + + self.core.log.info(_("Added package %(name)s containing %(count)d links") % {"name": name, "count": len(links)}) + + self.core.files.save() + + return pid + + + @permission(PERMS.ADD) + def parseURLs(self, html=None, url=None): + """Parses html content or any arbitaty text for links and returns result of `checkURLs` + + :param html: html source + :return: + """ + urls = [] + if html: + urls += [x[0] for x in urlmatcher.findall(html)] + if url: + page = getURL(url) + urls += [x[0] for x in urlmatcher.findall(page)] + # remove duplicates + return self.checkURLs(set(urls)) + + + @permission(PERMS.ADD) + def checkURLs(self, urls): + """ Gets urls and returns pluginname mapped to list of matches urls. + + :param urls: + :return: {plugin: urls} + """ + data = self.core.pluginManager.parseUrls(urls) + plugins = {} + + for url, plugintype, pluginname in data: + try: + plugins[plugintype][pluginname].append(url) + except Exception: + plugins[plugintype][pluginname] = [url] + + return plugins + + + @permission(PERMS.ADD) + def checkOnlineStatus(self, urls): + """ initiates online status check + + :param urls: + :return: initial set of data as `OnlineCheck` instance containing the result id + """ + data = self.core.pluginManager.parseUrls(urls) + + rid = self.core.threadManager.createResultThread(data, False) + + tmp = [(url, (url, OnlineStatus(url, (plugintype, pluginname), "unknown", 3, 0))) for url, plugintype, pluginname in data] + data = parseNames(tmp) + result = {} + for k, v in data.iteritems(): + for url, status in v: + status.packagename = k + result[url] = status + + return OnlineCheck(rid, result) + + + @permission(PERMS.ADD) + def checkOnlineStatusContainer(self, urls, container, data): + """ checks online status of urls and a submited container file + + :param urls: list of urls + :param container: container file name + :param data: file content + :return: online check + """ + with open(join(self.core.config.get("general", "download_folder"), "tmp_" + container), "wb") as th: + th.write(str(data)) + return self.checkOnlineStatus(urls + [th.name]) + + + @permission(PERMS.ADD) + def pollResults(self, rid): + """ Polls the result available for ResultID + + :param rid: `ResultID` + :return: `OnlineCheck`, if rid is -1 then no more data available + """ + result = self.core.threadManager.getInfoResult(rid) + if "ALL_INFO_FETCHED" in result: + del result['ALL_INFO_FETCHED'] + return OnlineCheck(-1, result) + else: + return OnlineCheck(rid, result) + + + @permission(PERMS.ADD) + def generatePackages(self, links): + """ Parses links, generates packages names from urls + + :param links: list of urls + :return: package names mapped to urls + """ + return parseNames((x, x) for x in links) + + + @permission(PERMS.ADD) + def generateAndAddPackages(self, links, dest=Destination.Queue): + """Generates and add packages + + :param links: list of urls + :param dest: `Destination` + :return: list of package ids + """ + return [self.addPackage(name, urls, dest) for name, urls + in self.generatePackages(links).iteritems()] + + + @permission(PERMS.ADD) + def checkAndAddPackages(self, links, dest=Destination.Queue): + """Checks online status, retrieves names, and will add packages.\ + Because of this packages are not added immediatly, only for internal use. + + :param links: list of urls + :param dest: `Destination` + :return: None + """ + data = self.core.pluginManager.parseUrls(links) + self.core.threadManager.createResultThread(data, True) + + + @permission(PERMS.LIST) + def getPackageData(self, pid): + """Returns complete information about package, and included files. + + :param pid: package id + :return: `PackageData` with .links attribute + """ + data = self.core.files.getPackageData(int(pid)) + if not data: + raise PackageDoesNotExists(pid) + return PackageData(data['id'], data['name'], data['folder'], data['site'], data['password'], + data['queue'], data['order'], + links=[self._convertPyFile(x) for x in data['links'].itervalues()]) + + + @permission(PERMS.LIST) + def getPackageInfo(self, pid): + """Returns information about package, without detailed information about containing files + + :param pid: package id + :return: `PackageData` with .fid attribute + """ + data = self.core.files.getPackageData(int(pid)) + + if not data: + raise PackageDoesNotExists(pid) + return PackageData(data['id'], data['name'], data['folder'], data['site'], data['password'], + data['queue'], data['order'], + fids=[int(x) for x in data['links']]) + + + @permission(PERMS.LIST) + def getFileData(self, fid): + """Get complete information about a specific file. + + :param fid: file id + :return: `FileData` + """ + info = self.core.files.getFileData(int(fid)) + if not info: + raise FileDoesNotExists(fid) + return self._convertPyFile(info.values()[0]) + + + @permission(PERMS.DELETE) + def deleteFiles(self, fids): + """Deletes several file entries from pyload. + + :param fids: list of file ids + """ + for fid in fids: + self.core.files.deleteLink(int(fid)) + self.core.files.save() + + + @permission(PERMS.DELETE) + def deletePackages(self, pids): + """Deletes packages and containing links. + + :param pids: list of package ids + """ + for pid in pids: + self.core.files.deletePackage(int(pid)) + self.core.files.save() + + + @permission(PERMS.LIST) + def getQueue(self): + """Returns info about queue and packages, **not** about files, see `getQueueData` \ + or `getPackageData` instead. + + :return: list of `PackageInfo` + """ + return [PackageData(pack['id'], pack['name'], pack['folder'], pack['site'], + pack['password'], pack['queue'], pack['order'], + pack['linksdone'], pack['sizedone'], pack['sizetotal'], + pack['linkstotal']) + for pack in self.core.files.getInfoData(Destination.Queue).itervalues()] + + + @permission(PERMS.LIST) + def getQueueData(self): + """Return complete data about everything in queue, this is very expensive use it sparely.\ + See `getQueue` for alternative. + + :return: list of `PackageData` + """ + return [PackageData(pack['id'], pack['name'], pack['folder'], pack['site'], + pack['password'], pack['queue'], pack['order'], + pack['linksdone'], pack['sizedone'], pack['sizetotal'], + links=[self._convertPyFile(x) for x in pack['links'].itervalues()]) + for pack in self.core.files.getCompleteData(Destination.Queue).itervalues()] + + + @permission(PERMS.LIST) + def getCollector(self): + """same as `getQueue` for collector. + + :return: list of `PackageInfo` + """ + return [PackageData(pack['id'], pack['name'], pack['folder'], pack['site'], + pack['password'], pack['queue'], pack['order'], + pack['linksdone'], pack['sizedone'], pack['sizetotal'], + pack['linkstotal']) + for pack in self.core.files.getInfoData(Destination.Collector).itervalues()] + + + @permission(PERMS.LIST) + def getCollectorData(self): + """same as `getQueueData` for collector. + + :return: list of `PackageInfo` + """ + return [PackageData(pack['id'], pack['name'], pack['folder'], pack['site'], + pack['password'], pack['queue'], pack['order'], + pack['linksdone'], pack['sizedone'], pack['sizetotal'], + links=[self._convertPyFile(x) for x in pack['links'].itervalues()]) + for pack in self.core.files.getCompleteData(Destination.Collector).itervalues()] + + + @permission(PERMS.ADD) + def addFiles(self, pid, links): + """Adds files to specific package. + + :param pid: package id + :param links: list of urls + """ + self.core.files.addLinks(links, int(pid)) + self.core.log.info(_("Added %(count)d links to package #%(package)d ") % {"count": len(links), "package": pid}) + self.core.files.save() + + + @permission(PERMS.MODIFY) + def pushToQueue(self, pid): + """Moves package from Collector to Queue. + + :param pid: package id + """ + self.core.files.setPackageLocation(pid, Destination.Queue) + + + @permission(PERMS.MODIFY) + def pullFromQueue(self, pid): + """Moves package from Queue to Collector. + + :param pid: package id + """ + self.core.files.setPackageLocation(pid, Destination.Collector) + + + @permission(PERMS.MODIFY) + def restartPackage(self, pid): + """Restarts a package, resets every containing files. + + :param pid: package id + """ + self.core.files.restartPackage(int(pid)) + + + @permission(PERMS.MODIFY) + def restartFile(self, fid): + """Resets file status, so it will be downloaded again. + + :param fid: file id + """ + self.core.files.restartFile(int(fid)) + + + @permission(PERMS.MODIFY) + def recheckPackage(self, pid): + """Proofes online status of all files in a package, also a default action when package is added. + + :param pid: + :return: + """ + self.core.files.reCheckPackage(int(pid)) + + + @permission(PERMS.MODIFY) + def stopAllDownloads(self): + """Aborts all running downloads.""" + + pyfiles = self.core.files.cache.values() + for pyfile in pyfiles: + pyfile.abortDownload() + + + @permission(PERMS.MODIFY) + def stopDownloads(self, fids): + """Aborts specific downloads. + + :param fids: list of file ids + :return: + """ + pyfiles = self.core.files.cache.values() + for pyfile in pyfiles: + if pyfile.id in fids: + pyfile.abortDownload() + + + @permission(PERMS.MODIFY) + def setPackageName(self, pid, name): + """Renames a package. + + :param pid: package id + :param name: new package name + """ + pack = self.core.files.getPackage(pid) + pack.name = name + pack.sync() + + + @permission(PERMS.MODIFY) + def movePackage(self, destination, pid): + """Set a new package location. + + :param destination: `Destination` + :param pid: package id + """ + if destination in (0, 1): + self.core.files.setPackageLocation(pid, destination) + + + @permission(PERMS.MODIFY) + def moveFiles(self, fids, pid): + """Move multiple files to another package + + :param fids: list of file ids + :param pid: destination package + :return: + """ + # TODO: implement + pass + + + @permission(PERMS.ADD) + def uploadContainer(self, filename, data): + """Uploads and adds a container file to pyLoad. + + :param filename: filename, extension is important so it can correctly decrypted + :param data: file content + """ + with open(join(self.core.config.get("general", "download_folder"), "tmp_" + filename), "wb") as th: + th.write(str(data)) + self.addPackage(th.name, [th.name], Destination.Queue) + + + @permission(PERMS.MODIFY) + def orderPackage(self, pid, position): + """Gives a package a new position. + + :param pid: package id + :param position: + """ + self.core.files.reorderPackage(pid, position) + + + @permission(PERMS.MODIFY) + def orderFile(self, fid, position): + """Gives a new position to a file within its package. + + :param fid: file id + :param position: + """ + self.core.files.reorderFile(fid, position) + + + @permission(PERMS.MODIFY) + def setPackageData(self, pid, data): + """Allows to modify several package attributes. + + :param pid: package id + :param data: dict that maps attribute to desired value + """ + package = self.core.files.getPackage(pid) + if not package: + raise PackageDoesNotExists(pid) + for key, value in data.iteritems(): + if key == "id": + continue + setattr(package, key, value) + package.sync() + self.core.files.save() + + + @permission(PERMS.DELETE) + def deleteFinished(self): + """Deletes all finished files and completly finished packages. + + :return: list of deleted package ids + """ + return self.core.files.deleteFinishedLinks() + + + @permission(PERMS.MODIFY) + def restartFailed(self): + """Restarts all failed failes.""" + self.core.files.restartFailed() + + + @permission(PERMS.LIST) + def getPackageOrder(self, destination): + """Returns information about package order. + + :param destination: `Destination` + :return: dict mapping order to package id + """ + packs = self.core.files.getInfoData(destination) + order = {} + for pid in packs: + pack = self.core.files.getPackageData(int(pid)) + while pack['order'] in order.keys(): #: just in case + pack['order'] += 1 + order[pack['order']] = pack['id'] + return order + + + @permission(PERMS.LIST) + def getFileOrder(self, pid): + """Information about file order within package. + + :param pid: + :return: dict mapping order to file id + """ + rawdata = self.core.files.getPackageData(int(pid)) + order = {} + for id, pyfile in rawdata['links'].iteritems(): + while pyfile['order'] in order.keys(): #: just in case + pyfile['order'] += 1 + order[pyfile['order']] = pyfile['id'] + return order + + + @permission(PERMS.STATUS) + def isCaptchaWaiting(self): + """Indicates wether a captcha task is available + + :return: bool + """ + self.core.lastClientConnected = time() + task = self.core.captchaManager.getTask() + return not task is None + + + @permission(PERMS.STATUS) + def getCaptchaTask(self, exclusive=False): + """Returns a captcha task + + :param exclusive: unused + :return: `CaptchaTask` + """ + self.core.lastClientConnected = time() + task = self.core.captchaManager.getTask() + if task: + task.setWatingForUser(exclusive=exclusive) + data, type, result = task.getCaptcha() + ctask = CaptchaTask(int(task.id), standard_b64encode(data), type, result) + return ctask + return CaptchaTask(-1) + + + @permission(PERMS.STATUS) + def getCaptchaTaskStatus(self, tid): + """Get information about captcha task + + :param tid: task id + :return: string + """ + self.core.lastClientConnected = time() + task = self.core.captchaManager.getTaskByID(tid) + return task.getStatus() if task else "" + + + @permission(PERMS.STATUS) + def setCaptchaResult(self, tid, result): + """Set result for a captcha task + + :param tid: task id + :param result: captcha result + """ + self.core.lastClientConnected = time() + task = self.core.captchaManager.getTaskByID(tid) + if task: + task.setResult(result) + self.core.captchaManager.removeTask(task) + + + @permission(PERMS.STATUS) + def getEvents(self, uuid): + """Lists occured events, may be affected to changes in future. + + :param uuid: + :return: list of `Events` + """ + events = self.core.pullManager.getEvents(uuid) + new_events = [] + + + def convDest(d): + return Destination.Queue if d == "queue" else Destination.Collector + + for e in events: + event = EventInfo() + event.eventname = e[0] + if e[0] in ("update", "remove", "insert"): + event.id = e[3] + event.type = ElementType.Package if e[2] == "pack" else ElementType.File + event.destination = convDest(e[1]) + elif e[0] == "order": + if e[1]: + event.id = e[1] + event.type = ElementType.Package if e[2] == "pack" else ElementType.File + event.destination = convDest(e[3]) + elif e[0] == "reload": + event.destination = convDest(e[1]) + new_events.append(event) + return new_events + + + @permission(PERMS.ACCOUNTS) + def getAccounts(self, refresh): + """Get information about all entered accounts. + + :param refresh: reload account info + :return: list of `AccountInfo` + """ + accs = self.core.accountManager.getAccountInfos(False, refresh) + for group in accs.values(): + accounts = [AccountInfo(acc['validuntil'], acc['login'], acc['options'], acc['valid'], + acc['trafficleft'], acc['maxtraffic'], acc['premium'], acc['type']) + for acc in group] + return accounts or list() + + + @permission(PERMS.ALL) + def getAccountTypes(self): + """All available account types. + + :return: list + """ + return self.core.accountManager.accounts.keys() + + + @permission(PERMS.ACCOUNTS) + def updateAccount(self, plugin, account, password=None, options={}): + """Changes pw/options for specific account.""" + self.core.accountManager.updateAccount(plugin, account, password, options) + + + @permission(PERMS.ACCOUNTS) + def removeAccount(self, plugin, account): + """Remove account from pyload. + + :param plugin: pluginname + :param account: accountname + """ + self.core.accountManager.removeAccount(plugin, account) + + + @permission(PERMS.ALL) + def login(self, username, password, remoteip=None): + """Login into pyLoad, this **must** be called when using rpc before any methods can be used. + + :param username: + :param password: + :param remoteip: Omit this argument, its only used internal + :return: bool indicating login was successful + """ + return bool(self.checkAuth(username, password, remoteip)) + + + def checkAuth(self, username, password, remoteip=None): + """Check authentication and returns details + + :param username: + :param password: + :param remoteip: + :return: dict with info, empty when login is incorrect + """ + if self.core.config.get("remote", "nolocalauth") and remoteip == "127.0.0.1": + return "local" + else: + return self.core.db.checkAuth(username, password) + + + def isAuthorized(self, func, userdata): + """checks if the user is authorized for specific method + + :param func: function name + :param userdata: dictionary of user data + :return: boolean + """ + if userdata == "local" or userdata['role'] == ROLE.ADMIN: + return True + elif func in permMap and has_permission(userdata['permission'], permMap[func]): + return True + else: + return False + + + @permission(PERMS.ALL) + def getUserData(self, username, password): + """similar to `checkAuth` but returns UserData thrift type """ + user = self.checkAuth(username, password) + if user: + return UserData(user['name'], user['email'], user['role'], user['permission'], user['template']) + else: + return UserData() + + + def getAllUserData(self): + """returns all known user and info""" + return dict((user, UserData(user, data['email'], data['role'], data['permission'], data['template'])) for user, data + in self.core.db.getAllUserData().iteritems()) + + + @permission(PERMS.STATUS) + def getServices(self): + """ A dict of available services, these can be defined by addon plugins. + + :return: dict with this style: {"plugin": {"method": "description"}} + """ + return dict((plugin, funcs) for plugin, funcs in self.core.addonManager.methods.iteritems()) + + + @permission(PERMS.STATUS) + def hasService(self, plugin, func): + """Checks wether a service is available. + + :param plugin: + :param func: + :return: bool + """ + cont = self.core.addonManager.methods + return plugin in cont and func in cont[plugin] + + + @permission(PERMS.STATUS) + def call(self, info): + """Calls a service (a method in addon plugin). + + :param info: `ServiceCall` + :return: result + :raises: ServiceDoesNotExists, when its not available + :raises: ServiceException, when a exception was raised + """ + plugin = info.plugin + func = info.func + args = info.arguments + parse = info.parseArguments + if not self.hasService(plugin, func): + raise ServiceDoesNotExists(plugin, func) + try: + ret = self.core.addonManager.callRPC(plugin, func, args, parse) + except Exception, e: + raise ServiceException(e.message) + + + @permission(PERMS.STATUS) + def getAllInfo(self): + """Returns all information stored by addon plugins. Values are always strings + + :return: {"plugin": {"name": value}} + """ + return self.core.addonManager.getAllInfo() + + + @permission(PERMS.STATUS) + def getInfoByPlugin(self, plugin): + """Returns information stored by a specific plugin. + + :param plugin: pluginname + :return: dict of attr names mapped to value {"name": value} + """ + return self.core.addonManager.getInfo(plugin) + + + def changePassword(self, user, oldpw, newpw): + """ changes password for specific user """ + return self.core.db.changePassword(user, oldpw, newpw) + + + def setUserPermission(self, user, perm, role): + self.core.db.setPermission(user, perm) + self.core.db.setRole(user, role) diff --git a/pyload/api/types.py b/pyload/api/types.py new file mode 100644 index 000000000..9381df3c7 --- /dev/null +++ b/pyload/api/types.py @@ -0,0 +1,562 @@ +# -*- coding: utf-8 -*- +# Autogenerated by pyload +# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING + +class BaseObject(object): + __slots__ = [] + + +class Destination(object): + Collector = 0 + Queue = 1 + + +class DownloadStatus(object): + Aborted = 9 + Custom = 11 + Decrypting = 10 + Downloading = 12 + Failed = 8 + Finished = 0 + Offline = 1 + Online = 2 + Processing = 13 + Queued = 3 + Skipped = 4 + Starting = 7 + TempOffline = 6 + Unknown = 14 + Waiting = 5 + + +class ElementType(object): + File = 1 + Package = 0 + + +class Input(object): + BOOL = 4 + CHOICE = 6 + CLICK = 5 + LIST = 8 + MULTIPLE = 7 + NONE = 0 + PASSWORD = 3 + TABLE = 9 + TEXT = 1 + TEXTBOX = 2 + + +class Output(object): + CAPTCHA = 1 + NOTIFICATION = 4 + QUESTION = 2 + + +class AccountInfo(BaseObject): + __slots__ = ['validuntil', 'login', 'options', 'valid', 'trafficleft', 'maxtraffic', 'premium', 'type'] + + + def __init__(self, validuntil=None, login=None, options=None, valid=None, trafficleft=None, maxtraffic=None, premium=None, type=None): + self.validuntil = validuntil + self.login = login + self.options = options + self.valid = valid + self.trafficleft = trafficleft + self.maxtraffic = maxtraffic + self.premium = premium + self.type = type + + +class CaptchaTask(BaseObject): + __slots__ = ['tid', 'data', 'type', 'resultType'] + + + def __init__(self, tid=None, data=None, type=None, resultType=None): + self.tid = tid + self.data = data + self.type = type + self.resultType = resultType + + +class ConfigItem(BaseObject): + __slots__ = ['name', 'description', 'value', 'type'] + + + def __init__(self, name=None, description=None, value=None, type=None): + self.name = name + self.description = description + self.value = value + self.type = type + + +class ConfigSection(BaseObject): + __slots__ = ['name', 'description', 'items', 'outline'] + + + def __init__(self, name=None, description=None, items=None, outline=None): + self.name = name + self.description = description + self.items = items + self.outline = outline + + +class DownloadInfo(BaseObject): + __slots__ = ['fid', 'name', 'speed', 'eta', 'format_eta', 'bleft', 'size', 'format_size', 'percent', 'status', 'statusmsg', 'format_wait', 'wait_until', 'packageID', 'packageName', 'plugin'] + + + def __init__(self, fid=None, name=None, speed=None, eta=None, format_eta=None, bleft=None, size=None, format_size=None, percent=None, status=None, statusmsg=None, format_wait=None, wait_until=None, packageID=None, packageName=None, plugin=None): + self.fid = fid + self.name = name + self.speed = speed + self.eta = eta + self.format_eta = format_eta + self.bleft = bleft + self.size = size + self.format_size = format_size + self.percent = percent + self.status = status + self.statusmsg = statusmsg + self.format_wait = format_wait + self.wait_until = wait_until + self.packageID = packageID + self.packageName = packageName + self.plugin = plugin + + +class EventInfo(BaseObject): + __slots__ = ['eventname', 'id', 'type', 'destination'] + + + def __init__(self, eventname=None, id=None, type=None, destination=None): + self.eventname = eventname + self.id = id + self.type = type + self.destination = destination + + +class FileData(BaseObject): + __slots__ = ['fid', 'url', 'name', 'plugin', 'size', 'format_size', 'status', 'statusmsg', 'packageID', 'error', 'order'] + + + def __init__(self, fid=None, url=None, name=None, plugin=None, size=None, format_size=None, status=None, statusmsg=None, packageID=None, error=None, order=None): + self.fid = fid + self.url = url + self.name = name + self.plugin = plugin + self.size = size + self.format_size = format_size + self.status = status + self.statusmsg = statusmsg + self.packageID = packageID + self.error = error + self.order = order + + +class FileDoesNotExists(Exception): + __slots__ = ['fid'] + + + def __init__(self, fid=None): + self.fid = fid + + +class InteractionTask(BaseObject): + __slots__ = ['iid', 'input', 'structure', 'preset', 'output', 'data', 'title', 'description', 'plugin'] + + + def __init__(self, iid=None, input=None, structure=None, preset=None, output=None, data=None, title=None, description=None, plugin=None): + self.iid = iid + self.input = input + self.structure = structure + self.preset = preset + self.output = output + self.data = data + self.title = title + self.description = description + self.plugin = plugin + + +class OnlineCheck(BaseObject): + __slots__ = ['rid', 'data'] + + + def __init__(self, rid=None, data=None): + self.rid = rid + self.data = data + + +class OnlineStatus(BaseObject): + __slots__ = ['name', 'plugin', 'packagename', 'status', 'size'] + + + def __init__(self, name=None, plugin=(None, None), packagename=None, status=None, size=None): + self.name = name + self.plugin = plugin + self.packagename = packagename + self.status = status + self.size = size + + +class PackageData(BaseObject): + __slots__ = ['pid', 'name', 'folder', 'site', 'password', 'dest', 'order', 'linksdone', 'sizedone', 'sizetotal', 'linkstotal', 'links', 'fids'] + + + def __init__(self, pid=None, name=None, folder=None, site=None, password=None, dest=None, order=None, linksdone=None, sizedone=None, sizetotal=None, linkstotal=None, links=None, fids=None): + self.pid = pid + self.name = name + self.folder = folder + self.site = site + self.password = password + self.dest = dest + self.order = order + self.linksdone = linksdone + self.sizedone = sizedone + self.sizetotal = sizetotal + self.linkstotal = linkstotal + self.links = links + self.fids = fids + + +class PackageDoesNotExists(Exception): + __slots__ = ['pid'] + + + def __init__(self, pid=None): + self.pid = pid + + +class ServerStatus(BaseObject): + __slots__ = ['pause', 'active', 'queue', 'total', 'speed', 'download', 'reconnect'] + + + def __init__(self, pause=None, active=None, queue=None, total=None, speed=None, download=None, reconnect=None): + self.pause = pause + self.active = active + self.queue = queue + self.total = total + self.speed = speed + self.download = download + self.reconnect = reconnect + + +class ServiceCall(BaseObject): + __slots__ = ['plugin', 'func', 'arguments', 'parseArguments'] + + + def __init__(self, plugin=None, func=None, arguments=None, parseArguments=None): + self.plugin = plugin + self.func = func + self.arguments = arguments + self.parseArguments = parseArguments + + +class ServiceDoesNotExists(Exception): + __slots__ = ['plugin', 'func'] + + + def __init__(self, plugin=None, func=None): + self.plugin = plugin + self.func = func + + +class ServiceException(Exception): + __slots__ = ['msg'] + + + def __init__(self, msg=None): + self.msg = msg + + +class UserData(BaseObject): + __slots__ = ['name', 'email', 'role', 'permission', 'templateName'] + + + def __init__(self, name=None, email=None, role=None, permission=None, templateName=None): + self.name = name + self.email = email + self.role = role + self.permission = permission + self.templateName = templateName + + +class Iface(object): + + def addFiles(self, pid, links): + pass + + + def addPackage(self, name, links, dest): + pass + + + def call(self, info): + pass + + + def checkOnlineStatus(self, urls): + pass + + + def checkOnlineStatusContainer(self, urls, filename, data): + pass + + + def checkURLs(self, urls): + pass + + + def deleteFiles(self, fids): + pass + + + def deleteFinished(self): + pass + + + def deletePackages(self, pids): + pass + + + def freeSpace(self): + pass + + + def generateAndAddPackages(self, links, dest): + pass + + + def generatePackages(self, links): + pass + + + def getAccountTypes(self): + pass + + + def getAccounts(self, refresh): + pass + + + def getAllInfo(self): + pass + + + def getAllUserData(self): + pass + + + def getCaptchaTask(self, exclusive): + pass + + + def getCaptchaTaskStatus(self, tid): + pass + + + def getCollector(self): + pass + + + def getCollectorData(self): + pass + + + def getConfig(self): + pass + + + def getConfigValue(self, category, option, section): + pass + + + def getEvents(self, uuid): + pass + + + def getFileData(self, fid): + pass + + + def getFileOrder(self, pid): + pass + + + def getInfoByPlugin(self, plugin): + pass + + + def getLog(self, offset): + pass + + + def getPackageData(self, pid): + pass + + + def getPackageInfo(self, pid): + pass + + + def getPackageOrder(self, destination): + pass + + + def getPluginConfig(self): + pass + + + def getQueue(self): + pass + + + def getQueueData(self): + pass + + + def getServerVersion(self): + pass + + + def getServices(self): + pass + + + def getUserData(self, username, password): + pass + + + def hasService(self, plugin, func): + pass + + + def isCaptchaWaiting(self): + pass + + + def isTimeDownload(self): + pass + + + def isTimeReconnect(self): + pass + + + def kill(self): + pass + + + def login(self, username, password): + pass + + + def moveFiles(self, fids, pid): + pass + + + def movePackage(self, destination, pid): + pass + + + def orderFile(self, fid, position): + pass + + + def orderPackage(self, pid, position): + pass + + + def parseURLs(self, html, url): + pass + + + def pauseServer(self): + pass + + + def pollResults(self, rid): + pass + + + def pullFromQueue(self, pid): + pass + + + def pushToQueue(self, pid): + pass + + + def recheckPackage(self, pid): + pass + + + def removeAccount(self, plugin, account): + pass + + + def restart(self): + pass + + + def restartFailed(self): + pass + + + def restartFile(self, fid): + pass + + + def restartPackage(self, pid): + pass + + + def setCaptchaResult(self, tid, result): + pass + + + def setConfigValue(self, category, option, value, section): + pass + + + def setPackageData(self, pid, data): + pass + + + def setPackageName(self, pid, name): + pass + + + def statusDownloads(self): + pass + + + def statusServer(self): + pass + + + def stopAllDownloads(self): + pass + + + def stopDownloads(self, fids): + pass + + + def togglePause(self): + pass + + + def toggleReconnect(self): + pass + + + def unpauseServer(self): + pass + + + def updateAccount(self, plugin, account, password, options): + pass + + + def uploadContainer(self, filename, data): + pass diff --git a/pyload/cli/AddPackage.py b/pyload/cli/AddPackage.py new file mode 100644 index 000000000..e750274ca --- /dev/null +++ b/pyload/cli/AddPackage.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +# @author: RaNaN + +from pyload.cli.Handler import Handler +from pyload.utils.printer import * + + +class AddPackage(Handler): + """ let the user add packages """ + + def init(self): + self.name = "" + self.urls = [] + + + def onEnter(self, inp): + if inp == "0": + self.cli.reset() + + if not self.name: + self.name = inp + self.setInput() + elif inp == "END": + # add package + self.client.addPackage(self.name, self.urls, 1) + self.cli.reset() + else: + if inp.strip(): + self.urls.append(inp) + self.setInput() + + + def renderBody(self, line): + println(line, white(_("Add Package:"))) + println(line + 1, "") + line += 2 + + if not self.name: + println(line, _("Enter a name for the new package")) + println(line + 1, "") + line += 2 + else: + println(line, _("Package: %s") % self.name) + println(line + 1, _("Parse the links you want to add.")) + println(line + 2, _("Type %s when done.") % mag("END")) + println(line + 3, _("Links added: ") + mag(len(self.urls))) + line += 4 + + println(line, "") + println(line + 1, mag("0.") + _(" back to main menu")) + + return line + 2 diff --git a/pyload/cli/Cli.py b/pyload/cli/Cli.py new file mode 100644 index 000000000..812212d31 --- /dev/null +++ b/pyload/cli/Cli.py @@ -0,0 +1,578 @@ +# -*- coding: utf-8 -*- +# @author: RaNaN + +from __future__ import with_statement + +import os +import sys +import threading +import traceback + +import pyload.utils.pylgettext as gettext + +from codecs import getwriter +from getopt import GetoptError, getopt +from os import _exit +from os.path import join, exists, basename +from sys import exit +from time import sleep + +from Getch import Getch +from rename_process import renameProcess + +from pyload.api import Destination +from pyload.cli import AddPackage, ManageFiles +from pyload.config.Parser import ConfigParser +from pyload.remote.thriftbackend.ThriftClient import ThriftClient, NoConnection, NoSSL, WrongLogin, ConnectionClosed +from pyload.utils import formatSize, decode +from pyload.utils.printer import * + + +class Cli(object): + + def __init__(self, client, command): + self.client = client + self.command = command + + if not self.command: + renameProcess('pyload-cli') + self.getch = Getch() + self.input = "" + self.inputline = 0 + self.lastLowestLine = 0 + self.menuline = 0 + + self.lock = threading.Lock() + + # processor funcions, these will be changed dynamically depending on control flow + self.headerHandler = self #: the download status + self.bodyHandler = self #: the menu section + self.inputHandler = self + + os.system("clear") + println(1, blue("py") + yellow("Load") + white(_(" Command Line Interface"))) + println(2, "") + + self.thread = RefreshThread(self) + self.thread.start() + + self.start() + else: + self.processCommand() + + + def reset(self): + """ reset to initial main menu """ + self.input = "" + self.headerHandler = self.bodyHandler = self.inputHandler = self + + + def start(self): + """ main loop. handle input """ + while True: + # inp = raw_input() + inp = self.getch.impl() + if ord(inp) == 3: + os.system("clear") + sys.exit() #: ctrl + c + elif ord(inp) == 13: #: enter + try: + self.lock.acquire() + self.inputHandler.onEnter(self.input) + + except Exception, e: + println(2, red(e)) + finally: + self.lock.release() + + elif ord(inp) == 127: + self.input = self.input[:-1] #: backspace + try: + self.lock.acquire() + self.inputHandler.onBackSpace() + finally: + self.lock.release() + + elif ord(inp) == 27: #: ugly symbol + pass + else: + self.input += inp + try: + self.lock.acquire() + self.inputHandler.onChar(inp) + finally: + self.lock.release() + + self.inputline = self.bodyHandler.renderBody(self.menuline) + self.renderFooter(self.inputline) + + + def refresh(self): + """refresh screen""" + + println(1, blue("py") + yellow("Load") + white(_(" Command Line Interface"))) + println(2, "") + + self.lock.acquire() + + self.menuline = self.headerHandler.renderHeader(3) + 1 + println(self.menuline - 1, "") + self.inputline = self.bodyHandler.renderBody(self.menuline) + self.renderFooter(self.inputline) + + self.lock.release() + + + def setInput(self, string=""): + self.input = string + + + def setHandler(self, klass): + # create new handler with reference to cli + self.bodyHandler = self.inputHandler = klass(self) + self.input = "" + + + def renderHeader(self, line): + """ prints download status """ + # print updated information + # print "\033[J" #: clear screen + # self.println(1, blue("py") + yellow("Load") + white(_(" Command Line Interface"))) + # self.println(2, "") + # self.println(3, white(_("%s Downloads:") % (len(data)))) + + data = self.client.statusDownloads() + speed = 0 + + println(line, white(_("%s Downloads:") % (len(data)))) + line += 1 + + for download in data: + if download.status == 12: #: downloading + percent = download.percent + z = percent / 4 + speed += download.speed + println(line, cyan(download.name)) + line += 1 + println(line, + blue("[") + yellow(z * "#" + (25 - z) * " ") + blue("] ") + green(str(percent) + "%") + _( + " Speed: ") + green(formatSize(download.speed) + "/s") + _(" Size: ") + green( + download.format_size) + _(" Finished in: ") + green(download.format_eta) + _( + " ID: ") + green(download.fid)) + line += 1 + if download.status == 5: + println(line, cyan(download.name)) + line += 1 + println(line, _("waiting: ") + green(download.format_wait)) + line += 1 + + println(line, "") + line += 1 + status = self.client.statusServer() + if status.pause: + paused = _("Status:") + " " + red(_("paused")) + else: + paused = _("Status:") + " " + red(_("running")) + + println(line, "%s %s: %s %s: %s %s: %s" % ( + paused, _("total Speed"), red(formatSize(speed) + "/s"), _("Files in queue"), red( + status.queue), _("Total"), red(status.total))) + + return line + 1 + + + def renderBody(self, line): + """ prints initial menu """ + println(line, white(_("Menu:"))) + println(line + 1, "") + println(line + 2, mag("1.") + _(" Add Links")) + println(line + 3, mag("2.") + _(" Manage Queue")) + println(line + 4, mag("3.") + _(" Manage Collector")) + println(line + 5, mag("4.") + _(" (Un)Pause Server")) + println(line + 6, mag("5.") + _(" Kill Server")) + println(line + 7, mag("6.") + _(" Quit")) + + return line + 8 + + + def renderFooter(self, line): + """ prints out the input line with input """ + println(line, "") + line += 1 + + println(line, white(" Input: ") + decode(self.input)) + + # clear old output + if line < self.lastLowestLine: + for i in xrange(line + 1, self.lastLowestLine + 1): + println(i, "") + + self.lastLowestLine = line + + # set cursor to position + print "\033[" + str(self.inputline) + ";0H" + + + def onChar(self, char): + """ default no special handling for single chars """ + if char == "1": + self.setHandler(AddPackage) + elif char == "2": + self.setHandler(ManageFiles) + elif char == "3": + self.setHandler(ManageFiles) + self.bodyHandler.target = Destination.Collector + elif char == "4": + self.client.togglePause() + self.setInput() + elif char == "5": + self.client.kill() + self.client.close() + sys.exit() + elif char == "6": + os.system('clear') + sys.exit() + + + def onEnter(self, inp): + pass + + + def onBackSpace(self): + pass + + + def processCommand(self): + command = self.command[0] + args = [] + if len(self.command) > 1: + args = self.command[1:] + + if command == "status": + files = self.client.statusDownloads() + + if not files: + print "No downloads running." + + for download in files: + if download.status == 12: #: downloading + print print_status(download) + print "\tDownloading: %s @ %s/s\t %s (%s%%)" % ( + download.format_eta, formatSize(download.speed), formatSize(download.size - download.bleft), + download.percent) + elif download.status == 5: + print print_status(download) + print "\tWaiting: %s" % download.format_wait + else: + print print_status(download) + + elif command == "queue": + print_packages(self.client.getQueueData()) + + elif command == "collector": + print_packages(self.client.getCollectorData()) + + elif command == "add": + if len(args) < 2: + print _("Please use this syntax: add <Package name> <link> <link2> ...") + return + + self.client.addPackage(args[0], args[1:], Destination.Queue) + + elif command == "add_coll": + if len(args) < 2: + print _("Please use this syntax: add <Package name> <link> <link2> ...") + return + + self.client.addPackage(args[0], args[1:], Destination.Collector) + + elif command == "del_file": + self.client.deleteFiles([int(x) for x in args]) + print "Files deleted." + + elif command == "del_package": + self.client.deletePackages([int(x) for x in args]) + print "Packages deleted." + + elif command == "move": + for pid in args: + pack = self.client.getPackageInfo(int(pid)) + self.client.movePackage((pack.dest + 1) % 2, pack.pid) + + elif command == "check": + print _("Checking %d links:") % len(args) + print + rid = self.client.checkOnlineStatus(args).rid + self.printOnlineCheck(self.client, rid) + + elif command == "check_container": + path = args[0] + if not exists(join(owd, path)): + print _("File does not exists.") + return + + with open(join(owd, path), "rb") as f: + content = f.read() + + rid = self.client.checkOnlineStatusContainer([], basename(f.name), content).rid + self.printOnlineCheck(self.client, rid) + + elif command == "pause": + self.client.pause() + + elif command == "unpause": + self.client.unpause() + + elif command == "toggle": + self.client.togglePause() + + elif command == "kill": + self.client.kill() + elif command == "restart_file": + for x in args: + self.client.restartFile(int(x)) + print "Files restarted." + elif command == "restart_package": + for pid in args: + self.client.restartPackage(int(pid)) + print "Packages restarted." + + else: + print_commands() + + + def printOnlineCheck(self, client, rid): + while True: + sleep(1) + result = client.pollResults(rid) + for url, status in result.data.iteritems(): + if status.status == 2: + check = "Online" + elif status.status == 1: + check = "Offline" + else: + check = "Unknown" + + print "%-45s %-12s\t %-15s\t %s" % (status.name, formatSize(status.size), status.plugin, check) + + if result.rid == -1: + break + + +class RefreshThread(Thread): + + def __init__(self, cli): + Thread.__init__(self) + self.setDaemon(True) + self.cli = cli + + + def run(self): + while True: + sleep(1) + try: + self.cli.refresh() + except ConnectionClosed: + os.system("clear") + print _("pyLoad was terminated") + _exit(0) + except Exception, e: + println(2, red(str(e))) + self.cli.reset() + traceback.print_exc() + + +def print_help(config): + print + print "pyLoad CLI Copyright (c) 2008-2015 the pyLoad Team" + print + print "Usage: [python] pyload-cli.py [options] [command]" + print + print "<Commands>" + print "See pyload-cli.py -c for a complete listing." + print + print "<Options>" + print " -i, --interactive", " Start in interactive mode" + print + print " -u, --username=", " " * 2, "Specify Username" + print " --pw=<password>", " " * 2, "Password" + print " -a, --address=", " " * 3, "Specify address (current=%s)" % config['addr'] + print " -p, --port", " " * 7, "Specify port (current=%s)" % config['port'] + print + print " -l, --language", " " * 3, "Set user interface language (current=%s)" % config['language'] + print " -h, --help", " " * 7, "Display this help screen" + print " -c, --commands", " " * 3, "List all available commands" + print + + +def print_packages(data): + for pack in data: + print "Package %s (#%s):" % (pack.name, pack.pid) + for download in pack.links: + print "\t" + print_file(download) + print + + +def print_file(download): + return "#%(id)-6d %(name)-30s %(statusmsg)-10s %(plugin)-8s" % { + "id": download.fid, + "name": download.name, + "statusmsg": download.statusmsg, + "plugin": download.plugin + } + + +def print_status(download): + return "#%(id)-6s %(name)-40s Status: %(statusmsg)-10s Size: %(size)s" % { + "id": download.fid, + "name": download.name, + "statusmsg": download.statusmsg, + "size": download.format_size + } + + +def print_commands(): + commands = [("status", _("Prints server status")), + ("queue", _("Prints downloads in queue")), + ("collector", _("Prints downloads in collector")), + ("add <name> <link1> <link2>...", _("Adds package to queue")), + ("add_coll <name> <link1> <link2>...", _("Adds package to collector")), + ("del_file <fid> <fid2>...", _("Delete Files from Queue/Collector")), + ("del_package <pid> <pid2>...", _("Delete Packages from Queue/Collector")), + ("move <pid> <pid2>...", _("Move Packages from Queue to Collector or vice versa")), + ("restart_file <fid> <fid2>...", _("Restart files")), + ("restart_package <pid> <pid2>...", _("Restart packages")), + ("check <container|url> ...", _("Check online status, works with local container")), + ("check_container path", _("Checks online status of a container file")), + ("pause", _("Pause the server")), + ("unpause", _("continue downloads")), + ("toggle", _("Toggle pause/unpause")), + ("kill", _("kill server")), ] + + print _("List of commands:") + print + for c in commands: + print "%-35s %s" % c + + +def writeConfig(opts): + try: + with open(join(homedir, ".pyload-cli"), "w") as cfgfile: + cfgfile.write("[cli]") + for opt in opts: + cfgfile.write("%s=%s\n" % (opt, opts[opt])) + except Exception: + print _("Couldn't write user config file") + + +def main(): + config = {"addr": "127.0.0.1", "port": "7227", "language": "en"} + try: + config['language'] = os.environ['LANG'][0:2] + except Exception: + pass + + if (not exists(join(pypath, "locale", config['language']))) or config['language'] == "": + config['language'] = "en" + + configFile = ConfigParser.ConfigParser() + configFile.read(join(homedir, ".pyload-cli")) + + if configFile.has_section("cli"): + for opt in configFile.items("cli"): + config[opt[0]] = opt[1] + + gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None]) + translation = gettext.translation("Cli", join(pypath, "locale"), + languages=[config['language'], "en"], fallback=True) + translation.install(unicode=True) + + interactive = False + command = None + username = "" + password = "" + + shortOptions = 'iu:p:a:hcl:' + longOptions = ['interactive', "username=", "pw=", "address=", "port=", "help", "commands", "language="] + + try: + opts, extraparams = getopt(sys.argv[1:], shortOptions, longOptions) + for option, params in opts: + if option in ("-i", "--interactive"): + interactive = True + elif option in ("-u", "--username"): + username = params + elif option in ("-a", "--address"): + config['addr'] = params + elif option in ("-p", "--port"): + config['port'] = params + elif option in ("-l", "--language"): + config['language'] = params + gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None]) + translation = gettext.translation("Cli", join(pypath, "locale"), + languages=[config['language'], "en"], fallback=True) + translation.install(unicode=True) + elif option in ("-h", "--help"): + print_help(config) + exit() + elif option in ("--pw"): + password = params + elif option in ("-c", "--comands"): + print_commands() + exit() + + except GetoptError: + print 'Unknown Argument(s) "%s"' % " ".join(sys.argv[1:]) + print_help(config) + exit() + + if len(extraparams) >= 1: + command = extraparams + + client = False + + if interactive: + try: + client = ThriftClient(config['addr'], int(config['port']), username, password) + except WrongLogin: + pass + except NoSSL: + print _("You need py-openssl to connect to this pyLoad Core.") + exit() + except NoConnection: + config['addr'] = False + config['port'] = False + + if not client: + if not config['addr']: config['addr'] = raw_input(_("Address: ")) + if not config['port']: config['port'] = raw_input(_("Port: ")) + if not username: username = raw_input(_("Username: ")) + if not password: + from getpass import getpass + + password = getpass(_("Password: ")) + + try: + client = ThriftClient(config['addr'], int(config['port']), username, password) + except WrongLogin: + print _("Login data is wrong.") + except NoConnection: + print _("Could not establish connection to %(addr)s:%(port)s." % {"addr": config['addr'], + "port": config['port']}) + + else: + try: + client = ThriftClient(config['addr'], int(config['port']), username, password) + except WrongLogin: + print _("Login data is wrong.") + except NoConnection: + print _("Could not establish connection to %(addr)s:%(port)s." % {"addr": config['addr'], + "port": config['port']}) + except NoSSL: + print _("You need py-openssl to connect to this pyLoad core.") + + if interactive and command: print _("Interactive mode ignored since you passed some commands.") + + if client: + writeConfig(config) + cli = Cli(client, command) diff --git a/pyload/cli/Handler.py b/pyload/cli/Handler.py new file mode 100644 index 000000000..33e5dd8e6 --- /dev/null +++ b/pyload/cli/Handler.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# @author: RaNaN + +class Handler(object): + + def __init__(self, cli): + self.cli = cli + self.init() + + client = property(lambda self: self.cli.client) + input = property(lambda self: self.cli.input) + + + def init(self): + pass + + + def onChar(self, char): + pass + + + def onBackSpace(self): + pass + + + def onEnter(self, inp): + pass + + + def setInput(self, inp=""): + self.cli.setInput(inp) + + + def backspace(self): + self.cli.setInput(self.input[:-1]) + + + def renderBody(self, line): + """ gets the line where to render output and should return the line number below its content """ + return line + 1 diff --git a/pyload/cli/ManageFiles.py b/pyload/cli/ManageFiles.py new file mode 100644 index 000000000..3833b2c48 --- /dev/null +++ b/pyload/cli/ManageFiles.py @@ -0,0 +1,178 @@ +# -*- coding: utf-8 -*- +# @author: RaNaN + +from itertools import islice +from time import time + +from pyload.cli.Handler import Handler +from pyload.utils.printer import * + +from pyload.api import Destination, PackageData + + +class ManageFiles(Handler): + """ possibility to manage queue/collector """ + + def init(self): + self.target = Destination.Queue + self.pos = 0 #: position in queue + self.package = -1 #: choosen package + self.mode = "" #: move/delete/restart + + self.cache = None + self.links = None + self.time = 0 + + + def onChar(self, char): + if char in ("m", "d", "r"): + self.mode = char + self.setInput() + elif char == "p": + self.pos = max(0, self.pos - 5) + self.backspace() + elif char == "n": + self.pos += 5 + self.backspace() + + + def onBackSpace(self): + if not self.input and self.mode: + self.mode = "" + if not self.input and self.package > -1: + self.package = -1 + + + def onEnter(self, input): + if input == "0": + self.cli.reset() + elif self.package < 0 and self.mode: + # mode select + packs = self.parseInput(input) + if self.mode == "m": + [self.client.movePackage((self.target + 1) % 2, x) for x in packs] + elif self.mode == "d": + self.client.deletePackages(packs) + elif self.mode == "r": + [self.client.restartPackage(x) for x in packs] + + elif self.mode: + # edit links + links = self.parseInput(input, False) + + if self.mode == "d": + self.client.deleteFiles(links) + elif self.mode == "r": + map(self.client.restartFile, links) + + else: + # look into package + try: + self.package = int(input) + except Exception: + pass + + self.cache = None + self.links = None + self.pos = 0 + self.mode = "" + self.setInput() + + + def renderBody(self, line): + if self.package < 0: + println(line, white(_("Manage Packages:"))) + else: + println(line, white((_("Manage Links:")))) + line += 1 + + if self.mode: + if self.mode == "m": + println(line, _("What do you want to move?")) + elif self.mode == "d": + println(line, _("What do you want to delete?")) + elif self.mode == "r": + println(line, _("What do you want to restart?")) + + println(line + 1, "Enter single number, comma seperated numbers or ranges. eg. 1, 2, 3 or 1-3.") + line += 2 + else: + println(line, _("Choose what yout want to do or enter package number.")) + println(line + 1, ("%s - %%s, %s - %%s, %s - %%s" % (mag("d"), mag("m"), mag("r"))) % ( + _("delete"), _("move"), _("restart"))) + line += 2 + + if self.package < 0: + # print package info + pack = self.getPackages() + i = 0 + for value in islice(pack, self.pos, self.pos + 5): + try: + println(line, mag(str(value.pid)) + ": " + value.name) + line += 1 + i += 1 + except Exception: + pass + for _i in xrange(5 - i): + println(line, "") + line += 1 + else: + # print links info + pack = self.getLinks() + i = 0 + for value in islice(pack.links, self.pos, self.pos + 5): + try: + println(line, mag(value.fid) + ": %s | %s | %s" % ( + value.name, value.statusmsg, value.plugin)) + line += 1 + i += 1 + except Exception, e: + pass + for _i in xrange(5 - i): + println(line, "") + line += 1 + + println(line, mag("p") + _(" - previous") + " | " + mag("n") + _(" - next")) + println(line + 1, mag("0.") + _(" back to main menu")) + return line + 2 + + + def getPackages(self): + if self.cache and self.time + 2 < time(): + return self.cache + + if self.target == Destination.Queue: + data = self.client.getQueue() + else: + data = self.client.getCollector() + + self.cache = data + self.time = time() + + return data + + + def getLinks(self): + if self.links and self.time + 1 < time(): + return self.links + try: + data = self.client.getPackageData(self.package) + except Exception: + data = PackageData(links=[]) + self.links = data + self.time = time() + return data + + + def parseInput(self, inp, package=True): + inp = inp.strip() + if "-" in inp: + l, _, h = inp.partition("-") + r = xrange(int(l), int(h) + 1) + + if package: + return [p.pid for p in self.cache if p.pid in r] + return [l.lid for l in self.links.links if l.lid in r] + + else: + return [int(x) for x in inp.split(",")] diff --git a/pyload/cli/__init__.py b/pyload/cli/__init__.py new file mode 100644 index 000000000..a64fc0c0c --- /dev/null +++ b/pyload/cli/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- + +from pyload.cli.AddPackage import AddPackage +from pyload.cli.ManageFiles import ManageFiles diff --git a/pyload/config/Parser.py b/pyload/config/Parser.py new file mode 100644 index 000000000..d74238007 --- /dev/null +++ b/pyload/config/Parser.py @@ -0,0 +1,358 @@ +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +import shutil +import traceback + +from os.path import exists, join +from time import sleep + +from pyload.utils import chmod, encode, decode + + +CONF_VERSION = 1 + + +class ConfigParser(object): + """ + holds and manage the configuration + + current dict layout: + + { + + section: { + option: { + value: + type: + desc: + } + desc: + + } + """ + + def __init__(self): + """Constructor""" + self.config = {} #: the config values + self.plugin = {} #: the config for plugins + self.oldRemoteData = {} + + self.pluginCB = None #: callback when plugin config value is changed + + self.checkVersion() + + self.readConfig() + + + def checkVersion(self, n=0): + """determines if config need to be copied""" + try: + if not exists("pyload.conf"): + shutil.copy(join(pypath, "pyload", "config", "default.conf"), "pyload.conf") + + if not exists("plugin.conf"): + with open("plugin.conf", "wb") as f: + f.write("version: " + str(CONF_VERSION)) + + with open("pyload.conf", "rb") as f: + v = f.readline() + v = v[v.find(":") + 1:].strip() + + if not v or int(v) < CONF_VERSION: + shutil.copy(join(pypath, "pyload", "config", "default.conf"), "pyload.conf") + print "Old version of config was replaced" + + with open("plugin.conf", "rb") as f: + v = f.readline() + v = v[v.find(":") + 1:].strip() + + if not v or int(v) < CONF_VERSION: + with open("plugin.conf", "wb") as f: + f.write("version: " + str(CONF_VERSION)) + print "Old version of plugin-config replaced" + + except Exception: + if n >= 3: + raise + sleep(0.3) + self.checkVersion(n + 1) + + + def readConfig(self): + """reads the config file""" + self.config = self.parseConfig(join(pypath, "pyload", "config", "default.conf")) + self.plugin = self.parseConfig("plugin.conf") + + try: + homeconf = self.parseConfig("pyload.conf") + if "username" in homeconf['remote']: + if "password" in homeconf['remote']: + self.oldRemoteData = {"username": homeconf['remote']['username']['value'], + "password": homeconf['remote']['username']['value']} + del homeconf['remote']['password'] + del homeconf['remote']['username'] + self.updateValues(homeconf, self.config) + except Exception: + print "Config Warning" + traceback.print_exc() + + + def parseConfig(self, config): + """parses a given configfile""" + + with open(config) as f: + config = f.read() + + config = config.splitlines()[1:] + + conf = {} + + section, option, value, typ, desc = "", "", "", "", "" + + listmode = False + + for line in config: + comment = line.rfind("#") + if line.find(":", comment) < 0 > line.find("=", comment) and comment > 0 and line[comment - 1].isspace(): + line = line.rpartition("#") #: removes comments + if line[1]: + line = line[0] + else: + line = line[2] + + line = line.strip() + + try: + if line == "": + continue + elif line.endswith(":"): + section, none, desc = line[:-1].partition('-') + section = section.strip() + desc = desc.replace('"', "").strip() + conf[section] = {"desc": desc} + else: + if listmode: + if line.endswith("]"): + listmode = False + line = line.replace("]", "") + + value += [self.cast(typ, x.strip()) for x in line.split(",") if x] + + if not listmode: + conf[section][option] = {"desc": desc, + "type": typ, + "value": value, + "idx": len(conf[section])} + + + else: + content, none, value = line.partition("=") + + content, none, desc = content.partition(":") + + desc = desc.replace('"', "").strip() + + typ, none, option = content.strip().rpartition(" ") + + value = value.strip() + typ = typ.strip() + + if value.startswith("["): + if value.endswith("]"): + listmode = False + value = value[:-1] + else: + listmode = True + + value = [self.cast(typ, x.strip()) for x in value[1:].split(",") if x] + else: + value = self.cast(typ, value) + + if not listmode: + conf[section][option] = {"desc": desc, + "type": typ, + "value": value, + "idx": len(conf[section])} + + except Exception, e: + print "Config Warning" + traceback.print_exc() + + return conf + + + def updateValues(self, config, dest): + """sets the config values from a parsed config file to values in destination""" + + for section in config.iterkeys(): + if section in dest: + for option in config[section].iterkeys(): + if option in ("desc", "outline"): + continue + + if option in dest[section]: + dest[section][option]['value'] = config[section][option]['value'] + + # else: + # dest[section][option] = config[section][option] + + + # else: + # dest[section] = config[section] + + + def saveConfig(self, config, filename): + """saves config to filename""" + with open(filename, "wb") as f: + chmod(filename, 0600) + f.write("version: %i \n" % CONF_VERSION) + for section in config.iterkeys(): + f.write('\n%s - "%s":\n' % (section, config[section]['desc'])) + + for option, data in sorted(config[section].items(), key=lambda i: i[1]['idx'] if i[0] not in ("desc", "outline") else 0): + if option in ("desc", "outline"): + continue + + if isinstance(data['value'], list): + value = "[ \n" + for x in data['value']: + value += "\t\t" + str(x) + ",\n" + value += "\t\t]\n" + else: + if isinstance(data['value'], basestring): + value = data['value'] + "\n" + else: + value = str(data['value']) + "\n" + try: + f.write('\t%s %s : "%s" = %s' % (data['type'], option, data['desc'], value)) + except UnicodeEncodeError: + f.write('\t%s %s : "%s" = %s' % (data['type'], option, data['desc'], encode(value))) + + + def cast(self, typ, value): + """cast value to given format""" + if not isinstance(value, basestring): + return value + + elif typ == "int": + return int(value) + elif typ == "bool": + return value.lower() in ("1", "true", "on", "an", "yes") + elif typ == "time": + if not value: + value = "0:00" + if not ":" in value: + value += ":00" + return value + elif typ in ("str", "file", "folder"): + return encode(value) + else: + return value + + + def save(self): + """saves the configs to disk""" + self.saveConfig(self.config, "pyload.conf") + self.saveConfig(self.plugin, "plugin.conf") + + + def __getitem__(self, section): + """provides dictonary like access: c['section']['option']""" + return Section(self, section) + + + def get(self, section, option): + """get value""" + value = self.config[section][option]['value'] + return decode(value) + + + def set(self, section, option, value): + """set value""" + value = self.cast(self.config[section][option]['type'], value) + self.config[section][option]['value'] = value + self.save() + + + def getPlugin(self, plugin, option): + """gets a value for a plugin""" + value = self.plugin[plugin][option]['value'] + return encode(value) + + + def setPlugin(self, plugin, option, value): + """sets a value for a plugin""" + + value = self.cast(self.plugin[plugin][option]['type'], value) + + if self.pluginCB: self.pluginCB(plugin, option, value) + + self.plugin[plugin][option]['value'] = value + self.save() + + + def removeDeletedPlugins(self, plugins): + for name in self.plugin.keys(): + if not name in plugins: + del self.plugin[name] + + + def getMetaData(self, section, option): + """ get all config data for an option """ + return self.config[section][option] + + + def addPluginConfig(self, name, config, outline=""): + """adds config options with tuples (name, type, desc, default)""" + if name not in self.plugin: + conf = {"desc": name, + "outline": outline} + self.plugin[name] = conf + else: + conf = self.plugin[name] + conf['outline'] = outline + + for item in config: + if item[0] in conf: + conf[item[0]]['type'] = item[1] + conf[item[0]]['desc'] = item[2] + else: + conf[item[0]] = { + "desc": item[2], + "type": item[1], + "value": self.cast(item[1], item[3]), + "idx": len(conf) + } + + values = [x[0] for x in config] + ["desc", "outline"] + # delete old values + for item in conf.keys(): + if item not in values: + del conf[item] + + + def deleteConfig(self, name): + """Removes a plugin config""" + if name in self.plugin: + del self.plugin[name] + + +class Section(object): + """provides dictionary like access for configparser""" + + def __init__(self, parser, section): + """Constructor""" + self.parser = parser + self.section = section + + + def __getitem__(self, item): + """getitem""" + return self.parser.get(self.section, item) + + + def __setitem__(self, item, value): + """setitem""" + self.parser.set(self.section, item, value) diff --git a/pyload/config/Setup.py b/pyload/config/Setup.py new file mode 100644 index 000000000..e2f96c802 --- /dev/null +++ b/pyload/config/Setup.py @@ -0,0 +1,568 @@ +# -*- coding: utf-8 -*- +# @author: vuolter + +from __future__ import with_statement + +import __builtin__ + +import os +import sys + +from getpass import getpass +from os import chdir, makedirs, path +from subprocess import PIPE, call + +from pyload.network.JsEngine import JsEngine +from pyload.utils import get_console_encoding, load_translation, fs_join, versiontuple + + +class SetupAssistant(object): + """ pyLoads initial setup configuration assistant """ + + def __init__(self, config): + self.config = config + self.lang = "en" + self.stdin_encoding = get_console_encoding(sys.stdin.encoding) + + # Input shorthand for yes + self.yes = "y" + # Input shorthand for no + self.no = "n" + + + def start(self): + print + langs = sorted(self.config.getMetaData("general", "language")['type'].split(";")) + self.lang = self.ask(u"Choose setup language", "en", langs) + + load_translation("setup", self.lang) + + # Input shorthand for yes + self.yes = _("y") + # Input shorthand for no + self.no = _("n") + + # print + # print _("Would you like to configure pyLoad via Webinterface?") + # print _("You need a Browser and a connection to this PC for it.") + # viaweb = self.ask(_("Start initial webinterface for configuration?"), "y", bool=True) + # ... + + print + print + print _("## Welcome to the pyLoad Configuration Assistant ##") + print + print _("It will check your system and make a basic setup in order to run pyLoad.") + print + print _("The value in brackets [] always is the default value,") + print _("in case you don't want to change it or you are unsure what to choose, just hit enter.") + print _( + "Don't forget: You can always rerun this assistant with --setup or -s parameter, when you start pyload.py .") + print _("If you have any problems with this assistant hit STRG-C,") + print _("to abort and don't let him start with pyload.py automatically anymore.") + print + print + raw_input(_("When you are ready for system check, hit enter.")) + print + print + + basic, ssl, captcha, web, js = self.system_check() + print + print + + if not basic: + print _("You need pycurl, sqlite and python 2.5, 2.6 or 2.7 to run pyLoad.") + print _("Please correct this and re-run pyLoad.") + print + print _("Setup will now close.") + print + print + raw_input(_("Press Enter to exit.")) + return False + + raw_input(_("System check finished, hit enter to see your status report.")) + print + print + print _("## Status ##") + print + + avail = [] + if self.check_module("Crypto"): + avail.append(_("- container decrypting")) + if ssl: + avail.append(_("- ssl connection")) + if captcha: + avail.append(_("- automatic captcha decryption")) + if web: + avail.append(_("- webinterface")) + if js: + avail.append(_("- extended Click'N'Load")) + + if avail: + print _("AVAILABLE FEATURES:") + for feature in avail: + print feature + print + + if len(avail) < 5: + print _("MISSING FEATURES:") + + if not self.check_module("Crypto"): + print _("- no py-crypto available") + print _("You need this if you want to decrypt container files.") + print + + if not ssl: + print _("- no SSL available") + print _("This is needed if you want to establish a secure connection to core or webinterface.") + print _("If you only want to access locally to pyLoad ssl is not usefull.") + print + + if not captcha: + print _("- no Captcha Recognition available") + print _("Only needed for some hosters and as freeuser.") + print + + if not js: + print _("- no JavaScript engine found") + print _("You will need this for some Click'N'Load links. Install Spidermonkey, ossp-js, pyv8 or rhino") + print + + print + print _("You can abort the setup now and fix some dependicies if you want.") + else: + print _("NO MISSING FEATURES!") + + print + print + con = self.ask(_("Continue with setup?"), self.yes, bool=True) + + if not con: + return False + + print + print + print _("CURRENT CONFIG PATH: %s") % configdir + print + print _("NOTE: If you use pyLoad on a server or the home partition lives on an iternal flash it may be a good idea to change it.") + confpath = self.ask(_("Do you want to change the config path?"), self.no, bool=True) + if confpath: + print + self.conf_path() + print + + print + print _("Do you want to configure login data and basic settings?") + print _("This is recommend for first run.") + con = self.ask(_("Make basic setup?"), self.yes, bool=True) + + if con: + print + print + self.conf_basic() + + if ssl: + print + print _("Do you want to configure ssl?") + ssl = self.ask(_("Configure ssl?"), self.no, bool=True) + if ssl: + print + print + self.conf_ssl() + + if web: + print + print _("Do you want to configure webinterface?") + web = self.ask(_("Configure webinterface?"), self.yes, bool=True) + if web: + print + print + self.conf_web() + + print + print + print _("Setup finished successfully!") + print + print + raw_input(_("Hit enter to exit and restart pyLoad.")) + return True + + + def system_check(self): + """ make a systemcheck and return the results """ + import platform + + print _("## System Information ##") + print + print _("Platform: ") + platform.platform(aliased=True) + print _("OS: ") + platform.system() or "Unknown" + print _("Python: ") + sys.version.replace("\n", "") + print + print + + print _("## System Check ##") + print + + if (2, 5) > sys.version_info > (2, 7): + python = False + else: + python = True + + self.print_dep("python", python, false="NOT OK") + + curl = self.check_module("pycurl") + self.print_dep("pycurl", curl) + + sqlite = self.check_module("sqlite3") + self.print_dep("sqlite3", sqlite) + + basic = python and curl and sqlite + + print + + crypto = self.check_module("Crypto") + self.print_dep("pycrypto", crypto) + + ssl = self.check_module("OpenSSL") + self.print_dep("py-OpenSSL", ssl) + + print + + pil = self.check_module("Image") + self.print_dep("py-imaging", pil) + + if os.name == "nt": + tesser = self.check_prog([path.join(pypath, "tesseract", "tesseract.exe"), "-v"]) + else: + tesser = self.check_prog(["tesseract", "-v"]) + + self.print_dep("tesseract", tesser) + + captcha = pil and tesser + + print + + try: + import jinja2 + + v = jinja2.__version__ + if v and versiontuple(v) < (2, 5, 0): + jinja = False + else: + jinja = True + except Exception: + jinja = False + jinja_error = "MISSING" + else: + jinja_error = "NOT OK" + + self.print_dep("jinja2", jinja, false=jinja_error) + + beaker = self.check_module("beaker") + self.print_dep("beaker", beaker) + + bjoern = self.check_module("bjoern") + self.print_dep("bjoern", bjoern) + + web = sqlite and beaker + + js = bool(JsEngine.find()) + self.print_dep(_("JS engine"), js) + + if not python: + print + print + if sys.version_info > (2, 7): + print _("WARNING: Your python version is too NEW!") + print _("Please use Python version 2.6/2.7 .") + else: + print _("WARNING: Your python version is too OLD!") + print _("Please use at least Python version 2.5 .") + + if not jinja and jinja_error == "NOT OK": + print + print + print _("WARNING: Your installed jinja2 version %s is too OLD!") % jinja2.__version__ + print _("You can safely continue but if the webinterface is not working,") + print _("please upgrade or uninstall it, because pyLoad self-includes jinja2 libary.") + + return basic, ssl, captcha, web, js + + + def conf_basic(self): + print _("## Basic Setup ##") + + print + print _("The following logindata is valid for CLI and webinterface.") + + from pyload.database import DatabaseBackend + + db = DatabaseBackend(None) + db.setup() + print _("NOTE: Consider a password of 10 or more symbols if you expect to access to your local network from outside (ex. internet).") + print + username = self.ask(_("Username"), "User") + password = self.ask("", "", password=True) + db.addUser(username, password) + db.shutdown() + + print + print + print _("External clients (GUI, CLI or other) need remote access to work over the network.") + print _("However, if you only want to use the webinterface you may disable it to save ram.") + self.config.set("remote", "activated", self.ask(_("Enable remote access"), self.no, bool=True)) + + print + langs = sorted(self.config.getMetaData("general", "language")['type'].split(";")) + self.config.set("general", "language", self.ask(_("Choose system language"), self.lang, langs)) + + print + self.config.set("general", "download_folder", self.ask(_("Download folder"), "Downloads")) + print + self.config.set("download", "max_downloads", self.ask(_("Max parallel downloads"), "3")) + print + reconnect = self.ask(_("Use Reconnect?"), self.no, bool=True) + self.config.set("reconnect", "activated", reconnect) + if reconnect: + self.config.set("reconnect", "method", self.ask(_("Reconnect script location"), "./reconnect.sh")) + + + def conf_web(self): + print _("## Webinterface Setup ##") + + print + print _("Listen address, if you use 127.0.0.1 or localhost, the webinterface will only accessible locally.") + self.config.set("webui", "host", self.ask(_("Address"), "0.0.0.0")) + self.config.set("webui", "port", self.ask(_("Port"), "8000")) + print + print _("pyLoad offers several server backends, now following a short explanation.") + print "- auto:", _("Automatically choose the best webserver for your platform.") + print "- builtin:", _("First choice if you plan to use pyLoad just for you.") + print "- threaded:", _("Support SSL connection and can serve simultaneously more client flawlessly.") + print "- fastcgi:", _( + "Can be used by apache, lighttpd, etc.; needs to be properly configured before.") + if os.name != "nt": + print "- lightweight:", _("Very fast alternative to builtin; requires libev and bjoern packages.") + + print + print _("NOTE: In some rare cases the builtin server not works correctly, so if you have troubles with the web interface") + print _("run this setup assistant again and change the builtin server to the threaded.") + + if os.name == "nt": + servers = ["auto", "builtin", "threaded", "fastcgi"] + else: + servers = ["auto", "builtin", "threaded", "fastcgi", "lightweight"] + + self.config.set("webui", "server", self.ask(_("Choose webserver"), "auto", servers)) + + + def conf_ssl(self): + print _("## SSL Setup ##") + print + print _("Execute these commands from pyLoad config folder to make ssl certificates:") + print + print "openssl genrsa -out ssl.key 1024" + print "openssl req -new -key ssl.key -out ssl.csr" + print "openssl req -days 36500 -x509 -key ssl.key -in ssl.csr > ssl.crt " + print + print _("If you're done and everything went fine, you can activate ssl now.") + + ssl = self.ask(_("Activate SSL?"), self.yes, bool=True) + self.config.set("remote", "ssl", ssl) + self.config.set("webui", "ssl", ssl) + + + def set_user(self): + load_translation("setup", self.config.get("general", "language")) + + from pyload.database import DatabaseBackend + + db = DatabaseBackend(None) + db.setup() + + noaction = True + try: + while True: + print _("Select action") + print _("1 - Create/Edit user") + print _("2 - List users") + print _("3 - Remove user") + print _("4 - Quit") + action = raw_input("1/2/3/4: ") + if not action in ("1", "2", "3", "4"): + continue + elif action == "1": + print + username = self.ask(_("Username"), "User") + password = self.ask("", "", password=True) + db.addUser(username, password) + noaction = False + elif action == "2": + print + print _("Users") + print "-----" + users = db.listUsers() + noaction = False + for user in users: + print user + print "-----" + print + elif action == "3": + print + username = self.ask(_("Username"), "") + if username: + db.removeUser(username) + noaction = False + elif action == "4": + break + finally: + if not noaction: + db.shutdown() + + + def set_configdir(self, configdir, persistent=False): + dirname = path.abspath(configdir) + try: + if not path.exists(dirname): + makedirs(dirname, 0700) + + chdir(dirname) + + if persistent: + c = path.join(rootdir, "config", "configdir") + if not path.exists(c): + makedirs(c, 0700) + + with open(c, "wb") as f: + f.write(dirname) + + except IOError: + return False + + else: + __builtin__.configdir = dirname + return dirname #: return always abspath + + + def conf_path(self): + print _("Setting new config path.") + print _("NOTE: Current configuration will not be transfered!") + + while True: + confdir = self.ask(_("CONFIG PATH"), configdir) + confpath = self.set_configdir(confdir) + print + if not confpath: + print _("Failed to change the current CONFIG PATH!") + print + else: + print _("CONFIG PATH successfully changed to: %s") % configdir + break + + + def print_dep(self, name, value, false="MISSING", true="OK"): + """ Print Status of dependency """ + if value and isinstance(value, basestring): + info = ", ".join(value) + else: + info = "" + + print "%(dep)-12s %(bool)s (%(info)s)" % {'dep': name + ':', + 'bool': _(true if value else false).upper(), + 'info': info} + + + def check_module(self, module): + try: + __import__(module) + return True + except Exception: + return False + + + def check_prog(self, command): + pipe = PIPE + try: + call(command, stdout=pipe, stderr=pipe) + return True + except Exception: + return False + + + def ask(self, qst, default, answers=[], bool=False, password=False): + """ produce one line to asking for input """ + if answers: + info = "(" + + for i, answer in enumerate(answers): + info += (", " if i != 0 else "") + str((answer == default and "[%s]" % answer) or answer) + + info += ")" + elif bool: + if default == self.yes: + info = "([%s]/%s)" % (self.yes, self.no) + else: + info = "(%s/[%s])" % (self.yes, self.no) + else: + info = "[%s]" % default + + if password: + p1 = True + p2 = False + pwlen = 8 + while p1 != p2: + sys.stdout.write(_("Password: ")) + p1 = getpass("").strip("\n\r") + + if len(p1) < pwlen: + print + print _("Password too short! Use at least %s symbols." % pwlen) + print + continue + elif not p1.isalnum(): + print + print _("Password must be alphanumeric.") + print + continue + + sys.stdout.write(_("Password (again): ")) + p2 = getpass("").strip("\n\r") + + if p1 == p2: + print + if self.ask(_("Show password?"), self.no, bool=True): + print + print _("Your Password is: %s") % p1 + return p1 + else: + print + print _("Passwords did not match.") + + while True: + try: + input = raw_input(qst + " %s: " % info) + except KeyboardInterrupt: + print "\nSetup interrupted" + sys.exit() + + input = input.decode(self.stdin_encoding) + + if input.strip() == "": + input = default + + if bool: + # yes, true, t are inputs for booleans with value true + if input.lower().strip() in [self.yes, _("yes"), _("true"), _("t"), "yes"]: + return True + # no, false, f are inputs for booleans with value false + elif input.lower().strip() in [self.no, _("no"), _("false"), _("f"), "no"]: + return False + else: + print _("Invalid Input") + print + continue + + if not answers or input in answers: + return input + else: + print _("Invalid Input") diff --git a/pyload/config/__init__.py b/pyload/config/__init__.py new file mode 100644 index 000000000..40a96afc6 --- /dev/null +++ b/pyload/config/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/pyload/config/default.conf b/pyload/config/default.conf new file mode 100644 index 000000000..c70d4d71a --- /dev/null +++ b/pyload/config/default.conf @@ -0,0 +1,76 @@ +version: 1 + +remote - "Remote": + bool activated : "Activated" = True + int port : "Port" = 7227 + ip listenaddr : "Address" = 0.0.0.0 + bool nolocalauth : "No authentication on local connections" = True + +ssl - "SSL": + bool activated : "Activated" = False + file cert : "SSL Certificate" = ssl.crt + file key : "SSL Key" = ssl.key + +webui - "Web User Interface": + bool activated : "Activated" = True + auto;threaded;fastcgi;lightweight server : "Server" = auto + bool https : "Use HTTPS" = False + ip host : "IP" = 0.0.0.0 + int port : "Port" = 8001 + Default;Dark;Flat;Next theme : "Theme" = Next + str prefix : "Path Prefix" = None + +log - "Log": + bool file_log : "File Log" = True + folder log_folder : "Folder" = Logs + int log_count : "Count" = 5 + int log_size : "Size in KB" = 100 + bool log_rotate : "Log Rotate" = True + bool color_console : "Colored console" = True + label;line console_mode : "Colored console mode" = line + +general - "General": + en; language : "Language" = en + folder download_folder : "Download Folder" = Downloads + bool debug_mode : "Debug Mode" = False + int min_free_space : "Min Free Space in MB" = 200 + bool folder_per_package : "Create folder for each package" = True + int renice : "CPU Priority" = 0 + auto;common;pyv8;node;rhino;jsc jsengine : "JS Engine" = auto + +download - "Download": + int chunks : "Max connections for one download" = 3 + int max_downloads : "Max Parallel Downloads" = 3 + int max_speed : "Max Download Speed in KB/s" = -1 + bool limit_speed : "Limit Download Speed" = False + str interface : "Download interface to bind (ip or Name)" = None + bool ipv6 : "Allow IPv6" = False + bool skip_existing : "Skip already existing files" = False + +permission - "Permissions": + bool change_user : "Change user of running process" = False + str user : "Username" = user + str folder : "Folder Permission mode" = 0755 + bool change_file : "Change file mode of downloads" = False + str file : "Filemode for Downloads" = 0644 + bool change_group : "Change group of running process" = False + str group : "Groupname" = users + bool change_dl : "Change Group and User of Downloads" = False + +reconnect - "Reconnect": + bool activated : "Activated" = False + str method : "Method" = None + time startTime : "Start" = 0:00 + time endTime : "End" = 0:00 + +downloadTime - "Download Time": + time start : "Start" = 0:00 + time end : "End" = 0:00 + +proxy - "Proxy": + str address : "Address" = "localhost" + int port : "Port" = 7070 + http;socks4;socks5 type : "Protocol" = http + str username : "Username" = None + password password : "Password" = None + bool proxy : "Use Proxy" = False diff --git a/pyload/database/Backend.py b/pyload/database/Backend.py new file mode 100644 index 000000000..9acca287c --- /dev/null +++ b/pyload/database/Backend.py @@ -0,0 +1,331 @@ +# -*- coding: utf-8 -*- +# @author: RaNaN, mkaay + +from __future__ import with_statement + +try: + from pysqlite2 import dbapi2 as sqlite3 +except Exception: + import sqlite3 + +import shutil +import threading +import traceback + +from Queue import Queue +from os import remove +from os.path import exists + +from pyload.utils import chmod + + +DB_VERSION = 4 + + +class style(object): + db = None + + + @classmethod + def setDB(cls, db): + cls.db = db + + + @classmethod + def inner(cls, f): + + + @staticmethod + def x(*args, **kwargs): + if cls.db: + return f(cls.db, *args, **kwargs) + + return x + + + @classmethod + def queue(cls, f): + + + @staticmethod + def x(*args, **kwargs): + if cls.db: + return cls.db.queue(f, *args, **kwargs) + + return x + + + @classmethod + def async(cls, f): + + + @staticmethod + def x(*args, **kwargs): + if cls.db: + return cls.db.async(f, *args, **kwargs) + return x + + +class DatabaseJob(object): + + def __init__(self, f, *args, **kwargs): + self.done = threading.Event() + + self.f = f + self.args = args + self.kwargs = kwargs + + self.result = None + self.exception = False + + # import inspect + # self.frame = inspect.currentframe() + + + def __repr__(self): + from os.path import basename + + frame = self.frame.f_back + output = "" + for _i in xrange(5): + output += "\t%s:%s, %s\n" % (basename(frame.f_code.co_filename), frame.f_lineno, frame.f_code.co_name) + frame = frame.f_back + del frame + del self.frame + + return "DataBase Job %s:%s\n%sResult: %s" % (self.f.__name__, self.args[1:], output, self.result) + + + def processJob(self): + try: + self.result = self.f(*self.args, **self.kwargs) + except Exception, e: + traceback.print_exc() + try: + print "Database Error @", self.f.__name__, self.args[1:], self.kwargs, e + except Exception: + pass + + self.exception = e + finally: + self.done.set() + + + def wait(self): + self.done.wait() + + +class DatabaseBackend(Thread): + subs = [] + + + def __init__(self, core): + Thread.__init__(self) + self.setDaemon(True) + self.core = core + + self.jobs = Queue() + + self.setuplock = threading.Event() + + style.setDB(self) + + + def setup(self): + self.start() + self.setuplock.wait() + + + def run(self): + """main loop, which executes commands""" + convert = self._checkVersion() #: returns None or current version + + self.conn = sqlite3.connect("files.db") + chmod("files.db", 0600) + + self.c = self.conn.cursor() #: compatibility + + if convert is not None: + self._convertDB(convert) + + self._createTables() + self._migrateUser() + + self.conn.commit() + + self.setuplock.set() + + while True: + j = self.jobs.get() + if j == "quit": + self.c.close() + self.conn.close() + break + j.processJob() + + + @style.queue + def shutdown(self): + self.conn.commit() + self.jobs.put("quit") + + + def _checkVersion(self): + """ check db version and delete it if needed""" + if not exists("files.version"): + with open("files.version", "wb") as f: + f.write(str(DB_VERSION)) + return + + with open("files.version", "rb") as f: + v = int(f.read().strip() or 0) + + if v < DB_VERSION: + if v < 2: + try: + self.manager.core.log.warning(_("Filedatabase was deleted due to incompatible version.")) + except Exception: + print "Filedatabase was deleted due to incompatible version." + reshutil.move("files.version") + shutil.move("files.db", "files.backup.db") + + with open("files.version", "wb") as f: + f.write(str(DB_VERSION)) + + return v + + + def _convertDB(self, v): + try: + getattr(self, "_convertV%i" % v)() + except Exception: + try: + self.core.log.error(_("Filedatabase could NOT be converted.")) + except Exception: + print "Filedatabase could NOT be converted." + + # convert scripts start --------------------------------------------------- + + + def _convertV2(self): + self.c.execute( + 'CREATE TABLE IF NOT EXISTS "storage" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "identifier" TEXT NOT NULL, "key" TEXT NOT NULL, "value" TEXT DEFAULT "")') + try: + self.manager.core.log.info(_("Database was converted from v2 to v3.")) + except Exception: + print "Database was converted from v2 to v3." + self._convertV3() + + + def _convertV3(self): + self.c.execute( + 'CREATE TABLE IF NOT EXISTS "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "name" TEXT NOT NULL, "email" TEXT DEFAULT "" NOT NULL, "password" TEXT NOT NULL, "role" INTEGER DEFAULT 0 NOT NULL, "permission" INTEGER DEFAULT 0 NOT NULL, "template" TEXT DEFAULT "default" NOT NULL)') + try: + self.manager.core.log.info(_("Database was converted from v3 to v4.")) + except Exception: + print "Database was converted from v3 to v4." + + # convert scripts end ----------------------------------------------------- + + + def _createTables(self): + """create tables for database""" + + self.c.execute( + 'CREATE TABLE IF NOT EXISTS "packages" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "name" TEXT NOT NULL, "folder" TEXT, "password" TEXT DEFAULT "", "site" TEXT DEFAULT "", "queue" INTEGER DEFAULT 0 NOT NULL, "packageorder" INTEGER DEFAULT 0 NOT NULL)') + self.c.execute( + 'CREATE TABLE IF NOT EXISTS "links" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "url" TEXT NOT NULL, "name" TEXT, "size" INTEGER DEFAULT 0 NOT NULL, "status" INTEGER DEFAULT 3 NOT NULL, "plugin" TEXT DEFAULT "BasePlugin" NOT NULL, "error" TEXT DEFAULT "", "linkorder" INTEGER DEFAULT 0 NOT NULL, "package" INTEGER DEFAULT 0 NOT NULL, FOREIGN KEY(package) REFERENCES packages(id))') + self.c.execute('CREATE INDEX IF NOT EXISTS "pIdIndex" ON links(package)') + self.c.execute( + 'CREATE TABLE IF NOT EXISTS "storage" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "identifier" TEXT NOT NULL, "key" TEXT NOT NULL, "value" TEXT DEFAULT "")') + self.c.execute( + 'CREATE TABLE IF NOT EXISTS "users" ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "name" TEXT NOT NULL, "email" TEXT DEFAULT "" NOT NULL, "password" TEXT NOT NULL, "role" INTEGER DEFAULT 0 NOT NULL, "permission" INTEGER DEFAULT 0 NOT NULL, "template" TEXT DEFAULT "default" NOT NULL)') + + self.c.execute('CREATE VIEW IF NOT EXISTS "pstats" AS \ + SELECT p.id AS id, SUM(l.size) AS sizetotal, COUNT(l.id) AS linkstotal, linksdone, sizedone\ + FROM packages p JOIN links l ON p.id = l.package LEFT OUTER JOIN\ + (SELECT p.id AS id, COUNT(*) AS linksdone, SUM(l.size) AS sizedone \ + FROM packages p JOIN links l ON p.id = l.package AND l.status IN (0, 4, 13) GROUP BY p.id) s ON s.id = p.id \ + GROUP BY p.id') + + # try to lower ids + self.c.execute('SELECT max(id) FROM LINKS') + fid = self.c.fetchone()[0] + fid = int(fid) if fid else 0 + self.c.execute('UPDATE SQLITE_SEQUENCE SET seq=? WHERE name=?', (fid, "links")) + + self.c.execute('SELECT max(id) FROM packages') + pid = self.c.fetchone()[0] + pid = int(pid) if pid else 0 + self.c.execute('UPDATE SQLITE_SEQUENCE SET seq=? WHERE name=?', (pid, "packages")) + + self.c.execute('VACUUM') + + + def _migrateUser(self): + if exists("pyload.db"): + try: + self.core.log.info(_("Converting old Django DB")) + except Exception: + print "Converting old Django DB" + conn = sqlite3.connect('pyload.db') + c = conn.cursor() + c.execute("SELECT username, password, email FROM auth_user WHERE is_superuser") + users = [] + for r in c: + pw = r[1].split("$") + users.append((r[0], pw[1] + pw[2], r[2])) + c.close() + conn.close() + + self.c.executemany("INSERT INTO users(name, password, email) VALUES (?, ?, ?)", users) + shutil.move("pyload.db", "pyload.old.db") + + + def createCursor(self): + return self.conn.cursor() + + + @style.async + def commit(self): + self.conn.commit() + + + @style.queue + def syncSave(self): + self.conn.commit() + + + @style.async + def rollback(self): + self.conn.rollback() + + + def async(self, f, *args, **kwargs): + args = (self,) + args + job = DatabaseJob(f, *args, **kwargs) + self.jobs.put(job) + + + def queue(self, f, *args, **kwargs): + args = (self,) + args + job = DatabaseJob(f, *args, **kwargs) + self.jobs.put(job) + job.wait() + return job.result + + + @classmethod + def registerSub(cls, klass): + cls.subs.append(klass) + + + @classmethod + def unregisterSub(cls, klass): + cls.subs.reshutil.move(klass) + + + def __getattr__(self, attr): + for sub in DatabaseBackend.subs: + if hasattr(sub, attr): + return getattr(sub, attr) diff --git a/pyload/database/File.py b/pyload/database/File.py new file mode 100644 index 000000000..db4b13a52 --- /dev/null +++ b/pyload/database/File.py @@ -0,0 +1,986 @@ +# -*- coding: utf-8 -*- +# @author: RaNaN, mkaay + +import threading + +from pyload.utils import formatSize, lock +from pyload.manager.Event import InsertEvent, ReloadAllEvent, RemoveEvent, UpdateEvent +from pyload.datatype.Package import PyPackage +from pyload.datatype.File import PyFile +from pyload.database import DatabaseBackend, style + +try: + from pysqlite2 import dbapi2 as sqlite3 +except Exception: + import sqlite3 + + +class FileHandler(object): + """Handles all request made to obtain information, + modify status or other request for links or packages""" + + def __init__(self, core): + """Constructor""" + self.core = core + + # translations + self.statusMsg = [_("finished"), _("offline"), _("online"), _("queued"), _("skipped"), _("waiting"), + _("temp. offline"), _("starting"), _("failed"), _("aborted"), _("decrypting"), _("custom"), + _("downloading"), _("processing"), _("unknown")] + + self.cache = {} #: holds instances for files + self.packageCache = {} #: same for packages + #@TODO: purge the cache + + self.jobCache = {} + + self.lock = threading.RLock() #@TODO: should be a Lock w/o R + # self.lock._Verbose__verbose = True + + self.filecount = -1 #: if an invalid value is set get current value from db + self.queuecount = -1 #: number of package to be loaded + self.unchanged = False #: determines if any changes was made since last call + + self.db = self.core.db + + + def change(func): + + + def new(*args): + args[0].unchanged = False + args[0].filecount = -1 + args[0].queuecount = -1 + args[0].jobCache = {} + return func(*args) + + return new + + + #-------------------------------------------------------------------------- + + def save(self): + """saves all data to backend""" + self.db.commit() + + + #-------------------------------------------------------------------------- + + def syncSave(self): + """saves all data to backend and waits until all data are written""" + pyfiles = self.cache.values() + for pyfile in pyfiles: + pyfile.sync() + + pypacks = self.packageCache.values() + for pypack in pypacks: + pypack.sync() + + self.db.syncSave() + + + @lock + def getCompleteData(self, queue=1): + """gets a complete data representation""" + + data = self.db.getAllLinks(queue) + packs = self.db.getAllPackages(queue) + + data.update([(x.id, x.toDbDict()[x.id]) for x in self.cache.values()]) + + for x in self.packageCache.itervalues(): + if x.queue != queue or x.id not in packs: + continue + packs[x.id].update(x.toDict()[x.id]) + + for key, value in data.iteritems(): + if value['package'] in packs: + packs[value['package']]['links'][key] = value + + return packs + + + @lock + def getInfoData(self, queue=1): + """gets a data representation without links""" + + packs = self.db.getAllPackages(queue) + for x in self.packageCache.itervalues(): + if x.queue != queue or x.id not in packs: + continue + packs[x.id].update(x.toDict()[x.id]) + + return packs + + + @lock + @change + def addLinks(self, urls, package): + """adds links""" + + self.core.addonManager.dispatchEvent("links-added", urls, package) + + data = self.core.pluginManager.parseUrls(urls) + + self.db.addLinks(data, package) + self.core.threadManager.createInfoThread(data, package) + + #@TODO: change from reloadAll event to package update event + self.core.pullManager.addEvent(ReloadAllEvent("collector")) + + + #-------------------------------------------------------------------------- + + @lock + @change + def addPackage(self, name, folder, queue=0): + """adds a package, default to link collector""" + lastID = self.db.addPackage(name, folder, queue) + p = self.db.getPackage(lastID) + e = InsertEvent("pack", lastID, p.order, "collector" if not queue else "queue") + self.core.pullManager.addEvent(e) + return lastID + + + #-------------------------------------------------------------------------- + + @lock + @change + def deletePackage(self, id): + """delete package and all contained links""" + + p = self.getPackage(id) + if not p: + if id in self.packageCache: + del self.packageCache[id] + return + + oldorder = p.order + queue = p.queue + + e = RemoveEvent("pack", id, "collector" if not p.queue else "queue") + + pyfiles = self.cache.values() + + for pyfile in pyfiles: + if pyfile.packageid == id: + pyfile.abortDownload() + pyfile.release() + + self.db.deletePackage(p) + self.core.pullManager.addEvent(e) + self.core.addonManager.dispatchEvent("package-deleted", id) + + if id in self.packageCache: + del self.packageCache[id] + + packs = self.packageCache.values() + for pack in packs: + if pack.queue == queue and pack.order > oldorder: + pack.order -= 1 + pack.notifyChange() + + + #-------------------------------------------------------------------------- + + @lock + @change + def deleteLink(self, id): + """deletes links""" + + f = self.getFile(id) + if not f: + return None + + pid = f.packageid + e = RemoveEvent("file", id, "collector" if not f.package().queue else "queue") + + oldorder = f.order + + if id in self.core.threadManager.processingIds(): + self.cache[id].abortDownload() + + if id in self.cache: + del self.cache[id] + + self.db.deleteLink(f) + + self.core.pullManager.addEvent(e) + + p = self.getPackage(pid) + if not len(p.getChildren()): + p.delete() + + pyfiles = self.cache.values() + for pyfile in pyfiles: + if pyfile.packageid == pid and pyfile.order > oldorder: + pyfile.order -= 1 + pyfile.notifyChange() + + + #-------------------------------------------------------------------------- + + def releaseLink(self, id): + """removes pyfile from cache""" + if id in self.cache: + del self.cache[id] + + + #-------------------------------------------------------------------------- + + def releasePackage(self, id): + """removes package from cache""" + if id in self.packageCache: + del self.packageCache[id] + + + #-------------------------------------------------------------------------- + + def updateLink(self, pyfile): + """updates link""" + self.db.updateLink(pyfile) + + e = UpdateEvent("file", pyfile.id, "collector" if not pyfile.package().queue else "queue") + self.core.pullManager.addEvent(e) + + + #-------------------------------------------------------------------------- + + def updatePackage(self, pypack): + """updates a package""" + self.db.updatePackage(pypack) + + e = UpdateEvent("pack", pypack.id, "collector" if not pypack.queue else "queue") + self.core.pullManager.addEvent(e) + + + #-------------------------------------------------------------------------- + + def getPackage(self, id): + """return package instance""" + + if id in self.packageCache: + return self.packageCache[id] + else: + return self.db.getPackage(id) + + + #-------------------------------------------------------------------------- + + def getPackageData(self, id): + """returns dict with package information""" + pack = self.getPackage(id) + + if not pack: + return None + + pack = pack.toDict()[id] + + data = self.db.getPackageData(id) + + tmplist = [] + + cache = self.cache.values() + for x in cache: + if int(x.toDbDict()[x.id]['package']) == int(id): + tmplist.append((x.id, x.toDbDict()[x.id])) + data.update(tmplist) + + pack['links'] = data + + return pack + + + #-------------------------------------------------------------------------- + + def getFileData(self, id): + """returns dict with file information""" + if id in self.cache: + return self.cache[id].toDbDict() + + return self.db.getLinkData(id) + + + #-------------------------------------------------------------------------- + + def getFile(self, id): + """returns pyfile instance""" + if id in self.cache: + return self.cache[id] + else: + return self.db.getFile(id) + + + #-------------------------------------------------------------------------- + + @lock + def getJob(self, occ): + """get suitable job""" + + #@TODO: clean mess + #@TODO: improve selection of valid jobs + + if occ in self.jobCache: + if self.jobCache[occ]: + id = self.jobCache[occ].pop() + if id == "empty": + pyfile = None + self.jobCache[occ].append("empty") + else: + pyfile = self.getFile(id) + else: + jobs = self.db.getJob(occ) + jobs.reverse() + if not jobs: + self.jobCache[occ].append("empty") + pyfile = None + else: + self.jobCache[occ].extend(jobs) + pyfile = self.getFile(self.jobCache[occ].pop()) + + else: + self.jobCache = {} #: better not caching to much + jobs = self.db.getJob(occ) + jobs.reverse() + self.jobCache[occ] = jobs + + if not jobs: + self.jobCache[occ].append("empty") + pyfile = None + else: + pyfile = self.getFile(self.jobCache[occ].pop()) + + #@TODO: maybe the new job has to be approved... + + # pyfile = self.getFile(self.jobCache[occ].pop()) + return pyfile + + + @lock + def getDecryptJob(self): + """return job for decrypting""" + if "decrypt" in self.jobCache: + return None + + plugins = self.core.pluginManager.crypterPlugins.keys() + self.core.pluginManager.containerPlugins.keys() + plugins = str(tuple(plugins)) + + jobs = self.db.getPluginJob(plugins) + if jobs: + return self.getFile(jobs[0]) + else: + self.jobCache['decrypt'] = "empty" + return None + + + def getFileCount(self): + """returns number of files""" + + if self.filecount == -1: + self.filecount = self.db.filecount(1) + + return self.filecount + + + def getQueueCount(self, force=False): + """number of files that have to be processed""" + if self.queuecount == -1 or force: + self.queuecount = self.db.queuecount(1) + + return self.queuecount + + + def checkAllLinksFinished(self): + """checks if all files are finished and dispatch event""" + + if not self.getQueueCount(True): + self.core.addonManager.dispatchEvent("all_downloads-finished") + self.core.log.debug("All downloads finished") + return True + + return False + + + def checkAllLinksProcessed(self, fid): + """checks if all files was processed and pyload would idle now, needs fid which will be ignored when counting""" + + # reset count so statistic will update (this is called when dl was processed) + self.resetCount() + + if not self.db.processcount(1, fid): + self.core.addonManager.dispatchEvent("all_downloads-processed") + self.core.log.debug("All downloads processed") + return True + + return False + + + def resetCount(self): + self.queuecount = -1 + + + @lock + @change + def restartPackage(self, id): + """restart package""" + pyfiles = self.cache.values() + for pyfile in pyfiles: + if pyfile.packageid == id: + self.restartFile(pyfile.id) + + self.db.restartPackage(id) + + if id in self.packageCache: + self.packageCache[id].setFinished = False + + e = UpdateEvent("pack", id, "collector" if not self.getPackage(id).queue else "queue") + self.core.pullManager.addEvent(e) + + + @lock + @change + def restartFile(self, id): + """ restart file""" + if id in self.cache: + self.cache[id].status = 3 + self.cache[id].name = self.cache[id].url + self.cache[id].error = "" + self.cache[id].abortDownload() + + self.db.restartFile(id) + + e = UpdateEvent("file", id, "collector" if not self.getFile(id).package().queue else "queue") + self.core.pullManager.addEvent(e) + + + @lock + @change + def setPackageLocation(self, id, queue): + """push package to queue""" + + p = self.db.getPackage(id) + oldorder = p.order + + e = RemoveEvent("pack", id, "collector" if not p.queue else "queue") + self.core.pullManager.addEvent(e) + + self.db.clearPackageOrder(p) + + p = self.db.getPackage(id) + + p.queue = queue + self.db.updatePackage(p) + + self.db.reorderPackage(p, -1, True) + + packs = self.packageCache.values() + for pack in packs: + if pack.queue != queue and pack.order > oldorder: + pack.order -= 1 + pack.notifyChange() + + self.db.commit() + self.releasePackage(id) + p = self.getPackage(id) + + e = InsertEvent("pack", id, p.order, "collector" if not p.queue else "queue") + self.core.pullManager.addEvent(e) + + + @lock + @change + def reorderPackage(self, id, position): + p = self.getPackage(id) + + e = RemoveEvent("pack", id, "collector" if not p.queue else "queue") + self.core.pullManager.addEvent(e) + self.db.reorderPackage(p, position) + + packs = self.packageCache.values() + for pack in packs: + if pack.queue != p.queue or pack.order < 0 or pack == p: + continue + if p.order > position: + if position <= pack.order < p.order: + pack.order += 1 + pack.notifyChange() + elif p.order < position: + if position >= pack.order > p.order: + pack.order -= 1 + pack.notifyChange() + + p.order = position + self.db.commit() + + e = InsertEvent("pack", id, position, "collector" if not p.queue else "queue") + self.core.pullManager.addEvent(e) + + + @lock + @change + def reorderFile(self, id, position): + f = self.getFileData(id) + f = f[id] + + e = RemoveEvent("file", id, "collector" if not self.getPackage(f['package']).queue else "queue") + self.core.pullManager.addEvent(e) + + self.db.reorderLink(f, position) + + pyfiles = self.cache.values() + for pyfile in pyfiles: + if pyfile.packageid != f['package'] or pyfile.order < 0: + continue + if f['order'] > position: + if position <= pyfile.order < f['order']: + pyfile.order += 1 + pyfile.notifyChange() + elif f['order'] < position: + if position >= pyfile.order > f['order']: + pyfile.order -= 1 + pyfile.notifyChange() + + if id in self.cache: + self.cache[id].order = position + + self.db.commit() + + e = InsertEvent("file", id, position, "collector" if not self.getPackage(f['package']).queue else "queue") + self.core.pullManager.addEvent(e) + + + @change + def updateFileInfo(self, data, pid): + """ updates file info (name, size, status, url)""" + ids = self.db.updateLinkInfo(data) + e = UpdateEvent("pack", pid, "collector" if not self.getPackage(pid).queue else "queue") + self.core.pullManager.addEvent(e) + + + def checkPackageFinished(self, pyfile): + """ checks if package is finished and calls AddonManager """ + + ids = self.db.getUnfinished(pyfile.packageid) + if not ids or (pyfile.id in ids and len(ids) == 1): + if not pyfile.package().setFinished: + self.core.log.info(_("Package finished: %s") % pyfile.package().name) + self.core.addonManager.packageFinished(pyfile.package()) + pyfile.package().setFinished = True + + + def reCheckPackage(self, pid): + """ recheck links in package """ + data = self.db.getPackageData(pid) + + urls = [] + + for pyfile in data.itervalues(): + if pyfile['status'] not in (0, 12, 13): + urls.append((pyfile['url'], pyfile['plugin'])) + + self.core.threadManager.createInfoThread(urls, pid) + + + @lock + @change + def deleteFinishedLinks(self): + """ deletes finished links and packages, return deleted packages """ + + old_packs = self.getInfoData(0) + old_packs.update(self.getInfoData(1)) + + self.db.deleteFinished() + + new_packs = self.db.getAllPackages(0) + new_packs.update(self.db.getAllPackages(1)) + # get new packages only from db + + deleted = [id for id in old_packs.iterkeys() if id not in new_packs] + for id_deleted in deleted: + self.deletePackage(int(id_deleted)) + + return deleted + + + @lock + @change + def restartFailed(self): + """ restart all failed links """ + self.db.restartFailed() + + +class FileMethods(object): + + + @style.queue + def filecount(self, queue): + """returns number of files in queue""" + self.c.execute("SELECT COUNT(*) FROM links as l INNER JOIN packages as p ON l.package=p.id WHERE p.queue=?", + (queue,)) + return self.c.fetchone()[0] + + + @style.queue + def queuecount(self, queue): + """ number of files in queue not finished yet""" + self.c.execute( + "SELECT COUNT(*) FROM links as l INNER JOIN packages as p ON l.package=p.id WHERE p.queue=? AND l.status NOT IN (0, 4)", + (queue,)) + return self.c.fetchone()[0] + + + @style.queue + def processcount(self, queue, fid): + """ number of files which have to be proccessed """ + self.c.execute( + "SELECT COUNT(*) FROM links as l INNER JOIN packages as p ON l.package=p.id WHERE p.queue=? AND l.status IN (2, 3, 5, 7, 12) AND l.id != ?", + (queue, str(fid))) + return self.c.fetchone()[0] + + + @style.inner + def _nextPackageOrder(self, queue=0): + self.c.execute('SELECT MAX(packageorder) FROM packages WHERE queue=?', (queue,)) + max = self.c.fetchone()[0] + if max is not None: + return max + 1 + else: + return 0 + + + @style.inner + def _nextFileOrder(self, package): + self.c.execute('SELECT MAX(linkorder) FROM links WHERE package=?', (package,)) + max = self.c.fetchone()[0] + if max is not None: + return max + 1 + else: + return 0 + + + @style.queue + def addLink(self, url, name, plugin, package): + order = self._nextFileOrder(package) + self.c.execute('INSERT INTO links(url, name, plugin, package, linkorder) VALUES(?,?,?,?,?)', + (url, name, ".".join(plugintype, pluginname), package, order)) + return self.c.lastrowid + + + @style.queue + def addLinks(self, links, package): + """ links is a list of tupels (url, plugin)""" + order = self._nextFileOrder(package) + orders = [order + x for x in xrange(len(links))] + links = [(x[0], x[0], ".".join((x[1], x[2])), package, o) for x, o in zip(links, orders)] + self.c.executemany('INSERT INTO links(url, name, plugin, package, linkorder) VALUES(?,?,?,?,?)', links) + + + @style.queue + def addPackage(self, name, folder, queue): + order = self._nextPackageOrder(queue) + self.c.execute('INSERT INTO packages(name, folder, queue, packageorder) VALUES(?,?,?,?)', + (name, folder, queue, order)) + return self.c.lastrowid + + + @style.queue + def deletePackage(self, p): + self.c.execute('DELETE FROM links WHERE package=?', (str(p.id),)) + self.c.execute('DELETE FROM packages WHERE id=?', (str(p.id),)) + self.c.execute('UPDATE packages SET packageorder=packageorder-1 WHERE packageorder > ? AND queue=?', + (p.order, p.queue)) + + + @style.queue + def deleteLink(self, f): + self.c.execute('DELETE FROM links WHERE id=?', (str(f.id),)) + self.c.execute('UPDATE links SET linkorder=linkorder-1 WHERE linkorder > ? AND package=?', + (f.order, str(f.packageid))) + + + @style.queue + def getAllLinks(self, q): + """return information about all links in queue q + + q0 queue + q1 collector + + format: + + { + id: {'name': name, ... 'package': id }, ... + } + + """ + self.c.execute( + 'SELECT l.id, l.url, l.name, l.size, l.status, l.error, l.plugin, l.package, l.linkorder FROM links as l INNER JOIN packages as p ON l.package=p.id WHERE p.queue=? ORDER BY l.linkorder', + (q,)) + data = {} + for r in self.c: + data[r[0]] = { + 'id': r[0], + 'url': r[1], + 'name': r[2], + 'size': r[3], + 'format_size': formatSize(r[3]), + 'status': r[4], + 'statusmsg': self.manager.statusMsg[r[4]], + 'error': r[5], + 'plugin': tuple(r[6].split('.')), + 'package': r[7], + 'order': r[8], + } + + return data + + + @style.queue + def getAllPackages(self, q): + """return information about packages in queue q + (only useful in get all data) + + q0 queue + q1 collector + + format: + + { + id: {'name': name ... 'links': {}}, ... + } + """ + self.c.execute('SELECT p.id, p.name, p.folder, p.site, p.password, p.queue, p.packageorder, s.sizetotal, s.sizedone, s.linksdone, s.linkstotal \ + FROM packages p JOIN pstats s ON p.id = s.id \ + WHERE p.queue=? ORDER BY p.packageorder', str(q)) + + data = {} + for r in self.c: + data[r[0]] = { + 'id': r[0], + 'name': r[1], + 'folder': r[2], + 'site': r[3], + 'password': r[4], + 'queue': r[5], + 'order': r[6], + 'sizetotal': int(r[7]), + 'sizedone': r[8] if r[8] else 0, #: these can be None + 'linksdone': r[9] if r[9] else 0, + 'linkstotal': r[10], + 'links': {} + } + + return data + + + @style.queue + def getLinkData(self, id): + """get link information as dict""" + self.c.execute('SELECT id, url, name, size, status, error, plugin, package, linkorder FROM links WHERE id=?', + (str(id),)) + data = {} + r = self.c.fetchone() + if not r: + return None + data[r[0]] = { + 'id': r[0], + 'url': r[1], + 'name': r[2], + 'size': r[3], + 'format_size': formatSize(r[3]), + 'status': r[4], + 'statusmsg': self.manager.statusMsg[r[4]], + 'error': r[5], + 'plugin': tuple(r[6].split('.')), + 'package': r[7], + 'order': r[8], + } + + return data + + + @style.queue + def getPackageData(self, id): + """get data about links for a package""" + self.c.execute( + 'SELECT id, url, name, size, status, error, plugin, package, linkorder FROM links WHERE package=? ORDER BY linkorder', + (str(id),)) + + data = {} + for r in self.c: + data[r[0]] = { + 'id': r[0], + 'url': r[1], + 'name': r[2], + 'size': r[3], + 'format_size': formatSize(r[3]), + 'status': r[4], + 'statusmsg': self.manager.statusMsg[r[4]], + 'error': r[5], + 'plugin': tuple(r[6].split('.')), + 'package': r[7], + 'order': r[8], + } + + return data + + + @style.async + def updateLink(self, f): + self.c.execute('UPDATE links SET url=?, name=?, size=?, status=?, error=?, package=? WHERE id=?', + (f.url, f.name, f.size, f.status, str(f.error), str(f.packageid), str(f.id))) + + + @style.queue + def updatePackage(self, p): + self.c.execute('UPDATE packages SET name=?, folder=?, site=?, password=?, queue=? WHERE id=?', + (p.name, p.folder, p.site, p.password, p.queue, str(p.id))) + + @style.queue + def updateLinkInfo(self, data): + """ data is list of tupels (name, size, status, url) """ + self.c.executemany('UPDATE links SET name=?, size=?, status=? WHERE url=? AND status IN (1, 2, 3, 14)', data) + ids = [] + self.c.execute('SELECT id FROM links WHERE url IN (\'%s\')' % "','".join([x[3] for x in data])) + for r in self.c: + ids.append(int(r[0])) + return ids + + + @style.queue + def reorderPackage(self, p, position, noMove=False): + if position == -1: + position = self._nextPackageOrder(p.queue) + if not noMove: + if p.order > position: + self.c.execute( + 'UPDATE packages SET packageorder=packageorder+1 WHERE packageorder >= ? AND packageorder < ? AND queue=? AND packageorder >= 0', + (position, p.order, p.queue)) + elif p.order < position: + self.c.execute( + 'UPDATE packages SET packageorder=packageorder-1 WHERE packageorder <= ? AND packageorder > ? AND queue=? AND packageorder >= 0', + (position, p.order, p.queue)) + + self.c.execute('UPDATE packages SET packageorder=? WHERE id=?', (position, str(p.id))) + + + @style.queue + def reorderLink(self, f, position): + """ reorder link with f as dict for pyfile """ + if f['order'] > position: + self.c.execute('UPDATE links SET linkorder=linkorder+1 WHERE linkorder >= ? AND linkorder < ? AND package=?', + (position, f['order'], f['package'])) + elif f['order'] < position: + self.c.execute('UPDATE links SET linkorder=linkorder-1 WHERE linkorder <= ? AND linkorder > ? AND package=?', + (position, f['order'], f['package'])) + + self.c.execute('UPDATE links SET linkorder=? WHERE id=?', (position, f['id'])) + + + @style.queue + def clearPackageOrder(self, p): + self.c.execute('UPDATE packages SET packageorder=? WHERE id=?', (-1, str(p.id))) + self.c.execute('UPDATE packages SET packageorder=packageorder-1 WHERE packageorder > ? AND queue=? AND id != ?', + (p.order, p.queue, str(p.id))) + + + @style.async + def restartFile(self, id): + self.c.execute('UPDATE links SET status=3, error="" WHERE id=?', (str(id),)) + + + @style.async + def restartPackage(self, id): + self.c.execute('UPDATE links SET status=3 WHERE package=?', (str(id),)) + + + @style.queue + def getPackage(self, id): + """return package instance from id""" + self.c.execute("SELECT name, folder, site, password, queue, packageorder FROM packages WHERE id=?", (str(id),)) + r = self.c.fetchone() + if not r: + return None + return PyPackage(self.manager, id, *r) + + + #-------------------------------------------------------------------------- + + @style.queue + def getFile(self, id): + """return link instance from id""" + self.c.execute("SELECT url, name, size, status, error, plugin, package, linkorder FROM links WHERE id=?", + (str(id),)) + r = self.c.fetchone() + if not r: + return None + r = list(r) + r[5] = tuple(r[5].split('.')) + return PyFile(self.manager, id, *r) + + + @style.queue + def getJob(self, occ): + """return pyfile ids, which are suitable for download and dont use a occupied plugin""" + + #@TODO: improve this hardcoded method + pre = "('CCF', 'DLC', 'LinkList', 'RSDF', 'TXT')" #: plugins which are processed in collector + + cmd = "(" + for i, item in enumerate(occ): + if i: cmd += ", " + cmd += "'%s'" % item + + cmd += ")" + + cmd = "SELECT l.id FROM links as l INNER JOIN packages as p ON l.package=p.id WHERE ((p.queue=1 AND l.plugin NOT IN %s) OR l.plugin IN %s) AND l.status IN (2, 3, 14) ORDER BY p.packageorder ASC, l.linkorder ASC LIMIT 5" % (cmd, pre) + + self.c.execute(cmd) #: very bad! + + return [x[0] for x in self.c] + + + @style.queue + def getPluginJob(self, plugins): + """returns pyfile ids with suited plugins""" + cmd = "SELECT l.id FROM links as l INNER JOIN packages as p ON l.package=p.id WHERE l.plugin IN %s AND l.status IN (2, 3, 14) ORDER BY p.packageorder ASC, l.linkorder ASC LIMIT 5" % plugins + + self.c.execute(cmd) #: very bad! + + return [x[0] for x in self.c] + + + @style.queue + def getUnfinished(self, pid): + """return list of max length 3 ids with pyfiles in package not finished or processed""" + + self.c.execute("SELECT id FROM links WHERE package=? AND status NOT IN (0, 4, 13) LIMIT 3", (str(pid),)) + return [r[0] for r in self.c] + + + @style.queue + def deleteFinished(self): + self.c.execute("DELETE FROM links WHERE status IN (0, 4)") + self.c.execute("DELETE FROM packages WHERE NOT EXISTS(SELECT 1 FROM links WHERE packages.id=links.package)") + + + @style.queue + def restartFailed(self): + self.c.execute("UPDATE links SET status=3, error='' WHERE status IN (6, 8, 9)") + + + @style.queue + def findDuplicates(self, id, folder, filename): + """ checks if filename exists with different id and same package """ + self.c.execute( + "SELECT l.plugin FROM links as l INNER JOIN packages as p ON l.package=p.id AND p.folder=? WHERE l.id!=? AND l.status=0 AND l.name=?", + (folder, id, filename)) + return self.c.fetchone() + + + @style.queue + def purgeLinks(self): + self.c.execute("DELETE FROM links;") + self.c.execute("DELETE FROM packages;") + + +DatabaseBackend.registerSub(FileMethods) diff --git a/pyload/database/Storage.py b/pyload/database/Storage.py new file mode 100644 index 000000000..70932b55c --- /dev/null +++ b/pyload/database/Storage.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- +# @author: mkaay + +from pyload.database import style +from pyload.database import DatabaseBackend + + +class StorageMethods(object): + + + @style.queue + def setStorage(db, identifier, key, value): + db.c.execute("SELECT id FROM storage WHERE identifier=? AND key=?", (identifier, key)) + if db.c.fetchone() is not None: + db.c.execute("UPDATE storage SET value=? WHERE identifier=? AND key=?", (value, identifier, key)) + else: + db.c.execute("INSERT INTO storage (identifier, key, value) VALUES (?, ?, ?)", (identifier, key, value)) + + + @style.queue + def getStorage(db, identifier, key=None): + if key is not None: + db.c.execute("SELECT value FROM storage WHERE identifier=? AND key=?", (identifier, key)) + row = db.c.fetchone() + if row is not None: + return row[0] + else: + db.c.execute("SELECT key, value FROM storage WHERE identifier=?", (identifier,)) + return {row[0]: row[1] for row in db.c} + + + @style.queue + def delStorage(db, identifier, key): + db.c.execute("DELETE FROM storage WHERE identifier=? AND key=?", (identifier, key)) + + +DatabaseBackend.registerSub(StorageMethods) diff --git a/pyload/database/User.py b/pyload/database/User.py new file mode 100644 index 000000000..b5b44c50a --- /dev/null +++ b/pyload/database/User.py @@ -0,0 +1,93 @@ +# -*- coding: utf-8 -*- +# @author: mkaay + +from hashlib import sha1 +import random + +from pyload.database import DatabaseBackend, style + + +class UserMethods(object): + + + @style.queue + def checkAuth(db, user, password): + c = db.c + c.execute('SELECT id, name, password, role, permission, template, email FROM "users" WHERE name=?', (user,)) + r = c.fetchone() + if not r: + return {} + + salt = r[2][:5] + pw = r[2][5:] + h = sha1(salt + password) + if h.hexdigest() == pw: + return {"id": r[0], "name": r[1], "role": r[3], + "permission": r[4], "template": r[5], "email": r[6]} + else: + return {} + + + @style.queue + def addUser(db, user, password): + salt = reduce(lambda x, y: x + y, [str(random.randint(0, 9)) for _i in xrange(0, 5)]) + h = sha1(salt + password) + password = salt + h.hexdigest() + + c = db.c + c.execute('SELECT name FROM users WHERE name=?', (user,)) + if c.fetchone() is not None: + c.execute('UPDATE users SET password=? WHERE name=?', (password, user)) + else: + c.execute('INSERT INTO users (name, password) VALUES (?, ?)', (user, password)) + + + @style.queue + def changePassword(db, user, oldpw, newpw): + db.c.execute('SELECT id, name, password FROM users WHERE name=?', (user,)) + r = db.c.fetchone() + if not r: + return False + + salt = r[2][:5] + pw = r[2][5:] + h = sha1(salt + oldpw) + if h.hexdigest() == pw: + salt = reduce(lambda x, y: x + y, [str(random.randint(0, 9)) for _i in xrange(0, 5)]) + h = sha1(salt + newpw) + password = salt + h.hexdigest() + + db.c.execute("UPDATE users SET password=? WHERE name=?", (password, user)) + return True + + return False + + + @style.async + def setPermission(db, user, perms): + db.c.execute("UPDATE users SET permission=? WHERE name=?", (perms, user)) + + + @style.async + def setRole(db, user, role): + db.c.execute("UPDATE users SET role=? WHERE name=?", (role, user)) + + + @style.queue + def listUsers(db): + db.c.execute('SELECT name FROM users') + return [row[0] for row in db.c] + + + @style.queue + def getAllUserData(db): + db.c.execute("SELECT name, permission, role, template, email FROM users") + return {r[0]: {"permission": r[1], "role": r[2], "template": r[3], "email": r[4]} for r in db.c} + + + @style.queue + def removeUser(db, user): + db.c.execute('DELETE FROM users WHERE name=?', (user,)) + + +DatabaseBackend.registerSub(UserMethods) diff --git a/pyload/database/__init__.py b/pyload/database/__init__.py new file mode 100644 index 000000000..4e0edc5d1 --- /dev/null +++ b/pyload/database/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- + +from pyload.database.Backend import DatabaseBackend, style +from pyload.database.File import FileHandler +from pyload.database.Storage import StorageMethods +from pyload.database.User import UserMethods diff --git a/pyload/datatype/File.py b/pyload/datatype/File.py new file mode 100644 index 000000000..f46529d87 --- /dev/null +++ b/pyload/datatype/File.py @@ -0,0 +1,300 @@ +# -*- coding: utf-8 -*- +# @author: RaNaN, mkaay + +import threading + +from time import sleep, time + +from pyload.manager.Event import UpdateEvent +from pyload.utils import formatSize, lock + + +statusMap = { + "finished" : 0, + "offline" : 1, + "online" : 2, + "queued" : 3, + "skipped" : 4, + "waiting" : 5, + "temp. offline": 6, + "starting" : 7, + "failed" : 8, + "aborted" : 9, + "decrypting" : 10, + "custom" : 11, + "downloading" : 12, + "processing" : 13, + "unknown" : 14, +} + + +def setSize(self, value): + self._size = int(value) + + +class PyFile(object): + """ + Represents a file object at runtime + """ + __slots__ = ("m", "id", "url", "name", "size", "_size", "status", "plugintype", "pluginname", + "packageid", "error", "order", "lock", "plugin", "waitUntil", + "active", "abort", "statusname", "reconnected", "progress", + "maxprogress", "pluginmodule", "pluginclass") + + + def __init__(self, manager, id, url, name, size, status, error, plugin, package, order): + self.m = manager + + self.id = int(id) + self.url = url + self.name = name + self.size = size + self.status = status + self.plugintype, self.pluginname = plugin + self.packageid = package #: should not be used, use package() instead + self.error = error + self.order = order + # database information ends here + + self.lock = threading.RLock() + + self.plugin = None + # self.download = None + + self.waitUntil = 0 #: time() + time to wait + + # status attributes + self.active = False #: obsolete? + self.abort = False + self.reconnected = False + + self.statusname = None + + self.progress = 0 + self.maxprogress = 100 + + self.m.cache[int(id)] = self + + # will convert all sizes to ints + size = property(lambda self: self._size, setSize) + + + def __repr__(self): + return "PyFile %s: %s@%s" % (self.id, self.name, self.pluginname) + + + @lock + def initPlugin(self): + """ inits plugin instance """ + if not self.plugin: + self.pluginmodule = self.m.core.pluginManager.getPlugin(self.plugintype, self.pluginname) + self.pluginclass = getattr(self.pluginmodule, self.m.core.pluginManager.getPluginName(self.plugintype, self.pluginname)) + self.plugin = self.pluginclass(self) + + + @lock + def hasPlugin(self): + """Thread safe way to determine this file has initialized plugin attribute + + :return: + """ + return hasattr(self, "plugin") and self.plugin + + + def package(self): + """ return package instance""" + return self.m.getPackage(self.packageid) + + + def setStatus(self, status): + self.status = statusMap[status] + self.sync() #@TODO: needed aslong no better job approving exists + + + def setCustomStatus(self, msg, status="processing"): + self.statusname = msg + self.setStatus(status) + + + def getStatusName(self): + if self.status not in (13, 14) or not self.statusname: + return self.m.statusMsg[self.status] + else: + return self.statusname + + + def hasStatus(self, status): + return statusMap[status] == self.status + + + def sync(self): + """sync PyFile instance with database""" + self.m.updateLink(self) + + + @lock + def release(self): + """sync and remove from cache""" + # file has valid package + if self.packageid > 0: + self.sync() + + if hasattr(self, "plugin") and self.plugin: + self.plugin.clean() + del self.plugin + + self.m.releaseLink(self.id) + + + def delete(self): + """delete pyfile from database""" + self.m.deleteLink(self.id) + + + def toDict(self): + """return dict with all information for interface""" + return self.toDbDict() + + + def toDbDict(self): + """return data as dict for databse + + format: + + { + id: {'url': url, 'name': name ... } + } + + """ + return { + self.id: { + 'id': self.id, + 'url': self.url, + 'name': self.name, + 'plugin': self.pluginname, + 'size': self.getSize(), + 'format_size': self.formatSize(), + 'status': self.status, + 'statusmsg': self.getStatusName(), + 'package': self.packageid, + 'error': self.error, + 'order': self.order + } + } + + + def abortDownload(self): + """abort pyfile if possible""" + while self.id in self.m.core.threadManager.processingIds(): + self.abort = True + if self.plugin and self.plugin.req: + self.plugin.req.abortDownloads() + sleep(0.1) + + self.abort = False + if self.hasPlugin() and self.plugin.req: + self.plugin.req.abortDownloads() + + self.release() + + + def finishIfDone(self): + """set status to finish and release file if every thread is finished with it""" + + if self.id in self.m.core.threadManager.processingIds(): + return False + + self.setStatus("finished") + self.release() + self.m.checkAllLinksFinished() + return True + + + def checkIfProcessed(self): + self.m.checkAllLinksProcessed(self.id) + + + def formatWait(self): + """ formats and return wait time in humanreadable format """ + seconds = self.waitUntil - time() + + if seconds < 0: + return "00:00:00" + + hours, seconds = divmod(seconds, 3600) + minutes, seconds = divmod(seconds, 60) + return "%.2i:%.2i:%.2i" % (hours, minutes, seconds) + + + def formatSize(self): + """ formats size to readable format """ + return formatSize(self.getSize()) + + + def formatETA(self): + """ formats eta to readable format """ + seconds = self.getETA() + + if seconds < 0: + return "00:00:00" + + hours, seconds = divmod(seconds, 3600) + minutes, seconds = divmod(seconds, 60) + return "%.2i:%.2i:%.2i" % (hours, minutes, seconds) + + + def getSpeed(self): + """ calculates speed """ + try: + return self.plugin.req.speed + except Exception: + return 0 + + + def getETA(self): + """ gets established time of arrival""" + try: + return self.getBytesLeft() / self.getSpeed() + except Exception: + return 0 + + + def getBytesLeft(self): + """ gets bytes left """ + try: + return self.getSize() - self.plugin.req.arrived + except Exception: + return 0 + + + def getPercent(self): + """ get % of download """ + if self.status == 12: + try: + return self.plugin.req.percent + except Exception: + return 0 + else: + return self.progress + + + def getSize(self): + """ get size of download """ + try: + if self.plugin.req.size: + return self.plugin.req.size + else: + return self.size + except Exception: + return self.size + + + def notifyChange(self): + e = UpdateEvent("file", self.id, "collector" if not self.package().queue else "queue") + self.m.core.pullManager.addEvent(e) + + + def setProgress(self, value): + if not value == self.progress: + self.progress = value + self.notifyChange() diff --git a/pyload/datatype/Package.py b/pyload/datatype/Package.py new file mode 100644 index 000000000..5ba42f596 --- /dev/null +++ b/pyload/datatype/Package.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +# @author: RaNaN, mkaay + +from pyload.manager.Event import UpdateEvent +from pyload.utils import safe_filename + + +class PyPackage(object): + """ + Represents a package object at runtime + """ + + def __init__(self, manager, id, name, folder, site, password, queue, order): + self.m = manager + self.m.packageCache[int(id)] = self + + self.id = int(id) + self.name = name + self._folder = folder + self.site = site + self.password = password + self.queue = queue + self.order = order + self.setFinished = False + + + @property + def folder(self): + return safe_filename(self._folder) + + + def toDict(self): + """ Returns a dictionary representation of the data. + + :return: dict: {id: { attr: value }} + """ + return { + self.id: { + 'id': self.id, + 'name': self.name, + 'folder': self.folder, + 'site': self.site, + 'password': self.password, + 'queue': self.queue, + 'order': self.order, + 'links': {} + } + } + + + def getChildren(self): + """get information about contained links""" + return self.m.getPackageData(self.id)["links"] + + + def sync(self): + """sync with db""" + self.m.updatePackage(self) + + + def release(self): + """sync and delete from cache""" + self.sync() + self.m.releasePackage(self.id) + + + def delete(self): + self.m.deletePackage(self.id) + + + def notifyChange(self): + e = UpdateEvent("pack", self.id, "collector" if not self.queue else "queue") + self.m.core.pullManager.addEvent(e) diff --git a/pyload/datatype/__init__.py b/pyload/datatype/__init__.py new file mode 100644 index 000000000..40a96afc6 --- /dev/null +++ b/pyload/datatype/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/pyload/manager/Account.py b/pyload/manager/Account.py new file mode 100644 index 000000000..ad770a1a8 --- /dev/null +++ b/pyload/manager/Account.py @@ -0,0 +1,197 @@ +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +import shutil +import threading + +from os.path import exists + +from pyload.manager.Event import AccountUpdateEvent +from pyload.utils import chmod, lock + + +ACC_VERSION = 1 + + +class AccountManager(object): + """manages all accounts""" + + def __init__(self, core): + """Constructor""" + + self.core = core + self.lock = threading.Lock() + + self.initPlugins() + self.saveAccounts() #: save to add categories to conf + + + def initPlugins(self): + self.accounts = {} #: key = ( plugin ) + self.plugins = {} + + self.initAccountPlugins() + self.loadAccounts() + + + def getAccountPlugin(self, plugin): + """get account instance for plugin or None if anonymous""" + try: + if plugin in self.accounts: + if plugin not in self.plugins: + klass = self.core.pluginManager.loadClass("accounts", plugin) + if klass: + self.plugins[plugin] = klass(self, self.accounts[plugin]) + else: #@NOTE: The account class no longer exists (blacklisted plugin). Skipping the account to avoid crash + raise + + return self.plugins[plugin] + else: + raise + except Exception: + return None + + + def getAccountPlugins(self): + """ get all account instances""" + + plugins = [] + for plugin in self.accounts.keys(): + plugins.append(self.getAccountPlugin(plugin)) + + return plugins + + + #---------------------------------------------------------------------- + + def loadAccounts(self): + """loads all accounts available""" + + try: + with open("accounts.conf", "a+") as f: + content = f.readlines() + version = content[0].split(":")[1].strip() if content else "" + + if not version or int(version) < ACC_VERSION: + shutil.copy("accounts.conf", "accounts.backup") + f.seek(0) + f.write("version: " + str(ACC_VERSION)) + + self.core.log.warning(_("Account settings deleted, due to new config format")) + return + + except IOError, e: + self.core.log.error(str(e)) + return + + plugin = "" + name = "" + + for line in content[1:]: + line = line.strip() + + if not line: + continue + if line.startswith("#"): + continue + if line.startswith("version"): + continue + + if line.endswith(":") and line.count(":") == 1: + plugin = line[:-1] + self.accounts[plugin] = {} + + elif line.startswith("@"): + try: + option = line[1:].split() + self.accounts[plugin][name]['options'][option[0]] = [] if len(option) < 2 else ([option[1]] if len(option) < 3 else option[1:]) + except Exception: + pass + + elif ":" in line: + name, sep, pw = line.partition(":") + self.accounts[plugin][name] = {"password": pw, "options": {}, "valid": True} + + + #---------------------------------------------------------------------- + + def saveAccounts(self): + """save all account information""" + + try: + with open("accounts.conf", "wb") as f: + f.write("version: " + str(ACC_VERSION) + "\n") + + for plugin, accounts in self.accounts.iteritems(): + f.write("\n") + f.write(plugin + ":\n") + + for name, data in accounts.iteritems(): + f.write("\n\t%s:%s\n" % (name, data['password']) ) + if data['options']: + for option, values in data['options'].iteritems(): + f.write("\t@%s %s\n" % (option, " ".join(values))) + + chmod(f.name, 0600) + + except Exception, e: + self.core.log.error(str(e)) + + + #---------------------------------------------------------------------- + + def initAccountPlugins(self): + """init names""" + for name in self.core.pluginManager.getAccountPlugins(): + self.accounts[name] = {} + + + @lock + def updateAccount(self, plugin, user, password=None, options={}): + """add or update account""" + if plugin in self.accounts: + p = self.getAccountPlugin(plugin) + updated = p.updateAccounts(user, password, options) + # since accounts is a ref in plugin self.accounts doesnt need to be updated here + + self.saveAccounts() + if updated: p.scheduleRefresh(user, force=False) + + + @lock + def removeAccount(self, plugin, user): + """remove account""" + + if plugin in self.accounts: + p = self.getAccountPlugin(plugin) + p.removeAccount(user) + + self.saveAccounts() + + + @lock + def getAccountInfos(self, force=True, refresh=False): + data = {} + + if refresh: + self.core.scheduler.addJob(0, self.core.accountManager.getAccountInfos) + force = False + + for p in self.accounts.keys(): + if self.accounts[p]: + p = self.getAccountPlugin(p) + if p: + data[p.__class__.__name__] = p.getAllAccounts(force) + else: #@NOTE: When an account has been skipped, p is None + data[p] = [] + else: + data[p] = [] + e = AccountUpdateEvent() + self.core.pullManager.addEvent(e) + return data + + + def sendChange(self): + e = AccountUpdateEvent() + self.core.pullManager.addEvent(e) diff --git a/pyload/manager/Addon.py b/pyload/manager/Addon.py new file mode 100644 index 000000000..2b3d1c456 --- /dev/null +++ b/pyload/manager/Addon.py @@ -0,0 +1,303 @@ +# -*- coding: utf-8 -*- +# @author: RaNaN, mkaay +# @interface-version: 0.1 + +import __builtin__ + +import threading +import traceback + +from types import MethodType + +from pyload.manager.thread.Addon import AddonThread +from pyload.manager.Plugin import literal_eval +from pyload.utils import lock + + +class AddonManager(object): + """Manages addons, delegates and handles Events. + + Every plugin can define events, \ + but some very usefull events are called by the Core. + Contrary to overwriting addon methods you can use event listener, + which provides additional entry point in the control flow. + Only do very short tasks or use threads. + + **Known Events:** + Most addon methods exists as events. These are the additional known events. + + ======================= ============== ================================== + Name Arguments Description + ======================= ============== ================================== + download-preparing fid A download was just queued and will be prepared now. + download-start fid A plugin will immediately starts the download afterwards. + links-added links, pid Someone just added links, you are able to modify the links. + all_downloads-processed Every link was handled, pyload would idle afterwards. + all_downloads-finished Every download in queue is finished. + config-changed The config was changed via the api. + pluginConfigChanged The plugin config changed, due to api or internal process. + ======================= ============== ================================== + + | Notes: + | all_downloads-processed is *always* called before all_downloads-finished. + | config-changed is *always* called before pluginConfigChanged. + """ + + def __init__(self, core): + self.core = core + + __builtin__.addonManager = self #: needed to let addons register themself + + self.plugins = [] + self.pluginMap = {} + self.methods = {} #: dict of names and list of methods usable by rpc + + self.events = {} #: contains events + + # registering callback for config event + self.core.config.pluginCB = MethodType(self.dispatchEvent, "pluginConfigChanged", basestring) #@TODO: Rename event pluginConfigChanged + + self.addEvent("pluginConfigChanged", self.manageAddon) + + self.lock = threading.RLock() + self.createIndex() + + + def try_catch(func): + + + def new(*args): + try: + return func(*args) + except Exception, e: + args[0].core.log.error(_("Error executing addon: %s") % e) + if args[0].core.debug: + traceback.print_exc() + + return new + + + def addRPC(self, plugin, func, doc): + plugin = plugin.rpartition(".")[2] + doc = doc.strip() if doc else "" + + if plugin in self.methods: + self.methods[plugin][func] = doc + else: + self.methods[plugin] = {func: doc} + + + def callRPC(self, plugin, func, args, parse): + if not args: + args = () + if parse: + args = tuple([literal_eval(x) for x in args]) + plugin = self.pluginMap[plugin] + f = getattr(plugin, func) + return f(*args) + + + def createIndex(self): + plugins = [] + + for type in ("addon", "hook"): + active = [] + deactive = [] + for pluginname in getattr(self.core.pluginManager, "%sPlugins" % type): + try: + if self.core.config.getPlugin("%s_%s" % (pluginname, type), "activated"): + pluginClass = self.core.pluginManager.loadClass(type, pluginname) + if not pluginClass: + continue + + plugin = pluginClass(self.core, self) + plugins.append(plugin) + self.pluginMap[pluginClass.__name__] = plugin + if plugin.isActivated(): + active.append(pluginClass.__name__) + else: + deactive.append(pluginname) + + except Exception: + self.core.log.warning(_("Failed activating %(name)s") % {"name": pluginname}) + if self.core.debug: + traceback.print_exc() + + self.core.log.info(_("Activated %ss: %s") % (type, ", ".join(sorted(active)))) + self.core.log.info(_("Deactivated %ss: %s") % (type, ", ".join(sorted(deactive)))) + + self.plugins = plugins + + + def manageAddon(self, plugin, name, value): + if name == "activated" and value: + self.activateAddon(plugin) + + elif name == "activated" and not value: + self.deactivateAddon(plugin) + + + def activateAddon(self, pluginname): + # check if already loaded + for inst in self.plugins: + if inst.__class__.__name__ == pluginname: + return + + pluginClass = self.core.pluginManager.loadClass("addon", pluginname) + + if not pluginClass: + return + + self.core.log.debug("Activate addon: %s" % pluginname) + + addon = pluginClass(self.core, self) + self.plugins.append(addon) + self.pluginMap[pluginClass.__name__] = addon + + addon.activate() + + + def deactivateAddon(self, pluginname): + for plugin in self.plugins: + if plugin.__class__.__name__ == pluginname: + addon = plugin + break + else: + return + + self.core.log.debug("Deactivate addon: %s" % pluginname) + + addon.deactivate() + + # remove periodic call + self.core.log.debug("Removed callback: %s" % self.core.scheduler.removeJob(addon.cb)) + + self.plugins.remove(addon) + del self.pluginMap[addon.__class__.__name__] + + + @try_catch + def coreReady(self): + for plugin in self.plugins: + if plugin.isActivated(): + plugin.activate() + + self.dispatchEvent("addon-start") + + + @try_catch + def coreExiting(self): + for plugin in self.plugins: + if plugin.isActivated(): + plugin.exit() + + self.dispatchEvent("addon-exit") + + + @lock + def downloadPreparing(self, pyfile): + for plugin in self.plugins: + if plugin.isActivated(): + plugin.downloadPreparing(pyfile) + + self.dispatchEvent("download-preparing", pyfile) + + + @lock + def downloadFinished(self, pyfile): + for plugin in self.plugins: + if plugin.isActivated(): + plugin.downloadFinished(pyfile) + + self.dispatchEvent("download-finished", pyfile) + + + @lock + @try_catch + def downloadFailed(self, pyfile): + for plugin in self.plugins: + if plugin.isActivated(): + plugin.downloadFailed(pyfile) + + self.dispatchEvent("download-failed", pyfile) + + + @lock + def packageFinished(self, package): + for plugin in self.plugins: + if plugin.isActivated(): + plugin.packageFinished(package) + + self.dispatchEvent("package-finished", package) + + + @lock + def beforeReconnecting(self, ip): + for plugin in self.plugins: + plugin.beforeReconnecting(ip) + + self.dispatchEvent("beforeReconnecting", ip) + + + @lock + def afterReconnecting(self, ip): + for plugin in self.plugins: + if plugin.isActivated(): + plugin.afterReconnecting(ip) + + self.dispatchEvent("afterReconnecting", ip) + + + def startThread(self, function, *args, **kwargs): + return AddonThread(self.core.threadManager, function, args, kwargs) + + + def activePlugins(self): + """ returns all active plugins """ + return [x for x in self.plugins if x.isActivated()] + + + def getAllInfo(self): + """returns info stored by addon plugins""" + info = {} + for name, plugin in self.pluginMap.iteritems(): + if plugin.info: + # copy and convert so str + info[name] = dict( + [(x, str(y) if not isinstance(y, basestring) else y) for x, y in plugin.info.iteritems()]) + return info + + + def getInfo(self, plugin): + info = {} + if plugin in self.pluginMap and self.pluginMap[plugin].info: + info = dict((x, str(y) if not isinstance(y, basestring) else y) + for x, y in self.pluginMap[plugin].info.iteritems()) + return info + + + def addEvent(self, event, func): + """Adds an event listener for event name""" + if event in self.events: + self.events[event].append(func) + else: + self.events[event] = [func] + + + def removeEvent(self, event, func): + """removes previously added event listener""" + if event in self.events: + self.events[event].remove(func) + + + def dispatchEvent(self, event, *args): + """dispatches event with args""" + if event in self.events: + for f in self.events[event]: + try: + f(*args) + except Exception, e: + self.core.log.warning("Error calling event handler %s: %s, %s, %s" + % (event, f, args, str(e))) + if self.core.debug: + traceback.print_exc() diff --git a/pyload/manager/Captcha.py b/pyload/manager/Captcha.py new file mode 100644 index 000000000..748f2e425 --- /dev/null +++ b/pyload/manager/Captcha.py @@ -0,0 +1,159 @@ +# -*- coding: utf-8 -*- +# @author: RaNaN, mkaay + +import threading +import traceback + +from time import time + +from pyload.utils import encode + + +class CaptchaManager(object): + + def __init__(self, core): + self.lock = threading.Lock() + self.core = core + self.tasks = [] #: task store, for outgoing tasks only + self.ids = 0 #: only for internal purpose + + + def newTask(self, img, format, file, result_type): + task = CaptchaTask(self.ids, img, format, file, result_type) + self.ids += 1 + return task + + + def removeTask(self, task): + self.lock.acquire() + if task in self.tasks: + self.tasks.remove(task) + self.lock.release() + + + def getTask(self): + self.lock.acquire() + for task in self.tasks: + if task.status in ("waiting", "shared-user"): + self.lock.release() + return task + self.lock.release() + return None + + + def getTaskByID(self, tid): + self.lock.acquire() + for task in self.tasks: + if task.id == str(tid): #: task ids are strings + self.lock.release() + return task + self.lock.release() + return None + + + def handleCaptcha(self, task, timeout=50): + cli = self.core.isClientConnected() + + if cli: #: client connected -> should solve the captcha + task.setWaiting(timeout) #: wait 50 sec for response + + for plugin in self.core.addonManager.activePlugins(): + try: + plugin.captchaTask(task) + except Exception: + if self.core.debug: + traceback.print_exc() + + if task.handler or cli: #: the captcha was handled + self.tasks.append(task) + return True + task.error = _("No Client connected for captcha decrypting") + return False + + +class CaptchaTask(object): + + def __init__(self, id, img, format, file, result_type='textual'): + self.id = str(id) + self.captchaImg = img + self.captchaFormat = format + self.captchaFile = file + self.captchaResultType = result_type + self.handler = [] #: the hook plugins that will take care of the solution + self.result = None + self.waitUntil = None + self.error = None #: error message + self.status = "init" + self.data = {} #: handler can store data here + + + def getCaptcha(self): + return self.captchaImg, self.captchaFormat, self.captchaResultType + + + def setResult(self, text): + if self.isTextual(): + self.result = text + if self.isPositional(): + try: + parts = text.split(',') + self.result = (int(parts[0]), int(parts[1])) + except Exception: + self.result = None + + + def getResult(self): + return encode(self.result) + + + def getStatus(self): + return self.status + + + def setWaiting(self, sec): + """ let the captcha wait secs for the solution """ + self.waitUntil = max(time() + sec, self.waitUntil) + self.status = "waiting" + + + def isWaiting(self): + if self.result or self.error or self.timedOut(): + return False + else: + return True + + + def isTextual(self): + """ returns if text is written on the captcha """ + return self.captchaResultType == 'textual' + + + def isPositional(self): + """ returns if user have to click a specific region on the captcha """ + return self.captchaResultType == 'positional' + + + def setWatingForUser(self, exclusive): + if exclusive: + self.status = "user" + else: + self.status = "shared-user" + + + def timedOut(self): + return time() > self.waitUntil + + + def invalid(self): + """ indicates the captcha was not correct """ + for x in self.handler: + x.captchaInvalid(self) + + + def correct(self): + for x in self.handler: + x.captchaCorrect(self) + + + def __str__(self): + return "<CaptchaTask '%s'>" % self.id diff --git a/pyload/manager/Event.py b/pyload/manager/Event.py new file mode 100644 index 000000000..b3d22619f --- /dev/null +++ b/pyload/manager/Event.py @@ -0,0 +1,131 @@ +# -*- coding: utf-8 -*- +# @author: mkaay + +from time import time +from pyload.utils import uniqify + + +class PullManager(object): + + def __init__(self, core): + self.core = core + self.clients = [] + + + def newClient(self, uuid): + self.clients.append(Client(uuid)) + + + def clean(self): + for n, client in enumerate(self.clients): + if client.lastActive + 30 < time(): + del self.clients[n] + + + def getEvents(self, uuid): + events = [] + validUuid = False + for client in self.clients: + if client.uuid == uuid: + client.lastActive = time() + validUuid = True + while client.newEvents(): + events.append(client.popEvent().toList()) + break + if not validUuid: + self.newClient(uuid) + events = [ReloadAllEvent("queue").toList(), ReloadAllEvent("collector").toList()] + return uniqify(events) + + + def addEvent(self, event): + for client in self.clients: + client.addEvent(event) + + +class Client(object): + + def __init__(self, uuid): + self.uuid = uuid + self.lastActive = time() + self.events = [] + + + def newEvents(self): + return len(self.events) > 0 + + + def popEvent(self): + if not len(self.events): + return None + return self.events.pop(0) + + + def addEvent(self, event): + self.events.append(event) + + +class UpdateEvent(object): + + def __init__(self, itype, iid, destination): + assert itype == "pack" or itype == "file" + assert destination == "queue" or destination == "collector" + self.type = itype + self.id = iid + self.destination = destination + + + def toList(self): + return ["update", self.destination, self.type, self.id] + + +class RemoveEvent(object): + + def __init__(self, itype, iid, destination): + assert itype == "pack" or itype == "file" + assert destination == "queue" or destination == "collector" + self.type = itype + self.id = iid + self.destination = destination + + + def toList(self): + return ["remove", self.destination, self.type, self.id] + + +class InsertEvent(object): + + def __init__(self, itype, iid, after, destination): + assert itype == "pack" or itype == "file" + assert destination == "queue" or destination == "collector" + self.type = itype + self.id = iid + self.after = after + self.destination = destination + + + def toList(self): + return ["insert", self.destination, self.type, self.id, self.after] + + +class ReloadAllEvent(object): + + def __init__(self, destination): + assert destination == "queue" or destination == "collector" + self.destination = destination + + + def toList(self): + return ["reload", self.destination] + + +class AccountUpdateEvent(object): + + def toList(self): + return ["account"] + + +class ConfigUpdateEvent(object): + + def toList(self): + return ["config"] diff --git a/pyload/manager/Plugin.py b/pyload/manager/Plugin.py new file mode 100644 index 000000000..0efd238b6 --- /dev/null +++ b/pyload/manager/Plugin.py @@ -0,0 +1,404 @@ +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +import re +import sys +import traceback + +from itertools import chain +from os import listdir, makedirs +from os.path import isdir, isfile, join, exists, abspath +from sys import version_info +from urllib import unquote + +from SafeEval import const_eval as literal_eval + + +class PluginManager(object): + ROOT = "pyload.plugin." + USERROOT = "userplugins." + TYPES = ["account", "addon", "container", "crypter", "extractor", "hook", "hoster", "internal", "ocr"] + + PATTERN = re.compile(r'__pattern\s*=\s*u?r("|\')([^"\']+)') + VERSION = re.compile(r'__version\s*=\s*("|\')([\d.]+)') + CONFIG = re.compile(r'__config\s*=\s*\[([^\]]+)', re.M) + DESC = re.compile(r'__description\s*=\s*("|"""|\')([^"\']+)') + + + def __init__(self, core): + self.core = core + + self.plugins = {} + self.createIndex() + + # register for import addon + sys.meta_path.append(self) + + + def loadTypes(self): + rootdir = join(pypath, "pyload", "plugin") + userdir = "userplugins" + + types = set().union(*[[d for d in listdir(p) if isdir(join(p, d))] + for p in (rootdir, userdir) if exists(p)]) + + if not types: + self.core.log.critical(_("No plugins found!")) + + self.TYPES = list(set(self.TYPES) | types) + + + def createIndex(self): + """create information for all plugins available""" + + sys.path.append(abspath("")) + + self.loadTypes() + + configs = [] + + for type in self.TYPES: + self.plugins[type] = self.parse(type) + setattr(self, "%sPlugins" % type, self.plugins[type]) + configs.extend("%s_%s" % (p, type) for p in self.plugins[type]) + + self.core.config.removeDeletedPlugins(configs) + + self.core.log.debug("Created index of plugins") + + + def parse(self, folder, rootplugins={}): + """ + returns dict with information + home contains parsed plugins from pyload. + """ + + plugins = {} + + if rootplugins: + try: + pfolder = join("userplugins", folder) + if not exists(pfolder): + makedirs(pfolder) + + for ifile in (join("userplugins", "__init__.py"), + join(pfolder, "__init__.py")): + if not exists(ifile): + f = open(ifile, "wb") + f.close() + + except IOError, e: + self.core.log.critical(str(e)) + return rootplugins + + else: + pfolder = join(pypath, "pyload", "plugin", folder) + + for f in listdir(pfolder): + if isfile(join(pfolder, f)) and f.endswith(".py") and not f.startswith("_"): + + try: + with open(join(pfolder, f)) as data: + content = data.read() + + except IOError, e: + self.core.log.error(str(e)) + continue + + name = f[:-3] + if name[-1] == ".": + name = name[:-4] + + if not re.search("class\\s+%s\\(" % name, content): + self.core.log.error(_("invalid classname: %s ignored") % join(pfolder, f)) + + version = self.VERSION.findall(content) + if version: + version = float(version[0][1]) + else: + version = 0 + + if rootplugins and name in rootplugins: + if rootplugins[name]['version'] >= version: + continue + + plugins[name] = {} + plugins[name]['version'] = version + + module = f.replace(".pyc", "").replace(".py", "") + + # the plugin is loaded from user directory + plugins[name]['user'] = bool(rootplugins) + plugins[name]['name'] = module + + pattern = self.PATTERN.findall(content) + + if pattern: + pattern = pattern[0][1] + + try: + regexp = re.compile(pattern) + except Exception: + self.core.log.error(_("%s has a invalid pattern") % name) + pattern = r'^unmatchable$' + regexp = re.compile(pattern) + + plugins[name]['pattern'] = pattern + plugins[name]['re'] = regexp + + # internals have no config + if folder == "internal": + self.core.config.deleteConfig("internal") + continue + + config = self.CONFIG.findall(content) + if config: + try: + config = literal_eval(config[0].strip().replace("\n", "").replace("\r", "")) + desc = self.DESC.findall(content) + desc = desc[0][1] if desc else "" + + if type(config[0]) == tuple: + config = [list(x) for x in config] + else: + config = [list(config)] + + if folder not in ("account", "internal") and not [True for item in config if item[0] == "activated"]: + config.insert(0, ["activated", "bool", "Activated", not folder in ("addon", "hook")]) + + self.core.config.addPluginConfig("%s_%s" % (name, folder), config, desc) + except Exception: + self.core.log.error("Invalid config in %s: %s" % (name, config)) + + elif folder in ("addon", "hook"): #: force config creation + desc = self.DESC.findall(content) + desc = desc[0][1] if desc else "" + config = (["activated", "bool", "Activated", False],) + + try: + self.core.config.addPluginConfig("%s_%s" % (name, folder), config, desc) + except Exception: + self.core.log.error("Invalid config in %s: %s" % (name, config)) + + if not rootplugins and plugins: #: Double check + plugins.update(self.parse(folder, plugins)) + + return plugins + + + def parseUrls(self, urls): + """parse plugins for given list of urls""" + + last = None + res = [] #: tupels of (url, plugintype, pluginname) + + for url in urls: + if type(url) not in (str, unicode, buffer): + continue + + url = unquote(url) + + if last and last[2]['re'].match(url): + res.append((url, last[0], last[1])) + continue + + for plugintype in self.TYPES: + m = None + for name, plugin in self.plugins[plugintype].iteritems(): + try: + if 'pattern' in plugin: + m = plugin['re'].match(url) + + except KeyError: + self.core.log.error(_("Plugin [%(type)s] %(name)s skipped due broken pattern") + % {'name': plugin['name'], 'type': plugintype}) + + if m: + res.append((url, plugintype, name)) + last = (plugintype, name, plugin) + break + if m: + break + else: + res.append((url, "internal", "BasePlugin")) + print res + return res + + + def findPlugin(self, type, name): + if isinstance(type, tuple): + for typ in type: + if name in self.plugins[typ]: + return (self.plugins[typ][name], typ) + + if isinstance(type, tuple) or type not in self.plugins or name not in self.plugins[type]: + self.core.log.warning(_("Plugin [%(type)s] %(name)s not found | Using plugin: [internal] BasePlugin") + % {'name': name, 'type': type}) + return self.internalPlugins['BasePlugin'] + + else: + return self.plugins[type][name] + + + def getPlugin(self, type, name, original=False): + """return plugin module from hoster|decrypter|container""" + plugin = self.findPlugin(type, name) + + if plugin is None: + return {} + + if "new_module" in plugin and not original: + return plugin['new_module'] + else: + return self.loadModule(type, name) + + + def getPluginName(self, type, name): + """ used to obtain new name if other plugin was injected""" + plugin = self.findPlugin(type, name) + + if plugin is None: + return "" + + if "new_name" in plugin: + return plugin['new_name'] + + return name + + + def loadModule(self, type, name): + """ Returns loaded module for plugin + + :param type: plugin type, subfolder of pyload.plugins + :param name: + """ + plugins = self.plugins[type] + + if name in plugins: + if "module" in plugins[name]: + return plugins[name]['module'] + + try: + module = __import__(self.ROOT + "%s.%s" % (type, plugins[name]['name']), globals(), locals(), + plugins[name]['name']) + + except Exception, e: + self.core.log.error(_("Error importing plugin: [%(type)s] %(name)s (v%(version).2f) | %(errmsg)s") + % {'name': name, 'type': type, 'version': plugins[name]['version'], "errmsg": str(e)}) + if self.core.debug: + traceback.print_exc() + + else: + plugins[name]['module'] = module # : cache import, maybe unneeded + + self.core.log.debug(_("Loaded plugin: [%(type)s] %(name)s (v%(version).2f)") + % {'name': name, 'type': type, 'version': plugins[name]['version']}) + return module + + + def loadClass(self, type, name): + """Returns the class of a plugin with the same name""" + module = self.loadModule(type, name) + if module: + return getattr(module, name) + else: + return None + + + def getAccountPlugins(self): + """return list of account plugin names""" + return self.accountPlugins.keys() + + + def find_module(self, fullname, path=None): + # redirecting imports if necesarry + if fullname.startswith(self.ROOT) or fullname.startswith(self.USERROOT): #: seperate pyload plugins + if fullname.startswith(self.USERROOT): user = 1 + else: user = 0 #: used as bool and int + + split = fullname.split(".") + if len(split) != 4 - user: + return + type, name = split[2 - user:4 - user] + + if type in self.plugins and name in self.plugins[type]: + # userplugin is a newer version + if not user and self.plugins[type][name]['user']: + return self + # imported from userdir, but pyloads is newer + if user and not self.plugins[type][name]['user']: + return self + + + def load_module(self, name, replace=True): + if name not in sys.modules: #: could be already in modules + if replace: + if self.ROOT in name: + newname = name.replace(self.ROOT, self.USERROOT) + else: + newname = name.replace(self.USERROOT, self.ROOT) + else: + newname = name + + base, plugin = newname.rsplit(".", 1) + + self.core.log.debug("Redirected import %s -> %s" % (name, newname)) + + module = __import__(newname, globals(), locals(), [plugin]) + # inject under new an old name + sys.modules[name] = module + sys.modules[newname] = module + + return sys.modules[name] + + + def reloadPlugins(self, type_plugins): + """ reload and reindex plugins """ + if not type_plugins: + return None + + self.core.log.debug("Request reload of plugins: %s" % type_plugins) + + reloaded = [] + + as_dict = {} + for t, n in type_plugins: + if t in as_dict: + as_dict[t].append(n) + else: + as_dict[t] = [n] + + for type in as_dict.iterkeys(): + if type in ("addon", "internal"): # : do not reload them because would cause to much side effects + self.core.log.debug("Skipping reload for plugins from type: %(type)s" % {'type': type}) + continue + + for plugin in as_dict[type]: + if plugin in self.plugins[type] and "module" in self.plugins[type][plugin]: + self.core.log.debug(_("Reloading plugin: [%(type)s] %(name)s") % {'name': plugin, 'type': type}) + + try: + reload(self.plugins[type][plugin]['module']) + + except Exception, e: + self.core.log.error(_("Error when reloading plugin: [%(type)s] %(name)s") % {'name': plugin, 'type': type}, e) + continue + + else: + reloaded.append((type, plugin)) + + # index creation + self.plugins[type] = self.parse(type) + setattr(self, "%sPlugins" % type, self.plugins[type]) + + if "account" in as_dict: #: accounts needs to be reloaded + self.core.accountManager.initPlugins() + self.core.scheduler.addJob(0, self.core.accountManager.getAccountInfos) + + return reloaded #: return a list of the plugins successfully reloaded + + + def reloadPlugin(self, type_plugin): + """ reload and reindex ONE plugin """ + return bool(self.reloadPlugins(type_plugin)) diff --git a/pyload/manager/Remote.py b/pyload/manager/Remote.py new file mode 100644 index 000000000..3c6dabefa --- /dev/null +++ b/pyload/manager/Remote.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- +# @author: RaNaN + +import traceback + + +class BackendBase(Thread): + + def __init__(self, manager): + Thread.__init__(self) + self.m = manager + self.core = manager.core + self.enabled = True + self.running = False + + + def run(self): + self.running = True + try: + self.serve() + except Exception, e: + self.core.log.error(_("Remote backend error: %s") % e) + if self.core.debug: + traceback.print_exc() + finally: + self.running = False + + + def setup(self, host, port): + pass + + + def checkDeps(self): + return True + + + def serve(self): + pass + + + def shutdown(self): + pass + + + def stop(self): + self.enabled = False #: set flag and call shutdowm message, so thread can react + self.shutdown() + + +class RemoteManager(object): + available = [] + + + def __init__(self, core): + self.core = core + self.backends = [] + + if self.core.remote: + self.available.append("ThriftBackend") + # else: + # self.available.append("SocketBackend") + + + def startBackends(self): + host = self.core.config.get("remote", "listenaddr") + port = self.core.config.get("remote", "port") + + for b in self.available: + klass = getattr(__import__("pyload.remote.%s" % b, globals(), locals(), [b], -1), b) + backend = klass(self) + if not backend.checkDeps(): + continue + try: + backend.setup(host, port) + self.core.log.info(_("Starting %(name)s: %(addr)s:%(port)s") % {"name": b, "addr": host, "port": port}) + except Exception, e: + self.core.log.error(_("Failed loading backend %(name)s | %(error)s") % {"name": b, "error": str(e)}) + if self.core.debug: + traceback.print_exc() + else: + backend.start() + self.backends.append(backend) + + port += 1 diff --git a/pyload/manager/Thread.py b/pyload/manager/Thread.py new file mode 100644 index 000000000..ecfcb3e26 --- /dev/null +++ b/pyload/manager/Thread.py @@ -0,0 +1,317 @@ +# -*- coding: utf-8 -*- +# @author: RaNaN + +import random +import re +import threading +import traceback + +import pycurl + +from os.path import exists, join +from random import choice +from subprocess import Popen +from time import sleep, time + +from pyload.datatype.File import PyFile +from pyload.manager.thread.Decrypter import DecrypterThread +from pyload.manager.thread.Download import DownloadThread +from pyload.manager.thread.Info import InfoThread +from pyload.network.RequestFactory import getURL +from pyload.utils import freeSpace, lock + + +class ThreadManager(object): + """manages the download threads, assign jobs, reconnect etc""" + + def __init__(self, core): + """Constructor""" + self.core = core + + self.threads = [] #: thread list + self.localThreads = [] #: addon+decrypter threads + + self.pause = True + + self.reconnecting = threading.Event() + self.reconnecting.clear() + self.downloaded = 0 #: number of files downloaded since last cleanup + + self.lock = threading.Lock() + + # some operations require to fetch url info from hoster, so we caching them so it wont be done twice + # contains a timestamp and will be purged after timeout + self.infoCache = {} + + # pool of ids for online check + self.resultIDs = 0 + + # threads which are fetching hoster results + self.infoResults = {} + # timeout for cache purge + self.timestamp = 0 + + pycurl.global_init(pycurl.GLOBAL_DEFAULT) + + for _i in xrange(0, self.core.config.get("download", "max_downloads")): + self.createThread() + + + def createThread(self): + """create a download thread""" + + thread = DownloadThread(self) + self.threads.append(thread) + + + def createInfoThread(self, data, pid): + """ + start a thread whichs fetches online status and other infos + data = [ .. () .. ] + """ + self.timestamp = time() + 5 * 60 + + InfoThread(self, data, pid) + + + @lock + def createResultThread(self, data, add=False): + """ creates a thread to fetch online status, returns result id """ + self.timestamp = time() + 5 * 60 + + rid = self.resultIDs + self.resultIDs += 1 + + InfoThread(self, data, rid=rid, add=add) + + return rid + + + @lock + def getInfoResult(self, rid): + """returns result and clears it""" + self.timestamp = time() + 5 * 60 + + if rid in self.infoResults: + data = self.infoResults[rid] + self.infoResults[rid] = {} + return data + else: + return {} + + + @lock + def setInfoResults(self, rid, result): + self.infoResults[rid].update(result) + + + def getActiveFiles(self): + active = [x.active for x in self.threads if x.active and isinstance(x.active, PyFile)] + + for t in self.localThreads: + active.extend(t.getActiveFiles()) + + return active + + + def processingIds(self): + """get a id list of all pyfiles processed""" + return [x.id for x in self.getActiveFiles()] + + + def work(self): + """run all task which have to be done (this is for repetivive call by core)""" + try: + self.tryReconnect() + except Exception, e: + self.core.log.error(_("Reconnect Failed: %s") % str(e)) + self.reconnecting.clear() + if self.core.debug: + traceback.print_exc() + self.checkThreadCount() + + try: + self.assignJob() + except Exception, e: + self.core.log.warning("Assign job error", e) + if self.core.debug: + traceback.print_exc() + + sleep(0.5) + self.assignJob() + # it may be failed non critical so we try it again + + if (self.infoCache or self.infoResults) and self.timestamp < time(): + self.infoCache.clear() + self.infoResults.clear() + self.core.log.debug("Cleared Result cache") + + + #-------------------------------------------------------------------------- + + def tryReconnect(self): + """checks if reconnect needed""" + + if not (self.core.config.get("reconnect", "activated") and self.core.api.isTimeReconnect()): + return False + + active = [x.active.plugin.wantReconnect and x.active.plugin.waiting for x in self.threads if x.active] + + if not (0 < active.count(True) == len(active)): + return False + + if not exists(self.core.config.get("reconnect", "method")): + if exists(join(pypath, self.core.config.get("reconnect", "method"))): + self.core.config.set("reconnect", "method", join(pypath, self.core.config.get("reconnect", "method"))) + else: + self.core.config.set("reconnect", "activated", False) + self.core.log.warning(_("Reconnect script not found!")) + return + + self.reconnecting.set() + + # Do reconnect + self.core.log.info(_("Starting reconnect")) + + while [x.active.plugin.waiting for x in self.threads if x.active].count(True) != 0: + sleep(0.25) + + ip = self.getIP() + + self.core.addonManager.beforeReconnecting(ip) + + self.core.log.debug("Old IP: %s" % ip) + + try: + reconn = Popen(self.core.config.get("reconnect", "method"), bufsize=-1, shell=True) # , stdout=subprocess.PIPE) + except Exception: + self.core.log.warning(_("Failed executing reconnect script!")) + self.core.config.set("reconnect", "activated", False) + self.reconnecting.clear() + if self.core.debug: + traceback.print_exc() + return + + reconn.wait() + sleep(1) + ip = self.getIP() + self.core.addonManager.afterReconnecting(ip) + + self.core.log.info(_("Reconnected, new IP: %s") % ip) + + self.reconnecting.clear() + + + def getIP(self): + """retrieve current ip""" + services = [("http://automation.whatismyip.com/n09230945.asp", "(\S+)"), + ("http://checkip.dyndns.org/", ".*Current IP Address: (\S+)</body>.*")] + + ip = "" + for _i in xrange(10): + try: + sv = random.choice(services) + ip = getURL(sv[0]) + ip = re.match(sv[1], ip).group(1) + break + except Exception: + ip = "" + sleep(1) + + return ip + + + #-------------------------------------------------------------------------- + + def checkThreadCount(self): + """checks if there are need for increasing or reducing thread count""" + + if len(self.threads) == self.core.config.get("download", "max_downloads"): + return True + elif len(self.threads) < self.core.config.get("download", "max_downloads"): + self.createThread() + else: + free = [x for x in self.threads if not x.active] + if free: + free[0].put("quit") + + + def cleanPycurl(self): + """ make a global curl cleanup (currently ununused) """ + if self.processingIds(): + return False + pycurl.global_cleanup() + pycurl.global_init(pycurl.GLOBAL_DEFAULT) + self.downloaded = 0 + self.core.log.debug("Cleaned up pycurl") + return True + + + #-------------------------------------------------------------------------- + + def assignJob(self): + """assing a job to a thread if possible""" + + if self.pause or not self.core.api.isTimeDownload(): + return + + # if self.downloaded > 20: + # if not self.cleanPyCurl(): + return + + free = [x for x in self.threads if not x.active] + + inuse = set([((x.active.plugintype, x.active.pluginname), self.getLimit(x)) for x in self.threads if x.active and isinstance(x.active, PyFile) and x.active.hasPlugin() and x.active.plugin.account]) + inuse = map(lambda x: ('.'.join(x[0]), x[1], len([y for y in self.threads if y.active and isinstance(y.active, PyFile) and y.active.plugintype == x[0][0] and y.active.pluginname == x[0][1]])), inuse) + onlimit = [x[0] for x in inuse if x[1] > 0 and x[2] >= x[1]] + + occ = [x.active.plugintype + '.' + x.active.pluginname for x in self.threads if x.active and isinstance(x.active, PyFile) and x.active.hasPlugin() and not x.active.plugin.multiDL] + onlimit + + occ.sort() + occ = tuple(set(occ)) + job = self.core.files.getJob(occ) + if job: + try: + job.initPlugin() + except Exception, e: + self.core.log.critical(str(e)) + traceback.print_exc() + job.setStatus("failed") + job.error = str(e) + job.release() + return + + if job.plugin.getPluginType() == "hoster": + spaceLeft = freeSpace(self.core.config.get("general", "download_folder")) / 1024 / 1024 + if spaceLeft < self.core.config.get("general", "min_free_space"): + self.core.log.warning(_("Not enough space left on device")) + self.pause = True + + if free and not self.pause: + thread = free[0] + # self.downloaded += 1 + + thread.put(job) + else: + # put job back + if occ not in self.core.files.jobCache: + self.core.files.jobCache[occ] = [] + self.core.files.jobCache[occ].append(job.id) + + # check for decrypt jobs + job = self.core.files.getDecryptJob() + if job: + job.initPlugin() + thread = DecrypterThread(self, job) + else: + thread = DecrypterThread(self, job) + + + def getLimit(self, thread): + limit = thread.active.plugin.account.getAccountData(thread.active.plugin.user)["options"].get("limitDL", ["0"])[0] + return int(limit) + + + def cleanup(self): + """do global cleanup, should be called when finished with pycurl""" + pycurl.global_cleanup() diff --git a/pyload/manager/__init__.py b/pyload/manager/__init__.py new file mode 100644 index 000000000..40a96afc6 --- /dev/null +++ b/pyload/manager/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/pyload/manager/event/Scheduler.py b/pyload/manager/event/Scheduler.py new file mode 100644 index 000000000..d7098ae10 --- /dev/null +++ b/pyload/manager/event/Scheduler.py @@ -0,0 +1,141 @@ +# -*- coding: utf-8 -*- +# @author: mkaay + +import threading + +from heapq import heappop, heappush +from time import time + + +class AlreadyCalled(Exception): + pass + + +class Deferred(object): + + def __init__(self): + self.call = [] + self.result = () + + + def addCallback(self, f, *cargs, **ckwargs): + self.call.append((f, cargs, ckwargs)) + + + def callback(self, *args, **kwargs): + if self.result: + raise AlreadyCalled + self.result = (args, kwargs) + for f, cargs, ckwargs in self.call: + args += tuple(cargs) + kwargs.update(ckwargs) + f(*args ** kwargs) + + +class Scheduler(object): + + def __init__(self, core): + self.core = core + + self.queue = PriorityQueue() + + + def addJob(self, t, call, args=[], kwargs={}, threaded=True): + d = Deferred() + t += time() + j = Job(t, call, args, kwargs, d, threaded) + self.queue.put((t, j)) + return d + + + def removeJob(self, d): + """ + :param d: defered object + :return: if job was deleted + """ + index = -1 + + for i, j in enumerate(self.queue): + if j[1].deferred == d: + index = i + + if index >= 0: + del self.queue[index] + return True + + return False + + + def work(self): + while True: + t, j = self.queue.get() + if not j: + break + else: + if t <= time(): + j.start() + else: + self.queue.put((t, j)) + break + + +class Job(object): + + def __init__(self, time, call, args=[], kwargs={}, deferred=None, threaded=True): + self.time = float(time) + self.call = call + self.args = args + self.kwargs = kwargs + self.deferred = deferred + self.threaded = threaded + + + def run(self): + ret = self.call(*self.args, **self.kwargs) + if self.deferred is None: + return + else: + self.deferred.callback(ret) + + + def start(self): + if self.threaded: + t = threading.Thread(target=self.run) + t.setDaemon(True) + t.start() + else: + self.run() + + +class PriorityQueue(object): + """ a non blocking priority queue """ + + def __init__(self): + self.queue = [] + self.lock = threading.Lock() + + + def __iter__(self): + return iter(self.queue) + + + def __delitem__(self, key): + del self.queue[key] + + + def put(self, element): + self.lock.acquire() + heappush(self.queue, element) + self.lock.release() + + + def get(self): + """ return element or None """ + self.lock.acquire() + try: + el = heappop(self.queue) + return el + except IndexError: + return None, None + finally: + self.lock.release() diff --git a/pyload/manager/event/__init__.py b/pyload/manager/event/__init__.py new file mode 100644 index 000000000..40a96afc6 --- /dev/null +++ b/pyload/manager/event/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/pyload/manager/thread/Addon.py b/pyload/manager/thread/Addon.py new file mode 100644 index 000000000..3cda99950 --- /dev/null +++ b/pyload/manager/thread/Addon.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- +# @author: RaNaN + +import traceback + +from Queue import Queue +from copy import copy +from os import listdir, stat +from os.path import join +from pprint import pformat +from sys import exc_info, exc_clear +from time import sleep, time, strftime, gmtime +from types import MethodType + +from pyload.manager.thread.Plugin import PluginThread + + +class AddonThread(PluginThread): + """thread for addons""" + + def __init__(self, m, function, args, kwargs): + """Constructor""" + PluginThread.__init__(self, m) + + self.f = function + self.args = args + self.kwargs = kwargs + + self.active = [] + + m.localThreads.append(self) + + self.start() + + + def getActiveFiles(self): + return self.active + + + def addActive(self, pyfile): + """ Adds a pyfile to active list and thus will be displayed on overview""" + if pyfile not in self.active: + self.active.append(pyfile) + + + def finishFile(self, pyfile): + if pyfile in self.active: + self.active.remove(pyfile) + + pyfile.finishIfDone() + + + def run(self): + try: + try: + self.kwargs['thread'] = self + self.f(*self.args, **self.kwargs) + except TypeError, e: + # dirty method to filter out exceptions + if "unexpected keyword argument 'thread'" not in e.args[0]: + raise + + del self.kwargs['thread'] + self.f(*self.args, **self.kwargs) + finally: + local = copy(self.active) + for x in local: + self.finishFile(x) + + self.m.localThreads.remove(self) diff --git a/pyload/manager/thread/Decrypter.py b/pyload/manager/thread/Decrypter.py new file mode 100644 index 000000000..3554feac4 --- /dev/null +++ b/pyload/manager/thread/Decrypter.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- +# @author: RaNaN + +import traceback + +from Queue import Queue +from copy import copy +from os import listdir, stat +from os.path import join +from pprint import pformat +from sys import exc_info, exc_clear +from time import sleep, time, strftime, gmtime +from types import MethodType + +from pyload.manager.thread.Plugin import PluginThread +from pyload.plugin.Plugin import Abort, Fail, Retry + + +class DecrypterThread(PluginThread): + + """thread for decrypting""" + + def __init__(self, manager, pyfile): + """constructor""" + PluginThread.__init__(self, manager) + + self.active = pyfile + manager.localThreads.append(self) + + pyfile.setStatus("decrypting") + + self.start() + + + def getActiveFiles(self): + return [self.active] + + + def run(self): + """run method""" + + pyfile = self.active + retry = False + + try: + self.m.core.log.info(_("Decrypting starts: %s") % pyfile.name) + pyfile.error = "" + pyfile.plugin.preprocessing(self) + + except NotImplementedError: + self.m.core.log.error(_("Plugin %s is missing a function.") % pyfile.pluginname) + return + + except Fail, e: + msg = e.args[0] + + if msg == "offline": + pyfile.setStatus("offline") + self.m.core.log.warning( + _("Download is offline: %s") % pyfile.name) + else: + pyfile.setStatus("failed") + self.m.core.log.error( + _("Decrypting failed: %(name)s | %(msg)s") % {"name": pyfile.name, "msg": msg}) + pyfile.error = msg + + if self.m.core.debug: + traceback.print_exc() + return + + except Abort: + self.m.core.log.info(_("Download aborted: %s") % pyfile.name) + pyfile.setStatus("aborted") + + if self.m.core.debug: + traceback.print_exc() + return + + except Retry: + self.m.core.log.info(_("Retrying %s") % pyfile.name) + retry = True + return self.run() + + except Exception, e: + pyfile.setStatus("failed") + self.m.core.log.error(_("Decrypting failed: %(name)s | %(msg)s") % { + "name": pyfile.name, "msg": str(e)}) + pyfile.error = str(e) + + if self.m.core.debug: + traceback.print_exc() + self.writeDebugReport(pyfile) + + return + + finally: + if not retry: + pyfile.release() + self.active = False + self.m.core.files.save() + self.m.localThreads.remove(self) + exc_clear() + + if not retry: + pyfile.delete() diff --git a/pyload/manager/thread/Download.py b/pyload/manager/thread/Download.py new file mode 100644 index 000000000..04f73b2ed --- /dev/null +++ b/pyload/manager/thread/Download.py @@ -0,0 +1,213 @@ +# -*- coding: utf-8 -*- +# @author: RaNaN + +import traceback + +import pycurl + +from Queue import Queue +from copy import copy +from os import listdir, stat +from os.path import join +from pprint import pformat +from sys import exc_info, exc_clear +from time import sleep, time, strftime, gmtime +from types import MethodType + +from pyload.manager.thread.Plugin import PluginThread +from pyload.plugin.Plugin import Abort, Fail, Reconnect, Retry, SkipDownload + + +class DownloadThread(PluginThread): + """thread for downloading files from 'real' hoster plugins""" + + def __init__(self, manager): + """Constructor""" + PluginThread.__init__(self, manager) + + self.queue = Queue() #: job queue + self.active = False + + self.start() + + + #-------------------------------------------------------------------------- + + def run(self): + """run method""" + pyfile = None + + while True: + del pyfile + self.active = False #: sets the thread inactive when it is ready to get next job + self.active = self.queue.get() + pyfile = self.active + + if self.active == "quit": + self.active = False + self.m.threads.remove(self) + return True + + try: + if not pyfile.hasPlugin(): + continue + # this pyfile was deleted while queueing + + pyfile.plugin.checkForSameFiles(starting=True) + self.m.core.log.info(_("Download starts: %s" % pyfile.name)) + + # start download + self.m.core.addonManager.downloadPreparing(pyfile) + pyfile.error = "" + pyfile.plugin.preprocessing(self) + + self.m.core.log.info(_("Download finished: %s") % pyfile.name) + self.m.core.addonManager.downloadFinished(pyfile) + self.m.core.files.checkPackageFinished(pyfile) + + except NotImplementedError: + self.m.core.log.error(_("Plugin %s is missing a function.") % pyfile.pluginname) + pyfile.setStatus("failed") + pyfile.error = "Plugin does not work" + self.clean(pyfile) + continue + + except Abort: + try: + self.m.core.log.info(_("Download aborted: %s") % pyfile.name) + except Exception: + pass + + pyfile.setStatus("aborted") + + if self.m.core.debug: + traceback.print_exc() + + self.clean(pyfile) + continue + + except Reconnect: + self.queue.put(pyfile) + # pyfile.req.clearCookies() + + while self.m.reconnecting.isSet(): + sleep(0.5) + + continue + + except Retry, e: + reason = e.args[0] + self.m.core.log.info(_("Download restarted: %(name)s | %(msg)s") % {"name": pyfile.name, "msg": reason}) + self.queue.put(pyfile) + continue + + except Fail, e: + msg = e.args[0] + + if msg == "offline": + pyfile.setStatus("offline") + self.m.core.log.warning(_("Download is offline: %s") % pyfile.name) + elif msg == "temp. offline": + pyfile.setStatus("temp. offline") + self.m.core.log.warning(_("Download is temporary offline: %s") % pyfile.name) + else: + pyfile.setStatus("failed") + self.m.core.log.warning(_("Download failed: %(name)s | %(msg)s") % {"name": pyfile.name, "msg": msg}) + pyfile.error = msg + + if self.m.core.debug: + traceback.print_exc() + + self.m.core.addonManager.downloadFailed(pyfile) + self.clean(pyfile) + continue + + except pycurl.error, e: + if len(e.args) == 2: + code, msg = e.args + else: + code = 0 + msg = e.args + + self.m.core.log.debug("pycurl exception %s: %s" % (code, msg)) + + if code in (7, 18, 28, 52, 56): + self.m.core.log.warning(_("Couldn't connect to host or connection reset, waiting 1 minute and retry.")) + wait = time() + 60 + + pyfile.waitUntil = wait + pyfile.setStatus("waiting") + while time() < wait: + sleep(1) + if pyfile.abort: + break + + if pyfile.abort: + self.m.core.log.info(_("Download aborted: %s") % pyfile.name) + pyfile.setStatus("aborted") + + self.clean(pyfile) + else: + self.queue.put(pyfile) + + continue + + else: + pyfile.setStatus("failed") + self.m.core.log.error("pycurl error %s: %s" % (code, msg)) + if self.m.core.debug: + traceback.print_exc() + self.writeDebugReport(pyfile) + + self.m.core.addonManager.downloadFailed(pyfile) + + self.clean(pyfile) + continue + + except SkipDownload, e: + pyfile.setStatus("skipped") + + self.m.core.log.info(_("Download skipped: %(name)s due to %(plugin)s") % {"name": pyfile.name, "plugin": e.message}) + + self.clean(pyfile) + + self.m.core.files.checkPackageFinished(pyfile) + + self.active = False + self.m.core.files.save() + + continue + + except Exception, e: + pyfile.setStatus("failed") + self.m.core.log.warning(_("Download failed: %(name)s | %(msg)s") % {"name": pyfile.name, "msg": str(e)}) + pyfile.error = str(e) + + if self.m.core.debug: + traceback.print_exc() + self.writeDebugReport(pyfile) + + self.m.core.addonManager.downloadFailed(pyfile) + self.clean(pyfile) + continue + + finally: + self.m.core.files.save() + pyfile.checkIfProcessed() + exc_clear() + + # pyfile.plugin.req.clean() + + self.active = False + pyfile.finishIfDone() + self.m.core.files.save() + + + def put(self, job): + """assing job to thread""" + self.queue.put(job) + + + def stop(self): + """stops the thread""" + self.put("quit") diff --git a/pyload/manager/thread/Info.py b/pyload/manager/thread/Info.py new file mode 100644 index 000000000..856c8facf --- /dev/null +++ b/pyload/manager/thread/Info.py @@ -0,0 +1,221 @@ +# -*- coding: utf-8 -*- +# @author: RaNaN + +import traceback + +from Queue import Queue +from copy import copy +from os import listdir, stat +from os.path import join +from pprint import pformat +from sys import exc_info, exc_clear +from time import sleep, time, strftime, gmtime +from types import MethodType + +from pyload.api import OnlineStatus +from pyload.datatype.File import PyFile +from pyload.manager.thread.Plugin import PluginThread + + +class InfoThread(PluginThread): + + def __init__(self, manager, data, pid=-1, rid=-1, add=False): + """Constructor""" + PluginThread.__init__(self, manager) + + self.data = data + self.pid = pid #: package id + # [ .. (name, plugin) .. ] + + self.rid = rid #: result id + self.add = add #: add packages instead of return result + + self.cache = [] #: accumulated data + + self.start() + + + def run(self): + """run method""" + + plugins = {} + container = [] + + for url, plugintype, pluginname in self.data: + # filter out container plugins + if plugintype == 'container': + container.appen((pluginname, url)) + else: + if (plugintype, pluginname) in plugins: + plugins[(plugintype, pluginname)].append(url) + else: + plugins[(plugintype, pluginname)] = [url] + + # directly write to database + if self.pid > -1: + for (plugintype, pluginname), urls in plugins.iteritems(): + plugin = self.m.core.pluginManager.getPlugin(plugintype, pluginname, True) + if hasattr(plugin, "getInfo"): + self.fetchForPlugin(pluginname, plugin, urls, self.updateDB) + self.m.core.files.save() + + elif self.add: + for (plugintype, pluginname), urls in plugins.iteritems(): + plugin = self.m.core.pluginManager.getPlugin(plugintype, pluginname, True) + if hasattr(plugin, "getInfo"): + self.fetchForPlugin(pluginname, plugin, urls, self.updateCache, True) + + else: + # generate default result + result = [(url, 0, 3, url) for url in urls] + + self.updateCache(pluginname, result) + + packs = parseNames([(name, url) for name, x, y, url in self.cache]) + + self.m.log.debug("Fetched and generated %d packages" % len(packs)) + + for k, v in packs: + self.m.core.api.addPackage(k, v) + + # empty cache + del self.cache[:] + + else: #: post the results + + for name, url in container: + # attach container content + try: + data = self.decryptContainer(name, url) + except Exception: + traceback.print_exc() + self.m.log.error("Could not decrypt container.") + data = [] + + for url, plugintype, pluginname in data: + try: + plugins[plugintype][pluginname].append(url) + except Exception: + plugins[plugintype][pluginname] = [url] + + self.m.infoResults[self.rid] = {} + + for plugintype, pluginname, urls in plugins.iteritems(): + plugin = self.m.core.pluginManager.getPlugin(plugintype, pluginname, True) + if hasattr(plugin, "getInfo"): + self.fetchForPlugin(pluginname, plugin, urls, self.updateResult, True) + + # force to process cache + if self.cache: + self.updateResult(pluginname, [], True) + + else: + # generate default result + result = [(url, 0, 3, url) for url in urls] + + self.updateResult(pluginname, result, True) + + self.m.infoResults[self.rid]['ALL_INFO_FETCHED'] = {} + + self.m.timestamp = time() + 5 * 60 + + + def updateDB(self, plugin, result): + self.m.core.files.updateFileInfo(result, self.pid) + + + def updateResult(self, plugin, result, force=False): + # parse package name and generate result + # accumulate results + + self.cache.extend(result) + + if len(self.cache) >= 20 or force: + # used for package generating + tmp = [(name, (url, OnlineStatus(name, plugin, "unknown", status, int(size)))) for name, size, status, url in self.cache] + + data = parseNames(tmp) + result = {} + for k, v in data.iteritems(): + for url, status in v: + status.packagename = k + result[url] = status + + self.m.setInfoResults(self.rid, result) + + self.cache = [] + + + def updateCache(self, plugin, result): + self.cache.extend(result) + + + def fetchForPlugin(self, pluginname, plugin, urls, cb, err=None): + try: + result = [] #: result loaded from cache + process = [] #: urls to process + for url in urls: + if url in self.m.infoCache: + result.append(self.m.infoCache[url]) + else: + process.append(url) + + if result: + self.m.log.debug("Fetched %d values from cache for %s" % (len(result), pluginname)) + cb(pluginname, result) + + if process: + self.m.log.debug("Run Info Fetching for %s" % pluginname) + for result in plugin.getInfo(process): + # result = [ .. (name, size, status, url) .. ] + if not type(result) == list: + result = [result] + + for res in result: + self.m.infoCache[res[3]] = res # : why don't assign res dict directly? + + cb(pluginname, result) + + self.m.log.debug("Finished Info Fetching for %s" % pluginname) + except Exception, e: + self.m.log.warning(_("Info Fetching for %(name)s failed | %(err)s") % {"name": pluginname, "err": str(e)}) + if self.m.core.debug: + traceback.print_exc() + + # generate default results + if err: + result = [(url, 0, 3, url) for url in urls] + cb(pluginname, result) + + + def decryptContainer(self, plugin, url): + data = [] + # only works on container plugins + + self.m.log.debug("Pre decrypting %s with %s" % (url, plugin)) + + # dummy pyfile + pyfile = PyFile(self.m.core.files, -1, url, url, 0, 0, "", plugin, -1, -1) + + pyfile.initPlugin() + + # little plugin lifecycle + try: + pyfile.plugin.setup() + pyfile.plugin.loadToDisk() + pyfile.plugin.decrypt(pyfile) + pyfile.plugin.deleteTmp() + + for pack in pyfile.plugin.packages: + pyfile.plugin.urls.extend(pack[1]) + + data = self.m.core.pluginManager.parseUrls(pyfile.plugin.urls) + + self.m.log.debug("Got %d links." % len(data)) + + except Exception, e: + self.m.log.debug("Pre decrypting error: %s" % str(e)) + finally: + pyfile.release() + + return data diff --git a/pyload/manager/thread/Plugin.py b/pyload/manager/thread/Plugin.py new file mode 100644 index 000000000..2621bc861 --- /dev/null +++ b/pyload/manager/thread/Plugin.py @@ -0,0 +1,131 @@ +# -*- coding: utf-8 -*- +# @author: RaNaN + +from __future__ import with_statement + +import traceback + +from Queue import Queue +from copy import copy +from os import listdir, stat +from os.path import join +from pprint import pformat +from sys import exc_info, exc_clear +from time import sleep, time, strftime, gmtime +from types import MethodType + +from pyload.api import OnlineStatus +from pyload.datatype.File import PyFile +from pyload.plugin.Plugin import Abort, Fail, Reconnect, Retry, SkipDownload +from pyload.utils.packagetools import parseNames +from pyload.utils import fs_join + + +class PluginThread(Thread): + """abstract base class for thread types""" + + def __init__(self, manager): + """Constructor""" + Thread.__init__(self) + self.setDaemon(True) + self.m = manager #: thread manager + + + def writeDebugReport(self, pyfile): + """ writes a + :return: + """ + + dump_name = "debug_%s_%s.zip" % (pyfile.pluginname, strftime("%d-%m-%Y_%H-%M-%S")) + dump = self.getDebugDump(pyfile) + + try: + import zipfile + + zip = zipfile.ZipFile(dump_name, "w") + + for f in listdir(join("tmp", pyfile.pluginname)): + try: + # avoid encoding errors + zip.write(join("tmp", pyfile.pluginname, f), fs_join(pyfile.pluginname, f)) + except Exception: + pass + + info = zipfile.ZipInfo(fs_join(pyfile.pluginname, "debug_Report.txt"), gmtime()) + info.external_attr = 0644 << 16L #: change permissions + + zip.writestr(info, dump) + zip.close() + + if not stat(dump_name).st_size: + raise Exception("Empty Zipfile") + + except Exception, e: + self.m.log.debug("Error creating zip file: %s" % e) + + dump_name = dump_name.replace(".zip", ".txt") + with open(dump_name, "wb") as f: + f.write(dump) + + self.m.core.log.info("Debug Report written to %s" % dump_name) + + + def getDebugDump(self, pyfile): + dump = "pyLoad %s Debug Report of %s %s \n\nTRACEBACK:\n %s \n\nFRAMESTACK:\n" % ( + self.m.core.api.getServerVersion(), pyfile.pluginname, pyfile.plugin.__version, traceback.format_exc()) + + tb = exc_info()[2] + stack = [] + while tb: + stack.append(tb.tb_frame) + tb = tb.tb_next + + for frame in stack[1:]: + dump += "\nFrame %s in %s at line %s\n" % (frame.f_code.co_name, + frame.f_code.co_filename, + frame.f_lineno) + + for key, value in frame.f_locals.items(): + dump += "\t%20s = " % key + try: + dump += pformat(value) + "\n" + except Exception, e: + dump += "<ERROR WHILE PRINTING VALUE> " + str(e) + "\n" + + del frame + + del stack #: delete it just to be sure... + + dump += "\n\nPLUGIN OBJECT DUMP: \n\n" + + for name in dir(pyfile.plugin): + attr = getattr(pyfile.plugin, name) + if not name.endswith("__") and type(attr) != MethodType: + dump += "\t%20s = " % name + try: + dump += pformat(attr) + "\n" + except Exception, e: + dump += "<ERROR WHILE PRINTING VALUE> " + str(e) + "\n" + + dump += "\nPYFILE OBJECT DUMP: \n\n" + + for name in dir(pyfile): + attr = getattr(pyfile, name) + if not name.endswith("__") and type(attr) != MethodType: + dump += "\t%20s = " % name + try: + dump += pformat(attr) + "\n" + except Exception, e: + dump += "<ERROR WHILE PRINTING VALUE> " + str(e) + "\n" + + if pyfile.pluginname in self.m.core.config.plugin: + dump += "\n\nCONFIG: \n\n" + dump += pformat(self.m.core.config.plugin[pyfile.pluginname]) + "\n" + + return dump + + + def clean(self, pyfile): + """ set thread unactive and release pyfile """ + self.active = True #: release pyfile but lets the thread active + pyfile.release() diff --git a/pyload/manager/thread/Server.py b/pyload/manager/thread/Server.py new file mode 100644 index 000000000..a26ffe6a1 --- /dev/null +++ b/pyload/manager/thread/Server.py @@ -0,0 +1,122 @@ +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +import logging +import os +import threading + +core = None +setup = None +log = logging.getLogger("log") + + +class WebServer(threading.Thread): + + def __init__(self, pycore): + global core + + threading.Thread.__init__(self) + self.core = pycore + core = pycore + self.running = True + self.server = pycore.config.get("webui", "server") + self.https = pycore.config.get("webui", "https") + self.cert = pycore.config.get("ssl", "cert") + self.key = pycore.config.get("ssl", "key") + self.host = pycore.config.get("webui", "host") + self.port = pycore.config.get("webui", "port") + + self.setDaemon(True) + + + def run(self): + import pyload.webui as webinterface + global webinterface + + reset = False + + if self.https and (not os.exists(self.cert) or not os.exists(self.key)): + log.warning(_("SSL certificates not found.")) + self.https = False + + if self.server in ("lighttpd", "nginx"): + log.warning(_("Sorry, we dropped support for starting %s directly within pyLoad") % self.server) + log.warning(_("You can use the threaded server which offers good performance and ssl,")) + log.warning(_("of course you can still use your existing %s with pyLoads fastcgi server") % self.server) + log.warning(_("sample configs are located in the pyload/web/servers directory")) + reset = True + elif self.server == "fastcgi": + try: + import flup + except Exception: + log.warning(_("Can't use %(server)s, python-flup is not installed!") % { + "server": self.server}) + reset = True + + if reset or self.server == "lightweight": + if os.name != "nt": + try: + import bjoern + except Exception, e: + log.error(_("Error importing lightweight server: %s") % e) + log.warning(_("You need to download and compile bjoern, https://github.com/jonashaag/bjoern")) + log.warning(_("Copy the boern.so file to lib/Python/Lib or use setup.py install")) + log.warning(_("Of course you need to be familiar with linux and know how to compile software")) + self.server = "auto" + else: + self.core.log.info(_("Server set to threaded, due to known performance problems on windows.")) + self.core.config.set("webui", "server", "threaded") + self.server = "threaded" + + if self.server == "threaded": + self.start_threaded() + elif self.server == "fastcgi": + self.start_fcgi() + elif self.server == "lightweight": + self.start_lightweight() + else: + self.start_auto() + + + def start_auto(self): + if self.https: + log.warning(_("This server offers no SSL, please consider using `threaded` instead")) + + self.core.log.info(_("Starting builtin webserver: %(host)s:%(port)d") % {"host": self.host, "port": self.port}) + webinterface.run_auto(host=self.host, port=self.port) + + + def start_threaded(self): + if self.https: + self.core.log.info( + _("Starting threaded SSL webserver: %(host)s:%(port)d") % {"host": self.host, "port": self.port}) + else: + self.cert = "" + self.key = "" + self.core.log.info( + _("Starting threaded webserver: %(host)s:%(port)d") % {"host": self.host, "port": self.port}) + + webinterface.run_threaded(host=self.host, port=self.port, cert=self.cert, key=self.key) + + + def start_fcgi(self): + self.core.log.info(_("Starting fastcgi server: %(host)s:%(port)d") % {"host": self.host, "port": self.port}) + try: + webinterface.run_fcgi(host=self.host, port=self.port) + + except ValueError: #@TODO: Fix https://github.com/pyload/pyload/issues/1145 + pass + + + def start_lightweight(self): + if self.https: + log.warning(_("This server offers no SSL, please consider using `threaded` instead")) + + self.core.log.info( + _("Starting lightweight webserver (bjoern): %(host)s:%(port)d") % {"host": self.host, "port": self.port}) + webinterface.run_lightweight(host=self.host, port=self.port) + + + def quit(self): + self.running = False diff --git a/pyload/manager/thread/__init__.py b/pyload/manager/thread/__init__.py new file mode 100644 index 000000000..40a96afc6 --- /dev/null +++ b/pyload/manager/thread/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/pyload/network/Browser.py b/pyload/network/Browser.py new file mode 100644 index 000000000..482c2320a --- /dev/null +++ b/pyload/network/Browser.py @@ -0,0 +1,151 @@ +# -*- coding: utf-8 -*- + +from logging import getLogger + +from pyload.network.HTTPRequest import HTTPRequest +from pyload.network.HTTPDownload import HTTPDownload + + +class Browser(object): + __slots__ = ("log", "options", "bucket", "cj", "_size", "http", "dl") + + + def __init__(self, bucket=None, options={}): + self.log = getLogger("log") + + self.options = options #: holds pycurl options + self.bucket = bucket + + self.cj = None #: needs to be setted later + self._size = 0 + + self.renewHTTPRequest() + self.dl = None + + + def renewHTTPRequest(self): + if hasattr(self, "http"): + self.http.close() + self.http = HTTPRequest(self.cj, self.options) + + + def setLastURL(self, val): + self.http.lastURL = val + + # tunnel some attributes from HTTP Request to Browser + lastEffectiveURL = property(lambda self: self.http.lastEffectiveURL) + lastURL = property(lambda self: self.http.lastURL, setLastURL) + code = property(lambda self: self.http.code) + cookieJar = property(lambda self: self.cj) + + + def setCookieJar(self, cj): + self.cj = cj + self.http.cj = cj + + + @property + def speed(self): + if self.dl: + return self.dl.speed + return 0 + + + @property + def size(self): + if self._size: + return self._size + if self.dl: + return self.dl.size + return 0 + + + @property + def arrived(self): + return self.dl.arrived if self.dl else 0 + + + @property + def percent(self): + return (self.arrived * 100) / self.size if self.size else 0 + + + def clearCookies(self): + if self.cj: + self.cj.clear() + self.http.clearCookies() + + + def clearReferer(self): + self.http.lastURL = None + + + def abortDownloads(self): + self.http.abort = True + if self.dl: + self._size = self.dl.size + self.dl.abort = True + + + def httpDownload(self, url, filename, get={}, post={}, ref=True, cookies=True, chunks=1, resume=False, + progressNotify=None, disposition=False): + """ this can also download ftp """ + self._size = 0 + self.dl = HTTPDownload(url, filename, get, post, self.lastEffectiveURL if ref else None, + self.cj if cookies else None, self.bucket, self.options, progressNotify, disposition) + name = self.dl.download(chunks, resume) + self._size = self.dl.size + + self.dl = None + + return name + + + def load(self, *args, **kwargs): + """ retrieves page """ + return self.http.load(*args, **kwargs) + + + def putHeader(self, name, value): + """ add a header to the request """ + self.http.putHeader(name, value) + + + def addAuth(self, pwd): + """Adds user and pw for http auth + + :param pwd: string, user:password + """ + self.options['auth'] = pwd + self.renewHTTPRequest() #: we need a new request + + + def removeAuth(self): + if "auth" in self.options: + del self.options['auth'] + self.renewHTTPRequest() + + + def setOption(self, name, value): + """Adds an option to the request, see HTTPRequest for existing ones""" + self.options[name] = value + + + def deleteOption(self, name): + if name in self.options: + del self.options[name] + + + def clearHeaders(self): + self.http.clearHeaders() + + + def close(self): + """ cleanup """ + if hasattr(self, "http"): + self.http.close() + del self.http + if hasattr(self, "dl"): + del self.dl + if hasattr(self, "cj"): + del self.cj diff --git a/pyload/network/Bucket.py b/pyload/network/Bucket.py new file mode 100644 index 000000000..b7f33f993 --- /dev/null +++ b/pyload/network/Bucket.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- +# @author: RaNaN + +import threading + +from time import time + + +MIN_RATE = 10240 #: 10kb minimum rate + + +class Bucket(object): + + def __init__(self): + self.rate = 0 #: bytes per second, maximum targeted throughput + self.tokens = 0 + self.timestamp = time() + self.lock = threading.Lock() + + + def __nonzero__(self): + return self.rate >= MIN_RATE + + + def setRate(self, rate): + self.lock.acquire() + self.rate = int(rate) + self.lock.release() + + + def consumed(self, amount): + """ return the time the process has to sleep, after it consumed a specified amount """ + if self.rate < MIN_RATE: + return 0 #: May become unresponsive otherwise + + self.lock.acquire() + self.calc_tokens() + self.tokens -= amount + + time = -self.tokens / float(self.rate) if self.tokens < 0 else 0 + + self.lock.release() + return time + + + def calc_tokens(self): + if self.tokens < self.rate: + now = time() + delta = self.rate * (now - self.timestamp) + self.tokens = min(self.rate, self.tokens + delta) + self.timestamp = now diff --git a/pyload/network/CookieJar.py b/pyload/network/CookieJar.py new file mode 100644 index 000000000..a970a08e5 --- /dev/null +++ b/pyload/network/CookieJar.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +# @author: RaNaN + +import Cookie + +from datetime import datetime, timedelta +from time import time + + +# monkey patch for 32 bit systems +def _getdate(future=0, weekdayname=Cookie._weekdayname, monthname=Cookie._monthname): + dt = datetime.now() + timedelta(seconds=int(future)) + return "%s, %02d %3s %4d %02d:%02d:%02d GMT" % \ + (weekdayname[dt.weekday()], dt.day, monthname[dt.month], dt.year, dt.hour, dt.minute, dt.second) + +Cookie._getdate = _getdate + + +class CookieJar(Cookie.SimpleCookie): + + def getCookie(self, name): + return self[name].value + + + def setCookie(self, domain, name, value, path="/", exp=None, secure="FALSE"): + self[name] = value + self[name]['domain'] = domain + self[name]['path'] = path + + # Value of expires should be integer if possible + # otherwise the cookie won't be used + if not exp: + expires = time() + 3600 * 24 * 180 + else: + try: + expires = int(exp) + except ValueError: + expires = exp + + self[name]['expires'] = expires + + if secure == "TRUE": + self[name]['secure'] = secure diff --git a/pyload/network/HTTPChunk.py b/pyload/network/HTTPChunk.py new file mode 100644 index 000000000..85c20d519 --- /dev/null +++ b/pyload/network/HTTPChunk.py @@ -0,0 +1,318 @@ +# -*- coding: utf-8 -*- +# @author: RaNaN + +from os import remove, stat, fsync +from os.path import exists +from time import sleep +from re import search +from pyload.utils import fs_encode +import codecs +import pycurl +import urllib + +from pyload.network.HTTPRequest import HTTPRequest + + +class WrongFormat(Exception): + pass + + +class ChunkInfo(object): + + def __init__(self, name): + self.name = unicode(name) + self.size = 0 + self.resume = False + self.chunks = [] + + + def __repr__(self): + ret = "ChunkInfo: %s, %s\n" % (self.name, self.size) + for i, c in enumerate(self.chunks): + ret += "%s# %s\n" % (i, c[1]) + return ret + + + def setSize(self, size): + self.size = int(size) + + + def addChunk(self, name, range): + self.chunks.append((name, range)) + + + def clear(self): + self.chunks = [] + + + def createChunks(self, chunks): + self.clear() + chunk_size = self.size / chunks + + current = 0 + for i in xrange(chunks): + end = self.size - 1 if (i == chunks - 1) else current + chunk_size + self.addChunk("%s.chunk%s" % (self.name, i), (current, end)) + current += chunk_size + 1 + + + def save(self): + fs_name = fs_encode("%s.chunks" % self.name) + fh = codecs.open(fs_name, "w", "utf_8") + fh.write("name:%s\n" % self.name) + fh.write("size:%s\n" % self.size) + for i, c in enumerate(self.chunks): + fh.write("#%d:\n" % i) + fh.write("\tname:%s\n" % c[0]) + fh.write("\trange:%i-%i\n" % c[1]) + fh.close() + + + @staticmethod + def load(name): + fs_name = fs_encode("%s.chunks" % name) + if not exists(fs_name): + raise IOError() + fh = codecs.open(fs_name, "r", "utf_8") + name = fh.readline()[:-1] + size = fh.readline()[:-1] + if name.startswith("name:") and size.startswith("size:"): + name = name[5:] + size = size[5:] + else: + fh.close() + raise WrongFormat() + ci = ChunkInfo(name) + ci.loaded = True + ci.setSize(size) + while True: + if not fh.readline(): #: skip line + break + name = fh.readline()[1:-1] + range = fh.readline()[1:-1] + if name.startswith("name:") and range.startswith("range:"): + name = name[5:] + range = range[6:].split("-") + else: + raise WrongFormat() + + ci.addChunk(name, (long(range[0]), long(range[1]))) + fh.close() + return ci + + + def remove(self): + fs_name = fs_encode("%s.chunks" % self.name) + if exists(fs_name): remove(fs_name) + + + def getCount(self): + return len(self.chunks) + + + def getChunkName(self, index): + return self.chunks[index][0] + + + def getChunkRange(self, index): + return self.chunks[index][1] + + +class HTTPChunk(HTTPRequest): + + def __init__(self, id, parent, range=None, resume=False): + self.id = id + self.p = parent #: HTTPDownload instance + self.range = range #: tuple (start, end) + self.resume = resume + self.log = parent.log + + self.size = range[1] - range[0] if range else -1 + self.arrived = 0 + self.lastURL = self.p.referer + + self.c = pycurl.Curl() + + self.header = "" + self.headerParsed = False #: indicates if the header has been processed + + self.fp = None #: file handle + + self.initHandle() + self.setInterface(self.p.options) + + self.BOMChecked = False #: check and remove byte order mark + + self.rep = None + + self.sleep = 0.000 + self.lastSize = 0 + + + def __repr__(self): + return "<HTTPChunk id=%d, size=%d, arrived=%d>" % (self.id, self.size, self.arrived) + + + @property + def cj(self): + return self.p.cj + + + def getHandle(self): + """ returns a Curl handle ready to use for perform/multiperform """ + + self.setRequestContext(self.p.url, self.p.get, self.p.post, self.p.referer, self.p.cj) + self.c.setopt(pycurl.WRITEFUNCTION, self.writeBody) + self.c.setopt(pycurl.HEADERFUNCTION, self.writeHeader) + + # request all bytes, since some servers in russia seems to have a defect arihmetic unit + + fs_name = fs_encode(self.p.info.getChunkName(self.id)) + if self.resume: + self.fp = open(fs_name, "ab") + self.arrived = self.fp.tell() + if not self.arrived: + self.arrived = stat(fs_name).st_size + + if self.range: + # do nothing if chunk already finished + if self.arrived + self.range[0] >= self.range[1]: + return None + + if self.id == len(self.p.info.chunks) - 1: #: as last chunk dont set end range, so we get everything + range = "%i-" % (self.arrived + self.range[0]) + else: + range = "%i-%i" % (self.arrived + self.range[0], min(self.range[1] + 1, self.p.size - 1)) + + self.log.debug("Chunked resume with range %s" % range) + self.c.setopt(pycurl.RANGE, range) + else: + self.log.debug("Resume File from %i" % self.arrived) + self.c.setopt(pycurl.RESUME_FROM, self.arrived) + + else: + if self.range: + if self.id == len(self.p.info.chunks) - 1: #: see above + range = "%i-" % self.range[0] + else: + range = "%i-%i" % (self.range[0], min(self.range[1] + 1, self.p.size - 1)) + + self.log.debug("Chunked with range %s" % range) + self.c.setopt(pycurl.RANGE, range) + + self.fp = open(fs_name, "wb") + + return self.c + + + def writeHeader(self, buf): + self.header += buf + #@TODO forward headers?, this is possibly unneeeded, when we just parse valid 200 headers + # as first chunk, we will parse the headers + if not self.range and self.header.endswith("\r\n\r\n"): + self.parseHeader() + elif not self.range and buf.startswith("150") and "data connection" in buf.lower(): #: ftp file size parsing + size = search(r"(\d+) bytes", buf) + if size: + self.p.size = int(size.group(1)) + self.p.chunkSupport = True + + self.headerParsed = True + + + def writeBody(self, buf): + # ignore BOM, it confuses unrar + if not self.BOMChecked: + if [ord(b) for b in buf[:3]] == [239, 187, 191]: + buf = buf[3:] + self.BOMChecked = True + + size = len(buf) + + self.arrived += size + + self.fp.write(buf) + + if self.p.bucket: + sleep(self.p.bucket.consumed(size)) + else: + # Avoid small buffers, increasing sleep time slowly if buffer size gets smaller + # otherwise reduce sleep time percentual (values are based on tests) + # So in general cpu time is saved without reducing bandwith too much + + if size < self.lastSize: + self.sleep += 0.002 + else: + self.sleep *= 0.7 + + self.lastSize = size + + sleep(self.sleep) + + if self.range and self.arrived > self.size: + return 0 #: close if we have enough data + + + def parseHeader(self): + """parse data from recieved header""" + for orgline in self.decodeResponse(self.header).splitlines(): + line = orgline.strip().lower() + + if line.startswith("accept-ranges") and "bytes" in line: + self.p.chunkSupport = True + + if line.startswith("content-disposition") and ("filename=" in line or "filename*=" in line): + if "filename*=" in line: + # extended version according to RFC 6266 and RFC 5987. + encoding = line.partition("*=")[2].partition("''")[0] + name = orgline.partition("''")[2] + name = urllib.unquote(str(name)).decode(charEnc(encoding)) + else: + # basic version, US-ASCII only + name = orgline.partition("filename=")[2] + + name = name.replace('"', "").replace("'", "").replace(";", "").replace("/", "_").strip() + self.p.nameDisposition = name + + self.log.debug("Content-Disposition: %s" % name) + + if not self.resume and line.startswith("content-length"): + self.p.size = int(line.split(":")[1]) + + self.headerParsed = True + + + def stop(self): + """The download will not proceed after next call of writeBody""" + self.range = [0, 0] + self.size = 0 + + + def resetRange(self): + """ Reset the range, so the download will load all data available """ + self.range = None + + + def setRange(self, range): + self.range = range + self.size = range[1] - range[0] + + + def flushFile(self): + """ flush and close file """ + self.fp.flush() + fsync(self.fp.fileno()) #: make sure everything was written to disk + self.fp.close() #: needs to be closed, or merging chunks will fail + + + def close(self): + """ closes everything, unusable after this """ + if self.fp: self.fp.close() + self.c.close() + if hasattr(self, "p"): + del self.p + + +def charEnc(enc): + return {'utf-8' : "utf_8", + 'iso-8859-1': "latin_1"}.get(enc, "unknown") diff --git a/pyload/network/HTTPDownload.py b/pyload/network/HTTPDownload.py new file mode 100644 index 000000000..2276af609 --- /dev/null +++ b/pyload/network/HTTPDownload.py @@ -0,0 +1,322 @@ +# -*- coding: utf-8 -*- +# @author: RaNaN + +from __future__ import with_statement + +import shutil + +import pycurl + +from os import remove, fsync +from os.path import dirname +from time import sleep, time +from logging import getLogger + +from pyload.network.HTTPChunk import ChunkInfo, HTTPChunk +from pyload.network.HTTPRequest import BadHeader + +from pyload.plugin.Plugin import Abort +from pyload.utils import fs_join, fs_encode + + +class HTTPDownload(object): + """ loads a url http + ftp """ + + def __init__(self, url, filename, get={}, post={}, referer=None, cj=None, bucket=None, + options={}, progress=None, disposition=False): + self.url = url + self.filename = filename #: complete file destination, not only name + self.get = get + self.post = post + self.referer = referer + self.cj = cj #: cookiejar if cookies are needed + self.bucket = bucket + self.options = options + self.disposition = disposition + # all arguments + + self.abort = False + self.size = 0 + self.nameDisposition = None #: will be parsed from content disposition + + self.chunks = [] + + self.log = getLogger("log") + + try: + self.info = ChunkInfo.load(filename) + self.info.resume = True #: resume is only possible with valid info file + self.size = self.info.size + self.infoSaved = True + except IOError: + self.info = ChunkInfo(filename) + + self.chunkSupport = True + self.m = pycurl.CurlMulti() + + # needed for speed calculation + self.lastArrived = [] + self.speeds = [] + self.lastSpeeds = [0, 0] + + self.progress = progress + + + @property + def speed(self): + last = [sum(x) for x in self.lastSpeeds if x] + return (sum(self.speeds) + sum(last)) / (1 + len(last)) + + + @property + def arrived(self): + return sum([c.arrived for c in self.chunks]) + + + @property + def percent(self): + return (self.arrived * 100) / self.size if self.size else 0 + + def _copyChunks(self): + init = fs_encode(self.info.getChunkName(0)) #: initial chunk name + + if self.info.getCount() > 1: + with open(init, "rb+") as fo: #: first chunkfile + for i in xrange(1, self.info.getCount()): + # input file + fo.seek( + self.info.getChunkRange(i - 1)[1] + 1) #: seek to beginning of chunk, to get rid of overlapping chunks + fname = fs_encode("%s.chunk%d" % (self.filename, i)) + with open(fname, "rb") as fi: + buf = 32 * 1024 + while True: #: copy in chunks, consumes less memory + data = fi.read(buf) + if not data: + break + fo.write(data) + if fo.tell() < self.info.getChunkRange(i)[1]: + reshutil.move(init) + self.info.reshutil.move() #: there are probably invalid chunks + raise Exception("Downloaded content was smaller than expected. Try to reduce download connections.") + reshutil.move(fname) #: remove chunk + + if self.nameDisposition and self.disposition: + self.filename = fs_join(dirname(self.filename), self.nameDisposition) + + shutil.move(init, fs_encode(self.filename)) + self.info.reshutil.move() #: remove info file + + + def download(self, chunks=1, resume=False): + """ returns new filename or None """ + + chunks = max(1, chunks) + resume = self.info.resume and resume + + try: + self._download(chunks, resume) + except pycurl.error, e: + # code 33 - no resume + code = e.args[0] + if resume is True and code == 33: + # try again without resume + self.log.debug("Errno 33 -> Restart without resume") + + # remove old handles + for chunk in self.chunks: + self.closeChunk(chunk) + + return self._download(chunks, False) + else: + raise + finally: + self.close() + + if self.nameDisposition and self.disposition: + return self.nameDisposition + return None + + + def _download(self, chunks, resume): + if not resume: + self.info.clear() + self.info.addChunk("%s.chunk0" % self.filename, (0, 0)) #: create an initial entry + self.info.save() + + self.chunks = [] + + init = HTTPChunk(0, self, None, resume) #: initial chunk that will load complete file (if needed) + + self.chunks.append(init) + self.m.add_handle(init.getHandle()) + + lastFinishCheck = 0 + lastTimeCheck = 0 + chunksDone = set() #: list of curl handles that are finished + chunksCreated = False + done = False + if self.info.getCount() is 0: #: This is a resume, if we were chunked originally assume still can + self.chunkSupport = False + + while 1: + # need to create chunks + if not chunksCreated and self.chunkSupport and self.size: #: will be setted later by first chunk + + if not resume: + self.info.setSize(self.size) + self.info.createChunks(chunks) + self.info.save() + + chunks = self.info.getCount() + + init.setRange(self.info.getChunkRange(0)) + + for i in xrange(1, chunks): + c = HTTPChunk(i, self, self.info.getChunkRange(i), resume) + + handle = c.getHandle() + if handle: + self.chunks.append(c) + self.m.add_handle(handle) + else: + # close immediatly + self.log.debug("Invalid curl handle -> closed") + c.close() + + chunksCreated = True + + while 1: + ret, num_handles = self.m.perform() + if ret != pycurl.E_CALL_MULTI_PERFORM: + break + + t = time() + + # reduce these calls + while lastFinishCheck + 0.5 < t: + # list of failed curl handles + failed = [] + ex = None #: save only last exception, we can only raise one anyway + + num_q, ok_list, err_list = self.m.info_read() + for c in ok_list: + chunk = self.findChunk(c) + try: #: check if the header implies success, else add it to failed list + chunk.verifyHeader() + except BadHeader, e: + self.log.debug("Chunk %d failed: %s" % (chunk.id + 1, str(e))) + failed.append(chunk) + ex = e + else: + chunksDone.add(c) + + for c in err_list: + curl, errno, msg = c + chunk = self.findChunk(curl) + # test if chunk was finished + if errno != 23 or "0 !=" not in msg: + failed.append(chunk) + ex = pycurl.error(errno, msg) + self.log.debug("Chunk %d failed: %s" % (chunk.id + 1, str(ex))) + continue + + try: #: check if the header implies success, else add it to failed list + chunk.verifyHeader() + except BadHeader, e: + self.log.debug("Chunk %d failed: %s" % (chunk.id + 1, str(e))) + failed.append(chunk) + ex = e + else: + chunksDone.add(curl) + if not num_q: #: no more infos to get + + # check if init is not finished so we reset download connections + # note that other chunks are closed and downloaded with init too + if failed and init not in failed and init.c not in chunksDone: + self.log.error(_("Download chunks failed, fallback to single connection | %s" % (str(ex)))) + + # list of chunks to clean and remove + to_clean = filter(lambda x: x is not init, self.chunks) + for chunk in to_clean: + self.closeChunk(chunk) + self.chunks.reshutil.move(chunk) + reshutil.move(fs_encode(self.info.getChunkName(chunk.id))) + + # let first chunk load the rest and update the info file + init.resetRange() + self.info.clear() + self.info.addChunk("%s.chunk0" % self.filename, (0, self.size)) + self.info.save() + elif failed: + raise ex + + lastFinishCheck = t + + if len(chunksDone) >= len(self.chunks): + if len(chunksDone) > len(self.chunks): + self.log.warning("Finished download chunks size incorrect, please report bug.") + done = True #: all chunks loaded + + break + + if done: + break #: all chunks loaded + + # calc speed once per second, averaging over 3 seconds + if lastTimeCheck + 1 < t: + diff = [c.arrived - (self.lastArrived[i] if len(self.lastArrived) > i else 0) for i, c in + enumerate(self.chunks)] + + self.lastSpeeds[1] = self.lastSpeeds[0] + self.lastSpeeds[0] = self.speeds + self.speeds = [float(a) / (t - lastTimeCheck) for a in diff] + self.lastArrived = [c.arrived for c in self.chunks] + lastTimeCheck = t + self.updateProgress() + + if self.abort: + raise Abort + + # sleep(0.003) #: supress busy waiting - limits dl speed to (1 / x) * buffersize + self.m.select(1) + + for chunk in self.chunks: + chunk.flushFile() #: make sure downloads are written to disk + + self._copyChunks() + + + def updateProgress(self): + if self.progress: + self.progress(self.percent) + + + def findChunk(self, handle): + """ linear search to find a chunk (should be ok since chunk size is usually low) """ + for chunk in self.chunks: + if chunk.c == handle: + return chunk + + + def closeChunk(self, chunk): + try: + self.m.remove_handle(chunk.c) + except pycurl.error, e: + self.log.debug("Error removing chunk: %s" % str(e)) + finally: + chunk.close() + + + def close(self): + """ cleanup """ + for chunk in self.chunks: + self.closeChunk(chunk) + + self.chunks = [] + if hasattr(self, "m"): + self.m.close() + del self.m + if hasattr(self, "cj"): + del self.cj + if hasattr(self, "info"): + del self.info diff --git a/pyload/network/HTTPRequest.py b/pyload/network/HTTPRequest.py new file mode 100644 index 000000000..74b83cf12 --- /dev/null +++ b/pyload/network/HTTPRequest.py @@ -0,0 +1,321 @@ +# -*- coding: utf-8 -*- +# @author: RaNaN + +from __future__ import with_statement + +import pycurl + +from codecs import getincrementaldecoder, lookup, BOM_UTF8 +from urllib import quote, urlencode +from httplib import responses +from logging import getLogger +from cStringIO import StringIO + +from pyload.plugin.Plugin import Abort, Fail + +from pyload.utils import encode + + +def myquote(url): + return quote(encode(url), safe="%/:=&?~#+!$,;'@()*[]") + + +def myurlencode(data): + data = dict(data) + return urlencode(dict((encode(x), encode(y)) for x, y in data.iteritems())) + +bad_headers = range(400, 404) + range(405, 418) + range(500, 506) + + +class BadHeader(Exception): + + def __init__(self, code, content=""): + Exception.__init__(self, "Bad server response: %s %s" % (code, responses[int(code)])) + self.code = code + self.content = content + + +class HTTPRequest(object): + + def __init__(self, cookies=None, options=None): + self.c = pycurl.Curl() + self.rep = StringIO() + + self.cj = cookies #: cookiejar + + self.lastURL = None + self.lastEffectiveURL = None + self.abort = False + self.code = 0 #: last http code + + self.header = "" + + self.headers = [] #: temporary request header + + self.initHandle() + self.setInterface(options) + + self.c.setopt(pycurl.WRITEFUNCTION, self.write) + self.c.setopt(pycurl.HEADERFUNCTION, self.writeHeader) + + self.log = getLogger("log") + + + def initHandle(self): + """ sets common options to curl handle """ + self.c.setopt(pycurl.FOLLOWLOCATION, 1) + self.c.setopt(pycurl.MAXREDIRS, 10) + self.c.setopt(pycurl.CONNECTTIMEOUT, 30) + self.c.setopt(pycurl.NOSIGNAL, 1) + self.c.setopt(pycurl.NOPROGRESS, 1) + if hasattr(pycurl, "AUTOREFERER"): + self.c.setopt(pycurl.AUTOREFERER, 1) + self.c.setopt(pycurl.SSL_VERIFYPEER, 0) + self.c.setopt(pycurl.LOW_SPEED_TIME, 60) + self.c.setopt(pycurl.LOW_SPEED_LIMIT, 5) + if hasattr(pycurl, "USE_SSL"): + self.c.setopt(pycurl.USE_SSL, pycurl.CURLUSESSL_TRY) + + # self.c.setopt(pycurl.VERBOSE, 1) + + self.c.setopt(pycurl.USERAGENT, + "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:37.0) Gecko/20100101 Firefox/37.0") + + if pycurl.version_info()[7]: + self.c.setopt(pycurl.ENCODING, "gzip, deflate") + self.c.setopt(pycurl.HTTPHEADER, ["Accept: */*", + "Accept-Language: en-US, en", + "Accept-Charset: ISO-8859-1, utf-8;q=0.7,*;q=0.7", + "Connection: keep-alive", + "Keep-Alive: 300", + "Expect:"]) + + + def setInterface(self, options): + + interface, proxy, ipv6 = options['interface'], options['proxies'], options['ipv6'] + + if interface and interface.lower() != "none": + self.c.setopt(pycurl.INTERFACE, str(interface)) + + if proxy: + if proxy['type'] == "socks4": + self.c.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_SOCKS4) + elif proxy['type'] == "socks5": + self.c.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_SOCKS5) + else: + self.c.setopt(pycurl.PROXYTYPE, pycurl.PROXYTYPE_HTTP) + + self.c.setopt(pycurl.PROXY, str(proxy['address'])) + self.c.setopt(pycurl.PROXYPORT, proxy['port']) + + if proxy['username']: + self.c.setopt(pycurl.PROXYUSERPWD, str("%s:%s" % (proxy['username'], proxy['password']))) + + if ipv6: + self.c.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_WHATEVER) + else: + self.c.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_V4) + + if "auth" in options: + self.c.setopt(pycurl.USERPWD, str(options['auth'])) + + if "timeout" in options: + self.c.setopt(pycurl.LOW_SPEED_TIME, options['timeout']) + + + def addCookies(self): + """ put cookies from curl handle to cj """ + if self.cj: + self.cj.addCookies(self.c.getinfo(pycurl.INFO_COOKIELIST)) + + + def getCookies(self): + """ add cookies from cj to curl handle """ + if self.cj: + for c in self.cj.getCookies(): + self.c.setopt(pycurl.COOKIELIST, c) + return + + + def clearCookies(self): + self.c.setopt(pycurl.COOKIELIST, "") + + + def setRequestContext(self, url, get, post, referer, cookies, multipart=False): + """ sets everything needed for the request """ + + url = myquote(url) + + if get: + get = urlencode(get) + url = "%s?%s" % (url, get) + + self.c.setopt(pycurl.URL, url) + self.c.lastUrl = url + + if post: + self.c.setopt(pycurl.POST, 1) + if not multipart: + if type(post) == unicode: + post = str(post) #: unicode not allowed + elif type(post) == str: + pass + else: + post = myurlencode(post) + + self.c.setopt(pycurl.POSTFIELDS, post) + else: + post = [(x, encode(y)) for x, y in post.iteritems()] + self.c.setopt(pycurl.HTTPPOST, post) + else: + self.c.setopt(pycurl.POST, 0) + + if referer and self.lastURL: + self.c.setopt(pycurl.REFERER, str(self.lastURL)) + + if cookies: + self.c.setopt(pycurl.COOKIEFILE, "") + self.c.setopt(pycurl.COOKIEJAR, "") + self.getCookies() + + + def load(self, url, get={}, post={}, referer=True, cookies=True, just_header=False, multipart=False, decode=False, follow_location=True, save_cookies=True): + """ load and returns a given page """ + + self.setRequestContext(url, get, post, referer, cookies, multipart) + + self.header = "" + + self.c.setopt(pycurl.HTTPHEADER, self.headers) + + if post: + self.c.setopt(pycurl.POST, 1) + else: + self.c.setopt(pycurl.HTTPGET, 1) + + if not follow_location: + self.c.setopt(pycurl.FOLLOWLOCATION, 0) + + if just_header: + self.c.setopt(pycurl.NOBODY, 1) + + self.c.perform() + rep = self.header if just_header else self.getResponse() + + if not follow_location: + self.c.setopt(pycurl.FOLLOWLOCATION, 1) + + if just_header: + self.c.setopt(pycurl.NOBODY, 0) + + self.c.setopt(pycurl.POSTFIELDS, "") + self.lastEffectiveURL = self.c.getinfo(pycurl.EFFECTIVE_URL) + self.code = self.verifyHeader() + + if save_cookies: + self.addCookies() + + if decode: + rep = self.decodeResponse(rep) + + return rep + + + def verifyHeader(self): + """ raise an exceptions on bad headers """ + code = int(self.c.getinfo(pycurl.RESPONSE_CODE)) + if code in bad_headers: + # 404 will NOT raise an exception + raise BadHeader(code, self.getResponse()) + return code + + + def checkHeader(self): + """ check if header indicates failure""" + return int(self.c.getinfo(pycurl.RESPONSE_CODE)) not in bad_headers + + + def getResponse(self): + """ retrieve response from string io """ + if self.rep is None: + return "" + else: + value = self.rep.getvalue() + self.rep.close() + self.rep = StringIO() + return value + + + def decodeResponse(self, rep): + """ decode with correct encoding, relies on header """ + header = self.header.splitlines() + encoding = "utf8" #: default encoding + + for line in header: + line = line.lower().replace(" ", "") + if not line.startswith("content-type:") or \ + ("text" not in line and "application" not in line): + continue + + none, delemiter, charset = line.rpartition("charset=") + if delemiter: + charset = charset.split(";") + if charset: + encoding = charset[0] + + try: + # self.log.debug("Decoded %s" % encoding ) + if lookup(encoding).name == 'utf-8' and rep.startswith(BOM_UTF8): + encoding = 'utf-8-sig' + + decoder = getincrementaldecoder(encoding)("replace") + rep = decoder.decode(rep, True) + + # TODO: html_unescape as default + + except LookupError: + self.log.debug("No Decoder foung for %s" % encoding) + + except Exception: + self.log.debug("Error when decoding string from %s." % encoding) + + return rep + + + def write(self, buf): + """ writes response """ + if self.rep.tell() > 1000000 or self.abort: + rep = self.getResponse() + + if self.abort: + raise Abort + + with open("response.dump", "wb") as f: + f.write(rep) + raise Fail("Loaded url exceeded size limit") + else: + self.rep.write(buf) + + + def writeHeader(self, buf): + """ writes header """ + self.header += buf + + + def putHeader(self, name, value): + self.headers.append("%s: %s" % (name, value)) + + + def clearHeaders(self): + self.headers = [] + + + def close(self): + """ cleanup, unusable after this """ + self.rep.close() + if hasattr(self, "cj"): + del self.cj + if hasattr(self, "c"): + self.c.close() + del self.c diff --git a/pyload/network/JsEngine.py b/pyload/network/JsEngine.py new file mode 100644 index 000000000..b59d07dc4 --- /dev/null +++ b/pyload/network/JsEngine.py @@ -0,0 +1,257 @@ +# -*- coding: utf-8 -*- +# @author: vuolter + +import subprocess +import sys + +from os import path +from urllib import quote + +from pyload.utils import encode, decode, uniqify + + +class JsEngine(object): + """ JS Engine superclass """ + + def __init__(self, core, engine=None): + self.core = core + self.engine = None #: Engine Instance + + if not engine: + engine = self.core.config.get("general", "jsengine") + + if engine != "auto" and self.set(engine) is False: + engine = "auto" + self.core.log.warning("JS Engine set to \"auto\" for safely") + + if engine == "auto": + for E in self.find(): + if self.set(E) is True: + break + else: + self.core.log.error("No JS Engine available") + + + @classmethod + def find(cls): + """ Check if there is any engine available """ + return [E for E in ENGINES if E.find()] + + + def get(self, engine=None): + """ Convert engine name (string) to relative JSE class (AbstractEngine extended) """ + if not engine: + JSE = self.engine + + elif isinstance(engine, basestring): + engine_name = engine.lower() + for E in ENGINES: + if E._name == engine_name: #: doesn't check if E(NGINE) is available, just convert string to class + JSE = E + break + else: + raise ValueError("JSE") + + elif issubclass(engine, AbstractEngine): + JSE = engine + + else: + raise TypeError("engine") + + return JSE + + + def set(self, engine): + """ Set engine name (string) or JSE class (AbstractEngine extended) as default engine """ + if isinstance(engine, basestring): + return self.set(self.get(engine)) + + elif issubclass(engine, AbstractEngine) and engine.find(): + self.engine = engine + return True + + else: + return False + + + def eval(self, script, engine=None): #: engine can be a jse name """string""" or an AbstractEngine """class""" + JSE = self.get(engine) + + if not JSE: + raise TypeError("engine") + + script = encode(script) + + out, err = JSE.eval(script) + + results = [out] + + if self.core.config.get("general", "debug"): + if err: + self.core.log.debug(JSE._name + ":", err) + + engines = self.find() + engines.remove(JSE) + for E in engines: + out, err = E.eval(script) + res = err or out + self.core.log.debug(E._name + ":", res) + results.append(res) + + if len(results) > 1 and len(uniqify(results)) > 1: + self.core.log.warning("JS output of two or more engines mismatch") + + return results[0] + + +class AbstractEngine(object): + """ JSE base class """ + + _name = "" + + + def __init__(self, force=False): + self.setup() + self.available = force or self.find() + + + def setup(self): + pass + + + @classmethod + def find(cls): + """ Check if the engine is available """ + try: + __import__(cls._name) + except Exception: + try: + out, err = cls(True).eval("23+19") + except Exception: + res = False + else: + res = out == "42" + else: + res = True + + return res + + + def _eval(self, args): + if not self.available: + return None, "JS Engine \"%s\" not found" % self._name + + try: + p = subprocess.Popen(args, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + bufsize=-1) + return map(lambda x: x.strip(), p.communicate()) + except Exception, e: + return None, e + + + def eval(self, script): + raise NotImplementedError + + +class Pyv8Engine(AbstractEngine): + + _name = "PyV8" + + + def eval(self, script): + if not self.available: + return None, "JS Engine \"%s\" not found" % self._name + + try: + rt = PyV8.JSContext() + rt.enter() + res = rt.eval(script), None #@TODO: parse stderr + except Exception, e: + res = None, e + + return res + + +class CommonEngine(AbstractEngine): + + _name = "js" + + + def setup(self): + # subprocess.Popen(["js", "-v"], bufsize=-1).communicate() + pass + + + def eval(self, script): + script = "print(eval(unescape('%s')))" % quote(script) + args = ["js", "-e", script] + return self._eval(args) + + +class NodeEngine(AbstractEngine): + + _name = "nodejs" + + + def setup(self): + subprocess.Popen(["node", "-v"], bufsize=-1).communicate() + + + def eval(self, script): + script = "console.log(eval(unescape('%s')))" % quote(script) + args = ["node", "-e", script] + return self._eval(args) + + +class RhinoEngine(AbstractEngine): + + _name = "rhino" + + + def setup(self): + jspath = [ + "/usr/share/java/js.jar", + "js.jar", + path.join(pypath, "js.jar") + ] + for p in jspath: + if path.exists(p): + self.path = p + break + else: + self.path = "" + + + def eval(self, script): + script = "print(eval(unescape('%s')))" % quote(script) + args = ["java", "-cp", self.path, "org.mozilla.javascript.tools.shell.Main", "-e", script] + res = decode(self._eval(args)) + try: + return res.encode("ISO-8859-1") + finally: + return res + + +class JscEngine(AbstractEngine): + + _name = "javascriptcore" + + + def setup(self): + jspath = "/System/Library/Frameworks/JavaScriptCore.framework/Resources/jsc" + self.path = jspath if path.exists(jspath) else "" + + + def eval(self, script): + script = "print(eval(unescape('%s')))" % quote(script) + args = [self.path, "-e", script] + return self._eval(args) + + +#@NOTE: Priority ordered +ENGINES = [CommonEngine, Pyv8Engine, NodeEngine, RhinoEngine] + +if sys.platform == "darwin": + ENGINES.insert(JscEngine) diff --git a/pyload/network/RequestFactory.py b/pyload/network/RequestFactory.py new file mode 100644 index 000000000..d10c35cf0 --- /dev/null +++ b/pyload/network/RequestFactory.py @@ -0,0 +1,133 @@ +# -*- coding: utf-8 -*- +# @author: RaNaN, mkaay + +import threading + +from pyload.network.Browser import Browser +from pyload.network.Bucket import Bucket +from pyload.network.CookieJar import CookieJar +from pyload.network.HTTPRequest import HTTPRequest +from pyload.network.XDCCRequest import XDCCRequest + + +class RequestFactory(object): + + def __init__(self, core): + self.lock = threading.Lock() + self.core = core + self.bucket = Bucket() + self.updateBucket() + self.cookiejars = {} + + + def iface(self): + return self.core.config.get("download", "interface") + + + def getRequest(self, pluginName, account=None, type="HTTP"): + self.lock.acquire() + + if type == "XDCC": + return XDCCRequest(proxies=self.getProxies()) + + req = Browser(self.bucket, self.getOptions()) + + if account: + cj = self.getCookieJar(pluginName, account) + req.setCookieJar(cj) + else: + req.setCookieJar(CookieJar(pluginName)) + + self.lock.release() + return req + + + def getHTTPRequest(self, **kwargs): + """ returns a http request, dont forget to close it ! """ + options = self.getOptions() + options.update(kwargs) #: submit kwargs as additional options + return HTTPRequest(CookieJar(None), options) + + + def getURL(self, *args, **kwargs): + """ see HTTPRequest for argument list """ + cj = None + + if 'cookies' in kwargs: + if isinstance(kwargs['cookies'], CookieJar): + cj = kwargs['cookies'] + elif isinstance(kwargs['cookies'], list): + cj = CookieJar(None) + for cookie in kwargs['cookies']: + if isinstance(cookie, tuple) and len(cookie) == 3: + cj.setCookie(*cookie) + + h = HTTPRequest(cj, self.getOptions()) + try: + rep = h.load(*args, **kwargs) + finally: + h.close() + + return rep + + + def getCookieJar(self, pluginName, account=None): + if (pluginName, account) in self.cookiejars: + return self.cookiejars[(pluginName, account)] + + cj = CookieJar(pluginName, account) + self.cookiejars[(pluginName, account)] = cj + return cj + + + def getProxies(self): + """ returns a proxy list for the request classes """ + if not self.core.config.get("proxy", "proxy"): + return {} + else: + type = "http" + setting = self.core.config.get("proxy", "type").lower() + if setting == "socks4": + type = "socks4" + elif setting == "socks5": + type = "socks5" + + username = None + if self.core.config.get("proxy", "username") and self.core.config.get("proxy", "username").lower() != "none": + username = self.core.config.get("proxy", "username") + + pw = None + if self.core.config.get("proxy", "password") and self.core.config.get("proxy", "password").lower() != "none": + pw = self.core.config.get("proxy", "password") + + return { + "type": type, + "address": self.core.config.get("proxy", "address"), + "port": self.core.config.get("proxy", "port"), + "username": username, + "password": pw, + } + + + def getOptions(self): + """returns options needed for pycurl""" + return {"interface": self.iface(), + "proxies": self.getProxies(), + "ipv6": self.core.config.get("download", "ipv6")} + + + def updateBucket(self): + """ set values in the bucket according to settings""" + if not self.core.config.get("download", "limit_speed"): + self.bucket.setRate(-1) + else: + self.bucket.setRate(self.core.config.get("download", "max_speed") * 1024) + + +# needs pyreq in global namespace +def getURL(*args, **kwargs): + return pyreq.getURL(*args, **kwargs) + + +def getRequest(*args, **kwargs): + return pyreq.getHTTPRequest() diff --git a/pyload/network/XDCCRequest.py b/pyload/network/XDCCRequest.py new file mode 100644 index 000000000..24146ccaa --- /dev/null +++ b/pyload/network/XDCCRequest.py @@ -0,0 +1,149 @@ +# -*- coding: utf-8 -*- +# @author: jeix + +import socket +import struct + +from os import remove +from os.path import exists +from select import select +from time import time + +from pyload.plugin.Plugin import Abort + + +class XDCCRequest(object): + + def __init__(self, timeout=30, proxies={}): + + self.proxies = proxies + self.timeout = timeout + + self.filesize = 0 + self.recv = 0 + self.speed = 0 + + self.abort = False + + + def createSocket(self): + # proxytype = None + # proxy = None + # if self.proxies.has_key("socks5"): + # proxytype = socks.PROXY_TYPE_SOCKS5 + # proxy = self.proxies['socks5'] + # elif self.proxies.has_key("socks4"): + # proxytype = socks.PROXY_TYPE_SOCKS4 + # proxy = self.proxies['socks4'] + # if proxytype: + # sock = socks.socksocket() + # t = _parse_proxy(proxy) + # sock.setproxy(proxytype, addr=t[3].split(":")[0], port=int(t[3].split(":")[1]), username=t[1], password=t[2]) + # else: + # sock = socket.socket() + # return sock + + return socket.socket() + + + def download(self, ip, port, filename, irc, progress=None): + + ircbuffer = "" + lastUpdate = time() + cumRecvLen = 0 + + dccsock = self.createSocket() + + dccsock.settimeout(self.timeout) + dccsock.connect((ip, port)) + + if exists(filename): + i = 0 + nameParts = filename.rpartition(".") + while True: + newfilename = "%s-%d%s%s" % (nameParts[0], i, nameParts[1], nameParts[2]) + i += 1 + + if not exists(newfilename): + filename = newfilename + break + + fh = open(filename, "wb") + + # recv loop for dcc socket + while True: + if self.abort: + dccsock.close() + fh.close() + remove(filename) + raise Abort + + self._keepAlive(irc, ircbuffer) + + data = dccsock.recv(4096) + dataLen = len(data) + self.recv += dataLen + + cumRecvLen += dataLen + + now = time() + timespan = now - lastUpdate + if timespan > 1: + self.speed = cumRecvLen / timespan + cumRecvLen = 0 + lastUpdate = now + + if progress: + progress(self.percent) + + if not data: + break + + fh.write(data) + + # acknowledge data by sending number of recceived bytes + dccsock.send(struct.pack('!I', self.recv)) + + dccsock.close() + fh.close() + + return filename + + + def _keepAlive(self, sock, *readbuffer): + fdset = select([sock], [], [], 0) + if sock not in fdset[0]: + return + + readbuffer += sock.recv(1024) + temp = readbuffer.split("\n") + readbuffer = temp.pop() + + for line in temp: + line = line.rstrip() + first = line.split() + if first[0] == "PING": + sock.send("PONG %s\r\n" % first[1]) + + + def abortDownloads(self): + self.abort = True + + + @property + def size(self): + return self.filesize + + + @property + def arrived(self): + return self.recv + + + @property + def percent(self): + return (self.recv * 100) / self.filesize if elf.filesize else 0 + + + def close(self): + pass diff --git a/pyload/network/__init__.py b/pyload/network/__init__.py new file mode 100644 index 000000000..40a96afc6 --- /dev/null +++ b/pyload/network/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/pyload/plugin/Account.py b/pyload/plugin/Account.py new file mode 100644 index 000000000..e4bfd76e8 --- /dev/null +++ b/pyload/plugin/Account.py @@ -0,0 +1,309 @@ +# -*- coding: utf-8 -*- + +import random +import threading +import time +import traceback + +from pyload.plugin.Plugin import Base +from pyload.utils import compare_time, parseFileSize, lock + + +class WrongPassword(Exception): + pass + + +class Account(Base): + """ + Base class for every Account plugin. + Just overwrite `login` and cookies will be stored and account becomes accessible in\ + associated hoster plugin. Plugin should also provide `loadAccountInfo` + """ + __name = "Account" + __type = "account" + __version = "0.03" + + __description = """Base account plugin""" + __license = "GPLv3" + __authors = [("mkaay", "mkaay@mkaay.de")] + + + #: after that time (in minutes) pyload will relogin the account + login_timeout = 10 * 60 + #: after that time (in minutes) account data will be reloaded + info_threshold = 10 * 60 + + + def __init__(self, manager, accounts): + Base.__init__(self, manager.core) + + self.manager = manager + self.accounts = {} + self.infos = {} #: cache for account information + self.lock = threading.RLock() + self.timestamps = {} + + self.init() + + self.setAccounts(accounts) + + + def init(self): + pass + + + def login(self, user, data, req): + """login into account, the cookies will be saved so user can be recognized + + :param user: loginname + :param data: data dictionary + :param req: `Request` instance + """ + pass + + + @lock + def _login(self, user, data): + # set timestamp for login + self.timestamps[user] = time.time() + + req = self.getAccountRequest(user) + try: + self.login(user, data, req) + except WrongPassword: + self.logWarning( + _("Could not login with account %(user)s | %(msg)s") % {"user": user, + "msg": _("Wrong Password")}) + success = data['valid'] = False + except Exception, e: + self.logWarning( + _("Could not login with account %(user)s | %(msg)s") % {"user": user, + "msg": e}) + success = data['valid'] = False + if self.core.debug: + traceback.print_exc() + else: + success = True + finally: + if req: + req.close() + return success + + + def relogin(self, user): + req = self.getAccountRequest(user) + if req: + req.cj.clear() + req.close() + if user in self.infos: + del self.infos[user] #: delete old information + + return self._login(user, self.accounts[user]) + + + def setAccounts(self, accounts): + self.accounts = accounts + for user, data in self.accounts.iteritems(): + self._login(user, data) + self.infos[user] = {} + + + def updateAccounts(self, user, password=None, options={}): + """ updates account and return true if anything changed """ + + if user in self.accounts: + self.accounts[user]['valid'] = True #: do not remove or accounts will not login + if password: + self.accounts[user]['password'] = password + self.relogin(user) + return True + if options: + before = self.accounts[user]['options'] + self.accounts[user]['options'].update(options) + return self.accounts[user]['options'] != before + else: + self.accounts[user] = {"password": password, "options": options, "valid": True} + self._login(user, self.accounts[user]) + return True + + + def removeAccount(self, user): + if user in self.accounts: + del self.accounts[user] + if user in self.infos: + del self.infos[user] + if user in self.timestamps: + del self.timestamps[user] + + + @lock + def getAccountInfo(self, name, force=False): + """retrieve account infos for an user, do **not** overwrite this method!\\ + just use it to retrieve infos in hoster plugins. see `loadAccountInfo` + + :param name: username + :param force: reloads cached account information + :return: dictionary with information + """ + data = Account.loadAccountInfo(self, name) + + if force or name not in self.infos: + self.logDebug("Get Account Info for %s" % name) + req = self.getAccountRequest(name) + + try: + infos = self.loadAccountInfo(name, req) + if not type(infos) == dict: + raise Exception("Wrong return format") + except Exception, e: + infos = {"error": str(e)} + traceback.print_exc() + + if req: + req.close() + + self.logDebug("Account Info: %s" % infos) + + infos['timestamp'] = time.time() + self.infos[name] = infos + elif "timestamp" in self.infos[name] and self.infos[name]['timestamp'] + self.info_threshold * 60 < time.time(): + self.logDebug("Reached timeout for account data") + self.scheduleRefresh(name) + + data.update(self.infos[name]) + return data + + + def isPremium(self, user): + info = self.getAccountInfo(user) + return info['premium'] + + + def loadAccountInfo(self, name, req=None): + """this should be overwritten in account plugin,\ + and retrieving account information for user + + :param name: + :param req: `Request` instance + :return: + """ + return {"validuntil" : None, #: -1 for unlimited + "login" : name, + # "password" : self.accounts[name]['password'], #: commented due security reason + "options" : self.accounts[name]['options'], + "valid" : self.accounts[name]['valid'], + "trafficleft": None, #: in bytes, -1 for unlimited + "maxtraffic" : None, + "premium" : None, + "timestamp" : 0, #: time this info was retrieved + "type" : self.getClassName()} + + + def getAllAccounts(self, force=False): + return [self.getAccountInfo(user, force) for user, data in self.accounts.iteritems()] + + + def getAccountRequest(self, user=None): + if not user: + user, data = self.selectAccount() + if not user: + return None + + req = self.core.requestFactory.getRequest(self.getClassName(), user) + return req + + + def getAccountCookies(self, user=None): + if not user: + user, data = self.selectAccount() + if not user: + return None + + cj = self.core.requestFactory.getCookieJar(self.getClassName(), user) + return cj + + + def getAccountData(self, user): + return self.accounts[user] + + + def selectAccount(self): + """ returns an valid account name and data""" + usable = [] + for user, data in self.accounts.iteritems(): + if not data['valid']: + continue + + if "time" in data['options'] and data['options']['time']: + time_data = "" + try: + time_data = data['options']['time'][0] + start, end = time_data.split("-") + if not compare_time.time(start.split(":"), end.split(":")): + continue + except Exception: + self.logWarning(_("Your Time %s has wrong format, use: 1:22-3:44") % time_data) + + if user in self.infos: + if "validuntil" in self.infos[user]: + if self.infos[user]['validuntil'] > 0 and time.time() > self.infos[user]['validuntil']: + continue + if "trafficleft" in self.infos[user]: + if self.infos[user]['trafficleft'] == 0: + continue + + usable.append((user, data)) + + if not usable: + return None, None + + return random.choice(usable) + + + def canUse(self): + return self.selectAccount() != (None, None) + + + def parseTraffic(self, value, unit=None): #: return bytes + if not unit and not isinstance(value, basestring): + unit = "KB" + return parseFileSize(value, unit) + + + def wrongPassword(self): + raise WrongPassword + + + def empty(self, user): + if user in self.infos: + self.logWarning(_("Account %s has not enough traffic, checking again in 30min") % user) + + self.infos[user].update({"trafficleft": 0}) + self.scheduleRefresh(user, 30 * 60) + + + def expired(self, user): + if user in self.infos: + self.logWarning(_("Account %s is expired, checking again in 1h") % user) + + self.infos[user].update({"validuntil": time.time() - 1}) + self.scheduleRefresh(user, 60 * 60) + + + def scheduleRefresh(self, user, time=0, force=True): + """ add task to refresh account info to sheduler """ + self.logDebug("Scheduled Account refresh for %s in %s seconds." % (user, time)) + self.core.scheduler.addJob(time, self.getAccountInfo, [user, force]) + + + @lock + def checkLogin(self, user): + """ checks if user is still logged in """ + if user in self.timestamps: + if self.login_timeout > 0 and self.timestamps[user] + self.login_timeout * 60 < time.time(): + self.logDebug("Reached login timeout for %s" % user) + return self.relogin(user) + else: + return True + else: + return False diff --git a/pyload/plugin/Addon.py b/pyload/plugin/Addon.py new file mode 100644 index 000000000..cf3397002 --- /dev/null +++ b/pyload/plugin/Addon.py @@ -0,0 +1,189 @@ +# -*- coding: utf-8 -*- + +import traceback + +from pyload.plugin.Plugin import Base +from pyload.utils import has_method + + +class Expose(object): + """ used for decoration to declare rpc services """ + + def __new__(cls, f, *args, **kwargs): + addonManager.addRPC(f.__module__, f.func_name, f.func_doc) + return f + + +def threaded(fn): + + def run(*args, **kwargs): + addonManager.startThread(fn, *args, **kwargs) + + return run + + +class Addon(Base): + __name = "Addon" + __type = "addon" + __version = "0.01" + + __config = [] #: [("name", "type", "desc", "default")] + + __description = """Base addon plugin""" + __license = "GPLv3" + __authors = [("mkaay", "mkaay@mkaay.de"), + ("RaNaN", "RaNaN@pyload.org")] + + + #: automatically register event listeners for functions, attribute will be deleted dont use it yourself + event_map = {} + + # Deprecated alternative to event_map + #: List of events the plugin can handle, name the functions exactly like eventname. + event_list = [] #@NOTE: dont make duplicate entries in event_map + + + def __init__(self, core, manager): + Base.__init__(self, core) + + #: Provide information in dict here, usable by API `getInfo` + self.info = {} + + #: Callback of periodical job task, used by AddonManager + self.cb = None + self.interval = 60 + + #: `AddonManager` + self.manager = manager + + # register events + if self.event_map: + for event, funcs in self.event_map.iteritems(): + if type(funcs) in (list, tuple): + for f in funcs: + self.manager.addEvent(event, getattr(self, f)) + else: + self.manager.addEvent(event, getattr(self, funcs)) + + # delete for various reasons + self.event_map = None + + if self.event_list: + self.logWarning(_("Plugin used deprecated `event_list`, use `event_map` instead")) + + for f in self.event_list: + self.manager.addEvent(f, getattr(self, f)) + + self.event_list = None + + self.setup() + + + def initPeriodical(self, delay=0, threaded=False): + self.cb = self.core.scheduler.addJob(max(0, delay), self._periodical, [threaded], threaded=threaded) + + + def _periodical(self, threaded): + if self.interval < 0: + self.cb = None + return + + try: + self.periodical() + + except Exception, e: + self.logError(_("Error executing addon: %s") % e) + if self.core.debug: + traceback.print_exc() + + self.cb = self.core.scheduler.addJob(self.interval, self._periodical, [threaded], threaded=threaded) + + + def __repr__(self): + return "<Addon %s>" % self.getClassName() + + + def setup(self): + """ more init stuff if needed """ + pass + + + def deactivate(self): + """ called when addon was deactivated """ + if has_method(self.__class__, "unload"): + self.logWarning(_("Deprecated method `unload`, use `deactivate` instead")) + self.unload() + + + def unload(self): #: Deprecated, use method `deactivate` instead + pass + + + def isActivated(self): + """ checks if addon is activated""" + return self.getConfig("activated") + + + # Event methods - overwrite these if needed + + + def activate(self): + """ called when addon was activated """ + if has_method(self.__class__, "coreReady"): + self.logWarning(_("Deprecated method `coreReady`, use `activate` instead")) + self.coreReady() + + + def coreReady(self): #: Deprecated, use method `activate` instead + pass + + + def exit(self): + """ called by core.shutdown just before pyLoad exit """ + if has_method(self.__class__, "coreExiting"): + self.coreExiting() + + + def coreExiting(self): #: Deprecated, use method `exit` instead + pass + + + def downloadPreparing(self, pyfile): + pass + + + def downloadFinished(self, pyfile): + pass + + + def downloadFailed(self, pyfile): + pass + + + def packageFinished(self, pypack): + pass + + + def beforeReconnecting(self, ip): + pass + + + def afterReconnecting(self, ip): + pass + + + def periodical(self): + pass + + + def captchaTask(self, task): + """ new captcha task for the plugin, it MUST set the handler and timeout or will be ignored """ + pass + + + def captchaCorrect(self, task): + pass + + + def captchaInvalid(self, task): + pass diff --git a/pyload/plugin/Captcha.py b/pyload/plugin/Captcha.py new file mode 100644 index 000000000..3bf64eb19 --- /dev/null +++ b/pyload/plugin/Captcha.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.Plugin import Base + + +#@TODO: Extend Plugin class; remove all `html` args +class Captcha(Base): + __name = "Captcha" + __type = "captcha" + __version = "0.25" + + __description = """Base captcha service plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + def __init__(self, plugin): + self.plugin = plugin + self.key = None #: last key detected + super(Captcha, self).__init__(plugin.core) + + + def detect_key(self, html=None): + raise NotImplementedError + + + def challenge(self, key=None, html=None): + raise NotImplementedError + + + def result(self, server, challenge): + raise NotImplementedError diff --git a/pyload/plugin/Container.py b/pyload/plugin/Container.py new file mode 100644 index 000000000..e8e42bb2b --- /dev/null +++ b/pyload/plugin/Container.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +import re + +from os import remove +from os.path import basename, exists + +from pyload.plugin.internal.Crypter import Crypter +from pyload.utils import fs_join + + +class Container(Crypter): + __name = "Container" + __type = "container" + __version = "0.01" + + __pattern = r'^unmatchable$' + __config = [] #: [("name", "type", "desc", "default")] + + __description = """Base container decrypter plugin""" + __license = "GPLv3" + __authors = [("mkaay", "mkaay@mkaay.de")] + + + def preprocessing(self, thread): + """prepare""" + + self.setup() + self.thread = thread + + self.loadToDisk() + + self.decrypt(self.pyfile) + self.deleteTmp() + + self.createPackages() + + + def loadToDisk(self): + """loads container to disk if its stored remotely and overwrite url, + or check existent on several places at disk""" + + if self.pyfile.url.startswith("http"): + self.pyfile.name = re.findall("([^\/=]+)", self.pyfile.url)[-1] + content = self.load(self.pyfile.url) + self.pyfile.url = fs_join(self.core.config.get("general", "download_folder"), self.pyfile.name) + try: + with open(self.pyfile.url, "wb") as f: + f.write(content) + except IOError, e: + self.fail(str(e)) + + else: + self.pyfile.name = basename(self.pyfile.url) + if not exists(self.pyfile.url): + if exists(fs_join(pypath, self.pyfile.url)): + self.pyfile.url = fs_join(pypath, self.pyfile.url) + else: + self.fail(_("File not exists")) + + + def deleteTmp(self): + if self.pyfile.name.startswith("tmp_"): + remove(self.pyfile.url) diff --git a/pyload/plugin/Crypter.py b/pyload/plugin/Crypter.py new file mode 100644 index 000000000..22afe74d8 --- /dev/null +++ b/pyload/plugin/Crypter.py @@ -0,0 +1,107 @@ +# -*- coding: utf-8 -*- + +import urlparse + +from pyload.plugin.Plugin import Plugin +from pyload.utils import decode, safe_filename + + +class Crypter(Plugin): + __name = "Crypter" + __type = "crypter" + __version = "0.05" + + __pattern = r'^unmatchable$' + __config = [("use_subfolder", "bool", "Save package to subfolder", True), #: Overrides core.config.get("general", "folder_per_package") + ("subfolder_per_package", "bool", "Create a subfolder for each package", True)] + + __description = """Base decrypter plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + html = None #: last html loaded + + + def __init__(self, pyfile): + #: Put all packages here. It's a list of tuples like: ( name, [list of links], folder ) + self.packages = [] + + #: List of urls, pyLoad will generate packagenames + self.urls = [] + + Plugin.__init__(self, pyfile) + + + def process(self, pyfile): + """ main method """ + + self.decrypt(pyfile) + + if self.urls: + self.generatePackages() + + elif not self.packages: + self.error(_("No link grabbed"), "decrypt") + + self.createPackages() + + + def decrypt(self, pyfile): + raise NotImplementedError + + + def generatePackages(self): + """ generate new packages from self.urls """ + + packages = map(lambda name, links: (name, links, None), self.core.api.generatePackages(self.urls).iteritems()) + self.packages.extend(packages) + + + def createPackages(self): + """ create new packages from self.packages """ + + package_folder = self.pyfile.package().folder + package_password = self.pyfile.package().password + package_queue = self.pyfile.package().queue + + folder_per_package = self.core.config.get("general", "folder_per_package") + try: + use_subfolder = self.getConfig('use_subfolder') + except Exception: + use_subfolder = folder_per_package + try: + subfolder_per_package = self.getConfig('subfolder_per_package') + except Exception: + subfolder_per_package = True + + for pack in self.packages: + name, links, folder = pack + + self.logDebug("Parsed package: %s" % name, + "%d links" % len(links), + "Saved to folder: %s" % folder if folder else "Saved to download folder") + + links = map(decode, links) + + pid = self.core.api.addPackage(name, links, package_queue) + + if package_password: + self.core.api.setPackageData(pid, {"password": package_password}) + + setFolder = lambda x: self.core.api.setPackageData(pid, {"folder": x or ""}) #: Workaround to do not break API addPackage method + + if use_subfolder: + if not subfolder_per_package: + setFolder(package_folder) + self.logDebug("Set package %(name)s folder to: %(folder)s" % {"name": name, "folder": folder}) + + elif not folder_per_package or name != folder: + if not folder: + folder = urlparse.urlparse(name).path.split("/")[-1] + + setFolder(safe_filename(folder)) + self.logDebug("Set package %(name)s folder to: %(folder)s" % {"name": name, "folder": folder}) + + elif folder_per_package: + setFolder(None) diff --git a/pyload/plugin/Extractor.py b/pyload/plugin/Extractor.py new file mode 100644 index 000000000..539ab624d --- /dev/null +++ b/pyload/plugin/Extractor.py @@ -0,0 +1,154 @@ +# -*- coding: utf-8 -*- + +import os +import re + +from pyload.datatype.File import PyFile +from pyload.plugin.Plugin import Base + + +class ArchiveError(Exception): + pass + + +class CRCError(Exception): + pass + + +class PasswordError(Exception): + pass + + +class Extractor: + __name = "Extractor" + __type = "extractor" + __version = "0.24" + + __description = """Base extractor plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com"), + ("Immenz" , "immenz@gmx.net")] + + + EXTENSIONS = [] + NAME = __name__ + VERSION = "" + REPAIR = False + + + @classmethod + def isArchive(cls, filename): + name = os.path.basename(filename).lower() + return any(name.endswith(ext) for ext in cls.EXTENSIONS) + + + @classmethod + def isMultipart(cls, filename): + return False + + + @classmethod + def isUsable(cls): + """ Check if system statisfy dependencies + :return: boolean + """ + return None + + + @classmethod + def getTargets(cls, files_ids): + """ Filter suited targets from list of filename id tuple list + :param files_ids: List of filepathes + :return: List of targets, id tuple list + """ + targets = [] + processed = [] + + for fname, id, fout in files_ids: + if cls.isArchive(fname): + pname = re.sub(cls.re_multipart, '', fname) if cls.isMultipart(fname) else os.path.splitext(fname)[0] + if pname not in processed: + processed.append(pname) + targets.append((fname, id, fout)) + return targets + + + def __init__(self, manager, filename, out, + fullpath=True, + overwrite=False, + excludefiles=[], + renice=0, + delete='No', + keepbroken=False, + fid=None): + """ Initialize extractor for specific file """ + self.manager = manager + self.filename = filename + self.out = out + self.fullpath = fullpath + self.overwrite = overwrite + self.excludefiles = excludefiles + self.renice = renice + self.delete = delete + self.keepbroken = keepbroken + self.files = [] #: Store extracted files here + + pyfile = self.manager.core.files.getFile(fid) if fid else None + self.notifyProgress = lambda x: pyfile.setProgress(x) if pyfile else lambda x: None + + + def init(self): + """ Initialize additional data structures """ + pass + + + def check(self): + """Quick Check by listing content of archive. + Raises error if password is needed, integrity is questionable or else. + + :raises PasswordError + :raises CRCError + :raises ArchiveError + """ + raise NotImplementedError + + + def verify(self): + """Testing with Extractors buildt-in method + Raises error if password is needed, integrity is questionable or else. + + :raises PasswordError + :raises CRCError + :raises ArchiveError + """ + raise NotImplementedError + + + def repair(self): + return None + + + def extract(self, password=None): + """Extract the archive. Raise specific errors in case of failure. + + :param progress: Progress function, call this to update status + :param password password to use + :raises PasswordError + :raises CRCError + :raises ArchiveError + :return: + """ + raise NotImplementedError + + + def getDeleteFiles(self): + """Return list of files to delete, do *not* delete them here. + + :return: List with paths of files to delete + """ + return [self.filename] + + + def list(self, password=None): + """Populate self.files at some point while extracting""" + return self.files diff --git a/pyload/plugin/Hook.py b/pyload/plugin/Hook.py new file mode 100644 index 000000000..ccd4d635b --- /dev/null +++ b/pyload/plugin/Hook.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.Addon import Addon, threaded + + +class Hook(Addon): + __name = "Hook" + __type = "hook" + __version = "0.03" + + __config = [] #: [("name", "type", "desc", "default")] + + __description = """Base hook plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] diff --git a/pyload/plugin/Hoster.py b/pyload/plugin/Hoster.py new file mode 100644 index 000000000..64c635c45 --- /dev/null +++ b/pyload/plugin/Hoster.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.Plugin import Plugin + + +def getInfo(self): + # result = [ .. (name, size, status, url) .. ] + return + + +class Hoster(Plugin): + __name = "Hoster" + __type = "hoster" + __version = "0.02" + + __pattern = r'^unmatchable$' + __config = [] #: [("name", "type", "desc", "default")] + + __description = """Base hoster plugin""" + __license = "GPLv3" + __authors = [("mkaay", "mkaay@mkaay.de")] diff --git a/pyload/plugin/OCR.py b/pyload/plugin/OCR.py new file mode 100644 index 000000000..126283f01 --- /dev/null +++ b/pyload/plugin/OCR.py @@ -0,0 +1,322 @@ +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +try: + from PIL import Image, GifImagePlugin, JpegImagePlugin, PngImagePlugin, TiffImagePlugin + +except ImportError: + import Image, GifImagePlugin, JpegImagePlugin, PngImagePlugin, TiffImagePlugin + +import logging +import os +import subprocess +# import tempfile + +from pyload.plugin.Plugin import Base +from pyload.utils import fs_join + + +class OCR(Base): + __name = "OCR" + __type = "ocr" + __version = "0.12" + + __description = """OCR base plugin""" + __license = "GPLv3" + __authors = [("pyLoad Team", "admin@pyload.org")] + + + def __init__(self): + self.logger = logging.getLogger("log") + + + def load_image(self, image): + self.image = Image.open(image) + self.pixels = self.image.load() + self.result_captcha = '' + + + def deactivate(self): + """delete all tmp images""" + pass + + + def threshold(self, value): + self.image = self.image.point(lambda a: a * value + 10) + + + def run(self, command): + """Run a command""" + + popen = subprocess.Popen(command, bufsize=-1, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + popen.wait() + output = popen.stdout.read() + " | " + popen.stderr.read() + popen.stdout.close() + popen.stderr.close() + self.logger.debug("Tesseract ReturnCode %s Output: %s" % (popen.returncode, output)) + + + def run_tesser(self, subset=False, digits=True, lowercase=True, uppercase=True, pagesegmode=None): + # tmpTif = tempfile.NamedTemporaryFile(suffix=".tif") + try: + tmpTif = open(fs_join("tmp", "tmpTif_%s.tif" % self.getClassName()), "wb") + tmpTif.close() + + # tmpTxt = tempfile.NamedTemporaryFile(suffix=".txt") + tmpTxt = open(fs_join("tmp", "tmpTxt_%s.txt" % self.getClassName()), "wb") + tmpTxt.close() + + except IOError, e: + self.logError(e) + return + + self.logger.debug("save tiff") + self.image.save(tmpTif.name, 'TIFF') + + if os.name == "nt": + tessparams = [os.path.join(pypath, "tesseract", "tesseract.exe")] + else: + tessparams = ["tesseract"] + + tessparams.extend([os.path.abspath(tmpTif.name), os.path.abspath(tmpTxt.name).replace(".txt", "")]) + + if pagesegmode: + tessparams.extend(["-psm", str(pagesegmode)]) + + if subset and (digits or lowercase or uppercase): + # tmpSub = tempfile.NamedTemporaryFile(suffix=".subset") + with open(fs_join("tmp", "tmpSub_%s.subset" % self.getClassName()), "wb") as tmpSub: + tmpSub.write("tessedit_char_whitelist ") + + if digits: + tmpSub.write("0123456789") + if lowercase: + tmpSub.write("abcdefghijklmnopqrstuvwxyz") + if uppercase: + tmpSub.write("ABCDEFGHIJKLMNOPQRSTUVWXYZ") + + tmpSub.write("\n") + tessparams.append("nobatch") + tessparams.append(os.path.abspath(tmpSub.name)) + + self.logger.debug("run tesseract") + self.run(tessparams) + self.logger.debug("read txt") + + try: + with open(tmpTxt.name, 'r') as f: + self.result_captcha = f.read().replace("\n", "") + except Exception: + self.result_captcha = "" + + self.logger.debug(self.result_captcha) + try: + os.remove(tmpTif.name) + os.remove(tmpTxt.name) + if subset and (digits or lowercase or uppercase): + os.remove(tmpSub.name) + except Exception: + pass + + + def get_captcha(self, name): + raise NotImplementedError + + + def to_greyscale(self): + if self.image.mode != 'L': + self.image = self.image.convert('L') + + self.pixels = self.image.load() + + + def eval_black_white(self, limit): + self.pixels = self.image.load() + w, h = self.image.size + for x in xrange(w): + for y in xrange(h): + if self.pixels[x, y] > limit: + self.pixels[x, y] = 255 + else: + self.pixels[x, y] = 0 + + + def clean(self, allowed): + pixels = self.pixels + + w, h = self.image.size + + for x in xrange(w): + for y in xrange(h): + if pixels[x, y] == 255: + continue + # No point in processing white pixels since we only want to remove black pixel + count = 0 + + try: + if pixels[x - 1, y - 1] != 255: + count += 1 + if pixels[x - 1, y] != 255: + count += 1 + if pixels[x - 1, y + 1] != 255: + count += 1 + if pixels[x, y + 1] != 255: + count += 1 + if pixels[x + 1, y + 1] != 255: + count += 1 + if pixels[x + 1, y] != 255: + count += 1 + if pixels[x + 1, y - 1] != 255: + count += 1 + if pixels[x, y - 1] != 255: + count += 1 + except Exception: + pass + + # not enough neighbors are dark pixels so mark this pixel + # to be changed to white + if count < allowed: + pixels[x, y] = 1 + + # second pass: this time set all 1's to 255 (white) + for x in xrange(w): + for y in xrange(h): + if pixels[x, y] == 1: + pixels[x, y] = 255 + + self.pixels = pixels + + + def derotate_by_average(self): + """rotate by checking each angle and guess most suitable""" + + w, h = self.image.size + pixels = self.pixels + + for x in xrange(w): + for y in xrange(h): + if pixels[x, y] == 0: + pixels[x, y] = 155 + + highest = {} + counts = {} + + for angle in xrange(-45, 45): + + tmpimage = self.image.rotate(angle) + + pixels = tmpimage.load() + + w, h = self.image.size + + for x in xrange(w): + for y in xrange(h): + if pixels[x, y] == 0: + pixels[x, y] = 255 + + count = {} + + for x in xrange(w): + count[x] = 0 + for y in xrange(h): + if pixels[x, y] == 155: + count[x] += 1 + + sum = 0 + cnt = 0 + + for x in count.values(): + if x != 0: + sum += x + cnt += 1 + + avg = sum / cnt + counts[angle] = cnt + highest[angle] = 0 + for x in count.values(): + if x > highest[angle]: + highest[angle] = x + + highest[angle] = highest[angle] - avg + + hkey = 0 + hvalue = 0 + + for key, value in highest.iteritems(): + if value > hvalue: + hkey = key + hvalue = value + + self.image = self.image.rotate(hkey) + pixels = self.image.load() + + for x in xrange(w): + for y in xrange(h): + if pixels[x, y] == 0: + pixels[x, y] = 255 + + if pixels[x, y] == 155: + pixels[x, y] = 0 + + self.pixels = pixels + + + def split_captcha_letters(self): + captcha = self.image + started = False + letters = [] + width, height = captcha.size + bottomY, topY = 0, height + pixels = captcha.load() + + for x in xrange(width): + black_pixel_in_col = False + for y in xrange(height): + if pixels[x, y] != 255: + if not started: + started = True + firstX = x + lastX = x + + if y > bottomY: + bottomY = y + if y < topY: + topY = y + if x > lastX: + lastX = x + + black_pixel_in_col = True + + if black_pixel_in_col is False and started is True: + rect = (firstX, topY, lastX, bottomY) + new_captcha = captcha.crop(rect) + + w, h = new_captcha.size + if w > 5 and h > 5: + letters.append(new_captcha) + + started = False + bottomY, topY = 0, height + + return letters + + + def correct(self, values, var=None): + if var: + result = var + else: + result = self.result_captcha + + for key, item in values.iteritems(): + + if key.__class__ == str: + result = result.replace(key, item) + else: + for expr in key: + result = result.replace(expr, item) + + if var: + return result + else: + self.result_captcha = result diff --git a/pyload/plugin/Plugin.py b/pyload/plugin/Plugin.py new file mode 100644 index 000000000..26590c9da --- /dev/null +++ b/pyload/plugin/Plugin.py @@ -0,0 +1,776 @@ +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +import traceback + +import os +import random +import re +import time +import urllib +import urlparse + +from itertools import islice +from os import remove, makedirs, chmod, stat +from os.path import exists, join + +if os.name != "nt": + from os import chown + from pwd import getpwnam + from grp import getgrnam + +from pyload.utils import fs_decode, fs_encode, safe_filename, fs_join, encode + + +def chunks(iterable, size): + it = iter(iterable) + item = list(islice(it, size)) + while item: + yield item + item = list(islice(it, size)) + + +class Abort(Exception): + """ raised when aborted """ + + +class Fail(Exception): + """ raised when failed """ + + +class Reconnect(Exception): + """ raised when reconnected """ + + +class Retry(Exception): + """ raised when start again from beginning """ + + +class SkipDownload(Exception): + """ raised when download should be skipped """ + + +class Base(object): + """ + A Base class with log/config/db methods *all* plugin types can use + """ + + + def __init__(self, core): + #: Core instance + self.core = core + + + def _log(self, type, args): + msg = " | ".join([encode(str(a)).strip() for a in args if a]) + logger = getattr(self.core.log, type) + logger("%s: %s" % (self.getClassName(), msg or _("%s MARK" % type.upper()))) + + + def logDebug(self, *args): + if self.core.debug: + return self._log("debug", args) + + + def logInfo(self, *args): + return self._log("info", args) + + + def logWarning(self, *args): + return self._log("warning", args) + + + def logError(self, *args): + return self._log("error", args) + + + def logCritical(self, *args): + return self._log("critical", args) + + + def getPluginType(self): + return getattr(self, "_%s__type" % self.getClassName()) + + + @classmethod + def getClassName(cls): + return cls.__name__ + + + def getPluginConfSection(self): + return "%s_%s" % (self.getClassName(), getattr(self, "_%s__type" % self.getClassName())) + + #: Deprecated method + + + def setConf(self, option, value): + """ see `setConfig` """ + self.setConfig(option, value) + + + def setConfig(self, option, value): + """ Set config value for current plugin + + :param option: + :param value: + :return: + """ + self.core.config.setPlugin(self.getPluginConfSection(), option, value) + + #: Deprecated method + + + def getConf(self, option): + """ see `getConfig` """ + return self.core.config.getPlugin(self.getPluginConfSection(), option) + + + def getConfig(self, option): + """ Returns config value for current plugin + + :param option: + :return: + """ + return self.core.config.getPlugin(self.getPluginConfSection(), option) + + + def setStorage(self, key, value): + """ Saves a value persistently to the database """ + self.core.db.setStorage(self.getPluginConfSection(), key, value) + + + def store(self, key, value): + """ same as `setStorage` """ + self.core.db.setStorage(self.getPluginConfSection(), key, value) + + + def getStorage(self, key=None, default=None): + """ Retrieves saved value or dict of all saved entries if key is None """ + if key: + return self.core.db.getStorage(self.getPluginConfSection(), key) or default + return self.core.db.getStorage(self.getPluginConfSection(), key) + + + def retrieve(self, *args, **kwargs): + """ same as `getStorage` """ + return self.getStorage(*args, **kwargs) + + + def delStorage(self, key): + """ Delete entry in db """ + self.core.db.delStorage(self.getClassName(), key) + + +class Plugin(Base): + """ + Base plugin for hoster/crypter. + Overwrite `process` / `decrypt` in your subclassed plugin. + """ + __name = "Plugin" + __type = "hoster" + __version = "0.07" + + __pattern = r'^unmatchable$' + __config = [] #: [("name", "type", "desc", "default")] + + __description = """Base plugin""" + __license = "GPLv3" + __authors = [("RaNaN", "RaNaN@pyload.org"), + ("spoob", "spoob@pyload.org"), + ("mkaay", "mkaay@mkaay.de")] + + info = {} #: file info dict + + + def __init__(self, pyfile): + Base.__init__(self, pyfile.m.core) + + #: engage wan reconnection + self.wantReconnect = False + + #: enable simultaneous processing of multiple downloads + self.multiDL = True + self.limitDL = 0 + + #: chunk limit + self.chunkLimit = 1 + self.resumeDownload = False + + #: time.time() + wait in seconds + self.waitUntil = 0 + self.waiting = False + + #: captcha reader instance + self.ocr = None + + #: account handler instance, see :py:class:`Account` + self.account = pyfile.m.core.accountManager.getAccountPlugin(self.getClassName()) + + #: premium status + self.premium = False + #: username/login + self.user = None + + if self.account and not self.account.canUse(): + self.account = None + + if self.account: + self.user, data = self.account.selectAccount() + #: Browser instance, see `network.Browser` + self.req = self.account.getAccountRequest(self.user) + self.chunkLimit = -1 #: chunk limit, -1 for unlimited + #: enables resume (will be ignored if server dont accept chunks) + self.resumeDownload = True + self.multiDL = True #: every hoster with account should provide multiple downloads + #: premium status + self.premium = self.account.isPremium(self.user) + else: + self.req = pyfile.m.core.requestFactory.getRequest(self.getClassName()) + + #: associated pyfile instance, see `PyFile` + self.pyfile = pyfile + + self.thread = None #: holds thread in future + + #: location where the last call to download was saved + self.lastDownload = "" + #: re match of the last call to `checkDownload` + self.lastCheck = None + + #: js engine, see `JsEngine` + self.js = self.core.js + + #: captcha task + self.cTask = None + + self.html = None #@TODO: Move to hoster class in 0.4.10 + self.retries = 0 + + self.init() + + + def getChunkCount(self): + if self.chunkLimit <= 0: + return self.core.config.get("download", "chunks") + return min(self.core.config.get("download", "chunks"), self.chunkLimit) + + + def __call__(self): + return self.getClassName() + + + def init(self): + """initialize the plugin (in addition to `__init__`)""" + pass + + + def setup(self): + """ setup for enviroment and other things, called before downloading (possibly more than one time)""" + pass + + + def preprocessing(self, thread): + """ handles important things to do before starting """ + self.thread = thread + + if self.account: + self.account.checkLogin(self.user) + else: + self.req.clearCookies() + + self.setup() + + self.pyfile.setStatus("starting") + + return self.process(self.pyfile) + + + def process(self, pyfile): + """the 'main' method of every plugin, you **have to** overwrite it""" + raise NotImplementedError + + + def resetAccount(self): + """ dont use account and retry download """ + self.account = None + self.req = self.core.requestFactory.getRequest(self.getClassName()) + self.retry() + + + def checksum(self, local_file=None): + """ + return codes: + 0 - checksum ok + 1 - checksum wrong + 5 - can't get checksum + 10 - not implemented + 20 - unknown error + """ + #@TODO checksum check addon + + return True, 10 + + + def setReconnect(self, reconnect): + reconnect = bool(reconnect) + self.logDebug("Set wantReconnect to: %s (previous: %s)" % (reconnect, self.wantReconnect)) + self.wantReconnect = reconnect + + + def setWait(self, seconds, reconnect=None): + """Set a specific wait time later used with `wait` + + :param seconds: wait time in seconds + :param reconnect: True if a reconnect would avoid wait time + """ + wait_time = int(seconds) + 1 + wait_until = time.time() + wait_time + + self.logDebug("Set waitUntil to: %f (previous: %f)" % (wait_until, self.pyfile.waitUntil), + "Wait: %d seconds" % wait_time) + + self.pyfile.waitUntil = wait_until + + if reconnect is not None: + self.setReconnect(reconnect) + + + def wait(self, seconds=None, reconnect=None): + """ waits the time previously set """ + + pyfile = self.pyfile + + if seconds is not None: + self.setWait(seconds) + + if reconnect is not None: + self.setReconnect(reconnect) + + self.waiting = True + + status = pyfile.status + pyfile.setStatus("waiting") + + self.logInfo(_("Wait: %d seconds") % (pyfile.waitUntil - time.time()), + _("Reconnect: %s") % self.wantReconnect) + + if self.account: + self.logDebug("Ignore reconnection due account logged") + + while pyfile.waitUntil > time.time(): + if pyfile.abort: + self.abort() + + time.sleep(1) + else: + while pyfile.waitUntil > time.time(): + self.thread.m.reconnecting.wait(2) + + if pyfile.abort: + self.abort() + + if self.thread.m.reconnecting.isSet(): + self.waiting = False + self.wantReconnect = False + raise Reconnect + + time.sleep(1) + + self.waiting = False + + pyfile.status = status + + + def fail(self, reason): + """ fail and give reason """ + raise Fail(reason) + + + def abort(self, reason=""): + """ abort and give reason """ + if reason: + self.pyfile.error = str(reason) + raise Abort + + + def error(self, reason="", type=""): + if not reason and not type: + type = "unknown" + + msg = _("%s error") % _(type.strip().capitalize()) if type else _("Error") + msg += ": " + reason.strip() if reason else "" + msg += _(" | Plugin may be out of date") + + raise Fail(msg) + + + def offline(self, reason=""): + """ fail and indicate file is offline """ + if reason: + self.pyfile.error = str(reason) + raise Fail("offline") + + + def tempOffline(self, reason=""): + """ fail and indicates file ist temporary offline, the core may take consequences """ + if reason: + self.pyfile.error = str(reason) + raise Fail("temp. offline") + + + def retry(self, max_tries=5, wait_time=1, reason=""): + """Retries and begin again from the beginning + + :param max_tries: number of maximum retries + :param wait_time: time to wait in seconds + :param reason: reason for retrying, will be passed to fail if max_tries reached + """ + if 0 < max_tries <= self.retries: + self.error(reason or _("Max retries reached"), "retry") + + self.wait(wait_time, False) + + self.retries += 1 + raise Retry(reason) + + + def invalidCaptcha(self): + self.logError(_("Invalid captcha")) + if self.cTask: + self.cTask.invalid() + + + def correctCaptcha(self): + self.logInfo(_("Correct captcha")) + if self.cTask: + self.cTask.correct() + + + def decryptCaptcha(self, url, get={}, post={}, cookies=False, forceUser=False, imgtype='jpg', + result_type='textual', timeout=290): + """ Loads a captcha and decrypts it with ocr, plugin, user input + + :param url: url of captcha image + :param get: get part for request + :param post: post part for request + :param cookies: True if cookies should be enabled + :param forceUser: if True, ocr is not used + :param imgtype: Type of the Image + :param result_type: 'textual' if text is written on the captcha\ + or 'positional' for captcha where the user have to click\ + on a specific region on the captcha + + :return: result of decrypting + """ + + img = self.load(url, get=get, post=post, cookies=cookies) + + id = ("%.2f" % time.time())[-6:].replace(".", "") + + with open(join("tmp", "tmpCaptcha_%s_%s.%s" % (self.getClassName(), id, imgtype)), "wb") as tmpCaptcha: + tmpCaptcha.write(img) + + has_plugin = self.getClassName() in self.core.pluginManager.ocrPlugins + + if self.core.captcha: + Ocr = self.core.pluginManager.loadClass("ocr", self.getClassName()) + else: + Ocr = None + + if Ocr and not forceUser: + time.sleep(random.randint(3000, 5000) / 1000.0) + if self.pyfile.abort: + self.abort() + + ocr = Ocr() + result = ocr.get_captcha(tmpCaptcha.name) + else: + captchaManager = self.core.captchaManager + task = captchaManager.newTask(img, imgtype, tmpCaptcha.name, result_type) + self.cTask = task + captchaManager.handleCaptcha(task, timeout) + + while task.isWaiting(): + if self.pyfile.abort: + captchaManager.removeTask(task) + self.abort() + time.sleep(1) + + captchaManager.removeTask(task) + + if task.error and has_plugin: #: ignore default error message since the user could use OCR + self.fail(_("Pil and tesseract not installed and no Client connected for captcha decrypting")) + elif task.error: + self.fail(task.error) + elif not task.result: + self.fail(_("No captcha result obtained in appropiate time by any of the plugins")) + + result = task.result + self.logDebug("Received captcha result: %s" % result) + + if not self.core.debug: + try: + remove(tmpCaptcha.name) + except Exception: + pass + + return result + + + def load(self, url, get={}, post={}, ref=True, cookies=True, just_header=False, decode=False, follow_location=True, save_cookies=True): + """Load content at url and returns it + + :param url: + :param get: + :param post: + :param ref: + :param cookies: + :param just_header: If True only the header will be retrieved and returned as dict + :param decode: Wether to decode the output according to http header, should be True in most cases + :param follow_location: If True follow location else not + :param save_cookies: If True saves received cookies else discard them + :return: Loaded content + """ + if self.pyfile.abort: + self.abort() + + if not url: + self.fail(_("No url given")) + + url = urllib.unquote(encode(url).strip()) #@NOTE: utf8 vs decode -> please use decode attribute in all future plugins + + if self.core.debug: + self.logDebug("Load url: " + url, *["%s=%s" % (key, val) for key, val in locals().iteritems() if key not in ("self", "url")]) + + res = self.req.load(url, get, post, ref, cookies, just_header, decode=decode, follow_location=follow_location, save_cookies=save_cookies) + + if decode: + res = encode(res) + + if self.core.debug: + from inspect import currentframe + + frame = currentframe() + framefile = fs_join("tmp", self.getClassName(), "%s_line%s.dump.html" % (frame.f_back.f_code.co_name, frame.f_back.f_lineno)) + try: + if not exists(join("tmp", self.getClassName())): + makedirs(join("tmp", self.getClassName())) + + with open(framefile, "wb") as f: + del frame #: delete the frame or it wont be cleaned + f.write(res) + except IOError, e: + self.logError(e) + + if just_header: + # parse header + header = {"code": self.req.code} + for line in res.splitlines(): + line = line.strip() + if not line or ":" not in line: + continue + + key, none, value = line.partition(":") + key = key.strip().lower() + value = value.strip() + + if key in header: + if type(header[key]) == list: + header[key].append(value) + else: + header[key] = [header[key], value] + else: + header[key] = value + res = header + + return res + + + def download(self, url, get={}, post={}, ref=True, cookies=True, disposition=False): + """Downloads the content at url to download folder + + :param url: + :param get: + :param post: + :param ref: + :param cookies: + :param disposition: if True and server provides content-disposition header\ + the filename will be changed if needed + :return: The location where the file was saved + """ + if self.pyfile.abort: + self.abort() + + if not url: + self.fail(_("No url given")) + + url = urllib.unquote(encode(url).strip()) + + if self.core.debug: + self.logDebug("Download url: " + url, *["%s=%s" % (key, val) for key, val in locals().iteritems() if key not in ("self", "url")]) + + self.checkForSameFiles() + + self.pyfile.setStatus("downloading") + + if disposition: + self.pyfile.name = urlparse.urlparse(url).path.split('/')[-1] or self.pyfile.name + + download_folder = self.core.config.get("general", "download_folder") + + location = fs_join(download_folder, self.pyfile.package().folder) + + if not exists(location): + try: + makedirs(location, int(self.core.config.get("permission", "folder"), 8)) + + if self.core.config.get("permission", "change_dl") and os.name != "nt": + uid = getpwnam(self.core.config.get("permission", "user"))[2] + gid = getgrnam(self.core.config.get("permission", "group"))[2] + chown(location, uid, gid) + + except Exception, e: + self.fail(e) + + # convert back to unicode + location = fs_decode(location) + name = safe_filename(self.pyfile.name) + + filename = join(location, name) + + self.core.addonManager.dispatchEvent("download-start", self.pyfile, url, filename) + + try: + newname = self.req.httpDownload(url, filename, get=get, post=post, ref=ref, cookies=cookies, + chunks=self.getChunkCount(), resume=self.resumeDownload, + progressNotify=self.pyfile.setProgress, disposition=disposition) + finally: + self.pyfile.size = self.req.size + + if newname: + newname = urlparse.urlparse(newname).path.split('/')[-1] + + if disposition and newname != name: + self.logInfo(_("%(name)s saved as %(newname)s") % {"name": name, "newname": newname}) + self.pyfile.name = newname + filename = join(location, newname) + + fs_filename = fs_encode(filename) + + if self.core.config.get("permission", "change_file"): + try: + chmod(fs_filename, int(self.core.config.get("permission", "file"), 8)) + except Exception, e: + self.logWarning(_("Setting file mode failed"), e) + + if self.core.config.get("permission", "change_dl") and os.name != "nt": + try: + uid = getpwnam(self.core.config.get("permission", "user"))[2] + gid = getgrnam(self.core.config.get("permission", "group"))[2] + chown(fs_filename, uid, gid) + + except Exception, e: + self.logWarning(_("Setting User and Group failed"), e) + + self.lastDownload = filename + return self.lastDownload + + + def checkDownload(self, rules, api_size=0, max_size=50000, delete=True, read_size=0): + """ checks the content of the last downloaded file, re match is saved to `lastCheck` + + :param rules: dict with names and rules to match (compiled regexp or strings) + :param api_size: expected file size + :param max_size: if the file is larger then it wont be checked + :param delete: delete if matched + :param read_size: amount of bytes to read from files larger then max_size + :return: dictionary key of the first rule that matched + """ + lastDownload = fs_encode(self.lastDownload) + if not exists(lastDownload): + return None + + size = stat(lastDownload) + size = size.st_size + + if api_size and api_size <= size: + return None + elif size > max_size and not read_size: + return None + self.logDebug("Download Check triggered") + + with open(lastDownload, "rb") as f: + content = f.read(read_size if read_size else -1) + + # produces encoding errors, better log to other file in the future? + # self.logDebug("Content: %s" % content) + for name, rule in rules.iteritems(): + if isinstance(rule, basestring): + if rule in content: + if delete: + remove(lastDownload) + return name + elif hasattr(rule, "search"): + m = rule.search(content) + if m: + if delete: + remove(lastDownload) + self.lastCheck = m + return name + + + def getPassword(self): + """ get the password the user provided in the package""" + password = self.pyfile.package().password + if not password: + return "" + return password + + + def checkForSameFiles(self, starting=False): + """ checks if same file was/is downloaded within same package + + :param starting: indicates that the current download is going to start + :raises SkipDownload: + """ + + pack = self.pyfile.package() + + for pyfile in self.core.files.cache.values(): + if pyfile != self.pyfile and pyfile.name == self.pyfile.name and pyfile.package().folder == pack.folder: + if pyfile.status in (0, 12): #: finished or downloading + raise SkipDownload(pyfile.pluginname) + elif pyfile.status in (5, 7) and starting: #: a download is waiting/starting and was appenrently started before + raise SkipDownload(pyfile.pluginname) + + download_folder = self.core.config.get("general", "download_folder") + location = fs_join(download_folder, pack.folder, self.pyfile.name) + + if starting and self.core.config.get("download", "skip_existing") and exists(location): + size = os.stat(location).st_size + if size >= self.pyfile.size: + raise SkipDownload("File exists") + + pyfile = self.core.db.findDuplicates(self.pyfile.id, self.pyfile.package().folder, self.pyfile.name) + if pyfile: + if exists(location): + raise SkipDownload(pyfile[0]) + + self.logDebug("File %s not skipped, because it does not exists." % self.pyfile.name) + + + def clean(self): + """ clean everything and remove references """ + if hasattr(self, "pyfile"): + del self.pyfile + + if hasattr(self, "req"): + self.req.close() + del self.req + + if hasattr(self, "thread"): + del self.thread + + if hasattr(self, "html"): + del self.html diff --git a/pyload/plugin/__init__.py b/pyload/plugin/__init__.py new file mode 100644 index 000000000..40a96afc6 --- /dev/null +++ b/pyload/plugin/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/pyload/plugin/account/AlldebridCom.py b/pyload/plugin/account/AlldebridCom.py new file mode 100644 index 000000000..efc5753f8 --- /dev/null +++ b/pyload/plugin/account/AlldebridCom.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- + +import re +import time +import xml.dom.minidom as dom + +from BeautifulSoup import BeautifulSoup + +from pyload.plugin.Account import Account + + +class AlldebridCom(Account): + __name = "AlldebridCom" + __type = "account" + __version = "0.23" + + __description = """AllDebrid.com account plugin""" + __license = "GPLv3" + __authors = [("Andy Voigt", "spamsales@online.de")] + + + def loadAccountInfo(self, user, req): + data = self.getAccountData(user) + html = req.load("http://www.alldebrid.com/account/") + soup = BeautifulSoup(html) + + # Try to parse expiration date directly from the control panel page (better accuracy) + try: + time_text = soup.find('div', attrs={'class': 'remaining_time_text'}).strong.string + + self.logDebug("Account expires in: %s" % time_text) + + p = re.compile('\d+') + exp_data = p.findall(time_text) + exp_time = time.time() + int(exp_data[0]) * 24 * 60 * 60 + int( + exp_data[1]) * 60 * 60 + (int(exp_data[2]) - 1) * 60 + + # Get expiration date from API + except Exception: + data = self.getAccountData(user) + html = req.load("http://www.alldebrid.com/api.php", + get={'action': "info_user", 'login': user, 'pw': data['password']}) + + self.logDebug(html) + + xml = dom.parseString(html) + exp_time = time.time() + int(xml.getElementsByTagName("date")[0].childNodes[0].nodeValue) * 24 * 60 * 60 + + account_info = {"validuntil": exp_time, "trafficleft": -1} + return account_info + + + def login(self, user, data, req): + html = req.load("http://www.alldebrid.com/register/", + get={'action' : "login", + 'login_login' : user, + 'login_password': data['password']}, + decode=True) + + if "This login doesn't exist" in html \ + or "The password is not valid" in html \ + or "Invalid captcha" in html: + self.wrongPassword() diff --git a/pyload/plugin/account/BackinNet.py b/pyload/plugin/account/BackinNet.py new file mode 100644 index 000000000..79d46c761 --- /dev/null +++ b/pyload/plugin/account/BackinNet.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSAccount import XFSAccount + + +class BackinNet(XFSAccount): + __name = "BackinNet" + __type = "account" + __version = "0.01" + + __description = """Backin.net account plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + HOSTER_DOMAIN = "backin.net" diff --git a/pyload/plugin/account/BillionuploadsCom.py b/pyload/plugin/account/BillionuploadsCom.py new file mode 100644 index 000000000..982a2cc34 --- /dev/null +++ b/pyload/plugin/account/BillionuploadsCom.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSAccount import XFSAccount + + +class BillionuploadsCom(XFSAccount): + __name = "BillionuploadsCom" + __type = "account" + __version = "0.02" + + __description = """Billionuploads.com account plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + HOSTER_DOMAIN = "billionuploads.com" diff --git a/pyload/plugin/account/BitshareCom.py b/pyload/plugin/account/BitshareCom.py new file mode 100644 index 000000000..8a64d3b4c --- /dev/null +++ b/pyload/plugin/account/BitshareCom.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.Account import Account + + +class BitshareCom(Account): + __name = "BitshareCom" + __type = "account" + __version = "0.13" + + __description = """Bitshare account plugin""" + __license = "GPLv3" + __authors = [("Paul King", "")] + + + def loadAccountInfo(self, user, req): + html = req.load("http://bitshare.com/mysettings.html") + + if "\"http://bitshare.com/myupgrade.html\">Free" in html: + return {"validuntil": -1, "trafficleft": -1, "premium": False} + + if not '<input type="checkbox" name="directdownload" checked="checked" />' in html: + self.logWarning(_("Activate direct Download in your Bitshare Account")) + + return {"validuntil": -1, "trafficleft": -1, "premium": True} + + + def login(self, user, data, req): + html = req.load("http://bitshare.com/login.html", + post={"user": user, "password": data['password'], "submit": "Login"}, + decode=True) + + if "login" in req.lastEffectiveURL: + self.wrongPassword() diff --git a/pyload/plugin/account/CatShareNet.py b/pyload/plugin/account/CatShareNet.py new file mode 100644 index 000000000..8f821413f --- /dev/null +++ b/pyload/plugin/account/CatShareNet.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- + +import re +import time + +from pyload.plugin.Account import Account + + +class CatShareNet(Account): + __name = "CatShareNet" + __type = "account" + __version = "0.05" + + __description = """CatShareNet account plugin""" + __license = "GPLv3" + __authors = [("prOq", "")] + + + PREMIUM_PATTERN = r'<a href="/premium">Konto:[\s\n]*Premium' + VALID_UNTIL_PATTERN = r'>Konto premium.*?<strong>(.*?)</strong></span>' + TRAFFIC_LEFT_PATTERN = r'<a href="/premium">([0-9.]+ [kMG]B)' + + + def loadAccountInfo(self, user, req): + premium = False + validuntil = -1 + trafficleft = -1 + + html = req.load("http://catshare.net/", decode=True) + + if re.search(self.PREMIUM_PATTERN, html): + premium = True + + try: + expiredate = re.search(self.VALID_UNTIL_PATTERN, html).group(1) + self.logDebug("Expire date: " + expiredate) + + validuntil = time.mktime(time.strptime(expiredate, "%Y-%m-%d %H:%M:%S")) + + except Exception: + pass + + try: + trafficleft = self.parseTraffic(re.search(self.TRAFFIC_LEFT_PATTERN, html).group(1)) + + except Exception: + pass + + return {'premium': premium, 'trafficleft': trafficleft, 'validuntil': validuntil} + + + def login(self, user, data, req): + html = req.load("http://catshare.net/login", + post={'user_email': user, + 'user_password': data['password'], + 'remindPassword': 0, + 'user[submit]': "Login"}, + decode=True) + + if not '<a href="/logout">Wyloguj</a>' in html: + self.wrongPassword() diff --git a/pyload/plugin/account/CloudzillaTo.py b/pyload/plugin/account/CloudzillaTo.py new file mode 100644 index 000000000..bee7c5a17 --- /dev/null +++ b/pyload/plugin/account/CloudzillaTo.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.Account import Account + + +class CloudzillaTo(Account): + __name = "CloudzillaTo" + __type = "account" + __version = "0.02" + + __description = """Cloudzilla.to account plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + PREMIUM_PATTERN = r'<h2>account type</h2>\s*Premium Account' + + + def loadAccountInfo(self, user, req): + html = req.load("http://www.cloudzilla.to/") + + premium = re.search(self.PREMIUM_PATTERN, html) is not None + + return {'validuntil': -1, 'trafficleft': -1, 'premium': premium} + + + def login(self, user, data, req): + html = req.load("http://www.cloudzilla.to/", + post={'lusername': user, + 'lpassword': data['password'], + 'w' : "dologin"}, + decode=True) + + if "ERROR" in html: + self.wrongPassword() diff --git a/pyload/plugin/account/CramitIn.py b/pyload/plugin/account/CramitIn.py new file mode 100644 index 000000000..ccd291776 --- /dev/null +++ b/pyload/plugin/account/CramitIn.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSAccount import XFSAccount + + +class CramitIn(XFSAccount): + __name = "CramitIn" + __type = "account" + __version = "0.03" + + __description = """Cramit.in account plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + HOSTER_DOMAIN = "cramit.in" diff --git a/pyload/plugin/account/CzshareCom.py b/pyload/plugin/account/CzshareCom.py new file mode 100644 index 000000000..4913ed88f --- /dev/null +++ b/pyload/plugin/account/CzshareCom.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- + +import re +import time + +from pyload.plugin.Account import Account + + +class CzshareCom(Account): + __name = "CzshareCom" + __type = "account" + __version = "0.18" + + __description = """Czshare.com account plugin, now Sdilej.cz""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz"), + ("stickell", "l.stickell@yahoo.it")] + + + CREDIT_LEFT_PATTERN = r'<tr class="active">\s*<td>([\d ,]+) (KiB|MiB|GiB)</td>\s*<td>([^<]*)</td>\s*</tr>' + + + def loadAccountInfo(self, user, req): + premium = False + validuntil = None + trafficleft = None + + html = req.load("http://sdilej.cz/prehled_kreditu/") + + try: + m = re.search(self.CREDIT_LEFT_PATTERN, html) + trafficleft = self.parseTraffic(m.group(1).replace(' ', '').replace(',', '.')) + m.group(2) + validuntil = time.mktime(time.strptime(m.group(3), '%d.%m.%y %H:%M')) + + except Exception, e: + self.logError(e) + + else: + premium = True + + return {'premium' : premium, + 'validuntil' : validuntil, + 'trafficleft': trafficleft} + + + def login(self, user, data, req): + html = req.load('https://sdilej.cz/index.php', + post={"Prihlasit": "Prihlasit", + "login-password": data['password'], + "login-name": user}, + decode=True) + + if '<div class="login' in html: + self.wrongPassword() diff --git a/pyload/plugin/account/DebridItaliaCom.py b/pyload/plugin/account/DebridItaliaCom.py new file mode 100644 index 000000000..49959d5c3 --- /dev/null +++ b/pyload/plugin/account/DebridItaliaCom.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- + +import re +import time + +from pyload.plugin.Account import Account + + +class DebridItaliaCom(Account): + __name = "DebridItaliaCom" + __type = "account" + __version = "0.13" + + __description = """Debriditalia.com account plugin""" + __license = "GPLv3" + __authors = [("stickell", "l.stickell@yahoo.it"), + ("Walter Purcaro", "vuolter@gmail.com")] + + + WALID_UNTIL_PATTERN = r'Premium valid till: (.+?) \|' + + + def loadAccountInfo(self, user, req): + info = {'premium': False, 'validuntil': None, 'trafficleft': None} + html = req.load("http://debriditalia.com/") + + if 'Account premium not activated' not in html: + m = re.search(self.WALID_UNTIL_PATTERN, html) + if m: + validuntil = time.mktime(time.strptime(m.group(1), "%d/%m/%Y %H:%M")) + info = {'premium': True, 'validuntil': validuntil, 'trafficleft': -1} + else: + self.logError(_("Unable to retrieve account information")) + + return info + + + def login(self, user, data, req): + html = req.load("http://debriditalia.com/login.php", + get={'u': user, 'p': data['password']}, + decode=True) + + if 'NO' in html: + self.wrongPassword() diff --git a/pyload/plugin/account/DepositfilesCom.py b/pyload/plugin/account/DepositfilesCom.py new file mode 100644 index 000000000..4e09ee2ed --- /dev/null +++ b/pyload/plugin/account/DepositfilesCom.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- + +import re +import time + +from pyload.plugin.Account import Account + + +class DepositfilesCom(Account): + __name = "DepositfilesCom" + __type = "account" + __version = "0.32" + + __description = """Depositfiles.com account plugin""" + __license = "GPLv3" + __authors = [("mkaay", "mkaay@mkaay.de"), + ("stickell", "l.stickell@yahoo.it"), + ("Walter Purcaro", "vuolter@gmail.com")] + + + def loadAccountInfo(self, user, req): + html = req.load("https://dfiles.eu/de/gold/") + validuntil = re.search(r"Sie haben Gold Zugang bis: <b>(.*?)</b></div>", html).group(1) + + validuntil = time.mktime(time.strptime(validuntil, "%Y-%m-%d %H:%M:%S")) + + return {"validuntil": validuntil, "trafficleft": -1} + + + def login(self, user, data, req): + html = req.load("https://dfiles.eu/de/login.php", get={"return": "/de/gold/payment.php"}, + post={"login": user, "password": data['password']}, + decode=True) + + if r'<div class="error_message">Sie haben eine falsche Benutzername-Passwort-Kombination verwendet.</div>' in html: + self.wrongPassword() diff --git a/pyload/plugin/account/DropboxCom.py b/pyload/plugin/account/DropboxCom.py new file mode 100644 index 000000000..055c28195 --- /dev/null +++ b/pyload/plugin/account/DropboxCom.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class DropboxCom(SimpleHoster): + __name = "DropboxCom" + __type = "hoster" + __version = "0.04" + + __pattern = r'https?://(?:www\.)?dropbox\.com/.+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Dropbox.com hoster plugin""" + __license = "GPLv3" + __authors = [("zapp-brannigan", "fuerst.reinje@web.de")] + + + NAME_PATTERN = r'<title>Dropbox - (?P<N>.+?)<' + SIZE_PATTERN = r' · (?P<S>[\d.,]+) (?P<U>[\w^_]+)' + + OFFLINE_PATTERN = r'<title>Dropbox - (404|Shared link error)<' + + COOKIES = [("dropbox.com", "lang", "en")] + + + def setup(self): + self.multiDL = True + self.chunkLimit = 1 + self.resumeDownload = True + + + def handleFree(self, pyfile): + self.download(pyfile.url, get={'dl': "1"}) diff --git a/pyload/plugin/account/EasybytezCom.py b/pyload/plugin/account/EasybytezCom.py new file mode 100644 index 000000000..6419369e8 --- /dev/null +++ b/pyload/plugin/account/EasybytezCom.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSAccount import XFSAccount + + +class EasybytezCom(XFSAccount): + __name = "EasybytezCom" + __type = "account" + __version = "0.12" + + __description = """EasyBytez.com account plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz"), + ("guidobelix", "guidobelix@hotmail.it")] + + + HOSTER_DOMAIN = "easybytez.com" diff --git a/pyload/plugin/account/EuroshareEu.py b/pyload/plugin/account/EuroshareEu.py new file mode 100644 index 000000000..e1e037bf9 --- /dev/null +++ b/pyload/plugin/account/EuroshareEu.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- + +import re +import time + +from pyload.plugin.Account import Account + + +class EuroshareEu(Account): + __name = "EuroshareEu" + __type = "account" + __version = "0.02" + + __description = """Euroshare.eu account plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + def loadAccountInfo(self, user, req): + self.relogin(user) + html = req.load("http://euroshare.eu/customer-zone/settings/") + + m = re.search('id="input_expire_date" value="(\d+\.\d+\.\d+ \d+:\d+)"', html) + if m is None: + premium, validuntil = False, -1 + else: + premium = True + validuntil = time.mktime(time.strptime(m.group(1), "%d.%m.%Y %H:%M")) + + return {"validuntil": validuntil, "trafficleft": -1, "premium": premium} + + + def login(self, user, data, req): + html = req.load('http://euroshare.eu/customer-zone/login/', + post={"trvale": "1", + "login": user, + "password": data['password']}, + decode=True) + + if u">Nesprávne prihlasovacie meno alebo heslo" in html: + self.wrongPassword() diff --git a/pyload/plugin/account/ExashareCom.py b/pyload/plugin/account/ExashareCom.py new file mode 100644 index 000000000..e61c2f12d --- /dev/null +++ b/pyload/plugin/account/ExashareCom.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSAccount import XFSAccount + + +class ExashareCom(XFSAccount): + __name = "ExashareCom" + __type = "account" + __version = "0.01" + + __description = """Exashare.com account plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + HOSTER_DOMAIN = "exashare.com" diff --git a/pyload/plugin/account/FastixRu.py b/pyload/plugin/account/FastixRu.py new file mode 100644 index 000000000..ead4e63aa --- /dev/null +++ b/pyload/plugin/account/FastixRu.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.Account import Account +from pyload.utils import json_loads + + +class FastixRu(Account): + __name = "FastixRu" + __type = "account" + __version = "0.03" + + __description = """Fastix account plugin""" + __license = "GPLv3" + __authors = [("Massimo Rosamilia", "max@spiritix.eu")] + + + def loadAccountInfo(self, user, req): + data = self.getAccountData(user) + html = json_loads(req.load("http://fastix.ru/api_v2/", get={'apikey': data['api'], 'sub': "getaccountdetails"})) + + points = html['points'] + trafficleft = float(points) * 1024 ** 2 / 1000 + + if points > 0: + account_info = {"validuntil": -1, "trafficleft": trafficleft} + else: + account_info = {"validuntil": None, "trafficleft": None, "premium": False} + + return account_info + + + def login(self, user, data, req): + html = req.load("http://fastix.ru/api_v2/", + get={'sub': "get_apikey", 'email': user, 'password': data['password']}) + + api = json_loads(html) + api = api['apikey'] + + data['api'] = api + + if "error_code" in html: + self.wrongPassword() diff --git a/pyload/plugin/account/FastshareCz.py b/pyload/plugin/account/FastshareCz.py new file mode 100644 index 000000000..f1ed9d634 --- /dev/null +++ b/pyload/plugin/account/FastshareCz.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.Account import Account +from pyload.utils import parseFileSize + + +class FastshareCz(Account): + __name = "FastshareCz" + __type = "account" + __version = "0.06" + + __description = """Fastshare.cz account plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz"), + ("stickell", "l.stickell@yahoo.it")] + + + CREDIT_PATTERN = r'Credit\s*:\s*</td>\s*<td>(.+?)\s*<' + + + def loadAccountInfo(self, user, req): + validuntil = -1 + trafficleft = None + premium = False + + html = req.load("http://www.fastshare.cz/user", decode=True) + + m = re.search(self.CREDIT_PATTERN, html) + if m: + trafficleft = self.parseTraffic(m.group(1)) + + premium = bool(trafficleft) + + return {'validuntil' : validuntil, + 'trafficleft': trafficleft, + 'premium' : premium} + + + def login(self, user, data, req): + req.cj.setCookie("fastshare.cz", "lang", "en") + + req.load('http://www.fastshare.cz/login') #: Do not remove or it will not login + + html = req.load("http://www.fastshare.cz/sql.php", + post={'login': user, 'heslo': data['password']}, + decode=True) + + if ">Wrong username or password" in html: + self.wrongPassword() diff --git a/pyload/plugin/account/File4SafeCom.py b/pyload/plugin/account/File4SafeCom.py new file mode 100644 index 000000000..1c7e00fcf --- /dev/null +++ b/pyload/plugin/account/File4SafeCom.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSAccount import XFSAccount + + +class File4SafeCom(XFSAccount): + __name = "File4SafeCom" + __type = "account" + __version = "0.05" + + __description = """File4Safe.com account plugin""" + __license = "GPLv3" + __authors = [("stickell", "l.stickell@yahoo.it")] + + + HOSTER_DOMAIN = "file4safe.com" + + LOGIN_FAIL_PATTERN = r'input_login' diff --git a/pyload/plugin/account/FileParadoxIn.py b/pyload/plugin/account/FileParadoxIn.py new file mode 100644 index 000000000..0e103c4e7 --- /dev/null +++ b/pyload/plugin/account/FileParadoxIn.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSAccount import XFSAccount + + +class FileParadoxIn(XFSAccount): + __name = "FileParadoxIn" + __type = "account" + __version = "0.02" + + __description = """FileParadox.in account plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + HOSTER_DOMAIN = "fileparadox.in" diff --git a/pyload/plugin/account/FilecloudIo.py b/pyload/plugin/account/FilecloudIo.py new file mode 100644 index 000000000..b07fe981a --- /dev/null +++ b/pyload/plugin/account/FilecloudIo.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.Account import Account +from pyload.utils import json_loads + + +class FilecloudIo(Account): + __name = "FilecloudIo" + __type = "account" + __version = "0.04" + + __description = """FilecloudIo account plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz"), + ("stickell", "l.stickell@yahoo.it")] + + + def loadAccountInfo(self, user, req): + # It looks like the first API request always fails, so we retry 5 times, it should work on the second try + for _i in xrange(5): + rep = req.load("https://secure.filecloud.io/api-fetch_apikey.api", + post={"username": user, "password": self.getAccountData(user)['password']}) + rep = json_loads(rep) + if rep['status'] == 'ok': + break + elif rep['status'] == 'error' and rep['message'] == 'no such user or wrong password': + self.logError(_("Wrong username or password")) + return {"valid": False, "premium": False} + else: + return {"premium": False} + + akey = rep['akey'] + self.accounts[user]['akey'] = akey #: Saved for hoster plugin + rep = req.load("http://api.filecloud.io/api-fetch_account_details.api", + post={"akey": akey}) + rep = json_loads(rep) + + if rep['is_premium'] == 1: + return {"validuntil": float(rep['premium_until']), "trafficleft": -1} + else: + return {"premium": False} + + + def login(self, user, data, req): + req.cj.setCookie("secure.filecloud.io", "lang", "en") + html = req.load('https://secure.filecloud.io/user-login.html') + + if not hasattr(self, "form_data"): + self.form_data = {} + + self.form_data['username'] = user + self.form_data['password'] = data['password'] + + html = req.load('https://secure.filecloud.io/user-login_p.html', + post=self.form_data, + multipart=True) + + if "you have successfully logged in" not in html: + self.wrongPassword() diff --git a/pyload/plugin/account/FilefactoryCom.py b/pyload/plugin/account/FilefactoryCom.py new file mode 100644 index 000000000..5c80c6e1c --- /dev/null +++ b/pyload/plugin/account/FilefactoryCom.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- + +import pycurl +import re +import time + +from pyload.plugin.Account import Account + + +class FilefactoryCom(Account): + __name = "FilefactoryCom" + __type = "account" + __version = "0.15" + + __description = """Filefactory.com account plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz"), + ("stickell", "l.stickell@yahoo.it")] + + + VALID_UNTIL_PATTERN = r'Premium valid until: <strong>(?P<D>\d{1,2})\w{1,2} (?P<M>\w{3}), (?P<Y>\d{4})</strong>' + + + def loadAccountInfo(self, user, req): + html = req.load("http://www.filefactory.com/account/") + + m = re.search(self.VALID_UNTIL_PATTERN, html) + if m: + premium = True + validuntil = re.sub(self.VALID_UNTIL_PATTERN, '\g<D> \g<M> \g<Y>', m.group(0)) + validuntil = time.mktime(time.strptime(validuntil, "%d %b %Y")) + else: + premium = False + validuntil = -1 + + return {"premium": premium, "trafficleft": -1, "validuntil": validuntil} + + + def login(self, user, data, req): + req.http.c.setopt(pycurl.REFERER, "http://www.filefactory.com/member/login.php") + + html = req.load("http://www.filefactory.com/member/signin.php", + post={"loginEmail" : user, + "loginPassword": data['password'], + "Submit" : "Sign In"}) + + if req.lastEffectiveURL != "http://www.filefactory.com/account/": + self.wrongPassword() diff --git a/pyload/plugin/account/FilejungleCom.py b/pyload/plugin/account/FilejungleCom.py new file mode 100644 index 000000000..19d343372 --- /dev/null +++ b/pyload/plugin/account/FilejungleCom.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- + +import re +import time + +from pyload.plugin.Account import Account + + +class FilejungleCom(Account): + __name = "FilejungleCom" + __type = "account" + __version = "0.12" + + __description = """Filejungle.com account plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + login_timeout = 60 + + URL = "http://filejungle.com/" + TRAFFIC_LEFT_PATTERN = r'"/extend_premium\.php">Until (\d+ \w+ \d+)<br' + LOGIN_FAILED_PATTERN = r'<span htmlfor="loginUser(Name|Password)" generated="true" class="fail_info">' + + + def loadAccountInfo(self, user, req): + html = req.load(self.URL + "dashboard.php") + m = re.search(self.TRAFFIC_LEFT_PATTERN, html) + if m: + premium = True + validuntil = time.mktime(time.strptime(m.group(1), "%d %b %Y")) + else: + premium = False + validuntil = -1 + + return {"premium": premium, "trafficleft": -1, "validuntil": validuntil} + + + def login(self, user, data, req): + html = req.load(self.URL + "login.php", + post={"loginUserName": user, + "loginUserPassword": data['password'], + "loginFormSubmit": "Login", + "recaptcha_challenge_field": "", + "recaptcha_response_field": "", + "recaptcha_shortencode_field": ""}, + decode=True) + + if re.search(self.LOGIN_FAILED_PATTERN, html): + self.wrongPassword() diff --git a/pyload/plugin/account/FileomCom.py b/pyload/plugin/account/FileomCom.py new file mode 100644 index 000000000..2868e49e6 --- /dev/null +++ b/pyload/plugin/account/FileomCom.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSAccount import XFSAccount + + +class FileomCom(XFSAccount): + __name = "FileomCom" + __type = "account" + __version = "0.02" + + __description = """Fileom.com account plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + HOSTER_DOMAIN = "fileom.com" diff --git a/pyload/plugin/account/FilerNet.py b/pyload/plugin/account/FilerNet.py new file mode 100644 index 000000000..b6baddacc --- /dev/null +++ b/pyload/plugin/account/FilerNet.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- + +import re +import time + +from pyload.plugin.Account import Account + + +class FilerNet(Account): + __name = "FilerNet" + __type = "account" + __version = "0.04" + + __description = """Filer.net account plugin""" + __license = "GPLv3" + __authors = [("stickell", "l.stickell@yahoo.it")] + + + TOKEN_PATTERN = r'_csrf_token" value="(.+?)" />' + WALID_UNTIL_PATTERN = r'Der Premium-Zugang ist gÃŒltig bis (.+)\.\s*</td>' + TRAFFIC_PATTERN = r'Traffic</th>\s*<td>([^<]+)</td>' + FREE_PATTERN = r'Account Status</th>\s*<td>\s*Free' + + + def loadAccountInfo(self, user, req): + html = req.load("https://filer.net/profile") + + # Free user + if re.search(self.FREE_PATTERN, html): + return {"premium": False, "validuntil": None, "trafficleft": None} + + until = re.search(self.WALID_UNTIL_PATTERN, html) + traffic = re.search(self.TRAFFIC_PATTERN, html) + + if until and traffic: + validuntil = time.mktime(time.strptime(until.group(1), "%d.%m.%Y %H:%M:%S")) + trafficleft = self.parseTraffic(traffic.group(1)) + return {"premium": True, "validuntil": validuntil, "trafficleft": trafficleft} + + else: + self.logError(_("Unable to retrieve account information")) + return {"premium": False, "validuntil": None, "trafficleft": None} + + + def login(self, user, data, req): + html = req.load("https://filer.net/login") + + token = re.search(self.TOKEN_PATTERN, html).group(1) + + html = req.load("https://filer.net/login_check", + post={"_username": user, + "_password": data['password'], + "_remember_me": "on", + "_csrf_token": token, + "_target_path": "https://filer.net/"}, + decode=True) + + if 'Logout' not in html: + self.wrongPassword() diff --git a/pyload/plugin/account/FilerioCom.py b/pyload/plugin/account/FilerioCom.py new file mode 100644 index 000000000..d222fa78b --- /dev/null +++ b/pyload/plugin/account/FilerioCom.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSAccount import XFSAccount + + +class FilerioCom(XFSAccount): + __name = "FilerioCom" + __type = "account" + __version = "0.03" + + __description = """FileRio.in account plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + HOSTER_DOMAIN = "filerio.in" diff --git a/pyload/plugin/account/FilesMailRu.py b/pyload/plugin/account/FilesMailRu.py new file mode 100644 index 000000000..03b56be44 --- /dev/null +++ b/pyload/plugin/account/FilesMailRu.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.Account import Account + + +class FilesMailRu(Account): + __name = "FilesMailRu" + __type = "account" + __version = "0.11" + + __description = """Filesmail.ru account plugin""" + __license = "GPLv3" + __authors = [("RaNaN", "RaNaN@pyload.org")] + + + def loadAccountInfo(self, user, req): + return {"validuntil": None, "trafficleft": None} + + + def login(self, user, data, req): + user, domain = user.split("@") + + html = req.load("http://swa.mail.ru/cgi-bin/auth", + post={"Domain": domain, + "Login": user, + "Password": data['password'], + "Page": "http://files.mail.ru/"}, + decode=True) + + if "ÐевеÑМПе ÐžÐŒÑ Ð¿ÐŸÐ»ÑзПваÑÐµÐ»Ñ ÐžÐ»Ðž паÑПлÑ" in html: + self.wrongPassword() diff --git a/pyload/plugin/account/FileserveCom.py b/pyload/plugin/account/FileserveCom.py new file mode 100644 index 000000000..d68285a33 --- /dev/null +++ b/pyload/plugin/account/FileserveCom.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- + +import time + +from pyload.plugin.Account import Account +from pyload.utils import json_loads + + +class FileserveCom(Account): + __name = "FileserveCom" + __type = "account" + __version = "0.20" + + __description = """Fileserve.com account plugin""" + __license = "GPLv3" + __authors = [("mkaay", "mkaay@mkaay.de")] + + + def loadAccountInfo(self, user, req): + data = self.getAccountData(user) + + html = req.load("http://app.fileserve.com/api/login/", post={"username": user, "password": data['password'], + "submit": "Submit+Query"}) + res = json_loads(html) + + if res['type'] == "premium": + validuntil = time.mktime(time.strptime(res['expireTime'], "%Y-%m-%d %H:%M:%S")) + return {"trafficleft": res['traffic'], "validuntil": validuntil} + else: + return {"premium": False, "trafficleft": None, "validuntil": None} + + + def login(self, user, data, req): + html = req.load("http://app.fileserve.com/api/login/", post={"username": user, "password": data['password'], + "submit": "Submit+Query"}) + res = json_loads(html) + + if not res['type']: + self.wrongPassword() + + # login at fileserv html + req.load("http://www.fileserve.com/login.php", + post={"loginUserName": user, "loginUserPassword": data['password'], "autoLogin": "checked", + "loginFormSubmit": "Login"}) diff --git a/pyload/plugin/account/FourSharedCom.py b/pyload/plugin/account/FourSharedCom.py new file mode 100644 index 000000000..a04c9bc46 --- /dev/null +++ b/pyload/plugin/account/FourSharedCom.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.Account import Account +from pyload.utils import json_loads + + +class FourSharedCom(Account): + __name = "FourSharedCom" + __type = "account" + __version = "0.04" + + __description = """FourShared.com account plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz"), + ("stickell", "l.stickell@yahoo.it")] + + + def loadAccountInfo(self, user, req): + # Free mode only for now + return {"premium": False} + + + def login(self, user, data, req): + req.cj.setCookie("4shared.com", "4langcookie", "en") + + res = req.load("http://www.4shared.com/web/login", + post={'login' : user, + 'password' : data['password'], + 'remember' : "on", + '_remember': "on", + 'returnTo' : "http://www.4shared.com/account/home.jsp"}, + decode=True) + + if 'Please log in to access your 4shared account' in res: + self.wrongPassword() diff --git a/pyload/plugin/account/FreakshareCom.py b/pyload/plugin/account/FreakshareCom.py new file mode 100644 index 000000000..5bfcf9bcd --- /dev/null +++ b/pyload/plugin/account/FreakshareCom.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- + +import re +import time + +from pyload.plugin.Account import Account + + +class FreakshareCom(Account): + __name = "FreakshareCom" + __type = "account" + __version = "0.13" + + __description = """Freakshare.com account plugin""" + __license = "GPLv3" + __authors = [("RaNaN", "RaNaN@pyload.org")] + + + def loadAccountInfo(self, user, req): + premium = False + validuntil = None + trafficleft = None + + html = req.load("http://freakshare.com/") + + try: + m = re.search(r'ltig bis:</td>\s*<td><b>([\d.:-]+)</b></td>', html, re.M) + validuntil = time.mktime(time.strptime(m.group(1).strip(), "%d.%m.%Y - %H:%M")) + + except Exception: + pass + + try: + m = re.search(r'Traffic verbleibend:</td>\s*<td>([^<]+)', html, re.M) + trafficleft = self.parseTraffic(m.group(1)) + + except Exception: + pass + + return {"premium": premium, "validuntil": validuntil, "trafficleft": trafficleft} + + + def login(self, user, data, req): + req.load("http://freakshare.com/index.php?language=EN") + + html = req.load("http://freakshare.com/login.html", + post={"submit": "Login", "user": user, "pass": data['password']}, + decode=True) + + if ">Wrong Username or Password" in html: + self.wrongPassword() diff --git a/pyload/plugin/account/FreeWayMe.py b/pyload/plugin/account/FreeWayMe.py new file mode 100644 index 000000000..e139b25a6 --- /dev/null +++ b/pyload/plugin/account/FreeWayMe.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.Account import Account +from pyload.utils import json_loads + + +class FreeWayMe(Account): + __name = "FreeWayMe" + __type = "account" + __version = "0.13" + + __description = """FreeWayMe account plugin""" + __license = "GPLv3" + __authors = [("Nicolas Giese", "james@free-way.me")] + + + def loadAccountInfo(self, user, req): + status = self.getAccountStatus(user, req) + + self.logDebug(status) + + account_info = {"validuntil": -1, "premium": False} + if status['premium'] == "Free": + account_info['trafficleft'] = self.parseTraffic(status['guthaben'] + "MB") + elif status['premium'] == "Spender": + account_info['trafficleft'] = -1 + elif status['premium'] == "Flatrate": + account_info = {"validuntil": float(status['Flatrate']), + "trafficleft": -1, + "premium": True} + + return account_info + + + def login(self, user, data, req): + status = self.getAccountStatus(user, req) + + # Check if user and password are valid + if not status: + self.wrongPassword() + + + def getAccountStatus(self, user, req): + answer = req.load("https://www.free-way.me/ajax/jd.php", + get={"id": 4, "user": user, "pass": self.getAccountData(user)['password']}) + + self.logDebug("Login: %s" % answer) + + if answer == "Invalid login": + self.wrongPassword() + + return json_loads(answer) diff --git a/pyload/plugin/account/FshareVn.py b/pyload/plugin/account/FshareVn.py new file mode 100644 index 000000000..9f0beec84 --- /dev/null +++ b/pyload/plugin/account/FshareVn.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- + +import re +import time + +from pyload.plugin.Account import Account + + +class FshareVn(Account): + __name = "FshareVn" + __type = "account" + __version = "0.09" + + __description = """Fshare.vn account plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz"), + ("stickell", "l.stickell@yahoo.it")] + + + VALID_UNTIL_PATTERN = ur'<dt>Thá»i hạn dùng:</dt>\s*<dd>([^<]+)</dd>' + LIFETIME_PATTERN = ur'<dt>Lần ÄÄng nháºp trưá»c:</dt>\s*<dd>.+?</dd>' + TRAFFIC_LEFT_PATTERN = ur'<dt>Tá»ng Dung Lượng Tà i Khoản</dt>\s*<dd.*?>([\d.]+) ([kKMG])B</dd>' + DIRECT_DOWNLOAD_PATTERN = ur'<input type="checkbox"\s*([^=>]*)[^>]*/>KÃch hoạt download trá»±c tiếp</dt>' + + + def loadAccountInfo(self, user, req): + html = req.load("http://www.fshare.vn/account_info.php", decode=True) + + if re.search(self.LIFETIME_PATTERN, html): + self.logDebug("Lifetime membership detected") + trafficleft = self.getTrafficLeft() + return {"validuntil": -1, "trafficleft": trafficleft, "premium": True} + + m = re.search(self.VALID_UNTIL_PATTERN, html) + if m: + premium = True + validuntil = time.mktime(time.strptime(m.group(1), '%I:%M:%S %p %d-%m-%Y')) + trafficleft = self.getTrafficLeft() + else: + premium = False + validuntil = None + trafficleft = None + + return {"validuntil": validuntil, "trafficleft": trafficleft, "premium": premium} + + + def login(self, user, data, req): + html = req.load("https://www.fshare.vn/login.php", + post={'LoginForm[email]' : user, + 'LoginForm[password]' : data['password'], + 'LoginForm[rememberMe]': 1, + 'yt0' : "Login"}, + referer=True, + decode=True) + + if not re.search(r'<img\s+alt="VIP"', html): + self.wrongPassword() + + + def getTrafficLeft(self): + m = re.search(self.TRAFFIC_LEFT_PATTERN, html) + return self.parseTraffic(m.group(1) + m.group(2)) if m else 0 diff --git a/pyload/plugin/account/Ftp.py b/pyload/plugin/account/Ftp.py new file mode 100644 index 000000000..c7983b0c2 --- /dev/null +++ b/pyload/plugin/account/Ftp.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.Account import Account + + +class Ftp(Account): + __name = "Ftp" + __type = "account" + __version = "0.01" + + __description = """Ftp dummy account plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + login_timeout = -1 #: Unlimited + info_threshold = -1 #: Unlimited diff --git a/pyload/plugin/account/HellshareCz.py b/pyload/plugin/account/HellshareCz.py new file mode 100644 index 000000000..68843ee80 --- /dev/null +++ b/pyload/plugin/account/HellshareCz.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- + +import re +import time + +from pyload.plugin.Account import Account + + +class HellshareCz(Account): + __name = "HellshareCz" + __type = "account" + __version = "0.16" + + __description = """Hellshare.cz account plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + CREDIT_LEFT_PATTERN = r'<div class="credit-link">\s*<table>\s*<tr>\s*<th>(\d+|\d\d\.\d\d\.)</th>' + + + def loadAccountInfo(self, user, req): + self.relogin(user) + html = req.load("http://www.hellshare.com/") + + m = re.search(self.CREDIT_LEFT_PATTERN, html) + if m is None: + trafficleft = None + validuntil = None + premium = False + else: + credit = m.group(1) + premium = True + try: + if "." in credit: + # Time-based account + vt = [int(x) for x in credit.split('.')[:2]] + lt = time.localtime() + year = lt.tm_year + int(vt[1] < lt.tm_mon or (vt[1] == lt.tm_mon and vt[0] < lt.tm_mday)) + validuntil = time.mktime(time.strptime("%s%d 23:59:59" % (credit, year), "%d.%m.%Y %H:%M:%S")) + trafficleft = -1 + else: + # Traffic-based account + trafficleft = self.parseTraffic(credit + "MB") + validuntil = -1 + except Exception, e: + self.logError(_("Unable to parse credit info"), e) + validuntil = -1 + trafficleft = -1 + + return {"validuntil": validuntil, "trafficleft": trafficleft, "premium": premium} + + + def login(self, user, data, req): + html = req.load('http://www.hellshare.com/', decode=True) + if req.lastEffectiveURL != 'http://www.hellshare.com/': + # Switch to English + self.logDebug("Switch lang - URL: %s" % req.lastEffectiveURL) + + json = req.load("%s?do=locRouter-show" % req.lastEffectiveURL) + hash = re.search(r"(\-\-[0-9a-f]+\-)", json).group(1) + + self.logDebug("Switch lang - HASH: %s" % hash) + + html = req.load('http://www.hellshare.com/%s/' % hash, decode=True) + + if re.search(self.CREDIT_LEFT_PATTERN, html): + self.logDebug("Already logged in") + return + + html = req.load('http://www.hellshare.com/login?do=loginForm-submit', + post={"login": "Log in", + "password": data['password'], + "username": user, + "perm_login": "on"}, + decode=True) + + if "<p>You input a wrong user name or wrong password</p>" in html: + self.wrongPassword() diff --git a/pyload/plugin/account/Http.py b/pyload/plugin/account/Http.py new file mode 100644 index 000000000..aacdbf89f --- /dev/null +++ b/pyload/plugin/account/Http.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.Account import Account + + +class Http(Account): + __name = "Http" + __type = "account" + __version = "0.01" + + __description = """Http dummy account plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + login_timeout = -1 #: Unlimited + info_threshold = -1 #: Unlimited diff --git a/pyload/plugin/account/HugefilesNet.py b/pyload/plugin/account/HugefilesNet.py new file mode 100644 index 000000000..b4cd6f8c4 --- /dev/null +++ b/pyload/plugin/account/HugefilesNet.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSAccount import XFSAccount + + +class HugefilesNet(XFSAccount): + __name = "HugefilesNet" + __type = "account" + __version = "0.02" + + __description = """Hugefiles.net account plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + HOSTER_DOMAIN = "hugefiles.net" diff --git a/pyload/plugin/account/HundredEightyUploadCom.py b/pyload/plugin/account/HundredEightyUploadCom.py new file mode 100644 index 000000000..15ee1a12a --- /dev/null +++ b/pyload/plugin/account/HundredEightyUploadCom.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSAccount import XFSAccount + + +class HundredEightyUploadCom(XFSAccount): + __name = "HundredEightyUploadCom" + __type = "account" + __version = "0.03" + + __description = """180upload.com account plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + HOSTER_DOMAIN = "180upload.com" diff --git a/pyload/plugin/account/JunkyvideoCom.py b/pyload/plugin/account/JunkyvideoCom.py new file mode 100644 index 000000000..6c600f971 --- /dev/null +++ b/pyload/plugin/account/JunkyvideoCom.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSAccount import XFSAccount + + +class JunkyvideoCom(XFSAccount): + __name = "JunkyvideoCom" + __type = "account" + __version = "0.01" + + __description = """Junkyvideo.com account plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + HOSTER_DOMAIN = "junkyvideo.com" diff --git a/pyload/plugin/account/JunocloudMe.py b/pyload/plugin/account/JunocloudMe.py new file mode 100644 index 000000000..75307c6dd --- /dev/null +++ b/pyload/plugin/account/JunocloudMe.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSAccount import XFSAccount + + +class JunocloudMe(XFSAccount): + __name = "JunocloudMe" + __type = "account" + __version = "0.02" + + __description = """Junocloud.me account plugin""" + __license = "GPLv3" + __authors = [("guidobelix", "guidobelix@hotmail.it")] + + + HOSTER_DOMAIN = "junocloud.me" diff --git a/pyload/plugin/account/Keep2ShareCc.py b/pyload/plugin/account/Keep2ShareCc.py new file mode 100644 index 000000000..56ac5e9ab --- /dev/null +++ b/pyload/plugin/account/Keep2ShareCc.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- + +import re +import time + +from pyload.plugin.Account import Account + + +class Keep2ShareCc(Account): + __name = "Keep2ShareCc" + __type = "account" + __version = "0.05" + + __description = """Keep2Share.cc account plugin""" + __license = "GPLv3" + __authors = [("aeronaut", "aeronaut@pianoguy.de"), + ("Walter Purcaro", "vuolter@gmail.com")] + + + VALID_UNTIL_PATTERN = r'Premium expires:\s*<b>(.+?)<' + TRAFFIC_LEFT_PATTERN = r'Available traffic \(today\):\s*<b><a href="/user/statistic.html">(.+?)<' + + LOGIN_FAIL_PATTERN = r'Please fix the following input errors' + + + def loadAccountInfo(self, user, req): + validuntil = None + trafficleft = -1 + premium = False + + html = req.load("http://keep2share.cc/site/profile.html", decode=True) + + m = re.search(self.VALID_UNTIL_PATTERN, html) + if m: + expiredate = m.group(1).strip() + self.logDebug("Expire date: " + expiredate) + + if expiredate == "LifeTime": + premium = True + validuntil = -1 + else: + try: + validuntil = time.mktime(time.strptime(expiredate, "%Y.%m.%d")) + + except Exception, e: + self.logError(e) + + else: + premium = validuntil > time.mktime(time.gmtime()) + + m = re.search(self.TRAFFIC_LEFT_PATTERN, html) + if m: + try: + trafficleft = self.parseTraffic(m.group(1)) + + except Exception, e: + self.logError(e) + + return {'validuntil': validuntil, 'trafficleft': trafficleft, 'premium': premium} + + + def login(self, user, data, req): + req.cj.setCookie("keep2share.cc", "lang", "en") + + html = req.load("http://keep2share.cc/login.html", + post={'LoginForm[username]' : user, + 'LoginForm[password]' : data['password'], + 'LoginForm[rememberMe]': 1, + 'yt0' : ""}, + decode=True) + + if re.search(self.LOGIN_FAIL_PATTERN, html): + self.wrongPassword() diff --git a/pyload/plugin/account/LetitbitNet.py b/pyload/plugin/account/LetitbitNet.py new file mode 100644 index 000000000..0ad36f219 --- /dev/null +++ b/pyload/plugin/account/LetitbitNet.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.Account import Account +# from pyload.utils import json_loads, json_dumps + + +class LetitbitNet(Account): + __name = "LetitbitNet" + __type = "account" + __version = "0.02" + + __description = """Letitbit.net account plugin""" + __license = "GPLv3" + __authors = [("stickell", "l.stickell@yahoo.it")] + + + def loadAccountInfo(self, user, req): + ## DISABLED BECAUSE IT GET 'key exausted' EVEN IF VALID ## + # api_key = self.getAccountData(user)['password'] + # json_data = [api_key, ['key/info']] + # api_rep = req.load('http://api.letitbit.net/json', post={'r': json_dumps(json_data)}) + # self.logDebug("API Key Info: " + api_rep) + # api_rep = json_loads(api_rep) + # + # if api_rep['status'] == 'FAIL': + # self.logWarning(api_rep['data']) + # return {'valid': False, 'premium': False} + + return {"premium": True} + + + def login(self, user, data, req): + # API_KEY is the username and the PREMIUM_KEY is the password + self.logInfo(_("You must use your API KEY as username and the PREMIUM KEY as password")) diff --git a/pyload/plugin/account/LinestorageCom.py b/pyload/plugin/account/LinestorageCom.py new file mode 100644 index 000000000..824d3bf77 --- /dev/null +++ b/pyload/plugin/account/LinestorageCom.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSAccount import XFSAccount + + +class LinestorageCom(XFSAccount): + __name = "LinestorageCom" + __type = "account" + __version = "0.03" + + __description = """Linestorage.com account plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + HOSTER_DOMAIN = "linestorage.com" + HOSTER_URL = "http://linestorage.com/" diff --git a/pyload/plugin/account/LinksnappyCom.py b/pyload/plugin/account/LinksnappyCom.py new file mode 100644 index 000000000..942cefbee --- /dev/null +++ b/pyload/plugin/account/LinksnappyCom.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- + +import hashlib + +from pyload.plugin.Account import Account +from pyload.utils import json_loads + + +class LinksnappyCom(Account): + __name = "LinksnappyCom" + __type = "account" + __version = "0.05" + __description = """Linksnappy.com account plugin""" + __license = "GPLv3" + __authors = [("stickell", "l.stickell@yahoo.it")] + + + def loadAccountInfo(self, user, req): + data = self.getAccountData(user) + r = req.load('http://gen.linksnappy.com/lseAPI.php', + get={'act': 'USERDETAILS', 'username': user, 'password': hashlib.md5(data['password']).hexdigest()}) + + self.logDebug("JSON data: " + r) + + j = json_loads(r) + + if j['error']: + return {"premium": False} + + validuntil = j['return']['expire'] + + if validuntil == 'lifetime': + validuntil = -1 + + elif validuntil == 'expired': + return {"premium": False} + + else: + validuntil = float(validuntil) + + if 'trafficleft' not in j['return'] or isinstance(j['return']['trafficleft'], str): + trafficleft = -1 + else: + trafficleft = self.parseTraffic("%d MB" % j['return']['trafficleft']) + + return {"premium": True, "validuntil": validuntil, "trafficleft": trafficleft} + + + def login(self, user, data, req): + r = req.load("http://gen.linksnappy.com/lseAPI.php", + get={'act' : 'USERDETAILS', + 'username': user, + 'password': hashlib.md5(data['password']).hexdigest()}, + decode=True) + + if 'Invalid Account Details' in r: + self.wrongPassword() diff --git a/pyload/plugin/account/MegaDebridEu.py b/pyload/plugin/account/MegaDebridEu.py new file mode 100644 index 000000000..67af94541 --- /dev/null +++ b/pyload/plugin/account/MegaDebridEu.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.Account import Account +from pyload.utils import json_loads + + +class MegaDebridEu(Account): + __name = "MegaDebridEu" + __type = "account" + __version = "0.20" + + __description = """mega-debrid.eu account plugin""" + __license = "GPLv3" + __authors = [("D.Ducatel", "dducatel@je-geek.fr")] + + + # Define the base URL of MegaDebrid api + API_URL = "https://www.mega-debrid.eu/api.php" + + + def loadAccountInfo(self, user, req): + data = self.getAccountData(user) + jsonResponse = req.load(self.API_URL, + get={'action': 'connectUser', 'login': user, 'password': data['password']}) + res = json_loads(jsonResponse) + + if res['response_code'] == "ok": + return {"premium": True, "validuntil": float(res['vip_end']), "status": True} + else: + self.logError(res) + return {"status": False, "premium": False} + + + def login(self, user, data, req): + jsonResponse = req.load(self.API_URL, + get={'action': 'connectUser', 'login': user, 'password': data['password']}) + res = json_loads(jsonResponse) + if res['response_code'] != "ok": + self.wrongPassword() diff --git a/pyload/plugin/account/MegaRapidCz.py b/pyload/plugin/account/MegaRapidCz.py new file mode 100644 index 000000000..ea788d446 --- /dev/null +++ b/pyload/plugin/account/MegaRapidCz.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- + +import re +import time + +from pyload.plugin.Account import Account + + +class MegaRapidCz(Account): + __name = "MegaRapidCz" + __type = "account" + __version = "0.35" + + __description = """MegaRapid.cz account plugin""" + __license = "GPLv3" + __authors = [("MikyWoW", "mikywow@seznam.cz"), + ("zoidberg", "zoidberg@mujmail.cz")] + + + login_timeout = 60 + + LIMITDL_PATTERN = ur'<td>Max. poÄet paralelnÃch stahovánÃ: </td><td>(\d+)' + VALID_UNTIL_PATTERN = ur'<td>Paušálnà stahovánà aktivnÃ. Vypršà </td><td><strong>(.*?)</strong>' + TRAFFIC_LEFT_PATTERN = r'<tr><td>Kredit</td><td>(.*?) GiB' + + + def loadAccountInfo(self, user, req): + htmll = req.load("http://megarapid.cz/mujucet/", decode=True) + + m = re.search(self.LIMITDL_PATTERN, htmll) + if m: + data = self.getAccountData(user) + data['options']['limitDL'] = [int(m.group(1))] + + m = re.search(self.VALID_UNTIL_PATTERN, htmll) + if m: + validuntil = time.mktime(time.strptime(m.group(1), "%d.%m.%Y - %H:%M")) + return {"premium": True, "trafficleft": -1, "validuntil": validuntil} + + m = re.search(self.TRAFFIC_LEFT_PATTERN, htmll) + if m: + trafficleft = float(m.group(1)) * (1 << 20) + return {"premium": True, "trafficleft": trafficleft, "validuntil": -1} + + return {"premium": False, "trafficleft": None, "validuntil": None} + + + def login(self, user, data, req): + html = req.load("http://megarapid.cz/prihlaseni/", decode=True) + + if "Heslo:" in html: + start = html.index('id="inp_hash" name="hash" value="') + html = html[start + 33:] + hashes = html[0:32] + html = req.load("http://megarapid.cz/prihlaseni/", + post={"hash": hashes, + "login": user, + "pass1": data['password'], + "remember": 0, + "sbmt": u"PÅihlásit"}) diff --git a/pyload/plugin/account/MegaRapidoNet.py b/pyload/plugin/account/MegaRapidoNet.py new file mode 100644 index 000000000..c4ee559da --- /dev/null +++ b/pyload/plugin/account/MegaRapidoNet.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- + +import re +import time + +from pyload.plugin.Account import Account + + +class MegaRapidoNet(Account): + __name = "MegaRapidoNet" + __type = "account" + __version = "0.02" + + __description = """MegaRapido.net account plugin""" + __license = "GPLv3" + __authors = [("Kagenoshin", "kagenoshin@gmx.ch")] + + + VALID_UNTIL_PATTERN = r'<\s*?div[^>]*?class\s*?=\s*?[\'"]premium_index[\'"].*?>[^<]*?<[^>]*?b.*?>\s*?TEMPO\s*?PREMIUM.*?<[^>]*?/b.*?>\s*?(\d*)[^\d]*?DIAS[^\d]*?(\d*)[^\d]*?HORAS[^\d]*?(\d*)[^\d]*?MINUTOS[^\d]*?(\d*)[^\d]*?SEGUNDOS' + USER_ID_PATTERN = r'<\s*?div[^>]*?class\s*?=\s*?["\']checkbox_compartilhar["\'].*?>.*?<\s*?input[^>]*?name\s*?=\s*?["\']usar["\'].*?>.*?<\s*?input[^>]*?name\s*?=\s*?["\']user["\'][^>]*?value\s*?=\s*?["\'](.*?)\s*?["\']' + + + def loadAccountInfo(self, user, req): + validuntil = None + trafficleft = None + premium = False + + html = req.load("http://megarapido.net/gerador", decode=True) + + validuntil = re.search(self.VALID_UNTIL_PATTERN, html) + if validuntil: + # hier weitermachen!!! (mÃŒssen umbedingt die zeit richtig machen damit! (sollte aber möglich)) + validuntil = time.time() + int(validuntil.group(1)) * 24 * 3600 + int(validuntil.group(2)) * 3600 + int(validuntil.group(3)) * 60 + int(validuntil.group(4)) + trafficleft = -1 + premium = True + + return {'validuntil' : validuntil, + 'trafficleft': trafficleft, + 'premium' : premium} + + + def login(self, user, data, req): + req.load("http://megarapido.net/login") + req.load("http://megarapido.net/painel_user/ajax/logar.php", + post={'login': user, 'senha': data['password']}, + decode=True) + + html = req.load("http://megarapido.net/gerador") + + if "sair" not in html.lower(): + self.wrongPassword() + else: + m = re.search(self.USER_ID_PATTERN, html) + if m: + data['uid'] = m.group(1) + else: + self.fail("Couldn't find the user ID") diff --git a/pyload/plugin/account/MegasharesCom.py b/pyload/plugin/account/MegasharesCom.py new file mode 100644 index 000000000..8920bb2db --- /dev/null +++ b/pyload/plugin/account/MegasharesCom.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- + +import re +import time + +from pyload.plugin.Account import Account + + +class MegasharesCom(Account): + __name = "MegasharesCom" + __type = "account" + __version = "0.03" + + __description = """Megashares.com account plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + VALID_UNTIL_PATTERN = r'<p class="premium_info_box">Period Ends: (\w{3} \d{1,2}, \d{4})</p>' + + + def loadAccountInfo(self, user, req): + # self.relogin(user) + html = req.load("http://d01.megashares.com/myms.php", decode=True) + + premium = '>Premium Upgrade<' not in html + + validuntil = trafficleft = -1 + try: + timestr = re.search(self.VALID_UNTIL_PATTERN, html).group(1) + self.logDebug(timestr) + validuntil = time.mktime(time.strptime(timestr, "%b %d, %Y")) + except Exception, e: + self.logError(e) + + return {"validuntil": validuntil, "trafficleft": -1, "premium": premium} + + + def login(self, user, data, req): + html = req.load('http://d01.megashares.com/myms_login.php', + post={"httpref" : "", + "myms_login" : "Login", + "mymslogin_name": user, + "mymspassword" : data['password']}, + decode=True) + + if not '<span class="b ml">%s</span>' % user in html: + self.wrongPassword() diff --git a/pyload/plugin/account/MovReelCom.py b/pyload/plugin/account/MovReelCom.py new file mode 100644 index 000000000..9eabd0a6d --- /dev/null +++ b/pyload/plugin/account/MovReelCom.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSAccount import XFSAccount + + +class MovReelCom(XFSAccount): + __name = "MovReelCom" + __type = "account" + __version = "0.03" + + __description = """Movreel.com account plugin""" + __license = "GPLv3" + __authors = [("t4skforce", "t4skforce1337[AT]gmail[DOT]com")] + + + login_timeout = 60 + info_threshold = 30 + + HOSTER_DOMAIN = "movreel.com" diff --git a/pyload/plugin/account/MultihostersCom.py b/pyload/plugin/account/MultihostersCom.py new file mode 100644 index 000000000..bc48979bb --- /dev/null +++ b/pyload/plugin/account/MultihostersCom.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.account.ZeveraCom import ZeveraCom + + +class MultihostersCom(ZeveraCom): + __name = "MultihostersCom" + __type = "account" + __version = "0.03" + + __description = """Multihosters.com account plugin""" + __license = "GPLv3" + __authors = [("tjeh", "tjeh@gmx.net")] + + + HOSTER_DOMAIN = "multihosters.com" diff --git a/pyload/plugin/account/MultishareCz.py b/pyload/plugin/account/MultishareCz.py new file mode 100644 index 000000000..66ab3dd47 --- /dev/null +++ b/pyload/plugin/account/MultishareCz.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.Account import Account + + +class MultishareCz(Account): + __name = "MultishareCz" + __type = "account" + __version = "0.05" + + __description = """Multishare.cz account plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + TRAFFIC_LEFT_PATTERN = r'<span class="profil-zvyrazneni">Kredit:</span>\s*<strong>(?P<S>[\d.,]+) (?P<U>[\w^_]+)</strong>' + ACCOUNT_INFO_PATTERN = r'<input type="hidden" id="(u_ID|u_hash)" name=".+?" value="(.+?)">' + + + def loadAccountInfo(self, user, req): + # self.relogin(user) + html = req.load("http://www.multishare.cz/profil/", decode=True) + + m = re.search(self.TRAFFIC_LEFT_PATTERN, html) + trafficleft = self.parseTraffic(m.group('S') + m.group('U')) if m else 0 + self.premium = bool(trafficleft) + + html = req.load("http://www.multishare.cz/", decode=True) + mms_info = dict(re.findall(self.ACCOUNT_INFO_PATTERN, html)) + + return dict(mms_info, **{"validuntil": -1, "trafficleft": trafficleft}) + + + def login(self, user, data, req): + html = req.load('http://www.multishare.cz/html/prihlaseni_process.php', + post={"akce" : "PÅihlásit", + "heslo": data['password'], + "jmeno": user}, + decode=True) + + if '<div class="akce-chyba akce">' in html: + self.wrongPassword() diff --git a/pyload/plugin/account/MyfastfileCom.py b/pyload/plugin/account/MyfastfileCom.py new file mode 100644 index 000000000..a58767b72 --- /dev/null +++ b/pyload/plugin/account/MyfastfileCom.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- + +import time + +from pyload.plugin.Account import Account +from pyload.utils import json_loads + + +class MyfastfileCom(Account): + __name = "MyfastfileCom" + __type = "account" + __version = "0.04" + + __description = """Myfastfile.com account plugin""" + __license = "GPLv3" + __authors = [("stickell", "l.stickell@yahoo.it")] + + + def loadAccountInfo(self, user, req): + if 'days_left' in self.json_data: + validuntil = time.time() + self.json_data['days_left'] * 24 * 60 * 60 + return {"premium": True, "validuntil": validuntil, "trafficleft": -1} + else: + self.logError(_("Unable to get account information")) + + + def login(self, user, data, req): + # Password to use is the API-Password written in http://myfastfile.com/myaccount + html = req.load("http://myfastfile.com/api.php", + get={"user": user, "pass": data['password']}) + + self.logDebug("JSON data: " + html) + + self.json_data = json_loads(html) + if self.json_data['status'] != 'ok': + self.logError(_('Invalid login. The password to use is the API-Password you find in your "My Account" page')) + self.wrongPassword() diff --git a/pyload/plugin/account/NetloadIn.py b/pyload/plugin/account/NetloadIn.py new file mode 100644 index 000000000..db0f0ea5a --- /dev/null +++ b/pyload/plugin/account/NetloadIn.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- + +import re +import time + +from pyload.plugin.Account import Account + + +class NetloadIn(Account): + __name = "NetloadIn" + __type = "account" + __version = "0.24" + + __description = """Netload.in account plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + def api_response(self, id, password, req): + return req.load("http://api.netload.in/user_info.php", + get={'auth' : "BVm96BWDSoB4WkfbEhn42HgnjIe1ilMt", + 'user_id' : id, + 'user_password': password}).strip() + + + def loadAccountInfo(self, user, req): + validuntil = None + trafficleft = -1 + premium = False + + html = self.api_response(user, self.getAccountData(user)['password'], req) + + if html == "-1": + premium = True + + elif html == "0": + validuntil = -1 + + else: + try: + validuntil = time.mktime(time.strptime(html, "%Y-%m-%d %H:%M")) + + except Exception, e: + self.logError(e) + + else: + self.logDebug("Valid until: %s" % validuntil) + + if validuntil > time.mktime(time.gmtime()): + premium = True + else: + validuntil = -1 + + return {'validuntil' : validuntil, + 'trafficleft': trafficleft, + 'premium' : premium} + + + def login(self, user, data, req): + html = self.api_response(user, data['password'], req) + + if not html or re.search(r'disallowed_agent|unknown_auth|login_failed', html): + self.wrongPassword() diff --git a/pyload/plugin/account/NoPremiumPl.py b/pyload/plugin/account/NoPremiumPl.py new file mode 100644 index 000000000..6cefed550 --- /dev/null +++ b/pyload/plugin/account/NoPremiumPl.py @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- + +import datetime +import hashlib +import time + +from pyload.plugin.Account import Account +from pyload.utils import json_loads + + +class NoPremiumPl(Account): + __name = "NoPremiumPl" + __version = "0.01" + __type = "account" + __description = "NoPremium.pl account plugin" + __license = "GPLv3" + __authors = [("goddie", "dev@nopremium.pl")] + + _api_url = "http://crypt.nopremium.pl" + + _api_query = { + "site": "nopremium", + "username": "", + "password": "", + "output": "json", + "loc": "1", + "info": "1" + } + + _req = None + _usr = None + _pwd = None + + + def loadAccountInfo(self, name, req): + self._req = req + try: + result = json_loads(self.runAuthQuery()) + except Exception: + #@TODO: return or let it be thrown? + return + + premium = False + valid_untill = -1 + + if "expire" in result.keys() and result['expire']: + premium = True + valid_untill = time.mktime(datetime.datetime.fromtimestamp(int(result['expire'])).timetuple()) + traffic_left = result['balance'] * 2 ** 20 + + return ({ + "validuntil": valid_untill, + "trafficleft": traffic_left, + "premium": premium + }) + + + def login(self, user, data, req): + self._usr = user + self._pwd = hashlib.sha1(hashlib.md5(data['password']).hexdigest()).hexdigest() + self._req = req + + try: + response = json_loads(self.runAuthQuery()) + except Exception: + self.wrongPassword() + + if "errno" in response.keys(): + self.wrongPassword() + data['usr'] = self._usr + data['pwd'] = self._pwd + + + def createAuthQuery(self): + query = self._api_query + query['username'] = self._usr + query['password'] = self._pwd + + return query + + + def runAuthQuery(self): + data = self._req.load(self._api_url, post=self.createAuthQuery()) + + return data diff --git a/pyload/plugin/account/NosuploadCom.py b/pyload/plugin/account/NosuploadCom.py new file mode 100644 index 000000000..10f9007a6 --- /dev/null +++ b/pyload/plugin/account/NosuploadCom.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSAccount import XFSAccount + + +class NosuploadCom(XFSAccount): + __name = "NosuploadCom" + __type = "account" + __version = "0.02" + + __description = """Nosupload.com account plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + HOSTER_DOMAIN = "nosupload.com" diff --git a/pyload/plugin/account/NovafileCom.py b/pyload/plugin/account/NovafileCom.py new file mode 100644 index 000000000..8400cc267 --- /dev/null +++ b/pyload/plugin/account/NovafileCom.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSAccount import XFSAccount + + +class NovafileCom(XFSAccount): + __name = "NovafileCom" + __type = "account" + __version = "0.02" + + __description = """Novafile.com account plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + HOSTER_DOMAIN = "novafile.com" diff --git a/pyload/plugin/account/NowVideoSx.py b/pyload/plugin/account/NowVideoSx.py new file mode 100644 index 000000000..f072e3687 --- /dev/null +++ b/pyload/plugin/account/NowVideoSx.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- + +import re +import time + +from pyload.plugin.Account import Account + + +class NowVideoSx(Account): + __name = "NowVideoSx" + __type = "account" + __version = "0.03" + + __description = """NowVideo.at account plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + VALID_UNTIL_PATTERN = r'>Your premium membership expires on: (.+?)<' + + + def loadAccountInfo(self, user, req): + validuntil = None + trafficleft = -1 + premium = None + + html = req.load("http://www.nowvideo.sx/premium.php") + + m = re.search(self.VALID_UNTIL_PATTERN, html) + if m: + expiredate = m.group(1).strip() + self.logDebug("Expire date: " + expiredate) + + try: + validuntil = time.mktime(time.strptime(expiredate, "%Y-%b-%d")) + + except Exception, e: + self.logError(e) + + else: + if validuntil > time.mktime(time.gmtime()): + premium = True + else: + premium = False + validuntil = -1 + + return {"validuntil": validuntil, "trafficleft": trafficleft, "premium": premium} + + + def login(self, user, data, req): + html = req.load("http://www.nowvideo.sx/login.php", + post={'user': user, 'pass': data['password']}, + decode=True) + + if re.search(r'>Log In<', html): + self.wrongPassword() diff --git a/pyload/plugin/account/OboomCom.py b/pyload/plugin/account/OboomCom.py new file mode 100644 index 000000000..4669ca61e --- /dev/null +++ b/pyload/plugin/account/OboomCom.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- + +try: + from beaker.crypto.pbkdf2 import PBKDF2 + +except ImportError: + from beaker.crypto.pbkdf2 import pbkdf2 + from binascii import b2a_hex + + class PBKDF2(object): + + def __init__(self, passphrase, salt, iterations=1000): + self.passphrase = passphrase + self.salt = salt + self.iterations = iterations + + + def hexread(self, octets): + return b2a_hex(pbkdf2(self.passphrase, self.salt, self.iterations, octets)) + +from pyload.utils import json_loads +from pyload.plugin.Account import Account + + +class OboomCom(Account): + __name = "OboomCom" + __type = "account" + __version = "0.24" + + __description = """Oboom.com account plugin""" + __license = "GPLv3" + __authors = [("stanley", "stanley.foerster@gmail.com")] + + + def loadAccountData(self, user, req): + passwd = self.getAccountData(user)['password'] + salt = passwd[::-1] + pbkdf2 = PBKDF2(passwd, salt, 1000).hexread(16) + + result = json_loads(req.load("https://www.oboom.com/1/login", get={"auth": user, "pass": pbkdf2})) + + if not result[0] == 200: + self.logWarning(_("Failed to log in: %s") % result[1]) + self.wrongPassword() + + return result[1] + + + def loadAccountInfo(self, name, req): + accountData = self.loadAccountData(name, req) + + userData = accountData['user'] + + if userData['premium'] == "null": + premium = False + else: + premium = True + + if userData['premium_unix'] == "null": + validUntil = -1 + else: + validUntil = float(userData['premium_unix']) + + traffic = userData['traffic'] + + trafficLeft = traffic['current'] + maxTraffic = traffic['max'] + + session = accountData['session'] + + return {'premium' : premium, + 'validuntil' : validUntil, + 'trafficleft': trafficLeft, + 'maxtraffic' : maxTraffic, + 'session' : session} + + + def login(self, user, data, req): + self.loadAccountData(user, req) diff --git a/pyload/plugin/account/OneFichierCom.py b/pyload/plugin/account/OneFichierCom.py new file mode 100644 index 000000000..531f5f735 --- /dev/null +++ b/pyload/plugin/account/OneFichierCom.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- + +import pycurl +import re +import time + +from pyload.plugin.Account import Account + + +class OneFichierCom(Account): + __name = "OneFichierCom" + __type = "account" + __version = "0.12" + + __description = """1fichier.com account plugin""" + __license = "GPLv3" + __authors = [("Elrick69", "elrick69[AT]rocketmail[DOT]com"), + ("Walter Purcaro", "vuolter@gmail.com")] + + + VALID_UNTIL_PATTERN = r'Your Premium Status will end the (\d+/\d+/\d+)' + + + def loadAccountInfo(self, user, req): + validuntil = None + trafficleft = -1 + premium = None + + html = req.load("https://1fichier.com/console/abo.pl") + + m = re.search(self.VALID_UNTIL_PATTERN, html) + if m: + expiredate = m.group(1) + self.logDebug("Expire date: " + expiredate) + + try: + validuntil = time.mktime(time.strptime(expiredate, "%d/%m/%Y")) + except Exception, e: + self.logError(e) + else: + premium = True + + return {'validuntil': validuntil, 'trafficleft': trafficleft, 'premium': premium or False} + + + def login(self, user, data, req): + req.http.c.setopt(pycurl.REFERER, "https://1fichier.com/login.pl?lg=en") + + html = req.load("https://1fichier.com/login.pl?lg=en", + post={'mail' : user, + 'pass' : data['password'], + 'It' : "on", + 'purge' : "off", + 'valider': "Send"}, + decode=True) + + if '>Invalid email address' in html or '>Invalid password' in html: + self.wrongPassword() diff --git a/pyload/plugin/account/OverLoadMe.py b/pyload/plugin/account/OverLoadMe.py new file mode 100644 index 000000000..d65e4ad31 --- /dev/null +++ b/pyload/plugin/account/OverLoadMe.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.Account import Account +from pyload.utils import json_loads + + +class OverLoadMe(Account): + __name = "OverLoadMe" + __type = "account" + __version = "0.04" + + __description = """Over-Load.me account plugin""" + __license = "GPLv3" + __authors = [("marley", "marley@over-load.me")] + + + def loadAccountInfo(self, user, req): + https = "https" if self.getConfig('ssl') else "http" + data = self.getAccountData(user) + html = req.load(https + "://api.over-load.me/account.php", + get={'user': user, + 'auth': data['password']}).strip() + + data = json_loads(html) + self.logDebug(data) + + # Check for premium + if data['membership'] == "Free": + return {'premium': False, 'validuntil': None, 'trafficleft': None} + else: + return {'premium': True, 'validuntil': data['expirationunix'], 'trafficleft': -1} + + + def login(self, user, data, req): + https = "https" if self.getConfig('ssl') else "http" + jsondata = req.load(https + "://api.over-load.me/account.php", + get={'user': user, + 'auth': data['password']}).strip() + + data = json_loads(jsondata) + + if data['err'] == 1: + self.wrongPassword() diff --git a/pyload/plugin/account/PremiumTo.py b/pyload/plugin/account/PremiumTo.py new file mode 100644 index 000000000..586ddf635 --- /dev/null +++ b/pyload/plugin/account/PremiumTo.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.Account import Account + + +class PremiumTo(Account): + __name = "PremiumTo" + __type = "account" + __version = "0.08" + + __description = """Premium.to account plugin""" + __license = "GPLv3" + __authors = [("RaNaN", "RaNaN@pyload.org"), + ("zoidberg", "zoidberg@mujmail.cz"), + ("stickell", "l.stickell@yahoo.it")] + + + def loadAccountInfo(self, user, req): + traffic = req.load("http://premium.to/api/straffic.php", + get={'username': self.username, 'password': self.password}) + + if "wrong username" not in traffic: + trafficleft = sum(map(float, traffic.split(';'))) + return {'premium': True, 'trafficleft': trafficleft, 'validuntil': -1} + else: + return {'premium': False, 'trafficleft': None, 'validuntil': None} + + + def login(self, user, data, req): + self.username = user + self.password = data['password'] + authcode = req.load("http://premium.to/api/getauthcode.php", + get={'username': user, 'password': self.password}, + decode=True) + + if "wrong username" in authcode: + self.wrongPassword() diff --git a/pyload/plugin/account/PremiumizeMe.py b/pyload/plugin/account/PremiumizeMe.py new file mode 100644 index 000000000..06e6ffb98 --- /dev/null +++ b/pyload/plugin/account/PremiumizeMe.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.Account import Account + +from pyload.utils import json_loads + + +class PremiumizeMe(Account): + __name = "PremiumizeMe" + __type = "account" + __version = "0.13" + + __description = """Premiumize.me account plugin""" + __license = "GPLv3" + __authors = [("Florian Franzen", "FlorianFranzen@gmail.com")] + + + def loadAccountInfo(self, user, req): + # Get user data from premiumize.me + status = self.getAccountStatus(user, req) + self.logDebug(status) + + # Parse account info + account_info = {"validuntil": float(status['result']['expires']), + "trafficleft": max(0, status['result']['trafficleft_bytes'])} + + if status['result']['type'] == 'free': + account_info['premium'] = False + + return account_info + + + def login(self, user, data, req): + # Get user data from premiumize.me + status = self.getAccountStatus(user, req) + + # Check if user and password are valid + if status['status'] != 200: + self.wrongPassword() + + + def getAccountStatus(self, user, req): + # Use premiumize.me API v1 (see https://secure.premiumize.me/?show=api) + # to retrieve account info and return the parsed json answer + answer = req.load("https://api.premiumize.me/pm-api/v1.php", + get={'method' : "accountstatus", + 'params[login]': user, + 'params[pass]' : self.getAccountData(user)['password']}) + return json_loads(answer) diff --git a/pyload/plugin/account/PutdriveCom.py b/pyload/plugin/account/PutdriveCom.py new file mode 100644 index 000000000..3d7279034 --- /dev/null +++ b/pyload/plugin/account/PutdriveCom.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.account.ZeveraCom import ZeveraCom + + +class PutdriveCom(ZeveraCom): + __name = "PutdriveCom" + __type = "account" + __version = "0.02" + + __description = """Putdrive.com account plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + HOSTER_DOMAIN = "putdrive.com" diff --git a/pyload/plugin/account/QuickshareCz.py b/pyload/plugin/account/QuickshareCz.py new file mode 100644 index 000000000..2f71d9ae8 --- /dev/null +++ b/pyload/plugin/account/QuickshareCz.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.Account import Account + + +class QuickshareCz(Account): + __name = "QuickshareCz" + __type = "account" + __version = "0.03" + + __description = """Quickshare.cz account plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + TRAFFIC_LEFT_PATTERN = r'Stav kreditu: <strong>(.+?)</strong>' + + + def loadAccountInfo(self, user, req): + html = req.load("http://www.quickshare.cz/premium", decode=True) + + m = re.search(self.TRAFFIC_LEFT_PATTERN, html) + if m: + trafficleft = self.parseTraffic(m.group(1)) + premium = bool(trafficleft) + else: + trafficleft = None + premium = False + + return {"validuntil": -1, "trafficleft": trafficleft, "premium": premium} + + + def login(self, user, data, req): + html = req.load('http://www.quickshare.cz/html/prihlaseni_process.php', + post={"akce": u'PÅihlásit', + "heslo": data['password'], + "jmeno": user}, + decode=True) + + if u'>TakovÜ uÅŸivatel neexistuje.<' in html or u'>Å patné heslo.<' in html: + self.wrongPassword() diff --git a/pyload/plugin/account/RPNetBiz.py b/pyload/plugin/account/RPNetBiz.py new file mode 100644 index 000000000..562436e85 --- /dev/null +++ b/pyload/plugin/account/RPNetBiz.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.Account import Account +from pyload.utils import json_loads + + +class RPNetBiz(Account): + __name = "RPNetBiz" + __type = "account" + __version = "0.12" + + __description = """RPNet.biz account plugin""" + __license = "GPLv3" + __authors = [("Dman", "dmanugm@gmail.com")] + + + def loadAccountInfo(self, user, req): + # Get account information from rpnet.biz + res = self.getAccountStatus(user, req) + try: + if res['accountInfo']['isPremium']: + # Parse account info. Change the trafficleft later to support per host info. + account_info = {"validuntil": float(res['accountInfo']['premiumExpiry']), + "trafficleft": -1, "premium": True} + else: + account_info = {"validuntil": None, "trafficleft": None, "premium": False} + + except KeyError: + # handle wrong password exception + account_info = {"validuntil": None, "trafficleft": None, "premium": False} + + return account_info + + + def login(self, user, data, req): + # Get account information from rpnet.biz + res = self.getAccountStatus(user, req) + + # If we have an error in the res, we have wrong login information + if 'error' in res: + self.wrongPassword() + + + def getAccountStatus(self, user, req): + # Using the rpnet API, check if valid premium account + res = req.load("https://premium.rpnet.biz/client_api.php", + get={"username": user, "password": self.getAccountData(user)['password'], + "action": "showAccountInformation"}) + self.logDebug("JSON data: %s" % res) + + return json_loads(res) diff --git a/pyload/plugin/account/RapideoPl.py b/pyload/plugin/account/RapideoPl.py new file mode 100644 index 000000000..c58414b53 --- /dev/null +++ b/pyload/plugin/account/RapideoPl.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- + +import datetime +import hashlib +import time + +from pyload.plugin.Account import Account +from pyload.utils import json_loads + + +class RapideoPl(Account): + __name = "RapideoPl" + __version = "0.01" + __type = "account" + __description = "Rapideo.pl account plugin" + __license = "GPLv3" + __authors = [("goddie", "dev@rapideo.pl")] + + _api_url = "http://enc.rapideo.pl" + + _api_query = { + "site": "newrd", + "username": "", + "password": "", + "output": "json", + "loc": "1", + "info": "1" + } + + _req = None + _usr = None + _pwd = None + + + def loadAccountInfo(self, name, req): + self._req = req + try: + result = json_loads(self.runAuthQuery()) + except Exception: + #@TODO: return or let it be thrown? + return + + premium = False + valid_untill = -1 + if "expire" in result.keys() and result['expire']: + premium = True + valid_untill = time.mktime(datetime.datetime.fromtimestamp(int(result['expire'])).timetuple()) + + traffic_left = result['balance'] + + return ({ + "validuntil": valid_untill, + "trafficleft": traffic_left, + "premium": premium + }) + + + def login(self, user, data, req): + self._usr = user + self._pwd = hashlib.md5(data['password']).hexdigest() + self._req = req + try: + response = json_loads(self.runAuthQuery()) + except Exception: + self.wrongPassword() + + if "errno" in response.keys(): + self.wrongPassword() + data['usr'] = self._usr + data['pwd'] = self._pwd + + + def createAuthQuery(self): + query = self._api_query + query['username'] = self._usr + query['password'] = self._pwd + + return query + + + def runAuthQuery(self): + data = self._req.load(self._api_url, post=self.createAuthQuery()) + + return data diff --git a/pyload/plugin/account/RapidfileshareNet.py b/pyload/plugin/account/RapidfileshareNet.py new file mode 100644 index 000000000..0b0ed210c --- /dev/null +++ b/pyload/plugin/account/RapidfileshareNet.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSAccount import XFSAccount + + +class RapidfileshareNet(XFSAccount): + __name = "RapidfileshareNet" + __type = "account" + __version = "0.05" + + __description = """Rapidfileshare.net account plugin""" + __license = "GPLv3" + __authors = [("guidobelix", "guidobelix@hotmail.it")] + + + HOSTER_DOMAIN = "rapidfileshare.net" + + TRAFFIC_LEFT_PATTERN = r'>Traffic available today:</TD><TD><label for="name">\s*(?P<S>[\d.,]+)\s*(?:(?P<U>[\w^_]+))?' diff --git a/pyload/plugin/account/RapidgatorNet.py b/pyload/plugin/account/RapidgatorNet.py new file mode 100644 index 000000000..729635037 --- /dev/null +++ b/pyload/plugin/account/RapidgatorNet.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.Account import Account +from pyload.utils import json_loads + + +class RapidgatorNet(Account): + __name = "RapidgatorNet" + __type = "account" + __version = "0.09" + + __description = """Rapidgator.net account plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + API_URL = "http://rapidgator.net/api/user" + + + def loadAccountInfo(self, user, req): + validuntil = None + trafficleft = None + premium = False + sid = None + + try: + sid = self.getAccountData(user).get('sid') + assert sid + + html = req.load("%s/info" % self.API_URL, get={'sid': sid}) + + self.logDebug("API:USERINFO", html) + + json = json_loads(html) + + if json['response_status'] == 200: + if "reset_in" in json['response']: + self.scheduleRefresh(user, json['response']['reset_in']) + + validuntil = json['response']['expire_date'] + trafficleft = float(json['response']['traffic_left']) + premium = True + else: + self.logError(json['response_details']) + + except Exception, e: + self.logError(e) + + return {'validuntil' : validuntil, + 'trafficleft': trafficleft, + 'premium' : premium, + 'sid' : sid} + + + def login(self, user, data, req): + try: + html = req.load('%s/login' % self.API_URL, post={"username": user, "password": data['password']}) + + self.logDebug("API:LOGIN", html) + + json = json_loads(html) + + if json['response_status'] == 200: + data['sid'] = str(json['response']['session_id']) + return + else: + self.logError(json['response_details']) + + except Exception, e: + self.logError(e) + + self.wrongPassword() diff --git a/pyload/plugin/account/RapiduNet.py b/pyload/plugin/account/RapiduNet.py new file mode 100644 index 000000000..c19e54a9b --- /dev/null +++ b/pyload/plugin/account/RapiduNet.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- + +import re +import time + +from pyload.plugin.Account import Account +from pyload.utils import json_loads + + +class RapiduNet(Account): + __name = "RapiduNet" + __type = "account" + __version = "0.05" + + __description = """Rapidu.net account plugin""" + __license = "GPLv3" + __authors = [("prOq", None), + ("Walter Purcaro", "vuolter@gmail.com")] + + + PREMIUM_PATTERN = r'>Account: <b>Premium' + + VALID_UNTIL_PATTERN = r'>Account: <b>\w+ \((\d+)' + + TRAFFIC_LEFT_PATTERN = r'class="tipsyS"><b>(.+?)<' + + + def loadAccountInfo(self, user, req): + validuntil = None + trafficleft = -1 + premium = False + + html = req.load("https://rapidu.net/", decode=True) + + if re.search(self.PREMIUM_PATTERN, html): + premium = True + + m = re.search(self.VALID_UNTIL_PATTERN, html) + if m: + validuntil = time.time() + (86400 * int(m.group(1))) + + m = re.search(self.TRAFFIC_LEFT_PATTERN, html) + if m: + trafficleft = self.parseTraffic(m.group(1)) + + return {'validuntil': validuntil, 'trafficleft': trafficleft, 'premium': premium} + + + def login(self, user, data, req): + req.load("https://rapidu.net/ajax.php", + get={'a': "getChangeLang"}, + post={'_go' : "", + 'lang': "en"}) + + json = json_loads(req.load("https://rapidu.net/ajax.php", + get={'a': "getUserLogin"}, + post={'_go' : "", + 'login' : user, + 'pass' : data['password'], + 'remember': "1"})) + + self.logDebug(json) + + if not json['message'] == "success": + self.wrongPassword() diff --git a/pyload/plugin/account/RarefileNet.py b/pyload/plugin/account/RarefileNet.py new file mode 100644 index 000000000..fc736bafc --- /dev/null +++ b/pyload/plugin/account/RarefileNet.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSAccount import XFSAccount + + +class RarefileNet(XFSAccount): + __name = "RarefileNet" + __type = "account" + __version = "0.04" + + __description = """RareFile.net account plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + HOSTER_DOMAIN = "rarefile.net" diff --git a/pyload/plugin/account/RealdebridCom.py b/pyload/plugin/account/RealdebridCom.py new file mode 100644 index 000000000..0d28f2ad6 --- /dev/null +++ b/pyload/plugin/account/RealdebridCom.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- + +import xml.dom.minidom as dom + +from pyload.plugin.Account import Account + + +class RealdebridCom(Account): + __name = "RealdebridCom" + __type = "account" + __version = "0.45" + + __description = """Real-Debrid.com account plugin""" + __license = "GPLv3" + __authors = [("Devirex Hazzard", "naibaf_11@yahoo.de")] + + + def loadAccountInfo(self, user, req): + if self.pin_code: + return {"premium": False} + html = req.load("https://real-debrid.com/api/account.php") + xml = dom.parseString(html) + account_info = {"validuntil": float(xml.getElementsByTagName("expiration")[0].childNodes[0].nodeValue), + "trafficleft": -1} + + return account_info + + + def login(self, user, data, req): + self.pin_code = False + html = req.load("https://real-debrid.com/ajax/login.php", + get={"user": user, "pass": data['password']}, + decode=True) + + if "Your login informations are incorrect" in html: + self.wrongPassword() + + elif "PIN Code required" in html: + self.logWarning(_("PIN code required. Please login to https://real-debrid.com using the PIN or disable the double authentication in your control panel on https://real-debrid.com")) + self.pin_code = True diff --git a/pyload/plugin/account/RehostTo.py b/pyload/plugin/account/RehostTo.py new file mode 100644 index 000000000..3b02145fc --- /dev/null +++ b/pyload/plugin/account/RehostTo.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.Account import Account + + +class RehostTo(Account): + __name = "RehostTo" + __type = "account" + __version = "0.16" + + __description = """Rehost.to account plugin""" + __license = "GPLv3" + __authors = [("RaNaN", "RaNaN@pyload.org")] + + + def loadAccountInfo(self, user, req): + premium = False + trafficleft = None + validuntil = -1 + session = "" + + html = req.load("http://rehost.to/api.php", + get={'cmd' : "login", 'user': user, + 'pass': self.getAccountData(user)['password']}) + try: + session = html.split(",")[1].split("=")[1] + + html = req.load("http://rehost.to/api.php", + get={'cmd': "get_premium_credits", 'long_ses': session}) + + if html.strip() == "0,0" or "ERROR" in html: + self.logDebug(html) + else: + traffic, valid = html.split(",") + + premium = True + trafficleft = self.parseTraffic(traffic + "MB") + validuntil = float(valid) + + finally: + return {'premium' : premium, + 'trafficleft': trafficleft, + 'validuntil' : validuntil, + 'session' : session} + + + def login(self, user, data, req): + html = req.load("http://rehost.to/api.php", + get={'cmd': "login", 'user': user, 'pass': data['password']}, + decode=True) + + if "ERROR" in html: + self.logDebug(html) + self.wrongPassword() diff --git a/pyload/plugin/account/RyushareCom.py b/pyload/plugin/account/RyushareCom.py new file mode 100644 index 000000000..f34d1d388 --- /dev/null +++ b/pyload/plugin/account/RyushareCom.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSAccount import XFSAccount + + +class RyushareCom(XFSAccount): + __name = "RyushareCom" + __type = "account" + __version = "0.06" + + __description = """Ryushare.com account plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + HOSTER_DOMAIN = "ryushare.com" diff --git a/pyload/plugin/account/SafesharingEu.py b/pyload/plugin/account/SafesharingEu.py new file mode 100644 index 000000000..eae8140fb --- /dev/null +++ b/pyload/plugin/account/SafesharingEu.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSAccount import XFSAccount + + +class SafesharingEu(XFSAccount): + __name = "SafesharingEu" + __type = "account" + __version = "0.02" + + __description = """Safesharing.eu account plugin""" + __license = "GPLv3" + __authors = [("guidobelix", "guidobelix@hotmail.it")] + + + HOSTER_DOMAIN = "safesharing.eu" diff --git a/pyload/plugin/account/SecureUploadEu.py b/pyload/plugin/account/SecureUploadEu.py new file mode 100644 index 000000000..b349e893f --- /dev/null +++ b/pyload/plugin/account/SecureUploadEu.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSAccount import XFSAccount + + +class SecureUploadEu(XFSAccount): + __name = "SecureUploadEu" + __type = "account" + __version = "0.02" + + __description = """SecureUpload.eu account plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + HOSTER_DOMAIN = "secureupload.eu" diff --git a/pyload/plugin/account/SendmywayCom.py b/pyload/plugin/account/SendmywayCom.py new file mode 100644 index 000000000..add0b2183 --- /dev/null +++ b/pyload/plugin/account/SendmywayCom.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSAccount import XFSAccount + + +class SendmywayCom(XFSAccount): + __name = "SendmywayCom" + __type = "account" + __version = "0.02" + + __description = """Sendmyway.com account plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + HOSTER_DOMAIN = "sendmyway.com" diff --git a/pyload/plugin/account/ShareonlineBiz.py b/pyload/plugin/account/ShareonlineBiz.py new file mode 100644 index 000000000..06b2818c9 --- /dev/null +++ b/pyload/plugin/account/ShareonlineBiz.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.Account import Account + + +class ShareonlineBiz(Account): + __name = "ShareonlineBiz" + __type = "account" + __version = "0.31" + + __description = """Share-online.biz account plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + def api_response(self, user, req): + return req.load("http://api.share-online.biz/cgi-bin", + get={'q' : "userdetails", + 'aux' : "traffic", + 'username': user, + 'password': self.getAccountData(user)['password']}) + + + def loadAccountInfo(self, user, req): + premium = False + validuntil = None + trafficleft = -1 + maxtraffic = 100 * 2 ** 30 #: 100 GB + + api = {} + for line in self.api_response(user, req).splitlines(): + if "=" in line: + key, value = line.split("=") + api[key] = value + + self.logDebug(api) + + if api['a'].lower() != "not_available": + req.cj.setCookie("share-online.biz", 'a', api['a']) + + premium = api['group'] in ("Premium", "PrePaid") + + validuntil = float(api['expire_date']) + + traffic = float(api['traffic_1d'].split(";")[0]) + maxtraffic = max(maxtraffic, traffic) + trafficleft = maxtraffic - traffic + + return {'premium' : premium, + 'validuntil' : validuntil, + 'trafficleft': trafficleft, + 'maxtraffic' : maxtraffic} + + + def login(self, user, data, req): + html = self.api_response(user, req) + err = re.search(r'\*\*(.+?)\*\*', html) + if err: + self.logError(err.group(1)) + self.wrongPassword() diff --git a/pyload/plugin/account/SimplyPremiumCom.py b/pyload/plugin/account/SimplyPremiumCom.py new file mode 100644 index 000000000..2547dad56 --- /dev/null +++ b/pyload/plugin/account/SimplyPremiumCom.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- + +from pyload.utils import json_loads +from pyload.plugin.Account import Account + + +class SimplyPremiumCom(Account): + __name = "SimplyPremiumCom" + __type = "account" + __version = "0.05" + + __description = """Simply-Premium.com account plugin""" + __license = "GPLv3" + __authors = [("EvolutionClip", "evolutionclip@live.de")] + + + def loadAccountInfo(self, user, req): + premium = False + validuntil = -1 + trafficleft = None + + json_data = req.load('http://www.simply-premium.com/api/user.php?format=json') + + self.logDebug("JSON data: %s" % json_data) + + json_data = json_loads(json_data) + + if 'vip' in json_data['result'] and json_data['result']['vip']: + premium = True + + if 'timeend' in json_data['result'] and json_data['result']['timeend']: + validuntil = float(json_data['result']['timeend']) + + if 'remain_traffic' in json_data['result'] and json_data['result']['remain_traffic']: + trafficleft = float(json_data['result']['remain_traffic']) + + return {"premium": premium, "validuntil": validuntil, "trafficleft": trafficleft} + + + def login(self, user, data, req): + req.cj.setCookie("simply-premium.com", "lang", "EN") + + html = req.load("http://www.simply-premium.com/login.php", + post={'key': user} if not data['password'] else {'login_name': user, 'login_pass': data['password']}, + decode=True) + + if 'logout' not in html: + self.wrongPassword() diff --git a/pyload/plugin/account/SimplydebridCom.py b/pyload/plugin/account/SimplydebridCom.py new file mode 100644 index 000000000..dc855c441 --- /dev/null +++ b/pyload/plugin/account/SimplydebridCom.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- + +import time + +from pyload.plugin.Account import Account + + +class SimplydebridCom(Account): + __name = "SimplydebridCom" + __type = "account" + __version = "0.11" + + __description = """Simply-Debrid.com account plugin""" + __license = "GPLv3" + __authors = [("Kagenoshin", "kagenoshin@gmx.ch")] + + + def loadAccountInfo(self, user, req): + get_data = {'login': 2, 'u': self.loginname, 'p': self.password} + res = req.load("http://simply-debrid.com/api.php", get=get_data, decode=True) + data = [x.strip() for x in res.split(";")] + if str(data[0]) != "1": + return {"premium": False} + else: + return {"trafficleft": -1, "validuntil": time.mktime(time.strptime(str(data[2]), "%d/%m/%Y"))} + + + def login(self, user, data, req): + self.loginname = user + self.password = data['password'] + get_data = {'login': 1, 'u': self.loginname, 'p': self.password} + + res = req.load("http://simply-debrid.com/api.php", get=get_data, decode=True) + if res != "02: loggin success": + self.wrongPassword() diff --git a/pyload/plugin/account/SmoozedCom.py b/pyload/plugin/account/SmoozedCom.py new file mode 100644 index 000000000..f24799caf --- /dev/null +++ b/pyload/plugin/account/SmoozedCom.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- + +import hashlib +import time + +try: + from beaker.crypto.pbkdf2 import PBKDF2 + +except ImportError: + from beaker.crypto.pbkdf2 import pbkdf2 + from binascii import b2a_hex + + class PBKDF2(object): + + def __init__(self, passphrase, salt, iterations=1000): + self.passphrase = passphrase + self.salt = salt + self.iterations = iterations + + + def hexread(self, octets): + return b2a_hex(pbkdf2(self.passphrase, self.salt, self.iterations, octets)) + +from pyload.utils import json_loads +from pyload.plugin.Account import Account + + +class SmoozedCom(Account): + __name = "SmoozedCom" + __type = "account" + __version = "0.04" + + __description = """Smoozed.com account plugin""" + __license = "GPLv3" + __authors = [("", "")] + + + def loadAccountInfo(self, user, req): + # Get user data from premiumize.me + status = self.getAccountStatus(user, req) + + self.logDebug(status) + + if status['state'] != 'ok': + info = {'validuntil' : None, + 'trafficleft': None, + 'premium' : False} + else: + # Parse account info + info = {'validuntil' : float(status['data']['user']['user_premium']), + 'trafficleft': max(0, status['data']['traffic'][1] - status['data']['traffic'][0]), + 'session' : status['data']['session_key'], + 'hosters' : [hoster['name'] for hoster in status['data']['hoster']]} + + if info['validuntil'] < time.time(): + info['premium'] = False + else: + info['premium'] = True + + return info + + + def login(self, user, data, req): + # Get user data from premiumize.me + status = self.getAccountStatus(user, req) + + # Check if user and password are valid + if status['state'] != 'ok': + self.wrongPassword() + + + def getAccountStatus(self, user, req): + password = self.getAccountData(user)['password'] + salt = hashlib.sha256(password).hexdigest() + encrypted = PBKDF2(password, salt, iterations=1000).hexread(32) + + return json_loads(req.load("http://www2.smoozed.com/api/login", + get={'auth': user, 'password': encrypted})) diff --git a/pyload/plugin/account/StahnuTo.py b/pyload/plugin/account/StahnuTo.py new file mode 100644 index 000000000..57f9adc10 --- /dev/null +++ b/pyload/plugin/account/StahnuTo.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.Account import Account + + +class StahnuTo(Account): + __name = "StahnuTo" + __type = "account" + __version = "0.05" + + __description = """StahnuTo account plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + def loadAccountInfo(self, user, req): + html = req.load("http://www.stahnu.to/") + + m = re.search(r'>VIP: (\d+.*)<', html) + trafficleft = self.parseTraffic(m.group(1)) if m else 0 + + return {"premium": trafficleft > 512, "trafficleft": trafficleft, "validuntil": -1} + + + def login(self, user, data, req): + html = req.load("http://www.stahnu.to/login.php", + post={"username": user, + "password": data['password'], + "submit": "Login"}, + decode=True) + + if not '<a href="logout.php">' in html: + self.wrongPassword() diff --git a/pyload/plugin/account/StreamcloudEu.py b/pyload/plugin/account/StreamcloudEu.py new file mode 100644 index 000000000..f3eb6cce9 --- /dev/null +++ b/pyload/plugin/account/StreamcloudEu.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSAccount import XFSAccount + + +class StreamcloudEu(XFSAccount): + __name = "StreamcloudEu" + __type = "account" + __version = "0.02" + + __description = """Streamcloud.eu account plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + HOSTER_DOMAIN = "streamcloud.eu" diff --git a/pyload/plugin/account/TurbobitNet.py b/pyload/plugin/account/TurbobitNet.py new file mode 100644 index 000000000..4b1b6b2a0 --- /dev/null +++ b/pyload/plugin/account/TurbobitNet.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- + +import re +import time + +from pyload.plugin.Account import Account + + +class TurbobitNet(Account): + __name = "TurbobitNet" + __type = "account" + __version = "0.02" + + __description = """TurbobitNet account plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + def loadAccountInfo(self, user, req): + html = req.load("http://turbobit.net") + + m = re.search(r'<u>Turbo Access</u> to ([\d.]+)', html) + if m: + premium = True + validuntil = time.mktime(time.strptime(m.group(1), "%d.%m.%Y")) + else: + premium = False + validuntil = -1 + + return {"premium": premium, "trafficleft": -1, "validuntil": validuntil} + + + def login(self, user, data, req): + req.cj.setCookie("turbobit.net", "user_lang", "en") + + html = req.load("http://turbobit.net/user/login", + post={"user[login]": user, + "user[pass]": data['password'], + "user[submit]": "Login"}, + decode=True) + + if not '<div class="menu-item user-name">' in html: + self.wrongPassword() diff --git a/pyload/plugin/account/TusfilesNet.py b/pyload/plugin/account/TusfilesNet.py new file mode 100644 index 000000000..df22a36e6 --- /dev/null +++ b/pyload/plugin/account/TusfilesNet.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSAccount import XFSAccount + + +class TusfilesNet(XFSAccount): + __name = "TusfilesNet" + __type = "account" + __version = "0.06" + + __description = """Tusfile.net account plugin""" + __license = "GPLv3" + __authors = [("guidobelix", "guidobelix@hotmail.it")] + + + HOSTER_DOMAIN = "tusfiles.net" + + VALID_UNTIL_PATTERN = r'<span class="label label-default">([^<]+)</span>' + TRAFFIC_LEFT_PATTERN = r'<td><img src="//www\.tusfiles\.net/i/icon/meter\.png" alt=""/></td>\n<td> (?P<S>[\d.,]+)' diff --git a/pyload/plugin/account/UlozTo.py b/pyload/plugin/account/UlozTo.py new file mode 100644 index 000000000..20f03f907 --- /dev/null +++ b/pyload/plugin/account/UlozTo.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- + +import re +import urlparse + +from pyload.plugin.Account import Account + + +class UlozTo(Account): + __name = "UlozTo" + __type = "account" + __version = "0.10" + + __description = """Uloz.to account plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz"), + ("pulpe", "")] + + + TRAFFIC_LEFT_PATTERN = r'<li class="menu-kredit"><a .*?title=".+?GB = ([\d.]+) MB"' + + + def loadAccountInfo(self, user, req): + html = req.load("http://www.ulozto.net/", decode=True) + + m = re.search(self.TRAFFIC_LEFT_PATTERN, html) + + trafficleft = float(m.group(1).replace(' ', '').replace(',', '.')) * 1000 * 1.048 if m else 0 + premium = bool(trafficleft) + + return {'validuntil': -1, 'trafficleft': trafficleft, 'premium': premium} + + + def login(self, user, data, req): + login_page = req.load('http://www.ulozto.net/?do=web-login', decode=True) + action = re.findall('<form action="(.+?)"', login_page)[1].replace('&', '&') + token = re.search('_token_" value="(.+?)"', login_page).group(1) + + html = req.load(urlparse.urljoin("http://www.ulozto.net/", action), + post={'_token_' : token, + 'do' : "loginForm-submit", + 'login' : u"PÅihlásit", + 'password': data['password'], + 'username': user, + 'remember': "on"}, + decode=True) + + if '<div class="flash error">' in html: + self.wrongPassword() diff --git a/pyload/plugin/account/UnrestrictLi.py b/pyload/plugin/account/UnrestrictLi.py new file mode 100644 index 000000000..d57b998ca --- /dev/null +++ b/pyload/plugin/account/UnrestrictLi.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.Account import Account +from pyload.utils import json_loads + + +class UnrestrictLi(Account): + __name = "UnrestrictLi" + __type = "account" + __version = "0.05" + + __description = """Unrestrict.li account plugin""" + __license = "GPLv3" + __authors = [("stickell", "l.stickell@yahoo.it")] + + + def loadAccountInfo(self, user, req): + json_data = req.load('http://unrestrict.li/api/jdownloader/user.php?format=json') + self.logDebug("JSON data: " + json_data) + json_data = json_loads(json_data) + + if 'vip' in json_data['result'] and json_data['result']['vip'] == 0: + return {"premium": False} + + validuntil = json_data['result']['expires'] + trafficleft = float(json_data['result']['traffic']) + + return {"premium": True, "validuntil": validuntil, "trafficleft": trafficleft} + + + def login(self, user, data, req): + req.cj.setCookie("unrestrict.li", "lang", "EN") + html = req.load("https://unrestrict.li/sign_in", decode=True) + + if 'solvemedia' in html: + self.logError(_("A Captcha is required. Go to http://unrestrict.li/sign_in and login, then retry")) + return + + post_data = {"username": user, "password": data['password'], + "remember_me": "remember", "signin": "Sign in"} + html = req.load("https://unrestrict.li/sign_in", post=post_data, decode=True) + + if 'sign_out' not in html: + self.wrongPassword() diff --git a/pyload/plugin/account/UploadableCh.py b/pyload/plugin/account/UploadableCh.py new file mode 100644 index 000000000..c95fe7f0b --- /dev/null +++ b/pyload/plugin/account/UploadableCh.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.Account import Account + + +class UploadableCh(Account): + __name = "UploadableCh" + __type = "account" + __version = "0.03" + + __description = """Uploadable.ch account plugin""" + __license = "GPLv3" + __authors = [("Sasch", "gsasch@gmail.com")] + + + def loadAccountInfo(self, user, req): + html = req.load("http://www.uploadable.ch/login.php") + + premium = '<a href="/logout.php"' in html + trafficleft = -1 if premium else None + + return {'validuntil': None, 'trafficleft': trafficleft, 'premium': premium} #@TODO: validuntil + + + def login(self, user, data, req): + html = req.load("http://www.uploadable.ch/login.php", + post={'userName' : user, + 'userPassword' : data['password'], + 'autoLogin' : "1", + 'action__login': "normalLogin"}, + decode=True) + + if "Login failed" in html: + self.wrongPassword() diff --git a/pyload/plugin/account/UploadcCom.py b/pyload/plugin/account/UploadcCom.py new file mode 100644 index 000000000..01102168c --- /dev/null +++ b/pyload/plugin/account/UploadcCom.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSAccount import XFSAccount + + +class UploadcCom(XFSAccount): + __name = "UploadcCom" + __type = "account" + __version = "0.02" + + __description = """Uploadc.com account plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + HOSTER_DOMAIN = "uploadc.com" diff --git a/pyload/plugin/account/UploadedTo.py b/pyload/plugin/account/UploadedTo.py new file mode 100644 index 000000000..1b8ae5b27 --- /dev/null +++ b/pyload/plugin/account/UploadedTo.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- + +import re +import time + +from pyload.plugin.Account import Account + + +class UploadedTo(Account): + __name = "UploadedTo" + __type = "account" + __version = "0.30" + + __description = """Uploaded.to account plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + PREMIUM_PATTERN = r'<em>Premium</em>' + VALID_UNTIL_PATTERN = r'<td>Duration:</td>\s*<th>(.+?)<' + TRAFFIC_LEFT_PATTERN = r'<b class="cB">(?P<S>[\d.,]+) (?P<U>[\w^_]+)' + + + def loadAccountInfo(self, user, req): + validuntil = None + trafficleft = None + premium = None + + html = req.load("http://uploaded.net/me") + + premium = re.search(self.PREMIUM_PATTERN, html) is not None + + m = re.search(self.VALID_UNTIL_PATTERN, html, re.M) + if m: + expiredate = m.group(1).lower().strip() + + if expiredate == "unlimited": + validuntil = -1 + else: + m = re.findall(r'(\d+) (week|day|hour)', expiredate) + if m: + validuntil = time.time() + for n, u in m: + validuntil += float(n) * 60 * 60 * {'week': 168, 'day': 24, 'hour': 1}[u] + + m = re.search(self.TRAFFIC_LEFT_PATTERN, html) + if m: + traffic = m.groupdict() + size = traffic['S'].replace('.', '') + unit = traffic['U'].lower() + + trafficleft = self.parseTraffic(size + unit) + + return {'validuntil' : validuntil, + 'trafficleft': trafficleft, + 'premium' : premium} + + + def login(self, user, data, req): + # req.cj.setCookie("uploaded.net", "lang", "en") + + html = req.load("http://uploaded.net/io/login", + post={'id': user, 'pw': data['password'], '_': ""}, + decode=True) + + if '"err"' in html: + self.wrongPassword() diff --git a/pyload/plugin/account/UploadheroCom.py b/pyload/plugin/account/UploadheroCom.py new file mode 100644 index 000000000..a0c8e2aa8 --- /dev/null +++ b/pyload/plugin/account/UploadheroCom.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- + +import re +import datetime +import time + +from pyload.plugin.Account import Account + + +class UploadheroCom(Account): + __name = "UploadheroCom" + __type = "account" + __version = "0.21" + + __description = """Uploadhero.co account plugin""" + __license = "GPLv3" + __authors = [("mcmyst", "mcmyst@hotmail.fr")] + + + def loadAccountInfo(self, user, req): + premium_pattern = re.compile('Il vous reste <span class="bleu">(\d+)</span> jours premium') + + data = self.getAccountData(user) + html = req.load("http://uploadhero.co/my-account") + + if premium_pattern.search(html): + end_date = datetime.date.today() + datetime.timedelta(days=int(premium_pattern.search(html).group(1))) + end_date = time.mktime(future.timetuple()) + account_info = {"validuntil": end_date, "trafficleft": -1, "premium": True} + else: + account_info = {"validuntil": -1, "trafficleft": -1, "premium": False} + + return account_info + + + def login(self, user, data, req): + html = req.load("http://uploadhero.co/lib/connexion.php", + post={"pseudo_login": user, "password_login": data['password']}, + decode=True) + + if "mot de passe invalide" in html: + self.wrongPassword() diff --git a/pyload/plugin/account/UploadingCom.py b/pyload/plugin/account/UploadingCom.py new file mode 100644 index 000000000..992e63615 --- /dev/null +++ b/pyload/plugin/account/UploadingCom.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- + +import re +import time + +from pyload.plugin.Account import Account +from pyload.plugin.internal.SimpleHoster import set_cookies + + +class UploadingCom(Account): + __name = "UploadingCom" + __type = "account" + __version = "0.12" + + __description = """Uploading.com account plugin""" + __license = "GPLv3" + __authors = [("mkaay", "mkaay@mkaay.de")] + + + PREMIUM_PATTERN = r'UPGRADE TO PREMIUM' + VALID_UNTIL_PATTERN = r'Valid Until:(.+?)<' + + + def loadAccountInfo(self, user, req): + validuntil = None + trafficleft = None + premium = None + + html = req.load("http://uploading.com/") + + premium = re.search(self.PREMIUM_PATTERN, html) is None + + m = re.search(self.VALID_UNTIL_PATTERN, html) + if m: + expiredate = m.group(1).strip() + self.logDebug("Expire date: " + expiredate) + + try: + validuntil = time.mktime(time.strptime(expiredate, "%b %d, %Y")) + + except Exception, e: + self.logError(e) + + else: + if validuntil > time.mktime(time.gmtime()): + premium = True + else: + premium = False + validuntil = None + + return {'validuntil' : validuntil, + 'trafficleft': trafficleft, + 'premium' : premium} + + + def login(self, user, data, req): + set_cookies(req.cj, + [("uploading.com", "lang" , "1" ), + ("uploading.com", "language", "1" ), + ("uploading.com", "setlang" , "en"), + ("uploading.com", "_lang" , "en")]) + + req.load("http://uploading.com/") + req.load("http://uploading.com/general/login_form/?JsHttpRequest=%s-xml" % long(time.time() * 1000), + post={'email': user, 'password': data['password'], 'remember': "on"}) diff --git a/pyload/plugin/account/UptoboxCom.py b/pyload/plugin/account/UptoboxCom.py new file mode 100644 index 000000000..f7e715a33 --- /dev/null +++ b/pyload/plugin/account/UptoboxCom.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSAccount import XFSAccount + + +class UptoboxCom(XFSAccount): + __name = "UptoboxCom" + __type = "account" + __version = "0.08" + + __description = """DDLStorage.com account plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + HOSTER_DOMAIN = "uptobox.com" + HOSTER_URL = "https://uptobox.com/" + LOGIN_URL = "https://login.uptobox.com/" diff --git a/pyload/plugin/account/VidPlayNet.py b/pyload/plugin/account/VidPlayNet.py new file mode 100644 index 000000000..c5d4e0b5a --- /dev/null +++ b/pyload/plugin/account/VidPlayNet.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSAccount import XFSAccount + + +class VidPlayNet(XFSAccount): + __name = "VidPlayNet" + __type = "account" + __version = "0.02" + + __description = """VidPlay.net account plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + HOSTER_DOMAIN = "vidplay.net" diff --git a/pyload/plugin/account/WebshareCz.py b/pyload/plugin/account/WebshareCz.py new file mode 100644 index 000000000..c547534ba --- /dev/null +++ b/pyload/plugin/account/WebshareCz.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- + +import hashlib +import re +import time + +from passlib.hash import md5_crypt + +from pyload.plugin.Account import Account + + +class WebshareCz(Account): + __name = "WebshareCz" + __type = "account" + __version = "0.07" + + __description = """Webshare.cz account plugin""" + __license = "GPLv3" + __authors = [("rush", "radek.senfeld@gmail.com")] + + + VALID_UNTIL_PATTERN = r'<vip_until>(.+)</vip_until>' + + TRAFFIC_LEFT_PATTERN = r'<bytes>(.+)</bytes>' + + + def loadAccountInfo(self, user, req): + html = req.load("https://webshare.cz/api/user_data/", + post={'wst': self.infos['wst']}, + decode=True) + + self.logDebug("Response: " + html) + + expiredate = re.search(self.VALID_UNTIL_PATTERN, html).group(1) + self.logDebug("Expire date: " + expiredate) + + validuntil = time.mktime(time.strptime(expiredate, "%Y-%m-%d %H:%M:%S")) + trafficleft = self.parseTraffic(re.search(self.TRAFFIC_LEFT_PATTERN, html).group(1)) + premium = validuntil > time.time() + + return {'validuntil': validuntil, 'trafficleft': -1, 'premium': premium} + + + def login(self, user, data, req): + salt = req.load("https://webshare.cz/api/salt/", + post={'username_or_email': user, + 'wst' : ""}, + decode=True) + + if "<status>OK</status>" not in salt: + self.wrongPassword() + + salt = re.search('<salt>(.+)</salt>', salt).group(1) + password = hashlib.sha1(md5_crypt.encrypt(data['password'], salt=salt)).hexdigest() + digest = hashlib.md5(user + ":Webshare:" + password).hexdigest() + + login = req.load("https://webshare.cz/api/login/", + post={'digest' : digest, + 'keep_logged_in' : 1, + 'password' : password, + 'username_or_email': user, + 'wst' : ""}, + decode=True) + + if "<status>OK</status>" not in login: + self.wrongPassword() + + self.infos['wst'] = re.search('<token>(.+)</token>', login).group(1) diff --git a/pyload/plugin/account/XFileSharingPro.py b/pyload/plugin/account/XFileSharingPro.py new file mode 100644 index 000000000..79b5427ef --- /dev/null +++ b/pyload/plugin/account/XFileSharingPro.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSAccount import XFSAccount + + +class XFileSharingPro(XFSAccount): + __name = "XFileSharingPro" + __type = "account" + __version = "0.06" + + __description = """XFileSharingPro multi-purpose account plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + HOSTER_DOMAIN = None + + + def init(self): + if self.HOSTER_DOMAIN: + return super(XFileSharingPro, self).init() + + + def loadAccountInfo(self, user, req): + return super(XFileSharingPro if self.HOSTER_DOMAIN else XFSAccount, self).loadAccountInfo(user, req) + + + def login(self, user, data, req): + if self.HOSTER_DOMAIN: + try: + return super(XFileSharingPro, self).login(user, data, req) + except Exception: + self.HOSTER_URL = self.HOSTER_URL.replace("www.", "") + return super(XFileSharingPro, self).login(user, data, req) diff --git a/pyload/plugin/account/YibaishiwuCom.py b/pyload/plugin/account/YibaishiwuCom.py new file mode 100644 index 000000000..e12e3f3f2 --- /dev/null +++ b/pyload/plugin/account/YibaishiwuCom.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.Account import Account + + +class YibaishiwuCom(Account): + __name = "YibaishiwuCom" + __type = "account" + __version = "0.02" + + __description = """115.com account plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + ACCOUNT_INFO_PATTERN = r'var USER_PERMISSION = {(.*?)}' + + + def loadAccountInfo(self, user, req): + # self.relogin(user) + html = req.load("http://115.com/", decode=True) + + m = re.search(self.ACCOUNT_INFO_PATTERN, html, re.S) + premium = m and 'is_vip: 1' in m.group(1) + validuntil = trafficleft = (-1 if m else 0) + return dict({"validuntil": validuntil, "trafficleft": trafficleft, "premium": premium}) + + + def login(self, user, data, req): + html = req.load("http://passport.115.com/?ac=login", + post={"back": "http://www.115.com/", + "goto": "http://115.com/", + "login[account]": user, + "login[passwd]": data['password']}, + decode=True) + + if not 'var USER_PERMISSION = {' in html: + self.wrongPassword() diff --git a/pyload/plugin/account/ZeveraCom.py b/pyload/plugin/account/ZeveraCom.py new file mode 100644 index 000000000..1e5eacb4c --- /dev/null +++ b/pyload/plugin/account/ZeveraCom.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- + +import time + +from pyload.plugin.Account import Account + + +class ZeveraCom(Account): + __name = "ZeveraCom" + __type = "account" + __version = "0.26" + + __description = """Zevera.com account plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz"), + ("Walter Purcaro", "vuolter@gmail.com")] + + + HOSTER_DOMAIN = "zevera.com" + + + def init(self): + if not self.HOSTER_DOMAIN: + self.logError(_("Missing HOSTER_DOMAIN")) + + if not hasattr(self, "API_URL"): + self.API_URL = "http://api.%s/jDownloader.ashx" % (self.HOSTER_DOMAIN or "") + + + def loadAccountInfo(self, user, req): + validuntil = None + trafficleft = None + premium = False + + api = self.api_response(req) + + if "No trafic" not in api and api['endsubscriptiondate'] != "Expired!": + validuntil = time.mktime(time.strptime(api['endsubscriptiondate'], "%Y/%m/%d %H:%M:%S")) + trafficleft = float(api['availabletodaytraffic']) * 2 ** 20 if api['orondaytrafficlimit'] != '0' else -1 + premium = True + + return {'validuntil': validuntil, 'trafficleft': trafficleft, 'premium': premium} + + + def login(self, user, data, req): + self.user = user + self.password = data['password'] + + if self.api_response(req) == "No trafic": + self.wrongPassword() + + + def api_response(self, req, just_header=False, **kwargs): + get_data = {'cmd' : "accountinfo", + 'login': self.user, + 'pass' : self.password} + + get_data.update(kwargs) + + res = req.load(self.API_URL, + get=get_data, + just_header=just_header, + decode=True) + + self.logDebug(res) + + if ':' in res: + if not just_header: + res = res.replace(',', '\n') + return dict((y.strip().lower(), z.strip()) for (y, z) in + [x.split(':', 1) for x in res.splitlines() if ':' in x]) + else: + return res diff --git a/pyload/plugin/account/__init__.py b/pyload/plugin/account/__init__.py new file mode 100644 index 000000000..40a96afc6 --- /dev/null +++ b/pyload/plugin/account/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/pyload/plugin/addon/AndroidPhoneNotify.py b/pyload/plugin/addon/AndroidPhoneNotify.py new file mode 100644 index 000000000..8332f668d --- /dev/null +++ b/pyload/plugin/addon/AndroidPhoneNotify.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- + +import time + +from pyload.network.RequestFactory import getURL +from pyload.plugin.Addon import Addon, Expose + + +class AndroidPhoneNotify(Addon): + __name = "AndroidPhoneNotify" + __type = "addon" + __version = "0.07" + + __config = [("apikey" , "str" , "API key" , "" ), + ("notifycaptcha" , "bool", "Notify captcha request" , True ), + ("notifypackage" , "bool", "Notify package finished" , True ), + ("notifyprocessed", "bool", "Notify packages processed" , True ), + ("notifyupdate" , "bool", "Notify plugin updates" , True ), + ("notifyexit" , "bool", "Notify pyLoad shutdown" , True ), + ("sendtimewait" , "int" , "Timewait in seconds between notifications", 5 ), + ("sendpermin" , "int" , "Max notifications per minute" , 12 ), + ("ignoreclient" , "bool", "Send notifications if client is connected", False)] + + __description = """Send push notifications to your Android Phone (using notifymyandroid.com)""" + __license = "GPLv3" + __authors = [("Steven Kosyra" , "steven.kosyra@gmail.com"), + ("Walter Purcaro", "vuolter@gmail.com" )] + + + event_list = ["allDownloadsProcessed", "plugin_updated"] + + + def setup(self): + self.last_notify = 0 + self.notifications = 0 + + + def plugin_updated(self, type_plugins): + if not self.getConfig('notifyupdate'): + return + + self.notify(_("Plugins updated"), str(type_plugins)) + + + def exit(self): + if not self.getConfig('notifyexit'): + return + + if self.core.do_restart: + self.notify(_("Restarting pyLoad")) + else: + self.notify(_("Exiting pyLoad")) + + + def newCaptchaTask(self, task): + if not self.getConfig('notifycaptcha'): + return + + self.notify(_("Captcha"), _("New request waiting user input")) + + + def packageFinished(self, pypack): + if self.getConfig('notifypackage'): + self.notify(_("Package finished"), pypack.name) + + + def allDownloadsProcessed(self): + if not self.getConfig('notifyprocessed'): + return + + if any(True for pdata in self.core.api.getQueue() if pdata.linksdone < pdata.linkstotal): + self.notify(_("Package failed"), _("One or more packages was not completed successfully")) + else: + self.notify(_("All packages finished")) + + + @Expose + def notify(self, + event, + msg="", + key=self.getConfig('apikey')): + + if not key: + return + + if self.core.isClientConnected() and not self.getConfig('ignoreclient'): + return + + elapsed_time = time.time() - self.last_notify + + if elapsed_time < self.getConf("sendtimewait"): + return + + if elapsed_time > 60: + self.notifications = 0 + + elif self.notifications >= self.getConf("sendpermin"): + return + + + getURL("http://www.notifymyandroid.com/publicapi/notify", + get={'apikey' : key, + 'application': "pyLoad", + 'event' : event, + 'description': msg}) + + self.last_notify = time.time() + self.notifications += 1 diff --git a/pyload/plugin/addon/AntiVirus.py b/pyload/plugin/addon/AntiVirus.py new file mode 100644 index 000000000..e88dfd0f3 --- /dev/null +++ b/pyload/plugin/addon/AntiVirus.py @@ -0,0 +1,115 @@ +# -*- coding: utf-8 -*- + +import os +import shutil +import subprocess + +try: + import send2trash +except ImportError: + pass + +from pyload.plugin.Addon import Addon, Expose, threaded +from pyload.utils import fs_encode, fs_join + + +class AntiVirus(Addon): + __name = "AntiVirus" + __type = "addon" + __version = "0.09" + + #@TODO: add trash option (use Send2Trash lib) + __config = [("action" , "Antivirus default;Delete;Quarantine", "Manage infected files" , "Antivirus default"), + ("quardir" , "folder" , "Quarantine folder" , "" ), + ("deltotrash", "bool" , "Move to trash (recycle bin) instead delete", True ), + ("scanfailed", "bool" , "Scan incompleted files (failed downloads)" , False ), + ("cmdfile" , "file" , "Antivirus executable" , "" ), + ("cmdargs" , "str" , "Scan options" , "" ), + ("ignore-err", "bool" , "Ignore scan errors" , False )] + + __description = """Scan downloaded files with antivirus program""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + + + + @Expose + @threaded + def scan(self, pyfile, thread): + file = fs_encode(pyfile.plugin.lastDownload) + filename = os.path.basename(pyfile.plugin.lastDownload) + cmdfile = fs_encode(self.getConfig('cmdfile')) + cmdargs = fs_encode(self.getConfig('cmdargs').strip()) + + if not os.path.isfile(file) or not os.path.isfile(cmdfile): + return + + thread.addActive(pyfile) + pyfile.setCustomStatus(_("virus scanning")) + pyfile.setProgress(0) + + try: + p = subprocess.Popen([cmdfile, cmdargs, file], bufsize=-1, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + + out, err = map(str.strip, p.communicate()) + + if out: + self.logInfo(filename, out) + + if err: + self.logWarning(filename, err) + if not self.getConfig('ignore-err'): + self.logDebug("Delete/Quarantine task is aborted") + return + + if p.returncode: + pyfile.error = _("infected file") + action = self.getConfig('action') + try: + if action == "Delete": + if not self.getConfig('deltotrash'): + os.remove(file) + + else: + try: + send2trash.send2trash(file) + + except NameError: + self.logWarning(_("Send2Trash lib not found, moving to quarantine instead")) + pyfile.setCustomStatus(_("file moving")) + shutil.move(file, self.getConfig('quardir')) + + except Exception, e: + self.logWarning(_("Unable to move file to trash: %s, moving to quarantine instead") % e.message) + pyfile.setCustomStatus(_("file moving")) + shutil.move(file, self.getConfig('quardir')) + + else: + self.logDebug(_("Successfully moved file to trash")) + + elif action == "Quarantine": + pyfile.setCustomStatus(_("file moving")) + shutil.move(file, self.getConfig('quardir')) + + except (IOError, shutil.Error), e: + self.logError(filename, action + " action failed!", e) + + elif not out and not err: + self.logDebug(filename, "No infected file found") + + finally: + pyfile.setProgress(100) + thread.finishFile(pyfile) + + + def downloadFinished(self, pyfile): + return self.scan(pyfile) + + + def downloadFailed(self, pyfile): + #: Check if pyfile is still "failed", + # maybe might has been restarted in meantime + if pyfile.status == 8 and self.getConfig('scanfailed'): + return self.scan(pyfile) diff --git a/pyload/plugin/addon/Checksum.py b/pyload/plugin/addon/Checksum.py new file mode 100644 index 000000000..ada52d56e --- /dev/null +++ b/pyload/plugin/addon/Checksum.py @@ -0,0 +1,195 @@ +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +import hashlib +import os +import re +import zlib + +from pyload.plugin.Addon import Addon +from pyload.utils import fs_join, fs_encode + + +def computeChecksum(local_file, algorithm): + if algorithm in getattr(hashlib, "algorithms", ("md5", "sha1", "sha224", "sha256", "sha384", "sha512")): + h = getattr(hashlib, algorithm)() + + with open(local_file, 'rb') as f: + for chunk in iter(lambda: f.read(128 * h.block_size), ''): + h.update(chunk) + + return h.hexdigest() + + elif algorithm in ("adler32", "crc32"): + hf = getattr(zlib, algorithm) + last = 0 + + with open(local_file, 'rb') as f: + for chunk in iter(lambda: f.read(8192), ''): + last = hf(chunk, last) + + return "%x" % last + + else: + return None + + +class Checksum(Addon): + __name = "Checksum" + __type = "addon" + __version = "0.16" + + __config = [("activated" , "bool" , "Activated" , True ), + ("check_checksum", "bool" , "Check checksum? (If False only size will be verified)", True ), + ("check_action" , "fail;retry;nothing", "What to do if check fails?" , "retry"), + ("max_tries" , "int" , "Number of retries" , 2 ), + ("retry_action" , "fail;nothing" , "What to do if all retries fail?" , "fail" ), + ("wait_time" , "int" , "Time to wait before each retry (seconds)" , 1 )] + + __description = """Verify downloaded file size and checksum""" + __license = "GPLv3" + __authors = [("zoidberg" , "zoidberg@mujmail.cz"), + ("Walter Purcaro", "vuolter@gmail.com" ), + ("stickell" , "l.stickell@yahoo.it")] + + + methods = {'sfv' : 'crc32', + 'crc' : 'crc32', + 'hash': 'md5'} + regexps = {'sfv' : r'^(?P<NAME>[^;].+)\s+(?P<HASH>[0-9A-Fa-f]{8})$', + 'md5' : r'^(?P<NAME>[0-9A-Fa-f]{32}) (?P<FILE>.+)$', + 'crc' : r'filename=(?P<NAME>.+)\nsize=(?P<SIZE>\d+)\ncrc32=(?P<HASH>[0-9A-Fa-f]{8})$', + 'default': r'^(?P<HASH>[0-9A-Fa-f]+)\s+\*?(?P<NAME>.+)$'} + + + def activate(self): + if not self.getConfig('check_checksum'): + self.logInfo(_("Checksum validation is disabled in plugin configuration")) + + + def setup(self): + self.algorithms = sorted( + getattr(hashlib, "algorithms", ("md5", "sha1", "sha224", "sha256", "sha384", "sha512")), reverse=True) + + self.algorithms.extend(["crc32", "adler32"]) + + self.formats = self.algorithms + ["sfv", "crc", "hash"] + + + def downloadFinished(self, pyfile): + """ + Compute checksum for the downloaded file and compare it with the hash provided by the hoster. + pyfile.plugin.check_data should be a dictionary which can contain: + a) if known, the exact filesize in bytes (e.g. "size": 123456789) + b) hexadecimal hash string with algorithm name as key (e.g. "md5": "d76505d0869f9f928a17d42d66326307") + """ + if hasattr(pyfile.plugin, "check_data") and isinstance(pyfile.plugin.check_data, dict): + data = pyfile.plugin.check_data.copy() + + elif hasattr(pyfile.plugin, "api_data") and isinstance(pyfile.plugin.api_data, dict): + data = pyfile.plugin.api_data.copy() + + elif hasattr(pyfile.plugin, "info") and isinstance(pyfile.plugin.info, dict): + data = pyfile.plugin.info.copy() + data.pop('size', None) #@NOTE: Don't check file size until a similary matcher will be implemented + + else: + return + + self.logDebug(data) + + if not pyfile.plugin.lastDownload: + self.checkFailed(pyfile, None, "No file downloaded") + + local_file = fs_encode(pyfile.plugin.lastDownload) + # download_folder = self.config.get("general", "download_folder") + # local_file = fs_encode(fs_join(download_folder, pyfile.package().folder, pyfile.name)) + + if not os.path.isfile(local_file): + self.checkFailed(pyfile, None, "File does not exist") + + # validate file size + if "size" in data: + api_size = int(data['size']) + file_size = os.path.getsize(local_file) + + if api_size != file_size: + self.logWarning(_("File %s has incorrect size: %d B (%d expected)") % (pyfile.name, file_size, api_size)) + self.checkFailed(pyfile, local_file, "Incorrect file size") + + data.pop('size', None) + + # validate checksum + if data and self.getConfig('check_checksum'): + + if not 'md5' in data: + for type in ("checksum", "hashsum", "hash"): + if type in data: + data['md5'] = data[type] #@NOTE: What happens if it's not an md5 hash? + break + + for key in self.algorithms: + if key in data: + checksum = computeChecksum(local_file, key.replace("-", "").lower()) + if checksum: + if checksum == data[key].lower(): + self.logInfo(_('File integrity of "%s" verified by %s checksum (%s)') % + (pyfile.name, key.upper(), checksum)) + break + else: + self.logWarning(_("%s checksum for file %s does not match (%s != %s)") % + (key.upper(), pyfile.name, checksum, data[key])) + self.checkFailed(pyfile, local_file, "Checksums do not match") + else: + self.logWarning(_("Unsupported hashing algorithm"), key.upper()) + else: + self.logWarning(_("Unable to validate checksum for file: ") + pyfile.name) + + + def checkFailed(self, pyfile, local_file, msg): + check_action = self.getConfig('check_action') + if check_action == "retry": + max_tries = self.getConfig('max_tries') + retry_action = self.getConfig('retry_action') + if pyfile.plugin.retries < max_tries: + if local_file: + os.remove(local_file) + pyfile.plugin.retry(max_tries, self.getConfig('wait_time'), msg) + elif retry_action == "nothing": + return + elif check_action == "nothing": + return + pyfile.plugin.fail(reason=msg) + + + def packageFinished(self, pypack): + download_folder = fs_join(self.config.get("general", "download_folder"), pypack.folder, "") + + for link in pypack.getChildren().itervalues(): + file_type = os.path.splitext(link['name'])[1][1:].lower() + + if file_type not in self.formats: + continue + + hash_file = fs_encode(fs_join(download_folder, link['name'])) + if not os.path.isfile(hash_file): + self.logWarning(_("File not found"), link['name']) + continue + + with open(hash_file) as f: + text = f.read() + + for m in re.finditer(self.regexps.get(file_type, self.regexps['default']), text): + data = m.groupdict() + self.logDebug(link['name'], data) + + local_file = fs_encode(fs_join(download_folder, data['NAME'])) + algorithm = self.methods.get(file_type, file_type) + checksum = computeChecksum(local_file, algorithm) + if checksum == data['HASH']: + self.logInfo(_('File integrity of "%s" verified by %s checksum (%s)') % + (data['NAME'], algorithm, checksum)) + else: + self.logWarning(_("%s checksum for file %s does not match (%s != %s)") % + (algorithm, data['NAME'], checksum, data['HASH'])) diff --git a/pyload/plugin/addon/ClickNLoad.py b/pyload/plugin/addon/ClickNLoad.py new file mode 100644 index 000000000..9e62072ba --- /dev/null +++ b/pyload/plugin/addon/ClickNLoad.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- + +import socket +import threading +import time + +from pyload.plugin.Addon import Addon, threaded + + +def forward(source, destination): + try: + bufsize = 1024 + bufdata = source.recv(bufsize) + while bufdata: + destination.sendall(bufdata) + bufdata = source.recv(bufsize) + finally: + destination.shutdown(socket.SHUT_WR) + # destination.close() + + +#@TODO: IPv6 support +class ClickNLoad(Addon): + __name = "ClickNLoad" + __type = "addon" + __version = "0.41" + + __config = [("activated", "bool", "Activated" , True), + ("port" , "int" , "Port" , 9666), + ("extern" , "bool", "Listen on the public network interface", True)] + + __description = """Click'n'Load addon plugin""" + __license = "GPLv3" + __authors = [("RaNaN" , "RaNaN@pyload.de" ), + ("Walter Purcaro", "vuolter@gmail.com")] + + + def activate(self): + if not self.core.config.get("webui", "activated"): + return + + ip = "" if self.getConfig('extern') else "127.0.0.1" + webport = self.core.config.get("webui", "port") + cnlport = self.getConfig('port') + + self.proxy(ip, webport, cnlport) + + + @threaded + def proxy(self, ip, webport, cnlport): + time.sleep(10) #@TODO: Remove in 0.4.10 (implement addon delay on startup) + + self.logInfo(_("Proxy listening on %s:%s") % (ip or "0.0.0.0", cnlport)) + + self._server(ip, webport, cnlport) + + lock = threading.Lock() + lock.acquire() + lock.acquire() + + + @threaded + def _server(self, ip, webport, cnlport): + try: + dock_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + dock_socket.bind((ip, cnlport)) + dock_socket.listen(5) + + while True: + client_socket, client_addr = dock_socket.accept() + self.logDebug("Connection from %s:%s" % client_addr) + + server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server_socket.connect(("127.0.0.1", webport)) + + self.manager.startThread(forward, client_socket, server_socket) + self.manager.startThread(forward, server_socket, client_socket) + + except socket.timeout: + self.logDebug("Connection timed out, retrying...") + return self._server(ip, webport, cnlport) + + except socket.error, e: + self.logError(e) + time.sleep(240) + return self._server(ip, webport, cnlport) diff --git a/pyload/plugin/addon/DeleteFinished.py b/pyload/plugin/addon/DeleteFinished.py new file mode 100644 index 000000000..8fe0b1497 --- /dev/null +++ b/pyload/plugin/addon/DeleteFinished.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- + +from pyload.database import style +from pyload.plugin.Addon import Addon + + +class DeleteFinished(Addon): + __name = "DeleteFinished" + __type = "addon" + __version = "1.12" + + __config = [("interval" , "int" , "Check interval in hours" , 72 ), + ("deloffline", "bool", "Delete package with offline links", False)] + + __description = """Automatically delete all finished packages from queue""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + # event_list = ["pluginConfigChanged"] + + MIN_CHECK_INTERVAL = 1 * 60 * 60 #: 1 hour + + + ## overwritten methods ## + + + def setup(self): + self.interval = self.MIN_CHECK_INTERVAL + + + def periodical(self): + if not self.info['sleep']: + deloffline = self.getConfig('deloffline') + mode = '0,1,4' if deloffline else '0,4' + msg = _('delete all finished packages in queue list (%s packages with offline links)') + self.logInfo(msg % (_('including') if deloffline else _('excluding'))) + self.deleteFinished(mode) + self.info['sleep'] = True + self.addEvent('packageFinished', self.wakeup) + + + # def pluginConfigChanged(self, plugin, name, value): + # if name == "interval" and value != self.interval: + # self.interval = value * 3600 + # self.initPeriodical() + + + def deactivate(self): + self.manager.removeEvent('packageFinished', self.wakeup) + + + def activate(self): + self.info['sleep'] = True + # interval = self.getConfig('interval') + # self.pluginConfigChanged(self.getClassName(), 'interval', interval) + self.interval = max(self.MIN_CHECK_INTERVAL, self.getConfig('interval') * 60 * 60) + self.addEvent('packageFinished', self.wakeup) + self.initPeriodical() + + + ## own methods ## + + + @style.queue + def deleteFinished(self, mode): + self.c.execute('DELETE FROM packages WHERE NOT EXISTS(SELECT 1 FROM links WHERE package=packages.id AND status NOT IN (%s))' % mode) + self.c.execute('DELETE FROM links WHERE NOT EXISTS(SELECT 1 FROM packages WHERE id=links.package)') + + + def wakeup(self, pypack): + self.manager.removeEvent('packageFinished', self.wakeup) + self.info['sleep'] = False + + + ## event managing ## + + + def addEvent(self, event, func): + """Adds an event listener for event name""" + if event in self.manager.events: + if func in self.manager.events[event]: + self.logDebug("Function already registered", func) + else: + self.manager.events[event].append(func) + else: + self.manager.events[event] = [func] diff --git a/pyload/plugin/addon/DownloadScheduler.py b/pyload/plugin/addon/DownloadScheduler.py new file mode 100644 index 000000000..62cee31c5 --- /dev/null +++ b/pyload/plugin/addon/DownloadScheduler.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- + +import re +import time + +from pyload.plugin.Addon import Addon + + +class DownloadScheduler(Addon): + __name = "DownloadScheduler" + __type = "addon" + __version = "0.22" + + __config = [("timetable", "str" , "List time periods as hh:mm full or number(kB/s)" , "0:00 full, 7:00 250, 10:00 0, 17:00 150"), + ("abort" , "bool", "Abort active downloads when start period with speed 0", False )] + + __description = """Download Scheduler""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz"), + ("stickell", "l.stickell@yahoo.it")] + + + def activate(self): + self.updateSchedule() + + + def updateSchedule(self, schedule=None): + if schedule is None: + schedule = self.getConfig('timetable') + + schedule = re.findall("(\d{1,2}):(\d{2})[\s]*(-?\d+)", + schedule.lower().replace("full", "-1").replace("none", "0")) + if not schedule: + self.logError(_("Invalid schedule")) + return + + t0 = time.localtime() + now = (t0.tm_hour, t0.tm_min, t0.tm_sec, "X") + schedule = sorted([(int(x[0]), int(x[1]), 0, int(x[2])) for x in schedule] + [now]) + + self.logDebug("Schedule", schedule) + + for i, v in enumerate(schedule): + if v[3] == "X": + last, next = schedule[i - 1], schedule[(i + 1) % len(schedule)] + self.logDebug("Now/Last/Next", now, last, next) + + self.setDownloadSpeed(last[3]) + + next_time = (((24 + next[0] - now[0]) * 60 + next[1] - now[1]) * 60 + next[2] - now[2]) % 86400 + self.core.scheduler.removeJob(self.cb) + self.cb = self.core.scheduler.addJob(next_time, self.updateSchedule, threaded=False) + + + def setDownloadSpeed(self, speed): + if speed == 0: + abort = self.getConfig('abort') + self.logInfo(_("Stopping download server. (Running downloads will %sbe aborted.)") % '' if abort else _('not ')) + self.core.api.pauseServer() + if abort: + self.core.api.stopAllDownloads() + else: + self.core.api.unpauseServer() + + if speed > 0: + self.logInfo(_("Setting download speed to %d kB/s") % speed) + self.core.api.setConfigValue("download", "limit_speed", 1) + self.core.api.setConfigValue("download", "max_speed", speed) + else: + self.logInfo(_("Setting download speed to FULL")) + self.core.api.setConfigValue("download", "limit_speed", 0) + self.core.api.setConfigValue("download", "max_speed", -1) diff --git a/pyload/plugin/addon/ExternalScripts.py b/pyload/plugin/addon/ExternalScripts.py new file mode 100644 index 000000000..341dd6dcd --- /dev/null +++ b/pyload/plugin/addon/ExternalScripts.py @@ -0,0 +1,215 @@ +# -*- coding: utf-8 -*- + +import os +import subprocess + +from pyload.plugin.Addon import Addon +from pyload.utils import fs_encode, fs_join + + +class ExternalScripts(Addon): + __name = "ExternalScripts" + __type = "addon" + __version = "0.39" + + __config = [("activated", "bool", "Activated" , True), + ("waitend" , "bool", "Wait script ending", False)] + + __description = """Run external scripts""" + __license = "GPLv3" + __authors = [("mkaay" , "mkaay@mkaay.de"), + ("RaNaN" , "ranan@pyload.org"), + ("spoob" , "spoob@pyload.org"), + ("Walter Purcaro", "vuolter@gmail.com")] + + event_list = ["archive_extract_failed", "archive_extracted" , + "package_extract_failed", "package_extracted" , + "all_archives_extracted", "all_archives_processed", + "allDownloadsFinished" , "allDownloadsProcessed" , + "packageDeleted"] + + + def setup(self): + self.info['oldip'] = None + self.scripts = {} + + folders = ["pyload_start", "pyload_restart", "pyload_stop", + "before_reconnect", "after_reconnect", + "download_preparing", "download_failed", "download_finished", + "archive_extract_failed", "archive_extracted", + "package_finished", "package_deleted", "package_extract_failed", "package_extracted", + "all_downloads_processed", "all_downloads_finished", #@TODO: Invert `all_downloads_processed`, `all_downloads_finished` order in 0.4.10 + "all_archives_extracted", "all_archives_processed"] + + for folder in folders: + self.scripts[folder] = [] + for dir in (pypath, ''): + self.initPluginType(folder, os.path.join(dir, 'scripts', folder)) + + for script_type, names in self.scripts.iteritems(): + if names: + self.logInfo(_("Installed scripts for: ") + script_type, ", ".join(map(os.path.basename, names))) + + self.pyload_start() + + + def initPluginType(self, name, dir): + if not os.path.isdir(dir): + try: + os.makedirs(dir) + + except OSError, e: + self.logDebug(e) + return + + for filename in os.listdir(dir): + file = fs_join(dir, filename) + + if not os.path.isfile(file): + continue + + if filename[0] in ("#", "_") or filename.endswith("~") or filename.endswith(".swp"): + continue + + if not os.access(file, os.X_OK): + self.logWarning(_("Script not executable:") + " %s/%s" % (name, filename)) + + self.scripts[name].append(file) + + + def callScript(self, script, *args): + try: + cmd_args = [fs_encode(str(x) if not isinstance(x, basestring) else x) for x in args] + cmd = [script] + cmd_args + + self.logDebug("Executing: %s" % os.path.abspath(script), "Args: " + ' '.join(cmd_args)) + + p = subprocess.Popen(cmd, bufsize=-1) #@NOTE: output goes to pyload + if self.getConfig('waitend'): + p.communicate() + + except Exception, e: + try: + self.logError(_("Runtime error: %s") % os.path.abspath(script), e) + except Exception: + self.logError(_("Runtime error: %s") % os.path.abspath(script), _("Unknown error")) + + + def pyload_start(self): + for script in self.scripts['pyload_start']: + self.callScript(script) + + + def exit(self): + for script in self.scripts['pyload_restart' if self.core.do_restart else 'pyload_stop']: + self.callScript(script) + + + def beforeReconnecting(self, ip): + for script in self.scripts['before_reconnect']: + self.callScript(script, ip) + self.info['oldip'] = ip + + + def afterReconnecting(self, ip): + for script in self.scripts['after_reconnect']: + self.callScript(script, ip, self.info['oldip']) #@TODO: Use built-in oldip in 0.4.10 + + + def downloadPreparing(self, pyfile): + for script in self.scripts['download_preparing']: + self.callScript(script, pyfile.id, pyfile.name, None, pyfile.pluginname, pyfile.url) + + + def downloadFailed(self, pyfile): + if self.core.config.get("general", "folder_per_package"): + download_folder = fs_join(self.core.config.get("general", "download_folder"), pyfile.package().folder) + else: + download_folder = self.core.config.get("general", "download_folder") + + for script in self.scripts['download_failed']: + file = fs_join(download_folder, pyfile.name) + self.callScript(script, pyfile.id, pyfile.name, file, pyfile.pluginname, pyfile.url) + + + def downloadFinished(self, pyfile): + if self.core.config.get("general", "folder_per_package"): + download_folder = fs_join(self.core.config.get("general", "download_folder"), pyfile.package().folder) + else: + download_folder = self.core.config.get("general", "download_folder") + + for script in self.scripts['download_finished']: + file = fs_join(download_folder, pyfile.name) + self.callScript(script, pyfile.id, pyfile.name, file, pyfile.pluginname, pyfile.url) + + + def archive_extract_failed(self, pyfile, archive): + for script in self.scripts['archive_extract_failed']: + self.callScript(script, pyfile.id, pyfile.name, archive.filename, archive.out, archive.files) + + + def archive_extracted(self, pyfile, archive): + for script in self.scripts['archive_extracted']: + self.callScript(script, pyfile.id, pyfile.name, archive.filename, archive.out, archive.files) + + + def packageFinished(self, pypack): + if self.core.config.get("general", "folder_per_package"): + download_folder = fs_join(self.core.config.get("general", "download_folder"), pypack.folder) + else: + download_folder = self.core.config.get("general", "download_folder") + + for script in self.scripts['package_finished']: + self.callScript(script, pypack.id, pypack.name, download_folder, pypack.password) + + + def packageDeleted(self, pid): + pack = self.core.api.getPackageInfo(pid) + + if self.core.config.get("general", "folder_per_package"): + download_folder = fs_join(self.core.config.get("general", "download_folder"), pack.folder) + else: + download_folder = self.core.config.get("general", "download_folder") + + for script in self.scripts['package_deleted']: + self.callScript(script, pack.id, pack.name, download_folder, pack.password) + + + def package_extract_failed(self, pypack): + if self.core.config.get("general", "folder_per_package"): + download_folder = fs_join(self.core.config.get("general", "download_folder"), pypack.folder) + else: + download_folder = self.core.config.get("general", "download_folder") + + for script in self.scripts['package_extract_failed']: + self.callScript(script, pypack.id, pypack.name, download_folder, pypack.password) + + + def package_extracted(self, pypack): + if self.core.config.get("general", "folder_per_package"): + download_folder = fs_join(self.core.config.get("general", "download_folder"), pypack.folder) + else: + download_folder = self.core.config.get("general", "download_folder") + + for script in self.scripts['package_extracted']: + self.callScript(script, pypack.id, pypack.name, download_folder) + + + def allDownloadsFinished(self): + for script in self.scripts['all_downloads_finished']: + self.callScript(script) + + + def allDownloadsProcessed(self): + for script in self.scripts['all_downloads_processed']: + self.callScript(script) + + + def all_archives_extracted(self): + for script in self.scripts['all_archives_extracted']: + self.callScript(script) + + + def all_archives_processed(self): + for script in self.scripts['all_archives_processed']: + self.callScript(script) diff --git a/pyload/plugin/addon/ExtractArchive.py b/pyload/plugin/addon/ExtractArchive.py new file mode 100644 index 000000000..a2b22e90c --- /dev/null +++ b/pyload/plugin/addon/ExtractArchive.py @@ -0,0 +1,572 @@ +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +import os +import sys +import traceback + +# monkey patch bug in python 2.6 and lower +# http://bugs.python.org/issue6122, http://bugs.python.org/issue1236, http://bugs.python.org/issue1731717 +if sys.version_info < (2, 7) and os.name != "nt": + import errno + import subprocess + + + def _eintr_retry_call(func, *args): + while True: + try: + return func(*args) + + except OSError, e: + if e.errno == errno.EINTR: + continue + raise + + + # unsued timeout option for older python version + + + def wait(self, timeout=0): + """Wait for child process to terminate. Returns returncode + attribute.""" + if self.returncode is None: + try: + pid, sts = _eintr_retry_call(os.waitpid, self.pid, 0) + + except OSError, e: + if e.errno != errno.ECHILD: + raise + # This happens if SIGCLD is set to be ignored or waiting + # for child processes has otherwise been disabled for our + # process. This child is dead, we can't get the status. + sts = 0 + self._handle_exitstatus(sts) + return self.returncode + + subprocess.Popen.wait = wait + +try: + import send2trash +except ImportError: + pass + +from copy import copy +if os.name != "nt": + from grp import getgrnam + from pwd import getpwnam + +from pyload.plugin.Addon import Addon, threaded, Expose +from pyload.plugin.Extractor import ArchiveError, CRCError, PasswordError +from pyload.plugin.internal.SimpleHoster import replace_patterns +from pyload.utils import fs_encode, fs_join, uniqify + + +class ArchiveQueue(object): + + def __init__(self, plugin, storage): + self.plugin = plugin + self.storage = storage + + + def get(self): + try: + return [int(pid) for pid in self.plugin.getStorage("ExtractArchive:%s" % self.storage, "").decode('base64').split()] + except Exception: + return [] + + + def set(self, value): + if isinstance(value, list): + item = str(value)[1:-1].replace(' ', '').replace(',', ' ') + else: + item = str(value).strip() + return self.plugin.setStorage("ExtractArchive:%s" % self.storage, item.encode('base64')[:-1]) + + + def delete(self): + return self.plugin.delStorage("ExtractArchive:%s" % self.storage) + + + def add(self, item): + queue = self.get() + if item not in queue: + return self.set(queue + [item]) + else: + return True + + + def remove(self, item): + queue = self.get() + try: + queue.remove(item) + + except ValueError: + pass + + if queue == []: + return self.delete() + + return self.set(queue) + + +class ExtractArchive(Addon): + __name = "ExtractArchive" + __type = "addon" + __version = "1.44" + + __config = [("activated" , "bool" , "Activated" , True), + ("fullpath" , "bool" , "Extract with full paths" , True), + ("overwrite" , "bool" , "Overwrite files" , False), + ("keepbroken" , "bool" , "Try to extract broken archives" , False), + ("repair" , "bool" , "Repair broken archives (RAR required)" , False), + ("test" , "bool" , "Test archive before extracting" , False), + ("usepasswordfile", "bool" , "Use password file" , True), + ("passwordfile" , "file" , "Password file" , "archive_password.txt"), + ("delete" , "bool" , "Delete archive after extraction" , True), + ("deltotrash" , "bool" , "Move to trash (recycle bin) instead delete", True), + ("subfolder" , "bool" , "Create subfolder for each package" , False), + ("destination" , "folder" , "Extract files to folder" , ""), + ("extensions" , "str" , "Extract archives ending with extension" , "7z,bz2,bzip2,gz,gzip,lha,lzh,lzma,rar,tar,taz,tbz,tbz2,tgz,xar,xz,z,zip"), + ("excludefiles" , "str" , "Don't extract the following files" , "*.nfo,*.DS_Store,index.dat,thumb.db"), + ("recursive" , "bool" , "Extract archives in archives" , True), + ("waitall" , "bool" , "Run after all downloads was processed" , False), + ("renice" , "int" , "CPU priority" , 0)] + + __description = """Extract different kind of archives""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com"), + ("Immenz" , "immenz@gmx.net")] + + + event_list = ["allDownloadsProcessed", "packageDeleted"] + + NAME_REPLACEMENTS = [(r'\.part\d+\.rar$', ".part.rar")] + + + def setup(self): + self.queue = ArchiveQueue(self, "Queue") + self.failed = ArchiveQueue(self, "Failed") + + self.interval = 60 + self.extracting = False + self.lastPackage = False + self.extractors = [] + self.passwords = [] + self.repair = False + + + def activate(self): + for p in ("UnRar", "SevenZip", "UnZip"): + try: + module = self.core.pluginManager.loadModule("extractor", p) + klass = getattr(module, p) + if klass.isUsable(): + self.extractors.append(klass) + if klass.REPAIR: + self.repair = self.getConfig('repair') + + except OSError, e: + if e.errno == 2: + self.logWarning(_("No %s installed") % p) + else: + self.logWarning(_("Could not activate: %s") % p, e) + if self.core.debug: + traceback.print_exc() + + except Exception, e: + self.logWarning(_("Could not activate: %s") % p, e) + if self.core.debug: + traceback.print_exc() + + if self.extractors: + self.logDebug(*["Found %s %s" % (Extractor.NAME, Extractor.VERSION) for Extractor in self.extractors]) + self.extractQueued() #: Resume unfinished extractions + else: + self.logInfo(_("No Extract plugins activated")) + + + @threaded + def extractQueued(self, thread): + if self.extracting: #@NOTE: doing the check here for safty (called by coreReady) + return + + self.extracting = True + + packages = self.queue.get() + while packages: + if self.lastPackage: #: called from allDownloadsProcessed + self.lastPackage = False + if self.extract(packages, thread): #@NOTE: check only if all gone fine, no failed reporting for now + self.manager.dispatchEvent("all_archives_extracted") + self.manager.dispatchEvent("all_archives_processed") + else: + if self.extract(packages, thread): #@NOTE: check only if all gone fine, no failed reporting for now + pass + + packages = self.queue.get() #: check for packages added during extraction + + self.extracting = False + + + @Expose + def extractPackage(self, *ids): + """ Extract packages with given id""" + for id in ids: + self.queue.add(id) + if not self.getConfig('waitall') and not self.extracting: + self.extractQueued() + + + def packageDeleted(self, pid): + self.queue.remove(pid) + + + def packageFinished(self, pypack): + self.queue.add(pypack.id) + if not self.getConfig('waitall') and not self.extracting: + self.extractQueued() + + + def allDownloadsProcessed(self): + self.lastPackage = True + if self.getConfig('waitall') and not self.extracting: + self.extractQueued() + + + @Expose + def extract(self, ids, thread=None): #@TODO: Use pypack, not pid to improve method usability + if not ids: + return False + + processed = [] + extracted = [] + failed = [] + + toList = lambda string: string.replace(' ', '').replace(',', '|').replace(';', '|').split('|') + + destination = self.getConfig('destination') + subfolder = self.getConfig('subfolder') + fullpath = self.getConfig('fullpath') + overwrite = self.getConfig('overwrite') + renice = self.getConfig('renice') + recursive = self.getConfig('recursive') + delete = self.getConfig('delete') + keepbroken = self.getConfig('keepbroken') + + extensions = [x.lstrip('.').lower() for x in toList(self.getConfig('extensions'))] + excludefiles = toList(self.getConfig('excludefiles')) + + if extensions: + self.logDebug("Use for extensions: %s" % "|.".join(extensions)) + + # reload from txt file + self.reloadPasswords() + + download_folder = self.config.get("general", "download_folder") + + # iterate packages -> extractors -> targets + for pid in ids: + pypack = self.core.files.getPackage(pid) + + if not pypack: + self.queue.remove(pid) + continue + + self.logInfo(_("Check package: %s") % pypack.name) + + # determine output folder + out = fs_join(download_folder, pypack.folder, destination, "") #: force trailing slash + + if subfolder: + out = fs_join(out, pypack.folder) + + if not os.path.exists(out): + os.makedirs(out) + + matched = False + success = True + files_ids = dict((pylink['name'], ((fs_join(download_folder, pypack.folder, pylink['name'])), pylink['id'], out)) for pylink + in sorted(pypack.getChildren().itervalues(), key=lambda k: k['name'])).values() #: remove duplicates + + # check as long there are unseen files + while files_ids: + new_files_ids = [] + + if extensions: + files_ids = [(fname, fid, fout) for fname, fid, fout in files_ids + if filter(lambda ext: fname.lower().endswith(ext), extensions)] + + for Extractor in self.extractors: + targets = Extractor.getTargets(files_ids) + if targets: + self.logDebug("Targets for %s: %s" % (Extractor.__name__, targets)) + matched = True + + for fname, fid, fout in targets: + name = os.path.basename(fname) + + if not os.path.exists(fname): + self.logDebug(name, "File not found") + continue + + self.logInfo(name, _("Extract to: %s") % fout) + try: + pyfile = self.core.files.getFile(fid) + archive = Extractor(self, + fname, + fout, + fullpath, + overwrite, + excludefiles, + renice, + delete, + keepbroken, + fid) + + thread.addActive(pyfile) + archive.init() + + try: + new_files = self._extract(pyfile, archive, pypack.password) + + finally: + pyfile.setProgress(100) + thread.finishFile(pyfile) + + except Exception, e: + self.logError(name, e) + success = False + continue + + # remove processed file and related multiparts from list + files_ids = [(fname, fid, fout) for fname, fid, fout in files_ids + if fname not in archive.getDeleteFiles()] + self.logDebug("Extracted files: %s" % new_files) + self.setPermissions(new_files) + + for filename in new_files: + file = fs_encode(fs_join(os.path.dirname(archive.filename), filename)) + if not os.path.exists(file): + self.logDebug("New file %s does not exists" % filename) + continue + + if recursive and os.path.isfile(file): + new_files_ids.append((filename, fid, os.path.dirname(filename))) #: append as new target + + self.manager.dispatchEvent("archive_extracted", pyfile, archive) + + files_ids = new_files_ids #: also check extracted files + + if matched: + if success: + extracted.append(pid) + self.manager.dispatchEvent("package_extracted", pypack) + + else: + failed.append(pid) + self.manager.dispatchEvent("package_extract_failed", pypack) + + self.failed.add(pid) + else: + self.logInfo(_("No files found to extract")) + + if not matched or not success and subfolder: + try: + os.rmdir(out) + + except OSError: + pass + + self.queue.remove(pid) + + return True if not failed else False + + + def _extract(self, pyfile, archive, password): + name = os.path.basename(archive.filename) + + pyfile.setStatus("processing") + + encrypted = False + try: + self.logDebug("Password: %s" % (password or "None provided")) + passwords = uniqify([password] + self.getPasswords(False)) if self.getConfig('usepasswordfile') else [password] + for pw in passwords: + try: + if self.getConfig('test') or self.repair: + pyfile.setCustomStatus(_("archive testing")) + if pw: + self.logDebug("Testing with password: %s" % pw) + pyfile.setProgress(0) + archive.verify(pw) + pyfile.setProgress(100) + else: + archive.check(pw) + + self.addPassword(pw) + break + + except PasswordError: + if not encrypted: + self.logInfo(name, _("Password protected")) + encrypted = True + + except CRCError, e: + self.logDebug(name, e) + self.logInfo(name, _("CRC Error")) + + if self.repair: + self.logWarning(name, _("Repairing...")) + + pyfile.setCustomStatus(_("archive repairing")) + pyfile.setProgress(0) + repaired = archive.repair() + pyfile.setProgress(100) + + if not repaired and not self.getConfig('keepbroken'): + raise CRCError("Archive damaged") + + self.addPassword(pw) + break + + raise CRCError("Archive damaged") + + except ArchiveError, e: + raise ArchiveError(e) + + pyfile.setCustomStatus(_("extracting")) + pyfile.setProgress(0) + + if not encrypted or not self.getConfig('usepasswordfile'): + self.logDebug("Extracting using password: %s" % (password or "None")) + archive.extract(password) + else: + for pw in filter(None, uniqify([password] + self.getPasswords(False))): + try: + self.logDebug("Extracting using password: %s" % pw) + + archive.extract(pw) + self.addPassword(pw) + break + + except PasswordError: + self.logDebug("Password was wrong") + else: + raise PasswordError + + pyfile.setProgress(100) + pyfile.setStatus("processing") + + delfiles = archive.getDeleteFiles() + self.logDebug("Would delete: " + ", ".join(delfiles)) + + if self.getConfig('delete'): + self.logInfo(_("Deleting %s files") % len(delfiles)) + + deltotrash = self.getConfig('deltotrash') + for f in delfiles: + file = fs_encode(f) + if not os.path.exists(file): + continue + + if not deltotrash: + os.remove(file) + + else: + try: + send2trash.send2trash(file) + + except NameError: + self.logWarning(_("Unable to move %s to trash: Send2Trash lib not found") % os.path.basename(f)) + + except Exception, e: + self.logWarning(_("Unable to move %s to trash: %s") % (os.path.basename(f), e.message)) + + else: + self.logDebug(_("Successfully moved %s to trash") % os.path.basename(f)) + + self.logInfo(name, _("Extracting finished")) + extracted_files = archive.files or archive.list() + + return extracted_files + + except PasswordError: + self.logError(name, _("Wrong password" if password else "No password found")) + + except CRCError, e: + self.logError(name, _("CRC mismatch"), e) + + except ArchiveError, e: + self.logError(name, _("Archive error"), e) + + except Exception, e: + self.logError(name, _("Unknown error"), e) + if self.core.debug: + traceback.print_exc() + + self.manager.dispatchEvent("archive_extract_failed", pyfile, archive) + + raise Exception(_("Extract failed")) + + + @Expose + def getPasswords(self, reload=True): + """ List of saved passwords """ + if reload: + self.reloadPasswords() + + return self.passwords + + + def reloadPasswords(self): + try: + passwords = [] + + file = fs_encode(self.getConfig('passwordfile')) + with open(file) as f: + for pw in f.read().splitlines(): + passwords.append(pw) + + except IOError, e: + self.logError(e) + + else: + self.passwords = passwords + + + @Expose + def addPassword(self, password): + """ Adds a password to saved list""" + try: + self.passwords = uniqify([password] + self.passwords) + + file = fs_encode(self.getConfig('passwordfile')) + with open(file, "wb") as f: + for pw in self.passwords: + f.write(pw + '\n') + + except IOError, e: + self.logError(e) + + + def setPermissions(self, files): + for f in files: + if not os.path.exists(f): + continue + + try: + if self.config.get("permission", "change_file"): + if os.path.isfile(f): + os.chmod(f, int(self.config.get("permission", "file"), 8)) + + elif os.path.isdir(f): + os.chmod(f, int(self.config.get("permission", "folder"), 8)) + + if self.config.get("permission", "change_dl") and os.name != "nt": + uid = getpwnam(self.config.get("permission", "user"))[2] + gid = getgrnam(self.config.get("permission", "group"))[2] + os.chown(f, uid, gid) + + except Exception, e: + self.logWarning(_("Setting User and Group failed"), e) diff --git a/pyload/plugin/addon/HotFolder.py b/pyload/plugin/addon/HotFolder.py new file mode 100644 index 000000000..f7f543c3a --- /dev/null +++ b/pyload/plugin/addon/HotFolder.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +import os +import shutil +import time + +from pyload.plugin.Addon import Addon +from pyload.utils import fs_encode, fs_join + + +class HotFolder(Addon): + __name = "HotFolder" + __type = "addon" + __version = "0.14" + + __config = [("folder" , "str" , "Folder to observe" , "container"), + ("watch_file", "bool", "Observe link file" , False ), + ("keep" , "bool", "Keep added containers", True ), + ("file" , "str" , "Link file" , "links.txt")] + + __description = """Observe folder and file for changes and add container and links""" + __license = "GPLv3" + __authors = [("RaNaN", "RaNaN@pyload.de")] + + + def setup(self): + self.interval = 30 + + + def activate(self): + self.initPeriodical() + + + def periodical(self): + folder = fs_encode(self.getConfig('folder')) + file = fs_encode(self.getConfig('file')) + + try: + if not os.path.isdir(os.path.join(folder, "finished")): + os.makedirs(os.path.join(folder, "finished")) + + if self.getConfig('watch_file'): + with open(file, "a+") as f: + f.seek(0) + content = f.read().strip() + + if content: + f = open(file, "wb") + f.close() + + name = "%s_%s.txt" % (file, time.strftime("%H-%M-%S_%d%b%Y")) + + with open(fs_join(folder, "finished", name), "wb") as f: + f.write(content) + + self.core.api.addPackage(f.name, [f.name], 1) + + for f in os.listdir(folder): + path = os.path.join(folder, f) + + if not os.path.isfile(path) or f.endswith("~") or f.startswith("#") or f.startswith("."): + continue + + newpath = os.path.join(folder, "finished", f if self.getConfig('keep') else "tmp_" + f) + shutil.move(path, newpath) + + self.logInfo(_("Added %s from HotFolder") % f) + self.core.api.addPackage(f, [newpath], 1) + + except (IOError, OSError), e: + self.logError(e) diff --git a/pyload/plugin/addon/IRCInterface.py b/pyload/plugin/addon/IRCInterface.py new file mode 100644 index 000000000..3cf21b409 --- /dev/null +++ b/pyload/plugin/addon/IRCInterface.py @@ -0,0 +1,431 @@ +# -*- coding: utf-8 -*- + +import re +import socket +import ssl +import time +import traceback + +import pycurl + +from select import select + +from pyload.api import PackageDoesNotExists, FileDoesNotExists +from pyload.network.RequestFactory import getURL +from pyload.plugin.Addon import Addon +from pyload.utils import formatSize + + +class IRCInterface(Thread, Addon): + __name = "IRCInterface" + __type = "addon" + __version = "0.13" + + __config = [("host" , "str" , "IRC-Server Address" , "Enter your server here!"), + ("port" , "int" , "IRC-Server Port" , 6667 ), + ("ident" , "str" , "Clients ident" , "pyload-irc" ), + ("realname" , "str" , "Realname" , "pyload-irc" ), + ("ssl" , "bool", "Use SSL" , False ), + ("nick" , "str" , "Nickname the Client will take" , "pyLoad-IRC" ), + ("owner" , "str" , "Nickname the Client will accept commands from", "Enter your nick here!" ), + ("info_file", "bool", "Inform about every file finished" , False ), + ("info_pack", "bool", "Inform about every package finished" , True ), + ("captcha" , "bool", "Send captcha requests" , True )] + + __description = """Connect to irc and let owner perform different tasks""" + __license = "GPLv3" + __authors = [("Jeix", "Jeix@hasnomail.com")] + + + def __init__(self, core, manager): + Thread.__init__(self) + Addon.__init__(self, core, manager) + self.setDaemon(True) + + + def activate(self): + self.abort = False + self.more = [] + self.new_package = {} + + self.start() + + + def packageFinished(self, pypack): + try: + if self.getConfig('info_pack'): + self.response(_("Package finished: %s") % pypack.name) + except Exception: + pass + + + def downloadFinished(self, pyfile): + try: + if self.getConfig('info_file'): + self.response( + _("Download finished: %(name)s @ %(plugin)s ") % {"name": pyfile.name, "plugin": pyfile.pluginname}) + except Exception: + pass + + + def captchaTask(self, task): + if self.getConfig('captcha') and task.isTextual(): + task.handler.append(self) + task.setWaiting(60) + + html = getURL("http://www.freeimagehosting.net/upload.php", + post={"attached": (pycurl.FORM_FILE, task.captchaFile)}, multipart=True) + + url = re.search(r"\[img\]([^\[]+)\[/img\]\[/url\]", html).group(1) + self.response(_("New Captcha Request: %s") % url) + self.response(_("Answer with 'c %s text on the captcha'") % task.id) + + + def run(self): + # connect to IRC etc. + self.sock = socket.socket() + host = self.getConfig('host') + self.sock.connect((host, self.getConfig('port'))) + + if self.getConfig('ssl'): + self.sock = ssl.wrap_socket(self.sock, cert_reqs=ssl.CERT_NONE) #@TODO: support certificate + + nick = self.getConfig('nick') + self.sock.send("NICK %s\r\n" % nick) + self.sock.send("USER %s %s bla :%s\r\n" % (nick, host, nick)) + for t in self.getConfig('owner').split(): + if t.strip().startswith("#"): + self.sock.send("JOIN %s\r\n" % t.strip()) + self.logInfo(_("Connected to"), host) + self.logInfo(_("Switching to listening mode!")) + try: + self.main_loop() + + except IRCError, ex: + self.sock.send("QUIT :byebye\r\n") + traceback.print_exc() + self.sock.close() + + + def main_loop(self): + readbuffer = "" + while True: + time.sleep(1) + fdset = select([self.sock], [], [], 0) + if self.sock not in fdset[0]: + continue + + if self.abort: + raise IRCError("quit") + + readbuffer += self.sock.recv(1024) + temp = readbuffer.split("\n") + readbuffer = temp.pop() + + for line in temp: + line = line.rstrip() + first = line.split() + + if first[0] == "PING": + self.sock.send("PONG %s\r\n" % first[1]) + + if first[0] == "ERROR": + raise IRCError(line) + + msg = line.split(None, 3) + if len(msg) < 4: + continue + + msg = { + "origin": msg[0][1:], + "action": msg[1], + "target": msg[2], + "text": msg[3][1:] + } + + self.handle_events(msg) + + + def handle_events(self, msg): + if not msg['origin'].split("!", 1)[0] in self.getConfig('owner').split(): + return + + if msg['target'].split("!", 1)[0] != self.getConfig('nick'): + return + + if msg['action'] != "PRIVMSG": + return + + # HANDLE CTCP ANTI FLOOD/BOT PROTECTION + if msg['text'] == "\x01VERSION\x01": + self.logDebug("Sending CTCP VERSION") + self.sock.send("NOTICE %s :%s\r\n" % (msg['origin'], "pyLoad! IRC Interface")) + return + elif msg['text'] == "\x01TIME\x01": + self.logDebug("Sending CTCP TIME") + self.sock.send("NOTICE %s :%d\r\n" % (msg['origin'], time.time())) + return + elif msg['text'] == "\x01LAG\x01": + self.logDebug("Received CTCP LAG") #: don't know how to answer + return + + trigger = "pass" + args = None + + try: + temp = msg['text'].split() + trigger = temp[0] + if len(temp) > 1: + args = temp[1:] + except Exception: + pass + + handler = getattr(self, "event_%s" % trigger, self.event_pass) + try: + res = handler(args) + for line in res: + self.response(line, msg['origin']) + except Exception, e: + self.logError(e) + + + def response(self, msg, origin=""): + if origin == "": + for t in self.getConfig('owner').split(): + self.sock.send("PRIVMSG %s :%s\r\n" % (t.strip(), msg)) + else: + self.sock.send("PRIVMSG %s :%s\r\n" % (origin.split("!", 1)[0], msg)) + + + #### Events + + + def event_pass(self, args): + return [] + + + def event_status(self, args): + downloads = self.core.api.statusDownloads() + if not downloads: + return ["INFO: There are no active downloads currently."] + + temp_progress = "" + lines = ["ID - Name - Status - Speed - ETA - Progress"] + for data in downloads: + + if data.status == 5: + temp_progress = data.format_wait + else: + temp_progress = "%d%% (%s)" % (data.percent, data.format_size) + + lines.append("#%d - %s - %s - %s - %s - %s" % + ( + data.fid, + data.name, + data.statusmsg, + "%s/s" % formatSize(data.speed), + "%s" % data.format_eta, + temp_progress + )) + return lines + + + def event_queue(self, args): + ps = self.core.api.getQueueData() + + if not ps: + return ["INFO: There are no packages in queue."] + + lines = [] + for pack in ps: + lines.append('PACKAGE #%s: "%s" with %d links.' % (pack.pid, pack.name, len(pack.links))) + + return lines + + + def event_collector(self, args): + ps = self.core.api.getCollectorData() + if not ps: + return ["INFO: No packages in collector!"] + + lines = [] + for pack in ps: + lines.append('PACKAGE #%s: "%s" with %d links.' % (pack.pid, pack.name, len(pack.links))) + + return lines + + + def event_info(self, args): + if not args: + return ["ERROR: Use info like this: info <id>"] + + info = None + try: + info = self.core.api.getFileData(int(args[0])) + + except FileDoesNotExists: + return ["ERROR: Link doesn't exists."] + + return ['LINK #%s: %s (%s) [%s][%s]' % (info.fid, info.name, info.format_size, info.statusmsg, info.plugin)] + + + def event_packinfo(self, args): + if not args: + return ["ERROR: Use packinfo like this: packinfo <id>"] + + lines = [] + pack = None + try: + pack = self.core.api.getPackageData(int(args[0])) + + except PackageDoesNotExists: + return ["ERROR: Package doesn't exists."] + + id = args[0] + + self.more = [] + + lines.append('PACKAGE #%s: "%s" with %d links' % (id, pack.name, len(pack.links))) + for pyfile in pack.links: + self.more.append('LINK #%s: %s (%s) [%s][%s]' % (pyfile.fid, pyfile.name, pyfile.format_size, + pyfile.statusmsg, pyfile.plugin)) + + if len(self.more) < 6: + lines.extend(self.more) + self.more = [] + else: + lines.extend(self.more[:6]) + self.more = self.more[6:] + lines.append("%d more links do display." % len(self.more)) + + return lines + + + def event_more(self, args): + if not self.more: + return ["No more information to display."] + + lines = self.more[:6] + self.more = self.more[6:] + lines.append("%d more links do display." % len(self.more)) + + return lines + + + def event_start(self, args): + self.core.api.unpauseServer() + return ["INFO: Starting downloads."] + + + def event_stop(self, args): + self.core.api.pauseServer() + return ["INFO: No new downloads will be started."] + + + def event_add(self, args): + if len(args) < 2: + return ['ERROR: Add links like this: "add <packagename|id> links". ', + "This will add the link <link> to to the package <package> / the package with id <id>!"] + + pack = args[0].strip() + links = [x.strip() for x in args[1:]] + + count_added = 0 + count_failed = 0 + try: + id = int(pack) + pack = self.core.api.getPackageData(id) + if not pack: + return ["ERROR: Package doesn't exists."] + + # TODO add links + + return ["INFO: Added %d links to Package %s [#%d]" % (len(links), pack['name'], id)] + + except Exception: + # create new package + id = self.core.api.addPackage(pack, links, 1) + return ["INFO: Created new Package %s [#%d] with %d links." % (pack, id, len(links))] + + + def event_del(self, args): + if len(args) < 2: + return ["ERROR: Use del command like this: del -p|-l <id> [...] (-p indicates that the ids are from packages, -l indicates that the ids are from links)"] + + if args[0] == "-p": + ret = self.core.api.deletePackages(map(int, args[1:])) + return ["INFO: Deleted %d packages!" % len(args[1:])] + + elif args[0] == "-l": + ret = self.core.api.delLinks(map(int, args[1:])) + return ["INFO: Deleted %d links!" % len(args[1:])] + + else: + return ["ERROR: Use del command like this: del <-p|-l> <id> [...] (-p indicates that the ids are from packages, -l indicates that the ids are from links)"] + + + def event_push(self, args): + if not args: + return ["ERROR: Push package to queue like this: push <package id>"] + + id = int(args[0]) + try: + info = self.core.api.getPackageInfo(id) + except PackageDoesNotExists: + return ["ERROR: Package #%d does not exist." % id] + + self.core.api.pushToQueue(id) + return ["INFO: Pushed package #%d to queue." % id] + + + def event_pull(self, args): + if not args: + return ["ERROR: Pull package from queue like this: pull <package id>."] + + id = int(args[0]) + if not self.core.api.getPackageData(id): + return ["ERROR: Package #%d does not exist." % id] + + self.core.api.pullFromQueue(id) + return ["INFO: Pulled package #%d from queue to collector." % id] + + + def event_c(self, args): + """ captcha answer """ + if not args: + return ["ERROR: Captcha ID missing."] + + task = self.core.captchaManager.getTaskByID(args[0]) + if not task: + return ["ERROR: Captcha Task with ID %s does not exists." % args[0]] + + task.setResult(" ".join(args[1:])) + return ["INFO: Result %s saved." % " ".join(args[1:])] + + + def event_help(self, args): + lines = ["The following commands are available:", + "add <package|packid> <links> [...] Adds link to package. (creates new package if it does not exist)", + "queue Shows all packages in the queue", + "collector Shows all packages in collector", + "del -p|-l <id> [...] Deletes all packages|links with the ids specified", + "info <id> Shows info of the link with id <id>", + "packinfo <id> Shows info of the package with id <id>", + "more Shows more info when the result was truncated", + "start Starts all downloads", + "stop Stops the download (but not abort active downloads)", + "push <id> Push package to queue", + "pull <id> Pull package from queue", + "status Show general download status", + "help Shows this help message"] + return lines + + +class IRCError(Exception): + + def __init__(self, value): + self.value = value + + + def __str__(self): + return repr(self.value) diff --git a/pyload/plugin/addon/JustPremium.py b/pyload/plugin/addon/JustPremium.py new file mode 100644 index 000000000..03a2b9261 --- /dev/null +++ b/pyload/plugin/addon/JustPremium.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.Addon import Addon + + +class JustPremium(Addon): + __name = "JustPremium" + __type = "addon" + __version = "0.22" + + __config = [("excluded", "str", "Exclude hosters (comma separated)", ""), + ("included", "str", "Include hosters (comma separated)", "")] + + __description = """Remove not-premium links from added urls""" + __license = "GPLv3" + __authors = [("mazleu" , "mazleica@gmail.com"), + ("Walter Purcaro", "vuolter@gmail.com" ), + ("immenz" , "immenz@gmx.net" )] + + + event_list = ["linksAdded"] + + + def linksAdded(self, links, pid): + hosterdict = self.core.pluginManager.hosterPlugins + linkdict = self.core.api.checkURLs(links) + + premiumplugins = set(account.type for account in self.core.api.getAccounts(False) \ + if account.valid and account.premium) + multihosters = set(hoster for hoster in self.core.pluginManager.hosterPlugins \ + if 'new_name' in hosterdict[hoster] \ + and hosterdict[hoster]['new_name'] in premiumplugins) + + excluded = map(lambda domain: "".join(part.capitalize() for part in re.split(r'(\.|\d+)', domain) if part != '.'), + self.getConfig('excluded').replace(' ', '').replace(',', '|').replace(';', '|').split('|')) + included = map(lambda domain: "".join(part.capitalize() for part in re.split(r'(\.|\d+)', domain) if part != '.'), + self.getConfig('included').replace(' ', '').replace(',', '|').replace(';', '|').split('|')) + + hosterlist = (premiumplugins | multihosters).union(excluded).difference(included) + + #: Found at least one hoster with account or multihoster + if not any( True for pluginname in linkdict if pluginname in hosterlist ): + return + + for pluginname in set(linkdict.keys()) - hosterlist: + self.logInfo(_("Remove links of plugin: %s") % pluginname) + for link in linkdict[pluginname]: + self.logDebug("Remove link: %s" % link) + links.remove(link) diff --git a/pyload/plugin/addon/MergeFiles.py b/pyload/plugin/addon/MergeFiles.py new file mode 100644 index 000000000..4f95ef9d7 --- /dev/null +++ b/pyload/plugin/addon/MergeFiles.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +import os +import re +import traceback + +from pyload.plugin.Addon import Addon, threaded +from pyload.utils import fs_join + + +class MergeFiles(Addon): + __name = "MergeFiles" + __type = "addon" + __version = "0.14" + + __config = [("activated", "bool", "Activated", True)] + + __description = """Merges parts splitted with hjsplit""" + __license = "GPLv3" + __authors = [("and9000", "me@has-no-mail.com")] + + + BUFFER_SIZE = 4096 + + + @threaded + def packageFinished(self, pack): + files = {} + fid_dict = {} + for fid, data in pack.getChildren().iteritems(): + if re.search("\.\d{3}$", data['name']): + if data['name'][:-4] not in files: + files[data['name'][:-4]] = [] + files[data['name'][:-4]].append(data['name']) + files[data['name'][:-4]].sort() + fid_dict[data['name']] = fid + + download_folder = self.config.get("general", "download_folder") + + if self.config.get("general", "folder_per_package"): + download_folder = fs_join(download_folder, pack.folder) + + for name, file_list in files.iteritems(): + self.logInfo(_("Starting merging of"), name) + + with open(fs_join(download_folder, name), "wb") as final_file: + for splitted_file in file_list: + self.logDebug("Merging part", splitted_file) + + pyfile = self.core.files.getFile(fid_dict[splitted_file]) + + pyfile.setStatus("processing") + + try: + with open(fs_join(download_folder, splitted_file), "rb") as s_file: + size_written = 0 + s_file_size = int(os.path.getsize(os.path.join(download_folder, splitted_file))) + + while True: + f_buffer = s_file.read(self.BUFFER_SIZE) + if f_buffer: + final_file.write(f_buffer) + size_written += self.BUFFER_SIZE + pyfile.setProgress((size_written * 100) / s_file_size) + else: + break + + self.logDebug("Finished merging part", splitted_file) + + except Exception, e: + traceback.print_exc() + + finally: + pyfile.setProgress(100) + pyfile.setStatus("finished") + pyfile.release() + + self.logInfo(_("Finished merging of"), name) diff --git a/pyload/plugin/addon/MultiHome.py b/pyload/plugin/addon/MultiHome.py new file mode 100644 index 000000000..bca6493db --- /dev/null +++ b/pyload/plugin/addon/MultiHome.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- + +import time + +from pyload.plugin.Addon import Addon + + +class MultiHome(Addon): + __name = "MultiHome" + __type = "addon" + __version = "0.12" + + __config = [("interfaces", "str", "Interfaces", "None")] + + __description = """Ip address changer""" + __license = "GPLv3" + __authors = [("mkaay", "mkaay@mkaay.de")] + + + def setup(self): + self.register = {} + self.interfaces = [] + + self.parseInterfaces(self.getConfig('interfaces').split(";")) + + if not self.interfaces: + self.parseInterfaces([self.config.get("download", "interface")]) + self.setConfig("interfaces", self.toConfig()) + + + def toConfig(self): + return ";".join(i.adress for i in self.interfaces) + + + def parseInterfaces(self, interfaces): + for interface in interfaces: + if not interface or str(interface).lower() == "none": + continue + self.interfaces.append(Interface(interface)) + + + def activate(self): + requestFactory = self.core.requestFactory + oldGetRequest = requestFactory.getRequest + + + def getRequest(pluginName, account=None): + iface = self.bestInterface(pluginName, account) + if iface: + iface.useFor(pluginName, account) + requestFactory.iface = lambda: iface.adress + self.logDebug("Using address", iface.adress) + return oldGetRequest(pluginName, account) + + requestFactory.getRequest = getRequest + + + def bestInterface(self, pluginName, account): + best = None + for interface in self.interfaces: + if not best or interface.lastPluginAccess(pluginName, account) < best.lastPluginAccess(pluginName, account): + best = interface + return best + + +class Interface(object): + + def __init__(self, adress): + self.adress = adress + self.history = {} + + + def lastPluginAccess(self, pluginName, account): + if (pluginName, account) in self.history: + return self.history[(pluginName, account)] + return 0 + + + def useFor(self, pluginName, account): + self.history[(pluginName, account)] = time.time() + + + def __repr__(self): + return "<Interface - %s>" % self.adress diff --git a/pyload/plugin/addon/RestartFailed.py b/pyload/plugin/addon/RestartFailed.py new file mode 100644 index 000000000..8a3a36fe1 --- /dev/null +++ b/pyload/plugin/addon/RestartFailed.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.Addon import Addon + + +class RestartFailed(Addon): + __name = "RestartFailed" + __type = "addon" + __version = "1.58" + + __config = [("activated", "bool", "Activated" , True), + ("interval" , "int" , "Check interval in minutes", 90 )] + + __description = """Restart all the failed downloads in queue""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + # event_list = ["pluginConfigChanged"] + + MIN_CHECK_INTERVAL = 15 * 60 #: 15 minutes + + + # def pluginConfigChanged(self, plugin, name, value): + # if name == "interval": + # interval = value * 60 + # if self.MIN_CHECK_INTERVAL <= interval != self.interval: + # self.core.scheduler.removeJob(self.cb) + # self.interval = interval + # self.initPeriodical() + # else: + # self.logDebug("Invalid interval value, kept current") + + + def periodical(self): + self.logDebug(_("Restart failed downloads")) + self.core.api.restartFailed() + + + def activate(self): + # self.pluginConfigChanged(self.getClassName(), "interval", self.getConfig('interval')) + self.interval = max(self.MIN_CHECK_INTERVAL, self.getConfig('interval') * 60) + self.initPeriodical() diff --git a/pyload/plugin/addon/SkipRev.py b/pyload/plugin/addon/SkipRev.py new file mode 100644 index 000000000..b54e66af5 --- /dev/null +++ b/pyload/plugin/addon/SkipRev.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- + +import re +import urllib +import urlparse + +from types import MethodType + +from pyload.datatype.File import PyFile +from pyload.plugin.Addon import Addon +from pyload.plugin.Plugin import SkipDownload + + +class SkipRev(Addon): + __name = "SkipRev" + __type = "addon" + __version = "0.29" + + __config = [("mode" , "Auto;Manual", "Choose recovery archives to skip" , "Auto"), + ("revtokeep", "int" , "Number of recovery archives to keep for package", 0 )] + + __description = """Skip recovery archives (.rev)""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + @staticmethod + def _setup(self): + self.pyfile.plugin._setup() + if self.pyfile.hasStatus("skipped"): + raise SkipDownload(self.pyfile.statusname or self.pyfile.pluginname) + + + def _name(self, pyfile): + if hasattr(pyfile.pluginmodule, "getInfo"): #@NOTE: getInfo is deprecated in 0.4.10 + return pyfile.pluginmodule.getInfo([pyfile.url]).next()[0] + else: + self.logWarning("Unable to grab file name") + return urlparse.urlparse(urllib.unquote(pyfile.url)).path.split('/')[-1] + + + def _pyfile(self, link): + return PyFile(self.core.files, + link.fid, + link.url, + link.name, + link.size, + link.status, + link.error, + link.plugin, + link.packageID, + link.order) + + + def downloadPreparing(self, pyfile): + name = self._name(pyfile) + + if pyfile.statusname is _("unskipped") or not name.endswith(".rev") or not ".part" in name: + return + + revtokeep = -1 if self.getConfig('mode') == "Auto" else self.getConfig('revtokeep') + + if revtokeep: + status_list = (1, 4, 8, 9, 14) if revtokeep < 0 else (1, 3, 4, 8, 9, 14) + pyname = re.compile(r'%s\.part\d+\.rev$' % name.rsplit('.', 2)[0].replace('.', '\.')) + + queued = [True for link in self.core.api.getPackageData(pyfile.package().id).links \ + if link.status not in status_list and pyname.match(link.name)].count(True) + + if not queued or queued < revtokeep: #: keep one rev at least in auto mode + return + + pyfile.setCustomStatus("SkipRev", "skipped") + + if not hasattr(pyfile.plugin, "_setup"): + # Work-around: inject status checker inside the preprocessing routine of the plugin + pyfile.plugin._setup = pyfile.plugin.setup + pyfile.plugin.setup = MethodType(self._setup, pyfile.plugin) + + + def downloadFailed(self, pyfile): + #: Check if pyfile is still "failed", + # maybe might has been restarted in meantime + if pyfile.status != 8 or pyfile.name.rsplit('.', 1)[-1].strip() not in ("rar", "rev"): + return + + revtokeep = -1 if self.getConfig('mode') == "Auto" else self.getConfig('revtokeep') + + if not revtokeep: + return + + pyname = re.compile(r'%s\.part\d+\.rev$' % pyfile.name.rsplit('.', 2)[0].replace('.', '\.')) + + for link in self.core.api.getPackageData(pyfile.package().id).links: + if link.status is 4 and pyname.match(link.name): + pylink = self._pyfile(link) + + if revtokeep > -1 or pyfile.name.endswith(".rev"): + pylink.setStatus("queued") + else: + pylink.setCustomStatus(_("unskipped"), "queued") + + self.core.files.save() + pylink.release() + return diff --git a/pyload/plugin/addon/UnSkipOnFail.py b/pyload/plugin/addon/UnSkipOnFail.py new file mode 100644 index 000000000..048547a1b --- /dev/null +++ b/pyload/plugin/addon/UnSkipOnFail.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- + +from pyload.datatype.File import PyFile +from pyload.plugin.Addon import Addon + + +class UnSkipOnFail(Addon): + __name = "UnSkipOnFail" + __type = "addon" + __version = "0.07" + + __config = [("activated", "bool", "Activated", True)] + + __description = """Restart skipped duplicates when download fails""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + def downloadFailed(self, pyfile): + #: Check if pyfile is still "failed", + # maybe might has been restarted in meantime + if pyfile.status != 8: + return + + msg = _("Looking for skipped duplicates of: %s (pid:%s)") + self.logInfo(msg % (pyfile.name, pyfile.package().id)) + + link = self.findDuplicate(pyfile) + if link: + self.logInfo(_("Queue found duplicate: %s (pid:%s)") % (link.name, link.packageID)) + + #: Change status of "link" to "new_status". + # "link" has to be a valid FileData object, + # "new_status" has to be a valid status name + # (i.e. "queued" for this Plugin) + # It creates a temporary PyFile object using + # "link" data, changes its status, and tells + # the core.files-manager to save its data. + pylink = self._pyfile(link) + + pylink.setCustomStatus(_("unskipped"), "queued") + + self.core.files.save() + pylink.release() + + else: + self.logInfo(_("No duplicates found")) + + + def findDuplicate(self, pyfile): + """ Search all packages for duplicate links to "pyfile". + Duplicates are links that would overwrite "pyfile". + To test on duplicity the package-folder and link-name + of twolinks are compared (link.name). + So this method returns a list of all links with equal + package-folders and filenames as "pyfile", but except + the data for "pyfile" iotselöf. + It does MOT check the link's status. + """ + queue = self.core.api.getQueue() #: get packages (w/o files, as most file data is useless here) + + for package in queue: + #: check if package-folder equals pyfile's package folder + if package.folder != pyfile.package().folder: + continue + + #: now get packaged data w/ files/links + pdata = self.core.api.getPackageData(package.pid) + for link in pdata.links: + #: check if link is "skipped" + if link.status != 4: + continue + + #: check if link name collides with pdata's name + #: AND at last check if it is not pyfile itself + if link.name == pyfile.name and link.fid != pyfile.id: + return link + + + def _pyfile(self, link): + return PyFile(self.core.files, + link.fid, + link.url, + link.name, + link.size, + link.status, + link.error, + link.plugin, + link.packageID, + link.order) diff --git a/pyload/plugin/addon/UpdateManager.py b/pyload/plugin/addon/UpdateManager.py new file mode 100644 index 000000000..84d282bde --- /dev/null +++ b/pyload/plugin/addon/UpdateManager.py @@ -0,0 +1,325 @@ +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +import os +import re +import sys +import time + +from operator import itemgetter + +from pyload.network.RequestFactory import getURL +from pyload.plugin.Addon import Expose, Addon, threaded +from pyload.utils import fs_join +from pyload import __status_code__ as release_status + + +# Case-sensitive os.path.exists +def exists(path): + if os.path.exists(path): + if os.name == 'nt': + dir, name = os.path.split(path) + return name in os.listdir(dir) + else: + return True + else: + return False + + +class UpdateManager(Addon): + __name = "UpdateManager" + __type = "addon" + __version = "0.51" + + __config = [("activated", "bool", "Activated", False), + ("checkinterval", "int", "Check interval in hours", 8), + ("autorestart", "bool", + "Auto-restart pyLoad when required", True), + ("checkonstart", "bool", "Check for updates on startup", True), + ("checkperiod", "bool", + "Check for updates periodically", True), + ("reloadplugins", "bool", + "Monitor plugin code changes in debug mode", True), + ("nodebugupdate", "bool", "Don't update plugins in debug mode", False)] + + __description = """ Check for updates """ + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + SERVER_URL = "http://updatemanager.pyload.org" if release_status == 5 else None + MIN_CHECK_INTERVAL = 3 * 60 * 60 #: 3 hours + + event_list = ["allDownloadsProcessed"] + + + def activate(self): + if self.checkonstart: + self.update() + + self.initPeriodical() + + + def setup(self): + self.interval = 10 + self.info = {'pyload': False, 'version': None, 'plugins': False, 'last_check': time.time()} + self.mtimes = {} #: store modification time for each plugin + + if self.getConfig('checkonstart'): + self.core.api.pauseServer() + self.checkonstart = True + else: + self.checkonstart = False + + self.do_restart = False + + + def allDownloadsProcessed(self): + if self.do_restart is True: + self.logWarning(_("Downloads are done, restarting pyLoad to reload the updated plugins")) + self.core.api.restart() + + + def periodical(self): + if self.core.debug: + if self.getConfig('reloadplugins'): + self.autoreloadPlugins() + + if self.getConfig('nodebugupdate'): + return + + if self.getConfig('checkperiod') \ + and time.time() - max(self.MIN_CHECK_INTERVAL, self.getConfig('checkinterval') * 60 * 60) > self.info['last_check']: + self.update() + + + @Expose + def autoreloadPlugins(self): + """ reload and reindex all modified plugins """ + modules = filter( + lambda m: m and (m.__name__.startswith("pyload.plugin.") or + m.__name__.startswith("userplugins.")) and + m.__name__.count(".") >= 2, sys.modules.itervalues() + ) + + reloads = [] + + for m in modules: + root, type, name = m.__name__.rsplit(".", 2) + id = (type, name) + if type in self.core.pluginManager.plugins: + f = m.__file__.replace(".pyc", ".py") + if not os.path.isfile(f): + continue + + mtime = os.stat(f).st_mtime + + if id not in self.mtimes: + self.mtimes[id] = mtime + elif self.mtimes[id] < mtime: + reloads.append(id) + self.mtimes[id] = mtime + + return bool(self.core.pluginManager.reloadPlugins(reloads)) + + + def server_response(self): + try: + return getURL(self.SERVER_URL, get={'v': self.core.api.getServerVersion()}).splitlines() + + except Exception: + self.logWarning(_("Unable to retrieve server to get updates")) + + + @Expose + @threaded + def update(self): + """ check for updates """ + + self.core.api.pauseServer() + + if self._update() is 2 and self.getConfig('autorestart'): + if not self.core.api.statusDownloads(): + self.core.api.restart() + else: + self.do_restart = True + self.logWarning(_("Downloads are active, will restart once the download is done")) + else: + self.core.api.unpauseServer() + + + def _update(self): + data = self.server_response() + + self.info['last_check'] = time.time() + + if not data: + exitcode = 0 + + elif data[0] == "None": + self.logInfo(_("No new pyLoad version available")) + exitcode = self._updatePlugins(data[1:]) + + elif onlyplugin: + exitcode = 0 + + else: + self.logInfo(_("*** New pyLoad Version %s available ***") % data[0]) + self.logInfo(_("*** Get it here: https://github.com/pyload/pyload/releases ***")) + self.info['pyload'] = True + self.info['version'] = data[0] + exitcode = 3 + + # Exit codes: + # -1 = No plugin updated, new pyLoad version available + # 0 = No plugin updated + # 1 = Plugins updated + # 2 = Plugins updated, but restart required + return exitcode + + + def _updatePlugins(self, data): + """ check for plugin updates """ + + exitcode = 0 + updated = [] + + url = data[0] + schema = data[1].split('|') + + VERSION = re.compile(r'__version.*=.*("|\')([\d.]+)') + + if "BLACKLIST" in data: + blacklist = data[data.index('BLACKLIST') + 1:] + updatelist = data[2:data.index('BLACKLIST')] + else: + blacklist = [] + updatelist = data[2:] + + updatelist = [dict(zip(schema, x.split('|'))) for x in updatelist] + blacklist = [dict(zip(schema, x.split('|'))) for x in blacklist] + + if blacklist: + type_plugins = [(plugin['type'], plugin['name'].rsplit('.', 1)[0]) for plugin in blacklist] + + # Protect UpdateManager from self-removing + try: + type_plugins.remove(("addon", "UpdateManager")) + except ValueError: + pass + + for t, n in type_plugins: + for idx, plugin in enumerate(updatelist): + if n == plugin['name'] and t == plugin['type']: + updatelist.pop(idx) + break + + for t, n in self.removePlugins(sorted(type_plugins)): + self.logInfo(_("Removed blacklisted plugin: [%(type)s] %(name)s") % { + 'type': t, + 'name': n, + }) + + for plugin in sorted(updatelist, key=itemgetter("type", "name")): + filename = plugin['name'] + type = plugin['type'] + version = plugin['version'] + + if filename.endswith(".pyc"): + name = filename[:filename.find("_")] + else: + name = filename.replace(".py", "") + + plugins = getattr(self.core.pluginManager, "%sPlugins" % type) + + oldver = float(plugins[name]['version']) if name in plugins else None + newver = float(version) + + if not oldver: + msg = "New plugin: [%(type)s] %(name)s (v%(newver).2f)" + elif newver > oldver: + msg = "New version of plugin: [%(type)s] %(name)s (v%(oldver).2f -> v%(newver).2f)" + else: + continue + + self.logInfo(_(msg) % {'type': type, + 'name': name, + 'oldver': oldver, + 'newver': newver}) + try: + content = getURL(url % plugin) + m = VERSION.search(content) + + if m and m.group(2) == version: + with open(fs_join("userplugins", type, filename), "wb") as f: + f.write(content) + + updated.append((type, name)) + else: + raise Exception, _("Version mismatch") + + except Exception, e: + self.logError(_("Error updating plugin: %s") % filename, e) + + if updated: + self.logInfo(_("*** Plugins updated ***")) + + if self.core.pluginManager.reloadPlugins(updated): + exitcode = 1 + else: + self.logWarning(_("pyLoad restart required to reload the updated plugins")) + self.info['plugins'] = True + exitcode = 2 + + self.manager.dispatchEvent("plugin_updated", updated) + else: + self.logInfo(_("No plugin updates available")) + + # Exit codes: + # 0 = No plugin updated + # 1 = Plugins updated + # 2 = Plugins updated, but restart required + return exitcode + + + @Expose + def removePlugins(self, type_plugins): + """ delete plugins from disk """ + + if not type_plugins: + return + + removed = set() + + self.logDebug("Requested deletion of plugins: %s" % type_plugins) + + for type, name in type_plugins: + rootplugins = os.path.join(pypath, "module", "plugins") + + for dir in ("userplugins", rootplugins): + py_filename = fs_join(dir, type, name + ".py") + pyc_filename = py_filename + "c" + + if type == "addon": + try: + self.manager.deactivateAddon(name) + + except Exception, e: + self.logDebug(e) + + for filename in (py_filename, pyc_filename): + if not exists(filename): + continue + + try: + os.remove(filename) + + except OSError, e: + self.logError(_("Error removing: %s") % filename, e) + + else: + id = (type, name) + removed.add(id) + + #: return a list of the plugins successfully removed + return list(removed) diff --git a/pyload/plugin/addon/UserAgentSwitcher.py b/pyload/plugin/addon/UserAgentSwitcher.py new file mode 100644 index 000000000..31a2b763b --- /dev/null +++ b/pyload/plugin/addon/UserAgentSwitcher.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +import os +import pycurl +import random + +from pyload.plugin.Addon import Addon +from pyload.utils import fs_encode + + +class UserAgentSwitcher(Addon): + __name = "UserAgentSwitcher" + __type = "addon" + __version = "0.04" + + __config = [("activated", "bool", "Activated" , True ), + ("uaf" , "file", "Random user-agents file" , "" ), + ("uar" , "bool", "Random user-agent" , False ), + ("uas" , "str" , "Custom user-agent string", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:37.0) Gecko/20100101 Firefox/37.0")] + + __description = """Custom user-agent""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + def downloadPreparing(self, pyfile): + uar = self.getConfig('uar') + uaf = fs_encode(self.getConfig('uaf')) + + if uar and os.path.isfile(uaf): + with open(uaf) as f: + uas = random.choice([ua for ua in f.read().splitlines()]) + else: + uas = self.getConfig('uas') + + if uas: + self.logDebug("Use custom user-agent string: " + uas) + pyfile.plugin.req.http.c.setopt(pycurl.USERAGENT, uas.encode('utf-8')) diff --git a/pyload/plugin/addon/WindowsPhoneNotify.py b/pyload/plugin/addon/WindowsPhoneNotify.py new file mode 100644 index 000000000..b1d1c8b0f --- /dev/null +++ b/pyload/plugin/addon/WindowsPhoneNotify.py @@ -0,0 +1,124 @@ +# -*- coding: utf-8 -*- + +import httplib +import time + +from pyload.plugin.Addon import Addon, Expose + + +class WindowsPhoneNotify(Addon): + __name = "WindowsPhoneNotify" + __type = "addon" + __version = "0.09" + + __config = [("id" , "str" , "Push ID" , "" ), + ("url" , "str" , "Push url" , "" ), + ("notifycaptcha" , "bool", "Notify captcha request" , True ), + ("notifypackage" , "bool", "Notify package finished" , True ), + ("notifyprocessed", "bool", "Notify packages processed" , True ), + ("notifyupdate" , "bool", "Notify plugin updates" , True ), + ("notifyexit" , "bool", "Notify pyLoad shutdown" , True ), + ("sendtimewait" , "int" , "Timewait in seconds between notifications", 5 ), + ("sendpermin" , "int" , "Max notifications per minute" , 12 ), + ("ignoreclient" , "bool", "Send notifications if client is connected", False)] + + __description = """Send push notifications to Windows Phone""" + __license = "GPLv3" + __authors = [("Andy Voigt" , "phone-support@hotmail.de"), + ("Walter Purcaro", "vuolter@gmail.com" )] + + + event_list = ["allDownloadsProcessed", "plugin_updated"] + + + def setup(self): + self.last_notify = 0 + self.notifications = 0 + + + def plugin_updated(self, type_plugins): + if not self.getConfig('notifyupdate'): + return + + self.notify(_("Plugins updated"), str(type_plugins)) + + + def exit(self): + if not self.getConfig('notifyexit'): + return + + if self.core.do_restart: + self.notify(_("Restarting pyLoad")) + else: + self.notify(_("Exiting pyLoad")) + + + def newCaptchaTask(self, task): + if not self.getConfig('notifycaptcha'): + return + + self.notify(_("Captcha"), _("New request waiting user input")) + + + def packageFinished(self, pypack): + if self.getConfig('notifypackage'): + self.notify(_("Package finished"), pypack.name) + + + def allDownloadsProcessed(self): + if not self.getConfig('notifyprocessed'): + return + + if any(True for pdata in self.core.api.getQueue() if pdata.linksdone < pdata.linkstotal): + self.notify(_("Package failed"), _("One or more packages was not completed successfully")) + else: + self.notify(_("All packages finished")) + + + def getXmlData(self, msg): + return ("<?xml version='1.0' encoding='utf-8'?> <wp:Notification xmlns:wp='WPNotification'> " + "<wp:Toast> <wp:Text1>pyLoad</wp:Text1> <wp:Text2>%s</wp:Text2> " + "</wp:Toast> </wp:Notification>" % msg) + + + @Expose + def notify(self, + event, + msg="", + key=(self.getConfig('id'), self.getConfig('url'))): + + id, url = key + + if not id or not url: + return + + if self.core.isClientConnected() and not self.getConfig('ignoreclient'): + return + + elapsed_time = time.time() - self.last_notify + + if elapsed_time < self.getConf("sendtimewait"): + return + + if elapsed_time > 60: + self.notifications = 0 + + elif self.notifications >= self.getConf("sendpermin"): + return + + + request = self.getXmlData("%s: %s" % (event, msg) if msg else event) + webservice = httplib.HTTP(url) + + webservice.putrequest("POST", id) + webservice.putheader("Host", url) + webservice.putheader("Content-type", "text/xml") + webservice.putheader("X-NotificationClass", "2") + webservice.putheader("X-WindowsPhone-Target", "toast") + webservice.putheader("Content-length", "%d" % len(request)) + webservice.endheaders() + webservice.send(request) + webservice.close() + + self.last_notify = time.time() + self.notifications += 1 diff --git a/pyload/plugin/addon/XMPPInterface.py b/pyload/plugin/addon/XMPPInterface.py new file mode 100644 index 000000000..5ce5b5e8b --- /dev/null +++ b/pyload/plugin/addon/XMPPInterface.py @@ -0,0 +1,251 @@ +# -*- coding: utf-8 -*- + +from pyxmpp import streamtls +from pyxmpp.all import JID, Message +from pyxmpp.interface import implements +from pyxmpp.interfaces import * +from pyxmpp.jabber.client import JabberClient + +from pyload.plugin.addon.IRCInterface import IRCInterface + + +class XMPPInterface(IRCInterface, JabberClient): + __name = "XMPPInterface" + __type = "addon" + __version = "0.11" + + __config = [("jid" , "str" , "Jabber ID" , "user@exmaple-jabber-server.org" ), + ("pw" , "str" , "Password" , "" ), + ("tls" , "bool", "Use TLS" , False ), + ("owners" , "str" , "List of JIDs accepting commands from", "me@icq-gateway.org;some@msn-gateway.org"), + ("info_file", "bool", "Inform about every file finished" , False ), + ("info_pack", "bool", "Inform about every package finished" , True ), + ("captcha" , "bool", "Send captcha requests" , True )] + + __description = """Connect to jabber and let owner perform different tasks""" + __license = "GPLv3" + __authors = [("RaNaN", "RaNaN@pyload.org")] + + + implements(IMessageHandlersProvider) + + + def __init__(self, core, manager): + IRCInterface.__init__(self, core, manager) + + self.jid = JID(self.getConfig('jid')) + password = self.getConfig('pw') + + # if bare JID is provided add a resource -- it is required + if not self.jid.resource: + self.jid = JID(self.jid.node, self.jid.domain, "pyLoad") + + if self.getConfig('tls'): + tls_settings = streamtls.TLSSettings(require=True, verify_peer=False) + auth = ("sasl:PLAIN", "sasl:DIGEST-MD5") + else: + tls_settings = None + auth = ("sasl:DIGEST-MD5", "digest") + + # setup client with provided connection information + # and identity data + JabberClient.__init__(self, self.jid, password, + disco_name="pyLoad XMPP Client", disco_type="bot", + tls_settings=tls_settings, auth_methods=auth) + + self.interface_providers = [ + VersionHandler(self), + self, + ] + + + def activate(self): + self.new_package = {} + + self.start() + + + def packageFinished(self, pypack): + try: + if self.getConfig('info_pack'): + self.announce(_("Package finished: %s") % pypack.name) + except Exception: + pass + + + def downloadFinished(self, pyfile): + try: + if self.getConfig('info_file'): + self.announce( + _("Download finished: %(name)s @ %(plugin)s") % {"name": pyfile.name, "plugin": pyfile.pluginname}) + except Exception: + pass + + + def run(self): + # connect to IRC etc. + self.connect() + try: + self.loop() + except Exception, ex: + self.logError(ex) + + + def stream_state_changed(self, state, arg): + """This one is called when the state of stream connecting the component + to a server changes. This will usually be used to let the user + know what is going on.""" + self.logDebug("*** State changed: %s %r ***" % (state, arg)) + + + def disconnected(self): + self.logDebug("Client was disconnected") + + + def stream_closed(self, stream): + self.logDebug("Stream was closed", stream) + + + def stream_error(self, err): + self.logDebug("Stream Error", err) + + + def get_message_handlers(self): + """Return list of (message_type, message_handler) tuples. + + The handlers returned will be called when matching message is received + in a client session.""" + return [("normal", self.message)] + + + def message(self, stanza): + """Message handler for the component.""" + subject = stanza.get_subject() + body = stanza.get_body() + t = stanza.get_type() + self.logDebug("Message from %s received." % unicode(stanza.get_from())) + self.logDebug("Body: %s Subject: %s Type: %s" % (body, subject, t)) + + if t == "headline": + # 'headline' messages should never be replied to + return True + if subject: + subject = u"Re: " + subject + + to_jid = stanza.get_from() + from_jid = stanza.get_to() + + # j = JID() + to_name = to_jid.as_utf8() + from_name = from_jid.as_utf8() + + names = self.getConfig('owners').split(";") + + if to_name in names or to_jid.node + "@" + to_jid.domain in names: + messages = [] + + trigger = "pass" + args = None + + try: + temp = body.split() + trigger = temp[0] + if len(temp) > 1: + args = temp[1:] + except Exception: + pass + + handler = getattr(self, "event_%s" % trigger, self.event_pass) + try: + res = handler(args) + for line in res: + m = Message( + to_jid=to_jid, + from_jid=from_jid, + stanza_type=stanza.get_type(), + subject=subject, + body=line) + + messages.append(m) + except Exception, e: + self.logError(e) + + return messages + + else: + return True + + + def response(self, msg, origin=""): + return self.announce(msg) + + + def announce(self, message): + """ send message to all owners""" + for user in self.getConfig('owners').split(";"): + self.logDebug("Send message to", user) + + to_jid = JID(user) + + m = Message(from_jid=self.jid, + to_jid=to_jid, + stanza_type="chat", + body=message) + + stream = self.get_stream() + if not stream: + self.connect() + stream = self.get_stream() + + stream.send(m) + + + def beforeReconnecting(self, ip): + self.disconnect() + + + def afterReconnecting(self, ip): + self.connect() + + +class VersionHandler(object): + """Provides handler for a version query. + This class will answer version query and announce 'jabber:iq:version' namespace + in the client's disco#info results.""" + + implements(IIqHandlersProvider, IFeaturesProvider) + + + def __init__(self, client): + """Just remember who created this.""" + self.client = client + + + def get_features(self): + """Return namespace which should the client include in its reply to a + disco#info query.""" + return ["jabber:iq:version"] + + + def get_iq_get_handlers(self): + """Return list of tuples (element_name, namespace, handler) describing + handlers of <iq type='get'/> stanzas""" + return [("query", "jabber:iq:version", self.get_version)] + + + def get_iq_set_handlers(self): + """Return empty list, as this class provides no <iq type='set'/> stanza handler.""" + return [] + + + def get_version(self, iq): + """Handler for jabber:iq:version queries. + + jabber:iq:version queries are not supported directly by PyXMPP, so the + XML node is accessed directly through the libxml2 API. This should be + used very carefully!""" + iq = iq.make_result_response() + q = iq.new_query("jabber:iq:version") + q.newTextChild(q.ns(), "name", "Echo component") + q.newTextChild(q.ns(), "version", "1.0") + return iq diff --git a/pyload/plugin/addon/__init__.py b/pyload/plugin/addon/__init__.py new file mode 100644 index 000000000..40a96afc6 --- /dev/null +++ b/pyload/plugin/addon/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/pyload/plugin/captcha/AdYouLike.py b/pyload/plugin/captcha/AdYouLike.py new file mode 100644 index 000000000..a29524bcc --- /dev/null +++ b/pyload/plugin/captcha/AdYouLike.py @@ -0,0 +1,108 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.Captcha import Captcha +from pyload.utils import json_loads + + +class AdYouLike(Captcha): + __name = "AdYouLike" + __type = "captcha" + __version = "0.05" + + __description = """AdYouLike captcha service plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + AYL_PATTERN = r'Adyoulike\.create\s*\((.+?)\)' + CALLBACK_PATTERN = r'(Adyoulike\.g\._jsonp_\d+)' + + + def detect_key(self, html=None): + if not html: + if hasattr(self.plugin, "html") and self.plugin.html: + html = self.plugin.html + else: + errmsg = _("AdYouLike html not found") + self.plugin.fail(errmsg) + raise TypeError(errmsg) + + m = re.search(self.AYL_PATTERN, html) + n = re.search(self.CALLBACK_PATTERN, html) + if m and n: + self.key = (m.group(1).strip(), n.group(1).strip()) + self.logDebug("Ayl|callback: %s | %s" % self.key) + return self.key #: key is the tuple(ayl, callback) + else: + self.logDebug("Ayl or callback not found") + return None + + + def challenge(self, key=None, html=None): + if not key: + if self.detect_key(html): + key = self.key + else: + errmsg = _("AdYouLike key not found") + self.plugin.fail(errmsg) + raise TypeError(errmsg) + + ayl, callback = key + + # {"adyoulike":{"key":"P~zQ~O0zV0WTiAzC-iw0navWQpCLoYEP"}, + # "all":{"element_id":"ayl_private_cap_92300","lang":"fr","env":"prod"}} + ayl = json_loads(ayl) + + html = self.plugin.req.load("http://api-ayl.appspot.com/challenge", + get={'key': ayl['adyoulike']['key'], + 'env': ayl['all']['env'], + 'callback': callback}) + try: + challenge = json_loads(re.search(callback + r'\s*\((.+?)\)', html).group(1)) + + except AttributeError: + errmsg = _("AdYouLike challenge pattern not found") + self.plugin.fail(errmsg) + raise AttributeError(errmsg) + + self.logDebug("Challenge: %s" % challenge) + + return self.result(ayl, challenge), challenge + + + def result(self, server, challenge): + # Adyoulike.g._jsonp_5579316662423138 + # ({"translations":{"fr":{"instructions_visual":"Recopiez « Soonnight » ci-dessous :"}}, + # "site_under":true,"clickable":true,"pixels":{"VIDEO_050":[],"DISPLAY":[],"VIDEO_000":[],"VIDEO_100":[], + # "VIDEO_025":[],"VIDEO_075":[]},"medium_type":"image/adyoulike", + # "iframes":{"big":"<iframe src=\"http://www.soonnight.com/campagn.html\" scrolling=\"no\" + # height=\"250\" width=\"300\" frameborder=\"0\"></iframe>"},"shares":{},"id":256, + # "token":"e6QuI4aRSnbIZJg02IsV6cp4JQ9~MjA1","formats":{"small":{"y":300,"x":0,"w":300,"h":60}, + # "big":{"y":0,"x":0,"w":300,"h":250},"hover":{"y":440,"x":0,"w":300,"h":60}}, + # "tid":"SqwuAdxT1EZoi4B5q0T63LN2AkiCJBg5"}) + + if isinstance(server, basestring): + server = json_loads(server) + + if isinstance(challenge, basestring): + challenge = json_loads(challenge) + + try: + instructions_visual = challenge['translations'][server['all']['lang']]['instructions_visual'] + result = re.search(u'«(.+?)»', instructions_visual).group(1).strip() + + except AttributeError: + errmsg = _("AdYouLike result not found") + self.plugin.fail(errmsg) + raise AttributeError(errmsg) + + result = {'_ayl_captcha_engine': "adyoulike", + '_ayl_env': server['all']['env'], + '_ayl_tid': challenge['tid'], + '_ayl_token_challenge': challenge['token'], + '_ayl_response': response} + + self.logDebug("Result: %s" % result) + + return result diff --git a/pyload/plugin/captcha/AdsCaptcha.py b/pyload/plugin/captcha/AdsCaptcha.py new file mode 100644 index 000000000..c2caa7bf6 --- /dev/null +++ b/pyload/plugin/captcha/AdsCaptcha.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- + +import random +import re + +from pyload.plugin.Captcha import Captcha + + +class AdsCaptcha(Captcha): + __name = "AdsCaptcha" + __type = "captcha" + __version = "0.08" + + __description = """AdsCaptcha captcha service plugin""" + __license = "GPLv3" + __authors = [("pyLoad Team", "admin@pyload.org")] + + + CAPTCHAID_PATTERN = r'api\.adscaptcha\.com/Get\.aspx\?.*?CaptchaId=(\d+)' + PUBLICKEY_PATTERN = r'api\.adscaptcha\.com/Get\.aspx\?.*?PublicKey=([\w-]+)' + + + def detect_key(self, html=None): + if not html: + if hasattr(self.plugin, "html") and self.plugin.html: + html = self.plugin.html + else: + errmsg = _("AdsCaptcha html not found") + self.plugin.fail(errmsg) + raise TypeError(errmsg) + + m = re.search(self.PUBLICKEY_PATTERN, html) + n = re.search(self.CAPTCHAID_PATTERN, html) + if m and n: + self.key = (m.group(1).strip(), n.group(1).strip()) #: key is the tuple(PublicKey, CaptchaId) + self.logDebug("Key|id: %s | %s" % self.key) + return self.key + else: + self.logDebug("Key or id not found") + return None + + + def challenge(self, key=None, html=None): + if not key: + if self.detect_key(html): + key = self.key + else: + errmsg = _("AdsCaptcha key not found") + self.plugin.fail(errmsg) + raise TypeError(errmsg) + + PublicKey, CaptchaId = key + + html = self.plugin.req.load("http://api.adscaptcha.com/Get.aspx", + get={'CaptchaId': CaptchaId, + 'PublicKey': PublicKey}) + try: + challenge = re.search("challenge: '(.+?)',", html).group(1) + server = re.search("server: '(.+?)',", html).group(1) + + except AttributeError: + errmsg = _("AdsCaptcha challenge pattern not found") + self.plugin.fail(errmsg) + raise AttributeError(errmsg) + + self.logDebug("Challenge: %s" % challenge) + + return self.result(server, challenge), challenge + + + def result(self, server, challenge): + result = self.plugin.decryptCaptcha("%sChallenge.aspx" % server, + get={'cid': challenge, 'dummy': random.random()}, + cookies=True, + imgtype="jpg") + + self.logDebug("Result: %s" % result) + + return result diff --git a/pyload/plugin/captcha/ReCaptcha.py b/pyload/plugin/captcha/ReCaptcha.py new file mode 100644 index 000000000..dc431f2ea --- /dev/null +++ b/pyload/plugin/captcha/ReCaptcha.py @@ -0,0 +1,199 @@ +# -*- coding: utf-8 -*- + +import random +import re +import time +import urlparse + +from base64 import b64encode + +from pyload.plugin.Captcha import Captcha + + +class ReCaptcha(Captcha): + __name = "ReCaptcha" + __type = "captcha" + __version = "0.15" + + __description = """ReCaptcha captcha service plugin""" + __license = "GPLv3" + __authors = [("pyLoad Team", "admin@pyload.org"), + ("Walter Purcaro", "vuolter@gmail.com"), + ("zapp-brannigan", "fuerst.reinje@web.de")] + + KEY_V2_PATTERN = r'(?:data-sitekey=["\']|["\']sitekey["\']:\s*["\'])([\w-]+)' + KEY_V1_PATTERN = r'(?:recaptcha(?:/api|\.net)/(?:challenge|noscript)\?k=|Recaptcha\.create\s*\(\s*["\'])([\w-]+)' + + + def detect_key(self, html=None): + if not html: + if hasattr(self.plugin, "html") and self.plugin.html: + html = self.plugin.html + else: + errmsg = _("ReCaptcha html not found") + self.plugin.fail(errmsg) + raise TypeError(errmsg) + + m = re.search(self.KEY_V2_PATTERN, html) or re.search(self.KEY_V1_PATTERN, html) + if m: + self.key = m.group(1).strip() + self.logDebug("Key: %s" % self.key) + return self.key + else: + self.logDebug("Key not found") + return None + + + def challenge(self, key=None, html=None, version=None): + if not key: + if self.detect_key(html): + key = self.key + else: + errmsg = _("ReCaptcha key not found") + self.plugin.fail(errmsg) + raise TypeError(errmsg) + + if version in (1, 2): + return getattr(self, "_challenge_v%s" % version)(key) + + elif not html and hasattr(self.plugin, "html") and self.plugin.html: + version = 2 if re.search(self.KEY_V2_PATTERN, self.plugin.html) else 1 + return self.challenge(key, self.plugin.html, version) + + else: + errmsg = _("ReCaptcha html not found") + self.plugin.fail(errmsg) + raise TypeError(errmsg) + + + def _challenge_v1(self, key): + html = self.plugin.req.load("http://www.google.com/recaptcha/api/challenge", + get={'k': key}) + try: + challenge = re.search("challenge : '(.+?)',", html).group(1) + server = re.search("server : '(.+?)',", html).group(1) + + except AttributeError: + errmsg = _("ReCaptcha challenge pattern not found") + self.plugin.fail(errmsg) + raise AttributeError(errmsg) + + self.logDebug("Challenge: %s" % challenge) + + return self.result(server, challenge), challenge + + + def result(self, server, challenge): + result = self.plugin.decryptCaptcha("%simage" % server, + get={'c': challenge}, + cookies=True, + forceUser=True, + imgtype="jpg") + + self.logDebug("Result: %s" % result) + + return result + + + def _collectApiInfo(self): + html = self.plugin.req.load("http://www.google.com/recaptcha/api.js") + a = re.search(r'po.src = \'(.*?)\';', html).group(1) + vers = a.split("/")[5] + + self.logDebug("API version: %s" % vers) + + language = a.split("__")[1].split(".")[0] + + self.logDebug("API language: %s" % language) + + html = self.plugin.req.load("https://apis.google.com/js/api.js") + b = re.search(r'"h":"(.*?)","', html).group(1) + jsh = b.decode('unicode-escape') + + self.logDebug("API jsh-string: %s" % jsh) + + return vers, language, jsh + + + def _prepareTimeAndRpc(self): + self.plugin.req.load("http://www.google.com/recaptcha/api2/demo") + + millis = int(round(time.time() * 1000)) + + self.logDebug("Time: %s" % millis) + + rand = random.randint(1, 99999999) + a = "0.%s" % str(rand * 2147483647) + rpc = int(100000000 * float(a)) + + self.logDebug("Rpc-token: %s" % rpc) + + return millis, rpc + + + def _challenge_v2(self, key, parent=None): + if parent is None: + try: + parent = urlparse.urljoin("http://", urlparse.urlparse(self.plugin.pyfile.url).netloc) + + except Exception: + parent = "" + + botguardstring = "!A" + vers, language, jsh = self._collectApiInfo() + millis, rpc = self._prepareTimeAndRpc() + + html = self.plugin.req.load("https://www.google.com/recaptcha/api2/anchor", + get={'k' : key, + 'hl' : language, + 'v' : vers, + 'usegapi' : "1", + 'jsh' : "%s#id=IO_%s" % (jsh, millis), + 'parent' : parent, + 'pfname' : "", + 'rpctoken': rpc}) + + token1 = re.search(r'id="recaptcha-token" value="(.*?)">', html) + self.logDebug("Token #1: %s" % token1.group(1)) + + html = self.plugin.req.load("https://www.google.com/recaptcha/api2/frame", + get={'c' : token1.group(1), + 'hl' : language, + 'v' : vers, + 'bg' : botguardstring, + 'k' : key, + 'usegapi': "1", + 'jsh' : jsh}).decode('unicode-escape') + + token2 = re.search(r'"finput","(.*?)",', html) + self.logDebug("Token #2: %s" % token2.group(1)) + + token3 = re.search(r'"rresp","(.*?)",', html) + self.logDebug("Token #3: %s" % token3.group(1)) + + millis_captcha_loading = int(round(time.time() * 1000)) + captcha_response = self.plugin.decryptCaptcha("https://www.google.com/recaptcha/api2/payload", + get={'c':token3.group(1), 'k':key}, + cookies=True, + forceUser=True) + response = b64encode('{"response":"%s"}' % captcha_response) + + self.logDebug("Result: %s" % response) + + timeToSolve = int(round(time.time() * 1000)) - millis_captcha_loading + timeToSolveMore = timeToSolve + int(float("0." + str(random.randint(1, 99999999))) * 500) + + html = self.plugin.req.load("https://www.google.com/recaptcha/api2/userverify", + post={'k' : key, + 'c' : token3.group(1), + 'response': response, + 't' : timeToSolve, + 'ct' : timeToSolveMore, + 'bg' : botguardstring}) + + token4 = re.search(r'"uvresp","(.*?)",', html) + self.logDebug("Token #4: %s" % token4.group(1)) + + result = token4.group(1) + + return result, None diff --git a/pyload/plugin/captcha/SolveMedia.py b/pyload/plugin/captcha/SolveMedia.py new file mode 100644 index 000000000..40a198b80 --- /dev/null +++ b/pyload/plugin/captcha/SolveMedia.py @@ -0,0 +1,114 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.Captcha import Captcha + + +class SolveMedia(Captcha): + __name = "SolveMedia" + __type = "captcha" + __version = "0.12" + + __description = """SolveMedia captcha service plugin""" + __license = "GPLv3" + __authors = [("pyLoad Team", "admin@pyload.org")] + + + KEY_PATTERN = r'api\.solvemedia\.com/papi/challenge\.(?:no)?script\?k=(.+?)["\']' + + + def detect_key(self, html=None): + if not html: + if hasattr(self.plugin, "html") and self.plugin.html: + html = self.plugin.html + else: + errmsg = _("SolveMedia html not found") + self.plugin.fail(errmsg) + raise TypeError(errmsg) + + m = re.search(self.KEY_PATTERN, html) + if m: + self.key = m.group(1).strip() + self.logDebug("Key: %s" % self.key) + return self.key + else: + self.logDebug("Key not found") + return None + + + def challenge(self, key=None, html=None): + if not key: + if self.detect_key(html): + key = self.key + else: + errmsg = _("SolveMedia key not found") + self.plugin.fail(errmsg) + raise TypeError(errmsg) + + html = self.plugin.req.load("http://api.solvemedia.com/papi/challenge.noscript", + get={'k': key}) + try: + challenge = re.search(r'<input type=hidden name="adcopy_challenge" id="adcopy_challenge" value="(.+?)">', + html).group(1) + server = "http://api.solvemedia.com/papi/media" + + except AttributeError: + errmsg = _("SolveMedia challenge pattern not found") + self.plugin.fail(errmsg) + raise AttributeError(errmsg) + + self.logDebug("Challenge: %s" % challenge) + + result = self.result(server, challenge) + + try: + magic = re.search(r'name="magic" value="(.+?)"', html).group(1) + + except AttributeError: + self.logDebug("Magic code not found") + + else: + if not self._verify(key, magic, result, challenge): + self.logDebug("Captcha code was invalid") + + return result, challenge + + + def _verify(self, key, magic, result, challenge, ref=None): #@TODO: Clean up + if ref is None: + try: + ref = self.plugin.pyfile.url + + except Exception: + ref = "" + + html = self.plugin.req.load("http://api.solvemedia.com/papi/verify.noscript", + post={'adcopy_response' : result, + 'k' : key, + 'l' : "en", + 't' : "img", + 's' : "standard", + 'magic' : magic, + 'adcopy_challenge' : challenge, + 'ref' : ref}) + try: + html = self.plugin.req.load(re.search(r'URL=(.+?)">', html).group(1)) + gibberish = re.search(r'id=gibberish>(.+?)</textarea>', html).group(1) + + except Exception: + return False + + else: + return True + + + def result(self, server, challenge): + result = self.plugin.decryptCaptcha(server, + get={'c': challenge}, + cookies=True, + imgtype="gif") + + self.logDebug("Result: %s" % result) + + return result diff --git a/pyload/plugin/captcha/__init__.py b/pyload/plugin/captcha/__init__.py new file mode 100644 index 000000000..40a96afc6 --- /dev/null +++ b/pyload/plugin/captcha/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/pyload/plugin/container/CCF.py b/pyload/plugin/container/CCF.py new file mode 100644 index 000000000..024987174 --- /dev/null +++ b/pyload/plugin/container/CCF.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +import MultipartPostHandler +import re +import urllib2 + +from pyload.plugin.Container import Container +from pyload.utils import fs_encode, fs_join + + +class CCF(Container): + __name = "CCF" + __type = "container" + __version = "0.23" + + __pattern = r'.+\.ccf$' + + __description = """CCF container decrypter plugin""" + __license = "GPLv3" + __authors = [("Willnix", "Willnix@pyload.org"), + ("Walter Purcaro", "vuolter@gmail.com")] + + + def decrypt(self, pyfile): + fs_filename = fs_encode(pyfile.url.strip()) + opener = urllib2.build_opener(MultipartPostHandler.MultipartPostHandler) + + dlc_content = opener.open('http://service.jdownloader.net/dlcrypt/getDLC.php', + {'src' : "ccf", + 'filename': "test.ccf", + 'upload' : open(fs_filename, "rb")}).read() + + download_folder = self.config.get("general", "download_folder") + dlc_file = fs_join(download_folder, "tmp_%s.dlc" % pyfile.name) + + try: + dlc = re.search(r'<dlc>(.+)</dlc>', dlc_content, re.S).group(1).decode('base64') + + except AttributeError: + self.fail(_("Container is corrupted")) + + with open(dlc_file, "w") as tempdlc: + tempdlc.write(dlc) + + self.urls = [dlc_file] diff --git a/pyload/plugin/container/DLC.py b/pyload/plugin/container/DLC.py new file mode 100644 index 000000000..b2bfea30e --- /dev/null +++ b/pyload/plugin/container/DLC.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +import re +import xml.dom.minidom + +from Crypto.Cipher import AES + +from pyload.plugin.Container import Container +from pyload.utils import decode, fs_encode + + +class DLC(Container): + __name = "DLC" + __type = "container" + __version = "0.24" + + __pattern = r'.+\.dlc$' + + __description = """DLC container decrypter plugin""" + __license = "GPLv3" + __authors = [("RaNaN", "RaNaN@pyload.org"), + ("spoob", "spoob@pyload.org"), + ("mkaay", "mkaay@mkaay.de"), + ("Schnusch", "Schnusch@users.noreply.github.com"), + ("Walter Purcaro", "vuolter@gmail.com")] + + + KEY = "cb99b5cbc24db398" + IV = "9bc24cb995cb8db3" + API_URL = "http://service.jdownloader.org/dlcrypt/service.php?srcType=dlc&destType=pylo&data=%s" + + + def decrypt(self, pyfile): + fs_filename = fs_encode(pyfile.url.strip()) + with open(fs_filename) as dlc: + data = dlc.read().strip() + + data += '=' * (-len(data) % 4) + + dlc_key = data[-88:] + dlc_data = data[:-88].decode('base64') + dlc_content = self.load(self.API_URL % dlc_key) + + try: + rc = re.search(r'<rc>(.+)</rc>', dlc_content, re.S).group(1).decode('base64') + + except AttributeError: + self.fail(_("Container is corrupted")) + + key = iv = AES.new(self.KEY, AES.MODE_CBC, self.IV).decrypt(rc) + + self.data = AES.new(key, AES.MODE_CBC, iv).decrypt(dlc_data).decode('base64') + self.packages = [(name or pyfile.name, links, name or pyfile.name) \ + for name, links in self.getPackages()] + + + def getPackages(self): + root = xml.dom.minidom.parseString(self.data).documentElement + content = root.getElementsByTagName("content")[0] + return self.parsePackages(content) + + + def parsePackages(self, startNode): + return [(decode(node.getAttribute("name")).decode('base64'), self.parseLinks(node)) \ + for node in startNode.getElementsByTagName("package")] + + + def parseLinks(self, startNode): + return [node.getElementsByTagName("url")[0].firstChild.data.decode('base64') \ + for node in startNode.getElementsByTagName("file")] diff --git a/pyload/plugin/container/RSDF.py b/pyload/plugin/container/RSDF.py new file mode 100644 index 000000000..6f56ec06a --- /dev/null +++ b/pyload/plugin/container/RSDF.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +import binascii +import re + +from Crypto.Cipher import AES + +from pyload.plugin.Container import Container +from pyload.utils import fs_encode + + +class RSDF(Container): + __name = "RSDF" + __type = "container" + __version = "0.29" + + __pattern = r'.+\.rsdf$' + + __description = """RSDF container decrypter plugin""" + __license = "GPLv3" + __authors = [("RaNaN", "RaNaN@pyload.org"), + ("spoob", "spoob@pyload.org"), + ("Walter Purcaro", "vuolter@gmail.com")] + + + KEY = "8C35192D964DC3182C6F84F3252239EB4A320D2500000000" + IV = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + + + def decrypt(self, pyfile): + KEY = binascii.unhexlify(self.KEY) + IV = binascii.unhexlify(self.IV) + + iv = AES.new(KEY, AES.MODE_ECB).encrypt(IV) + cipher = AES.new(KEY, AES.MODE_CFB, iv) + + try: + fs_filename = fs_encode(pyfile.url.strip()) + with open(fs_filename, 'r') as rsdf: + data = rsdf.read() + + except IOError, e: + self.fail(e) + + if re.search(r"<title>404 - Not Found</title>", data): + pyfile.setStatus("offline") + + else: + try: + raw_links = binascii.unhexlify(''.join(data.split())).splitlines() + + except TypeError: + self.fail(_("Container is corrupted")) + + for link in raw_links: + if not link: + continue + link = cipher.decrypt(link.decode('base64')).replace('CCF: ', '') + self.urls.append(link) diff --git a/pyload/plugin/container/TXT.py b/pyload/plugin/container/TXT.py new file mode 100644 index 000000000..4ef29c0b7 --- /dev/null +++ b/pyload/plugin/container/TXT.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- + +import codecs + +from pyload.plugin.Container import Container +from pyload.utils import fs_encode + + +class TXT(Container): + __name = "TXT" + __type = "container" + __version = "0.15" + + __pattern = r'.+\.(txt|text)$' + __config = [("flush" , "bool" , "Flush list after adding", False ), + ("encoding", "string", "File encoding" , "utf-8")] + + __description = """Read link lists in plain text formats""" + __license = "GPLv3" + __authors = [("spoob", "spoob@pyload.org"), + ("jeix", "jeix@hasnomail.com")] + + + def decrypt(self, pyfile): + try: + encoding = codecs.lookup(self.getConfig('encoding')).name + + except Exception: + encoding = "utf-8" + + fs_filename = fs_encode(pyfile.url.strip()) + txt = codecs.open(fs_filename, 'r', encoding) + curPack = "Parsed links from %s" % pyfile.name + packages = {curPack:[],} + + for link in txt.readlines(): + link = link.strip() + + if not link: + continue + + if link.startswith(";"): + continue + + if link.startswith("[") and link.endswith("]"): + # new package + curPack = link[1:-1] + packages[curPack] = [] + continue + + packages[curPack].append(link) + + txt.close() + + # empty packages fix + for key, value in packages.iteritems(): + if not value: + packages.pop(key, None) + + if self.getConfig('flush'): + try: + txt = open(fs_filename, 'wb') + txt.close() + + except IOError: + self.logWarning(_("Failed to flush list")) + + for name, links in packages.iteritems(): + self.packages.append((name, links, name)) diff --git a/pyload/plugin/container/__init__.py b/pyload/plugin/container/__init__.py new file mode 100644 index 000000000..40a96afc6 --- /dev/null +++ b/pyload/plugin/container/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/pyload/plugin/crypter/BitshareCom.py b/pyload/plugin/crypter/BitshareCom.py new file mode 100644 index 000000000..2a4d17da1 --- /dev/null +++ b/pyload/plugin/crypter/BitshareCom.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.SimpleCrypter import SimpleCrypter + + +class BitshareCom(SimpleCrypter): + __name = "BitshareCom" + __type = "crypter" + __version = "0.03" + + __pattern = r'http://(?:www\.)?bitshare\.com/\?d=\w+' + __config = [("use_premium" , "bool", "Use premium account if available" , True), + ("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Bitshare.com folder decrypter plugin""" + __license = "GPLv3" + __authors = [("stickell", "l.stickell@yahoo.it")] + + + LINK_PATTERN = r'<a href="(http://bitshare\.com/files/.+)">.+</a></td>' + NAME_PATTERN = r'View public folder "(?P<N>.+)"</h1>' diff --git a/pyload/plugin/crypter/C1NeonCom.py b/pyload/plugin/crypter/C1NeonCom.py new file mode 100644 index 000000000..e0e1bda17 --- /dev/null +++ b/pyload/plugin/crypter/C1NeonCom.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadCrypter import DeadCrypter + + +class C1NeonCom(DeadCrypter): + __name = "C1NeonCom" + __type = "crypter" + __version = "0.05" + + __pattern = r'http://(?:www\.)?c1neon\.com/.+' + __config = [] + + __description = """C1neon.com decrypter plugin""" + __license = "GPLv3" + __authors = [("godofdream", "soilfiction@gmail.com")] diff --git a/pyload/plugin/crypter/ChipDe.py b/pyload/plugin/crypter/ChipDe.py new file mode 100644 index 000000000..164531ee5 --- /dev/null +++ b/pyload/plugin/crypter/ChipDe.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- + +import re +from pyload.plugin.Crypter import Crypter + + +class ChipDe(Crypter): + __name = "ChipDe" + __type = "crypter" + __version = "0.10" + + __pattern = r'http://(?:www\.)?chip\.de/video/.+\.html' + __config = [("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Chip.de decrypter plugin""" + __license = "GPLv3" + __authors = [("4Christopher", "4Christopher@gmx.de")] + + + def decrypt(self, pyfile): + self.html = self.load(pyfile.url) + try: + f = re.search(r'"(http://video\.chip\.de/.+)"', self.html) + except Exception: + self.fail(_("Failed to find the URL")) + else: + self.urls = [f.group(1)] + self.logDebug("The file URL is %s" % self.urls[0]) diff --git a/pyload/plugin/crypter/CloudzillaTo.py b/pyload/plugin/crypter/CloudzillaTo.py new file mode 100644 index 000000000..a12b2736c --- /dev/null +++ b/pyload/plugin/crypter/CloudzillaTo.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.internal.SimpleCrypter import SimpleCrypter + + +class CloudzillaTo(SimpleHoster): + __name = "CloudzillaTo" + __type = "crypter" + __version = "0.02" + + __pattern = r'http://(?:www\.)?cloudzilla\.to/share/folder/(?P<ID>[\w^_]+)' + + __description = """Cloudzilla.to folder decrypter plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + INFO_PATTERN = r'<span class="name" title="(?P<N>.+?)"' + OFFLINE_PATTERN = r'>File not found...<' + + LINK_PATTERN = r'<a href="(.+?)" class="item_href">' + + PASSWORD_PATTERN = r'<div id="pwd_protected">' + + + def checkErrors(self): + m = re.search(self.PASSWORD_PATTERN, self.html) + if m: + self.html = self.load(self.pyfile.url, get={'key': self.getPassword()}) + + if re.search(self.PASSWORD_PATTERN, self.html): + self.retry(reason="Wrong password") diff --git a/pyload/plugin/crypter/CrockoCom.py b/pyload/plugin/crypter/CrockoCom.py new file mode 100644 index 000000000..71dadd8d5 --- /dev/null +++ b/pyload/plugin/crypter/CrockoCom.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.SimpleCrypter import SimpleCrypter + + +class CrockoCom(SimpleCrypter): + __name = "CrockoCom" + __type = "crypter" + __version = "0.01" + + __pattern = r'http://(?:www\.)?crocko\.com/f/.+' + __config = [("use_premium" , "bool", "Use premium account if available" , True), + ("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Crocko.com folder decrypter plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + LINK_PATTERN = r'<td class="last"><a href="(.+?)">download</a>' diff --git a/pyload/plugin/crypter/CryptItCom.py b/pyload/plugin/crypter/CryptItCom.py new file mode 100644 index 000000000..615626f4f --- /dev/null +++ b/pyload/plugin/crypter/CryptItCom.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadCrypter import DeadCrypter + + +class CryptItCom(DeadCrypter): + __name = "CryptItCom" + __type = "crypter" + __version = "0.11" + + __pattern = r'http://(?:www\.)?crypt-it\.com/(s|e|d|c)/\w+' + __config = [] + + __description = """Crypt-it.com decrypter plugin""" + __license = "GPLv3" + __authors = [("jeix", "jeix@hasnomail.de")] diff --git a/pyload/plugin/crypter/CzshareCom.py b/pyload/plugin/crypter/CzshareCom.py new file mode 100644 index 000000000..0c106bddc --- /dev/null +++ b/pyload/plugin/crypter/CzshareCom.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- + +import re +from pyload.plugin.Crypter import Crypter + + +class CzshareCom(Crypter): + __name = "CzshareCom" + __type = "crypter" + __version = "0.20" + + __pattern = r'http://(?:www\.)?(czshare|sdilej)\.(com|cz)/folders/.+' + __config = [("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Czshare.com folder decrypter plugin, now Sdilej.cz""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + FOLDER_PATTERN = r'<tr class="subdirectory">\s*<td>\s*<table>(.*?)</table>' + LINK_PATTERN = r'<td class="col2"><a href="(.+?)">info</a></td>' + + + def decrypt(self, pyfile): + html = self.load(pyfile.url) + + m = re.search(self.FOLDER_PATTERN, html, re.S) + if m is None: + self.error(_("FOLDER_PATTERN not found")) + + self.urls.extend(re.findall(self.LINK_PATTERN, m.group(1))) diff --git a/pyload/plugin/crypter/DailymotionComFolder.py b/pyload/plugin/crypter/DailymotionComFolder.py new file mode 100644 index 000000000..06834f9a5 --- /dev/null +++ b/pyload/plugin/crypter/DailymotionComFolder.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- + +import re +import urlparse + +from pyload.utils import json_loads +from pyload.plugin.Crypter import Crypter +from pyload.utils import fs_join + + +class DailymotionComFolder(Crypter): + __name = "DailymotionComFolder" + __type = "crypter" + __version = "0.01" + + __pattern = r'https?://(?:www\.)?dailymotion\.com/((playlists/)?(?P<TYPE>playlist|user)/)?(?P<ID>[\w^_]+)(?(TYPE)|#)' + __config = [("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Dailymotion.com channel & playlist decrypter""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + def api_response(self, ref, req=None): + url = urlparse.urljoin("https://api.dailymotion.com/", ref) + html = self.load(url, get=req) + return json_loads(html) + + + def getPlaylistInfo(self, id): + ref = "playlist/" + id + req = {"fields": "name,owner.screenname"} + playlist = self.api_response(ref, req) + + if "error" in playlist: + return + + name = playlist['name'] + owner = playlist['owner.screenname'] + return name, owner + + + def _getPlaylists(self, user_id, page=1): + ref = "user/%s/playlists" % user_id + req = {"fields": "id", "page": page, "limit": 100} + user = self.api_response(ref, req) + + if "error" in user: + return + + for playlist in user['list']: + yield playlist['id'] + + if user['has_more']: + for item in self._getPlaylists(user_id, page + 1): + yield item + + + def getPlaylists(self, user_id): + return [(id,) + self.getPlaylistInfo(id) for id in self._getPlaylists(user_id)] + + + def _getVideos(self, id, page=1): + ref = "playlist/%s/videos" % id + req = {"fields": "url", "page": page, "limit": 100} + playlist = self.api_response(ref, req) + + if "error" in playlist: + return + + for video in playlist['list']: + yield video['url'] + + if playlist['has_more']: + for item in self._getVideos(id, page + 1): + yield item + + + def getVideos(self, playlist_id): + return list(self._getVideos(playlist_id))[::-1] + + + def decrypt(self, pyfile): + m = re.match(self.__pattern, pyfile.url) + m_id = m.group('ID') + m_type = m.group('TYPE') + + if m_type == "playlist": + self.logDebug("Url recognized as Playlist") + p_info = self.getPlaylistInfo(m_id) + playlists = [(m_id,) + p_info] if p_info else None + else: + self.logDebug("Url recognized as Channel") + playlists = self.getPlaylists(m_id) + self.logDebug("%s playlist\s found on channel \"%s\"" % (len(playlists), m_id)) + + if not playlists: + self.fail(_("No playlist available")) + + for p_id, p_name, p_owner in playlists: + p_videos = self.getVideos(p_id) + p_folder = fs_join(self.config.get("general", "download_folder"), p_owner, p_name) + self.logDebug("%s video\s found on playlist \"%s\"" % (len(p_videos), p_name)) + self.packages.append((p_name, p_videos, p_folder)) #: folder is NOT recognized by pyload 0.4.9! diff --git a/pyload/plugin/crypter/DataHu.py b/pyload/plugin/crypter/DataHu.py new file mode 100644 index 000000000..a6c219d29 --- /dev/null +++ b/pyload/plugin/crypter/DataHu.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.SimpleCrypter import SimpleCrypter + + +class DataHu(SimpleCrypter): + __name = "DataHu" + __type = "crypter" + __version = "0.06" + + __pattern = r'http://(?:www\.)?data\.hu/dir/\w+' + __config = [("use_premium" , "bool", "Use premium account if available" , True), + ("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Data.hu folder decrypter plugin""" + __license = "GPLv3" + __authors = [("crash", ""), + ("stickell", "l.stickell@yahoo.it")] + + + LINK_PATTERN = r'<a href=\'(http://data\.hu/get/.+)\' target=\'_blank\'>\1</a>' + NAME_PATTERN = ur'<title>(?P<N>.+) Let\xf6lt\xe9se</title>' + + + def prepare(self): + super(DataHu, self).prepare() + + if u'K\xe9rlek add meg a jelsz\xf3t' in self.html: #: Password protected + password = self.getPassword() + if not password: + self.fail(_("Password required")) + + self.logDebug("The folder is password protected', 'Using password: " + password) + + self.html = self.load(self.pyfile.url, post={'mappa_pass': password}, decode=True) + + if u'Hib\xe1s jelsz\xf3' in self.html: #: Wrong password + self.fail(_("Wrong password")) diff --git a/pyload/plugin/crypter/DdlstorageCom.py b/pyload/plugin/crypter/DdlstorageCom.py new file mode 100644 index 000000000..e3e1f1956 --- /dev/null +++ b/pyload/plugin/crypter/DdlstorageCom.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadCrypter import DeadCrypter + + +class DdlstorageCom(DeadCrypter): + __name = "DdlstorageCom" + __type = "crypter" + __version = "0.03" + + __pattern = r'https?://(?:www\.)?ddlstorage\.com/folder/\w+' + __config = [] + + __description = """DDLStorage.com folder decrypter plugin""" + __license = "GPLv3" + __authors = [("godofdream", "soilfiction@gmail.com"), + ("stickell", "l.stickell@yahoo.it")] diff --git a/pyload/plugin/crypter/DepositfilesCom.py b/pyload/plugin/crypter/DepositfilesCom.py new file mode 100644 index 000000000..d86f67426 --- /dev/null +++ b/pyload/plugin/crypter/DepositfilesCom.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.SimpleCrypter import SimpleCrypter + + +class DepositfilesCom(SimpleCrypter): + __name = "DepositfilesCom" + __type = "crypter" + __version = "0.01" + + __pattern = r'http://(?:www\.)?depositfiles\.com/folders/\w+' + __config = [("use_premium" , "bool", "Use premium account if available" , True), + ("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Depositfiles.com folder decrypter plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + LINK_PATTERN = r'<div class="progressName".*?>\s*<a href="(.+?)" title=".+?" target="_blank">' diff --git a/pyload/plugin/crypter/Dereferer.py b/pyload/plugin/crypter/Dereferer.py new file mode 100644 index 000000000..b4b52f278 --- /dev/null +++ b/pyload/plugin/crypter/Dereferer.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.Crypter import Crypter + + +class Dereferer(SimpleDereferer): + __name = "Dereferer" + __type = "crypter" + __version = "0.11" + + __pattern = r'https?://([^/]+)/.*?(?P<LINK>(ht|f)tps?(://|%3A%2F%2F).+)' + __config = [("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Crypter for dereferers""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] diff --git a/pyload/plugin/crypter/DevhostSt.py b/pyload/plugin/crypter/DevhostSt.py new file mode 100644 index 000000000..7ede22106 --- /dev/null +++ b/pyload/plugin/crypter/DevhostSt.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- +# +# Test links: +# http://d-h.st/users/shine/?fld_id=37263#files + +import re +import urlparse + +from pyload.plugin.internal.SimpleCrypter import SimpleCrypter + + +class DevhostSt(SimpleCrypter): + __name = "DevhostSt" + __type = "crypter" + __version = "0.05" + + __pattern = r'http://(?:www\.)?d-h\.st/users/(?P<USER>\w+)(/\?fld_id=(?P<ID>\d+))?' + __config = [("use_premium" , "bool", "Use premium account if available" , True), + ("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """d-h.st folder decrypter plugin""" + __license = "GPLv3" + __authors = [("zapp-brannigan", "fuerst.reinje@web.de"), + ("Walter Purcaro", "vuolter@gmail.com")] + + + LINK_PATTERN = r'(?:/> |;">)<a href="(.+?)"(?!>Back to \w+<)' + OFFLINE_PATTERN = r'"/cHP">test\.png<' + + + def checkNameSize(self, getinfo=True): + if not self.info or getinfo: + self.logDebug("File info (BEFORE): %s" % self.info) + self.info.update(self.getInfo(self.pyfile.url, self.html)) + self.logDebug("File info (AFTER): %s" % self.info) + + try: + if self.info['pattern']['ID'] == "0": + raise + + p = r'href="(.+?)">Back to \w+<' + m = re.search(p, self.html) + html = self.load(urlparse.urljoin("http://d-h.st", m.group(1)), + cookies=False) + + p = '\?fld_id=%s.*?">(.+?)<' % self.info['pattern']['ID'] + m = re.search(p, html) + self.pyfile.name = m.group(1) + + except Exception, e: + self.logDebug(e) + self.pyfile.name = self.info['pattern']['USER'] + + try: + folder = self.info['folder'] = self.pyfile.name + + except Exception: + pass + self.logDebug("File name: %s" % self.pyfile.name, + "File folder: %s" % self.pyfile.name) diff --git a/pyload/plugin/crypter/DlProtectCom.py b/pyload/plugin/crypter/DlProtectCom.py new file mode 100644 index 000000000..eae6d1d83 --- /dev/null +++ b/pyload/plugin/crypter/DlProtectCom.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- + +import re +import time + +from base64 import urlsafe_b64encode + +from pyload.plugin.internal.SimpleCrypter import SimpleCrypter + + +class DlProtectCom(SimpleCrypter): + __name = "DlProtectCom" + __type = "crypter" + __version = "0.03" + + __pattern = r'https?://(?:www\.)?dl-protect\.com/((en|fr)/)?\w+' + __config = [("use_premium" , "bool", "Use premium account if available" , True), + ("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Dl-protect.com decrypter plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + COOKIES = [("dl-protect.com", "l", "en")] + + OFFLINE_PATTERN = r'Unfortunately, the link you are looking for is not found' + + + def getLinks(self): + # Direct link with redirect + if not re.match(r"https?://(?:www\.)?dl-protect\.com/.+", self.req.http.lastEffectiveURL): + return [self.req.http.lastEffectiveURL] + + post_req = {'key' : re.search(r'name="key" value="(.+?)"', self.html).group(1), + 'submitform': ""} + + if "Please click on continue to see the content" in self.html: + post_req['submitform'] = "Continue" + self.wait(2) + + else: + mstime = int(round(time.time() * 1000)) + b64time = "_" + urlsafe_b64encode(str(mstime)).replace("=", "%3D") + + post_req.update({'i' : b64time, + 'submitform': "Decrypt+link"}) + + if "Password :" in self.html: + post_req['pwd'] = self.getPassword() + + if "Security Code" in self.html: + captcha_id = re.search(r'/captcha\.php\?uid=(.+?)"', self.html).group(1) + captcha_url = "http://www.dl-protect.com/captcha.php?uid=" + captcha_id + captcha_code = self.decryptCaptcha(captcha_url, imgtype="gif") + + post_req['secure'] = captcha_code + + self.html = self.load(self.pyfile.url, post=post_req) + + for errmsg in ("The password is incorrect", "The security code is incorrect"): + if errmsg in self.html: + self.fail(_(errmsg[1:])) + + return re.findall(r'<a href="([^/].+?)" target="_blank">', self.html) diff --git a/pyload/plugin/crypter/DontKnowMe.py b/pyload/plugin/crypter/DontKnowMe.py new file mode 100644 index 000000000..e4edd8129 --- /dev/null +++ b/pyload/plugin/crypter/DontKnowMe.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.Crypter import Crypter + + +class DontKnowMe(SimpleDereferer): + __name = "DontKnowMe" + __type = "crypter" + __version = "0.11" + + __pattern = r'http://(?:www\.)?dontknow\.me/at/\?(?P<LINK>.+)' + __config = [("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """DontKnow.me decrypter plugin""" + __license = "GPLv3" + __authors = [("selaux", "")] diff --git a/pyload/plugin/crypter/DuckCryptInfo.py b/pyload/plugin/crypter/DuckCryptInfo.py new file mode 100644 index 000000000..3463d44f9 --- /dev/null +++ b/pyload/plugin/crypter/DuckCryptInfo.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- + +import re + +from BeautifulSoup import BeautifulSoup + +from pyload.plugin.Crypter import Crypter + + +class DuckCryptInfo(Crypter): + __name = "DuckCryptInfo" + __type = "crypter" + __version = "0.02" + + __pattern = r'http://(?:www\.)?duckcrypt\.info/(folder|wait|link)/(\w+)/?(\w*)' + __config = [("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """DuckCrypt.info decrypter plugin""" + __license = "GPLv3" + __authors = [("godofdream", "soilfiction@gmail.com")] + + + TIMER_PATTERN = r'<span id="timer">(.*)</span>' + + + def decrypt(self, pyfile): + url = pyfile.url + + m = re.match(self.__pattern, url) + if m is None: + self.fail(_("Weird error in link")) + if str(m.group(1)) == "link": + self.handleLink(url) + else: + self.handleFolder(m) + + + def handleFolder(self, m): + html = self.load("http://duckcrypt.info/ajax/auth.php?hash=" + str(m.group(2))) + m = re.match(self.__pattern, html) + self.logDebug("Redirectet to " + str(m.group(0))) + html = self.load(str(m.group(0))) + soup = BeautifulSoup(html) + cryptlinks = soup.findAll("div", attrs={"class": "folderbox"}) + self.logDebug("Redirectet to " + str(cryptlinks)) + if not cryptlinks: + self.error(_("No link found")) + for clink in cryptlinks: + if clink.find("a"): + self.handleLink(clink.find("a")['href']) + + + def handleLink(self, url): + html = self.load(url) + soup = BeautifulSoup(html) + self.urls = [soup.find("iframe")['src']] + if not self.urls: + self.logInfo(_("No link found")) diff --git a/pyload/plugin/crypter/DuploadOrg.py b/pyload/plugin/crypter/DuploadOrg.py new file mode 100644 index 000000000..445318ccc --- /dev/null +++ b/pyload/plugin/crypter/DuploadOrg.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadCrypter import DeadCrypter + + +class DuploadOrg(DeadCrypter): + __name = "DuploadOrg" + __type = "crypter" + __version = "0.02" + + __pattern = r'http://(?:www\.)?dupload\.org/folder/\d+' + __config = [] + + __description = """Dupload.org folder decrypter plugin""" + __license = "GPLv3" + __authors = [("stickell", "l.stickell@yahoo.it")] diff --git a/pyload/plugin/crypter/EasybytezCom.py b/pyload/plugin/crypter/EasybytezCom.py new file mode 100644 index 000000000..06a958729 --- /dev/null +++ b/pyload/plugin/crypter/EasybytezCom.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSCrypter import XFSCrypter + + +class EasybytezCom(XFSCrypter): + __name = "EasybytezCom" + __type = "crypter" + __version = "0.10" + + __pattern = r'http://(?:www\.)?easybytez\.com/users/\d+/\d+' + __config = [("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Easybytez.com folder decrypter plugin""" + __license = "GPLv3" + __authors = [("stickell", "l.stickell@yahoo.it")] + + + LOGIN_ACCOUNT = True diff --git a/pyload/plugin/crypter/EmbeduploadCom.py b/pyload/plugin/crypter/EmbeduploadCom.py new file mode 100644 index 000000000..942627ad9 --- /dev/null +++ b/pyload/plugin/crypter/EmbeduploadCom.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- + +import re +from pyload.plugin.Crypter import Crypter +from pyload.network.HTTPRequest import BadHeader + + +class EmbeduploadCom(Crypter): + __name = "EmbeduploadCom" + __type = "crypter" + __version = "0.02" + + __pattern = r'http://(?:www\.)?embedupload\.com/\?d=.+' + __config = [("use_subfolder" , "bool", "Save package to subfolder" , True ), + ("subfolder_per_pack", "bool", "Create a subfolder for each package" , True ), + ("preferedHoster" , "str" , "Prefered hoster list (bar-separated)", "embedupload"), + ("ignoredHoster" , "str" , "Ignored hoster list (bar-separated)" , "" )] + + __description = """EmbedUpload.com decrypter plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + LINK_PATTERN = r'<div id="(.+?)".*?>\s*<a href="(.+?)" target="_blank" (?:class="DownloadNow"|style="color:red")>' + + + def decrypt(self, pyfile): + self.html = self.load(pyfile.url, decode=True) + tmp_links = [] + + m = re.findall(self.LINK_PATTERN, self.html) + if m: + prefered_set = set(self.getConfig('preferedHoster').split('|')) + prefered_set = map(lambda s: s.lower().split('.')[0], prefered_set) + + self.logDebug("PF: %s" % prefered_set) + + tmp_links.extend(x[1] for x in m if x[0] in prefered_set) + self.urls = self.getLocation(tmp_links) + + if not self.urls: + ignored_set = set(self.getConfig('ignoredHoster').split('|')) + ignored_set = map(lambda s: s.lower().split('.')[0], ignored_set) + + self.logDebug("IG: %s" % ignored_set) + + tmp_links.extend(x[1] for x in m if x[0] not in ignored_set) + self.urls = self.getLocation(tmp_links) + + + def getLocation(self, tmp_links): + new_links = [] + for link in tmp_links: + try: + header = self.load(link, just_header=True) + if 'location' in header: + new_links.append(header['location']) + except BadHeader: + pass + return new_links diff --git a/pyload/plugin/crypter/FilebeerInfo.py b/pyload/plugin/crypter/FilebeerInfo.py new file mode 100644 index 000000000..5ec059375 --- /dev/null +++ b/pyload/plugin/crypter/FilebeerInfo.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadCrypter import DeadCrypter + + +class FilebeerInfo(DeadCrypter): + __name = "FilebeerInfo" + __type = "crypter" + __version = "0.02" + + __pattern = r'http://(?:www\.)?filebeer\.info/\d*~f\w+' + __config = [] + + __description = """Filebeer.info folder decrypter plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] diff --git a/pyload/plugin/crypter/FilecloudIo.py b/pyload/plugin/crypter/FilecloudIo.py new file mode 100644 index 000000000..91e7dfc3a --- /dev/null +++ b/pyload/plugin/crypter/FilecloudIo.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.SimpleCrypter import SimpleCrypter + + +class FilecloudIo(SimpleCrypter): + __name = "FilecloudIo" + __type = "crypter" + __version = "0.03" + + __pattern = r'https?://(?:www\.)?(filecloud\.io|ifile\.it)/_\w+' + __config = [("use_premium" , "bool", "Use premium account if available" , True), + ("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Filecloud.io folder decrypter plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + LINK_PATTERN = r'href="(http://filecloud\.io/\w+)" title' + NAME_PATTERN = r'>(?P<N>.+?) - filecloud\.io<' diff --git a/pyload/plugin/crypter/FilecryptCc.py b/pyload/plugin/crypter/FilecryptCc.py new file mode 100644 index 000000000..db939357a --- /dev/null +++ b/pyload/plugin/crypter/FilecryptCc.py @@ -0,0 +1,179 @@ +# -*- coding: utf-8 -*- +# +# Test links: +# http://filecrypt.cc/Container/64E039F859.html + +import binascii +import re +import urlparse + +from Crypto.Cipher import AES + +from pyload.plugin.Crypter import Crypter +from pyload.plugin.captcha.ReCaptcha import ReCaptcha + + +class FilecryptCc(Crypter): + __name = "FilecryptCc" + __type = "crypter" + __version = "0.14" + + __pattern = r'https?://(?:www\.)?filecrypt\.cc/Container/\w+' + + __description = """Filecrypt.cc decrypter plugin""" + __license = "GPLv3" + __authors = [("zapp-brannigan", "fuerst.reinje@web.de")] + + + # URL_REPLACEMENTS = [(r'.html$', ""), (r'$', ".html")] #@TODO: Extend SimpleCrypter + + DLC_LINK_PATTERN = r'<button class="dlcdownload" type="button" title="Download \*.dlc" onclick="DownloadDLC\(\'(.+)\'\);"><i></i><span>dlc<' + WEBLINK_PATTERN = r"openLink.?'([\w_-]*)'," + + CAPTCHA_PATTERN = r'<img id="nc" src="(.+?)"' + CIRCLE_CAPTCHA_PATTERN = r'<input type="image" src="(.+?)"' + + MIRROR_PAGE_PATTERN = r'"[\w]*" href="(https?://(?:www\.)?filecrypt.cc/Container/\w+\.html\?mirror=\d+)">' + + + def setup(self): + self.links = [] + + + def decrypt(self, pyfile): + self.html = self.load(pyfile.url) + self.base_url = self.pyfile.url.split("Container")[0] + + if "content notfound" in self.html: #@NOTE: "content notfound" is NOT a typo + self.offline() + + self.handlePasswordProtection() + self.handleCaptcha() + self.handleMirrorPages() + + for handle in (self.handleCNL, self.handleWeblinks, self.handleDlcContainer): + handle() + if self.links: + self.packages = [(pyfile.package().name, self.links, pyfile.package().name)] + return + + + def handleMirrorPages(self): + if "mirror=" not in self.siteWithLinks: + return + + mirror = re.findall(self.MIRROR_PAGE_PATTERN, self.siteWithLinks) + + self.logInfo(_("Found %d mirrors") % len(mirror)) + + for i in mirror[1:]: + self.siteWithLinks = self.siteWithLinks + self.load(i).decode("utf-8", "replace") + + + def handlePasswordProtection(self): + if '<input type="text" name="password"' not in self.html: + return + + self.logInfo(_("Folder is password protected")) + + password = self.getPassword() + + if not password: + self.fail(_("Please enter the password in package section and try again")) + + self.html = self.load(self.pyfile.url, post={"password": password}) + + + def handleCaptcha(self): + m = re.search(self.CAPTCHA_PATTERN, self.html) + m2 = re.search(self.CIRCLE_CAPTCHA_PATTERN, self.html) + + if m: #: normal captcha + self.logDebug("Captcha-URL: %s" % m.group(1)) + + captcha_code = self.decryptCaptcha(urlparse.urljoin(self.base_url, m.group(1)), + forceUser=True, + imgtype="gif") + + self.siteWithLinks = self.load(self.pyfile.url, + post={'recaptcha_response_field': captcha_code}, + decode=True) + elif m2: #: circle captcha + self.logDebug("Captcha-URL: %s" % m2.group(1)) + + captcha_code = self.decryptCaptcha('%s%s?c=abc' %(self.base_url, m2.group(1)), + result_type='positional') + + self.siteWithLinks = self.load(self.pyfile.url, + post={'button.x': captcha_code[0], 'button.y': captcha_code[1]}, + decode=True) + + else: + recaptcha = ReCaptcha(self) + captcha_key = recaptcha.detect_key() + + if captcha_key: + response, challenge = recaptcha.challenge(captcha_key) + self.siteWithLinks = self.load(self.pyfile.url, + post={'g-recaptcha-response': response}, + decode=True) + else: + self.logInfo(_("No captcha found")) + self.siteWithLinks = self.html + + if "recaptcha_image" in self.siteWithLinks or "data-sitekey" in self.siteWithLinks: + self.invalidCaptcha() + self.retry() + + + def handleDlcContainer(self): + dlc = re.findall(self.DLC_LINK_PATTERN, self.siteWithLinks) + + if not dlc: + return + + for i in dlc: + self.links.append("%s/DLC/%s.dlc" % (self.base_url, i)) + + + def handleWeblinks(self): + try: + weblinks = re.findall(self.WEBLINK_PATTERN, self.siteWithLinks) + + for link in weblinks: + res = self.load("%s/Link/%s.html" % (self.base_url, link)) + link2 = re.search('<iframe noresize src="(.*)"></iframe>', res) + res2 = self.load(link2.group(1), just_header=True) + self.links.append(res2['location']) + + except Exception, e: + self.logDebug("Error decrypting weblinks: %s" % e) + + + def handleCNL(self): + try: + vjk = re.findall('<input type="hidden" name="jk" value="function f\(\){ return \'(.*)\';}">', self.siteWithLinks) + vcrypted = re.findall('<input type="hidden" name="crypted" value="(.*)">', self.siteWithLinks) + + for i in xrange(len(vcrypted)): + self.links.extend(self._getLinks(vcrypted[i], vjk[i])) + + except Exception, e: + self.logDebug("Error decrypting CNL: %s" % e) + + + def _getLinks(self, crypted, jk): + # Get key + key = binascii.unhexlify(str(jk)) + + # Decrypt + Key = key + IV = key + obj = AES.new(Key, AES.MODE_CBC, IV) + text = obj.decrypt(crypted.decode('base64')) + + # Extract links + text = text.replace("\x00", "").replace("\r", "") + links = filter(bool, text.split('\n')) + + return links diff --git a/pyload/plugin/crypter/FilefactoryCom.py b/pyload/plugin/crypter/FilefactoryCom.py new file mode 100644 index 000000000..0b6290273 --- /dev/null +++ b/pyload/plugin/crypter/FilefactoryCom.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.SimpleCrypter import SimpleCrypter + + +class FilefactoryCom(SimpleCrypter): + __name = "FilefactoryCom" + __type = "crypter" + __version = "0.32" + + __pattern = r'https?://(?:www\.)?filefactory\.com/(?:f|folder)/\w+' + __config = [("use_premium" , "bool", "Use premium account if available" , True), + ("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Filefactory.com folder decrypter plugin""" + __license = "GPLv3" + __authors = [("stickell", "l.stickell@yahoo.it")] + + + COOKIES = [("filefactory.com", "locale", "en_US.utf8")] + + LINK_PATTERN = r'<td>\s*<a href="(.+?)"' + NAME_PATTERN = r'<h1>Files in <span>(?P<N>.+?)<' + PAGES_PATTERN = r'data-paginator-totalPages="(\d+)' + + + def loadPage(self, page_n): + return self.load(self.pyfile.url, get={'page': page_n, 'show': 100}) diff --git a/pyload/plugin/crypter/FilerNet.py b/pyload/plugin/crypter/FilerNet.py new file mode 100644 index 000000000..a57f2908a --- /dev/null +++ b/pyload/plugin/crypter/FilerNet.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.SimpleCrypter import SimpleCrypter + + +class FilerNet(SimpleCrypter): + __name = "FilerNet" + __type = "crypter" + __version = "0.42" + + __pattern = r'https?://filer\.net/folder/\w{16}' + __config = [("use_premium" , "bool", "Use premium account if available" , True), + ("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Filer.net decrypter plugin""" + __license = "GPLv3" + __authors = [("nath_schwarz", "nathan.notwhite@gmail.com"), + ("stickell", "l.stickell@yahoo.it")] + + + LINK_PATTERN = r'href="(/get/\w{16})">(?!<)' + + NAME_PATTERN = r'<h3>(?P<N>.+?) - <small' + OFFLINE_PATTERN = r'Nicht gefunden' diff --git a/pyload/plugin/crypter/FileserveCom.py b/pyload/plugin/crypter/FileserveCom.py new file mode 100644 index 000000000..bf593b545 --- /dev/null +++ b/pyload/plugin/crypter/FileserveCom.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.Crypter import Crypter + + +class FileserveCom(Crypter): + __name = "FileserveCom" + __type = "crypter" + __version = "0.11" + + __pattern = r'http://(?:www\.)?fileserve\.com/list/\w+' + __config = [("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """FileServe.com folder decrypter plugin""" + __license = "GPLv3" + __authors = [("fionnc", "fionnc@gmail.com")] + + + FOLDER_PATTERN = r'<table class="file_list">(.*?)</table>' + LINK_PATTERN = r'<a href="(.+?)" class="sheet_icon wbold">' + + + def decrypt(self, pyfile): + html = self.load(pyfile.url) + + new_links = [] + + folder = re.search(self.FOLDER_PATTERN, html, re.S) + if folder is None: + self.error(_("FOLDER_PATTERN not found")) + + new_links.extend(re.findall(self.LINK_PATTERN, folder.group(1))) + + if new_links: + self.urls = [map(lambda s: "http://fileserve.com%s" % s, new_links)] diff --git a/pyload/plugin/crypter/FilesonicCom.py b/pyload/plugin/crypter/FilesonicCom.py new file mode 100644 index 000000000..5114d03b8 --- /dev/null +++ b/pyload/plugin/crypter/FilesonicCom.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadCrypter import DeadCrypter + + +class FilesonicCom(DeadCrypter): + __name = "FilesonicCom" + __type = "crypter" + __version = "0.12" + + __pattern = r'http://(?:www\.)?filesonic\.com/folder/\w+' + __config = [] + + __description = """Filesonic.com folder decrypter plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] diff --git a/pyload/plugin/crypter/FilestubeCom.py b/pyload/plugin/crypter/FilestubeCom.py new file mode 100644 index 000000000..fadeddedc --- /dev/null +++ b/pyload/plugin/crypter/FilestubeCom.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.SimpleCrypter import SimpleCrypter + + +class FilestubeCom(SimpleCrypter): + __name = "FilestubeCom" + __type = "crypter" + __version = "0.05" + + __pattern = r'http://(?:www\.)?filestube\.(?:com|to)/\w+' + __config = [("use_premium" , "bool", "Use premium account if available" , True), + ("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Filestube.com decrypter plugin""" + __license = "GPLv3" + __authors = [("stickell", "l.stickell@yahoo.it")] + + + LINK_PATTERN = r'<a class=\"file-link-main(?: noref)?\" [^>]* href=\"(http://[^\"]+)' + NAME_PATTERN = r'<h1\s*> (?P<N>.+) download\s*</h1>' diff --git a/pyload/plugin/crypter/FiletramCom.py b/pyload/plugin/crypter/FiletramCom.py new file mode 100644 index 000000000..4d6e898b4 --- /dev/null +++ b/pyload/plugin/crypter/FiletramCom.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.SimpleCrypter import SimpleCrypter + + +class FiletramCom(SimpleCrypter): + __name = "FiletramCom" + __type = "crypter" + __version = "0.03" + + __pattern = r'http://(?:www\.)?filetram\.com/[^/]+/.+' + __config = [("use_premium" , "bool", "Use premium account if available" , True), + ("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Filetram.com decrypter plugin""" + __license = "GPLv3" + __authors = [("igel", "igelkun@myopera.com"), + ("stickell", "l.stickell@yahoo.it")] + + + LINK_PATTERN = r'\s+(http://.+)' + NAME_PATTERN = r'<title>(?P<N>.+?) - Free Download' diff --git a/pyload/plugin/crypter/FiredriveCom.py b/pyload/plugin/crypter/FiredriveCom.py new file mode 100644 index 000000000..be3a9ebdb --- /dev/null +++ b/pyload/plugin/crypter/FiredriveCom.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadCrypter import DeadCrypter + + +class FiredriveCom(DeadCrypter): + __name = "FiredriveCom" + __type = "crypter" + __version = "0.03" + + __pattern = r'https?://(?:www\.)?(firedrive|putlocker)\.com/share/.+' + __config = [] + + __description = """Firedrive.com folder decrypter plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] diff --git a/pyload/plugin/crypter/FourChanOrg.py b/pyload/plugin/crypter/FourChanOrg.py new file mode 100644 index 000000000..6bafc6eb4 --- /dev/null +++ b/pyload/plugin/crypter/FourChanOrg.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- +# +# Based on 4chandl by Roland Beermann (https://gist.github.com/enkore/3492599) + +import re + +from pyload.plugin.Crypter import Crypter + + +class FourChanOrg(Crypter): + __name = "FourChanOrg" + __type = "crypter" + __version = "0.31" + + __pattern = r'http://(?:www\.)?boards\.4chan\.org/\w+/res/(\d+)' + __config = [("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """4chan.org folder decrypter plugin""" + __license = "GPLv3" + __authors = [] + + + def decrypt(self, pyfile): + pagehtml = self.load(pyfile.url) + images = set(re.findall(r'(images\.4chan\.org/[^/]*/src/[^"<]+)', pagehtml)) + self.urls = ["http://" + image for image in images] diff --git a/pyload/plugin/crypter/FreakhareCom.py b/pyload/plugin/crypter/FreakhareCom.py new file mode 100644 index 000000000..a0c19ac04 --- /dev/null +++ b/pyload/plugin/crypter/FreakhareCom.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.internal.SimpleCrypter import SimpleCrypter + + +class FreakhareCom(SimpleCrypter): + __name = "FreakhareCom" + __type = "crypter" + __version = "0.03" + + __pattern = r'http://(?:www\.)?freakshare\.com/folder/.+' + __config = [("use_premium" , "bool", "Use premium account if available" , True), + ("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Freakhare.com folder decrypter plugin""" + __license = "GPLv3" + __authors = [("stickell", "l.stickell@yahoo.it")] + + + LINK_PATTERN = r'<a href="(http://freakshare\.com/files/.+?)" target="_blank">' + NAME_PATTERN = r'Folder:</b> (?P<N>.+)' + PAGES_PATTERN = r'Pages: +(\d+)' + + + def loadPage(self, page_n): + if not hasattr(self, 'f_id') and not hasattr(self, 'f_md5'): + m = re.search(r'http://freakshare.com/\?x=folder&f_id=(\d+)&f_md5=(\w+)', self.html) + if m: + self.f_id = m.group(1) + self.f_md5 = m.group(2) + return self.load('http://freakshare.com/', get={'x': 'folder', + 'f_id': self.f_id, + 'f_md5': self.f_md5, + 'entrys': '20', + 'page': page_n - 1, + 'order': ''}, decode=True) diff --git a/pyload/plugin/crypter/FreetexthostCom.py b/pyload/plugin/crypter/FreetexthostCom.py new file mode 100644 index 000000000..1f82c8ffa --- /dev/null +++ b/pyload/plugin/crypter/FreetexthostCom.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.internal.SimpleCrypter import SimpleCrypter + + +class FreetexthostCom(SimpleCrypter): + __name = "FreetexthostCom" + __type = "crypter" + __version = "0.01" + + __pattern = r'http://(?:www\.)?freetexthost\.com/\w+' + __config = [("use_premium" , "bool", "Use premium account if available" , True), + ("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Freetexthost.com decrypter plugin""" + __license = "GPLv3" + __authors = [("stickell", "l.stickell@yahoo.it")] + + + def getLinks(self): + m = re.search(r'<div id="contentsinner">\s*(.+)<div class="viewcount">', self.html, re.S) + if m is None: + self.error(_("Unable to extract links")) + links = m.group(1) + return links.strip().split("<br />\r\n") diff --git a/pyload/plugin/crypter/FshareVn.py b/pyload/plugin/crypter/FshareVn.py new file mode 100644 index 000000000..09adfee3e --- /dev/null +++ b/pyload/plugin/crypter/FshareVn.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.SimpleCrypter import SimpleCrypter + + +class FshareVn(SimpleCrypter): + __name = "FshareVn" + __type = "crypter" + __version = "0.01" + + __pattern = r'http://(?:www\.)?fshare\.vn/folder/.+' + __config = [("use_premium" , "bool", "Use premium account if available" , True), + ("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Fshare.vn folder decrypter plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + LINK_PATTERN = r'<li class="w_80pc"><a href="(.+?)" target="_blank">' diff --git a/pyload/plugin/crypter/Go4UpCom.py b/pyload/plugin/crypter/Go4UpCom.py new file mode 100644 index 000000000..eef9efddc --- /dev/null +++ b/pyload/plugin/crypter/Go4UpCom.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- + +import re +import urlparse + +from pyload.plugin.internal.SimpleCrypter import SimpleCrypter + + +class Go4UpCom(SimpleCrypter): + __name = "Go4UpCom" + __type = "crypter" + __version = "0.12" + + __pattern = r'http://go4up\.com/(dl/\w{12}|rd/\w{12}/\d+)' + __config = [("use_premium" , "bool", "Use premium account if available" , True), + ("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Go4Up.com decrypter plugin""" + __license = "GPLv3" + __authors = [("rlindner81", "rlindner81@gmail.com"), + ("Walter Purcaro", "vuolter@gmail.com")] + + + LINK_PATTERN = r'(http://go4up\.com/rd/.+?)<' + + NAME_PATTERN = r'<title>Download (.+?)<' + + OFFLINE_PATTERN = r'>\s*(404 Page Not Found|File not Found|Mirror does not exist)' + + + def getLinks(self): + links = [] + + m = re.search(r'(/download/gethosts/.+?)"', self.html) + if m: + self.html = self.load(urlparse.urljoin("http://go4up.com/", m.group(1))) + pages = [self.load(url) for url in re.findall(self.LINK_PATTERN, self.html)] + else: + pages = [self.html] + + for html in pages: + try: + links.append(re.search(r'<b><a href="(.+?)"', html).group(1)) + except Exception: + continue + + return links diff --git a/pyload/plugin/crypter/GooGl.py b/pyload/plugin/crypter/GooGl.py new file mode 100644 index 000000000..552a9ea01 --- /dev/null +++ b/pyload/plugin/crypter/GooGl.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.Crypter import Crypter +from pyload.utils import json_loads + + +class GooGl(Crypter): + __name = "GooGl" + __type = "crypter" + __version = "0.01" + + __pattern = r'https?://(?:www\.)?goo\.gl/\w+' + __config = [("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Goo.gl decrypter plugin""" + __license = "GPLv3" + __authors = [("stickell", "l.stickell@yahoo.it")] + + + API_URL = "https://www.googleapis.com/urlshortener/v1/url" + + + def decrypt(self, pyfile): + rep = self.load(self.API_URL, get={'shortUrl': pyfile.url}) + self.logDebug("JSON data: " + rep) + rep = json_loads(rep) + + if 'longUrl' in rep: + self.urls = [rep['longUrl']] + else: + self.fail(_("Unable to expand shortened link")) diff --git a/pyload/plugin/crypter/HoerbuchIn.py b/pyload/plugin/crypter/HoerbuchIn.py new file mode 100644 index 000000000..500dad8cc --- /dev/null +++ b/pyload/plugin/crypter/HoerbuchIn.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- + +import re + +from BeautifulSoup import BeautifulSoup, BeautifulStoneSoup + +from pyload.plugin.Crypter import Crypter + + +class HoerbuchIn(Crypter): + __name = "HoerbuchIn" + __type = "crypter" + __version = "0.60" + + __pattern = r'http://(?:www\.)?hoerbuch\.in/(wp/horbucher/\d+/.+/|tp/out\.php\?.+|protection/folder_\d+\.html)' + __config = [("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Hoerbuch.in decrypter plugin""" + __license = "GPLv3" + __authors = [("spoob", "spoob@pyload.org"), + ("mkaay", "mkaay@mkaay.de")] + + + article = re.compile("http://(?:www\.)?hoerbuch\.in/wp/horbucher/\d+/.+/") + protection = re.compile("http://(?:www\.)?hoerbuch\.in/protection/folder_\d+.html") + + + def decrypt(self, pyfile): + self.pyfile = pyfile + + if self.article.match(pyfile.url): + html = self.load(pyfile.url) + soup = BeautifulSoup(html, convertEntities=BeautifulStoneSoup.HTML_ENTITIES) + + abookname = soup.find("a", attrs={"rel": "bookmark"}).text + for a in soup.findAll("a", attrs={"href": self.protection}): + package = "%s (%s)" % (abookname, a.previousSibling.previousSibling.text[:-1]) + links = self.decryptFolder(a['href']) + + self.packages.append((package, links, package)) + else: + self.urls = self.decryptFolder(pyfile.url) + + + def decryptFolder(self, url): + m = self.protection.search(url) + if m is None: + self.fail(_("Bad URL")) + url = m.group(0) + + self.pyfile.url = url + html = self.load(url, post={"viewed": "adpg"}) + + links = [] + pattern = re.compile("http://www\.hoerbuch\.in/protection/(\w+)/(.*?)\"") + for hoster, lid in pattern.findall(html): + self.req.lastURL = url + self.load("http://www.hoerbuch.in/protection/%s/%s" % (hoster, lid)) + links.append(self.req.lastEffectiveURL) + + return links diff --git a/pyload/plugin/crypter/HotfileCom.py b/pyload/plugin/crypter/HotfileCom.py new file mode 100644 index 000000000..8fc10cf88 --- /dev/null +++ b/pyload/plugin/crypter/HotfileCom.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadCrypter import DeadCrypter + + +class HotfileCom(DeadCrypter): + __name = "HotfileCom" + __type = "crypter" + __version = "0.30" + + __pattern = r'https?://(?:www\.)?hotfile\.com/list/\w+/\w+' + __config = [] + + __description = """Hotfile.com folder decrypter plugin""" + __license = "GPLv3" + __authors = [("RaNaN", "RaNaN@pyload.org")] diff --git a/pyload/plugin/crypter/ILoadTo.py b/pyload/plugin/crypter/ILoadTo.py new file mode 100644 index 000000000..e00a88743 --- /dev/null +++ b/pyload/plugin/crypter/ILoadTo.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadCrypter import DeadCrypter + + +class ILoadTo(DeadCrypter): + __name = "ILoadTo" + __type = "crypter" + __version = "0.11" + + __pattern = r'http://(?:www\.)?iload\.to/go/\d+-[\w.-]+/' + __config = [] + + __description = """Iload.to decrypter plugin""" + __license = "GPLv3" + __authors = [("hzpz", "")] diff --git a/pyload/plugin/crypter/ImgurComAlbum.py b/pyload/plugin/crypter/ImgurComAlbum.py new file mode 100644 index 000000000..33a5553f9 --- /dev/null +++ b/pyload/plugin/crypter/ImgurComAlbum.py @@ -0,0 +1,28 @@ +import re + +from pyload.plugin.internal.SimpleCrypter import SimpleCrypter +from pyload.utils import uniqify + + +class ImgurComAlbum(SimpleCrypter): + __name = "ImgurComAlbum" + __type = "crypter" + __version = "0.51" + + __pattern = r'https?://(?:www\.|m\.)?imgur\.com/(a|gallery|)/?\w{5,7}' + __config = [("use_premium" , "bool", "Use premium account if available" , True), + ("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Imgur.com decrypter plugin""" + __license = "GPLv3" + __authors = [("nath_schwarz", "nathan.notwhite@gmail.com")] + + + NAME_PATTERN = r'(?P<N>.+?) - Imgur' + LINK_PATTERN = r'i\.imgur\.com/\w{7}s?\.(?:jpeg|jpg|png|gif|apng)' + + + def getLinks(self): + f = lambda url: "http://" + re.sub(r'(\w{7})s\.', r'\1.', url) + return uniqify(map(f, re.findall(self.LINK_PATTERN, self.html))) diff --git a/pyload/plugin/crypter/LetitbitNet.py b/pyload/plugin/crypter/LetitbitNet.py new file mode 100644 index 000000000..51f54014f --- /dev/null +++ b/pyload/plugin/crypter/LetitbitNet.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- + +import re +from pyload.plugin.Crypter import Crypter + + +class LetitbitNet(Crypter): + __name = "LetitbitNet" + __type = "crypter" + __version = "0.10" + + __pattern = r'http://(?:www\.)?letitbit\.net/folder/\w+' + __config = [("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Letitbit.net folder decrypter plugin""" + __license = "GPLv3" + __authors = [("DHMH", "webmaster@pcProfil.de"), + ("z00nx", "z00nx0@gmail.com")] + + + FOLDER_PATTERN = r'<table>(.*)</table>' + LINK_PATTERN = r'<a href="(.+?)" target="_blank">' + + + def decrypt(self, pyfile): + html = self.load(pyfile.url) + + folder = re.search(self.FOLDER_PATTERN, html, re.S) + if folder is None: + self.error(_("FOLDER_PATTERN not found")) + + self.urls.extend(re.findall(self.LINK_PATTERN, folder.group(0))) diff --git a/pyload/plugin/crypter/LinkCryptWs.py b/pyload/plugin/crypter/LinkCryptWs.py new file mode 100644 index 000000000..c997cbf9f --- /dev/null +++ b/pyload/plugin/crypter/LinkCryptWs.py @@ -0,0 +1,322 @@ +# -*- coding: utf-8 -*- + +import binascii +import re + +import pycurl + +from Crypto.Cipher import AES + +from pyload.plugin.Crypter import Crypter +from pyload.utils import html_unescape + + +class LinkCryptWs(Crypter): + __name = "LinkCryptWs" + __type = "crypter" + __version = "0.08" + + __pattern = r'http://(?:www\.)?linkcrypt\.ws/(dir|container)/(?P<ID>\w+)' + + __description = """LinkCrypt.ws decrypter plugin""" + __license = "GPLv3" + __authors = [("kagenoshin", "kagenoshin[AT]gmx[DOT]ch"), + ("glukgluk", ""), + ("Gummibaer", "")] + + + CRYPTED_KEY = "crypted" + JK_KEY = "jk" + + + def setup(self): + self.captcha = False + self.links = [] + self.sources = ['cnl', 'web', 'dlc', 'rsdf', 'ccf'] + + + def prepare(self): + # Init + self.fileid = re.match(self.__pattern, self.pyfile.url).group('ID') + + self.req.cj.setCookie("linkcrypt.ws", "language", "en") + + # Request package + self.req.http.c.setopt(pycurl.USERAGENT, "Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko") #: better chance to not get those key-captchas + self.html = self.load(self.pyfile.url) + + + def decrypt(self, pyfile): + if not self.js: + self.fail(_("Missing JS Engine")) + + self.prepare() + + if not self.isOnline(): + self.offline() + + if self.isKeyCaptchaProtected(): + self.retry(8, 15, _("Can't handle Key-Captcha")) + + if self.isCaptchaProtected(): + self.captcha = True + self.unlockCaptchaProtection() + self.handleCaptchaErrors() + + # Check for protection + if self.isPasswordProtected(): + self.unlockPasswordProtection() + self.handleErrors() + + # get unrar password + self.getunrarpw() + + # Get package name and folder + package_name, folder_name = self.getPackageInfo() + + # get the container definitions from script section + self.get_container_html() + + # Extract package links + for type in self.sources: + links = self.handleLinkSource(type) + + if links: + self.links.extend(links) + break + + if self.links: + self.packages = [(package_name, self.links, folder_name)] + + + def isOnline(self): + if "<title>Linkcrypt.ws // Error 404</title>" in self.html: + self.logDebug("folder doesen't exist anymore") + return False + else: + return True + + + def isPasswordProtected(self): + if "Authorizing" in self.html: + self.logDebug("Links are password protected") + return True + else: + return False + + + def isCaptchaProtected(self): + if 'id="captcha">' in self.html: + self.logDebug("Links are captcha protected") + return True + else: + return False + + + def isKeyCaptchaProtected(self): + if re.search(r'>If the folder does not open after klick on <', self.html, re.I): + return True + else: + return False + + + def unlockPasswordProtection(self): + password = self.getPassword() + + if password: + self.logDebug("Submitting password [%s] for protected links" % password) + self.html = self.load(self.pyfile.url, post={"password": password, 'x': "0", 'y': "0"}) + else: + self.fail(_("Folder is password protected")) + + + def unlockCaptchaProtection(self): + captcha_url = re.search(r'<form.*?id\s*?=\s*?"captcha"[^>]*?>.*?<\s*?input.*?src="(.+?)"', self.html, re.I | re.S).group(1) + captcha_code = self.decryptCaptcha(captcha_url, forceUser=True, imgtype="gif", result_type='positional') + + self.html = self.load(self.pyfile.url, post={"x": captcha_code[0], "y": captcha_code[1]}) + + + def getPackageInfo(self): + name = self.pyfile.package().name + folder = self.pyfile.package().folder + + self.logDebug("Defaulting to pyfile name [%s] and folder [%s] for package" % (name, folder)) + + return name, folder + + + def getunrarpw(self): + sitein = self.html + indexi = sitein.find("|source|") + 8 + indexe = sitein.find("|",indexi) + + unrarpw = sitein[indexi:indexe] + + if not (unrarpw == "Password" or "Dateipasswort") : + self.logDebug("File password set to: [%s]"% unrarpw) + self.pyfile.package().password = unrarpw + + + def handleErrors(self): + if self.isPasswordProtected(): + self.fail(_("Incorrect password")) + + + def handleCaptchaErrors(self): + if self.captcha: + if "Your choice was wrong!" in self.html: + self.invalidCaptcha() + self.retry() + else: + self.correctCaptcha() + + + def handleLinkSource(self, type): + if type == 'cnl': + return self.handleCNL2() + + elif type == 'web': + return self.handleWebLinks() + + elif type in ('rsdf', 'ccf', 'dlc'): + return self.handleContainer(type) + + else: + self.fail(_("Unknown source type: %s") % type) #@TODO: Replace with self.error in 0.4.10 + + + def handleWebLinks(self): + self.logDebug("Search for Web links ") + + package_links = [] + pattern = r'<form action="http://linkcrypt.ws/out.html"[^>]*?>.*?<input[^>]*?value="(.+?)"[^>]*?name="file"' + ids = re.findall(pattern, self.html, re.I | re.S) + + self.logDebug("Decrypting %d Web links" % len(ids)) + + for idx, weblink_id in enumerate(ids): + try: + self.logDebug("Decrypting Web link %d, %s" % (idx + 1, weblink_id)) + + res = self.load("http://linkcrypt.ws/out.html", post = {'file':weblink_id}) + + indexs = res.find("window.location =") + 19 + indexe = res.find('"', indexs) + + link2 = res[indexs:indexe] + + self.logDebug(link2) + + link2 = html_unescape(link2) + package_links.append(link2) + + except Exception, detail: + self.logDebug("Error decrypting Web link %s, %s" % (weblink_id, detail)) + + return package_links + + + def get_container_html(self): + self.container_html = [] + + script = re.search(r'<div.*?id="ad_cont".*?<script.*?javascrip[^>]*?>(.*?)</script', self.html, re.I | re.S) + + if script: + container_html_text = script.group(1) + container_html_text.strip() + self.container_html = container_html_text.splitlines() + + + def handle_javascript(self, line): + return self.js.eval(line.replace('{}))',"{}).replace('document.open();document.write','').replace(';document.close();',''))")) + + + def handleContainer(self, type): + package_links = [] + type = type.lower() + + self.logDebug('Search for %s Container links' % type.upper()) + + if not type.isalnum(): #: check to prevent broken re-pattern (cnl2,rsdf,ccf,dlc,web are all alpha-numeric) + self.fail(_("Unknown container type: %s") % type) #@TODO: Replace with self.error in 0.4.10 + + for line in self.container_html: + if type in line: + jseval = self.handle_javascript(line) + clink = re.search(r'href=["\'](["\']+)', jseval, re.I) + + if not clink: + continue + + self.logDebug("clink avaible") + + package_name, folder_name = self.getPackageInfo() + self.logDebug("Added package with name %s.%s and container link %s" %( package_name, type, clink.group(1))) + self.core.api.uploadContainer( "%s.%s" %(package_name, type), self.load(clink.group(1))) + return "Found it" + + return package_links + + + def handleCNL2(self): + self.logDebug("Search for CNL links") + + package_links = [] + cnl_line = None + + for line in self.container_html: + if "cnl" in line: + cnl_line = line + break + + if cnl_line: + self.logDebug("cnl_line gefunden") + + try: + cnl_section = self.handle_javascript(cnl_line) + (vcrypted, vjk) = self._getCipherParams(cnl_section) + for (crypted, jk) in zip(vcrypted, vjk): + package_links.extend(self._getLinks(crypted, jk)) + except Exception: + self.logError(_("Unable to decrypt CNL links (JS Error) try to get over links")) + return self.handleWebLinks() + + return package_links + + + def _getCipherParams(self, cnl_section): + # Get jk + jk_re = r'<INPUT.*?NAME="%s".*?VALUE="(.*?)"' % LinkCryptWs.JK_KEY + vjk = re.findall(jk_re, cnl_section) + + # Get crypted + crypted_re = r'<INPUT.*?NAME="%s".*?VALUE="(.*?)"' % LinkCryptWs.CRYPTED_KEY + vcrypted = re.findall(crypted_re, cnl_section) + + # Log and return + self.logDebug("Detected %d crypted blocks" % len(vcrypted)) + return vcrypted, vjk + + + def _getLinks(self, crypted, jk): + # Get key + jreturn = self.js.eval("%s f()" % jk) + key = binascii.unhexlify(jreturn) + + self.logDebug("JsEngine returns value [%s]" % jreturn) + + # Decrypt + Key = key + IV = key + obj = AES.new(Key, AES.MODE_CBC, IV) + text = obj.decrypt(crypted.decode('base64')) + + # Extract links + text = text.replace("\x00", "").replace("\r", "") + links = filter(bool, text.split('\n')) + + # Log and return + self.logDebug("Package has %d links" % len(links)) + + return links diff --git a/pyload/plugin/crypter/LinkSaveIn.py b/pyload/plugin/crypter/LinkSaveIn.py new file mode 100644 index 000000000..8c6f0b001 --- /dev/null +++ b/pyload/plugin/crypter/LinkSaveIn.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.SimpleDereferer import SimpleDereferer + + +class LinkSaveIn(SimpleDereferer): + __name = "LinkSaveIn" + __type = "crypter" + __version = "2.03" + + __pattern = r'https?://(?:www\.)?linksave\.in/\w+' + __config = [("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """LinkSave.in decrypter plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + COOKIES = [("linksave.in", "Linksave_Language", "english")] + + OFFLINE_PATTERN = r'>(Error )?404 -' diff --git a/pyload/plugin/crypter/LinkdecrypterCom.py b/pyload/plugin/crypter/LinkdecrypterCom.py new file mode 100644 index 000000000..907e3accd --- /dev/null +++ b/pyload/plugin/crypter/LinkdecrypterCom.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.Crypter import Crypter + + +class LinkdecrypterCom(Crypter): + __name = "LinkdecrypterCom" + __type = "crypter" + __version = "0.29" + + __pattern = r'^unmatchable$' + __config = [("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Linkdecrypter.com decrypter plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz"), + ("flowlee", "")] + + + TEXTAREA_PATTERN = r'<textarea name="links" wrap="off" readonly="1" class="caja_des">(.+)</textarea>' + PASSWORD_PATTERN = r'<input type="text" name="password"' + CAPTCHA_PATTERN = r'<img class="captcha" src="(.+?)"(.*?)>' + REDIR_PATTERN = r'<i>(Click <a href="./">here</a> if your browser does not redirect you).</i>' + + + def setup(self): + self.password = self.getPassword() + self.req.setOption("timeout", 300) + + + def decrypt(self, pyfile): + retries = 5 + + post_dict = {"link_cache": "on", "pro_links": pyfile.url, "modo_links": "text"} + self.html = self.load('http://linkdecrypter.com/', post=post_dict, decode=True) + + while retries: + m = re.search(self.TEXTAREA_PATTERN, self.html, re.S) + if m: + self.urls = [x for x in m.group(1).splitlines() if '[LINK-ERROR]' not in x] + + m = re.search(self.CAPTCHA_PATTERN, self.html) + if m: + captcha_url = 'http://linkdecrypter.com/' + m.group(1) + result_type = "positional" if "getPos" in m.group(2) else "textual" + + m = re.search(r"<p><i><b>([^<]+)</b></i></p>", self.html) + msg = m.group(1) if m else "" + self.logInfo(_("Captcha protected link"), result_type, msg) + + captcha = self.decryptCaptcha(captcha_url, result_type=result_type) + if result_type == "positional": + captcha = "%d|%d" % captcha + self.html = self.load('http://linkdecrypter.com/', post={"captcha": captcha}, decode=True) + retries -= 1 + + elif self.PASSWORD_PATTERN in self.html: + if self.password: + self.logInfo(_("Password protected link")) + self.html = self.load('http://linkdecrypter.com/', post={'password': self.password}, decode=True) + else: + self.fail(_("Missing password")) + + else: + retries -= 1 + self.html = self.load('http://linkdecrypter.com/', decode=True) diff --git a/pyload/plugin/crypter/LixIn.py b/pyload/plugin/crypter/LixIn.py new file mode 100644 index 000000000..d15761014 --- /dev/null +++ b/pyload/plugin/crypter/LixIn.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.Crypter import Crypter + + +class LixIn(Crypter): + __name = "LixIn" + __type = "crypter" + __version = "0.22" + + __pattern = r'http://(?:www\.)?lix\.in/(?P<ID>.+)' + __config = [("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Lix.in decrypter plugin""" + __license = "GPLv3" + __authors = [("spoob", "spoob@pyload.org")] + + + CAPTCHA_PATTERN = r'<img src="(captcha_img\.php\?.*?)"' + SUBMIT_PATTERN = r'value=\'continue.*?\'' + LINK_PATTERN = r'name="ifram" src="(.*?)"' + + + def decrypt(self, pyfile): + url = pyfile.url + + m = re.match(self.__pattern, url) + if m is None: + self.error(_("Unable to identify file ID")) + + id = m.group('ID') + self.logDebug("File id is %s" % id) + + self.html = self.load(url, decode=True) + + m = re.search(self.SUBMIT_PATTERN, self.html) + if m is None: + self.error(_("Link doesn't seem valid")) + + m = re.search(self.CAPTCHA_PATTERN, self.html) + if m: + for _i in xrange(5): + m = re.search(self.CAPTCHA_PATTERN, self.html) + if m: + self.logDebug("Trying captcha") + captcharesult = self.decryptCaptcha("http://lix.in/" + m.group(1)) + self.html = self.load(url, decode=True, + post={"capt": captcharesult, "submit": "submit", "tiny": id}) + else: + self.logDebug("No captcha/captcha solved") + else: + self.html = self.load(url, decode=True, post={"submit": "submit", "tiny": id}) + + m = re.search(self.LINK_PATTERN, self.html) + if m is None: + self.error(_("Unable to find destination url")) + else: + self.urls = [m.group(1)] + self.logDebug("Found link %s, adding to package" % self.urls[0]) diff --git a/pyload/plugin/crypter/LofCc.py b/pyload/plugin/crypter/LofCc.py new file mode 100644 index 000000000..680027b43 --- /dev/null +++ b/pyload/plugin/crypter/LofCc.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadCrypter import DeadCrypter + + +class LofCc(DeadCrypter): + __name = "LofCc" + __type = "crypter" + __version = "0.21" + + __pattern = r'http://(?:www\.)?lof\.cc/(.+)' + __config = [] + + __description = """Lof.cc decrypter plugin""" + __license = "GPLv3" + __authors = [("mkaay", "mkaay@mkaay.de")] diff --git a/pyload/plugin/crypter/MBLinkInfo.py b/pyload/plugin/crypter/MBLinkInfo.py new file mode 100644 index 000000000..7b39f9b5b --- /dev/null +++ b/pyload/plugin/crypter/MBLinkInfo.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadCrypter import DeadCrypter + + +class MBLinkInfo(DeadCrypter): + __name = "MBLinkInfo" + __type = "crypter" + __version = "0.03" + + __pattern = r'http://(?:www\.)?mblink\.info/?\?id=(\d+)' + __config = [] + + __description = """MBLink.info decrypter plugin""" + __license = "GPLv3" + __authors = [("Gummibaer", "Gummibaer@wiki-bierkiste.de"), + ("stickell", "l.stickell@yahoo.it")] diff --git a/pyload/plugin/crypter/MediafireCom.py b/pyload/plugin/crypter/MediafireCom.py new file mode 100644 index 000000000..aae727a90 --- /dev/null +++ b/pyload/plugin/crypter/MediafireCom.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- + +import re +from pyload.plugin.Crypter import Crypter +from pyload.plugin.hoster.MediafireCom import checkHTMLHeader +from pyload.utils import json_loads + + +class MediafireCom(Crypter): + __name = "MediafireCom" + __type = "crypter" + __version = "0.14" + + __pattern = r'http://(?:www\.)?mediafire\.com/(folder/|\?sharekey=|\?\w{13}($|[/#]))' + __config = [("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Mediafire.com folder decrypter plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + FOLDER_KEY_PATTERN = r'var afI= \'(\w+)' + LINK_PATTERN = r'<meta property="og:url" content="http://www\.mediafire\.com/\?(\w+)"/>' + + + def decrypt(self, pyfile): + url, result = checkHTMLHeader(pyfile.url) + self.logDebug("Location (%d): %s" % (result, url)) + + if result == 0: + # load and parse html + html = self.load(pyfile.url) + m = re.search(self.LINK_PATTERN, html) + if m: + # file page + self.urls.append("http://www.mediafire.com/file/%s" % m.group(1)) + else: + # folder page + m = re.search(self.FOLDER_KEY_PATTERN, html) + if m: + folder_key = m.group(1) + self.logDebug("FOLDER KEY: %s" % folder_key) + + json_resp = json_loads(self.load("http://www.mediafire.com/api/folder/get_info.php", + get={'folder_key' : folder_key, + 'response_format': "json", + 'version' : 1})) + # self.logInfo(json_resp) + if json_resp['response']['result'] == "Success": + for link in json_resp['response']['folder_info']['files']: + self.urls.append("http://www.mediafire.com/file/%s" % link['quickkey']) + else: + self.fail(json_resp['response']['message']) + elif result == 1: + self.offline() + else: + self.urls.append(url) diff --git a/pyload/plugin/crypter/MegaCoNz.py b/pyload/plugin/crypter/MegaCoNz.py new file mode 100644 index 000000000..c66b3f112 --- /dev/null +++ b/pyload/plugin/crypter/MegaCoNz.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.Crypter import Crypter + + +class MegaCoNz(Crypter): + __name = "MegaCoNz" + __type = "crypter" + __version = "0.04" + + __pattern = r'(?:https?://(?:www\.)?mega\.co\.nz/|mega:|chrome:.+?)#F!(?P<ID>[\w^_]+)!(?P<KEY>[\w,\\-]+)' + __config = [("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Mega.co.nz folder decrypter plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + def setup(self): + self.req.setOption("timeout", 300) + + + def decrypt(self, pyfile): + url = "https://mega.co.nz/#F!%s!%s" % re.match(self.__pattern, pyfile.url).groups() + self.html = self.load("http://rapidgen.org/linkfinder", post={'linklisturl': url}) + self.urls = re.findall(r'(https://mega.co.nz/#N!.+?)<', self.html) diff --git a/pyload/plugin/crypter/MegaRapidCz.py b/pyload/plugin/crypter/MegaRapidCz.py new file mode 100644 index 000000000..fe490464a --- /dev/null +++ b/pyload/plugin/crypter/MegaRapidCz.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.SimpleCrypter import SimpleCrypter + + +class MegaRapidCz(SimpleCrypter): + __name = "MegaRapidCz" + __type = "crypter" + __version = "0.02" + + __pattern = r'http://(?:www\.)?(share|mega)rapid\.cz/slozka/\d+/\w+' + __config = [("use_premium" , "bool", "Use premium account if available" , True), + ("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Share-Rapid.com folder decrypter plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + LINK_PATTERN = r'<td class="soubor".*?><a href="(.+?)">' diff --git a/pyload/plugin/crypter/MegauploadCom.py b/pyload/plugin/crypter/MegauploadCom.py new file mode 100644 index 000000000..f300408e8 --- /dev/null +++ b/pyload/plugin/crypter/MegauploadCom.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadCrypter import DeadCrypter + + +class MegauploadCom(DeadCrypter): + __name = "MegauploadCom" + __type = "crypter" + __version = "0.02" + + __pattern = r'http://(?:www\.)?megaupload\.com/(\?f|xml/folderfiles\.php\?.*&?folderid)=\w+' + __config = [] + + __description = """Megaupload.com folder decrypter plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] diff --git a/pyload/plugin/crypter/Movie2KTo.py b/pyload/plugin/crypter/Movie2KTo.py new file mode 100644 index 000000000..e807855af --- /dev/null +++ b/pyload/plugin/crypter/Movie2KTo.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadCrypter import DeadCrypter + + +class Movie2KTo(DeadCrypter): + __name = "Movie2KTo" + __type = "crypter" + __version = "0.51" + + __pattern = r'http://(?:www\.)?movie2k\.to/(.+)\.html' + __config = [] + + __description = """Movie2k.to decrypter plugin""" + __license = "GPLv3" + __authors = [("4Christopher", "4Christopher@gmx.de")] diff --git a/pyload/plugin/crypter/MultiUpOrg.py b/pyload/plugin/crypter/MultiUpOrg.py new file mode 100644 index 000000000..54d9a979d --- /dev/null +++ b/pyload/plugin/crypter/MultiUpOrg.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- + +import re +import urlparse + +from pyload.plugin.internal.SimpleCrypter import SimpleCrypter + + +class MultiUpOrg(SimpleCrypter): + __name = "MultiUpOrg" + __type = "crypter" + __version = "0.03" + + __pattern = r'http://(?:www\.)?multiup\.org/(en|fr)/(?P<TYPE>project|download|miror)/\w+(/\w+)?' + __config = [("use_premium" , "bool", "Use premium account if available" , True), + ("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """MultiUp.org decrypter plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + NAME_PATTERN = r'<title>.*(?:Project|Projet|ownload|élécharger) (?P<N>.+?) (\(|- )' + + + def getLinks(self): + m_type = re.match(self.__pattern, self.pyfile.url).group('TYPE') + + if m_type == "project": + pattern = r'\n(http://www\.multiup\.org/(?:en|fr)/download/.*)' + else: + pattern = r'style="width:97%;text-align:left".*\n.*href="(.*)"' + if m_type == "download": + dl_pattern = r'href="(.*)">.*\n.*<h5>DOWNLOAD</h5>' + miror_page = urlparse.urljoin("http://www.multiup.org", re.search(dl_pattern, self.html).group(1)) + self.html = self.load(miror_page) + + return re.findall(pattern, self.html) diff --git a/pyload/plugin/crypter/MultiloadCz.py b/pyload/plugin/crypter/MultiloadCz.py new file mode 100644 index 000000000..2e2d69037 --- /dev/null +++ b/pyload/plugin/crypter/MultiloadCz.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- + +import re +from pyload.plugin.Crypter import Crypter + + +class MultiloadCz(Crypter): + __name = "MultiloadCz" + __type = "crypter" + __version = "0.40" + + __pattern = r'http://(?:[^/]*\.)?multiload\.cz/(stahnout|slozka)/.+' + __config = [("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package" , True), + ("usedHoster" , "str" , "Prefered hoster list (bar-separated)", "" ), + ("ignoredHoster" , "str" , "Ignored hoster list (bar-separated)" , "" )] + + __description = """Multiload.cz decrypter plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + FOLDER_PATTERN = r'<form action="" method="get"><textarea.*?>([^>]*)</textarea></form>' + LINK_PATTERN = r'<p class="manager-server"><strong>([^<]+)</strong></p><p class="manager-linky"><a href="(.+?)">' + + + def decrypt(self, pyfile): + self.html = self.load(pyfile.url, decode=True) + + if re.match(self.__pattern, pyfile.url).group(1) == "slozka": + m = re.search(self.FOLDER_PATTERN, self.html) + if m: + self.urls.extend(m.group(1).split()) + else: + m = re.findall(self.LINK_PATTERN, self.html) + if m: + prefered_set = set(self.getConfig('usedHoster').split('|')) + self.urls.extend(x[1] for x in m if x[0] in prefered_set) + + if not self.urls: + ignored_set = set(self.getConfig('ignoredHoster').split('|')) + self.urls.extend(x[1] for x in m if x[0] not in ignored_set) diff --git a/pyload/plugin/crypter/MultiuploadCom.py b/pyload/plugin/crypter/MultiuploadCom.py new file mode 100644 index 000000000..439e0e69b --- /dev/null +++ b/pyload/plugin/crypter/MultiuploadCom.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadCrypter import DeadCrypter + + +class MultiuploadCom(DeadCrypter): + __name = "MultiuploadCom" + __type = "crypter" + __version = "0.02" + + __pattern = r'http://(?:www\.)?multiupload\.(com|nl)/\w+' + + __description = """MultiUpload.com decrypter plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] diff --git a/pyload/plugin/crypter/NCryptIn.py b/pyload/plugin/crypter/NCryptIn.py new file mode 100644 index 000000000..bc9702f21 --- /dev/null +++ b/pyload/plugin/crypter/NCryptIn.py @@ -0,0 +1,310 @@ +# -*- coding: utf-8 -*- + +import binascii +import re + +from Crypto.Cipher import AES + +from pyload.plugin.Crypter import Crypter +from pyload.plugin.captcha.ReCaptcha import ReCaptcha + + +class NCryptIn(Crypter): + __name = "NCryptIn" + __type = "crypter" + __version = "1.34" + + __pattern = r'http://(?:www\.)?ncrypt\.in/(?P<TYPE>folder|link|frame)-([^/\?]+)' + __config = [("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """NCrypt.in decrypter plugin""" + __license = "GPLv3" + __authors = [("fragonib", "fragonib[AT]yahoo[DOT]es"), + ("stickell", "l.stickell@yahoo.it")] + + + JK_KEY = "jk" + CRYPTED_KEY = "crypted" + + NAME_PATTERN = r'<meta name="description" content="(?P<N>.+?)"' + + + def setup(self): + self.package = None + self.cleanedHtml = None + self.links_source_order = ["cnl2", "rsdf", "ccf", "dlc", "web"] + self.protection_type = None + + + def decrypt(self, pyfile): + # Init + self.package = pyfile.package() + package_links = [] + package_name = self.package.name + folder_name = self.package.folder + + # Deal with single links + if self.isSingleLink(): + package_links.extend(self.handleSingleLink()) + + # Deal with folders + else: + + # Request folder home + self.html = self.requestFolderHome() + self.cleanedHtml = self.removeHtmlCrap(self.html) + if not self.isOnline(): + self.offline() + + # Check for folder protection + if self.isProtected(): + self.html = self.unlockProtection() + self.cleanedHtml = self.removeHtmlCrap(self.html) + self.handleErrors() + + # Prepare package name and folder + (package_name, folder_name) = self.getPackageInfo() + + # Extract package links + for link_source_type in self.links_source_order: + package_links.extend(self.handleLinkSource(link_source_type)) + if package_links: #: use only first source which provides links + break + package_links = set(package_links) + + # Pack and return links + if package_links: + self.packages = [(package_name, package_links, folder_name)] + + + def isSingleLink(self): + link_type = re.match(self.__pattern, self.pyfile.url).group('TYPE') + return link_type in ("link", "frame") + + + def requestFolderHome(self): + return self.load(self.pyfile.url, decode=True) + + + def removeHtmlCrap(self, content): + patterns = (r'(type="hidden".*?(name=".*?")?.*?value=".*?")', + r'display:none;">(.*?)</(div|span)>', + r'<div\s+class="jdownloader"(.*?)</div>', + r'<table class="global">(.*?)</table>', + r'<iframe\s+style="display:none(.*?)</iframe>') + for pattern in patterns: + rexpr = re.compile(pattern, re.S) + content = re.sub(rexpr, "", content) + return content + + + def isOnline(self): + if "Your folder does not exist" in self.cleanedHtml: + self.logDebug("File not m") + return False + return True + + + def isProtected(self): + form = re.search(r'<form.*?name.*?protected.*?>(.*?)</form>', self.cleanedHtml, re.S) + if form: + content = form.group(1) + for keyword in ("password", "captcha"): + if keyword in content: + self.protection_type = keyword + self.logDebug("Links are %s protected" % self.protection_type) + return True + return False + + + def getPackageInfo(self): + m = re.search(self.NAME_PATTERN, self.html) + if m: + name = folder = m.group('N').strip() + self.logDebug("Found name [%s] and folder [%s] in package info" % (name, folder)) + else: + name = self.package.name + folder = self.package.folder + self.logDebug("Package info not m, defaulting to pyfile name [%s] and folder [%s]" % (name, folder)) + return name, folder + + + def unlockProtection(self): + postData = {} + + form = re.search(r'<form name="protected"(.*?)</form>', self.cleanedHtml, re.S).group(1) + + # Submit package password + if "password" in form: + password = self.getPassword() + self.logDebug("Submitting password [%s] for protected links" % password) + postData['password'] = password + + # Resolve anicaptcha + if "anicaptcha" in form: + self.logDebug("Captcha protected") + captchaUri = re.search(r'src="(/temp/anicaptcha/.+?)"', form).group(1) + captcha = self.decryptCaptcha("http://ncrypt.in" + captchaUri) + self.logDebug("Captcha resolved [%s]" % captcha) + postData['captcha'] = captcha + + # Resolve recaptcha + if "recaptcha" in form: + self.logDebug("ReCaptcha protected") + captcha_key = re.search(r'\?k=(.*?)"', form).group(1) + self.logDebug("Resolving ReCaptcha with key [%s]" % captcha_key) + recaptcha = ReCaptcha(self) + response, challenge = recaptcha.challenge(captcha_key) + postData['recaptcha_challenge_field'] = challenge + postData['recaptcha_response_field'] = response + + # Resolve circlecaptcha + if "circlecaptcha" in form: + self.logDebug("CircleCaptcha protected") + captcha_img_url = "http://ncrypt.in/classes/captcha/circlecaptcha.php" + coords = self.decryptCaptcha(captcha_img_url, forceUser=True, imgtype="png", result_type='positional') + self.logDebug("Captcha resolved, coords [%s]" % str(coords)) + postData['circle.x'] = coords[0] + postData['circle.y'] = coords[1] + + # Unlock protection + postData['submit_protected'] = 'Continue to folder' + return self.load(self.pyfile.url, post=postData, decode=True) + + + def handleErrors(self): + if self.protection_type == "password": + if "This password is invalid!" in self.cleanedHtml: + self.logDebug("Incorrect password, please set right password on 'Edit package' form and retry") + self.fail(_("Incorrect password, please set right password on 'Edit package' form and retry")) + + if self.protection_type == "captcha": + if "The securitycheck was wrong!" in self.cleanedHtml: + self.invalidCaptcha() + self.retry() + else: + self.correctCaptcha() + + + def handleLinkSource(self, link_source_type): + # Check for JS engine + require_js_engine = link_source_type in ("cnl2", "rsdf", "ccf", "dlc") + if require_js_engine and not self.js: + self.logDebug("No JS engine available, skip %s links" % link_source_type) + return [] + + # Select suitable handler + if link_source_type == 'single': + return self.handleSingleLink() + if link_source_type == 'cnl2': + return self.handleCNL2() + elif link_source_type in ("rsdf", "ccf", "dlc"): + return self.handleContainer(link_source_type) + elif link_source_type == "web": + return self.handleWebLinks() + else: + self.error(_('Unknown source type "%s"') % link_source_type) + + + def handleSingleLink(self): + self.logDebug("Handling Single link") + package_links = [] + + # Decrypt single link + decrypted_link = self.decryptLink(self.pyfile.url) + if decrypted_link: + package_links.append(decrypted_link) + + return package_links + + + def handleCNL2(self): + self.logDebug("Handling CNL2 links") + package_links = [] + + if 'cnl2_output' in self.cleanedHtml: + try: + (vcrypted, vjk) = self._getCipherParams() + for (crypted, jk) in zip(vcrypted, vjk): + package_links.extend(self._getLinks(crypted, jk)) + except Exception: + self.fail(_("Unable to decrypt CNL2 links")) + + return package_links + + + def handleContainers(self): + self.logDebug("Handling Container links") + package_links = [] + + pattern = r'/container/(rsdf|dlc|ccf)/(\w+)' + containersLinks = re.findall(pattern, self.html) + self.logDebug("Decrypting %d Container links" % len(containersLinks)) + for containerLink in containersLinks: + link = "http://ncrypt.in/container/%s/%s.%s" % (containerLink[0], containerLink[1], containerLink[0]) + package_links.append(link) + + return package_links + + + def handleWebLinks(self): + self.logDebug("Handling Web links") + pattern = r'(http://ncrypt\.in/link-.*?=)' + links = re.findall(pattern, self.html) + + package_links = [] + self.logDebug("Decrypting %d Web links" % len(links)) + for i, link in enumerate(links): + self.logDebug("Decrypting Web link %d, %s" % (i + 1, link)) + decrypted_link = self.decrypt(link) + if decrypted_link: + package_links.append(decrypted_link) + + return package_links + + + def decryptLink(self, link): + try: + url = link.replace("link-", "frame-") + link = self.load(url, just_header=True)['location'] + return link + except Exception, detail: + self.logDebug("Error decrypting link %s, %s" % (link, detail)) + + + def _getCipherParams(self): + pattern = r'<input.*?name="%s".*?value="(.*?)"' + + # Get jk + jk_re = pattern % NCryptIn.JK_KEY + vjk = re.findall(jk_re, self.html) + + # Get crypted + crypted_re = pattern % NCryptIn.CRYPTED_KEY + vcrypted = re.findall(crypted_re, self.html) + + # Log and return + self.logDebug("Detected %d crypted blocks" % len(vcrypted)) + return vcrypted, vjk + + + def _getLinks(self, crypted, jk): + # Get key + jreturn = self.js.eval("%s f()" % jk) + self.logDebug("JsEngine returns value [%s]" % jreturn) + key = binascii.unhexlify(jreturn) + + # Decrypt + Key = key + IV = key + obj = AES.new(Key, AES.MODE_CBC, IV) + text = obj.decrypt(crypted.decode('base64')) + + # Extract links + text = text.replace("\x00", "").replace("\r", "") + links = filter(bool, text.split('\n')) + + # Log and return + self.logDebug("Block has %d links" % len(links)) + return links diff --git a/pyload/plugin/crypter/NetfolderIn.py b/pyload/plugin/crypter/NetfolderIn.py new file mode 100644 index 000000000..f036795d5 --- /dev/null +++ b/pyload/plugin/crypter/NetfolderIn.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.internal.SimpleCrypter import SimpleCrypter + + +class NetfolderIn(SimpleCrypter): + __name = "NetfolderIn" + __type = "crypter" + __version = "0.72" + + __pattern = r'http://(?:www\.)?netfolder\.in/(folder\.php\?folder_id=)?(?P<ID>\w+)(?(1)|/\w+)' + __config = [("use_premium" , "bool", "Use premium account if available" , True), + ("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """NetFolder.in decrypter plugin""" + __license = "GPLv3" + __authors = [("RaNaN", "RaNaN@pyload.org"), + ("fragonib", "fragonib[AT]yahoo[DOT]es")] + + + NAME_PATTERN = r'<div class="Text">Inhalt des Ordners <span.*>(?P<N>.+)</span></div>' + + + def prepare(self): + super(NetfolderIn, self).prepare() + + # Check for password protection + if self.isPasswordProtected(): + self.html = self.submitPassword() + if not self.html: + self.fail(_("Incorrect password, please set right password on Add package form and retry")) + + + def isPasswordProtected(self): + if '<input type="password" name="password"' in self.html: + self.logDebug("Links are password protected") + return True + return False + + + def submitPassword(self): + # Gather data + try: + m = re.match(self.__pattern, self.pyfile.url) + id = m.group('ID') + except AttributeError: + self.logDebug("Unable to get package id from url [%s]" % self.pyfile.url) + return + url = "http://netfolder.in/folder.php?folder_id=" + id + password = self.getPassword() + + # Submit package password + post = {'password': password, 'save': 'Absenden'} + self.logDebug("Submitting password [%s] for protected links with id [%s]" % (password, id)) + html = self.load(url, {}, post) + + # Check for invalid password + if '<div class="InPage_Error">' in html: + self.logDebug("Incorrect password, please set right password on Edit package form and retry") + return None + + return html + + + def getLinks(self): + links = re.search(r'name="list" value="(.*?)"', self.html).group(1).split(",") + self.logDebug("Package has %d links" % len(links)) + return links diff --git a/pyload/plugin/crypter/NosvideoCom.py b/pyload/plugin/crypter/NosvideoCom.py new file mode 100644 index 000000000..2afc697f3 --- /dev/null +++ b/pyload/plugin/crypter/NosvideoCom.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.SimpleCrypter import SimpleCrypter + + +class NosvideoCom(SimpleCrypter): + __name = "NosvideoCom" + __type = "crypter" + __version = "0.03" + + __pattern = r'http://(?:www\.)?nosvideo\.com/\?v=\w+' + __config = [("use_premium" , "bool", "Use premium account if available" , True), + ("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Nosvideo.com decrypter plugin""" + __license = "GPLv3" + __authors = [("igel", "igelkun@myopera.com")] + + + LINK_PATTERN = r'href="(http://(?:w{3}\.)?nosupload\.com/\?d=\w+)"' + NAME_PATTERN = r'<[tT]itle>Watch (?P<N>.+?)<' diff --git a/pyload/plugin/crypter/OneKhDe.py b/pyload/plugin/crypter/OneKhDe.py new file mode 100644 index 000000000..9d21710de --- /dev/null +++ b/pyload/plugin/crypter/OneKhDe.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.utils import html_unescape + +from pyload.plugin.Crypter import Crypter + + +class OneKhDe(Crypter): + __name = "OneKhDe" + __type = "crypter" + __version = "0.11" + + __pattern = r'http://(?:www\.)?1kh\.de/f/' + __config = [("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """1kh.de decrypter plugin""" + __license = "GPLv3" + __authors = [("spoob", "spoob@pyload.org")] + + + def __init__(self, parent): + Crypter.__init__(self, parent) + self.parent = parent + + + def file_exists(self): + """ returns True or False + """ + return True + + + def proceed(self, url, location): + url = self.parent.url + self.html = self.load(url) + link_ids = re.findall(r"<a id=\"DownloadLink_(\d*)\" href=\"http://1kh.de/", self.html) + for id in link_ids: + new_link = html_unescape(re.search("width=\"100%\" src=\"(.*)\"></iframe>", self.load("http://1kh.de/l/" + id)).group(1)) + self.urls.append(new_link) diff --git a/pyload/plugin/crypter/OronCom.py b/pyload/plugin/crypter/OronCom.py new file mode 100644 index 000000000..316e6d525 --- /dev/null +++ b/pyload/plugin/crypter/OronCom.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadCrypter import DeadCrypter + + +class OronCom(DeadCrypter): + __name = "OronCom" + __type = "crypter" + __version = "0.11" + + __pattern = r'http://(?:www\.)?oron\.com/folder/\w+' + __config = [] + + __description = """Oron.com folder decrypter plugin""" + __license = "GPLv3" + __authors = [("DHMH", "webmaster@pcProfil.de")] diff --git a/pyload/plugin/crypter/PastebinCom.py b/pyload/plugin/crypter/PastebinCom.py new file mode 100644 index 000000000..034c859a1 --- /dev/null +++ b/pyload/plugin/crypter/PastebinCom.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.SimpleCrypter import SimpleCrypter + + +class PastebinCom(SimpleCrypter): + __name = "PastebinCom" + __type = "crypter" + __version = "0.03" + + __pattern = r'http://(?:www\.)?pastebin\.com/\w+' + __config = [("use_premium" , "bool", "Use premium account if available" , True), + ("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Pastebin.com decrypter plugin""" + __license = "GPLv3" + __authors = [("stickell", "l.stickell@yahoo.it")] + + + LINK_PATTERN = r'<div class="de\d+">(https?://[^ <]+)(?:[^<]*)</div>' + NAME_PATTERN = r'<div class="paste_box_line1" title="(?P<N>.+?)">' diff --git a/pyload/plugin/crypter/QuickshareCz.py b/pyload/plugin/crypter/QuickshareCz.py new file mode 100644 index 000000000..201fc1f6d --- /dev/null +++ b/pyload/plugin/crypter/QuickshareCz.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- + +import re +from pyload.plugin.Crypter import Crypter + + +class QuickshareCz(Crypter): + __name = "QuickshareCz" + __type = "crypter" + __version = "0.10" + + __pattern = r'http://(?:www\.)?quickshare\.cz/slozka-\d+' + __config = [("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Quickshare.cz folder decrypter plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + FOLDER_PATTERN = r'<textarea.*?>(.*?)</textarea>' + LINK_PATTERN = r'(http://www\.quickshare\.cz/\S+)' + + + def decrypt(self, pyfile): + html = self.load(pyfile.url) + + m = re.search(self.FOLDER_PATTERN, html, re.S) + if m is None: + self.error(_("FOLDER_PATTERN not found")) + self.urls.extend(re.findall(self.LINK_PATTERN, m.group(1))) diff --git a/pyload/plugin/crypter/RSLayerCom.py b/pyload/plugin/crypter/RSLayerCom.py new file mode 100644 index 000000000..756c356f7 --- /dev/null +++ b/pyload/plugin/crypter/RSLayerCom.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadCrypter import DeadCrypter + + +class RSLayerCom(DeadCrypter): + __name = "RSLayerCom" + __type = "crypter" + __version = "0.21" + + __pattern = r'http://(?:www\.)?rs-layer\.com/directory-' + __config = [] + + __description = """RS-Layer.com decrypter plugin""" + __license = "GPLv3" + __authors = [("hzpz", "")] diff --git a/pyload/plugin/crypter/RelinkUs.py b/pyload/plugin/crypter/RelinkUs.py new file mode 100644 index 000000000..2b9a85401 --- /dev/null +++ b/pyload/plugin/crypter/RelinkUs.py @@ -0,0 +1,293 @@ +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +import binascii +import re +import os + +from Crypto.Cipher import AES +from pyload.plugin.Crypter import Crypter +from pyload.utils import fs_join + + +class RelinkUs(Crypter): + __name = "RelinkUs" + __type = "crypter" + __version = "3.12" + + __pattern = r'http://(?:www\.)?relink\.us/(f/|((view|go)\.php\?id=))(?P<ID>.+)' + __config = [("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Relink.us decrypter plugin""" + __license = "GPLv3" + __authors = [("fragonib", "fragonib[AT]yahoo[DOT]es"), + ("AndroKev", "neureither.kevin@gmail.com")] + + + PREFERRED_LINK_SOURCES = ["cnl2", "dlc", "web"] + + OFFLINE_TOKEN = r'<title>Tattooside' + + PASSWORD_TOKEN = r'container_password.php' + PASSWORD_ERROR_ROKEN = r'You have entered an incorrect password' + PASSWORD_SUBMIT_URL = r'http://www.relink.us/container_password.php' + + CAPTCHA_TOKEN = r'container_captcha.php' + CAPTCHA_ERROR_ROKEN = r'You have solved the captcha wrong' + CAPTCHA_IMG_URL = r'http://www.relink.us/core/captcha/circlecaptcha.php' + CAPTCHA_SUBMIT_URL = r'http://www.relink.us/container_captcha.php' + + FILE_TITLE_REGEX = r'<th>Title</th><td>(.*)</td></tr>' + FILE_NOTITLE = r'No title' + + CNL2_FORM_REGEX = r'<form id="cnl_form-(.*?)</form>' + CNL2_FORMINPUT_REGEX = r'<input.*?name="%s".*?value="(.*?)"' + CNL2_JK_KEY = "jk" + CNL2_CRYPTED_KEY = "crypted" + + DLC_LINK_REGEX = r'<a href=".*?" class="dlc_button" target="_blank">' + DLC_DOWNLOAD_URL = r'http://www.relink.us/download.php' + + WEB_FORWARD_REGEX = r'getFile\(\'(.+)\'\)' + WEB_FORWARD_URL = r'http://www.relink.us/frame.php' + WEB_LINK_REGEX = r'<iframe name="Container" height="100%" frameborder="no" width="100%" src="(.+)"></iframe>' + + + def setup(self): + self.fileid = None + self.package = None + self.captcha = False + + + def decrypt(self, pyfile): + # Init + self.initPackage(pyfile) + + # Request package + self.requestPackage() + + # Check for online + if not self.isOnline(): + self.offline() + + # Check for protection + if self.isPasswordProtected(): + self.unlockPasswordProtection() + self.handleErrors() + + if self.isCaptchaProtected(): + self.captcha = True + self.unlockCaptchaProtection() + self.handleErrors() + + # Get package name and folder + (package_name, folder_name) = self.getPackageInfo() + + # Extract package links + package_links = [] + for sources in self.PREFERRED_LINK_SOURCES: + package_links.extend(self.handleLinkSource(sources)) + if package_links: #: use only first source which provides links + break + package_links = set(package_links) + + # Pack + if package_links: + self.packages = [(package_name, package_links, folder_name)] + + + def initPackage(self, pyfile): + self.fileid = re.match(self.__pattern, pyfile.url).group('ID') + self.package = pyfile.package() + + + def requestPackage(self): + self.html = self.load(self.pyfile.url, decode=True) + + + def isOnline(self): + if self.OFFLINE_TOKEN in self.html: + self.logDebug("File not found") + return False + return True + + + def isPasswordProtected(self): + if self.PASSWORD_TOKEN in self.html: + self.logDebug("Links are password protected") + return True + + + def isCaptchaProtected(self): + if self.CAPTCHA_TOKEN in self.html: + self.logDebug("Links are captcha protected") + return True + return False + + + def unlockPasswordProtection(self): + password = self.getPassword() + + self.logDebug("Submitting password [%s] for protected links" % password) + + if password: + passwd_url = self.PASSWORD_SUBMIT_URL + "?id=%s" % self.fileid + passwd_data = {'id': self.fileid, 'password': password, 'pw': 'submit'} + self.html = self.load(passwd_url, post=passwd_data, decode=True) + + + def unlockCaptchaProtection(self): + self.logDebug("Request user positional captcha resolving") + captcha_img_url = self.CAPTCHA_IMG_URL + "?id=%s" % self.fileid + coords = self.decryptCaptcha(captcha_img_url, forceUser=True, imgtype="png", result_type='positional') + self.logDebug("Captcha resolved, coords [%s]" % str(coords)) + captcha_post_url = self.CAPTCHA_SUBMIT_URL + "?id=%s" % self.fileid + captcha_post_data = {'button.x': coords[0], 'button.y': coords[1], 'captcha': 'submit'} + self.html = self.load(captcha_post_url, post=captcha_post_data, decode=True) + + + def getPackageInfo(self): + name = folder = None + + # Try to get info from web + m = re.search(self.FILE_TITLE_REGEX, self.html) + if m: + title = m.group(1).strip() + if not self.FILE_NOTITLE in title: + name = folder = title + self.logDebug("Found name [%s] and folder [%s] in package info" % (name, folder)) + + # Fallback to defaults + if not name or not folder: + name = self.package.name + folder = self.package.folder + self.logDebug("Package info not found, defaulting to pyfile name [%s] and folder [%s]" % (name, folder)) + + # Return package info + return name, folder + + + def handleErrors(self): + if self.PASSWORD_ERROR_ROKEN in self.html: + msg = "Incorrect password, please set right password on 'Edit package' form and retry" + self.logDebug(msg) + self.fail(_(msg)) + + if self.captcha: + if self.CAPTCHA_ERROR_ROKEN in self.html: + self.invalidCaptcha() + self.retry() + else: + self.correctCaptcha() + + + def handleLinkSource(self, source): + if source == 'cnl2': + return self.handleCNL2Links() + elif source == 'dlc': + return self.handleDLCLinks() + elif source == 'web': + return self.handleWEBLinks() + else: + self.error(_('Unknown source type "%s"') % source) + + + def handleCNL2Links(self): + self.logDebug("Search for CNL2 links") + package_links = [] + m = re.search(self.CNL2_FORM_REGEX, self.html, re.S) + if m: + cnl2_form = m.group(1) + try: + (vcrypted, vjk) = self._getCipherParams(cnl2_form) + for (crypted, jk) in zip(vcrypted, vjk): + package_links.extend(self._getLinks(crypted, jk)) + except Exception: + self.logDebug("Unable to decrypt CNL2 links") + return package_links + + + def handleDLCLinks(self): + self.logDebug("Search for DLC links") + package_links = [] + m = re.search(self.DLC_LINK_REGEX, self.html) + if m: + container_url = self.DLC_DOWNLOAD_URL + "?id=%s&dlc=1" % self.fileid + self.logDebug("Downloading DLC container link [%s]" % container_url) + try: + dlc = self.load(container_url) + dlc_filename = self.fileid + ".dlc" + dlc_filepath = fs_join(self.config.get("general", "download_folder"), dlc_filename) + with open(dlc_filepath, "wb") as f: + f.write(dlc) + package_links.append(dlc_filepath) + + except Exception: + self.fail(_("Unable to download DLC container")) + + return package_links + + + def handleWEBLinks(self): + self.logDebug("Search for WEB links") + + package_links = [] + params = re.findall(self.WEB_FORWARD_REGEX, self.html) + + self.logDebug("Decrypting %d Web links" % len(params)) + + for index, param in enumerate(params): + try: + url = self.WEB_FORWARD_URL + "?%s" % param + + self.logDebug("Decrypting Web link %d, %s" % (index + 1, url)) + + res = self.load(url, decode=True) + link = re.search(self.WEB_LINK_REGEX, res).group(1) + + package_links.append(link) + + except Exception, detail: + self.logDebug("Error decrypting Web link %s, %s" % (index, detail)) + + self.setWait(4) + self.wait() + + return package_links + + + def _getCipherParams(self, cnl2_form): + # Get jk + jk_re = self.CNL2_FORMINPUT_REGEX % self.CNL2_JK_KEY + vjk = re.findall(jk_re, cnl2_form, re.I) + + # Get crypted + crypted_re = self.CNL2_FORMINPUT_REGEX % RelinkUs.CNL2_CRYPTED_KEY + vcrypted = re.findall(crypted_re, cnl2_form, re.I) + + # Log and return + self.logDebug("Detected %d crypted blocks" % len(vcrypted)) + return vcrypted, vjk + + + def _getLinks(self, crypted, jk): + # Get key + jreturn = self.js.eval("%s f()" % jk) + self.logDebug("JsEngine returns value [%s]" % jreturn) + key = binascii.unhexlify(jreturn) + + # Decrypt + Key = key + IV = key + obj = AES.new(Key, AES.MODE_CBC, IV) + text = obj.decrypt(crypted.decode('base64')) + + # Extract links + text = text.replace("\x00", "").replace("\r", "") + links = filter(bool, text.split('\n')) + + # Log and return + self.logDebug("Package has %d links" % len(links)) + return links diff --git a/pyload/plugin/crypter/SafelinkingNet.py b/pyload/plugin/crypter/SafelinkingNet.py new file mode 100644 index 000000000..a949d17b1 --- /dev/null +++ b/pyload/plugin/crypter/SafelinkingNet.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- + +import re + +from BeautifulSoup import BeautifulSoup + +from pyload.utils import json_loads +from pyload.plugin.Crypter import Crypter +from pyload.plugin.captcha.SolveMedia import SolveMedia + + +class SafelinkingNet(Crypter): + __name = "SafelinkingNet" + __type = "crypter" + __version = "0.14" + + __pattern = r'https?://(?:www\.)?safelinking\.net/([pd])/\w+' + __config = [("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Safelinking.net decrypter plugin""" + __license = "GPLv3" + __authors = [("quareevo", "quareevo@arcor.de")] + + + SOLVEMEDIA_PATTERN = "solvemediaApiKey = '([\w.-]+)';" + + + def decrypt(self, pyfile): + url = pyfile.url + + if re.match(self.__pattern, url).group(1) == "d": + + header = self.load(url, just_header=True) + if 'location' in header: + self.urls = [header['location']] + else: + self.error(_("Couldn't find forwarded Link")) + + else: + postData = {"post-protect": "1"} + + self.html = self.load(url) + + if "link-password" in self.html: + postData['link-password'] = self.getPassword() + + if "altcaptcha" in self.html: + for _i in xrange(5): + m = re.search(self.SOLVEMEDIA_PATTERN, self.html) + if m: + captchaKey = m.group(1) + captcha = SolveMedia(self) + captchaProvider = "Solvemedia" + else: + self.fail(_("Error parsing captcha")) + + response, challenge = captcha.challenge(captchaKey) + postData['adcopy_challenge'] = challenge + postData['adcopy_response'] = response + + self.html = self.load(url, post=postData) + if "The password you entered was incorrect" in self.html: + self.fail(_("Incorrect Password")) + if not "The CAPTCHA code you entered was wrong" in self.html: + break + + pyfile.package().password = "" + soup = BeautifulSoup(self.html) + scripts = soup.findAll("script") + for s in scripts: + if "d_links" in s.text: + break + m = re.search('d_links":(\[.*?\])', s.text) + if m: + linkDict = json_loads(m.group(1)) + for link in linkDict: + if not "http://" in link['full']: + self.urls.append("https://safelinking.net/d/" + link['full']) + else: + self.urls.append(link['full']) diff --git a/pyload/plugin/crypter/SecuredIn.py b/pyload/plugin/crypter/SecuredIn.py new file mode 100644 index 000000000..23bcea8ab --- /dev/null +++ b/pyload/plugin/crypter/SecuredIn.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadCrypter import DeadCrypter + + +class SecuredIn(DeadCrypter): + __name = "SecuredIn" + __type = "crypter" + __version = "0.21" + + __pattern = r'http://(?:www\.)?secured\.in/download-[\d]+-\w{8}\.html' + __config = [] + + __description = """Secured.in decrypter plugin""" + __license = "GPLv3" + __authors = [("mkaay", "mkaay@mkaay.de")] diff --git a/pyload/plugin/crypter/SexuriaCom.py b/pyload/plugin/crypter/SexuriaCom.py new file mode 100644 index 000000000..7a104ce38 --- /dev/null +++ b/pyload/plugin/crypter/SexuriaCom.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.Crypter import Crypter + + +class SexuriaCom(Crypter): + __name = "SexuriaCom" + __type = "crypter" + __version = "0.01" + + __pattern = r'http://(?:www\.)?sexuria\.com/(v1/)?(Pornos_Kostenlos_.+?_(\d+)\.html|dl_links_\d+_\d+\.html|id=\d+\&part=\d+\&link=\d+)' + __config = [("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Sexuria.com decrypter plugin""" + __license = "GPLv3" + __authors = [("NETHead", "NETHead.AT.gmx.DOT.net")] + + + PATTERN_SUPPORTED_MAIN = re.compile(r'http://(www\.)?sexuria\.com/(v1/)?Pornos_Kostenlos_.+?_(\d+)\.html', re.I) + PATTERN_SUPPORTED_CRYPT = re.compile(r'http://(www\.)?sexuria\.com/(v1/)?dl_links_\d+_(?P<ID>\d+)\.html', re.I) + PATTERN_SUPPORTED_REDIRECT = re.compile(r'http://(www\.)?sexuria\.com/out\.php\?id=(?P<ID>\d+)\&part=\d+\&link=\d+', re.I) + PATTERN_TITLE = re.compile(r'<title> - (?P<TITLE>.*) Sexuria - Kostenlose Pornos - Rapidshare XXX Porn</title>', re.I) + PATTERN_PASSWORD = re.compile(r'<strong>Passwort: </strong></div></td>.*?bgcolor="#EFEFEF">(?P<PWD>.*?)</td>', re.I | re.S) + PATTERN_DL_LINK_PAGE = re.compile(r'"(dl_links_\d+_\d+\.html)"', re.I) + PATTERN_REDIRECT_LINKS = re.compile(r'value="(http://sexuria\.com/out\.php\?id=\d+\&part=\d+\&link=\d+)" readonly', re.I) + + + def decrypt(self, pyfile): + # Init + self.pyfile = pyfile + self.package = pyfile.package() + + # Get package links + package_name, self.links, folder_name, package_pwd = self.decryptLinks(self.pyfile.url) + self.packages = [(package_name, self.links, folder_name)] + + + def decryptLinks(self, url): + linklist = [] + name = self.package.name + folder = self.package.folder + password = None + + if re.match(self.PATTERN_SUPPORTED_MAIN, url): + # Processing main page + html = self.load(url) + links = re.findall(self.PATTERN_DL_LINK_PAGE, html) + for link in links: + linklist.append("http://sexuria.com/v1/" + link) + + elif re.match(self.PATTERN_SUPPORTED_REDIRECT, url): + # Processing direct redirect link (out.php), redirecting to main page + id = re.search(self.PATTERN_SUPPORTED_REDIRECT, url).group('ID') + if id: + linklist.append("http://sexuria.com/v1/Pornos_Kostenlos_liebe_%s.html" % id) + + elif re.match(self.PATTERN_SUPPORTED_CRYPT, url): + # Extract info from main file + id = re.search(self.PATTERN_SUPPORTED_CRYPT, url).group('ID') + html = self.load("http://sexuria.com/v1/Pornos_Kostenlos_info_%s.html" % id, decode=True) + + title = re.search(self.PATTERN_TITLE, html).group('TITLE').strip() + if title: + name = folder = title + self.logDebug("Package info found, name [%s] and folder [%s]" % (name, folder)) + + pwd = re.search(self.PATTERN_PASSWORD, html).group('PWD') + if pwd: + password = pwd.strip() + self.logDebug("Password info [%s] found" % password) + + # Process link (dl_link) + html = self.load(url) + links = re.findall(self.PATTERN_REDIRECT_LINKS, html) + if len(links) == 0: + self.LogError("Broken for link %s" % link) + else: + for link in links: + link = link.replace("http://sexuria.com/", "http://www.sexuria.com/") + finallink = self.load(link, just_header=True)['location'] + if not finallink or "sexuria.com/" in finallink: + self.LogError("Broken for link %s" % link) + else: + linklist.append(finallink) + + # Debug log + self.logDebug("%d supported links" % len(linklist)) + for i, link in enumerate(linklist): + self.logDebug("Supported link %d, %s" % (i + 1, link)) + + return name, linklist, folder, password diff --git a/pyload/plugin/crypter/ShareLinksBiz.py b/pyload/plugin/crypter/ShareLinksBiz.py new file mode 100644 index 000000000..8add5214d --- /dev/null +++ b/pyload/plugin/crypter/ShareLinksBiz.py @@ -0,0 +1,279 @@ +# -*- coding: utf-8 -*- + +import binascii +import re + +from Crypto.Cipher import AES +from pyload.plugin.Crypter import Crypter + + +class ShareLinksBiz(Crypter): + __name = "ShareLinksBiz" + __type = "crypter" + __version = "1.14" + + __pattern = r'http://(?:www\.)?(share-links|s2l)\.biz/(?P<ID>_?\w+)' + __config = [("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Share-Links.biz decrypter plugin""" + __license = "GPLv3" + __authors = [("fragonib", "fragonib[AT]yahoo[DOT]es")] + + + def setup(self): + self.baseUrl = None + self.fileId = None + self.package = None + self.captcha = False + + + def decrypt(self, pyfile): + # Init + self.initFile(pyfile) + + # Request package + url = self.baseUrl + '/' + self.fileId + self.html = self.load(url, decode=True) + + # Unblock server (load all images) + self.unblockServer() + + # Check for protection + if self.isPasswordProtected(): + self.unlockPasswordProtection() + self.handleErrors() + + if self.isCaptchaProtected(): + self.captcha = True + self.unlockCaptchaProtection() + self.handleErrors() + + # Extract package links + package_links = [] + package_links.extend(self.handleWebLinks()) + package_links.extend(self.handleContainers()) + package_links.extend(self.handleCNL2()) + package_links = set(package_links) + + # Get package info + package_name, package_folder = self.getPackageInfo() + + # Pack + self.packages = [(package_name, package_links, package_folder)] + + + def initFile(self, pyfile): + url = pyfile.url + if 's2l.biz' in url: + url = self.load(url, just_header=True)['location'] + self.baseUrl = "http://www.%s.biz" % re.match(self.__pattern, url).group(1) + self.fileId = re.match(self.__pattern, url).group('ID') + self.package = pyfile.package() + + + def isOnline(self): + if "No usable content was found" in self.html: + self.logDebug("File not found") + return False + return True + + + def isPasswordProtected(self): + if re.search(r'''<form.*?id="passwordForm".*?>''', self.html): + self.logDebug("Links are protected") + return True + return False + + + def isCaptchaProtected(self): + if '<map id="captchamap"' in self.html: + self.logDebug("Links are captcha protected") + return True + return False + + + def unblockServer(self): + imgs = re.findall(r"(/template/images/.*?\.gif)", self.html) + for img in imgs: + self.load(self.baseUrl + img) + + + def unlockPasswordProtection(self): + password = self.getPassword() + self.logDebug("Submitting password [%s] for protected links" % password) + post = {"password": password, 'login': 'Submit form'} + url = self.baseUrl + '/' + self.fileId + self.html = self.load(url, post=post, decode=True) + + + def unlockCaptchaProtection(self): + # Get captcha map + captchaMap = self._getCaptchaMap() + self.logDebug("Captcha map with [%d] positions" % len(captchaMap.keys())) + + # Request user for captcha coords + m = re.search(r'<img src="/captcha.gif\?d=(.*?)&PHPSESSID=(.*?)&legend=1"', self.html) + captchaUrl = self.baseUrl + '/captcha.gif?d=%s&PHPSESSID=%s' % (m.group(1), m.group(2)) + self.logDebug("Waiting user for correct position") + coords = self.decryptCaptcha(captchaUrl, forceUser=True, imgtype="gif", result_type='positional') + self.logDebug("Captcha resolved, coords [%s]" % str(coords)) + + # Resolve captcha + href = self._resolveCoords(coords, captchaMap) + if href is None: + self.invalidCaptcha() + self.retry(wait_time=5) + url = self.baseUrl + href + self.html = self.load(url, decode=True) + + + def _getCaptchaMap(self): + mapp = {} + for m in re.finditer(r'<area shape="rect" coords="(.*?)" href="(.*?)"', self.html): + rect = eval('(' + m.group(1) + ')') + href = m.group(2) + mapp[rect] = href + return mapp + + + def _resolveCoords(self, coords, captchaMap): + x, y = coords + for rect, href in captchaMap.iteritems(): + x1, y1, x2, y2 = rect + if (x >= x1 and x <= x2) and (y >= y1 and y <= y2): + return href + + + def handleErrors(self): + if "The inserted password was wrong" in self.html: + self.logDebug("Incorrect password, please set right password on 'Edit package' form and retry") + self.fail(_("Incorrect password, please set right password on 'Edit package' form and retry")) + + if self.captcha: + if "Your choice was wrong" in self.html: + self.invalidCaptcha() + self.retry(wait_time=5) + else: + self.correctCaptcha() + + + def getPackageInfo(self): + name = folder = None + + # Extract from web package header + title_re = r'<h2><img.*?/>(.*)</h2>' + m = re.search(title_re, self.html, re.S) + if m: + title = m.group(1).strip() + if 'unnamed' not in title: + name = folder = title + self.logDebug("Found name [%s] and folder [%s] in package info" % (name, folder)) + + # Fallback to defaults + if not name or not folder: + name = self.package.name + folder = self.package.folder + self.logDebug("Package info not found, defaulting to pyfile name [%s] and folder [%s]" % (name, folder)) + + # Return package info + return name, folder + + + def handleWebLinks(self): + package_links = [] + self.logDebug("Handling Web links") + + #@TODO: Gather paginated web links + pattern = r'javascript:_get\(\'(.*?)\', \d+, \'\'\)' + ids = re.findall(pattern, self.html) + self.logDebug("Decrypting %d Web links" % len(ids)) + for i, ID in enumerate(ids): + try: + self.logDebug("Decrypting Web link %d, [%s]" % (i + 1, ID)) + + dwLink = self.baseUrl + "/get/lnk/" + ID + res = self.load(dwLink) + + code = re.search(r'frm/(\d+)', res).group(1) + fwLink = self.baseUrl + "/get/frm/" + code + res = self.load(fwLink) + + jscode = re.search(r'<script language="javascript">\s*eval\((.*)\)\s*</script>', res, re.S).group(1) + jscode = self.js.eval("f = %s" % jscode) + jslauncher = "window=''; parent={frames:{Main:{location:{href:''}}},location:''}; %s; parent.frames.Main.location.href" + + dlLink = self.js.eval(jslauncher % jscode) + + self.logDebug("JsEngine returns value [%s] for redirection link" % dlLink) + + package_links.append(dlLink) + except Exception, detail: + self.logDebug("Error decrypting Web link [%s], %s" % (ID, detail)) + return package_links + + + def handleContainers(self): + package_links = [] + self.logDebug("Handling Container links") + + pattern = r'javascript:_get\(\'(.*?)\', 0, \'(rsdf|ccf|dlc)\'\)' + containersLinks = re.findall(pattern, self.html) + self.logDebug("Decrypting %d Container links" % len(containersLinks)) + for containerLink in containersLinks: + link = "%s/get/%s/%s" % (self.baseUrl, containerLink[1], containerLink[0]) + package_links.append(link) + return package_links + + + def handleCNL2(self): + package_links = [] + self.logDebug("Handling CNL2 links") + + if '/lib/cnl2/ClicknLoad.swf' in self.html: + try: + (crypted, jk) = self._getCipherParams() + package_links.extend(self._getLinks(crypted, jk)) + except Exception: + self.fail(_("Unable to decrypt CNL2 links")) + return package_links + + + def _getCipherParams(self): + # Request CNL2 + code = re.search(r'ClicknLoad.swf\?code=(.*?)"', self.html).group(1) + url = "%s/get/cnl2/%s" % (self.baseUrl, code) + res = self.load(url) + params = res.split(";;") + + # Get jk + strlist = list(params[1].decode('base64')) + jk = ''.join(strlist[::-1]) + + # Get crypted + strlist = list(params[2].decode('base64')) + crypted = ''.join(strlist[::-1]) + + # Log and return + return crypted, jk + + + def _getLinks(self, crypted, jk): + # Get key + jreturn = self.js.eval("%s f()" % jk) + self.logDebug("JsEngine returns value [%s]" % jreturn) + key = binascii.unhexlify(jreturn) + + # Decrypt + Key = key + IV = key + obj = AES.new(Key, AES.MODE_CBC, IV) + text = obj.decrypt(crypted.decode('base64')) + + # Extract links + text = text.replace("\x00", "").replace("\r", "") + links = filter(bool, text.split('\n')) + + # Log and return + self.logDebug("Block has %d links" % len(links)) + return links diff --git a/pyload/plugin/crypter/SharingmatrixCom.py b/pyload/plugin/crypter/SharingmatrixCom.py new file mode 100644 index 000000000..4224bbd2d --- /dev/null +++ b/pyload/plugin/crypter/SharingmatrixCom.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadCrypter import DeadCrypter + + +class SharingmatrixCom(DeadCrypter): + __name = "SharingmatrixCom" + __type = "crypter" + __version = "0.01" + + __pattern = r'http://(?:www\.)?sharingmatrix\.com/folder/\w+' + __config = [] + + __description = """Sharingmatrix.com folder decrypter plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] diff --git a/pyload/plugin/crypter/SpeedLoadOrg.py b/pyload/plugin/crypter/SpeedLoadOrg.py new file mode 100644 index 000000000..32677e9ef --- /dev/null +++ b/pyload/plugin/crypter/SpeedLoadOrg.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadCrypter import DeadCrypter + + +class SpeedLoadOrg(DeadCrypter): + __name = "SpeedLoadOrg" + __type = "crypter" + __version = "0.30" + + __pattern = r'http://(?:www\.)?speedload\.org/(\d+~f$|folder/\d+/)' + __config = [] + + __description = """Speedload decrypter plugin""" + __license = "GPLv3" + __authors = [("stickell", "l.stickell@yahoo.it")] diff --git a/pyload/plugin/crypter/StealthTo.py b/pyload/plugin/crypter/StealthTo.py new file mode 100644 index 000000000..b974a169c --- /dev/null +++ b/pyload/plugin/crypter/StealthTo.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadCrypter import DeadCrypter + + +class StealthTo(DeadCrypter): + __name = "StealthTo" + __type = "crypter" + __version = "0.20" + + __pattern = r'http://(?:www\.)?stealth\.to/folder/.+' + __config = [] + + __description = """Stealth.to decrypter plugin""" + __license = "GPLv3" + __authors = [("spoob", "spoob@pyload.org")] diff --git a/pyload/plugin/crypter/TnyCz.py b/pyload/plugin/crypter/TnyCz.py new file mode 100644 index 000000000..8de611a8e --- /dev/null +++ b/pyload/plugin/crypter/TnyCz.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.SimpleCrypter import SimpleCrypter + +import re + + +class TnyCz(SimpleCrypter): + __name = "TnyCz" + __type = "crypter" + __version = "0.03" + + __pattern = r'http://(?:www\.)?tny\.cz/\w+' + __config = [("use_premium" , "bool", "Use premium account if available" , True), + ("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Tny.cz decrypter plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + NAME_PATTERN = r'<title>(?P<N>.+) - .+</title>' + + + def getLinks(self): + m = re.search(r'<a id=\'save_paste\' href="(.+save\.php\?hash=.+)">', self.html) + return re.findall(".+", self.load(m.group(1), decode=True)) if m else None diff --git a/pyload/plugin/crypter/TrailerzoneInfo.py b/pyload/plugin/crypter/TrailerzoneInfo.py new file mode 100644 index 000000000..cad464d47 --- /dev/null +++ b/pyload/plugin/crypter/TrailerzoneInfo.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadCrypter import DeadCrypter + + +class TrailerzoneInfo(DeadCrypter): + __name = "TrailerzoneInfo" + __type = "crypter" + __version = "0.03" + + __pattern = r'http://(?:www\.)?trailerzone\.info/.+' + __config = [] + + __description = """TrailerZone.info decrypter plugin""" + __license = "GPLv3" + __authors = [("godofdream", "soilfiction@gmail.com")] diff --git a/pyload/plugin/crypter/TurbobitNet.py b/pyload/plugin/crypter/TurbobitNet.py new file mode 100644 index 000000000..d00380d71 --- /dev/null +++ b/pyload/plugin/crypter/TurbobitNet.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.internal.SimpleCrypter import SimpleCrypter +from pyload.utils import json_loads + + +class TurbobitNet(SimpleCrypter): + __name = "TurbobitNet" + __type = "crypter" + __version = "0.05" + + __pattern = r'http://(?:www\.)?turbobit\.net/download/folder/(?P<ID>\w+)' + __config = [("use_premium" , "bool", "Use premium account if available" , True), + ("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Turbobit.net folder decrypter plugin""" + __license = "GPLv3" + __authors = [("stickell", "l.stickell@yahoo.it"), + ("Walter Purcaro", "vuolter@gmail.com")] + + + NAME_PATTERN = r'src=\'/js/lib/grid/icon/folder.png\'> <span>(?P<N>.+?)</span>' + + + def _getLinks(self, id, page=1): + gridFile = self.load("http://turbobit.net/downloadfolder/gridFile", + get={"rootId": id, "rows": 200, "page": page}, decode=True) + grid = json_loads(gridFile) + + if grid['rows']: + for i in grid['rows']: + yield i['id'] + for id in self._getLinks(id, page + 1): + yield id + else: + return + + + def getLinks(self): + id = re.match(self.__pattern, self.pyfile.url).group('ID') + fixurl = lambda id: "http://turbobit.net/%s.html" % id + return map(fixurl, self._getLinks(id)) diff --git a/pyload/plugin/crypter/TusfilesNet.py b/pyload/plugin/crypter/TusfilesNet.py new file mode 100644 index 000000000..23d305003 --- /dev/null +++ b/pyload/plugin/crypter/TusfilesNet.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- + +import math +import re +import urlparse + +from pyload.plugin.internal.XFSCrypter import XFSCrypter + + +class TusfilesNet(XFSCrypter): + __name = "TusfilesNet" + __type = "crypter" + __version = "0.08" + + __pattern = r'https?://(?:www\.)?tusfiles\.net/go/(?P<ID>\w+)' + __config = [("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Tusfiles.net folder decrypter plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com"), + ("stickell", "l.stickell@yahoo.it")] + + + PAGES_PATTERN = r'>\((\d+) \w+\)<' + + URL_REPLACEMENTS = [(__pattern + ".*", r'https://www.tusfiles.net/go/\g<ID>/')] + + + def loadPage(self, page_n): + return self.load(urlparse.urljoin(self.pyfile.url, str(page_n)), decode=True) + + + def handlePages(self, pyfile): + pages = re.search(self.PAGES_PATTERN, self.html) + if pages: + pages = int(math.ceil(int(pages.group('pages')) / 25.0)) + else: + return + + for p in xrange(2, pages + 1): + self.html = self.loadPage(p) + self.links += self.getLinks() diff --git a/pyload/plugin/crypter/UlozTo.py b/pyload/plugin/crypter/UlozTo.py new file mode 100644 index 000000000..f2f6745c2 --- /dev/null +++ b/pyload/plugin/crypter/UlozTo.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- + +import re +from pyload.plugin.Crypter import Crypter + + +class UlozTo(Crypter): + __name = "UlozTo" + __type = "crypter" + __version = "0.20" + + __pattern = r'http://(?:www\.)?(uloz\.to|ulozto\.(cz|sk|net)|bagruj\.cz|zachowajto\.pl)/(m|soubory)/.+' + __config = [("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Uloz.to folder decrypter plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + FOLDER_PATTERN = r'<ul class="profile_files">(.*?)</ul>' + LINK_PATTERN = r'<br /><a href="/(.+?)">.+?</a>' + NEXT_PAGE_PATTERN = r'<a class="next " href="/(.+?)"> </a>' + + + def decrypt(self, pyfile): + html = self.load(pyfile.url) + + new_links = [] + for i in xrange(1, 100): + self.logInfo(_("Fetching links from page %i") % i) + m = re.search(self.FOLDER_PATTERN, html, re.S) + if m is None: + self.error(_("FOLDER_PATTERN not found")) + + new_links.extend(re.findall(self.LINK_PATTERN, m.group(1))) + m = re.search(self.NEXT_PAGE_PATTERN, html) + if m: + html = self.load("http://ulozto.net/" + m.group(1)) + else: + break + else: + self.logInfo(_("Limit of 99 pages reached, aborting")) + + if new_links: + self.urls = [map(lambda s: "http://ulozto.net/%s" % s, new_links)] diff --git a/pyload/plugin/crypter/UploadableCh.py b/pyload/plugin/crypter/UploadableCh.py new file mode 100644 index 000000000..45e57deb2 --- /dev/null +++ b/pyload/plugin/crypter/UploadableCh.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.SimpleCrypter import SimpleCrypter + + +class UploadableCh(SimpleCrypter): + __name = "UploadableCh" + __type = "crypter" + __version = "0.03" + + __pattern = r'http://(?:www\.)?uploadable\.ch/list/\w+' + __config = [("use_premium" , "bool", "Use premium account if available" , True), + ("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Uploadable.ch folder decrypter plugin""" + __license = "GPLv3" + __authors = [("guidobelix", "guidobelix@hotmail.it"), + ("Walter Purcaro", "vuolter@gmail.com")] + + + LINK_PATTERN = r'"(.+?)" class="icon_zipfile">' + NAME_PATTERN = r'<div class="folder"><span> </span>(?P<N>.+?)</div>' + OFFLINE_PATTERN = r'We are sorry... The URL you entered cannot be found on the server.' + TEMP_OFFLINE_PATTERN = r'<div class="icon_err">' diff --git a/pyload/plugin/crypter/UploadedTo.py b/pyload/plugin/crypter/UploadedTo.py new file mode 100644 index 000000000..895579a26 --- /dev/null +++ b/pyload/plugin/crypter/UploadedTo.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- + +import re +import urlparse + +from pyload.plugin.internal.SimpleCrypter import SimpleCrypter + + +class UploadedTo(SimpleCrypter): + __name = "UploadedTo" + __type = "crypter" + __version = "0.42" + + __pattern = r'http://(?:www\.)?(uploaded|ul)\.(to|net)/(f|folder|list)/(?P<ID>\w+)' + __config = [("use_premium" , "bool", "Use premium account if available" , True), + ("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """UploadedTo decrypter plugin""" + __license = "GPLv3" + __authors = [("stickell", "l.stickell@yahoo.it")] + + + PLAIN_PATTERN = r'<small class="date"><a href="([\w/]+)" onclick=' + NAME_PATTERN = r'<title>(?P<N>.+?)<' + + + def getLinks(self): + m = re.search(self.PLAIN_PATTERN, self.html) + if m is None: + self.error(_("PLAIN_PATTERN not found")) + + plain_link = urlparse.urljoin("http://uploaded.net/", m.group(1)) + return self.load(plain_link).split('\n')[:-1] diff --git a/pyload/plugin/crypter/WiiReloadedOrg.py b/pyload/plugin/crypter/WiiReloadedOrg.py new file mode 100644 index 000000000..1fed5e4d2 --- /dev/null +++ b/pyload/plugin/crypter/WiiReloadedOrg.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadCrypter import DeadCrypter + + +class WiiReloadedOrg(DeadCrypter): + __name = "WiiReloadedOrg" + __type = "crypter" + __version = "0.11" + + __pattern = r'http://(?:www\.)?wii-reloaded\.org/protect/get\.php\?i=.+' + __config = [] + + __description = """Wii-Reloaded.org decrypter plugin""" + __license = "GPLv3" + __authors = [("hzpz", "")] diff --git a/pyload/plugin/crypter/WuploadCom.py b/pyload/plugin/crypter/WuploadCom.py new file mode 100644 index 000000000..29e2c97c5 --- /dev/null +++ b/pyload/plugin/crypter/WuploadCom.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadCrypter import DeadCrypter + + +class WuploadCom(DeadCrypter): + __name = "WuploadCom" + __type = "crypter" + __version = "0.01" + + __pattern = r'http://(?:www\.)?wupload\.com/folder/\w+' + __config = [] + + __description = """Wupload.com folder decrypter plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] diff --git a/pyload/plugin/crypter/XFileSharingPro.py b/pyload/plugin/crypter/XFileSharingPro.py new file mode 100644 index 000000000..b78d83262 --- /dev/null +++ b/pyload/plugin/crypter/XFileSharingPro.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.internal.XFSCrypter import XFSCrypter + + +class XFileSharingPro(XFSCrypter): + __name = "XFileSharingPro" + __type = "crypter" + __version = "0.05" + + __pattern = r'^unmatchable$' + __config = [("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """XFileSharingPro dummy folder decrypter plugin for hook""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + def _log(self, type, args): + msg = " | ".join(str(a).strip() for a in args if a) + logger = getattr(self.log, type) + logger("%s: %s: %s" % (self.getClassName(), self.HOSTER_NAME, msg or _("%s MARK" % type.upper()))) + + + def init(self): + super(XFileSharingPro, self).init() + + self.__pattern = self.core.pluginManager.crypterPlugins[self.getClassName()]['pattern'] + + self.HOSTER_DOMAIN = re.match(self.__pattern, self.pyfile.url).group("DOMAIN").lower() + self.HOSTER_NAME = "".join(part.capitalize() for part in re.split(r'(\.|\d+)', self.HOSTER_DOMAIN) if part != '.') + + account = self.core.accountManager.getAccountPlugin(self.HOSTER_NAME) + + if account and account.canUse(): + self.account = account + + elif self.account: + self.account.HOSTER_DOMAIN = self.HOSTER_DOMAIN + + else: + return + + self.user, data = self.account.selectAccount() + self.req = self.account.getAccountRequest(self.user) + self.premium = self.account.isPremium(self.user) diff --git a/pyload/plugin/crypter/XupPl.py b/pyload/plugin/crypter/XupPl.py new file mode 100644 index 000000000..6dd9c3df8 --- /dev/null +++ b/pyload/plugin/crypter/XupPl.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.Crypter import Crypter + + +class XupPl(Crypter): + __name = "XupPl" + __type = "crypter" + __version = "0.10" + + __pattern = r'https?://(?:[^/]*\.)?xup\.pl/.+' + __config = [("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Xup.pl decrypter plugin""" + __license = "GPLv3" + __authors = [("z00nx", "z00nx0@gmail.com")] + + + def decrypt(self, pyfile): + header = self.load(pyfile.url, just_header=True) + if 'location' in header: + self.urls = [header['location']] + else: + self.fail(_("Unable to find link")) diff --git a/pyload/plugin/crypter/YoutubeComFolder.py b/pyload/plugin/crypter/YoutubeComFolder.py new file mode 100644 index 000000000..6873c9148 --- /dev/null +++ b/pyload/plugin/crypter/YoutubeComFolder.py @@ -0,0 +1,147 @@ +# -*- coding: utf-8 -*- + +import re +import urlparse + +from pyload.utils import json_loads +from pyload.plugin.Crypter import Crypter +from pyload.utils import fs_join + + +class YoutubeComFolder(Crypter): + __name = "YoutubeComFolder" + __type = "crypter" + __version = "1.01" + + __pattern = r'https?://(?:www\.|m\.)?youtube\.com/(?P<TYPE>user|playlist|view_play_list)(/|.*?[?&](?:list|p)=)(?P<ID>[\w-]+)' + __config = [("use_subfolder" , "bool", "Save package to subfolder" , True ), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True ), + ("likes" , "bool", "Grab user (channel) liked videos" , False), + ("favorites" , "bool", "Grab user (channel) favorite videos", False), + ("uploads" , "bool", "Grab channel unplaylisted videos" , True )] + + __description = """Youtube.com channel & playlist decrypter plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + API_KEY = "AIzaSyCKnWLNlkX-L4oD1aEzqqhRw1zczeD6_k0" + + + def api_response(self, ref, req): + req.update({"key": self.API_KEY}) + url = urlparse.urljoin("https://www.googleapis.com/youtube/v3/", ref) + html = self.load(url, get=req) + return json_loads(html) + + + def getChannel(self, user): + channels = self.api_response("channels", {"part": "id,snippet,contentDetails", "forUsername": user, "maxResults": "50"}) + if channels['items']: + channel = channels['items'][0] + return {"id": channel['id'], + "title": channel['snippet']['title'], + "relatedPlaylists": channel['contentDetails']['relatedPlaylists'], + "user": user} #: One lone channel for user? + + + def getPlaylist(self, p_id): + playlists = self.api_response("playlists", {"part": "snippet", "id": p_id}) + if playlists['items']: + playlist = playlists['items'][0] + return {"id": p_id, + "title": playlist['snippet']['title'], + "channelId": playlist['snippet']['channelId'], + "channelTitle": playlist['snippet']['channelTitle']} + + + def _getPlaylists(self, id, token=None): + req = {"part": "id", "maxResults": "50", "channelId": id} + if token: + req.update({"pageToken": token}) + + playlists = self.api_response("playlists", req) + + for playlist in playlists['items']: + yield playlist['id'] + + if "nextPageToken" in playlists: + for item in self._getPlaylists(id, playlists['nextPageToken']): + yield item + + + def getPlaylists(self, ch_id): + return map(self.getPlaylist, self._getPlaylists(ch_id)) + + + def _getVideosId(self, id, token=None): + req = {"part": "contentDetails", "maxResults": "50", "playlistId": id} + if token: + req.update({"pageToken": token}) + + playlist = self.api_response("playlistItems", req) + + for item in playlist['items']: + yield item['contentDetails']['videoId'] + + if "nextPageToken" in playlist: + for item in self._getVideosId(id, playlist['nextPageToken']): + yield item + + + def getVideosId(self, p_id): + return list(self._getVideosId(p_id)) + + + def decrypt(self, pyfile): + m = re.match(self.__pattern, pyfile.url) + m_id = m.group('ID') + m_type = m.group('TYPE') + + if m_type == "user": + self.logDebug("Url recognized as Channel") + user = m_id + channel = self.getChannel(user) + + if channel: + playlists = self.getPlaylists(channel['id']) + self.logDebug("%s playlist\s found on channel \"%s\"" % (len(playlists), channel['title'])) + + relatedplaylist = {p_name: self.getPlaylist(p_id) for p_name, p_id in channel['relatedPlaylists'].iteritems()} + self.logDebug("Channel's related playlists found = %s" % relatedplaylist.keys()) + + relatedplaylist['uploads']['title'] = "Unplaylisted videos" + relatedplaylist['uploads']['checkDups'] = True #: checkDups flag + + for p_name, p_data in relatedplaylist.iteritems(): + if self.getConfig(p_name): + p_data['title'] += " of " + user + playlists.append(p_data) + else: + playlists = [] + else: + self.logDebug("Url recognized as Playlist") + playlists = [self.getPlaylist(m_id)] + + if not playlists: + self.fail(_("No playlist available")) + + addedvideos = [] + urlize = lambda x: "https://www.youtube.com/watch?v=" + x + for p in playlists: + p_name = p['title'] + p_videos = self.getVideosId(p['id']) + p_folder = fs_join(self.config.get("general", "download_folder"), p['channelTitle'], p_name) + self.logDebug("%s video\s found on playlist \"%s\"" % (len(p_videos), p_name)) + + if not p_videos: + continue + elif "checkDups" in p: + p_urls = [urlize(v_id) for v_id in p_videos if v_id not in addedvideos] + self.logDebug("%s video\s available on playlist \"%s\" after duplicates cleanup" % (len(p_urls), p_name)) + else: + p_urls = map(urlize, p_videos) + + self.packages.append((p_name, p_urls, p_folder)) #: folder is NOT recognized by pyload 0.4.9! + + addedvideos.extend(p_videos) diff --git a/pyload/plugin/crypter/__init__.py b/pyload/plugin/crypter/__init__.py new file mode 100644 index 000000000..40a96afc6 --- /dev/null +++ b/pyload/plugin/crypter/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/pyload/plugin/extractor/SevenZip.py b/pyload/plugin/extractor/SevenZip.py new file mode 100644 index 000000000..cf5f7bfb8 --- /dev/null +++ b/pyload/plugin/extractor/SevenZip.py @@ -0,0 +1,154 @@ +# -*- coding: utf-8 -*- + +import os +import re +import subprocess + +from pyload.plugin.extractor.UnRar import ArchiveError, CRCError, PasswordError, UnRar, renice +from pyload.utils import fs_encode, fs_join + + +class SevenZip(UnRar): + __name = "SevenZip" + __type = "extractor" + __version = "0.11" + + __description = """7-Zip extractor plugin""" + __license = "GPLv3" + __authors = [("Michael Nowak" , ""), + ("Walter Purcaro", "vuolter@gmail.com")] + + + CMD = "7z" + NAME = __name__.rsplit('.', 1)[1] + VERSION = "" + + EXTENSIONS = [".7z", ".xz", ".zip", ".gz", ".gzip", ".tgz", ".bz2", ".bzip2", + ".tbz2", ".tbz", ".tar", ".wim", ".swm", ".lzma", ".rar", ".cab", + ".arj", ".z", ".taz", ".cpio", ".rpm", ".deb", ".lzh", ".lha", + ".chm", ".chw", ".hxs", ".iso", ".msi", ".doc", ".xls", ".ppt", + ".dmg", ".xar", ".hfs", ".exe", ".ntfs", ".fat", ".vhd", ".mbr", + ".squashfs", ".cramfs", ".scap"] + + + #@NOTE: there are some more uncovered 7z formats + re_filelist = re.compile(r'([\d\:]+)\s+([\d\:]+)\s+([\w\.]+)\s+(\d+)\s+(\d+)\s+(.+)') + re_wrongpwd = re.compile(r'(Can not open encrypted archive|Wrong password|Encrypted\s+\=\s+\+)', re.I) + re_wrongcrc = re.compile(r'CRC Failed|Can not open file', re.I) + re_version = re.compile(r'7-Zip\s(?:\[64\]\s)?(\d+\.\d+)', re.I) + + + @classmethod + def isUsable(cls): + if os.name == "nt": + cls.CMD = os.path.join(pypath, "7z.exe") + p = subprocess.Popen([cls.CMD], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = p.communicate() + else: + p = subprocess.Popen([cls.CMD], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = p.communicate() + + m = cls.re_version.search(out) + cls.VERSION = m.group(1) if m else '(version unknown)' + + return True + + + def verify(self, password): + # 7z can't distinguish crc and pw error in test + p = self.call_cmd("l", "-slt", fs_encode(self.filename)) + out, err = p.communicate() + + if self.re_wrongpwd.search(out): + raise PasswordError + + if self.re_wrongpwd.search(err): + raise PasswordError + + if self.re_wrongcrc.search(err): + raise CRCError(err) + + + def check(self, password): + p = self.call_cmd("l", "-slt", fs_encode(self.filename)) + out, err = p.communicate() + + # check if output or error macthes the 'wrong password'-Regexp + if self.re_wrongpwd.search(out): + raise PasswordError + + if self.re_wrongcrc.search(out): + raise CRCError(_("Header protected")) + + + def repair(self): + return False + + + def extract(self, password=None): + command = "x" if self.fullpath else "e" + + p = self.call_cmd(command, '-o' + self.out, fs_encode(self.filename), password=password) + + renice(p.pid, self.renice) + + # communicate and retrieve stderr + self._progress(p) + err = p.stderr.read().strip() + + if err: + if self.re_wrongpwd.search(err): + raise PasswordError + + elif self.re_wrongcrc.search(err): + raise CRCError(err) + + else: #: raise error if anything is on stderr + raise ArchiveError(err) + + if p.returncode > 1: + raise ArchiveError(_("Process return code: %d") % p.returncode) + + self.files = self.list(password) + + + def list(self, password=None): + command = "l" if self.fullpath else "l" + + p = self.call_cmd(command, fs_encode(self.filename), password=password) + out, err = p.communicate() + + if "Can not open" in err: + raise ArchiveError(_("Cannot open file")) + + if p.returncode > 1: + raise ArchiveError(_("Process return code: %d") % p.returncode) + + result = set() + for groups in self.re_filelist.findall(out): + f = groups[-1].strip() + result.add(fs_join(self.out, f)) + + return list(result) + + + def call_cmd(self, command, *xargs, **kwargs): + args = [] + + # overwrite flag + if self.overwrite: + args.append("-y") + + # set a password + if "password" in kwargs and kwargs['password']: + args.append("-p%s" % kwargs['password']) + else: + args.append("-p-") + + #@NOTE: return codes are not reliable, some kind of threading, cleanup whatever issue + call = [self.CMD, command] + args + list(xargs) + + self.manager.logDebug(" ".join(call)) + + p = subprocess.Popen(call, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + return p diff --git a/pyload/plugin/extractor/UnRar.py b/pyload/plugin/extractor/UnRar.py new file mode 100644 index 000000000..2efba9cd4 --- /dev/null +++ b/pyload/plugin/extractor/UnRar.py @@ -0,0 +1,243 @@ +# -*- coding: utf-8 -*- + +import os +import re +import string +import subprocess + +from pyload.plugin.Extractor import Extractor, ArchiveError, CRCError, PasswordError +from pyload.utils import fs_decode, fs_encode, fs_join + + +def renice(pid, value): + if value and os.name != "nt": + try: + subprocess.Popen(["renice", str(value), str(pid)], stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=-1) + + except Exception: + pass + + +class UnRar(Extractor): + __name = "UnRar" + __type = "extractor" + __version = "1.20" + + __description = """Rar extractor plugin""" + __license = "GPLv3" + __authors = [("RaNaN", "RaNaN@pyload.org"), + ("Walter Purcaro", "vuolter@gmail.com"), + ("Immenz", "immenz@gmx.net")] + + + CMD = "unrar" + NAME = __name__.rsplit('.', 1)[1] + VERSION = "" + EXTENSIONS = [".rar"] + + + re_multipart = re.compile(r'\.(part|r)(\d+)(?:\.rar)?(\.rev|\.bad)?', re.I) + + re_filefixed = re.compile(r'Building (.+)') + re_filelist = re.compile(r'^(.)(\s*[\w\.\-]+)\s+(\d+\s+)+(?:\d+\%\s+)?[\d\-]{8}\s+[\d\:]{5}', re.M | re.I) + + re_wrongpwd = re.compile(r'password', re.I) + re_wrongcrc = re.compile(r'encrypted|damaged|CRC failed|checksum error|corrupt', re.I) + + re_version = re.compile(r'(?:UN)?RAR\s(\d+\.\d+)', re.I) + + + @classmethod + def isUsable(cls): + if os.name == "nt": + try: + cls.CMD = os.path.join(pypath, "RAR.exe") + p = subprocess.Popen([cls.CMD], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = p.communicate() + cls.NAME = "RAR" + cls.REPAIR = True + + except OSError: + cls.CMD = os.path.join(pypath, "UnRAR.exe") + p = subprocess.Popen([cls.CMD], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = p.communicate() + else: + try: + p = subprocess.Popen(["rar"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = p.communicate() + cls.NAME = "RAR" + cls.REPAIR = True + + except OSError: #: fallback to unrar + p = subprocess.Popen([cls.CMD], stdout=subprocess.PIPE, stderr=subprocess.PIPE) + out, err = p.communicate() + + m = cls.re_version.search(out) + cls.VERSION = m.group(1) if m else '(version unknown)' + + return True + + + @classmethod + def isMultipart(cls, filename): + return cls.re_multipart.search(filename) is not None + + + def verify(self, password): + p = self.call_cmd("t", "-v", fs_encode(self.filename), password=password) + self._progress(p) + err = p.stderr.read().strip() + + if self.re_wrongpwd.search(err): + raise PasswordError + + if self.re_wrongcrc.search(err): + raise CRCError(err) + + + def check(self, password): + p = self.call_cmd("l", "-v", fs_encode(self.filename), password=password) + out, err = p.communicate() + + if self.re_wrongpwd.search(err): + raise PasswordError + + if self.re_wrongcrc.search(err): + raise CRCError(err) + + # output only used to check if passworded files are present + for attr in self.re_filelist.findall(out): + if attr[0].startswith("*"): + raise PasswordError + + + def repair(self): + p = self.call_cmd("rc", fs_encode(self.filename)) + + # communicate and retrieve stderr + self._progress(p) + err = p.stderr.read().strip() + if err or p.returncode: + return False + return True + + + def _progress(self, process): + s = "" + while True: + c = process.stdout.read(1) + # quit loop on eof + if not c: + break + # reading a percentage sign -> set progress and restart + if c == '%': + self.notifyProgress(int(s)) + s = "" + # not reading a digit -> therefore restart + elif c not in string.digits: + s = "" + # add digit to progressstring + else: + s += c + + + def extract(self, password=None): + command = "x" if self.fullpath else "e" + + p = self.call_cmd(command, fs_encode(self.filename), self.out, password=password) + + renice(p.pid, self.renice) + + # communicate and retrieve stderr + self._progress(p) + err = p.stderr.read().strip() + + if err: + if self.re_wrongpwd.search(err): + raise PasswordError + + elif self.re_wrongcrc.search(err): + raise CRCError(err) + + else: #: raise error if anything is on stderr + raise ArchiveError(err) + + if p.returncode: + raise ArchiveError(_("Process return code: %d") % p.returncode) + + self.files = self.list(password) + + + def getDeleteFiles(self): + dir, name = os.path.split(self.filename) + + # actually extracted file + files = [self.filename] + + # eventually Multipart Files + files.extend(fs_join(dir, os.path.basename(file)) for file in filter(self.isMultipart, os.listdir(dir)) + if re.sub(self.re_multipart, ".rar", name) == re.sub(self.re_multipart, ".rar", file)) + + return files + + + def list(self, password=None): + command = "vb" if self.fullpath else "lb" + + p = self.call_cmd(command, "-v", fs_encode(self.filename), password=password) + out, err = p.communicate() + + if "Cannot open" in err: + raise ArchiveError(_("Cannot open file")) + + if err.strip(): #: only log error at this point + self.manager.logError(err.strip()) + + result = set() + if not self.fullpath and self.VERSION.startswith('5'): + # NOTE: Unrar 5 always list full path + for f in fs_decode(out).splitlines(): + f = fs_join(self.out, os.path.basename(f.strip())) + if os.path.isfile(f): + result.add(fs_join(self.out, os.path.basename(f))) + else: + for f in fs_decode(out).splitlines(): + f = f.strip() + result.add(fs_join(self.out, f)) + + return list(result) + + + def call_cmd(self, command, *xargs, **kwargs): + args = [] + + # overwrite flag + if self.overwrite: + args.append("-o+") + else: + args.append("-o-") + if self.delete != 'No': + args.append("-or") + + for word in self.excludefiles: + args.append("-x'%s'" % word.strip()) + + # assume yes on all queries + args.append("-y") + + # set a password + if "password" in kwargs and kwargs['password']: + args.append("-p%s" % kwargs['password']) + else: + args.append("-p-") + + if self.keepbroken: + args.append("-kb") + + # NOTE: return codes are not reliable, some kind of threading, cleanup whatever issue + call = [self.CMD, command] + args + list(xargs) + + self.manager.logDebug(" ".join(call)) + + p = subprocess.Popen(call, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + return p diff --git a/pyload/plugin/extractor/UnZip.py b/pyload/plugin/extractor/UnZip.py new file mode 100644 index 000000000..8bfd63480 --- /dev/null +++ b/pyload/plugin/extractor/UnZip.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +import os +import sys +import zipfile + +from pyload.plugin.Extractor import Extractor, ArchiveError, CRCError, PasswordError +from pyload.utils import fs_encode + + +class UnZip(Extractor): + __name = "UnZip" + __type = "extractor" + __version = "1.12" + + __description = """Zip extractor plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + EXTENSIONS = [".zip", ".zip64"] + NAME = __name__.rsplit('.', 1)[1] + VERSION = "(python %s.%s.%s)" % (sys.version_info[0], sys.version_info[1], sys.version_info[2]) + + + @classmethod + def isUsable(cls): + return sys.version_info[:2] >= (2, 6) + + + def list(self, password=None): + with zipfile.ZipFile(fs_encode(self.filename), 'r', allowZip64=True) as z: + z.setpassword(password) + return z.namelist() + + + def check(self, password): + pass + + + def verify(self): + with zipfile.ZipFile(fs_encode(self.filename), 'r', allowZip64=True) as z: + badfile = z.testzip() + + if badfile: + raise CRCError(badfile) + else: + raise PasswordError + + + def extract(self, password=None): + try: + with zipfile.ZipFile(fs_encode(self.filename), 'r', allowZip64=True) as z: + z.setpassword(password) + + badfile = z.testzip() + + if badfile: + raise CRCError(badfile) + else: + z.extractall(self.out) + + except (zipfile.BadZipfile, zipfile.LargeZipFile), e: + raise ArchiveError(e) + + except RuntimeError, e: + if "encrypted" in e: + raise PasswordError + else: + raise ArchiveError(e) + else: + self.files = z.namelist() diff --git a/pyload/plugin/extractor/__init__.py b/pyload/plugin/extractor/__init__.py new file mode 100644 index 000000000..40a96afc6 --- /dev/null +++ b/pyload/plugin/extractor/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/pyload/plugin/hook/AlldebridCom.py b/pyload/plugin/hook/AlldebridCom.py new file mode 100644 index 000000000..141f86779 --- /dev/null +++ b/pyload/plugin/hook/AlldebridCom.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.MultiHook import MultiHook + + +class AlldebridCom(MultiHook): + __name = "AlldebridCom" + __type = "hook" + __version = "0.16" + + __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"), + ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ), + ("revertfailed" , "bool" , "Revert to standard download if fails", True ), + ("reload" , "bool" , "Reload plugin list" , True ), + ("reloadinterval", "int" , "Reload interval in hours" , 12 ), + ("ssl" , "bool" , "Use HTTPS" , True )] + + __description = """Alldebrid.com hook plugin""" + __license = "GPLv3" + __authors = [("Andy Voigt", "spamsales@online.de")] + + + def getHosters(self): + https = "https" if self.getConfig('ssl') else "http" + html = self.getURL(https + "://www.alldebrid.com/api.php", get={'action': "get_host"}).replace("\"", "").strip() + + return [x.strip() for x in html.split(",") if x.strip()] diff --git a/pyload/plugin/hook/BypassCaptcha.py b/pyload/plugin/hook/BypassCaptcha.py new file mode 100644 index 000000000..8e8a27d8c --- /dev/null +++ b/pyload/plugin/hook/BypassCaptcha.py @@ -0,0 +1,135 @@ +# -*- coding: utf-8 -*- + +import pycurl + +from pyload.network.HTTPRequest import BadHeader +from pyload.network.RequestFactory import getURL, getRequest +from pyload.plugin.Hook import Hook, threaded + + +class BypassCaptchaException(Exception): + + def __init__(self, err): + self.err = err + + + def getCode(self): + return self.err + + + def __str__(self): + return "<BypassCaptchaException %s>" % self.err + + + def __repr__(self): + return "<BypassCaptchaException %s>" % self.err + + +class BypassCaptcha(Hook): + __name = "BypassCaptcha" + __type = "hook" + __version = "0.06" + + __config = [("force", "bool", "Force BC even if client is connected", False), + ("passkey", "password", "Passkey", "")] + + __description = """Send captchas to BypassCaptcha.com""" + __license = "GPLv3" + __authors = [("RaNaN" , "RaNaN@pyload.org"), + ("Godofdream", "soilfcition@gmail.com"), + ("zoidberg" , "zoidberg@mujmail.cz")] + + + PYLOAD_KEY = "4f771155b640970d5607f919a615bdefc67e7d32" + + SUBMIT_URL = "http://bypasscaptcha.com/upload.php" + RESPOND_URL = "http://bypasscaptcha.com/check_value.php" + GETCREDITS_URL = "http://bypasscaptcha.com/ex_left.php" + + + def getCredits(self): + res = getURL(self.GETCREDITS_URL, post={"key": self.getConfig('passkey')}) + + data = dict(x.split(' ', 1) for x in res.splitlines()) + return int(data['Left']) + + + def submit(self, captcha, captchaType="file", match=None): + req = getRequest() + + # raise timeout threshold + req.c.setopt(pycurl.LOW_SPEED_TIME, 80) + + try: + res = req.load(self.SUBMIT_URL, + post={'vendor_key': self.PYLOAD_KEY, + 'key': self.getConfig('passkey'), + 'gen_task_id': "1", + 'file': (pycurl.FORM_FILE, captcha)}, + multipart=True) + finally: + req.close() + + data = dict(x.split(' ', 1) for x in res.splitlines()) + if not data or "Value" not in data: + raise BypassCaptchaException(res) + + result = data['Value'] + ticket = data['TaskId'] + self.logDebug("Result %s : %s" % (ticket, result)) + + return ticket, result + + + def respond(self, ticket, success): + try: + res = getURL(self.RESPOND_URL, post={"task_id": ticket, "key": self.getConfig('passkey'), + "cv": 1 if success else 0}) + except BadHeader, e: + self.logError(_("Could not send response"), e) + + + def captchaTask(self, task): + if "service" in task.data: + return False + + if not task.isTextual(): + return False + + if not self.getConfig('passkey'): + return False + + if self.core.isClientConnected() and not self.getConfig('force'): + return False + + if self.getCredits() > 0: + task.handler.append(self) + task.data['service'] = self.getClassName() + task.setWaiting(100) + self._processCaptcha(task) + + else: + self.logInfo(_("Your %s account has not enough credits") % self.getClassName()) + + + def captchaCorrect(self, task): + if task.data['service'] == self.getClassName() and "ticket" in task.data: + self.respond(task.data['ticket'], True) + + + def captchaInvalid(self, task): + if task.data['service'] == self.getClassName() and "ticket" in task.data: + self.respond(task.data['ticket'], False) + + + @threaded + def _processCaptcha(self, task): + c = task.captchaFile + try: + ticket, result = self.submit(c) + except BypassCaptchaException, e: + task.error = e.getCode() + return + + task.data['ticket'] = ticket + task.setResult(result) diff --git a/pyload/plugin/hook/Captcha9Kw.py b/pyload/plugin/hook/Captcha9Kw.py new file mode 100644 index 000000000..9ceab4b2b --- /dev/null +++ b/pyload/plugin/hook/Captcha9Kw.py @@ -0,0 +1,251 @@ +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +import re +import time + +from base64 import b64encode + +from pyload.network.HTTPRequest import BadHeader +from pyload.network.RequestFactory import getURL + +from pyload.plugin.Hook import Hook, threaded + + +class Captcha9Kw(Hook): + __name = "Captcha9Kw" + __type = "hook" + __version = "0.28" + + __config = [("ssl" , "bool" , "Use HTTPS" , True), + ("force" , "bool" , "Force captcha resolving even if client is connected" , True), + ("confirm" , "bool" , "Confirm Captcha (cost +6 credits)" , False), + ("captchaperhour", "int" , "Captcha per hour" , "9999"), + ("captchapermin" , "int" , "Captcha per minute" , "9999"), + ("prio" , "int" , "Priority (max 10)(cost +0 -> +10 credits)" , "0"), + ("queue" , "int" , "Max. Queue (max 999)" , "50"), + ("hoster_options", "string" , "Hoster options (format: pluginname:prio=1:selfsolfe=1:confirm=1:timeout=900|...)", "ShareonlineBiz:prio=0:timeout=999 | UploadedTo:prio=0:timeout=999"), + ("selfsolve" , "bool" , "Selfsolve (manually solve your captcha in your 9kw client if active)" , "0"), + ("passkey" , "password", "API key" , ""), + ("timeout" , "int" , "Timeout in seconds (min 60, max 3999)" , "900")] + + __description = """Send captchas to 9kw.eu""" + __license = "GPLv3" + __authors = [("RaNaN" , "RaNaN@pyload.org"), + ("Walter Purcaro", "vuolter@gmail.com")] + + + API_URL = "http://www.9kw.eu/index.cgi" + + + def activate(self): + if self.getConfig('ssl'): + self.API_URL = self.API_URL.replace("http://", "https://") + + + def getCredits(self): + res = getURL(self.API_URL, + get={'apikey': self.getConfig('passkey'), + 'pyload': "1", + 'source': "pyload", + 'action': "usercaptchaguthaben"}) + + if res.isdigit(): + self.logInfo(_("%s credits left") % res) + credits = self.info['credits'] = int(res) + return credits + else: + self.logError(res) + return 0 + + + @threaded + def _processCaptcha(self, task): + try: + with open(task.captchaFile, 'rb') as f: + data = f.read() + + except IOError, e: + self.logError(e) + return + + pluginname = re.search(r'_([^_]*)_\d+.\w+', task.captchaFile).group(1) + option = {'min' : 2, + 'max' : 50, + 'phrase' : 0, + 'numeric' : 0, + 'case_sensitive': 0, + 'math' : 0, + 'prio' : min(max(self.getConfig('prio'), 0), 10), + 'confirm' : self.getConfig('confirm'), + 'timeout' : min(max(self.getConfig('timeout'), 300), 3999), + 'selfsolve' : self.getConfig('selfsolve'), + 'cph' : self.getConfig('captchaperhour'), + 'cpm' : self.getConfig('captchapermin')} + + for opt in str(self.getConfig('hoster_options').split('|')): + + details = map(str.strip, opt.split(':')) + + if not details or details[0].lower() != pluginname.lower(): + continue + + for d in details: + hosteroption = d.split("=") + + if len(hosteroption) < 2 or not hosteroption[1].isdigit(): + continue + + o = hosteroption[0].lower() + if o in option: + option[o] = hosteroption[1] + + break + + post_data = {'apikey' : self.getConfig('passkey'), + 'prio' : option['prio'], + 'confirm' : option['confirm'], + 'maxtimeout' : option['timeout'], + 'selfsolve' : option['selfsolve'], + 'captchaperhour': option['cph'], + 'captchapermin' : option['cpm'], + 'case-sensitive': option['case_sensitive'], + 'min_len' : option['min'], + 'max_len' : option['max'], + 'phrase' : option['phrase'], + 'numeric' : option['numeric'], + 'math' : option['math'], + 'oldsource' : pluginname, + 'pyload' : "1", + 'source' : "pyload", + 'base64' : "1", + 'mouse' : 1 if task.isPositional() else 0, + 'file-upload-01': b64encode(data), + 'action' : "usercaptchaupload"} + + for _i in xrange(5): + try: + res = getURL(self.API_URL, post=post_data) + except BadHeader, e: + time.sleep(3) + else: + if res and res.isdigit(): + break + else: + self.logError(_("Bad upload: %s") % res) + return + + self.logDebug(_("NewCaptchaID ticket: %s") % res, task.captchaFile) + + task.data['ticket'] = res + + for _i in xrange(int(self.getConfig('timeout') / 5)): + result = getURL(self.API_URL, + get={'apikey': self.getConfig('passkey'), + 'id' : res, + 'pyload': "1", + 'info' : "1", + 'source': "pyload", + 'action': "usercaptchacorrectdata"}) + + if not result or result == "NO DATA": + time.sleep(5) + else: + break + else: + self.logDebug("Could not send request: %s" % res) + result = None + + self.logInfo(_("Captcha result for ticket %s: %s") % (res, result)) + + task.setResult(result) + + + def captchaTask(self, task): + if not task.isTextual() and not task.isPositional(): + return + + if not self.getConfig('passkey'): + return + + if self.core.isClientConnected() and not self.getConfig('force'): + return + + credits = self.getCredits() + + if not credits: + self.logError(_("Your captcha 9kw.eu account has not enough credits")) + return + + queue = min(self.getConfig('queue'), 999) + timeout = min(max(self.getConfig('timeout'), 300), 3999) + pluginname = re.search(r'_([^_]*)_\d+.\w+', task.captchaFile).group(1) + + for _i in xrange(5): + servercheck = getURL("http://www.9kw.eu/grafik/servercheck.txt") + if queue < re.search(r'queue=(\d+)', servercheck).group(1): + break + + time.sleep(10) + else: + self.fail(_("Too many captchas in queue")) + + for opt in str(self.getConfig('hoster_options').split('|')): + details = map(str.strip, opt.split(':')) + + if not details or details[0].lower() != pluginname.lower(): + continue + + for d in details: + hosteroption = d.split("=") + + if len(hosteroption) > 1 \ + and hosteroption[0].lower() == 'timeout' \ + and hosteroption[1].isdigit(): + timeout = int(hosteroption[1]) + + break + + task.handler.append(self) + + task.setWaiting(timeout) + + self._processCaptcha(task) + + + def _captchaResponse(self, task, correct): + type = "correct" if correct else "refund" + + if 'ticket' not in task.data: + self.logDebug("No CaptchaID for %s request (task: %s)" % (type, task)) + return + + passkey = self.getConfig('passkey') + + for _i in xrange(3): + res = getURL(self.API_URL, + get={'action' : "usercaptchacorrectback", + 'apikey' : passkey, + 'api_key': passkey, + 'correct': "1" if correct else "2", + 'pyload' : "1", + 'source' : "pyload", + 'id' : task.data['ticket']}) + + self.logDebug("Request %s: %s" % (type, res)) + + if res == "OK": + break + + time.sleep(5) + else: + self.logDebug("Could not send %s request: %s" % (type, res)) + + + def captchaCorrect(self, task): + self._captchaResponse(task, True) + + + def captchaInvalid(self, task): + self._captchaResponse(task, False) diff --git a/pyload/plugin/hook/CaptchaBrotherhood.py b/pyload/plugin/hook/CaptchaBrotherhood.py new file mode 100644 index 000000000..75c7d55b7 --- /dev/null +++ b/pyload/plugin/hook/CaptchaBrotherhood.py @@ -0,0 +1,171 @@ +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +import StringIO +import pycurl +import time +import urllib + +try: + from PIL import Image +except ImportError: + import Image + +from pyload.network.RequestFactory import getURL, getRequest +from pyload.plugin.Hook import Hook, threaded + + +class CaptchaBrotherhoodException(Exception): + + def __init__(self, err): + self.err = err + + + def getCode(self): + return self.err + + + def __str__(self): + return "<CaptchaBrotherhoodException %s>" % self.err + + + def __repr__(self): + return "<CaptchaBrotherhoodException %s>" % self.err + + +class CaptchaBrotherhood(Hook): + __name = "CaptchaBrotherhood" + __type = "hook" + __version = "0.08" + + __config = [("username", "str", "Username", ""), + ("force", "bool", "Force CT even if client is connected", False), + ("passkey", "password", "Password", "")] + + __description = """Send captchas to CaptchaBrotherhood.com""" + __license = "GPLv3" + __authors = [("RaNaN" , "RaNaN@pyload.org"), + ("zoidberg", "zoidberg@mujmail.cz")] + + + API_URL = "http://www.captchabrotherhood.com/" + + + def activate(self): + if self.getConfig('ssl'): + self.API_URL = self.API_URL.replace("http://", "https://") + + + def getCredits(self): + res = getURL(self.API_URL + "askCredits.aspx", + get={"username": self.getConfig('username'), "password": self.getConfig('passkey')}) + if not res.startswith("OK"): + raise CaptchaBrotherhoodException(res) + else: + credits = int(res[3:]) + self.logInfo(_("%d credits left") % credits) + self.info['credits'] = credits + return credits + + + def submit(self, captcha, captchaType="file", match=None): + try: + img = Image.open(captcha) + output = StringIO.StringIO() + self.logDebug("CAPTCHA IMAGE", img, img.format, img.mode) + if img.format in ("GIF", "JPEG"): + img.save(output, img.format) + else: + if img.mode != "RGB": + img = img.convert("RGB") + img.save(output, "JPEG") + data = output.getvalue() + output.close() + except Exception, e: + raise CaptchaBrotherhoodException("Reading or converting captcha image failed: %s" % e) + + req = getRequest() + + url = "%ssendNewCaptcha.aspx?%s" % (self.API_URL, + urllib.urlencode({'username' : self.getConfig('username'), + 'password' : self.getConfig('passkey'), + 'captchaSource': "pyLoad", + 'timeout' : "80"})) + + req.c.setopt(pycurl.URL, url) + req.c.setopt(pycurl.POST, 1) + req.c.setopt(pycurl.POSTFIELDS, data) + req.c.setopt(pycurl.HTTPHEADER, ["Content-Type: text/html"]) + + try: + req.c.perform() + res = req.getResponse() + except Exception, e: + raise CaptchaBrotherhoodException("Submit captcha image failed") + + req.close() + + if not res.startswith("OK"): + raise CaptchaBrotherhoodException(res[1]) + + ticket = res[3:] + + for _i in xrange(15): + time.sleep(5) + res = self.api_response("askCaptchaResult", ticket) + if res.startswith("OK-answered"): + return ticket, res[12:] + + raise CaptchaBrotherhoodException("No solution received in time") + + + def api_response(self, api, ticket): + res = getURL("%s%s.aspx" % (self.API_URL, api), + get={"username": self.getConfig('username'), + "password": self.getConfig('passkey'), + "captchaID": ticket}) + if not res.startswith("OK"): + raise CaptchaBrotherhoodException("Unknown response: %s" % res) + + return res + + + def captchaTask(self, task): + if "service" in task.data: + return False + + if not task.isTextual(): + return False + + if not self.getConfig('username') or not self.getConfig('passkey'): + return False + + if self.core.isClientConnected() and not self.getConfig('force'): + return False + + if self.getCredits() > 10: + task.handler.append(self) + task.data['service'] = self.getClassName() + task.setWaiting(100) + self._processCaptcha(task) + else: + self.logInfo(_("Your CaptchaBrotherhood Account has not enough credits")) + + + def captchaInvalid(self, task): + if task.data['service'] == self.getClassName() and "ticket" in task.data: + res = self.api_response("complainCaptcha", task.data['ticket']) + + + @threaded + def _processCaptcha(self, task): + c = task.captchaFile + try: + ticket, result = self.submit(c) + except CaptchaBrotherhoodException, e: + task.error = e.getCode() + return + + task.data['ticket'] = ticket + task.setResult(result) diff --git a/pyload/plugin/hook/DeathByCaptcha.py b/pyload/plugin/hook/DeathByCaptcha.py new file mode 100644 index 000000000..670807bf5 --- /dev/null +++ b/pyload/plugin/hook/DeathByCaptcha.py @@ -0,0 +1,219 @@ +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +import pycurl +import re +import time + +from base64 import b64encode + +from pyload.utils import json_loads +from pyload.network.HTTPRequest import BadHeader +from pyload.network.RequestFactory import getRequest +from pyload.plugin.Hook import Hook, threaded + + +class DeathByCaptchaException(Exception): + DBC_ERRORS = {'not-logged-in': 'Access denied, check your credentials', + 'invalid-credentials': 'Access denied, check your credentials', + 'banned': 'Access denied, account is suspended', + 'insufficient-funds': 'Insufficient account balance to decrypt CAPTCHA', + 'invalid-captcha': 'CAPTCHA is not a valid image', + 'service-overload': 'CAPTCHA was rejected due to service overload, try again later', + 'invalid-request': 'Invalid request', + 'timed-out': 'No CAPTCHA solution received in time'} + + + def __init__(self, err): + self.err = err + + + def getCode(self): + return self.err + + + def getDesc(self): + if self.err in self.DBC_ERRORS.keys(): + return self.DBC_ERRORS[self.err] + else: + return self.err + + + def __str__(self): + return "<DeathByCaptchaException %s>" % self.err + + + def __repr__(self): + return "<DeathByCaptchaException %s>" % self.err + + +class DeathByCaptcha(Hook): + __name = "DeathByCaptcha" + __type = "hook" + __version = "0.06" + + __config = [("username", "str", "Username", ""), + ("passkey", "password", "Password", ""), + ("force", "bool", "Force DBC even if client is connected", False)] + + __description = """Send captchas to DeathByCaptcha.com""" + __license = "GPLv3" + __authors = [("RaNaN" , "RaNaN@pyload.org"), + ("zoidberg", "zoidberg@mujmail.cz")] + + + API_URL = "http://api.dbcapi.me/api/" + + + def activate(self): + if self.getConfig('ssl'): + self.API_URL = self.API_URL.replace("http://", "https://") + + + def api_response(self, api="captcha", post=False, multipart=False): + req = getRequest() + req.c.setopt(pycurl.HTTPHEADER, ["Accept: application/json", "User-Agent: pyLoad %s" % self.core.version]) + + if post: + if not isinstance(post, dict): + post = {} + post.update({"username": self.getConfig('username'), + "password": self.getConfig('passkey')}) + + res = None + try: + json = req.load("%s%s" % (self.API_URL, api), + post=post, + multipart=multipart) + self.logDebug(json) + res = json_loads(json) + + if "error" in res: + raise DeathByCaptchaException(res['error']) + elif "status" not in res: + raise DeathByCaptchaException(str(res)) + + except BadHeader, e: + if 403 == e.code: + raise DeathByCaptchaException('not-logged-in') + elif 413 == e.code: + raise DeathByCaptchaException('invalid-captcha') + elif 503 == e.code: + raise DeathByCaptchaException('service-overload') + elif e.code in (400, 405): + raise DeathByCaptchaException('invalid-request') + else: + raise + + finally: + req.close() + + return res + + + def getCredits(self): + res = self.api_response("user", True) + + if 'is_banned' in res and res['is_banned']: + raise DeathByCaptchaException('banned') + elif 'balance' in res and 'rate' in res: + self.info.update(res) + else: + raise DeathByCaptchaException(res) + + + def getStatus(self): + res = self.api_response("status", False) + + if 'is_service_overloaded' in res and res['is_service_overloaded']: + raise DeathByCaptchaException('service-overload') + + + def submit(self, captcha, captchaType="file", match=None): + #@NOTE: Workaround multipart-post bug in HTTPRequest.py + if re.match("^\w*$", self.getConfig('passkey')): + multipart = True + data = (pycurl.FORM_FILE, captcha) + else: + multipart = False + with open(captcha, 'rb') as f: + data = f.read() + data = "base64:" + b64encode(data) + + res = self.api_response("captcha", {"captchafile": data}, multipart) + + if "captcha" not in res: + raise DeathByCaptchaException(res) + ticket = res['captcha'] + + for _i in xrange(24): + time.sleep(5) + res = self.api_response("captcha/%d" % ticket, False) + if res['text'] and res['is_correct']: + break + else: + raise DeathByCaptchaException('timed-out') + + result = res['text'] + self.logDebug("Result %s : %s" % (ticket, result)) + + return ticket, result + + + def captchaTask(self, task): + if "service" in task.data: + return False + + if not task.isTextual(): + return False + + if not self.getConfig('username') or not self.getConfig('passkey'): + return False + + if self.core.isClientConnected() and not self.getConfig('force'): + return False + + try: + self.getStatus() + self.getCredits() + except DeathByCaptchaException, e: + self.logError(e.getDesc()) + return False + + balance, rate = self.info['balance'], self.info['rate'] + self.logInfo(_("Account balance"), + _("US$%.3f (%d captchas left at %.2f cents each)") % (balance / 100, + balance // rate, rate)) + + if balance > rate: + task.handler.append(self) + task.data['service'] = self.getClassName() + task.setWaiting(180) + self._processCaptcha(task) + + + def captchaInvalid(self, task): + if task.data['service'] == self.getClassName() and "ticket" in task.data: + try: + res = self.api_response("captcha/%d/report" % task.data['ticket'], True) + + except DeathByCaptchaException, e: + self.logError(e.getDesc()) + + except Exception, e: + self.logError(e) + + + @threaded + def _processCaptcha(self, task): + c = task.captchaFile + try: + ticket, result = self.submit(c) + except DeathByCaptchaException, e: + task.error = e.getCode() + self.logError(e.getDesc()) + return + + task.data['ticket'] = ticket + task.setResult(result) diff --git a/pyload/plugin/hook/DebridItaliaCom.py b/pyload/plugin/hook/DebridItaliaCom.py new file mode 100644 index 000000000..e15f7d6ec --- /dev/null +++ b/pyload/plugin/hook/DebridItaliaCom.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.internal.MultiHook import MultiHook + + +class DebridItaliaCom(MultiHook): + __name = "DebridItaliaCom" + __type = "hook" + __version = "0.12" + + __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"), + ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ), + ("revertfailed" , "bool" , "Revert to standard download if fails", True ), + ("reload" , "bool" , "Reload plugin list" , True ), + ("reloadinterval", "int" , "Reload interval in hours" , 12 )] + + __description = """Debriditalia.com hook plugin""" + __license = "GPLv3" + __authors = [("stickell" , "l.stickell@yahoo.it"), + ("Walter Purcaro", "vuolter@gmail.com" )] + + + def getHosters(self): + return self.getURL("http://debriditalia.com/api.php", get={'hosts': ""}).replace('"', '').split(',') diff --git a/pyload/plugin/hook/EasybytezCom.py b/pyload/plugin/hook/EasybytezCom.py new file mode 100644 index 000000000..68b4d96d2 --- /dev/null +++ b/pyload/plugin/hook/EasybytezCom.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.internal.MultiHook import MultiHook + + +class EasybytezCom(MultiHook): + __name = "EasybytezCom" + __type = "hook" + __version = "0.07" + + __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"), + ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ), + ("revertfailed" , "bool" , "Revert to standard download if fails", True ), + ("reload" , "bool" , "Reload plugin list" , True ), + ("reloadinterval", "int" , "Reload interval in hours" , 12 )] + + __description = """EasyBytez.com hook plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + def getHosters(self): + user, data = self.account.selectAccount() + + req = self.account.getAccountRequest(user) + html = req.load("http://www.easybytez.com") + + return re.search(r'</textarea>\s*Supported sites:(.*)', html).group(1).split(',') diff --git a/pyload/plugin/hook/ExpertDecoders.py b/pyload/plugin/hook/ExpertDecoders.py new file mode 100644 index 000000000..0727c0407 --- /dev/null +++ b/pyload/plugin/hook/ExpertDecoders.py @@ -0,0 +1,102 @@ +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +import pycurl +import uuid + +from base64 import b64encode + +from pyload.network.HTTPRequest import BadHeader +from pyload.network.RequestFactory import getURL, getRequest +from pyload.plugin.Hook import Hook, threaded + + +class ExpertDecoders(Hook): + __name = "ExpertDecoders" + __type = "hook" + __version = "0.04" + + __config = [("force", "bool", "Force CT even if client is connected", False), + ("passkey", "password", "Access key", "")] + + __description = """Send captchas to expertdecoders.com""" + __license = "GPLv3" + __authors = [("RaNaN" , "RaNaN@pyload.org" ), + ("zoidberg", "zoidberg@mujmail.cz")] + + + API_URL = "http://www.fasttypers.org/imagepost.ashx" + + + def activate(self): + if self.getConfig('ssl'): + self.API_URL = self.API_URL.replace("http://", "https://") + + + def getCredits(self): + res = getURL(self.API_URL, post={"key": self.getConfig('passkey'), "action": "balance"}) + + if res.isdigit(): + self.logInfo(_("%s credits left") % res) + self.info['credits'] = credits = int(res) + return credits + else: + self.logError(res) + return 0 + + + @threaded + def _processCaptcha(self, task): + task.data['ticket'] = ticket = uuid.uuid4() + result = None + + with open(task.captchaFile, 'rb') as f: + data = f.read() + + req = getRequest() + # raise timeout threshold + req.c.setopt(pycurl.LOW_SPEED_TIME, 80) + + try: + result = req.load(self.API_URL, + post={'action' : "upload", + 'key' : self.getConfig('passkey'), + 'file' : b64encode(data), + 'gen_task_id': ticket}) + finally: + req.close() + + self.logDebug("Result %s : %s" % (ticket, result)) + task.setResult(result) + + + def captchaTask(self, task): + if not task.isTextual(): + return False + + if not self.getConfig('passkey'): + return False + + if self.core.isClientConnected() and not self.getConfig('force'): + return False + + if self.getCredits() > 0: + task.handler.append(self) + task.setWaiting(100) + self._processCaptcha(task) + + else: + self.logInfo(_("Your ExpertDecoders Account has not enough credits")) + + + def captchaInvalid(self, task): + if "ticket" in task.data: + + try: + res = getURL(self.API_URL, + post={'action': "refund", 'key': self.getConfig('passkey'), 'gen_task_id': task.data['ticket']}) + self.logInfo(_("Request refund"), res) + + except BadHeader, e: + self.logError(_("Could not send refund request"), e) diff --git a/pyload/plugin/hook/FastixRu.py b/pyload/plugin/hook/FastixRu.py new file mode 100644 index 000000000..6c71384c8 --- /dev/null +++ b/pyload/plugin/hook/FastixRu.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- + +from pyload.utils import json_loads +from pyload.plugin.internal.MultiHook import MultiHook + + +class FastixRu(MultiHook): + __name = "FastixRu" + __type = "hook" + __version = "0.05" + + __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"), + ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ), + ("revertfailed" , "bool" , "Revert to standard download if fails", True ), + ("reload" , "bool" , "Reload plugin list" , True ), + ("reloadinterval", "int" , "Reload interval in hours" , 12 )] + + __description = """Fastix.ru hook plugin""" + __license = "GPLv3" + __authors = [("Massimo Rosamilia", "max@spiritix.eu")] + + + def getHosters(self): + html = self.getURL("http://fastix.ru/api_v2", + get={'apikey': "5182964c3f8f9a7f0b00000a_kelmFB4n1IrnCDYuIFn2y", + 'sub' : "allowed_sources"}) + host_list = json_loads(html) + host_list = host_list['allow'] + return host_list diff --git a/pyload/plugin/hook/FreeWayMe.py b/pyload/plugin/hook/FreeWayMe.py new file mode 100644 index 000000000..fd1017ba9 --- /dev/null +++ b/pyload/plugin/hook/FreeWayMe.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.MultiHook import MultiHook + + +class FreeWayMe(MultiHook): + __name = "FreeWayMe" + __type = "hook" + __version = "0.15" + + __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"), + ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ), + ("revertfailed" , "bool" , "Revert to standard download if fails", True ), + ("reload" , "bool" , "Reload plugin list" , True ), + ("reloadinterval", "int" , "Reload interval in hours" , 12 )] + + __description = """FreeWay.me hook plugin""" + __license = "GPLv3" + __authors = [("Nicolas Giese", "james@free-way.me")] + + + def getHosters(self): + # Get account data + if not self.account or not self.account.canUse(): + hostis = self.getURL("https://www.free-way.me/ajax/jd.php", get={"id": 3}).replace("\"", "").strip() + else: + self.logDebug("AccountInfo available - Get HosterList with User Pass") + (user, data) = self.account.selectAccount() + hostis = self.getURL("https://www.free-way.me/ajax/jd.php", get={"id": 3, "user": user, "pass": data['password']}).replace("\"", "").strip() + + self.logDebug("hosters: %s" % hostis) + return [x.strip() for x in hostis.split(",") if x.strip()] diff --git a/pyload/plugin/hook/ImageTyperz.py b/pyload/plugin/hook/ImageTyperz.py new file mode 100644 index 000000000..3f0147776 --- /dev/null +++ b/pyload/plugin/hook/ImageTyperz.py @@ -0,0 +1,153 @@ +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +import pycurl +import re + +from base64 import b64encode + +from pyload.network.RequestFactory import getURL, getRequest +from pyload.plugin.Hook import Hook, threaded + + +class ImageTyperzException(Exception): + + def __init__(self, err): + self.err = err + + + def getCode(self): + return self.err + + + def __str__(self): + return "<ImageTyperzException %s>" % self.err + + + def __repr__(self): + return "<ImageTyperzException %s>" % self.err + + +class ImageTyperz(Hook): + __name = "ImageTyperz" + __type = "hook" + __version = "0.06" + + __config = [("username", "str", "Username", ""), + ("passkey", "password", "Password", ""), + ("force", "bool", "Force IT even if client is connected", False)] + + __description = """Send captchas to ImageTyperz.com""" + __license = "GPLv3" + __authors = [("RaNaN" , "RaNaN@pyload.org"), + ("zoidberg", "zoidberg@mujmail.cz")] + + + SUBMIT_URL = "http://captchatypers.com/Forms/UploadFileAndGetTextNEW.ashx" + RESPOND_URL = "http://captchatypers.com/Forms/SetBadImage.ashx" + GETCREDITS_URL = "http://captchatypers.com/Forms/RequestBalance.ashx" + + + def getCredits(self): + res = getURL(self.GETCREDITS_URL, + post={'action': "REQUESTBALANCE", + 'username': self.getConfig('username'), + 'password': self.getConfig('passkey')}) + + if res.startswith('ERROR'): + raise ImageTyperzException(res) + + try: + balance = float(res) + except Exception: + raise ImageTyperzException("Invalid response") + + self.logInfo(_("Account balance: $%s left") % res) + return balance + + + def submit(self, captcha, captchaType="file", match=None): + req = getRequest() + # raise timeout threshold + req.c.setopt(pycurl.LOW_SPEED_TIME, 80) + + try: + #@NOTE: Workaround multipart-post bug in HTTPRequest.py + if re.match("^\w*$", self.getConfig('passkey')): + multipart = True + data = (pycurl.FORM_FILE, captcha) + else: + multipart = False + with open(captcha, 'rb') as f: + data = f.read() + data = b64encode(data) + + res = req.load(self.SUBMIT_URL, + post={'action': "UPLOADCAPTCHA", + 'username': self.getConfig('username'), + 'password': self.getConfig('passkey'), "file": data}, + multipart=multipart) + finally: + req.close() + + if res.startswith("ERROR"): + raise ImageTyperzException(res) + else: + data = res.split('|') + if len(data) == 2: + ticket, result = data + else: + raise ImageTyperzException("Unknown response: %s" % res) + + return ticket, result + + + def captchaTask(self, task): + if "service" in task.data: + return False + + if not task.isTextual(): + return False + + if not self.getConfig('username') or not self.getConfig('passkey'): + return False + + if self.core.isClientConnected() and not self.getConfig('force'): + return False + + if self.getCredits() > 0: + task.handler.append(self) + task.data['service'] = self.getClassName() + task.setWaiting(100) + self._processCaptcha(task) + + else: + self.logInfo(_("Your %s account has not enough credits") % self.getClassName()) + + + def captchaInvalid(self, task): + if task.data['service'] == self.getClassName() and "ticket" in task.data: + res = getURL(self.RESPOND_URL, + post={'action': "SETBADIMAGE", + 'username': self.getConfig('username'), + 'password': self.getConfig('passkey'), + 'imageid': task.data['ticket']}) + + if res == "SUCCESS": + self.logInfo(_("Bad captcha solution received, requested refund")) + else: + self.logError(_("Bad captcha solution received, refund request failed"), res) + + + @threaded + def _processCaptcha(self, task): + c = task.captchaFile + try: + ticket, result = self.submit(c) + except ImageTyperzException, e: + task.error = e.getCode() + return + + task.data['ticket'] = ticket + task.setResult(result) diff --git a/pyload/plugin/hook/LinkdecrypterCom.py b/pyload/plugin/hook/LinkdecrypterCom.py new file mode 100644 index 000000000..d3f5c0afc --- /dev/null +++ b/pyload/plugin/hook/LinkdecrypterCom.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.internal.MultiHook import MultiHook + + +class LinkdecrypterCom(MultiHook): + __name = "LinkdecrypterCom" + __type = "hook" + __version = "1.04" + + __config = [("activated" , "bool" , "Activated" , True ), + ("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"), + ("pluginlist" , "str" , "Plugin list (comma separated)", "" ), + ("reload" , "bool" , "Reload plugin list" , True ), + ("reloadinterval", "int" , "Reload interval in hours" , 12 )] + + __description = """Linkdecrypter.com hook plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + def getCrypters(self): + return re.search(r'>Supported\(\d+\)</b>: <i>(.[\w.\-, ]+)', + self.getURL("http://linkdecrypter.com/", decode=True).replace("(g)", "")).group(1).split(', ') diff --git a/pyload/plugin/hook/LinksnappyCom.py b/pyload/plugin/hook/LinksnappyCom.py new file mode 100644 index 000000000..e09021d6d --- /dev/null +++ b/pyload/plugin/hook/LinksnappyCom.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- + +from pyload.utils import json_loads +from pyload.plugin.internal.MultiHook import MultiHook + + +class LinksnappyCom(MultiHook): + __name = "LinksnappyCom" + __type = "hook" + __version = "0.04" + + __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"), + ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ), + ("revertfailed" , "bool" , "Revert to standard download if fails", True ), + ("reload" , "bool" , "Reload plugin list" , True ), + ("reloadinterval", "int" , "Reload interval in hours" , 12 )] + + __description = """Linksnappy.com hook plugin""" + __license = "GPLv3" + __authors = [("stickell", "l.stickell@yahoo.it")] + + + def getHosters(self): + json_data = self.getURL("http://gen.linksnappy.com/lseAPI.php", get={'act': "FILEHOSTS"}) + json_data = json_loads(json_data) + + return json_data['return'].keys() diff --git a/pyload/plugin/hook/MegaDebridEu.py b/pyload/plugin/hook/MegaDebridEu.py new file mode 100644 index 000000000..41abce37b --- /dev/null +++ b/pyload/plugin/hook/MegaDebridEu.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- + +from pyload.utils import json_loads +from pyload.plugin.internal.MultiHook import MultiHook + + +class MegaDebridEu(MultiHook): + __name = "MegaDebridEu" + __type = "hook" + __version = "0.05" + + __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"), + ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ), + ("revertfailed" , "bool" , "Revert to standard download if fails", True ), + ("reload" , "bool" , "Reload plugin list" , True ), + ("reloadinterval", "int" , "Reload interval in hours" , 12 )] + + __description = """Mega-debrid.eu hook plugin""" + __license = "GPLv3" + __authors = [("D.Ducatel", "dducatel@je-geek.fr")] + + + def getHosters(self): + reponse = self.getURL("http://www.mega-debrid.eu/api.php", get={'action': "getHosters"}) + json_data = json_loads(reponse) + + if json_data['response_code'] == "ok": + host_list = [element[0] for element in json_data['hosters']] + else: + self.logError(_("Unable to retrieve hoster list")) + host_list = [] + + return host_list diff --git a/pyload/plugin/hook/MegaRapidoNet.py b/pyload/plugin/hook/MegaRapidoNet.py new file mode 100644 index 000000000..4650d8b6d --- /dev/null +++ b/pyload/plugin/hook/MegaRapidoNet.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.internal.MultiHook import MultiHook + + +class MegaRapidoNet(MultiHook): + __name = "MegaRapidoNet" + __type = "hook" + __version = "0.02" + + __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"), + ("pluginlist" , "str" , "Plugin list (comma separated)", "" ), + ("reload" , "bool" , "Reload plugin list" , True ), + ("reloadinterval", "int" , "Reload interval in hours" , 12 )] + + __description = """MegaRapido.net hook plugin""" + __license = "GPLv3" + __authors = [("Kagenoshin", "kagenoshin@gmx.ch")] + + + def getHosters(self): + hosters = {'1fichier' : [],#leave it there are so many possible addresses? + '1st-files' : ['1st-files.com'], + '2shared' : ['2shared.com'], + '4shared' : ['4shared.com', '4shared-china.com'], + 'asfile' : ['http://asfile.com/'], + 'bitshare' : ['bitshare.com'], + 'brupload' : ['brupload.net'], + 'crocko' : ['crocko.com','easy-share.com'], + 'dailymotion' : ['dailymotion.com'], + 'depfile' : ['depfile.com'], + 'depositfiles': ['depositfiles.com', 'dfiles.eu'], + 'dizzcloud' : ['dizzcloud.com'], + 'dl.dropbox' : [], + 'extabit' : ['extabit.com'], + 'extmatrix' : ['extmatrix.com'], + 'facebook' : [], + 'file4go' : ['file4go.com'], + 'filecloud' : ['filecloud.io','ifile.it','mihd.net'], + 'filefactory' : ['filefactory.com'], + 'fileom' : ['fileom.com'], + 'fileparadox' : ['fileparadox.in'], + 'filepost' : ['filepost.com', 'fp.io'], + 'filerio' : ['filerio.in','filerio.com','filekeen.com'], + 'filesflash' : ['filesflash.com'], + 'firedrive' : ['firedrive.com', 'putlocker.com'], + 'flashx' : [], + 'freakshare' : ['freakshare.net', 'freakshare.com'], + 'gigasize' : ['gigasize.com'], + 'hipfile' : ['hipfile.com'], + 'junocloud' : ['junocloud.me'], + 'letitbit' : ['letitbit.net','shareflare.net'], + 'mediafire' : ['mediafire.com'], + 'mega' : ['mega.co.nz'], + 'megashares' : ['megashares.com'], + 'metacafe' : ['metacafe.com'], + 'netload' : ['netload.in'], + 'oboom' : ['oboom.com'], + 'rapidgator' : ['rapidgator.net'], + 'rapidshare' : ['rapidshare.com'], + 'rarefile' : ['rarefile.net'], + 'ryushare' : ['ryushare.com'], + 'sendspace' : ['sendspace.com'], + 'turbobit' : ['turbobit.net', 'unextfiles.com'], + 'uploadable' : ['uploadable.ch'], + 'uploadbaz' : ['uploadbaz.com'], + 'uploaded' : ['uploaded.to', 'uploaded.net', 'ul.to'], + 'uploadhero' : ['uploadhero.com'], + 'uploading' : ['uploading.com'], + 'uptobox' : ['uptobox.com'], + 'xvideos' : ['xvideos.com'], + 'youtube' : ['youtube.com']} + + hoster_list = [] + + for item in hosters.itervalues(): + hoster_list.extend(item) + + return hoster_list diff --git a/pyload/plugin/hook/MultihostersCom.py b/pyload/plugin/hook/MultihostersCom.py new file mode 100644 index 000000000..5cb2396ee --- /dev/null +++ b/pyload/plugin/hook/MultihostersCom.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.hook.ZeveraCom import ZeveraCom + + +class MultihostersCom(ZeveraCom): + __name = "MultihostersCom" + __type = "hook" + __version = "0.02" + + __config = [("mode" , "all;listed;unlisted", "Use for plugins (if supported)" , "all"), + ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ), + ("revertfailed", "bool" , "Revert to standard download if download fails", False), + ("interval" , "int" , "Reload interval in hours (0 to disable)" , 12 )] + + __description = """Multihosters.com hook plugin""" + __license = "GPLv3" + __authors = [("tjeh", "tjeh@gmx.net")] diff --git a/pyload/plugin/hook/MultishareCz.py b/pyload/plugin/hook/MultishareCz.py new file mode 100644 index 000000000..39817d7ce --- /dev/null +++ b/pyload/plugin/hook/MultishareCz.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.internal.MultiHook import MultiHook + + +class MultishareCz(MultiHook): + __name = "MultishareCz" + __type = "hook" + __version = "0.07" + + __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"), + ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ), + ("revertfailed" , "bool" , "Revert to standard download if fails", True ), + ("reload" , "bool" , "Reload plugin list" , True ), + ("reloadinterval", "int" , "Reload interval in hours" , 12 )] + + __description = """MultiShare.cz hook plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + HOSTER_PATTERN = r'<img class="logo-shareserveru"[^>]*?alt="(.+?)"></td>\s*<td class="stav">[^>]*?alt="OK"' + + + def getHosters(self): + html = self.getURL("http://www.multishare.cz/monitoring/") + return re.findall(self.HOSTER_PATTERN, html) diff --git a/pyload/plugin/hook/MyfastfileCom.py b/pyload/plugin/hook/MyfastfileCom.py new file mode 100644 index 000000000..b6573e56d --- /dev/null +++ b/pyload/plugin/hook/MyfastfileCom.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.MultiHook import MultiHook +from pyload.utils import json_loads + + +class MyfastfileCom(MultiHook): + __name = "MyfastfileCom" + __type = "hook" + __version = "0.05" + + __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"), + ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ), + ("revertfailed" , "bool" , "Revert to standard download if fails", True ), + ("reload" , "bool" , "Reload plugin list" , True ), + ("reloadinterval", "int" , "Reload interval in hours" , 12 )] + + __description = """Myfastfile.com hook plugin""" + __license = "GPLv3" + __authors = [("stickell", "l.stickell@yahoo.it")] + + + def getHosters(self): + json_data = self.getURL("http://myfastfile.com/api.php", get={'hosts': ""}, decode=True) + self.logDebug("JSON data", json_data) + json_data = json_loads(json_data) + + return json_data['hosts'] diff --git a/pyload/plugin/hook/NoPremiumPl.py b/pyload/plugin/hook/NoPremiumPl.py new file mode 100644 index 000000000..527413a88 --- /dev/null +++ b/pyload/plugin/hook/NoPremiumPl.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- + +from pyload.utils import json_loads +from pyload.plugin.internal.MultiHook import MultiHook + + +class NoPremiumPl(MultiHook): + __name = "NoPremiumPl" + __type = "hook" + __version = "0.03" + + __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"), + ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ), + ("revertfailed" , "bool" , "Revert to standard download if fails", True ), + ("reload" , "bool" , "Reload plugin list" , True ), + ("reloadinterval", "int" , "Reload interval in hours" , 12 )] + + __description = """NoPremium.pl hook plugin""" + __license = "GPLv3" + __authors = [("goddie", "dev@nopremium.pl")] + + + def getHosters(self): + hostings = json_loads(self.getURL("https://www.nopremium.pl/clipboard.php?json=3").strip()) + hostings_domains = [domain for row in hostings for domain in row['domains'] if row['sdownload'] == "0"] + + self.logDebug(hostings_domains) + + return hostings_domains diff --git a/pyload/plugin/hook/OverLoadMe.py b/pyload/plugin/hook/OverLoadMe.py new file mode 100644 index 000000000..5fc04e6f4 --- /dev/null +++ b/pyload/plugin/hook/OverLoadMe.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.MultiHook import MultiHook + + +class OverLoadMe(MultiHook): + __name = "OverLoadMe" + __type = "hook" + __version = "0.04" + + __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"), + ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ), + ("revertfailed" , "bool" , "Revert to standard download if fails", True ), + ("reload" , "bool" , "Reload plugin list" , True ), + ("reloadinterval", "int" , "Reload interval in hours" , 12 ), + ("ssl" , "bool" , "Use HTTPS" , True )] + + __description = """Over-Load.me hook plugin""" + __license = "GPLv3" + __authors = [("marley", "marley@over-load.me")] + + + def getHosters(self): + https = "https" if self.getConfig('ssl') else "http" + html = self.getURL(https + "://api.over-load.me/hoster.php", + get={'auth': "0001-cb1f24dadb3aa487bda5afd3b76298935329be7700cd7-5329be77-00cf-1ca0135f"}).replace("\"", "").strip() + self.logDebug("Hosterlist", html) + + return [x.strip() for x in html.split(",") if x.strip()] diff --git a/pyload/plugin/hook/PremiumTo.py b/pyload/plugin/hook/PremiumTo.py new file mode 100644 index 000000000..361f072a3 --- /dev/null +++ b/pyload/plugin/hook/PremiumTo.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.MultiHook import MultiHook + + +class PremiumTo(MultiHook): + __name = "PremiumTo" + __type = "hook" + __version = "0.08" + + __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"), + ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ), + ("revertfailed" , "bool" , "Revert to standard download if fails", True ), + ("reload" , "bool" , "Reload plugin list" , True ), + ("reloadinterval", "int" , "Reload interval in hours" , 12 )] + + __description = """Premium.to hook plugin""" + __license = "GPLv3" + __authors = [("RaNaN" , "RaNaN@pyload.org" ), + ("zoidberg", "zoidberg@mujmail.cz"), + ("stickell", "l.stickell@yahoo.it")] + + + def getHosters(self): + html = self.getURL("http://premium.to/api/hosters.php", + get={'username': self.account.username, 'password': self.account.password}) + return [x.strip() for x in html.replace("\"", "").split(";")] diff --git a/pyload/plugin/hook/PremiumizeMe.py b/pyload/plugin/hook/PremiumizeMe.py new file mode 100644 index 000000000..1e4a58592 --- /dev/null +++ b/pyload/plugin/hook/PremiumizeMe.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- + +from pyload.utils import json_loads +from pyload.plugin.internal.MultiHook import MultiHook + + +class PremiumizeMe(MultiHook): + __name = "PremiumizeMe" + __type = "hook" + __version = "0.17" + + __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"), + ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ), + ("revertfailed" , "bool" , "Revert to standard download if fails", True ), + ("reload" , "bool" , "Reload plugin list" , True ), + ("reloadinterval", "int" , "Reload interval in hours" , 12 )] + + __description = """Premiumize.me hook plugin""" + __license = "GPLv3" + __authors = [("Florian Franzen", "FlorianFranzen@gmail.com")] + + + def getHosters(self): + # Get account data + user, data = self.account.selectAccount() + + # Get supported hosters list from premiumize.me using the + # json API v1 (see https://secure.premiumize.me/?show=api) + answer = self.getURL("https://api.premiumize.me/pm-api/v1.php", + get={'method': "hosterlist", 'params[login]': user, 'params[pass]': data['password']}) + data = json_loads(answer) + + # If account is not valid thera are no hosters available + if data['status'] != 200: + return [] + + # Extract hosters from json file + return data['result']['hosterlist'] diff --git a/pyload/plugin/hook/PutdriveCom.py b/pyload/plugin/hook/PutdriveCom.py new file mode 100644 index 000000000..126eb86e8 --- /dev/null +++ b/pyload/plugin/hook/PutdriveCom.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.hook.ZeveraCom import ZeveraCom + + +class PutdriveCom(ZeveraCom): + __name = "PutdriveCom" + __type = "hook" + __version = "0.01" + + __config = [("mode" , "all;listed;unlisted", "Use for plugins (if supported)" , "all"), + ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ), + ("revertfailed", "bool" , "Revert to standard download if download fails", False), + ("interval" , "int" , "Reload interval in hours (0 to disable)" , 12 )] + + __description = """Putdrive.com hook plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] diff --git a/pyload/plugin/hook/RPNetBiz.py b/pyload/plugin/hook/RPNetBiz.py new file mode 100644 index 000000000..d9d3e6177 --- /dev/null +++ b/pyload/plugin/hook/RPNetBiz.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- + +from pyload.utils import json_loads +from pyload.plugin.internal.MultiHook import MultiHook + + +class RPNetBiz(MultiHook): + __name = "RPNetBiz" + __type = "hook" + __version = "0.14" + + __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"), + ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ), + ("revertfailed" , "bool" , "Revert to standard download if fails", True ), + ("reload" , "bool" , "Reload plugin list" , True ), + ("reloadinterval", "int" , "Reload interval in hours" , 12 )] + + __description = """RPNet.biz hook plugin""" + __license = "GPLv3" + __authors = [("Dman", "dmanugm@gmail.com")] + + + def getHosters(self): + # Get account data + user, data = self.account.selectAccount() + + res = self.getURL("https://premium.rpnet.biz/client_api.php", + get={'username': user, 'password': data['password'], 'action': "showHosterList"}) + hoster_list = json_loads(res) + + # If account is not valid thera are no hosters available + if 'error' in hoster_list: + return [] + + # Extract hosters from json file + return hoster_list['hosters'] diff --git a/pyload/plugin/hook/RapideoPl.py b/pyload/plugin/hook/RapideoPl.py new file mode 100644 index 000000000..1761659db --- /dev/null +++ b/pyload/plugin/hook/RapideoPl.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- + +from pyload.utils import json_loads +from pyload.plugin.internal.MultiHook import MultiHook + + +class RapideoPl(MultiHook): + __name = "RapideoPl" + __type = "hook" + __version = "0.03" + + __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"), + ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ), + ("revertfailed" , "bool" , "Revert to standard download if fails", True ), + ("reload" , "bool" , "Reload plugin list" , True ), + ("reloadinterval", "int" , "Reload interval in hours" , 12 )] + + __description = """Rapideo.pl hook plugin""" + __license = "GPLv3" + __authors = [("goddie", "dev@rapideo.pl")] + + + def getHosters(self): + hostings = json_loads(self.getURL("https://www.rapideo.pl/clipboard.php?json=3").strip()) + hostings_domains = [domain for row in hostings for domain in row['domains'] if row['sdownload'] == "0"] + + self.logDebug(hostings_domains) + + return hostings_domains diff --git a/pyload/plugin/hook/RealdebridCom.py b/pyload/plugin/hook/RealdebridCom.py new file mode 100644 index 000000000..916737cd8 --- /dev/null +++ b/pyload/plugin/hook/RealdebridCom.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.MultiHook import MultiHook + + +class RealdebridCom(MultiHook): + __name = "RealdebridCom" + __type = "hook" + __version = "0.46" + + __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"), + ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ), + ("revertfailed" , "bool" , "Revert to standard download if fails", True ), + ("reload" , "bool" , "Reload plugin list" , True ), + ("reloadinterval", "int" , "Reload interval in hours" , 12 ), + ("ssl" , "bool" , "Use HTTPS" , True )] + + __description = """Real-Debrid.com hook plugin""" + __license = "GPLv3" + __authors = [("Devirex Hazzard", "naibaf_11@yahoo.de")] + + + def getHosters(self): + https = "https" if self.getConfig('ssl') else "http" + html = self.getURL(https + "://real-debrid.com/api/hosters.php").replace("\"", "").strip() + + return [x.strip() for x in html.split(",") if x.strip()] diff --git a/pyload/plugin/hook/RehostTo.py b/pyload/plugin/hook/RehostTo.py new file mode 100644 index 000000000..087701c5b --- /dev/null +++ b/pyload/plugin/hook/RehostTo.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.MultiHook import MultiHook + + +class RehostTo(MultiHook): + __name = "RehostTo" + __type = "hook" + __version = "0.50" + + __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"), + ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ), + ("revertfailed" , "bool" , "Revert to standard download if fails", True ), + ("reload" , "bool" , "Reload plugin list" , True ), + ("reloadinterval", "int" , "Reload interval in hours" , 12 )] + + __description = """Rehost.to hook plugin""" + __license = "GPLv3" + __authors = [("RaNaN", "RaNaN@pyload.org")] + + + def getHosters(self): + user, data = self.account.selectAccount() + html = self.getURL("http://rehost.to/api.php", + get={'cmd' : "get_supported_och_dl", + 'long_ses': self.account.getAccountInfo(user)['session']}) + return [x.strip() for x in html.replace("\"", "").split(",")] diff --git a/pyload/plugin/hook/SimplyPremiumCom.py b/pyload/plugin/hook/SimplyPremiumCom.py new file mode 100644 index 000000000..f095cd7e6 --- /dev/null +++ b/pyload/plugin/hook/SimplyPremiumCom.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- + +from pyload.utils import json_loads +from pyload.plugin.internal.MultiHook import MultiHook + + +class SimplyPremiumCom(MultiHook): + __name = "SimplyPremiumCom" + __type = "hook" + __version = "0.05" + + __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"), + ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ), + ("revertfailed" , "bool" , "Revert to standard download if fails", True ), + ("reload" , "bool" , "Reload plugin list" , True ), + ("reloadinterval", "int" , "Reload interval in hours" , 12 )] + + __description = """Simply-Premium.com hook plugin""" + __license = "GPLv3" + __authors = [("EvolutionClip", "evolutionclip@live.de")] + + + def getHosters(self): + json_data = self.getURL("http://www.simply-premium.com/api/hosts.php", get={'format': "json", 'online': 1}) + json_data = json_loads(json_data) + + host_list = [element['regex'] for element in json_data['result']] + + return host_list diff --git a/pyload/plugin/hook/SimplydebridCom.py b/pyload/plugin/hook/SimplydebridCom.py new file mode 100644 index 000000000..d831bf532 --- /dev/null +++ b/pyload/plugin/hook/SimplydebridCom.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.MultiHook import MultiHook + + +class SimplydebridCom(MultiHook): + __name = "SimplydebridCom" + __type = "hook" + __version = "0.04" + + __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"), + ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ), + ("revertfailed" , "bool" , "Revert to standard download if fails", True ), + ("reload" , "bool" , "Reload plugin list" , True ), + ("reloadinterval", "int" , "Reload interval in hours" , 12 )] + + __description = """Simply-Debrid.com hook plugin""" + __license = "GPLv3" + __authors = [("Kagenoshin", "kagenoshin@gmx.ch")] + + + def getHosters(self): + html = self.getURL("http://simply-debrid.com/api.php", get={'list': 1}) + return [x.strip() for x in html.rstrip(';').replace("\"", "").split(";")] diff --git a/pyload/plugin/hook/SmoozedCom.py b/pyload/plugin/hook/SmoozedCom.py new file mode 100644 index 000000000..caecb8bc2 --- /dev/null +++ b/pyload/plugin/hook/SmoozedCom.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.MultiHook import MultiHook + + +class SmoozedCom(MultiHook): + __name = "SmoozedCom" + __type = "hook" + __version = "0.03" + + __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"), + ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ), + ("revertfailed" , "bool" , "Revert to standard download if fails", True ), + ("reload" , "bool" , "Reload plugin list" , True ), + ("reloadinterval", "int" , "Reload interval in hours" , 12 )] + + __description = """Smoozed.com hook plugin""" + __license = "GPLv3" + __authors = [("", "")] + + + def getHosters(self): + user, data = self.account.selectAccount() + return self.account.getAccountInfo(user)["hosters"] diff --git a/pyload/plugin/hook/UnrestrictLi.py b/pyload/plugin/hook/UnrestrictLi.py new file mode 100644 index 000000000..124a5109a --- /dev/null +++ b/pyload/plugin/hook/UnrestrictLi.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- + +from pyload.utils import json_loads +from pyload.plugin.internal.MultiHook import MultiHook + + +class UnrestrictLi(MultiHook): + __name = "UnrestrictLi" + __type = "hook" + __version = "0.05" + + __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"), + ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ), + ("revertfailed" , "bool" , "Revert to standard download if fails", True ), + ("reload" , "bool" , "Reload plugin list" , True ), + ("reloadinterval", "int" , "Reload interval in hours" , 12 ), + ("history" , "bool" , "Delete History" , False)] + + __description = """Unrestrict.li hook plugin""" + __license = "GPLv3" + __authors = [("stickell", "l.stickell@yahoo.it")] + + + def getHosters(self): + json_data = self.getURL("http://unrestrict.li/api/jdownloader/hosts.php", get={'format': "json"}) + json_data = json_loads(json_data) + + return [element['host'] for element in json_data['result']] diff --git a/pyload/plugin/hook/XFileSharingPro.py b/pyload/plugin/hook/XFileSharingPro.py new file mode 100644 index 000000000..3c305c74b --- /dev/null +++ b/pyload/plugin/hook/XFileSharingPro.py @@ -0,0 +1,110 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.Hook import Hook + + +class XFileSharingPro(Hook): + __name = "XFileSharingPro" + __type = "hook" + __version = "0.37" + + __config = [("activated" , "bool", "Activated" , True ), + ("use_hoster_list" , "bool", "Load listed hosters only" , False), + ("use_crypter_list", "bool", "Load listed crypters only" , False), + ("use_builtin_list", "bool", "Load built-in plugin list" , True ), + ("hoster_list" , "str" , "Hoster list (comma separated)" , "" ), + ("crypter_list" , "str" , "Crypter list (comma separated)", "" )] + + __description = """Load XFileSharingPro based hosters and crypter which don't need a own plugin to run""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + # event_list = ["pluginConfigChanged"] + regexp = {'hoster' : (r'https?://(?:www\.)?(?P<DOMAIN>[\w\-.^_]{3,63}(?:\.[a-zA-Z]{2,})(?:\:\d+)?)/(?:embed-)?\w{12}(?:\W|$)', + r'https?://(?:[^/]+\.)?(?P<DOMAIN>%s)/(?:embed-)?\w+'), + 'crypter': (r'https?://(?:www\.)?(?P<DOMAIN>[\w\-.^_]{3,63}(?:\.[a-zA-Z]{2,})(?:\:\d+)?)/(?:user|folder)s?/\w+', + r'https?://(?:[^/]+\.)?(?P<DOMAIN>%s)/(?:user|folder)s?/\w+')} + + HOSTER_BUILTIN = [#WORKING HOSTERS: + "backin.net", "eyesfile.ca", "file4safe.com", "fileband.com", "filedwon.com", "fileparadox.in", + "filevice.com", "hostingbulk.com", "junkyvideo.com", "linestorage.com", "ravishare.com", "ryushare.com", + "salefiles.com", "sendmyway.com", "sharesix.com", "thefile.me", "verzend.be", "xvidstage.com", + # NOT TESTED: + "101shared.com", "4upfiles.com", "filemaze.ws", "filenuke.com", "linkzhost.com", "mightyupload.com", + "rockdizfile.com", "sharebeast.com", "sharerepo.com", "shareswift.com", "uploadbaz.com", "uploadc.com", + "vidbull.com", "worldbytez.com", "zalaa.com", "zomgupload.com", + # NOT WORKING: + "amonshare.com", "banicrazy.info", "boosterking.com", "host4desi.com", "laoupload.com", "rd-fs.com"] + CRYPTER_BUILTIN = ["junocloud.me", "rapidfileshare.net"] + + + # def pluginConfigChanged(self, plugin, name, value): + # self.loadPattern() + + + def activate(self): + self.loadPattern() + + + def loadPattern(self): + use_builtin_list = self.getConfig("use_builtin_list") + + for type in ("hoster", "crypter"): + every_plugin = not self.getConfig('use_%s_list' % type) + + if every_plugin: + self.logInfo(_("Handling any %s I can!") % type) + pattern = self.regexp[type][0] + else: + plugins = self.getConfig('%s_list' % type) + plugin_set = set(plugins.replace(' ', '').replace('\\', '').replace('|', ',').replace(';', ',').lower().split(',')) + + if use_builtin_list: + plugin_set |= set(x.lower() for x in getattr(self, "%s_BUILTIN" % type.upper())) + + plugin_set -= set(('', u'')) + + if not plugin_set: + self.logInfo(_("No %s to handle") % type) + self._unload(type) + return + + match_list = '|'.join(sorted(plugin_set)) + + len_match_list = len(plugin_set) + self.logInfo(_("Handling %d %s%s: %s") % (len_match_list, + type, + "" if len_match_list == 1 else "s", + match_list.replace('|', ', '))) + + pattern = self.regexp[type][1] % match_list.replace('.', '\.') + + dict = self.core.pluginManager.plugins[type]['XFileSharingPro'] + dict['pattern'] = pattern + dict['re'] = re.compile(pattern) + + self.logDebug("Loaded %s pattern: %s" % (type, pattern)) + + + def _unload(self, type): + dict = self.core.pluginManager.plugins[type]['XFileSharingPro'] + dict['pattern'] = r'^unmatchable$' + dict['re'] = re.compile(dict['pattern']) + + + def deactivate(self): + # self.unloadHoster("BasePlugin") + for type in ("hoster", "crypter"): + self._unload(type, "XFileSharingPro") + + + # def downloadFailed(self, pyfile): + # if pyfile.pluginname == "BasePlugin" \ + # and pyfile.hasStatus("failed") \ + # and not self.getConfig('use_hoster_list') \ + # and self.unloadHoster("BasePlugin"): + # self.logDebug("Unloaded XFileSharingPro from BasePlugin") + # pyfile.setStatus("queued") diff --git a/pyload/plugin/hook/ZeveraCom.py b/pyload/plugin/hook/ZeveraCom.py new file mode 100644 index 000000000..e52ec8eed --- /dev/null +++ b/pyload/plugin/hook/ZeveraCom.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.MultiHook import MultiHook + + +class ZeveraCom(MultiHook): + __name = "ZeveraCom" + __type = "hook" + __version = "0.05" + + __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"), + ("pluginlist" , "str" , "Plugin list (comma separated)" , "" ), + ("revertfailed" , "bool" , "Revert to standard download if fails", True ), + ("reload" , "bool" , "Reload plugin list" , True ), + ("reloadinterval", "int" , "Reload interval in hours" , 12 )] + + __description = """Zevera.com hook plugin""" + __license = "GPLv3" + __authors = [("zoidberg" , "zoidberg@mujmail.cz"), + ("Walter Purcaro", "vuolter@gmail.com" )] + + + def getHosters(self): + html = self.account.api_response(pyreq.getHTTPRequest(timeout=120), cmd="gethosters") + return [x.strip() for x in html.split(",")] diff --git a/pyload/plugin/hook/__init__.py b/pyload/plugin/hook/__init__.py new file mode 100644 index 000000000..40a96afc6 --- /dev/null +++ b/pyload/plugin/hook/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/pyload/plugin/hoster/AlldebridCom.py b/pyload/plugin/hoster/AlldebridCom.py new file mode 100644 index 000000000..9f7497e72 --- /dev/null +++ b/pyload/plugin/hoster/AlldebridCom.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- + +from pyload.utils import json_loads +from pyload.plugin.internal.MultiHoster import MultiHoster +from pyload.utils import parseFileSize + + +class AlldebridCom(MultiHoster): + __name = "AlldebridCom" + __type = "hoster" + __version = "0.46" + + __pattern = r'https?://(?:www\.|s\d+\.)?alldebrid\.com/dl/[\w^_]+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Alldebrid.com multi-hoster plugin""" + __license = "GPLv3" + __authors = [("Andy Voigt", "spamsales@online.de")] + + + def setup(self): + self.chunkLimit = 16 + + + def handlePremium(self, pyfile): + password = self.getPassword() + + data = json_loads(self.load("http://www.alldebrid.com/service.php", + get={'link': pyfile.url, 'json': "true", 'pw': password})) + + self.logDebug("Json data", data) + + if data['error']: + if data['error'] == "This link isn't available on the hoster website.": + self.offline() + else: + self.logWarning(data['error']) + self.tempOffline() + else: + if pyfile.name and not pyfile.name.endswith('.tmp'): + pyfile.name = data['filename'] + pyfile.size = parseFileSize(data['filesize']) + self.link = data['link'] + + if self.getConfig('ssl'): + self.link = self.link.replace("http://", "https://") + else: + self.link = self.link.replace("https://", "http://") diff --git a/pyload/plugin/hoster/AndroidfilehostCom.py b/pyload/plugin/hoster/AndroidfilehostCom.py new file mode 100644 index 000000000..584001725 --- /dev/null +++ b/pyload/plugin/hoster/AndroidfilehostCom.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -* +# +# Test links: +# https://www.androidfilehost.com/?fid=95916177934518197 + +import re + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class AndroidfilehostCom(SimpleHoster): + __name = "AndroidfilehostCom" + __type = "hoster" + __version = "0.01" + + __pattern = r'https?://(?:www\.)?androidfilehost\.com/\?fid=\d+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Androidfilehost.com hoster plugin""" + __license = "GPLv3" + __authors = [("zapp-brannigan", "fuerst.reinje@web.de")] + + + NAME_PATTERN = r'<br />(?P<N>.*?)</h1>' + SIZE_PATTERN = r'<h4>size</h4>\s*<p>(?P<S>[\d.,]+)(?P<U>[\w^_]+)</p>' + HASHSUM_PATTERN = r'<h4>(?P<T>.*?)</h4>\s*<p><code>(?P<H>.*?)</code></p>' + + OFFLINE_PATTERN = r'404 not found' + + WAIT_PATTERN = r'users must wait <strong>(\d+) secs' + + + def setup(self): + self.multiDL = True + self.resumeDownload = True + self.chunkLimit = 1 + + + def handleFree(self, pyfile): + wait = re.search(self.WAIT_PATTERN, self.html) + self.logDebug("Waiting time: %s seconds" % wait.group(1)) + + fid = re.search(r'id="fid" value="(\d+)" />', self.html).group(1) + self.logDebug("fid: %s" % fid) + + html = self.load("https://www.androidfilehost.com/libs/otf/mirrors.otf.php", + post={'submit': 'submit', + 'action': 'getdownloadmirrors', + 'fid' : fid}, + decode=True) + + self.link = re.findall('"url":"(.*?)"', html)[0].replace("\\", "") + mirror_host = self.link.split("/")[2] + + self.logDebug("Mirror Host: %s" % mirror_host) + + html = self.load("https://www.androidfilehost.com/libs/otf/stats.otf.php", + get={'fid' : fid, + 'w' : 'download', + 'mirror': mirror_host}, + decode=True) diff --git a/pyload/plugin/hoster/BasketbuildCom.py b/pyload/plugin/hoster/BasketbuildCom.py new file mode 100644 index 000000000..d005eae74 --- /dev/null +++ b/pyload/plugin/hoster/BasketbuildCom.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -* +# +# Test links: +# https://s.basketbuild.com/filedl/devs?dev=pacman&dl=pacman/falcon/RC-3/pac_falcon-RC-3-20141103.zip +# https://s.basketbuild.com/filedl/gapps?dl=gapps-gb-20110828-signed.zip + +import re + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class BasketbuildCom(SimpleHoster): + __name = "BasketbuildCom" + __type = "hoster" + __version = "0.03" + + __pattern = r'https?://(?:www\.)?(?:\w\.)?basketbuild\.com/filedl/.+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """basketbuild.com hoster plugin""" + __license = "GPLv3" + __authors = [("zapp-brannigan", "fuerst.reinje@web.de")] + + + NAME_PATTERN = r'File Name:</strong> (?P<N>.+?)<br/>' + SIZE_PATTERN = r'File Size:</strong> (?P<S>[\d.,]+) (?P<U>[\w^_]+)' + OFFLINE_PATTERN = r'404 - Page Not Found' + + + def setup(self): + self.multiDL = True + self.resumeDownload = True + self.chunkLimit = 1 + + + def handleFree(self, pyfile): + try: + link1 = re.search(r'href="(.+dlgate/.+)"', self.html).group(1) + self.html = self.load(link1) + + except AttributeError: + self.error(_("Hop #1 not found")) + + else: + self.logDebug("Next hop: %s" % link1) + + try: + wait = re.search(r'var sec = (\d+)', self.html).group(1) + self.logDebug("Wait %s seconds" % wait) + self.wait(wait) + + except AttributeError: + self.logDebug("No wait time found") + + try: + self.link = re.search(r'id="dlLink">\s*<a href="(.+?)"', self.html).group(1) + + except AttributeError: + self.error(_("DL-Link not found")) diff --git a/pyload/plugin/hoster/BayfilesCom.py b/pyload/plugin/hoster/BayfilesCom.py new file mode 100644 index 000000000..b14e28dcb --- /dev/null +++ b/pyload/plugin/hoster/BayfilesCom.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class BayfilesCom(DeadHoster): + __name = "BayfilesCom" + __type = "hoster" + __version = "0.09" + + __pattern = r'https?://(?:www\.)?bayfiles\.(com|net)/file/(?P<ID>\w+/\w+/[^/]+)' + __config = [] + + __description = """Bayfiles.com hoster plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] diff --git a/pyload/plugin/hoster/BezvadataCz.py b/pyload/plugin/hoster/BezvadataCz.py new file mode 100644 index 000000000..fa29c83d5 --- /dev/null +++ b/pyload/plugin/hoster/BezvadataCz.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class BezvadataCz(SimpleHoster): + __name = "BezvadataCz" + __type = "hoster" + __version = "0.26" + + __pattern = r'http://(?:www\.)?bezvadata\.cz/stahnout/.+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """BezvaData.cz hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + NAME_PATTERN = r'<p><b>Soubor: (?P<N>[^<]+)</b></p>' + SIZE_PATTERN = r'<li><strong>Velikost:</strong> (?P<S>[^<]+)</li>' + OFFLINE_PATTERN = r'<title>BezvaData \| Soubor nenalezen</title>' + + + def setup(self): + self.resumeDownload = True + self.multiDL = True + + + def handleFree(self, pyfile): + # download button + m = re.search(r'<a class="stahnoutSoubor".*?href="(.*?)"', self.html) + if m is None: + self.error(_("Page 1 URL not found")) + url = "http://bezvadata.cz%s" % m.group(1) + + # captcha form + self.html = self.load(url) + self.checkErrors() + for _i in xrange(5): + action, inputs = self.parseHtmlForm('frm-stahnoutFreeForm') + if not inputs: + self.error(_("FreeForm")) + + m = re.search(r'<img src="data:image/png;base64,(.*?)"', self.html) + if m is None: + self.error(_("Wrong captcha image")) + + # captcha image is contained in html page as base64encoded data but decryptCaptcha() expects image url + self.load, proper_load = self.loadcaptcha, self.load + try: + inputs['captcha'] = self.decryptCaptcha(m.group(1), imgtype='png') + finally: + self.load = proper_load + + if '<img src="data:image/png;base64' in self.html: + self.invalidCaptcha() + else: + self.correctCaptcha() + break + else: + self.fail(_("No valid captcha code entered")) + + # download url + self.html = self.load("http://bezvadata.cz%s" % action, post=inputs) + self.checkErrors() + m = re.search(r'<a class="stahnoutSoubor2" href="(.*?)">', self.html) + if m is None: + self.error(_("Page 2 URL not found")) + url = "http://bezvadata.cz%s" % m.group(1) + self.logDebug("DL URL %s" % url) + + # countdown + m = re.search(r'id="countdown">(\d\d):(\d\d)<', self.html) + wait_time = (int(m.group(1)) * 60 + int(m.group(2))) if m else 120 + self.wait(wait_time, False) + + self.link = url + + + def checkErrors(self): + if 'images/button-download-disable.png' in self.html: + self.longWait(5 * 60, 24) #: parallel dl limit + elif '<div class="infobox' in self.html: + self.tempOffline() + + self.info.pop('error', None) + + + def loadcaptcha(self, data, *args, **kwargs): + return data.decode('base64') diff --git a/pyload/plugin/hoster/BillionuploadsCom.py b/pyload/plugin/hoster/BillionuploadsCom.py new file mode 100644 index 000000000..e34584868 --- /dev/null +++ b/pyload/plugin/hoster/BillionuploadsCom.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSHoster import XFSHoster + + +class BillionuploadsCom(XFSHoster): + __name = "BillionuploadsCom" + __type = "hoster" + __version = "0.04" + + __pattern = r'http://(?:www\.)?billionuploads\.com/\w{12}' + + __description = """Billionuploads.com hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + NAME_PATTERN = r'<td class="dofir" title="(?P<N>.+?)"' + SIZE_PATTERN = r'<td class="dofir">(?P<S>[\d.,]+) (?P<U>[\w^_]+)' diff --git a/pyload/plugin/hoster/BitshareCom.py b/pyload/plugin/hoster/BitshareCom.py new file mode 100644 index 000000000..0d26c2d53 --- /dev/null +++ b/pyload/plugin/hoster/BitshareCom.py @@ -0,0 +1,156 @@ +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +import re + +from pyload.plugin.captcha.ReCaptcha import ReCaptcha +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class BitshareCom(SimpleHoster): + __name = "BitshareCom" + __type = "hoster" + __version = "0.53" + + __pattern = r'http://(?:www\.)?bitshare\.com/(files/)?(?(1)|\?f=)(?P<ID>\w+)(?(1)/(?P<NAME>.+?)\.html)' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Bitshare.com hoster plugin""" + __license = "GPLv3" + __authors = [("Paul King", ""), + ("fragonib", "fragonib[AT]yahoo[DOT]es")] + + + COOKIES = [("bitshare.com", "language_selection", "EN")] + + INFO_PATTERN = r'Downloading (?P<N>.+) - (?P<S>[\d.,]+) (?P<U>[\w^_]+)</h1>' + OFFLINE_PATTERN = r'[Ff]ile (not available|was deleted|was not found)' + + AJAXID_PATTERN = r'var ajaxdl = "(.*?)";' + TRAFFIC_USED_UP = r'Your Traffic is used up for today' + + + def setup(self): + self.multiDL = self.premium + self.chunkLimit = 1 + + + def process(self, pyfile): + if self.premium: + self.account.relogin(self.user) + + # File id + m = re.match(self.__pattern, pyfile.url) + self.file_id = max(m.group('ID1'), m.group('ID2')) + self.logDebug("File id is [%s]" % self.file_id) + + # Load main page + self.html = self.load(pyfile.url, ref=False, decode=True) + + # Check offline + if re.search(self.OFFLINE_PATTERN, self.html): + self.offline() + + # Check Traffic used up + if re.search(self.TRAFFIC_USED_UP, self.html): + self.logInfo(_("Your Traffic is used up for today")) + self.wait(30 * 60, True) + self.retry() + + # File name + m = re.match(self.__pattern, pyfile.url) + name1 = m.group('NAME') if m else None + + m = re.search(self.INFO_PATTERN, self.html) + name2 = m.group('N') if m else None + + pyfile.name = max(name1, name2) + + # Ajax file id + self.ajaxid = re.search(self.AJAXID_PATTERN, self.html).group(1) + self.logDebug("File ajax id is [%s]" % self.ajaxid) + + # This may either download our file or forward us to an error page + self.link = self.getDownloadUrl() + + if self.checkDownload({"error": ">Error occured<"}): + self.retry(5, 5 * 60, "Bitshare host : Error occured") + + + def getDownloadUrl(self): + # Return location if direct download is active + if self.premium: + header = self.load(self.pyfile.url, just_header=True) + if 'location' in header: + return header['location'] + + # Get download info + self.logDebug("Getting download info") + res = self.load("http://bitshare.com/files-ajax/" + self.file_id + "/request.html", + post={"request": "generateID", "ajaxid": self.ajaxid}) + + self.handleErrors(res, ':') + + parts = res.split(":") + filetype = parts[0] + wait = int(parts[1]) + captcha = int(parts[2]) + + self.logDebug("Download info [type: '%s', waiting: %d, captcha: %d]" % (filetype, wait, captcha)) + + # Waiting + if wait > 0: + self.logDebug("Waiting %d seconds." % wait) + if wait < 120: + self.wait(wait, False) + else: + self.wait(wait - 55, True) + self.retry() + + # Resolve captcha + if captcha == 1: + self.logDebug("File is captcha protected") + recaptcha = ReCaptcha(self) + + # Try up to 3 times + for _i in xrange(3): + response, challenge = recaptcha.challenge() + res = self.load("http://bitshare.com/files-ajax/" + self.file_id + "/request.html", + post={"request" : "validateCaptcha", + "ajaxid" : self.ajaxid, + "recaptcha_challenge_field": challenge, + "recaptcha_response_field" : response}) + if self.handleCaptchaErrors(res): + break + + # Get download URL + self.logDebug("Getting download url") + res = self.load("http://bitshare.com/files-ajax/" + self.file_id + "/request.html", + post={"request": "getDownloadURL", "ajaxid": self.ajaxid}) + + self.handleErrors(res, '#') + + url = res.split("#")[-1] + + return url + + + def handleErrors(self, res, separator): + self.logDebug("Checking response [%s]" % res) + if "ERROR:Session timed out" in res: + self.retry() + elif "ERROR" in res: + msg = res.split(separator)[-1] + self.fail(msg) + + + def handleCaptchaErrors(self, res): + self.logDebug("Result of captcha resolving [%s]" % res) + if "SUCCESS" in res: + self.correctCaptcha() + return True + elif "ERROR:SESSION ERROR" in res: + self.retry() + + self.invalidCaptcha() diff --git a/pyload/plugin/hoster/BoltsharingCom.py b/pyload/plugin/hoster/BoltsharingCom.py new file mode 100644 index 000000000..10716e695 --- /dev/null +++ b/pyload/plugin/hoster/BoltsharingCom.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class BoltsharingCom(DeadHoster): + __name = "BoltsharingCom" + __type = "hoster" + __version = "0.02" + + __pattern = r'http://(?:www\.)?boltsharing\.com/\w{12}' + __config = [] + + __description = """Boltsharing.com hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] diff --git a/pyload/plugin/hoster/CatShareNet.py b/pyload/plugin/hoster/CatShareNet.py new file mode 100644 index 000000000..fa3c13b3f --- /dev/null +++ b/pyload/plugin/hoster/CatShareNet.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.captcha.ReCaptcha import ReCaptcha +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class CatShareNet(SimpleHoster): + __name = "CatShareNet" + __type = "hoster" + __version = "0.13" + + __pattern = r'http://(?:www\.)?catshare\.net/\w{16}' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """CatShare.net hoster plugin""" + __license = "GPLv3" + __authors = [("z00nx", "z00nx0@gmail.com"), + ("prOq", ""), + ("Walter Purcaro", "vuolter@gmail.com")] + + + TEXT_ENCODING = True + + INFO_PATTERN = r'<title>(?P<N>.+) \((?P<S>[\d.,]+) (?P<U>[\w^_]+)\)<' + OFFLINE_PATTERN = r'<div class="alert alert-error"' + + IP_BLOCKED_PATTERN = ur'>Nasz serwis wykryÅ ÅŒe Twój adres IP nie pochodzi z Polski.<' + WAIT_PATTERN = r'var\scount\s=\s(\d+);' + + LINK_FREE_PATTERN = r'<form action="(.+?)" method="GET">' + LINK_PREMIUM_PATTERN = r'<form action="(.+?)" method="GET">' + + + def setup(self): + self.multiDL = self.premium + self.resumeDownload = True + + + def checkErrors(self): + m = re.search(self.IP_BLOCKED_PATTERN, self.html) + if m: + self.fail(_("Only connections from Polish IP address are allowed")) + + return super(CatShareNet, self).checkErrors() + + + def handleFree(self, pyfile): + recaptcha = ReCaptcha(self) + + response, challenge = recaptcha.challenge() + self.html = self.load(pyfile.url, + post={'recaptcha_challenge_field': challenge, + 'recaptcha_response_field' : response}) + + m = re.search(self.LINK_FREE_PATTERN, self.html) + if m: + self.link = m.group(1) diff --git a/pyload/plugin/hoster/CloudzerNet.py b/pyload/plugin/hoster/CloudzerNet.py new file mode 100644 index 000000000..c1b3beae8 --- /dev/null +++ b/pyload/plugin/hoster/CloudzerNet.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class CloudzerNet(DeadHoster): + __name = "CloudzerNet" + __type = "hoster" + __version = "0.05" + + __pattern = r'https?://(?:www\.)?(cloudzer\.net/file/|clz\.to/(file/)?)\w+' + __config = [] + + __description = """Cloudzer.net hoster plugin""" + __license = "GPLv3" + __authors = [("gs", "I-_-I-_-I@web.de"), + ("z00nx", "z00nx0@gmail.com"), + ("stickell", "l.stickell@yahoo.it")] diff --git a/pyload/plugin/hoster/CloudzillaTo.py b/pyload/plugin/hoster/CloudzillaTo.py new file mode 100644 index 000000000..a244b1fae --- /dev/null +++ b/pyload/plugin/hoster/CloudzillaTo.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class CloudzillaTo(SimpleHoster): + __name = "CloudzillaTo" + __type = "hoster" + __version = "0.06" + + __pattern = r'http://(?:www\.)?cloudzilla\.to/share/file/(?P<ID>[\w^_]+)' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Cloudzilla.to hoster plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + INFO_PATTERN = r'title="(?P<N>.+?)">\1</span> <span class="size">\((?P<S>[\d.]+) (?P<U>[\w^_]+)' + OFFLINE_PATTERN = r'>File not found...<' + + PASSWORD_PATTERN = r'<div id="pwd_protected">' + + + def checkErrors(self): + m = re.search(self.PASSWORD_PATTERN, self.html) + if m: + self.html = self.load(self.pyfile.url, get={'key': self.getPassword()}) + + if re.search(self.PASSWORD_PATTERN, self.html): + self.retry(reason="Wrong password") + + + def handleFree(self, pyfile): + self.html = self.load("http://www.cloudzilla.to/generateticket/", + post={'file_id': self.info['pattern']['ID'], 'key': self.getPassword()}) + + ticket = dict(re.findall(r'<(.+?)>([^<>]+?)</', self.html)) + + self.logDebug(ticket) + + if 'error' in ticket: + if "File is password protected" in ticket['error']: + self.retry(reason="Wrong password") + else: + self.fail(ticket['error']) + + if 'wait' in ticket: + self.wait(ticket['wait'], int(ticket['wait']) > 5) + + self.link = "http://%(server)s/download/%(file_id)s/%(ticket_id)s" % {'server' : ticket['server'], + 'file_id' : self.info['pattern']['ID'], + 'ticket_id': ticket['ticket_id']} + + + def handlePremium(self, pyfile): + return self.handleFree(pyfile) diff --git a/pyload/plugin/hoster/CramitIn.py b/pyload/plugin/hoster/CramitIn.py new file mode 100644 index 000000000..9d8d4960d --- /dev/null +++ b/pyload/plugin/hoster/CramitIn.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSHoster import XFSHoster + + +class CramitIn(XFSHoster): + __name = "CramitIn" + __type = "hoster" + __version = "0.07" + + __pattern = r'http://(?:www\.)?cramit\.in/\w{12}' + + __description = """Cramit.in hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + INFO_PATTERN = r'<span class=t2>\s*(?P<N>.*?)</span>.*?<small>\s*\((?P<S>.*?)\)' + + LINK_PATTERN = r'href="(http://cramit\.in/file_download/.*?)"' diff --git a/pyload/plugin/hoster/CrockoCom.py b/pyload/plugin/hoster/CrockoCom.py new file mode 100644 index 000000000..3be0c5909 --- /dev/null +++ b/pyload/plugin/hoster/CrockoCom.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.captcha.ReCaptcha import ReCaptcha +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class CrockoCom(SimpleHoster): + __name = "CrockoCom" + __type = "hoster" + __version = "0.19" + + __pattern = r'http://(?:www\.)?(crocko|easy-share)\.com/\w+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Crocko hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + NAME_PATTERN = r'<span class="fz24">Download:\s*<strong>(?P<N>.*)' + SIZE_PATTERN = r'<span class="tip1"><span class="inner">(?P<S>[^<]+)</span></span>' + OFFLINE_PATTERN = r'<h1>Sorry,<br />the page you\'re looking for <br />isn\'t here.</h1>|File not found' + + CAPTCHA_PATTERN = r"u='(/file_contents/captcha/\w+)';\s*w='(\d+)';" + + FORM_PATTERN = r'<form method="post" action="(.+?)">(.*?)</form>' + FORM_INPUT_PATTERN = r'<input[^>]* name="?([^" ]+)"? value="?([^" ]+)"?.*?>' + + NAME_REPLACEMENTS = [(r'<.*?>', '')] + + + def handleFree(self, pyfile): + if "You need Premium membership to download this file." in self.html: + self.fail(_("You need Premium membership to download this file")) + + for _i in xrange(5): + m = re.search(self.CAPTCHA_PATTERN, self.html) + if m: + url, wait_time = 'http://crocko.com' + m.group(1), int(m.group(2)) + self.wait(wait_time) + self.html = self.load(url) + else: + break + + m = re.search(self.FORM_PATTERN, self.html, re.S) + if m is None: + self.error(_("FORM_PATTERN not found")) + + action, form = m.groups() + inputs = dict(re.findall(self.FORM_INPUT_PATTERN, form)) + recaptcha = ReCaptcha(self) + + for _i in xrange(5): + inputs['recaptcha_response_field'], inputs['recaptcha_challenge_field'] = recaptcha.challenge() + self.download(action, post=inputs) + + if self.checkDownload({"captcha": recaptcha.KEY_AJAX_PATTERN}): + self.invalidCaptcha() + else: + break + else: + self.fail(_("No valid captcha solution received")) diff --git a/pyload/plugin/hoster/CyberlockerCh.py b/pyload/plugin/hoster/CyberlockerCh.py new file mode 100644 index 000000000..b56736a8e --- /dev/null +++ b/pyload/plugin/hoster/CyberlockerCh.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class CyberlockerCh(DeadHoster): + __name = "CyberlockerCh" + __type = "hoster" + __version = "0.02" + + __pattern = r'http://(?:www\.)?cyberlocker\.ch/\w+' + __config = [] + + __description = """Cyberlocker.ch hoster plugin""" + __license = "GPLv3" + __authors = [("stickell", "l.stickell@yahoo.it")] diff --git a/pyload/plugin/hoster/CzshareCom.py b/pyload/plugin/hoster/CzshareCom.py new file mode 100644 index 000000000..9926c8d74 --- /dev/null +++ b/pyload/plugin/hoster/CzshareCom.py @@ -0,0 +1,159 @@ +# -*- coding: utf-8 -*- +# +# Test links: +# http://czshare.com/5278880/random.bin + +import re + +from pyload.plugin.internal.SimpleHoster import SimpleHoster +from pyload.utils import parseFileSize + + +class CzshareCom(SimpleHoster): + __name = "CzshareCom" + __type = "hoster" + __version = "0.99" + + __pattern = r'http://(?:www\.)?(czshare|sdilej)\.(com|cz)/(\d+/|download\.php\?).+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """CZshare.com hoster plugin, now Sdilej.cz""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + NAME_PATTERN = r'<div class="tab" id="parameters">\s*<p>\s*Cel. n.zev: <a href=.*?>(?P<N>[^<]+)</a>' + SIZE_PATTERN = r'<div class="tab" id="category">(?:\s*<p>[^\n]*</p>)*\s*Velikost:\s*(?P<S>[\d .,]+)(?P<U>[\w^_]+)\s*</div>' + OFFLINE_PATTERN = r'<div class="header clearfix">\s*<h2 class="red">' + + SIZE_REPLACEMENTS = [(' ', '')] + URL_REPLACEMENTS = [(r'http://[^/]*/download.php\?.*?id=(\w+).*', r'http://sdilej.cz/\1/x/')] + + CHECK_TRAFFIC = True + + FREE_URL_PATTERN = r'<a href="(.+?)" class="page-download">[^>]*alt="(.+?)" /></a>' + FREE_FORM_PATTERN = r'<form action="download\.php" method="post">\s*<img src="captcha\.php" id="captcha" />(.*?)</form>' + PREMIUM_FORM_PATTERN = r'<form action="/profi_down\.php" method="post">(.*?)</form>' + FORM_INPUT_PATTERN = r'<input[^>]* name="(.+?)" value="(.+?)"[^>]*/>' + MULTIDL_PATTERN = r'<p><font color=\'red\'>Z[^<]*PROFI.</font></p>' + USER_CREDIT_PATTERN = r'<div class="credit">\s*kredit: <strong>([\d .,]+)(\w+)</strong>\s*</div><!-- .credit -->' + + + def checkTrafficLeft(self): + # check if user logged in + m = re.search(self.USER_CREDIT_PATTERN, self.html) + if m is None: + self.account.relogin(self.user) + self.html = self.load(self.pyfile.url, decode=True) + m = re.search(self.USER_CREDIT_PATTERN, self.html) + if m is None: + return False + + # check user credit + try: + credit = parseFileSize(m.group(1).replace(' ', ''), m.group(2)) + self.logInfo(_("Premium download for %i KiB of Credit") % (self.pyfile.size / 1024)) + self.logInfo(_("User %s has %i KiB left") % (self.user, credit / 1024)) + if credit < self.pyfile.size: + self.logInfo(_("Not enough credit to download file: %s") % self.pyfile.name) + return False + except Exception, e: + # let's continue and see what happens... + self.logError(e) + + return True + + + def handlePremium(self, pyfile): + # parse download link + try: + form = re.search(self.PREMIUM_FORM_PATTERN, self.html, re.S).group(1) + inputs = dict(re.findall(self.FORM_INPUT_PATTERN, form)) + except Exception, e: + self.logError(e) + self.resetAccount() + + # download the file, destination is determined by pyLoad + self.download("http://sdilej.cz/profi_down.php", post=inputs, disposition=True) + + + def handleFree(self, pyfile): + # get free url + m = re.search(self.FREE_URL_PATTERN, self.html) + if m is None: + self.error(_("FREE_URL_PATTERN not found")) + + parsed_url = "http://sdilej.cz" + m.group(1) + + self.logDebug("PARSED_URL:" + parsed_url) + + # get download ticket and parse html + self.html = self.load(parsed_url, decode=True) + if re.search(self.MULTIDL_PATTERN, self.html): + self.longWait(5 * 60, 12) + + try: + form = re.search(self.FREE_FORM_PATTERN, self.html, re.S).group(1) + inputs = dict(re.findall(self.FORM_INPUT_PATTERN, form)) + pyfile.size = int(inputs['size']) + + except Exception, e: + self.logError(e) + self.error(_("Form")) + + # get and decrypt captcha + captcha_url = 'http://sdilej.cz/captcha.php' + for _i in xrange(5): + inputs['captchastring2'] = self.decryptCaptcha(captcha_url) + self.html = self.load(parsed_url, post=inputs, decode=True) + + if u"<li>ZadanÜ ovÄÅovacà kód nesouhlasÃ!</li>" in self.html: + self.invalidCaptcha() + + elif re.search(self.MULTIDL_PATTERN, self.html): + self.longWait(5 * 60, 12) + + else: + self.correctCaptcha() + break + else: + self.fail(_("No valid captcha code entered")) + + m = re.search("countdown_number = (\d+);", self.html) + self.setWait(int(m.group(1)) if m else 50) + + # download the file, destination is determined by pyLoad + self.logDebug("WAIT URL", self.req.lastEffectiveURL) + + m = re.search("free_wait.php\?server=(.*?)&(.*)", self.req.lastEffectiveURL) + if m is None: + self.error(_("Download URL not found")) + + self.link = "http://%s/download.php?%s" % (m.group(1), m.group(2)) + + self.wait() + + + def checkFile(self, rules={}): + # check download + check = self.checkDownload({ + "temp offline" : re.compile(r"^Soubor je do.*asn.* nedostupn.*$"), + "credit" : re.compile(r"^Nem.*te dostate.*n.* kredit.$"), + "multi-dl" : re.compile(self.MULTIDL_PATTERN), + "captcha" : "<li>ZadanÜ ovÄÅovacà kód nesouhlasÃ!</li>" + }) + + if check == "temp offline": + self.fail(_("File not available - try later")) + + elif check == "credit": + self.resetAccount() + + elif check == "multi-dl": + self.longWait(5 * 60, 12) + + elif check == "captcha": + self.invalidCaptcha() + self.retry() + + return super(CzshareCom, self).checkFile(rules) diff --git a/pyload/plugin/hoster/DailymotionCom.py b/pyload/plugin/hoster/DailymotionCom.py new file mode 100644 index 000000000..3fdac761c --- /dev/null +++ b/pyload/plugin/hoster/DailymotionCom.py @@ -0,0 +1,125 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.datatype.File import statusMap +from pyload.utils import json_loads +from pyload.network.RequestFactory import getURL +from pyload.plugin.Hoster import Hoster + + +def getInfo(urls): + result = [] + regex = re.compile(DailymotionCom.__pattern) + apiurl = "https://api.dailymotion.com/video/%s" + request = {"fields": "access_error,status,title"} + + for url in urls: + id = regex.match(url).group('ID') + html = getURL(apiurl % id, get=request) + info = json_loads(html) + + name = info['title'] + ".mp4" if "title" in info else url + + if "error" in info or info['access_error']: + status = "offline" + else: + status = info['status'] + if status in ("ready", "published"): + status = "online" + elif status in ("waiting", "processing"): + status = "temp. offline" + else: + status = "offline" + + result.append((name, 0, statusMap[status], url)) + + return result + + +class DailymotionCom(Hoster): + __name = "DailymotionCom" + __type = "hoster" + __version = "0.20" + + __pattern = r'https?://(?:www\.)?dailymotion\.com/.*video/(?P<ID>[\w^_]+)' + __config = [("quality", "Lowest;LD 144p;LD 240p;SD 384p;HQ 480p;HD 720p;HD 1080p;Highest", "Quality", "Highest")] + + __description = """Dailymotion.com hoster plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + def setup(self): + self.resumeDownload = True + self.multiDL = True + + + def getStreams(self): + streams = [] + + for result in re.finditer(r"\"(?P<URL>http:\\/\\/www.dailymotion.com\\/cdn\\/H264-(?P<QF>.*?)\\.*?)\"", + self.html): + url = result.group('URL') + qf = result.group('QF') + + link = url.replace("\\", "") + quality = tuple(int(x) for x in qf.split("x")) + + streams.append((quality, link)) + + return sorted(streams, key=lambda x: x[0][::-1]) + + + def getQuality(self): + q = self.getConfig('quality') + + if q == "Lowest": + quality = 0 + elif q == "Highest": + quality = -1 + else: + quality = int(q.rsplit(" ")[1][:-1]) + + return quality + + + def getLink(self, streams, quality): + if quality > 0: + for x, s in [item for item in enumerate(streams)][::-1]: + qf = s[0][1] + if qf <= quality: + idx = x + break + else: + idx = 0 + else: + idx = quality + + s = streams[idx] + + self.logInfo(_("Download video quality %sx%s") % s[0]) + + return s[1] + + + def checkInfo(self, pyfile): + pyfile.name, pyfile.size, pyfile.status, pyfile.url = getInfo([pyfile.url])[0] + + if pyfile.status == 1: + self.offline() + + elif pyfile.status == 6: + self.tempOffline() + + + def process(self, pyfile): + self.checkInfo(pyfile) + + id = re.match(self.__pattern, pyfile.url).group('ID') + self.html = self.load("http://www.dailymotion.com/embed/video/" + id, decode=True) + + streams = self.getStreams() + quality = self.getQuality() + + self.download(self.getLink(streams, quality)) diff --git a/pyload/plugin/hoster/DataHu.py b/pyload/plugin/hoster/DataHu.py new file mode 100644 index 000000000..16b3c2737 --- /dev/null +++ b/pyload/plugin/hoster/DataHu.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# +# Test links: +# http://data.hu/get/6381232/random.bin + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class DataHu(SimpleHoster): + __name = "DataHu" + __type = "hoster" + __version = "0.03" + + __pattern = r'http://(?:www\.)?data\.hu/get/\w+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Data.hu hoster plugin""" + __license = "GPLv3" + __authors = [("crash", ""), + ("stickell", "l.stickell@yahoo.it")] + + + INFO_PATTERN = ur'<title>(?P<N>.*) \((?P<S>[^)]+)\) let\xf6lt\xe9se</title>' + OFFLINE_PATTERN = ur'Az adott f\xe1jl nem l\xe9tezik' + LINK_FREE_PATTERN = r'<div class="download_box_button"><a href="(.+?)">' + + + def setup(self): + self.resumeDownload = True + self.multiDL = self.premium diff --git a/pyload/plugin/hoster/DataportCz.py b/pyload/plugin/hoster/DataportCz.py new file mode 100644 index 000000000..9a7ceda46 --- /dev/null +++ b/pyload/plugin/hoster/DataportCz.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class DataportCz(SimpleHoster): + __name = "DataportCz" + __type = "hoster" + __version = "0.41" + + __pattern = r'http://(?:www\.)?dataport\.cz/file/(.+)' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Dataport.cz hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + NAME_PATTERN = r'<span itemprop="name">(?P<N>[^<]+)</span>' + SIZE_PATTERN = r'<td class="fil">Velikost</td>\s*<td>(?P<S>[^<]+)</td>' + OFFLINE_PATTERN = r'<h2>Soubor nebyl nalezen</h2>' + + CAPTCHA_PATTERN = r'<section id="captcha_bg">\s*<img src="(.*?)"' + FREE_SLOTS_PATTERN = ur'PoÄet volnÜch slotů: <span class="darkblue">(\d+)</span><br />' + + + def handleFree(self, pyfile): + captchas = {"1": "jkeG", "2": "hMJQ", "3": "vmEK", "4": "ePQM", "5": "blBd"} + + for _i in xrange(60): + action, inputs = self.parseHtmlForm('free_download_form') + self.logDebug(action, inputs) + if not action or not inputs: + self.error(_("free_download_form")) + + if "captchaId" in inputs and inputs['captchaId'] in captchas: + inputs['captchaCode'] = captchas[inputs['captchaId']] + else: + self.error(_("captcha")) + + self.download("http://www.dataport.cz%s" % action, post=inputs) + + check = self.checkDownload({"captcha": 'alert("\u0160patn\u011b opsan\u00fd k\u00f3d z obr\u00e1zu");', + "slot" : 'alert("Je n\u00e1m l\u00edto, ale moment\u00e1ln\u011b nejsou'}) + if check == "captcha": + self.error(_("invalid captcha")) + + elif check == "slot": + self.logDebug("No free slots - wait 60s and retry") + self.wait(60, False) + self.html = self.load(pyfile.url, decode=True) + continue + + else: + break diff --git a/pyload/plugin/hoster/DateiTo.py b/pyload/plugin/hoster/DateiTo.py new file mode 100644 index 000000000..f967c894a --- /dev/null +++ b/pyload/plugin/hoster/DateiTo.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.captcha.ReCaptcha import ReCaptcha +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class DateiTo(SimpleHoster): + __name = "DateiTo" + __type = "hoster" + __version = "0.07" + + __pattern = r'http://(?:www\.)?datei\.to/datei/(?P<ID>\w+)\.html' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Datei.to hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + NAME_PATTERN = r'Dateiname:</td>\s*<td colspan="2"><strong>(?P<N>.*?)</' + SIZE_PATTERN = r'Dateigröße:</td>\s*<td colspan="2">(?P<S>.*?)</' + OFFLINE_PATTERN = r'>Datei wurde nicht gefunden<|>Bitte wÀhle deine Datei aus... <' + + WAIT_PATTERN = r'countdown\({seconds: (\d+)' + MULTIDL_PATTERN = r'>Du lÀdst bereits eine Datei herunter<' + + DATA_PATTERN = r'url: "(.*?)", data: "(.*?)",' + + + def handleFree(self, pyfile): + url = 'http://datei.to/ajax/download.php' + data = {'P': 'I', 'ID': self.info['pattern']['ID']} + recaptcha = ReCaptcha(self) + + for _i in xrange(10): + self.logDebug("URL", url, "POST", data) + self.html = self.load(url, post=data) + self.checkErrors() + + if url.endswith('download.php') and 'P' in data: + if data['P'] == 'I': + self.doWait() + + elif data['P'] == 'IV': + break + + m = re.search(self.DATA_PATTERN, self.html) + if m is None: + self.error(_("data")) + url = 'http://datei.to/' + m.group(1) + data = dict(x.split('=') for x in m.group(2).split('&')) + + if url.endswith('recaptcha.php'): + data['recaptcha_response_field'], data['recaptcha_challenge_field'] = recaptcha.challenge() + else: + self.fail(_("Too bad...")) + + self.link = self.html + + + def checkErrors(self): + m = re.search(self.MULTIDL_PATTERN, self.html) + if m: + m = re.search(self.WAIT_PATTERN, self.html) + wait_time = int(m.group(1)) if m else 30 + + errmsg = self.info['error'] = _("Parallel downloads") + self.retry(wait_time=wait_time, reason=errmsg) + + self.info.pop('error', None) + + + def doWait(self): + m = re.search(self.WAIT_PATTERN, self.html) + wait_time = int(m.group(1)) if m else 30 + + self.load('http://datei.to/ajax/download.php', post={'P': 'Ads'}) + self.wait(wait_time, False) diff --git a/pyload/plugin/hoster/DdlstorageCom.py b/pyload/plugin/hoster/DdlstorageCom.py new file mode 100644 index 000000000..53c0df7cb --- /dev/null +++ b/pyload/plugin/hoster/DdlstorageCom.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class DdlstorageCom(DeadHoster): + __name = "DdlstorageCom" + __type = "hoster" + __version = "1.02" + + __pattern = r'https?://(?:www\.)?ddlstorage\.com/\w+' + __config = [] + + __description = """DDLStorage.com hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz"), + ("stickell", "l.stickell@yahoo.it")] diff --git a/pyload/plugin/hoster/DebridItaliaCom.py b/pyload/plugin/hoster/DebridItaliaCom.py new file mode 100644 index 000000000..a0308e28e --- /dev/null +++ b/pyload/plugin/hoster/DebridItaliaCom.py @@ -0,0 +1,41 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.internal.MultiHoster import MultiHoster + + +class DebridItaliaCom(MultiHoster): + __name = "DebridItaliaCom" + __type = "hoster" + __version = "0.17" + + __pattern = r'https?://(?:www\.|s\d+\.)?debriditalia\.com/dl/\d+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Debriditalia.com multi-hoster plugin""" + __license = "GPLv3" + __authors = [("stickell", "l.stickell@yahoo.it"), + ("Walter Purcaro", "vuolter@gmail.com")] + + + URL_REPLACEMENTS = [("https://", "http://")] + + + def handlePremium(self, pyfile): + self.html = self.load("http://www.debriditalia.com/api.php", + get={'generate': "on", 'link': pyfile.url, 'p': self.getPassword()}) + + if "ERROR:" not in self.html: + self.link = self.html.strip() + else: + self.info['error'] = re.search(r'ERROR:(.*)', self.html).group(1).strip() + + self.html = self.load("http://debriditalia.com/linkgen2.php", + post={'xjxfun' : "convertiLink", + 'xjxargs[]': "S<![CDATA[%s]]>" % pyfile.url, + 'xjxargs[]': "S%s" % self.getPassword()}) + try: + self.link = re.search(r'<a href="(.+?)"', self.html).group(1) + except AttributeError: + pass diff --git a/pyload/plugin/hoster/DepositfilesCom.py b/pyload/plugin/hoster/DepositfilesCom.py new file mode 100644 index 000000000..e16222856 --- /dev/null +++ b/pyload/plugin/hoster/DepositfilesCom.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- + +import re +import urllib + +from pyload.plugin.captcha.ReCaptcha import ReCaptcha +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class DepositfilesCom(SimpleHoster): + __name = "DepositfilesCom" + __type = "hoster" + __version = "0.55" + + __pattern = r'https?://(?:www\.)?(depositfiles\.com|dfiles\.(eu|ru))(/\w{1,3})?/files/(?P<ID>\w+)' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Depositfiles.com hoster plugin""" + __license = "GPLv3" + __authors = [("spoob", "spoob@pyload.org"), + ("zoidberg", "zoidberg@mujmail.cz"), + ("Walter Purcaro", "vuolter@gmail.com")] + + + NAME_PATTERN = r'<script type="text/javascript">eval\( unescape\(\'(?P<N>.*?)\'' + SIZE_PATTERN = r': <b>(?P<S>[\d.,]+) (?P<U>[\w^_]+)</b>' + OFFLINE_PATTERN = r'<span class="html_download_api-not_exists"></span>' + + NAME_REPLACEMENTS = [(r'\%u([0-9A-Fa-f]{4})', lambda m: unichr(int(m.group(1), 16))), + (r'.*<b title="(?P<N>.+?)".*', "\g<N>")] + URL_REPLACEMENTS = [(__pattern + ".*", "https://dfiles.eu/files/\g<ID>")] + + COOKIES = [("dfiles.eu", "lang_current", "en")] + + WAIT_PATTERN = r'(?:download_waiter_remain">|html_download_api-limit_interval">|>Please wait|>Try in).+' + ERROR_PATTER = r'File is checked, please try again in a minute' + + LINK_FREE_PATTERN = r'<form id="downloader_file_form" action="(http://.+?\.(dfiles\.eu|depositfiles\.com)/.+?)" method="post"' + LINK_PREMIUM_PATTERN = r'class="repeat"><a href="(.+?)"' + LINK_MIRROR_PATTERN = r'class="repeat_mirror"><a href="(.+?)"' + + + def handleFree(self, pyfile): + self.html = self.load(pyfile.url, post={'gateway_result': "1"}) + + self.checkErrors() + + m = re.search(r"var fid = '(\w+)';", self.html) + if m is None: + self.retry(wait_time=5) + params = {'fid': m.group(1)} + self.logDebug("FID: %s" % params['fid']) + + self.checkErrors() + + recaptcha = ReCaptcha(self) + captcha_key = recaptcha.detect_key() + if captcha_key is None: + return + + self.html = self.load("https://dfiles.eu/get_file.php", get=params) + + if '<input type=button value="Continue" onclick="check_recaptcha' in self.html: + params['response'], params['challenge'] = recaptcha.challenge(captcha_key) + self.html = self.load("https://dfiles.eu/get_file.php", get=params) + + m = re.search(self.LINK_FREE_PATTERN, self.html) + if m: + self.link = urllib.unquote(m.group(1)) + + + def handlePremium(self, pyfile): + if '<span class="html_download_api-gold_traffic_limit">' in self.html: + self.logWarning(_("Download limit reached")) + self.retry(25, 60 * 60, "Download limit reached") + + elif 'onClick="show_gold_offer' in self.html: + self.account.relogin(self.user) + self.retry() + + else: + link = re.search(self.LINK_PREMIUM_PATTERN, self.html) + mirror = re.search(self.LINK_MIRROR_PATTERN, self.html) + + if link: + self.link = link.group(1) + + elif mirror: + self.link = mirror.group(1) diff --git a/pyload/plugin/hoster/DevhostSt.py b/pyload/plugin/hoster/DevhostSt.py new file mode 100644 index 000000000..f35530523 --- /dev/null +++ b/pyload/plugin/hoster/DevhostSt.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# +# Test links: +# http://d-h.st/mM8 + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class DevhostSt(SimpleHoster): + __name = "DevhostSt" + __type = "hoster" + __version = "0.05" + + __pattern = r'http://(?:www\.)?d-h\.st/(?!users/)\w{3}' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """d-h.st hoster plugin""" + __license = "GPLv3" + __authors = [("zapp-brannigan", "fuerst.reinje@web.de")] + + + NAME_PATTERN = r'<span title="(?P<N>.*?)"' + SIZE_PATTERN = r'</span> \((?P<S>[\d.,]+) (?P<U>[\w^_]+)\)<br' + HASHSUM_PATTERN = r'>(?P<T>.*?) Sum</span>: (?P<H>.*?)<br' + + OFFLINE_PATTERN = r'>File Not Found' + LINK_FREE_PATTERN = r'var product_download_url= \'(.+?)\'' + + + def setup(self): + self.multiDL = True + self.chunkLimit = 1 diff --git a/pyload/plugin/hoster/DlFreeFr.py b/pyload/plugin/hoster/DlFreeFr.py new file mode 100644 index 000000000..684da2b6d --- /dev/null +++ b/pyload/plugin/hoster/DlFreeFr.py @@ -0,0 +1,138 @@ +# -*- coding: utf-8 -*- + +import pycurl +import re + +from pyload.network.Browser import Browser +from pyload.network.CookieJar import CookieJar +from pyload.plugin.captcha.AdYouLike import AdYouLike +from pyload.plugin.internal.SimpleHoster import SimpleHoster, replace_patterns +from pyload.utils import json_loads + + +class CustomBrowser(Browser): + + def __init__(self, bucket=None, options={}): + Browser.__init__(self, bucket, options) + + + def load(self, *args, **kwargs): + post = kwargs.get("post") + + if post is None and len(args) > 2: + post = args[2] + + if post: + self.http.c.setopt(pycurl.FOLLOWLOCATION, 0) + self.http.c.setopt(pycurl.POST, 1) + self.http.c.setopt(pycurl.CUSTOMREQUEST, "POST") + else: + self.http.c.setopt(pycurl.FOLLOWLOCATION, 1) + self.http.c.setopt(pycurl.POST, 0) + self.http.c.setopt(pycurl.CUSTOMREQUEST, "GET") + + return Browser.load(self, *args, **kwargs) + + +class DlFreeFr(SimpleHoster): + __name = "DlFreeFr" + __type = "hoster" + __version = "0.28" + + __pattern = r'http://(?:www\.)?dl\.free\.fr/(\w+|getfile\.pl\?file=/\w+)' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Dl.free.fr hoster plugin""" + __license = "GPLv3" + __authors = [("the-razer", "daniel_ AT gmx DOT net"), + ("zoidberg", "zoidberg@mujmail.cz"), + ("Toilal", "toilal.dev@gmail.com")] + + + NAME_PATTERN = r'Fichier:</td>\s*<td.*?>(?P<N>[^>]*)</td>' + SIZE_PATTERN = r'Taille:</td>\s*<td.*?>(?P<S>[\d.,]+\w)o' + OFFLINE_PATTERN = r'Erreur 404 - Document non trouv|Fichier inexistant|Le fichier demandé n\'a pas été trouvé' + + + def setup(self): + self.resumeDownload = True + self.multiDL = True + self.limitDL = 5 + self.chunkLimit = 1 + + + def init(self): + factory = self.core.requestFactory + self.req = CustomBrowser(factory.bucket, factory.getOptions()) + + + def process(self, pyfile): + pyfile.url = replace_patterns(pyfile.url, self.URL_REPLACEMENTS) + valid_url = pyfile.url + headers = self.load(valid_url, just_header=True) + + if headers.get('code') == 302: + valid_url = headers.get('location') + headers = self.load(valid_url, just_header=True) + + if headers.get('code') == 200: + content_type = headers.get('content-type') + if content_type and content_type.startswith("text/html"): + # Undirect acces to requested file, with a web page providing it (captcha) + self.html = self.load(valid_url) + self.handleFree(pyfile) + else: + # Direct access to requested file for users using free.fr as Internet Service Provider. + self.link = valid_url + + elif headers.get('code') == 404: + self.offline() + + else: + self.fail(_("Invalid return code: ") + str(headers.get('code'))) + + + def handleFree(self, pyfile): + action, inputs = self.parseHtmlForm('action="getfile.pl"') + + adyoulike = AdYouLike(self) + response, challenge = adyoulike.challenge() + inputs.update(response) + + self.load("http://dl.free.fr/getfile.pl", post=inputs) + headers = self.getLastHeaders() + if headers.get("code") == 302 and "set-cookie" in headers and "location" in headers: + m = re.search("(.*?)=(.*?); path=(.*?); domain=(.*?)", headers.get("set-cookie")) + cj = CookieJar(__name) + if m: + cj.setCookie(m.group(4), m.group(1), m.group(2), m.group(3)) + else: + self.fail(_("Cookie error")) + + self.link = headers.get("location") + + self.req.setCookieJar(cj) + else: + self.fail(_("Invalid response")) + + + def getLastHeaders(self): + # parse header + header = {"code": self.req.code} + for line in self.req.http.header.splitlines(): + line = line.strip() + if not line or ":" not in line: + continue + + key, none, value = line.partition(":") + key = key.lower().strip() + value = value.strip() + + if key in header: + if type(header[key]) == list: + header[key].append(value) + else: + header[key] = [header[key], value] + else: + header[key] = value + return header diff --git a/pyload/plugin/hoster/DodanePl.py b/pyload/plugin/hoster/DodanePl.py new file mode 100644 index 000000000..8edbb64c0 --- /dev/null +++ b/pyload/plugin/hoster/DodanePl.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class DodanePl(DeadHoster): + __name = "DodanePl" + __type = "hoster" + __version = "0.03" + + __pattern = r'http://(?:www\.)?dodane\.pl/file/\d+' + __config = [] + + __description = """Dodane.pl hoster plugin""" + __license = "GPLv3" + __authors = [("z00nx", "z00nx0@gmail.com")] diff --git a/pyload/plugin/hoster/DuploadOrg.py b/pyload/plugin/hoster/DuploadOrg.py new file mode 100644 index 000000000..decd1a7ff --- /dev/null +++ b/pyload/plugin/hoster/DuploadOrg.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class DuploadOrg(DeadHoster): + __name = "DuploadOrg" + __type = "hoster" + __version = "0.02" + + __pattern = r'http://(?:www\.)?dupload\.org/\w{12}' + __config = [] + + __description = """Dupload.grg hoster plugin""" + __license = "GPLv3" + __authors = [("stickell", "l.stickell@yahoo.it")] diff --git a/pyload/plugin/hoster/EasybytezCom.py b/pyload/plugin/hoster/EasybytezCom.py new file mode 100644 index 000000000..8dcb04b98 --- /dev/null +++ b/pyload/plugin/hoster/EasybytezCom.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSHoster import XFSHoster + + +class EasybytezCom(XFSHoster): + __name = "EasybytezCom" + __type = "hoster" + __version = "0.23" + + __pattern = r'http://(?:www\.)?easybytez\.com/\w{12}' + + __description = """Easybytez.com hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz"), + ("stickell", "l.stickell@yahoo.it")] + + + OFFLINE_PATTERN = r'>File not available' + + LINK_PATTERN = r'(http://(\w+\.(easybytez|easyload|ezbytez|zingload)\.(com|to)|\d+\.\d+\.\d+\.\d+)/files/\d+/\w+/.+?)["\'<]' diff --git a/pyload/plugin/hoster/EdiskCz.py b/pyload/plugin/hoster/EdiskCz.py new file mode 100644 index 000000000..7f5f76b73 --- /dev/null +++ b/pyload/plugin/hoster/EdiskCz.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class EdiskCz(SimpleHoster): + __name = "EdiskCz" + __type = "hoster" + __version = "0.23" + + __pattern = r'http://(?:www\.)?edisk\.(cz|sk|eu)/(stahni|sk/stahni|en/download)/.+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Edisk.cz hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + INFO_PATTERN = r'<span class="fl" title="(?P<N>.+?)">\s*.*?\((?P<S>[\d.,]+) (?P<U>[\w^_]+)\)</h1></span>' + OFFLINE_PATTERN = r'<h3>This file does not exist due to one of the following:</h3><ul><li>' + + ACTION_PATTERN = r'/en/download/(\d+/.*\.html)' + LINK_FREE_PATTERN = r'http://.*edisk\.cz.*\.html' + + + def setup(self): + self.multiDL = False + + + def process(self, pyfile): + url = re.sub("/(stahni|sk/stahni)/", "/en/download/", pyfile.url) + + self.logDebug("URL:" + url) + + m = re.search(self.ACTION_PATTERN, url) + if m is None: + self.error(_("ACTION_PATTERN not found")) + action = m.group(1) + + self.html = self.load(url, decode=True) + self.getFileInfo() + + self.html = self.load(re.sub("/en/download/", "/en/download-slow/", url)) + + url = self.load(re.sub("/en/download/", "/x-download/", url), post={ + "action": action + }) + + if not re.match(self.LINK_FREE_PATTERN, url): + self.fail(_("Unexpected server response")) + + self.link = url diff --git a/pyload/plugin/hoster/EgoFilesCom.py b/pyload/plugin/hoster/EgoFilesCom.py new file mode 100644 index 000000000..f87c96dd1 --- /dev/null +++ b/pyload/plugin/hoster/EgoFilesCom.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class EgoFilesCom(DeadHoster): + __name = "EgoFilesCom" + __type = "hoster" + __version = "0.16" + + __pattern = r'https?://(?:www\.)?egofiles\.com/\w+' + __config = [] + + __description = """Egofiles.com hoster plugin""" + __license = "GPLv3" + __authors = [("stickell", "l.stickell@yahoo.it")] diff --git a/pyload/plugin/hoster/EnteruploadCom.py b/pyload/plugin/hoster/EnteruploadCom.py new file mode 100644 index 000000000..e64c284f6 --- /dev/null +++ b/pyload/plugin/hoster/EnteruploadCom.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class EnteruploadCom(DeadHoster): + __name = "EnteruploadCom" + __type = "hoster" + __version = "0.02" + + __pattern = r'http://(?:www\.)?enterupload\.com/\w+' + __config = [] + + __description = """EnterUpload.com hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] diff --git a/pyload/plugin/hoster/EpicShareNet.py b/pyload/plugin/hoster/EpicShareNet.py new file mode 100644 index 000000000..05a1aca40 --- /dev/null +++ b/pyload/plugin/hoster/EpicShareNet.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class EpicShareNet(DeadHoster): + __name = "EpicShareNet" + __type = "hoster" + __version = "0.02" + + __pattern = r'https?://(?:www\.)?epicshare\.net/\w{12}' + __config = [] + + __description = """EpicShare.net hoster plugin""" + __license = "GPLv3" + __authors = [("t4skforce", "t4skforce1337[AT]gmail[DOT]com")] diff --git a/pyload/plugin/hoster/EuroshareEu.py b/pyload/plugin/hoster/EuroshareEu.py new file mode 100644 index 000000000..b66369fe0 --- /dev/null +++ b/pyload/plugin/hoster/EuroshareEu.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class EuroshareEu(SimpleHoster): + __name = "EuroshareEu" + __type = "hoster" + __version = "0.28" + + __pattern = r'http://(?:www\.)?euroshare\.(eu|sk|cz|hu|pl)/file/.+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Euroshare.eu hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + INFO_PATTERN = r'<span style="float: left;"><strong>(?P<N>.+?)</strong> \((?P<S>.+?)\)</span>' + OFFLINE_PATTERN = ur'<h2>S.bor sa nena.iel</h2>|PoÅŸadovaná stránka neexistuje!' + + LINK_FREE_PATTERN = r'<a href="(/file/\d+/[^/]*/download/)"><div class="downloadButton"' + + ERR_PARDL_PATTERN = r'<h2>Prebieha s.ahovanie</h2>|<p>Naraz je z jednej IP adresy mo.n. s.ahova. iba jeden s.bor' + ERR_NOT_LOGGED_IN_PATTERN = r'href="/customer-zone/login/"' + + URL_REPLACEMENTS = [(r"(http://[^/]*\.)(sk|cz|hu|pl)/", r"\1eu/")] + + + def handlePremium(self, pyfile): + if self.ERR_NOT_LOGGED_IN_PATTERN in self.html: + self.account.relogin(self.user) + self.retry(reason=_("User not logged in")) + + self.link = pyfile.url.rstrip('/') + "/download/" + + check = self.checkDownload({"login": re.compile(self.ERR_NOT_LOGGED_IN_PATTERN), + "json" : re.compile(r'\{"status":"error".*?"message":"(.*?)"')}) + + if check == "login" or (check == "json" and self.lastCheck.group(1) == "Access token expired"): + self.account.relogin(self.user) + self.retry(reason=_("Access token expired")) + + elif check == "json": + self.fail(self.lastCheck.group(1)) + + + def handleFree(self, pyfile): + if re.search(self.ERR_PARDL_PATTERN, self.html): + self.longWait(5 * 60, 12) + + m = re.search(self.LINK_FREE_PATTERN, self.html) + if m is None: + self.error(_("LINK_FREE_PATTERN not found")) + + self.link = "http://euroshare.eu%s" % m.group(1) + + + def checkFile(self, rules={}): + if self.checkDownload({"multi-dl": re.compile(self.ERR_PARDL_PATTERN)}) + self.longWait(5 * 60, 12) + + return super(EuroshareEu, self).checkFile(rules) diff --git a/pyload/plugin/hoster/ExashareCom.py b/pyload/plugin/hoster/ExashareCom.py new file mode 100644 index 000000000..c7b876076 --- /dev/null +++ b/pyload/plugin/hoster/ExashareCom.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.internal.XFSHoster import XFSHoster + + +class ExashareCom(XFSHoster): + __name = "ExashareCom" + __type = "hoster" + __version = "0.01" + + __pattern = r'http://(?:www\.)?exashare\.com/\w{12}' + + __description = """Exashare.com hoster plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + INFO_PATTERN = r'>(?P<NAME>.+?)<small>\( (?P<S>[\d.,]+) (?P<U>[\w^_]+)' + LINK_FREE_PATTERN = r'file: "(.+?)"' + + + def setup(self): + self.multiDL = True + self.chunkLimit = 1 + self.resumeDownload = self.premium + + + def handleFree(self, pyfile): + m = re.search(self.LINK_FREE_PATTERN, self.html) + if m is None: + self.error(_("Free download link not found")) + else: + self.link = m.group(1) diff --git a/pyload/plugin/hoster/ExtabitCom.py b/pyload/plugin/hoster/ExtabitCom.py new file mode 100644 index 000000000..d0bcaa4e6 --- /dev/null +++ b/pyload/plugin/hoster/ExtabitCom.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.utils import json_loads + +from pyload.plugin.captcha.ReCaptcha import ReCaptcha +from pyload.plugin.internal.SimpleHoster import SimpleHoster, secondsToMidnight + + +class ExtabitCom(SimpleHoster): + __name = "ExtabitCom" + __type = "hoster" + __version = "0.65" + + __pattern = r'http://(?:www\.)?extabit\.com/(file|go|fid)/(?P<ID>\w+)' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Extabit.com hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + NAME_PATTERN = r'<th>File:</th>\s*<td class="col-fileinfo">\s*<div title="(?P<N>.+?)">' + SIZE_PATTERN = r'<th>Size:</th>\s*<td class="col-fileinfo">(?P<S>[^<]+)</td>' + OFFLINE_PATTERN = r'>File not found<' + TEMP_OFFLINE_PATTERN = r'>(File is temporary unavailable|No download mirror)<' + + LINK_FREE_PATTERN = r'[\'"](http://guest\d+\.extabit\.com/\w+/.*?)[\'"]' + + + def handleFree(self, pyfile): + if r">Only premium users can download this file" in self.html: + self.fail(_("Only premium users can download this file")) + + m = re.search(r"Next free download from your ip will be available in <b>(\d+)\s*minutes", self.html) + if m: + self.wait(int(m.group(1)) * 60, True) + elif "The daily downloads limit from your IP is exceeded" in self.html: + self.logWarning(_("You have reached your daily downloads limit for today")) + self.wait(secondsToMidnight(gmt=2), True) + + self.logDebug("URL: " + self.req.http.lastEffectiveURL) + m = re.match(self.__pattern, self.req.http.lastEffectiveURL) + fileID = m.group('ID') if m else self.info['pattern']['ID'] + + m = re.search(r'recaptcha/api/challenge\?k=(\w+)', self.html) + if m: + recaptcha = ReCaptcha(self) + captcha_key = m.group(1) + + for _i in xrange(5): + get_data = {"type": "recaptcha"} + get_data['capture'], get_data['challenge'] = recaptcha.challenge(captcha_key) + res = json_loads(self.load("http://extabit.com/file/%s/" % fileID, get=get_data)) + if "ok" in res: + self.correctCaptcha() + break + else: + self.invalidCaptcha() + else: + self.fail(_("Invalid captcha")) + else: + self.error(_("Captcha")) + + if not "href" in res: + self.error(_("Bad JSON response")) + + self.html = self.load("http://extabit.com/file/%s%s" % (fileID, res['href'])) + + m = re.search(self.LINK_FREE_PATTERN, self.html) + if m is None: + self.error(_("LINK_FREE_PATTERN not found")) + + self.link = m.group(1) diff --git a/pyload/plugin/hoster/FastixRu.py b/pyload/plugin/hoster/FastixRu.py new file mode 100644 index 000000000..eee4da08c --- /dev/null +++ b/pyload/plugin/hoster/FastixRu.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- + +from pyload.utils import json_loads +from pyload.plugin.internal.MultiHoster import MultiHoster + + +class FastixRu(MultiHoster): + __name = "FastixRu" + __type = "hoster" + __version = "0.11" + + __pattern = r'http://(?:www\.)?fastix\.(ru|it)/file/\w{24}' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Fastix multi-hoster plugin""" + __license = "GPLv3" + __authors = [("Massimo Rosamilia", "max@spiritix.eu")] + + + def setup(self): + self.chunkLimit = 3 + + + def handlePremium(self, pyfile): + api_key = self.account.getAccountData(self.user) + api_key = api_key['api'] + + self.html = self.load("http://fastix.ru/api_v2/", + get={'apikey': api_key, 'sub': "getdirectlink", 'link': pyfile.url}) + + data = json_loads(self.html) + + self.logDebug("Json data", data) + + if "error\":true" in self.html: + self.offline() + else: + self.link = data['downloadlink'] diff --git a/pyload/plugin/hoster/FastshareCz.py b/pyload/plugin/hoster/FastshareCz.py new file mode 100644 index 000000000..7e688a941 --- /dev/null +++ b/pyload/plugin/hoster/FastshareCz.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- + +import re +import urlparse + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class FastshareCz(SimpleHoster): + __name = "FastshareCz" + __type = "hoster" + __version = "0.29" + + __pattern = r'http://(?:www\.)?fastshare\.cz/\d+/.+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """FastShare.cz hoster plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + URL_REPLACEMENTS = [("#.*", "")] + + COOKIES = [("fastshare.cz", "lang", "en")] + + NAME_PATTERN = r'<h3 class="section_title">(?P<N>.+?)<' + SIZE_PATTERN = r'>Size\s*:</strong> (?P<S>[\d.,]+) (?P<U>[\w^_]+)' + OFFLINE_PATTERN = r'>(The file has been deleted|Requested page not found)' + + LINK_FREE_PATTERN = r'>Enter the code\s*:</em>\s*<span><img src="(.+?)"' + LINK_PREMIUM_PATTERN = r'(http://\w+\.fastshare\.cz/download\.php\?id=\d+&)' + + SLOT_ERROR = "> 100% of FREE slots are full" + CREDIT_ERROR = " credit for " + + + def checkErrors(self): + if self.SLOT_ERROR in self.html: + errmsg = self.info['error'] = _("No free slots") + self.retry(12, 60, errmsg) + + if self.CREDIT_ERROR in self.html: + errmsg = self.info['error'] = _("Not enough traffic left") + self.logWarning(errmsg) + self.resetAccount() + + self.info.pop('error', None) + + + def handleFree(self, pyfile): + m = re.search(self.FREE_URL_PATTERN, self.html) + if m: + action, captcha_src = m.groups() + else: + self.error(_("FREE_URL_PATTERN not found")) + + baseurl = "http://www.fastshare.cz" + captcha = self.decryptCaptcha(urlparse.urljoin(baseurl, captcha_src)) + self.download(urlparse.urljoin(baseurl, action), post={'code': captcha, 'btn.x': 77, 'btn.y': 18}) + + + def checkFile(self, rules={}): + check = self.checkDownload({ + 'paralell-dl' : re.compile(r"<title>FastShare.cz</title>|<script>alert\('Pres FREE muzete stahovat jen jeden soubor najednou.'\)"), + 'wrong captcha': re.compile(r'Download for FREE'), + 'credit' : re.compile(self.CREDIT_ERROR) + }) + + if check == "paralell-dl": + self.retry(6, 10 * 60, _("Paralell download")) + + elif check == "wrong captcha": + self.retry(max_tries=5, reason=_("Wrong captcha")) + + elif check == "credit": + self.resetAccount() + + return super(FastshareCz, self).checkFile(rules) diff --git a/pyload/plugin/hoster/FileApeCom.py b/pyload/plugin/hoster/FileApeCom.py new file mode 100644 index 000000000..87d995817 --- /dev/null +++ b/pyload/plugin/hoster/FileApeCom.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class FileApeCom(DeadHoster): + __name = "FileApeCom" + __type = "hoster" + __version = "0.12" + + __pattern = r'http://(?:www\.)?fileape\.com/(index\.php\?act=download\&id=|dl/)\w+' + __config = [] + + __description = """FileApe.com hoster plugin""" + __license = "GPLv3" + __authors = [("espes", "")] diff --git a/pyload/plugin/hoster/FileSharkPl.py b/pyload/plugin/hoster/FileSharkPl.py new file mode 100644 index 000000000..5d37a55c6 --- /dev/null +++ b/pyload/plugin/hoster/FileSharkPl.py @@ -0,0 +1,114 @@ +# -*- coding: utf-8 -*- + +import re +import urlparse + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class FileSharkPl(SimpleHoster): + __name = "FileSharkPl" + __type = "hoster" + __version = "0.10" + + __pattern = r'http://(?:www\.)?fileshark\.pl/pobierz/\d+/\w+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """FileShark.pl hoster plugin""" + __license = "GPLv3" + __authors = [("prOq", ""), + ("Walter Purcaro", "vuolter@gmail.com")] + + + NAME_PATTERN = r'<h2 class="name-file">(?P<N>.+)</h2>' + SIZE_PATTERN = r'<p class="size-file">(.*?)<strong>(?P<S>\d+\.?\d*)\s(?P<U>\w+)</strong></p>' + OFFLINE_PATTERN = r'(P|p)lik zosta. (usuni.ty|przeniesiony)' + + LINK_FREE_PATTERN = r'<a rel="nofollow" href="(.*?)" class="btn-upload-free">' + LINK_PREMIUM_PATTERN = r'<a rel="nofollow" href="(.*?)" class="btn-upload-premium">' + + WAIT_PATTERN = r'var timeToDownload = (\d+);' + ERROR_PATTERN = r'<p class="lead text-center alert alert-warning">(.*?)</p>' + IP_ERROR_PATTERN = r'Strona jest dost.pna wy..cznie dla u.ytkownik.w znajduj.cych si. na terenie Polski' + SLOT_ERROR_PATTERN = r'Osi.gni.to maksymaln. liczb. .ci.ganych jednocze.nie plik.w\.' + + CAPTCHA_PATTERN = r'<img src="data:image/jpeg;base64,(.*?)" title="captcha"' + TOKEN_PATTERN = r'name="form\[_token\]" value="(.*?)" />' + + + def setup(self): + self.resumeDownload = True + + if self.premium: + self.multiDL = True + self.limitDL = 20 + else: + self.multiDL = False + + + def checkErrors(self): + # check if file is now available for download (-> file name can be found in html body) + m = re.search(self.WAIT_PATTERN, self.html) + if m: + errmsg = self.info['error'] = _("Another download already run") + self.retry(15, int(m.group(1)), errmsg) + + m = re.search(self.ERROR_PATTERN, self.html) + if m: + alert = m.group(1) + + if re.match(self.IP_ERROR_PATTERN, alert): + self.fail(_("Only connections from Polish IP are allowed")) + + elif re.match(self.SLOT_ERROR_PATTERN, alert): + errmsg = self.info['error'] = _("No free download slots available") + self.logWarning(errmsg) + self.retry(10, 30 * 60, _("Still no free download slots available")) + + else: + self.info['error'] = alert + self.retry(10, 10 * 60, _("Try again later")) + + self.info.pop('error', None) + + + def handleFree(self, pyfile): + m = re.search(self.LINK_FREE_PATTERN, self.html) + if m is None: + self.error(_("Download url not found")) + + link = urlparse.urljoin("http://fileshark.pl", m.group(1)) + + self.html = self.load(link) + + m = re.search(self.WAIT_PATTERN, self.html) + if m: + seconds = int(m.group(1)) + self.logDebug("Wait %s seconds" % seconds) + self.wait(seconds) + + action, inputs = self.parseHtmlForm('action=""') + + m = re.search(self.TOKEN_PATTERN, self.html) + if m is None: + self.retry(reason=_("Captcha form not found")) + + inputs['form[_token]'] = m.group(1) + + m = re.search(self.CAPTCHA_PATTERN, self.html) + if m is None: + self.retry(reason=_("Captcha image not found")) + + tmp_load = self.load + self.load = self._decode64 #: work-around: injects decode64 inside decryptCaptcha + + inputs['form[captcha]'] = self.decryptCaptcha(m.group(1), imgtype='jpeg') + inputs['form[start]'] = "" + + self.load = tmp_load + + self.download(link, post=inputs, disposition=True) + + + def _decode64(self, data, *args, **kwargs): + return data.decode('base64') diff --git a/pyload/plugin/hoster/FileStoreTo.py b/pyload/plugin/hoster/FileStoreTo.py new file mode 100644 index 000000000..6315f208d --- /dev/null +++ b/pyload/plugin/hoster/FileStoreTo.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class FileStoreTo(SimpleHoster): + __name = "FileStoreTo" + __type = "hoster" + __version = "0.05" + + __pattern = r'http://(?:www\.)?filestore\.to/\?d=(?P<ID>\w+)' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """FileStore.to hoster plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com"), + ("stickell", "l.stickell@yahoo.it")] + + + INFO_PATTERN = r'File: <span.*?>(?P<N>.+?)<.*>Size: (?P<S>[\d.,]+) (?P<U>[\w^_]+)' + OFFLINE_PATTERN = r'>Download-Datei wurde nicht gefunden<' + TEMP_OFFLINE_PATTERN = r'>Der Download ist nicht bereit !<' + + + def setup(self): + self.resumeDownload = True + self.multiDL = True + + + def handleFree(self, pyfile): + self.wait(10) + self.link = self.load("http://filestore.to/ajax/download.php", + get={'D': re.search(r'"D=(\w+)', self.html).group(1)}) diff --git a/pyload/plugin/hoster/FilebeerInfo.py b/pyload/plugin/hoster/FilebeerInfo.py new file mode 100644 index 000000000..a929a0787 --- /dev/null +++ b/pyload/plugin/hoster/FilebeerInfo.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class FilebeerInfo(DeadHoster): + __name = "FilebeerInfo" + __type = "hoster" + __version = "0.03" + + __pattern = r'http://(?:www\.)?filebeer\.info/(?!\d*~f)(?P<ID>\w+)' + __config = [] + + __description = """Filebeer.info plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] diff --git a/pyload/plugin/hoster/FilecloudIo.py b/pyload/plugin/hoster/FilecloudIo.py new file mode 100644 index 000000000..4f57d3a74 --- /dev/null +++ b/pyload/plugin/hoster/FilecloudIo.py @@ -0,0 +1,123 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.utils import json_loads +from pyload.plugin.captcha.ReCaptcha import ReCaptcha +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class FilecloudIo(SimpleHoster): + __name = "FilecloudIo" + __type = "hoster" + __version = "0.08" + + __pattern = r'http://(?:www\.)?(?:filecloud\.io|ifile\.it|mihd\.net)/(?P<ID>\w+)' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Filecloud.io hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz"), + ("stickell", "l.stickell@yahoo.it")] + + + LOGIN_ACCOUNT = True + + NAME_PATTERN = r'id="aliasSpan">(?P<N>.*?) <' + SIZE_PATTERN = r'{var __ab1 = (?P<S>\d+);}' + OFFLINE_PATTERN = r'l10n\.(FILES__DOESNT_EXIST|REMOVED)' + TEMP_OFFLINE_PATTERN = r'l10n\.FILES__WARNING' + + UKEY_PATTERN = r'\'ukey\'\s*:\'(\w+)' + AB1_PATTERN = r'if\( __ab1 == \'(\w+)\' \)' + + ERROR_MSG_PATTERN = r'var __error_msg\s*=\s*l10n\.(.*?);' + + RECAPTCHA_PATTERN = r'var __recaptcha_public\s*=\s*\'(.+?)\';' + + LINK_FREE_PATTERN = r'"(http://s\d+\.filecloud\.io/%s/\d+/.*?)"' + + + def setup(self): + self.resumeDownload = True + self.multiDL = True + self.chunkLimit = 1 + + + def handleFree(self, pyfile): + data = {"ukey": self.info['pattern']['ID']} + + m = re.search(self.AB1_PATTERN, self.html) + if m is None: + self.error(_("__AB1")) + data['__ab1'] = m.group(1) + + recaptcha = ReCaptcha(self) + + m = re.search(self.RECAPTCHA_PATTERN, self.html) + captcha_key = m.group(1) if m else recaptcha.detect_key() + + if captcha_key is None: + self.error(_("ReCaptcha key not found")) + + response, challenge = recaptcha.challenge(captcha_key) + self.account.form_data = {"recaptcha_challenge_field": challenge, + "recaptcha_response_field" : response} + self.account.relogin(self.user) + self.retry(2) + + json_url = "http://filecloud.io/download-request.json" + res = self.load(json_url, post=data) + self.logDebug(res) + res = json_loads(res) + + if "error" in res and res['error']: + self.fail(res) + + self.logDebug(res) + if res['captcha']: + data['ctype'] = "recaptcha" + + for _i in xrange(5): + data['recaptcha_response'], data['recaptcha_challenge'] = recaptcha.challenge(captcha_key) + + json_url = "http://filecloud.io/download-request.json" + res = self.load(json_url, post=data) + self.logDebug(res) + res = json_loads(res) + + if "retry" in res and res['retry']: + self.invalidCaptcha() + else: + self.correctCaptcha() + break + else: + self.fail(_("Incorrect captcha")) + + if res['dl']: + self.html = self.load('http://filecloud.io/download.html') + + m = re.search(self.LINK_FREE_PATTERN % self.info['pattern']['ID'], self.html) + if m is None: + self.error(_("LINK_FREE_PATTERN not found")) + + if "size" in self.info and self.info['size']: + self.check_data = {"size": int(self.info['size'])} + + self.link = m.group(1) + else: + self.fail(_("Unexpected server response")) + + + def handlePremium(self, pyfile): + akey = self.account.getAccountData(self.user)['akey'] + ukey = self.info['pattern']['ID'] + self.logDebug("Akey: %s | Ukey: %s" % (akey, ukey)) + rep = self.load("http://api.filecloud.io/api-fetch_download_url.api", + post={"akey": akey, "ukey": ukey}) + self.logDebug("FetchDownloadUrl: " + rep) + rep = json_loads(rep) + if rep['status'] == 'ok': + self.link = rep['download_url'] + else: + self.fail(rep['message']) diff --git a/pyload/plugin/hoster/FilefactoryCom.py b/pyload/plugin/hoster/FilefactoryCom.py new file mode 100644 index 000000000..782b6ba48 --- /dev/null +++ b/pyload/plugin/hoster/FilefactoryCom.py @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- + +import re +import urlparse + +from pyload.network.RequestFactory import getURL +from pyload.plugin.internal.SimpleHoster import SimpleHoster, parseFileInfo + + +def getInfo(urls): + for url in urls: + h = getURL(url, just_header=True) + m = re.search(r'Location: (.+)\r\n', h) + if m and not re.match(m.group(1), FilefactoryCom.__pattern): #: It's a direct link! Skipping + yield (url, 0, 3, url) + else: #: It's a standard html page + yield parseFileInfo(FilefactoryCom, url, getURL(url)) + + +class FilefactoryCom(SimpleHoster): + __name = "FilefactoryCom" + __type = "hoster" + __version = "0.54" + + __pattern = r'https?://(?:www\.)?filefactory\.com/(file|trafficshare/\w+)/\w+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Filefactory.com hoster plugin""" + __license = "GPLv3" + __authors = [("stickell", "l.stickell@yahoo.it"), + ("Walter Purcaro", "vuolter@gmail.com")] + + + INFO_PATTERN = r'<div id="file_name"[^>]*>\s*<h2>(?P<N>[^<]+)</h2>\s*<div id="file_info">\s*(?P<S>[\d.,]+) (?P<U>[\w^_]+) uploaded' + OFFLINE_PATTERN = r'<h2>File Removed</h2>|This file is no longer available' + + LINK_FREE_PATTERN = LINK_PREMIUM_PATTERN = r'"([^"]+filefactory\.com/get.+?)"' + + WAIT_PATTERN = r'<div id="countdown_clock" data-delay="(\d+)">' + PREMIUM_ONLY_PATTERN = r'>Premium Account Required' + + COOKIES = [("filefactory.com", "locale", "en_US.utf8")] + + + def handleFree(self, pyfile): + if "Currently only Premium Members can download files larger than" in self.html: + self.fail(_("File too large for free download")) + elif "All free download slots on this server are currently in use" in self.html: + self.retry(50, 15 * 60, _("All free slots are busy")) + + m = re.search(self.LINK_FREE_PATTERN, self.html) + if m is None: + self.error(_("Free download link not found")) + + self.link = m.group(1) + + m = re.search(self.WAIT_PATTERN, self.html) + if m: + self.wait(m.group(1)) + + + def checkFile(self, rules={}): + check = self.checkDownload({'multiple': "You are currently downloading too many files at once.", + 'error' : '<div id="errorMessage">'}) + + if check == "multiple": + self.logDebug("Parallel downloads detected; waiting 15 minutes") + self.retry(wait_time=15 * 60, reason=_("Parallel downloads")) + + elif check == "error": + self.error(_("Unknown error")) + + return super(FilefactoryCom, self).checkFile(rules) + + + def handlePremium(self, pyfile): + self.link = self.directLink(self.load(pyfile.url, just_header=True)) + + if not self.link: + html = self.load(pyfile.url) + m = re.search(self.LINK_PREMIUM_PATTERN, html) + if m: + self.link = m.group(1) + else: + self.error(_("Premium download link not found")) diff --git a/pyload/plugin/hoster/FilejungleCom.py b/pyload/plugin/hoster/FilejungleCom.py new file mode 100644 index 000000000..2fe238de8 --- /dev/null +++ b/pyload/plugin/hoster/FilejungleCom.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.hoster.FileserveCom import FileserveCom, checkFile +from pyload.plugin.Plugin import chunks + + +class FilejungleCom(FileserveCom): + __name = "FilejungleCom" + __type = "hoster" + __version = "0.51" + + __pattern = r'http://(?:www\.)?filejungle\.com/f/(?P<ID>[^/]+)' + + __description = """Filejungle.com hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + URLS = ["http://www.filejungle.com/f/", "http://www.filejungle.com/check_links.php", + "http://www.filejungle.com/checkReCaptcha.php"] + LINKCHECK_TR = r'<li>\s*(<div class="col1">.*?)</li>' + LINKCHECK_TD = r'<div class="(?:col )?col\d">(?:<.*?>| )*([^<]*)' + + LONG_WAIT_PATTERN = r'<h1>Please wait for (\d+) (\w+)\s*to download the next file\.</h1>' + + +def getInfo(urls): + for chunk in chunks(urls, 100): + yield checkFile(FilejungleCom, chunk) diff --git a/pyload/plugin/hoster/FileomCom.py b/pyload/plugin/hoster/FileomCom.py new file mode 100644 index 000000000..b01b34db0 --- /dev/null +++ b/pyload/plugin/hoster/FileomCom.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# +# Test links: +# http://fileom.com/gycaytyzdw3g/random.bin.html + +from pyload.plugin.internal.XFSHoster import XFSHoster + + +class FileomCom(XFSHoster): + __name = "FileomCom" + __type = "hoster" + __version = "0.05" + + __pattern = r'https?://(?:www\.)?fileom\.com/\w{12}' + + __description = """Fileom.com hoster plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + NAME_PATTERN = r'Filename: <span>(?P<N>.+?)<' + SIZE_PATTERN = r'File Size: <span class="size">(?P<S>[\d.,]+) (?P<U>[\w^_]+)' + + LINK_PATTERN = r'var url2 = \'(.+?)\';' + + + def setup(self): + self.multiDL = True + self.chunkLimit = 1 + self.resumeDownload = self.premium diff --git a/pyload/plugin/hoster/FilepostCom.py b/pyload/plugin/hoster/FilepostCom.py new file mode 100644 index 000000000..1d2843a2e --- /dev/null +++ b/pyload/plugin/hoster/FilepostCom.py @@ -0,0 +1,121 @@ +# -*- coding: utf-8 -*- + +import re +import time + +from pyload.utils import json_loads +from pyload.plugin.captcha.ReCaptcha import ReCaptcha +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class FilepostCom(SimpleHoster): + __name = "FilepostCom" + __type = "hoster" + __version = "0.33" + + __pattern = r'https?://(?:www\.)?(?:filepost\.com/files|fp\.io)/(?P<ID>[^/]+)' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Filepost.com hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + INFO_PATTERN = r'<input type="text" id="url" value=\'<a href.*?>(?P<N>[^>]+?) - (?P<S>[\d.,]+) (?P<U>[\w^_]+)</a>\' class="inp_text"/>' + OFFLINE_PATTERN = r'class="error_msg_title"> Invalid or Deleted File. </div>|<div class="file_info file_info_deleted">' + + PREMIUM_ONLY_PATTERN = r'members only. Please upgrade to premium|a premium membership is required to download this file' + RECAPTCHA_PATTERN = r'Captcha.init\({\s*key:\s*\'(.+?)\'' + FLP_TOKEN_PATTERN = r'set_store_options\({token: \'(.+?)\'' + + + def handleFree(self, pyfile): + m = re.search(self.FLP_TOKEN_PATTERN, self.html) + if m is None: + self.error(_("Token")) + flp_token = m.group(1) + + m = re.search(self.RECAPTCHA_PATTERN, self.html) + if m is None: + self.error(_("Captcha key")) + captcha_key = m.group(1) + + # Get wait time + get_dict = {'SID': self.req.cj.getCookie('SID'), 'JsHttpRequest': str(int(time.time() * 10000)) + '-xml'} + post_dict = {'action': 'set_download', 'token': flp_token, 'code': self.info['pattern']['ID']} + wait_time = int(self.getJsonResponse(get_dict, post_dict, 'wait_time')) + + if wait_time > 0: + self.wait(wait_time) + + post_dict = {"token": flp_token, "code": self.info['pattern']['ID'], "file_pass": ''} + + if 'var is_pass_exists = true;' in self.html: + # Solve password + password = self.getPassword() + + if password: + self.logInfo(_("Password protected link, trying ") + file_pass) + + get_dict['JsHttpRequest'] = str(int(time.time() * 10000)) + '-xml' + post_dict['file_pass'] = file_pass + + self.link = self.getJsonResponse(get_dict, post_dict, 'link') + + if not self.link: + self.fail(_("Incorrect password")) + else: + self.fail(_("No password found")) + + else: + # Solve recaptcha + recaptcha = ReCaptcha(self) + + for i in xrange(5): + get_dict['JsHttpRequest'] = str(int(time.time() * 10000)) + '-xml' + if i: + post_dict['recaptcha_response_field'], post_dict['recaptcha_challenge_field'] = recaptcha.challenge( + captcha_key) + self.logDebug(u"RECAPTCHA: %s : %s : %s" % ( + captcha_key, post_dict['recaptcha_challenge_field'], post_dict['recaptcha_response_field'])) + + self.link = self.getJsonResponse(get_dict, post_dict, 'link') + + else: + self.fail(_("Invalid captcha")) + + + def getJsonResponse(self, get_dict, post_dict, field): + res = json_loads(self.load('https://filepost.com/files/get/', get=get_dict, post=post_dict)) + + self.logDebug(res) + + if not 'js' in res: + self.error(_("JSON %s 1") % field) + + # i changed js_answer to res['js'] since js_answer is nowhere set. + # i don't know the JSON-HTTP specs in detail, but the previous author + # accessed res['js']['error'] as well as js_answer['error']. + # see the two lines commented out with "# ~?". + if 'error' in res['js']: + + if res['js']['error'] == 'download_delay': + self.retry(wait_time=res['js']['params']['next_download']) + # ~? self.retry(wait_time=js_answer['params']['next_download']) + + elif 'Wrong file password' in res['js']['error'] \ + or 'You entered a wrong CAPTCHA code' in res['js']['error'] \ + or 'CAPTCHA Code nicht korrekt' in res['js']['error']: + return None + + elif 'CAPTCHA' in res['js']['error']: + self.logDebug("Error response is unknown, but mentions CAPTCHA") + return None + + else: + self.fail(res['js']['error']) + + if not 'answer' in res['js'] or not field in res['js']['answer']: + self.error(_("JSON %s 2") % field) + + return res['js']['answer'][field] diff --git a/pyload/plugin/hoster/FilepupNet.py b/pyload/plugin/hoster/FilepupNet.py new file mode 100644 index 000000000..91d640e00 --- /dev/null +++ b/pyload/plugin/hoster/FilepupNet.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +# +# Test links: +# http://www.filepup.net/files/k5w4ZVoF1410184283.html +# http://www.filepup.net/files/R4GBq9XH1410186553.html + +import re + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class FilepupNet(SimpleHoster): + __name = "FilepupNet" + __type = "hoster" + __version = "0.03" + + __pattern = r'http://(?:www\.)?filepup\.net/files/\w+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Filepup.net hoster plugin""" + __license = "GPLv3" + __authors = [("zapp-brannigan", "fuerst.reinje@web.de"), + ("Walter Purcaro", "vuolter@gmail.com")] + + + NAME_PATTERN = r'>(?P<N>.+?)</h1>' + SIZE_PATTERN = r'class="fa fa-archive"></i> \((?P<S>[\d.,]+) (?P<U>[\w^_]+)' + + OFFLINE_PATTERN = r'>This file has been deleted' + + LINK_FREE_PATTERN = r'(http://www\.filepup\.net/get/.+?)\'' + + + def setup(self): + self.multiDL = False + self.chunkLimit = 1 + + + def handleFree(self, pyfile): + m = re.search(self.LINK_FREE_PATTERN, self.html) + if m is None: + self.error(_("Download link not found")) + + dl_link = m.group(1) + self.download(dl_link, post={'task': "download"}) diff --git a/pyload/plugin/hoster/FilerNet.py b/pyload/plugin/hoster/FilerNet.py new file mode 100644 index 000000000..106dd2ade --- /dev/null +++ b/pyload/plugin/hoster/FilerNet.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +# +# Test links: +# http://filer.net/get/ivgf5ztw53et3ogd +# http://filer.net/get/hgo14gzcng3scbvv + +import re +import urlparse + +from pyload.plugin.captcha.ReCaptcha import ReCaptcha +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class FilerNet(SimpleHoster): + __name = "FilerNet" + __type = "hoster" + __version = "0.19" + + __pattern = r'https?://(?:www\.)?filer\.net/get/\w+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Filer.net hoster plugin""" + __license = "GPLv3" + __authors = [("stickell", "l.stickell@yahoo.it"), + ("Walter Purcaro", "vuolter@gmail.com")] + + INFO_PATTERN = r'<h1 class="page-header">Free Download (?P<N>\S+) <small>(?P<S>[\w.]+) (?P<U>[\w^_]+)</small></h1>' + OFFLINE_PATTERN = r'Nicht gefunden' + + WAIT_PATTERN = r'musst du <span id="time">(\d+)' + + LINK_FREE_PATTERN = LINK_PREMIUM_PATTERN = r'href="([^"]+)">Get download</a>' + + + def handleFree(self, pyfile): + inputs = self.parseHtmlForm(input_names={'token': re.compile(r'.+')})[1] + if 'token' not in inputs: + self.error(_("Unable to detect token")) + + self.html = self.load(pyfile.url, post={'token': inputs['token']}, decode=True) + + inputs = self.parseHtmlForm(input_names={'hash': re.compile(r'.+')})[1] + if 'hash' not in inputs: + self.error(_("Unable to detect hash")) + + recaptcha = ReCaptcha(self) + response, challenge = recaptcha.challenge() + + self.load(pyfile.url, post={'recaptcha_challenge_field': challenge, + 'recaptcha_response_field': response, + 'hash': inputs['hash']}, follow_location=False) + + if 'location' in self.req.http.header.lower(): + self.link = re.search(r'location: (\S+)', self.req.http.header, re.I).group(1) + self.correctCaptcha() + else: + self.invalidCaptcha() diff --git a/pyload/plugin/hoster/FilerioCom.py b/pyload/plugin/hoster/FilerioCom.py new file mode 100644 index 000000000..c55fd2c0b --- /dev/null +++ b/pyload/plugin/hoster/FilerioCom.py @@ -0,0 +1,20 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSHoster import XFSHoster + + +class FilerioCom(XFSHoster): + __name = "FilerioCom" + __type = "hoster" + __version = "0.07" + + __pattern = r'http://(?:www\.)?(filerio\.(in|com)|filekeen\.com)/\w{12}' + + __description = """FileRio.in hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + URL_REPLACEMENTS = [(r'filekeen\.com', "filerio.in")] + + OFFLINE_PATTERN = r'>"File Not Found|File has been removed' diff --git a/pyload/plugin/hoster/FilesMailRu.py b/pyload/plugin/hoster/FilesMailRu.py new file mode 100644 index 000000000..0d7f47536 --- /dev/null +++ b/pyload/plugin/hoster/FilesMailRu.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.network.RequestFactory import getURL +from pyload.plugin.Hoster import Hoster +from pyload.plugin.Plugin import chunks + + +def getInfo(urls): + result = [] + for chunk in chunks(urls, 10): + for url in chunk: + html = getURL(url) + if r'<div class="errorMessage mb10">' in html: + result.append((url, 0, 1, url)) + elif r'Page cannot be displayed' in html: + result.append((url, 0, 1, url)) + else: + try: + url_pattern = '<a href="(.+?)" onclick="return Act\(this\, \'dlink\'\, event\)">(.+?)</a>' + file_name = re.search(url_pattern, html).group(0).split(', event)">')[1].split('</a>')[0] + result.append((file_name, 0, 2, url)) + except Exception: + pass + + # status 1=OFFLINE, 2=OK, 3=UNKNOWN + # result.append((#name,#size,#status,#url)) + yield result + + +class FilesMailRu(Hoster): + __name = "FilesMailRu" + __type = "hoster" + __version = "0.32" + + __pattern = r'http://(?:www\.)?files\.mail\.ru/.+' + + __description = """Files.mail.ru hoster plugin""" + __license = "GPLv3" + __authors = [("oZiRiz", "ich@oziriz.de")] + + + def setup(self): + self.multiDL = bool(self.account) + + + def process(self, pyfile): + self.html = self.load(pyfile.url) + self.url_pattern = '<a href="(.+?)" onclick="return Act\(this\, \'dlink\'\, event\)">(.+?)</a>' + + # marks the file as "offline" when the pattern was found on the html-page''' + if r'<div class="errorMessage mb10">' in self.html: + self.offline() + + elif r'Page cannot be displayed' in self.html: + self.offline() + + # the filename that will be showed in the list (e.g. test.part1.rar)''' + pyfile.name = self.getFileName() + + # prepare and download''' + if not self.account: + self.prepare() + self.download(self.getFileUrl()) + self.myPostProcess() + else: + self.download(self.getFileUrl()) + self.myPostProcess() + + + def prepare(self): + """You have to wait some seconds. Otherwise you will get a 40Byte HTML Page instead of the file you expected""" + self.setWait(10) + self.wait() + return True + + + def getFileUrl(self): + """gives you the URL to the file. Extracted from the Files.mail.ru HTML-page stored in self.html""" + return re.search(self.url_pattern, self.html).group(0).split('<a href="')[1].split('" onclick="return Act')[0] + + + def getFileName(self): + """gives you the Name for each file. Also extracted from the HTML-Page""" + return re.search(self.url_pattern, self.html).group(0).split(', event)">')[1].split('</a>')[0] + + + def myPostProcess(self): + # searches the file for HTMl-Code. Sometimes the Redirect + # doesn't work (maybe a curl Problem) and you get only a small + # HTML file and the Download is marked as "finished" + # then the download will be restarted. It's only bad for these + # who want download a HTML-File (it's one in a million ;-) ) + # + # The maximum UploadSize allowed on files.mail.ru at the moment is 100MB + # so i set it to check every download because sometimes there are downloads + # that contain the HTML-Text and 60MB ZEROs after that in a xyzfile.part1.rar file + # (Loading 100MB in to ram is not an option) + check = self.checkDownload({"html": "<meta name="}, read_size=50000) + if check == "html": + self.logInfo(_( + "There was HTML Code in the Downloaded File (%s)...redirect error? The Download will be restarted." % + self.pyfile.name)) + self.retry() diff --git a/pyload/plugin/hoster/FileserveCom.py b/pyload/plugin/hoster/FileserveCom.py new file mode 100644 index 000000000..1d4179e5c --- /dev/null +++ b/pyload/plugin/hoster/FileserveCom.py @@ -0,0 +1,216 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.utils import json_loads +from pyload.network.RequestFactory import getURL +from pyload.plugin.Hoster import Hoster +from pyload.plugin.Plugin import chunks +from pyload.plugin.captcha.ReCaptcha import ReCaptcha +from pyload.plugin.internal.SimpleHoster import secondsToMidnight +from pyload.utils import parseFileSize + + +def checkFile(plugin, urls): + html = getURL(plugin.URLS[1], post={"urls": "\n".join(urls)}, decode=True) + + file_info = [] + for li in re.finditer(plugin.LINKCHECK_TR, html, re.S): + try: + cols = re.findall(plugin.LINKCHECK_TD, li.group(1)) + if cols: + file_info.append(( + cols[1] if cols[1] != '--' else cols[0], + parseFileSize(cols[2]) if cols[2] != '--' else 0, + 2 if cols[3].startswith('Available') else 1, + cols[0])) + except Exception, e: + continue + + return file_info + + +class FileserveCom(Hoster): + __name = "FileserveCom" + __type = "hoster" + __version = "0.54" + + __pattern = r'http://(?:www\.)?fileserve\.com/file/(?P<ID>[^/]+)' + + __description = """Fileserve.com hoster plugin""" + __license = "GPLv3" + __authors = [("jeix", "jeix@hasnomail.de"), + ("mkaay", "mkaay@mkaay.de"), + ("Paul King", ""), + ("zoidberg", "zoidberg@mujmail.cz")] + + + URLS = ["http://www.fileserve.com/file/", "http://www.fileserve.com/link-checker.php", + "http://www.fileserve.com/checkReCaptcha.php"] + LINKCHECK_TR = r'<tr>\s*(<td>http://www\.fileserve\.com/file/.*?)</tr>' + LINKCHECK_TD = r'<td>(?:<.*?>| )*([^<]*)' + + CAPTCHA_KEY_PATTERN = r'var reCAPTCHA_publickey=\'(.+?)\'' + LONG_WAIT_PATTERN = r'<li class="title">You need to wait (\d+) (\w+) to start another download\.</li>' + LINK_EXPIRED_PATTERN = r'Your download link has expired' + DAILY_LIMIT_PATTERN = r'Your daily download limit has been reached' + NOT_LOGGED_IN_PATTERN = r'<form (name="loginDialogBoxForm"|id="login_form")|<li><a href="/login\.php">Login</a></li>' + + + def setup(self): + self.resumeDownload = self.multiDL = self.premium + self.file_id = re.match(self.__pattern, self.pyfile.url).group('ID') + self.url = "%s%s" % (self.URLS[0], self.file_id) + + self.logDebug("File ID: %s URL: %s" % (self.file_id, self.url)) + + + def process(self, pyfile): + pyfile.name, pyfile.size, status, self.url = checkFile(self, [self.url])[0] + if status != 2: + self.offline() + self.logDebug("File Name: %s Size: %d" % (pyfile.name, pyfile.size)) + + if self.premium: + self.handlePremium(pyfile) + else: + self.handleFree(pyfile) + + + def handleFree(self, pyfile): + self.html = self.load(self.url) + action = self.load(self.url, post={"checkDownload": "check"}, decode=True) + action = json_loads(action) + self.logDebug(action) + + if "fail" in action: + if action['fail'] == "timeLimit": + self.html = self.load(self.url, post={"checkDownload": "showError", "errorType": "timeLimit"}, + decode=True) + + self.doLongWait(re.search(self.LONG_WAIT_PATTERN, self.html)) + + elif action['fail'] == "parallelDownload": + self.logWarning(_("Parallel download error, now waiting 60s")) + self.retry(wait_time=60, reason=_("parallelDownload")) + + else: + self.fail(_("Download check returned: %s") % action['fail']) + + elif "success" in action: + if action['success'] == "showCaptcha": + self.doCaptcha() + self.doTimmer() + elif action['success'] == "showTimmer": + self.doTimmer() + + else: + self.error(_("Unknown server response")) + + # show download link + res = self.load(self.url, post={"downloadLink": "show"}, decode=True) + self.logDebug("Show downloadLink response: %s" % res) + if "fail" in res: + self.error(_("Couldn't retrieve download url")) + + # this may either download our file or forward us to an error page + self.download(self.url, post={"download": "normal"}) + self.logDebug(self.req.http.lastEffectiveURL) + + check = self.checkDownload({"expired": self.LINK_EXPIRED_PATTERN, + "wait" : re.compile(self.LONG_WAIT_PATTERN), + "limit" : self.DAILY_LIMIT_PATTERN}) + + if check == "expired": + self.logDebug("Download link was expired") + self.retry() + + elif check == "wait": + self.doLongWait(self.lastCheck) + + elif check == "limit": + self.logWarning(_("Download limited reached for today")) + self.setWait(secondsToMidnight(gmt=2), True) + self.wait() + self.retry() + + self.thread.m.reconnecting.wait(3) #: Ease issue with later downloads appearing to be in parallel + + + def doTimmer(self): + res = self.load(self.url, post={"downloadLink": "wait"}, decode=True) + self.logDebug("Wait response: %s" % res[:80]) + + if "fail" in res: + self.fail(_("Failed getting wait time")) + + if self.getClassName() == "FilejungleCom": + m = re.search(r'"waitTime":(\d+)', res) + if m is None: + self.fail(_("Cannot get wait time")) + wait_time = int(m.group(1)) + else: + wait_time = int(res) + 3 + + self.setWait(wait_time) + self.wait() + + + def doCaptcha(self): + captcha_key = re.search(self.CAPTCHA_KEY_PATTERN, self.html).group(1) + recaptcha = ReCaptcha(self) + + for _i in xrange(5): + response, challenge = recaptcha.challenge(captcha_key) + res = json_loads(self.load(self.URLS[2], + post={'recaptcha_challenge_field' : challenge, + 'recaptcha_response_field' : response, + 'recaptcha_shortencode_field': self.file_id})) + if not res['success']: + self.invalidCaptcha() + else: + self.correctCaptcha() + break + else: + self.fail(_("Invalid captcha")) + + + def doLongWait(self, m): + wait_time = (int(m.group(1)) * {'seconds': 1, 'minutes': 60, 'hours': 3600}[m.group(2)]) if m else 12 * 60 + self.setWait(wait_time, True) + self.wait() + self.retry() + + + def handlePremium(self, pyfile): + premium_url = None + if self.getClassName() == "FileserveCom": + # try api download + res = self.load("http://app.fileserve.com/api/download/premium/", + post={"username": self.user, + "password": self.account.getAccountData(self.user)['password'], + "shorten": self.file_id}, + decode=True) + if res: + res = json_loads(res) + if res['error_code'] == "302": + premium_url = res['next'] + elif res['error_code'] in ["305", "500"]: + self.tempOffline() + elif res['error_code'] in ["403", "605"]: + self.resetAccount() + elif res['error_code'] in ["606", "607", "608"]: + self.offline() + else: + self.logError(res['error_code'], res['error_message']) + + self.download(premium_url or pyfile.url) + + if not premium_url and self.checkDownload({"login": re.compile(self.NOT_LOGGED_IN_PATTERN)}): + self.account.relogin(self.user) + self.retry(reason=_("Not logged in")) + + +def getInfo(urls): + for chunk in chunks(urls, 100): + yield checkFile(FileserveCom, chunk) diff --git a/pyload/plugin/hoster/FileshareInUa.py b/pyload/plugin/hoster/FileshareInUa.py new file mode 100644 index 000000000..8b09a086a --- /dev/null +++ b/pyload/plugin/hoster/FileshareInUa.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class FileshareInUa(DeadHoster): + __name = "FileshareInUa" + __type = "hoster" + __version = "0.02" + + __pattern = r'https?://(?:www\.)?fileshare\.in\.ua/\w{7}' + __config = [] + + __description = """Fileshare.in.ua hoster plugin""" + __license = "GPLv3" + __authors = [("fwannmacher", "felipe@warhammerproject.com")] diff --git a/pyload/plugin/hoster/FilesonicCom.py b/pyload/plugin/hoster/FilesonicCom.py new file mode 100644 index 000000000..f02f7bff6 --- /dev/null +++ b/pyload/plugin/hoster/FilesonicCom.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class FilesonicCom(DeadHoster): + __name = "FilesonicCom" + __type = "hoster" + __version = "0.35" + + __pattern = r'http://(?:www\.)?filesonic\.com/file/\w+' + __config = [] + + __description = """Filesonic.com hoster plugin""" + __license = "GPLv3" + __authors = [("jeix", "jeix@hasnomail.de"), + ("paulking", "")] diff --git a/pyload/plugin/hoster/FilezyNet.py b/pyload/plugin/hoster/FilezyNet.py new file mode 100644 index 000000000..bd6ac5984 --- /dev/null +++ b/pyload/plugin/hoster/FilezyNet.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class FilezyNet(DeadHoster): + __name = "FilezyNet" + __type = "hoster" + __version = "0.20" + + __pattern = r'http://(?:www\.)?filezy\.net/\w{12}' + __config = [] + + __description = """Filezy.net hoster plugin""" + __license = "GPLv3" + __authors = [] diff --git a/pyload/plugin/hoster/FiredriveCom.py b/pyload/plugin/hoster/FiredriveCom.py new file mode 100644 index 000000000..a5e56191f --- /dev/null +++ b/pyload/plugin/hoster/FiredriveCom.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class FiredriveCom(DeadHoster): + __name = "FiredriveCom" + __type = "hoster" + __version = "0.05" + + __pattern = r'https?://(?:www\.)?(firedrive|putlocker)\.com/(mobile/)?(file|embed)/(?P<ID>\w+)' + __config = [] + + __description = """Firedrive.com hoster plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] diff --git a/pyload/plugin/hoster/FlyFilesNet.py b/pyload/plugin/hoster/FlyFilesNet.py new file mode 100644 index 000000000..4f1d71d21 --- /dev/null +++ b/pyload/plugin/hoster/FlyFilesNet.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- + +import re +import urllib + +from pyload.network.RequestFactory import getURL +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class FlyFilesNet(SimpleHoster): + __name = "FlyFilesNet" + __type = "hoster" + __version = "0.10" + + __pattern = r'http://(?:www\.)?flyfiles\.net/.+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """FlyFiles.net hoster plugin""" + __license = "GPLv3" + __authors = [] + + SESSION_PATTERN = r'flyfiles\.net/(.*)/.*' + NAME_PATTERN = r'flyfiles\.net/.*/(.*)' + + + def process(self, pyfile): + name = re.search(self.NAME_PATTERN, pyfile.url).group(1) + pyfile.name = urllib.unquote_plus(name) + + session = re.search(self.SESSION_PATTERN, pyfile.url).group(1) + + url = "http://flyfiles.net" + + # get download URL + parsed_url = getURL(url, post={"getDownLink": session}) + self.logDebug("Parsed URL: %s" % parsed_url) + + if parsed_url == '#downlink|' or parsed_url == "#downlink|#": + self.logWarning(_("Could not get the download URL. Please wait 10 minutes")) + self.wait(10 * 60, True) + self.retry() + + self.link = parsed_url.replace('#downlink|', '') diff --git a/pyload/plugin/hoster/FourSharedCom.py b/pyload/plugin/hoster/FourSharedCom.py new file mode 100644 index 000000000..7ea5e55b8 --- /dev/null +++ b/pyload/plugin/hoster/FourSharedCom.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class FourSharedCom(SimpleHoster): + __name = "FourSharedCom" + __type = "hoster" + __version = "0.31" + + __pattern = r'https?://(?:www\.)?4shared(\-china)?\.com/(account/)?(download|get|file|document|photo|video|audio|mp3|office|rar|zip|archive|music)/.+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """4Shared.com hoster plugin""" + __license = "GPLv3" + __authors = [("jeix", "jeix@hasnomail.de"), + ("zoidberg", "zoidberg@mujmail.cz")] + + + NAME_PATTERN = r'<meta name="title" content="(?P<N>.+?)"' + SIZE_PATTERN = r'<span title="Size: (?P<S>[\d.,]+) (?P<U>[\w^_]+)">' + OFFLINE_PATTERN = r'The file link that you requested is not valid\.|This file was deleted.' + + NAME_REPLACEMENTS = [(r"&#(\d+).", lambda m: unichr(int(m.group(1))))] + SIZE_REPLACEMENTS = [(",", "")] + + DIRECT_LINK = False + LOGIN_ACCOUNT = True + + LINK_FREE_PATTERN = r'name="d3link" value="(.*?)"' + LINK_BTN_PATTERN = r'id="btnLink" href="(.*?)"' + + ID_PATTERN = r'name="d3fid" value="(.*?)"' + + + def handleFree(self, pyfile): + m = re.search(self.LINK_BTN_PATTERN, self.html) + if m: + link = m.group(1) + else: + link = re.sub(r'/(download|get|file|document|photo|video|audio)/', r'/get/', pyfile.url) + + self.html = self.load(link) + + m = re.search(self.LINK_FREE_PATTERN, self.html) + if m is None: + self.error(_("Download link")) + + self.link = m.group(1) + + try: + m = re.search(self.ID_PATTERN, self.html) + res = self.load('http://www.4shared.com/web/d2/getFreeDownloadLimitInfo?fileId=%s' % m.group(1)) + self.logDebug(res) + except Exception: + pass + + self.wait(20) diff --git a/pyload/plugin/hoster/FreakshareCom.py b/pyload/plugin/hoster/FreakshareCom.py new file mode 100644 index 000000000..6cf447128 --- /dev/null +++ b/pyload/plugin/hoster/FreakshareCom.py @@ -0,0 +1,182 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.Hoster import Hoster +from pyload.plugin.captcha.ReCaptcha import ReCaptcha +from pyload.plugin.internal.SimpleHoster import secondsToMidnight + + +class FreakshareCom(Hoster): + __name = "FreakshareCom" + __type = "hoster" + __version = "0.40" + + __pattern = r'http://(?:www\.)?freakshare\.(net|com)/files/\S*?/' + + __description = """Freakshare.com hoster plugin""" + __license = "GPLv3" + __authors = [("sitacuisses", "sitacuisses@yahoo.de"), + ("spoob", "spoob@pyload.org"), + ("mkaay", "mkaay@mkaay.de"), + ("Toilal", "toilal.dev@gmail.com")] + + + def setup(self): + self.multiDL = False + self.req_opts = [] + + + def process(self, pyfile): + self.pyfile = pyfile + + pyfile.url = pyfile.url.replace("freakshare.net/", "freakshare.com/") + + if self.account: + self.html = self.load(pyfile.url, cookies=False) + pyfile.name = self.get_file_name() + self.download(pyfile.url) + + else: + self.prepare() + self.get_file_url() + + self.download(pyfile.url, post=self.req_opts) + + check = self.checkDownload({"bad" : "bad try", + "paralell" : "> Sorry, you cant download more then 1 files at time. <", + "empty" : "Warning: Unknown: Filename cannot be empty", + "wrong_captcha" : "Wrong Captcha!", + "downloadserver": "No Downloadserver. Please try again later!"}) + + if check == "bad": + self.fail(_("Bad Try")) + + elif check == "paralell": + self.setWait(300, True) + self.wait() + self.retry() + + elif check == "empty": + self.fail(_("File not downloadable")) + + elif check == "wrong_captcha": + self.invalidCaptcha() + self.retry() + + elif check == "downloadserver": + self.retry(5, 15 * 60, _("No Download server")) + + + def prepare(self): + pyfile = self.pyfile + + self.download_html() + + if not self.file_exists(): + self.offline() + + self.setWait(self.get_waiting_time()) + + pyfile.name = self.get_file_name() + pyfile.size = self.get_file_size() + + self.wait() + + return True + + + def download_html(self): + self.load("http://freakshare.com/index.php", {"language": "EN"}) #: Set english language in server session + self.html = self.load(self.pyfile.url) + + + def get_file_url(self): + """ returns the absolute downloadable filepath + """ + if not self.html: + self.download_html() + if not self.wantReconnect: + self.req_opts = self.get_download_options() #: get the Post options for the Request + # file_url = self.pyfile.url + # return file_url + else: + self.offline() + + + def get_file_name(self): + if not self.html: + self.download_html() + + if not self.wantReconnect: + m = re.search(r"<h1\sclass=\"box_heading\"\sstyle=\"text-align:center;\">([^ ]+)", self.html) + if m: + file_name = m.group(1) + else: + file_name = self.pyfile.url + + return file_name + else: + return self.pyfile.url + + + def get_file_size(self): + size = 0 + if not self.html: + self.download_html() + + if not self.wantReconnect: + m = re.search(r"<h1\sclass=\"box_heading\"\sstyle=\"text-align:center;\">[^ ]+ - ([^ ]+) (\w\w)yte", self.html) + if m: + units = float(m.group(1).replace(",", "")) + pow = {'KB': 1, 'MB': 2, 'GB': 3}[m.group(2)] + size = int(units * (2 ** 20) ** pow) + + return size + + + def get_waiting_time(self): + if not self.html: + self.download_html() + + if "Your Traffic is used up for today" in self.html: + self.wantReconnect = True + return secondsToMidnight(gmt=2) + + timestring = re.search('\s*var\s(?:downloadWait|time)\s=\s(\d*)[\d.]*;', self.html) + if timestring: + return int(timestring.group(1)) + else: + return 60 + + + def file_exists(self): + """ returns True or False + """ + if not self.html: + self.download_html() + if re.search(r"This file does not exist!", self.html): + return False + else: + return True + + + def get_download_options(self): + re_envelope = re.search(r".*?value=\"Free\sDownload\".*?\n*?(.*?<.*?>\n*)*?\n*\s*?</form>", + self.html).group(0) #: get the whole request + to_sort = re.findall(r"<input\stype=\"hidden\"\svalue=\"(.*?)\"\sname=\"(.*?)\"\s\/>", re_envelope) + request_options = dict((n, v) for (v, n) in to_sort) + + herewego = self.load(self.pyfile.url, None, request_options) #: the actual download-Page + + to_sort = re.findall(r"<input\stype=\".*?\"\svalue=\"(\S*?)\".*?name=\"(\S*?)\"\s.*?\/>", herewego) + request_options = dict((n, v) for (v, n) in to_sort) + + challenge = re.search(r"http://api\.recaptcha\.net/challenge\?k=(\w+)", herewego) + + if challenge: + re_captcha = ReCaptcha(self) + (request_options['recaptcha_challenge_field'], + request_options['recaptcha_response_field']) = re_captcha.challenge(challenge.group(1)) + + return request_options diff --git a/pyload/plugin/hoster/FreeWayMe.py b/pyload/plugin/hoster/FreeWayMe.py new file mode 100644 index 000000000..2406ed031 --- /dev/null +++ b/pyload/plugin/hoster/FreeWayMe.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.MultiHoster import MultiHoster + + +class FreeWayMe(MultiHoster): + __name = "FreeWayMe" + __type = "hoster" + __version = "0.16" + + __pattern = r'https://(?:www\.)?free-way\.me/.+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """FreeWayMe multi-hoster plugin""" + __license = "GPLv3" + __authors = [("Nicolas Giese", "james@free-way.me")] + + + def setup(self): + self.resumeDownload = False + self.multiDL = self.premium + self.chunkLimit = 1 + + + def handlePremium(self, pyfile): + user, data = self.account.selectAccount() + + for _i in xrange(5): + # try it five times + header = self.load("https://www.free-way.me/load.php", + get={'multiget': 7, + 'url' : pyfile.url, + 'user' : user, + 'pw' : self.account.getAccountData(user)['password'], + 'json' : ""}, + just_header=True) + + if 'location' in header: + headers = self.load(header['location'], just_header=True) + if headers['code'] == 500: + # error on 2nd stage + self.logError(_("Error [stage2]")) + else: + # seems to work.. + self.download(header['location']) + break + else: + # error page first stage + self.logError(_("Error [stage1]")) + + #@TODO: handle errors diff --git a/pyload/plugin/hoster/FreevideoCz.py b/pyload/plugin/hoster/FreevideoCz.py new file mode 100644 index 000000000..f616097f2 --- /dev/null +++ b/pyload/plugin/hoster/FreevideoCz.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class FreevideoCz(DeadHoster): + __name = "FreevideoCz" + __type = "hoster" + __version = "0.30" + + __pattern = r'http://(?:www\.)?freevideo\.cz/vase-videa/.+' + __config = [] + + __description = """Freevideo.cz hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] diff --git a/pyload/plugin/hoster/FshareVn.py b/pyload/plugin/hoster/FshareVn.py new file mode 100644 index 000000000..05f213680 --- /dev/null +++ b/pyload/plugin/hoster/FshareVn.py @@ -0,0 +1,110 @@ +# -*- coding: utf-8 -*- + +import re +import time +import urlparse + +from pyload.network.RequestFactory import getURL +from pyload.plugin.internal.SimpleHoster import SimpleHoster, parseFileInfo + + +def getInfo(urls): + for url in urls: + html = getURL("http://www.fshare.vn/check_link.php", + post={'action': "check_link", 'arrlinks': url}, + decode=True) + + yield parseFileInfo(FshareVn, url, html) + + +def doubleDecode(m): + return m.group(1).decode('raw_unicode_escape') + + +class FshareVn(SimpleHoster): + __name = "FshareVn" + __type = "hoster" + __version = "0.20" + + __pattern = r'http://(?:www\.)?fshare\.vn/file/.+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """FshareVn hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + INFO_PATTERN = r'<p>(?P<N>[^<]+)<\\/p>[\\trn\s]*<p>(?P<S>[\d.,]+)\s*(?P<U>[\w^_]+)<\\/p>' + OFFLINE_PATTERN = r'<div class=\\"f_left file_w\\"|<\\/p>\\t\\t\\t\\t\\r\\n\\t\\t<p><\\/p>\\t\\t\\r\\n\\t\\t<p>0 KB<\\/p>' + + NAME_REPLACEMENTS = [("(.*)", doubleDecode)] + + LINK_FREE_PATTERN = r'action="(http://download.*?)[#"]' + WAIT_PATTERN = ur'Lượt tải xuá»ng kế tiếp là :\s*(.*?)\s*<' + + + def preload(self): + self.html = self.load("http://www.fshare.vn/check_link.php", + post={'action': "check_link", 'arrlinks': pyfile.url}, + decode=True) + + if isinstance(self.TEXT_ENCODING, basestring): + self.html = unicode(self.html, self.TEXT_ENCODING) + + + def handleFree(self, pyfile): + self.html = self.load(pyfile.url, decode=True) + + self.checkErrors() + + action, inputs = self.parseHtmlForm('frm_download') + url = urlparse.urljoin(pyfile.url, action) + + if not inputs: + self.error(_("No FORM")) + + elif 'link_file_pwd_dl' in inputs: + password = self.getPassword() + + if password: + self.logInfo(_("Password protected link, trying ") + password) + inputs['link_file_pwd_dl'] = password + self.html = self.load(url, post=inputs, decode=True) + + if 'name="link_file_pwd_dl"' in self.html: + self.fail(_("Incorrect password")) + else: + self.fail(_("No password found")) + + else: + self.html = self.load(url, post=inputs, decode=True) + + self.checkErrors() + + m = re.search(r'var count = (\d+)', self.html) + self.setWait(int(m.group(1)) if m else 30) + + m = re.search(self.LINK_FREE_PATTERN, self.html) + if m is None: + self.error(_("LINK_FREE_PATTERN not found")) + + self.link = m.group(1) + self.wait() + + + def checkErrors(self): + if '/error.php?' in self.req.lastEffectiveURL or u"Liên kết bạn chá»n khÃŽng tá»n" in self.html: + self.offline() + + m = re.search(self.WAIT_PATTERN, self.html) + if m: + self.logInfo(_("Wait until %s ICT") % m.group(1)) + wait_until = time.mktime.time(time.strptime.time(m.group(1), "%d/%m/%Y %H:%M")) + self.wait(wait_until - time.mktime.time(time.gmtime.time()) - 7 * 60 * 60, True) + self.retry() + elif '<ul class="message-error">' in self.html: + msg = "Unknown error occured or wait time not parsed" + self.logError(msg) + self.retry(30, 2 * 60, msg) + + self.info.pop('error', None) diff --git a/pyload/plugin/hoster/Ftp.py b/pyload/plugin/hoster/Ftp.py new file mode 100644 index 000000000..d26e3ad0b --- /dev/null +++ b/pyload/plugin/hoster/Ftp.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- + +import pycurl +import re +import urllib +import urlparse + +from pyload.plugin.Hoster import Hoster + + +class Ftp(Hoster): + __name = "Ftp" + __type = "hoster" + __version = "0.51" + + __pattern = r'(?:ftps?|sftp)://([\w.-]+(:[\w.-]+)?@)?[\w.-]+(:\d+)?/.+' + + __description = """Download from ftp directory""" + __license = "GPLv3" + __authors = [("jeix", "jeix@hasnomail.com"), + ("mkaay", "mkaay@mkaay.de"), + ("zoidberg", "zoidberg@mujmail.cz")] + + + def setup(self): + self.chunkLimit = -1 + self.resumeDownload = True + def process(self, pyfile): + parsed_url = urlparse.urlparse(pyfile.url) + netloc = parsed_url.netloc + + pyfile.name = parsed_url.path.rpartition('/')[2] + try: + pyfile.name = urllib.unquote(str(pyfile.name)).decode('utf8') + except Exception: + pass + + if not "@" in netloc: + servers = [x['login'] for x in self.account.getAllAccounts()] if self.account else [] + + if netloc in servers: + self.logDebug("Logging on to %s" % netloc) + self.req.addAuth(self.account.getAccountInfo(netloc)['password']) + else: + pwd = self.getPassword() + if ':' in pwd: + self.req.addAuth(pwd) + + self.req.http.c.setopt(pycurl.NOBODY, 1) + + try: + res = self.load(pyfile.url) + except pycurl.error, e: + self.fail(_("Error %d: %s") % e.args) + + self.req.http.c.setopt(pycurl.NOBODY, 0) + self.logDebug(self.req.http.header) + + m = re.search(r"Content-Length:\s*(\d+)", res) + if m: + pyfile.size = int(m.group(1)) + self.download(pyfile.url) + else: + # Naive ftp directory listing + if re.search(r'^25\d.*?"', self.req.http.header, re.M): + pyfile.url = pyfile.url.rstrip('/') + pkgname = "/".join(pyfile.package().name, urlparse.urlparse(pyfile.url).path.rpartition('/')[2]) + pyfile.url += '/' + self.req.http.c.setopt(48, 1) #: CURLOPT_DIRLISTONLY + res = self.load(pyfile.url, decode=False) + links = [pyfile.url + urllib.quote(x) for x in res.splitlines()] + self.logDebug("LINKS", links) + self.core.api.addPackage(pkgname, links) + else: + self.fail(_("Unexpected server response")) diff --git a/pyload/plugin/hoster/GamefrontCom.py b/pyload/plugin/hoster/GamefrontCom.py new file mode 100644 index 000000000..81568e376 --- /dev/null +++ b/pyload/plugin/hoster/GamefrontCom.py @@ -0,0 +1,90 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.network.RequestFactory import getURL +from pyload.plugin.Hoster import Hoster +from pyload.utils import parseFileSize + + +class GamefrontCom(Hoster): + __name = "GamefrontCom" + __type = "hoster" + __version = "0.04" + + __pattern = r'http://(?:www\.)?gamefront\.com/files/\w+' + + __description = """Gamefront.com hoster plugin""" + __license = "GPLv3" + __authors = [("fwannmacher", "felipe@warhammerproject.com")] + + + PATTERN_FILENAME = r'<title>(.*?) | Game Front' + PATTERN_FILESIZE = r'<dt>File Size:</dt>[\n\s]*<dd>(.*?)</dd>' + PATTERN_OFFLINE = r'This file doesn\'t exist, or has been removed.' + + + def setup(self): + self.resumeDownload = True + self.multiDL = True + self.chunkLimit = -1 + + + def process(self, pyfile): + self.pyfile = pyfile + self.html = self.load(pyfile.url, decode=True) + + if not self._checkOnline(): + self.offline() + + pyfile.name = self._getName() + + link = self._getLink() + + if not link.startswith('http://'): + link = "http://www.gamefront.com/" + link + + self.download(link) + + + def _checkOnline(self): + if re.search(self.PATTERN_OFFLINE, self.html): + return False + else: + return True + + + def _getName(self): + name = re.search(self.PATTERN_FILENAME, self.html) + if name is None: + self.fail(_("Plugin broken") + + return name.group(1) + + + def _getLink(self): + self.html2 = self.load("http://www.gamefront.com/" + re.search("(files/service/thankyou\\?id=\w+)", + self.html).group(1)) + return re.search("<a href=\"(http://media\d+\.gamefront.com/.*)\">click here</a>", self.html2).group(1).replace("&", "&") + + +def getInfo(urls): + result = [] + + for url in urls: + html = getURL(url) + + if re.search(GamefrontCom.PATTERN_OFFLINE, html): + result.append((url, 0, 1, url)) + else: + name = re.search(GamefrontCom.PATTERN_FILENAME, html) + if name is None: + result.append((url, 0, 1, url)) + else: + name = name.group(1) + size = re.search(GamefrontCom.PATTERN_FILESIZE, html) + size = parseFileSize(size.group(1)) + + result.append((name, size, 3, url)) + + yield result diff --git a/pyload/plugin/hoster/GigapetaCom.py b/pyload/plugin/hoster/GigapetaCom.py new file mode 100644 index 000000000..6314a17c8 --- /dev/null +++ b/pyload/plugin/hoster/GigapetaCom.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- + +import random +import re + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class GigapetaCom(SimpleHoster): + __name = "GigapetaCom" + __type = "hoster" + __version = "0.03" + + __pattern = r'http://(?:www\.)?gigapeta\.com/dl/\w+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """GigaPeta.com hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + NAME_PATTERN = r'<img src=".*" alt="file" />-->\s*(?P<N>.*?)\s*</td>' + SIZE_PATTERN = r'<th>\s*Size\s*</th>\s*<td>\s*(?P<S>.*?)\s*</td>' + OFFLINE_PATTERN = r'<div id="page_error">' + + COOKIES = [("gigapeta.com", "lang", "us")] + + + def handleFree(self, pyfile): + captcha_key = str(random.randint(1, 100000000)) + captcha_url = "http://gigapeta.com/img/captcha.gif?x=%s" % captcha_key + + for _i in xrange(5): + self.checkErrors() + + captcha = self.decryptCaptcha(captcha_url) + self.html = self.load(pyfile.url, + post={'captcha_key': captcha_key, + 'captcha' : captcha, + 'download' : "Download"}, + follow_location=False) + + m = re.search(r'Location\s*:\s*(.+)', self.req.http.header, re.I) + if m: + self.link = m.group(1) + break + elif "Entered figures don`t coincide with the picture" in self.html: + self.invalidCaptcha() + else: + self.fail(_("No valid captcha code entered")) + + + def checkErrors(self): + if "All threads for IP" in self.html: + self.logDebug("Your IP is already downloading a file") + self.wait(5 * 60, True) + self.retry() + + self.info.pop('error', None) diff --git a/pyload/plugin/hoster/GooIm.py b/pyload/plugin/hoster/GooIm.py new file mode 100644 index 000000000..6fc4a1fce --- /dev/null +++ b/pyload/plugin/hoster/GooIm.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- +# +# Test links: +# https://goo.im/devs/liquidsmooth/3.x/codina/Nightly/LS-KK-v3.2-2014-08-01-codina.zip + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class GooIm(SimpleHoster): + __name = "GooIm" + __type = "hoster" + __version = "0.04" + + __pattern = r'https?://(?:www\.)?goo\.im/.+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Goo.im hoster plugin""" + __license = "GPLv3" + __authors = [("zapp-brannigan", "fuerst.reinje@web.de")] + + + NAME_PATTERN = r'You will be redirected to .*(?P<N>[^/ ]+) in' + OFFLINE_PATTERN = r'The file you requested was not found' + + + def setup(self): + self.resumeDownload = True + self.multiDL = True + + + def handleFree(self, pyfile): + self.wait(10) + self.link = pyfile.url diff --git a/pyload/plugin/hoster/GoogledriveCom.py b/pyload/plugin/hoster/GoogledriveCom.py new file mode 100644 index 000000000..db4ee788a --- /dev/null +++ b/pyload/plugin/hoster/GoogledriveCom.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -* +# +# Test links: +# https://drive.google.com/file/d/0B6RNTe4ygItBQm15RnJiTmMyckU/view?pli=1 + +import re + +from pyload.plugin.internal.SimpleHoster import SimpleHoster +from pyload.utils import html_unescape + + +class GoogledriveCom(SimpleHoster): + __name = "GoogledriveCom" + __type = "hoster" + __version = "0.08" + + __pattern = r'https?://(?:www\.)?drive\.google\.com/file/.+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Drive.google.com hoster plugin""" + __license = "GPLv3" + __authors = [("zapp-brannigan", "fuerst.reinje@web.de")] + + + DISPOSITION = False #: Remove in 0.4.10 + + NAME_PATTERN = r'"og:title" content="(?P<N>.*?)">' + OFFLINE_PATTERN = r'align="center"><p class="errorMessage"' + + + def setup(self): + self.multiDL = True + self.resumeDownload = True + self.chunkLimit = 1 + + + def handleFree(self, pyfile): + try: + link1 = re.search(r'"(https://docs.google.com/uc\?id.*?export=download)",', + self.html.decode('unicode-escape')).group(1) + + except AttributeError: + self.error(_("Hop #1 not found")) + + else: + self.logDebug("Next hop: %s" % link1) + + self.html = self.load(link1).decode('unicode-escape') + + try: + link2 = html_unescape(re.search(r'href="(/uc\?export=download.*?)">', + self.html).group(1)) + + except AttributeError: + self.error(_("Hop #2 not found")) + + else: + self.logDebug("Next hop: %s" % link2) + + link3 = self.load("https://docs.google.com" + link2, just_header=True) + self.logDebug("DL-Link: %s" % link3['location']) + + self.link = link3['location'] diff --git a/pyload/plugin/hoster/HellshareCz.py b/pyload/plugin/hoster/HellshareCz.py new file mode 100644 index 000000000..0e909c599 --- /dev/null +++ b/pyload/plugin/hoster/HellshareCz.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class HellshareCz(SimpleHoster): + __name = "HellshareCz" + __type = "hoster" + __version = "0.85" + + __pattern = r'http://(?:www\.)?hellshare\.(?:cz|com|sk|hu|pl)/[^?]*/\d+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Hellshare.cz hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + CHECK_TRAFFIC = True + LOGIN_ACCOUNT = True + + NAME_PATTERN = r'<h1 id="filename"[^>]*>(?P<N>[^<]+)</h1>' + SIZE_PATTERN = r'<strong id="FileSize_master">(?P<S>[\d.,]+) (?P<U>[\w^_]+)</strong>' + OFFLINE_PATTERN = r'<h1>File not found.</h1>' + + LINK_FREE_PATTERN = LINK_PREMIUM_PATTERN = r'<a href="([^?]+/(\d+)/\?do=(fileDownloadButton|relatedFileDownloadButton-\2)-showDownloadWindow)"' + + + def setup(self): + self.resumeDownload = self.multiDL = bool(self.account) + self.chunkLimit = 1 diff --git a/pyload/plugin/hoster/HellspyCz.py b/pyload/plugin/hoster/HellspyCz.py new file mode 100644 index 000000000..b64becf9c --- /dev/null +++ b/pyload/plugin/hoster/HellspyCz.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class HellspyCz(DeadHoster): + __name = "HellspyCz" + __type = "hoster" + __version = "0.28" + + __pattern = r'http://(?:www\.)?(?:hellspy\.(?:cz|com|sk|hu|pl)|sciagaj\.pl)(/\S+/\d+)' + __config = [] + + __description = """HellSpy.cz hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] diff --git a/pyload/plugin/hoster/HostujeNet.py b/pyload/plugin/hoster/HostujeNet.py new file mode 100644 index 000000000..4dd5bb1c3 --- /dev/null +++ b/pyload/plugin/hoster/HostujeNet.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class HostujeNet(SimpleHoster): + __name__ = "HostujeNet" + __type__ = "hoster" + __version__ = "0.01" + + __pattern__ = r'http://(?:www\.)?hostuje\.net/\w+' + + __description__ = """Hostuje.net hoster plugin""" + __license__ = "GPLv3" + __authors__ = [("GammaC0de", None)] + + + NAME_PATTERN = r'<input type="hidden" name="name" value="(?P<N>.+?)">' + SIZE_PATTERN = r'<b>Rozmiar:</b> (?P<S>[\d.,]+) (?P<U>[\w^_]+)<br>' + OFFLINE_PATTERN = ur'Podany plik nie zostaÅ odnaleziony\.\.\.' + + + def setup(self): + self.multiDL = True + self.chunkLimit = 1 + + + def handleFree(self, pyfile): + m = re.search(r'<script src="([\w^_]+.php)"></script>', self.html) + if m: + jscript = self.load("http://hostuje.net/" + m.group(1)) + m = re.search(r"\('(\w+\.php\?i=\w+)'\);", jscript) + if m: + self.load("http://hostuje.net/" + m.group(1)) + else: + self.error(_("unexpected javascript format")) + else: + self.error(_("script not found")) + + action, inputs = self.parseHtmlForm(pyfile.url.replace(".", "\.").replace( "?", "\?")) + if not action: + self.error(_("form not found")) + + self.download(action, post=inputs) diff --git a/pyload/plugin/hoster/HotfileCom.py b/pyload/plugin/hoster/HotfileCom.py new file mode 100644 index 000000000..743c24e23 --- /dev/null +++ b/pyload/plugin/hoster/HotfileCom.py @@ -0,0 +1,19 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class HotfileCom(DeadHoster): + __name = "HotfileCom" + __type = "hoster" + __version = "0.37" + + __pattern = r'https?://(?:www\.)?hotfile\.com/dl/\d+/\w+' + __config = [] + + __description = """Hotfile.com hoster plugin""" + __license = "GPLv3" + __authors = [("sitacuisses", "sitacuisses@yhoo.de"), + ("spoob", "spoob@pyload.org"), + ("mkaay", "mkaay@mkaay.de"), + ("JoKoT3", "jokot3@gmail.com")] diff --git a/pyload/plugin/hoster/HugefilesNet.py b/pyload/plugin/hoster/HugefilesNet.py new file mode 100644 index 000000000..25a337739 --- /dev/null +++ b/pyload/plugin/hoster/HugefilesNet.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.internal.XFSHoster import XFSHoster + + +class HugefilesNet(XFSHoster): + __name = "HugefilesNet" + __type = "hoster" + __version = "0.05" + + __pattern = r'http://(?:www\.)?hugefiles\.net/\w{12}' + + __description = """Hugefiles.net hoster plugin""" + __license = "GPLv3" + __authors = [("stickell", "l.stickell@yahoo.it")] + + + SIZE_PATTERN = r'File Size:</span>\s*<span.*?>(?P<S>[^<]+)</span></div>' + + FORM_INPUTS_MAP = {'ctype': re.compile(r'\d+')} diff --git a/pyload/plugin/hoster/HundredEightyUploadCom.py b/pyload/plugin/hoster/HundredEightyUploadCom.py new file mode 100644 index 000000000..de312245e --- /dev/null +++ b/pyload/plugin/hoster/HundredEightyUploadCom.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSHoster import XFSHoster + + +class HundredEightyUploadCom(XFSHoster): + __name = "HundredEightyUploadCom" + __type = "hoster" + __version = "0.05" + + __pattern = r'http://(?:www\.)?180upload\.com/\w{12}' + + __description = """180upload.com hoster plugin""" + __license = "GPLv3" + __authors = [("stickell", "l.stickell@yahoo.it")] + + + OFFLINE_PATTERN = r'>File Not Found' diff --git a/pyload/plugin/hoster/IFileWs.py b/pyload/plugin/hoster/IFileWs.py new file mode 100644 index 000000000..57c86d490 --- /dev/null +++ b/pyload/plugin/hoster/IFileWs.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class IFileWs(DeadHoster): + __name = "IFileWs" + __type = "hoster" + __version = "0.02" + + __pattern = r'http://(?:www\.)?ifile\.ws/\w{12}' + __config = [] + + __description = """Ifile.ws hoster plugin""" + __license = "GPLv3" + __authors = [("z00nx", "z00nx0@gmail.com")] diff --git a/pyload/plugin/hoster/IcyFilesCom.py b/pyload/plugin/hoster/IcyFilesCom.py new file mode 100644 index 000000000..88ec57a98 --- /dev/null +++ b/pyload/plugin/hoster/IcyFilesCom.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class IcyFilesCom(DeadHoster): + __name = "IcyFilesCom" + __type = "hoster" + __version = "0.06" + + __pattern = r'http://(?:www\.)?icyfiles\.com/(.+)' + __config = [] + + __description = """IcyFiles.com hoster plugin""" + __license = "GPLv3" + __authors = [("godofdream", "soilfiction@gmail.com")] diff --git a/pyload/plugin/hoster/IfileIt.py b/pyload/plugin/hoster/IfileIt.py new file mode 100644 index 000000000..647e06c01 --- /dev/null +++ b/pyload/plugin/hoster/IfileIt.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class IfileIt(DeadHoster): + __name = "IfileIt" + __type = "hoster" + __version = "0.29" + + __pattern = r'^unmatchable$' + __config = [] + + __description = """Ifile.it""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] diff --git a/pyload/plugin/hoster/IfolderRu.py b/pyload/plugin/hoster/IfolderRu.py new file mode 100644 index 000000000..d16dc695c --- /dev/null +++ b/pyload/plugin/hoster/IfolderRu.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class IfolderRu(SimpleHoster): + __name = "IfolderRu" + __type = "hoster" + __version = "0.39" + + __pattern = r'http://(?:www)?(files\.)?(ifolder\.ru|metalarea\.org|rusfolder\.(com|net|ru))/(files/)?(?P<ID>\d+)' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Ifolder.ru hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + SIZE_REPLACEMENTS = [(u'Ðб', 'KB'), (u'Ðб', 'MB'), (u'Ðб', 'GB')] + + NAME_PATTERN = ur'(?:<div><span>)?ÐазваМОе:(?:</span>)? <b>(?P<N>[^<]+)</b><(?:/div|br)>' + SIZE_PATTERN = ur'(?:<div><span>)?РазЌеÑ:(?:</span>)? <b>(?P<S>[^<]+)</b><(?:/div|br)>' + OFFLINE_PATTERN = ur'<p>Ѐайл ÐœÐŸÐŒÐµÑ <b>.*?</b> (Ме МайЎеМ|ÑЎалеМ) !!!</p>' + + SESSION_ID_PATTERN = r'<input type="hidden" name="session" value="(.+?)"' + INTS_SESSION_PATTERN = r'\(\'ints_session\'\);\s*if\(tag\)\{tag\.value = "(.+?)";\}' + HIDDEN_INPUT_PATTERN = r'var v = .*?name=\'(.+?)\' value=\'1\'' + + LINK_FREE_PATTERN = r'<a href="(.+?)" class="downloadbutton_files"' + + WRONG_CAPTCHA_PATTERN = ur'<font color=Red>МевеÑМÑй кПЎ,<br>ввеЎОÑе еÑе Ñаз</font><br>' + + + def setup(self): + self.resumeDownload = self.multiDL = bool(self.account) + self.chunkLimit = 1 + + + def handleFree(self, pyfile): + url = "http://rusfolder.com/%s" % self.info['pattern']['ID'] + self.html = self.load("http://rusfolder.com/%s" % self.info['pattern']['ID'], decode=True) + self.getFileInfo() + + session_id = re.search(self.SESSION_ID_PATTERN, self.html).groups() + + captcha_url = "http://ints.rusfolder.com/random/images/?session=%s" % session_id + for _i in xrange(5): + action, inputs = self.parseHtmlForm('id="download-step-one-form"') + inputs['confirmed_number'] = self.decryptCaptcha(captcha_url, cookies=True) + inputs['action'] = '1' + self.logDebug(inputs) + + self.html = self.load(url, decode=True, post=inputs) + if self.WRONG_CAPTCHA_PATTERN in self.html: + self.invalidCaptcha() + else: + break + else: + self.fail(_("Invalid captcha")) + + self.link = re.search(self.LINK_FREE_PATTERN, self.html).group(1) diff --git a/pyload/plugin/hoster/JumbofilesCom.py b/pyload/plugin/hoster/JumbofilesCom.py new file mode 100644 index 000000000..69e2c645a --- /dev/null +++ b/pyload/plugin/hoster/JumbofilesCom.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class JumbofilesCom(SimpleHoster): + __name = "JumbofilesCom" + __type = "hoster" + __version = "0.03" + + __pattern = r'http://(?:www\.)?jumbofiles\.com/(?P<ID>\w{12})' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """JumboFiles.com hoster plugin""" + __license = "GPLv3" + __authors = [("godofdream", "soilfiction@gmail.com")] + + + INFO_PATTERN = r'<TR><TD>(?P<N>[^<]+?)\s*<small>\((?P<S>[\d.,]+)\s*(?P<U>[\w^_]+)' + OFFLINE_PATTERN = r'Not Found or Deleted / Disabled due to inactivity or DMCA' + LINK_FREE_PATTERN = r'<meta http-equiv="refresh" content="10;url=(.+)">' + + + def setup(self): + self.resumeDownload = True + self.multiDL = True + + + def handleFree(self, pyfile): + post_data = {"id": self.info['pattern']['ID'], "op": "download3", "rand": ""} + html = self.load(self.pyfile.url, post=post_data, decode=True) + self.link = re.search(self.LINK_FREE_PATTERN, html).group(1) diff --git a/pyload/plugin/hoster/JunocloudMe.py b/pyload/plugin/hoster/JunocloudMe.py new file mode 100644 index 000000000..9054734fb --- /dev/null +++ b/pyload/plugin/hoster/JunocloudMe.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSHoster import XFSHoster + + +class JunocloudMe(XFSHoster): + __name = "JunocloudMe" + __type = "hoster" + __version = "0.05" + + __pattern = r'http://(?:\w+\.)?junocloud\.me/\w{12}' + + __description = """Junocloud.me hoster plugin""" + __license = "GPLv3" + __authors = [("guidobelix", "guidobelix@hotmail.it")] + + + URL_REPLACEMENTS = [(r'//(www\.)?junocloud', "//dl3.junocloud")] + + OFFLINE_PATTERN = r'>No such file with this filename<' + TEMP_OFFLINE_PATTERN = r'The page may have been renamed, removed or be temporarily unavailable.<' diff --git a/pyload/plugin/hoster/Keep2ShareCc.py b/pyload/plugin/hoster/Keep2ShareCc.py new file mode 100644 index 000000000..05dafffa8 --- /dev/null +++ b/pyload/plugin/hoster/Keep2ShareCc.py @@ -0,0 +1,115 @@ +# -*- coding: utf-8 -*- + +import re +import urlparse + +from pyload.plugin.captcha.ReCaptcha import ReCaptcha +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class Keep2ShareCc(SimpleHoster): + __name = "Keep2ShareCc" + __type = "hoster" + __version = "0.21" + + __pattern = r'https?://(?:www\.)?(keep2share|k2s|keep2s)\.cc/file/(?P<ID>\w+)' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Keep2Share.cc hoster plugin""" + __license = "GPLv3" + __authors = [("stickell", "l.stickell@yahoo.it"), + ("Walter Purcaro", "vuolter@gmail.com")] + + + URL_REPLACEMENTS = [(__pattern + ".*", "http://keep2s.cc/file/\g<ID>")] + + NAME_PATTERN = r'File: <span>(?P<N>.+)</span>' + SIZE_PATTERN = r'Size: (?P<S>[^<]+)</div>' + + OFFLINE_PATTERN = r'File not found or deleted|Sorry, this file is blocked or deleted|Error 404' + TEMP_OFFLINE_PATTERN = r'Downloading blocked due to' + + LINK_FREE_PATTERN = r'"(.+?url.html\?file=.+?)"|window\.location\.href = \'(.+?)\';' + LINK_PREMIUM_PATTERN = r'window\.location\.href = \'(.+?)\';' + + CAPTCHA_PATTERN = r'src="(/file/captcha\.html.+?)"' + + WAIT_PATTERN = r'Please wait ([\d:]+) to download this file' + TEMP_ERROR_PATTERN = r'>\s*(Download count files exceed|Traffic limit exceed|Free account does not allow to download more than one file at the same time)' + ERROR_PATTERN = r'>\s*(Free user can\'t download large files|You no can access to this file|This download available only for premium users|This is private file)' + + + def checkErrors(self): + m = re.search(self.TEMP_ERROR_PATTERN, self.html) + if m: + self.info['error'] = m.group(1) + self.wantReconnect = True + self.retry(wait_time=30 * 60, reason=m.group(0)) + + m = re.search(self.ERROR_PATTERN, self.html) + if m: + errmsg = self.info['error'] = m.group(1) + self.error(errmsg) + + m = re.search(self.WAIT_PATTERN, self.html) + if m: + self.logDebug("Hoster told us to wait for %s" % m.group(1)) + + # string to time convert courtesy of https://stackoverflow.com/questions/10663720 + ftr = [3600, 60, 1] + wait_time = sum(a * b for a, b in zip(ftr, map(int, m.group(1).split(':')))) + + self.wantReconnect = True + self.retry(wait_time=wait_time, reason="Please wait to download this file") + + self.info.pop('error', None) + + + def handleFree(self, pyfile): + self.fid = re.search(r'<input type="hidden" name="slow_id" value="(.+?)">', self.html).group(1) + self.html = self.load(pyfile.url, post={'yt0': '', 'slow_id': self.fid}) + + # self.logDebug(self.fid) + # self.logDebug(pyfile.url) + + self.checkErrors() + + m = re.search(self.LINK_FREE_PATTERN, self.html) + if m is None: + self.handleCaptcha() + self.wait(31) + self.html = self.load(pyfile.url) + + m = re.search(self.LINK_FREE_PATTERN, self.html) + if m is None: + self.error(_("Free download link not found")) + + self.link = m.group(1) + + + def handleCaptcha(self): + post_data = {'free' : 1, + 'freeDownloadRequest': 1, + 'uniqueId' : self.fid, + 'yt0' : ''} + + m = re.search(r'id="(captcha\-form)"', self.html) + self.logDebug("captcha-form found %s" % m) + + m = re.search(self.CAPTCHA_PATTERN, self.html) + self.logDebug("CAPTCHA_PATTERN found %s" % m) + if m: + captcha_url = urlparse.urljoin("http://keep2s.cc/", m.group(1)) + post_data['CaptchaForm[code]'] = self.decryptCaptcha(captcha_url) + else: + recaptcha = ReCaptcha(self) + response, challenge = recaptcha.challenge() + post_data.update({'recaptcha_challenge_field': challenge, + 'recaptcha_response_field' : response}) + + self.html = self.load(self.pyfile.url, post=post_data) + + if 'verification code is incorrect' not in self.html: + self.correctCaptcha() + else: + self.invalidCaptcha() diff --git a/pyload/plugin/hoster/KickloadCom.py b/pyload/plugin/hoster/KickloadCom.py new file mode 100644 index 000000000..11c79cef9 --- /dev/null +++ b/pyload/plugin/hoster/KickloadCom.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class KickloadCom(DeadHoster): + __name = "KickloadCom" + __type = "hoster" + __version = "0.21" + + __pattern = r'http://(?:www\.)?kickload\.com/get/.+' + __config = [] + + __description = """Kickload.com hoster plugin""" + __license = "GPLv3" + __authors = [("mkaay", "mkaay@mkaay.de")] diff --git a/pyload/plugin/hoster/KingfilesNet.py b/pyload/plugin/hoster/KingfilesNet.py new file mode 100644 index 000000000..557f1f836 --- /dev/null +++ b/pyload/plugin/hoster/KingfilesNet.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.captcha.SolveMedia import SolveMedia +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class KingfilesNet(SimpleHoster): + __name = "KingfilesNet" + __type = "hoster" + __version = "0.07" + + __pattern = r'http://(?:www\.)?kingfiles\.net/(?P<ID>\w{12})' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Kingfiles.net hoster plugin""" + __license = "GPLv3" + __authors = [("zapp-brannigan", "fuerst.reinje@web.de"), + ("Walter Purcaro", "vuolter@gmail.com")] + + + NAME_PATTERN = r'name="fname" value="(?P<N>.+?)">' + SIZE_PATTERN = r'>Size: .+?">(?P<S>[\d.,]+) (?P<U>[\w^_]+)' + + OFFLINE_PATTERN = r'>(File Not Found</b><br><br>|File Not Found</h2>)' + + RAND_ID_PATTERN = r'type=\"hidden\" name=\"rand\" value=\"(.+)\">' + + LINK_FREE_PATTERN = r'var download_url = \'(.+)\';' + + + def setup(self): + self.resumeDownload = True + self.multiDL = True + + + def handleFree(self, pyfile): + # Click the free user button + post_data = {'op' : "download1", + 'usr_login' : "", + 'id' : self.info['pattern']['ID'], + 'fname' : pyfile.name, + 'referer' : "", + 'method_free': "+"} + + self.html = self.load(pyfile.url, post=post_data, decode=True) + + solvemedia = SolveMedia(self) + response, challenge = solvemedia.challenge() + + # Make the downloadlink appear and load the file + m = re.search(self.RAND_ID_PATTERN, self.html) + if m is None: + self.error(_("Random key not found")) + + rand = m.group(1) + self.logDebug("rand = ", rand) + + post_data = {'op' : "download2", + 'id' : self.info['pattern']['ID'], + 'rand' : rand, + 'referer' : pyfile.url, + 'method_free' : "+", + 'method_premium' : "", + 'adcopy_response' : response, + 'adcopy_challenge': challenge, + 'down_direct' : "1"} + + self.html = self.load(pyfile.url, post=post_data, decode=True) + + m = re.search(self.LINK_FREE_PATTERN, self.html) + if m is None: + self.error(_("Download url not found")) + + self.link = m.group(1) diff --git a/pyload/plugin/hoster/LemUploadsCom.py b/pyload/plugin/hoster/LemUploadsCom.py new file mode 100644 index 000000000..c43867a99 --- /dev/null +++ b/pyload/plugin/hoster/LemUploadsCom.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class LemUploadsCom(DeadHoster): + __name = "LemUploadsCom" + __type = "hoster" + __version = "0.02" + + __pattern = r'https?://(?:www\.)?lemuploads\.com/\w{12}' + __config = [] + + __description = """LemUploads.com hoster plugin""" + __license = "GPLv3" + __authors = [("t4skforce", "t4skforce1337[AT]gmail[DOT]com")] diff --git a/pyload/plugin/hoster/LetitbitNet.py b/pyload/plugin/hoster/LetitbitNet.py new file mode 100644 index 000000000..85fd55b89 --- /dev/null +++ b/pyload/plugin/hoster/LetitbitNet.py @@ -0,0 +1,136 @@ +# -*- coding: utf-8 -*- +# +# API Documentation: +# http://api.letitbit.net/reg/static/api.pdf +# +# Test links: +# http://letitbit.net/download/07874.0b5709a7d3beee2408bb1f2eefce/random.bin.html + +import re +import urlparse + +from pyload.utils import json_loads, json_dumps +from pyload.network.RequestFactory import getURL +from pyload.plugin.captcha.ReCaptcha import ReCaptcha +from pyload.plugin.internal.SimpleHoster import SimpleHoster, secondsToMidnight + + +def api_response(url): + json_data = ["yw7XQy2v9", ["download/info", {"link": url}]] + api_rep = getURL("http://api.letitbit.net/json", + post={'r': json_dumps(json_data)}) + return json_loads(api_rep) + + +def getInfo(urls): + for url in urls: + api_rep = api_response(url) + if api_rep['status'] == 'OK': + info = api_rep['data'][0] + yield (info['name'], info['size'], 2, url) + else: + yield (url, 0, 1, url) + + +class LetitbitNet(SimpleHoster): + __name = "LetitbitNet" + __type = "hoster" + __version = "0.30" + + __pattern = r'https?://(?:www\.)?(letitbit|shareflare)\.net/download/.+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Letitbit.net hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz"), + ("z00nx", "z00nx0@gmail.com")] + + + URL_REPLACEMENTS = [(r"(?<=http://)([^/]+)", "letitbit.net")] + + SECONDS_PATTERN = r'seconds\s*=\s*(\d+);' + CAPTCHA_CONTROL_FIELD = r'recaptcha_control_field\s=\s\'(.+?)\'' + + + def setup(self): + self.resumeDownload = True + + + def handleFree(self, pyfile): + action, inputs = self.parseHtmlForm('id="ifree_form"') + if not action: + self.error(_("ifree_form")) + + pyfile.size = float(inputs['sssize']) + self.logDebug(action, inputs) + inputs['desc'] = "" + + self.html = self.load(urlparse.urljoin("http://letitbit.net/", action), post=inputs) + + m = re.search(self.SECONDS_PATTERN, self.html) + seconds = int(m.group(1)) if m else 60 + + self.logDebug("Seconds found", seconds) + + m = re.search(self.CAPTCHA_CONTROL_FIELD, self.html) + recaptcha_control_field = m.group(1) + + self.logDebug("ReCaptcha control field found", recaptcha_control_field) + + self.wait(seconds) + + res = self.load("http://letitbit.net/ajax/download3.php", post=" ") + if res != '1': + self.error(_("Unknown response - ajax_check_url")) + + self.logDebug(res) + + recaptcha = ReCaptcha(self) + response, challenge = recaptcha.challenge() + + post_data = {"recaptcha_challenge_field": challenge, + "recaptcha_response_field": response, + "recaptcha_control_field": recaptcha_control_field} + + self.logDebug("Post data to send", post_data) + + res = self.load("http://letitbit.net/ajax/check_recaptcha.php", post=post_data) + + self.logDebug(res) + + if not res: + self.invalidCaptcha() + + if res == "error_free_download_blocked": + self.logWarning(_("Daily limit reached")) + self.wait(secondsToMidnight(gmt=2), True) + + if res == "error_wrong_captcha": + self.invalidCaptcha() + self.retry() + + elif res.startswith('['): + urls = json_loads(res) + + elif res.startswith('http://'): + urls = [res] + + else: + self.error(_("Unknown response - captcha check")) + + self.link = urls[0] + + + def handlePremium(self, pyfile): + api_key = self.user + premium_key = self.account.getAccountData(self.user)['password'] + + json_data = [api_key, ["download/direct_links", {"pass": premium_key, "link": pyfile.url}]] + api_rep = self.load('http://api.letitbit.net/json', post={'r': json_dumps(json_data)}) + self.logDebug("API Data: " + api_rep) + api_rep = json_loads(api_rep) + + if api_rep['status'] == 'FAIL': + self.fail(api_rep['data']) + + self.link = api_rep['data'][0][0] diff --git a/pyload/plugin/hoster/LinksnappyCom.py b/pyload/plugin/hoster/LinksnappyCom.py new file mode 100644 index 000000000..186639a81 --- /dev/null +++ b/pyload/plugin/hoster/LinksnappyCom.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- + +import re +import urlparse + +from pyload.utils import json_loads, json_dumps +from pyload.plugin.internal.MultiHoster import MultiHoster + + +class LinksnappyCom(MultiHoster): + __name = "LinksnappyCom" + __type = "hoster" + __version = "0.08" + + __pattern = r'https?://(?:[^/]+\.)?linksnappy\.com' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Linksnappy.com multi-hoster plugin""" + __license = "GPLv3" + __authors = [("stickell", "l.stickell@yahoo.it")] + + + SINGLE_CHUNK_HOSTERS = ["easybytez.com"] + + + def handlePremium(self, pyfile): + host = self._get_host(pyfile.url) + json_params = json_dumps({'link' : pyfile.url, + 'type' : host, + 'username': self.user, + 'password': self.account.getAccountData(self.user)['password']}) + + r = self.load("http://gen.linksnappy.com/genAPI.php", + post={'genLinks': json_params}) + + self.logDebug("JSON data: " + r) + + j = json_loads(r)['links'][0] + + if j['error']: + self.error(_("Error converting the link")) + + pyfile.name = j['filename'] + self.link = j['generated'] + + if host in self.SINGLE_CHUNK_HOSTERS: + self.chunkLimit = 1 + else: + self.setup() + + + @staticmethod + def _get_host(url): + host = urlparse.urlsplit(url).netloc + return re.search(r'[\w-]+\.\w+$', host).group(0) diff --git a/pyload/plugin/hoster/LoadTo.py b/pyload/plugin/hoster/LoadTo.py new file mode 100644 index 000000000..3a625dbe3 --- /dev/null +++ b/pyload/plugin/hoster/LoadTo.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +# +# Test links: +# http://www.load.to/JWydcofUY6/random.bin +# http://www.load.to/oeSmrfkXE/random100.bin + +import re + +from pyload.plugin.captcha.SolveMedia import SolveMedia +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class LoadTo(SimpleHoster): + __name = "LoadTo" + __type = "hoster" + __version = "0.22" + + __pattern = r'http://(?:www\.)?load\.to/\w+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Load.to hoster plugin""" + __license = "GPLv3" + __authors = [("halfman", "Pulpan3@gmail.com"), + ("stickell", "l.stickell@yahoo.it")] + + + NAME_PATTERN = r'<h1>(?P<N>.+)</h1>' + SIZE_PATTERN = r'Size: (?P<S>[\d.,]+) (?P<U>[\w^_]+)' + OFFLINE_PATTERN = r'>Can\'t find file' + + LINK_FREE_PATTERN = r'<form method="post" action="(.+?)"' + WAIT_PATTERN = r'type="submit" value="Download \((\d+)\)"' + + URL_REPLACEMENTS = [(r'(\w)$', r'\1/')] + + + def setup(self): + self.multiDL = True + self.chunkLimit = 1 + + + def handleFree(self, pyfile): + # Search for Download URL + m = re.search(self.LINK_FREE_PATTERN, self.html) + if m is None: + self.error(_("LINK_FREE_PATTERN not found")) + + self.link = m.group(1) + + # Set Timer - may be obsolete + m = re.search(self.WAIT_PATTERN, self.html) + if m: + self.wait(m.group(1)) + + # Load.to is using solvemedia captchas since ~july 2014: + solvemedia = SolveMedia(self) + captcha_key = solvemedia.detect_key() + + if captcha_key: + response, challenge = solvemedia.challenge(captcha_key) + self.download(self.link, + post={'adcopy_challenge': challenge, + 'adcopy_response' : response, + 'returnUrl' : pyfile.url}) diff --git a/pyload/plugin/hoster/LolabitsEs.py b/pyload/plugin/hoster/LolabitsEs.py new file mode 100644 index 000000000..890cb0239 --- /dev/null +++ b/pyload/plugin/hoster/LolabitsEs.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -* + +import HTMLParser +import re + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class LolabitsEs(SimpleHoster): + __name = "LolabitsEs" + __type = "hoster" + __version = "0.02" + + __pattern = r'https?://(?:www\.)?lolabits\.es/.+' + + __description = """Lolabits.es hoster plugin""" + __license = "GPLv3" + __authors = [("zapp-brannigan", "fuerst.reinje@web.de")] + + + NAME_PATTERN = r'Descargar: <b>(?P<N>.+?)<' + SIZE_PATTERN = r'class="fileSize">(?P<S>[\d.,]+) (?P<U>[\w^_]+)' + OFFLINE_PATTERN = r'Un usuario con este nombre no existe' + + FILEID_PATTERN = r'name="FileId" value="(\d+)"' + TOKEN_PATTERN = r'name="__RequestVerificationToken" type="hidden" value="(.+?)"' + LINK_PATTERN = r'"redirectUrl":"(.+?)"' + + + def setup(self): + self.chunkLimit = 1 + + + def handleFree(self, pyfile): + fileid = re.search(self.FILEID_PATTERN, self.html).group(1) + self.logDebug("FileID: " + fileid) + + token = re.search(self.TOKEN_PATTERN, self.html).group(1) + self.logDebug("Token: " + token) + + self.html = self.load("http://lolabits.es/action/License/Download", + post={'fileId' : fileid, + '__RequestVerificationToken' : token}).decode('unicode-escape') + + self.link = HTMLParser.HTMLParser().unescape(re.search(self.LINK_PATTERN, self.html).group(1)) diff --git a/pyload/plugin/hoster/LomafileCom.py b/pyload/plugin/hoster/LomafileCom.py new file mode 100644 index 000000000..de56d2d58 --- /dev/null +++ b/pyload/plugin/hoster/LomafileCom.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class LomafileCom(DeadHoster): + __name = "LomafileCom" + __type = "hoster" + __version = "0.52" + + __pattern = r'http://lomafile\.com/\w{12}' + __config = [] + + __description = """Lomafile.com hoster plugin""" + __license = "GPLv3" + __authors = [("nath_schwarz", "nathan.notwhite@gmail.com"), + ("guidobelix", "guidobelix@hotmail.it")] diff --git a/pyload/plugin/hoster/LuckyShareNet.py b/pyload/plugin/hoster/LuckyShareNet.py new file mode 100644 index 000000000..6dff7d517 --- /dev/null +++ b/pyload/plugin/hoster/LuckyShareNet.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- + +import re + +from bottle import json_loads + +from pyload.plugin.captcha.ReCaptcha import ReCaptcha +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class LuckyShareNet(SimpleHoster): + __name = "LuckyShareNet" + __type = "hoster" + __version = "0.06" + + __pattern = r'https?://(?:www\.)?luckyshare\.net/(?P<ID>\d{10,})' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """LuckyShare.net hoster plugin""" + __license = "GPLv3" + __authors = [("stickell", "l.stickell@yahoo.it")] + + + INFO_PATTERN = r'<h1 class=\'file_name\'>(?P<N>\S+)</h1>\s*<span class=\'file_size\'>Filesize: (?P<S>[\d.,]+)(?P<U>[\w^_]+)</span>' + OFFLINE_PATTERN = r'There is no such file available' + + + def parseJson(self, rep): + if 'AJAX Error' in rep: + html = self.load(self.pyfile.url, decode=True) + m = re.search(r"waitingtime = (\d+);", html) + if m: + seconds = int(m.group(1)) + self.logDebug("You have to wait %d seconds between free downloads" % seconds) + self.retry(wait_time=seconds) + else: + self.error(_("Unable to detect wait time between free downloads")) + elif 'Hash expired' in rep: + self.retry(reason=_("Hash expired")) + return json_loads(rep) + + + # TODO: There should be a filesize limit for free downloads + # TODO: Some files could not be downloaded in free mode + + + def handleFree(self, pyfile): + rep = self.load(r"http://luckyshare.net/download/request/type/time/file/" + self.info['pattern']['ID'], decode=True) + + self.logDebug("JSON: " + rep) + + json = self.parseJson(rep) + self.wait(json['time']) + + recaptcha = ReCaptcha(self) + + for _i in xrange(5): + response, challenge = recaptcha.challenge() + rep = self.load(r"http://luckyshare.net/download/verify/challenge/%s/response/%s/hash/%s" % + (challenge, response, json['hash']), decode=True) + self.logDebug("JSON: " + rep) + if 'link' in rep: + json.update(self.parseJson(rep)) + self.correctCaptcha() + break + elif 'Verification failed' in rep: + self.invalidCaptcha() + else: + self.error(_("Unable to get downlaod link")) + + if not json['link']: + self.fail(_("No Download url retrieved/all captcha attempts failed")) + + self.link = json['link'] diff --git a/pyload/plugin/hoster/MediafireCom.py b/pyload/plugin/hoster/MediafireCom.py new file mode 100644 index 000000000..d05a51479 --- /dev/null +++ b/pyload/plugin/hoster/MediafireCom.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.captcha.SolveMedia import SolveMedia +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class MediafireCom(SimpleHoster): + __name = "MediafireCom" + __type = "hoster" + __version = "0.86" + + __pattern = r'https?://(?:www\.)?mediafire\.com/(file/|view/\??|download(\.php\?|/)|\?)\w{15}' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Mediafire.com hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz"), + ("stickell", "l.stickell@yahoo.it"), + ("Walter Purcaro", "vuolter@gmail.com")] + + + NAME_PATTERN = r'<META NAME="description" CONTENT="(?P<N>.+?)"/>' + SIZE_PATTERN = r'<li>File size: <span>(?P<S>[\d.,]+) (?P<U>[\w^_]+)' + INFO_PATTERN = r'oFileSharePopup\.ald\(\'.*?\',\'(?P<N>.+?)\',\'(?P<S>[\d.,]+)\s*(?P<U>[\w^_]+)\',\'\',\'(?P<H>.+?)\'\)' + OFFLINE_PATTERN = r'class="error_msg_title"' + + LINK_FREE_PATTERN = r'kNO = "(.+?)"' + + PASSWORD_PATTERN = r'<form name="form_password"' + + + def setup(self): + self.resumeDownload = True + self.multiDL = True + + + def handleFree(self, pyfile): + solvemedia = SolveMedia(self) + captcha_key = solvemedia.detect_key() + + if captcha_key: + response, challenge = solvemedia.challenge(captcha_key) + self.html = self.load(pyfile.url, + post={'adcopy_challenge': challenge, + 'adcopy_response' : response}, + decode=True) + + if self.PASSWORD_PATTERN in self.html: + password = self.getPassword() + + if not password: + self.fail(_("No password found")) + else: + self.logInfo(_("Password protected link, trying: ") + password) + self.html = self.load(self.link, post={'downloadp': password}) + + if self.PASSWORD_PATTERN in self.html: + self.fail(_("Incorrect password")) + + return super(MediafireCom, self).handleFree(pyfile) diff --git a/pyload/plugin/hoster/MegaCoNz.py b/pyload/plugin/hoster/MegaCoNz.py new file mode 100644 index 000000000..2d7b40d98 --- /dev/null +++ b/pyload/plugin/hoster/MegaCoNz.py @@ -0,0 +1,217 @@ +# -*- coding: utf-8 -*- + +import array +import os +# import pycurl +import random +import re + +from base64 import standard_b64decode + +from Crypto.Cipher import AES +from Crypto.Util import Counter + +from pyload.utils import json_loads, json_dumps +from pyload.plugin.Hoster import Hoster +from pyload.utils import decode, fs_decode, fs_encode + + +############################ General errors ################################### +# EINTERNAL (-1): An internal error has occurred. Please submit a bug report, detailing the exact circumstances in which this error occurred +# EARGS (-2): You have passed invalid arguments to this command +# EAGAIN (-3): (always at the request level) A temporary congestion or server malfunction prevented your request from being processed. No data was altered. Retry. Retries must be spaced with exponential backoff +# ERATELIMIT (-4): You have exceeded your command weight per time quota. Please wait a few seconds, then try again (this should never happen in sane real-life applications) +# +############################ Upload errors #################################### +# EFAILED (-5): The upload failed. Please restart it from scratch +# ETOOMANY (-6): Too many concurrent IP addresses are accessing this upload target URL +# ERANGE (-7): The upload file packet is out of range or not starting and ending on a chunk boundary +# EEXPIRED (-8): The upload target URL you are trying to access has expired. Please request a fresh one +# +############################ Stream/System errors ############################# +# ENOENT (-9): Object (typically, node or user) not found +# ECIRCULAR (-10): Circular linkage attempted +# EACCESS (-11): Access violation (e.g., trying to write to a read-only share) +# EEXIST (-12): Trying to create an object that already exists +# EINCOMPLETE (-13): Trying to access an incomplete resource +# EKEY (-14): A decryption operation failed (never returned by the API) +# ESID (-15): Invalid or expired user session, please relogin +# EBLOCKED (-16): User blocked +# EOVERQUOTA (-17): Request over quota +# ETEMPUNAVAIL (-18): Resource temporarily not available, please try again later +# ETOOMANYCONNECTIONS (-19): Too many connections on this resource +# EWRITE (-20): Write failed +# EREAD (-21): Read failed +# EAPPKEY (-22): Invalid application key; request not processed + + +class MegaCoNz(Hoster): + __name = "MegaCoNz" + __type = "hoster" + __version = "0.26" + + __pattern = r'(?:https?://(?:www\.)?mega\.co\.nz/|mega:|chrome:.+?)#(?P<TYPE>N|)!(?P<ID>[\w^_]+)!(?P<KEY>[\w,-]+)' + + __description = """Mega.co.nz hoster plugin""" + __license = "GPLv3" + __authors = [("RaNaN", "ranan@pyload.org"), + ("Walter Purcaro", "vuolter@gmail.com")] + + + API_URL = "https://eu.api.mega.co.nz/cs" + FILE_SUFFIX = ".crypted" + + + def b64_decode(self, data): + data = data.replace("-", "+").replace("_", "/") + return standard_b64decode(data + '=' * (-len(data) % 4)) + + + def getCipherKey(self, key): + """ Construct the cipher key from the given data """ + a = array.array("I", self.b64_decode(key)) + + k = array.array("I", (a[0] ^ a[4], a[1] ^ a[5], a[2] ^ a[6], a[3] ^ a[7])) + iv = a[4:6] + array.array("I", (0, 0)) + meta_mac = a[6:8] + + return k, iv, meta_mac + + + def api_response(self, **kwargs): + """ Dispatch a call to the api, see https://mega.co.nz/#developers """ + + # generate a session id, no idea where to obtain elsewhere + uid = random.random.randint(10 << 9, 10 ** 10) + + res = self.load(self.API_URL, get={'id': uid}, post=json_dumps([kwargs])) + self.logDebug("Api Response: " + res) + return json_loads(res) + + + def decryptAttr(self, data, key): + k, iv, meta_mac = self.getCipherKey(key) + cbc = AES.new(k, AES.MODE_CBC, "\0" * 16) + attr = decode(cbc.decrypt(self.b64_decode(data))) + + self.logDebug("Decrypted Attr: %s" % attr) + if not attr.startswith("MEGA"): + self.fail(_("Decryption failed")) + + # Data is padded, 0-bytes must be stripped + return json_loads(re.search(r'{.+?}', attr).group(0)) + + + def decryptFile(self, key): + """ Decrypts the file at lastDownload` """ + + # upper 64 bit of counter start + n = self.b64_decode(key)[16:24] + + # convert counter to long and shift bytes + k, iv, meta_mac = self.getCipherKey(key) + ctr = Counter.new(128, initial_value=long(n.encode("hex"), 16) << 64) + cipher = AES.new(k, AES.MODE_CTR, counter=ctr) + + self.pyfile.setStatus("decrypting") + self.pyfile.setProgress(0) + + file_crypted = fs_encode(self.lastDownload) + file_decrypted = file_crypted.rsplit(self.FILE_SUFFIX)[0] + + try: + f = open(file_crypted, "rb") + df = open(file_decrypted, "wb") + + except IOError, e: + self.fail(e) + + chunk_size = 2 ** 15 #: buffer size, 32k + # file_mac = [0, 0, 0, 0] #: calculate CBC-MAC for checksum + + chunks = os.path.getsize(file_crypted) / chunk_size + 1 + for i in xrange(chunks): + buf = f.read(chunk_size) + if not buf: + break + + chunk = cipher.decrypt(buf) + df.write(chunk) + + self.pyfile.setProgress(int((100.0 / chunks) * i)) + + # chunk_mac = [iv[0], iv[1], iv[0], iv[1]] + # for i in xrange(0, chunk_size, 16): + # block = chunk[i:i+16] + # if len(block) % 16: + # block += '=' * (16 - (len(block) % 16)) + # block = array.array("I", block) + + # chunk_mac = [chunk_mac[0] ^ a_[0], chunk_mac[1] ^ block[1], chunk_mac[2] ^ block[2], chunk_mac[3] ^ block[3]] + # chunk_mac = aes_cbc_encrypt_a32(chunk_mac, k) + + # file_mac = [file_mac[0] ^ chunk_mac[0], file_mac[1] ^ chunk_mac[1], file_mac[2] ^ chunk_mac[2], file_mac[3] ^ chunk_mac[3]] + # file_mac = aes_cbc_encrypt_a32(file_mac, k) + + self.pyfile.setProgress(100) + + f.close() + df.close() + + # if file_mac[0] ^ file_mac[1], file_mac[2] ^ file_mac[3] != meta_mac: + # os.remove(file_decrypted) + # self.fail(_("Checksum mismatch")) + + os.remove(file_crypted) + self.lastDownload = fs_decode(file_decrypted) + + + def checkError(self, code): + ecode = abs(code) + + if ecode in (9, 16, 21): + self.offline() + + elif ecode in (3, 13, 17, 18, 19): + self.tempOffline() + + elif ecode in (1, 4, 6, 10, 15, 21): + self.retry(5, 30, _("Error code: [%s]") % -ecode) + + else: + self.fail(_("Error code: [%s]") % -ecode) + + + def process(self, pyfile): + pattern = re.match(self.__pattern, pyfile.url).groupdict() + id = pattern['ID'] + key = pattern['KEY'] + public = pattern['TYPE'] == '' + + self.logDebug("ID: %s" % id, "Key: %s" % key, "Type: %s" % ("public" if public else "node")) + + # g is for requesting a download url + # this is similar to the calls in the mega js app, documentation is very bad + if public: + mega = self.api_response(a="g", g=1, p=id, ssl=1)[0] + else: + mega = self.api_response(a="g", g=1, n=id, ssl=1)[0] + + if isinstance(mega, int): + self.checkError(mega) + elif "e" in mega: + self.checkError(mega['e']) + + attr = self.decryptAttr(mega['at'], key) + + pyfile.name = attr['n'] + self.FILE_SUFFIX + pyfile.size = mega['s'] + + # self.req.http.c.setopt(pycurl.SSL_CIPHER_LIST, "RC4-MD5:DEFAULT") + + self.download(mega['g']) + + self.decryptFile(key) + + # Everything is finished and final name can be set + pyfile.name = attr['n'] diff --git a/pyload/plugin/hoster/MegaDebridEu.py b/pyload/plugin/hoster/MegaDebridEu.py new file mode 100644 index 000000000..fd578a83c --- /dev/null +++ b/pyload/plugin/hoster/MegaDebridEu.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- + +from pyload.utils import json_loads +from pyload.plugin.internal.MultiHoster import MultiHoster + + +class MegaDebridEu(MultiHoster): + __name = "MegaDebridEu" + __type = "hoster" + __version = "0.47" + + __pattern = r'http://((?:www\d+\.|s\d+\.)?mega-debrid\.eu|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/download/file/[\w^_]+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """mega-debrid.eu multi-hoster plugin""" + __license = "GPLv3" + __authors = [("D.Ducatel", "dducatel@je-geek.fr")] + + + API_URL = "https://www.mega-debrid.eu/api.php" + + + def api_load(self): + """ + Connexion to the mega-debrid API + Return True if succeed + """ + user, data = self.account.selectAccount() + jsonResponse = self.load(self.API_URL, + get={'action': 'connectUser', 'login': user, 'password': data['password']}) + res = json_loads(jsonResponse) + + if res['response_code'] == "ok": + self.token = res['token'] + return True + else: + return False + + + def handlePremium(self, pyfile): + """ + Debrid a link + Return The debrided link if succeed or original link if fail + """ + if not self.api_load(): + self.error("Unable to connect to remote API") + + jsonResponse = self.load(self.API_URL, + get={'action': 'getLink', 'token': self.token}, + post={'link': pyfile.url}) + + res = json_loads(jsonResponse) + if res['response_code'] == "ok": + self.link = res['debridLink'][1:-1] diff --git a/pyload/plugin/hoster/MegaFilesSe.py b/pyload/plugin/hoster/MegaFilesSe.py new file mode 100644 index 000000000..51667c786 --- /dev/null +++ b/pyload/plugin/hoster/MegaFilesSe.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class MegaFilesSe(DeadHoster): + __name = "MegaFilesSe" + __type = "hoster" + __version = "0.02" + + __pattern = r'http://(?:www\.)?megafiles\.se/\w{12}' + __config = [] + + __description = """MegaFiles.se hoster plugin""" + __license = "GPLv3" + __authors = [("t4skforce", "t4skforce1337[AT]gmail[DOT]com")] diff --git a/pyload/plugin/hoster/MegaRapidCz.py b/pyload/plugin/hoster/MegaRapidCz.py new file mode 100644 index 000000000..d7ed78202 --- /dev/null +++ b/pyload/plugin/hoster/MegaRapidCz.py @@ -0,0 +1,65 @@ +# -*- coding: utf-8 -*- + +import pycurl +import re + +from pyload.network.HTTPRequest import BadHeader +from pyload.network.RequestFactory import getRequest +from pyload.plugin.internal.SimpleHoster import SimpleHoster, parseFileInfo + + +def getInfo(urls): + h = getRequest() + h.c.setopt(pycurl.HTTPHEADER, + ["Accept: text/html", + "User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:25.0) Gecko/20100101 Firefox/25.0"]) + + for url in urls: + html = h.load(url, decode=True) + yield parseFileInfo(MegaRapidCz, url, html) + + +class MegaRapidCz(SimpleHoster): + __name = "MegaRapidCz" + __type = "hoster" + __version = "0.56" + + __pattern = r'http://(?:www\.)?(share|mega)rapid\.cz/soubor/\d+/.+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """MegaRapid.cz hoster plugin""" + __license = "GPLv3" + __authors = [("MikyWoW", "mikywow@seznam.cz"), + ("zoidberg", "zoidberg@mujmail.cz"), + ("stickell", "l.stickell@yahoo.it"), + ("Walter Purcaro", "vuolter@gmail.com")] + + + NAME_PATTERN = r'<h1.*?><span.*?>(?:<a.*?>)?(?P<N>[^<]+)' + SIZE_PATTERN = r'<td class="i">Velikost:</td>\s*<td class="h"><strong>\s*(?P<S>[\d.,]+) (?P<U>[\w^_]+)</strong></td>' + OFFLINE_PATTERN = ur'Nastala chyba 404|Soubor byl smazán' + + CHECK_TRAFFIC = True + + LINK_PREMIUM_PATTERN = r'<a href="(.+?)" title="Stahnout">([^<]+)</a>' + + ERR_LOGIN_PATTERN = ur'<div class="error_div"><strong>Stahovánà je pÅÃstupné pouze pÅihlášenÜm uÅŸivatelům' + ERR_CREDIT_PATTERN = ur'<div class="error_div"><strong>Stahovánà zdarma je moÅŸné jen pÅes náš' + + + def setup(self): + self.chunkLimit = 1 + + + def handlePremium(self, pyfile): + m = re.search(self.LINK_PREMIUM_PATTERN, self.html) + if m: + self.link = m.group(1) + else: + if re.search(self.ERR_LOGIN_PATTERN, self.html): + self.relogin(self.user) + self.retry(wait_time=60, reason=_("User login failed")) + elif re.search(self.ERR_CREDIT_PATTERN, self.html): + self.fail(_("Not enough credit left")) + else: + self.fail(_("Download link not found")) diff --git a/pyload/plugin/hoster/MegaRapidoNet.py b/pyload/plugin/hoster/MegaRapidoNet.py new file mode 100644 index 000000000..1be61f235 --- /dev/null +++ b/pyload/plugin/hoster/MegaRapidoNet.py @@ -0,0 +1,54 @@ +# -*- coding: utf-8 -*- + +import random + +from pyload.plugin.internal.MultiHoster import MultiHoster + + +def random_with_N_digits(n): + rand = "0." + not_zero = 0 + for _i in xrange(1, n + 1): + r = random.randint(0, 9) + if(r > 0): + not_zero += 1 + rand += str(r) + + if not_zero > 0: + return rand + else: + return random_with_N_digits(n) + + +class MegaRapidoNet(MultiHoster): + __name = "MegaRapidoNet" + __type = "hoster" + __version = "0.02" + + __pattern = r'http://(?:www\.)?\w+\.megarapido\.net/\?file=\w+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """MegaRapido.net multi-hoster plugin""" + __license = "GPLv3" + __authors = [("Kagenoshin", "kagenoshin@gmx.ch")] + + + LINK_PREMIUM_PATTERN = r'<\s*?a[^>]*?title\s*?=\s*?["\'].*?download["\'][^>]*?href=["\']([^"\']+)' + + ERROR_PATTERN = r'<\s*?div[^>]*?class\s*?=\s*?["\']?alert-message error.*?>([^<]*)' + + + def handlePremium(self, pyfile): + self.html = self.load("http://megarapido.net/gerar.php", + post={'rand' :random_with_N_digits(16), + 'urllist' : pyfile.url, + 'links' : pyfile.url, + 'exibir' : "normal", + 'usar' : "premium", + 'user' : self.account.getAccountInfo(self.user).get('sid', None), + 'autoreset': ""}) + + if "desloga e loga novamente para gerar seus links" in self.html.lower(): + self.error("You have logged in at another place") + + return super(MegaRapidoNet, self).handlePremium(pyfile) diff --git a/pyload/plugin/hoster/MegacrypterCom.py b/pyload/plugin/hoster/MegacrypterCom.py new file mode 100644 index 000000000..264ad958d --- /dev/null +++ b/pyload/plugin/hoster/MegacrypterCom.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.utils import json_loads, json_dumps + +from pyload.plugin.hoster.MegaCoNz import MegaCoNz + + +class MegacrypterCom(MegaCoNz): + __name = "MegacrypterCom" + __type = "hoster" + __version = "0.22" + + __pattern = r'https?://\w{0,10}\.?megacrypter\.com/[\w!-]+' + + __description = """Megacrypter.com decrypter plugin""" + __license = "GPLv3" + __authors = [("GonzaloSR", "gonzalo@gonzalosr.com")] + + + API_URL = "http://megacrypter.com/api" + FILE_SUFFIX = ".crypted" + + + def api_response(self, **kwargs): + """ Dispatch a call to the api, see megacrypter.com/api_doc """ + self.logDebug("JSON request: " + json_dumps(kwargs)) + res = self.load(self.API_URL, post=json_dumps(kwargs)) + self.logDebug("API Response: " + res) + return json_loads(res) + + + def process(self, pyfile): + # match is guaranteed because plugin was chosen to handle url + node = re.match(self.__pattern, pyfile.url).group(0) + + # get Mega.co.nz link info + info = self.api_response(link=node, m="info") + + # get crypted file URL + dl = self.api_response(link=node, m="dl") + + # TODO: map error codes, implement password protection + # if info['pass'] is True: + # crypted_file_key, md5_file_key = info['key'].split("#") + + key = self.b64_decode(info['key']) + + pyfile.name = info['name'] + self.FILE_SUFFIX + + self.download(dl['url']) + + self.decryptFile(key) + + # Everything is finished and final name can be set + pyfile.name = info['name'] diff --git a/pyload/plugin/hoster/MegareleaseOrg.py b/pyload/plugin/hoster/MegareleaseOrg.py new file mode 100644 index 000000000..65f1242a9 --- /dev/null +++ b/pyload/plugin/hoster/MegareleaseOrg.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class MegareleaseOrg(DeadHoster): + __name = "MegareleaseOrg" + __type = "hoster" + __version = "0.02" + + __pattern = r'https?://(?:www\.)?megarelease\.org/\w{12}' + __config = [] + + __description = """Megarelease.org hoster plugin""" + __license = "GPLv3" + __authors = [("derek3x", "derek3x@vmail.me"), + ("stickell", "l.stickell@yahoo.it")] diff --git a/pyload/plugin/hoster/MegasharesCom.py b/pyload/plugin/hoster/MegasharesCom.py new file mode 100644 index 000000000..ac7a81313 --- /dev/null +++ b/pyload/plugin/hoster/MegasharesCom.py @@ -0,0 +1,109 @@ +# -*- coding: utf-8 -*- + +import re +import time + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class MegasharesCom(SimpleHoster): + __name = "MegasharesCom" + __type = "hoster" + __version = "0.28" + + __pattern = r'http://(?:www\.)?(d\d{2}\.)?megashares\.com/((index\.php)?\?d\d{2}=|dl/)\w+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Megashares.com hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz"), + ("Walter Purcaro", "vuolter@gmail.com")] + + + NAME_PATTERN = r'<h1 class="black xxl"[^>]*title="(?P<N>.+?)">' + SIZE_PATTERN = r'<strong><span class="black">Filesize:</span></strong> (?P<S>[\d.,]+) (?P<U>[\w^_]+)' + OFFLINE_PATTERN = r'<dd class="red">(Invalid Link Request|Link has been deleted|Invalid link)' + + LINK_PATTERN = r'<div id="show_download_button_%d".*?>\s*<a href="(.+?)">' + + PASSPORT_LEFT_PATTERN = r'Your Download Passport is: <.*?>(\w+).*?You have.*?<.*?>.*?([\d.]+) (\w+)' + PASSPORT_RENEW_PATTERN = r'(\d+):<strong>(\d+)</strong>:<strong>(\d+)</strong>' + REACTIVATE_NUM_PATTERN = r'<input[^>]*id="random_num" value="(\d+)" />' + REACTIVATE_PASSPORT_PATTERN = r'<input[^>]*id="passport_num" value="(\w+)" />' + REQUEST_URI_PATTERN = r'var request_uri = "(.+?)";' + NO_SLOTS_PATTERN = r'<dd class="red">All download slots for this link are currently filled' + + + def setup(self): + self.resumeDownload = True + self.multiDL = self.premium + + + def handlePremium(self, pyfile): + self.handleDownload(True) + + + def handleFree(self, pyfile): + if self.NO_SLOTS_PATTERN in self.html: + self.retry(wait_time=5 * 60) + + m = re.search(self.REACTIVATE_PASSPORT_PATTERN, self.html) + if m: + passport_num = m.group(1) + request_uri = re.search(self.REQUEST_URI_PATTERN, self.html).group(1) + + for _i in xrange(5): + random_num = re.search(self.REACTIVATE_NUM_PATTERN, self.html).group(1) + + verifyinput = self.decryptCaptcha("http://d01.megashares.com/index.php", + get={'secgfx': "gfx", 'random_num': random_num}) + + self.logInfo(_("Reactivating passport %s: %s %s") % (passport_num, random_num, verifyinput)) + + res = self.load("http://d01.megashares.com%s" % request_uri, + get={'rs' : "check_passport_renewal", + 'rsargs[]': verifyinput, + 'rsargs[]': random_num, + 'rsargs[]': passport_num, + 'rsargs[]': "replace_sec_pprenewal", + 'rsrnd[]' : str(int(time.time() * 1000))}) + + if 'Thank you for reactivating your passport.' in res: + self.correctCaptcha() + self.retry() + else: + self.invalidCaptcha() + else: + self.fail(_("Failed to reactivate passport")) + + m = re.search(self.PASSPORT_RENEW_PATTERN, self.html) + if m: + time = [int(x) for x in m.groups()] + renew = time[0] + (time[1] * 60) + (time[2] * 60) + self.logDebug("Waiting %d seconds for a new passport" % renew) + self.retry(wait_time=renew, reason=_("Passport renewal")) + + # Check traffic left on passport + m = re.search(self.PASSPORT_LEFT_PATTERN, self.html, re.M | re.S) + if m is None: + self.fail(_("Passport not found")) + + self.logInfo(_("Download passport: %s") % m.group(1)) + data_left = float(m.group(2)) * (2 ** 20) ** {'B': 0, 'KB': 1, 'MB': 2, 'GB': 3}[m.group(3)] + self.logInfo(_("Data left: %s %s (%d MB needed)") % (m.group(2), m.group(3), self.pyfile.size / 1048576)) + + if not data_left: + self.retry(wait_time=600, reason=_("Passport renewal")) + + self.handleDownload(False) + + + def handleDownload(self, premium=False): + # Find download link; + m = re.search(self.LINK_PATTERN % (1 if premium else 2), self.html) + msg = _('%s download URL' % ('Premium' if premium else 'Free')) + if m is None: + self.error(msg) + + self.link = m.group(1) + self.logDebug("%s: %s" % (msg, self.link)) diff --git a/pyload/plugin/hoster/MegauploadCom.py b/pyload/plugin/hoster/MegauploadCom.py new file mode 100644 index 000000000..20acad9a6 --- /dev/null +++ b/pyload/plugin/hoster/MegauploadCom.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class MegauploadCom(DeadHoster): + __name = "MegauploadCom" + __type = "hoster" + __version = "0.31" + + __pattern = r'http://(?:www\.)?megaupload\.com/\?.*&?(d|v)=\w+' + __config = [] + + __description = """Megaupload.com hoster plugin""" + __license = "GPLv3" + __authors = [("spoob", "spoob@pyload.org")] diff --git a/pyload/plugin/hoster/MegavideoCom.py b/pyload/plugin/hoster/MegavideoCom.py new file mode 100644 index 000000000..f50ce4365 --- /dev/null +++ b/pyload/plugin/hoster/MegavideoCom.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class MegavideoCom(DeadHoster): + __name = "MegavideoCom" + __type = "hoster" + __version = "0.21" + + __pattern = r'http://(?:www\.)?megavideo\.com/\?.*&?(d|v)=\w+' + __config = [] + + __description = """Megavideo.com hoster plugin""" + __license = "GPLv3" + __authors = [("jeix", "jeix@hasnomail.de"), + ("mkaay", "mkaay@mkaay.de")] diff --git a/pyload/plugin/hoster/MovReelCom.py b/pyload/plugin/hoster/MovReelCom.py new file mode 100644 index 000000000..fb942c59f --- /dev/null +++ b/pyload/plugin/hoster/MovReelCom.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSHoster import XFSHoster + + +class MovReelCom(XFSHoster): + __name = "MovReelCom" + __type = "hoster" + __version = "1.24" + + __pattern = r'http://(?:www\.)?movreel\.com/\w{12}' + + __description = """MovReel.com hoster plugin""" + __license = "GPLv3" + __authors = [("JorisV83", "jorisv83-pyload@yahoo.com")] + + + LINK_PATTERN = r'<a href="(.+?)">Download Link' diff --git a/pyload/plugin/hoster/MultihostersCom.py b/pyload/plugin/hoster/MultihostersCom.py new file mode 100644 index 000000000..1bb2452bb --- /dev/null +++ b/pyload/plugin/hoster/MultihostersCom.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.hoster.ZeveraCom import ZeveraCom + + +class MultihostersCom(ZeveraCom): + __name = "MultihostersCom" + __type = "hoster" + __version = "0.03" + + __pattern = r'https?://(?:www\.)multihosters\.com/(getFiles\.ashx|Members/download\.ashx)\?.*ourl=.+' + + __description = """Multihosters.com multi-hoster plugin""" + __license = "GPLv3" + __authors = [("tjeh", "tjeh@gmx.net")] diff --git a/pyload/plugin/hoster/MultishareCz.py b/pyload/plugin/hoster/MultishareCz.py new file mode 100644 index 000000000..b19dfba38 --- /dev/null +++ b/pyload/plugin/hoster/MultishareCz.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- + +import random + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class MultishareCz(SimpleHoster): + __name = "MultishareCz" + __type = "hoster" + __version = "0.40" + + __pattern = r'http://(?:www\.)?multishare\.cz/stahnout/(?P<ID>\d+)' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """MultiShare.cz hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + SIZE_REPLACEMENTS = [(' ', '')] + + CHECK_TRAFFIC = True + MULTI_HOSTER = True + + INFO_PATTERN = ur'(?:<li>Název|Soubor): <strong>(?P<N>[^<]+)</strong><(?:/li><li|br)>Velikost: <strong>(?P<S>[^<]+)</strong>' + OFFLINE_PATTERN = ur'<h1>Stáhnout soubor</h1><p><strong>PoÅŸadovanÜ soubor neexistuje.</strong></p>' + + + def handleFree(self, pyfile): + self.download("http://www.multishare.cz/html/download_free.php", get={'ID': self.info['pattern']['ID']}) + + + def handlePremium(self, pyfile): + self.download("http://www.multishare.cz/html/download_premium.php", get={'ID': self.info['pattern']['ID']}) + + + def handleMulti(self, pyfile): + self.html = self.load('http://www.multishare.cz/html/mms_ajax.php', post={"link": pyfile.url}, decode=True) + + self.checkInfo() + + if not self.checkTrafficLeft(): + self.fail(_("Not enough credit left to download file")) + + self.download("http://dl%d.mms.multishare.cz/html/mms_process.php" % round(random.random() * 10000 * random.random()), + get={'u_ID' : self.acc_info['u_ID'], + 'u_hash': self.acc_info['u_hash'], + 'link' : pyfile.url}, + disposition=True) diff --git a/pyload/plugin/hoster/MyfastfileCom.py b/pyload/plugin/hoster/MyfastfileCom.py new file mode 100644 index 000000000..12418670a --- /dev/null +++ b/pyload/plugin/hoster/MyfastfileCom.py @@ -0,0 +1,34 @@ +# -*- coding: utf-8 -*- + +from pyload.utils import json_loads +from pyload.plugin.internal.MultiHoster import MultiHoster + + +class MyfastfileCom(MultiHoster): + __name = "MyfastfileCom" + __type = "hoster" + __version = "0.08" + + __pattern = r'http://\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/dl/' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Myfastfile.com multi-hoster plugin""" + __license = "GPLv3" + __authors = [("stickell", "l.stickell@yahoo.it")] + + + def setup(self): + self.chunkLimit = -1 + + + def handlePremium(self, pyfile): + self.html = self.load('http://myfastfile.com/api.php', + get={'user': self.user, 'pass': self.account.getAccountData(self.user)['password'], + 'link': pyfile.url}) + self.logDebug("JSON data: " + self.html) + + self.html = json_loads(self.html) + if self.html['status'] != 'ok': + self.fail(_("Unable to unrestrict link")) + + self.link = self.html['link'] diff --git a/pyload/plugin/hoster/MystoreTo.py b/pyload/plugin/hoster/MystoreTo.py new file mode 100644 index 000000000..581dec612 --- /dev/null +++ b/pyload/plugin/hoster/MystoreTo.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +# +# Test link: +# http://mystore.to/dl/mxcA50jKfP + +import re + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class MystoreTo(SimpleHoster): + __name = "MystoreTo" + __type = "hoster" + __version = "0.03" + + __pattern = r'https?://(?:www\.)?mystore\.to/dl/.+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Mystore.to hoster plugin""" + __license = "GPLv3" + __authors = [("zapp-brannigan", "")] + + + NAME_PATTERN = r'<h1>(?P<N>.+?)<' + SIZE_PATTERN = r'FILESIZE: (?P<S>[\d\.,]+) (?P<U>[\w^_]+)' + OFFLINE_PATTERN = r'>file not found<' + + + def setup(self): + self.chunkLimit = 1 + self.resumeDownload = True + self.multiDL = True + + + def handleFree(self, pyfile): + try: + fid = re.search(r'wert="(.+?)"', self.html).group(1) + + except AttributeError: + self.error(_("File-ID not found")) + + self.link = self.load("http://mystore.to/api/download", + post={'FID': fid}) diff --git a/pyload/plugin/hoster/MyvideoDe.py b/pyload/plugin/hoster/MyvideoDe.py new file mode 100644 index 000000000..08652cdc9 --- /dev/null +++ b/pyload/plugin/hoster/MyvideoDe.py @@ -0,0 +1,49 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.Hoster import Hoster +from pyload.utils import html_unescape + + +class MyvideoDe(Hoster): + __name = "MyvideoDe" + __type = "hoster" + __version = "0.90" + + __pattern = r'http://(?:www\.)?myvideo\.de/watch/' + + __description = """Myvideo.de hoster plugin""" + __license = "GPLv3" + __authors = [("spoob", "spoob@pyload.org")] + + + def process(self, pyfile): + self.pyfile = pyfile + self.download_html() + pyfile.name = self.get_file_name() + self.download(self.get_file_url()) + + + def download_html(self): + self.html = self.load(self.pyfile.url) + + + def get_file_url(self): + videoId = re.search(r"addVariable\('_videoid','(.*)'\);p.addParam\('quality'", self.html).group(1) + videoServer = re.search("rel='image_src' href='(.*)thumbs/.*' />", self.html).group(1) + file_url = videoServer + videoId + ".flv" + return file_url + + + def get_file_name(self): + file_name_pattern = r"<h1 class='globalHd'>(.*)</h1>" + return html_unescape(re.search(file_name_pattern, self.html).group(1).replace("/", "") + '.flv') + + + def file_exists(self): + self.download_html() + self.load(str(self.pyfile.url), cookies=False, just_header=True) + if self.req.lastEffectiveURL == "http://www.myvideo.de/": + return False + return True diff --git a/pyload/plugin/hoster/NahrajCz.py b/pyload/plugin/hoster/NahrajCz.py new file mode 100644 index 000000000..14f041e17 --- /dev/null +++ b/pyload/plugin/hoster/NahrajCz.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class NahrajCz(DeadHoster): + __name = "NahrajCz" + __type = "hoster" + __version = "0.21" + + __pattern = r'http://(?:www\.)?nahraj\.cz/content/download/.+' + __config = [] + + __description = """Nahraj.cz hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] diff --git a/pyload/plugin/hoster/NarodRu.py b/pyload/plugin/hoster/NarodRu.py new file mode 100644 index 000000000..456baefec --- /dev/null +++ b/pyload/plugin/hoster/NarodRu.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- + +import random +import re + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class NarodRu(SimpleHoster): + __name = "NarodRu" + __type = "hoster" + __version = "0.12" + + __pattern = r'http://(?:www\.)?narod(\.yandex)?\.ru/(disk|start/\d+\.\w+-narod\.yandex\.ru)/(?P<ID>\d+)/.+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Narod.ru hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + NAME_PATTERN = r'<dt class="name">(?:<[^<]*>)*(?P<N>[^<]+)</dt>' + SIZE_PATTERN = r'<dd class="size">(?P<S>\d[^<]*)</dd>' + OFFLINE_PATTERN = r'<title>404</title>|Ѐайл ÑЎалеМ Ñ ÑеÑвОÑа|ÐакПМÑОлÑÑ ÑÑПк Ñ
ÑÐ°ÐœÐµÐœÐžÑ Ñайла\.' + + SIZE_REPLACEMENTS = [(u'ÐÐ', 'KB'), (u'ÐÐ', 'MB'), (u'ÐÐ', 'GB')] + URL_REPLACEMENTS = [("narod.yandex.ru/", "narod.ru/"), + (r"/start/\d+\.\w+-narod\.yandex\.ru/(\d{6,15})/\w+/(\w+)", r"/disk/\1/\2")] + + CAPTCHA_PATTERN = r'<number url="(.*?)">(\w+)</number>' + LINK_FREE_PATTERN = r'<a class="h-link" rel="yandex_bar" href="(.+?)">' + + + def handleFree(self, pyfile): + for _i in xrange(5): + self.html = self.load('http://narod.ru/disk/getcapchaxml/?rnd=%d' % int(random.random() * 777)) + + m = re.search(self.CAPTCHA_PATTERN, self.html) + if m is None: + self.error(_("Captcha")) + + post_data = {"action": "sendcapcha"} + captcha_url, post_data['key'] = m.groups() + post_data['rep'] = self.decryptCaptcha(captcha_url) + + self.html = self.load(pyfile.url, post=post_data, decode=True) + + m = re.search(self.LINK_FREE_PATTERN, self.html) + if m: + self.link = 'http://narod.ru' + m.group(1) + self.correctCaptcha() + break + + elif u'<b class="error-msg"><strong>ÐÑОблОÑÑ?</strong>' in self.html: + self.invalidCaptcha() + + else: + self.error(_("Download link")) + + else: + self.fail(_("No valid captcha code entered")) diff --git a/pyload/plugin/hoster/NetloadIn.py b/pyload/plugin/hoster/NetloadIn.py new file mode 100644 index 000000000..9e05b02c5 --- /dev/null +++ b/pyload/plugin/hoster/NetloadIn.py @@ -0,0 +1,297 @@ +# -*- coding: utf-8 -*- + +import re +import time +import urlparse + +from pyload.network.RequestFactory import getURL +from pyload.plugin.Hoster import Hoster +from pyload.plugin.Plugin import chunks +from pyload.plugin.captcha.ReCaptcha import ReCaptcha + + +def getInfo(urls): + ## returns list of tupels (name, size (in bytes), status (see database.File), url) + + apiurl = "http://api.netload.in/info.php" + id_regex = re.compile(NetloadIn.__pattern) + urls_per_query = 80 + + for chunk in chunks(urls, urls_per_query): + ids = "" + for url in chunk: + match = id_regex.search(url) + if match: + ids = ids + match.group('ID') + ";" + + api = getURL(apiurl, + get={'auth' : "Zf9SnQh9WiReEsb18akjvQGqT0I830e8", + 'bz' : 1, + 'md5' : 1, + 'file_id': ids}, + decode=True) + + if api is None or len(api) < 10: + self.logDebug("Prefetch failed") + return + + if api.find("unknown_auth") >= 0: + self.logDebug("Outdated auth code") + return + + result = [] + + for i, r in enumerate(api.splitlines()): + try: + tmp = r.split(";") + + try: + size = int(tmp[2]) + except Exception: + size = 0 + + result.append((tmp[1], size, 2 if tmp[3] == "online" else 1, chunk[i] )) + + except Exception: + self.logDebug("Error while processing response: %s" % r) + + yield result + + +class NetloadIn(Hoster): + __name = "NetloadIn" + __type = "hoster" + __version = "0.49" + + __pattern = r'https?://(?:www\.)?netload\.in/(?P<PATH>datei|index\.php\?id=10&file_id=)(?P<ID>\w+)' + + __description = """Netload.in hoster plugin""" + __license = "GPLv3" + __authors = [("spoob", "spoob@pyload.org"), + ("RaNaN", "ranan@pyload.org"), + ("Gregy", "gregy@gregy.cz")] + + + RECAPTCHA_KEY = "6LcLJMQSAAAAAJzquPUPKNovIhbK6LpSqCjYrsR1" + + + def setup(self): + self.multiDL = self.resumeDownload = self.premium + + + def process(self, pyfile): + self.url = pyfile.url + + self.prepare() + + pyfile.setStatus("downloading") + + self.proceed(self.url) + + + def prepare(self): + self.api_load() + + if self.api_data and self.api_data['filename']: + self.pyfile.name = self.api_data['filename'] + + if self.premium: + self.logDebug("Use Premium Account") + + settings = self.load("http://www.netload.in/index.php", get={'id': 2, 'lang': "en"}) + + if '<option value="2" selected="selected">Direkter Download' in settings: + self.logDebug("Using direct download") + return True + else: + self.logDebug("Direct downloads not enabled. Parsing html for a download URL") + + if self.download_html(): + return True + else: + self.fail(_("Failed")) + return False + + + def api_load(self, n=0): + url = self.url + id_regex = re.compile(self.__pattern) + match = id_regex.search(url) + + if match: + # normalize url + self.url = 'http://www.netload.in/datei%s.htm' % match.group('ID') + self.logDebug("URL: %s" % self.url) + else: + self.api_data = False + return + + apiurl = "http://api.netload.in/info.php" + html = self.load(apiurl, cookies=False, + get={"file_id": match.group('ID'), "auth": "Zf9SnQh9WiReEsb18akjvQGqT0I830e8", "bz": "1", + "md5": "1"}, decode=True).strip() + if not html and n <= 3: + self.setWait(2) + self.wait() + self.api_load(n + 1) + return + + self.logDebug("APIDATA: " + html) + + self.api_data = {} + + if html and ";" in html and html not in ("unknown file_data", "unknown_server_data", "No input file specified."): + lines = html.split(";") + self.api_data['exists'] = True + self.api_data['fileid'] = lines[0] + self.api_data['filename'] = lines[1] + self.api_data['size'] = lines[2] + self.api_data['status'] = lines[3] + + if self.api_data['status'] == "online": + self.api_data['checksum'] = lines[4].strip() + else: + self.api_data = False #: check manually since api data is useless sometimes + + if lines[0] == lines[1] and lines[2] == "0": #: useless api data + self.api_data = False + else: + self.api_data = False + + + def final_wait(self, page): + wait_time = self.get_wait_time.time(page) + + self.setWait(wait_time) + + self.logDebug("Final wait %d seconds" % wait_time) + + self.wait() + + self.url = self.get_file_url(page) + + + def check_free_wait(self, page): + if ">An access request has been made from IP address <" in page: + self.wantReconnect = True + self.setWait(self.get_wait_time.time(page) or 30) + self.wait() + return True + else: + return False + + + def download_html(self): + page = self.load(self.url, decode=True) + + if "/share/templates/download_hddcrash.tpl" in page: + self.logError(_("Netload HDD Crash")) + self.fail(_("File temporarily not available")) + + if not self.api_data: + self.logDebug("API Data may be useless, get details from html page") + + if "* The file was deleted" in page: + self.offline() + + name = re.search(r'class="dl_first_filename">([^<]+)', page, re.M) + # the found filename is not truncated + if name: + name = name.group(1).strip() + if not name.endswith(".."): + self.pyfile.name = name + + captchawaited = False + + for i in xrange(5): + if not page: + page = self.load(self.url) + t = time.time() + 30 + + if "/share/templates/download_hddcrash.tpl" in page: + self.logError(_("Netload HDD Crash")) + self.fail(_("File temporarily not available")) + + self.logDebug("Try number %d " % i) + + if ">Your download is being prepared.<" in page: + self.logDebug("We will prepare your download") + self.final_wait(page) + return True + + self.logDebug("Trying to find captcha") + + try: + url_captcha_html = re.search(r'(index.php\?id=10&.*&captcha=1)', page).group(1).replace("amp;", "") + + except Exception, e: + self.logDebug("Exception during Captcha regex: %s" % e.message) + page = None + + else: + url_captcha_html = urlparse.urljoin("http://netload.in/", url_captcha_html) + break + + self.html = self.load(url_captcha_html) + + recaptcha = ReCaptcha(self) + + for _i in xrange(5): + response, challenge = recaptcha.challenge(self.RECAPTCHA_KEY) + + response_page = self.load("http://www.netload.in/index.php?id=10", + post={'captcha_check' : '1', + 'recaptcha_challenge_field': challenge, + 'recaptcha_response_field' : response, + 'file_id' : self.api_data['fileid'], + 'Download_Next' : ''}) + if "Orange_Link" in response_page: + break + + if self.check_free_wait(response_page): + self.logDebug("Had to wait for next free slot, trying again") + return self.download_html() + + else: + download_url = self.get_file_url(response_page) + self.logDebug("Download URL after get_file: " + download_url) + if not download_url.startswith("http://"): + self.error(_("Download url: %s") % download_url) + self.wait() + + self.url = download_url + return True + + + def get_file_url(self, page): + try: + file_url_pattern = r'<a class="Orange_Link" href="(http://.+)".?>Or click here' + attempt = re.search(file_url_pattern, page) + if attempt: + return attempt.group(1) + else: + self.logDebug("Backup try for final link") + file_url_pattern = r'<a href="(.+)" class="Orange_Link">Click here' + attempt = re.search(file_url_pattern, page) + return "http://netload.in/" + attempt.group(1) + + except Exception, e: + self.logDebug("Getting final link failed", e.message) + return None + + + def get_wait_time.time(self, page): + return int(re.search(r"countdown\((.+),'change\(\)'\)", page).group(1)) / 100 + + + def proceed(self, url): + self.download(url, disposition=True) + + check = self.checkDownload({'empty' : re.compile(r'^$'), + 'offline': re.compile("The file was deleted")}) + if check == "empty": + self.logInfo(_("Downloaded File was empty")) + self.retry() + + elif check == "offline": + self.offline() diff --git a/pyload/plugin/hoster/NitroflareCom.py b/pyload/plugin/hoster/NitroflareCom.py new file mode 100644 index 000000000..a93ff67ec --- /dev/null +++ b/pyload/plugin/hoster/NitroflareCom.py @@ -0,0 +1,101 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.captcha.ReCaptcha import ReCaptcha +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class NitroflareCom(SimpleHoster): + __name = "NitroflareCom" + __type = "hoster" + __version = "0.09" + + __pattern = r'https?://(?:www\.)?nitroflare\.com/view/(?P<ID>[\w^_]+)' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Nitroflare.com hoster plugin""" + __license = "GPLv3" + __authors = [("sahil", "sahilshekhawat01@gmail.com"), + ("Walter Purcaro", "vuolter@gmail.com"), + ("Stickell", "l.stickell@yahoo.it")] + + # URL_REPLACEMENTS = [("http://", "https://")] + + INFO_PATTERN = r'title="(?P<N>.+?)".+>(?P<S>[\d.,]+) (?P<U>[\w^_]+)' + OFFLINE_PATTERN = r'>File doesn\'t exist' + + LINK_FREE_PATTERN = r'(https?://[\w\-]+\.nitroflare\.com/.+?)"' + + RECAPTCHA_KEY = "6Lenx_USAAAAAF5L1pmTWvWcH73dipAEzNnmNLgy" + + PREMIUM_ONLY_PATTERN = r'This file is available with Premium only' + WAIT_PATTERN = r'You have to wait .+?<' + ERROR_PATTERN = r'downloading is not possible' + + + def checkErrors(self): + if not self.html: + return + + if not self.premium and re.search(self.PREMIUM_ONLY_PATTERN, self.html): + self.fail(_("Link require a premium account to be handled")) + + elif hasattr(self, 'WAIT_PATTERN'): + m = re.search(self.WAIT_PATTERN, self.html) + if m: + wait_time = sum(int(v) * {"hr": 3600, "hour": 3600, "min": 60, "sec": 1}[u.lower()] for v, u in + re.findall(r'(\d+)\s*(hr|hour|min|sec)', m.group(0), re.I)) + self.wait(wait_time, wait_time > 300) + return + + elif hasattr(self, 'ERROR_PATTERN'): + m = re.search(self.ERROR_PATTERN, self.html) + if m: + errmsg = self.info['error'] = m.group(1) + self.error(errmsg) + + self.info.pop('error', None) + + + def handleFree(self, pyfile): + # used here to load the cookies which will be required later + self.load(pyfile.url, post={'goToFreePage': ""}) + + self.load("http://nitroflare.com/ajax/setCookie.php", post={'fileId': self.info['pattern']['ID']}) + self.html = self.load("http://nitroflare.com/ajax/freeDownload.php", + post={'method': "startTimer", 'fileId': self.info['pattern']['ID']}) + + self.checkErrors() + + try: + js_file = self.load("http://nitroflare.com/js/downloadFree.js?v=1.0.1") + var_time = re.search("var time = (\\d+);", js_file) + wait_time = int(var_time.groups()[0]) + + except Exception: + wait_time = 60 + + self.wait(wait_time) + + recaptcha = ReCaptcha(self) + response, challenge = recaptcha.challenge(self.RECAPTCHA_KEY) + + self.html = self.load("http://nitroflare.com/ajax/freeDownload.php", + post={'method' : "fetchDownload", + 'recaptcha_challenge_field': challenge, + 'recaptcha_response_field' : response}) + + if "The captcha wasn't entered correctly" in self.html: + self.logWarning("The captcha wasn't entered correctly") + return + + if "You have to fill the captcha" in self.html: + self.logWarning("Captcha unfilled") + return + + m = re.search(self.LINK_FREE_PATTERN, self.html) + if m: + self.link = m.group(1) + else: + self.logError("Unable to detect direct link") diff --git a/pyload/plugin/hoster/NoPremiumPl.py b/pyload/plugin/hoster/NoPremiumPl.py new file mode 100644 index 000000000..be1e4794e --- /dev/null +++ b/pyload/plugin/hoster/NoPremiumPl.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- + +from pyload.utils import json_loads +from pyload.plugin.internal.MultiHoster import MultiHoster + + +class NoPremiumPl(MultiHoster): + __name = "NoPremiumPl" + __type = "hoster" + __version = "0.02" + + __pattern = r'https?://direct\.nopremium\.pl.+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """NoPremium.pl multi-hoster plugin""" + __license = "GPLv3" + __authors = [("goddie", "dev@nopremium.pl")] + + + API_URL = "http://crypt.nopremium.pl" + + API_QUERY = {'site' : "nopremium", + 'output' : "json", + 'username': "", + 'password': "", + 'url' : ""} + + ERROR_CODES = {0 : "[%s] Incorrect login credentials", + 1 : "[%s] Not enough transfer to download - top-up your account", + 2 : "[%s] Incorrect / dead link", + 3 : "[%s] Error connecting to hosting, try again later", + 9 : "[%s] Premium account has expired", + 15: "[%s] Hosting no longer supported", + 80: "[%s] Too many incorrect login attempts, account blocked for 24h"} + + + def prepare(self): + super(NoPremiumPl, self).prepare() + + data = self.account.getAccountData(self.user) + + self.usr = data['usr'] + self.pwd = data['pwd'] + + + def runFileQuery(self, url, mode=None): + query = self.API_QUERY.copy() + + query['username'] = self.usr + query['password'] = self.pwd + query['url'] = url + + if mode == "fileinfo": + query['check'] = 2 + query['loc'] = 1 + + self.logDebug(query) + + return self.load(self.API_URL, post=query) + + + def handleFree(self, pyfile): + try: + data = self.runFileQuery(pyfile.url, 'fileinfo') + + except Exception: + self.logDebug("runFileQuery error") + self.tempOffline() + + try: + parsed = json_loads(data) + + except Exception: + self.logDebug("loads error") + self.tempOffline() + + self.logDebug(parsed) + + if "errno" in parsed.keys(): + if parsed['errno'] in self.ERROR_CODES: + # error code in known + self.fail(self.ERROR_CODES[parsed['errno']] % self.getClassName()) + else: + # error code isn't yet added to plugin + self.fail( + parsed['errstring'] + or _("Unknown error (code: %s)") % parsed['errno'] + ) + + if "sdownload" in parsed: + if parsed['sdownload'] == "1": + self.fail( + _("Download from %s is possible only using NoPremium.pl website \ + directly") % parsed['hosting']) + + pyfile.name = parsed['filename'] + pyfile.size = parsed['filesize'] + + try: + self.link = self.runFileQuery(pyfile.url, 'filedownload') + + except Exception: + self.logDebug("runFileQuery error #2") + self.tempOffline() diff --git a/pyload/plugin/hoster/NosuploadCom.py b/pyload/plugin/hoster/NosuploadCom.py new file mode 100644 index 000000000..af79da22b --- /dev/null +++ b/pyload/plugin/hoster/NosuploadCom.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.internal.XFSHoster import XFSHoster + + +class NosuploadCom(XFSHoster): + __name = "NosuploadCom" + __type = "hoster" + __version = "0.31" + + __pattern = r'http://(?:www\.)?nosupload\.com/\?d=\w{12}' + + __description = """Nosupload.com hoster plugin""" + __license = "GPLv3" + __authors = [("igel", "igelkun@myopera.com")] + + + SIZE_PATTERN = r'<p><strong>Size:</strong> (?P<S>[\d.,]+) (?P<U>[\w^_]+)</p>' + LINK_PATTERN = r'<a class="select" href="(http://.+?)">Download</a>' + + WAIT_PATTERN = r'Please wait.*?>(\d+)</span>' + + + def getDownloadLink(self): + # stage1: press the "Free Download" button + data = self.getPostParameters() + self.html = self.load(self.pyfile.url, post=data, decode=True) + + # stage2: wait some time and press the "Download File" button + data = self.getPostParameters() + wait_time = re.search(self.WAIT_PATTERN, self.html, re.M | re.S).group(1) + self.logDebug("Hoster told us to wait %s seconds" % wait_time) + self.wait(wait_time) + self.html = self.load(self.pyfile.url, post=data, decode=True) + + # stage3: get the download link + return re.search(self.LINK_PATTERN, self.html, re.S).group(1) diff --git a/pyload/plugin/hoster/NovafileCom.py b/pyload/plugin/hoster/NovafileCom.py new file mode 100644 index 000000000..f76d77269 --- /dev/null +++ b/pyload/plugin/hoster/NovafileCom.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +# +# Test links: +# http://novafile.com/vfun4z6o2cit +# http://novafile.com/s6zrr5wemuz4 + +from pyload.plugin.internal.XFSHoster import XFSHoster + + +class NovafileCom(XFSHoster): + __name = "NovafileCom" + __type = "hoster" + __version = "0.05" + + __pattern = r'http://(?:www\.)?novafile\.com/\w{12}' + + __description = """Novafile.com hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz"), + ("stickell", "l.stickell@yahoo.it")] + + + ERROR_PATTERN = r'class="alert.+?alert-separate".*?>\s*(?:<p>)?(.*?)\s*</' + WAIT_PATTERN = r'<p>Please wait <span id="count".*?>(\d+)</span> seconds</p>' + + LINK_PATTERN = r'<a href="(http://s\d+\.novafile\.com/.*?)" class="btn btn-green">Download File</a>' diff --git a/pyload/plugin/hoster/NowDownloadSx.py b/pyload/plugin/hoster/NowDownloadSx.py new file mode 100644 index 000000000..967c14e81 --- /dev/null +++ b/pyload/plugin/hoster/NowDownloadSx.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.internal.SimpleHoster import SimpleHoster +from pyload.utils import fixup + + +class NowDownloadSx(SimpleHoster): + __name = "NowDownloadSx" + __type = "hoster" + __version = "0.09" + + __pattern = r'http://(?:www\.)?(nowdownload\.[a-zA-Z]{2,}/(dl/|download\.php.+?id=|mobile/(#/files/|.+?id=))|likeupload\.org/)\w+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """NowDownload.sx hoster plugin""" + __license = "GPLv3" + __authors = [("godofdream", "soilfiction@gmail.com"), + ("Walter Purcaro", "vuolter@gmail.com")] + + + INFO_PATTERN = r'Downloading</span> <br> (?P<N>.*) (?P<S>[\d.,]+) (?P<U>[\w^_]+) </h4>' + OFFLINE_PATTERN = r'>This file does not exist' + + TOKEN_PATTERN = r'"(/api/token\.php\?token=\w+)"' + CONTINUE_PATTERN = r'"(/dl2/\w+/\w+)"' + WAIT_PATTERN = r'\.countdown\(\{until: \+(\d+),' + LINK_FREE_PATTERN = r'(http://s\d+\.coolcdn\.info/nowdownload/.+?)["\']' + + NAME_REPLACEMENTS = [("&#?\w+;", fixup), (r'<.*?>', '')] + + + def setup(self): + self.resumeDownload = True + self.multiDL = True + self.chunkLimit = -1 + + + def handleFree(self, pyfile): + tokenlink = re.search(self.TOKEN_PATTERN, self.html) + continuelink = re.search(self.CONTINUE_PATTERN, self.html) + if tokenlink is None or continuelink is None: + self.error() + + m = re.search(self.WAIT_PATTERN, self.html) + if m: + wait = int(m.group(1)) + else: + wait = 60 + + baseurl = "http://www.nowdownload.at" + self.html = self.load(baseurl + str(tokenlink.group(1))) + self.wait(wait) + + self.html = self.load(baseurl + str(continuelink.group(1))) + + m = re.search(self.LINK_FREE_PATTERN, self.html) + if m is None: + self.error(_("Download link not found")) + + self.link = m.group(1) diff --git a/pyload/plugin/hoster/NowVideoSx.py b/pyload/plugin/hoster/NowVideoSx.py new file mode 100644 index 000000000..bfa186e60 --- /dev/null +++ b/pyload/plugin/hoster/NowVideoSx.py @@ -0,0 +1,42 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class NowVideoSx(SimpleHoster): + __name = "NowVideoSx" + __type = "hoster" + __version = "0.12" + + __pattern = r'http://(?:www\.)?nowvideo\.[a-zA-Z]{2,}/(video/|mobile/(#/videos/|.+?id=))(?P<ID>\w+)' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """NowVideo.sx hoster plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + URL_REPLACEMENTS = [(__pattern + ".*", r'http://www.nowvideo.sx/video/\g<ID>')] + + NAME_PATTERN = r'<h4>(?P<N>.+?)<' + OFFLINE_PATTERN = r'>This file no longer exists' + + LINK_FREE_PATTERN = r'<source src="(.+?)"' + LINK_PREMIUM_PATTERN = r'<div id="content_player" >\s*<a href="(.+?)"' + + + def setup(self): + self.resumeDownload = True + self.multiDL = True + + + def handleFree(self, pyfile): + self.html = self.load("http://www.nowvideo.sx/mobile/video.php", get={'id': self.info['pattern']['ID']}) + + m = re.search(self.LINK_FREE_PATTERN, self.html) + if m is None: + self.error(_("Free download link not found")) + + self.link = m.group(1) diff --git a/pyload/plugin/hoster/OboomCom.py b/pyload/plugin/hoster/OboomCom.py new file mode 100644 index 000000000..5b9b11485 --- /dev/null +++ b/pyload/plugin/hoster/OboomCom.py @@ -0,0 +1,145 @@ +# -*- coding: utf-8 -*- +# +# Test links: +# https://www.oboom.com/B7CYZIEB/10Mio.dat + +import re + +from pyload.utils import json_loads +from pyload.plugin.Hoster import Hoster +from pyload.plugin.captcha.ReCaptcha import ReCaptcha + + +class OboomCom(Hoster): + __name = "OboomCom" + __type = "hoster" + __version = "0.31" + + __pattern = r'https?://(?:www\.)?oboom\.com/(#(id=|/)?)?(?P<ID>\w{8})' + + __description = """oboom.com hoster plugin""" + __license = "GPLv3" + __authors = [("stanley", "stanley.foerster@gmail.com")] + + + RECAPTCHA_KEY = "6LdqpO0SAAAAAJGHXo63HyalP7H4qlRs_vff0kJX" + + + def setup(self): + self.chunkLimit = 1 + self.multiDL = self.resumeDownload = self.premium + + + def process(self, pyfile): + self.pyfile.url.replace(".com/#id=", ".com/#") + self.pyfile.url.replace(".com/#/", ".com/#") + self.getFileId(self.pyfile.url) + self.getSessionToken() + self.getFileInfo(self.sessionToken, self.fileId) + self.pyfile.name = self.fileName + self.pyfile.size = self.fileSize + if not self.premium: + self.solveCaptcha() + self.getDownloadTicket() + self.download("https://%s/1.0/dlh" % self.downloadDomain, get={"ticket": self.downloadTicket, "http_errors": 0}) + + + def loadUrl(self, url, get=None): + if get is None: + get = dict() + return json_loads(self.load(url, get, decode=True)) + + + def getFileId(self, url): + self.fileId = re.match(OboomCom.__pattern, url).group('ID') + + + def getSessionToken(self): + if self.premium: + accountInfo = self.account.getAccountInfo(self.user, True) + if "session" in accountInfo: + self.sessionToken = accountInfo['session'] + else: + self.fail(_("Could not retrieve premium session")) + else: + apiUrl = "https://www.oboom.com/1.0/guestsession" + result = self.loadUrl(apiUrl) + if result[0] == 200: + self.sessionToken = result[1] + else: + self.fail(_("Could not retrieve token for guest session. Error code: %s") % result[0]) + + + def solveCaptcha(self): + recaptcha = ReCaptcha(self) + + for _i in xrange(5): + response, challenge = recaptcha.challenge(self.RECAPTCHA_KEY) + apiUrl = "https://www.oboom.com/1.0/download/ticket" + params = {"recaptcha_challenge_field": challenge, + "recaptcha_response_field": response, + "download_id": self.fileId, + "token": self.sessionToken} + result = self.loadUrl(apiUrl, params) + + if result[0] == 200: + self.downloadToken = result[1] + self.downloadAuth = result[2] + self.correctCaptcha() + self.setWait(30) + self.wait() + break + + elif result[0] == 400: + if result[1] == "incorrect-captcha-sol": + self.invalidCaptcha() + elif result[1] == "captcha-timeout": + self.invalidCaptcha() + elif result[1] == "forbidden": + self.retry(5, 15 * 60, _("Service unavailable")) + + elif result[0] == 403: + if result[1] == -1: #: another download is running + self.setWait(15 * 60) + else: + self.setWait(result[1], True) + self.wait() + self.retry(5) + else: + self.invalidCaptcha() + self.fail(_("Received invalid captcha 5 times")) + + + def getFileInfo(self, token, fileId): + apiUrl = "https://api.oboom.com/1.0/info" + params = {"token": token, "items": fileId, "http_errors": 0} + + result = self.loadUrl(apiUrl, params) + if result[0] == 200: + item = result[1][0] + if item['state'] == "online": + self.fileSize = item['size'] + self.fileName = item['name'] + else: + self.offline() + else: + self.fail(_("Could not retrieve file info. Error code %s: %s") % (result[0], result[1])) + + + def getDownloadTicket(self): + apiUrl = "https://api.oboom.com/1/dl" + params = {"item": self.fileId, "http_errors": 0} + if self.premium: + params['token'] = self.sessionToken + else: + params['token'] = self.downloadToken + params['auth'] = self.downloadAuth + + result = self.loadUrl(apiUrl, params) + if result[0] == 200: + self.downloadDomain = result[1] + self.downloadTicket = result[2] + elif result[0] == 421: + self.retry(wait_time=result[2] + 60, reason=_("Connection limit exceeded")) + else: + self.fail(_("Could not retrieve download ticket. Error code: %s") % result[0]) diff --git a/pyload/plugin/hoster/OneFichierCom.py b/pyload/plugin/hoster/OneFichierCom.py new file mode 100644 index 000000000..1fc89db10 --- /dev/null +++ b/pyload/plugin/hoster/OneFichierCom.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class OneFichierCom(SimpleHoster): + __name = "OneFichierCom" + __type = "hoster" + __version = "0.84" + + __pattern = r'https?://(?:www\.)?(?:(?P<ID1>\w+)\.)?(?P<HOST>1fichier\.com|alterupload\.com|cjoint\.net|d(es)?fichiers\.com|dl4free\.com|megadl\.fr|mesfichiers\.org|piecejointe\.net|pjointe\.com|tenvoi\.com)(?:/\?(?P<ID2>\w+))?' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """1fichier.com hoster plugin""" + __license = "GPLv3" + __authors = [("fragonib", "fragonib[AT]yahoo[DOT]es"), + ("the-razer", "daniel_ AT gmx DOT net"), + ("zoidberg", "zoidberg@mujmail.cz"), + ("imclem", ""), + ("stickell", "l.stickell@yahoo.it"), + ("Elrick69", "elrick69[AT]rocketmail[DOT]com"), + ("Walter Purcaro", "vuolter@gmail.com"), + ("Ludovic Lehmann", "ludo.lehmann@gmail.com")] + + + COOKIES = [("1fichier.com", "LG", "en")] + DISPOSITION = False #: Remove in 0.4.10 + + NAME_PATTERN = r'>FileName :</td>\s*<td.*>(?P<N>.+?)<' + SIZE_PATTERN = r'>Size :</td>\s*<td.*>(?P<S>[\d.,]+) (?P<U>[\w^_]+)' + OFFLINE_PATTERN = r'File not found !\s*<' + + WAIT_PATTERN = r'>You must wait \d+ minutes' + + + def setup(self): + self.multiDL = self.premium + self.resumeDownload = True + + + def handleFree(self, pyfile): + id = self.info['pattern']['ID1'] or self.info['pattern']['ID2'] + url, inputs = self.parseHtmlForm('action="https://1fichier.com/\?%s' % id) + + if not url: + self.fail(_("Download link not found")) + + if "pass" in inputs: + inputs['pass'] = self.getPassword() + + inputs['submit'] = "Download" + + self.download(url, post=inputs) + + + def handlePremium(self, pyfile): + self.download(pyfile.url, post={'dl': "Download", 'did': 0}) diff --git a/pyload/plugin/hoster/OronCom.py b/pyload/plugin/hoster/OronCom.py new file mode 100644 index 000000000..29ca267a0 --- /dev/null +++ b/pyload/plugin/hoster/OronCom.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class OronCom(DeadHoster): + __name = "OronCom" + __type = "hoster" + __version = "0.14" + + __pattern = r'https?://(?:www\.)?oron\.com/\w{12}' + __config = [] + + __description = """Oron.com hoster plugin""" + __license = "GPLv3" + __authors = [("chrox", "chrox@pyload.org"), + ("DHMH", "DHMH@pyload.org")] diff --git a/pyload/plugin/hoster/OverLoadMe.py b/pyload/plugin/hoster/OverLoadMe.py new file mode 100644 index 000000000..c9ee5e653 --- /dev/null +++ b/pyload/plugin/hoster/OverLoadMe.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- + +from pyload.utils import json_loads +from pyload.plugin.internal.MultiHoster import MultiHoster +from pyload.utils import parseFileSize + + +class OverLoadMe(MultiHoster): + __name = "OverLoadMe" + __type = "hoster" + __version = "0.11" + + __pattern = r'https?://.*overload\.me/.+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Over-Load.me multi-hoster plugin""" + __license = "GPLv3" + __authors = [("marley", "marley@over-load.me")] + + + def setup(self): + self.chunkLimit = 5 + + + def handlePremium(self, pyfile): + https = "https" if self.getConfig('ssl') else "http" + data = self.account.getAccountData(self.user) + page = self.load(https + "://api.over-load.me/getdownload.php", + get={'auth': data['password'], + 'link': pyfile.url}) + + data = json_loads(page) + + self.logDebug(data) + + if data['error'] == 1: + self.logWarning(data['msg']) + self.tempOffline() + else: + if pyfile.name and pyfile.name.endswith('.tmp') and data['filename']: + pyfile.name = data['filename'] + pyfile.size = parseFileSize(data['filesize']) + + http_repl = ["http://", "https://"] + self.link = data['downloadlink'].replace(*http_repl if self.getConfig('ssl') else http_repl[::-1]) diff --git a/pyload/plugin/hoster/PandaplaNet.py b/pyload/plugin/hoster/PandaplaNet.py new file mode 100644 index 000000000..dc23ef6a0 --- /dev/null +++ b/pyload/plugin/hoster/PandaplaNet.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class PandaplaNet(DeadHoster): + __name = "PandaplaNet" + __type = "hoster" + __version = "0.03" + + __pattern = r'http://(?:www\.)?pandapla\.net/\w{12}' + __config = [] + + __description = """Pandapla.net hoster plugin""" + __license = "GPLv3" + __authors = [("t4skforce", "t4skforce1337[AT]gmail[DOT]com")] diff --git a/pyload/plugin/hoster/PornhostCom.py b/pyload/plugin/hoster/PornhostCom.py new file mode 100644 index 000000000..103882166 --- /dev/null +++ b/pyload/plugin/hoster/PornhostCom.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.Hoster import Hoster + + +class PornhostCom(Hoster): + __name = "PornhostCom" + __type = "hoster" + __version = "0.20" + + __pattern = r'http://(?:www\.)?pornhost\.com/(\d+/\d+\.html|\d+)' + + __description = """Pornhost.com hoster plugin""" + __license = "GPLv3" + __authors = [("jeix", "jeix@hasnomail.de")] + + + def process(self, pyfile): + self.download_html() + if not self.file_exists(): + self.offline() + + pyfile.name = self.get_file_name() + self.download(self.get_file_url()) + + + # Old interface + + + def download_html(self): + url = self.pyfile.url + self.html = self.load(url) + + + def get_file_url(self): + """ returns the absolute downloadable filepath + """ + if not self.html: + self.download_html() + + url = re.search(r'download this file</label>.*?<a href="(.*?)"', self.html) + if url is None: + url = re.search(r'"(http://dl\d+\.pornhost\.com/files/.*?/.*?/.*?/.*?/.*?/.*?\..*?)"', self.html) + if url is None: + url = re.search(r'width: 894px; height: 675px">.*?<img src="(.*?)"', self.html) + if url is None: + url = re.search(r'"http://file\d+\.pornhost\.com/\d+/.*?"', + self.html) #: TODO: fix this one since it doesn't match + + return url.group(1).strip() + + + def get_file_name(self): + if not self.html: + self.download_html() + + name = re.search(r'<title>pornhost\.com - free file hosting with a twist - gallery(.*?)</title>', self.html) + if name is None: + name = re.search(r'id="url" value="http://www\.pornhost\.com/(.*?)/"', self.html) + if name is None: + name = re.search(r'<title>pornhost\.com - free file hosting with a twist -(.*?)</title>', self.html) + if name is None: + name = re.search(r'"http://file\d+\.pornhost\.com/.*?/(.*?)"', self.html) + + name = name.group(1).strip() + ".flv" + + return name + + + def file_exists(self): + """ returns True or False + """ + if not self.html: + self.download_html() + + if re.search(r'gallery not found|You will be redirected to', self.html): + return False + else: + return True diff --git a/pyload/plugin/hoster/PornhubCom.py b/pyload/plugin/hoster/PornhubCom.py new file mode 100644 index 000000000..c7bfa339e --- /dev/null +++ b/pyload/plugin/hoster/PornhubCom.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.Hoster import Hoster + + +class PornhubCom(Hoster): + __name = "PornhubCom" + __type = "hoster" + __version = "0.50" + + __pattern = r'http://(?:www\.)?pornhub\.com/view_video\.php\?viewkey=\w+' + + __description = """Pornhub.com hoster plugin""" + __license = "GPLv3" + __authors = [("jeix", "jeix@hasnomail.de")] + + + def process(self, pyfile): + self.download_html() + if not self.file_exists(): + self.offline() + + pyfile.name = self.get_file_name() + self.download(self.get_file_url()) + + + def download_html(self): + url = self.pyfile.url + self.html = self.load(url) + + + def get_file_url(self): + """ returns the absolute downloadable filepath + """ + if not self.html: + self.download_html() + + url = "http://www.pornhub.com//gateway.php" + video_id = self.pyfile.url.split('=')[-1] + # thanks to jD team for this one v + post_data = "\x00\x03\x00\x00\x00\x01\x00\x0c\x70\x6c\x61\x79\x65\x72\x43\x6f\x6e\x66\x69\x67\x00\x02\x2f\x31\x00\x00\x00\x44\x0a\x00\x00\x00\x03\x02\x00" + post_data += chr(len(video_id)) + post_data += video_id + post_data += "\x02\x00\x02\x2d\x31\x02\x00\x20" + post_data += "add299463d4410c6d1b1c418868225f7" + + content = self.load(url, post=str(post_data)) + + new_content = "" + for x in content: + if ord(x) < 32 or ord(x) > 176: + new_content += '#' + else: + new_content += x + + content = new_content + + return re.search(r'flv_url.*(http.*?)##post_roll', content).group(1) + + + def get_file_name(self): + if not self.html: + self.download_html() + + m = re.search(r'<title.+?>([^<]+) - ', self.html) + if m: + name = m.group(1) + else: + matches = re.findall('<h1>(.*?)</h1>', self.html) + if len(matches) > 1: + name = matches[1] + else: + name = matches[0] + + return name + '.flv' + + + def file_exists(self): + """ returns True or False + """ + if not self.html: + self.download_html() + + if re.search(r'This video is no longer in our database or is in conversion', self.html): + return False + else: + return True diff --git a/pyload/plugin/hoster/PotloadCom.py b/pyload/plugin/hoster/PotloadCom.py new file mode 100644 index 000000000..b6b86b689 --- /dev/null +++ b/pyload/plugin/hoster/PotloadCom.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class PotloadCom(DeadHoster): + __name = "PotloadCom" + __type = "hoster" + __version = "0.02" + + __pattern = r'http://(?:www\.)?potload\.com/\w{12}' + __config = [] + + __description = """Potload.com hoster plugin""" + __license = "GPLv3" + __authors = [("stickell", "l.stickell@yahoo.it")] diff --git a/pyload/plugin/hoster/PremiumTo.py b/pyload/plugin/hoster/PremiumTo.py new file mode 100644 index 000000000..59fd37459 --- /dev/null +++ b/pyload/plugin/hoster/PremiumTo.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +import os + +from pyload.plugin.internal.MultiHoster import MultiHoster +from pyload.utils import fs_encode + + +class PremiumTo(MultiHoster): + __name = "PremiumTo" + __type = "hoster" + __version = "0.22" + + __pattern = r'^unmatchable$' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Premium.to multi-hoster plugin""" + __license = "GPLv3" + __authors = [("RaNaN", "RaNaN@pyload.org"), + ("zoidberg", "zoidberg@mujmail.cz"), + ("stickell", "l.stickell@yahoo.it")] + + + CHECK_TRAFFIC = True + + + def handlePremium(self, pyfile): + # raise timeout to 2min + self.download("http://premium.to/api/getfile.php", + get={'username': self.account.username, + 'password': self.account.password, + 'link' : pyfile.url}, + disposition=True) + + + def checkFile(self, rules={}): + if self.checkDownload({'nopremium': "No premium account available"}): + self.retry(60, 5 * 60, "No premium account available") + + err = '' + if self.req.http.code == '420': + # Custom error code send - fail + file = fs_encode(self.lastDownload) + with open(file, "rb") as f: + err = f.read(256).strip() + os.remove(file) + + if err: + self.fail(err) + + return super(PremiumTo, self).checkFile(rules) diff --git a/pyload/plugin/hoster/PremiumizeMe.py b/pyload/plugin/hoster/PremiumizeMe.py new file mode 100644 index 000000000..f577da90e --- /dev/null +++ b/pyload/plugin/hoster/PremiumizeMe.py @@ -0,0 +1,58 @@ +# -*- coding: utf-8 -*- + +from pyload.utils import json_loads +from pyload.plugin.internal.MultiHoster import MultiHoster + + +class PremiumizeMe(MultiHoster): + __name = "PremiumizeMe" + __type = "hoster" + __version = "0.16" + + __pattern = r'^unmatchable$' #: Since we want to allow the user to specify the list of hoster to use we let MultiHoster.activate + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Premiumize.me multi-hoster plugin""" + __license = "GPLv3" + __authors = [("Florian Franzen", "FlorianFranzen@gmail.com")] + + + def handlePremium(self, pyfile): + # In some cases hostsers do not supply us with a filename at download, so we + # are going to set a fall back filename (e.g. for freakshare or xfileshare) + pyfile.name = pyfile.name.split('/').pop() #: Remove everthing before last slash + + # Correction for automatic assigned filename: Removing html at end if needed + suffix_to_remove = ["html", "htm", "php", "php3", "asp", "shtm", "shtml", "cfml", "cfm"] + temp = pyfile.name.split('.') + if temp.pop() in suffix_to_remove: + pyfile.name = ".".join(temp) + + # Get account data + user, data = self.account.selectAccount() + + # Get rewritten link using the premiumize.me api v1 (see https://secure.premiumize.me/?show=api) + data = json_loads(self.load("https://api.premiumize.me/pm-api/v1.php", + get={'method' : "directdownloadlink", + 'params[login]': user, + 'params[pass]' : data['password'], + 'params[link]' : pyfile.url})) + + # Check status and decide what to do + status = data['status'] + + if status == 200: + self.link = data['result']['location'] + return + + elif status == 400: + self.fail(_("Invalid link")) + + elif status == 404: + self.offline() + + elif status >= 500: + self.tempOffline() + + else: + self.fail(data['statusmessage']) diff --git a/pyload/plugin/hoster/PromptfileCom.py b/pyload/plugin/hoster/PromptfileCom.py new file mode 100644 index 000000000..27b669382 --- /dev/null +++ b/pyload/plugin/hoster/PromptfileCom.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class PromptfileCom(SimpleHoster): + __name = "PromptfileCom" + __type = "hoster" + __version = "0.13" + + __pattern = r'https?://(?:www\.)?promptfile\.com/' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Promptfile.com hoster plugin""" + __license = "GPLv3" + __authors = [("igel", "igelkun@myopera.com")] + + + INFO_PATTERN = r'<span style=".+?" title=".+?">(?P<N>.*?) \((?P<S>[\d.,]+) (?P<U>[\w^_]+)\)</span>' + OFFLINE_PATTERN = r'<span style=".+?" title="File Not Found">File Not Found</span>' + + CHASH_PATTERN = r'<input type="hidden" name="chash" value="(.+?)" />' + LINK_FREE_PATTERN = r'<a href=\"(.+)\" target=\"_blank\" class=\"view_dl_link\">Download File</a>' + + + def handleFree(self, pyfile): + # STAGE 1: get link to continue + m = re.search(self.CHASH_PATTERN, self.html) + if m is None: + self.error(_("CHASH_PATTERN not found")) + chash = m.group(1) + self.logDebug("Read chash %s" % chash) + # continue to stage2 + self.html = self.load(pyfile.url, decode=True, post={'chash': chash}) + + # STAGE 2: get the direct link + m = re.search(self.LINK_FREE_PATTERN, self.html) + if m is None: + self.error(_("LINK_FREE_PATTERN not found")) + + self.link = m.group(1) diff --git a/pyload/plugin/hoster/PrzeklejPl.py b/pyload/plugin/hoster/PrzeklejPl.py new file mode 100644 index 000000000..f4781abb9 --- /dev/null +++ b/pyload/plugin/hoster/PrzeklejPl.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class PrzeklejPl(DeadHoster): + __name = "PrzeklejPl" + __type = "hoster" + __version = "0.11" + + __pattern = r'http://(?:www\.)?przeklej\.pl/plik/.+' + __config = [] + + __description = """Przeklej.pl hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] diff --git a/pyload/plugin/hoster/PutdriveCom.py b/pyload/plugin/hoster/PutdriveCom.py new file mode 100644 index 000000000..051da9473 --- /dev/null +++ b/pyload/plugin/hoster/PutdriveCom.py @@ -0,0 +1,15 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.hoster.ZeveraCom import ZeveraCom + + +class PutdriveCom(ZeveraCom): + __name = "PutdriveCom" + __type = "hoster" + __version = "0.02" + + __pattern = r'https?://(?:www\.)putdrive\.com/(getFiles\.ashx|Members/download\.ashx)\?.*ourl=.+' + + __description = """Multihosters.com multi-hoster plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] diff --git a/pyload/plugin/hoster/QuickshareCz.py b/pyload/plugin/hoster/QuickshareCz.py new file mode 100644 index 000000000..9d66147f6 --- /dev/null +++ b/pyload/plugin/hoster/QuickshareCz.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- + +import re +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class QuickshareCz(SimpleHoster): + __name = "QuickshareCz" + __type = "hoster" + __version = "0.56" + + __pattern = r'http://(?:[^/]*\.)?quickshare\.cz/stahnout-soubor/.+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Quickshare.cz hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + NAME_PATTERN = r'<th width="145px">Název:</th>\s*<td style="word-wrap:break-word;">(?P<N>[^<]+)</td>' + SIZE_PATTERN = r'<th>Velikost:</th>\s*<td>(?P<S>[\d.,]+) (?P<U>[\w^_]+)</td>' + OFFLINE_PATTERN = r'<script type="text/javascript">location\.href=\'/chyba\';</script>' + + + def process(self, pyfile): + self.html = self.load(pyfile.url, decode=True) + self.getFileInfo() + + # parse js variables + self.jsvars = dict((x, y.strip("'")) for x, y in re.findall(r"var (\w+) = ([\d.]+|'.+?')", self.html)) + self.logDebug(self.jsvars) + pyfile.name = self.jsvars['ID3'] + + # determine download type - free or premium + if self.premium: + if 'UU_prihlasen' in self.jsvars: + if self.jsvars['UU_prihlasen'] == '0': + self.logWarning(_("User not logged in")) + self.relogin(self.user) + self.retry() + elif float(self.jsvars['UU_kredit']) < float(self.jsvars['kredit_odecet']): + self.logWarning(_("Not enough credit left")) + self.premium = False + + if self.premium: + self.handlePremium(pyfile) + else: + self.handleFree(pyfile) + + if self.checkDownload({"error": re.compile(r"\AChyba!")}, max_size=100): + self.fail(_("File not m or plugin defect")) + + + def handleFree(self, pyfile): + # get download url + download_url = '%s/download.php' % self.jsvars['server'] + data = dict((x, self.jsvars[x]) for x in self.jsvars if x in ("ID1", "ID2", "ID3", "ID4")) + self.logDebug("FREE URL1:" + download_url, data) + + self.load(download_url, post=data, follow_location=False) + self.header = self.req.http.header + + m = re.search(r'Location\s*:\s*(.+)', self.header, re.I) + if m is None: + self.fail(_("File not found")) + + self.link = m.group(1) + self.logDebug("FREE URL2:" + self.link) + + # check errors + m = re.search(r'/chyba/(\d+)', self.link) + if m: + if m.group(1) == '1': + self.retry(60, 2 * 60, "This IP is already downloading") + elif m.group(1) == '2': + self.retry(60, 60, "No free slots available") + else: + self.fail(_("Error %d") % m.group(1)) + + + def handlePremium(self, pyfile): + download_url = '%s/download_premium.php' % self.jsvars['server'] + data = dict((x, self.jsvars[x]) for x in self.jsvars if x in ("ID1", "ID2", "ID4", "ID5")) + self.download(download_url, get=data) diff --git a/pyload/plugin/hoster/RPNetBiz.py b/pyload/plugin/hoster/RPNetBiz.py new file mode 100644 index 000000000..b84f171c3 --- /dev/null +++ b/pyload/plugin/hoster/RPNetBiz.py @@ -0,0 +1,76 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.MultiHoster import MultiHoster +from pyload.utils import json_loads + + +class RPNetBiz(MultiHoster): + __name = "RPNetBiz" + __type = "hoster" + __version = "0.14" + + __pattern = r'https?://.+rpnet\.biz' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """RPNet.biz multi-hoster plugin""" + __license = "GPLv3" + __authors = [("Dman", "dmanugm@gmail.com")] + + + def setup(self): + self.chunkLimit = -1 + + + def handlePremium(self, pyfile): + user, data = self.account.selectAccount() + + # Get the download link + res = self.load("https://premium.rpnet.biz/client_api.php", + get={"username": user, + "password": data['password'], + "action" : "generate", + "links" : pyfile.url}) + + self.logDebug("JSON data: %s" % res) + link_status = json_loads(res)['links'][0] #: get the first link... since we only queried one + + # Check if we only have an id as a HDD link + if 'id' in link_status: + self.logDebug("Need to wait at least 30 seconds before requery") + self.setWait(30) #: wait for 30 seconds + self.wait() + # Lets query the server again asking for the status on the link, + # we need to keep doing this until we reach 100 + max_tries = 30 + my_try = 0 + while (my_try <= max_tries): + self.logDebug("Try: %d ; Max Tries: %d" % (my_try, max_tries)) + res = self.load("https://premium.rpnet.biz/client_api.php", + get={"username": user, + "password": data['password'], + "action": "downloadInformation", + "id": link_status['id']}) + self.logDebug("JSON data hdd query: %s" % res) + download_status = json_loads(res)['download'] + + if download_status['status'] == '100': + link_status['generated'] = download_status['rpnet_link'] + self.logDebug("Successfully downloaded to rpnet HDD: %s" % link_status['generated']) + break + else: + self.logDebug("At %s%% for the file download" % download_status['status']) + + self.setWait(30) + self.wait() + my_try += 1 + + if my_try > max_tries: #: We went over the limit! + self.fail(_("Waited for about 15 minutes for download to finish but failed")) + + if 'generated' in link_status: + self.link = link_status['generated'] + return + elif 'error' in link_status: + self.fail(link_status['error']) + else: + self.fail(_("Something went wrong, not supposed to enter here")) diff --git a/pyload/plugin/hoster/RapideoPl.py b/pyload/plugin/hoster/RapideoPl.py new file mode 100644 index 000000000..35d4da0ad --- /dev/null +++ b/pyload/plugin/hoster/RapideoPl.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- + +from pyload.utils import json_loads +from pyload.plugin.internal.MultiHoster import MultiHoster + + +class RapideoPl(MultiHoster): + __name = "RapideoPl" + __type = "hoster" + __version = "0.02" + + __pattern = r'^unmatchable$' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Rapideo.pl multi-hoster plugin""" + __license = "GPLv3" + __authors = [("goddie", "dev@rapideo.pl")] + + + API_URL = "http://enc.rapideo.pl" + + API_QUERY = {'site' : "newrd", + 'output' : "json", + 'username': "", + 'password': "", + 'url' : ""} + + ERROR_CODES = {0 : "[%s] Incorrect login credentials", + 1 : "[%s] Not enough transfer to download - top-up your account", + 2 : "[%s] Incorrect / dead link", + 3 : "[%s] Error connecting to hosting, try again later", + 9 : "[%s] Premium account has expired", + 15: "[%s] Hosting no longer supported", + 80: "[%s] Too many incorrect login attempts, account blocked for 24h"} + + + def prepare(self): + super(RapideoPl, self).prepare() + + data = self.account.getAccountData(self.user) + + self.usr = data['usr'] + self.pwd = data['pwd'] + + + def runFileQuery(self, url, mode=None): + query = self.API_QUERY.copy() + + query['username'] = self.usr + query['password'] = self.pwd + query['url'] = url + + if mode == "fileinfo": + query['check'] = 2 + query['loc'] = 1 + + self.logDebug(query) + + return self.load(self.API_URL, post=query) + + + def handleFree(self, pyfile): + try: + data = self.runFileQuery(pyfile.url, 'fileinfo') + + except Exception: + self.logDebug("RunFileQuery error") + self.tempOffline() + + try: + parsed = json_loads(data) + + except Exception: + self.logDebug("Loads error") + self.tempOffline() + + self.logDebug(parsed) + + if "errno" in parsed.keys(): + if parsed['errno'] in self.ERROR_CODES: + # error code in known + self.fail(self.ERROR_CODES[parsed['errno']] % self.getClassName()) + else: + # error code isn't yet added to plugin + self.fail( + parsed['errstring'] + or _("Unknown error (code: %s)") % parsed['errno'] + ) + + if "sdownload" in parsed: + if parsed['sdownload'] == "1": + self.fail( + _("Download from %s is possible only using Rapideo.pl website \ + directly") % parsed['hosting']) + + pyfile.name = parsed['filename'] + pyfile.size = parsed['filesize'] + + try: + self.link = self.runFileQuery(pyfile.url, 'filedownload') + + except Exception: + self.logDebug("runFileQuery error #2") + self.tempOffline() diff --git a/pyload/plugin/hoster/RapidfileshareNet.py b/pyload/plugin/hoster/RapidfileshareNet.py new file mode 100644 index 000000000..8252b49e7 --- /dev/null +++ b/pyload/plugin/hoster/RapidfileshareNet.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSHoster import XFSHoster + + +class RapidfileshareNet(XFSHoster): + __name = "RapidfileshareNet" + __type = "hoster" + __version = "0.03" + + __pattern = r'http://(?:www\.)?rapidfileshare\.net/\w{12}' + + __description = """Rapidfileshare.net hoster plugin""" + __license = "GPLv3" + __authors = [("guidobelix", "guidobelix@hotmail.it")] + + + NAME_PATTERN = r'<input type="hidden" name="fname" value="(?P<N>.+?)">' + SIZE_PATTERN = r'>http://www.rapidfileshare.net/\w+?</font> \((?P<S>[\d.,]+) (?P<U>[\w^_]+)\)</font>' + + OFFLINE_PATTERN = r'>No such file with this filename' + TEMP_OFFLINE_PATTERN = r'The page may have been renamed, removed or be temporarily unavailable.<' diff --git a/pyload/plugin/hoster/RapidgatorNet.py b/pyload/plugin/hoster/RapidgatorNet.py new file mode 100644 index 000000000..b05b0d5d0 --- /dev/null +++ b/pyload/plugin/hoster/RapidgatorNet.py @@ -0,0 +1,161 @@ +# -*- coding: utf-8 -*- + +import pycurl +import re + +from pyload.utils import json_loads +from pyload.network.HTTPRequest import BadHeader +from pyload.plugin.captcha.AdsCaptcha import AdsCaptcha +from pyload.plugin.captcha.ReCaptcha import ReCaptcha +from pyload.plugin.captcha.SolveMedia import SolveMedia +from pyload.plugin.internal.SimpleHoster import SimpleHoster, secondsToMidnight + + +class RapidgatorNet(SimpleHoster): + __name = "RapidgatorNet" + __type = "hoster" + __version = "0.33" + + __pattern = r'http://(?:www\.)?(rapidgator\.net|rg\.to)/file/\w+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Rapidgator.net hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz"), + ("chrox", ""), + ("stickell", "l.stickell@yahoo.it"), + ("Walter Purcaro", "vuolter@gmail.com")] + + + API_URL = "http://rapidgator.net/api/file" + + COOKIES = [("rapidgator.net", "lang", "en")] + + NAME_PATTERN = r'<title>Download file (?P<N>.*)</title>' + SIZE_PATTERN = r'File size:\s*<strong>(?P<S>[\d.,]+) (?P<U>[\w^_]+)</strong>' + OFFLINE_PATTERN = r'>(File not found|Error 404)' + + JSVARS_PATTERN = r'\s+var\s*(startTimerUrl|getDownloadUrl|captchaUrl|fid|secs)\s*=\s*\'?(.*?)\'?;' + + PREMIUM_ONLY_PATTERN = r'You can download files up to|This file can be downloaded by premium only<' + ERROR_PATTERN = r'You have reached your (?:daily|hourly) downloads limit' + WAIT_PATTERN = r'(Delay between downloads must be not less than|Try again in).+' + + LINK_FREE_PATTERN = r'return \'(http://\w+.rapidgator.net/.*)\';' + + RECAPTCHA_PATTERN = r'"http://api\.recaptcha\.net/challenge\?k=(.*?)"' + ADSCAPTCHA_PATTERN = r'(http://api\.adscaptcha\.com/Get\.aspx[^"\']+)' + SOLVEMEDIA_PATTERN = r'http://api\.solvemedia\.com/papi/challenge\.script\?k=(.*?)"' + + + def setup(self): + if self.account: + self.sid = self.account.getAccountInfo(self.user).get('sid', None) + else: + self.sid = None + + if self.sid: + self.premium = True + + self.resumeDownload = self.multiDL = self.premium + self.chunkLimit = 1 + + + def api_response(self, cmd): + try: + json = self.load('%s/%s' % (self.API_URL, cmd), + get={'sid': self.sid, + 'url': self.pyfile.url}, decode=True) + self.logDebug("API:%s" % cmd, json, "SID: %s" % self.sid) + json = json_loads(json) + status = json['response_status'] + msg = json['response_details'] + + except BadHeader, e: + self.logError("API: %s" % cmd, e, "SID: %s" % self.sid) + status = e.code + msg = e + + if status == 200: + return json['response'] + + elif status == 423: + self.account.empty(self.user) + self.retry() + + else: + self.account.relogin(self.user) + self.retry(wait_time=60) + + + def handlePremium(self, pyfile): + self.api_data = self.api_response('info') + self.api_data['md5'] = self.api_data['hash'] + + pyfile.name = self.api_data['filename'] + pyfile.size = self.api_data['size'] + + self.link = self.api_response('download')['url'] + + + def handleFree(self, pyfile): + jsvars = dict(re.findall(self.JSVARS_PATTERN, self.html)) + self.logDebug(jsvars) + + self.req.http.lastURL = pyfile.url + self.req.http.c.setopt(pycurl.HTTPHEADER, ["X-Requested-With: XMLHttpRequest"]) + + url = "http://rapidgator.net%s?fid=%s" % ( + jsvars.get('startTimerUrl', '/download/AjaxStartTimer'), jsvars['fid']) + jsvars.update(self.getJsonResponse(url)) + + self.wait(jsvars.get('secs', 45), False) + + url = "http://rapidgator.net%s?sid=%s" % ( + jsvars.get('getDownloadUrl', '/download/AjaxGetDownload'), jsvars['sid']) + jsvars.update(self.getJsonResponse(url)) + + self.req.http.lastURL = pyfile.url + self.req.http.c.setopt(pycurl.HTTPHEADER, ["X-Requested-With:"]) + + url = "http://rapidgator.net%s" % jsvars.get('captchaUrl', '/download/captcha') + self.html = self.load(url) + + for _i in xrange(5): + m = re.search(self.LINK_FREE_PATTERN, self.html) + if m: + self.link = m.group(1) + break + else: + captcha = self.handleCaptcha() + + if not captcha: + self.error(_("Captcha pattern not found")) + + response, challenge = captcha.challenge() + + self.html = self.load(url, post={'DownloadCaptchaForm[captcha]': "", + 'adcopy_challenge' : challenge, + 'adcopy_response' : response}) + + if "The verification code is incorrect" in self.html: + self.invalidCaptcha() + else: + self.correctCaptcha() + else: + self.error(_("Download link")) + + + def handleCaptcha(self): + for klass in (AdsCaptcha, ReCaptcha, SolveMedia): + inst = klass(self) + if inst.detect_key(): + return inst + + + def getJsonResponse(self, url): + res = self.load(url, decode=True) + if not res.startswith('{'): + self.retry() + self.logDebug(url, res) + return json_loads(res) diff --git a/pyload/plugin/hoster/RapiduNet.py b/pyload/plugin/hoster/RapiduNet.py new file mode 100644 index 000000000..81d25a3fa --- /dev/null +++ b/pyload/plugin/hoster/RapiduNet.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- + +import pycurl +import time + +from pyload.utils import json_loads +from pyload.plugin.captcha.ReCaptcha import ReCaptcha +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class RapiduNet(SimpleHoster): + __name = "RapiduNet" + __type = "hoster" + __version = "0.08" + + __pattern = r'https?://(?:www\.)?rapidu\.net/(?P<ID>\d{10})' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Rapidu.net hoster plugin""" + __license = "GPLv3" + __authors = [("prOq", "")] + + + COOKIES = [("rapidu.net", "rapidu_lang", "en")] + + INFO_PATTERN = r'<h1 title="(?P<N>.*)">.*</h1>\s*<small>(?P<S>\d+(\.\d+)?)\s(?P<U>\w+)</small>' + OFFLINE_PATTERN = r'<h1>404' + + ERROR_PATTERN = r'<div class="error">' + + RECAPTCHA_KEY = r'6Ld12ewSAAAAAHoE6WVP_pSfCdJcBQScVweQh8Io' + + + def setup(self): + self.resumeDownload = True + self.multiDL = self.premium + + + def handleFree(self, pyfile): + self.req.http.lastURL = pyfile.url + self.req.http.c.setopt(pycurl.HTTPHEADER, ["X-Requested-With: XMLHttpRequest"]) + + jsvars = self.getJsonResponse("https://rapidu.net/ajax.php", + get={'a': "getLoadTimeToDownload"}, + post={'_go': ""}, + decode=True) + + if str(jsvars['timeToDownload']) is "stop": + t = (24 * 60 * 60) - (int(time.time()) % (24 * 60 * 60)) + time.altzone + + self.logInfo("You've reach your daily download transfer") + + self.retry(10, 10 if t < 1 else None, _("Try tomorrow again")) #@NOTE: check t in case of not synchronised clock + + else: + self.wait(int(jsvars['timeToDownload']) - int(time.time())) + + recaptcha = ReCaptcha(self) + response, challenge = recaptcha.challenge(self.RECAPTCHA_KEY) + + jsvars = self.getJsonResponse("https://rapidu.net/ajax.php", + get={'a': "getCheckCaptcha"}, + post={'_go' : "", + 'captcha1': challenge, + 'captcha2': response, + 'fileId' : self.info['pattern']['ID']}, + decode=True) + + if jsvars['message'] == 'success': + self.link = jsvars['url'] + + + def getJsonResponse(self, *args, **kwargs): + res = self.load(*args, **kwargs) + if not res.startswith('{'): + self.retry() + + self.logDebug(res) + + return json_loads(res) diff --git a/pyload/plugin/hoster/RarefileNet.py b/pyload/plugin/hoster/RarefileNet.py new file mode 100644 index 000000000..2c853c6ba --- /dev/null +++ b/pyload/plugin/hoster/RarefileNet.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSHoster import XFSHoster + + +class RarefileNet(XFSHoster): + __name = "RarefileNet" + __type = "hoster" + __version = "0.09" + + __pattern = r'http://(?:www\.)?rarefile\.net/\w{12}' + + __description = """Rarefile.net hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + LINK_PATTERN = r'<a href="(.+?)">\1</a>' diff --git a/pyload/plugin/hoster/RealdebridCom.py b/pyload/plugin/hoster/RealdebridCom.py new file mode 100644 index 000000000..642de3f92 --- /dev/null +++ b/pyload/plugin/hoster/RealdebridCom.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- + +import time + +from pyload.utils import json_loads +from pyload.plugin.internal.MultiHoster import MultiHoster +from pyload.utils import parseFileSize + + +class RealdebridCom(MultiHoster): + __name = "RealdebridCom" + __type = "hoster" + __version = "0.67" + + __pattern = r'https?://((?:www\.|s\d+\.)?real-debrid\.com/dl/|[\w^_]\.rdb\.so/d/)[\w^_]+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Real-Debrid.com multi-hoster plugin""" + __license = "GPLv3" + __authors = [("Devirex Hazzard", "naibaf_11@yahoo.de")] + + + def setup(self): + self.chunkLimit = 3 + + + def handlePremium(self, pyfile): + data = json_loads(self.load("https://real-debrid.com/ajax/unrestrict.php", + get={'lang' : "en", + 'link' : pyfile.url, + 'password': self.getPassword(), + 'time' : int(time.time() * 1000)})) + + self.logDebug("Returned Data: %s" % data) + + if data['error'] != 0: + if data['message'] == "Your file is unavailable on the hoster.": + self.offline() + else: + self.logWarning(data['message']) + self.tempOffline() + else: + if pyfile.name and pyfile.name.endswith('.tmp') and data['file_name']: + pyfile.name = data['file_name'] + pyfile.size = parseFileSize(data['file_size']) + self.link = data['generated_links'][0][-1] + + if self.getConfig('ssl'): + self.link = self.link.replace("http://", "https://") + else: + self.link = self.link.replace("https://", "http://") diff --git a/pyload/plugin/hoster/RedtubeCom.py b/pyload/plugin/hoster/RedtubeCom.py new file mode 100644 index 000000000..4b9afbc2b --- /dev/null +++ b/pyload/plugin/hoster/RedtubeCom.py @@ -0,0 +1,62 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.Hoster import Hoster +from pyload.utils import html_unescape + + +class RedtubeCom(Hoster): + __name = "RedtubeCom" + __type = "hoster" + __version = "0.20" + + __pattern = r'http://(?:www\.)?redtube\.com/\d+' + + __description = """Redtube.com hoster plugin""" + __license = "GPLv3" + __authors = [("jeix", "jeix@hasnomail.de")] + + + def process(self, pyfile): + self.download_html() + if not self.file_exists(): + self.offline() + + pyfile.name = self.get_file_name() + self.download(self.get_file_url()) + + + def download_html(self): + url = self.pyfile.url + self.html = self.load(url) + + + def get_file_url(self): + """ returns the absolute downloadable filepath + """ + if not self.html: + self.download_html() + + file_url = html_unescape(re.search(r'hashlink=(http.*?)"', self.html).group(1)) + + return file_url + + + def get_file_name(self): + if not self.html: + self.download_html() + + return re.search('<title>(.*?)- RedTube - Free Porn Videos</title>', self.html).group(1).strip() + ".flv" + + + def file_exists(self): + """ returns True or False + """ + if not self.html: + self.download_html() + + if re.search(r'This video has been removed.', self.html): + return False + else: + return True diff --git a/pyload/plugin/hoster/RehostTo.py b/pyload/plugin/hoster/RehostTo.py new file mode 100644 index 000000000..f6f5630fb --- /dev/null +++ b/pyload/plugin/hoster/RehostTo.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.MultiHoster import MultiHoster + + +class RehostTo(MultiHoster): + __name = "RehostTo" + __type = "hoster" + __version = "0.21" + + __pattern = r'https?://.*rehost\.to\..+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Rehost.com multi-hoster plugin""" + __license = "GPLv3" + __authors = [("RaNaN", "RaNaN@pyload.org")] + + + def handlePremium(self, pyfile): + self.download("http://rehost.to/process_download.php", + get={'user': "cookie", + 'pass': self.account.getAccountInfo(self.user)['session'], + 'dl' : pyfile.url}, + disposition=True) diff --git a/pyload/plugin/hoster/RemixshareCom.py b/pyload/plugin/hoster/RemixshareCom.py new file mode 100644 index 000000000..ba61887cd --- /dev/null +++ b/pyload/plugin/hoster/RemixshareCom.py @@ -0,0 +1,55 @@ +# -*- coding: utf-8 -*- +# +# Test links: +# http://remixshare.com/download/z8uli +# +# Note: +# The remixshare.com website is very very slow, so +# if your download not starts because of pycurl timeouts: +# Adjust timeouts in /usr/share/pyload/pyload/network/HTTPRequest.py + +import re + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class RemixshareCom(SimpleHoster): + __name = "RemixshareCom" + __type = "hoster" + __version = "0.05" + + __pattern = r'https?://remixshare\.com/(download|dl)/\w+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Remixshare.com hoster plugin""" + __license = "GPLv3" + __authors = [("zapp-brannigan", "fuerst.reinje@web.de" ), + ("Walter Purcaro", "vuolter@gmail.com" ), + ("sraedler" , "simon.raedler@yahoo.de")] + + + INFO_PATTERN = r'title=\'.+?\'>(?P<N>.+?)</span><span class=\'light2\'> \((?P<S>\d+) (?P<U>[\w^_]+)\)<' + HASHSUM_PATTERN = r'>(?P<T>MD5): (?P<H>\w+)' + OFFLINE_PATTERN = r'<h1>Ooops!' + + LINK_PATTERN = r'var uri = "(.+?)"' + TOKEN_PATTERN = r'var acc = (\d+)' + + WAIT_PATTERN = r'var XYZ = "(\d+)"' + + + def setup(self): + self.multiDL = True + self.chunkLimit = 1 + + + def handleFree(self, pyfile): + b = re.search(self.LINK_PATTERN, self.html) + if not b: + self.error(_("File url")) + + c = re.search(self.TOKEN_PATTERN, self.html) + if not c: + self.error(_("File token")) + + self.link = b.group(1) + "/zzz/" + c.group(1) diff --git a/pyload/plugin/hoster/RgHostNet.py b/pyload/plugin/hoster/RgHostNet.py new file mode 100644 index 000000000..04ecbda8f --- /dev/null +++ b/pyload/plugin/hoster/RgHostNet.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class RgHostNet(SimpleHoster): + __name = "RgHostNet" + __type = "hoster" + __version = "0.04" + + __pattern = r'http://(?:www\.)?rghost\.(net|ru)/[\d-]+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """RgHost.net hoster plugin""" + __license = "GPLv3" + __authors = [("z00nx", "z00nx0@gmail.com")] + + + INFO_PATTERN = r'data-share42-text="(?P<N>.+?) \((?P<S>[\d.,]+) (?P<U>[\w^_]+)' + HASHSUM_PATTERN = r'<dt>(?P<T>\w+)</dt>\s*<dd>(?P<H>\w+)' + OFFLINE_PATTERN = r'>(File is deleted|page not found)' + + LINK_FREE_PATTERN = r'<a href="(.+?)" class="btn large' diff --git a/pyload/plugin/hoster/SafesharingEu.py b/pyload/plugin/hoster/SafesharingEu.py new file mode 100644 index 000000000..0676015a2 --- /dev/null +++ b/pyload/plugin/hoster/SafesharingEu.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSHoster import XFSHoster + + +class SafesharingEu(XFSHoster): + __name = "SafesharingEu" + __type = "hoster" + __version = "0.05" + + __pattern = r'https?://(?:www\.)?safesharing\.eu/\w{12}' + + __description = """Safesharing.eu hoster plugin""" + __license = "GPLv3" + __authors = [("zapp-brannigan", "fuerst.reinje@web.de")] + + + ERROR_PATTERN = r'(?:<div class="alert alert-danger">)(.+?)(?:</div>)' diff --git a/pyload/plugin/hoster/SecureUploadEu.py b/pyload/plugin/hoster/SecureUploadEu.py new file mode 100644 index 000000000..8cd310c2c --- /dev/null +++ b/pyload/plugin/hoster/SecureUploadEu.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSHoster import XFSHoster + + +class SecureUploadEu(XFSHoster): + __name = "SecureUploadEu" + __type = "hoster" + __version = "0.05" + + __pattern = r'https?://(?:www\.)?secureupload\.eu/\w{12}' + + __description = """SecureUpload.eu hoster plugin""" + __license = "GPLv3" + __authors = [("z00nx", "z00nx0@gmail.com")] + + + INFO_PATTERN = r'<h3>Downloading (?P<N>[^<]+) \((?P<S>[^<]+)\)</h3>' diff --git a/pyload/plugin/hoster/SendspaceCom.py b/pyload/plugin/hoster/SendspaceCom.py new file mode 100644 index 000000000..740774313 --- /dev/null +++ b/pyload/plugin/hoster/SendspaceCom.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class SendspaceCom(SimpleHoster): + __name = "SendspaceCom" + __type = "hoster" + __version = "0.17" + + __pattern = r'https?://(?:www\.)?sendspace\.com/file/\w+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Sendspace.com hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + NAME_PATTERN = r'<h2 class="bgray">\s*<(?:b|strong)>(?P<N>[^<]+)</' + SIZE_PATTERN = r'<div class="file_description reverse margin_center">\s*<b>File Size:</b>\s*(?P<S>[\d.,]+)(?P<U>[\w^_]+)\s*</div>' + OFFLINE_PATTERN = r'<div class="msg error" style="cursor: default">Sorry, the file you requested is not available.</div>' + + LINK_FREE_PATTERN = r'<a id="download_button" href="(.+?)"' + + CAPTCHA_PATTERN = r'<td><img src="(/captchas/captcha\.php?captcha=(.+?))"></td>' + USER_CAPTCHA_PATTERN = r'<td><img src="/captchas/captcha\.php?user=(.+?))"></td>' + + + def handleFree(self, pyfile): + params = {} + for _i in xrange(3): + m = re.search(self.LINK_FREE_PATTERN, self.html) + if m: + if 'captcha_hash' in params: + self.correctCaptcha() + self.link = m.group(1) + break + + m = re.search(self.CAPTCHA_PATTERN, self.html) + if m: + if 'captcha_hash' in params: + self.invalidCaptcha() + captcha_url1 = "http://www.sendspace.com/" + m.group(1) + m = re.search(self.USER_CAPTCHA_PATTERN, self.html) + captcha_url2 = "http://www.sendspace.com/" + m.group(1) + params = {'captcha_hash': m.group(2), + 'captcha_submit': 'Verify', + 'captcha_answer': self.decryptCaptcha(captcha_url1) + " " + self.decryptCaptcha(captcha_url2)} + else: + params = {'download': "Regular Download"} + + self.logDebug(params) + self.html = self.load(pyfile.url, post=params) + else: + self.fail(_("Download link not found")) diff --git a/pyload/plugin/hoster/Share4WebCom.py b/pyload/plugin/hoster/Share4WebCom.py new file mode 100644 index 000000000..72e3e4a02 --- /dev/null +++ b/pyload/plugin/hoster/Share4WebCom.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.hoster.UnibytesCom import UnibytesCom + + +class Share4WebCom(UnibytesCom): + __name = "Share4WebCom" + __type = "hoster" + __version = "0.11" + + __pattern = r'https?://(?:www\.)?share4web\.com/get/\w+' + + __description = """Share4web.com hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + HOSTER_DOMAIN = "share4web.com" diff --git a/pyload/plugin/hoster/Share76Com.py b/pyload/plugin/hoster/Share76Com.py new file mode 100644 index 000000000..fda983401 --- /dev/null +++ b/pyload/plugin/hoster/Share76Com.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class Share76Com(DeadHoster): + __name = "Share76Com" + __type = "hoster" + __version = "0.04" + + __pattern = r'http://(?:www\.)?share76\.com/\w{12}' + __config = [] + + __description = """Share76.com hoster plugin""" + __license = "GPLv3" + __authors = [] diff --git a/pyload/plugin/hoster/ShareFilesCo.py b/pyload/plugin/hoster/ShareFilesCo.py new file mode 100644 index 000000000..37e386034 --- /dev/null +++ b/pyload/plugin/hoster/ShareFilesCo.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class ShareFilesCo(DeadHoster): + __name = "ShareFilesCo" + __type = "hoster" + __version = "0.02" + + __pattern = r'http://(?:www\.)?sharefiles\.co/\w{12}' + __config = [] + + __description = """Sharefiles.co hoster plugin""" + __license = "GPLv3" + __authors = [("stickell", "l.stickell@yahoo.it")] diff --git a/pyload/plugin/hoster/SharebeesCom.py b/pyload/plugin/hoster/SharebeesCom.py new file mode 100644 index 000000000..5b5017fc2 --- /dev/null +++ b/pyload/plugin/hoster/SharebeesCom.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class SharebeesCom(DeadHoster): + __name = "SharebeesCom" + __type = "hoster" + __version = "0.02" + + __pattern = r'http://(?:www\.)?sharebees\.com/\w{12}' + __config = [] + + __description = """ShareBees hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] diff --git a/pyload/plugin/hoster/ShareonlineBiz.py b/pyload/plugin/hoster/ShareonlineBiz.py new file mode 100644 index 000000000..0f5a8692a --- /dev/null +++ b/pyload/plugin/hoster/ShareonlineBiz.py @@ -0,0 +1,181 @@ +# -*- coding: utf-8 -*- + +import re +import time +import urllib +import urlparse + +from pyload.network.RequestFactory import getURL +from pyload.plugin.captcha.ReCaptcha import ReCaptcha +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class ShareonlineBiz(SimpleHoster): + __name = "ShareonlineBiz" + __type = "hoster" + __version = "0.49" + + __pattern = r'https?://(?:www\.)?(share-online\.biz|egoshare\.com)/(download\.php\?id=|dl/)(?P<ID>\w+)' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Shareonline.biz hoster plugin""" + __license = "GPLv3" + __authors = [("spoob", "spoob@pyload.org"), + ("mkaay", "mkaay@mkaay.de"), + ("zoidberg", "zoidberg@mujmail.cz"), + ("Walter Purcaro", "vuolter@gmail.com")] + + + URL_REPLACEMENTS = [(__pattern + ".*", "http://www.share-online.biz/dl/\g<ID>")] + + CHECK_TRAFFIC = True + + RECAPTCHA_KEY = "6LdatrsSAAAAAHZrB70txiV5p-8Iv8BtVxlTtjKX" + + ERROR_PATTERN = r'<p class="b">Information:</p>\s*<div>\s*<strong>(.*?)</strong>' + + + @classmethod + def getInfo(cls, url="", html=""): + info = {'name': urlparse.urlparse(urllib.unquote(url)).path.split('/')[-1] or _("Unknown"), 'size': 0, 'status': 3 if url else 1, 'url': url} + + if url: + info['pattern'] = re.match(cls.__pattern, url).groupdict() + + field = getURL("http://api.share-online.biz/linkcheck.php", + get={'md5': "1"}, + post={'links': info['pattern']['ID']}, + decode=True).split(";") + + if field[1] == "OK": + info['fileid'] = field[0] + info['status'] = 2 + info['name'] = field[2] + info['size'] = field[3] #: in bytes + info['md5'] = field[4].strip().lower().replace("\n\n", "") #: md5 + + elif field[1] in ("DELETED", "NOT FOUND"): + info['status'] = 1 + + return info + + + def setup(self): + self.resumeDownload = self.premium + self.multiDL = False + + + def handleCaptcha(self): + recaptcha = ReCaptcha(self) + + for _i in xrange(5): + response, challenge = recaptcha.challenge(self.RECAPTCHA_KEY) + + m = re.search(r'var wait=(\d+);', self.html) + self.setWait(int(m.group(1)) if m else 30) + + res = self.load("%s/free/captcha/%d" % (self.pyfile.url, int(time.time() * 1000)), + post={'dl_free' : "1", + 'recaptcha_challenge_field': challenge, + 'recaptcha_response_field' : response}) + if not res == '0': + self.correctCaptcha() + return res + else: + self.invalidCaptcha() + else: + self.invalidCaptcha() + self.fail(_("No valid captcha solution received")) + + + def handleFree(self, pyfile): + self.wait(3) + + self.html = self.load("%s/free/" % pyfile.url, + post={'dl_free': "1", 'choice': "free"}, + decode=True) + + self.checkErrors() + + res = self.handleCaptcha() + self.link = res.decode('base64') + + if not self.link.startswith("http://"): + self.error(_("Wrong download url")) + + self.wait() + + + def checkFile(self, rules={}): + check = self.checkDownload({'cookie': re.compile(r'<div id="dl_failure"'), + 'fail' : re.compile(r"<title>Share-Online")}) + + if check == "cookie": + self.invalidCaptcha() + self.retry(5, 60, _("Cookie failure")) + + elif check == "fail": + self.invalidCaptcha() + self.retry(5, 5 * 60, _("Download failed")) + + return super(ShareonlineBiz, self).checkFile(rules) + + + def handlePremium(self, pyfile): #: should be working better loading (account) api internally + html = self.load("http://api.share-online.biz/account.php", + get={'username': self.user, + 'password': self.account.getAccountData(self.user)['password'], + 'act' : "download", + 'lid' : self.info['fileid']}) + + self.api_data = dlinfo = {} + + for line in html.splitlines(): + key, value = line.split(": ") + dlinfo[key.lower()] = value + + self.logDebug(dlinfo) + + if not dlinfo['status'] == "online": + self.offline() + else: + pyfile.name = dlinfo['name'] + pyfile.size = int(dlinfo['size']) + + self.link = dlinfo['url'] + + if self.link == "server_under_maintenance": + self.tempOffline() + else: + self.multiDL = True + + + def checkErrors(self): + m = re.search(r"/failure/(.*?)/1", self.req.lastEffectiveURL) + if m is None: + self.info.pop('error', None) + return + + errmsg = m.group(1).lower() + + try: + self.logError(errmsg, re.search(self.ERROR_PATTERN, self.html).group(1)) + except Exception: + self.logError("Unknown error occurred", errmsg) + + if errmsg is "invalid": + self.fail(_("File not available")) + + elif errmsg in ("freelimit", "size", "proxy"): + self.fail(_("Premium account needed")) + + elif errmsg in ("expired", "server"): + self.retry(wait_time=600, reason=errmsg) + + elif 'slot' in errmsg: + self.wantReconnect = True + self.retry(24, 3600, errmsg) + + else: + self.wantReconnect = True + self.retry(wait_time=60, reason=errmsg) diff --git a/pyload/plugin/hoster/ShareplaceCom.py b/pyload/plugin/hoster/ShareplaceCom.py new file mode 100644 index 000000000..b1361e859 --- /dev/null +++ b/pyload/plugin/hoster/ShareplaceCom.py @@ -0,0 +1,88 @@ +# -*- coding: utf-8 -*- + +import re +import urllib + +from pyload.plugin.Hoster import Hoster + + +class ShareplaceCom(Hoster): + __name = "ShareplaceCom" + __type = "hoster" + __version = "0.12" + + __pattern = r'http://(?:www\.)?shareplace\.(com|org)/\?\w+' + + __description = """Shareplace.com hoster plugin""" + __license = "GPLv3" + __authors = [("ACCakut", "")] + + + def process(self, pyfile): + self.pyfile = pyfile + self.prepare() + self.download(self.get_file_url()) + + + def prepare(self): + if not self.file_exists(): + self.offline() + + self.pyfile.name = self.get_file_name() + + wait_time = self.get_waiting_time() + self.setWait(wait_time) + self.wait() + + + def get_waiting_time(self): + if not self.html: + self.download_html() + + # var zzipitime = 15; + m = re.search(r'var zzipitime = (\d+);', self.html) + if m: + sec = int(m.group(1)) + else: + sec = 0 + + return sec + + + def download_html(self): + url = re.sub("shareplace.com\/\?", "shareplace.com//index1.php/?a=", self.pyfile.url) + self.html = self.load(url, decode=True) + + + def get_file_url(self): + """ returns the absolute downloadable filepath + """ + url = re.search(r"var beer = '(.*?)';", self.html) + if url: + url = url.group(1) + url = urllib.unquote( + url.replace("http://http:/", "").replace("vvvvvvvvv", "").replace("lllllllll", "").replace( + "teletubbies", "")) + self.logDebug("URL: %s" % url) + return url + else: + self.error(_("Absolute filepath not found")) + + + def get_file_name(self): + if not self.html: + self.download_html() + + return re.search("<title>\s*(.*?)\s*</title>", self.html).group(1) + + + def file_exists(self): + """ returns True or False + """ + if not self.html: + self.download_html() + + if re.search(r"HTTP Status 404", self.html): + return False + else: + return True diff --git a/pyload/plugin/hoster/SharingmatrixCom.py b/pyload/plugin/hoster/SharingmatrixCom.py new file mode 100644 index 000000000..c4938f9bc --- /dev/null +++ b/pyload/plugin/hoster/SharingmatrixCom.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class SharingmatrixCom(DeadHoster): + __name = "SharingmatrixCom" + __type = "hoster" + __version = "0.01" + + __pattern = r'http://(?:www\.)?sharingmatrix\.com/file/\w+' + __config = [] + + __description = """Sharingmatrix.com hoster plugin""" + __license = "GPLv3" + __authors = [("jeix", "jeix@hasnomail.de"), + ("paulking", "")] diff --git a/pyload/plugin/hoster/ShragleCom.py b/pyload/plugin/hoster/ShragleCom.py new file mode 100644 index 000000000..c435d01a7 --- /dev/null +++ b/pyload/plugin/hoster/ShragleCom.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class ShragleCom(DeadHoster): + __name = "ShragleCom" + __type = "hoster" + __version = "0.22" + + __pattern = r'http://(?:www\.)?(cloudnator|shragle)\.com/files/(?P<ID>.+?)/' + __config = [] + + __description = """Cloudnator.com (Shragle.com) hoster plugin""" + __license = "GPLv3" + __authors = [("RaNaN", "RaNaN@pyload.org"), + ("zoidberg", "zoidberg@mujmail.cz")] diff --git a/pyload/plugin/hoster/SimplyPremiumCom.py b/pyload/plugin/hoster/SimplyPremiumCom.py new file mode 100644 index 000000000..327bfdcc1 --- /dev/null +++ b/pyload/plugin/hoster/SimplyPremiumCom.py @@ -0,0 +1,77 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.internal.MultiHoster import MultiHoster +from pyload.plugin.internal.SimpleHoster import secondsToMidnight + + +class SimplyPremiumCom(MultiHoster): + __name = "SimplyPremiumCom" + __type = "hoster" + __version = "0.08" + + __pattern = r'https?://.+simply-premium\.com' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Simply-Premium.com multi-hoster plugin""" + __license = "GPLv3" + __authors = [("EvolutionClip", "evolutionclip@live.de")] + + + def setup(self): + self.chunkLimit = 16 + + + def checkErrors(self): + if '<valid>0</valid>' in self.html or ( + "You are not allowed to download from this host" in self.html and self.premium): + self.account.relogin(self.user) + self.retry() + + elif "NOTFOUND" in self.html: + self.offline() + + elif "downloadlimit" in self.html: + self.logWarning(_("Reached maximum connctions")) + self.retry(5, 60, _("Reached maximum connctions")) + + elif "trafficlimit" in self.html: + self.logWarning(_("Reached daily limit for this host")) + self.retry(wait_time=secondsToMidnight(gmt=2), reason="Daily limit for this host reached") + + elif "hostererror" in self.html: + self.logWarning(_("Hoster temporarily unavailable, waiting 1 minute and retry")) + self.retry(5, 60, _("Hoster is temporarily unavailable")) + + + def handlePremium(self, pyfile): + for _i in xrange(5): + self.html = self.load("http://www.simply-premium.com/premium.php", get={'info': "", 'link': self.pyfile.url}) + + if self.html: + self.logDebug("JSON data: " + self.html) + break + else: + self.logInfo(_("Unable to get API data, waiting 1 minute and retry")) + self.retry(5, 60, _("Unable to get API data")) + + self.checkErrors() + + try: + self.pyfile.name = re.search(r'<name>([^<]+)</name>', self.html).group(1) + + except AttributeError: + self.pyfile.name = "" + + try: + self.pyfile.size = re.search(r'<size>(\d+)</size>', self.html).group(1) + + except AttributeError: + self.pyfile.size = 0 + + try: + self.link = re.search(r'<download>([^<]+)</download>', self.html).group(1) + + except AttributeError: + self.link = 'http://www.simply-premium.com/premium.php?link=' + self.pyfile.url diff --git a/pyload/plugin/hoster/SimplydebridCom.py b/pyload/plugin/hoster/SimplydebridCom.py new file mode 100644 index 000000000..fb7beb3fb --- /dev/null +++ b/pyload/plugin/hoster/SimplydebridCom.py @@ -0,0 +1,44 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.MultiHoster import MultiHoster, replace_patterns + + +class SimplydebridCom(MultiHoster): + __name = "SimplydebridCom" + __type = "hoster" + __version = "0.17" + + __pattern = r'http://\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/sd\.php' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Simply-debrid.com multi-hoster plugin""" + __license = "GPLv3" + __authors = [("Kagenoshin", "kagenoshin@gmx.ch")] + + + def handlePremium(self, pyfile): + # fix the links for simply-debrid.com! + self.link = replace_patterns(pyfile.url, [("clz.to", "cloudzer.net/file") + ("http://share-online", "http://www.share-online") + ("ul.to", "uploaded.net/file") + ("uploaded.com", "uploaded.net") + ("filerio.com", "filerio.in") + ("lumfile.com", "lumfile.se")]) + + if 'fileparadox' in self.link: + self.link = self.link.replace("http://", "https://") + + self.html = self.load("http://simply-debrid.com/api.php", get={'dl': self.link}) + if 'tiger Link' in self.html or 'Invalid Link' in self.html or ('API' in self.html and 'ERROR' in self.html): + self.error(_("Unable to unrestrict link")) + + self.link = self.html + + self.wait(5) + + + def checkFile(self, rules={}): + if self.checkDownload({"error": "No address associated with hostname"}): + self.retry(24, 3 * 60, _("Bad file downloaded")) + + return super(SimplydebridCom, self).checkFile(rules) diff --git a/pyload/plugin/hoster/SizedriveCom.py b/pyload/plugin/hoster/SizedriveCom.py new file mode 100644 index 000000000..c9491d0ab --- /dev/null +++ b/pyload/plugin/hoster/SizedriveCom.py @@ -0,0 +1,38 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class SizedriveCom(SimpleHoster): + __name__ = "SizedriveCom" + __type__ = "hoster" + __version__ = "0.01" + + __pattern__ = r'http://(?:www\.)?sizedrive\.com/[rd]/(?P<ID>\w+)' + + __description__ = """Sizedrive.com hoster plugin""" + __license__ = "GPLv3" + __authors__ = [("GammaC0de", None)] + + + NAME_PATTERN = r'>Nome:</b> (?P<N>.+?)<' + SIZE_PATTERN = r'>Tamanho:</b>(?P<S>[\d.,]+) (?P<U>[\w^_]+)' + OFFLINE_PATTERN = r'ARQUIVO DELATADO POR' + + + def setup(self): + self.resumeDownload = False + self.multiDL = False + self.chunkLimit = 1 + + + def handleFree(self, pyfile): + self.wait(5) + self.html = self.load("http://www.sizedrive.com/getdownload.php", + post={'id': self.info['pattern']['ID']}) + + m = re.search(r'<span id="boton_download" ><a href="(.+?)"', self.html) + if m: + self.link = m.group(1) diff --git a/pyload/plugin/hoster/SmoozedCom.py b/pyload/plugin/hoster/SmoozedCom.py new file mode 100644 index 000000000..f216a95bc --- /dev/null +++ b/pyload/plugin/hoster/SmoozedCom.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- + +from pyload.utils import json_loads +from pyload.plugin.internal.MultiHoster import MultiHoster + + +class SmoozedCom(MultiHoster): + __name = "SmoozedCom" + __type = "hoster" + __version = "0.04" + + __pattern = r'^unmatchable$' #: Since we want to allow the user to specify the list of hoster to use we let MultiHoster.activate + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Smoozed.com hoster plugin""" + __license = "GPLv3" + __authors = [("", "")] + + + def handlePremium(self, pyfile): + # In some cases hostsers do not supply us with a filename at download, so we + # are going to set a fall back filename (e.g. for freakshare or xfileshare) + pyfile.name = pyfile.name.split('/').pop() #: Remove everthing before last slash + + # Correction for automatic assigned filename: Removing html at end if needed + suffix_to_remove = ["html", "htm", "php", "php3", "asp", "shtm", "shtml", "cfml", "cfm"] + temp = pyfile.name.split('.') + + if temp.pop() in suffix_to_remove: + pyfile.name = ".".join(temp) + + # Check the link + get_data = {'session_key': self.account.getAccountInfo(self.user)['session'], + 'url' : pyfile.url} + + data = json_loads(self.load("http://www2.smoozed.com/api/check", get=get_data)) + + if data['state'] != "ok": + self.fail(data['message']) + + if data['data'].get("state", "ok") != "ok": + if data['data'] == "Offline": + self.offline() + else: + self.fail(data['data']['message']) + + pyfile.name = data['data']['name'] + pyfile.size = int(data['data']['size']) + + # Start the download + header = self.load("http://www2.smoozed.com/api/download", get=get_data, just_header=True) + + if not "location" in header: + self.fail(_("Unable to initialize download")) + else: + self.link = header['location'][-1] if isinstance(header['location'], list) else header['location'] + + + def checkFile(self, rules={}): + if self.checkDownload({'error': '{"state":"error"}', + 'retry': '{"state":"retry"}'}): + self.fail(_("Error response received")) + + return super(SmoozedCom, self).checkFile(rules) diff --git a/pyload/plugin/hoster/SockshareCom.py b/pyload/plugin/hoster/SockshareCom.py new file mode 100644 index 000000000..f6cff5858 --- /dev/null +++ b/pyload/plugin/hoster/SockshareCom.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class SockshareCom(DeadHoster): + __name = "SockshareCom" + __type = "hoster" + __version = "0.05" + + __pattern = r'http://(?:www\.)?sockshare\.com/(mobile/)?(file|embed)/(?P<ID>\w+)' + __config = [] + + __description = """Sockshare.com hoster plugin""" + __license = "GPLv3" + __authors = [("jeix", "jeix@hasnomail.de"), + ("stickell", "l.stickell@yahoo.it"), + ("Walter Purcaro", "vuolter@gmail.com")] diff --git a/pyload/plugin/hoster/SolidfilesCom.py b/pyload/plugin/hoster/SolidfilesCom.py new file mode 100644 index 000000000..9998f26ad --- /dev/null +++ b/pyload/plugin/hoster/SolidfilesCom.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# +# Test links: +# http://www.solidfiles.com/d/609cdb4b1b + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class SolidfilesCom(SimpleHoster): + __name = "SolidfilesCom" + __type = "hoster" + __version = "0.02" + + __pattern = r'http://(?:www\.)?solidfiles\.com\/d/\w+' + + __description = """Solidfiles.com hoster plugin""" + __license = "GPLv3" + __authors = [("sraedler", "simon.raedler@yahoo.de")] + + + NAME_PATTERN = r'<h1 title="(?P<N>.+?)"' + SIZE_PATTERN = r'<p class="meta">(?P<S>[\d.,]+) (?P<U>[\w_^]+)' + OFFLINE_PATTERN = r'<h1>404' + + LINK_FREE_PATTERN = r'id="ddl-text" href="(.+?)"' + + + def setup(self): + self.multiDL = True + self.chunkLimit = 1 diff --git a/pyload/plugin/hoster/SoundcloudCom.py b/pyload/plugin/hoster/SoundcloudCom.py new file mode 100644 index 000000000..7ce0e3787 --- /dev/null +++ b/pyload/plugin/hoster/SoundcloudCom.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.internal.SimpleHoster import SimpleHoster +from pyload.utils import json_loads + + +class SoundcloudCom(SimpleHoster): + __name = "SoundcloudCom" + __type = "hoster" + __version = "0.11" + + __pattern = r'https?://(?:www\.)?soundcloud\.com/[\w-]+/[\w-]+' + __config = [("use_premium", "bool" , "Use premium account if available", True ), + ("quality" , "Lower;Higher", "Quality" , "Higher")] + + __description = """SoundCloud.com hoster plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + NAME_PATTERN = r'title" content="(?P<N>.+?)"' + OFFLINE_PATTERN = r'<title>"SoundCloud - Hear the worldâs sounds"</title>' + + + def handleFree(self, pyfile): + try: + song_id = re.search(r'sounds:(\d+)"', self.html).group(1) + + except Exception: + self.error(_("Could not find song id")) + + try: + client_id = re.search(r'"clientID":"(.+?)"', self.html).group(1) + + except Exception: + client_id = "b45b1aa10f1ac2941910a7f0d10f8e28" + + # url to retrieve the actual song url + streams = json_loads(self.load("https://api.soundcloud.com/tracks/%s/streams" % song_id, + get={'client_id': client_id})) + + regex = re.compile(r'[^\d]') + http_streams = sorted([(key, value) for key, value in streams.iteritems() if key.startswith('http_')], + key=lambda t: regex.sub(t[0], ''), + reverse=True) + + self.logDebug("Streams found: %s" % (http_streams or "None")) + + if http_streams: + stream_name, self.link = http_streams[0 if self.getConfig('quality') == "Higher" else -1] + pyfile.name += '.' + stream_name.split('_')[1].lower() diff --git a/pyload/plugin/hoster/SpeedLoadOrg.py b/pyload/plugin/hoster/SpeedLoadOrg.py new file mode 100644 index 000000000..383d60817 --- /dev/null +++ b/pyload/plugin/hoster/SpeedLoadOrg.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class SpeedLoadOrg(DeadHoster): + __name = "SpeedLoadOrg" + __type = "hoster" + __version = "1.02" + + __pattern = r'http://(?:www\.)?speedload\.org/(?P<ID>\w+)' + __config = [] + + __description = """Speedload.org hoster plugin""" + __license = "GPLv3" + __authors = [("stickell", "l.stickell@yahoo.it")] diff --git a/pyload/plugin/hoster/SpeedfileCz.py b/pyload/plugin/hoster/SpeedfileCz.py new file mode 100644 index 000000000..bb1b2c527 --- /dev/null +++ b/pyload/plugin/hoster/SpeedfileCz.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class SpeedfileCz(DeadHoster): + __name = "SpeedFileCz" + __type = "hoster" + __version = "0.32" + + __pattern = r'http://(?:www\.)?speedfile\.cz/.+' + __config = [] + + __description = """Speedfile.cz hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] diff --git a/pyload/plugin/hoster/SpeedyshareCom.py b/pyload/plugin/hoster/SpeedyshareCom.py new file mode 100644 index 000000000..27b866f70 --- /dev/null +++ b/pyload/plugin/hoster/SpeedyshareCom.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +# +# Test links: +# http://speedy.sh/ep2qY/Zapp-Brannigan.jpg + +import re + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class SpeedyshareCom(SimpleHoster): + __name = "SpeedyshareCom" + __type = "hoster" + __version = "0.05" + + __pattern = r'https?://(?:www\.)?(speedyshare\.com|speedy\.sh)/\w+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Speedyshare.com hoster plugin""" + __license = "GPLv3" + __authors = [("zapp-brannigan", "fuerst.reinje@web.de")] + + + NAME_PATTERN = r'class=downloadfilename>(?P<N>.*)</span></td>' + SIZE_PATTERN = r'class=sizetagtext>(?P<S>.*) (?P<U>[kKmM]?[iI]?[bB]?)</div>' + + OFFLINE_PATTERN = r'class=downloadfilenamenotfound>.*</span>' + + LINK_FREE_PATTERN = r'<a href=\'(.*)\'><img src=/gf/slowdownload\.png alt=\'Slow Download\' border=0' + + + def setup(self): + self.multiDL = False + self.chunkLimit = 1 + + + def handleFree(self, pyfile): + m = re.search(self.LINK_FREE_PATTERN, self.html) + if m is None: + self.link = m.group(1) diff --git a/pyload/plugin/hoster/StorageTo.py b/pyload/plugin/hoster/StorageTo.py new file mode 100644 index 000000000..8c0cc046e --- /dev/null +++ b/pyload/plugin/hoster/StorageTo.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class StorageTo(DeadHoster): + __name = "StorageTo" + __type = "hoster" + __version = "0.01" + + __pattern = r'http://(?:www\.)?storage\.to/get/.+' + __config = [] + + __description = """Storage.to hoster plugin""" + __license = "GPLv3" + __authors = [("mkaay", "mkaay@mkaay.de")] diff --git a/pyload/plugin/hoster/StreamCz.py b/pyload/plugin/hoster/StreamCz.py new file mode 100644 index 000000000..0500574ca --- /dev/null +++ b/pyload/plugin/hoster/StreamCz.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.network.RequestFactory import getURL +from pyload.plugin.Hoster import Hoster + + +def getInfo(urls): + result = [] + + for url in urls: + + html = getURL(url) + if re.search(StreamCz.OFFLINE_PATTERN, html): + # File offline + result.append((url, 0, 1, url)) + else: + result.append((url, 0, 2, url)) + yield result + + +class StreamCz(Hoster): + __name = "StreamCz" + __type = "hoster" + __version = "0.20" + + __pattern = r'https?://(?:www\.)?stream\.cz/[^/]+/\d+' + + __description = """Stream.cz hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + NAME_PATTERN = r'<link rel="video_src" href="http://www\.stream\.cz/\w+/(\d+)-(.+?)" />' + OFFLINE_PATTERN = r'<h1 class="commonTitle">Str.nku nebylo mo.n. nal.zt \(404\)</h1>' + + CDN_PATTERN = r'<param name="flashvars" value=".+?&id=(?P<ID>\d+)(?:&cdnLQ=(?P<cdnLQ>\d*))?(?:&cdnHQ=(?P<cdnHQ>\d*))?(?:&cdnHD=(?P<cdnHD>\d*))?&' + + + def setup(self): + self.resumeDownload = True + self.multiDL = True + + + def process(self, pyfile): + self.html = self.load(pyfile.url, decode=True) + + if re.search(self.OFFLINE_PATTERN, self.html): + self.offline() + + m = re.search(self.CDN_PATTERN, self.html) + if m is None: + self.error(_("CDN_PATTERN not found")) + cdn = m.groupdict() + self.logDebug(cdn) + for cdnkey in ("cdnHD", "cdnHQ", "cdnLQ"): + if cdnkey in cdn and cdn[cdnkey] > '': + cdnid = cdn[cdnkey] + break + else: + self.fail(_("Stream URL not found")) + + m = re.search(self.NAME_PATTERN, self.html) + if m is None: + self.error(_("NAME_PATTERN not found")) + pyfile.name = "%s-%s.%s.mp4" % (m.group(2), m.group(1), cdnkey[-2:]) + + download_url = "http://cdn-dispatcher.stream.cz/?id=" + cdnid + self.logInfo(_("STREAM: %s") % cdnkey[-2:], download_url) + self.download(download_url) diff --git a/pyload/plugin/hoster/StreamcloudEu.py b/pyload/plugin/hoster/StreamcloudEu.py new file mode 100644 index 000000000..da759ae3b --- /dev/null +++ b/pyload/plugin/hoster/StreamcloudEu.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSHoster import XFSHoster + + +class StreamcloudEu(XFSHoster): + __name = "StreamcloudEu" + __type = "hoster" + __version = "0.10" + + __pattern = r'http://(?:www\.)?streamcloud\.eu/\w{12}' + + __description = """Streamcloud.eu hoster plugin""" + __license = "GPLv3" + __authors = [("seoester", "seoester@googlemail.com")] + + + WAIT_PATTERN = r'var count = (\d+)' + + LINK_PATTERN = r'file: "(http://(stor|cdn)\d+\.streamcloud\.eu:?\d*/.*/video\.(mp4|flv))",' + + + def setup(self): + self.multiDL = True + self.chunkLimit = 1 + self.resumeDownload = self.premium diff --git a/pyload/plugin/hoster/TurbobitNet.py b/pyload/plugin/hoster/TurbobitNet.py new file mode 100644 index 000000000..a55d824a3 --- /dev/null +++ b/pyload/plugin/hoster/TurbobitNet.py @@ -0,0 +1,165 @@ +# -*- coding: utf-8 -*- + +import binascii +import pycurl +import random +import re +import time +import urllib + +from Crypto.Cipher import ARC4 + +from pyload.network.RequestFactory import getURL +from pyload.plugin.captcha.ReCaptcha import ReCaptcha +from pyload.plugin.internal.SimpleHoster import SimpleHoster, timestamp + + +class TurbobitNet(SimpleHoster): + __name = "TurbobitNet" + __type = "hoster" + __version = "0.19" + + __pattern = r'http://(?:www\.)?turbobit\.net/(?:download/free/)?(?P<ID>\w+)' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Turbobit.net hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz"), + ("prOq", "")] + + + URL_REPLACEMENTS = [(__pattern + ".*", "http://turbobit.net/\g<ID>.html")] + + COOKIES = [("turbobit.net", "user_lang", "en")] + + NAME_PATTERN = r'id="file-title">(?P<N>.+?)<' + SIZE_PATTERN = r'class="file-size">(?P<S>[\d.,]+) (?P<U>[\w^_]+)' + OFFLINE_PATTERN = r'<h2>File Not Found</h2>|html\(\'File (?:was )?not found' + + LINK_FREE_PATTERN = LINK_PREMIUM_PATTERN = r'(/download/redirect/[^"\']+)' + + LIMIT_WAIT_PATTERN = r'<div id=\'timeout\'>(\d+)<' + CAPTCHA_PATTERN = r'<img alt="Captcha" src="(.+?)"' + + + def handleFree(self, pyfile): + self.html = self.load("http://turbobit.net/download/free/%s" % self.info['pattern']['ID'], + decode=True) + + rtUpdate = self.getRtUpdate() + + self.solveCaptcha() + + self.req.http.c.setopt(pycurl.HTTPHEADER, ["X-Requested-With: XMLHttpRequest"]) + + self.html = self.load(self.getDownloadUrl(rtUpdate)) + + self.req.http.c.setopt(pycurl.HTTPHEADER, ["X-Requested-With:"]) + + m = re.search(self.LINK_FREE_PATTERN, self.html) + if m: + self.link = m.group(1) + + + def solveCaptcha(self): + for _i in xrange(5): + m = re.search(self.LIMIT_WAIT_PATTERN, self.html) + if m: + wait_time = int(m.group(1)) + self.wait(wait_time, wait_time > 60) + self.retry() + + action, inputs = self.parseHtmlForm("action='#'") + if not inputs: + self.error(_("Captcha form not found")) + self.logDebug(inputs) + + if inputs['captcha_type'] == 'recaptcha': + recaptcha = ReCaptcha(self) + inputs['recaptcha_response_field'], inputs['recaptcha_challenge_field'] = recaptcha.challenge() + else: + m = re.search(self.CAPTCHA_PATTERN, self.html) + if m is None: + self.error(_("captcha")) + captcha_url = m.group(1) + inputs['captcha_response'] = self.decryptCaptcha(captcha_url) + + self.logDebug(inputs) + self.html = self.load(self.url, post=inputs) + + if '<div class="captcha-error">Incorrect, try again!<' in self.html: + self.invalidCaptcha() + else: + self.correctCaptcha() + break + else: + self.fail(_("Invalid captcha")) + + + def getRtUpdate(self): + rtUpdate = self.getStorage("rtUpdate") + if not rtUpdate: + if self.getStorage("version") != self.__version \ + or int(self.getStorage("timestamp", 0)) + 86400000 < timestamp(): + # that's right, we are even using jdownloader updates + rtUpdate = getURL("http://update0.jdownloader.org/pluginstuff/tbupdate.js") + rtUpdate = self.decrypt(rtUpdate.splitlines()[1]) + # but we still need to fix the syntax to work with other engines than rhino + rtUpdate = re.sub(r'for each\(var (\w+) in(\[[^\]]+\])\)\{', + r'zza=\2;for(var zzi=0;zzi<zza.length;zzi++){\1=zza[zzi];', rtUpdate) + rtUpdate = re.sub(r"for\((\w+)=", r"for(var \1=", rtUpdate) + + self.setStorage("rtUpdate", rtUpdate) + self.setStorage("timestamp", timestamp()) + self.setStorage("version", self.__version) + else: + self.logError(_("Unable to download, wait for update...")) + self.tempOffline() + + return rtUpdate + + + def getDownloadUrl(self, rtUpdate): + self.req.http.lastURL = self.url + + m = re.search("(/\w+/timeout\.js\?\w+=)([^\"\'<>]+)", self.html) + if m: + url = "http://turbobit.net%s%s" % m.groups() + else: + url = "http://turbobit.net/files/timeout.js?ver=%s" % "".join(random.choice('0123456789ABCDEF') for _i in xrange(32)) + + fun = self.load(url) + + self.setWait(65, False) + + for b in [1, 3]: + self.jscode = "var id = \'%s\';var b = %d;var inn = \'%s\';%sout" % ( + self.info['pattern']['ID'], b, urllib.quote(fun), rtUpdate) + + try: + out = self.js.eval(self.jscode) + self.logDebug("URL", self.js.engine, out) + if out.startswith('/download/'): + return "http://turbobit.net%s" % out.strip() + + except Exception, e: + self.logError(e) + else: + if self.retries >= 2: + # retry with updated js + self.delStorage("rtUpdate") + else: + self.retry() + + self.wait() + + + def decrypt(self, data): + cipher = ARC4.new(binascii.hexlify('E\x15\xa1\x9e\xa3M\xa0\xc6\xa0\x84\xb6H\x83\xa8o\xa0')) + return binascii.unhexlify(cipher.encrypt(binascii.unhexlify(data))) + + + def getLocalTimeString(self): + lt = time.localtime() + tz = time.altzone if lt.tm_isdst else time.timezone + return "%s GMT%+03d%02d" % (time.strftime("%a %b %d %Y %H:%M:%S", lt), -tz // 3600, tz % 3600) diff --git a/pyload/plugin/hoster/TurbouploadCom.py b/pyload/plugin/hoster/TurbouploadCom.py new file mode 100644 index 000000000..1452f05be --- /dev/null +++ b/pyload/plugin/hoster/TurbouploadCom.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class TurbouploadCom(DeadHoster): + __name = "TurbouploadCom" + __type = "hoster" + __version = "0.03" + + __pattern = r'http://(?:www\.)?turboupload\.com/(\w+)' + __config = [] + + __description = """Turboupload.com hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] diff --git a/pyload/plugin/hoster/TusfilesNet.py b/pyload/plugin/hoster/TusfilesNet.py new file mode 100644 index 000000000..068b8df58 --- /dev/null +++ b/pyload/plugin/hoster/TusfilesNet.py @@ -0,0 +1,37 @@ +# -*- coding: utf-8 -*- + +from pyload.network.HTTPRequest import BadHeader +from pyload.plugin.internal.XFSHoster import XFSHoster + + +class TusfilesNet(XFSHoster): + __name = "TusfilesNet" + __type = "hoster" + __version = "0.10" + + __pattern = r'https?://(?:www\.)?tusfiles\.net/\w{12}' + + __description = """Tusfiles.net hoster plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com"), + ("guidobelix", "guidobelix@hotmail.it")] + + + INFO_PATTERN = r'\](?P<N>.+) - (?P<S>[\d.,]+) (?P<U>[\w^_]+)\[' + OFFLINE_PATTERN = r'>File Not Found|<Title>TusFiles - Fast Sharing Files!|The file you are trying to download is no longer available' + + + def setup(self): + self.chunkLimit = -1 + self.multiDL = True + self.resumeDownload = True + + + def downloadLink(self, link, disposition=True): + try: + return super(TusfilesNet, self).downloadLink(link, disposition) + + except BadHeader, e: + if e.code is 503: + self.multiDL = False + raise Retry("503") diff --git a/pyload/plugin/hoster/TwoSharedCom.py b/pyload/plugin/hoster/TwoSharedCom.py new file mode 100644 index 000000000..988c50620 --- /dev/null +++ b/pyload/plugin/hoster/TwoSharedCom.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class TwoSharedCom(SimpleHoster): + __name = "TwoSharedCom" + __type = "hoster" + __version = "0.13" + + __pattern = r'http://(?:www\.)?2shared\.com/(account/)?(download|get|file|document|photo|video|audio)/.+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """2Shared.com hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + NAME_PATTERN = r'<h1>(?P<N>.*)</h1>' + SIZE_PATTERN = r'<span class="dtitle">File size:</span>\s*(?P<S>[\d.,]+) (?P<U>[\w^_]+)' + OFFLINE_PATTERN = r'The file link that you requested is not valid\.|This file was deleted\.' + + LINK_FREE_PATTERN = r'window.location =\'(.+?)\';' + + + def setup(self): + self.resumeDownload = True + self.multiDL = True diff --git a/pyload/plugin/hoster/UlozTo.py b/pyload/plugin/hoster/UlozTo.py new file mode 100644 index 000000000..611541990 --- /dev/null +++ b/pyload/plugin/hoster/UlozTo.py @@ -0,0 +1,150 @@ +# -*- coding: utf-8 -*- + +import re +import time + +from pyload.utils import json_loads +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +def convertDecimalPrefix(m): + # decimal prefixes used in filesize and traffic + return ("%%.%df" % {'k': 3, 'M': 6, 'G': 9}[m.group(2)] % float(m.group(1))).replace('.', '') + + +class UlozTo(SimpleHoster): + __name = "UlozTo" + __type = "hoster" + __version = "1.09" + + __pattern = r'http://(?:www\.)?(uloz\.to|ulozto\.(cz|sk|net)|bagruj\.cz|zachowajto\.pl)/(?:live/)?(?P<ID>\w+/[^/?]*)' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Uloz.to hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + INFO_PATTERN = r'<p>File <strong>(?P<N>[^<]+)</strong> is password protected</p>' + NAME_PATTERN = r'<title>(?P<N>[^<]+) \| Uloz\.to</title>' + SIZE_PATTERN = r'<span id="fileSize">.*?(?P<S>[\d.,]+\s[kMG]?B)</span>' + OFFLINE_PATTERN = r'<title>404 - Page not found</title>|<h1 class="h1">File (has been deleted|was banned)</h1>' + + URL_REPLACEMENTS = [(r'(?<=http://)([^/]+)', "www.ulozto.net")] + SIZE_REPLACEMENTS = [(r'([\d.]+)\s([kMG])B', convertDecimalPrefix)] + + CHECK_TRAFFIC = True + DISPOSITION = False #: Remove in 0.4.10 + + ADULT_PATTERN = r'<form action="(.+?)" method="post" id="frm-askAgeForm">' + PASSWD_PATTERN = r'<div class="passwordProtectedFile">' + VIPLINK_PATTERN = r'<a href=".+?\?disclaimer=1" class="linkVip">' + TOKEN_PATTERN = r'<input type="hidden" name="_token_" .*?value="(.+?)"' + + + def setup(self): + self.chunkLimit = 16 if self.premium else 1 + self.multiDL = True + self.resumeDownload = True + + + def handleFree(self, pyfile): + action, inputs = self.parseHtmlForm('id="frm-downloadDialog-freeDownloadForm"') + if not action or not inputs: + self.error(_("Free download form not found")) + + self.logDebug("inputs.keys = " + str(inputs.keys())) + # get and decrypt captcha + if all(key in inputs for key in ("captcha_value", "captcha_id", "captcha_key")): + # Old version - last seen 9.12.2013 + self.logDebug('Using "old" version') + + captcha_value = self.decryptCaptcha("http://img.uloz.to/captcha/%s.png" % inputs['captcha_id']) + self.logDebug("CAPTCHA ID: " + inputs['captcha_id'] + ", CAPTCHA VALUE: " + captcha_value) + + inputs.update({'captcha_id': inputs['captcha_id'], 'captcha_key': inputs['captcha_key'], 'captcha_value': captcha_value}) + + elif all(key in inputs for key in ("captcha_value", "timestamp", "salt", "hash")): + # New version - better to get new parameters (like captcha reload) because of image url - since 6.12.2013 + self.logDebug('Using "new" version') + + xapca = self.load("http://www.ulozto.net/reloadXapca.php", get={'rnd': str(int(time.time()))}) + self.logDebug("xapca = " + str(xapca)) + + data = json_loads(xapca) + captcha_value = self.decryptCaptcha(str(data['image'])) + self.logDebug("CAPTCHA HASH: " + data['hash'], "CAPTCHA SALT: " + str(data['salt']), "CAPTCHA VALUE: " + captcha_value) + + inputs.update({'timestamp': data['timestamp'], 'salt': data['salt'], 'hash': data['hash'], 'captcha_value': captcha_value}) + + else: + self.error(_("CAPTCHA form changed")) + + self.download("http://www.ulozto.net" + action, post=inputs) + + + def handlePremium(self, pyfile): + self.download(pyfile.url, get={'do': "directDownload"}) + + + def checkErrors(self): + if re.search(self.ADULT_PATTERN, self.html): + self.logInfo(_("Adult content confirmation needed")) + + m = re.search(self.TOKEN_PATTERN, self.html) + if m is None: + self.error(_("TOKEN_PATTERN not found")) + + self.html = self.load(pyfile.url, + get={'do': "askAgeForm-submit"}, + post={"agree": "Confirm", "_token_": m.group(1)}) + + if self.PASSWD_PATTERN in self.html: + password = self.getPassword() + + if password: + self.logInfo(_("Password protected link, trying ") + password) + self.html = self.load(pyfile.url, + get={'do': "passwordProtectedForm-submit"}, + post={"password": password, "password_send": 'Send'}) + + if self.PASSWD_PATTERN in self.html: + self.fail(_("Incorrect password")) + else: + self.fail(_("No password found")) + + if re.search(self.VIPLINK_PATTERN, self.html): + self.html = self.load(pyfile.url, get={'disclaimer': "1"}) + + return super(UlozTo, self).checkErrors() + + + def checkFile(self, rules={}): + check = self.checkDownload({ + "wrong_captcha": re.compile(r'<ul class="error">\s*<li>Error rewriting the text.</li>'), + "offline" : re.compile(self.OFFLINE_PATTERN), + "passwd" : self.PASSWD_PATTERN, + "server_error" : 'src="http://img.ulozto.cz/error403/vykricnik.jpg"', #: paralell dl, server overload etc. + "not_found" : "<title>UloÅŸ.to</title>" + }) + + if check == "wrong_captcha": + self.invalidCaptcha() + self.retry(reason=_("Wrong captcha code")) + + elif check == "offline": + self.offline() + + elif check == "passwd": + self.fail(_("Wrong password")) + + elif check == "server_error": + self.logError(_("Server error, try downloading later")) + self.multiDL = False + self.wait(1 * 60 * 60, True) + self.retry() + + elif check == "not_found": + self.fail(_("Server error, file not downloadable")) + + return super(UlozTo, self).checkFile(rules) diff --git a/pyload/plugin/hoster/UloziskoSk.py b/pyload/plugin/hoster/UloziskoSk.py new file mode 100644 index 000000000..4a37d5ba9 --- /dev/null +++ b/pyload/plugin/hoster/UloziskoSk.py @@ -0,0 +1,68 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class UloziskoSk(SimpleHoster): + __name = "UloziskoSk" + __type = "hoster" + __version = "0.25" + + __pattern = r'http://(?:www\.)?ulozisko\.sk/.+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Ulozisko.sk hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + NAME_PATTERN = r'<div class="down1">(?P<N>[^<]+)</div>' + SIZE_PATTERN = ur'VeÄŸkosÅ¥ súboru: <strong>(?P<S>[\d.,]+) (?P<U>[\w^_]+)</strong><br />' + OFFLINE_PATTERN = ur'<span class = "red">ZadanÜ súbor neexistuje z jedného z nasledujúcich dÃŽvodov:</span>' + + LINK_FREE_PATTERN = r'<form name = "formular" action = "(.+?)" method = "post">' + ID_PATTERN = r'<input type = "hidden" name = "id" value = "(.+?)" />' + CAPTCHA_PATTERN = r'<img src="(/obrazky/obrazky\.php\?fid=.+?)" alt="" />' + IMG_PATTERN = ur'<strong>PRE ZVÃÄÅ ENIE KLIKNITE NA OBRÃZOK</strong><br /><a href = "(.+?)">' + + + def process(self, pyfile): + self.html = self.load(pyfile.url, decode=True) + self.getFileInfo() + + m = re.search(self.IMG_PATTERN, self.html) + if m: + self.link = "http://ulozisko.sk" + m.group(1) + else: + self.handleFree(pyfile) + + + def handleFree(self, pyfile): + m = re.search(self.LINK_FREE_PATTERN, self.html) + if m is None: + self.error(_("LINK_FREE_PATTERN not found")) + parsed_url = 'http://www.ulozisko.sk' + m.group(1) + + m = re.search(self.ID_PATTERN, self.html) + if m is None: + self.error(_("ID_PATTERN not found")) + id = m.group(1) + + self.logDebug("URL:" + parsed_url + ' ID:' + id) + + m = re.search(self.CAPTCHA_PATTERN, self.html) + if m is None: + self.error(_("CAPTCHA_PATTERN not found")) + captcha_url = 'http://www.ulozisko.sk' + m.group(1) + + captcha = self.decryptCaptcha(captcha_url, cookies=True) + + self.logDebug("CAPTCHA_URL:" + captcha_url + ' CAPTCHA:' + captcha) + + self.download(parsed_url, + post={"antispam": captcha, + "id" : id, + "name" : pyfile.name, + "but" : "++++STIAHNI+S%DABOR++++"}) diff --git a/pyload/plugin/hoster/UnibytesCom.py b/pyload/plugin/hoster/UnibytesCom.py new file mode 100644 index 000000000..4a2592941 --- /dev/null +++ b/pyload/plugin/hoster/UnibytesCom.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- + +import re +import urlparse +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class UnibytesCom(SimpleHoster): + __name = "UnibytesCom" + __type = "hoster" + __version = "0.12" + + __pattern = r'https?://(?:www\.)?unibytes\.com/[\w .-]{11}B' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """UniBytes.com hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + HOSTER_DOMAIN = "unibytes.com" + + INFO_PATTERN = r'<span[^>]*?id="fileName".*?>(?P<N>[^>]+)</span>\s*\((?P<S>\d.*?)\)' + + WAIT_PATTERN = r'Wait for <span id="slowRest">(\d+)</span> sec' + LINK_FREE_PATTERN = r'<a href="(.+?)">Download</a>' + + + def handleFree(self, pyfile): + domain = "http://www.%s/" % self.HOSTER_DOMAIN + action, post_data = self.parseHtmlForm('id="startForm"') + + + for _i in xrange(8): + self.logDebug(action, post_data) + self.html = self.load(urlparse.urljoin(domain, action), post=post_data, follow_location=False) + + m = re.search(r'location:\s*(\S+)', self.req.http.header, re.I) + if m: + self.link = m.group(1) + break + + if '>Somebody else is already downloading using your IP-address<' in self.html: + self.wait(10 * 60, True) + self.retry() + + if post_data['step'] == 'last': + m = re.search(self.LINK_FREE_PATTERN, self.html) + if m: + self.link = m.group(1) + self.correctCaptcha() + break + else: + self.invalidCaptcha() + + last_step = post_data['step'] + action, post_data = self.parseHtmlForm('id="stepForm"') + + if last_step == 'timer': + m = re.search(self.WAIT_PATTERN, self.html) + self.wait(m.group(1) if m else 60, False) + + elif last_step in ("captcha", "last"): + post_data['captcha'] = self.decryptCaptcha(urlparse.urljoin(domain, "/captcha.jpg")) + + else: + self.fail(_("No valid captcha code entered")) diff --git a/pyload/plugin/hoster/UnrestrictLi.py b/pyload/plugin/hoster/UnrestrictLi.py new file mode 100644 index 000000000..b0230a7b4 --- /dev/null +++ b/pyload/plugin/hoster/UnrestrictLi.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- + +from pyload.utils import json_loads +from pyload.plugin.internal.MultiHoster import MultiHoster +from pyload.plugin.internal.SimpleHoster import secondsToMidnight + + +class UnrestrictLi(MultiHoster): + __name = "UnrestrictLi" + __type = "hoster" + __version = "0.22" + + __pattern = r'https?://(?:www\.)?(unrestrict|unr)\.li/dl/[\w^_]+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Unrestrict.li multi-hoster plugin""" + __license = "GPLv3" + __authors = [("stickell", "l.stickell@yahoo.it")] + + + LOGIN_ACCOUNT = False + + + def setup(self): + self.chunkLimit = 16 + self.resumeDownload = True + + + def handleFree(self, pyfile): + for _i in xrange(5): + self.html = self.load('https://unrestrict.li/unrestrict.php', + post={'link': pyfile.url, 'domain': 'long'}) + + self.logDebug("JSON data: " + self.html) + + if self.html: + break + else: + self.logInfo(_("Unable to get API data, waiting 1 minute and retry")) + self.retry(5, 60, "Unable to get API data") + + if 'Expired session' in self.html \ + or ("You are not allowed to download from this host" in self.html and self.premium): + self.account.relogin(self.user) + self.retry() + + elif "File offline" in self.html: + self.offline() + + elif "You are not allowed to download from this host" in self.html: + self.fail(_("You are not allowed to download from this host")) + + elif "You have reached your daily limit for this host" in self.html: + self.logWarning(_("Reached daily limit for this host")) + self.retry(5, secondsToMidnight(gmt=2), "Daily limit for this host reached") + + elif "ERROR_HOSTER_TEMPORARILY_UNAVAILABLE" in self.html: + self.logInfo(_("Hoster temporarily unavailable, waiting 1 minute and retry")) + self.retry(5, 60, "Hoster is temporarily unavailable") + + self.html = json_loads(self.html) + self.link = self.html.keys()[0] + self.api_data = self.html[self.link] + + if hasattr(self, 'api_data'): + self.setNameSize() + + + def checkFile(self, rules={}): + super(UnrestrictLi, self).checkFile(rules) + + if self.getConfig('history'): + self.load("https://unrestrict.li/history/", get={'delete': "all"}) + self.logInfo(_("Download history deleted")) + + + def setNameSize(self): + if 'name' in self.api_data: + self.pyfile.name = self.api_data['name'] + if 'size' in self.api_data: + self.pyfile.size = self.api_data['size'] diff --git a/pyload/plugin/hoster/UpleaCom.py b/pyload/plugin/hoster/UpleaCom.py new file mode 100644 index 000000000..0459a82ea --- /dev/null +++ b/pyload/plugin/hoster/UpleaCom.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- + +import re +import urlparse + +from pyload.plugin.internal.XFSHoster import XFSHoster + + +class UpleaCom(XFSHoster): + __name = "UpleaCom" + __type = "hoster" + __version = "0.10" + + __pattern = r'https?://(?:www\.)?uplea\.com/dl/\w{15}' + + __description = """Uplea.com hoster plugin""" + __license = "GPLv3" + __authors = [("Redleon", None), + ("GammaC0de", None)] + + + DISPOSITION = False #@TODO: Remove in 0.4.10 + + HOSTER_DOMAIN = "uplea.com" + + SIZE_REPLACEMENTS = [('ko','KB'), ('mo','MB'), ('go','GB'), ('Ko','KB'), ('Mo','MB'), ('Go','GB')] + + NAME_PATTERN = r'<span class="gold-text">(?P<N>.+?)</span>' + SIZE_PATTERN = r'<span class="label label-info agmd">(?P<S>[\d.,]+) (?P<U>[\w^_]+?)</span>' + OFFLINE_PATTERN = r'>You followed an invalid or expired link' + + LINK_PATTERN = r'"(https?://\w+\.uplea\.com/anonym/.*?)"' + + PREMIUM_ONLY_PATTERN = r'You need to have a Premium subscription to download this file' + WAIT_PATTERN = r'timeText: ?([\d.]+),' + STEP_PATTERN = r'<a href="(/step/.+)">' + + + def setup(self): + self.multiDL = False + self.chunkLimit = 1 + self.resumeDownload = True + + + def handleFree(self, pyfile): + m = re.search(self.STEP_PATTERN, self.html) + if m is None: + self.error(_("STEP_PATTERN not found")) + + self.html = self.load(urlparse.urljoin("http://uplea.com/", m.group(1))) + + m = re.search(self.WAIT_PATTERN, self.html) + if m: + self.logDebug(_("Waiting %s seconds") % m.group(1)) + self.wait(m.group(1), True) + self.retry() + + m = re.search(self.LINK_PATTERN, self.html) + if m is None: + self.error(_("LINK_PATTERN not found")) + + self.link = m.group(1) + self.wait(15) diff --git a/pyload/plugin/hoster/UploadStationCom.py b/pyload/plugin/hoster/UploadStationCom.py new file mode 100644 index 000000000..ab55c71d4 --- /dev/null +++ b/pyload/plugin/hoster/UploadStationCom.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class UploadStationCom(DeadHoster): + __name = "UploadStationCom" + __type = "hoster" + __version = "0.52" + + __pattern = r'http://(?:www\.)?uploadstation\.com/file/(?P<ID>\w+)' + __config = [] + + __description = """UploadStation.com hoster plugin""" + __license = "GPLv3" + __authors = [("fragonib", "fragonib[AT]yahoo[DOT]es"), + ("zoidberg", "zoidberg@mujmail.cz")] diff --git a/pyload/plugin/hoster/UploadableCh.py b/pyload/plugin/hoster/UploadableCh.py new file mode 100644 index 000000000..c030c4178 --- /dev/null +++ b/pyload/plugin/hoster/UploadableCh.py @@ -0,0 +1,74 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.captcha.ReCaptcha import ReCaptcha +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class UploadableCh(SimpleHoster): + __name = "UploadableCh" + __type = "hoster" + __version = "0.09" + + __pattern = r'http://(?:www\.)?uploadable\.ch/file/(?P<ID>\w+)' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Uploadable.ch hoster plugin""" + __license = "GPLv3" + __authors = [("zapp-brannigan", "fuerst.reinje@web.de"), + ("Walter Purcaro", "vuolter@gmail.com")] + + + URL_REPLACEMENTS = [(__pattern + ".*", r'http://www.uploadable.ch/file/\g<ID>')] + + INFO_PATTERN = r'div id=\"file_name\" title=.*>(?P<N>.+)<span class=\"filename_normal\">\((?P<S>[\d.]+) (?P<U>\w+)\)</span><' + + OFFLINE_PATTERN = r'>(File not available|This file is no longer available)' + TEMP_OFFLINE_PATTERN = r'<div class="icon_err">' + + WAIT_PATTERN = r'>Please wait.+?<' + + RECAPTCHA_KEY = "6LdlJuwSAAAAAPJbPIoUhyqOJd7-yrah5Nhim5S3" + + + def handleFree(self, pyfile): + # Click the "free user" button and wait + a = self.load(pyfile.url, post={'downloadLink': "wait"}, decode=True) + self.logDebug(a) + + self.wait(30) + + # Make the recaptcha appear and show it the pyload interface + b = self.load(pyfile.url, post={'checkDownload': "check"}, decode=True) + self.logDebug(b) #: Expected output: {"success":"showCaptcha"} + + recaptcha = ReCaptcha(self) + + response, challenge = recaptcha.challenge(self.RECAPTCHA_KEY) + + # Submit the captcha solution + self.load("http://www.uploadable.ch/checkReCaptcha.php", + post={'recaptcha_challenge_field' : challenge, + 'recaptcha_response_field' : response, + 'recaptcha_shortencode_field': self.info['pattern']['ID']}, + decode=True) + + self.wait(3) + + # Get ready for downloading + self.load(pyfile.url, post={'downloadLink': "show"}, decode=True) + + self.wait(3) + + # Download the file + self.download(pyfile.url, post={'download': "normal"}, disposition=True) + + + def checkFile(self, rules={}): + if self.checkDownload({'wait': re.compile("Please wait for")}): + self.logInfo("Downloadlimit reached, please wait or reconnect") + self.wait(60 * 60, True) + self.retry() + + return super(UploadableCh, self).checkFile(rules) diff --git a/pyload/plugin/hoster/UploadboxCom.py b/pyload/plugin/hoster/UploadboxCom.py new file mode 100644 index 000000000..eb769919e --- /dev/null +++ b/pyload/plugin/hoster/UploadboxCom.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class UploadboxCom(DeadHoster): + __name = "Uploadbox" + __type = "hoster" + __version = "0.05" + + __pattern = r'http://(?:www\.)?uploadbox\.com/files/.+' + __config = [] + + __description = """UploadBox.com hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] diff --git a/pyload/plugin/hoster/UploadedTo.py b/pyload/plugin/hoster/UploadedTo.py new file mode 100644 index 000000000..308b7c1ca --- /dev/null +++ b/pyload/plugin/hoster/UploadedTo.py @@ -0,0 +1,121 @@ +# -*- coding: utf-8 -*- + +import re +import time + +from pyload.network.RequestFactory import getURL +from pyload.plugin.captcha.ReCaptcha import ReCaptcha +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class UploadedTo(SimpleHoster): + __name = "UploadedTo" + __type = "hoster" + __version = "0.87" + + __pattern = r'https?://(?:www\.)?(uploaded\.(to|net)|ul\.to)(/file/|/?\?id=|.*?&id=|/)(?P<ID>\w+)' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Uploaded.net hoster plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + DISPOSITION = False + + API_KEY = "lhF2IeeprweDfu9ccWlxXVVypA5nA3EL" + + URL_REPLACEMENTS = [(__pattern + ".*", r'http://uploaded.net/file/\g<ID>')] + + TEMP_OFFLINE_PATTERN = r'<title>uploaded\.net - Maintenance' + + LINK_PREMIUM_PATTERN = r'<div class="tfree".*\s*<form method="post" action="(.+?)"' + + WAIT_PATTERN = r'Current waiting period: <span>(\d+)' + DL_LIMIT_ERROR = r'You have reached the max. number of possible free downloads for this hour' + + + @classmethod + def apiInfo(cls, url="", get={}, post={}): + info = super(UploadedTo, cls).apiInfo(url) + + for _i in xrange(5): + html = getURL("http://uploaded.net/api/filemultiple", + get={"apikey": cls.API_KEY, 'id_0': re.match(cls.__pattern, url).group('ID')}, + decode=True) + + if html != "can't find request": + api = html.split(",", 4) + if api[0] == "online": + info.update({'name': api[4].strip(), 'size': api[2], 'status': 2}) + else: + info['status'] = 1 + break + else: + time.sleep(3) + + return info + + + def setup(self): + self.multiDL = self.resumeDownload = self.premium + self.chunkLimit = 1 #: critical problems with more chunks + + + def checkErrors(self): + if 'var free_enabled = false;' in self.html: + self.logError(_("Free-download capacities exhausted")) + self.retry(24, 5 * 60) + + elif "limit-size" in self.html: + self.fail(_("File too big for free download")) + + elif "limit-slot" in self.html: #: Temporary restriction so just wait a bit + self.wait(30 * 60, True) + self.retry() + + elif "limit-parallel" in self.html: + self.fail(_("Cannot download in parallel")) + + elif "limit-dl" in self.html or self.DL_LIMIT_ERROR in self.html: #: limit-dl + self.wait(3 * 60 * 60, True) + self.retry() + + elif '"err":"captcha"' in self.html: + self.invalidCaptcha() + + else: + m = re.search(self.WAIT_PATTERN, self.html) + if m: + self.wait(m.group(1)) + + + def handleFree(self, pyfile): + self.load("http://uploaded.net/language/en", just_header=True) + + self.html = self.load("http://uploaded.net/js/download.js", decode=True) + + recaptcha = ReCaptcha(self) + response, challenge = recaptcha.challenge() + + self.html = self.load("http://uploaded.net/io/ticket/captcha/%s" % self.info['pattern']['ID'], + post={'recaptcha_challenge_field': challenge, + 'recaptcha_response_field' : response}) + + if "type:'download'" in self.html: + self.correctCaptcha() + try: + self.link = re.search("url:'(.+?)'", self.html).group(1) + + except Exception: + pass + + self.checkErrors() + + + def checkFile(self, rules={}): + if self.checkDownload({'limit-dl': self.DL_LIMIT_ERROR}): + self.wait(3 * 60 * 60, True) + self.retry() + + return super(UploadedTo, self).checkFile(rules) diff --git a/pyload/plugin/hoster/UploadhereCom.py b/pyload/plugin/hoster/UploadhereCom.py new file mode 100644 index 000000000..789f5e9f7 --- /dev/null +++ b/pyload/plugin/hoster/UploadhereCom.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class UploadhereCom(DeadHoster): + __name = "UploadhereCom" + __type = "hoster" + __version = "0.12" + + __pattern = r'http://(?:www\.)?uploadhere\.com/\w{10}' + __config = [] + + __description = """Uploadhere.com hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] diff --git a/pyload/plugin/hoster/UploadheroCom.py b/pyload/plugin/hoster/UploadheroCom.py new file mode 100644 index 000000000..4a01d5db0 --- /dev/null +++ b/pyload/plugin/hoster/UploadheroCom.py @@ -0,0 +1,67 @@ +# -*- coding: utf-8 -*- +# +# Test links: +# http://uploadhero.co/dl/wQBRAVSM + +import re +import urlparse + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class UploadheroCom(SimpleHoster): + __name = "UploadheroCom" + __type = "hoster" + __version = "0.18" + + __pattern = r'http://(?:www\.)?uploadhero\.com?/dl/\w+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """UploadHero.co plugin""" + __license = "GPLv3" + __authors = [("mcmyst", "mcmyst@hotmail.fr"), + ("zoidberg", "zoidberg@mujmail.cz")] + + + NAME_PATTERN = r'<div class="nom_de_fichier">(?P<N>.+?)<' + SIZE_PATTERN = r'>Filesize: </span><strong>(?P<S>[\d.,]+) (?P<U>[\w^_]+)' + OFFLINE_PATTERN = r'<p class="titre_dl_2">' + + COOKIES = [("uploadhero.co", "lang", "en")] + + IP_BLOCKED_PATTERN = r'href="(/lightbox_block_download\.php\?min=.+?)"' + IP_WAIT_PATTERN = r'<span id="minutes">(\d+)</span>.*\s*<span id="seconds">(\d+)</span>' + + CAPTCHA_PATTERN = r'"(/captchadl\.php\?\w+)"' + + LINK_FREE_PATTERN = r'var magicomfg = \'<a href="(.+?)"|"(http://storage\d+\.uploadhero\.co.+?)"' + LINK_PREMIUM_PATTERN = r'<a href="(.+?)" id="downloadnow"' + + + def handleFree(self, pyfile): + m = re.search(self.CAPTCHA_PATTERN, self.html) + if m is None: + self.error(_("Captcha not found")) + + captcha = self.decryptCaptcha(urlparse.urljoin("http://uploadhero.co", m.group(1))) + + self.html = self.load(pyfile.url, + get={"code": captcha}) + + m = re.search(self.LINK_FREE_PATTERN, self.html) + if m: + self.link = m.group(1) or m.group(2) + self.wait(50) + + + def checkErrors(self): + m = re.search(self.IP_BLOCKED_PATTERN, self.html) + if m: + self.html = self.load(urlparse.urljoin("http://uploadhero.co", m.group(1))) + + m = re.search(self.IP_WAIT_PATTERN, self.html) + wait_time = (int(m.group(1)) * 60 + int(m.group(2))) if m else 5 * 60 + self.wait(wait_time, True) + self.retry() + + return super(UploadheroCom, self).checkErrors() diff --git a/pyload/plugin/hoster/UploadingCom.py b/pyload/plugin/hoster/UploadingCom.py new file mode 100644 index 000000000..e6c4cb7de --- /dev/null +++ b/pyload/plugin/hoster/UploadingCom.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- + +import pycurl +import re + +from pyload.utils import json_loads +from pyload.plugin.internal.SimpleHoster import SimpleHoster, timestamp + + +class UploadingCom(SimpleHoster): + __name = "UploadingCom" + __type = "hoster" + __version = "0.40" + + __pattern = r'http://(?:www\.)?uploading\.com/files/(?:get/)?(?P<ID>\w+)' + + __description = """Uploading.com hoster plugin""" + __license = "GPLv3" + __authors = [("jeix", "jeix@hasnomail.de"), + ("mkaay", "mkaay@mkaay.de"), + ("zoidberg", "zoidberg@mujmail.cz")] + + + NAME_PATTERN = r'id="file_title">(?P<N>.+)</' + SIZE_PATTERN = r'size tip_container">(?P<S>[\d.,]+) (?P<U>[\w^_]+)<' + OFFLINE_PATTERN = r'(Page|file) not found' + + COOKIES = [("uploading.com", "lang", "1"), + (".uploading.com", "language", "1"), + (".uploading.com", "setlang", "en"), + (".uploading.com", "_lang", "en")] + + + def process(self, pyfile): + if not "/get/" in pyfile.url: + pyfile.url = pyfile.url.replace("/files", "/files/get") + + self.html = self.load(pyfile.url, decode=True) + self.getFileInfo() + + if self.premium: + self.handlePremium(pyfile) + else: + self.handleFree(pyfile) + + + def handlePremium(self, pyfile): + postData = {'action': 'get_link', + 'code' : self.info['pattern']['ID'], + 'pass' : 'undefined'} + + self.html = self.load('http://uploading.com/files/get/?JsHttpRequest=%d-xml' % timestamp(), post=postData) + url = re.search(r'"link"\s*:\s*"(.*?)"', self.html) + if url: + self.link = url.group(1).replace("\\/", "/") + + raise Exception("Plugin defect") + + + def handleFree(self, pyfile): + m = re.search('<h2>((Daily )?Download Limit)</h2>', self.html) + if m: + pyfile.error = m.group(1) + self.logWarning(pyfile.error) + self.retry(6, (6 * 60 if m.group(2) else 15) * 60, pyfile.error) + + ajax_url = "http://uploading.com/files/get/?ajax" + self.req.http.c.setopt(pycurl.HTTPHEADER, ["X-Requested-With: XMLHttpRequest"]) + self.req.http.lastURL = pyfile.url + + res = json_loads(self.load(ajax_url, post={'action': 'second_page', 'code': self.info['pattern']['ID']})) + + if 'answer' in res and 'wait_time' in res['answer']: + wait_time = int(res['answer']['wait_time']) + self.logInfo(_("Waiting %d seconds") % wait_time) + self.wait(wait_time) + else: + self.error(_("No AJAX/WAIT")) + + res = json_loads(self.load(ajax_url, post={'action': 'get_link', 'code': self.info['pattern']['ID'], 'pass': 'false'})) + + if 'answer' in res and 'link' in res['answer']: + url = res['answer']['link'] + else: + self.error(_("No AJAX/URL")) + + self.html = self.load(url) + m = re.search(r'<form id="file_form" action="(.*?)"', self.html) + if m: + url = m.group(1) + else: + self.error(_("No URL")) + + self.link = url diff --git a/pyload/plugin/hoster/UploadkingCom.py b/pyload/plugin/hoster/UploadkingCom.py new file mode 100644 index 000000000..31d8874d8 --- /dev/null +++ b/pyload/plugin/hoster/UploadkingCom.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class UploadkingCom(DeadHoster): + __name = "UploadkingCom" + __type = "hoster" + __version = "0.14" + + __pattern = r'http://(?:www\.)?uploadking\.com/\w{10}' + __config = [] + + __description = """UploadKing.com hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] diff --git a/pyload/plugin/hoster/UpstoreNet.py b/pyload/plugin/hoster/UpstoreNet.py new file mode 100644 index 000000000..adf63e382 --- /dev/null +++ b/pyload/plugin/hoster/UpstoreNet.py @@ -0,0 +1,70 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.captcha.ReCaptcha import ReCaptcha +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class UpstoreNet(SimpleHoster): + __name = "UpstoreNet" + __type = "hoster" + __version = "0.05" + + __pattern = r'https?://(?:www\.)?upstore\.net/' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Upstore.Net File Download Hoster""" + __license = "GPLv3" + __authors = [("igel", "igelkun@myopera.com")] + + + INFO_PATTERN = r'<div class="comment">.*?</div>\s*\n<h2 style="margin:0">(?P<N>.*?)</h2>\s*\n<div class="comment">\s*\n\s*(?P<S>[\d.,]+) (?P<U>[\w^_]+)' + OFFLINE_PATTERN = r'<span class="error">File not found</span>' + + WAIT_PATTERN = r'var sec = (\d+)' + CHASH_PATTERN = r'<input type="hidden" name="hash" value="(.+?)">' + LINK_FREE_PATTERN = r'<a href="(https?://.*?)" target="_blank"><b>' + + + def handleFree(self, pyfile): + # STAGE 1: get link to continue + m = re.search(self.CHASH_PATTERN, self.html) + if m is None: + self.error(_("CHASH_PATTERN not found")) + chash = m.group(1) + self.logDebug("Read hash " + chash) + # continue to stage2 + post_data = {'hash': chash, 'free': 'Slow download'} + self.html = self.load(pyfile.url, post=post_data, decode=True) + + # STAGE 2: solv captcha and wait + # first get the infos we need: recaptcha key and wait time + recaptcha = ReCaptcha(self) + + # try the captcha 5 times + for _i in xrange(5): + m = re.search(self.WAIT_PATTERN, self.html) + if m is None: + self.error(_("Wait pattern not found")) + wait_time = int(m.group(1)) + + # then, do the waiting + self.wait(wait_time) + + # then, handle the captcha + response, challenge = recaptcha.challenge() + post_data.update({'recaptcha_challenge_field': challenge, + 'recaptcha_response_field' : response}) + + self.html = self.load(pyfile.url, post=post_data, decode=True) + + # STAGE 3: get direct link + m = re.search(self.LINK_FREE_PATTERN, self.html, re.S) + if m: + break + + if m is None: + self.error(_("Download link not found")) + + self.link = m.group(1) diff --git a/pyload/plugin/hoster/UptoboxCom.py b/pyload/plugin/hoster/UptoboxCom.py new file mode 100644 index 000000000..a2d1e8fcc --- /dev/null +++ b/pyload/plugin/hoster/UptoboxCom.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.XFSHoster import XFSHoster + + +class UptoboxCom(XFSHoster): + __name = "UptoboxCom" + __type = "hoster" + __version = "0.18" + + __pattern = r'https?://(?:www\.)?(uptobox|uptostream)\.com/\w{12}' + + __description = """Uptobox.com hoster plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + INFO_PATTERN = r'"para_title">(?P<N>.+) \((?P<S>[\d.,]+) (?P<U>[\w^_]+)\)' + OFFLINE_PATTERN = r'>(File not found|Access Denied|404 Not Found)' + TEMP_OFFLINE_PATTERN = r'>Service Unavailable' + + LINK_PATTERN = r'"(https?://\w+\.uptobox\.com/d/.*?)"' + + ERROR_PATTERN = r'>(You have to wait.+till next download.)<' #@TODO: Check XFSHoster ERROR_PATTERN + + + def setup(self): + self.multiDL = True + self.chunkLimit = 1 + self.resumeDownload = True diff --git a/pyload/plugin/hoster/VeehdCom.py b/pyload/plugin/hoster/VeehdCom.py new file mode 100644 index 000000000..6e28164de --- /dev/null +++ b/pyload/plugin/hoster/VeehdCom.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.Hoster import Hoster + + +class VeehdCom(Hoster): + __name = "VeehdCom" + __type = "hoster" + __version = "0.23" + + __pattern = r'http://veehd\.com/video/\d+_\S+' + __config = [("filename_spaces", "bool", "Allow spaces in filename", False), + ("replacement_char", "str", "Filename replacement character", "_")] + + __description = """Veehd.com hoster plugin""" + __license = "GPLv3" + __authors = [("cat", "cat@pyload")] + + + def setup(self): + self.multiDL = True + self.req.canContinue = True + + + def process(self, pyfile): + self.download_html() + if not self.file_exists(): + self.offline() + + pyfile.name = self.get_file_name() + self.download(self.get_file_url()) + + + def download_html(self): + url = self.pyfile.url + self.logDebug("Requesting page: %s" % url) + self.html = self.load(url) + + + def file_exists(self): + if not self.html: + self.download_html() + + if '<title>Veehd</title>' in self.html: + return False + return True + + + def get_file_name(self): + if not self.html: + self.download_html() + + m = re.search(r'<title.*?>([^<]+) on Veehd</title>', self.html) + if m is None: + self.error(_("Video title not found")) + + name = m.group(1) + + # replace unwanted characters in filename + if self.getConfig('filename_spaces'): + pattern = '[^\w ]+' + else: + pattern = '[^\w.]+' + + return re.sub(pattern, self.getConfig('replacement_char'), name) + '.avi' + + + def get_file_url(self): + """ returns the absolute downloadable filepath + """ + if not self.html: + self.download_html() + + m = re.search(r'<embed type="video/divx" src="(http://([^/]*\.)?veehd\.com/dl/.+?)"', + self.html) + if m is None: + self.error(_("Embedded video url not found")) + + return m.group(1) diff --git a/pyload/plugin/hoster/VeohCom.py b/pyload/plugin/hoster/VeohCom.py new file mode 100644 index 000000000..e1ee00ea1 --- /dev/null +++ b/pyload/plugin/hoster/VeohCom.py @@ -0,0 +1,51 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class VeohCom(SimpleHoster): + __name = "VeohCom" + __type = "hoster" + __version = "0.22" + + __pattern = r'http://(?:www\.)?veoh\.com/(tv/)?(watch|videos)/(?P<ID>v\w+)' + __config = [("use_premium", "bool" , "Use premium account if available", True ), + ("quality" , "Low;High;Auto", "Quality" , "Auto")] + + __description = """Veoh.com hoster plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + NAME_PATTERN = r'<meta name="title" content="(?P<N>.*?)"' + OFFLINE_PATTERN = r'>Sorry, we couldn\'t find the video you were looking for' + + URL_REPLACEMENTS = [(__pattern + ".*", r'http://www.veoh.com/watch/\g<ID>')] + + COOKIES = [("veoh.com", "lassieLocale", "en")] + + + def setup(self): + self.resumeDownload = True + self.multiDL = True + self.chunkLimit = -1 + + + def handleFree(self, pyfile): + quality = self.getConfig('quality') + if quality == "Auto": + quality = ("High", "Low") + + for q in quality: + pattern = r'"fullPreviewHash%sPath":"(.+?)"' % q + m = re.search(pattern, self.html) + if m: + pyfile.name += ".mp4" + self.link = m.group(1).replace("\\", "") + return + else: + self.logInfo(_("No %s quality video found") % q.upper()) + else: + self.fail(_("No video found!")) diff --git a/pyload/plugin/hoster/VidPlayNet.py b/pyload/plugin/hoster/VidPlayNet.py new file mode 100644 index 000000000..5d98d2fb3 --- /dev/null +++ b/pyload/plugin/hoster/VidPlayNet.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# +# Test links: +# http://vidplay.net/38lkev0h3jv0 + +from pyload.plugin.internal.XFSHoster import XFSHoster + + +class VidPlayNet(XFSHoster): + __name = "VidPlayNet" + __type = "hoster" + __version = "0.04" + + __pattern = r'https?://(?:www\.)?vidplay\.net/\w{12}' + + __description = """VidPlay.net hoster plugin""" + __license = "GPLv3" + __authors = [("t4skforce", "t4skforce1337[AT]gmail[DOT]com")] + + + NAME_PATTERN = r'<b>Password:</b></div>\s*<h[1-6]>(?P<N>[^<]+)</h[1-6]>' diff --git a/pyload/plugin/hoster/VimeoCom.py b/pyload/plugin/hoster/VimeoCom.py new file mode 100644 index 000000000..f83e15815 --- /dev/null +++ b/pyload/plugin/hoster/VimeoCom.py @@ -0,0 +1,72 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class VimeoCom(SimpleHoster): + __name = "VimeoCom" + __type = "hoster" + __version = "0.04" + + __pattern = r'https?://(?:www\.)?(player\.)?vimeo\.com/(video/)?(?P<ID>\d+)' + __config = [("use_premium", "bool" , "Use premium account if available" , True ), + ("quality" , "Lowest;Mobile;SD;HD;Highest", "Quality" , "Highest"), + ("original" , "bool" , "Try to download the original file", True )] + + __description = """Vimeo.com hoster plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + NAME_PATTERN = r'<title>(?P<N>.+) on Vimeo<' + OFFLINE_PATTERN = r'class="exception_header"' + TEMP_OFFLINE_PATTERN = r'Please try again in a few minutes.<' + + URL_REPLACEMENTS = [(__pattern + ".*", r'https://www.vimeo.com/\g<ID>')] + + COOKIES = [("vimeo.com", "language", "en")] + + + def setup(self): + self.resumeDownload = True + self.multiDL = True + self.chunkLimit = -1 + + + def handleFree(self, pyfile): + password = self.getPassword() + + if self.js and 'class="btn iconify_down_b"' in self.html: + html = self.js.eval(self.load(pyfile.url, get={'action': "download", 'password': password}, decode=True)) + pattern = r'href="(?P<URL>http://vimeo\.com.+?)".*?\>(?P<QL>.+?) ' + else: + html = self.load("https://player.vimeo.com/video/" + self.info['pattern']['ID'], get={'password': password}) + pattern = r'"(?P<QL>\w+)":{"profile".*?"(?P<URL>http://pdl\.vimeocdn\.com.+?)"' + + link = dict((l.group('QL').lower(), l.group('URL')) for l in re.finditer(pattern, html)) + + if self.getConfig('original'): + if "original" in link: + self.link = link[q] + return + else: + self.logInfo(_("Original file not downloadable")) + + quality = self.getConfig('quality') + if quality == "Highest": + qlevel = ("hd", "sd", "mobile") + elif quality == "Lowest": + qlevel = ("mobile", "sd", "hd") + else: + qlevel = quality.lower() + + for q in qlevel: + if q in link: + self.link = link[q] + return + else: + self.logInfo(_("No %s quality video found") % q.upper()) + else: + self.fail(_("No video found!")) diff --git a/pyload/plugin/hoster/Vipleech4UCom.py b/pyload/plugin/hoster/Vipleech4UCom.py new file mode 100644 index 000000000..cd68410f0 --- /dev/null +++ b/pyload/plugin/hoster/Vipleech4UCom.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class Vipleech4UCom(DeadHoster): + __name = "Vipleech4UCom" + __type = "hoster" + __version = "0.20" + + __pattern = r'http://(?:www\.)?vipleech4u\.com/manager\.php' + __config = [] + + __description = """Vipleech4u.com hoster plugin""" + __license = "GPLv3" + __authors = [("Kagenoshin", "kagenoshin@gmx.ch")] diff --git a/pyload/plugin/hoster/WarserverCz.py b/pyload/plugin/hoster/WarserverCz.py new file mode 100644 index 000000000..2556a1393 --- /dev/null +++ b/pyload/plugin/hoster/WarserverCz.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class WarserverCz(DeadHoster): + __name = "WarserverCz" + __type = "hoster" + __version = "0.13" + + __pattern = r'http://(?:www\.)?warserver\.cz/stahnout/\d+' + __config = [] + + __description = """Warserver.cz hoster plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] diff --git a/pyload/plugin/hoster/WebshareCz.py b/pyload/plugin/hoster/WebshareCz.py new file mode 100644 index 000000000..49a8da89f --- /dev/null +++ b/pyload/plugin/hoster/WebshareCz.py @@ -0,0 +1,61 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.network.RequestFactory import getURL +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class WebshareCz(SimpleHoster): + __name = "WebshareCz" + __type = "hoster" + __version = "0.16" + + __pattern = r'https?://(?:www\.)?webshare\.cz/(?:#/)?file/(?P<ID>\w+)' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """WebShare.cz hoster plugin""" + __license = "GPLv3" + __authors = [("stickell", "l.stickell@yahoo.it"), + ("rush", "radek.senfeld@gmail.com")] + + + @classmethod + def getInfo(cls, url="", html=""): + info = super(WebshareCz, cls).getInfo(url, html) + + if url: + info['pattern'] = re.match(cls.__pattern, url).groupdict() + + api_data = getURL("https://webshare.cz/api/file_info/", + post={'ident': info['pattern']['ID']}, + decode=True) + + if 'File not found' in api_data: + info['status'] = 1 + else: + info['status'] = 2 + info['name'] = re.search('<name>(.+)</name>', api_data).group(1) or info['name'] + info['size'] = re.search('<size>(.+)</size>', api_data).group(1) or info['size'] + + return info + + + def handleFree(self, pyfile): + wst = self.account.infos['wst'] if self.account and 'wst' in self.account.infos else "" + + api_data = getURL('https://webshare.cz/api/file_link/', + post={'ident': self.info['pattern']['ID'], 'wst': wst}, + decode=True) + + self.logDebug("API data: " + api_data) + + m = re.search('<link>(.+)</link>', api_data) + if m is None: + self.error(_("Unable to detect direct link")) + + self.link = m.group(1) + + + def handlePremium(self, pyfile): + return self.handleFree(pyfile) diff --git a/pyload/plugin/hoster/WrzucTo.py b/pyload/plugin/hoster/WrzucTo.py new file mode 100644 index 000000000..34306b75f --- /dev/null +++ b/pyload/plugin/hoster/WrzucTo.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- + +import pycurl +import re + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class WrzucTo(SimpleHoster): + __name = "WrzucTo" + __type = "hoster" + __version = "0.03" + + __pattern = r'http://(?:www\.)?wrzuc\.to/(\w+(\.wt|\.html)|(\w+/?linki/\w+))' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Wrzuc.to hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + NAME_PATTERN = r'id="file_info">\s*<strong>(?P<N>.*?)</strong>' + SIZE_PATTERN = r'class="info">\s*<tr>\s*<td>(?P<S>.*?)</td>' + + COOKIES = [("wrzuc.to", "language", "en")] + + + def setup(self): + self.multiDL = True + + + def handleFree(self, pyfile): + data = dict(re.findall(r'(md5|file): "(.*?)"', self.html)) + if len(data) != 2: + self.error(_("No file ID")) + + self.req.http.c.setopt(pycurl.HTTPHEADER, ["X-Requested-With: XMLHttpRequest"]) + self.req.http.lastURL = pyfile.url + self.load("http://www.wrzuc.to/ajax/server/prepair", post={"md5": data['md5']}) + + self.req.http.lastURL = pyfile.url + self.html = self.load("http://www.wrzuc.to/ajax/server/download_link", post={"file": data['file']}) + + data.update(re.findall(r'"(download_link|server_id)":"(.*?)"', self.html)) + if len(data) != 4: + self.error(_("No download URL")) + + self.link = "http://%s.wrzuc.to/pobierz/%s" % (data['server_id'], data['download_link']) diff --git a/pyload/plugin/hoster/WuploadCom.py b/pyload/plugin/hoster/WuploadCom.py new file mode 100644 index 000000000..2af68ced7 --- /dev/null +++ b/pyload/plugin/hoster/WuploadCom.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class WuploadCom(DeadHoster): + __name = "WuploadCom" + __type = "hoster" + __version = "0.23" + + __pattern = r'http://(?:www\.)?wupload\..+?/file/((\w+/)?\d+)(/.*)?' + __config = [] + + __description = """Wupload.com hoster plugin""" + __license = "GPLv3" + __authors = [("jeix", "jeix@hasnomail.de"), + ("Paul King", "")] diff --git a/pyload/plugin/hoster/X7To.py b/pyload/plugin/hoster/X7To.py new file mode 100644 index 000000000..865fc4b45 --- /dev/null +++ b/pyload/plugin/hoster/X7To.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class X7To(DeadHoster): + __name = "X7To" + __type = "hoster" + __version = "0.41" + + __pattern = r'http://(?:www\.)?x7\.to/' + __config = [] + + __description = """X7.to hoster plugin""" + __license = "GPLv3" + __authors = [("ernieb", "ernieb")] diff --git a/pyload/plugin/hoster/XFileSharingPro.py b/pyload/plugin/hoster/XFileSharingPro.py new file mode 100644 index 000000000..140a69a72 --- /dev/null +++ b/pyload/plugin/hoster/XFileSharingPro.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.internal.XFSHoster import XFSHoster + + +class XFileSharingPro(XFSHoster): + __name = "XFileSharingPro" + __type = "hoster" + __version = "0.45" + + __pattern = r'^unmatchable$' + + __description = """XFileSharingPro dummy hoster plugin for hook""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + URL_REPLACEMENTS = [("/embed-", "/")] + + + def _log(self, type, args): + msg = " | ".join(str(a).strip() for a in args if a) + logger = getattr(self.log, type) + logger("%s: %s: %s" % (self.getClassName(), self.HOSTER_NAME, msg or _("%s MARK" % type.upper()))) + + + def init(self): + super(XFileSharingPro, self).init() + + self.__pattern = self.core.pluginManager.hosterPlugins[self.getClassName()]['pattern'] + + self.HOSTER_DOMAIN = re.match(self.__pattern, self.pyfile.url).group("DOMAIN").lower() + self.HOSTER_NAME = "".join(part.capitalize() for part in re.split(r'(\.|\d+)', self.HOSTER_DOMAIN) if part != '.') + + account = self.core.accountManager.getAccountPlugin(self.HOSTER_NAME) + + if account and account.canUse(): + self.account = account + + elif self.account: + self.account.HOSTER_DOMAIN = self.HOSTER_DOMAIN + + else: + return + + self.user, data = self.account.selectAccount() + self.req = self.account.getAccountRequest(self.user) + self.premium = self.account.isPremium(self.user) + + + def setup(self): + self.chunkLimit = 1 + self.resumeDownload = self.premium + self.multiDL = True diff --git a/pyload/plugin/hoster/XHamsterCom.py b/pyload/plugin/hoster/XHamsterCom.py new file mode 100644 index 000000000..4be06833b --- /dev/null +++ b/pyload/plugin/hoster/XHamsterCom.py @@ -0,0 +1,128 @@ +# -*- coding: utf-8 -*- + +import re +import urllib + +from pyload.utils import json_loads +from pyload.plugin.Hoster import Hoster + + +def clean_json(json_expr): + json_expr = re.sub('[\n\r]', '', json_expr) + json_expr = re.sub(' +', '', json_expr) + json_expr = re.sub('\'', '"', json_expr) + + return json_expr + + +class XHamsterCom(Hoster): + __name = "XHamsterCom" + __type = "hoster" + __version = "0.12" + + __pattern = r'http://(?:www\.)?xhamster\.com/movies/.+' + __config = [("type", ".mp4;.flv", "Preferred type", ".mp4")] + + __description = """XHamster.com hoster plugin""" + __license = "GPLv3" + __authors = [] + + + def process(self, pyfile): + self.pyfile = pyfile + + if not self.file_exists(): + self.offline() + + if self.getConfig('type'): + self.desired_fmt = self.getConfig('type') + + pyfile.name = self.get_file_name() + self.desired_fmt + self.download(self.get_file_url()) + + + def download_html(self): + url = self.pyfile.url + self.html = self.load(url) + + + def get_file_url(self): + """ returns the absolute downloadable filepath + """ + if not self.html: + self.download_html() + + flashvar_pattern = re.compile('flashvars = ({.*?});', re.S) + json_flashvar = flashvar_pattern.search(self.html) + + if not json_flashvar: + self.error(_("flashvar not found")) + + j = clean_json(json_flashvar.group(1)) + flashvars = json_loads(j) + + if flashvars['srv']: + srv_url = flashvars['srv'] + '/' + else: + self.error(_("srv_url not found")) + + if flashvars['url_mode']: + url_mode = flashvars['url_mode'] + + + else: + self.error(_("url_mode not found")) + + if self.desired_fmt == ".mp4": + file_url = re.search(r"<a href=\"" + srv_url + "(.+?)\"", self.html) + if file_url is None: + self.error(_("file_url not found")) + file_url = file_url.group(1) + long_url = srv_url + file_url + self.logDebug("long_url = " + long_url) + else: + if flashvars['file']: + file_url = urllib.unquote(flashvars['file']) + else: + self.error(_("file_url not found")) + + if url_mode == '3': + long_url = file_url + self.logDebug("long_url = " + long_url) + else: + long_url = srv_url + "key=" + file_url + self.logDebug("long_url = " + long_url) + + return long_url + + + def get_file_name(self): + if not self.html: + self.download_html() + + pattern = r'<title>(.*?) - xHamster\.com</title>' + name = re.search(pattern, self.html) + if name is None: + pattern = r'<h1 >(.*)</h1>' + name = re.search(pattern, self.html) + if name is None: + pattern = r'http://[www.]+xhamster\.com/movies/.*/(.*?)\.html?' + name = re.match(file_name_pattern, self.pyfile.url) + if name is None: + pattern = r'<div id="element_str_id" style="display:none;">(.*)</div>' + name = re.search(pattern, self.html) + if name is None: + return "Unknown" + + return name.group(1) + + + def file_exists(self): + """ returns True or False + """ + if not self.html: + self.download_html() + if re.search(r"(.*Video not found.*)", self.html): + return False + else: + return True diff --git a/pyload/plugin/hoster/XVideosCom.py b/pyload/plugin/hoster/XVideosCom.py new file mode 100644 index 000000000..be168fbb9 --- /dev/null +++ b/pyload/plugin/hoster/XVideosCom.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- + +import re +import urllib + +from pyload.plugin.Hoster import Hoster + + +class XVideosCom(Hoster): + __name = "XVideos.com" + __type = "hoster" + __version = "0.10" + + __pattern = r'http://(?:www\.)?xvideos\.com/video(\d+)' + + __description = """XVideos.com hoster plugin""" + __license = "GPLv3" + __authors = [] + + + def process(self, pyfile): + site = self.load(pyfile.url) + pyfile.name = "%s (%s).flv" % ( + re.search(r"<h2>([^<]+)<span", site).group(1), + re.match(self.__pattern, pyfile.url).group(1), + ) + self.download(urllib.unquote(re.search(r"flv_url=([^&]+)&", site).group(1))) diff --git a/pyload/plugin/hoster/XdadevelopersCom.py b/pyload/plugin/hoster/XdadevelopersCom.py new file mode 100644 index 000000000..9183563b7 --- /dev/null +++ b/pyload/plugin/hoster/XdadevelopersCom.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -* +# +# Test links: +# http://forum.xda-developers.com/devdb/project/dl/?id=10885 + +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class XdadevelopersCom(SimpleHoster): + __name = "XdadevelopersCom" + __type = "hoster" + __version = "0.03" + + __pattern = r'https?://(?:www\.)?forum\.xda-developers\.com/devdb/project/dl/\?id=\d+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Xda-developers.com hoster plugin""" + __license = "GPLv3" + __authors = [("zapp-brannigan", "fuerst.reinje@web.de")] + + + NAME_PATTERN = r'<label>Filename:</label>\s*<div>\s*(?P<N>.*?)\n' + SIZE_PATTERN = r'<label>Size:</label>\s*<div>\s*(?P<S>[\d.,]+)(?P<U>[\w^_]+)' + OFFLINE_PATTERN = r'</i> Device Filter</h3>' + + + def setup(self): + self.multiDL = True + self.resumeDownload = True + self.chunkLimit = 1 + + + def handleFree(self, pyfile): + self.download(pyfile.url, + get={'task': "get"}) diff --git a/pyload/plugin/hoster/Xdcc.py b/pyload/plugin/hoster/Xdcc.py new file mode 100644 index 000000000..d7593a936 --- /dev/null +++ b/pyload/plugin/hoster/Xdcc.py @@ -0,0 +1,208 @@ +# -*- coding: utf-8 -*- + +import re +import socket +import struct +import sys +import time + +from select import select + +from pyload.plugin.Hoster import Hoster +from pyload.utils import fs_join + + +class Xdcc(Hoster): + __name = "Xdcc" + __type = "hoster" + __version = "0.32" + + __config = [("nick", "str", "Nickname", "pyload"), + ("ident", "str", "Ident", "pyloadident"), + ("realname", "str", "Realname", "pyloadreal")] + + __description = """Download from IRC XDCC bot""" + __license = "GPLv3" + __authors = [("jeix", "jeix@hasnomail.com")] + + + def setup(self): + self.debug = 0 #: 0,1,2 + self.timeout = 30 + self.multiDL = False + + + def process(self, pyfile): + # change request type + self.req = pyfile.m.core.requestFactory.getRequest(self.getClassName(), type="XDCC") + + self.pyfile = pyfile + for _i in xrange(0, 3): + try: + nmn = self.doDownload(pyfile.url) + self.logDebug("Download of %s finished." % nmn) + return + except socket.error, e: + if hasattr(e, "errno"): + errno = e.errno + else: + errno = e.args[0] + + if errno == 10054: + self.logDebug("Server blocked our ip, retry in 5 min") + self.setWait(300) + self.wait() + continue + + self.fail(_("Failed due to socket errors. Code: %d") % errno) + + self.fail(_("Server blocked our ip, retry again later manually")) + + + def doDownload(self, url): + self.pyfile.setStatus("waiting") #: real link + + m = re.match(r'xdcc://(.*?)/#?(.*?)/(.*?)/#?(\d+)/?', url) + server = m.group(1) + chan = m.group(2) + bot = m.group(3) + pack = m.group(4) + nick = self.getConfig('nick') + ident = self.getConfig('ident') + real = self.getConfig('realname') + + temp = server.split(':') + ln = len(temp) + if ln == 2: + host, port = temp + elif ln == 1: + host, port = temp[0], 6667 + else: + self.fail(_("Invalid hostname for IRC Server: %s") % server) + + ####################### + # CONNECT TO IRC AND IDLE FOR REAL LINK + dl_time = time.time() + + sock = socket.socket() + sock.connect((host, int(port))) + if nick == "pyload": + nick = "pyload-%d" % (time.time() % 1000) #: last 3 digits + sock.send("NICK %s\r\n" % nick) + sock.send("USER %s %s bla :%s\r\n" % (ident, host, real)) + + self.setWait(3) + self.wait() + + sock.send("JOIN #%s\r\n" % chan) + sock.send("PRIVMSG %s :xdcc send #%s\r\n" % (bot, pack)) + + # IRC recv loop + readbuffer = "" + done = False + retry = None + m = None + while True: + + # done is set if we got our real link + if done: + break + + if retry: + if time.time() > retry: + retry = None + dl_time = time.time() + sock.send("PRIVMSG %s :xdcc send #%s\r\n" % (bot, pack)) + + else: + if (dl_time + self.timeout) < time.time(): #@TODO: add in config + sock.send("QUIT :byebye\r\n") + sock.close() + self.fail(_("XDCC Bot did not answer")) + + fdset = select([sock], [], [], 0) + if sock not in fdset[0]: + continue + + readbuffer += sock.recv(1024) + temp = readbuffer.split("\n") + readbuffer = temp.pop() + + for line in temp: + if self.debug is 2: + print "*> " + unicode(line, errors='ignore') + line = line.rstrip() + first = line.split() + + if first[0] == "PING": + sock.send("PONG %s\r\n" % first[1]) + + if first[0] == "ERROR": + self.fail(_("IRC-Error: %s") % line) + + msg = line.split(None, 3) + if len(msg) != 4: + continue + + msg = { + "origin": msg[0][1:], + "action": msg[1], + "target": msg[2], + "text": msg[3][1:] + } + + if nick == msg['target'][0:len(nick)] and "PRIVMSG" == msg['action']: + if msg['text'] == "\x01VERSION\x01": + self.logDebug("Sending CTCP VERSION") + sock.send("NOTICE %s :%s\r\n" % (msg['origin'], "pyLoad! IRC Interface")) + elif msg['text'] == "\x01TIME\x01": + self.logDebug("Sending CTCP TIME") + sock.send("NOTICE %s :%d\r\n" % (msg['origin'], time.time())) + elif msg['text'] == "\x01LAG\x01": + pass #: don't know how to answer + + if not (bot == msg['origin'][0:len(bot)] + and nick == msg['target'][0:len(nick)] + and msg['action'] in ("PRIVMSG", "NOTICE")): + continue + + if self.debug is 1: + print "%s: %s" % (msg['origin'], msg['text']) + + if "You already requested that pack" in msg['text']: + retry = time.time() + 300 + + if "you must be on a known channel to request a pack" in msg['text']: + self.fail(_("Wrong channel")) + + m = re.match('\x01DCC SEND (.*?) (\d+) (\d+)(?: (\d+))?\x01', msg['text']) + if m: + done = True + + # get connection data + ip = socket.inet_ntoa(struct.pack('L', socket.ntohl(int(m.group(2))))) + port = int(m.group(3)) + packname = m.group(1) + + if len(m.groups()) > 3: + self.req.filesize = int(m.group(4)) + + self.pyfile.name = packname + + download_folder = self.config.get("general", "download_folder") + filename = fs_join(download_folder, packname) + + self.logInfo(_("Downloading %s from %s:%d") % (packname, ip, port)) + + self.pyfile.setStatus("downloading") + newname = self.req.download(ip, port, filename, sock, self.pyfile.setProgress) + if newname and newname != filename: + self.logInfo(_("%(name)s saved as %(newname)s") % {"name": self.pyfile.name, "newname": newname}) + filename = newname + + # kill IRC socket + # sock.send("QUIT :byebye\r\n") + sock.close() + + self.lastDownload = filename + return self.lastDownload diff --git a/pyload/plugin/hoster/YadiSk.py b/pyload/plugin/hoster/YadiSk.py new file mode 100644 index 000000000..a9d9e4706 --- /dev/null +++ b/pyload/plugin/hoster/YadiSk.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- + +import random +import re + +from pyload.plugin.internal.SimpleHoster import SimpleHoster +from pyload.utils import json_loads + + +class YadiSk(SimpleHoster): + __name = "YadiSk" + __type = "hoster" + __version = "0.05" + + __pattern = r'https?://yadi\.sk/d/[\w-]+' + + __description = """Yadi.sk hoster plugin""" + __license = "GPLv3" + __authors = [("GammaC0de", None)] + + + OFFLINE_PATTERN = r'Nothing found' + + + @classmethod + def getInfo(cls, url="", html=""): + info = super(YadiSk, cls).getInfo(url, html) + + if html: + if 'idclient' not in info: + info['idclient'] = "" + for _i in xrange(32): + info ['idclient'] += random.choice('0123456abcdef') + + m = re.search(r'<script id="models-client" type="application/json">(.+?)</script>', html) + if m: + api_data = json_loads(m.group(1)) + try: + for sect in api_data: + if 'model' in sect: + if sect['model'] == "config": + info['version'] = sect['data']['version'] + info['sk'] = sect['data']['sk'] + + elif sect['model'] == "resource": + info['id'] = sect['data']['id'] + info['size'] = sect['data']['meta']['size'] + info['name'] = sect['data']['name'] + + except Exception, e: + info['status'] = 8 + info['error'] = _("Unexpected server response: %s") % e.message + + else: + info['status'] = 8 + info['error'] = _("could not find required json data") + + return info + + + def setup(self): + self.resumeDownload = False + self.multiDL = False + self.chunkLimit = 1 + + + def handleFree(self, pyfile): + if any(True for _k in ['id', 'sk', 'version', 'idclient'] if _k not in self.info): + self.error(_("Missing JSON data")) + + + try: + self.html = self.load("https://yadi.sk/models/", + get={'_m': "do-get-resource-url"}, + post={'idClient': self.info['idclient'], + 'version' : self.info['version'], + '_model.0': "do-get-resource-url", + 'sk' : self.info['sk'], + 'id.0' : self.info['id']}) + + self.link = json_loads(self.html)['models'][0]['data']['file'] + + except Exception: + pass diff --git a/pyload/plugin/hoster/YibaishiwuCom.py b/pyload/plugin/hoster/YibaishiwuCom.py new file mode 100644 index 000000000..6581f7534 --- /dev/null +++ b/pyload/plugin/hoster/YibaishiwuCom.py @@ -0,0 +1,56 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.utils import json_loads +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class YibaishiwuCom(SimpleHoster): + __name = "YibaishiwuCom" + __type = "hoster" + __version = "0.14" + + __pattern = r'http://(?:www\.)?(?:u\.)?115\.com/file/(?P<ID>\w+)' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """115.com hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + NAME_PATTERN = r'file_name: \'(?P<N>.+?)\'' + SIZE_PATTERN = r'file_size: \'(?P<S>.+?)\'' + OFFLINE_PATTERN = ur'<h3><i style="color:red;">ååïŒæåç äžååšïŒäžåŠšææçå§ïŒ</i></h3>' + + LINK_FREE_PATTERN = r'(/\?ct=(pickcode|download)[^"\']+)' + + + def handleFree(self, pyfile): + m = re.search(self.LINK_FREE_PATTERN, self.html) + if m is None: + self.error(_("LINK_FREE_PATTERN not found")) + + url = m.group(1) + + self.logDebug(('FREEUSER' if m.group(2) == 'download' else 'GUEST') + ' URL', url) + + res = json_loads(self.load("http://115.com" + url, decode=False)) + if "urls" in res: + mirrors = res['urls'] + + elif "data" in res: + mirrors = res['data'] + + else: + mirrors = None + + for mr in mirrors: + try: + self.link = mr['url'].replace("\\", "") + self.logDebug("Trying URL: " + self.link) + break + except Exception: + continue + else: + self.fail(_("No working link found")) diff --git a/pyload/plugin/hoster/YoupornCom.py b/pyload/plugin/hoster/YoupornCom.py new file mode 100644 index 000000000..980211864 --- /dev/null +++ b/pyload/plugin/hoster/YoupornCom.py @@ -0,0 +1,60 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.Hoster import Hoster + + +class YoupornCom(Hoster): + __name = "YoupornCom" + __type = "hoster" + __version = "0.20" + + __pattern = r'http://(?:www\.)?youporn\.com/watch/.+' + + __description = """Youporn.com hoster plugin""" + __license = "GPLv3" + __authors = [("willnix", "willnix@pyload.org")] + + + def process(self, pyfile): + self.pyfile = pyfile + + if not self.file_exists(): + self.offline() + + pyfile.name = self.get_file_name() + self.download(self.get_file_url()) + + + def download_html(self): + url = self.pyfile.url + self.html = self.load(url, post={"user_choice": "Enter"}, cookies=False) + + + def get_file_url(self): + """ returns the absolute downloadable filepath + """ + if not self.html: + self.download_html() + + return re.search(r'(http://download\.youporn\.com/download/\d+\?save=1)">', self.html).group(1) + + + def get_file_name(self): + if not self.html: + self.download_html() + + file_name_pattern = r'<title>(.+) - ' + return re.search(file_name_pattern, self.html).group(1).replace("&", "&").replace("/", "") + '.flv' + + + def file_exists(self): + """ returns True or False + """ + if not self.html: + self.download_html() + if re.search(r"(.*invalid video_id.*)", self.html): + return False + else: + return True diff --git a/pyload/plugin/hoster/YourfilesTo.py b/pyload/plugin/hoster/YourfilesTo.py new file mode 100644 index 000000000..62b66d668 --- /dev/null +++ b/pyload/plugin/hoster/YourfilesTo.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- + +import re +import urllib + +from pyload.plugin.Hoster import Hoster + + +class YourfilesTo(Hoster): + __name = "YourfilesTo" + __type = "hoster" + __version = "0.22" + + __pattern = r'http://(?:www\.)?yourfiles\.(to|biz)/\?d=\w+' + + __description = """Youfiles.to hoster plugin""" + __license = "GPLv3" + __authors = [("jeix", "jeix@hasnomail.de"), + ("skydancer", "skydancer@hasnomail.de")] + + + def process(self, pyfile): + self.pyfile = pyfile + self.prepare() + self.download(self.get_file_url()) + + + def prepare(self): + if not self.file_exists(): + self.offline() + + self.pyfile.name = self.get_file_name() + + wait_time = self.get_waiting_time() + self.setWait(wait_time) + self.wait() + + + def get_waiting_time(self): + if not self.html: + self.download_html() + + # var zzipitime = 15; + m = re.search(r'var zzipitime = (\d+);', self.html) + if m: + sec = int(m.group(1)) + else: + sec = 0 + + return sec + + + def download_html(self): + url = self.pyfile.url + self.html = self.load(url) + + + def get_file_url(self): + """ returns the absolute downloadable filepath + """ + url = re.search(r"var bla = '(.*?)';", self.html) + if url: + url = url.group(1) + url = urllib.unquote(url.replace("http://http:/http://", "http://").replace("dumdidum", "")) + return url + else: + self.error(_("Absolute filepath not found")) + + + def get_file_name(self): + if not self.html: + self.download_html() + + return re.search("<title>(.*)</title>", self.html).group(1) + + + def file_exists(self): + """ returns True or False + """ + if not self.html: + self.download_html() + + if re.search(r"HTTP Status 404", self.html): + return False + else: + return True diff --git a/pyload/plugin/hoster/YoutubeCom.py b/pyload/plugin/hoster/YoutubeCom.py new file mode 100644 index 000000000..4998efbec --- /dev/null +++ b/pyload/plugin/hoster/YoutubeCom.py @@ -0,0 +1,192 @@ +# -*- coding: utf-8 -*- + +import os +import re +import subprocess +import urllib + +from pyload.plugin.Hoster import Hoster +from pyload.plugin.internal.SimpleHoster import replace_patterns +from pyload.utils import html_unescape + + +def which(program): + """Works exactly like the unix command which + Courtesy of http://stackoverflow.com/a/377028/675646""" + + isExe = lambda x: os.path.isfile(x) and os.access(x, os.X_OK) + + fpath, fname = os.path.split(program) + + if fpath: + if isExe(program): + return program + else: + for path in os.environ['PATH'].split(os.pathsep): + path = path.strip('"') + exe_file = os.path.join(path, program) + if isExe(exe_file): + return exe_file + + +class YoutubeCom(Hoster): + __name = "YoutubeCom" + __type = "hoster" + __version = "0.41" + + __pattern = r'https?://(?:[^/]*\.)?(youtube\.com|youtu\.be)/watch\?(?:.*&)?v=.+' + __config = [("quality", "sd;hd;fullhd;240p;360p;480p;720p;1080p;3072p", "Quality Setting" , "hd" ), + ("fmt" , "int" , "FMT/ITAG Number (0 for auto)", 0 ), + (".mp4" , "bool" , "Allow .mp4" , True ), + (".flv" , "bool" , "Allow .flv" , True ), + (".webm" , "bool" , "Allow .webm" , False), + (".3gp" , "bool" , "Allow .3gp" , False), + ("3d" , "bool" , "Prefer 3D" , False)] + + __description = """Youtube.com hoster plugin""" + __license = "GPLv3" + __authors = [("spoob", "spoob@pyload.org"), + ("zoidberg", "zoidberg@mujmail.cz")] + + + URL_REPLACEMENTS = [(r'youtu\.be/', 'youtube.com/')] + + # Invalid characters that must be removed from the file name + invalidChars = u'\u2605:?><"|\\' + + # name, width, height, quality ranking, 3D + formats = {5 : (".flv" , 400 , 240 , 1 , False), + 6 : (".flv" , 640 , 400 , 4 , False), + 17 : (".3gp" , 176 , 144 , 0 , False), + 18 : (".mp4" , 480 , 360 , 2 , False), + 22 : (".mp4" , 1280, 720 , 8 , False), + 43 : (".webm", 640 , 360 , 3 , False), + 34 : (".flv" , 640 , 360 , 4 , False), + 35 : (".flv" , 854 , 480 , 6 , False), + 36 : (".3gp" , 400 , 240 , 1 , False), + 37 : (".mp4" , 1920, 1080, 9 , False), + 38 : (".mp4" , 4096, 3072, 10, False), + 44 : (".webm", 854 , 480 , 5 , False), + 45 : (".webm", 1280, 720 , 7 , False), + 46 : (".webm", 1920, 1080, 9 , False), + 82 : (".mp4" , 640 , 360 , 3 , True ), + 83 : (".mp4" , 400 , 240 , 1 , True ), + 84 : (".mp4" , 1280, 720 , 8 , True ), + 85 : (".mp4" , 1920, 1080, 9 , True ), + 100: (".webm", 640 , 360 , 3 , True ), + 101: (".webm", 640 , 360 , 4 , True ), + 102: (".webm", 1280, 720 , 8 , True )} + + + def setup(self): + self.resumeDownload = True + self.multiDL = True + + + def process(self, pyfile): + pyfile.url = replace_patterns(pyfile.url, self.URL_REPLACEMENTS) + html = self.load(pyfile.url, decode=True) + + if re.search(r'<div id="player-unavailable" class="\s*player-width player-height\s*">', html): + self.offline() + + if "We have been receiving a large volume of requests from your network." in html: + self.tempOffline() + + # get config + use3d = self.getConfig('3d') + + if use3d: + quality = {"sd": 82, "hd": 84, "fullhd": 85, "240p": 83, "360p": 82, + "480p": 82, "720p": 84, "1080p": 85, "3072p": 85} + else: + quality = {"sd": 18, "hd": 22, "fullhd": 37, "240p": 5, "360p": 18, + "480p": 35, "720p": 22, "1080p": 37, "3072p": 38} + + desired_fmt = self.getConfig('fmt') + + if not desired_fmt: + desired_fmt = quality.get(self.getConfig('quality'), 18) + + elif desired_fmt not in self.formats: + self.logWarning(_("FMT %d unknown, using default") % desired_fmt) + desired_fmt = 0 + + # parse available streams + streams = re.search(r'"url_encoded_fmt_stream_map":"(.+?)",', html).group(1) + streams = [x.split('\u0026') for x in streams.split(',')] + streams = [dict((y.split('=', 1)) for y in x) for x in streams] + streams = [(int(x['itag']), urllib.unquote(x['url'])) for x in streams] + + # self.logDebug("Found links: %s" % streams) + + self.logDebug("AVAILABLE STREAMS: %s" % [x[0] for x in streams]) + + # build dictionary of supported itags (3D/2D) + allowed = lambda x: self.getConfig(self.formats[x][0]) + streams = [x for x in streams if x[0] in self.formats and allowed(x[0])] + + if not streams: + self.fail(_("No available stream meets your preferences")) + + fmt_dict = dict([x for x in streams if self.formats[x[0]][4] == use3d] or streams) + + self.logDebug("DESIRED STREAM: ITAG:%d (%s) %sfound, %sallowed" % + (desired_fmt, "%s %dx%d Q:%d 3D:%s" % self.formats[desired_fmt], + "" if desired_fmt in fmt_dict else "NOT ", "" if allowed(desired_fmt) else "NOT ")) + + # return fmt nearest to quality index + if desired_fmt in fmt_dict and allowed(desired_fmt): + fmt = desired_fmt + else: + sel = lambda x: self.formats[x][3] #: select quality index + comp = lambda x, y: abs(sel(x) - sel(y)) + + self.logDebug("Choosing nearest fmt: %s" % [(x, allowed(x), comp(x, desired_fmt)) for x in fmt_dict.keys()]) + + fmt = reduce(lambda x, y: x if comp(x, desired_fmt) <= comp(y, desired_fmt) and + sel(x) > sel(y) else y, fmt_dict.keys()) + + self.logDebug("Chosen fmt: %s" % fmt) + + url = fmt_dict[fmt] + + self.logDebug("URL: %s" % url) + + # set file name + file_suffix = self.formats[fmt][0] if fmt in self.formats else ".flv" + file_name_pattern = '<meta name="title" content="(.+?)">' + name = re.search(file_name_pattern, html).group(1).replace("/", "") + + # Cleaning invalid characters from the file name + name = name.encode('ascii', 'replace') + for c in self.invalidChars: + name = name.replace(c, '_') + + pyfile.name = html_unescape(name) + + time = re.search(r"t=((\d+)m)?(\d+)s", pyfile.url) + ffmpeg = which("ffmpeg") + if ffmpeg and time: + m, s = time.groups()[1:] + if m is None: + m = "0" + + pyfile.name += " (starting at %s:%s)" % (m, s) + + pyfile.name += file_suffix + filename = self.download(url) + + if ffmpeg and time: + inputfile = filename + "_" + os.rename(filename, inputfile) + + subprocess.call([ + ffmpeg, + "-ss", "00:%s:%s" % (m, s), + "-i", inputfile, + "-vcodec", "copy", + "-acodec", "copy", + filename]) + + os.remove(inputfile) diff --git a/pyload/plugin/hoster/ZDF.py b/pyload/plugin/hoster/ZDF.py new file mode 100644 index 000000000..c02eadc23 --- /dev/null +++ b/pyload/plugin/hoster/ZDF.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- + +import re + +from xml.etree.ElementTree import fromstring + +from pyload.plugin.Hoster import Hoster + + +# Based on zdfm by Roland Beermann (http://github.com/enkore/zdfm/) +class ZDF(Hoster): + __name = "ZDF Mediathek" + __type = "hoster" + __version = "0.80" + + __pattern = r'http://(?:www\.)?zdf\.de/ZDFmediathek/\D*(\d+)\D*' + + __description = """ZDF.de hoster plugin""" + __license = "GPLv3" + __authors = [] + + XML_API = "http://www.zdf.de/ZDFmediathek/xmlservice/web/beitragsDetails?id=%i" + + + @staticmethod + def video_key(video): + return ( + int(video.findtext("videoBitrate", "0")), + any(f.text == "progressive" for f in video.iter("facet")), + ) + + + @staticmethod + def video_valid(video): + return video.findtext("url").startswith("http") and video.findtext("url").endswith(".mp4") and \ + video.findtext("facets/facet").startswith("progressive") + + + @staticmethod + def get_id(url): + return int(re.search(r"\D*(\d{4,})\D*", url).group(1)) + + + def process(self, pyfile): + xml = fromstring(self.load(self.XML_API % self.get_id(pyfile.url))) + + status = xml.findtext("./status/statuscode") + if status != "ok": + self.fail(_("Error retrieving manifest")) + + video = xml.find("video") + title = video.findtext("information/title") + + pyfile.name = title + + target_url = sorted((v for v in video.iter("formitaet") if self.video_valid(v)), + key=self.video_key)[-1].findtext("url") + + self.download(target_url) diff --git a/pyload/plugin/hoster/ZShareNet.py b/pyload/plugin/hoster/ZShareNet.py new file mode 100644 index 000000000..a2461fb6a --- /dev/null +++ b/pyload/plugin/hoster/ZShareNet.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.DeadHoster import DeadHoster + + +class ZShareNet(DeadHoster): + __name = "ZShareNet" + __type = "hoster" + __version = "0.21" + + __pattern = r'https?://(?:ww[2w]\.)?zshares?\.net/.+' + __config = [] + + __description = """ZShare.net hoster plugin""" + __license = "GPLv3" + __authors = [("espes", ""), + ("Cptn Sandwich", "")] diff --git a/pyload/plugin/hoster/ZeveraCom.py b/pyload/plugin/hoster/ZeveraCom.py new file mode 100644 index 000000000..005c32cf8 --- /dev/null +++ b/pyload/plugin/hoster/ZeveraCom.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.MultiHoster import MultiHoster + + +class ZeveraCom(MultiHoster): + __name = "ZeveraCom" + __type = "hoster" + __version = "0.29" + + __pattern = r'https?://(?:www\.)zevera\.com/(getFiles\.ashx|Members/download\.ashx)\?.*ourl=.+' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Zevera.com multi-hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz"), + ("Walter Purcaro", "vuolter@gmail.com")] + + + def handlePremium(self, pyfile): + self.link = "https://%s/getFiles.ashx?ourl=%s" % (self.account.HOSTER_DOMAIN, pyfile.url) + + + def checkFile(self, rules={}): + if self.checkDownload({"error": 'action="ErrorDownload.aspx'}): + self.fail(_("Error response received")) + + return super(ZeveraCom, self).checkFile(rules) diff --git a/pyload/plugin/hoster/ZippyshareCom.py b/pyload/plugin/hoster/ZippyshareCom.py new file mode 100644 index 000000000..7f91c04e5 --- /dev/null +++ b/pyload/plugin/hoster/ZippyshareCom.py @@ -0,0 +1,92 @@ +# -*- coding: utf-8 -*- + +import re +import urllib + +from BeautifulSoup import BeautifulSoup + +from pyload.plugin.captcha.ReCaptcha import ReCaptcha +from pyload.plugin.internal.SimpleHoster import SimpleHoster + + +class ZippyshareCom(SimpleHoster): + __name = "ZippyshareCom" + __type = "hoster" + __version = "0.78" + + __pattern = r'http://www\d{0,2}\.zippyshare\.com/v(/|iew\.jsp.*key=)(?P<KEY>[\w^_]+)' + __config = [("use_premium", "bool", "Use premium account if available", True)] + + __description = """Zippyshare.com hoster plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com"), + ("sebdelsol", "seb.morin@gmail.com")] + + + COOKIES = [("zippyshare.com", "ziplocale", "en")] + + NAME_PATTERN = r'(<title>Zippyshare.com - |"/)(?P<N>[^/]+)(</title>|";)' + SIZE_PATTERN = r'>Size:.+?">(?P<S>[\d.,]+) (?P<U>[\w^_]+)' + OFFLINE_PATTERN = r'does not exist (anymore )?on this server<' + + LINK_PREMIUM_PATTERN = r"document.location = '(.+?)'" + + + def setup(self): + self.chunkLimit = -1 + self.multiDL = True + self.resumeDownload = True + + + def handleFree(self, pyfile): + recaptcha = ReCaptcha(self) + captcha_key = recaptcha.detect_key() + + if captcha_key: + try: + self.link = re.search(self.LINK_PREMIUM_PATTERN, self.html) + recaptcha.challenge() + + except Exception, e: + self.error(e) + + else: + self.link = self.get_link() + + if self.link and pyfile.name == 'file.html': + pyfile.name = urllib.unquote(self.link.split('/')[-1]) + + + def get_link(self): + # get all the scripts inside the html body + soup = BeautifulSoup(self.html) + scripts = (s.getText().strip() for s in soup.body.findAll('script', type='text/javascript')) + + # meant to be populated with the initialization of all the DOM elements found in the scripts + initScripts = set() + + + def replElementById(element): + id = element.group(1) #: id might be either 'x' (a real id) or x (a variable) + attr = element.group(4) #: attr might be None + + varName = re.sub(r'-', '', 'GVAR[%s+"_%s"]' %(id, attr)) + + realid = id.strip('"\'') + if id != realid: #: id is not a variable, so look for realid.attr in the html + initValues = filter(None, [elt.get(attr, None) for elt in soup.findAll(id=realid)]) + initValue = '"%s"' % initValues[-1] if initValues else 'null' + initScripts.add('%s = %s;' % (varName, initValue)) + + return varName + + # handle all getElementById + reVar = r'document.getElementById\(([\'"\w-]+)\)(\.)?(getAttribute\([\'"])?(\w+)?([\'"]\))?' + scripts = [re.sub(reVar, replElementById, script) for script in scripts if script] + + # add try/catch in JS to handle deliberate errors + scripts = ['\n'.join(('try{', script, '} catch(err){}')) for script in scripts] + + # get the file's url by evaluating all the scripts + scripts = ['var GVAR = {}'] + list(initScripts) + scripts + ['GVAR['dlbutton_href']'] + return self.js.eval('\n'.join(scripts)) diff --git a/pyload/plugin/hoster/__init__.py b/pyload/plugin/hoster/__init__.py new file mode 100644 index 000000000..40a96afc6 --- /dev/null +++ b/pyload/plugin/hoster/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/pyload/plugin/internal/BasePlugin.py b/pyload/plugin/internal/BasePlugin.py new file mode 100644 index 000000000..6be9880fc --- /dev/null +++ b/pyload/plugin/internal/BasePlugin.py @@ -0,0 +1,98 @@ +# -*- coding: utf-8 -*- + +import re +import urllib +import urlparse + +from pyload.network.HTTPRequest import BadHeader +from pyload.plugin.internal.SimpleHoster import getFileURL +from pyload.plugin.Hoster import Hoster + + +class BasePlugin(Hoster): + __name = "BasePlugin" + __type = "hoster" + __version = "0.43" + + __pattern = r'^unmatchable$' + + __description = """Base plugin when any other didnt fit""" + __license = "GPLv3" + __authors = [("RaNaN", "RaNaN@pyload.org"), + ("Walter Purcaro", "vuolter@gmail.com")] + + + @classmethod + def getInfo(cls, url="", html=""): #@TODO: Move to hoster class in 0.4.10 + url = urllib.unquote(url) + url_p = urlparse.urlparse(url) + return {'name' : (url_p.path.split('/')[-1] + or url_p.query.split('=', 1)[::-1][0].split('&', 1)[0] + or url_p.netloc.split('.', 1)[0]), + 'size' : 0, + 'status': 3 if url else 8, + 'url' : url} + + + def setup(self): + self.chunkLimit = -1 + self.multiDL = True + self.resumeDownload = True + def process(self, pyfile): + """main function""" + + pyfile.name = self.getInfo(pyfile.url)['name'] + + if not pyfile.url.startswith("http"): + self.fail(_("No plugin matched")) + + for _i in xrange(5): + try: + link = getFileURL(self, urllib.unquote(pyfile.url)) + + if link: + self.download(link, ref=False, disposition=True) + else: + self.fail(_("File not found")) + + except BadHeader, e: + if e.code is 404: + self.offline() + + elif e.code in (401, 403): + self.logDebug("Auth required", "Received HTTP status code: %d" % e.code) + + account = self.core.accountManager.getAccountPlugin('Http') + servers = [x['login'] for x in account.getAllAccounts()] + server = urlparse.urlparse(pyfile.url).netloc + + if server in servers: + self.logDebug("Logging on to %s" % server) + self.req.addAuth(account.getAccountData(server)['password']) + else: + pwd = self.getPassword() + if ':' in pwd: + self.req.addAuth(pwd) + else: + self.fail(_("Authorization required")) + else: + self.fail(e) + else: + break + else: + self.fail(_("No file downloaded")) #@TODO: Move to hoster class in 0.4.10 + + errmsg = self.checkDownload({'Empty file' : re.compile(r'\A\s*\Z'), + 'Html error' : re.compile(r'\A(?:\s*<.+>)?((?:[\w\s]*(?:[Ee]rror|ERROR)\s*\:?)?\s*\d{3})(?:\Z|\s+)'), + 'Html file' : re.compile(r'\A\s*<!DOCTYPE html'), + 'Request error': re.compile(r'([Aa]n error occured while processing your request)')}) + if not errmsg: + return + + try: + errmsg += " | " + self.lastCheck.group(1).strip() + except Exception: + pass + + self.logWarning("Check result: " + errmsg, "Waiting 1 minute and retry") + self.retry(3, 60, errmsg) diff --git a/pyload/plugin/internal/DeadCrypter.py b/pyload/plugin/internal/DeadCrypter.py new file mode 100644 index 000000000..daa7e1a0d --- /dev/null +++ b/pyload/plugin/internal/DeadCrypter.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.Crypter import Crypter as _Crypter + + +class DeadCrypter(_Crypter): + __name = "DeadCrypter" + __type = "crypter" + __version = "0.04" + + __pattern = r'^unmatchable$' + + __description = """Crypter is no longer available""" + __license = "GPLv3" + __authors = [("stickell", "l.stickell@yahoo.it")] + + + @classmethod + def apiInfo(cls, url="", get={}, post={}): + api = super(DeadCrypter, self).apiInfo(url, get, post) + api['status'] = 1 + return api + + + def setup(self): + self.pyfile.error = "Crypter is no longer available" + self.offline() #@TODO: self.offline("Crypter is no longer available") diff --git a/pyload/plugin/internal/DeadHoster.py b/pyload/plugin/internal/DeadHoster.py new file mode 100644 index 000000000..2e57decdb --- /dev/null +++ b/pyload/plugin/internal/DeadHoster.py @@ -0,0 +1,27 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.Hoster import Hoster as _Hoster + + +class DeadHoster(_Hoster): + __name = "DeadHoster" + __type = "hoster" + __version = "0.14" + + __pattern = r'^unmatchable$' + + __description = """Hoster is no longer available""" + __license = "GPLv3" + __authors = [("zoidberg", "zoidberg@mujmail.cz")] + + + @classmethod + def apiInfo(cls, url="", get={}, post={}): + api = super(DeadHoster, self).apiInfo(url, get, post) + api['status'] = 1 + return api + + + def setup(self): + self.pyfile.error = "Hoster is no longer available" + self.offline() #@TODO: self.offline("Hoster is no longer available") diff --git a/pyload/plugin/internal/MultiHook.py b/pyload/plugin/internal/MultiHook.py new file mode 100644 index 000000000..dc97ffea9 --- /dev/null +++ b/pyload/plugin/internal/MultiHook.py @@ -0,0 +1,284 @@ +# -*- coding: utf-8 -*- + +import re +import time +import traceback + +from pyload.plugin.Hook import Hook +from pyload.utils import decode, remove_chars + + +class MultiHook(Hook): + __name = "MultiHook" + __type = "hook" + __version = "0.44" + + __config = [("pluginmode" , "all;listed;unlisted", "Use for plugins" , "all"), + ("pluginlist" , "str" , "Plugin list (comma separated)", ""), + ("reload" , "bool" , "Reload plugin list" , True), + ("reloadinterval", "int" , "Reload interval in hours" , 12)] + + __description = """Hook plugin for multi hoster/crypter""" + __license = "GPLv3" + __authors = [("pyLoad Team" , "admin@pyload.org"), + ("Walter Purcaro", "vuolter@gmail.com")] + + + MIN_RELOAD_INTERVAL = 1 * 60 * 60 #: 1 hour + + DOMAIN_REPLACEMENTS = [(r'180upload\.com' , "hundredeightyupload.com"), + (r'bayfiles\.net' , "bayfiles.com"), + (r'cloudnator\.com' , "shragle.com"), + (r'dfiles\.eu' , "depositfiles.com"), + (r'easy-share\.com' , "crocko.com"), + (r'freakshare\.net' , "freakshare.com"), + (r'hellshare\.com' , "hellshare.cz"), + (r'ifile\.it' , "filecloud.io"), + (r'nowdownload\.\w+', "nowdownload.sx"), + (r'nowvideo\.\w+' , "nowvideo.sx"), + (r'putlocker\.com' , "firedrive.com"), + (r'share-?rapid\.cz', "multishare.cz"), + (r'ul\.to' , "uploaded.to"), + (r'uploaded\.net' , "uploaded.to"), + (r'uploadhero\.co' , "uploadhero.com"), + (r'zshares\.net' , "zshare.net"), + (r'^1' , "one"), + (r'^2' , "two"), + (r'^3' , "three"), + (r'^4' , "four"), + (r'^5' , "five"), + (r'^6' , "six"), + (r'^7' , "seven"), + (r'^8' , "eight"), + (r'^9' , "nine"), + (r'^0' , "zero")] + + + def setup(self): + self.plugins = [] + self.supported = [] + self.new_supported = [] + + self.account = None + self.pluginclass = None + self.pluginmodule = None + self.pluginname = None + self.plugintype = None + + self.initPlugin() + + + def initPlugin(self): + self.pluginname = self.getClassName() + plugin, self.plugintype = self.core.pluginManager.findPlugin(("hoster", "crypter", "container"), self.pluginname) + + if plugin: + self.pluginmodule = self.core.pluginManager.loadModule(self.plugintype, self.pluginname) + self.pluginclass = getattr(self.pluginmodule, self.pluginname) + else: + self.logWarning("Hook plugin will be deactivated due missing plugin reference") + self.setConfig('activated', False) + + + def loadAccount(self): + self.account = self.core.accountManager.getAccountPlugin(self.pluginname) + + if self.account and not self.account.canUse(): + self.account = None + + if not self.account and hasattr(self.pluginclass, "LOGIN_ACCOUNT") and self.pluginclass.LOGIN_ACCOUNT: + self.logWarning("Hook plugin will be deactivated due missing account reference") + self.setConfig('activated', False) + + + def activate(self): + self.initPeriodical(threaded=True) + + + def getURL(self, *args, **kwargs): #@TODO: Remove in 0.4.10 + """ see HTTPRequest for argument list """ + h = pyreq.getHTTPRequest(timeout=120) + try: + if not 'decode' in kwargs: + kwargs['decode'] = True + rep = h.load(*args, **kwargs) + finally: + h.close() + + return rep + + + def getConfig(self, option, default=''): #@TODO: Remove in 0.4.10 + """getConfig with default value - sublass may not implements all config options""" + try: + return self.getConf(option) + + except KeyError: + return default + + + def pluginsCached(self): + if self.plugins: + return self.plugins + + for _i in xrange(2): + try: + pluginset = self._pluginSet(self.getHosters() if self.plugintype == "hoster" else self.getCrypters()) + break + + except Exception, e: + self.logDebug(e, "Waiting 1 minute and retry") + time.sleep(60) + else: + self.logWarning(_("Fallback to default reload interval due plugin parse error")) + self.interval = self.MIN_RELOAD_INTERVAL + return list() + + try: + configmode = self.getConfig('pluginmode', 'all') + if configmode in ("listed", "unlisted"): + pluginlist = self.getConfig('pluginlist', '').replace('|', ',').replace(';', ',').split(',') + configset = self._pluginSet(pluginlist) + + if configmode == "listed": + pluginset &= configset + else: + pluginset -= configset + + except Exception, e: + self.logError(e) + + self.plugins = list(pluginset) + + return self.plugins + + + def _pluginSet(self, plugins): + regexp = re.compile(r'^[\w\-.^_]{3,63}\.[a-zA-Z]{2,}$', re.U) + plugins = [decode(p.strip()).lower() for p in plugins if regexp.match(p.strip())] + + for r in self.DOMAIN_REPLACEMENTS: + rf, rt = r + repr = re.compile(rf, re.I | re.U) + plugins = [re.sub(rf, rt, p) if repr.match(p) else p for p in plugins] + + return set(plugins) + + + def getHosters(self): + """Load list of supported hoster + + :return: List of domain names + """ + raise NotImplementedError + + + def getCrypters(self): + """Load list of supported crypters + + :return: List of domain names + """ + raise NotImplementedError + + + def periodical(self): + """reload plugin list periodically""" + self.loadAccount() + + if self.getConfig('reload', True): + self.interval = max(self.getConfig('reloadinterval', 12) * 60 * 60, self.MIN_RELOAD_INTERVAL) + else: + self.core.scheduler.removeJob(self.cb) + self.cb = None + + self.logInfo(_("Reloading supported %s list") % self.plugintype) + + old_supported = self.supported + + self.supported = [] + self.new_supported = [] + self.plugins = [] + + self.overridePlugins() + + old_supported = [plugin for plugin in old_supported if plugin not in self.supported] + + if old_supported: + self.logDebug("Unload: %s" % ", ".join(old_supported)) + for plugin in old_supported: + self.unloadPlugin(plugin) + + + def overridePlugins(self): + excludedList = [] + + if self.plugintype == "hoster": + pluginMap = dict((name.lower(), name) for name in self.core.pluginManager.hosterPlugins.iterkeys()) + accountList = [account.type.lower() for account in self.core.api.getAccounts(False) if account.valid and account.premium] + else: + pluginMap = {} + accountList = [name[::-1].replace("Folder"[::-1], "", 1).lower()[::-1] for name in self.core.pluginManager.crypterPlugins.iterkeys()] + + for plugin in self.pluginsCached(): + name = remove_chars(plugin, "-.") + + if name in accountList: + excludedList.append(plugin) + else: + if name in pluginMap: + self.supported.append(pluginMap[name]) + else: + self.new_supported.append(plugin) + + if not self.supported and not self.new_supported: + self.logError(_("No %s loaded") % self.plugintype) + return + + # inject plugin plugin + self.logDebug("Overwritten %ss: %s" % (self.plugintype, ", ".join(sorted(self.supported)))) + + for plugin in self.supported: + hdict = self.core.pluginManager.plugins[self.plugintype][plugin] + hdict['new_module'] = self.pluginmodule + hdict['new_name'] = self.pluginname + + if excludedList: + self.logInfo(_("%ss not overwritten: %s") % (self.plugintype.capitalize(), ", ".join(sorted(excludedList)))) + + if self.new_supported: + plugins = sorted(self.new_supported) + + self.logDebug("New %ss: %s" % (self.plugintype, ", ".join(plugins))) + + # create new regexp + regexp = r'.*(?P<DOMAIN>%s).*' % "|".join(x.replace('.', '\.') for x in plugins) + if hasattr(self.pluginclass, "__pattern") and isinstance(self.pluginclass.__pattern, basestring) and '://' in self.pluginclass.__pattern: + regexp = r'%s|%s' % (self.pluginclass.__pattern, regexp) + + self.logDebug("Regexp: %s" % regexp) + + hdict = self.core.pluginManager.plugins[self.plugintype][self.pluginname] + hdict['pattern'] = regexp + hdict['re'] = re.compile(regexp) + + + def unloadPlugin(self, plugin): + hdict = self.core.pluginManager.plugins[self.plugintype][plugin] + if "module" in hdict: + hdict.pop('module', None) + + if "new_module" in hdict: + hdict.pop('new_module', None) + hdict.pop('new_name', None) + + + def deactivate(self): + """Remove override for all plugins. Scheduler job is removed by hookmanager""" + for plugin in self.supported: + self.unloadPlugin(plugin) + + # reset pattern + hdict = self.core.pluginManager.plugins[self.plugintype][self.pluginname] + + hdict['pattern'] = getattr(self.pluginclass, "__pattern", r'^unmatchable$') + hdict['re'] = re.compile(hdict['pattern']) diff --git a/pyload/plugin/internal/MultiHoster.py b/pyload/plugin/internal/MultiHoster.py new file mode 100644 index 000000000..8dbcf4f30 --- /dev/null +++ b/pyload/plugin/internal/MultiHoster.py @@ -0,0 +1,116 @@ +# -*- coding: utf-8 -*- + +import re + +from pyload.plugin.Plugin import Fail, Retry +from pyload.plugin.internal.SimpleHoster import SimpleHoster, replace_patterns, set_cookies + + +class MultiHoster(SimpleHoster): + __name = "MultiHoster" + __type = "hoster" + __version = "0.39" + + __pattern = r'^unmatchable$' + __config = [("use_premium" , "bool", "Use premium account if available" , True), + ("revertfailed", "bool", "Revert to standard download if fails", True)] + + __description = """Multi hoster plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + LOGIN_ACCOUNT = True + + + def setup(self): + self.chunkLimit = 1 + self.multiDL = bool(self.account) + self.resumeDownload = self.premium + + + def prepare(self): + self.info = {} + self.html = "" + self.link = "" #@TODO: Move to hoster class in 0.4.10 + self.directDL = False #@TODO: Move to hoster class in 0.4.10 + + if not self.getConfig('use_premium', True): + self.retryFree() + + if self.LOGIN_ACCOUNT and not self.account: + self.fail(_("Required account not found")) + + self.req.setOption("timeout", 120) + + if isinstance(self.COOKIES, list): + set_cookies(self.req.cj, self.COOKIES) + + if self.DIRECT_LINK is None: + self.directDL = self.__pattern != r'^unmatchable$' and re.match(self.__pattern, self.pyfile.url) + else: + self.directDL = self.DIRECT_LINK + + self.pyfile.url = replace_patterns(self.pyfile.url, self.URL_REPLACEMENTS) + + + def process(self, pyfile): + try: + self.prepare() + + if self.directDL: + self.checkInfo() + self.logDebug("Looking for direct download link...") + self.handleDirect(pyfile) + + if not self.link and not self.lastDownload: + self.preload() + + self.checkErrors() + self.checkStatus(getinfo=False) + + if self.premium and (not self.CHECK_TRAFFIC or self.checkTrafficLeft()): + self.logDebug("Handled as premium download") + self.handlePremium(pyfile) + + elif not self.LOGIN_ACCOUNT or (not self.CHECK_TRAFFIC or self.checkTrafficLeft()): + self.logDebug("Handled as free download") + self.handleFree(pyfile) + + self.downloadLink(self.link, True) + self.checkFile() + + except Fail, e: #@TODO: Move to PluginThread in 0.4.10 + if self.premium: + self.logWarning(_("Premium download failed")) + self.retryFree() + + elif self.getConfig('revertfailed', True) \ + and "new_module" in self.core.pluginManager.hosterPlugins[self.getClassName()]: + hdict = self.core.pluginManager.hosterPlugins[self.getClassName()] + + tmp_module = hdict['new_module'] + tmp_name = hdict['new_name'] + hdict.pop('new_module', None) + hdict.pop('new_name', None) + + pyfile.initPlugin() + + hdict['new_module'] = tmp_module + hdict['new_name'] = tmp_name + + raise Retry(_("Revert to original hoster plugin")) + + else: + raise Fail(e) + + + def handlePremium(self, pyfile): + return self.handleFree(pyfile) + + + def handleFree(self, pyfile): + if self.premium: + raise NotImplementedError + else: + self.fail(_("Required premium account not found")) diff --git a/pyload/plugin/internal/SimpleCrypter.py b/pyload/plugin/internal/SimpleCrypter.py new file mode 100644 index 000000000..e22a4df29 --- /dev/null +++ b/pyload/plugin/internal/SimpleCrypter.py @@ -0,0 +1,153 @@ +# -*- coding: utf-8 -*- + +import re +import urlparse + +from pyload.plugin.Crypter import Crypter +from pyload.plugin.internal.SimpleHoster import SimpleHoster, replace_patterns, set_cookies +from pyload.utils import fixup + + +class SimpleCrypter(Crypter, SimpleHoster): + __name = "SimpleCrypter" + __type = "crypter" + __version = "0.43" + + __pattern = r'^unmatchable$' + __config = [("use_subfolder" , "bool", "Save package to subfolder" , True), #: Overrides core.config.get("general", "folder_per_package") + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Simple decrypter plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + """ + Following patterns should be defined by each crypter: + + LINK_PATTERN: Download link or regex to catch links in group(1) + example: LINK_PATTERN = r'<div class="link"><a href="(.+?)"' + + NAME_PATTERN: (optional) folder name or page title + example: NAME_PATTERN = r'<title>Files of: (?P<N>[^<]+) folder</title>' + + OFFLINE_PATTERN: (optional) Checks if the page is unreachable + example: OFFLINE_PATTERN = r'File (deleted|not found)' + + TEMP_OFFLINE_PATTERN: (optional) Checks if the page is temporarily unreachable + example: TEMP_OFFLINE_PATTERN = r'Server maintainance' + + + You can override the getLinks method if you need a more sophisticated way to extract the links. + + + If the links are splitted on multiple pages you can define the PAGES_PATTERN regex: + + PAGES_PATTERN: (optional) group(1) should be the number of overall pages containing the links + example: PAGES_PATTERN = r'Pages: (\d+)' + + and its loadPage method: + + def loadPage(self, page_n): + return the html of the page number page_n + """ + + LINK_PATTERN = None + + NAME_REPLACEMENTS = [("&#?\w+;", fixup)] + URL_REPLACEMENTS = [] + + TEXT_ENCODING = False #: Set to True or encoding name if encoding in http header is not correct + COOKIES = True #: or False or list of tuples [(domain, name, value)] + + LOGIN_ACCOUNT = False + LOGIN_PREMIUM = False + + + def prepare(self): + self.pyfile.error = "" #@TODO: Remove in 0.4.10 + + self.info = {} + self.html = "" + self.links = [] #@TODO: Move to hoster class in 0.4.10 + + if self.LOGIN_PREMIUM and not self.premium: + self.fail(_("Required premium account not found")) + + if self.LOGIN_ACCOUNT and not self.account: + self.fail(_("Required account not found")) + + self.req.setOption("timeout", 120) + + if isinstance(self.COOKIES, list): + set_cookies(self.req.cj, self.COOKIES) + + self.pyfile.url = replace_patterns(self.pyfile.url, self.URL_REPLACEMENTS) + + + def decrypt(self, pyfile): + self.prepare() + + self.preload() + self.checkInfo() + + self.links = self.getLinks() + + if hasattr(self, 'PAGES_PATTERN') and hasattr(self, 'loadPage'): + self.handlePages(pyfile) + + self.logDebug("Package has %d links" % len(self.links)) + + if self.links: + self.packages = [(self.info['name'], self.links, self.info['folder'])] + + elif not self.urls and not self.packages: #@TODO: Remove in 0.4.10 + self.fail(_("No link grabbed")) + + + def checkNameSize(self, getinfo=True): + if not self.info or getinfo: + self.logDebug("File info (BEFORE): %s" % self.info) + self.info.update(self.getInfo(self.pyfile.url, self.html)) + self.logDebug("File info (AFTER): %s" % self.info) + + try: + url = self.info['url'].strip() + name = self.info['name'].strip() + if name and name != url: + self.pyfile.name = name + + except Exception: + pass + + try: + folder = self.info['folder'] = self.pyfile.name + + except Exception: + pass + + self.logDebug("File name: %s" % self.pyfile.name, + "File folder: %s" % self.pyfile.name) + + + def getLinks(self): + """ + Returns the links extracted from self.html + You should override this only if it's impossible to extract links using only the LINK_PATTERN. + """ + url_p = urlparse.urlparse(self.pyfile.url) + baseurl = "%s://%s" % (url_p.scheme, url_p.netloc) + + return [urlparse.urljoin(baseurl, link) if not urlparse.urlparse(link).scheme else link \ + for link in re.findall(self.LINK_PATTERN, self.html)] + + + def handlePages(self, pyfile): + try: + pages = int(re.search(self.PAGES_PATTERN, self.html).group(1)) + except Exception: + pages = 1 + + for p in xrange(2, pages + 1): + self.html = self.loadPage(p) + self.links += self.getLinks() diff --git a/pyload/plugin/internal/SimpleDereferer.py b/pyload/plugin/internal/SimpleDereferer.py new file mode 100644 index 000000000..0ec947751 --- /dev/null +++ b/pyload/plugin/internal/SimpleDereferer.py @@ -0,0 +1,94 @@ +# -*- coding: utf-8 -*- + +import re +import urllib + +from pyload.plugin.Crypter import Crypter +from pyload.plugin.internal.SimpleHoster import getFileURL, set_cookies + + +class SimpleDereferer(Crypter): + __name = "SimpleDereferer" + __type = "crypter" + __version = "0.11" + + __pattern = r'^unmatchable$' + __config = [("use_subfolder" , "bool", "Save package to subfolder" , True), + ("subfolder_per_pack", "bool", "Create a subfolder for each package", True)] + + __description = """Simple dereferer plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + """ + Following patterns should be defined by each crypter: + + LINK_PATTERN: Regex to catch the redirect url in group(1) + example: LINK_PATTERN = r'<div class="link"><a href="(.+?)"' + + OFFLINE_PATTERN: (optional) Checks if the page is unreachable + example: OFFLINE_PATTERN = r'File (deleted|not found)' + + TEMP_OFFLINE_PATTERN: (optional) Checks if the page is temporarily unreachable + example: TEMP_OFFLINE_PATTERN = r'Server maintainance' + + + You can override the getLinks method if you need a more sophisticated way to extract the redirect url. + """ + + LINK_PATTERN = None + + TEXT_ENCODING = False + COOKIES = True + + + def decrypt(self, pyfile): + link = getFileURL(self, pyfile.url) + + if not link: + try: + link = urllib.unquote(re.match(self.__pattern, pyfile.url).group('LINK')) + + except Exception: + self.prepare() + self.preload() + self.checkStatus() + + link = self.getLink() + + if link.strip(): + self.urls = [link] + + + def prepare(self): + self.info = {} + self.html = "" + + self.req.setOption("timeout", 120) + + if isinstance(self.COOKIES, list): + set_cookies(self.req.cj, self.COOKIES) + + + def preload(self): + self.html = self.load(self.pyfile.url, cookies=bool(self.COOKIES), decode=not self.TEXT_ENCODING) + + if isinstance(self.TEXT_ENCODING, basestring): + self.html = unicode(self.html, self.TEXT_ENCODING) + + + def checkStatus(self): + if hasattr(self, "OFFLINE_PATTERN") and re.search(self.OFFLINE_PATTERN, self.html): + self.offline() + + elif hasattr(self, "TEMP_OFFLINE_PATTERN") and re.search(self.TEMP_OFFLINE_PATTERN, self.html): + self.tempOffline() + + + def getLink(self): + try: + return re.search(self.LINK_PATTERN, self.html).group(1) + + except Exception: + pass diff --git a/pyload/plugin/internal/SimpleHoster.py b/pyload/plugin/internal/SimpleHoster.py new file mode 100644 index 000000000..d3e2edc48 --- /dev/null +++ b/pyload/plugin/internal/SimpleHoster.py @@ -0,0 +1,758 @@ +# -*- coding: utf-8 -*- + +import datetime +import mimetypes +import os +import re +import time +import urllib +import urlparse + +from pyload.datatype.File import statusMap as _statusMap +from pyload.network.CookieJar import CookieJar +from pyload.network.HTTPRequest import BadHeader +from pyload.network.RequestFactory import getURL +from pyload.plugin.Hoster import Hoster +from pyload.plugin.Plugin import Fail, Retry +from pyload.utils import fixup, fs_encode, parseFileSize + + +#@TODO: Adapt and move to PyFile in 0.4.10 +statusMap = dict((v, k) for k, v in _statusMap.iteritems()) + + +#@TODO: Remove in 0.4.10 and redirect to self.error instead +def _error(self, reason, type): + if not reason and not type: + type = "unknown" + + msg = _("%s error") % type.strip().capitalize() if type else _("Error") + msg += (": %s" % reason.strip()) if reason else "" + msg += _(" | Plugin may be out of date") + + raise Fail(msg) + + +#@TODO: Remove in 0.4.10 +def _wait(self, seconds, reconnect): + if seconds: + self.setWait(int(seconds) + 1) + + if reconnect is not None: + self.wantReconnect = reconnect + + super(SimpleHoster, self).wait() + + +def replace_patterns(string, ruleslist): + for r in ruleslist: + rf, rt = r + string = re.sub(rf, rt, string) + return string + + +def set_cookies(cj, cookies): + for cookie in cookies: + if isinstance(cookie, tuple) and len(cookie) == 3: + domain, name, value = cookie + cj.setCookie(domain, name, value) + + +def parseHtmlTagAttrValue(attr_name, tag): + m = re.search(r"%s\s*=\s*([\"']?)((?<=\")[^\"]+|(?<=')[^']+|[^>\s\"'][^>\s]*)\1" % attr_name, tag, re.I) + return m.group(2) if m else None + + +def parseHtmlForm(attr_str, html, input_names={}): + for form in re.finditer(r"(?P<TAG><form[^>]*%s[^>]*>)(?P<CONTENT>.*?)</?(form|body|html)[^>]*>" % attr_str, + html, re.S | re.I): + inputs = {} + action = parseHtmlTagAttrValue("action", form.group('TAG')) + + for inputtag in re.finditer(r'(<(input|textarea)[^>]*>)([^<]*(?=</\2)|)', form.group('CONTENT'), re.S | re.I): + name = parseHtmlTagAttrValue("name", inputtag.group(1)) + if name: + value = parseHtmlTagAttrValue("value", inputtag.group(1)) + if not value: + inputs[name] = inputtag.group(3) or '' + else: + inputs[name] = value + + if input_names: + # check input attributes + for key, val in input_names.iteritems(): + if key in inputs: + if isinstance(val, basestring) and inputs[key] == val: + continue + elif isinstance(val, tuple) and inputs[key] in val: + continue + elif hasattr(val, "search") and re.match(val, inputs[key]): + continue + break #: attibute value does not match + else: + break #: attibute name does not match + else: + return action, inputs #: passed attribute check + else: + # no attribute check + return action, inputs + + return {}, None #: no matching form found + + +#: Deprecated +def parseFileInfo(plugin, url="", html=""): + if hasattr(plugin, "getInfo"): + info = plugin.getInfo(url, html) + res = info['name'], info['size'], info['status'], info['url'] + else: + url = urllib.unquote(url) + url_p = urlparse.urlparse(url) + res = ((url_p.path.split('/')[-1] + or url_p.query.split('=', 1)[::-1][0].split('&', 1)[0] + or url_p.netloc.split('.', 1)[0]), + 0, + 3 if url else 8, + url) + + return res + + +#@TODO: Remove in 0.4.10 +#@NOTE: Every plugin must have own parseInfos classmethod to work with 0.4.10 +# def create_getInfo(plugin): + + # def generator(list): + # for x in list: + # yield x + + # if hasattr(plugin, "parseInfos"): + # fn = lambda urls: generator((info['name'], info['size'], info['status'], info['url']) for info in plugin.parseInfos(urls)) + # else: + # fn = lambda urls: generator(parseFileInfo(url) for url in urls) + + # return fn + + +def timestamp(): + return int(time.time() * 1000) + + +#@TODO: Move to hoster class in 0.4.10 +def getFileURL(self, url, follow_location=None): + link = "" + redirect = 1 + + if type(follow_location) is int: + redirect = max(follow_location, 1) + else: + redirect = 10 + + for i in xrange(redirect): + try: + self.logDebug("Redirect #%d to: %s" % (i, url)) + header = self.load(url, just_header=True, decode=True) + + except Exception: #: Bad bad bad... + req = pyreq.getHTTPRequest() + res = req.load(url, just_header=True, decode=True) + + req.close() + + header = {"code": req.code} + for line in res.splitlines(): + line = line.strip() + if not line or ":" not in line: + continue + + key, none, value = line.partition(":") + key = key.lower().strip() + value = value.strip() + + if key in header: + if type(header[key]) == list: + header[key].append(value) + else: + header[key] = [header[key], value] + else: + header[key] = value + + if 'content-disposition' in header: + link = url + + elif 'location' in header and header['location'].strip(): + location = header['location'] + + if not urlparse.urlparse(location).scheme: + url_p = urlparse.urlparse(url) + baseurl = "%s://%s" % (url_p.scheme, url_p.netloc) + location = urlparse.urljoin(baseurl, location) + + if 'code' in header and header['code'] == 302: + link = location + + if follow_location: + url = location + continue + + else: + extension = os.path.splitext(urlparse.urlparse(url).path.split('/')[-1])[-1] + + if 'content-type' in header and header['content-type'].strip(): + mimetype = header['content-type'].split(';')[0].strip() + + elif extension: + mimetype = mimetypes.guess_type(extension, False)[0] or "application/octet-stream" + + else: + mimetype = "" + + if mimetype and (link or 'html' not in mimetype): + link = url + else: + link = "" + + break + + else: + try: + self.logError(_("Too many redirects")) + except Exception: + pass + + return link + + +def secondsToMidnight(gmt=0): + now = datetime.datetime.utcnow() + datetime.timedelta(hours=gmt) + + if now.hour is 0 and now.minute < 10: + midnight = now + else: + midnight = now + datetime.timedelta(days=1) + + td = midnight.replace(hour=0, minute=10, second=0, microsecond=0) - now + + if hasattr(td, 'total_seconds'): + res = td.total_seconds() + else: #: work-around for python 2.5 and 2.6 missing datetime.timedelta.total_seconds + res = (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10 ** 6) / 10 ** 6 + + return int(res) + + +class SimpleHoster(Hoster): + __name = "SimpleHoster" + __type = "hoster" + __version = "1.42" + + __pattern = r'^unmatchable$' + __config = [("use_premium", "bool", "Use premium account if available" , True), + ("fallback" , "bool", "Fallback to free download if premium fails", True)] + + __description = """Simple hoster plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + """ + Info patterns should be defined by each hoster: + + INFO_PATTERN: (optional) Name and Size of the file + example: INFO_PATTERN = r'(?P<N>file_name) (?P<S>file_size) (?P<U>size_unit)' + or + NAME_PATTERN: (optional) Name that will be set for the file + example: NAME_PATTERN = r'(?P<N>file_name)' + SIZE_PATTERN: (optional) Size that will be checked for the file + example: SIZE_PATTERN = r'(?P<S>file_size) (?P<U>size_unit)' + + HASHSUM_PATTERN: (optional) Hash code and type of the file + example: HASHSUM_PATTERN = r'(?P<H>hash_code) (?P<T>MD5)' + + OFFLINE_PATTERN: (optional) Check if the page is unreachable + example: OFFLINE_PATTERN = r'File (deleted|not found)' + + TEMP_OFFLINE_PATTERN: (optional) Check if the page is temporarily unreachable + example: TEMP_OFFLINE_PATTERN = r'Server (maintenance|maintainance)' + + + Error handling patterns are all optional: + + WAIT_PATTERN: (optional) Detect waiting time + example: WAIT_PATTERN = r'' + + PREMIUM_ONLY_PATTERN: (optional) Check if the file can be downloaded only with a premium account + example: PREMIUM_ONLY_PATTERN = r'Premium account required' + + ERROR_PATTERN: (optional) Detect any error preventing download + example: ERROR_PATTERN = r'' + + + Instead overriding handleFree and handlePremium methods you can define the following patterns for direct download: + + LINK_FREE_PATTERN: (optional) group(1) should be the direct link for free download + example: LINK_FREE_PATTERN = r'<div class="link"><a href="(.+?)"' + + LINK_PREMIUM_PATTERN: (optional) group(1) should be the direct link for premium download + example: LINK_PREMIUM_PATTERN = r'<div class="link"><a href="(.+?)"' + """ + + NAME_REPLACEMENTS = [("&#?\w+;", fixup)] + SIZE_REPLACEMENTS = [] + URL_REPLACEMENTS = [] + + TEXT_ENCODING = False #: Set to True or encoding name if encoding value in http header is not correct + COOKIES = True #: or False or list of tuples [(domain, name, value)] + CHECK_TRAFFIC = False #: Set to True to force checking traffic left for premium account + DIRECT_LINK = None #: Set to True to looking for direct link (as defined in handleDirect method), set to None to do it if self.account is True else False + MULTI_HOSTER = False #: Set to True to leech other hoster link (as defined in handleMulti method) + LOGIN_ACCOUNT = False #: Set to True to require account login + DISPOSITION = True #: Set to True to use any content-disposition value in http header as file name + + directLink = getFileURL #@TODO: Remove in 0.4.10 + + + @classmethod + def parseInfos(cls, urls): #@TODO: Built-in in 0.4.10 core (remove from plugins) + for url in urls: + url = replace_patterns(url, cls.URL_REPLACEMENTS) + yield cls.getInfo(url) + + + @classmethod + def apiInfo(cls, url="", get={}, post={}): + url = urllib.unquote(url) + url_p = urlparse.urlparse(url) + return {'name': (url_p.path.split('/')[-1] + or url_p.query.split('=', 1)[::-1][0].split('&', 1)[0] + or url_p.netloc.split('.', 1)[0]), + 'size': 0, + 'status': 3 if url else 8, + 'url': url} + + + @classmethod + def getInfo(cls, url="", html=""): + info = cls.apiInfo(url) + online = info['status'] == 2 + + try: + info['pattern'] = re.match(cls.__pattern, url).groupdict() #: pattern groups will be saved here + + except Exception: + info['pattern'] = {} + + if not html and not online: + if not url: + info['error'] = "missing url" + info['status'] = 1 + + elif info['status'] is 3 and not getFileURL(None, url): + try: + html = getURL(url, cookies=cls.COOKIES, decode=not cls.TEXT_ENCODING) + + if isinstance(cls.TEXT_ENCODING, basestring): + html = unicode(html, cls.TEXT_ENCODING) + + except BadHeader, e: + info['error'] = "%d: %s" % (e.code, e.content) + + if e.code is 404: + info['status'] = 1 + + elif e.code is 503: + info['status'] = 6 + + if html: + if hasattr(cls, "OFFLINE_PATTERN") and re.search(cls.OFFLINE_PATTERN, html): + info['status'] = 1 + + elif hasattr(cls, "TEMP_OFFLINE_PATTERN") and re.search(cls.TEMP_OFFLINE_PATTERN, html): + info['status'] = 6 + + else: + for pattern in ("INFO_PATTERN", "NAME_PATTERN", "SIZE_PATTERN", "HASHSUM_PATTERN"): + try: + attr = getattr(cls, pattern) + pdict = re.search(attr, html).groupdict() + + if all(True for k in pdict if k not in info['pattern']): + info['pattern'].update(pdict) + + except AttributeError: + continue + + else: + online = True + + if online: + info['status'] = 2 + + if 'N' in info['pattern']: + info['name'] = replace_patterns(urllib.unquote(info['pattern']['N'].strip()), + cls.NAME_REPLACEMENTS) + + if 'S' in info['pattern']: + size = replace_patterns(info['pattern']['S'] + info['pattern']['U'] if 'U' in info['pattern'] else info['pattern']['S'], + cls.SIZE_REPLACEMENTS) + info['size'] = parseFileSize(size) + + elif isinstance(info['size'], basestring): + unit = info['units'] if 'units' in info else None + info['size'] = parseFileSize(info['size'], unit) + + if 'H' in info['pattern']: + hashtype = info['pattern']['T'] if 'T' in info['pattern'] else "hash" + info[hashtype] = info['pattern']['H'] + + if not info['pattern']: + info.pop('pattern', None) + + return info + + + def setup(self): + self.resumeDownload = self.multiDL = self.premium + + + def prepare(self): + self.pyfile.error = "" #@TODO: Remove in 0.4.10 + + self.info = {} + self.html = "" + self.link = "" #@TODO: Move to hoster class in 0.4.10 + self.directDL = False #@TODO: Move to hoster class in 0.4.10 + self.multihost = False #@TODO: Move to hoster class in 0.4.10 + + if not self.getConfig('use_premium', True): + self.retryFree() + + if self.LOGIN_ACCOUNT and not self.account: + self.fail(_("Required account not found")) + + self.req.setOption("timeout", 120) + + if isinstance(self.COOKIES, list): + set_cookies(self.req.cj, self.COOKIES) + + if (self.MULTI_HOSTER + and (self.__pattern != self.core.pluginManager.hosterPlugins[self.getClassName()]['pattern'] + or re.match(self.__pattern, self.pyfile.url) is None)): + self.multihost = True + return + + if self.DIRECT_LINK is None: + self.directDL = bool(self.account) + else: + self.directDL = self.DIRECT_LINK + + self.pyfile.url = replace_patterns(self.pyfile.url, self.URL_REPLACEMENTS) + + + def preload(self): + self.html = self.load(self.pyfile.url, cookies=bool(self.COOKIES), decode=not self.TEXT_ENCODING) + + if isinstance(self.TEXT_ENCODING, basestring): + self.html = unicode(self.html, self.TEXT_ENCODING) + + + def process(self, pyfile): + try: + self.prepare() + self.checkInfo() + + if self.directDL: + self.logDebug("Looking for direct download link...") + self.handleDirect(pyfile) + + if self.multihost and not self.link and not self.lastDownload: + self.logDebug("Looking for leeched download link...") + self.handleMulti(pyfile) + + if not self.link and not self.lastDownload: + self.MULTI_HOSTER = False + self.retry(1, reason="Multi hoster fails") + + if not self.link and not self.lastDownload: + self.preload() + self.checkInfo() + + if self.premium and (not self.CHECK_TRAFFIC or self.checkTrafficLeft()): + self.logDebug("Handled as premium download") + self.handlePremium(pyfile) + + elif not self.LOGIN_ACCOUNT or (not self.CHECK_TRAFFIC or self.checkTrafficLeft()): + self.logDebug("Handled as free download") + self.handleFree(pyfile) + + self.downloadLink(self.link, self.DISPOSITION) + self.checkFile() + + except Fail, e: #@TODO: Move to PluginThread in 0.4.10 + if self.getConfig('fallback', True) and self.premium: + self.logWarning(_("Premium download failed"), e) + self.retryFree() + else: + raise Fail(e) + def downloadLink(self, link, disposition=True): + if link and isinstance(link, basestring): + self.correctCaptcha() + + if not urlparse.urlparse(link).scheme: + url_p = urlparse.urlparse(self.pyfile.url) + baseurl = "%s://%s" % (url_p.scheme, url_p.netloc) + link = urlparse.urljoin(baseurl, link) + + self.download(link, ref=False, disposition=disposition) + + + def checkFile(self, rules={}): + if self.cTask and not self.lastDownload: + self.invalidCaptcha() + self.retry(10, reason=_("Wrong captcha")) + + elif not self.lastDownload or not os.path.exists(fs_encode(self.lastDownload)): + self.lastDownload = "" + self.error(self.pyfile.error or _("No file downloaded")) + + else: + errmsg = self.checkDownload({'Empty file': re.compile(r'\A\s*\Z'), + 'Html error': re.compile(r'\A(?:\s*<.+>)?((?:[\w\s]*(?:[Ee]rror|ERROR)\s*\:?)?\s*\d{3})(?:\Z|\s+)')}) + + if not errmsg: + for r, p in [('Html file', re.compile(r'\A\s*<!DOCTYPE html')), + ('Request error', re.compile(r'([Aa]n error occured while processing your request)'))]: + if r not in rules: + rules[r] = p + + for r, a in [('Error', "ERROR_PATTERN"), + ('Premium only', "PREMIUM_ONLY_PATTERN"), + ('Wait error', "WAIT_PATTERN")]: + if r not in rules and hasattr(self, a): + rules[r] = getattr(self, a) + + errmsg = self.checkDownload(rules) + + if not errmsg: + return + + errmsg = errmsg.strip().capitalize() + + try: + errmsg += " | " + self.lastCheck.group(1).strip() + except Exception: + pass + + self.logWarning("Check result: " + errmsg, "Waiting 1 minute and retry") + self.retry(3, 60, errmsg) + + + def checkErrors(self): + if not self.html: + self.logWarning(_("No html code to check")) + return + + if hasattr(self, 'PREMIUM_ONLY_PATTERN') and not self.premium and re.search(self.PREMIUM_ONLY_PATTERN, self.html): + self.fail(_("Link require a premium account to be handled")) + + elif hasattr(self, 'ERROR_PATTERN'): + m = re.search(self.ERROR_PATTERN, self.html) + if m: + try: + errmsg = m.group(1).strip() + except Exception: + errmsg = m.group(0).strip() + + self.info['error'] = errmsg + + if "hour" in errmsg: + self.wait(1 * 60 * 60, True) + + elif re.search("da(il)?y|today", errmsg): + self.wait(secondsToMidnight(gmt=2), True) + + elif "minute" in errmsg: + self.wait(1 * 60) + + else: + self.error(errmsg) + + elif hasattr(self, 'WAIT_PATTERN'): + m = re.search(self.WAIT_PATTERN, self.html) + if m: + try: + waitmsg = m.group(1).strip() + except Exception: + waitmsg = m.group(0).strip() + + wait_time = sum(int(v) * {"hr": 3600, "hour": 3600, "min": 60, "sec": 1, "": 1}[u.lower()] for v, u in + re.findall(r'(\d+)\s*(hr|hour|min|sec|)', waitmsg, re.I)) + self.wait(wait_time, wait_time > 300) + + self.info.pop('error', None) + + + def checkStatus(self, getinfo=True): + if not self.info or getinfo: + self.logDebug("Update file info...") + self.logDebug("Previous file info: %s" % self.info) + self.info.update(self.getInfo(self.pyfile.url, self.html)) + self.logDebug("Current file info: %s" % self.info) + + try: + status = self.info['status'] + + if status is 1: + self.offline() + + elif status is 6: + self.tempOffline() + + elif status is 8: + self.fail(self.info['error'] if 'error' in self.info else "Failed") + + finally: + self.logDebug("File status: %s" % statusMap[status]) + + + def checkNameSize(self, getinfo=True): + if not self.info or getinfo: + self.logDebug("Update file info...") + self.logDebug("Previous file info: %s" % self.info) + self.info.update(self.getInfo(self.pyfile.url, self.html)) + self.logDebug("Current file info: %s" % self.info) + + try: + url = self.info['url'].strip() + name = self.info['name'].strip() + if name and name != url: + self.pyfile.name = name + + except Exception: + pass + + try: + size = self.info['size'] + if size > 0: + self.pyfile.size = size + + except Exception: + pass + + self.logDebug("File name: %s" % self.pyfile.name, + "File size: %s byte" % self.pyfile.size if self.pyfile.size > 0 else "File size: Unknown") + + + def checkInfo(self): + self.checkNameSize() + + if self.html: + self.checkErrors() + self.checkNameSize() + + self.checkStatus(getinfo=False) + + + #: Deprecated + def getFileInfo(self): + self.info = {} + self.checkInfo() + return self.info + + + def handleDirect(self, pyfile): + link = self.directLink(pyfile.url, self.resumeDownload) + + if link: + self.logInfo(_("Direct download link detected")) + self.link = link + else: + self.logDebug("Direct download link not found") + + + def handleMulti(self, pyfile): #: Multi-hoster handler + pass + + + def handleFree(self, pyfile): + if not hasattr(self, 'LINK_FREE_PATTERN'): + self.logError(_("Free download not implemented")) + + m = re.search(self.LINK_FREE_PATTERN, self.html) + if m is None: + self.error(_("Free download link not found")) + else: + self.link = m.group(1) + + + def handlePremium(self, pyfile): + if not hasattr(self, 'LINK_PREMIUM_PATTERN'): + self.logError(_("Premium download not implemented")) + self.logDebug("Handled as free download") + self.handleFree(pyfile) + + m = re.search(self.LINK_PREMIUM_PATTERN, self.html) + if m is None: + self.error(_("Premium download link not found")) + else: + self.link = m.group(1) + + + def longWait(self, wait_time=None, max_tries=3): + if wait_time and isinstance(wait_time, (int, long, float)): + time_str = "%dh %dm" % divmod(wait_time / 60, 60) + else: + wait_time = 900 + time_str = _("(unknown time)") + max_tries = 100 + + self.logInfo(_("Download limit reached, reconnect or wait %s") % time_str) + + self.wait(wait_time, True) + self.retry(max_tries=max_tries, reason=_("Download limit reached")) + + + def parseHtmlForm(self, attr_str="", input_names={}): + return parseHtmlForm(attr_str, self.html, input_names) + + + def checkTrafficLeft(self): + if not self.account: + return True + + traffic = self.account.getAccountInfo(self.user, True)['trafficleft'] + + if traffic is None: + return False + elif traffic == -1: + return True + else: + size = self.pyfile.size + self.logInfo(_("Filesize: %i KiB, Traffic left for user %s: %i KiB") % (size / 1024, self.user, traffic / 1024)) + return size <= traffic + + + def getConfig(self, option, default=''): #@TODO: Remove in 0.4.10 + """getConfig with default value - sublass may not implements all config options""" + try: + return self.getConf(option) + + except KeyError: + return default + + + def retryFree(self): + if not self.premium: + return + self.premium = False + self.account = None + self.req = self.core.requestFactory.getRequest(self.getClassName()) + self.retries = -1 + raise Retry(_("Fallback to free download")) + + + def wait(self, seconds=0, reconnect=None): + return _wait(self, seconds, reconnect) + + + def error(self, reason="", type="parse"): + return _error(self, reason, type) diff --git a/pyload/plugin/internal/XFSAccount.py b/pyload/plugin/internal/XFSAccount.py new file mode 100644 index 000000000..105df3cd5 --- /dev/null +++ b/pyload/plugin/internal/XFSAccount.py @@ -0,0 +1,173 @@ +# -*- coding: utf-8 -*- + +import re +import time +import urlparse + +from pyload.plugin.Account import Account +from pyload.plugin.internal.SimpleHoster import parseHtmlForm, set_cookies + + +class XFSAccount(Account): + __name = "XFSAccount" + __type = "account" + __version = "0.36" + + __description = """XFileSharing account plugin""" + __license = "GPLv3" + __authors = [("zoidberg" , "zoidberg@mujmail.cz"), + ("Walter Purcaro", "vuolter@gmail.com" )] + + + HOSTER_DOMAIN = None + HOSTER_URL = None + LOGIN_URL = None + + COOKIES = True + + PREMIUM_PATTERN = r'\(Premium only\)' + + VALID_UNTIL_PATTERN = r'Premium.[Aa]ccount expire:.*?(\d{1,2} [\w^_]+ \d{4})' + + TRAFFIC_LEFT_PATTERN = r'Traffic available today:.*?<b>\s*(?P<S>[\d.,]+|[Uu]nlimited)\s*(?:(?P<U>[\w^_]+)\s*)?</b>' + TRAFFIC_LEFT_UNIT = "MB" #: used only if no group <U> was found + + LEECH_TRAFFIC_PATTERN = r'Leech Traffic left:<b>.*?(?P<S>[\d.,]+|[Uu]nlimited)\s*(?:(?P<U>[\w^_]+)\s*)?</b>' + LEECH_TRAFFIC_UNIT = "MB" #: used only if no group <U> was found + + LOGIN_FAIL_PATTERN = r'Incorrect Login or Password|account was banned|Error<' + + + def init(self): + if not self.HOSTER_DOMAIN: + self.logError(_("Missing HOSTER_DOMAIN")) + self.COOKIES = False + + else: + if not self.HOSTER_URL: + self.HOSTER_URL = "http://www.%s/" % self.HOSTER_DOMAIN + + if isinstance(self.COOKIES, list): + self.COOKIES.insert((self.HOSTER_DOMAIN, "lang", "english")) + set_cookies(req.cj, self.COOKIES) + + + def loadAccountInfo(self, user, req): + validuntil = None + trafficleft = None + leechtraffic = None + premium = None + + if not self.HOSTER_URL: #@TODO: Remove in 0.4.10 + return {'validuntil' : validuntil, + 'trafficleft' : trafficleft, + 'leechtraffic': leechtraffic, + 'premium' : premium} + + html = req.load(self.HOSTER_URL, get={'op': "my_account"}, decode=True) + + premium = re.search(self.PREMIUM_PATTERN, html) is not None + + m = re.search(self.VALID_UNTIL_PATTERN, html) + if m: + expiredate = m.group(1).strip() + self.logDebug("Expire date: " + expiredate) + + try: + validuntil = time.mktime(time.strptime(expiredate, "%d %B %Y")) + + except Exception, e: + self.logError(e) + + else: + self.logDebug("Valid until: %s" % validuntil) + + if validuntil > time.mktime(time.gmtime()): + premium = True + trafficleft = -1 + else: + premium = False + validuntil = None #: registered account type (not premium) + else: + self.logDebug("VALID_UNTIL_PATTERN not found") + + m = re.search(self.TRAFFIC_LEFT_PATTERN, html) + if m: + try: + traffic = m.groupdict() + size = traffic['S'] + + if "nlimited" in size: + trafficleft = -1 + if validuntil is None: + validuntil = -1 + else: + if 'U' in traffic: + unit = traffic['U'] + elif isinstance(self.TRAFFIC_LEFT_UNIT, basestring): + unit = self.TRAFFIC_LEFT_UNIT + else: + unit = "" + + trafficleft = self.parseTraffic(size + unit) + + except Exception, e: + self.logError(e) + else: + self.logDebug("TRAFFIC_LEFT_PATTERN not found") + + leech = [m.groupdict() for m in re.finditer(self.LEECH_TRAFFIC_PATTERN, html)] + if leech: + leechtraffic = 0 + try: + for traffic in leech: + size = traffic['S'] + + if "nlimited" in size: + leechtraffic = -1 + if validuntil is None: + validuntil = -1 + break + else: + if 'U' in traffic: + unit = traffic['U'] + elif isinstance(self.LEECH_TRAFFIC_UNIT, basestring): + unit = self.LEECH_TRAFFIC_UNIT + else: + unit = "" + + leechtraffic += self.parseTraffic(size + unit) + + except Exception, e: + self.logError(e) + else: + self.logDebug("LEECH_TRAFFIC_PATTERN not found") + + return {'validuntil' : validuntil, + 'trafficleft' : trafficleft, + 'leechtraffic': leechtraffic, + 'premium' : premium} + + + def login(self, user, data, req): + if not self.HOSTER_URL: #@TODO: Remove in 0.4.10 + raise Exception(_("Missing HOSTER_DOMAIN")) + + if not self.LOGIN_URL: + self.LOGIN_URL = urlparse.urljoin(self.HOSTER_URL, "login.html") + html = req.load(self.LOGIN_URL, decode=True) + + action, inputs = parseHtmlForm('name="FL"', html) + if not inputs: + inputs = {'op' : "login", + 'redirect': self.HOSTER_URL} + + inputs.update({'login' : user, + 'password': data['password']}) + + if not action: + action = self.HOSTER_URL + html = req.load(action, post=inputs, decode=True) + + if re.search(self.LOGIN_FAIL_PATTERN, html): + self.wrongPassword() diff --git a/pyload/plugin/internal/XFSCrypter.py b/pyload/plugin/internal/XFSCrypter.py new file mode 100644 index 000000000..85b99712a --- /dev/null +++ b/pyload/plugin/internal/XFSCrypter.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.internal.SimpleCrypter import SimpleCrypter + + +class XFSCrypter(SimpleCrypter): + __name = "XFSCrypter" + __type = "crypter" + __version = "0.08" + + __pattern = r'^unmatchable$' + + __description = """XFileSharing decrypter plugin""" + __license = "GPLv3" + __authors = [("Walter Purcaro", "vuolter@gmail.com")] + + + HOSTER_DOMAIN = None + + URL_REPLACEMENTS = [(r'&?per_page=\d+', ""), (r'[?/&]+$', ""), (r'(.+/[^?]+)$', r'\1?'), (r'$', r'&per_page=10000')] + + LINK_PATTERN = r'<a href="(.+?)".*?>.+?(?:</a>)?\s*(<.+>\s*)?</(?:td|TD)>' + NAME_PATTERN = r'<[Tt]itle>.*?\: (?P<N>.+) folder</[Tt]itle>' + + OFFLINE_PATTERN = r'>\s*\w+ (Not Found|file (was|has been) removed)' + TEMP_OFFLINE_PATTERN = r'>\s*\w+ server (is in )?(maintenance|maintainance)' + + + def prepare(self): + if not self.HOSTER_DOMAIN: + if self.account: + account = self.account + else: + account_name = (self.getClassName() + ".py").replace("Folder.py", "").replace(".py", "") + account = self.pyfile.m.core.accountManager.getAccountPlugin(account_name) + + if account and hasattr(account, "HOSTER_DOMAIN") and account.HOSTER_DOMAIN: + self.HOSTER_DOMAIN = account.HOSTER_DOMAIN + else: + self.fail(_("Missing HOSTER_DOMAIN")) + + if isinstance(self.COOKIES, list): + self.COOKIES.insert((self.HOSTER_DOMAIN, "lang", "english")) + + return super(XFSCrypter, self).prepare() diff --git a/pyload/plugin/internal/XFSHoster.py b/pyload/plugin/internal/XFSHoster.py new file mode 100644 index 000000000..956f02b95 --- /dev/null +++ b/pyload/plugin/internal/XFSHoster.py @@ -0,0 +1,317 @@ +# -*- coding: utf-8 -*- + +import random +import re +from pyload.plugin.captcha.ReCaptcha import ReCaptcha +from pyload.plugin.captcha.SolveMedia import SolveMedia +from pyload.plugin.internal.SimpleHoster import SimpleHoster, secondsToMidnight +from pyload.utils import html_unescape + + +class XFSHoster(SimpleHoster): + __name = "XFSHoster" + __type = "hoster" + __version = "0.47" + + __pattern = r'^unmatchable$' + + __description = """XFileSharing hoster plugin""" + __license = "GPLv3" + __authors = [("zoidberg" , "zoidberg@mujmail.cz"), + ("stickell" , "l.stickell@yahoo.it"), + ("Walter Purcaro", "vuolter@gmail.com")] + + + HOSTER_DOMAIN = None + + TEXT_ENCODING = False + DIRECT_LINK = None + MULTI_HOSTER = True #@NOTE: Should be default to False for safe, but I'm lazy... + + NAME_PATTERN = r'(Filename[ ]*:[ ]*</b>(</td><td nowrap>)?|name="fname"[ ]+value="|<[\w^_]+ class="(file)?name">)\s*(?P<N>.+?)(\s*<|")' + SIZE_PATTERN = r'(Size[ ]*:[ ]*</b>(</td><td>)?|File:.*>|</font>\s*\(|<[\w^_]+ class="size">)\s*(?P<S>[\d.,]+)\s*(?P<U>[\w^_]+)' + + OFFLINE_PATTERN = r'>\s*\w+ (Not Found|file (was|has been) removed)' + TEMP_OFFLINE_PATTERN = r'>\s*\w+ server (is in )?(maintenance|maintainance)' + + WAIT_PATTERN = r'<span id="countdown_str".*>(\d+)</span>|id="countdown" value=".*?(\d+).*?"' + PREMIUM_ONLY_PATTERN = r'>This file is available for Premium Users only' + ERROR_PATTERN = r'(?:class=["\']err["\'].*?>|<[Cc]enter><b>|>Error</td>|>\(ERROR:)(?:\s*<.+?>\s*)*(.+?)(?:["\']|<|\))' + + LINK_LEECH_PATTERN = r'<h2>Download Link</h2>\s*<textarea[^>]*>([^<]+)' + LINK_PATTERN = None #: final download url pattern + + CAPTCHA_PATTERN = r'(https?://[^"\']+?/captchas?/[^"\']+)' + CAPTCHA_BLOCK_PATTERN = r'>Enter code.*?<div.*?>(.+?)</div>' + RECAPTCHA_PATTERN = None + SOLVEMEDIA_PATTERN = None + + FORM_PATTERN = None + FORM_INPUTS_MAP = None #: dict passed as input_names to parseHtmlForm + + + def setup(self): + self.chunkLimit = -1 if self.premium else 1 + self.resumeDownload = self.multiDL = self.premium + + + def prepare(self): + """ Initialize important variables """ + if not self.HOSTER_DOMAIN: + if self.account: + account = self.account + else: + account = self.pyfile.m.core.accountManager.getAccountPlugin(self.getClassName()) + + if account and hasattr(account, "HOSTER_DOMAIN") and account.HOSTER_DOMAIN: + self.HOSTER_DOMAIN = account.HOSTER_DOMAIN + else: + self.fail(_("Missing HOSTER_DOMAIN")) + + if isinstance(self.COOKIES, list): + self.COOKIES.insert((self.HOSTER_DOMAIN, "lang", "english")) + + if not self.LINK_PATTERN: + pattern = r'(https?://(?:www\.)?([^/]*?%s|\d+\.\d+\.\d+\.\d+)(\:\d+)?(/d/|(/files)?/\d+/\w+/).+?)["\'<]' + self.LINK_PATTERN = pattern % self.HOSTER_DOMAIN.replace('.', '\.') + + self.captcha = None + self.errmsg = None + + super(XFSHoster, self).prepare() + + if self.DIRECT_LINK is None: + self.directDL = self.premium + + + def handleFree(self, pyfile): + for i in xrange(1, 6): + self.logDebug("Getting download link: #%d" % i) + + self.checkErrors() + + m = re.search(self.LINK_PATTERN, self.html, re.S) + if m: + break + + data = self.getPostParameters() + + self.html = self.load(pyfile.url, post=data, ref=True, decode=True, follow_location=False) + + m = re.search(r'Location\s*:\s*(.+)', self.req.http.header, re.I) + if m and not "op=" in m.group(1): + break + + m = re.search(self.LINK_PATTERN, self.html, re.S) + if m: + break + else: + self.logError(data['op'] if 'op' in data else _("UNKNOWN")) + return "" + + self.link = m.group(1) + + + def handlePremium(self, pyfile): + return self.handleFree(pyfile) + + + def handleMulti(self, pyfile): + if not self.account: + self.fail(_("Only registered or premium users can use url leech feature")) + + # only tested with easybytez.com + self.html = self.load("http://www.%s/" % self.HOSTER_DOMAIN) + + action, inputs = self.parseHtmlForm() + + upload_id = "%012d" % int(random.random() * 10 ** 12) + action += upload_id + "&js_on=1&utype=prem&upload_type=url" + + inputs['tos'] = '1' + inputs['url_mass'] = pyfile.url + inputs['up1oad_type'] = 'url' + + self.logDebug(action, inputs) + + self.req.setOption("timeout", 600) #: wait for file to upload to easybytez.com + + self.html = self.load(action, post=inputs) + + self.checkErrors() + + action, inputs = self.parseHtmlForm('F1') + if not inputs: + self.retry(reason=self.errmsg or _("TEXTAREA F1 not found")) + + self.logDebug(inputs) + + stmsg = inputs['st'] + + if stmsg == 'OK': + self.html = self.load(action, post=inputs) + + elif 'Can not leech file' in stmsg: + self.retry(20, 3 * 60, _("Can not leech file")) + + elif 'today' in stmsg: + self.retry(wait_time=secondsToMidnight(gmt=2), reason=_("You've used all Leech traffic today")) + + else: + self.fail(stmsg) + + # get easybytez.com link for uploaded file + m = re.search(self.LINK_LEECH_PATTERN, self.html) + if m is None: + self.error(_("LINK_LEECH_PATTERN not found")) + + header = self.load(m.group(1), just_header=True, decode=True) + + if 'location' in header: #: Direct download link + self.link = header['location'] + + + def checkErrors(self): + m = re.search(self.ERROR_PATTERN, self.html) + if m is None: + self.errmsg = None + else: + self.errmsg = m.group(1).strip() + + self.logWarning(re.sub(r"<.*?>", " ", self.errmsg)) + + if 'wait' in self.errmsg: + wait_time = sum(int(v) * {"hr": 3600, "hour": 3600, "min": 60, "sec": 1, "": 1}[u.lower()] for v, u in + re.findall(r'(\d+)\s*(hr|hour|min|sec|)', self.errmsg, re.I)) + self.wait(wait_time, wait_time > 300) + + elif 'country' in self.errmsg: + self.fail(_("Downloads are disabled for your country")) + + elif 'captcha' in self.errmsg: + self.invalidCaptcha() + + elif 'premium' in self.errmsg and 'require' in self.errmsg: + self.fail(_("File can be downloaded by premium users only")) + + elif 'limit' in self.errmsg: + if 'day' in self.errmsg: + delay = secondsToMidnight(gmt=2) + retries = 3 + else: + delay = 1 * 60 * 60 + retries = 24 + + self.wantReconnect = True + self.retry(retries, delay, _("Download limit exceeded")) + + elif 'countdown' in self.errmsg or 'Expired' in self.errmsg: + self.retry(reason=_("Link expired")) + + elif 'maintenance' in self.errmsg or 'maintainance' in self.errmsg: + self.tempOffline() + + elif 'up to' in self.errmsg: + self.fail(_("File too large for free download")) + + else: + self.wantReconnect = True + self.retry(wait_time=60, reason=self.errmsg) + + if self.errmsg: + self.info['error'] = self.errmsg + else: + self.info.pop('error', None) + + + def getPostParameters(self): + if self.FORM_PATTERN or self.FORM_INPUTS_MAP: + action, inputs = self.parseHtmlForm(self.FORM_PATTERN or "", self.FORM_INPUTS_MAP or {}) + else: + action, inputs = self.parseHtmlForm(input_names={'op': re.compile(r'^download')}) + + if not inputs: + action, inputs = self.parseHtmlForm('F1') + if not inputs: + self.retry(reason=self.errmsg or _("TEXTAREA F1 not found")) + + self.logDebug(inputs) + + if 'op' in inputs: + if "password" in inputs: + password = self.getPassword() + if password: + inputs['password'] = password + else: + self.fail(_("Missing password")) + + if not self.premium: + m = re.search(self.WAIT_PATTERN, self.html) + if m: + wait_time = int(m.group(1)) + self.setWait(wait_time, False) + + self.captcha = self.handleCaptcha(inputs) + + self.wait() + else: + inputs['referer'] = self.pyfile.url + + if self.premium: + inputs['method_premium'] = "Premium Download" + inputs.pop('method_free', None) + else: + inputs['method_free'] = "Free Download" + inputs.pop('method_premium', None) + + return inputs + + + def handleCaptcha(self, inputs): + m = re.search(self.CAPTCHA_PATTERN, self.html) + if m: + captcha_url = m.group(1) + inputs['code'] = self.decryptCaptcha(captcha_url) + return 1 + + m = re.search(self.CAPTCHA_BLOCK_PATTERN, self.html, re.S) + if m: + captcha_div = m.group(1) + numerals = re.findall(r'<span.*?padding-left\s*:\s*(\d+).*?>(\d)</span>', html_unescape(captcha_div)) + + self.logDebug(captcha_div) + + inputs['code'] = "".join(a[1] for a in sorted(numerals, key=lambda num: int(num[0]))) + + self.logDebug("Captcha code: %s" % inputs['code'], numerals) + return 2 + + recaptcha = ReCaptcha(self) + try: + captcha_key = re.search(self.RECAPTCHA_PATTERN, self.html).group(1) + + except Exception: + captcha_key = recaptcha.detect_key() + + else: + self.logDebug("ReCaptcha key: %s" % captcha_key) + + if captcha_key: + inputs['recaptcha_response_field'], inputs['recaptcha_challenge_field'] = recaptcha.challenge(captcha_key) + return 3 + + solvemedia = SolveMedia(self) + try: + captcha_key = re.search(self.SOLVEMEDIA_PATTERN, self.html).group(1) + + except Exception: + captcha_key = solvemedia.detect_key() + + else: + self.logDebug("SolveMedia key: %s" % captcha_key) + + if captcha_key: + inputs['adcopy_response'], inputs['adcopy_challenge'] = solvemedia.challenge(captcha_key) + return 4 + + return 0 diff --git a/pyload/plugin/internal/__init__.py b/pyload/plugin/internal/__init__.py new file mode 100644 index 000000000..40a96afc6 --- /dev/null +++ b/pyload/plugin/internal/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/pyload/plugin/ocr/GigasizeCom.py b/pyload/plugin/ocr/GigasizeCom.py new file mode 100644 index 000000000..0c15b03e7 --- /dev/null +++ b/pyload/plugin/ocr/GigasizeCom.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.OCR import OCR + + +class GigasizeCom(OCR): + __name = "GigasizeCom" + __type = "ocr" + __version = "0.11" + + __description = """Gigasize.com ocr plugin""" + __license = "GPLv3" + __authors = [("pyLoad Team", "admin@pyload.org")] + + + def __init__(self): + OCR.__init__(self) + + + def get_captcha(self, image): + self.load_image(image) + self.threshold(2.8) + self.run_tesser(True, False, False, True) + return self.result_captcha diff --git a/pyload/plugin/ocr/LinksaveIn.py b/pyload/plugin/ocr/LinksaveIn.py new file mode 100644 index 000000000..44ab08592 --- /dev/null +++ b/pyload/plugin/ocr/LinksaveIn.py @@ -0,0 +1,157 @@ +# -*- coding: utf-8 -*- + +try: + from PIL import Image +except ImportError: + import Image + +import glob +import os + +from pyload.plugin.OCR import OCR + + +class LinksaveIn(OCR): + __name = "LinksaveIn" + __type = "ocr" + __version = "0.11" + + __description = """Linksave.in ocr plugin""" + __license = "GPLv3" + __authors = [("pyLoad Team", "admin@pyload.org")] + + + def __init__(self): + OCR.__init__(self) + self.data_dir = os.path.dirname(os.path.abspath(__file__)) + os.sep + "LinksaveIn" + os.sep + + + def load_image(self, image): + im = Image.open(image) + frame_nr = 0 + + lut = im.resize((256, 1)) + lut.putdata(range(256)) + lut = list(lut.convert("RGB").getdata()) + + new = Image.new("RGB", im.size) + npix = new.load() + while True: + try: + im.seek(frame_nr) + except EOFError: + break + frame = im.copy() + pix = frame.load() + for x in xrange(frame.size[0]): + for y in xrange(frame.size[1]): + if lut[pix[x, y]] != (0, 0, 0): + npix[x, y] = lut[pix[x, y]] + frame_nr += 1 + new.save(self.data_dir+"unblacked.png") + self.image = new.copy() + self.pixels = self.image.load() + self.result_captcha = '' + + + def get_bg(self): + stat = {} + cstat = {} + img = self.image.convert("P") + for bgpath in glob.glob(self.data_dir+"bg/*.gif"): + stat[bgpath] = 0 + bg = Image.open(bgpath) + + bglut = bg.resize((256, 1)) + bglut.putdata(range(256)) + bglut = list(bglut.convert("RGB").getdata()) + + lut = img.resize((256, 1)) + lut.putdata(range(256)) + lut = list(lut.convert("RGB").getdata()) + + bgpix = bg.load() + pix = img.load() + for x in xrange(bg.size[0]): + for y in xrange(bg.size[1]): + rgb_bg = bglut[bgpix[x, y]] + rgb_c = lut[pix[x, y]] + try: + cstat[rgb_c] += 1 + except Exception: + cstat[rgb_c] = 1 + if rgb_bg == rgb_c: + stat[bgpath] += 1 + max_p = 0 + bg = "" + for bgpath, value in stat.iteritems(): + if max_p < value: + bg = bgpath + max_p = value + return bg + + + def substract_bg(self, bgpath): + bg = Image.open(bgpath) + img = self.image.convert("P") + + bglut = bg.resize((256, 1)) + bglut.putdata(range(256)) + bglut = list(bglut.convert("RGB").getdata()) + + lut = img.resize((256, 1)) + lut.putdata(range(256)) + lut = list(lut.convert("RGB").getdata()) + + bgpix = bg.load() + pix = img.load() + orgpix = self.image.load() + for x in xrange(bg.size[0]): + for y in xrange(bg.size[1]): + rgb_bg = bglut[bgpix[x, y]] + rgb_c = lut[pix[x, y]] + if rgb_c == rgb_bg: + orgpix[x, y] = (255, 255, 255) + + + def eval_black_white(self): + new = Image.new("RGB", (140, 75)) + pix = new.load() + orgpix = self.image.load() + thresh = 4 + for x in xrange(new.size[0]): + for y in xrange(new.size[1]): + rgb = orgpix[x, y] + r, g, b = rgb + pix[x, y] = (255, 255, 255) + if r > max(b, g)+thresh: + pix[x, y] = (0, 0, 0) + if g < min(r, b): + pix[x, y] = (0, 0, 0) + if g > max(r, b)+thresh: + pix[x, y] = (0, 0, 0) + if b > max(r, g)+thresh: + pix[x, y] = (0, 0, 0) + self.image = new + self.pixels = self.image.load() + + + def get_captcha(self, image): + self.load_image(image) + bg = self.get_bg() + self.substract_bg(bg) + self.eval_black_white() + self.to_greyscale() + self.image.save(self.data_dir+"cleaned_pass1.png") + self.clean(4) + self.clean(4) + self.image.save(self.data_dir+"cleaned_pass2.png") + letters = self.split_captcha_letters() + final = "" + for n, letter in enumerate(letters): + self.image = letter + self.image.save(ocr.data_dir+"letter%d.png" % n) + self.run_tesser(True, True, False, False) + final += self.result_captcha + + return final diff --git a/pyload/plugin/ocr/NetloadIn.py b/pyload/plugin/ocr/NetloadIn.py new file mode 100644 index 000000000..57651428b --- /dev/null +++ b/pyload/plugin/ocr/NetloadIn.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.OCR import OCR + + +class NetloadIn(OCR): + __name = "NetloadIn" + __type = "ocr" + __version = "0.11" + + __description = """Netload.in ocr plugin""" + __license = "GPLv3" + __authors = [("pyLoad Team", "admin@pyload.org")] + + + def __init__(self): + OCR.__init__(self) + + + def get_captcha(self, image): + self.load_image(image) + self.to_greyscale() + self.clean(3) + self.clean(3) + self.run_tesser(True, True, False, False) + + self.result_captcha = self.result_captcha.replace(" ", "")[:4] #: cut to 4 numbers + + return self.result_captcha diff --git a/pyload/plugin/ocr/ShareonlineBiz.py b/pyload/plugin/ocr/ShareonlineBiz.py new file mode 100644 index 000000000..8efbdee35 --- /dev/null +++ b/pyload/plugin/ocr/ShareonlineBiz.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- + +from pyload.plugin.OCR import OCR + + +class ShareonlineBiz(OCR): + __name = "ShareonlineBiz" + __type = "ocr" + __version = "0.11" + + __description = """Shareonline.biz ocr plugin""" + __license = "GPLv3" + __authors = [("RaNaN", "RaNaN@pyload.org")] + + + def __init__(self): + OCR.__init__(self) + + + def get_captcha(self, image): + self.load_image(image) + self.to_greyscale() + self.image = self.image.resize((160, 50)) + self.pixels = self.image.load() + self.threshold(1.85) + # self.eval_black_white(240) + # self.derotate_by_average() + + letters = self.split_captcha_letters() + + final = "" + for letter in letters: + self.image = letter + self.run_tesser(True, True, False, False) + final += self.result_captcha + + return final + + # tesseract at 60% diff --git a/pyload/plugin/ocr/__init__.py b/pyload/plugin/ocr/__init__.py new file mode 100644 index 000000000..40a96afc6 --- /dev/null +++ b/pyload/plugin/ocr/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/pyload/remote/ClickNLoadBackend.py b/pyload/remote/ClickNLoadBackend.py new file mode 100644 index 000000000..2d3e29dbc --- /dev/null +++ b/pyload/remote/ClickNLoadBackend.py @@ -0,0 +1,171 @@ +# -*- coding: utf-8 -*- +# @author: RaNaN + +import re +from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler +from cgi import FieldStorage +from urllib import unquote +from base64 import standard_b64decode +from binascii import unhexlify + +try: + from Crypto.Cipher import AES +except Exception: + pass + +from pyload.manager.Remote import BackendBase + +core = None +js = None + + +class ClickNLoadBackend(BackendBase): + + def setup(self, host, port): + self.httpd = HTTPServer((host, port), CNLHandler) + global core, js + core = self.m.core + js = core.js + + + def serve(self): + while self.enabled: + self.httpd.handle_request() + + +class CNLHandler(BaseHTTPRequestHandler): + + def add_package(self, name, urls, queue=0): + print "name", name + print "urls", urls + print "queue", queue + + + def get_post(self, name, default=""): + if name in self.post: + return self.post[name] + else: + return default + + + def start_response(self, string): + + self.send_response(200) + + self.send_header("Content-Length", len(string)) + self.send_header("Content-Language", "de") + self.send_header("Vary", "Accept-Language, Cookie") + self.send_header("Cache-Control", "no-cache, must-revalidate") + self.send_header("Content-type", "text/html") + self.end_headers() + + + def do_GET(self): + path = self.path.strip("/").lower() + # self.wfile.write(path+"\n") + + self.map = [(r"add$", self.add), + (r"addcrypted$", self.addcrypted), + (r"addcrypted2$", self.addcrypted2), + (r"flashgot", self.flashgot), + (r"crossdomain\.xml", self.crossdomain), + (r"checkSupportForUrl", self.checksupport), + (r"jdcheck.js", self.jdcheck), + (r"", self.flash)] + + func = None + for r, f in self.map: + if re.match(r"(flash(got)?/?)?" + r, path): + func = f + break + + if func: + try: + resp = func() + if not resp: resp = "success" + resp += "\r\n" + self.start_response(resp) + self.wfile.write(resp) + except Exception, e: + self.send_error(500, str(e)) + else: + self.send_error(404, "Not Found") + + + def do_POST(self): + form = FieldStorage( + fp=self.rfile, + headers=self.headers, + environ={'REQUEST_METHOD': 'POST', + 'CONTENT_TYPE': self.headers['Content-Type'], + }) + + self.post = {} + for name in form.keys(): + self.post[name] = form[name].value + + return self.do_GET() + + + def flash(self): + return "JDownloader" + + + def add(self): + package = self.get_post('referer', 'ClickNLoad Package') + urls = filter(lambda x: x != "", self.get_post('urls').split("\n")) + + self.add_package(package, urls, 0) + + + def addcrypted(self): + package = self.get_post('referer', 'ClickNLoad Package') + dlc = self.get_post('crypted').replace(" ", "+") + + core.upload_container(package, dlc) + + + def addcrypted2(self): + package = self.get_post("source", "ClickNLoad Package") + crypted = self.get_post("crypted") + jk = self.get_post("jk") + + crypted = standard_b64decode(unquote(crypted.replace(" ", "+"))) + jk = "%s f()" % jk + jk = js.eval(jk) + Key = unhexlify(jk) + IV = Key + + obj = AES.new(Key, AES.MODE_CBC, IV) + result = obj.decrypt(crypted).replace("\x00", "").replace("\r", "").split("\n") + + result = filter(lambda x: x != "", result) + + self.add_package(package, result, 0) + + + def flashgot(self): + autostart = int(self.get_post('autostart', 0)) + package = self.get_post('package', "FlashGot") + urls = filter(lambda x: x != "", self.get_post('urls').split("\n")) + + self.add_package(package, urls, autostart) + + + def crossdomain(self): + rep = "<?xml version=\"1.0\"?>\n" + rep += "<!DOCTYPE cross-domain-policy SYSTEM \"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd\">\n" + rep += "<cross-domain-policy>\n" + rep += "<allow-access-from domain=\"*\" />\n" + rep += "</cross-domain-policy>" + return rep + + + def checksupport(self): + pass + + + def jdcheck(self): + rep = "jdownloader=true;\n" + rep += "var version='10629';\n" + return rep diff --git a/pyload/remote/SocketBackend.py b/pyload/remote/SocketBackend.py new file mode 100644 index 000000000..8b74d19ff --- /dev/null +++ b/pyload/remote/SocketBackend.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- + +import SocketServer + +from pyload.manager.Remote import BackendBase + + +class RequestHandler(SocketServer.BaseRequestHandler): + + def setup(self): + pass + + + def handle(self): + print self.request.recv(1024) + + +class SocketBackend(BackendBase): + + def setup(self, host, port): + # local only + self.server = SocketServer.ThreadingTCPServer(("localhost", port), RequestHandler) + + + def serve(self): + self.server.serve_forever() diff --git a/pyload/remote/ThriftBackend.py b/pyload/remote/ThriftBackend.py new file mode 100644 index 000000000..f71e264e2 --- /dev/null +++ b/pyload/remote/ThriftBackend.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +# @author: RaNaN, mkaay + +from os.path import exists + +from pyload.manager.Remote import BackendBase + +from pyload.remote.thriftbackend.Processor import Processor +from pyload.remote.thriftbackend.Protocol import ProtocolFactory +from pyload.remote.thriftbackend.Socket import ServerSocket +from pyload.remote.thriftbackend.Transport import TransportFactory +# from pyload.remote.thriftbackend.Transport import TransportFactoryCompressed + +from thrift.server import TServer + + +class ThriftBackend(BackendBase): + + def setup(self, host, port): + processor = Processor(self.core.api) + + key = None + cert = None + + if self.core.config.get("ssl", "activated"): + if exists(self.core.config.get("ssl", "cert")) and exists(self.core.config.get("ssl", "key")): + self.core.log.info(_("Using SSL ThriftBackend")) + key = self.core.config.get("ssl", "key") + cert = self.core.config.get("ssl", "cert") + + transport = ServerSocket(port, host, key, cert) + + + # tfactory = TransportFactoryCompressed() + tfactory = TransportFactory() + pfactory = ProtocolFactory() + + self.server = TServer.TThreadedServer(processor, transport, tfactory, pfactory) + # self.server = TNonblockingServer.TNonblockingServer(processor, transport, tfactory, pfactory) + + # server = TServer.TThreadPoolServer(processor, transport, tfactory, pfactory) + + + def serve(self): + self.server.serve() diff --git a/pyload/remote/__init__.py b/pyload/remote/__init__.py new file mode 100644 index 000000000..60dfa77c7 --- /dev/null +++ b/pyload/remote/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +activated = True diff --git a/pyload/remote/socketbackend/__init__.py b/pyload/remote/socketbackend/__init__.py new file mode 100644 index 000000000..40a96afc6 --- /dev/null +++ b/pyload/remote/socketbackend/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/pyload/remote/socketbackend/create_ttypes.py b/pyload/remote/socketbackend/create_ttypes.py new file mode 100644 index 000000000..00752dc6b --- /dev/null +++ b/pyload/remote/socketbackend/create_ttypes.py @@ -0,0 +1,87 @@ +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +import inspect +import os +import platform +import sys + +sys.path.append(os.path.join(pypath, "pyload", "remote")) + +from pyload.remote.thriftbackend.thriftgen.pyload import ttypes +from pyload.remote.thriftbackend.thriftgen.pyload.Pyload import Iface + + +def main(): + enums = [] + classes = [] + + print "generating lightweight ttypes.py" + + for name in dir(ttypes): + klass = getattr(ttypes, name) + + if name in ("TBase", "TExceptionBase") or name.startswith("_") or not ( + issubclass(klass, ttypes.TBase) or issubclass(klass, ttypes.TExceptionBase)): + continue + + if hasattr(klass, "thrift_spec"): + classes.append(klass) + else: + enums.append(klass) + + + with open(os.path.join(pypath, "pyload", "api", "types.py"), "wb") as f: + f.write( +"""# -*- coding: utf-8 -*- +# Autogenerated by pyload +# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING + + +class BaseObject(object): +\t__slots__ = [] + +""") + + # generate enums + for enum in enums: + name = enum.__name__ + f.write("class %s:\n" % name) + + for attr in dir(enum): + if attr.startswith("_") or attr in ("read", "write"): + continue + + f.write("\t%s = %s\n" % (attr, getattr(enum, attr))) + + f.write("\n") + + for klass in classes: + name = klass.__name__ + base = "Exception" if issubclass(klass, ttypes.TExceptionBase) else "BaseObject" + f.write("class %s(%s):\n" % (name, base)) + f.write("\t__slots__ = %s\n\n" % klass.__slots__) + + # create init + args = ["self"] + ["%s=None" % x for x in klass.__slots__] + + f.write("\tdef __init__(%s):\n" % ", ".join(args)) + for attr in klass.__slots__: + f.write("\t\tself.%s = %s\n" % (attr, attr)) + + f.write("\n") + + f.write("class Iface(object):\n") + + for name in dir(Iface): + if name.startswith("_"): + continue + + func = inspect.getargspec(getattr(Iface, name)) + + f.write("\tdef %s(%s):\n\t\tpass\n" % (name, ", ".join(func.args))) + + f.write("\n") + + f.close() diff --git a/pyload/remote/thriftbackend/Processor.py b/pyload/remote/thriftbackend/Processor.py new file mode 100644 index 000000000..204047e2f --- /dev/null +++ b/pyload/remote/thriftbackend/Processor.py @@ -0,0 +1,81 @@ +# -*- coding: utf-8 -*- + +from pyload.remote.thriftbackend.thriftgen.pyload import Pyload + + +class Processor(Pyload.Processor): + + def __init__(self, *args, **kwargs): + Pyload.Processor.__init__(self, *args, **kwargs) + self.authenticated = {} + + + def process(self, iprot, oprot): + trans = oprot.trans + if trans not in self.authenticated: + self.authenticated[trans] = False + oldclose = trans.close + + + def wrap(): + if self in self.authenticated: + del self.authenticated[trans] + oldclose() + + trans.close = wrap + authenticated = self.authenticated[trans] + (name, type, seqid) = iprot.readMessageBegin() + + # unknown method + if name not in self._processMap: + iprot.skip(Pyload.TType.STRUCT) + iprot.readMessageEnd() + x = Pyload.TApplicationException(Pyload.TApplicationException.UNKNOWN_METHOD, 'Unknown function %s' % name) + oprot.writeMessageBegin(name, Pyload.TMessageType.EXCEPTION, seqid) + x.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + return + + # not logged in + elif not authenticated and not name == "login": + iprot.skip(Pyload.TType.STRUCT) + iprot.readMessageEnd() + # 20 - Not logged in (in situ declared error code) + x = Pyload.TApplicationException(20, 'Not logged in') + oprot.writeMessageBegin(name, Pyload.TMessageType.EXCEPTION, seqid) + x.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + return + + elif not authenticated and name == "login": + args = Pyload.login_args() + args.read(iprot) + iprot.readMessageEnd() + result = Pyload.login_result() + # api login + self.authenticated[trans] = self._handler.checkAuth(args.username, args.password, trans.remoteaddr[0]) + + result.success = bool(self.authenticated[trans]) + oprot.writeMessageBegin("login", Pyload.TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + elif self._handler.isAuthorized(name, authenticated): + self._processMap[name](self, seqid, iprot, oprot) + + else: + # no permission + iprot.skip(Pyload.TType.STRUCT) + iprot.readMessageEnd() + # 21 - Not authorized + x = Pyload.TApplicationException(21, 'Not authorized') + oprot.writeMessageBegin(name, Pyload.TMessageType.EXCEPTION, seqid) + x.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + return + + return True diff --git a/pyload/remote/thriftbackend/Protocol.py b/pyload/remote/thriftbackend/Protocol.py new file mode 100644 index 000000000..ecf0680ad --- /dev/null +++ b/pyload/remote/thriftbackend/Protocol.py @@ -0,0 +1,33 @@ +# -*- coding: utf-8 -*- + +from thrift.protocol import TBinaryProtocol + + +class Protocol(TBinaryProtocol.TBinaryProtocol): + + def writeString(self, str): + try: + str = str.encode("utf8", "ignore") + except Exception: + pass + + self.writeI32(len(str)) + self.trans.write(str) + + + def readString(self): + len = self.readI32() + str = self.trans.readAll(len) + try: + str = str.decode("utf8", "ignore") + except Exception: + pass + + return str + + +class ProtocolFactory(TBinaryProtocol.TBinaryProtocolFactory): + + def getProtocol(self, trans): + prot = Protocol(trans, self.strictRead, self.strictWrite) + return prot diff --git a/pyload/remote/thriftbackend/Socket.py b/pyload/remote/thriftbackend/Socket.py new file mode 100644 index 000000000..0ca9ed178 --- /dev/null +++ b/pyload/remote/thriftbackend/Socket.py @@ -0,0 +1,144 @@ +# -*- coding: utf-8 -*- + +import sys +import socket +import errno + +from time import sleep + +from thrift.transport.TSocket import TSocket, TServerSocket, TTransportException + +WantReadError = Exception #: overwritten when ssl is used + + +class SecureSocketConnection(object): + + def __init__(self, connection): + self.__dict__['connection'] = connection + + + def __getattr__(self, name): + return getattr(self.__dict__['connection'], name) + + + def __setattr__(self, name, value): + setattr(self.__dict__['connection'], name, value) + + + def shutdown(self, how=1): + self.__dict__['connection'].shutdown() + + + def accept(self): + connection, address = self.__dict__['connection'].accept() + return SecureSocketConnection(connection), address + + + def send(self, buff): + try: + return self.__dict__['connection'].send(buff) + except WantReadError: + sleep(0.1) + return self.send(buff) + + + def recv(self, buff): + try: + return self.__dict__['connection'].recv(buff) + except WantReadError: + sleep(0.1) + return self.recv(buff) + + +class Socket(TSocket): + + def __init__(self, host='localhost', port=7228, ssl=False): + TSocket.__init__(self, host, port) + self.ssl = ssl + + + def open(self): + if self.ssl: + SSL = __import__("OpenSSL", globals(), locals(), "SSL", -1).SSL + WantReadError = SSL.WantReadError + ctx = SSL.Context(SSL.SSLv23_METHOD) + c = SSL.Connection(ctx, socket.socket(socket.AF_INET, socket.SOCK_STREAM)) + c.set_connect_state() + self.handle = SecureSocketConnection(c) + else: + self.handle = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + # errno 104 connection reset + + self.handle.settimeout(self._timeout) + self.handle.connect((self.host, self.port)) + + + def read(self, sz): + try: + buff = self.handle.recv(sz) + except socket.error, e: + if (e.args[0] == errno.ECONNRESET and + (sys.platform == 'darwin' or sys.platform.startswith('freebsd'))): + # freebsd and Mach don't follow POSIX semantic of recv + # and fail with ECONNRESET if peer performed shutdown. + # See corresponding comment and code in TSocket::read() + # in lib/cpp/src/transport/TSocket.cpp. + self.close() + # Trigger the check to raise the END_OF_FILE exception below. + buff = '' + else: + raise + except Exception, e: + # SSL connection was closed + if e.args == (-1, 'Unexpected EOF'): + buff = '' + elif e.args == ([('SSL routines', 'SSL23_GET_CLIENT_HELLO', 'unknown protocol')],): + # a socket not using ssl tried to connect + buff = '' + else: + raise + + if not len(buff): + raise TTransportException(type=TTransportException.END_OF_FILE, message='TSocket read 0 bytes') + return buff + + +class ServerSocket(TServerSocket, Socket): + + def __init__(self, port=7228, host="0.0.0.0", key="", cert=""): + self.host = host + self.port = port + self.key = key + self.cert = cert + self.handle = None + + + def listen(self): + if self.cert and self.key: + SSL = __import__("OpenSSL", globals(), locals(), "SSL", -1).SSL + WantReadError = SSL.WantReadError + ctx = SSL.Context(SSL.SSLv23_METHOD) + ctx.use_privatekey_file(self.key) + ctx.use_certificate_file(self.cert) + + tmpConnection = SSL.Connection(ctx, socket.socket(socket.AF_INET, socket.SOCK_STREAM)) + tmpConnection.set_accept_state() + self.handle = SecureSocketConnection(tmpConnection) + + else: + self.handle = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + + + self.handle.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + if hasattr(self.handle, 'set_timeout'): + self.handle.set_timeout(None) + self.handle.bind((self.host, self.port)) + self.handle.listen(128) + + + def accept(self): + client, addr = self.handle.accept() + result = Socket() + result.setHandle(client) + return result diff --git a/pyload/remote/thriftbackend/ThriftClient.py b/pyload/remote/thriftbackend/ThriftClient.py new file mode 100644 index 000000000..ecd4086fa --- /dev/null +++ b/pyload/remote/thriftbackend/ThriftClient.py @@ -0,0 +1,89 @@ +# -*- coding: utf-8 -*- + +import sys +import thrift +import traceback + +from Protocol import Protocol +from Socket import Socket +from socket import error +from thrift.transport import TTransport +# from thrift.transport.TZlibTransport import TZlibTransport + +# modules should import ttypes from here, when want to avoid importing API +from pyload.remote.thriftbackend.thriftgen.pyload import Pyload +from pyload.remote.thriftbackend.thriftgen.pyload.ttypes import * + +ConnectionClosed = TTransport.TTransportException + + +class WrongLogin(Exception): + pass + + +class NoConnection(Exception): + pass + + +class NoSSL(Exception): + pass + + +class ThriftClient(object): + + def __init__(self, host="localhost", port=7227, user="", password=""): + + self.createConnection(host, port) + try: + self.transport.open() + except error, e: + if e.args and e.args[0] in (111, 10061): + raise NoConnection + else: + traceback.print_exc() + raise NoConnection + + try: + correct = self.client.login(user, password) + except error, e: + if e.args and e.args[0] == 104: + # connection reset by peer, probably wants ssl + try: + self.createConnection(host, port, True) + # set timeout or a ssl socket will block when querying none ssl server + self.socket.setTimeout(10) + + except ImportError: + #@TODO untested + raise NoSSL + try: + self.transport.open() + correct = self.client.login(user, password) + finally: + self.socket.setTimeout(None) + elif e.args and e.args[0] == 32: + raise NoConnection + else: + traceback.print_exc() + raise NoConnection + + if not correct: + self.transport.close() + raise WrongLogin + + + def createConnection(self, host, port, ssl=False): + self.socket = Socket(host, port, ssl) + self.transport = TTransport.TBufferedTransport(self.socket) +# self.transport = TZlibTransport(TTransport.TBufferedTransport(self.socket)) + + protocol = Protocol(self.transport) + self.client = Pyload.Client(protocol) + + + def close(self): + self.transport.close() + + + def __getattr__(self, item): + return getattr(self.client, item) diff --git a/pyload/remote/thriftbackend/ThriftTest.py b/pyload/remote/thriftbackend/ThriftTest.py new file mode 100644 index 000000000..c95e060b8 --- /dev/null +++ b/pyload/remote/thriftbackend/ThriftTest.py @@ -0,0 +1,95 @@ +# -*- coding: utf-8 -*- + +import os +import platform +import sys + +if "64" in platform.machine(): + sys.path.append(os.path.join(pypath, "lib64")) +sys.path.append(os.path.join(pypath, "lib", "Python", "Lib")) + + +from pyload.remote.thriftbackend.thriftgen.pyload import Pyload +from pyload.remote.thriftbackend.thriftgen.pyload.ttypes import * +from Socket import Socket + +from thrift import Thrift +from thrift.transport import TTransport + +from Protocol import Protocol + +from time import time + +import xmlrpclib + + +def bench(f, *args, **kwargs): + s = time() + ret = [f(*args, **kwargs) for _i in xrange(0, 100)] + e = time() + try: + print "%s: %f s" % (f._Method__name, e-s) + except Exception: + print "%s: %f s" % (f.__name__, e-s) + return ret + +from getpass import getpass +user = raw_input("user ") +passwd = getpass("password ") + +server_url = "http%s://%s:%s@%s:%s/" % ( + "", + user, + passwd, + "127.0.0.1", + 7227 +) +proxy = xmlrpclib.ServerProxy(server_url, allow_none=True) + +bench(proxy.get_server_version) +bench(proxy.status_server) +bench(proxy.status_downloads) +# bench(proxy.get_queue) +# bench(proxy.get_collector) +print +try: + + # Make socket + transport = Socket('localhost', 7228, False) + + # Buffering is critical. Raw sockets are very slow + transport = TTransport.TBufferedTransport(transport) + + # Wrap in a protocol + protocol = Protocol(transport) + + # Create a client to use the protocol encoder + client = Pyload.Client(protocol) + + # Connect! + transport.open() + + print "Login", client.login(user, passwd) + + bench(client.getServerVersion) + bench(client.statusServer) + bench(client.statusDownloads) + # bench(client.getQueue) + # bench(client.getCollector) + + print + print client.getServerVersion() + print client.statusServer() + print client.statusDownloads() + q = client.getQueue() + + for p in q: + data = client.getPackageData(p.pid) + print data + print "Package Name: ", data.name + + # Close! + transport.close() + +except Thrift.TException, tx: + print 'ThriftExpection: %s' % tx.message diff --git a/pyload/remote/thriftbackend/Transport.py b/pyload/remote/thriftbackend/Transport.py new file mode 100644 index 000000000..1d3d81718 --- /dev/null +++ b/pyload/remote/thriftbackend/Transport.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- + +from thrift.transport.TTransport import TBufferedTransport +from thrift.transport.TZlibTransport import TZlibTransport + + +class Transport(TBufferedTransport): + DEFAULT_BUFFER = 4096 + + + def __init__(self, trans, rbuf_size = DEFAULT_BUFFER): + TBufferedTransport.__init__(self, trans, rbuf_size) + self.handle = trans.handle + self.remoteaddr = trans.handle.getpeername() + + +class TransportCompressed(TZlibTransport): + DEFAULT_BUFFER = 4096 + + + def __init__(self, trans, rbuf_size = DEFAULT_BUFFER): + TZlibTransport.__init__(self, trans, rbuf_size) + self.handle = trans.handle + self.remoteaddr = trans.handle.getpeername() + + +class TransportFactory(object): + + def getTransport(self, trans): + buffered = Transport(trans) + return buffered + + +class TransportFactoryCompressed(object): + _last_trans = None + _last_z = None + + + def getTransport(self, trans, compresslevel=9): + if trans == self._last_trans: + return self._last_z + ztrans = TransportCompressed(Transport(trans), compresslevel) + self._last_trans = trans + self._last_z = ztrans + return ztrans diff --git a/pyload/remote/thriftbackend/__init__.py b/pyload/remote/thriftbackend/__init__.py new file mode 100644 index 000000000..40a96afc6 --- /dev/null +++ b/pyload/remote/thriftbackend/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/pyload/remote/thriftbackend/pyload.thrift b/pyload/remote/thriftbackend/pyload.thrift new file mode 100644 index 000000000..3aef7af00 --- /dev/null +++ b/pyload/remote/thriftbackend/pyload.thrift @@ -0,0 +1,337 @@ +namespace java org.pyload.thrift + +typedef i32 FileID +typedef i32 PackageID +typedef i32 TaskID +typedef i32 ResultID +typedef i32 InteractionID +typedef list<string> LinkList +typedef string PluginName +typedef byte Progress +typedef byte Priority + + +enum DownloadStatus { + Finished + Offline, + Online, + Queued, + Skipped, + Waiting, + TempOffline, + Starting, + Failed, + Aborted, + Decrypting, + Custom, + Downloading, + Processing, + Unknown +} + +enum Destination { + Collector, + Queue +} + +enum ElementType { + Package, + File +} + +// types for user interaction +// some may only be place holder currently not supported +// also all input - output combination are not reasonable, see InteractionManager for further info +enum Input { + NONE, + TEXT, + TEXTBOX, + PASSWORD, + BOOL, // confirm like, yes or no dialog + CLICK, // for positional captchas + CHOICE, // choice from list + MULTIPLE, // multiple choice from list of elements + LIST, // arbitary list of elements + TABLE // table like data structure +} +// more can be implemented by need + +// this describes the type of the outgoing interaction +// ensure they can be logcial or'ed +enum Output { + CAPTCHA = 1, + QUESTION = 2, + NOTIFICATION = 4, +} + +struct DownloadInfo { + 1: FileID fid, + 2: string name, + 3: i64 speed, + 4: i32 eta, + 5: string format_eta, + 6: i64 bleft, + 7: i64 size, + 8: string format_size, + 9: Progress percent, + 10: DownloadStatus status, + 11: string statusmsg, + 12: string format_wait, + 13: i64 wait_until, + 14: PackageID packageID, + 15: string packageName, + 16: PluginName plugin, +} + +struct ServerStatus { + 1: bool pause, + 2: i16 active, + 3: i16 queue, + 4: i16 total, + 5: i64 speed, + 6: bool download, + 7: bool reconnect +} + +struct ConfigItem { + 1: string name, + 2: string description, + 3: string value, + 4: string type, +} + +struct ConfigSection { + 1: string name, + 2: string description, + 3: list<ConfigItem> items, + 4: optional string outline +} + +struct FileData { + 1: FileID fid, + 2: string url, + 3: string name, + 4: PluginName plugin, + 5: i64 size, + 6: string format_size, + 7: DownloadStatus status, + 8: string statusmsg, + 9: PackageID packageID, + 10: string error, + 11: i16 order +} + +struct PackageData { + 1: PackageID pid, + 2: string name, + 3: string folder, + 4: string site, + 5: string password, + 6: Destination dest, + 7: i16 order, + 8: optional i16 linksdone, + 9: optional i64 sizedone, + 10: optional i64 sizetotal, + 11: optional i16 linkstotal, + 12: optional list<FileData> links, + 13: optional list<FileID> fids +} + +struct InteractionTask { + 1: InteractionID iid, + 2: Input input, + 3: list<string> structure, + 4: list<string> preset, + 5: Output output, + 6: list<string> data, + 7: string title, + 8: string description, + 9: string plugin, +} + +struct CaptchaTask { + 1: i16 tid, + 2: binary data, + 3: string type, + 4: string resultType +} + +struct EventInfo { + 1: string eventname, + 2: optional i32 id, + 3: optional ElementType type, + 4: optional Destination destination +} + +struct UserData { + 1: string name, + 2: string email, + 3: i32 role, + 4: i32 permission, + 5: string templateName +} + +struct AccountInfo { + 1: i64 validuntil, + 2: string login, + 3: map<string, list<string>> options, + 4: bool valid, + 5: i64 trafficleft, + 6: i64 maxtraffic, + 7: bool premium, + 8: string type, +} + +struct ServiceCall { + 1: PluginName plugin, + 2: string func, + 3: optional list<string> arguments, + 4: optional bool parseArguments, //default False +} + +struct OnlineStatus { + 1: string name, + 2: PluginName plugin, + 3: string packagename, + 4: DownloadStatus status, + 5: i64 size, // size <= 0: unknown +} + +struct OnlineCheck { + 1: ResultID rid, // -1 -> nothing more to get + 2: map<string, OnlineStatus> data, //url to result +} + + +// exceptions + +exception PackageDoesNotExists{ + 1: PackageID pid +} + +exception FileDoesNotExists{ + 1: FileID fid +} + +exception ServiceDoesNotExists{ + 1: string plugin + 2: string func +} + +exception ServiceException{ + 1: string msg +} + +service Pyload { + + //config + string getConfigValue(1: string category, 2: string option, 3: string section), + void setConfigValue(1: string category, 2: string option, 3: string value, 4: string section), + map<string, ConfigSection> getConfig(), + map<string, ConfigSection> getPluginConfig(), + + // server status + void pauseServer(), + void unpauseServer(), + bool togglePause(), + ServerStatus statusServer(), + i64 freeSpace(), + string getServerVersion(), + void kill(), + void restart(), + list<string> getLog(1: i32 offset), + bool isTimeDownload(), + bool isTimeReconnect(), + bool toggleReconnect(), + + // download preparing + + // packagename - urls + map<string, LinkList> generatePackages(1: LinkList links), + map<PluginName, LinkList> checkURLs(1: LinkList urls), + map<PluginName, LinkList> parseURLs(1: string html, 2: string url), + + // parses results and generates packages + OnlineCheck checkOnlineStatus(1: LinkList urls), + OnlineCheck checkOnlineStatusContainer(1: LinkList urls, 2: string filename, 3: binary data) + + // poll results from previosly started online check + OnlineCheck pollResults(1: ResultID rid), + + // downloads - information + list<DownloadInfo> statusDownloads(), + PackageData getPackageData(1: PackageID pid) throws (1: PackageDoesNotExists e), + PackageData getPackageInfo(1: PackageID pid) throws (1: PackageDoesNotExists e), + FileData getFileData(1: FileID fid) throws (1: FileDoesNotExists e), + list<PackageData> getQueue(), + list<PackageData> getCollector(), + list<PackageData> getQueueData(), + list<PackageData> getCollectorData(), + map<i16, PackageID> getPackageOrder(1: Destination destination), + map<i16, FileID> getFileOrder(1: PackageID pid) + + // downloads - adding/deleting + list<PackageID> generateAndAddPackages(1: LinkList links, 2: Destination dest), + PackageID addPackage(1: string name, 2: LinkList links, 3: Destination dest), + void addFiles(1: PackageID pid, 2: LinkList links), + void uploadContainer(1: string filename, 2: binary data), + void deleteFiles(1: list<FileID> fids), + void deletePackages(1: list<PackageID> pids), + + // downloads - modifying + void pushToQueue(1: PackageID pid), + void pullFromQueue(1: PackageID pid), + void restartPackage(1: PackageID pid), + void restartFile(1: FileID fid), + void recheckPackage(1: PackageID pid), + void stopAllDownloads(), + void stopDownloads(1: list<FileID> fids), + void setPackageName(1: PackageID pid, 2: string name), + void movePackage(1: Destination destination, 2: PackageID pid), + void moveFiles(1: list<FileID> fids, 2: PackageID pid), + void orderPackage(1: PackageID pid, 2: i16 position), + void orderFile(1: FileID fid, 2: i16 position), + void setPackageData(1: PackageID pid, 2: map<string, string> data) throws (1: PackageDoesNotExists e), + list<PackageID> deleteFinished(), + void restartFailed(), + + //events + list<EventInfo> getEvents(1: string uuid) + + //accounts + list<AccountInfo> getAccounts(1: bool refresh), + list<string> getAccountTypes() + void updateAccount(1: PluginName plugin, 2: string account, 3: string password, 4: map<string, string> options), + void removeAccount(1: PluginName plugin, 2: string account), + + //auth + bool login(1: string username, 2: string password), + UserData getUserData(1: string username, 2:string password), + map<string, UserData> getAllUserData(), + + //services + + // servicename: description + map<PluginName, map<string, string>> getServices(), + bool hasService(1: PluginName plugin, 2: string func), + string call(1: ServiceCall info) throws (1: ServiceDoesNotExists ex, 2: ServiceException e), + + + //info + // {plugin: {name: value}} + map<PluginName, map<string, string>> getAllInfo(), + map<string, string> getInfoByPlugin(1: PluginName plugin), + + //scheduler + + // TODO + + + // User interaction + + //captcha + bool isCaptchaWaiting(), + CaptchaTask getCaptchaTask(1: bool exclusive), + string getCaptchaTaskStatus(1: TaskID tid), + void setCaptchaResult(1: TaskID tid, 2: string result), +} diff --git a/pyload/remote/thriftbackend/thriftgen/__init__.py b/pyload/remote/thriftbackend/thriftgen/__init__.py new file mode 100644 index 000000000..40a96afc6 --- /dev/null +++ b/pyload/remote/thriftbackend/thriftgen/__init__.py @@ -0,0 +1 @@ +# -*- coding: utf-8 -*- diff --git a/pyload/remote/thriftbackend/thriftgen/pyload/Pyload-remote b/pyload/remote/thriftbackend/thriftgen/pyload/Pyload-remote new file mode 100644 index 000000000..ddc1dd451 --- /dev/null +++ b/pyload/remote/thriftbackend/thriftgen/pyload/Pyload-remote @@ -0,0 +1,570 @@ +# +# Autogenerated by Thrift Compiler (0.9.0-dev) +# +# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING +# +# options string: py:slots, dynamic +# + +import sys +import pprint +from urlparse import urlparse +from thrift.transport import TTransport +from thrift.transport import TSocket +from thrift.transport import THttpClient +from thrift.protocol import TBinaryProtocol + +import Pyload +from ttypes import * + +if len(sys.argv) <= 1 or sys.argv[1] == '--help': + print + print 'Usage: ' + sys.argv[0] + ' [-h host[:port]] [-u url] [-f[ramed]] function [arg1 [arg2...]]' + print + print 'Functions:' + print ' string getConfigValue(string category, string option, string section)' + print ' void setConfigValue(string category, string option, string value, string section)' + print ' getConfig()' + print ' getPluginConfig()' + print ' void pauseServer()' + print ' void unpauseServer()' + print ' bool togglePause()' + print ' ServerStatus statusServer()' + print ' i64 freeSpace()' + print ' string getServerVersion()' + print ' void kill()' + print ' void restart()' + print ' getLog(i32 offset)' + print ' bool isTimeDownload()' + print ' bool isTimeReconnect()' + print ' bool toggleReconnect()' + print ' generatePackages(LinkList links)' + print ' checkURLs(LinkList urls)' + print ' parseURLs(string html, string url)' + print ' OnlineCheck checkOnlineStatus(LinkList urls)' + print ' OnlineCheck checkOnlineStatusContainer(LinkList urls, string filename, string data)' + print ' OnlineCheck pollResults(ResultID rid)' + print ' statusDownloads()' + print ' PackageData getPackageData(PackageID pid)' + print ' PackageData getPackageInfo(PackageID pid)' + print ' FileData getFileData(FileID fid)' + print ' getQueue()' + print ' getCollector()' + print ' getQueueData()' + print ' getCollectorData()' + print ' getPackageOrder(Destination destination)' + print ' getFileOrder(PackageID pid)' + print ' generateAndAddPackages(LinkList links, Destination dest)' + print ' PackageID addPackage(string name, LinkList links, Destination dest)' + print ' void addFiles(PackageID pid, LinkList links)' + print ' void uploadContainer(string filename, string data)' + print ' void deleteFiles( fids)' + print ' void deletePackages( pids)' + print ' void pushToQueue(PackageID pid)' + print ' void pullFromQueue(PackageID pid)' + print ' void restartPackage(PackageID pid)' + print ' void restartFile(FileID fid)' + print ' void recheckPackage(PackageID pid)' + print ' void stopAllDownloads()' + print ' void stopDownloads( fids)' + print ' void setPackageName(PackageID pid, string name)' + print ' void movePackage(Destination destination, PackageID pid)' + print ' void moveFiles( fids, PackageID pid)' + print ' void orderPackage(PackageID pid, i16 position)' + print ' void orderFile(FileID fid, i16 position)' + print ' void setPackageData(PackageID pid, data)' + print ' deleteFinished()' + print ' void restartFailed()' + print ' getEvents(string uuid)' + print ' getAccounts(bool refresh)' + print ' getAccountTypes()' + print ' void updateAccount(PluginName plugin, string account, string password, options)' + print ' void removeAccount(PluginName plugin, string account)' + print ' bool login(string username, string password)' + print ' UserData getUserData(string username, string password)' + print ' getAllUserData()' + print ' getServices()' + print ' bool hasService(PluginName plugin, string func)' + print ' string call(ServiceCall info)' + print ' getAllInfo()' + print ' getInfoByPlugin(PluginName plugin)' + print ' bool isCaptchaWaiting()' + print ' CaptchaTask getCaptchaTask(bool exclusive)' + print ' string getCaptchaTaskStatus(TaskID tid)' + print ' void setCaptchaResult(TaskID tid, string result)' + print + sys.exit(0) + +pp = pprint.PrettyPrinter(indent = 2) +host = 'localhost' +port = 9090 +uri = '' +framed = False +http = False +argi = 1 + +if sys.argv[argi] == '-h': + parts = sys.argv[argi+1].split(':') + host = parts[0] + if len(parts) > 1: + port = int(parts[1]) + argi += 2 + +if sys.argv[argi] == '-u': + url = urlparse(sys.argv[argi+1]) + parts = url[1].split(':') + host = parts[0] + if len(parts) > 1: + port = int(parts[1]) + else: + port = 80 + uri = url[2] + if url[4]: + uri += '?%s' % url[4] + http = True + argi += 2 + +if sys.argv[argi] == '-f' or sys.argv[argi] == '-framed': + framed = True + argi += 1 + +cmd = sys.argv[argi] +args = sys.argv[argi+1:] + +if http: + transport = THttpClient.THttpClient(host, port, uri) +else: + socket = TSocket.TSocket(host, port) + if framed: + transport = TTransport.TFramedTransport(socket) + else: + transport = TTransport.TBufferedTransport(socket) +protocol = TBinaryProtocol.TBinaryProtocol(transport) +client = Pyload.Client(protocol) +transport.open() + +if cmd == 'getConfigValue': + if len(args) != 3: + print 'getConfigValue requires 3 args' + sys.exit(1) + pp.pprint(client.getConfigValue(args[0], args[1], args[2],)) + +elif cmd == 'setConfigValue': + if len(args) != 4: + print 'setConfigValue requires 4 args' + sys.exit(1) + pp.pprint(client.setConfigValue(args[0], args[1], args[2], args[3],)) + +elif cmd == 'getConfig': + if len(args) != 0: + print 'getConfig requires 0 args' + sys.exit(1) + pp.pprint(client.getConfig()) + +elif cmd == 'getPluginConfig': + if len(args) != 0: + print 'getPluginConfig requires 0 args' + sys.exit(1) + pp.pprint(client.getPluginConfig()) + +elif cmd == 'pauseServer': + if len(args) != 0: + print 'pauseServer requires 0 args' + sys.exit(1) + pp.pprint(client.pauseServer()) + +elif cmd == 'unpauseServer': + if len(args) != 0: + print 'unpauseServer requires 0 args' + sys.exit(1) + pp.pprint(client.unpauseServer()) + +elif cmd == 'togglePause': + if len(args) != 0: + print 'togglePause requires 0 args' + sys.exit(1) + pp.pprint(client.togglePause()) + +elif cmd == 'statusServer': + if len(args) != 0: + print 'statusServer requires 0 args' + sys.exit(1) + pp.pprint(client.statusServer()) + +elif cmd == 'freeSpace': + if len(args) != 0: + print 'freeSpace requires 0 args' + sys.exit(1) + pp.pprint(client.freeSpace()) + +elif cmd == 'getServerVersion': + if len(args) != 0: + print 'getServerVersion requires 0 args' + sys.exit(1) + pp.pprint(client.getServerVersion()) + +elif cmd == 'kill': + if len(args) != 0: + print 'kill requires 0 args' + sys.exit(1) + pp.pprint(client.kill()) + +elif cmd == 'restart': + if len(args) != 0: + print 'restart requires 0 args' + sys.exit(1) + pp.pprint(client.restart()) + +elif cmd == 'getLog': + if len(args) != 1: + print 'getLog requires 1 args' + sys.exit(1) + pp.pprint(client.getLog(eval(args[0]),)) + +elif cmd == 'isTimeDownload': + if len(args) != 0: + print 'isTimeDownload requires 0 args' + sys.exit(1) + pp.pprint(client.isTimeDownload()) + +elif cmd == 'isTimeReconnect': + if len(args) != 0: + print 'isTimeReconnect requires 0 args' + sys.exit(1) + pp.pprint(client.isTimeReconnect()) + +elif cmd == 'toggleReconnect': + if len(args) != 0: + print 'toggleReconnect requires 0 args' + sys.exit(1) + pp.pprint(client.toggleReconnect()) + +elif cmd == 'generatePackages': + if len(args) != 1: + print 'generatePackages requires 1 args' + sys.exit(1) + pp.pprint(client.generatePackages(eval(args[0]),)) + +elif cmd == 'checkURLs': + if len(args) != 1: + print 'checkURLs requires 1 args' + sys.exit(1) + pp.pprint(client.checkURLs(eval(args[0]),)) + +elif cmd == 'parseURLs': + if len(args) != 2: + print 'parseURLs requires 2 args' + sys.exit(1) + pp.pprint(client.parseURLs(args[0], args[1],)) + +elif cmd == 'checkOnlineStatus': + if len(args) != 1: + print 'checkOnlineStatus requires 1 args' + sys.exit(1) + pp.pprint(client.checkOnlineStatus(eval(args[0]),)) + +elif cmd == 'checkOnlineStatusContainer': + if len(args) != 3: + print 'checkOnlineStatusContainer requires 3 args' + sys.exit(1) + pp.pprint(client.checkOnlineStatusContainer(eval(args[0]), args[1], args[2],)) + +elif cmd == 'pollResults': + if len(args) != 1: + print 'pollResults requires 1 args' + sys.exit(1) + pp.pprint(client.pollResults(eval(args[0]),)) + +elif cmd == 'statusDownloads': + if len(args) != 0: + print 'statusDownloads requires 0 args' + sys.exit(1) + pp.pprint(client.statusDownloads()) + +elif cmd == 'getPackageData': + if len(args) != 1: + print 'getPackageData requires 1 args' + sys.exit(1) + pp.pprint(client.getPackageData(eval(args[0]),)) + +elif cmd == 'getPackageInfo': + if len(args) != 1: + print 'getPackageInfo requires 1 args' + sys.exit(1) + pp.pprint(client.getPackageInfo(eval(args[0]),)) + +elif cmd == 'getFileData': + if len(args) != 1: + print 'getFileData requires 1 args' + sys.exit(1) + pp.pprint(client.getFileData(eval(args[0]),)) + +elif cmd == 'getQueue': + if len(args) != 0: + print 'getQueue requires 0 args' + sys.exit(1) + pp.pprint(client.getQueue()) + +elif cmd == 'getCollector': + if len(args) != 0: + print 'getCollector requires 0 args' + sys.exit(1) + pp.pprint(client.getCollector()) + +elif cmd == 'getQueueData': + if len(args) != 0: + print 'getQueueData requires 0 args' + sys.exit(1) + pp.pprint(client.getQueueData()) + +elif cmd == 'getCollectorData': + if len(args) != 0: + print 'getCollectorData requires 0 args' + sys.exit(1) + pp.pprint(client.getCollectorData()) + +elif cmd == 'getPackageOrder': + if len(args) != 1: + print 'getPackageOrder requires 1 args' + sys.exit(1) + pp.pprint(client.getPackageOrder(eval(args[0]),)) + +elif cmd == 'getFileOrder': + if len(args) != 1: + print 'getFileOrder requires 1 args' + sys.exit(1) + pp.pprint(client.getFileOrder(eval(args[0]),)) + +elif cmd == 'generateAndAddPackages': + if len(args) != 2: + print 'generateAndAddPackages requires 2 args' + sys.exit(1) + pp.pprint(client.generateAndAddPackages(eval(args[0]), eval(args[1]),)) + +elif cmd == 'addPackage': + if len(args) != 3: + print 'addPackage requires 3 args' + sys.exit(1) + pp.pprint(client.addPackage(args[0], eval(args[1]), eval(args[2]),)) + +elif cmd == 'addFiles': + if len(args) != 2: + print 'addFiles requires 2 args' + sys.exit(1) + pp.pprint(client.addFiles(eval(args[0]), eval(args[1]),)) + +elif cmd == 'uploadContainer': + if len(args) != 2: + print 'uploadContainer requires 2 args' + sys.exit(1) + pp.pprint(client.uploadContainer(args[0], args[1],)) + +elif cmd == 'deleteFiles': + if len(args) != 1: + print 'deleteFiles requires 1 args' + sys.exit(1) + pp.pprint(client.deleteFiles(eval(args[0]),)) + +elif cmd == 'deletePackages': + if len(args) != 1: + print 'deletePackages requires 1 args' + sys.exit(1) + pp.pprint(client.deletePackages(eval(args[0]),)) + +elif cmd == 'pushToQueue': + if len(args) != 1: + print 'pushToQueue requires 1 args' + sys.exit(1) + pp.pprint(client.pushToQueue(eval(args[0]),)) + +elif cmd == 'pullFromQueue': + if len(args) != 1: + print 'pullFromQueue requires 1 args' + sys.exit(1) + pp.pprint(client.pullFromQueue(eval(args[0]),)) + +elif cmd == 'restartPackage': + if len(args) != 1: + print 'restartPackage requires 1 args' + sys.exit(1) + pp.pprint(client.restartPackage(eval(args[0]),)) + +elif cmd == 'restartFile': + if len(args) != 1: + print 'restartFile requires 1 args' + sys.exit(1) + pp.pprint(client.restartFile(eval(args[0]),)) + +elif cmd == 'recheckPackage': + if len(args) != 1: + print 'recheckPackage requires 1 args' + sys.exit(1) + pp.pprint(client.recheckPackage(eval(args[0]),)) + +elif cmd == 'stopAllDownloads': + if len(args) != 0: + print 'stopAllDownloads requires 0 args' + sys.exit(1) + pp.pprint(client.stopAllDownloads()) + +elif cmd == 'stopDownloads': + if len(args) != 1: + print 'stopDownloads requires 1 args' + sys.exit(1) + pp.pprint(client.stopDownloads(eval(args[0]),)) + +elif cmd == 'setPackageName': + if len(args) != 2: + print 'setPackageName requires 2 args' + sys.exit(1) + pp.pprint(client.setPackageName(eval(args[0]), args[1],)) + +elif cmd == 'movePackage': + if len(args) != 2: + print 'movePackage requires 2 args' + sys.exit(1) + pp.pprint(client.movePackage(eval(args[0]), eval(args[1]),)) + +elif cmd == 'moveFiles': + if len(args) != 2: + print 'moveFiles requires 2 args' + sys.exit(1) + pp.pprint(client.moveFiles(eval(args[0]), eval(args[1]),)) + +elif cmd == 'orderPackage': + if len(args) != 2: + print 'orderPackage requires 2 args' + sys.exit(1) + pp.pprint(client.orderPackage(eval(args[0]), eval(args[1]),)) + +elif cmd == 'orderFile': + if len(args) != 2: + print 'orderFile requires 2 args' + sys.exit(1) + pp.pprint(client.orderFile(eval(args[0]), eval(args[1]),)) + +elif cmd == 'setPackageData': + if len(args) != 2: + print 'setPackageData requires 2 args' + sys.exit(1) + pp.pprint(client.setPackageData(eval(args[0]), eval(args[1]),)) + +elif cmd == 'deleteFinished': + if len(args) != 0: + print 'deleteFinished requires 0 args' + sys.exit(1) + pp.pprint(client.deleteFinished()) + +elif cmd == 'restartFailed': + if len(args) != 0: + print 'restartFailed requires 0 args' + sys.exit(1) + pp.pprint(client.restartFailed()) + +elif cmd == 'getEvents': + if len(args) != 1: + print 'getEvents requires 1 args' + sys.exit(1) + pp.pprint(client.getEvents(args[0],)) + +elif cmd == 'getAccounts': + if len(args) != 1: + print 'getAccounts requires 1 args' + sys.exit(1) + pp.pprint(client.getAccounts(eval(args[0]),)) + +elif cmd == 'getAccountTypes': + if len(args) != 0: + print 'getAccountTypes requires 0 args' + sys.exit(1) + pp.pprint(client.getAccountTypes()) + +elif cmd == 'updateAccount': + if len(args) != 4: + print 'updateAccount requires 4 args' + sys.exit(1) + pp.pprint(client.updateAccount(eval(args[0]), args[1], args[2], eval(args[3]),)) + +elif cmd == 'removeAccount': + if len(args) != 2: + print 'removeAccount requires 2 args' + sys.exit(1) + pp.pprint(client.removeAccount(eval(args[0]), args[1],)) + +elif cmd == 'login': + if len(args) != 2: + print 'login requires 2 args' + sys.exit(1) + pp.pprint(client.login(args[0], args[1],)) + +elif cmd == 'getUserData': + if len(args) != 2: + print 'getUserData requires 2 args' + sys.exit(1) + pp.pprint(client.getUserData(args[0], args[1],)) + +elif cmd == 'getAllUserData': + if len(args) != 0: + print 'getAllUserData requires 0 args' + sys.exit(1) + pp.pprint(client.getAllUserData()) + +elif cmd == 'getServices': + if len(args) != 0: + print 'getServices requires 0 args' + sys.exit(1) + pp.pprint(client.getServices()) + +elif cmd == 'hasService': + if len(args) != 2: + print 'hasService requires 2 args' + sys.exit(1) + pp.pprint(client.hasService(eval(args[0]), args[1],)) + +elif cmd == 'call': + if len(args) != 1: + print 'call requires 1 args' + sys.exit(1) + pp.pprint(client.call(eval(args[0]),)) + +elif cmd == 'getAllInfo': + if len(args) != 0: + print 'getAllInfo requires 0 args' + sys.exit(1) + pp.pprint(client.getAllInfo()) + +elif cmd == 'getInfoByPlugin': + if len(args) != 1: + print 'getInfoByPlugin requires 1 args' + sys.exit(1) + pp.pprint(client.getInfoByPlugin(eval(args[0]),)) + +elif cmd == 'isCaptchaWaiting': + if len(args) != 0: + print 'isCaptchaWaiting requires 0 args' + sys.exit(1) + pp.pprint(client.isCaptchaWaiting()) + +elif cmd == 'getCaptchaTask': + if len(args) != 1: + print 'getCaptchaTask requires 1 args' + sys.exit(1) + pp.pprint(client.getCaptchaTask(eval(args[0]),)) + +elif cmd == 'getCaptchaTaskStatus': + if len(args) != 1: + print 'getCaptchaTaskStatus requires 1 args' + sys.exit(1) + pp.pprint(client.getCaptchaTaskStatus(eval(args[0]),)) + +elif cmd == 'setCaptchaResult': + if len(args) != 2: + print 'setCaptchaResult requires 2 args' + sys.exit(1) + pp.pprint(client.setCaptchaResult(eval(args[0]), args[1],)) + +else: + print 'Unrecognized method %s' % cmd + sys.exit(1) + +transport.close() diff --git a/pyload/remote/thriftbackend/thriftgen/pyload/Pyload.py b/pyload/remote/thriftbackend/thriftgen/pyload/Pyload.py new file mode 100644 index 000000000..1ba11dbb6 --- /dev/null +++ b/pyload/remote/thriftbackend/thriftgen/pyload/Pyload.py @@ -0,0 +1,5976 @@ +# +# Autogenerated by Thrift Compiler (0.9.0-dev) +# +# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING +# +# options string: py:slots, dynamic +# + +from thrift.Thrift import TType, TMessageType, TException +from ttypes import * +from thrift.Thrift import TProcessor +from thrift.protocol.TBase import TBase, TExceptionBase + + +class Iface(object): + + def getConfigValue(self, category, option, section): + """ + Parameters: + - category + - option + - section + """ + pass + + + def setConfigValue(self, category, option, value, section): + """ + Parameters: + - category + - option + - value + - section + """ + pass + + + def getConfig(self,): + pass + + + def getPluginConfig(self,): + pass + + + def pauseServer(self,): + pass + + + def unpauseServer(self,): + pass + + + def togglePause(self,): + pass + + + def statusServer(self,): + pass + + + def freeSpace(self,): + pass + + + def getServerVersion(self,): + pass + + + def kill(self,): + pass + + + def restart(self,): + pass + + + def getLog(self, offset): + """ + Parameters: + - offset + """ + pass + + + def isTimeDownload(self,): + pass + + + def isTimeReconnect(self,): + pass + + + def toggleReconnect(self,): + pass + + + def generatePackages(self, links): + """ + Parameters: + - links + """ + pass + + + def checkURLs(self, urls): + """ + Parameters: + - urls + """ + pass + + + def parseURLs(self, html, url): + """ + Parameters: + - html + - url + """ + pass + + + def checkOnlineStatus(self, urls): + """ + Parameters: + - urls + """ + pass + + + def checkOnlineStatusContainer(self, urls, filename, data): + """ + Parameters: + - urls + - filename + - data + """ + pass + + + def pollResults(self, rid): + """ + Parameters: + - rid + """ + pass + + + def statusDownloads(self,): + pass + + + def getPackageData(self, pid): + """ + Parameters: + - pid + """ + pass + + + def getPackageInfo(self, pid): + """ + Parameters: + - pid + """ + pass + + + def getFileData(self, fid): + """ + Parameters: + - fid + """ + pass + + + def getQueue(self,): + pass + + + def getCollector(self,): + pass + + + def getQueueData(self,): + pass + + + def getCollectorData(self,): + pass + + + def getPackageOrder(self, destination): + """ + Parameters: + - destination + """ + pass + + + def getFileOrder(self, pid): + """ + Parameters: + - pid + """ + pass + + + def generateAndAddPackages(self, links, dest): + """ + Parameters: + - links + - dest + """ + pass + + + def addPackage(self, name, links, dest): + """ + Parameters: + - name + - links + - dest + """ + pass + + + def addFiles(self, pid, links): + """ + Parameters: + - pid + - links + """ + pass + + + def uploadContainer(self, filename, data): + """ + Parameters: + - filename + - data + """ + pass + + + def deleteFiles(self, fids): + """ + Parameters: + - fids + """ + pass + + + def deletePackages(self, pids): + """ + Parameters: + - pids + """ + pass + + + def pushToQueue(self, pid): + """ + Parameters: + - pid + """ + pass + + + def pullFromQueue(self, pid): + """ + Parameters: + - pid + """ + pass + + + def restartPackage(self, pid): + """ + Parameters: + - pid + """ + pass + + + def restartFile(self, fid): + """ + Parameters: + - fid + """ + pass + + + def recheckPackage(self, pid): + """ + Parameters: + - pid + """ + pass + + + def stopAllDownloads(self,): + pass + + + def stopDownloads(self, fids): + """ + Parameters: + - fids + """ + pass + + + def setPackageName(self, pid, name): + """ + Parameters: + - pid + - name + """ + pass + + + def movePackage(self, destination, pid): + """ + Parameters: + - destination + - pid + """ + pass + + + def moveFiles(self, fids, pid): + """ + Parameters: + - fids + - pid + """ + pass + + + def orderPackage(self, pid, position): + """ + Parameters: + - pid + - position + """ + pass + + + def orderFile(self, fid, position): + """ + Parameters: + - fid + - position + """ + pass + + + def setPackageData(self, pid, data): + """ + Parameters: + - pid + - data + """ + pass + + + def deleteFinished(self,): + pass + + + def restartFailed(self,): + pass + + + def getEvents(self, uuid): + """ + Parameters: + - uuid + """ + pass + + + def getAccounts(self, refresh): + """ + Parameters: + - refresh + """ + pass + + + def getAccountTypes(self,): + pass + + + def updateAccount(self, plugin, account, password, options): + """ + Parameters: + - plugin + - account + - password + - options + """ + pass + + + def removeAccount(self, plugin, account): + """ + Parameters: + - plugin + - account + """ + pass + + + def login(self, username, password): + """ + Parameters: + - username + - password + """ + pass + + + def getUserData(self, username, password): + """ + Parameters: + - username + - password + """ + pass + + + def getAllUserData(self,): + pass + + + def getServices(self,): + pass + + + def hasService(self, plugin, func): + """ + Parameters: + - plugin + - func + """ + pass + + + def call(self, info): + """ + Parameters: + - info + """ + pass + + + def getAllInfo(self,): + pass + + + def getInfoByPlugin(self, plugin): + """ + Parameters: + - plugin + """ + pass + + + def isCaptchaWaiting(self,): + pass + + + def getCaptchaTask(self, exclusive): + """ + Parameters: + - exclusive + """ + pass + + + def getCaptchaTaskStatus(self, tid): + """ + Parameters: + - tid + """ + pass + + + def setCaptchaResult(self, tid, result): + """ + Parameters: + - tid + - result + """ + pass + + +class Client(Iface): + + def __init__(self, iprot, oprot=None): + self._iprot = self._oprot = iprot + if oprot is not None: + self._oprot = oprot + self._seqid = 0 + + + def getConfigValue(self, category, option, section): + """ + Parameters: + - category + - option + - section + """ + self.send_getConfigValue(category, option, section) + return self.recv_getConfigValue() + + + def send_getConfigValue(self, category, option, section): + self._oprot.writeMessageBegin('getConfigValue', TMessageType.CALL, self._seqid) + args = getConfigValue_args() + args.category = category + args.option = option + args.section = section + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_getConfigValue(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = getConfigValue_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + raise TApplicationException(TApplicationException.MISSING_RESULT, "getConfigValue failed: unknown result"); + + + def setConfigValue(self, category, option, value, section): + """ + Parameters: + - category + - option + - value + - section + """ + self.send_setConfigValue(category, option, value, section) + self.recv_setConfigValue() + + + def send_setConfigValue(self, category, option, value, section): + self._oprot.writeMessageBegin('setConfigValue', TMessageType.CALL, self._seqid) + args = setConfigValue_args() + args.category = category + args.option = option + args.value = value + args.section = section + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_setConfigValue(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = setConfigValue_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + return + + + def getConfig(self,): + self.send_getConfig() + return self.recv_getConfig() + + + def send_getConfig(self,): + self._oprot.writeMessageBegin('getConfig', TMessageType.CALL, self._seqid) + args = getConfig_args() + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_getConfig(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = getConfig_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + raise TApplicationException(TApplicationException.MISSING_RESULT, "getConfig failed: unknown result"); + + + def getPluginConfig(self,): + self.send_getPluginConfig() + return self.recv_getPluginConfig() + + + def send_getPluginConfig(self,): + self._oprot.writeMessageBegin('getPluginConfig', TMessageType.CALL, self._seqid) + args = getPluginConfig_args() + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_getPluginConfig(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = getPluginConfig_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + raise TApplicationException(TApplicationException.MISSING_RESULT, "getPluginConfig failed: unknown result"); + + + def pauseServer(self,): + self.send_pauseServer() + self.recv_pauseServer() + + + def send_pauseServer(self,): + self._oprot.writeMessageBegin('pauseServer', TMessageType.CALL, self._seqid) + args = pauseServer_args() + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_pauseServer(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = pauseServer_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + return + + + def unpauseServer(self,): + self.send_unpauseServer() + self.recv_unpauseServer() + + + def send_unpauseServer(self,): + self._oprot.writeMessageBegin('unpauseServer', TMessageType.CALL, self._seqid) + args = unpauseServer_args() + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_unpauseServer(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = unpauseServer_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + return + + + def togglePause(self,): + self.send_togglePause() + return self.recv_togglePause() + + + def send_togglePause(self,): + self._oprot.writeMessageBegin('togglePause', TMessageType.CALL, self._seqid) + args = togglePause_args() + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_togglePause(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = togglePause_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + raise TApplicationException(TApplicationException.MISSING_RESULT, "togglePause failed: unknown result"); + + + def statusServer(self,): + self.send_statusServer() + return self.recv_statusServer() + + + def send_statusServer(self,): + self._oprot.writeMessageBegin('statusServer', TMessageType.CALL, self._seqid) + args = statusServer_args() + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_statusServer(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = statusServer_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + raise TApplicationException(TApplicationException.MISSING_RESULT, "statusServer failed: unknown result"); + + + def freeSpace(self,): + self.send_freeSpace() + return self.recv_freeSpace() + + + def send_freeSpace(self,): + self._oprot.writeMessageBegin('freeSpace', TMessageType.CALL, self._seqid) + args = freeSpace_args() + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_freeSpace(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = freeSpace_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + raise TApplicationException(TApplicationException.MISSING_RESULT, "freeSpace failed: unknown result"); + + + def getServerVersion(self,): + self.send_getServerVersion() + return self.recv_getServerVersion() + + + def send_getServerVersion(self,): + self._oprot.writeMessageBegin('getServerVersion', TMessageType.CALL, self._seqid) + args = getServerVersion_args() + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_getServerVersion(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = getServerVersion_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + raise TApplicationException(TApplicationException.MISSING_RESULT, "getServerVersion failed: unknown result"); + + + def kill(self,): + self.send_kill() + self.recv_kill() + + + def send_kill(self,): + self._oprot.writeMessageBegin('kill', TMessageType.CALL, self._seqid) + args = kill_args() + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_kill(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = kill_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + return + + + def restart(self,): + self.send_restart() + self.recv_restart() + + + def send_restart(self,): + self._oprot.writeMessageBegin('restart', TMessageType.CALL, self._seqid) + args = restart_args() + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_restart(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = restart_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + return + + + def getLog(self, offset): + """ + Parameters: + - offset + """ + self.send_getLog(offset) + return self.recv_getLog() + + + def send_getLog(self, offset): + self._oprot.writeMessageBegin('getLog', TMessageType.CALL, self._seqid) + args = getLog_args() + args.offset = offset + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_getLog(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = getLog_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + raise TApplicationException(TApplicationException.MISSING_RESULT, "getLog failed: unknown result"); + + + def isTimeDownload(self,): + self.send_isTimeDownload() + return self.recv_isTimeDownload() + + + def send_isTimeDownload(self,): + self._oprot.writeMessageBegin('isTimeDownload', TMessageType.CALL, self._seqid) + args = isTimeDownload_args() + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_isTimeDownload(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = isTimeDownload_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + raise TApplicationException(TApplicationException.MISSING_RESULT, "isTimeDownload failed: unknown result"); + + + def isTimeReconnect(self,): + self.send_isTimeReconnect() + return self.recv_isTimeReconnect() + + + def send_isTimeReconnect(self,): + self._oprot.writeMessageBegin('isTimeReconnect', TMessageType.CALL, self._seqid) + args = isTimeReconnect_args() + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_isTimeReconnect(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = isTimeReconnect_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + raise TApplicationException(TApplicationException.MISSING_RESULT, "isTimeReconnect failed: unknown result"); + + + def toggleReconnect(self,): + self.send_toggleReconnect() + return self.recv_toggleReconnect() + + + def send_toggleReconnect(self,): + self._oprot.writeMessageBegin('toggleReconnect', TMessageType.CALL, self._seqid) + args = toggleReconnect_args() + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_toggleReconnect(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = toggleReconnect_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + raise TApplicationException(TApplicationException.MISSING_RESULT, "toggleReconnect failed: unknown result"); + + + def generatePackages(self, links): + """ + Parameters: + - links + """ + self.send_generatePackages(links) + return self.recv_generatePackages() + + + def send_generatePackages(self, links): + self._oprot.writeMessageBegin('generatePackages', TMessageType.CALL, self._seqid) + args = generatePackages_args() + args.links = links + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_generatePackages(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = generatePackages_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + raise TApplicationException(TApplicationException.MISSING_RESULT, "generatePackages failed: unknown result"); + + + def checkURLs(self, urls): + """ + Parameters: + - urls + """ + self.send_checkURLs(urls) + return self.recv_checkURLs() + + + def send_checkURLs(self, urls): + self._oprot.writeMessageBegin('checkURLs', TMessageType.CALL, self._seqid) + args = checkURLs_args() + args.urls = urls + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_checkURLs(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = checkURLs_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + raise TApplicationException(TApplicationException.MISSING_RESULT, "checkURLs failed: unknown result"); + + + def parseURLs(self, html, url): + """ + Parameters: + - html + - url + """ + self.send_parseURLs(html, url) + return self.recv_parseURLs() + + + def send_parseURLs(self, html, url): + self._oprot.writeMessageBegin('parseURLs', TMessageType.CALL, self._seqid) + args = parseURLs_args() + args.html = html + args.url = url + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_parseURLs(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = parseURLs_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + raise TApplicationException(TApplicationException.MISSING_RESULT, "parseURLs failed: unknown result"); + + + def checkOnlineStatus(self, urls): + """ + Parameters: + - urls + """ + self.send_checkOnlineStatus(urls) + return self.recv_checkOnlineStatus() + + + def send_checkOnlineStatus(self, urls): + self._oprot.writeMessageBegin('checkOnlineStatus', TMessageType.CALL, self._seqid) + args = checkOnlineStatus_args() + args.urls = urls + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_checkOnlineStatus(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = checkOnlineStatus_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + raise TApplicationException(TApplicationException.MISSING_RESULT, "checkOnlineStatus failed: unknown result"); + + + def checkOnlineStatusContainer(self, urls, filename, data): + """ + Parameters: + - urls + - filename + - data + """ + self.send_checkOnlineStatusContainer(urls, filename, data) + return self.recv_checkOnlineStatusContainer() + + + def send_checkOnlineStatusContainer(self, urls, filename, data): + self._oprot.writeMessageBegin('checkOnlineStatusContainer', TMessageType.CALL, self._seqid) + args = checkOnlineStatusContainer_args() + args.urls = urls + args.filename = filename + args.data = data + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_checkOnlineStatusContainer(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = checkOnlineStatusContainer_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + raise TApplicationException(TApplicationException.MISSING_RESULT, "checkOnlineStatusContainer failed: unknown result"); + + + def pollResults(self, rid): + """ + Parameters: + - rid + """ + self.send_pollResults(rid) + return self.recv_pollResults() + + + def send_pollResults(self, rid): + self._oprot.writeMessageBegin('pollResults', TMessageType.CALL, self._seqid) + args = pollResults_args() + args.rid = rid + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_pollResults(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = pollResults_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + raise TApplicationException(TApplicationException.MISSING_RESULT, "pollResults failed: unknown result"); + + + def statusDownloads(self,): + self.send_statusDownloads() + return self.recv_statusDownloads() + + + def send_statusDownloads(self,): + self._oprot.writeMessageBegin('statusDownloads', TMessageType.CALL, self._seqid) + args = statusDownloads_args() + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_statusDownloads(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = statusDownloads_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + raise TApplicationException(TApplicationException.MISSING_RESULT, "statusDownloads failed: unknown result"); + + + def getPackageData(self, pid): + """ + Parameters: + - pid + """ + self.send_getPackageData(pid) + return self.recv_getPackageData() + + + def send_getPackageData(self, pid): + self._oprot.writeMessageBegin('getPackageData', TMessageType.CALL, self._seqid) + args = getPackageData_args() + args.pid = pid + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_getPackageData(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = getPackageData_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + if result.e is not None: + raise result.e + raise TApplicationException(TApplicationException.MISSING_RESULT, "getPackageData failed: unknown result"); + + + def getPackageInfo(self, pid): + """ + Parameters: + - pid + """ + self.send_getPackageInfo(pid) + return self.recv_getPackageInfo() + + + def send_getPackageInfo(self, pid): + self._oprot.writeMessageBegin('getPackageInfo', TMessageType.CALL, self._seqid) + args = getPackageInfo_args() + args.pid = pid + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_getPackageInfo(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = getPackageInfo_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + if result.e is not None: + raise result.e + raise TApplicationException(TApplicationException.MISSING_RESULT, "getPackageInfo failed: unknown result"); + + + def getFileData(self, fid): + """ + Parameters: + - fid + """ + self.send_getFileData(fid) + return self.recv_getFileData() + + + def send_getFileData(self, fid): + self._oprot.writeMessageBegin('getFileData', TMessageType.CALL, self._seqid) + args = getFileData_args() + args.fid = fid + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_getFileData(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = getFileData_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + if result.e is not None: + raise result.e + raise TApplicationException(TApplicationException.MISSING_RESULT, "getFileData failed: unknown result"); + + + def getQueue(self,): + self.send_getQueue() + return self.recv_getQueue() + + + def send_getQueue(self,): + self._oprot.writeMessageBegin('getQueue', TMessageType.CALL, self._seqid) + args = getQueue_args() + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_getQueue(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = getQueue_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + raise TApplicationException(TApplicationException.MISSING_RESULT, "getQueue failed: unknown result"); + + + def getCollector(self,): + self.send_getCollector() + return self.recv_getCollector() + + + def send_getCollector(self,): + self._oprot.writeMessageBegin('getCollector', TMessageType.CALL, self._seqid) + args = getCollector_args() + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_getCollector(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = getCollector_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + raise TApplicationException(TApplicationException.MISSING_RESULT, "getCollector failed: unknown result"); + + + def getQueueData(self,): + self.send_getQueueData() + return self.recv_getQueueData() + + + def send_getQueueData(self,): + self._oprot.writeMessageBegin('getQueueData', TMessageType.CALL, self._seqid) + args = getQueueData_args() + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_getQueueData(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = getQueueData_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + raise TApplicationException(TApplicationException.MISSING_RESULT, "getQueueData failed: unknown result"); + + + def getCollectorData(self,): + self.send_getCollectorData() + return self.recv_getCollectorData() + + + def send_getCollectorData(self,): + self._oprot.writeMessageBegin('getCollectorData', TMessageType.CALL, self._seqid) + args = getCollectorData_args() + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_getCollectorData(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = getCollectorData_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + raise TApplicationException(TApplicationException.MISSING_RESULT, "getCollectorData failed: unknown result"); + + + def getPackageOrder(self, destination): + """ + Parameters: + - destination + """ + self.send_getPackageOrder(destination) + return self.recv_getPackageOrder() + + + def send_getPackageOrder(self, destination): + self._oprot.writeMessageBegin('getPackageOrder', TMessageType.CALL, self._seqid) + args = getPackageOrder_args() + args.destination = destination + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_getPackageOrder(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = getPackageOrder_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + raise TApplicationException(TApplicationException.MISSING_RESULT, "getPackageOrder failed: unknown result"); + + + def getFileOrder(self, pid): + """ + Parameters: + - pid + """ + self.send_getFileOrder(pid) + return self.recv_getFileOrder() + + + def send_getFileOrder(self, pid): + self._oprot.writeMessageBegin('getFileOrder', TMessageType.CALL, self._seqid) + args = getFileOrder_args() + args.pid = pid + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_getFileOrder(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = getFileOrder_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + raise TApplicationException(TApplicationException.MISSING_RESULT, "getFileOrder failed: unknown result"); + + + def generateAndAddPackages(self, links, dest): + """ + Parameters: + - links + - dest + """ + self.send_generateAndAddPackages(links, dest) + return self.recv_generateAndAddPackages() + + + def send_generateAndAddPackages(self, links, dest): + self._oprot.writeMessageBegin('generateAndAddPackages', TMessageType.CALL, self._seqid) + args = generateAndAddPackages_args() + args.links = links + args.dest = dest + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_generateAndAddPackages(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = generateAndAddPackages_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + raise TApplicationException(TApplicationException.MISSING_RESULT, "generateAndAddPackages failed: unknown result"); + + + def addPackage(self, name, links, dest): + """ + Parameters: + - name + - links + - dest + """ + self.send_addPackage(name, links, dest) + return self.recv_addPackage() + + + def send_addPackage(self, name, links, dest): + self._oprot.writeMessageBegin('addPackage', TMessageType.CALL, self._seqid) + args = addPackage_args() + args.name = name + args.links = links + args.dest = dest + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_addPackage(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = addPackage_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + raise TApplicationException(TApplicationException.MISSING_RESULT, "addPackage failed: unknown result"); + + + def addFiles(self, pid, links): + """ + Parameters: + - pid + - links + """ + self.send_addFiles(pid, links) + self.recv_addFiles() + + + def send_addFiles(self, pid, links): + self._oprot.writeMessageBegin('addFiles', TMessageType.CALL, self._seqid) + args = addFiles_args() + args.pid = pid + args.links = links + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_addFiles(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = addFiles_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + return + + + def uploadContainer(self, filename, data): + """ + Parameters: + - filename + - data + """ + self.send_uploadContainer(filename, data) + self.recv_uploadContainer() + + + def send_uploadContainer(self, filename, data): + self._oprot.writeMessageBegin('uploadContainer', TMessageType.CALL, self._seqid) + args = uploadContainer_args() + args.filename = filename + args.data = data + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_uploadContainer(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = uploadContainer_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + return + + + def deleteFiles(self, fids): + """ + Parameters: + - fids + """ + self.send_deleteFiles(fids) + self.recv_deleteFiles() + + + def send_deleteFiles(self, fids): + self._oprot.writeMessageBegin('deleteFiles', TMessageType.CALL, self._seqid) + args = deleteFiles_args() + args.fids = fids + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_deleteFiles(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = deleteFiles_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + return + + + def deletePackages(self, pids): + """ + Parameters: + - pids + """ + self.send_deletePackages(pids) + self.recv_deletePackages() + + + def send_deletePackages(self, pids): + self._oprot.writeMessageBegin('deletePackages', TMessageType.CALL, self._seqid) + args = deletePackages_args() + args.pids = pids + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_deletePackages(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = deletePackages_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + return + + + def pushToQueue(self, pid): + """ + Parameters: + - pid + """ + self.send_pushToQueue(pid) + self.recv_pushToQueue() + + + def send_pushToQueue(self, pid): + self._oprot.writeMessageBegin('pushToQueue', TMessageType.CALL, self._seqid) + args = pushToQueue_args() + args.pid = pid + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_pushToQueue(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = pushToQueue_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + return + + + def pullFromQueue(self, pid): + """ + Parameters: + - pid + """ + self.send_pullFromQueue(pid) + self.recv_pullFromQueue() + + + def send_pullFromQueue(self, pid): + self._oprot.writeMessageBegin('pullFromQueue', TMessageType.CALL, self._seqid) + args = pullFromQueue_args() + args.pid = pid + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_pullFromQueue(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = pullFromQueue_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + return + + + def restartPackage(self, pid): + """ + Parameters: + - pid + """ + self.send_restartPackage(pid) + self.recv_restartPackage() + + + def send_restartPackage(self, pid): + self._oprot.writeMessageBegin('restartPackage', TMessageType.CALL, self._seqid) + args = restartPackage_args() + args.pid = pid + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_restartPackage(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = restartPackage_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + return + + + def restartFile(self, fid): + """ + Parameters: + - fid + """ + self.send_restartFile(fid) + self.recv_restartFile() + + + def send_restartFile(self, fid): + self._oprot.writeMessageBegin('restartFile', TMessageType.CALL, self._seqid) + args = restartFile_args() + args.fid = fid + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_restartFile(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = restartFile_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + return + + + def recheckPackage(self, pid): + """ + Parameters: + - pid + """ + self.send_recheckPackage(pid) + self.recv_recheckPackage() + + + def send_recheckPackage(self, pid): + self._oprot.writeMessageBegin('recheckPackage', TMessageType.CALL, self._seqid) + args = recheckPackage_args() + args.pid = pid + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_recheckPackage(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = recheckPackage_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + return + + + def stopAllDownloads(self,): + self.send_stopAllDownloads() + self.recv_stopAllDownloads() + + + def send_stopAllDownloads(self,): + self._oprot.writeMessageBegin('stopAllDownloads', TMessageType.CALL, self._seqid) + args = stopAllDownloads_args() + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_stopAllDownloads(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = stopAllDownloads_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + return + + + def stopDownloads(self, fids): + """ + Parameters: + - fids + """ + self.send_stopDownloads(fids) + self.recv_stopDownloads() + + + def send_stopDownloads(self, fids): + self._oprot.writeMessageBegin('stopDownloads', TMessageType.CALL, self._seqid) + args = stopDownloads_args() + args.fids = fids + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_stopDownloads(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = stopDownloads_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + return + + + def setPackageName(self, pid, name): + """ + Parameters: + - pid + - name + """ + self.send_setPackageName(pid, name) + self.recv_setPackageName() + + + def send_setPackageName(self, pid, name): + self._oprot.writeMessageBegin('setPackageName', TMessageType.CALL, self._seqid) + args = setPackageName_args() + args.pid = pid + args.name = name + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_setPackageName(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = setPackageName_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + return + + + def movePackage(self, destination, pid): + """ + Parameters: + - destination + - pid + """ + self.send_movePackage(destination, pid) + self.recv_movePackage() + + + def send_movePackage(self, destination, pid): + self._oprot.writeMessageBegin('movePackage', TMessageType.CALL, self._seqid) + args = movePackage_args() + args.destination = destination + args.pid = pid + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_movePackage(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = movePackage_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + return + + + def moveFiles(self, fids, pid): + """ + Parameters: + - fids + - pid + """ + self.send_moveFiles(fids, pid) + self.recv_moveFiles() + + + def send_moveFiles(self, fids, pid): + self._oprot.writeMessageBegin('moveFiles', TMessageType.CALL, self._seqid) + args = moveFiles_args() + args.fids = fids + args.pid = pid + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_moveFiles(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = moveFiles_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + return + + + def orderPackage(self, pid, position): + """ + Parameters: + - pid + - position + """ + self.send_orderPackage(pid, position) + self.recv_orderPackage() + + + def send_orderPackage(self, pid, position): + self._oprot.writeMessageBegin('orderPackage', TMessageType.CALL, self._seqid) + args = orderPackage_args() + args.pid = pid + args.position = position + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_orderPackage(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = orderPackage_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + return + + + def orderFile(self, fid, position): + """ + Parameters: + - fid + - position + """ + self.send_orderFile(fid, position) + self.recv_orderFile() + + + def send_orderFile(self, fid, position): + self._oprot.writeMessageBegin('orderFile', TMessageType.CALL, self._seqid) + args = orderFile_args() + args.fid = fid + args.position = position + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_orderFile(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = orderFile_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + return + + + def setPackageData(self, pid, data): + """ + Parameters: + - pid + - data + """ + self.send_setPackageData(pid, data) + self.recv_setPackageData() + + + def send_setPackageData(self, pid, data): + self._oprot.writeMessageBegin('setPackageData', TMessageType.CALL, self._seqid) + args = setPackageData_args() + args.pid = pid + args.data = data + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_setPackageData(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = setPackageData_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.e is not None: + raise result.e + return + + + def deleteFinished(self,): + self.send_deleteFinished() + return self.recv_deleteFinished() + + + def send_deleteFinished(self,): + self._oprot.writeMessageBegin('deleteFinished', TMessageType.CALL, self._seqid) + args = deleteFinished_args() + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_deleteFinished(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = deleteFinished_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + raise TApplicationException(TApplicationException.MISSING_RESULT, "deleteFinished failed: unknown result"); + + + def restartFailed(self,): + self.send_restartFailed() + self.recv_restartFailed() + + + def send_restartFailed(self,): + self._oprot.writeMessageBegin('restartFailed', TMessageType.CALL, self._seqid) + args = restartFailed_args() + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_restartFailed(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = restartFailed_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + return + + + def getEvents(self, uuid): + """ + Parameters: + - uuid + """ + self.send_getEvents(uuid) + return self.recv_getEvents() + + + def send_getEvents(self, uuid): + self._oprot.writeMessageBegin('getEvents', TMessageType.CALL, self._seqid) + args = getEvents_args() + args.uuid = uuid + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_getEvents(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = getEvents_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + raise TApplicationException(TApplicationException.MISSING_RESULT, "getEvents failed: unknown result"); + + + def getAccounts(self, refresh): + """ + Parameters: + - refresh + """ + self.send_getAccounts(refresh) + return self.recv_getAccounts() + + + def send_getAccounts(self, refresh): + self._oprot.writeMessageBegin('getAccounts', TMessageType.CALL, self._seqid) + args = getAccounts_args() + args.refresh = refresh + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_getAccounts(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = getAccounts_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + raise TApplicationException(TApplicationException.MISSING_RESULT, "getAccounts failed: unknown result"); + + + def getAccountTypes(self,): + self.send_getAccountTypes() + return self.recv_getAccountTypes() + + + def send_getAccountTypes(self,): + self._oprot.writeMessageBegin('getAccountTypes', TMessageType.CALL, self._seqid) + args = getAccountTypes_args() + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_getAccountTypes(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = getAccountTypes_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + raise TApplicationException(TApplicationException.MISSING_RESULT, "getAccountTypes failed: unknown result"); + + + def updateAccount(self, plugin, account, password, options): + """ + Parameters: + - plugin + - account + - password + - options + """ + self.send_updateAccount(plugin, account, password, options) + self.recv_updateAccount() + + + def send_updateAccount(self, plugin, account, password, options): + self._oprot.writeMessageBegin('updateAccount', TMessageType.CALL, self._seqid) + args = updateAccount_args() + args.plugin = plugin + args.account = account + args.password = password + args.options = options + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_updateAccount(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = updateAccount_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + return + + + def removeAccount(self, plugin, account): + """ + Parameters: + - plugin + - account + """ + self.send_removeAccount(plugin, account) + self.recv_removeAccount() + + + def send_removeAccount(self, plugin, account): + self._oprot.writeMessageBegin('removeAccount', TMessageType.CALL, self._seqid) + args = removeAccount_args() + args.plugin = plugin + args.account = account + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_removeAccount(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = removeAccount_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + return + + + def login(self, username, password): + """ + Parameters: + - username + - password + """ + self.send_login(username, password) + return self.recv_login() + + + def send_login(self, username, password): + self._oprot.writeMessageBegin('login', TMessageType.CALL, self._seqid) + args = login_args() + args.username = username + args.password = password + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_login(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = login_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + raise TApplicationException(TApplicationException.MISSING_RESULT, "login failed: unknown result"); + + + def getUserData(self, username, password): + """ + Parameters: + - username + - password + """ + self.send_getUserData(username, password) + return self.recv_getUserData() + + + def send_getUserData(self, username, password): + self._oprot.writeMessageBegin('getUserData', TMessageType.CALL, self._seqid) + args = getUserData_args() + args.username = username + args.password = password + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_getUserData(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = getUserData_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + raise TApplicationException(TApplicationException.MISSING_RESULT, "getUserData failed: unknown result"); + + + def getAllUserData(self,): + self.send_getAllUserData() + return self.recv_getAllUserData() + + + def send_getAllUserData(self,): + self._oprot.writeMessageBegin('getAllUserData', TMessageType.CALL, self._seqid) + args = getAllUserData_args() + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_getAllUserData(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = getAllUserData_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + raise TApplicationException(TApplicationException.MISSING_RESULT, "getAllUserData failed: unknown result"); + + + def getServices(self,): + self.send_getServices() + return self.recv_getServices() + + + def send_getServices(self,): + self._oprot.writeMessageBegin('getServices', TMessageType.CALL, self._seqid) + args = getServices_args() + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_getServices(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = getServices_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + raise TApplicationException(TApplicationException.MISSING_RESULT, "getServices failed: unknown result"); + + + def hasService(self, plugin, func): + """ + Parameters: + - plugin + - func + """ + self.send_hasService(plugin, func) + return self.recv_hasService() + + + def send_hasService(self, plugin, func): + self._oprot.writeMessageBegin('hasService', TMessageType.CALL, self._seqid) + args = hasService_args() + args.plugin = plugin + args.func = func + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_hasService(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = hasService_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + raise TApplicationException(TApplicationException.MISSING_RESULT, "hasService failed: unknown result"); + + + def call(self, info): + """ + Parameters: + - info + """ + self.send_call(info) + return self.recv_call() + + + def send_call(self, info): + self._oprot.writeMessageBegin('call', TMessageType.CALL, self._seqid) + args = call_args() + args.info = info + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_call(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = call_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + if result.ex is not None: + raise result.ex + if result.e is not None: + raise result.e + raise TApplicationException(TApplicationException.MISSING_RESULT, "call failed: unknown result"); + + + def getAllInfo(self,): + self.send_getAllInfo() + return self.recv_getAllInfo() + + + def send_getAllInfo(self,): + self._oprot.writeMessageBegin('getAllInfo', TMessageType.CALL, self._seqid) + args = getAllInfo_args() + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_getAllInfo(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = getAllInfo_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + raise TApplicationException(TApplicationException.MISSING_RESULT, "getAllInfo failed: unknown result"); + + + def getInfoByPlugin(self, plugin): + """ + Parameters: + - plugin + """ + self.send_getInfoByPlugin(plugin) + return self.recv_getInfoByPlugin() + + + def send_getInfoByPlugin(self, plugin): + self._oprot.writeMessageBegin('getInfoByPlugin', TMessageType.CALL, self._seqid) + args = getInfoByPlugin_args() + args.plugin = plugin + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_getInfoByPlugin(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = getInfoByPlugin_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + raise TApplicationException(TApplicationException.MISSING_RESULT, "getInfoByPlugin failed: unknown result"); + + + def isCaptchaWaiting(self,): + self.send_isCaptchaWaiting() + return self.recv_isCaptchaWaiting() + + + def send_isCaptchaWaiting(self,): + self._oprot.writeMessageBegin('isCaptchaWaiting', TMessageType.CALL, self._seqid) + args = isCaptchaWaiting_args() + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_isCaptchaWaiting(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = isCaptchaWaiting_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + raise TApplicationException(TApplicationException.MISSING_RESULT, "isCaptchaWaiting failed: unknown result"); + + + def getCaptchaTask(self, exclusive): + """ + Parameters: + - exclusive + """ + self.send_getCaptchaTask(exclusive) + return self.recv_getCaptchaTask() + + + def send_getCaptchaTask(self, exclusive): + self._oprot.writeMessageBegin('getCaptchaTask', TMessageType.CALL, self._seqid) + args = getCaptchaTask_args() + args.exclusive = exclusive + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_getCaptchaTask(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = getCaptchaTask_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + raise TApplicationException(TApplicationException.MISSING_RESULT, "getCaptchaTask failed: unknown result"); + + + def getCaptchaTaskStatus(self, tid): + """ + Parameters: + - tid + """ + self.send_getCaptchaTaskStatus(tid) + return self.recv_getCaptchaTaskStatus() + + + def send_getCaptchaTaskStatus(self, tid): + self._oprot.writeMessageBegin('getCaptchaTaskStatus', TMessageType.CALL, self._seqid) + args = getCaptchaTaskStatus_args() + args.tid = tid + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_getCaptchaTaskStatus(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = getCaptchaTaskStatus_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + if result.success is not None: + return result.success + raise TApplicationException(TApplicationException.MISSING_RESULT, "getCaptchaTaskStatus failed: unknown result"); + + + def setCaptchaResult(self, tid, result): + """ + Parameters: + - tid + - result + """ + self.send_setCaptchaResult(tid, result) + self.recv_setCaptchaResult() + + + def send_setCaptchaResult(self, tid, result): + self._oprot.writeMessageBegin('setCaptchaResult', TMessageType.CALL, self._seqid) + args = setCaptchaResult_args() + args.tid = tid + args.result = result + args.write(self._oprot) + self._oprot.writeMessageEnd() + self._oprot.trans.flush() + + + def recv_setCaptchaResult(self,): + (fname, mtype, rseqid) = self._iprot.readMessageBegin() + if mtype == TMessageType.EXCEPTION: + x = TApplicationException() + x.read(self._iprot) + self._iprot.readMessageEnd() + raise x + result = setCaptchaResult_result() + result.read(self._iprot) + self._iprot.readMessageEnd() + return + + +class Processor(Iface, TProcessor): + + def __init__(self, handler): + self._handler = handler + self._processMap = {} + self._processMap['getConfigValue'] = Processor.process_getConfigValue + self._processMap['setConfigValue'] = Processor.process_setConfigValue + self._processMap['getConfig'] = Processor.process_getConfig + self._processMap['getPluginConfig'] = Processor.process_getPluginConfig + self._processMap['pauseServer'] = Processor.process_pauseServer + self._processMap['unpauseServer'] = Processor.process_unpauseServer + self._processMap['togglePause'] = Processor.process_togglePause + self._processMap['statusServer'] = Processor.process_statusServer + self._processMap['freeSpace'] = Processor.process_freeSpace + self._processMap['getServerVersion'] = Processor.process_getServerVersion + self._processMap['kill'] = Processor.process_kill + self._processMap['restart'] = Processor.process_restart + self._processMap['getLog'] = Processor.process_getLog + self._processMap['isTimeDownload'] = Processor.process_isTimeDownload + self._processMap['isTimeReconnect'] = Processor.process_isTimeReconnect + self._processMap['toggleReconnect'] = Processor.process_toggleReconnect + self._processMap['generatePackages'] = Processor.process_generatePackages + self._processMap['checkURLs'] = Processor.process_checkURLs + self._processMap['parseURLs'] = Processor.process_parseURLs + self._processMap['checkOnlineStatus'] = Processor.process_checkOnlineStatus + self._processMap['checkOnlineStatusContainer'] = Processor.process_checkOnlineStatusContainer + self._processMap['pollResults'] = Processor.process_pollResults + self._processMap['statusDownloads'] = Processor.process_statusDownloads + self._processMap['getPackageData'] = Processor.process_getPackageData + self._processMap['getPackageInfo'] = Processor.process_getPackageInfo + self._processMap['getFileData'] = Processor.process_getFileData + self._processMap['getQueue'] = Processor.process_getQueue + self._processMap['getCollector'] = Processor.process_getCollector + self._processMap['getQueueData'] = Processor.process_getQueueData + self._processMap['getCollectorData'] = Processor.process_getCollectorData + self._processMap['getPackageOrder'] = Processor.process_getPackageOrder + self._processMap['getFileOrder'] = Processor.process_getFileOrder + self._processMap['generateAndAddPackages'] = Processor.process_generateAndAddPackages + self._processMap['addPackage'] = Processor.process_addPackage + self._processMap['addFiles'] = Processor.process_addFiles + self._processMap['uploadContainer'] = Processor.process_uploadContainer + self._processMap['deleteFiles'] = Processor.process_deleteFiles + self._processMap['deletePackages'] = Processor.process_deletePackages + self._processMap['pushToQueue'] = Processor.process_pushToQueue + self._processMap['pullFromQueue'] = Processor.process_pullFromQueue + self._processMap['restartPackage'] = Processor.process_restartPackage + self._processMap['restartFile'] = Processor.process_restartFile + self._processMap['recheckPackage'] = Processor.process_recheckPackage + self._processMap['stopAllDownloads'] = Processor.process_stopAllDownloads + self._processMap['stopDownloads'] = Processor.process_stopDownloads + self._processMap['setPackageName'] = Processor.process_setPackageName + self._processMap['movePackage'] = Processor.process_movePackage + self._processMap['moveFiles'] = Processor.process_moveFiles + self._processMap['orderPackage'] = Processor.process_orderPackage + self._processMap['orderFile'] = Processor.process_orderFile + self._processMap['setPackageData'] = Processor.process_setPackageData + self._processMap['deleteFinished'] = Processor.process_deleteFinished + self._processMap['restartFailed'] = Processor.process_restartFailed + self._processMap['getEvents'] = Processor.process_getEvents + self._processMap['getAccounts'] = Processor.process_getAccounts + self._processMap['getAccountTypes'] = Processor.process_getAccountTypes + self._processMap['updateAccount'] = Processor.process_updateAccount + self._processMap['removeAccount'] = Processor.process_removeAccount + self._processMap['login'] = Processor.process_login + self._processMap['getUserData'] = Processor.process_getUserData + self._processMap['getAllUserData'] = Processor.process_getAllUserData + self._processMap['getServices'] = Processor.process_getServices + self._processMap['hasService'] = Processor.process_hasService + self._processMap['call'] = Processor.process_call + self._processMap['getAllInfo'] = Processor.process_getAllInfo + self._processMap['getInfoByPlugin'] = Processor.process_getInfoByPlugin + self._processMap['isCaptchaWaiting'] = Processor.process_isCaptchaWaiting + self._processMap['getCaptchaTask'] = Processor.process_getCaptchaTask + self._processMap['getCaptchaTaskStatus'] = Processor.process_getCaptchaTaskStatus + self._processMap['setCaptchaResult'] = Processor.process_setCaptchaResult + + + def process(self, iprot, oprot): + (name, type, seqid) = iprot.readMessageBegin() + if name not in self._processMap: + iprot.skip(TType.STRUCT) + iprot.readMessageEnd() + x = TApplicationException(TApplicationException.UNKNOWN_METHOD, 'Unknown function %s' % (name)) + oprot.writeMessageBegin(name, TMessageType.EXCEPTION, seqid) + x.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + return + else: + self._processMap[name](self, seqid, iprot, oprot) + return True + + + def process_getConfigValue(self, seqid, iprot, oprot): + args = getConfigValue_args() + args.read(iprot) + iprot.readMessageEnd() + result = getConfigValue_result() + result.success = self._handler.getConfigValue(args.category, args.option, args.section) + oprot.writeMessageBegin("getConfigValue", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_setConfigValue(self, seqid, iprot, oprot): + args = setConfigValue_args() + args.read(iprot) + iprot.readMessageEnd() + result = setConfigValue_result() + self._handler.setConfigValue(args.category, args.option, args.value, args.section) + oprot.writeMessageBegin("setConfigValue", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_getConfig(self, seqid, iprot, oprot): + args = getConfig_args() + args.read(iprot) + iprot.readMessageEnd() + result = getConfig_result() + result.success = self._handler.getConfig() + oprot.writeMessageBegin("getConfig", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_getPluginConfig(self, seqid, iprot, oprot): + args = getPluginConfig_args() + args.read(iprot) + iprot.readMessageEnd() + result = getPluginConfig_result() + result.success = self._handler.getPluginConfig() + oprot.writeMessageBegin("getPluginConfig", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_pauseServer(self, seqid, iprot, oprot): + args = pauseServer_args() + args.read(iprot) + iprot.readMessageEnd() + result = pauseServer_result() + self._handler.pauseServer() + oprot.writeMessageBegin("pauseServer", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_unpauseServer(self, seqid, iprot, oprot): + args = unpauseServer_args() + args.read(iprot) + iprot.readMessageEnd() + result = unpauseServer_result() + self._handler.unpauseServer() + oprot.writeMessageBegin("unpauseServer", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_togglePause(self, seqid, iprot, oprot): + args = togglePause_args() + args.read(iprot) + iprot.readMessageEnd() + result = togglePause_result() + result.success = self._handler.togglePause() + oprot.writeMessageBegin("togglePause", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_statusServer(self, seqid, iprot, oprot): + args = statusServer_args() + args.read(iprot) + iprot.readMessageEnd() + result = statusServer_result() + result.success = self._handler.statusServer() + oprot.writeMessageBegin("statusServer", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_freeSpace(self, seqid, iprot, oprot): + args = freeSpace_args() + args.read(iprot) + iprot.readMessageEnd() + result = freeSpace_result() + result.success = self._handler.freeSpace() + oprot.writeMessageBegin("freeSpace", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_getServerVersion(self, seqid, iprot, oprot): + args = getServerVersion_args() + args.read(iprot) + iprot.readMessageEnd() + result = getServerVersion_result() + result.success = self._handler.getServerVersion() + oprot.writeMessageBegin("getServerVersion", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_kill(self, seqid, iprot, oprot): + args = kill_args() + args.read(iprot) + iprot.readMessageEnd() + result = kill_result() + self._handler.kill() + oprot.writeMessageBegin("kill", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_restart(self, seqid, iprot, oprot): + args = restart_args() + args.read(iprot) + iprot.readMessageEnd() + result = restart_result() + self._handler.restart() + oprot.writeMessageBegin("restart", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_getLog(self, seqid, iprot, oprot): + args = getLog_args() + args.read(iprot) + iprot.readMessageEnd() + result = getLog_result() + result.success = self._handler.getLog(args.offset) + oprot.writeMessageBegin("getLog", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_isTimeDownload(self, seqid, iprot, oprot): + args = isTimeDownload_args() + args.read(iprot) + iprot.readMessageEnd() + result = isTimeDownload_result() + result.success = self._handler.isTimeDownload() + oprot.writeMessageBegin("isTimeDownload", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_isTimeReconnect(self, seqid, iprot, oprot): + args = isTimeReconnect_args() + args.read(iprot) + iprot.readMessageEnd() + result = isTimeReconnect_result() + result.success = self._handler.isTimeReconnect() + oprot.writeMessageBegin("isTimeReconnect", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_toggleReconnect(self, seqid, iprot, oprot): + args = toggleReconnect_args() + args.read(iprot) + iprot.readMessageEnd() + result = toggleReconnect_result() + result.success = self._handler.toggleReconnect() + oprot.writeMessageBegin("toggleReconnect", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_generatePackages(self, seqid, iprot, oprot): + args = generatePackages_args() + args.read(iprot) + iprot.readMessageEnd() + result = generatePackages_result() + result.success = self._handler.generatePackages(args.links) + oprot.writeMessageBegin("generatePackages", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_checkURLs(self, seqid, iprot, oprot): + args = checkURLs_args() + args.read(iprot) + iprot.readMessageEnd() + result = checkURLs_result() + result.success = self._handler.checkURLs(args.urls) + oprot.writeMessageBegin("checkURLs", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_parseURLs(self, seqid, iprot, oprot): + args = parseURLs_args() + args.read(iprot) + iprot.readMessageEnd() + result = parseURLs_result() + result.success = self._handler.parseURLs(args.html, args.url) + oprot.writeMessageBegin("parseURLs", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_checkOnlineStatus(self, seqid, iprot, oprot): + args = checkOnlineStatus_args() + args.read(iprot) + iprot.readMessageEnd() + result = checkOnlineStatus_result() + result.success = self._handler.checkOnlineStatus(args.urls) + oprot.writeMessageBegin("checkOnlineStatus", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_checkOnlineStatusContainer(self, seqid, iprot, oprot): + args = checkOnlineStatusContainer_args() + args.read(iprot) + iprot.readMessageEnd() + result = checkOnlineStatusContainer_result() + result.success = self._handler.checkOnlineStatusContainer(args.urls, args.filename, args.data) + oprot.writeMessageBegin("checkOnlineStatusContainer", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_pollResults(self, seqid, iprot, oprot): + args = pollResults_args() + args.read(iprot) + iprot.readMessageEnd() + result = pollResults_result() + result.success = self._handler.pollResults(args.rid) + oprot.writeMessageBegin("pollResults", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_statusDownloads(self, seqid, iprot, oprot): + args = statusDownloads_args() + args.read(iprot) + iprot.readMessageEnd() + result = statusDownloads_result() + result.success = self._handler.statusDownloads() + oprot.writeMessageBegin("statusDownloads", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_getPackageData(self, seqid, iprot, oprot): + args = getPackageData_args() + args.read(iprot) + iprot.readMessageEnd() + result = getPackageData_result() + try: + result.success = self._handler.getPackageData(args.pid) + except PackageDoesNotExists, e: + result.e = e + oprot.writeMessageBegin("getPackageData", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_getPackageInfo(self, seqid, iprot, oprot): + args = getPackageInfo_args() + args.read(iprot) + iprot.readMessageEnd() + result = getPackageInfo_result() + try: + result.success = self._handler.getPackageInfo(args.pid) + except PackageDoesNotExists, e: + result.e = e + oprot.writeMessageBegin("getPackageInfo", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_getFileData(self, seqid, iprot, oprot): + args = getFileData_args() + args.read(iprot) + iprot.readMessageEnd() + result = getFileData_result() + try: + result.success = self._handler.getFileData(args.fid) + except FileDoesNotExists, e: + result.e = e + oprot.writeMessageBegin("getFileData", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_getQueue(self, seqid, iprot, oprot): + args = getQueue_args() + args.read(iprot) + iprot.readMessageEnd() + result = getQueue_result() + result.success = self._handler.getQueue() + oprot.writeMessageBegin("getQueue", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_getCollector(self, seqid, iprot, oprot): + args = getCollector_args() + args.read(iprot) + iprot.readMessageEnd() + result = getCollector_result() + result.success = self._handler.getCollector() + oprot.writeMessageBegin("getCollector", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_getQueueData(self, seqid, iprot, oprot): + args = getQueueData_args() + args.read(iprot) + iprot.readMessageEnd() + result = getQueueData_result() + result.success = self._handler.getQueueData() + oprot.writeMessageBegin("getQueueData", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_getCollectorData(self, seqid, iprot, oprot): + args = getCollectorData_args() + args.read(iprot) + iprot.readMessageEnd() + result = getCollectorData_result() + result.success = self._handler.getCollectorData() + oprot.writeMessageBegin("getCollectorData", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_getPackageOrder(self, seqid, iprot, oprot): + args = getPackageOrder_args() + args.read(iprot) + iprot.readMessageEnd() + result = getPackageOrder_result() + result.success = self._handler.getPackageOrder(args.destination) + oprot.writeMessageBegin("getPackageOrder", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_getFileOrder(self, seqid, iprot, oprot): + args = getFileOrder_args() + args.read(iprot) + iprot.readMessageEnd() + result = getFileOrder_result() + result.success = self._handler.getFileOrder(args.pid) + oprot.writeMessageBegin("getFileOrder", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_generateAndAddPackages(self, seqid, iprot, oprot): + args = generateAndAddPackages_args() + args.read(iprot) + iprot.readMessageEnd() + result = generateAndAddPackages_result() + result.success = self._handler.generateAndAddPackages(args.links, args.dest) + oprot.writeMessageBegin("generateAndAddPackages", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_addPackage(self, seqid, iprot, oprot): + args = addPackage_args() + args.read(iprot) + iprot.readMessageEnd() + result = addPackage_result() + result.success = self._handler.addPackage(args.name, args.links, args.dest) + oprot.writeMessageBegin("addPackage", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_addFiles(self, seqid, iprot, oprot): + args = addFiles_args() + args.read(iprot) + iprot.readMessageEnd() + result = addFiles_result() + self._handler.addFiles(args.pid, args.links) + oprot.writeMessageBegin("addFiles", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_uploadContainer(self, seqid, iprot, oprot): + args = uploadContainer_args() + args.read(iprot) + iprot.readMessageEnd() + result = uploadContainer_result() + self._handler.uploadContainer(args.filename, args.data) + oprot.writeMessageBegin("uploadContainer", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_deleteFiles(self, seqid, iprot, oprot): + args = deleteFiles_args() + args.read(iprot) + iprot.readMessageEnd() + result = deleteFiles_result() + self._handler.deleteFiles(args.fids) + oprot.writeMessageBegin("deleteFiles", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_deletePackages(self, seqid, iprot, oprot): + args = deletePackages_args() + args.read(iprot) + iprot.readMessageEnd() + result = deletePackages_result() + self._handler.deletePackages(args.pids) + oprot.writeMessageBegin("deletePackages", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_pushToQueue(self, seqid, iprot, oprot): + args = pushToQueue_args() + args.read(iprot) + iprot.readMessageEnd() + result = pushToQueue_result() + self._handler.pushToQueue(args.pid) + oprot.writeMessageBegin("pushToQueue", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_pullFromQueue(self, seqid, iprot, oprot): + args = pullFromQueue_args() + args.read(iprot) + iprot.readMessageEnd() + result = pullFromQueue_result() + self._handler.pullFromQueue(args.pid) + oprot.writeMessageBegin("pullFromQueue", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_restartPackage(self, seqid, iprot, oprot): + args = restartPackage_args() + args.read(iprot) + iprot.readMessageEnd() + result = restartPackage_result() + self._handler.restartPackage(args.pid) + oprot.writeMessageBegin("restartPackage", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_restartFile(self, seqid, iprot, oprot): + args = restartFile_args() + args.read(iprot) + iprot.readMessageEnd() + result = restartFile_result() + self._handler.restartFile(args.fid) + oprot.writeMessageBegin("restartFile", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_recheckPackage(self, seqid, iprot, oprot): + args = recheckPackage_args() + args.read(iprot) + iprot.readMessageEnd() + result = recheckPackage_result() + self._handler.recheckPackage(args.pid) + oprot.writeMessageBegin("recheckPackage", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_stopAllDownloads(self, seqid, iprot, oprot): + args = stopAllDownloads_args() + args.read(iprot) + iprot.readMessageEnd() + result = stopAllDownloads_result() + self._handler.stopAllDownloads() + oprot.writeMessageBegin("stopAllDownloads", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_stopDownloads(self, seqid, iprot, oprot): + args = stopDownloads_args() + args.read(iprot) + iprot.readMessageEnd() + result = stopDownloads_result() + self._handler.stopDownloads(args.fids) + oprot.writeMessageBegin("stopDownloads", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_setPackageName(self, seqid, iprot, oprot): + args = setPackageName_args() + args.read(iprot) + iprot.readMessageEnd() + result = setPackageName_result() + self._handler.setPackageName(args.pid, args.name) + oprot.writeMessageBegin("setPackageName", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_movePackage(self, seqid, iprot, oprot): + args = movePackage_args() + args.read(iprot) + iprot.readMessageEnd() + result = movePackage_result() + self._handler.movePackage(args.destination, args.pid) + oprot.writeMessageBegin("movePackage", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_moveFiles(self, seqid, iprot, oprot): + args = moveFiles_args() + args.read(iprot) + iprot.readMessageEnd() + result = moveFiles_result() + self._handler.moveFiles(args.fids, args.pid) + oprot.writeMessageBegin("moveFiles", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_orderPackage(self, seqid, iprot, oprot): + args = orderPackage_args() + args.read(iprot) + iprot.readMessageEnd() + result = orderPackage_result() + self._handler.orderPackage(args.pid, args.position) + oprot.writeMessageBegin("orderPackage", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_orderFile(self, seqid, iprot, oprot): + args = orderFile_args() + args.read(iprot) + iprot.readMessageEnd() + result = orderFile_result() + self._handler.orderFile(args.fid, args.position) + oprot.writeMessageBegin("orderFile", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_setPackageData(self, seqid, iprot, oprot): + args = setPackageData_args() + args.read(iprot) + iprot.readMessageEnd() + result = setPackageData_result() + try: + self._handler.setPackageData(args.pid, args.data) + except PackageDoesNotExists, e: + result.e = e + oprot.writeMessageBegin("setPackageData", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_deleteFinished(self, seqid, iprot, oprot): + args = deleteFinished_args() + args.read(iprot) + iprot.readMessageEnd() + result = deleteFinished_result() + result.success = self._handler.deleteFinished() + oprot.writeMessageBegin("deleteFinished", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_restartFailed(self, seqid, iprot, oprot): + args = restartFailed_args() + args.read(iprot) + iprot.readMessageEnd() + result = restartFailed_result() + self._handler.restartFailed() + oprot.writeMessageBegin("restartFailed", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_getEvents(self, seqid, iprot, oprot): + args = getEvents_args() + args.read(iprot) + iprot.readMessageEnd() + result = getEvents_result() + result.success = self._handler.getEvents(args.uuid) + oprot.writeMessageBegin("getEvents", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_getAccounts(self, seqid, iprot, oprot): + args = getAccounts_args() + args.read(iprot) + iprot.readMessageEnd() + result = getAccounts_result() + result.success = self._handler.getAccounts(args.refresh) + oprot.writeMessageBegin("getAccounts", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_getAccountTypes(self, seqid, iprot, oprot): + args = getAccountTypes_args() + args.read(iprot) + iprot.readMessageEnd() + result = getAccountTypes_result() + result.success = self._handler.getAccountTypes() + oprot.writeMessageBegin("getAccountTypes", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_updateAccount(self, seqid, iprot, oprot): + args = updateAccount_args() + args.read(iprot) + iprot.readMessageEnd() + result = updateAccount_result() + self._handler.updateAccount(args.plugin, args.account, args.password, args.options) + oprot.writeMessageBegin("updateAccount", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_removeAccount(self, seqid, iprot, oprot): + args = removeAccount_args() + args.read(iprot) + iprot.readMessageEnd() + result = removeAccount_result() + self._handler.removeAccount(args.plugin, args.account) + oprot.writeMessageBegin("removeAccount", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_login(self, seqid, iprot, oprot): + args = login_args() + args.read(iprot) + iprot.readMessageEnd() + result = login_result() + result.success = self._handler.login(args.username, args.password) + oprot.writeMessageBegin("login", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_getUserData(self, seqid, iprot, oprot): + args = getUserData_args() + args.read(iprot) + iprot.readMessageEnd() + result = getUserData_result() + result.success = self._handler.getUserData(args.username, args.password) + oprot.writeMessageBegin("getUserData", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_getAllUserData(self, seqid, iprot, oprot): + args = getAllUserData_args() + args.read(iprot) + iprot.readMessageEnd() + result = getAllUserData_result() + result.success = self._handler.getAllUserData() + oprot.writeMessageBegin("getAllUserData", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_getServices(self, seqid, iprot, oprot): + args = getServices_args() + args.read(iprot) + iprot.readMessageEnd() + result = getServices_result() + result.success = self._handler.getServices() + oprot.writeMessageBegin("getServices", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_hasService(self, seqid, iprot, oprot): + args = hasService_args() + args.read(iprot) + iprot.readMessageEnd() + result = hasService_result() + result.success = self._handler.hasService(args.plugin, args.func) + oprot.writeMessageBegin("hasService", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_call(self, seqid, iprot, oprot): + args = call_args() + args.read(iprot) + iprot.readMessageEnd() + result = call_result() + try: + result.success = self._handler.call(args.info) + except ServiceDoesNotExists, ex: + result.ex = ex + except ServiceException, e: + result.e = e + oprot.writeMessageBegin("call", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_getAllInfo(self, seqid, iprot, oprot): + args = getAllInfo_args() + args.read(iprot) + iprot.readMessageEnd() + result = getAllInfo_result() + result.success = self._handler.getAllInfo() + oprot.writeMessageBegin("getAllInfo", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_getInfoByPlugin(self, seqid, iprot, oprot): + args = getInfoByPlugin_args() + args.read(iprot) + iprot.readMessageEnd() + result = getInfoByPlugin_result() + result.success = self._handler.getInfoByPlugin(args.plugin) + oprot.writeMessageBegin("getInfoByPlugin", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_isCaptchaWaiting(self, seqid, iprot, oprot): + args = isCaptchaWaiting_args() + args.read(iprot) + iprot.readMessageEnd() + result = isCaptchaWaiting_result() + result.success = self._handler.isCaptchaWaiting() + oprot.writeMessageBegin("isCaptchaWaiting", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_getCaptchaTask(self, seqid, iprot, oprot): + args = getCaptchaTask_args() + args.read(iprot) + iprot.readMessageEnd() + result = getCaptchaTask_result() + result.success = self._handler.getCaptchaTask(args.exclusive) + oprot.writeMessageBegin("getCaptchaTask", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_getCaptchaTaskStatus(self, seqid, iprot, oprot): + args = getCaptchaTaskStatus_args() + args.read(iprot) + iprot.readMessageEnd() + result = getCaptchaTaskStatus_result() + result.success = self._handler.getCaptchaTaskStatus(args.tid) + oprot.writeMessageBegin("getCaptchaTaskStatus", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + + def process_setCaptchaResult(self, seqid, iprot, oprot): + args = setCaptchaResult_args() + args.read(iprot) + iprot.readMessageEnd() + result = setCaptchaResult_result() + self._handler.setCaptchaResult(args.tid, args.result) + oprot.writeMessageBegin("setCaptchaResult", TMessageType.REPLY, seqid) + result.write(oprot) + oprot.writeMessageEnd() + oprot.trans.flush() + + +# HELPER FUNCTIONS AND STRUCTURES + + +class getConfigValue_args(TBase): + """ + Attributes: + - category + - option + - section + """ + + __slots__ = [ + 'category', + 'option', + 'section', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.STRING, 'category', None, None,), #: 1 + (2, TType.STRING, 'option', None, None,), #: 2 + (3, TType.STRING, 'section', None, None,), #: 3 + ) + + + def __init__(self, category=None, option=None, section=None,): + self.category = category + self.option = option + self.section = section + + +class getConfigValue_result(TBase): + """ + Attributes: + - success + """ + + __slots__ = [ + 'success', + ] + + thrift_spec = ( + (0, TType.STRING, 'success', None, None,), #: 0 + ) + + + def __init__(self, success=None,): + self.success = success + + +class setConfigValue_args(TBase): + """ + Attributes: + - category + - option + - value + - section + """ + + __slots__ = [ + 'category', + 'option', + 'value', + 'section', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.STRING, 'category', None, None,), #: 1 + (2, TType.STRING, 'option', None, None,), #: 2 + (3, TType.STRING, 'value', None, None,), #: 3 + (4, TType.STRING, 'section', None, None,), #: 4 + ) + + + def __init__(self, category=None, option=None, value=None, section=None,): + self.category = category + self.option = option + self.value = value + self.section = section + + +class setConfigValue_result(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class getConfig_args(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class getConfig_result(TBase): + """ + Attributes: + - success + """ + + __slots__ = [ + 'success', + ] + + thrift_spec = ( + (0, TType.MAP, 'success', (TType.STRING, None, TType.STRUCT, (ConfigSection, ConfigSection.thrift_spec)), None,), #: 0 + ) + + + def __init__(self, success=None,): + self.success = success + + +class getPluginConfig_args(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class getPluginConfig_result(TBase): + """ + Attributes: + - success + """ + + __slots__ = [ + 'success', + ] + + thrift_spec = ( + (0, TType.MAP, 'success', (TType.STRING, None, TType.STRUCT, (ConfigSection, ConfigSection.thrift_spec)), None,), #: 0 + ) + + + def __init__(self, success=None,): + self.success = success + + +class pauseServer_args(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class pauseServer_result(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class unpauseServer_args(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class unpauseServer_result(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class togglePause_args(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class togglePause_result(TBase): + """ + Attributes: + - success + """ + + __slots__ = [ + 'success', + ] + + thrift_spec = ( + (0, TType.BOOL, 'success', None, None,), #: 0 + ) + + + def __init__(self, success=None,): + self.success = success + + +class statusServer_args(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class statusServer_result(TBase): + """ + Attributes: + - success + """ + + __slots__ = [ + 'success', + ] + + thrift_spec = ( + (0, TType.STRUCT, 'success', (ServerStatus, ServerStatus.thrift_spec), None,), #: 0 + ) + + + def __init__(self, success=None,): + self.success = success + + +class freeSpace_args(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class freeSpace_result(TBase): + """ + Attributes: + - success + """ + + __slots__ = [ + 'success', + ] + + thrift_spec = ( + (0, TType.I64, 'success', None, None,), #: 0 + ) + + + def __init__(self, success=None,): + self.success = success + + +class getServerVersion_args(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class getServerVersion_result(TBase): + """ + Attributes: + - success + """ + + __slots__ = [ + 'success', + ] + + thrift_spec = ( + (0, TType.STRING, 'success', None, None,), #: 0 + ) + + + def __init__(self, success=None,): + self.success = success + + +class kill_args(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class kill_result(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class restart_args(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class restart_result(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class getLog_args(TBase): + """ + Attributes: + - offset + """ + + __slots__ = [ + 'offset', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.I32, 'offset', None, None,), #: 1 + ) + + + def __init__(self, offset=None,): + self.offset = offset + + +class getLog_result(TBase): + """ + Attributes: + - success + """ + + __slots__ = [ + 'success', + ] + + thrift_spec = ( + (0, TType.LIST, 'success', (TType.STRING, None), None,), #: 0 + ) + + + def __init__(self, success=None,): + self.success = success + + +class isTimeDownload_args(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class isTimeDownload_result(TBase): + """ + Attributes: + - success + """ + + __slots__ = [ + 'success', + ] + + thrift_spec = ( + (0, TType.BOOL, 'success', None, None,), #: 0 + ) + + + def __init__(self, success=None,): + self.success = success + + +class isTimeReconnect_args(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class isTimeReconnect_result(TBase): + """ + Attributes: + - success + """ + + __slots__ = [ + 'success', + ] + + thrift_spec = ( + (0, TType.BOOL, 'success', None, None,), #: 0 + ) + + + def __init__(self, success=None,): + self.success = success + + +class toggleReconnect_args(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class toggleReconnect_result(TBase): + """ + Attributes: + - success + """ + + __slots__ = [ + 'success', + ] + + thrift_spec = ( + (0, TType.BOOL, 'success', None, None,), #: 0 + ) + + + def __init__(self, success=None,): + self.success = success + + +class generatePackages_args(TBase): + """ + Attributes: + - links + """ + + __slots__ = [ + 'links', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.LIST, 'links', (TType.STRING, None), None,), #: 1 + ) + + + def __init__(self, links=None,): + self.links = links + + +class generatePackages_result(TBase): + """ + Attributes: + - success + """ + + __slots__ = [ + 'success', + ] + + thrift_spec = ( + (0, TType.MAP, 'success', (TType.STRING, None, TType.LIST, (TType.STRING, None)), None,), #: 0 + ) + + + def __init__(self, success=None,): + self.success = success + + +class checkURLs_args(TBase): + """ + Attributes: + - urls + """ + + __slots__ = [ + 'urls', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.LIST, 'urls', (TType.STRING, None), None,), #: 1 + ) + + + def __init__(self, urls=None,): + self.urls = urls + + +class checkURLs_result(TBase): + """ + Attributes: + - success + """ + + __slots__ = [ + 'success', + ] + + thrift_spec = ( + (0, TType.MAP, 'success', (TType.STRING, None, TType.LIST, (TType.STRING, None)), None,), #: 0 + ) + + + def __init__(self, success=None,): + self.success = success + + +class parseURLs_args(TBase): + """ + Attributes: + - html + - url + """ + + __slots__ = [ + 'html', + 'url', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.STRING, 'html', None, None,), #: 1 + (2, TType.STRING, 'url', None, None,), #: 2 + ) + + + def __init__(self, html=None, url=None,): + self.html = html + self.url = url + + +class parseURLs_result(TBase): + """ + Attributes: + - success + """ + + __slots__ = [ + 'success', + ] + + thrift_spec = ( + (0, TType.MAP, 'success', (TType.STRING, None, TType.LIST, (TType.STRING, None)), None,), #: 0 + ) + + + def __init__(self, success=None,): + self.success = success + + +class checkOnlineStatus_args(TBase): + """ + Attributes: + - urls + """ + + __slots__ = [ + 'urls', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.LIST, 'urls', (TType.STRING, None), None,), #: 1 + ) + + + def __init__(self, urls=None,): + self.urls = urls + + +class checkOnlineStatus_result(TBase): + """ + Attributes: + - success + """ + + __slots__ = [ + 'success', + ] + + thrift_spec = ( + (0, TType.STRUCT, 'success', (OnlineCheck, OnlineCheck.thrift_spec), None,), #: 0 + ) + + + def __init__(self, success=None,): + self.success = success + + +class checkOnlineStatusContainer_args(TBase): + """ + Attributes: + - urls + - filename + - data + """ + + __slots__ = [ + 'urls', + 'filename', + 'data', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.LIST, 'urls', (TType.STRING, None), None,), #: 1 + (2, TType.STRING, 'filename', None, None,), #: 2 + (3, TType.STRING, 'data', None, None,), #: 3 + ) + + + def __init__(self, urls=None, filename=None, data=None,): + self.urls = urls + self.filename = filename + self.data = data + + +class checkOnlineStatusContainer_result(TBase): + """ + Attributes: + - success + """ + + __slots__ = [ + 'success', + ] + + thrift_spec = ( + (0, TType.STRUCT, 'success', (OnlineCheck, OnlineCheck.thrift_spec), None,), #: 0 + ) + + + def __init__(self, success=None,): + self.success = success + + +class pollResults_args(TBase): + """ + Attributes: + - rid + """ + + __slots__ = [ + 'rid', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.I32, 'rid', None, None,), #: 1 + ) + + + def __init__(self, rid=None,): + self.rid = rid + + +class pollResults_result(TBase): + """ + Attributes: + - success + """ + + __slots__ = [ + 'success', + ] + + thrift_spec = ( + (0, TType.STRUCT, 'success', (OnlineCheck, OnlineCheck.thrift_spec), None,), #: 0 + ) + + + def __init__(self, success=None,): + self.success = success + + +class statusDownloads_args(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class statusDownloads_result(TBase): + """ + Attributes: + - success + """ + + __slots__ = [ + 'success', + ] + + thrift_spec = ( + (0, TType.LIST, 'success', (TType.STRUCT, (DownloadInfo, DownloadInfo.thrift_spec)), None,), #: 0 + ) + + + def __init__(self, success=None,): + self.success = success + + +class getPackageData_args(TBase): + """ + Attributes: + - pid + """ + + __slots__ = [ + 'pid', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.I32, 'pid', None, None,), #: 1 + ) + + + def __init__(self, pid=None,): + self.pid = pid + + +class getPackageData_result(TBase): + """ + Attributes: + - success + - e + """ + + __slots__ = [ + 'success', + 'e', + ] + + thrift_spec = ( + (0, TType.STRUCT, 'success', (PackageData, PackageData.thrift_spec), None,), #: 0 + (1, TType.STRUCT, 'e', (PackageDoesNotExists, PackageDoesNotExists.thrift_spec), None,), #: 1 + ) + + + def __init__(self, success=None, e=None,): + self.success = success + self.e = e + + +class getPackageInfo_args(TBase): + """ + Attributes: + - pid + """ + + __slots__ = [ + 'pid', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.I32, 'pid', None, None,), #: 1 + ) + + + def __init__(self, pid=None,): + self.pid = pid + + +class getPackageInfo_result(TBase): + """ + Attributes: + - success + - e + """ + + __slots__ = [ + 'success', + 'e', + ] + + thrift_spec = ( + (0, TType.STRUCT, 'success', (PackageData, PackageData.thrift_spec), None,), #: 0 + (1, TType.STRUCT, 'e', (PackageDoesNotExists, PackageDoesNotExists.thrift_spec), None,), #: 1 + ) + + + def __init__(self, success=None, e=None,): + self.success = success + self.e = e + + +class getFileData_args(TBase): + """ + Attributes: + - fid + """ + + __slots__ = [ + 'fid', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.I32, 'fid', None, None,), #: 1 + ) + + + def __init__(self, fid=None,): + self.fid = fid + + +class getFileData_result(TBase): + """ + Attributes: + - success + - e + """ + + __slots__ = [ + 'success', + 'e', + ] + + thrift_spec = ( + (0, TType.STRUCT, 'success', (FileData, FileData.thrift_spec), None,), #: 0 + (1, TType.STRUCT, 'e', (FileDoesNotExists, FileDoesNotExists.thrift_spec), None,), #: 1 + ) + + + def __init__(self, success=None, e=None,): + self.success = success + self.e = e + + +class getQueue_args(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class getQueue_result(TBase): + """ + Attributes: + - success + """ + + __slots__ = [ + 'success', + ] + + thrift_spec = ( + (0, TType.LIST, 'success', (TType.STRUCT, (PackageData, PackageData.thrift_spec)), None,), #: 0 + ) + + + def __init__(self, success=None,): + self.success = success + + +class getCollector_args(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class getCollector_result(TBase): + """ + Attributes: + - success + """ + + __slots__ = [ + 'success', + ] + + thrift_spec = ( + (0, TType.LIST, 'success', (TType.STRUCT, (PackageData, PackageData.thrift_spec)), None,), #: 0 + ) + + + def __init__(self, success=None,): + self.success = success + + +class getQueueData_args(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class getQueueData_result(TBase): + """ + Attributes: + - success + """ + + __slots__ = [ + 'success', + ] + + thrift_spec = ( + (0, TType.LIST, 'success', (TType.STRUCT, (PackageData, PackageData.thrift_spec)), None,), #: 0 + ) + + + def __init__(self, success=None,): + self.success = success + + +class getCollectorData_args(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class getCollectorData_result(TBase): + """ + Attributes: + - success + """ + + __slots__ = [ + 'success', + ] + + thrift_spec = ( + (0, TType.LIST, 'success', (TType.STRUCT, (PackageData, PackageData.thrift_spec)), None,), #: 0 + ) + + + def __init__(self, success=None,): + self.success = success + + +class getPackageOrder_args(TBase): + """ + Attributes: + - destination + """ + + __slots__ = [ + 'destination', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.I32, 'destination', None, None,), #: 1 + ) + + + def __init__(self, destination=None,): + self.destination = destination + + +class getPackageOrder_result(TBase): + """ + Attributes: + - success + """ + + __slots__ = [ + 'success', + ] + + thrift_spec = ( + (0, TType.MAP, 'success', (TType.I16, None, TType.I32, None), None,), #: 0 + ) + + + def __init__(self, success=None,): + self.success = success + + +class getFileOrder_args(TBase): + """ + Attributes: + - pid + """ + + __slots__ = [ + 'pid', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.I32, 'pid', None, None,), #: 1 + ) + + + def __init__(self, pid=None,): + self.pid = pid + + +class getFileOrder_result(TBase): + """ + Attributes: + - success + """ + + __slots__ = [ + 'success', + ] + + thrift_spec = ( + (0, TType.MAP, 'success', (TType.I16, None, TType.I32, None), None,), #: 0 + ) + + + def __init__(self, success=None,): + self.success = success + + +class generateAndAddPackages_args(TBase): + """ + Attributes: + - links + - dest + """ + + __slots__ = [ + 'links', + 'dest', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.LIST, 'links', (TType.STRING, None), None,), #: 1 + (2, TType.I32, 'dest', None, None,), #: 2 + ) + + + def __init__(self, links=None, dest=None,): + self.links = links + self.dest = dest + + +class generateAndAddPackages_result(TBase): + """ + Attributes: + - success + """ + + __slots__ = [ + 'success', + ] + + thrift_spec = ( + (0, TType.LIST, 'success', (TType.I32, None), None,), #: 0 + ) + + + def __init__(self, success=None,): + self.success = success + + +class addPackage_args(TBase): + """ + Attributes: + - name + - links + - dest + """ + + __slots__ = [ + 'name', + 'links', + 'dest', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.STRING, 'name', None, None,), #: 1 + (2, TType.LIST, 'links', (TType.STRING, None), None,), #: 2 + (3, TType.I32, 'dest', None, None,), #: 3 + ) + + + def __init__(self, name=None, links=None, dest=None,): + self.name = name + self.links = links + self.dest = dest + + +class addPackage_result(TBase): + """ + Attributes: + - success + """ + + __slots__ = [ + 'success', + ] + + thrift_spec = ( + (0, TType.I32, 'success', None, None,), #: 0 + ) + + + def __init__(self, success=None,): + self.success = success + + +class addFiles_args(TBase): + """ + Attributes: + - pid + - links + """ + + __slots__ = [ + 'pid', + 'links', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.I32, 'pid', None, None,), #: 1 + (2, TType.LIST, 'links', (TType.STRING, None), None,), #: 2 + ) + + + def __init__(self, pid=None, links=None,): + self.pid = pid + self.links = links + + +class addFiles_result(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class uploadContainer_args(TBase): + """ + Attributes: + - filename + - data + """ + + __slots__ = [ + 'filename', + 'data', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.STRING, 'filename', None, None,), #: 1 + (2, TType.STRING, 'data', None, None,), #: 2 + ) + + + def __init__(self, filename=None, data=None,): + self.filename = filename + self.data = data + + +class uploadContainer_result(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class deleteFiles_args(TBase): + """ + Attributes: + - fids + """ + + __slots__ = [ + 'fids', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.LIST, 'fids', (TType.I32, None), None,), #: 1 + ) + + + def __init__(self, fids=None,): + self.fids = fids + + +class deleteFiles_result(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class deletePackages_args(TBase): + """ + Attributes: + - pids + """ + + __slots__ = [ + 'pids', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.LIST, 'pids', (TType.I32, None), None,), #: 1 + ) + + + def __init__(self, pids=None,): + self.pids = pids + + +class deletePackages_result(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class pushToQueue_args(TBase): + """ + Attributes: + - pid + """ + + __slots__ = [ + 'pid', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.I32, 'pid', None, None,), #: 1 + ) + + + def __init__(self, pid=None,): + self.pid = pid + + +class pushToQueue_result(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class pullFromQueue_args(TBase): + """ + Attributes: + - pid + """ + + __slots__ = [ + 'pid', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.I32, 'pid', None, None,), #: 1 + ) + + + def __init__(self, pid=None,): + self.pid = pid + + +class pullFromQueue_result(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class restartPackage_args(TBase): + """ + Attributes: + - pid + """ + + __slots__ = [ + 'pid', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.I32, 'pid', None, None,), #: 1 + ) + + + def __init__(self, pid=None,): + self.pid = pid + + +class restartPackage_result(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class restartFile_args(TBase): + """ + Attributes: + - fid + """ + + __slots__ = [ + 'fid', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.I32, 'fid', None, None,), #: 1 + ) + + + def __init__(self, fid=None,): + self.fid = fid + + +class restartFile_result(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class recheckPackage_args(TBase): + """ + Attributes: + - pid + """ + + __slots__ = [ + 'pid', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.I32, 'pid', None, None,), #: 1 + ) + + + def __init__(self, pid=None,): + self.pid = pid + + +class recheckPackage_result(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class stopAllDownloads_args(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class stopAllDownloads_result(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class stopDownloads_args(TBase): + """ + Attributes: + - fids + """ + + __slots__ = [ + 'fids', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.LIST, 'fids', (TType.I32, None), None,), #: 1 + ) + + + def __init__(self, fids=None,): + self.fids = fids + + +class stopDownloads_result(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class setPackageName_args(TBase): + """ + Attributes: + - pid + - name + """ + + __slots__ = [ + 'pid', + 'name', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.I32, 'pid', None, None,), #: 1 + (2, TType.STRING, 'name', None, None,), #: 2 + ) + + + def __init__(self, pid=None, name=None,): + self.pid = pid + self.name = name + + +class setPackageName_result(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class movePackage_args(TBase): + """ + Attributes: + - destination + - pid + """ + + __slots__ = [ + 'destination', + 'pid', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.I32, 'destination', None, None,), #: 1 + (2, TType.I32, 'pid', None, None,), #: 2 + ) + + + def __init__(self, destination=None, pid=None,): + self.destination = destination + self.pid = pid + + +class movePackage_result(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class moveFiles_args(TBase): + """ + Attributes: + - fids + - pid + """ + + __slots__ = [ + 'fids', + 'pid', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.LIST, 'fids', (TType.I32, None), None,), #: 1 + (2, TType.I32, 'pid', None, None,), #: 2 + ) + + + def __init__(self, fids=None, pid=None,): + self.fids = fids + self.pid = pid + + +class moveFiles_result(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class orderPackage_args(TBase): + """ + Attributes: + - pid + - position + """ + + __slots__ = [ + 'pid', + 'position', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.I32, 'pid', None, None,), #: 1 + (2, TType.I16, 'position', None, None,), #: 2 + ) + + + def __init__(self, pid=None, position=None,): + self.pid = pid + self.position = position + + +class orderPackage_result(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class orderFile_args(TBase): + """ + Attributes: + - fid + - position + """ + + __slots__ = [ + 'fid', + 'position', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.I32, 'fid', None, None,), #: 1 + (2, TType.I16, 'position', None, None,), #: 2 + ) + + + def __init__(self, fid=None, position=None,): + self.fid = fid + self.position = position + + +class orderFile_result(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class setPackageData_args(TBase): + """ + Attributes: + - pid + - data + """ + + __slots__ = [ + 'pid', + 'data', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.I32, 'pid', None, None,), #: 1 + (2, TType.MAP, 'data', (TType.STRING, None, TType.STRING, None), None,), #: 2 + ) + + + def __init__(self, pid=None, data=None,): + self.pid = pid + self.data = data + + +class setPackageData_result(TBase): + """ + Attributes: + - e + """ + + __slots__ = [ + 'e', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.STRUCT, 'e', (PackageDoesNotExists, PackageDoesNotExists.thrift_spec), None,), #: 1 + ) + + + def __init__(self, e=None,): + self.e = e + + +class deleteFinished_args(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class deleteFinished_result(TBase): + """ + Attributes: + - success + """ + + __slots__ = [ + 'success', + ] + + thrift_spec = ( + (0, TType.LIST, 'success', (TType.I32, None), None,), #: 0 + ) + + + def __init__(self, success=None,): + self.success = success + + +class restartFailed_args(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class restartFailed_result(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class getEvents_args(TBase): + """ + Attributes: + - uuid + """ + + __slots__ = [ + 'uuid', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.STRING, 'uuid', None, None,), #: 1 + ) + + + def __init__(self, uuid=None,): + self.uuid = uuid + + +class getEvents_result(TBase): + """ + Attributes: + - success + """ + + __slots__ = [ + 'success', + ] + + thrift_spec = ( + (0, TType.LIST, 'success', (TType.STRUCT, (EventInfo, EventInfo.thrift_spec)), None,), #: 0 + ) + + + def __init__(self, success=None,): + self.success = success + + +class getAccounts_args(TBase): + """ + Attributes: + - refresh + """ + + __slots__ = [ + 'refresh', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.BOOL, 'refresh', None, None,), #: 1 + ) + + + def __init__(self, refresh=None,): + self.refresh = refresh + + +class getAccounts_result(TBase): + """ + Attributes: + - success + """ + + __slots__ = [ + 'success', + ] + + thrift_spec = ( + (0, TType.LIST, 'success', (TType.STRUCT, (AccountInfo, AccountInfo.thrift_spec)), None,), #: 0 + ) + + + def __init__(self, success=None,): + self.success = success + + +class getAccountTypes_args(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class getAccountTypes_result(TBase): + """ + Attributes: + - success + """ + + __slots__ = [ + 'success', + ] + + thrift_spec = ( + (0, TType.LIST, 'success', (TType.STRING, None), None,), #: 0 + ) + + + def __init__(self, success=None,): + self.success = success + + +class updateAccount_args(TBase): + """ + Attributes: + - plugin + - account + - password + - options + """ + + __slots__ = [ + 'plugin', + 'account', + 'password', + 'options', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.STRING, 'plugin', None, None,), #: 1 + (2, TType.STRING, 'account', None, None,), #: 2 + (3, TType.STRING, 'password', None, None,), #: 3 + (4, TType.MAP, 'options', (TType.STRING, None, TType.STRING, None), None,), #: 4 + ) + + + def __init__(self, plugin=None, account=None, password=None, options=None,): + self.plugin = plugin + self.account = account + self.password = password + self.options = options + + +class updateAccount_result(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class removeAccount_args(TBase): + """ + Attributes: + - plugin + - account + """ + + __slots__ = [ + 'plugin', + 'account', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.STRING, 'plugin', None, None,), #: 1 + (2, TType.STRING, 'account', None, None,), #: 2 + ) + + + def __init__(self, plugin=None, account=None,): + self.plugin = plugin + self.account = account + + +class removeAccount_result(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class login_args(TBase): + """ + Attributes: + - username + - password + """ + + __slots__ = [ + 'username', + 'password', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.STRING, 'username', None, None,), #: 1 + (2, TType.STRING, 'password', None, None,), #: 2 + ) + + + def __init__(self, username=None, password=None,): + self.username = username + self.password = password + + +class login_result(TBase): + """ + Attributes: + - success + """ + + __slots__ = [ + 'success', + ] + + thrift_spec = ( + (0, TType.BOOL, 'success', None, None,), #: 0 + ) + + + def __init__(self, success=None,): + self.success = success + + +class getUserData_args(TBase): + """ + Attributes: + - username + - password + """ + + __slots__ = [ + 'username', + 'password', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.STRING, 'username', None, None,), #: 1 + (2, TType.STRING, 'password', None, None,), #: 2 + ) + + + def __init__(self, username=None, password=None,): + self.username = username + self.password = password + + +class getUserData_result(TBase): + """ + Attributes: + - success + """ + + __slots__ = [ + 'success', + ] + + thrift_spec = ( + (0, TType.STRUCT, 'success', (UserData, UserData.thrift_spec), None,), #: 0 + ) + + + def __init__(self, success=None,): + self.success = success + + +class getAllUserData_args(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class getAllUserData_result(TBase): + """ + Attributes: + - success + """ + + __slots__ = [ + 'success', + ] + + thrift_spec = ( + (0, TType.MAP, 'success', (TType.STRING, None, TType.STRUCT, (UserData, UserData.thrift_spec)), None,), #: 0 + ) + + + def __init__(self, success=None,): + self.success = success + + +class getServices_args(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class getServices_result(TBase): + """ + Attributes: + - success + """ + + __slots__ = [ + 'success', + ] + + thrift_spec = ( + (0, TType.MAP, 'success', (TType.STRING, None, TType.MAP, (TType.STRING, None, TType.STRING, None)), None,), #: 0 + ) + + + def __init__(self, success=None,): + self.success = success + + +class hasService_args(TBase): + """ + Attributes: + - plugin + - func + """ + + __slots__ = [ + 'plugin', + 'func', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.STRING, 'plugin', None, None,), #: 1 + (2, TType.STRING, 'func', None, None,), #: 2 + ) + + + def __init__(self, plugin=None, func=None,): + self.plugin = plugin + self.func = func + + +class hasService_result(TBase): + """ + Attributes: + - success + """ + + __slots__ = [ + 'success', + ] + + thrift_spec = ( + (0, TType.BOOL, 'success', None, None,), #: 0 + ) + + + def __init__(self, success=None,): + self.success = success + + +class call_args(TBase): + """ + Attributes: + - info + """ + + __slots__ = [ + 'info', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.STRUCT, 'info', (ServiceCall, ServiceCall.thrift_spec), None,), #: 1 + ) + + + def __init__(self, info=None,): + self.info = info + + +class call_result(TBase): + """ + Attributes: + - success + - ex + - e + """ + + __slots__ = [ + 'success', + 'ex', + 'e', + ] + + thrift_spec = ( + (0, TType.STRING, 'success', None, None,), #: 0 + (1, TType.STRUCT, 'ex', (ServiceDoesNotExists, ServiceDoesNotExists.thrift_spec), None,), #: 1 + (2, TType.STRUCT, 'e', (ServiceException, ServiceException.thrift_spec), None,), #: 2 + ) + + + def __init__(self, success=None, ex=None, e=None,): + self.success = success + self.ex = ex + self.e = e + + +class getAllInfo_args(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class getAllInfo_result(TBase): + """ + Attributes: + - success + """ + + __slots__ = [ + 'success', + ] + + thrift_spec = ( + (0, TType.MAP, 'success', (TType.STRING, None, TType.MAP, (TType.STRING, None, TType.STRING, None)), None,), #: 0 + ) + + + def __init__(self, success=None,): + self.success = success + + +class getInfoByPlugin_args(TBase): + """ + Attributes: + - plugin + """ + + __slots__ = [ + 'plugin', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.STRING, 'plugin', None, None,), #: 1 + ) + + + def __init__(self, plugin=None,): + self.plugin = plugin + + +class getInfoByPlugin_result(TBase): + """ + Attributes: + - success + """ + + __slots__ = [ + 'success', + ] + + thrift_spec = ( + (0, TType.MAP, 'success', (TType.STRING, None, TType.STRING, None), None,), #: 0 + ) + + + def __init__(self, success=None,): + self.success = success + + +class isCaptchaWaiting_args(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) + + +class isCaptchaWaiting_result(TBase): + """ + Attributes: + - success + """ + + __slots__ = [ + 'success', + ] + + thrift_spec = ( + (0, TType.BOOL, 'success', None, None,), #: 0 + ) + + + def __init__(self, success=None,): + self.success = success + + +class getCaptchaTask_args(TBase): + """ + Attributes: + - exclusive + """ + + __slots__ = [ + 'exclusive', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.BOOL, 'exclusive', None, None,), #: 1 + ) + + + def __init__(self, exclusive=None,): + self.exclusive = exclusive + + +class getCaptchaTask_result(TBase): + """ + Attributes: + - success + """ + + __slots__ = [ + 'success', + ] + + thrift_spec = ( + (0, TType.STRUCT, 'success', (CaptchaTask, CaptchaTask.thrift_spec), None,), #: 0 + ) + + + def __init__(self, success=None,): + self.success = success + + +class getCaptchaTaskStatus_args(TBase): + """ + Attributes: + - tid + """ + + __slots__ = [ + 'tid', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.I32, 'tid', None, None,), #: 1 + ) + + + def __init__(self, tid=None,): + self.tid = tid + + +class getCaptchaTaskStatus_result(TBase): + """ + Attributes: + - success + """ + + __slots__ = [ + 'success', + ] + + thrift_spec = ( + (0, TType.STRING, 'success', None, None,), #: 0 + ) + + + def __init__(self, success=None,): + self.success = success + + +class setCaptchaResult_args(TBase): + """ + Attributes: + - tid + - result + """ + + __slots__ = [ + 'tid', + 'result', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.I32, 'tid', None, None,), #: 1 + (2, TType.STRING, 'result', None, None,), #: 2 + ) + + + def __init__(self, tid=None, result=None,): + self.tid = tid + self.result = result + + +class setCaptchaResult_result(TBase): + + __slots__ = [ + ] + + thrift_spec = ( + ) diff --git a/pyload/remote/thriftbackend/thriftgen/pyload/__init__.py b/pyload/remote/thriftbackend/thriftgen/pyload/__init__.py new file mode 100644 index 000000000..9a0fb88bf --- /dev/null +++ b/pyload/remote/thriftbackend/thriftgen/pyload/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +__all__ = ['ttypes', 'constants', 'Pyload'] diff --git a/pyload/remote/thriftbackend/thriftgen/pyload/constants.py b/pyload/remote/thriftbackend/thriftgen/pyload/constants.py new file mode 100644 index 000000000..e0a811c8a --- /dev/null +++ b/pyload/remote/thriftbackend/thriftgen/pyload/constants.py @@ -0,0 +1,11 @@ +# +# Autogenerated by Thrift Compiler (0.9.0-dev) +# +# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING +# +# options string: py:slots, dynamic +# + +from thrift.Thrift import TType, TMessageType, TException +from ttypes import * + diff --git a/pyload/remote/thriftbackend/thriftgen/pyload/ttypes.py b/pyload/remote/thriftbackend/thriftgen/pyload/ttypes.py new file mode 100644 index 000000000..8abd775a9 --- /dev/null +++ b/pyload/remote/thriftbackend/thriftgen/pyload/ttypes.py @@ -0,0 +1,860 @@ +# +# Autogenerated by Thrift Compiler (0.9.0-dev) +# +# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING +# +# options string: py:slots, dynamic +# + +from thrift.Thrift import TType, TMessageType, TException + +from thrift.protocol.TBase import TBase, TExceptionBase + + +class DownloadStatus(TBase): + Finished = 0 + Offline = 1 + Online = 2 + Queued = 3 + Skipped = 4 + Waiting = 5 + TempOffline = 6 + Starting = 7 + Failed = 8 + Aborted = 9 + Decrypting = 10 + Custom = 11 + Downloading = 12 + Processing = 13 + Unknown = 14 + + _VALUES_TO_NAMES = { + 0: "Finished", + 1: "Offline", + 2: "Online", + 3: "Queued", + 4: "Skipped", + 5: "Waiting", + 6: "TempOffline", + 7: "Starting", + 8: "Failed", + 9: "Aborted", + 10: "Decrypting", + 11: "Custom", + 12: "Downloading", + 13: "Processing", + 14: "Unknown", + } + + _NAMES_TO_VALUES = { + "Finished": 0, + "Offline": 1, + "Online": 2, + "Queued": 3, + "Skipped": 4, + "Waiting": 5, + "TempOffline": 6, + "Starting": 7, + "Failed": 8, + "Aborted": 9, + "Decrypting": 10, + "Custom": 11, + "Downloading": 12, + "Processing": 13, + "Unknown": 14, + } + + +class Destination(TBase): + Collector = 0 + Queue = 1 + + _VALUES_TO_NAMES = { + 0: "Collector", + 1: "Queue", + } + + _NAMES_TO_VALUES = { + "Collector": 0, + "Queue": 1, + } + + +class ElementType(TBase): + Package = 0 + File = 1 + + _VALUES_TO_NAMES = { + 0: "Package", + 1: "File", + } + + _NAMES_TO_VALUES = { + "Package": 0, + "File": 1, + } + + +class Input(TBase): + NONE = 0 + TEXT = 1 + TEXTBOX = 2 + PASSWORD = 3 + BOOL = 4 + CLICK = 5 + CHOICE = 6 + MULTIPLE = 7 + LIST = 8 + TABLE = 9 + + _VALUES_TO_NAMES = { + 0: "NONE", + 1: "TEXT", + 2: "TEXTBOX", + 3: "PASSWORD", + 4: "BOOL", + 5: "CLICK", + 6: "CHOICE", + 7: "MULTIPLE", + 8: "LIST", + 9: "TABLE", + } + + _NAMES_TO_VALUES = { + "NONE": 0, + "TEXT": 1, + "TEXTBOX": 2, + "PASSWORD": 3, + "BOOL": 4, + "CLICK": 5, + "CHOICE": 6, + "MULTIPLE": 7, + "LIST": 8, + "TABLE": 9, + } + + +class Output(TBase): + CAPTCHA = 1 + QUESTION = 2 + NOTIFICATION = 4 + + _VALUES_TO_NAMES = { + 1: "CAPTCHA", + 2: "QUESTION", + 4: "NOTIFICATION", + } + + _NAMES_TO_VALUES = { + "CAPTCHA": 1, + "QUESTION": 2, + "NOTIFICATION": 4, + } + + +class DownloadInfo(TBase): + """ + Attributes: + - fid + - name + - speed + - eta + - format_eta + - bleft + - size + - format_size + - percent + - status + - statusmsg + - format_wait + - wait_until + - packageID + - packageName + - plugin + """ + + __slots__ = [ + 'fid', + 'name', + 'speed', + 'eta', + 'format_eta', + 'bleft', + 'size', + 'format_size', + 'percent', + 'status', + 'statusmsg', + 'format_wait', + 'wait_until', + 'packageID', + 'packageName', + 'plugin', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.I32, 'fid', None, None,), #: 1 + (2, TType.STRING, 'name', None, None,), #: 2 + (3, TType.I64, 'speed', None, None,), #: 3 + (4, TType.I32, 'eta', None, None,), #: 4 + (5, TType.STRING, 'format_eta', None, None,), #: 5 + (6, TType.I64, 'bleft', None, None,), #: 6 + (7, TType.I64, 'size', None, None,), #: 7 + (8, TType.STRING, 'format_size', None, None,), #: 8 + (9, TType.BYTE, 'percent', None, None,), #: 9 + (10, TType.I32, 'status', None, None,), #: 10 + (11, TType.STRING, 'statusmsg', None, None,), #: 11 + (12, TType.STRING, 'format_wait', None, None,), #: 12 + (13, TType.I64, 'wait_until', None, None,), #: 13 + (14, TType.I32, 'packageID', None, None,), #: 14 + (15, TType.STRING, 'packageName', None, None,), #: 15 + (16, TType.STRING, 'plugin', None, None,), #: 16 + ) + + + def __init__(self, fid=None, name=None, speed=None, eta=None, format_eta=None, bleft=None, size=None, format_size=None, percent=None, status=None, statusmsg=None, format_wait=None, wait_until=None, packageID=None, packageName=None, plugin=None,): + self.fid = fid + self.name = name + self.speed = speed + self.eta = eta + self.format_eta = format_eta + self.bleft = bleft + self.size = size + self.format_size = format_size + self.percent = percent + self.status = status + self.statusmsg = statusmsg + self.format_wait = format_wait + self.wait_until = wait_until + self.packageID = packageID + self.packageName = packageName + self.plugin = plugin + + +class ServerStatus(TBase): + """ + Attributes: + - pause + - active + - queue + - total + - speed + - download + - reconnect + """ + + __slots__ = [ + 'pause', + 'active', + 'queue', + 'total', + 'speed', + 'download', + 'reconnect', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.BOOL, 'pause', None, None,), #: 1 + (2, TType.I16, 'active', None, None,), #: 2 + (3, TType.I16, 'queue', None, None,), #: 3 + (4, TType.I16, 'total', None, None,), #: 4 + (5, TType.I64, 'speed', None, None,), #: 5 + (6, TType.BOOL, 'download', None, None,), #: 6 + (7, TType.BOOL, 'reconnect', None, None,), #: 7 + ) + + + def __init__(self, pause=None, active=None, queue=None, total=None, speed=None, download=None, reconnect=None,): + self.pause = pause + self.active = active + self.queue = queue + self.total = total + self.speed = speed + self.download = download + self.reconnect = reconnect + + +class ConfigItem(TBase): + """ + Attributes: + - name + - description + - value + - type + """ + + __slots__ = [ + 'name', + 'description', + 'value', + 'type', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.STRING, 'name', None, None,), #: 1 + (2, TType.STRING, 'description', None, None,), #: 2 + (3, TType.STRING, 'value', None, None,), #: 3 + (4, TType.STRING, 'type', None, None,), #: 4 + ) + + + def __init__(self, name=None, description=None, value=None, type=None,): + self.name = name + self.description = description + self.value = value + self.type = type + + +class ConfigSection(TBase): + """ + Attributes: + - name + - description + - items + - outline + """ + + __slots__ = [ + 'name', + 'description', + 'items', + 'outline', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.STRING, 'name', None, None,), #: 1 + (2, TType.STRING, 'description', None, None,), #: 2 + (3, TType.LIST, 'items', (TType.STRUCT, (ConfigItem, ConfigItem.thrift_spec)), None,), #: 3 + (4, TType.STRING, 'outline', None, None,), #: 4 + ) + + + def __init__(self, name=None, description=None, items=None, outline=None,): + self.name = name + self.description = description + self.items = items + self.outline = outline + + +class FileData(TBase): + """ + Attributes: + - fid + - url + - name + - plugin + - size + - format_size + - status + - statusmsg + - packageID + - error + - order + """ + + __slots__ = [ + 'fid', + 'url', + 'name', + 'plugin', + 'size', + 'format_size', + 'status', + 'statusmsg', + 'packageID', + 'error', + 'order', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.I32, 'fid', None, None,), #: 1 + (2, TType.STRING, 'url', None, None,), #: 2 + (3, TType.STRING, 'name', None, None,), #: 3 + (4, TType.STRING, 'plugin', None, None,), #: 4 + (5, TType.I64, 'size', None, None,), #: 5 + (6, TType.STRING, 'format_size', None, None,), #: 6 + (7, TType.I32, 'status', None, None,), #: 7 + (8, TType.STRING, 'statusmsg', None, None,), #: 8 + (9, TType.I32, 'packageID', None, None,), #: 9 + (10, TType.STRING, 'error', None, None,), #: 10 + (11, TType.I16, 'order', None, None,), #: 11 + ) + + + def __init__(self, fid=None, url=None, name=None, plugin=None, size=None, format_size=None, status=None, statusmsg=None, packageID=None, error=None, order=None,): + self.fid = fid + self.url = url + self.name = name + self.plugin = plugin + self.size = size + self.format_size = format_size + self.status = status + self.statusmsg = statusmsg + self.packageID = packageID + self.error = error + self.order = order + + +class PackageData(TBase): + """ + Attributes: + - pid + - name + - folder + - site + - password + - dest + - order + - linksdone + - sizedone + - sizetotal + - linkstotal + - links + - fids + """ + + __slots__ = [ + 'pid', + 'name', + 'folder', + 'site', + 'password', + 'dest', + 'order', + 'linksdone', + 'sizedone', + 'sizetotal', + 'linkstotal', + 'links', + 'fids', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.I32, 'pid', None, None,), #: 1 + (2, TType.STRING, 'name', None, None,), #: 2 + (3, TType.STRING, 'folder', None, None,), #: 3 + (4, TType.STRING, 'site', None, None,), #: 4 + (5, TType.STRING, 'password', None, None,), #: 5 + (6, TType.I32, 'dest', None, None,), #: 6 + (7, TType.I16, 'order', None, None,), #: 7 + (8, TType.I16, 'linksdone', None, None,), #: 8 + (9, TType.I64, 'sizedone', None, None,), #: 9 + (10, TType.I64, 'sizetotal', None, None,), #: 10 + (11, TType.I16, 'linkstotal', None, None,), #: 11 + (12, TType.LIST, 'links', (TType.STRUCT, (FileData, FileData.thrift_spec)), None,), #: 12 + (13, TType.LIST, 'fids', (TType.I32, None), None,), #: 13 + ) + + + def __init__(self, pid=None, name=None, folder=None, site=None, password=None, dest=None, order=None, linksdone=None, sizedone=None, sizetotal=None, linkstotal=None, links=None, fids=None,): + self.pid = pid + self.name = name + self.folder = folder + self.site = site + self.password = password + self.dest = dest + self.order = order + self.linksdone = linksdone + self.sizedone = sizedone + self.sizetotal = sizetotal + self.linkstotal = linkstotal + self.links = links + self.fids = fids + + +class InteractionTask(TBase): + """ + Attributes: + - iid + - input + - structure + - preset + - output + - data + - title + - description + - plugin + """ + + __slots__ = [ + 'iid', + 'input', + 'structure', + 'preset', + 'output', + 'data', + 'title', + 'description', + 'plugin', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.I32, 'iid', None, None,), #: 1 + (2, TType.I32, 'input', None, None,), #: 2 + (3, TType.LIST, 'structure', (TType.STRING, None), None,), #: 3 + (4, TType.LIST, 'preset', (TType.STRING, None), None,), #: 4 + (5, TType.I32, 'output', None, None,), #: 5 + (6, TType.LIST, 'data', (TType.STRING, None), None,), #: 6 + (7, TType.STRING, 'title', None, None,), #: 7 + (8, TType.STRING, 'description', None, None,), #: 8 + (9, TType.STRING, 'plugin', None, None,), #: 9 + ) + + + def __init__(self, iid=None, input=None, structure=None, preset=None, output=None, data=None, title=None, description=None, plugin=None,): + self.iid = iid + self.input = input + self.structure = structure + self.preset = preset + self.output = output + self.data = data + self.title = title + self.description = description + self.plugin = plugin + + +class CaptchaTask(TBase): + """ + Attributes: + - tid + - data + - type + - resultType + """ + + __slots__ = [ + 'tid', + 'data', + 'type', + 'resultType', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.I16, 'tid', None, None,), #: 1 + (2, TType.STRING, 'data', None, None,), #: 2 + (3, TType.STRING, 'type', None, None,), #: 3 + (4, TType.STRING, 'resultType', None, None,), #: 4 + ) + + + def __init__(self, tid=None, data=None, type=None, resultType=None,): + self.tid = tid + self.data = data + self.type = type + self.resultType = resultType + + +class EventInfo(TBase): + """ + Attributes: + - eventname + - id + - type + - destination + """ + + __slots__ = [ + 'eventname', + 'id', + 'type', + 'destination', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.STRING, 'eventname', None, None,), #: 1 + (2, TType.I32, 'id', None, None,), #: 2 + (3, TType.I32, 'type', None, None,), #: 3 + (4, TType.I32, 'destination', None, None,), #: 4 + ) + + + def __init__(self, eventname=None, id=None, type=None, destination=None,): + self.eventname = eventname + self.id = id + self.type = type + self.destination = destination + + +class UserData(TBase): + """ + Attributes: + - name + - email + - role + - permission + - templateName + """ + + __slots__ = [ + 'name', + 'email', + 'role', + 'permission', + 'templateName', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.STRING, 'name', None, None,), #: 1 + (2, TType.STRING, 'email', None, None,), #: 2 + (3, TType.I32, 'role', None, None,), #: 3 + (4, TType.I32, 'permission', None, None,), #: 4 + (5, TType.STRING, 'templateName', None, None,), #: 5 + ) + + + def __init__(self, name=None, email=None, role=None, permission=None, templateName=None,): + self.name = name + self.email = email + self.role = role + self.permission = permission + self.templateName = templateName + + +class AccountInfo(TBase): + """ + Attributes: + - validuntil + - login + - options + - valid + - trafficleft + - maxtraffic + - premium + - type + """ + + __slots__ = [ + 'validuntil', + 'login', + 'options', + 'valid', + 'trafficleft', + 'maxtraffic', + 'premium', + 'type', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.I64, 'validuntil', None, None,), #: 1 + (2, TType.STRING, 'login', None, None,), #: 2 + (3, TType.MAP, 'options', (TType.STRING, None, TType.LIST, (TType.STRING, None)), None,), #: 3 + (4, TType.BOOL, 'valid', None, None,), #: 4 + (5, TType.I64, 'trafficleft', None, None,), #: 5 + (6, TType.I64, 'maxtraffic', None, None,), #: 6 + (7, TType.BOOL, 'premium', None, None,), #: 7 + (8, TType.STRING, 'type', None, None,), #: 8 + ) + + + def __init__(self, validuntil=None, login=None, options=None, valid=None, trafficleft=None, maxtraffic=None, premium=None, type=None,): + self.validuntil = validuntil + self.login = login + self.options = options + self.valid = valid + self.trafficleft = trafficleft + self.maxtraffic = maxtraffic + self.premium = premium + self.type = type + + +class ServiceCall(TBase): + """ + Attributes: + - plugin + - func + - arguments + - parseArguments + """ + + __slots__ = [ + 'plugin', + 'func', + 'arguments', + 'parseArguments', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.STRING, 'plugin', None, None,), #: 1 + (2, TType.STRING, 'func', None, None,), #: 2 + (3, TType.LIST, 'arguments', (TType.STRING, None), None,), #: 3 + (4, TType.BOOL, 'parseArguments', None, None,), #: 4 + ) + + + def __init__(self, plugin=None, func=None, arguments=None, parseArguments=None,): + self.plugin = plugin + self.func = func + self.arguments = arguments + self.parseArguments = parseArguments + + +class OnlineStatus(TBase): + """ + Attributes: + - name + - plugin + - packagename + - status + - size + """ + + __slots__ = [ + 'name', + 'plugin', + 'packagename', + 'status', + 'size', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.STRING, 'name', None, None,), #: 1 + (2, TType.STRING, 'plugin', None, None,), #: 2 + (3, TType.STRING, 'packagename', None, None,), #: 3 + (4, TType.I32, 'status', None, None,), #: 4 + (5, TType.I64, 'size', None, None,), #: 5 + ) + + + def __init__(self, name=None, plugin=None, packagename=None, status=None, size=None,): + self.name = name + self.plugin = plugin + self.packagename = packagename + self.status = status + self.size = size + + +class OnlineCheck(TBase): + """ + Attributes: + - rid + - data + """ + + __slots__ = [ + 'rid', + 'data', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.I32, 'rid', None, None,), #: 1 + (2, TType.MAP, 'data', (TType.STRING, None, TType.STRUCT, (OnlineStatus, OnlineStatus.thrift_spec)), None,), #: 2 + ) + + + def __init__(self, rid=None, data=None,): + self.rid = rid + self.data = data + + +class PackageDoesNotExists(TExceptionBase): + """ + Attributes: + - pid + """ + + __slots__ = [ + 'pid', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.I32, 'pid', None, None,), #: 1 + ) + + + def __init__(self, pid=None,): + self.pid = pid + + + def __str__(self): + return repr(self) + + +class FileDoesNotExists(TExceptionBase): + """ + Attributes: + - fid + """ + + __slots__ = [ + 'fid', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.I32, 'fid', None, None,), #: 1 + ) + + + def __init__(self, fid=None,): + self.fid = fid + + + def __str__(self): + return repr(self) + + +class ServiceDoesNotExists(TExceptionBase): + """ + Attributes: + - plugin + - func + """ + + __slots__ = [ + 'plugin', + 'func', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.STRING, 'plugin', None, None,), #: 1 + (2, TType.STRING, 'func', None, None,), #: 2 + ) + + + def __init__(self, plugin=None, func=None,): + self.plugin = plugin + self.func = func + + + def __str__(self): + return repr(self) + + +class ServiceException(TExceptionBase): + """ + Attributes: + - msg + """ + + __slots__ = [ + 'msg', + ] + + thrift_spec = ( + None, #: 0 + (1, TType.STRING, 'msg', None, None,), #: 1 + ) + + + def __init__(self, msg=None,): + self.msg = msg + + + def __str__(self): + return repr(self) diff --git a/pyload/utils/__init__.py b/pyload/utils/__init__.py new file mode 100644 index 000000000..5033780c0 --- /dev/null +++ b/pyload/utils/__init__.py @@ -0,0 +1,272 @@ +# -*- coding: utf-8 -*- +# @author: vuolter + +""" Store all useful functions here """ + +import bitmath +import os +import re +import sys +import time + +# from gettext import gettext +import pylgettext as gettext +from htmlentitydefs import name2codepoint +from os.path import join +from string import maketrans +from urllib import unquote + +# abstraction layer for json operations +try: + import simplejson as json +except ImportError: + import json + +json_loads = json.loads +json_dumps = json.dumps + + +def chmod(*args): + try: + os.chmod(*args) + except Exception: + pass + + +def decode(string): + """ Decode string to unicode with utf8 """ + if type(string) == str: + return string.decode("utf8", "replace") + else: + return string + + +def encode(string): + """ Decode string to utf8 """ + if type(string) == unicode: + return string.encode("utf8", "replace") + else: + return string + + +def remove_chars(string, repl): + """ removes all chars in repl from string""" + if type(repl) == unicode: + for badc in list(repl): + string = string.replace(badc, "") + return string + else: + if type(string) == str: + return string.translate(maketrans("", ""), repl) + elif type(string) == unicode: + return string.translate(dict((ord(s), None) for s in repl)) + + +def safe_filename(name): + """ remove bad chars """ + name = unquote(name).encode('ascii', 'replace') #: Non-ASCII chars usually breaks file saving. Replacing. + if os.name == 'nt': + return remove_chars(name, u'\00\01\02\03\04\05\06\07\10\11\12\13\14\15\16\17\20\21\22\23\24\25\26\27\30\31\32' + u'\33\34\35\36\37/?%*|"<>') + else: + return remove_chars(name, u'\0\\"') + + +#: Deprecated method +def save_path(name): + return safe_filename(name) + + +def fs_join(*args): + """ joins a path, encoding aware """ + return fs_encode(join(*[x if type(x) == unicode else decode(x) for x in args])) + + +#: Deprecated method +def save_join(*args): + return fs_join(*args) + + +# File System Encoding functions: +# Use fs_encode before accesing files on disk, it will encode the string properly + +if sys.getfilesystemencoding().startswith('ANSI'): + + def fs_encode(string): + return safe_filename(encode(string)) + + fs_decode = decode #: decode utf8 + +else: + fs_encode = fs_decode = lambda x: x #: do nothing + + +def get_console_encoding(enc): + if os.name == "nt": + if enc == "cp65001": #: aka UTF-8 + print "WARNING: Windows codepage 65001 is not supported." + enc = "cp850" + else: + enc = "utf8" + + return enc + + +def compare_time(start, end): + start = map(int, start) + end = map(int, end) + + if start == end: + return True + + now = list(time.localtime()[3:5]) + if start < now < end: + return True + elif start > end and (now > start or now < end): + return True + elif start < now > end < start: + return True + return False + + +def formatSize(size): + """formats size of bytes""" + return bitmath.Byte(int(size)).best_prefix() + + +def formatSpeed(speed): + return formatSize(speed) + "/s" + + +def freeSpace(folder): + if os.name == "nt": + import ctypes + + free_bytes = ctypes.c_ulonglong(0) + ctypes.windll.kernel32.GetDiskFreeSpaceExW(ctypes.c_wchar_p(folder), None, None, ctypes.pointer(free_bytes)) + return free_bytes.value + else: + s = os.statvfs(folder) + return s.f_frsize * s.f_bavail + + +def fs_bsize(path): + """ get optimal file system buffer size (in bytes) for I/O calls """ + path = fs_encode(path) + + if os.name == "nt": + import ctypes + + drive = "%s\\" % os.path.splitdrive(path)[0] + cluster_sectors, sector_size = ctypes.c_longlong(0) + ctypes.windll.kernel32.GetDiskFreeSpaceW(ctypes.c_wchar_p(drive), ctypes.pointer(cluster_sectors), ctypes.pointer(sector_size), None, None) + return cluster_sectors * sector_size + else: + return os.statvfs(path).f_frsize + + +def uniqify(seq): #: Originally by Dave Kirby + """ Remove duplicates from list preserving order """ + seen = set() + seen_add = seen.add + return [x for x in seq if x not in seen and not seen_add(x)] + + +def parseFileSize(string, unit=None): #: returns bytes + if not unit: + m = re.match(r"([\d.,]+) *([a-zA-Z]*)", string.strip().lower()) + if m: + traffic = float(m.group(1).replace(",", ".")) + unit = m.group(2) + else: + return 0 + else: + if isinstance(string, basestring): + traffic = float(string.replace(",", ".")) + else: + traffic = string + + # ignore case + unit = unit.lower().strip() + + if unit in ("eb", "ebyte", "exabyte", "eib", "e"): + traffic *= 1 << 60 + elif unit in ("pb", "pbyte", "petabyte", "pib", "p"): + traffic *= 1 << 50 + elif unit in ("tb", "tbyte", "terabyte", "tib", "t"): + traffic *= 1 << 40 + elif unit in ("gb", "gbyte", "gigabyte", "gib", "g", "gig"): + traffic *= 1 << 30 + elif unit in ("mb", "mbyte", "megabyte", "mib", "m"): + traffic *= 1 << 20 + elif unit in ("kb", "kbyte", "kilobyte", "kib", "k"): + traffic *= 1 << 10 + + return traffic + + +def lock(func): + + def new(*args): + # print "Handler: %s args: %s" % (func, args[1:]) + args[0].lock.acquire() + try: + return func(*args) + finally: + args[0].lock.release() + + return new + + +def fixup(m): + text = m.group(0) + if text[:2] == "&#": + # character reference + try: + if text[:3] == "&#x": + return unichr(int(text[3:-1], 16)) + else: + return unichr(int(text[2:-1])) + except ValueError: + pass + else: + # named entity + try: + name = text[1:-1] + text = unichr(name2codepoint[name]) + except KeyError: + pass + + return text #: leave as is + + +def has_method(obj, name): + """ Check if "name" was defined in obj, (false if it was inhereted) """ + return hasattr(obj, '__dict__') and name in obj.__dict__ + + +def html_unescape(text): + """Removes HTML or XML character references and entities from a text string""" + return re.sub("&#?\w+;", fixup, text) + + +def versiontuple(v): #: By kindall (http://stackoverflow.com/a/11887825) + return tuple(map(int, (v.split(".")))) + + +def load_translation(name, locale, default="en"): + """ Load language and return its translation object or None """ + + from os.path import join + from traceback import print_exc + + try: + gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None]) + translation = gettext.translation(name, join(pypath, "locale"), + languages=[locale, default], fallback=True) + except Exception: + print_exc() + return None + else: + translation.install(True) + return translation diff --git a/pyload/utils/packagetools.py b/pyload/utils/packagetools.py new file mode 100644 index 000000000..8ed55d0f7 --- /dev/null +++ b/pyload/utils/packagetools.py @@ -0,0 +1,155 @@ +# -*- coding: utf-8 -*- + +import re + +from urlparse import urlparse + + +endings = ("jdeatme", "3gp", "7zip", "7z", "abr", "ac3", "aiff", "aifc", "aif", "ai", + "au", "avi", "apk", "bin", "bmp", "bat", "bz2", "cbr", "cbz", "ccf", "chm", + "cr2", "cso", "cue", "cvd", "dta", "deb", "divx", "djvu", "dlc", "dmg", "doc", + "docx", "dot", "eps", "epub", "exe", "ff", "flv", "flac", "f4v", "gsd", "gif", + "gpg", "gz", "iwd", "idx", "iso", "ipa", "ipsw", "java", "jar", "jpe?g", "load", + "m2ts", "m4v", "m4a", "md5", "mkv", "mp2", "mp3", "mp4", "mobi", "mov", "movie", + "mpeg", "mpe", "mpg", "mpq", "msi", "msu", "msp", "mv", "mws", "nfo", "npk", "oga", + "ogg", "ogv", "otrkey", "par2", "pkg", "png", "pdf", "pptx?", "ppsx?", "ppz", "pot", + "psd", "qt", "rmvb", "rm", "rar", "ram", "ra", "rev", "rnd", "rpm", "run", "rsdf", + "reg", "rtf", "shnf", "sh(?!tml)", "ssa", "smi", "sub", "srt", "snd", "sfv", "sfx", + "swf", "swc", "tar\.(gz|bz2|xz)", "tar", "tgz", "tiff?", "ts", "txt", "viv", "vivo", + "vob", "vtt", "webm", "wav", "wmv", "wma", "xla", "xls", "xpi", "zeno", "zip", + "[r-z]\d{2}", "_[_a-z]{2}", "\d{3,4}(?=\?|$|\"|\r|\n)") + +rarPats = [re.compile(r'(.*)(\.|_|-)pa?r?t?\.?\d+.(rar|exe)$', re.I), + re.compile(r'(.*)(\.|_|-)part\.?[0]*[1].(rar|exe)$', re.I), + re.compile(r'(.*)\.rar$', re.I), + re.compile(r'(.*)\.r\d+$', re.I), + re.compile(r'(.*)(\.|_|-)\d+$', re.I)] + +zipPats = [re.compile(r'(.*)\.zip$', re.I), + re.compile(r'(.*)\.z\d+$', re.I), + re.compile(r'(?is).*\.7z\.[\d]+$', re.I), + re.compile(r'(.*)\.a.$', re.I)] + +ffsjPats = [re.compile(r'(.*)\._((_[a-z])|([a-z]{2}))(\.|$)'), + re.compile(r'(.*)(\.|_|-)[\d]+(\.(' + '|'.join(endings) + ')$)', re.I)] + +iszPats = [re.compile(r'(.*)\.isz$', re.I), + re.compile(r'(.*)\.i\d{2}$', re.I)] + +pat0 = re.compile(r'www\d*\.', re.I) + +pat1 = re.compile(r'(\.?CD\d+)', re.I) +pat2 = re.compile(r'(\.?part\d+)', re.I) + +pat3 = re.compile(r'(.+)[\.\-_]+$') +pat4 = re.compile(r'(.+)\.\d+\.xtm$') + + +def matchFirst(string, *args): + """ matches against list of regexp and returns first match """ + for patternlist in args: + for pattern in patternlist: + m = pattern.search(string) + if m is not None: + name = m.group(1) + return name + + return string + + +def parseNames(files): + """ Generates packages names from name, data lists + + :param files: list of (name, data) + :return: packagenames mapped to data lists (eg. urls) + """ + packs = {} + + for file, url in files: + patternMatch = False + + if file is None: + continue + + # remove trailing / + name = file.rstrip('/') + + # extract last path part .. if there is a path + split = name.rsplit("/", 1) + if len(split) > 1: + name = split.pop(1) + + # check if an already existing package may be ok for this file + # found = False + # for pack in packs: + # if pack in file: + # packs[pack].append(url) + # found = True + # break + # + # if found: + # continue + + # unrar pattern, 7zip/zip and hjmerge pattern, isz pattern, FFSJ pattern + before = name + name = matchFirst(name, rarPats, zipPats, iszPats, ffsjPats) + if before != name: + patternMatch = True + + # xtremsplit pattern + m = pat4.search(name) + if m is not None: + name = m.group(1) + + # remove part and cd pattern + m = pat1.search(name) + if m is not None: + name = name.replace(m.group(0), "") + patternMatch = True + + m = pat2.search(name) + if m is not None: + name = name.replace(m.group(0), "") + patternMatch = True + + # additional checks if extension pattern matched + if patternMatch: + # remove extension + index = name.rfind(".") + if index <= 0: + index = name.rfind("_") + if index > 0: + length = len(name) - index + if length <= 4: + name = name[:-length] + + # remove endings like . _ - + m = pat3.search(name) + if m is not None: + name = m.group(1) + + # replace . and _ with space + name = name.replace(".", " ") + name = name.replace("_", " ") + + name = name.strip() + else: + name = "" + + #@NOTE: fallback: package by hoster + if not name: + name = urlparse(file).netloc + if name: + name = pat0.sub("", name) + + # fallback : default name + if not name: + name = _("Unnamed package") + + # build mapping + if name in packs: + packs[name].append(url) + else: + packs[name] = [url] + + return packs diff --git a/pyload/utils/printer.py b/pyload/utils/printer.py new file mode 100644 index 000000000..e4f6a360a --- /dev/null +++ b/pyload/utils/printer.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +# @author: vuolter + +import colorama + +colorama.init(autoreset=True) + + +def color(color, text): + return colorama.Fore[c.upper()](text) + +for c in colorama.Fore: + eval("%(color)s = lambda msg: color(%(color)s, msg)" % {'color': c.lower()}) + + +def overline(line, msg): + print "\033[%(line)s;0H\033[2K%(msg)s" % {'line': str(line), 'msg': msg} diff --git a/pyload/utils/pylgettext.py b/pyload/utils/pylgettext.py new file mode 100644 index 000000000..76bb268ec --- /dev/null +++ b/pyload/utils/pylgettext.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- + +from gettext import * + +_searchdirs = None + +origfind = find + + +def setpaths(pathlist): + global _searchdirs + _searchdirs = pathlist if isinstance(pathlist, list) else list(pathlist) + + +def addpath(path): + global _searchdirs + if _searchdirs is None: + _searchdirs = list(path) + else: + if path not in _searchdirs: + _searchdirs.append(path) + + +def delpath(path): + global _searchdirs + if _searchdirs is not None: + if path in _searchdirs: + _searchdirs.remove(path) + + +def clearpath(): + global _searchdirs + _searchdirs = None + + +def find(domain, localedir=None, languages=None, all=False): + if _searchdirs is None: + return origfind(domain, localedir, languages, all) + searches = [localedir] + _searchdirs + results = [] + for dir in searches: + res = origfind(domain, dir, languages, all) + if all is False: + results.append(res) + else: + results.extend(res) + if all is False: + results = filter(lambda x: x is not None, results) + if len(results) == 0: + return None + else: + return results[0] + else: + return results + +# Is there a smarter/cleaner pythonic way for this? +translation.func_globals['find'] = find diff --git a/pyload/webui/__init__.py b/pyload/webui/__init__.py new file mode 100644 index 000000000..3b3d62b26 --- /dev/null +++ b/pyload/webui/__init__.py @@ -0,0 +1,131 @@ +# -*- coding: utf-8 -*- +# @author: RaNaN, vuolter + +import sys +import pyload.utils.pylgettext as gettext + +import os +from os.path import join, abspath, dirname, exists +from os import makedirs + +THEME_DIR = abspath(join(dirname(__file__), "themes")) +PYLOAD_DIR = abspath(join(THEME_DIR, "..", "..", "..")) + +sys.path.append(PYLOAD_DIR) + +from pyload.utils import decode, formatSize + +import bottle +from bottle import run, app + +from jinja2 import Environment, FileSystemLoader, PrefixLoader, FileSystemBytecodeCache +from middlewares import StripPathMiddleware, GZipMiddleWare, PrefixMiddleware + +SETUP = None +PYLOAD = None + +from pyload.manager.thread import Server +from pyload.network.JsEngine import JsEngine + +if not Server.core: + if Server.setup: + SETUP = Server.setup + config = SETUP.config + JS = JsEngine(SETUP) + else: + raise Exception("Could not access pyLoad Core") +else: + PYLOAD = Server.core.api + config = Server.core.config + JS = JsEngine(Server.core) + +THEME = config.get('webui', 'theme') +DL_ROOT = config.get('general', 'download_folder') +LOG_ROOT = config.get('log', 'log_folder') +PREFIX = config.get('webui', 'prefix') + +if PREFIX: + PREFIX = PREFIX.rstrip("/") + if not PREFIX.startswith("/"): + PREFIX = "/" + PREFIX + +DEBUG = config.get("general", "debug_mode") or "-d" in sys.argv or "--debug" in sys.argv +bottle.debug(DEBUG) + +cache = join("tmp", "jinja_cache") +if not exists(cache): + makedirs(cache) + +bcc = FileSystemBytecodeCache(cache, '%s.cache') + +loader = FileSystemLoader([THEME_DIR, join(THEME_DIR, THEME)]) + +env = Environment(loader=loader, extensions=['jinja2.ext.i18n', 'jinja2.ext.autoescape'], trim_blocks=True, auto_reload=False, + bytecode_cache=bcc) + +from filters import quotepath, path_make_relative, path_make_absolute, truncate, date + +env.filters['quotepath'] = quotepath +env.filters['truncate'] = truncate +env.filters['date'] = date +env.filters['path_make_relative'] = path_make_relative +env.filters['path_make_absolute'] = path_make_absolute +env.filters['decode'] = decode +env.filters['type'] = lambda x: str(type(x)) +env.filters['formatsize'] = formatSize +env.filters['getitem'] = lambda x, y: x.__getitem__(y) +if PREFIX: + env.filters['url'] = lambda x: x +else: + env.filters['url'] = lambda x: PREFIX + x if x.startswith("/") else x + +gettext.setpaths([join(os.sep, "usr", "share", "pyload", "locale"), None]) +translation = gettext.translation("django", join(PYLOAD_DIR, "locale"), + languages=[config.get("general", "language"), "en"],fallback=True) +translation.install(True) +env.install_gettext_translations(translation) + +from beaker.middleware import SessionMiddleware + +session_opts = { + 'session.type': 'file', + 'session.cookie_expires': False, + 'session.data_dir': './tmp', + 'session.auto': False +} + +web = StripPathMiddleware(SessionMiddleware(app(), session_opts)) +web = GZipMiddleWare(web) + +if PREFIX: + web = PrefixMiddleware(web, prefix=PREFIX) + +import pyload.webui.app + + +def run_auto(host="0.0.0.0", port="8000"): + run(app=web, host=host, port=port, server="auto", quiet=True) + + +def run_lightweight(host="0.0.0.0", port="8000"): + run(app=web, host=host, port=port, server="bjoern", quiet=True) + + +def run_threaded(host="0.0.0.0", port="8000", theads=3, cert="", key=""): + from wsgiserver import CherryPyWSGIServer + + if cert and key: + CherryPyWSGIServer.ssl_certificate = cert + CherryPyWSGIServer.ssl_private_key = key + + CherryPyWSGIServer.numthreads = theads + + from pyload.webui.app.utils import CherryPyWSGI + + run(app=web, host=host, port=port, server=CherryPyWSGI, quiet=True) + + +def run_fcgi(host="0.0.0.0", port="8000"): + from bottle import FlupFCGIServer + + run(app=web, host=host, port=port, server=FlupFCGIServer, quiet=True) diff --git a/pyload/webui/app/__init__.py b/pyload/webui/app/__init__.py new file mode 100644 index 000000000..43c9ecbe9 --- /dev/null +++ b/pyload/webui/app/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from pyload.webui.app import api, cnl, json, pyloadweb diff --git a/pyload/webui/app/api.py b/pyload/webui/app/api.py new file mode 100644 index 000000000..7f1b230da --- /dev/null +++ b/pyload/webui/app/api.py @@ -0,0 +1,100 @@ +# -*- coding: utf-8 -*- + +import traceback + +from itertools import chain +from urllib import unquote + +from SafeEval import const_eval as literal_eval +from bottle import route, request, response, HTTPError + +from pyload.api import BaseObject +from pyload.utils import json +from pyload.webui import PYLOAD +from pyload.webui.app.utils import toDict, set_session + + +# json encoder that accepts TBase objects +class TBaseEncoder(json.JSONEncoder): + + def default(self, o): + if isinstance(o, BaseObject): + return toDict(o) + return json.JSONEncoder.default(self, o) + + +# accepting positional arguments, as well as kwargs via post and get +@route('/api/<func><args:re:[a-zA-Z0-9\-_/\"\'\[\]%{},]*>') +@route('/api/<func><args:re:[a-zA-Z0-9\-_/\"\'\[\]%{},]*>', method='POST') +def call_api(func, args=""): + response.headers.replace("Content-type", "application/json") + response.headers.append("Cache-Control", "no-cache, must-revalidate") + + s = request.environ.get('beaker.session') + if 'session' in request.POST: + s = s.get_by_id(request.POST['session']) + + if not s or not s.get("authenticated", False): + return HTTPError(403, json.dumps("Forbidden")) + + if not PYLOAD.isAuthorized(func, {"role": s['role'], "permission": s['perms']}): + return HTTPError(401, json.dumps("Unauthorized")) + + args = args.split("/")[1:] + kwargs = {} + + for x, y in chain(request.GET.iteritems(), request.POST.iteritems()): + if x == "session": + continue + kwargs[x] = unquote(y) + + try: + return callApi(func, *args, **kwargs) + except Exception, e: + traceback.print_exc() + return HTTPError(500, json.dumps({"error": e.message, "traceback": traceback.format_exc()})) + + +def callApi(func, *args, **kwargs): + if not hasattr(PYLOAD.EXTERNAL, func) or func.startswith("_"): + print "Invalid API call", func + return HTTPError(404, json.dumps("Not Found")) + + result = getattr(PYLOAD, func)(*[literal_eval(x) for x in args], + **dict((x, literal_eval(y)) for x, y in kwargs.iteritems())) + + # null is invalid json response + return json.dumps(result or True, cls=TBaseEncoder) + + +# post -> username, password +@route('/api/login', method='POST') +def login(): + response.headers.replace("Content-type", "application/json") + response.headers.append("Cache-Control", "no-cache, must-revalidate") + + user = request.forms.get("username") + password = request.forms.get("password") + + info = PYLOAD.checkAuth(user, password) + + if not info: + return json.dumps(False) + + s = set_session(request, info) + + # get the session id by dirty way, documentations seems wrong + try: + sid = s._headers['cookie_out'].split("=")[1].split(";")[0] + return json.dumps(sid) + except Exception: + return json.dumps(True) + + +@route('/api/logout') +def logout(): + response.headers.replace("Content-type", "application/json") + response.headers.append("Cache-Control", "no-cache, must-revalidate") + + s = request.environ.get('beaker.session') + s.delete() diff --git a/pyload/webui/app/cnl.py b/pyload/webui/app/cnl.py new file mode 100644 index 000000000..635d4030c --- /dev/null +++ b/pyload/webui/app/cnl.py @@ -0,0 +1,172 @@ +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +from os.path import join +import re +from urllib import unquote +from base64 import standard_b64decode +from binascii import unhexlify + +from bottle import route, request, HTTPError + +from pyload.webui import PYLOAD, DL_ROOT, JS + + +try: + from Crypto.Cipher import AES +except Exception: + pass + + +def local_check(function): + + + def _view(*args, **kwargs): + if request.environ.get("REMOTE_ADDR", "0") in ("127.0.0.1", "localhost") \ + or request.environ.get("HTTP_HOST", "0") in ("127.0.0.1:9666", "localhost:9666"): + return function(*args, **kwargs) + else: + return HTTPError(403, "Forbidden") + + return _view + + +@route('/flash') +@route('/flash/<id>') +@route('/flash', method='POST') +@local_check +def flash(id="0"): + return "JDownloader\r\n" + + +@route('/flash/add', method='POST') +@local_check +def add(request): + package = request.POST.get('referer', None) + urls = filter(lambda x: x != "", request.POST['urls'].split("\n")) + + if package: + PYLOAD.addPackage(package, urls, 0) + else: + PYLOAD.generateAndAddPackages(urls, 0) + + return "" + + +@route('/flash/addcrypted', method='POST') +@local_check +def addcrypted(): + package = request.forms.get('referer', 'ClickNLoad Package') + dlc = request.forms['crypted'].replace(" ", "+") + + dlc_path = join(DL_ROOT, package.replace("/", "").replace("\\", "").replace(":", "") + ".dlc") + with open(dlc_path, "wb") as dlc_file: + dlc_file.write(dlc) + + try: + PYLOAD.addPackage(package, [dlc_path], 0) + except Exception: + return HTTPError() + else: + return "success\r\n" + + +@route('/flash/addcrypted2', method='POST') +@local_check +def addcrypted2(): + package = request.forms.get("source", None) + crypted = request.forms['crypted'] + jk = request.forms['jk'] + + crypted = standard_b64decode(unquote(crypted.replace(" ", "+"))) + if JS: + jk = "%s f()" % jk + jk = JS.eval(jk) + + else: + try: + jk = re.findall(r"return ('|\")(.+)('|\")", jk)[0][1] + except Exception: + # Test for some known js functions to decode + if jk.find("dec") > -1 and jk.find("org") > -1: + org = re.findall(r"var org = ('|\")([^\"']+)", jk)[0][1] + jk = list(org) + jk.reverse() + jk = "".join(jk) + else: + print "Could not decrypt key, please install py-spidermonkey or ossp-js" + + try: + Key = unhexlify(jk) + except Exception: + print "Could not decrypt key, please install py-spidermonkey or ossp-js" + return "failed" + + IV = Key + + obj = AES.new(Key, AES.MODE_CBC, IV) + result = obj.decrypt(crypted).replace("\x00", "").replace("\r", "").split("\n") + + result = filter(lambda x: x != "", result) + + try: + if package: + PYLOAD.addPackage(package, result, 0) + else: + PYLOAD.generateAndAddPackages(result, 0) + except Exception: + return "failed can't add" + else: + return "success\r\n" + + +@route('/flashgot_pyload') +@route('/flashgot_pyload', method='POST') +@route('/flashgot') +@route('/flashgot', method='POST') +@local_check +def flashgot(): + if request.environ['HTTP_REFERER'] not in ("http://localhost:9666/flashgot", "http://127.0.0.1:9666/flashgot"): + return HTTPError() + + autostart = int(request.forms.get('autostart', 0)) + package = request.forms.get('package', None) + urls = filter(lambda x: x != "", request.forms['urls'].split("\n")) + folder = request.forms.get('dir', None) + + if package: + PYLOAD.addPackage(package, urls, autostart) + else: + PYLOAD.generateAndAddPackages(urls, autostart) + + return "" + + +@route('/crossdomain.xml') +@local_check +def crossdomain(): + rep = "<?xml version=\"1.0\"?>\n" + rep += "<!DOCTYPE cross-domain-policy SYSTEM \"http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd\">\n" + rep += "<cross-domain-policy>\n" + rep += "<allow-access-from domain=\"*\" />\n" + rep += "</cross-domain-policy>" + return rep + + +@route('/flash/checkSupportForUrl') +@local_check +def checksupport(): + url = request.GET.get("url") + res = PYLOAD.checkURLs([url]) + supported = (not res[0][1] is None) + + return str(supported).lower() + + +@route('/jdcheck.js') +@local_check +def jdcheck(): + rep = "jdownloader=true;\n" + rep += "var version='9.581;'" + return rep diff --git a/pyload/webui/app/json.py b/pyload/webui/app/json.py new file mode 100644 index 000000000..400eba624 --- /dev/null +++ b/pyload/webui/app/json.py @@ -0,0 +1,315 @@ +# -*- coding: utf-8 -*- + +from __future__ import with_statement + +import shutil +import traceback + +from os.path import join + +from bottle import route, request, HTTPError + +from pyload.utils import decode, formatSize +from pyload.webui import PYLOAD +from pyload.webui.app.utils import login_required, render_to_response, toDict + + +def format_time(seconds): + seconds = int(seconds) + + hours, seconds = divmod(seconds, 3600) + minutes, seconds = divmod(seconds, 60) + return "%.2i:%.2i:%.2i" % (hours, minutes, seconds) + + +def get_sort_key(item): + return item['order'] + + +@route('/json/status') +@route('/json/status', method='POST') +@login_required('LIST') +def status(): + try: + status = toDict(PYLOAD.statusServer()) + status['captcha'] = PYLOAD.isCaptchaWaiting() + return status + except Exception: + return HTTPError() + + +@route('/json/links') +@route('/json/links', method='POST') +@login_required('LIST') +def links(): + try: + links = [toDict(x) for x in PYLOAD.statusDownloads()] + ids = [] + for link in links: + ids.append(link['fid']) + + if link['status'] == 12: + link['info'] = "%s @ %s/s" % (link['format_eta'], formatSize(link['speed'])) + elif link['status'] == 5: + link['percent'] = 0 + link['size'] = 0 + link['bleft'] = 0 + link['info'] = _("waiting %s") % link['format_wait'] + else: + link['info'] = "" + + data = {'links': links, 'ids': ids} + return data + except Exception, e: + traceback.print_exc() + return HTTPError() + + +@route('/json/packages') +@login_required('LIST') +def packages(): + print "/json/packages" + try: + data = PYLOAD.getQueue() + + for package in data: + package['links'] = [] + for file in PYLOAD.get_package_files(package['id']): + package['links'].append(PYLOAD.get_file_info(file)) + + return data + + except Exception: + return HTTPError() + + +@route('/json/package/<id:int>') +@login_required('LIST') +def package(id): + try: + data = toDict(PYLOAD.getPackageData(id)) + data['links'] = [toDict(x) for x in data['links']] + + for pyfile in data['links']: + if pyfile['status'] == 0: + pyfile['icon'] = "status_finished.png" + elif pyfile['status'] in (2, 3): + pyfile['icon'] = "status_queue.png" + elif pyfile['status'] in (9, 1): + pyfile['icon'] = "status_offline.png" + elif pyfile['status'] == 5: + pyfile['icon'] = "status_waiting.png" + elif pyfile['status'] == 8: + pyfile['icon'] = "status_failed.png" + elif pyfile['status'] == 4: + pyfile['icon'] = "arrow_right.png" + elif pyfile['status'] in (11, 13): + pyfile['icon'] = "status_proc.png" + else: + pyfile['icon'] = "status_downloading.png" + + tmp = data['links'] + tmp.sort(key=get_sort_key) + data['links'] = tmp + return data + + except Exception: + traceback.print_exc() + return HTTPError() + + +@route('/json/package_order/<ids>') +@login_required('ADD') +def package_order(ids): + try: + pid, pos = ids.split("|") + PYLOAD.orderPackage(int(pid), int(pos)) + return {"response": "success"} + except Exception: + return HTTPError() + + +@route('/json/abort_link/<id:int>') +@login_required('DELETE') +def abort_link(id): + try: + PYLOAD.stopDownloads([id]) + return {"response": "success"} + except Exception: + return HTTPError() + + +@route('/json/link_order/<ids>') +@login_required('ADD') +def link_order(ids): + try: + pid, pos = ids.split("|") + PYLOAD.orderFile(int(pid), int(pos)) + return {"response": "success"} + except Exception: + return HTTPError() + + +@route('/json/add_package') +@route('/json/add_package', method='POST') +@login_required('ADD') +def add_package(): + name = request.forms.get("add_name", "New Package").strip() + queue = int(request.forms['add_dest']) + links = decode(request.forms['add_links']) + links = links.split("\n") + pw = request.forms.get("add_password", "").strip("\n\r") + + try: + f = request.files['add_file'] + + if not name or name == "New Package": + name = f.name + + fpath = join(PYLOAD.getConfigValue("general", "download_folder"), "tmp_" + f.filename) + with open(fpath, 'wb') as destination: + shutil.copyfileobj(f.file, destination) + links.insert(0, fpath) + except Exception: + pass + + name = name.decode("utf8", "ignore") + + links = map(lambda x: x.strip(), links) + links = filter(lambda x: x != "", links) + + pack = PYLOAD.addPackage(name, links, queue) + if pw: + pw = pw.decode("utf8", "ignore") + data = {"password": pw} + PYLOAD.setPackageData(pack, data) + + +@route('/json/move_package/<dest:int>/<id:int>') +@login_required('MODIFY') +def move_package(dest, id): + try: + PYLOAD.movePackage(dest, id) + return {"response": "success"} + except Exception: + return HTTPError() + + +@route('/json/edit_package', method='POST') +@login_required('MODIFY') +def edit_package(): + try: + id = int(request.forms.get("pack_id")) + data = {"name": request.forms.get("pack_name").decode("utf8", "ignore"), + "folder": request.forms.get("pack_folder").decode("utf8", "ignore"), + "password": request.forms.get("pack_pws").decode("utf8", "ignore")} + + PYLOAD.setPackageData(id, data) + return {"response": "success"} + + except Exception: + return HTTPError() + + +@route('/json/set_captcha') +@route('/json/set_captcha', method='POST') +@login_required('ADD') +def set_captcha(): + if request.environ.get('REQUEST_METHOD', "GET") == "POST": + try: + PYLOAD.setCaptchaResult(request.forms['cap_id'], request.forms['cap_result']) + except Exception: + pass + + task = PYLOAD.getCaptchaTask() + + if task.tid >= 0: + src = "data:image/%s;base64,%s" % (task.type, task.data) + + return {'captcha': True, 'id': task.tid, 'src': src, 'result_type': task.resultType} + else: + return {'captcha': False} + + +@route('/json/load_config/<category>/<section>') +@login_required("SETTINGS") +def load_config(category, section): + conf = None + if category == "general": + conf = PYLOAD.getConfigDict() + elif category == "plugin": + conf = PYLOAD.getPluginConfigDict() + + for key, option in conf[section].iteritems(): + if key in ("desc", "outline"): + continue + + if ";" in option['type']: + option['list'] = option['type'].split(";") + + option['value'] = decode(option['value']) + + return render_to_response("settings_item.html", {"sorted_conf": lambda c: sorted(c.items(), key=lambda i: i[1]['idx'] if i[0] not in ("desc", "outline") else 0), + "skey": section, "section": conf[section]}) + + +@route('/json/save_config/<category>', method='POST') +@login_required("SETTINGS") +def save_config(category): + for key, value in request.POST.iteritems(): + try: + section, option = key.split("|") + except Exception: + continue + + if category == "general": category = "core" + + PYLOAD.setConfigValue(section, option, decode(value), category) + + +@route('/json/add_account', method='POST') +@login_required("ACCOUNTS") +def add_account(): + login = request.POST['account_login'] + password = request.POST['account_password'] + type = request.POST['account_type'] + + PYLOAD.updateAccount(type, login, password) + + +@route('/json/update_accounts', method='POST') +@login_required("ACCOUNTS") +def update_accounts(): + deleted = [] #: dont update deleted accs or they will be created again + + for name, value in request.POST.iteritems(): + value = value.strip() + if not value: + continue + + tmp, user = name.split(";") + plugin, action = tmp.split("|") + + if (plugin, user) in deleted: + continue + + if action == "password": + PYLOAD.updateAccount(plugin, user, value) + elif action == "time" and "-" in value: + PYLOAD.updateAccount(plugin, user, options={"time": [value]}) + elif action == "limitdl" and value.isdigit(): + PYLOAD.updateAccount(plugin, user, options={"limitDL": [value]}) + elif action == "delete": + deleted.append((plugin, user)) + PYLOAD.removeAccount(plugin, user) + + +@route('/json/change_password', method='POST') +def change_password(): + user = request.POST['user_login'] + oldpw = request.POST['login_current_password'] + newpw = request.POST['login_new_password'] + + if not PYLOAD.changePassword(user, oldpw, newpw): + print "Wrong password" + return HTTPError() diff --git a/pyload/webui/app/pyloadweb.py b/pyload/webui/app/pyloadweb.py new file mode 100644 index 000000000..7f2317bd1 --- /dev/null +++ b/pyload/webui/app/pyloadweb.py @@ -0,0 +1,530 @@ +# -*- coding: utf-8 -*- +# @author: RaNaN + +from datetime import datetime +from operator import itemgetter, attrgetter + +import time +import os +import sys +from os import listdir +from os.path import isdir, isfile, join, abspath +from sys import getfilesystemencoding +from urllib import unquote + +from bottle import route, static_file, request, response, redirect, error + +from pyload.webui import PYLOAD, PYLOAD_DIR, THEME_DIR, THEME, SETUP, env + +from pyload.webui.app.utils import render_to_response, parse_permissions, parse_userdata, \ + login_required, get_permission, set_permission, permlist, toDict, set_session + +from pyload.webui.filters import relpath, unquotepath + +from pyload.utils import formatSize, fs_join, fs_encode, fs_decode + +# Helper + + +def pre_processor(): + s = request.environ.get('beaker.session') + user = parse_userdata(s) + perms = parse_permissions(s) + status = {} + captcha = False + update = False + plugins = False + if user['is_authenticated']: + status = PYLOAD.statusServer() + info = PYLOAD.getInfoByPlugin("UpdateManager") + captcha = PYLOAD.isCaptchaWaiting() + + # check if update check is available + if info: + if info['pyload'] == "True": + update = info['version'] + if info['plugins'] == "True": + plugins = True + + return {"user": user, + 'status': status, + 'captcha': captcha, + 'perms': perms, + 'url': request.url, + 'update': update, + 'plugins': plugins} + + +def base(messages): + return render_to_response('base.html', {'messages': messages}, [pre_processor]) + + +# Views +@error(403) +def error403(code): + return "The parameter you passed has the wrong format" + + +@error(404) +def error404(code): + return "Sorry, this page does not exist" + + +@error(500) +def error500(error): + traceback = error.traceback + if traceback: + print traceback + return base(["An Error occured, please enable debug mode to get more details.", error, + traceback.replace("\n", "<br>") if traceback else "No Traceback"]) + + +@route('/<theme>/<file:re:(.+/)?[^/]+\.min\.[^/]+>') +def server_min(theme, file): + filename = join(THEME_DIR, THEME, theme, file) + if not isfile(filename): + file = file.replace(".min.", ".") + if file.endswith(".js"): + return server_js(theme, file) + else: + return server_static(theme, file) + + +@route('/<theme>/<file:re:.+\.js>') +def server_js(theme, file): + response.headers['Content-Type'] = "text/javascript; charset=UTF-8" + + if "/render/" in file or ".render." in file or True: + response.headers['Expires'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", + time.gmtime(time.time() + 24 * 7 * 60 * 60)) + response.headers['Cache-control'] = "public" + + path = "/".join((theme, file)) + return env.get_template(path).render() + else: + return server_static(theme, file) + + +@route('/<theme>/<file:path>') +def server_static(theme, file): + response.headers['Expires'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", + time.gmtime(time.time() + 24 * 7 * 60 * 60)) + response.headers['Cache-control'] = "public" + + return static_file(file, root=join(THEME_DIR, THEME, theme)) + + +@route('/favicon.ico') +def favicon(): + return static_file("icon.ico", root=join(PYLOAD_DIR, "docs", "resources")) + + +@route('/login', method="GET") +def login(): + if not PYLOAD and SETUP: + redirect("/setup") + else: + return render_to_response("login.html", proc=[pre_processor]) + + +@route('/nopermission') +def nopermission(): + return base([_("You dont have permission to access this page.")]) + + +@route('/login', method='POST') +def login_post(): + user = request.forms.get("username") + password = request.forms.get("password") + + info = PYLOAD.checkAuth(user, password) + + if not info: + return render_to_response("login.html", {"errors": True}, [pre_processor]) + + set_session(request, info) + return redirect("/") + + +@route('/logout') +def logout(): + s = request.environ.get('beaker.session') + s.delete() + return render_to_response("logout.html", proc=[pre_processor]) + + +@route('/') +@route('/home') +@login_required("LIST") +def home(): + try: + res = [toDict(x) for x in PYLOAD.statusDownloads()] + except Exception: + s = request.environ.get('beaker.session') + s.delete() + return redirect("/login") + + for link in res: + if link['status'] == 12: + link['information'] = "%s kB @ %s kB/s" % (link['size'] - link['bleft'], link['speed']) + + return render_to_response("home.html", {"res": res}, [pre_processor]) + + +@route('/queue') +@login_required("LIST") +def queue(): + queue = PYLOAD.getQueue() + + queue.sort(key=attrgetter("order")) + + return render_to_response('queue.html', {'content': queue, 'target': 1}, [pre_processor]) + + +@route('/collector') +@login_required('LIST') +def collector(): + queue = PYLOAD.getCollector() + + queue.sort(key=attrgetter("order")) + + return render_to_response('queue.html', {'content': queue, 'target': 0}, [pre_processor]) + + +@route('/downloads') +@login_required('DOWNLOAD') +def downloads(): + root = PYLOAD.getConfigValue("general", "download_folder") + + if not isdir(root): + return base([_('Download directory not found.')]) + data = { + 'folder': [], + 'files': [] + } + + items = listdir(fs_encode(root)) + + for item in sorted([fs_decode(x) for x in items]): + if isdir(fs_join(root, item)): + folder = { + 'name': item, + 'path': item, + 'files': [] + } + files = listdir(fs_join(root, item)) + for file in sorted([fs_decode(x) for x in files]): + try: + if isfile(fs_join(root, item, file)): + folder['files'].append(file) + except Exception: + pass + + data['folder'].append(folder) + elif isfile(join(root, item)): + data['files'].append(item) + + return render_to_response('downloads.html', {'files': data}, [pre_processor]) + + +@route('/downloads/get/<path:path>') +@login_required("DOWNLOAD") +def get_download(path): + path = unquote(path).decode("utf8") + #@TODO some files can not be downloaded + + root = PYLOAD.getConfigValue("general", "download_folder") + + path = path.replace("..", "") + return static_file(fs_encode(path), fs_encode(root)) + + +@route('/settings') +@login_required('SETTINGS') +def config(): + conf = PYLOAD.getConfig() + plugin = PYLOAD.getPluginConfig() + conf_menu = [] + plugin_menu = [] + + for entry in sorted(conf.keys()): + conf_menu.append((entry, conf[entry].description)) + + last_name = None + for entry in sorted(plugin.keys()): + desc = plugin[entry].description + name, none, type = desc.partition("_") + + if type in PYLOAD.core.pluginManager.TYPES: + if name == last_name or len([a for a, b in plugin.iteritems() if b.description.startswith(name + "_")]) > 1: + desc = name + " (" + type.title() + ")" + else: + desc = name + last_name = name + plugin_menu.append((entry, desc)) + + accs = PYLOAD.getAccounts(False) + + for data in accs: + if data.trafficleft == -1: + data.trafficleft = _("unlimited") + elif not data.trafficleft: + data.trafficleft = _("not available") + else: + data.trafficleft = formatSize(data.trafficleft) + + if data.validuntil == -1: + data.validuntil = _("unlimited") + elif not data.validuntil: + data.validuntil = _("not available") + else: + t = time.localtime(data.validuntil) + data.validuntil = time.strftime("%d.%m.%Y - %H:%M:%S", t) + + try: + data.options['time'] = data.options['time'][0] + except Exception: + data.options['time'] = "0:00-0:00" + + if "limitDL" in data.options: + data.options['limitdl'] = data.options['limitDL'][0] + else: + data.options['limitdl'] = "0" + + return render_to_response('settings.html', + {'conf': {'plugin': plugin_menu, 'general': conf_menu, 'accs': accs}, + 'types': PYLOAD.getAccountTypes()}, + [pre_processor]) + + +@route('/filechooser') +@route('/pathchooser') +@route('/filechooser/<file:path>') +@route('/pathchooser/<path:path>') +@login_required('STATUS') +def path(file="", path=""): + type = "file" if file else "folder" + + path = os.path.normpath(unquotepath(path)) + + if os.path.isfile(path): + oldfile = path + path = os.path.dirname(path) + else: + oldfile = '' + + abs = False + + if os.path.isdir(path): + if os.path.isabs(path): + cwd = os.path.abspath(path) + abs = True + else: + cwd = relpath(path) + else: + cwd = os.getcwd() + + try: + cwd = cwd.encode("utf8") + except Exception: + pass + + cwd = os.path.normpath(os.path.abspath(cwd)) + parentdir = os.path.dirname(cwd) + if not abs: + if os.path.abspath(cwd) == "/": + cwd = relpath(cwd) + else: + cwd = relpath(cwd) + os.path.sep + parentdir = relpath(parentdir) + os.path.sep + + if os.path.abspath(cwd) == "/": + parentdir = "" + + try: + folders = os.listdir(cwd) + except Exception: + folders = [] + + files = [] + + for f in folders: + try: + f = f.decode(getfilesystemencoding()) + data = {'name': f, 'fullpath': join(cwd, f)} + data['sort'] = data['fullpath'].lower() + data['modified'] = datetime.fromtimestamp(int(os.path.getmtime(join(cwd, f)))) + data['ext'] = os.path.splitext(f)[1] + except Exception: + continue + + data['type'] = 'dir' if os.path.isdir(join(cwd, f)) else 'file' + + if os.path.isfile(join(cwd, f)): + data['size'] = os.path.getsize(join(cwd, f)) + + power = 0 + while (data['size'] / 1024) > 0.3: + power += 1 + data['size'] /= 1024. + units = ('', 'K', 'M', 'G', 'T') + data['unit'] = units[power] + 'Byte' + else: + data['size'] = '' + + files.append(data) + + files = sorted(files, key=itemgetter('type', 'sort')) + + return render_to_response('pathchooser.html', + {'cwd': cwd, 'files': files, 'parentdir': parentdir, 'type': type, 'oldfile': oldfile, + 'absolute': abs}, []) + + +@route('/logs') +@route('/logs', method='POST') +@route('/logs/<item>') +@route('/logs/<item>', method='POST') +@login_required('LOGS') +def logs(item=-1): + s = request.environ.get('beaker.session') + + perpage = s.get('perpage', 34) + reversed = s.get('reversed', False) + + warning = "" + conf = PYLOAD.getConfigValue("log", "file_log") + color_template = PYLOAD.getConfigValue("log", "color_template") if PYLOAD.getConfigValue("log", "color_console") else "" + if not conf: + warning = "Warning: File log is disabled, see settings page." + + perpage_p = ((20, 20), (34, 34), (40, 40), (100, 100), (0, 'all')) + fro = None + + if request.environ.get('REQUEST_METHOD', "GET") == "POST": + try: + fro = datetime.strptime(request.forms['from'], '%d.%m.%Y %H:%M:%S') + except Exception: + pass + try: + perpage = int(request.forms['perpage']) + s['perpage'] = perpage + + reversed = bool(request.forms.get('reversed', False)) + s['reversed'] = reversed + except Exception: + pass + + s.save() + + try: + item = int(item) + except Exception: + pass + + log = PYLOAD.getLog() + if not perpage: + item = 1 + + if item < 1 or type(item) is not int: + item = 1 if len(log) - perpage + 1 < 1 else len(log) - perpage + 1 + + if type(fro) is datetime: #: we will search for datetime + item = -1 + + data = [] + counter = 0 + perpagecheck = 0 + for l in log: + counter += 1 + + if counter >= item: + try: + date, time, level, message = l.decode("utf8", "ignore").split(" ", 3) + dtime = datetime.strptime(date + ' ' + time, '%Y-%m-%d %H:%M:%S') + except Exception: + dtime = None + date = '?' + time = ' ' + level = '?' + message = l + if item == -1 and dtime is not None and fro <= dtime: + item = counter #: found our datetime + if item >= 0: + data.append({'line': counter, 'date': date + " " + time, 'level': level, 'message': message}) + perpagecheck += 1 + if fro is None and dtime is not None: #: if fro not set set it to first showed line + fro = dtime + if perpagecheck >= perpage > 0: + break + + if fro is None: #: still not set, empty log? + fro = datetime.now() + if reversed: + data.reverse() + return render_to_response('logs.html', {'warning': warning, 'log': data, 'from': fro.strftime('%d.%m.%Y %H:%M:%S'), + 'reversed': reversed, 'perpage': perpage, 'perpage_p': sorted(perpage_p), + 'iprev': 1 if item - perpage < 1 else item - perpage, + 'inext': (item + perpage) if item + perpage < len(log) else item, + 'color_template': color_template.title()}, + [pre_processor]) + + +@route('/admin') +@route('/admin', method='POST') +@login_required("ADMIN") +def admin(): + # convert to dict + user = dict((name, toDict(y)) for name, y in PYLOAD.getAllUserData().iteritems()) + perms = permlist() + + for data in user.itervalues(): + data['perms'] = {} + get_permission(data['perms'], data['permission']) + data['perms']['admin'] = data['role'] is 0 + + s = request.environ.get('beaker.session') + if request.environ.get('REQUEST_METHOD', "GET") == "POST": + for name in user: + if request.POST.get("%s|admin" % name, False): + user[name]['role'] = 0 + user[name]['perms']['admin'] = True + elif name != s['name']: + user[name]['role'] = 1 + user[name]['perms']['admin'] = False + + # set all perms to false + for perm in perms: + user[name]['perms'][perm] = False + + for perm in request.POST.getall("%s|perms" % name): + user[name]['perms'][perm] = True + + user[name]['permission'] = set_permission(user[name]['perms']) + + PYLOAD.setUserPermission(name, user[name]['permission'], user[name]['role']) + + return render_to_response("admin.html", {"users": user, "permlist": perms}, [pre_processor]) + + +@route('/setup') +def setup(): + return base([_("Run pyload.py -s to access the setup.")]) + + +@route('/info') +def info(): + conf = PYLOAD.getConfigDict() + extra = os.uname() if hasattr(os, "uname") else tuple() + + data = {"python" : sys.version, + "os" : " ".join((os.name, sys.platform) + extra), + "version" : PYLOAD.getServerVersion(), + "folder" : abspath(PYLOAD_DIR), "config": abspath(""), + "download" : abspath(conf['general']['download_folder']['value']), + "freespace": formatSize(PYLOAD.freeSpace()), + "remote" : conf['remote']['port']['value'], + "webif" : conf['webui']['port']['value'], + "language" : conf['general']['language']['value']} + + return render_to_response("info.html", data, [pre_processor]) diff --git a/pyload/webui/app/utils.py b/pyload/webui/app/utils.py new file mode 100644 index 000000000..2753b7feb --- /dev/null +++ b/pyload/webui/app/utils.py @@ -0,0 +1,127 @@ +# -*- coding: utf-8 -*- +# @author: RaNaN, vuolter + +from os.path import join + +from bottle import request, HTTPError, redirect, ServerAdapter + +from pyload.webui import env, THEME + +from pyload.api import has_permission, PERMS, ROLE + + +def render_to_response(file, args={}, proc=[]): + for p in proc: + args.update(p()) + path = "tml/" + file + return env.get_template(path).render(**args) + + +def parse_permissions(session): + perms = dict((x, False) for x in dir(PERMS) if not x.startswith("_")) + perms['ADMIN'] = False + perms['is_admin'] = False + + if not session.get("authenticated", False): + return perms + + if session.get("role") == ROLE.ADMIN: + for k in perms.iterkeys(): + perms[k] = True + + elif session.get("perms"): + p = session.get("perms") + get_permission(perms, p) + + return perms + + +def permlist(): + return [x for x in dir(PERMS) if not x.startswith("_") and x != "ALL"] + + +def get_permission(perms, p): + """Returns a dict with permission key + + :param perms: dictionary + :param p: bits + """ + for name in permlist(): + perms[name] = has_permission(p, getattr(PERMS, name)) + + +def set_permission(perms): + """generates permission bits from dictionary + + :param perms: dict + """ + permission = 0 + for name in dir(PERMS): + if name.startswith("_"): + continue + + if name in perms and perms[name]: + permission |= getattr(PERMS, name) + + return permission + + +def set_session(request, info): + s = request.environ.get('beaker.session') + s['authenticated'] = True + s['user_id'] = info['id'] + s['name'] = info['name'] + s['role'] = info['role'] + s['perms'] = info['permission'] + s['template'] = info['template'] + s.save() + + return s + + +def parse_userdata(session): + return {"name" : session.get("name", "Anonymous"), + "is_admin" : session.get("role", 1) == 0, + "is_authenticated": session.get("authenticated", False)} + + +def login_required(perm=None): + + + def _dec(func): + + + def _view(*args, **kwargs): + s = request.environ.get('beaker.session') + if s.get("name", None) and s.get("authenticated", False): + if perm: + perms = parse_permissions(s) + if perm not in perms or not perms[perm]: + if request.headers.get('X-Requested-With') == 'XMLHttpRequest': + return HTTPError(403, "Forbidden") + else: + return redirect("/nopermission") + + return func(*args, **kwargs) + else: + if request.headers.get('X-Requested-With') == 'XMLHttpRequest': + return HTTPError(403, "Forbidden") + else: + return redirect("/login") + + return _view + + return _dec + + +def toDict(obj): + return {att: getattr(obj, att) for att in obj.__slots__} + + +class CherryPyWSGI(ServerAdapter): + + def run(self, handler): + from wsgiserver import CherryPyWSGIServer + + server = CherryPyWSGIServer((self.host, self.port), handler) + server.start() diff --git a/pyload/webui/filters.py b/pyload/webui/filters.py new file mode 100644 index 000000000..e11944c94 --- /dev/null +++ b/pyload/webui/filters.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- + +import os +from os.path import abspath, commonprefix, join + +quotechar = "::/" + +try: + from os.path import relpath +except Exception: + from posixpath import curdir, sep, pardir + + + def relpath(path, start=curdir): + """Return a relative version of a path""" + if not path: + raise ValueError("no path specified") + start_list = abspath(start).split(sep) + path_list = abspath(path).split(sep) + # Work out how much of the filepath is shared by start and path. + i = len(commonprefix([start_list, path_list])) + rel_list = [pardir] * (len(start_list) - i) + path_list[i:] + if not rel_list: + return curdir + return join(*rel_list) + + +def quotepath(path): + try: + return path.replace("../", quotechar) + except AttributeError: + return path + except Exception: + return "" + + +def unquotepath(path): + try: + return path.replace(quotechar, "../") + except AttributeError: + return path + except Exception: + return "" + + +def path_make_absolute(path): + p = os.path.abspath(path) + if p[-1] == os.path.sep: + return p + else: + return p + os.path.sep + + +def path_make_relative(path): + p = relpath(path) + if p[-1] == os.path.sep: + return p + else: + return p + os.path.sep + + +def truncate(value, n): + if (n - len(value)) < 3: + return value[:n] + "..." + return value + + +def date(date, format): + return date diff --git a/pyload/webui/middlewares.py b/pyload/webui/middlewares.py new file mode 100644 index 000000000..c3f4952db --- /dev/null +++ b/pyload/webui/middlewares.py @@ -0,0 +1,144 @@ +# -*- coding: utf-8 -*- + +import gzip + +try: + from cStringIO import StringIO +except ImportError: + from StringIO import StringIO + + +class StripPathMiddleware(object): + + def __init__(self, app): + self.app = app + + + def __call__(self, e, h): + e['PATH_INFO'] = e['PATH_INFO'].rstrip('/') + return self.app(e, h) + + +class PrefixMiddleware(object): + + def __init__(self, app, prefix="/pyload"): + self.app = app + self.prefix = prefix + + + def __call__(self, e, h): + path = e['PATH_INFO'] + if path.startswith(self.prefix): + e['PATH_INFO'] = path.replace(self.prefix, "", 1) + return self.app(e, h) + +# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) +# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php + +# (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) +# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php + +# WSGI middleware +# Gzip-encodes the response. + + +class GZipMiddleWare(object): + + def __init__(self, application, compress_level=6): + self.application = application + self.compress_level = int(compress_level) + + + def __call__(self, environ, start_response): + if 'gzip' not in environ.get('HTTP_ACCEPT_ENCODING', ''): + # nothing for us to do, so this middleware will + # be a no-op: + return self.application(environ, start_response) + response = GzipResponse(start_response, self.compress_level) + app_iter = self.application(environ, + response.gzip_start_response) + if app_iter is not None: + response.finish_response(app_iter) + + return response.write() + + +def header_value(headers, key): + for header, value in headers: + if key.lower() == header.lower(): + return value + + +def update_header(headers, key, value): + remove_header(headers, key) + headers.append((key, value)) + + +def remove_header(headers, key): + for header, value in headers: + if key.lower() == header.lower(): + headers.remove((header, value)) + break + + +class GzipResponse(object): + + def __init__(self, start_response, compress_level): + self.start_response = start_response + self.compress_level = compress_level + self.buffer = StringIO() + self.compressible = False + self.content_length = None + self.headers = () + + + def gzip_start_response(self, status, headers, exc_info=None): + self.headers = headers + ct = header_value(headers, 'content-type') + ce = header_value(headers, 'content-encoding') + cl = header_value(headers, 'content-length') + if cl: + cl = int(cl) + else: + cl = 201 + self.compressible = False + if ct and (ct.startswith('text/') or ct.startswith('application/')) and 'zip' not in ct and cl > 200: + self.compressible = True + if ce: + self.compressible = False + if self.compressible: + headers.append(('content-encoding', 'gzip')) + remove_header(headers, 'content-length') + self.headers = headers + self.status = status + return self.buffer.write + + + def write(self): + out = self.buffer + out.seek(0) + s = out.getvalue() + out.close() + return [s] + + + def finish_response(self, app_iter): + if self.compressible: + output = gzip.GzipFile(mode='wb', compresslevel=self.compress_level, fileobj=self.buffer) + else: + output = self.buffer + try: + for s in app_iter: + output.write(s) + if self.compressible: + output.close() + finally: + if hasattr(app_iter, 'close'): + try: + app_iter.close() + except Exception: + pass + + content_length = self.buffer.tell() + update_header(self.headers, "Content-Length", str(content_length)) + self.start_response(self.status, self.headers) diff --git a/pyload/webui/servers/lighttpd_default.conf b/pyload/webui/servers/lighttpd_default.conf new file mode 100644 index 000000000..9ccd264db --- /dev/null +++ b/pyload/webui/servers/lighttpd_default.conf @@ -0,0 +1,154 @@ +# lighttpd configuration file +# +# use it as a base for lighttpd 1.0.0 and above +# +# $Id: lighttpd.conf,v 1.7 2004/11/03 22:26:05 weigon Exp $ + +############ Options you really have to take care of #################### + +## modules to load +# at least mod_access and mod_accesslog should be loaded +# all other module should only be loaded if really neccesary +# - saves some time +# - saves memory +server.modules = ( + "mod_rewrite", + "mod_redirect", + "mod_alias", + "mod_access", +# "mod_trigger_b4_dl", +# "mod_auth", +# "mod_status", +# "mod_setenv", + "mod_fastcgi", +# "mod_proxy", +# "mod_simple_vhost", +# "mod_evhost", +# "mod_userdir", +# "mod_cgi", +# "mod_compress", +# "mod_ssi", +# "mod_usertrack", +# "mod_expire", +# "mod_secdownload", +# "mod_rrdtool", +# "mod_accesslog" +) + +## A static document-root. For virtual hosting take a look at the +## mod_simple_vhost module. +server.document-root = "%(path)" + +## where to send error-messages to +server.errorlog = "%(path)/error.log" + +# files to check for if .../ is requested +index-file.names = ( "index.php", "index.html", + "index.htm", "default.htm" ) + +## set the event-handler (read the performance section in the manual) +# server.event-handler = "freebsd-kqueue" #: needed on OS X + +# mimetype mapping +mimetype.assign = ( + ".pdf" => "application/pdf", + ".sig" => "application/pgp-signature", + ".spl" => "application/futuresplash", + ".class" => "application/octet-stream", + ".ps" => "application/postscript", + ".torrent" => "application/x-bittorrent", + ".dvi" => "application/x-dvi", + ".gz" => "application/x-gzip", + ".pac" => "application/x-ns-proxy-autoconfig", + ".swf" => "application/x-shockwave-flash", + ".tar.gz" => "application/x-tgz", + ".tgz" => "application/x-tgz", + ".tar" => "application/x-tar", + ".zip" => "application/zip", + ".mp3" => "audio/mpeg", + ".m3u" => "audio/x-mpegurl", + ".wma" => "audio/x-ms-wma", + ".wax" => "audio/x-ms-wax", + ".ogg" => "application/ogg", + ".wav" => "audio/x-wav", + ".gif" => "image/gif", + ".jar" => "application/x-java-archive", + ".jpg" => "image/jpeg", + ".jpeg" => "image/jpeg", + ".png" => "image/png", + ".xbm" => "image/x-xbitmap", + ".xpm" => "image/x-xpixmap", + ".xwd" => "image/x-xwindowdump", + ".css" => "text/css", + ".html" => "text/html", + ".htm" => "text/html", + ".js" => "text/javascript", + ".asc" => "text/plain", + ".c" => "text/plain", + ".cpp" => "text/plain", + ".log" => "text/plain", + ".conf" => "text/plain", + ".text" => "text/plain", + ".txt" => "text/plain", + ".dtd" => "text/xml", + ".xml" => "text/xml", + ".mpeg" => "video/mpeg", + ".mpg" => "video/mpeg", + ".mov" => "video/quicktime", + ".qt" => "video/quicktime", + ".avi" => "video/x-msvideo", + ".asf" => "video/x-ms-asf", + ".asx" => "video/x-ms-asf", + ".wmv" => "video/x-ms-wmv", + ".bz2" => "application/x-bzip", + ".tbz" => "application/x-bzip-compressed-tar", + ".tar.bz2" => "application/x-bzip-compressed-tar", + # default mime type + "" => "application/octet-stream", +) + +# Use the "Content-Type" extended attribute to obtain mime type if possible +# mimetype.use-xattr = "enable" + +#### accesslog module +accesslog.filename = "%(path)/access.log" + +url.access-deny = ( "~", ".inc" ) + +$HTTP['url'] =~ "\.pdf$" { + server.range-requests = "disable" +} + +static-file.exclude-extensions = ( ".php", ".pl", ".fcgi" ) + +server.pid-file = "%(path)/lighttpd.pid" +server.bind = "%(host)" +server.port = %(port) + +# server.document-root = "/home/user/public_html" + +fastcgi.server = ( + "/pyload.fcgi" => ( + "main" => ( + "host" => "127.0.0.1", + "port" => 9295, + "check-local" => "disable", + "docroot" => "/", + ) + ), +) + +alias.url = ( + "/media/" => "%(media)/", + "/admin/media/" => "/usr/lib/python%(version)/site-packages/django/contrib/admin/media/", +) + +url.rewrite-once = ( + "^(/media.*)$" => "$1", + "^(/admin/media.*)$" => "$1", + "^/favicon\.ico$" => "/media/img/favicon.ico", + "^(/pyload.fcgi.*)$" => "$1", + "^(/.*)$" => "/pyload.fcgi$1", +) + +%(ssl) diff --git a/pyload/webui/servers/nginx_default.conf b/pyload/webui/servers/nginx_default.conf new file mode 100644 index 000000000..c1cfcafca --- /dev/null +++ b/pyload/webui/servers/nginx_default.conf @@ -0,0 +1,87 @@ +daemon off; +pid %(path)/nginx.pid; +worker_processes 2; + +error_log %(path)/error.log info; + +events { + worker_connections 1024; + use epoll; +} + +http { + include /etc/nginx/conf/mime.types; + default_type application/octet-stream; + + %(ssl) + + log_format main + '$remote_addr - $remote_user [$time_local] ' + '"$request" $status $bytes_sent ' + '"$http_referer" "$http_user_agent" ' + '"$gzip_ratio"'; + + error_log %(path)/error.log info; + + client_header_timeout 10m; + client_body_timeout 10m; + send_timeout 10m; + + client_body_temp_path %(path)/client_body_temp; + proxy_temp_path %(path)/proxy_temp; + fastcgi_temp_path %(path)/fastcgi_temp; + + + connection_pool_size 256; + client_header_buffer_size 1k; + large_client_header_buffers 4 2k; + request_pool_size 4k; + + gzip on; + gzip_min_length 1100; + gzip_buffers 4 8k; + gzip_types text/plain; + + output_buffers 1 32k; + postpone_output 1460; + + sendfile on; + tcp_nopush on; + tcp_nodelay on; + + keepalive_timeout 75 20; + + ignore_invalid_headers on; + + server { + listen %(port); + server_name %(host); + # site_media - folder in uri for static files + location ^~ /media { + root %(media)/..; + } + location ^~ /admin/media { + root /usr/lib/python%(version)/site-packages/django/contrib; + } +location ~* ^.+\.(jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|doc|xls|exe|pdf|ppt|txt|tar|mid|midi|wav|bmp|rtf|js|mov) { + access_log off; + expires 30d; +} + location / { + # host and port to fastcgi server + fastcgi_pass 127.0.0.1:9295; + fastcgi_param PATH_INFO $fastcgi_script_name; + fastcgi_param REQUEST_METHOD $request_method; + fastcgi_param QUERY_STRING $query_string; + fastcgi_param CONTENT_TYPE $content_type; + fastcgi_param CONTENT_LENGTH $content_length; + fastcgi_param SERVER_NAME $server_name; + fastcgi_param SERVER_PORT $server_port; + fastcgi_param SERVER_PROTOCOL $server_protocol; + fastcgi_pass_header Authorization; + fastcgi_intercept_errors off; + } + access_log %(path)/access.log main; + error_log %(path)/error.log; + } + } diff --git a/pyload/webui/themes/Dark/css/MooDialog.css b/pyload/webui/themes/Dark/css/MooDialog.css new file mode 100644 index 000000000..3c070efa9 --- /dev/null +++ b/pyload/webui/themes/Dark/css/MooDialog.css @@ -0,0 +1,94 @@ +/* Created by Arian Stolwijk <http://www.aryweb.nl> */ + +.MooDialog { +/* position: fixed;*/ + margin: 0 auto 0 -350px; + width:600px; + padding:14px; + left:50%; + top: 100px; + color:white; + + position: absolute; + left: 50%; + z-index: 50000; + + background: url(../img/dark-bg.jpg); + color: black; + border-radius: 7px; + -moz-border-radius: 7px; + -webkit-border-radius: 7px; + border-radius: 7px; + -moz-box-shadow: 1px 1px 5px rgba(0,0,0,0.8); + -webkit-box-shadow: 1px 1px 5px rgba(0,0,0,0.8); + box-shadow: 1px 1px 5px rgba(0,0,0,0.8); +} + +.MooDialogTitle { + padding-top: 30px; +} + +.MooDialog .title { + position: absolute; + top: 0; + left: 0; + right: 0; + padding: 3px 20px; + background: #b7c4dc; + border-bottom: 1px solid #a1aec5; + font-weight: bold; + text-shadow: 1px 1px 0 #fff; + color: black; + border-radius: 7px; + -moz-border-radius: 7px; + -webkit-border-radius: 7px; +} + +.MooDialog .close { + background: url(../lib/MooTools/MooDialog/css/dialog-close.png) no-repeat; + width: 16px; + height: 16px; + display: block; + cursor: pointer; + top: -5px; + left: -5px; + position: absolute; +} + +.MooDialog .buttons { + text-align: right; + margin: 0; + padding: 0; + border: 0; + background: none; +} + +.MooDialog .iframe { + width: 100%; + height: 100%; +} + +.MooDialog .textInput { + width: 200px; + float: left; + color:white; +} + +.MooDialog .MooDialogAlert, +.MooDialog .MooDialogConfirm, +.MooDialog .MooDialogPrompt, +.MooDialog .MooDialogError { + background: url(../lib/MooTools/MooDialog/css/dialog-warning.png) no-repeat; + padding-left: 40px; + min-height: 40px; + color:white; +} + +.MooDialog .MooDialogConfirm, +.MooDialog .MooDialogPromt { + background: url(../lib/MooTools/MooDialog/css/dialog-question.png) no-repeat; +} + +.MooDialog .MooDialogError { + background: url(../lib/MooTools/MooDialog/css/dialog-error.png) no-repeat; +} diff --git a/pyload/webui/themes/Dark/css/base.css b/pyload/webui/themes/Dark/css/base.css new file mode 100644 index 000000000..80dc363e1 --- /dev/null +++ b/pyload/webui/themes/Dark/css/base.css @@ -0,0 +1,962 @@ +.hidden { + display:none; +} +.leftalign { + text-align:left; +} +.centeralign { + text-align:center; +} +.rightalign { + text-align:right; +} + + +.dokuwiki div.plugin_translation ul li a.wikilink1:link, .dokuwiki div.plugin_translation ul li a.wikilink1:hover, .dokuwiki div.plugin_translation ul li a.wikilink1:active, .dokuwiki div.plugin_translation ul li a.wikilink1:visited { + background-color:#000080; + color:#fff !important; + text-decoration:none; + padding:0 0.2em; + margin:0.1em 0.2em; + border:none !important; +} +.dokuwiki div.plugin_translation ul li a.wikilink2:link, .dokuwiki div.plugin_translation ul li a.wikilink2:hover, .dokuwiki div.plugin_translation ul li a.wikilink2:active, .dokuwiki div.plugin_translation ul li a.wikilink2:visited { + background-color:#808080; + color:#fff !important; + text-decoration:none; + padding:0 0.2em; + margin:0.1em 0.2em; + border:none !important; +} + +.dokuwiki div.plugin_translation ul li a:hover img { + opacity:1.0; + height:15px; +} + +body { + margin:0; + padding:0; + background-image: url(../img/dark-bg.jpg); + color:white; + font-size:12px; + font-family:Verdana, Helvetica, "Lucida Grande", Lucida, Arial, sans-serif; + font-family:sans-serif; + font-size:99, 96%; + font-size-adjust:none; + font-style:normal; + font-variant:normal; + font-weight:normal; + line-height:normal; +} +hr { + border-width:0; + border-bottom:1px #aaa dotted; +} +img { + border:none; +} +form { + margin:0px; + padding:0px; + border:none; + display:inline; + background:transparent; +} +ul li { + margin:5px; +} +textarea { + font-family:monospace; +} +table { + margin:0.5em 0; + border-collapse:collapse; +} +td { + padding:0.25em; + border:1pt solid #ADB9CC; +} +a { + color:#3465a4; + text-decoration:none; +} +a:hover { + text-decoration:underline; +} + +option { + border:0 none #fff; +} +strong.highlight { + background-color:#fc9; + padding:1pt; +} +#pagebottom { + clear:both; +} +hr { + height:1px; + color:#c0c0c0; + background-color:#c0c0c0; + border:none; + margin:.2em 0 .2em 0; +} + +.invisible { + margin:0px; + border:0px; + padding:0px; + height:0px; + visibility:hidden; +} +.left { + float:left !important; +} +.right { + float:right !important; +} +.center { + text-align:center; +} +div#body-wrapper { + padding:40px 40px 10px 40px; + font-size:127%; +} +div#content { + margin-top:-20px; + padding:0; + font-size:14px; + color:white; + line-height:1.5em; +} +h1, h2, h3, h4, h5, h6 { + background:transparent none repeat scroll 0 0; + border-bottom:1px solid #aaa; + color:white; + font-weight:normal; + margin:0; + padding:0; + padding-bottom:0.17em; + padding-top:0.5em; +} +h1 { + font-size:188%; + line-height:1.2em; + margin-bottom:0.1em; + padding-bottom:0; +} +h2 { + font-size:150%; +} +h3, h4, h5, h6 { + border-bottom:none; + font-weight:bold; +} +h3 { + font-size:132%; +} +h4 { + font-size:116%; +} +h5 { + font-size:100%; +} +h6 { + font-size:80%; +} +ul#page-actions, ul#page-actions-more { + float:right; + margin:10px 10px 0 10px; + padding:6px; + color:white; + background-color:#202020; + list-style-type:none; + white-space: nowrap; + border-radius:5px; + -moz-border-radius:5px; + border:1px solid grey; +} +ul#user-actions { + padding:5px; + margin:0; + display:inline; + color:white; + background-color:#202020; + list-style-type:none; + -moz-border-radius:3px; + border-radius:3px; +} +ul#page-actions li, ul#user-actions li, ul#page-actions-more li { + display:inline; +} +ul#page-actions a, ul#user-actions a, ul#page-actions-more a { + text-decoration:none; + color:white; + display:inline; + margin:0 3px; + padding:2px 0px 2px 18px; +} +ul#page-actions a:hover, ul#page-actions a:focus, ul#user-actions a:hover, ul#user-actions a:focus { + /*text-decoration:underline;*/ +} +/***************************/ +ul#page-actions2 { + float:left; + margin:10px 10px 0 10px; + padding:6px; + color:white; + background-color:#202020; + list-style-type:none; + border-radius:5px; + -moz-border-radius:5px; + border:1px solid grey; +} +ul#user-actions2 { + padding:5px; + margin:0; + display:inline; + color:white; + background-color:#202020; + list-style-type:none; + border-radius:3px; + -moz-border-radius:3px; +} +ul#page-actions2 li, ul#user-actions2 li { + display:inline; +} +ul#page-actions2 a, ul#user-actions2 a { + text-decoration:none; + color:white; + display:inline; + margin:0 3px; + padding:2px 0px 2px 18px; +} +ul#page-actions2 a:hover, ul#page-actions2 a:focus, ul#user-actions2 a:hover, ul#user-actions2 a:focus, +ul#page-actions-more a:hover, ul#page-actions-more a:focus{ + color: #4e7bb4; +} +/****************************/ +.hidden { + display:none; +} + +a.logout { + background:transparent url(../img/user-actions-logout.png) 0px 1px no-repeat; +} + +a.info { + background:transparent url(../img/user-info.png) 0px 1px no-repeat; +} + +a.admin { + background:transparent url(../img/user-actions-admin.png) 0px 1px no-repeat; +} +a.profile { + background:transparent url(../img/user-actions-profile.png) 0px 1px no-repeat; +} +a.create, a.edit { + background:transparent url(../img/page-tools-edit.png) 0px 1px no-repeat; +} +a.source, a.show { + background:transparent url(../img/page-tools-source.png) 0px 1px no-repeat; +} +a.revisions { + background:transparent url(../img/page-tools-revisions.png) 0px 1px no-repeat; +} +a.subscribe, a.unsubscribe { + background:transparent url(../img/page-tools-subscribe.png) 0px 1px no-repeat; +} +a.backlink { + background:transparent url(../img/page-tools-backlinks.png) 0px 1px no-repeat; +} +a.play { + background:transparent url(../img/control_play.png) 0px 1px no-repeat; +} +.time { + background:transparent url(../img/status_None.png) 0px 1px no-repeat; + padding: 2px 0px 2px 18px; + margin: 0px 3px; +} +.reconnect { + background:transparent url(../img/reconnect.png) 0px 1px no-repeat; + padding: 2px 0px 2px 18px; + margin: 0px 3px; +} +a.play:hover { + background:transparent url(../img/control_play_blue.png) 0px 1px no-repeat; +} +a.cancel { + background:transparent url(../img/control_cancel.png) 0px 1px no-repeat; +} +a.cancel:hover { + background:transparent url(../img/control_cancel_blue.png) 0px 1px no-repeat; +} +a.pause { + background:transparent url(../img/control_pause.png) 0px 1px no-repeat; +} +a.pause:hover { + background:transparent url(../img/control_pause_blue.png) 0px 1px no-repeat; + font-weight: bold; +} +a.stop { + background:transparent url(../img/control_stop.png) 0px 1px no-repeat; +} +a.stop:hover { + background:transparent url(../img/control_stop_blue.png) 0px 1px no-repeat; +} +a.add { + background:transparent url(../img/control_add.png) 0px 1px no-repeat; +} +a.add:hover { + background:transparent url(../img/control_add_blue.png) 0px 1px no-repeat; +} +a.cog { + background:transparent url(../img/cog.png) 0px 1px no-repeat; +} +#head-panel { + background:#525252 url(../img/head_bg1.png) bottom left repeat-x; +} +#head-panel h1 { + display:none; + margin:0; + text-decoration:none; + padding-top:0.8em; + padding-left:3.3em; + font-size:2.6em; + color:#eeeeec; +} +#head-panel #head-logo { + float:left; + margin:5px 0 -15px 5px; + padding:0; + overflow:visible; +} +#head-menu { + background:transparent url(../img/tabs-border-bottom.png) 0 100% repeat-x; + width:100%; + float:left; + margin:0; + padding:0; + padding-top:0.8em; +} +#head-menu ul { + list-style:none; + margin:0 1em 0 2em; +} +#head-menu ul li { + float:left; + margin:0; + margin-left:0.3em; + font-size:14px; + margin-bottom:4px; +} +#head-menu ul li.selected, #head-menu ul li:hover { + margin-bottom:0px; +} +#head-menu ul li a img { + height:22px; + width:22px; + vertical-align:middle; +} +#head-menu ul li a, #head-menu ul li a:link { + float:left; + text-decoration:none; + color:white; + background: url(../img/dark-bg.jpg) 0 100% repeat-x; + padding:3px 7px 3px 7px; + border:2px solid #ccc; + border-bottom:0px solid transparent; + padding-bottom:3px; + -moz-border-radius:5px; + border-radius:5px; +} +#head-menu ul li a:hover, #head-menu ul li a:focus { + color:#3465a4; + background-image: url(../img/dark-bg.jpg); + padding-bottom:7px; + border-bottom:0px none transparent; + outline:none; + border-bottom-left-radius: 0px; + border-bottom-right-radius: 0px; + -moz-border-radius-bottomright:0px; + -moz-border-radius-bottomleft:0px; +} +#head-menu ul li a:focus { + margin-bottom:-4px; +} +#head-menu ul li.selected a { + color:white; + background-image: url(../img/dark-bg.jpg); + padding-bottom:7px; + border-bottom:0px none transparent; + border-bottom-left-radius: 0px; + border-bottom-right-radius: 0px; + -moz-border-radius-bottomright:0px; + -moz-border-radius-bottomleft:0px; +} +#head-menu ul li.selected a:hover, #head-menu ul li.selected a:focus { + color:#3465a4; +} +div#head-search-and-login { + float:right; + margin:0 1em 0 0; + background-color:#222; + padding:7px 7px 5px 5px; + color:white; + white-space: nowrap; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + -moz-border-radius-bottomright:6px; + -moz-border-radius-bottomleft:6px; + border-right:1px solid grey; + border-left:1px solid grey; + border-bottom:1px solid grey; +} +div#head-search-and-login form { + display:inline; + padding:0 3px; +} +div#head-search-and-login form input { + border:2px solid #888; + background:#eee; + font-size:14px; + padding:2px; + border-radius:3px; + -moz-border-radius:3px; +} +div#head-search-and-login form input:focus { + background:#fff; +} +#head-search { + font-size:14px; +} +#head-username, #head-password { + width:80px; + font-size:14px; +} +#pageinfo { + clear:both; + color:#888; + padding:0.6em 0; + margin:0; +} +#foot { + font-style:normal; + color:#888; + text-align:center; +} +#foot a { + color:#aaf; +} +#foot img { + vertical-align:middle; +} +div.toc { + border:1px dotted #888; + background:#f0f0f0; + margin:1em 0 1em 1em; + float:right; + font-size:95%; +} +div.toc .tocheader { + font-weight:bold; + margin:0.5em 1em; +} +div.toc ol { + margin:1em 0.5em 1em 1em; + padding:0; +} +div.toc ol li { + margin:0; + padding:0; + margin-left:1em; +} +div.toc ol ol { + margin:0.5em 0.5em 0.5em 1em; + padding:0; +} +div.recentchanges table { + clear:both; +} +div#editor-help { + font-size:90%; + border:1px dotted #888; + padding:0ex 1ex 1ex 1ex; + background:#f7f6f2; +} +div#preview { + margin-top:1em; +} +label.block { + display:block; + text-align:right; + font-weight:bold; +} +label.simple { + display:block; + text-align:left; + font-weight:normal; +} +label.block input.edit { + width:50%; +} +/*fieldset { + width:300px; + text-align:center; + padding:0.5em; + margin:auto; +} +*/ +div.editor { + margin:0 0 0 0; +} +table { + margin:0.5em 0; + border-collapse:collapse; +} +td { + padding:0.25em; + border:1pt solid #ADB9CC; +} +td p { + margin:0; + padding:0; +} +.u { + text-decoration:underline; +} +.footnotes ul { + padding:0 2em; + margin:0 0 1em; +} +.footnotes li { + list-style:none; +} +.userpref table, .userpref td { + border:none; +} +#message { + clear:both; + padding:5px 10px; + background-color:#eee; + border-bottom:2px solid #ccc; +} +#message p { + margin:5px 0; + padding:0; + font-weight:bold; +} +#message div.buttons { + font-weight:normal; +} +.diff { + width:99%; +} +.diff-title { + background-color:#C0C0C0; +} +.searchresult dd span { + font-weight:bold; +} +.boxtext { + font-family:tahoma, arial, sans-serif; + font-size:11px; + color:#000; + float:none; + padding:3px 0 0 10px; +} +.statusbutton { + width:32px; + height:32px; + float:left; + margin-left:-32px; + margin-right:5px; + opacity:0; + cursor:pointer +} +.dlsize { + float:left; + padding-right: 8px; +} +.dlspeed { + float:left; + padding-right: 8px; +} +.package { + margin-bottom: 10px; +} +.packagename { + font-weight: bold; +} + +.child { + margin-left: 20px; +} +.child_status { + margin-right: 10px; +} +.child_secrow { + font-size: 10px; +} + +.header, .header th { + text-align: left; + font-weight: normal; + background-color:#202020; + border-top:1px solid grey; + border-bottom:1px solid grey; + -moz-border-radius:5px; + border-radius:5px; +} +.progress_bar { + background: #0C0; + height: 5px; + +} + +.queue { + border: none +} + +.queue tr td { + border: none +} + +.header, .header th{ + text-align: left; + font-weight: normal; +} + + +.clearer +{ + clear: both; + height: 1px; +} + +.left +{ + float: left; +} + +.right +{ + float: right; +} + + +.setfield +{ + display: table-cell; +} + +ul.tabs li a +{ + padding: 5px 16px 4px 15px; + border: none; + font-weight: bold; + + border-radius: 5px 5px 0 0; + -moz-border-radius: 5px 5px 0 0; + +} + + +#tabs span +{ + display: none; +} + +#tabs span.selected +{ + display: inline; +} + +#tabsback +{ + background-color: #525252; + margin: 2px 0 0; + padding: 6px 4px 1px 4px; + + border-top-right-radius: 30px; + border-top-left-radius: 3px; + -moz-border-radius-topright: 30px; + -moz-border-radius-topleft: 3px; +} +ul.tabs +{ + list-style-type: none; + margin:0; + padding: 0 40px 0 0; +} + +ul.tabs li +{ + display: inline; + margin-left: 8px; +} + +ul.tabs li a +{ + color: white; + background-color: #202020; + border: 1px solid grey; + border-bottom:none; + margin: 0; + text-decoration: none; + + outline: 0; + + padding: 5px 16px 4px 15px; + font-weight: bold; + + border-radius: 5px 5px 0 0; + -moz-border-radius: 5px 5px 0 0; + +} + +ul.tabs li a.selected, ul.tabs li a:hover +{ + color: #3465a4; + background-color: white; + + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; + -moz-border-radius-bottomright: 0; + -moz-border-radius-bottomleft: 0; +} + +ul.tabs li a:hover +{ + background-color: #202020; +} + +ul.tabs li a.selected +{ + font-weight: bold; + background-color: #525252; + padding-bottom: 5px; + color: white; +} + + +#tabs-body { + position: relative; + overflow: hidden; +} + + +span.tabContent +{ + border: 2px solid #525252; + margin: 0; + padding: 0; + padding-bottom: 10px; +} + +#tabs-body > span { + display: none; +} + +#tabs-body > span.active { + display: block; +} + +.hide +{ + display: none; +} + +.settable +{ + color:white; + margin: 20px; + border: none; +} +.settable td +{ + border: none; + margin: 0; + padding: 5px; +} + +.settable th{ + padding-bottom: 8px; +} + +.settable.wide td , .settable.wide th { + padding-left: 15px; + padding-right: 15px; +} + +.settable input { +background-color:#202020; +color:white; +} +.settable select { +background-color:#202020; +color:white; +} + +ul.nav { + margin: -30px 0 0; + padding: 0; + list-style: none; + position: absolute; +} + + +ul.nav li { + position: relative; + float: left; + padding: 5px; +} + +ul.nav > li a { + background: #202020; + -moz-border-radius: 4px 4px 4px 4px; + border: 1px solid grey; + border-bottom: medium none; + color: white; +} + +ul.nav ul { + position: absolute; + top: 26px; + left: 10px; + margin: 0; + padding: 0; + list-style: none; + border: 1px solid #AAA; + background: #202020; + -webkit-box-shadow: 1px 1px 5px #AAA; + -moz-box-shadow: 1px 1px 5px #AAA; + box-shadow: 1px 1px 5px #AAA; + cursor: pointer; +} + +ul.nav .open { + display: block; +} + +ul.nav .close { + display: none; +} + +ul.nav ul li { + float: none; + padding: 0; +} + +ul.nav ul li a { + width: 130px; + background: #f1f1f1; + padding: 3px; + display: block; + font-weight: normal; +} + +ul.nav ul li a:hover { + background: #CDCDCD; +} + +ul.nav ul ul { + left: 137px; + top: 0; +} + +.purr-wrapper{ + margin:10px; +} + +/*Purr alert styles*/ + +.purr-alert{ + margin-bottom:10px; + padding:10px; + background:#000; + font-size:13px; + font-weight:bold; + color:#FFF; + -moz-border-radius:5px; + -webkit-border-radius:5px; + /*-moz-box-shadow: 0 0 10px rgba(255,255,0,.25);*/ + width:300px; +} +.purr-alert.error{ + color:#F55; + padding-left:30px; + background:url(../img/error.png) no-repeat #000 7px 10px; + width:280px; +} +.purr-alert.success{ + color:#5F5; + padding-left:30px; + background:url(../img/success.png) no-repeat #000 7px 10px; + width:280px; +} +.purr-alert.notice{ + color:#99F; + padding-left:30px; + background:url(../img/notice.png) no-repeat #000 7px 10px; + width:280px; +} + +table.system { + border: none; + margin-left: 10px; +} + +table.system td { + border: none +} + +table.system tr > td:first-child { + font-weight: bold; + padding-right: 10px; +} + +#foot { +color:white; +} + +#login_table { +margin-left:auto; +margin-right:auto; +} + +#login_table td { +padding:5px; +border:1px solid grey; +} + +#login_table input[type=text], #login_table input[type=password] { +width:120px; +background-color:transparent; +-moz-opacity: 0.10; +color:white; +border: 1px solid grey; +-moz-border-radius: 2px; +-webkit-border-radius: 2px; +border-radius: 18px; +padding-left:5px; +padding-right:5px; +} + +#login_table input[type=text]:focus, #login_table input[type=password]:focus { +border:1px solid #3465a4; +} + +#login_table input[type=submit] { +background-color:transparent; +color:white; +border: 1px solid grey; +-moz-border-radius: 2px; +-webkit-border-radius: 2px; +border-radius: 18px; +} + +#login_table input[type=submit]:hover { +border:1px solid #3465a4; +}
\ No newline at end of file diff --git a/pyload/webui/themes/Dark/css/log.css b/pyload/webui/themes/Dark/css/log.css new file mode 100644 index 000000000..3362e9381 --- /dev/null +++ b/pyload/webui/themes/Dark/css/log.css @@ -0,0 +1,75 @@ + +html, body, #content +{ + height: 100%; +} +#body-wrapper +{ + height: 70%; +} +.logdiv +{ + height: 90%; + width: 100%; + overflow: auto; + border: 2px solid #CCC; + outline: 1px solid #666; + background-color: #FFE; + margin-right: auto; + margin-left: auto; + background-color:#202020; + color:white; +} +.logform +{ + display: table; + margin: 0 auto 0 auto; + padding-top: 5px; + color: white; +} +.logtable +{ + + margin: 0px; +} +.logtable td +{ + border: none; + white-space: nowrap; + + + font-family: monospace; + font-size: 16px; + margin: 0px; + padding: 0px 10px 0px 10px; + line-height: 110%; +} +td.logline +{ + background-color: #202020; + text-align:right; + padding: 0px 5px 0px 5px; +} +td.loglevel +{ + text-align:right; +} +.logperpage +{ + float: right; + padding-bottom: 8px; +} +.logpaginator +{ + float: left; + padding-top: 5px; +} +.logpaginator a +{ + padding: 0px 8px 0px 8px; +} +.logwarn +{ + text-align: center; + color: red; +}
\ No newline at end of file diff --git a/pyload/webui/themes/Dark/css/pathchooser.css b/pyload/webui/themes/Dark/css/pathchooser.css new file mode 100644 index 000000000..204812aa1 --- /dev/null +++ b/pyload/webui/themes/Dark/css/pathchooser.css @@ -0,0 +1,68 @@ +table { + width: 90%; + border: 1px dotted #888888; + font-family: sans-serif; + font-size: 10pt; +} + +th { + background-color: #525252; + color: #E0E0E0; +} + +table, tr, td { + background-color: #F0F0F0; +} + +a, a:visited { + text-decoration: none; + font-weight: bold; +} + +#paths { + width: 90%; + text-align: left; +} + +.file_directory { + color: #c0c0c0; +} +.path_directory { + color: #3c3c3c; +} +.file_file { + color: #3c3c3c; +} +.path_file { + color: #c0c0c0; +} + +.parentdir { + color: #000000; + font-size: 10pt; +} +.name { + text-align: left; +} +.size { + text-align: right; +} +.type { + text-align: left; +} +.mtime { + text-align: center; +} + +.path_abs_rel { + color: #3c3c3c; + text-decoration: none; + font-weight: bold; + font-family: sans-serif; + font-size: 10pt; +} + +.path_abs_rel a { + color: #3c3c3c; + font-style: italic; +} diff --git a/pyload/webui/themes/Dark/css/window.css b/pyload/webui/themes/Dark/css/window.css new file mode 100644 index 000000000..1bcc58e6c --- /dev/null +++ b/pyload/webui/themes/Dark/css/window.css @@ -0,0 +1,92 @@ +/* ----------- stylized ----------- */ +.window_table td { +border:none; +text-align:left; +} +#add_box { +background-image: url(../img/dark-bg.jpg); +color:white; +} +#pack_box { +background-image: url(../img/dark-bg.jpg); +color:white; +} +.window_box h1{ + font-size:14px; + font-weight:bold; + margin-bottom:8px; +} +.window_box p{ + font-size:11px; + color:white; + margin-bottom:20px; + border-bottom:solid 1px #b7ddf2; + padding-bottom:10px; +} +.window_box label{ /*Linke Seite*/ + display:block; + font-weight:bold; + text-align:right; + width:240px; + float:left; + color:white; +} +.window_box .small{ + color:grey; + display:block; + font-size:11px; + font-weight:normal; + text-align:right; + width:240px; +} +.window_box select, .window_box input{ + float:left; + font-size:12px; + padding:4px 2px; + border:solid 1px #aacfe4; + width:300px; + margin:2px 0 20px 10px; + background-color:#202020; + color:white; +} +.window_box .cont{ + float:left; + font-size:12px; + padding: 0px 10px 15px 0px; + width:300px; + margin:0px 0px 0px 10px; + color:white; +} +.window_box .cont input{ + float: none; + margin: 0px 15px 0px 1px; + color:white; +} +.window_box textarea{ + float:left; + font-size:12px; + padding:4px 2px; + border:solid 1px #aacfe4; + width:300px; + margin:2px 0 20px 10px; + background-color:#202020; + color:white; +} +.window_box button, .styled_button{ + clear:both; + margin-left:150px; + width:125px; + height:31px; + background:#666666 url(../img/button.png) no-repeat; + text-align:center; + line-height:31px; + color:#FFFFFF; + font-size:11px; + font-weight:bold; + border: 0px; +} + +.styled_button { + margin-left: 15px; + cursor: pointer; +} diff --git a/pyload/webui/themes/Dark/img/add_folder.png b/pyload/webui/themes/Dark/img/add_folder.png Binary files differnew file mode 100644 index 000000000..8acbc411b --- /dev/null +++ b/pyload/webui/themes/Dark/img/add_folder.png diff --git a/pyload/webui/themes/Dark/img/ajax-loader.gif b/pyload/webui/themes/Dark/img/ajax-loader.gif Binary files differnew file mode 100644 index 000000000..2fd8e0737 --- /dev/null +++ b/pyload/webui/themes/Dark/img/ajax-loader.gif diff --git a/pyload/webui/themes/Dark/img/arrow_refresh.png b/pyload/webui/themes/Dark/img/arrow_refresh.png Binary files differnew file mode 100644 index 000000000..0de26566d --- /dev/null +++ b/pyload/webui/themes/Dark/img/arrow_refresh.png diff --git a/pyload/webui/themes/Dark/img/arrow_right.png b/pyload/webui/themes/Dark/img/arrow_right.png Binary files differnew file mode 100644 index 000000000..b1a181923 --- /dev/null +++ b/pyload/webui/themes/Dark/img/arrow_right.png diff --git a/pyload/webui/themes/Dark/img/big_button.gif b/pyload/webui/themes/Dark/img/big_button.gif Binary files differnew file mode 100644 index 000000000..7680490ea --- /dev/null +++ b/pyload/webui/themes/Dark/img/big_button.gif diff --git a/pyload/webui/themes/Dark/img/big_button_over.gif b/pyload/webui/themes/Dark/img/big_button_over.gif Binary files differnew file mode 100644 index 000000000..2e3ee10d2 --- /dev/null +++ b/pyload/webui/themes/Dark/img/big_button_over.gif diff --git a/pyload/webui/themes/Dark/img/body.png b/pyload/webui/themes/Dark/img/body.png Binary files differnew file mode 100644 index 000000000..7ff1043e0 --- /dev/null +++ b/pyload/webui/themes/Dark/img/body.png diff --git a/pyload/webui/themes/Dark/img/button.png b/pyload/webui/themes/Dark/img/button.png Binary files differnew file mode 100644 index 000000000..bb408a7d6 --- /dev/null +++ b/pyload/webui/themes/Dark/img/button.png diff --git a/pyload/webui/themes/Dark/img/closebtn.gif b/pyload/webui/themes/Dark/img/closebtn.gif Binary files differnew file mode 100644 index 000000000..3e27e6030 --- /dev/null +++ b/pyload/webui/themes/Dark/img/closebtn.gif diff --git a/pyload/webui/themes/Dark/img/cog.png b/pyload/webui/themes/Dark/img/cog.png Binary files differnew file mode 100644 index 000000000..67de2c6cc --- /dev/null +++ b/pyload/webui/themes/Dark/img/cog.png diff --git a/pyload/webui/themes/Dark/img/control_add.png b/pyload/webui/themes/Dark/img/control_add.png Binary files differnew file mode 100644 index 000000000..d39886893 --- /dev/null +++ b/pyload/webui/themes/Dark/img/control_add.png diff --git a/pyload/webui/themes/Dark/img/control_add_blue.png b/pyload/webui/themes/Dark/img/control_add_blue.png Binary files differnew file mode 100644 index 000000000..d11b7f41d --- /dev/null +++ b/pyload/webui/themes/Dark/img/control_add_blue.png diff --git a/pyload/webui/themes/Dark/img/control_cancel.png b/pyload/webui/themes/Dark/img/control_cancel.png Binary files differnew file mode 100644 index 000000000..7b9bc3fba --- /dev/null +++ b/pyload/webui/themes/Dark/img/control_cancel.png diff --git a/pyload/webui/themes/Dark/img/control_cancel_blue.png b/pyload/webui/themes/Dark/img/control_cancel_blue.png Binary files differnew file mode 100644 index 000000000..0c5c96ce3 --- /dev/null +++ b/pyload/webui/themes/Dark/img/control_cancel_blue.png diff --git a/pyload/webui/themes/Dark/img/control_pause.png b/pyload/webui/themes/Dark/img/control_pause.png Binary files differnew file mode 100644 index 000000000..2d9ce9c4e --- /dev/null +++ b/pyload/webui/themes/Dark/img/control_pause.png diff --git a/pyload/webui/themes/Dark/img/control_pause_blue.png b/pyload/webui/themes/Dark/img/control_pause_blue.png Binary files differnew file mode 100644 index 000000000..ec61099b0 --- /dev/null +++ b/pyload/webui/themes/Dark/img/control_pause_blue.png diff --git a/pyload/webui/themes/Dark/img/control_play.png b/pyload/webui/themes/Dark/img/control_play.png Binary files differnew file mode 100644 index 000000000..0846555d0 --- /dev/null +++ b/pyload/webui/themes/Dark/img/control_play.png diff --git a/pyload/webui/themes/Dark/img/control_play_blue.png b/pyload/webui/themes/Dark/img/control_play_blue.png Binary files differnew file mode 100644 index 000000000..f8c8ec683 --- /dev/null +++ b/pyload/webui/themes/Dark/img/control_play_blue.png diff --git a/pyload/webui/themes/Dark/img/control_stop.png b/pyload/webui/themes/Dark/img/control_stop.png Binary files differnew file mode 100644 index 000000000..893bb60e5 --- /dev/null +++ b/pyload/webui/themes/Dark/img/control_stop.png diff --git a/pyload/webui/themes/Dark/img/control_stop_blue.png b/pyload/webui/themes/Dark/img/control_stop_blue.png Binary files differnew file mode 100644 index 000000000..e6f75d232 --- /dev/null +++ b/pyload/webui/themes/Dark/img/control_stop_blue.png diff --git a/pyload/webui/themes/Dark/img/dark-bg.jpg b/pyload/webui/themes/Dark/img/dark-bg.jpg Binary files differnew file mode 100644 index 000000000..637fa6b93 --- /dev/null +++ b/pyload/webui/themes/Dark/img/dark-bg.jpg diff --git a/pyload/webui/themes/Dark/img/delete.png b/pyload/webui/themes/Dark/img/delete.png Binary files differnew file mode 100644 index 000000000..08f249365 --- /dev/null +++ b/pyload/webui/themes/Dark/img/delete.png diff --git a/pyload/webui/themes/Dark/img/drag_corner.gif b/pyload/webui/themes/Dark/img/drag_corner.gif Binary files differnew file mode 100644 index 000000000..befb1adf1 --- /dev/null +++ b/pyload/webui/themes/Dark/img/drag_corner.gif diff --git a/pyload/webui/themes/Dark/img/error.png b/pyload/webui/themes/Dark/img/error.png Binary files differnew file mode 100644 index 000000000..c37bd062e --- /dev/null +++ b/pyload/webui/themes/Dark/img/error.png diff --git a/pyload/webui/themes/Dark/img/folder.png b/pyload/webui/themes/Dark/img/folder.png Binary files differnew file mode 100644 index 000000000..784e8fa48 --- /dev/null +++ b/pyload/webui/themes/Dark/img/folder.png diff --git a/pyload/webui/themes/Dark/img/full.png b/pyload/webui/themes/Dark/img/full.png Binary files differnew file mode 100644 index 000000000..fea52af76 --- /dev/null +++ b/pyload/webui/themes/Dark/img/full.png diff --git a/pyload/webui/themes/Dark/img/head-login.png b/pyload/webui/themes/Dark/img/head-login.png Binary files differnew file mode 100644 index 000000000..b59b7cbbf --- /dev/null +++ b/pyload/webui/themes/Dark/img/head-login.png diff --git a/pyload/webui/themes/Dark/img/head-menu-collector.png b/pyload/webui/themes/Dark/img/head-menu-collector.png Binary files differnew file mode 100644 index 000000000..861be40bc --- /dev/null +++ b/pyload/webui/themes/Dark/img/head-menu-collector.png diff --git a/pyload/webui/themes/Dark/img/head-menu-config.png b/pyload/webui/themes/Dark/img/head-menu-config.png Binary files differnew file mode 100644 index 000000000..bbf43d4f3 --- /dev/null +++ b/pyload/webui/themes/Dark/img/head-menu-config.png diff --git a/pyload/webui/themes/Dark/img/head-menu-development.png b/pyload/webui/themes/Dark/img/head-menu-development.png Binary files differnew file mode 100644 index 000000000..fad150fe1 --- /dev/null +++ b/pyload/webui/themes/Dark/img/head-menu-development.png diff --git a/pyload/webui/themes/Dark/img/head-menu-download.png b/pyload/webui/themes/Dark/img/head-menu-download.png Binary files differnew file mode 100644 index 000000000..98c5da9db --- /dev/null +++ b/pyload/webui/themes/Dark/img/head-menu-download.png diff --git a/pyload/webui/themes/Dark/img/head-menu-home.png b/pyload/webui/themes/Dark/img/head-menu-home.png Binary files differnew file mode 100644 index 000000000..9d62109aa --- /dev/null +++ b/pyload/webui/themes/Dark/img/head-menu-home.png diff --git a/pyload/webui/themes/Dark/img/head-menu-index.png b/pyload/webui/themes/Dark/img/head-menu-index.png Binary files differnew file mode 100644 index 000000000..44d631064 --- /dev/null +++ b/pyload/webui/themes/Dark/img/head-menu-index.png diff --git a/pyload/webui/themes/Dark/img/head-menu-news.png b/pyload/webui/themes/Dark/img/head-menu-news.png Binary files differnew file mode 100644 index 000000000..43950ebc9 --- /dev/null +++ b/pyload/webui/themes/Dark/img/head-menu-news.png diff --git a/pyload/webui/themes/Dark/img/head-menu-queue.png b/pyload/webui/themes/Dark/img/head-menu-queue.png Binary files differnew file mode 100644 index 000000000..be98793ce --- /dev/null +++ b/pyload/webui/themes/Dark/img/head-menu-queue.png diff --git a/pyload/webui/themes/Dark/img/head-menu-recent.png b/pyload/webui/themes/Dark/img/head-menu-recent.png Binary files differnew file mode 100644 index 000000000..fc9b0497f --- /dev/null +++ b/pyload/webui/themes/Dark/img/head-menu-recent.png diff --git a/pyload/webui/themes/Dark/img/head-menu-wiki.png b/pyload/webui/themes/Dark/img/head-menu-wiki.png Binary files differnew file mode 100644 index 000000000..07cf0102d --- /dev/null +++ b/pyload/webui/themes/Dark/img/head-menu-wiki.png diff --git a/pyload/webui/themes/Dark/img/head-search-noshadow.png b/pyload/webui/themes/Dark/img/head-search-noshadow.png Binary files differnew file mode 100644 index 000000000..aafdae015 --- /dev/null +++ b/pyload/webui/themes/Dark/img/head-search-noshadow.png diff --git a/pyload/webui/themes/Dark/img/head_bg1.png b/pyload/webui/themes/Dark/img/head_bg1.png Binary files differnew file mode 100644 index 000000000..f2848c3cc --- /dev/null +++ b/pyload/webui/themes/Dark/img/head_bg1.png diff --git a/pyload/webui/themes/Dark/img/images.png b/pyload/webui/themes/Dark/img/images.png Binary files differnew file mode 100644 index 000000000..184860d1e --- /dev/null +++ b/pyload/webui/themes/Dark/img/images.png diff --git a/pyload/webui/themes/Dark/img/notice.png b/pyload/webui/themes/Dark/img/notice.png Binary files differnew file mode 100644 index 000000000..12cd1aef9 --- /dev/null +++ b/pyload/webui/themes/Dark/img/notice.png diff --git a/pyload/webui/themes/Dark/img/package_go.png b/pyload/webui/themes/Dark/img/package_go.png Binary files differnew file mode 100644 index 000000000..aace63ad6 --- /dev/null +++ b/pyload/webui/themes/Dark/img/package_go.png diff --git a/pyload/webui/themes/Dark/img/page-tools-backlinks.png b/pyload/webui/themes/Dark/img/page-tools-backlinks.png Binary files differnew file mode 100644 index 000000000..3eb6a9ce3 --- /dev/null +++ b/pyload/webui/themes/Dark/img/page-tools-backlinks.png diff --git a/pyload/webui/themes/Dark/img/page-tools-edit.png b/pyload/webui/themes/Dark/img/page-tools-edit.png Binary files differnew file mode 100644 index 000000000..188e1c12b --- /dev/null +++ b/pyload/webui/themes/Dark/img/page-tools-edit.png diff --git a/pyload/webui/themes/Dark/img/page-tools-revisions.png b/pyload/webui/themes/Dark/img/page-tools-revisions.png Binary files differnew file mode 100644 index 000000000..5c3b8587f --- /dev/null +++ b/pyload/webui/themes/Dark/img/page-tools-revisions.png diff --git a/pyload/webui/themes/Dark/img/parseUri.png b/pyload/webui/themes/Dark/img/parseUri.png Binary files differnew file mode 100644 index 000000000..937bded9d --- /dev/null +++ b/pyload/webui/themes/Dark/img/parseUri.png diff --git a/pyload/webui/themes/Dark/img/pencil.png b/pyload/webui/themes/Dark/img/pencil.png Binary files differnew file mode 100644 index 000000000..0bfecd50e --- /dev/null +++ b/pyload/webui/themes/Dark/img/pencil.png diff --git a/pyload/webui/themes/Dark/img/pyload-logo.png b/pyload/webui/themes/Dark/img/pyload-logo.png Binary files differnew file mode 100644 index 000000000..e878afee5 --- /dev/null +++ b/pyload/webui/themes/Dark/img/pyload-logo.png diff --git a/pyload/webui/themes/Dark/img/reconnect.png b/pyload/webui/themes/Dark/img/reconnect.png Binary files differnew file mode 100644 index 000000000..49b269145 --- /dev/null +++ b/pyload/webui/themes/Dark/img/reconnect.png diff --git a/pyload/webui/themes/Dark/img/status_None.png b/pyload/webui/themes/Dark/img/status_None.png Binary files differnew file mode 100644 index 000000000..293b13f77 --- /dev/null +++ b/pyload/webui/themes/Dark/img/status_None.png diff --git a/pyload/webui/themes/Dark/img/status_downloading.png b/pyload/webui/themes/Dark/img/status_downloading.png Binary files differnew file mode 100644 index 000000000..fb4ebc850 --- /dev/null +++ b/pyload/webui/themes/Dark/img/status_downloading.png diff --git a/pyload/webui/themes/Dark/img/status_failed.png b/pyload/webui/themes/Dark/img/status_failed.png Binary files differnew file mode 100644 index 000000000..c37bd062e --- /dev/null +++ b/pyload/webui/themes/Dark/img/status_failed.png diff --git a/pyload/webui/themes/Dark/img/status_finished.png b/pyload/webui/themes/Dark/img/status_finished.png Binary files differnew file mode 100644 index 000000000..89c8129a4 --- /dev/null +++ b/pyload/webui/themes/Dark/img/status_finished.png diff --git a/pyload/webui/themes/Dark/img/status_offline.png b/pyload/webui/themes/Dark/img/status_offline.png Binary files differnew file mode 100644 index 000000000..0cfd58596 --- /dev/null +++ b/pyload/webui/themes/Dark/img/status_offline.png diff --git a/pyload/webui/themes/Dark/img/status_proc.png b/pyload/webui/themes/Dark/img/status_proc.png Binary files differnew file mode 100644 index 000000000..67de2c6cc --- /dev/null +++ b/pyload/webui/themes/Dark/img/status_proc.png diff --git a/pyload/webui/themes/Dark/img/status_queue.png b/pyload/webui/themes/Dark/img/status_queue.png Binary files differnew file mode 100644 index 000000000..293b13f77 --- /dev/null +++ b/pyload/webui/themes/Dark/img/status_queue.png diff --git a/pyload/webui/themes/Dark/img/status_waiting.png b/pyload/webui/themes/Dark/img/status_waiting.png Binary files differnew file mode 100644 index 000000000..2842cc338 --- /dev/null +++ b/pyload/webui/themes/Dark/img/status_waiting.png diff --git a/pyload/webui/themes/Dark/img/success.png b/pyload/webui/themes/Dark/img/success.png Binary files differnew file mode 100644 index 000000000..89c8129a4 --- /dev/null +++ b/pyload/webui/themes/Dark/img/success.png diff --git a/pyload/webui/themes/Dark/img/tab-background.png b/pyload/webui/themes/Dark/img/tab-background.png Binary files differnew file mode 100644 index 000000000..ee96b8407 --- /dev/null +++ b/pyload/webui/themes/Dark/img/tab-background.png diff --git a/pyload/webui/themes/Dark/img/tabs-border-bottom.png b/pyload/webui/themes/Dark/img/tabs-border-bottom.png Binary files differnew file mode 100644 index 000000000..02440f428 --- /dev/null +++ b/pyload/webui/themes/Dark/img/tabs-border-bottom.png diff --git a/pyload/webui/themes/Dark/img/user-actions-logout.png b/pyload/webui/themes/Dark/img/user-actions-logout.png Binary files differnew file mode 100644 index 000000000..0010931e2 --- /dev/null +++ b/pyload/webui/themes/Dark/img/user-actions-logout.png diff --git a/pyload/webui/themes/Dark/img/user-actions-profile.png b/pyload/webui/themes/Dark/img/user-actions-profile.png Binary files differnew file mode 100644 index 000000000..46573fff6 --- /dev/null +++ b/pyload/webui/themes/Dark/img/user-actions-profile.png diff --git a/pyload/webui/themes/Dark/img/user-info.png b/pyload/webui/themes/Dark/img/user-info.png Binary files differnew file mode 100644 index 000000000..6e643100f --- /dev/null +++ b/pyload/webui/themes/Dark/img/user-info.png diff --git a/pyload/webui/themes/Dark/js/admin.coffee b/pyload/webui/themes/Dark/js/admin.coffee new file mode 100644 index 000000000..5afbcbb66 --- /dev/null +++ b/pyload/webui/themes/Dark/js/admin.coffee @@ -0,0 +1,58 @@ +root = this + +window.addEvent "domready", -> + + root.passwordDialog = new MooDialog {destroyOnHide: false} + root.passwordDialog.setContent $ 'password_box' + + $("login_password_reset").addEvent "click", (e) -> root.passwordDialog.close() + $("login_password_button").addEvent "click", (e) -> + + newpw = $("login_new_password").get("value") + newpw2 = $("login_new_password2").get("value") + + if newpw is newpw2 + form = $("password_form") + form.set "send", { + onSuccess: (data) -> + root.notify.alert "Success", { + 'className': 'success' + } + onFailure: (data) -> + root.notify.alert "Error", { + 'className': 'error' + } + } + + form.send() + + root.passwordDialog.close() + else + alert '{{_("Passwords did not match.")}}' + + e.stop() + + for item in $$(".change_password") + id = item.get("id") + user = id.split("|")[1] + $("user_login").set("value", user) + item.addEvent "click", (e) -> root.passwordDialog.open() + + $('quit-pyload').addEvent "click", (e) -> + new MooDialog.Confirm "{{_('You are really sure you want to quit pyLoad?')}}", -> + new Request.JSON({ + url: '/api/kill' + method: 'get' + }).send() + , -> + e.stop() + + $('restart-pyload').addEvent "click", (e) -> + new MooDialog.Confirm "{{_('Are you sure you want to restart pyLoad?')}}", -> + new Request.JSON({ + url: '/api/restart' + method: 'get' + onSuccess: (data) -> alert "{{_('pyLoad restarted')}}" + }).send() + , -> + e.stop() diff --git a/pyload/webui/themes/Dark/js/admin.min.js b/pyload/webui/themes/Dark/js/admin.min.js new file mode 100644 index 000000000..94a5e494d --- /dev/null +++ b/pyload/webui/themes/Dark/js/admin.min.js @@ -0,0 +1,3 @@ +{% autoescape true %} +var root;root=this;window.addEvent("domready",function(){var f,c,b,e,a,d;root.passwordDialog=new MooDialog({destroyOnHide:false});root.passwordDialog.setContent($("password_box"));$("login_password_reset").addEvent("click",function(g){return root.passwordDialog.close()});$("login_password_button").addEvent("click",function(j){var h,i,g;i=$("login_new_password").get("value");g=$("login_new_password2").get("value");if(i===g){h=$("password_form");h.set("send",{onSuccess:function(k){return root.notify.alert("Success",{className:"success"})},onFailure:function(k){return root.notify.alert("Error",{className:"error"})}});h.send();root.passwordDialog.close()}else{alert('{{_("Passwords did not match.")}}')}return j.stop()});d=$$(".change_password");for(e=0,a=d.length;e<a;e++){c=d[e];f=c.get("id");b=f.split("|")[1];$("user_login").set("value",b);c.addEvent("click",function(g){return root.passwordDialog.open()})}$("quit-pyload").addEvent("click",function(g){new MooDialog.Confirm("{{_('You are really sure you want to quit pyLoad?')}}",function(){return new Request.JSON({url:"/api/kill",method:"get"}).send()},function(){});return g.stop()});return $("restart-pyload").addEvent("click",function(g){new MooDialog.Confirm("{{_('Are you sure you want to restart pyLoad?')}}",function(){return new Request.JSON({url:"/api/restart",method:"get",onSuccess:function(h){return alert("{{_('pyLoad restarted')}}")}}).send()},function(){});return g.stop()})}); +{% endautoescape %} diff --git a/pyload/webui/themes/Dark/js/base.coffee b/pyload/webui/themes/Dark/js/base.coffee new file mode 100644 index 000000000..07b8bfb6f --- /dev/null +++ b/pyload/webui/themes/Dark/js/base.coffee @@ -0,0 +1,177 @@ +# External scope +root = this + +# helper functions +humanFileSize = (size) -> + filesizename = new Array("B", "KiB", "MiB", "GiB", "TiB", "PiB") + loga = Math.log(size) / Math.log(1024) + i = Math.floor(loga) + a = Math.pow(1024, i) + if size is 0 then "0 B" else (Math.round(size * 100 / a) / 100 + " " + filesizename[i]) + + +parseUri = () -> + oldString = $("add_links").value + regxp = new RegExp('(ht|f)tp(s?):\/\/[a-zA-Z0-9\-\.\/\?=_&%#]+[<| |\"|\'|\r|\n|\t]{1}', 'g') + resu = oldString.match regxp + return if resu == null + res = "" + + for part in resu + if part.indexOf(" ") != -1 + res = res + part.replace(" ", " \n") + else if part.indexOf("\t") != -1 + res = res + part.replace("\t", " \n") + else if part.indexOf("\r") != -1 + res = res + part.replace("\r", " \n") + else if part.indexOf("\"") != -1 + res = res + part.replace("\"", " \n") + else if part.indexOf("<") != -1 + res = res + part.replace("<", " \n") + else if part.indexOf("'") != -1 + res = res + part.replace("'", " \n") + else + res = res + part.replace("\n", " \n") + + $("add_links").value = res + + +Array::remove = (from, to) -> + rest = this.slice((to || from) + 1 || this.length) + this.length = from < 0 ? this.length + from : from + return [] if this.length == 0 + return this.push.apply(this, rest) + + +document.addEvent "domready", -> + + # global notification + root.notify = new Purr { + 'mode': 'top' + 'position': 'center' + } + + root.captchaBox = new MooDialog {destroyOnHide: false} + root.captchaBox.setContent $ 'cap_box' + + root.addBox = new MooDialog {destroyOnHide: false} + root.addBox.setContent $ 'add_box' + + $('add_form').onsubmit = -> + $('add_form').target = 'upload_target' + if $('add_name').value is "" and $('add_file').value is "" + alert '{{_("Please Enter a packagename.")}}' + return false + else + root.addBox.close() + return true + + $('add_reset').addEvent 'click', -> root.addBox.close() + + $('action_add').addEvent 'click', -> $("add_form").reset(); root.addBox.open() + $('action_play').addEvent 'click', -> new Request({method: 'get', url: '/api/unpauseServer'}).send() + $('action_cancel').addEvent 'click', -> new Request({method: 'get', url: '/api/stopAllDownloads'}).send() + $('action_stop').addEvent 'click', -> new Request({method: 'get', url: '/api/pauseServer'}).send() + + + # captcha events + + $('cap_info').addEvent 'click', -> + load_captcha "get", "" + root.captchaBox.open() + $('cap_reset').addEvent 'click', -> root.captchaBox.close() + $('cap_form').addEvent 'submit', (e) -> + submit_captcha() + e.stop() + + $('cap_positional').addEvent 'click', on_captcha_click + + new Request.JSON({ + url: "/json/status" + onSuccess: LoadJsonToContent + secure: false + async: true + initialDelay: 0 + delay: 4000 + limit: 3000 + }).startTimer() + + +LoadJsonToContent = (data) -> + $("speed").set 'text', humanFileSize(data.speed)+"/s" + $("aktiv").set 'text', data.active + $("aktiv_from").set 'text', data.queue + $("aktiv_total").set 'text', data.total + + if data.captcha + if $("cap_info").getStyle("display") != "inline" + $("cap_info").setStyle 'display', 'inline' + root.notify.alert '{{_("New Captcha Request")}}', { + 'className': 'notify' + } + else + $("cap_info").setStyle 'display', 'none' + + + if data.download + $("time").set 'text', ' {{_("on")}}' + $("time").setStyle 'background-color', "#8ffc25" + else + $("time").set 'text', ' {{_("off")}}' + $("time").setStyle 'background-color', "#fc6e26" + + if data.reconnect + $("reconnect").set 'text', ' {{_("on")}}' + $("reconnect").setStyle 'background-color', "#8ffc25" + else + $("reconnect").set 'text', ' {{_("off")}}' + $("reconnect").setStyle 'background-color', "#fc6e26" + + return null + + +set_captcha = (data) -> + $('cap_id').set 'value', data.id + if (data.result_type is 'textual') + $('cap_textual_img').set 'src', data.src + $('cap_title').set 'text', '{{_("Please read the text on the captcha.")}}' + $('cap_submit').setStyle 'display', 'inline' + $('cap_textual').setStyle 'display', 'block' + $('cap_positional').setStyle 'display', 'none' + + else if (data.result_type == 'positional') + $('cap_positional_img').set('src', data.src) + $('cap_title').set('text', '{{_("Please click on the right captcha position.")}}') + $('cap_submit').setStyle('display', 'none') + $('cap_textual').setStyle('display', 'none') + + +load_captcha = (method, post) -> + new Request.JSON({ + url: "/json/set_captcha" + onSuccess: (data) -> set_captcha(data) if data.captcha else clear_captcha() + secure: false + async: true + method: method + }).send(post) + + +clear_captcha = -> + $('cap_textual').setStyle 'display', 'none' + $('cap_textual_img').set 'src', '' + $('cap_positional').setStyle 'display', 'none' + $('cap_positional_img').set 'src', '' + $('cap_title').set 'text', '{{_("No Captchas to read.")}}' + + +submit_captcha = -> + load_captcha("post", "cap_id=" + $('cap_id').get('value') + "&cap_result=" + $('cap_result').get('value') ) + $('cap_result').set('value', '') + + +on_captcha_click = (e) -> + position = e.target.getPosition() + x = e.page.x - position.x + y = e.page.y - position.y + $('cap_result').value = x + "," + y + submit_captcha() diff --git a/pyload/webui/themes/Dark/js/base.min.js b/pyload/webui/themes/Dark/js/base.min.js new file mode 100644 index 000000000..1ba1d73f9 --- /dev/null +++ b/pyload/webui/themes/Dark/js/base.min.js @@ -0,0 +1,3 @@ +{% autoescape true %} +var LoadJsonToContent,clear_captcha,humanFileSize,load_captcha,on_captcha_click,parseUri,root,set_captcha,submit_captcha;root=this;humanFileSize=function(f){var c,d,e,b;d=new Array("B","KiB","MiB","GiB","TiB","PiB");b=Math.log(f)/Math.log(1024);e=Math.floor(b);c=Math.pow(1024,e);if(f===0){return"0 B"}else{return Math.round(f*100/c)/100+" "+d[e]}};parseUri=function(){var b,c,g,e,d,f,a;b=$("add_links").value;g=new RegExp("(ht|f)tp(s?)://[a-zA-Z0-9-./?=_&%#]+[<| |\"|'|\r|\n|\t]{1}","g");d=b.match(g);if(d===null){return}e="";for(f=0,a=d.length;f<a;f++){c=d[f];if(c.indexOf(" ")!==-1){e=e+c.replace(" "," \n")}else{if(c.indexOf("\t")!==-1){e=e+c.replace("\t"," \n")}else{if(c.indexOf("\r")!==-1){e=e+c.replace("\r"," \n")}else{if(c.indexOf('"')!==-1){e=e+c.replace('"'," \n")}else{if(c.indexOf("<")!==-1){e=e+c.replace("<"," \n")}else{if(c.indexOf("'")!==-1){e=e+c.replace("'"," \n")}else{e=e+c.replace("\n"," \n")}}}}}}}return $("add_links").value=e};Array.prototype.remove=function(d,c){var a,b;a=this.slice((c||d)+1||this.length);this.length=(b=d<0)!=null?b:this.length+{from:d};if(this.length===0){return[]}return this.push.apply(this,a)};document.addEvent("domready",function(){root.notify=new Purr({mode:"top",position:"center"});root.captchaBox=new MooDialog({destroyOnHide:false});root.captchaBox.setContent($("cap_box"));root.addBox=new MooDialog({destroyOnHide:false});root.addBox.setContent($("add_box"));$("add_form").onsubmit=function(){$("add_form").target="upload_target";if($("add_name").value===""&&$("add_file").value===""){alert('{{_("Please Enter a packagename.")}}');return false}else{root.addBox.close();return true}};$("add_reset").addEvent("click",function(){return root.addBox.close()});$("action_add").addEvent("click",function(){$("add_form").reset();return root.addBox.open()});$("action_play").addEvent("click",function(){return new Request({method:"get",url:"/api/unpauseServer"}).send()});$("action_cancel").addEvent("click",function(){return new Request({method:"get",url:"/api/stopAllDownloads"}).send()});$("action_stop").addEvent("click",function(){return new Request({method:"get",url:"/api/pauseServer"}).send()});$("cap_info").addEvent("click",function(){load_captcha("get","");return root.captchaBox.open()});$("cap_reset").addEvent("click",function(){return root.captchaBox.close()});$("cap_form").addEvent("submit",function(a){submit_captcha();return a.stop()});$("cap_positional").addEvent("click",on_captcha_click);return new Request.JSON({url:"/json/status",onSuccess:LoadJsonToContent,secure:false,async:true,initialDelay:0,delay:4000,limit:3000}).startTimer()});LoadJsonToContent=function(a){$("speed").set("text",humanFileSize(a.speed)+"/s");$("aktiv").set("text",a.active);$("aktiv_from").set("text",a.queue);$("aktiv_total").set("text",a.total);if(a.captcha){if($("cap_info").getStyle("display")!=="inline"){$("cap_info").setStyle("display","inline");root.notify.alert('{{_("New Captcha Request")}}',{className:"notify"})}}else{$("cap_info").setStyle("display","none")}if(a.download){$("time").set("text",' {{_("on")}}');$("time").setStyle("background-color","#8ffc25")}else{$("time").set("text",' {{_("off")}}');$("time").setStyle("background-color","#fc6e26")}if(a.reconnect){$("reconnect").set("text",' {{_("on")}}');$("reconnect").setStyle("background-color","#8ffc25")}else{$("reconnect").set("text",' {{_("off")}}');$("reconnect").setStyle("background-color","#fc6e26")}return null};set_captcha=function(a){$("cap_id").set("value",a.id);if(a.result_type==="textual"){$("cap_textual_img").set("src",a.src);$("cap_title").set("text",'{{_("Please read the text on the captcha.")}}');$("cap_submit").setStyle("display","inline");$("cap_textual").setStyle("display","block");return $("cap_positional").setStyle("display","none")}else{if(a.result_type==="positional"){$("cap_positional_img").set("src",a.src);$("cap_title").set("text",'{{_("Please click on the right captcha position.")}}');$("cap_submit").setStyle("display","none");return $("cap_textual").setStyle("display","none")}}};load_captcha=function(b,a){return new Request.JSON({url:"/json/set_captcha",onSuccess:function(c){return set_captcha(c)(c.captcha?void 0:clear_captcha())},secure:false,async:true,method:b}).send(a)};clear_captcha=function(){$("cap_textual").setStyle("display","none");$("cap_textual_img").set("src","");$("cap_positional").setStyle("display","none");$("cap_positional_img").set("src","");return $("cap_title").set("text",'{{_("No Captchas to read.")}}')};submit_captcha=function(){load_captcha("post","cap_id="+$("cap_id").get("value")+"&cap_result="+$("cap_result").get("value"));$("cap_result").set("value","");return false};on_captcha_click=function(c){var b,a,d;b=c.target.getPosition();a=c.page.x-b.x;d=c.page.y-b.y;$("cap_result").value=a+","+d;return submit_captcha()}; +{% endautoescape %} diff --git a/pyload/webui/themes/Dark/js/package.js b/pyload/webui/themes/Dark/js/package.js new file mode 100644 index 000000000..659a8e6fc --- /dev/null +++ b/pyload/webui/themes/Dark/js/package.js @@ -0,0 +1,376 @@ +var root = this; + +document.addEvent("domready", function() { + root.load = new Fx.Tween($("load-indicator"), {link: "cancel"}); + root.load.set("opacity", 0); + + + root.packageBox = new MooDialog({destroyOnHide: false}); + root.packageBox.setContent($('pack_box')); + + $('pack_reset').addEvent('click', function() { + $('pack_form').reset(); + root.packageBox.close(); + }); +}); + +function indicateLoad() { + //$("load-indicator").reveal(); + root.load.start("opacity", 1) +} + +function indicateFinish() { + root.load.start("opacity", 0) +} + +function indicateSuccess() { + indicateFinish(); + root.notify.alert('{{_("Success")}}.', { + 'className': 'success' + }); +} + +function indicateFail() { + indicateFinish(); + root.notify.alert('{{_("Failed")}}.', { + 'className': 'error' + }); +} + +var PackageUI = new Class({ + initialize: function(url, type) { + this.url = url; + this.type = type; + this.packages = []; + this.parsePackages(); + + this.sorts = new Sortables($("package-list"), { + constrain: false, + clone: true, + revert: true, + opacity: 0.4, + handle: ".package_drag", + onComplete: this.saveSort.bind(this) + }); + + $("del_finished").addEvent("click", this.deleteFinished.bind(this)); + $("restart_failed").addEvent("click", this.restartFailed.bind(this)); + + }, + + parsePackages: function() { + $("package-list").getChildren("li").each(function(ele) { + var id = ele.getFirst().get("id").match(/[0-9]+/); + this.packages.push(new Package(this, id, ele)) + }.bind(this)) + }, + + loadPackages: function() { + }, + + deleteFinished: function() { + indicateLoad(); + new Request.JSON({ + method: 'get', + url: '/api/deleteFinished', + onSuccess: function(data) { + if (data.length > 0) { + window.location.reload() + } else { + this.packages.each(function(pack) { + pack.close(); + }); + indicateSuccess(); + } + }.bind(this), + onFailure: indicateFail + }).send(); + }, + + restartFailed: function() { + indicateLoad(); + new Request.JSON({ + method: 'get', + url: '/api/restartFailed', + onSuccess: function(data) { + this.packages.each(function(pack) { + pack.close(); + }); + indicateSuccess(); + }.bind(this), + onFailure: indicateFail + }).send(); + }, + + startSort: function(ele, copy) { + }, + + saveSort: function(ele, copy) { + var order = []; + this.sorts.serialize(function(li, pos) { + if (li == ele && ele.retrieve("order") != pos) { + order.push(ele.retrieve("pid") + "|" + pos) + } + li.store("order", pos) + }); + if (order.length > 0) { + indicateLoad(); + new Request.JSON({ + method: 'get', + url: '/json/package_order/' + order[0], + onSuccess: indicateFinish, + onFailure: indicateFail + }).send(); + } + } + +}); + +var Package = new Class({ + initialize: function(ui, id, ele, data) { + this.ui = ui; + this.id = id; + this.linksLoaded = false; + + if (!ele) { + this.createElement(data); + } else { + this.ele = ele; + this.order = ele.getElements("div.order")[0].get("html"); + this.ele.store("order", this.order); + this.ele.store("pid", this.id); + this.parseElement(); + } + + var pname = this.ele.getElements(".packagename")[0]; + this.buttons = new Fx.Tween(this.ele.getElements(".buttons")[0], {link: "cancel"}); + this.buttons.set("opacity", 0); + + pname.addEvent("mouseenter", function(e) { + this.buttons.start("opacity", 1) + }.bind(this)); + + pname.addEvent("mouseleave", function(e) { + this.buttons.start("opacity", 0) + }.bind(this)); + + + }, + + createElement: function() { + alert("create") + }, + + parseElement: function() { + var imgs = this.ele.getElements('img'); + + this.name = this.ele.getElements('.name')[0]; + this.folder = this.ele.getElements('.folder')[0]; + this.password = this.ele.getElements('.password')[0]; + + imgs[1].addEvent('click', this.deletePackage.bind(this)); + imgs[2].addEvent('click', this.restartPackage.bind(this)); + imgs[3].addEvent('click', this.editPackage.bind(this)); + imgs[4].addEvent('click', this.movePackage.bind(this)); + + this.ele.getElement('.packagename').addEvent('click', this.toggle.bind(this)); + + }, + + loadLinks: function() { + indicateLoad(); + new Request.JSON({ + method: 'get', + url: '/json/package/' + this.id, + onSuccess: this.createLinks.bind(this), + onFailure: indicateFail + }).send(); + }, + + createLinks: function(data) { + var ul = $("sort_children_{id}".substitute({'id': this.id})); + ul.set("html", ""); + data.links.each(function(link) { + link.id = link.fid; + var li = new Element("li", { + 'style': { + 'margin-left': 0 + } + }); + + var html = "<span style='cursor: move' class='child_status sorthandle'><img src='../img/{icon}' style='width: 12px; height:12px;'/></span>\n".substitute({'icon': link.icon}); + html += "<span style='font-size: 15px'><a href=\"{url}\" target=\"_blank\">{name}</a></span><br /><div class='child_secrow'>".substitute({'url': link.url, 'name': link.name}); + html += "<span class='child_status'>{statusmsg}</span>{error} ".substitute({'statusmsg': link.statusmsg, 'error':link.error}); + html += "<span class='child_status'>{format_size}</span>".substitute({'format_size': link.format_size}); + html += "<span class='child_status'>{plugin}</span> ".substitute({'plugin': link.plugin}); + html += "<img title='{{_(\"Delete Link\")}}' style='cursor: pointer;' width='10px' height='10px' src='../img/delete.png' /> "; + html += "<img title='{{_(\"Restart Link\")}}' style='cursor: pointer;margin-left: -4px' width='10px' height='10px' src='../img/arrow_refresh.png' /></div>"; + + var div = new Element("div", { + 'id': "file_" + link.id, + 'class': "child", + 'html': html + }); + + li.store("order", link.order); + li.store("lid", link.id); + + li.adopt(div); + ul.adopt(li); + }); + this.sorts = new Sortables(ul, { + constrain: false, + clone: true, + revert: true, + opacity: 0.4, + handle: ".sorthandle", + onComplete: this.saveSort.bind(this) + }); + this.registerLinkEvents(); + this.linksLoaded = true; + indicateFinish(); + this.toggle(); + }, + + registerLinkEvents: function() { + this.ele.getElements('.child').each(function(child) { + var lid = child.get('id').match(/[0-9]+/); + var imgs = child.getElements('.child_secrow img'); + imgs[0].addEvent('click', function(e) { + new Request({ + method: 'get', + url: '/api/deleteFiles/[' + this + "]", + onSuccess: function() { + $('file_' + this).nix() + }.bind(this), + onFailure: indicateFail + }).send(); + }.bind(lid)); + + imgs[1].addEvent('click', function(e) { + new Request({ + method: 'get', + url: '/api/restartFile/' + this, + onSuccess: function() { + var ele = $('file_' + this); + var imgs = ele.getElements("img"); + imgs[0].set("src", "../img/status_queue.png"); + var spans = ele.getElements(".child_status"); + spans[1].set("html", "queued"); + indicateSuccess(); + }.bind(this), + onFailure: indicateFail + }).send(); + }.bind(lid)); + }); + }, + + toggle: function() { + var child = this.ele.getElement('.children'); + if (child.getStyle('display') == "block") { + child.dissolve(); + } else { + if (!this.linksLoaded) { + this.loadLinks(); + } else { + child.reveal(); + } + } + }, + + + deletePackage: function(event) { + indicateLoad(); + new Request({ + method: 'get', + url: '/api/deletePackages/[' + this.id + "]", + onSuccess: function() { + this.ele.nix(); + indicateFinish(); + }.bind(this), + onFailure: indicateFail + }).send(); + //hide_pack(); + event.stop(); + }, + + restartPackage: function(event) { + indicateLoad(); + new Request({ + method: 'get', + url: '/api/restartPackage/' + this.id, + onSuccess: function() { + this.close(); + indicateSuccess(); + }.bind(this), + onFailure: indicateFail + }).send(); + event.stop(); + }, + + close: function() { + var child = this.ele.getElement('.children'); + if (child.getStyle('display') == "block") { + child.dissolve(); + } + var ul = $("sort_children_{id}".substitute({'id': this.id})); + ul.erase("html"); + this.linksLoaded = false; + }, + + movePackage: function(event) { + indicateLoad(); + new Request({ + method: 'get', + url: '/json/move_package/' + ((this.ui.type + 1) % 2) + "/" + this.id, + onSuccess: function() { + this.ele.nix(); + indicateFinish(); + }.bind(this), + onFailure: indicateFail + }).send(); + event.stop(); + }, + + editPackage: function(event) { + $("pack_form").removeEvents("submit"); + $("pack_form").addEvent("submit", this.savePackage.bind(this)); + + $("pack_id").set("value", this.id); + $("pack_name").set("value", this.name.get("text")); + $("pack_folder").set("value", this.folder.get("text")); + $("pack_pws").set("value", this.password.get("text")); + + root.packageBox.open(); + event.stop(); + }, + + savePackage: function(event) { + $("pack_form").send(); + this.name.set("text", $("pack_name").get("value")); + this.folder.set("text", $("pack_folder").get("value")); + this.password.set("text", $("pack_pws").get("value")); + root.packageBox.close(); + event.stop(); + }, + + saveSort: function(ele, copy) { + var order = []; + this.sorts.serialize(function(li, pos) { + if (li == ele && ele.retrieve("order") != pos) { + order.push(ele.retrieve("lid") + "|" + pos) + } + li.store("order", pos) + }); + if (order.length > 0) { + indicateLoad(); + new Request.JSON({ + method: 'get', + url: '/json/link_order/' + order[0], + onSuccess: indicateFinish, + onFailure: indicateFail + }).send(); + } + } + +}); diff --git a/pyload/webui/themes/Dark/js/settings.coffee b/pyload/webui/themes/Dark/js/settings.coffee new file mode 100644 index 000000000..d522741b9 --- /dev/null +++ b/pyload/webui/themes/Dark/js/settings.coffee @@ -0,0 +1,107 @@ +root = this + +window.addEvent 'domready', -> + root.accountDialog = new MooDialog {destroyOnHide: false} + root.accountDialog.setContent $ 'account_box' + + new TinyTab $$('#toptabs li a'), $$('#tabs-body > span') + + $$('ul.nav').each (nav) -> + new MooDropMenu nav, { + onOpen: (el) -> el.fade 'in' + onClose: (el) -> el.fade 'out' + onInitialize: (el) -> el.fade('hide').set 'tween', {duration:500} + } + + new SettingsUI() + + +class SettingsUI + constructor: -> + @menu = $$ "#general-menu li" + @menu.append $$ "#plugin-menu li" + + @name = $ "tabsback" + @general = $ "general_form_content" + @plugin = $ "plugin_form_content" + + el.addEvent 'click', @menuClick.bind(this) for el in @menu + + $("general|submit").addEvent "click", @configSubmit.bind(this) + $("plugin|submit").addEvent "click", @configSubmit.bind(this) + + $("account_add").addEvent "click", (e) -> + root.accountDialog.open() + e.stop() + + $("account_reset").addEvent "click", (e) -> + root.accountDialog.close() + + $("account_add_button").addEvent "click", @addAccount.bind(this) + $("account_submit").addEvent "click", @submitAccounts.bind(this) + + + menuClick: (e) -> + [category, section] = e.target.get("id").split("|") + name = e.target.get "text" + + + target = if category is "general" then @general else @plugin + target.dissolve() + + new Request({ + "method" : "get" + "url" : "/json/load_config/#{category}/#{section}" + 'onSuccess': (data) => + target.set "html", data + target.reveal() + this.name.set "text", name + }).send() + + + configSubmit: (e) -> + category = e.target.get("id").split("|")[0] + form = $("#{category}_form") + + form.set "send", { + 'method': "post" + 'url': "/json/save_config/#{category}" + "onSuccess" : -> + root.notify.alert '{{ _("Settings saved.")}}', { + 'className': 'success' + } + 'onFailure': -> + root.notify.alert '{{ _("Error occured.")}}', { + 'className': 'error' + } + } + form.send() + e.stop() + + addAccount: (e) -> + form = $ "add_account_form" + form.set "send", { + 'method': "post" + "onSuccess" : -> window.location.reload() + 'onFailure': -> + root.notify.alert '{{_("Error occured.")}}', { + 'className': 'error' + } + } + + form.send() + e.stop() + + submitAccounts: (e) -> + form = $ "account_form" + form.set "send", { + 'method': "post", + "onSuccess" : -> window.location.reload() + 'onFailure': -> + root.notify.alert('{{ _("Error occured.") }}', { + 'className': 'error' + }) + } + + form.send() + e.stop() diff --git a/pyload/webui/themes/Dark/js/settings.min.js b/pyload/webui/themes/Dark/js/settings.min.js new file mode 100644 index 000000000..41d1cb25a --- /dev/null +++ b/pyload/webui/themes/Dark/js/settings.min.js @@ -0,0 +1,3 @@ +{% autoescape true %} +var SettingsUI,root;var __bind=function(a,b){return function(){return a.apply(b,arguments)}};root=this;window.addEvent("domready",function(){root.accountDialog=new MooDialog({destroyOnHide:false});root.accountDialog.setContent($("account_box"));new TinyTab($$("#toptabs li a"),$$("#tabs-body > span"));$$("ul.nav").each(function(a){return new MooDropMenu(a,{onOpen:function(b){return b.fade("in")},onClose:function(b){return b.fade("out")},onInitialize:function(b){return b.fade("hide").set("tween",{duration:500})}})});return new SettingsUI()});SettingsUI=(function(){function a(){var c,e,b,d;this.menu=$$("#general-menu li");this.menu.append($$("#plugin-menu li"));this.name=$("tabsback");this.general=$("general_form_content");this.plugin=$("plugin_form_content");d=this.menu;for(e=0,b=d.length;e<b;e++){c=d[e];c.addEvent("click",this.menuClick.bind(this))}$("general|submit").addEvent("click",this.configSubmit.bind(this));$("plugin|submit").addEvent("click",this.configSubmit.bind(this));$("account_add").addEvent("click",function(f){root.accountDialog.open();return f.stop()});$("account_reset").addEvent("click",function(f){return root.accountDialog.close()});$("account_add_button").addEvent("click",this.addAccount.bind(this));$("account_submit").addEvent("click",this.submitAccounts.bind(this))}a.prototype.menuClick=function(h){var c,b,g,f,d;d=h.target.get("id").split("|"),c=d[0],g=d[1];b=h.target.get("text");f=c==="general"?this.general:this.plugin;f.dissolve();return new Request({method:"get",url:"/json/load_config/"+c+"/"+g,onSuccess:__bind(function(e){f.set("html",e);f.reveal();return this.name.set("text",b)},this)}).send()};a.prototype.configSubmit=function(d){var c,b;c=d.target.get("id").split("|")[0];b=$(""+c+"_form");b.set("send",{method:"post",url:"/json/save_config/"+c,onSuccess:function(){return root.notify.alert('{{ _("Settings saved.")}}',{className:"success"})},onFailure:function(){return root.notify.alert('{{ _("Error occured.")}}',{className:"error"})}});b.send();return d.stop()};a.prototype.addAccount=function(c){var b;b=$("add_account_form");b.set("send",{method:"post",onSuccess:function(){return window.location.reload()},onFailure:function(){return root.notify.alert('{{_("Error occured.")}}',{className:"error"})}});b.send();return c.stop()};a.prototype.submitAccounts=function(c){var b;b=$("account_form");b.set("send",{method:"post",onSuccess:function(){return window.location.reload()},onFailure:function(){return root.notify.alert('{{ _("Error occured.") }}',{className:"error"})}});b.send();return c.stop()};return a})(); +{% endautoescape %} diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Alert.js b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Alert.js new file mode 100644 index 000000000..1e2d4180b --- /dev/null +++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Alert.js @@ -0,0 +1,45 @@ +/* +--- +name: MooDialog.Alert +description: Creates an Alert dialog +authors: Arian Stolwijk +license: MIT-style license +requires: MooDialog +provides: MooDialog.Alert +... +*/ + + +MooDialog.Alert = new Class({ + + Extends: MooDialog, + + options: { + okText: 'Ok', + focus: true, + textPClass: 'MooDialogAlert' + }, + + initialize: function(msg, options){ + this.parent(options); + + var okButton = new Element('button', { + events: { + click: this.close.bind(this) + }, + text: this.options.okText + }); + + this.setContent( + new Element('p.' + this.options.textPClass, {text: msg}), + new Element('div.buttons').adopt(okButton) + ); + if (this.options.autoOpen) this.open(); + + if (this.options.focus) this.addEvent('show', function(){ + okButton.focus() + }); + + } +}); + diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Confirm.js b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Confirm.js new file mode 100644 index 000000000..16f32e290 --- /dev/null +++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Confirm.js @@ -0,0 +1,80 @@ +/* +--- +name: MooDialog.Confirm +description: Creates an Confirm Dialog +authors: Arian Stolwijk +license: MIT-style license +requires: MooDialog +provides: [MooDialog.Confirm, Element.confirmLinkClick, Element.confirmFormSubmit] +... +*/ + + +MooDialog.Confirm = new Class({ + + Extends: MooDialog, + + options: { + okText: 'Ok', + cancelText: 'Cancel', + focus: true, + textPClass: 'MooDialogConfirm' + }, + + initialize: function(msg, fn, fn1, options){ + this.parent(options); + var emptyFn = function(){}, + self = this; + + var buttons = [ + {fn: fn || emptyFn, txt: this.options.okText}, + {fn: fn1 || emptyFn, txt: this.options.cancelText} + ].map(function(button){ + return new Element('button', { + events: { + click: function(){ + button.fn(); + self.close(); + } + }, + text: button.txt + }); + }); + + this.setContent( + new Element('p.' + this.options.textPClass, {text: msg}), + new Element('div.buttons').adopt(buttons) + ); + if (this.options.autoOpen) this.open(); + + if(this.options.focus) this.addEvent('show', function(){ + buttons[1].focus(); + }); + + } +}); + + +Element.implement({ + + confirmLinkClick: function(msg, options){ + this.addEvent('click', function(e){ + e.stop(); + new MooDialog.Confirm(msg, function(){ + location.href = this.get('href'); + }.bind(this), null, options) + }); + return this; + }, + + confirmFormSubmit: function(msg, options){ + this.addEvent('submit', function(e){ + e.stop(); + new MooDialog.Confirm(msg, function(){ + this.submit(); + }.bind(this), null, options) + }.bind(this)); + return this; + } + +}); diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Error.js b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Error.js new file mode 100644 index 000000000..d32e30bce --- /dev/null +++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Error.js @@ -0,0 +1,21 @@ +/* +--- +name: MooDialog.Error +description: Creates an Error dialog +authors: Arian Stolwijk +license: MIT-style license +requires: MooDialog +provides: MooDialog.Error +... +*/ + + +MooDialog.Error = new Class({ + + Extends: MooDialog.Alert, + + options: { + textPClass: 'MooDialogError' + } + +}); diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Fx.js b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Fx.js new file mode 100644 index 000000000..353d947f5 --- /dev/null +++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Fx.js @@ -0,0 +1,47 @@ +/* +--- +name: MooDialog.Fx +description: Overwrite the default events so the Dialogs are using Fx on open and close +authors: Arian Stolwijk +license: MIT-style license +requires: [Core/Fx.Tween, Overlay] +provides: MooDialog.Fx +... +*/ + + +MooDialog.implement('options', { + + duration: 400, + closeOnOverlayClick: true, + + onInitialize: function(wrapper){ + this.fx = new Fx.Tween(wrapper, { + property: 'opacity', + duration: this.options.duration + }).set(0); + this.overlay = new Overlay(this.options.inject, { + duration: this.options.duration + }); + if (this.options.closeOnOverlayClick) this.overlay.addEvent('click', this.close.bind(this)); + + this.addEvent('hide', function(){ + if (this.options.destroyOnHide) this.overlay.overlay.destroy(); + }.bind(this)); + }, + + onBeforeOpen: function(wrapper){ + this.overlay.open(); + this.fx.start(1).chain(function(){ + this.fireEvent('show'); + }.bind(this)); + }, + + onBeforeClose: function(wrapper){ + this.overlay.close(); + this.fx.start(0).chain(function(){ + this.fireEvent('hide'); + }.bind(this)); + } + +}); diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.IFrame.js b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.IFrame.js new file mode 100644 index 000000000..029bf1f09 --- /dev/null +++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.IFrame.js @@ -0,0 +1,33 @@ +/* +--- +name: MooDialog.IFrame +description: Opens an IFrame in a MooDialog +authors: Arian Stolwijk +license: MIT-style license +requires: MooDialog +provides: MooDialog.IFrame +... +*/ + + +MooDialog.IFrame = new Class({ + + Extends: MooDialog, + + options: { + useScrollBar: true + }, + + initialize: function(url, options){ + this.parent(options); + + this.setContent( + new Element('iframe', { + src: url, + frameborder: 0, + scrolling: this.options.useScrollBar ? 'auto' : 'no' + }) + ); + if (this.options.autoOpen) this.open(); + } +}); diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Prompt.js b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Prompt.js new file mode 100644 index 000000000..c693e4a58 --- /dev/null +++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Prompt.js @@ -0,0 +1,48 @@ +/* +--- +name: MooDialog.Prompt +description: Creates a Prompt dialog +authors: Arian Stolwijk +license: MIT-style license +requires: MooDialog +provides: MooDialog.Prompt +... +*/ + + +MooDialog.Prompt = new Class({ + + Extends: MooDialog, + + options: { + okText: 'Ok', + focus: true, + textPClass: 'MooDialogPrompt', + defaultValue: '' + }, + + initialize: function(msg, fn, options){ + this.parent(options); + if (!fn) fn = function(){}; + + var textInput = new Element('input.textInput', {type: 'text', value: this.options.defaultValue}), + submitButton = new Element('input[type=submit]', {value: this.options.okText}), + formEvents = { + submit: function(e){ + e.stop(); + fn(textInput.get('value')); + this.close(); + }.bind(this) + }; + + this.setContent( + new Element('p.' + this.options.textPClass, {text: msg}), + new Element('form.buttons', {events: formEvents}).adopt(textInput, submitButton) + ); + if (this.options.autoOpen) this.open(); + + if (this.options.focus) this.addEvent('show', function(){ + textInput.focus(); + }); + } +}); diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Request.js b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Request.js new file mode 100644 index 000000000..7b8eb23c4 --- /dev/null +++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.Request.js @@ -0,0 +1,37 @@ +/* +--- +name: MooDialog.Request +description: Loads Data into a Dialog with Request +authors: Arian Stolwijk +license: MIT-style license +requires: [MooDialog, Core/Request.HTML] +provides: MooDialog.Request +... +*/ + +MooDialog.Request = new Class({ + + Extends: MooDialog, + + initialize: function(url, requestOptions, options){ + this.parent(options); + this.requestOptions = requestOptions || {}; + + this.addEvent('open', function(){ + var request = new Request.HTML(this.requestOptions).addEvent('success', function(text){ + this.setContent(text); + }.bind(this)).send({ + url: url + }); + }.bind(this)); + + if (this.options.autoOpen) this.open(); + + }, + + setRequestOptions: function(options){ + this.requestOptions = Object.merge(this.requestOptions, options); + return this; + } + +}); diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.js b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.js new file mode 100644 index 000000000..45a52496f --- /dev/null +++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/MooDialog.js @@ -0,0 +1,140 @@ +/* +--- +name: MooDialog +description: The base class of MooDialog +authors: Arian Stolwijk +license: MIT-style license +requires: [Core/Class, Core/Element, Core/Element.Style, Core/Element.Event] +provides: [MooDialog, Element.MooDialog] +... +*/ + + +var MooDialog = new Class({ + + Implements: [Options, Events], + + options: { + 'class': 'MooDialog', + title: null, + scroll: true, // IE + forceScroll: false, + useEscKey: true, + destroyOnHide: true, + autoOpen: true, + closeButton: true, + onInitialize: function(){ + this.wrapper.setStyle('display', 'none'); + }, + onBeforeOpen: function(){ + this.wrapper.setStyle('display', 'block'); + this.fireEvent('show'); + }, + onBeforeClose: function(){ + this.wrapper.setStyle('display', 'none'); + this.fireEvent('hide'); + }/*, + onOpen: function(){}, + onClose: function(){}, + onShow: function(){}, + onHide: function(){}, + onInitialize: function(wrapper){}, + onContentChange: function(content){}*/ + }, + + initialize: function(options){ + this.setOptions(options); + this.options.inject = this.options.inject || document.body; + options = this.options; + + var wrapper = this.wrapper = new Element('div.' + options['class'].replace(' ', '.')).inject(options.inject); + this.content = new Element('div.content').inject(wrapper); + + if (options.title){ + this.title = new Element('div.title').set('text', options.title).inject(wrapper); + wrapper.addClass('MooDialogTitle'); + } + + if (options.closeButton){ + this.closeButton = new Element('a.close', { + events: {click: this.close.bind(this)} + }).inject(wrapper); + } + + + /*<ie6>*/// IE 6 scroll + if ((options.scroll && Browser.ie6) || options.forceScroll){ + wrapper.setStyle('position', 'absolute'); + var position = wrapper.getPosition(options.inject); + window.addEvent('scroll', function(){ + var scroll = document.getScroll(); + wrapper.setPosition({ + x: position.x + scroll.x, + y: position.y + scroll.y + }); + }); + } + /*</ie6>*/ + + if (options.useEscKey){ + // Add event for the esc key + document.addEvent('keydown', function(e){ + if (e.key == 'esc') this.close(); + }.bind(this)); + } + + this.addEvent('hide', function(){ + if (options.destroyOnHide) this.destroy(); + }.bind(this)); + + this.fireEvent('initialize', wrapper); + }, + + setContent: function(){ + var content = Array.from(arguments); + if (content.length == 1) content = content[0]; + + this.content.empty(); + + var type = typeOf(content); + if (['string', 'number'].contains(type)) this.content.set('text', content); + else this.content.adopt(content); + + this.fireEvent('contentChange', this.content); + + return this; + }, + + open: function(){ + this.fireEvent('beforeOpen', this.wrapper).fireEvent('open'); + this.opened = true; + return this; + }, + + close: function(){ + this.fireEvent('beforeClose', this.wrapper).fireEvent('close'); + this.opened = false; + return this; + }, + + destroy: function(){ + this.wrapper.destroy(); + }, + + toElement: function(){ + return this.wrapper; + } + +}); + + +Element.implement({ + + MooDialog: function(options){ + this.store('MooDialog', + new MooDialog(options).setContent(this).open() + ); + return this; + } + +}); diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooDialog/Overlay.js b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/Overlay.js new file mode 100644 index 000000000..35ab19c48 --- /dev/null +++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/Overlay.js @@ -0,0 +1,137 @@ +/* +--- + +name: Overlay + +authors: + - David Walsh (http://davidwalsh.name) + +license: + - MIT-style license + +requires: [Core/Class, Core/Element.Style, Core/Element.Event, Core/Element.Dimensions, Core/Fx.Tween] + +provides: + - Overlay +... +*/ + +var Overlay = new Class({ + + Implements: [Options, Events], + + options: { + id: 'overlay', + color: '#000', + duration: 500, + opacity: 0.5, + zIndex: 5000/*, + onClick: function(){}, + onClose: function(){}, + onHide: function(){}, + onOpen: function(){}, + onShow: function(){} + */ + }, + + initialize: function(container, options){ + this.setOptions(options); + this.container = document.id(container); + + this.bound = { + 'window': { + resize: this.resize.bind(this), + scroll: this.scroll.bind(this) + }, + overlayClick: this.overlayClick.bind(this), + tweenStart: this.tweenStart.bind(this), + tweenComplete: this.tweenComplete.bind(this) + }; + + this.build().attach(); + }, + + build: function(){ + this.overlay = new Element('div', { + id: this.options.id, + styles: { + position: (Browser.ie6) ? 'absolute' : 'fixed', + background: this.options.color, + left: 0, + top: 0, + 'z-index': this.options.zIndex, + opacity: 0 + } + }).inject(this.container); + this.tween = new Fx.Tween(this.overlay, { + duration: this.options.duration, + link: 'cancel', + property: 'opacity' + }); + return this; + }.protect(), + + attach: function(){ + window.addEvents(this.bound.window); + this.overlay.addEvent('click', this.bound.overlayClick); + this.tween.addEvents({ + onStart: this.bound.tweenStart, + onComplete: this.bound.tweenComplete + }); + return this; + }, + + detach: function(){ + var args = Array.prototype.slice.call(arguments); + args.each(function(item){ + if(item == 'window') window.removeEvents(this.bound.window); + if(item == 'overlay') this.overlay.removeEvent('click', this.bound.overlayClick); + }, this); + return this; + }, + + overlayClick: function(){ + this.fireEvent('click'); + return this; + }, + + tweenStart: function(){ + this.overlay.setStyles({ + width: '100%', + height: this.container.getScrollSize().y, + visibility: 'visible' + }); + return this; + }, + + tweenComplete: function(){ + var event = this.overlay.getStyle('opacity') == this.options.opacity ? 'show' : 'hide'; + if (event == 'hide') this.overlay.setStyle('visibility', 'hidden'); + return this; + }, + + open: function(){ + this.fireEvent('open'); + this.tween.start(this.options.opacity); + return this; + }, + + close: function(){ + this.fireEvent('close'); + this.tween.start(0); + return this; + }, + + resize: function(){ + this.fireEvent('resize'); + this.overlay.setStyle('height', this.container.getScrollSize().y); + return this; + }, + + scroll: function(){ + this.fireEvent('scroll'); + if (Browser.ie6) this.overlay.setStyle('left', window.getScroll().x); + return this; + } + +}); diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/MooDialog.css b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/MooDialog.css new file mode 100644 index 000000000..c88773ae9 --- /dev/null +++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/MooDialog.css @@ -0,0 +1,95 @@ +/* Created by Arian Stolwijk <http://www.aryweb.nl> */ + +.MooDialog { + position: fixed; + width: 300px; + height: 100px; + top: 50%; + left: 50%; + margin: -150px 0 0 -150px; + padding: 10px; + z-index: 50000; + + background: #eef5f8; + color: black; + border-radius: 7px; + -moz-border-radius: 7px; + -webkit-border-radius: 7px; + border-radius: 7px; + -moz-box-shadow: 1px 1px 5px rgba(0,0,0,0.8); + -webkit-box-shadow: 1px 1px 5px rgba(0,0,0,0.8); + box-shadow: 1px 1px 5px rgba(0,0,0,0.8); +} + +.MooDialogTitle { + padding-top: 30px; +} + +.MooDialog .content { + height: 100px; +} + +.MooDialog .title { + position: absolute; + top: 0; + left: 0; + right: 0; + padding: 3px 20px; + + background: #b7c4dc; + border-bottom: 1px solid #a1aec5; + font-weight: bold; + text-shadow: 1px 1px 0 #fff; + color: black; + border-radius: 7px; + -moz-border-radius: 7px; + -webkit-border-radius: 7px; +} + +.MooDialog .close { + position: absolute; + width: 16px; + height: 16px; + top: -5px; + left: -5px; + + background: url(dialog-close.png) no-repeat; + display: block; + cursor: pointer; +} + +.MooDialog .buttons { + margin: 0; + padding: 0; + border: 0; + background: none; + text-align: right; +} + +.MooDialog .iframe { + width: 100%; + height: 100%; +} + +.MooDialog .textInput { + width: 200px; + float: left; +} + +.MooDialog .MooDialogAlert, +.MooDialog .MooDialogConfirm, +.MooDialog .MooDialogPrompt, +.MooDialog .MooDialogError { + padding-left: 40px; + min-height: 40px; + background: url(dialog-warning.png) no-repeat; +} + +.MooDialog .MooDialogConfirm, +.MooDialog .MooDialogPrompt { + background: url(dialog-question.png) no-repeat; +} + +.MooDialog .MooDialogError { + background: url(dialog-error.png) no-repeat; +} diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/dialog-close.png b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/dialog-close.png Binary files differnew file mode 100644 index 000000000..81ebb88b2 --- /dev/null +++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/dialog-close.png diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/dialog-error.png b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/dialog-error.png Binary files differnew file mode 100644 index 000000000..d70328403 --- /dev/null +++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/dialog-error.png diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/dialog-question.png b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/dialog-question.png Binary files differnew file mode 100644 index 000000000..b0af3db5b --- /dev/null +++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/dialog-question.png diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/dialog-warning.png b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/dialog-warning.png Binary files differnew file mode 100644 index 000000000..aad64d4be --- /dev/null +++ b/pyload/webui/themes/Dark/lib/MooTools/MooDialog/css/dialog-warning.png diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooDropMenu/MooDropMenu.js b/pyload/webui/themes/Dark/lib/MooTools/MooDropMenu/MooDropMenu.js new file mode 100644 index 000000000..ac0fa1874 --- /dev/null +++ b/pyload/webui/themes/Dark/lib/MooTools/MooDropMenu/MooDropMenu.js @@ -0,0 +1,86 @@ +/* +--- +description: This provides a simple Drop Down menu with infinit levels + +license: MIT-style + +authors: +- Arian Stolwijk + +requires: + - Core/Class.Extras + - Core/Element.Event + - Core/Selectors + +provides: [MooDropMenu, Element.MooDropMenu] + +... +*/ + +var MooDropMenu = new Class({ + + Implements: [Options, Events], + + options: { + onOpen: function(el){ + el.removeClass('close').addClass('open'); + }, + onClose: function(el){ + el.removeClass('open').addClass('close'); + }, + onInitialize: function(el){ + el.removeClass('open').addClass('close'); + }, + mouseoutDelay: 200, + mouseoverDelay: 0, + listSelector: 'ul', + itemSelector: 'li', + openEvent: 'mouseenter', + closeEvent: 'mouseleave' + }, + + initialize: function(menu, options, level){ + this.setOptions(options); + options = this.options; + + var menu = this.menu = document.id(menu); + + menu.getElements(options.itemSelector + ' > ' + options.listSelector).each(function(el){ + + this.fireEvent('initialize', el); + + var parent = el.getParent(options.itemSelector), + timer; + + parent.addEvent(options.openEvent, function(){ + parent.store('DropDownOpen', true); + + clearTimeout(timer); + if (options.mouseoverDelay) timer = this.fireEvent.delay(options.mouseoverDelay, this, ['open', el]); + else this.fireEvent('open', el); + + }.bind(this)).addEvent(options.closeEvent, function(){ + parent.store('DropDownOpen', false); + + clearTimeout(timer); + timer = (function(){ + if (!parent.retrieve('DropDownOpen')) this.fireEvent('close', el); + }).delay(options.mouseoutDelay, this); + + }.bind(this)); + + }, this); + }, + + toElement: function(){ + return this.menu + } + +}); + +/* So you can do like this $('nav').MooDropMenu(); or even $('nav').MooDropMenu().setStyle('border',1); */ +Element.implement({ + MooDropMenu: function(options){ + return this.store('MooDropMenu', new MooDropMenu(this, options)); + } +}); diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooDropMenu/css/MooDropMenu.css b/pyload/webui/themes/Dark/lib/MooTools/MooDropMenu/css/MooDropMenu.css new file mode 100644 index 000000000..e08c2f9fa --- /dev/null +++ b/pyload/webui/themes/Dark/lib/MooTools/MooDropMenu/css/MooDropMenu.css @@ -0,0 +1,66 @@ +#nav { + margin: 0; + padding: 0; + list-style: none; +} + +#nav > li { + background: #F5F5F5; + border-bottom: 1px solid #aaa; +} + +#nav li { + position: relative; + float: left; + padding: 5px; +} + + +#nav ul { + position: absolute; + top: 26px; + left: 10px; + margin: 0; + padding: 0; + list-style: none; + width: 136px; + border: 1px solid #AAA; + background: #f1f1f1; + -webkit-box-shadow: 1px 1px 5px #AAA; + -moz-box-shadow: 1px 1px 5px #AAA; + box-shadow: 1px 1px 5px #AAA; +} + + +#nav .open { + display: block; +} + +#nav .close { + display: none; +} + +#nav ul li { + float: none; + padding: 0; +} + +#nav ul li a { + width: 130px; + _width: 127px; + background: #f1f1f1; + padding: 3px; + display: block; + _float: left; + font-weight: normal; +} + +#nav ul li a:hover { + background: #CDCDCD; +} + +#nav ul ul { + left: 137px; + _left: 0; + top: 0; +} diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooTools-Core.js b/pyload/webui/themes/Dark/lib/MooTools/MooTools-Core.js new file mode 100644 index 000000000..e0bea6df6 --- /dev/null +++ b/pyload/webui/themes/Dark/lib/MooTools/MooTools-Core.js @@ -0,0 +1,6068 @@ +/* MooTools: the javascript framework. license: MIT-style license. copyright: Copyright (c) 2006-2015 [Valerio Proietti](http://mad4milk.net/).*/ +/* +Web Build: http://mootools.net/core/builder/e426a9ae7167c5807b173d5deff673fc +*/ +/* +--- + +name: Core + +description: The heart of MooTools. + +license: MIT-style license. + +copyright: Copyright (c) 2006-2014 [Valerio Proietti](http://mad4milk.net/). + +authors: The MooTools production team (http://mootools.net/developers/) + +inspiration: + - Class implementation inspired by [Base.js](http://dean.edwards.name/weblog/2006/03/base/) Copyright (c) 2006 Dean Edwards, [GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php) + - Some functionality inspired by [Prototype.js](http://prototypejs.org) Copyright (c) 2005-2007 Sam Stephenson, [MIT License](http://opensource.org/licenses/mit-license.php) + +provides: [Core, MooTools, Type, typeOf, instanceOf, Native] + +... +*/ +/*! MooTools: the javascript framework. license: MIT-style license. copyright: Copyright (c) 2006-2014 [Valerio Proietti](http://mad4milk.net/).*/ +(function(){ + +this.MooTools = { + version: '1.5.1', + build: '0542c135fdeb7feed7d9917e01447a408f22c876' +}; + +// typeOf, instanceOf + +var typeOf = this.typeOf = function(item){ + if (item == null) return 'null'; + if (item.$family != null) return item.$family(); + + if (item.nodeName){ + if (item.nodeType == 1) return 'element'; + if (item.nodeType == 3) return (/\S/).test(item.nodeValue) ? 'textnode' : 'whitespace'; + } else if (typeof item.length == 'number'){ + if ('callee' in item) return 'arguments'; + if ('item' in item) return 'collection'; + } + + return typeof item; +}; + +var instanceOf = this.instanceOf = function(item, object){ + if (item == null) return false; + var constructor = item.$constructor || item.constructor; + while (constructor){ + if (constructor === object) return true; + constructor = constructor.parent; + } + /*<ltIE8>*/ + if (!item.hasOwnProperty) return false; + /*</ltIE8>*/ + return item instanceof object; +}; + +// Function overloading + +var Function = this.Function; + +var enumerables = true; +for (var i in {toString: 1}) enumerables = null; +if (enumerables) enumerables = ['hasOwnProperty', 'valueOf', 'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString', 'toString', 'constructor']; + +Function.prototype.overloadSetter = function(usePlural){ + var self = this; + return function(a, b){ + if (a == null) return this; + if (usePlural || typeof a != 'string'){ + for (var k in a) self.call(this, k, a[k]); + if (enumerables) for (var i = enumerables.length; i--;){ + k = enumerables[i]; + if (a.hasOwnProperty(k)) self.call(this, k, a[k]); + } + } else { + self.call(this, a, b); + } + return this; + }; +}; + +Function.prototype.overloadGetter = function(usePlural){ + var self = this; + return function(a){ + var args, result; + if (typeof a != 'string') args = a; + else if (arguments.length > 1) args = arguments; + else if (usePlural) args = [a]; + if (args){ + result = {}; + for (var i = 0; i < args.length; i++) result[args[i]] = self.call(this, args[i]); + } else { + result = self.call(this, a); + } + return result; + }; +}; + +Function.prototype.extend = function(key, value){ + this[key] = value; +}.overloadSetter(); + +Function.prototype.implement = function(key, value){ + this.prototype[key] = value; +}.overloadSetter(); + +// From + +var slice = Array.prototype.slice; + +Function.from = function(item){ + return (typeOf(item) == 'function') ? item : function(){ + return item; + }; +}; + +Array.from = function(item){ + if (item == null) return []; + return (Type.isEnumerable(item) && typeof item != 'string') ? (typeOf(item) == 'array') ? item : slice.call(item) : [item]; +}; + +Number.from = function(item){ + var number = parseFloat(item); + return isFinite(number) ? number : null; +}; + +String.from = function(item){ + return item + ''; +}; + +// hide, protect + +Function.implement({ + + hide: function(){ + this.$hidden = true; + return this; + }, + + protect: function(){ + this.$protected = true; + return this; + } + +}); + +// Type + +var Type = this.Type = function(name, object){ + if (name){ + var lower = name.toLowerCase(); + var typeCheck = function(item){ + return (typeOf(item) == lower); + }; + + Type['is' + name] = typeCheck; + if (object != null){ + object.prototype.$family = (function(){ + return lower; + }).hide(); + + } + } + + if (object == null) return null; + + object.extend(this); + object.$constructor = Type; + object.prototype.$constructor = object; + + return object; +}; + +var toString = Object.prototype.toString; + +Type.isEnumerable = function(item){ + return (item != null && typeof item.length == 'number' && toString.call(item) != '[object Function]' ); +}; + +var hooks = {}; + +var hooksOf = function(object){ + var type = typeOf(object.prototype); + return hooks[type] || (hooks[type] = []); +}; + +var implement = function(name, method){ + if (method && method.$hidden) return; + + var hooks = hooksOf(this); + + for (var i = 0; i < hooks.length; i++){ + var hook = hooks[i]; + if (typeOf(hook) == 'type') implement.call(hook, name, method); + else hook.call(this, name, method); + } + + var previous = this.prototype[name]; + if (previous == null || !previous.$protected) this.prototype[name] = method; + + if (this[name] == null && typeOf(method) == 'function') extend.call(this, name, function(item){ + return method.apply(item, slice.call(arguments, 1)); + }); +}; + +var extend = function(name, method){ + if (method && method.$hidden) return; + var previous = this[name]; + if (previous == null || !previous.$protected) this[name] = method; +}; + +Type.implement({ + + implement: implement.overloadSetter(), + + extend: extend.overloadSetter(), + + alias: function(name, existing){ + implement.call(this, name, this.prototype[existing]); + }.overloadSetter(), + + mirror: function(hook){ + hooksOf(this).push(hook); + return this; + } + +}); + +new Type('Type', Type); + +// Default Types + +var force = function(name, object, methods){ + var isType = (object != Object), + prototype = object.prototype; + + if (isType) object = new Type(name, object); + + for (var i = 0, l = methods.length; i < l; i++){ + var key = methods[i], + generic = object[key], + proto = prototype[key]; + + if (generic) generic.protect(); + if (isType && proto) object.implement(key, proto.protect()); + } + + if (isType){ + var methodsEnumerable = prototype.propertyIsEnumerable(methods[0]); + object.forEachMethod = function(fn){ + if (!methodsEnumerable) for (var i = 0, l = methods.length; i < l; i++){ + fn.call(prototype, prototype[methods[i]], methods[i]); + } + for (var key in prototype) fn.call(prototype, prototype[key], key); + }; + } + + return force; +}; + +force('String', String, [ + 'charAt', 'charCodeAt', 'concat', 'contains', 'indexOf', 'lastIndexOf', 'match', 'quote', 'replace', 'search', + 'slice', 'split', 'substr', 'substring', 'trim', 'toLowerCase', 'toUpperCase' +])('Array', Array, [ + 'pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift', 'concat', 'join', 'slice', + 'indexOf', 'lastIndexOf', 'filter', 'forEach', 'every', 'map', 'some', 'reduce', 'reduceRight' +])('Number', Number, [ + 'toExponential', 'toFixed', 'toLocaleString', 'toPrecision' +])('Function', Function, [ + 'apply', 'call', 'bind' +])('RegExp', RegExp, [ + 'exec', 'test' +])('Object', Object, [ + 'create', 'defineProperty', 'defineProperties', 'keys', + 'getPrototypeOf', 'getOwnPropertyDescriptor', 'getOwnPropertyNames', + 'preventExtensions', 'isExtensible', 'seal', 'isSealed', 'freeze', 'isFrozen' +])('Date', Date, ['now']); + +Object.extend = extend.overloadSetter(); + +Date.extend('now', function(){ + return +(new Date); +}); + +new Type('Boolean', Boolean); + +// fixes NaN returning as Number + +Number.prototype.$family = function(){ + return isFinite(this) ? 'number' : 'null'; +}.hide(); + +// Number.random + +Number.extend('random', function(min, max){ + return Math.floor(Math.random() * (max - min + 1) + min); +}); + +// forEach, each + +var hasOwnProperty = Object.prototype.hasOwnProperty; +Object.extend('forEach', function(object, fn, bind){ + for (var key in object){ + if (hasOwnProperty.call(object, key)) fn.call(bind, object[key], key, object); + } +}); + +Object.each = Object.forEach; + +Array.implement({ + + /*<!ES5>*/ + forEach: function(fn, bind){ + for (var i = 0, l = this.length; i < l; i++){ + if (i in this) fn.call(bind, this[i], i, this); + } + }, + /*</!ES5>*/ + + each: function(fn, bind){ + Array.forEach(this, fn, bind); + return this; + } + +}); + +// Array & Object cloning, Object merging and appending + +var cloneOf = function(item){ + switch (typeOf(item)){ + case 'array': return item.clone(); + case 'object': return Object.clone(item); + default: return item; + } +}; + +Array.implement('clone', function(){ + var i = this.length, clone = new Array(i); + while (i--) clone[i] = cloneOf(this[i]); + return clone; +}); + +var mergeOne = function(source, key, current){ + switch (typeOf(current)){ + case 'object': + if (typeOf(source[key]) == 'object') Object.merge(source[key], current); + else source[key] = Object.clone(current); + break; + case 'array': source[key] = current.clone(); break; + default: source[key] = current; + } + return source; +}; + +Object.extend({ + + merge: function(source, k, v){ + if (typeOf(k) == 'string') return mergeOne(source, k, v); + for (var i = 1, l = arguments.length; i < l; i++){ + var object = arguments[i]; + for (var key in object) mergeOne(source, key, object[key]); + } + return source; + }, + + clone: function(object){ + var clone = {}; + for (var key in object) clone[key] = cloneOf(object[key]); + return clone; + }, + + append: function(original){ + for (var i = 1, l = arguments.length; i < l; i++){ + var extended = arguments[i] || {}; + for (var key in extended) original[key] = extended[key]; + } + return original; + } + +}); + +// Object-less types + +['Object', 'WhiteSpace', 'TextNode', 'Collection', 'Arguments'].each(function(name){ + new Type(name); +}); + +// Unique ID + +var UID = Date.now(); + +String.extend('uniqueID', function(){ + return (UID++).toString(36); +}); + + + +})(); + +/* +--- + +name: Array + +description: Contains Array Prototypes like each, contains, and erase. + +license: MIT-style license. + +requires: [Type] + +provides: Array + +... +*/ + +Array.implement({ + + /*<!ES5>*/ + every: function(fn, bind){ + for (var i = 0, l = this.length >>> 0; i < l; i++){ + if ((i in this) && !fn.call(bind, this[i], i, this)) return false; + } + return true; + }, + + filter: function(fn, bind){ + var results = []; + for (var value, i = 0, l = this.length >>> 0; i < l; i++) if (i in this){ + value = this[i]; + if (fn.call(bind, value, i, this)) results.push(value); + } + return results; + }, + + indexOf: function(item, from){ + var length = this.length >>> 0; + for (var i = (from < 0) ? Math.max(0, length + from) : from || 0; i < length; i++){ + if (this[i] === item) return i; + } + return -1; + }, + + map: function(fn, bind){ + var length = this.length >>> 0, results = Array(length); + for (var i = 0; i < length; i++){ + if (i in this) results[i] = fn.call(bind, this[i], i, this); + } + return results; + }, + + some: function(fn, bind){ + for (var i = 0, l = this.length >>> 0; i < l; i++){ + if ((i in this) && fn.call(bind, this[i], i, this)) return true; + } + return false; + }, + /*</!ES5>*/ + + clean: function(){ + return this.filter(function(item){ + return item != null; + }); + }, + + invoke: function(methodName){ + var args = Array.slice(arguments, 1); + return this.map(function(item){ + return item[methodName].apply(item, args); + }); + }, + + associate: function(keys){ + var obj = {}, length = Math.min(this.length, keys.length); + for (var i = 0; i < length; i++) obj[keys[i]] = this[i]; + return obj; + }, + + link: function(object){ + var result = {}; + for (var i = 0, l = this.length; i < l; i++){ + for (var key in object){ + if (object[key](this[i])){ + result[key] = this[i]; + delete object[key]; + break; + } + } + } + return result; + }, + + contains: function(item, from){ + return this.indexOf(item, from) != -1; + }, + + append: function(array){ + this.push.apply(this, array); + return this; + }, + + getLast: function(){ + return (this.length) ? this[this.length - 1] : null; + }, + + getRandom: function(){ + return (this.length) ? this[Number.random(0, this.length - 1)] : null; + }, + + include: function(item){ + if (!this.contains(item)) this.push(item); + return this; + }, + + combine: function(array){ + for (var i = 0, l = array.length; i < l; i++) this.include(array[i]); + return this; + }, + + erase: function(item){ + for (var i = this.length; i--;){ + if (this[i] === item) this.splice(i, 1); + } + return this; + }, + + empty: function(){ + this.length = 0; + return this; + }, + + flatten: function(){ + var array = []; + for (var i = 0, l = this.length; i < l; i++){ + var type = typeOf(this[i]); + if (type == 'null') continue; + array = array.concat((type == 'array' || type == 'collection' || type == 'arguments' || instanceOf(this[i], Array)) ? Array.flatten(this[i]) : this[i]); + } + return array; + }, + + pick: function(){ + for (var i = 0, l = this.length; i < l; i++){ + if (this[i] != null) return this[i]; + } + return null; + }, + + hexToRgb: function(array){ + if (this.length != 3) return null; + var rgb = this.map(function(value){ + if (value.length == 1) value += value; + return parseInt(value, 16); + }); + return (array) ? rgb : 'rgb(' + rgb + ')'; + }, + + rgbToHex: function(array){ + if (this.length < 3) return null; + if (this.length == 4 && this[3] == 0 && !array) return 'transparent'; + var hex = []; + for (var i = 0; i < 3; i++){ + var bit = (this[i] - 0).toString(16); + hex.push((bit.length == 1) ? '0' + bit : bit); + } + return (array) ? hex : '#' + hex.join(''); + } + +}); + + + +/* +--- + +name: Function + +description: Contains Function Prototypes like create, bind, pass, and delay. + +license: MIT-style license. + +requires: Type + +provides: Function + +... +*/ + +Function.extend({ + + attempt: function(){ + for (var i = 0, l = arguments.length; i < l; i++){ + try { + return arguments[i](); + } catch (e){} + } + return null; + } + +}); + +Function.implement({ + + attempt: function(args, bind){ + try { + return this.apply(bind, Array.from(args)); + } catch (e){} + + return null; + }, + + /*<!ES5-bind>*/ + bind: function(that){ + var self = this, + args = arguments.length > 1 ? Array.slice(arguments, 1) : null, + F = function(){}; + + var bound = function(){ + var context = that, length = arguments.length; + if (this instanceof bound){ + F.prototype = self.prototype; + context = new F; + } + var result = (!args && !length) + ? self.call(context) + : self.apply(context, args && length ? args.concat(Array.slice(arguments)) : args || arguments); + return context == that ? result : context; + }; + return bound; + }, + /*</!ES5-bind>*/ + + pass: function(args, bind){ + var self = this; + if (args != null) args = Array.from(args); + return function(){ + return self.apply(bind, args || arguments); + }; + }, + + delay: function(delay, bind, args){ + return setTimeout(this.pass((args == null ? [] : args), bind), delay); + }, + + periodical: function(periodical, bind, args){ + return setInterval(this.pass((args == null ? [] : args), bind), periodical); + } + +}); + + + +/* +--- + +name: Number + +description: Contains Number Prototypes like limit, round, times, and ceil. + +license: MIT-style license. + +requires: Type + +provides: Number + +... +*/ + +Number.implement({ + + limit: function(min, max){ + return Math.min(max, Math.max(min, this)); + }, + + round: function(precision){ + precision = Math.pow(10, precision || 0).toFixed(precision < 0 ? -precision : 0); + return Math.round(this * precision) / precision; + }, + + times: function(fn, bind){ + for (var i = 0; i < this; i++) fn.call(bind, i, this); + }, + + toFloat: function(){ + return parseFloat(this); + }, + + toInt: function(base){ + return parseInt(this, base || 10); + } + +}); + +Number.alias('each', 'times'); + +(function(math){ + var methods = {}; + math.each(function(name){ + if (!Number[name]) methods[name] = function(){ + return Math[name].apply(null, [this].concat(Array.from(arguments))); + }; + }); + Number.implement(methods); +})(['abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', 'log', 'max', 'min', 'pow', 'sin', 'sqrt', 'tan']); + +/* +--- + +name: String + +description: Contains String Prototypes like camelCase, capitalize, test, and toInt. + +license: MIT-style license. + +requires: [Type, Array] + +provides: String + +... +*/ + +String.implement({ + + //<!ES6> + contains: function(string, index){ + return (index ? String(this).slice(index) : String(this)).indexOf(string) > -1; + }, + //</!ES6> + + test: function(regex, params){ + return ((typeOf(regex) == 'regexp') ? regex : new RegExp('' + regex, params)).test(this); + }, + + trim: function(){ + return String(this).replace(/^\s+|\s+$/g, ''); + }, + + clean: function(){ + return String(this).replace(/\s+/g, ' ').trim(); + }, + + camelCase: function(){ + return String(this).replace(/-\D/g, function(match){ + return match.charAt(1).toUpperCase(); + }); + }, + + hyphenate: function(){ + return String(this).replace(/[A-Z]/g, function(match){ + return ('-' + match.charAt(0).toLowerCase()); + }); + }, + + capitalize: function(){ + return String(this).replace(/\b[a-z]/g, function(match){ + return match.toUpperCase(); + }); + }, + + escapeRegExp: function(){ + return String(this).replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1'); + }, + + toInt: function(base){ + return parseInt(this, base || 10); + }, + + toFloat: function(){ + return parseFloat(this); + }, + + hexToRgb: function(array){ + var hex = String(this).match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/); + return (hex) ? hex.slice(1).hexToRgb(array) : null; + }, + + rgbToHex: function(array){ + var rgb = String(this).match(/\d{1,3}/g); + return (rgb) ? rgb.rgbToHex(array) : null; + }, + + substitute: function(object, regexp){ + return String(this).replace(regexp || (/\\?\{([^{}]+)\}/g), function(match, name){ + if (match.charAt(0) == '\\') return match.slice(1); + return (object[name] != null) ? object[name] : ''; + }); + } + +}); + + + +/* +--- + +name: Browser + +description: The Browser Object. Contains Browser initialization, Window and Document, and the Browser Hash. + +license: MIT-style license. + +requires: [Array, Function, Number, String] + +provides: [Browser, Window, Document] + +... +*/ + +(function(){ + +var document = this.document; +var window = document.window = this; + +var parse = function(ua, platform){ + ua = ua.toLowerCase(); + platform = (platform ? platform.toLowerCase() : ''); + + var UA = ua.match(/(opera|ie|firefox|chrome|trident|crios|version)[\s\/:]([\w\d\.]+)?.*?(safari|(?:rv[\s\/:]|version[\s\/:])([\w\d\.]+)|$)/) || [null, 'unknown', 0]; + + if (UA[1] == 'trident'){ + UA[1] = 'ie'; + if (UA[4]) UA[2] = UA[4]; + } else if (UA[1] == 'crios'){ + UA[1] = 'chrome'; + } + + platform = ua.match(/ip(?:ad|od|hone)/) ? 'ios' : (ua.match(/(?:webos|android)/) || platform.match(/mac|win|linux/) || ['other'])[0]; + if (platform == 'win') platform = 'windows'; + + return { + extend: Function.prototype.extend, + name: (UA[1] == 'version') ? UA[3] : UA[1], + version: parseFloat((UA[1] == 'opera' && UA[4]) ? UA[4] : UA[2]), + platform: platform + }; +}; + +var Browser = this.Browser = parse(navigator.userAgent, navigator.platform); + +if (Browser.name == 'ie'){ + Browser.version = document.documentMode; +} + +Browser.extend({ + Features: { + xpath: !!(document.evaluate), + air: !!(window.runtime), + query: !!(document.querySelector), + json: !!(window.JSON) + }, + parseUA: parse +}); + + + +// Request + +Browser.Request = (function(){ + + var XMLHTTP = function(){ + return new XMLHttpRequest(); + }; + + var MSXML2 = function(){ + return new ActiveXObject('MSXML2.XMLHTTP'); + }; + + var MSXML = function(){ + return new ActiveXObject('Microsoft.XMLHTTP'); + }; + + return Function.attempt(function(){ + XMLHTTP(); + return XMLHTTP; + }, function(){ + MSXML2(); + return MSXML2; + }, function(){ + MSXML(); + return MSXML; + }); + +})(); + +Browser.Features.xhr = !!(Browser.Request); + + + +// String scripts + +Browser.exec = function(text){ + if (!text) return text; + if (window.execScript){ + window.execScript(text); + } else { + var script = document.createElement('script'); + script.setAttribute('type', 'text/javascript'); + script.text = text; + document.head.appendChild(script); + document.head.removeChild(script); + } + return text; +}; + +String.implement('stripScripts', function(exec){ + var scripts = ''; + var text = this.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi, function(all, code){ + scripts += code + '\n'; + return ''; + }); + if (exec === true) Browser.exec(scripts); + else if (typeOf(exec) == 'function') exec(scripts, text); + return text; +}); + +// Window, Document + +Browser.extend({ + Document: this.Document, + Window: this.Window, + Element: this.Element, + Event: this.Event +}); + +this.Window = this.$constructor = new Type('Window', function(){}); + +this.$family = Function.from('window').hide(); + +Window.mirror(function(name, method){ + window[name] = method; +}); + +this.Document = document.$constructor = new Type('Document', function(){}); + +document.$family = Function.from('document').hide(); + +Document.mirror(function(name, method){ + document[name] = method; +}); + +document.html = document.documentElement; +if (!document.head) document.head = document.getElementsByTagName('head')[0]; + +if (document.execCommand) try { + document.execCommand("BackgroundImageCache", false, true); +} catch (e){} + +/*<ltIE9>*/ +if (this.attachEvent && !this.addEventListener){ + var unloadEvent = function(){ + this.detachEvent('onunload', unloadEvent); + document.head = document.html = document.window = null; + window = this.Window = document = null; + }; + this.attachEvent('onunload', unloadEvent); +} + +// IE fails on collections and <select>.options (refers to <select>) +var arrayFrom = Array.from; +try { + arrayFrom(document.html.childNodes); +} catch(e){ + Array.from = function(item){ + if (typeof item != 'string' && Type.isEnumerable(item) && typeOf(item) != 'array'){ + var i = item.length, array = new Array(i); + while (i--) array[i] = item[i]; + return array; + } + return arrayFrom(item); + }; + + var prototype = Array.prototype, + slice = prototype.slice; + ['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift', 'concat', 'join', 'slice'].each(function(name){ + var method = prototype[name]; + Array[name] = function(item){ + return method.apply(Array.from(item), slice.call(arguments, 1)); + }; + }); +} +/*</ltIE9>*/ + + + +})(); + +/* +--- + +name: Class + +description: Contains the Class Function for easily creating, extending, and implementing reusable Classes. + +license: MIT-style license. + +requires: [Array, String, Function, Number] + +provides: Class + +... +*/ + +(function(){ + +var Class = this.Class = new Type('Class', function(params){ + if (instanceOf(params, Function)) params = {initialize: params}; + + var newClass = function(){ + reset(this); + if (newClass.$prototyping) return this; + this.$caller = null; + var value = (this.initialize) ? this.initialize.apply(this, arguments) : this; + this.$caller = this.caller = null; + return value; + }.extend(this).implement(params); + + newClass.$constructor = Class; + newClass.prototype.$constructor = newClass; + newClass.prototype.parent = parent; + + return newClass; +}); + +var parent = function(){ + if (!this.$caller) throw new Error('The method "parent" cannot be called.'); + var name = this.$caller.$name, + parent = this.$caller.$owner.parent, + previous = (parent) ? parent.prototype[name] : null; + if (!previous) throw new Error('The method "' + name + '" has no parent.'); + return previous.apply(this, arguments); +}; + +var reset = function(object){ + for (var key in object){ + var value = object[key]; + switch (typeOf(value)){ + case 'object': + var F = function(){}; + F.prototype = value; + object[key] = reset(new F); + break; + case 'array': object[key] = value.clone(); break; + } + } + return object; +}; + +var wrap = function(self, key, method){ + if (method.$origin) method = method.$origin; + var wrapper = function(){ + if (method.$protected && this.$caller == null) throw new Error('The method "' + key + '" cannot be called.'); + var caller = this.caller, current = this.$caller; + this.caller = current; this.$caller = wrapper; + var result = method.apply(this, arguments); + this.$caller = current; this.caller = caller; + return result; + }.extend({$owner: self, $origin: method, $name: key}); + return wrapper; +}; + +var implement = function(key, value, retain){ + if (Class.Mutators.hasOwnProperty(key)){ + value = Class.Mutators[key].call(this, value); + if (value == null) return this; + } + + if (typeOf(value) == 'function'){ + if (value.$hidden) return this; + this.prototype[key] = (retain) ? value : wrap(this, key, value); + } else { + Object.merge(this.prototype, key, value); + } + + return this; +}; + +var getInstance = function(klass){ + klass.$prototyping = true; + var proto = new klass; + delete klass.$prototyping; + return proto; +}; + +Class.implement('implement', implement.overloadSetter()); + +Class.Mutators = { + + Extends: function(parent){ + this.parent = parent; + this.prototype = getInstance(parent); + }, + + Implements: function(items){ + Array.from(items).each(function(item){ + var instance = new item; + for (var key in instance) implement.call(this, key, instance[key], true); + }, this); + } +}; + +})(); + +/* +--- + +name: Class.Extras + +description: Contains Utility Classes that can be implemented into your own Classes to ease the execution of many common tasks. + +license: MIT-style license. + +requires: Class + +provides: [Class.Extras, Chain, Events, Options] + +... +*/ + +(function(){ + +this.Chain = new Class({ + + $chain: [], + + chain: function(){ + this.$chain.append(Array.flatten(arguments)); + return this; + }, + + callChain: function(){ + return (this.$chain.length) ? this.$chain.shift().apply(this, arguments) : false; + }, + + clearChain: function(){ + this.$chain.empty(); + return this; + } + +}); + +var removeOn = function(string){ + return string.replace(/^on([A-Z])/, function(full, first){ + return first.toLowerCase(); + }); +}; + +this.Events = new Class({ + + $events: {}, + + addEvent: function(type, fn, internal){ + type = removeOn(type); + + + + this.$events[type] = (this.$events[type] || []).include(fn); + if (internal) fn.internal = true; + return this; + }, + + addEvents: function(events){ + for (var type in events) this.addEvent(type, events[type]); + return this; + }, + + fireEvent: function(type, args, delay){ + type = removeOn(type); + var events = this.$events[type]; + if (!events) return this; + args = Array.from(args); + events.each(function(fn){ + if (delay) fn.delay(delay, this, args); + else fn.apply(this, args); + }, this); + return this; + }, + + removeEvent: function(type, fn){ + type = removeOn(type); + var events = this.$events[type]; + if (events && !fn.internal){ + var index = events.indexOf(fn); + if (index != -1) delete events[index]; + } + return this; + }, + + removeEvents: function(events){ + var type; + if (typeOf(events) == 'object'){ + for (type in events) this.removeEvent(type, events[type]); + return this; + } + if (events) events = removeOn(events); + for (type in this.$events){ + if (events && events != type) continue; + var fns = this.$events[type]; + for (var i = fns.length; i--;) if (i in fns){ + this.removeEvent(type, fns[i]); + } + } + return this; + } + +}); + +this.Options = new Class({ + + setOptions: function(){ + var options = this.options = Object.merge.apply(null, [{}, this.options].append(arguments)); + if (this.addEvent) for (var option in options){ + if (typeOf(options[option]) != 'function' || !(/^on[A-Z]/).test(option)) continue; + this.addEvent(option, options[option]); + delete options[option]; + } + return this; + } + +}); + +})(); + +/* +--- + +name: Object + +description: Object generic methods + +license: MIT-style license. + +requires: Type + +provides: [Object, Hash] + +... +*/ + +(function(){ + +var hasOwnProperty = Object.prototype.hasOwnProperty; + +Object.extend({ + + subset: function(object, keys){ + var results = {}; + for (var i = 0, l = keys.length; i < l; i++){ + var k = keys[i]; + if (k in object) results[k] = object[k]; + } + return results; + }, + + map: function(object, fn, bind){ + var results = {}; + for (var key in object){ + if (hasOwnProperty.call(object, key)) results[key] = fn.call(bind, object[key], key, object); + } + return results; + }, + + filter: function(object, fn, bind){ + var results = {}; + for (var key in object){ + var value = object[key]; + if (hasOwnProperty.call(object, key) && fn.call(bind, value, key, object)) results[key] = value; + } + return results; + }, + + every: function(object, fn, bind){ + for (var key in object){ + if (hasOwnProperty.call(object, key) && !fn.call(bind, object[key], key)) return false; + } + return true; + }, + + some: function(object, fn, bind){ + for (var key in object){ + if (hasOwnProperty.call(object, key) && fn.call(bind, object[key], key)) return true; + } + return false; + }, + + keys: function(object){ + var keys = []; + for (var key in object){ + if (hasOwnProperty.call(object, key)) keys.push(key); + } + return keys; + }, + + values: function(object){ + var values = []; + for (var key in object){ + if (hasOwnProperty.call(object, key)) values.push(object[key]); + } + return values; + }, + + getLength: function(object){ + return Object.keys(object).length; + }, + + keyOf: function(object, value){ + for (var key in object){ + if (hasOwnProperty.call(object, key) && object[key] === value) return key; + } + return null; + }, + + contains: function(object, value){ + return Object.keyOf(object, value) != null; + }, + + toQueryString: function(object, base){ + var queryString = []; + + Object.each(object, function(value, key){ + if (base) key = base + '[' + key + ']'; + var result; + switch (typeOf(value)){ + case 'object': result = Object.toQueryString(value, key); break; + case 'array': + var qs = {}; + value.each(function(val, i){ + qs[i] = val; + }); + result = Object.toQueryString(qs, key); + break; + default: result = key + '=' + encodeURIComponent(value); + } + if (value != null) queryString.push(result); + }); + + return queryString.join('&'); + } + +}); + +})(); + + + +/* +--- +name: Slick.Parser +description: Standalone CSS3 Selector parser +provides: Slick.Parser +... +*/ + +;(function(){ + +var parsed, + separatorIndex, + combinatorIndex, + reversed, + cache = {}, + reverseCache = {}, + reUnescape = /\\/g; + +var parse = function(expression, isReversed){ + if (expression == null) return null; + if (expression.Slick === true) return expression; + expression = ('' + expression).replace(/^\s+|\s+$/g, ''); + reversed = !!isReversed; + var currentCache = (reversed) ? reverseCache : cache; + if (currentCache[expression]) return currentCache[expression]; + parsed = { + Slick: true, + expressions: [], + raw: expression, + reverse: function(){ + return parse(this.raw, true); + } + }; + separatorIndex = -1; + while (expression != (expression = expression.replace(regexp, parser))); + parsed.length = parsed.expressions.length; + return currentCache[parsed.raw] = (reversed) ? reverse(parsed) : parsed; +}; + +var reverseCombinator = function(combinator){ + if (combinator === '!') return ' '; + else if (combinator === ' ') return '!'; + else if ((/^!/).test(combinator)) return combinator.replace(/^!/, ''); + else return '!' + combinator; +}; + +var reverse = function(expression){ + var expressions = expression.expressions; + for (var i = 0; i < expressions.length; i++){ + var exp = expressions[i]; + var last = {parts: [], tag: '*', combinator: reverseCombinator(exp[0].combinator)}; + + for (var j = 0; j < exp.length; j++){ + var cexp = exp[j]; + if (!cexp.reverseCombinator) cexp.reverseCombinator = ' '; + cexp.combinator = cexp.reverseCombinator; + delete cexp.reverseCombinator; + } + + exp.reverse().push(last); + } + return expression; +}; + +var escapeRegExp = function(string){// Credit: XRegExp 0.6.1 (c) 2007-2008 Steven Levithan <http://stevenlevithan.com/regex/xregexp/> MIT License + return string.replace(/[-[\]{}()*+?.\\^$|,#\s]/g, function(match){ + return '\\' + match; + }); +}; + +var regexp = new RegExp( +/* +#!/usr/bin/env ruby +puts "\t\t" + DATA.read.gsub(/\(\?x\)|\s+#.*$|\s+|\\$|\\n/,'') +__END__ + "(?x)^(?:\ + \\s* ( , ) \\s* # Separator \n\ + | \\s* ( <combinator>+ ) \\s* # Combinator \n\ + | ( \\s+ ) # CombinatorChildren \n\ + | ( <unicode>+ | \\* ) # Tag \n\ + | \\# ( <unicode>+ ) # ID \n\ + | \\. ( <unicode>+ ) # ClassName \n\ + | # Attribute \n\ + \\[ \ + \\s* (<unicode1>+) (?: \ + \\s* ([*^$!~|]?=) (?: \ + \\s* (?:\ + ([\"']?)(.*?)\\9 \ + )\ + ) \ + )? \\s* \ + \\](?!\\]) \n\ + | :+ ( <unicode>+ )(?:\ + \\( (?:\ + (?:([\"'])([^\\12]*)\\12)|((?:\\([^)]+\\)|[^()]*)+)\ + ) \\)\ + )?\ + )" +*/ + "^(?:\\s*(,)\\s*|\\s*(<combinator>+)\\s*|(\\s+)|(<unicode>+|\\*)|\\#(<unicode>+)|\\.(<unicode>+)|\\[\\s*(<unicode1>+)(?:\\s*([*^$!~|]?=)(?:\\s*(?:([\"']?)(.*?)\\9)))?\\s*\\](?!\\])|(:+)(<unicode>+)(?:\\((?:(?:([\"'])([^\\13]*)\\13)|((?:\\([^)]+\\)|[^()]*)+))\\))?)" + .replace(/<combinator>/, '[' + escapeRegExp(">+~`!@$%^&={}\\;</") + ']') + .replace(/<unicode>/g, '(?:[\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])') + .replace(/<unicode1>/g, '(?:[:\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])') +); + +function parser( + rawMatch, + + separator, + combinator, + combinatorChildren, + + tagName, + id, + className, + + attributeKey, + attributeOperator, + attributeQuote, + attributeValue, + + pseudoMarker, + pseudoClass, + pseudoQuote, + pseudoClassQuotedValue, + pseudoClassValue +){ + if (separator || separatorIndex === -1){ + parsed.expressions[++separatorIndex] = []; + combinatorIndex = -1; + if (separator) return ''; + } + + if (combinator || combinatorChildren || combinatorIndex === -1){ + combinator = combinator || ' '; + var currentSeparator = parsed.expressions[separatorIndex]; + if (reversed && currentSeparator[combinatorIndex]) + currentSeparator[combinatorIndex].reverseCombinator = reverseCombinator(combinator); + currentSeparator[++combinatorIndex] = {combinator: combinator, tag: '*'}; + } + + var currentParsed = parsed.expressions[separatorIndex][combinatorIndex]; + + if (tagName){ + currentParsed.tag = tagName.replace(reUnescape, ''); + + } else if (id){ + currentParsed.id = id.replace(reUnescape, ''); + + } else if (className){ + className = className.replace(reUnescape, ''); + + if (!currentParsed.classList) currentParsed.classList = []; + if (!currentParsed.classes) currentParsed.classes = []; + currentParsed.classList.push(className); + currentParsed.classes.push({ + value: className, + regexp: new RegExp('(^|\\s)' + escapeRegExp(className) + '(\\s|$)') + }); + + } else if (pseudoClass){ + pseudoClassValue = pseudoClassValue || pseudoClassQuotedValue; + pseudoClassValue = pseudoClassValue ? pseudoClassValue.replace(reUnescape, '') : null; + + if (!currentParsed.pseudos) currentParsed.pseudos = []; + currentParsed.pseudos.push({ + key: pseudoClass.replace(reUnescape, ''), + value: pseudoClassValue, + type: pseudoMarker.length == 1 ? 'class' : 'element' + }); + + } else if (attributeKey){ + attributeKey = attributeKey.replace(reUnescape, ''); + attributeValue = (attributeValue || '').replace(reUnescape, ''); + + var test, regexp; + + switch (attributeOperator){ + case '^=' : regexp = new RegExp( '^'+ escapeRegExp(attributeValue) ); break; + case '$=' : regexp = new RegExp( escapeRegExp(attributeValue) +'$' ); break; + case '~=' : regexp = new RegExp( '(^|\\s)'+ escapeRegExp(attributeValue) +'(\\s|$)' ); break; + case '|=' : regexp = new RegExp( '^'+ escapeRegExp(attributeValue) +'(-|$)' ); break; + case '=' : test = function(value){ + return attributeValue == value; + }; break; + case '*=' : test = function(value){ + return value && value.indexOf(attributeValue) > -1; + }; break; + case '!=' : test = function(value){ + return attributeValue != value; + }; break; + default : test = function(value){ + return !!value; + }; + } + + if (attributeValue == '' && (/^[*$^]=$/).test(attributeOperator)) test = function(){ + return false; + }; + + if (!test) test = function(value){ + return value && regexp.test(value); + }; + + if (!currentParsed.attributes) currentParsed.attributes = []; + currentParsed.attributes.push({ + key: attributeKey, + operator: attributeOperator, + value: attributeValue, + test: test + }); + + } + + return ''; +}; + +// Slick NS + +var Slick = (this.Slick || {}); + +Slick.parse = function(expression){ + return parse(expression); +}; + +Slick.escapeRegExp = escapeRegExp; + +if (!this.Slick) this.Slick = Slick; + +}).apply(/*<CommonJS>*/(typeof exports != 'undefined') ? exports : /*</CommonJS>*/this); + +/* +--- +name: Slick.Finder +description: The new, superfast css selector engine. +provides: Slick.Finder +requires: Slick.Parser +... +*/ + +;(function(){ + +var local = {}, + featuresCache = {}, + toString = Object.prototype.toString; + +// Feature / Bug detection + +local.isNativeCode = function(fn){ + return (/\{\s*\[native code\]\s*\}/).test('' + fn); +}; + +local.isXML = function(document){ + return (!!document.xmlVersion) || (!!document.xml) || (toString.call(document) == '[object XMLDocument]') || + (document.nodeType == 9 && document.documentElement.nodeName != 'HTML'); +}; + +local.setDocument = function(document){ + + // convert elements / window arguments to document. if document cannot be extrapolated, the function returns. + var nodeType = document.nodeType; + if (nodeType == 9); // document + else if (nodeType) document = document.ownerDocument; // node + else if (document.navigator) document = document.document; // window + else return; + + // check if it's the old document + + if (this.document === document) return; + this.document = document; + + // check if we have done feature detection on this document before + + var root = document.documentElement, + rootUid = this.getUIDXML(root), + features = featuresCache[rootUid], + feature; + + if (features){ + for (feature in features){ + this[feature] = features[feature]; + } + return; + } + + features = featuresCache[rootUid] = {}; + + features.root = root; + features.isXMLDocument = this.isXML(document); + + features.brokenStarGEBTN + = features.starSelectsClosedQSA + = features.idGetsName + = features.brokenMixedCaseQSA + = features.brokenGEBCN + = features.brokenCheckedQSA + = features.brokenEmptyAttributeQSA + = features.isHTMLDocument + = features.nativeMatchesSelector + = false; + + var starSelectsClosed, starSelectsComments, + brokenSecondClassNameGEBCN, cachedGetElementsByClassName, + brokenFormAttributeGetter; + + var selected, id = 'slick_uniqueid'; + var testNode = document.createElement('div'); + + var testRoot = document.body || document.getElementsByTagName('body')[0] || root; + testRoot.appendChild(testNode); + + // on non-HTML documents innerHTML and getElementsById doesnt work properly + try { + testNode.innerHTML = '<a id="'+id+'"></a>'; + features.isHTMLDocument = !!document.getElementById(id); + } catch(e){}; + + if (features.isHTMLDocument){ + + testNode.style.display = 'none'; + + // IE returns comment nodes for getElementsByTagName('*') for some documents + testNode.appendChild(document.createComment('')); + starSelectsComments = (testNode.getElementsByTagName('*').length > 1); + + // IE returns closed nodes (EG:"</foo>") for getElementsByTagName('*') for some documents + try { + testNode.innerHTML = 'foo</foo>'; + selected = testNode.getElementsByTagName('*'); + starSelectsClosed = (selected && !!selected.length && selected[0].nodeName.charAt(0) == '/'); + } catch(e){}; + + features.brokenStarGEBTN = starSelectsComments || starSelectsClosed; + + // IE returns elements with the name instead of just id for getElementsById for some documents + try { + testNode.innerHTML = '<a name="'+ id +'"></a><b id="'+ id +'"></b>'; + features.idGetsName = document.getElementById(id) === testNode.firstChild; + } catch(e){}; + + if (testNode.getElementsByClassName){ + + // Safari 3.2 getElementsByClassName caches results + try { + testNode.innerHTML = '<a class="f"></a><a class="b"></a>'; + testNode.getElementsByClassName('b').length; + testNode.firstChild.className = 'b'; + cachedGetElementsByClassName = (testNode.getElementsByClassName('b').length != 2); + } catch(e){}; + + // Opera 9.6 getElementsByClassName doesnt detects the class if its not the first one + try { + testNode.innerHTML = '<a class="a"></a><a class="f b a"></a>'; + brokenSecondClassNameGEBCN = (testNode.getElementsByClassName('a').length != 2); + } catch(e){}; + + features.brokenGEBCN = cachedGetElementsByClassName || brokenSecondClassNameGEBCN; + } + + if (testNode.querySelectorAll){ + // IE 8 returns closed nodes (EG:"</foo>") for querySelectorAll('*') for some documents + try { + testNode.innerHTML = 'foo</foo>'; + selected = testNode.querySelectorAll('*'); + features.starSelectsClosedQSA = (selected && !!selected.length && selected[0].nodeName.charAt(0) == '/'); + } catch(e){}; + + // Safari 3.2 querySelectorAll doesnt work with mixedcase on quirksmode + try { + testNode.innerHTML = '<a class="MiX"></a>'; + features.brokenMixedCaseQSA = !testNode.querySelectorAll('.MiX').length; + } catch(e){}; + + // Webkit and Opera dont return selected options on querySelectorAll + try { + testNode.innerHTML = '<select><option selected="selected">a</option></select>'; + features.brokenCheckedQSA = (testNode.querySelectorAll(':checked').length == 0); + } catch(e){}; + + // IE returns incorrect results for attr[*^$]="" selectors on querySelectorAll + try { + testNode.innerHTML = '<a class=""></a>'; + features.brokenEmptyAttributeQSA = (testNode.querySelectorAll('[class*=""]').length != 0); + } catch(e){}; + + } + + // IE6-7, if a form has an input of id x, form.getAttribute(x) returns a reference to the input + try { + testNode.innerHTML = '<form action="s"><input id="action"/></form>'; + brokenFormAttributeGetter = (testNode.firstChild.getAttribute('action') != 's'); + } catch(e){}; + + // native matchesSelector function + + features.nativeMatchesSelector = root.matches || /*root.msMatchesSelector ||*/ root.mozMatchesSelector || root.webkitMatchesSelector; + if (features.nativeMatchesSelector) try { + // if matchesSelector trows errors on incorrect sintaxes we can use it + features.nativeMatchesSelector.call(root, ':slick'); + features.nativeMatchesSelector = null; + } catch(e){}; + + } + + try { + root.slick_expando = 1; + delete root.slick_expando; + features.getUID = this.getUIDHTML; + } catch(e){ + features.getUID = this.getUIDXML; + } + + testRoot.removeChild(testNode); + testNode = selected = testRoot = null; + + // getAttribute + + features.getAttribute = (features.isHTMLDocument && brokenFormAttributeGetter) ? function(node, name){ + var method = this.attributeGetters[name]; + if (method) return method.call(node); + var attributeNode = node.getAttributeNode(name); + return (attributeNode) ? attributeNode.nodeValue : null; + } : function(node, name){ + var method = this.attributeGetters[name]; + return (method) ? method.call(node) : node.getAttribute(name); + }; + + // hasAttribute + + features.hasAttribute = (root && this.isNativeCode(root.hasAttribute)) ? function(node, attribute){ + return node.hasAttribute(attribute); + } : function(node, attribute){ + node = node.getAttributeNode(attribute); + return !!(node && (node.specified || node.nodeValue)); + }; + + // contains + // FIXME: Add specs: local.contains should be different for xml and html documents? + var nativeRootContains = root && this.isNativeCode(root.contains), + nativeDocumentContains = document && this.isNativeCode(document.contains); + + features.contains = (nativeRootContains && nativeDocumentContains) ? function(context, node){ + return context.contains(node); + } : (nativeRootContains && !nativeDocumentContains) ? function(context, node){ + // IE8 does not have .contains on document. + return context === node || ((context === document) ? document.documentElement : context).contains(node); + } : (root && root.compareDocumentPosition) ? function(context, node){ + return context === node || !!(context.compareDocumentPosition(node) & 16); + } : function(context, node){ + if (node) do { + if (node === context) return true; + } while ((node = node.parentNode)); + return false; + }; + + // document order sorting + // credits to Sizzle (http://sizzlejs.com/) + + features.documentSorter = (root.compareDocumentPosition) ? function(a, b){ + if (!a.compareDocumentPosition || !b.compareDocumentPosition) return 0; + return a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1; + } : ('sourceIndex' in root) ? function(a, b){ + if (!a.sourceIndex || !b.sourceIndex) return 0; + return a.sourceIndex - b.sourceIndex; + } : (document.createRange) ? function(a, b){ + if (!a.ownerDocument || !b.ownerDocument) return 0; + var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange(); + aRange.setStart(a, 0); + aRange.setEnd(a, 0); + bRange.setStart(b, 0); + bRange.setEnd(b, 0); + return aRange.compareBoundaryPoints(Range.START_TO_END, bRange); + } : null ; + + root = null; + + for (feature in features){ + this[feature] = features[feature]; + } +}; + +// Main Method + +var reSimpleSelector = /^([#.]?)((?:[\w-]+|\*))$/, + reEmptyAttribute = /\[.+[*$^]=(?:""|'')?\]/, + qsaFailExpCache = {}; + +local.search = function(context, expression, append, first){ + + var found = this.found = (first) ? null : (append || []); + + if (!context) return found; + else if (context.navigator) context = context.document; // Convert the node from a window to a document + else if (!context.nodeType) return found; + + // setup + + var parsed, i, + uniques = this.uniques = {}, + hasOthers = !!(append && append.length), + contextIsDocument = (context.nodeType == 9); + + if (this.document !== (contextIsDocument ? context : context.ownerDocument)) this.setDocument(context); + + // avoid duplicating items already in the append array + if (hasOthers) for (i = found.length; i--;) uniques[this.getUID(found[i])] = true; + + // expression checks + + if (typeof expression == 'string'){ // expression is a string + + /*<simple-selectors-override>*/ + var simpleSelector = expression.match(reSimpleSelector); + simpleSelectors: if (simpleSelector){ + + var symbol = simpleSelector[1], + name = simpleSelector[2], + node, nodes; + + if (!symbol){ + + if (name == '*' && this.brokenStarGEBTN) break simpleSelectors; + nodes = context.getElementsByTagName(name); + if (first) return nodes[0] || null; + for (i = 0; node = nodes[i++];){ + if (!(hasOthers && uniques[this.getUID(node)])) found.push(node); + } + + } else if (symbol == '#'){ + + if (!this.isHTMLDocument || !contextIsDocument) break simpleSelectors; + node = context.getElementById(name); + if (!node) return found; + if (this.idGetsName && node.getAttributeNode('id').nodeValue != name) break simpleSelectors; + if (first) return node || null; + if (!(hasOthers && uniques[this.getUID(node)])) found.push(node); + + } else if (symbol == '.'){ + + if (!this.isHTMLDocument || ((!context.getElementsByClassName || this.brokenGEBCN) && context.querySelectorAll)) break simpleSelectors; + if (context.getElementsByClassName && !this.brokenGEBCN){ + nodes = context.getElementsByClassName(name); + if (first) return nodes[0] || null; + for (i = 0; node = nodes[i++];){ + if (!(hasOthers && uniques[this.getUID(node)])) found.push(node); + } + } else { + var matchClass = new RegExp('(^|\\s)'+ Slick.escapeRegExp(name) +'(\\s|$)'); + nodes = context.getElementsByTagName('*'); + for (i = 0; node = nodes[i++];){ + className = node.className; + if (!(className && matchClass.test(className))) continue; + if (first) return node; + if (!(hasOthers && uniques[this.getUID(node)])) found.push(node); + } + } + + } + + if (hasOthers) this.sort(found); + return (first) ? null : found; + + } + /*</simple-selectors-override>*/ + + /*<query-selector-override>*/ + querySelector: if (context.querySelectorAll){ + + if (!this.isHTMLDocument + || qsaFailExpCache[expression] + //TODO: only skip when expression is actually mixed case + || this.brokenMixedCaseQSA + || (this.brokenCheckedQSA && expression.indexOf(':checked') > -1) + || (this.brokenEmptyAttributeQSA && reEmptyAttribute.test(expression)) + || (!contextIsDocument //Abort when !contextIsDocument and... + // there are multiple expressions in the selector + // since we currently only fix non-document rooted QSA for single expression selectors + && expression.indexOf(',') > -1 + ) + || Slick.disableQSA + ) break querySelector; + + var _expression = expression, _context = context; + if (!contextIsDocument){ + // non-document rooted QSA + // credits to Andrew Dupont + var currentId = _context.getAttribute('id'), slickid = 'slickid__'; + _context.setAttribute('id', slickid); + _expression = '#' + slickid + ' ' + _expression; + context = _context.parentNode; + } + + try { + if (first) return context.querySelector(_expression) || null; + else nodes = context.querySelectorAll(_expression); + } catch(e){ + qsaFailExpCache[expression] = 1; + break querySelector; + } finally { + if (!contextIsDocument){ + if (currentId) _context.setAttribute('id', currentId); + else _context.removeAttribute('id'); + context = _context; + } + } + + if (this.starSelectsClosedQSA) for (i = 0; node = nodes[i++];){ + if (node.nodeName > '@' && !(hasOthers && uniques[this.getUID(node)])) found.push(node); + } else for (i = 0; node = nodes[i++];){ + if (!(hasOthers && uniques[this.getUID(node)])) found.push(node); + } + + if (hasOthers) this.sort(found); + return found; + + } + /*</query-selector-override>*/ + + parsed = this.Slick.parse(expression); + if (!parsed.length) return found; + } else if (expression == null){ // there is no expression + return found; + } else if (expression.Slick){ // expression is a parsed Slick object + parsed = expression; + } else if (this.contains(context.documentElement || context, expression)){ // expression is a node + (found) ? found.push(expression) : found = expression; + return found; + } else { // other junk + return found; + } + + /*<pseudo-selectors>*//*<nth-pseudo-selectors>*/ + + // cache elements for the nth selectors + + this.posNTH = {}; + this.posNTHLast = {}; + this.posNTHType = {}; + this.posNTHTypeLast = {}; + + /*</nth-pseudo-selectors>*//*</pseudo-selectors>*/ + + // if append is null and there is only a single selector with one expression use pushArray, else use pushUID + this.push = (!hasOthers && (first || (parsed.length == 1 && parsed.expressions[0].length == 1))) ? this.pushArray : this.pushUID; + + if (found == null) found = []; + + // default engine + + var j, m, n; + var combinator, tag, id, classList, classes, attributes, pseudos; + var currentItems, currentExpression, currentBit, lastBit, expressions = parsed.expressions; + + search: for (i = 0; (currentExpression = expressions[i]); i++) for (j = 0; (currentBit = currentExpression[j]); j++){ + + combinator = 'combinator:' + currentBit.combinator; + if (!this[combinator]) continue search; + + tag = (this.isXMLDocument) ? currentBit.tag : currentBit.tag.toUpperCase(); + id = currentBit.id; + classList = currentBit.classList; + classes = currentBit.classes; + attributes = currentBit.attributes; + pseudos = currentBit.pseudos; + lastBit = (j === (currentExpression.length - 1)); + + this.bitUniques = {}; + + if (lastBit){ + this.uniques = uniques; + this.found = found; + } else { + this.uniques = {}; + this.found = []; + } + + if (j === 0){ + this[combinator](context, tag, id, classes, attributes, pseudos, classList); + if (first && lastBit && found.length) break search; + } else { + if (first && lastBit) for (m = 0, n = currentItems.length; m < n; m++){ + this[combinator](currentItems[m], tag, id, classes, attributes, pseudos, classList); + if (found.length) break search; + } else for (m = 0, n = currentItems.length; m < n; m++) this[combinator](currentItems[m], tag, id, classes, attributes, pseudos, classList); + } + + currentItems = this.found; + } + + // should sort if there are nodes in append and if you pass multiple expressions. + if (hasOthers || (parsed.expressions.length > 1)) this.sort(found); + + return (first) ? (found[0] || null) : found; +}; + +// Utils + +local.uidx = 1; +local.uidk = 'slick-uniqueid'; + +local.getUIDXML = function(node){ + var uid = node.getAttribute(this.uidk); + if (!uid){ + uid = this.uidx++; + node.setAttribute(this.uidk, uid); + } + return uid; +}; + +local.getUIDHTML = function(node){ + return node.uniqueNumber || (node.uniqueNumber = this.uidx++); +}; + +// sort based on the setDocument documentSorter method. + +local.sort = function(results){ + if (!this.documentSorter) return results; + results.sort(this.documentSorter); + return results; +}; + +/*<pseudo-selectors>*//*<nth-pseudo-selectors>*/ + +local.cacheNTH = {}; + +local.matchNTH = /^([+-]?\d*)?([a-z]+)?([+-]\d+)?$/; + +local.parseNTHArgument = function(argument){ + var parsed = argument.match(this.matchNTH); + if (!parsed) return false; + var special = parsed[2] || false; + var a = parsed[1] || 1; + if (a == '-') a = -1; + var b = +parsed[3] || 0; + parsed = + (special == 'n') ? {a: a, b: b} : + (special == 'odd') ? {a: 2, b: 1} : + (special == 'even') ? {a: 2, b: 0} : {a: 0, b: a}; + + return (this.cacheNTH[argument] = parsed); +}; + +local.createNTHPseudo = function(child, sibling, positions, ofType){ + return function(node, argument){ + var uid = this.getUID(node); + if (!this[positions][uid]){ + var parent = node.parentNode; + if (!parent) return false; + var el = parent[child], count = 1; + if (ofType){ + var nodeName = node.nodeName; + do { + if (el.nodeName != nodeName) continue; + this[positions][this.getUID(el)] = count++; + } while ((el = el[sibling])); + } else { + do { + if (el.nodeType != 1) continue; + this[positions][this.getUID(el)] = count++; + } while ((el = el[sibling])); + } + } + argument = argument || 'n'; + var parsed = this.cacheNTH[argument] || this.parseNTHArgument(argument); + if (!parsed) return false; + var a = parsed.a, b = parsed.b, pos = this[positions][uid]; + if (a == 0) return b == pos; + if (a > 0){ + if (pos < b) return false; + } else { + if (b < pos) return false; + } + return ((pos - b) % a) == 0; + }; +}; + +/*</nth-pseudo-selectors>*//*</pseudo-selectors>*/ + +local.pushArray = function(node, tag, id, classes, attributes, pseudos){ + if (this.matchSelector(node, tag, id, classes, attributes, pseudos)) this.found.push(node); +}; + +local.pushUID = function(node, tag, id, classes, attributes, pseudos){ + var uid = this.getUID(node); + if (!this.uniques[uid] && this.matchSelector(node, tag, id, classes, attributes, pseudos)){ + this.uniques[uid] = true; + this.found.push(node); + } +}; + +local.matchNode = function(node, selector){ + if (this.isHTMLDocument && this.nativeMatchesSelector){ + try { + return this.nativeMatchesSelector.call(node, selector.replace(/\[([^=]+)=\s*([^'"\]]+?)\s*\]/g, '[$1="$2"]')); + } catch(matchError){} + } + + var parsed = this.Slick.parse(selector); + if (!parsed) return true; + + // simple (single) selectors + var expressions = parsed.expressions, simpleExpCounter = 0, i, currentExpression; + for (i = 0; (currentExpression = expressions[i]); i++){ + if (currentExpression.length == 1){ + var exp = currentExpression[0]; + if (this.matchSelector(node, (this.isXMLDocument) ? exp.tag : exp.tag.toUpperCase(), exp.id, exp.classes, exp.attributes, exp.pseudos)) return true; + simpleExpCounter++; + } + } + + if (simpleExpCounter == parsed.length) return false; + + var nodes = this.search(this.document, parsed), item; + for (i = 0; item = nodes[i++];){ + if (item === node) return true; + } + return false; +}; + +local.matchPseudo = function(node, name, argument){ + var pseudoName = 'pseudo:' + name; + if (this[pseudoName]) return this[pseudoName](node, argument); + var attribute = this.getAttribute(node, name); + return (argument) ? argument == attribute : !!attribute; +}; + +local.matchSelector = function(node, tag, id, classes, attributes, pseudos){ + if (tag){ + var nodeName = (this.isXMLDocument) ? node.nodeName : node.nodeName.toUpperCase(); + if (tag == '*'){ + if (nodeName < '@') return false; // Fix for comment nodes and closed nodes + } else { + if (nodeName != tag) return false; + } + } + + if (id && node.getAttribute('id') != id) return false; + + var i, part, cls; + if (classes) for (i = classes.length; i--;){ + cls = this.getAttribute(node, 'class'); + if (!(cls && classes[i].regexp.test(cls))) return false; + } + if (attributes) for (i = attributes.length; i--;){ + part = attributes[i]; + if (part.operator ? !part.test(this.getAttribute(node, part.key)) : !this.hasAttribute(node, part.key)) return false; + } + if (pseudos) for (i = pseudos.length; i--;){ + part = pseudos[i]; + if (!this.matchPseudo(node, part.key, part.value)) return false; + } + return true; +}; + +var combinators = { + + ' ': function(node, tag, id, classes, attributes, pseudos, classList){ // all child nodes, any level + + var i, item, children; + + if (this.isHTMLDocument){ + getById: if (id){ + item = this.document.getElementById(id); + if ((!item && node.all) || (this.idGetsName && item && item.getAttributeNode('id').nodeValue != id)){ + // all[id] returns all the elements with that name or id inside node + // if theres just one it will return the element, else it will be a collection + children = node.all[id]; + if (!children) return; + if (!children[0]) children = [children]; + for (i = 0; item = children[i++];){ + var idNode = item.getAttributeNode('id'); + if (idNode && idNode.nodeValue == id){ + this.push(item, tag, null, classes, attributes, pseudos); + break; + } + } + return; + } + if (!item){ + // if the context is in the dom we return, else we will try GEBTN, breaking the getById label + if (this.contains(this.root, node)) return; + else break getById; + } else if (this.document !== node && !this.contains(node, item)) return; + this.push(item, tag, null, classes, attributes, pseudos); + return; + } + getByClass: if (classes && node.getElementsByClassName && !this.brokenGEBCN){ + children = node.getElementsByClassName(classList.join(' ')); + if (!(children && children.length)) break getByClass; + for (i = 0; item = children[i++];) this.push(item, tag, id, null, attributes, pseudos); + return; + } + } + getByTag: { + children = node.getElementsByTagName(tag); + if (!(children && children.length)) break getByTag; + if (!this.brokenStarGEBTN) tag = null; + for (i = 0; item = children[i++];) this.push(item, tag, id, classes, attributes, pseudos); + } + }, + + '>': function(node, tag, id, classes, attributes, pseudos){ // direct children + if ((node = node.firstChild)) do { + if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos); + } while ((node = node.nextSibling)); + }, + + '+': function(node, tag, id, classes, attributes, pseudos){ // next sibling + while ((node = node.nextSibling)) if (node.nodeType == 1){ + this.push(node, tag, id, classes, attributes, pseudos); + break; + } + }, + + '^': function(node, tag, id, classes, attributes, pseudos){ // first child + node = node.firstChild; + if (node){ + if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos); + else this['combinator:+'](node, tag, id, classes, attributes, pseudos); + } + }, + + '~': function(node, tag, id, classes, attributes, pseudos){ // next siblings + while ((node = node.nextSibling)){ + if (node.nodeType != 1) continue; + var uid = this.getUID(node); + if (this.bitUniques[uid]) break; + this.bitUniques[uid] = true; + this.push(node, tag, id, classes, attributes, pseudos); + } + }, + + '++': function(node, tag, id, classes, attributes, pseudos){ // next sibling and previous sibling + this['combinator:+'](node, tag, id, classes, attributes, pseudos); + this['combinator:!+'](node, tag, id, classes, attributes, pseudos); + }, + + '~~': function(node, tag, id, classes, attributes, pseudos){ // next siblings and previous siblings + this['combinator:~'](node, tag, id, classes, attributes, pseudos); + this['combinator:!~'](node, tag, id, classes, attributes, pseudos); + }, + + '!': function(node, tag, id, classes, attributes, pseudos){ // all parent nodes up to document + while ((node = node.parentNode)) if (node !== this.document) this.push(node, tag, id, classes, attributes, pseudos); + }, + + '!>': function(node, tag, id, classes, attributes, pseudos){ // direct parent (one level) + node = node.parentNode; + if (node !== this.document) this.push(node, tag, id, classes, attributes, pseudos); + }, + + '!+': function(node, tag, id, classes, attributes, pseudos){ // previous sibling + while ((node = node.previousSibling)) if (node.nodeType == 1){ + this.push(node, tag, id, classes, attributes, pseudos); + break; + } + }, + + '!^': function(node, tag, id, classes, attributes, pseudos){ // last child + node = node.lastChild; + if (node){ + if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos); + else this['combinator:!+'](node, tag, id, classes, attributes, pseudos); + } + }, + + '!~': function(node, tag, id, classes, attributes, pseudos){ // previous siblings + while ((node = node.previousSibling)){ + if (node.nodeType != 1) continue; + var uid = this.getUID(node); + if (this.bitUniques[uid]) break; + this.bitUniques[uid] = true; + this.push(node, tag, id, classes, attributes, pseudos); + } + } + +}; + +for (var c in combinators) local['combinator:' + c] = combinators[c]; + +var pseudos = { + + /*<pseudo-selectors>*/ + + 'empty': function(node){ + var child = node.firstChild; + return !(child && child.nodeType == 1) && !(node.innerText || node.textContent || '').length; + }, + + 'not': function(node, expression){ + return !this.matchNode(node, expression); + }, + + 'contains': function(node, text){ + return (node.innerText || node.textContent || '').indexOf(text) > -1; + }, + + 'first-child': function(node){ + while ((node = node.previousSibling)) if (node.nodeType == 1) return false; + return true; + }, + + 'last-child': function(node){ + while ((node = node.nextSibling)) if (node.nodeType == 1) return false; + return true; + }, + + 'only-child': function(node){ + var prev = node; + while ((prev = prev.previousSibling)) if (prev.nodeType == 1) return false; + var next = node; + while ((next = next.nextSibling)) if (next.nodeType == 1) return false; + return true; + }, + + /*<nth-pseudo-selectors>*/ + + 'nth-child': local.createNTHPseudo('firstChild', 'nextSibling', 'posNTH'), + + 'nth-last-child': local.createNTHPseudo('lastChild', 'previousSibling', 'posNTHLast'), + + 'nth-of-type': local.createNTHPseudo('firstChild', 'nextSibling', 'posNTHType', true), + + 'nth-last-of-type': local.createNTHPseudo('lastChild', 'previousSibling', 'posNTHTypeLast', true), + + 'index': function(node, index){ + return this['pseudo:nth-child'](node, '' + (index + 1)); + }, + + 'even': function(node){ + return this['pseudo:nth-child'](node, '2n'); + }, + + 'odd': function(node){ + return this['pseudo:nth-child'](node, '2n+1'); + }, + + /*</nth-pseudo-selectors>*/ + + /*<of-type-pseudo-selectors>*/ + + 'first-of-type': function(node){ + var nodeName = node.nodeName; + while ((node = node.previousSibling)) if (node.nodeName == nodeName) return false; + return true; + }, + + 'last-of-type': function(node){ + var nodeName = node.nodeName; + while ((node = node.nextSibling)) if (node.nodeName == nodeName) return false; + return true; + }, + + 'only-of-type': function(node){ + var prev = node, nodeName = node.nodeName; + while ((prev = prev.previousSibling)) if (prev.nodeName == nodeName) return false; + var next = node; + while ((next = next.nextSibling)) if (next.nodeName == nodeName) return false; + return true; + }, + + /*</of-type-pseudo-selectors>*/ + + // custom pseudos + + 'enabled': function(node){ + return !node.disabled; + }, + + 'disabled': function(node){ + return node.disabled; + }, + + 'checked': function(node){ + return node.checked || node.selected; + }, + + 'focus': function(node){ + return this.isHTMLDocument && this.document.activeElement === node && (node.href || node.type || this.hasAttribute(node, 'tabindex')); + }, + + 'root': function(node){ + return (node === this.root); + }, + + 'selected': function(node){ + return node.selected; + } + + /*</pseudo-selectors>*/ +}; + +for (var p in pseudos) local['pseudo:' + p] = pseudos[p]; + +// attributes methods + +var attributeGetters = local.attributeGetters = { + + 'for': function(){ + return ('htmlFor' in this) ? this.htmlFor : this.getAttribute('for'); + }, + + 'href': function(){ + return ('href' in this) ? this.getAttribute('href', 2) : this.getAttribute('href'); + }, + + 'style': function(){ + return (this.style) ? this.style.cssText : this.getAttribute('style'); + }, + + 'tabindex': function(){ + var attributeNode = this.getAttributeNode('tabindex'); + return (attributeNode && attributeNode.specified) ? attributeNode.nodeValue : null; + }, + + 'type': function(){ + return this.getAttribute('type'); + }, + + 'maxlength': function(){ + var attributeNode = this.getAttributeNode('maxLength'); + return (attributeNode && attributeNode.specified) ? attributeNode.nodeValue : null; + } + +}; + +attributeGetters.MAXLENGTH = attributeGetters.maxLength = attributeGetters.maxlength; + +// Slick + +var Slick = local.Slick = (this.Slick || {}); + +Slick.version = '1.1.7'; + +// Slick finder + +Slick.search = function(context, expression, append){ + return local.search(context, expression, append); +}; + +Slick.find = function(context, expression){ + return local.search(context, expression, null, true); +}; + +// Slick containment checker + +Slick.contains = function(container, node){ + local.setDocument(container); + return local.contains(container, node); +}; + +// Slick attribute getter + +Slick.getAttribute = function(node, name){ + local.setDocument(node); + return local.getAttribute(node, name); +}; + +Slick.hasAttribute = function(node, name){ + local.setDocument(node); + return local.hasAttribute(node, name); +}; + +// Slick matcher + +Slick.match = function(node, selector){ + if (!(node && selector)) return false; + if (!selector || selector === node) return true; + local.setDocument(node); + return local.matchNode(node, selector); +}; + +// Slick attribute accessor + +Slick.defineAttributeGetter = function(name, fn){ + local.attributeGetters[name] = fn; + return this; +}; + +Slick.lookupAttributeGetter = function(name){ + return local.attributeGetters[name]; +}; + +// Slick pseudo accessor + +Slick.definePseudo = function(name, fn){ + local['pseudo:' + name] = function(node, argument){ + return fn.call(node, argument); + }; + return this; +}; + +Slick.lookupPseudo = function(name){ + var pseudo = local['pseudo:' + name]; + if (pseudo) return function(argument){ + return pseudo.call(this, argument); + }; + return null; +}; + +// Slick overrides accessor + +Slick.override = function(regexp, fn){ + local.override(regexp, fn); + return this; +}; + +Slick.isXML = local.isXML; + +Slick.uidOf = function(node){ + return local.getUIDHTML(node); +}; + +if (!this.Slick) this.Slick = Slick; + +}).apply(/*<CommonJS>*/(typeof exports != 'undefined') ? exports : /*</CommonJS>*/this); + +/* +--- + +name: Element + +description: One of the most important items in MooTools. Contains the dollar function, the dollars function, and an handful of cross-browser, time-saver methods to let you easily work with HTML Elements. + +license: MIT-style license. + +requires: [Window, Document, Array, String, Function, Object, Number, Slick.Parser, Slick.Finder] + +provides: [Element, Elements, $, $$, IFrame, Selectors] + +... +*/ + +var Element = this.Element = function(tag, props){ + var konstructor = Element.Constructors[tag]; + if (konstructor) return konstructor(props); + if (typeof tag != 'string') return document.id(tag).set(props); + + if (!props) props = {}; + + if (!(/^[\w-]+$/).test(tag)){ + var parsed = Slick.parse(tag).expressions[0][0]; + tag = (parsed.tag == '*') ? 'div' : parsed.tag; + if (parsed.id && props.id == null) props.id = parsed.id; + + var attributes = parsed.attributes; + if (attributes) for (var attr, i = 0, l = attributes.length; i < l; i++){ + attr = attributes[i]; + if (props[attr.key] != null) continue; + + if (attr.value != null && attr.operator == '=') props[attr.key] = attr.value; + else if (!attr.value && !attr.operator) props[attr.key] = true; + } + + if (parsed.classList && props['class'] == null) props['class'] = parsed.classList.join(' '); + } + + return document.newElement(tag, props); +}; + + +if (Browser.Element){ + Element.prototype = Browser.Element.prototype; + // IE8 and IE9 require the wrapping. + Element.prototype._fireEvent = (function(fireEvent){ + return function(type, event){ + return fireEvent.call(this, type, event); + }; + })(Element.prototype.fireEvent); +} + +new Type('Element', Element).mirror(function(name){ + if (Array.prototype[name]) return; + + var obj = {}; + obj[name] = function(){ + var results = [], args = arguments, elements = true; + for (var i = 0, l = this.length; i < l; i++){ + var element = this[i], result = results[i] = element[name].apply(element, args); + elements = (elements && typeOf(result) == 'element'); + } + return (elements) ? new Elements(results) : results; + }; + + Elements.implement(obj); +}); + +if (!Browser.Element){ + Element.parent = Object; + + Element.Prototype = { + '$constructor': Element, + '$family': Function.from('element').hide() + }; + + Element.mirror(function(name, method){ + Element.Prototype[name] = method; + }); +} + +Element.Constructors = {}; + + + +var IFrame = new Type('IFrame', function(){ + var params = Array.link(arguments, { + properties: Type.isObject, + iframe: function(obj){ + return (obj != null); + } + }); + + var props = params.properties || {}, iframe; + if (params.iframe) iframe = document.id(params.iframe); + var onload = props.onload || function(){}; + delete props.onload; + props.id = props.name = [props.id, props.name, iframe ? (iframe.id || iframe.name) : 'IFrame_' + String.uniqueID()].pick(); + iframe = new Element(iframe || 'iframe', props); + + var onLoad = function(){ + onload.call(iframe.contentWindow); + }; + + if (window.frames[props.id]) onLoad(); + else iframe.addListener('load', onLoad); + return iframe; +}); + +var Elements = this.Elements = function(nodes){ + if (nodes && nodes.length){ + var uniques = {}, node; + for (var i = 0; node = nodes[i++];){ + var uid = Slick.uidOf(node); + if (!uniques[uid]){ + uniques[uid] = true; + this.push(node); + } + } + } +}; + +Elements.prototype = {length: 0}; +Elements.parent = Array; + +new Type('Elements', Elements).implement({ + + filter: function(filter, bind){ + if (!filter) return this; + return new Elements(Array.filter(this, (typeOf(filter) == 'string') ? function(item){ + return item.match(filter); + } : filter, bind)); + }.protect(), + + push: function(){ + var length = this.length; + for (var i = 0, l = arguments.length; i < l; i++){ + var item = document.id(arguments[i]); + if (item) this[length++] = item; + } + return (this.length = length); + }.protect(), + + unshift: function(){ + var items = []; + for (var i = 0, l = arguments.length; i < l; i++){ + var item = document.id(arguments[i]); + if (item) items.push(item); + } + return Array.prototype.unshift.apply(this, items); + }.protect(), + + concat: function(){ + var newElements = new Elements(this); + for (var i = 0, l = arguments.length; i < l; i++){ + var item = arguments[i]; + if (Type.isEnumerable(item)) newElements.append(item); + else newElements.push(item); + } + return newElements; + }.protect(), + + append: function(collection){ + for (var i = 0, l = collection.length; i < l; i++) this.push(collection[i]); + return this; + }.protect(), + + empty: function(){ + while (this.length) delete this[--this.length]; + return this; + }.protect() + +}); + + + +(function(){ + +// FF, IE +var splice = Array.prototype.splice, object = {'0': 0, '1': 1, length: 2}; + +splice.call(object, 1, 1); +if (object[1] == 1) Elements.implement('splice', function(){ + var length = this.length; + var result = splice.apply(this, arguments); + while (length >= this.length) delete this[length--]; + return result; +}.protect()); + +Array.forEachMethod(function(method, name){ + Elements.implement(name, method); +}); + +Array.mirror(Elements); + +/*<ltIE8>*/ +var createElementAcceptsHTML; +try { + createElementAcceptsHTML = (document.createElement('<input name=x>').name == 'x'); +} catch (e){} + +var escapeQuotes = function(html){ + return ('' + html).replace(/&/g, '&').replace(/"/g, '"'); +}; +/*</ltIE8>*/ + +/*<ltIE9>*/ +// #2479 - IE8 Cannot set HTML of style element +var canChangeStyleHTML = (function(){ + var div = document.createElement('style'), + flag = false; + try { + div.innerHTML = '#justTesing{margin: 0px;}'; + flag = !!div.innerHTML; + } catch(e){} + return flag; +})(); +/*</ltIE9>*/ + +Document.implement({ + + newElement: function(tag, props){ + if (props){ + if (props.checked != null) props.defaultChecked = props.checked; + if ((props.type == 'checkbox' || props.type == 'radio') && props.value == null) props.value = 'on'; + /*<ltIE9>*/ // IE needs the type to be set before changing content of style element + if (!canChangeStyleHTML && tag == 'style'){ + var styleElement = document.createElement('style'); + styleElement.setAttribute('type', 'text/css'); + if (props.type) delete props.type; + return this.id(styleElement).set(props); + } + /*</ltIE9>*/ + /*<ltIE8>*/// Fix for readonly name and type properties in IE < 8 + if (createElementAcceptsHTML){ + tag = '<' + tag; + if (props.name) tag += ' name="' + escapeQuotes(props.name) + '"'; + if (props.type) tag += ' type="' + escapeQuotes(props.type) + '"'; + tag += '>'; + delete props.name; + delete props.type; + } + /*</ltIE8>*/ + } + return this.id(this.createElement(tag)).set(props); + } + +}); + +})(); + +(function(){ + +Slick.uidOf(window); +Slick.uidOf(document); + +Document.implement({ + + newTextNode: function(text){ + return this.createTextNode(text); + }, + + getDocument: function(){ + return this; + }, + + getWindow: function(){ + return this.window; + }, + + id: (function(){ + + var types = { + + string: function(id, nocash, doc){ + id = Slick.find(doc, '#' + id.replace(/(\W)/g, '\\$1')); + return (id) ? types.element(id, nocash) : null; + }, + + element: function(el, nocash){ + Slick.uidOf(el); + if (!nocash && !el.$family && !(/^(?:object|embed)$/i).test(el.tagName)){ + var fireEvent = el.fireEvent; + // wrapping needed in IE7, or else crash + el._fireEvent = function(type, event){ + return fireEvent(type, event); + }; + Object.append(el, Element.Prototype); + } + return el; + }, + + object: function(obj, nocash, doc){ + if (obj.toElement) return types.element(obj.toElement(doc), nocash); + return null; + } + + }; + + types.textnode = types.whitespace = types.window = types.document = function(zero){ + return zero; + }; + + return function(el, nocash, doc){ + if (el && el.$family && el.uniqueNumber) return el; + var type = typeOf(el); + return (types[type]) ? types[type](el, nocash, doc || document) : null; + }; + + })() + +}); + +if (window.$ == null) Window.implement('$', function(el, nc){ + return document.id(el, nc, this.document); +}); + +Window.implement({ + + getDocument: function(){ + return this.document; + }, + + getWindow: function(){ + return this; + } + +}); + +[Document, Element].invoke('implement', { + + getElements: function(expression){ + return Slick.search(this, expression, new Elements); + }, + + getElement: function(expression){ + return document.id(Slick.find(this, expression)); + } + +}); + +var contains = {contains: function(element){ + return Slick.contains(this, element); +}}; + +if (!document.contains) Document.implement(contains); +if (!document.createElement('div').contains) Element.implement(contains); + + + +// tree walking + +var injectCombinator = function(expression, combinator){ + if (!expression) return combinator; + + expression = Object.clone(Slick.parse(expression)); + + var expressions = expression.expressions; + for (var i = expressions.length; i--;) + expressions[i][0].combinator = combinator; + + return expression; +}; + +Object.forEach({ + getNext: '~', + getPrevious: '!~', + getParent: '!' +}, function(combinator, method){ + Element.implement(method, function(expression){ + return this.getElement(injectCombinator(expression, combinator)); + }); +}); + +Object.forEach({ + getAllNext: '~', + getAllPrevious: '!~', + getSiblings: '~~', + getChildren: '>', + getParents: '!' +}, function(combinator, method){ + Element.implement(method, function(expression){ + return this.getElements(injectCombinator(expression, combinator)); + }); +}); + +Element.implement({ + + getFirst: function(expression){ + return document.id(Slick.search(this, injectCombinator(expression, '>'))[0]); + }, + + getLast: function(expression){ + return document.id(Slick.search(this, injectCombinator(expression, '>')).getLast()); + }, + + getWindow: function(){ + return this.ownerDocument.window; + }, + + getDocument: function(){ + return this.ownerDocument; + }, + + getElementById: function(id){ + return document.id(Slick.find(this, '#' + ('' + id).replace(/(\W)/g, '\\$1'))); + }, + + match: function(expression){ + return !expression || Slick.match(this, expression); + } + +}); + + + +if (window.$$ == null) Window.implement('$$', function(selector){ + if (arguments.length == 1){ + if (typeof selector == 'string') return Slick.search(this.document, selector, new Elements); + else if (Type.isEnumerable(selector)) return new Elements(selector); + } + return new Elements(arguments); +}); + +// Inserters + +var inserters = { + + before: function(context, element){ + var parent = element.parentNode; + if (parent) parent.insertBefore(context, element); + }, + + after: function(context, element){ + var parent = element.parentNode; + if (parent) parent.insertBefore(context, element.nextSibling); + }, + + bottom: function(context, element){ + element.appendChild(context); + }, + + top: function(context, element){ + element.insertBefore(context, element.firstChild); + } + +}; + +inserters.inside = inserters.bottom; + + + +// getProperty / setProperty + +var propertyGetters = {}, propertySetters = {}; + +// properties + +var properties = {}; +Array.forEach([ + 'type', 'value', 'defaultValue', 'accessKey', 'cellPadding', 'cellSpacing', 'colSpan', + 'frameBorder', 'rowSpan', 'tabIndex', 'useMap' +], function(property){ + properties[property.toLowerCase()] = property; +}); + +properties.html = 'innerHTML'; +properties.text = (document.createElement('div').textContent == null) ? 'innerText': 'textContent'; + +Object.forEach(properties, function(real, key){ + propertySetters[key] = function(node, value){ + node[real] = value; + }; + propertyGetters[key] = function(node){ + return node[real]; + }; +}); + +/*<ltIE9>*/ +propertySetters.text = (function(setter){ + return function(node, value){ + if (node.get('tag') == 'style') node.set('html', value); + else node[properties.text] = value; + }; +})(propertySetters.text); + +propertyGetters.text = (function(getter){ + return function(node){ + return (node.get('tag') == 'style') ? node.innerHTML : getter(node); + }; +})(propertyGetters.text); +/*</ltIE9>*/ + +// Booleans + +var bools = [ + 'compact', 'nowrap', 'ismap', 'declare', 'noshade', 'checked', + 'disabled', 'readOnly', 'multiple', 'selected', 'noresize', + 'defer', 'defaultChecked', 'autofocus', 'controls', 'autoplay', + 'loop' +]; + +var booleans = {}; +Array.forEach(bools, function(bool){ + var lower = bool.toLowerCase(); + booleans[lower] = bool; + propertySetters[lower] = function(node, value){ + node[bool] = !!value; + }; + propertyGetters[lower] = function(node){ + return !!node[bool]; + }; +}); + +// Special cases + +Object.append(propertySetters, { + + 'class': function(node, value){ + ('className' in node) ? node.className = (value || '') : node.setAttribute('class', value); + }, + + 'for': function(node, value){ + ('htmlFor' in node) ? node.htmlFor = value : node.setAttribute('for', value); + }, + + 'style': function(node, value){ + (node.style) ? node.style.cssText = value : node.setAttribute('style', value); + }, + + 'value': function(node, value){ + node.value = (value != null) ? value : ''; + } + +}); + +propertyGetters['class'] = function(node){ + return ('className' in node) ? node.className || null : node.getAttribute('class'); +}; + +/* <webkit> */ +var el = document.createElement('button'); +// IE sets type as readonly and throws +try { el.type = 'button'; } catch(e){} +if (el.type != 'button') propertySetters.type = function(node, value){ + node.setAttribute('type', value); +}; +el = null; +/* </webkit> */ + +/*<IE>*/ + +/*<ltIE9>*/ +// #2479 - IE8 Cannot set HTML of style element +var canChangeStyleHTML = (function(){ + var div = document.createElement('style'), + flag = false; + try { + div.innerHTML = '#justTesing{margin: 0px;}'; + flag = !!div.innerHTML; + } catch(e){} + return flag; +})(); +/*</ltIE9>*/ + +var input = document.createElement('input'), volatileInputValue, html5InputSupport; + +// #2178 +input.value = 't'; +input.type = 'submit'; +volatileInputValue = input.value != 't'; + +// #2443 - IE throws "Invalid Argument" when trying to use html5 input types +try { + input.type = 'email'; + html5InputSupport = input.type == 'email'; +} catch(e){} + +input = null; + +if (volatileInputValue || !html5InputSupport) propertySetters.type = function(node, type){ + try { + var value = node.value; + node.type = type; + node.value = value; + } catch (e){} +}; +/*</IE>*/ + +/* getProperty, setProperty */ + +/* <ltIE9> */ +var pollutesGetAttribute = (function(div){ + div.random = 'attribute'; + return (div.getAttribute('random') == 'attribute'); +})(document.createElement('div')); + +var hasCloneBug = (function(test){ + test.innerHTML = '<object><param name="should_fix" value="the unknown" /></object>'; + return test.cloneNode(true).firstChild.childNodes.length != 1; +})(document.createElement('div')); +/* </ltIE9> */ + +var hasClassList = !!document.createElement('div').classList; + +var classes = function(className){ + var classNames = (className || '').clean().split(" "), uniques = {}; + return classNames.filter(function(className){ + if (className !== "" && !uniques[className]) return uniques[className] = className; + }); +}; + +var addToClassList = function(name){ + this.classList.add(name); +}; + +var removeFromClassList = function(name){ + this.classList.remove(name); +}; + +Element.implement({ + + setProperty: function(name, value){ + var setter = propertySetters[name.toLowerCase()]; + if (setter){ + setter(this, value); + } else { + /* <ltIE9> */ + var attributeWhiteList; + if (pollutesGetAttribute) attributeWhiteList = this.retrieve('$attributeWhiteList', {}); + /* </ltIE9> */ + + if (value == null){ + this.removeAttribute(name); + /* <ltIE9> */ + if (pollutesGetAttribute) delete attributeWhiteList[name]; + /* </ltIE9> */ + } else { + this.setAttribute(name, '' + value); + /* <ltIE9> */ + if (pollutesGetAttribute) attributeWhiteList[name] = true; + /* </ltIE9> */ + } + } + return this; + }, + + setProperties: function(attributes){ + for (var attribute in attributes) this.setProperty(attribute, attributes[attribute]); + return this; + }, + + getProperty: function(name){ + var getter = propertyGetters[name.toLowerCase()]; + if (getter) return getter(this); + /* <ltIE9> */ + if (pollutesGetAttribute){ + var attr = this.getAttributeNode(name), attributeWhiteList = this.retrieve('$attributeWhiteList', {}); + if (!attr) return null; + if (attr.expando && !attributeWhiteList[name]){ + var outer = this.outerHTML; + // segment by the opening tag and find mention of attribute name + if (outer.substr(0, outer.search(/\/?['"]?>(?![^<]*<['"])/)).indexOf(name) < 0) return null; + attributeWhiteList[name] = true; + } + } + /* </ltIE9> */ + var result = Slick.getAttribute(this, name); + return (!result && !Slick.hasAttribute(this, name)) ? null : result; + }, + + getProperties: function(){ + var args = Array.from(arguments); + return args.map(this.getProperty, this).associate(args); + }, + + removeProperty: function(name){ + return this.setProperty(name, null); + }, + + removeProperties: function(){ + Array.each(arguments, this.removeProperty, this); + return this; + }, + + set: function(prop, value){ + var property = Element.Properties[prop]; + (property && property.set) ? property.set.call(this, value) : this.setProperty(prop, value); + }.overloadSetter(), + + get: function(prop){ + var property = Element.Properties[prop]; + return (property && property.get) ? property.get.apply(this) : this.getProperty(prop); + }.overloadGetter(), + + erase: function(prop){ + var property = Element.Properties[prop]; + (property && property.erase) ? property.erase.apply(this) : this.removeProperty(prop); + return this; + }, + + hasClass: hasClassList ? function(className){ + return this.classList.contains(className); + } : function(className){ + return classes(this.className).contains(className); + }, + + addClass: hasClassList ? function(className){ + classes(className).forEach(addToClassList, this); + return this; + } : function(className){ + this.className = classes(className + ' ' + this.className).join(' '); + return this; + }, + + removeClass: hasClassList ? function(className){ + classes(className).forEach(removeFromClassList, this); + return this; + } : function(className){ + var classNames = classes(this.className); + classes(className).forEach(classNames.erase, classNames); + this.className = classNames.join(' '); + return this; + }, + + toggleClass: function(className, force){ + if (force == null) force = !this.hasClass(className); + return (force) ? this.addClass(className) : this.removeClass(className); + }, + + adopt: function(){ + var parent = this, fragment, elements = Array.flatten(arguments), length = elements.length; + if (length > 1) parent = fragment = document.createDocumentFragment(); + + for (var i = 0; i < length; i++){ + var element = document.id(elements[i], true); + if (element) parent.appendChild(element); + } + + if (fragment) this.appendChild(fragment); + + return this; + }, + + appendText: function(text, where){ + return this.grab(this.getDocument().newTextNode(text), where); + }, + + grab: function(el, where){ + inserters[where || 'bottom'](document.id(el, true), this); + return this; + }, + + inject: function(el, where){ + inserters[where || 'bottom'](this, document.id(el, true)); + return this; + }, + + replaces: function(el){ + el = document.id(el, true); + el.parentNode.replaceChild(this, el); + return this; + }, + + wraps: function(el, where){ + el = document.id(el, true); + return this.replaces(el).grab(el, where); + }, + + getSelected: function(){ + this.selectedIndex; // Safari 3.2.1 + return new Elements(Array.from(this.options).filter(function(option){ + return option.selected; + })); + }, + + toQueryString: function(){ + var queryString = []; + this.getElements('input, select, textarea').each(function(el){ + var type = el.type; + if (!el.name || el.disabled || type == 'submit' || type == 'reset' || type == 'file' || type == 'image') return; + + var value = (el.get('tag') == 'select') ? el.getSelected().map(function(opt){ + // IE + return document.id(opt).get('value'); + }) : ((type == 'radio' || type == 'checkbox') && !el.checked) ? null : el.get('value'); + + Array.from(value).each(function(val){ + if (typeof val != 'undefined') queryString.push(encodeURIComponent(el.name) + '=' + encodeURIComponent(val)); + }); + }); + return queryString.join('&'); + } + +}); + + +// appendHTML + +var appendInserters = { + before: 'beforeBegin', + after: 'afterEnd', + bottom: 'beforeEnd', + top: 'afterBegin', + inside: 'beforeEnd' +}; + +Element.implement('appendHTML', ('insertAdjacentHTML' in document.createElement('div')) ? function(html, where){ + this.insertAdjacentHTML(appendInserters[where || 'bottom'], html); + return this; +} : function(html, where){ + var temp = new Element('div', {html: html}), + children = temp.childNodes, + fragment = temp.firstChild; + + if (!fragment) return this; + if (children.length > 1){ + fragment = document.createDocumentFragment(); + for (var i = 0, l = children.length; i < l; i++){ + fragment.appendChild(children[i]); + } + } + + inserters[where || 'bottom'](fragment, this); + return this; +}); + +var collected = {}, storage = {}; + +var get = function(uid){ + return (storage[uid] || (storage[uid] = {})); +}; + +var clean = function(item){ + var uid = item.uniqueNumber; + if (item.removeEvents) item.removeEvents(); + if (item.clearAttributes) item.clearAttributes(); + if (uid != null){ + delete collected[uid]; + delete storage[uid]; + } + return item; +}; + +var formProps = {input: 'checked', option: 'selected', textarea: 'value'}; + +Element.implement({ + + destroy: function(){ + var children = clean(this).getElementsByTagName('*'); + Array.each(children, clean); + Element.dispose(this); + return null; + }, + + empty: function(){ + Array.from(this.childNodes).each(Element.dispose); + return this; + }, + + dispose: function(){ + return (this.parentNode) ? this.parentNode.removeChild(this) : this; + }, + + clone: function(contents, keepid){ + contents = contents !== false; + var clone = this.cloneNode(contents), ce = [clone], te = [this], i; + + if (contents){ + ce.append(Array.from(clone.getElementsByTagName('*'))); + te.append(Array.from(this.getElementsByTagName('*'))); + } + + for (i = ce.length; i--;){ + var node = ce[i], element = te[i]; + if (!keepid) node.removeAttribute('id'); + /*<ltIE9>*/ + if (node.clearAttributes){ + node.clearAttributes(); + node.mergeAttributes(element); + node.removeAttribute('uniqueNumber'); + if (node.options){ + var no = node.options, eo = element.options; + for (var j = no.length; j--;) no[j].selected = eo[j].selected; + } + } + /*</ltIE9>*/ + var prop = formProps[element.tagName.toLowerCase()]; + if (prop && element[prop]) node[prop] = element[prop]; + } + + /*<ltIE9>*/ + if (hasCloneBug){ + var co = clone.getElementsByTagName('object'), to = this.getElementsByTagName('object'); + for (i = co.length; i--;) co[i].outerHTML = to[i].outerHTML; + } + /*</ltIE9>*/ + return document.id(clone); + } + +}); + +[Element, Window, Document].invoke('implement', { + + addListener: function(type, fn){ + if (window.attachEvent && !window.addEventListener){ + collected[Slick.uidOf(this)] = this; + } + if (this.addEventListener) this.addEventListener(type, fn, !!arguments[2]); + else this.attachEvent('on' + type, fn); + return this; + }, + + removeListener: function(type, fn){ + if (this.removeEventListener) this.removeEventListener(type, fn, !!arguments[2]); + else this.detachEvent('on' + type, fn); + return this; + }, + + retrieve: function(property, dflt){ + var storage = get(Slick.uidOf(this)), prop = storage[property]; + if (dflt != null && prop == null) prop = storage[property] = dflt; + return prop != null ? prop : null; + }, + + store: function(property, value){ + var storage = get(Slick.uidOf(this)); + storage[property] = value; + return this; + }, + + eliminate: function(property){ + var storage = get(Slick.uidOf(this)); + delete storage[property]; + return this; + } + +}); + +/*<ltIE9>*/ +if (window.attachEvent && !window.addEventListener){ + var gc = function(){ + Object.each(collected, clean); + if (window.CollectGarbage) CollectGarbage(); + window.removeListener('unload', gc); + } + window.addListener('unload', gc); +} +/*</ltIE9>*/ + +Element.Properties = {}; + + + +Element.Properties.style = { + + set: function(style){ + this.style.cssText = style; + }, + + get: function(){ + return this.style.cssText; + }, + + erase: function(){ + this.style.cssText = ''; + } + +}; + +Element.Properties.tag = { + + get: function(){ + return this.tagName.toLowerCase(); + } + +}; + +Element.Properties.html = { + + set: function(html){ + if (html == null) html = ''; + else if (typeOf(html) == 'array') html = html.join(''); + + /*<ltIE9>*/ + if (this.styleSheet && !canChangeStyleHTML) this.styleSheet.cssText = html; + else /*</ltIE9>*/this.innerHTML = html; + }, + erase: function(){ + this.set('html', ''); + } + +}; + +var supportsHTML5Elements = true, supportsTableInnerHTML = true, supportsTRInnerHTML = true; + +/*<ltIE9>*/ +// technique by jdbarlett - http://jdbartlett.com/innershiv/ +var div = document.createElement('div'); +div.innerHTML = '<nav></nav>'; +supportsHTML5Elements = (div.childNodes.length == 1); +if (!supportsHTML5Elements){ + var tags = 'abbr article aside audio canvas datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video'.split(' '), + fragment = document.createDocumentFragment(), l = tags.length; + while (l--) fragment.createElement(tags[l]); +} +div = null; +/*</ltIE9>*/ + +/*<IE>*/ +supportsTableInnerHTML = Function.attempt(function(){ + var table = document.createElement('table'); + table.innerHTML = '<tr><td></td></tr>'; + return true; +}); + +/*<ltFF4>*/ +var tr = document.createElement('tr'), html = '<td></td>'; +tr.innerHTML = html; +supportsTRInnerHTML = (tr.innerHTML == html); +tr = null; +/*</ltFF4>*/ + +if (!supportsTableInnerHTML || !supportsTRInnerHTML || !supportsHTML5Elements){ + + Element.Properties.html.set = (function(set){ + + var translations = { + table: [1, '<table>', '</table>'], + select: [1, '<select>', '</select>'], + tbody: [2, '<table><tbody>', '</tbody></table>'], + tr: [3, '<table><tbody><tr>', '</tr></tbody></table>'] + }; + + translations.thead = translations.tfoot = translations.tbody; + + return function(html){ + + /*<ltIE9>*/ + if (this.styleSheet) return set.call(this, html); + /*</ltIE9>*/ + var wrap = translations[this.get('tag')]; + if (!wrap && !supportsHTML5Elements) wrap = [0, '', '']; + if (!wrap) return set.call(this, html); + + var level = wrap[0], wrapper = document.createElement('div'), target = wrapper; + if (!supportsHTML5Elements) fragment.appendChild(wrapper); + wrapper.innerHTML = [wrap[1], html, wrap[2]].flatten().join(''); + while (level--) target = target.firstChild; + this.empty().adopt(target.childNodes); + if (!supportsHTML5Elements) fragment.removeChild(wrapper); + wrapper = null; + }; + + })(Element.Properties.html.set); +} +/*</IE>*/ + +/*<ltIE9>*/ +var testForm = document.createElement('form'); +testForm.innerHTML = '<select><option>s</option></select>'; + +if (testForm.firstChild.value != 's') Element.Properties.value = { + + set: function(value){ + var tag = this.get('tag'); + if (tag != 'select') return this.setProperty('value', value); + var options = this.getElements('option'); + value = String(value); + for (var i = 0; i < options.length; i++){ + var option = options[i], + attr = option.getAttributeNode('value'), + optionValue = (attr && attr.specified) ? option.value : option.get('text'); + if (optionValue === value) return option.selected = true; + } + }, + + get: function(){ + var option = this, tag = option.get('tag'); + + if (tag != 'select' && tag != 'option') return this.getProperty('value'); + + if (tag == 'select' && !(option = option.getSelected()[0])) return ''; + + var attr = option.getAttributeNode('value'); + return (attr && attr.specified) ? option.value : option.get('text'); + } + +}; +testForm = null; +/*</ltIE9>*/ + +/*<IE>*/ +if (document.createElement('div').getAttributeNode('id')) Element.Properties.id = { + set: function(id){ + this.id = this.getAttributeNode('id').value = id; + }, + get: function(){ + return this.id || null; + }, + erase: function(){ + this.id = this.getAttributeNode('id').value = ''; + } +}; +/*</IE>*/ + +})(); + +/* +--- + +name: Event + +description: Contains the Event Type, to make the event object cross-browser. + +license: MIT-style license. + +requires: [Window, Document, Array, Function, String, Object] + +provides: Event + +... +*/ + +(function(){ + +var _keys = {}; +var normalizeWheelSpeed = function(event){ + var normalized; + if (event.wheelDelta){ + normalized = event.wheelDelta % 120 == 0 ? event.wheelDelta / 120 : event.wheelDelta / 12; + } else { + var rawAmount = event.deltaY || event.detail || 0; + normalized = -(rawAmount % 3 == 0 ? rawAmount / 3 : rawAmount * 10); + } + return normalized; +} + +var DOMEvent = this.DOMEvent = new Type('DOMEvent', function(event, win){ + if (!win) win = window; + event = event || win.event; + if (event.$extended) return event; + this.event = event; + this.$extended = true; + this.shift = event.shiftKey; + this.control = event.ctrlKey; + this.alt = event.altKey; + this.meta = event.metaKey; + var type = this.type = event.type; + var target = event.target || event.srcElement; + while (target && target.nodeType == 3) target = target.parentNode; + this.target = document.id(target); + + if (type.indexOf('key') == 0){ + var code = this.code = (event.which || event.keyCode); + this.key = _keys[code]; + if (type == 'keydown' || type == 'keyup'){ + if (code > 111 && code < 124) this.key = 'f' + (code - 111); + else if (code > 95 && code < 106) this.key = code - 96; + } + if (this.key == null) this.key = String.fromCharCode(code).toLowerCase(); + } else if (type == 'click' || type == 'dblclick' || type == 'contextmenu' || type == 'wheel' || type == 'DOMMouseScroll' || type.indexOf('mouse') == 0){ + var doc = win.document; + doc = (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body; + this.page = { + x: (event.pageX != null) ? event.pageX : event.clientX + doc.scrollLeft, + y: (event.pageY != null) ? event.pageY : event.clientY + doc.scrollTop + }; + this.client = { + x: (event.pageX != null) ? event.pageX - win.pageXOffset : event.clientX, + y: (event.pageY != null) ? event.pageY - win.pageYOffset : event.clientY + }; + if (type == 'DOMMouseScroll' || type == 'wheel' || type == 'mousewheel') this.wheel = normalizeWheelSpeed(event); + this.rightClick = (event.which == 3 || event.button == 2); + if (type == 'mouseover' || type == 'mouseout'){ + var related = event.relatedTarget || event[(type == 'mouseover' ? 'from' : 'to') + 'Element']; + while (related && related.nodeType == 3) related = related.parentNode; + this.relatedTarget = document.id(related); + } + } else if (type.indexOf('touch') == 0 || type.indexOf('gesture') == 0){ + this.rotation = event.rotation; + this.scale = event.scale; + this.targetTouches = event.targetTouches; + this.changedTouches = event.changedTouches; + var touches = this.touches = event.touches; + if (touches && touches[0]){ + var touch = touches[0]; + this.page = {x: touch.pageX, y: touch.pageY}; + this.client = {x: touch.clientX, y: touch.clientY}; + } + } + + if (!this.client) this.client = {}; + if (!this.page) this.page = {}; +}); + +DOMEvent.implement({ + + stop: function(){ + return this.preventDefault().stopPropagation(); + }, + + stopPropagation: function(){ + if (this.event.stopPropagation) this.event.stopPropagation(); + else this.event.cancelBubble = true; + return this; + }, + + preventDefault: function(){ + if (this.event.preventDefault) this.event.preventDefault(); + else this.event.returnValue = false; + return this; + } + +}); + +DOMEvent.defineKey = function(code, key){ + _keys[code] = key; + return this; +}; + +DOMEvent.defineKeys = DOMEvent.defineKey.overloadSetter(true); + +DOMEvent.defineKeys({ + '38': 'up', '40': 'down', '37': 'left', '39': 'right', + '27': 'esc', '32': 'space', '8': 'backspace', '9': 'tab', + '46': 'delete', '13': 'enter' +}); + +})(); + + + + + +/* +--- + +name: Element.Event + +description: Contains Element methods for dealing with events. This file also includes mouseenter and mouseleave custom Element Events, if necessary. + +license: MIT-style license. + +requires: [Element, Event] + +provides: Element.Event + +... +*/ + +(function(){ + +Element.Properties.events = {set: function(events){ + this.addEvents(events); +}}; + +[Element, Window, Document].invoke('implement', { + + addEvent: function(type, fn){ + var events = this.retrieve('events', {}); + if (!events[type]) events[type] = {keys: [], values: []}; + if (events[type].keys.contains(fn)) return this; + events[type].keys.push(fn); + var realType = type, + custom = Element.Events[type], + condition = fn, + self = this; + if (custom){ + if (custom.onAdd) custom.onAdd.call(this, fn, type); + if (custom.condition){ + condition = function(event){ + if (custom.condition.call(this, event, type)) return fn.call(this, event); + return true; + }; + } + if (custom.base) realType = Function.from(custom.base).call(this, type); + } + var defn = function(){ + return fn.call(self); + }; + var nativeEvent = Element.NativeEvents[realType]; + if (nativeEvent){ + if (nativeEvent == 2){ + defn = function(event){ + event = new DOMEvent(event, self.getWindow()); + if (condition.call(self, event) === false) event.stop(); + }; + } + this.addListener(realType, defn, arguments[2]); + } + events[type].values.push(defn); + return this; + }, + + removeEvent: function(type, fn){ + var events = this.retrieve('events'); + if (!events || !events[type]) return this; + var list = events[type]; + var index = list.keys.indexOf(fn); + if (index == -1) return this; + var value = list.values[index]; + delete list.keys[index]; + delete list.values[index]; + var custom = Element.Events[type]; + if (custom){ + if (custom.onRemove) custom.onRemove.call(this, fn, type); + if (custom.base) type = Function.from(custom.base).call(this, type); + } + return (Element.NativeEvents[type]) ? this.removeListener(type, value, arguments[2]) : this; + }, + + addEvents: function(events){ + for (var event in events) this.addEvent(event, events[event]); + return this; + }, + + removeEvents: function(events){ + var type; + if (typeOf(events) == 'object'){ + for (type in events) this.removeEvent(type, events[type]); + return this; + } + var attached = this.retrieve('events'); + if (!attached) return this; + if (!events){ + for (type in attached) this.removeEvents(type); + this.eliminate('events'); + } else if (attached[events]){ + attached[events].keys.each(function(fn){ + this.removeEvent(events, fn); + }, this); + delete attached[events]; + } + return this; + }, + + fireEvent: function(type, args, delay){ + var events = this.retrieve('events'); + if (!events || !events[type]) return this; + args = Array.from(args); + + events[type].keys.each(function(fn){ + if (delay) fn.delay(delay, this, args); + else fn.apply(this, args); + }, this); + return this; + }, + + cloneEvents: function(from, type){ + from = document.id(from); + var events = from.retrieve('events'); + if (!events) return this; + if (!type){ + for (var eventType in events) this.cloneEvents(from, eventType); + } else if (events[type]){ + events[type].keys.each(function(fn){ + this.addEvent(type, fn); + }, this); + } + return this; + } + +}); + +Element.NativeEvents = { + click: 2, dblclick: 2, mouseup: 2, mousedown: 2, contextmenu: 2, //mouse buttons + wheel: 2, mousewheel: 2, DOMMouseScroll: 2, //mouse wheel + mouseover: 2, mouseout: 2, mousemove: 2, selectstart: 2, selectend: 2, //mouse movement + keydown: 2, keypress: 2, keyup: 2, //keyboard + orientationchange: 2, // mobile + touchstart: 2, touchmove: 2, touchend: 2, touchcancel: 2, // touch + gesturestart: 2, gesturechange: 2, gestureend: 2, // gesture + focus: 2, blur: 2, change: 2, reset: 2, select: 2, submit: 2, paste: 2, input: 2, //form elements + load: 2, unload: 1, beforeunload: 2, resize: 1, move: 1, DOMContentLoaded: 1, readystatechange: 1, //window + hashchange: 1, popstate: 2, // history + error: 1, abort: 1, scroll: 1, message: 2 //misc +}; + +Element.Events = { + mousewheel: { + base: 'onwheel' in document ? 'wheel' : 'onmousewheel' in document ? 'mousewheel' : 'DOMMouseScroll' + } +}; + +var check = function(event){ + var related = event.relatedTarget; + if (related == null) return true; + if (!related) return false; + return (related != this && related.prefix != 'xul' && typeOf(this) != 'document' && !this.contains(related)); +}; + +if ('onmouseenter' in document.documentElement){ + Element.NativeEvents.mouseenter = Element.NativeEvents.mouseleave = 2; + Element.MouseenterCheck = check; +} else { + Element.Events.mouseenter = { + base: 'mouseover', + condition: check + }; + + Element.Events.mouseleave = { + base: 'mouseout', + condition: check + }; +} + +/*<ltIE9>*/ +if (!window.addEventListener){ + Element.NativeEvents.propertychange = 2; + Element.Events.change = { + base: function(){ + var type = this.type; + return (this.get('tag') == 'input' && (type == 'radio' || type == 'checkbox')) ? 'propertychange' : 'change'; + }, + condition: function(event){ + return event.type != 'propertychange' || event.event.propertyName == 'checked'; + } + }; +} +/*</ltIE9>*/ + + + +})(); + +/* +--- + +name: Element.Delegation + +description: Extends the Element native object to include the delegate method for more efficient event management. + +license: MIT-style license. + +requires: [Element.Event] + +provides: [Element.Delegation] + +... +*/ + +(function(){ + +var eventListenerSupport = !!window.addEventListener; + +Element.NativeEvents.focusin = Element.NativeEvents.focusout = 2; + +var bubbleUp = function(self, match, fn, event, target){ + while (target && target != self){ + if (match(target, event)) return fn.call(target, event, target); + target = document.id(target.parentNode); + } +}; + +var map = { + mouseenter: { + base: 'mouseover', + condition: Element.MouseenterCheck + }, + mouseleave: { + base: 'mouseout', + condition: Element.MouseenterCheck + }, + focus: { + base: 'focus' + (eventListenerSupport ? '' : 'in'), + capture: true + }, + blur: { + base: eventListenerSupport ? 'blur' : 'focusout', + capture: true + } +}; + +/*<ltIE9>*/ +var _key = '$delegation:'; +var formObserver = function(type){ + + return { + + base: 'focusin', + + remove: function(self, uid){ + var list = self.retrieve(_key + type + 'listeners', {})[uid]; + if (list && list.forms) for (var i = list.forms.length; i--;){ + // the form may have been destroyed, so it won't have the + // removeEvent method anymore. In that case the event was + // removed as well. + if (list.forms[i].removeEvent) list.forms[i].removeEvent(type, list.fns[i]); + } + }, + + listen: function(self, match, fn, event, target, uid){ + var form = (target.get('tag') == 'form') ? target : event.target.getParent('form'); + if (!form) return; + + var listeners = self.retrieve(_key + type + 'listeners', {}), + listener = listeners[uid] || {forms: [], fns: []}, + forms = listener.forms, fns = listener.fns; + + if (forms.indexOf(form) != -1) return; + forms.push(form); + + var _fn = function(event){ + bubbleUp(self, match, fn, event, target); + }; + form.addEvent(type, _fn); + fns.push(_fn); + + listeners[uid] = listener; + self.store(_key + type + 'listeners', listeners); + } + }; +}; + +var inputObserver = function(type){ + return { + base: 'focusin', + listen: function(self, match, fn, event, target){ + var events = {blur: function(){ + this.removeEvents(events); + }}; + events[type] = function(event){ + bubbleUp(self, match, fn, event, target); + }; + event.target.addEvents(events); + } + }; +}; + +if (!eventListenerSupport) Object.append(map, { + submit: formObserver('submit'), + reset: formObserver('reset'), + change: inputObserver('change'), + select: inputObserver('select') +}); +/*</ltIE9>*/ + +var proto = Element.prototype, + addEvent = proto.addEvent, + removeEvent = proto.removeEvent; + +var relay = function(old, method){ + return function(type, fn, useCapture){ + if (type.indexOf(':relay') == -1) return old.call(this, type, fn, useCapture); + var parsed = Slick.parse(type).expressions[0][0]; + if (parsed.pseudos[0].key != 'relay') return old.call(this, type, fn, useCapture); + var newType = parsed.tag; + parsed.pseudos.slice(1).each(function(pseudo){ + newType += ':' + pseudo.key + (pseudo.value ? '(' + pseudo.value + ')' : ''); + }); + old.call(this, type, fn); + return method.call(this, newType, parsed.pseudos[0].value, fn); + }; +}; + +var delegation = { + + addEvent: function(type, match, fn){ + var storage = this.retrieve('$delegates', {}), stored = storage[type]; + if (stored) for (var _uid in stored){ + if (stored[_uid].fn == fn && stored[_uid].match == match) return this; + } + + var _type = type, _match = match, _fn = fn, _map = map[type] || {}; + type = _map.base || _type; + + match = function(target){ + return Slick.match(target, _match); + }; + + var elementEvent = Element.Events[_type]; + if (_map.condition || elementEvent && elementEvent.condition){ + var __match = match, condition = _map.condition || elementEvent.condition; + match = function(target, event){ + return __match(target, event) && condition.call(target, event, type); + }; + } + + var self = this, uid = String.uniqueID(); + var delegator = _map.listen ? function(event, target){ + if (!target && event && event.target) target = event.target; + if (target) _map.listen(self, match, fn, event, target, uid); + } : function(event, target){ + if (!target && event && event.target) target = event.target; + if (target) bubbleUp(self, match, fn, event, target); + }; + + if (!stored) stored = {}; + stored[uid] = { + match: _match, + fn: _fn, + delegator: delegator + }; + storage[_type] = stored; + return addEvent.call(this, type, delegator, _map.capture); + }, + + removeEvent: function(type, match, fn, _uid){ + var storage = this.retrieve('$delegates', {}), stored = storage[type]; + if (!stored) return this; + + if (_uid){ + var _type = type, delegator = stored[_uid].delegator, _map = map[type] || {}; + type = _map.base || _type; + if (_map.remove) _map.remove(this, _uid); + delete stored[_uid]; + storage[_type] = stored; + return removeEvent.call(this, type, delegator, _map.capture); + } + + var __uid, s; + if (fn) for (__uid in stored){ + s = stored[__uid]; + if (s.match == match && s.fn == fn) return delegation.removeEvent.call(this, type, match, fn, __uid); + } else for (__uid in stored){ + s = stored[__uid]; + if (s.match == match) delegation.removeEvent.call(this, type, match, s.fn, __uid); + } + return this; + } + +}; + +[Element, Window, Document].invoke('implement', { + addEvent: relay(addEvent, delegation.addEvent), + removeEvent: relay(removeEvent, delegation.removeEvent) +}); + +})(); + +/* +--- + +name: Element.Style + +description: Contains methods for interacting with the styles of Elements in a fashionable way. + +license: MIT-style license. + +requires: Element + +provides: Element.Style + +... +*/ + +(function(){ + +var html = document.html, el; + +//<ltIE9> +// Check for oldIE, which does not remove styles when they're set to null +el = document.createElement('div'); +el.style.color = 'red'; +el.style.color = null; +var doesNotRemoveStyles = el.style.color == 'red'; + +// check for oldIE, which returns border* shorthand styles in the wrong order (color-width-style instead of width-style-color) +var border = '1px solid #123abc'; +el.style.border = border; +var returnsBordersInWrongOrder = el.style.border != border; +el = null; +//</ltIE9> + +var hasGetComputedStyle = !!window.getComputedStyle, + supportBorderRadius = document.createElement('div').style.borderRadius != null; + +Element.Properties.styles = {set: function(styles){ + this.setStyles(styles); +}}; + +var hasOpacity = (html.style.opacity != null), + hasFilter = (html.style.filter != null), + reAlpha = /alpha\(opacity=([\d.]+)\)/i; + +var setVisibility = function(element, opacity){ + element.store('$opacity', opacity); + element.style.visibility = opacity > 0 || opacity == null ? 'visible' : 'hidden'; +}; + +//<ltIE9> +var setFilter = function(element, regexp, value){ + var style = element.style, + filter = style.filter || element.getComputedStyle('filter') || ''; + style.filter = (regexp.test(filter) ? filter.replace(regexp, value) : filter + ' ' + value).trim(); + if (!style.filter) style.removeAttribute('filter'); +}; +//</ltIE9> + +var setOpacity = (hasOpacity ? function(element, opacity){ + element.style.opacity = opacity; +} : (hasFilter ? function(element, opacity){ + if (!element.currentStyle || !element.currentStyle.hasLayout) element.style.zoom = 1; + if (opacity == null || opacity == 1){ + setFilter(element, reAlpha, ''); + if (opacity == 1 && getOpacity(element) != 1) setFilter(element, reAlpha, 'alpha(opacity=100)'); + } else { + setFilter(element, reAlpha, 'alpha(opacity=' + (opacity * 100).limit(0, 100).round() + ')'); + } +} : setVisibility)); + +var getOpacity = (hasOpacity ? function(element){ + var opacity = element.style.opacity || element.getComputedStyle('opacity'); + return (opacity == '') ? 1 : opacity.toFloat(); +} : (hasFilter ? function(element){ + var filter = (element.style.filter || element.getComputedStyle('filter')), + opacity; + if (filter) opacity = filter.match(reAlpha); + return (opacity == null || filter == null) ? 1 : (opacity[1] / 100); +} : function(element){ + var opacity = element.retrieve('$opacity'); + if (opacity == null) opacity = (element.style.visibility == 'hidden' ? 0 : 1); + return opacity; +})); + +var floatName = (html.style.cssFloat == null) ? 'styleFloat' : 'cssFloat', + namedPositions = {left: '0%', top: '0%', center: '50%', right: '100%', bottom: '100%'}, + hasBackgroundPositionXY = (html.style.backgroundPositionX != null); + +//<ltIE9> +var removeStyle = function(style, property){ + if (property == 'backgroundPosition'){ + style.removeAttribute(property + 'X'); + property += 'Y'; + } + style.removeAttribute(property); +}; +//</ltIE9> + +Element.implement({ + + getComputedStyle: function(property){ + if (!hasGetComputedStyle && this.currentStyle) return this.currentStyle[property.camelCase()]; + var defaultView = Element.getDocument(this).defaultView, + computed = defaultView ? defaultView.getComputedStyle(this, null) : null; + return (computed) ? computed.getPropertyValue((property == floatName) ? 'float' : property.hyphenate()) : ''; + }, + + setStyle: function(property, value){ + if (property == 'opacity'){ + if (value != null) value = parseFloat(value); + setOpacity(this, value); + return this; + } + property = (property == 'float' ? floatName : property).camelCase(); + if (typeOf(value) != 'string'){ + var map = (Element.Styles[property] || '@').split(' '); + value = Array.from(value).map(function(val, i){ + if (!map[i]) return ''; + return (typeOf(val) == 'number') ? map[i].replace('@', Math.round(val)) : val; + }).join(' '); + } else if (value == String(Number(value))){ + value = Math.round(value); + } + this.style[property] = value; + //<ltIE9> + if ((value == '' || value == null) && doesNotRemoveStyles && this.style.removeAttribute){ + removeStyle(this.style, property); + } + //</ltIE9> + return this; + }, + + getStyle: function(property){ + if (property == 'opacity') return getOpacity(this); + property = (property == 'float' ? floatName : property).camelCase(); + if (supportBorderRadius && property.indexOf('borderRadius') != -1){ + return ['borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomRightRadius', 'borderBottomLeftRadius'].map(function(corner){ + return this.style[corner] || '0px'; + }, this).join(' '); + } + var result = this.style[property]; + if (!result || property == 'zIndex'){ + if (Element.ShortStyles.hasOwnProperty(property)){ + result = []; + for (var s in Element.ShortStyles[property]) result.push(this.getStyle(s)); + return result.join(' '); + } + result = this.getComputedStyle(property); + } + if (hasBackgroundPositionXY && /^backgroundPosition[XY]?$/.test(property)){ + return result.replace(/(top|right|bottom|left)/g, function(position){ + return namedPositions[position]; + }) || '0px'; + } + if (!result && property == 'backgroundPosition') return '0px 0px'; + if (result){ + result = String(result); + var color = result.match(/rgba?\([\d\s,]+\)/); + if (color) result = result.replace(color[0], color[0].rgbToHex()); + } + if (!hasGetComputedStyle && !this.style[property]){ + if ((/^(height|width)$/).test(property) && !(/px$/.test(result))){ + var values = (property == 'width') ? ['left', 'right'] : ['top', 'bottom'], size = 0; + values.each(function(value){ + size += this.getStyle('border-' + value + '-width').toInt() + this.getStyle('padding-' + value).toInt(); + }, this); + return this['offset' + property.capitalize()] - size + 'px'; + } + if ((/^border(.+)Width|margin|padding/).test(property) && isNaN(parseFloat(result))){ + return '0px'; + } + } + //<ltIE9> + if (returnsBordersInWrongOrder && /^border(Top|Right|Bottom|Left)?$/.test(property) && /^#/.test(result)){ + return result.replace(/^(.+)\s(.+)\s(.+)$/, '$2 $3 $1'); + } + //</ltIE9> + + return result; + }, + + setStyles: function(styles){ + for (var style in styles) this.setStyle(style, styles[style]); + return this; + }, + + getStyles: function(){ + var result = {}; + Array.flatten(arguments).each(function(key){ + result[key] = this.getStyle(key); + }, this); + return result; + } + +}); + +Element.Styles = { + left: '@px', top: '@px', bottom: '@px', right: '@px', + width: '@px', height: '@px', maxWidth: '@px', maxHeight: '@px', minWidth: '@px', minHeight: '@px', + backgroundColor: 'rgb(@, @, @)', backgroundSize: '@px', backgroundPosition: '@px @px', color: 'rgb(@, @, @)', + fontSize: '@px', letterSpacing: '@px', lineHeight: '@px', clip: 'rect(@px @px @px @px)', + margin: '@px @px @px @px', padding: '@px @px @px @px', border: '@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)', + borderWidth: '@px @px @px @px', borderStyle: '@ @ @ @', borderColor: 'rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)', + zIndex: '@', 'zoom': '@', fontWeight: '@', textIndent: '@px', opacity: '@', borderRadius: '@px @px @px @px' +}; + + + + + +Element.ShortStyles = {margin: {}, padding: {}, border: {}, borderWidth: {}, borderStyle: {}, borderColor: {}}; + +['Top', 'Right', 'Bottom', 'Left'].each(function(direction){ + var Short = Element.ShortStyles; + var All = Element.Styles; + ['margin', 'padding'].each(function(style){ + var sd = style + direction; + Short[style][sd] = All[sd] = '@px'; + }); + var bd = 'border' + direction; + Short.border[bd] = All[bd] = '@px @ rgb(@, @, @)'; + var bdw = bd + 'Width', bds = bd + 'Style', bdc = bd + 'Color'; + Short[bd] = {}; + Short.borderWidth[bdw] = Short[bd][bdw] = All[bdw] = '@px'; + Short.borderStyle[bds] = Short[bd][bds] = All[bds] = '@'; + Short.borderColor[bdc] = Short[bd][bdc] = All[bdc] = 'rgb(@, @, @)'; +}); + +if (hasBackgroundPositionXY) Element.ShortStyles.backgroundPosition = {backgroundPositionX: '@', backgroundPositionY: '@'}; +})(); + +/* +--- + +name: Element.Dimensions + +description: Contains methods to work with size, scroll, or positioning of Elements and the window object. + +license: MIT-style license. + +credits: + - Element positioning based on the [qooxdoo](http://qooxdoo.org/) code and smart browser fixes, [LGPL License](http://www.gnu.org/licenses/lgpl.html). + - Viewport dimensions based on [YUI](http://developer.yahoo.com/yui/) code, [BSD License](http://developer.yahoo.com/yui/license.html). + +requires: [Element, Element.Style] + +provides: [Element.Dimensions] + +... +*/ + +(function(){ + +var element = document.createElement('div'), + child = document.createElement('div'); +element.style.height = '0'; +element.appendChild(child); +var brokenOffsetParent = (child.offsetParent === element); +element = child = null; + +var heightComponents = ['height', 'paddingTop', 'paddingBottom', 'borderTopWidth', 'borderBottomWidth'], + widthComponents = ['width', 'paddingLeft', 'paddingRight', 'borderLeftWidth', 'borderRightWidth']; + +var svgCalculateSize = function(el){ + + var gCS = window.getComputedStyle(el), + bounds = {x: 0, y: 0}; + + heightComponents.each(function(css){ + bounds.y += parseFloat(gCS[css]); + }); + widthComponents.each(function(css){ + bounds.x += parseFloat(gCS[css]); + }); + return bounds; +}; + +var isOffset = function(el){ + return styleString(el, 'position') != 'static' || isBody(el); +}; + +var isOffsetStatic = function(el){ + return isOffset(el) || (/^(?:table|td|th)$/i).test(el.tagName); +}; + +Element.implement({ + + scrollTo: function(x, y){ + if (isBody(this)){ + this.getWindow().scrollTo(x, y); + } else { + this.scrollLeft = x; + this.scrollTop = y; + } + return this; + }, + + getSize: function(){ + if (isBody(this)) return this.getWindow().getSize(); + + //<ltIE9> + // This if clause is because IE8- cannot calculate getBoundingClientRect of elements with visibility hidden. + if (!window.getComputedStyle) return {x: this.offsetWidth, y: this.offsetHeight}; + //</ltIE9> + + // This svg section under, calling `svgCalculateSize()`, can be removed when FF fixed the svg size bug. + // Bug info: https://bugzilla.mozilla.org/show_bug.cgi?id=530985 + if (this.get('tag') == 'svg') return svgCalculateSize(this); + + var bounds = this.getBoundingClientRect(); + return {x: bounds.width, y: bounds.height}; + }, + + getScrollSize: function(){ + if (isBody(this)) return this.getWindow().getScrollSize(); + return {x: this.scrollWidth, y: this.scrollHeight}; + }, + + getScroll: function(){ + if (isBody(this)) return this.getWindow().getScroll(); + return {x: this.scrollLeft, y: this.scrollTop}; + }, + + getScrolls: function(){ + var element = this.parentNode, position = {x: 0, y: 0}; + while (element && !isBody(element)){ + position.x += element.scrollLeft; + position.y += element.scrollTop; + element = element.parentNode; + } + return position; + }, + + getOffsetParent: brokenOffsetParent ? function(){ + var element = this; + if (isBody(element) || styleString(element, 'position') == 'fixed') return null; + + var isOffsetCheck = (styleString(element, 'position') == 'static') ? isOffsetStatic : isOffset; + while ((element = element.parentNode)){ + if (isOffsetCheck(element)) return element; + } + return null; + } : function(){ + var element = this; + if (isBody(element) || styleString(element, 'position') == 'fixed') return null; + + try { + return element.offsetParent; + } catch(e){} + return null; + }, + + getOffsets: function(){ + var hasGetBoundingClientRect = this.getBoundingClientRect; + + if (hasGetBoundingClientRect){ + var bound = this.getBoundingClientRect(), + html = document.id(this.getDocument().documentElement), + htmlScroll = html.getScroll(), + elemScrolls = this.getScrolls(), + isFixed = (styleString(this, 'position') == 'fixed'); + + return { + x: bound.left.toInt() + elemScrolls.x + ((isFixed) ? 0 : htmlScroll.x) - html.clientLeft, + y: bound.top.toInt() + elemScrolls.y + ((isFixed) ? 0 : htmlScroll.y) - html.clientTop + }; + } + + var element = this, position = {x: 0, y: 0}; + if (isBody(this)) return position; + + while (element && !isBody(element)){ + position.x += element.offsetLeft; + position.y += element.offsetTop; + + element = element.offsetParent; + } + + return position; + }, + + getPosition: function(relative){ + var offset = this.getOffsets(), + scroll = this.getScrolls(); + var position = { + x: offset.x - scroll.x, + y: offset.y - scroll.y + }; + + if (relative && (relative = document.id(relative))){ + var relativePosition = relative.getPosition(); + return {x: position.x - relativePosition.x - leftBorder(relative), y: position.y - relativePosition.y - topBorder(relative)}; + } + return position; + }, + + getCoordinates: function(element){ + if (isBody(this)) return this.getWindow().getCoordinates(); + var position = this.getPosition(element), + size = this.getSize(); + var obj = { + left: position.x, + top: position.y, + width: size.x, + height: size.y + }; + obj.right = obj.left + obj.width; + obj.bottom = obj.top + obj.height; + return obj; + }, + + computePosition: function(obj){ + return { + left: obj.x - styleNumber(this, 'margin-left'), + top: obj.y - styleNumber(this, 'margin-top') + }; + }, + + setPosition: function(obj){ + return this.setStyles(this.computePosition(obj)); + } + +}); + + +[Document, Window].invoke('implement', { + + getSize: function(){ + var doc = getCompatElement(this); + return {x: doc.clientWidth, y: doc.clientHeight}; + }, + + getScroll: function(){ + var win = this.getWindow(), doc = getCompatElement(this); + return {x: win.pageXOffset || doc.scrollLeft, y: win.pageYOffset || doc.scrollTop}; + }, + + getScrollSize: function(){ + var doc = getCompatElement(this), + min = this.getSize(), + body = this.getDocument().body; + + return {x: Math.max(doc.scrollWidth, body.scrollWidth, min.x), y: Math.max(doc.scrollHeight, body.scrollHeight, min.y)}; + }, + + getPosition: function(){ + return {x: 0, y: 0}; + }, + + getCoordinates: function(){ + var size = this.getSize(); + return {top: 0, left: 0, bottom: size.y, right: size.x, height: size.y, width: size.x}; + } + +}); + +// private methods + +var styleString = Element.getComputedStyle; + +function styleNumber(element, style){ + return styleString(element, style).toInt() || 0; +} + +function borderBox(element){ + return styleString(element, '-moz-box-sizing') == 'border-box'; +} + +function topBorder(element){ + return styleNumber(element, 'border-top-width'); +} + +function leftBorder(element){ + return styleNumber(element, 'border-left-width'); +} + +function isBody(element){ + return (/^(?:body|html)$/i).test(element.tagName); +} + +function getCompatElement(element){ + var doc = element.getDocument(); + return (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body; +} + +})(); + +//aliases +Element.alias({position: 'setPosition'}); //compatability + +[Window, Document, Element].invoke('implement', { + + getHeight: function(){ + return this.getSize().y; + }, + + getWidth: function(){ + return this.getSize().x; + }, + + getScrollTop: function(){ + return this.getScroll().y; + }, + + getScrollLeft: function(){ + return this.getScroll().x; + }, + + getScrollHeight: function(){ + return this.getScrollSize().y; + }, + + getScrollWidth: function(){ + return this.getScrollSize().x; + }, + + getTop: function(){ + return this.getPosition().y; + }, + + getLeft: function(){ + return this.getPosition().x; + } + +}); + +/* +--- + +name: Fx + +description: Contains the basic animation logic to be extended by all other Fx Classes. + +license: MIT-style license. + +requires: [Chain, Events, Options] + +provides: Fx + +... +*/ + +(function(){ + +var Fx = this.Fx = new Class({ + + Implements: [Chain, Events, Options], + + options: { + /* + onStart: nil, + onCancel: nil, + onComplete: nil, + */ + fps: 60, + unit: false, + duration: 500, + frames: null, + frameSkip: true, + link: 'ignore' + }, + + initialize: function(options){ + this.subject = this.subject || this; + this.setOptions(options); + }, + + getTransition: function(){ + return function(p){ + return -(Math.cos(Math.PI * p) - 1) / 2; + }; + }, + + step: function(now){ + if (this.options.frameSkip){ + var diff = (this.time != null) ? (now - this.time) : 0, frames = diff / this.frameInterval; + this.time = now; + this.frame += frames; + } else { + this.frame++; + } + + if (this.frame < this.frames){ + var delta = this.transition(this.frame / this.frames); + this.set(this.compute(this.from, this.to, delta)); + } else { + this.frame = this.frames; + this.set(this.compute(this.from, this.to, 1)); + this.stop(); + } + }, + + set: function(now){ + return now; + }, + + compute: function(from, to, delta){ + return Fx.compute(from, to, delta); + }, + + check: function(){ + if (!this.isRunning()) return true; + switch (this.options.link){ + case 'cancel': this.cancel(); return true; + case 'chain': this.chain(this.caller.pass(arguments, this)); return false; + } + return false; + }, + + start: function(from, to){ + if (!this.check(from, to)) return this; + this.from = from; + this.to = to; + this.frame = (this.options.frameSkip) ? 0 : -1; + this.time = null; + this.transition = this.getTransition(); + var frames = this.options.frames, fps = this.options.fps, duration = this.options.duration; + this.duration = Fx.Durations[duration] || duration.toInt(); + this.frameInterval = 1000 / fps; + this.frames = frames || Math.round(this.duration / this.frameInterval); + this.fireEvent('start', this.subject); + pushInstance.call(this, fps); + return this; + }, + + stop: function(){ + if (this.isRunning()){ + this.time = null; + pullInstance.call(this, this.options.fps); + if (this.frames == this.frame){ + this.fireEvent('complete', this.subject); + if (!this.callChain()) this.fireEvent('chainComplete', this.subject); + } else { + this.fireEvent('stop', this.subject); + } + } + return this; + }, + + cancel: function(){ + if (this.isRunning()){ + this.time = null; + pullInstance.call(this, this.options.fps); + this.frame = this.frames; + this.fireEvent('cancel', this.subject).clearChain(); + } + return this; + }, + + pause: function(){ + if (this.isRunning()){ + this.time = null; + pullInstance.call(this, this.options.fps); + } + return this; + }, + + resume: function(){ + if (this.isPaused()) pushInstance.call(this, this.options.fps); + return this; + }, + + isRunning: function(){ + var list = instances[this.options.fps]; + return list && list.contains(this); + }, + + isPaused: function(){ + return (this.frame < this.frames) && !this.isRunning(); + } + +}); + +Fx.compute = function(from, to, delta){ + return (to - from) * delta + from; +}; + +Fx.Durations = {'short': 250, 'normal': 500, 'long': 1000}; + +// global timers + +var instances = {}, timers = {}; + +var loop = function(){ + var now = Date.now(); + for (var i = this.length; i--;){ + var instance = this[i]; + if (instance) instance.step(now); + } +}; + +var pushInstance = function(fps){ + var list = instances[fps] || (instances[fps] = []); + list.push(this); + if (!timers[fps]) timers[fps] = loop.periodical(Math.round(1000 / fps), list); +}; + +var pullInstance = function(fps){ + var list = instances[fps]; + if (list){ + list.erase(this); + if (!list.length && timers[fps]){ + delete instances[fps]; + timers[fps] = clearInterval(timers[fps]); + } + } +}; + +})(); + +/* +--- + +name: Fx.CSS + +description: Contains the CSS animation logic. Used by Fx.Tween, Fx.Morph, Fx.Elements. + +license: MIT-style license. + +requires: [Fx, Element.Style] + +provides: Fx.CSS + +... +*/ + +Fx.CSS = new Class({ + + Extends: Fx, + + //prepares the base from/to object + + prepare: function(element, property, values){ + values = Array.from(values); + var from = values[0], to = values[1]; + if (to == null){ + to = from; + from = element.getStyle(property); + var unit = this.options.unit; + // adapted from: https://github.com/ryanmorr/fx/blob/master/fx.js#L299 + if (unit && from && typeof from == 'string' && from.slice(-unit.length) != unit && parseFloat(from) != 0){ + element.setStyle(property, to + unit); + var value = element.getComputedStyle(property); + // IE and Opera support pixelLeft or pixelWidth + if (!(/px$/.test(value))){ + value = element.style[('pixel-' + property).camelCase()]; + if (value == null){ + // adapted from Dean Edwards' http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 + var left = element.style.left; + element.style.left = to + unit; + value = element.style.pixelLeft; + element.style.left = left; + } + } + from = (to || 1) / (parseFloat(value) || 1) * (parseFloat(from) || 0); + element.setStyle(property, from + unit); + } + } + return {from: this.parse(from), to: this.parse(to)}; + }, + + //parses a value into an array + + parse: function(value){ + value = Function.from(value)(); + value = (typeof value == 'string') ? value.split(' ') : Array.from(value); + return value.map(function(val){ + val = String(val); + var found = false; + Object.each(Fx.CSS.Parsers, function(parser, key){ + if (found) return; + var parsed = parser.parse(val); + if (parsed || parsed === 0) found = {value: parsed, parser: parser}; + }); + found = found || {value: val, parser: Fx.CSS.Parsers.String}; + return found; + }); + }, + + //computes by a from and to prepared objects, using their parsers. + + compute: function(from, to, delta){ + var computed = []; + (Math.min(from.length, to.length)).times(function(i){ + computed.push({value: from[i].parser.compute(from[i].value, to[i].value, delta), parser: from[i].parser}); + }); + computed.$family = Function.from('fx:css:value'); + return computed; + }, + + //serves the value as settable + + serve: function(value, unit){ + if (typeOf(value) != 'fx:css:value') value = this.parse(value); + var returned = []; + value.each(function(bit){ + returned = returned.concat(bit.parser.serve(bit.value, unit)); + }); + return returned; + }, + + //renders the change to an element + + render: function(element, property, value, unit){ + element.setStyle(property, this.serve(value, unit)); + }, + + //searches inside the page css to find the values for a selector + + search: function(selector){ + if (Fx.CSS.Cache[selector]) return Fx.CSS.Cache[selector]; + var to = {}, selectorTest = new RegExp('^' + selector.escapeRegExp() + '$'); + + var searchStyles = function(rules){ + Array.each(rules, function(rule, i){ + if (rule.media){ + searchStyles(rule.rules || rule.cssRules); + return; + } + if (!rule.style) return; + var selectorText = (rule.selectorText) ? rule.selectorText.replace(/^\w+/, function(m){ + return m.toLowerCase(); + }) : null; + if (!selectorText || !selectorTest.test(selectorText)) return; + Object.each(Element.Styles, function(value, style){ + if (!rule.style[style] || Element.ShortStyles[style]) return; + value = String(rule.style[style]); + to[style] = ((/^rgb/).test(value)) ? value.rgbToHex() : value; + }); + }); + }; + + Array.each(document.styleSheets, function(sheet, j){ + var href = sheet.href; + if (href && href.indexOf('://') > -1 && href.indexOf(document.domain) == -1) return; + var rules = sheet.rules || sheet.cssRules; + searchStyles(rules); + }); + return Fx.CSS.Cache[selector] = to; + } + +}); + +Fx.CSS.Cache = {}; + +Fx.CSS.Parsers = { + + Color: { + parse: function(value){ + if (value.match(/^#[0-9a-f]{3,6}$/i)) return value.hexToRgb(true); + return ((value = value.match(/(\d+),\s*(\d+),\s*(\d+)/))) ? [value[1], value[2], value[3]] : false; + }, + compute: function(from, to, delta){ + return from.map(function(value, i){ + return Math.round(Fx.compute(from[i], to[i], delta)); + }); + }, + serve: function(value){ + return value.map(Number); + } + }, + + Number: { + parse: parseFloat, + compute: Fx.compute, + serve: function(value, unit){ + return (unit) ? value + unit : value; + } + }, + + String: { + parse: Function.from(false), + compute: function(zero, one){ + return one; + }, + serve: function(zero){ + return zero; + } + } + +}; + + + +/* +--- + +name: Fx.Morph + +description: Formerly Fx.Styles, effect to transition any number of CSS properties for an element using an object of rules, or CSS based selector rules. + +license: MIT-style license. + +requires: Fx.CSS + +provides: Fx.Morph + +... +*/ + +Fx.Morph = new Class({ + + Extends: Fx.CSS, + + initialize: function(element, options){ + this.element = this.subject = document.id(element); + this.parent(options); + }, + + set: function(now){ + if (typeof now == 'string') now = this.search(now); + for (var p in now) this.render(this.element, p, now[p], this.options.unit); + return this; + }, + + compute: function(from, to, delta){ + var now = {}; + for (var p in from) now[p] = this.parent(from[p], to[p], delta); + return now; + }, + + start: function(properties){ + if (!this.check(properties)) return this; + if (typeof properties == 'string') properties = this.search(properties); + var from = {}, to = {}; + for (var p in properties){ + var parsed = this.prepare(this.element, p, properties[p]); + from[p] = parsed.from; + to[p] = parsed.to; + } + return this.parent(from, to); + } + +}); + +Element.Properties.morph = { + + set: function(options){ + this.get('morph').cancel().setOptions(options); + return this; + }, + + get: function(){ + var morph = this.retrieve('morph'); + if (!morph){ + morph = new Fx.Morph(this, {link: 'cancel'}); + this.store('morph', morph); + } + return morph; + } + +}; + +Element.implement({ + + morph: function(props){ + this.get('morph').start(props); + return this; + } + +}); + +/* +--- + +name: Fx.Transitions + +description: Contains a set of advanced transitions to be used with any of the Fx Classes. + +license: MIT-style license. + +credits: + - Easing Equations by Robert Penner, <http://www.robertpenner.com/easing/>, modified and optimized to be used with MooTools. + +requires: Fx + +provides: Fx.Transitions + +... +*/ + +Fx.implement({ + + getTransition: function(){ + var trans = this.options.transition || Fx.Transitions.Sine.easeInOut; + if (typeof trans == 'string'){ + var data = trans.split(':'); + trans = Fx.Transitions; + trans = trans[data[0]] || trans[data[0].capitalize()]; + if (data[1]) trans = trans['ease' + data[1].capitalize() + (data[2] ? data[2].capitalize() : '')]; + } + return trans; + } + +}); + +Fx.Transition = function(transition, params){ + params = Array.from(params); + var easeIn = function(pos){ + return transition(pos, params); + }; + return Object.append(easeIn, { + easeIn: easeIn, + easeOut: function(pos){ + return 1 - transition(1 - pos, params); + }, + easeInOut: function(pos){ + return (pos <= 0.5 ? transition(2 * pos, params) : (2 - transition(2 * (1 - pos), params))) / 2; + } + }); +}; + +Fx.Transitions = { + + linear: function(zero){ + return zero; + } + +}; + + + +Fx.Transitions.extend = function(transitions){ + for (var transition in transitions) Fx.Transitions[transition] = new Fx.Transition(transitions[transition]); +}; + +Fx.Transitions.extend({ + + Pow: function(p, x){ + return Math.pow(p, x && x[0] || 6); + }, + + Expo: function(p){ + return Math.pow(2, 8 * (p - 1)); + }, + + Circ: function(p){ + return 1 - Math.sin(Math.acos(p)); + }, + + Sine: function(p){ + return 1 - Math.cos(p * Math.PI / 2); + }, + + Back: function(p, x){ + x = x && x[0] || 1.618; + return Math.pow(p, 2) * ((x + 1) * p - x); + }, + + Bounce: function(p){ + var value; + for (var a = 0, b = 1; 1; a += b, b /= 2){ + if (p >= (7 - 4 * a) / 11){ + value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2); + break; + } + } + return value; + }, + + Elastic: function(p, x){ + return Math.pow(2, 10 * --p) * Math.cos(20 * p * Math.PI * (x && x[0] || 1) / 3); + } + +}); + +['Quad', 'Cubic', 'Quart', 'Quint'].each(function(transition, i){ + Fx.Transitions[transition] = new Fx.Transition(function(p){ + return Math.pow(p, i + 2); + }); +}); + +/* +--- + +name: Fx.Tween + +description: Formerly Fx.Style, effect to transition any CSS property for an element. + +license: MIT-style license. + +requires: Fx.CSS + +provides: [Fx.Tween, Element.fade, Element.highlight] + +... +*/ + +Fx.Tween = new Class({ + + Extends: Fx.CSS, + + initialize: function(element, options){ + this.element = this.subject = document.id(element); + this.parent(options); + }, + + set: function(property, now){ + if (arguments.length == 1){ + now = property; + property = this.property || this.options.property; + } + this.render(this.element, property, now, this.options.unit); + return this; + }, + + start: function(property, from, to){ + if (!this.check(property, from, to)) return this; + var args = Array.flatten(arguments); + this.property = this.options.property || args.shift(); + var parsed = this.prepare(this.element, this.property, args); + return this.parent(parsed.from, parsed.to); + } + +}); + +Element.Properties.tween = { + + set: function(options){ + this.get('tween').cancel().setOptions(options); + return this; + }, + + get: function(){ + var tween = this.retrieve('tween'); + if (!tween){ + tween = new Fx.Tween(this, {link: 'cancel'}); + this.store('tween', tween); + } + return tween; + } + +}; + +Element.implement({ + + tween: function(property, from, to){ + this.get('tween').start(property, from, to); + return this; + }, + + fade: function(how){ + var fade = this.get('tween'), method, args = ['opacity'].append(arguments), toggle; + if (args[1] == null) args[1] = 'toggle'; + switch (args[1]){ + case 'in': method = 'start'; args[1] = 1; break; + case 'out': method = 'start'; args[1] = 0; break; + case 'show': method = 'set'; args[1] = 1; break; + case 'hide': method = 'set'; args[1] = 0; break; + case 'toggle': + var flag = this.retrieve('fade:flag', this.getStyle('opacity') == 1); + method = 'start'; + args[1] = flag ? 0 : 1; + this.store('fade:flag', !flag); + toggle = true; + break; + default: method = 'start'; + } + if (!toggle) this.eliminate('fade:flag'); + fade[method].apply(fade, args); + var to = args[args.length - 1]; + if (method == 'set' || to != 0) this.setStyle('visibility', to == 0 ? 'hidden' : 'visible'); + else fade.chain(function(){ + this.element.setStyle('visibility', 'hidden'); + this.callChain(); + }); + return this; + }, + + highlight: function(start, end){ + if (!end){ + end = this.retrieve('highlight:original', this.getStyle('background-color')); + end = (end == 'transparent') ? '#fff' : end; + } + var tween = this.get('tween'); + tween.start('background-color', start || '#ffff88', end).chain(function(){ + this.setStyle('background-color', this.retrieve('highlight:original')); + tween.callChain(); + }.bind(this)); + return this; + } + +}); + +/* +--- + +name: Request + +description: Powerful all purpose Request Class. Uses XMLHTTPRequest. + +license: MIT-style license. + +requires: [Object, Element, Chain, Events, Options, Browser] + +provides: Request + +... +*/ + +(function(){ + +var empty = function(){}, + progressSupport = ('onprogress' in new Browser.Request); + +var Request = this.Request = new Class({ + + Implements: [Chain, Events, Options], + + options: {/* + onRequest: function(){}, + onLoadstart: function(event, xhr){}, + onProgress: function(event, xhr){}, + onComplete: function(){}, + onCancel: function(){}, + onSuccess: function(responseText, responseXML){}, + onFailure: function(xhr){}, + onException: function(headerName, value){}, + onTimeout: function(){}, + user: '', + password: '', + withCredentials: false,*/ + url: '', + data: '', + headers: { + 'X-Requested-With': 'XMLHttpRequest', + 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' + }, + async: true, + format: false, + method: 'post', + link: 'ignore', + isSuccess: null, + emulation: true, + urlEncoded: true, + encoding: 'utf-8', + evalScripts: false, + evalResponse: false, + timeout: 0, + noCache: false + }, + + initialize: function(options){ + this.xhr = new Browser.Request(); + this.setOptions(options); + this.headers = this.options.headers; + }, + + onStateChange: function(){ + var xhr = this.xhr; + if (xhr.readyState != 4 || !this.running) return; + this.running = false; + this.status = 0; + Function.attempt(function(){ + var status = xhr.status; + this.status = (status == 1223) ? 204 : status; + }.bind(this)); + xhr.onreadystatechange = empty; + if (progressSupport) xhr.onprogress = xhr.onloadstart = empty; + if (this.timer){ + clearTimeout(this.timer); + delete this.timer; + } + + this.response = {text: this.xhr.responseText || '', xml: this.xhr.responseXML}; + if (this.options.isSuccess.call(this, this.status)) + this.success(this.response.text, this.response.xml); + else + this.failure(); + }, + + isSuccess: function(){ + var status = this.status; + return (status >= 200 && status < 300); + }, + + isRunning: function(){ + return !!this.running; + }, + + processScripts: function(text){ + if (this.options.evalResponse || (/(ecma|java)script/).test(this.getHeader('Content-type'))) return Browser.exec(text); + return text.stripScripts(this.options.evalScripts); + }, + + success: function(text, xml){ + this.onSuccess(this.processScripts(text), xml); + }, + + onSuccess: function(){ + this.fireEvent('complete', arguments).fireEvent('success', arguments).callChain(); + }, + + failure: function(){ + this.onFailure(); + }, + + onFailure: function(){ + this.fireEvent('complete').fireEvent('failure', this.xhr); + }, + + loadstart: function(event){ + this.fireEvent('loadstart', [event, this.xhr]); + }, + + progress: function(event){ + this.fireEvent('progress', [event, this.xhr]); + }, + + timeout: function(){ + this.fireEvent('timeout', this.xhr); + }, + + setHeader: function(name, value){ + this.headers[name] = value; + return this; + }, + + getHeader: function(name){ + return Function.attempt(function(){ + return this.xhr.getResponseHeader(name); + }.bind(this)); + }, + + check: function(){ + if (!this.running) return true; + switch (this.options.link){ + case 'cancel': this.cancel(); return true; + case 'chain': this.chain(this.caller.pass(arguments, this)); return false; + } + return false; + }, + + send: function(options){ + if (!this.check(options)) return this; + + this.options.isSuccess = this.options.isSuccess || this.isSuccess; + this.running = true; + + var type = typeOf(options); + if (type == 'string' || type == 'element') options = {data: options}; + + var old = this.options; + options = Object.append({data: old.data, url: old.url, method: old.method}, options); + var data = options.data, url = String(options.url), method = options.method.toLowerCase(); + + switch (typeOf(data)){ + case 'element': data = document.id(data).toQueryString(); break; + case 'object': case 'hash': data = Object.toQueryString(data); + } + + if (this.options.format){ + var format = 'format=' + this.options.format; + data = (data) ? format + '&' + data : format; + } + + if (this.options.emulation && !['get', 'post'].contains(method)){ + var _method = '_method=' + method; + data = (data) ? _method + '&' + data : _method; + method = 'post'; + } + + if (this.options.urlEncoded && ['post', 'put'].contains(method)){ + var encoding = (this.options.encoding) ? '; charset=' + this.options.encoding : ''; + this.headers['Content-type'] = 'application/x-www-form-urlencoded' + encoding; + } + + if (!url) url = document.location.pathname; + + var trimPosition = url.lastIndexOf('/'); + if (trimPosition > -1 && (trimPosition = url.indexOf('#')) > -1) url = url.substr(0, trimPosition); + + if (this.options.noCache) + url += (url.indexOf('?') > -1 ? '&' : '?') + String.uniqueID(); + + if (data && (method == 'get' || method == 'delete')){ + url += (url.indexOf('?') > -1 ? '&' : '?') + data; + data = null; + } + + var xhr = this.xhr; + if (progressSupport){ + xhr.onloadstart = this.loadstart.bind(this); + xhr.onprogress = this.progress.bind(this); + } + + xhr.open(method.toUpperCase(), url, this.options.async, this.options.user, this.options.password); + if ((this.options.withCredentials) && 'withCredentials' in xhr) xhr.withCredentials = true; + + xhr.onreadystatechange = this.onStateChange.bind(this); + + Object.each(this.headers, function(value, key){ + try { + xhr.setRequestHeader(key, value); + } catch (e){ + this.fireEvent('exception', [key, value]); + } + }, this); + + this.fireEvent('request'); + xhr.send(data); + if (!this.options.async) this.onStateChange(); + else if (this.options.timeout) this.timer = this.timeout.delay(this.options.timeout, this); + return this; + }, + + cancel: function(){ + if (!this.running) return this; + this.running = false; + var xhr = this.xhr; + xhr.abort(); + if (this.timer){ + clearTimeout(this.timer); + delete this.timer; + } + xhr.onreadystatechange = empty; + if (progressSupport) xhr.onprogress = xhr.onloadstart = empty; + this.xhr = new Browser.Request(); + this.fireEvent('cancel'); + return this; + } + +}); + +var methods = {}; +['get', 'post', 'put', 'delete', 'patch', 'head', 'GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD'].each(function(method){ + methods[method] = function(data){ + var object = { + method: method + }; + if (data != null) object.data = data; + return this.send(object); + }; +}); + +Request.implement(methods); + +Element.Properties.send = { + + set: function(options){ + var send = this.get('send').cancel(); + send.setOptions(options); + return this; + }, + + get: function(){ + var send = this.retrieve('send'); + if (!send){ + send = new Request({ + data: this, link: 'cancel', method: this.get('method') || 'post', url: this.get('action') + }); + this.store('send', send); + } + return send; + } + +}; + +Element.implement({ + + send: function(url){ + var sender = this.get('send'); + sender.send({data: this, url: url || sender.options.url}); + return this; + } + +}); + +})(); + +/* +--- + +name: Request.HTML + +description: Extends the basic Request Class with additional methods for interacting with HTML responses. + +license: MIT-style license. + +requires: [Element, Request] + +provides: Request.HTML + +... +*/ + +Request.HTML = new Class({ + + Extends: Request, + + options: { + update: false, + append: false, + evalScripts: true, + filter: false, + headers: { + Accept: 'text/html, application/xml, text/xml, */*' + } + }, + + success: function(text){ + var options = this.options, response = this.response; + + response.html = text.stripScripts(function(script){ + response.javascript = script; + }); + + var match = response.html.match(/<body[^>]*>([\s\S]*?)<\/body>/i); + if (match) response.html = match[1]; + var temp = new Element('div').set('html', response.html); + + response.tree = temp.childNodes; + response.elements = temp.getElements(options.filter || '*'); + + if (options.filter) response.tree = response.elements; + if (options.update){ + var update = document.id(options.update).empty(); + if (options.filter) update.adopt(response.elements); + else update.set('html', response.html); + } else if (options.append){ + var append = document.id(options.append); + if (options.filter) response.elements.reverse().inject(append); + else append.adopt(temp.getChildren()); + } + if (options.evalScripts) Browser.exec(response.javascript); + + this.onSuccess(response.tree, response.elements, response.html, response.javascript); + } + +}); + +Element.Properties.load = { + + set: function(options){ + var load = this.get('load').cancel(); + load.setOptions(options); + return this; + }, + + get: function(){ + var load = this.retrieve('load'); + if (!load){ + load = new Request.HTML({data: this, link: 'cancel', update: this, method: 'get'}); + this.store('load', load); + } + return load; + } + +}; + +Element.implement({ + + load: function(){ + this.get('load').send(Array.link(arguments, {data: Type.isObject, url: Type.isString})); + return this; + } + +}); + +/* +--- + +name: JSON + +description: JSON encoder and decoder. + +license: MIT-style license. + +SeeAlso: <http://www.json.org/> + +requires: [Array, String, Number, Function] + +provides: JSON + +... +*/ + +if (typeof JSON == 'undefined') this.JSON = {}; + + + +(function(){ + +var special = {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\'}; + +var escape = function(chr){ + return special[chr] || '\\u' + ('0000' + chr.charCodeAt(0).toString(16)).slice(-4); +}; + +JSON.validate = function(string){ + string = string.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'). + replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'). + replace(/(?:^|:|,)(?:\s*\[)+/g, ''); + + return (/^[\],:{}\s]*$/).test(string); +}; + +JSON.encode = JSON.stringify ? function(obj){ + return JSON.stringify(obj); +} : function(obj){ + if (obj && obj.toJSON) obj = obj.toJSON(); + + switch (typeOf(obj)){ + case 'string': + return '"' + obj.replace(/[\x00-\x1f\\"]/g, escape) + '"'; + case 'array': + return '[' + obj.map(JSON.encode).clean() + ']'; + case 'object': case 'hash': + var string = []; + Object.each(obj, function(value, key){ + var json = JSON.encode(value); + if (json) string.push(JSON.encode(key) + ':' + json); + }); + return '{' + string + '}'; + case 'number': case 'boolean': return '' + obj; + case 'null': return 'null'; + } + + return null; +}; + +JSON.secure = true; + + +JSON.decode = function(string, secure){ + if (!string || typeOf(string) != 'string') return null; + + if (secure == null) secure = JSON.secure; + if (secure){ + if (JSON.parse) return JSON.parse(string); + if (!JSON.validate(string)) throw new Error('JSON could not decode the input; security is enabled and the value is not secure.'); + } + + return eval('(' + string + ')'); +}; + +})(); + +/* +--- + +name: Request.JSON + +description: Extends the basic Request Class with additional methods for sending and receiving JSON data. + +license: MIT-style license. + +requires: [Request, JSON] + +provides: Request.JSON + +... +*/ + +Request.JSON = new Class({ + + Extends: Request, + + options: { + /*onError: function(text, error){},*/ + secure: true + }, + + initialize: function(options){ + this.parent(options); + Object.append(this.headers, { + 'Accept': 'application/json', + 'X-Request': 'JSON' + }); + }, + + success: function(text){ + var json; + try { + json = this.response.json = JSON.decode(text, this.options.secure); + } catch (error){ + this.fireEvent('error', [text, error]); + return; + } + if (json == null) this.onFailure(); + else this.onSuccess(json, text); + } + +}); + +/* +--- + +name: Cookie + +description: Class for creating, reading, and deleting browser Cookies. + +license: MIT-style license. + +credits: + - Based on the functions by Peter-Paul Koch (http://quirksmode.org). + +requires: [Options, Browser] + +provides: Cookie + +... +*/ + +var Cookie = new Class({ + + Implements: Options, + + options: { + path: '/', + domain: false, + duration: false, + secure: false, + document: document, + encode: true + }, + + initialize: function(key, options){ + this.key = key; + this.setOptions(options); + }, + + write: function(value){ + if (this.options.encode) value = encodeURIComponent(value); + if (this.options.domain) value += '; domain=' + this.options.domain; + if (this.options.path) value += '; path=' + this.options.path; + if (this.options.duration){ + var date = new Date(); + date.setTime(date.getTime() + this.options.duration * 24 * 60 * 60 * 1000); + value += '; expires=' + date.toGMTString(); + } + if (this.options.secure) value += '; secure'; + this.options.document.cookie = this.key + '=' + value; + return this; + }, + + read: function(){ + var value = this.options.document.cookie.match('(?:^|;)\\s*' + this.key.escapeRegExp() + '=([^;]*)'); + return (value) ? decodeURIComponent(value[1]) : null; + }, + + dispose: function(){ + new Cookie(this.key, Object.merge({}, this.options, {duration: -1})).write(''); + return this; + } + +}); + +Cookie.write = function(key, value, options){ + return new Cookie(key, options).write(value); +}; + +Cookie.read = function(key){ + return new Cookie(key).read(); +}; + +Cookie.dispose = function(key, options){ + return new Cookie(key, options).dispose(); +}; + +/* +--- + +name: DOMReady + +description: Contains the custom event domready. + +license: MIT-style license. + +requires: [Browser, Element, Element.Event] + +provides: [DOMReady, DomReady] + +... +*/ + +(function(window, document){ + +var ready, + loaded, + checks = [], + shouldPoll, + timer, + testElement = document.createElement('div'); + +var domready = function(){ + clearTimeout(timer); + if (!ready) { + Browser.loaded = ready = true; + document.removeListener('DOMContentLoaded', domready).removeListener('readystatechange', check); + document.fireEvent('domready'); + window.fireEvent('domready'); + } + // cleanup scope vars + document = window = testElement = null; +}; + +var check = function(){ + for (var i = checks.length; i--;) if (checks[i]()){ + domready(); + return true; + } + return false; +}; + +var poll = function(){ + clearTimeout(timer); + if (!check()) timer = setTimeout(poll, 10); +}; + +document.addListener('DOMContentLoaded', domready); + +/*<ltIE8>*/ +// doScroll technique by Diego Perini http://javascript.nwbox.com/IEContentLoaded/ +// testElement.doScroll() throws when the DOM is not ready, only in the top window +var doScrollWorks = function(){ + try { + testElement.doScroll(); + return true; + } catch (e){} + return false; +}; +// If doScroll works already, it can't be used to determine domready +// e.g. in an iframe +if (testElement.doScroll && !doScrollWorks()){ + checks.push(doScrollWorks); + shouldPoll = true; +} +/*</ltIE8>*/ + +if (document.readyState) checks.push(function(){ + var state = document.readyState; + return (state == 'loaded' || state == 'complete'); +}); + +if ('onreadystatechange' in document) document.addListener('readystatechange', check); +else shouldPoll = true; + +if (shouldPoll) poll(); + +Element.Events.domready = { + onAdd: function(fn){ + if (ready) fn.call(this); + } +}; + +// Make sure that domready fires before load +Element.Events.load = { + base: 'load', + onAdd: function(fn){ + if (loaded && this == window) fn.call(this); + }, + condition: function(){ + if (this == window){ + domready(); + delete Element.Events.load; + } + return true; + } +}; + +// This is based on the custom load event +window.addEvent('load', function(){ + loaded = true; +}); + +})(window, document); diff --git a/pyload/webui/themes/Dark/lib/MooTools/MooTools-More.js b/pyload/webui/themes/Dark/lib/MooTools/MooTools-More.js new file mode 100644 index 000000000..15a277029 --- /dev/null +++ b/pyload/webui/themes/Dark/lib/MooTools/MooTools-More.js @@ -0,0 +1,14345 @@ +/* MooTools: the javascript framework. license: MIT-style license. copyright: Copyright (c) 2006-2015 [Valerio Proietti](http://mad4milk.net/).*/ +/* +Web Build: http://mootools.net/more/builder/a3048f4bfdf603b22a69c141dbd0fca9 +*/ +/* +--- + +script: More.js + +name: More + +description: MooTools More + +license: MIT-style license + +authors: + - Guillermo Rauch + - Thomas Aylott + - Scott Kyle + - Arian Stolwijk + - Tim Wienk + - Christoph Pojer + - Aaron Newton + - Jacob Thornton + +requires: + - Core/MooTools + +provides: [MooTools.More] + +... +*/ + +MooTools.More = { + version: '1.5.1', + build: '2dd695ba957196ae4b0275a690765d6636a61ccd' +}; + +/* +--- + +script: Chain.Wait.js + +name: Chain.Wait + +description: value, Adds a method to inject pauses between chained events. + +license: MIT-style license. + +authors: + - Aaron Newton + +requires: + - Core/Chain + - Core/Element + - Core/Fx + - MooTools.More + +provides: [Chain.Wait] + +... +*/ + +(function(){ + + var wait = { + wait: function(duration){ + return this.chain(function(){ + this.callChain.delay(duration == null ? 500 : duration, this); + return this; + }.bind(this)); + } + }; + + Chain.implement(wait); + + if (this.Fx) Fx.implement(wait); + + if (this.Element && Element.implement && this.Fx){ + Element.implement({ + + chains: function(effects){ + Array.from(effects || ['tween', 'morph', 'reveal']).each(function(effect){ + effect = this.get(effect); + if (!effect) return; + effect.setOptions({ + link:'chain' + }); + }, this); + return this; + }, + + pauseFx: function(duration, effect){ + this.chains(effect).get(effect || 'tween').wait(duration); + return this; + } + + }); + } + +})(); + +/* +--- + +script: Class.Binds.js + +name: Class.Binds + +description: Automagically binds specified methods in a class to the instance of the class. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Class + - MooTools.More + +provides: [Class.Binds] + +... +*/ + +Class.Mutators.Binds = function(binds){ + if (!this.prototype.initialize) this.implement('initialize', function(){}); + return Array.from(binds).concat(this.prototype.Binds || []); +}; + +Class.Mutators.initialize = function(initialize){ + return function(){ + Array.from(this.Binds).each(function(name){ + var original = this[name]; + if (original) this[name] = original.bind(this); + }, this); + return initialize.apply(this, arguments); + }; +}; + +/* +--- + +script: Class.Occlude.js + +name: Class.Occlude + +description: Prevents a class from being applied to a DOM element twice. + +license: MIT-style license. + +authors: + - Aaron Newton + +requires: + - Core/Class + - Core/Element + - MooTools.More + +provides: [Class.Occlude] + +... +*/ + +Class.Occlude = new Class({ + + occlude: function(property, element){ + element = document.id(element || this.element); + var instance = element.retrieve(property || this.property); + if (instance && !this.occluded) + return (this.occluded = instance); + + this.occluded = false; + element.store(property || this.property, this); + return this.occluded; + } + +}); + +/* +--- + +script: Class.Refactor.js + +name: Class.Refactor + +description: Extends a class onto itself with new property, preserving any items attached to the class's namespace. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Class + - MooTools.More + +# Some modules declare themselves dependent on Class.Refactor +provides: [Class.refactor, Class.Refactor] + +... +*/ + +Class.refactor = function(original, refactors){ + + Object.each(refactors, function(item, name){ + var origin = original.prototype[name]; + origin = (origin && origin.$origin) || origin || function(){}; + original.implement(name, (typeof item == 'function') ? function(){ + var old = this.previous; + this.previous = origin; + var value = item.apply(this, arguments); + this.previous = old; + return value; + } : item); + }); + + return original; + +}; + +/* +--- + +name: Events.Pseudos + +description: Adds the functionality to add pseudo events + +license: MIT-style license + +authors: + - Arian Stolwijk + +requires: [Core/Class.Extras, Core/Slick.Parser, MooTools.More] + +provides: [Events.Pseudos] + +... +*/ + +(function(){ + +Events.Pseudos = function(pseudos, addEvent, removeEvent){ + + var storeKey = '_monitorEvents:'; + + var storageOf = function(object){ + return { + store: object.store ? function(key, value){ + object.store(storeKey + key, value); + } : function(key, value){ + (object._monitorEvents || (object._monitorEvents = {}))[key] = value; + }, + retrieve: object.retrieve ? function(key, dflt){ + return object.retrieve(storeKey + key, dflt); + } : function(key, dflt){ + if (!object._monitorEvents) return dflt; + return object._monitorEvents[key] || dflt; + } + }; + }; + + var splitType = function(type){ + if (type.indexOf(':') == -1 || !pseudos) return null; + + var parsed = Slick.parse(type).expressions[0][0], + parsedPseudos = parsed.pseudos, + l = parsedPseudos.length, + splits = []; + + while (l--){ + var pseudo = parsedPseudos[l].key, + listener = pseudos[pseudo]; + if (listener != null) splits.push({ + event: parsed.tag, + value: parsedPseudos[l].value, + pseudo: pseudo, + original: type, + listener: listener + }); + } + return splits.length ? splits : null; + }; + + return { + + addEvent: function(type, fn, internal){ + var split = splitType(type); + if (!split) return addEvent.call(this, type, fn, internal); + + var storage = storageOf(this), + events = storage.retrieve(type, []), + eventType = split[0].event, + args = Array.slice(arguments, 2), + stack = fn, + self = this; + + split.each(function(item){ + var listener = item.listener, + stackFn = stack; + if (listener == false) eventType += ':' + item.pseudo + '(' + item.value + ')'; + else stack = function(){ + listener.call(self, item, stackFn, arguments, stack); + }; + }); + + events.include({type: eventType, event: fn, monitor: stack}); + storage.store(type, events); + + if (type != eventType) addEvent.apply(this, [type, fn].concat(args)); + return addEvent.apply(this, [eventType, stack].concat(args)); + }, + + removeEvent: function(type, fn){ + var split = splitType(type); + if (!split) return removeEvent.call(this, type, fn); + + var storage = storageOf(this), + events = storage.retrieve(type); + if (!events) return this; + + var args = Array.slice(arguments, 2); + + removeEvent.apply(this, [type, fn].concat(args)); + events.each(function(monitor, i){ + if (!fn || monitor.event == fn) removeEvent.apply(this, [monitor.type, monitor.monitor].concat(args)); + delete events[i]; + }, this); + + storage.store(type, events); + return this; + } + + }; + +}; + +var pseudos = { + + once: function(split, fn, args, monitor){ + fn.apply(this, args); + this.removeEvent(split.event, monitor) + .removeEvent(split.original, fn); + }, + + throttle: function(split, fn, args){ + if (!fn._throttled){ + fn.apply(this, args); + fn._throttled = setTimeout(function(){ + fn._throttled = false; + }, split.value || 250); + } + }, + + pause: function(split, fn, args){ + clearTimeout(fn._pause); + fn._pause = fn.delay(split.value || 250, this, args); + } + +}; + +Events.definePseudo = function(key, listener){ + pseudos[key] = listener; + return this; +}; + +Events.lookupPseudo = function(key){ + return pseudos[key]; +}; + +var proto = Events.prototype; +Events.implement(Events.Pseudos(pseudos, proto.addEvent, proto.removeEvent)); + +['Request', 'Fx'].each(function(klass){ + if (this[klass]) this[klass].implement(Events.prototype); +}); + +})(); + +/* +--- + +script: Drag.js + +name: Drag + +description: The base Drag Class. Can be used to drag and resize Elements using mouse events. + +license: MIT-style license + +authors: + - Valerio Proietti + - Tom Occhinno + - Jan Kassens + +requires: + - Core/Events + - Core/Options + - Core/Element.Event + - Core/Element.Style + - Core/Element.Dimensions + - MooTools.More + +provides: [Drag] +... + +*/ + +var Drag = new Class({ + + Implements: [Events, Options], + + options: {/* + onBeforeStart: function(thisElement){}, + onStart: function(thisElement, event){}, + onSnap: function(thisElement){}, + onDrag: function(thisElement, event){}, + onCancel: function(thisElement){}, + onComplete: function(thisElement, event){},*/ + snap: 6, + unit: 'px', + grid: false, + style: true, + limit: false, + handle: false, + invert: false, + preventDefault: false, + stopPropagation: false, + compensateScroll: false, + modifiers: {x: 'left', y: 'top'} + }, + + initialize: function(){ + var params = Array.link(arguments, { + 'options': Type.isObject, + 'element': function(obj){ + return obj != null; + } + }); + + this.element = document.id(params.element); + this.document = this.element.getDocument(); + this.setOptions(params.options || {}); + var htype = typeOf(this.options.handle); + this.handles = ((htype == 'array' || htype == 'collection') ? $$(this.options.handle) : document.id(this.options.handle)) || this.element; + this.mouse = {'now': {}, 'pos': {}}; + this.value = {'start': {}, 'now': {}}; + this.offsetParent = (function(el){ + var offsetParent = el.getOffsetParent(); + var isBody = !offsetParent || (/^(?:body|html)$/i).test(offsetParent.tagName); + return isBody ? window : document.id(offsetParent); + })(this.element); + this.selection = 'selectstart' in document ? 'selectstart' : 'mousedown'; + + this.compensateScroll = {start: {}, diff: {}, last: {}}; + + if ('ondragstart' in document && !('FileReader' in window) && !Drag.ondragstartFixed){ + document.ondragstart = Function.from(false); + Drag.ondragstartFixed = true; + } + + this.bound = { + start: this.start.bind(this), + check: this.check.bind(this), + drag: this.drag.bind(this), + stop: this.stop.bind(this), + cancel: this.cancel.bind(this), + eventStop: Function.from(false), + scrollListener: this.scrollListener.bind(this) + }; + this.attach(); + }, + + attach: function(){ + this.handles.addEvent('mousedown', this.bound.start); + if (this.options.compensateScroll) this.offsetParent.addEvent('scroll', this.bound.scrollListener); + return this; + }, + + detach: function(){ + this.handles.removeEvent('mousedown', this.bound.start); + if (this.options.compensateScroll) this.offsetParent.removeEvent('scroll', this.bound.scrollListener); + return this; + }, + + scrollListener: function(){ + + if (!this.mouse.start) return; + var newScrollValue = this.offsetParent.getScroll(); + + if (this.element.getStyle('position') == 'absolute'){ + var scrollDiff = this.sumValues(newScrollValue, this.compensateScroll.last, -1); + this.mouse.now = this.sumValues(this.mouse.now, scrollDiff, 1); + } else { + this.compensateScroll.diff = this.sumValues(newScrollValue, this.compensateScroll.start, -1); + } + if (this.offsetParent != window) this.compensateScroll.diff = this.sumValues(this.compensateScroll.start, newScrollValue, -1); + this.compensateScroll.last = newScrollValue; + this.render(this.options); + }, + + sumValues: function(alpha, beta, op){ + var sum = {}, options = this.options; + for (z in options.modifiers){ + if (!options.modifiers[z]) continue; + sum[z] = alpha[z] + beta[z] * op; + } + return sum; + }, + + start: function(event){ + var options = this.options; + + if (event.rightClick) return; + + if (options.preventDefault) event.preventDefault(); + if (options.stopPropagation) event.stopPropagation(); + this.compensateScroll.start = this.compensateScroll.last = this.offsetParent.getScroll(); + this.compensateScroll.diff = {x: 0, y: 0}; + this.mouse.start = event.page; + this.fireEvent('beforeStart', this.element); + + var limit = options.limit; + this.limit = {x: [], y: []}; + + var z, coordinates, offsetParent = this.offsetParent == window ? null : this.offsetParent; + for (z in options.modifiers){ + if (!options.modifiers[z]) continue; + + var style = this.element.getStyle(options.modifiers[z]); + + // Some browsers (IE and Opera) don't always return pixels. + if (style && !style.match(/px$/)){ + if (!coordinates) coordinates = this.element.getCoordinates(offsetParent); + style = coordinates[options.modifiers[z]]; + } + + if (options.style) this.value.now[z] = (style || 0).toInt(); + else this.value.now[z] = this.element[options.modifiers[z]]; + + if (options.invert) this.value.now[z] *= -1; + + this.mouse.pos[z] = event.page[z] - this.value.now[z]; + + if (limit && limit[z]){ + var i = 2; + while (i--){ + var limitZI = limit[z][i]; + if (limitZI || limitZI === 0) this.limit[z][i] = (typeof limitZI == 'function') ? limitZI() : limitZI; + } + } + } + + if (typeOf(this.options.grid) == 'number') this.options.grid = { + x: this.options.grid, + y: this.options.grid + }; + + var events = { + mousemove: this.bound.check, + mouseup: this.bound.cancel + }; + events[this.selection] = this.bound.eventStop; + this.document.addEvents(events); + }, + + check: function(event){ + if (this.options.preventDefault) event.preventDefault(); + var distance = Math.round(Math.sqrt(Math.pow(event.page.x - this.mouse.start.x, 2) + Math.pow(event.page.y - this.mouse.start.y, 2))); + if (distance > this.options.snap){ + this.cancel(); + this.document.addEvents({ + mousemove: this.bound.drag, + mouseup: this.bound.stop + }); + this.fireEvent('start', [this.element, event]).fireEvent('snap', this.element); + } + }, + + drag: function(event){ + var options = this.options; + if (options.preventDefault) event.preventDefault(); + this.mouse.now = this.sumValues(event.page, this.compensateScroll.diff, -1); + + this.render(options); + this.fireEvent('drag', [this.element, event]); + }, + + render: function(options){ + for (var z in options.modifiers){ + if (!options.modifiers[z]) continue; + this.value.now[z] = this.mouse.now[z] - this.mouse.pos[z]; + + if (options.invert) this.value.now[z] *= -1; + if (options.limit && this.limit[z]){ + if ((this.limit[z][1] || this.limit[z][1] === 0) && (this.value.now[z] > this.limit[z][1])){ + this.value.now[z] = this.limit[z][1]; + } else if ((this.limit[z][0] || this.limit[z][0] === 0) && (this.value.now[z] < this.limit[z][0])){ + this.value.now[z] = this.limit[z][0]; + } + } + if (options.grid[z]) this.value.now[z] -= ((this.value.now[z] - (this.limit[z][0]||0)) % options.grid[z]); + if (options.style) this.element.setStyle(options.modifiers[z], this.value.now[z] + options.unit); + else this.element[options.modifiers[z]] = this.value.now[z]; + } + }, + + cancel: function(event){ + this.document.removeEvents({ + mousemove: this.bound.check, + mouseup: this.bound.cancel + }); + if (event){ + this.document.removeEvent(this.selection, this.bound.eventStop); + this.fireEvent('cancel', this.element); + } + }, + + stop: function(event){ + var events = { + mousemove: this.bound.drag, + mouseup: this.bound.stop + }; + events[this.selection] = this.bound.eventStop; + this.document.removeEvents(events); + this.mouse.start = null; + if (event) this.fireEvent('complete', [this.element, event]); + } + +}); + +Element.implement({ + + makeResizable: function(options){ + var drag = new Drag(this, Object.merge({ + modifiers: { + x: 'width', + y: 'height' + } + }, options)); + + this.store('resizer', drag); + return drag.addEvent('drag', function(){ + this.fireEvent('resize', drag); + }.bind(this)); + } + +}); + +/* +--- + +script: Drag.Move.js + +name: Drag.Move + +description: A Drag extension that provides support for the constraining of draggables to containers and droppables. + +license: MIT-style license + +authors: + - Valerio Proietti + - Tom Occhinno + - Jan Kassens + - Aaron Newton + - Scott Kyle + +requires: + - Core/Element.Dimensions + - Drag + +provides: [Drag.Move] + +... +*/ + +Drag.Move = new Class({ + + Extends: Drag, + + options: {/* + onEnter: function(thisElement, overed){}, + onLeave: function(thisElement, overed){}, + onDrop: function(thisElement, overed, event){},*/ + droppables: [], + container: false, + precalculate: false, + includeMargins: true, + checkDroppables: true + }, + + initialize: function(element, options){ + this.parent(element, options); + element = this.element; + + this.droppables = $$(this.options.droppables); + this.setContainer(this.options.container); + + if (this.options.style){ + if (this.options.modifiers.x == 'left' && this.options.modifiers.y == 'top'){ + var parent = element.getOffsetParent(), + styles = element.getStyles('left', 'top'); + if (parent && (styles.left == 'auto' || styles.top == 'auto')){ + element.setPosition(element.getPosition(parent)); + } + } + + if (element.getStyle('position') == 'static') element.setStyle('position', 'absolute'); + } + + this.addEvent('start', this.checkDroppables, true); + this.overed = null; + }, + + setContainer: function(container) { + this.container = document.id(container); + if (this.container && typeOf(this.container) != 'element'){ + this.container = document.id(this.container.getDocument().body); + } + }, + + start: function(event){ + if (this.container) this.options.limit = this.calculateLimit(); + + if (this.options.precalculate){ + this.positions = this.droppables.map(function(el){ + return el.getCoordinates(); + }); + } + + this.parent(event); + }, + + calculateLimit: function(){ + var element = this.element, + container = this.container, + + offsetParent = document.id(element.getOffsetParent()) || document.body, + containerCoordinates = container.getCoordinates(offsetParent), + elementMargin = {}, + elementBorder = {}, + containerMargin = {}, + containerBorder = {}, + offsetParentPadding = {}, + offsetScroll = offsetParent.getScroll(); + + ['top', 'right', 'bottom', 'left'].each(function(pad){ + elementMargin[pad] = element.getStyle('margin-' + pad).toInt(); + elementBorder[pad] = element.getStyle('border-' + pad).toInt(); + containerMargin[pad] = container.getStyle('margin-' + pad).toInt(); + containerBorder[pad] = container.getStyle('border-' + pad).toInt(); + offsetParentPadding[pad] = offsetParent.getStyle('padding-' + pad).toInt(); + }, this); + + var width = element.offsetWidth + elementMargin.left + elementMargin.right, + height = element.offsetHeight + elementMargin.top + elementMargin.bottom, + left = 0 + offsetScroll.x, + top = 0 + offsetScroll.y, + right = containerCoordinates.right - containerBorder.right - width + offsetScroll.x, + bottom = containerCoordinates.bottom - containerBorder.bottom - height + offsetScroll.y; + + if (this.options.includeMargins){ + left += elementMargin.left; + top += elementMargin.top; + } else { + right += elementMargin.right; + bottom += elementMargin.bottom; + } + + if (element.getStyle('position') == 'relative'){ + var coords = element.getCoordinates(offsetParent); + coords.left -= element.getStyle('left').toInt(); + coords.top -= element.getStyle('top').toInt(); + + left -= coords.left; + top -= coords.top; + if (container.getStyle('position') != 'relative'){ + left += containerBorder.left; + top += containerBorder.top; + } + right += elementMargin.left - coords.left; + bottom += elementMargin.top - coords.top; + + if (container != offsetParent){ + left += containerMargin.left + offsetParentPadding.left; + if (!offsetParentPadding.left && left < 0) left = 0; + top += offsetParent == document.body ? 0 : containerMargin.top + offsetParentPadding.top; + if (!offsetParentPadding.top && top < 0) top = 0; + } + } else { + left -= elementMargin.left; + top -= elementMargin.top; + if (container != offsetParent){ + left += containerCoordinates.left + containerBorder.left; + top += containerCoordinates.top + containerBorder.top; + } + } + + return { + x: [left, right], + y: [top, bottom] + }; + }, + + getDroppableCoordinates: function(element){ + var position = element.getCoordinates(); + if (element.getStyle('position') == 'fixed'){ + var scroll = window.getScroll(); + position.left += scroll.x; + position.right += scroll.x; + position.top += scroll.y; + position.bottom += scroll.y; + } + return position; + }, + + checkDroppables: function(){ + var overed = this.droppables.filter(function(el, i){ + el = this.positions ? this.positions[i] : this.getDroppableCoordinates(el); + var now = this.mouse.now; + return (now.x > el.left && now.x < el.right && now.y < el.bottom && now.y > el.top); + }, this).getLast(); + + if (this.overed != overed){ + if (this.overed) this.fireEvent('leave', [this.element, this.overed]); + if (overed) this.fireEvent('enter', [this.element, overed]); + this.overed = overed; + } + }, + + drag: function(event){ + this.parent(event); + if (this.options.checkDroppables && this.droppables.length) this.checkDroppables(); + }, + + stop: function(event){ + this.checkDroppables(); + this.fireEvent('drop', [this.element, this.overed, event]); + this.overed = null; + return this.parent(event); + } + +}); + +Element.implement({ + + makeDraggable: function(options){ + var drag = new Drag.Move(this, options); + this.store('dragger', drag); + return drag; + } + +}); + +/* +--- + +script: Element.Measure.js + +name: Element.Measure + +description: Extends the Element native object to include methods useful in measuring dimensions. + +credits: "Element.measure / .expose methods by Daniel Steigerwald License: MIT-style license. Copyright: Copyright (c) 2008 Daniel Steigerwald, daniel.steigerwald.cz" + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Element.Style + - Core/Element.Dimensions + - MooTools.More + +provides: [Element.Measure] + +... +*/ + +(function(){ + +var getStylesList = function(styles, planes){ + var list = []; + Object.each(planes, function(directions){ + Object.each(directions, function(edge){ + styles.each(function(style){ + list.push(style + '-' + edge + (style == 'border' ? '-width' : '')); + }); + }); + }); + return list; +}; + +var calculateEdgeSize = function(edge, styles){ + var total = 0; + Object.each(styles, function(value, style){ + if (style.test(edge)) total = total + value.toInt(); + }); + return total; +}; + +var isVisible = function(el){ + return !!(!el || el.offsetHeight || el.offsetWidth); +}; + + +Element.implement({ + + measure: function(fn){ + if (isVisible(this)) return fn.call(this); + var parent = this.getParent(), + toMeasure = []; + while (!isVisible(parent) && parent != document.body){ + toMeasure.push(parent.expose()); + parent = parent.getParent(); + } + var restore = this.expose(), + result = fn.call(this); + restore(); + toMeasure.each(function(restore){ + restore(); + }); + return result; + }, + + expose: function(){ + if (this.getStyle('display') != 'none') return function(){}; + var before = this.style.cssText; + this.setStyles({ + display: 'block', + position: 'absolute', + visibility: 'hidden' + }); + return function(){ + this.style.cssText = before; + }.bind(this); + }, + + getDimensions: function(options){ + options = Object.merge({computeSize: false}, options); + var dim = {x: 0, y: 0}; + + var getSize = function(el, options){ + return (options.computeSize) ? el.getComputedSize(options) : el.getSize(); + }; + + var parent = this.getParent('body'); + + if (parent && this.getStyle('display') == 'none'){ + dim = this.measure(function(){ + return getSize(this, options); + }); + } else if (parent){ + try { //safari sometimes crashes here, so catch it + dim = getSize(this, options); + }catch(e){} + } + + return Object.append(dim, (dim.x || dim.x === 0) ? { + width: dim.x, + height: dim.y + } : { + x: dim.width, + y: dim.height + } + ); + }, + + getComputedSize: function(options){ + + + options = Object.merge({ + styles: ['padding','border'], + planes: { + height: ['top','bottom'], + width: ['left','right'] + }, + mode: 'both' + }, options); + + var styles = {}, + size = {width: 0, height: 0}, + dimensions; + + if (options.mode == 'vertical'){ + delete size.width; + delete options.planes.width; + } else if (options.mode == 'horizontal'){ + delete size.height; + delete options.planes.height; + } + + getStylesList(options.styles, options.planes).each(function(style){ + styles[style] = this.getStyle(style).toInt(); + }, this); + + Object.each(options.planes, function(edges, plane){ + + var capitalized = plane.capitalize(), + style = this.getStyle(plane); + + if (style == 'auto' && !dimensions) dimensions = this.getDimensions(); + + style = styles[plane] = (style == 'auto') ? dimensions[plane] : style.toInt(); + size['total' + capitalized] = style; + + edges.each(function(edge){ + var edgesize = calculateEdgeSize(edge, styles); + size['computed' + edge.capitalize()] = edgesize; + size['total' + capitalized] += edgesize; + }); + + }, this); + + return Object.append(size, styles); + } + +}); + +})(); + +/* +--- + +script: Slider.js + +name: Slider + +description: Class for creating horizontal and vertical slider controls. + +license: MIT-style license + +authors: + - Valerio Proietti + +requires: + - Core/Element.Dimensions + - Core/Number + - Class.Binds + - Drag + - Element.Measure + +provides: [Slider] + +... +*/ + +var Slider = new Class({ + + Implements: [Events, Options], + + Binds: ['clickedElement', 'draggedKnob', 'scrolledElement'], + + options: {/* + onTick: function(intPosition){}, + onMove: function(){}, + onChange: function(intStep){}, + onComplete: function(strStep){},*/ + onTick: function(position){ + this.setKnobPosition(position); + }, + initialStep: 0, + snap: false, + offset: 0, + range: false, + wheel: false, + steps: 100, + mode: 'horizontal' + }, + + initialize: function(element, knob, options){ + this.setOptions(options); + options = this.options; + this.element = document.id(element); + knob = this.knob = document.id(knob); + this.previousChange = this.previousEnd = this.step = options.initialStep ? options.initialStep : options.range ? options.range[0] : 0; + + var limit = {}, + modifiers = {x: false, y: false}; + + switch (options.mode){ + case 'vertical': + this.axis = 'y'; + this.property = 'top'; + this.offset = 'offsetHeight'; + break; + case 'horizontal': + this.axis = 'x'; + this.property = 'left'; + this.offset = 'offsetWidth'; + } + + this.setSliderDimensions(); + this.setRange(options.range, null, true); + + if (knob.getStyle('position') == 'static') knob.setStyle('position', 'relative'); + knob.setStyle(this.property, -options.offset); + modifiers[this.axis] = this.property; + limit[this.axis] = [-options.offset, this.full - options.offset]; + + var dragOptions = { + snap: 0, + limit: limit, + modifiers: modifiers, + onDrag: this.draggedKnob, + onStart: this.draggedKnob, + onBeforeStart: (function(){ + this.isDragging = true; + }).bind(this), + onCancel: function(){ + this.isDragging = false; + }.bind(this), + onComplete: function(){ + this.isDragging = false; + this.draggedKnob(); + this.end(); + }.bind(this) + }; + if (options.snap) this.setSnap(dragOptions); + + this.drag = new Drag(knob, dragOptions); + if (options.initialStep != null) this.set(options.initialStep, true); + this.attach(); + }, + + attach: function(){ + this.element.addEvent('mousedown', this.clickedElement); + if (this.options.wheel) this.element.addEvent('mousewheel', this.scrolledElement); + this.drag.attach(); + return this; + }, + + detach: function(){ + this.element.removeEvent('mousedown', this.clickedElement) + .removeEvent('mousewheel', this.scrolledElement); + this.drag.detach(); + return this; + }, + + autosize: function(){ + this.setSliderDimensions() + .setKnobPosition(this.toPosition(this.step)); + this.drag.options.limit[this.axis] = [-this.options.offset, this.full - this.options.offset]; + if (this.options.snap) this.setSnap(); + return this; + }, + + setSnap: function(options){ + if (!options) options = this.drag.options; + options.grid = Math.ceil(this.stepWidth); + options.limit[this.axis][1] = this.element[this.offset]; + return this; + }, + + setKnobPosition: function(position){ + if (this.options.snap) position = this.toPosition(this.step); + this.knob.setStyle(this.property, position); + return this; + }, + + setSliderDimensions: function(){ + this.full = this.element.measure(function(){ + this.half = this.knob[this.offset] / 2; + return this.element[this.offset] - this.knob[this.offset] + (this.options.offset * 2); + }.bind(this)); + return this; + }, + + set: function(step, silently){ + if (!((this.range > 0) ^ (step < this.min))) step = this.min; + if (!((this.range > 0) ^ (step > this.max))) step = this.max; + + this.step = (step).round(this.modulus.decimalLength); + if (silently) this.checkStep().setKnobPosition(this.toPosition(this.step)); + else this.checkStep().fireEvent('tick', this.toPosition(this.step)).fireEvent('move').end(); + return this; + }, + + setRange: function(range, pos, silently){ + this.min = Array.pick([range[0], 0]); + this.max = Array.pick([range[1], this.options.steps]); + this.range = this.max - this.min; + this.steps = this.options.steps || this.full; + var stepSize = this.stepSize = Math.abs(this.range) / this.steps; + this.stepWidth = this.stepSize * this.full / Math.abs(this.range); + this.setModulus(); + + if (range) this.set(Array.pick([pos, this.step]).limit(this.min,this.max), silently); + return this; + }, + + setModulus: function(){ + var decimals = ((this.stepSize + '').split('.')[1] || []).length, + modulus = 1 + ''; + while (decimals--) modulus += '0'; + this.modulus = {multiplier: (modulus).toInt(10), decimalLength: modulus.length - 1}; + }, + + clickedElement: function(event){ + if (this.isDragging || event.target == this.knob) return; + + var dir = this.range < 0 ? -1 : 1, + position = event.page[this.axis] - this.element.getPosition()[this.axis] - this.half; + + position = position.limit(-this.options.offset, this.full - this.options.offset); + + this.step = (this.min + dir * this.toStep(position)).round(this.modulus.decimalLength); + + this.checkStep() + .fireEvent('tick', position) + .fireEvent('move') + .end(); + }, + + scrolledElement: function(event){ + var mode = (this.options.mode == 'horizontal') ? (event.wheel < 0) : (event.wheel > 0); + this.set(this.step + (mode ? -1 : 1) * this.stepSize); + event.stop(); + }, + + draggedKnob: function(){ + var dir = this.range < 0 ? -1 : 1, + position = this.drag.value.now[this.axis]; + + position = position.limit(-this.options.offset, this.full -this.options.offset); + + this.step = (this.min + dir * this.toStep(position)).round(this.modulus.decimalLength); + this.checkStep(); + this.fireEvent('move'); + }, + + checkStep: function(){ + var step = this.step; + if (this.previousChange != step){ + this.previousChange = step; + this.fireEvent('change', step); + } + return this; + }, + + end: function(){ + var step = this.step; + if (this.previousEnd !== step){ + this.previousEnd = step; + this.fireEvent('complete', step + ''); + } + return this; + }, + + toStep: function(position){ + var step = (position + this.options.offset) * this.stepSize / this.full * this.steps; + return this.options.steps ? (step - (step * this.modulus.multiplier) % (this.stepSize * this.modulus.multiplier) / this.modulus.multiplier).round(this.modulus.decimalLength) : step; + }, + + toPosition: function(step){ + return (this.full * Math.abs(this.min - step)) / (this.steps * this.stepSize) - this.options.offset || 0; + } + +}); + +/* +--- + +script: Sortables.js + +name: Sortables + +description: Class for creating a drag and drop sorting interface for lists of items. + +license: MIT-style license + +authors: + - Tom Occhino + +requires: + - Core/Fx.Morph + - Drag.Move + +provides: [Sortables] + +... +*/ + +var Sortables = new Class({ + + Implements: [Events, Options], + + options: {/* + onSort: function(element, clone){}, + onStart: function(element, clone){}, + onComplete: function(element){},*/ + opacity: 1, + clone: false, + revert: false, + handle: false, + dragOptions: {}, + unDraggableTags: ['button', 'input', 'a', 'textarea', 'select', 'option'] + }, + + initialize: function(lists, options){ + this.setOptions(options); + + this.elements = []; + this.lists = []; + this.idle = true; + + this.addLists($$(document.id(lists) || lists)); + + if (!this.options.clone) this.options.revert = false; + if (this.options.revert) this.effect = new Fx.Morph(null, Object.merge({ + duration: 250, + link: 'cancel' + }, this.options.revert)); + }, + + attach: function(){ + this.addLists(this.lists); + return this; + }, + + detach: function(){ + this.lists = this.removeLists(this.lists); + return this; + }, + + addItems: function(){ + Array.flatten(arguments).each(function(element){ + this.elements.push(element); + var start = element.retrieve('sortables:start', function(event){ + this.start.call(this, event, element); + }.bind(this)); + (this.options.handle ? element.getElement(this.options.handle) || element : element).addEvent('mousedown', start); + }, this); + return this; + }, + + addLists: function(){ + Array.flatten(arguments).each(function(list){ + this.lists.include(list); + this.addItems(list.getChildren()); + }, this); + return this; + }, + + removeItems: function(){ + return $$(Array.flatten(arguments).map(function(element){ + this.elements.erase(element); + var start = element.retrieve('sortables:start'); + (this.options.handle ? element.getElement(this.options.handle) || element : element).removeEvent('mousedown', start); + + return element; + }, this)); + }, + + removeLists: function(){ + return $$(Array.flatten(arguments).map(function(list){ + this.lists.erase(list); + this.removeItems(list.getChildren()); + + return list; + }, this)); + }, + + getDroppableCoordinates: function (element){ + var offsetParent = element.getOffsetParent(); + var position = element.getPosition(offsetParent); + var scroll = { + w: window.getScroll(), + offsetParent: offsetParent.getScroll() + }; + position.x += scroll.offsetParent.x; + position.y += scroll.offsetParent.y; + + if (offsetParent.getStyle('position') == 'fixed'){ + position.x -= scroll.w.x; + position.y -= scroll.w.y; + } + + return position; + }, + + getClone: function(event, element){ + if (!this.options.clone) return new Element(element.tagName).inject(document.body); + if (typeOf(this.options.clone) == 'function') return this.options.clone.call(this, event, element, this.list); + var clone = element.clone(true).setStyles({ + margin: 0, + position: 'absolute', + visibility: 'hidden', + width: element.getStyle('width') + }).addEvent('mousedown', function(event){ + element.fireEvent('mousedown', event); + }); + //prevent the duplicated radio inputs from unchecking the real one + if (clone.get('html').test('radio')){ + clone.getElements('input[type=radio]').each(function(input, i){ + input.set('name', 'clone_' + i); + if (input.get('checked')) element.getElements('input[type=radio]')[i].set('checked', true); + }); + } + + return clone.inject(this.list).setPosition(this.getDroppableCoordinates(this.element)); + }, + + getDroppables: function(){ + var droppables = this.list.getChildren().erase(this.clone).erase(this.element); + if (!this.options.constrain) droppables.append(this.lists).erase(this.list); + return droppables; + }, + + insert: function(dragging, element){ + var where = 'inside'; + if (this.lists.contains(element)){ + this.list = element; + this.drag.droppables = this.getDroppables(); + } else { + where = this.element.getAllPrevious().contains(element) ? 'before' : 'after'; + } + this.element.inject(element, where); + this.fireEvent('sort', [this.element, this.clone]); + }, + + start: function(event, element){ + if ( + !this.idle || + event.rightClick || + (!this.options.handle && this.options.unDraggableTags.contains(event.target.get('tag'))) + ) return; + + this.idle = false; + this.element = element; + this.opacity = element.getStyle('opacity'); + this.list = element.getParent(); + this.clone = this.getClone(event, element); + + this.drag = new Drag.Move(this.clone, Object.merge({ + + droppables: this.getDroppables() + }, this.options.dragOptions)).addEvents({ + onSnap: function(){ + event.stop(); + this.clone.setStyle('visibility', 'visible'); + this.element.setStyle('opacity', this.options.opacity || 0); + this.fireEvent('start', [this.element, this.clone]); + }.bind(this), + onEnter: this.insert.bind(this), + onCancel: this.end.bind(this), + onComplete: this.end.bind(this) + }); + + this.clone.inject(this.element, 'before'); + this.drag.start(event); + }, + + end: function(){ + this.drag.detach(); + this.element.setStyle('opacity', this.opacity); + var self = this; + if (this.effect){ + var dim = this.element.getStyles('width', 'height'), + clone = this.clone, + pos = clone.computePosition(this.getDroppableCoordinates(clone)); + + var destroy = function(){ + this.removeEvent('cancel', destroy); + clone.destroy(); + self.reset(); + }; + + this.effect.element = clone; + this.effect.start({ + top: pos.top, + left: pos.left, + width: dim.width, + height: dim.height, + opacity: 0.25 + }).addEvent('cancel', destroy).chain(destroy); + } else { + this.clone.destroy(); + self.reset(); + } + + }, + + reset: function(){ + this.idle = true; + this.fireEvent('complete', this.element); + }, + + serialize: function(){ + var params = Array.link(arguments, { + modifier: Type.isFunction, + index: function(obj){ + return obj != null; + } + }); + var serial = this.lists.map(function(list){ + return list.getChildren().map(params.modifier || function(element){ + return element.get('id'); + }, this); + }, this); + + var index = params.index; + if (this.lists.length == 1) index = 0; + return (index || index === 0) && index >= 0 && index < this.lists.length ? serial[index] : serial; + } + +}); + +/* +--- + +name: Element.Event.Pseudos + +description: Adds the functionality to add pseudo events for Elements + +license: MIT-style license + +authors: + - Arian Stolwijk + +requires: [Core/Element.Event, Core/Element.Delegation, Events.Pseudos] + +provides: [Element.Event.Pseudos, Element.Delegation.Pseudo] + +... +*/ + +(function(){ + +var pseudos = {relay: false}, + copyFromEvents = ['once', 'throttle', 'pause'], + count = copyFromEvents.length; + +while (count--) pseudos[copyFromEvents[count]] = Events.lookupPseudo(copyFromEvents[count]); + +DOMEvent.definePseudo = function(key, listener){ + pseudos[key] = listener; + return this; +}; + +var proto = Element.prototype; +[Element, Window, Document].invoke('implement', Events.Pseudos(pseudos, proto.addEvent, proto.removeEvent)); + +})(); + +/* +--- + +name: Element.Event.Pseudos.Keys + +description: Adds functionality fire events if certain keycombinations are pressed + +license: MIT-style license + +authors: + - Arian Stolwijk + +requires: [Element.Event.Pseudos] + +provides: [Element.Event.Pseudos.Keys] + +... +*/ + +(function(){ + +var keysStoreKey = '$moo:keys-pressed', + keysKeyupStoreKey = '$moo:keys-keyup'; + + +DOMEvent.definePseudo('keys', function(split, fn, args){ + + var event = args[0], + keys = [], + pressed = this.retrieve(keysStoreKey, []), + value = split.value; + + if (value != '+') keys.append(value.replace('++', function(){ + keys.push('+'); // shift++ and shift+++a + return ''; + }).split('+')); + else keys = ['+']; + + pressed.include(event.key); + + if (keys.every(function(key){ + return pressed.contains(key); + })) fn.apply(this, args); + + this.store(keysStoreKey, pressed); + + if (!this.retrieve(keysKeyupStoreKey)){ + var keyup = function(event){ + (function(){ + pressed = this.retrieve(keysStoreKey, []).erase(event.key); + this.store(keysStoreKey, pressed); + }).delay(0, this); // Fix for IE + }; + this.store(keysKeyupStoreKey, keyup).addEvent('keyup', keyup); + } + +}); + +DOMEvent.defineKeys({ + '16': 'shift', + '17': 'control', + '18': 'alt', + '20': 'capslock', + '33': 'pageup', + '34': 'pagedown', + '35': 'end', + '36': 'home', + '144': 'numlock', + '145': 'scrolllock', + '186': ';', + '187': '=', + '188': ',', + '190': '.', + '191': '/', + '192': '`', + '219': '[', + '220': '\\', + '221': ']', + '222': "'", + '107': '+', + '109': '-', // subtract + '189': '-' // dash +}) + +})(); + +/* +--- + +script: String.Extras.js + +name: String.Extras + +description: Extends the String native object to include methods useful in managing various kinds of strings (query strings, urls, html, etc). + +license: MIT-style license + +authors: + - Aaron Newton + - Guillermo Rauch + - Christopher Pitt + +requires: + - Core/String + - Core/Array + - MooTools.More + +provides: [String.Extras] + +... +*/ + +(function(){ + +var special = { + 'a': /[à áâãÀåÄÄ
]/g, + 'A': /[ÃÃÃÃÃÃ
ÄÄ]/g, + 'c': /[ÄÄç]/g, + 'C': /[ÄÄÃ]/g, + 'd': /[ÄÄ]/g, + 'D': /[ÄÃ]/g, + 'e': /[ÚéêëÄÄ]/g, + 'E': /[ÃÃÃÃÄÄ]/g, + 'g': /[Ä]/g, + 'G': /[Ä]/g, + 'i': /[ìÃîï]/g, + 'I': /[ÃÃÃÃ]/g, + 'l': /[ĺğÅ]/g, + 'L': /[ĹĜÅ]/g, + 'n': /[ñÅÅ]/g, + 'N': /[ÃÅÅ]/g, + 'o': /[òóÎõöÞÅ]/g, + 'O': /[ÃÃÃÃÃÃ]/g, + 'r': /[ÅÅ]/g, + 'R': /[ÅÅ]/g, + 's': /[ššÅ]/g, + 'S': /[Å ÅÅ]/g, + 't': /[ťţ]/g, + 'T': /[ŀŢ]/g, + 'u': /[ùúûů̵]/g, + 'U': /[ÃÃÃÅ®Ã]/g, + 'y': /[ÿÜ]/g, + 'Y': /[ÅžÃ]/g, + 'z': /[şźŌ]/g, + 'Z': /[ŜŹŻ]/g, + 'th': /[ß]/g, + 'TH': /[Ã]/g, + 'dh': /[ð]/g, + 'DH': /[Ã]/g, + 'ss': /[Ã]/g, + 'oe': /[Å]/g, + 'OE': /[Å]/g, + 'ae': /[Ê]/g, + 'AE': /[Ã]/g +}, + +tidy = { + ' ': /[\xa0\u2002\u2003\u2009]/g, + '*': /[\xb7]/g, + '\'': /[\u2018\u2019]/g, + '"': /[\u201c\u201d]/g, + '...': /[\u2026]/g, + '-': /[\u2013]/g, +// '--': /[\u2014]/g, + '»': /[\uFFFD]/g +}, + +conversions = { + ms: 1, + s: 1000, + m: 6e4, + h: 36e5 +}, + +findUnits = /(\d*.?\d+)([msh]+)/; + +var walk = function(string, replacements){ + var result = string, key; + for (key in replacements) result = result.replace(replacements[key], key); + return result; +}; + +var getRegexForTag = function(tag, contents){ + tag = tag || ''; + var regstr = contents ? "<" + tag + "(?!\\w)[^>]*>([\\s\\S]*?)<\/" + tag + "(?!\\w)>" : "<\/?" + tag + "([^>]+)?>", + reg = new RegExp(regstr, "gi"); + return reg; +}; + +String.implement({ + + standardize: function(){ + return walk(this, special); + }, + + repeat: function(times){ + return new Array(times + 1).join(this); + }, + + pad: function(length, str, direction){ + if (this.length >= length) return this; + + var pad = (str == null ? ' ' : '' + str) + .repeat(length - this.length) + .substr(0, length - this.length); + + if (!direction || direction == 'right') return this + pad; + if (direction == 'left') return pad + this; + + return pad.substr(0, (pad.length / 2).floor()) + this + pad.substr(0, (pad.length / 2).ceil()); + }, + + getTags: function(tag, contents){ + return this.match(getRegexForTag(tag, contents)) || []; + }, + + stripTags: function(tag, contents){ + return this.replace(getRegexForTag(tag, contents), ''); + }, + + tidy: function(){ + return walk(this, tidy); + }, + + truncate: function(max, trail, atChar){ + var string = this; + if (trail == null && arguments.length == 1) trail = 'âŠ'; + if (string.length > max){ + string = string.substring(0, max); + if (atChar){ + var index = string.lastIndexOf(atChar); + if (index != -1) string = string.substr(0, index); + } + if (trail) string += trail; + } + return string; + }, + + ms: function(){ + // "Borrowed" from https://gist.github.com/1503944 + var units = findUnits.exec(this); + if (units == null) return Number(this); + return Number(units[1]) * conversions[units[2]]; + } + +}); + +})(); + +/* +--- + +script: Element.Forms.js + +name: Element.Forms + +description: Extends the Element native object to include methods useful in managing inputs. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Element + - String.Extras + - MooTools.More + +provides: [Element.Forms] + +... +*/ + +Element.implement({ + + tidy: function(){ + this.set('value', this.get('value').tidy()); + }, + + getTextInRange: function(start, end){ + return this.get('value').substring(start, end); + }, + + getSelectedText: function(){ + if (this.setSelectionRange) return this.getTextInRange(this.getSelectionStart(), this.getSelectionEnd()); + return document.selection.createRange().text; + }, + + getSelectedRange: function(){ + if (this.selectionStart != null){ + return { + start: this.selectionStart, + end: this.selectionEnd + }; + } + + var pos = { + start: 0, + end: 0 + }; + var range = this.getDocument().selection.createRange(); + if (!range || range.parentElement() != this) return pos; + var duplicate = range.duplicate(); + + if (this.type == 'text'){ + pos.start = 0 - duplicate.moveStart('character', -100000); + pos.end = pos.start + range.text.length; + } else { + var value = this.get('value'); + var offset = value.length; + duplicate.moveToElementText(this); + duplicate.setEndPoint('StartToEnd', range); + if (duplicate.text.length) offset -= value.match(/[\n\r]*$/)[0].length; + pos.end = offset - duplicate.text.length; + duplicate.setEndPoint('StartToStart', range); + pos.start = offset - duplicate.text.length; + } + return pos; + }, + + getSelectionStart: function(){ + return this.getSelectedRange().start; + }, + + getSelectionEnd: function(){ + return this.getSelectedRange().end; + }, + + setCaretPosition: function(pos){ + if (pos == 'end') pos = this.get('value').length; + this.selectRange(pos, pos); + return this; + }, + + getCaretPosition: function(){ + return this.getSelectedRange().start; + }, + + selectRange: function(start, end){ + if (this.setSelectionRange){ + this.focus(); + this.setSelectionRange(start, end); + } else { + var value = this.get('value'); + var diff = value.substr(start, end - start).replace(/\r/g, '').length; + start = value.substr(0, start).replace(/\r/g, '').length; + var range = this.createTextRange(); + range.collapse(true); + range.moveEnd('character', start + diff); + range.moveStart('character', start); + range.select(); + } + return this; + }, + + insertAtCursor: function(value, select){ + var pos = this.getSelectedRange(); + var text = this.get('value'); + this.set('value', text.substring(0, pos.start) + value + text.substring(pos.end, text.length)); + if (select !== false) this.selectRange(pos.start, pos.start + value.length); + else this.setCaretPosition(pos.start + value.length); + return this; + }, + + insertAroundCursor: function(options, select){ + options = Object.append({ + before: '', + defaultMiddle: '', + after: '' + }, options); + + var value = this.getSelectedText() || options.defaultMiddle; + var pos = this.getSelectedRange(); + var text = this.get('value'); + + if (pos.start == pos.end){ + this.set('value', text.substring(0, pos.start) + options.before + value + options.after + text.substring(pos.end, text.length)); + this.selectRange(pos.start + options.before.length, pos.end + options.before.length + value.length); + } else { + var current = text.substring(pos.start, pos.end); + this.set('value', text.substring(0, pos.start) + options.before + current + options.after + text.substring(pos.end, text.length)); + var selStart = pos.start + options.before.length; + if (select !== false) this.selectRange(selStart, selStart + current.length); + else this.setCaretPosition(selStart + text.length); + } + return this; + } + +}); + +/* +--- + +script: Element.Pin.js + +name: Element.Pin + +description: Extends the Element native object to include the pin method useful for fixed positioning for elements. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Element.Event + - Core/Element.Dimensions + - Core/Element.Style + - MooTools.More + +provides: [Element.Pin] + +... +*/ + +(function(){ + var supportsPositionFixed = false, + supportTested = false; + + var testPositionFixed = function(){ + var test = new Element('div').setStyles({ + position: 'fixed', + top: 0, + right: 0 + }).inject(document.body); + supportsPositionFixed = (test.offsetTop === 0); + test.dispose(); + supportTested = true; + }; + + Element.implement({ + + pin: function(enable, forceScroll){ + if (!supportTested) testPositionFixed(); + if (this.getStyle('display') == 'none') return this; + + var pinnedPosition, + scroll = window.getScroll(), + parent, + scrollFixer; + + if (enable !== false){ + pinnedPosition = this.getPosition(); + if (!this.retrieve('pin:_pinned')) { + var currentPosition = { + top: pinnedPosition.y - scroll.y, + left: pinnedPosition.x - scroll.x, + margin: '0px', + padding: '0px' + }; + + if (supportsPositionFixed && !forceScroll){ + this.setStyle('position', 'fixed').setStyles(currentPosition); + } else { + + parent = this.getOffsetParent(); + var position = this.getPosition(parent), + styles = this.getStyles('left', 'top'); + + if (parent && styles.left == 'auto' || styles.top == 'auto') this.setPosition(position); + if (this.getStyle('position') == 'static') this.setStyle('position', 'absolute'); + + position = { + x: styles.left.toInt() - scroll.x, + y: styles.top.toInt() - scroll.y + }; + + scrollFixer = function(){ + if (!this.retrieve('pin:_pinned')) return; + var scroll = window.getScroll(); + this.setStyles({ + left: position.x + scroll.x, + top: position.y + scroll.y + }); + }.bind(this); + + this.store('pin:_scrollFixer', scrollFixer); + window.addEvent('scroll', scrollFixer); + } + this.store('pin:_pinned', true); + } + + } else { + if (!this.retrieve('pin:_pinned')) return this; + + parent = this.getParent(); + var offsetParent = (parent.getComputedStyle('position') != 'static' ? parent : parent.getOffsetParent()); + + pinnedPosition = this.getPosition(); + + this.store('pin:_pinned', false); + scrollFixer = this.retrieve('pin:_scrollFixer'); + if (!scrollFixer){ + this.setStyles({ + position: 'absolute', + top: pinnedPosition.y + scroll.y, + left: pinnedPosition.x + scroll.x + }); + } else { + this.store('pin:_scrollFixer', null); + window.removeEvent('scroll', scrollFixer); + } + this.removeClass('isPinned'); + } + return this; + }, + + unpin: function(){ + return this.pin(false); + }, + + togglePin: function(){ + return this.pin(!this.retrieve('pin:_pinned')); + } + + }); + + + +})(); + +/* +--- + +script: Element.Position.js + +name: Element.Position + +description: Extends the Element native object to include methods useful positioning elements relative to others. + +license: MIT-style license + +authors: + - Aaron Newton + - Jacob Thornton + +requires: + - Core/Options + - Core/Element.Dimensions + - Element.Measure + +provides: [Element.Position] + +... +*/ + +(function(original){ + +var local = Element.Position = { + + options: {/* + edge: false, + returnPos: false, + minimum: {x: 0, y: 0}, + maximum: {x: 0, y: 0}, + relFixedPosition: false, + ignoreMargins: false, + ignoreScroll: false, + allowNegative: false,*/ + relativeTo: document.body, + position: { + x: 'center', //left, center, right + y: 'center' //top, center, bottom + }, + offset: {x: 0, y: 0} + }, + + getOptions: function(element, options){ + options = Object.merge({}, local.options, options); + local.setPositionOption(options); + local.setEdgeOption(options); + local.setOffsetOption(element, options); + local.setDimensionsOption(element, options); + return options; + }, + + setPositionOption: function(options){ + options.position = local.getCoordinateFromValue(options.position); + }, + + setEdgeOption: function(options){ + var edgeOption = local.getCoordinateFromValue(options.edge); + options.edge = edgeOption ? edgeOption : + (options.position.x == 'center' && options.position.y == 'center') ? {x: 'center', y: 'center'} : + {x: 'left', y: 'top'}; + }, + + setOffsetOption: function(element, options){ + var parentOffset = {x: 0, y: 0}; + var parentScroll = {x: 0, y: 0}; + var offsetParent = element.measure(function(){ + return document.id(this.getOffsetParent()); + }); + + if (!offsetParent || offsetParent == element.getDocument().body) return; + + parentScroll = offsetParent.getScroll(); + parentOffset = offsetParent.measure(function(){ + var position = this.getPosition(); + if (this.getStyle('position') == 'fixed'){ + var scroll = window.getScroll(); + position.x += scroll.x; + position.y += scroll.y; + } + return position; + }); + + options.offset = { + parentPositioned: offsetParent != document.id(options.relativeTo), + x: options.offset.x - parentOffset.x + parentScroll.x, + y: options.offset.y - parentOffset.y + parentScroll.y + }; + }, + + setDimensionsOption: function(element, options){ + options.dimensions = element.getDimensions({ + computeSize: true, + styles: ['padding', 'border', 'margin'] + }); + }, + + getPosition: function(element, options){ + var position = {}; + options = local.getOptions(element, options); + var relativeTo = document.id(options.relativeTo) || document.body; + + local.setPositionCoordinates(options, position, relativeTo); + if (options.edge) local.toEdge(position, options); + + var offset = options.offset; + position.left = ((position.x >= 0 || offset.parentPositioned || options.allowNegative) ? position.x : 0).toInt(); + position.top = ((position.y >= 0 || offset.parentPositioned || options.allowNegative) ? position.y : 0).toInt(); + + local.toMinMax(position, options); + + if (options.relFixedPosition || relativeTo.getStyle('position') == 'fixed') local.toRelFixedPosition(relativeTo, position); + if (options.ignoreScroll) local.toIgnoreScroll(relativeTo, position); + if (options.ignoreMargins) local.toIgnoreMargins(position, options); + + position.left = Math.ceil(position.left); + position.top = Math.ceil(position.top); + delete position.x; + delete position.y; + + return position; + }, + + setPositionCoordinates: function(options, position, relativeTo){ + var offsetY = options.offset.y, + offsetX = options.offset.x, + calc = (relativeTo == document.body) ? window.getScroll() : relativeTo.getPosition(), + top = calc.y, + left = calc.x, + winSize = window.getSize(); + + switch(options.position.x){ + case 'left': position.x = left + offsetX; break; + case 'right': position.x = left + offsetX + relativeTo.offsetWidth; break; + default: position.x = left + ((relativeTo == document.body ? winSize.x : relativeTo.offsetWidth) / 2) + offsetX; break; + } + + switch(options.position.y){ + case 'top': position.y = top + offsetY; break; + case 'bottom': position.y = top + offsetY + relativeTo.offsetHeight; break; + default: position.y = top + ((relativeTo == document.body ? winSize.y : relativeTo.offsetHeight) / 2) + offsetY; break; + } + }, + + toMinMax: function(position, options){ + var xy = {left: 'x', top: 'y'}, value; + ['minimum', 'maximum'].each(function(minmax){ + ['left', 'top'].each(function(lr){ + value = options[minmax] ? options[minmax][xy[lr]] : null; + if (value != null && ((minmax == 'minimum') ? position[lr] < value : position[lr] > value)) position[lr] = value; + }); + }); + }, + + toRelFixedPosition: function(relativeTo, position){ + var winScroll = window.getScroll(); + position.top += winScroll.y; + position.left += winScroll.x; + }, + + toIgnoreScroll: function(relativeTo, position){ + var relScroll = relativeTo.getScroll(); + position.top -= relScroll.y; + position.left -= relScroll.x; + }, + + toIgnoreMargins: function(position, options){ + position.left += options.edge.x == 'right' + ? options.dimensions['margin-right'] + : (options.edge.x != 'center' + ? -options.dimensions['margin-left'] + : -options.dimensions['margin-left'] + ((options.dimensions['margin-right'] + options.dimensions['margin-left']) / 2)); + + position.top += options.edge.y == 'bottom' + ? options.dimensions['margin-bottom'] + : (options.edge.y != 'center' + ? -options.dimensions['margin-top'] + : -options.dimensions['margin-top'] + ((options.dimensions['margin-bottom'] + options.dimensions['margin-top']) / 2)); + }, + + toEdge: function(position, options){ + var edgeOffset = {}, + dimensions = options.dimensions, + edge = options.edge; + + switch(edge.x){ + case 'left': edgeOffset.x = 0; break; + case 'right': edgeOffset.x = -dimensions.x - dimensions.computedRight - dimensions.computedLeft; break; + // center + default: edgeOffset.x = -(Math.round(dimensions.totalWidth / 2)); break; + } + + switch(edge.y){ + case 'top': edgeOffset.y = 0; break; + case 'bottom': edgeOffset.y = -dimensions.y - dimensions.computedTop - dimensions.computedBottom; break; + // center + default: edgeOffset.y = -(Math.round(dimensions.totalHeight / 2)); break; + } + + position.x += edgeOffset.x; + position.y += edgeOffset.y; + }, + + getCoordinateFromValue: function(option){ + if (typeOf(option) != 'string') return option; + option = option.toLowerCase(); + + return { + x: option.test('left') ? 'left' + : (option.test('right') ? 'right' : 'center'), + y: option.test(/upper|top/) ? 'top' + : (option.test('bottom') ? 'bottom' : 'center') + }; + } + +}; + +Element.implement({ + + position: function(options){ + if (options && (options.x != null || options.y != null)){ + return (original ? original.apply(this, arguments) : this); + } + var position = this.setStyle('position', 'absolute').calculatePosition(options); + return (options && options.returnPos) ? position : this.setStyles(position); + }, + + calculatePosition: function(options){ + return local.getPosition(this, options); + } + +}); + +})(Element.prototype.position); + +/* +--- + +script: Element.Shortcuts.js + +name: Element.Shortcuts + +description: Extends the Element native object to include some shortcut methods. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Element.Style + - MooTools.More + +provides: [Element.Shortcuts] + +... +*/ + +Element.implement({ + + isDisplayed: function(){ + return this.getStyle('display') != 'none'; + }, + + isVisible: function(){ + var w = this.offsetWidth, + h = this.offsetHeight; + return (w == 0 && h == 0) ? false : (w > 0 && h > 0) ? true : this.style.display != 'none'; + }, + + toggle: function(){ + return this[this.isDisplayed() ? 'hide' : 'show'](); + }, + + hide: function(){ + var d; + try { + //IE fails here if the element is not in the dom + d = this.getStyle('display'); + } catch(e){} + if (d == 'none') return this; + return this.store('element:_originalDisplay', d || '').setStyle('display', 'none'); + }, + + show: function(display){ + if (!display && this.isDisplayed()) return this; + display = display || this.retrieve('element:_originalDisplay') || 'block'; + return this.setStyle('display', (display == 'none') ? 'block' : display); + }, + + swapClass: function(remove, add){ + return this.removeClass(remove).addClass(add); + } + +}); + +Document.implement({ + + clearSelection: function(){ + if (window.getSelection){ + var selection = window.getSelection(); + if (selection && selection.removeAllRanges) selection.removeAllRanges(); + } else if (document.selection && document.selection.empty){ + try { + //IE fails here if selected element is not in dom + document.selection.empty(); + } catch(e){} + } + } + +}); + +/* +--- + +script: Elements.From.js + +name: Elements.From + +description: Returns a collection of elements from a string of html. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/String + - Core/Element + - MooTools.More + +provides: [Elements.from, Elements.From] + +... +*/ + +Elements.from = function(text, excludeScripts){ + if (excludeScripts || excludeScripts == null) text = text.stripScripts(); + + var container, match = text.match(/^\s*(?:<!--.*?-->\s*)*<(t[dhr]|tbody|tfoot|thead)/i); + + if (match){ + container = new Element('table'); + var tag = match[1].toLowerCase(); + if (['td', 'th', 'tr'].contains(tag)){ + container = new Element('tbody').inject(container); + if (tag != 'tr') container = new Element('tr').inject(container); + } + } + + return (container || new Element('div')).set('html', text).getChildren(); +}; + +/* +--- + +script: IframeShim.js + +name: IframeShim + +description: Defines IframeShim, a class for obscuring select lists and flash objects in IE. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Element.Event + - Core/Element.Style + - Core/Options + - Core/Events + - Element.Position + - Class.Occlude + +provides: [IframeShim] + +... +*/ + +(function(){ + +var browsers = false; + + +this.IframeShim = new Class({ + + Implements: [Options, Events, Class.Occlude], + + options: { + className: 'iframeShim', + src: 'javascript:false;document.write("");', + display: false, + zIndex: null, + margin: 0, + offset: {x: 0, y: 0}, + browsers: browsers + }, + + property: 'IframeShim', + + initialize: function(element, options){ + this.element = document.id(element); + if (this.occlude()) return this.occluded; + this.setOptions(options); + this.makeShim(); + return this; + }, + + makeShim: function(){ + if (this.options.browsers){ + var zIndex = this.element.getStyle('zIndex').toInt(); + + if (!zIndex){ + zIndex = 1; + var pos = this.element.getStyle('position'); + if (pos == 'static' || !pos) this.element.setStyle('position', 'relative'); + this.element.setStyle('zIndex', zIndex); + } + zIndex = ((this.options.zIndex != null || this.options.zIndex === 0) && zIndex > this.options.zIndex) ? this.options.zIndex : zIndex - 1; + if (zIndex < 0) zIndex = 1; + this.shim = new Element('iframe', { + src: this.options.src, + scrolling: 'no', + frameborder: 0, + styles: { + zIndex: zIndex, + position: 'absolute', + border: 'none', + filter: 'progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)' + }, + 'class': this.options.className + }).store('IframeShim', this); + var inject = (function(){ + this.shim.inject(this.element, 'after'); + this[this.options.display ? 'show' : 'hide'](); + this.fireEvent('inject'); + }).bind(this); + if (!IframeShim.ready) window.addEvent('load', inject); + else inject(); + } else { + this.position = this.hide = this.show = this.dispose = Function.from(this); + } + }, + + position: function(){ + if (!IframeShim.ready || !this.shim) return this; + var size = this.element.measure(function(){ + return this.getSize(); + }); + if (this.options.margin != undefined){ + size.x = size.x - (this.options.margin * 2); + size.y = size.y - (this.options.margin * 2); + this.options.offset.x += this.options.margin; + this.options.offset.y += this.options.margin; + } + this.shim.set({width: size.x, height: size.y}).position({ + relativeTo: this.element, + offset: this.options.offset + }); + return this; + }, + + hide: function(){ + if (this.shim) this.shim.setStyle('display', 'none'); + return this; + }, + + show: function(){ + if (this.shim) this.shim.setStyle('display', 'block'); + return this.position(); + }, + + dispose: function(){ + if (this.shim) this.shim.dispose(); + return this; + }, + + destroy: function(){ + if (this.shim) this.shim.destroy(); + return this; + } + +}); + +})(); + +window.addEvent('load', function(){ + IframeShim.ready = true; +}); + +/* +--- + +script: Mask.js + +name: Mask + +description: Creates a mask element to cover another. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Options + - Core/Events + - Core/Element.Event + - Class.Binds + - Element.Position + - IframeShim + +provides: [Mask] + +... +*/ + +var Mask = new Class({ + + Implements: [Options, Events], + + Binds: ['position'], + + options: {/* + onShow: function(){}, + onHide: function(){}, + onDestroy: function(){}, + onClick: function(event){}, + inject: { + where: 'after', + target: null, + }, + hideOnClick: false, + id: null, + destroyOnHide: false,*/ + style: {}, + 'class': 'mask', + maskMargins: false, + useIframeShim: true, + iframeShimOptions: {} + }, + + initialize: function(target, options){ + this.target = document.id(target) || document.id(document.body); + this.target.store('mask', this); + this.setOptions(options); + this.render(); + this.inject(); + }, + + render: function(){ + this.element = new Element('div', { + 'class': this.options['class'], + id: this.options.id || 'mask-' + String.uniqueID(), + styles: Object.merge({}, this.options.style, { + display: 'none' + }), + events: { + click: function(event){ + this.fireEvent('click', event); + if (this.options.hideOnClick) this.hide(); + }.bind(this) + } + }); + + this.hidden = true; + }, + + toElement: function(){ + return this.element; + }, + + inject: function(target, where){ + where = where || (this.options.inject ? this.options.inject.where : '') || (this.target == document.body ? 'inside' : 'after'); + target = target || (this.options.inject && this.options.inject.target) || this.target; + + this.element.inject(target, where); + + if (this.options.useIframeShim){ + this.shim = new IframeShim(this.element, this.options.iframeShimOptions); + + this.addEvents({ + show: this.shim.show.bind(this.shim), + hide: this.shim.hide.bind(this.shim), + destroy: this.shim.destroy.bind(this.shim) + }); + } + }, + + position: function(){ + this.resize(this.options.width, this.options.height); + + this.element.position({ + relativeTo: this.target, + position: 'topLeft', + ignoreMargins: !this.options.maskMargins, + ignoreScroll: this.target == document.body + }); + + return this; + }, + + resize: function(x, y){ + var opt = { + styles: ['padding', 'border'] + }; + if (this.options.maskMargins) opt.styles.push('margin'); + + var dim = this.target.getComputedSize(opt); + if (this.target == document.body){ + this.element.setStyles({width: 0, height: 0}); + var win = window.getScrollSize(); + if (dim.totalHeight < win.y) dim.totalHeight = win.y; + if (dim.totalWidth < win.x) dim.totalWidth = win.x; + } + this.element.setStyles({ + width: Array.pick([x, dim.totalWidth, dim.x]), + height: Array.pick([y, dim.totalHeight, dim.y]) + }); + + return this; + }, + + show: function(){ + if (!this.hidden) return this; + + window.addEvent('resize', this.position); + this.position(); + this.showMask.apply(this, arguments); + + return this; + }, + + showMask: function(){ + this.element.setStyle('display', 'block'); + this.hidden = false; + this.fireEvent('show'); + }, + + hide: function(){ + if (this.hidden) return this; + + window.removeEvent('resize', this.position); + this.hideMask.apply(this, arguments); + if (this.options.destroyOnHide) return this.destroy(); + + return this; + }, + + hideMask: function(){ + this.element.setStyle('display', 'none'); + this.hidden = true; + this.fireEvent('hide'); + }, + + toggle: function(){ + this[this.hidden ? 'show' : 'hide'](); + }, + + destroy: function(){ + this.hide(); + this.element.destroy(); + this.fireEvent('destroy'); + this.target.eliminate('mask'); + } + +}); + +Element.Properties.mask = { + + set: function(options){ + var mask = this.retrieve('mask'); + if (mask) mask.destroy(); + return this.eliminate('mask').store('mask:options', options); + }, + + get: function(){ + var mask = this.retrieve('mask'); + if (!mask){ + mask = new Mask(this, this.retrieve('mask:options')); + this.store('mask', mask); + } + return mask; + } + +}; + +Element.implement({ + + mask: function(options){ + if (options) this.set('mask', options); + this.get('mask').show(); + return this; + }, + + unmask: function(){ + this.get('mask').hide(); + return this; + } + +}); + +/* +--- + +script: Spinner.js + +name: Spinner + +description: Adds a semi-transparent overlay over a dom element with a spinnin ajax icon. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Fx.Tween + - Core/Request + - Class.refactor + - Mask + +provides: [Spinner] + +... +*/ + +var Spinner = new Class({ + + Extends: Mask, + + Implements: Chain, + + options: {/* + message: false,*/ + 'class': 'spinner', + containerPosition: {}, + content: { + 'class': 'spinner-content' + }, + messageContainer: { + 'class': 'spinner-msg' + }, + img: { + 'class': 'spinner-img' + }, + fxOptions: { + link: 'chain' + } + }, + + initialize: function(target, options){ + this.target = document.id(target) || document.id(document.body); + this.target.store('spinner', this); + this.setOptions(options); + this.render(); + this.inject(); + + // Add this to events for when noFx is true; parent methods handle hide/show. + var deactivate = function(){ this.active = false; }.bind(this); + this.addEvents({ + hide: deactivate, + show: deactivate + }); + }, + + render: function(){ + this.parent(); + + this.element.set('id', this.options.id || 'spinner-' + String.uniqueID()); + + this.content = document.id(this.options.content) || new Element('div', this.options.content); + this.content.inject(this.element); + + if (this.options.message){ + this.msg = document.id(this.options.message) || new Element('p', this.options.messageContainer).appendText(this.options.message); + this.msg.inject(this.content); + } + + if (this.options.img){ + this.img = document.id(this.options.img) || new Element('div', this.options.img); + this.img.inject(this.content); + } + + this.element.set('tween', this.options.fxOptions); + }, + + show: function(noFx){ + if (this.active) return this.chain(this.show.bind(this)); + if (!this.hidden){ + this.callChain.delay(20, this); + return this; + } + + this.target.set('aria-busy', 'true'); + this.active = true; + + return this.parent(noFx); + }, + + showMask: function(noFx){ + var pos = function(){ + this.content.position(Object.merge({ + relativeTo: this.element + }, this.options.containerPosition)); + }.bind(this); + + if (noFx){ + this.parent(); + pos(); + } else { + if (!this.options.style.opacity) this.options.style.opacity = this.element.getStyle('opacity').toFloat(); + this.element.setStyles({ + display: 'block', + opacity: 0 + }).tween('opacity', this.options.style.opacity); + pos(); + this.hidden = false; + this.fireEvent('show'); + this.callChain(); + } + }, + + hide: function(noFx){ + if (this.active) return this.chain(this.hide.bind(this)); + if (this.hidden){ + this.callChain.delay(20, this); + return this; + } + + this.target.set('aria-busy', 'false'); + this.active = true; + + return this.parent(noFx); + }, + + hideMask: function(noFx){ + if (noFx) return this.parent(); + this.element.tween('opacity', 0).get('tween').chain(function(){ + this.element.setStyle('display', 'none'); + this.hidden = true; + this.fireEvent('hide'); + this.callChain(); + }.bind(this)); + }, + + destroy: function(){ + this.content.destroy(); + this.parent(); + this.target.eliminate('spinner'); + } + +}); + +Request = Class.refactor(Request, { + + options: { + useSpinner: false, + spinnerOptions: {}, + spinnerTarget: false + }, + + initialize: function(options){ + this._send = this.send; + this.send = function(options){ + var spinner = this.getSpinner(); + if (spinner) spinner.chain(this._send.pass(options, this)).show(); + else this._send(options); + return this; + }; + this.previous(options); + }, + + getSpinner: function(){ + if (!this.spinner){ + var update = document.id(this.options.spinnerTarget) || document.id(this.options.update); + if (this.options.useSpinner && update){ + update.set('spinner', this.options.spinnerOptions); + var spinner = this.spinner = update.get('spinner'); + ['complete', 'exception', 'cancel'].each(function(event){ + this.addEvent(event, spinner.hide.bind(spinner)); + }, this); + } + } + return this.spinner; + } + +}); + +Element.Properties.spinner = { + + set: function(options){ + var spinner = this.retrieve('spinner'); + if (spinner) spinner.destroy(); + return this.eliminate('spinner').store('spinner:options', options); + }, + + get: function(){ + var spinner = this.retrieve('spinner'); + if (!spinner){ + spinner = new Spinner(this, this.retrieve('spinner:options')); + this.store('spinner', spinner); + } + return spinner; + } + +}; + +Element.implement({ + + spin: function(options){ + if (options) this.set('spinner', options); + this.get('spinner').show(); + return this; + }, + + unspin: function(){ + this.get('spinner').hide(); + return this; + } + +}); + +/* +--- + +script: String.QueryString.js + +name: String.QueryString + +description: Methods for dealing with URI query strings. + +license: MIT-style license + +authors: + - Sebastian MarkbÃ¥ge + - Aaron Newton + - Lennart Pilon + - Valerio Proietti + +requires: + - Core/Array + - Core/String + - MooTools.More + +provides: [String.QueryString] + +... +*/ + +String.implement({ + + parseQueryString: function(decodeKeys, decodeValues){ + if (decodeKeys == null) decodeKeys = true; + if (decodeValues == null) decodeValues = true; + + var vars = this.split(/[&;]/), + object = {}; + if (!vars.length) return object; + + vars.each(function(val){ + var index = val.indexOf('=') + 1, + value = index ? val.substr(index) : '', + keys = index ? val.substr(0, index - 1).match(/([^\]\[]+|(\B)(?=\]))/g) : [val], + obj = object; + if (!keys) return; + if (decodeValues) value = decodeURIComponent(value); + keys.each(function(key, i){ + if (decodeKeys) key = decodeURIComponent(key); + var current = obj[key]; + + if (i < keys.length - 1) obj = obj[key] = current || {}; + else if (typeOf(current) == 'array') current.push(value); + else obj[key] = current != null ? [current, value] : value; + }); + }); + + return object; + }, + + cleanQueryString: function(method){ + return this.split('&').filter(function(val){ + var index = val.indexOf('='), + key = index < 0 ? '' : val.substr(0, index), + value = val.substr(index + 1); + + return method ? method.call(null, key, value) : (value || value === 0); + }).join('&'); + } + +}); + +/* +--- + +script: Form.Request.js + +name: Form.Request + +description: Handles the basic functionality of submitting a form and updating a dom element with the result. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Request.HTML + - Class.Binds + - Class.Occlude + - Spinner + - String.QueryString + - Element.Delegation.Pseudo + +provides: [Form.Request] + +... +*/ + +if (!window.Form) window.Form = {}; + +(function(){ + + Form.Request = new Class({ + + Binds: ['onSubmit', 'onFormValidate'], + + Implements: [Options, Events, Class.Occlude], + + options: {/* + onFailure: function(){}, + onSuccess: function(){}, // aliased to onComplete, + onSend: function(){}*/ + requestOptions: { + evalScripts: true, + useSpinner: true, + emulation: false, + link: 'ignore' + }, + sendButtonClicked: true, + extraData: {}, + resetForm: true + }, + + property: 'form.request', + + initialize: function(form, target, options){ + this.element = document.id(form); + if (this.occlude()) return this.occluded; + this.setOptions(options) + .setTarget(target) + .attach(); + }, + + setTarget: function(target){ + this.target = document.id(target); + if (!this.request){ + this.makeRequest(); + } else { + this.request.setOptions({ + update: this.target + }); + } + return this; + }, + + toElement: function(){ + return this.element; + }, + + makeRequest: function(){ + var self = this; + this.request = new Request.HTML(Object.merge({ + update: this.target, + emulation: false, + spinnerTarget: this.element, + method: this.element.get('method') || 'post' + }, this.options.requestOptions)).addEvents({ + success: function(tree, elements, html, javascript){ + ['complete', 'success'].each(function(evt){ + self.fireEvent(evt, [self.target, tree, elements, html, javascript]); + }); + }, + failure: function(){ + self.fireEvent('complete', arguments).fireEvent('failure', arguments); + }, + exception: function(){ + self.fireEvent('failure', arguments); + } + }); + return this.attachReset(); + }, + + attachReset: function(){ + if (!this.options.resetForm) return this; + this.request.addEvent('success', function(){ + Function.attempt(function(){ + this.element.reset(); + }.bind(this)); + if (window.OverText) OverText.update(); + }.bind(this)); + return this; + }, + + attach: function(attach){ + var method = (attach != false) ? 'addEvent' : 'removeEvent'; + this.element[method]('click:relay(button, input[type=submit])', this.saveClickedButton.bind(this)); + + var fv = this.element.retrieve('validator'); + if (fv) fv[method]('onFormValidate', this.onFormValidate); + else this.element[method]('submit', this.onSubmit); + + return this; + }, + + detach: function(){ + return this.attach(false); + }, + + //public method + enable: function(){ + return this.attach(); + }, + + //public method + disable: function(){ + return this.detach(); + }, + + onFormValidate: function(valid, form, event){ + //if there's no event, then this wasn't a submit event + if (!event) return; + var fv = this.element.retrieve('validator'); + if (valid || (fv && !fv.options.stopOnFailure)){ + event.stop(); + this.send(); + } + }, + + onSubmit: function(event){ + var fv = this.element.retrieve('validator'); + if (fv){ + //form validator was created after Form.Request + this.element.removeEvent('submit', this.onSubmit); + fv.addEvent('onFormValidate', this.onFormValidate); + fv.validate(event); + return; + } + if (event) event.stop(); + this.send(); + }, + + saveClickedButton: function(event, target){ + var targetName = target.get('name'); + if (!targetName || !this.options.sendButtonClicked) return; + this.options.extraData[targetName] = target.get('value') || true; + this.clickedCleaner = function(){ + delete this.options.extraData[targetName]; + this.clickedCleaner = function(){}; + }.bind(this); + }, + + clickedCleaner: function(){}, + + send: function(){ + var str = this.element.toQueryString().trim(), + data = Object.toQueryString(this.options.extraData); + + if (str) str += "&" + data; + else str = data; + + this.fireEvent('send', [this.element, str.parseQueryString()]); + this.request.send({ + data: str, + url: this.options.requestOptions.url || this.element.get('action') + }); + this.clickedCleaner(); + return this; + } + + }); + + Element.implement('formUpdate', function(update, options){ + var fq = this.retrieve('form.request'); + if (!fq){ + fq = new Form.Request(this, update, options); + } else { + if (update) fq.setTarget(update); + if (options) fq.setOptions(options).makeRequest(); + } + fq.send(); + return this; + }); + +})(); + +/* +--- + +script: Fx.Reveal.js + +name: Fx.Reveal + +description: Defines Fx.Reveal, a class that shows and hides elements with a transition. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Fx.Morph + - Element.Shortcuts + - Element.Measure + +provides: [Fx.Reveal] + +... +*/ + +(function(){ + + +var hideTheseOf = function(object){ + var hideThese = object.options.hideInputs; + if (window.OverText){ + var otClasses = [null]; + OverText.each(function(ot){ + otClasses.include('.' + ot.options.labelClass); + }); + if (otClasses) hideThese += otClasses.join(', '); + } + return (hideThese) ? object.element.getElements(hideThese) : null; +}; + + +Fx.Reveal = new Class({ + + Extends: Fx.Morph, + + options: {/* + onShow: function(thisElement){}, + onHide: function(thisElement){}, + onComplete: function(thisElement){}, + heightOverride: null, + widthOverride: null,*/ + link: 'cancel', + styles: ['padding', 'border', 'margin'], + transitionOpacity: 'opacity' in document.documentElement, + mode: 'vertical', + display: function(){ + return this.element.get('tag') != 'tr' ? 'block' : 'table-row'; + }, + opacity: 1, + hideInputs: !('opacity' in document.documentElement) ? 'select, input, textarea, object, embed' : null + }, + + dissolve: function(){ + if (!this.hiding && !this.showing){ + if (this.element.getStyle('display') != 'none'){ + this.hiding = true; + this.showing = false; + this.hidden = true; + this.cssText = this.element.style.cssText; + + var startStyles = this.element.getComputedSize({ + styles: this.options.styles, + mode: this.options.mode + }); + if (this.options.transitionOpacity) startStyles.opacity = this.options.opacity; + + var zero = {}; + Object.each(startStyles, function(style, name){ + zero[name] = [style, 0]; + }); + + this.element.setStyles({ + display: Function.from(this.options.display).call(this), + overflow: 'hidden' + }); + + var hideThese = hideTheseOf(this); + if (hideThese) hideThese.setStyle('visibility', 'hidden'); + + this.$chain.unshift(function(){ + if (this.hidden){ + this.hiding = false; + this.element.style.cssText = this.cssText; + this.element.setStyle('display', 'none'); + if (hideThese) hideThese.setStyle('visibility', 'visible'); + } + this.fireEvent('hide', this.element); + this.callChain(); + }.bind(this)); + + this.start(zero); + } else { + this.callChain.delay(10, this); + this.fireEvent('complete', this.element); + this.fireEvent('hide', this.element); + } + } else if (this.options.link == 'chain'){ + this.chain(this.dissolve.bind(this)); + } else if (this.options.link == 'cancel' && !this.hiding){ + this.cancel(); + this.dissolve(); + } + return this; + }, + + reveal: function(){ + if (!this.showing && !this.hiding){ + if (this.element.getStyle('display') == 'none'){ + this.hiding = false; + this.showing = true; + this.hidden = false; + this.cssText = this.element.style.cssText; + + var startStyles; + this.element.measure(function(){ + startStyles = this.element.getComputedSize({ + styles: this.options.styles, + mode: this.options.mode + }); + }.bind(this)); + if (this.options.heightOverride != null) startStyles.height = this.options.heightOverride.toInt(); + if (this.options.widthOverride != null) startStyles.width = this.options.widthOverride.toInt(); + if (this.options.transitionOpacity){ + this.element.setStyle('opacity', 0); + startStyles.opacity = this.options.opacity; + } + + var zero = { + height: 0, + display: Function.from(this.options.display).call(this) + }; + Object.each(startStyles, function(style, name){ + zero[name] = 0; + }); + zero.overflow = 'hidden'; + + this.element.setStyles(zero); + + var hideThese = hideTheseOf(this); + if (hideThese) hideThese.setStyle('visibility', 'hidden'); + + this.$chain.unshift(function(){ + this.element.style.cssText = this.cssText; + this.element.setStyle('display', Function.from(this.options.display).call(this)); + if (!this.hidden) this.showing = false; + if (hideThese) hideThese.setStyle('visibility', 'visible'); + this.callChain(); + this.fireEvent('show', this.element); + }.bind(this)); + + this.start(startStyles); + } else { + this.callChain(); + this.fireEvent('complete', this.element); + this.fireEvent('show', this.element); + } + } else if (this.options.link == 'chain'){ + this.chain(this.reveal.bind(this)); + } else if (this.options.link == 'cancel' && !this.showing){ + this.cancel(); + this.reveal(); + } + return this; + }, + + toggle: function(){ + if (this.element.getStyle('display') == 'none'){ + this.reveal(); + } else { + this.dissolve(); + } + return this; + }, + + cancel: function(){ + this.parent.apply(this, arguments); + if (this.cssText != null) this.element.style.cssText = this.cssText; + this.hiding = false; + this.showing = false; + return this; + } + +}); + +Element.Properties.reveal = { + + set: function(options){ + this.get('reveal').cancel().setOptions(options); + return this; + }, + + get: function(){ + var reveal = this.retrieve('reveal'); + if (!reveal){ + reveal = new Fx.Reveal(this); + this.store('reveal', reveal); + } + return reveal; + } + +}; + +Element.Properties.dissolve = Element.Properties.reveal; + +Element.implement({ + + reveal: function(options){ + this.get('reveal').setOptions(options).reveal(); + return this; + }, + + dissolve: function(options){ + this.get('reveal').setOptions(options).dissolve(); + return this; + }, + + nix: function(options){ + var params = Array.link(arguments, {destroy: Type.isBoolean, options: Type.isObject}); + this.get('reveal').setOptions(options).dissolve().chain(function(){ + this[params.destroy ? 'destroy' : 'dispose'](); + }.bind(this)); + return this; + }, + + wink: function(){ + var params = Array.link(arguments, {duration: Type.isNumber, options: Type.isObject}); + var reveal = this.get('reveal').setOptions(params.options); + reveal.reveal().chain(function(){ + (function(){ + reveal.dissolve(); + }).delay(params.duration || 2000); + }); + } + +}); + +})(); + +/* +--- + +script: Form.Request.Append.js + +name: Form.Request.Append + +description: Handles the basic functionality of submitting a form and updating a dom element with the result. The result is appended to the DOM element instead of replacing its contents. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Form.Request + - Fx.Reveal + - Elements.from + +provides: [Form.Request.Append] + +... +*/ + +Form.Request.Append = new Class({ + + Extends: Form.Request, + + options: { + //onBeforeEffect: function(){}, + useReveal: true, + revealOptions: {}, + inject: 'bottom' + }, + + makeRequest: function(){ + this.request = new Request.HTML(Object.merge({ + url: this.element.get('action'), + method: this.element.get('method') || 'post', + spinnerTarget: this.element + }, this.options.requestOptions, { + evalScripts: false + }) + ).addEvents({ + success: function(tree, elements, html, javascript){ + var container; + var kids = Elements.from(html); + if (kids.length == 1){ + container = kids[0]; + } else { + container = new Element('div', { + styles: { + display: 'none' + } + }).adopt(kids); + } + container.inject(this.target, this.options.inject); + if (this.options.requestOptions.evalScripts) Browser.exec(javascript); + this.fireEvent('beforeEffect', container); + var finish = function(){ + this.fireEvent('success', [container, this.target, tree, elements, html, javascript]); + }.bind(this); + if (this.options.useReveal){ + container.set('reveal', this.options.revealOptions).get('reveal').chain(finish); + container.reveal(); + } else { + finish(); + } + }.bind(this), + failure: function(xhr){ + this.fireEvent('failure', xhr); + }.bind(this) + }); + this.attachReset(); + } + +}); + +/* +--- + +script: Object.Extras.js + +name: Object.Extras + +description: Extra Object generics, like getFromPath which allows a path notation to child elements. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Object + - MooTools.More + +provides: [Object.Extras] + +... +*/ + +(function(){ + +var defined = function(value){ + return value != null; +}; + +var hasOwnProperty = Object.prototype.hasOwnProperty; + +Object.extend({ + + getFromPath: function(source, parts){ + if (typeof parts == 'string') parts = parts.split('.'); + for (var i = 0, l = parts.length; i < l; i++){ + if (hasOwnProperty.call(source, parts[i])) source = source[parts[i]]; + else return null; + } + return source; + }, + + cleanValues: function(object, method){ + method = method || defined; + for (var key in object) if (!method(object[key])){ + delete object[key]; + } + return object; + }, + + erase: function(object, key){ + if (hasOwnProperty.call(object, key)) delete object[key]; + return object; + }, + + run: function(object){ + var args = Array.slice(arguments, 1); + for (var key in object) if (object[key].apply){ + object[key].apply(object, args); + } + return object; + } + +}); + +})(); + +/* +--- + +script: Locale.js + +name: Locale + +description: Provides methods for localization. + +license: MIT-style license + +authors: + - Aaron Newton + - Arian Stolwijk + +requires: + - Core/Events + - Object.Extras + - MooTools.More + +provides: [Locale, Lang] + +... +*/ + +(function(){ + +var current = null, + locales = {}, + inherits = {}; + +var getSet = function(set){ + if (instanceOf(set, Locale.Set)) return set; + else return locales[set]; +}; + +var Locale = this.Locale = { + + define: function(locale, set, key, value){ + var name; + if (instanceOf(locale, Locale.Set)){ + name = locale.name; + if (name) locales[name] = locale; + } else { + name = locale; + if (!locales[name]) locales[name] = new Locale.Set(name); + locale = locales[name]; + } + + if (set) locale.define(set, key, value); + + + + if (!current) current = locale; + + return locale; + }, + + use: function(locale){ + locale = getSet(locale); + + if (locale){ + current = locale; + + this.fireEvent('change', locale); + + + } + + return this; + }, + + getCurrent: function(){ + return current; + }, + + get: function(key, args){ + return (current) ? current.get(key, args) : ''; + }, + + inherit: function(locale, inherits, set){ + locale = getSet(locale); + + if (locale) locale.inherit(inherits, set); + return this; + }, + + list: function(){ + return Object.keys(locales); + } + +}; + +Object.append(Locale, new Events); + +Locale.Set = new Class({ + + sets: {}, + + inherits: { + locales: [], + sets: {} + }, + + initialize: function(name){ + this.name = name || ''; + }, + + define: function(set, key, value){ + var defineData = this.sets[set]; + if (!defineData) defineData = {}; + + if (key){ + if (typeOf(key) == 'object') defineData = Object.merge(defineData, key); + else defineData[key] = value; + } + this.sets[set] = defineData; + + return this; + }, + + get: function(key, args, _base){ + var value = Object.getFromPath(this.sets, key); + if (value != null){ + var type = typeOf(value); + if (type == 'function') value = value.apply(null, Array.from(args)); + else if (type == 'object') value = Object.clone(value); + return value; + } + + // get value of inherited locales + var index = key.indexOf('.'), + set = index < 0 ? key : key.substr(0, index), + names = (this.inherits.sets[set] || []).combine(this.inherits.locales).include('en-US'); + if (!_base) _base = []; + + for (var i = 0, l = names.length; i < l; i++){ + if (_base.contains(names[i])) continue; + _base.include(names[i]); + + var locale = locales[names[i]]; + if (!locale) continue; + + value = locale.get(key, args, _base); + if (value != null) return value; + } + + return ''; + }, + + inherit: function(names, set){ + names = Array.from(names); + + if (set && !this.inherits.sets[set]) this.inherits.sets[set] = []; + + var l = names.length; + while (l--) (set ? this.inherits.sets[set] : this.inherits.locales).unshift(names[l]); + + return this; + } + +}); + + + +})(); + +/* +--- + +name: Locale.en-US.Date + +description: Date messages for US English. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Locale + +provides: [Locale.en-US.Date] + +... +*/ + +Locale.define('en-US', 'Date', { + + months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + months_abbr: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], + days_abbr: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + + // Culture's date order: MM/DD/YYYY + dateOrder: ['month', 'date', 'year'], + shortDate: '%m/%d/%Y', + shortTime: '%I:%M%p', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 0, + + // Date.Extras + ordinal: function(dayOfMonth){ + // 1st, 2nd, 3rd, etc. + return (dayOfMonth > 3 && dayOfMonth < 21) ? 'th' : ['th', 'st', 'nd', 'rd', 'th'][Math.min(dayOfMonth % 10, 4)]; + }, + + lessThanMinuteAgo: 'less than a minute ago', + minuteAgo: 'about a minute ago', + minutesAgo: '{delta} minutes ago', + hourAgo: 'about an hour ago', + hoursAgo: 'about {delta} hours ago', + dayAgo: '1 day ago', + daysAgo: '{delta} days ago', + weekAgo: '1 week ago', + weeksAgo: '{delta} weeks ago', + monthAgo: '1 month ago', + monthsAgo: '{delta} months ago', + yearAgo: '1 year ago', + yearsAgo: '{delta} years ago', + + lessThanMinuteUntil: 'less than a minute from now', + minuteUntil: 'about a minute from now', + minutesUntil: '{delta} minutes from now', + hourUntil: 'about an hour from now', + hoursUntil: 'about {delta} hours from now', + dayUntil: '1 day from now', + daysUntil: '{delta} days from now', + weekUntil: '1 week from now', + weeksUntil: '{delta} weeks from now', + monthUntil: '1 month from now', + monthsUntil: '{delta} months from now', + yearUntil: '1 year from now', + yearsUntil: '{delta} years from now' + +}); + +/* +--- + +script: Date.js + +name: Date + +description: Extends the Date native object to include methods useful in managing dates. + +license: MIT-style license + +authors: + - Aaron Newton + - Nicholas Barthelemy - https://svn.nbarthelemy.com/date-js/ + - Harald Kirshner - mail [at] digitarald.de; http://digitarald.de + - Scott Kyle - scott [at] appden.com; http://appden.com + +requires: + - Core/Array + - Core/String + - Core/Number + - MooTools.More + - Locale + - Locale.en-US.Date + +provides: [Date] + +... +*/ + +(function(){ + +var Date = this.Date; + +var DateMethods = Date.Methods = { + ms: 'Milliseconds', + year: 'FullYear', + min: 'Minutes', + mo: 'Month', + sec: 'Seconds', + hr: 'Hours' +}; + +['Date', 'Day', 'FullYear', 'Hours', 'Milliseconds', 'Minutes', 'Month', 'Seconds', 'Time', 'TimezoneOffset', + 'Week', 'Timezone', 'GMTOffset', 'DayOfYear', 'LastMonth', 'LastDayOfMonth', 'UTCDate', 'UTCDay', 'UTCFullYear', + 'AMPM', 'Ordinal', 'UTCHours', 'UTCMilliseconds', 'UTCMinutes', 'UTCMonth', 'UTCSeconds', 'UTCMilliseconds'].each(function(method){ + Date.Methods[method.toLowerCase()] = method; +}); + +var pad = function(n, digits, string){ + if (digits == 1) return n; + return n < Math.pow(10, digits - 1) ? (string || '0') + pad(n, digits - 1, string) : n; +}; + +Date.implement({ + + set: function(prop, value){ + prop = prop.toLowerCase(); + var method = DateMethods[prop] && 'set' + DateMethods[prop]; + if (method && this[method]) this[method](value); + return this; + }.overloadSetter(), + + get: function(prop){ + prop = prop.toLowerCase(); + var method = DateMethods[prop] && 'get' + DateMethods[prop]; + if (method && this[method]) return this[method](); + return null; + }.overloadGetter(), + + clone: function(){ + return new Date(this.get('time')); + }, + + increment: function(interval, times){ + interval = interval || 'day'; + times = times != null ? times : 1; + + switch (interval){ + case 'year': + return this.increment('month', times * 12); + case 'month': + var d = this.get('date'); + this.set('date', 1).set('mo', this.get('mo') + times); + return this.set('date', d.min(this.get('lastdayofmonth'))); + case 'week': + return this.increment('day', times * 7); + case 'day': + return this.set('date', this.get('date') + times); + } + + if (!Date.units[interval]) throw new Error(interval + ' is not a supported interval'); + + return this.set('time', this.get('time') + times * Date.units[interval]()); + }, + + decrement: function(interval, times){ + return this.increment(interval, -1 * (times != null ? times : 1)); + }, + + isLeapYear: function(){ + return Date.isLeapYear(this.get('year')); + }, + + clearTime: function(){ + return this.set({hr: 0, min: 0, sec: 0, ms: 0}); + }, + + diff: function(date, resolution){ + if (typeOf(date) == 'string') date = Date.parse(date); + + return ((date - this) / Date.units[resolution || 'day'](3, 3)).round(); // non-leap year, 30-day month + }, + + getLastDayOfMonth: function(){ + return Date.daysInMonth(this.get('mo'), this.get('year')); + }, + + getDayOfYear: function(){ + return (Date.UTC(this.get('year'), this.get('mo'), this.get('date') + 1) + - Date.UTC(this.get('year'), 0, 1)) / Date.units.day(); + }, + + setDay: function(day, firstDayOfWeek){ + if (firstDayOfWeek == null){ + firstDayOfWeek = Date.getMsg('firstDayOfWeek'); + if (firstDayOfWeek === '') firstDayOfWeek = 1; + } + + day = (7 + Date.parseDay(day, true) - firstDayOfWeek) % 7; + var currentDay = (7 + this.get('day') - firstDayOfWeek) % 7; + + return this.increment('day', day - currentDay); + }, + + getWeek: function(firstDayOfWeek){ + if (firstDayOfWeek == null){ + firstDayOfWeek = Date.getMsg('firstDayOfWeek'); + if (firstDayOfWeek === '') firstDayOfWeek = 1; + } + + var date = this, + dayOfWeek = (7 + date.get('day') - firstDayOfWeek) % 7, + dividend = 0, + firstDayOfYear; + + if (firstDayOfWeek == 1){ + // ISO-8601, week belongs to year that has the most days of the week (i.e. has the thursday of the week) + var month = date.get('month'), + startOfWeek = date.get('date') - dayOfWeek; + + if (month == 11 && startOfWeek > 28) return 1; // Week 1 of next year + + if (month == 0 && startOfWeek < -2){ + // Use a date from last year to determine the week + date = new Date(date).decrement('day', dayOfWeek); + dayOfWeek = 0; + } + + firstDayOfYear = new Date(date.get('year'), 0, 1).get('day') || 7; + if (firstDayOfYear > 4) dividend = -7; // First week of the year is not week 1 + } else { + // In other cultures the first week of the year is always week 1 and the last week always 53 or 54. + // Days in the same week can have a different weeknumber if the week spreads across two years. + firstDayOfYear = new Date(date.get('year'), 0, 1).get('day'); + } + + dividend += date.get('dayofyear'); + dividend += 6 - dayOfWeek; // Add days so we calculate the current date's week as a full week + dividend += (7 + firstDayOfYear - firstDayOfWeek) % 7; // Make up for first week of the year not being a full week + + return (dividend / 7); + }, + + getOrdinal: function(day){ + return Date.getMsg('ordinal', day || this.get('date')); + }, + + getTimezone: function(){ + return this.toString() + .replace(/^.*? ([A-Z]{3}).[0-9]{4}.*$/, '$1') + .replace(/^.*?\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\)$/, '$1$2$3'); + }, + + getGMTOffset: function(){ + var off = this.get('timezoneOffset'); + return ((off > 0) ? '-' : '+') + pad((off.abs() / 60).floor(), 2) + pad(off % 60, 2); + }, + + setAMPM: function(ampm){ + ampm = ampm.toUpperCase(); + var hr = this.get('hr'); + if (hr > 11 && ampm == 'AM') return this.decrement('hour', 12); + else if (hr < 12 && ampm == 'PM') return this.increment('hour', 12); + return this; + }, + + getAMPM: function(){ + return (this.get('hr') < 12) ? 'AM' : 'PM'; + }, + + parse: function(str){ + this.set('time', Date.parse(str)); + return this; + }, + + isValid: function(date){ + if (!date) date = this; + return typeOf(date) == 'date' && !isNaN(date.valueOf()); + }, + + format: function(format){ + if (!this.isValid()) return 'invalid date'; + + if (!format) format = '%x %X'; + if (typeof format == 'string') format = formats[format.toLowerCase()] || format; + if (typeof format == 'function') return format(this); + + var d = this; + return format.replace(/%([a-z%])/gi, + function($0, $1){ + switch ($1){ + case 'a': return Date.getMsg('days_abbr')[d.get('day')]; + case 'A': return Date.getMsg('days')[d.get('day')]; + case 'b': return Date.getMsg('months_abbr')[d.get('month')]; + case 'B': return Date.getMsg('months')[d.get('month')]; + case 'c': return d.format('%a %b %d %H:%M:%S %Y'); + case 'd': return pad(d.get('date'), 2); + case 'e': return pad(d.get('date'), 2, ' '); + case 'H': return pad(d.get('hr'), 2); + case 'I': return pad((d.get('hr') % 12) || 12, 2); + case 'j': return pad(d.get('dayofyear'), 3); + case 'k': return pad(d.get('hr'), 2, ' '); + case 'l': return pad((d.get('hr') % 12) || 12, 2, ' '); + case 'L': return pad(d.get('ms'), 3); + case 'm': return pad((d.get('mo') + 1), 2); + case 'M': return pad(d.get('min'), 2); + case 'o': return d.get('ordinal'); + case 'p': return Date.getMsg(d.get('ampm')); + case 's': return Math.round(d / 1000); + case 'S': return pad(d.get('seconds'), 2); + case 'T': return d.format('%H:%M:%S'); + case 'U': return pad(d.get('week'), 2); + case 'w': return d.get('day'); + case 'x': return d.format(Date.getMsg('shortDate')); + case 'X': return d.format(Date.getMsg('shortTime')); + case 'y': return d.get('year').toString().substr(2); + case 'Y': return d.get('year'); + case 'z': return d.get('GMTOffset'); + case 'Z': return d.get('Timezone'); + } + return $1; + } + ); + }, + + toISOString: function(){ + return this.format('iso8601'); + } + +}).alias({ + toJSON: 'toISOString', + compare: 'diff', + strftime: 'format' +}); + +// The day and month abbreviations are standardized, so we cannot use simply %a and %b because they will get localized +var rfcDayAbbr = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + rfcMonthAbbr = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; + +var formats = { + db: '%Y-%m-%d %H:%M:%S', + compact: '%Y%m%dT%H%M%S', + 'short': '%d %b %H:%M', + 'long': '%B %d, %Y %H:%M', + rfc822: function(date){ + return rfcDayAbbr[date.get('day')] + date.format(', %d ') + rfcMonthAbbr[date.get('month')] + date.format(' %Y %H:%M:%S %Z'); + }, + rfc2822: function(date){ + return rfcDayAbbr[date.get('day')] + date.format(', %d ') + rfcMonthAbbr[date.get('month')] + date.format(' %Y %H:%M:%S %z'); + }, + iso8601: function(date){ + return ( + date.getUTCFullYear() + '-' + + pad(date.getUTCMonth() + 1, 2) + '-' + + pad(date.getUTCDate(), 2) + 'T' + + pad(date.getUTCHours(), 2) + ':' + + pad(date.getUTCMinutes(), 2) + ':' + + pad(date.getUTCSeconds(), 2) + '.' + + pad(date.getUTCMilliseconds(), 3) + 'Z' + ); + } +}; + +var parsePatterns = [], + nativeParse = Date.parse; + +var parseWord = function(type, word, num){ + var ret = -1, + translated = Date.getMsg(type + 's'); + switch (typeOf(word)){ + case 'object': + ret = translated[word.get(type)]; + break; + case 'number': + ret = translated[word]; + if (!ret) throw new Error('Invalid ' + type + ' index: ' + word); + break; + case 'string': + var match = translated.filter(function(name){ + return this.test(name); + }, new RegExp('^' + word, 'i')); + if (!match.length) throw new Error('Invalid ' + type + ' string'); + if (match.length > 1) throw new Error('Ambiguous ' + type); + ret = match[0]; + } + + return (num) ? translated.indexOf(ret) : ret; +}; + +var startCentury = 1900, + startYear = 70; + +Date.extend({ + + getMsg: function(key, args){ + return Locale.get('Date.' + key, args); + }, + + units: { + ms: Function.from(1), + second: Function.from(1000), + minute: Function.from(60000), + hour: Function.from(3600000), + day: Function.from(86400000), + week: Function.from(608400000), + month: function(month, year){ + var d = new Date; + return Date.daysInMonth(month != null ? month : d.get('mo'), year != null ? year : d.get('year')) * 86400000; + }, + year: function(year){ + year = year || new Date().get('year'); + return Date.isLeapYear(year) ? 31622400000 : 31536000000; + } + }, + + daysInMonth: function(month, year){ + return [31, Date.isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]; + }, + + isLeapYear: function(year){ + return ((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0); + }, + + parse: function(from){ + var t = typeOf(from); + if (t == 'number') return new Date(from); + if (t != 'string') return from; + from = from.clean(); + if (!from.length) return null; + + var parsed; + parsePatterns.some(function(pattern){ + var bits = pattern.re.exec(from); + return (bits) ? (parsed = pattern.handler(bits)) : false; + }); + + if (!(parsed && parsed.isValid())){ + parsed = new Date(nativeParse(from)); + if (!(parsed && parsed.isValid())) parsed = new Date(from.toInt()); + } + return parsed; + }, + + parseDay: function(day, num){ + return parseWord('day', day, num); + }, + + parseMonth: function(month, num){ + return parseWord('month', month, num); + }, + + parseUTC: function(value){ + var localDate = new Date(value); + var utcSeconds = Date.UTC( + localDate.get('year'), + localDate.get('mo'), + localDate.get('date'), + localDate.get('hr'), + localDate.get('min'), + localDate.get('sec'), + localDate.get('ms') + ); + return new Date(utcSeconds); + }, + + orderIndex: function(unit){ + return Date.getMsg('dateOrder').indexOf(unit) + 1; + }, + + defineFormat: function(name, format){ + formats[name] = format; + return this; + }, + + + + defineParser: function(pattern){ + parsePatterns.push((pattern.re && pattern.handler) ? pattern : build(pattern)); + return this; + }, + + defineParsers: function(){ + Array.flatten(arguments).each(Date.defineParser); + return this; + }, + + define2DigitYearStart: function(year){ + startYear = year % 100; + startCentury = year - startYear; + return this; + } + +}).extend({ + defineFormats: Date.defineFormat.overloadSetter() +}); + +var regexOf = function(type){ + return new RegExp('(?:' + Date.getMsg(type).map(function(name){ + return name.substr(0, 3); + }).join('|') + ')[a-z]*'); +}; + +var replacers = function(key){ + switch (key){ + case 'T': + return '%H:%M:%S'; + case 'x': // iso8601 covers yyyy-mm-dd, so just check if month is first + return ((Date.orderIndex('month') == 1) ? '%m[-./]%d' : '%d[-./]%m') + '([-./]%y)?'; + case 'X': + return '%H([.:]%M)?([.:]%S([.:]%s)?)? ?%p? ?%z?'; + } + return null; +}; + +var keys = { + d: /[0-2]?[0-9]|3[01]/, + H: /[01]?[0-9]|2[0-3]/, + I: /0?[1-9]|1[0-2]/, + M: /[0-5]?\d/, + s: /\d+/, + o: /[a-z]*/, + p: /[ap]\.?m\.?/, + y: /\d{2}|\d{4}/, + Y: /\d{4}/, + z: /Z|[+-]\d{2}(?::?\d{2})?/ +}; + +keys.m = keys.I; +keys.S = keys.M; + +var currentLanguage; + +var recompile = function(language){ + currentLanguage = language; + + keys.a = keys.A = regexOf('days'); + keys.b = keys.B = regexOf('months'); + + parsePatterns.each(function(pattern, i){ + if (pattern.format) parsePatterns[i] = build(pattern.format); + }); +}; + +var build = function(format){ + if (!currentLanguage) return {format: format}; + + var parsed = []; + var re = (format.source || format) // allow format to be regex + .replace(/%([a-z])/gi, + function($0, $1){ + return replacers($1) || $0; + } + ).replace(/\((?!\?)/g, '(?:') // make all groups non-capturing + .replace(/ (?!\?|\*)/g, ',? ') // be forgiving with spaces and commas + .replace(/%([a-z%])/gi, + function($0, $1){ + var p = keys[$1]; + if (!p) return $1; + parsed.push($1); + return '(' + p.source + ')'; + } + ).replace(/\[a-z\]/gi, '[a-z\\u00c0-\\uffff;\&]'); // handle unicode words + + return { + format: format, + re: new RegExp('^' + re + '$', 'i'), + handler: function(bits){ + bits = bits.slice(1).associate(parsed); + var date = new Date().clearTime(), + year = bits.y || bits.Y; + + if (year != null) handle.call(date, 'y', year); // need to start in the right year + if ('d' in bits) handle.call(date, 'd', 1); + if ('m' in bits || bits.b || bits.B) handle.call(date, 'm', 1); + + for (var key in bits) handle.call(date, key, bits[key]); + return date; + } + }; +}; + +var handle = function(key, value){ + if (!value) return this; + + switch (key){ + case 'a': case 'A': return this.set('day', Date.parseDay(value, true)); + case 'b': case 'B': return this.set('mo', Date.parseMonth(value, true)); + case 'd': return this.set('date', value); + case 'H': case 'I': return this.set('hr', value); + case 'm': return this.set('mo', value - 1); + case 'M': return this.set('min', value); + case 'p': return this.set('ampm', value.replace(/\./g, '')); + case 'S': return this.set('sec', value); + case 's': return this.set('ms', ('0.' + value) * 1000); + case 'w': return this.set('day', value); + case 'Y': return this.set('year', value); + case 'y': + value = +value; + if (value < 100) value += startCentury + (value < startYear ? 100 : 0); + return this.set('year', value); + case 'z': + if (value == 'Z') value = '+00'; + var offset = value.match(/([+-])(\d{2}):?(\d{2})?/); + offset = (offset[1] + '1') * (offset[2] * 60 + (+offset[3] || 0)) + this.getTimezoneOffset(); + return this.set('time', this - offset * 60000); + } + + return this; +}; + +Date.defineParsers( + '%Y([-./]%m([-./]%d((T| )%X)?)?)?', // "1999-12-31", "1999-12-31 11:59pm", "1999-12-31 23:59:59", ISO8601 + '%Y%m%d(T%H(%M%S?)?)?', // "19991231", "19991231T1159", compact + '%x( %X)?', // "12/31", "12.31.99", "12-31-1999", "12/31/2008 11:59 PM" + '%d%o( %b( %Y)?)?( %X)?', // "31st", "31st December", "31 Dec 1999", "31 Dec 1999 11:59pm" + '%b( %d%o)?( %Y)?( %X)?', // Same as above with month and day switched + '%Y %b( %d%o( %X)?)?', // Same as above with year coming first + '%o %b %d %X %z %Y', // "Thu Oct 22 08:11:23 +0000 2009" + '%T', // %H:%M:%S + '%H:%M( ?%p)?' // "11:05pm", "11:05 am" and "11:05" +); + +Locale.addEvent('change', function(language){ + if (Locale.get('Date')) recompile(language); +}).fireEvent('change', Locale.getCurrent()); + +})(); + +/* +--- + +name: Locale.en-US.Form.Validator + +description: Form Validator messages for English. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Locale + +provides: [Locale.en-US.Form.Validator] + +... +*/ + +Locale.define('en-US', 'FormValidator', { + + required: 'This field is required.', + length: 'Please enter {length} characters (you entered {elLength} characters)', + minLength: 'Please enter at least {minLength} characters (you entered {length} characters).', + maxLength: 'Please enter no more than {maxLength} characters (you entered {length} characters).', + integer: 'Please enter an integer in this field. Numbers with decimals (e.g. 1.25) are not permitted.', + numeric: 'Please enter only numeric values in this field (i.e. "1" or "1.1" or "-1" or "-1.1").', + digits: 'Please use numbers and punctuation only in this field (for example, a phone number with dashes or dots is permitted).', + alpha: 'Please use only letters (a-z) within this field. No spaces or other characters are allowed.', + alphanum: 'Please use only letters (a-z) or numbers (0-9) in this field. No spaces or other characters are allowed.', + dateSuchAs: 'Please enter a valid date such as {date}', + dateInFormatMDY: 'Please enter a valid date such as MM/DD/YYYY (i.e. "12/31/1999")', + email: 'Please enter a valid email address. For example "fred@domain.com".', + url: 'Please enter a valid URL such as http://www.example.com.', + currencyDollar: 'Please enter a valid $ amount. For example $100.00 .', + oneRequired: 'Please enter something for at least one of these inputs.', + errorPrefix: 'Error: ', + warningPrefix: 'Warning: ', + + // Form.Validator.Extras + noSpace: 'There can be no spaces in this input.', + reqChkByNode: 'No items are selected.', + requiredChk: 'This field is required.', + reqChkByName: 'Please select a {label}.', + match: 'This field needs to match the {matchName} field', + startDate: 'the start date', + endDate: 'the end date', + currentDate: 'the current date', + afterDate: 'The date should be the same or after {label}.', + beforeDate: 'The date should be the same or before {label}.', + startMonth: 'Please select a start month', + sameMonth: 'These two dates must be in the same month - you must change one or the other.', + creditcard: 'The credit card number entered is invalid. Please check the number and try again. {length} digits entered.' + +}); + +/* +--- + +script: Form.Validator.js + +name: Form.Validator + +description: A css-class based form validation system. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Options + - Core/Events + - Core/Element.Delegation + - Core/Slick.Finder + - Core/Element.Event + - Core/Element.Style + - Core/JSON + - Locale + - Class.Binds + - Date + - Element.Forms + - Locale.en-US.Form.Validator + - Element.Shortcuts + +provides: [Form.Validator, InputValidator, FormValidator.BaseValidators] + +... +*/ +if (!window.Form) window.Form = {}; + +var InputValidator = this.InputValidator = new Class({ + + Implements: [Options], + + options: { + errorMsg: 'Validation failed.', + test: Function.from(true) + }, + + initialize: function(className, options){ + this.setOptions(options); + this.className = className; + }, + + test: function(field, props){ + field = document.id(field); + return (field) ? this.options.test(field, props || this.getProps(field)) : false; + }, + + getError: function(field, props){ + field = document.id(field); + var err = this.options.errorMsg; + if (typeOf(err) == 'function') err = err(field, props || this.getProps(field)); + return err; + }, + + getProps: function(field){ + field = document.id(field); + return (field) ? field.get('validatorProps') : {}; + } + +}); + +Element.Properties.validators = { + + get: function(){ + return (this.get('data-validators') || this.className).clean().split(' '); + } + +}; + +Element.Properties.validatorProps = { + + set: function(props){ + return this.eliminate('$moo:validatorProps').store('$moo:validatorProps', props); + }, + + get: function(props){ + if (props) this.set(props); + if (this.retrieve('$moo:validatorProps')) return this.retrieve('$moo:validatorProps'); + if (this.getProperty('data-validator-properties') || this.getProperty('validatorProps')){ + try { + this.store('$moo:validatorProps', JSON.decode(this.getProperty('validatorProps') || this.getProperty('data-validator-properties'), false)); + }catch(e){ + return {}; + } + } else { + var vals = this.get('validators').filter(function(cls){ + return cls.test(':'); + }); + if (!vals.length){ + this.store('$moo:validatorProps', {}); + } else { + props = {}; + vals.each(function(cls){ + var split = cls.split(':'); + if (split[1]){ + try { + props[split[0]] = JSON.decode(split[1], false); + } catch(e){} + } + }); + this.store('$moo:validatorProps', props); + } + } + return this.retrieve('$moo:validatorProps'); + } + +}; + +Form.Validator = new Class({ + + Implements: [Options, Events], + + options: {/* + onFormValidate: function(isValid, form, event){}, + onElementValidate: function(isValid, field, className, warn){}, + onElementPass: function(field){}, + onElementFail: function(field, validatorsFailed){}, */ + fieldSelectors: 'input, select, textarea', + ignoreHidden: true, + ignoreDisabled: true, + useTitles: false, + evaluateOnSubmit: true, + evaluateFieldsOnBlur: true, + evaluateFieldsOnChange: true, + serial: true, + stopOnFailure: true, + warningPrefix: function(){ + return Form.Validator.getMsg('warningPrefix') || 'Warning: '; + }, + errorPrefix: function(){ + return Form.Validator.getMsg('errorPrefix') || 'Error: '; + } + }, + + initialize: function(form, options){ + this.setOptions(options); + this.element = document.id(form); + this.warningPrefix = Function.from(this.options.warningPrefix)(); + this.errorPrefix = Function.from(this.options.errorPrefix)(); + this._bound = { + onSubmit: this.onSubmit.bind(this), + blurOrChange: function(event, field){ + this.validationMonitor(field, true); + }.bind(this) + }; + this.enable(); + }, + + toElement: function(){ + return this.element; + }, + + getFields: function(){ + return (this.fields = this.element.getElements(this.options.fieldSelectors)); + }, + + enable: function(){ + this.element.store('validator', this); + if (this.options.evaluateOnSubmit) this.element.addEvent('submit', this._bound.onSubmit); + if (this.options.evaluateFieldsOnBlur){ + this.element.addEvent('blur:relay(input,select,textarea)', this._bound.blurOrChange); + } + if (this.options.evaluateFieldsOnChange){ + this.element.addEvent('change:relay(input,select,textarea)', this._bound.blurOrChange); + } + }, + + disable: function(){ + this.element.eliminate('validator'); + this.element.removeEvents({ + submit: this._bound.onSubmit, + 'blur:relay(input,select,textarea)': this._bound.blurOrChange, + 'change:relay(input,select,textarea)': this._bound.blurOrChange + }); + }, + + validationMonitor: function(){ + clearTimeout(this.timer); + this.timer = this.validateField.delay(50, this, arguments); + }, + + onSubmit: function(event){ + if (this.validate(event)) this.reset(); + }, + + reset: function(){ + this.getFields().each(this.resetField, this); + return this; + }, + + validate: function(event){ + var result = this.getFields().map(function(field){ + return this.validateField(field, true); + }, this).every(function(v){ + return v; + }); + this.fireEvent('formValidate', [result, this.element, event]); + if (this.options.stopOnFailure && !result && event) event.preventDefault(); + return result; + }, + + validateField: function(field, force){ + if (this.paused) return true; + field = document.id(field); + var passed = !field.hasClass('validation-failed'); + var failed, warned; + if (this.options.serial && !force){ + failed = this.element.getElement('.validation-failed'); + warned = this.element.getElement('.warning'); + } + if (field && (!failed || force || field.hasClass('validation-failed') || (failed && !this.options.serial))){ + var validationTypes = field.get('validators'); + var validators = validationTypes.some(function(cn){ + return this.getValidator(cn); + }, this); + var validatorsFailed = []; + validationTypes.each(function(className){ + if (className && !this.test(className, field)) validatorsFailed.include(className); + }, this); + passed = validatorsFailed.length === 0; + if (validators && !this.hasValidator(field, 'warnOnly')){ + if (passed){ + field.addClass('validation-passed').removeClass('validation-failed'); + this.fireEvent('elementPass', [field]); + } else { + field.addClass('validation-failed').removeClass('validation-passed'); + this.fireEvent('elementFail', [field, validatorsFailed]); + } + } + if (!warned){ + var warnings = validationTypes.some(function(cn){ + if (cn.test('^warn')) + return this.getValidator(cn.replace(/^warn-/,'')); + else return null; + }, this); + field.removeClass('warning'); + var warnResult = validationTypes.map(function(cn){ + if (cn.test('^warn')) + return this.test(cn.replace(/^warn-/,''), field, true); + else return null; + }, this); + } + } + return passed; + }, + + test: function(className, field, warn){ + field = document.id(field); + if ((this.options.ignoreHidden && !field.isVisible()) || (this.options.ignoreDisabled && field.get('disabled'))) return true; + var validator = this.getValidator(className); + if (warn != null) warn = false; + if (this.hasValidator(field, 'warnOnly')) warn = true; + var isValid = field.hasClass('ignoreValidation') || (validator ? validator.test(field) : true); + if (validator) this.fireEvent('elementValidate', [isValid, field, className, warn]); + if (warn) return true; + return isValid; + }, + + hasValidator: function(field, value){ + return field.get('validators').contains(value); + }, + + resetField: function(field){ + field = document.id(field); + if (field){ + field.get('validators').each(function(className){ + if (className.test('^warn-')) className = className.replace(/^warn-/, ''); + field.removeClass('validation-failed'); + field.removeClass('warning'); + field.removeClass('validation-passed'); + }, this); + } + return this; + }, + + stop: function(){ + this.paused = true; + return this; + }, + + start: function(){ + this.paused = false; + return this; + }, + + ignoreField: function(field, warn){ + field = document.id(field); + if (field){ + this.enforceField(field); + if (warn) field.addClass('warnOnly'); + else field.addClass('ignoreValidation'); + } + return this; + }, + + enforceField: function(field){ + field = document.id(field); + if (field) field.removeClass('warnOnly').removeClass('ignoreValidation'); + return this; + } + +}); + +Form.Validator.getMsg = function(key){ + return Locale.get('FormValidator.' + key); +}; + +Form.Validator.adders = { + + validators:{}, + + add : function(className, options){ + this.validators[className] = new InputValidator(className, options); + //if this is a class (this method is used by instances of Form.Validator and the Form.Validator namespace) + //extend these validators into it + //this allows validators to be global and/or per instance + if (!this.initialize){ + this.implement({ + validators: this.validators + }); + } + }, + + addAllThese : function(validators){ + Array.from(validators).each(function(validator){ + this.add(validator[0], validator[1]); + }, this); + }, + + getValidator: function(className){ + return this.validators[className.split(':')[0]]; + } + +}; + +Object.append(Form.Validator, Form.Validator.adders); + +Form.Validator.implement(Form.Validator.adders); + +Form.Validator.add('IsEmpty', { + + errorMsg: false, + test: function(element){ + if (element.type == 'select-one' || element.type == 'select') + return !(element.selectedIndex >= 0 && element.options[element.selectedIndex].value != ''); + else + return ((element.get('value') == null) || (element.get('value').length == 0)); + } + +}); + +Form.Validator.addAllThese([ + + ['required', { + errorMsg: function(){ + return Form.Validator.getMsg('required'); + }, + test: function(element){ + return !Form.Validator.getValidator('IsEmpty').test(element); + } + }], + + ['length', { + errorMsg: function(element, props){ + if (typeOf(props.length) != 'null') + return Form.Validator.getMsg('length').substitute({length: props.length, elLength: element.get('value').length}); + else return ''; + }, + test: function(element, props){ + if (typeOf(props.length) != 'null') return (element.get('value').length == props.length || element.get('value').length == 0); + else return true; + } + }], + + ['minLength', { + errorMsg: function(element, props){ + if (typeOf(props.minLength) != 'null') + return Form.Validator.getMsg('minLength').substitute({minLength: props.minLength, length: element.get('value').length}); + else return ''; + }, + test: function(element, props){ + if (typeOf(props.minLength) != 'null') return (element.get('value').length >= (props.minLength || 0)); + else return true; + } + }], + + ['maxLength', { + errorMsg: function(element, props){ + //props is {maxLength:10} + if (typeOf(props.maxLength) != 'null') + return Form.Validator.getMsg('maxLength').substitute({maxLength: props.maxLength, length: element.get('value').length}); + else return ''; + }, + test: function(element, props){ + return element.get('value').length <= (props.maxLength || 10000); + } + }], + + ['validate-integer', { + errorMsg: Form.Validator.getMsg.pass('integer'), + test: function(element){ + return Form.Validator.getValidator('IsEmpty').test(element) || (/^(-?[1-9]\d*|0)$/).test(element.get('value')); + } + }], + + ['validate-numeric', { + errorMsg: Form.Validator.getMsg.pass('numeric'), + test: function(element){ + return Form.Validator.getValidator('IsEmpty').test(element) || + (/^-?(?:0$0(?=\d*\.)|[1-9]|0)\d*(\.\d+)?$/).test(element.get('value')); + } + }], + + ['validate-digits', { + errorMsg: Form.Validator.getMsg.pass('digits'), + test: function(element){ + return Form.Validator.getValidator('IsEmpty').test(element) || (/^[\d() .:\-\+#]+$/.test(element.get('value'))); + } + }], + + ['validate-alpha', { + errorMsg: Form.Validator.getMsg.pass('alpha'), + test: function(element){ + return Form.Validator.getValidator('IsEmpty').test(element) || (/^[a-zA-Z]+$/).test(element.get('value')); + } + }], + + ['validate-alphanum', { + errorMsg: Form.Validator.getMsg.pass('alphanum'), + test: function(element){ + return Form.Validator.getValidator('IsEmpty').test(element) || !(/\W/).test(element.get('value')); + } + }], + + ['validate-date', { + errorMsg: function(element, props){ + if (Date.parse){ + var format = props.dateFormat || '%x'; + return Form.Validator.getMsg('dateSuchAs').substitute({date: new Date().format(format)}); + } else { + return Form.Validator.getMsg('dateInFormatMDY'); + } + }, + test: function(element, props){ + if (Form.Validator.getValidator('IsEmpty').test(element)) return true; + var dateLocale = Locale.get('Date'), + dateNouns = new RegExp([dateLocale.days, dateLocale.days_abbr, dateLocale.months, dateLocale.months_abbr, dateLocale.AM, dateLocale.PM].flatten().join('|'), 'i'), + value = element.get('value'), + wordsInValue = value.match(/[a-z]+/gi); + + if (wordsInValue && !wordsInValue.every(dateNouns.exec, dateNouns)) return false; + + var date = Date.parse(value); + if (!date) return false; + + var format = props.dateFormat || '%x', + formatted = date.format(format); + if (formatted != 'invalid date') element.set('value', formatted); + return date.isValid(); + } + }], + + ['validate-email', { + errorMsg: Form.Validator.getMsg.pass('email'), + test: function(element){ + /* + var chars = "[a-z0-9!#$%&'*+/=?^_`{|}~-]", + local = '(?:' + chars + '\\.?){0,63}' + chars, + + label = '[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?', + hostname = '(?:' + label + '\\.)*' + label; + + octet = '(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)', + ipv4 = '\\[(?:' + octet + '\\.){3}' + octet + '\\]', + + domain = '(?:' + hostname + '|' + ipv4 + ')'; + + var regex = new RegExp('^' + local + '@' + domain + '$', 'i'); + */ + return Form.Validator.getValidator('IsEmpty').test(element) || (/^(?:[a-z0-9!#$%&'*+\/=?^_`{|}~-]\.?){0,63}[a-z0-9!#$%&'*+\/=?^_`{|}~-]@(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\])$/i).test(element.get('value')); + } + }], + + ['validate-url', { + errorMsg: Form.Validator.getMsg.pass('url'), + test: function(element){ + return Form.Validator.getValidator('IsEmpty').test(element) || (/^(https?|ftp|rmtp|mms):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\/?/i).test(element.get('value')); + } + }], + + ['validate-currency-dollar', { + errorMsg: Form.Validator.getMsg.pass('currencyDollar'), + test: function(element){ + return Form.Validator.getValidator('IsEmpty').test(element) || (/^\$?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(element.get('value')); + } + }], + + ['validate-one-required', { + errorMsg: Form.Validator.getMsg.pass('oneRequired'), + test: function(element, props){ + var p = document.id(props['validate-one-required']) || element.getParent(props['validate-one-required']); + return p.getElements('input').some(function(el){ + if (['checkbox', 'radio'].contains(el.get('type'))) return el.get('checked'); + return el.get('value'); + }); + } + }] + +]); + +Element.Properties.validator = { + + set: function(options){ + this.get('validator').setOptions(options); + }, + + get: function(){ + var validator = this.retrieve('validator'); + if (!validator){ + validator = new Form.Validator(this); + this.store('validator', validator); + } + return validator; + } + +}; + +Element.implement({ + + validate: function(options){ + if (options) this.set('validator', options); + return this.get('validator').validate(); + } + +}); + + + + + + +/* +--- + +script: Form.Validator.Extras.js + +name: Form.Validator.Extras + +description: Additional validators for the Form.Validator class. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Form.Validator + +provides: [Form.Validator.Extras] + +... +*/ +Form.Validator.addAllThese([ + + ['validate-enforce-oncheck', { + test: function(element, props){ + var fv = element.getParent('form').retrieve('validator'); + if (!fv) return true; + (props.toEnforce || document.id(props.enforceChildrenOf).getElements('input, select, textarea')).map(function(item){ + if (element.checked){ + fv.enforceField(item); + } else { + fv.ignoreField(item); + fv.resetField(item); + } + }); + return true; + } + }], + + ['validate-ignore-oncheck', { + test: function(element, props){ + var fv = element.getParent('form').retrieve('validator'); + if (!fv) return true; + (props.toIgnore || document.id(props.ignoreChildrenOf).getElements('input, select, textarea')).each(function(item){ + if (element.checked){ + fv.ignoreField(item); + fv.resetField(item); + } else { + fv.enforceField(item); + } + }); + return true; + } + }], + + ['validate-nospace', { + errorMsg: function(){ + return Form.Validator.getMsg('noSpace'); + }, + test: function(element, props){ + return !element.get('value').test(/\s/); + } + }], + + ['validate-toggle-oncheck', { + test: function(element, props){ + var fv = element.getParent('form').retrieve('validator'); + if (!fv) return true; + var eleArr = props.toToggle || document.id(props.toToggleChildrenOf).getElements('input, select, textarea'); + if (!element.checked){ + eleArr.each(function(item){ + fv.ignoreField(item); + fv.resetField(item); + }); + } else { + eleArr.each(function(item){ + fv.enforceField(item); + }); + } + return true; + } + }], + + ['validate-reqchk-bynode', { + errorMsg: function(){ + return Form.Validator.getMsg('reqChkByNode'); + }, + test: function(element, props){ + return (document.id(props.nodeId).getElements(props.selector || 'input[type=checkbox], input[type=radio]')).some(function(item){ + return item.checked; + }); + } + }], + + ['validate-required-check', { + errorMsg: function(element, props){ + return props.useTitle ? element.get('title') : Form.Validator.getMsg('requiredChk'); + }, + test: function(element, props){ + return !!element.checked; + } + }], + + ['validate-reqchk-byname', { + errorMsg: function(element, props){ + return Form.Validator.getMsg('reqChkByName').substitute({label: props.label || element.get('type')}); + }, + test: function(element, props){ + var grpName = props.groupName || element.get('name'); + var oneCheckedItem = $$(document.getElementsByName(grpName)).some(function(item, index){ + return item.checked; + }); + var fv = element.getParent('form').retrieve('validator'); + if (oneCheckedItem && fv) fv.resetField(element); + return oneCheckedItem; + } + }], + + ['validate-match', { + errorMsg: function(element, props){ + return Form.Validator.getMsg('match').substitute({matchName: props.matchName || document.id(props.matchInput).get('name')}); + }, + test: function(element, props){ + var eleVal = element.get('value'); + var matchVal = document.id(props.matchInput) && document.id(props.matchInput).get('value'); + return eleVal && matchVal ? eleVal == matchVal : true; + } + }], + + ['validate-after-date', { + errorMsg: function(element, props){ + return Form.Validator.getMsg('afterDate').substitute({ + label: props.afterLabel || (props.afterElement ? Form.Validator.getMsg('startDate') : Form.Validator.getMsg('currentDate')) + }); + }, + test: function(element, props){ + var start = document.id(props.afterElement) ? Date.parse(document.id(props.afterElement).get('value')) : new Date(); + var end = Date.parse(element.get('value')); + return end && start ? end >= start : true; + } + }], + + ['validate-before-date', { + errorMsg: function(element, props){ + return Form.Validator.getMsg('beforeDate').substitute({ + label: props.beforeLabel || (props.beforeElement ? Form.Validator.getMsg('endDate') : Form.Validator.getMsg('currentDate')) + }); + }, + test: function(element, props){ + var start = Date.parse(element.get('value')); + var end = document.id(props.beforeElement) ? Date.parse(document.id(props.beforeElement).get('value')) : new Date(); + return end && start ? end >= start : true; + } + }], + + ['validate-custom-required', { + errorMsg: function(){ + return Form.Validator.getMsg('required'); + }, + test: function(element, props){ + return element.get('value') != props.emptyValue; + } + }], + + ['validate-same-month', { + errorMsg: function(element, props){ + var startMo = document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value'); + var eleVal = element.get('value'); + if (eleVal != '') return Form.Validator.getMsg(startMo ? 'sameMonth' : 'startMonth'); + }, + test: function(element, props){ + var d1 = Date.parse(element.get('value')); + var d2 = Date.parse(document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value')); + return d1 && d2 ? d1.format('%B') == d2.format('%B') : true; + } + }], + + + ['validate-cc-num', { + errorMsg: function(element){ + var ccNum = element.get('value').replace(/[^0-9]/g, ''); + return Form.Validator.getMsg('creditcard').substitute({length: ccNum.length}); + }, + test: function(element){ + // required is a different test + if (Form.Validator.getValidator('IsEmpty').test(element)) return true; + + // Clean number value + var ccNum = element.get('value'); + ccNum = ccNum.replace(/[^0-9]/g, ''); + + var valid_type = false; + + if (ccNum.test(/^4[0-9]{12}([0-9]{3})?$/)) valid_type = 'Visa'; + else if (ccNum.test(/^5[1-5]([0-9]{14})$/)) valid_type = 'Master Card'; + else if (ccNum.test(/^3[47][0-9]{13}$/)) valid_type = 'American Express'; + else if (ccNum.test(/^6(?:011|5[0-9]{2})[0-9]{12}$/)) valid_type = 'Discover'; + else if (ccNum.test(/^3(?:0[0-5]|[68][0-9])[0-9]{11}$/)) valid_type = 'Diners Club'; + + if (valid_type){ + var sum = 0; + var cur = 0; + + for (var i=ccNum.length-1; i>=0; --i){ + cur = ccNum.charAt(i).toInt(); + if (cur == 0) continue; + + if ((ccNum.length-i) % 2 == 0) cur += cur; + if (cur > 9){ + cur = cur.toString().charAt(0).toInt() + cur.toString().charAt(1).toInt(); + } + + sum += cur; + } + if ((sum % 10) == 0) return true; + } + + var chunks = ''; + while (ccNum != ''){ + chunks += ' ' + ccNum.substr(0,4); + ccNum = ccNum.substr(4); + } + + element.getParent('form').retrieve('validator').ignoreField(element); + element.set('value', chunks.clean()); + element.getParent('form').retrieve('validator').enforceField(element); + return false; + } + }] + + +]); + +/* +--- + +script: Form.Validator.Inline.js + +name: Form.Validator.Inline + +description: Extends Form.Validator to add inline messages. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Form.Validator + +provides: [Form.Validator.Inline] + +... +*/ + +Form.Validator.Inline = new Class({ + + Extends: Form.Validator, + + options: { + showError: function(errorElement){ + if (errorElement.reveal) errorElement.reveal(); + else errorElement.setStyle('display', 'block'); + }, + hideError: function(errorElement){ + if (errorElement.dissolve) errorElement.dissolve(); + else errorElement.setStyle('display', 'none'); + }, + scrollToErrorsOnSubmit: true, + scrollToErrorsOnBlur: false, + scrollToErrorsOnChange: false, + scrollFxOptions: { + transition: 'quad:out', + offset: { + y: -20 + } + } + }, + + initialize: function(form, options){ + this.parent(form, options); + this.addEvent('onElementValidate', function(isValid, field, className, warn){ + var validator = this.getValidator(className); + if (!isValid && validator.getError(field)){ + if (warn) field.addClass('warning'); + var advice = this.makeAdvice(className, field, validator.getError(field), warn); + this.insertAdvice(advice, field); + this.showAdvice(className, field); + } else { + this.hideAdvice(className, field); + } + }); + }, + + makeAdvice: function(className, field, error, warn){ + var errorMsg = (warn) ? this.warningPrefix : this.errorPrefix; + errorMsg += (this.options.useTitles) ? field.title || error:error; + var cssClass = (warn) ? 'warning-advice' : 'validation-advice'; + var advice = this.getAdvice(className, field); + if (advice){ + advice = advice.set('html', errorMsg); + } else { + advice = new Element('div', { + html: errorMsg, + styles: { display: 'none' }, + id: 'advice-' + className.split(':')[0] + '-' + this.getFieldId(field) + }).addClass(cssClass); + } + field.store('$moo:advice-' + className, advice); + return advice; + }, + + getFieldId : function(field){ + return field.id ? field.id : field.id = 'input_' + field.name; + }, + + showAdvice: function(className, field){ + var advice = this.getAdvice(className, field); + if ( + advice && + !field.retrieve('$moo:' + this.getPropName(className)) && + ( + advice.getStyle('display') == 'none' || + advice.getStyle('visibility') == 'hidden' || + advice.getStyle('opacity') == 0 + ) + ){ + field.store('$moo:' + this.getPropName(className), true); + this.options.showError(advice); + this.fireEvent('showAdvice', [field, advice, className]); + } + }, + + hideAdvice: function(className, field){ + var advice = this.getAdvice(className, field); + if (advice && field.retrieve('$moo:' + this.getPropName(className))){ + field.store('$moo:' + this.getPropName(className), false); + this.options.hideError(advice); + this.fireEvent('hideAdvice', [field, advice, className]); + } + }, + + getPropName: function(className){ + return 'advice' + className; + }, + + resetField: function(field){ + field = document.id(field); + if (!field) return this; + this.parent(field); + field.get('validators').each(function(className){ + this.hideAdvice(className, field); + }, this); + return this; + }, + + getAllAdviceMessages: function(field, force){ + var advice = []; + if (field.hasClass('ignoreValidation') && !force) return advice; + var validators = field.get('validators').some(function(cn){ + var warner = cn.test('^warn-') || field.hasClass('warnOnly'); + if (warner) cn = cn.replace(/^warn-/, ''); + var validator = this.getValidator(cn); + if (!validator) return; + advice.push({ + message: validator.getError(field), + warnOnly: warner, + passed: validator.test(), + validator: validator + }); + }, this); + return advice; + }, + + getAdvice: function(className, field){ + return field.retrieve('$moo:advice-' + className); + }, + + insertAdvice: function(advice, field){ + //Check for error position prop + var props = field.get('validatorProps'); + //Build advice + if (!props.msgPos || !document.id(props.msgPos)){ + if (field.type && field.type.toLowerCase() == 'radio') field.getParent().adopt(advice); + else advice.inject(document.id(field), 'after'); + } else { + document.id(props.msgPos).grab(advice); + } + }, + + validateField: function(field, force, scroll){ + var result = this.parent(field, force); + if (((this.options.scrollToErrorsOnSubmit && scroll == null) || scroll) && !result){ + var failed = document.id(this).getElement('.validation-failed'); + var par = document.id(this).getParent(); + while (par != document.body && par.getScrollSize().y == par.getSize().y){ + par = par.getParent(); + } + var fx = par.retrieve('$moo:fvScroller'); + if (!fx && window.Fx && Fx.Scroll){ + fx = new Fx.Scroll(par, this.options.scrollFxOptions); + par.store('$moo:fvScroller', fx); + } + if (failed){ + if (fx) fx.toElement(failed); + else par.scrollTo(par.getScroll().x, failed.getPosition(par).y - 20); + } + } + return result; + }, + + watchFields: function(fields){ + fields.each(function(el){ + if (this.options.evaluateFieldsOnBlur){ + el.addEvent('blur', this.validationMonitor.pass([el, false, this.options.scrollToErrorsOnBlur], this)); + } + if (this.options.evaluateFieldsOnChange){ + el.addEvent('change', this.validationMonitor.pass([el, true, this.options.scrollToErrorsOnChange], this)); + } + }, this); + } + +}); + +/* +--- + +script: OverText.js + +name: OverText + +description: Shows text over an input that disappears when the user clicks into it. The text remains hidden if the user adds a value. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Options + - Core/Events + - Core/Element.Event + - Class.Binds + - Class.Occlude + - Element.Position + - Element.Shortcuts + +provides: [OverText] + +... +*/ + +var OverText = new Class({ + + Implements: [Options, Events, Class.Occlude], + + Binds: ['reposition', 'assert', 'focus', 'hide'], + + options: {/* + textOverride: null, + onFocus: function(){}, + onTextHide: function(textEl, inputEl){}, + onTextShow: function(textEl, inputEl){}, */ + element: 'label', + labelClass: 'overTxtLabel', + positionOptions: { + position: 'upperLeft', + edge: 'upperLeft', + offset: { + x: 4, + y: 2 + } + }, + poll: false, + pollInterval: 250, + wrap: false + }, + + property: 'OverText', + + initialize: function(element, options){ + element = this.element = document.id(element); + + if (this.occlude()) return this.occluded; + this.setOptions(options); + + this.attach(element); + OverText.instances.push(this); + + if (this.options.poll) this.poll(); + }, + + toElement: function(){ + return this.element; + }, + + attach: function(){ + var element = this.element, + options = this.options, + value = options.textOverride || element.get('alt') || element.get('title'); + + if (!value) return this; + + var text = this.text = new Element(options.element, { + 'class': options.labelClass, + styles: { + lineHeight: 'normal', + position: 'absolute', + cursor: 'text' + }, + html: value, + events: { + click: this.hide.pass(options.element == 'label', this) + } + }).inject(element, 'after'); + + if (options.element == 'label'){ + if (!element.get('id')) element.set('id', 'input_' + String.uniqueID()); + text.set('for', element.get('id')); + } + + if (options.wrap){ + this.textHolder = new Element('div.overTxtWrapper', { + styles: { + lineHeight: 'normal', + position: 'relative' + } + }).grab(text).inject(element, 'before'); + } + + return this.enable(); + }, + + destroy: function(){ + this.element.eliminate(this.property); // Class.Occlude storage + this.disable(); + if (this.text) this.text.destroy(); + if (this.textHolder) this.textHolder.destroy(); + return this; + }, + + disable: function(){ + this.element.removeEvents({ + focus: this.focus, + blur: this.assert, + change: this.assert + }); + window.removeEvent('resize', this.reposition); + this.hide(true, true); + return this; + }, + + enable: function(){ + this.element.addEvents({ + focus: this.focus, + blur: this.assert, + change: this.assert + }); + window.addEvent('resize', this.reposition); + this.reposition(); + return this; + }, + + wrap: function(){ + if (this.options.element == 'label'){ + if (!this.element.get('id')) this.element.set('id', 'input_' + String.uniqueID()); + this.text.set('for', this.element.get('id')); + } + }, + + startPolling: function(){ + this.pollingPaused = false; + return this.poll(); + }, + + poll: function(stop){ + //start immediately + //pause on focus + //resumeon blur + if (this.poller && !stop) return this; + if (stop){ + clearInterval(this.poller); + } else { + this.poller = (function(){ + if (!this.pollingPaused) this.assert(true); + }).periodical(this.options.pollInterval, this); + } + + return this; + }, + + stopPolling: function(){ + this.pollingPaused = true; + return this.poll(true); + }, + + focus: function(){ + if (this.text && (!this.text.isDisplayed() || this.element.get('disabled'))) return this; + return this.hide(); + }, + + hide: function(suppressFocus, force){ + if (this.text && (this.text.isDisplayed() && (!this.element.get('disabled') || force))){ + this.text.hide(); + this.fireEvent('textHide', [this.text, this.element]); + this.pollingPaused = true; + if (!suppressFocus){ + try { + this.element.fireEvent('focus'); + this.element.focus(); + } catch(e){} //IE barfs if you call focus on hidden elements + } + } + return this; + }, + + show: function(){ + if (document.id(this.text) && !this.text.isDisplayed()){ + this.text.show(); + this.reposition(); + this.fireEvent('textShow', [this.text, this.element]); + this.pollingPaused = false; + } + return this; + }, + + test: function(){ + return !this.element.get('value'); + }, + + assert: function(suppressFocus){ + return this[this.test() ? 'show' : 'hide'](suppressFocus); + }, + + reposition: function(){ + this.assert(true); + if (!this.element.isVisible()) return this.stopPolling().hide(); + if (this.text && this.test()){ + this.text.position(Object.merge(this.options.positionOptions, { + relativeTo: this.element + })); + } + return this; + } + +}); + +OverText.instances = []; + +Object.append(OverText, { + + each: function(fn){ + return OverText.instances.each(function(ot, i){ + if (ot.element && ot.text) fn.call(OverText, ot, i); + }); + }, + + update: function(){ + + return OverText.each(function(ot){ + return ot.reposition(); + }); + + }, + + hideAll: function(){ + + return OverText.each(function(ot){ + return ot.hide(true, true); + }); + + }, + + showAll: function(){ + return OverText.each(function(ot){ + return ot.show(); + }); + } + +}); + + +/* +--- + +script: Fx.Elements.js + +name: Fx.Elements + +description: Effect to change any number of CSS properties of any number of Elements. + +license: MIT-style license + +authors: + - Valerio Proietti + +requires: + - Core/Fx.CSS + - MooTools.More + +provides: [Fx.Elements] + +... +*/ + +Fx.Elements = new Class({ + + Extends: Fx.CSS, + + initialize: function(elements, options){ + this.elements = this.subject = $$(elements); + this.parent(options); + }, + + compute: function(from, to, delta){ + var now = {}; + + for (var i in from){ + var iFrom = from[i], iTo = to[i], iNow = now[i] = {}; + for (var p in iFrom) iNow[p] = this.parent(iFrom[p], iTo[p], delta); + } + + return now; + }, + + set: function(now){ + for (var i in now){ + if (!this.elements[i]) continue; + + var iNow = now[i]; + for (var p in iNow) this.render(this.elements[i], p, iNow[p], this.options.unit); + } + + return this; + }, + + start: function(obj){ + if (!this.check(obj)) return this; + var from = {}, to = {}; + + for (var i in obj){ + if (!this.elements[i]) continue; + + var iProps = obj[i], iFrom = from[i] = {}, iTo = to[i] = {}; + + for (var p in iProps){ + var parsed = this.prepare(this.elements[i], p, iProps[p]); + iFrom[p] = parsed.from; + iTo[p] = parsed.to; + } + } + + return this.parent(from, to); + } + +}); + +/* +--- + +script: Fx.Accordion.js + +name: Fx.Accordion + +description: An Fx.Elements extension which allows you to easily create accordion type controls. + +license: MIT-style license + +authors: + - Valerio Proietti + +requires: + - Core/Element.Event + - Fx.Elements + +provides: [Fx.Accordion] + +... +*/ + +Fx.Accordion = new Class({ + + Extends: Fx.Elements, + + options: {/* + onActive: function(toggler, section){}, + onBackground: function(toggler, section){},*/ + fixedHeight: false, + fixedWidth: false, + display: 0, + show: false, + height: true, + width: false, + opacity: true, + alwaysHide: false, + trigger: 'click', + initialDisplayFx: true, + resetHeight: true + }, + + initialize: function(){ + var defined = function(obj){ + return obj != null; + }; + + var params = Array.link(arguments, { + 'container': Type.isElement, //deprecated + 'options': Type.isObject, + 'togglers': defined, + 'elements': defined + }); + this.parent(params.elements, params.options); + + var options = this.options, + togglers = this.togglers = $$(params.togglers); + + this.previous = -1; + this.internalChain = new Chain(); + + if (options.alwaysHide) this.options.link = 'chain'; + + if (options.show || this.options.show === 0){ + options.display = false; + this.previous = options.show; + } + + if (options.start){ + options.display = false; + options.show = false; + } + + var effects = this.effects = {}; + + if (options.opacity) effects.opacity = 'fullOpacity'; + if (options.width) effects.width = options.fixedWidth ? 'fullWidth' : 'offsetWidth'; + if (options.height) effects.height = options.fixedHeight ? 'fullHeight' : 'scrollHeight'; + + for (var i = 0, l = togglers.length; i < l; i++) this.addSection(togglers[i], this.elements[i]); + + this.elements.each(function(el, i){ + if (options.show === i){ + this.fireEvent('active', [togglers[i], el]); + } else { + for (var fx in effects) el.setStyle(fx, 0); + } + }, this); + + if (options.display || options.display === 0 || options.initialDisplayFx === false){ + this.display(options.display, options.initialDisplayFx); + } + + if (options.fixedHeight !== false) options.resetHeight = false; + this.addEvent('complete', this.internalChain.callChain.bind(this.internalChain)); + }, + + addSection: function(toggler, element){ + toggler = document.id(toggler); + element = document.id(element); + this.togglers.include(toggler); + this.elements.include(element); + + var togglers = this.togglers, + options = this.options, + test = togglers.contains(toggler), + idx = togglers.indexOf(toggler), + displayer = this.display.pass(idx, this); + + toggler.store('accordion:display', displayer) + .addEvent(options.trigger, displayer); + + if (options.height) element.setStyles({'padding-top': 0, 'border-top': 'none', 'padding-bottom': 0, 'border-bottom': 'none'}); + if (options.width) element.setStyles({'padding-left': 0, 'border-left': 'none', 'padding-right': 0, 'border-right': 'none'}); + + element.fullOpacity = 1; + if (options.fixedWidth) element.fullWidth = options.fixedWidth; + if (options.fixedHeight) element.fullHeight = options.fixedHeight; + element.setStyle('overflow', 'hidden'); + + if (!test) for (var fx in this.effects){ + element.setStyle(fx, 0); + } + return this; + }, + + removeSection: function(toggler, displayIndex){ + var togglers = this.togglers, + idx = togglers.indexOf(toggler), + element = this.elements[idx]; + + var remover = function(){ + togglers.erase(toggler); + this.elements.erase(element); + this.detach(toggler); + }.bind(this); + + if (this.now == idx || displayIndex != null){ + this.display(displayIndex != null ? displayIndex : (idx - 1 >= 0 ? idx - 1 : 0)).chain(remover); + } else { + remover(); + } + return this; + }, + + detach: function(toggler){ + var remove = function(toggler){ + toggler.removeEvent(this.options.trigger, toggler.retrieve('accordion:display')); + }.bind(this); + + if (!toggler) this.togglers.each(remove); + else remove(toggler); + return this; + }, + + display: function(index, useFx){ + if (!this.check(index, useFx)) return this; + + var obj = {}, + elements = this.elements, + options = this.options, + effects = this.effects; + + if (useFx == null) useFx = true; + if (typeOf(index) == 'element') index = elements.indexOf(index); + if (index == this.current && !options.alwaysHide) return this; + + if (options.resetHeight){ + var prev = elements[this.current]; + if (prev && !this.selfHidden){ + for (var fx in effects) prev.setStyle(fx, prev[effects[fx]]); + } + } + + if ((this.timer && options.link == 'chain') || (index === this.current && !options.alwaysHide)) return this; + + if (this.current != null) this.previous = this.current; + this.current = index; + this.selfHidden = false; + + elements.each(function(el, i){ + obj[i] = {}; + var hide; + if (i != index){ + hide = true; + } else if (options.alwaysHide && ((el.offsetHeight > 0 && options.height) || el.offsetWidth > 0 && options.width)){ + hide = true; + this.selfHidden = true; + } + this.fireEvent(hide ? 'background' : 'active', [this.togglers[i], el]); + for (var fx in effects) obj[i][fx] = hide ? 0 : el[effects[fx]]; + if (!useFx && !hide && options.resetHeight) obj[i].height = 'auto'; + }, this); + + this.internalChain.clearChain(); + this.internalChain.chain(function(){ + if (options.resetHeight && !this.selfHidden){ + var el = elements[index]; + if (el) el.setStyle('height', 'auto'); + } + }.bind(this)); + + return useFx ? this.start(obj) : this.set(obj).internalChain.callChain(); + } + +}); + + + +/* +--- + +script: Fx.Move.js + +name: Fx.Move + +description: Defines Fx.Move, a class that works with Element.Position.js to transition an element from one location to another. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Fx.Morph + - Element.Position + +provides: [Fx.Move] + +... +*/ + +Fx.Move = new Class({ + + Extends: Fx.Morph, + + options: { + relativeTo: document.body, + position: 'center', + edge: false, + offset: {x: 0, y: 0} + }, + + start: function(destination){ + var element = this.element, + topLeft = element.getStyles('top', 'left'); + if (topLeft.top == 'auto' || topLeft.left == 'auto'){ + element.setPosition(element.getPosition(element.getOffsetParent())); + } + return this.parent(element.position(Object.merge({}, this.options, destination, {returnPos: true}))); + } + +}); + +Element.Properties.move = { + + set: function(options){ + this.get('move').cancel().setOptions(options); + return this; + }, + + get: function(){ + var move = this.retrieve('move'); + if (!move){ + move = new Fx.Move(this, {link: 'cancel'}); + this.store('move', move); + } + return move; + } + +}; + +Element.implement({ + + move: function(options){ + this.get('move').start(options); + return this; + } + +}); + +/* +--- + +script: Fx.Scroll.js + +name: Fx.Scroll + +description: Effect to smoothly scroll any element, including the window. + +license: MIT-style license + +authors: + - Valerio Proietti + +requires: + - Core/Fx + - Core/Element.Event + - Core/Element.Dimensions + - MooTools.More + +provides: [Fx.Scroll] + +... +*/ + +(function(){ + +Fx.Scroll = new Class({ + + Extends: Fx, + + options: { + offset: {x: 0, y: 0}, + wheelStops: true + }, + + initialize: function(element, options){ + this.element = this.subject = document.id(element); + this.parent(options); + + if (typeOf(this.element) != 'element') this.element = document.id(this.element.getDocument().body); + + if (this.options.wheelStops){ + var stopper = this.element, + cancel = this.cancel.pass(false, this); + this.addEvent('start', function(){ + stopper.addEvent('mousewheel', cancel); + }, true); + this.addEvent('complete', function(){ + stopper.removeEvent('mousewheel', cancel); + }, true); + } + }, + + set: function(){ + var now = Array.flatten(arguments); + this.element.scrollTo(now[0], now[1]); + return this; + }, + + compute: function(from, to, delta){ + return [0, 1].map(function(i){ + return Fx.compute(from[i], to[i], delta); + }); + }, + + start: function(x, y){ + if (!this.check(x, y)) return this; + var scroll = this.element.getScroll(); + return this.parent([scroll.x, scroll.y], [x, y]); + }, + + calculateScroll: function(x, y){ + var element = this.element, + scrollSize = element.getScrollSize(), + scroll = element.getScroll(), + size = element.getSize(), + offset = this.options.offset, + values = {x: x, y: y}; + + for (var z in values){ + if (!values[z] && values[z] !== 0) values[z] = scroll[z]; + if (typeOf(values[z]) != 'number') values[z] = scrollSize[z] - size[z]; + values[z] += offset[z]; + } + + return [values.x, values.y]; + }, + + toTop: function(){ + return this.start.apply(this, this.calculateScroll(false, 0)); + }, + + toLeft: function(){ + return this.start.apply(this, this.calculateScroll(0, false)); + }, + + toRight: function(){ + return this.start.apply(this, this.calculateScroll('right', false)); + }, + + toBottom: function(){ + return this.start.apply(this, this.calculateScroll(false, 'bottom')); + }, + + toElement: function(el, axes){ + axes = axes ? Array.from(axes) : ['x', 'y']; + var scroll = isBody(this.element) ? {x: 0, y: 0} : this.element.getScroll(); + var position = Object.map(document.id(el).getPosition(this.element), function(value, axis){ + return axes.contains(axis) ? value + scroll[axis] : false; + }); + return this.start.apply(this, this.calculateScroll(position.x, position.y)); + }, + + toElementEdge: function(el, axes, offset){ + axes = axes ? Array.from(axes) : ['x', 'y']; + el = document.id(el); + var to = {}, + position = el.getPosition(this.element), + size = el.getSize(), + scroll = this.element.getScroll(), + containerSize = this.element.getSize(), + edge = { + x: position.x + size.x, + y: position.y + size.y + }; + + ['x', 'y'].each(function(axis){ + if (axes.contains(axis)){ + if (edge[axis] > scroll[axis] + containerSize[axis]) to[axis] = edge[axis] - containerSize[axis]; + if (position[axis] < scroll[axis]) to[axis] = position[axis]; + } + if (to[axis] == null) to[axis] = scroll[axis]; + if (offset && offset[axis]) to[axis] = to[axis] + offset[axis]; + }, this); + + if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y); + return this; + }, + + toElementCenter: function(el, axes, offset){ + axes = axes ? Array.from(axes) : ['x', 'y']; + el = document.id(el); + var to = {}, + position = el.getPosition(this.element), + size = el.getSize(), + scroll = this.element.getScroll(), + containerSize = this.element.getSize(); + + ['x', 'y'].each(function(axis){ + if (axes.contains(axis)){ + to[axis] = position[axis] - (containerSize[axis] - size[axis]) / 2; + } + if (to[axis] == null) to[axis] = scroll[axis]; + if (offset && offset[axis]) to[axis] = to[axis] + offset[axis]; + }, this); + + if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y); + return this; + } + +}); + + + +function isBody(element){ + return (/^(?:body|html)$/i).test(element.tagName); +} + +})(); + +/* +--- + +script: Fx.Slide.js + +name: Fx.Slide + +description: Effect to slide an element in and out of view. + +license: MIT-style license + +authors: + - Valerio Proietti + +requires: + - Core/Fx + - Core/Element.Style + - MooTools.More + +provides: [Fx.Slide] + +... +*/ + +Fx.Slide = new Class({ + + Extends: Fx, + + options: { + mode: 'vertical', + wrapper: false, + hideOverflow: true, + resetHeight: false + }, + + initialize: function(element, options){ + element = this.element = this.subject = document.id(element); + this.parent(options); + options = this.options; + + var wrapper = element.retrieve('wrapper'), + styles = element.getStyles('margin', 'position', 'overflow'); + + if (options.hideOverflow) styles = Object.append(styles, {overflow: 'hidden'}); + if (options.wrapper) wrapper = document.id(options.wrapper).setStyles(styles); + + if (!wrapper) wrapper = new Element('div', { + styles: styles + }).wraps(element); + + element.store('wrapper', wrapper).setStyle('margin', 0); + if (element.getStyle('overflow') == 'visible') element.setStyle('overflow', 'hidden'); + + this.now = []; + this.open = true; + this.wrapper = wrapper; + + this.addEvent('complete', function(){ + this.open = (wrapper['offset' + this.layout.capitalize()] != 0); + if (this.open && this.options.resetHeight) wrapper.setStyle('height', ''); + }, true); + }, + + vertical: function(){ + this.margin = 'margin-top'; + this.layout = 'height'; + this.offset = this.element.offsetHeight; + }, + + horizontal: function(){ + this.margin = 'margin-left'; + this.layout = 'width'; + this.offset = this.element.offsetWidth; + }, + + set: function(now){ + this.element.setStyle(this.margin, now[0]); + this.wrapper.setStyle(this.layout, now[1]); + return this; + }, + + compute: function(from, to, delta){ + return [0, 1].map(function(i){ + return Fx.compute(from[i], to[i], delta); + }); + }, + + start: function(how, mode){ + if (!this.check(how, mode)) return this; + this[mode || this.options.mode](); + + var margin = this.element.getStyle(this.margin).toInt(), + layout = this.wrapper.getStyle(this.layout).toInt(), + caseIn = [[margin, layout], [0, this.offset]], + caseOut = [[margin, layout], [-this.offset, 0]], + start; + + switch (how){ + case 'in': start = caseIn; break; + case 'out': start = caseOut; break; + case 'toggle': start = (layout == 0) ? caseIn : caseOut; + } + return this.parent(start[0], start[1]); + }, + + slideIn: function(mode){ + return this.start('in', mode); + }, + + slideOut: function(mode){ + return this.start('out', mode); + }, + + hide: function(mode){ + this[mode || this.options.mode](); + this.open = false; + return this.set([-this.offset, 0]); + }, + + show: function(mode){ + this[mode || this.options.mode](); + this.open = true; + return this.set([0, this.offset]); + }, + + toggle: function(mode){ + return this.start('toggle', mode); + } + +}); + +Element.Properties.slide = { + + set: function(options){ + this.get('slide').cancel().setOptions(options); + return this; + }, + + get: function(){ + var slide = this.retrieve('slide'); + if (!slide){ + slide = new Fx.Slide(this, {link: 'cancel'}); + this.store('slide', slide); + } + return slide; + } + +}; + +Element.implement({ + + slide: function(how, mode){ + how = how || 'toggle'; + var slide = this.get('slide'), toggle; + switch (how){ + case 'hide': slide.hide(mode); break; + case 'show': slide.show(mode); break; + case 'toggle': + var flag = this.retrieve('slide:flag', slide.open); + slide[flag ? 'slideOut' : 'slideIn'](mode); + this.store('slide:flag', !flag); + toggle = true; + break; + default: slide.start(how, mode); + } + if (!toggle) this.eliminate('slide:flag'); + return this; + } + +}); + +/* +--- + +script: Fx.SmoothScroll.js + +name: Fx.SmoothScroll + +description: Class for creating a smooth scrolling effect to all internal links on the page. + +license: MIT-style license + +authors: + - Valerio Proietti + +requires: + - Core/Slick.Finder + - Fx.Scroll + +provides: [Fx.SmoothScroll] + +... +*/ + +Fx.SmoothScroll = new Class({ + + Extends: Fx.Scroll, + + options: { + axes: ['x', 'y'] + }, + + initialize: function(options, context){ + context = context || document; + this.doc = context.getDocument(); + this.parent(this.doc, options); + + var win = context.getWindow(), + location = win.location.href.match(/^[^#]*/)[0] + '#', + links = $$(this.options.links || this.doc.links); + + links.each(function(link){ + if (link.href.indexOf(location) != 0) return; + var anchor = link.href.substr(location.length); + if (anchor) this.useLink(link, anchor); + }, this); + + this.addEvent('complete', function(){ + win.location.hash = this.anchor; + this.element.scrollTo(this.to[0], this.to[1]); + }, true); + }, + + useLink: function(link, anchor){ + + link.addEvent('click', function(event){ + var el = document.id(anchor) || this.doc.getElement('a[name=' + anchor + ']'); + if (!el) return; + + event.preventDefault(); + this.toElement(el, this.options.axes).chain(function(){ + this.fireEvent('scrolledTo', [link, el]); + }.bind(this)); + + this.anchor = anchor; + + }.bind(this)); + + return this; + } +}); + +/* +--- + +script: Fx.Sort.js + +name: Fx.Sort + +description: Defines Fx.Sort, a class that reorders lists with a transition. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Element.Dimensions + - Fx.Elements + - Element.Measure + +provides: [Fx.Sort] + +... +*/ + +Fx.Sort = new Class({ + + Extends: Fx.Elements, + + options: { + mode: 'vertical' + }, + + initialize: function(elements, options){ + this.parent(elements, options); + this.elements.each(function(el){ + if (el.getStyle('position') == 'static') el.setStyle('position', 'relative'); + }); + this.setDefaultOrder(); + }, + + setDefaultOrder: function(){ + this.currentOrder = this.elements.map(function(el, index){ + return index; + }); + }, + + sort: function(){ + if (!this.check(arguments)) return this; + var newOrder = Array.flatten(arguments); + + var top = 0, + left = 0, + next = {}, + zero = {}, + vert = this.options.mode == 'vertical'; + + var current = this.elements.map(function(el, index){ + var size = el.getComputedSize({styles: ['border', 'padding', 'margin']}); + var val; + if (vert){ + val = { + top: top, + margin: size['margin-top'], + height: size.totalHeight + }; + top += val.height - size['margin-top']; + } else { + val = { + left: left, + margin: size['margin-left'], + width: size.totalWidth + }; + left += val.width; + } + var plane = vert ? 'top' : 'left'; + zero[index] = {}; + var start = el.getStyle(plane).toInt(); + zero[index][plane] = start || 0; + return val; + }, this); + + this.set(zero); + newOrder = newOrder.map(function(i){ return i.toInt(); }); + if (newOrder.length != this.elements.length){ + this.currentOrder.each(function(index){ + if (!newOrder.contains(index)) newOrder.push(index); + }); + if (newOrder.length > this.elements.length) + newOrder.splice(this.elements.length-1, newOrder.length - this.elements.length); + } + var margin = 0; + top = left = 0; + newOrder.each(function(item){ + var newPos = {}; + if (vert){ + newPos.top = top - current[item].top - margin; + top += current[item].height; + } else { + newPos.left = left - current[item].left; + left += current[item].width; + } + margin = margin + current[item].margin; + next[item]=newPos; + }, this); + var mapped = {}; + Array.clone(newOrder).sort().each(function(index){ + mapped[index] = next[index]; + }); + this.start(mapped); + this.currentOrder = newOrder; + + return this; + }, + + rearrangeDOM: function(newOrder){ + newOrder = newOrder || this.currentOrder; + var parent = this.elements[0].getParent(); + var rearranged = []; + this.elements.setStyle('opacity', 0); + //move each element and store the new default order + newOrder.each(function(index){ + rearranged.push(this.elements[index].inject(parent).setStyles({ + top: 0, + left: 0 + })); + }, this); + this.elements.setStyle('opacity', 1); + this.elements = $$(rearranged); + this.setDefaultOrder(); + return this; + }, + + getDefaultOrder: function(){ + return this.elements.map(function(el, index){ + return index; + }); + }, + + getCurrentOrder: function(){ + return this.currentOrder; + }, + + forward: function(){ + return this.sort(this.getDefaultOrder()); + }, + + backward: function(){ + return this.sort(this.getDefaultOrder().reverse()); + }, + + reverse: function(){ + return this.sort(this.currentOrder.reverse()); + }, + + sortByElements: function(elements){ + return this.sort(elements.map(function(el){ + return this.elements.indexOf(el); + }, this)); + }, + + swap: function(one, two){ + if (typeOf(one) == 'element') one = this.elements.indexOf(one); + if (typeOf(two) == 'element') two = this.elements.indexOf(two); + + var newOrder = Array.clone(this.currentOrder); + newOrder[this.currentOrder.indexOf(one)] = two; + newOrder[this.currentOrder.indexOf(two)] = one; + + return this.sort(newOrder); + } + +}); + +/* +--- + +script: Keyboard.js + +name: Keyboard + +description: KeyboardEvents used to intercept events on a class for keyboard and format modifiers in a specific order so as to make alt+shift+c the same as shift+alt+c. + +license: MIT-style license + +authors: + - Perrin Westrich + - Aaron Newton + - Scott Kyle + +requires: + - Core/Events + - Core/Options + - Core/Element.Event + - Element.Event.Pseudos.Keys + +provides: [Keyboard] + +... +*/ + +(function(){ + + var Keyboard = this.Keyboard = new Class({ + + Extends: Events, + + Implements: [Options], + + options: {/* + onActivate: function(){}, + onDeactivate: function(){},*/ + defaultEventType: 'keydown', + active: false, + manager: null, + events: {}, + nonParsedEvents: ['activate', 'deactivate', 'onactivate', 'ondeactivate', 'changed', 'onchanged'] + }, + + initialize: function(options){ + if (options && options.manager){ + this._manager = options.manager; + delete options.manager; + } + this.setOptions(options); + this._setup(); + }, + + addEvent: function(type, fn, internal){ + return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn, internal); + }, + + removeEvent: function(type, fn){ + return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn); + }, + + toggleActive: function(){ + return this[this.isActive() ? 'deactivate' : 'activate'](); + }, + + activate: function(instance){ + if (instance){ + if (instance.isActive()) return this; + //if we're stealing focus, store the last keyboard to have it so the relinquish command works + if (this._activeKB && instance != this._activeKB){ + this.previous = this._activeKB; + this.previous.fireEvent('deactivate'); + } + //if we're enabling a child, assign it so that events are now passed to it + this._activeKB = instance.fireEvent('activate'); + Keyboard.manager.fireEvent('changed'); + } else if (this._manager){ + //else we're enabling ourselves, we must ask our parent to do it for us + this._manager.activate(this); + } + return this; + }, + + isActive: function(){ + return this._manager ? (this._manager._activeKB == this) : (Keyboard.manager == this); + }, + + deactivate: function(instance){ + if (instance){ + if (instance === this._activeKB){ + this._activeKB = null; + instance.fireEvent('deactivate'); + Keyboard.manager.fireEvent('changed'); + } + } else if (this._manager){ + this._manager.deactivate(this); + } + return this; + }, + + relinquish: function(){ + if (this.isActive() && this._manager && this._manager.previous) this._manager.activate(this._manager.previous); + else this.deactivate(); + return this; + }, + + //management logic + manage: function(instance){ + if (instance._manager) instance._manager.drop(instance); + this._instances.push(instance); + instance._manager = this; + if (!this._activeKB) this.activate(instance); + return this; + }, + + drop: function(instance){ + instance.relinquish(); + this._instances.erase(instance); + if (this._activeKB == instance){ + if (this.previous && this._instances.contains(this.previous)) this.activate(this.previous); + else this._activeKB = this._instances[0]; + } + return this; + }, + + trace: function(){ + Keyboard.trace(this); + }, + + each: function(fn){ + Keyboard.each(this, fn); + }, + + /* + PRIVATE METHODS + */ + + _instances: [], + + _disable: function(instance){ + if (this._activeKB == instance) this._activeKB = null; + }, + + _setup: function(){ + this.addEvents(this.options.events); + //if this is the root manager, nothing manages it + if (Keyboard.manager && !this._manager) Keyboard.manager.manage(this); + if (this.options.active) this.activate(); + else this.relinquish(); + }, + + _handle: function(event, type){ + //Keyboard.stop(event) prevents key propagation + if (event.preventKeyboardPropagation) return; + + var bubbles = !!this._manager; + if (bubbles && this._activeKB){ + this._activeKB._handle(event, type); + if (event.preventKeyboardPropagation) return; + } + this.fireEvent(type, event); + + if (!bubbles && this._activeKB) this._activeKB._handle(event, type); + } + + }); + + var parsed = {}; + var modifiers = ['shift', 'control', 'alt', 'meta']; + var regex = /^(?:shift|control|ctrl|alt|meta)$/; + + Keyboard.parse = function(type, eventType, ignore){ + if (ignore && ignore.contains(type.toLowerCase())) return type; + + type = type.toLowerCase().replace(/^(keyup|keydown):/, function($0, $1){ + eventType = $1; + return ''; + }); + + if (!parsed[type]){ + if (type != '+'){ + var key, mods = {}; + type.split('+').each(function(part){ + if (regex.test(part)) mods[part] = true; + else key = part; + }); + + mods.control = mods.control || mods.ctrl; // allow both control and ctrl + + var keys = []; + modifiers.each(function(mod){ + if (mods[mod]) keys.push(mod); + }); + + if (key) keys.push(key); + parsed[type] = keys.join('+'); + } else { + parsed[type] = type; + } + } + + return eventType + ':keys(' + parsed[type] + ')'; + }; + + Keyboard.each = function(keyboard, fn){ + var current = keyboard || Keyboard.manager; + while (current){ + fn(current); + current = current._activeKB; + } + }; + + Keyboard.stop = function(event){ + event.preventKeyboardPropagation = true; + }; + + Keyboard.manager = new Keyboard({ + active: true + }); + + Keyboard.trace = function(keyboard){ + keyboard = keyboard || Keyboard.manager; + var hasConsole = window.console && console.log; + if (hasConsole) console.log('the following items have focus: '); + Keyboard.each(keyboard, function(current){ + if (hasConsole) console.log(document.id(current.widget) || current.wiget || current); + }); + }; + + var handler = function(event){ + var keys = []; + modifiers.each(function(mod){ + if (event[mod]) keys.push(mod); + }); + + if (!regex.test(event.key)) keys.push(event.key); + Keyboard.manager._handle(event, event.type + ':keys(' + keys.join('+') + ')'); + }; + + document.addEvents({ + 'keyup': handler, + 'keydown': handler + }); + +})(); + +/* +--- + +script: Keyboard.Extras.js + +name: Keyboard.Extras + +description: Enhances Keyboard by adding the ability to name and describe keyboard shortcuts, and the ability to grab shortcuts by name and bind the shortcut to different keys. + +license: MIT-style license + +authors: + - Perrin Westrich + +requires: + - Keyboard + - MooTools.More + +provides: [Keyboard.Extras] + +... +*/ +Keyboard.prototype.options.nonParsedEvents.combine(['rebound', 'onrebound']); + +Keyboard.implement({ + + /* + shortcut should be in the format of: + { + 'keys': 'shift+s', // the default to add as an event. + 'description': 'blah blah blah', // a brief description of the functionality. + 'handler': function(){} // the event handler to run when keys are pressed. + } + */ + addShortcut: function(name, shortcut){ + this._shortcuts = this._shortcuts || []; + this._shortcutIndex = this._shortcutIndex || {}; + + shortcut.getKeyboard = Function.from(this); + shortcut.name = name; + this._shortcutIndex[name] = shortcut; + this._shortcuts.push(shortcut); + if (shortcut.keys) this.addEvent(shortcut.keys, shortcut.handler); + return this; + }, + + addShortcuts: function(obj){ + for (var name in obj) this.addShortcut(name, obj[name]); + return this; + }, + + removeShortcut: function(name){ + var shortcut = this.getShortcut(name); + if (shortcut && shortcut.keys){ + this.removeEvent(shortcut.keys, shortcut.handler); + delete this._shortcutIndex[name]; + this._shortcuts.erase(shortcut); + } + return this; + }, + + removeShortcuts: function(names){ + names.each(this.removeShortcut, this); + return this; + }, + + getShortcuts: function(){ + return this._shortcuts || []; + }, + + getShortcut: function(name){ + return (this._shortcutIndex || {})[name]; + } + +}); + +Keyboard.rebind = function(newKeys, shortcuts){ + Array.from(shortcuts).each(function(shortcut){ + shortcut.getKeyboard().removeEvent(shortcut.keys, shortcut.handler); + shortcut.getKeyboard().addEvent(newKeys, shortcut.handler); + shortcut.keys = newKeys; + shortcut.getKeyboard().fireEvent('rebound'); + }); +}; + + +Keyboard.getActiveShortcuts = function(keyboard){ + var activeKBS = [], activeSCS = []; + Keyboard.each(keyboard, [].push.bind(activeKBS)); + activeKBS.each(function(kb){ activeSCS.extend(kb.getShortcuts()); }); + return activeSCS; +}; + +Keyboard.getShortcut = function(name, keyboard, opts){ + opts = opts || {}; + var shortcuts = opts.many ? [] : null, + set = opts.many ? function(kb){ + var shortcut = kb.getShortcut(name); + if (shortcut) shortcuts.push(shortcut); + } : function(kb){ + if (!shortcuts) shortcuts = kb.getShortcut(name); + }; + Keyboard.each(keyboard, set); + return shortcuts; +}; + +Keyboard.getShortcuts = function(name, keyboard){ + return Keyboard.getShortcut(name, keyboard, { many: true }); +}; + +/* +--- + +script: HtmlTable.js + +name: HtmlTable + +description: Builds table elements with methods to add rows. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Options + - Core/Events + - Class.Occlude + +provides: [HtmlTable] + +... +*/ + +var HtmlTable = new Class({ + + Implements: [Options, Events, Class.Occlude], + + options: { + properties: { + cellpadding: 0, + cellspacing: 0, + border: 0 + }, + rows: [], + headers: [], + footers: [] + }, + + property: 'HtmlTable', + + initialize: function(){ + var params = Array.link(arguments, {options: Type.isObject, table: Type.isElement, id: Type.isString}); + this.setOptions(params.options); + if (!params.table && params.id) params.table = document.id(params.id); + this.element = params.table || new Element('table', this.options.properties); + if (this.occlude()) return this.occluded; + this.build(); + }, + + build: function(){ + this.element.store('HtmlTable', this); + + this.body = document.id(this.element.tBodies[0]) || new Element('tbody').inject(this.element); + $$(this.body.rows); + + if (this.options.headers.length) this.setHeaders(this.options.headers); + else this.thead = document.id(this.element.tHead); + + if (this.thead) this.head = this.getHead(); + if (this.options.footers.length) this.setFooters(this.options.footers); + + this.tfoot = document.id(this.element.tFoot); + if (this.tfoot) this.foot = document.id(this.tfoot.rows[0]); + + this.options.rows.each(function(row){ + this.push(row); + }, this); + }, + + toElement: function(){ + return this.element; + }, + + empty: function(){ + this.body.empty(); + return this; + }, + + set: function(what, items){ + var target = (what == 'headers') ? 'tHead' : 'tFoot', + lower = target.toLowerCase(); + + this[lower] = (document.id(this.element[target]) || new Element(lower).inject(this.element, 'top')).empty(); + var data = this.push(items, {}, this[lower], what == 'headers' ? 'th' : 'td'); + + if (what == 'headers') this.head = this.getHead(); + else this.foot = this.getHead(); + + return data; + }, + + getHead: function(){ + var rows = this.thead.rows; + return rows.length > 1 ? $$(rows) : rows.length ? document.id(rows[0]) : false; + }, + + setHeaders: function(headers){ + this.set('headers', headers); + return this; + }, + + setFooters: function(footers){ + this.set('footers', footers); + return this; + }, + + update: function(tr, row, tag){ + var tds = tr.getChildren(tag || 'td'), last = tds.length - 1; + + row.each(function(data, index){ + var td = tds[index] || new Element(tag || 'td').inject(tr), + content = ((data && Object.prototype.hasOwnProperty.call(data, 'content')) ? data.content : '') || data, + type = typeOf(content); + + if (data && Object.prototype.hasOwnProperty.call(data, 'properties')) td.set(data.properties); + if (/(element(s?)|array|collection)/.test(type)) td.empty().adopt(content); + else td.set('html', content); + + if (index > last) tds.push(td); + else tds[index] = td; + }); + + return { + tr: tr, + tds: tds + }; + }, + + push: function(row, rowProperties, target, tag, where){ + if (typeOf(row) == 'element' && row.get('tag') == 'tr'){ + row.inject(target || this.body, where); + return { + tr: row, + tds: row.getChildren('td') + }; + } + return this.update(new Element('tr', rowProperties).inject(target || this.body, where), row, tag); + }, + + pushMany: function(rows, rowProperties, target, tag, where){ + return rows.map(function(row){ + return this.push(row, rowProperties, target, tag, where); + }, this); + } + +}); + + +['adopt', 'inject', 'wraps', 'grab', 'replaces', 'dispose'].each(function(method){ + HtmlTable.implement(method, function(){ + this.element[method].apply(this.element, arguments); + return this; + }); +}); + + + +/* +--- + +script: HtmlTable.Select.js + +name: HtmlTable.Select + +description: Builds a stripy, sortable table with methods to add rows. Rows can be selected with the mouse or keyboard navigation. + +license: MIT-style license + +authors: + - Harald Kirschner + - Aaron Newton + +requires: + - Keyboard + - Keyboard.Extras + - HtmlTable + - Class.refactor + - Element.Delegation.Pseudo + - Element.Shortcuts + +provides: [HtmlTable.Select] + +... +*/ + +HtmlTable = Class.refactor(HtmlTable, { + + options: { + /*onRowFocus: function(){}, + onRowUnfocus: function(){},*/ + useKeyboard: true, + classRowSelected: 'table-tr-selected', + classRowHovered: 'table-tr-hovered', + classSelectable: 'table-selectable', + shiftForMultiSelect: true, + allowMultiSelect: true, + selectable: false, + selectHiddenRows: false + }, + + initialize: function(){ + this.previous.apply(this, arguments); + if (this.occluded) return this.occluded; + + this.selectedRows = new Elements(); + + if (!this.bound) this.bound = {}; + this.bound.mouseleave = this.mouseleave.bind(this); + this.bound.clickRow = this.clickRow.bind(this); + this.bound.activateKeyboard = function(){ + if (this.keyboard && this.selectEnabled) this.keyboard.activate(); + }.bind(this); + + if (this.options.selectable) this.enableSelect(); + }, + + empty: function(){ + if (this.body.rows.length) this.selectNone(); + return this.previous(); + }, + + enableSelect: function(){ + this.selectEnabled = true; + this.attachSelects(); + this.element.addClass(this.options.classSelectable); + return this; + }, + + disableSelect: function(){ + this.selectEnabled = false; + this.attachSelects(false); + this.element.removeClass(this.options.classSelectable); + return this; + }, + + push: function(){ + var ret = this.previous.apply(this, arguments); + this.updateSelects(); + return ret; + }, + + toggleRow: function(row){ + return this[(this.isSelected(row) ? 'de' : '') + 'selectRow'](row); + }, + + selectRow: function(row, _nocheck){ + //private variable _nocheck: boolean whether or not to confirm the row is in the table body + //added here for optimization when selecting ranges + if (this.isSelected(row) || (!_nocheck && !this.body.getChildren().contains(row))) return; + if (!this.options.allowMultiSelect) this.selectNone(); + + if (!this.isSelected(row)){ + this.selectedRows.push(row); + row.addClass(this.options.classRowSelected); + this.fireEvent('rowFocus', [row, this.selectedRows]); + this.fireEvent('stateChanged'); + } + + this.focused = row; + document.clearSelection(); + + return this; + }, + + isSelected: function(row){ + return this.selectedRows.contains(row); + }, + + getSelected: function(){ + return this.selectedRows; + }, + + serialize: function(){ + var previousSerialization = this.previous.apply(this, arguments) || {}; + if (this.options.selectable){ + previousSerialization.selectedRows = this.selectedRows.map(function(row){ + return Array.indexOf(this.body.rows, row); + }.bind(this)); + } + return previousSerialization; + }, + + restore: function(tableState){ + if(this.options.selectable && tableState.selectedRows){ + tableState.selectedRows.each(function(index){ + this.selectRow(this.body.rows[index]); + }.bind(this)); + } + this.previous.apply(this, arguments); + }, + + deselectRow: function(row, _nocheck){ + if (!this.isSelected(row) || (!_nocheck && !this.body.getChildren().contains(row))) return; + + this.selectedRows = new Elements(Array.from(this.selectedRows).erase(row)); + row.removeClass(this.options.classRowSelected); + this.fireEvent('rowUnfocus', [row, this.selectedRows]); + this.fireEvent('stateChanged'); + return this; + }, + + selectAll: function(selectNone){ + if (!selectNone && !this.options.allowMultiSelect) return; + this.selectRange(0, this.body.rows.length, selectNone); + return this; + }, + + selectNone: function(){ + return this.selectAll(true); + }, + + selectRange: function(startRow, endRow, _deselect){ + if (!this.options.allowMultiSelect && !_deselect) return; + var method = _deselect ? 'deselectRow' : 'selectRow', + rows = Array.clone(this.body.rows); + + if (typeOf(startRow) == 'element') startRow = rows.indexOf(startRow); + if (typeOf(endRow) == 'element') endRow = rows.indexOf(endRow); + endRow = endRow < rows.length - 1 ? endRow : rows.length - 1; + + if (endRow < startRow){ + var tmp = startRow; + startRow = endRow; + endRow = tmp; + } + + for (var i = startRow; i <= endRow; i++){ + if (this.options.selectHiddenRows || rows[i].isDisplayed()) this[method](rows[i], true); + } + + return this; + }, + + deselectRange: function(startRow, endRow){ + this.selectRange(startRow, endRow, true); + }, + +/* + Private methods: +*/ + + enterRow: function(row){ + if (this.hovered) this.hovered = this.leaveRow(this.hovered); + this.hovered = row.addClass(this.options.classRowHovered); + }, + + leaveRow: function(row){ + row.removeClass(this.options.classRowHovered); + }, + + updateSelects: function(){ + Array.each(this.body.rows, function(row){ + var binders = row.retrieve('binders'); + if (!binders && !this.selectEnabled) return; + if (!binders){ + binders = { + mouseenter: this.enterRow.pass([row], this), + mouseleave: this.leaveRow.pass([row], this) + }; + row.store('binders', binders); + } + if (this.selectEnabled) row.addEvents(binders); + else row.removeEvents(binders); + }, this); + }, + + shiftFocus: function(offset, event){ + if (!this.focused) return this.selectRow(this.body.rows[0], event); + var to = this.getRowByOffset(offset, this.options.selectHiddenRows); + if (to === null || this.focused == this.body.rows[to]) return this; + this.toggleRow(this.body.rows[to], event); + }, + + clickRow: function(event, row){ + var selecting = (event.shift || event.meta || event.control) && this.options.shiftForMultiSelect; + if (!selecting && !(event.rightClick && this.isSelected(row) && this.options.allowMultiSelect)) this.selectNone(); + + if (event.rightClick) this.selectRow(row); + else this.toggleRow(row); + + if (event.shift){ + this.selectRange(this.rangeStart || this.body.rows[0], row, this.rangeStart ? !this.isSelected(row) : true); + this.focused = row; + } + this.rangeStart = row; + }, + + getRowByOffset: function(offset, includeHiddenRows){ + if (!this.focused) return 0; + var index = Array.indexOf(this.body.rows, this.focused); + if ((index == 0 && offset < 0) || (index == this.body.rows.length -1 && offset > 0)) return null; + if (includeHiddenRows){ + index += offset; + } else { + var limit = 0, + count = 0; + if (offset > 0){ + while (count < offset && index < this.body.rows.length -1){ + if (this.body.rows[++index].isDisplayed()) count++; + } + } else { + while (count > offset && index > 0){ + if (this.body.rows[--index].isDisplayed()) count--; + } + } + } + return index; + }, + + attachSelects: function(attach){ + attach = attach != null ? attach : true; + + var method = attach ? 'addEvents' : 'removeEvents'; + this.element[method]({ + mouseleave: this.bound.mouseleave, + click: this.bound.activateKeyboard + }); + + this.body[method]({ + 'click:relay(tr)': this.bound.clickRow, + 'contextmenu:relay(tr)': this.bound.clickRow + }); + + if (this.options.useKeyboard || this.keyboard){ + if (!this.keyboard) this.keyboard = new Keyboard(); + if (!this.selectKeysDefined){ + this.selectKeysDefined = true; + var timer, held; + + var move = function(offset){ + var mover = function(e){ + clearTimeout(timer); + e.preventDefault(); + var to = this.body.rows[this.getRowByOffset(offset, this.options.selectHiddenRows)]; + if (e.shift && to && this.isSelected(to)){ + this.deselectRow(this.focused); + this.focused = to; + } else { + if (to && (!this.options.allowMultiSelect || !e.shift)){ + this.selectNone(); + } + this.shiftFocus(offset, e); + } + + if (held){ + timer = mover.delay(100, this, e); + } else { + timer = (function(){ + held = true; + mover(e); + }).delay(400); + } + }.bind(this); + return mover; + }.bind(this); + + var clear = function(){ + clearTimeout(timer); + held = false; + }; + + this.keyboard.addEvents({ + 'keydown:shift+up': move(-1), + 'keydown:shift+down': move(1), + 'keyup:shift+up': clear, + 'keyup:shift+down': clear, + 'keyup:up': clear, + 'keyup:down': clear + }); + + var shiftHint = ''; + if (this.options.allowMultiSelect && this.options.shiftForMultiSelect && this.options.useKeyboard){ + shiftHint = " (Shift multi-selects)."; + } + + this.keyboard.addShortcuts({ + 'Select Previous Row': { + keys: 'up', + shortcut: 'up arrow', + handler: move(-1), + description: 'Select the previous row in the table.' + shiftHint + }, + 'Select Next Row': { + keys: 'down', + shortcut: 'down arrow', + handler: move(1), + description: 'Select the next row in the table.' + shiftHint + } + }); + + } + this.keyboard[attach ? 'activate' : 'deactivate'](); + } + this.updateSelects(); + }, + + mouseleave: function(){ + if (this.hovered) this.leaveRow(this.hovered); + } + +}); + +/* +--- + +script: HtmlTable.Sort.js + +name: HtmlTable.Sort + +description: Builds a stripy, sortable table with methods to add rows. + +license: MIT-style license + +authors: + - Harald Kirschner + - Aaron Newton + - Jacob Thornton + +requires: + - Core/Hash + - HtmlTable + - Class.refactor + - Element.Delegation.Pseudo + - String.Extras + - Date + +provides: [HtmlTable.Sort] + +... +*/ +(function(){ + +var readOnlyNess = document.createElement('table'); +try { + readOnlyNess.innerHTML = '<tr><td></td></tr>'; + readOnlyNess = readOnlyNess.childNodes.length === 0; +} catch (e){ + readOnlyNess = true; +} + +HtmlTable = Class.refactor(HtmlTable, { + + options: {/* + onSort: function(){}, */ + sortIndex: 0, + sortReverse: false, + parsers: [], + defaultParser: 'string', + classSortable: 'table-sortable', + classHeadSort: 'table-th-sort', + classHeadSortRev: 'table-th-sort-rev', + classNoSort: 'table-th-nosort', + classGroupHead: 'table-tr-group-head', + classGroup: 'table-tr-group', + classCellSort: 'table-td-sort', + classSortSpan: 'table-th-sort-span', + sortable: false, + thSelector: 'th' + }, + + initialize: function (){ + this.previous.apply(this, arguments); + if (this.occluded) return this.occluded; + this.sorted = {index: null, dir: 1}; + if (!this.bound) this.bound = {}; + this.bound.headClick = this.headClick.bind(this); + this.sortSpans = new Elements(); + if (this.options.sortable){ + this.enableSort(); + if (this.options.sortIndex != null) this.sort(this.options.sortIndex, this.options.sortReverse); + } + }, + + attachSorts: function(attach){ + this.detachSorts(); + if (attach !== false) this.element.addEvent('click:relay(' + this.options.thSelector + ')', this.bound.headClick); + }, + + detachSorts: function(){ + this.element.removeEvents('click:relay(' + this.options.thSelector + ')'); + }, + + setHeaders: function(){ + this.previous.apply(this, arguments); + if (this.sortable) this.setParsers(); + }, + + setParsers: function(){ + this.parsers = this.detectParsers(); + }, + + detectParsers: function(){ + return this.head && this.head.getElements(this.options.thSelector).flatten().map(this.detectParser, this); + }, + + detectParser: function(cell, index){ + if (cell.hasClass(this.options.classNoSort) || cell.retrieve('htmltable-parser')) return cell.retrieve('htmltable-parser'); + var thDiv = new Element('div'); + thDiv.adopt(cell.childNodes).inject(cell); + var sortSpan = new Element('span', {'class': this.options.classSortSpan}).inject(thDiv, 'top'); + this.sortSpans.push(sortSpan); + var parser = this.options.parsers[index], + rows = this.body.rows, + cancel; + switch (typeOf(parser)){ + case 'function': parser = {convert: parser}; cancel = true; break; + case 'string': parser = parser; cancel = true; break; + } + if (!cancel){ + HtmlTable.ParserPriority.some(function(parserName){ + var current = HtmlTable.Parsers[parserName], + match = current.match; + if (!match) return false; + for (var i = 0, j = rows.length; i < j; i++){ + var cell = document.id(rows[i].cells[index]), + text = cell ? cell.get('html').clean() : ''; + if (text && match.test(text)){ + parser = current; + return true; + } + } + }); + } + if (!parser) parser = this.options.defaultParser; + cell.store('htmltable-parser', parser); + return parser; + }, + + headClick: function(event, el){ + if (!this.head || el.hasClass(this.options.classNoSort)) return; + return this.sort(Array.indexOf(this.head.getElements(this.options.thSelector).flatten(), el) % this.body.rows[0].cells.length); + }, + + serialize: function(){ + var previousSerialization = this.previous.apply(this, arguments) || {}; + if (this.options.sortable){ + previousSerialization.sortIndex = this.sorted.index; + previousSerialization.sortReverse = this.sorted.reverse; + } + return previousSerialization; + }, + + restore: function(tableState){ + if(this.options.sortable && tableState.sortIndex){ + this.sort(tableState.sortIndex, tableState.sortReverse); + } + this.previous.apply(this, arguments); + }, + + setSortedState: function(index, reverse){ + if (reverse != null) this.sorted.reverse = reverse; + else if (this.sorted.index == index) this.sorted.reverse = !this.sorted.reverse; + else this.sorted.reverse = this.sorted.index == null; + + if (index != null) this.sorted.index = index; + }, + + setHeadSort: function(sorted){ + var head = $$(!this.head.length ? this.head.cells[this.sorted.index] : this.head.map(function(row){ + return row.getElements(this.options.thSelector)[this.sorted.index]; + }, this).clean()); + if (!head.length) return; + if (sorted){ + head.addClass(this.options.classHeadSort); + if (this.sorted.reverse) head.addClass(this.options.classHeadSortRev); + else head.removeClass(this.options.classHeadSortRev); + } else { + head.removeClass(this.options.classHeadSort).removeClass(this.options.classHeadSortRev); + } + }, + + setRowSort: function(data, pre){ + var count = data.length, + body = this.body, + group, + rowIndex; + + while (count){ + var item = data[--count], + position = item.position, + row = body.rows[position]; + + if (row.disabled) continue; + if (!pre){ + group = this.setGroupSort(group, row, item); + this.setRowStyle(row, count); + } + body.appendChild(row); + + for (rowIndex = 0; rowIndex < count; rowIndex++){ + if (data[rowIndex].position > position) data[rowIndex].position--; + } + } + }, + + setRowStyle: function(row, i){ + this.previous(row, i); + row.cells[this.sorted.index].addClass(this.options.classCellSort); + }, + + setGroupSort: function(group, row, item){ + if (group == item.value) row.removeClass(this.options.classGroupHead).addClass(this.options.classGroup); + else row.removeClass(this.options.classGroup).addClass(this.options.classGroupHead); + return item.value; + }, + + getParser: function(){ + var parser = this.parsers[this.sorted.index]; + return typeOf(parser) == 'string' ? HtmlTable.Parsers[parser] : parser; + }, + + sort: function(index, reverse, pre, sortFunction){ + if (!this.head) return; + + if (!pre){ + this.clearSort(); + this.setSortedState(index, reverse); + this.setHeadSort(true); + } + + var parser = this.getParser(); + if (!parser) return; + + var rel; + if (!readOnlyNess){ + rel = this.body.getParent(); + this.body.dispose(); + } + + var data = this.parseData(parser).sort(sortFunction ? sortFunction : function(a, b){ + if (a.value === b.value) return 0; + return a.value > b.value ? 1 : -1; + }); + + if (this.sorted.reverse == (parser == HtmlTable.Parsers['input-checked'])) data.reverse(true); + this.setRowSort(data, pre); + + if (rel) rel.grab(this.body); + this.fireEvent('stateChanged'); + return this.fireEvent('sort', [this.body, this.sorted.index]); + }, + + parseData: function(parser){ + return Array.map(this.body.rows, function(row, i){ + var value = parser.convert.call(document.id(row.cells[this.sorted.index])); + return { + position: i, + value: value + }; + }, this); + }, + + clearSort: function(){ + this.setHeadSort(false); + this.body.getElements('td').removeClass(this.options.classCellSort); + }, + + reSort: function(){ + if (this.sortable) this.sort.call(this, this.sorted.index, this.sorted.reverse); + return this; + }, + + enableSort: function(){ + this.element.addClass(this.options.classSortable); + this.attachSorts(true); + this.setParsers(); + this.sortable = true; + return this; + }, + + disableSort: function(){ + this.element.removeClass(this.options.classSortable); + this.attachSorts(false); + this.sortSpans.each(function(span){ + span.destroy(); + }); + this.sortSpans.empty(); + this.sortable = false; + return this; + } + +}); + +HtmlTable.ParserPriority = ['date', 'input-checked', 'input-value', 'float', 'number']; + +HtmlTable.Parsers = { + + 'date': { + match: /^\d{2}[-\/ ]\d{2}[-\/ ]\d{2,4}$/, + convert: function(){ + var d = Date.parse(this.get('text').stripTags()); + return (typeOf(d) == 'date') ? d.format('db') : ''; + }, + type: 'date' + }, + 'input-checked': { + match: / type="(radio|checkbox)"/, + convert: function(){ + return this.getElement('input').checked; + } + }, + 'input-value': { + match: /<input/, + convert: function(){ + return this.getElement('input').value; + } + }, + 'number': { + match: /^\d+[^\d.,]*$/, + convert: function(){ + return this.get('text').stripTags().toInt(); + }, + number: true + }, + 'numberLax': { + match: /^[^\d]+\d+$/, + convert: function(){ + return this.get('text').replace(/[^-?^0-9]/, '').stripTags().toInt(); + }, + number: true + }, + 'float': { + match: /^[\d]+\.[\d]+/, + convert: function(){ + return this.get('text').replace(/[^-?^\d.e]/, '').stripTags().toFloat(); + }, + number: true + }, + 'floatLax': { + match: /^[^\d]+[\d]+\.[\d]+$/, + convert: function(){ + return this.get('text').replace(/[^-?^\d.]/, '').stripTags().toFloat(); + }, + number: true + }, + 'string': { + match: null, + convert: function(){ + return this.get('text').stripTags().toLowerCase(); + } + }, + 'title': { + match: null, + convert: function(){ + return this.title; + } + } + +}; + + + +HtmlTable.defineParsers = function(parsers){ + HtmlTable.Parsers = Object.append(HtmlTable.Parsers, parsers); + for (var parser in parsers){ + HtmlTable.ParserPriority.unshift(parser); + } +}; + +})(); + + +/* +--- + +script: HtmlTable.Zebra.js + +name: HtmlTable.Zebra + +description: Builds a stripy table with methods to add rows. + +license: MIT-style license + +authors: + - Harald Kirschner + - Aaron Newton + +requires: + - HtmlTable + - Element.Shortcuts + - Class.refactor + +provides: [HtmlTable.Zebra] + +... +*/ + +HtmlTable = Class.refactor(HtmlTable, { + + options: { + classZebra: 'table-tr-odd', + zebra: true, + zebraOnlyVisibleRows: true + }, + + initialize: function(){ + this.previous.apply(this, arguments); + if (this.occluded) return this.occluded; + if (this.options.zebra) this.updateZebras(); + }, + + updateZebras: function(){ + var index = 0; + Array.each(this.body.rows, function(row){ + if (!this.options.zebraOnlyVisibleRows || row.isDisplayed()){ + this.zebra(row, index++); + } + }, this); + }, + + setRowStyle: function(row, i){ + if (this.previous) this.previous(row, i); + this.zebra(row, i); + }, + + zebra: function(row, i){ + return row[((i % 2) ? 'remove' : 'add')+'Class'](this.options.classZebra); + }, + + push: function(){ + var pushed = this.previous.apply(this, arguments); + if (this.options.zebra) this.updateZebras(); + return pushed; + } + +}); + +/* +--- + +script: Scroller.js + +name: Scroller + +description: Class which scrolls the contents of any Element (including the window) when the mouse reaches the Element's boundaries. + +license: MIT-style license + +authors: + - Valerio Proietti + +requires: + - Core/Events + - Core/Options + - Core/Element.Event + - Core/Element.Dimensions + - MooTools.More + +provides: [Scroller] + +... +*/ + +var Scroller = new Class({ + + Implements: [Events, Options], + + options: { + area: 20, + velocity: 1, + onChange: function(x, y){ + this.element.scrollTo(x, y); + }, + fps: 50 + }, + + initialize: function(element, options){ + this.setOptions(options); + this.element = document.id(element); + this.docBody = document.id(this.element.getDocument().body); + this.listener = (typeOf(this.element) != 'element') ? this.docBody : this.element; + this.timer = null; + this.bound = { + attach: this.attach.bind(this), + detach: this.detach.bind(this), + getCoords: this.getCoords.bind(this) + }; + }, + + start: function(){ + this.listener.addEvents({ + mouseover: this.bound.attach, + mouseleave: this.bound.detach + }); + return this; + }, + + stop: function(){ + this.listener.removeEvents({ + mouseover: this.bound.attach, + mouseleave: this.bound.detach + }); + this.detach(); + this.timer = clearInterval(this.timer); + return this; + }, + + attach: function(){ + this.listener.addEvent('mousemove', this.bound.getCoords); + }, + + detach: function(){ + this.listener.removeEvent('mousemove', this.bound.getCoords); + this.timer = clearInterval(this.timer); + }, + + getCoords: function(event){ + this.page = (this.listener.get('tag') == 'body') ? event.client : event.page; + if (!this.timer) this.timer = this.scroll.periodical(Math.round(1000 / this.options.fps), this); + }, + + scroll: function(){ + var size = this.element.getSize(), + scroll = this.element.getScroll(), + pos = ((this.element != this.docBody) && (this.element != window)) ? element.getOffsets() : {x: 0, y: 0}, + scrollSize = this.element.getScrollSize(), + change = {x: 0, y: 0}, + top = this.options.area.top || this.options.area, + bottom = this.options.area.bottom || this.options.area; + for (var z in this.page){ + if (this.page[z] < (top + pos[z]) && scroll[z] != 0){ + change[z] = (this.page[z] - top - pos[z]) * this.options.velocity; + } else if (this.page[z] + bottom > (size[z] + pos[z]) && scroll[z] + size[z] != scrollSize[z]){ + change[z] = (this.page[z] - size[z] + bottom - pos[z]) * this.options.velocity; + } + change[z] = change[z].round(); + } + if (change.y || change.x) this.fireEvent('change', [scroll.x + change.x, scroll.y + change.y]); + } + +}); + +/* +--- + +script: Tips.js + +name: Tips + +description: Class for creating nice tips that follow the mouse cursor when hovering an element. + +license: MIT-style license + +authors: + - Valerio Proietti + - Christoph Pojer + - Luis Merino + +requires: + - Core/Options + - Core/Events + - Core/Element.Event + - Core/Element.Style + - Core/Element.Dimensions + - MooTools.More + +provides: [Tips] + +... +*/ + +(function(){ + +var read = function(option, element){ + return (option) ? (typeOf(option) == 'function' ? option(element) : element.get(option)) : ''; +}; + +this.Tips = new Class({ + + Implements: [Events, Options], + + options: {/* + id: null, + onAttach: function(element){}, + onDetach: function(element){}, + onBound: function(coords){},*/ + onShow: function(){ + this.tip.setStyle('display', 'block'); + }, + onHide: function(){ + this.tip.setStyle('display', 'none'); + }, + title: 'title', + text: function(element){ + return element.get('rel') || element.get('href'); + }, + showDelay: 100, + hideDelay: 100, + className: 'tip-wrap', + offset: {x: 16, y: 16}, + windowPadding: {x:0, y:0}, + fixed: false, + waiAria: true + }, + + initialize: function(){ + var params = Array.link(arguments, { + options: Type.isObject, + elements: function(obj){ + return obj != null; + } + }); + this.setOptions(params.options); + if (params.elements) this.attach(params.elements); + this.container = new Element('div', {'class': 'tip'}); + + if (this.options.id){ + this.container.set('id', this.options.id); + if (this.options.waiAria) this.attachWaiAria(); + } + }, + + toElement: function(){ + if (this.tip) return this.tip; + + this.tip = new Element('div', { + 'class': this.options.className, + styles: { + position: 'absolute', + top: 0, + left: 0 + } + }).adopt( + new Element('div', {'class': 'tip-top'}), + this.container, + new Element('div', {'class': 'tip-bottom'}) + ); + + return this.tip; + }, + + attachWaiAria: function(){ + var id = this.options.id; + this.container.set('role', 'tooltip'); + + if (!this.waiAria){ + this.waiAria = { + show: function(element){ + if (id) element.set('aria-describedby', id); + this.container.set('aria-hidden', 'false'); + }, + hide: function(element){ + if (id) element.erase('aria-describedby'); + this.container.set('aria-hidden', 'true'); + } + }; + } + this.addEvents(this.waiAria); + }, + + detachWaiAria: function(){ + if (this.waiAria){ + this.container.erase('role'); + this.container.erase('aria-hidden'); + this.removeEvents(this.waiAria); + } + }, + + attach: function(elements){ + $$(elements).each(function(element){ + var title = read(this.options.title, element), + text = read(this.options.text, element); + + element.set('title', '').store('tip:native', title).retrieve('tip:title', title); + element.retrieve('tip:text', text); + this.fireEvent('attach', [element]); + + var events = ['enter', 'leave']; + if (!this.options.fixed) events.push('move'); + + events.each(function(value){ + var event = element.retrieve('tip:' + value); + if (!event) event = function(event){ + this['element' + value.capitalize()].apply(this, [event, element]); + }.bind(this); + + element.store('tip:' + value, event).addEvent('mouse' + value, event); + }, this); + }, this); + + return this; + }, + + detach: function(elements){ + $$(elements).each(function(element){ + ['enter', 'leave', 'move'].each(function(value){ + element.removeEvent('mouse' + value, element.retrieve('tip:' + value)).eliminate('tip:' + value); + }); + + this.fireEvent('detach', [element]); + + if (this.options.title == 'title'){ // This is necessary to check if we can revert the title + var original = element.retrieve('tip:native'); + if (original) element.set('title', original); + } + }, this); + + return this; + }, + + elementEnter: function(event, element){ + clearTimeout(this.timer); + this.timer = (function(){ + this.container.empty(); + + ['title', 'text'].each(function(value){ + var content = element.retrieve('tip:' + value); + var div = this['_' + value + 'Element'] = new Element('div', { + 'class': 'tip-' + value + }).inject(this.container); + if (content) this.fill(div, content); + }, this); + this.show(element); + this.position((this.options.fixed) ? {page: element.getPosition()} : event); + }).delay(this.options.showDelay, this); + }, + + elementLeave: function(event, element){ + clearTimeout(this.timer); + this.timer = this.hide.delay(this.options.hideDelay, this, element); + this.fireForParent(event, element); + }, + + setTitle: function(title){ + if (this._titleElement){ + this._titleElement.empty(); + this.fill(this._titleElement, title); + } + return this; + }, + + setText: function(text){ + if (this._textElement){ + this._textElement.empty(); + this.fill(this._textElement, text); + } + return this; + }, + + fireForParent: function(event, element){ + element = element.getParent(); + if (!element || element == document.body) return; + if (element.retrieve('tip:enter')) element.fireEvent('mouseenter', event); + else this.fireForParent(event, element); + }, + + elementMove: function(event, element){ + this.position(event); + }, + + position: function(event){ + if (!this.tip) document.id(this); + + var size = window.getSize(), scroll = window.getScroll(), + tip = {x: this.tip.offsetWidth, y: this.tip.offsetHeight}, + props = {x: 'left', y: 'top'}, + bounds = {y: false, x2: false, y2: false, x: false}, + obj = {}; + + for (var z in props){ + obj[props[z]] = event.page[z] + this.options.offset[z]; + if (obj[props[z]] < 0) bounds[z] = true; + if ((obj[props[z]] + tip[z] - scroll[z]) > size[z] - this.options.windowPadding[z]){ + obj[props[z]] = event.page[z] - this.options.offset[z] - tip[z]; + bounds[z+'2'] = true; + } + } + + this.fireEvent('bound', bounds); + this.tip.setStyles(obj); + }, + + fill: function(element, contents){ + if (typeof contents == 'string') element.set('html', contents); + else element.adopt(contents); + }, + + show: function(element){ + if (!this.tip) document.id(this); + if (!this.tip.getParent()) this.tip.inject(document.body); + this.fireEvent('show', [this.tip, element]); + }, + + hide: function(element){ + if (!this.tip) document.id(this); + this.fireEvent('hide', [this.tip, element]); + } + +}); + +})(); + +/* +--- + +name: Locale.EU.Number + +description: Number messages for Europe. + +license: MIT-style license + +authors: + - Arian Stolwijk + +requires: + - Locale + +provides: [Locale.EU.Number] + +... +*/ + +Locale.define('EU', 'Number', { + + decimal: ',', + group: '.', + + currency: { + prefix: '⬠' + } + +}); + +/* +--- + +script: Locale.Set.From.js + +name: Locale.Set.From + +description: Provides an alternative way to create Locale.Set objects. + +license: MIT-style license + +authors: + - Tim Wienk + +requires: + - Core/JSON + - Locale + +provides: Locale.Set.From + +... +*/ + +(function(){ + +var parsers = { + 'json': JSON.decode +}; + +Locale.Set.defineParser = function(name, fn){ + parsers[name] = fn; +}; + +Locale.Set.from = function(set, type){ + if (instanceOf(set, Locale.Set)) return set; + + if (!type && typeOf(set) == 'string') type = 'json'; + if (parsers[type]) set = parsers[type](set); + + var locale = new Locale.Set; + + locale.sets = set.sets || {}; + + if (set.inherits){ + locale.inherits.locales = Array.from(set.inherits.locales); + locale.inherits.sets = set.inherits.sets || {}; + } + + return locale; +}; + +})(); + +/* +--- + +name: Locale.ZA.Number + +description: Number messages for ZA. + +license: MIT-style license + +authors: + - Werner Mollentze + +requires: + - Locale + +provides: [Locale.ZA.Number] + +... +*/ + +Locale.define('ZA', 'Number', { + + decimal: '.', + group: ',', + + currency: { + prefix: 'R ' + } + +}); + + + +/* +--- + +name: Locale.af-ZA.Date + +description: Date messages for ZA Afrikaans. + +license: MIT-style license + +authors: + - Werner Mollentze + +requires: + - Locale + +provides: [Locale.af-ZA.Date] + +... +*/ + +Locale.define('af-ZA', 'Date', { + + months: ['Januarie', 'Februarie', 'Maart', 'April', 'Mei', 'Junie', 'Julie', 'Augustus', 'September', 'Oktober', 'November', 'Desember'], + months_abbr: ['Jan', 'Feb', 'Mrt', 'Apr', 'Mei', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'], + days: ['Sondag', 'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrydag', 'Saterdag'], + days_abbr: ['Son', 'Maa', 'Din', 'Woe', 'Don', 'Vry', 'Sat'], + + // Culture's date order: MM/DD/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d-%m-%Y', + shortTime: '%H:%M', + AM: 'VM', + PM: 'NM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: function(dayOfMonth){ + return ((dayOfMonth > 1 && dayOfMonth < 20 && dayOfMonth != 8) || (dayOfMonth > 100 && dayOfMonth.toString().substr(-2, 1) == '1')) ? 'de' : 'ste'; + }, + + lessThanMinuteAgo: 'minder as \'n minuut gelede', + minuteAgo: 'ongeveer \'n minuut gelede', + minutesAgo: '{delta} minute gelede', + hourAgo: 'omtret \'n uur gelede', + hoursAgo: 'ongeveer {delta} ure gelede', + dayAgo: '1 dag gelede', + daysAgo: '{delta} dae gelede', + weekAgo: '1 week gelede', + weeksAgo: '{delta} weke gelede', + monthAgo: '1 maand gelede', + monthsAgo: '{delta} maande gelede', + yearAgo: '1 jaar gelede', + yearsAgo: '{delta} jare gelede', + + lessThanMinuteUntil: 'oor minder as \'n minuut', + minuteUntil: 'oor ongeveer \'n minuut', + minutesUntil: 'oor {delta} minute', + hourUntil: 'oor ongeveer \'n uur', + hoursUntil: 'oor {delta} uur', + dayUntil: 'oor ongeveer \'n dag', + daysUntil: 'oor {delta} dae', + weekUntil: 'oor \'n week', + weeksUntil: 'oor {delta} weke', + monthUntil: 'oor \'n maand', + monthsUntil: 'oor {delta} maande', + yearUntil: 'oor \'n jaar', + yearsUntil: 'oor {delta} jaar' + +}); + +/* +--- + +name: Locale.af-ZA.Form.Validator + +description: Form Validator messages for Afrikaans. + +license: MIT-style license + +authors: + - Werner Mollentze + +requires: + - Locale + +provides: [Locale.af-ZA.Form.Validator] + +... +*/ + +Locale.define('af-ZA', 'FormValidator', { + + required: 'Hierdie veld word vereis.', + length: 'Voer asseblief {length} karakters in (u het {elLength} karakters ingevoer)', + minLength: 'Voer asseblief ten minste {minLength} karakters in (u het {length} karakters ingevoer).', + maxLength: 'Moet asseblief nie meer as {maxLength} karakters invoer nie (u het {length} karakters ingevoer).', + integer: 'Voer asseblief \'n heelgetal in hierdie veld in. Getalle met desimale (bv. 1.25) word nie toegelaat nie.', + numeric: 'Voer asseblief slegs numeriese waardes in hierdie veld in (bv. "1" of "1.1" of "-1" of "-1.1").', + digits: 'Gebruik asseblief slegs nommers en punktuasie in hierdie veld. (by voorbeeld, \'n telefoon nommer wat koppeltekens en punte bevat is toelaatbaar).', + alpha: 'Gebruik asseblief slegs letters (a-z) binne-in hierdie veld. Geen spasies of ander karakters word toegelaat nie.', + alphanum: 'Gebruik asseblief slegs letters (a-z) en nommers (0-9) binne-in hierdie veld. Geen spasies of ander karakters word toegelaat nie.', + dateSuchAs: 'Voer asseblief \'n geldige datum soos {date} in', + dateInFormatMDY: 'Voer asseblief \'n geldige datum soos MM/DD/YYYY in (bv. "12/31/1999")', + email: 'Voer asseblief \'n geldige e-pos adres in. Byvoorbeeld "fred@domain.com".', + url: 'Voer asseblief \'n geldige bronadres (URL) soos http://www.example.com in.', + currencyDollar: 'Voer asseblief \'n geldige $ bedrag in. Byvoorbeeld $100.00 .', + oneRequired: 'Voer asseblief iets in vir ten minste een van hierdie velde.', + errorPrefix: 'Fout: ', + warningPrefix: 'Waarskuwing: ', + + // Form.Validator.Extras + noSpace: 'Daar mag geen spasies in hierdie toevoer wees nie.', + reqChkByNode: 'Geen items is gekies nie.', + requiredChk: 'Hierdie veld word vereis.', + reqChkByName: 'Kies asseblief \'n {label}.', + match: 'Hierdie veld moet by die {matchName} veld pas', + startDate: 'die begin datum', + endDate: 'die eind datum', + currentDate: 'die huidige datum', + afterDate: 'Die datum moet dieselfde of na {label} wees.', + beforeDate: 'Die datum moet dieselfde of voor {label} wees.', + startMonth: 'Kies asseblief \'n begin maand', + sameMonth: 'Hierdie twee datums moet in dieselfde maand wees - u moet een of beide verander.', + creditcard: 'Die ingevoerde kredietkaart nommer is ongeldig. Bevestig asseblief die nommer en probeer weer. {length} syfers is ingevoer.' + +}); + +/* +--- + +name: Locale.af-ZA.Number + +description: Number messages for ZA Afrikaans. + +license: MIT-style license + +authors: + - Werner Mollentze + +requires: + - Locale + - Locale.ZA.Number + +provides: [Locale.af-ZA.Number] + +... +*/ + +Locale.define('af-ZA').inherit('ZA', 'Number'); + +/* +--- + +name: Locale.ar.Date + +description: Date messages for Arabic. + +license: MIT-style license + +authors: + - Chafik Barbar + +requires: + - Locale + +provides: [Locale.ar.Date] + +... +*/ + +Locale.define('ar', 'Date', { + + // Culture's date order: DD/MM/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d/%m/%Y', + shortTime: '%H:%M' + +}); + +/* +--- + +name: Locale.ar.Form.Validator + +description: Form Validator messages for Arabic. + +license: MIT-style license + +authors: + - Chafik Barbar + +requires: + - Locale + +provides: [Locale.ar.Form.Validator] + +... +*/ + +Locale.define('ar', 'FormValidator', { + + required: 'ÙØ°Ø§ Ø§ÙØÙÙ Ù
Ø·ÙÙØš.', + minLength: 'رجاء٠إدخا٠{minLength} Ø£ØØ±Ù عÙÙ Ø§ÙØ£ÙÙ (تÙ
إدخا٠{length} Ø£ØØ±Ù).', + maxLength: 'Ø§ÙØ±Ø¬Ø§Ø¡ عدÙ
Ø¥Ø¯Ø®Ø§Ù Ø£ÙØ«Ø± Ù
Ù {maxLength} Ø£ØØ±Ù (تÙ
إدخا٠{length} Ø£ØØ±Ù).', + integer: 'Ø§ÙØ±Ø¬Ø§Ø¡ إدخا٠عدد صØÙØ ÙÙ ÙØ°Ø§ Ø§ÙØÙÙ. أ٠رÙÙ
Ø°Ù ÙØ³Ø± ع؎ر٠أ٠Ù
ØŠÙÙ (Ù
ثا٠1.25 ) ØºÙØ± Ù
سÙ
ÙØ.', + numeric: 'Ø§ÙØ±Ø¬Ø§Ø¡ إدخا٠ÙÙÙ
رÙÙ
ÙØ© ÙÙ ÙØ°Ø§ Ø§ÙØÙÙ (Ù
ثا٠"1" Ø£Ù "1.1" Ø£Ù "-1" Ø£Ù "-1.1").', + digits: 'Ø§ÙØ±Ø¬Ø§Ø¡ أستخداÙ
ÙÙÙ
رÙÙ
ÙØ© ÙØ¹ÙاÙ
ات ترÙÙÙ
ÙØ© ÙÙØ· ÙÙ ÙØ°Ø§ Ø§ÙØÙÙ (Ù
ثاÙ, رÙÙ
ÙØ§ØªÙ Ù
ع ÙÙØ·Ø© Ø£Ù ØŽØØ·Ø©)', + alpha: 'Ø§ÙØ±Ø¬Ø§Ø¡ أستخداÙ
Ø£ØØ±Ù ÙÙØ· (ا-Ù) ÙÙ ÙØ°Ø§ Ø§ÙØÙÙ. Ø£Ù ÙØ±Ø§ØºØ§Øª Ø£Ù Ø¹ÙØ§Ù
ات ØºÙØ± Ù
سÙ
ÙØØ©.', + alphanum: 'Ø§ÙØ±Ø¬Ø§Ø¡ أستخداÙ
Ø£ØØ±Ù ÙÙØ· (ا-Ù) Ø£Ù Ø£Ø±ÙØ§Ù
(0-9) ÙÙØ· ÙÙ ÙØ°Ø§ Ø§ÙØÙÙ. Ø£Ù ÙØ±Ø§ØºØ§Øª Ø£Ù Ø¹ÙØ§Ù
ات ØºÙØ± Ù
سÙ
ÙØØ©.', + dateSuchAs: 'Ø§ÙØ±Ø¬Ø§Ø¡ Ø¥Ø¯Ø®Ø§Ù ØªØ§Ø±ÙØ® صØÙØ ÙØ§ÙتاÙÙ {date}', + dateInFormatMDY: 'Ø§ÙØ±Ø¬Ø§Ø¡ Ø¥Ø¯Ø®Ø§Ù ØªØ§Ø±ÙØ® صØÙØ (Ù
ثاÙ, 31-12-1999)', + email: 'Ø§ÙØ±Ø¬Ø§Ø¡ Ø¥Ø¯Ø®Ø§Ù ØšØ±ÙØ¯ Ø¥ÙÙØªØ±ÙÙ٠صØÙØ.', + url: 'Ø§ÙØ±Ø¬Ø§Ø¡ إدخا٠عÙÙØ§Ù Ø¥ÙÙØªØ±ÙÙ٠صØÙØ Ù
ث٠http://www.example.com', + currencyDollar: 'Ø§ÙØ±Ø¬Ø§Ø¡ إدخا٠ÙÙÙ
Ø© $ صØÙØØ©. Ù
ثاÙ, 100.00$', + oneRequired: 'Ø§ÙØ±Ø¬Ø§Ø¡ إدخا٠ÙÙÙ
Ø© ÙÙ Ø£ØØ¯ ÙØ°Ù Ø§ÙØÙÙ٠عÙÙ Ø§ÙØ£ÙÙ.', + errorPrefix: 'خطأ: ', + warningPrefix: 'ØªØØ°Ùر: ' + +}); + +/* +--- + +name: Locale.ca-CA.Date + +description: Date messages for Catalan. + +license: MIT-style license + +authors: + - Ãlfons Sanchez + +requires: + - Locale + +provides: [Locale.ca-CA.Date] + +... +*/ + +Locale.define('ca-CA', 'Date', { + + months: ['Gener', 'Febrer', 'Març', 'Abril', 'Maig', 'Juny', 'Juli', 'Agost', 'Setembre', 'Octubre', 'Novembre', 'Desembre'], + months_abbr: ['gen.', 'febr.', 'març', 'abr.', 'maig', 'juny', 'jul.', 'ag.', 'set.', 'oct.', 'nov.', 'des.'], + days: ['Diumenge', 'Dilluns', 'Dimarts', 'Dimecres', 'Dijous', 'Divendres', 'Dissabte'], + days_abbr: ['dg', 'dl', 'dt', 'dc', 'dj', 'dv', 'ds'], + + // Culture's date order: DD/MM/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d/%m/%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 0, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: 'fa menys d`un minut', + minuteAgo: 'fa un minut', + minutesAgo: 'fa {delta} minuts', + hourAgo: 'fa un hora', + hoursAgo: 'fa unes {delta} hores', + dayAgo: 'fa un dia', + daysAgo: 'fa {delta} dies', + + lessThanMinuteUntil: 'menys d`un minut des d`ara', + minuteUntil: 'un minut des d`ara', + minutesUntil: '{delta} minuts des d`ara', + hourUntil: 'un hora des d`ara', + hoursUntil: 'unes {delta} hores des d`ara', + dayUntil: '1 dia des d`ara', + daysUntil: '{delta} dies des d`ara' + +}); + +/* +--- + +name: Locale.ca-CA.Form.Validator + +description: Form Validator messages for Catalan. + +license: MIT-style license + +authors: + - Miquel Hudin + - Ãlfons Sanchez + +requires: + - Locale + +provides: [Locale.ca-CA.Form.Validator] + +... +*/ + +Locale.define('ca-CA', 'FormValidator', { + + required: 'Aquest camp es obligatori.', + minLength: 'Per favor introdueix al menys {minLength} caracters (has introduit {length} caracters).', + maxLength: 'Per favor introdueix no mes de {maxLength} caracters (has introduit {length} caracters).', + integer: 'Per favor introdueix un nombre enter en aquest camp. Nombres amb decimals (p.e. 1,25) no estan permesos.', + numeric: 'Per favor introdueix sols valors numerics en aquest camp (p.e. "1" o "1,1" o "-1" o "-1,1").', + digits: 'Per favor usa sols numeros i puntuacio en aquest camp (per exemple, un nombre de telefon amb guions i punts no esta permes).', + alpha: 'Per favor utilitza lletres nomes (a-z) en aquest camp. No sÂŽadmiteixen espais ni altres caracters.', + alphanum: 'Per favor, utilitza nomes lletres (a-z) o numeros (0-9) en aquest camp. No sÂŽadmiteixen espais ni altres caracters.', + dateSuchAs: 'Per favor introdueix una data valida com {date}', + dateInFormatMDY: 'Per favor introdueix una data valida com DD/MM/YYYY (p.e. "31/12/1999")', + email: 'Per favor, introdueix una adreça de correu electronic valida. Per exemple, "fred@domain.com".', + url: 'Per favor introdueix una URL valida com http://www.example.com.', + currencyDollar: 'Per favor introdueix una quantitat valida de â¬. Per exemple â¬100,00 .', + oneRequired: 'Per favor introdueix alguna cosa per al menys una dÂŽaquestes entrades.', + errorPrefix: 'Error: ', + warningPrefix: 'Avis: ', + + // Form.Validator.Extras + noSpace: 'No poden haver espais en aquesta entrada.', + reqChkByNode: 'No hi han elements seleccionats.', + requiredChk: 'Aquest camp es obligatori.', + reqChkByName: 'Per favor selecciona una {label}.', + match: 'Aquest camp necessita coincidir amb el camp {matchName}', + startDate: 'la data de inici', + endDate: 'la data de fi', + currentDate: 'la data actual', + afterDate: 'La data deu ser igual o posterior a {label}.', + beforeDate: 'La data deu ser igual o anterior a {label}.', + startMonth: 'Per favor selecciona un mes dÂŽorige', + sameMonth: 'Aquestes dos dates deuen estar dins del mateix mes - deus canviar una o altra.' + +}); + +/* +--- + +name: Locale.cs-CZ.Date + +description: Date messages for Czech. + +license: MIT-style license + +authors: + - Jan ÄernÜ chemiX + - Christopher Zukowski + +requires: + - Locale + +provides: [Locale.cs-CZ.Date] + +... +*/ +(function(){ + +// Czech language pluralization rules, see http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html +// one -> n is 1; 1 +// few -> n in 2..4; 2-4 +// other -> everything else 0, 5-999, 1.31, 2.31, 5.31... +var pluralize = function (n, one, few, other){ + if (n == 1) return one; + else if (n == 2 || n == 3 || n == 4) return few; + else return other; +}; + +Locale.define('cs-CZ', 'Date', { + + months: ['Leden', 'Ãnor', 'BÅezen', 'Duben', 'KvÄten', 'Äerven', 'Äervenec', 'Srpen', 'ZáÅÃ', 'ÅÃjen', 'Listopad', 'Prosinec'], + months_abbr: ['ledna', 'února', 'bÅezna', 'dubna', 'kvÄtna', 'Äervna', 'Äervence', 'srpna', 'záÅÃ', 'ÅÃjna', 'listopadu', 'prosince'], + days: ['NedÄle', 'PondÄlÃ', 'ÃterÜ', 'StÅeda', 'Ätvrtek', 'Pátek', 'Sobota'], + days_abbr: ['ne', 'po', 'út', 'st', 'Ät', 'pá', 'so'], + + // Culture's date order: DD.MM.YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d.%m.%Y', + shortTime: '%H:%M', + AM: 'dop.', + PM: 'odp.', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '.', + + lessThanMinuteAgo: 'pÅed chvÃlÃ', + minuteAgo: 'pÅibliÅŸnÄ pÅed minutou', + minutesAgo: function(delta){ return 'pÅed {delta} ' + pluralize(delta, 'minutou', 'minutami', 'minutami'); }, + hourAgo: 'pÅibliÅŸnÄ pÅed hodinou', + hoursAgo: function(delta){ return 'pÅed {delta} ' + pluralize(delta, 'hodinou', 'hodinami', 'hodinami'); }, + dayAgo: 'pÅed dnem', + daysAgo: function(delta){ return 'pÅed {delta} ' + pluralize(delta, 'dnem', 'dny', 'dny'); }, + weekAgo: 'pÅed tÜdnem', + weeksAgo: function(delta){ return 'pÅed {delta} ' + pluralize(delta, 'tÜdnem', 'tÜdny', 'tÜdny'); }, + monthAgo: 'pÅed mÄsÃcem', + monthsAgo: function(delta){ return 'pÅed {delta} ' + pluralize(delta, 'mÄsÃcem', 'mÄsÃci', 'mÄsÃci'); }, + yearAgo: 'pÅed rokem', + yearsAgo: function(delta){ return 'pÅed {delta} ' + pluralize(delta, 'rokem', 'lety', 'lety'); }, + + lessThanMinuteUntil: 'za chvÃli', + minuteUntil: 'pÅibliÅŸnÄ za minutu', + minutesUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'minutu', 'minuty', 'minut'); }, + hourUntil: 'pÅibliÅŸnÄ za hodinu', + hoursUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'hodinu', 'hodiny', 'hodin'); }, + dayUntil: 'za den', + daysUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'den', 'dny', 'dnů'); }, + weekUntil: 'za tÜden', + weeksUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'tÜden', 'tÜdny', 'tÜdnů'); }, + monthUntil: 'za mÄsÃc', + monthsUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'mÄsÃc', 'mÄsÃce', 'mÄsÃců'); }, + yearUntil: 'za rok', + yearsUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'rok', 'roky', 'let'); } +}); + +})(); + +/* +--- + +name: Locale.cs-CZ.Form.Validator + +description: Form Validator messages for Czech. + +license: MIT-style license + +authors: + - Jan ÄernÜ chemiX + +requires: + - Locale + +provides: [Locale.cs-CZ.Form.Validator] + +... +*/ + +Locale.define('cs-CZ', 'FormValidator', { + + required: 'Tato poloÅŸka je povinná.', + minLength: 'Zadejte prosÃm alespoÅ {minLength} znaků (napsáno {length} znaků).', + maxLength: 'Zadejte prosÃm ménÄ neÅŸ {maxLength} znaků (nápsáno {length} znaků).', + integer: 'Zadejte prosÃm celé ÄÃslo. Desetinná ÄÃsla (napÅ. 1.25) nejsou povolena.', + numeric: 'Zadejte jen ÄÃselné hodnoty (tj. "1" nebo "1.1" nebo "-1" nebo "-1.1").', + digits: 'Zadejte prosÃm pouze ÄÃsla a interpunkÄnà znaménka(napÅÃklad telefonnà ÄÃslo s pomlÄkami nebo teÄkami je povoleno).', + alpha: 'Zadejte prosÃm pouze pÃsmena (a-z). Mezery nebo jiné znaky nejsou povoleny.', + alphanum: 'Zadejte prosÃm pouze pÃsmena (a-z) nebo ÄÃslice (0-9). Mezery nebo jiné znaky nejsou povoleny.', + dateSuchAs: 'Zadejte prosÃm platné datum jako {date}', + dateInFormatMDY: 'Zadejte prosÃm platné datum jako MM / DD / RRRR (tj. "12/31/1999")', + email: 'Zadejte prosÃm platnou e-mailovou adresu. NapÅÃklad "fred@domain.com".', + url: 'Zadejte prosÃm platnou URL adresu jako http://www.example.com.', + currencyDollar: 'Zadejte prosÃm platnou Äástku. NapÅÃklad $100.00.', + oneRequired: 'Zadejte prosÃm alespoÅ jednu hodnotu pro tyto poloÅŸky.', + errorPrefix: 'Chyba: ', + warningPrefix: 'UpozornÄnÃ: ', + + // Form.Validator.Extras + noSpace: 'V této poloÅŸce nejsou povoleny mezery', + reqChkByNode: 'Nejsou vybrány şádné poloÅŸky.', + requiredChk: 'Tato poloÅŸka je vyÅŸadována.', + reqChkByName: 'ProsÃm vyberte {label}.', + match: 'Tato poloÅŸka se musà shodovat s poloÅŸkou {matchName}', + startDate: 'datum zahájenÃ', + endDate: 'datum ukonÄenÃ', + currentDate: 'aktuálnà datum', + afterDate: 'Datum by mÄlo bÜt stejné nebo vÄtšà neÅŸ {label}.', + beforeDate: 'Datum by mÄlo bÜt stejné nebo menšà neÅŸ {label}.', + startMonth: 'Vyberte poÄáteÄnà mÄsÃc.', + sameMonth: 'Tyto dva datumy musà bÜt ve stejném mÄsÃci - zmÄÅte jeden z nich.', + creditcard: 'Zadané ÄÃslo kreditnà karty je neplatné. ProsÃm opravte ho. Bylo zadáno {length} ÄÃsel.' + +}); + +/* +--- + +name: Locale.da-DK.Date + +description: Date messages for Danish. + +license: MIT-style license + +authors: + - Martin Overgaard + - Henrik Hansen + +requires: + - Locale + +provides: [Locale.da-DK.Date] + +... +*/ + +Locale.define('da-DK', 'Date', { + + months: ['Januar', 'Februar', 'Marts', 'April', 'Maj', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'December'], + months_abbr: ['jan.', 'feb.', 'mar.', 'apr.', 'maj.', 'jun.', 'jul.', 'aug.', 'sep.', 'okt.', 'nov.', 'dec.'], + days: ['SÞndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'LÞrdag'], + days_abbr: ['sÞn', 'man', 'tir', 'ons', 'tor', 'fre', 'lÞr'], + + // Culture's date order: DD-MM-YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d-%m-%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '.', + + lessThanMinuteAgo: 'mindre end et minut siden', + minuteAgo: 'omkring et minut siden', + minutesAgo: '{delta} minutter siden', + hourAgo: 'omkring en time siden', + hoursAgo: 'omkring {delta} timer siden', + dayAgo: '1 dag siden', + daysAgo: '{delta} dage siden', + weekAgo: '1 uge siden', + weeksAgo: '{delta} uger siden', + monthAgo: '1 mÃ¥ned siden', + monthsAgo: '{delta} mÃ¥neder siden', + yearAgo: '1 Ã¥r siden', + yearsAgo: '{delta} Ã¥r siden', + + lessThanMinuteUntil: 'mindre end et minut fra nu', + minuteUntil: 'omkring et minut fra nu', + minutesUntil: '{delta} minutter fra nu', + hourUntil: 'omkring en time fra nu', + hoursUntil: 'omkring {delta} timer fra nu', + dayUntil: '1 dag fra nu', + daysUntil: '{delta} dage fra nu', + weekUntil: '1 uge fra nu', + weeksUntil: '{delta} uger fra nu', + monthUntil: '1 mÃ¥ned fra nu', + monthsUntil: '{delta} mÃ¥neder fra nu', + yearUntil: '1 Ã¥r fra nu', + yearsUntil: '{delta} Ã¥r fra nu' + +}); + +/* +--- + +name: Locale.da-DK.Form.Validator + +description: Form Validator messages for Danish. + +license: MIT-style license + +authors: + - Martin Overgaard + +requires: + - Locale + +provides: [Locale.da-DK.Form.Validator] + +... +*/ + +Locale.define('da-DK', 'FormValidator', { + + required: 'Feltet skal udfyldes.', + minLength: 'Skriv mindst {minLength} tegn (du skrev {length} tegn).', + maxLength: 'Skriv maksimalt {maxLength} tegn (du skrev {length} tegn).', + integer: 'Skriv et tal i dette felt. Decimal tal (f.eks. 1.25) er ikke tilladt.', + numeric: 'Skriv kun tal i dette felt (i.e. "1" eller "1.1" eller "-1" eller "-1.1").', + digits: 'Skriv kun tal og tegnsÊtning i dette felt (eksempel, et telefon nummer med bindestreg eller punktum er tilladt).', + alpha: 'Skriv kun bogstaver (a-z) i dette felt. Mellemrum og andre tegn er ikke tilladt.', + alphanum: 'Skriv kun bogstaver (a-z) eller tal (0-9) i dette felt. Mellemrum og andre tegn er ikke tilladt.', + dateSuchAs: 'Skriv en gyldig dato som {date}', + dateInFormatMDY: 'Skriv dato i formatet DD-MM-YYYY (f.eks. "31-12-1999")', + email: 'Skriv en gyldig e-mail adresse. F.eks "fred@domain.com".', + url: 'Skriv en gyldig URL adresse. F.eks "http://www.example.com".', + currencyDollar: 'Skriv et gldigt belÞb. F.eks Kr.100.00 .', + oneRequired: 'Et eller flere af felterne i denne formular skal udfyldes.', + errorPrefix: 'Fejl: ', + warningPrefix: 'Advarsel: ', + + // Form.Validator.Extras + noSpace: 'Der mÃ¥ ikke benyttes mellemrum i dette felt.', + reqChkByNode: 'Foretag et valg.', + requiredChk: 'Dette felt skal udfyldes.', + reqChkByName: 'VÊlg en {label}.', + match: 'Dette felt skal matche {matchName} feltet', + startDate: 'start dato', + endDate: 'slut dato', + currentDate: 'dags dato', + afterDate: 'Datoen skal vÊre stÞrre end eller lig med {label}.', + beforeDate: 'Datoen skal vÊre mindre end eller lig med {label}.', + startMonth: 'VÊlg en start mÃ¥ned', + sameMonth: 'De valgte datoer skal vÊre i samme mÃ¥ned - skift en af dem.' + +}); + +/* +--- + +name: Locale.de-DE.Date + +description: Date messages for German. + +license: MIT-style license + +authors: + - Christoph Pojer + - Frank Rossi + - Ulrich Petri + - Fabian Beiner + +requires: + - Locale + +provides: [Locale.de-DE.Date] + +... +*/ + +Locale.define('de-DE', 'Date', { + + months: ['Januar', 'Februar', 'MÀrz', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'], + months_abbr: ['Jan', 'Feb', 'MÀr', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'], + days: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'], + days_abbr: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'], + + // Culture's date order: DD.MM.YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d.%m.%Y', + shortTime: '%H:%M', + AM: 'vormittags', + PM: 'nachmittags', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '.', + + lessThanMinuteAgo: 'vor weniger als einer Minute', + minuteAgo: 'vor einer Minute', + minutesAgo: 'vor {delta} Minuten', + hourAgo: 'vor einer Stunde', + hoursAgo: 'vor {delta} Stunden', + dayAgo: 'vor einem Tag', + daysAgo: 'vor {delta} Tagen', + weekAgo: 'vor einer Woche', + weeksAgo: 'vor {delta} Wochen', + monthAgo: 'vor einem Monat', + monthsAgo: 'vor {delta} Monaten', + yearAgo: 'vor einem Jahr', + yearsAgo: 'vor {delta} Jahren', + + lessThanMinuteUntil: 'in weniger als einer Minute', + minuteUntil: 'in einer Minute', + minutesUntil: 'in {delta} Minuten', + hourUntil: 'in ca. einer Stunde', + hoursUntil: 'in ca. {delta} Stunden', + dayUntil: 'in einem Tag', + daysUntil: 'in {delta} Tagen', + weekUntil: 'in einer Woche', + weeksUntil: 'in {delta} Wochen', + monthUntil: 'in einem Monat', + monthsUntil: 'in {delta} Monaten', + yearUntil: 'in einem Jahr', + yearsUntil: 'in {delta} Jahren' + +}); + +/* +--- + +name: Locale.de-CH.Date + +description: Date messages for German (Switzerland). + +license: MIT-style license + +authors: + - Michael van der Weg + +requires: + - Locale + - Locale.de-DE.Date + +provides: [Locale.de-CH.Date] + +... +*/ + +Locale.define('de-CH').inherit('de-DE', 'Date'); + +/* +--- + +name: Locale.de-CH.Form.Validator + +description: Form Validator messages for German (Switzerland). + +license: MIT-style license + +authors: + - Michael van der Weg + +requires: + - Locale + +provides: [Locale.de-CH.Form.Validator] + +... +*/ + +Locale.define('de-CH', 'FormValidator', { + + required: 'Dieses Feld ist obligatorisch.', + minLength: 'Geben Sie bitte mindestens {minLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).', + maxLength: 'Bitte geben Sie nicht mehr als {maxLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).', + integer: 'Geben Sie bitte eine ganze Zahl ein. Dezimalzahlen (z.B. 1.25) sind nicht erlaubt.', + numeric: 'Geben Sie bitte nur Zahlenwerte in dieses Eingabefeld ein (z.B. "1", "1.1", "-1" oder "-1.1").', + digits: 'Benutzen Sie bitte nur Zahlen und Satzzeichen in diesem Eingabefeld (erlaubt ist z.B. eine Telefonnummer mit Bindestrichen und Punkten).', + alpha: 'Benutzen Sie bitte nur Buchstaben (a-z) in diesem Feld. Leerzeichen und andere Zeichen sind nicht erlaubt.', + alphanum: 'Benutzen Sie bitte nur Buchstaben (a-z) und Zahlen (0-9) in diesem Eingabefeld. Leerzeichen und andere Zeichen sind nicht erlaubt.', + dateSuchAs: 'Geben Sie bitte ein gültiges Datum ein. Wie zum Beispiel {date}', + dateInFormatMDY: 'Geben Sie bitte ein gültiges Datum ein. Wie zum Beispiel TT.MM.JJJJ (z.B. "31.12.1999")', + email: 'Geben Sie bitte eine gültige E-Mail Adresse ein. Wie zum Beispiel "maria@bernasconi.ch".', + url: 'Geben Sie bitte eine gültige URL ein. Wie zum Beispiel http://www.example.com.', + currencyDollar: 'Geben Sie bitte einen gültigen Betrag in Schweizer Franken ein. Wie zum Beispiel 100.00 CHF .', + oneRequired: 'Machen Sie für mindestens eines der Eingabefelder einen Eintrag.', + errorPrefix: 'Fehler: ', + warningPrefix: 'Warnung: ', + + // Form.Validator.Extras + noSpace: 'In diesem Eingabefeld darf kein Leerzeichen sein.', + reqChkByNode: 'Es wurden keine Elemente gewählt.', + requiredChk: 'Dieses Feld ist obligatorisch.', + reqChkByName: 'Bitte wählen Sie ein {label}.', + match: 'Dieses Eingabefeld muss mit dem Feld {matchName} übereinstimmen.', + startDate: 'Das Anfangsdatum', + endDate: 'Das Enddatum', + currentDate: 'Das aktuelle Datum', + afterDate: 'Das Datum sollte zur gleichen Zeit oder später sein {label}.', + beforeDate: 'Das Datum sollte zur gleichen Zeit oder früher sein {label}.', + startMonth: 'Wählen Sie bitte einen Anfangsmonat', + sameMonth: 'Diese zwei Datumsangaben müssen im selben Monat sein - Sie müssen eine von beiden verändern.', + creditcard: 'Die eingegebene Kreditkartennummer ist ungültig. Bitte überprüfen Sie diese und versuchen Sie es erneut. {length} Zahlen eingegeben.' + +}); + +/* +--- + +name: Locale.de-DE.Form.Validator + +description: Form Validator messages for German. + +license: MIT-style license + +authors: + - Frank Rossi + - Ulrich Petri + - Fabian Beiner + +requires: + - Locale + +provides: [Locale.de-DE.Form.Validator] + +... +*/ + +Locale.define('de-DE', 'FormValidator', { + + required: 'Dieses Eingabefeld muss ausgefÃŒllt werden.', + minLength: 'Geben Sie bitte mindestens {minLength} Zeichen ein (Sie haben nur {length} Zeichen eingegeben).', + maxLength: 'Geben Sie bitte nicht mehr als {maxLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).', + integer: 'Geben Sie in diesem Eingabefeld bitte eine ganze Zahl ein. Dezimalzahlen (z.B. "1.25") sind nicht erlaubt.', + numeric: 'Geben Sie in diesem Eingabefeld bitte nur Zahlenwerte (z.B. "1", "1.1", "-1" oder "-1.1") ein.', + digits: 'Geben Sie in diesem Eingabefeld bitte nur Zahlen und Satzzeichen ein (z.B. eine Telefonnummer mit Bindestrichen und Punkten ist erlaubt).', + alpha: 'Geben Sie in diesem Eingabefeld bitte nur Buchstaben (a-z) ein. Leerzeichen und andere Zeichen sind nicht erlaubt.', + alphanum: 'Geben Sie in diesem Eingabefeld bitte nur Buchstaben (a-z) und Zahlen (0-9) ein. Leerzeichen oder andere Zeichen sind nicht erlaubt.', + dateSuchAs: 'Geben Sie bitte ein gÃŒltiges Datum ein (z.B. "{date}").', + dateInFormatMDY: 'Geben Sie bitte ein gÃŒltiges Datum im Format TT.MM.JJJJ ein (z.B. "31.12.1999").', + email: 'Geben Sie bitte eine gÃŒltige E-Mail-Adresse ein (z.B. "max@mustermann.de").', + url: 'Geben Sie bitte eine gÃŒltige URL ein (z.B. "http://www.example.com").', + currencyDollar: 'Geben Sie bitte einen gÃŒltigen Betrag in EURO ein (z.B. 100.00â¬).', + oneRequired: 'Bitte fÃŒllen Sie mindestens ein Eingabefeld aus.', + errorPrefix: 'Fehler: ', + warningPrefix: 'Warnung: ', + + // Form.Validator.Extras + noSpace: 'Es darf kein Leerzeichen in diesem Eingabefeld sein.', + reqChkByNode: 'Es wurden keine Elemente gewÀhlt.', + requiredChk: 'Dieses Feld muss ausgefÃŒllt werden.', + reqChkByName: 'Bitte wÀhlen Sie ein {label}.', + match: 'Dieses Eingabefeld muss mit dem {matchName} Eingabefeld ÃŒbereinstimmen.', + startDate: 'Das Anfangsdatum', + endDate: 'Das Enddatum', + currentDate: 'Das aktuelle Datum', + afterDate: 'Das Datum sollte zur gleichen Zeit oder spÀter sein als {label}.', + beforeDate: 'Das Datum sollte zur gleichen Zeit oder frÃŒher sein als {label}.', + startMonth: 'WÀhlen Sie bitte einen Anfangsmonat', + sameMonth: 'Diese zwei Datumsangaben mÃŒssen im selben Monat sein - Sie mÃŒssen eines von beiden verÀndern.', + creditcard: 'Die eingegebene Kreditkartennummer ist ungÃŒltig. Bitte ÃŒberprÃŒfen Sie diese und versuchen Sie es erneut. {length} Zahlen eingegeben.' + +}); + +/* +--- + +name: Locale.de-DE.Number + +description: Number messages for German. + +license: MIT-style license + +authors: + - Christoph Pojer + +requires: + - Locale + - Locale.EU.Number + +provides: [Locale.de-DE.Number] + +... +*/ + +Locale.define('de-DE').inherit('EU', 'Number'); + +/* +--- + +name: Locale.el-GR.Date + +description: Date messages for Greek language. + +license: MIT-style license + +authors: + - Periklis Argiriadis + +requires: + - Locale + +provides: [Locale.el-GR.Date] + +... +*/ + +Locale.define('el-GR', 'Date', { + + months: ['ÎαΜοÏ
άÏιοÏ', 'ΊεβÏοÏ
άÏιοÏ', 'ÎάÏÏιοÏ', 'ÎÏÏίλιοÏ', 'ÎάιοÏ', 'ÎοÏΜιοÏ', 'ÎοÏλιοÏ', 'ÎÏγοÏ
ÏÏοÏ', 'ΣεÏÏÎΌβÏιοÏ', 'ÎκÏÏβÏιοÏ', 'ÎοÎΌβÏιοÏ', 'ÎεκÎΌβÏιοÏ'], + months_abbr: ['ÎαΜ', 'Ίεβ', 'ÎαÏ', 'ÎÏÏ', 'Îάι', 'ÎοÏ
Μ', 'ÎοÏ
λ', 'ÎÏ
γ', 'ΣεÏ', 'ÎκÏ', 'Îοε', 'Îεκ'], + days: ['ÎÏ
Ïιακή', 'ÎεÏ
ÏÎÏα', '΀ÏίÏη', '΀εÏάÏÏη', 'Î ÎÎŒÏÏη', 'ΠαÏαÏκεÏ
ή', 'ΣάββαÏο'], + days_abbr: ['ÎÏ
Ï', 'ÎεÏ
', '΀Ïι', '΀εÏ', 'ΠεΌ', 'ΠαÏ', 'Σαβ'], + + // Culture's date order: DD/MM/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d/%m/%Y', + shortTime: '%I:%M%p', + AM: 'ÏÎŒ', + PM: 'ΌΌ', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: function(dayOfMonth){ + // 1st, 2nd, 3rd, etc. + return (dayOfMonth > 3 && dayOfMonth < 21) ? 'οÏ' : ['οÏ'][Math.min(dayOfMonth % 10, 4)]; + }, + + lessThanMinuteAgo: 'λιγÏÏεÏο αÏÏ ÎΜα λεÏÏÏ ÏÏιΜ', + minuteAgo: 'ÏεÏίÏοÏ
ÎΜα λεÏÏÏ ÏÏιΜ', + minutesAgo: '{delta} λεÏÏά ÏÏιΜ', + hourAgo: 'ÏεÏίÏοÏ
Όια ÏÏα ÏÏιΜ', + hoursAgo: 'ÏεÏίÏοÏ
{delta} ÏÏÎµÏ ÏÏιΜ', + dayAgo: '1 ηΌÎÏα ÏÏιΜ', + daysAgo: '{delta} ηΌÎÏÎµÏ ÏÏιΜ', + weekAgo: '1 εβΎοΌάΎα ÏÏιΜ', + weeksAgo: '{delta} ÎµÎ²ÎŽÎ¿ÎŒÎ¬ÎŽÎµÏ ÏÏιΜ', + monthAgo: '1 ΌήΜα ÏÏιΜ', + monthsAgo: '{delta} ÎŒÎ®ÎœÎµÏ ÏÏιΜ', + yearAgo: '1 ÏÏÏΜο ÏÏιΜ', + yearsAgo: '{delta} ÏÏÏΜια ÏÏιΜ', + + lessThanMinuteUntil: 'λιγÏÏεÏο αÏÏ Î»ÎµÏÏÏ Î±ÏÏ ÏÏÏα', + minuteUntil: 'ÏεÏίÏοÏ
ÎΜα λεÏÏÏ Î±ÏÏ ÏÏÏα', + minutesUntil: '{delta} λεÏÏά αÏÏ ÏÏÏα', + hourUntil: 'ÏεÏίÏοÏ
Όια ÏÏα αÏÏ ÏÏÏα', + hoursUntil: 'ÏεÏίÏοÏ
{delta} ÏÏÎµÏ Î±ÏÏ ÏÏÏα', + dayUntil: '1 ηΌÎÏα αÏÏ ÏÏÏα', + daysUntil: '{delta} ηΌÎÏÎµÏ Î±ÏÏ ÏÏÏα', + weekUntil: '1 εβΎοΌάΎα αÏÏ ÏÏÏα', + weeksUntil: '{delta} ÎµÎ²ÎŽÎ¿ÎŒÎ¬ÎŽÎµÏ Î±ÏÏ ÏÏÏα', + monthUntil: '1 ÎŒÎ®ÎœÎ±Ï Î±ÏÏ ÏÏÏα', + monthsUntil: '{delta} ÎŒÎ®ÎœÎµÏ Î±ÏÏ ÏÏÏα', + yearUntil: '1 ÏÏÏÎœÎ¿Ï Î±ÏÏ ÏÏÏα', + yearsUntil: '{delta} ÏÏÏΜια αÏÏ ÏÏÏα' + +}); + +/* +--- + +name: Locale.el-GR.Form.Validator + +description: Form Validator messages for Greek language. + +license: MIT-style license + +authors: + - Dimitris Tsironis + +requires: + - Locale + +provides: [Locale.el-GR.Form.Validator] + +... +*/ + +Locale.define('el-GR', 'FormValidator', { + + required: 'ÎÏ
ÏÏ Ïο ÏεΎίο είΜαι αÏαÏαίÏηÏο.', + length: 'ΠαÏακαλοÏΌε, ειÏάγεÏε {length} ÏαÏακÏήÏÎµÏ (ÎÏεÏε ήΎη ειÏάγει {elLength} ÏαÏακÏήÏεÏ).', + minLength: 'ΠαÏακαλοÏΌε, ειÏάγεÏε ÏοÏ
λάÏιÏÏοΜ {minLength} ÏαÏακÏήÏÎµÏ (ÎÏεÏε ήΎη ειÏάγε {length} ÏαÏακÏήÏεÏ).', + maxlength: 'ΠαÏακαλοÏΌε, ειÏάγεÏε εÏÏ {maxlength} ÏαÏακÏήÏÎµÏ (ÎÏεÏε ήΎη ειÏάγε {length} ÏαÏακÏήÏεÏ).', + integer: 'ΠαÏακαλοÏΌε, ειÏάγεÏε ÎΜαΜ ακÎÏαιο αÏÎ¹ÎžÎŒÏ Ïε αÏ
ÏÏ Ïο ÏεΎίο. Îι αÏιΞΌοί Όε ΎεκαΎικά ÏηÏία (Ï.Ï. 1.25) ΎεΜ εÏιÏÏÎÏοΜÏαι.', + numeric: 'ΠαÏακαλοÏΌε, ειÏάγεÏε ÎŒÏΜο αÏιΞΌηÏικÎÏ ÏιΌÎÏ Ïε αÏ
ÏÏ Ïο ÏεΎίο (Ï.Ï." 1 " ή " 1.1 " ή " -1 " ή " -1.1 " ).', + digits: 'ΠαÏακαλοÏΌε, ÏÏηÏιΌοÏοιήÏÏε ÎŒÏΜο αÏιΞΌοÏÏ ÎºÎ±Î¹ ÏηΌεία ÏÏÎ¯ÎŸÎ·Ï Ïε αÏ
ÏÏΜ ÏοΜ ÏοΌÎα (Ï.Ï. εÏιÏÏÎÏεÏαι αÏιΞΌÏÏ ÏηλεÏÏΜοÏ
Όε ÏαÏÎ»ÎµÏ Î® ÏελείεÏ).', + alpha: 'ΠαÏακαλοÏΌε, ÏÏηÏιΌοÏοιήÏÏε ÎŒÏΜο γÏάΌΌαÏα (a-z) Ïε αÏ
ÏÏ Ïο ÏεΎίο. ÎεΜ εÏιÏÏÎÏοΜÏαι κεΜά ή άλλοι ÏαÏακÏήÏεÏ.', + alphanum: 'ΠαÏακαλοÏΌε, ÏÏηÏιΌοÏοιήÏÏε ÎŒÏΜο γÏάΌΌαÏα (a-z) ή αÏιΞΌοÏÏ (0-9) Ïε αÏ
ÏÏΜ ÏοΜ ÏοΌÎα. ÎεΜ εÏιÏÏÎÏοΜÏαι κεΜά ή άλλοι ÏαÏακÏήÏεÏ.', + dateSuchAs: 'ΠαÏακαλοÏΌε, ειÏάγεÏε Όια ÎγκÏ
Ïη ηΌεÏοΌηΜία, ÏÏÏÏ {date}', + dateInFormatMDY: 'ΠαÏÎ±ÎºÎ±Î»Ï ÎµÎ¹ÏάγεÏε Όια ÎγκÏ
Ïη ηΌεÏοΌηΜία, ÏÏÏÏ ÎÎ/ÎÎ/ÎÎÎÎ (Ï.Ï. "12/31/1999").', + email: 'ΠαÏακαλοÏΌε, ειÏάγεÏε Όια ÎγκÏ
Ïη ΎιεÏΞÏ
ΜÏη ηλεκÏÏÎ¿ÎœÎ¹ÎºÎ¿Ï ÏαÏÏ
ÎŽÏοΌείοÏ
(Ï.Ï. "fred@domain.com").', + url: 'ΠαÏακαλοÏΌε, ειÏάγεÏε Όια ÎγκÏ
Ïη URL ΎιεÏΞÏ
ΜÏη, ÏÏÏÏ http://www.example.com', + currencyDollar: 'ΠαÏακαλοÏΌε, ειÏάγεÏε ÎΜα ÎγκÏ
Ïο ÏοÏÏ Ïε ΎολλάÏια (Ï.Ï. $100.00).', + oneRequired: 'ΠαÏακαλοÏΌε, ειÏάγεÏε κάÏι για ÏοÏ
λάÏιÏÏοΜ ÎΜα αÏÏ Î±Ï
Ïά Ïα ÏεΎία.', + errorPrefix: 'ΣÏάλΌα: ', + warningPrefix: 'Î ÏοÏοÏή: ', + + // Form.Validator.Extras + noSpace: 'ÎεΜ εÏιÏÏÎÏοΜÏαι Ïα κεΜά Ïε αÏ
ÏÏ Ïο ÏεΎίο.', + reqChkByNode: 'ÎεΜ ÎÏει εÏιλεγεί κάÏοιο αΜÏικείΌεΜο', + requiredChk: 'ÎÏ
ÏÏ Ïο ÏεΎίο είΜαι αÏαÏαίÏηÏο.', + reqChkByName: 'ΠαÏακαλοÏΌε, εÏιλÎΟÏε Όια εÏικÎÏα {label}.', + match: 'ÎÏ
ÏÏ Ïο ÏεΎίο ÏÏÎÏει Μα ÏαιÏιάζει Όε Ïο ÏεΎίο {matchName}.', + startDate: 'η ηΌεÏοΌηΜία ÎΜαÏΟηÏ', + endDate: 'η ηΌεÏοΌηΜία λήΟηÏ', + currentDate: 'η ÏÏÎÏοÏ
Ïα ηΌεÏοΌηΜία', + afterDate: 'ΠηΌεÏοΌηΜία ÏÏÎÏει Μα είΜαι η ίΎια ή ΌεÏά αÏÏ ÏηΜ {label}.', + beforeDate: 'ΠηΌεÏοΌηΜία ÏÏÎÏει Μα είΜαι η ίΎια ή ÏÏιΜ αÏÏ ÏηΜ {label}.', + startMonth: 'ΠαÏÎ±ÎºÎ±Î»Ï ÎµÏιλÎΟÏε ÎΜα ΌήΜα αÏÏήÏ.', + sameMonth: 'ÎÏ
ÏÎÏ Î¿Î¹ ÎŽÏο ηΌεÏÎ¿ÎŒÎ·ÎœÎ¯ÎµÏ ÏÏÎÏει Μα ÎÏοÏ
Μ ÏοΜ ίΎιο ΌήΜα - Ξα ÏÏÎÏει Μα αλλάΟεÏε ή Ïο ÎΜα ή Ïο άλλο', + creditcard: 'ΠαÏιΞΌÏÏ ÏÎ·Ï ÏιÏÏÏÏÎ¹ÎºÎ®Ï ÎºÎ¬ÏÏÎ±Ï ÎŽÎµÎœ είΜαι ÎγκÏ
ÏοÏ. ΠαÏακαλοÏΌε ελÎγΟÏε ÏοΜ αÏÎ¹ÎžÎŒÏ ÎºÎ±Î¹ ΎοκιΌάÏÏε ΟαΜά. {length} ÎŒÎ®ÎºÎ¿Ï ÏηÏίÏΜ.' + +}); + +/* +--- + +name: Locale.en-GB.Date + +description: Date messages for British English. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Locale + - Locale.en-US.Date + +provides: [Locale.en-GB.Date] + +... +*/ + +Locale.define('en-GB', 'Date', { + + // Culture's date order: DD/MM/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d/%m/%Y', + shortTime: '%H:%M' + +}).inherit('en-US', 'Date'); + +/* +--- + +name: Locale.en-US.Number + +description: Number messages for US English. + +license: MIT-style license + +authors: + - Arian Stolwijk + +requires: + - Locale + +provides: [Locale.en-US.Number] + +... +*/ + +Locale.define('en-US', 'Number', { + + decimal: '.', + group: ',', + +/* Commented properties are the defaults for Number.format + decimals: 0, + precision: 0, + scientific: null, + + prefix: null, + suffic: null, + + // Negative/Currency/percentage will mixin Number + negative: { + prefix: '-' + },*/ + + currency: { +// decimals: 2, + prefix: '$ ' + }/*, + + percentage: { + decimals: 2, + suffix: '%' + }*/ + +}); + + + +/* +--- + +name: Locale.es-ES.Date + +description: Date messages for Spanish. + +license: MIT-style license + +authors: + - Ãlfons Sanchez + +requires: + - Locale + +provides: [Locale.es-ES.Date] + +... +*/ + +Locale.define('es-ES', 'Date', { + + months: ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'], + months_abbr: ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic'], + days: ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado'], + days_abbr: ['dom', 'lun', 'mar', 'mié', 'juv', 'vie', 'sáb'], + + // Culture's date order: DD/MM/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d/%m/%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: 'hace menos de un minuto', + minuteAgo: 'hace un minuto', + minutesAgo: 'hace {delta} minutos', + hourAgo: 'hace una hora', + hoursAgo: 'hace unas {delta} horas', + dayAgo: 'hace un dÃa', + daysAgo: 'hace {delta} dÃas', + weekAgo: 'hace una semana', + weeksAgo: 'hace unas {delta} semanas', + monthAgo: 'hace un mes', + monthsAgo: 'hace {delta} meses', + yearAgo: 'hace un año', + yearsAgo: 'hace {delta} años', + + lessThanMinuteUntil: 'menos de un minuto desde ahora', + minuteUntil: 'un minuto desde ahora', + minutesUntil: '{delta} minutos desde ahora', + hourUntil: 'una hora desde ahora', + hoursUntil: 'unas {delta} horas desde ahora', + dayUntil: 'un dÃa desde ahora', + daysUntil: '{delta} dÃas desde ahora', + weekUntil: 'una semana desde ahora', + weeksUntil: 'unas {delta} semanas desde ahora', + monthUntil: 'un mes desde ahora', + monthsUntil: '{delta} meses desde ahora', + yearUntil: 'un año desde ahora', + yearsUntil: '{delta} años desde ahora' + +}); + +/* +--- + +name: Locale.es-AR.Date + +description: Date messages for Spanish (Argentina). + +license: MIT-style license + +authors: + - Ãlfons Sanchez + - Diego Massanti + +requires: + - Locale + - Locale.es-ES.Date + +provides: [Locale.es-AR.Date] + +... +*/ + +Locale.define('es-AR').inherit('es-ES', 'Date'); + +/* +--- + +name: Locale.es-AR.Form.Validator + +description: Form Validator messages for Spanish (Argentina). + +license: MIT-style license + +authors: + - Diego Massanti + +requires: + - Locale + +provides: [Locale.es-AR.Form.Validator] + +... +*/ + +Locale.define('es-AR', 'FormValidator', { + + required: 'Este campo es obligatorio.', + minLength: 'Por favor ingrese al menos {minLength} caracteres (ha ingresado {length} caracteres).', + maxLength: 'Por favor no ingrese más de {maxLength} caracteres (ha ingresado {length} caracteres).', + integer: 'Por favor ingrese un número entero en este campo. Números con decimales (p.e. 1,25) no se permiten.', + numeric: 'Por favor ingrese solo valores numéricos en este campo (p.e. "1" o "1,1" o "-1" o "-1,1").', + digits: 'Por favor use sólo números y puntuación en este campo (por ejemplo, un número de teléfono con guiones y/o puntos no está permitido).', + alpha: 'Por favor use sólo letras (a-z) en este campo. No se permiten espacios ni otros caracteres.', + alphanum: 'Por favor, usa sólo letras (a-z) o números (0-9) en este campo. No se permiten espacios u otros caracteres.', + dateSuchAs: 'Por favor ingrese una fecha válida como {date}', + dateInFormatMDY: 'Por favor ingrese una fecha válida, utulizando el formato DD/MM/YYYY (p.e. "31/12/1999")', + email: 'Por favor, ingrese una dirección de e-mail válida. Por ejemplo, "fred@dominio.com".', + url: 'Por favor ingrese una URL válida como http://www.example.com.', + currencyDollar: 'Por favor ingrese una cantidad válida de pesos. Por ejemplo $100,00 .', + oneRequired: 'Por favor ingrese algo para por lo menos una de estas entradas.', + errorPrefix: 'Error: ', + warningPrefix: 'Advertencia: ', + + // Form.Validator.Extras + noSpace: 'No se permiten espacios en este campo.', + reqChkByNode: 'No hay elementos seleccionados.', + requiredChk: 'Este campo es obligatorio.', + reqChkByName: 'Por favor selecciona una {label}.', + match: 'Este campo necesita coincidir con el campo {matchName}', + startDate: 'la fecha de inicio', + endDate: 'la fecha de fin', + currentDate: 'la fecha actual', + afterDate: 'La fecha debe ser igual o posterior a {label}.', + beforeDate: 'La fecha debe ser igual o anterior a {label}.', + startMonth: 'Por favor selecciona un mes de origen', + sameMonth: 'Estas dos fechas deben estar en el mismo mes - debes cambiar una u otra.' + +}); + +/* +--- + +name: Locale.es-ES.Form.Validator + +description: Form Validator messages for Spanish. + +license: MIT-style license + +authors: + - Ãlfons Sanchez + +requires: + - Locale + +provides: [Locale.es-ES.Form.Validator] + +... +*/ + +Locale.define('es-ES', 'FormValidator', { + + required: 'Este campo es obligatorio.', + minLength: 'Por favor introduce al menos {minLength} caracteres (has introducido {length} caracteres).', + maxLength: 'Por favor introduce no más de {maxLength} caracteres (has introducido {length} caracteres).', + integer: 'Por favor introduce un número entero en este campo. Números con decimales (p.e. 1,25) no se permiten.', + numeric: 'Por favor introduce solo valores numéricos en este campo (p.e. "1" o "1,1" o "-1" o "-1,1").', + digits: 'Por favor usa solo números y puntuación en este campo (por ejemplo, un número de teléfono con guiones y puntos no esta permitido).', + alpha: 'Por favor usa letras solo (a-z) en este campo. No se admiten espacios ni otros caracteres.', + alphanum: 'Por favor, usa solo letras (a-z) o números (0-9) en este campo. No se admiten espacios ni otros caracteres.', + dateSuchAs: 'Por favor introduce una fecha válida como {date}', + dateInFormatMDY: 'Por favor introduce una fecha válida como DD/MM/YYYY (p.e. "31/12/1999")', + email: 'Por favor, introduce una dirección de email válida. Por ejemplo, "fred@domain.com".', + url: 'Por favor introduce una URL válida como http://www.example.com.', + currencyDollar: 'Por favor introduce una cantidad válida de â¬. Por ejemplo â¬100,00 .', + oneRequired: 'Por favor introduce algo para por lo menos una de estas entradas.', + errorPrefix: 'Error: ', + warningPrefix: 'Aviso: ', + + // Form.Validator.Extras + noSpace: 'No pueden haber espacios en esta entrada.', + reqChkByNode: 'No hay elementos seleccionados.', + requiredChk: 'Este campo es obligatorio.', + reqChkByName: 'Por favor selecciona una {label}.', + match: 'Este campo necesita coincidir con el campo {matchName}', + startDate: 'la fecha de inicio', + endDate: 'la fecha de fin', + currentDate: 'la fecha actual', + afterDate: 'La fecha debe ser igual o posterior a {label}.', + beforeDate: 'La fecha debe ser igual o anterior a {label}.', + startMonth: 'Por favor selecciona un mes de origen', + sameMonth: 'Estas dos fechas deben estar en el mismo mes - debes cambiar una u otra.' + +}); + +/* +--- + +name: Locale.es-VE.Date + +description: Date messages for Spanish (Venezuela). + +license: MIT-style license + +authors: + - Daniel Barreto + +requires: + - Locale + - Locale.es-ES.Date + +provides: [Locale.es-VE.Date] + +... +*/ + +Locale.define('es-VE').inherit('es-ES', 'Date'); + +/* +--- + +name: Locale.es-VE.Form.Validator + +description: Form Validator messages for Spanish (Venezuela). + +license: MIT-style license + +authors: + - Daniel Barreto + +requires: + - Locale + - Locale.es-ES.Form.Validator + +provides: [Locale.es-VE.Form.Validator] + +... +*/ + +Locale.define('es-VE', 'FormValidator', { + + digits: 'Por favor usa solo números y puntuación en este campo. Por ejemplo, un número de teléfono con guiones y puntos no esta permitido.', + alpha: 'Por favor usa solo letras (a-z) en este campo. No se admiten espacios ni otros caracteres.', + currencyDollar: 'Por favor introduce una cantidad válida de Bs. Por ejemplo Bs. 100,00 .', + oneRequired: 'Por favor introduce un valor para por lo menos una de estas entradas.', + + // Form.Validator.Extras + startDate: 'La fecha de inicio', + endDate: 'La fecha de fin', + currentDate: 'La fecha actual' + +}).inherit('es-ES', 'FormValidator'); + +/* +--- + +name: Locale.es-VE.Number + +description: Number messages for Spanish (Venezuela). + +license: MIT-style license + +authors: + - Daniel Barreto + +requires: + - Locale + +provides: [Locale.es-VE.Number] + +... +*/ + +Locale.define('es-VE', 'Number', { + + decimal: ',', + group: '.', +/* + decimals: 0, + precision: 0, +*/ + // Negative/Currency/percentage will mixin Number + negative: { + prefix: '-' + }, + + currency: { + decimals: 2, + prefix: 'Bs. ' + }, + + percentage: { + decimals: 2, + suffix: '%' + } + +}); + +/* +--- + +name: Locale.et-EE.Date + +description: Date messages for Estonian. + +license: MIT-style license + +authors: + - Kevin Valdek + +requires: + - Locale + +provides: [Locale.et-EE.Date] + +... +*/ + +Locale.define('et-EE', 'Date', { + + months: ['jaanuar', 'veebruar', 'mÀrts', 'aprill', 'mai', 'juuni', 'juuli', 'august', 'september', 'oktoober', 'november', 'detsember'], + months_abbr: ['jaan', 'veebr', 'mÀrts', 'apr', 'mai', 'juuni', 'juuli', 'aug', 'sept', 'okt', 'nov', 'dets'], + days: ['pÃŒhapÀev', 'esmaspÀev', 'teisipÀev', 'kolmapÀev', 'neljapÀev', 'reede', 'laupÀev'], + days_abbr: ['pÃŒhap', 'esmasp', 'teisip', 'kolmap', 'neljap', 'reede', 'laup'], + + // Culture's date order: MM.DD.YYYY + dateOrder: ['month', 'date', 'year'], + shortDate: '%m.%d.%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: 'vÀhem kui minut aega tagasi', + minuteAgo: 'umbes minut aega tagasi', + minutesAgo: '{delta} minutit tagasi', + hourAgo: 'umbes tund aega tagasi', + hoursAgo: 'umbes {delta} tundi tagasi', + dayAgo: '1 pÀev tagasi', + daysAgo: '{delta} pÀeva tagasi', + weekAgo: '1 nÀdal tagasi', + weeksAgo: '{delta} nÀdalat tagasi', + monthAgo: '1 kuu tagasi', + monthsAgo: '{delta} kuud tagasi', + yearAgo: '1 aasta tagasi', + yearsAgo: '{delta} aastat tagasi', + + lessThanMinuteUntil: 'vÀhem kui minuti aja pÀrast', + minuteUntil: 'umbes minuti aja pÀrast', + minutesUntil: '{delta} minuti pÀrast', + hourUntil: 'umbes tunni aja pÀrast', + hoursUntil: 'umbes {delta} tunni pÀrast', + dayUntil: '1 pÀeva pÀrast', + daysUntil: '{delta} pÀeva pÀrast', + weekUntil: '1 nÀdala pÀrast', + weeksUntil: '{delta} nÀdala pÀrast', + monthUntil: '1 kuu pÀrast', + monthsUntil: '{delta} kuu pÀrast', + yearUntil: '1 aasta pÀrast', + yearsUntil: '{delta} aasta pÀrast' + +}); + +/* +--- + +name: Locale.et-EE.Form.Validator + +description: Form Validator messages for Estonian. + +license: MIT-style license + +authors: + - Kevin Valdek + +requires: + - Locale + +provides: [Locale.et-EE.Form.Validator] + +... +*/ + +Locale.define('et-EE', 'FormValidator', { + + required: 'VÀli peab olema tÀidetud.', + minLength: 'Palun sisestage vÀhemalt {minLength} tÀhte (te sisestasite {length} tÀhte).', + maxLength: 'Palun Àrge sisestage rohkem kui {maxLength} tÀhte (te sisestasite {length} tÀhte).', + integer: 'Palun sisestage vÀljale tÀisarv. KÃŒmnendarvud (nÀiteks 1.25) ei ole lubatud.', + numeric: 'Palun sisestage ainult numbreid vÀljale (nÀiteks "1", "1.1", "-1" või "-1.1").', + digits: 'Palun kasutage ainult numbreid ja kirjavahemÀrke (telefoninumbri sisestamisel on lubatud kasutada kriipse ja punkte).', + alpha: 'Palun kasutage ainult tÀhti (a-z). TÃŒhikud ja teised sÃŒmbolid on keelatud.', + alphanum: 'Palun kasutage ainult tÀhti (a-z) või numbreid (0-9). TÃŒhikud ja teised sÃŒmbolid on keelatud.', + dateSuchAs: 'Palun sisestage kehtiv kuupÀev kujul {date}', + dateInFormatMDY: 'Palun sisestage kehtiv kuupÀev kujul MM.DD.YYYY (nÀiteks: "12.31.1999").', + email: 'Palun sisestage kehtiv e-maili aadress (nÀiteks: "fred@domain.com").', + url: 'Palun sisestage kehtiv URL (nÀiteks: http://www.example.com).', + currencyDollar: 'Palun sisestage kehtiv $ summa (nÀiteks: $100.00).', + oneRequired: 'Palun sisestage midagi vÀhemalt ÃŒhele antud vÀljadest.', + errorPrefix: 'Viga: ', + warningPrefix: 'Hoiatus: ', + + // Form.Validator.Extras + noSpace: 'VÀli ei tohi sisaldada tÃŒhikuid.', + reqChkByNode: 'Ãkski vÀljadest pole valitud.', + requiredChk: 'VÀlja tÀitmine on vajalik.', + reqChkByName: 'Palun valige ÃŒks {label}.', + match: 'VÀli peab sobima {matchName} vÀljaga', + startDate: 'algkuupÀev', + endDate: 'lõppkuupÀev', + currentDate: 'praegune kuupÀev', + afterDate: 'KuupÀev peab olema võrdne või pÀrast {label}.', + beforeDate: 'KuupÀev peab olema võrdne või enne {label}.', + startMonth: 'Palun valige algkuupÀev.', + sameMonth: 'Antud kaks kuupÀeva peavad olema samas kuus - peate muutma ÃŒhte kuupÀeva.' + +}); + +/* +--- + +name: Locale.fa.Date + +description: Date messages for Persian. + +license: MIT-style license + +authors: + - Amir Hossein Hodjaty Pour + +requires: + - Locale + +provides: [Locale.fa.Date] + +... +*/ + +Locale.define('fa', 'Date', { + + months: ['ÚØ§ÙÙÛÙ', 'ÙÙØ±ÛÙ', 'Ù
ارس', 'آٟرÛÙ', 'Ù
Ù', 'ÚÙØŠÙ', 'ÚÙØŠÛÙ', 'Ø¢Ú¯ÙØ³Øª', 'سٟتاÙ
ؚر', 'اکتؚر', 'ÙÙØ§Ù
ؚر', 'دساÙ
ؚر'], + months_abbr: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'], + days: ['ÛÚ©ØŽÙØšÙ', 'Ø¯ÙØŽÙØšÙ', 'Ø³Ù ØŽÙØšÙ', 'ÚÙØ§Ø±ØŽÙØšÙ', 'ÙŸÙØ¬ØŽÙØšÙ', 'جÙ
عÙ', 'ØŽÙØšÙ'], + days_abbr: ['Ù', 'د', 'س', 'Ú', 'ÙŸ', 'ج', 'ØŽ'], + + // Culture's date order: MM/DD/YYYY + dateOrder: ['month', 'date', 'year'], + shortDate: '%m/%d/%Y', + shortTime: '%I:%M%p', + AM: 'Ù.Øž', + PM: 'Øš.Øž', + + // Date.Extras + ordinal: 'اÙ
', + + lessThanMinuteAgo: 'Ú©Ù
تر از ÛÚ© دÙÛÙÙ ÙŸÛØŽ', + minuteAgo: 'ØØ¯Ùد ÛÚ© دÙÛÙÙ ÙŸÛØŽ', + minutesAgo: '{delta} دÙÛÙÙ ÙŸÛØŽ', + hourAgo: 'ØØ¯Ùد ÛÚ© ساعت ÙŸÛØŽ', + hoursAgo: 'ØØ¯Ùد {delta} ساعت ÙŸÛØŽ', + dayAgo: '1 Ø±ÙØ² ÙŸÛØŽ', + daysAgo: '{delta} Ø±ÙØ² ÙŸÛØŽ', + weekAgo: '1 ÙÙØªÙ ÙŸÛØŽ', + weeksAgo: '{delta} ÙÙØªÙ ÙŸÛØŽ', + monthAgo: '1 Ù
Ø§Ù ÙŸÛØŽ', + monthsAgo: '{delta} Ù
Ø§Ù ÙŸÛØŽ', + yearAgo: '1 Ø³Ø§Ù ÙŸÛØŽ', + yearsAgo: '{delta} Ø³Ø§Ù ÙŸÛØŽ', + + lessThanMinuteUntil: 'Ú©Ù
تر از ÛÚ© دÙÛÙ٠از ØØ§Ùا', + minuteUntil: 'ØØ¯Ùد ÛÚ© دÙÛÙ٠از ØØ§Ùا', + minutesUntil: '{delta} دÙÛÙ٠از ØØ§Ùا', + hourUntil: 'ØØ¯Ùد ÛÚ© ساعت از ØØ§Ùا', + hoursUntil: 'ØØ¯Ùد {delta} ساعت از ØØ§Ùا', + dayUntil: '1 Ø±ÙØ² از ØØ§Ùا', + daysUntil: '{delta} Ø±ÙØ² از ØØ§Ùا', + weekUntil: '1 ÙÙØªÙ از ØØ§Ùا', + weeksUntil: '{delta} ÙÙØªÙ از ØØ§Ùا', + monthUntil: '1 Ù
ا٠از ØØ§Ùا', + monthsUntil: '{delta} Ù
ا٠از ØØ§Ùا', + yearUntil: '1 سا٠از ØØ§Ùا', + yearsUntil: '{delta} سا٠از ØØ§Ùا' + +}); + +/* +--- + +name: Locale.fa.Form.Validator + +description: Form Validator messages for Persian. + +license: MIT-style license + +authors: + - Amir Hossein Hodjaty Pour + +requires: + - Locale + +provides: [Locale.fa.Form.Validator] + +... +*/ + +Locale.define('fa', 'FormValidator', { + + required: 'اÛÙ ÙÛÙØ¯ Ø§ÙØ²Ø§Ù
Û Ø§Ø³Øª.', + minLength: 'ØŽÙ
ا ØšØ§ÛØ¯ ØØ¯Ø§ÙÙ {minLength} ØØ±Ù ÙØ§Ø±Ø¯ Ú©ÙÛØ¯ ({length} ØØ±Ù ÙØ§Ø±Ø¯ Ú©Ø±Ø¯Ù Ø§ÛØ¯).', + maxLength: 'ÙØ·Ùا ØØ¯Ø§Ú©Ø«Ø± {maxLength} ØØ±Ù ÙØ§Ø±Ø¯ Ú©ÙÛØ¯ (ØŽÙ
ا {length} ØØ±Ù ÙØ§Ø±Ø¯ Ú©Ø±Ø¯Ù Ø§ÛØ¯).', + integer: 'ÙØ·Ùا از عدد صØÛØ Ø§Ø³ØªÙØ§Ø¯Ù Ú©ÙÛØ¯. اعداد Ø§Ø¹ØŽØ§Ø±Û (Ù
اÙÙØ¯ 1.25) Ù
جاز ÙÛØ³ØªÙد.', + numeric: 'ÙØ·Ùا ÙÙØ· Ø¯Ø§Ø¯Ù Ø¹Ø¯Ø¯Û ÙØ§Ø±Ø¯ Ú©ÙÛØ¯ (Ù
اÙÙØ¯ "1" ÛØ§ "1.1" ÛØ§ "1-" ÛØ§ "1.1-").', + digits: 'ÙØ·Ùا ÙÙØ· از اعداد Ù Ø¹ÙØ§Ù
ØªÙØ§ در اÛÙ ÙÛÙØ¯ Ø§Ø³ØªÙØ§Ø¯Ù Ú©ÙÛØ¯ (ØšØ±Ø§Û Ù
ثا٠؎Ù
ار٠تÙÙ٠ؚا خط ØªÛØ±Ù Ù ÙÙØ·Ù ÙØ§ØšÙ ÙØšÙ٠است).', + alpha: 'ÙØ·Ùا ÙÙØ· از ØØ±Ù٠اÙÙØšØ§Ø¡ ØšØ±Ø§Û Ø§Û٠ؚخ؎ Ø§Ø³ØªÙØ§Ø¯Ù Ú©ÙÛØ¯. Ú©Ø§Ø±Ø§Ú©ØªØ±ÙØ§Û دÛگر Ù ÙØ§ØµÙÙ Ù
جاز ÙÛØ³ØªÙد.', + alphanum: 'ÙØ·Ùا ÙÙØ· از ØØ±Ù٠اÙÙØšØ§Ø¡ ٠اعداد در اÛ٠ؚخ؎ Ø§Ø³ØªÙØ§Ø¯Ù Ú©ÙÛØ¯. Ú©Ø§Ø±Ø§Ú©ØªØ±ÙØ§Û دÛگر Ù ÙØ§ØµÙÙ Ù
جاز ÙÛØ³ØªÙد.', + dateSuchAs: 'ÙØ·Ùا ÛÚ© ØªØ§Ø±ÛØ® Ù
عتؚر Ù
اÙÙØ¯ {date} ÙØ§Ø±Ø¯ Ú©ÙÛØ¯.', + dateInFormatMDY: 'ÙØ·Ùا ÛÚ© ØªØ§Ø±ÛØ® Ù
عتؚر ØšÙ ØŽÚ©Ù MM/DD/YYYY ÙØ§Ø±Ø¯ Ú©ÙÛØ¯ (Ù
اÙÙØ¯ "12/31/1999").', + email: 'ÙØ·Ùا ÛÚ© آدرس اÛÙ
ÛÙ Ù
عتؚر ÙØ§Ø±Ø¯ Ú©ÙÛØ¯. ØšØ±Ø§Û Ù
ثا٠"fred@domain.com".', + url: 'ÙØ·Ùا ÛÚ© URL Ù
عتؚر Ù
اÙÙØ¯ http://www.example.com ÙØ§Ø±Ø¯ Ú©ÙÛØ¯.', + currencyDollar: 'ÙØ·Ùا ÛÚ© Ù
ØØ¯Ùد٠Ù
عتؚر ØšØ±Ø§Û Ø§Û٠ؚخ؎ ÙØ§Ø±Ø¯ Ú©ÙÛØ¯ Ù
اÙÙØ¯ 100.00$ .', + oneRequired: 'ÙØ·Ùا ØØ¯Ø§ÙÙ ÛÚ©Û Ø§Ø² ÙÛÙØ¯Ùا را ٟر Ú©ÙÛØ¯.', + errorPrefix: 'خطا: ', + warningPrefix: 'ÙØŽØ¯Ø§Ø±: ', + + // Form.Validator.Extras + noSpace: 'Ø§Ø³ØªÙØ§Ø¯Ù از ÙØ§ØµÙ٠در اÛ٠ؚخ؎ Ù
جاز ÙÛØ³Øª.', + reqChkByNode: 'Ù
ÙØ±Ø¯Û Ø§ÙØªØ®Ø§Øš ÙØŽØ¯Ù است.', + requiredChk: 'اÛÙ ÙÛÙØ¯ Ø§ÙØ²Ø§Ù
Û Ø§Ø³Øª.', + reqChkByName: 'ÙØ·Ùا ÛÚ© {label} را Ø§ÙØªØ®Ø§Øš Ú©ÙÛØ¯.', + match: 'اÛÙ ÙÛÙØ¯ ØšØ§ÛØ¯ ؚا ÙÛÙØ¯ {matchName} Ù
Ø·Ø§ØšÙØª دا؎ت٠ؚا؎د.', + startDate: 'ØªØ§Ø±ÛØ® ØŽØ±ÙØ¹', + endDate: 'ØªØ§Ø±ÛØ® ÙŸØ§ÛØ§Ù', + currentDate: 'ØªØ§Ø±ÛØ® Ú©ÙÙÙÛ', + afterDate: 'ØªØ§Ø±ÛØ® Ù
ÛØšØ§Ûست ؚراؚر ÛØ§ ؚعد از {label} ؚا؎د', + beforeDate: 'ØªØ§Ø±ÛØ® Ù
ÛØšØ§Ûست ؚراؚر ÛØ§ ÙØšÙ از {label} ؚا؎د', + startMonth: 'ÙØ·Ùا Ù
Ø§Ù ØŽØ±ÙØ¹ را Ø§ÙØªØ®Ø§Øš Ú©ÙÛØ¯', + sameMonth: 'اÛÙ Ø¯Ù ØªØ§Ø±ÛØ® ØšØ§ÛØ¯ در ÛÚ© Ù
Ø§Ù ØšØ§ØŽÙØ¯ - ØŽÙ
ا ØšØ§ÛØ¯ ÛÚ©Û ÛØ§ ÙØ± د٠را تغÛÛØ± دÙÛØ¯.', + creditcard: 'ØŽÙ
ار٠کارت Ø§Ø¹ØªØšØ§Ø±Û Ú©Ù ÙØ§Ø±Ø¯ Ú©Ø±Ø¯Ù Ø§ÛØ¯ Ù
عتؚر ÙÛØ³Øª. ÙØ·Ùا ØŽÙ
ار٠را ØšØ±Ø±Ø³Û Ú©ÙÛØ¯ Ù Ù
جددا ØªÙØ§ØŽ Ú©ÙÛØ¯. {length} رÙÙ
ÙØ§Ø±Ø¯ ؎د٠است.' + +}); + +/* +--- + +name: Locale.fi-FI.Date + +description: Date messages for Finnish. + +license: MIT-style license + +authors: + - ksel + +requires: + - Locale + +provides: [Locale.fi-FI.Date] + +... +*/ + +Locale.define('fi-FI', 'Date', { + + // NOTE: months and days are not capitalized in finnish + months: ['tammikuu', 'helmikuu', 'maaliskuu', 'huhtikuu', 'toukokuu', 'kesÀkuu', 'heinÀkuu', 'elokuu', 'syyskuu', 'lokakuu', 'marraskuu', 'joulukuu'], + + // these abbreviations are really not much used in finnish because they obviously won't abbreviate very much. ;) + // NOTE: sometimes one can see forms such as "tammi", "helmi", etc. but that is not proper finnish. + months_abbr: ['tammik.', 'helmik.', 'maalisk.', 'huhtik.', 'toukok.', 'kesÀk.', 'heinÀk.', 'elok.', 'syysk.', 'lokak.', 'marrask.', 'jouluk.'], + + days: ['sunnuntai', 'maanantai', 'tiistai', 'keskiviikko', 'torstai', 'perjantai', 'lauantai'], + days_abbr: ['su', 'ma', 'ti', 'ke', 'to', 'pe', 'la'], + + // Culture's date order: DD/MM/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d.%m.%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '.', + + lessThanMinuteAgo: 'vajaa minuutti sitten', + minuteAgo: 'noin minuutti sitten', + minutesAgo: '{delta} minuuttia sitten', + hourAgo: 'noin tunti sitten', + hoursAgo: 'noin {delta} tuntia sitten', + dayAgo: 'pÀivÀ sitten', + daysAgo: '{delta} pÀivÀÀ sitten', + weekAgo: 'viikko sitten', + weeksAgo: '{delta} viikkoa sitten', + monthAgo: 'kuukausi sitten', + monthsAgo: '{delta} kuukautta sitten', + yearAgo: 'vuosi sitten', + yearsAgo: '{delta} vuotta sitten', + + lessThanMinuteUntil: 'vajaan minuutin kuluttua', + minuteUntil: 'noin minuutin kuluttua', + minutesUntil: '{delta} minuutin kuluttua', + hourUntil: 'noin tunnin kuluttua', + hoursUntil: 'noin {delta} tunnin kuluttua', + dayUntil: 'pÀivÀn kuluttua', + daysUntil: '{delta} pÀivÀn kuluttua', + weekUntil: 'viikon kuluttua', + weeksUntil: '{delta} viikon kuluttua', + monthUntil: 'kuukauden kuluttua', + monthsUntil: '{delta} kuukauden kuluttua', + yearUntil: 'vuoden kuluttua', + yearsUntil: '{delta} vuoden kuluttua' + +}); + +/* +--- + +name: Locale.fi-FI.Form.Validator + +description: Form Validator messages for Finnish. + +license: MIT-style license + +authors: + - ksel + +requires: + - Locale + +provides: [Locale.fi-FI.Form.Validator] + +... +*/ + +Locale.define('fi-FI', 'FormValidator', { + + required: 'TÀmÀ kenttÀ on pakollinen.', + minLength: 'Ole hyvÀ ja anna vÀhintÀÀn {minLength} merkkiÀ (annoit {length} merkkiÀ).', + maxLength: 'ÃlÀ anna enempÀÀ kuin {maxLength} merkkiÀ (annoit {length} merkkiÀ).', + integer: 'Ole hyvÀ ja anna kokonaisluku. Luvut, joissa on desimaaleja (esim. 1.25) eivÀt ole sallittuja.', + numeric: 'Anna tÀhÀn kenttÀÀn lukuarvo (kuten "1" tai "1.1" tai "-1" tai "-1.1").', + digits: 'KÀytÀ pelkÀstÀÀn numeroita ja vÀlimerkkejÀ tÀssÀ kentÀssÀ (syötteet, kuten esim. puhelinnumero, jossa on vÀliviivoja, pilkkuja tai pisteitÀ, kelpaa).', + alpha: 'Anna tÀhÀn kenttÀÀn vain kirjaimia (a-z). VÀlilyönnit tai muut merkit eivÀt ole sallittuja.', + alphanum: 'Anna tÀhÀn kenttÀÀn vain kirjaimia (a-z) tai numeroita (0-9). VÀlilyönnit tai muut merkit eivÀt ole sallittuja.', + dateSuchAs: 'Ole hyvÀ ja anna kelvollinen pÀivmÀÀrÀ, kuten esimerkiksi {date}', + dateInFormatMDY: 'Ole hyvÀ ja anna kelvollinen pÀivÀmÀÀrÀ muodossa pp/kk/vvvv (kuten "12/31/1999")', + email: 'Ole hyvÀ ja anna kelvollinen sÀhköpostiosoite (kuten esimerkiksi "matti@meikalainen.com").', + url: 'Ole hyvÀ ja anna kelvollinen URL, kuten esimerkiksi http://www.example.com.', + currencyDollar: 'Ole hyvÀ ja anna kelvollinen eurosumma (kuten esimerkiksi 100,00 EUR) .', + oneRequired: 'Ole hyvÀ ja syötÀ jotakin ainakin johonkin nÀistÀ kentistÀ.', + errorPrefix: 'Virhe: ', + warningPrefix: 'Varoitus: ', + + // Form.Validator.Extras + noSpace: 'TÀssÀ syötteessÀ ei voi olla vÀlilyöntejÀ', + reqChkByNode: 'Ei valintoja.', + requiredChk: 'TÀmÀ kenttÀ on pakollinen.', + reqChkByName: 'Ole hyvÀ ja valitse {label}.', + match: 'TÀmÀn kentÀn tulee vastata kenttÀÀ {matchName}', + startDate: 'alkupÀivÀmÀÀrÀ', + endDate: 'loppupÀivÀmÀÀrÀ', + currentDate: 'nykyinen pÀivÀmÀÀrÀ', + afterDate: 'PÀivÀmÀÀrÀn tulisi olla sama tai myöhÀisempi ajankohta kuin {label}.', + beforeDate: 'PÀivÀmÀÀrÀn tulisi olla sama tai aikaisempi ajankohta kuin {label}.', + startMonth: 'Ole hyvÀ ja valitse aloituskuukausi', + sameMonth: 'NÀiden kahden pÀivÀmÀÀrÀn tulee olla saman kuun sisÀllÀ -- sinun pitÀÀ muuttaa jompaa kumpaa.', + creditcard: 'Annettu luottokortin numero ei kelpaa. Ole hyvÀ ja tarkista numero sekÀ yritÀ uudelleen. {length} numeroa syötetty.' + +}); + +/* +--- + +name: Locale.fi-FI.Number + +description: Finnish number messages + +license: MIT-style license + +authors: + - ksel + +requires: + - Locale + - Locale.EU.Number + +provides: [Locale.fi-FI.Number] + +... +*/ + +Locale.define('fi-FI', 'Number', { + + group: ' ' // grouped by space + +}).inherit('EU', 'Number'); + +/* +--- + +name: Locale.fr-FR.Date + +description: Date messages for French. + +license: MIT-style license + +authors: + - Nicolas Sorosac + - Antoine Abt + +requires: + - Locale + +provides: [Locale.fr-FR.Date] + +... +*/ + +Locale.define('fr-FR', 'Date', { + + months: ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'], + months_abbr: ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sept.', 'oct.', 'nov.', 'déc.'], + days: ['Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi'], + days_abbr: ['dim.', 'lun.', 'mar.', 'mer.', 'jeu.', 'ven.', 'sam.'], + + // Culture's date order: DD/MM/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d/%m/%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: function(dayOfMonth){ + return (dayOfMonth > 1) ? '' : 'er'; + }, + + lessThanMinuteAgo: "il y a moins d'une minute", + minuteAgo: 'il y a une minute', + minutesAgo: 'il y a {delta} minutes', + hourAgo: 'il y a une heure', + hoursAgo: 'il y a {delta} heures', + dayAgo: 'il y a un jour', + daysAgo: 'il y a {delta} jours', + weekAgo: 'il y a une semaine', + weeksAgo: 'il y a {delta} semaines', + monthAgo: 'il y a 1 mois', + monthsAgo: 'il y a {delta} mois', + yearthAgo: 'il y a 1 an', + yearsAgo: 'il y a {delta} ans', + + lessThanMinuteUntil: "dans moins d'une minute", + minuteUntil: 'dans une minute', + minutesUntil: 'dans {delta} minutes', + hourUntil: 'dans une heure', + hoursUntil: 'dans {delta} heures', + dayUntil: 'dans un jour', + daysUntil: 'dans {delta} jours', + weekUntil: 'dans 1 semaine', + weeksUntil: 'dans {delta} semaines', + monthUntil: 'dans 1 mois', + monthsUntil: 'dans {delta} mois', + yearUntil: 'dans 1 an', + yearsUntil: 'dans {delta} ans' + +}); + +/* +--- + +name: Locale.fr-FR.Form.Validator + +description: Form Validator messages for French. + +license: MIT-style license + +authors: + - Miquel Hudin + - Nicolas Sorosac + +requires: + - Locale + +provides: [Locale.fr-FR.Form.Validator] + +... +*/ + +Locale.define('fr-FR', 'FormValidator', { + + required: 'Ce champ est obligatoire.', + length: 'Veuillez saisir {length} caractère(s) (vous avez saisi {elLength} caractère(s)', + minLength: 'Veuillez saisir un minimum de {minLength} caractère(s) (vous avez saisi {length} caractère(s)).', + maxLength: 'Veuillez saisir un maximum de {maxLength} caractère(s) (vous avez saisi {length} caractère(s)).', + integer: 'Veuillez saisir un nombre entier dans ce champ. Les nombres décimaux (ex : "1,25") ne sont pas autorisés.', + numeric: 'Veuillez saisir uniquement des chiffres dans ce champ (ex : "1" ou "1,1" ou "-1" ou "-1,1").', + digits: "Veuillez saisir uniquement des chiffres et des signes de ponctuation dans ce champ (ex : un numéro de téléphone avec des traits d'union est autorisé).", + alpha: 'Veuillez saisir uniquement des lettres (a-z) dans ce champ. Les espaces ou autres caractères ne sont pas autorisés.', + alphanum: 'Veuillez saisir uniquement des lettres (a-z) ou des chiffres (0-9) dans ce champ. Les espaces ou autres caractères ne sont pas autorisés.', + dateSuchAs: 'Veuillez saisir une date correcte comme {date}', + dateInFormatMDY: 'Veuillez saisir une date correcte, au format JJ/MM/AAAA (ex : "31/11/1999").', + email: 'Veuillez saisir une adresse de courrier électronique. Par exemple "fred@domaine.com".', + url: 'Veuillez saisir une URL, comme http://www.exemple.com.', + currencyDollar: 'Veuillez saisir une quantité correcte. Par exemple 100,00€.', + oneRequired: 'Veuillez sélectionner au moins une de ces options.', + errorPrefix: 'Erreur : ', + warningPrefix: 'Attention : ', + + // Form.Validator.Extras + noSpace: "Ce champ n'accepte pas les espaces.", + reqChkByNode: "Aucun élément n'est sélectionné.", + requiredChk: 'Ce champ est obligatoire.', + reqChkByName: 'Veuillez sélectionner un(e) {label}.', + match: 'Ce champ doit correspondre avec le champ {matchName}.', + startDate: 'date de début', + endDate: 'date de fin', + currentDate: 'date actuelle', + afterDate: 'La date doit être identique ou postérieure à {label}.', + beforeDate: 'La date doit être identique ou antérieure à {label}.', + startMonth: 'Veuillez sélectionner un mois de début.', + sameMonth: 'Ces deux dates doivent être dans le même mois - vous devez en modifier une.', + creditcard: 'Le numéro de carte de crédit est invalide. Merci de vérifier le numéro et de réessayer. Vous avez entré {length} chiffre(s).' + +}); + +/* +--- + +name: Locale.fr-FR.Number + +description: Number messages for French. + +license: MIT-style license + +authors: + - Arian Stolwijk + - sv1l + +requires: + - Locale + - Locale.EU.Number + +provides: [Locale.fr-FR.Number] + +... +*/ + +Locale.define('fr-FR', 'Number', { + + group: ' ' // In fr-FR localization, group character is a blank space + +}).inherit('EU', 'Number'); + +/* +--- + +name: Locale.he-IL.Date + +description: Date messages for Hebrew. + +license: MIT-style license + +authors: + - Elad Ossadon + +requires: + - Locale + +provides: [Locale.he-IL.Date] + +... +*/ + +Locale.define('he-IL', 'Date', { + + months: ['×× ××ך', '×€×ך××ך', '×ךץ', '×׀ך××', '×××', '××× ×', '××××', '××××ס×', 'ס׀×××ך', '×××§×××ך', '× ××××ך', '×׊××ך'], + months_abbr: ['×× ××ך', '×€×ך××ך', '×ךץ', '×׀ך××', '×××', '××× ×', '××××', '××××ס×', 'ס׀×××ך', '×××§×××ך', '× ××××ך', '×׊××ך'], + days: ['ך×ש××', '×©× ×', 'ש××ש×', 'ך×××¢×', '×××ש×', 'ש×ש×', 'ש×ת'], + days_abbr: ['ך×ש××', '×©× ×', 'ש××ש×', 'ך×××¢×', '×××ש×', 'ש×ש×', 'ש×ת'], + + // Culture's date order: MM/DD/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d/%m/%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 0, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: '××€× × ×€××ת ×××§×', + minuteAgo: '××€× × ×××§×', + minutesAgo: '××€× × {delta} ××§×ת', + hourAgo: '××€× × ×שע×', + hoursAgo: '××€× × {delta} שע×ת', + dayAgo: '××€× × ×××', + daysAgo: '××€× × {delta} ××××', + weekAgo: '××€× × ×©×××¢', + weeksAgo: '××€× × {delta} ש×××¢×ת', + monthAgo: '××€× × ×××ש', + monthsAgo: '××€× × {delta} ×××ש××', + yearAgo: '××€× × ×©× ×', + yearsAgo: '××€× × {delta} ×©× ××', + + lessThanMinuteUntil: '××¢×× ×€××ת ×××§×', + minuteUntil: '××¢×× ×××§×', + minutesUntil: '××¢×× {delta} ××§×ת', + hourUntil: '××¢×× ×שע×', + hoursUntil: '××¢×× {delta} שע×ת', + dayUntil: '××¢×× ×××', + daysUntil: '××¢×× {delta} ××××', + weekUntil: '××¢×× ×©×××¢', + weeksUntil: '××¢×× {delta} ש×××¢×ת', + monthUntil: '××¢×× ×××ש', + monthsUntil: '××¢×× {delta} ×××ש××', + yearUntil: '××¢×× ×©× ×', + yearsUntil: '××¢×× {delta} ×©× ××' + +}); + +/* +--- + +name: Locale.he-IL.Form.Validator + +description: Form Validator messages for Hebrew. + +license: MIT-style license + +authors: + - Elad Ossadon + +requires: + - Locale + +provides: [Locale.he-IL.Form.Validator] + +... +*/ + +Locale.define('he-IL', 'FormValidator', { + + required: '× × ×××× ×©×× ××.', + minLength: '× × ××××× ××€××ת {minLength} ת×××× (××× ×ª {length} ת××××).', + maxLength: '× × ××××× ×¢× {maxLength} ת×××× (××× ×ª {length} ת××××).', + integer: '× × ××××× ×ס׀ך ש×× ×ש×× ××. ×ס׀ך×× ×¢×©×š×× ××× (××× 1.25) ××× × ×××§×××.', + numeric: '× × ××××× ×¢×š× ××¡×€×š× ×××× ×ש×× ×× (××× "1", "1.1", "-1" ×× "-1.1").', + digits: '× × ××××× ×š×§ ס׀ך×ת ×ס××× × ×׀ך×× ×ש×× ×× (××ש×, ×ס׀ך ×××€×× ×¢× ××§×€×× ×× × ×§×××ת ××× ×××§×).', + alpha: '× × ××××× ×š×§ ××ת××ת ××× ×××ת (a-z) ×ש×× ××. ך××××× ×× ×ª×××× ××ך×× ××× × ×××§×××.', + alphanum: '× × ××××× ×š×§ ××תך××ת ××× ×××ת (a-z) ×× ×¡×€×š×ת (0-9) ×ש×× ××. ××××ך×× ×× ×ª×××× ××ך×× ××× × ×××§×××.', + dateSuchAs: '× × ××××× ×ª×ך×× ×××§×, ××× {date}', + dateInFormatMDY: '× × ××××× ×ª×ך×× ×××§× ××€×ך×× MM/DD/YYYY (××× "12/31/1999")', + email: '× × ××××× ×ת××ת ×××××× ×××§×ת. ××××××: "fred@domain.com".', + url: '× × ××××× ×ת××ת ×תך ×××§×ת, ××× http://www.example.com.', + currencyDollar: '× × ××××× ×¡××× ××××š× ×××§×. ×××××× $100.00.', + oneRequired: '× × ××××ך ××€××ת ×ש×× ×××.', + errorPrefix: 'ש××××: ', + warningPrefix: '×××ך×: ', + + // Form.Validator.Extras + noSpace: '××× ××××× ×š××××× ×ש×× ××.', + reqChkByNode: '× × ××××ך ××ת ×××׀שך×××ת.', + requiredChk: 'ש×× ×× × ×ךש.', + reqChkByName: '× × ××××ך {label}.', + match: 'ש×× ×× ×Š×š×× ××ת××× ×ש×× {matchName}', + startDate: 'ת×ך×× ××ת×××', + endDate: 'ת×ך×× ×ס×××', + currentDate: '×ת×ך×× ×× ××××', + afterDate: '×ת×ך×× ×Š×š×× ××××ת ××× ×× ×××š× {label}.', + beforeDate: '×ת×ך×× ×Š×š×× ××××ת ××× ×× ××€× × {label}.', + startMonth: '× × ××××ך ×××ש ×ת×××', + sameMonth: '×©× × ×ª×ך×××× ××× ×Š×š×××× ××××ת ××××ª× ×××ש - × × ××©× ×ת ××× ×ת×ך××××.', + creditcard: '×ס׀ך ×ך××ס ××שך×× ×©×××× ××× × ×××§×. × × ×××××§ ×©× ×ת. ×××× × {length} ס׀ך×ת.' + +}); + +/* +--- + +name: Locale.he-IL.Number + +description: Number messages for Hebrew. + +license: MIT-style license + +authors: + - Elad Ossadon + +requires: + - Locale + +provides: [Locale.he-IL.Number] + +... +*/ + +Locale.define('he-IL', 'Number', { + + decimal: '.', + group: ',', + + currency: { + suffix: ' âª' + } + +}); + +/* +--- + +name: Locale.hu-HU.Date + +description: Date messages for Hungarian. + +license: MIT-style license + +authors: + - Zsolt SzegheÅ + +requires: + - Locale + +provides: [Locale.hu-HU.Date] + +... +*/ + +Locale.define('hu-HU', 'Date', { + + months: ['Január', 'Február', 'Március', 'Ãprilis', 'Május', 'Június', 'Július', 'Augusztus', 'Szeptember', 'Október', 'November', 'December'], + months_abbr: ['jan.', 'febr.', 'márc.', 'ápr.', 'máj.', 'jún.', 'júl.', 'aug.', 'szept.', 'okt.', 'nov.', 'dec.'], + days: ['Vasárnap', 'HétfÅ', 'Kedd', 'Szerda', 'CsÃŒtörtök', 'Péntek', 'Szombat'], + days_abbr: ['V', 'H', 'K', 'Sze', 'Cs', 'P', 'Szo'], + + // Culture's date order: YYYY.MM.DD. + dateOrder: ['year', 'month', 'date'], + shortDate: '%Y.%m.%d.', + shortTime: '%I:%M', + AM: 'de.', + PM: 'du.', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '.', + + lessThanMinuteAgo: 'alig egy perce', + minuteAgo: 'egy perce', + minutesAgo: '{delta} perce', + hourAgo: 'egy órája', + hoursAgo: '{delta} órája', + dayAgo: '1 napja', + daysAgo: '{delta} napja', + weekAgo: '1 hete', + weeksAgo: '{delta} hete', + monthAgo: '1 hónapja', + monthsAgo: '{delta} hónapja', + yearAgo: '1 éve', + yearsAgo: '{delta} éve', + + lessThanMinuteUntil: 'alig egy perc múlva', + minuteUntil: 'egy perc múlva', + minutesUntil: '{delta} perc múlva', + hourUntil: 'egy óra múlva', + hoursUntil: '{delta} óra múlva', + dayUntil: '1 nap múlva', + daysUntil: '{delta} nap múlva', + weekUntil: '1 hét múlva', + weeksUntil: '{delta} hét múlva', + monthUntil: '1 hónap múlva', + monthsUntil: '{delta} hónap múlva', + yearUntil: '1 év múlva', + yearsUntil: '{delta} év múlva' + +}); + +/* +--- + +name: Locale.hu-HU.Form.Validator + +description: Form Validator messages for Hungarian. + +license: MIT-style license + +authors: + - Zsolt SzegheÅ + +requires: + - Locale + +provides: [Locale.hu-HU.Form.Validator] + +... +*/ + +Locale.define('hu-HU', 'FormValidator', { + + required: 'A mezÅ kitöltése kötelezÅ.', + minLength: 'Legalább {minLength} karakter megadása szÃŒkséges (megadva {length} karakter).', + maxLength: 'Legfeljebb {maxLength} karakter megadása lehetséges (megadva {length} karakter).', + integer: 'Egész szám megadása szÃŒkséges. A tizedesjegyek (pl. 1.25) nem engedélyezettek.', + numeric: 'Szám megadása szÃŒkséges (pl. "1" vagy "1.1" vagy "-1" vagy "-1.1").', + digits: 'Csak számok és Ãrásjelek megadása lehetséges (pl. telefonszám kötÅjelek és/vagy perjelekkel).', + alpha: 'Csak betűk (a-z) megadása lehetséges. Szóköz és egyéb karakterek nem engedélyezettek.', + alphanum: 'Csak betűk (a-z) vagy számok (0-9) megadása lehetséges. Szóköz és egyéb karakterek nem engedélyezettek.', + dateSuchAs: 'Valós dátum megadása szÃŒkséges (pl. {date}).', + dateInFormatMDY: 'Valós dátum megadása szÃŒkséges ÃÃÃÃ.HH.NN. formában. (pl. "1999.12.31.")', + email: 'Valós e-mail cÃm megadása szÃŒkséges (pl. "fred@domain.hu").', + url: 'Valós URL megadása szÃŒkséges (pl. http://www.example.com).', + currencyDollar: 'Valós pénzösszeg megadása szÃŒkséges (pl. 100.00 Ft.).', + oneRequired: 'Az alábbi mezÅk legalább egyikének kitöltése kötelezÅ.', + errorPrefix: 'Hiba: ', + warningPrefix: 'Figyelem: ', + + // Form.Validator.Extras + noSpace: 'A mezÅ nem tartalmazhat szóközöket.', + reqChkByNode: 'Nincs egyetlen kijelölt elem sem.', + requiredChk: 'A mezÅ kitöltése kötelezÅ.', + reqChkByName: 'Egy {label} kiválasztása szÃŒkséges.', + match: 'A mezÅnek egyeznie kell a(z) {matchName} mezÅvel.', + startDate: 'a kezdet dátuma', + endDate: 'a vég dátuma', + currentDate: 'jelenlegi dátum', + afterDate: 'A dátum nem lehet kisebb, mint {label}.', + beforeDate: 'A dátum nem lehet nagyobb, mint {label}.', + startMonth: 'Kezdeti hónap megadása szÃŒkséges.', + sameMonth: 'A két dátumnak ugyanazon hónapban kell lennie.', + creditcard: 'A megadott bankkártyaszám nem valódi (megadva {length} számjegy).' + +}); + +/* +--- + +name: Locale.it-IT.Date + +description: Date messages for Italian. + +license: MIT-style license. + +authors: + - Andrea Novero + - Valerio Proietti + +requires: + - Locale + +provides: [Locale.it-IT.Date] + +... +*/ + +Locale.define('it-IT', 'Date', { + + months: ['Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre'], + months_abbr: ['gen', 'feb', 'mar', 'apr', 'mag', 'giu', 'lug', 'ago', 'set', 'ott', 'nov', 'dic'], + days: ['Domenica', 'Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato'], + days_abbr: ['dom', 'lun', 'mar', 'mer', 'gio', 'ven', 'sab'], + + // Culture's date order: DD/MM/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d/%m/%Y', + shortTime: '%H.%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: 'º', + + lessThanMinuteAgo: 'meno di un minuto fa', + minuteAgo: 'circa un minuto fa', + minutesAgo: 'circa {delta} minuti fa', + hourAgo: "circa un'ora fa", + hoursAgo: 'circa {delta} ore fa', + dayAgo: 'circa 1 giorno fa', + daysAgo: 'circa {delta} giorni fa', + weekAgo: 'una settimana fa', + weeksAgo: '{delta} settimane fa', + monthAgo: 'un mese fa', + monthsAgo: '{delta} mesi fa', + yearAgo: 'un anno fa', + yearsAgo: '{delta} anni fa', + + lessThanMinuteUntil: 'tra meno di un minuto', + minuteUntil: 'tra circa un minuto', + minutesUntil: 'tra circa {delta} minuti', + hourUntil: "tra circa un'ora", + hoursUntil: 'tra circa {delta} ore', + dayUntil: 'tra circa un giorno', + daysUntil: 'tra circa {delta} giorni', + weekUntil: 'tra una settimana', + weeksUntil: 'tra {delta} settimane', + monthUntil: 'tra un mese', + monthsUntil: 'tra {delta} mesi', + yearUntil: 'tra un anno', + yearsUntil: 'tra {delta} anni' + +}); + +/* +--- + +name: Locale.it-IT.Form.Validator + +description: Form Validator messages for Italian. + +license: MIT-style license + +authors: + - Leonardo Laureti + - Andrea Novero + +requires: + - Locale + +provides: [Locale.it-IT.Form.Validator] + +... +*/ + +Locale.define('it-IT', 'FormValidator', { + + required: 'Il campo è obbligatorio.', + minLength: 'Inserire almeno {minLength} caratteri (ne sono stati inseriti {length}).', + maxLength: 'Inserire al massimo {maxLength} caratteri (ne sono stati inseriti {length}).', + integer: 'Inserire un numero intero. Non sono consentiti decimali (es.: 1.25).', + numeric: 'Inserire solo valori numerici (es.: "1" oppure "1.1" oppure "-1" oppure "-1.1").', + digits: 'Inserire solo numeri e caratteri di punteggiatura. Per esempio è consentito un numero telefonico con trattini o punti.', + alpha: 'Inserire solo lettere (a-z). Non sono consentiti spazi o altri caratteri.', + alphanum: 'Inserire solo lettere (a-z) o numeri (0-9). Non sono consentiti spazi o altri caratteri.', + dateSuchAs: 'Inserire una data valida del tipo {date}', + dateInFormatMDY: 'Inserire una data valida nel formato MM/GG/AAAA (es.: "12/31/1999")', + email: 'Inserire un indirizzo email valido. Per esempio "nome@dominio.com".', + url: 'Inserire un indirizzo valido. Per esempio "http://www.example.com".', + currencyDollar: 'Inserire un importo valido. Per esempio "$100.00".', + oneRequired: 'Completare almeno uno dei campi richiesti.', + errorPrefix: 'Errore: ', + warningPrefix: 'Attenzione: ', + + // Form.Validator.Extras + noSpace: 'Non sono consentiti spazi.', + reqChkByNode: 'Nessuna voce selezionata.', + requiredChk: 'Il campo è obbligatorio.', + reqChkByName: 'Selezionare un(a) {label}.', + match: 'Il valore deve corrispondere al campo {matchName}', + startDate: "data d'inizio", + endDate: 'data di fine', + currentDate: 'data attuale', + afterDate: 'La data deve corrispondere o essere successiva al {label}.', + beforeDate: 'La data deve corrispondere o essere precedente al {label}.', + startMonth: "Selezionare un mese d'inizio", + sameMonth: 'Le due date devono essere dello stesso mese - occorre modificarne una.' + +}); + +/* +--- + +name: Locale.ja-JP.Date + +description: Date messages for Japanese. + +license: MIT-style license + +authors: + - Noritaka Horio + +requires: + - Locale + +provides: [Locale.ja-JP.Date] + +... +*/ + +Locale.define('ja-JP', 'Date', { + + months: ['1æ', '2æ', '3æ', '4æ', '5æ', '6æ', '7æ', '8æ', '9æ', '10æ', '11æ', '12æ'], + months_abbr: ['1æ', '2æ', '3æ', '4æ', '5æ', '6æ', '7æ', '8æ', '9æ', '10æ', '11æ', '12æ'], + days: ['æ¥ææ¥', 'æææ¥', 'ç«ææ¥', 'æ°Žææ¥', 'æšææ¥', 'éææ¥', 'åææ¥'], + days_abbr: ['æ¥', 'æ', 'ç«', 'æ°Ž', 'æš', 'é', 'å'], + + // Culture's date order: YYYY/MM/DD + dateOrder: ['year', 'month', 'date'], + shortDate: '%Y/%m/%d', + shortTime: '%H:%M', + AM: 'åå', + PM: 'ååŸ', + firstDayOfWeek: 0, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: '1å以å
å', + minuteAgo: 'çŽ1åå', + minutesAgo: 'çŽ{delta}åå', + hourAgo: 'çŽ1æéå', + hoursAgo: 'çŽ{delta}æéå', + dayAgo: '1æ¥å', + daysAgo: '{delta}æ¥å', + weekAgo: '1é±éå', + weeksAgo: '{delta}é±éå', + monthAgo: '1ã¶æå', + monthsAgo: '{delta}ã¶æå', + yearAgo: '1幎å', + yearsAgo: '{delta}幎å', + + lessThanMinuteUntil: 'ä»ããçŽ1å以å
', + minuteUntil: 'ä»ããçŽ1å', + minutesUntil: 'ä»ããçŽ{delta}å', + hourUntil: 'ä»ããçŽ1æé', + hoursUntil: 'ä»ããçŽ{delta}æé', + dayUntil: 'ä»ãã1æ¥é', + daysUntil: 'ä»ãã{delta}æ¥é', + weekUntil: 'ä»ãã1é±é', + weeksUntil: 'ä»ãã{delta}é±é', + monthUntil: 'ä»ãã1ã¶æ', + monthsUntil: 'ä»ãã{delta}ã¶æ', + yearUntil: 'ä»ãã1幎', + yearsUntil: 'ä»ãã{delta}幎' + +}); + +/* +--- + +name: Locale.ja-JP.Form.Validator + +description: Form Validator messages for Japanese. + +license: MIT-style license + +authors: + - Noritaka Horio + +requires: + - Locale + +provides: [Locale.ja-JP.Form.Validator] + +... +*/ + +Locale.define("ja-JP", "FormValidator", { + + required: 'å
¥åã¯å¿
é ã§ãã', + minLength: 'å
¥åæåæ°ã¯{minLength}以äžã«ããŠãã ããã({length}æå)', + maxLength: 'å
¥åæåæ°ã¯{maxLength}以äžã«ããŠãã ããã({length}æå)', + integer: 'æŽæ°ãå
¥åããŠãã ããã', + numeric: 'å
¥åã§ããã®ã¯æ°å€ã ãã§ãã(äŸ: "1", "1.1", "-1", "-1.1"....)', + digits: 'å
¥åã§ããã®ã¯æ°å€ãšå¥èªèšå·ã§ãã (äŸ: -ã+ãå«ãé»è©±çªå·ãªã©).', + alpha: 'å
¥åã§ããã®ã¯åè§è±åã ãã§ãããã以å€ã®æåã¯å
¥åã§ããŸããã', + alphanum: 'å
¥åã§ããã®ã¯åè§è±æ°åã ãã§ãããã以å€ã®æåã¯å
¥åã§ããŸããã', + dateSuchAs: 'æå¹ãªæ¥ä»ãå
¥åããŠãã ããã{date}', + dateInFormatMDY: 'æ¥ä»ã®æžåŒã«èª€ãããããŸããYYYY/MM/DD (i.e. "1999/12/31")', + email: 'ã¡ãŒã«ã¢ãã¬ã¹ã«èª€ãããããŸãã', + url: 'URLã¢ãã¬ã¹ã«èª€ãããããŸãã', + currencyDollar: 'éé¡ã«èª€ãããããŸãã', + oneRequired: 'ã²ãšã€ä»¥äžå
¥åããŠãã ããã', + errorPrefix: 'ãšã©ãŒ: ', + warningPrefix: 'èŠå: ', + + // FormValidator.Extras + noSpace: 'ã¹ããŒã¹ã¯å
¥åã§ããŸããã', + reqChkByNode: 'éžæãããŠããŸããã', + requiredChk: 'ãã®é
ç®ã¯å¿
é ã§ãã', + reqChkByName: '{label}ãéžæããŠãã ããã', + match: '{matchName}ãå
¥åãããŠããå Žåå¿
é ã§ãã', + startDate: 'éå§æ¥', + endDate: 'çµäºæ¥', + currentDate: '仿¥', + afterDate: '{label}以éã®æ¥ä»ã«ããŠãã ããã', + beforeDate: '{label}以åã®æ¥ä»ã«ããŠãã ããã', + startMonth: 'éå§æãéžæããŠãã ããã', + sameMonth: 'æ¥ä»ãåäžã§ããã©ã¡ããã倿ŽããŠãã ããã' + +}); + +/* +--- + +name: Locale.ja-JP.Number + +description: Number messages for Japanese. + +license: MIT-style license + +authors: + - Noritaka Horio + +requires: + - Locale + +provides: [Locale.ja-JP.Number] + +... +*/ + +Locale.define('ja-JP', 'Number', { + + decimal: '.', + group: ',', + + currency: { + decimals: 0, + prefix: '\\' + } + +}); + +/* +--- + +name: Locale.nl-NL.Date + +description: Date messages for Dutch. + +license: MIT-style license + +authors: + - Lennart Pilon + - Tim Wienk + +requires: + - Locale + +provides: [Locale.nl-NL.Date] + +... +*/ + +Locale.define('nl-NL', 'Date', { + + months: ['januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli', 'augustus', 'september', 'oktober', 'november', 'december'], + months_abbr: ['jan', 'feb', 'mrt', 'apr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'], + days: ['zondag', 'maandag', 'dinsdag', 'woensdag', 'donderdag', 'vrijdag', 'zaterdag'], + days_abbr: ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'], + + // Culture's date order: DD-MM-YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d-%m-%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: 'e', + + lessThanMinuteAgo: 'minder dan een minuut geleden', + minuteAgo: 'ongeveer een minuut geleden', + minutesAgo: '{delta} minuten geleden', + hourAgo: 'ongeveer een uur geleden', + hoursAgo: 'ongeveer {delta} uur geleden', + dayAgo: 'een dag geleden', + daysAgo: '{delta} dagen geleden', + weekAgo: 'een week geleden', + weeksAgo: '{delta} weken geleden', + monthAgo: 'een maand geleden', + monthsAgo: '{delta} maanden geleden', + yearAgo: 'een jaar geleden', + yearsAgo: '{delta} jaar geleden', + + lessThanMinuteUntil: 'over minder dan een minuut', + minuteUntil: 'over ongeveer een minuut', + minutesUntil: 'over {delta} minuten', + hourUntil: 'over ongeveer een uur', + hoursUntil: 'over {delta} uur', + dayUntil: 'over ongeveer een dag', + daysUntil: 'over {delta} dagen', + weekUntil: 'over een week', + weeksUntil: 'over {delta} weken', + monthUntil: 'over een maand', + monthsUntil: 'over {delta} maanden', + yearUntil: 'over een jaar', + yearsUntil: 'over {delta} jaar' + +}); + +/* +--- + +name: Locale.nl-NL.Form.Validator + +description: Form Validator messages for Dutch. + +license: MIT-style license + +authors: + - Lennart Pilon + - Arian Stolwijk + - Tim Wienk + +requires: + - Locale + +provides: [Locale.nl-NL.Form.Validator] + +... +*/ + +Locale.define('nl-NL', 'FormValidator', { + + required: 'Dit veld is verplicht.', + length: 'Vul precies {length} karakters in (je hebt {elLength} karakters ingevoerd).', + minLength: 'Vul minimaal {minLength} karakters in (je hebt {length} karakters ingevoerd).', + maxLength: 'Vul niet meer dan {maxLength} karakters in (je hebt {length} karakters ingevoerd).', + integer: 'Vul een getal in. Getallen met decimalen (bijvoorbeeld 1.25) zijn niet toegestaan.', + numeric: 'Vul alleen numerieke waarden in (bijvoorbeeld "1" of "1.1" of "-1" of "-1.1").', + digits: 'Vul alleen nummers en leestekens in (bijvoorbeeld een telefoonnummer met streepjes is toegestaan).', + alpha: 'Vul alleen letters in (a-z). Spaties en andere karakters zijn niet toegestaan.', + alphanum: 'Vul alleen letters (a-z) of nummers (0-9) in. Spaties en andere karakters zijn niet toegestaan.', + dateSuchAs: 'Vul een geldige datum in, zoals {date}', + dateInFormatMDY: 'Vul een geldige datum, in het formaat MM/DD/YYYY (bijvoorbeeld "12/31/1999")', + email: 'Vul een geldig e-mailadres in. Bijvoorbeeld "fred@domein.nl".', + url: 'Vul een geldige URL in, zoals http://www.example.com.', + currencyDollar: 'Vul een geldig $ bedrag in. Bijvoorbeeld $100.00 .', + oneRequired: 'Vul iets in bij in ieder geval een van deze velden.', + warningPrefix: 'Waarschuwing: ', + errorPrefix: 'Fout: ', + + // Form.Validator.Extras + noSpace: 'Spaties zijn niet toegestaan in dit veld.', + reqChkByNode: 'Er zijn geen items geselecteerd.', + requiredChk: 'Dit veld is verplicht.', + reqChkByName: 'Selecteer een {label}.', + match: 'Dit veld moet overeen komen met het {matchName} veld', + startDate: 'de begin datum', + endDate: 'de eind datum', + currentDate: 'de huidige datum', + afterDate: 'De datum moet hetzelfde of na {label} zijn.', + beforeDate: 'De datum moet hetzelfde of voor {label} zijn.', + startMonth: 'Selecteer een begin maand', + sameMonth: 'Deze twee data moeten in dezelfde maand zijn - u moet een van beide aanpassen.', + creditcard: 'Het ingevulde creditcardnummer is niet geldig. Controleer het nummer en probeer opnieuw. {length} getallen ingevuld.' + +}); + +/* +--- + +name: Locale.nl-NL.Number + +description: Number messages for Dutch. + +license: MIT-style license + +authors: + - Arian Stolwijk + +requires: + - Locale + - Locale.EU.Number + +provides: [Locale.nl-NL.Number] + +... +*/ + +Locale.define('nl-NL').inherit('EU', 'Number'); + + + + +/* +--- + +name: Locale.no-NO.Date + +description: Date messages for Norwegian. + +license: MIT-style license + +authors: + - Espen 'Rexxars' Hovlandsdal + - Ole TÞsse Kolvik +requires: + - Locale + +provides: [Locale.no-NO.Date] + +... +*/ + +Locale.define('no-NO', 'Date', { + months: ['Januar', 'Februar', 'Mars', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Desember'], + months_abbr: ['Jan', 'Feb', 'Mar', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'], + days: ['SÞndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'LÞrdag'], + days_abbr: ['SÞn', 'Man', 'Tir', 'Ons', 'Tor', 'Fre', 'LÞr'], + + // Culture's date order: DD.MM.YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d.%m.%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + lessThanMinuteAgo: 'mindre enn et minutt siden', + minuteAgo: 'omtrent et minutt siden', + minutesAgo: '{delta} minutter siden', + hourAgo: 'omtrent en time siden', + hoursAgo: 'omtrent {delta} timer siden', + dayAgo: '{delta} dag siden', + daysAgo: '{delta} dager siden', + weekAgo: 'en uke siden', + weeksAgo: '{delta} uker siden', + monthAgo: 'en mÃ¥ned siden', + monthsAgo: '{delta} mÃ¥neder siden', + yearAgo: 'ett Ã¥r siden', + yearsAgo: '{delta} Ã¥r siden', + + lessThanMinuteUntil: 'mindre enn et minutt til', + minuteUntil: 'omtrent et minutt til', + minutesUntil: '{delta} minutter til', + hourUntil: 'omtrent en time til', + hoursUntil: 'omtrent {delta} timer til', + dayUntil: 'en dag til', + daysUntil: '{delta} dager til', + weekUntil: 'en uke til', + weeksUntil: '{delta} uker til', + monthUntil: 'en mÃ¥ned til', + monthsUntil: '{delta} mÃ¥neder til', + yearUntil: 'et Ã¥r til', + yearsUntil: '{delta} Ã¥r til' +}); + +/* +--- + +name: Locale.no-NO.Form.Validator + +description: Form Validator messages for Norwegian. + +license: MIT-style license + +authors: + - Espen 'Rexxars' Hovlandsdal + +requires: + - Locale + +provides: [Locale.no-NO.Form.Validator] + +... +*/ + +Locale.define('no-NO', 'FormValidator', { + + required: 'Dette feltet er pÃÂ¥krevd.', + minLength: 'Vennligst skriv inn minst {minLength} tegn (du skrev {length} tegn).', + maxLength: 'Vennligst skriv inn maksimalt {maxLength} tegn (du skrev {length} tegn).', + integer: 'Vennligst skriv inn et tall i dette feltet. Tall med desimaler (for eksempel 1,25) er ikke tillat.', + numeric: 'Vennligst skriv inn kun numeriske verdier i dette feltet (for eksempel "1", "1.1", "-1" eller "-1.1").', + digits: 'Vennligst bruk kun nummer og skilletegn i dette feltet.', + alpha: 'Vennligst bruk kun bokstaver (a-z) i dette feltet. Ingen mellomrom eller andre tegn er tillat.', + alphanum: 'Vennligst bruk kun bokstaver (a-z) eller nummer (0-9) i dette feltet. Ingen mellomrom eller andre tegn er tillat.', + dateSuchAs: 'Vennligst skriv inn en gyldig dato, som {date}', + dateInFormatMDY: 'Vennligst skriv inn en gyldig dato, i formatet MM/DD/YYYY (for eksempel "12/31/1999")', + email: 'Vennligst skriv inn en gyldig epost-adresse. For eksempel "espen@domene.no".', + url: 'Vennligst skriv inn en gyldig URL, for eksempel http://www.example.com.', + currencyDollar: 'Vennligst fyll ut et gyldig $ belÞp. For eksempel $100.00 .', + oneRequired: 'Vennligst fyll ut noe i minst ett av disse feltene.', + errorPrefix: 'Feil: ', + warningPrefix: 'Advarsel: ' + +}); + +/* +--- + +name: Locale.pl-PL.Date + +description: Date messages for Polish. + +license: MIT-style license + +authors: + - Oskar Krawczyk + +requires: + - Locale + +provides: [Locale.pl-PL.Date] + +... +*/ + +Locale.define('pl-PL', 'Date', { + + months: ['StyczeÅ', 'Luty', 'Marzec', 'KwiecieÅ', 'Maj', 'Czerwiec', 'Lipiec', 'SierpieÅ', 'WrzesieÅ', 'Październik', 'Listopad', 'GrudzieÅ'], + months_abbr: ['sty', 'lut', 'mar', 'kwi', 'maj', 'cze', 'lip', 'sie', 'wrz', 'paź', 'lis', 'gru'], + days: ['Niedziela', 'PoniedziaÅek', 'Wtorek', 'Åroda', 'Czwartek', 'PiÄ
tek', 'Sobota'], + days_abbr: ['niedz.', 'pon.', 'wt.', 'År.', 'czw.', 'pt.', 'sob.'], + + // Culture's date order: YYYY-MM-DD + dateOrder: ['year', 'month', 'date'], + shortDate: '%Y-%m-%d', + shortTime: '%H:%M', + AM: 'nad ranem', + PM: 'po poÅudniu', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: function(dayOfMonth){ + return (dayOfMonth > 3 && dayOfMonth < 21) ? 'ty' : ['ty', 'szy', 'gi', 'ci', 'ty'][Math.min(dayOfMonth % 10, 4)]; + }, + + lessThanMinuteAgo: 'mniej niÅŒ minute temu', + minuteAgo: 'okoÅo minutÄ temu', + minutesAgo: '{delta} minut temu', + hourAgo: 'okoÅo godzinÄ temu', + hoursAgo: 'okoÅo {delta} godzin temu', + dayAgo: 'Wczoraj', + daysAgo: '{delta} dni temu', + + lessThanMinuteUntil: 'za niecaÅÄ
minutÄ', + minuteUntil: 'za okoÅo minutÄ', + minutesUntil: 'za {delta} minut', + hourUntil: 'za okoÅo godzinÄ', + hoursUntil: 'za okoÅo {delta} godzin', + dayUntil: 'za 1 dzieÅ', + daysUntil: 'za {delta} dni' + +}); + +/* +--- + +name: Locale.pl-PL.Form.Validator + +description: Form Validator messages for Polish. + +license: MIT-style license + +authors: + - Oskar Krawczyk + +requires: + - Locale + +provides: [Locale.pl-PL.Form.Validator] + +... +*/ + +Locale.define('pl-PL', 'FormValidator', { + + required: 'To pole jest wymagane.', + minLength: 'Wymagane jest przynajmniej {minLength} znaków (wpisanych zostaÅo tylko {length}).', + maxLength: 'Dozwolone jest nie wiÄcej niÅŒ {maxLength} znaków (wpisanych zostaÅo {length})', + integer: 'To pole wymaga liczb caÅych. Liczby dziesiÄtne (np. 1.25) sÄ
niedozwolone.', + numeric: 'Prosimy uÅŒywaÄ tylko numerycznych wartoÅci w tym polu (np. "1", "1.1", "-1" lub "-1.1").', + digits: 'Prosimy uÅŒywaÄ liczb oraz zankow punktuacyjnych w typ polu (dla przykÅadu, przy numerze telefonu myÅlniki i kropki sÄ
dozwolone).', + alpha: 'Prosimy uÅŒywaÄ tylko liter (a-z) w tym polu. Spacje oraz inne znaki sÄ
niedozwolone.', + alphanum: 'Prosimy uÅŒywaÄ tylko liter (a-z) lub liczb (0-9) w tym polu. Spacje oraz inne znaki sÄ
niedozwolone.', + dateSuchAs: 'Prosimy podaÄ prawidÅowÄ
datÄ w formacie: {date}', + dateInFormatMDY: 'Prosimy podaÄ poprawnÄ
date w formacie DD.MM.RRRR (i.e. "12.01.2009")', + email: 'Prosimy podaÄ prawidÅowy adres e-mail, np. "jan@domena.pl".', + url: 'Prosimy podaÄ prawidÅowy adres URL, np. http://www.example.com.', + currencyDollar: 'Prosimy podaÄ prawidÅowÄ
sumÄ w PLN. Dla przykÅadu: 100.00 PLN.', + oneRequired: 'Prosimy wypeÅniÄ chociaÅŒ jedno z pól.', + errorPrefix: 'BÅÄ
d: ', + warningPrefix: 'Uwaga: ', + + // Form.Validator.Extras + noSpace: 'W tym polu nie mogÄ
znajdowaÄ siÄ spacje.', + reqChkByNode: 'Brak zaznaczonych elementów.', + requiredChk: 'To pole jest wymagane.', + reqChkByName: 'Prosimy wybraÄ z {label}.', + match: 'To pole musi byÄ takie samo jak {matchName}', + startDate: 'data poczÄ
tkowa', + endDate: 'data koÅcowa', + currentDate: 'aktualna data', + afterDate: 'Podana data poinna byÄ taka sama lub po {label}.', + beforeDate: 'Podana data poinna byÄ taka sama lub przed {label}.', + startMonth: 'Prosimy wybraÄ poczÄ
tkowy miesiÄ
c.', + sameMonth: 'Te dwie daty muszÄ
byÄ w zakresie tego samego miesiÄ
ca - wymagana jest zmiana któregoÅ z pól.' + +}); + +/* +--- + +name: Locale.pt-PT.Date + +description: Date messages for Portuguese. + +license: MIT-style license + +authors: + - Fabio Miranda Costa + +requires: + - Locale + +provides: [Locale.pt-PT.Date] + +... +*/ + +Locale.define('pt-PT', 'Date', { + + months: ['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'], + months_abbr: ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'], + days: ['Domingo', 'Segunda-feira', 'Terça-feira', 'Quarta-feira', 'Quinta-feira', 'Sexta-feira', 'Sábado'], + days_abbr: ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'], + + // Culture's date order: DD-MM-YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d-%m-%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: 'º', + + lessThanMinuteAgo: 'há menos de um minuto', + minuteAgo: 'há cerca de um minuto', + minutesAgo: 'há {delta} minutos', + hourAgo: 'há cerca de uma hora', + hoursAgo: 'há cerca de {delta} horas', + dayAgo: 'há um dia', + daysAgo: 'há {delta} dias', + weekAgo: 'há uma semana', + weeksAgo: 'há {delta} semanas', + monthAgo: 'há um mês', + monthsAgo: 'há {delta} meses', + yearAgo: 'há um ano', + yearsAgo: 'há {delta} anos', + + lessThanMinuteUntil: 'em menos de um minuto', + minuteUntil: 'em um minuto', + minutesUntil: 'em {delta} minutos', + hourUntil: 'em uma hora', + hoursUntil: 'em {delta} horas', + dayUntil: 'em um dia', + daysUntil: 'em {delta} dias', + weekUntil: 'em uma semana', + weeksUntil: 'em {delta} semanas', + monthUntil: 'em um mês', + monthsUntil: 'em {delta} meses', + yearUntil: 'em um ano', + yearsUntil: 'em {delta} anos' + +}); + +/* +--- + +name: Locale.pt-BR.Date + +description: Date messages for Portuguese (Brazil). + +license: MIT-style license + +authors: + - Fabio Miranda Costa + +requires: + - Locale + - Locale.pt-PT.Date + +provides: [Locale.pt-BR.Date] + +... +*/ + +Locale.define('pt-BR', 'Date', { + + // Culture's date order: DD/MM/YYYY + shortDate: '%d/%m/%Y' + +}).inherit('pt-PT', 'Date'); + +/* +--- + +name: Locale.pt-BR.Form.Validator + +description: Form Validator messages for Portuguese (Brazil). + +license: MIT-style license + +authors: + - Fábio Miranda Costa + +requires: + - Locale + +provides: [Locale.pt-BR.Form.Validator] + +... +*/ + +Locale.define('pt-BR', 'FormValidator', { + + required: 'Este campo é obrigatório.', + minLength: 'Digite pelo menos {minLength} caracteres (tamanho atual: {length}).', + maxLength: 'Não digite mais de {maxLength} caracteres (tamanho atual: {length}).', + integer: 'Por favor digite apenas um número inteiro neste campo. Não são permitidos números decimais (por exemplo, 1,25).', + numeric: 'Por favor digite apenas valores numéricos neste campo (por exemplo, "1" ou "1.1" ou "-1" ou "-1,1").', + digits: 'Por favor use apenas números e pontuação neste campo (por exemplo, um número de telefone com traços ou pontos é permitido).', + alpha: 'Por favor use somente letras (a-z). Espaço e outros caracteres não são permitidos.', + alphanum: 'Use somente letras (a-z) ou números (0-9) neste campo. Espaço e outros caracteres não são permitidos.', + dateSuchAs: 'Digite uma data válida, como {date}', + dateInFormatMDY: 'Digite uma data válida, como DD/MM/YYYY (por exemplo, "31/12/1999")', + email: 'Digite um endereço de email válido. Por exemplo "nome@dominio.com".', + url: 'Digite uma URL válida. Exemplo: http://www.example.com.', + currencyDollar: 'Digite um valor em dinheiro válido. Exemplo: R$100,00 .', + oneRequired: 'Digite algo para pelo menos um desses campos.', + errorPrefix: 'Erro: ', + warningPrefix: 'Aviso: ', + + // Form.Validator.Extras + noSpace: 'Não é possÃvel digitar espaços neste campo.', + reqChkByNode: 'Não foi selecionado nenhum item.', + requiredChk: 'Este campo é obrigatório.', + reqChkByName: 'Por favor digite um {label}.', + match: 'Este campo deve ser igual ao campo {matchName}.', + startDate: 'a data inicial', + endDate: 'a data final', + currentDate: 'a data atual', + afterDate: 'A data deve ser igual ou posterior a {label}.', + beforeDate: 'A data deve ser igual ou anterior a {label}.', + startMonth: 'Por favor selecione uma data inicial.', + sameMonth: 'Estas duas datas devem ter o mesmo mês - você deve modificar uma das duas.', + creditcard: 'O número do cartão de crédito informado é inválido. Por favor verifique o valor e tente novamente. {length} números informados.' + +}); + +/* +--- + +name: Locale.pt-BR.Number + +description: Number messages for PT Brazilian. + +license: MIT-style license + +authors: + - Arian Stolwijk + - Danillo César + +requires: + - Locale + +provides: [Locale.pt-BR.Number] + +... +*/ + +Locale.define('pt-BR', 'Number', { + + decimal: ',', + group: '.', + + currency: { + prefix: 'R$ ' + } + +}); + + + +/* +--- + +name: Locale.pt-PT.Form.Validator + +description: Form Validator messages for Portuguese. + +license: MIT-style license + +authors: + - Miquel Hudin + +requires: + - Locale + +provides: [Locale.pt-PT.Form.Validator] + +... +*/ + +Locale.define('pt-PT', 'FormValidator', { + + required: 'Este campo é necessário.', + minLength: 'Digite pelo menos{minLength} caracteres (comprimento {length} caracteres).', + maxLength: 'Não insira mais de {maxLength} caracteres (comprimento {length} caracteres).', + integer: 'Digite um número inteiro neste domÃnio. Com números decimais (por exemplo, 1,25), não são permitidas.', + numeric: 'Digite apenas valores numéricos neste domÃnio (p.ex., "1" ou "1.1" ou "-1" ou "-1,1").', + digits: 'Por favor, use números e pontuação apenas neste campo (p.ex., um número de telefone com traços ou pontos é permitida).', + alpha: 'Por favor use somente letras (a-z), com nesta área. Não utilize espaços nem outros caracteres são permitidos.', + alphanum: 'Use somente letras (a-z) ou números (0-9) neste campo. Não utilize espaços nem outros caracteres são permitidos.', + dateSuchAs: 'Digite uma data válida, como {date}', + dateInFormatMDY: 'Digite uma data válida, como DD/MM/YYYY (p.ex. "31/12/1999")', + email: 'Digite um endereço de email válido. Por exemplo "fred@domain.com".', + url: 'Digite uma URL válida, como http://www.example.com.', + currencyDollar: 'Digite um valor válido $. Por exemplo $ 100,00. ', + oneRequired: 'Digite algo para pelo menos um desses insumos.', + errorPrefix: 'Erro: ', + warningPrefix: 'Aviso: ' + +}); + +/* +--- + +name: Locale.ru-RU-unicode.Date + +description: Date messages for Russian (utf-8). + +license: MIT-style license + +authors: + - Evstigneev Pavel + - Kuryanovich Egor + +requires: + - Locale + +provides: [Locale.ru-RU.Date] + +... +*/ + +(function(){ + +// Russian language pluralization rules, taken from CLDR project, http://unicode.org/cldr/ +// one -> n mod 10 is 1 and n mod 100 is not 11; +// few -> n mod 10 in 2..4 and n mod 100 not in 12..14; +// many -> n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14; +// other -> everything else (example 3.14) +var pluralize = function (n, one, few, many, other){ + var modulo10 = n % 10, + modulo100 = n % 100; + + if (modulo10 == 1 && modulo100 != 11){ + return one; + } else if ((modulo10 == 2 || modulo10 == 3 || modulo10 == 4) && !(modulo100 == 12 || modulo100 == 13 || modulo100 == 14)){ + return few; + } else if (modulo10 == 0 || (modulo10 == 5 || modulo10 == 6 || modulo10 == 7 || modulo10 == 8 || modulo10 == 9) || (modulo100 == 11 || modulo100 == 12 || modulo100 == 13 || modulo100 == 14)){ + return many; + } else { + return other; + } +}; + +Locale.define('ru-RU', 'Date', { + + months: ['ЯМваÑÑ', 'ЀевÑалÑ', 'ÐаÑÑ', 'ÐпÑелÑ', 'Ðай', 'ÐÑМÑ', 'ÐÑлÑ', 'ÐвгÑÑÑ', 'СеМÑÑбÑÑ', 'ÐкÑÑбÑÑ', 'ÐПÑбÑÑ', 'ÐекабÑÑ'], + months_abbr: ['ÑМв', 'ÑевÑ', 'ЌаÑÑ', 'апÑ', 'Ќай','ОÑМÑ','ОÑлÑ','авг','ÑеМÑ','ПкÑ','МПÑб','Ўек'], + days: ['ÐПÑкÑеÑеМÑе', 'ÐПМеЎелÑМОк', 'ÐÑПÑМОк', 'СÑеЎа', 'ЧеÑвеÑг', 'ÐÑÑМОÑа', 'СÑббПÑа'], + days_abbr: ['ÐÑ', 'ÐМ', 'ÐÑ', 'СÑ', 'ЧÑ', 'ÐÑ', 'Сб'], + + // Culture's date order: DD.MM.YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d.%m.%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: 'ЌеМÑÑе ЌОМÑÑÑ ÐœÐ°Ð·Ð°ÐŽ', + minuteAgo: 'ЌОМÑÑÑ ÐœÐ°Ð·Ð°ÐŽ', + minutesAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ЌОМÑÑÑ', 'ЌОМÑÑÑ', 'ЌОМÑÑ') + ' МазаЎ'; }, + hourAgo: 'ÑÐ°Ñ ÐœÐ°Ð·Ð°ÐŽ', + hoursAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ÑаÑ', 'ÑаÑа', 'ÑаÑПв') + ' МазаЎ'; }, + dayAgo: 'вÑеÑа', + daysAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ЎеМÑ', 'ЎМÑ', 'ЎМей') + ' МазаЎ'; }, + weekAgo: 'ÐœÐµÐŽÐµÐ»Ñ ÐœÐ°Ð·Ð°ÐŽ', + weeksAgo: function(delta){ return '{delta} ' + pluralize(delta, 'МеЎелÑ', 'МеЎелО', 'МеЎелÑ') + ' МазаЎ'; }, + monthAgo: 'ЌеÑÑÑ ÐœÐ°Ð·Ð°ÐŽ', + monthsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ЌеÑÑÑ', 'ЌеÑÑÑа', 'ЌеÑÑÑев') + ' МазаЎ'; }, + yearAgo: 'гПЎ МазаЎ', + yearsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'гПЎ', 'гПЎа', 'леÑ') + ' МазаЎ'; }, + + lessThanMinuteUntil: 'ЌеМÑÑе ÑеЌ ÑеÑез ЌОМÑÑÑ', + minuteUntil: 'ÑеÑез ЌОМÑÑÑ', + minutesUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'ЌОМÑÑÑ', 'ЌОМÑÑÑ', 'ЌОМÑÑ') + ''; }, + hourUntil: 'ÑеÑез ÑаÑ', + hoursUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'ÑаÑ', 'ÑаÑа', 'ÑаÑПв') + ''; }, + dayUntil: 'завÑÑа', + daysUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'ЎеМÑ', 'ЎМÑ', 'ЎМей') + ''; }, + weekUntil: 'ÑеÑез МеЎелÑ', + weeksUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'МеЎелÑ', 'МеЎелО', 'МеЎелÑ') + ''; }, + monthUntil: 'ÑеÑез ЌеÑÑÑ', + monthsUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'ЌеÑÑÑ', 'ЌеÑÑÑа', 'ЌеÑÑÑев') + ''; }, + yearUntil: 'ÑеÑез', + yearsUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'гПЎ', 'гПЎа', 'леÑ') + ''; } + +}); + + + +})(); + +/* +--- + +name: Locale.ru-RU-unicode.Form.Validator + +description: Form Validator messages for Russian (utf-8). + +license: MIT-style license + +authors: + - Chernodarov Egor + +requires: + - Locale + +provides: [Locale.ru-RU.Form.Validator] + +... +*/ + +Locale.define('ru-RU', 'FormValidator', { + + required: 'ÐÑП пПле ПбÑзаÑелÑМП к запПлМеМОÑ.', + minLength: 'ÐПжалÑйÑÑа, ввеЎОÑе Ñ
ПÑÑ Ð±Ñ {minLength} ÑОЌвПлПв (ÐÑ Ð²Ð²ÐµÐ»Ðž {length}).', + maxLength: 'ÐПжалÑйÑÑа, ввеЎОÑе Ме бПлÑÑе {maxLength} ÑОЌвПлПв (ÐÑ Ð²Ð²ÐµÐ»Ðž {length}).', + integer: 'ÐПжалÑйÑÑа, ввеЎОÑе в ÑÑП пПле ÑОÑлП. ÐÑПбМÑе ÑОÑла (МапÑÐžÐŒÐµÑ 1.25) ÑÑÑ ÐœÐµ ÑазÑеÑеМÑ.', + numeric: 'ÐПжалÑйÑÑа, ввеЎОÑе в ÑÑП пПле ÑОÑлП (МапÑÐžÐŒÐµÑ "1" ОлО "1.1", ОлО "-1", ОлО "-1.1").', + digits: 'Ð ÑÑПЌ пПле ÐÑ ÐŒÐŸÐ¶ÐµÑе ОÑпПлÑзПваÑÑ ÑПлÑкП ÑОÑÑÑ Ðž зМакО пÑМкÑÑаÑОО (МапÑОЌеÑ, ÑелеÑПММÑй ÐœÐŸÐŒÐµÑ ÑП зМакаЌО ЎеÑОÑа ОлО Ñ ÑПÑкаЌО).', + alpha: 'Ð ÑÑПЌ пПле ЌПжМП ОÑпПлÑзПваÑÑ ÑПлÑкП лаÑОМÑкОе бÑÐºÐ²Ñ (a-z). ÐÑÐŸÐ±ÐµÐ»Ñ Ðž ÐŽÑÑгОе ÑÐžÐŒÐ²ÐŸÐ»Ñ Ð·Ð°Ð¿ÑеÑеМÑ.', + alphanum: 'Ð ÑÑПЌ пПле ЌПжМП ОÑпПлÑзПваÑÑ ÑПлÑкП лаÑОМÑкОе бÑÐºÐ²Ñ (a-z) О ÑОÑÑÑ (0-9). ÐÑÐŸÐ±ÐµÐ»Ñ Ðž ÐŽÑÑгОе ÑÐžÐŒÐ²ÐŸÐ»Ñ Ð·Ð°Ð¿ÑеÑеМÑ.', + dateSuchAs: 'ÐПжалÑйÑÑа, ввеЎОÑе кПÑÑекÑМÑÑ ÐŽÐ°ÑÑ {date}', + dateInFormatMDY: 'ÐПжалÑйÑÑа, ввеЎОÑе ЎаÑÑ Ð² ÑПÑЌаÑе ÐÐ/ÐÐ/ÐÐÐÐ (МапÑÐžÐŒÐµÑ "12/31/1999")', + email: 'ÐПжалÑйÑÑа, ввеЎОÑе кПÑÑекÑМÑй еЌейл-аЎÑеÑ. ÐÐ»Ñ Ð¿ÑОЌеÑа "fred@domain.com".', + url: 'ÐПжалÑйÑÑа, ввеЎОÑе пÑавОлÑМÑÑ ÑÑÑÐ»ÐºÑ Ð²ÐžÐŽÐ° http://www.example.com.', + currencyDollar: 'ÐПжалÑйÑÑа, ввеЎОÑе ÑÑÐŒÐŒÑ Ð² ЎПллаÑаÑ
. ÐапÑОЌеÑ: $100.00 .', + oneRequired: 'ÐПжалÑйÑÑа, вÑбеÑОÑе Ñ
ПÑÑ ÑÑП-МОбÑÐŽÑ Ð² ПЎМПЌ Оз ÑÑОÑ
пПлей.', + errorPrefix: 'ÐÑОбка: ', + warningPrefix: 'ÐМОЌаМОе: ' + +}); + + + +/* +--- + +name: Locale.sk-SK.Date + +description: Date messages for Slovak. + +license: MIT-style license + +authors: + - Ivan Masár + +requires: + - Locale + +provides: [Locale.sk-SK.Date] + +... +*/ +(function(){ + +// Slovak language pluralization rules, see http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html +// one -> n is 1; 1 +// few -> n in 2..4; 2-4 +// other -> everything else 0, 5-999, 1.31, 2.31, 5.31... +var pluralize = function (n, one, few, other){ + if (n == 1) return one; + else if (n == 2 || n == 3 || n == 4) return few; + else return other; +}; + +Locale.define('sk-SK', 'Date', { + + months: ['Január', 'Február', 'Marec', 'AprÃl', 'Máj', 'Jún', 'Júl', 'August', 'September', 'Október', 'November', 'December'], + months_abbr: ['januára', 'februára', 'marca', 'aprÃla', 'mája', 'júna', 'júla', 'augusta', 'septembra', 'októbra', 'novembra', 'decembra'], + days: ['Nedele', 'PondelÃ', 'ÃterÜ', 'Streda', 'Ätvrtek', 'Pátek', 'Sobota'], + days_abbr: ['ne', 'po', 'ut', 'st', 'Å¡t', 'pi', 'so'], + + // Culture's date order: DD.MM.YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d.%m.%Y', + shortTime: '%H:%M', + AM: 'dop.', + PM: 'pop.', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '.', + + lessThanMinuteAgo: 'pred chvÃÄŸou', + minuteAgo: 'pribliÅŸne pred minútou', + minutesAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'minútou', 'minútami', 'minútami'); }, + hourAgo: 'pribliÅŸne pred hodinou', + hoursAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'hodinou', 'hodinami', 'hodinami'); }, + dayAgo: 'pred dÅom', + daysAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'dÅom', 'dÅami', 'dÅami'); }, + weekAgo: 'pred tÜşdÅom', + weeksAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'tÜşdÅom', 'tÜşdÅami', 'tÜşdÅami'); }, + monthAgo: 'pred mesiacom', + monthsAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'mesiacom', 'mesiacmi', 'mesiacmi'); }, + yearAgo: 'pred rokom', + yearsAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'rokom', 'rokmi', 'rokmi'); }, + + lessThanMinuteUntil: 'o chvÃÄŸu', + minuteUntil: 'pribliÅŸne o minútu', + minutesUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'minútu', 'minúty', 'minúty'); }, + hourUntil: 'pribliÅŸne o hodinu', + hoursUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'hodinu', 'hodiny', 'hodÃn'); }, + dayUntil: 'o deÅ', + daysUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'deÅ', 'dni', 'dnÃ'); }, + weekUntil: 'o tÜşdeÅ', + weeksUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'tÜşdeÅ', 'tÜşdne', 'tÜşdÅov'); }, + monthUntil: 'o mesiac', + monthsUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'mesiac', 'mesiace', 'mesiacov'); }, + yearUntil: 'o rok', + yearsUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'rok', 'roky', 'rokov'); } +}); + +})(); + +/* +--- + +name: Locale.sk-SK.Form.Validator + +description: Form Validator messages for Czech. + +license: MIT-style license + +authors: + - Ivan Masár + +requires: + - Locale + +provides: [Locale.sk-SK.Form.Validator] + +... +*/ + +Locale.define('sk-SK', 'FormValidator', { + + required: 'Táto poloÅŸka je povinná.', + minLength: 'Zadajte prosÃm aspoÅ {minLength} znakov (momentálne {length} znakov).', + maxLength: 'Zadajte prosÃm menej ako {maxLength} znakov (momentálne {length} znakov).', + integer: 'Zadajte prosÃm celé ÄÃslo. Desetinné ÄÃsla (napr. 1.25) nie sú povolené.', + numeric: 'Zadajte len ÄÃselné hodnoty (t.j. â1â alebo â1.1â alebo â-1â alebo â-1.1â).', + digits: 'Zadajte prosÃm len ÄÃsla a interpunkÄné znamienka (naprÃklad telefónne ÄÃslo s pomlÄkami albo bodkami je povolené).', + alpha: 'Zadajte prosÃm len pÃsmená (a-z). Medzery alebo iné znaky nie sú povolené.', + alphanum: 'Zadajte prosÃm len pÃsmená (a-z) alebo ÄÃslice (0-9). Medzery alebo iné znaky nie sú povolené.', + dateSuchAs: 'Zadajte prosÃm platnÜ dátum v tvare {date}', + dateInFormatMDY: 'Zadajte prosÃm platnÜ datum v tvare MM / DD / RRRR (t.j. â12/31/1999â)', + email: 'Zadajte prosÃm platnú emailovú adresu. NaprÃklad âfred@domain.comâ.', + url: 'Zadajte prosÃm platnoú adresu URL v tvare http://www.example.com.', + currencyDollar: 'Zadajte prosÃm platnú Äiastku. NaprÃklad $100.00.', + oneRequired: 'Zadajte prosÃm aspoÅ jednu hodnotu z tÜchto poloÅŸiek.', + errorPrefix: 'Chyba: ', + warningPrefix: 'Upozornenie: ', + + // Form.Validator.Extras + noSpace: 'V tejto poloÅŸle nie sú povolené medzery', + reqChkByNode: 'Nie sú vybrané ÅŸiadne poloÅŸky.', + requiredChk: 'Táto poloÅŸka je povinná.', + reqChkByName: 'ProsÃm vyberte {label}.', + match: 'Táto poloÅŸka sa musà zhodovaÅ¥ s poloÅŸkou {matchName}', + startDate: 'dátum zaÄiatku', + endDate: 'dátum ukonÄenia', + currendDate: 'aktuálny dátum', + afterDate: 'Dátum by mal bÜť rovnakÜ alebo vÀÄšà ako {label}.', + beforeDate: 'Dátum by mal byÅ¥ rovnakÜ alebo menšà ako {label}.', + startMonth: 'Vyberte poÄiatoÄnÜ mesiac.', + sameMonth: 'Tieto dva dátumy musia bÜť v rovnakom mesiaci - zmeÅte jeden z nich.', + creditcard: 'Zadané ÄÃslo kreditnej karty je neplatné. ProsÃm, opravte ho. Bolo zadanÜch {length} ÄÃslic.' + +}); + +/* +--- + +name: Locale.si-SI.Date + +description: Date messages for Slovenian. + +license: MIT-style license + +authors: + - Radovan Lozej + +requires: + - Locale + +provides: [Locale.si-SI.Date] + +... +*/ + +(function(){ + +var pluralize = function(n, one, two, three, other){ + return (n >= 1 && n <= 3) ? arguments[n] : other; +}; + +Locale.define('sl-SI', 'Date', { + + months: ['januar', 'februar', 'marec', 'april', 'maj', 'junij', 'julij', 'avgust', 'september', 'oktober', 'november', 'december'], + months_abbr: ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'avg', 'sep', 'okt', 'nov', 'dec'], + days: ['nedelja', 'ponedeljek', 'torek', 'sreda', 'Äetrtek', 'petek', 'sobota'], + days_abbr: ['ned', 'pon', 'tor', 'sre', 'Äet', 'pet', 'sob'], + + // Culture's date order: DD.MM.YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d.%m.%Y', + shortTime: '%H.%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '.', + + lessThanMinuteAgo: 'manj kot minuto nazaj', + minuteAgo: 'minuto nazaj', + minutesAgo: function(delta){ return '{delta} ' + pluralize(delta, 'minuto', 'minuti', 'minute', 'minut') + ' nazaj'; }, + hourAgo: 'uro nazaj', + hoursAgo: function(delta){ return '{delta} ' + pluralize(delta, 'uro', 'uri', 'ure', 'ur') + ' nazaj'; }, + dayAgo: 'dan nazaj', + daysAgo: function(delta){ return '{delta} ' + pluralize(delta, 'dan', 'dneva', 'dni', 'dni') + ' nazaj'; }, + weekAgo: 'teden nazaj', + weeksAgo: function(delta){ return '{delta} ' + pluralize(delta, 'teden', 'tedna', 'tedne', 'tednov') + ' nazaj'; }, + monthAgo: 'mesec nazaj', + monthsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'mesec', 'meseca', 'mesece', 'mesecov') + ' nazaj'; }, + yearthAgo: 'leto nazaj', + yearsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'leto', 'leti', 'leta', 'let') + ' nazaj'; }, + + lessThanMinuteUntil: 'Å¡e manj kot minuto', + minuteUntil: 'Å¡e minuta', + minutesUntil: function(delta){ return 'Å¡e {delta} ' + pluralize(delta, 'minuta', 'minuti', 'minute', 'minut'); }, + hourUntil: 'Å¡e ura', + hoursUntil: function(delta){ return 'Å¡e {delta} ' + pluralize(delta, 'ura', 'uri', 'ure', 'ur'); }, + dayUntil: 'Å¡e dan', + daysUntil: function(delta){ return 'Å¡e {delta} ' + pluralize(delta, 'dan', 'dneva', 'dnevi', 'dni'); }, + weekUntil: 'Å¡e tedn', + weeksUntil: function(delta){ return 'Å¡e {delta} ' + pluralize(delta, 'teden', 'tedna', 'tedni', 'tednov'); }, + monthUntil: 'Å¡e mesec', + monthsUntil: function(delta){ return 'Å¡e {delta} ' + pluralize(delta, 'mesec', 'meseca', 'meseci', 'mesecov'); }, + yearUntil: 'Å¡e leto', + yearsUntil: function(delta){ return 'Å¡e {delta} ' + pluralize(delta, 'leto', 'leti', 'leta', 'let'); } + +}); + +})(); + +/* +--- + +name: Locale.si-SI.Form.Validator + +description: Form Validator messages for Slovenian. + +license: MIT-style license + +authors: + - Radovan Lozej + +requires: + - Locale + +provides: [Locale.si-SI.Form.Validator] + +... +*/ + +Locale.define('sl-SI', 'FormValidator', { + + required: 'To polje je obvezno', + minLength: 'Prosim, vnesite vsaj {minLength} znakov (vnesli ste {length} znakov).', + maxLength: 'Prosim, ne vnesite veÄ kot {maxLength} znakov (vnesli ste {length} znakov).', + integer: 'Prosim, vnesite celo Å¡tevilo. Decimalna Å¡tevila (kot 1,25) niso dovoljena.', + numeric: 'Prosim, vnesite samo numeriÄne vrednosti (kot "1" ali "1.1" ali "-1" ali "-1.1").', + digits: 'Prosim, uporabite Å¡tevilke in loÄila le na tem polju (na primer, dovoljena je telefonska Å¡tevilka z pomiÅ¡laji ali pikami).', + alpha: 'Prosim, uporabite le Ärke v tem plju. Presledki in drugi znaki niso dovoljeni.', + alphanum: 'Prosim, uporabite samo Ärke ali Å¡tevilke v tem polju. Presledki in drugi znaki niso dovoljeni.', + dateSuchAs: 'Prosim, vnesite pravilen datum kot {date}', + dateInFormatMDY: 'Prosim, vnesite pravilen datum kot MM.DD.YYYY (primer "12.31.1999")', + email: 'Prosim, vnesite pravilen email naslov. Na primer "fred@domain.com".', + url: 'Prosim, vnesite pravilen URL kot http://www.example.com.', + currencyDollar: 'Prosim, vnesit epravilno vrednost â¬. Primer 100,00⬠.', + oneRequired: 'Prosimo, vnesite nekaj za vsaj eno izmed teh polj.', + errorPrefix: 'Napaka: ', + warningPrefix: 'Opozorilo: ', + + // Form.Validator.Extras + noSpace: 'To vnosno polje ne dopuÅ¡Äa presledkov.', + reqChkByNode: 'NiÄ niste izbrali.', + requiredChk: 'To polje je obvezno', + reqChkByName: 'Prosim, izberite {label}.', + match: 'To polje se mora ujemati z poljem {matchName}', + startDate: 'datum zaÄetka', + endDate: 'datum konca', + currentDate: 'trenuten datum', + afterDate: 'Datum bi moral biti isti ali po {label}.', + beforeDate: 'Datum bi moral biti isti ali pred {label}.', + startMonth: 'Prosim, vnesite zaÄetni datum', + sameMonth: 'Ta dva datuma morata biti v istem mesecu - premeniti morate eno ali drugo.', + creditcard: 'Å tevilka kreditne kartice ni pravilna. Preverite Å¡tevilko ali poskusite Å¡e enkrat. VneÅ¡enih {length} znakov.' + +}); + +/* +--- + +name: Locale.sv-SE.Date + +description: Date messages for Swedish. + +license: MIT-style license + +authors: + - Martin Lundgren + +requires: + - Locale + +provides: [Locale.sv-SE.Date] + +... +*/ + +Locale.define('sv-SE', 'Date', { + + months: ['januari', 'februari', 'mars', 'april', 'maj', 'juni', 'juli', 'augusti', 'september', 'oktober', 'november', 'december'], + months_abbr: ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'], + days: ['söndag', 'mÃ¥ndag', 'tisdag', 'onsdag', 'torsdag', 'fredag', 'lördag'], + days_abbr: ['sön', 'mÃ¥n', 'tis', 'ons', 'tor', 'fre', 'lör'], + + // Culture's date order: YYYY-MM-DD + dateOrder: ['year', 'month', 'date'], + shortDate: '%Y-%m-%d', + shortTime: '%H:%M', + AM: '', + PM: '', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: 'mindre Àn en minut sedan', + minuteAgo: 'ungefÀr en minut sedan', + minutesAgo: '{delta} minuter sedan', + hourAgo: 'ungefÀr en timme sedan', + hoursAgo: 'ungefÀr {delta} timmar sedan', + dayAgo: '1 dag sedan', + daysAgo: '{delta} dagar sedan', + + lessThanMinuteUntil: 'mindre Àn en minut sedan', + minuteUntil: 'ungefÀr en minut sedan', + minutesUntil: '{delta} minuter sedan', + hourUntil: 'ungefÀr en timme sedan', + hoursUntil: 'ungefÀr {delta} timmar sedan', + dayUntil: '1 dag sedan', + daysUntil: '{delta} dagar sedan' + +}); + +/* +--- + +name: Locale.sv-SE.Form.Validator + +description: Form Validator messages for Swedish. + +license: MIT-style license + +authors: + - Martin Lundgren + +requires: + - Locale + +provides: [Locale.sv-SE.Form.Validator] + +... +*/ + +Locale.define('sv-SE', 'FormValidator', { + + required: 'FÀltet Àr obligatoriskt.', + minLength: 'Ange minst {minLength} tecken (du angav {length} tecken).', + maxLength: 'Ange högst {maxLength} tecken (du angav {length} tecken). ', + integer: 'Ange ett heltal i fÀltet. Tal med decimaler (t.ex. 1,25) Àr inte tillÃ¥tna.', + numeric: 'Ange endast numeriska vÀrden i detta fÀlt (t.ex. "1" eller "1.1" eller "-1" eller "-1,1").', + digits: 'AnvÀnd endast siffror och skiljetecken i detta fÀlt (till exempel ett telefonnummer med bindestreck tillÃ¥tet).', + alpha: 'AnvÀnd endast bokstÀver (a-ö) i detta fÀlt. Inga mellanslag eller andra tecken Àr tillÃ¥tna.', + alphanum: 'AnvÀnd endast bokstÀver (a-ö) och siffror (0-9) i detta fÀlt. Inga mellanslag eller andra tecken Àr tillÃ¥tna.', + dateSuchAs: 'Ange ett giltigt datum som t.ex. {date}', + dateInFormatMDY: 'Ange ett giltigt datum som t.ex. YYYY-MM-DD (i.e. "1999-12-31")', + email: 'Ange en giltig e-postadress. Till exempel "erik@domain.com".', + url: 'Ange en giltig webbadress som http://www.example.com.', + currencyDollar: 'Ange en giltig belopp. Exempelvis 100,00.', + oneRequired: 'VÀnligen ange minst ett av dessa alternativ.', + errorPrefix: 'Fel: ', + warningPrefix: 'Varning: ', + + // Form.Validator.Extras + noSpace: 'Det fÃ¥r inte finnas nÃ¥gra mellanslag i detta fÀlt.', + reqChkByNode: 'Inga objekt Àr valda.', + requiredChk: 'Detta Àr ett obligatoriskt fÀlt.', + reqChkByName: 'VÀlj en {label}.', + match: 'Detta fÀlt mÃ¥ste matcha {matchName}', + startDate: 'startdatumet', + endDate: 'slutdatum', + currentDate: 'dagens datum', + afterDate: 'Datumet bör vara samma eller senare Àn {label}.', + beforeDate: 'Datumet bör vara samma eller tidigare Àn {label}.', + startMonth: 'VÀlj en start mÃ¥nad', + sameMonth: 'Dessa tvÃ¥ datum mÃ¥ste vara i samma mÃ¥nad - du mÃ¥ste Àndra det ena eller det andra.' + +}); + +/* +--- + +name: Locale.sv-SE.Number + +description: Number messages for Swedish. + +license: MIT-style license + +authors: + - Arian Stolwijk + - Martin Lundgren + +requires: + - Locale + - Locale.EU.Number + +provides: [Locale.sv-SE.Number] + +... +*/ + +Locale.define('sv-SE', 'Number', { + + currency: { + prefix: 'SEK ' + } + +}).inherit('EU', 'Number'); + +/* +--- + +name: Locale.tr-TR.Date + +description: Date messages for Turkish. + +license: MIT-style license + +authors: + - Faruk Can Bilir + +requires: + - Locale + +provides: [Locale.tr-TR.Date] + +... +*/ + +Locale.define('tr-TR', 'Date', { + + months: ['Ocak', 'Åubat', 'Mart', 'Nisan', 'Mayıs', 'Haziran', 'Temmuz', 'AÄustos', 'EylÃŒl', 'Ekim', 'Kasım', 'Aralık'], + months_abbr: ['Oca', 'Åub', 'Mar', 'Nis', 'May', 'Haz', 'Tem', 'AÄu', 'Eyl', 'Eki', 'Kas', 'Ara'], + days: ['Pazar', 'Pazartesi', 'Salı', 'ÃarÅamba', 'PerÅembe', 'Cuma', 'Cumartesi'], + days_abbr: ['Pa', 'Pzt', 'Sa', 'Ãa', 'Pe', 'Cu', 'Cmt'], + + // Culture's date order: MM/DD/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d/%m/%Y', + shortTime: '%H.%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: 'bir dakikadan önce', + minuteAgo: 'yaklaÅık bir dakika önce', + minutesAgo: '{delta} dakika önce', + hourAgo: 'bir saat kadar önce', + hoursAgo: '{delta} saat kadar önce', + dayAgo: 'bir gÃŒn önce', + daysAgo: '{delta} gÃŒn önce', + weekAgo: 'bir hafta önce', + weeksAgo: '{delta} hafta önce', + monthAgo: 'bir ay önce', + monthsAgo: '{delta} ay önce', + yearAgo: 'bir yıl önce', + yearsAgo: '{delta} yıl önce', + + lessThanMinuteUntil: 'bir dakikadan az sonra', + minuteUntil: 'bir dakika kadar sonra', + minutesUntil: '{delta} dakika sonra', + hourUntil: 'bir saat kadar sonra', + hoursUntil: '{delta} saat kadar sonra', + dayUntil: 'bir gÃŒn sonra', + daysUntil: '{delta} gÃŒn sonra', + weekUntil: 'bir hafta sonra', + weeksUntil: '{delta} hafta sonra', + monthUntil: 'bir ay sonra', + monthsUntil: '{delta} ay sonra', + yearUntil: 'bir yıl sonra', + yearsUntil: '{delta} yıl sonra' + +}); + +/* +--- + +name: Locale.tr-TR.Form.Validator + +description: Form Validator messages for Turkish. + +license: MIT-style license + +authors: + - Faruk Can Bilir + +requires: + - Locale + +provides: [Locale.tr-TR.Form.Validator] + +... +*/ + +Locale.define('tr-TR', 'FormValidator', { + + required: 'Bu alan zorunlu.', + minLength: 'LÃŒtfen en az {minLength} karakter girin (siz {length} karakter girdiniz).', + maxLength: 'LÃŒtfen en fazla {maxLength} karakter girin (siz {length} karakter girdiniz).', + integer: 'LÃŒtfen bu alana sadece tamsayı girin. Ondalıklı sayılar (ör: 1.25) kullanılamaz.', + numeric: 'LÃŒtfen bu alana sadece sayısal deÄer girin (ör: "1", "1.1", "-1" ya da "-1.1").', + digits: 'LÃŒtfen bu alana sadece sayısal deÄer ve noktalama iÅareti girin (örneÄin, nokta ve tire içeren bir telefon numarası kullanılabilir).', + alpha: 'LÃŒtfen bu alanda yalnızca harf kullanın. BoÅluk ve diÄer karakterler kullanılamaz.', + alphanum: 'LÃŒtfen bu alanda sadece harf ve rakam kullanın. BoÅluk ve diÄer karakterler kullanılamaz.', + dateSuchAs: 'LÃŒtfen geçerli bir tarih girin (Ãr: {date})', + dateInFormatMDY: 'LÃŒtfen geçerli bir tarih girin (GG/AA/YYYY, ör: "31/12/1999")', + email: 'LÃŒtfen geçerli bir email adresi girin. Ãr: "kemal@etikan.com".', + url: 'LÃŒtfen geçerli bir URL girin. Ãr: http://www.example.com.', + currencyDollar: 'LÃŒtfen geçerli bir TL miktarı girin. Ãr: 100,00 TL .', + oneRequired: 'LÃŒtfen en az bir tanesini doldurun.', + errorPrefix: 'Hata: ', + warningPrefix: 'Uyarı: ', + + // Form.Validator.Extras + noSpace: 'Bu alanda boÅluk kullanılamaz.', + reqChkByNode: 'Hiçbir öÄe seçilmemiÅ.', + requiredChk: 'Bu alan zorunlu.', + reqChkByName: 'LÃŒtfen bir {label} girin.', + match: 'Bu alan, {matchName} alanıyla uyuÅmalı', + startDate: 'baÅlangıç tarihi', + endDate: 'bitiÅ tarihi', + currentDate: 'bugÃŒnÃŒn tarihi', + afterDate: 'Tarih, {label} tarihiyle aynı gÃŒn ya da ondan sonra olmalıdır.', + beforeDate: 'Tarih, {label} tarihiyle aynı gÃŒn ya da ondan önce olmalıdır.', + startMonth: 'LÃŒtfen bir baÅlangıç ayı seçin', + sameMonth: 'Bu iki tarih aynı ayda olmalı - bir tanesini deÄiÅtirmeniz gerekiyor.', + creditcard: 'GirdiÄiniz kredi kartı numarası geçersiz. LÃŒtfen kontrol edip tekrar deneyin. {length} hane girildi.' + +}); + +/* +--- + +name: Locale.tr-TR.Number + +description: Number messages for Turkish. + +license: MIT-style license + +authors: + - Faruk Can Bilir + +requires: + - Locale + - Locale.EU.Number + +provides: [Locale.tr-TR.Number] + +... +*/ + +Locale.define('tr-TR', 'Number', { + + currency: { + decimals: 0, + suffix: ' TL' + } + +}).inherit('EU', 'Number'); + +/* +--- + +name: Locale.uk-UA.Date + +description: Date messages for Ukrainian (utf-8). + +license: MIT-style license + +authors: + - Slik + +requires: + - Locale + +provides: [Locale.uk-UA.Date] + +... +*/ + +(function(){ + +var pluralize = function(n, one, few, many, other){ + var d = (n / 10).toInt(), + z = n % 10, + s = (n / 100).toInt(); + + if (d == 1 && n > 10) return many; + if (z == 1) return one; + if (z > 0 && z < 5) return few; + return many; +}; + +Locale.define('uk-UA', 'Date', { + + months: ['СÑÑеМÑ', 'ÐÑÑОй', 'ÐеÑезеМÑ', 'ÐвÑÑеМÑ', 'ТÑавеМÑ', 'ЧеÑвеМÑ', 'ÐОпеМÑ', 'СеÑпеМÑ', 'ÐеÑеÑеМÑ', 'ÐПвÑеМÑ', 'ÐОÑÑПпаЎ', 'ÐÑÑЎеМÑ'], + months_abbr: ['СÑÑ', 'ÐÑÑ', 'ÐеÑ', 'ÐвÑÑ', 'ТÑав', 'ЧеÑв', 'ÐОп', 'СеÑп', 'ÐеÑ', 'ÐПвÑ', 'ÐОÑÑ', 'ÐÑÑÐŽ' ], + days: ['ÐеЎÑлÑ', 'ÐПМеЎÑлПк', 'ÐÑвÑПÑПк', 'СеÑеЎа', 'ЧеÑвеÑ', "Ð'ÑÑМОÑÑ", 'СÑбПÑа'], + days_abbr: ['ÐÐŽ', 'ÐМ', 'ÐÑ', 'СÑ', 'ЧÑ', 'ÐÑ', 'Сб'], + + // Culture's date order: DD/MM/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d/%m/%Y', + shortTime: '%H:%M', + AM: 'ЎП пПлÑЎМÑ', + PM: 'пП пПлÑЎМÑ', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: 'ЌеМÑÑе Ñ
вОлОМО ÑПЌÑ', + minuteAgo: 'Ñ
Ð²ÐžÐ»ÐžÐœÑ ÑПЌÑ', + minutesAgo: function(delta){ return '{delta} ' + pluralize(delta, 'Ñ
вОлОМÑ', 'Ñ
вОлОМО', 'Ñ
вОлОМ') + ' ÑПЌÑ'; }, + hourAgo: 'Ð³ÐŸÐŽÐžÐœÑ ÑПЌÑ', + hoursAgo: function(delta){ return '{delta} ' + pluralize(delta, 'гПЎОМÑ', 'гПЎОМО', 'гПЎОМ') + ' ÑПЌÑ'; }, + dayAgo: 'вÑПÑа', + daysAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ЎеМÑ', 'ЎМÑ', 'ЎМÑв') + ' ÑПЌÑ'; }, + weekAgo: 'ÑÐžÐ¶ÐŽÐµÐœÑ ÑПЌÑ', + weeksAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ÑОжЎеМÑ', 'ÑОжМÑ', 'ÑОжМÑв') + ' ÑПЌÑ'; }, + monthAgo: 'ÐŒÑÑÑÑÑ ÑПЌÑ', + monthsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ÐŒÑÑÑÑÑ', 'ÐŒÑÑÑÑÑ', 'ÐŒÑÑÑÑÑв') + ' ÑПЌÑ'; }, + yearAgo: 'ÑÑк ÑПЌÑ', + yearsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ÑÑк', 'ÑПкО', 'ÑПкÑв') + ' ÑПЌÑ'; }, + + lessThanMinuteUntil: 'за ЌОÑÑ', + minuteUntil: 'ÑеÑез Ñ
вОлОМÑ', + minutesUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'Ñ
вОлОМÑ', 'Ñ
вОлОМО', 'Ñ
вОлОМ'); }, + hourUntil: 'ÑеÑез гПЎОМÑ', + hoursUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'гПЎОМÑ', 'гПЎОМО', 'гПЎОМ'); }, + dayUntil: 'завÑÑа', + daysUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'ЎеМÑ', 'ЎМÑ', 'ЎМÑв'); }, + weekUntil: 'ÑеÑез ÑОжЎеМÑ', + weeksUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'ÑОжЎеМÑ', 'ÑОжМÑ', 'ÑОжМÑв'); }, + monthUntil: 'ÑеÑез ÐŒÑÑÑÑÑ', + monthesUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'ÐŒÑÑÑÑÑ', 'ÐŒÑÑÑÑÑ', 'ÐŒÑÑÑÑÑв'); }, + yearUntil: 'ÑеÑез ÑÑк', + yearsUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'ÑÑк', 'ÑПкО', 'ÑПкÑв'); } + +}); + +})(); + +/* +--- + +name: Locale.uk-UA.Form.Validator + +description: Form Validator messages for Ukrainian (utf-8). + +license: MIT-style license + +authors: + - Slik + +requires: + - Locale + +provides: [Locale.uk-UA.Form.Validator] + +... +*/ + +Locale.define('uk-UA', 'FormValidator', { + + required: 'Ње пПле пПвОММе бÑÑО запПвМеМОЌ.', + minLength: 'ÐвеЎÑÑÑ Ñ
ПÑа б {minLength} ÑОЌвПлÑв (ÐО ввелО {length}).', + maxLength: 'ÐÑлÑкÑÑÑÑ ÑОЌвПлÑв Ме ЌПже бÑÑО бÑлÑÑе {maxLength} (ÐО ввелО {length}).', + integer: 'ÐвеЎÑÑÑ Ð² Ñе пПле ÑОÑлП. ÐÑÐŸÐ±ÐŸÐ²Ñ ÑОÑла (МапÑОклаЎ 1.25) Ме ЎПзвПлеМÑ.', + numeric: 'ÐвеЎÑÑÑ Ð² Ñе пПле ÑОÑлП (МапÑОклаЎ "1" абП "1.1", абП "-1", абП "-1.1").', + digits: 'Ð ÑÑÐŸÐŒÑ Ð¿ÐŸÐ»Ñ Ð²Ðž ЌПжеÑе вОкПÑОÑÑПвÑваÑО лОÑе ÑОÑÑО Ñ Ð·ÐœÐ°ÐºÐž пÑМкÑÑаÑÑÑ (МапÑОклаЎ, ÑелеÑПММОй ÐœÐŸÐŒÐµÑ Ð· зМакаЌО ЎеÑÑÐ·Ñ Ð°Ð±ÐŸ з кÑапкаЌО).', + alpha: 'Ð ÑÑÐŸÐŒÑ Ð¿ÐŸÐ»Ñ ÐŒÐŸÐ¶ÐœÐ° вОкПÑОÑÑПвÑваÑО лОÑе лаÑОМÑÑÐºÑ Ð»ÑÑеÑО (a-z). ÐÑПбÑлО Ñ ÑМÑÑ ÑОЌвПлО забПÑПМеМÑ.', + alphanum: 'Ð ÑÑÐŸÐŒÑ Ð¿ÐŸÐ»Ñ ÐŒÐŸÐ¶ÐœÐ° вОкПÑОÑÑПвÑваÑО лОÑе лаÑОМÑÑÐºÑ Ð»ÑÑеÑО (a-z) Ñ ÑОÑÑО (0-9). ÐÑПбÑлО Ñ ÑМÑÑ ÑОЌвПлО забПÑПМеМÑ.', + dateSuchAs: 'ÐвеЎÑÑÑ ÐºÐŸÑекÑÐœÑ ÐŽÐ°ÑÑ {date}.', + dateInFormatMDY: 'ÐвеЎÑÑÑ ÐŽÐ°ÑÑ Ð² ÑПÑЌаÑÑ ÐÐ/ÐÐ/Ð Ð Ð Ð (МапÑОклаЎ "12/31/2009").', + email: 'ÐвеЎÑÑÑ ÐºÐŸÑекÑÐœÑ Ð°ÐŽÑеÑÑ ÐµÐ»ÐµÐºÑÑÐŸÐœÐœÐŸÑ Ð¿ÐŸÑÑО (МапÑОклаЎ "name@domain.com").', + url: 'ÐвеЎÑÑÑ ÐºÐŸÑекÑМе ÑМÑеÑМеÑ-пПÑÐžÐ»Ð°ÐœÐœÑ (МапÑОклаЎ http://www.example.com).', + currencyDollar: 'ÐвеЎÑÑÑ ÑÑÐŒÑ Ð² ЎПлаÑаÑ
(МапÑОклаЎ "$100.00").', + oneRequired: 'ÐапПвМÑÑÑ ÐŸÐŽÐœÐµ з пПлÑв.', + errorPrefix: 'ÐПЌОлка: ', + warningPrefix: 'Увага: ', + + noSpace: 'ÐÑПбÑлО забПÑПМеМÑ.', + reqChkByNode: 'Ðе вÑÐŽÐŒÑÑеМП жПЎМПгП ваÑÑаМÑÑ.', + requiredChk: 'Ње пПле пПвОММе бÑÑО вÑÐŒÑÑеМОЌ.', + reqChkByName: 'ÐÑÐŽÑ Ð»Ð°Ñка, вÑÐŽÐŒÑÑÑÑе {label}.', + match: 'Ње пПле пПвОММП вÑЎпПвÑЎаÑО {matchName}', + startDate: 'пПÑаÑкПва ЎаÑа', + endDate: 'кÑМÑева ЎаÑа', + currentDate: 'ÑÑПгПЎМÑÑÐœÑ ÐŽÐ°Ñа', + afterDate: 'ÐŠÑ ÐŽÐ°Ñа пПвОММа бÑÑО ÑÐ°ÐºÐŸÑ Ð¶, абП пÑзМÑÑÐŸÑ Ð·Ð° {label}.', + beforeDate: 'ÐŠÑ ÐŽÐ°Ñа пПвОММа бÑÑО ÑÐ°ÐºÐŸÑ Ð¶, абП ÑаМÑÑÐŸÑ Ð·Ð° {label}.', + startMonth: 'ÐÑÐŽÑ Ð»Ð°Ñка, вОбеÑÑÑÑ Ð¿ÐŸÑаÑкПвОй ÐŒÑÑÑÑÑ', + sameMonth: 'ÐŠÑ ÐŽÐ°ÑО Ð¿ÐŸÐ²ÐžÐœÐœÑ Ð²ÑЎМПÑОÑОÑÑ ÐŸÐŽÐœÐŸÐ³ÐŸ Ñ ÑПгП ж ÐŒÑÑÑÑÑ. ÐÑÐŽÑ Ð»Ð°Ñка, зЌÑМÑÑÑ ÐŸÐŽÐœÑ Ð· МОÑ
.', + creditcard: 'ÐÐŸÐŒÐµÑ ÐºÑеЎОÑÐœÐŸÑ ÐºÐ°ÑÑО ввеЎеМОй МепÑавОлÑМП. ÐÑÐŽÑ Ð»Ð°Ñка, пеÑевÑÑÑе йПгП. ÐвеЎеМП {length} ÑОЌвПлÑв.' + +}); + +/* +--- + +name: Locale.zh-CH.Date + +description: Date messages for Chinese (simplified and traditional). + +license: MIT-style license + +authors: + - YMind Chan + +requires: + - Locale + +provides: [Locale.zh-CH.Date] + +... +*/ + +// Simplified Chinese +Locale.define('zh-CHS', 'Date', { + + months: ['äžæ', 'äºæ', 'äžæ', 'åæ', 'äºæ', 'å
æ', 'äžæ', 'å
«æ', '乿', 'åæ', 'åäžæ', 'åäºæ'], + months_abbr: ['äž', 'äº', 'äž', 'å', 'äº', 'å
', 'äž', 'å
«', 'ä¹', 'å', 'åäž', 'åäº'], + days: ['æææ¥', 'ææäž', 'ææäº', 'ææäž', 'ææå', 'ææäº', 'ææå
'], + days_abbr: ['æ¥', 'äž', 'äº', 'äž', 'å', 'äº', 'å
'], + + // Culture's date order: YYYY-MM-DD + dateOrder: ['year', 'month', 'date'], + shortDate: '%Y-%m-%d', + shortTime: '%I:%M%p', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: 'äžå°1åéå', + minuteAgo: '倧纊1åéå', + minutesAgo: '{delta}åéä¹å', + hourAgo: '倧纊1å°æ¶å', + hoursAgo: '倧纊{delta}å°æ¶å', + dayAgo: '1倩å', + daysAgo: '{delta}倩å', + weekAgo: '1ææå', + weeksAgo: '{delta}ææå', + monthAgo: '1䞪æå', + monthsAgo: '{delta}䞪æå', + yearAgo: '1幎å', + yearsAgo: '{delta}幎å', + + lessThanMinuteUntil: 'ä»ç°åšåŒå§äžå°1åé', + minuteUntil: 'ä»ç°åšåŒå§çŽ1åé', + minutesUntil: 'ä»ç°åšåŒå§çºŠ{delta}åé', + hourUntil: 'ä»ç°åšåŒå§1å°æ¶', + hoursUntil: 'ä»ç°åšåŒå§çºŠ{delta}å°æ¶', + dayUntil: 'ä»ç°åšåŒå§1倩', + daysUntil: 'ä»ç°åšåŒå§{delta}倩', + weekUntil: 'ä»ç°åšåŒå§1ææ', + weeksUntil: 'ä»ç°åšåŒå§{delta}ææ', + monthUntil: 'ä»ç°åšåŒå§äžäžªæ', + monthsUntil: 'ä»ç°åšåŒå§{delta}䞪æ', + yearUntil: 'ä»ç°åšåŒå§1幎', + yearsUntil: 'ä»ç°åšåŒå§{delta}幎' + +}); + +// Traditional Chinese +Locale.define('zh-CHT', 'Date', { + + months: ['äžæ', 'äºæ', 'äžæ', 'åæ', 'äºæ', 'å
æ', 'äžæ', 'å
«æ', '乿', 'åæ', 'åäžæ', 'åäºæ'], + months_abbr: ['äž', 'äº', 'äž', 'å', 'äº', 'å
', 'äž', 'å
«', 'ä¹', 'å', 'åäž', 'åäº'], + days: ['æææ¥', 'ææäž', 'ææäº', 'ææäž', 'ææå', 'ææäº', 'ææå
'], + days_abbr: ['æ¥', 'äž', 'äº', 'äž', 'å', 'äº', 'å
'], + + // Culture's date order: YYYY-MM-DD + dateOrder: ['year', 'month', 'date'], + shortDate: '%Y-%m-%d', + shortTime: '%I:%M%p', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: 'äžå°1åéå', + minuteAgo: '倧çŽ1åéå', + minutesAgo: '{delta}åéä¹å', + hourAgo: '倧çŽ1å°æå', + hoursAgo: '倧çŽ{delta}å°æå', + dayAgo: '1倩å', + daysAgo: '{delta}倩å', + weekAgo: '1ææå', + weeksAgo: '{delta}ææå', + monthAgo: '1䞪æå', + monthsAgo: '{delta}䞪æå', + yearAgo: '1幎å', + yearsAgo: '{delta}幎å', + + lessThanMinuteUntil: 'åŸçŸåšéå§äžå°1åé', + minuteUntil: 'åŸçŸåšéå§çŽ1åé', + minutesUntil: 'åŸçŸåšéå§çŽ{delta}åé', + hourUntil: 'åŸçŸåšéå§1å°æ', + hoursUntil: 'åŸçŸåšéå§çŽ{delta}å°æ', + dayUntil: 'åŸçŸåšéå§1倩', + daysUntil: 'åŸçŸåšéå§{delta}倩', + weekUntil: 'åŸçŸåšéå§1ææ', + weeksUntil: 'åŸçŸåšéå§{delta}ææ', + monthUntil: 'åŸçŸåšéå§äžåæ', + monthsUntil: 'åŸçŸåšéå§{delta}åæ', + yearUntil: 'åŸçŸåšéå§1幎', + yearsUntil: 'åŸçŸåšéå§{delta}幎' + +}); + +/* +--- + +name: Locale.zh-CH.Form.Validator + +description: Form Validator messages for Chinese (simplified and traditional). + +license: MIT-style license + +authors: + - YMind Chan + +requires: + - Locale + - Form.Validator + +provides: [Form.zh-CH.Form.Validator, Form.Validator.CurrencyYuanValidator] + +... +*/ + +// Simplified Chinese +Locale.define('zh-CHS', 'FormValidator', { + + required: 'æ€é¡¹å¿
å¡«ã', + minLength: '请è³å°èŸå
¥ {minLength} 䞪å笊 (å·²èŸå
¥ {length} 䞪)ã', + maxLength: 'æå€åªèœèŸå
¥ {maxLength} 䞪å笊 (å·²èŸå
¥ {length} 䞪)ã', + integer: '请èŸå
¥äžäžªæŽæ°ïŒäžèœå
å«å°æ°ç¹ãäŸåŠïŒ"1", "200"ã', + numeric: '请èŸå
¥äžäžªæ°åïŒäŸåŠïŒ"1", "1.1", "-1", "-1.1"ã', + digits: '请èŸå
¥ç±æ°ååæ ç¹ç¬Šå·ç»æçå
容ãäŸåŠçµè¯å·ç ã', + alpha: '请èŸå
¥ A-Z ç 26 äžªåæ¯ïŒäžèœå
å«ç©ºæ Œæä»»äœå
¶ä»å笊ã', + alphanum: '请èŸå
¥ A-Z ç 26 äžªåæ¯æ 0-9 ç 10 䞪æ°åïŒäžèœå
å«ç©ºæ Œæä»»äœå
¶ä»å笊ã', + dateSuchAs: '请èŸå
¥åæ³çæ¥ææ ŒåŒïŒåŠïŒ{date}ã', + dateInFormatMDY: '请èŸå
¥åæ³çæ¥ææ ŒåŒïŒäŸåŠïŒYYYY-MM-DD ("2010-12-31")ã', + email: '请èŸå
¥åæ³ççµåä¿¡ç®±å°åïŒäŸåŠïŒ"fred@domain.com"ã', + url: '请èŸå
¥åæ³ç Url å°åïŒäŸåŠïŒhttp://www.example.comã', + currencyDollar: '请èŸå
¥åæ³ç莧åžç¬Šå·ïŒäŸåŠïŒï¿¥100.0', + oneRequired: '请è³å°éæ©äžé¡¹ã', + errorPrefix: 'é误ïŒ', + warningPrefix: 'èŠåïŒ', + + // Form.Validator.Extras + noSpace: 'äžèœå
å«ç©ºæ Œã', + reqChkByNode: 'æªéæ©ä»»äœå
容ã', + requiredChk: 'æ€é¡¹å¿
å¡«ã', + reqChkByName: 'è¯·éæ© {label}.', + match: 'å¿
é¡»äž{matchName}çžå¹é
', + startDate: 'èµ·å§æ¥æ', + endDate: 'ç»ææ¥æ', + currentDate: 'åœåæ¥æ', + afterDate: 'æ¥æå¿
é¡»çäºææäº {label}.', + beforeDate: 'æ¥æå¿
é¡»æ©äºæçäº {label}.', + startMonth: 'è¯·éæ©èµ·å§æä»œ', + sameMonth: 'æšå¿
须修æ¹äž€äžªæ¥æäžçäžäžªïŒä»¥ç¡®ä¿å®ä»¬åšåäžæä»œã', + creditcard: 'æšèŸå
¥çä¿¡çšå¡å·ç äžæ£ç¡®ãåœåå·²èŸå
¥{length}䞪å笊ã' + +}); + +// Traditional Chinese +Locale.define('zh-CHT', 'FormValidator', { + + required: 'æ€é
å¿
å¡«ã ', + minLength: 'è«è³å°èŒžå
¥{minLength} åå笊(已茞å
¥{length} å)ã ', + maxLength: 'æå€åªèœèŒžå
¥{maxLength} åå笊(已茞å
¥{length} å)ã ', + integer: 'è«èŒžå
¥äžåæŽæžïŒäžèœå
å«å°æžé»ãäŸåŠïŒ"1", "200"ã ', + numeric: 'è«èŒžå
¥äžåæžåïŒäŸåŠïŒ"1", "1.1", "-1", "-1.1"ã ', + digits: 'è«èŒžå
¥ç±æžååæšé»ç¬Šèçµæçå
§å®¹ãäŸåŠé»è©±è碌ã ', + alpha: 'è«èŒžå
¥AZ ç26 å忝ïŒäžèœå
å«ç©ºæ Œæä»»äœå
¶ä»å笊ã ', + alphanum: 'è«èŒžå
¥AZ ç26 å忝æ0-9 ç10 åæžåïŒäžèœå
å«ç©ºæ Œæä»»äœå
¶ä»å笊ã ', + dateSuchAs: 'è«èŒžå
¥åæ³çæ¥ææ ŒåŒïŒåŠïŒ{date}ã ', + dateInFormatMDY: 'è«èŒžå
¥åæ³çæ¥ææ ŒåŒïŒäŸåŠïŒYYYY-MM-DD ("2010-12-31")ã ', + email: 'è«èŒžå
¥åæ³çé»åä¿¡ç®±å°åïŒäŸåŠïŒ"fred@domain.com"ã ', + url: 'è«èŒžå
¥åæ³çUrl å°åïŒäŸåŠïŒhttp://www.example.comã ', + currencyDollar: 'è«èŒžå
¥åæ³ç貚幣笊èïŒäŸåŠïŒï¿¥100.0', + oneRequired: 'è«è³å°éžæäžé
ã ', + errorPrefix: 'é¯èª€ïŒ', + warningPrefix: 'èŠåïŒ', + + // Form.Validator.Extras + noSpace: 'äžèœå
å«ç©ºæ Œã ', + reqChkByNode: 'æªéžæä»»äœå
§å®¹ã ', + requiredChk: 'æ€é
å¿
å¡«ã ', + reqChkByName: 'è«éžæ {label}.', + match: 'å¿
é è{matchName}çžå¹é
', + startDate: 'èµ·å§æ¥æ', + endDate: 'çµææ¥æ', + currentDate: 'ç¶åæ¥æ', + afterDate: 'æ¥æå¿
é çæŒæææŒ{label}.', + beforeDate: 'æ¥æå¿
é æ©æŒæçæŒ{label}.', + startMonth: 'è«éžæèµ·å§æä»œ', + sameMonth: 'æšå¿
é ä¿®æ¹å
©åæ¥æäžçäžåïŒä»¥ç¢ºä¿å®ååšåäžæä»œã ', + creditcard: 'æšèŒžå
¥çä¿¡çšå¡èç¢Œäžæ£ç¢ºãç¶å已茞å
¥{length}åå笊ã ' + +}); + +Form.Validator.add('validate-currency-yuan', { + + errorMsg: function(){ + return Form.Validator.getMsg('currencyYuan'); + }, + + test: function(element){ + // [ï¿¥]1[##][,###]+[.##] + // [ï¿¥]1###+[.##] + // [ï¿¥]0.## + // [ï¿¥].## + return Form.Validator.getValidator('IsEmpty').test(element) || (/^ï¿¥?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(element.get('value')); + } + +}); + +/* +--- + +name: Locale.zh-CH.Number + +description: Number messages for for Chinese (simplified and traditional). + +license: MIT-style license + +authors: + - YMind Chan + +requires: + - Locale + - Locale.en-US.Number + +provides: [Locale.zh-CH.Number] + +... +*/ + +// Simplified Chinese +Locale.define('zh-CHS', 'Number', { + + currency: { + prefix: 'ï¿¥ ' + } + +}).inherit('en-US', 'Number'); + +// Traditional Chinese +Locale.define('zh-CHT').inherit('zh-CHS', 'Number'); + +/* +--- + +script: Request.JSONP.js + +name: Request.JSONP + +description: Defines Request.JSONP, a class for cross domain javascript via script injection. + +license: MIT-style license + +authors: + - Aaron Newton + - Guillermo Rauch + - Arian Stolwijk + +requires: + - Core/Element + - Core/Request + - MooTools.More + +provides: [Request.JSONP] + +... +*/ + +Request.JSONP = new Class({ + + Implements: [Chain, Events, Options], + + options: {/* + onRequest: function(src, scriptElement){}, + onComplete: function(data){}, + onSuccess: function(data){}, + onCancel: function(){}, + onTimeout: function(){}, + onError: function(){}, */ + onRequest: function(src){ + if (this.options.log && window.console && console.log){ + console.log('JSONP retrieving script with url:' + src); + } + }, + onError: function(src){ + if (this.options.log && window.console && console.warn){ + console.warn('JSONP '+ src +' will fail in Internet Explorer, which enforces a 2083 bytes length limit on URIs'); + } + }, + url: '', + callbackKey: 'callback', + injectScript: document.head, + data: '', + link: 'ignore', + timeout: 0, + log: false + }, + + initialize: function(options){ + this.setOptions(options); + }, + + send: function(options){ + if (!Request.prototype.check.call(this, options)) return this; + this.running = true; + + var type = typeOf(options); + if (type == 'string' || type == 'element') options = {data: options}; + options = Object.merge(this.options, options || {}); + + var data = options.data; + switch (typeOf(data)){ + case 'element': data = document.id(data).toQueryString(); break; + case 'object': case 'hash': data = Object.toQueryString(data); + } + + var index = this.index = Request.JSONP.counter++; + + var src = options.url + + (options.url.test('\\?') ? '&' :'?') + + (options.callbackKey) + + '=Request.JSONP.request_map.request_'+ index + + (data ? '&' + data : ''); + + if (src.length > 2083) this.fireEvent('error', src); + + Request.JSONP.request_map['request_' + index] = function(){ + this.success(arguments, index); + }.bind(this); + + var script = this.getScript(src).inject(options.injectScript); + this.fireEvent('request', [src, script]); + + if (options.timeout) this.timeout.delay(options.timeout, this); + + return this; + }, + + getScript: function(src){ + if (!this.script) this.script = new Element('script', { + type: 'text/javascript', + async: true, + src: src + }); + return this.script; + }, + + success: function(args, index){ + if (!this.running) return; + this.clear() + .fireEvent('complete', args).fireEvent('success', args) + .callChain(); + }, + + cancel: function(){ + if (this.running) this.clear().fireEvent('cancel'); + return this; + }, + + isRunning: function(){ + return !!this.running; + }, + + clear: function(){ + this.running = false; + if (this.script){ + this.script.destroy(); + this.script = null; + } + return this; + }, + + timeout: function(){ + if (this.running){ + this.running = false; + this.fireEvent('timeout', [this.script.get('src'), this.script]).fireEvent('failure').cancel(); + } + return this; + } + +}); + +Request.JSONP.counter = 0; +Request.JSONP.request_map = {}; + +/* +--- + +script: Request.Periodical.js + +name: Request.Periodical + +description: Requests the same URL to pull data from a server but increases the intervals if no data is returned to reduce the load + +license: MIT-style license + +authors: + - Christoph Pojer + +requires: + - Core/Request + - MooTools.More + +provides: [Request.Periodical] + +... +*/ + +Request.implement({ + + options: { + initialDelay: 5000, + delay: 5000, + limit: 60000 + }, + + startTimer: function(data){ + var fn = function(){ + if (!this.running) this.send({data: data}); + }; + this.lastDelay = this.options.initialDelay; + this.timer = fn.delay(this.lastDelay, this); + this.completeCheck = function(response){ + clearTimeout(this.timer); + this.lastDelay = (response) ? this.options.delay : (this.lastDelay + this.options.delay).min(this.options.limit); + this.timer = fn.delay(this.lastDelay, this); + }; + return this.addEvent('complete', this.completeCheck); + }, + + stopTimer: function(){ + clearTimeout(this.timer); + return this.removeEvent('complete', this.completeCheck); + } + +}); + +/* +--- + +script: Request.Queue.js + +name: Request.Queue + +description: Controls several instances of Request and its variants to run only one request at a time. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Element + - Core/Request + - Class.Binds + +provides: [Request.Queue] + +... +*/ + +Request.Queue = new Class({ + + Implements: [Options, Events], + + Binds: ['attach', 'request', 'complete', 'cancel', 'success', 'failure', 'exception'], + + options: {/* + onRequest: function(argsPassedToOnRequest){}, + onSuccess: function(argsPassedToOnSuccess){}, + onComplete: function(argsPassedToOnComplete){}, + onCancel: function(argsPassedToOnCancel){}, + onException: function(argsPassedToOnException){}, + onFailure: function(argsPassedToOnFailure){}, + onEnd: function(){}, + */ + stopOnFailure: true, + autoAdvance: true, + concurrent: 1, + requests: {} + }, + + initialize: function(options){ + var requests; + if (options){ + requests = options.requests; + delete options.requests; + } + this.setOptions(options); + this.requests = {}; + this.queue = []; + this.reqBinders = {}; + + if (requests) this.addRequests(requests); + }, + + addRequest: function(name, request){ + this.requests[name] = request; + this.attach(name, request); + return this; + }, + + addRequests: function(obj){ + Object.each(obj, function(req, name){ + this.addRequest(name, req); + }, this); + return this; + }, + + getName: function(req){ + return Object.keyOf(this.requests, req); + }, + + attach: function(name, req){ + if (req._groupSend) return this; + ['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){ + if (!this.reqBinders[name]) this.reqBinders[name] = {}; + this.reqBinders[name][evt] = function(){ + this['on' + evt.capitalize()].apply(this, [name, req].append(arguments)); + }.bind(this); + req.addEvent(evt, this.reqBinders[name][evt]); + }, this); + req._groupSend = req.send; + req.send = function(options){ + this.send(name, options); + return req; + }.bind(this); + return this; + }, + + removeRequest: function(req){ + var name = typeOf(req) == 'object' ? this.getName(req) : req; + if (!name && typeOf(name) != 'string') return this; + req = this.requests[name]; + if (!req) return this; + ['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){ + req.removeEvent(evt, this.reqBinders[name][evt]); + }, this); + req.send = req._groupSend; + delete req._groupSend; + return this; + }, + + getRunning: function(){ + return Object.filter(this.requests, function(r){ + return r.running; + }); + }, + + isRunning: function(){ + return !!(Object.keys(this.getRunning()).length); + }, + + send: function(name, options){ + var q = function(){ + this.requests[name]._groupSend(options); + this.queue.erase(q); + }.bind(this); + + q.name = name; + if (Object.keys(this.getRunning()).length >= this.options.concurrent || (this.error && this.options.stopOnFailure)) this.queue.push(q); + else q(); + return this; + }, + + hasNext: function(name){ + return (!name) ? !!this.queue.length : !!this.queue.filter(function(q){ return q.name == name; }).length; + }, + + resume: function(){ + this.error = false; + (this.options.concurrent - Object.keys(this.getRunning()).length).times(this.runNext, this); + return this; + }, + + runNext: function(name){ + if (!this.queue.length) return this; + if (!name){ + this.queue[0](); + } else { + var found; + this.queue.each(function(q){ + if (!found && q.name == name){ + found = true; + q(); + } + }); + } + return this; + }, + + runAll: function(){ + this.queue.each(function(q){ + q(); + }); + return this; + }, + + clear: function(name){ + if (!name){ + this.queue.empty(); + } else { + this.queue = this.queue.map(function(q){ + if (q.name != name) return q; + else return false; + }).filter(function(q){ + return q; + }); + } + return this; + }, + + cancel: function(name){ + this.requests[name].cancel(); + return this; + }, + + onRequest: function(){ + this.fireEvent('request', arguments); + }, + + onComplete: function(){ + this.fireEvent('complete', arguments); + if (!this.queue.length) this.fireEvent('end'); + }, + + onCancel: function(){ + if (this.options.autoAdvance && !this.error) this.runNext(); + this.fireEvent('cancel', arguments); + }, + + onSuccess: function(){ + if (this.options.autoAdvance && !this.error) this.runNext(); + this.fireEvent('success', arguments); + }, + + onFailure: function(){ + this.error = true; + if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext(); + this.fireEvent('failure', arguments); + }, + + onException: function(){ + this.error = true; + if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext(); + this.fireEvent('exception', arguments); + } + +}); + +/* +--- + +script: Array.Extras.js + +name: Array.Extras + +description: Extends the Array native object to include useful methods to work with arrays. + +license: MIT-style license + +authors: + - Christoph Pojer + - Sebastian MarkbÃ¥ge + +requires: + - Core/Array + - MooTools.More + +provides: [Array.Extras] + +... +*/ + +(function(nil){ + +Array.implement({ + + min: function(){ + return Math.min.apply(null, this); + }, + + max: function(){ + return Math.max.apply(null, this); + }, + + average: function(){ + return this.length ? this.sum() / this.length : 0; + }, + + sum: function(){ + var result = 0, l = this.length; + if (l){ + while (l--){ + if (this[l] != null) result += parseFloat(this[l]); + } + } + return result; + }, + + unique: function(){ + return [].combine(this); + }, + + shuffle: function(){ + for (var i = this.length; i && --i;){ + var temp = this[i], r = Math.floor(Math.random() * ( i + 1 )); + this[i] = this[r]; + this[r] = temp; + } + return this; + }, + + reduce: function(fn, value){ + for (var i = 0, l = this.length; i < l; i++){ + if (i in this) value = value === nil ? this[i] : fn.call(null, value, this[i], i, this); + } + return value; + }, + + reduceRight: function(fn, value){ + var i = this.length; + while (i--){ + if (i in this) value = value === nil ? this[i] : fn.call(null, value, this[i], i, this); + } + return value; + }, + + pluck: function(prop){ + return this.map(function(item){ + return item[prop]; + }); + } + +}); + +})(); + +/* +--- + +script: Date.Extras.js + +name: Date.Extras + +description: Extends the Date native object to include extra methods (on top of those in Date.js). + +license: MIT-style license + +authors: + - Aaron Newton + - Scott Kyle + +requires: + - Date + +provides: [Date.Extras] + +... +*/ + +Date.implement({ + + timeDiffInWords: function(to){ + return Date.distanceOfTimeInWords(this, to || new Date); + }, + + timeDiff: function(to, separator){ + if (to == null) to = new Date; + var delta = ((to - this) / 1000).floor().abs(); + + var vals = [], + durations = [60, 60, 24, 365, 0], + names = ['s', 'm', 'h', 'd', 'y'], + value, duration; + + for (var item = 0; item < durations.length; item++){ + if (item && !delta) break; + value = delta; + if ((duration = durations[item])){ + value = (delta % duration); + delta = (delta / duration).floor(); + } + vals.unshift(value + (names[item] || '')); + } + + return vals.join(separator || ':'); + } + +}).extend({ + + distanceOfTimeInWords: function(from, to){ + return Date.getTimePhrase(((to - from) / 1000).toInt()); + }, + + getTimePhrase: function(delta){ + var suffix = (delta < 0) ? 'Until' : 'Ago'; + if (delta < 0) delta *= -1; + + var units = { + minute: 60, + hour: 60, + day: 24, + week: 7, + month: 52 / 12, + year: 12, + eon: Infinity + }; + + var msg = 'lessThanMinute'; + + for (var unit in units){ + var interval = units[unit]; + if (delta < 1.5 * interval){ + if (delta > 0.75 * interval) msg = unit; + break; + } + delta /= interval; + msg = unit + 's'; + } + + delta = delta.round(); + return Date.getMsg(msg + suffix, delta).substitute({delta: delta}); + } + +}).defineParsers( + + { + // "today", "tomorrow", "yesterday" + re: /^(?:tod|tom|yes)/i, + handler: function(bits){ + var d = new Date().clearTime(); + switch (bits[0]){ + case 'tom': return d.increment(); + case 'yes': return d.decrement(); + default: return d; + } + } + }, + + { + // "next Wednesday", "last Thursday" + re: /^(next|last) ([a-z]+)$/i, + handler: function(bits){ + var d = new Date().clearTime(); + var day = d.getDay(); + var newDay = Date.parseDay(bits[2], true); + var addDays = newDay - day; + if (newDay <= day) addDays += 7; + if (bits[1] == 'last') addDays -= 7; + return d.set('date', d.getDate() + addDays); + } + } + +).alias('timeAgoInWords', 'timeDiffInWords'); + +/* +--- + +name: Hash + +description: Contains Hash Prototypes. Provides a means for overcoming the JavaScript practical impossibility of extending native Objects. + +license: MIT-style license. + +requires: + - Core/Object + - MooTools.More + +provides: [Hash] + +... +*/ + +(function(){ + +if (this.Hash) return; + +var Hash = this.Hash = new Type('Hash', function(object){ + if (typeOf(object) == 'hash') object = Object.clone(object.getClean()); + for (var key in object) this[key] = object[key]; + return this; +}); + +this.$H = function(object){ + return new Hash(object); +}; + +Hash.implement({ + + forEach: function(fn, bind){ + Object.forEach(this, fn, bind); + }, + + getClean: function(){ + var clean = {}; + for (var key in this){ + if (this.hasOwnProperty(key)) clean[key] = this[key]; + } + return clean; + }, + + getLength: function(){ + var length = 0; + for (var key in this){ + if (this.hasOwnProperty(key)) length++; + } + return length; + } + +}); + +Hash.alias('each', 'forEach'); + +Hash.implement({ + + has: Object.prototype.hasOwnProperty, + + keyOf: function(value){ + return Object.keyOf(this, value); + }, + + hasValue: function(value){ + return Object.contains(this, value); + }, + + extend: function(properties){ + Hash.each(properties || {}, function(value, key){ + Hash.set(this, key, value); + }, this); + return this; + }, + + combine: function(properties){ + Hash.each(properties || {}, function(value, key){ + Hash.include(this, key, value); + }, this); + return this; + }, + + erase: function(key){ + if (this.hasOwnProperty(key)) delete this[key]; + return this; + }, + + get: function(key){ + return (this.hasOwnProperty(key)) ? this[key] : null; + }, + + set: function(key, value){ + if (!this[key] || this.hasOwnProperty(key)) this[key] = value; + return this; + }, + + empty: function(){ + Hash.each(this, function(value, key){ + delete this[key]; + }, this); + return this; + }, + + include: function(key, value){ + if (this[key] == undefined) this[key] = value; + return this; + }, + + map: function(fn, bind){ + return new Hash(Object.map(this, fn, bind)); + }, + + filter: function(fn, bind){ + return new Hash(Object.filter(this, fn, bind)); + }, + + every: function(fn, bind){ + return Object.every(this, fn, bind); + }, + + some: function(fn, bind){ + return Object.some(this, fn, bind); + }, + + getKeys: function(){ + return Object.keys(this); + }, + + getValues: function(){ + return Object.values(this); + }, + + toQueryString: function(base){ + return Object.toQueryString(this, base); + } + +}); + +Hash.alias({indexOf: 'keyOf', contains: 'hasValue'}); + + +})(); + + +/* +--- + +script: Hash.Extras.js + +name: Hash.Extras + +description: Extends the Hash Type to include getFromPath which allows a path notation to child elements. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Hash + - Object.Extras + +provides: [Hash.Extras] + +... +*/ + +Hash.implement({ + + getFromPath: function(notation){ + return Object.getFromPath(this, notation); + }, + + cleanValues: function(method){ + return new Hash(Object.cleanValues(this, method)); + }, + + run: function(){ + Object.run(arguments); + } + +}); + +/* +--- +name: Number.Format +description: Extends the Number Type object to include a number formatting method. +license: MIT-style license +authors: [Arian Stolwijk] +requires: [Core/Number, Locale.en-US.Number] +# Number.Extras is for compatibility +provides: [Number.Format, Number.Extras] +... +*/ + + +Number.implement({ + + format: function(options){ + // Thanks dojo and YUI for some inspiration + var value = this; + options = options ? Object.clone(options) : {}; + var getOption = function(key){ + if (options[key] != null) return options[key]; + return Locale.get('Number.' + key); + }; + + var negative = value < 0, + decimal = getOption('decimal'), + precision = getOption('precision'), + group = getOption('group'), + decimals = getOption('decimals'); + + if (negative){ + var negativeLocale = getOption('negative') || {}; + if (negativeLocale.prefix == null && negativeLocale.suffix == null) negativeLocale.prefix = '-'; + ['prefix', 'suffix'].each(function(key){ + if (negativeLocale[key]) options[key] = getOption(key) + negativeLocale[key]; + }); + + value = -value; + } + + var prefix = getOption('prefix'), + suffix = getOption('suffix'); + + if (decimals !== '' && decimals >= 0 && decimals <= 20) value = value.toFixed(decimals); + if (precision >= 1 && precision <= 21) value = (+value).toPrecision(precision); + + value += ''; + var index; + if (getOption('scientific') === false && value.indexOf('e') > -1){ + var match = value.split('e'), + zeros = +match[1]; + value = match[0].replace('.', ''); + + if (zeros < 0){ + zeros = -zeros - 1; + index = match[0].indexOf('.'); + if (index > -1) zeros -= index - 1; + while (zeros--) value = '0' + value; + value = '0.' + value; + } else { + index = match[0].lastIndexOf('.'); + if (index > -1) zeros -= match[0].length - index - 1; + while (zeros--) value += '0'; + } + } + + if (decimal != '.') value = value.replace('.', decimal); + + if (group){ + index = value.lastIndexOf(decimal); + index = (index > -1) ? index : value.length; + var newOutput = value.substring(index), + i = index; + + while (i--){ + if ((index - i - 1) % 3 == 0 && i != (index - 1)) newOutput = group + newOutput; + newOutput = value.charAt(i) + newOutput; + } + + value = newOutput; + } + + if (prefix) value = prefix + value; + if (suffix) value += suffix; + + return value; + }, + + formatCurrency: function(decimals){ + var locale = Locale.get('Number.currency') || {}; + if (locale.scientific == null) locale.scientific = false; + locale.decimals = decimals != null ? decimals + : (locale.decimals == null ? 2 : locale.decimals); + + return this.format(locale); + }, + + formatPercentage: function(decimals){ + var locale = Locale.get('Number.percentage') || {}; + if (locale.suffix == null) locale.suffix = '%'; + locale.decimals = decimals != null ? decimals + : (locale.decimals == null ? 2 : locale.decimals); + + return this.format(locale); + } + +}); + +/* +--- + +script: URI.js + +name: URI + +description: Provides methods useful in managing the window location and uris. + +license: MIT-style license + +authors: + - Sebastian MarkbÃ¥ge + - Aaron Newton + +requires: + - Core/Object + - Core/Class + - Core/Class.Extras + - Core/Element + - String.QueryString + +provides: [URI] + +... +*/ + +(function(){ + +var toString = function(){ + return this.get('value'); +}; + +var URI = this.URI = new Class({ + + Implements: Options, + + options: { + /*base: false*/ + }, + + regex: /^(?:(\w+):)?(?:\/\/(?:(?:([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)?(\.\.?$|(?:[^?#\/]*\/)*)([^?#]*)(?:\?([^#]*))?(?:#(.*))?/, + parts: ['scheme', 'user', 'password', 'host', 'port', 'directory', 'file', 'query', 'fragment'], + schemes: {http: 80, https: 443, ftp: 21, rtsp: 554, mms: 1755, file: 0}, + + initialize: function(uri, options){ + this.setOptions(options); + var base = this.options.base || URI.base; + if (!uri) uri = base; + + if (uri && uri.parsed) this.parsed = Object.clone(uri.parsed); + else this.set('value', uri.href || uri.toString(), base ? new URI(base) : false); + }, + + parse: function(value, base){ + var bits = value.match(this.regex); + if (!bits) return false; + bits.shift(); + return this.merge(bits.associate(this.parts), base); + }, + + merge: function(bits, base){ + if ((!bits || !bits.scheme) && (!base || !base.scheme)) return false; + if (base){ + this.parts.every(function(part){ + if (bits[part]) return false; + bits[part] = base[part] || ''; + return true; + }); + } + bits.port = bits.port || this.schemes[bits.scheme.toLowerCase()]; + bits.directory = bits.directory ? this.parseDirectory(bits.directory, base ? base.directory : '') : '/'; + return bits; + }, + + parseDirectory: function(directory, baseDirectory){ + directory = (directory.substr(0, 1) == '/' ? '' : (baseDirectory || '/')) + directory; + if (!directory.test(URI.regs.directoryDot)) return directory; + var result = []; + directory.replace(URI.regs.endSlash, '').split('/').each(function(dir){ + if (dir == '..' && result.length > 0) result.pop(); + else if (dir != '.') result.push(dir); + }); + return result.join('/') + '/'; + }, + + combine: function(bits){ + return bits.value || bits.scheme + '://' + + (bits.user ? bits.user + (bits.password ? ':' + bits.password : '') + '@' : '') + + (bits.host || '') + (bits.port && bits.port != this.schemes[bits.scheme] ? ':' + bits.port : '') + + (bits.directory || '/') + (bits.file || '') + + (bits.query ? '?' + bits.query : '') + + (bits.fragment ? '#' + bits.fragment : ''); + }, + + set: function(part, value, base){ + if (part == 'value'){ + var scheme = value.match(URI.regs.scheme); + if (scheme) scheme = scheme[1]; + if (scheme && this.schemes[scheme.toLowerCase()] == null) this.parsed = { scheme: scheme, value: value }; + else this.parsed = this.parse(value, (base || this).parsed) || (scheme ? { scheme: scheme, value: value } : { value: value }); + } else if (part == 'data'){ + this.setData(value); + } else { + this.parsed[part] = value; + } + return this; + }, + + get: function(part, base){ + switch (part){ + case 'value': return this.combine(this.parsed, base ? base.parsed : false); + case 'data' : return this.getData(); + } + return this.parsed[part] || ''; + }, + + go: function(){ + document.location.href = this.toString(); + }, + + toURI: function(){ + return this; + }, + + getData: function(key, part){ + var qs = this.get(part || 'query'); + if (!(qs || qs === 0)) return key ? null : {}; + var obj = qs.parseQueryString(); + return key ? obj[key] : obj; + }, + + setData: function(values, merge, part){ + if (typeof values == 'string'){ + var data = this.getData(); + data[arguments[0]] = arguments[1]; + values = data; + } else if (merge){ + values = Object.merge(this.getData(null, part), values); + } + return this.set(part || 'query', Object.toQueryString(values)); + }, + + clearData: function(part){ + return this.set(part || 'query', ''); + }, + + toString: toString, + valueOf: toString + +}); + +URI.regs = { + endSlash: /\/$/, + scheme: /^(\w+):/, + directoryDot: /\.\/|\.$/ +}; + +URI.base = new URI(Array.from(document.getElements('base[href]', true)).getLast(), {base: document.location}); + +String.implement({ + + toURI: function(options){ + return new URI(this, options); + } + +}); + +})(); + +/* +--- + +script: URI.Relative.js + +name: URI.Relative + +description: Extends the URI class to add methods for computing relative and absolute urls. + +license: MIT-style license + +authors: + - Sebastian MarkbÃ¥ge + + +requires: + - Class.refactor + - URI + +provides: [URI.Relative] + +... +*/ + +URI = Class.refactor(URI, { + + combine: function(bits, base){ + if (!base || bits.scheme != base.scheme || bits.host != base.host || bits.port != base.port) + return this.previous.apply(this, arguments); + var end = bits.file + (bits.query ? '?' + bits.query : '') + (bits.fragment ? '#' + bits.fragment : ''); + + if (!base.directory) return (bits.directory || (bits.file ? '' : './')) + end; + + var baseDir = base.directory.split('/'), + relDir = bits.directory.split('/'), + path = '', + offset; + + var i = 0; + for (offset = 0; offset < baseDir.length && offset < relDir.length && baseDir[offset] == relDir[offset]; offset++); + for (i = 0; i < baseDir.length - offset - 1; i++) path += '../'; + for (i = offset; i < relDir.length - 1; i++) path += relDir[i] + '/'; + + return (path || (bits.file ? '' : './')) + end; + }, + + toAbsolute: function(base){ + base = new URI(base); + if (base) base.set('directory', '').set('file', ''); + return this.toRelative(base); + }, + + toRelative: function(base){ + return this.get('value', new URI(base)); + } + +}); + +/* +--- + +script: Assets.js + +name: Assets + +description: Provides methods to dynamically load JavaScript, CSS, and Image files into the document. + +license: MIT-style license + +authors: + - Valerio Proietti + +requires: + - Core/Element.Event + - MooTools.More + +provides: [Assets] + +... +*/ + +var Asset = { + + javascript: function(source, properties){ + if (!properties) properties = {}; + + var script = new Element('script', {src: source, type: 'text/javascript'}), + doc = properties.document || document, + load = properties.onload || properties.onLoad; + + delete properties.onload; + delete properties.onLoad; + delete properties.document; + + if (load){ + if (!script.addEventListener){ + script.addEvent('readystatechange', function(){ + if (['loaded', 'complete'].contains(this.readyState)) load.call(this); + }); + } else { + script.addEvent('load', load); + } + } + + return script.set(properties).inject(doc.head); + }, + + css: function(source, properties){ + if (!properties) properties = {}; + + var load = properties.onload || properties.onLoad, + doc = properties.document || document, + timeout = properties.timeout || 3000; + + ['onload', 'onLoad', 'document'].each(function(prop){ + delete properties[prop]; + }); + + var link = new Element('link', { + type: 'text/css', + rel: 'stylesheet', + media: 'screen', + href: source + }).setProperties(properties).inject(doc.head); + + if (load){ + // based on article at http://www.yearofmoo.com/2011/03/cross-browser-stylesheet-preloading.html + var loaded = false, retries = 0; + var check = function(){ + var stylesheets = document.styleSheets; + for (var i = 0; i < stylesheets.length; i++){ + var file = stylesheets[i]; + var owner = file.ownerNode ? file.ownerNode : file.owningElement; + if (owner && owner == link){ + loaded = true; + return load.call(link); + } + } + retries++; + if (!loaded && retries < timeout / 50) return setTimeout(check, 50); + } + setTimeout(check, 0); + } + return link; + }, + + image: function(source, properties){ + if (!properties) properties = {}; + + var image = new Image(), + element = document.id(image) || new Element('img'); + + ['load', 'abort', 'error'].each(function(name){ + var type = 'on' + name, + cap = 'on' + name.capitalize(), + event = properties[type] || properties[cap] || function(){}; + + delete properties[cap]; + delete properties[type]; + + image[type] = function(){ + if (!image) return; + if (!element.parentNode){ + element.width = image.width; + element.height = image.height; + } + image = image.onload = image.onabort = image.onerror = null; + event.delay(1, element, element); + element.fireEvent(name, element, 1); + }; + }); + + image.src = element.src = source; + if (image && image.complete) image.onload.delay(1); + return element.set(properties); + }, + + images: function(sources, options){ + sources = Array.from(sources); + + var fn = function(){}, + counter = 0; + + options = Object.merge({ + onComplete: fn, + onProgress: fn, + onError: fn, + properties: {} + }, options); + + return new Elements(sources.map(function(source, index){ + return Asset.image(source, Object.append(options.properties, { + onload: function(){ + counter++; + options.onProgress.call(this, counter, index, source); + if (counter == sources.length) options.onComplete(); + }, + onerror: function(){ + counter++; + options.onError.call(this, counter, index, source); + if (counter == sources.length) options.onComplete(); + } + })); + })); + } + +}; + +/* +--- + +script: Color.js + +name: Color + +description: Class for creating and manipulating colors in JavaScript. Supports HSB -> RGB Conversions and vice versa. + +license: MIT-style license + +authors: + - Valerio Proietti + +requires: + - Core/Array + - Core/String + - Core/Number + - Core/Hash + - Core/Function + - MooTools.More + +provides: [Color] + +... +*/ + +(function(){ + +var Color = this.Color = new Type('Color', function(color, type){ + if (arguments.length >= 3){ + type = 'rgb'; color = Array.slice(arguments, 0, 3); + } else if (typeof color == 'string'){ + if (color.match(/rgb/)) color = color.rgbToHex().hexToRgb(true); + else if (color.match(/hsb/)) color = color.hsbToRgb(); + else color = color.hexToRgb(true); + } + type = type || 'rgb'; + switch (type){ + case 'hsb': + var old = color; + color = color.hsbToRgb(); + color.hsb = old; + break; + case 'hex': color = color.hexToRgb(true); break; + } + color.rgb = color.slice(0, 3); + color.hsb = color.hsb || color.rgbToHsb(); + color.hex = color.rgbToHex(); + return Object.append(color, this); +}); + +Color.implement({ + + mix: function(){ + var colors = Array.slice(arguments); + var alpha = (typeOf(colors.getLast()) == 'number') ? colors.pop() : 50; + var rgb = this.slice(); + colors.each(function(color){ + color = new Color(color); + for (var i = 0; i < 3; i++) rgb[i] = Math.round((rgb[i] / 100 * (100 - alpha)) + (color[i] / 100 * alpha)); + }); + return new Color(rgb, 'rgb'); + }, + + invert: function(){ + return new Color(this.map(function(value){ + return 255 - value; + })); + }, + + setHue: function(value){ + return new Color([value, this.hsb[1], this.hsb[2]], 'hsb'); + }, + + setSaturation: function(percent){ + return new Color([this.hsb[0], percent, this.hsb[2]], 'hsb'); + }, + + setBrightness: function(percent){ + return new Color([this.hsb[0], this.hsb[1], percent], 'hsb'); + } + +}); + +this.$RGB = function(r, g, b){ + return new Color([r, g, b], 'rgb'); +}; + +this.$HSB = function(h, s, b){ + return new Color([h, s, b], 'hsb'); +}; + +this.$HEX = function(hex){ + return new Color(hex, 'hex'); +}; + +Array.implement({ + + rgbToHsb: function(){ + var red = this[0], + green = this[1], + blue = this[2], + hue = 0; + var max = Math.max(red, green, blue), + min = Math.min(red, green, blue); + var delta = max - min; + var brightness = max / 255, + saturation = (max != 0) ? delta / max : 0; + if (saturation != 0){ + var rr = (max - red) / delta; + var gr = (max - green) / delta; + var br = (max - blue) / delta; + if (red == max) hue = br - gr; + else if (green == max) hue = 2 + rr - br; + else hue = 4 + gr - rr; + hue /= 6; + if (hue < 0) hue++; + } + return [Math.round(hue * 360), Math.round(saturation * 100), Math.round(brightness * 100)]; + }, + + hsbToRgb: function(){ + var br = Math.round(this[2] / 100 * 255); + if (this[1] == 0){ + return [br, br, br]; + } else { + var hue = this[0] % 360; + var f = hue % 60; + var p = Math.round((this[2] * (100 - this[1])) / 10000 * 255); + var q = Math.round((this[2] * (6000 - this[1] * f)) / 600000 * 255); + var t = Math.round((this[2] * (6000 - this[1] * (60 - f))) / 600000 * 255); + switch (Math.floor(hue / 60)){ + case 0: return [br, t, p]; + case 1: return [q, br, p]; + case 2: return [p, br, t]; + case 3: return [p, q, br]; + case 4: return [t, p, br]; + case 5: return [br, p, q]; + } + } + return false; + } + +}); + +String.implement({ + + rgbToHsb: function(){ + var rgb = this.match(/\d{1,3}/g); + return (rgb) ? rgb.rgbToHsb() : null; + }, + + hsbToRgb: function(){ + var hsb = this.match(/\d{1,3}/g); + return (hsb) ? hsb.hsbToRgb() : null; + } + +}); + +})(); + + +/* +--- + +script: Group.js + +name: Group + +description: Class for monitoring collections of events + +license: MIT-style license + +authors: + - Valerio Proietti + +requires: + - Core/Events + - MooTools.More + +provides: [Group] + +... +*/ + +(function(){ + +this.Group = new Class({ + + initialize: function(){ + this.instances = Array.flatten(arguments); + }, + + addEvent: function(type, fn){ + var instances = this.instances, + len = instances.length, + togo = len, + args = new Array(len), + self = this; + + instances.each(function(instance, i){ + instance.addEvent(type, function(){ + if (!args[i]) togo--; + args[i] = arguments; + if (!togo){ + fn.call(self, instances, instance, args); + togo = len; + args = new Array(len); + } + }); + }); + } + +}); + +})(); + +/* +--- + +script: Hash.Cookie.js + +name: Hash.Cookie + +description: Class for creating, reading, and deleting Cookies in JSON format. + +license: MIT-style license + +authors: + - Valerio Proietti + - Aaron Newton + +requires: + - Core/Cookie + - Core/JSON + - MooTools.More + - Hash + +provides: [Hash.Cookie] + +... +*/ + +Hash.Cookie = new Class({ + + Extends: Cookie, + + options: { + autoSave: true + }, + + initialize: function(name, options){ + this.parent(name, options); + this.load(); + }, + + save: function(){ + var value = JSON.encode(this.hash); + if (!value || value.length > 4096) return false; //cookie would be truncated! + if (value == '{}') this.dispose(); + else this.write(value); + return true; + }, + + load: function(){ + this.hash = new Hash(JSON.decode(this.read(), true)); + return this; + } + +}); + +Hash.each(Hash.prototype, function(method, name){ + if (typeof method == 'function') Hash.Cookie.implement(name, function(){ + var value = method.apply(this.hash, arguments); + if (this.options.autoSave) this.save(); + return value; + }); +}); + +/* +--- + +name: Swiff + +description: Wrapper for embedding SWF movies. Supports External Interface Communication. + +license: MIT-style license. + +credits: + - Flash detection & Internet Explorer + Flash Player 9 fix inspired by SWFObject. + +requires: [Core/Options, Core/Object, Core/Element] + +provides: Swiff + +... +*/ + +(function(){ + +var Swiff = this.Swiff = new Class({ + + Implements: Options, + + options: { + id: null, + height: 1, + width: 1, + container: null, + properties: {}, + params: { + quality: 'high', + allowScriptAccess: 'always', + wMode: 'window', + swLiveConnect: true + }, + callBacks: {}, + vars: {} + }, + + toElement: function(){ + return this.object; + }, + + initialize: function(path, options){ + this.instance = 'Swiff_' + String.uniqueID(); + + this.setOptions(options); + options = this.options; + var id = this.id = options.id || this.instance; + var container = document.id(options.container); + + Swiff.CallBacks[this.instance] = {}; + + var params = options.params, vars = options.vars, callBacks = options.callBacks; + var properties = Object.append({height: options.height, width: options.width}, options.properties); + + var self = this; + + for (var callBack in callBacks){ + Swiff.CallBacks[this.instance][callBack] = (function(option){ + return function(){ + return option.apply(self.object, arguments); + }; + })(callBacks[callBack]); + vars[callBack] = 'Swiff.CallBacks.' + this.instance + '.' + callBack; + } + + params.flashVars = Object.toQueryString(vars); + if ('ActiveXObject' in window){ + properties.classid = 'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000'; + params.movie = path; + } else { + properties.type = 'application/x-shockwave-flash'; + } + properties.data = path; + + var build = '<object id="' + id + '"'; + for (var property in properties) build += ' ' + property + '="' + properties[property] + '"'; + build += '>'; + for (var param in params){ + if (params[param]) build += '<param name="' + param + '" value="' + params[param] + '" />'; + } + build += '</object>'; + this.object = ((container) ? container.empty() : new Element('div')).set('html', build).firstChild; + }, + + replaces: function(element){ + element = document.id(element, true); + element.parentNode.replaceChild(this.toElement(), element); + return this; + }, + + inject: function(element){ + document.id(element, true).appendChild(this.toElement()); + return this; + }, + + remote: function(){ + return Swiff.remote.apply(Swiff, [this.toElement()].append(arguments)); + } + +}); + +Swiff.CallBacks = {}; + +Swiff.remote = function(obj, fn){ + var rs = obj.CallFunction('<invoke name="' + fn + '" returntype="javascript">' + __flash__argumentsToXML(arguments, 2) + '</invoke>'); + return eval(rs); +}; + +})(); + +/* +--- +name: Table +description: LUA-Style table implementation. +license: MIT-style license +authors: + - Valerio Proietti +requires: [Core/Array] +provides: [Table] +... +*/ + +(function(){ + +var Table = this.Table = function(){ + + this.length = 0; + var keys = [], + values = []; + + this.set = function(key, value){ + var index = keys.indexOf(key); + if (index == -1){ + var length = keys.length; + keys[length] = key; + values[length] = value; + this.length++; + } else { + values[index] = value; + } + return this; + }; + + this.get = function(key){ + var index = keys.indexOf(key); + return (index == -1) ? null : values[index]; + }; + + this.erase = function(key){ + var index = keys.indexOf(key); + if (index != -1){ + this.length--; + keys.splice(index, 1); + return values.splice(index, 1)[0]; + } + return null; + }; + + this.each = this.forEach = function(fn, bind){ + for (var i = 0, l = this.length; i < l; i++) fn.call(bind, keys[i], values[i], this); + }; + +}; + +if (this.Type) new Type('Table', Table); + +})(); diff --git a/pyload/webui/themes/Dark/lib/MooTools/Purr/purr.js b/pyload/webui/themes/Dark/lib/MooTools/Purr/purr.js new file mode 100644 index 000000000..9cbc503d9 --- /dev/null +++ b/pyload/webui/themes/Dark/lib/MooTools/Purr/purr.js @@ -0,0 +1,309 @@ +/* +--- +script: purr.js + +description: Class to create growl-style popup notifications. + +license: MIT-style + +authors: [atom smith] + +requires: +- core/1.3: [Core, Browser, Array, Function, Number, String, Hash, Event, Class.Extras, Element.Event, Element.Style, Element.Dimensions, Fx.CSS, FX.Tween, Fx.Morph] + +provides: [Purr, Element.alert] +... +*/ + + +var Purr = new Class({ + + 'options': { + 'mode': 'top', + 'position': 'left', + 'elementAlertClass': 'purr-element-alert', + 'elements': { + 'wrapper': 'div', + 'alert': 'div', + 'buttonWrapper': 'div', + 'button': 'button' + }, + 'elementOptions': { + 'wrapper': { + 'styles': { + 'position': 'fixed', + 'z-index': '9999' + }, + 'class': 'purr-wrapper' + }, + 'alert': { + 'class': 'purr-alert', + 'styles': { + 'opacity': '.85' + } + }, + 'buttonWrapper': { + 'class': 'purr-button-wrapper' + }, + 'button': { + 'class': 'purr-button' + } + }, + 'alert': { + 'buttons': [], + 'clickDismiss': true, + 'hoverWait': true, + 'hideAfter': 5000, + 'fx': { + 'duration': 500 + }, + 'highlight': false, + 'highlightRepeat': false, + 'highlight': { + 'start': '#FF0', + 'end': false + } + } + }, + + 'Implements': [Options, Events, Chain], + + 'initialize': function(options){ + this.setOptions(options); + this.createWrapper(); + return this; + }, + + 'bindAlert': function(){ + return this.alert.bind(this); + }, + + 'createWrapper': function(){ + this.wrapper = new Element(this.options.elements.wrapper, this.options.elementOptions.wrapper); + if(this.options.mode == 'top') + { + this.wrapper.setStyle('top', 0); + } + else + { + this.wrapper.setStyle('bottom', 0); + } + document.id(document.body).grab(this.wrapper); + this.positionWrapper(this.options.position); + }, + + 'positionWrapper': function(position){ + if(typeOf(position) == 'object') + { + + var wrapperCoords = this.getWrapperCoords(); + + this.wrapper.setStyles({ + 'bottom': '', + 'left': position.x, + 'top': position.y - wrapperCoords.height, + 'position': 'absolute' + }); + } + else if(position == 'left') + { + this.wrapper.setStyle('left', 0); + } + else if(position == 'right') + { + this.wrapper.setStyle('right', 0); + } + else + { + this.wrapper.setStyle('left', (window.innerWidth / 2) - (this.getWrapperCoords().width / 2)); + } + return this; + }, + + 'getWrapperCoords': function(){ + this.wrapper.setStyle('visibility', 'hidden'); + var measurer = this.alert('need something in here to measure'); + var coords = this.wrapper.getCoordinates(); + measurer.destroy(); + this.wrapper.setStyle('visibility',''); + return coords; + }, + + 'alert': function(msg, options){ + + options = Object.merge({}, this.options.alert, options || {}); + + var alert = new Element(this.options.elements.alert, this.options.elementOptions.alert); + + if(typeOf(msg) == 'string') + { + alert.set('html', msg); + } + else if(typeOf(msg) == 'element') + { + alert.grab(msg); + } + else if(typeOf(msg) == 'array') + { + var alerts = []; + msg.each(function(m){ + alerts.push(this.alert(m, options)); + }, this); + return alerts; + } + + alert.store('options', options); + + if(options.buttons.length > 0) + { + options.clickDismiss = false; + options.hideAfter = false; + options.hoverWait = false; + var buttonWrapper = new Element(this.options.elements.buttonWrapper, this.options.elementOptions.buttonWrapper); + alert.grab(buttonWrapper); + options.buttons.each(function(button){ + if(button.text != undefined) + { + var callbackButton = new Element(this.options.elements.button, this.options.elementOptions.button); + callbackButton.set('html', button.text); + if(button.callback != undefined) + { + callbackButton.addEvent('click', button.callback.pass(alert)); + } + if(button.dismiss != undefined && button.dismiss) + { + callbackButton.addEvent('click', this.dismiss.pass(alert, this)); + } + buttonWrapper.grab(callbackButton); + } + }, this); + } + if(options.className != undefined) + { + alert.addClass(options.className); + } + + this.wrapper.grab(alert, (this.options.mode == 'top') ? 'bottom' : 'top'); + + var fx = Object.merge(this.options.alert.fx, options.fx); + var alertFx = new Fx.Morph(alert, fx); + alert.store('fx', alertFx); + this.fadeIn(alert); + + if(options.highlight) + { + alertFx.addEvent('complete', function(){ + alert.highlight(options.highlight.start, options.highlight.end); + if(options.highlightRepeat) + { + alert.highlight.periodical(options.highlightRepeat, alert, [options.highlight.start, options.highlight.end]); + } + }); + } + if(options.hideAfter) + { + this.dismiss(alert); + } + + if(options.clickDismiss) + { + alert.addEvent('click', function(){ + this.holdUp = false; + this.dismiss(alert, true); + }.bind(this)); + } + + if(options.hoverWait) + { + alert.addEvents({ + 'mouseenter': function(){ + this.holdUp = true; + }.bind(this), + 'mouseleave': function(){ + this.holdUp = false; + }.bind(this) + }); + } + + return alert; + }, + + 'fadeIn': function(alert){ + var alertFx = alert.retrieve('fx'); + alertFx.set({ + 'opacity': 0 + }); + alertFx.start({ + 'opacity': [this.options.elementOptions.alert.styles.opacity, .9].pick(), + }); + }, + + 'dismiss': function(alert, now){ + now = now || false; + var options = alert.retrieve('options'); + if(now) + { + this.fadeOut(alert); + } + else + { + this.fadeOut.delay(options.hideAfter, this, alert); + } + }, + + 'fadeOut': function(alert){ + if(this.holdUp) + { + this.dismiss.delay(100, this, [alert, true]) + return null; + } + var alertFx = alert.retrieve('fx'); + if(!alertFx) + { + return null; + } + var to = { + 'opacity': 0 + } + if(this.options.mode == 'top') + { + to['margin-top'] = '-'+alert.offsetHeight+'px'; + } + else + { + to['margin-bottom'] = '-'+alert.offsetHeight+'px'; + } + alertFx.start(to); + alertFx.addEvent('complete', function(){ + alert.destroy(); + }); + } +}); + +Element.implement({ + + 'alert': function(msg, options){ + var alert = this.retrieve('alert'); + if(!alert) + { + options = options || { + 'mode':'top' + }; + alert = new Purr(options) + this.store('alert', alert); + } + + var coords = this.getCoordinates(); + + alert.alert(msg, options); + + alert.wrapper.setStyles({ + 'bottom': '', + 'left': (coords.left - (alert.wrapper.getWidth() / 2)) + (this.getWidth() / 2), + 'top': coords.top - (alert.wrapper.getHeight()), + 'position': 'absolute' + }); + + } + +});
\ No newline at end of file diff --git a/pyload/webui/themes/Dark/lib/MooTools/TinyTab/tinytab.js b/pyload/webui/themes/Dark/lib/MooTools/TinyTab/tinytab.js new file mode 100644 index 000000000..de50279fc --- /dev/null +++ b/pyload/webui/themes/Dark/lib/MooTools/TinyTab/tinytab.js @@ -0,0 +1,43 @@ +/* +--- +description: TinyTab - Tiny and simple tab handler for Mootools. + +license: MIT-style + +authors: +- Danillo César de O. Melo + +requires: +- core/1.2.4: '*' + +provides: TinyTab + +... +*/ +(function($) { + this.TinyTab = new Class({ + Implements: Events, + initialize: function(tabs, contents, opt) { + this.tabs = tabs; + this.contents = contents; + if(!opt) opt = {}; + this.css = opt.selectedClass || 'selected'; + this.select(this.tabs[0]); + tabs.each(function(el){ + el.addEvent('click',function(e){ + this.select(el); + e.stop(); + }.bind(this)); + }.bind(this)); + }, + + select: function(el) { + this.tabs.removeClass(this.css); + el.addClass(this.css); + this.contents.setStyle('display','none'); + var content = this.contents[this.tabs.indexOf(el)]; + content.setStyle('display','block'); + this.fireEvent('change',[content,el]); + } + }); +})(document.id);
\ No newline at end of file diff --git a/pyload/webui/themes/Dark/tml/admin.html b/pyload/webui/themes/Dark/tml/admin.html new file mode 100644 index 000000000..c5cdb494b --- /dev/null +++ b/pyload/webui/themes/Dark/tml/admin.html @@ -0,0 +1,98 @@ +{% extends '/tml/base.html' %} + +{% block head %} + <script type="text/javascript" src="/js/admin.min.js"></script> +{% endblock %} + + +{% block title %}{{ _("Administrate") }} - {{ super() }} {% endblock %} +{% block subtitle %}{{ _("Administrate") }}{% endblock %} + +{% block content %} + + <a href="#" id="quit-pyload" style="font-size: large; font-weight: bold;">{{_("Quit pyLoad")}}</a> | + <a href="#" id="restart-pyload" style="font-size: large; font-weight: bold;">{{_("Restart pyLoad")}}</a> + <br> + <br> + + {{ _("To add user or change passwords use:") }} <b>python pyload.py -u</b><br> + {{ _("Important: Admin user have always all permissions!") }} + + <form action="" method="POST"> + <table class="settable wide"> + <thead style="font-size: 11px"> + <th> + {{ _("Name") }} + </th> + <th> + {{ _("Change Password") }} + </th> + <th> + {{ _("Admin") }} + </th> + <th> + {{ _("Permissions") }} + </th> + </thead> + + {% for name, data in users.iteritems() %} + <tr> + <td>{{ name }}</td> + <td><a class="change_password" href="#" id="change_pw|{{name}}">{{ _("change") }}</a></td> + <td><input name="{{ name }}|admin" type="checkbox" {% if data.perms.admin %} + checked="True" {% endif %}"></td> + <td> + <select multiple="multiple" size="{{ permlist|length }}" name="{{ name }}|perms"> + {% for perm in permlist %} + {% if data.perms|getitem(perm) %} + <option selected="selected">{{ perm }}</option> + {% else %} + <option>{{ perm }}</option> + {% endif %} + {% endfor %} + </select> + </td> + </tr> + {% endfor %} + + + </table> + + <button class="styled_button" type="submit">{{ _("Submit") }}</button> + </form> +{% endblock %} +{% block hidden %} + <div id="password_box" class="window_box" style="z-index: 2"> + <form id="password_form" action="/json/change_password" method="POST" enctype="multipart/form-data"> + <h1>{{ _("Change Password") }}</h1> + + <p>{{ _("Enter your current and desired Password.") }}</p> + <label for="user_login">{{ _("User") }} + <span class="small">{{ _("Your username.") }}</span> + </label> + <input id="user_login" name="user_login" type="text" size="20"/> + + <label for="login_current_password">{{ _("Current password") }} + <span class="small">{{ _("The password for this account.") }}</span> + </label> + <input id="login_current_password" name="login_current_password" type="password" size="20"/> + + <label for="login_new_password">{{ _("New password") }} + <span class="small">{{ _("The new password.") }}</span> + </label> + <input id="login_new_password" name="login_new_password" type="password" size="20"/> + + <label for="login_new_password2">{{ _("New password (repeat)") }} + <span class="small">{{ _("Please repeat the new password.") }}</span> + </label> + <input id="login_new_password2" name="login_new_password2" type="password" size="20"/> + + + <button id="login_password_button" type="submit">{{ _("Submit") }}</button> + <button id="login_password_reset" style="margin-left: 0" type="reset">{{ _("Reset") }}</button> + <div class="spacer"></div> + + </form> + + </div> +{% endblock %} diff --git a/pyload/webui/themes/Dark/tml/base.html b/pyload/webui/themes/Dark/tml/base.html new file mode 100644 index 000000000..cc2c93db9 --- /dev/null +++ b/pyload/webui/themes/Dark/tml/base.html @@ -0,0 +1,177 @@ +<?xml version="1.0" ?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + +<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> +<link rel="stylesheet" type="text/css" href="/css/base.css"/> +<link rel="stylesheet" type="text/css" href="/css/window.css"/> +<link rel="stylesheet" type="text/css" href="/css/MooDialog.css"/> + +<script type="text/javascript" src="/lib/MooTools/MooTools-Core.js"></script> +<script type="text/javascript" src="/lib/MooTools/MooTools-More.js"></script> +<script type="text/javascript" src="/lib/MooTools/MooDialog/MooDialog.js"></script> +<script type="text/javascript" src="/lib/MooTools/Purr/purr.js"></script> + + +<script type="text/javascript" src="/js/base.min.js"></script> + +<title>{% block title %}pyLoad {{_("Webinterface")}}{% endblock %}</title> + +{% block head %} +{% endblock %} +</head> +<body> +<a class="anchor" name="top" id="top"></a> + +<div id="head-panel"> + + + <div id="head-search-and-login"> + {% block headpanel %} + + {% if user.is_authenticated %} + + +{% if update %} +<span> +<span style="font-weight: bold; margin: 0 2px 0 2px;">{{_("pyLoad Update available!")}}</span> +</span> +{% endif %} + + +{% if plugins %} +<span> +<span style="font-weight: bold; margin: 0 2px 0 2px;">{{_("Plugins updated, please restart!")}}</span> +</span> +{% endif %} + +<span id="cap_info" style="display: {% if captcha %}inline{%else%}none{% endif %}"> +<img src="/img/images.png" alt="Captcha:" style="vertical-align:middle; margin:2px" /> +<span style="font-weight: bold; cursor: pointer; margin-right: 2px;">{{_("Captcha waiting")}}</span> +</span> + + <img src="/img/head-login.png" alt="User:" style="vertical-align:middle; margin:2px" /><span style="padding-right: 2px;">{{user.name}}</span> + <ul id="user-actions"> + <li><a href="/logout" class="action logout" rel="nofollow">{{_("Logout")}}</a></li> + {% if user.is_admin %} + <li><a href="/admin" class="action profile" rel="nofollow">{{_("Administrate")}}</a></li> + {% endif %} + <li><a href="/info" class="action info" rel="nofollow">{{_("Info")}}</a></li> + + </ul> +{% else %} + <span style="padding-right: 2px;">{{_("Please Login!")}}</span> +{% endif %} + + {% endblock %} + </div> + + <a href="/"><img id="head-logo" src="/img/pyload-logo.png" alt="pyLoad" /></a> +{% if user.is_authenticated %} + <div id="head-menu"> + <ul> + + {% macro selected(name, right=False) -%} + {% if name in url -%}class="{% if right -%}right {% endif %}selected"{%- endif %} + {% if not name in url and right -%}class="right"{%- endif %} + {%- endmacro %} + + + {% block menu %} + <li> + <a href="/" title=""><img src="/img/head-menu-home.png" alt="" /> {{_("Home")}}</a> + </li> + <li {{ selected('queue') }}> + <a href="/queue/" title=""><img src="/img/head-menu-queue.png" alt="" /> {{_("Queue")}}</a> + </li> + <li {{ selected('collector') }}> + <a href="/collector/" title=""><img src="/img/head-menu-collector.png" alt="" /> {{_("Collector")}}</a> + </li> + <li {{ selected('downloads') }}> + <a href="/downloads/" title=""><img src="/img/head-menu-development.png" alt="" /> {{_("Downloads")}}</a> + </li> + <li {{ selected('logs', True) }}> + <a href="/logs/" title=""><img src="/img/head-menu-index.png" alt="" />{{_("Logs")}}</a> + </li> + <li {{ selected('settings', True) }}> + <a href="/settings/" title=""><img src="/img/head-menu-config.png" alt="" />{{_("Config")}}</a> + </li> + {% endblock %} + + </ul> + </div>{% endif %} + + <div style="clear:both;"></div> +</div> + +{% if perms.STATUS %} +<ul id="page-actions2"> + <li id="action_play"><a href="#" class="action play" accesskey="o" rel="nofollow">{{_("Start")}}</a></li> + <li id="action_stop"><a href="#" class="action stop" accesskey="o" rel="nofollow">{{_("Stop")}}</a></li> + <li id="action_cancel"><a href="#" class="action cancel" accesskey="o" rel="nofollow">{{_("Cancel")}}</a></li> + <li id="action_add"><a href="#" class="action add" accesskey="o" rel="nofollow" >{{_("Add")}}</a></li> +</ul> +{% endif %} + +{% if perms.LIST %} +<ul id="page-actions"> + <li><span class="time">{{_("Download:")}}</span><a id="time" style=" background-color: {% if status.download %}#8ffc25{% else %} #fc6e26{% endif %}; padding-left: 0cm; padding-right: 0.1cm;color:black; "> {% if status.download %}{{_("on")}}{% else %}{{_("off")}}{% endif %}</a></li> + <li><span class="reconnect">{{_("Reconnect:")}}</span><a id="reconnect" style=" background-color: {% if status.reconnect %}#8ffc25{% else %} #fc6e26{% endif %}; padding-left: 0cm; padding-right: 0.1cm;color:black; "> {% if status.reconnect %}{{_("on")}}{% else %}{{_("off")}}{% endif %}</a></li> + <li><a class="action backlink">{{_("Speed:")}} <b id="speed">{{ status.speed }}</b></a></li> + <li><a class="action cog">{{_("Active:")}} <b id="aktiv" title="{{_("Active")}}">{{ status.active }}</b> / <b id="aktiv_from" title="{{_("Queued")}}">{{ status.queue }}</b> / <b id="aktiv_total" title="{{_("Total")}}">{{ status.total }}</b></a></li> + <li><a href="" class="action revisions" accesskey="o" rel="nofollow">{{_("Reload page")}}</a></li> +</ul> +{% endif %} + +{% block pageactions %} +{% endblock %} +<br/> + +<div id="body-wrapper" class="dokuwiki"> + +<div id="content" lang="en" dir="ltr"> + +<h1>{% block subtitle %}pyLoad - {{_("Webinterface")}}{% endblock %}</h1> + +{% block statusbar %} +{% endblock %} + + +<br/> + +<div class="level1" style="clear:both"> +</div> +<noscript><h1>Enable JavaScript to use the webinterface.</h1></noscript> + +{% for message in messages %} + <b><p>{{message}}</p></b> +{% endfor %} + +<div id="load-indicator" style="opacity: 0; float: right; margin-top: -10px;"> + <img src="/img/ajax-loader.gif" alt="" style="padding-right: 5px"/> + {{_("loading")}} +</div> + +{% block content %} +{% endblock content %} + + <hr style="clear: both;" /> + +<div id="foot">© 2008-2015 pyLoad Team +<a href="#top" class="action top" accesskey="x"><span>{{_("Back to top")}}</span></a><br /> +<!--<div class="breadcrumbs"></div>--> + +</div> +</div> +</div> + +<div id="window_popup" style="display: none;"> + {% include '/tml/window.html' %} + {% include '/tml/captcha.html' %} + {% block hidden %} + {% endblock %} +</div> +</body> +</html> diff --git a/pyload/webui/themes/Dark/tml/captcha.html b/pyload/webui/themes/Dark/tml/captcha.html new file mode 100644 index 000000000..3bfa6cbcf --- /dev/null +++ b/pyload/webui/themes/Dark/tml/captcha.html @@ -0,0 +1,42 @@ +<!-- Captcha box --> +<div id="cap_box" class="window_box"> + + <form id="cap_form" action="/json/set_captcha" method="POST" enctype="multipart/form-data" onsubmit="return false;"> + + <h1>{{_("Captcha reading")}}</h1> + <p id="cap_title">{{_("Please read the text on the captcha.")}}</p> + + <div id="cap_textual"> + + <input id="cap_id" name="cap_id" type="hidden" value="" /> + + <label>{{_("Captcha")}} + <span class="small">{{_("The captcha.")}}</span> + </label> + <span class="cont"> + <img id="cap_textual_img" src=""> + </span> + + <label>{{_("Text")}} + <span class="small">{{_("Input the text on the captcha.")}}</span> + </label> + <input id="cap_result" name="cap_result" type="text" size="20" /> + + </div> + + <div id="cap_positional" style="text-align: center"> + <img id="cap_positional_img" src="" style="margin: 10px; cursor:pointer"> + </div> + + <div id="button_bar" style="text-align: center"> + <span> + <button id="cap_submit" type="submit" style="margin-left: 0">{{_("Submit")}}</button> + <button id="cap_reset" type="reset" style="margin-left: 0">{{_("Close")}}</button> + </span> + </div> + + <div class="spacer"></div> + + </form> + +</div> diff --git a/pyload/webui/themes/Dark/tml/downloads.html b/pyload/webui/themes/Dark/tml/downloads.html new file mode 100644 index 000000000..f6e581c5b --- /dev/null +++ b/pyload/webui/themes/Dark/tml/downloads.html @@ -0,0 +1,29 @@ +{% extends '/tml/base.html' %} + +{% block title %}Downloads - {{super()}} {% endblock %} + +{% block subtitle %} +{{_("Downloads")}} +{% endblock %} + +{% block content %} + +<ul> + {% for folder in files.folder %} + <li> + {{ folder.name }} + <ul> + {% for file in folder.files %} + <li><a href='get/{{ folder.path|escape }}/{{ file|escape }}'>{{file}}</a></li> + {% endfor %} + </ul> + </li> + {% endfor %} + + {% for file in files.files %} + <li> <a href='get/{{ file|escape }}'>{{ file }}</a></li> + {% endfor %} + +</ul> + +{% endblock %} diff --git a/pyload/webui/themes/Dark/tml/folder.html b/pyload/webui/themes/Dark/tml/folder.html new file mode 100644 index 000000000..21f3f4a03 --- /dev/null +++ b/pyload/webui/themes/Dark/tml/folder.html @@ -0,0 +1,15 @@ +<li class="folder"> + <input type="hidden" name="path" class="path" value="{{ path }}" /> + <input type="hidden" name="name" class="name" value="{{ name }}" /> + <span> + <b>{{ name }}</b> + <span class="buttons" style="opacity:0"> + <img title="{{_("Rename Directory")}}" class="rename" style="cursor: pointer" height="12px" src="/img/pencil.png" /> + + <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/delete.png" /> + + <img title="{{_("Add subdirectory")}}" class="mkdir" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/add_folder.png" /> + </span> + </span> + <div style="display:none">{{ _("Folder is empty") }}</div> +</li> diff --git a/pyload/webui/themes/Dark/tml/home.html b/pyload/webui/themes/Dark/tml/home.html new file mode 100644 index 000000000..6ce60de0b --- /dev/null +++ b/pyload/webui/themes/Dark/tml/home.html @@ -0,0 +1,263 @@ +{% extends '/tml/base.html' %} +{% block head %} + +<script type="text/javascript"> + +var em; +var operafix = (navigator.userAgent.toLowerCase().search("opera") >= 0); + +document.addEvent("domready", function(){ + em = new EntryManager(); +}); + +var EntryManager = new Class({ + initialize: function(){ + this.json = new Request.JSON({ + url: "json/links", + secure: false, + async: true, + onSuccess: this.update.bind(this), + initialDelay: 0, + delay: 2500, + limit: 30000 + }); + + this.ids = [{% for link in content %} + {% if forloop.last %} + {{ link.id }} + {% else %} + {{ link.id }}, + {% endif %} + {% endfor %}]; + + this.entries = []; + this.container = $('LinksAktiv'); + + this.parseFromContent(); + + this.json.startTimer(); + }, + parseFromContent: function(){ + this.ids.each(function(id,index){ + var entry = new LinkEntry(id); + entry.parse(); + this.entries.push(entry) + }, this); + }, + update: function(data){ + + try{ + this.ids = this.entries.map(function(item){ + return item.fid + }); + + this.ids.filter(function(id){ + return !this.ids.contains(id) + },data).each(function(id){ + var index = this.ids.indexOf(id); + this.entries[index].remove(); + this.entries = this.entries.filter(function(item){return item.fid != this},id); + this.ids = this.ids.erase(id) + }, this); + + data.links.each(function(link, i){ + if (this.ids.contains(link.fid)){ + + var index = this.ids.indexOf(link.fid); + this.entries[index].update(link) + + }else{ + var entry = new LinkEntry(link.fid); + entry.insert(link); + this.entries.push(entry); + this.ids.push(link.fid); + this.container.adopt(entry.elements.tr,entry.elements.pgbTr); + entry.fade.start('opacity', 1); + entry.fadeBar.start('opacity', 1); + + } + }, this) + }catch(e){ + //alert(e) + } + } +}); + + +var LinkEntry = new Class({ + initialize: function(id){ + this.fid = id; + this.id = id; + }, + parse: function(){ + this.elements = { + tr: $("link_{id}".substitute({id: this.id})), + name: $("link_{id}_name".substitute({id: this.id})), + status: $("link_{id}_status".substitute({id: this.id})), + info: $("link_{id}_info".substitute({id: this.id})), + bleft: $("link_{id}_bleft".substitute({id: this.id})), + percent: $("link_{id}_percent".substitute({id: this.id})), + remove: $("link_{id}_remove".substitute({id: this.id})), + pgbTr: $("link_{id}_pgb_tr".substitute({id: this.id})), + pgb: $("link_{id}_pgb".substitute({id: this.id})) + }; + this.initEffects(); + }, + insert: function(item){ + try{ + + this.elements = { + tr: new Element('tr', { + 'html': '', + 'styles':{ + 'opacity': 0 + } + }), + name: new Element('td', { + 'html': item.name + }), + status: new Element('td', { + 'html': item.statusmsg + }), + info: new Element('td', { + 'html': item.info + }), + bleft: new Element('td', { + 'html': humanFileSize(item.size) + }), + percent: new Element('span', { + 'html': item.percent+ '% / '+ humanFileSize(item.size-item.bleft) + }), + remove: new Element('img',{ + 'src': '/img/control_cancel.png', + 'styles':{ + 'vertical-align': 'middle', + 'margin-right': '-20px', + 'margin-left': '5px', + 'margin-top': '-2px', + 'cursor': 'pointer' + } + }), + pgbTr: new Element('tr', { + 'html':'' + }), + pgb: new Element('div', { + 'html': ' ', + 'styles':{ + 'height': '4px', + 'width': item.percent+'%', + 'background-color': '#ddd' + } + }) + }; + + this.elements.tr.adopt(this.elements.name,this.elements.status,this.elements.info,this.elements.bleft,new Element('td').adopt(this.elements.percent,this.elements.remove)); + this.elements.pgbTr.adopt(new Element('td',{'colspan':5}).adopt(this.elements.pgb)); + this.initEffects(); + }catch(e){ + alert(e) + } + }, + initEffects: function(){ + if(!operafix) + this.bar = new Fx.Morph(this.elements.pgb, {unit: '%', duration: 5000, link: 'link', fps:30}); + this.fade = new Fx.Tween(this.elements.tr); + this.fadeBar = new Fx.Tween(this.elements.pgbTr); + + this.elements.remove.addEvent('click', function(){ + new Request({method: 'get', url: '/json/abort_link/'+this.id}).send(); + }.bind(this)); + + }, + update: function(item){ + this.elements.name.set('text', item.name); + this.elements.status.set('text', item.statusmsg); + this.elements.info.set('text', item.info); + this.elements.bleft.set('text', item.format_size); + this.elements.percent.set('text', item.percent+ '% / '+ humanFileSize(item.size-item.bleft)); + if(!operafix) + { + this.bar.start({ + 'width': item.percent, + 'background-color': [Math.round(120/100*item.percent),100,100].hsbToRgb().rgbToHex() + }); + } + else + { + this.elements.pgb.set( + 'styles', { + 'height': '4px', + 'width': item.percent+'%', + 'background-color': [Math.round(120/100*item.percent),100,100].hsbToRgb().rgbToHex(), + }); + } + }, + remove: function(){ + this.fade.start('opacity',0).chain(function(){this.elements.tr.dispose();}.bind(this)); + this.fadeBar.start('opacity',0).chain(function(){this.elements.pgbTr.dispose();}.bind(this)); + + } + }); +</script> + +{% endblock %} + +{% block subtitle %} +{{_("Active Downloads")}} +{% endblock %} + +{% block menu %} +<li class="selected"> + <a href="/" title=""><img src="/img/head-menu-home.png" alt="" /> {{_("Home")}}</a> +</li> +<li> + <a href="/queue/" title=""><img src="/img/head-menu-queue.png" alt="" /> {{_("Queue")}}</a> +</li> +<li> + <a href="/collector/" title=""><img src="/img/head-menu-collector.png" alt="" /> {{_("Collector")}}</a> +</li> +<li> + <a href="/downloads/" title=""><img src="/img/head-menu-development.png" alt="" /> {{_("Downloads")}}</a> +</li> +<li class="right"> + <a href="/logs/" title=""><img src="/img/head-menu-index.png" alt="" />{{_("Logs")}}</a> +</li> +<li class="right"> + <a href="/settings/" title=""><img src="/img/head-menu-config.png" alt="" />{{_("Config")}}</a> +</li> +{% endblock %} + +{% block content %} +<table width="100%" class="queue"> + <thead> + <tr class="header"> + <th>{{_("Name")}}</th> + <th>{{_("Status")}}</th> + <th>{{_("Information")}}</th> + <th>{{_("Size")}}</th> + <th>{{_("Progress")}}</th> + </tr> + </thead> + <tbody id="LinksAktiv"> + + {% for link in content %} + <tr id="link_{{ link.id }}"> + <td id="link_{{ link.id }}_name">{{ link.name }}</td> + <td id="link_{{ link.id }}_status">{{ link.status }}</td> + <td id="link_{{ link.id }}_info">{{ link.info }}</td> + <td id="link_{{ link.id }}_bleft">{{ link.format_size }}</td> + <td> + <span id="link_{{ link.id }}_percent">{{ link.percent }}% /{{ link.bleft }}</span> + <img id="link_{{ link.id }}_remove" style="vertical-align: middle; margin-right: -20px; margin-left: 5px; margin-top: -2px; cursor:pointer;" src="/img/control_cancel.png"/> + </td> + </tr> + <tr id="link_{{ link.id }}_pgb_tr"> + <td colspan="5"> + <div id="link_{{ link.id }}_pgb" class="progressBar" style="background-color: green; height:4px; width: {{ link.percent }}%;"> </div> + </td> + </tr> + {% endfor %} + + </tbody> +</table> +{% endblock %} diff --git a/pyload/webui/themes/Dark/tml/info.html b/pyload/webui/themes/Dark/tml/info.html new file mode 100644 index 000000000..62a8df571 --- /dev/null +++ b/pyload/webui/themes/Dark/tml/info.html @@ -0,0 +1,76 @@ +{% extends '/tml/base.html' %} + +{% block head %} +{% endblock %} + +{% block title %}{{ _("Information") }} - {{ super() }} {% endblock %} +{% block subtitle %}{{ _("Information") }}{% endblock %} + +{% block content %} + <h3>{{ _("News") }}</h3> + + <ul id="twitter_update_list"></ul> + <script type="text/javascript" src="http://twitter.com/javascripts/blogger.min.js"></script> + <script type="text/javascript" src="http://api.twitter.com/1/statuses/user_timeline.json?screen_name=pyLoad&include_rts=true&count=5&callback=twitterCallback2"></script> + + <h3>{{ _("Support") }}</h3> + + <ul> + <li style="font-weight:bold;"> + <a href="http://pyload.org/wiki" target="_blank">Wiki</a> + | + <a href="http://forum.pyload.org/" target="_blank">Forum</a> + | + <a href="http://pyload.org/irc/" target="_blank">Chat</a> + </li> + <li style="font-weight:bold;"><a href="http://docs.pyload.org" target="_blank">Documentation</a></li> + <li style="font-weight:bold;"><a href="https://bitbucket.org/spoob/pyload/overview" target="_blank">Development</a></li> + <li style="font-weight:bold;"><a href="https://bitbucket.org/spoob/pyload/issues?status=new&status=open" target="_blank">Issue Tracker</a></li> + + </ul> + + <h3>{{ _("System") }}</h3> + <table class="system"> + <tr> + <td>{{ _("Python:") }}</td> + <td>{{ python }}</td> + </tr> + <tr> + <td>{{ _("OS:") }}</td> + <td>{{ os }}</td> + </tr> + <tr> + <td>{{ _("pyLoad version:") }}</td> + <td>{{ version }}</td> + </tr> + <tr> + <td>{{ _("Installation Folder:") }}</td> + <td>{{ folder }}</td> + </tr> + <tr> + <td>{{ _("Config Folder:") }}</td> + <td>{{ config }}</td> + </tr> + <tr> + <td>{{ _("Download Folder:") }}</td> + <td>{{ download }}</td> + </tr> + <tr> + <td>{{ _("Free Space:") }}</td> + <td>{{ freespace }}</td> + </tr> + <tr> + <td>{{ _("Language:") }}</td> + <td>{{ language }}</td> + </tr> + <tr> + <td>{{ _("Webinterface Port:") }}</td> + <td>{{ webif }}</td> + </tr> + <tr> + <td>{{ _("Remote Interface Port:") }}</td> + <td>{{ remote }}</td> + </tr> + </table> + +{% endblock %} diff --git a/pyload/webui/themes/Dark/tml/login.html b/pyload/webui/themes/Dark/tml/login.html new file mode 100644 index 000000000..004bc506b --- /dev/null +++ b/pyload/webui/themes/Dark/tml/login.html @@ -0,0 +1,37 @@ +{% extends '/tml/base.html' %} + +{% block title %}{{_("Login")}} - {{super()}} {% endblock %} + +{% block content %} + +<div class="centeralign"> +<form action="" method="post" accept-charset="utf-8" id="login"> + <div class="no"> + <input type="hidden" name="do" value="login" /> + <fieldset> + <legend>Login</legend> +{% if errors %} +<p style="color:red;">{{_("Your username and password didn't match. Please try again.")}}</p> +{% endif %} + <table id="login_table"> + <tr> + <td>{{_("Username")}}</td> + <td><input type="text" size="20" name="username" /></td> + </tr> + <tr> + <td>{{_("Password")}}</td> + <td><input type="password" size="20" name="password" /></td> + </tr> +<tr> +<td> </td> + <td><input type="submit" value="Login" class="button" /></td> +</tr> +</table> + </fieldset> + </div> +</form> + +</div> +<br> + +{% endblock %} diff --git a/pyload/webui/themes/Dark/tml/logout.html b/pyload/webui/themes/Dark/tml/logout.html new file mode 100644 index 000000000..db7e9290e --- /dev/null +++ b/pyload/webui/themes/Dark/tml/logout.html @@ -0,0 +1,9 @@ +{% extends '/tml/base.html' %} + +{% block head %} +<meta http-equiv="refresh" content="3; url=/"> +{% endblock %} + +{% block content %} +<p><b>{{_("You were successfully logged out.")}}</b></p> +{% endblock %} diff --git a/pyload/webui/themes/Dark/tml/logs.html b/pyload/webui/themes/Dark/tml/logs.html new file mode 100644 index 000000000..429306aae --- /dev/null +++ b/pyload/webui/themes/Dark/tml/logs.html @@ -0,0 +1,41 @@ +{% extends '/tml/base.html' %} + +{% block title %}{{_("Logs")}} - {{super()}} {% endblock %} +{% block subtitle %}{{_("Logs")}}{% endblock %} +{% block head %} +<link rel="stylesheet" type="text/css" href="/css/log.css"/> +{% endblock %} + +{% block content %} +<div style="clear: both;"></div> + +<div class="logpaginator"><a href="{{ "/logs/1" }}"><< {{_("Start")}}</a> <a href="{{ "/logs/" + iprev|string }}">< {{_("prev")}}</a> <a href="{{ "/logs/" + inext|string }}">{{_("next")}} ></a> <a href="/logs/">{{_("End")}} >></a></div> +<div class="logperpage"> + <form id="logform1" action="" method="POST"> + <label for="reversed">Reversed:</label> + <input type="checkbox" name="reversed" onchange="this.form.submit();" {% if reversed %} checked="checked" {% endif %} /> + <label for="perpage">Lines per page:</label> + <select name="perpage" onchange="this.form.submit();"> + {% for value in perpage_p %} + <option value="{{value.0}}"{% if value.0 == perpage %} selected="selected" {% endif %}>{{value.1}}</option> + {% endfor %} + </select> + </form> +</div> +<div class="logwarn">{{warning}}</div> +<div style="clear: both;"></div> +<div class="logdiv"> + <table class="logtable" cellpadding="0" cellspacing="0"> + {% for line in log %} + <tr><td class="logline">{{line.line}}</td><td>{{line.date}}</td><td class="loglevel">{{line.level}}</td><td>{{line.message}}</td></tr> + {% endfor %} + </table> +</div> +<div class="logform"> +<form id="logform2" action="" method="POST"> + <label for="from">Jump to time:</label><input type="text" name="from" size="15" value="{{from}}"/> + <input type="submit" value="ok" /> +</form> +</div> +<div style="clear: both; height: 10px;"> </div> +{% endblock %} diff --git a/pyload/webui/themes/Dark/tml/pathchooser.html b/pyload/webui/themes/Dark/tml/pathchooser.html new file mode 100644 index 000000000..6e58ab536 --- /dev/null +++ b/pyload/webui/themes/Dark/tml/pathchooser.html @@ -0,0 +1,76 @@ +<html> +<head> + <script class="javascript"> + function chosen() + { + opener.ifield.value = document.forms[0].p.value; + close(); + } + function exit() + { + close(); + } + function setInvalid() { + document.forms[0].send.disabled = 'disabled'; + document.forms[0].p.style.color = '#FF0000'; + } + function setValid() { + document.forms[0].send.disabled = ''; + document.forms[0].p.style.color = '#000000'; + } + function setFile(file) + { + document.forms[0].p.value = file; + setValid(); + + } + </script> + <link rel="stylesheet" type="text/css" href="/css/pathchooser.css"/> +</head> +<body{% if type == 'file' %}{% if not oldfile %} onload="setInvalid();"{% endif %}{% endif %}> +<center> + <div id="paths"> + <form method="get" action="?" onSubmit="chosen();" onReset="exit();"> + <input type="text" name="p" value="{{ oldfile|default(cwd) }}" size="60" onfocus="setValid();"> + <input type="submit" value="Ok" name="send"> + </form> + + {% if type == 'folder' %} + <span class="path_abs_rel">{{_("Path")}}: <a href="{{ "/pathchooser" + cwd|path_make_absolute|quotepath }}"{% if absolute %} style="text-decoration: underline;"{% endif %}>{{_("absolute")}}</a> | <a href="{{ "/pathchooser/" + cwd|path_make_relative|quotepath }}"{% if not absolute %} style="text-decoration: underline;"{% endif %}>{{_("relative")}}</a></span> + {% else %} + <span class="path_abs_rel">{{_("Path")}}: <a href="{{ "/filechooser/" + cwd|path_make_absolute|quotepath }}"{% if absolute %} style="text-decoration: underline;"{% endif %}>{{_("absolute")}}</a> | <a href="{{ "/filechooser/" + cwd|path_make_relative|quotepath }}"{% if not absolute %} style="text-decoration: underline;"{% endif %}>{{_("relative")}}</a></span> + {% endif %} + </div> + <table border="0" cellspacing="0" cellpadding="3"> + <tr> + <th>{{_("name")}}</th> + <th>{{_("size")}}</th> + <th>{{_("type")}}</th> + <th>{{_("last modified")}}</th> + </tr> + {% if parentdir %} + <tr> + <td colspan="4"> + <a href="{% if type == 'folder' %}{{ "/pathchooser/" + parentdir|quotepath }}{% else %}{{ "/filechooser/" + parentdir|quotepath }}{% endif %}"><span class="parentdir">{{_("parent directory")}}</span></a> + </td> + </tr> + {% endif %} +{% for file in files %} + <tr> + {% if type == 'folder' %} + <td class="name">{% if file.type == 'dir' %}<a href="{{ "/pathchooser/" + file.fullpath|quotepath }}" title="{{ file.fullpath }}"><span class="path_directory">{{ file.name|truncate(25) }}</span></a>{% else %}<span class="path_file" title="{{ file.fullpath }}">{{ file.name|truncate(25) }}{% endif %}</span></td> + {% else %} + <td class="name">{% if file.type == 'dir' %}<a href="{{ "/filechooser/" + file.fullpath|quotepath }}" title="{{ file.fullpath }}"><span class="file_directory">{{ file.name|truncate(25) }}</span></a>{% else %}<a href="#" onclick="setFile('{{ file.fullpath }}');" title="{{ file.fullpath }}"><span class="file_file">{{ file.name|truncate(25) }}{% endif %}</span></a></td> + {% endif %} + <td class="size">{{ file.size|float|filesizeformat }}</td> + <td class="type">{% if file.type == 'dir' %}directory{% else %}{{ file.ext|default("file") }}{% endif %}</td> + <td class="mtime">{{ file.modified|date("d.m.Y - H:i:s") }}</td> + <tr> +<!-- <tr> + <td colspan="4">{{_("no content")}}</td> + </tr> --> +{% endfor %} + </table> + </center> +</body> +</html> diff --git a/pyload/webui/themes/Dark/tml/queue.html b/pyload/webui/themes/Dark/tml/queue.html new file mode 100644 index 000000000..6e7cb6754 --- /dev/null +++ b/pyload/webui/themes/Dark/tml/queue.html @@ -0,0 +1,104 @@ +{% extends '/tml/base.html' %} +{% block head %} + +<script type="text/javascript" src="/js/package.js"></script> + +<script type="text/javascript"> + +document.addEvent("domready", function(){ + var pUI = new PackageUI("url", {{ target }}); +}); +</script> +{% endblock %} + +{% if target %} + {% set name = _("Queue") %} +{% else %} + {% set name = _("Collector") %} +{% endif %} + +{% block title %}{{name}} - {{super()}} {% endblock %} +{% block subtitle %}{{name}}{% endblock %} + +{% block pageactions %} +<ul id="page-actions-more"> + <li id="del_finished"><a style="padding: 0; font-weight: bold;" href="#">{{_("Delete Finished")}}</a></li> + <li id="restart_failed"><a style="padding: 0; font-weight: bold;" href="#">{{_("Restart Failed")}}</a></li> +</ul> +{% endblock %} + +{% block content %} +{% autoescape true %} + +<ul id="package-list" style="list-style: none; padding-left: 0; margin-top: -10px;"> +{% for package in content %} + <li> +<div id="package_{{package.pid}}" class="package"> + <div class="order" style="display: none;">{{ package.order }}</div> + + <div class="packagename" style="cursor: pointer"> + <img class="package_drag" src="/img/folder.png" style="cursor: move; margin-bottom: -2px"> + <span class="name">{{package.name}}</span> + + <span class="buttons" style="opacity:0"> + <img title="{{_("Delete Package")}}" style="cursor: pointer" width="12px" height="12px" src="/img/delete.png" /> + + <img title="{{_("Restart Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/img/arrow_refresh.png" /> + + <img title="{{_("Edit Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/img/pencil.png" /> + + <img title="{{_("Move Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/img/package_go.png" /> + </span> + </div> + {% set progress = (package.linksdone * 100) / package.linkstotal %} + + <div id="progress" style="border-radius: 4px; border: 1px solid grey; width: 50%; height: 1em"> + <div style="width: {{ progress }}%; height: 100%; background-color: #525252;"></div> + <label style="font-size: 0.8em; font-weight: bold; padding-left: 5px; position: relative; top: -17px"> + {{ package.sizedone|formatsize }} / {{ package.sizetotal|formatsize }}</label> + <label style="font-size: 0.8em; font-weight: bold; padding-right: 5px ;float: right; position: relative; top: -17px"> + {{ package.linksdone }} / {{ package.linkstotal }}</label> + </div> + <div style="clear: both; margin-bottom: -10px"></div> + + <div id="children_{{package.pid}}" style="display: none;" class="children"> + <span class="child_secrow">{{_("Folder:")}} <span class="folder">{{package.folder}}</span> | {{_("Password:")}} <span class="password">{{package.password}}</span></span> + <ul id="sort_children_{{package.pid}}" style="list-style: none; padding-left: 0"> + </ul> + </div> +</div> + </li> +{% endfor %} +</ul> +{% endautoescape %} +{% endblock %} + +{% block hidden %} +<div id="pack_box" class="window_box" style="z-index: 2"> + <form id="pack_form" action="/json/edit_package" method="POST" enctype="multipart/form-data"> + <h1>{{_("Edit Package")}}</h1> + <p>{{_("Edit the package detais below.")}}</p> + <input name="pack_id" id="pack_id" type="hidden" value=""/> + <label for="pack_name">{{_("Name")}} + <span class="small">{{_("The name of the package.")}}</span> + </label> + <input id="pack_name" name="pack_name" type="text" size="20" /> + + <label for="pack_folder">{{_("Folder")}} + <span class="small">{{_("Name of subfolder for these downloads.")}}</span> + </label> + <input id="pack_folder" name="pack_folder" type="text" size="20" /> + + <label for="pack_pws">{{_("Password")}} + <span class="small">{{_("List of passwords used for unrar.")}}</span> + </label> + <textarea rows="3" name="pack_pws" id="pack_pws"></textarea> + + <button type="submit">{{_("Submit")}}</button> + <button id="pack_reset" style="margin-left: 0" type="reset" >{{_("Reset")}}</button> + <div class="spacer"></div> + + </form> + +</div> +{% endblock %} diff --git a/pyload/webui/themes/Dark/tml/settings.html b/pyload/webui/themes/Dark/tml/settings.html new file mode 100644 index 000000000..7df0b2f23 --- /dev/null +++ b/pyload/webui/themes/Dark/tml/settings.html @@ -0,0 +1,204 @@ +{% extends '/tml/base.html' %} + +{% block title %}{{ _("Config") }} - {{ super() }} {% endblock %} +{% block subtitle %}{{ _("Config") }}{% endblock %} + +{% block head %} + <script type="text/javascript" src="/lib/MooTools/TinyTab/tinytab.js"></script> + <script type="text/javascript" src="/lib/MooTools/MooDropMenu/MooDropMenu.js"></script> + <script type="text/javascript" src="/js/settings.min.js"></script> + +{% endblock %} + +{% block content %} + + <ul id="toptabs" class="tabs"> + <li><a class="selected" href="#">{{ _("General") }}</a></li> + <li><a href="#">{{ _("Plugins") }}</a></li> + <li><a href="#">{{ _("Accounts") }}</a></li> + </ul> + + <div id="tabsback" style="height: 20px; padding-left: 150px; color: white; font-weight: bold;"> + + </div> + + <span id="tabs-body"> + <!-- General --> + <span id="general" class="active tabContent"> + <ul class="nav tabs"> + <li class> + <a>Menu</a> + <ul id="general-menu"> + {% for entry,name in conf.general %} + <nobr> + <li style="color:white;" id="general|{{ entry }}">{{ name }}</li> + </nobr> + <br> + {% endfor %} + </ul> + </li> + </ul> + + <form id="general_form" action="" method="POST" autocomplete="off"> + <span id="general_form_content"> + <br> + <h3> {{ _("Choose a section from the menu") }}</h3> + <br> + </span> + + <input id="general|submit" class="styled_button" type="submit" value="{{_("Submit")}}"/> + </form> + </span> + + <!-- Plugins --> + <span id="plugins" class="tabContent"> + <ul class="nav tabs"> + <li class> + <a>Menu</a> + <ul id="plugin-menu"> + {% for entry,name in conf.plugin %} + <nobr> + <li id="plugin|{{ entry }}">{{ name }}</li> + </nobr> + <br> + {% endfor %} + </ul> + </li> + </ul> + + + <form id="plugin_form" action="" method="POST" autocomplete="off"> + + <span id="plugin_form_content"> + <br> + <h3> {{ _("Choose a section from the menu") }}</h3> + <br> + </span> + <input id="plugin|submit" class="styled_button" type="submit" value="{{_("Submit")}}"/> + </form> + + </span> + + <!-- Accounts --> + <span id="accounts" class="tabContent"> + <form id="account_form" action="/json/update_accounts" method="POST"> + + <table class="settable wide"> + + <thead> + <tr> + <th>{{ _("Plugin") }}</th> + <th>{{ _("Name") }}</th> + <th>{{ _("Password") }}</th> + <th>{{ _("Status") }}</th> + <th>{{ _("Premium") }}</th> + <th>{{ _("Valid until") }}</th> + <th>{{ _("Traffic left") }}</th> + <th>{{ _("Time") }}</th> + <th>{{ _("Max Parallel") }}</th> + <th>{{ _("Delete?") }}</th> + </tr> + </thead> + + + {% for account in conf.accs %} + {% set plugin = account.type %} + <tr> + <td> + <span style="padding:5px">{{ plugin }}</span> + </td> + + <td><label for="{{plugin}}|password;{{account.login}}" + style="color:grey;">{{ account.login }}</label></td> + <td> + <input id="{{plugin}}|password;{{account.login}}" + name="{{plugin}}|password;{{account.login}}" + type="password" value="{{account.password}}" size="12"/> + </td> + <td> + {% if account.valid %} + <span style="font-weight: bold; color: #006400;"> + {{ _("valid") }} + {% else %} + <span style="font-weight: bold; color: #8b0000;"> + {{ _("not valid") }} + {% endif %} + </span> + </td> + <td> + {% if account.premium %} + <span style="font-weight: bold; color: #006400;"> + {{ _("yes") }} + {% else %} + <span style="font-weight: bold; color: #8b0000;"> + {{ _("no") }} + {% endif %} + </span> + </td> + <td> + <span style="font-weight: bold;"> + {{ account.validuntil }} + </span> + </td> + <td> + <span style="font-weight: bold;"> + {{ account.trafficleft }} + </span> + </td> + <td> + <input id="{{plugin}}|time;{{account.login}}" + name="{{plugin}}|time;{{account.login}}" type="text" + size="7" value="{{account.time}}"/> + </td> + <td> + <input id="{{plugin}}|limitdl;{{account.login}}" + name="{{plugin}}|limitdl;{{account.login}}" type="text" + size="2" value="{{account.limitdl}}"/> + </td> + <td> + <input id="{{plugin}}|delete;{{account.login}}" + name="{{plugin}}|delete;{{account.login}}" type="checkbox" + value="True"/> + </td> + </tr> + {% endfor %} + </table> + + <button id="account_submit" type="submit" class="styled_button">{{_("Submit")}}</button> + <button id="account_add" style="margin-left: 0" type="submit" class="styled_button">{{_("Add")}}</button> + </form> + </span> + </span> +{% endblock %} +{% block hidden %} +<div id="account_box" class="window_box" style="z-index: 2"> +<form id="add_account_form" action="/json/add_account" method="POST" enctype="multipart/form-data"> +<h1>{{_("Add Account")}}</h1> +<p>{{_("Enter your account data to use premium features.")}}</p> +<label for="account_login">{{_("Login")}} +<span class="small">{{_("Your username.")}}</span> +</label> +<input id="account_login" name="account_login" type="text" size="20" /> + +<label for="account_password">{{_("Password")}} +<span class="small">{{_("The password for this account.")}}</span> +</label> +<input id="account_password" name="account_password" type="password" size="20" /> + +<label for="account_type">{{_("Type")}} +<span class="small">{{_("Choose the hoster for your account.")}}</span> +</label> + <select name=account_type id="account_type"> + {% for type in types|sort %} + <option value="{{ type }}">{{ type }}</option> + {% endfor %} + </select> + +<button id="account_add_button" type="submit">{{_("Add")}}</button> +<button id="account_reset" style="margin-left: 0" type="reset">{{_("Reset")}}</button> +<div class="spacer"></div> + +</form> + +</div> +{% endblock %} diff --git a/pyload/webui/themes/Dark/tml/settings_item.html b/pyload/webui/themes/Dark/tml/settings_item.html new file mode 100644 index 000000000..c7e60865e --- /dev/null +++ b/pyload/webui/themes/Dark/tml/settings_item.html @@ -0,0 +1,48 @@ +<table class="settable"> + {% if section.outline %} + <tr><th colspan="2">{{ section.outline }}</th></tr> + {% endif %} + {% for okey, option in sorted_conf(section) %} + {% if okey not in ("desc","outline") %} + <tr> + <td><label for="{{skey}}|{{okey}}" + style="color:white;">{{ option.desc }}:</label></td> + <td> + {% if option.type == "bool" %} + <select id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}"> + <option {% if option.value %} selected="selected" + {% endif %}value="True">{{ _("on") }}</option> + <option {% if not option.value %} selected="selected" + {% endif %}value="False">{{ _("off") }}</option> + </select> + {% elif ";" in option.type %} + <select id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}"> + {% for entry in option.list %} + <option {% if option.value == entry %} + selected="selected" {% endif %}>{{ entry }}</option> + {% endfor %} + </select> + {% elif option.type == "folder" %} + <input name="{{skey}}|{{okey}}" type="text" + id="{{skey}}|{{okey}}" value="{{option.value}}"/> + <input name="browsebutton" type="button" + onclick="ifield = document.getElementById('{{skey}}|{{okey}}'); pathchooser = window.open('{% if option.value %}{{ "/pathchooser/" + option.value|quotepath }}{% else %}{{ pathroot }}{% endif %}', 'pathchooser', 'scrollbars=yes,toolbar=no,menubar=no,statusbar=no,width=650,height=300'); pathchooser.ifield = ifield; window.ifield = ifield;" + value="{{_("Browse")}}"/> + {% elif option.type == "file" %} + <input name="{{skey}}|{{okey}}" type="text" + id="{{skey}}|{{okey}}" value="{{option.value}}"/> + <input name="browsebutton" type="button" + onclick="ifield = document.getElementById('{{skey}}|{{okey}}'); filechooser = window.open('{% if option.value %}{{ "/filechooser/" + option.value|quotepath }}{% else %}{{ fileroot }}{% endif %}', 'filechooser', 'scrollbars=yes,toolbar=no,menubar=no,statusbar=no,width=650,height=300'); filechooser.ifield = ifield; window.ifield = ifield;" + value="{{_("Browse")}}"/> + {% elif option.type == "password" %} + <input id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}" + type="password" value="{{option.value}}"/> + {% else %} + <input id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}" + type="text" value="{{option.value}}"/> + {% endif %} + </td> + </tr> + {% endif %} + {% endfor %} +</table> diff --git a/pyload/webui/themes/Dark/tml/window.html b/pyload/webui/themes/Dark/tml/window.html new file mode 100644 index 000000000..8f5efae36 --- /dev/null +++ b/pyload/webui/themes/Dark/tml/window.html @@ -0,0 +1,52 @@ +<iframe id="upload_target" name="upload_target" src="" style="display: none; width:0;height:0"></iframe> + +<div id="add_box" class="window_box"> +<form id="add_form" action="/json/add_package" method="POST" enctype="multipart/form-data"> +<h1>{{_("Add Package")}}</h1> +<p>{{_("Paste your links or upload a container.")}}</p> +<label for="add_name">{{_("Name")}} +<span class="small">{{_("The name of the new package.")}}</span> +</label> +<input id="add_name" name="add_name" type="text" size="20" /> + +<label for="add_links">{{_("Links")}} +<span class="small">{{_("Paste your links here or any text and press the filter button.")}}</span> +<span class="small"> {{ _("Filter urls") }} +<img alt="URIParsing" Title="Parse Uri" src="/img/parseUri.png" style="cursor:pointer; vertical-align: text-bottom;" onclick="parseUri()"/> +</span> + +</label> +<textarea rows="5" name="add_links" id="add_links"></textarea> + +<label for="add_password">{{_("Password")}} + <span class="small">{{_("Password for RAR-Archive")}}</span> +</label> +<input id="add_password" name="add_password" type="text" size="20"> + +<label>{{_("File")}} +<span class="small">{{_("Upload a container.")}}</span> +</label> +<input type="file" name="add_file" id="add_file"/> + +<label for="add_dest">{{_("Destination")}} +</label> +<span class="cont"> +<table class="window_table"> +<tr> + <td>{{_("Queue")}}</td> + <td><input type="radio" name="add_dest" id="add_dest" value="1" checked="checked" /></td> +</tr> +<tr> + <td>{{_("Collector")}}</td> + <td><input type="radio" name="add_dest" id="add_dest2" value="0" /></td> +</tr> +</table> +</span> + +<button type="submit">{{_("Add Package")}}</button> +<button id="add_reset" style="margin-left:0;" type="reset">{{_("Reset")}}</button> +<div class="spacer"></div> + +</form> + +</div> diff --git a/pyload/webui/themes/Default/css/base.css b/pyload/webui/themes/Default/css/base.css new file mode 100644 index 000000000..f5078bfbb --- /dev/null +++ b/pyload/webui/themes/Default/css/base.css @@ -0,0 +1,902 @@ +.hidden { + display:none; +} +.leftalign { + text-align:left; +} +.centeralign { + text-align:center; +} +.rightalign { + text-align:right; +} + + +.dokuwiki div.plugin_translation ul li a.wikilink1:link, .dokuwiki div.plugin_translation ul li a.wikilink1:hover, .dokuwiki div.plugin_translation ul li a.wikilink1:active, .dokuwiki div.plugin_translation ul li a.wikilink1:visited { + background-color:#000080; + color:#fff !important; + text-decoration:none; + padding:0 0.2em; + margin:0.1em 0.2em; + border:none !important; +} +.dokuwiki div.plugin_translation ul li a.wikilink2:link, .dokuwiki div.plugin_translation ul li a.wikilink2:hover, .dokuwiki div.plugin_translation ul li a.wikilink2:active, .dokuwiki div.plugin_translation ul li a.wikilink2:visited { + background-color:#808080; + color:#fff !important; + text-decoration:none; + padding:0 0.2em; + margin:0.1em 0.2em; + border:none !important; +} + +.dokuwiki div.plugin_translation ul li a:hover img { + opacity:1.0; + height:15px; +} + +body { + margin:0; + padding:0; + background-color:white; + color:black; + font-size:12px; + font-family:Verdana, Helvetica, "Lucida Grande", Lucida, Arial, sans-serif; + font-family:sans-serif; + font-size:99, 96%; + font-size-adjust:none; + font-style:normal; + font-variant:normal; + font-weight:normal; + line-height:normal; +} +hr { + border-width:0; + border-bottom:1px #aaa dotted; +} +img { + border:none; +} +form { + margin:0px; + padding:0px; + border:none; + display:inline; + background:transparent; +} +ul li { + margin:5px; +} +textarea { + font-family:monospace; +} +table { + margin:0.5em 0; + border-collapse:collapse; +} +td { + padding:0.25em; + border:1pt solid #ADB9CC; +} +a { + color:#3465a4; + text-decoration:none; +} +a:hover { + text-decoration:underline; +} + +option { + border:0 none #fff; +} +strong.highlight { + background-color:#fc9; + padding:1pt; +} +#pagebottom { + clear:both; +} +hr { + height:1px; + color:#c0c0c0; + background-color:#c0c0c0; + border:none; + margin:.2em 0 .2em 0; +} + +.invisible { + margin:0px; + border:0px; + padding:0px; + height:0px; + visibility:hidden; +} +.left { + float:left !important; +} +.right { + float:right !important; +} +.center { + text-align:center; +} +div#body-wrapper { + padding:40px 40px 10px 40px; + font-size:127%; +} +div#content { + margin-top:-20px; + padding:0; + font-size:14px; + color:black; + line-height:1.5em; +} +h1, h2, h3, h4, h5, h6 { + background:transparent none repeat scroll 0 0; + border-bottom:1px solid #aaa; + color:black; + font-weight:normal; + margin:0; + padding:0; + padding-bottom:0.17em; + padding-top:0.5em; +} +h1 { + font-size:188%; + line-height:1.2em; + margin-bottom:0.1em; + padding-bottom:0; +} +h2 { + font-size:150%; +} +h3, h4, h5, h6 { + border-bottom:none; + font-weight:bold; +} +h3 { + font-size:132%; +} +h4 { + font-size:116%; +} +h5 { + font-size:100%; +} +h6 { + font-size:80%; +} +ul#page-actions, ul#page-actions-more { + float:right; + margin:10px 10px 0 10px; + padding:6px; + color:black; + background-color:#ececec; + list-style-type:none; + white-space: nowrap; + border-radius:5px; + -moz-border-radius:5px; +} +ul#user-actions { + padding:5px; + margin:0; + display:inline; + color:black; + background-color:#ececec; + list-style-type:none; + -moz-border-radius:3px; + border-radius:3px; +} +ul#page-actions li, ul#user-actions li, ul#page-actions-more li { + display:inline; +} +ul#page-actions a, ul#user-actions a, ul#page-actions-more a { + text-decoration:none; + color:black; + display:inline; + margin:0 3px; + padding:2px 0px 2px 18px; +} +ul#page-actions a:hover, ul#page-actions a:focus, ul#user-actions a:hover, ul#user-actions a:focus { + /*text-decoration:underline;*/ +} +/***************************/ +ul#page-actions2 { + float:left; + margin:10px 10px 0 10px; + padding:6px; + color:black; + background-color:#ececec; + list-style-type:none; + border-radius:5px; + -moz-border-radius:5px; +} +ul#user-actions2 { + padding:5px; + margin:0; + display:inline; + color:black; + background-color:#ececec; + list-style-type:none; + border-radius:3px; + -moz-border-radius:3px; +} +ul#page-actions2 li, ul#user-actions2 li { + display:inline; +} +ul#page-actions2 a, ul#user-actions2 a { + text-decoration:none; + color:black; + display:inline; + margin:0 3px; + padding:2px 0px 2px 18px; +} +ul#page-actions2 a:hover, ul#page-actions2 a:focus, ul#user-actions2 a:hover, ul#user-actions2 a:focus, +ul#page-actions-more a:hover, ul#page-actions-more a:focus{ + color: #4e7bb4; +} +/****************************/ +.hidden { + display:none; +} + +a.logout { + background:transparent url(../img/user-actions-logout.png) 0px 1px no-repeat; +} + +a.info { + background:transparent url(../img/user-info.png) 0px 1px no-repeat; +} + +a.admin { + background:transparent url(../img/user-actions-admin.png) 0px 1px no-repeat; +} +a.profile { + background:transparent url(../img/user-actions-profile.png) 0px 1px no-repeat; +} +a.create, a.edit { + background:transparent url(../img/page-tools-edit.png) 0px 1px no-repeat; +} +a.source, a.show { + background:transparent url(../img/page-tools-source.png) 0px 1px no-repeat; +} +a.revisions { + background:transparent url(../img/page-tools-revisions.png) 0px 1px no-repeat; +} +a.subscribe, a.unsubscribe { + background:transparent url(../img/page-tools-subscribe.png) 0px 1px no-repeat; +} +a.backlink { + background:transparent url(../img/page-tools-backlinks.png) 0px 1px no-repeat; +} +a.play { + background:transparent url(../img/control_play.png) 0px 1px no-repeat; +} +.time { + background:transparent url(../img/status_None.png) 0px 1px no-repeat; + padding: 2px 0px 2px 18px; + margin: 0px 3px; +} +.reconnect { + background:transparent url(../img/reconnect.png) 0px 1px no-repeat; + padding: 2px 0px 2px 18px; + margin: 0px 3px; +} +a.play:hover { + background:transparent url(../img/control_play_blue.png) 0px 1px no-repeat; +} +a.cancel { + background:transparent url(../img/control_cancel.png) 0px 1px no-repeat; +} +a.cancel:hover { + background:transparent url(../img/control_cancel_blue.png) 0px 1px no-repeat; +} +a.pause { + background:transparent url(../img/control_pause.png) 0px 1px no-repeat; +} +a.pause:hover { + background:transparent url(../img/control_pause_blue.png) 0px 1px no-repeat; + font-weight: bold; +} +a.stop { + background:transparent url(../img/control_stop.png) 0px 1px no-repeat; +} +a.stop:hover { + background:transparent url(../img/control_stop_blue.png) 0px 1px no-repeat; +} +a.add { + background:transparent url(../img/control_add.png) 0px 1px no-repeat; +} +a.add:hover { + background:transparent url(../img/control_add_blue.png) 0px 1px no-repeat; +} +a.cog { + background:transparent url(../img/cog.png) 0px 1px no-repeat; +} +#head-panel { + background:#525252 url(../img/head_bg1.png) bottom left repeat-x; +} +#head-panel h1 { + display:none; + margin:0; + text-decoration:none; + padding-top:0.8em; + padding-left:3.3em; + font-size:2.6em; + color:#eeeeec; +} +#head-panel #head-logo { + float:left; + margin:5px 0 -15px 5px; + padding:0; + overflow:visible; +} +#head-menu { + background:transparent url(../img/tabs-border-bottom.png) 0 100% repeat-x; + width:100%; + float:left; + margin:0; + padding:0; + padding-top:0.8em; +} +#head-menu ul { + list-style:none; + margin:0 1em 0 2em; +} +#head-menu ul li { + float:left; + margin:0; + margin-left:0.3em; + font-size:14px; + margin-bottom:4px; +} +#head-menu ul li.selected, #head-menu ul li:hover { + margin-bottom:0px; +} +#head-menu ul li a img { + height:22px; + width:22px; + vertical-align:middle; +} +#head-menu ul li a, #head-menu ul li a:link { + float:left; + text-decoration:none; + color:#555; + background:#eaeaea url(../img/tab-background.png) 0 100% repeat-x; + padding:3px 7px 3px 7px; + border:2px solid #ccc; + border-bottom:0px solid transparent; + padding-bottom:3px; + -moz-border-radius:5px; + border-radius:5px; +} +#head-menu ul li a:hover, #head-menu ul li a:focus { + color:#111; + padding-bottom:7px; + border-bottom:0px none transparent; + outline:none; + border-bottom-left-radius: 0px; + border-bottom-right-radius: 0px; + -moz-border-radius-bottomright:0px; + -moz-border-radius-bottomleft:0px; +} +#head-menu ul li a:focus { + margin-bottom:-4px; +} +#head-menu ul li.selected a { + color:#3566A5; + background:#fff; + padding-bottom:7px; + border-bottom:0px none transparent; + border-bottom-left-radius: 0px; + border-bottom-right-radius: 0px; + -moz-border-radius-bottomright:0px; + -moz-border-radius-bottomleft:0px; +} +#head-menu ul li.selected a:hover, #head-menu ul li.selected a:focus { + color:#111; +} +div#head-search-and-login { + float:right; + margin:0 1em 0 0; + background-color:#222; + padding:7px 7px 5px 5px; + color:white; + white-space: nowrap; + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + -moz-border-radius-bottomright:6px; + -moz-border-radius-bottomleft:6px; +} +div#head-search-and-login form { + display:inline; + padding:0 3px; +} +div#head-search-and-login form input { + border:2px solid #888; + background:#eee; + font-size:14px; + padding:2px; + border-radius:3px; + -moz-border-radius:3px; +} +div#head-search-and-login form input:focus { + background:#fff; +} +#head-search { + font-size:14px; +} +#head-username, #head-password { + width:80px; + font-size:14px; +} +#pageinfo { + clear:both; + color:#888; + padding:0.6em 0; + margin:0; +} +#foot { + font-style:normal; + color:#888; + text-align:center; +} +#foot a { + color:#aaf; +} +#foot img { + vertical-align:middle; +} +div.toc { + border:1px dotted #888; + background:#f0f0f0; + margin:1em 0 1em 1em; + float:right; + font-size:95%; +} +div.toc .tocheader { + font-weight:bold; + margin:0.5em 1em; +} +div.toc ol { + margin:1em 0.5em 1em 1em; + padding:0; +} +div.toc ol li { + margin:0; + padding:0; + margin-left:1em; +} +div.toc ol ol { + margin:0.5em 0.5em 0.5em 1em; + padding:0; +} +div.recentchanges table { + clear:both; +} +div#editor-help { + font-size:90%; + border:1px dotted #888; + padding:0ex 1ex 1ex 1ex; + background:#f7f6f2; +} +div#preview { + margin-top:1em; +} +label.block { + display:block; + text-align:right; + font-weight:bold; +} +label.simple { + display:block; + text-align:left; + font-weight:normal; +} +label.block input.edit { + width:50%; +} +/*fieldset { + width:300px; + text-align:center; + padding:0.5em; + margin:auto; +} +*/ +div.editor { + margin:0 0 0 0; +} +table { + margin:0.5em 0; + border-collapse:collapse; +} +td { + padding:0.25em; + border:1pt solid #ADB9CC; +} +td p { + margin:0; + padding:0; +} +.u { + text-decoration:underline; +} +.footnotes ul { + padding:0 2em; + margin:0 0 1em; +} +.footnotes li { + list-style:none; +} +.userpref table, .userpref td { + border:none; +} +#message { + clear:both; + padding:5px 10px; + background-color:#eee; + border-bottom:2px solid #ccc; +} +#message p { + margin:5px 0; + padding:0; + font-weight:bold; +} +#message div.buttons { + font-weight:normal; +} +.diff { + width:99%; +} +.diff-title { + background-color:#C0C0C0; +} +.searchresult dd span { + font-weight:bold; +} +.boxtext { + font-family:tahoma, arial, sans-serif; + font-size:11px; + color:#000; + float:none; + padding:3px 0 0 10px; +} +.statusbutton { + width:32px; + height:32px; + float:left; + margin-left:-32px; + margin-right:5px; + opacity:0; + cursor:pointer +} +.dlsize { + float:left; + padding-right: 8px; +} +.dlspeed { + float:left; + padding-right: 8px; +} +.package { + margin-bottom: 10px; +} +.packagename { + font-weight: bold; +} + +.child { + margin-left: 20px; +} +.child_status { + margin-right: 10px; +} +.child_secrow { + font-size: 10px; +} + +.header, .header th { + text-align: left; + font-weight: normal; + background-color:#ececec; + -moz-border-radius:5px; + border-radius:5px; +} +.progress_bar { + background: #0C0; + height: 5px; + +} + +.queue { + border: none +} + +.queue tr td { + border: none +} + +.header, .header th{ + text-align: left; + font-weight: normal; +} + + +.clearer +{ + clear: both; + height: 1px; +} + +.left +{ + float: left; +} + +.right +{ + float: right; +} + + +.setfield +{ + display: table-cell; +} + +ul.tabs li a +{ + padding: 5px 16px 4px 15px; + border: none; + font-weight: bold; + + border-radius: 5px 5px 0 0; + -moz-border-radius: 5px 5px 0 0; + +} + + +#tabs span +{ + display: none; +} + +#tabs span.selected +{ + display: inline; +} + +#tabsback +{ + background-color: #525252; + margin: 2px 0 0; + padding: 6px 4px 1px 4px; + + border-top-right-radius: 30px; + border-top-left-radius: 3px; + -moz-border-radius-topright: 30px; + -moz-border-radius-topleft: 3px; +} +ul.tabs +{ + list-style-type: none; + margin:0; + padding: 0 40px 0 0; +} + +ul.tabs li +{ + display: inline; + margin-left: 8px; +} + + +ul.tabs li a +{ + color: #42454a; + background-color: #eaeaea; + border: 1px none #c9c3ba; + margin: 0; + text-decoration: none; + + outline: 0; + + padding: 5px 16px 4px 15px; + font-weight: bold; + + border-radius: 5px 5px 0 0; + -moz-border-radius: 5px 5px 0 0; + +} + +ul.tabs li a.selected, ul.tabs li a:hover +{ + color: #000; + background-color: white; + + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; + -moz-border-radius-bottomright: 0; + -moz-border-radius-bottomleft: 0; +} + +ul.tabs li a:hover +{ + background-color: #f1f4ee; +} + +ul.tabs li a.selected +{ + font-weight: bold; + background-color: #525252; + padding-bottom: 5px; + color: white; +} + + +#tabs-body { + position: relative; + overflow: hidden; +} + + +span.tabContent +{ + border: 2px solid #525252; + margin: 0; + padding: 0; + padding-bottom: 10px; +} + +#tabs-body > span { + display: none; +} + +#tabs-body > span.active { + display: block; +} + +.hide +{ + display: none; +} + +.settable +{ + margin: 20px; + border: none; +} +.settable td +{ + border: none; + margin: 0; + padding: 5px; +} + +.settable th{ + padding-bottom: 8px; +} + +.settable.wide td , .settable.wide th { + padding-left: 15px; + padding-right: 15px; +} + + +/*settings navbar*/ +ul.nav { + margin: -30px 0 0; + padding: 0; + list-style: none; + position: absolute; +} + + +ul.nav li { + position: relative; + float: left; + padding: 5px; +} + +ul.nav > li a { + background: white; + -moz-border-radius: 4px 4px 4px 4px; + border: 1px solid #C9C3BA; + border-bottom: medium none; + color: black; +} + +ul.nav ul { + position: absolute; + top: 26px; + left: 10px; + margin: 0; + padding: 0; + list-style: none; + border: 1px solid #AAA; + background: #f1f1f1; + -webkit-box-shadow: 1px 1px 5px #AAA; + -moz-box-shadow: 1px 1px 5px #AAA; + box-shadow: 1px 1px 5px #AAA; + cursor: pointer; +} + +ul.nav .open { + display: block; +} + +ul.nav .close { + display: none; +} + +ul.nav ul li { + float: none; + padding: 0; +} + +ul.nav ul li a { + width: 130px; + background: #f1f1f1; + padding: 3px; + display: block; + font-weight: normal; +} + +ul.nav ul li a:hover { + background: #CDCDCD; +} + +ul.nav ul ul { + left: 137px; + top: 0; +} + +.purr-wrapper{ + margin:10px; +} + +/*Purr alert styles*/ + +.purr-alert{ + margin-bottom:10px; + padding:10px; + background:#000; + font-size:13px; + font-weight:bold; + color:#FFF; + -moz-border-radius:5px; + -webkit-border-radius:5px; + /*-moz-box-shadow: 0 0 10px rgba(255,255,0,.25);*/ + width:300px; +} +.purr-alert.error{ + color:#F55; + padding-left:30px; + background:url(../img/error.png) no-repeat #000 7px 10px; + width:280px; +} +.purr-alert.success{ + color:#5F5; + padding-left:30px; + background:url(../img/success.png) no-repeat #000 7px 10px; + width:280px; +} +.purr-alert.notice{ + color:#99F; + padding-left:30px; + background:url(../img/notice.png) no-repeat #000 7px 10px; + width:280px; +} + +table.system { + border: none; + margin-left: 10px; +} + +table.system td { + border: none +} + +table.system tr > td:first-child { + font-weight: bold; + padding-right: 10px; +} diff --git a/pyload/webui/themes/Default/css/log.css b/pyload/webui/themes/Default/css/log.css new file mode 100644 index 000000000..33e67397d --- /dev/null +++ b/pyload/webui/themes/Default/css/log.css @@ -0,0 +1,71 @@ + +html, body, #content +{ + height: 100%; +} +#body-wrapper +{ + height: 70%; +} +.logdiv +{ + height: 90%; + width: 100%; + overflow: auto; + border: 2px solid #CCC; + outline: 1px solid #666; + background-color: #FFE; + margin-right: auto; + margin-left: auto; +} +.logform +{ + display: table; + margin: 0 auto 0 auto; + padding-top: 5px; +} +.logtable +{ + + margin: 0px; +} +.logtable td +{ + border: none; + white-space: nowrap; + + font-family: monospace; + font-size: 16px; + margin: 0px; + padding: 0px 10px 0px 10px; + line-height: 110%; +} +td.logline +{ + background-color: #EEE; + text-align:right; + padding: 0px 5px 0px 5px; +} +td.loglevel +{ + text-align:right; +} +.logperpage +{ + float: right; + padding-bottom: 8px; +} +.logpaginator +{ + float: left; + padding-top: 5px; +} +.logpaginator a +{ + padding: 0px 8px 0px 8px; +} +.logwarn +{ + text-align: center; + color: red; +} diff --git a/pyload/webui/themes/Default/css/pathchooser.css b/pyload/webui/themes/Default/css/pathchooser.css new file mode 100644 index 000000000..204812aa1 --- /dev/null +++ b/pyload/webui/themes/Default/css/pathchooser.css @@ -0,0 +1,68 @@ +table { + width: 90%; + border: 1px dotted #888888; + font-family: sans-serif; + font-size: 10pt; +} + +th { + background-color: #525252; + color: #E0E0E0; +} + +table, tr, td { + background-color: #F0F0F0; +} + +a, a:visited { + text-decoration: none; + font-weight: bold; +} + +#paths { + width: 90%; + text-align: left; +} + +.file_directory { + color: #c0c0c0; +} +.path_directory { + color: #3c3c3c; +} +.file_file { + color: #3c3c3c; +} +.path_file { + color: #c0c0c0; +} + +.parentdir { + color: #000000; + font-size: 10pt; +} +.name { + text-align: left; +} +.size { + text-align: right; +} +.type { + text-align: left; +} +.mtime { + text-align: center; +} + +.path_abs_rel { + color: #3c3c3c; + text-decoration: none; + font-weight: bold; + font-family: sans-serif; + font-size: 10pt; +} + +.path_abs_rel a { + color: #3c3c3c; + font-style: italic; +} diff --git a/pyload/webui/themes/Default/css/window.css b/pyload/webui/themes/Default/css/window.css new file mode 100644 index 000000000..b469b4259 --- /dev/null +++ b/pyload/webui/themes/Default/css/window.css @@ -0,0 +1,73 @@ +/* ----------- stylized ----------- */ +.window_box h1{ + font-size:14px; + font-weight:bold; + margin-bottom:8px; +} +.window_box p{ + font-size:11px; + color:#666666; + margin-bottom:20px; + border-bottom:solid 1px #b7ddf2; + padding-bottom:10px; +} +.window_box label{ + display:block; + font-weight:bold; + text-align:right; + width:240px; + float:left; +} +.window_box .small{ + color:#666666; + display:block; + font-size:11px; + font-weight:normal; + text-align:right; + width:240px; +} +.window_box select, .window_box input{ + float:left; + font-size:12px; + padding:4px 2px; + border:solid 1px #aacfe4; + width:300px; + margin:2px 0 20px 10px; +} +.window_box .cont{ + float:left; + font-size:12px; + padding: 0px 10px 15px 0px; + width:300px; + margin:0px 0px 0px 10px; +} +.window_box .cont input{ + float: none; + margin: 0px 15px 0px 1px; +} +.window_box textarea{ + float:left; + font-size:12px; + padding:4px 2px; + border:solid 1px #aacfe4; + width:300px; + margin:2px 0 20px 10px; +} +.window_box button, .styled_button{ + clear:both; + margin-left:150px; + width:125px; + height:31px; + background:#666666 url(../img/button.png) no-repeat; + text-align:center; + line-height:31px; + color:#FFFFFF; + font-size:11px; + font-weight:bold; + border: 0px; +} + +.styled_button { + margin-left: 15px; + cursor: pointer; +} diff --git a/pyload/webui/themes/Default/img/add_folder.png b/pyload/webui/themes/Default/img/add_folder.png Binary files differnew file mode 100644 index 000000000..8acbc411b --- /dev/null +++ b/pyload/webui/themes/Default/img/add_folder.png diff --git a/pyload/webui/themes/Default/img/ajax-loader.gif b/pyload/webui/themes/Default/img/ajax-loader.gif Binary files differnew file mode 100644 index 000000000..2fd8e0737 --- /dev/null +++ b/pyload/webui/themes/Default/img/ajax-loader.gif diff --git a/pyload/webui/themes/Default/img/arrow_refresh.png b/pyload/webui/themes/Default/img/arrow_refresh.png Binary files differnew file mode 100644 index 000000000..0de26566d --- /dev/null +++ b/pyload/webui/themes/Default/img/arrow_refresh.png diff --git a/pyload/webui/themes/Default/img/arrow_right.png b/pyload/webui/themes/Default/img/arrow_right.png Binary files differnew file mode 100644 index 000000000..b1a181923 --- /dev/null +++ b/pyload/webui/themes/Default/img/arrow_right.png diff --git a/pyload/webui/themes/Default/img/big_button.gif b/pyload/webui/themes/Default/img/big_button.gif Binary files differnew file mode 100644 index 000000000..7680490ea --- /dev/null +++ b/pyload/webui/themes/Default/img/big_button.gif diff --git a/pyload/webui/themes/Default/img/big_button_over.gif b/pyload/webui/themes/Default/img/big_button_over.gif Binary files differnew file mode 100644 index 000000000..2e3ee10d2 --- /dev/null +++ b/pyload/webui/themes/Default/img/big_button_over.gif diff --git a/pyload/webui/themes/Default/img/body.png b/pyload/webui/themes/Default/img/body.png Binary files differnew file mode 100644 index 000000000..7ff1043e0 --- /dev/null +++ b/pyload/webui/themes/Default/img/body.png diff --git a/pyload/webui/themes/Default/img/button.png b/pyload/webui/themes/Default/img/button.png Binary files differnew file mode 100644 index 000000000..890160614 --- /dev/null +++ b/pyload/webui/themes/Default/img/button.png diff --git a/pyload/webui/themes/Default/img/closebtn.gif b/pyload/webui/themes/Default/img/closebtn.gif Binary files differnew file mode 100644 index 000000000..3e27e6030 --- /dev/null +++ b/pyload/webui/themes/Default/img/closebtn.gif diff --git a/pyload/webui/themes/Default/img/cog.png b/pyload/webui/themes/Default/img/cog.png Binary files differnew file mode 100644 index 000000000..67de2c6cc --- /dev/null +++ b/pyload/webui/themes/Default/img/cog.png diff --git a/pyload/webui/themes/Default/img/control_add.png b/pyload/webui/themes/Default/img/control_add.png Binary files differnew file mode 100644 index 000000000..d39886893 --- /dev/null +++ b/pyload/webui/themes/Default/img/control_add.png diff --git a/pyload/webui/themes/Default/img/control_add_blue.png b/pyload/webui/themes/Default/img/control_add_blue.png Binary files differnew file mode 100644 index 000000000..d11b7f41d --- /dev/null +++ b/pyload/webui/themes/Default/img/control_add_blue.png diff --git a/pyload/webui/themes/Default/img/control_cancel.png b/pyload/webui/themes/Default/img/control_cancel.png Binary files differnew file mode 100644 index 000000000..7b9bc3fba --- /dev/null +++ b/pyload/webui/themes/Default/img/control_cancel.png diff --git a/pyload/webui/themes/Default/img/control_cancel_blue.png b/pyload/webui/themes/Default/img/control_cancel_blue.png Binary files differnew file mode 100644 index 000000000..0c5c96ce3 --- /dev/null +++ b/pyload/webui/themes/Default/img/control_cancel_blue.png diff --git a/pyload/webui/themes/Default/img/control_pause.png b/pyload/webui/themes/Default/img/control_pause.png Binary files differnew file mode 100644 index 000000000..2d9ce9c4e --- /dev/null +++ b/pyload/webui/themes/Default/img/control_pause.png diff --git a/pyload/webui/themes/Default/img/control_pause_blue.png b/pyload/webui/themes/Default/img/control_pause_blue.png Binary files differnew file mode 100644 index 000000000..ec61099b0 --- /dev/null +++ b/pyload/webui/themes/Default/img/control_pause_blue.png diff --git a/pyload/webui/themes/Default/img/control_play.png b/pyload/webui/themes/Default/img/control_play.png Binary files differnew file mode 100644 index 000000000..0846555d0 --- /dev/null +++ b/pyload/webui/themes/Default/img/control_play.png diff --git a/pyload/webui/themes/Default/img/control_play_blue.png b/pyload/webui/themes/Default/img/control_play_blue.png Binary files differnew file mode 100644 index 000000000..f8c8ec683 --- /dev/null +++ b/pyload/webui/themes/Default/img/control_play_blue.png diff --git a/pyload/webui/themes/Default/img/control_stop.png b/pyload/webui/themes/Default/img/control_stop.png Binary files differnew file mode 100644 index 000000000..893bb60e5 --- /dev/null +++ b/pyload/webui/themes/Default/img/control_stop.png diff --git a/pyload/webui/themes/Default/img/control_stop_blue.png b/pyload/webui/themes/Default/img/control_stop_blue.png Binary files differnew file mode 100644 index 000000000..e6f75d232 --- /dev/null +++ b/pyload/webui/themes/Default/img/control_stop_blue.png diff --git a/pyload/webui/themes/Default/img/delete.png b/pyload/webui/themes/Default/img/delete.png Binary files differnew file mode 100644 index 000000000..08f249365 --- /dev/null +++ b/pyload/webui/themes/Default/img/delete.png diff --git a/pyload/webui/themes/Default/img/drag_corner.gif b/pyload/webui/themes/Default/img/drag_corner.gif Binary files differnew file mode 100644 index 000000000..befb1adf1 --- /dev/null +++ b/pyload/webui/themes/Default/img/drag_corner.gif diff --git a/pyload/webui/themes/Default/img/error.png b/pyload/webui/themes/Default/img/error.png Binary files differnew file mode 100644 index 000000000..c37bd062e --- /dev/null +++ b/pyload/webui/themes/Default/img/error.png diff --git a/pyload/webui/themes/Default/img/folder.png b/pyload/webui/themes/Default/img/folder.png Binary files differnew file mode 100644 index 000000000..784e8fa48 --- /dev/null +++ b/pyload/webui/themes/Default/img/folder.png diff --git a/pyload/webui/themes/Default/img/full.png b/pyload/webui/themes/Default/img/full.png Binary files differnew file mode 100644 index 000000000..fea52af76 --- /dev/null +++ b/pyload/webui/themes/Default/img/full.png diff --git a/pyload/webui/themes/Default/img/head-login.png b/pyload/webui/themes/Default/img/head-login.png Binary files differnew file mode 100644 index 000000000..b59b7cbbf --- /dev/null +++ b/pyload/webui/themes/Default/img/head-login.png diff --git a/pyload/webui/themes/Default/img/head-menu-collector.png b/pyload/webui/themes/Default/img/head-menu-collector.png Binary files differnew file mode 100644 index 000000000..861be40bc --- /dev/null +++ b/pyload/webui/themes/Default/img/head-menu-collector.png diff --git a/pyload/webui/themes/Default/img/head-menu-config.png b/pyload/webui/themes/Default/img/head-menu-config.png Binary files differnew file mode 100644 index 000000000..bbf43d4f3 --- /dev/null +++ b/pyload/webui/themes/Default/img/head-menu-config.png diff --git a/pyload/webui/themes/Default/img/head-menu-development.png b/pyload/webui/themes/Default/img/head-menu-development.png Binary files differnew file mode 100644 index 000000000..fad150fe1 --- /dev/null +++ b/pyload/webui/themes/Default/img/head-menu-development.png diff --git a/pyload/webui/themes/Default/img/head-menu-download.png b/pyload/webui/themes/Default/img/head-menu-download.png Binary files differnew file mode 100644 index 000000000..98c5da9db --- /dev/null +++ b/pyload/webui/themes/Default/img/head-menu-download.png diff --git a/pyload/webui/themes/Default/img/head-menu-home.png b/pyload/webui/themes/Default/img/head-menu-home.png Binary files differnew file mode 100644 index 000000000..9d62109aa --- /dev/null +++ b/pyload/webui/themes/Default/img/head-menu-home.png diff --git a/pyload/webui/themes/Default/img/head-menu-index.png b/pyload/webui/themes/Default/img/head-menu-index.png Binary files differnew file mode 100644 index 000000000..44d631064 --- /dev/null +++ b/pyload/webui/themes/Default/img/head-menu-index.png diff --git a/pyload/webui/themes/Default/img/head-menu-news.png b/pyload/webui/themes/Default/img/head-menu-news.png Binary files differnew file mode 100644 index 000000000..43950ebc9 --- /dev/null +++ b/pyload/webui/themes/Default/img/head-menu-news.png diff --git a/pyload/webui/themes/Default/img/head-menu-queue.png b/pyload/webui/themes/Default/img/head-menu-queue.png Binary files differnew file mode 100644 index 000000000..be98793ce --- /dev/null +++ b/pyload/webui/themes/Default/img/head-menu-queue.png diff --git a/pyload/webui/themes/Default/img/head-menu-recent.png b/pyload/webui/themes/Default/img/head-menu-recent.png Binary files differnew file mode 100644 index 000000000..fc9b0497f --- /dev/null +++ b/pyload/webui/themes/Default/img/head-menu-recent.png diff --git a/pyload/webui/themes/Default/img/head-menu-wiki.png b/pyload/webui/themes/Default/img/head-menu-wiki.png Binary files differnew file mode 100644 index 000000000..07cf0102d --- /dev/null +++ b/pyload/webui/themes/Default/img/head-menu-wiki.png diff --git a/pyload/webui/themes/Default/img/head-search-noshadow.png b/pyload/webui/themes/Default/img/head-search-noshadow.png Binary files differnew file mode 100644 index 000000000..aafdae015 --- /dev/null +++ b/pyload/webui/themes/Default/img/head-search-noshadow.png diff --git a/pyload/webui/themes/Default/img/head_bg1.png b/pyload/webui/themes/Default/img/head_bg1.png Binary files differnew file mode 100644 index 000000000..f2848c3cc --- /dev/null +++ b/pyload/webui/themes/Default/img/head_bg1.png diff --git a/pyload/webui/themes/Default/img/images.png b/pyload/webui/themes/Default/img/images.png Binary files differnew file mode 100644 index 000000000..184860d1e --- /dev/null +++ b/pyload/webui/themes/Default/img/images.png diff --git a/pyload/webui/themes/Default/img/notice.png b/pyload/webui/themes/Default/img/notice.png Binary files differnew file mode 100644 index 000000000..12cd1aef9 --- /dev/null +++ b/pyload/webui/themes/Default/img/notice.png diff --git a/pyload/webui/themes/Default/img/package_go.png b/pyload/webui/themes/Default/img/package_go.png Binary files differnew file mode 100644 index 000000000..aace63ad6 --- /dev/null +++ b/pyload/webui/themes/Default/img/package_go.png diff --git a/pyload/webui/themes/Default/img/page-tools-backlinks.png b/pyload/webui/themes/Default/img/page-tools-backlinks.png Binary files differnew file mode 100644 index 000000000..3eb6a9ce3 --- /dev/null +++ b/pyload/webui/themes/Default/img/page-tools-backlinks.png diff --git a/pyload/webui/themes/Default/img/page-tools-edit.png b/pyload/webui/themes/Default/img/page-tools-edit.png Binary files differnew file mode 100644 index 000000000..188e1c12b --- /dev/null +++ b/pyload/webui/themes/Default/img/page-tools-edit.png diff --git a/pyload/webui/themes/Default/img/page-tools-revisions.png b/pyload/webui/themes/Default/img/page-tools-revisions.png Binary files differnew file mode 100644 index 000000000..5c3b8587f --- /dev/null +++ b/pyload/webui/themes/Default/img/page-tools-revisions.png diff --git a/pyload/webui/themes/Default/img/parseUri.png b/pyload/webui/themes/Default/img/parseUri.png Binary files differnew file mode 100644 index 000000000..937bded9d --- /dev/null +++ b/pyload/webui/themes/Default/img/parseUri.png diff --git a/pyload/webui/themes/Default/img/pencil.png b/pyload/webui/themes/Default/img/pencil.png Binary files differnew file mode 100644 index 000000000..0bfecd50e --- /dev/null +++ b/pyload/webui/themes/Default/img/pencil.png diff --git a/pyload/webui/themes/Default/img/pyload-logo.png b/pyload/webui/themes/Default/img/pyload-logo.png Binary files differnew file mode 100644 index 000000000..2443cd8b1 --- /dev/null +++ b/pyload/webui/themes/Default/img/pyload-logo.png diff --git a/pyload/webui/themes/Default/img/reconnect.png b/pyload/webui/themes/Default/img/reconnect.png Binary files differnew file mode 100644 index 000000000..49b269145 --- /dev/null +++ b/pyload/webui/themes/Default/img/reconnect.png diff --git a/pyload/webui/themes/Default/img/status_None.png b/pyload/webui/themes/Default/img/status_None.png Binary files differnew file mode 100644 index 000000000..293b13f77 --- /dev/null +++ b/pyload/webui/themes/Default/img/status_None.png diff --git a/pyload/webui/themes/Default/img/status_downloading.png b/pyload/webui/themes/Default/img/status_downloading.png Binary files differnew file mode 100644 index 000000000..fb4ebc850 --- /dev/null +++ b/pyload/webui/themes/Default/img/status_downloading.png diff --git a/pyload/webui/themes/Default/img/status_failed.png b/pyload/webui/themes/Default/img/status_failed.png Binary files differnew file mode 100644 index 000000000..c37bd062e --- /dev/null +++ b/pyload/webui/themes/Default/img/status_failed.png diff --git a/pyload/webui/themes/Default/img/status_finished.png b/pyload/webui/themes/Default/img/status_finished.png Binary files differnew file mode 100644 index 000000000..89c8129a4 --- /dev/null +++ b/pyload/webui/themes/Default/img/status_finished.png diff --git a/pyload/webui/themes/Default/img/status_offline.png b/pyload/webui/themes/Default/img/status_offline.png Binary files differnew file mode 100644 index 000000000..0cfd58596 --- /dev/null +++ b/pyload/webui/themes/Default/img/status_offline.png diff --git a/pyload/webui/themes/Default/img/status_proc.png b/pyload/webui/themes/Default/img/status_proc.png Binary files differnew file mode 100644 index 000000000..67de2c6cc --- /dev/null +++ b/pyload/webui/themes/Default/img/status_proc.png diff --git a/pyload/webui/themes/Default/img/status_queue.png b/pyload/webui/themes/Default/img/status_queue.png Binary files differnew file mode 100644 index 000000000..293b13f77 --- /dev/null +++ b/pyload/webui/themes/Default/img/status_queue.png diff --git a/pyload/webui/themes/Default/img/status_waiting.png b/pyload/webui/themes/Default/img/status_waiting.png Binary files differnew file mode 100644 index 000000000..2842cc338 --- /dev/null +++ b/pyload/webui/themes/Default/img/status_waiting.png diff --git a/pyload/webui/themes/Default/img/success.png b/pyload/webui/themes/Default/img/success.png Binary files differnew file mode 100644 index 000000000..89c8129a4 --- /dev/null +++ b/pyload/webui/themes/Default/img/success.png diff --git a/pyload/webui/themes/Default/img/tab-background.png b/pyload/webui/themes/Default/img/tab-background.png Binary files differnew file mode 100644 index 000000000..29a5d1991 --- /dev/null +++ b/pyload/webui/themes/Default/img/tab-background.png diff --git a/pyload/webui/themes/Default/img/tabs-border-bottom.png b/pyload/webui/themes/Default/img/tabs-border-bottom.png Binary files differnew file mode 100644 index 000000000..02440f428 --- /dev/null +++ b/pyload/webui/themes/Default/img/tabs-border-bottom.png diff --git a/pyload/webui/themes/Default/img/user-actions-logout.png b/pyload/webui/themes/Default/img/user-actions-logout.png Binary files differnew file mode 100644 index 000000000..0010931e2 --- /dev/null +++ b/pyload/webui/themes/Default/img/user-actions-logout.png diff --git a/pyload/webui/themes/Default/img/user-actions-profile.png b/pyload/webui/themes/Default/img/user-actions-profile.png Binary files differnew file mode 100644 index 000000000..46573fff6 --- /dev/null +++ b/pyload/webui/themes/Default/img/user-actions-profile.png diff --git a/pyload/webui/themes/Default/img/user-info.png b/pyload/webui/themes/Default/img/user-info.png Binary files differnew file mode 100644 index 000000000..6e643100f --- /dev/null +++ b/pyload/webui/themes/Default/img/user-info.png diff --git a/pyload/webui/themes/Default/js/admin.coffee b/pyload/webui/themes/Default/js/admin.coffee new file mode 100644 index 000000000..5afbcbb66 --- /dev/null +++ b/pyload/webui/themes/Default/js/admin.coffee @@ -0,0 +1,58 @@ +root = this + +window.addEvent "domready", -> + + root.passwordDialog = new MooDialog {destroyOnHide: false} + root.passwordDialog.setContent $ 'password_box' + + $("login_password_reset").addEvent "click", (e) -> root.passwordDialog.close() + $("login_password_button").addEvent "click", (e) -> + + newpw = $("login_new_password").get("value") + newpw2 = $("login_new_password2").get("value") + + if newpw is newpw2 + form = $("password_form") + form.set "send", { + onSuccess: (data) -> + root.notify.alert "Success", { + 'className': 'success' + } + onFailure: (data) -> + root.notify.alert "Error", { + 'className': 'error' + } + } + + form.send() + + root.passwordDialog.close() + else + alert '{{_("Passwords did not match.")}}' + + e.stop() + + for item in $$(".change_password") + id = item.get("id") + user = id.split("|")[1] + $("user_login").set("value", user) + item.addEvent "click", (e) -> root.passwordDialog.open() + + $('quit-pyload').addEvent "click", (e) -> + new MooDialog.Confirm "{{_('You are really sure you want to quit pyLoad?')}}", -> + new Request.JSON({ + url: '/api/kill' + method: 'get' + }).send() + , -> + e.stop() + + $('restart-pyload').addEvent "click", (e) -> + new MooDialog.Confirm "{{_('Are you sure you want to restart pyLoad?')}}", -> + new Request.JSON({ + url: '/api/restart' + method: 'get' + onSuccess: (data) -> alert "{{_('pyLoad restarted')}}" + }).send() + , -> + e.stop() diff --git a/pyload/webui/themes/Default/js/admin.min.js b/pyload/webui/themes/Default/js/admin.min.js new file mode 100644 index 000000000..94a5e494d --- /dev/null +++ b/pyload/webui/themes/Default/js/admin.min.js @@ -0,0 +1,3 @@ +{% autoescape true %} +var root;root=this;window.addEvent("domready",function(){var f,c,b,e,a,d;root.passwordDialog=new MooDialog({destroyOnHide:false});root.passwordDialog.setContent($("password_box"));$("login_password_reset").addEvent("click",function(g){return root.passwordDialog.close()});$("login_password_button").addEvent("click",function(j){var h,i,g;i=$("login_new_password").get("value");g=$("login_new_password2").get("value");if(i===g){h=$("password_form");h.set("send",{onSuccess:function(k){return root.notify.alert("Success",{className:"success"})},onFailure:function(k){return root.notify.alert("Error",{className:"error"})}});h.send();root.passwordDialog.close()}else{alert('{{_("Passwords did not match.")}}')}return j.stop()});d=$$(".change_password");for(e=0,a=d.length;e<a;e++){c=d[e];f=c.get("id");b=f.split("|")[1];$("user_login").set("value",b);c.addEvent("click",function(g){return root.passwordDialog.open()})}$("quit-pyload").addEvent("click",function(g){new MooDialog.Confirm("{{_('You are really sure you want to quit pyLoad?')}}",function(){return new Request.JSON({url:"/api/kill",method:"get"}).send()},function(){});return g.stop()});return $("restart-pyload").addEvent("click",function(g){new MooDialog.Confirm("{{_('Are you sure you want to restart pyLoad?')}}",function(){return new Request.JSON({url:"/api/restart",method:"get",onSuccess:function(h){return alert("{{_('pyLoad restarted')}}")}}).send()},function(){});return g.stop()})}); +{% endautoescape %} diff --git a/pyload/webui/themes/Default/js/base.coffee b/pyload/webui/themes/Default/js/base.coffee new file mode 100644 index 000000000..07b8bfb6f --- /dev/null +++ b/pyload/webui/themes/Default/js/base.coffee @@ -0,0 +1,177 @@ +# External scope +root = this + +# helper functions +humanFileSize = (size) -> + filesizename = new Array("B", "KiB", "MiB", "GiB", "TiB", "PiB") + loga = Math.log(size) / Math.log(1024) + i = Math.floor(loga) + a = Math.pow(1024, i) + if size is 0 then "0 B" else (Math.round(size * 100 / a) / 100 + " " + filesizename[i]) + + +parseUri = () -> + oldString = $("add_links").value + regxp = new RegExp('(ht|f)tp(s?):\/\/[a-zA-Z0-9\-\.\/\?=_&%#]+[<| |\"|\'|\r|\n|\t]{1}', 'g') + resu = oldString.match regxp + return if resu == null + res = "" + + for part in resu + if part.indexOf(" ") != -1 + res = res + part.replace(" ", " \n") + else if part.indexOf("\t") != -1 + res = res + part.replace("\t", " \n") + else if part.indexOf("\r") != -1 + res = res + part.replace("\r", " \n") + else if part.indexOf("\"") != -1 + res = res + part.replace("\"", " \n") + else if part.indexOf("<") != -1 + res = res + part.replace("<", " \n") + else if part.indexOf("'") != -1 + res = res + part.replace("'", " \n") + else + res = res + part.replace("\n", " \n") + + $("add_links").value = res + + +Array::remove = (from, to) -> + rest = this.slice((to || from) + 1 || this.length) + this.length = from < 0 ? this.length + from : from + return [] if this.length == 0 + return this.push.apply(this, rest) + + +document.addEvent "domready", -> + + # global notification + root.notify = new Purr { + 'mode': 'top' + 'position': 'center' + } + + root.captchaBox = new MooDialog {destroyOnHide: false} + root.captchaBox.setContent $ 'cap_box' + + root.addBox = new MooDialog {destroyOnHide: false} + root.addBox.setContent $ 'add_box' + + $('add_form').onsubmit = -> + $('add_form').target = 'upload_target' + if $('add_name').value is "" and $('add_file').value is "" + alert '{{_("Please Enter a packagename.")}}' + return false + else + root.addBox.close() + return true + + $('add_reset').addEvent 'click', -> root.addBox.close() + + $('action_add').addEvent 'click', -> $("add_form").reset(); root.addBox.open() + $('action_play').addEvent 'click', -> new Request({method: 'get', url: '/api/unpauseServer'}).send() + $('action_cancel').addEvent 'click', -> new Request({method: 'get', url: '/api/stopAllDownloads'}).send() + $('action_stop').addEvent 'click', -> new Request({method: 'get', url: '/api/pauseServer'}).send() + + + # captcha events + + $('cap_info').addEvent 'click', -> + load_captcha "get", "" + root.captchaBox.open() + $('cap_reset').addEvent 'click', -> root.captchaBox.close() + $('cap_form').addEvent 'submit', (e) -> + submit_captcha() + e.stop() + + $('cap_positional').addEvent 'click', on_captcha_click + + new Request.JSON({ + url: "/json/status" + onSuccess: LoadJsonToContent + secure: false + async: true + initialDelay: 0 + delay: 4000 + limit: 3000 + }).startTimer() + + +LoadJsonToContent = (data) -> + $("speed").set 'text', humanFileSize(data.speed)+"/s" + $("aktiv").set 'text', data.active + $("aktiv_from").set 'text', data.queue + $("aktiv_total").set 'text', data.total + + if data.captcha + if $("cap_info").getStyle("display") != "inline" + $("cap_info").setStyle 'display', 'inline' + root.notify.alert '{{_("New Captcha Request")}}', { + 'className': 'notify' + } + else + $("cap_info").setStyle 'display', 'none' + + + if data.download + $("time").set 'text', ' {{_("on")}}' + $("time").setStyle 'background-color', "#8ffc25" + else + $("time").set 'text', ' {{_("off")}}' + $("time").setStyle 'background-color', "#fc6e26" + + if data.reconnect + $("reconnect").set 'text', ' {{_("on")}}' + $("reconnect").setStyle 'background-color', "#8ffc25" + else + $("reconnect").set 'text', ' {{_("off")}}' + $("reconnect").setStyle 'background-color', "#fc6e26" + + return null + + +set_captcha = (data) -> + $('cap_id').set 'value', data.id + if (data.result_type is 'textual') + $('cap_textual_img').set 'src', data.src + $('cap_title').set 'text', '{{_("Please read the text on the captcha.")}}' + $('cap_submit').setStyle 'display', 'inline' + $('cap_textual').setStyle 'display', 'block' + $('cap_positional').setStyle 'display', 'none' + + else if (data.result_type == 'positional') + $('cap_positional_img').set('src', data.src) + $('cap_title').set('text', '{{_("Please click on the right captcha position.")}}') + $('cap_submit').setStyle('display', 'none') + $('cap_textual').setStyle('display', 'none') + + +load_captcha = (method, post) -> + new Request.JSON({ + url: "/json/set_captcha" + onSuccess: (data) -> set_captcha(data) if data.captcha else clear_captcha() + secure: false + async: true + method: method + }).send(post) + + +clear_captcha = -> + $('cap_textual').setStyle 'display', 'none' + $('cap_textual_img').set 'src', '' + $('cap_positional').setStyle 'display', 'none' + $('cap_positional_img').set 'src', '' + $('cap_title').set 'text', '{{_("No Captchas to read.")}}' + + +submit_captcha = -> + load_captcha("post", "cap_id=" + $('cap_id').get('value') + "&cap_result=" + $('cap_result').get('value') ) + $('cap_result').set('value', '') + + +on_captcha_click = (e) -> + position = e.target.getPosition() + x = e.page.x - position.x + y = e.page.y - position.y + $('cap_result').value = x + "," + y + submit_captcha() diff --git a/pyload/webui/themes/Default/js/base.min.js b/pyload/webui/themes/Default/js/base.min.js new file mode 100644 index 000000000..1ba1d73f9 --- /dev/null +++ b/pyload/webui/themes/Default/js/base.min.js @@ -0,0 +1,3 @@ +{% autoescape true %} +var LoadJsonToContent,clear_captcha,humanFileSize,load_captcha,on_captcha_click,parseUri,root,set_captcha,submit_captcha;root=this;humanFileSize=function(f){var c,d,e,b;d=new Array("B","KiB","MiB","GiB","TiB","PiB");b=Math.log(f)/Math.log(1024);e=Math.floor(b);c=Math.pow(1024,e);if(f===0){return"0 B"}else{return Math.round(f*100/c)/100+" "+d[e]}};parseUri=function(){var b,c,g,e,d,f,a;b=$("add_links").value;g=new RegExp("(ht|f)tp(s?)://[a-zA-Z0-9-./?=_&%#]+[<| |\"|'|\r|\n|\t]{1}","g");d=b.match(g);if(d===null){return}e="";for(f=0,a=d.length;f<a;f++){c=d[f];if(c.indexOf(" ")!==-1){e=e+c.replace(" "," \n")}else{if(c.indexOf("\t")!==-1){e=e+c.replace("\t"," \n")}else{if(c.indexOf("\r")!==-1){e=e+c.replace("\r"," \n")}else{if(c.indexOf('"')!==-1){e=e+c.replace('"'," \n")}else{if(c.indexOf("<")!==-1){e=e+c.replace("<"," \n")}else{if(c.indexOf("'")!==-1){e=e+c.replace("'"," \n")}else{e=e+c.replace("\n"," \n")}}}}}}}return $("add_links").value=e};Array.prototype.remove=function(d,c){var a,b;a=this.slice((c||d)+1||this.length);this.length=(b=d<0)!=null?b:this.length+{from:d};if(this.length===0){return[]}return this.push.apply(this,a)};document.addEvent("domready",function(){root.notify=new Purr({mode:"top",position:"center"});root.captchaBox=new MooDialog({destroyOnHide:false});root.captchaBox.setContent($("cap_box"));root.addBox=new MooDialog({destroyOnHide:false});root.addBox.setContent($("add_box"));$("add_form").onsubmit=function(){$("add_form").target="upload_target";if($("add_name").value===""&&$("add_file").value===""){alert('{{_("Please Enter a packagename.")}}');return false}else{root.addBox.close();return true}};$("add_reset").addEvent("click",function(){return root.addBox.close()});$("action_add").addEvent("click",function(){$("add_form").reset();return root.addBox.open()});$("action_play").addEvent("click",function(){return new Request({method:"get",url:"/api/unpauseServer"}).send()});$("action_cancel").addEvent("click",function(){return new Request({method:"get",url:"/api/stopAllDownloads"}).send()});$("action_stop").addEvent("click",function(){return new Request({method:"get",url:"/api/pauseServer"}).send()});$("cap_info").addEvent("click",function(){load_captcha("get","");return root.captchaBox.open()});$("cap_reset").addEvent("click",function(){return root.captchaBox.close()});$("cap_form").addEvent("submit",function(a){submit_captcha();return a.stop()});$("cap_positional").addEvent("click",on_captcha_click);return new Request.JSON({url:"/json/status",onSuccess:LoadJsonToContent,secure:false,async:true,initialDelay:0,delay:4000,limit:3000}).startTimer()});LoadJsonToContent=function(a){$("speed").set("text",humanFileSize(a.speed)+"/s");$("aktiv").set("text",a.active);$("aktiv_from").set("text",a.queue);$("aktiv_total").set("text",a.total);if(a.captcha){if($("cap_info").getStyle("display")!=="inline"){$("cap_info").setStyle("display","inline");root.notify.alert('{{_("New Captcha Request")}}',{className:"notify"})}}else{$("cap_info").setStyle("display","none")}if(a.download){$("time").set("text",' {{_("on")}}');$("time").setStyle("background-color","#8ffc25")}else{$("time").set("text",' {{_("off")}}');$("time").setStyle("background-color","#fc6e26")}if(a.reconnect){$("reconnect").set("text",' {{_("on")}}');$("reconnect").setStyle("background-color","#8ffc25")}else{$("reconnect").set("text",' {{_("off")}}');$("reconnect").setStyle("background-color","#fc6e26")}return null};set_captcha=function(a){$("cap_id").set("value",a.id);if(a.result_type==="textual"){$("cap_textual_img").set("src",a.src);$("cap_title").set("text",'{{_("Please read the text on the captcha.")}}');$("cap_submit").setStyle("display","inline");$("cap_textual").setStyle("display","block");return $("cap_positional").setStyle("display","none")}else{if(a.result_type==="positional"){$("cap_positional_img").set("src",a.src);$("cap_title").set("text",'{{_("Please click on the right captcha position.")}}');$("cap_submit").setStyle("display","none");return $("cap_textual").setStyle("display","none")}}};load_captcha=function(b,a){return new Request.JSON({url:"/json/set_captcha",onSuccess:function(c){return set_captcha(c)(c.captcha?void 0:clear_captcha())},secure:false,async:true,method:b}).send(a)};clear_captcha=function(){$("cap_textual").setStyle("display","none");$("cap_textual_img").set("src","");$("cap_positional").setStyle("display","none");$("cap_positional_img").set("src","");return $("cap_title").set("text",'{{_("No Captchas to read.")}}')};submit_captcha=function(){load_captcha("post","cap_id="+$("cap_id").get("value")+"&cap_result="+$("cap_result").get("value"));$("cap_result").set("value","");return false};on_captcha_click=function(c){var b,a,d;b=c.target.getPosition();a=c.page.x-b.x;d=c.page.y-b.y;$("cap_result").value=a+","+d;return submit_captcha()}; +{% endautoescape %} diff --git a/pyload/webui/themes/Default/js/package.js b/pyload/webui/themes/Default/js/package.js new file mode 100644 index 000000000..659a8e6fc --- /dev/null +++ b/pyload/webui/themes/Default/js/package.js @@ -0,0 +1,376 @@ +var root = this; + +document.addEvent("domready", function() { + root.load = new Fx.Tween($("load-indicator"), {link: "cancel"}); + root.load.set("opacity", 0); + + + root.packageBox = new MooDialog({destroyOnHide: false}); + root.packageBox.setContent($('pack_box')); + + $('pack_reset').addEvent('click', function() { + $('pack_form').reset(); + root.packageBox.close(); + }); +}); + +function indicateLoad() { + //$("load-indicator").reveal(); + root.load.start("opacity", 1) +} + +function indicateFinish() { + root.load.start("opacity", 0) +} + +function indicateSuccess() { + indicateFinish(); + root.notify.alert('{{_("Success")}}.', { + 'className': 'success' + }); +} + +function indicateFail() { + indicateFinish(); + root.notify.alert('{{_("Failed")}}.', { + 'className': 'error' + }); +} + +var PackageUI = new Class({ + initialize: function(url, type) { + this.url = url; + this.type = type; + this.packages = []; + this.parsePackages(); + + this.sorts = new Sortables($("package-list"), { + constrain: false, + clone: true, + revert: true, + opacity: 0.4, + handle: ".package_drag", + onComplete: this.saveSort.bind(this) + }); + + $("del_finished").addEvent("click", this.deleteFinished.bind(this)); + $("restart_failed").addEvent("click", this.restartFailed.bind(this)); + + }, + + parsePackages: function() { + $("package-list").getChildren("li").each(function(ele) { + var id = ele.getFirst().get("id").match(/[0-9]+/); + this.packages.push(new Package(this, id, ele)) + }.bind(this)) + }, + + loadPackages: function() { + }, + + deleteFinished: function() { + indicateLoad(); + new Request.JSON({ + method: 'get', + url: '/api/deleteFinished', + onSuccess: function(data) { + if (data.length > 0) { + window.location.reload() + } else { + this.packages.each(function(pack) { + pack.close(); + }); + indicateSuccess(); + } + }.bind(this), + onFailure: indicateFail + }).send(); + }, + + restartFailed: function() { + indicateLoad(); + new Request.JSON({ + method: 'get', + url: '/api/restartFailed', + onSuccess: function(data) { + this.packages.each(function(pack) { + pack.close(); + }); + indicateSuccess(); + }.bind(this), + onFailure: indicateFail + }).send(); + }, + + startSort: function(ele, copy) { + }, + + saveSort: function(ele, copy) { + var order = []; + this.sorts.serialize(function(li, pos) { + if (li == ele && ele.retrieve("order") != pos) { + order.push(ele.retrieve("pid") + "|" + pos) + } + li.store("order", pos) + }); + if (order.length > 0) { + indicateLoad(); + new Request.JSON({ + method: 'get', + url: '/json/package_order/' + order[0], + onSuccess: indicateFinish, + onFailure: indicateFail + }).send(); + } + } + +}); + +var Package = new Class({ + initialize: function(ui, id, ele, data) { + this.ui = ui; + this.id = id; + this.linksLoaded = false; + + if (!ele) { + this.createElement(data); + } else { + this.ele = ele; + this.order = ele.getElements("div.order")[0].get("html"); + this.ele.store("order", this.order); + this.ele.store("pid", this.id); + this.parseElement(); + } + + var pname = this.ele.getElements(".packagename")[0]; + this.buttons = new Fx.Tween(this.ele.getElements(".buttons")[0], {link: "cancel"}); + this.buttons.set("opacity", 0); + + pname.addEvent("mouseenter", function(e) { + this.buttons.start("opacity", 1) + }.bind(this)); + + pname.addEvent("mouseleave", function(e) { + this.buttons.start("opacity", 0) + }.bind(this)); + + + }, + + createElement: function() { + alert("create") + }, + + parseElement: function() { + var imgs = this.ele.getElements('img'); + + this.name = this.ele.getElements('.name')[0]; + this.folder = this.ele.getElements('.folder')[0]; + this.password = this.ele.getElements('.password')[0]; + + imgs[1].addEvent('click', this.deletePackage.bind(this)); + imgs[2].addEvent('click', this.restartPackage.bind(this)); + imgs[3].addEvent('click', this.editPackage.bind(this)); + imgs[4].addEvent('click', this.movePackage.bind(this)); + + this.ele.getElement('.packagename').addEvent('click', this.toggle.bind(this)); + + }, + + loadLinks: function() { + indicateLoad(); + new Request.JSON({ + method: 'get', + url: '/json/package/' + this.id, + onSuccess: this.createLinks.bind(this), + onFailure: indicateFail + }).send(); + }, + + createLinks: function(data) { + var ul = $("sort_children_{id}".substitute({'id': this.id})); + ul.set("html", ""); + data.links.each(function(link) { + link.id = link.fid; + var li = new Element("li", { + 'style': { + 'margin-left': 0 + } + }); + + var html = "<span style='cursor: move' class='child_status sorthandle'><img src='../img/{icon}' style='width: 12px; height:12px;'/></span>\n".substitute({'icon': link.icon}); + html += "<span style='font-size: 15px'><a href=\"{url}\" target=\"_blank\">{name}</a></span><br /><div class='child_secrow'>".substitute({'url': link.url, 'name': link.name}); + html += "<span class='child_status'>{statusmsg}</span>{error} ".substitute({'statusmsg': link.statusmsg, 'error':link.error}); + html += "<span class='child_status'>{format_size}</span>".substitute({'format_size': link.format_size}); + html += "<span class='child_status'>{plugin}</span> ".substitute({'plugin': link.plugin}); + html += "<img title='{{_(\"Delete Link\")}}' style='cursor: pointer;' width='10px' height='10px' src='../img/delete.png' /> "; + html += "<img title='{{_(\"Restart Link\")}}' style='cursor: pointer;margin-left: -4px' width='10px' height='10px' src='../img/arrow_refresh.png' /></div>"; + + var div = new Element("div", { + 'id': "file_" + link.id, + 'class': "child", + 'html': html + }); + + li.store("order", link.order); + li.store("lid", link.id); + + li.adopt(div); + ul.adopt(li); + }); + this.sorts = new Sortables(ul, { + constrain: false, + clone: true, + revert: true, + opacity: 0.4, + handle: ".sorthandle", + onComplete: this.saveSort.bind(this) + }); + this.registerLinkEvents(); + this.linksLoaded = true; + indicateFinish(); + this.toggle(); + }, + + registerLinkEvents: function() { + this.ele.getElements('.child').each(function(child) { + var lid = child.get('id').match(/[0-9]+/); + var imgs = child.getElements('.child_secrow img'); + imgs[0].addEvent('click', function(e) { + new Request({ + method: 'get', + url: '/api/deleteFiles/[' + this + "]", + onSuccess: function() { + $('file_' + this).nix() + }.bind(this), + onFailure: indicateFail + }).send(); + }.bind(lid)); + + imgs[1].addEvent('click', function(e) { + new Request({ + method: 'get', + url: '/api/restartFile/' + this, + onSuccess: function() { + var ele = $('file_' + this); + var imgs = ele.getElements("img"); + imgs[0].set("src", "../img/status_queue.png"); + var spans = ele.getElements(".child_status"); + spans[1].set("html", "queued"); + indicateSuccess(); + }.bind(this), + onFailure: indicateFail + }).send(); + }.bind(lid)); + }); + }, + + toggle: function() { + var child = this.ele.getElement('.children'); + if (child.getStyle('display') == "block") { + child.dissolve(); + } else { + if (!this.linksLoaded) { + this.loadLinks(); + } else { + child.reveal(); + } + } + }, + + + deletePackage: function(event) { + indicateLoad(); + new Request({ + method: 'get', + url: '/api/deletePackages/[' + this.id + "]", + onSuccess: function() { + this.ele.nix(); + indicateFinish(); + }.bind(this), + onFailure: indicateFail + }).send(); + //hide_pack(); + event.stop(); + }, + + restartPackage: function(event) { + indicateLoad(); + new Request({ + method: 'get', + url: '/api/restartPackage/' + this.id, + onSuccess: function() { + this.close(); + indicateSuccess(); + }.bind(this), + onFailure: indicateFail + }).send(); + event.stop(); + }, + + close: function() { + var child = this.ele.getElement('.children'); + if (child.getStyle('display') == "block") { + child.dissolve(); + } + var ul = $("sort_children_{id}".substitute({'id': this.id})); + ul.erase("html"); + this.linksLoaded = false; + }, + + movePackage: function(event) { + indicateLoad(); + new Request({ + method: 'get', + url: '/json/move_package/' + ((this.ui.type + 1) % 2) + "/" + this.id, + onSuccess: function() { + this.ele.nix(); + indicateFinish(); + }.bind(this), + onFailure: indicateFail + }).send(); + event.stop(); + }, + + editPackage: function(event) { + $("pack_form").removeEvents("submit"); + $("pack_form").addEvent("submit", this.savePackage.bind(this)); + + $("pack_id").set("value", this.id); + $("pack_name").set("value", this.name.get("text")); + $("pack_folder").set("value", this.folder.get("text")); + $("pack_pws").set("value", this.password.get("text")); + + root.packageBox.open(); + event.stop(); + }, + + savePackage: function(event) { + $("pack_form").send(); + this.name.set("text", $("pack_name").get("value")); + this.folder.set("text", $("pack_folder").get("value")); + this.password.set("text", $("pack_pws").get("value")); + root.packageBox.close(); + event.stop(); + }, + + saveSort: function(ele, copy) { + var order = []; + this.sorts.serialize(function(li, pos) { + if (li == ele && ele.retrieve("order") != pos) { + order.push(ele.retrieve("lid") + "|" + pos) + } + li.store("order", pos) + }); + if (order.length > 0) { + indicateLoad(); + new Request.JSON({ + method: 'get', + url: '/json/link_order/' + order[0], + onSuccess: indicateFinish, + onFailure: indicateFail + }).send(); + } + } + +}); diff --git a/pyload/webui/themes/Default/js/settings.coffee b/pyload/webui/themes/Default/js/settings.coffee new file mode 100644 index 000000000..d522741b9 --- /dev/null +++ b/pyload/webui/themes/Default/js/settings.coffee @@ -0,0 +1,107 @@ +root = this + +window.addEvent 'domready', -> + root.accountDialog = new MooDialog {destroyOnHide: false} + root.accountDialog.setContent $ 'account_box' + + new TinyTab $$('#toptabs li a'), $$('#tabs-body > span') + + $$('ul.nav').each (nav) -> + new MooDropMenu nav, { + onOpen: (el) -> el.fade 'in' + onClose: (el) -> el.fade 'out' + onInitialize: (el) -> el.fade('hide').set 'tween', {duration:500} + } + + new SettingsUI() + + +class SettingsUI + constructor: -> + @menu = $$ "#general-menu li" + @menu.append $$ "#plugin-menu li" + + @name = $ "tabsback" + @general = $ "general_form_content" + @plugin = $ "plugin_form_content" + + el.addEvent 'click', @menuClick.bind(this) for el in @menu + + $("general|submit").addEvent "click", @configSubmit.bind(this) + $("plugin|submit").addEvent "click", @configSubmit.bind(this) + + $("account_add").addEvent "click", (e) -> + root.accountDialog.open() + e.stop() + + $("account_reset").addEvent "click", (e) -> + root.accountDialog.close() + + $("account_add_button").addEvent "click", @addAccount.bind(this) + $("account_submit").addEvent "click", @submitAccounts.bind(this) + + + menuClick: (e) -> + [category, section] = e.target.get("id").split("|") + name = e.target.get "text" + + + target = if category is "general" then @general else @plugin + target.dissolve() + + new Request({ + "method" : "get" + "url" : "/json/load_config/#{category}/#{section}" + 'onSuccess': (data) => + target.set "html", data + target.reveal() + this.name.set "text", name + }).send() + + + configSubmit: (e) -> + category = e.target.get("id").split("|")[0] + form = $("#{category}_form") + + form.set "send", { + 'method': "post" + 'url': "/json/save_config/#{category}" + "onSuccess" : -> + root.notify.alert '{{ _("Settings saved.")}}', { + 'className': 'success' + } + 'onFailure': -> + root.notify.alert '{{ _("Error occured.")}}', { + 'className': 'error' + } + } + form.send() + e.stop() + + addAccount: (e) -> + form = $ "add_account_form" + form.set "send", { + 'method': "post" + "onSuccess" : -> window.location.reload() + 'onFailure': -> + root.notify.alert '{{_("Error occured.")}}', { + 'className': 'error' + } + } + + form.send() + e.stop() + + submitAccounts: (e) -> + form = $ "account_form" + form.set "send", { + 'method': "post", + "onSuccess" : -> window.location.reload() + 'onFailure': -> + root.notify.alert('{{ _("Error occured.") }}', { + 'className': 'error' + }) + } + + form.send() + e.stop() diff --git a/pyload/webui/themes/Default/js/settings.min.js b/pyload/webui/themes/Default/js/settings.min.js new file mode 100644 index 000000000..41d1cb25a --- /dev/null +++ b/pyload/webui/themes/Default/js/settings.min.js @@ -0,0 +1,3 @@ +{% autoescape true %} +var SettingsUI,root;var __bind=function(a,b){return function(){return a.apply(b,arguments)}};root=this;window.addEvent("domready",function(){root.accountDialog=new MooDialog({destroyOnHide:false});root.accountDialog.setContent($("account_box"));new TinyTab($$("#toptabs li a"),$$("#tabs-body > span"));$$("ul.nav").each(function(a){return new MooDropMenu(a,{onOpen:function(b){return b.fade("in")},onClose:function(b){return b.fade("out")},onInitialize:function(b){return b.fade("hide").set("tween",{duration:500})}})});return new SettingsUI()});SettingsUI=(function(){function a(){var c,e,b,d;this.menu=$$("#general-menu li");this.menu.append($$("#plugin-menu li"));this.name=$("tabsback");this.general=$("general_form_content");this.plugin=$("plugin_form_content");d=this.menu;for(e=0,b=d.length;e<b;e++){c=d[e];c.addEvent("click",this.menuClick.bind(this))}$("general|submit").addEvent("click",this.configSubmit.bind(this));$("plugin|submit").addEvent("click",this.configSubmit.bind(this));$("account_add").addEvent("click",function(f){root.accountDialog.open();return f.stop()});$("account_reset").addEvent("click",function(f){return root.accountDialog.close()});$("account_add_button").addEvent("click",this.addAccount.bind(this));$("account_submit").addEvent("click",this.submitAccounts.bind(this))}a.prototype.menuClick=function(h){var c,b,g,f,d;d=h.target.get("id").split("|"),c=d[0],g=d[1];b=h.target.get("text");f=c==="general"?this.general:this.plugin;f.dissolve();return new Request({method:"get",url:"/json/load_config/"+c+"/"+g,onSuccess:__bind(function(e){f.set("html",e);f.reveal();return this.name.set("text",b)},this)}).send()};a.prototype.configSubmit=function(d){var c,b;c=d.target.get("id").split("|")[0];b=$(""+c+"_form");b.set("send",{method:"post",url:"/json/save_config/"+c,onSuccess:function(){return root.notify.alert('{{ _("Settings saved.")}}',{className:"success"})},onFailure:function(){return root.notify.alert('{{ _("Error occured.")}}',{className:"error"})}});b.send();return d.stop()};a.prototype.addAccount=function(c){var b;b=$("add_account_form");b.set("send",{method:"post",onSuccess:function(){return window.location.reload()},onFailure:function(){return root.notify.alert('{{_("Error occured.")}}',{className:"error"})}});b.send();return c.stop()};a.prototype.submitAccounts=function(c){var b;b=$("account_form");b.set("send",{method:"post",onSuccess:function(){return window.location.reload()},onFailure:function(){return root.notify.alert('{{ _("Error occured.") }}',{className:"error"})}});b.send();return c.stop()};return a})(); +{% endautoescape %} diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Alert.js b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Alert.js new file mode 100644 index 000000000..1e2d4180b --- /dev/null +++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Alert.js @@ -0,0 +1,45 @@ +/* +--- +name: MooDialog.Alert +description: Creates an Alert dialog +authors: Arian Stolwijk +license: MIT-style license +requires: MooDialog +provides: MooDialog.Alert +... +*/ + + +MooDialog.Alert = new Class({ + + Extends: MooDialog, + + options: { + okText: 'Ok', + focus: true, + textPClass: 'MooDialogAlert' + }, + + initialize: function(msg, options){ + this.parent(options); + + var okButton = new Element('button', { + events: { + click: this.close.bind(this) + }, + text: this.options.okText + }); + + this.setContent( + new Element('p.' + this.options.textPClass, {text: msg}), + new Element('div.buttons').adopt(okButton) + ); + if (this.options.autoOpen) this.open(); + + if (this.options.focus) this.addEvent('show', function(){ + okButton.focus() + }); + + } +}); + diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Confirm.js b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Confirm.js new file mode 100644 index 000000000..16f32e290 --- /dev/null +++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Confirm.js @@ -0,0 +1,80 @@ +/* +--- +name: MooDialog.Confirm +description: Creates an Confirm Dialog +authors: Arian Stolwijk +license: MIT-style license +requires: MooDialog +provides: [MooDialog.Confirm, Element.confirmLinkClick, Element.confirmFormSubmit] +... +*/ + + +MooDialog.Confirm = new Class({ + + Extends: MooDialog, + + options: { + okText: 'Ok', + cancelText: 'Cancel', + focus: true, + textPClass: 'MooDialogConfirm' + }, + + initialize: function(msg, fn, fn1, options){ + this.parent(options); + var emptyFn = function(){}, + self = this; + + var buttons = [ + {fn: fn || emptyFn, txt: this.options.okText}, + {fn: fn1 || emptyFn, txt: this.options.cancelText} + ].map(function(button){ + return new Element('button', { + events: { + click: function(){ + button.fn(); + self.close(); + } + }, + text: button.txt + }); + }); + + this.setContent( + new Element('p.' + this.options.textPClass, {text: msg}), + new Element('div.buttons').adopt(buttons) + ); + if (this.options.autoOpen) this.open(); + + if(this.options.focus) this.addEvent('show', function(){ + buttons[1].focus(); + }); + + } +}); + + +Element.implement({ + + confirmLinkClick: function(msg, options){ + this.addEvent('click', function(e){ + e.stop(); + new MooDialog.Confirm(msg, function(){ + location.href = this.get('href'); + }.bind(this), null, options) + }); + return this; + }, + + confirmFormSubmit: function(msg, options){ + this.addEvent('submit', function(e){ + e.stop(); + new MooDialog.Confirm(msg, function(){ + this.submit(); + }.bind(this), null, options) + }.bind(this)); + return this; + } + +}); diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Error.js b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Error.js new file mode 100644 index 000000000..d32e30bce --- /dev/null +++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Error.js @@ -0,0 +1,21 @@ +/* +--- +name: MooDialog.Error +description: Creates an Error dialog +authors: Arian Stolwijk +license: MIT-style license +requires: MooDialog +provides: MooDialog.Error +... +*/ + + +MooDialog.Error = new Class({ + + Extends: MooDialog.Alert, + + options: { + textPClass: 'MooDialogError' + } + +}); diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Fx.js b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Fx.js new file mode 100644 index 000000000..353d947f5 --- /dev/null +++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Fx.js @@ -0,0 +1,47 @@ +/* +--- +name: MooDialog.Fx +description: Overwrite the default events so the Dialogs are using Fx on open and close +authors: Arian Stolwijk +license: MIT-style license +requires: [Core/Fx.Tween, Overlay] +provides: MooDialog.Fx +... +*/ + + +MooDialog.implement('options', { + + duration: 400, + closeOnOverlayClick: true, + + onInitialize: function(wrapper){ + this.fx = new Fx.Tween(wrapper, { + property: 'opacity', + duration: this.options.duration + }).set(0); + this.overlay = new Overlay(this.options.inject, { + duration: this.options.duration + }); + if (this.options.closeOnOverlayClick) this.overlay.addEvent('click', this.close.bind(this)); + + this.addEvent('hide', function(){ + if (this.options.destroyOnHide) this.overlay.overlay.destroy(); + }.bind(this)); + }, + + onBeforeOpen: function(wrapper){ + this.overlay.open(); + this.fx.start(1).chain(function(){ + this.fireEvent('show'); + }.bind(this)); + }, + + onBeforeClose: function(wrapper){ + this.overlay.close(); + this.fx.start(0).chain(function(){ + this.fireEvent('hide'); + }.bind(this)); + } + +}); diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.IFrame.js b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.IFrame.js new file mode 100644 index 000000000..029bf1f09 --- /dev/null +++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.IFrame.js @@ -0,0 +1,33 @@ +/* +--- +name: MooDialog.IFrame +description: Opens an IFrame in a MooDialog +authors: Arian Stolwijk +license: MIT-style license +requires: MooDialog +provides: MooDialog.IFrame +... +*/ + + +MooDialog.IFrame = new Class({ + + Extends: MooDialog, + + options: { + useScrollBar: true + }, + + initialize: function(url, options){ + this.parent(options); + + this.setContent( + new Element('iframe', { + src: url, + frameborder: 0, + scrolling: this.options.useScrollBar ? 'auto' : 'no' + }) + ); + if (this.options.autoOpen) this.open(); + } +}); diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Prompt.js b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Prompt.js new file mode 100644 index 000000000..c693e4a58 --- /dev/null +++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Prompt.js @@ -0,0 +1,48 @@ +/* +--- +name: MooDialog.Prompt +description: Creates a Prompt dialog +authors: Arian Stolwijk +license: MIT-style license +requires: MooDialog +provides: MooDialog.Prompt +... +*/ + + +MooDialog.Prompt = new Class({ + + Extends: MooDialog, + + options: { + okText: 'Ok', + focus: true, + textPClass: 'MooDialogPrompt', + defaultValue: '' + }, + + initialize: function(msg, fn, options){ + this.parent(options); + if (!fn) fn = function(){}; + + var textInput = new Element('input.textInput', {type: 'text', value: this.options.defaultValue}), + submitButton = new Element('input[type=submit]', {value: this.options.okText}), + formEvents = { + submit: function(e){ + e.stop(); + fn(textInput.get('value')); + this.close(); + }.bind(this) + }; + + this.setContent( + new Element('p.' + this.options.textPClass, {text: msg}), + new Element('form.buttons', {events: formEvents}).adopt(textInput, submitButton) + ); + if (this.options.autoOpen) this.open(); + + if (this.options.focus) this.addEvent('show', function(){ + textInput.focus(); + }); + } +}); diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Request.js b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Request.js new file mode 100644 index 000000000..7b8eb23c4 --- /dev/null +++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.Request.js @@ -0,0 +1,37 @@ +/* +--- +name: MooDialog.Request +description: Loads Data into a Dialog with Request +authors: Arian Stolwijk +license: MIT-style license +requires: [MooDialog, Core/Request.HTML] +provides: MooDialog.Request +... +*/ + +MooDialog.Request = new Class({ + + Extends: MooDialog, + + initialize: function(url, requestOptions, options){ + this.parent(options); + this.requestOptions = requestOptions || {}; + + this.addEvent('open', function(){ + var request = new Request.HTML(this.requestOptions).addEvent('success', function(text){ + this.setContent(text); + }.bind(this)).send({ + url: url + }); + }.bind(this)); + + if (this.options.autoOpen) this.open(); + + }, + + setRequestOptions: function(options){ + this.requestOptions = Object.merge(this.requestOptions, options); + return this; + } + +}); diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.js b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.js new file mode 100644 index 000000000..45a52496f --- /dev/null +++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/MooDialog.js @@ -0,0 +1,140 @@ +/* +--- +name: MooDialog +description: The base class of MooDialog +authors: Arian Stolwijk +license: MIT-style license +requires: [Core/Class, Core/Element, Core/Element.Style, Core/Element.Event] +provides: [MooDialog, Element.MooDialog] +... +*/ + + +var MooDialog = new Class({ + + Implements: [Options, Events], + + options: { + 'class': 'MooDialog', + title: null, + scroll: true, // IE + forceScroll: false, + useEscKey: true, + destroyOnHide: true, + autoOpen: true, + closeButton: true, + onInitialize: function(){ + this.wrapper.setStyle('display', 'none'); + }, + onBeforeOpen: function(){ + this.wrapper.setStyle('display', 'block'); + this.fireEvent('show'); + }, + onBeforeClose: function(){ + this.wrapper.setStyle('display', 'none'); + this.fireEvent('hide'); + }/*, + onOpen: function(){}, + onClose: function(){}, + onShow: function(){}, + onHide: function(){}, + onInitialize: function(wrapper){}, + onContentChange: function(content){}*/ + }, + + initialize: function(options){ + this.setOptions(options); + this.options.inject = this.options.inject || document.body; + options = this.options; + + var wrapper = this.wrapper = new Element('div.' + options['class'].replace(' ', '.')).inject(options.inject); + this.content = new Element('div.content').inject(wrapper); + + if (options.title){ + this.title = new Element('div.title').set('text', options.title).inject(wrapper); + wrapper.addClass('MooDialogTitle'); + } + + if (options.closeButton){ + this.closeButton = new Element('a.close', { + events: {click: this.close.bind(this)} + }).inject(wrapper); + } + + + /*<ie6>*/// IE 6 scroll + if ((options.scroll && Browser.ie6) || options.forceScroll){ + wrapper.setStyle('position', 'absolute'); + var position = wrapper.getPosition(options.inject); + window.addEvent('scroll', function(){ + var scroll = document.getScroll(); + wrapper.setPosition({ + x: position.x + scroll.x, + y: position.y + scroll.y + }); + }); + } + /*</ie6>*/ + + if (options.useEscKey){ + // Add event for the esc key + document.addEvent('keydown', function(e){ + if (e.key == 'esc') this.close(); + }.bind(this)); + } + + this.addEvent('hide', function(){ + if (options.destroyOnHide) this.destroy(); + }.bind(this)); + + this.fireEvent('initialize', wrapper); + }, + + setContent: function(){ + var content = Array.from(arguments); + if (content.length == 1) content = content[0]; + + this.content.empty(); + + var type = typeOf(content); + if (['string', 'number'].contains(type)) this.content.set('text', content); + else this.content.adopt(content); + + this.fireEvent('contentChange', this.content); + + return this; + }, + + open: function(){ + this.fireEvent('beforeOpen', this.wrapper).fireEvent('open'); + this.opened = true; + return this; + }, + + close: function(){ + this.fireEvent('beforeClose', this.wrapper).fireEvent('close'); + this.opened = false; + return this; + }, + + destroy: function(){ + this.wrapper.destroy(); + }, + + toElement: function(){ + return this.wrapper; + } + +}); + + +Element.implement({ + + MooDialog: function(options){ + this.store('MooDialog', + new MooDialog(options).setContent(this).open() + ); + return this; + } + +}); diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDialog/Overlay.js b/pyload/webui/themes/Default/lib/MooTools/MooDialog/Overlay.js new file mode 100644 index 000000000..35ab19c48 --- /dev/null +++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/Overlay.js @@ -0,0 +1,137 @@ +/* +--- + +name: Overlay + +authors: + - David Walsh (http://davidwalsh.name) + +license: + - MIT-style license + +requires: [Core/Class, Core/Element.Style, Core/Element.Event, Core/Element.Dimensions, Core/Fx.Tween] + +provides: + - Overlay +... +*/ + +var Overlay = new Class({ + + Implements: [Options, Events], + + options: { + id: 'overlay', + color: '#000', + duration: 500, + opacity: 0.5, + zIndex: 5000/*, + onClick: function(){}, + onClose: function(){}, + onHide: function(){}, + onOpen: function(){}, + onShow: function(){} + */ + }, + + initialize: function(container, options){ + this.setOptions(options); + this.container = document.id(container); + + this.bound = { + 'window': { + resize: this.resize.bind(this), + scroll: this.scroll.bind(this) + }, + overlayClick: this.overlayClick.bind(this), + tweenStart: this.tweenStart.bind(this), + tweenComplete: this.tweenComplete.bind(this) + }; + + this.build().attach(); + }, + + build: function(){ + this.overlay = new Element('div', { + id: this.options.id, + styles: { + position: (Browser.ie6) ? 'absolute' : 'fixed', + background: this.options.color, + left: 0, + top: 0, + 'z-index': this.options.zIndex, + opacity: 0 + } + }).inject(this.container); + this.tween = new Fx.Tween(this.overlay, { + duration: this.options.duration, + link: 'cancel', + property: 'opacity' + }); + return this; + }.protect(), + + attach: function(){ + window.addEvents(this.bound.window); + this.overlay.addEvent('click', this.bound.overlayClick); + this.tween.addEvents({ + onStart: this.bound.tweenStart, + onComplete: this.bound.tweenComplete + }); + return this; + }, + + detach: function(){ + var args = Array.prototype.slice.call(arguments); + args.each(function(item){ + if(item == 'window') window.removeEvents(this.bound.window); + if(item == 'overlay') this.overlay.removeEvent('click', this.bound.overlayClick); + }, this); + return this; + }, + + overlayClick: function(){ + this.fireEvent('click'); + return this; + }, + + tweenStart: function(){ + this.overlay.setStyles({ + width: '100%', + height: this.container.getScrollSize().y, + visibility: 'visible' + }); + return this; + }, + + tweenComplete: function(){ + var event = this.overlay.getStyle('opacity') == this.options.opacity ? 'show' : 'hide'; + if (event == 'hide') this.overlay.setStyle('visibility', 'hidden'); + return this; + }, + + open: function(){ + this.fireEvent('open'); + this.tween.start(this.options.opacity); + return this; + }, + + close: function(){ + this.fireEvent('close'); + this.tween.start(0); + return this; + }, + + resize: function(){ + this.fireEvent('resize'); + this.overlay.setStyle('height', this.container.getScrollSize().y); + return this; + }, + + scroll: function(){ + this.fireEvent('scroll'); + if (Browser.ie6) this.overlay.setStyle('left', window.getScroll().x); + return this; + } + +}); diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/MooDialog.css b/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/MooDialog.css new file mode 100644 index 000000000..c88773ae9 --- /dev/null +++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/MooDialog.css @@ -0,0 +1,95 @@ +/* Created by Arian Stolwijk <http://www.aryweb.nl> */ + +.MooDialog { + position: fixed; + width: 300px; + height: 100px; + top: 50%; + left: 50%; + margin: -150px 0 0 -150px; + padding: 10px; + z-index: 50000; + + background: #eef5f8; + color: black; + border-radius: 7px; + -moz-border-radius: 7px; + -webkit-border-radius: 7px; + border-radius: 7px; + -moz-box-shadow: 1px 1px 5px rgba(0,0,0,0.8); + -webkit-box-shadow: 1px 1px 5px rgba(0,0,0,0.8); + box-shadow: 1px 1px 5px rgba(0,0,0,0.8); +} + +.MooDialogTitle { + padding-top: 30px; +} + +.MooDialog .content { + height: 100px; +} + +.MooDialog .title { + position: absolute; + top: 0; + left: 0; + right: 0; + padding: 3px 20px; + + background: #b7c4dc; + border-bottom: 1px solid #a1aec5; + font-weight: bold; + text-shadow: 1px 1px 0 #fff; + color: black; + border-radius: 7px; + -moz-border-radius: 7px; + -webkit-border-radius: 7px; +} + +.MooDialog .close { + position: absolute; + width: 16px; + height: 16px; + top: -5px; + left: -5px; + + background: url(dialog-close.png) no-repeat; + display: block; + cursor: pointer; +} + +.MooDialog .buttons { + margin: 0; + padding: 0; + border: 0; + background: none; + text-align: right; +} + +.MooDialog .iframe { + width: 100%; + height: 100%; +} + +.MooDialog .textInput { + width: 200px; + float: left; +} + +.MooDialog .MooDialogAlert, +.MooDialog .MooDialogConfirm, +.MooDialog .MooDialogPrompt, +.MooDialog .MooDialogError { + padding-left: 40px; + min-height: 40px; + background: url(dialog-warning.png) no-repeat; +} + +.MooDialog .MooDialogConfirm, +.MooDialog .MooDialogPrompt { + background: url(dialog-question.png) no-repeat; +} + +.MooDialog .MooDialogError { + background: url(dialog-error.png) no-repeat; +} diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-close.png b/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-close.png Binary files differnew file mode 100644 index 000000000..81ebb88b2 --- /dev/null +++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-close.png diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-error.png b/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-error.png Binary files differnew file mode 100644 index 000000000..d70328403 --- /dev/null +++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-error.png diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-question.png b/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-question.png Binary files differnew file mode 100644 index 000000000..b0af3db5b --- /dev/null +++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-question.png diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-warning.png b/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-warning.png Binary files differnew file mode 100644 index 000000000..aad64d4be --- /dev/null +++ b/pyload/webui/themes/Default/lib/MooTools/MooDialog/css/dialog-warning.png diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDropMenu/MooDropMenu.js b/pyload/webui/themes/Default/lib/MooTools/MooDropMenu/MooDropMenu.js new file mode 100644 index 000000000..ac0fa1874 --- /dev/null +++ b/pyload/webui/themes/Default/lib/MooTools/MooDropMenu/MooDropMenu.js @@ -0,0 +1,86 @@ +/* +--- +description: This provides a simple Drop Down menu with infinit levels + +license: MIT-style + +authors: +- Arian Stolwijk + +requires: + - Core/Class.Extras + - Core/Element.Event + - Core/Selectors + +provides: [MooDropMenu, Element.MooDropMenu] + +... +*/ + +var MooDropMenu = new Class({ + + Implements: [Options, Events], + + options: { + onOpen: function(el){ + el.removeClass('close').addClass('open'); + }, + onClose: function(el){ + el.removeClass('open').addClass('close'); + }, + onInitialize: function(el){ + el.removeClass('open').addClass('close'); + }, + mouseoutDelay: 200, + mouseoverDelay: 0, + listSelector: 'ul', + itemSelector: 'li', + openEvent: 'mouseenter', + closeEvent: 'mouseleave' + }, + + initialize: function(menu, options, level){ + this.setOptions(options); + options = this.options; + + var menu = this.menu = document.id(menu); + + menu.getElements(options.itemSelector + ' > ' + options.listSelector).each(function(el){ + + this.fireEvent('initialize', el); + + var parent = el.getParent(options.itemSelector), + timer; + + parent.addEvent(options.openEvent, function(){ + parent.store('DropDownOpen', true); + + clearTimeout(timer); + if (options.mouseoverDelay) timer = this.fireEvent.delay(options.mouseoverDelay, this, ['open', el]); + else this.fireEvent('open', el); + + }.bind(this)).addEvent(options.closeEvent, function(){ + parent.store('DropDownOpen', false); + + clearTimeout(timer); + timer = (function(){ + if (!parent.retrieve('DropDownOpen')) this.fireEvent('close', el); + }).delay(options.mouseoutDelay, this); + + }.bind(this)); + + }, this); + }, + + toElement: function(){ + return this.menu + } + +}); + +/* So you can do like this $('nav').MooDropMenu(); or even $('nav').MooDropMenu().setStyle('border',1); */ +Element.implement({ + MooDropMenu: function(options){ + return this.store('MooDropMenu', new MooDropMenu(this, options)); + } +}); diff --git a/pyload/webui/themes/Default/lib/MooTools/MooDropMenu/css/MooDropMenu.css b/pyload/webui/themes/Default/lib/MooTools/MooDropMenu/css/MooDropMenu.css new file mode 100644 index 000000000..e08c2f9fa --- /dev/null +++ b/pyload/webui/themes/Default/lib/MooTools/MooDropMenu/css/MooDropMenu.css @@ -0,0 +1,66 @@ +#nav { + margin: 0; + padding: 0; + list-style: none; +} + +#nav > li { + background: #F5F5F5; + border-bottom: 1px solid #aaa; +} + +#nav li { + position: relative; + float: left; + padding: 5px; +} + + +#nav ul { + position: absolute; + top: 26px; + left: 10px; + margin: 0; + padding: 0; + list-style: none; + width: 136px; + border: 1px solid #AAA; + background: #f1f1f1; + -webkit-box-shadow: 1px 1px 5px #AAA; + -moz-box-shadow: 1px 1px 5px #AAA; + box-shadow: 1px 1px 5px #AAA; +} + + +#nav .open { + display: block; +} + +#nav .close { + display: none; +} + +#nav ul li { + float: none; + padding: 0; +} + +#nav ul li a { + width: 130px; + _width: 127px; + background: #f1f1f1; + padding: 3px; + display: block; + _float: left; + font-weight: normal; +} + +#nav ul li a:hover { + background: #CDCDCD; +} + +#nav ul ul { + left: 137px; + _left: 0; + top: 0; +} diff --git a/pyload/webui/themes/Default/lib/MooTools/MooTools-Core.js b/pyload/webui/themes/Default/lib/MooTools/MooTools-Core.js new file mode 100644 index 000000000..e0bea6df6 --- /dev/null +++ b/pyload/webui/themes/Default/lib/MooTools/MooTools-Core.js @@ -0,0 +1,6068 @@ +/* MooTools: the javascript framework. license: MIT-style license. copyright: Copyright (c) 2006-2015 [Valerio Proietti](http://mad4milk.net/).*/ +/* +Web Build: http://mootools.net/core/builder/e426a9ae7167c5807b173d5deff673fc +*/ +/* +--- + +name: Core + +description: The heart of MooTools. + +license: MIT-style license. + +copyright: Copyright (c) 2006-2014 [Valerio Proietti](http://mad4milk.net/). + +authors: The MooTools production team (http://mootools.net/developers/) + +inspiration: + - Class implementation inspired by [Base.js](http://dean.edwards.name/weblog/2006/03/base/) Copyright (c) 2006 Dean Edwards, [GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php) + - Some functionality inspired by [Prototype.js](http://prototypejs.org) Copyright (c) 2005-2007 Sam Stephenson, [MIT License](http://opensource.org/licenses/mit-license.php) + +provides: [Core, MooTools, Type, typeOf, instanceOf, Native] + +... +*/ +/*! MooTools: the javascript framework. license: MIT-style license. copyright: Copyright (c) 2006-2014 [Valerio Proietti](http://mad4milk.net/).*/ +(function(){ + +this.MooTools = { + version: '1.5.1', + build: '0542c135fdeb7feed7d9917e01447a408f22c876' +}; + +// typeOf, instanceOf + +var typeOf = this.typeOf = function(item){ + if (item == null) return 'null'; + if (item.$family != null) return item.$family(); + + if (item.nodeName){ + if (item.nodeType == 1) return 'element'; + if (item.nodeType == 3) return (/\S/).test(item.nodeValue) ? 'textnode' : 'whitespace'; + } else if (typeof item.length == 'number'){ + if ('callee' in item) return 'arguments'; + if ('item' in item) return 'collection'; + } + + return typeof item; +}; + +var instanceOf = this.instanceOf = function(item, object){ + if (item == null) return false; + var constructor = item.$constructor || item.constructor; + while (constructor){ + if (constructor === object) return true; + constructor = constructor.parent; + } + /*<ltIE8>*/ + if (!item.hasOwnProperty) return false; + /*</ltIE8>*/ + return item instanceof object; +}; + +// Function overloading + +var Function = this.Function; + +var enumerables = true; +for (var i in {toString: 1}) enumerables = null; +if (enumerables) enumerables = ['hasOwnProperty', 'valueOf', 'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString', 'toString', 'constructor']; + +Function.prototype.overloadSetter = function(usePlural){ + var self = this; + return function(a, b){ + if (a == null) return this; + if (usePlural || typeof a != 'string'){ + for (var k in a) self.call(this, k, a[k]); + if (enumerables) for (var i = enumerables.length; i--;){ + k = enumerables[i]; + if (a.hasOwnProperty(k)) self.call(this, k, a[k]); + } + } else { + self.call(this, a, b); + } + return this; + }; +}; + +Function.prototype.overloadGetter = function(usePlural){ + var self = this; + return function(a){ + var args, result; + if (typeof a != 'string') args = a; + else if (arguments.length > 1) args = arguments; + else if (usePlural) args = [a]; + if (args){ + result = {}; + for (var i = 0; i < args.length; i++) result[args[i]] = self.call(this, args[i]); + } else { + result = self.call(this, a); + } + return result; + }; +}; + +Function.prototype.extend = function(key, value){ + this[key] = value; +}.overloadSetter(); + +Function.prototype.implement = function(key, value){ + this.prototype[key] = value; +}.overloadSetter(); + +// From + +var slice = Array.prototype.slice; + +Function.from = function(item){ + return (typeOf(item) == 'function') ? item : function(){ + return item; + }; +}; + +Array.from = function(item){ + if (item == null) return []; + return (Type.isEnumerable(item) && typeof item != 'string') ? (typeOf(item) == 'array') ? item : slice.call(item) : [item]; +}; + +Number.from = function(item){ + var number = parseFloat(item); + return isFinite(number) ? number : null; +}; + +String.from = function(item){ + return item + ''; +}; + +// hide, protect + +Function.implement({ + + hide: function(){ + this.$hidden = true; + return this; + }, + + protect: function(){ + this.$protected = true; + return this; + } + +}); + +// Type + +var Type = this.Type = function(name, object){ + if (name){ + var lower = name.toLowerCase(); + var typeCheck = function(item){ + return (typeOf(item) == lower); + }; + + Type['is' + name] = typeCheck; + if (object != null){ + object.prototype.$family = (function(){ + return lower; + }).hide(); + + } + } + + if (object == null) return null; + + object.extend(this); + object.$constructor = Type; + object.prototype.$constructor = object; + + return object; +}; + +var toString = Object.prototype.toString; + +Type.isEnumerable = function(item){ + return (item != null && typeof item.length == 'number' && toString.call(item) != '[object Function]' ); +}; + +var hooks = {}; + +var hooksOf = function(object){ + var type = typeOf(object.prototype); + return hooks[type] || (hooks[type] = []); +}; + +var implement = function(name, method){ + if (method && method.$hidden) return; + + var hooks = hooksOf(this); + + for (var i = 0; i < hooks.length; i++){ + var hook = hooks[i]; + if (typeOf(hook) == 'type') implement.call(hook, name, method); + else hook.call(this, name, method); + } + + var previous = this.prototype[name]; + if (previous == null || !previous.$protected) this.prototype[name] = method; + + if (this[name] == null && typeOf(method) == 'function') extend.call(this, name, function(item){ + return method.apply(item, slice.call(arguments, 1)); + }); +}; + +var extend = function(name, method){ + if (method && method.$hidden) return; + var previous = this[name]; + if (previous == null || !previous.$protected) this[name] = method; +}; + +Type.implement({ + + implement: implement.overloadSetter(), + + extend: extend.overloadSetter(), + + alias: function(name, existing){ + implement.call(this, name, this.prototype[existing]); + }.overloadSetter(), + + mirror: function(hook){ + hooksOf(this).push(hook); + return this; + } + +}); + +new Type('Type', Type); + +// Default Types + +var force = function(name, object, methods){ + var isType = (object != Object), + prototype = object.prototype; + + if (isType) object = new Type(name, object); + + for (var i = 0, l = methods.length; i < l; i++){ + var key = methods[i], + generic = object[key], + proto = prototype[key]; + + if (generic) generic.protect(); + if (isType && proto) object.implement(key, proto.protect()); + } + + if (isType){ + var methodsEnumerable = prototype.propertyIsEnumerable(methods[0]); + object.forEachMethod = function(fn){ + if (!methodsEnumerable) for (var i = 0, l = methods.length; i < l; i++){ + fn.call(prototype, prototype[methods[i]], methods[i]); + } + for (var key in prototype) fn.call(prototype, prototype[key], key); + }; + } + + return force; +}; + +force('String', String, [ + 'charAt', 'charCodeAt', 'concat', 'contains', 'indexOf', 'lastIndexOf', 'match', 'quote', 'replace', 'search', + 'slice', 'split', 'substr', 'substring', 'trim', 'toLowerCase', 'toUpperCase' +])('Array', Array, [ + 'pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift', 'concat', 'join', 'slice', + 'indexOf', 'lastIndexOf', 'filter', 'forEach', 'every', 'map', 'some', 'reduce', 'reduceRight' +])('Number', Number, [ + 'toExponential', 'toFixed', 'toLocaleString', 'toPrecision' +])('Function', Function, [ + 'apply', 'call', 'bind' +])('RegExp', RegExp, [ + 'exec', 'test' +])('Object', Object, [ + 'create', 'defineProperty', 'defineProperties', 'keys', + 'getPrototypeOf', 'getOwnPropertyDescriptor', 'getOwnPropertyNames', + 'preventExtensions', 'isExtensible', 'seal', 'isSealed', 'freeze', 'isFrozen' +])('Date', Date, ['now']); + +Object.extend = extend.overloadSetter(); + +Date.extend('now', function(){ + return +(new Date); +}); + +new Type('Boolean', Boolean); + +// fixes NaN returning as Number + +Number.prototype.$family = function(){ + return isFinite(this) ? 'number' : 'null'; +}.hide(); + +// Number.random + +Number.extend('random', function(min, max){ + return Math.floor(Math.random() * (max - min + 1) + min); +}); + +// forEach, each + +var hasOwnProperty = Object.prototype.hasOwnProperty; +Object.extend('forEach', function(object, fn, bind){ + for (var key in object){ + if (hasOwnProperty.call(object, key)) fn.call(bind, object[key], key, object); + } +}); + +Object.each = Object.forEach; + +Array.implement({ + + /*<!ES5>*/ + forEach: function(fn, bind){ + for (var i = 0, l = this.length; i < l; i++){ + if (i in this) fn.call(bind, this[i], i, this); + } + }, + /*</!ES5>*/ + + each: function(fn, bind){ + Array.forEach(this, fn, bind); + return this; + } + +}); + +// Array & Object cloning, Object merging and appending + +var cloneOf = function(item){ + switch (typeOf(item)){ + case 'array': return item.clone(); + case 'object': return Object.clone(item); + default: return item; + } +}; + +Array.implement('clone', function(){ + var i = this.length, clone = new Array(i); + while (i--) clone[i] = cloneOf(this[i]); + return clone; +}); + +var mergeOne = function(source, key, current){ + switch (typeOf(current)){ + case 'object': + if (typeOf(source[key]) == 'object') Object.merge(source[key], current); + else source[key] = Object.clone(current); + break; + case 'array': source[key] = current.clone(); break; + default: source[key] = current; + } + return source; +}; + +Object.extend({ + + merge: function(source, k, v){ + if (typeOf(k) == 'string') return mergeOne(source, k, v); + for (var i = 1, l = arguments.length; i < l; i++){ + var object = arguments[i]; + for (var key in object) mergeOne(source, key, object[key]); + } + return source; + }, + + clone: function(object){ + var clone = {}; + for (var key in object) clone[key] = cloneOf(object[key]); + return clone; + }, + + append: function(original){ + for (var i = 1, l = arguments.length; i < l; i++){ + var extended = arguments[i] || {}; + for (var key in extended) original[key] = extended[key]; + } + return original; + } + +}); + +// Object-less types + +['Object', 'WhiteSpace', 'TextNode', 'Collection', 'Arguments'].each(function(name){ + new Type(name); +}); + +// Unique ID + +var UID = Date.now(); + +String.extend('uniqueID', function(){ + return (UID++).toString(36); +}); + + + +})(); + +/* +--- + +name: Array + +description: Contains Array Prototypes like each, contains, and erase. + +license: MIT-style license. + +requires: [Type] + +provides: Array + +... +*/ + +Array.implement({ + + /*<!ES5>*/ + every: function(fn, bind){ + for (var i = 0, l = this.length >>> 0; i < l; i++){ + if ((i in this) && !fn.call(bind, this[i], i, this)) return false; + } + return true; + }, + + filter: function(fn, bind){ + var results = []; + for (var value, i = 0, l = this.length >>> 0; i < l; i++) if (i in this){ + value = this[i]; + if (fn.call(bind, value, i, this)) results.push(value); + } + return results; + }, + + indexOf: function(item, from){ + var length = this.length >>> 0; + for (var i = (from < 0) ? Math.max(0, length + from) : from || 0; i < length; i++){ + if (this[i] === item) return i; + } + return -1; + }, + + map: function(fn, bind){ + var length = this.length >>> 0, results = Array(length); + for (var i = 0; i < length; i++){ + if (i in this) results[i] = fn.call(bind, this[i], i, this); + } + return results; + }, + + some: function(fn, bind){ + for (var i = 0, l = this.length >>> 0; i < l; i++){ + if ((i in this) && fn.call(bind, this[i], i, this)) return true; + } + return false; + }, + /*</!ES5>*/ + + clean: function(){ + return this.filter(function(item){ + return item != null; + }); + }, + + invoke: function(methodName){ + var args = Array.slice(arguments, 1); + return this.map(function(item){ + return item[methodName].apply(item, args); + }); + }, + + associate: function(keys){ + var obj = {}, length = Math.min(this.length, keys.length); + for (var i = 0; i < length; i++) obj[keys[i]] = this[i]; + return obj; + }, + + link: function(object){ + var result = {}; + for (var i = 0, l = this.length; i < l; i++){ + for (var key in object){ + if (object[key](this[i])){ + result[key] = this[i]; + delete object[key]; + break; + } + } + } + return result; + }, + + contains: function(item, from){ + return this.indexOf(item, from) != -1; + }, + + append: function(array){ + this.push.apply(this, array); + return this; + }, + + getLast: function(){ + return (this.length) ? this[this.length - 1] : null; + }, + + getRandom: function(){ + return (this.length) ? this[Number.random(0, this.length - 1)] : null; + }, + + include: function(item){ + if (!this.contains(item)) this.push(item); + return this; + }, + + combine: function(array){ + for (var i = 0, l = array.length; i < l; i++) this.include(array[i]); + return this; + }, + + erase: function(item){ + for (var i = this.length; i--;){ + if (this[i] === item) this.splice(i, 1); + } + return this; + }, + + empty: function(){ + this.length = 0; + return this; + }, + + flatten: function(){ + var array = []; + for (var i = 0, l = this.length; i < l; i++){ + var type = typeOf(this[i]); + if (type == 'null') continue; + array = array.concat((type == 'array' || type == 'collection' || type == 'arguments' || instanceOf(this[i], Array)) ? Array.flatten(this[i]) : this[i]); + } + return array; + }, + + pick: function(){ + for (var i = 0, l = this.length; i < l; i++){ + if (this[i] != null) return this[i]; + } + return null; + }, + + hexToRgb: function(array){ + if (this.length != 3) return null; + var rgb = this.map(function(value){ + if (value.length == 1) value += value; + return parseInt(value, 16); + }); + return (array) ? rgb : 'rgb(' + rgb + ')'; + }, + + rgbToHex: function(array){ + if (this.length < 3) return null; + if (this.length == 4 && this[3] == 0 && !array) return 'transparent'; + var hex = []; + for (var i = 0; i < 3; i++){ + var bit = (this[i] - 0).toString(16); + hex.push((bit.length == 1) ? '0' + bit : bit); + } + return (array) ? hex : '#' + hex.join(''); + } + +}); + + + +/* +--- + +name: Function + +description: Contains Function Prototypes like create, bind, pass, and delay. + +license: MIT-style license. + +requires: Type + +provides: Function + +... +*/ + +Function.extend({ + + attempt: function(){ + for (var i = 0, l = arguments.length; i < l; i++){ + try { + return arguments[i](); + } catch (e){} + } + return null; + } + +}); + +Function.implement({ + + attempt: function(args, bind){ + try { + return this.apply(bind, Array.from(args)); + } catch (e){} + + return null; + }, + + /*<!ES5-bind>*/ + bind: function(that){ + var self = this, + args = arguments.length > 1 ? Array.slice(arguments, 1) : null, + F = function(){}; + + var bound = function(){ + var context = that, length = arguments.length; + if (this instanceof bound){ + F.prototype = self.prototype; + context = new F; + } + var result = (!args && !length) + ? self.call(context) + : self.apply(context, args && length ? args.concat(Array.slice(arguments)) : args || arguments); + return context == that ? result : context; + }; + return bound; + }, + /*</!ES5-bind>*/ + + pass: function(args, bind){ + var self = this; + if (args != null) args = Array.from(args); + return function(){ + return self.apply(bind, args || arguments); + }; + }, + + delay: function(delay, bind, args){ + return setTimeout(this.pass((args == null ? [] : args), bind), delay); + }, + + periodical: function(periodical, bind, args){ + return setInterval(this.pass((args == null ? [] : args), bind), periodical); + } + +}); + + + +/* +--- + +name: Number + +description: Contains Number Prototypes like limit, round, times, and ceil. + +license: MIT-style license. + +requires: Type + +provides: Number + +... +*/ + +Number.implement({ + + limit: function(min, max){ + return Math.min(max, Math.max(min, this)); + }, + + round: function(precision){ + precision = Math.pow(10, precision || 0).toFixed(precision < 0 ? -precision : 0); + return Math.round(this * precision) / precision; + }, + + times: function(fn, bind){ + for (var i = 0; i < this; i++) fn.call(bind, i, this); + }, + + toFloat: function(){ + return parseFloat(this); + }, + + toInt: function(base){ + return parseInt(this, base || 10); + } + +}); + +Number.alias('each', 'times'); + +(function(math){ + var methods = {}; + math.each(function(name){ + if (!Number[name]) methods[name] = function(){ + return Math[name].apply(null, [this].concat(Array.from(arguments))); + }; + }); + Number.implement(methods); +})(['abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', 'log', 'max', 'min', 'pow', 'sin', 'sqrt', 'tan']); + +/* +--- + +name: String + +description: Contains String Prototypes like camelCase, capitalize, test, and toInt. + +license: MIT-style license. + +requires: [Type, Array] + +provides: String + +... +*/ + +String.implement({ + + //<!ES6> + contains: function(string, index){ + return (index ? String(this).slice(index) : String(this)).indexOf(string) > -1; + }, + //</!ES6> + + test: function(regex, params){ + return ((typeOf(regex) == 'regexp') ? regex : new RegExp('' + regex, params)).test(this); + }, + + trim: function(){ + return String(this).replace(/^\s+|\s+$/g, ''); + }, + + clean: function(){ + return String(this).replace(/\s+/g, ' ').trim(); + }, + + camelCase: function(){ + return String(this).replace(/-\D/g, function(match){ + return match.charAt(1).toUpperCase(); + }); + }, + + hyphenate: function(){ + return String(this).replace(/[A-Z]/g, function(match){ + return ('-' + match.charAt(0).toLowerCase()); + }); + }, + + capitalize: function(){ + return String(this).replace(/\b[a-z]/g, function(match){ + return match.toUpperCase(); + }); + }, + + escapeRegExp: function(){ + return String(this).replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1'); + }, + + toInt: function(base){ + return parseInt(this, base || 10); + }, + + toFloat: function(){ + return parseFloat(this); + }, + + hexToRgb: function(array){ + var hex = String(this).match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/); + return (hex) ? hex.slice(1).hexToRgb(array) : null; + }, + + rgbToHex: function(array){ + var rgb = String(this).match(/\d{1,3}/g); + return (rgb) ? rgb.rgbToHex(array) : null; + }, + + substitute: function(object, regexp){ + return String(this).replace(regexp || (/\\?\{([^{}]+)\}/g), function(match, name){ + if (match.charAt(0) == '\\') return match.slice(1); + return (object[name] != null) ? object[name] : ''; + }); + } + +}); + + + +/* +--- + +name: Browser + +description: The Browser Object. Contains Browser initialization, Window and Document, and the Browser Hash. + +license: MIT-style license. + +requires: [Array, Function, Number, String] + +provides: [Browser, Window, Document] + +... +*/ + +(function(){ + +var document = this.document; +var window = document.window = this; + +var parse = function(ua, platform){ + ua = ua.toLowerCase(); + platform = (platform ? platform.toLowerCase() : ''); + + var UA = ua.match(/(opera|ie|firefox|chrome|trident|crios|version)[\s\/:]([\w\d\.]+)?.*?(safari|(?:rv[\s\/:]|version[\s\/:])([\w\d\.]+)|$)/) || [null, 'unknown', 0]; + + if (UA[1] == 'trident'){ + UA[1] = 'ie'; + if (UA[4]) UA[2] = UA[4]; + } else if (UA[1] == 'crios'){ + UA[1] = 'chrome'; + } + + platform = ua.match(/ip(?:ad|od|hone)/) ? 'ios' : (ua.match(/(?:webos|android)/) || platform.match(/mac|win|linux/) || ['other'])[0]; + if (platform == 'win') platform = 'windows'; + + return { + extend: Function.prototype.extend, + name: (UA[1] == 'version') ? UA[3] : UA[1], + version: parseFloat((UA[1] == 'opera' && UA[4]) ? UA[4] : UA[2]), + platform: platform + }; +}; + +var Browser = this.Browser = parse(navigator.userAgent, navigator.platform); + +if (Browser.name == 'ie'){ + Browser.version = document.documentMode; +} + +Browser.extend({ + Features: { + xpath: !!(document.evaluate), + air: !!(window.runtime), + query: !!(document.querySelector), + json: !!(window.JSON) + }, + parseUA: parse +}); + + + +// Request + +Browser.Request = (function(){ + + var XMLHTTP = function(){ + return new XMLHttpRequest(); + }; + + var MSXML2 = function(){ + return new ActiveXObject('MSXML2.XMLHTTP'); + }; + + var MSXML = function(){ + return new ActiveXObject('Microsoft.XMLHTTP'); + }; + + return Function.attempt(function(){ + XMLHTTP(); + return XMLHTTP; + }, function(){ + MSXML2(); + return MSXML2; + }, function(){ + MSXML(); + return MSXML; + }); + +})(); + +Browser.Features.xhr = !!(Browser.Request); + + + +// String scripts + +Browser.exec = function(text){ + if (!text) return text; + if (window.execScript){ + window.execScript(text); + } else { + var script = document.createElement('script'); + script.setAttribute('type', 'text/javascript'); + script.text = text; + document.head.appendChild(script); + document.head.removeChild(script); + } + return text; +}; + +String.implement('stripScripts', function(exec){ + var scripts = ''; + var text = this.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi, function(all, code){ + scripts += code + '\n'; + return ''; + }); + if (exec === true) Browser.exec(scripts); + else if (typeOf(exec) == 'function') exec(scripts, text); + return text; +}); + +// Window, Document + +Browser.extend({ + Document: this.Document, + Window: this.Window, + Element: this.Element, + Event: this.Event +}); + +this.Window = this.$constructor = new Type('Window', function(){}); + +this.$family = Function.from('window').hide(); + +Window.mirror(function(name, method){ + window[name] = method; +}); + +this.Document = document.$constructor = new Type('Document', function(){}); + +document.$family = Function.from('document').hide(); + +Document.mirror(function(name, method){ + document[name] = method; +}); + +document.html = document.documentElement; +if (!document.head) document.head = document.getElementsByTagName('head')[0]; + +if (document.execCommand) try { + document.execCommand("BackgroundImageCache", false, true); +} catch (e){} + +/*<ltIE9>*/ +if (this.attachEvent && !this.addEventListener){ + var unloadEvent = function(){ + this.detachEvent('onunload', unloadEvent); + document.head = document.html = document.window = null; + window = this.Window = document = null; + }; + this.attachEvent('onunload', unloadEvent); +} + +// IE fails on collections and <select>.options (refers to <select>) +var arrayFrom = Array.from; +try { + arrayFrom(document.html.childNodes); +} catch(e){ + Array.from = function(item){ + if (typeof item != 'string' && Type.isEnumerable(item) && typeOf(item) != 'array'){ + var i = item.length, array = new Array(i); + while (i--) array[i] = item[i]; + return array; + } + return arrayFrom(item); + }; + + var prototype = Array.prototype, + slice = prototype.slice; + ['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift', 'concat', 'join', 'slice'].each(function(name){ + var method = prototype[name]; + Array[name] = function(item){ + return method.apply(Array.from(item), slice.call(arguments, 1)); + }; + }); +} +/*</ltIE9>*/ + + + +})(); + +/* +--- + +name: Class + +description: Contains the Class Function for easily creating, extending, and implementing reusable Classes. + +license: MIT-style license. + +requires: [Array, String, Function, Number] + +provides: Class + +... +*/ + +(function(){ + +var Class = this.Class = new Type('Class', function(params){ + if (instanceOf(params, Function)) params = {initialize: params}; + + var newClass = function(){ + reset(this); + if (newClass.$prototyping) return this; + this.$caller = null; + var value = (this.initialize) ? this.initialize.apply(this, arguments) : this; + this.$caller = this.caller = null; + return value; + }.extend(this).implement(params); + + newClass.$constructor = Class; + newClass.prototype.$constructor = newClass; + newClass.prototype.parent = parent; + + return newClass; +}); + +var parent = function(){ + if (!this.$caller) throw new Error('The method "parent" cannot be called.'); + var name = this.$caller.$name, + parent = this.$caller.$owner.parent, + previous = (parent) ? parent.prototype[name] : null; + if (!previous) throw new Error('The method "' + name + '" has no parent.'); + return previous.apply(this, arguments); +}; + +var reset = function(object){ + for (var key in object){ + var value = object[key]; + switch (typeOf(value)){ + case 'object': + var F = function(){}; + F.prototype = value; + object[key] = reset(new F); + break; + case 'array': object[key] = value.clone(); break; + } + } + return object; +}; + +var wrap = function(self, key, method){ + if (method.$origin) method = method.$origin; + var wrapper = function(){ + if (method.$protected && this.$caller == null) throw new Error('The method "' + key + '" cannot be called.'); + var caller = this.caller, current = this.$caller; + this.caller = current; this.$caller = wrapper; + var result = method.apply(this, arguments); + this.$caller = current; this.caller = caller; + return result; + }.extend({$owner: self, $origin: method, $name: key}); + return wrapper; +}; + +var implement = function(key, value, retain){ + if (Class.Mutators.hasOwnProperty(key)){ + value = Class.Mutators[key].call(this, value); + if (value == null) return this; + } + + if (typeOf(value) == 'function'){ + if (value.$hidden) return this; + this.prototype[key] = (retain) ? value : wrap(this, key, value); + } else { + Object.merge(this.prototype, key, value); + } + + return this; +}; + +var getInstance = function(klass){ + klass.$prototyping = true; + var proto = new klass; + delete klass.$prototyping; + return proto; +}; + +Class.implement('implement', implement.overloadSetter()); + +Class.Mutators = { + + Extends: function(parent){ + this.parent = parent; + this.prototype = getInstance(parent); + }, + + Implements: function(items){ + Array.from(items).each(function(item){ + var instance = new item; + for (var key in instance) implement.call(this, key, instance[key], true); + }, this); + } +}; + +})(); + +/* +--- + +name: Class.Extras + +description: Contains Utility Classes that can be implemented into your own Classes to ease the execution of many common tasks. + +license: MIT-style license. + +requires: Class + +provides: [Class.Extras, Chain, Events, Options] + +... +*/ + +(function(){ + +this.Chain = new Class({ + + $chain: [], + + chain: function(){ + this.$chain.append(Array.flatten(arguments)); + return this; + }, + + callChain: function(){ + return (this.$chain.length) ? this.$chain.shift().apply(this, arguments) : false; + }, + + clearChain: function(){ + this.$chain.empty(); + return this; + } + +}); + +var removeOn = function(string){ + return string.replace(/^on([A-Z])/, function(full, first){ + return first.toLowerCase(); + }); +}; + +this.Events = new Class({ + + $events: {}, + + addEvent: function(type, fn, internal){ + type = removeOn(type); + + + + this.$events[type] = (this.$events[type] || []).include(fn); + if (internal) fn.internal = true; + return this; + }, + + addEvents: function(events){ + for (var type in events) this.addEvent(type, events[type]); + return this; + }, + + fireEvent: function(type, args, delay){ + type = removeOn(type); + var events = this.$events[type]; + if (!events) return this; + args = Array.from(args); + events.each(function(fn){ + if (delay) fn.delay(delay, this, args); + else fn.apply(this, args); + }, this); + return this; + }, + + removeEvent: function(type, fn){ + type = removeOn(type); + var events = this.$events[type]; + if (events && !fn.internal){ + var index = events.indexOf(fn); + if (index != -1) delete events[index]; + } + return this; + }, + + removeEvents: function(events){ + var type; + if (typeOf(events) == 'object'){ + for (type in events) this.removeEvent(type, events[type]); + return this; + } + if (events) events = removeOn(events); + for (type in this.$events){ + if (events && events != type) continue; + var fns = this.$events[type]; + for (var i = fns.length; i--;) if (i in fns){ + this.removeEvent(type, fns[i]); + } + } + return this; + } + +}); + +this.Options = new Class({ + + setOptions: function(){ + var options = this.options = Object.merge.apply(null, [{}, this.options].append(arguments)); + if (this.addEvent) for (var option in options){ + if (typeOf(options[option]) != 'function' || !(/^on[A-Z]/).test(option)) continue; + this.addEvent(option, options[option]); + delete options[option]; + } + return this; + } + +}); + +})(); + +/* +--- + +name: Object + +description: Object generic methods + +license: MIT-style license. + +requires: Type + +provides: [Object, Hash] + +... +*/ + +(function(){ + +var hasOwnProperty = Object.prototype.hasOwnProperty; + +Object.extend({ + + subset: function(object, keys){ + var results = {}; + for (var i = 0, l = keys.length; i < l; i++){ + var k = keys[i]; + if (k in object) results[k] = object[k]; + } + return results; + }, + + map: function(object, fn, bind){ + var results = {}; + for (var key in object){ + if (hasOwnProperty.call(object, key)) results[key] = fn.call(bind, object[key], key, object); + } + return results; + }, + + filter: function(object, fn, bind){ + var results = {}; + for (var key in object){ + var value = object[key]; + if (hasOwnProperty.call(object, key) && fn.call(bind, value, key, object)) results[key] = value; + } + return results; + }, + + every: function(object, fn, bind){ + for (var key in object){ + if (hasOwnProperty.call(object, key) && !fn.call(bind, object[key], key)) return false; + } + return true; + }, + + some: function(object, fn, bind){ + for (var key in object){ + if (hasOwnProperty.call(object, key) && fn.call(bind, object[key], key)) return true; + } + return false; + }, + + keys: function(object){ + var keys = []; + for (var key in object){ + if (hasOwnProperty.call(object, key)) keys.push(key); + } + return keys; + }, + + values: function(object){ + var values = []; + for (var key in object){ + if (hasOwnProperty.call(object, key)) values.push(object[key]); + } + return values; + }, + + getLength: function(object){ + return Object.keys(object).length; + }, + + keyOf: function(object, value){ + for (var key in object){ + if (hasOwnProperty.call(object, key) && object[key] === value) return key; + } + return null; + }, + + contains: function(object, value){ + return Object.keyOf(object, value) != null; + }, + + toQueryString: function(object, base){ + var queryString = []; + + Object.each(object, function(value, key){ + if (base) key = base + '[' + key + ']'; + var result; + switch (typeOf(value)){ + case 'object': result = Object.toQueryString(value, key); break; + case 'array': + var qs = {}; + value.each(function(val, i){ + qs[i] = val; + }); + result = Object.toQueryString(qs, key); + break; + default: result = key + '=' + encodeURIComponent(value); + } + if (value != null) queryString.push(result); + }); + + return queryString.join('&'); + } + +}); + +})(); + + + +/* +--- +name: Slick.Parser +description: Standalone CSS3 Selector parser +provides: Slick.Parser +... +*/ + +;(function(){ + +var parsed, + separatorIndex, + combinatorIndex, + reversed, + cache = {}, + reverseCache = {}, + reUnescape = /\\/g; + +var parse = function(expression, isReversed){ + if (expression == null) return null; + if (expression.Slick === true) return expression; + expression = ('' + expression).replace(/^\s+|\s+$/g, ''); + reversed = !!isReversed; + var currentCache = (reversed) ? reverseCache : cache; + if (currentCache[expression]) return currentCache[expression]; + parsed = { + Slick: true, + expressions: [], + raw: expression, + reverse: function(){ + return parse(this.raw, true); + } + }; + separatorIndex = -1; + while (expression != (expression = expression.replace(regexp, parser))); + parsed.length = parsed.expressions.length; + return currentCache[parsed.raw] = (reversed) ? reverse(parsed) : parsed; +}; + +var reverseCombinator = function(combinator){ + if (combinator === '!') return ' '; + else if (combinator === ' ') return '!'; + else if ((/^!/).test(combinator)) return combinator.replace(/^!/, ''); + else return '!' + combinator; +}; + +var reverse = function(expression){ + var expressions = expression.expressions; + for (var i = 0; i < expressions.length; i++){ + var exp = expressions[i]; + var last = {parts: [], tag: '*', combinator: reverseCombinator(exp[0].combinator)}; + + for (var j = 0; j < exp.length; j++){ + var cexp = exp[j]; + if (!cexp.reverseCombinator) cexp.reverseCombinator = ' '; + cexp.combinator = cexp.reverseCombinator; + delete cexp.reverseCombinator; + } + + exp.reverse().push(last); + } + return expression; +}; + +var escapeRegExp = function(string){// Credit: XRegExp 0.6.1 (c) 2007-2008 Steven Levithan <http://stevenlevithan.com/regex/xregexp/> MIT License + return string.replace(/[-[\]{}()*+?.\\^$|,#\s]/g, function(match){ + return '\\' + match; + }); +}; + +var regexp = new RegExp( +/* +#!/usr/bin/env ruby +puts "\t\t" + DATA.read.gsub(/\(\?x\)|\s+#.*$|\s+|\\$|\\n/,'') +__END__ + "(?x)^(?:\ + \\s* ( , ) \\s* # Separator \n\ + | \\s* ( <combinator>+ ) \\s* # Combinator \n\ + | ( \\s+ ) # CombinatorChildren \n\ + | ( <unicode>+ | \\* ) # Tag \n\ + | \\# ( <unicode>+ ) # ID \n\ + | \\. ( <unicode>+ ) # ClassName \n\ + | # Attribute \n\ + \\[ \ + \\s* (<unicode1>+) (?: \ + \\s* ([*^$!~|]?=) (?: \ + \\s* (?:\ + ([\"']?)(.*?)\\9 \ + )\ + ) \ + )? \\s* \ + \\](?!\\]) \n\ + | :+ ( <unicode>+ )(?:\ + \\( (?:\ + (?:([\"'])([^\\12]*)\\12)|((?:\\([^)]+\\)|[^()]*)+)\ + ) \\)\ + )?\ + )" +*/ + "^(?:\\s*(,)\\s*|\\s*(<combinator>+)\\s*|(\\s+)|(<unicode>+|\\*)|\\#(<unicode>+)|\\.(<unicode>+)|\\[\\s*(<unicode1>+)(?:\\s*([*^$!~|]?=)(?:\\s*(?:([\"']?)(.*?)\\9)))?\\s*\\](?!\\])|(:+)(<unicode>+)(?:\\((?:(?:([\"'])([^\\13]*)\\13)|((?:\\([^)]+\\)|[^()]*)+))\\))?)" + .replace(/<combinator>/, '[' + escapeRegExp(">+~`!@$%^&={}\\;</") + ']') + .replace(/<unicode>/g, '(?:[\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])') + .replace(/<unicode1>/g, '(?:[:\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])') +); + +function parser( + rawMatch, + + separator, + combinator, + combinatorChildren, + + tagName, + id, + className, + + attributeKey, + attributeOperator, + attributeQuote, + attributeValue, + + pseudoMarker, + pseudoClass, + pseudoQuote, + pseudoClassQuotedValue, + pseudoClassValue +){ + if (separator || separatorIndex === -1){ + parsed.expressions[++separatorIndex] = []; + combinatorIndex = -1; + if (separator) return ''; + } + + if (combinator || combinatorChildren || combinatorIndex === -1){ + combinator = combinator || ' '; + var currentSeparator = parsed.expressions[separatorIndex]; + if (reversed && currentSeparator[combinatorIndex]) + currentSeparator[combinatorIndex].reverseCombinator = reverseCombinator(combinator); + currentSeparator[++combinatorIndex] = {combinator: combinator, tag: '*'}; + } + + var currentParsed = parsed.expressions[separatorIndex][combinatorIndex]; + + if (tagName){ + currentParsed.tag = tagName.replace(reUnescape, ''); + + } else if (id){ + currentParsed.id = id.replace(reUnescape, ''); + + } else if (className){ + className = className.replace(reUnescape, ''); + + if (!currentParsed.classList) currentParsed.classList = []; + if (!currentParsed.classes) currentParsed.classes = []; + currentParsed.classList.push(className); + currentParsed.classes.push({ + value: className, + regexp: new RegExp('(^|\\s)' + escapeRegExp(className) + '(\\s|$)') + }); + + } else if (pseudoClass){ + pseudoClassValue = pseudoClassValue || pseudoClassQuotedValue; + pseudoClassValue = pseudoClassValue ? pseudoClassValue.replace(reUnescape, '') : null; + + if (!currentParsed.pseudos) currentParsed.pseudos = []; + currentParsed.pseudos.push({ + key: pseudoClass.replace(reUnescape, ''), + value: pseudoClassValue, + type: pseudoMarker.length == 1 ? 'class' : 'element' + }); + + } else if (attributeKey){ + attributeKey = attributeKey.replace(reUnescape, ''); + attributeValue = (attributeValue || '').replace(reUnescape, ''); + + var test, regexp; + + switch (attributeOperator){ + case '^=' : regexp = new RegExp( '^'+ escapeRegExp(attributeValue) ); break; + case '$=' : regexp = new RegExp( escapeRegExp(attributeValue) +'$' ); break; + case '~=' : regexp = new RegExp( '(^|\\s)'+ escapeRegExp(attributeValue) +'(\\s|$)' ); break; + case '|=' : regexp = new RegExp( '^'+ escapeRegExp(attributeValue) +'(-|$)' ); break; + case '=' : test = function(value){ + return attributeValue == value; + }; break; + case '*=' : test = function(value){ + return value && value.indexOf(attributeValue) > -1; + }; break; + case '!=' : test = function(value){ + return attributeValue != value; + }; break; + default : test = function(value){ + return !!value; + }; + } + + if (attributeValue == '' && (/^[*$^]=$/).test(attributeOperator)) test = function(){ + return false; + }; + + if (!test) test = function(value){ + return value && regexp.test(value); + }; + + if (!currentParsed.attributes) currentParsed.attributes = []; + currentParsed.attributes.push({ + key: attributeKey, + operator: attributeOperator, + value: attributeValue, + test: test + }); + + } + + return ''; +}; + +// Slick NS + +var Slick = (this.Slick || {}); + +Slick.parse = function(expression){ + return parse(expression); +}; + +Slick.escapeRegExp = escapeRegExp; + +if (!this.Slick) this.Slick = Slick; + +}).apply(/*<CommonJS>*/(typeof exports != 'undefined') ? exports : /*</CommonJS>*/this); + +/* +--- +name: Slick.Finder +description: The new, superfast css selector engine. +provides: Slick.Finder +requires: Slick.Parser +... +*/ + +;(function(){ + +var local = {}, + featuresCache = {}, + toString = Object.prototype.toString; + +// Feature / Bug detection + +local.isNativeCode = function(fn){ + return (/\{\s*\[native code\]\s*\}/).test('' + fn); +}; + +local.isXML = function(document){ + return (!!document.xmlVersion) || (!!document.xml) || (toString.call(document) == '[object XMLDocument]') || + (document.nodeType == 9 && document.documentElement.nodeName != 'HTML'); +}; + +local.setDocument = function(document){ + + // convert elements / window arguments to document. if document cannot be extrapolated, the function returns. + var nodeType = document.nodeType; + if (nodeType == 9); // document + else if (nodeType) document = document.ownerDocument; // node + else if (document.navigator) document = document.document; // window + else return; + + // check if it's the old document + + if (this.document === document) return; + this.document = document; + + // check if we have done feature detection on this document before + + var root = document.documentElement, + rootUid = this.getUIDXML(root), + features = featuresCache[rootUid], + feature; + + if (features){ + for (feature in features){ + this[feature] = features[feature]; + } + return; + } + + features = featuresCache[rootUid] = {}; + + features.root = root; + features.isXMLDocument = this.isXML(document); + + features.brokenStarGEBTN + = features.starSelectsClosedQSA + = features.idGetsName + = features.brokenMixedCaseQSA + = features.brokenGEBCN + = features.brokenCheckedQSA + = features.brokenEmptyAttributeQSA + = features.isHTMLDocument + = features.nativeMatchesSelector + = false; + + var starSelectsClosed, starSelectsComments, + brokenSecondClassNameGEBCN, cachedGetElementsByClassName, + brokenFormAttributeGetter; + + var selected, id = 'slick_uniqueid'; + var testNode = document.createElement('div'); + + var testRoot = document.body || document.getElementsByTagName('body')[0] || root; + testRoot.appendChild(testNode); + + // on non-HTML documents innerHTML and getElementsById doesnt work properly + try { + testNode.innerHTML = '<a id="'+id+'"></a>'; + features.isHTMLDocument = !!document.getElementById(id); + } catch(e){}; + + if (features.isHTMLDocument){ + + testNode.style.display = 'none'; + + // IE returns comment nodes for getElementsByTagName('*') for some documents + testNode.appendChild(document.createComment('')); + starSelectsComments = (testNode.getElementsByTagName('*').length > 1); + + // IE returns closed nodes (EG:"</foo>") for getElementsByTagName('*') for some documents + try { + testNode.innerHTML = 'foo</foo>'; + selected = testNode.getElementsByTagName('*'); + starSelectsClosed = (selected && !!selected.length && selected[0].nodeName.charAt(0) == '/'); + } catch(e){}; + + features.brokenStarGEBTN = starSelectsComments || starSelectsClosed; + + // IE returns elements with the name instead of just id for getElementsById for some documents + try { + testNode.innerHTML = '<a name="'+ id +'"></a><b id="'+ id +'"></b>'; + features.idGetsName = document.getElementById(id) === testNode.firstChild; + } catch(e){}; + + if (testNode.getElementsByClassName){ + + // Safari 3.2 getElementsByClassName caches results + try { + testNode.innerHTML = '<a class="f"></a><a class="b"></a>'; + testNode.getElementsByClassName('b').length; + testNode.firstChild.className = 'b'; + cachedGetElementsByClassName = (testNode.getElementsByClassName('b').length != 2); + } catch(e){}; + + // Opera 9.6 getElementsByClassName doesnt detects the class if its not the first one + try { + testNode.innerHTML = '<a class="a"></a><a class="f b a"></a>'; + brokenSecondClassNameGEBCN = (testNode.getElementsByClassName('a').length != 2); + } catch(e){}; + + features.brokenGEBCN = cachedGetElementsByClassName || brokenSecondClassNameGEBCN; + } + + if (testNode.querySelectorAll){ + // IE 8 returns closed nodes (EG:"</foo>") for querySelectorAll('*') for some documents + try { + testNode.innerHTML = 'foo</foo>'; + selected = testNode.querySelectorAll('*'); + features.starSelectsClosedQSA = (selected && !!selected.length && selected[0].nodeName.charAt(0) == '/'); + } catch(e){}; + + // Safari 3.2 querySelectorAll doesnt work with mixedcase on quirksmode + try { + testNode.innerHTML = '<a class="MiX"></a>'; + features.brokenMixedCaseQSA = !testNode.querySelectorAll('.MiX').length; + } catch(e){}; + + // Webkit and Opera dont return selected options on querySelectorAll + try { + testNode.innerHTML = '<select><option selected="selected">a</option></select>'; + features.brokenCheckedQSA = (testNode.querySelectorAll(':checked').length == 0); + } catch(e){}; + + // IE returns incorrect results for attr[*^$]="" selectors on querySelectorAll + try { + testNode.innerHTML = '<a class=""></a>'; + features.brokenEmptyAttributeQSA = (testNode.querySelectorAll('[class*=""]').length != 0); + } catch(e){}; + + } + + // IE6-7, if a form has an input of id x, form.getAttribute(x) returns a reference to the input + try { + testNode.innerHTML = '<form action="s"><input id="action"/></form>'; + brokenFormAttributeGetter = (testNode.firstChild.getAttribute('action') != 's'); + } catch(e){}; + + // native matchesSelector function + + features.nativeMatchesSelector = root.matches || /*root.msMatchesSelector ||*/ root.mozMatchesSelector || root.webkitMatchesSelector; + if (features.nativeMatchesSelector) try { + // if matchesSelector trows errors on incorrect sintaxes we can use it + features.nativeMatchesSelector.call(root, ':slick'); + features.nativeMatchesSelector = null; + } catch(e){}; + + } + + try { + root.slick_expando = 1; + delete root.slick_expando; + features.getUID = this.getUIDHTML; + } catch(e){ + features.getUID = this.getUIDXML; + } + + testRoot.removeChild(testNode); + testNode = selected = testRoot = null; + + // getAttribute + + features.getAttribute = (features.isHTMLDocument && brokenFormAttributeGetter) ? function(node, name){ + var method = this.attributeGetters[name]; + if (method) return method.call(node); + var attributeNode = node.getAttributeNode(name); + return (attributeNode) ? attributeNode.nodeValue : null; + } : function(node, name){ + var method = this.attributeGetters[name]; + return (method) ? method.call(node) : node.getAttribute(name); + }; + + // hasAttribute + + features.hasAttribute = (root && this.isNativeCode(root.hasAttribute)) ? function(node, attribute){ + return node.hasAttribute(attribute); + } : function(node, attribute){ + node = node.getAttributeNode(attribute); + return !!(node && (node.specified || node.nodeValue)); + }; + + // contains + // FIXME: Add specs: local.contains should be different for xml and html documents? + var nativeRootContains = root && this.isNativeCode(root.contains), + nativeDocumentContains = document && this.isNativeCode(document.contains); + + features.contains = (nativeRootContains && nativeDocumentContains) ? function(context, node){ + return context.contains(node); + } : (nativeRootContains && !nativeDocumentContains) ? function(context, node){ + // IE8 does not have .contains on document. + return context === node || ((context === document) ? document.documentElement : context).contains(node); + } : (root && root.compareDocumentPosition) ? function(context, node){ + return context === node || !!(context.compareDocumentPosition(node) & 16); + } : function(context, node){ + if (node) do { + if (node === context) return true; + } while ((node = node.parentNode)); + return false; + }; + + // document order sorting + // credits to Sizzle (http://sizzlejs.com/) + + features.documentSorter = (root.compareDocumentPosition) ? function(a, b){ + if (!a.compareDocumentPosition || !b.compareDocumentPosition) return 0; + return a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1; + } : ('sourceIndex' in root) ? function(a, b){ + if (!a.sourceIndex || !b.sourceIndex) return 0; + return a.sourceIndex - b.sourceIndex; + } : (document.createRange) ? function(a, b){ + if (!a.ownerDocument || !b.ownerDocument) return 0; + var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange(); + aRange.setStart(a, 0); + aRange.setEnd(a, 0); + bRange.setStart(b, 0); + bRange.setEnd(b, 0); + return aRange.compareBoundaryPoints(Range.START_TO_END, bRange); + } : null ; + + root = null; + + for (feature in features){ + this[feature] = features[feature]; + } +}; + +// Main Method + +var reSimpleSelector = /^([#.]?)((?:[\w-]+|\*))$/, + reEmptyAttribute = /\[.+[*$^]=(?:""|'')?\]/, + qsaFailExpCache = {}; + +local.search = function(context, expression, append, first){ + + var found = this.found = (first) ? null : (append || []); + + if (!context) return found; + else if (context.navigator) context = context.document; // Convert the node from a window to a document + else if (!context.nodeType) return found; + + // setup + + var parsed, i, + uniques = this.uniques = {}, + hasOthers = !!(append && append.length), + contextIsDocument = (context.nodeType == 9); + + if (this.document !== (contextIsDocument ? context : context.ownerDocument)) this.setDocument(context); + + // avoid duplicating items already in the append array + if (hasOthers) for (i = found.length; i--;) uniques[this.getUID(found[i])] = true; + + // expression checks + + if (typeof expression == 'string'){ // expression is a string + + /*<simple-selectors-override>*/ + var simpleSelector = expression.match(reSimpleSelector); + simpleSelectors: if (simpleSelector){ + + var symbol = simpleSelector[1], + name = simpleSelector[2], + node, nodes; + + if (!symbol){ + + if (name == '*' && this.brokenStarGEBTN) break simpleSelectors; + nodes = context.getElementsByTagName(name); + if (first) return nodes[0] || null; + for (i = 0; node = nodes[i++];){ + if (!(hasOthers && uniques[this.getUID(node)])) found.push(node); + } + + } else if (symbol == '#'){ + + if (!this.isHTMLDocument || !contextIsDocument) break simpleSelectors; + node = context.getElementById(name); + if (!node) return found; + if (this.idGetsName && node.getAttributeNode('id').nodeValue != name) break simpleSelectors; + if (first) return node || null; + if (!(hasOthers && uniques[this.getUID(node)])) found.push(node); + + } else if (symbol == '.'){ + + if (!this.isHTMLDocument || ((!context.getElementsByClassName || this.brokenGEBCN) && context.querySelectorAll)) break simpleSelectors; + if (context.getElementsByClassName && !this.brokenGEBCN){ + nodes = context.getElementsByClassName(name); + if (first) return nodes[0] || null; + for (i = 0; node = nodes[i++];){ + if (!(hasOthers && uniques[this.getUID(node)])) found.push(node); + } + } else { + var matchClass = new RegExp('(^|\\s)'+ Slick.escapeRegExp(name) +'(\\s|$)'); + nodes = context.getElementsByTagName('*'); + for (i = 0; node = nodes[i++];){ + className = node.className; + if (!(className && matchClass.test(className))) continue; + if (first) return node; + if (!(hasOthers && uniques[this.getUID(node)])) found.push(node); + } + } + + } + + if (hasOthers) this.sort(found); + return (first) ? null : found; + + } + /*</simple-selectors-override>*/ + + /*<query-selector-override>*/ + querySelector: if (context.querySelectorAll){ + + if (!this.isHTMLDocument + || qsaFailExpCache[expression] + //TODO: only skip when expression is actually mixed case + || this.brokenMixedCaseQSA + || (this.brokenCheckedQSA && expression.indexOf(':checked') > -1) + || (this.brokenEmptyAttributeQSA && reEmptyAttribute.test(expression)) + || (!contextIsDocument //Abort when !contextIsDocument and... + // there are multiple expressions in the selector + // since we currently only fix non-document rooted QSA for single expression selectors + && expression.indexOf(',') > -1 + ) + || Slick.disableQSA + ) break querySelector; + + var _expression = expression, _context = context; + if (!contextIsDocument){ + // non-document rooted QSA + // credits to Andrew Dupont + var currentId = _context.getAttribute('id'), slickid = 'slickid__'; + _context.setAttribute('id', slickid); + _expression = '#' + slickid + ' ' + _expression; + context = _context.parentNode; + } + + try { + if (first) return context.querySelector(_expression) || null; + else nodes = context.querySelectorAll(_expression); + } catch(e){ + qsaFailExpCache[expression] = 1; + break querySelector; + } finally { + if (!contextIsDocument){ + if (currentId) _context.setAttribute('id', currentId); + else _context.removeAttribute('id'); + context = _context; + } + } + + if (this.starSelectsClosedQSA) for (i = 0; node = nodes[i++];){ + if (node.nodeName > '@' && !(hasOthers && uniques[this.getUID(node)])) found.push(node); + } else for (i = 0; node = nodes[i++];){ + if (!(hasOthers && uniques[this.getUID(node)])) found.push(node); + } + + if (hasOthers) this.sort(found); + return found; + + } + /*</query-selector-override>*/ + + parsed = this.Slick.parse(expression); + if (!parsed.length) return found; + } else if (expression == null){ // there is no expression + return found; + } else if (expression.Slick){ // expression is a parsed Slick object + parsed = expression; + } else if (this.contains(context.documentElement || context, expression)){ // expression is a node + (found) ? found.push(expression) : found = expression; + return found; + } else { // other junk + return found; + } + + /*<pseudo-selectors>*//*<nth-pseudo-selectors>*/ + + // cache elements for the nth selectors + + this.posNTH = {}; + this.posNTHLast = {}; + this.posNTHType = {}; + this.posNTHTypeLast = {}; + + /*</nth-pseudo-selectors>*//*</pseudo-selectors>*/ + + // if append is null and there is only a single selector with one expression use pushArray, else use pushUID + this.push = (!hasOthers && (first || (parsed.length == 1 && parsed.expressions[0].length == 1))) ? this.pushArray : this.pushUID; + + if (found == null) found = []; + + // default engine + + var j, m, n; + var combinator, tag, id, classList, classes, attributes, pseudos; + var currentItems, currentExpression, currentBit, lastBit, expressions = parsed.expressions; + + search: for (i = 0; (currentExpression = expressions[i]); i++) for (j = 0; (currentBit = currentExpression[j]); j++){ + + combinator = 'combinator:' + currentBit.combinator; + if (!this[combinator]) continue search; + + tag = (this.isXMLDocument) ? currentBit.tag : currentBit.tag.toUpperCase(); + id = currentBit.id; + classList = currentBit.classList; + classes = currentBit.classes; + attributes = currentBit.attributes; + pseudos = currentBit.pseudos; + lastBit = (j === (currentExpression.length - 1)); + + this.bitUniques = {}; + + if (lastBit){ + this.uniques = uniques; + this.found = found; + } else { + this.uniques = {}; + this.found = []; + } + + if (j === 0){ + this[combinator](context, tag, id, classes, attributes, pseudos, classList); + if (first && lastBit && found.length) break search; + } else { + if (first && lastBit) for (m = 0, n = currentItems.length; m < n; m++){ + this[combinator](currentItems[m], tag, id, classes, attributes, pseudos, classList); + if (found.length) break search; + } else for (m = 0, n = currentItems.length; m < n; m++) this[combinator](currentItems[m], tag, id, classes, attributes, pseudos, classList); + } + + currentItems = this.found; + } + + // should sort if there are nodes in append and if you pass multiple expressions. + if (hasOthers || (parsed.expressions.length > 1)) this.sort(found); + + return (first) ? (found[0] || null) : found; +}; + +// Utils + +local.uidx = 1; +local.uidk = 'slick-uniqueid'; + +local.getUIDXML = function(node){ + var uid = node.getAttribute(this.uidk); + if (!uid){ + uid = this.uidx++; + node.setAttribute(this.uidk, uid); + } + return uid; +}; + +local.getUIDHTML = function(node){ + return node.uniqueNumber || (node.uniqueNumber = this.uidx++); +}; + +// sort based on the setDocument documentSorter method. + +local.sort = function(results){ + if (!this.documentSorter) return results; + results.sort(this.documentSorter); + return results; +}; + +/*<pseudo-selectors>*//*<nth-pseudo-selectors>*/ + +local.cacheNTH = {}; + +local.matchNTH = /^([+-]?\d*)?([a-z]+)?([+-]\d+)?$/; + +local.parseNTHArgument = function(argument){ + var parsed = argument.match(this.matchNTH); + if (!parsed) return false; + var special = parsed[2] || false; + var a = parsed[1] || 1; + if (a == '-') a = -1; + var b = +parsed[3] || 0; + parsed = + (special == 'n') ? {a: a, b: b} : + (special == 'odd') ? {a: 2, b: 1} : + (special == 'even') ? {a: 2, b: 0} : {a: 0, b: a}; + + return (this.cacheNTH[argument] = parsed); +}; + +local.createNTHPseudo = function(child, sibling, positions, ofType){ + return function(node, argument){ + var uid = this.getUID(node); + if (!this[positions][uid]){ + var parent = node.parentNode; + if (!parent) return false; + var el = parent[child], count = 1; + if (ofType){ + var nodeName = node.nodeName; + do { + if (el.nodeName != nodeName) continue; + this[positions][this.getUID(el)] = count++; + } while ((el = el[sibling])); + } else { + do { + if (el.nodeType != 1) continue; + this[positions][this.getUID(el)] = count++; + } while ((el = el[sibling])); + } + } + argument = argument || 'n'; + var parsed = this.cacheNTH[argument] || this.parseNTHArgument(argument); + if (!parsed) return false; + var a = parsed.a, b = parsed.b, pos = this[positions][uid]; + if (a == 0) return b == pos; + if (a > 0){ + if (pos < b) return false; + } else { + if (b < pos) return false; + } + return ((pos - b) % a) == 0; + }; +}; + +/*</nth-pseudo-selectors>*//*</pseudo-selectors>*/ + +local.pushArray = function(node, tag, id, classes, attributes, pseudos){ + if (this.matchSelector(node, tag, id, classes, attributes, pseudos)) this.found.push(node); +}; + +local.pushUID = function(node, tag, id, classes, attributes, pseudos){ + var uid = this.getUID(node); + if (!this.uniques[uid] && this.matchSelector(node, tag, id, classes, attributes, pseudos)){ + this.uniques[uid] = true; + this.found.push(node); + } +}; + +local.matchNode = function(node, selector){ + if (this.isHTMLDocument && this.nativeMatchesSelector){ + try { + return this.nativeMatchesSelector.call(node, selector.replace(/\[([^=]+)=\s*([^'"\]]+?)\s*\]/g, '[$1="$2"]')); + } catch(matchError){} + } + + var parsed = this.Slick.parse(selector); + if (!parsed) return true; + + // simple (single) selectors + var expressions = parsed.expressions, simpleExpCounter = 0, i, currentExpression; + for (i = 0; (currentExpression = expressions[i]); i++){ + if (currentExpression.length == 1){ + var exp = currentExpression[0]; + if (this.matchSelector(node, (this.isXMLDocument) ? exp.tag : exp.tag.toUpperCase(), exp.id, exp.classes, exp.attributes, exp.pseudos)) return true; + simpleExpCounter++; + } + } + + if (simpleExpCounter == parsed.length) return false; + + var nodes = this.search(this.document, parsed), item; + for (i = 0; item = nodes[i++];){ + if (item === node) return true; + } + return false; +}; + +local.matchPseudo = function(node, name, argument){ + var pseudoName = 'pseudo:' + name; + if (this[pseudoName]) return this[pseudoName](node, argument); + var attribute = this.getAttribute(node, name); + return (argument) ? argument == attribute : !!attribute; +}; + +local.matchSelector = function(node, tag, id, classes, attributes, pseudos){ + if (tag){ + var nodeName = (this.isXMLDocument) ? node.nodeName : node.nodeName.toUpperCase(); + if (tag == '*'){ + if (nodeName < '@') return false; // Fix for comment nodes and closed nodes + } else { + if (nodeName != tag) return false; + } + } + + if (id && node.getAttribute('id') != id) return false; + + var i, part, cls; + if (classes) for (i = classes.length; i--;){ + cls = this.getAttribute(node, 'class'); + if (!(cls && classes[i].regexp.test(cls))) return false; + } + if (attributes) for (i = attributes.length; i--;){ + part = attributes[i]; + if (part.operator ? !part.test(this.getAttribute(node, part.key)) : !this.hasAttribute(node, part.key)) return false; + } + if (pseudos) for (i = pseudos.length; i--;){ + part = pseudos[i]; + if (!this.matchPseudo(node, part.key, part.value)) return false; + } + return true; +}; + +var combinators = { + + ' ': function(node, tag, id, classes, attributes, pseudos, classList){ // all child nodes, any level + + var i, item, children; + + if (this.isHTMLDocument){ + getById: if (id){ + item = this.document.getElementById(id); + if ((!item && node.all) || (this.idGetsName && item && item.getAttributeNode('id').nodeValue != id)){ + // all[id] returns all the elements with that name or id inside node + // if theres just one it will return the element, else it will be a collection + children = node.all[id]; + if (!children) return; + if (!children[0]) children = [children]; + for (i = 0; item = children[i++];){ + var idNode = item.getAttributeNode('id'); + if (idNode && idNode.nodeValue == id){ + this.push(item, tag, null, classes, attributes, pseudos); + break; + } + } + return; + } + if (!item){ + // if the context is in the dom we return, else we will try GEBTN, breaking the getById label + if (this.contains(this.root, node)) return; + else break getById; + } else if (this.document !== node && !this.contains(node, item)) return; + this.push(item, tag, null, classes, attributes, pseudos); + return; + } + getByClass: if (classes && node.getElementsByClassName && !this.brokenGEBCN){ + children = node.getElementsByClassName(classList.join(' ')); + if (!(children && children.length)) break getByClass; + for (i = 0; item = children[i++];) this.push(item, tag, id, null, attributes, pseudos); + return; + } + } + getByTag: { + children = node.getElementsByTagName(tag); + if (!(children && children.length)) break getByTag; + if (!this.brokenStarGEBTN) tag = null; + for (i = 0; item = children[i++];) this.push(item, tag, id, classes, attributes, pseudos); + } + }, + + '>': function(node, tag, id, classes, attributes, pseudos){ // direct children + if ((node = node.firstChild)) do { + if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos); + } while ((node = node.nextSibling)); + }, + + '+': function(node, tag, id, classes, attributes, pseudos){ // next sibling + while ((node = node.nextSibling)) if (node.nodeType == 1){ + this.push(node, tag, id, classes, attributes, pseudos); + break; + } + }, + + '^': function(node, tag, id, classes, attributes, pseudos){ // first child + node = node.firstChild; + if (node){ + if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos); + else this['combinator:+'](node, tag, id, classes, attributes, pseudos); + } + }, + + '~': function(node, tag, id, classes, attributes, pseudos){ // next siblings + while ((node = node.nextSibling)){ + if (node.nodeType != 1) continue; + var uid = this.getUID(node); + if (this.bitUniques[uid]) break; + this.bitUniques[uid] = true; + this.push(node, tag, id, classes, attributes, pseudos); + } + }, + + '++': function(node, tag, id, classes, attributes, pseudos){ // next sibling and previous sibling + this['combinator:+'](node, tag, id, classes, attributes, pseudos); + this['combinator:!+'](node, tag, id, classes, attributes, pseudos); + }, + + '~~': function(node, tag, id, classes, attributes, pseudos){ // next siblings and previous siblings + this['combinator:~'](node, tag, id, classes, attributes, pseudos); + this['combinator:!~'](node, tag, id, classes, attributes, pseudos); + }, + + '!': function(node, tag, id, classes, attributes, pseudos){ // all parent nodes up to document + while ((node = node.parentNode)) if (node !== this.document) this.push(node, tag, id, classes, attributes, pseudos); + }, + + '!>': function(node, tag, id, classes, attributes, pseudos){ // direct parent (one level) + node = node.parentNode; + if (node !== this.document) this.push(node, tag, id, classes, attributes, pseudos); + }, + + '!+': function(node, tag, id, classes, attributes, pseudos){ // previous sibling + while ((node = node.previousSibling)) if (node.nodeType == 1){ + this.push(node, tag, id, classes, attributes, pseudos); + break; + } + }, + + '!^': function(node, tag, id, classes, attributes, pseudos){ // last child + node = node.lastChild; + if (node){ + if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos); + else this['combinator:!+'](node, tag, id, classes, attributes, pseudos); + } + }, + + '!~': function(node, tag, id, classes, attributes, pseudos){ // previous siblings + while ((node = node.previousSibling)){ + if (node.nodeType != 1) continue; + var uid = this.getUID(node); + if (this.bitUniques[uid]) break; + this.bitUniques[uid] = true; + this.push(node, tag, id, classes, attributes, pseudos); + } + } + +}; + +for (var c in combinators) local['combinator:' + c] = combinators[c]; + +var pseudos = { + + /*<pseudo-selectors>*/ + + 'empty': function(node){ + var child = node.firstChild; + return !(child && child.nodeType == 1) && !(node.innerText || node.textContent || '').length; + }, + + 'not': function(node, expression){ + return !this.matchNode(node, expression); + }, + + 'contains': function(node, text){ + return (node.innerText || node.textContent || '').indexOf(text) > -1; + }, + + 'first-child': function(node){ + while ((node = node.previousSibling)) if (node.nodeType == 1) return false; + return true; + }, + + 'last-child': function(node){ + while ((node = node.nextSibling)) if (node.nodeType == 1) return false; + return true; + }, + + 'only-child': function(node){ + var prev = node; + while ((prev = prev.previousSibling)) if (prev.nodeType == 1) return false; + var next = node; + while ((next = next.nextSibling)) if (next.nodeType == 1) return false; + return true; + }, + + /*<nth-pseudo-selectors>*/ + + 'nth-child': local.createNTHPseudo('firstChild', 'nextSibling', 'posNTH'), + + 'nth-last-child': local.createNTHPseudo('lastChild', 'previousSibling', 'posNTHLast'), + + 'nth-of-type': local.createNTHPseudo('firstChild', 'nextSibling', 'posNTHType', true), + + 'nth-last-of-type': local.createNTHPseudo('lastChild', 'previousSibling', 'posNTHTypeLast', true), + + 'index': function(node, index){ + return this['pseudo:nth-child'](node, '' + (index + 1)); + }, + + 'even': function(node){ + return this['pseudo:nth-child'](node, '2n'); + }, + + 'odd': function(node){ + return this['pseudo:nth-child'](node, '2n+1'); + }, + + /*</nth-pseudo-selectors>*/ + + /*<of-type-pseudo-selectors>*/ + + 'first-of-type': function(node){ + var nodeName = node.nodeName; + while ((node = node.previousSibling)) if (node.nodeName == nodeName) return false; + return true; + }, + + 'last-of-type': function(node){ + var nodeName = node.nodeName; + while ((node = node.nextSibling)) if (node.nodeName == nodeName) return false; + return true; + }, + + 'only-of-type': function(node){ + var prev = node, nodeName = node.nodeName; + while ((prev = prev.previousSibling)) if (prev.nodeName == nodeName) return false; + var next = node; + while ((next = next.nextSibling)) if (next.nodeName == nodeName) return false; + return true; + }, + + /*</of-type-pseudo-selectors>*/ + + // custom pseudos + + 'enabled': function(node){ + return !node.disabled; + }, + + 'disabled': function(node){ + return node.disabled; + }, + + 'checked': function(node){ + return node.checked || node.selected; + }, + + 'focus': function(node){ + return this.isHTMLDocument && this.document.activeElement === node && (node.href || node.type || this.hasAttribute(node, 'tabindex')); + }, + + 'root': function(node){ + return (node === this.root); + }, + + 'selected': function(node){ + return node.selected; + } + + /*</pseudo-selectors>*/ +}; + +for (var p in pseudos) local['pseudo:' + p] = pseudos[p]; + +// attributes methods + +var attributeGetters = local.attributeGetters = { + + 'for': function(){ + return ('htmlFor' in this) ? this.htmlFor : this.getAttribute('for'); + }, + + 'href': function(){ + return ('href' in this) ? this.getAttribute('href', 2) : this.getAttribute('href'); + }, + + 'style': function(){ + return (this.style) ? this.style.cssText : this.getAttribute('style'); + }, + + 'tabindex': function(){ + var attributeNode = this.getAttributeNode('tabindex'); + return (attributeNode && attributeNode.specified) ? attributeNode.nodeValue : null; + }, + + 'type': function(){ + return this.getAttribute('type'); + }, + + 'maxlength': function(){ + var attributeNode = this.getAttributeNode('maxLength'); + return (attributeNode && attributeNode.specified) ? attributeNode.nodeValue : null; + } + +}; + +attributeGetters.MAXLENGTH = attributeGetters.maxLength = attributeGetters.maxlength; + +// Slick + +var Slick = local.Slick = (this.Slick || {}); + +Slick.version = '1.1.7'; + +// Slick finder + +Slick.search = function(context, expression, append){ + return local.search(context, expression, append); +}; + +Slick.find = function(context, expression){ + return local.search(context, expression, null, true); +}; + +// Slick containment checker + +Slick.contains = function(container, node){ + local.setDocument(container); + return local.contains(container, node); +}; + +// Slick attribute getter + +Slick.getAttribute = function(node, name){ + local.setDocument(node); + return local.getAttribute(node, name); +}; + +Slick.hasAttribute = function(node, name){ + local.setDocument(node); + return local.hasAttribute(node, name); +}; + +// Slick matcher + +Slick.match = function(node, selector){ + if (!(node && selector)) return false; + if (!selector || selector === node) return true; + local.setDocument(node); + return local.matchNode(node, selector); +}; + +// Slick attribute accessor + +Slick.defineAttributeGetter = function(name, fn){ + local.attributeGetters[name] = fn; + return this; +}; + +Slick.lookupAttributeGetter = function(name){ + return local.attributeGetters[name]; +}; + +// Slick pseudo accessor + +Slick.definePseudo = function(name, fn){ + local['pseudo:' + name] = function(node, argument){ + return fn.call(node, argument); + }; + return this; +}; + +Slick.lookupPseudo = function(name){ + var pseudo = local['pseudo:' + name]; + if (pseudo) return function(argument){ + return pseudo.call(this, argument); + }; + return null; +}; + +// Slick overrides accessor + +Slick.override = function(regexp, fn){ + local.override(regexp, fn); + return this; +}; + +Slick.isXML = local.isXML; + +Slick.uidOf = function(node){ + return local.getUIDHTML(node); +}; + +if (!this.Slick) this.Slick = Slick; + +}).apply(/*<CommonJS>*/(typeof exports != 'undefined') ? exports : /*</CommonJS>*/this); + +/* +--- + +name: Element + +description: One of the most important items in MooTools. Contains the dollar function, the dollars function, and an handful of cross-browser, time-saver methods to let you easily work with HTML Elements. + +license: MIT-style license. + +requires: [Window, Document, Array, String, Function, Object, Number, Slick.Parser, Slick.Finder] + +provides: [Element, Elements, $, $$, IFrame, Selectors] + +... +*/ + +var Element = this.Element = function(tag, props){ + var konstructor = Element.Constructors[tag]; + if (konstructor) return konstructor(props); + if (typeof tag != 'string') return document.id(tag).set(props); + + if (!props) props = {}; + + if (!(/^[\w-]+$/).test(tag)){ + var parsed = Slick.parse(tag).expressions[0][0]; + tag = (parsed.tag == '*') ? 'div' : parsed.tag; + if (parsed.id && props.id == null) props.id = parsed.id; + + var attributes = parsed.attributes; + if (attributes) for (var attr, i = 0, l = attributes.length; i < l; i++){ + attr = attributes[i]; + if (props[attr.key] != null) continue; + + if (attr.value != null && attr.operator == '=') props[attr.key] = attr.value; + else if (!attr.value && !attr.operator) props[attr.key] = true; + } + + if (parsed.classList && props['class'] == null) props['class'] = parsed.classList.join(' '); + } + + return document.newElement(tag, props); +}; + + +if (Browser.Element){ + Element.prototype = Browser.Element.prototype; + // IE8 and IE9 require the wrapping. + Element.prototype._fireEvent = (function(fireEvent){ + return function(type, event){ + return fireEvent.call(this, type, event); + }; + })(Element.prototype.fireEvent); +} + +new Type('Element', Element).mirror(function(name){ + if (Array.prototype[name]) return; + + var obj = {}; + obj[name] = function(){ + var results = [], args = arguments, elements = true; + for (var i = 0, l = this.length; i < l; i++){ + var element = this[i], result = results[i] = element[name].apply(element, args); + elements = (elements && typeOf(result) == 'element'); + } + return (elements) ? new Elements(results) : results; + }; + + Elements.implement(obj); +}); + +if (!Browser.Element){ + Element.parent = Object; + + Element.Prototype = { + '$constructor': Element, + '$family': Function.from('element').hide() + }; + + Element.mirror(function(name, method){ + Element.Prototype[name] = method; + }); +} + +Element.Constructors = {}; + + + +var IFrame = new Type('IFrame', function(){ + var params = Array.link(arguments, { + properties: Type.isObject, + iframe: function(obj){ + return (obj != null); + } + }); + + var props = params.properties || {}, iframe; + if (params.iframe) iframe = document.id(params.iframe); + var onload = props.onload || function(){}; + delete props.onload; + props.id = props.name = [props.id, props.name, iframe ? (iframe.id || iframe.name) : 'IFrame_' + String.uniqueID()].pick(); + iframe = new Element(iframe || 'iframe', props); + + var onLoad = function(){ + onload.call(iframe.contentWindow); + }; + + if (window.frames[props.id]) onLoad(); + else iframe.addListener('load', onLoad); + return iframe; +}); + +var Elements = this.Elements = function(nodes){ + if (nodes && nodes.length){ + var uniques = {}, node; + for (var i = 0; node = nodes[i++];){ + var uid = Slick.uidOf(node); + if (!uniques[uid]){ + uniques[uid] = true; + this.push(node); + } + } + } +}; + +Elements.prototype = {length: 0}; +Elements.parent = Array; + +new Type('Elements', Elements).implement({ + + filter: function(filter, bind){ + if (!filter) return this; + return new Elements(Array.filter(this, (typeOf(filter) == 'string') ? function(item){ + return item.match(filter); + } : filter, bind)); + }.protect(), + + push: function(){ + var length = this.length; + for (var i = 0, l = arguments.length; i < l; i++){ + var item = document.id(arguments[i]); + if (item) this[length++] = item; + } + return (this.length = length); + }.protect(), + + unshift: function(){ + var items = []; + for (var i = 0, l = arguments.length; i < l; i++){ + var item = document.id(arguments[i]); + if (item) items.push(item); + } + return Array.prototype.unshift.apply(this, items); + }.protect(), + + concat: function(){ + var newElements = new Elements(this); + for (var i = 0, l = arguments.length; i < l; i++){ + var item = arguments[i]; + if (Type.isEnumerable(item)) newElements.append(item); + else newElements.push(item); + } + return newElements; + }.protect(), + + append: function(collection){ + for (var i = 0, l = collection.length; i < l; i++) this.push(collection[i]); + return this; + }.protect(), + + empty: function(){ + while (this.length) delete this[--this.length]; + return this; + }.protect() + +}); + + + +(function(){ + +// FF, IE +var splice = Array.prototype.splice, object = {'0': 0, '1': 1, length: 2}; + +splice.call(object, 1, 1); +if (object[1] == 1) Elements.implement('splice', function(){ + var length = this.length; + var result = splice.apply(this, arguments); + while (length >= this.length) delete this[length--]; + return result; +}.protect()); + +Array.forEachMethod(function(method, name){ + Elements.implement(name, method); +}); + +Array.mirror(Elements); + +/*<ltIE8>*/ +var createElementAcceptsHTML; +try { + createElementAcceptsHTML = (document.createElement('<input name=x>').name == 'x'); +} catch (e){} + +var escapeQuotes = function(html){ + return ('' + html).replace(/&/g, '&').replace(/"/g, '"'); +}; +/*</ltIE8>*/ + +/*<ltIE9>*/ +// #2479 - IE8 Cannot set HTML of style element +var canChangeStyleHTML = (function(){ + var div = document.createElement('style'), + flag = false; + try { + div.innerHTML = '#justTesing{margin: 0px;}'; + flag = !!div.innerHTML; + } catch(e){} + return flag; +})(); +/*</ltIE9>*/ + +Document.implement({ + + newElement: function(tag, props){ + if (props){ + if (props.checked != null) props.defaultChecked = props.checked; + if ((props.type == 'checkbox' || props.type == 'radio') && props.value == null) props.value = 'on'; + /*<ltIE9>*/ // IE needs the type to be set before changing content of style element + if (!canChangeStyleHTML && tag == 'style'){ + var styleElement = document.createElement('style'); + styleElement.setAttribute('type', 'text/css'); + if (props.type) delete props.type; + return this.id(styleElement).set(props); + } + /*</ltIE9>*/ + /*<ltIE8>*/// Fix for readonly name and type properties in IE < 8 + if (createElementAcceptsHTML){ + tag = '<' + tag; + if (props.name) tag += ' name="' + escapeQuotes(props.name) + '"'; + if (props.type) tag += ' type="' + escapeQuotes(props.type) + '"'; + tag += '>'; + delete props.name; + delete props.type; + } + /*</ltIE8>*/ + } + return this.id(this.createElement(tag)).set(props); + } + +}); + +})(); + +(function(){ + +Slick.uidOf(window); +Slick.uidOf(document); + +Document.implement({ + + newTextNode: function(text){ + return this.createTextNode(text); + }, + + getDocument: function(){ + return this; + }, + + getWindow: function(){ + return this.window; + }, + + id: (function(){ + + var types = { + + string: function(id, nocash, doc){ + id = Slick.find(doc, '#' + id.replace(/(\W)/g, '\\$1')); + return (id) ? types.element(id, nocash) : null; + }, + + element: function(el, nocash){ + Slick.uidOf(el); + if (!nocash && !el.$family && !(/^(?:object|embed)$/i).test(el.tagName)){ + var fireEvent = el.fireEvent; + // wrapping needed in IE7, or else crash + el._fireEvent = function(type, event){ + return fireEvent(type, event); + }; + Object.append(el, Element.Prototype); + } + return el; + }, + + object: function(obj, nocash, doc){ + if (obj.toElement) return types.element(obj.toElement(doc), nocash); + return null; + } + + }; + + types.textnode = types.whitespace = types.window = types.document = function(zero){ + return zero; + }; + + return function(el, nocash, doc){ + if (el && el.$family && el.uniqueNumber) return el; + var type = typeOf(el); + return (types[type]) ? types[type](el, nocash, doc || document) : null; + }; + + })() + +}); + +if (window.$ == null) Window.implement('$', function(el, nc){ + return document.id(el, nc, this.document); +}); + +Window.implement({ + + getDocument: function(){ + return this.document; + }, + + getWindow: function(){ + return this; + } + +}); + +[Document, Element].invoke('implement', { + + getElements: function(expression){ + return Slick.search(this, expression, new Elements); + }, + + getElement: function(expression){ + return document.id(Slick.find(this, expression)); + } + +}); + +var contains = {contains: function(element){ + return Slick.contains(this, element); +}}; + +if (!document.contains) Document.implement(contains); +if (!document.createElement('div').contains) Element.implement(contains); + + + +// tree walking + +var injectCombinator = function(expression, combinator){ + if (!expression) return combinator; + + expression = Object.clone(Slick.parse(expression)); + + var expressions = expression.expressions; + for (var i = expressions.length; i--;) + expressions[i][0].combinator = combinator; + + return expression; +}; + +Object.forEach({ + getNext: '~', + getPrevious: '!~', + getParent: '!' +}, function(combinator, method){ + Element.implement(method, function(expression){ + return this.getElement(injectCombinator(expression, combinator)); + }); +}); + +Object.forEach({ + getAllNext: '~', + getAllPrevious: '!~', + getSiblings: '~~', + getChildren: '>', + getParents: '!' +}, function(combinator, method){ + Element.implement(method, function(expression){ + return this.getElements(injectCombinator(expression, combinator)); + }); +}); + +Element.implement({ + + getFirst: function(expression){ + return document.id(Slick.search(this, injectCombinator(expression, '>'))[0]); + }, + + getLast: function(expression){ + return document.id(Slick.search(this, injectCombinator(expression, '>')).getLast()); + }, + + getWindow: function(){ + return this.ownerDocument.window; + }, + + getDocument: function(){ + return this.ownerDocument; + }, + + getElementById: function(id){ + return document.id(Slick.find(this, '#' + ('' + id).replace(/(\W)/g, '\\$1'))); + }, + + match: function(expression){ + return !expression || Slick.match(this, expression); + } + +}); + + + +if (window.$$ == null) Window.implement('$$', function(selector){ + if (arguments.length == 1){ + if (typeof selector == 'string') return Slick.search(this.document, selector, new Elements); + else if (Type.isEnumerable(selector)) return new Elements(selector); + } + return new Elements(arguments); +}); + +// Inserters + +var inserters = { + + before: function(context, element){ + var parent = element.parentNode; + if (parent) parent.insertBefore(context, element); + }, + + after: function(context, element){ + var parent = element.parentNode; + if (parent) parent.insertBefore(context, element.nextSibling); + }, + + bottom: function(context, element){ + element.appendChild(context); + }, + + top: function(context, element){ + element.insertBefore(context, element.firstChild); + } + +}; + +inserters.inside = inserters.bottom; + + + +// getProperty / setProperty + +var propertyGetters = {}, propertySetters = {}; + +// properties + +var properties = {}; +Array.forEach([ + 'type', 'value', 'defaultValue', 'accessKey', 'cellPadding', 'cellSpacing', 'colSpan', + 'frameBorder', 'rowSpan', 'tabIndex', 'useMap' +], function(property){ + properties[property.toLowerCase()] = property; +}); + +properties.html = 'innerHTML'; +properties.text = (document.createElement('div').textContent == null) ? 'innerText': 'textContent'; + +Object.forEach(properties, function(real, key){ + propertySetters[key] = function(node, value){ + node[real] = value; + }; + propertyGetters[key] = function(node){ + return node[real]; + }; +}); + +/*<ltIE9>*/ +propertySetters.text = (function(setter){ + return function(node, value){ + if (node.get('tag') == 'style') node.set('html', value); + else node[properties.text] = value; + }; +})(propertySetters.text); + +propertyGetters.text = (function(getter){ + return function(node){ + return (node.get('tag') == 'style') ? node.innerHTML : getter(node); + }; +})(propertyGetters.text); +/*</ltIE9>*/ + +// Booleans + +var bools = [ + 'compact', 'nowrap', 'ismap', 'declare', 'noshade', 'checked', + 'disabled', 'readOnly', 'multiple', 'selected', 'noresize', + 'defer', 'defaultChecked', 'autofocus', 'controls', 'autoplay', + 'loop' +]; + +var booleans = {}; +Array.forEach(bools, function(bool){ + var lower = bool.toLowerCase(); + booleans[lower] = bool; + propertySetters[lower] = function(node, value){ + node[bool] = !!value; + }; + propertyGetters[lower] = function(node){ + return !!node[bool]; + }; +}); + +// Special cases + +Object.append(propertySetters, { + + 'class': function(node, value){ + ('className' in node) ? node.className = (value || '') : node.setAttribute('class', value); + }, + + 'for': function(node, value){ + ('htmlFor' in node) ? node.htmlFor = value : node.setAttribute('for', value); + }, + + 'style': function(node, value){ + (node.style) ? node.style.cssText = value : node.setAttribute('style', value); + }, + + 'value': function(node, value){ + node.value = (value != null) ? value : ''; + } + +}); + +propertyGetters['class'] = function(node){ + return ('className' in node) ? node.className || null : node.getAttribute('class'); +}; + +/* <webkit> */ +var el = document.createElement('button'); +// IE sets type as readonly and throws +try { el.type = 'button'; } catch(e){} +if (el.type != 'button') propertySetters.type = function(node, value){ + node.setAttribute('type', value); +}; +el = null; +/* </webkit> */ + +/*<IE>*/ + +/*<ltIE9>*/ +// #2479 - IE8 Cannot set HTML of style element +var canChangeStyleHTML = (function(){ + var div = document.createElement('style'), + flag = false; + try { + div.innerHTML = '#justTesing{margin: 0px;}'; + flag = !!div.innerHTML; + } catch(e){} + return flag; +})(); +/*</ltIE9>*/ + +var input = document.createElement('input'), volatileInputValue, html5InputSupport; + +// #2178 +input.value = 't'; +input.type = 'submit'; +volatileInputValue = input.value != 't'; + +// #2443 - IE throws "Invalid Argument" when trying to use html5 input types +try { + input.type = 'email'; + html5InputSupport = input.type == 'email'; +} catch(e){} + +input = null; + +if (volatileInputValue || !html5InputSupport) propertySetters.type = function(node, type){ + try { + var value = node.value; + node.type = type; + node.value = value; + } catch (e){} +}; +/*</IE>*/ + +/* getProperty, setProperty */ + +/* <ltIE9> */ +var pollutesGetAttribute = (function(div){ + div.random = 'attribute'; + return (div.getAttribute('random') == 'attribute'); +})(document.createElement('div')); + +var hasCloneBug = (function(test){ + test.innerHTML = '<object><param name="should_fix" value="the unknown" /></object>'; + return test.cloneNode(true).firstChild.childNodes.length != 1; +})(document.createElement('div')); +/* </ltIE9> */ + +var hasClassList = !!document.createElement('div').classList; + +var classes = function(className){ + var classNames = (className || '').clean().split(" "), uniques = {}; + return classNames.filter(function(className){ + if (className !== "" && !uniques[className]) return uniques[className] = className; + }); +}; + +var addToClassList = function(name){ + this.classList.add(name); +}; + +var removeFromClassList = function(name){ + this.classList.remove(name); +}; + +Element.implement({ + + setProperty: function(name, value){ + var setter = propertySetters[name.toLowerCase()]; + if (setter){ + setter(this, value); + } else { + /* <ltIE9> */ + var attributeWhiteList; + if (pollutesGetAttribute) attributeWhiteList = this.retrieve('$attributeWhiteList', {}); + /* </ltIE9> */ + + if (value == null){ + this.removeAttribute(name); + /* <ltIE9> */ + if (pollutesGetAttribute) delete attributeWhiteList[name]; + /* </ltIE9> */ + } else { + this.setAttribute(name, '' + value); + /* <ltIE9> */ + if (pollutesGetAttribute) attributeWhiteList[name] = true; + /* </ltIE9> */ + } + } + return this; + }, + + setProperties: function(attributes){ + for (var attribute in attributes) this.setProperty(attribute, attributes[attribute]); + return this; + }, + + getProperty: function(name){ + var getter = propertyGetters[name.toLowerCase()]; + if (getter) return getter(this); + /* <ltIE9> */ + if (pollutesGetAttribute){ + var attr = this.getAttributeNode(name), attributeWhiteList = this.retrieve('$attributeWhiteList', {}); + if (!attr) return null; + if (attr.expando && !attributeWhiteList[name]){ + var outer = this.outerHTML; + // segment by the opening tag and find mention of attribute name + if (outer.substr(0, outer.search(/\/?['"]?>(?![^<]*<['"])/)).indexOf(name) < 0) return null; + attributeWhiteList[name] = true; + } + } + /* </ltIE9> */ + var result = Slick.getAttribute(this, name); + return (!result && !Slick.hasAttribute(this, name)) ? null : result; + }, + + getProperties: function(){ + var args = Array.from(arguments); + return args.map(this.getProperty, this).associate(args); + }, + + removeProperty: function(name){ + return this.setProperty(name, null); + }, + + removeProperties: function(){ + Array.each(arguments, this.removeProperty, this); + return this; + }, + + set: function(prop, value){ + var property = Element.Properties[prop]; + (property && property.set) ? property.set.call(this, value) : this.setProperty(prop, value); + }.overloadSetter(), + + get: function(prop){ + var property = Element.Properties[prop]; + return (property && property.get) ? property.get.apply(this) : this.getProperty(prop); + }.overloadGetter(), + + erase: function(prop){ + var property = Element.Properties[prop]; + (property && property.erase) ? property.erase.apply(this) : this.removeProperty(prop); + return this; + }, + + hasClass: hasClassList ? function(className){ + return this.classList.contains(className); + } : function(className){ + return classes(this.className).contains(className); + }, + + addClass: hasClassList ? function(className){ + classes(className).forEach(addToClassList, this); + return this; + } : function(className){ + this.className = classes(className + ' ' + this.className).join(' '); + return this; + }, + + removeClass: hasClassList ? function(className){ + classes(className).forEach(removeFromClassList, this); + return this; + } : function(className){ + var classNames = classes(this.className); + classes(className).forEach(classNames.erase, classNames); + this.className = classNames.join(' '); + return this; + }, + + toggleClass: function(className, force){ + if (force == null) force = !this.hasClass(className); + return (force) ? this.addClass(className) : this.removeClass(className); + }, + + adopt: function(){ + var parent = this, fragment, elements = Array.flatten(arguments), length = elements.length; + if (length > 1) parent = fragment = document.createDocumentFragment(); + + for (var i = 0; i < length; i++){ + var element = document.id(elements[i], true); + if (element) parent.appendChild(element); + } + + if (fragment) this.appendChild(fragment); + + return this; + }, + + appendText: function(text, where){ + return this.grab(this.getDocument().newTextNode(text), where); + }, + + grab: function(el, where){ + inserters[where || 'bottom'](document.id(el, true), this); + return this; + }, + + inject: function(el, where){ + inserters[where || 'bottom'](this, document.id(el, true)); + return this; + }, + + replaces: function(el){ + el = document.id(el, true); + el.parentNode.replaceChild(this, el); + return this; + }, + + wraps: function(el, where){ + el = document.id(el, true); + return this.replaces(el).grab(el, where); + }, + + getSelected: function(){ + this.selectedIndex; // Safari 3.2.1 + return new Elements(Array.from(this.options).filter(function(option){ + return option.selected; + })); + }, + + toQueryString: function(){ + var queryString = []; + this.getElements('input, select, textarea').each(function(el){ + var type = el.type; + if (!el.name || el.disabled || type == 'submit' || type == 'reset' || type == 'file' || type == 'image') return; + + var value = (el.get('tag') == 'select') ? el.getSelected().map(function(opt){ + // IE + return document.id(opt).get('value'); + }) : ((type == 'radio' || type == 'checkbox') && !el.checked) ? null : el.get('value'); + + Array.from(value).each(function(val){ + if (typeof val != 'undefined') queryString.push(encodeURIComponent(el.name) + '=' + encodeURIComponent(val)); + }); + }); + return queryString.join('&'); + } + +}); + + +// appendHTML + +var appendInserters = { + before: 'beforeBegin', + after: 'afterEnd', + bottom: 'beforeEnd', + top: 'afterBegin', + inside: 'beforeEnd' +}; + +Element.implement('appendHTML', ('insertAdjacentHTML' in document.createElement('div')) ? function(html, where){ + this.insertAdjacentHTML(appendInserters[where || 'bottom'], html); + return this; +} : function(html, where){ + var temp = new Element('div', {html: html}), + children = temp.childNodes, + fragment = temp.firstChild; + + if (!fragment) return this; + if (children.length > 1){ + fragment = document.createDocumentFragment(); + for (var i = 0, l = children.length; i < l; i++){ + fragment.appendChild(children[i]); + } + } + + inserters[where || 'bottom'](fragment, this); + return this; +}); + +var collected = {}, storage = {}; + +var get = function(uid){ + return (storage[uid] || (storage[uid] = {})); +}; + +var clean = function(item){ + var uid = item.uniqueNumber; + if (item.removeEvents) item.removeEvents(); + if (item.clearAttributes) item.clearAttributes(); + if (uid != null){ + delete collected[uid]; + delete storage[uid]; + } + return item; +}; + +var formProps = {input: 'checked', option: 'selected', textarea: 'value'}; + +Element.implement({ + + destroy: function(){ + var children = clean(this).getElementsByTagName('*'); + Array.each(children, clean); + Element.dispose(this); + return null; + }, + + empty: function(){ + Array.from(this.childNodes).each(Element.dispose); + return this; + }, + + dispose: function(){ + return (this.parentNode) ? this.parentNode.removeChild(this) : this; + }, + + clone: function(contents, keepid){ + contents = contents !== false; + var clone = this.cloneNode(contents), ce = [clone], te = [this], i; + + if (contents){ + ce.append(Array.from(clone.getElementsByTagName('*'))); + te.append(Array.from(this.getElementsByTagName('*'))); + } + + for (i = ce.length; i--;){ + var node = ce[i], element = te[i]; + if (!keepid) node.removeAttribute('id'); + /*<ltIE9>*/ + if (node.clearAttributes){ + node.clearAttributes(); + node.mergeAttributes(element); + node.removeAttribute('uniqueNumber'); + if (node.options){ + var no = node.options, eo = element.options; + for (var j = no.length; j--;) no[j].selected = eo[j].selected; + } + } + /*</ltIE9>*/ + var prop = formProps[element.tagName.toLowerCase()]; + if (prop && element[prop]) node[prop] = element[prop]; + } + + /*<ltIE9>*/ + if (hasCloneBug){ + var co = clone.getElementsByTagName('object'), to = this.getElementsByTagName('object'); + for (i = co.length; i--;) co[i].outerHTML = to[i].outerHTML; + } + /*</ltIE9>*/ + return document.id(clone); + } + +}); + +[Element, Window, Document].invoke('implement', { + + addListener: function(type, fn){ + if (window.attachEvent && !window.addEventListener){ + collected[Slick.uidOf(this)] = this; + } + if (this.addEventListener) this.addEventListener(type, fn, !!arguments[2]); + else this.attachEvent('on' + type, fn); + return this; + }, + + removeListener: function(type, fn){ + if (this.removeEventListener) this.removeEventListener(type, fn, !!arguments[2]); + else this.detachEvent('on' + type, fn); + return this; + }, + + retrieve: function(property, dflt){ + var storage = get(Slick.uidOf(this)), prop = storage[property]; + if (dflt != null && prop == null) prop = storage[property] = dflt; + return prop != null ? prop : null; + }, + + store: function(property, value){ + var storage = get(Slick.uidOf(this)); + storage[property] = value; + return this; + }, + + eliminate: function(property){ + var storage = get(Slick.uidOf(this)); + delete storage[property]; + return this; + } + +}); + +/*<ltIE9>*/ +if (window.attachEvent && !window.addEventListener){ + var gc = function(){ + Object.each(collected, clean); + if (window.CollectGarbage) CollectGarbage(); + window.removeListener('unload', gc); + } + window.addListener('unload', gc); +} +/*</ltIE9>*/ + +Element.Properties = {}; + + + +Element.Properties.style = { + + set: function(style){ + this.style.cssText = style; + }, + + get: function(){ + return this.style.cssText; + }, + + erase: function(){ + this.style.cssText = ''; + } + +}; + +Element.Properties.tag = { + + get: function(){ + return this.tagName.toLowerCase(); + } + +}; + +Element.Properties.html = { + + set: function(html){ + if (html == null) html = ''; + else if (typeOf(html) == 'array') html = html.join(''); + + /*<ltIE9>*/ + if (this.styleSheet && !canChangeStyleHTML) this.styleSheet.cssText = html; + else /*</ltIE9>*/this.innerHTML = html; + }, + erase: function(){ + this.set('html', ''); + } + +}; + +var supportsHTML5Elements = true, supportsTableInnerHTML = true, supportsTRInnerHTML = true; + +/*<ltIE9>*/ +// technique by jdbarlett - http://jdbartlett.com/innershiv/ +var div = document.createElement('div'); +div.innerHTML = '<nav></nav>'; +supportsHTML5Elements = (div.childNodes.length == 1); +if (!supportsHTML5Elements){ + var tags = 'abbr article aside audio canvas datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video'.split(' '), + fragment = document.createDocumentFragment(), l = tags.length; + while (l--) fragment.createElement(tags[l]); +} +div = null; +/*</ltIE9>*/ + +/*<IE>*/ +supportsTableInnerHTML = Function.attempt(function(){ + var table = document.createElement('table'); + table.innerHTML = '<tr><td></td></tr>'; + return true; +}); + +/*<ltFF4>*/ +var tr = document.createElement('tr'), html = '<td></td>'; +tr.innerHTML = html; +supportsTRInnerHTML = (tr.innerHTML == html); +tr = null; +/*</ltFF4>*/ + +if (!supportsTableInnerHTML || !supportsTRInnerHTML || !supportsHTML5Elements){ + + Element.Properties.html.set = (function(set){ + + var translations = { + table: [1, '<table>', '</table>'], + select: [1, '<select>', '</select>'], + tbody: [2, '<table><tbody>', '</tbody></table>'], + tr: [3, '<table><tbody><tr>', '</tr></tbody></table>'] + }; + + translations.thead = translations.tfoot = translations.tbody; + + return function(html){ + + /*<ltIE9>*/ + if (this.styleSheet) return set.call(this, html); + /*</ltIE9>*/ + var wrap = translations[this.get('tag')]; + if (!wrap && !supportsHTML5Elements) wrap = [0, '', '']; + if (!wrap) return set.call(this, html); + + var level = wrap[0], wrapper = document.createElement('div'), target = wrapper; + if (!supportsHTML5Elements) fragment.appendChild(wrapper); + wrapper.innerHTML = [wrap[1], html, wrap[2]].flatten().join(''); + while (level--) target = target.firstChild; + this.empty().adopt(target.childNodes); + if (!supportsHTML5Elements) fragment.removeChild(wrapper); + wrapper = null; + }; + + })(Element.Properties.html.set); +} +/*</IE>*/ + +/*<ltIE9>*/ +var testForm = document.createElement('form'); +testForm.innerHTML = '<select><option>s</option></select>'; + +if (testForm.firstChild.value != 's') Element.Properties.value = { + + set: function(value){ + var tag = this.get('tag'); + if (tag != 'select') return this.setProperty('value', value); + var options = this.getElements('option'); + value = String(value); + for (var i = 0; i < options.length; i++){ + var option = options[i], + attr = option.getAttributeNode('value'), + optionValue = (attr && attr.specified) ? option.value : option.get('text'); + if (optionValue === value) return option.selected = true; + } + }, + + get: function(){ + var option = this, tag = option.get('tag'); + + if (tag != 'select' && tag != 'option') return this.getProperty('value'); + + if (tag == 'select' && !(option = option.getSelected()[0])) return ''; + + var attr = option.getAttributeNode('value'); + return (attr && attr.specified) ? option.value : option.get('text'); + } + +}; +testForm = null; +/*</ltIE9>*/ + +/*<IE>*/ +if (document.createElement('div').getAttributeNode('id')) Element.Properties.id = { + set: function(id){ + this.id = this.getAttributeNode('id').value = id; + }, + get: function(){ + return this.id || null; + }, + erase: function(){ + this.id = this.getAttributeNode('id').value = ''; + } +}; +/*</IE>*/ + +})(); + +/* +--- + +name: Event + +description: Contains the Event Type, to make the event object cross-browser. + +license: MIT-style license. + +requires: [Window, Document, Array, Function, String, Object] + +provides: Event + +... +*/ + +(function(){ + +var _keys = {}; +var normalizeWheelSpeed = function(event){ + var normalized; + if (event.wheelDelta){ + normalized = event.wheelDelta % 120 == 0 ? event.wheelDelta / 120 : event.wheelDelta / 12; + } else { + var rawAmount = event.deltaY || event.detail || 0; + normalized = -(rawAmount % 3 == 0 ? rawAmount / 3 : rawAmount * 10); + } + return normalized; +} + +var DOMEvent = this.DOMEvent = new Type('DOMEvent', function(event, win){ + if (!win) win = window; + event = event || win.event; + if (event.$extended) return event; + this.event = event; + this.$extended = true; + this.shift = event.shiftKey; + this.control = event.ctrlKey; + this.alt = event.altKey; + this.meta = event.metaKey; + var type = this.type = event.type; + var target = event.target || event.srcElement; + while (target && target.nodeType == 3) target = target.parentNode; + this.target = document.id(target); + + if (type.indexOf('key') == 0){ + var code = this.code = (event.which || event.keyCode); + this.key = _keys[code]; + if (type == 'keydown' || type == 'keyup'){ + if (code > 111 && code < 124) this.key = 'f' + (code - 111); + else if (code > 95 && code < 106) this.key = code - 96; + } + if (this.key == null) this.key = String.fromCharCode(code).toLowerCase(); + } else if (type == 'click' || type == 'dblclick' || type == 'contextmenu' || type == 'wheel' || type == 'DOMMouseScroll' || type.indexOf('mouse') == 0){ + var doc = win.document; + doc = (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body; + this.page = { + x: (event.pageX != null) ? event.pageX : event.clientX + doc.scrollLeft, + y: (event.pageY != null) ? event.pageY : event.clientY + doc.scrollTop + }; + this.client = { + x: (event.pageX != null) ? event.pageX - win.pageXOffset : event.clientX, + y: (event.pageY != null) ? event.pageY - win.pageYOffset : event.clientY + }; + if (type == 'DOMMouseScroll' || type == 'wheel' || type == 'mousewheel') this.wheel = normalizeWheelSpeed(event); + this.rightClick = (event.which == 3 || event.button == 2); + if (type == 'mouseover' || type == 'mouseout'){ + var related = event.relatedTarget || event[(type == 'mouseover' ? 'from' : 'to') + 'Element']; + while (related && related.nodeType == 3) related = related.parentNode; + this.relatedTarget = document.id(related); + } + } else if (type.indexOf('touch') == 0 || type.indexOf('gesture') == 0){ + this.rotation = event.rotation; + this.scale = event.scale; + this.targetTouches = event.targetTouches; + this.changedTouches = event.changedTouches; + var touches = this.touches = event.touches; + if (touches && touches[0]){ + var touch = touches[0]; + this.page = {x: touch.pageX, y: touch.pageY}; + this.client = {x: touch.clientX, y: touch.clientY}; + } + } + + if (!this.client) this.client = {}; + if (!this.page) this.page = {}; +}); + +DOMEvent.implement({ + + stop: function(){ + return this.preventDefault().stopPropagation(); + }, + + stopPropagation: function(){ + if (this.event.stopPropagation) this.event.stopPropagation(); + else this.event.cancelBubble = true; + return this; + }, + + preventDefault: function(){ + if (this.event.preventDefault) this.event.preventDefault(); + else this.event.returnValue = false; + return this; + } + +}); + +DOMEvent.defineKey = function(code, key){ + _keys[code] = key; + return this; +}; + +DOMEvent.defineKeys = DOMEvent.defineKey.overloadSetter(true); + +DOMEvent.defineKeys({ + '38': 'up', '40': 'down', '37': 'left', '39': 'right', + '27': 'esc', '32': 'space', '8': 'backspace', '9': 'tab', + '46': 'delete', '13': 'enter' +}); + +})(); + + + + + +/* +--- + +name: Element.Event + +description: Contains Element methods for dealing with events. This file also includes mouseenter and mouseleave custom Element Events, if necessary. + +license: MIT-style license. + +requires: [Element, Event] + +provides: Element.Event + +... +*/ + +(function(){ + +Element.Properties.events = {set: function(events){ + this.addEvents(events); +}}; + +[Element, Window, Document].invoke('implement', { + + addEvent: function(type, fn){ + var events = this.retrieve('events', {}); + if (!events[type]) events[type] = {keys: [], values: []}; + if (events[type].keys.contains(fn)) return this; + events[type].keys.push(fn); + var realType = type, + custom = Element.Events[type], + condition = fn, + self = this; + if (custom){ + if (custom.onAdd) custom.onAdd.call(this, fn, type); + if (custom.condition){ + condition = function(event){ + if (custom.condition.call(this, event, type)) return fn.call(this, event); + return true; + }; + } + if (custom.base) realType = Function.from(custom.base).call(this, type); + } + var defn = function(){ + return fn.call(self); + }; + var nativeEvent = Element.NativeEvents[realType]; + if (nativeEvent){ + if (nativeEvent == 2){ + defn = function(event){ + event = new DOMEvent(event, self.getWindow()); + if (condition.call(self, event) === false) event.stop(); + }; + } + this.addListener(realType, defn, arguments[2]); + } + events[type].values.push(defn); + return this; + }, + + removeEvent: function(type, fn){ + var events = this.retrieve('events'); + if (!events || !events[type]) return this; + var list = events[type]; + var index = list.keys.indexOf(fn); + if (index == -1) return this; + var value = list.values[index]; + delete list.keys[index]; + delete list.values[index]; + var custom = Element.Events[type]; + if (custom){ + if (custom.onRemove) custom.onRemove.call(this, fn, type); + if (custom.base) type = Function.from(custom.base).call(this, type); + } + return (Element.NativeEvents[type]) ? this.removeListener(type, value, arguments[2]) : this; + }, + + addEvents: function(events){ + for (var event in events) this.addEvent(event, events[event]); + return this; + }, + + removeEvents: function(events){ + var type; + if (typeOf(events) == 'object'){ + for (type in events) this.removeEvent(type, events[type]); + return this; + } + var attached = this.retrieve('events'); + if (!attached) return this; + if (!events){ + for (type in attached) this.removeEvents(type); + this.eliminate('events'); + } else if (attached[events]){ + attached[events].keys.each(function(fn){ + this.removeEvent(events, fn); + }, this); + delete attached[events]; + } + return this; + }, + + fireEvent: function(type, args, delay){ + var events = this.retrieve('events'); + if (!events || !events[type]) return this; + args = Array.from(args); + + events[type].keys.each(function(fn){ + if (delay) fn.delay(delay, this, args); + else fn.apply(this, args); + }, this); + return this; + }, + + cloneEvents: function(from, type){ + from = document.id(from); + var events = from.retrieve('events'); + if (!events) return this; + if (!type){ + for (var eventType in events) this.cloneEvents(from, eventType); + } else if (events[type]){ + events[type].keys.each(function(fn){ + this.addEvent(type, fn); + }, this); + } + return this; + } + +}); + +Element.NativeEvents = { + click: 2, dblclick: 2, mouseup: 2, mousedown: 2, contextmenu: 2, //mouse buttons + wheel: 2, mousewheel: 2, DOMMouseScroll: 2, //mouse wheel + mouseover: 2, mouseout: 2, mousemove: 2, selectstart: 2, selectend: 2, //mouse movement + keydown: 2, keypress: 2, keyup: 2, //keyboard + orientationchange: 2, // mobile + touchstart: 2, touchmove: 2, touchend: 2, touchcancel: 2, // touch + gesturestart: 2, gesturechange: 2, gestureend: 2, // gesture + focus: 2, blur: 2, change: 2, reset: 2, select: 2, submit: 2, paste: 2, input: 2, //form elements + load: 2, unload: 1, beforeunload: 2, resize: 1, move: 1, DOMContentLoaded: 1, readystatechange: 1, //window + hashchange: 1, popstate: 2, // history + error: 1, abort: 1, scroll: 1, message: 2 //misc +}; + +Element.Events = { + mousewheel: { + base: 'onwheel' in document ? 'wheel' : 'onmousewheel' in document ? 'mousewheel' : 'DOMMouseScroll' + } +}; + +var check = function(event){ + var related = event.relatedTarget; + if (related == null) return true; + if (!related) return false; + return (related != this && related.prefix != 'xul' && typeOf(this) != 'document' && !this.contains(related)); +}; + +if ('onmouseenter' in document.documentElement){ + Element.NativeEvents.mouseenter = Element.NativeEvents.mouseleave = 2; + Element.MouseenterCheck = check; +} else { + Element.Events.mouseenter = { + base: 'mouseover', + condition: check + }; + + Element.Events.mouseleave = { + base: 'mouseout', + condition: check + }; +} + +/*<ltIE9>*/ +if (!window.addEventListener){ + Element.NativeEvents.propertychange = 2; + Element.Events.change = { + base: function(){ + var type = this.type; + return (this.get('tag') == 'input' && (type == 'radio' || type == 'checkbox')) ? 'propertychange' : 'change'; + }, + condition: function(event){ + return event.type != 'propertychange' || event.event.propertyName == 'checked'; + } + }; +} +/*</ltIE9>*/ + + + +})(); + +/* +--- + +name: Element.Delegation + +description: Extends the Element native object to include the delegate method for more efficient event management. + +license: MIT-style license. + +requires: [Element.Event] + +provides: [Element.Delegation] + +... +*/ + +(function(){ + +var eventListenerSupport = !!window.addEventListener; + +Element.NativeEvents.focusin = Element.NativeEvents.focusout = 2; + +var bubbleUp = function(self, match, fn, event, target){ + while (target && target != self){ + if (match(target, event)) return fn.call(target, event, target); + target = document.id(target.parentNode); + } +}; + +var map = { + mouseenter: { + base: 'mouseover', + condition: Element.MouseenterCheck + }, + mouseleave: { + base: 'mouseout', + condition: Element.MouseenterCheck + }, + focus: { + base: 'focus' + (eventListenerSupport ? '' : 'in'), + capture: true + }, + blur: { + base: eventListenerSupport ? 'blur' : 'focusout', + capture: true + } +}; + +/*<ltIE9>*/ +var _key = '$delegation:'; +var formObserver = function(type){ + + return { + + base: 'focusin', + + remove: function(self, uid){ + var list = self.retrieve(_key + type + 'listeners', {})[uid]; + if (list && list.forms) for (var i = list.forms.length; i--;){ + // the form may have been destroyed, so it won't have the + // removeEvent method anymore. In that case the event was + // removed as well. + if (list.forms[i].removeEvent) list.forms[i].removeEvent(type, list.fns[i]); + } + }, + + listen: function(self, match, fn, event, target, uid){ + var form = (target.get('tag') == 'form') ? target : event.target.getParent('form'); + if (!form) return; + + var listeners = self.retrieve(_key + type + 'listeners', {}), + listener = listeners[uid] || {forms: [], fns: []}, + forms = listener.forms, fns = listener.fns; + + if (forms.indexOf(form) != -1) return; + forms.push(form); + + var _fn = function(event){ + bubbleUp(self, match, fn, event, target); + }; + form.addEvent(type, _fn); + fns.push(_fn); + + listeners[uid] = listener; + self.store(_key + type + 'listeners', listeners); + } + }; +}; + +var inputObserver = function(type){ + return { + base: 'focusin', + listen: function(self, match, fn, event, target){ + var events = {blur: function(){ + this.removeEvents(events); + }}; + events[type] = function(event){ + bubbleUp(self, match, fn, event, target); + }; + event.target.addEvents(events); + } + }; +}; + +if (!eventListenerSupport) Object.append(map, { + submit: formObserver('submit'), + reset: formObserver('reset'), + change: inputObserver('change'), + select: inputObserver('select') +}); +/*</ltIE9>*/ + +var proto = Element.prototype, + addEvent = proto.addEvent, + removeEvent = proto.removeEvent; + +var relay = function(old, method){ + return function(type, fn, useCapture){ + if (type.indexOf(':relay') == -1) return old.call(this, type, fn, useCapture); + var parsed = Slick.parse(type).expressions[0][0]; + if (parsed.pseudos[0].key != 'relay') return old.call(this, type, fn, useCapture); + var newType = parsed.tag; + parsed.pseudos.slice(1).each(function(pseudo){ + newType += ':' + pseudo.key + (pseudo.value ? '(' + pseudo.value + ')' : ''); + }); + old.call(this, type, fn); + return method.call(this, newType, parsed.pseudos[0].value, fn); + }; +}; + +var delegation = { + + addEvent: function(type, match, fn){ + var storage = this.retrieve('$delegates', {}), stored = storage[type]; + if (stored) for (var _uid in stored){ + if (stored[_uid].fn == fn && stored[_uid].match == match) return this; + } + + var _type = type, _match = match, _fn = fn, _map = map[type] || {}; + type = _map.base || _type; + + match = function(target){ + return Slick.match(target, _match); + }; + + var elementEvent = Element.Events[_type]; + if (_map.condition || elementEvent && elementEvent.condition){ + var __match = match, condition = _map.condition || elementEvent.condition; + match = function(target, event){ + return __match(target, event) && condition.call(target, event, type); + }; + } + + var self = this, uid = String.uniqueID(); + var delegator = _map.listen ? function(event, target){ + if (!target && event && event.target) target = event.target; + if (target) _map.listen(self, match, fn, event, target, uid); + } : function(event, target){ + if (!target && event && event.target) target = event.target; + if (target) bubbleUp(self, match, fn, event, target); + }; + + if (!stored) stored = {}; + stored[uid] = { + match: _match, + fn: _fn, + delegator: delegator + }; + storage[_type] = stored; + return addEvent.call(this, type, delegator, _map.capture); + }, + + removeEvent: function(type, match, fn, _uid){ + var storage = this.retrieve('$delegates', {}), stored = storage[type]; + if (!stored) return this; + + if (_uid){ + var _type = type, delegator = stored[_uid].delegator, _map = map[type] || {}; + type = _map.base || _type; + if (_map.remove) _map.remove(this, _uid); + delete stored[_uid]; + storage[_type] = stored; + return removeEvent.call(this, type, delegator, _map.capture); + } + + var __uid, s; + if (fn) for (__uid in stored){ + s = stored[__uid]; + if (s.match == match && s.fn == fn) return delegation.removeEvent.call(this, type, match, fn, __uid); + } else for (__uid in stored){ + s = stored[__uid]; + if (s.match == match) delegation.removeEvent.call(this, type, match, s.fn, __uid); + } + return this; + } + +}; + +[Element, Window, Document].invoke('implement', { + addEvent: relay(addEvent, delegation.addEvent), + removeEvent: relay(removeEvent, delegation.removeEvent) +}); + +})(); + +/* +--- + +name: Element.Style + +description: Contains methods for interacting with the styles of Elements in a fashionable way. + +license: MIT-style license. + +requires: Element + +provides: Element.Style + +... +*/ + +(function(){ + +var html = document.html, el; + +//<ltIE9> +// Check for oldIE, which does not remove styles when they're set to null +el = document.createElement('div'); +el.style.color = 'red'; +el.style.color = null; +var doesNotRemoveStyles = el.style.color == 'red'; + +// check for oldIE, which returns border* shorthand styles in the wrong order (color-width-style instead of width-style-color) +var border = '1px solid #123abc'; +el.style.border = border; +var returnsBordersInWrongOrder = el.style.border != border; +el = null; +//</ltIE9> + +var hasGetComputedStyle = !!window.getComputedStyle, + supportBorderRadius = document.createElement('div').style.borderRadius != null; + +Element.Properties.styles = {set: function(styles){ + this.setStyles(styles); +}}; + +var hasOpacity = (html.style.opacity != null), + hasFilter = (html.style.filter != null), + reAlpha = /alpha\(opacity=([\d.]+)\)/i; + +var setVisibility = function(element, opacity){ + element.store('$opacity', opacity); + element.style.visibility = opacity > 0 || opacity == null ? 'visible' : 'hidden'; +}; + +//<ltIE9> +var setFilter = function(element, regexp, value){ + var style = element.style, + filter = style.filter || element.getComputedStyle('filter') || ''; + style.filter = (regexp.test(filter) ? filter.replace(regexp, value) : filter + ' ' + value).trim(); + if (!style.filter) style.removeAttribute('filter'); +}; +//</ltIE9> + +var setOpacity = (hasOpacity ? function(element, opacity){ + element.style.opacity = opacity; +} : (hasFilter ? function(element, opacity){ + if (!element.currentStyle || !element.currentStyle.hasLayout) element.style.zoom = 1; + if (opacity == null || opacity == 1){ + setFilter(element, reAlpha, ''); + if (opacity == 1 && getOpacity(element) != 1) setFilter(element, reAlpha, 'alpha(opacity=100)'); + } else { + setFilter(element, reAlpha, 'alpha(opacity=' + (opacity * 100).limit(0, 100).round() + ')'); + } +} : setVisibility)); + +var getOpacity = (hasOpacity ? function(element){ + var opacity = element.style.opacity || element.getComputedStyle('opacity'); + return (opacity == '') ? 1 : opacity.toFloat(); +} : (hasFilter ? function(element){ + var filter = (element.style.filter || element.getComputedStyle('filter')), + opacity; + if (filter) opacity = filter.match(reAlpha); + return (opacity == null || filter == null) ? 1 : (opacity[1] / 100); +} : function(element){ + var opacity = element.retrieve('$opacity'); + if (opacity == null) opacity = (element.style.visibility == 'hidden' ? 0 : 1); + return opacity; +})); + +var floatName = (html.style.cssFloat == null) ? 'styleFloat' : 'cssFloat', + namedPositions = {left: '0%', top: '0%', center: '50%', right: '100%', bottom: '100%'}, + hasBackgroundPositionXY = (html.style.backgroundPositionX != null); + +//<ltIE9> +var removeStyle = function(style, property){ + if (property == 'backgroundPosition'){ + style.removeAttribute(property + 'X'); + property += 'Y'; + } + style.removeAttribute(property); +}; +//</ltIE9> + +Element.implement({ + + getComputedStyle: function(property){ + if (!hasGetComputedStyle && this.currentStyle) return this.currentStyle[property.camelCase()]; + var defaultView = Element.getDocument(this).defaultView, + computed = defaultView ? defaultView.getComputedStyle(this, null) : null; + return (computed) ? computed.getPropertyValue((property == floatName) ? 'float' : property.hyphenate()) : ''; + }, + + setStyle: function(property, value){ + if (property == 'opacity'){ + if (value != null) value = parseFloat(value); + setOpacity(this, value); + return this; + } + property = (property == 'float' ? floatName : property).camelCase(); + if (typeOf(value) != 'string'){ + var map = (Element.Styles[property] || '@').split(' '); + value = Array.from(value).map(function(val, i){ + if (!map[i]) return ''; + return (typeOf(val) == 'number') ? map[i].replace('@', Math.round(val)) : val; + }).join(' '); + } else if (value == String(Number(value))){ + value = Math.round(value); + } + this.style[property] = value; + //<ltIE9> + if ((value == '' || value == null) && doesNotRemoveStyles && this.style.removeAttribute){ + removeStyle(this.style, property); + } + //</ltIE9> + return this; + }, + + getStyle: function(property){ + if (property == 'opacity') return getOpacity(this); + property = (property == 'float' ? floatName : property).camelCase(); + if (supportBorderRadius && property.indexOf('borderRadius') != -1){ + return ['borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomRightRadius', 'borderBottomLeftRadius'].map(function(corner){ + return this.style[corner] || '0px'; + }, this).join(' '); + } + var result = this.style[property]; + if (!result || property == 'zIndex'){ + if (Element.ShortStyles.hasOwnProperty(property)){ + result = []; + for (var s in Element.ShortStyles[property]) result.push(this.getStyle(s)); + return result.join(' '); + } + result = this.getComputedStyle(property); + } + if (hasBackgroundPositionXY && /^backgroundPosition[XY]?$/.test(property)){ + return result.replace(/(top|right|bottom|left)/g, function(position){ + return namedPositions[position]; + }) || '0px'; + } + if (!result && property == 'backgroundPosition') return '0px 0px'; + if (result){ + result = String(result); + var color = result.match(/rgba?\([\d\s,]+\)/); + if (color) result = result.replace(color[0], color[0].rgbToHex()); + } + if (!hasGetComputedStyle && !this.style[property]){ + if ((/^(height|width)$/).test(property) && !(/px$/.test(result))){ + var values = (property == 'width') ? ['left', 'right'] : ['top', 'bottom'], size = 0; + values.each(function(value){ + size += this.getStyle('border-' + value + '-width').toInt() + this.getStyle('padding-' + value).toInt(); + }, this); + return this['offset' + property.capitalize()] - size + 'px'; + } + if ((/^border(.+)Width|margin|padding/).test(property) && isNaN(parseFloat(result))){ + return '0px'; + } + } + //<ltIE9> + if (returnsBordersInWrongOrder && /^border(Top|Right|Bottom|Left)?$/.test(property) && /^#/.test(result)){ + return result.replace(/^(.+)\s(.+)\s(.+)$/, '$2 $3 $1'); + } + //</ltIE9> + + return result; + }, + + setStyles: function(styles){ + for (var style in styles) this.setStyle(style, styles[style]); + return this; + }, + + getStyles: function(){ + var result = {}; + Array.flatten(arguments).each(function(key){ + result[key] = this.getStyle(key); + }, this); + return result; + } + +}); + +Element.Styles = { + left: '@px', top: '@px', bottom: '@px', right: '@px', + width: '@px', height: '@px', maxWidth: '@px', maxHeight: '@px', minWidth: '@px', minHeight: '@px', + backgroundColor: 'rgb(@, @, @)', backgroundSize: '@px', backgroundPosition: '@px @px', color: 'rgb(@, @, @)', + fontSize: '@px', letterSpacing: '@px', lineHeight: '@px', clip: 'rect(@px @px @px @px)', + margin: '@px @px @px @px', padding: '@px @px @px @px', border: '@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)', + borderWidth: '@px @px @px @px', borderStyle: '@ @ @ @', borderColor: 'rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)', + zIndex: '@', 'zoom': '@', fontWeight: '@', textIndent: '@px', opacity: '@', borderRadius: '@px @px @px @px' +}; + + + + + +Element.ShortStyles = {margin: {}, padding: {}, border: {}, borderWidth: {}, borderStyle: {}, borderColor: {}}; + +['Top', 'Right', 'Bottom', 'Left'].each(function(direction){ + var Short = Element.ShortStyles; + var All = Element.Styles; + ['margin', 'padding'].each(function(style){ + var sd = style + direction; + Short[style][sd] = All[sd] = '@px'; + }); + var bd = 'border' + direction; + Short.border[bd] = All[bd] = '@px @ rgb(@, @, @)'; + var bdw = bd + 'Width', bds = bd + 'Style', bdc = bd + 'Color'; + Short[bd] = {}; + Short.borderWidth[bdw] = Short[bd][bdw] = All[bdw] = '@px'; + Short.borderStyle[bds] = Short[bd][bds] = All[bds] = '@'; + Short.borderColor[bdc] = Short[bd][bdc] = All[bdc] = 'rgb(@, @, @)'; +}); + +if (hasBackgroundPositionXY) Element.ShortStyles.backgroundPosition = {backgroundPositionX: '@', backgroundPositionY: '@'}; +})(); + +/* +--- + +name: Element.Dimensions + +description: Contains methods to work with size, scroll, or positioning of Elements and the window object. + +license: MIT-style license. + +credits: + - Element positioning based on the [qooxdoo](http://qooxdoo.org/) code and smart browser fixes, [LGPL License](http://www.gnu.org/licenses/lgpl.html). + - Viewport dimensions based on [YUI](http://developer.yahoo.com/yui/) code, [BSD License](http://developer.yahoo.com/yui/license.html). + +requires: [Element, Element.Style] + +provides: [Element.Dimensions] + +... +*/ + +(function(){ + +var element = document.createElement('div'), + child = document.createElement('div'); +element.style.height = '0'; +element.appendChild(child); +var brokenOffsetParent = (child.offsetParent === element); +element = child = null; + +var heightComponents = ['height', 'paddingTop', 'paddingBottom', 'borderTopWidth', 'borderBottomWidth'], + widthComponents = ['width', 'paddingLeft', 'paddingRight', 'borderLeftWidth', 'borderRightWidth']; + +var svgCalculateSize = function(el){ + + var gCS = window.getComputedStyle(el), + bounds = {x: 0, y: 0}; + + heightComponents.each(function(css){ + bounds.y += parseFloat(gCS[css]); + }); + widthComponents.each(function(css){ + bounds.x += parseFloat(gCS[css]); + }); + return bounds; +}; + +var isOffset = function(el){ + return styleString(el, 'position') != 'static' || isBody(el); +}; + +var isOffsetStatic = function(el){ + return isOffset(el) || (/^(?:table|td|th)$/i).test(el.tagName); +}; + +Element.implement({ + + scrollTo: function(x, y){ + if (isBody(this)){ + this.getWindow().scrollTo(x, y); + } else { + this.scrollLeft = x; + this.scrollTop = y; + } + return this; + }, + + getSize: function(){ + if (isBody(this)) return this.getWindow().getSize(); + + //<ltIE9> + // This if clause is because IE8- cannot calculate getBoundingClientRect of elements with visibility hidden. + if (!window.getComputedStyle) return {x: this.offsetWidth, y: this.offsetHeight}; + //</ltIE9> + + // This svg section under, calling `svgCalculateSize()`, can be removed when FF fixed the svg size bug. + // Bug info: https://bugzilla.mozilla.org/show_bug.cgi?id=530985 + if (this.get('tag') == 'svg') return svgCalculateSize(this); + + var bounds = this.getBoundingClientRect(); + return {x: bounds.width, y: bounds.height}; + }, + + getScrollSize: function(){ + if (isBody(this)) return this.getWindow().getScrollSize(); + return {x: this.scrollWidth, y: this.scrollHeight}; + }, + + getScroll: function(){ + if (isBody(this)) return this.getWindow().getScroll(); + return {x: this.scrollLeft, y: this.scrollTop}; + }, + + getScrolls: function(){ + var element = this.parentNode, position = {x: 0, y: 0}; + while (element && !isBody(element)){ + position.x += element.scrollLeft; + position.y += element.scrollTop; + element = element.parentNode; + } + return position; + }, + + getOffsetParent: brokenOffsetParent ? function(){ + var element = this; + if (isBody(element) || styleString(element, 'position') == 'fixed') return null; + + var isOffsetCheck = (styleString(element, 'position') == 'static') ? isOffsetStatic : isOffset; + while ((element = element.parentNode)){ + if (isOffsetCheck(element)) return element; + } + return null; + } : function(){ + var element = this; + if (isBody(element) || styleString(element, 'position') == 'fixed') return null; + + try { + return element.offsetParent; + } catch(e){} + return null; + }, + + getOffsets: function(){ + var hasGetBoundingClientRect = this.getBoundingClientRect; + + if (hasGetBoundingClientRect){ + var bound = this.getBoundingClientRect(), + html = document.id(this.getDocument().documentElement), + htmlScroll = html.getScroll(), + elemScrolls = this.getScrolls(), + isFixed = (styleString(this, 'position') == 'fixed'); + + return { + x: bound.left.toInt() + elemScrolls.x + ((isFixed) ? 0 : htmlScroll.x) - html.clientLeft, + y: bound.top.toInt() + elemScrolls.y + ((isFixed) ? 0 : htmlScroll.y) - html.clientTop + }; + } + + var element = this, position = {x: 0, y: 0}; + if (isBody(this)) return position; + + while (element && !isBody(element)){ + position.x += element.offsetLeft; + position.y += element.offsetTop; + + element = element.offsetParent; + } + + return position; + }, + + getPosition: function(relative){ + var offset = this.getOffsets(), + scroll = this.getScrolls(); + var position = { + x: offset.x - scroll.x, + y: offset.y - scroll.y + }; + + if (relative && (relative = document.id(relative))){ + var relativePosition = relative.getPosition(); + return {x: position.x - relativePosition.x - leftBorder(relative), y: position.y - relativePosition.y - topBorder(relative)}; + } + return position; + }, + + getCoordinates: function(element){ + if (isBody(this)) return this.getWindow().getCoordinates(); + var position = this.getPosition(element), + size = this.getSize(); + var obj = { + left: position.x, + top: position.y, + width: size.x, + height: size.y + }; + obj.right = obj.left + obj.width; + obj.bottom = obj.top + obj.height; + return obj; + }, + + computePosition: function(obj){ + return { + left: obj.x - styleNumber(this, 'margin-left'), + top: obj.y - styleNumber(this, 'margin-top') + }; + }, + + setPosition: function(obj){ + return this.setStyles(this.computePosition(obj)); + } + +}); + + +[Document, Window].invoke('implement', { + + getSize: function(){ + var doc = getCompatElement(this); + return {x: doc.clientWidth, y: doc.clientHeight}; + }, + + getScroll: function(){ + var win = this.getWindow(), doc = getCompatElement(this); + return {x: win.pageXOffset || doc.scrollLeft, y: win.pageYOffset || doc.scrollTop}; + }, + + getScrollSize: function(){ + var doc = getCompatElement(this), + min = this.getSize(), + body = this.getDocument().body; + + return {x: Math.max(doc.scrollWidth, body.scrollWidth, min.x), y: Math.max(doc.scrollHeight, body.scrollHeight, min.y)}; + }, + + getPosition: function(){ + return {x: 0, y: 0}; + }, + + getCoordinates: function(){ + var size = this.getSize(); + return {top: 0, left: 0, bottom: size.y, right: size.x, height: size.y, width: size.x}; + } + +}); + +// private methods + +var styleString = Element.getComputedStyle; + +function styleNumber(element, style){ + return styleString(element, style).toInt() || 0; +} + +function borderBox(element){ + return styleString(element, '-moz-box-sizing') == 'border-box'; +} + +function topBorder(element){ + return styleNumber(element, 'border-top-width'); +} + +function leftBorder(element){ + return styleNumber(element, 'border-left-width'); +} + +function isBody(element){ + return (/^(?:body|html)$/i).test(element.tagName); +} + +function getCompatElement(element){ + var doc = element.getDocument(); + return (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body; +} + +})(); + +//aliases +Element.alias({position: 'setPosition'}); //compatability + +[Window, Document, Element].invoke('implement', { + + getHeight: function(){ + return this.getSize().y; + }, + + getWidth: function(){ + return this.getSize().x; + }, + + getScrollTop: function(){ + return this.getScroll().y; + }, + + getScrollLeft: function(){ + return this.getScroll().x; + }, + + getScrollHeight: function(){ + return this.getScrollSize().y; + }, + + getScrollWidth: function(){ + return this.getScrollSize().x; + }, + + getTop: function(){ + return this.getPosition().y; + }, + + getLeft: function(){ + return this.getPosition().x; + } + +}); + +/* +--- + +name: Fx + +description: Contains the basic animation logic to be extended by all other Fx Classes. + +license: MIT-style license. + +requires: [Chain, Events, Options] + +provides: Fx + +... +*/ + +(function(){ + +var Fx = this.Fx = new Class({ + + Implements: [Chain, Events, Options], + + options: { + /* + onStart: nil, + onCancel: nil, + onComplete: nil, + */ + fps: 60, + unit: false, + duration: 500, + frames: null, + frameSkip: true, + link: 'ignore' + }, + + initialize: function(options){ + this.subject = this.subject || this; + this.setOptions(options); + }, + + getTransition: function(){ + return function(p){ + return -(Math.cos(Math.PI * p) - 1) / 2; + }; + }, + + step: function(now){ + if (this.options.frameSkip){ + var diff = (this.time != null) ? (now - this.time) : 0, frames = diff / this.frameInterval; + this.time = now; + this.frame += frames; + } else { + this.frame++; + } + + if (this.frame < this.frames){ + var delta = this.transition(this.frame / this.frames); + this.set(this.compute(this.from, this.to, delta)); + } else { + this.frame = this.frames; + this.set(this.compute(this.from, this.to, 1)); + this.stop(); + } + }, + + set: function(now){ + return now; + }, + + compute: function(from, to, delta){ + return Fx.compute(from, to, delta); + }, + + check: function(){ + if (!this.isRunning()) return true; + switch (this.options.link){ + case 'cancel': this.cancel(); return true; + case 'chain': this.chain(this.caller.pass(arguments, this)); return false; + } + return false; + }, + + start: function(from, to){ + if (!this.check(from, to)) return this; + this.from = from; + this.to = to; + this.frame = (this.options.frameSkip) ? 0 : -1; + this.time = null; + this.transition = this.getTransition(); + var frames = this.options.frames, fps = this.options.fps, duration = this.options.duration; + this.duration = Fx.Durations[duration] || duration.toInt(); + this.frameInterval = 1000 / fps; + this.frames = frames || Math.round(this.duration / this.frameInterval); + this.fireEvent('start', this.subject); + pushInstance.call(this, fps); + return this; + }, + + stop: function(){ + if (this.isRunning()){ + this.time = null; + pullInstance.call(this, this.options.fps); + if (this.frames == this.frame){ + this.fireEvent('complete', this.subject); + if (!this.callChain()) this.fireEvent('chainComplete', this.subject); + } else { + this.fireEvent('stop', this.subject); + } + } + return this; + }, + + cancel: function(){ + if (this.isRunning()){ + this.time = null; + pullInstance.call(this, this.options.fps); + this.frame = this.frames; + this.fireEvent('cancel', this.subject).clearChain(); + } + return this; + }, + + pause: function(){ + if (this.isRunning()){ + this.time = null; + pullInstance.call(this, this.options.fps); + } + return this; + }, + + resume: function(){ + if (this.isPaused()) pushInstance.call(this, this.options.fps); + return this; + }, + + isRunning: function(){ + var list = instances[this.options.fps]; + return list && list.contains(this); + }, + + isPaused: function(){ + return (this.frame < this.frames) && !this.isRunning(); + } + +}); + +Fx.compute = function(from, to, delta){ + return (to - from) * delta + from; +}; + +Fx.Durations = {'short': 250, 'normal': 500, 'long': 1000}; + +// global timers + +var instances = {}, timers = {}; + +var loop = function(){ + var now = Date.now(); + for (var i = this.length; i--;){ + var instance = this[i]; + if (instance) instance.step(now); + } +}; + +var pushInstance = function(fps){ + var list = instances[fps] || (instances[fps] = []); + list.push(this); + if (!timers[fps]) timers[fps] = loop.periodical(Math.round(1000 / fps), list); +}; + +var pullInstance = function(fps){ + var list = instances[fps]; + if (list){ + list.erase(this); + if (!list.length && timers[fps]){ + delete instances[fps]; + timers[fps] = clearInterval(timers[fps]); + } + } +}; + +})(); + +/* +--- + +name: Fx.CSS + +description: Contains the CSS animation logic. Used by Fx.Tween, Fx.Morph, Fx.Elements. + +license: MIT-style license. + +requires: [Fx, Element.Style] + +provides: Fx.CSS + +... +*/ + +Fx.CSS = new Class({ + + Extends: Fx, + + //prepares the base from/to object + + prepare: function(element, property, values){ + values = Array.from(values); + var from = values[0], to = values[1]; + if (to == null){ + to = from; + from = element.getStyle(property); + var unit = this.options.unit; + // adapted from: https://github.com/ryanmorr/fx/blob/master/fx.js#L299 + if (unit && from && typeof from == 'string' && from.slice(-unit.length) != unit && parseFloat(from) != 0){ + element.setStyle(property, to + unit); + var value = element.getComputedStyle(property); + // IE and Opera support pixelLeft or pixelWidth + if (!(/px$/.test(value))){ + value = element.style[('pixel-' + property).camelCase()]; + if (value == null){ + // adapted from Dean Edwards' http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 + var left = element.style.left; + element.style.left = to + unit; + value = element.style.pixelLeft; + element.style.left = left; + } + } + from = (to || 1) / (parseFloat(value) || 1) * (parseFloat(from) || 0); + element.setStyle(property, from + unit); + } + } + return {from: this.parse(from), to: this.parse(to)}; + }, + + //parses a value into an array + + parse: function(value){ + value = Function.from(value)(); + value = (typeof value == 'string') ? value.split(' ') : Array.from(value); + return value.map(function(val){ + val = String(val); + var found = false; + Object.each(Fx.CSS.Parsers, function(parser, key){ + if (found) return; + var parsed = parser.parse(val); + if (parsed || parsed === 0) found = {value: parsed, parser: parser}; + }); + found = found || {value: val, parser: Fx.CSS.Parsers.String}; + return found; + }); + }, + + //computes by a from and to prepared objects, using their parsers. + + compute: function(from, to, delta){ + var computed = []; + (Math.min(from.length, to.length)).times(function(i){ + computed.push({value: from[i].parser.compute(from[i].value, to[i].value, delta), parser: from[i].parser}); + }); + computed.$family = Function.from('fx:css:value'); + return computed; + }, + + //serves the value as settable + + serve: function(value, unit){ + if (typeOf(value) != 'fx:css:value') value = this.parse(value); + var returned = []; + value.each(function(bit){ + returned = returned.concat(bit.parser.serve(bit.value, unit)); + }); + return returned; + }, + + //renders the change to an element + + render: function(element, property, value, unit){ + element.setStyle(property, this.serve(value, unit)); + }, + + //searches inside the page css to find the values for a selector + + search: function(selector){ + if (Fx.CSS.Cache[selector]) return Fx.CSS.Cache[selector]; + var to = {}, selectorTest = new RegExp('^' + selector.escapeRegExp() + '$'); + + var searchStyles = function(rules){ + Array.each(rules, function(rule, i){ + if (rule.media){ + searchStyles(rule.rules || rule.cssRules); + return; + } + if (!rule.style) return; + var selectorText = (rule.selectorText) ? rule.selectorText.replace(/^\w+/, function(m){ + return m.toLowerCase(); + }) : null; + if (!selectorText || !selectorTest.test(selectorText)) return; + Object.each(Element.Styles, function(value, style){ + if (!rule.style[style] || Element.ShortStyles[style]) return; + value = String(rule.style[style]); + to[style] = ((/^rgb/).test(value)) ? value.rgbToHex() : value; + }); + }); + }; + + Array.each(document.styleSheets, function(sheet, j){ + var href = sheet.href; + if (href && href.indexOf('://') > -1 && href.indexOf(document.domain) == -1) return; + var rules = sheet.rules || sheet.cssRules; + searchStyles(rules); + }); + return Fx.CSS.Cache[selector] = to; + } + +}); + +Fx.CSS.Cache = {}; + +Fx.CSS.Parsers = { + + Color: { + parse: function(value){ + if (value.match(/^#[0-9a-f]{3,6}$/i)) return value.hexToRgb(true); + return ((value = value.match(/(\d+),\s*(\d+),\s*(\d+)/))) ? [value[1], value[2], value[3]] : false; + }, + compute: function(from, to, delta){ + return from.map(function(value, i){ + return Math.round(Fx.compute(from[i], to[i], delta)); + }); + }, + serve: function(value){ + return value.map(Number); + } + }, + + Number: { + parse: parseFloat, + compute: Fx.compute, + serve: function(value, unit){ + return (unit) ? value + unit : value; + } + }, + + String: { + parse: Function.from(false), + compute: function(zero, one){ + return one; + }, + serve: function(zero){ + return zero; + } + } + +}; + + + +/* +--- + +name: Fx.Morph + +description: Formerly Fx.Styles, effect to transition any number of CSS properties for an element using an object of rules, or CSS based selector rules. + +license: MIT-style license. + +requires: Fx.CSS + +provides: Fx.Morph + +... +*/ + +Fx.Morph = new Class({ + + Extends: Fx.CSS, + + initialize: function(element, options){ + this.element = this.subject = document.id(element); + this.parent(options); + }, + + set: function(now){ + if (typeof now == 'string') now = this.search(now); + for (var p in now) this.render(this.element, p, now[p], this.options.unit); + return this; + }, + + compute: function(from, to, delta){ + var now = {}; + for (var p in from) now[p] = this.parent(from[p], to[p], delta); + return now; + }, + + start: function(properties){ + if (!this.check(properties)) return this; + if (typeof properties == 'string') properties = this.search(properties); + var from = {}, to = {}; + for (var p in properties){ + var parsed = this.prepare(this.element, p, properties[p]); + from[p] = parsed.from; + to[p] = parsed.to; + } + return this.parent(from, to); + } + +}); + +Element.Properties.morph = { + + set: function(options){ + this.get('morph').cancel().setOptions(options); + return this; + }, + + get: function(){ + var morph = this.retrieve('morph'); + if (!morph){ + morph = new Fx.Morph(this, {link: 'cancel'}); + this.store('morph', morph); + } + return morph; + } + +}; + +Element.implement({ + + morph: function(props){ + this.get('morph').start(props); + return this; + } + +}); + +/* +--- + +name: Fx.Transitions + +description: Contains a set of advanced transitions to be used with any of the Fx Classes. + +license: MIT-style license. + +credits: + - Easing Equations by Robert Penner, <http://www.robertpenner.com/easing/>, modified and optimized to be used with MooTools. + +requires: Fx + +provides: Fx.Transitions + +... +*/ + +Fx.implement({ + + getTransition: function(){ + var trans = this.options.transition || Fx.Transitions.Sine.easeInOut; + if (typeof trans == 'string'){ + var data = trans.split(':'); + trans = Fx.Transitions; + trans = trans[data[0]] || trans[data[0].capitalize()]; + if (data[1]) trans = trans['ease' + data[1].capitalize() + (data[2] ? data[2].capitalize() : '')]; + } + return trans; + } + +}); + +Fx.Transition = function(transition, params){ + params = Array.from(params); + var easeIn = function(pos){ + return transition(pos, params); + }; + return Object.append(easeIn, { + easeIn: easeIn, + easeOut: function(pos){ + return 1 - transition(1 - pos, params); + }, + easeInOut: function(pos){ + return (pos <= 0.5 ? transition(2 * pos, params) : (2 - transition(2 * (1 - pos), params))) / 2; + } + }); +}; + +Fx.Transitions = { + + linear: function(zero){ + return zero; + } + +}; + + + +Fx.Transitions.extend = function(transitions){ + for (var transition in transitions) Fx.Transitions[transition] = new Fx.Transition(transitions[transition]); +}; + +Fx.Transitions.extend({ + + Pow: function(p, x){ + return Math.pow(p, x && x[0] || 6); + }, + + Expo: function(p){ + return Math.pow(2, 8 * (p - 1)); + }, + + Circ: function(p){ + return 1 - Math.sin(Math.acos(p)); + }, + + Sine: function(p){ + return 1 - Math.cos(p * Math.PI / 2); + }, + + Back: function(p, x){ + x = x && x[0] || 1.618; + return Math.pow(p, 2) * ((x + 1) * p - x); + }, + + Bounce: function(p){ + var value; + for (var a = 0, b = 1; 1; a += b, b /= 2){ + if (p >= (7 - 4 * a) / 11){ + value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2); + break; + } + } + return value; + }, + + Elastic: function(p, x){ + return Math.pow(2, 10 * --p) * Math.cos(20 * p * Math.PI * (x && x[0] || 1) / 3); + } + +}); + +['Quad', 'Cubic', 'Quart', 'Quint'].each(function(transition, i){ + Fx.Transitions[transition] = new Fx.Transition(function(p){ + return Math.pow(p, i + 2); + }); +}); + +/* +--- + +name: Fx.Tween + +description: Formerly Fx.Style, effect to transition any CSS property for an element. + +license: MIT-style license. + +requires: Fx.CSS + +provides: [Fx.Tween, Element.fade, Element.highlight] + +... +*/ + +Fx.Tween = new Class({ + + Extends: Fx.CSS, + + initialize: function(element, options){ + this.element = this.subject = document.id(element); + this.parent(options); + }, + + set: function(property, now){ + if (arguments.length == 1){ + now = property; + property = this.property || this.options.property; + } + this.render(this.element, property, now, this.options.unit); + return this; + }, + + start: function(property, from, to){ + if (!this.check(property, from, to)) return this; + var args = Array.flatten(arguments); + this.property = this.options.property || args.shift(); + var parsed = this.prepare(this.element, this.property, args); + return this.parent(parsed.from, parsed.to); + } + +}); + +Element.Properties.tween = { + + set: function(options){ + this.get('tween').cancel().setOptions(options); + return this; + }, + + get: function(){ + var tween = this.retrieve('tween'); + if (!tween){ + tween = new Fx.Tween(this, {link: 'cancel'}); + this.store('tween', tween); + } + return tween; + } + +}; + +Element.implement({ + + tween: function(property, from, to){ + this.get('tween').start(property, from, to); + return this; + }, + + fade: function(how){ + var fade = this.get('tween'), method, args = ['opacity'].append(arguments), toggle; + if (args[1] == null) args[1] = 'toggle'; + switch (args[1]){ + case 'in': method = 'start'; args[1] = 1; break; + case 'out': method = 'start'; args[1] = 0; break; + case 'show': method = 'set'; args[1] = 1; break; + case 'hide': method = 'set'; args[1] = 0; break; + case 'toggle': + var flag = this.retrieve('fade:flag', this.getStyle('opacity') == 1); + method = 'start'; + args[1] = flag ? 0 : 1; + this.store('fade:flag', !flag); + toggle = true; + break; + default: method = 'start'; + } + if (!toggle) this.eliminate('fade:flag'); + fade[method].apply(fade, args); + var to = args[args.length - 1]; + if (method == 'set' || to != 0) this.setStyle('visibility', to == 0 ? 'hidden' : 'visible'); + else fade.chain(function(){ + this.element.setStyle('visibility', 'hidden'); + this.callChain(); + }); + return this; + }, + + highlight: function(start, end){ + if (!end){ + end = this.retrieve('highlight:original', this.getStyle('background-color')); + end = (end == 'transparent') ? '#fff' : end; + } + var tween = this.get('tween'); + tween.start('background-color', start || '#ffff88', end).chain(function(){ + this.setStyle('background-color', this.retrieve('highlight:original')); + tween.callChain(); + }.bind(this)); + return this; + } + +}); + +/* +--- + +name: Request + +description: Powerful all purpose Request Class. Uses XMLHTTPRequest. + +license: MIT-style license. + +requires: [Object, Element, Chain, Events, Options, Browser] + +provides: Request + +... +*/ + +(function(){ + +var empty = function(){}, + progressSupport = ('onprogress' in new Browser.Request); + +var Request = this.Request = new Class({ + + Implements: [Chain, Events, Options], + + options: {/* + onRequest: function(){}, + onLoadstart: function(event, xhr){}, + onProgress: function(event, xhr){}, + onComplete: function(){}, + onCancel: function(){}, + onSuccess: function(responseText, responseXML){}, + onFailure: function(xhr){}, + onException: function(headerName, value){}, + onTimeout: function(){}, + user: '', + password: '', + withCredentials: false,*/ + url: '', + data: '', + headers: { + 'X-Requested-With': 'XMLHttpRequest', + 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' + }, + async: true, + format: false, + method: 'post', + link: 'ignore', + isSuccess: null, + emulation: true, + urlEncoded: true, + encoding: 'utf-8', + evalScripts: false, + evalResponse: false, + timeout: 0, + noCache: false + }, + + initialize: function(options){ + this.xhr = new Browser.Request(); + this.setOptions(options); + this.headers = this.options.headers; + }, + + onStateChange: function(){ + var xhr = this.xhr; + if (xhr.readyState != 4 || !this.running) return; + this.running = false; + this.status = 0; + Function.attempt(function(){ + var status = xhr.status; + this.status = (status == 1223) ? 204 : status; + }.bind(this)); + xhr.onreadystatechange = empty; + if (progressSupport) xhr.onprogress = xhr.onloadstart = empty; + if (this.timer){ + clearTimeout(this.timer); + delete this.timer; + } + + this.response = {text: this.xhr.responseText || '', xml: this.xhr.responseXML}; + if (this.options.isSuccess.call(this, this.status)) + this.success(this.response.text, this.response.xml); + else + this.failure(); + }, + + isSuccess: function(){ + var status = this.status; + return (status >= 200 && status < 300); + }, + + isRunning: function(){ + return !!this.running; + }, + + processScripts: function(text){ + if (this.options.evalResponse || (/(ecma|java)script/).test(this.getHeader('Content-type'))) return Browser.exec(text); + return text.stripScripts(this.options.evalScripts); + }, + + success: function(text, xml){ + this.onSuccess(this.processScripts(text), xml); + }, + + onSuccess: function(){ + this.fireEvent('complete', arguments).fireEvent('success', arguments).callChain(); + }, + + failure: function(){ + this.onFailure(); + }, + + onFailure: function(){ + this.fireEvent('complete').fireEvent('failure', this.xhr); + }, + + loadstart: function(event){ + this.fireEvent('loadstart', [event, this.xhr]); + }, + + progress: function(event){ + this.fireEvent('progress', [event, this.xhr]); + }, + + timeout: function(){ + this.fireEvent('timeout', this.xhr); + }, + + setHeader: function(name, value){ + this.headers[name] = value; + return this; + }, + + getHeader: function(name){ + return Function.attempt(function(){ + return this.xhr.getResponseHeader(name); + }.bind(this)); + }, + + check: function(){ + if (!this.running) return true; + switch (this.options.link){ + case 'cancel': this.cancel(); return true; + case 'chain': this.chain(this.caller.pass(arguments, this)); return false; + } + return false; + }, + + send: function(options){ + if (!this.check(options)) return this; + + this.options.isSuccess = this.options.isSuccess || this.isSuccess; + this.running = true; + + var type = typeOf(options); + if (type == 'string' || type == 'element') options = {data: options}; + + var old = this.options; + options = Object.append({data: old.data, url: old.url, method: old.method}, options); + var data = options.data, url = String(options.url), method = options.method.toLowerCase(); + + switch (typeOf(data)){ + case 'element': data = document.id(data).toQueryString(); break; + case 'object': case 'hash': data = Object.toQueryString(data); + } + + if (this.options.format){ + var format = 'format=' + this.options.format; + data = (data) ? format + '&' + data : format; + } + + if (this.options.emulation && !['get', 'post'].contains(method)){ + var _method = '_method=' + method; + data = (data) ? _method + '&' + data : _method; + method = 'post'; + } + + if (this.options.urlEncoded && ['post', 'put'].contains(method)){ + var encoding = (this.options.encoding) ? '; charset=' + this.options.encoding : ''; + this.headers['Content-type'] = 'application/x-www-form-urlencoded' + encoding; + } + + if (!url) url = document.location.pathname; + + var trimPosition = url.lastIndexOf('/'); + if (trimPosition > -1 && (trimPosition = url.indexOf('#')) > -1) url = url.substr(0, trimPosition); + + if (this.options.noCache) + url += (url.indexOf('?') > -1 ? '&' : '?') + String.uniqueID(); + + if (data && (method == 'get' || method == 'delete')){ + url += (url.indexOf('?') > -1 ? '&' : '?') + data; + data = null; + } + + var xhr = this.xhr; + if (progressSupport){ + xhr.onloadstart = this.loadstart.bind(this); + xhr.onprogress = this.progress.bind(this); + } + + xhr.open(method.toUpperCase(), url, this.options.async, this.options.user, this.options.password); + if ((this.options.withCredentials) && 'withCredentials' in xhr) xhr.withCredentials = true; + + xhr.onreadystatechange = this.onStateChange.bind(this); + + Object.each(this.headers, function(value, key){ + try { + xhr.setRequestHeader(key, value); + } catch (e){ + this.fireEvent('exception', [key, value]); + } + }, this); + + this.fireEvent('request'); + xhr.send(data); + if (!this.options.async) this.onStateChange(); + else if (this.options.timeout) this.timer = this.timeout.delay(this.options.timeout, this); + return this; + }, + + cancel: function(){ + if (!this.running) return this; + this.running = false; + var xhr = this.xhr; + xhr.abort(); + if (this.timer){ + clearTimeout(this.timer); + delete this.timer; + } + xhr.onreadystatechange = empty; + if (progressSupport) xhr.onprogress = xhr.onloadstart = empty; + this.xhr = new Browser.Request(); + this.fireEvent('cancel'); + return this; + } + +}); + +var methods = {}; +['get', 'post', 'put', 'delete', 'patch', 'head', 'GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD'].each(function(method){ + methods[method] = function(data){ + var object = { + method: method + }; + if (data != null) object.data = data; + return this.send(object); + }; +}); + +Request.implement(methods); + +Element.Properties.send = { + + set: function(options){ + var send = this.get('send').cancel(); + send.setOptions(options); + return this; + }, + + get: function(){ + var send = this.retrieve('send'); + if (!send){ + send = new Request({ + data: this, link: 'cancel', method: this.get('method') || 'post', url: this.get('action') + }); + this.store('send', send); + } + return send; + } + +}; + +Element.implement({ + + send: function(url){ + var sender = this.get('send'); + sender.send({data: this, url: url || sender.options.url}); + return this; + } + +}); + +})(); + +/* +--- + +name: Request.HTML + +description: Extends the basic Request Class with additional methods for interacting with HTML responses. + +license: MIT-style license. + +requires: [Element, Request] + +provides: Request.HTML + +... +*/ + +Request.HTML = new Class({ + + Extends: Request, + + options: { + update: false, + append: false, + evalScripts: true, + filter: false, + headers: { + Accept: 'text/html, application/xml, text/xml, */*' + } + }, + + success: function(text){ + var options = this.options, response = this.response; + + response.html = text.stripScripts(function(script){ + response.javascript = script; + }); + + var match = response.html.match(/<body[^>]*>([\s\S]*?)<\/body>/i); + if (match) response.html = match[1]; + var temp = new Element('div').set('html', response.html); + + response.tree = temp.childNodes; + response.elements = temp.getElements(options.filter || '*'); + + if (options.filter) response.tree = response.elements; + if (options.update){ + var update = document.id(options.update).empty(); + if (options.filter) update.adopt(response.elements); + else update.set('html', response.html); + } else if (options.append){ + var append = document.id(options.append); + if (options.filter) response.elements.reverse().inject(append); + else append.adopt(temp.getChildren()); + } + if (options.evalScripts) Browser.exec(response.javascript); + + this.onSuccess(response.tree, response.elements, response.html, response.javascript); + } + +}); + +Element.Properties.load = { + + set: function(options){ + var load = this.get('load').cancel(); + load.setOptions(options); + return this; + }, + + get: function(){ + var load = this.retrieve('load'); + if (!load){ + load = new Request.HTML({data: this, link: 'cancel', update: this, method: 'get'}); + this.store('load', load); + } + return load; + } + +}; + +Element.implement({ + + load: function(){ + this.get('load').send(Array.link(arguments, {data: Type.isObject, url: Type.isString})); + return this; + } + +}); + +/* +--- + +name: JSON + +description: JSON encoder and decoder. + +license: MIT-style license. + +SeeAlso: <http://www.json.org/> + +requires: [Array, String, Number, Function] + +provides: JSON + +... +*/ + +if (typeof JSON == 'undefined') this.JSON = {}; + + + +(function(){ + +var special = {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\'}; + +var escape = function(chr){ + return special[chr] || '\\u' + ('0000' + chr.charCodeAt(0).toString(16)).slice(-4); +}; + +JSON.validate = function(string){ + string = string.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'). + replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'). + replace(/(?:^|:|,)(?:\s*\[)+/g, ''); + + return (/^[\],:{}\s]*$/).test(string); +}; + +JSON.encode = JSON.stringify ? function(obj){ + return JSON.stringify(obj); +} : function(obj){ + if (obj && obj.toJSON) obj = obj.toJSON(); + + switch (typeOf(obj)){ + case 'string': + return '"' + obj.replace(/[\x00-\x1f\\"]/g, escape) + '"'; + case 'array': + return '[' + obj.map(JSON.encode).clean() + ']'; + case 'object': case 'hash': + var string = []; + Object.each(obj, function(value, key){ + var json = JSON.encode(value); + if (json) string.push(JSON.encode(key) + ':' + json); + }); + return '{' + string + '}'; + case 'number': case 'boolean': return '' + obj; + case 'null': return 'null'; + } + + return null; +}; + +JSON.secure = true; + + +JSON.decode = function(string, secure){ + if (!string || typeOf(string) != 'string') return null; + + if (secure == null) secure = JSON.secure; + if (secure){ + if (JSON.parse) return JSON.parse(string); + if (!JSON.validate(string)) throw new Error('JSON could not decode the input; security is enabled and the value is not secure.'); + } + + return eval('(' + string + ')'); +}; + +})(); + +/* +--- + +name: Request.JSON + +description: Extends the basic Request Class with additional methods for sending and receiving JSON data. + +license: MIT-style license. + +requires: [Request, JSON] + +provides: Request.JSON + +... +*/ + +Request.JSON = new Class({ + + Extends: Request, + + options: { + /*onError: function(text, error){},*/ + secure: true + }, + + initialize: function(options){ + this.parent(options); + Object.append(this.headers, { + 'Accept': 'application/json', + 'X-Request': 'JSON' + }); + }, + + success: function(text){ + var json; + try { + json = this.response.json = JSON.decode(text, this.options.secure); + } catch (error){ + this.fireEvent('error', [text, error]); + return; + } + if (json == null) this.onFailure(); + else this.onSuccess(json, text); + } + +}); + +/* +--- + +name: Cookie + +description: Class for creating, reading, and deleting browser Cookies. + +license: MIT-style license. + +credits: + - Based on the functions by Peter-Paul Koch (http://quirksmode.org). + +requires: [Options, Browser] + +provides: Cookie + +... +*/ + +var Cookie = new Class({ + + Implements: Options, + + options: { + path: '/', + domain: false, + duration: false, + secure: false, + document: document, + encode: true + }, + + initialize: function(key, options){ + this.key = key; + this.setOptions(options); + }, + + write: function(value){ + if (this.options.encode) value = encodeURIComponent(value); + if (this.options.domain) value += '; domain=' + this.options.domain; + if (this.options.path) value += '; path=' + this.options.path; + if (this.options.duration){ + var date = new Date(); + date.setTime(date.getTime() + this.options.duration * 24 * 60 * 60 * 1000); + value += '; expires=' + date.toGMTString(); + } + if (this.options.secure) value += '; secure'; + this.options.document.cookie = this.key + '=' + value; + return this; + }, + + read: function(){ + var value = this.options.document.cookie.match('(?:^|;)\\s*' + this.key.escapeRegExp() + '=([^;]*)'); + return (value) ? decodeURIComponent(value[1]) : null; + }, + + dispose: function(){ + new Cookie(this.key, Object.merge({}, this.options, {duration: -1})).write(''); + return this; + } + +}); + +Cookie.write = function(key, value, options){ + return new Cookie(key, options).write(value); +}; + +Cookie.read = function(key){ + return new Cookie(key).read(); +}; + +Cookie.dispose = function(key, options){ + return new Cookie(key, options).dispose(); +}; + +/* +--- + +name: DOMReady + +description: Contains the custom event domready. + +license: MIT-style license. + +requires: [Browser, Element, Element.Event] + +provides: [DOMReady, DomReady] + +... +*/ + +(function(window, document){ + +var ready, + loaded, + checks = [], + shouldPoll, + timer, + testElement = document.createElement('div'); + +var domready = function(){ + clearTimeout(timer); + if (!ready) { + Browser.loaded = ready = true; + document.removeListener('DOMContentLoaded', domready).removeListener('readystatechange', check); + document.fireEvent('domready'); + window.fireEvent('domready'); + } + // cleanup scope vars + document = window = testElement = null; +}; + +var check = function(){ + for (var i = checks.length; i--;) if (checks[i]()){ + domready(); + return true; + } + return false; +}; + +var poll = function(){ + clearTimeout(timer); + if (!check()) timer = setTimeout(poll, 10); +}; + +document.addListener('DOMContentLoaded', domready); + +/*<ltIE8>*/ +// doScroll technique by Diego Perini http://javascript.nwbox.com/IEContentLoaded/ +// testElement.doScroll() throws when the DOM is not ready, only in the top window +var doScrollWorks = function(){ + try { + testElement.doScroll(); + return true; + } catch (e){} + return false; +}; +// If doScroll works already, it can't be used to determine domready +// e.g. in an iframe +if (testElement.doScroll && !doScrollWorks()){ + checks.push(doScrollWorks); + shouldPoll = true; +} +/*</ltIE8>*/ + +if (document.readyState) checks.push(function(){ + var state = document.readyState; + return (state == 'loaded' || state == 'complete'); +}); + +if ('onreadystatechange' in document) document.addListener('readystatechange', check); +else shouldPoll = true; + +if (shouldPoll) poll(); + +Element.Events.domready = { + onAdd: function(fn){ + if (ready) fn.call(this); + } +}; + +// Make sure that domready fires before load +Element.Events.load = { + base: 'load', + onAdd: function(fn){ + if (loaded && this == window) fn.call(this); + }, + condition: function(){ + if (this == window){ + domready(); + delete Element.Events.load; + } + return true; + } +}; + +// This is based on the custom load event +window.addEvent('load', function(){ + loaded = true; +}); + +})(window, document); diff --git a/pyload/webui/themes/Default/lib/MooTools/MooTools-More.js b/pyload/webui/themes/Default/lib/MooTools/MooTools-More.js new file mode 100644 index 000000000..15a277029 --- /dev/null +++ b/pyload/webui/themes/Default/lib/MooTools/MooTools-More.js @@ -0,0 +1,14345 @@ +/* MooTools: the javascript framework. license: MIT-style license. copyright: Copyright (c) 2006-2015 [Valerio Proietti](http://mad4milk.net/).*/ +/* +Web Build: http://mootools.net/more/builder/a3048f4bfdf603b22a69c141dbd0fca9 +*/ +/* +--- + +script: More.js + +name: More + +description: MooTools More + +license: MIT-style license + +authors: + - Guillermo Rauch + - Thomas Aylott + - Scott Kyle + - Arian Stolwijk + - Tim Wienk + - Christoph Pojer + - Aaron Newton + - Jacob Thornton + +requires: + - Core/MooTools + +provides: [MooTools.More] + +... +*/ + +MooTools.More = { + version: '1.5.1', + build: '2dd695ba957196ae4b0275a690765d6636a61ccd' +}; + +/* +--- + +script: Chain.Wait.js + +name: Chain.Wait + +description: value, Adds a method to inject pauses between chained events. + +license: MIT-style license. + +authors: + - Aaron Newton + +requires: + - Core/Chain + - Core/Element + - Core/Fx + - MooTools.More + +provides: [Chain.Wait] + +... +*/ + +(function(){ + + var wait = { + wait: function(duration){ + return this.chain(function(){ + this.callChain.delay(duration == null ? 500 : duration, this); + return this; + }.bind(this)); + } + }; + + Chain.implement(wait); + + if (this.Fx) Fx.implement(wait); + + if (this.Element && Element.implement && this.Fx){ + Element.implement({ + + chains: function(effects){ + Array.from(effects || ['tween', 'morph', 'reveal']).each(function(effect){ + effect = this.get(effect); + if (!effect) return; + effect.setOptions({ + link:'chain' + }); + }, this); + return this; + }, + + pauseFx: function(duration, effect){ + this.chains(effect).get(effect || 'tween').wait(duration); + return this; + } + + }); + } + +})(); + +/* +--- + +script: Class.Binds.js + +name: Class.Binds + +description: Automagically binds specified methods in a class to the instance of the class. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Class + - MooTools.More + +provides: [Class.Binds] + +... +*/ + +Class.Mutators.Binds = function(binds){ + if (!this.prototype.initialize) this.implement('initialize', function(){}); + return Array.from(binds).concat(this.prototype.Binds || []); +}; + +Class.Mutators.initialize = function(initialize){ + return function(){ + Array.from(this.Binds).each(function(name){ + var original = this[name]; + if (original) this[name] = original.bind(this); + }, this); + return initialize.apply(this, arguments); + }; +}; + +/* +--- + +script: Class.Occlude.js + +name: Class.Occlude + +description: Prevents a class from being applied to a DOM element twice. + +license: MIT-style license. + +authors: + - Aaron Newton + +requires: + - Core/Class + - Core/Element + - MooTools.More + +provides: [Class.Occlude] + +... +*/ + +Class.Occlude = new Class({ + + occlude: function(property, element){ + element = document.id(element || this.element); + var instance = element.retrieve(property || this.property); + if (instance && !this.occluded) + return (this.occluded = instance); + + this.occluded = false; + element.store(property || this.property, this); + return this.occluded; + } + +}); + +/* +--- + +script: Class.Refactor.js + +name: Class.Refactor + +description: Extends a class onto itself with new property, preserving any items attached to the class's namespace. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Class + - MooTools.More + +# Some modules declare themselves dependent on Class.Refactor +provides: [Class.refactor, Class.Refactor] + +... +*/ + +Class.refactor = function(original, refactors){ + + Object.each(refactors, function(item, name){ + var origin = original.prototype[name]; + origin = (origin && origin.$origin) || origin || function(){}; + original.implement(name, (typeof item == 'function') ? function(){ + var old = this.previous; + this.previous = origin; + var value = item.apply(this, arguments); + this.previous = old; + return value; + } : item); + }); + + return original; + +}; + +/* +--- + +name: Events.Pseudos + +description: Adds the functionality to add pseudo events + +license: MIT-style license + +authors: + - Arian Stolwijk + +requires: [Core/Class.Extras, Core/Slick.Parser, MooTools.More] + +provides: [Events.Pseudos] + +... +*/ + +(function(){ + +Events.Pseudos = function(pseudos, addEvent, removeEvent){ + + var storeKey = '_monitorEvents:'; + + var storageOf = function(object){ + return { + store: object.store ? function(key, value){ + object.store(storeKey + key, value); + } : function(key, value){ + (object._monitorEvents || (object._monitorEvents = {}))[key] = value; + }, + retrieve: object.retrieve ? function(key, dflt){ + return object.retrieve(storeKey + key, dflt); + } : function(key, dflt){ + if (!object._monitorEvents) return dflt; + return object._monitorEvents[key] || dflt; + } + }; + }; + + var splitType = function(type){ + if (type.indexOf(':') == -1 || !pseudos) return null; + + var parsed = Slick.parse(type).expressions[0][0], + parsedPseudos = parsed.pseudos, + l = parsedPseudos.length, + splits = []; + + while (l--){ + var pseudo = parsedPseudos[l].key, + listener = pseudos[pseudo]; + if (listener != null) splits.push({ + event: parsed.tag, + value: parsedPseudos[l].value, + pseudo: pseudo, + original: type, + listener: listener + }); + } + return splits.length ? splits : null; + }; + + return { + + addEvent: function(type, fn, internal){ + var split = splitType(type); + if (!split) return addEvent.call(this, type, fn, internal); + + var storage = storageOf(this), + events = storage.retrieve(type, []), + eventType = split[0].event, + args = Array.slice(arguments, 2), + stack = fn, + self = this; + + split.each(function(item){ + var listener = item.listener, + stackFn = stack; + if (listener == false) eventType += ':' + item.pseudo + '(' + item.value + ')'; + else stack = function(){ + listener.call(self, item, stackFn, arguments, stack); + }; + }); + + events.include({type: eventType, event: fn, monitor: stack}); + storage.store(type, events); + + if (type != eventType) addEvent.apply(this, [type, fn].concat(args)); + return addEvent.apply(this, [eventType, stack].concat(args)); + }, + + removeEvent: function(type, fn){ + var split = splitType(type); + if (!split) return removeEvent.call(this, type, fn); + + var storage = storageOf(this), + events = storage.retrieve(type); + if (!events) return this; + + var args = Array.slice(arguments, 2); + + removeEvent.apply(this, [type, fn].concat(args)); + events.each(function(monitor, i){ + if (!fn || monitor.event == fn) removeEvent.apply(this, [monitor.type, monitor.monitor].concat(args)); + delete events[i]; + }, this); + + storage.store(type, events); + return this; + } + + }; + +}; + +var pseudos = { + + once: function(split, fn, args, monitor){ + fn.apply(this, args); + this.removeEvent(split.event, monitor) + .removeEvent(split.original, fn); + }, + + throttle: function(split, fn, args){ + if (!fn._throttled){ + fn.apply(this, args); + fn._throttled = setTimeout(function(){ + fn._throttled = false; + }, split.value || 250); + } + }, + + pause: function(split, fn, args){ + clearTimeout(fn._pause); + fn._pause = fn.delay(split.value || 250, this, args); + } + +}; + +Events.definePseudo = function(key, listener){ + pseudos[key] = listener; + return this; +}; + +Events.lookupPseudo = function(key){ + return pseudos[key]; +}; + +var proto = Events.prototype; +Events.implement(Events.Pseudos(pseudos, proto.addEvent, proto.removeEvent)); + +['Request', 'Fx'].each(function(klass){ + if (this[klass]) this[klass].implement(Events.prototype); +}); + +})(); + +/* +--- + +script: Drag.js + +name: Drag + +description: The base Drag Class. Can be used to drag and resize Elements using mouse events. + +license: MIT-style license + +authors: + - Valerio Proietti + - Tom Occhinno + - Jan Kassens + +requires: + - Core/Events + - Core/Options + - Core/Element.Event + - Core/Element.Style + - Core/Element.Dimensions + - MooTools.More + +provides: [Drag] +... + +*/ + +var Drag = new Class({ + + Implements: [Events, Options], + + options: {/* + onBeforeStart: function(thisElement){}, + onStart: function(thisElement, event){}, + onSnap: function(thisElement){}, + onDrag: function(thisElement, event){}, + onCancel: function(thisElement){}, + onComplete: function(thisElement, event){},*/ + snap: 6, + unit: 'px', + grid: false, + style: true, + limit: false, + handle: false, + invert: false, + preventDefault: false, + stopPropagation: false, + compensateScroll: false, + modifiers: {x: 'left', y: 'top'} + }, + + initialize: function(){ + var params = Array.link(arguments, { + 'options': Type.isObject, + 'element': function(obj){ + return obj != null; + } + }); + + this.element = document.id(params.element); + this.document = this.element.getDocument(); + this.setOptions(params.options || {}); + var htype = typeOf(this.options.handle); + this.handles = ((htype == 'array' || htype == 'collection') ? $$(this.options.handle) : document.id(this.options.handle)) || this.element; + this.mouse = {'now': {}, 'pos': {}}; + this.value = {'start': {}, 'now': {}}; + this.offsetParent = (function(el){ + var offsetParent = el.getOffsetParent(); + var isBody = !offsetParent || (/^(?:body|html)$/i).test(offsetParent.tagName); + return isBody ? window : document.id(offsetParent); + })(this.element); + this.selection = 'selectstart' in document ? 'selectstart' : 'mousedown'; + + this.compensateScroll = {start: {}, diff: {}, last: {}}; + + if ('ondragstart' in document && !('FileReader' in window) && !Drag.ondragstartFixed){ + document.ondragstart = Function.from(false); + Drag.ondragstartFixed = true; + } + + this.bound = { + start: this.start.bind(this), + check: this.check.bind(this), + drag: this.drag.bind(this), + stop: this.stop.bind(this), + cancel: this.cancel.bind(this), + eventStop: Function.from(false), + scrollListener: this.scrollListener.bind(this) + }; + this.attach(); + }, + + attach: function(){ + this.handles.addEvent('mousedown', this.bound.start); + if (this.options.compensateScroll) this.offsetParent.addEvent('scroll', this.bound.scrollListener); + return this; + }, + + detach: function(){ + this.handles.removeEvent('mousedown', this.bound.start); + if (this.options.compensateScroll) this.offsetParent.removeEvent('scroll', this.bound.scrollListener); + return this; + }, + + scrollListener: function(){ + + if (!this.mouse.start) return; + var newScrollValue = this.offsetParent.getScroll(); + + if (this.element.getStyle('position') == 'absolute'){ + var scrollDiff = this.sumValues(newScrollValue, this.compensateScroll.last, -1); + this.mouse.now = this.sumValues(this.mouse.now, scrollDiff, 1); + } else { + this.compensateScroll.diff = this.sumValues(newScrollValue, this.compensateScroll.start, -1); + } + if (this.offsetParent != window) this.compensateScroll.diff = this.sumValues(this.compensateScroll.start, newScrollValue, -1); + this.compensateScroll.last = newScrollValue; + this.render(this.options); + }, + + sumValues: function(alpha, beta, op){ + var sum = {}, options = this.options; + for (z in options.modifiers){ + if (!options.modifiers[z]) continue; + sum[z] = alpha[z] + beta[z] * op; + } + return sum; + }, + + start: function(event){ + var options = this.options; + + if (event.rightClick) return; + + if (options.preventDefault) event.preventDefault(); + if (options.stopPropagation) event.stopPropagation(); + this.compensateScroll.start = this.compensateScroll.last = this.offsetParent.getScroll(); + this.compensateScroll.diff = {x: 0, y: 0}; + this.mouse.start = event.page; + this.fireEvent('beforeStart', this.element); + + var limit = options.limit; + this.limit = {x: [], y: []}; + + var z, coordinates, offsetParent = this.offsetParent == window ? null : this.offsetParent; + for (z in options.modifiers){ + if (!options.modifiers[z]) continue; + + var style = this.element.getStyle(options.modifiers[z]); + + // Some browsers (IE and Opera) don't always return pixels. + if (style && !style.match(/px$/)){ + if (!coordinates) coordinates = this.element.getCoordinates(offsetParent); + style = coordinates[options.modifiers[z]]; + } + + if (options.style) this.value.now[z] = (style || 0).toInt(); + else this.value.now[z] = this.element[options.modifiers[z]]; + + if (options.invert) this.value.now[z] *= -1; + + this.mouse.pos[z] = event.page[z] - this.value.now[z]; + + if (limit && limit[z]){ + var i = 2; + while (i--){ + var limitZI = limit[z][i]; + if (limitZI || limitZI === 0) this.limit[z][i] = (typeof limitZI == 'function') ? limitZI() : limitZI; + } + } + } + + if (typeOf(this.options.grid) == 'number') this.options.grid = { + x: this.options.grid, + y: this.options.grid + }; + + var events = { + mousemove: this.bound.check, + mouseup: this.bound.cancel + }; + events[this.selection] = this.bound.eventStop; + this.document.addEvents(events); + }, + + check: function(event){ + if (this.options.preventDefault) event.preventDefault(); + var distance = Math.round(Math.sqrt(Math.pow(event.page.x - this.mouse.start.x, 2) + Math.pow(event.page.y - this.mouse.start.y, 2))); + if (distance > this.options.snap){ + this.cancel(); + this.document.addEvents({ + mousemove: this.bound.drag, + mouseup: this.bound.stop + }); + this.fireEvent('start', [this.element, event]).fireEvent('snap', this.element); + } + }, + + drag: function(event){ + var options = this.options; + if (options.preventDefault) event.preventDefault(); + this.mouse.now = this.sumValues(event.page, this.compensateScroll.diff, -1); + + this.render(options); + this.fireEvent('drag', [this.element, event]); + }, + + render: function(options){ + for (var z in options.modifiers){ + if (!options.modifiers[z]) continue; + this.value.now[z] = this.mouse.now[z] - this.mouse.pos[z]; + + if (options.invert) this.value.now[z] *= -1; + if (options.limit && this.limit[z]){ + if ((this.limit[z][1] || this.limit[z][1] === 0) && (this.value.now[z] > this.limit[z][1])){ + this.value.now[z] = this.limit[z][1]; + } else if ((this.limit[z][0] || this.limit[z][0] === 0) && (this.value.now[z] < this.limit[z][0])){ + this.value.now[z] = this.limit[z][0]; + } + } + if (options.grid[z]) this.value.now[z] -= ((this.value.now[z] - (this.limit[z][0]||0)) % options.grid[z]); + if (options.style) this.element.setStyle(options.modifiers[z], this.value.now[z] + options.unit); + else this.element[options.modifiers[z]] = this.value.now[z]; + } + }, + + cancel: function(event){ + this.document.removeEvents({ + mousemove: this.bound.check, + mouseup: this.bound.cancel + }); + if (event){ + this.document.removeEvent(this.selection, this.bound.eventStop); + this.fireEvent('cancel', this.element); + } + }, + + stop: function(event){ + var events = { + mousemove: this.bound.drag, + mouseup: this.bound.stop + }; + events[this.selection] = this.bound.eventStop; + this.document.removeEvents(events); + this.mouse.start = null; + if (event) this.fireEvent('complete', [this.element, event]); + } + +}); + +Element.implement({ + + makeResizable: function(options){ + var drag = new Drag(this, Object.merge({ + modifiers: { + x: 'width', + y: 'height' + } + }, options)); + + this.store('resizer', drag); + return drag.addEvent('drag', function(){ + this.fireEvent('resize', drag); + }.bind(this)); + } + +}); + +/* +--- + +script: Drag.Move.js + +name: Drag.Move + +description: A Drag extension that provides support for the constraining of draggables to containers and droppables. + +license: MIT-style license + +authors: + - Valerio Proietti + - Tom Occhinno + - Jan Kassens + - Aaron Newton + - Scott Kyle + +requires: + - Core/Element.Dimensions + - Drag + +provides: [Drag.Move] + +... +*/ + +Drag.Move = new Class({ + + Extends: Drag, + + options: {/* + onEnter: function(thisElement, overed){}, + onLeave: function(thisElement, overed){}, + onDrop: function(thisElement, overed, event){},*/ + droppables: [], + container: false, + precalculate: false, + includeMargins: true, + checkDroppables: true + }, + + initialize: function(element, options){ + this.parent(element, options); + element = this.element; + + this.droppables = $$(this.options.droppables); + this.setContainer(this.options.container); + + if (this.options.style){ + if (this.options.modifiers.x == 'left' && this.options.modifiers.y == 'top'){ + var parent = element.getOffsetParent(), + styles = element.getStyles('left', 'top'); + if (parent && (styles.left == 'auto' || styles.top == 'auto')){ + element.setPosition(element.getPosition(parent)); + } + } + + if (element.getStyle('position') == 'static') element.setStyle('position', 'absolute'); + } + + this.addEvent('start', this.checkDroppables, true); + this.overed = null; + }, + + setContainer: function(container) { + this.container = document.id(container); + if (this.container && typeOf(this.container) != 'element'){ + this.container = document.id(this.container.getDocument().body); + } + }, + + start: function(event){ + if (this.container) this.options.limit = this.calculateLimit(); + + if (this.options.precalculate){ + this.positions = this.droppables.map(function(el){ + return el.getCoordinates(); + }); + } + + this.parent(event); + }, + + calculateLimit: function(){ + var element = this.element, + container = this.container, + + offsetParent = document.id(element.getOffsetParent()) || document.body, + containerCoordinates = container.getCoordinates(offsetParent), + elementMargin = {}, + elementBorder = {}, + containerMargin = {}, + containerBorder = {}, + offsetParentPadding = {}, + offsetScroll = offsetParent.getScroll(); + + ['top', 'right', 'bottom', 'left'].each(function(pad){ + elementMargin[pad] = element.getStyle('margin-' + pad).toInt(); + elementBorder[pad] = element.getStyle('border-' + pad).toInt(); + containerMargin[pad] = container.getStyle('margin-' + pad).toInt(); + containerBorder[pad] = container.getStyle('border-' + pad).toInt(); + offsetParentPadding[pad] = offsetParent.getStyle('padding-' + pad).toInt(); + }, this); + + var width = element.offsetWidth + elementMargin.left + elementMargin.right, + height = element.offsetHeight + elementMargin.top + elementMargin.bottom, + left = 0 + offsetScroll.x, + top = 0 + offsetScroll.y, + right = containerCoordinates.right - containerBorder.right - width + offsetScroll.x, + bottom = containerCoordinates.bottom - containerBorder.bottom - height + offsetScroll.y; + + if (this.options.includeMargins){ + left += elementMargin.left; + top += elementMargin.top; + } else { + right += elementMargin.right; + bottom += elementMargin.bottom; + } + + if (element.getStyle('position') == 'relative'){ + var coords = element.getCoordinates(offsetParent); + coords.left -= element.getStyle('left').toInt(); + coords.top -= element.getStyle('top').toInt(); + + left -= coords.left; + top -= coords.top; + if (container.getStyle('position') != 'relative'){ + left += containerBorder.left; + top += containerBorder.top; + } + right += elementMargin.left - coords.left; + bottom += elementMargin.top - coords.top; + + if (container != offsetParent){ + left += containerMargin.left + offsetParentPadding.left; + if (!offsetParentPadding.left && left < 0) left = 0; + top += offsetParent == document.body ? 0 : containerMargin.top + offsetParentPadding.top; + if (!offsetParentPadding.top && top < 0) top = 0; + } + } else { + left -= elementMargin.left; + top -= elementMargin.top; + if (container != offsetParent){ + left += containerCoordinates.left + containerBorder.left; + top += containerCoordinates.top + containerBorder.top; + } + } + + return { + x: [left, right], + y: [top, bottom] + }; + }, + + getDroppableCoordinates: function(element){ + var position = element.getCoordinates(); + if (element.getStyle('position') == 'fixed'){ + var scroll = window.getScroll(); + position.left += scroll.x; + position.right += scroll.x; + position.top += scroll.y; + position.bottom += scroll.y; + } + return position; + }, + + checkDroppables: function(){ + var overed = this.droppables.filter(function(el, i){ + el = this.positions ? this.positions[i] : this.getDroppableCoordinates(el); + var now = this.mouse.now; + return (now.x > el.left && now.x < el.right && now.y < el.bottom && now.y > el.top); + }, this).getLast(); + + if (this.overed != overed){ + if (this.overed) this.fireEvent('leave', [this.element, this.overed]); + if (overed) this.fireEvent('enter', [this.element, overed]); + this.overed = overed; + } + }, + + drag: function(event){ + this.parent(event); + if (this.options.checkDroppables && this.droppables.length) this.checkDroppables(); + }, + + stop: function(event){ + this.checkDroppables(); + this.fireEvent('drop', [this.element, this.overed, event]); + this.overed = null; + return this.parent(event); + } + +}); + +Element.implement({ + + makeDraggable: function(options){ + var drag = new Drag.Move(this, options); + this.store('dragger', drag); + return drag; + } + +}); + +/* +--- + +script: Element.Measure.js + +name: Element.Measure + +description: Extends the Element native object to include methods useful in measuring dimensions. + +credits: "Element.measure / .expose methods by Daniel Steigerwald License: MIT-style license. Copyright: Copyright (c) 2008 Daniel Steigerwald, daniel.steigerwald.cz" + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Element.Style + - Core/Element.Dimensions + - MooTools.More + +provides: [Element.Measure] + +... +*/ + +(function(){ + +var getStylesList = function(styles, planes){ + var list = []; + Object.each(planes, function(directions){ + Object.each(directions, function(edge){ + styles.each(function(style){ + list.push(style + '-' + edge + (style == 'border' ? '-width' : '')); + }); + }); + }); + return list; +}; + +var calculateEdgeSize = function(edge, styles){ + var total = 0; + Object.each(styles, function(value, style){ + if (style.test(edge)) total = total + value.toInt(); + }); + return total; +}; + +var isVisible = function(el){ + return !!(!el || el.offsetHeight || el.offsetWidth); +}; + + +Element.implement({ + + measure: function(fn){ + if (isVisible(this)) return fn.call(this); + var parent = this.getParent(), + toMeasure = []; + while (!isVisible(parent) && parent != document.body){ + toMeasure.push(parent.expose()); + parent = parent.getParent(); + } + var restore = this.expose(), + result = fn.call(this); + restore(); + toMeasure.each(function(restore){ + restore(); + }); + return result; + }, + + expose: function(){ + if (this.getStyle('display') != 'none') return function(){}; + var before = this.style.cssText; + this.setStyles({ + display: 'block', + position: 'absolute', + visibility: 'hidden' + }); + return function(){ + this.style.cssText = before; + }.bind(this); + }, + + getDimensions: function(options){ + options = Object.merge({computeSize: false}, options); + var dim = {x: 0, y: 0}; + + var getSize = function(el, options){ + return (options.computeSize) ? el.getComputedSize(options) : el.getSize(); + }; + + var parent = this.getParent('body'); + + if (parent && this.getStyle('display') == 'none'){ + dim = this.measure(function(){ + return getSize(this, options); + }); + } else if (parent){ + try { //safari sometimes crashes here, so catch it + dim = getSize(this, options); + }catch(e){} + } + + return Object.append(dim, (dim.x || dim.x === 0) ? { + width: dim.x, + height: dim.y + } : { + x: dim.width, + y: dim.height + } + ); + }, + + getComputedSize: function(options){ + + + options = Object.merge({ + styles: ['padding','border'], + planes: { + height: ['top','bottom'], + width: ['left','right'] + }, + mode: 'both' + }, options); + + var styles = {}, + size = {width: 0, height: 0}, + dimensions; + + if (options.mode == 'vertical'){ + delete size.width; + delete options.planes.width; + } else if (options.mode == 'horizontal'){ + delete size.height; + delete options.planes.height; + } + + getStylesList(options.styles, options.planes).each(function(style){ + styles[style] = this.getStyle(style).toInt(); + }, this); + + Object.each(options.planes, function(edges, plane){ + + var capitalized = plane.capitalize(), + style = this.getStyle(plane); + + if (style == 'auto' && !dimensions) dimensions = this.getDimensions(); + + style = styles[plane] = (style == 'auto') ? dimensions[plane] : style.toInt(); + size['total' + capitalized] = style; + + edges.each(function(edge){ + var edgesize = calculateEdgeSize(edge, styles); + size['computed' + edge.capitalize()] = edgesize; + size['total' + capitalized] += edgesize; + }); + + }, this); + + return Object.append(size, styles); + } + +}); + +})(); + +/* +--- + +script: Slider.js + +name: Slider + +description: Class for creating horizontal and vertical slider controls. + +license: MIT-style license + +authors: + - Valerio Proietti + +requires: + - Core/Element.Dimensions + - Core/Number + - Class.Binds + - Drag + - Element.Measure + +provides: [Slider] + +... +*/ + +var Slider = new Class({ + + Implements: [Events, Options], + + Binds: ['clickedElement', 'draggedKnob', 'scrolledElement'], + + options: {/* + onTick: function(intPosition){}, + onMove: function(){}, + onChange: function(intStep){}, + onComplete: function(strStep){},*/ + onTick: function(position){ + this.setKnobPosition(position); + }, + initialStep: 0, + snap: false, + offset: 0, + range: false, + wheel: false, + steps: 100, + mode: 'horizontal' + }, + + initialize: function(element, knob, options){ + this.setOptions(options); + options = this.options; + this.element = document.id(element); + knob = this.knob = document.id(knob); + this.previousChange = this.previousEnd = this.step = options.initialStep ? options.initialStep : options.range ? options.range[0] : 0; + + var limit = {}, + modifiers = {x: false, y: false}; + + switch (options.mode){ + case 'vertical': + this.axis = 'y'; + this.property = 'top'; + this.offset = 'offsetHeight'; + break; + case 'horizontal': + this.axis = 'x'; + this.property = 'left'; + this.offset = 'offsetWidth'; + } + + this.setSliderDimensions(); + this.setRange(options.range, null, true); + + if (knob.getStyle('position') == 'static') knob.setStyle('position', 'relative'); + knob.setStyle(this.property, -options.offset); + modifiers[this.axis] = this.property; + limit[this.axis] = [-options.offset, this.full - options.offset]; + + var dragOptions = { + snap: 0, + limit: limit, + modifiers: modifiers, + onDrag: this.draggedKnob, + onStart: this.draggedKnob, + onBeforeStart: (function(){ + this.isDragging = true; + }).bind(this), + onCancel: function(){ + this.isDragging = false; + }.bind(this), + onComplete: function(){ + this.isDragging = false; + this.draggedKnob(); + this.end(); + }.bind(this) + }; + if (options.snap) this.setSnap(dragOptions); + + this.drag = new Drag(knob, dragOptions); + if (options.initialStep != null) this.set(options.initialStep, true); + this.attach(); + }, + + attach: function(){ + this.element.addEvent('mousedown', this.clickedElement); + if (this.options.wheel) this.element.addEvent('mousewheel', this.scrolledElement); + this.drag.attach(); + return this; + }, + + detach: function(){ + this.element.removeEvent('mousedown', this.clickedElement) + .removeEvent('mousewheel', this.scrolledElement); + this.drag.detach(); + return this; + }, + + autosize: function(){ + this.setSliderDimensions() + .setKnobPosition(this.toPosition(this.step)); + this.drag.options.limit[this.axis] = [-this.options.offset, this.full - this.options.offset]; + if (this.options.snap) this.setSnap(); + return this; + }, + + setSnap: function(options){ + if (!options) options = this.drag.options; + options.grid = Math.ceil(this.stepWidth); + options.limit[this.axis][1] = this.element[this.offset]; + return this; + }, + + setKnobPosition: function(position){ + if (this.options.snap) position = this.toPosition(this.step); + this.knob.setStyle(this.property, position); + return this; + }, + + setSliderDimensions: function(){ + this.full = this.element.measure(function(){ + this.half = this.knob[this.offset] / 2; + return this.element[this.offset] - this.knob[this.offset] + (this.options.offset * 2); + }.bind(this)); + return this; + }, + + set: function(step, silently){ + if (!((this.range > 0) ^ (step < this.min))) step = this.min; + if (!((this.range > 0) ^ (step > this.max))) step = this.max; + + this.step = (step).round(this.modulus.decimalLength); + if (silently) this.checkStep().setKnobPosition(this.toPosition(this.step)); + else this.checkStep().fireEvent('tick', this.toPosition(this.step)).fireEvent('move').end(); + return this; + }, + + setRange: function(range, pos, silently){ + this.min = Array.pick([range[0], 0]); + this.max = Array.pick([range[1], this.options.steps]); + this.range = this.max - this.min; + this.steps = this.options.steps || this.full; + var stepSize = this.stepSize = Math.abs(this.range) / this.steps; + this.stepWidth = this.stepSize * this.full / Math.abs(this.range); + this.setModulus(); + + if (range) this.set(Array.pick([pos, this.step]).limit(this.min,this.max), silently); + return this; + }, + + setModulus: function(){ + var decimals = ((this.stepSize + '').split('.')[1] || []).length, + modulus = 1 + ''; + while (decimals--) modulus += '0'; + this.modulus = {multiplier: (modulus).toInt(10), decimalLength: modulus.length - 1}; + }, + + clickedElement: function(event){ + if (this.isDragging || event.target == this.knob) return; + + var dir = this.range < 0 ? -1 : 1, + position = event.page[this.axis] - this.element.getPosition()[this.axis] - this.half; + + position = position.limit(-this.options.offset, this.full - this.options.offset); + + this.step = (this.min + dir * this.toStep(position)).round(this.modulus.decimalLength); + + this.checkStep() + .fireEvent('tick', position) + .fireEvent('move') + .end(); + }, + + scrolledElement: function(event){ + var mode = (this.options.mode == 'horizontal') ? (event.wheel < 0) : (event.wheel > 0); + this.set(this.step + (mode ? -1 : 1) * this.stepSize); + event.stop(); + }, + + draggedKnob: function(){ + var dir = this.range < 0 ? -1 : 1, + position = this.drag.value.now[this.axis]; + + position = position.limit(-this.options.offset, this.full -this.options.offset); + + this.step = (this.min + dir * this.toStep(position)).round(this.modulus.decimalLength); + this.checkStep(); + this.fireEvent('move'); + }, + + checkStep: function(){ + var step = this.step; + if (this.previousChange != step){ + this.previousChange = step; + this.fireEvent('change', step); + } + return this; + }, + + end: function(){ + var step = this.step; + if (this.previousEnd !== step){ + this.previousEnd = step; + this.fireEvent('complete', step + ''); + } + return this; + }, + + toStep: function(position){ + var step = (position + this.options.offset) * this.stepSize / this.full * this.steps; + return this.options.steps ? (step - (step * this.modulus.multiplier) % (this.stepSize * this.modulus.multiplier) / this.modulus.multiplier).round(this.modulus.decimalLength) : step; + }, + + toPosition: function(step){ + return (this.full * Math.abs(this.min - step)) / (this.steps * this.stepSize) - this.options.offset || 0; + } + +}); + +/* +--- + +script: Sortables.js + +name: Sortables + +description: Class for creating a drag and drop sorting interface for lists of items. + +license: MIT-style license + +authors: + - Tom Occhino + +requires: + - Core/Fx.Morph + - Drag.Move + +provides: [Sortables] + +... +*/ + +var Sortables = new Class({ + + Implements: [Events, Options], + + options: {/* + onSort: function(element, clone){}, + onStart: function(element, clone){}, + onComplete: function(element){},*/ + opacity: 1, + clone: false, + revert: false, + handle: false, + dragOptions: {}, + unDraggableTags: ['button', 'input', 'a', 'textarea', 'select', 'option'] + }, + + initialize: function(lists, options){ + this.setOptions(options); + + this.elements = []; + this.lists = []; + this.idle = true; + + this.addLists($$(document.id(lists) || lists)); + + if (!this.options.clone) this.options.revert = false; + if (this.options.revert) this.effect = new Fx.Morph(null, Object.merge({ + duration: 250, + link: 'cancel' + }, this.options.revert)); + }, + + attach: function(){ + this.addLists(this.lists); + return this; + }, + + detach: function(){ + this.lists = this.removeLists(this.lists); + return this; + }, + + addItems: function(){ + Array.flatten(arguments).each(function(element){ + this.elements.push(element); + var start = element.retrieve('sortables:start', function(event){ + this.start.call(this, event, element); + }.bind(this)); + (this.options.handle ? element.getElement(this.options.handle) || element : element).addEvent('mousedown', start); + }, this); + return this; + }, + + addLists: function(){ + Array.flatten(arguments).each(function(list){ + this.lists.include(list); + this.addItems(list.getChildren()); + }, this); + return this; + }, + + removeItems: function(){ + return $$(Array.flatten(arguments).map(function(element){ + this.elements.erase(element); + var start = element.retrieve('sortables:start'); + (this.options.handle ? element.getElement(this.options.handle) || element : element).removeEvent('mousedown', start); + + return element; + }, this)); + }, + + removeLists: function(){ + return $$(Array.flatten(arguments).map(function(list){ + this.lists.erase(list); + this.removeItems(list.getChildren()); + + return list; + }, this)); + }, + + getDroppableCoordinates: function (element){ + var offsetParent = element.getOffsetParent(); + var position = element.getPosition(offsetParent); + var scroll = { + w: window.getScroll(), + offsetParent: offsetParent.getScroll() + }; + position.x += scroll.offsetParent.x; + position.y += scroll.offsetParent.y; + + if (offsetParent.getStyle('position') == 'fixed'){ + position.x -= scroll.w.x; + position.y -= scroll.w.y; + } + + return position; + }, + + getClone: function(event, element){ + if (!this.options.clone) return new Element(element.tagName).inject(document.body); + if (typeOf(this.options.clone) == 'function') return this.options.clone.call(this, event, element, this.list); + var clone = element.clone(true).setStyles({ + margin: 0, + position: 'absolute', + visibility: 'hidden', + width: element.getStyle('width') + }).addEvent('mousedown', function(event){ + element.fireEvent('mousedown', event); + }); + //prevent the duplicated radio inputs from unchecking the real one + if (clone.get('html').test('radio')){ + clone.getElements('input[type=radio]').each(function(input, i){ + input.set('name', 'clone_' + i); + if (input.get('checked')) element.getElements('input[type=radio]')[i].set('checked', true); + }); + } + + return clone.inject(this.list).setPosition(this.getDroppableCoordinates(this.element)); + }, + + getDroppables: function(){ + var droppables = this.list.getChildren().erase(this.clone).erase(this.element); + if (!this.options.constrain) droppables.append(this.lists).erase(this.list); + return droppables; + }, + + insert: function(dragging, element){ + var where = 'inside'; + if (this.lists.contains(element)){ + this.list = element; + this.drag.droppables = this.getDroppables(); + } else { + where = this.element.getAllPrevious().contains(element) ? 'before' : 'after'; + } + this.element.inject(element, where); + this.fireEvent('sort', [this.element, this.clone]); + }, + + start: function(event, element){ + if ( + !this.idle || + event.rightClick || + (!this.options.handle && this.options.unDraggableTags.contains(event.target.get('tag'))) + ) return; + + this.idle = false; + this.element = element; + this.opacity = element.getStyle('opacity'); + this.list = element.getParent(); + this.clone = this.getClone(event, element); + + this.drag = new Drag.Move(this.clone, Object.merge({ + + droppables: this.getDroppables() + }, this.options.dragOptions)).addEvents({ + onSnap: function(){ + event.stop(); + this.clone.setStyle('visibility', 'visible'); + this.element.setStyle('opacity', this.options.opacity || 0); + this.fireEvent('start', [this.element, this.clone]); + }.bind(this), + onEnter: this.insert.bind(this), + onCancel: this.end.bind(this), + onComplete: this.end.bind(this) + }); + + this.clone.inject(this.element, 'before'); + this.drag.start(event); + }, + + end: function(){ + this.drag.detach(); + this.element.setStyle('opacity', this.opacity); + var self = this; + if (this.effect){ + var dim = this.element.getStyles('width', 'height'), + clone = this.clone, + pos = clone.computePosition(this.getDroppableCoordinates(clone)); + + var destroy = function(){ + this.removeEvent('cancel', destroy); + clone.destroy(); + self.reset(); + }; + + this.effect.element = clone; + this.effect.start({ + top: pos.top, + left: pos.left, + width: dim.width, + height: dim.height, + opacity: 0.25 + }).addEvent('cancel', destroy).chain(destroy); + } else { + this.clone.destroy(); + self.reset(); + } + + }, + + reset: function(){ + this.idle = true; + this.fireEvent('complete', this.element); + }, + + serialize: function(){ + var params = Array.link(arguments, { + modifier: Type.isFunction, + index: function(obj){ + return obj != null; + } + }); + var serial = this.lists.map(function(list){ + return list.getChildren().map(params.modifier || function(element){ + return element.get('id'); + }, this); + }, this); + + var index = params.index; + if (this.lists.length == 1) index = 0; + return (index || index === 0) && index >= 0 && index < this.lists.length ? serial[index] : serial; + } + +}); + +/* +--- + +name: Element.Event.Pseudos + +description: Adds the functionality to add pseudo events for Elements + +license: MIT-style license + +authors: + - Arian Stolwijk + +requires: [Core/Element.Event, Core/Element.Delegation, Events.Pseudos] + +provides: [Element.Event.Pseudos, Element.Delegation.Pseudo] + +... +*/ + +(function(){ + +var pseudos = {relay: false}, + copyFromEvents = ['once', 'throttle', 'pause'], + count = copyFromEvents.length; + +while (count--) pseudos[copyFromEvents[count]] = Events.lookupPseudo(copyFromEvents[count]); + +DOMEvent.definePseudo = function(key, listener){ + pseudos[key] = listener; + return this; +}; + +var proto = Element.prototype; +[Element, Window, Document].invoke('implement', Events.Pseudos(pseudos, proto.addEvent, proto.removeEvent)); + +})(); + +/* +--- + +name: Element.Event.Pseudos.Keys + +description: Adds functionality fire events if certain keycombinations are pressed + +license: MIT-style license + +authors: + - Arian Stolwijk + +requires: [Element.Event.Pseudos] + +provides: [Element.Event.Pseudos.Keys] + +... +*/ + +(function(){ + +var keysStoreKey = '$moo:keys-pressed', + keysKeyupStoreKey = '$moo:keys-keyup'; + + +DOMEvent.definePseudo('keys', function(split, fn, args){ + + var event = args[0], + keys = [], + pressed = this.retrieve(keysStoreKey, []), + value = split.value; + + if (value != '+') keys.append(value.replace('++', function(){ + keys.push('+'); // shift++ and shift+++a + return ''; + }).split('+')); + else keys = ['+']; + + pressed.include(event.key); + + if (keys.every(function(key){ + return pressed.contains(key); + })) fn.apply(this, args); + + this.store(keysStoreKey, pressed); + + if (!this.retrieve(keysKeyupStoreKey)){ + var keyup = function(event){ + (function(){ + pressed = this.retrieve(keysStoreKey, []).erase(event.key); + this.store(keysStoreKey, pressed); + }).delay(0, this); // Fix for IE + }; + this.store(keysKeyupStoreKey, keyup).addEvent('keyup', keyup); + } + +}); + +DOMEvent.defineKeys({ + '16': 'shift', + '17': 'control', + '18': 'alt', + '20': 'capslock', + '33': 'pageup', + '34': 'pagedown', + '35': 'end', + '36': 'home', + '144': 'numlock', + '145': 'scrolllock', + '186': ';', + '187': '=', + '188': ',', + '190': '.', + '191': '/', + '192': '`', + '219': '[', + '220': '\\', + '221': ']', + '222': "'", + '107': '+', + '109': '-', // subtract + '189': '-' // dash +}) + +})(); + +/* +--- + +script: String.Extras.js + +name: String.Extras + +description: Extends the String native object to include methods useful in managing various kinds of strings (query strings, urls, html, etc). + +license: MIT-style license + +authors: + - Aaron Newton + - Guillermo Rauch + - Christopher Pitt + +requires: + - Core/String + - Core/Array + - MooTools.More + +provides: [String.Extras] + +... +*/ + +(function(){ + +var special = { + 'a': /[à áâãÀåÄÄ
]/g, + 'A': /[ÃÃÃÃÃÃ
ÄÄ]/g, + 'c': /[ÄÄç]/g, + 'C': /[ÄÄÃ]/g, + 'd': /[ÄÄ]/g, + 'D': /[ÄÃ]/g, + 'e': /[ÚéêëÄÄ]/g, + 'E': /[ÃÃÃÃÄÄ]/g, + 'g': /[Ä]/g, + 'G': /[Ä]/g, + 'i': /[ìÃîï]/g, + 'I': /[ÃÃÃÃ]/g, + 'l': /[ĺğÅ]/g, + 'L': /[ĹĜÅ]/g, + 'n': /[ñÅÅ]/g, + 'N': /[ÃÅÅ]/g, + 'o': /[òóÎõöÞÅ]/g, + 'O': /[ÃÃÃÃÃÃ]/g, + 'r': /[ÅÅ]/g, + 'R': /[ÅÅ]/g, + 's': /[ššÅ]/g, + 'S': /[Å ÅÅ]/g, + 't': /[ťţ]/g, + 'T': /[ŀŢ]/g, + 'u': /[ùúûů̵]/g, + 'U': /[ÃÃÃÅ®Ã]/g, + 'y': /[ÿÜ]/g, + 'Y': /[ÅžÃ]/g, + 'z': /[şźŌ]/g, + 'Z': /[ŜŹŻ]/g, + 'th': /[ß]/g, + 'TH': /[Ã]/g, + 'dh': /[ð]/g, + 'DH': /[Ã]/g, + 'ss': /[Ã]/g, + 'oe': /[Å]/g, + 'OE': /[Å]/g, + 'ae': /[Ê]/g, + 'AE': /[Ã]/g +}, + +tidy = { + ' ': /[\xa0\u2002\u2003\u2009]/g, + '*': /[\xb7]/g, + '\'': /[\u2018\u2019]/g, + '"': /[\u201c\u201d]/g, + '...': /[\u2026]/g, + '-': /[\u2013]/g, +// '--': /[\u2014]/g, + '»': /[\uFFFD]/g +}, + +conversions = { + ms: 1, + s: 1000, + m: 6e4, + h: 36e5 +}, + +findUnits = /(\d*.?\d+)([msh]+)/; + +var walk = function(string, replacements){ + var result = string, key; + for (key in replacements) result = result.replace(replacements[key], key); + return result; +}; + +var getRegexForTag = function(tag, contents){ + tag = tag || ''; + var regstr = contents ? "<" + tag + "(?!\\w)[^>]*>([\\s\\S]*?)<\/" + tag + "(?!\\w)>" : "<\/?" + tag + "([^>]+)?>", + reg = new RegExp(regstr, "gi"); + return reg; +}; + +String.implement({ + + standardize: function(){ + return walk(this, special); + }, + + repeat: function(times){ + return new Array(times + 1).join(this); + }, + + pad: function(length, str, direction){ + if (this.length >= length) return this; + + var pad = (str == null ? ' ' : '' + str) + .repeat(length - this.length) + .substr(0, length - this.length); + + if (!direction || direction == 'right') return this + pad; + if (direction == 'left') return pad + this; + + return pad.substr(0, (pad.length / 2).floor()) + this + pad.substr(0, (pad.length / 2).ceil()); + }, + + getTags: function(tag, contents){ + return this.match(getRegexForTag(tag, contents)) || []; + }, + + stripTags: function(tag, contents){ + return this.replace(getRegexForTag(tag, contents), ''); + }, + + tidy: function(){ + return walk(this, tidy); + }, + + truncate: function(max, trail, atChar){ + var string = this; + if (trail == null && arguments.length == 1) trail = 'âŠ'; + if (string.length > max){ + string = string.substring(0, max); + if (atChar){ + var index = string.lastIndexOf(atChar); + if (index != -1) string = string.substr(0, index); + } + if (trail) string += trail; + } + return string; + }, + + ms: function(){ + // "Borrowed" from https://gist.github.com/1503944 + var units = findUnits.exec(this); + if (units == null) return Number(this); + return Number(units[1]) * conversions[units[2]]; + } + +}); + +})(); + +/* +--- + +script: Element.Forms.js + +name: Element.Forms + +description: Extends the Element native object to include methods useful in managing inputs. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Element + - String.Extras + - MooTools.More + +provides: [Element.Forms] + +... +*/ + +Element.implement({ + + tidy: function(){ + this.set('value', this.get('value').tidy()); + }, + + getTextInRange: function(start, end){ + return this.get('value').substring(start, end); + }, + + getSelectedText: function(){ + if (this.setSelectionRange) return this.getTextInRange(this.getSelectionStart(), this.getSelectionEnd()); + return document.selection.createRange().text; + }, + + getSelectedRange: function(){ + if (this.selectionStart != null){ + return { + start: this.selectionStart, + end: this.selectionEnd + }; + } + + var pos = { + start: 0, + end: 0 + }; + var range = this.getDocument().selection.createRange(); + if (!range || range.parentElement() != this) return pos; + var duplicate = range.duplicate(); + + if (this.type == 'text'){ + pos.start = 0 - duplicate.moveStart('character', -100000); + pos.end = pos.start + range.text.length; + } else { + var value = this.get('value'); + var offset = value.length; + duplicate.moveToElementText(this); + duplicate.setEndPoint('StartToEnd', range); + if (duplicate.text.length) offset -= value.match(/[\n\r]*$/)[0].length; + pos.end = offset - duplicate.text.length; + duplicate.setEndPoint('StartToStart', range); + pos.start = offset - duplicate.text.length; + } + return pos; + }, + + getSelectionStart: function(){ + return this.getSelectedRange().start; + }, + + getSelectionEnd: function(){ + return this.getSelectedRange().end; + }, + + setCaretPosition: function(pos){ + if (pos == 'end') pos = this.get('value').length; + this.selectRange(pos, pos); + return this; + }, + + getCaretPosition: function(){ + return this.getSelectedRange().start; + }, + + selectRange: function(start, end){ + if (this.setSelectionRange){ + this.focus(); + this.setSelectionRange(start, end); + } else { + var value = this.get('value'); + var diff = value.substr(start, end - start).replace(/\r/g, '').length; + start = value.substr(0, start).replace(/\r/g, '').length; + var range = this.createTextRange(); + range.collapse(true); + range.moveEnd('character', start + diff); + range.moveStart('character', start); + range.select(); + } + return this; + }, + + insertAtCursor: function(value, select){ + var pos = this.getSelectedRange(); + var text = this.get('value'); + this.set('value', text.substring(0, pos.start) + value + text.substring(pos.end, text.length)); + if (select !== false) this.selectRange(pos.start, pos.start + value.length); + else this.setCaretPosition(pos.start + value.length); + return this; + }, + + insertAroundCursor: function(options, select){ + options = Object.append({ + before: '', + defaultMiddle: '', + after: '' + }, options); + + var value = this.getSelectedText() || options.defaultMiddle; + var pos = this.getSelectedRange(); + var text = this.get('value'); + + if (pos.start == pos.end){ + this.set('value', text.substring(0, pos.start) + options.before + value + options.after + text.substring(pos.end, text.length)); + this.selectRange(pos.start + options.before.length, pos.end + options.before.length + value.length); + } else { + var current = text.substring(pos.start, pos.end); + this.set('value', text.substring(0, pos.start) + options.before + current + options.after + text.substring(pos.end, text.length)); + var selStart = pos.start + options.before.length; + if (select !== false) this.selectRange(selStart, selStart + current.length); + else this.setCaretPosition(selStart + text.length); + } + return this; + } + +}); + +/* +--- + +script: Element.Pin.js + +name: Element.Pin + +description: Extends the Element native object to include the pin method useful for fixed positioning for elements. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Element.Event + - Core/Element.Dimensions + - Core/Element.Style + - MooTools.More + +provides: [Element.Pin] + +... +*/ + +(function(){ + var supportsPositionFixed = false, + supportTested = false; + + var testPositionFixed = function(){ + var test = new Element('div').setStyles({ + position: 'fixed', + top: 0, + right: 0 + }).inject(document.body); + supportsPositionFixed = (test.offsetTop === 0); + test.dispose(); + supportTested = true; + }; + + Element.implement({ + + pin: function(enable, forceScroll){ + if (!supportTested) testPositionFixed(); + if (this.getStyle('display') == 'none') return this; + + var pinnedPosition, + scroll = window.getScroll(), + parent, + scrollFixer; + + if (enable !== false){ + pinnedPosition = this.getPosition(); + if (!this.retrieve('pin:_pinned')) { + var currentPosition = { + top: pinnedPosition.y - scroll.y, + left: pinnedPosition.x - scroll.x, + margin: '0px', + padding: '0px' + }; + + if (supportsPositionFixed && !forceScroll){ + this.setStyle('position', 'fixed').setStyles(currentPosition); + } else { + + parent = this.getOffsetParent(); + var position = this.getPosition(parent), + styles = this.getStyles('left', 'top'); + + if (parent && styles.left == 'auto' || styles.top == 'auto') this.setPosition(position); + if (this.getStyle('position') == 'static') this.setStyle('position', 'absolute'); + + position = { + x: styles.left.toInt() - scroll.x, + y: styles.top.toInt() - scroll.y + }; + + scrollFixer = function(){ + if (!this.retrieve('pin:_pinned')) return; + var scroll = window.getScroll(); + this.setStyles({ + left: position.x + scroll.x, + top: position.y + scroll.y + }); + }.bind(this); + + this.store('pin:_scrollFixer', scrollFixer); + window.addEvent('scroll', scrollFixer); + } + this.store('pin:_pinned', true); + } + + } else { + if (!this.retrieve('pin:_pinned')) return this; + + parent = this.getParent(); + var offsetParent = (parent.getComputedStyle('position') != 'static' ? parent : parent.getOffsetParent()); + + pinnedPosition = this.getPosition(); + + this.store('pin:_pinned', false); + scrollFixer = this.retrieve('pin:_scrollFixer'); + if (!scrollFixer){ + this.setStyles({ + position: 'absolute', + top: pinnedPosition.y + scroll.y, + left: pinnedPosition.x + scroll.x + }); + } else { + this.store('pin:_scrollFixer', null); + window.removeEvent('scroll', scrollFixer); + } + this.removeClass('isPinned'); + } + return this; + }, + + unpin: function(){ + return this.pin(false); + }, + + togglePin: function(){ + return this.pin(!this.retrieve('pin:_pinned')); + } + + }); + + + +})(); + +/* +--- + +script: Element.Position.js + +name: Element.Position + +description: Extends the Element native object to include methods useful positioning elements relative to others. + +license: MIT-style license + +authors: + - Aaron Newton + - Jacob Thornton + +requires: + - Core/Options + - Core/Element.Dimensions + - Element.Measure + +provides: [Element.Position] + +... +*/ + +(function(original){ + +var local = Element.Position = { + + options: {/* + edge: false, + returnPos: false, + minimum: {x: 0, y: 0}, + maximum: {x: 0, y: 0}, + relFixedPosition: false, + ignoreMargins: false, + ignoreScroll: false, + allowNegative: false,*/ + relativeTo: document.body, + position: { + x: 'center', //left, center, right + y: 'center' //top, center, bottom + }, + offset: {x: 0, y: 0} + }, + + getOptions: function(element, options){ + options = Object.merge({}, local.options, options); + local.setPositionOption(options); + local.setEdgeOption(options); + local.setOffsetOption(element, options); + local.setDimensionsOption(element, options); + return options; + }, + + setPositionOption: function(options){ + options.position = local.getCoordinateFromValue(options.position); + }, + + setEdgeOption: function(options){ + var edgeOption = local.getCoordinateFromValue(options.edge); + options.edge = edgeOption ? edgeOption : + (options.position.x == 'center' && options.position.y == 'center') ? {x: 'center', y: 'center'} : + {x: 'left', y: 'top'}; + }, + + setOffsetOption: function(element, options){ + var parentOffset = {x: 0, y: 0}; + var parentScroll = {x: 0, y: 0}; + var offsetParent = element.measure(function(){ + return document.id(this.getOffsetParent()); + }); + + if (!offsetParent || offsetParent == element.getDocument().body) return; + + parentScroll = offsetParent.getScroll(); + parentOffset = offsetParent.measure(function(){ + var position = this.getPosition(); + if (this.getStyle('position') == 'fixed'){ + var scroll = window.getScroll(); + position.x += scroll.x; + position.y += scroll.y; + } + return position; + }); + + options.offset = { + parentPositioned: offsetParent != document.id(options.relativeTo), + x: options.offset.x - parentOffset.x + parentScroll.x, + y: options.offset.y - parentOffset.y + parentScroll.y + }; + }, + + setDimensionsOption: function(element, options){ + options.dimensions = element.getDimensions({ + computeSize: true, + styles: ['padding', 'border', 'margin'] + }); + }, + + getPosition: function(element, options){ + var position = {}; + options = local.getOptions(element, options); + var relativeTo = document.id(options.relativeTo) || document.body; + + local.setPositionCoordinates(options, position, relativeTo); + if (options.edge) local.toEdge(position, options); + + var offset = options.offset; + position.left = ((position.x >= 0 || offset.parentPositioned || options.allowNegative) ? position.x : 0).toInt(); + position.top = ((position.y >= 0 || offset.parentPositioned || options.allowNegative) ? position.y : 0).toInt(); + + local.toMinMax(position, options); + + if (options.relFixedPosition || relativeTo.getStyle('position') == 'fixed') local.toRelFixedPosition(relativeTo, position); + if (options.ignoreScroll) local.toIgnoreScroll(relativeTo, position); + if (options.ignoreMargins) local.toIgnoreMargins(position, options); + + position.left = Math.ceil(position.left); + position.top = Math.ceil(position.top); + delete position.x; + delete position.y; + + return position; + }, + + setPositionCoordinates: function(options, position, relativeTo){ + var offsetY = options.offset.y, + offsetX = options.offset.x, + calc = (relativeTo == document.body) ? window.getScroll() : relativeTo.getPosition(), + top = calc.y, + left = calc.x, + winSize = window.getSize(); + + switch(options.position.x){ + case 'left': position.x = left + offsetX; break; + case 'right': position.x = left + offsetX + relativeTo.offsetWidth; break; + default: position.x = left + ((relativeTo == document.body ? winSize.x : relativeTo.offsetWidth) / 2) + offsetX; break; + } + + switch(options.position.y){ + case 'top': position.y = top + offsetY; break; + case 'bottom': position.y = top + offsetY + relativeTo.offsetHeight; break; + default: position.y = top + ((relativeTo == document.body ? winSize.y : relativeTo.offsetHeight) / 2) + offsetY; break; + } + }, + + toMinMax: function(position, options){ + var xy = {left: 'x', top: 'y'}, value; + ['minimum', 'maximum'].each(function(minmax){ + ['left', 'top'].each(function(lr){ + value = options[minmax] ? options[minmax][xy[lr]] : null; + if (value != null && ((minmax == 'minimum') ? position[lr] < value : position[lr] > value)) position[lr] = value; + }); + }); + }, + + toRelFixedPosition: function(relativeTo, position){ + var winScroll = window.getScroll(); + position.top += winScroll.y; + position.left += winScroll.x; + }, + + toIgnoreScroll: function(relativeTo, position){ + var relScroll = relativeTo.getScroll(); + position.top -= relScroll.y; + position.left -= relScroll.x; + }, + + toIgnoreMargins: function(position, options){ + position.left += options.edge.x == 'right' + ? options.dimensions['margin-right'] + : (options.edge.x != 'center' + ? -options.dimensions['margin-left'] + : -options.dimensions['margin-left'] + ((options.dimensions['margin-right'] + options.dimensions['margin-left']) / 2)); + + position.top += options.edge.y == 'bottom' + ? options.dimensions['margin-bottom'] + : (options.edge.y != 'center' + ? -options.dimensions['margin-top'] + : -options.dimensions['margin-top'] + ((options.dimensions['margin-bottom'] + options.dimensions['margin-top']) / 2)); + }, + + toEdge: function(position, options){ + var edgeOffset = {}, + dimensions = options.dimensions, + edge = options.edge; + + switch(edge.x){ + case 'left': edgeOffset.x = 0; break; + case 'right': edgeOffset.x = -dimensions.x - dimensions.computedRight - dimensions.computedLeft; break; + // center + default: edgeOffset.x = -(Math.round(dimensions.totalWidth / 2)); break; + } + + switch(edge.y){ + case 'top': edgeOffset.y = 0; break; + case 'bottom': edgeOffset.y = -dimensions.y - dimensions.computedTop - dimensions.computedBottom; break; + // center + default: edgeOffset.y = -(Math.round(dimensions.totalHeight / 2)); break; + } + + position.x += edgeOffset.x; + position.y += edgeOffset.y; + }, + + getCoordinateFromValue: function(option){ + if (typeOf(option) != 'string') return option; + option = option.toLowerCase(); + + return { + x: option.test('left') ? 'left' + : (option.test('right') ? 'right' : 'center'), + y: option.test(/upper|top/) ? 'top' + : (option.test('bottom') ? 'bottom' : 'center') + }; + } + +}; + +Element.implement({ + + position: function(options){ + if (options && (options.x != null || options.y != null)){ + return (original ? original.apply(this, arguments) : this); + } + var position = this.setStyle('position', 'absolute').calculatePosition(options); + return (options && options.returnPos) ? position : this.setStyles(position); + }, + + calculatePosition: function(options){ + return local.getPosition(this, options); + } + +}); + +})(Element.prototype.position); + +/* +--- + +script: Element.Shortcuts.js + +name: Element.Shortcuts + +description: Extends the Element native object to include some shortcut methods. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Element.Style + - MooTools.More + +provides: [Element.Shortcuts] + +... +*/ + +Element.implement({ + + isDisplayed: function(){ + return this.getStyle('display') != 'none'; + }, + + isVisible: function(){ + var w = this.offsetWidth, + h = this.offsetHeight; + return (w == 0 && h == 0) ? false : (w > 0 && h > 0) ? true : this.style.display != 'none'; + }, + + toggle: function(){ + return this[this.isDisplayed() ? 'hide' : 'show'](); + }, + + hide: function(){ + var d; + try { + //IE fails here if the element is not in the dom + d = this.getStyle('display'); + } catch(e){} + if (d == 'none') return this; + return this.store('element:_originalDisplay', d || '').setStyle('display', 'none'); + }, + + show: function(display){ + if (!display && this.isDisplayed()) return this; + display = display || this.retrieve('element:_originalDisplay') || 'block'; + return this.setStyle('display', (display == 'none') ? 'block' : display); + }, + + swapClass: function(remove, add){ + return this.removeClass(remove).addClass(add); + } + +}); + +Document.implement({ + + clearSelection: function(){ + if (window.getSelection){ + var selection = window.getSelection(); + if (selection && selection.removeAllRanges) selection.removeAllRanges(); + } else if (document.selection && document.selection.empty){ + try { + //IE fails here if selected element is not in dom + document.selection.empty(); + } catch(e){} + } + } + +}); + +/* +--- + +script: Elements.From.js + +name: Elements.From + +description: Returns a collection of elements from a string of html. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/String + - Core/Element + - MooTools.More + +provides: [Elements.from, Elements.From] + +... +*/ + +Elements.from = function(text, excludeScripts){ + if (excludeScripts || excludeScripts == null) text = text.stripScripts(); + + var container, match = text.match(/^\s*(?:<!--.*?-->\s*)*<(t[dhr]|tbody|tfoot|thead)/i); + + if (match){ + container = new Element('table'); + var tag = match[1].toLowerCase(); + if (['td', 'th', 'tr'].contains(tag)){ + container = new Element('tbody').inject(container); + if (tag != 'tr') container = new Element('tr').inject(container); + } + } + + return (container || new Element('div')).set('html', text).getChildren(); +}; + +/* +--- + +script: IframeShim.js + +name: IframeShim + +description: Defines IframeShim, a class for obscuring select lists and flash objects in IE. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Element.Event + - Core/Element.Style + - Core/Options + - Core/Events + - Element.Position + - Class.Occlude + +provides: [IframeShim] + +... +*/ + +(function(){ + +var browsers = false; + + +this.IframeShim = new Class({ + + Implements: [Options, Events, Class.Occlude], + + options: { + className: 'iframeShim', + src: 'javascript:false;document.write("");', + display: false, + zIndex: null, + margin: 0, + offset: {x: 0, y: 0}, + browsers: browsers + }, + + property: 'IframeShim', + + initialize: function(element, options){ + this.element = document.id(element); + if (this.occlude()) return this.occluded; + this.setOptions(options); + this.makeShim(); + return this; + }, + + makeShim: function(){ + if (this.options.browsers){ + var zIndex = this.element.getStyle('zIndex').toInt(); + + if (!zIndex){ + zIndex = 1; + var pos = this.element.getStyle('position'); + if (pos == 'static' || !pos) this.element.setStyle('position', 'relative'); + this.element.setStyle('zIndex', zIndex); + } + zIndex = ((this.options.zIndex != null || this.options.zIndex === 0) && zIndex > this.options.zIndex) ? this.options.zIndex : zIndex - 1; + if (zIndex < 0) zIndex = 1; + this.shim = new Element('iframe', { + src: this.options.src, + scrolling: 'no', + frameborder: 0, + styles: { + zIndex: zIndex, + position: 'absolute', + border: 'none', + filter: 'progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)' + }, + 'class': this.options.className + }).store('IframeShim', this); + var inject = (function(){ + this.shim.inject(this.element, 'after'); + this[this.options.display ? 'show' : 'hide'](); + this.fireEvent('inject'); + }).bind(this); + if (!IframeShim.ready) window.addEvent('load', inject); + else inject(); + } else { + this.position = this.hide = this.show = this.dispose = Function.from(this); + } + }, + + position: function(){ + if (!IframeShim.ready || !this.shim) return this; + var size = this.element.measure(function(){ + return this.getSize(); + }); + if (this.options.margin != undefined){ + size.x = size.x - (this.options.margin * 2); + size.y = size.y - (this.options.margin * 2); + this.options.offset.x += this.options.margin; + this.options.offset.y += this.options.margin; + } + this.shim.set({width: size.x, height: size.y}).position({ + relativeTo: this.element, + offset: this.options.offset + }); + return this; + }, + + hide: function(){ + if (this.shim) this.shim.setStyle('display', 'none'); + return this; + }, + + show: function(){ + if (this.shim) this.shim.setStyle('display', 'block'); + return this.position(); + }, + + dispose: function(){ + if (this.shim) this.shim.dispose(); + return this; + }, + + destroy: function(){ + if (this.shim) this.shim.destroy(); + return this; + } + +}); + +})(); + +window.addEvent('load', function(){ + IframeShim.ready = true; +}); + +/* +--- + +script: Mask.js + +name: Mask + +description: Creates a mask element to cover another. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Options + - Core/Events + - Core/Element.Event + - Class.Binds + - Element.Position + - IframeShim + +provides: [Mask] + +... +*/ + +var Mask = new Class({ + + Implements: [Options, Events], + + Binds: ['position'], + + options: {/* + onShow: function(){}, + onHide: function(){}, + onDestroy: function(){}, + onClick: function(event){}, + inject: { + where: 'after', + target: null, + }, + hideOnClick: false, + id: null, + destroyOnHide: false,*/ + style: {}, + 'class': 'mask', + maskMargins: false, + useIframeShim: true, + iframeShimOptions: {} + }, + + initialize: function(target, options){ + this.target = document.id(target) || document.id(document.body); + this.target.store('mask', this); + this.setOptions(options); + this.render(); + this.inject(); + }, + + render: function(){ + this.element = new Element('div', { + 'class': this.options['class'], + id: this.options.id || 'mask-' + String.uniqueID(), + styles: Object.merge({}, this.options.style, { + display: 'none' + }), + events: { + click: function(event){ + this.fireEvent('click', event); + if (this.options.hideOnClick) this.hide(); + }.bind(this) + } + }); + + this.hidden = true; + }, + + toElement: function(){ + return this.element; + }, + + inject: function(target, where){ + where = where || (this.options.inject ? this.options.inject.where : '') || (this.target == document.body ? 'inside' : 'after'); + target = target || (this.options.inject && this.options.inject.target) || this.target; + + this.element.inject(target, where); + + if (this.options.useIframeShim){ + this.shim = new IframeShim(this.element, this.options.iframeShimOptions); + + this.addEvents({ + show: this.shim.show.bind(this.shim), + hide: this.shim.hide.bind(this.shim), + destroy: this.shim.destroy.bind(this.shim) + }); + } + }, + + position: function(){ + this.resize(this.options.width, this.options.height); + + this.element.position({ + relativeTo: this.target, + position: 'topLeft', + ignoreMargins: !this.options.maskMargins, + ignoreScroll: this.target == document.body + }); + + return this; + }, + + resize: function(x, y){ + var opt = { + styles: ['padding', 'border'] + }; + if (this.options.maskMargins) opt.styles.push('margin'); + + var dim = this.target.getComputedSize(opt); + if (this.target == document.body){ + this.element.setStyles({width: 0, height: 0}); + var win = window.getScrollSize(); + if (dim.totalHeight < win.y) dim.totalHeight = win.y; + if (dim.totalWidth < win.x) dim.totalWidth = win.x; + } + this.element.setStyles({ + width: Array.pick([x, dim.totalWidth, dim.x]), + height: Array.pick([y, dim.totalHeight, dim.y]) + }); + + return this; + }, + + show: function(){ + if (!this.hidden) return this; + + window.addEvent('resize', this.position); + this.position(); + this.showMask.apply(this, arguments); + + return this; + }, + + showMask: function(){ + this.element.setStyle('display', 'block'); + this.hidden = false; + this.fireEvent('show'); + }, + + hide: function(){ + if (this.hidden) return this; + + window.removeEvent('resize', this.position); + this.hideMask.apply(this, arguments); + if (this.options.destroyOnHide) return this.destroy(); + + return this; + }, + + hideMask: function(){ + this.element.setStyle('display', 'none'); + this.hidden = true; + this.fireEvent('hide'); + }, + + toggle: function(){ + this[this.hidden ? 'show' : 'hide'](); + }, + + destroy: function(){ + this.hide(); + this.element.destroy(); + this.fireEvent('destroy'); + this.target.eliminate('mask'); + } + +}); + +Element.Properties.mask = { + + set: function(options){ + var mask = this.retrieve('mask'); + if (mask) mask.destroy(); + return this.eliminate('mask').store('mask:options', options); + }, + + get: function(){ + var mask = this.retrieve('mask'); + if (!mask){ + mask = new Mask(this, this.retrieve('mask:options')); + this.store('mask', mask); + } + return mask; + } + +}; + +Element.implement({ + + mask: function(options){ + if (options) this.set('mask', options); + this.get('mask').show(); + return this; + }, + + unmask: function(){ + this.get('mask').hide(); + return this; + } + +}); + +/* +--- + +script: Spinner.js + +name: Spinner + +description: Adds a semi-transparent overlay over a dom element with a spinnin ajax icon. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Fx.Tween + - Core/Request + - Class.refactor + - Mask + +provides: [Spinner] + +... +*/ + +var Spinner = new Class({ + + Extends: Mask, + + Implements: Chain, + + options: {/* + message: false,*/ + 'class': 'spinner', + containerPosition: {}, + content: { + 'class': 'spinner-content' + }, + messageContainer: { + 'class': 'spinner-msg' + }, + img: { + 'class': 'spinner-img' + }, + fxOptions: { + link: 'chain' + } + }, + + initialize: function(target, options){ + this.target = document.id(target) || document.id(document.body); + this.target.store('spinner', this); + this.setOptions(options); + this.render(); + this.inject(); + + // Add this to events for when noFx is true; parent methods handle hide/show. + var deactivate = function(){ this.active = false; }.bind(this); + this.addEvents({ + hide: deactivate, + show: deactivate + }); + }, + + render: function(){ + this.parent(); + + this.element.set('id', this.options.id || 'spinner-' + String.uniqueID()); + + this.content = document.id(this.options.content) || new Element('div', this.options.content); + this.content.inject(this.element); + + if (this.options.message){ + this.msg = document.id(this.options.message) || new Element('p', this.options.messageContainer).appendText(this.options.message); + this.msg.inject(this.content); + } + + if (this.options.img){ + this.img = document.id(this.options.img) || new Element('div', this.options.img); + this.img.inject(this.content); + } + + this.element.set('tween', this.options.fxOptions); + }, + + show: function(noFx){ + if (this.active) return this.chain(this.show.bind(this)); + if (!this.hidden){ + this.callChain.delay(20, this); + return this; + } + + this.target.set('aria-busy', 'true'); + this.active = true; + + return this.parent(noFx); + }, + + showMask: function(noFx){ + var pos = function(){ + this.content.position(Object.merge({ + relativeTo: this.element + }, this.options.containerPosition)); + }.bind(this); + + if (noFx){ + this.parent(); + pos(); + } else { + if (!this.options.style.opacity) this.options.style.opacity = this.element.getStyle('opacity').toFloat(); + this.element.setStyles({ + display: 'block', + opacity: 0 + }).tween('opacity', this.options.style.opacity); + pos(); + this.hidden = false; + this.fireEvent('show'); + this.callChain(); + } + }, + + hide: function(noFx){ + if (this.active) return this.chain(this.hide.bind(this)); + if (this.hidden){ + this.callChain.delay(20, this); + return this; + } + + this.target.set('aria-busy', 'false'); + this.active = true; + + return this.parent(noFx); + }, + + hideMask: function(noFx){ + if (noFx) return this.parent(); + this.element.tween('opacity', 0).get('tween').chain(function(){ + this.element.setStyle('display', 'none'); + this.hidden = true; + this.fireEvent('hide'); + this.callChain(); + }.bind(this)); + }, + + destroy: function(){ + this.content.destroy(); + this.parent(); + this.target.eliminate('spinner'); + } + +}); + +Request = Class.refactor(Request, { + + options: { + useSpinner: false, + spinnerOptions: {}, + spinnerTarget: false + }, + + initialize: function(options){ + this._send = this.send; + this.send = function(options){ + var spinner = this.getSpinner(); + if (spinner) spinner.chain(this._send.pass(options, this)).show(); + else this._send(options); + return this; + }; + this.previous(options); + }, + + getSpinner: function(){ + if (!this.spinner){ + var update = document.id(this.options.spinnerTarget) || document.id(this.options.update); + if (this.options.useSpinner && update){ + update.set('spinner', this.options.spinnerOptions); + var spinner = this.spinner = update.get('spinner'); + ['complete', 'exception', 'cancel'].each(function(event){ + this.addEvent(event, spinner.hide.bind(spinner)); + }, this); + } + } + return this.spinner; + } + +}); + +Element.Properties.spinner = { + + set: function(options){ + var spinner = this.retrieve('spinner'); + if (spinner) spinner.destroy(); + return this.eliminate('spinner').store('spinner:options', options); + }, + + get: function(){ + var spinner = this.retrieve('spinner'); + if (!spinner){ + spinner = new Spinner(this, this.retrieve('spinner:options')); + this.store('spinner', spinner); + } + return spinner; + } + +}; + +Element.implement({ + + spin: function(options){ + if (options) this.set('spinner', options); + this.get('spinner').show(); + return this; + }, + + unspin: function(){ + this.get('spinner').hide(); + return this; + } + +}); + +/* +--- + +script: String.QueryString.js + +name: String.QueryString + +description: Methods for dealing with URI query strings. + +license: MIT-style license + +authors: + - Sebastian MarkbÃ¥ge + - Aaron Newton + - Lennart Pilon + - Valerio Proietti + +requires: + - Core/Array + - Core/String + - MooTools.More + +provides: [String.QueryString] + +... +*/ + +String.implement({ + + parseQueryString: function(decodeKeys, decodeValues){ + if (decodeKeys == null) decodeKeys = true; + if (decodeValues == null) decodeValues = true; + + var vars = this.split(/[&;]/), + object = {}; + if (!vars.length) return object; + + vars.each(function(val){ + var index = val.indexOf('=') + 1, + value = index ? val.substr(index) : '', + keys = index ? val.substr(0, index - 1).match(/([^\]\[]+|(\B)(?=\]))/g) : [val], + obj = object; + if (!keys) return; + if (decodeValues) value = decodeURIComponent(value); + keys.each(function(key, i){ + if (decodeKeys) key = decodeURIComponent(key); + var current = obj[key]; + + if (i < keys.length - 1) obj = obj[key] = current || {}; + else if (typeOf(current) == 'array') current.push(value); + else obj[key] = current != null ? [current, value] : value; + }); + }); + + return object; + }, + + cleanQueryString: function(method){ + return this.split('&').filter(function(val){ + var index = val.indexOf('='), + key = index < 0 ? '' : val.substr(0, index), + value = val.substr(index + 1); + + return method ? method.call(null, key, value) : (value || value === 0); + }).join('&'); + } + +}); + +/* +--- + +script: Form.Request.js + +name: Form.Request + +description: Handles the basic functionality of submitting a form and updating a dom element with the result. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Request.HTML + - Class.Binds + - Class.Occlude + - Spinner + - String.QueryString + - Element.Delegation.Pseudo + +provides: [Form.Request] + +... +*/ + +if (!window.Form) window.Form = {}; + +(function(){ + + Form.Request = new Class({ + + Binds: ['onSubmit', 'onFormValidate'], + + Implements: [Options, Events, Class.Occlude], + + options: {/* + onFailure: function(){}, + onSuccess: function(){}, // aliased to onComplete, + onSend: function(){}*/ + requestOptions: { + evalScripts: true, + useSpinner: true, + emulation: false, + link: 'ignore' + }, + sendButtonClicked: true, + extraData: {}, + resetForm: true + }, + + property: 'form.request', + + initialize: function(form, target, options){ + this.element = document.id(form); + if (this.occlude()) return this.occluded; + this.setOptions(options) + .setTarget(target) + .attach(); + }, + + setTarget: function(target){ + this.target = document.id(target); + if (!this.request){ + this.makeRequest(); + } else { + this.request.setOptions({ + update: this.target + }); + } + return this; + }, + + toElement: function(){ + return this.element; + }, + + makeRequest: function(){ + var self = this; + this.request = new Request.HTML(Object.merge({ + update: this.target, + emulation: false, + spinnerTarget: this.element, + method: this.element.get('method') || 'post' + }, this.options.requestOptions)).addEvents({ + success: function(tree, elements, html, javascript){ + ['complete', 'success'].each(function(evt){ + self.fireEvent(evt, [self.target, tree, elements, html, javascript]); + }); + }, + failure: function(){ + self.fireEvent('complete', arguments).fireEvent('failure', arguments); + }, + exception: function(){ + self.fireEvent('failure', arguments); + } + }); + return this.attachReset(); + }, + + attachReset: function(){ + if (!this.options.resetForm) return this; + this.request.addEvent('success', function(){ + Function.attempt(function(){ + this.element.reset(); + }.bind(this)); + if (window.OverText) OverText.update(); + }.bind(this)); + return this; + }, + + attach: function(attach){ + var method = (attach != false) ? 'addEvent' : 'removeEvent'; + this.element[method]('click:relay(button, input[type=submit])', this.saveClickedButton.bind(this)); + + var fv = this.element.retrieve('validator'); + if (fv) fv[method]('onFormValidate', this.onFormValidate); + else this.element[method]('submit', this.onSubmit); + + return this; + }, + + detach: function(){ + return this.attach(false); + }, + + //public method + enable: function(){ + return this.attach(); + }, + + //public method + disable: function(){ + return this.detach(); + }, + + onFormValidate: function(valid, form, event){ + //if there's no event, then this wasn't a submit event + if (!event) return; + var fv = this.element.retrieve('validator'); + if (valid || (fv && !fv.options.stopOnFailure)){ + event.stop(); + this.send(); + } + }, + + onSubmit: function(event){ + var fv = this.element.retrieve('validator'); + if (fv){ + //form validator was created after Form.Request + this.element.removeEvent('submit', this.onSubmit); + fv.addEvent('onFormValidate', this.onFormValidate); + fv.validate(event); + return; + } + if (event) event.stop(); + this.send(); + }, + + saveClickedButton: function(event, target){ + var targetName = target.get('name'); + if (!targetName || !this.options.sendButtonClicked) return; + this.options.extraData[targetName] = target.get('value') || true; + this.clickedCleaner = function(){ + delete this.options.extraData[targetName]; + this.clickedCleaner = function(){}; + }.bind(this); + }, + + clickedCleaner: function(){}, + + send: function(){ + var str = this.element.toQueryString().trim(), + data = Object.toQueryString(this.options.extraData); + + if (str) str += "&" + data; + else str = data; + + this.fireEvent('send', [this.element, str.parseQueryString()]); + this.request.send({ + data: str, + url: this.options.requestOptions.url || this.element.get('action') + }); + this.clickedCleaner(); + return this; + } + + }); + + Element.implement('formUpdate', function(update, options){ + var fq = this.retrieve('form.request'); + if (!fq){ + fq = new Form.Request(this, update, options); + } else { + if (update) fq.setTarget(update); + if (options) fq.setOptions(options).makeRequest(); + } + fq.send(); + return this; + }); + +})(); + +/* +--- + +script: Fx.Reveal.js + +name: Fx.Reveal + +description: Defines Fx.Reveal, a class that shows and hides elements with a transition. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Fx.Morph + - Element.Shortcuts + - Element.Measure + +provides: [Fx.Reveal] + +... +*/ + +(function(){ + + +var hideTheseOf = function(object){ + var hideThese = object.options.hideInputs; + if (window.OverText){ + var otClasses = [null]; + OverText.each(function(ot){ + otClasses.include('.' + ot.options.labelClass); + }); + if (otClasses) hideThese += otClasses.join(', '); + } + return (hideThese) ? object.element.getElements(hideThese) : null; +}; + + +Fx.Reveal = new Class({ + + Extends: Fx.Morph, + + options: {/* + onShow: function(thisElement){}, + onHide: function(thisElement){}, + onComplete: function(thisElement){}, + heightOverride: null, + widthOverride: null,*/ + link: 'cancel', + styles: ['padding', 'border', 'margin'], + transitionOpacity: 'opacity' in document.documentElement, + mode: 'vertical', + display: function(){ + return this.element.get('tag') != 'tr' ? 'block' : 'table-row'; + }, + opacity: 1, + hideInputs: !('opacity' in document.documentElement) ? 'select, input, textarea, object, embed' : null + }, + + dissolve: function(){ + if (!this.hiding && !this.showing){ + if (this.element.getStyle('display') != 'none'){ + this.hiding = true; + this.showing = false; + this.hidden = true; + this.cssText = this.element.style.cssText; + + var startStyles = this.element.getComputedSize({ + styles: this.options.styles, + mode: this.options.mode + }); + if (this.options.transitionOpacity) startStyles.opacity = this.options.opacity; + + var zero = {}; + Object.each(startStyles, function(style, name){ + zero[name] = [style, 0]; + }); + + this.element.setStyles({ + display: Function.from(this.options.display).call(this), + overflow: 'hidden' + }); + + var hideThese = hideTheseOf(this); + if (hideThese) hideThese.setStyle('visibility', 'hidden'); + + this.$chain.unshift(function(){ + if (this.hidden){ + this.hiding = false; + this.element.style.cssText = this.cssText; + this.element.setStyle('display', 'none'); + if (hideThese) hideThese.setStyle('visibility', 'visible'); + } + this.fireEvent('hide', this.element); + this.callChain(); + }.bind(this)); + + this.start(zero); + } else { + this.callChain.delay(10, this); + this.fireEvent('complete', this.element); + this.fireEvent('hide', this.element); + } + } else if (this.options.link == 'chain'){ + this.chain(this.dissolve.bind(this)); + } else if (this.options.link == 'cancel' && !this.hiding){ + this.cancel(); + this.dissolve(); + } + return this; + }, + + reveal: function(){ + if (!this.showing && !this.hiding){ + if (this.element.getStyle('display') == 'none'){ + this.hiding = false; + this.showing = true; + this.hidden = false; + this.cssText = this.element.style.cssText; + + var startStyles; + this.element.measure(function(){ + startStyles = this.element.getComputedSize({ + styles: this.options.styles, + mode: this.options.mode + }); + }.bind(this)); + if (this.options.heightOverride != null) startStyles.height = this.options.heightOverride.toInt(); + if (this.options.widthOverride != null) startStyles.width = this.options.widthOverride.toInt(); + if (this.options.transitionOpacity){ + this.element.setStyle('opacity', 0); + startStyles.opacity = this.options.opacity; + } + + var zero = { + height: 0, + display: Function.from(this.options.display).call(this) + }; + Object.each(startStyles, function(style, name){ + zero[name] = 0; + }); + zero.overflow = 'hidden'; + + this.element.setStyles(zero); + + var hideThese = hideTheseOf(this); + if (hideThese) hideThese.setStyle('visibility', 'hidden'); + + this.$chain.unshift(function(){ + this.element.style.cssText = this.cssText; + this.element.setStyle('display', Function.from(this.options.display).call(this)); + if (!this.hidden) this.showing = false; + if (hideThese) hideThese.setStyle('visibility', 'visible'); + this.callChain(); + this.fireEvent('show', this.element); + }.bind(this)); + + this.start(startStyles); + } else { + this.callChain(); + this.fireEvent('complete', this.element); + this.fireEvent('show', this.element); + } + } else if (this.options.link == 'chain'){ + this.chain(this.reveal.bind(this)); + } else if (this.options.link == 'cancel' && !this.showing){ + this.cancel(); + this.reveal(); + } + return this; + }, + + toggle: function(){ + if (this.element.getStyle('display') == 'none'){ + this.reveal(); + } else { + this.dissolve(); + } + return this; + }, + + cancel: function(){ + this.parent.apply(this, arguments); + if (this.cssText != null) this.element.style.cssText = this.cssText; + this.hiding = false; + this.showing = false; + return this; + } + +}); + +Element.Properties.reveal = { + + set: function(options){ + this.get('reveal').cancel().setOptions(options); + return this; + }, + + get: function(){ + var reveal = this.retrieve('reveal'); + if (!reveal){ + reveal = new Fx.Reveal(this); + this.store('reveal', reveal); + } + return reveal; + } + +}; + +Element.Properties.dissolve = Element.Properties.reveal; + +Element.implement({ + + reveal: function(options){ + this.get('reveal').setOptions(options).reveal(); + return this; + }, + + dissolve: function(options){ + this.get('reveal').setOptions(options).dissolve(); + return this; + }, + + nix: function(options){ + var params = Array.link(arguments, {destroy: Type.isBoolean, options: Type.isObject}); + this.get('reveal').setOptions(options).dissolve().chain(function(){ + this[params.destroy ? 'destroy' : 'dispose'](); + }.bind(this)); + return this; + }, + + wink: function(){ + var params = Array.link(arguments, {duration: Type.isNumber, options: Type.isObject}); + var reveal = this.get('reveal').setOptions(params.options); + reveal.reveal().chain(function(){ + (function(){ + reveal.dissolve(); + }).delay(params.duration || 2000); + }); + } + +}); + +})(); + +/* +--- + +script: Form.Request.Append.js + +name: Form.Request.Append + +description: Handles the basic functionality of submitting a form and updating a dom element with the result. The result is appended to the DOM element instead of replacing its contents. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Form.Request + - Fx.Reveal + - Elements.from + +provides: [Form.Request.Append] + +... +*/ + +Form.Request.Append = new Class({ + + Extends: Form.Request, + + options: { + //onBeforeEffect: function(){}, + useReveal: true, + revealOptions: {}, + inject: 'bottom' + }, + + makeRequest: function(){ + this.request = new Request.HTML(Object.merge({ + url: this.element.get('action'), + method: this.element.get('method') || 'post', + spinnerTarget: this.element + }, this.options.requestOptions, { + evalScripts: false + }) + ).addEvents({ + success: function(tree, elements, html, javascript){ + var container; + var kids = Elements.from(html); + if (kids.length == 1){ + container = kids[0]; + } else { + container = new Element('div', { + styles: { + display: 'none' + } + }).adopt(kids); + } + container.inject(this.target, this.options.inject); + if (this.options.requestOptions.evalScripts) Browser.exec(javascript); + this.fireEvent('beforeEffect', container); + var finish = function(){ + this.fireEvent('success', [container, this.target, tree, elements, html, javascript]); + }.bind(this); + if (this.options.useReveal){ + container.set('reveal', this.options.revealOptions).get('reveal').chain(finish); + container.reveal(); + } else { + finish(); + } + }.bind(this), + failure: function(xhr){ + this.fireEvent('failure', xhr); + }.bind(this) + }); + this.attachReset(); + } + +}); + +/* +--- + +script: Object.Extras.js + +name: Object.Extras + +description: Extra Object generics, like getFromPath which allows a path notation to child elements. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Object + - MooTools.More + +provides: [Object.Extras] + +... +*/ + +(function(){ + +var defined = function(value){ + return value != null; +}; + +var hasOwnProperty = Object.prototype.hasOwnProperty; + +Object.extend({ + + getFromPath: function(source, parts){ + if (typeof parts == 'string') parts = parts.split('.'); + for (var i = 0, l = parts.length; i < l; i++){ + if (hasOwnProperty.call(source, parts[i])) source = source[parts[i]]; + else return null; + } + return source; + }, + + cleanValues: function(object, method){ + method = method || defined; + for (var key in object) if (!method(object[key])){ + delete object[key]; + } + return object; + }, + + erase: function(object, key){ + if (hasOwnProperty.call(object, key)) delete object[key]; + return object; + }, + + run: function(object){ + var args = Array.slice(arguments, 1); + for (var key in object) if (object[key].apply){ + object[key].apply(object, args); + } + return object; + } + +}); + +})(); + +/* +--- + +script: Locale.js + +name: Locale + +description: Provides methods for localization. + +license: MIT-style license + +authors: + - Aaron Newton + - Arian Stolwijk + +requires: + - Core/Events + - Object.Extras + - MooTools.More + +provides: [Locale, Lang] + +... +*/ + +(function(){ + +var current = null, + locales = {}, + inherits = {}; + +var getSet = function(set){ + if (instanceOf(set, Locale.Set)) return set; + else return locales[set]; +}; + +var Locale = this.Locale = { + + define: function(locale, set, key, value){ + var name; + if (instanceOf(locale, Locale.Set)){ + name = locale.name; + if (name) locales[name] = locale; + } else { + name = locale; + if (!locales[name]) locales[name] = new Locale.Set(name); + locale = locales[name]; + } + + if (set) locale.define(set, key, value); + + + + if (!current) current = locale; + + return locale; + }, + + use: function(locale){ + locale = getSet(locale); + + if (locale){ + current = locale; + + this.fireEvent('change', locale); + + + } + + return this; + }, + + getCurrent: function(){ + return current; + }, + + get: function(key, args){ + return (current) ? current.get(key, args) : ''; + }, + + inherit: function(locale, inherits, set){ + locale = getSet(locale); + + if (locale) locale.inherit(inherits, set); + return this; + }, + + list: function(){ + return Object.keys(locales); + } + +}; + +Object.append(Locale, new Events); + +Locale.Set = new Class({ + + sets: {}, + + inherits: { + locales: [], + sets: {} + }, + + initialize: function(name){ + this.name = name || ''; + }, + + define: function(set, key, value){ + var defineData = this.sets[set]; + if (!defineData) defineData = {}; + + if (key){ + if (typeOf(key) == 'object') defineData = Object.merge(defineData, key); + else defineData[key] = value; + } + this.sets[set] = defineData; + + return this; + }, + + get: function(key, args, _base){ + var value = Object.getFromPath(this.sets, key); + if (value != null){ + var type = typeOf(value); + if (type == 'function') value = value.apply(null, Array.from(args)); + else if (type == 'object') value = Object.clone(value); + return value; + } + + // get value of inherited locales + var index = key.indexOf('.'), + set = index < 0 ? key : key.substr(0, index), + names = (this.inherits.sets[set] || []).combine(this.inherits.locales).include('en-US'); + if (!_base) _base = []; + + for (var i = 0, l = names.length; i < l; i++){ + if (_base.contains(names[i])) continue; + _base.include(names[i]); + + var locale = locales[names[i]]; + if (!locale) continue; + + value = locale.get(key, args, _base); + if (value != null) return value; + } + + return ''; + }, + + inherit: function(names, set){ + names = Array.from(names); + + if (set && !this.inherits.sets[set]) this.inherits.sets[set] = []; + + var l = names.length; + while (l--) (set ? this.inherits.sets[set] : this.inherits.locales).unshift(names[l]); + + return this; + } + +}); + + + +})(); + +/* +--- + +name: Locale.en-US.Date + +description: Date messages for US English. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Locale + +provides: [Locale.en-US.Date] + +... +*/ + +Locale.define('en-US', 'Date', { + + months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + months_abbr: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], + days_abbr: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + + // Culture's date order: MM/DD/YYYY + dateOrder: ['month', 'date', 'year'], + shortDate: '%m/%d/%Y', + shortTime: '%I:%M%p', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 0, + + // Date.Extras + ordinal: function(dayOfMonth){ + // 1st, 2nd, 3rd, etc. + return (dayOfMonth > 3 && dayOfMonth < 21) ? 'th' : ['th', 'st', 'nd', 'rd', 'th'][Math.min(dayOfMonth % 10, 4)]; + }, + + lessThanMinuteAgo: 'less than a minute ago', + minuteAgo: 'about a minute ago', + minutesAgo: '{delta} minutes ago', + hourAgo: 'about an hour ago', + hoursAgo: 'about {delta} hours ago', + dayAgo: '1 day ago', + daysAgo: '{delta} days ago', + weekAgo: '1 week ago', + weeksAgo: '{delta} weeks ago', + monthAgo: '1 month ago', + monthsAgo: '{delta} months ago', + yearAgo: '1 year ago', + yearsAgo: '{delta} years ago', + + lessThanMinuteUntil: 'less than a minute from now', + minuteUntil: 'about a minute from now', + minutesUntil: '{delta} minutes from now', + hourUntil: 'about an hour from now', + hoursUntil: 'about {delta} hours from now', + dayUntil: '1 day from now', + daysUntil: '{delta} days from now', + weekUntil: '1 week from now', + weeksUntil: '{delta} weeks from now', + monthUntil: '1 month from now', + monthsUntil: '{delta} months from now', + yearUntil: '1 year from now', + yearsUntil: '{delta} years from now' + +}); + +/* +--- + +script: Date.js + +name: Date + +description: Extends the Date native object to include methods useful in managing dates. + +license: MIT-style license + +authors: + - Aaron Newton + - Nicholas Barthelemy - https://svn.nbarthelemy.com/date-js/ + - Harald Kirshner - mail [at] digitarald.de; http://digitarald.de + - Scott Kyle - scott [at] appden.com; http://appden.com + +requires: + - Core/Array + - Core/String + - Core/Number + - MooTools.More + - Locale + - Locale.en-US.Date + +provides: [Date] + +... +*/ + +(function(){ + +var Date = this.Date; + +var DateMethods = Date.Methods = { + ms: 'Milliseconds', + year: 'FullYear', + min: 'Minutes', + mo: 'Month', + sec: 'Seconds', + hr: 'Hours' +}; + +['Date', 'Day', 'FullYear', 'Hours', 'Milliseconds', 'Minutes', 'Month', 'Seconds', 'Time', 'TimezoneOffset', + 'Week', 'Timezone', 'GMTOffset', 'DayOfYear', 'LastMonth', 'LastDayOfMonth', 'UTCDate', 'UTCDay', 'UTCFullYear', + 'AMPM', 'Ordinal', 'UTCHours', 'UTCMilliseconds', 'UTCMinutes', 'UTCMonth', 'UTCSeconds', 'UTCMilliseconds'].each(function(method){ + Date.Methods[method.toLowerCase()] = method; +}); + +var pad = function(n, digits, string){ + if (digits == 1) return n; + return n < Math.pow(10, digits - 1) ? (string || '0') + pad(n, digits - 1, string) : n; +}; + +Date.implement({ + + set: function(prop, value){ + prop = prop.toLowerCase(); + var method = DateMethods[prop] && 'set' + DateMethods[prop]; + if (method && this[method]) this[method](value); + return this; + }.overloadSetter(), + + get: function(prop){ + prop = prop.toLowerCase(); + var method = DateMethods[prop] && 'get' + DateMethods[prop]; + if (method && this[method]) return this[method](); + return null; + }.overloadGetter(), + + clone: function(){ + return new Date(this.get('time')); + }, + + increment: function(interval, times){ + interval = interval || 'day'; + times = times != null ? times : 1; + + switch (interval){ + case 'year': + return this.increment('month', times * 12); + case 'month': + var d = this.get('date'); + this.set('date', 1).set('mo', this.get('mo') + times); + return this.set('date', d.min(this.get('lastdayofmonth'))); + case 'week': + return this.increment('day', times * 7); + case 'day': + return this.set('date', this.get('date') + times); + } + + if (!Date.units[interval]) throw new Error(interval + ' is not a supported interval'); + + return this.set('time', this.get('time') + times * Date.units[interval]()); + }, + + decrement: function(interval, times){ + return this.increment(interval, -1 * (times != null ? times : 1)); + }, + + isLeapYear: function(){ + return Date.isLeapYear(this.get('year')); + }, + + clearTime: function(){ + return this.set({hr: 0, min: 0, sec: 0, ms: 0}); + }, + + diff: function(date, resolution){ + if (typeOf(date) == 'string') date = Date.parse(date); + + return ((date - this) / Date.units[resolution || 'day'](3, 3)).round(); // non-leap year, 30-day month + }, + + getLastDayOfMonth: function(){ + return Date.daysInMonth(this.get('mo'), this.get('year')); + }, + + getDayOfYear: function(){ + return (Date.UTC(this.get('year'), this.get('mo'), this.get('date') + 1) + - Date.UTC(this.get('year'), 0, 1)) / Date.units.day(); + }, + + setDay: function(day, firstDayOfWeek){ + if (firstDayOfWeek == null){ + firstDayOfWeek = Date.getMsg('firstDayOfWeek'); + if (firstDayOfWeek === '') firstDayOfWeek = 1; + } + + day = (7 + Date.parseDay(day, true) - firstDayOfWeek) % 7; + var currentDay = (7 + this.get('day') - firstDayOfWeek) % 7; + + return this.increment('day', day - currentDay); + }, + + getWeek: function(firstDayOfWeek){ + if (firstDayOfWeek == null){ + firstDayOfWeek = Date.getMsg('firstDayOfWeek'); + if (firstDayOfWeek === '') firstDayOfWeek = 1; + } + + var date = this, + dayOfWeek = (7 + date.get('day') - firstDayOfWeek) % 7, + dividend = 0, + firstDayOfYear; + + if (firstDayOfWeek == 1){ + // ISO-8601, week belongs to year that has the most days of the week (i.e. has the thursday of the week) + var month = date.get('month'), + startOfWeek = date.get('date') - dayOfWeek; + + if (month == 11 && startOfWeek > 28) return 1; // Week 1 of next year + + if (month == 0 && startOfWeek < -2){ + // Use a date from last year to determine the week + date = new Date(date).decrement('day', dayOfWeek); + dayOfWeek = 0; + } + + firstDayOfYear = new Date(date.get('year'), 0, 1).get('day') || 7; + if (firstDayOfYear > 4) dividend = -7; // First week of the year is not week 1 + } else { + // In other cultures the first week of the year is always week 1 and the last week always 53 or 54. + // Days in the same week can have a different weeknumber if the week spreads across two years. + firstDayOfYear = new Date(date.get('year'), 0, 1).get('day'); + } + + dividend += date.get('dayofyear'); + dividend += 6 - dayOfWeek; // Add days so we calculate the current date's week as a full week + dividend += (7 + firstDayOfYear - firstDayOfWeek) % 7; // Make up for first week of the year not being a full week + + return (dividend / 7); + }, + + getOrdinal: function(day){ + return Date.getMsg('ordinal', day || this.get('date')); + }, + + getTimezone: function(){ + return this.toString() + .replace(/^.*? ([A-Z]{3}).[0-9]{4}.*$/, '$1') + .replace(/^.*?\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\)$/, '$1$2$3'); + }, + + getGMTOffset: function(){ + var off = this.get('timezoneOffset'); + return ((off > 0) ? '-' : '+') + pad((off.abs() / 60).floor(), 2) + pad(off % 60, 2); + }, + + setAMPM: function(ampm){ + ampm = ampm.toUpperCase(); + var hr = this.get('hr'); + if (hr > 11 && ampm == 'AM') return this.decrement('hour', 12); + else if (hr < 12 && ampm == 'PM') return this.increment('hour', 12); + return this; + }, + + getAMPM: function(){ + return (this.get('hr') < 12) ? 'AM' : 'PM'; + }, + + parse: function(str){ + this.set('time', Date.parse(str)); + return this; + }, + + isValid: function(date){ + if (!date) date = this; + return typeOf(date) == 'date' && !isNaN(date.valueOf()); + }, + + format: function(format){ + if (!this.isValid()) return 'invalid date'; + + if (!format) format = '%x %X'; + if (typeof format == 'string') format = formats[format.toLowerCase()] || format; + if (typeof format == 'function') return format(this); + + var d = this; + return format.replace(/%([a-z%])/gi, + function($0, $1){ + switch ($1){ + case 'a': return Date.getMsg('days_abbr')[d.get('day')]; + case 'A': return Date.getMsg('days')[d.get('day')]; + case 'b': return Date.getMsg('months_abbr')[d.get('month')]; + case 'B': return Date.getMsg('months')[d.get('month')]; + case 'c': return d.format('%a %b %d %H:%M:%S %Y'); + case 'd': return pad(d.get('date'), 2); + case 'e': return pad(d.get('date'), 2, ' '); + case 'H': return pad(d.get('hr'), 2); + case 'I': return pad((d.get('hr') % 12) || 12, 2); + case 'j': return pad(d.get('dayofyear'), 3); + case 'k': return pad(d.get('hr'), 2, ' '); + case 'l': return pad((d.get('hr') % 12) || 12, 2, ' '); + case 'L': return pad(d.get('ms'), 3); + case 'm': return pad((d.get('mo') + 1), 2); + case 'M': return pad(d.get('min'), 2); + case 'o': return d.get('ordinal'); + case 'p': return Date.getMsg(d.get('ampm')); + case 's': return Math.round(d / 1000); + case 'S': return pad(d.get('seconds'), 2); + case 'T': return d.format('%H:%M:%S'); + case 'U': return pad(d.get('week'), 2); + case 'w': return d.get('day'); + case 'x': return d.format(Date.getMsg('shortDate')); + case 'X': return d.format(Date.getMsg('shortTime')); + case 'y': return d.get('year').toString().substr(2); + case 'Y': return d.get('year'); + case 'z': return d.get('GMTOffset'); + case 'Z': return d.get('Timezone'); + } + return $1; + } + ); + }, + + toISOString: function(){ + return this.format('iso8601'); + } + +}).alias({ + toJSON: 'toISOString', + compare: 'diff', + strftime: 'format' +}); + +// The day and month abbreviations are standardized, so we cannot use simply %a and %b because they will get localized +var rfcDayAbbr = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + rfcMonthAbbr = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; + +var formats = { + db: '%Y-%m-%d %H:%M:%S', + compact: '%Y%m%dT%H%M%S', + 'short': '%d %b %H:%M', + 'long': '%B %d, %Y %H:%M', + rfc822: function(date){ + return rfcDayAbbr[date.get('day')] + date.format(', %d ') + rfcMonthAbbr[date.get('month')] + date.format(' %Y %H:%M:%S %Z'); + }, + rfc2822: function(date){ + return rfcDayAbbr[date.get('day')] + date.format(', %d ') + rfcMonthAbbr[date.get('month')] + date.format(' %Y %H:%M:%S %z'); + }, + iso8601: function(date){ + return ( + date.getUTCFullYear() + '-' + + pad(date.getUTCMonth() + 1, 2) + '-' + + pad(date.getUTCDate(), 2) + 'T' + + pad(date.getUTCHours(), 2) + ':' + + pad(date.getUTCMinutes(), 2) + ':' + + pad(date.getUTCSeconds(), 2) + '.' + + pad(date.getUTCMilliseconds(), 3) + 'Z' + ); + } +}; + +var parsePatterns = [], + nativeParse = Date.parse; + +var parseWord = function(type, word, num){ + var ret = -1, + translated = Date.getMsg(type + 's'); + switch (typeOf(word)){ + case 'object': + ret = translated[word.get(type)]; + break; + case 'number': + ret = translated[word]; + if (!ret) throw new Error('Invalid ' + type + ' index: ' + word); + break; + case 'string': + var match = translated.filter(function(name){ + return this.test(name); + }, new RegExp('^' + word, 'i')); + if (!match.length) throw new Error('Invalid ' + type + ' string'); + if (match.length > 1) throw new Error('Ambiguous ' + type); + ret = match[0]; + } + + return (num) ? translated.indexOf(ret) : ret; +}; + +var startCentury = 1900, + startYear = 70; + +Date.extend({ + + getMsg: function(key, args){ + return Locale.get('Date.' + key, args); + }, + + units: { + ms: Function.from(1), + second: Function.from(1000), + minute: Function.from(60000), + hour: Function.from(3600000), + day: Function.from(86400000), + week: Function.from(608400000), + month: function(month, year){ + var d = new Date; + return Date.daysInMonth(month != null ? month : d.get('mo'), year != null ? year : d.get('year')) * 86400000; + }, + year: function(year){ + year = year || new Date().get('year'); + return Date.isLeapYear(year) ? 31622400000 : 31536000000; + } + }, + + daysInMonth: function(month, year){ + return [31, Date.isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]; + }, + + isLeapYear: function(year){ + return ((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0); + }, + + parse: function(from){ + var t = typeOf(from); + if (t == 'number') return new Date(from); + if (t != 'string') return from; + from = from.clean(); + if (!from.length) return null; + + var parsed; + parsePatterns.some(function(pattern){ + var bits = pattern.re.exec(from); + return (bits) ? (parsed = pattern.handler(bits)) : false; + }); + + if (!(parsed && parsed.isValid())){ + parsed = new Date(nativeParse(from)); + if (!(parsed && parsed.isValid())) parsed = new Date(from.toInt()); + } + return parsed; + }, + + parseDay: function(day, num){ + return parseWord('day', day, num); + }, + + parseMonth: function(month, num){ + return parseWord('month', month, num); + }, + + parseUTC: function(value){ + var localDate = new Date(value); + var utcSeconds = Date.UTC( + localDate.get('year'), + localDate.get('mo'), + localDate.get('date'), + localDate.get('hr'), + localDate.get('min'), + localDate.get('sec'), + localDate.get('ms') + ); + return new Date(utcSeconds); + }, + + orderIndex: function(unit){ + return Date.getMsg('dateOrder').indexOf(unit) + 1; + }, + + defineFormat: function(name, format){ + formats[name] = format; + return this; + }, + + + + defineParser: function(pattern){ + parsePatterns.push((pattern.re && pattern.handler) ? pattern : build(pattern)); + return this; + }, + + defineParsers: function(){ + Array.flatten(arguments).each(Date.defineParser); + return this; + }, + + define2DigitYearStart: function(year){ + startYear = year % 100; + startCentury = year - startYear; + return this; + } + +}).extend({ + defineFormats: Date.defineFormat.overloadSetter() +}); + +var regexOf = function(type){ + return new RegExp('(?:' + Date.getMsg(type).map(function(name){ + return name.substr(0, 3); + }).join('|') + ')[a-z]*'); +}; + +var replacers = function(key){ + switch (key){ + case 'T': + return '%H:%M:%S'; + case 'x': // iso8601 covers yyyy-mm-dd, so just check if month is first + return ((Date.orderIndex('month') == 1) ? '%m[-./]%d' : '%d[-./]%m') + '([-./]%y)?'; + case 'X': + return '%H([.:]%M)?([.:]%S([.:]%s)?)? ?%p? ?%z?'; + } + return null; +}; + +var keys = { + d: /[0-2]?[0-9]|3[01]/, + H: /[01]?[0-9]|2[0-3]/, + I: /0?[1-9]|1[0-2]/, + M: /[0-5]?\d/, + s: /\d+/, + o: /[a-z]*/, + p: /[ap]\.?m\.?/, + y: /\d{2}|\d{4}/, + Y: /\d{4}/, + z: /Z|[+-]\d{2}(?::?\d{2})?/ +}; + +keys.m = keys.I; +keys.S = keys.M; + +var currentLanguage; + +var recompile = function(language){ + currentLanguage = language; + + keys.a = keys.A = regexOf('days'); + keys.b = keys.B = regexOf('months'); + + parsePatterns.each(function(pattern, i){ + if (pattern.format) parsePatterns[i] = build(pattern.format); + }); +}; + +var build = function(format){ + if (!currentLanguage) return {format: format}; + + var parsed = []; + var re = (format.source || format) // allow format to be regex + .replace(/%([a-z])/gi, + function($0, $1){ + return replacers($1) || $0; + } + ).replace(/\((?!\?)/g, '(?:') // make all groups non-capturing + .replace(/ (?!\?|\*)/g, ',? ') // be forgiving with spaces and commas + .replace(/%([a-z%])/gi, + function($0, $1){ + var p = keys[$1]; + if (!p) return $1; + parsed.push($1); + return '(' + p.source + ')'; + } + ).replace(/\[a-z\]/gi, '[a-z\\u00c0-\\uffff;\&]'); // handle unicode words + + return { + format: format, + re: new RegExp('^' + re + '$', 'i'), + handler: function(bits){ + bits = bits.slice(1).associate(parsed); + var date = new Date().clearTime(), + year = bits.y || bits.Y; + + if (year != null) handle.call(date, 'y', year); // need to start in the right year + if ('d' in bits) handle.call(date, 'd', 1); + if ('m' in bits || bits.b || bits.B) handle.call(date, 'm', 1); + + for (var key in bits) handle.call(date, key, bits[key]); + return date; + } + }; +}; + +var handle = function(key, value){ + if (!value) return this; + + switch (key){ + case 'a': case 'A': return this.set('day', Date.parseDay(value, true)); + case 'b': case 'B': return this.set('mo', Date.parseMonth(value, true)); + case 'd': return this.set('date', value); + case 'H': case 'I': return this.set('hr', value); + case 'm': return this.set('mo', value - 1); + case 'M': return this.set('min', value); + case 'p': return this.set('ampm', value.replace(/\./g, '')); + case 'S': return this.set('sec', value); + case 's': return this.set('ms', ('0.' + value) * 1000); + case 'w': return this.set('day', value); + case 'Y': return this.set('year', value); + case 'y': + value = +value; + if (value < 100) value += startCentury + (value < startYear ? 100 : 0); + return this.set('year', value); + case 'z': + if (value == 'Z') value = '+00'; + var offset = value.match(/([+-])(\d{2}):?(\d{2})?/); + offset = (offset[1] + '1') * (offset[2] * 60 + (+offset[3] || 0)) + this.getTimezoneOffset(); + return this.set('time', this - offset * 60000); + } + + return this; +}; + +Date.defineParsers( + '%Y([-./]%m([-./]%d((T| )%X)?)?)?', // "1999-12-31", "1999-12-31 11:59pm", "1999-12-31 23:59:59", ISO8601 + '%Y%m%d(T%H(%M%S?)?)?', // "19991231", "19991231T1159", compact + '%x( %X)?', // "12/31", "12.31.99", "12-31-1999", "12/31/2008 11:59 PM" + '%d%o( %b( %Y)?)?( %X)?', // "31st", "31st December", "31 Dec 1999", "31 Dec 1999 11:59pm" + '%b( %d%o)?( %Y)?( %X)?', // Same as above with month and day switched + '%Y %b( %d%o( %X)?)?', // Same as above with year coming first + '%o %b %d %X %z %Y', // "Thu Oct 22 08:11:23 +0000 2009" + '%T', // %H:%M:%S + '%H:%M( ?%p)?' // "11:05pm", "11:05 am" and "11:05" +); + +Locale.addEvent('change', function(language){ + if (Locale.get('Date')) recompile(language); +}).fireEvent('change', Locale.getCurrent()); + +})(); + +/* +--- + +name: Locale.en-US.Form.Validator + +description: Form Validator messages for English. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Locale + +provides: [Locale.en-US.Form.Validator] + +... +*/ + +Locale.define('en-US', 'FormValidator', { + + required: 'This field is required.', + length: 'Please enter {length} characters (you entered {elLength} characters)', + minLength: 'Please enter at least {minLength} characters (you entered {length} characters).', + maxLength: 'Please enter no more than {maxLength} characters (you entered {length} characters).', + integer: 'Please enter an integer in this field. Numbers with decimals (e.g. 1.25) are not permitted.', + numeric: 'Please enter only numeric values in this field (i.e. "1" or "1.1" or "-1" or "-1.1").', + digits: 'Please use numbers and punctuation only in this field (for example, a phone number with dashes or dots is permitted).', + alpha: 'Please use only letters (a-z) within this field. No spaces or other characters are allowed.', + alphanum: 'Please use only letters (a-z) or numbers (0-9) in this field. No spaces or other characters are allowed.', + dateSuchAs: 'Please enter a valid date such as {date}', + dateInFormatMDY: 'Please enter a valid date such as MM/DD/YYYY (i.e. "12/31/1999")', + email: 'Please enter a valid email address. For example "fred@domain.com".', + url: 'Please enter a valid URL such as http://www.example.com.', + currencyDollar: 'Please enter a valid $ amount. For example $100.00 .', + oneRequired: 'Please enter something for at least one of these inputs.', + errorPrefix: 'Error: ', + warningPrefix: 'Warning: ', + + // Form.Validator.Extras + noSpace: 'There can be no spaces in this input.', + reqChkByNode: 'No items are selected.', + requiredChk: 'This field is required.', + reqChkByName: 'Please select a {label}.', + match: 'This field needs to match the {matchName} field', + startDate: 'the start date', + endDate: 'the end date', + currentDate: 'the current date', + afterDate: 'The date should be the same or after {label}.', + beforeDate: 'The date should be the same or before {label}.', + startMonth: 'Please select a start month', + sameMonth: 'These two dates must be in the same month - you must change one or the other.', + creditcard: 'The credit card number entered is invalid. Please check the number and try again. {length} digits entered.' + +}); + +/* +--- + +script: Form.Validator.js + +name: Form.Validator + +description: A css-class based form validation system. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Options + - Core/Events + - Core/Element.Delegation + - Core/Slick.Finder + - Core/Element.Event + - Core/Element.Style + - Core/JSON + - Locale + - Class.Binds + - Date + - Element.Forms + - Locale.en-US.Form.Validator + - Element.Shortcuts + +provides: [Form.Validator, InputValidator, FormValidator.BaseValidators] + +... +*/ +if (!window.Form) window.Form = {}; + +var InputValidator = this.InputValidator = new Class({ + + Implements: [Options], + + options: { + errorMsg: 'Validation failed.', + test: Function.from(true) + }, + + initialize: function(className, options){ + this.setOptions(options); + this.className = className; + }, + + test: function(field, props){ + field = document.id(field); + return (field) ? this.options.test(field, props || this.getProps(field)) : false; + }, + + getError: function(field, props){ + field = document.id(field); + var err = this.options.errorMsg; + if (typeOf(err) == 'function') err = err(field, props || this.getProps(field)); + return err; + }, + + getProps: function(field){ + field = document.id(field); + return (field) ? field.get('validatorProps') : {}; + } + +}); + +Element.Properties.validators = { + + get: function(){ + return (this.get('data-validators') || this.className).clean().split(' '); + } + +}; + +Element.Properties.validatorProps = { + + set: function(props){ + return this.eliminate('$moo:validatorProps').store('$moo:validatorProps', props); + }, + + get: function(props){ + if (props) this.set(props); + if (this.retrieve('$moo:validatorProps')) return this.retrieve('$moo:validatorProps'); + if (this.getProperty('data-validator-properties') || this.getProperty('validatorProps')){ + try { + this.store('$moo:validatorProps', JSON.decode(this.getProperty('validatorProps') || this.getProperty('data-validator-properties'), false)); + }catch(e){ + return {}; + } + } else { + var vals = this.get('validators').filter(function(cls){ + return cls.test(':'); + }); + if (!vals.length){ + this.store('$moo:validatorProps', {}); + } else { + props = {}; + vals.each(function(cls){ + var split = cls.split(':'); + if (split[1]){ + try { + props[split[0]] = JSON.decode(split[1], false); + } catch(e){} + } + }); + this.store('$moo:validatorProps', props); + } + } + return this.retrieve('$moo:validatorProps'); + } + +}; + +Form.Validator = new Class({ + + Implements: [Options, Events], + + options: {/* + onFormValidate: function(isValid, form, event){}, + onElementValidate: function(isValid, field, className, warn){}, + onElementPass: function(field){}, + onElementFail: function(field, validatorsFailed){}, */ + fieldSelectors: 'input, select, textarea', + ignoreHidden: true, + ignoreDisabled: true, + useTitles: false, + evaluateOnSubmit: true, + evaluateFieldsOnBlur: true, + evaluateFieldsOnChange: true, + serial: true, + stopOnFailure: true, + warningPrefix: function(){ + return Form.Validator.getMsg('warningPrefix') || 'Warning: '; + }, + errorPrefix: function(){ + return Form.Validator.getMsg('errorPrefix') || 'Error: '; + } + }, + + initialize: function(form, options){ + this.setOptions(options); + this.element = document.id(form); + this.warningPrefix = Function.from(this.options.warningPrefix)(); + this.errorPrefix = Function.from(this.options.errorPrefix)(); + this._bound = { + onSubmit: this.onSubmit.bind(this), + blurOrChange: function(event, field){ + this.validationMonitor(field, true); + }.bind(this) + }; + this.enable(); + }, + + toElement: function(){ + return this.element; + }, + + getFields: function(){ + return (this.fields = this.element.getElements(this.options.fieldSelectors)); + }, + + enable: function(){ + this.element.store('validator', this); + if (this.options.evaluateOnSubmit) this.element.addEvent('submit', this._bound.onSubmit); + if (this.options.evaluateFieldsOnBlur){ + this.element.addEvent('blur:relay(input,select,textarea)', this._bound.blurOrChange); + } + if (this.options.evaluateFieldsOnChange){ + this.element.addEvent('change:relay(input,select,textarea)', this._bound.blurOrChange); + } + }, + + disable: function(){ + this.element.eliminate('validator'); + this.element.removeEvents({ + submit: this._bound.onSubmit, + 'blur:relay(input,select,textarea)': this._bound.blurOrChange, + 'change:relay(input,select,textarea)': this._bound.blurOrChange + }); + }, + + validationMonitor: function(){ + clearTimeout(this.timer); + this.timer = this.validateField.delay(50, this, arguments); + }, + + onSubmit: function(event){ + if (this.validate(event)) this.reset(); + }, + + reset: function(){ + this.getFields().each(this.resetField, this); + return this; + }, + + validate: function(event){ + var result = this.getFields().map(function(field){ + return this.validateField(field, true); + }, this).every(function(v){ + return v; + }); + this.fireEvent('formValidate', [result, this.element, event]); + if (this.options.stopOnFailure && !result && event) event.preventDefault(); + return result; + }, + + validateField: function(field, force){ + if (this.paused) return true; + field = document.id(field); + var passed = !field.hasClass('validation-failed'); + var failed, warned; + if (this.options.serial && !force){ + failed = this.element.getElement('.validation-failed'); + warned = this.element.getElement('.warning'); + } + if (field && (!failed || force || field.hasClass('validation-failed') || (failed && !this.options.serial))){ + var validationTypes = field.get('validators'); + var validators = validationTypes.some(function(cn){ + return this.getValidator(cn); + }, this); + var validatorsFailed = []; + validationTypes.each(function(className){ + if (className && !this.test(className, field)) validatorsFailed.include(className); + }, this); + passed = validatorsFailed.length === 0; + if (validators && !this.hasValidator(field, 'warnOnly')){ + if (passed){ + field.addClass('validation-passed').removeClass('validation-failed'); + this.fireEvent('elementPass', [field]); + } else { + field.addClass('validation-failed').removeClass('validation-passed'); + this.fireEvent('elementFail', [field, validatorsFailed]); + } + } + if (!warned){ + var warnings = validationTypes.some(function(cn){ + if (cn.test('^warn')) + return this.getValidator(cn.replace(/^warn-/,'')); + else return null; + }, this); + field.removeClass('warning'); + var warnResult = validationTypes.map(function(cn){ + if (cn.test('^warn')) + return this.test(cn.replace(/^warn-/,''), field, true); + else return null; + }, this); + } + } + return passed; + }, + + test: function(className, field, warn){ + field = document.id(field); + if ((this.options.ignoreHidden && !field.isVisible()) || (this.options.ignoreDisabled && field.get('disabled'))) return true; + var validator = this.getValidator(className); + if (warn != null) warn = false; + if (this.hasValidator(field, 'warnOnly')) warn = true; + var isValid = field.hasClass('ignoreValidation') || (validator ? validator.test(field) : true); + if (validator) this.fireEvent('elementValidate', [isValid, field, className, warn]); + if (warn) return true; + return isValid; + }, + + hasValidator: function(field, value){ + return field.get('validators').contains(value); + }, + + resetField: function(field){ + field = document.id(field); + if (field){ + field.get('validators').each(function(className){ + if (className.test('^warn-')) className = className.replace(/^warn-/, ''); + field.removeClass('validation-failed'); + field.removeClass('warning'); + field.removeClass('validation-passed'); + }, this); + } + return this; + }, + + stop: function(){ + this.paused = true; + return this; + }, + + start: function(){ + this.paused = false; + return this; + }, + + ignoreField: function(field, warn){ + field = document.id(field); + if (field){ + this.enforceField(field); + if (warn) field.addClass('warnOnly'); + else field.addClass('ignoreValidation'); + } + return this; + }, + + enforceField: function(field){ + field = document.id(field); + if (field) field.removeClass('warnOnly').removeClass('ignoreValidation'); + return this; + } + +}); + +Form.Validator.getMsg = function(key){ + return Locale.get('FormValidator.' + key); +}; + +Form.Validator.adders = { + + validators:{}, + + add : function(className, options){ + this.validators[className] = new InputValidator(className, options); + //if this is a class (this method is used by instances of Form.Validator and the Form.Validator namespace) + //extend these validators into it + //this allows validators to be global and/or per instance + if (!this.initialize){ + this.implement({ + validators: this.validators + }); + } + }, + + addAllThese : function(validators){ + Array.from(validators).each(function(validator){ + this.add(validator[0], validator[1]); + }, this); + }, + + getValidator: function(className){ + return this.validators[className.split(':')[0]]; + } + +}; + +Object.append(Form.Validator, Form.Validator.adders); + +Form.Validator.implement(Form.Validator.adders); + +Form.Validator.add('IsEmpty', { + + errorMsg: false, + test: function(element){ + if (element.type == 'select-one' || element.type == 'select') + return !(element.selectedIndex >= 0 && element.options[element.selectedIndex].value != ''); + else + return ((element.get('value') == null) || (element.get('value').length == 0)); + } + +}); + +Form.Validator.addAllThese([ + + ['required', { + errorMsg: function(){ + return Form.Validator.getMsg('required'); + }, + test: function(element){ + return !Form.Validator.getValidator('IsEmpty').test(element); + } + }], + + ['length', { + errorMsg: function(element, props){ + if (typeOf(props.length) != 'null') + return Form.Validator.getMsg('length').substitute({length: props.length, elLength: element.get('value').length}); + else return ''; + }, + test: function(element, props){ + if (typeOf(props.length) != 'null') return (element.get('value').length == props.length || element.get('value').length == 0); + else return true; + } + }], + + ['minLength', { + errorMsg: function(element, props){ + if (typeOf(props.minLength) != 'null') + return Form.Validator.getMsg('minLength').substitute({minLength: props.minLength, length: element.get('value').length}); + else return ''; + }, + test: function(element, props){ + if (typeOf(props.minLength) != 'null') return (element.get('value').length >= (props.minLength || 0)); + else return true; + } + }], + + ['maxLength', { + errorMsg: function(element, props){ + //props is {maxLength:10} + if (typeOf(props.maxLength) != 'null') + return Form.Validator.getMsg('maxLength').substitute({maxLength: props.maxLength, length: element.get('value').length}); + else return ''; + }, + test: function(element, props){ + return element.get('value').length <= (props.maxLength || 10000); + } + }], + + ['validate-integer', { + errorMsg: Form.Validator.getMsg.pass('integer'), + test: function(element){ + return Form.Validator.getValidator('IsEmpty').test(element) || (/^(-?[1-9]\d*|0)$/).test(element.get('value')); + } + }], + + ['validate-numeric', { + errorMsg: Form.Validator.getMsg.pass('numeric'), + test: function(element){ + return Form.Validator.getValidator('IsEmpty').test(element) || + (/^-?(?:0$0(?=\d*\.)|[1-9]|0)\d*(\.\d+)?$/).test(element.get('value')); + } + }], + + ['validate-digits', { + errorMsg: Form.Validator.getMsg.pass('digits'), + test: function(element){ + return Form.Validator.getValidator('IsEmpty').test(element) || (/^[\d() .:\-\+#]+$/.test(element.get('value'))); + } + }], + + ['validate-alpha', { + errorMsg: Form.Validator.getMsg.pass('alpha'), + test: function(element){ + return Form.Validator.getValidator('IsEmpty').test(element) || (/^[a-zA-Z]+$/).test(element.get('value')); + } + }], + + ['validate-alphanum', { + errorMsg: Form.Validator.getMsg.pass('alphanum'), + test: function(element){ + return Form.Validator.getValidator('IsEmpty').test(element) || !(/\W/).test(element.get('value')); + } + }], + + ['validate-date', { + errorMsg: function(element, props){ + if (Date.parse){ + var format = props.dateFormat || '%x'; + return Form.Validator.getMsg('dateSuchAs').substitute({date: new Date().format(format)}); + } else { + return Form.Validator.getMsg('dateInFormatMDY'); + } + }, + test: function(element, props){ + if (Form.Validator.getValidator('IsEmpty').test(element)) return true; + var dateLocale = Locale.get('Date'), + dateNouns = new RegExp([dateLocale.days, dateLocale.days_abbr, dateLocale.months, dateLocale.months_abbr, dateLocale.AM, dateLocale.PM].flatten().join('|'), 'i'), + value = element.get('value'), + wordsInValue = value.match(/[a-z]+/gi); + + if (wordsInValue && !wordsInValue.every(dateNouns.exec, dateNouns)) return false; + + var date = Date.parse(value); + if (!date) return false; + + var format = props.dateFormat || '%x', + formatted = date.format(format); + if (formatted != 'invalid date') element.set('value', formatted); + return date.isValid(); + } + }], + + ['validate-email', { + errorMsg: Form.Validator.getMsg.pass('email'), + test: function(element){ + /* + var chars = "[a-z0-9!#$%&'*+/=?^_`{|}~-]", + local = '(?:' + chars + '\\.?){0,63}' + chars, + + label = '[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?', + hostname = '(?:' + label + '\\.)*' + label; + + octet = '(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)', + ipv4 = '\\[(?:' + octet + '\\.){3}' + octet + '\\]', + + domain = '(?:' + hostname + '|' + ipv4 + ')'; + + var regex = new RegExp('^' + local + '@' + domain + '$', 'i'); + */ + return Form.Validator.getValidator('IsEmpty').test(element) || (/^(?:[a-z0-9!#$%&'*+\/=?^_`{|}~-]\.?){0,63}[a-z0-9!#$%&'*+\/=?^_`{|}~-]@(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\])$/i).test(element.get('value')); + } + }], + + ['validate-url', { + errorMsg: Form.Validator.getMsg.pass('url'), + test: function(element){ + return Form.Validator.getValidator('IsEmpty').test(element) || (/^(https?|ftp|rmtp|mms):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\/?/i).test(element.get('value')); + } + }], + + ['validate-currency-dollar', { + errorMsg: Form.Validator.getMsg.pass('currencyDollar'), + test: function(element){ + return Form.Validator.getValidator('IsEmpty').test(element) || (/^\$?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(element.get('value')); + } + }], + + ['validate-one-required', { + errorMsg: Form.Validator.getMsg.pass('oneRequired'), + test: function(element, props){ + var p = document.id(props['validate-one-required']) || element.getParent(props['validate-one-required']); + return p.getElements('input').some(function(el){ + if (['checkbox', 'radio'].contains(el.get('type'))) return el.get('checked'); + return el.get('value'); + }); + } + }] + +]); + +Element.Properties.validator = { + + set: function(options){ + this.get('validator').setOptions(options); + }, + + get: function(){ + var validator = this.retrieve('validator'); + if (!validator){ + validator = new Form.Validator(this); + this.store('validator', validator); + } + return validator; + } + +}; + +Element.implement({ + + validate: function(options){ + if (options) this.set('validator', options); + return this.get('validator').validate(); + } + +}); + + + + + + +/* +--- + +script: Form.Validator.Extras.js + +name: Form.Validator.Extras + +description: Additional validators for the Form.Validator class. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Form.Validator + +provides: [Form.Validator.Extras] + +... +*/ +Form.Validator.addAllThese([ + + ['validate-enforce-oncheck', { + test: function(element, props){ + var fv = element.getParent('form').retrieve('validator'); + if (!fv) return true; + (props.toEnforce || document.id(props.enforceChildrenOf).getElements('input, select, textarea')).map(function(item){ + if (element.checked){ + fv.enforceField(item); + } else { + fv.ignoreField(item); + fv.resetField(item); + } + }); + return true; + } + }], + + ['validate-ignore-oncheck', { + test: function(element, props){ + var fv = element.getParent('form').retrieve('validator'); + if (!fv) return true; + (props.toIgnore || document.id(props.ignoreChildrenOf).getElements('input, select, textarea')).each(function(item){ + if (element.checked){ + fv.ignoreField(item); + fv.resetField(item); + } else { + fv.enforceField(item); + } + }); + return true; + } + }], + + ['validate-nospace', { + errorMsg: function(){ + return Form.Validator.getMsg('noSpace'); + }, + test: function(element, props){ + return !element.get('value').test(/\s/); + } + }], + + ['validate-toggle-oncheck', { + test: function(element, props){ + var fv = element.getParent('form').retrieve('validator'); + if (!fv) return true; + var eleArr = props.toToggle || document.id(props.toToggleChildrenOf).getElements('input, select, textarea'); + if (!element.checked){ + eleArr.each(function(item){ + fv.ignoreField(item); + fv.resetField(item); + }); + } else { + eleArr.each(function(item){ + fv.enforceField(item); + }); + } + return true; + } + }], + + ['validate-reqchk-bynode', { + errorMsg: function(){ + return Form.Validator.getMsg('reqChkByNode'); + }, + test: function(element, props){ + return (document.id(props.nodeId).getElements(props.selector || 'input[type=checkbox], input[type=radio]')).some(function(item){ + return item.checked; + }); + } + }], + + ['validate-required-check', { + errorMsg: function(element, props){ + return props.useTitle ? element.get('title') : Form.Validator.getMsg('requiredChk'); + }, + test: function(element, props){ + return !!element.checked; + } + }], + + ['validate-reqchk-byname', { + errorMsg: function(element, props){ + return Form.Validator.getMsg('reqChkByName').substitute({label: props.label || element.get('type')}); + }, + test: function(element, props){ + var grpName = props.groupName || element.get('name'); + var oneCheckedItem = $$(document.getElementsByName(grpName)).some(function(item, index){ + return item.checked; + }); + var fv = element.getParent('form').retrieve('validator'); + if (oneCheckedItem && fv) fv.resetField(element); + return oneCheckedItem; + } + }], + + ['validate-match', { + errorMsg: function(element, props){ + return Form.Validator.getMsg('match').substitute({matchName: props.matchName || document.id(props.matchInput).get('name')}); + }, + test: function(element, props){ + var eleVal = element.get('value'); + var matchVal = document.id(props.matchInput) && document.id(props.matchInput).get('value'); + return eleVal && matchVal ? eleVal == matchVal : true; + } + }], + + ['validate-after-date', { + errorMsg: function(element, props){ + return Form.Validator.getMsg('afterDate').substitute({ + label: props.afterLabel || (props.afterElement ? Form.Validator.getMsg('startDate') : Form.Validator.getMsg('currentDate')) + }); + }, + test: function(element, props){ + var start = document.id(props.afterElement) ? Date.parse(document.id(props.afterElement).get('value')) : new Date(); + var end = Date.parse(element.get('value')); + return end && start ? end >= start : true; + } + }], + + ['validate-before-date', { + errorMsg: function(element, props){ + return Form.Validator.getMsg('beforeDate').substitute({ + label: props.beforeLabel || (props.beforeElement ? Form.Validator.getMsg('endDate') : Form.Validator.getMsg('currentDate')) + }); + }, + test: function(element, props){ + var start = Date.parse(element.get('value')); + var end = document.id(props.beforeElement) ? Date.parse(document.id(props.beforeElement).get('value')) : new Date(); + return end && start ? end >= start : true; + } + }], + + ['validate-custom-required', { + errorMsg: function(){ + return Form.Validator.getMsg('required'); + }, + test: function(element, props){ + return element.get('value') != props.emptyValue; + } + }], + + ['validate-same-month', { + errorMsg: function(element, props){ + var startMo = document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value'); + var eleVal = element.get('value'); + if (eleVal != '') return Form.Validator.getMsg(startMo ? 'sameMonth' : 'startMonth'); + }, + test: function(element, props){ + var d1 = Date.parse(element.get('value')); + var d2 = Date.parse(document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value')); + return d1 && d2 ? d1.format('%B') == d2.format('%B') : true; + } + }], + + + ['validate-cc-num', { + errorMsg: function(element){ + var ccNum = element.get('value').replace(/[^0-9]/g, ''); + return Form.Validator.getMsg('creditcard').substitute({length: ccNum.length}); + }, + test: function(element){ + // required is a different test + if (Form.Validator.getValidator('IsEmpty').test(element)) return true; + + // Clean number value + var ccNum = element.get('value'); + ccNum = ccNum.replace(/[^0-9]/g, ''); + + var valid_type = false; + + if (ccNum.test(/^4[0-9]{12}([0-9]{3})?$/)) valid_type = 'Visa'; + else if (ccNum.test(/^5[1-5]([0-9]{14})$/)) valid_type = 'Master Card'; + else if (ccNum.test(/^3[47][0-9]{13}$/)) valid_type = 'American Express'; + else if (ccNum.test(/^6(?:011|5[0-9]{2})[0-9]{12}$/)) valid_type = 'Discover'; + else if (ccNum.test(/^3(?:0[0-5]|[68][0-9])[0-9]{11}$/)) valid_type = 'Diners Club'; + + if (valid_type){ + var sum = 0; + var cur = 0; + + for (var i=ccNum.length-1; i>=0; --i){ + cur = ccNum.charAt(i).toInt(); + if (cur == 0) continue; + + if ((ccNum.length-i) % 2 == 0) cur += cur; + if (cur > 9){ + cur = cur.toString().charAt(0).toInt() + cur.toString().charAt(1).toInt(); + } + + sum += cur; + } + if ((sum % 10) == 0) return true; + } + + var chunks = ''; + while (ccNum != ''){ + chunks += ' ' + ccNum.substr(0,4); + ccNum = ccNum.substr(4); + } + + element.getParent('form').retrieve('validator').ignoreField(element); + element.set('value', chunks.clean()); + element.getParent('form').retrieve('validator').enforceField(element); + return false; + } + }] + + +]); + +/* +--- + +script: Form.Validator.Inline.js + +name: Form.Validator.Inline + +description: Extends Form.Validator to add inline messages. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Form.Validator + +provides: [Form.Validator.Inline] + +... +*/ + +Form.Validator.Inline = new Class({ + + Extends: Form.Validator, + + options: { + showError: function(errorElement){ + if (errorElement.reveal) errorElement.reveal(); + else errorElement.setStyle('display', 'block'); + }, + hideError: function(errorElement){ + if (errorElement.dissolve) errorElement.dissolve(); + else errorElement.setStyle('display', 'none'); + }, + scrollToErrorsOnSubmit: true, + scrollToErrorsOnBlur: false, + scrollToErrorsOnChange: false, + scrollFxOptions: { + transition: 'quad:out', + offset: { + y: -20 + } + } + }, + + initialize: function(form, options){ + this.parent(form, options); + this.addEvent('onElementValidate', function(isValid, field, className, warn){ + var validator = this.getValidator(className); + if (!isValid && validator.getError(field)){ + if (warn) field.addClass('warning'); + var advice = this.makeAdvice(className, field, validator.getError(field), warn); + this.insertAdvice(advice, field); + this.showAdvice(className, field); + } else { + this.hideAdvice(className, field); + } + }); + }, + + makeAdvice: function(className, field, error, warn){ + var errorMsg = (warn) ? this.warningPrefix : this.errorPrefix; + errorMsg += (this.options.useTitles) ? field.title || error:error; + var cssClass = (warn) ? 'warning-advice' : 'validation-advice'; + var advice = this.getAdvice(className, field); + if (advice){ + advice = advice.set('html', errorMsg); + } else { + advice = new Element('div', { + html: errorMsg, + styles: { display: 'none' }, + id: 'advice-' + className.split(':')[0] + '-' + this.getFieldId(field) + }).addClass(cssClass); + } + field.store('$moo:advice-' + className, advice); + return advice; + }, + + getFieldId : function(field){ + return field.id ? field.id : field.id = 'input_' + field.name; + }, + + showAdvice: function(className, field){ + var advice = this.getAdvice(className, field); + if ( + advice && + !field.retrieve('$moo:' + this.getPropName(className)) && + ( + advice.getStyle('display') == 'none' || + advice.getStyle('visibility') == 'hidden' || + advice.getStyle('opacity') == 0 + ) + ){ + field.store('$moo:' + this.getPropName(className), true); + this.options.showError(advice); + this.fireEvent('showAdvice', [field, advice, className]); + } + }, + + hideAdvice: function(className, field){ + var advice = this.getAdvice(className, field); + if (advice && field.retrieve('$moo:' + this.getPropName(className))){ + field.store('$moo:' + this.getPropName(className), false); + this.options.hideError(advice); + this.fireEvent('hideAdvice', [field, advice, className]); + } + }, + + getPropName: function(className){ + return 'advice' + className; + }, + + resetField: function(field){ + field = document.id(field); + if (!field) return this; + this.parent(field); + field.get('validators').each(function(className){ + this.hideAdvice(className, field); + }, this); + return this; + }, + + getAllAdviceMessages: function(field, force){ + var advice = []; + if (field.hasClass('ignoreValidation') && !force) return advice; + var validators = field.get('validators').some(function(cn){ + var warner = cn.test('^warn-') || field.hasClass('warnOnly'); + if (warner) cn = cn.replace(/^warn-/, ''); + var validator = this.getValidator(cn); + if (!validator) return; + advice.push({ + message: validator.getError(field), + warnOnly: warner, + passed: validator.test(), + validator: validator + }); + }, this); + return advice; + }, + + getAdvice: function(className, field){ + return field.retrieve('$moo:advice-' + className); + }, + + insertAdvice: function(advice, field){ + //Check for error position prop + var props = field.get('validatorProps'); + //Build advice + if (!props.msgPos || !document.id(props.msgPos)){ + if (field.type && field.type.toLowerCase() == 'radio') field.getParent().adopt(advice); + else advice.inject(document.id(field), 'after'); + } else { + document.id(props.msgPos).grab(advice); + } + }, + + validateField: function(field, force, scroll){ + var result = this.parent(field, force); + if (((this.options.scrollToErrorsOnSubmit && scroll == null) || scroll) && !result){ + var failed = document.id(this).getElement('.validation-failed'); + var par = document.id(this).getParent(); + while (par != document.body && par.getScrollSize().y == par.getSize().y){ + par = par.getParent(); + } + var fx = par.retrieve('$moo:fvScroller'); + if (!fx && window.Fx && Fx.Scroll){ + fx = new Fx.Scroll(par, this.options.scrollFxOptions); + par.store('$moo:fvScroller', fx); + } + if (failed){ + if (fx) fx.toElement(failed); + else par.scrollTo(par.getScroll().x, failed.getPosition(par).y - 20); + } + } + return result; + }, + + watchFields: function(fields){ + fields.each(function(el){ + if (this.options.evaluateFieldsOnBlur){ + el.addEvent('blur', this.validationMonitor.pass([el, false, this.options.scrollToErrorsOnBlur], this)); + } + if (this.options.evaluateFieldsOnChange){ + el.addEvent('change', this.validationMonitor.pass([el, true, this.options.scrollToErrorsOnChange], this)); + } + }, this); + } + +}); + +/* +--- + +script: OverText.js + +name: OverText + +description: Shows text over an input that disappears when the user clicks into it. The text remains hidden if the user adds a value. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Options + - Core/Events + - Core/Element.Event + - Class.Binds + - Class.Occlude + - Element.Position + - Element.Shortcuts + +provides: [OverText] + +... +*/ + +var OverText = new Class({ + + Implements: [Options, Events, Class.Occlude], + + Binds: ['reposition', 'assert', 'focus', 'hide'], + + options: {/* + textOverride: null, + onFocus: function(){}, + onTextHide: function(textEl, inputEl){}, + onTextShow: function(textEl, inputEl){}, */ + element: 'label', + labelClass: 'overTxtLabel', + positionOptions: { + position: 'upperLeft', + edge: 'upperLeft', + offset: { + x: 4, + y: 2 + } + }, + poll: false, + pollInterval: 250, + wrap: false + }, + + property: 'OverText', + + initialize: function(element, options){ + element = this.element = document.id(element); + + if (this.occlude()) return this.occluded; + this.setOptions(options); + + this.attach(element); + OverText.instances.push(this); + + if (this.options.poll) this.poll(); + }, + + toElement: function(){ + return this.element; + }, + + attach: function(){ + var element = this.element, + options = this.options, + value = options.textOverride || element.get('alt') || element.get('title'); + + if (!value) return this; + + var text = this.text = new Element(options.element, { + 'class': options.labelClass, + styles: { + lineHeight: 'normal', + position: 'absolute', + cursor: 'text' + }, + html: value, + events: { + click: this.hide.pass(options.element == 'label', this) + } + }).inject(element, 'after'); + + if (options.element == 'label'){ + if (!element.get('id')) element.set('id', 'input_' + String.uniqueID()); + text.set('for', element.get('id')); + } + + if (options.wrap){ + this.textHolder = new Element('div.overTxtWrapper', { + styles: { + lineHeight: 'normal', + position: 'relative' + } + }).grab(text).inject(element, 'before'); + } + + return this.enable(); + }, + + destroy: function(){ + this.element.eliminate(this.property); // Class.Occlude storage + this.disable(); + if (this.text) this.text.destroy(); + if (this.textHolder) this.textHolder.destroy(); + return this; + }, + + disable: function(){ + this.element.removeEvents({ + focus: this.focus, + blur: this.assert, + change: this.assert + }); + window.removeEvent('resize', this.reposition); + this.hide(true, true); + return this; + }, + + enable: function(){ + this.element.addEvents({ + focus: this.focus, + blur: this.assert, + change: this.assert + }); + window.addEvent('resize', this.reposition); + this.reposition(); + return this; + }, + + wrap: function(){ + if (this.options.element == 'label'){ + if (!this.element.get('id')) this.element.set('id', 'input_' + String.uniqueID()); + this.text.set('for', this.element.get('id')); + } + }, + + startPolling: function(){ + this.pollingPaused = false; + return this.poll(); + }, + + poll: function(stop){ + //start immediately + //pause on focus + //resumeon blur + if (this.poller && !stop) return this; + if (stop){ + clearInterval(this.poller); + } else { + this.poller = (function(){ + if (!this.pollingPaused) this.assert(true); + }).periodical(this.options.pollInterval, this); + } + + return this; + }, + + stopPolling: function(){ + this.pollingPaused = true; + return this.poll(true); + }, + + focus: function(){ + if (this.text && (!this.text.isDisplayed() || this.element.get('disabled'))) return this; + return this.hide(); + }, + + hide: function(suppressFocus, force){ + if (this.text && (this.text.isDisplayed() && (!this.element.get('disabled') || force))){ + this.text.hide(); + this.fireEvent('textHide', [this.text, this.element]); + this.pollingPaused = true; + if (!suppressFocus){ + try { + this.element.fireEvent('focus'); + this.element.focus(); + } catch(e){} //IE barfs if you call focus on hidden elements + } + } + return this; + }, + + show: function(){ + if (document.id(this.text) && !this.text.isDisplayed()){ + this.text.show(); + this.reposition(); + this.fireEvent('textShow', [this.text, this.element]); + this.pollingPaused = false; + } + return this; + }, + + test: function(){ + return !this.element.get('value'); + }, + + assert: function(suppressFocus){ + return this[this.test() ? 'show' : 'hide'](suppressFocus); + }, + + reposition: function(){ + this.assert(true); + if (!this.element.isVisible()) return this.stopPolling().hide(); + if (this.text && this.test()){ + this.text.position(Object.merge(this.options.positionOptions, { + relativeTo: this.element + })); + } + return this; + } + +}); + +OverText.instances = []; + +Object.append(OverText, { + + each: function(fn){ + return OverText.instances.each(function(ot, i){ + if (ot.element && ot.text) fn.call(OverText, ot, i); + }); + }, + + update: function(){ + + return OverText.each(function(ot){ + return ot.reposition(); + }); + + }, + + hideAll: function(){ + + return OverText.each(function(ot){ + return ot.hide(true, true); + }); + + }, + + showAll: function(){ + return OverText.each(function(ot){ + return ot.show(); + }); + } + +}); + + +/* +--- + +script: Fx.Elements.js + +name: Fx.Elements + +description: Effect to change any number of CSS properties of any number of Elements. + +license: MIT-style license + +authors: + - Valerio Proietti + +requires: + - Core/Fx.CSS + - MooTools.More + +provides: [Fx.Elements] + +... +*/ + +Fx.Elements = new Class({ + + Extends: Fx.CSS, + + initialize: function(elements, options){ + this.elements = this.subject = $$(elements); + this.parent(options); + }, + + compute: function(from, to, delta){ + var now = {}; + + for (var i in from){ + var iFrom = from[i], iTo = to[i], iNow = now[i] = {}; + for (var p in iFrom) iNow[p] = this.parent(iFrom[p], iTo[p], delta); + } + + return now; + }, + + set: function(now){ + for (var i in now){ + if (!this.elements[i]) continue; + + var iNow = now[i]; + for (var p in iNow) this.render(this.elements[i], p, iNow[p], this.options.unit); + } + + return this; + }, + + start: function(obj){ + if (!this.check(obj)) return this; + var from = {}, to = {}; + + for (var i in obj){ + if (!this.elements[i]) continue; + + var iProps = obj[i], iFrom = from[i] = {}, iTo = to[i] = {}; + + for (var p in iProps){ + var parsed = this.prepare(this.elements[i], p, iProps[p]); + iFrom[p] = parsed.from; + iTo[p] = parsed.to; + } + } + + return this.parent(from, to); + } + +}); + +/* +--- + +script: Fx.Accordion.js + +name: Fx.Accordion + +description: An Fx.Elements extension which allows you to easily create accordion type controls. + +license: MIT-style license + +authors: + - Valerio Proietti + +requires: + - Core/Element.Event + - Fx.Elements + +provides: [Fx.Accordion] + +... +*/ + +Fx.Accordion = new Class({ + + Extends: Fx.Elements, + + options: {/* + onActive: function(toggler, section){}, + onBackground: function(toggler, section){},*/ + fixedHeight: false, + fixedWidth: false, + display: 0, + show: false, + height: true, + width: false, + opacity: true, + alwaysHide: false, + trigger: 'click', + initialDisplayFx: true, + resetHeight: true + }, + + initialize: function(){ + var defined = function(obj){ + return obj != null; + }; + + var params = Array.link(arguments, { + 'container': Type.isElement, //deprecated + 'options': Type.isObject, + 'togglers': defined, + 'elements': defined + }); + this.parent(params.elements, params.options); + + var options = this.options, + togglers = this.togglers = $$(params.togglers); + + this.previous = -1; + this.internalChain = new Chain(); + + if (options.alwaysHide) this.options.link = 'chain'; + + if (options.show || this.options.show === 0){ + options.display = false; + this.previous = options.show; + } + + if (options.start){ + options.display = false; + options.show = false; + } + + var effects = this.effects = {}; + + if (options.opacity) effects.opacity = 'fullOpacity'; + if (options.width) effects.width = options.fixedWidth ? 'fullWidth' : 'offsetWidth'; + if (options.height) effects.height = options.fixedHeight ? 'fullHeight' : 'scrollHeight'; + + for (var i = 0, l = togglers.length; i < l; i++) this.addSection(togglers[i], this.elements[i]); + + this.elements.each(function(el, i){ + if (options.show === i){ + this.fireEvent('active', [togglers[i], el]); + } else { + for (var fx in effects) el.setStyle(fx, 0); + } + }, this); + + if (options.display || options.display === 0 || options.initialDisplayFx === false){ + this.display(options.display, options.initialDisplayFx); + } + + if (options.fixedHeight !== false) options.resetHeight = false; + this.addEvent('complete', this.internalChain.callChain.bind(this.internalChain)); + }, + + addSection: function(toggler, element){ + toggler = document.id(toggler); + element = document.id(element); + this.togglers.include(toggler); + this.elements.include(element); + + var togglers = this.togglers, + options = this.options, + test = togglers.contains(toggler), + idx = togglers.indexOf(toggler), + displayer = this.display.pass(idx, this); + + toggler.store('accordion:display', displayer) + .addEvent(options.trigger, displayer); + + if (options.height) element.setStyles({'padding-top': 0, 'border-top': 'none', 'padding-bottom': 0, 'border-bottom': 'none'}); + if (options.width) element.setStyles({'padding-left': 0, 'border-left': 'none', 'padding-right': 0, 'border-right': 'none'}); + + element.fullOpacity = 1; + if (options.fixedWidth) element.fullWidth = options.fixedWidth; + if (options.fixedHeight) element.fullHeight = options.fixedHeight; + element.setStyle('overflow', 'hidden'); + + if (!test) for (var fx in this.effects){ + element.setStyle(fx, 0); + } + return this; + }, + + removeSection: function(toggler, displayIndex){ + var togglers = this.togglers, + idx = togglers.indexOf(toggler), + element = this.elements[idx]; + + var remover = function(){ + togglers.erase(toggler); + this.elements.erase(element); + this.detach(toggler); + }.bind(this); + + if (this.now == idx || displayIndex != null){ + this.display(displayIndex != null ? displayIndex : (idx - 1 >= 0 ? idx - 1 : 0)).chain(remover); + } else { + remover(); + } + return this; + }, + + detach: function(toggler){ + var remove = function(toggler){ + toggler.removeEvent(this.options.trigger, toggler.retrieve('accordion:display')); + }.bind(this); + + if (!toggler) this.togglers.each(remove); + else remove(toggler); + return this; + }, + + display: function(index, useFx){ + if (!this.check(index, useFx)) return this; + + var obj = {}, + elements = this.elements, + options = this.options, + effects = this.effects; + + if (useFx == null) useFx = true; + if (typeOf(index) == 'element') index = elements.indexOf(index); + if (index == this.current && !options.alwaysHide) return this; + + if (options.resetHeight){ + var prev = elements[this.current]; + if (prev && !this.selfHidden){ + for (var fx in effects) prev.setStyle(fx, prev[effects[fx]]); + } + } + + if ((this.timer && options.link == 'chain') || (index === this.current && !options.alwaysHide)) return this; + + if (this.current != null) this.previous = this.current; + this.current = index; + this.selfHidden = false; + + elements.each(function(el, i){ + obj[i] = {}; + var hide; + if (i != index){ + hide = true; + } else if (options.alwaysHide && ((el.offsetHeight > 0 && options.height) || el.offsetWidth > 0 && options.width)){ + hide = true; + this.selfHidden = true; + } + this.fireEvent(hide ? 'background' : 'active', [this.togglers[i], el]); + for (var fx in effects) obj[i][fx] = hide ? 0 : el[effects[fx]]; + if (!useFx && !hide && options.resetHeight) obj[i].height = 'auto'; + }, this); + + this.internalChain.clearChain(); + this.internalChain.chain(function(){ + if (options.resetHeight && !this.selfHidden){ + var el = elements[index]; + if (el) el.setStyle('height', 'auto'); + } + }.bind(this)); + + return useFx ? this.start(obj) : this.set(obj).internalChain.callChain(); + } + +}); + + + +/* +--- + +script: Fx.Move.js + +name: Fx.Move + +description: Defines Fx.Move, a class that works with Element.Position.js to transition an element from one location to another. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Fx.Morph + - Element.Position + +provides: [Fx.Move] + +... +*/ + +Fx.Move = new Class({ + + Extends: Fx.Morph, + + options: { + relativeTo: document.body, + position: 'center', + edge: false, + offset: {x: 0, y: 0} + }, + + start: function(destination){ + var element = this.element, + topLeft = element.getStyles('top', 'left'); + if (topLeft.top == 'auto' || topLeft.left == 'auto'){ + element.setPosition(element.getPosition(element.getOffsetParent())); + } + return this.parent(element.position(Object.merge({}, this.options, destination, {returnPos: true}))); + } + +}); + +Element.Properties.move = { + + set: function(options){ + this.get('move').cancel().setOptions(options); + return this; + }, + + get: function(){ + var move = this.retrieve('move'); + if (!move){ + move = new Fx.Move(this, {link: 'cancel'}); + this.store('move', move); + } + return move; + } + +}; + +Element.implement({ + + move: function(options){ + this.get('move').start(options); + return this; + } + +}); + +/* +--- + +script: Fx.Scroll.js + +name: Fx.Scroll + +description: Effect to smoothly scroll any element, including the window. + +license: MIT-style license + +authors: + - Valerio Proietti + +requires: + - Core/Fx + - Core/Element.Event + - Core/Element.Dimensions + - MooTools.More + +provides: [Fx.Scroll] + +... +*/ + +(function(){ + +Fx.Scroll = new Class({ + + Extends: Fx, + + options: { + offset: {x: 0, y: 0}, + wheelStops: true + }, + + initialize: function(element, options){ + this.element = this.subject = document.id(element); + this.parent(options); + + if (typeOf(this.element) != 'element') this.element = document.id(this.element.getDocument().body); + + if (this.options.wheelStops){ + var stopper = this.element, + cancel = this.cancel.pass(false, this); + this.addEvent('start', function(){ + stopper.addEvent('mousewheel', cancel); + }, true); + this.addEvent('complete', function(){ + stopper.removeEvent('mousewheel', cancel); + }, true); + } + }, + + set: function(){ + var now = Array.flatten(arguments); + this.element.scrollTo(now[0], now[1]); + return this; + }, + + compute: function(from, to, delta){ + return [0, 1].map(function(i){ + return Fx.compute(from[i], to[i], delta); + }); + }, + + start: function(x, y){ + if (!this.check(x, y)) return this; + var scroll = this.element.getScroll(); + return this.parent([scroll.x, scroll.y], [x, y]); + }, + + calculateScroll: function(x, y){ + var element = this.element, + scrollSize = element.getScrollSize(), + scroll = element.getScroll(), + size = element.getSize(), + offset = this.options.offset, + values = {x: x, y: y}; + + for (var z in values){ + if (!values[z] && values[z] !== 0) values[z] = scroll[z]; + if (typeOf(values[z]) != 'number') values[z] = scrollSize[z] - size[z]; + values[z] += offset[z]; + } + + return [values.x, values.y]; + }, + + toTop: function(){ + return this.start.apply(this, this.calculateScroll(false, 0)); + }, + + toLeft: function(){ + return this.start.apply(this, this.calculateScroll(0, false)); + }, + + toRight: function(){ + return this.start.apply(this, this.calculateScroll('right', false)); + }, + + toBottom: function(){ + return this.start.apply(this, this.calculateScroll(false, 'bottom')); + }, + + toElement: function(el, axes){ + axes = axes ? Array.from(axes) : ['x', 'y']; + var scroll = isBody(this.element) ? {x: 0, y: 0} : this.element.getScroll(); + var position = Object.map(document.id(el).getPosition(this.element), function(value, axis){ + return axes.contains(axis) ? value + scroll[axis] : false; + }); + return this.start.apply(this, this.calculateScroll(position.x, position.y)); + }, + + toElementEdge: function(el, axes, offset){ + axes = axes ? Array.from(axes) : ['x', 'y']; + el = document.id(el); + var to = {}, + position = el.getPosition(this.element), + size = el.getSize(), + scroll = this.element.getScroll(), + containerSize = this.element.getSize(), + edge = { + x: position.x + size.x, + y: position.y + size.y + }; + + ['x', 'y'].each(function(axis){ + if (axes.contains(axis)){ + if (edge[axis] > scroll[axis] + containerSize[axis]) to[axis] = edge[axis] - containerSize[axis]; + if (position[axis] < scroll[axis]) to[axis] = position[axis]; + } + if (to[axis] == null) to[axis] = scroll[axis]; + if (offset && offset[axis]) to[axis] = to[axis] + offset[axis]; + }, this); + + if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y); + return this; + }, + + toElementCenter: function(el, axes, offset){ + axes = axes ? Array.from(axes) : ['x', 'y']; + el = document.id(el); + var to = {}, + position = el.getPosition(this.element), + size = el.getSize(), + scroll = this.element.getScroll(), + containerSize = this.element.getSize(); + + ['x', 'y'].each(function(axis){ + if (axes.contains(axis)){ + to[axis] = position[axis] - (containerSize[axis] - size[axis]) / 2; + } + if (to[axis] == null) to[axis] = scroll[axis]; + if (offset && offset[axis]) to[axis] = to[axis] + offset[axis]; + }, this); + + if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y); + return this; + } + +}); + + + +function isBody(element){ + return (/^(?:body|html)$/i).test(element.tagName); +} + +})(); + +/* +--- + +script: Fx.Slide.js + +name: Fx.Slide + +description: Effect to slide an element in and out of view. + +license: MIT-style license + +authors: + - Valerio Proietti + +requires: + - Core/Fx + - Core/Element.Style + - MooTools.More + +provides: [Fx.Slide] + +... +*/ + +Fx.Slide = new Class({ + + Extends: Fx, + + options: { + mode: 'vertical', + wrapper: false, + hideOverflow: true, + resetHeight: false + }, + + initialize: function(element, options){ + element = this.element = this.subject = document.id(element); + this.parent(options); + options = this.options; + + var wrapper = element.retrieve('wrapper'), + styles = element.getStyles('margin', 'position', 'overflow'); + + if (options.hideOverflow) styles = Object.append(styles, {overflow: 'hidden'}); + if (options.wrapper) wrapper = document.id(options.wrapper).setStyles(styles); + + if (!wrapper) wrapper = new Element('div', { + styles: styles + }).wraps(element); + + element.store('wrapper', wrapper).setStyle('margin', 0); + if (element.getStyle('overflow') == 'visible') element.setStyle('overflow', 'hidden'); + + this.now = []; + this.open = true; + this.wrapper = wrapper; + + this.addEvent('complete', function(){ + this.open = (wrapper['offset' + this.layout.capitalize()] != 0); + if (this.open && this.options.resetHeight) wrapper.setStyle('height', ''); + }, true); + }, + + vertical: function(){ + this.margin = 'margin-top'; + this.layout = 'height'; + this.offset = this.element.offsetHeight; + }, + + horizontal: function(){ + this.margin = 'margin-left'; + this.layout = 'width'; + this.offset = this.element.offsetWidth; + }, + + set: function(now){ + this.element.setStyle(this.margin, now[0]); + this.wrapper.setStyle(this.layout, now[1]); + return this; + }, + + compute: function(from, to, delta){ + return [0, 1].map(function(i){ + return Fx.compute(from[i], to[i], delta); + }); + }, + + start: function(how, mode){ + if (!this.check(how, mode)) return this; + this[mode || this.options.mode](); + + var margin = this.element.getStyle(this.margin).toInt(), + layout = this.wrapper.getStyle(this.layout).toInt(), + caseIn = [[margin, layout], [0, this.offset]], + caseOut = [[margin, layout], [-this.offset, 0]], + start; + + switch (how){ + case 'in': start = caseIn; break; + case 'out': start = caseOut; break; + case 'toggle': start = (layout == 0) ? caseIn : caseOut; + } + return this.parent(start[0], start[1]); + }, + + slideIn: function(mode){ + return this.start('in', mode); + }, + + slideOut: function(mode){ + return this.start('out', mode); + }, + + hide: function(mode){ + this[mode || this.options.mode](); + this.open = false; + return this.set([-this.offset, 0]); + }, + + show: function(mode){ + this[mode || this.options.mode](); + this.open = true; + return this.set([0, this.offset]); + }, + + toggle: function(mode){ + return this.start('toggle', mode); + } + +}); + +Element.Properties.slide = { + + set: function(options){ + this.get('slide').cancel().setOptions(options); + return this; + }, + + get: function(){ + var slide = this.retrieve('slide'); + if (!slide){ + slide = new Fx.Slide(this, {link: 'cancel'}); + this.store('slide', slide); + } + return slide; + } + +}; + +Element.implement({ + + slide: function(how, mode){ + how = how || 'toggle'; + var slide = this.get('slide'), toggle; + switch (how){ + case 'hide': slide.hide(mode); break; + case 'show': slide.show(mode); break; + case 'toggle': + var flag = this.retrieve('slide:flag', slide.open); + slide[flag ? 'slideOut' : 'slideIn'](mode); + this.store('slide:flag', !flag); + toggle = true; + break; + default: slide.start(how, mode); + } + if (!toggle) this.eliminate('slide:flag'); + return this; + } + +}); + +/* +--- + +script: Fx.SmoothScroll.js + +name: Fx.SmoothScroll + +description: Class for creating a smooth scrolling effect to all internal links on the page. + +license: MIT-style license + +authors: + - Valerio Proietti + +requires: + - Core/Slick.Finder + - Fx.Scroll + +provides: [Fx.SmoothScroll] + +... +*/ + +Fx.SmoothScroll = new Class({ + + Extends: Fx.Scroll, + + options: { + axes: ['x', 'y'] + }, + + initialize: function(options, context){ + context = context || document; + this.doc = context.getDocument(); + this.parent(this.doc, options); + + var win = context.getWindow(), + location = win.location.href.match(/^[^#]*/)[0] + '#', + links = $$(this.options.links || this.doc.links); + + links.each(function(link){ + if (link.href.indexOf(location) != 0) return; + var anchor = link.href.substr(location.length); + if (anchor) this.useLink(link, anchor); + }, this); + + this.addEvent('complete', function(){ + win.location.hash = this.anchor; + this.element.scrollTo(this.to[0], this.to[1]); + }, true); + }, + + useLink: function(link, anchor){ + + link.addEvent('click', function(event){ + var el = document.id(anchor) || this.doc.getElement('a[name=' + anchor + ']'); + if (!el) return; + + event.preventDefault(); + this.toElement(el, this.options.axes).chain(function(){ + this.fireEvent('scrolledTo', [link, el]); + }.bind(this)); + + this.anchor = anchor; + + }.bind(this)); + + return this; + } +}); + +/* +--- + +script: Fx.Sort.js + +name: Fx.Sort + +description: Defines Fx.Sort, a class that reorders lists with a transition. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Element.Dimensions + - Fx.Elements + - Element.Measure + +provides: [Fx.Sort] + +... +*/ + +Fx.Sort = new Class({ + + Extends: Fx.Elements, + + options: { + mode: 'vertical' + }, + + initialize: function(elements, options){ + this.parent(elements, options); + this.elements.each(function(el){ + if (el.getStyle('position') == 'static') el.setStyle('position', 'relative'); + }); + this.setDefaultOrder(); + }, + + setDefaultOrder: function(){ + this.currentOrder = this.elements.map(function(el, index){ + return index; + }); + }, + + sort: function(){ + if (!this.check(arguments)) return this; + var newOrder = Array.flatten(arguments); + + var top = 0, + left = 0, + next = {}, + zero = {}, + vert = this.options.mode == 'vertical'; + + var current = this.elements.map(function(el, index){ + var size = el.getComputedSize({styles: ['border', 'padding', 'margin']}); + var val; + if (vert){ + val = { + top: top, + margin: size['margin-top'], + height: size.totalHeight + }; + top += val.height - size['margin-top']; + } else { + val = { + left: left, + margin: size['margin-left'], + width: size.totalWidth + }; + left += val.width; + } + var plane = vert ? 'top' : 'left'; + zero[index] = {}; + var start = el.getStyle(plane).toInt(); + zero[index][plane] = start || 0; + return val; + }, this); + + this.set(zero); + newOrder = newOrder.map(function(i){ return i.toInt(); }); + if (newOrder.length != this.elements.length){ + this.currentOrder.each(function(index){ + if (!newOrder.contains(index)) newOrder.push(index); + }); + if (newOrder.length > this.elements.length) + newOrder.splice(this.elements.length-1, newOrder.length - this.elements.length); + } + var margin = 0; + top = left = 0; + newOrder.each(function(item){ + var newPos = {}; + if (vert){ + newPos.top = top - current[item].top - margin; + top += current[item].height; + } else { + newPos.left = left - current[item].left; + left += current[item].width; + } + margin = margin + current[item].margin; + next[item]=newPos; + }, this); + var mapped = {}; + Array.clone(newOrder).sort().each(function(index){ + mapped[index] = next[index]; + }); + this.start(mapped); + this.currentOrder = newOrder; + + return this; + }, + + rearrangeDOM: function(newOrder){ + newOrder = newOrder || this.currentOrder; + var parent = this.elements[0].getParent(); + var rearranged = []; + this.elements.setStyle('opacity', 0); + //move each element and store the new default order + newOrder.each(function(index){ + rearranged.push(this.elements[index].inject(parent).setStyles({ + top: 0, + left: 0 + })); + }, this); + this.elements.setStyle('opacity', 1); + this.elements = $$(rearranged); + this.setDefaultOrder(); + return this; + }, + + getDefaultOrder: function(){ + return this.elements.map(function(el, index){ + return index; + }); + }, + + getCurrentOrder: function(){ + return this.currentOrder; + }, + + forward: function(){ + return this.sort(this.getDefaultOrder()); + }, + + backward: function(){ + return this.sort(this.getDefaultOrder().reverse()); + }, + + reverse: function(){ + return this.sort(this.currentOrder.reverse()); + }, + + sortByElements: function(elements){ + return this.sort(elements.map(function(el){ + return this.elements.indexOf(el); + }, this)); + }, + + swap: function(one, two){ + if (typeOf(one) == 'element') one = this.elements.indexOf(one); + if (typeOf(two) == 'element') two = this.elements.indexOf(two); + + var newOrder = Array.clone(this.currentOrder); + newOrder[this.currentOrder.indexOf(one)] = two; + newOrder[this.currentOrder.indexOf(two)] = one; + + return this.sort(newOrder); + } + +}); + +/* +--- + +script: Keyboard.js + +name: Keyboard + +description: KeyboardEvents used to intercept events on a class for keyboard and format modifiers in a specific order so as to make alt+shift+c the same as shift+alt+c. + +license: MIT-style license + +authors: + - Perrin Westrich + - Aaron Newton + - Scott Kyle + +requires: + - Core/Events + - Core/Options + - Core/Element.Event + - Element.Event.Pseudos.Keys + +provides: [Keyboard] + +... +*/ + +(function(){ + + var Keyboard = this.Keyboard = new Class({ + + Extends: Events, + + Implements: [Options], + + options: {/* + onActivate: function(){}, + onDeactivate: function(){},*/ + defaultEventType: 'keydown', + active: false, + manager: null, + events: {}, + nonParsedEvents: ['activate', 'deactivate', 'onactivate', 'ondeactivate', 'changed', 'onchanged'] + }, + + initialize: function(options){ + if (options && options.manager){ + this._manager = options.manager; + delete options.manager; + } + this.setOptions(options); + this._setup(); + }, + + addEvent: function(type, fn, internal){ + return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn, internal); + }, + + removeEvent: function(type, fn){ + return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn); + }, + + toggleActive: function(){ + return this[this.isActive() ? 'deactivate' : 'activate'](); + }, + + activate: function(instance){ + if (instance){ + if (instance.isActive()) return this; + //if we're stealing focus, store the last keyboard to have it so the relinquish command works + if (this._activeKB && instance != this._activeKB){ + this.previous = this._activeKB; + this.previous.fireEvent('deactivate'); + } + //if we're enabling a child, assign it so that events are now passed to it + this._activeKB = instance.fireEvent('activate'); + Keyboard.manager.fireEvent('changed'); + } else if (this._manager){ + //else we're enabling ourselves, we must ask our parent to do it for us + this._manager.activate(this); + } + return this; + }, + + isActive: function(){ + return this._manager ? (this._manager._activeKB == this) : (Keyboard.manager == this); + }, + + deactivate: function(instance){ + if (instance){ + if (instance === this._activeKB){ + this._activeKB = null; + instance.fireEvent('deactivate'); + Keyboard.manager.fireEvent('changed'); + } + } else if (this._manager){ + this._manager.deactivate(this); + } + return this; + }, + + relinquish: function(){ + if (this.isActive() && this._manager && this._manager.previous) this._manager.activate(this._manager.previous); + else this.deactivate(); + return this; + }, + + //management logic + manage: function(instance){ + if (instance._manager) instance._manager.drop(instance); + this._instances.push(instance); + instance._manager = this; + if (!this._activeKB) this.activate(instance); + return this; + }, + + drop: function(instance){ + instance.relinquish(); + this._instances.erase(instance); + if (this._activeKB == instance){ + if (this.previous && this._instances.contains(this.previous)) this.activate(this.previous); + else this._activeKB = this._instances[0]; + } + return this; + }, + + trace: function(){ + Keyboard.trace(this); + }, + + each: function(fn){ + Keyboard.each(this, fn); + }, + + /* + PRIVATE METHODS + */ + + _instances: [], + + _disable: function(instance){ + if (this._activeKB == instance) this._activeKB = null; + }, + + _setup: function(){ + this.addEvents(this.options.events); + //if this is the root manager, nothing manages it + if (Keyboard.manager && !this._manager) Keyboard.manager.manage(this); + if (this.options.active) this.activate(); + else this.relinquish(); + }, + + _handle: function(event, type){ + //Keyboard.stop(event) prevents key propagation + if (event.preventKeyboardPropagation) return; + + var bubbles = !!this._manager; + if (bubbles && this._activeKB){ + this._activeKB._handle(event, type); + if (event.preventKeyboardPropagation) return; + } + this.fireEvent(type, event); + + if (!bubbles && this._activeKB) this._activeKB._handle(event, type); + } + + }); + + var parsed = {}; + var modifiers = ['shift', 'control', 'alt', 'meta']; + var regex = /^(?:shift|control|ctrl|alt|meta)$/; + + Keyboard.parse = function(type, eventType, ignore){ + if (ignore && ignore.contains(type.toLowerCase())) return type; + + type = type.toLowerCase().replace(/^(keyup|keydown):/, function($0, $1){ + eventType = $1; + return ''; + }); + + if (!parsed[type]){ + if (type != '+'){ + var key, mods = {}; + type.split('+').each(function(part){ + if (regex.test(part)) mods[part] = true; + else key = part; + }); + + mods.control = mods.control || mods.ctrl; // allow both control and ctrl + + var keys = []; + modifiers.each(function(mod){ + if (mods[mod]) keys.push(mod); + }); + + if (key) keys.push(key); + parsed[type] = keys.join('+'); + } else { + parsed[type] = type; + } + } + + return eventType + ':keys(' + parsed[type] + ')'; + }; + + Keyboard.each = function(keyboard, fn){ + var current = keyboard || Keyboard.manager; + while (current){ + fn(current); + current = current._activeKB; + } + }; + + Keyboard.stop = function(event){ + event.preventKeyboardPropagation = true; + }; + + Keyboard.manager = new Keyboard({ + active: true + }); + + Keyboard.trace = function(keyboard){ + keyboard = keyboard || Keyboard.manager; + var hasConsole = window.console && console.log; + if (hasConsole) console.log('the following items have focus: '); + Keyboard.each(keyboard, function(current){ + if (hasConsole) console.log(document.id(current.widget) || current.wiget || current); + }); + }; + + var handler = function(event){ + var keys = []; + modifiers.each(function(mod){ + if (event[mod]) keys.push(mod); + }); + + if (!regex.test(event.key)) keys.push(event.key); + Keyboard.manager._handle(event, event.type + ':keys(' + keys.join('+') + ')'); + }; + + document.addEvents({ + 'keyup': handler, + 'keydown': handler + }); + +})(); + +/* +--- + +script: Keyboard.Extras.js + +name: Keyboard.Extras + +description: Enhances Keyboard by adding the ability to name and describe keyboard shortcuts, and the ability to grab shortcuts by name and bind the shortcut to different keys. + +license: MIT-style license + +authors: + - Perrin Westrich + +requires: + - Keyboard + - MooTools.More + +provides: [Keyboard.Extras] + +... +*/ +Keyboard.prototype.options.nonParsedEvents.combine(['rebound', 'onrebound']); + +Keyboard.implement({ + + /* + shortcut should be in the format of: + { + 'keys': 'shift+s', // the default to add as an event. + 'description': 'blah blah blah', // a brief description of the functionality. + 'handler': function(){} // the event handler to run when keys are pressed. + } + */ + addShortcut: function(name, shortcut){ + this._shortcuts = this._shortcuts || []; + this._shortcutIndex = this._shortcutIndex || {}; + + shortcut.getKeyboard = Function.from(this); + shortcut.name = name; + this._shortcutIndex[name] = shortcut; + this._shortcuts.push(shortcut); + if (shortcut.keys) this.addEvent(shortcut.keys, shortcut.handler); + return this; + }, + + addShortcuts: function(obj){ + for (var name in obj) this.addShortcut(name, obj[name]); + return this; + }, + + removeShortcut: function(name){ + var shortcut = this.getShortcut(name); + if (shortcut && shortcut.keys){ + this.removeEvent(shortcut.keys, shortcut.handler); + delete this._shortcutIndex[name]; + this._shortcuts.erase(shortcut); + } + return this; + }, + + removeShortcuts: function(names){ + names.each(this.removeShortcut, this); + return this; + }, + + getShortcuts: function(){ + return this._shortcuts || []; + }, + + getShortcut: function(name){ + return (this._shortcutIndex || {})[name]; + } + +}); + +Keyboard.rebind = function(newKeys, shortcuts){ + Array.from(shortcuts).each(function(shortcut){ + shortcut.getKeyboard().removeEvent(shortcut.keys, shortcut.handler); + shortcut.getKeyboard().addEvent(newKeys, shortcut.handler); + shortcut.keys = newKeys; + shortcut.getKeyboard().fireEvent('rebound'); + }); +}; + + +Keyboard.getActiveShortcuts = function(keyboard){ + var activeKBS = [], activeSCS = []; + Keyboard.each(keyboard, [].push.bind(activeKBS)); + activeKBS.each(function(kb){ activeSCS.extend(kb.getShortcuts()); }); + return activeSCS; +}; + +Keyboard.getShortcut = function(name, keyboard, opts){ + opts = opts || {}; + var shortcuts = opts.many ? [] : null, + set = opts.many ? function(kb){ + var shortcut = kb.getShortcut(name); + if (shortcut) shortcuts.push(shortcut); + } : function(kb){ + if (!shortcuts) shortcuts = kb.getShortcut(name); + }; + Keyboard.each(keyboard, set); + return shortcuts; +}; + +Keyboard.getShortcuts = function(name, keyboard){ + return Keyboard.getShortcut(name, keyboard, { many: true }); +}; + +/* +--- + +script: HtmlTable.js + +name: HtmlTable + +description: Builds table elements with methods to add rows. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Options + - Core/Events + - Class.Occlude + +provides: [HtmlTable] + +... +*/ + +var HtmlTable = new Class({ + + Implements: [Options, Events, Class.Occlude], + + options: { + properties: { + cellpadding: 0, + cellspacing: 0, + border: 0 + }, + rows: [], + headers: [], + footers: [] + }, + + property: 'HtmlTable', + + initialize: function(){ + var params = Array.link(arguments, {options: Type.isObject, table: Type.isElement, id: Type.isString}); + this.setOptions(params.options); + if (!params.table && params.id) params.table = document.id(params.id); + this.element = params.table || new Element('table', this.options.properties); + if (this.occlude()) return this.occluded; + this.build(); + }, + + build: function(){ + this.element.store('HtmlTable', this); + + this.body = document.id(this.element.tBodies[0]) || new Element('tbody').inject(this.element); + $$(this.body.rows); + + if (this.options.headers.length) this.setHeaders(this.options.headers); + else this.thead = document.id(this.element.tHead); + + if (this.thead) this.head = this.getHead(); + if (this.options.footers.length) this.setFooters(this.options.footers); + + this.tfoot = document.id(this.element.tFoot); + if (this.tfoot) this.foot = document.id(this.tfoot.rows[0]); + + this.options.rows.each(function(row){ + this.push(row); + }, this); + }, + + toElement: function(){ + return this.element; + }, + + empty: function(){ + this.body.empty(); + return this; + }, + + set: function(what, items){ + var target = (what == 'headers') ? 'tHead' : 'tFoot', + lower = target.toLowerCase(); + + this[lower] = (document.id(this.element[target]) || new Element(lower).inject(this.element, 'top')).empty(); + var data = this.push(items, {}, this[lower], what == 'headers' ? 'th' : 'td'); + + if (what == 'headers') this.head = this.getHead(); + else this.foot = this.getHead(); + + return data; + }, + + getHead: function(){ + var rows = this.thead.rows; + return rows.length > 1 ? $$(rows) : rows.length ? document.id(rows[0]) : false; + }, + + setHeaders: function(headers){ + this.set('headers', headers); + return this; + }, + + setFooters: function(footers){ + this.set('footers', footers); + return this; + }, + + update: function(tr, row, tag){ + var tds = tr.getChildren(tag || 'td'), last = tds.length - 1; + + row.each(function(data, index){ + var td = tds[index] || new Element(tag || 'td').inject(tr), + content = ((data && Object.prototype.hasOwnProperty.call(data, 'content')) ? data.content : '') || data, + type = typeOf(content); + + if (data && Object.prototype.hasOwnProperty.call(data, 'properties')) td.set(data.properties); + if (/(element(s?)|array|collection)/.test(type)) td.empty().adopt(content); + else td.set('html', content); + + if (index > last) tds.push(td); + else tds[index] = td; + }); + + return { + tr: tr, + tds: tds + }; + }, + + push: function(row, rowProperties, target, tag, where){ + if (typeOf(row) == 'element' && row.get('tag') == 'tr'){ + row.inject(target || this.body, where); + return { + tr: row, + tds: row.getChildren('td') + }; + } + return this.update(new Element('tr', rowProperties).inject(target || this.body, where), row, tag); + }, + + pushMany: function(rows, rowProperties, target, tag, where){ + return rows.map(function(row){ + return this.push(row, rowProperties, target, tag, where); + }, this); + } + +}); + + +['adopt', 'inject', 'wraps', 'grab', 'replaces', 'dispose'].each(function(method){ + HtmlTable.implement(method, function(){ + this.element[method].apply(this.element, arguments); + return this; + }); +}); + + + +/* +--- + +script: HtmlTable.Select.js + +name: HtmlTable.Select + +description: Builds a stripy, sortable table with methods to add rows. Rows can be selected with the mouse or keyboard navigation. + +license: MIT-style license + +authors: + - Harald Kirschner + - Aaron Newton + +requires: + - Keyboard + - Keyboard.Extras + - HtmlTable + - Class.refactor + - Element.Delegation.Pseudo + - Element.Shortcuts + +provides: [HtmlTable.Select] + +... +*/ + +HtmlTable = Class.refactor(HtmlTable, { + + options: { + /*onRowFocus: function(){}, + onRowUnfocus: function(){},*/ + useKeyboard: true, + classRowSelected: 'table-tr-selected', + classRowHovered: 'table-tr-hovered', + classSelectable: 'table-selectable', + shiftForMultiSelect: true, + allowMultiSelect: true, + selectable: false, + selectHiddenRows: false + }, + + initialize: function(){ + this.previous.apply(this, arguments); + if (this.occluded) return this.occluded; + + this.selectedRows = new Elements(); + + if (!this.bound) this.bound = {}; + this.bound.mouseleave = this.mouseleave.bind(this); + this.bound.clickRow = this.clickRow.bind(this); + this.bound.activateKeyboard = function(){ + if (this.keyboard && this.selectEnabled) this.keyboard.activate(); + }.bind(this); + + if (this.options.selectable) this.enableSelect(); + }, + + empty: function(){ + if (this.body.rows.length) this.selectNone(); + return this.previous(); + }, + + enableSelect: function(){ + this.selectEnabled = true; + this.attachSelects(); + this.element.addClass(this.options.classSelectable); + return this; + }, + + disableSelect: function(){ + this.selectEnabled = false; + this.attachSelects(false); + this.element.removeClass(this.options.classSelectable); + return this; + }, + + push: function(){ + var ret = this.previous.apply(this, arguments); + this.updateSelects(); + return ret; + }, + + toggleRow: function(row){ + return this[(this.isSelected(row) ? 'de' : '') + 'selectRow'](row); + }, + + selectRow: function(row, _nocheck){ + //private variable _nocheck: boolean whether or not to confirm the row is in the table body + //added here for optimization when selecting ranges + if (this.isSelected(row) || (!_nocheck && !this.body.getChildren().contains(row))) return; + if (!this.options.allowMultiSelect) this.selectNone(); + + if (!this.isSelected(row)){ + this.selectedRows.push(row); + row.addClass(this.options.classRowSelected); + this.fireEvent('rowFocus', [row, this.selectedRows]); + this.fireEvent('stateChanged'); + } + + this.focused = row; + document.clearSelection(); + + return this; + }, + + isSelected: function(row){ + return this.selectedRows.contains(row); + }, + + getSelected: function(){ + return this.selectedRows; + }, + + serialize: function(){ + var previousSerialization = this.previous.apply(this, arguments) || {}; + if (this.options.selectable){ + previousSerialization.selectedRows = this.selectedRows.map(function(row){ + return Array.indexOf(this.body.rows, row); + }.bind(this)); + } + return previousSerialization; + }, + + restore: function(tableState){ + if(this.options.selectable && tableState.selectedRows){ + tableState.selectedRows.each(function(index){ + this.selectRow(this.body.rows[index]); + }.bind(this)); + } + this.previous.apply(this, arguments); + }, + + deselectRow: function(row, _nocheck){ + if (!this.isSelected(row) || (!_nocheck && !this.body.getChildren().contains(row))) return; + + this.selectedRows = new Elements(Array.from(this.selectedRows).erase(row)); + row.removeClass(this.options.classRowSelected); + this.fireEvent('rowUnfocus', [row, this.selectedRows]); + this.fireEvent('stateChanged'); + return this; + }, + + selectAll: function(selectNone){ + if (!selectNone && !this.options.allowMultiSelect) return; + this.selectRange(0, this.body.rows.length, selectNone); + return this; + }, + + selectNone: function(){ + return this.selectAll(true); + }, + + selectRange: function(startRow, endRow, _deselect){ + if (!this.options.allowMultiSelect && !_deselect) return; + var method = _deselect ? 'deselectRow' : 'selectRow', + rows = Array.clone(this.body.rows); + + if (typeOf(startRow) == 'element') startRow = rows.indexOf(startRow); + if (typeOf(endRow) == 'element') endRow = rows.indexOf(endRow); + endRow = endRow < rows.length - 1 ? endRow : rows.length - 1; + + if (endRow < startRow){ + var tmp = startRow; + startRow = endRow; + endRow = tmp; + } + + for (var i = startRow; i <= endRow; i++){ + if (this.options.selectHiddenRows || rows[i].isDisplayed()) this[method](rows[i], true); + } + + return this; + }, + + deselectRange: function(startRow, endRow){ + this.selectRange(startRow, endRow, true); + }, + +/* + Private methods: +*/ + + enterRow: function(row){ + if (this.hovered) this.hovered = this.leaveRow(this.hovered); + this.hovered = row.addClass(this.options.classRowHovered); + }, + + leaveRow: function(row){ + row.removeClass(this.options.classRowHovered); + }, + + updateSelects: function(){ + Array.each(this.body.rows, function(row){ + var binders = row.retrieve('binders'); + if (!binders && !this.selectEnabled) return; + if (!binders){ + binders = { + mouseenter: this.enterRow.pass([row], this), + mouseleave: this.leaveRow.pass([row], this) + }; + row.store('binders', binders); + } + if (this.selectEnabled) row.addEvents(binders); + else row.removeEvents(binders); + }, this); + }, + + shiftFocus: function(offset, event){ + if (!this.focused) return this.selectRow(this.body.rows[0], event); + var to = this.getRowByOffset(offset, this.options.selectHiddenRows); + if (to === null || this.focused == this.body.rows[to]) return this; + this.toggleRow(this.body.rows[to], event); + }, + + clickRow: function(event, row){ + var selecting = (event.shift || event.meta || event.control) && this.options.shiftForMultiSelect; + if (!selecting && !(event.rightClick && this.isSelected(row) && this.options.allowMultiSelect)) this.selectNone(); + + if (event.rightClick) this.selectRow(row); + else this.toggleRow(row); + + if (event.shift){ + this.selectRange(this.rangeStart || this.body.rows[0], row, this.rangeStart ? !this.isSelected(row) : true); + this.focused = row; + } + this.rangeStart = row; + }, + + getRowByOffset: function(offset, includeHiddenRows){ + if (!this.focused) return 0; + var index = Array.indexOf(this.body.rows, this.focused); + if ((index == 0 && offset < 0) || (index == this.body.rows.length -1 && offset > 0)) return null; + if (includeHiddenRows){ + index += offset; + } else { + var limit = 0, + count = 0; + if (offset > 0){ + while (count < offset && index < this.body.rows.length -1){ + if (this.body.rows[++index].isDisplayed()) count++; + } + } else { + while (count > offset && index > 0){ + if (this.body.rows[--index].isDisplayed()) count--; + } + } + } + return index; + }, + + attachSelects: function(attach){ + attach = attach != null ? attach : true; + + var method = attach ? 'addEvents' : 'removeEvents'; + this.element[method]({ + mouseleave: this.bound.mouseleave, + click: this.bound.activateKeyboard + }); + + this.body[method]({ + 'click:relay(tr)': this.bound.clickRow, + 'contextmenu:relay(tr)': this.bound.clickRow + }); + + if (this.options.useKeyboard || this.keyboard){ + if (!this.keyboard) this.keyboard = new Keyboard(); + if (!this.selectKeysDefined){ + this.selectKeysDefined = true; + var timer, held; + + var move = function(offset){ + var mover = function(e){ + clearTimeout(timer); + e.preventDefault(); + var to = this.body.rows[this.getRowByOffset(offset, this.options.selectHiddenRows)]; + if (e.shift && to && this.isSelected(to)){ + this.deselectRow(this.focused); + this.focused = to; + } else { + if (to && (!this.options.allowMultiSelect || !e.shift)){ + this.selectNone(); + } + this.shiftFocus(offset, e); + } + + if (held){ + timer = mover.delay(100, this, e); + } else { + timer = (function(){ + held = true; + mover(e); + }).delay(400); + } + }.bind(this); + return mover; + }.bind(this); + + var clear = function(){ + clearTimeout(timer); + held = false; + }; + + this.keyboard.addEvents({ + 'keydown:shift+up': move(-1), + 'keydown:shift+down': move(1), + 'keyup:shift+up': clear, + 'keyup:shift+down': clear, + 'keyup:up': clear, + 'keyup:down': clear + }); + + var shiftHint = ''; + if (this.options.allowMultiSelect && this.options.shiftForMultiSelect && this.options.useKeyboard){ + shiftHint = " (Shift multi-selects)."; + } + + this.keyboard.addShortcuts({ + 'Select Previous Row': { + keys: 'up', + shortcut: 'up arrow', + handler: move(-1), + description: 'Select the previous row in the table.' + shiftHint + }, + 'Select Next Row': { + keys: 'down', + shortcut: 'down arrow', + handler: move(1), + description: 'Select the next row in the table.' + shiftHint + } + }); + + } + this.keyboard[attach ? 'activate' : 'deactivate'](); + } + this.updateSelects(); + }, + + mouseleave: function(){ + if (this.hovered) this.leaveRow(this.hovered); + } + +}); + +/* +--- + +script: HtmlTable.Sort.js + +name: HtmlTable.Sort + +description: Builds a stripy, sortable table with methods to add rows. + +license: MIT-style license + +authors: + - Harald Kirschner + - Aaron Newton + - Jacob Thornton + +requires: + - Core/Hash + - HtmlTable + - Class.refactor + - Element.Delegation.Pseudo + - String.Extras + - Date + +provides: [HtmlTable.Sort] + +... +*/ +(function(){ + +var readOnlyNess = document.createElement('table'); +try { + readOnlyNess.innerHTML = '<tr><td></td></tr>'; + readOnlyNess = readOnlyNess.childNodes.length === 0; +} catch (e){ + readOnlyNess = true; +} + +HtmlTable = Class.refactor(HtmlTable, { + + options: {/* + onSort: function(){}, */ + sortIndex: 0, + sortReverse: false, + parsers: [], + defaultParser: 'string', + classSortable: 'table-sortable', + classHeadSort: 'table-th-sort', + classHeadSortRev: 'table-th-sort-rev', + classNoSort: 'table-th-nosort', + classGroupHead: 'table-tr-group-head', + classGroup: 'table-tr-group', + classCellSort: 'table-td-sort', + classSortSpan: 'table-th-sort-span', + sortable: false, + thSelector: 'th' + }, + + initialize: function (){ + this.previous.apply(this, arguments); + if (this.occluded) return this.occluded; + this.sorted = {index: null, dir: 1}; + if (!this.bound) this.bound = {}; + this.bound.headClick = this.headClick.bind(this); + this.sortSpans = new Elements(); + if (this.options.sortable){ + this.enableSort(); + if (this.options.sortIndex != null) this.sort(this.options.sortIndex, this.options.sortReverse); + } + }, + + attachSorts: function(attach){ + this.detachSorts(); + if (attach !== false) this.element.addEvent('click:relay(' + this.options.thSelector + ')', this.bound.headClick); + }, + + detachSorts: function(){ + this.element.removeEvents('click:relay(' + this.options.thSelector + ')'); + }, + + setHeaders: function(){ + this.previous.apply(this, arguments); + if (this.sortable) this.setParsers(); + }, + + setParsers: function(){ + this.parsers = this.detectParsers(); + }, + + detectParsers: function(){ + return this.head && this.head.getElements(this.options.thSelector).flatten().map(this.detectParser, this); + }, + + detectParser: function(cell, index){ + if (cell.hasClass(this.options.classNoSort) || cell.retrieve('htmltable-parser')) return cell.retrieve('htmltable-parser'); + var thDiv = new Element('div'); + thDiv.adopt(cell.childNodes).inject(cell); + var sortSpan = new Element('span', {'class': this.options.classSortSpan}).inject(thDiv, 'top'); + this.sortSpans.push(sortSpan); + var parser = this.options.parsers[index], + rows = this.body.rows, + cancel; + switch (typeOf(parser)){ + case 'function': parser = {convert: parser}; cancel = true; break; + case 'string': parser = parser; cancel = true; break; + } + if (!cancel){ + HtmlTable.ParserPriority.some(function(parserName){ + var current = HtmlTable.Parsers[parserName], + match = current.match; + if (!match) return false; + for (var i = 0, j = rows.length; i < j; i++){ + var cell = document.id(rows[i].cells[index]), + text = cell ? cell.get('html').clean() : ''; + if (text && match.test(text)){ + parser = current; + return true; + } + } + }); + } + if (!parser) parser = this.options.defaultParser; + cell.store('htmltable-parser', parser); + return parser; + }, + + headClick: function(event, el){ + if (!this.head || el.hasClass(this.options.classNoSort)) return; + return this.sort(Array.indexOf(this.head.getElements(this.options.thSelector).flatten(), el) % this.body.rows[0].cells.length); + }, + + serialize: function(){ + var previousSerialization = this.previous.apply(this, arguments) || {}; + if (this.options.sortable){ + previousSerialization.sortIndex = this.sorted.index; + previousSerialization.sortReverse = this.sorted.reverse; + } + return previousSerialization; + }, + + restore: function(tableState){ + if(this.options.sortable && tableState.sortIndex){ + this.sort(tableState.sortIndex, tableState.sortReverse); + } + this.previous.apply(this, arguments); + }, + + setSortedState: function(index, reverse){ + if (reverse != null) this.sorted.reverse = reverse; + else if (this.sorted.index == index) this.sorted.reverse = !this.sorted.reverse; + else this.sorted.reverse = this.sorted.index == null; + + if (index != null) this.sorted.index = index; + }, + + setHeadSort: function(sorted){ + var head = $$(!this.head.length ? this.head.cells[this.sorted.index] : this.head.map(function(row){ + return row.getElements(this.options.thSelector)[this.sorted.index]; + }, this).clean()); + if (!head.length) return; + if (sorted){ + head.addClass(this.options.classHeadSort); + if (this.sorted.reverse) head.addClass(this.options.classHeadSortRev); + else head.removeClass(this.options.classHeadSortRev); + } else { + head.removeClass(this.options.classHeadSort).removeClass(this.options.classHeadSortRev); + } + }, + + setRowSort: function(data, pre){ + var count = data.length, + body = this.body, + group, + rowIndex; + + while (count){ + var item = data[--count], + position = item.position, + row = body.rows[position]; + + if (row.disabled) continue; + if (!pre){ + group = this.setGroupSort(group, row, item); + this.setRowStyle(row, count); + } + body.appendChild(row); + + for (rowIndex = 0; rowIndex < count; rowIndex++){ + if (data[rowIndex].position > position) data[rowIndex].position--; + } + } + }, + + setRowStyle: function(row, i){ + this.previous(row, i); + row.cells[this.sorted.index].addClass(this.options.classCellSort); + }, + + setGroupSort: function(group, row, item){ + if (group == item.value) row.removeClass(this.options.classGroupHead).addClass(this.options.classGroup); + else row.removeClass(this.options.classGroup).addClass(this.options.classGroupHead); + return item.value; + }, + + getParser: function(){ + var parser = this.parsers[this.sorted.index]; + return typeOf(parser) == 'string' ? HtmlTable.Parsers[parser] : parser; + }, + + sort: function(index, reverse, pre, sortFunction){ + if (!this.head) return; + + if (!pre){ + this.clearSort(); + this.setSortedState(index, reverse); + this.setHeadSort(true); + } + + var parser = this.getParser(); + if (!parser) return; + + var rel; + if (!readOnlyNess){ + rel = this.body.getParent(); + this.body.dispose(); + } + + var data = this.parseData(parser).sort(sortFunction ? sortFunction : function(a, b){ + if (a.value === b.value) return 0; + return a.value > b.value ? 1 : -1; + }); + + if (this.sorted.reverse == (parser == HtmlTable.Parsers['input-checked'])) data.reverse(true); + this.setRowSort(data, pre); + + if (rel) rel.grab(this.body); + this.fireEvent('stateChanged'); + return this.fireEvent('sort', [this.body, this.sorted.index]); + }, + + parseData: function(parser){ + return Array.map(this.body.rows, function(row, i){ + var value = parser.convert.call(document.id(row.cells[this.sorted.index])); + return { + position: i, + value: value + }; + }, this); + }, + + clearSort: function(){ + this.setHeadSort(false); + this.body.getElements('td').removeClass(this.options.classCellSort); + }, + + reSort: function(){ + if (this.sortable) this.sort.call(this, this.sorted.index, this.sorted.reverse); + return this; + }, + + enableSort: function(){ + this.element.addClass(this.options.classSortable); + this.attachSorts(true); + this.setParsers(); + this.sortable = true; + return this; + }, + + disableSort: function(){ + this.element.removeClass(this.options.classSortable); + this.attachSorts(false); + this.sortSpans.each(function(span){ + span.destroy(); + }); + this.sortSpans.empty(); + this.sortable = false; + return this; + } + +}); + +HtmlTable.ParserPriority = ['date', 'input-checked', 'input-value', 'float', 'number']; + +HtmlTable.Parsers = { + + 'date': { + match: /^\d{2}[-\/ ]\d{2}[-\/ ]\d{2,4}$/, + convert: function(){ + var d = Date.parse(this.get('text').stripTags()); + return (typeOf(d) == 'date') ? d.format('db') : ''; + }, + type: 'date' + }, + 'input-checked': { + match: / type="(radio|checkbox)"/, + convert: function(){ + return this.getElement('input').checked; + } + }, + 'input-value': { + match: /<input/, + convert: function(){ + return this.getElement('input').value; + } + }, + 'number': { + match: /^\d+[^\d.,]*$/, + convert: function(){ + return this.get('text').stripTags().toInt(); + }, + number: true + }, + 'numberLax': { + match: /^[^\d]+\d+$/, + convert: function(){ + return this.get('text').replace(/[^-?^0-9]/, '').stripTags().toInt(); + }, + number: true + }, + 'float': { + match: /^[\d]+\.[\d]+/, + convert: function(){ + return this.get('text').replace(/[^-?^\d.e]/, '').stripTags().toFloat(); + }, + number: true + }, + 'floatLax': { + match: /^[^\d]+[\d]+\.[\d]+$/, + convert: function(){ + return this.get('text').replace(/[^-?^\d.]/, '').stripTags().toFloat(); + }, + number: true + }, + 'string': { + match: null, + convert: function(){ + return this.get('text').stripTags().toLowerCase(); + } + }, + 'title': { + match: null, + convert: function(){ + return this.title; + } + } + +}; + + + +HtmlTable.defineParsers = function(parsers){ + HtmlTable.Parsers = Object.append(HtmlTable.Parsers, parsers); + for (var parser in parsers){ + HtmlTable.ParserPriority.unshift(parser); + } +}; + +})(); + + +/* +--- + +script: HtmlTable.Zebra.js + +name: HtmlTable.Zebra + +description: Builds a stripy table with methods to add rows. + +license: MIT-style license + +authors: + - Harald Kirschner + - Aaron Newton + +requires: + - HtmlTable + - Element.Shortcuts + - Class.refactor + +provides: [HtmlTable.Zebra] + +... +*/ + +HtmlTable = Class.refactor(HtmlTable, { + + options: { + classZebra: 'table-tr-odd', + zebra: true, + zebraOnlyVisibleRows: true + }, + + initialize: function(){ + this.previous.apply(this, arguments); + if (this.occluded) return this.occluded; + if (this.options.zebra) this.updateZebras(); + }, + + updateZebras: function(){ + var index = 0; + Array.each(this.body.rows, function(row){ + if (!this.options.zebraOnlyVisibleRows || row.isDisplayed()){ + this.zebra(row, index++); + } + }, this); + }, + + setRowStyle: function(row, i){ + if (this.previous) this.previous(row, i); + this.zebra(row, i); + }, + + zebra: function(row, i){ + return row[((i % 2) ? 'remove' : 'add')+'Class'](this.options.classZebra); + }, + + push: function(){ + var pushed = this.previous.apply(this, arguments); + if (this.options.zebra) this.updateZebras(); + return pushed; + } + +}); + +/* +--- + +script: Scroller.js + +name: Scroller + +description: Class which scrolls the contents of any Element (including the window) when the mouse reaches the Element's boundaries. + +license: MIT-style license + +authors: + - Valerio Proietti + +requires: + - Core/Events + - Core/Options + - Core/Element.Event + - Core/Element.Dimensions + - MooTools.More + +provides: [Scroller] + +... +*/ + +var Scroller = new Class({ + + Implements: [Events, Options], + + options: { + area: 20, + velocity: 1, + onChange: function(x, y){ + this.element.scrollTo(x, y); + }, + fps: 50 + }, + + initialize: function(element, options){ + this.setOptions(options); + this.element = document.id(element); + this.docBody = document.id(this.element.getDocument().body); + this.listener = (typeOf(this.element) != 'element') ? this.docBody : this.element; + this.timer = null; + this.bound = { + attach: this.attach.bind(this), + detach: this.detach.bind(this), + getCoords: this.getCoords.bind(this) + }; + }, + + start: function(){ + this.listener.addEvents({ + mouseover: this.bound.attach, + mouseleave: this.bound.detach + }); + return this; + }, + + stop: function(){ + this.listener.removeEvents({ + mouseover: this.bound.attach, + mouseleave: this.bound.detach + }); + this.detach(); + this.timer = clearInterval(this.timer); + return this; + }, + + attach: function(){ + this.listener.addEvent('mousemove', this.bound.getCoords); + }, + + detach: function(){ + this.listener.removeEvent('mousemove', this.bound.getCoords); + this.timer = clearInterval(this.timer); + }, + + getCoords: function(event){ + this.page = (this.listener.get('tag') == 'body') ? event.client : event.page; + if (!this.timer) this.timer = this.scroll.periodical(Math.round(1000 / this.options.fps), this); + }, + + scroll: function(){ + var size = this.element.getSize(), + scroll = this.element.getScroll(), + pos = ((this.element != this.docBody) && (this.element != window)) ? element.getOffsets() : {x: 0, y: 0}, + scrollSize = this.element.getScrollSize(), + change = {x: 0, y: 0}, + top = this.options.area.top || this.options.area, + bottom = this.options.area.bottom || this.options.area; + for (var z in this.page){ + if (this.page[z] < (top + pos[z]) && scroll[z] != 0){ + change[z] = (this.page[z] - top - pos[z]) * this.options.velocity; + } else if (this.page[z] + bottom > (size[z] + pos[z]) && scroll[z] + size[z] != scrollSize[z]){ + change[z] = (this.page[z] - size[z] + bottom - pos[z]) * this.options.velocity; + } + change[z] = change[z].round(); + } + if (change.y || change.x) this.fireEvent('change', [scroll.x + change.x, scroll.y + change.y]); + } + +}); + +/* +--- + +script: Tips.js + +name: Tips + +description: Class for creating nice tips that follow the mouse cursor when hovering an element. + +license: MIT-style license + +authors: + - Valerio Proietti + - Christoph Pojer + - Luis Merino + +requires: + - Core/Options + - Core/Events + - Core/Element.Event + - Core/Element.Style + - Core/Element.Dimensions + - MooTools.More + +provides: [Tips] + +... +*/ + +(function(){ + +var read = function(option, element){ + return (option) ? (typeOf(option) == 'function' ? option(element) : element.get(option)) : ''; +}; + +this.Tips = new Class({ + + Implements: [Events, Options], + + options: {/* + id: null, + onAttach: function(element){}, + onDetach: function(element){}, + onBound: function(coords){},*/ + onShow: function(){ + this.tip.setStyle('display', 'block'); + }, + onHide: function(){ + this.tip.setStyle('display', 'none'); + }, + title: 'title', + text: function(element){ + return element.get('rel') || element.get('href'); + }, + showDelay: 100, + hideDelay: 100, + className: 'tip-wrap', + offset: {x: 16, y: 16}, + windowPadding: {x:0, y:0}, + fixed: false, + waiAria: true + }, + + initialize: function(){ + var params = Array.link(arguments, { + options: Type.isObject, + elements: function(obj){ + return obj != null; + } + }); + this.setOptions(params.options); + if (params.elements) this.attach(params.elements); + this.container = new Element('div', {'class': 'tip'}); + + if (this.options.id){ + this.container.set('id', this.options.id); + if (this.options.waiAria) this.attachWaiAria(); + } + }, + + toElement: function(){ + if (this.tip) return this.tip; + + this.tip = new Element('div', { + 'class': this.options.className, + styles: { + position: 'absolute', + top: 0, + left: 0 + } + }).adopt( + new Element('div', {'class': 'tip-top'}), + this.container, + new Element('div', {'class': 'tip-bottom'}) + ); + + return this.tip; + }, + + attachWaiAria: function(){ + var id = this.options.id; + this.container.set('role', 'tooltip'); + + if (!this.waiAria){ + this.waiAria = { + show: function(element){ + if (id) element.set('aria-describedby', id); + this.container.set('aria-hidden', 'false'); + }, + hide: function(element){ + if (id) element.erase('aria-describedby'); + this.container.set('aria-hidden', 'true'); + } + }; + } + this.addEvents(this.waiAria); + }, + + detachWaiAria: function(){ + if (this.waiAria){ + this.container.erase('role'); + this.container.erase('aria-hidden'); + this.removeEvents(this.waiAria); + } + }, + + attach: function(elements){ + $$(elements).each(function(element){ + var title = read(this.options.title, element), + text = read(this.options.text, element); + + element.set('title', '').store('tip:native', title).retrieve('tip:title', title); + element.retrieve('tip:text', text); + this.fireEvent('attach', [element]); + + var events = ['enter', 'leave']; + if (!this.options.fixed) events.push('move'); + + events.each(function(value){ + var event = element.retrieve('tip:' + value); + if (!event) event = function(event){ + this['element' + value.capitalize()].apply(this, [event, element]); + }.bind(this); + + element.store('tip:' + value, event).addEvent('mouse' + value, event); + }, this); + }, this); + + return this; + }, + + detach: function(elements){ + $$(elements).each(function(element){ + ['enter', 'leave', 'move'].each(function(value){ + element.removeEvent('mouse' + value, element.retrieve('tip:' + value)).eliminate('tip:' + value); + }); + + this.fireEvent('detach', [element]); + + if (this.options.title == 'title'){ // This is necessary to check if we can revert the title + var original = element.retrieve('tip:native'); + if (original) element.set('title', original); + } + }, this); + + return this; + }, + + elementEnter: function(event, element){ + clearTimeout(this.timer); + this.timer = (function(){ + this.container.empty(); + + ['title', 'text'].each(function(value){ + var content = element.retrieve('tip:' + value); + var div = this['_' + value + 'Element'] = new Element('div', { + 'class': 'tip-' + value + }).inject(this.container); + if (content) this.fill(div, content); + }, this); + this.show(element); + this.position((this.options.fixed) ? {page: element.getPosition()} : event); + }).delay(this.options.showDelay, this); + }, + + elementLeave: function(event, element){ + clearTimeout(this.timer); + this.timer = this.hide.delay(this.options.hideDelay, this, element); + this.fireForParent(event, element); + }, + + setTitle: function(title){ + if (this._titleElement){ + this._titleElement.empty(); + this.fill(this._titleElement, title); + } + return this; + }, + + setText: function(text){ + if (this._textElement){ + this._textElement.empty(); + this.fill(this._textElement, text); + } + return this; + }, + + fireForParent: function(event, element){ + element = element.getParent(); + if (!element || element == document.body) return; + if (element.retrieve('tip:enter')) element.fireEvent('mouseenter', event); + else this.fireForParent(event, element); + }, + + elementMove: function(event, element){ + this.position(event); + }, + + position: function(event){ + if (!this.tip) document.id(this); + + var size = window.getSize(), scroll = window.getScroll(), + tip = {x: this.tip.offsetWidth, y: this.tip.offsetHeight}, + props = {x: 'left', y: 'top'}, + bounds = {y: false, x2: false, y2: false, x: false}, + obj = {}; + + for (var z in props){ + obj[props[z]] = event.page[z] + this.options.offset[z]; + if (obj[props[z]] < 0) bounds[z] = true; + if ((obj[props[z]] + tip[z] - scroll[z]) > size[z] - this.options.windowPadding[z]){ + obj[props[z]] = event.page[z] - this.options.offset[z] - tip[z]; + bounds[z+'2'] = true; + } + } + + this.fireEvent('bound', bounds); + this.tip.setStyles(obj); + }, + + fill: function(element, contents){ + if (typeof contents == 'string') element.set('html', contents); + else element.adopt(contents); + }, + + show: function(element){ + if (!this.tip) document.id(this); + if (!this.tip.getParent()) this.tip.inject(document.body); + this.fireEvent('show', [this.tip, element]); + }, + + hide: function(element){ + if (!this.tip) document.id(this); + this.fireEvent('hide', [this.tip, element]); + } + +}); + +})(); + +/* +--- + +name: Locale.EU.Number + +description: Number messages for Europe. + +license: MIT-style license + +authors: + - Arian Stolwijk + +requires: + - Locale + +provides: [Locale.EU.Number] + +... +*/ + +Locale.define('EU', 'Number', { + + decimal: ',', + group: '.', + + currency: { + prefix: '⬠' + } + +}); + +/* +--- + +script: Locale.Set.From.js + +name: Locale.Set.From + +description: Provides an alternative way to create Locale.Set objects. + +license: MIT-style license + +authors: + - Tim Wienk + +requires: + - Core/JSON + - Locale + +provides: Locale.Set.From + +... +*/ + +(function(){ + +var parsers = { + 'json': JSON.decode +}; + +Locale.Set.defineParser = function(name, fn){ + parsers[name] = fn; +}; + +Locale.Set.from = function(set, type){ + if (instanceOf(set, Locale.Set)) return set; + + if (!type && typeOf(set) == 'string') type = 'json'; + if (parsers[type]) set = parsers[type](set); + + var locale = new Locale.Set; + + locale.sets = set.sets || {}; + + if (set.inherits){ + locale.inherits.locales = Array.from(set.inherits.locales); + locale.inherits.sets = set.inherits.sets || {}; + } + + return locale; +}; + +})(); + +/* +--- + +name: Locale.ZA.Number + +description: Number messages for ZA. + +license: MIT-style license + +authors: + - Werner Mollentze + +requires: + - Locale + +provides: [Locale.ZA.Number] + +... +*/ + +Locale.define('ZA', 'Number', { + + decimal: '.', + group: ',', + + currency: { + prefix: 'R ' + } + +}); + + + +/* +--- + +name: Locale.af-ZA.Date + +description: Date messages for ZA Afrikaans. + +license: MIT-style license + +authors: + - Werner Mollentze + +requires: + - Locale + +provides: [Locale.af-ZA.Date] + +... +*/ + +Locale.define('af-ZA', 'Date', { + + months: ['Januarie', 'Februarie', 'Maart', 'April', 'Mei', 'Junie', 'Julie', 'Augustus', 'September', 'Oktober', 'November', 'Desember'], + months_abbr: ['Jan', 'Feb', 'Mrt', 'Apr', 'Mei', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'], + days: ['Sondag', 'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrydag', 'Saterdag'], + days_abbr: ['Son', 'Maa', 'Din', 'Woe', 'Don', 'Vry', 'Sat'], + + // Culture's date order: MM/DD/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d-%m-%Y', + shortTime: '%H:%M', + AM: 'VM', + PM: 'NM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: function(dayOfMonth){ + return ((dayOfMonth > 1 && dayOfMonth < 20 && dayOfMonth != 8) || (dayOfMonth > 100 && dayOfMonth.toString().substr(-2, 1) == '1')) ? 'de' : 'ste'; + }, + + lessThanMinuteAgo: 'minder as \'n minuut gelede', + minuteAgo: 'ongeveer \'n minuut gelede', + minutesAgo: '{delta} minute gelede', + hourAgo: 'omtret \'n uur gelede', + hoursAgo: 'ongeveer {delta} ure gelede', + dayAgo: '1 dag gelede', + daysAgo: '{delta} dae gelede', + weekAgo: '1 week gelede', + weeksAgo: '{delta} weke gelede', + monthAgo: '1 maand gelede', + monthsAgo: '{delta} maande gelede', + yearAgo: '1 jaar gelede', + yearsAgo: '{delta} jare gelede', + + lessThanMinuteUntil: 'oor minder as \'n minuut', + minuteUntil: 'oor ongeveer \'n minuut', + minutesUntil: 'oor {delta} minute', + hourUntil: 'oor ongeveer \'n uur', + hoursUntil: 'oor {delta} uur', + dayUntil: 'oor ongeveer \'n dag', + daysUntil: 'oor {delta} dae', + weekUntil: 'oor \'n week', + weeksUntil: 'oor {delta} weke', + monthUntil: 'oor \'n maand', + monthsUntil: 'oor {delta} maande', + yearUntil: 'oor \'n jaar', + yearsUntil: 'oor {delta} jaar' + +}); + +/* +--- + +name: Locale.af-ZA.Form.Validator + +description: Form Validator messages for Afrikaans. + +license: MIT-style license + +authors: + - Werner Mollentze + +requires: + - Locale + +provides: [Locale.af-ZA.Form.Validator] + +... +*/ + +Locale.define('af-ZA', 'FormValidator', { + + required: 'Hierdie veld word vereis.', + length: 'Voer asseblief {length} karakters in (u het {elLength} karakters ingevoer)', + minLength: 'Voer asseblief ten minste {minLength} karakters in (u het {length} karakters ingevoer).', + maxLength: 'Moet asseblief nie meer as {maxLength} karakters invoer nie (u het {length} karakters ingevoer).', + integer: 'Voer asseblief \'n heelgetal in hierdie veld in. Getalle met desimale (bv. 1.25) word nie toegelaat nie.', + numeric: 'Voer asseblief slegs numeriese waardes in hierdie veld in (bv. "1" of "1.1" of "-1" of "-1.1").', + digits: 'Gebruik asseblief slegs nommers en punktuasie in hierdie veld. (by voorbeeld, \'n telefoon nommer wat koppeltekens en punte bevat is toelaatbaar).', + alpha: 'Gebruik asseblief slegs letters (a-z) binne-in hierdie veld. Geen spasies of ander karakters word toegelaat nie.', + alphanum: 'Gebruik asseblief slegs letters (a-z) en nommers (0-9) binne-in hierdie veld. Geen spasies of ander karakters word toegelaat nie.', + dateSuchAs: 'Voer asseblief \'n geldige datum soos {date} in', + dateInFormatMDY: 'Voer asseblief \'n geldige datum soos MM/DD/YYYY in (bv. "12/31/1999")', + email: 'Voer asseblief \'n geldige e-pos adres in. Byvoorbeeld "fred@domain.com".', + url: 'Voer asseblief \'n geldige bronadres (URL) soos http://www.example.com in.', + currencyDollar: 'Voer asseblief \'n geldige $ bedrag in. Byvoorbeeld $100.00 .', + oneRequired: 'Voer asseblief iets in vir ten minste een van hierdie velde.', + errorPrefix: 'Fout: ', + warningPrefix: 'Waarskuwing: ', + + // Form.Validator.Extras + noSpace: 'Daar mag geen spasies in hierdie toevoer wees nie.', + reqChkByNode: 'Geen items is gekies nie.', + requiredChk: 'Hierdie veld word vereis.', + reqChkByName: 'Kies asseblief \'n {label}.', + match: 'Hierdie veld moet by die {matchName} veld pas', + startDate: 'die begin datum', + endDate: 'die eind datum', + currentDate: 'die huidige datum', + afterDate: 'Die datum moet dieselfde of na {label} wees.', + beforeDate: 'Die datum moet dieselfde of voor {label} wees.', + startMonth: 'Kies asseblief \'n begin maand', + sameMonth: 'Hierdie twee datums moet in dieselfde maand wees - u moet een of beide verander.', + creditcard: 'Die ingevoerde kredietkaart nommer is ongeldig. Bevestig asseblief die nommer en probeer weer. {length} syfers is ingevoer.' + +}); + +/* +--- + +name: Locale.af-ZA.Number + +description: Number messages for ZA Afrikaans. + +license: MIT-style license + +authors: + - Werner Mollentze + +requires: + - Locale + - Locale.ZA.Number + +provides: [Locale.af-ZA.Number] + +... +*/ + +Locale.define('af-ZA').inherit('ZA', 'Number'); + +/* +--- + +name: Locale.ar.Date + +description: Date messages for Arabic. + +license: MIT-style license + +authors: + - Chafik Barbar + +requires: + - Locale + +provides: [Locale.ar.Date] + +... +*/ + +Locale.define('ar', 'Date', { + + // Culture's date order: DD/MM/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d/%m/%Y', + shortTime: '%H:%M' + +}); + +/* +--- + +name: Locale.ar.Form.Validator + +description: Form Validator messages for Arabic. + +license: MIT-style license + +authors: + - Chafik Barbar + +requires: + - Locale + +provides: [Locale.ar.Form.Validator] + +... +*/ + +Locale.define('ar', 'FormValidator', { + + required: 'ÙØ°Ø§ Ø§ÙØÙÙ Ù
Ø·ÙÙØš.', + minLength: 'رجاء٠إدخا٠{minLength} Ø£ØØ±Ù عÙÙ Ø§ÙØ£ÙÙ (تÙ
إدخا٠{length} Ø£ØØ±Ù).', + maxLength: 'Ø§ÙØ±Ø¬Ø§Ø¡ عدÙ
Ø¥Ø¯Ø®Ø§Ù Ø£ÙØ«Ø± Ù
Ù {maxLength} Ø£ØØ±Ù (تÙ
إدخا٠{length} Ø£ØØ±Ù).', + integer: 'Ø§ÙØ±Ø¬Ø§Ø¡ إدخا٠عدد صØÙØ ÙÙ ÙØ°Ø§ Ø§ÙØÙÙ. أ٠رÙÙ
Ø°Ù ÙØ³Ø± ع؎ر٠أ٠Ù
ØŠÙÙ (Ù
ثا٠1.25 ) ØºÙØ± Ù
سÙ
ÙØ.', + numeric: 'Ø§ÙØ±Ø¬Ø§Ø¡ إدخا٠ÙÙÙ
رÙÙ
ÙØ© ÙÙ ÙØ°Ø§ Ø§ÙØÙÙ (Ù
ثا٠"1" Ø£Ù "1.1" Ø£Ù "-1" Ø£Ù "-1.1").', + digits: 'Ø§ÙØ±Ø¬Ø§Ø¡ أستخداÙ
ÙÙÙ
رÙÙ
ÙØ© ÙØ¹ÙاÙ
ات ترÙÙÙ
ÙØ© ÙÙØ· ÙÙ ÙØ°Ø§ Ø§ÙØÙÙ (Ù
ثاÙ, رÙÙ
ÙØ§ØªÙ Ù
ع ÙÙØ·Ø© Ø£Ù ØŽØØ·Ø©)', + alpha: 'Ø§ÙØ±Ø¬Ø§Ø¡ أستخداÙ
Ø£ØØ±Ù ÙÙØ· (ا-Ù) ÙÙ ÙØ°Ø§ Ø§ÙØÙÙ. Ø£Ù ÙØ±Ø§ØºØ§Øª Ø£Ù Ø¹ÙØ§Ù
ات ØºÙØ± Ù
سÙ
ÙØØ©.', + alphanum: 'Ø§ÙØ±Ø¬Ø§Ø¡ أستخداÙ
Ø£ØØ±Ù ÙÙØ· (ا-Ù) Ø£Ù Ø£Ø±ÙØ§Ù
(0-9) ÙÙØ· ÙÙ ÙØ°Ø§ Ø§ÙØÙÙ. Ø£Ù ÙØ±Ø§ØºØ§Øª Ø£Ù Ø¹ÙØ§Ù
ات ØºÙØ± Ù
سÙ
ÙØØ©.', + dateSuchAs: 'Ø§ÙØ±Ø¬Ø§Ø¡ Ø¥Ø¯Ø®Ø§Ù ØªØ§Ø±ÙØ® صØÙØ ÙØ§ÙتاÙÙ {date}', + dateInFormatMDY: 'Ø§ÙØ±Ø¬Ø§Ø¡ Ø¥Ø¯Ø®Ø§Ù ØªØ§Ø±ÙØ® صØÙØ (Ù
ثاÙ, 31-12-1999)', + email: 'Ø§ÙØ±Ø¬Ø§Ø¡ Ø¥Ø¯Ø®Ø§Ù ØšØ±ÙØ¯ Ø¥ÙÙØªØ±ÙÙ٠صØÙØ.', + url: 'Ø§ÙØ±Ø¬Ø§Ø¡ إدخا٠عÙÙØ§Ù Ø¥ÙÙØªØ±ÙÙ٠صØÙØ Ù
ث٠http://www.example.com', + currencyDollar: 'Ø§ÙØ±Ø¬Ø§Ø¡ إدخا٠ÙÙÙ
Ø© $ صØÙØØ©. Ù
ثاÙ, 100.00$', + oneRequired: 'Ø§ÙØ±Ø¬Ø§Ø¡ إدخا٠ÙÙÙ
Ø© ÙÙ Ø£ØØ¯ ÙØ°Ù Ø§ÙØÙÙ٠عÙÙ Ø§ÙØ£ÙÙ.', + errorPrefix: 'خطأ: ', + warningPrefix: 'ØªØØ°Ùر: ' + +}); + +/* +--- + +name: Locale.ca-CA.Date + +description: Date messages for Catalan. + +license: MIT-style license + +authors: + - Ãlfons Sanchez + +requires: + - Locale + +provides: [Locale.ca-CA.Date] + +... +*/ + +Locale.define('ca-CA', 'Date', { + + months: ['Gener', 'Febrer', 'Març', 'Abril', 'Maig', 'Juny', 'Juli', 'Agost', 'Setembre', 'Octubre', 'Novembre', 'Desembre'], + months_abbr: ['gen.', 'febr.', 'març', 'abr.', 'maig', 'juny', 'jul.', 'ag.', 'set.', 'oct.', 'nov.', 'des.'], + days: ['Diumenge', 'Dilluns', 'Dimarts', 'Dimecres', 'Dijous', 'Divendres', 'Dissabte'], + days_abbr: ['dg', 'dl', 'dt', 'dc', 'dj', 'dv', 'ds'], + + // Culture's date order: DD/MM/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d/%m/%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 0, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: 'fa menys d`un minut', + minuteAgo: 'fa un minut', + minutesAgo: 'fa {delta} minuts', + hourAgo: 'fa un hora', + hoursAgo: 'fa unes {delta} hores', + dayAgo: 'fa un dia', + daysAgo: 'fa {delta} dies', + + lessThanMinuteUntil: 'menys d`un minut des d`ara', + minuteUntil: 'un minut des d`ara', + minutesUntil: '{delta} minuts des d`ara', + hourUntil: 'un hora des d`ara', + hoursUntil: 'unes {delta} hores des d`ara', + dayUntil: '1 dia des d`ara', + daysUntil: '{delta} dies des d`ara' + +}); + +/* +--- + +name: Locale.ca-CA.Form.Validator + +description: Form Validator messages for Catalan. + +license: MIT-style license + +authors: + - Miquel Hudin + - Ãlfons Sanchez + +requires: + - Locale + +provides: [Locale.ca-CA.Form.Validator] + +... +*/ + +Locale.define('ca-CA', 'FormValidator', { + + required: 'Aquest camp es obligatori.', + minLength: 'Per favor introdueix al menys {minLength} caracters (has introduit {length} caracters).', + maxLength: 'Per favor introdueix no mes de {maxLength} caracters (has introduit {length} caracters).', + integer: 'Per favor introdueix un nombre enter en aquest camp. Nombres amb decimals (p.e. 1,25) no estan permesos.', + numeric: 'Per favor introdueix sols valors numerics en aquest camp (p.e. "1" o "1,1" o "-1" o "-1,1").', + digits: 'Per favor usa sols numeros i puntuacio en aquest camp (per exemple, un nombre de telefon amb guions i punts no esta permes).', + alpha: 'Per favor utilitza lletres nomes (a-z) en aquest camp. No sÂŽadmiteixen espais ni altres caracters.', + alphanum: 'Per favor, utilitza nomes lletres (a-z) o numeros (0-9) en aquest camp. No sÂŽadmiteixen espais ni altres caracters.', + dateSuchAs: 'Per favor introdueix una data valida com {date}', + dateInFormatMDY: 'Per favor introdueix una data valida com DD/MM/YYYY (p.e. "31/12/1999")', + email: 'Per favor, introdueix una adreça de correu electronic valida. Per exemple, "fred@domain.com".', + url: 'Per favor introdueix una URL valida com http://www.example.com.', + currencyDollar: 'Per favor introdueix una quantitat valida de â¬. Per exemple â¬100,00 .', + oneRequired: 'Per favor introdueix alguna cosa per al menys una dÂŽaquestes entrades.', + errorPrefix: 'Error: ', + warningPrefix: 'Avis: ', + + // Form.Validator.Extras + noSpace: 'No poden haver espais en aquesta entrada.', + reqChkByNode: 'No hi han elements seleccionats.', + requiredChk: 'Aquest camp es obligatori.', + reqChkByName: 'Per favor selecciona una {label}.', + match: 'Aquest camp necessita coincidir amb el camp {matchName}', + startDate: 'la data de inici', + endDate: 'la data de fi', + currentDate: 'la data actual', + afterDate: 'La data deu ser igual o posterior a {label}.', + beforeDate: 'La data deu ser igual o anterior a {label}.', + startMonth: 'Per favor selecciona un mes dÂŽorige', + sameMonth: 'Aquestes dos dates deuen estar dins del mateix mes - deus canviar una o altra.' + +}); + +/* +--- + +name: Locale.cs-CZ.Date + +description: Date messages for Czech. + +license: MIT-style license + +authors: + - Jan ÄernÜ chemiX + - Christopher Zukowski + +requires: + - Locale + +provides: [Locale.cs-CZ.Date] + +... +*/ +(function(){ + +// Czech language pluralization rules, see http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html +// one -> n is 1; 1 +// few -> n in 2..4; 2-4 +// other -> everything else 0, 5-999, 1.31, 2.31, 5.31... +var pluralize = function (n, one, few, other){ + if (n == 1) return one; + else if (n == 2 || n == 3 || n == 4) return few; + else return other; +}; + +Locale.define('cs-CZ', 'Date', { + + months: ['Leden', 'Ãnor', 'BÅezen', 'Duben', 'KvÄten', 'Äerven', 'Äervenec', 'Srpen', 'ZáÅÃ', 'ÅÃjen', 'Listopad', 'Prosinec'], + months_abbr: ['ledna', 'února', 'bÅezna', 'dubna', 'kvÄtna', 'Äervna', 'Äervence', 'srpna', 'záÅÃ', 'ÅÃjna', 'listopadu', 'prosince'], + days: ['NedÄle', 'PondÄlÃ', 'ÃterÜ', 'StÅeda', 'Ätvrtek', 'Pátek', 'Sobota'], + days_abbr: ['ne', 'po', 'út', 'st', 'Ät', 'pá', 'so'], + + // Culture's date order: DD.MM.YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d.%m.%Y', + shortTime: '%H:%M', + AM: 'dop.', + PM: 'odp.', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '.', + + lessThanMinuteAgo: 'pÅed chvÃlÃ', + minuteAgo: 'pÅibliÅŸnÄ pÅed minutou', + minutesAgo: function(delta){ return 'pÅed {delta} ' + pluralize(delta, 'minutou', 'minutami', 'minutami'); }, + hourAgo: 'pÅibliÅŸnÄ pÅed hodinou', + hoursAgo: function(delta){ return 'pÅed {delta} ' + pluralize(delta, 'hodinou', 'hodinami', 'hodinami'); }, + dayAgo: 'pÅed dnem', + daysAgo: function(delta){ return 'pÅed {delta} ' + pluralize(delta, 'dnem', 'dny', 'dny'); }, + weekAgo: 'pÅed tÜdnem', + weeksAgo: function(delta){ return 'pÅed {delta} ' + pluralize(delta, 'tÜdnem', 'tÜdny', 'tÜdny'); }, + monthAgo: 'pÅed mÄsÃcem', + monthsAgo: function(delta){ return 'pÅed {delta} ' + pluralize(delta, 'mÄsÃcem', 'mÄsÃci', 'mÄsÃci'); }, + yearAgo: 'pÅed rokem', + yearsAgo: function(delta){ return 'pÅed {delta} ' + pluralize(delta, 'rokem', 'lety', 'lety'); }, + + lessThanMinuteUntil: 'za chvÃli', + minuteUntil: 'pÅibliÅŸnÄ za minutu', + minutesUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'minutu', 'minuty', 'minut'); }, + hourUntil: 'pÅibliÅŸnÄ za hodinu', + hoursUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'hodinu', 'hodiny', 'hodin'); }, + dayUntil: 'za den', + daysUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'den', 'dny', 'dnů'); }, + weekUntil: 'za tÜden', + weeksUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'tÜden', 'tÜdny', 'tÜdnů'); }, + monthUntil: 'za mÄsÃc', + monthsUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'mÄsÃc', 'mÄsÃce', 'mÄsÃců'); }, + yearUntil: 'za rok', + yearsUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'rok', 'roky', 'let'); } +}); + +})(); + +/* +--- + +name: Locale.cs-CZ.Form.Validator + +description: Form Validator messages for Czech. + +license: MIT-style license + +authors: + - Jan ÄernÜ chemiX + +requires: + - Locale + +provides: [Locale.cs-CZ.Form.Validator] + +... +*/ + +Locale.define('cs-CZ', 'FormValidator', { + + required: 'Tato poloÅŸka je povinná.', + minLength: 'Zadejte prosÃm alespoÅ {minLength} znaků (napsáno {length} znaků).', + maxLength: 'Zadejte prosÃm ménÄ neÅŸ {maxLength} znaků (nápsáno {length} znaků).', + integer: 'Zadejte prosÃm celé ÄÃslo. Desetinná ÄÃsla (napÅ. 1.25) nejsou povolena.', + numeric: 'Zadejte jen ÄÃselné hodnoty (tj. "1" nebo "1.1" nebo "-1" nebo "-1.1").', + digits: 'Zadejte prosÃm pouze ÄÃsla a interpunkÄnà znaménka(napÅÃklad telefonnà ÄÃslo s pomlÄkami nebo teÄkami je povoleno).', + alpha: 'Zadejte prosÃm pouze pÃsmena (a-z). Mezery nebo jiné znaky nejsou povoleny.', + alphanum: 'Zadejte prosÃm pouze pÃsmena (a-z) nebo ÄÃslice (0-9). Mezery nebo jiné znaky nejsou povoleny.', + dateSuchAs: 'Zadejte prosÃm platné datum jako {date}', + dateInFormatMDY: 'Zadejte prosÃm platné datum jako MM / DD / RRRR (tj. "12/31/1999")', + email: 'Zadejte prosÃm platnou e-mailovou adresu. NapÅÃklad "fred@domain.com".', + url: 'Zadejte prosÃm platnou URL adresu jako http://www.example.com.', + currencyDollar: 'Zadejte prosÃm platnou Äástku. NapÅÃklad $100.00.', + oneRequired: 'Zadejte prosÃm alespoÅ jednu hodnotu pro tyto poloÅŸky.', + errorPrefix: 'Chyba: ', + warningPrefix: 'UpozornÄnÃ: ', + + // Form.Validator.Extras + noSpace: 'V této poloÅŸce nejsou povoleny mezery', + reqChkByNode: 'Nejsou vybrány şádné poloÅŸky.', + requiredChk: 'Tato poloÅŸka je vyÅŸadována.', + reqChkByName: 'ProsÃm vyberte {label}.', + match: 'Tato poloÅŸka se musà shodovat s poloÅŸkou {matchName}', + startDate: 'datum zahájenÃ', + endDate: 'datum ukonÄenÃ', + currentDate: 'aktuálnà datum', + afterDate: 'Datum by mÄlo bÜt stejné nebo vÄtšà neÅŸ {label}.', + beforeDate: 'Datum by mÄlo bÜt stejné nebo menšà neÅŸ {label}.', + startMonth: 'Vyberte poÄáteÄnà mÄsÃc.', + sameMonth: 'Tyto dva datumy musà bÜt ve stejném mÄsÃci - zmÄÅte jeden z nich.', + creditcard: 'Zadané ÄÃslo kreditnà karty je neplatné. ProsÃm opravte ho. Bylo zadáno {length} ÄÃsel.' + +}); + +/* +--- + +name: Locale.da-DK.Date + +description: Date messages for Danish. + +license: MIT-style license + +authors: + - Martin Overgaard + - Henrik Hansen + +requires: + - Locale + +provides: [Locale.da-DK.Date] + +... +*/ + +Locale.define('da-DK', 'Date', { + + months: ['Januar', 'Februar', 'Marts', 'April', 'Maj', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'December'], + months_abbr: ['jan.', 'feb.', 'mar.', 'apr.', 'maj.', 'jun.', 'jul.', 'aug.', 'sep.', 'okt.', 'nov.', 'dec.'], + days: ['SÞndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'LÞrdag'], + days_abbr: ['sÞn', 'man', 'tir', 'ons', 'tor', 'fre', 'lÞr'], + + // Culture's date order: DD-MM-YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d-%m-%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '.', + + lessThanMinuteAgo: 'mindre end et minut siden', + minuteAgo: 'omkring et minut siden', + minutesAgo: '{delta} minutter siden', + hourAgo: 'omkring en time siden', + hoursAgo: 'omkring {delta} timer siden', + dayAgo: '1 dag siden', + daysAgo: '{delta} dage siden', + weekAgo: '1 uge siden', + weeksAgo: '{delta} uger siden', + monthAgo: '1 mÃ¥ned siden', + monthsAgo: '{delta} mÃ¥neder siden', + yearAgo: '1 Ã¥r siden', + yearsAgo: '{delta} Ã¥r siden', + + lessThanMinuteUntil: 'mindre end et minut fra nu', + minuteUntil: 'omkring et minut fra nu', + minutesUntil: '{delta} minutter fra nu', + hourUntil: 'omkring en time fra nu', + hoursUntil: 'omkring {delta} timer fra nu', + dayUntil: '1 dag fra nu', + daysUntil: '{delta} dage fra nu', + weekUntil: '1 uge fra nu', + weeksUntil: '{delta} uger fra nu', + monthUntil: '1 mÃ¥ned fra nu', + monthsUntil: '{delta} mÃ¥neder fra nu', + yearUntil: '1 Ã¥r fra nu', + yearsUntil: '{delta} Ã¥r fra nu' + +}); + +/* +--- + +name: Locale.da-DK.Form.Validator + +description: Form Validator messages for Danish. + +license: MIT-style license + +authors: + - Martin Overgaard + +requires: + - Locale + +provides: [Locale.da-DK.Form.Validator] + +... +*/ + +Locale.define('da-DK', 'FormValidator', { + + required: 'Feltet skal udfyldes.', + minLength: 'Skriv mindst {minLength} tegn (du skrev {length} tegn).', + maxLength: 'Skriv maksimalt {maxLength} tegn (du skrev {length} tegn).', + integer: 'Skriv et tal i dette felt. Decimal tal (f.eks. 1.25) er ikke tilladt.', + numeric: 'Skriv kun tal i dette felt (i.e. "1" eller "1.1" eller "-1" eller "-1.1").', + digits: 'Skriv kun tal og tegnsÊtning i dette felt (eksempel, et telefon nummer med bindestreg eller punktum er tilladt).', + alpha: 'Skriv kun bogstaver (a-z) i dette felt. Mellemrum og andre tegn er ikke tilladt.', + alphanum: 'Skriv kun bogstaver (a-z) eller tal (0-9) i dette felt. Mellemrum og andre tegn er ikke tilladt.', + dateSuchAs: 'Skriv en gyldig dato som {date}', + dateInFormatMDY: 'Skriv dato i formatet DD-MM-YYYY (f.eks. "31-12-1999")', + email: 'Skriv en gyldig e-mail adresse. F.eks "fred@domain.com".', + url: 'Skriv en gyldig URL adresse. F.eks "http://www.example.com".', + currencyDollar: 'Skriv et gldigt belÞb. F.eks Kr.100.00 .', + oneRequired: 'Et eller flere af felterne i denne formular skal udfyldes.', + errorPrefix: 'Fejl: ', + warningPrefix: 'Advarsel: ', + + // Form.Validator.Extras + noSpace: 'Der mÃ¥ ikke benyttes mellemrum i dette felt.', + reqChkByNode: 'Foretag et valg.', + requiredChk: 'Dette felt skal udfyldes.', + reqChkByName: 'VÊlg en {label}.', + match: 'Dette felt skal matche {matchName} feltet', + startDate: 'start dato', + endDate: 'slut dato', + currentDate: 'dags dato', + afterDate: 'Datoen skal vÊre stÞrre end eller lig med {label}.', + beforeDate: 'Datoen skal vÊre mindre end eller lig med {label}.', + startMonth: 'VÊlg en start mÃ¥ned', + sameMonth: 'De valgte datoer skal vÊre i samme mÃ¥ned - skift en af dem.' + +}); + +/* +--- + +name: Locale.de-DE.Date + +description: Date messages for German. + +license: MIT-style license + +authors: + - Christoph Pojer + - Frank Rossi + - Ulrich Petri + - Fabian Beiner + +requires: + - Locale + +provides: [Locale.de-DE.Date] + +... +*/ + +Locale.define('de-DE', 'Date', { + + months: ['Januar', 'Februar', 'MÀrz', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'], + months_abbr: ['Jan', 'Feb', 'MÀr', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'], + days: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'], + days_abbr: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'], + + // Culture's date order: DD.MM.YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d.%m.%Y', + shortTime: '%H:%M', + AM: 'vormittags', + PM: 'nachmittags', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '.', + + lessThanMinuteAgo: 'vor weniger als einer Minute', + minuteAgo: 'vor einer Minute', + minutesAgo: 'vor {delta} Minuten', + hourAgo: 'vor einer Stunde', + hoursAgo: 'vor {delta} Stunden', + dayAgo: 'vor einem Tag', + daysAgo: 'vor {delta} Tagen', + weekAgo: 'vor einer Woche', + weeksAgo: 'vor {delta} Wochen', + monthAgo: 'vor einem Monat', + monthsAgo: 'vor {delta} Monaten', + yearAgo: 'vor einem Jahr', + yearsAgo: 'vor {delta} Jahren', + + lessThanMinuteUntil: 'in weniger als einer Minute', + minuteUntil: 'in einer Minute', + minutesUntil: 'in {delta} Minuten', + hourUntil: 'in ca. einer Stunde', + hoursUntil: 'in ca. {delta} Stunden', + dayUntil: 'in einem Tag', + daysUntil: 'in {delta} Tagen', + weekUntil: 'in einer Woche', + weeksUntil: 'in {delta} Wochen', + monthUntil: 'in einem Monat', + monthsUntil: 'in {delta} Monaten', + yearUntil: 'in einem Jahr', + yearsUntil: 'in {delta} Jahren' + +}); + +/* +--- + +name: Locale.de-CH.Date + +description: Date messages for German (Switzerland). + +license: MIT-style license + +authors: + - Michael van der Weg + +requires: + - Locale + - Locale.de-DE.Date + +provides: [Locale.de-CH.Date] + +... +*/ + +Locale.define('de-CH').inherit('de-DE', 'Date'); + +/* +--- + +name: Locale.de-CH.Form.Validator + +description: Form Validator messages for German (Switzerland). + +license: MIT-style license + +authors: + - Michael van der Weg + +requires: + - Locale + +provides: [Locale.de-CH.Form.Validator] + +... +*/ + +Locale.define('de-CH', 'FormValidator', { + + required: 'Dieses Feld ist obligatorisch.', + minLength: 'Geben Sie bitte mindestens {minLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).', + maxLength: 'Bitte geben Sie nicht mehr als {maxLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).', + integer: 'Geben Sie bitte eine ganze Zahl ein. Dezimalzahlen (z.B. 1.25) sind nicht erlaubt.', + numeric: 'Geben Sie bitte nur Zahlenwerte in dieses Eingabefeld ein (z.B. "1", "1.1", "-1" oder "-1.1").', + digits: 'Benutzen Sie bitte nur Zahlen und Satzzeichen in diesem Eingabefeld (erlaubt ist z.B. eine Telefonnummer mit Bindestrichen und Punkten).', + alpha: 'Benutzen Sie bitte nur Buchstaben (a-z) in diesem Feld. Leerzeichen und andere Zeichen sind nicht erlaubt.', + alphanum: 'Benutzen Sie bitte nur Buchstaben (a-z) und Zahlen (0-9) in diesem Eingabefeld. Leerzeichen und andere Zeichen sind nicht erlaubt.', + dateSuchAs: 'Geben Sie bitte ein gültiges Datum ein. Wie zum Beispiel {date}', + dateInFormatMDY: 'Geben Sie bitte ein gültiges Datum ein. Wie zum Beispiel TT.MM.JJJJ (z.B. "31.12.1999")', + email: 'Geben Sie bitte eine gültige E-Mail Adresse ein. Wie zum Beispiel "maria@bernasconi.ch".', + url: 'Geben Sie bitte eine gültige URL ein. Wie zum Beispiel http://www.example.com.', + currencyDollar: 'Geben Sie bitte einen gültigen Betrag in Schweizer Franken ein. Wie zum Beispiel 100.00 CHF .', + oneRequired: 'Machen Sie für mindestens eines der Eingabefelder einen Eintrag.', + errorPrefix: 'Fehler: ', + warningPrefix: 'Warnung: ', + + // Form.Validator.Extras + noSpace: 'In diesem Eingabefeld darf kein Leerzeichen sein.', + reqChkByNode: 'Es wurden keine Elemente gewählt.', + requiredChk: 'Dieses Feld ist obligatorisch.', + reqChkByName: 'Bitte wählen Sie ein {label}.', + match: 'Dieses Eingabefeld muss mit dem Feld {matchName} übereinstimmen.', + startDate: 'Das Anfangsdatum', + endDate: 'Das Enddatum', + currentDate: 'Das aktuelle Datum', + afterDate: 'Das Datum sollte zur gleichen Zeit oder später sein {label}.', + beforeDate: 'Das Datum sollte zur gleichen Zeit oder früher sein {label}.', + startMonth: 'Wählen Sie bitte einen Anfangsmonat', + sameMonth: 'Diese zwei Datumsangaben müssen im selben Monat sein - Sie müssen eine von beiden verändern.', + creditcard: 'Die eingegebene Kreditkartennummer ist ungültig. Bitte überprüfen Sie diese und versuchen Sie es erneut. {length} Zahlen eingegeben.' + +}); + +/* +--- + +name: Locale.de-DE.Form.Validator + +description: Form Validator messages for German. + +license: MIT-style license + +authors: + - Frank Rossi + - Ulrich Petri + - Fabian Beiner + +requires: + - Locale + +provides: [Locale.de-DE.Form.Validator] + +... +*/ + +Locale.define('de-DE', 'FormValidator', { + + required: 'Dieses Eingabefeld muss ausgefÃŒllt werden.', + minLength: 'Geben Sie bitte mindestens {minLength} Zeichen ein (Sie haben nur {length} Zeichen eingegeben).', + maxLength: 'Geben Sie bitte nicht mehr als {maxLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).', + integer: 'Geben Sie in diesem Eingabefeld bitte eine ganze Zahl ein. Dezimalzahlen (z.B. "1.25") sind nicht erlaubt.', + numeric: 'Geben Sie in diesem Eingabefeld bitte nur Zahlenwerte (z.B. "1", "1.1", "-1" oder "-1.1") ein.', + digits: 'Geben Sie in diesem Eingabefeld bitte nur Zahlen und Satzzeichen ein (z.B. eine Telefonnummer mit Bindestrichen und Punkten ist erlaubt).', + alpha: 'Geben Sie in diesem Eingabefeld bitte nur Buchstaben (a-z) ein. Leerzeichen und andere Zeichen sind nicht erlaubt.', + alphanum: 'Geben Sie in diesem Eingabefeld bitte nur Buchstaben (a-z) und Zahlen (0-9) ein. Leerzeichen oder andere Zeichen sind nicht erlaubt.', + dateSuchAs: 'Geben Sie bitte ein gÃŒltiges Datum ein (z.B. "{date}").', + dateInFormatMDY: 'Geben Sie bitte ein gÃŒltiges Datum im Format TT.MM.JJJJ ein (z.B. "31.12.1999").', + email: 'Geben Sie bitte eine gÃŒltige E-Mail-Adresse ein (z.B. "max@mustermann.de").', + url: 'Geben Sie bitte eine gÃŒltige URL ein (z.B. "http://www.example.com").', + currencyDollar: 'Geben Sie bitte einen gÃŒltigen Betrag in EURO ein (z.B. 100.00â¬).', + oneRequired: 'Bitte fÃŒllen Sie mindestens ein Eingabefeld aus.', + errorPrefix: 'Fehler: ', + warningPrefix: 'Warnung: ', + + // Form.Validator.Extras + noSpace: 'Es darf kein Leerzeichen in diesem Eingabefeld sein.', + reqChkByNode: 'Es wurden keine Elemente gewÀhlt.', + requiredChk: 'Dieses Feld muss ausgefÃŒllt werden.', + reqChkByName: 'Bitte wÀhlen Sie ein {label}.', + match: 'Dieses Eingabefeld muss mit dem {matchName} Eingabefeld ÃŒbereinstimmen.', + startDate: 'Das Anfangsdatum', + endDate: 'Das Enddatum', + currentDate: 'Das aktuelle Datum', + afterDate: 'Das Datum sollte zur gleichen Zeit oder spÀter sein als {label}.', + beforeDate: 'Das Datum sollte zur gleichen Zeit oder frÃŒher sein als {label}.', + startMonth: 'WÀhlen Sie bitte einen Anfangsmonat', + sameMonth: 'Diese zwei Datumsangaben mÃŒssen im selben Monat sein - Sie mÃŒssen eines von beiden verÀndern.', + creditcard: 'Die eingegebene Kreditkartennummer ist ungÃŒltig. Bitte ÃŒberprÃŒfen Sie diese und versuchen Sie es erneut. {length} Zahlen eingegeben.' + +}); + +/* +--- + +name: Locale.de-DE.Number + +description: Number messages for German. + +license: MIT-style license + +authors: + - Christoph Pojer + +requires: + - Locale + - Locale.EU.Number + +provides: [Locale.de-DE.Number] + +... +*/ + +Locale.define('de-DE').inherit('EU', 'Number'); + +/* +--- + +name: Locale.el-GR.Date + +description: Date messages for Greek language. + +license: MIT-style license + +authors: + - Periklis Argiriadis + +requires: + - Locale + +provides: [Locale.el-GR.Date] + +... +*/ + +Locale.define('el-GR', 'Date', { + + months: ['ÎαΜοÏ
άÏιοÏ', 'ΊεβÏοÏ
άÏιοÏ', 'ÎάÏÏιοÏ', 'ÎÏÏίλιοÏ', 'ÎάιοÏ', 'ÎοÏΜιοÏ', 'ÎοÏλιοÏ', 'ÎÏγοÏ
ÏÏοÏ', 'ΣεÏÏÎΌβÏιοÏ', 'ÎκÏÏβÏιοÏ', 'ÎοÎΌβÏιοÏ', 'ÎεκÎΌβÏιοÏ'], + months_abbr: ['ÎαΜ', 'Ίεβ', 'ÎαÏ', 'ÎÏÏ', 'Îάι', 'ÎοÏ
Μ', 'ÎοÏ
λ', 'ÎÏ
γ', 'ΣεÏ', 'ÎκÏ', 'Îοε', 'Îεκ'], + days: ['ÎÏ
Ïιακή', 'ÎεÏ
ÏÎÏα', '΀ÏίÏη', '΀εÏάÏÏη', 'Î ÎÎŒÏÏη', 'ΠαÏαÏκεÏ
ή', 'ΣάββαÏο'], + days_abbr: ['ÎÏ
Ï', 'ÎεÏ
', '΀Ïι', '΀εÏ', 'ΠεΌ', 'ΠαÏ', 'Σαβ'], + + // Culture's date order: DD/MM/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d/%m/%Y', + shortTime: '%I:%M%p', + AM: 'ÏÎŒ', + PM: 'ΌΌ', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: function(dayOfMonth){ + // 1st, 2nd, 3rd, etc. + return (dayOfMonth > 3 && dayOfMonth < 21) ? 'οÏ' : ['οÏ'][Math.min(dayOfMonth % 10, 4)]; + }, + + lessThanMinuteAgo: 'λιγÏÏεÏο αÏÏ ÎΜα λεÏÏÏ ÏÏιΜ', + minuteAgo: 'ÏεÏίÏοÏ
ÎΜα λεÏÏÏ ÏÏιΜ', + minutesAgo: '{delta} λεÏÏά ÏÏιΜ', + hourAgo: 'ÏεÏίÏοÏ
Όια ÏÏα ÏÏιΜ', + hoursAgo: 'ÏεÏίÏοÏ
{delta} ÏÏÎµÏ ÏÏιΜ', + dayAgo: '1 ηΌÎÏα ÏÏιΜ', + daysAgo: '{delta} ηΌÎÏÎµÏ ÏÏιΜ', + weekAgo: '1 εβΎοΌάΎα ÏÏιΜ', + weeksAgo: '{delta} ÎµÎ²ÎŽÎ¿ÎŒÎ¬ÎŽÎµÏ ÏÏιΜ', + monthAgo: '1 ΌήΜα ÏÏιΜ', + monthsAgo: '{delta} ÎŒÎ®ÎœÎµÏ ÏÏιΜ', + yearAgo: '1 ÏÏÏΜο ÏÏιΜ', + yearsAgo: '{delta} ÏÏÏΜια ÏÏιΜ', + + lessThanMinuteUntil: 'λιγÏÏεÏο αÏÏ Î»ÎµÏÏÏ Î±ÏÏ ÏÏÏα', + minuteUntil: 'ÏεÏίÏοÏ
ÎΜα λεÏÏÏ Î±ÏÏ ÏÏÏα', + minutesUntil: '{delta} λεÏÏά αÏÏ ÏÏÏα', + hourUntil: 'ÏεÏίÏοÏ
Όια ÏÏα αÏÏ ÏÏÏα', + hoursUntil: 'ÏεÏίÏοÏ
{delta} ÏÏÎµÏ Î±ÏÏ ÏÏÏα', + dayUntil: '1 ηΌÎÏα αÏÏ ÏÏÏα', + daysUntil: '{delta} ηΌÎÏÎµÏ Î±ÏÏ ÏÏÏα', + weekUntil: '1 εβΎοΌάΎα αÏÏ ÏÏÏα', + weeksUntil: '{delta} ÎµÎ²ÎŽÎ¿ÎŒÎ¬ÎŽÎµÏ Î±ÏÏ ÏÏÏα', + monthUntil: '1 ÎŒÎ®ÎœÎ±Ï Î±ÏÏ ÏÏÏα', + monthsUntil: '{delta} ÎŒÎ®ÎœÎµÏ Î±ÏÏ ÏÏÏα', + yearUntil: '1 ÏÏÏÎœÎ¿Ï Î±ÏÏ ÏÏÏα', + yearsUntil: '{delta} ÏÏÏΜια αÏÏ ÏÏÏα' + +}); + +/* +--- + +name: Locale.el-GR.Form.Validator + +description: Form Validator messages for Greek language. + +license: MIT-style license + +authors: + - Dimitris Tsironis + +requires: + - Locale + +provides: [Locale.el-GR.Form.Validator] + +... +*/ + +Locale.define('el-GR', 'FormValidator', { + + required: 'ÎÏ
ÏÏ Ïο ÏεΎίο είΜαι αÏαÏαίÏηÏο.', + length: 'ΠαÏακαλοÏΌε, ειÏάγεÏε {length} ÏαÏακÏήÏÎµÏ (ÎÏεÏε ήΎη ειÏάγει {elLength} ÏαÏακÏήÏεÏ).', + minLength: 'ΠαÏακαλοÏΌε, ειÏάγεÏε ÏοÏ
λάÏιÏÏοΜ {minLength} ÏαÏακÏήÏÎµÏ (ÎÏεÏε ήΎη ειÏάγε {length} ÏαÏακÏήÏεÏ).', + maxlength: 'ΠαÏακαλοÏΌε, ειÏάγεÏε εÏÏ {maxlength} ÏαÏακÏήÏÎµÏ (ÎÏεÏε ήΎη ειÏάγε {length} ÏαÏακÏήÏεÏ).', + integer: 'ΠαÏακαλοÏΌε, ειÏάγεÏε ÎΜαΜ ακÎÏαιο αÏÎ¹ÎžÎŒÏ Ïε αÏ
ÏÏ Ïο ÏεΎίο. Îι αÏιΞΌοί Όε ΎεκαΎικά ÏηÏία (Ï.Ï. 1.25) ΎεΜ εÏιÏÏÎÏοΜÏαι.', + numeric: 'ΠαÏακαλοÏΌε, ειÏάγεÏε ÎŒÏΜο αÏιΞΌηÏικÎÏ ÏιΌÎÏ Ïε αÏ
ÏÏ Ïο ÏεΎίο (Ï.Ï." 1 " ή " 1.1 " ή " -1 " ή " -1.1 " ).', + digits: 'ΠαÏακαλοÏΌε, ÏÏηÏιΌοÏοιήÏÏε ÎŒÏΜο αÏιΞΌοÏÏ ÎºÎ±Î¹ ÏηΌεία ÏÏÎ¯ÎŸÎ·Ï Ïε αÏ
ÏÏΜ ÏοΜ ÏοΌÎα (Ï.Ï. εÏιÏÏÎÏεÏαι αÏιΞΌÏÏ ÏηλεÏÏΜοÏ
Όε ÏαÏÎ»ÎµÏ Î® ÏελείεÏ).', + alpha: 'ΠαÏακαλοÏΌε, ÏÏηÏιΌοÏοιήÏÏε ÎŒÏΜο γÏάΌΌαÏα (a-z) Ïε αÏ
ÏÏ Ïο ÏεΎίο. ÎεΜ εÏιÏÏÎÏοΜÏαι κεΜά ή άλλοι ÏαÏακÏήÏεÏ.', + alphanum: 'ΠαÏακαλοÏΌε, ÏÏηÏιΌοÏοιήÏÏε ÎŒÏΜο γÏάΌΌαÏα (a-z) ή αÏιΞΌοÏÏ (0-9) Ïε αÏ
ÏÏΜ ÏοΜ ÏοΌÎα. ÎεΜ εÏιÏÏÎÏοΜÏαι κεΜά ή άλλοι ÏαÏακÏήÏεÏ.', + dateSuchAs: 'ΠαÏακαλοÏΌε, ειÏάγεÏε Όια ÎγκÏ
Ïη ηΌεÏοΌηΜία, ÏÏÏÏ {date}', + dateInFormatMDY: 'ΠαÏÎ±ÎºÎ±Î»Ï ÎµÎ¹ÏάγεÏε Όια ÎγκÏ
Ïη ηΌεÏοΌηΜία, ÏÏÏÏ ÎÎ/ÎÎ/ÎÎÎÎ (Ï.Ï. "12/31/1999").', + email: 'ΠαÏακαλοÏΌε, ειÏάγεÏε Όια ÎγκÏ
Ïη ΎιεÏΞÏ
ΜÏη ηλεκÏÏÎ¿ÎœÎ¹ÎºÎ¿Ï ÏαÏÏ
ÎŽÏοΌείοÏ
(Ï.Ï. "fred@domain.com").', + url: 'ΠαÏακαλοÏΌε, ειÏάγεÏε Όια ÎγκÏ
Ïη URL ΎιεÏΞÏ
ΜÏη, ÏÏÏÏ http://www.example.com', + currencyDollar: 'ΠαÏακαλοÏΌε, ειÏάγεÏε ÎΜα ÎγκÏ
Ïο ÏοÏÏ Ïε ΎολλάÏια (Ï.Ï. $100.00).', + oneRequired: 'ΠαÏακαλοÏΌε, ειÏάγεÏε κάÏι για ÏοÏ
λάÏιÏÏοΜ ÎΜα αÏÏ Î±Ï
Ïά Ïα ÏεΎία.', + errorPrefix: 'ΣÏάλΌα: ', + warningPrefix: 'Î ÏοÏοÏή: ', + + // Form.Validator.Extras + noSpace: 'ÎεΜ εÏιÏÏÎÏοΜÏαι Ïα κεΜά Ïε αÏ
ÏÏ Ïο ÏεΎίο.', + reqChkByNode: 'ÎεΜ ÎÏει εÏιλεγεί κάÏοιο αΜÏικείΌεΜο', + requiredChk: 'ÎÏ
ÏÏ Ïο ÏεΎίο είΜαι αÏαÏαίÏηÏο.', + reqChkByName: 'ΠαÏακαλοÏΌε, εÏιλÎΟÏε Όια εÏικÎÏα {label}.', + match: 'ÎÏ
ÏÏ Ïο ÏεΎίο ÏÏÎÏει Μα ÏαιÏιάζει Όε Ïο ÏεΎίο {matchName}.', + startDate: 'η ηΌεÏοΌηΜία ÎΜαÏΟηÏ', + endDate: 'η ηΌεÏοΌηΜία λήΟηÏ', + currentDate: 'η ÏÏÎÏοÏ
Ïα ηΌεÏοΌηΜία', + afterDate: 'ΠηΌεÏοΌηΜία ÏÏÎÏει Μα είΜαι η ίΎια ή ΌεÏά αÏÏ ÏηΜ {label}.', + beforeDate: 'ΠηΌεÏοΌηΜία ÏÏÎÏει Μα είΜαι η ίΎια ή ÏÏιΜ αÏÏ ÏηΜ {label}.', + startMonth: 'ΠαÏÎ±ÎºÎ±Î»Ï ÎµÏιλÎΟÏε ÎΜα ΌήΜα αÏÏήÏ.', + sameMonth: 'ÎÏ
ÏÎÏ Î¿Î¹ ÎŽÏο ηΌεÏÎ¿ÎŒÎ·ÎœÎ¯ÎµÏ ÏÏÎÏει Μα ÎÏοÏ
Μ ÏοΜ ίΎιο ΌήΜα - Ξα ÏÏÎÏει Μα αλλάΟεÏε ή Ïο ÎΜα ή Ïο άλλο', + creditcard: 'ΠαÏιΞΌÏÏ ÏÎ·Ï ÏιÏÏÏÏÎ¹ÎºÎ®Ï ÎºÎ¬ÏÏÎ±Ï ÎŽÎµÎœ είΜαι ÎγκÏ
ÏοÏ. ΠαÏακαλοÏΌε ελÎγΟÏε ÏοΜ αÏÎ¹ÎžÎŒÏ ÎºÎ±Î¹ ΎοκιΌάÏÏε ΟαΜά. {length} ÎŒÎ®ÎºÎ¿Ï ÏηÏίÏΜ.' + +}); + +/* +--- + +name: Locale.en-GB.Date + +description: Date messages for British English. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Locale + - Locale.en-US.Date + +provides: [Locale.en-GB.Date] + +... +*/ + +Locale.define('en-GB', 'Date', { + + // Culture's date order: DD/MM/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d/%m/%Y', + shortTime: '%H:%M' + +}).inherit('en-US', 'Date'); + +/* +--- + +name: Locale.en-US.Number + +description: Number messages for US English. + +license: MIT-style license + +authors: + - Arian Stolwijk + +requires: + - Locale + +provides: [Locale.en-US.Number] + +... +*/ + +Locale.define('en-US', 'Number', { + + decimal: '.', + group: ',', + +/* Commented properties are the defaults for Number.format + decimals: 0, + precision: 0, + scientific: null, + + prefix: null, + suffic: null, + + // Negative/Currency/percentage will mixin Number + negative: { + prefix: '-' + },*/ + + currency: { +// decimals: 2, + prefix: '$ ' + }/*, + + percentage: { + decimals: 2, + suffix: '%' + }*/ + +}); + + + +/* +--- + +name: Locale.es-ES.Date + +description: Date messages for Spanish. + +license: MIT-style license + +authors: + - Ãlfons Sanchez + +requires: + - Locale + +provides: [Locale.es-ES.Date] + +... +*/ + +Locale.define('es-ES', 'Date', { + + months: ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'], + months_abbr: ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic'], + days: ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado'], + days_abbr: ['dom', 'lun', 'mar', 'mié', 'juv', 'vie', 'sáb'], + + // Culture's date order: DD/MM/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d/%m/%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: 'hace menos de un minuto', + minuteAgo: 'hace un minuto', + minutesAgo: 'hace {delta} minutos', + hourAgo: 'hace una hora', + hoursAgo: 'hace unas {delta} horas', + dayAgo: 'hace un dÃa', + daysAgo: 'hace {delta} dÃas', + weekAgo: 'hace una semana', + weeksAgo: 'hace unas {delta} semanas', + monthAgo: 'hace un mes', + monthsAgo: 'hace {delta} meses', + yearAgo: 'hace un año', + yearsAgo: 'hace {delta} años', + + lessThanMinuteUntil: 'menos de un minuto desde ahora', + minuteUntil: 'un minuto desde ahora', + minutesUntil: '{delta} minutos desde ahora', + hourUntil: 'una hora desde ahora', + hoursUntil: 'unas {delta} horas desde ahora', + dayUntil: 'un dÃa desde ahora', + daysUntil: '{delta} dÃas desde ahora', + weekUntil: 'una semana desde ahora', + weeksUntil: 'unas {delta} semanas desde ahora', + monthUntil: 'un mes desde ahora', + monthsUntil: '{delta} meses desde ahora', + yearUntil: 'un año desde ahora', + yearsUntil: '{delta} años desde ahora' + +}); + +/* +--- + +name: Locale.es-AR.Date + +description: Date messages for Spanish (Argentina). + +license: MIT-style license + +authors: + - Ãlfons Sanchez + - Diego Massanti + +requires: + - Locale + - Locale.es-ES.Date + +provides: [Locale.es-AR.Date] + +... +*/ + +Locale.define('es-AR').inherit('es-ES', 'Date'); + +/* +--- + +name: Locale.es-AR.Form.Validator + +description: Form Validator messages for Spanish (Argentina). + +license: MIT-style license + +authors: + - Diego Massanti + +requires: + - Locale + +provides: [Locale.es-AR.Form.Validator] + +... +*/ + +Locale.define('es-AR', 'FormValidator', { + + required: 'Este campo es obligatorio.', + minLength: 'Por favor ingrese al menos {minLength} caracteres (ha ingresado {length} caracteres).', + maxLength: 'Por favor no ingrese más de {maxLength} caracteres (ha ingresado {length} caracteres).', + integer: 'Por favor ingrese un número entero en este campo. Números con decimales (p.e. 1,25) no se permiten.', + numeric: 'Por favor ingrese solo valores numéricos en este campo (p.e. "1" o "1,1" o "-1" o "-1,1").', + digits: 'Por favor use sólo números y puntuación en este campo (por ejemplo, un número de teléfono con guiones y/o puntos no está permitido).', + alpha: 'Por favor use sólo letras (a-z) en este campo. No se permiten espacios ni otros caracteres.', + alphanum: 'Por favor, usa sólo letras (a-z) o números (0-9) en este campo. No se permiten espacios u otros caracteres.', + dateSuchAs: 'Por favor ingrese una fecha válida como {date}', + dateInFormatMDY: 'Por favor ingrese una fecha válida, utulizando el formato DD/MM/YYYY (p.e. "31/12/1999")', + email: 'Por favor, ingrese una dirección de e-mail válida. Por ejemplo, "fred@dominio.com".', + url: 'Por favor ingrese una URL válida como http://www.example.com.', + currencyDollar: 'Por favor ingrese una cantidad válida de pesos. Por ejemplo $100,00 .', + oneRequired: 'Por favor ingrese algo para por lo menos una de estas entradas.', + errorPrefix: 'Error: ', + warningPrefix: 'Advertencia: ', + + // Form.Validator.Extras + noSpace: 'No se permiten espacios en este campo.', + reqChkByNode: 'No hay elementos seleccionados.', + requiredChk: 'Este campo es obligatorio.', + reqChkByName: 'Por favor selecciona una {label}.', + match: 'Este campo necesita coincidir con el campo {matchName}', + startDate: 'la fecha de inicio', + endDate: 'la fecha de fin', + currentDate: 'la fecha actual', + afterDate: 'La fecha debe ser igual o posterior a {label}.', + beforeDate: 'La fecha debe ser igual o anterior a {label}.', + startMonth: 'Por favor selecciona un mes de origen', + sameMonth: 'Estas dos fechas deben estar en el mismo mes - debes cambiar una u otra.' + +}); + +/* +--- + +name: Locale.es-ES.Form.Validator + +description: Form Validator messages for Spanish. + +license: MIT-style license + +authors: + - Ãlfons Sanchez + +requires: + - Locale + +provides: [Locale.es-ES.Form.Validator] + +... +*/ + +Locale.define('es-ES', 'FormValidator', { + + required: 'Este campo es obligatorio.', + minLength: 'Por favor introduce al menos {minLength} caracteres (has introducido {length} caracteres).', + maxLength: 'Por favor introduce no más de {maxLength} caracteres (has introducido {length} caracteres).', + integer: 'Por favor introduce un número entero en este campo. Números con decimales (p.e. 1,25) no se permiten.', + numeric: 'Por favor introduce solo valores numéricos en este campo (p.e. "1" o "1,1" o "-1" o "-1,1").', + digits: 'Por favor usa solo números y puntuación en este campo (por ejemplo, un número de teléfono con guiones y puntos no esta permitido).', + alpha: 'Por favor usa letras solo (a-z) en este campo. No se admiten espacios ni otros caracteres.', + alphanum: 'Por favor, usa solo letras (a-z) o números (0-9) en este campo. No se admiten espacios ni otros caracteres.', + dateSuchAs: 'Por favor introduce una fecha válida como {date}', + dateInFormatMDY: 'Por favor introduce una fecha válida como DD/MM/YYYY (p.e. "31/12/1999")', + email: 'Por favor, introduce una dirección de email válida. Por ejemplo, "fred@domain.com".', + url: 'Por favor introduce una URL válida como http://www.example.com.', + currencyDollar: 'Por favor introduce una cantidad válida de â¬. Por ejemplo â¬100,00 .', + oneRequired: 'Por favor introduce algo para por lo menos una de estas entradas.', + errorPrefix: 'Error: ', + warningPrefix: 'Aviso: ', + + // Form.Validator.Extras + noSpace: 'No pueden haber espacios en esta entrada.', + reqChkByNode: 'No hay elementos seleccionados.', + requiredChk: 'Este campo es obligatorio.', + reqChkByName: 'Por favor selecciona una {label}.', + match: 'Este campo necesita coincidir con el campo {matchName}', + startDate: 'la fecha de inicio', + endDate: 'la fecha de fin', + currentDate: 'la fecha actual', + afterDate: 'La fecha debe ser igual o posterior a {label}.', + beforeDate: 'La fecha debe ser igual o anterior a {label}.', + startMonth: 'Por favor selecciona un mes de origen', + sameMonth: 'Estas dos fechas deben estar en el mismo mes - debes cambiar una u otra.' + +}); + +/* +--- + +name: Locale.es-VE.Date + +description: Date messages for Spanish (Venezuela). + +license: MIT-style license + +authors: + - Daniel Barreto + +requires: + - Locale + - Locale.es-ES.Date + +provides: [Locale.es-VE.Date] + +... +*/ + +Locale.define('es-VE').inherit('es-ES', 'Date'); + +/* +--- + +name: Locale.es-VE.Form.Validator + +description: Form Validator messages for Spanish (Venezuela). + +license: MIT-style license + +authors: + - Daniel Barreto + +requires: + - Locale + - Locale.es-ES.Form.Validator + +provides: [Locale.es-VE.Form.Validator] + +... +*/ + +Locale.define('es-VE', 'FormValidator', { + + digits: 'Por favor usa solo números y puntuación en este campo. Por ejemplo, un número de teléfono con guiones y puntos no esta permitido.', + alpha: 'Por favor usa solo letras (a-z) en este campo. No se admiten espacios ni otros caracteres.', + currencyDollar: 'Por favor introduce una cantidad válida de Bs. Por ejemplo Bs. 100,00 .', + oneRequired: 'Por favor introduce un valor para por lo menos una de estas entradas.', + + // Form.Validator.Extras + startDate: 'La fecha de inicio', + endDate: 'La fecha de fin', + currentDate: 'La fecha actual' + +}).inherit('es-ES', 'FormValidator'); + +/* +--- + +name: Locale.es-VE.Number + +description: Number messages for Spanish (Venezuela). + +license: MIT-style license + +authors: + - Daniel Barreto + +requires: + - Locale + +provides: [Locale.es-VE.Number] + +... +*/ + +Locale.define('es-VE', 'Number', { + + decimal: ',', + group: '.', +/* + decimals: 0, + precision: 0, +*/ + // Negative/Currency/percentage will mixin Number + negative: { + prefix: '-' + }, + + currency: { + decimals: 2, + prefix: 'Bs. ' + }, + + percentage: { + decimals: 2, + suffix: '%' + } + +}); + +/* +--- + +name: Locale.et-EE.Date + +description: Date messages for Estonian. + +license: MIT-style license + +authors: + - Kevin Valdek + +requires: + - Locale + +provides: [Locale.et-EE.Date] + +... +*/ + +Locale.define('et-EE', 'Date', { + + months: ['jaanuar', 'veebruar', 'mÀrts', 'aprill', 'mai', 'juuni', 'juuli', 'august', 'september', 'oktoober', 'november', 'detsember'], + months_abbr: ['jaan', 'veebr', 'mÀrts', 'apr', 'mai', 'juuni', 'juuli', 'aug', 'sept', 'okt', 'nov', 'dets'], + days: ['pÃŒhapÀev', 'esmaspÀev', 'teisipÀev', 'kolmapÀev', 'neljapÀev', 'reede', 'laupÀev'], + days_abbr: ['pÃŒhap', 'esmasp', 'teisip', 'kolmap', 'neljap', 'reede', 'laup'], + + // Culture's date order: MM.DD.YYYY + dateOrder: ['month', 'date', 'year'], + shortDate: '%m.%d.%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: 'vÀhem kui minut aega tagasi', + minuteAgo: 'umbes minut aega tagasi', + minutesAgo: '{delta} minutit tagasi', + hourAgo: 'umbes tund aega tagasi', + hoursAgo: 'umbes {delta} tundi tagasi', + dayAgo: '1 pÀev tagasi', + daysAgo: '{delta} pÀeva tagasi', + weekAgo: '1 nÀdal tagasi', + weeksAgo: '{delta} nÀdalat tagasi', + monthAgo: '1 kuu tagasi', + monthsAgo: '{delta} kuud tagasi', + yearAgo: '1 aasta tagasi', + yearsAgo: '{delta} aastat tagasi', + + lessThanMinuteUntil: 'vÀhem kui minuti aja pÀrast', + minuteUntil: 'umbes minuti aja pÀrast', + minutesUntil: '{delta} minuti pÀrast', + hourUntil: 'umbes tunni aja pÀrast', + hoursUntil: 'umbes {delta} tunni pÀrast', + dayUntil: '1 pÀeva pÀrast', + daysUntil: '{delta} pÀeva pÀrast', + weekUntil: '1 nÀdala pÀrast', + weeksUntil: '{delta} nÀdala pÀrast', + monthUntil: '1 kuu pÀrast', + monthsUntil: '{delta} kuu pÀrast', + yearUntil: '1 aasta pÀrast', + yearsUntil: '{delta} aasta pÀrast' + +}); + +/* +--- + +name: Locale.et-EE.Form.Validator + +description: Form Validator messages for Estonian. + +license: MIT-style license + +authors: + - Kevin Valdek + +requires: + - Locale + +provides: [Locale.et-EE.Form.Validator] + +... +*/ + +Locale.define('et-EE', 'FormValidator', { + + required: 'VÀli peab olema tÀidetud.', + minLength: 'Palun sisestage vÀhemalt {minLength} tÀhte (te sisestasite {length} tÀhte).', + maxLength: 'Palun Àrge sisestage rohkem kui {maxLength} tÀhte (te sisestasite {length} tÀhte).', + integer: 'Palun sisestage vÀljale tÀisarv. KÃŒmnendarvud (nÀiteks 1.25) ei ole lubatud.', + numeric: 'Palun sisestage ainult numbreid vÀljale (nÀiteks "1", "1.1", "-1" või "-1.1").', + digits: 'Palun kasutage ainult numbreid ja kirjavahemÀrke (telefoninumbri sisestamisel on lubatud kasutada kriipse ja punkte).', + alpha: 'Palun kasutage ainult tÀhti (a-z). TÃŒhikud ja teised sÃŒmbolid on keelatud.', + alphanum: 'Palun kasutage ainult tÀhti (a-z) või numbreid (0-9). TÃŒhikud ja teised sÃŒmbolid on keelatud.', + dateSuchAs: 'Palun sisestage kehtiv kuupÀev kujul {date}', + dateInFormatMDY: 'Palun sisestage kehtiv kuupÀev kujul MM.DD.YYYY (nÀiteks: "12.31.1999").', + email: 'Palun sisestage kehtiv e-maili aadress (nÀiteks: "fred@domain.com").', + url: 'Palun sisestage kehtiv URL (nÀiteks: http://www.example.com).', + currencyDollar: 'Palun sisestage kehtiv $ summa (nÀiteks: $100.00).', + oneRequired: 'Palun sisestage midagi vÀhemalt ÃŒhele antud vÀljadest.', + errorPrefix: 'Viga: ', + warningPrefix: 'Hoiatus: ', + + // Form.Validator.Extras + noSpace: 'VÀli ei tohi sisaldada tÃŒhikuid.', + reqChkByNode: 'Ãkski vÀljadest pole valitud.', + requiredChk: 'VÀlja tÀitmine on vajalik.', + reqChkByName: 'Palun valige ÃŒks {label}.', + match: 'VÀli peab sobima {matchName} vÀljaga', + startDate: 'algkuupÀev', + endDate: 'lõppkuupÀev', + currentDate: 'praegune kuupÀev', + afterDate: 'KuupÀev peab olema võrdne või pÀrast {label}.', + beforeDate: 'KuupÀev peab olema võrdne või enne {label}.', + startMonth: 'Palun valige algkuupÀev.', + sameMonth: 'Antud kaks kuupÀeva peavad olema samas kuus - peate muutma ÃŒhte kuupÀeva.' + +}); + +/* +--- + +name: Locale.fa.Date + +description: Date messages for Persian. + +license: MIT-style license + +authors: + - Amir Hossein Hodjaty Pour + +requires: + - Locale + +provides: [Locale.fa.Date] + +... +*/ + +Locale.define('fa', 'Date', { + + months: ['ÚØ§ÙÙÛÙ', 'ÙÙØ±ÛÙ', 'Ù
ارس', 'آٟرÛÙ', 'Ù
Ù', 'ÚÙØŠÙ', 'ÚÙØŠÛÙ', 'Ø¢Ú¯ÙØ³Øª', 'سٟتاÙ
ؚر', 'اکتؚر', 'ÙÙØ§Ù
ؚر', 'دساÙ
ؚر'], + months_abbr: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'], + days: ['ÛÚ©ØŽÙØšÙ', 'Ø¯ÙØŽÙØšÙ', 'Ø³Ù ØŽÙØšÙ', 'ÚÙØ§Ø±ØŽÙØšÙ', 'ÙŸÙØ¬ØŽÙØšÙ', 'جÙ
عÙ', 'ØŽÙØšÙ'], + days_abbr: ['Ù', 'د', 'س', 'Ú', 'ÙŸ', 'ج', 'ØŽ'], + + // Culture's date order: MM/DD/YYYY + dateOrder: ['month', 'date', 'year'], + shortDate: '%m/%d/%Y', + shortTime: '%I:%M%p', + AM: 'Ù.Øž', + PM: 'Øš.Øž', + + // Date.Extras + ordinal: 'اÙ
', + + lessThanMinuteAgo: 'Ú©Ù
تر از ÛÚ© دÙÛÙÙ ÙŸÛØŽ', + minuteAgo: 'ØØ¯Ùد ÛÚ© دÙÛÙÙ ÙŸÛØŽ', + minutesAgo: '{delta} دÙÛÙÙ ÙŸÛØŽ', + hourAgo: 'ØØ¯Ùد ÛÚ© ساعت ÙŸÛØŽ', + hoursAgo: 'ØØ¯Ùد {delta} ساعت ÙŸÛØŽ', + dayAgo: '1 Ø±ÙØ² ÙŸÛØŽ', + daysAgo: '{delta} Ø±ÙØ² ÙŸÛØŽ', + weekAgo: '1 ÙÙØªÙ ÙŸÛØŽ', + weeksAgo: '{delta} ÙÙØªÙ ÙŸÛØŽ', + monthAgo: '1 Ù
Ø§Ù ÙŸÛØŽ', + monthsAgo: '{delta} Ù
Ø§Ù ÙŸÛØŽ', + yearAgo: '1 Ø³Ø§Ù ÙŸÛØŽ', + yearsAgo: '{delta} Ø³Ø§Ù ÙŸÛØŽ', + + lessThanMinuteUntil: 'Ú©Ù
تر از ÛÚ© دÙÛÙ٠از ØØ§Ùا', + minuteUntil: 'ØØ¯Ùد ÛÚ© دÙÛÙ٠از ØØ§Ùا', + minutesUntil: '{delta} دÙÛÙ٠از ØØ§Ùا', + hourUntil: 'ØØ¯Ùد ÛÚ© ساعت از ØØ§Ùا', + hoursUntil: 'ØØ¯Ùد {delta} ساعت از ØØ§Ùا', + dayUntil: '1 Ø±ÙØ² از ØØ§Ùا', + daysUntil: '{delta} Ø±ÙØ² از ØØ§Ùا', + weekUntil: '1 ÙÙØªÙ از ØØ§Ùا', + weeksUntil: '{delta} ÙÙØªÙ از ØØ§Ùا', + monthUntil: '1 Ù
ا٠از ØØ§Ùا', + monthsUntil: '{delta} Ù
ا٠از ØØ§Ùا', + yearUntil: '1 سا٠از ØØ§Ùا', + yearsUntil: '{delta} سا٠از ØØ§Ùا' + +}); + +/* +--- + +name: Locale.fa.Form.Validator + +description: Form Validator messages for Persian. + +license: MIT-style license + +authors: + - Amir Hossein Hodjaty Pour + +requires: + - Locale + +provides: [Locale.fa.Form.Validator] + +... +*/ + +Locale.define('fa', 'FormValidator', { + + required: 'اÛÙ ÙÛÙØ¯ Ø§ÙØ²Ø§Ù
Û Ø§Ø³Øª.', + minLength: 'ØŽÙ
ا ØšØ§ÛØ¯ ØØ¯Ø§ÙÙ {minLength} ØØ±Ù ÙØ§Ø±Ø¯ Ú©ÙÛØ¯ ({length} ØØ±Ù ÙØ§Ø±Ø¯ Ú©Ø±Ø¯Ù Ø§ÛØ¯).', + maxLength: 'ÙØ·Ùا ØØ¯Ø§Ú©Ø«Ø± {maxLength} ØØ±Ù ÙØ§Ø±Ø¯ Ú©ÙÛØ¯ (ØŽÙ
ا {length} ØØ±Ù ÙØ§Ø±Ø¯ Ú©Ø±Ø¯Ù Ø§ÛØ¯).', + integer: 'ÙØ·Ùا از عدد صØÛØ Ø§Ø³ØªÙØ§Ø¯Ù Ú©ÙÛØ¯. اعداد Ø§Ø¹ØŽØ§Ø±Û (Ù
اÙÙØ¯ 1.25) Ù
جاز ÙÛØ³ØªÙد.', + numeric: 'ÙØ·Ùا ÙÙØ· Ø¯Ø§Ø¯Ù Ø¹Ø¯Ø¯Û ÙØ§Ø±Ø¯ Ú©ÙÛØ¯ (Ù
اÙÙØ¯ "1" ÛØ§ "1.1" ÛØ§ "1-" ÛØ§ "1.1-").', + digits: 'ÙØ·Ùا ÙÙØ· از اعداد Ù Ø¹ÙØ§Ù
ØªÙØ§ در اÛÙ ÙÛÙØ¯ Ø§Ø³ØªÙØ§Ø¯Ù Ú©ÙÛØ¯ (ØšØ±Ø§Û Ù
ثا٠؎Ù
ار٠تÙÙ٠ؚا خط ØªÛØ±Ù Ù ÙÙØ·Ù ÙØ§ØšÙ ÙØšÙ٠است).', + alpha: 'ÙØ·Ùا ÙÙØ· از ØØ±Ù٠اÙÙØšØ§Ø¡ ØšØ±Ø§Û Ø§Û٠ؚخ؎ Ø§Ø³ØªÙØ§Ø¯Ù Ú©ÙÛØ¯. Ú©Ø§Ø±Ø§Ú©ØªØ±ÙØ§Û دÛگر Ù ÙØ§ØµÙÙ Ù
جاز ÙÛØ³ØªÙد.', + alphanum: 'ÙØ·Ùا ÙÙØ· از ØØ±Ù٠اÙÙØšØ§Ø¡ ٠اعداد در اÛ٠ؚخ؎ Ø§Ø³ØªÙØ§Ø¯Ù Ú©ÙÛØ¯. Ú©Ø§Ø±Ø§Ú©ØªØ±ÙØ§Û دÛگر Ù ÙØ§ØµÙÙ Ù
جاز ÙÛØ³ØªÙد.', + dateSuchAs: 'ÙØ·Ùا ÛÚ© ØªØ§Ø±ÛØ® Ù
عتؚر Ù
اÙÙØ¯ {date} ÙØ§Ø±Ø¯ Ú©ÙÛØ¯.', + dateInFormatMDY: 'ÙØ·Ùا ÛÚ© ØªØ§Ø±ÛØ® Ù
عتؚر ØšÙ ØŽÚ©Ù MM/DD/YYYY ÙØ§Ø±Ø¯ Ú©ÙÛØ¯ (Ù
اÙÙØ¯ "12/31/1999").', + email: 'ÙØ·Ùا ÛÚ© آدرس اÛÙ
ÛÙ Ù
عتؚر ÙØ§Ø±Ø¯ Ú©ÙÛØ¯. ØšØ±Ø§Û Ù
ثا٠"fred@domain.com".', + url: 'ÙØ·Ùا ÛÚ© URL Ù
عتؚر Ù
اÙÙØ¯ http://www.example.com ÙØ§Ø±Ø¯ Ú©ÙÛØ¯.', + currencyDollar: 'ÙØ·Ùا ÛÚ© Ù
ØØ¯Ùد٠Ù
عتؚر ØšØ±Ø§Û Ø§Û٠ؚخ؎ ÙØ§Ø±Ø¯ Ú©ÙÛØ¯ Ù
اÙÙØ¯ 100.00$ .', + oneRequired: 'ÙØ·Ùا ØØ¯Ø§ÙÙ ÛÚ©Û Ø§Ø² ÙÛÙØ¯Ùا را ٟر Ú©ÙÛØ¯.', + errorPrefix: 'خطا: ', + warningPrefix: 'ÙØŽØ¯Ø§Ø±: ', + + // Form.Validator.Extras + noSpace: 'Ø§Ø³ØªÙØ§Ø¯Ù از ÙØ§ØµÙ٠در اÛ٠ؚخ؎ Ù
جاز ÙÛØ³Øª.', + reqChkByNode: 'Ù
ÙØ±Ø¯Û Ø§ÙØªØ®Ø§Øš ÙØŽØ¯Ù است.', + requiredChk: 'اÛÙ ÙÛÙØ¯ Ø§ÙØ²Ø§Ù
Û Ø§Ø³Øª.', + reqChkByName: 'ÙØ·Ùا ÛÚ© {label} را Ø§ÙØªØ®Ø§Øš Ú©ÙÛØ¯.', + match: 'اÛÙ ÙÛÙØ¯ ØšØ§ÛØ¯ ؚا ÙÛÙØ¯ {matchName} Ù
Ø·Ø§ØšÙØª دا؎ت٠ؚا؎د.', + startDate: 'ØªØ§Ø±ÛØ® ØŽØ±ÙØ¹', + endDate: 'ØªØ§Ø±ÛØ® ÙŸØ§ÛØ§Ù', + currentDate: 'ØªØ§Ø±ÛØ® Ú©ÙÙÙÛ', + afterDate: 'ØªØ§Ø±ÛØ® Ù
ÛØšØ§Ûست ؚراؚر ÛØ§ ؚعد از {label} ؚا؎د', + beforeDate: 'ØªØ§Ø±ÛØ® Ù
ÛØšØ§Ûست ؚراؚر ÛØ§ ÙØšÙ از {label} ؚا؎د', + startMonth: 'ÙØ·Ùا Ù
Ø§Ù ØŽØ±ÙØ¹ را Ø§ÙØªØ®Ø§Øš Ú©ÙÛØ¯', + sameMonth: 'اÛÙ Ø¯Ù ØªØ§Ø±ÛØ® ØšØ§ÛØ¯ در ÛÚ© Ù
Ø§Ù ØšØ§ØŽÙØ¯ - ØŽÙ
ا ØšØ§ÛØ¯ ÛÚ©Û ÛØ§ ÙØ± د٠را تغÛÛØ± دÙÛØ¯.', + creditcard: 'ØŽÙ
ار٠کارت Ø§Ø¹ØªØšØ§Ø±Û Ú©Ù ÙØ§Ø±Ø¯ Ú©Ø±Ø¯Ù Ø§ÛØ¯ Ù
عتؚر ÙÛØ³Øª. ÙØ·Ùا ØŽÙ
ار٠را ØšØ±Ø±Ø³Û Ú©ÙÛØ¯ Ù Ù
جددا ØªÙØ§ØŽ Ú©ÙÛØ¯. {length} رÙÙ
ÙØ§Ø±Ø¯ ؎د٠است.' + +}); + +/* +--- + +name: Locale.fi-FI.Date + +description: Date messages for Finnish. + +license: MIT-style license + +authors: + - ksel + +requires: + - Locale + +provides: [Locale.fi-FI.Date] + +... +*/ + +Locale.define('fi-FI', 'Date', { + + // NOTE: months and days are not capitalized in finnish + months: ['tammikuu', 'helmikuu', 'maaliskuu', 'huhtikuu', 'toukokuu', 'kesÀkuu', 'heinÀkuu', 'elokuu', 'syyskuu', 'lokakuu', 'marraskuu', 'joulukuu'], + + // these abbreviations are really not much used in finnish because they obviously won't abbreviate very much. ;) + // NOTE: sometimes one can see forms such as "tammi", "helmi", etc. but that is not proper finnish. + months_abbr: ['tammik.', 'helmik.', 'maalisk.', 'huhtik.', 'toukok.', 'kesÀk.', 'heinÀk.', 'elok.', 'syysk.', 'lokak.', 'marrask.', 'jouluk.'], + + days: ['sunnuntai', 'maanantai', 'tiistai', 'keskiviikko', 'torstai', 'perjantai', 'lauantai'], + days_abbr: ['su', 'ma', 'ti', 'ke', 'to', 'pe', 'la'], + + // Culture's date order: DD/MM/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d.%m.%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '.', + + lessThanMinuteAgo: 'vajaa minuutti sitten', + minuteAgo: 'noin minuutti sitten', + minutesAgo: '{delta} minuuttia sitten', + hourAgo: 'noin tunti sitten', + hoursAgo: 'noin {delta} tuntia sitten', + dayAgo: 'pÀivÀ sitten', + daysAgo: '{delta} pÀivÀÀ sitten', + weekAgo: 'viikko sitten', + weeksAgo: '{delta} viikkoa sitten', + monthAgo: 'kuukausi sitten', + monthsAgo: '{delta} kuukautta sitten', + yearAgo: 'vuosi sitten', + yearsAgo: '{delta} vuotta sitten', + + lessThanMinuteUntil: 'vajaan minuutin kuluttua', + minuteUntil: 'noin minuutin kuluttua', + minutesUntil: '{delta} minuutin kuluttua', + hourUntil: 'noin tunnin kuluttua', + hoursUntil: 'noin {delta} tunnin kuluttua', + dayUntil: 'pÀivÀn kuluttua', + daysUntil: '{delta} pÀivÀn kuluttua', + weekUntil: 'viikon kuluttua', + weeksUntil: '{delta} viikon kuluttua', + monthUntil: 'kuukauden kuluttua', + monthsUntil: '{delta} kuukauden kuluttua', + yearUntil: 'vuoden kuluttua', + yearsUntil: '{delta} vuoden kuluttua' + +}); + +/* +--- + +name: Locale.fi-FI.Form.Validator + +description: Form Validator messages for Finnish. + +license: MIT-style license + +authors: + - ksel + +requires: + - Locale + +provides: [Locale.fi-FI.Form.Validator] + +... +*/ + +Locale.define('fi-FI', 'FormValidator', { + + required: 'TÀmÀ kenttÀ on pakollinen.', + minLength: 'Ole hyvÀ ja anna vÀhintÀÀn {minLength} merkkiÀ (annoit {length} merkkiÀ).', + maxLength: 'ÃlÀ anna enempÀÀ kuin {maxLength} merkkiÀ (annoit {length} merkkiÀ).', + integer: 'Ole hyvÀ ja anna kokonaisluku. Luvut, joissa on desimaaleja (esim. 1.25) eivÀt ole sallittuja.', + numeric: 'Anna tÀhÀn kenttÀÀn lukuarvo (kuten "1" tai "1.1" tai "-1" tai "-1.1").', + digits: 'KÀytÀ pelkÀstÀÀn numeroita ja vÀlimerkkejÀ tÀssÀ kentÀssÀ (syötteet, kuten esim. puhelinnumero, jossa on vÀliviivoja, pilkkuja tai pisteitÀ, kelpaa).', + alpha: 'Anna tÀhÀn kenttÀÀn vain kirjaimia (a-z). VÀlilyönnit tai muut merkit eivÀt ole sallittuja.', + alphanum: 'Anna tÀhÀn kenttÀÀn vain kirjaimia (a-z) tai numeroita (0-9). VÀlilyönnit tai muut merkit eivÀt ole sallittuja.', + dateSuchAs: 'Ole hyvÀ ja anna kelvollinen pÀivmÀÀrÀ, kuten esimerkiksi {date}', + dateInFormatMDY: 'Ole hyvÀ ja anna kelvollinen pÀivÀmÀÀrÀ muodossa pp/kk/vvvv (kuten "12/31/1999")', + email: 'Ole hyvÀ ja anna kelvollinen sÀhköpostiosoite (kuten esimerkiksi "matti@meikalainen.com").', + url: 'Ole hyvÀ ja anna kelvollinen URL, kuten esimerkiksi http://www.example.com.', + currencyDollar: 'Ole hyvÀ ja anna kelvollinen eurosumma (kuten esimerkiksi 100,00 EUR) .', + oneRequired: 'Ole hyvÀ ja syötÀ jotakin ainakin johonkin nÀistÀ kentistÀ.', + errorPrefix: 'Virhe: ', + warningPrefix: 'Varoitus: ', + + // Form.Validator.Extras + noSpace: 'TÀssÀ syötteessÀ ei voi olla vÀlilyöntejÀ', + reqChkByNode: 'Ei valintoja.', + requiredChk: 'TÀmÀ kenttÀ on pakollinen.', + reqChkByName: 'Ole hyvÀ ja valitse {label}.', + match: 'TÀmÀn kentÀn tulee vastata kenttÀÀ {matchName}', + startDate: 'alkupÀivÀmÀÀrÀ', + endDate: 'loppupÀivÀmÀÀrÀ', + currentDate: 'nykyinen pÀivÀmÀÀrÀ', + afterDate: 'PÀivÀmÀÀrÀn tulisi olla sama tai myöhÀisempi ajankohta kuin {label}.', + beforeDate: 'PÀivÀmÀÀrÀn tulisi olla sama tai aikaisempi ajankohta kuin {label}.', + startMonth: 'Ole hyvÀ ja valitse aloituskuukausi', + sameMonth: 'NÀiden kahden pÀivÀmÀÀrÀn tulee olla saman kuun sisÀllÀ -- sinun pitÀÀ muuttaa jompaa kumpaa.', + creditcard: 'Annettu luottokortin numero ei kelpaa. Ole hyvÀ ja tarkista numero sekÀ yritÀ uudelleen. {length} numeroa syötetty.' + +}); + +/* +--- + +name: Locale.fi-FI.Number + +description: Finnish number messages + +license: MIT-style license + +authors: + - ksel + +requires: + - Locale + - Locale.EU.Number + +provides: [Locale.fi-FI.Number] + +... +*/ + +Locale.define('fi-FI', 'Number', { + + group: ' ' // grouped by space + +}).inherit('EU', 'Number'); + +/* +--- + +name: Locale.fr-FR.Date + +description: Date messages for French. + +license: MIT-style license + +authors: + - Nicolas Sorosac + - Antoine Abt + +requires: + - Locale + +provides: [Locale.fr-FR.Date] + +... +*/ + +Locale.define('fr-FR', 'Date', { + + months: ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'], + months_abbr: ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sept.', 'oct.', 'nov.', 'déc.'], + days: ['Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi'], + days_abbr: ['dim.', 'lun.', 'mar.', 'mer.', 'jeu.', 'ven.', 'sam.'], + + // Culture's date order: DD/MM/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d/%m/%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: function(dayOfMonth){ + return (dayOfMonth > 1) ? '' : 'er'; + }, + + lessThanMinuteAgo: "il y a moins d'une minute", + minuteAgo: 'il y a une minute', + minutesAgo: 'il y a {delta} minutes', + hourAgo: 'il y a une heure', + hoursAgo: 'il y a {delta} heures', + dayAgo: 'il y a un jour', + daysAgo: 'il y a {delta} jours', + weekAgo: 'il y a une semaine', + weeksAgo: 'il y a {delta} semaines', + monthAgo: 'il y a 1 mois', + monthsAgo: 'il y a {delta} mois', + yearthAgo: 'il y a 1 an', + yearsAgo: 'il y a {delta} ans', + + lessThanMinuteUntil: "dans moins d'une minute", + minuteUntil: 'dans une minute', + minutesUntil: 'dans {delta} minutes', + hourUntil: 'dans une heure', + hoursUntil: 'dans {delta} heures', + dayUntil: 'dans un jour', + daysUntil: 'dans {delta} jours', + weekUntil: 'dans 1 semaine', + weeksUntil: 'dans {delta} semaines', + monthUntil: 'dans 1 mois', + monthsUntil: 'dans {delta} mois', + yearUntil: 'dans 1 an', + yearsUntil: 'dans {delta} ans' + +}); + +/* +--- + +name: Locale.fr-FR.Form.Validator + +description: Form Validator messages for French. + +license: MIT-style license + +authors: + - Miquel Hudin + - Nicolas Sorosac + +requires: + - Locale + +provides: [Locale.fr-FR.Form.Validator] + +... +*/ + +Locale.define('fr-FR', 'FormValidator', { + + required: 'Ce champ est obligatoire.', + length: 'Veuillez saisir {length} caractère(s) (vous avez saisi {elLength} caractère(s)', + minLength: 'Veuillez saisir un minimum de {minLength} caractère(s) (vous avez saisi {length} caractère(s)).', + maxLength: 'Veuillez saisir un maximum de {maxLength} caractère(s) (vous avez saisi {length} caractère(s)).', + integer: 'Veuillez saisir un nombre entier dans ce champ. Les nombres décimaux (ex : "1,25") ne sont pas autorisés.', + numeric: 'Veuillez saisir uniquement des chiffres dans ce champ (ex : "1" ou "1,1" ou "-1" ou "-1,1").', + digits: "Veuillez saisir uniquement des chiffres et des signes de ponctuation dans ce champ (ex : un numéro de téléphone avec des traits d'union est autorisé).", + alpha: 'Veuillez saisir uniquement des lettres (a-z) dans ce champ. Les espaces ou autres caractères ne sont pas autorisés.', + alphanum: 'Veuillez saisir uniquement des lettres (a-z) ou des chiffres (0-9) dans ce champ. Les espaces ou autres caractères ne sont pas autorisés.', + dateSuchAs: 'Veuillez saisir une date correcte comme {date}', + dateInFormatMDY: 'Veuillez saisir une date correcte, au format JJ/MM/AAAA (ex : "31/11/1999").', + email: 'Veuillez saisir une adresse de courrier électronique. Par exemple "fred@domaine.com".', + url: 'Veuillez saisir une URL, comme http://www.exemple.com.', + currencyDollar: 'Veuillez saisir une quantité correcte. Par exemple 100,00€.', + oneRequired: 'Veuillez sélectionner au moins une de ces options.', + errorPrefix: 'Erreur : ', + warningPrefix: 'Attention : ', + + // Form.Validator.Extras + noSpace: "Ce champ n'accepte pas les espaces.", + reqChkByNode: "Aucun élément n'est sélectionné.", + requiredChk: 'Ce champ est obligatoire.', + reqChkByName: 'Veuillez sélectionner un(e) {label}.', + match: 'Ce champ doit correspondre avec le champ {matchName}.', + startDate: 'date de début', + endDate: 'date de fin', + currentDate: 'date actuelle', + afterDate: 'La date doit être identique ou postérieure à {label}.', + beforeDate: 'La date doit être identique ou antérieure à {label}.', + startMonth: 'Veuillez sélectionner un mois de début.', + sameMonth: 'Ces deux dates doivent être dans le même mois - vous devez en modifier une.', + creditcard: 'Le numéro de carte de crédit est invalide. Merci de vérifier le numéro et de réessayer. Vous avez entré {length} chiffre(s).' + +}); + +/* +--- + +name: Locale.fr-FR.Number + +description: Number messages for French. + +license: MIT-style license + +authors: + - Arian Stolwijk + - sv1l + +requires: + - Locale + - Locale.EU.Number + +provides: [Locale.fr-FR.Number] + +... +*/ + +Locale.define('fr-FR', 'Number', { + + group: ' ' // In fr-FR localization, group character is a blank space + +}).inherit('EU', 'Number'); + +/* +--- + +name: Locale.he-IL.Date + +description: Date messages for Hebrew. + +license: MIT-style license + +authors: + - Elad Ossadon + +requires: + - Locale + +provides: [Locale.he-IL.Date] + +... +*/ + +Locale.define('he-IL', 'Date', { + + months: ['×× ××ך', '×€×ך××ך', '×ךץ', '×׀ך××', '×××', '××× ×', '××××', '××××ס×', 'ס׀×××ך', '×××§×××ך', '× ××××ך', '×׊××ך'], + months_abbr: ['×× ××ך', '×€×ך××ך', '×ךץ', '×׀ך××', '×××', '××× ×', '××××', '××××ס×', 'ס׀×××ך', '×××§×××ך', '× ××××ך', '×׊××ך'], + days: ['ך×ש××', '×©× ×', 'ש××ש×', 'ך×××¢×', '×××ש×', 'ש×ש×', 'ש×ת'], + days_abbr: ['ך×ש××', '×©× ×', 'ש××ש×', 'ך×××¢×', '×××ש×', 'ש×ש×', 'ש×ת'], + + // Culture's date order: MM/DD/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d/%m/%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 0, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: '××€× × ×€××ת ×××§×', + minuteAgo: '××€× × ×××§×', + minutesAgo: '××€× × {delta} ××§×ת', + hourAgo: '××€× × ×שע×', + hoursAgo: '××€× × {delta} שע×ת', + dayAgo: '××€× × ×××', + daysAgo: '××€× × {delta} ××××', + weekAgo: '××€× × ×©×××¢', + weeksAgo: '××€× × {delta} ש×××¢×ת', + monthAgo: '××€× × ×××ש', + monthsAgo: '××€× × {delta} ×××ש××', + yearAgo: '××€× × ×©× ×', + yearsAgo: '××€× × {delta} ×©× ××', + + lessThanMinuteUntil: '××¢×× ×€××ת ×××§×', + minuteUntil: '××¢×× ×××§×', + minutesUntil: '××¢×× {delta} ××§×ת', + hourUntil: '××¢×× ×שע×', + hoursUntil: '××¢×× {delta} שע×ת', + dayUntil: '××¢×× ×××', + daysUntil: '××¢×× {delta} ××××', + weekUntil: '××¢×× ×©×××¢', + weeksUntil: '××¢×× {delta} ש×××¢×ת', + monthUntil: '××¢×× ×××ש', + monthsUntil: '××¢×× {delta} ×××ש××', + yearUntil: '××¢×× ×©× ×', + yearsUntil: '××¢×× {delta} ×©× ××' + +}); + +/* +--- + +name: Locale.he-IL.Form.Validator + +description: Form Validator messages for Hebrew. + +license: MIT-style license + +authors: + - Elad Ossadon + +requires: + - Locale + +provides: [Locale.he-IL.Form.Validator] + +... +*/ + +Locale.define('he-IL', 'FormValidator', { + + required: '× × ×××× ×©×× ××.', + minLength: '× × ××××× ××€××ת {minLength} ת×××× (××× ×ª {length} ת××××).', + maxLength: '× × ××××× ×¢× {maxLength} ת×××× (××× ×ª {length} ת××××).', + integer: '× × ××××× ×ס׀ך ש×× ×ש×× ××. ×ס׀ך×× ×¢×©×š×× ××× (××× 1.25) ××× × ×××§×××.', + numeric: '× × ××××× ×¢×š× ××¡×€×š× ×××× ×ש×× ×× (××× "1", "1.1", "-1" ×× "-1.1").', + digits: '× × ××××× ×š×§ ס׀ך×ת ×ס××× × ×׀ך×× ×ש×× ×× (××ש×, ×ס׀ך ×××€×× ×¢× ××§×€×× ×× × ×§×××ת ××× ×××§×).', + alpha: '× × ××××× ×š×§ ××ת××ת ××× ×××ת (a-z) ×ש×× ××. ך××××× ×× ×ª×××× ××ך×× ××× × ×××§×××.', + alphanum: '× × ××××× ×š×§ ××תך××ת ××× ×××ת (a-z) ×× ×¡×€×š×ת (0-9) ×ש×× ××. ××××ך×× ×× ×ª×××× ××ך×× ××× × ×××§×××.', + dateSuchAs: '× × ××××× ×ª×ך×× ×××§×, ××× {date}', + dateInFormatMDY: '× × ××××× ×ª×ך×× ×××§× ××€×ך×× MM/DD/YYYY (××× "12/31/1999")', + email: '× × ××××× ×ת××ת ×××××× ×××§×ת. ××××××: "fred@domain.com".', + url: '× × ××××× ×ת××ת ×תך ×××§×ת, ××× http://www.example.com.', + currencyDollar: '× × ××××× ×¡××× ××××š× ×××§×. ×××××× $100.00.', + oneRequired: '× × ××××ך ××€××ת ×ש×× ×××.', + errorPrefix: 'ש××××: ', + warningPrefix: '×××ך×: ', + + // Form.Validator.Extras + noSpace: '××× ××××× ×š××××× ×ש×× ××.', + reqChkByNode: '× × ××××ך ××ת ×××׀שך×××ת.', + requiredChk: 'ש×× ×× × ×ךש.', + reqChkByName: '× × ××××ך {label}.', + match: 'ש×× ×× ×Š×š×× ××ת××× ×ש×× {matchName}', + startDate: 'ת×ך×× ××ת×××', + endDate: 'ת×ך×× ×ס×××', + currentDate: '×ת×ך×× ×× ××××', + afterDate: '×ת×ך×× ×Š×š×× ××××ת ××× ×× ×××š× {label}.', + beforeDate: '×ת×ך×× ×Š×š×× ××××ת ××× ×× ××€× × {label}.', + startMonth: '× × ××××ך ×××ש ×ת×××', + sameMonth: '×©× × ×ª×ך×××× ××× ×Š×š×××× ××××ת ××××ª× ×××ש - × × ××©× ×ת ××× ×ת×ך××××.', + creditcard: '×ס׀ך ×ך××ס ××שך×× ×©×××× ××× × ×××§×. × × ×××××§ ×©× ×ת. ×××× × {length} ס׀ך×ת.' + +}); + +/* +--- + +name: Locale.he-IL.Number + +description: Number messages for Hebrew. + +license: MIT-style license + +authors: + - Elad Ossadon + +requires: + - Locale + +provides: [Locale.he-IL.Number] + +... +*/ + +Locale.define('he-IL', 'Number', { + + decimal: '.', + group: ',', + + currency: { + suffix: ' âª' + } + +}); + +/* +--- + +name: Locale.hu-HU.Date + +description: Date messages for Hungarian. + +license: MIT-style license + +authors: + - Zsolt SzegheÅ + +requires: + - Locale + +provides: [Locale.hu-HU.Date] + +... +*/ + +Locale.define('hu-HU', 'Date', { + + months: ['Január', 'Február', 'Március', 'Ãprilis', 'Május', 'Június', 'Július', 'Augusztus', 'Szeptember', 'Október', 'November', 'December'], + months_abbr: ['jan.', 'febr.', 'márc.', 'ápr.', 'máj.', 'jún.', 'júl.', 'aug.', 'szept.', 'okt.', 'nov.', 'dec.'], + days: ['Vasárnap', 'HétfÅ', 'Kedd', 'Szerda', 'CsÃŒtörtök', 'Péntek', 'Szombat'], + days_abbr: ['V', 'H', 'K', 'Sze', 'Cs', 'P', 'Szo'], + + // Culture's date order: YYYY.MM.DD. + dateOrder: ['year', 'month', 'date'], + shortDate: '%Y.%m.%d.', + shortTime: '%I:%M', + AM: 'de.', + PM: 'du.', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '.', + + lessThanMinuteAgo: 'alig egy perce', + minuteAgo: 'egy perce', + minutesAgo: '{delta} perce', + hourAgo: 'egy órája', + hoursAgo: '{delta} órája', + dayAgo: '1 napja', + daysAgo: '{delta} napja', + weekAgo: '1 hete', + weeksAgo: '{delta} hete', + monthAgo: '1 hónapja', + monthsAgo: '{delta} hónapja', + yearAgo: '1 éve', + yearsAgo: '{delta} éve', + + lessThanMinuteUntil: 'alig egy perc múlva', + minuteUntil: 'egy perc múlva', + minutesUntil: '{delta} perc múlva', + hourUntil: 'egy óra múlva', + hoursUntil: '{delta} óra múlva', + dayUntil: '1 nap múlva', + daysUntil: '{delta} nap múlva', + weekUntil: '1 hét múlva', + weeksUntil: '{delta} hét múlva', + monthUntil: '1 hónap múlva', + monthsUntil: '{delta} hónap múlva', + yearUntil: '1 év múlva', + yearsUntil: '{delta} év múlva' + +}); + +/* +--- + +name: Locale.hu-HU.Form.Validator + +description: Form Validator messages for Hungarian. + +license: MIT-style license + +authors: + - Zsolt SzegheÅ + +requires: + - Locale + +provides: [Locale.hu-HU.Form.Validator] + +... +*/ + +Locale.define('hu-HU', 'FormValidator', { + + required: 'A mezÅ kitöltése kötelezÅ.', + minLength: 'Legalább {minLength} karakter megadása szÃŒkséges (megadva {length} karakter).', + maxLength: 'Legfeljebb {maxLength} karakter megadása lehetséges (megadva {length} karakter).', + integer: 'Egész szám megadása szÃŒkséges. A tizedesjegyek (pl. 1.25) nem engedélyezettek.', + numeric: 'Szám megadása szÃŒkséges (pl. "1" vagy "1.1" vagy "-1" vagy "-1.1").', + digits: 'Csak számok és Ãrásjelek megadása lehetséges (pl. telefonszám kötÅjelek és/vagy perjelekkel).', + alpha: 'Csak betűk (a-z) megadása lehetséges. Szóköz és egyéb karakterek nem engedélyezettek.', + alphanum: 'Csak betűk (a-z) vagy számok (0-9) megadása lehetséges. Szóköz és egyéb karakterek nem engedélyezettek.', + dateSuchAs: 'Valós dátum megadása szÃŒkséges (pl. {date}).', + dateInFormatMDY: 'Valós dátum megadása szÃŒkséges ÃÃÃÃ.HH.NN. formában. (pl. "1999.12.31.")', + email: 'Valós e-mail cÃm megadása szÃŒkséges (pl. "fred@domain.hu").', + url: 'Valós URL megadása szÃŒkséges (pl. http://www.example.com).', + currencyDollar: 'Valós pénzösszeg megadása szÃŒkséges (pl. 100.00 Ft.).', + oneRequired: 'Az alábbi mezÅk legalább egyikének kitöltése kötelezÅ.', + errorPrefix: 'Hiba: ', + warningPrefix: 'Figyelem: ', + + // Form.Validator.Extras + noSpace: 'A mezÅ nem tartalmazhat szóközöket.', + reqChkByNode: 'Nincs egyetlen kijelölt elem sem.', + requiredChk: 'A mezÅ kitöltése kötelezÅ.', + reqChkByName: 'Egy {label} kiválasztása szÃŒkséges.', + match: 'A mezÅnek egyeznie kell a(z) {matchName} mezÅvel.', + startDate: 'a kezdet dátuma', + endDate: 'a vég dátuma', + currentDate: 'jelenlegi dátum', + afterDate: 'A dátum nem lehet kisebb, mint {label}.', + beforeDate: 'A dátum nem lehet nagyobb, mint {label}.', + startMonth: 'Kezdeti hónap megadása szÃŒkséges.', + sameMonth: 'A két dátumnak ugyanazon hónapban kell lennie.', + creditcard: 'A megadott bankkártyaszám nem valódi (megadva {length} számjegy).' + +}); + +/* +--- + +name: Locale.it-IT.Date + +description: Date messages for Italian. + +license: MIT-style license. + +authors: + - Andrea Novero + - Valerio Proietti + +requires: + - Locale + +provides: [Locale.it-IT.Date] + +... +*/ + +Locale.define('it-IT', 'Date', { + + months: ['Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre'], + months_abbr: ['gen', 'feb', 'mar', 'apr', 'mag', 'giu', 'lug', 'ago', 'set', 'ott', 'nov', 'dic'], + days: ['Domenica', 'Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato'], + days_abbr: ['dom', 'lun', 'mar', 'mer', 'gio', 'ven', 'sab'], + + // Culture's date order: DD/MM/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d/%m/%Y', + shortTime: '%H.%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: 'º', + + lessThanMinuteAgo: 'meno di un minuto fa', + minuteAgo: 'circa un minuto fa', + minutesAgo: 'circa {delta} minuti fa', + hourAgo: "circa un'ora fa", + hoursAgo: 'circa {delta} ore fa', + dayAgo: 'circa 1 giorno fa', + daysAgo: 'circa {delta} giorni fa', + weekAgo: 'una settimana fa', + weeksAgo: '{delta} settimane fa', + monthAgo: 'un mese fa', + monthsAgo: '{delta} mesi fa', + yearAgo: 'un anno fa', + yearsAgo: '{delta} anni fa', + + lessThanMinuteUntil: 'tra meno di un minuto', + minuteUntil: 'tra circa un minuto', + minutesUntil: 'tra circa {delta} minuti', + hourUntil: "tra circa un'ora", + hoursUntil: 'tra circa {delta} ore', + dayUntil: 'tra circa un giorno', + daysUntil: 'tra circa {delta} giorni', + weekUntil: 'tra una settimana', + weeksUntil: 'tra {delta} settimane', + monthUntil: 'tra un mese', + monthsUntil: 'tra {delta} mesi', + yearUntil: 'tra un anno', + yearsUntil: 'tra {delta} anni' + +}); + +/* +--- + +name: Locale.it-IT.Form.Validator + +description: Form Validator messages for Italian. + +license: MIT-style license + +authors: + - Leonardo Laureti + - Andrea Novero + +requires: + - Locale + +provides: [Locale.it-IT.Form.Validator] + +... +*/ + +Locale.define('it-IT', 'FormValidator', { + + required: 'Il campo è obbligatorio.', + minLength: 'Inserire almeno {minLength} caratteri (ne sono stati inseriti {length}).', + maxLength: 'Inserire al massimo {maxLength} caratteri (ne sono stati inseriti {length}).', + integer: 'Inserire un numero intero. Non sono consentiti decimali (es.: 1.25).', + numeric: 'Inserire solo valori numerici (es.: "1" oppure "1.1" oppure "-1" oppure "-1.1").', + digits: 'Inserire solo numeri e caratteri di punteggiatura. Per esempio è consentito un numero telefonico con trattini o punti.', + alpha: 'Inserire solo lettere (a-z). Non sono consentiti spazi o altri caratteri.', + alphanum: 'Inserire solo lettere (a-z) o numeri (0-9). Non sono consentiti spazi o altri caratteri.', + dateSuchAs: 'Inserire una data valida del tipo {date}', + dateInFormatMDY: 'Inserire una data valida nel formato MM/GG/AAAA (es.: "12/31/1999")', + email: 'Inserire un indirizzo email valido. Per esempio "nome@dominio.com".', + url: 'Inserire un indirizzo valido. Per esempio "http://www.example.com".', + currencyDollar: 'Inserire un importo valido. Per esempio "$100.00".', + oneRequired: 'Completare almeno uno dei campi richiesti.', + errorPrefix: 'Errore: ', + warningPrefix: 'Attenzione: ', + + // Form.Validator.Extras + noSpace: 'Non sono consentiti spazi.', + reqChkByNode: 'Nessuna voce selezionata.', + requiredChk: 'Il campo è obbligatorio.', + reqChkByName: 'Selezionare un(a) {label}.', + match: 'Il valore deve corrispondere al campo {matchName}', + startDate: "data d'inizio", + endDate: 'data di fine', + currentDate: 'data attuale', + afterDate: 'La data deve corrispondere o essere successiva al {label}.', + beforeDate: 'La data deve corrispondere o essere precedente al {label}.', + startMonth: "Selezionare un mese d'inizio", + sameMonth: 'Le due date devono essere dello stesso mese - occorre modificarne una.' + +}); + +/* +--- + +name: Locale.ja-JP.Date + +description: Date messages for Japanese. + +license: MIT-style license + +authors: + - Noritaka Horio + +requires: + - Locale + +provides: [Locale.ja-JP.Date] + +... +*/ + +Locale.define('ja-JP', 'Date', { + + months: ['1æ', '2æ', '3æ', '4æ', '5æ', '6æ', '7æ', '8æ', '9æ', '10æ', '11æ', '12æ'], + months_abbr: ['1æ', '2æ', '3æ', '4æ', '5æ', '6æ', '7æ', '8æ', '9æ', '10æ', '11æ', '12æ'], + days: ['æ¥ææ¥', 'æææ¥', 'ç«ææ¥', 'æ°Žææ¥', 'æšææ¥', 'éææ¥', 'åææ¥'], + days_abbr: ['æ¥', 'æ', 'ç«', 'æ°Ž', 'æš', 'é', 'å'], + + // Culture's date order: YYYY/MM/DD + dateOrder: ['year', 'month', 'date'], + shortDate: '%Y/%m/%d', + shortTime: '%H:%M', + AM: 'åå', + PM: 'ååŸ', + firstDayOfWeek: 0, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: '1å以å
å', + minuteAgo: 'çŽ1åå', + minutesAgo: 'çŽ{delta}åå', + hourAgo: 'çŽ1æéå', + hoursAgo: 'çŽ{delta}æéå', + dayAgo: '1æ¥å', + daysAgo: '{delta}æ¥å', + weekAgo: '1é±éå', + weeksAgo: '{delta}é±éå', + monthAgo: '1ã¶æå', + monthsAgo: '{delta}ã¶æå', + yearAgo: '1幎å', + yearsAgo: '{delta}幎å', + + lessThanMinuteUntil: 'ä»ããçŽ1å以å
', + minuteUntil: 'ä»ããçŽ1å', + minutesUntil: 'ä»ããçŽ{delta}å', + hourUntil: 'ä»ããçŽ1æé', + hoursUntil: 'ä»ããçŽ{delta}æé', + dayUntil: 'ä»ãã1æ¥é', + daysUntil: 'ä»ãã{delta}æ¥é', + weekUntil: 'ä»ãã1é±é', + weeksUntil: 'ä»ãã{delta}é±é', + monthUntil: 'ä»ãã1ã¶æ', + monthsUntil: 'ä»ãã{delta}ã¶æ', + yearUntil: 'ä»ãã1幎', + yearsUntil: 'ä»ãã{delta}幎' + +}); + +/* +--- + +name: Locale.ja-JP.Form.Validator + +description: Form Validator messages for Japanese. + +license: MIT-style license + +authors: + - Noritaka Horio + +requires: + - Locale + +provides: [Locale.ja-JP.Form.Validator] + +... +*/ + +Locale.define("ja-JP", "FormValidator", { + + required: 'å
¥åã¯å¿
é ã§ãã', + minLength: 'å
¥åæåæ°ã¯{minLength}以äžã«ããŠãã ããã({length}æå)', + maxLength: 'å
¥åæåæ°ã¯{maxLength}以äžã«ããŠãã ããã({length}æå)', + integer: 'æŽæ°ãå
¥åããŠãã ããã', + numeric: 'å
¥åã§ããã®ã¯æ°å€ã ãã§ãã(äŸ: "1", "1.1", "-1", "-1.1"....)', + digits: 'å
¥åã§ããã®ã¯æ°å€ãšå¥èªèšå·ã§ãã (äŸ: -ã+ãå«ãé»è©±çªå·ãªã©).', + alpha: 'å
¥åã§ããã®ã¯åè§è±åã ãã§ãããã以å€ã®æåã¯å
¥åã§ããŸããã', + alphanum: 'å
¥åã§ããã®ã¯åè§è±æ°åã ãã§ãããã以å€ã®æåã¯å
¥åã§ããŸããã', + dateSuchAs: 'æå¹ãªæ¥ä»ãå
¥åããŠãã ããã{date}', + dateInFormatMDY: 'æ¥ä»ã®æžåŒã«èª€ãããããŸããYYYY/MM/DD (i.e. "1999/12/31")', + email: 'ã¡ãŒã«ã¢ãã¬ã¹ã«èª€ãããããŸãã', + url: 'URLã¢ãã¬ã¹ã«èª€ãããããŸãã', + currencyDollar: 'éé¡ã«èª€ãããããŸãã', + oneRequired: 'ã²ãšã€ä»¥äžå
¥åããŠãã ããã', + errorPrefix: 'ãšã©ãŒ: ', + warningPrefix: 'èŠå: ', + + // FormValidator.Extras + noSpace: 'ã¹ããŒã¹ã¯å
¥åã§ããŸããã', + reqChkByNode: 'éžæãããŠããŸããã', + requiredChk: 'ãã®é
ç®ã¯å¿
é ã§ãã', + reqChkByName: '{label}ãéžæããŠãã ããã', + match: '{matchName}ãå
¥åãããŠããå Žåå¿
é ã§ãã', + startDate: 'éå§æ¥', + endDate: 'çµäºæ¥', + currentDate: '仿¥', + afterDate: '{label}以éã®æ¥ä»ã«ããŠãã ããã', + beforeDate: '{label}以åã®æ¥ä»ã«ããŠãã ããã', + startMonth: 'éå§æãéžæããŠãã ããã', + sameMonth: 'æ¥ä»ãåäžã§ããã©ã¡ããã倿ŽããŠãã ããã' + +}); + +/* +--- + +name: Locale.ja-JP.Number + +description: Number messages for Japanese. + +license: MIT-style license + +authors: + - Noritaka Horio + +requires: + - Locale + +provides: [Locale.ja-JP.Number] + +... +*/ + +Locale.define('ja-JP', 'Number', { + + decimal: '.', + group: ',', + + currency: { + decimals: 0, + prefix: '\\' + } + +}); + +/* +--- + +name: Locale.nl-NL.Date + +description: Date messages for Dutch. + +license: MIT-style license + +authors: + - Lennart Pilon + - Tim Wienk + +requires: + - Locale + +provides: [Locale.nl-NL.Date] + +... +*/ + +Locale.define('nl-NL', 'Date', { + + months: ['januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli', 'augustus', 'september', 'oktober', 'november', 'december'], + months_abbr: ['jan', 'feb', 'mrt', 'apr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'], + days: ['zondag', 'maandag', 'dinsdag', 'woensdag', 'donderdag', 'vrijdag', 'zaterdag'], + days_abbr: ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'], + + // Culture's date order: DD-MM-YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d-%m-%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: 'e', + + lessThanMinuteAgo: 'minder dan een minuut geleden', + minuteAgo: 'ongeveer een minuut geleden', + minutesAgo: '{delta} minuten geleden', + hourAgo: 'ongeveer een uur geleden', + hoursAgo: 'ongeveer {delta} uur geleden', + dayAgo: 'een dag geleden', + daysAgo: '{delta} dagen geleden', + weekAgo: 'een week geleden', + weeksAgo: '{delta} weken geleden', + monthAgo: 'een maand geleden', + monthsAgo: '{delta} maanden geleden', + yearAgo: 'een jaar geleden', + yearsAgo: '{delta} jaar geleden', + + lessThanMinuteUntil: 'over minder dan een minuut', + minuteUntil: 'over ongeveer een minuut', + minutesUntil: 'over {delta} minuten', + hourUntil: 'over ongeveer een uur', + hoursUntil: 'over {delta} uur', + dayUntil: 'over ongeveer een dag', + daysUntil: 'over {delta} dagen', + weekUntil: 'over een week', + weeksUntil: 'over {delta} weken', + monthUntil: 'over een maand', + monthsUntil: 'over {delta} maanden', + yearUntil: 'over een jaar', + yearsUntil: 'over {delta} jaar' + +}); + +/* +--- + +name: Locale.nl-NL.Form.Validator + +description: Form Validator messages for Dutch. + +license: MIT-style license + +authors: + - Lennart Pilon + - Arian Stolwijk + - Tim Wienk + +requires: + - Locale + +provides: [Locale.nl-NL.Form.Validator] + +... +*/ + +Locale.define('nl-NL', 'FormValidator', { + + required: 'Dit veld is verplicht.', + length: 'Vul precies {length} karakters in (je hebt {elLength} karakters ingevoerd).', + minLength: 'Vul minimaal {minLength} karakters in (je hebt {length} karakters ingevoerd).', + maxLength: 'Vul niet meer dan {maxLength} karakters in (je hebt {length} karakters ingevoerd).', + integer: 'Vul een getal in. Getallen met decimalen (bijvoorbeeld 1.25) zijn niet toegestaan.', + numeric: 'Vul alleen numerieke waarden in (bijvoorbeeld "1" of "1.1" of "-1" of "-1.1").', + digits: 'Vul alleen nummers en leestekens in (bijvoorbeeld een telefoonnummer met streepjes is toegestaan).', + alpha: 'Vul alleen letters in (a-z). Spaties en andere karakters zijn niet toegestaan.', + alphanum: 'Vul alleen letters (a-z) of nummers (0-9) in. Spaties en andere karakters zijn niet toegestaan.', + dateSuchAs: 'Vul een geldige datum in, zoals {date}', + dateInFormatMDY: 'Vul een geldige datum, in het formaat MM/DD/YYYY (bijvoorbeeld "12/31/1999")', + email: 'Vul een geldig e-mailadres in. Bijvoorbeeld "fred@domein.nl".', + url: 'Vul een geldige URL in, zoals http://www.example.com.', + currencyDollar: 'Vul een geldig $ bedrag in. Bijvoorbeeld $100.00 .', + oneRequired: 'Vul iets in bij in ieder geval een van deze velden.', + warningPrefix: 'Waarschuwing: ', + errorPrefix: 'Fout: ', + + // Form.Validator.Extras + noSpace: 'Spaties zijn niet toegestaan in dit veld.', + reqChkByNode: 'Er zijn geen items geselecteerd.', + requiredChk: 'Dit veld is verplicht.', + reqChkByName: 'Selecteer een {label}.', + match: 'Dit veld moet overeen komen met het {matchName} veld', + startDate: 'de begin datum', + endDate: 'de eind datum', + currentDate: 'de huidige datum', + afterDate: 'De datum moet hetzelfde of na {label} zijn.', + beforeDate: 'De datum moet hetzelfde of voor {label} zijn.', + startMonth: 'Selecteer een begin maand', + sameMonth: 'Deze twee data moeten in dezelfde maand zijn - u moet een van beide aanpassen.', + creditcard: 'Het ingevulde creditcardnummer is niet geldig. Controleer het nummer en probeer opnieuw. {length} getallen ingevuld.' + +}); + +/* +--- + +name: Locale.nl-NL.Number + +description: Number messages for Dutch. + +license: MIT-style license + +authors: + - Arian Stolwijk + +requires: + - Locale + - Locale.EU.Number + +provides: [Locale.nl-NL.Number] + +... +*/ + +Locale.define('nl-NL').inherit('EU', 'Number'); + + + + +/* +--- + +name: Locale.no-NO.Date + +description: Date messages for Norwegian. + +license: MIT-style license + +authors: + - Espen 'Rexxars' Hovlandsdal + - Ole TÞsse Kolvik +requires: + - Locale + +provides: [Locale.no-NO.Date] + +... +*/ + +Locale.define('no-NO', 'Date', { + months: ['Januar', 'Februar', 'Mars', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Desember'], + months_abbr: ['Jan', 'Feb', 'Mar', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'], + days: ['SÞndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'LÞrdag'], + days_abbr: ['SÞn', 'Man', 'Tir', 'Ons', 'Tor', 'Fre', 'LÞr'], + + // Culture's date order: DD.MM.YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d.%m.%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + lessThanMinuteAgo: 'mindre enn et minutt siden', + minuteAgo: 'omtrent et minutt siden', + minutesAgo: '{delta} minutter siden', + hourAgo: 'omtrent en time siden', + hoursAgo: 'omtrent {delta} timer siden', + dayAgo: '{delta} dag siden', + daysAgo: '{delta} dager siden', + weekAgo: 'en uke siden', + weeksAgo: '{delta} uker siden', + monthAgo: 'en mÃ¥ned siden', + monthsAgo: '{delta} mÃ¥neder siden', + yearAgo: 'ett Ã¥r siden', + yearsAgo: '{delta} Ã¥r siden', + + lessThanMinuteUntil: 'mindre enn et minutt til', + minuteUntil: 'omtrent et minutt til', + minutesUntil: '{delta} minutter til', + hourUntil: 'omtrent en time til', + hoursUntil: 'omtrent {delta} timer til', + dayUntil: 'en dag til', + daysUntil: '{delta} dager til', + weekUntil: 'en uke til', + weeksUntil: '{delta} uker til', + monthUntil: 'en mÃ¥ned til', + monthsUntil: '{delta} mÃ¥neder til', + yearUntil: 'et Ã¥r til', + yearsUntil: '{delta} Ã¥r til' +}); + +/* +--- + +name: Locale.no-NO.Form.Validator + +description: Form Validator messages for Norwegian. + +license: MIT-style license + +authors: + - Espen 'Rexxars' Hovlandsdal + +requires: + - Locale + +provides: [Locale.no-NO.Form.Validator] + +... +*/ + +Locale.define('no-NO', 'FormValidator', { + + required: 'Dette feltet er pÃÂ¥krevd.', + minLength: 'Vennligst skriv inn minst {minLength} tegn (du skrev {length} tegn).', + maxLength: 'Vennligst skriv inn maksimalt {maxLength} tegn (du skrev {length} tegn).', + integer: 'Vennligst skriv inn et tall i dette feltet. Tall med desimaler (for eksempel 1,25) er ikke tillat.', + numeric: 'Vennligst skriv inn kun numeriske verdier i dette feltet (for eksempel "1", "1.1", "-1" eller "-1.1").', + digits: 'Vennligst bruk kun nummer og skilletegn i dette feltet.', + alpha: 'Vennligst bruk kun bokstaver (a-z) i dette feltet. Ingen mellomrom eller andre tegn er tillat.', + alphanum: 'Vennligst bruk kun bokstaver (a-z) eller nummer (0-9) i dette feltet. Ingen mellomrom eller andre tegn er tillat.', + dateSuchAs: 'Vennligst skriv inn en gyldig dato, som {date}', + dateInFormatMDY: 'Vennligst skriv inn en gyldig dato, i formatet MM/DD/YYYY (for eksempel "12/31/1999")', + email: 'Vennligst skriv inn en gyldig epost-adresse. For eksempel "espen@domene.no".', + url: 'Vennligst skriv inn en gyldig URL, for eksempel http://www.example.com.', + currencyDollar: 'Vennligst fyll ut et gyldig $ belÞp. For eksempel $100.00 .', + oneRequired: 'Vennligst fyll ut noe i minst ett av disse feltene.', + errorPrefix: 'Feil: ', + warningPrefix: 'Advarsel: ' + +}); + +/* +--- + +name: Locale.pl-PL.Date + +description: Date messages for Polish. + +license: MIT-style license + +authors: + - Oskar Krawczyk + +requires: + - Locale + +provides: [Locale.pl-PL.Date] + +... +*/ + +Locale.define('pl-PL', 'Date', { + + months: ['StyczeÅ', 'Luty', 'Marzec', 'KwiecieÅ', 'Maj', 'Czerwiec', 'Lipiec', 'SierpieÅ', 'WrzesieÅ', 'Październik', 'Listopad', 'GrudzieÅ'], + months_abbr: ['sty', 'lut', 'mar', 'kwi', 'maj', 'cze', 'lip', 'sie', 'wrz', 'paź', 'lis', 'gru'], + days: ['Niedziela', 'PoniedziaÅek', 'Wtorek', 'Åroda', 'Czwartek', 'PiÄ
tek', 'Sobota'], + days_abbr: ['niedz.', 'pon.', 'wt.', 'År.', 'czw.', 'pt.', 'sob.'], + + // Culture's date order: YYYY-MM-DD + dateOrder: ['year', 'month', 'date'], + shortDate: '%Y-%m-%d', + shortTime: '%H:%M', + AM: 'nad ranem', + PM: 'po poÅudniu', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: function(dayOfMonth){ + return (dayOfMonth > 3 && dayOfMonth < 21) ? 'ty' : ['ty', 'szy', 'gi', 'ci', 'ty'][Math.min(dayOfMonth % 10, 4)]; + }, + + lessThanMinuteAgo: 'mniej niÅŒ minute temu', + minuteAgo: 'okoÅo minutÄ temu', + minutesAgo: '{delta} minut temu', + hourAgo: 'okoÅo godzinÄ temu', + hoursAgo: 'okoÅo {delta} godzin temu', + dayAgo: 'Wczoraj', + daysAgo: '{delta} dni temu', + + lessThanMinuteUntil: 'za niecaÅÄ
minutÄ', + minuteUntil: 'za okoÅo minutÄ', + minutesUntil: 'za {delta} minut', + hourUntil: 'za okoÅo godzinÄ', + hoursUntil: 'za okoÅo {delta} godzin', + dayUntil: 'za 1 dzieÅ', + daysUntil: 'za {delta} dni' + +}); + +/* +--- + +name: Locale.pl-PL.Form.Validator + +description: Form Validator messages for Polish. + +license: MIT-style license + +authors: + - Oskar Krawczyk + +requires: + - Locale + +provides: [Locale.pl-PL.Form.Validator] + +... +*/ + +Locale.define('pl-PL', 'FormValidator', { + + required: 'To pole jest wymagane.', + minLength: 'Wymagane jest przynajmniej {minLength} znaków (wpisanych zostaÅo tylko {length}).', + maxLength: 'Dozwolone jest nie wiÄcej niÅŒ {maxLength} znaków (wpisanych zostaÅo {length})', + integer: 'To pole wymaga liczb caÅych. Liczby dziesiÄtne (np. 1.25) sÄ
niedozwolone.', + numeric: 'Prosimy uÅŒywaÄ tylko numerycznych wartoÅci w tym polu (np. "1", "1.1", "-1" lub "-1.1").', + digits: 'Prosimy uÅŒywaÄ liczb oraz zankow punktuacyjnych w typ polu (dla przykÅadu, przy numerze telefonu myÅlniki i kropki sÄ
dozwolone).', + alpha: 'Prosimy uÅŒywaÄ tylko liter (a-z) w tym polu. Spacje oraz inne znaki sÄ
niedozwolone.', + alphanum: 'Prosimy uÅŒywaÄ tylko liter (a-z) lub liczb (0-9) w tym polu. Spacje oraz inne znaki sÄ
niedozwolone.', + dateSuchAs: 'Prosimy podaÄ prawidÅowÄ
datÄ w formacie: {date}', + dateInFormatMDY: 'Prosimy podaÄ poprawnÄ
date w formacie DD.MM.RRRR (i.e. "12.01.2009")', + email: 'Prosimy podaÄ prawidÅowy adres e-mail, np. "jan@domena.pl".', + url: 'Prosimy podaÄ prawidÅowy adres URL, np. http://www.example.com.', + currencyDollar: 'Prosimy podaÄ prawidÅowÄ
sumÄ w PLN. Dla przykÅadu: 100.00 PLN.', + oneRequired: 'Prosimy wypeÅniÄ chociaÅŒ jedno z pól.', + errorPrefix: 'BÅÄ
d: ', + warningPrefix: 'Uwaga: ', + + // Form.Validator.Extras + noSpace: 'W tym polu nie mogÄ
znajdowaÄ siÄ spacje.', + reqChkByNode: 'Brak zaznaczonych elementów.', + requiredChk: 'To pole jest wymagane.', + reqChkByName: 'Prosimy wybraÄ z {label}.', + match: 'To pole musi byÄ takie samo jak {matchName}', + startDate: 'data poczÄ
tkowa', + endDate: 'data koÅcowa', + currentDate: 'aktualna data', + afterDate: 'Podana data poinna byÄ taka sama lub po {label}.', + beforeDate: 'Podana data poinna byÄ taka sama lub przed {label}.', + startMonth: 'Prosimy wybraÄ poczÄ
tkowy miesiÄ
c.', + sameMonth: 'Te dwie daty muszÄ
byÄ w zakresie tego samego miesiÄ
ca - wymagana jest zmiana któregoÅ z pól.' + +}); + +/* +--- + +name: Locale.pt-PT.Date + +description: Date messages for Portuguese. + +license: MIT-style license + +authors: + - Fabio Miranda Costa + +requires: + - Locale + +provides: [Locale.pt-PT.Date] + +... +*/ + +Locale.define('pt-PT', 'Date', { + + months: ['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'], + months_abbr: ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'], + days: ['Domingo', 'Segunda-feira', 'Terça-feira', 'Quarta-feira', 'Quinta-feira', 'Sexta-feira', 'Sábado'], + days_abbr: ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'], + + // Culture's date order: DD-MM-YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d-%m-%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: 'º', + + lessThanMinuteAgo: 'há menos de um minuto', + minuteAgo: 'há cerca de um minuto', + minutesAgo: 'há {delta} minutos', + hourAgo: 'há cerca de uma hora', + hoursAgo: 'há cerca de {delta} horas', + dayAgo: 'há um dia', + daysAgo: 'há {delta} dias', + weekAgo: 'há uma semana', + weeksAgo: 'há {delta} semanas', + monthAgo: 'há um mês', + monthsAgo: 'há {delta} meses', + yearAgo: 'há um ano', + yearsAgo: 'há {delta} anos', + + lessThanMinuteUntil: 'em menos de um minuto', + minuteUntil: 'em um minuto', + minutesUntil: 'em {delta} minutos', + hourUntil: 'em uma hora', + hoursUntil: 'em {delta} horas', + dayUntil: 'em um dia', + daysUntil: 'em {delta} dias', + weekUntil: 'em uma semana', + weeksUntil: 'em {delta} semanas', + monthUntil: 'em um mês', + monthsUntil: 'em {delta} meses', + yearUntil: 'em um ano', + yearsUntil: 'em {delta} anos' + +}); + +/* +--- + +name: Locale.pt-BR.Date + +description: Date messages for Portuguese (Brazil). + +license: MIT-style license + +authors: + - Fabio Miranda Costa + +requires: + - Locale + - Locale.pt-PT.Date + +provides: [Locale.pt-BR.Date] + +... +*/ + +Locale.define('pt-BR', 'Date', { + + // Culture's date order: DD/MM/YYYY + shortDate: '%d/%m/%Y' + +}).inherit('pt-PT', 'Date'); + +/* +--- + +name: Locale.pt-BR.Form.Validator + +description: Form Validator messages for Portuguese (Brazil). + +license: MIT-style license + +authors: + - Fábio Miranda Costa + +requires: + - Locale + +provides: [Locale.pt-BR.Form.Validator] + +... +*/ + +Locale.define('pt-BR', 'FormValidator', { + + required: 'Este campo é obrigatório.', + minLength: 'Digite pelo menos {minLength} caracteres (tamanho atual: {length}).', + maxLength: 'Não digite mais de {maxLength} caracteres (tamanho atual: {length}).', + integer: 'Por favor digite apenas um número inteiro neste campo. Não são permitidos números decimais (por exemplo, 1,25).', + numeric: 'Por favor digite apenas valores numéricos neste campo (por exemplo, "1" ou "1.1" ou "-1" ou "-1,1").', + digits: 'Por favor use apenas números e pontuação neste campo (por exemplo, um número de telefone com traços ou pontos é permitido).', + alpha: 'Por favor use somente letras (a-z). Espaço e outros caracteres não são permitidos.', + alphanum: 'Use somente letras (a-z) ou números (0-9) neste campo. Espaço e outros caracteres não são permitidos.', + dateSuchAs: 'Digite uma data válida, como {date}', + dateInFormatMDY: 'Digite uma data válida, como DD/MM/YYYY (por exemplo, "31/12/1999")', + email: 'Digite um endereço de email válido. Por exemplo "nome@dominio.com".', + url: 'Digite uma URL válida. Exemplo: http://www.example.com.', + currencyDollar: 'Digite um valor em dinheiro válido. Exemplo: R$100,00 .', + oneRequired: 'Digite algo para pelo menos um desses campos.', + errorPrefix: 'Erro: ', + warningPrefix: 'Aviso: ', + + // Form.Validator.Extras + noSpace: 'Não é possÃvel digitar espaços neste campo.', + reqChkByNode: 'Não foi selecionado nenhum item.', + requiredChk: 'Este campo é obrigatório.', + reqChkByName: 'Por favor digite um {label}.', + match: 'Este campo deve ser igual ao campo {matchName}.', + startDate: 'a data inicial', + endDate: 'a data final', + currentDate: 'a data atual', + afterDate: 'A data deve ser igual ou posterior a {label}.', + beforeDate: 'A data deve ser igual ou anterior a {label}.', + startMonth: 'Por favor selecione uma data inicial.', + sameMonth: 'Estas duas datas devem ter o mesmo mês - você deve modificar uma das duas.', + creditcard: 'O número do cartão de crédito informado é inválido. Por favor verifique o valor e tente novamente. {length} números informados.' + +}); + +/* +--- + +name: Locale.pt-BR.Number + +description: Number messages for PT Brazilian. + +license: MIT-style license + +authors: + - Arian Stolwijk + - Danillo César + +requires: + - Locale + +provides: [Locale.pt-BR.Number] + +... +*/ + +Locale.define('pt-BR', 'Number', { + + decimal: ',', + group: '.', + + currency: { + prefix: 'R$ ' + } + +}); + + + +/* +--- + +name: Locale.pt-PT.Form.Validator + +description: Form Validator messages for Portuguese. + +license: MIT-style license + +authors: + - Miquel Hudin + +requires: + - Locale + +provides: [Locale.pt-PT.Form.Validator] + +... +*/ + +Locale.define('pt-PT', 'FormValidator', { + + required: 'Este campo é necessário.', + minLength: 'Digite pelo menos{minLength} caracteres (comprimento {length} caracteres).', + maxLength: 'Não insira mais de {maxLength} caracteres (comprimento {length} caracteres).', + integer: 'Digite um número inteiro neste domÃnio. Com números decimais (por exemplo, 1,25), não são permitidas.', + numeric: 'Digite apenas valores numéricos neste domÃnio (p.ex., "1" ou "1.1" ou "-1" ou "-1,1").', + digits: 'Por favor, use números e pontuação apenas neste campo (p.ex., um número de telefone com traços ou pontos é permitida).', + alpha: 'Por favor use somente letras (a-z), com nesta área. Não utilize espaços nem outros caracteres são permitidos.', + alphanum: 'Use somente letras (a-z) ou números (0-9) neste campo. Não utilize espaços nem outros caracteres são permitidos.', + dateSuchAs: 'Digite uma data válida, como {date}', + dateInFormatMDY: 'Digite uma data válida, como DD/MM/YYYY (p.ex. "31/12/1999")', + email: 'Digite um endereço de email válido. Por exemplo "fred@domain.com".', + url: 'Digite uma URL válida, como http://www.example.com.', + currencyDollar: 'Digite um valor válido $. Por exemplo $ 100,00. ', + oneRequired: 'Digite algo para pelo menos um desses insumos.', + errorPrefix: 'Erro: ', + warningPrefix: 'Aviso: ' + +}); + +/* +--- + +name: Locale.ru-RU-unicode.Date + +description: Date messages for Russian (utf-8). + +license: MIT-style license + +authors: + - Evstigneev Pavel + - Kuryanovich Egor + +requires: + - Locale + +provides: [Locale.ru-RU.Date] + +... +*/ + +(function(){ + +// Russian language pluralization rules, taken from CLDR project, http://unicode.org/cldr/ +// one -> n mod 10 is 1 and n mod 100 is not 11; +// few -> n mod 10 in 2..4 and n mod 100 not in 12..14; +// many -> n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14; +// other -> everything else (example 3.14) +var pluralize = function (n, one, few, many, other){ + var modulo10 = n % 10, + modulo100 = n % 100; + + if (modulo10 == 1 && modulo100 != 11){ + return one; + } else if ((modulo10 == 2 || modulo10 == 3 || modulo10 == 4) && !(modulo100 == 12 || modulo100 == 13 || modulo100 == 14)){ + return few; + } else if (modulo10 == 0 || (modulo10 == 5 || modulo10 == 6 || modulo10 == 7 || modulo10 == 8 || modulo10 == 9) || (modulo100 == 11 || modulo100 == 12 || modulo100 == 13 || modulo100 == 14)){ + return many; + } else { + return other; + } +}; + +Locale.define('ru-RU', 'Date', { + + months: ['ЯМваÑÑ', 'ЀевÑалÑ', 'ÐаÑÑ', 'ÐпÑелÑ', 'Ðай', 'ÐÑМÑ', 'ÐÑлÑ', 'ÐвгÑÑÑ', 'СеМÑÑбÑÑ', 'ÐкÑÑбÑÑ', 'ÐПÑбÑÑ', 'ÐекабÑÑ'], + months_abbr: ['ÑМв', 'ÑевÑ', 'ЌаÑÑ', 'апÑ', 'Ќай','ОÑМÑ','ОÑлÑ','авг','ÑеМÑ','ПкÑ','МПÑб','Ўек'], + days: ['ÐПÑкÑеÑеМÑе', 'ÐПМеЎелÑМОк', 'ÐÑПÑМОк', 'СÑеЎа', 'ЧеÑвеÑг', 'ÐÑÑМОÑа', 'СÑббПÑа'], + days_abbr: ['ÐÑ', 'ÐМ', 'ÐÑ', 'СÑ', 'ЧÑ', 'ÐÑ', 'Сб'], + + // Culture's date order: DD.MM.YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d.%m.%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: 'ЌеМÑÑе ЌОМÑÑÑ ÐœÐ°Ð·Ð°ÐŽ', + minuteAgo: 'ЌОМÑÑÑ ÐœÐ°Ð·Ð°ÐŽ', + minutesAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ЌОМÑÑÑ', 'ЌОМÑÑÑ', 'ЌОМÑÑ') + ' МазаЎ'; }, + hourAgo: 'ÑÐ°Ñ ÐœÐ°Ð·Ð°ÐŽ', + hoursAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ÑаÑ', 'ÑаÑа', 'ÑаÑПв') + ' МазаЎ'; }, + dayAgo: 'вÑеÑа', + daysAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ЎеМÑ', 'ЎМÑ', 'ЎМей') + ' МазаЎ'; }, + weekAgo: 'ÐœÐµÐŽÐµÐ»Ñ ÐœÐ°Ð·Ð°ÐŽ', + weeksAgo: function(delta){ return '{delta} ' + pluralize(delta, 'МеЎелÑ', 'МеЎелО', 'МеЎелÑ') + ' МазаЎ'; }, + monthAgo: 'ЌеÑÑÑ ÐœÐ°Ð·Ð°ÐŽ', + monthsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ЌеÑÑÑ', 'ЌеÑÑÑа', 'ЌеÑÑÑев') + ' МазаЎ'; }, + yearAgo: 'гПЎ МазаЎ', + yearsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'гПЎ', 'гПЎа', 'леÑ') + ' МазаЎ'; }, + + lessThanMinuteUntil: 'ЌеМÑÑе ÑеЌ ÑеÑез ЌОМÑÑÑ', + minuteUntil: 'ÑеÑез ЌОМÑÑÑ', + minutesUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'ЌОМÑÑÑ', 'ЌОМÑÑÑ', 'ЌОМÑÑ') + ''; }, + hourUntil: 'ÑеÑез ÑаÑ', + hoursUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'ÑаÑ', 'ÑаÑа', 'ÑаÑПв') + ''; }, + dayUntil: 'завÑÑа', + daysUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'ЎеМÑ', 'ЎМÑ', 'ЎМей') + ''; }, + weekUntil: 'ÑеÑез МеЎелÑ', + weeksUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'МеЎелÑ', 'МеЎелО', 'МеЎелÑ') + ''; }, + monthUntil: 'ÑеÑез ЌеÑÑÑ', + monthsUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'ЌеÑÑÑ', 'ЌеÑÑÑа', 'ЌеÑÑÑев') + ''; }, + yearUntil: 'ÑеÑез', + yearsUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'гПЎ', 'гПЎа', 'леÑ') + ''; } + +}); + + + +})(); + +/* +--- + +name: Locale.ru-RU-unicode.Form.Validator + +description: Form Validator messages for Russian (utf-8). + +license: MIT-style license + +authors: + - Chernodarov Egor + +requires: + - Locale + +provides: [Locale.ru-RU.Form.Validator] + +... +*/ + +Locale.define('ru-RU', 'FormValidator', { + + required: 'ÐÑП пПле ПбÑзаÑелÑМП к запПлМеМОÑ.', + minLength: 'ÐПжалÑйÑÑа, ввеЎОÑе Ñ
ПÑÑ Ð±Ñ {minLength} ÑОЌвПлПв (ÐÑ Ð²Ð²ÐµÐ»Ðž {length}).', + maxLength: 'ÐПжалÑйÑÑа, ввеЎОÑе Ме бПлÑÑе {maxLength} ÑОЌвПлПв (ÐÑ Ð²Ð²ÐµÐ»Ðž {length}).', + integer: 'ÐПжалÑйÑÑа, ввеЎОÑе в ÑÑП пПле ÑОÑлП. ÐÑПбМÑе ÑОÑла (МапÑÐžÐŒÐµÑ 1.25) ÑÑÑ ÐœÐµ ÑазÑеÑеМÑ.', + numeric: 'ÐПжалÑйÑÑа, ввеЎОÑе в ÑÑП пПле ÑОÑлП (МапÑÐžÐŒÐµÑ "1" ОлО "1.1", ОлО "-1", ОлО "-1.1").', + digits: 'Ð ÑÑПЌ пПле ÐÑ ÐŒÐŸÐ¶ÐµÑе ОÑпПлÑзПваÑÑ ÑПлÑкП ÑОÑÑÑ Ðž зМакО пÑМкÑÑаÑОО (МапÑОЌеÑ, ÑелеÑПММÑй ÐœÐŸÐŒÐµÑ ÑП зМакаЌО ЎеÑОÑа ОлО Ñ ÑПÑкаЌО).', + alpha: 'Ð ÑÑПЌ пПле ЌПжМП ОÑпПлÑзПваÑÑ ÑПлÑкП лаÑОМÑкОе бÑÐºÐ²Ñ (a-z). ÐÑÐŸÐ±ÐµÐ»Ñ Ðž ÐŽÑÑгОе ÑÐžÐŒÐ²ÐŸÐ»Ñ Ð·Ð°Ð¿ÑеÑеМÑ.', + alphanum: 'Ð ÑÑПЌ пПле ЌПжМП ОÑпПлÑзПваÑÑ ÑПлÑкП лаÑОМÑкОе бÑÐºÐ²Ñ (a-z) О ÑОÑÑÑ (0-9). ÐÑÐŸÐ±ÐµÐ»Ñ Ðž ÐŽÑÑгОе ÑÐžÐŒÐ²ÐŸÐ»Ñ Ð·Ð°Ð¿ÑеÑеМÑ.', + dateSuchAs: 'ÐПжалÑйÑÑа, ввеЎОÑе кПÑÑекÑМÑÑ ÐŽÐ°ÑÑ {date}', + dateInFormatMDY: 'ÐПжалÑйÑÑа, ввеЎОÑе ЎаÑÑ Ð² ÑПÑЌаÑе ÐÐ/ÐÐ/ÐÐÐÐ (МапÑÐžÐŒÐµÑ "12/31/1999")', + email: 'ÐПжалÑйÑÑа, ввеЎОÑе кПÑÑекÑМÑй еЌейл-аЎÑеÑ. ÐÐ»Ñ Ð¿ÑОЌеÑа "fred@domain.com".', + url: 'ÐПжалÑйÑÑа, ввеЎОÑе пÑавОлÑМÑÑ ÑÑÑÐ»ÐºÑ Ð²ÐžÐŽÐ° http://www.example.com.', + currencyDollar: 'ÐПжалÑйÑÑа, ввеЎОÑе ÑÑÐŒÐŒÑ Ð² ЎПллаÑаÑ
. ÐапÑОЌеÑ: $100.00 .', + oneRequired: 'ÐПжалÑйÑÑа, вÑбеÑОÑе Ñ
ПÑÑ ÑÑП-МОбÑÐŽÑ Ð² ПЎМПЌ Оз ÑÑОÑ
пПлей.', + errorPrefix: 'ÐÑОбка: ', + warningPrefix: 'ÐМОЌаМОе: ' + +}); + + + +/* +--- + +name: Locale.sk-SK.Date + +description: Date messages for Slovak. + +license: MIT-style license + +authors: + - Ivan Masár + +requires: + - Locale + +provides: [Locale.sk-SK.Date] + +... +*/ +(function(){ + +// Slovak language pluralization rules, see http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html +// one -> n is 1; 1 +// few -> n in 2..4; 2-4 +// other -> everything else 0, 5-999, 1.31, 2.31, 5.31... +var pluralize = function (n, one, few, other){ + if (n == 1) return one; + else if (n == 2 || n == 3 || n == 4) return few; + else return other; +}; + +Locale.define('sk-SK', 'Date', { + + months: ['Január', 'Február', 'Marec', 'AprÃl', 'Máj', 'Jún', 'Júl', 'August', 'September', 'Október', 'November', 'December'], + months_abbr: ['januára', 'februára', 'marca', 'aprÃla', 'mája', 'júna', 'júla', 'augusta', 'septembra', 'októbra', 'novembra', 'decembra'], + days: ['Nedele', 'PondelÃ', 'ÃterÜ', 'Streda', 'Ätvrtek', 'Pátek', 'Sobota'], + days_abbr: ['ne', 'po', 'ut', 'st', 'Å¡t', 'pi', 'so'], + + // Culture's date order: DD.MM.YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d.%m.%Y', + shortTime: '%H:%M', + AM: 'dop.', + PM: 'pop.', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '.', + + lessThanMinuteAgo: 'pred chvÃÄŸou', + minuteAgo: 'pribliÅŸne pred minútou', + minutesAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'minútou', 'minútami', 'minútami'); }, + hourAgo: 'pribliÅŸne pred hodinou', + hoursAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'hodinou', 'hodinami', 'hodinami'); }, + dayAgo: 'pred dÅom', + daysAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'dÅom', 'dÅami', 'dÅami'); }, + weekAgo: 'pred tÜşdÅom', + weeksAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'tÜşdÅom', 'tÜşdÅami', 'tÜşdÅami'); }, + monthAgo: 'pred mesiacom', + monthsAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'mesiacom', 'mesiacmi', 'mesiacmi'); }, + yearAgo: 'pred rokom', + yearsAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'rokom', 'rokmi', 'rokmi'); }, + + lessThanMinuteUntil: 'o chvÃÄŸu', + minuteUntil: 'pribliÅŸne o minútu', + minutesUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'minútu', 'minúty', 'minúty'); }, + hourUntil: 'pribliÅŸne o hodinu', + hoursUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'hodinu', 'hodiny', 'hodÃn'); }, + dayUntil: 'o deÅ', + daysUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'deÅ', 'dni', 'dnÃ'); }, + weekUntil: 'o tÜşdeÅ', + weeksUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'tÜşdeÅ', 'tÜşdne', 'tÜşdÅov'); }, + monthUntil: 'o mesiac', + monthsUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'mesiac', 'mesiace', 'mesiacov'); }, + yearUntil: 'o rok', + yearsUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'rok', 'roky', 'rokov'); } +}); + +})(); + +/* +--- + +name: Locale.sk-SK.Form.Validator + +description: Form Validator messages for Czech. + +license: MIT-style license + +authors: + - Ivan Masár + +requires: + - Locale + +provides: [Locale.sk-SK.Form.Validator] + +... +*/ + +Locale.define('sk-SK', 'FormValidator', { + + required: 'Táto poloÅŸka je povinná.', + minLength: 'Zadajte prosÃm aspoÅ {minLength} znakov (momentálne {length} znakov).', + maxLength: 'Zadajte prosÃm menej ako {maxLength} znakov (momentálne {length} znakov).', + integer: 'Zadajte prosÃm celé ÄÃslo. Desetinné ÄÃsla (napr. 1.25) nie sú povolené.', + numeric: 'Zadajte len ÄÃselné hodnoty (t.j. â1â alebo â1.1â alebo â-1â alebo â-1.1â).', + digits: 'Zadajte prosÃm len ÄÃsla a interpunkÄné znamienka (naprÃklad telefónne ÄÃslo s pomlÄkami albo bodkami je povolené).', + alpha: 'Zadajte prosÃm len pÃsmená (a-z). Medzery alebo iné znaky nie sú povolené.', + alphanum: 'Zadajte prosÃm len pÃsmená (a-z) alebo ÄÃslice (0-9). Medzery alebo iné znaky nie sú povolené.', + dateSuchAs: 'Zadajte prosÃm platnÜ dátum v tvare {date}', + dateInFormatMDY: 'Zadajte prosÃm platnÜ datum v tvare MM / DD / RRRR (t.j. â12/31/1999â)', + email: 'Zadajte prosÃm platnú emailovú adresu. NaprÃklad âfred@domain.comâ.', + url: 'Zadajte prosÃm platnoú adresu URL v tvare http://www.example.com.', + currencyDollar: 'Zadajte prosÃm platnú Äiastku. NaprÃklad $100.00.', + oneRequired: 'Zadajte prosÃm aspoÅ jednu hodnotu z tÜchto poloÅŸiek.', + errorPrefix: 'Chyba: ', + warningPrefix: 'Upozornenie: ', + + // Form.Validator.Extras + noSpace: 'V tejto poloÅŸle nie sú povolené medzery', + reqChkByNode: 'Nie sú vybrané ÅŸiadne poloÅŸky.', + requiredChk: 'Táto poloÅŸka je povinná.', + reqChkByName: 'ProsÃm vyberte {label}.', + match: 'Táto poloÅŸka sa musà zhodovaÅ¥ s poloÅŸkou {matchName}', + startDate: 'dátum zaÄiatku', + endDate: 'dátum ukonÄenia', + currendDate: 'aktuálny dátum', + afterDate: 'Dátum by mal bÜť rovnakÜ alebo vÀÄšà ako {label}.', + beforeDate: 'Dátum by mal byÅ¥ rovnakÜ alebo menšà ako {label}.', + startMonth: 'Vyberte poÄiatoÄnÜ mesiac.', + sameMonth: 'Tieto dva dátumy musia bÜť v rovnakom mesiaci - zmeÅte jeden z nich.', + creditcard: 'Zadané ÄÃslo kreditnej karty je neplatné. ProsÃm, opravte ho. Bolo zadanÜch {length} ÄÃslic.' + +}); + +/* +--- + +name: Locale.si-SI.Date + +description: Date messages for Slovenian. + +license: MIT-style license + +authors: + - Radovan Lozej + +requires: + - Locale + +provides: [Locale.si-SI.Date] + +... +*/ + +(function(){ + +var pluralize = function(n, one, two, three, other){ + return (n >= 1 && n <= 3) ? arguments[n] : other; +}; + +Locale.define('sl-SI', 'Date', { + + months: ['januar', 'februar', 'marec', 'april', 'maj', 'junij', 'julij', 'avgust', 'september', 'oktober', 'november', 'december'], + months_abbr: ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'avg', 'sep', 'okt', 'nov', 'dec'], + days: ['nedelja', 'ponedeljek', 'torek', 'sreda', 'Äetrtek', 'petek', 'sobota'], + days_abbr: ['ned', 'pon', 'tor', 'sre', 'Äet', 'pet', 'sob'], + + // Culture's date order: DD.MM.YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d.%m.%Y', + shortTime: '%H.%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '.', + + lessThanMinuteAgo: 'manj kot minuto nazaj', + minuteAgo: 'minuto nazaj', + minutesAgo: function(delta){ return '{delta} ' + pluralize(delta, 'minuto', 'minuti', 'minute', 'minut') + ' nazaj'; }, + hourAgo: 'uro nazaj', + hoursAgo: function(delta){ return '{delta} ' + pluralize(delta, 'uro', 'uri', 'ure', 'ur') + ' nazaj'; }, + dayAgo: 'dan nazaj', + daysAgo: function(delta){ return '{delta} ' + pluralize(delta, 'dan', 'dneva', 'dni', 'dni') + ' nazaj'; }, + weekAgo: 'teden nazaj', + weeksAgo: function(delta){ return '{delta} ' + pluralize(delta, 'teden', 'tedna', 'tedne', 'tednov') + ' nazaj'; }, + monthAgo: 'mesec nazaj', + monthsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'mesec', 'meseca', 'mesece', 'mesecov') + ' nazaj'; }, + yearthAgo: 'leto nazaj', + yearsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'leto', 'leti', 'leta', 'let') + ' nazaj'; }, + + lessThanMinuteUntil: 'Å¡e manj kot minuto', + minuteUntil: 'Å¡e minuta', + minutesUntil: function(delta){ return 'Å¡e {delta} ' + pluralize(delta, 'minuta', 'minuti', 'minute', 'minut'); }, + hourUntil: 'Å¡e ura', + hoursUntil: function(delta){ return 'Å¡e {delta} ' + pluralize(delta, 'ura', 'uri', 'ure', 'ur'); }, + dayUntil: 'Å¡e dan', + daysUntil: function(delta){ return 'Å¡e {delta} ' + pluralize(delta, 'dan', 'dneva', 'dnevi', 'dni'); }, + weekUntil: 'Å¡e tedn', + weeksUntil: function(delta){ return 'Å¡e {delta} ' + pluralize(delta, 'teden', 'tedna', 'tedni', 'tednov'); }, + monthUntil: 'Å¡e mesec', + monthsUntil: function(delta){ return 'Å¡e {delta} ' + pluralize(delta, 'mesec', 'meseca', 'meseci', 'mesecov'); }, + yearUntil: 'Å¡e leto', + yearsUntil: function(delta){ return 'Å¡e {delta} ' + pluralize(delta, 'leto', 'leti', 'leta', 'let'); } + +}); + +})(); + +/* +--- + +name: Locale.si-SI.Form.Validator + +description: Form Validator messages for Slovenian. + +license: MIT-style license + +authors: + - Radovan Lozej + +requires: + - Locale + +provides: [Locale.si-SI.Form.Validator] + +... +*/ + +Locale.define('sl-SI', 'FormValidator', { + + required: 'To polje je obvezno', + minLength: 'Prosim, vnesite vsaj {minLength} znakov (vnesli ste {length} znakov).', + maxLength: 'Prosim, ne vnesite veÄ kot {maxLength} znakov (vnesli ste {length} znakov).', + integer: 'Prosim, vnesite celo Å¡tevilo. Decimalna Å¡tevila (kot 1,25) niso dovoljena.', + numeric: 'Prosim, vnesite samo numeriÄne vrednosti (kot "1" ali "1.1" ali "-1" ali "-1.1").', + digits: 'Prosim, uporabite Å¡tevilke in loÄila le na tem polju (na primer, dovoljena je telefonska Å¡tevilka z pomiÅ¡laji ali pikami).', + alpha: 'Prosim, uporabite le Ärke v tem plju. Presledki in drugi znaki niso dovoljeni.', + alphanum: 'Prosim, uporabite samo Ärke ali Å¡tevilke v tem polju. Presledki in drugi znaki niso dovoljeni.', + dateSuchAs: 'Prosim, vnesite pravilen datum kot {date}', + dateInFormatMDY: 'Prosim, vnesite pravilen datum kot MM.DD.YYYY (primer "12.31.1999")', + email: 'Prosim, vnesite pravilen email naslov. Na primer "fred@domain.com".', + url: 'Prosim, vnesite pravilen URL kot http://www.example.com.', + currencyDollar: 'Prosim, vnesit epravilno vrednost â¬. Primer 100,00⬠.', + oneRequired: 'Prosimo, vnesite nekaj za vsaj eno izmed teh polj.', + errorPrefix: 'Napaka: ', + warningPrefix: 'Opozorilo: ', + + // Form.Validator.Extras + noSpace: 'To vnosno polje ne dopuÅ¡Äa presledkov.', + reqChkByNode: 'NiÄ niste izbrali.', + requiredChk: 'To polje je obvezno', + reqChkByName: 'Prosim, izberite {label}.', + match: 'To polje se mora ujemati z poljem {matchName}', + startDate: 'datum zaÄetka', + endDate: 'datum konca', + currentDate: 'trenuten datum', + afterDate: 'Datum bi moral biti isti ali po {label}.', + beforeDate: 'Datum bi moral biti isti ali pred {label}.', + startMonth: 'Prosim, vnesite zaÄetni datum', + sameMonth: 'Ta dva datuma morata biti v istem mesecu - premeniti morate eno ali drugo.', + creditcard: 'Å tevilka kreditne kartice ni pravilna. Preverite Å¡tevilko ali poskusite Å¡e enkrat. VneÅ¡enih {length} znakov.' + +}); + +/* +--- + +name: Locale.sv-SE.Date + +description: Date messages for Swedish. + +license: MIT-style license + +authors: + - Martin Lundgren + +requires: + - Locale + +provides: [Locale.sv-SE.Date] + +... +*/ + +Locale.define('sv-SE', 'Date', { + + months: ['januari', 'februari', 'mars', 'april', 'maj', 'juni', 'juli', 'augusti', 'september', 'oktober', 'november', 'december'], + months_abbr: ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'], + days: ['söndag', 'mÃ¥ndag', 'tisdag', 'onsdag', 'torsdag', 'fredag', 'lördag'], + days_abbr: ['sön', 'mÃ¥n', 'tis', 'ons', 'tor', 'fre', 'lör'], + + // Culture's date order: YYYY-MM-DD + dateOrder: ['year', 'month', 'date'], + shortDate: '%Y-%m-%d', + shortTime: '%H:%M', + AM: '', + PM: '', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: 'mindre Àn en minut sedan', + minuteAgo: 'ungefÀr en minut sedan', + minutesAgo: '{delta} minuter sedan', + hourAgo: 'ungefÀr en timme sedan', + hoursAgo: 'ungefÀr {delta} timmar sedan', + dayAgo: '1 dag sedan', + daysAgo: '{delta} dagar sedan', + + lessThanMinuteUntil: 'mindre Àn en minut sedan', + minuteUntil: 'ungefÀr en minut sedan', + minutesUntil: '{delta} minuter sedan', + hourUntil: 'ungefÀr en timme sedan', + hoursUntil: 'ungefÀr {delta} timmar sedan', + dayUntil: '1 dag sedan', + daysUntil: '{delta} dagar sedan' + +}); + +/* +--- + +name: Locale.sv-SE.Form.Validator + +description: Form Validator messages for Swedish. + +license: MIT-style license + +authors: + - Martin Lundgren + +requires: + - Locale + +provides: [Locale.sv-SE.Form.Validator] + +... +*/ + +Locale.define('sv-SE', 'FormValidator', { + + required: 'FÀltet Àr obligatoriskt.', + minLength: 'Ange minst {minLength} tecken (du angav {length} tecken).', + maxLength: 'Ange högst {maxLength} tecken (du angav {length} tecken). ', + integer: 'Ange ett heltal i fÀltet. Tal med decimaler (t.ex. 1,25) Àr inte tillÃ¥tna.', + numeric: 'Ange endast numeriska vÀrden i detta fÀlt (t.ex. "1" eller "1.1" eller "-1" eller "-1,1").', + digits: 'AnvÀnd endast siffror och skiljetecken i detta fÀlt (till exempel ett telefonnummer med bindestreck tillÃ¥tet).', + alpha: 'AnvÀnd endast bokstÀver (a-ö) i detta fÀlt. Inga mellanslag eller andra tecken Àr tillÃ¥tna.', + alphanum: 'AnvÀnd endast bokstÀver (a-ö) och siffror (0-9) i detta fÀlt. Inga mellanslag eller andra tecken Àr tillÃ¥tna.', + dateSuchAs: 'Ange ett giltigt datum som t.ex. {date}', + dateInFormatMDY: 'Ange ett giltigt datum som t.ex. YYYY-MM-DD (i.e. "1999-12-31")', + email: 'Ange en giltig e-postadress. Till exempel "erik@domain.com".', + url: 'Ange en giltig webbadress som http://www.example.com.', + currencyDollar: 'Ange en giltig belopp. Exempelvis 100,00.', + oneRequired: 'VÀnligen ange minst ett av dessa alternativ.', + errorPrefix: 'Fel: ', + warningPrefix: 'Varning: ', + + // Form.Validator.Extras + noSpace: 'Det fÃ¥r inte finnas nÃ¥gra mellanslag i detta fÀlt.', + reqChkByNode: 'Inga objekt Àr valda.', + requiredChk: 'Detta Àr ett obligatoriskt fÀlt.', + reqChkByName: 'VÀlj en {label}.', + match: 'Detta fÀlt mÃ¥ste matcha {matchName}', + startDate: 'startdatumet', + endDate: 'slutdatum', + currentDate: 'dagens datum', + afterDate: 'Datumet bör vara samma eller senare Àn {label}.', + beforeDate: 'Datumet bör vara samma eller tidigare Àn {label}.', + startMonth: 'VÀlj en start mÃ¥nad', + sameMonth: 'Dessa tvÃ¥ datum mÃ¥ste vara i samma mÃ¥nad - du mÃ¥ste Àndra det ena eller det andra.' + +}); + +/* +--- + +name: Locale.sv-SE.Number + +description: Number messages for Swedish. + +license: MIT-style license + +authors: + - Arian Stolwijk + - Martin Lundgren + +requires: + - Locale + - Locale.EU.Number + +provides: [Locale.sv-SE.Number] + +... +*/ + +Locale.define('sv-SE', 'Number', { + + currency: { + prefix: 'SEK ' + } + +}).inherit('EU', 'Number'); + +/* +--- + +name: Locale.tr-TR.Date + +description: Date messages for Turkish. + +license: MIT-style license + +authors: + - Faruk Can Bilir + +requires: + - Locale + +provides: [Locale.tr-TR.Date] + +... +*/ + +Locale.define('tr-TR', 'Date', { + + months: ['Ocak', 'Åubat', 'Mart', 'Nisan', 'Mayıs', 'Haziran', 'Temmuz', 'AÄustos', 'EylÃŒl', 'Ekim', 'Kasım', 'Aralık'], + months_abbr: ['Oca', 'Åub', 'Mar', 'Nis', 'May', 'Haz', 'Tem', 'AÄu', 'Eyl', 'Eki', 'Kas', 'Ara'], + days: ['Pazar', 'Pazartesi', 'Salı', 'ÃarÅamba', 'PerÅembe', 'Cuma', 'Cumartesi'], + days_abbr: ['Pa', 'Pzt', 'Sa', 'Ãa', 'Pe', 'Cu', 'Cmt'], + + // Culture's date order: MM/DD/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d/%m/%Y', + shortTime: '%H.%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: 'bir dakikadan önce', + minuteAgo: 'yaklaÅık bir dakika önce', + minutesAgo: '{delta} dakika önce', + hourAgo: 'bir saat kadar önce', + hoursAgo: '{delta} saat kadar önce', + dayAgo: 'bir gÃŒn önce', + daysAgo: '{delta} gÃŒn önce', + weekAgo: 'bir hafta önce', + weeksAgo: '{delta} hafta önce', + monthAgo: 'bir ay önce', + monthsAgo: '{delta} ay önce', + yearAgo: 'bir yıl önce', + yearsAgo: '{delta} yıl önce', + + lessThanMinuteUntil: 'bir dakikadan az sonra', + minuteUntil: 'bir dakika kadar sonra', + minutesUntil: '{delta} dakika sonra', + hourUntil: 'bir saat kadar sonra', + hoursUntil: '{delta} saat kadar sonra', + dayUntil: 'bir gÃŒn sonra', + daysUntil: '{delta} gÃŒn sonra', + weekUntil: 'bir hafta sonra', + weeksUntil: '{delta} hafta sonra', + monthUntil: 'bir ay sonra', + monthsUntil: '{delta} ay sonra', + yearUntil: 'bir yıl sonra', + yearsUntil: '{delta} yıl sonra' + +}); + +/* +--- + +name: Locale.tr-TR.Form.Validator + +description: Form Validator messages for Turkish. + +license: MIT-style license + +authors: + - Faruk Can Bilir + +requires: + - Locale + +provides: [Locale.tr-TR.Form.Validator] + +... +*/ + +Locale.define('tr-TR', 'FormValidator', { + + required: 'Bu alan zorunlu.', + minLength: 'LÃŒtfen en az {minLength} karakter girin (siz {length} karakter girdiniz).', + maxLength: 'LÃŒtfen en fazla {maxLength} karakter girin (siz {length} karakter girdiniz).', + integer: 'LÃŒtfen bu alana sadece tamsayı girin. Ondalıklı sayılar (ör: 1.25) kullanılamaz.', + numeric: 'LÃŒtfen bu alana sadece sayısal deÄer girin (ör: "1", "1.1", "-1" ya da "-1.1").', + digits: 'LÃŒtfen bu alana sadece sayısal deÄer ve noktalama iÅareti girin (örneÄin, nokta ve tire içeren bir telefon numarası kullanılabilir).', + alpha: 'LÃŒtfen bu alanda yalnızca harf kullanın. BoÅluk ve diÄer karakterler kullanılamaz.', + alphanum: 'LÃŒtfen bu alanda sadece harf ve rakam kullanın. BoÅluk ve diÄer karakterler kullanılamaz.', + dateSuchAs: 'LÃŒtfen geçerli bir tarih girin (Ãr: {date})', + dateInFormatMDY: 'LÃŒtfen geçerli bir tarih girin (GG/AA/YYYY, ör: "31/12/1999")', + email: 'LÃŒtfen geçerli bir email adresi girin. Ãr: "kemal@etikan.com".', + url: 'LÃŒtfen geçerli bir URL girin. Ãr: http://www.example.com.', + currencyDollar: 'LÃŒtfen geçerli bir TL miktarı girin. Ãr: 100,00 TL .', + oneRequired: 'LÃŒtfen en az bir tanesini doldurun.', + errorPrefix: 'Hata: ', + warningPrefix: 'Uyarı: ', + + // Form.Validator.Extras + noSpace: 'Bu alanda boÅluk kullanılamaz.', + reqChkByNode: 'Hiçbir öÄe seçilmemiÅ.', + requiredChk: 'Bu alan zorunlu.', + reqChkByName: 'LÃŒtfen bir {label} girin.', + match: 'Bu alan, {matchName} alanıyla uyuÅmalı', + startDate: 'baÅlangıç tarihi', + endDate: 'bitiÅ tarihi', + currentDate: 'bugÃŒnÃŒn tarihi', + afterDate: 'Tarih, {label} tarihiyle aynı gÃŒn ya da ondan sonra olmalıdır.', + beforeDate: 'Tarih, {label} tarihiyle aynı gÃŒn ya da ondan önce olmalıdır.', + startMonth: 'LÃŒtfen bir baÅlangıç ayı seçin', + sameMonth: 'Bu iki tarih aynı ayda olmalı - bir tanesini deÄiÅtirmeniz gerekiyor.', + creditcard: 'GirdiÄiniz kredi kartı numarası geçersiz. LÃŒtfen kontrol edip tekrar deneyin. {length} hane girildi.' + +}); + +/* +--- + +name: Locale.tr-TR.Number + +description: Number messages for Turkish. + +license: MIT-style license + +authors: + - Faruk Can Bilir + +requires: + - Locale + - Locale.EU.Number + +provides: [Locale.tr-TR.Number] + +... +*/ + +Locale.define('tr-TR', 'Number', { + + currency: { + decimals: 0, + suffix: ' TL' + } + +}).inherit('EU', 'Number'); + +/* +--- + +name: Locale.uk-UA.Date + +description: Date messages for Ukrainian (utf-8). + +license: MIT-style license + +authors: + - Slik + +requires: + - Locale + +provides: [Locale.uk-UA.Date] + +... +*/ + +(function(){ + +var pluralize = function(n, one, few, many, other){ + var d = (n / 10).toInt(), + z = n % 10, + s = (n / 100).toInt(); + + if (d == 1 && n > 10) return many; + if (z == 1) return one; + if (z > 0 && z < 5) return few; + return many; +}; + +Locale.define('uk-UA', 'Date', { + + months: ['СÑÑеМÑ', 'ÐÑÑОй', 'ÐеÑезеМÑ', 'ÐвÑÑеМÑ', 'ТÑавеМÑ', 'ЧеÑвеМÑ', 'ÐОпеМÑ', 'СеÑпеМÑ', 'ÐеÑеÑеМÑ', 'ÐПвÑеМÑ', 'ÐОÑÑПпаЎ', 'ÐÑÑЎеМÑ'], + months_abbr: ['СÑÑ', 'ÐÑÑ', 'ÐеÑ', 'ÐвÑÑ', 'ТÑав', 'ЧеÑв', 'ÐОп', 'СеÑп', 'ÐеÑ', 'ÐПвÑ', 'ÐОÑÑ', 'ÐÑÑÐŽ' ], + days: ['ÐеЎÑлÑ', 'ÐПМеЎÑлПк', 'ÐÑвÑПÑПк', 'СеÑеЎа', 'ЧеÑвеÑ', "Ð'ÑÑМОÑÑ", 'СÑбПÑа'], + days_abbr: ['ÐÐŽ', 'ÐМ', 'ÐÑ', 'СÑ', 'ЧÑ', 'ÐÑ', 'Сб'], + + // Culture's date order: DD/MM/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d/%m/%Y', + shortTime: '%H:%M', + AM: 'ЎП пПлÑЎМÑ', + PM: 'пП пПлÑЎМÑ', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: 'ЌеМÑÑе Ñ
вОлОМО ÑПЌÑ', + minuteAgo: 'Ñ
Ð²ÐžÐ»ÐžÐœÑ ÑПЌÑ', + minutesAgo: function(delta){ return '{delta} ' + pluralize(delta, 'Ñ
вОлОМÑ', 'Ñ
вОлОМО', 'Ñ
вОлОМ') + ' ÑПЌÑ'; }, + hourAgo: 'Ð³ÐŸÐŽÐžÐœÑ ÑПЌÑ', + hoursAgo: function(delta){ return '{delta} ' + pluralize(delta, 'гПЎОМÑ', 'гПЎОМО', 'гПЎОМ') + ' ÑПЌÑ'; }, + dayAgo: 'вÑПÑа', + daysAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ЎеМÑ', 'ЎМÑ', 'ЎМÑв') + ' ÑПЌÑ'; }, + weekAgo: 'ÑÐžÐ¶ÐŽÐµÐœÑ ÑПЌÑ', + weeksAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ÑОжЎеМÑ', 'ÑОжМÑ', 'ÑОжМÑв') + ' ÑПЌÑ'; }, + monthAgo: 'ÐŒÑÑÑÑÑ ÑПЌÑ', + monthsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ÐŒÑÑÑÑÑ', 'ÐŒÑÑÑÑÑ', 'ÐŒÑÑÑÑÑв') + ' ÑПЌÑ'; }, + yearAgo: 'ÑÑк ÑПЌÑ', + yearsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ÑÑк', 'ÑПкО', 'ÑПкÑв') + ' ÑПЌÑ'; }, + + lessThanMinuteUntil: 'за ЌОÑÑ', + minuteUntil: 'ÑеÑез Ñ
вОлОМÑ', + minutesUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'Ñ
вОлОМÑ', 'Ñ
вОлОМО', 'Ñ
вОлОМ'); }, + hourUntil: 'ÑеÑез гПЎОМÑ', + hoursUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'гПЎОМÑ', 'гПЎОМО', 'гПЎОМ'); }, + dayUntil: 'завÑÑа', + daysUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'ЎеМÑ', 'ЎМÑ', 'ЎМÑв'); }, + weekUntil: 'ÑеÑез ÑОжЎеМÑ', + weeksUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'ÑОжЎеМÑ', 'ÑОжМÑ', 'ÑОжМÑв'); }, + monthUntil: 'ÑеÑез ÐŒÑÑÑÑÑ', + monthesUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'ÐŒÑÑÑÑÑ', 'ÐŒÑÑÑÑÑ', 'ÐŒÑÑÑÑÑв'); }, + yearUntil: 'ÑеÑез ÑÑк', + yearsUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'ÑÑк', 'ÑПкО', 'ÑПкÑв'); } + +}); + +})(); + +/* +--- + +name: Locale.uk-UA.Form.Validator + +description: Form Validator messages for Ukrainian (utf-8). + +license: MIT-style license + +authors: + - Slik + +requires: + - Locale + +provides: [Locale.uk-UA.Form.Validator] + +... +*/ + +Locale.define('uk-UA', 'FormValidator', { + + required: 'Ње пПле пПвОММе бÑÑО запПвМеМОЌ.', + minLength: 'ÐвеЎÑÑÑ Ñ
ПÑа б {minLength} ÑОЌвПлÑв (ÐО ввелО {length}).', + maxLength: 'ÐÑлÑкÑÑÑÑ ÑОЌвПлÑв Ме ЌПже бÑÑО бÑлÑÑе {maxLength} (ÐО ввелО {length}).', + integer: 'ÐвеЎÑÑÑ Ð² Ñе пПле ÑОÑлП. ÐÑÐŸÐ±ÐŸÐ²Ñ ÑОÑла (МапÑОклаЎ 1.25) Ме ЎПзвПлеМÑ.', + numeric: 'ÐвеЎÑÑÑ Ð² Ñе пПле ÑОÑлП (МапÑОклаЎ "1" абП "1.1", абП "-1", абП "-1.1").', + digits: 'Ð ÑÑÐŸÐŒÑ Ð¿ÐŸÐ»Ñ Ð²Ðž ЌПжеÑе вОкПÑОÑÑПвÑваÑО лОÑе ÑОÑÑО Ñ Ð·ÐœÐ°ÐºÐž пÑМкÑÑаÑÑÑ (МапÑОклаЎ, ÑелеÑПММОй ÐœÐŸÐŒÐµÑ Ð· зМакаЌО ЎеÑÑÐ·Ñ Ð°Ð±ÐŸ з кÑапкаЌО).', + alpha: 'Ð ÑÑÐŸÐŒÑ Ð¿ÐŸÐ»Ñ ÐŒÐŸÐ¶ÐœÐ° вОкПÑОÑÑПвÑваÑО лОÑе лаÑОМÑÑÐºÑ Ð»ÑÑеÑО (a-z). ÐÑПбÑлО Ñ ÑМÑÑ ÑОЌвПлО забПÑПМеМÑ.', + alphanum: 'Ð ÑÑÐŸÐŒÑ Ð¿ÐŸÐ»Ñ ÐŒÐŸÐ¶ÐœÐ° вОкПÑОÑÑПвÑваÑО лОÑе лаÑОМÑÑÐºÑ Ð»ÑÑеÑО (a-z) Ñ ÑОÑÑО (0-9). ÐÑПбÑлО Ñ ÑМÑÑ ÑОЌвПлО забПÑПМеМÑ.', + dateSuchAs: 'ÐвеЎÑÑÑ ÐºÐŸÑекÑÐœÑ ÐŽÐ°ÑÑ {date}.', + dateInFormatMDY: 'ÐвеЎÑÑÑ ÐŽÐ°ÑÑ Ð² ÑПÑЌаÑÑ ÐÐ/ÐÐ/Ð Ð Ð Ð (МапÑОклаЎ "12/31/2009").', + email: 'ÐвеЎÑÑÑ ÐºÐŸÑекÑÐœÑ Ð°ÐŽÑеÑÑ ÐµÐ»ÐµÐºÑÑÐŸÐœÐœÐŸÑ Ð¿ÐŸÑÑО (МапÑОклаЎ "name@domain.com").', + url: 'ÐвеЎÑÑÑ ÐºÐŸÑекÑМе ÑМÑеÑМеÑ-пПÑÐžÐ»Ð°ÐœÐœÑ (МапÑОклаЎ http://www.example.com).', + currencyDollar: 'ÐвеЎÑÑÑ ÑÑÐŒÑ Ð² ЎПлаÑаÑ
(МапÑОклаЎ "$100.00").', + oneRequired: 'ÐапПвМÑÑÑ ÐŸÐŽÐœÐµ з пПлÑв.', + errorPrefix: 'ÐПЌОлка: ', + warningPrefix: 'Увага: ', + + noSpace: 'ÐÑПбÑлО забПÑПМеМÑ.', + reqChkByNode: 'Ðе вÑÐŽÐŒÑÑеМП жПЎМПгП ваÑÑаМÑÑ.', + requiredChk: 'Ње пПле пПвОММе бÑÑО вÑÐŒÑÑеМОЌ.', + reqChkByName: 'ÐÑÐŽÑ Ð»Ð°Ñка, вÑÐŽÐŒÑÑÑÑе {label}.', + match: 'Ње пПле пПвОММП вÑЎпПвÑЎаÑО {matchName}', + startDate: 'пПÑаÑкПва ЎаÑа', + endDate: 'кÑМÑева ЎаÑа', + currentDate: 'ÑÑПгПЎМÑÑÐœÑ ÐŽÐ°Ñа', + afterDate: 'ÐŠÑ ÐŽÐ°Ñа пПвОММа бÑÑО ÑÐ°ÐºÐŸÑ Ð¶, абП пÑзМÑÑÐŸÑ Ð·Ð° {label}.', + beforeDate: 'ÐŠÑ ÐŽÐ°Ñа пПвОММа бÑÑО ÑÐ°ÐºÐŸÑ Ð¶, абП ÑаМÑÑÐŸÑ Ð·Ð° {label}.', + startMonth: 'ÐÑÐŽÑ Ð»Ð°Ñка, вОбеÑÑÑÑ Ð¿ÐŸÑаÑкПвОй ÐŒÑÑÑÑÑ', + sameMonth: 'ÐŠÑ ÐŽÐ°ÑО Ð¿ÐŸÐ²ÐžÐœÐœÑ Ð²ÑЎМПÑОÑОÑÑ ÐŸÐŽÐœÐŸÐ³ÐŸ Ñ ÑПгП ж ÐŒÑÑÑÑÑ. ÐÑÐŽÑ Ð»Ð°Ñка, зЌÑМÑÑÑ ÐŸÐŽÐœÑ Ð· МОÑ
.', + creditcard: 'ÐÐŸÐŒÐµÑ ÐºÑеЎОÑÐœÐŸÑ ÐºÐ°ÑÑО ввеЎеМОй МепÑавОлÑМП. ÐÑÐŽÑ Ð»Ð°Ñка, пеÑевÑÑÑе йПгП. ÐвеЎеМП {length} ÑОЌвПлÑв.' + +}); + +/* +--- + +name: Locale.zh-CH.Date + +description: Date messages for Chinese (simplified and traditional). + +license: MIT-style license + +authors: + - YMind Chan + +requires: + - Locale + +provides: [Locale.zh-CH.Date] + +... +*/ + +// Simplified Chinese +Locale.define('zh-CHS', 'Date', { + + months: ['äžæ', 'äºæ', 'äžæ', 'åæ', 'äºæ', 'å
æ', 'äžæ', 'å
«æ', '乿', 'åæ', 'åäžæ', 'åäºæ'], + months_abbr: ['äž', 'äº', 'äž', 'å', 'äº', 'å
', 'äž', 'å
«', 'ä¹', 'å', 'åäž', 'åäº'], + days: ['æææ¥', 'ææäž', 'ææäº', 'ææäž', 'ææå', 'ææäº', 'ææå
'], + days_abbr: ['æ¥', 'äž', 'äº', 'äž', 'å', 'äº', 'å
'], + + // Culture's date order: YYYY-MM-DD + dateOrder: ['year', 'month', 'date'], + shortDate: '%Y-%m-%d', + shortTime: '%I:%M%p', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: 'äžå°1åéå', + minuteAgo: '倧纊1åéå', + minutesAgo: '{delta}åéä¹å', + hourAgo: '倧纊1å°æ¶å', + hoursAgo: '倧纊{delta}å°æ¶å', + dayAgo: '1倩å', + daysAgo: '{delta}倩å', + weekAgo: '1ææå', + weeksAgo: '{delta}ææå', + monthAgo: '1䞪æå', + monthsAgo: '{delta}䞪æå', + yearAgo: '1幎å', + yearsAgo: '{delta}幎å', + + lessThanMinuteUntil: 'ä»ç°åšåŒå§äžå°1åé', + minuteUntil: 'ä»ç°åšåŒå§çŽ1åé', + minutesUntil: 'ä»ç°åšåŒå§çºŠ{delta}åé', + hourUntil: 'ä»ç°åšåŒå§1å°æ¶', + hoursUntil: 'ä»ç°åšåŒå§çºŠ{delta}å°æ¶', + dayUntil: 'ä»ç°åšåŒå§1倩', + daysUntil: 'ä»ç°åšåŒå§{delta}倩', + weekUntil: 'ä»ç°åšåŒå§1ææ', + weeksUntil: 'ä»ç°åšåŒå§{delta}ææ', + monthUntil: 'ä»ç°åšåŒå§äžäžªæ', + monthsUntil: 'ä»ç°åšåŒå§{delta}䞪æ', + yearUntil: 'ä»ç°åšåŒå§1幎', + yearsUntil: 'ä»ç°åšåŒå§{delta}幎' + +}); + +// Traditional Chinese +Locale.define('zh-CHT', 'Date', { + + months: ['äžæ', 'äºæ', 'äžæ', 'åæ', 'äºæ', 'å
æ', 'äžæ', 'å
«æ', '乿', 'åæ', 'åäžæ', 'åäºæ'], + months_abbr: ['äž', 'äº', 'äž', 'å', 'äº', 'å
', 'äž', 'å
«', 'ä¹', 'å', 'åäž', 'åäº'], + days: ['æææ¥', 'ææäž', 'ææäº', 'ææäž', 'ææå', 'ææäº', 'ææå
'], + days_abbr: ['æ¥', 'äž', 'äº', 'äž', 'å', 'äº', 'å
'], + + // Culture's date order: YYYY-MM-DD + dateOrder: ['year', 'month', 'date'], + shortDate: '%Y-%m-%d', + shortTime: '%I:%M%p', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: 'äžå°1åéå', + minuteAgo: '倧çŽ1åéå', + minutesAgo: '{delta}åéä¹å', + hourAgo: '倧çŽ1å°æå', + hoursAgo: '倧çŽ{delta}å°æå', + dayAgo: '1倩å', + daysAgo: '{delta}倩å', + weekAgo: '1ææå', + weeksAgo: '{delta}ææå', + monthAgo: '1䞪æå', + monthsAgo: '{delta}䞪æå', + yearAgo: '1幎å', + yearsAgo: '{delta}幎å', + + lessThanMinuteUntil: 'åŸçŸåšéå§äžå°1åé', + minuteUntil: 'åŸçŸåšéå§çŽ1åé', + minutesUntil: 'åŸçŸåšéå§çŽ{delta}åé', + hourUntil: 'åŸçŸåšéå§1å°æ', + hoursUntil: 'åŸçŸåšéå§çŽ{delta}å°æ', + dayUntil: 'åŸçŸåšéå§1倩', + daysUntil: 'åŸçŸåšéå§{delta}倩', + weekUntil: 'åŸçŸåšéå§1ææ', + weeksUntil: 'åŸçŸåšéå§{delta}ææ', + monthUntil: 'åŸçŸåšéå§äžåæ', + monthsUntil: 'åŸçŸåšéå§{delta}åæ', + yearUntil: 'åŸçŸåšéå§1幎', + yearsUntil: 'åŸçŸåšéå§{delta}幎' + +}); + +/* +--- + +name: Locale.zh-CH.Form.Validator + +description: Form Validator messages for Chinese (simplified and traditional). + +license: MIT-style license + +authors: + - YMind Chan + +requires: + - Locale + - Form.Validator + +provides: [Form.zh-CH.Form.Validator, Form.Validator.CurrencyYuanValidator] + +... +*/ + +// Simplified Chinese +Locale.define('zh-CHS', 'FormValidator', { + + required: 'æ€é¡¹å¿
å¡«ã', + minLength: '请è³å°èŸå
¥ {minLength} 䞪å笊 (å·²èŸå
¥ {length} 䞪)ã', + maxLength: 'æå€åªèœèŸå
¥ {maxLength} 䞪å笊 (å·²èŸå
¥ {length} 䞪)ã', + integer: '请èŸå
¥äžäžªæŽæ°ïŒäžèœå
å«å°æ°ç¹ãäŸåŠïŒ"1", "200"ã', + numeric: '请èŸå
¥äžäžªæ°åïŒäŸåŠïŒ"1", "1.1", "-1", "-1.1"ã', + digits: '请èŸå
¥ç±æ°ååæ ç¹ç¬Šå·ç»æçå
容ãäŸåŠçµè¯å·ç ã', + alpha: '请èŸå
¥ A-Z ç 26 äžªåæ¯ïŒäžèœå
å«ç©ºæ Œæä»»äœå
¶ä»å笊ã', + alphanum: '请èŸå
¥ A-Z ç 26 äžªåæ¯æ 0-9 ç 10 䞪æ°åïŒäžèœå
å«ç©ºæ Œæä»»äœå
¶ä»å笊ã', + dateSuchAs: '请èŸå
¥åæ³çæ¥ææ ŒåŒïŒåŠïŒ{date}ã', + dateInFormatMDY: '请èŸå
¥åæ³çæ¥ææ ŒåŒïŒäŸåŠïŒYYYY-MM-DD ("2010-12-31")ã', + email: '请èŸå
¥åæ³ççµåä¿¡ç®±å°åïŒäŸåŠïŒ"fred@domain.com"ã', + url: '请èŸå
¥åæ³ç Url å°åïŒäŸåŠïŒhttp://www.example.comã', + currencyDollar: '请èŸå
¥åæ³ç莧åžç¬Šå·ïŒäŸåŠïŒï¿¥100.0', + oneRequired: '请è³å°éæ©äžé¡¹ã', + errorPrefix: 'é误ïŒ', + warningPrefix: 'èŠåïŒ', + + // Form.Validator.Extras + noSpace: 'äžèœå
å«ç©ºæ Œã', + reqChkByNode: 'æªéæ©ä»»äœå
容ã', + requiredChk: 'æ€é¡¹å¿
å¡«ã', + reqChkByName: 'è¯·éæ© {label}.', + match: 'å¿
é¡»äž{matchName}çžå¹é
', + startDate: 'èµ·å§æ¥æ', + endDate: 'ç»ææ¥æ', + currentDate: 'åœåæ¥æ', + afterDate: 'æ¥æå¿
é¡»çäºææäº {label}.', + beforeDate: 'æ¥æå¿
é¡»æ©äºæçäº {label}.', + startMonth: 'è¯·éæ©èµ·å§æä»œ', + sameMonth: 'æšå¿
须修æ¹äž€äžªæ¥æäžçäžäžªïŒä»¥ç¡®ä¿å®ä»¬åšåäžæä»œã', + creditcard: 'æšèŸå
¥çä¿¡çšå¡å·ç äžæ£ç¡®ãåœåå·²èŸå
¥{length}䞪å笊ã' + +}); + +// Traditional Chinese +Locale.define('zh-CHT', 'FormValidator', { + + required: 'æ€é
å¿
å¡«ã ', + minLength: 'è«è³å°èŒžå
¥{minLength} åå笊(已茞å
¥{length} å)ã ', + maxLength: 'æå€åªèœèŒžå
¥{maxLength} åå笊(已茞å
¥{length} å)ã ', + integer: 'è«èŒžå
¥äžåæŽæžïŒäžèœå
å«å°æžé»ãäŸåŠïŒ"1", "200"ã ', + numeric: 'è«èŒžå
¥äžåæžåïŒäŸåŠïŒ"1", "1.1", "-1", "-1.1"ã ', + digits: 'è«èŒžå
¥ç±æžååæšé»ç¬Šèçµæçå
§å®¹ãäŸåŠé»è©±è碌ã ', + alpha: 'è«èŒžå
¥AZ ç26 å忝ïŒäžèœå
å«ç©ºæ Œæä»»äœå
¶ä»å笊ã ', + alphanum: 'è«èŒžå
¥AZ ç26 å忝æ0-9 ç10 åæžåïŒäžèœå
å«ç©ºæ Œæä»»äœå
¶ä»å笊ã ', + dateSuchAs: 'è«èŒžå
¥åæ³çæ¥ææ ŒåŒïŒåŠïŒ{date}ã ', + dateInFormatMDY: 'è«èŒžå
¥åæ³çæ¥ææ ŒåŒïŒäŸåŠïŒYYYY-MM-DD ("2010-12-31")ã ', + email: 'è«èŒžå
¥åæ³çé»åä¿¡ç®±å°åïŒäŸåŠïŒ"fred@domain.com"ã ', + url: 'è«èŒžå
¥åæ³çUrl å°åïŒäŸåŠïŒhttp://www.example.comã ', + currencyDollar: 'è«èŒžå
¥åæ³ç貚幣笊èïŒäŸåŠïŒï¿¥100.0', + oneRequired: 'è«è³å°éžæäžé
ã ', + errorPrefix: 'é¯èª€ïŒ', + warningPrefix: 'èŠåïŒ', + + // Form.Validator.Extras + noSpace: 'äžèœå
å«ç©ºæ Œã ', + reqChkByNode: 'æªéžæä»»äœå
§å®¹ã ', + requiredChk: 'æ€é
å¿
å¡«ã ', + reqChkByName: 'è«éžæ {label}.', + match: 'å¿
é è{matchName}çžå¹é
', + startDate: 'èµ·å§æ¥æ', + endDate: 'çµææ¥æ', + currentDate: 'ç¶åæ¥æ', + afterDate: 'æ¥æå¿
é çæŒæææŒ{label}.', + beforeDate: 'æ¥æå¿
é æ©æŒæçæŒ{label}.', + startMonth: 'è«éžæèµ·å§æä»œ', + sameMonth: 'æšå¿
é ä¿®æ¹å
©åæ¥æäžçäžåïŒä»¥ç¢ºä¿å®ååšåäžæä»œã ', + creditcard: 'æšèŒžå
¥çä¿¡çšå¡èç¢Œäžæ£ç¢ºãç¶å已茞å
¥{length}åå笊ã ' + +}); + +Form.Validator.add('validate-currency-yuan', { + + errorMsg: function(){ + return Form.Validator.getMsg('currencyYuan'); + }, + + test: function(element){ + // [ï¿¥]1[##][,###]+[.##] + // [ï¿¥]1###+[.##] + // [ï¿¥]0.## + // [ï¿¥].## + return Form.Validator.getValidator('IsEmpty').test(element) || (/^ï¿¥?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(element.get('value')); + } + +}); + +/* +--- + +name: Locale.zh-CH.Number + +description: Number messages for for Chinese (simplified and traditional). + +license: MIT-style license + +authors: + - YMind Chan + +requires: + - Locale + - Locale.en-US.Number + +provides: [Locale.zh-CH.Number] + +... +*/ + +// Simplified Chinese +Locale.define('zh-CHS', 'Number', { + + currency: { + prefix: 'ï¿¥ ' + } + +}).inherit('en-US', 'Number'); + +// Traditional Chinese +Locale.define('zh-CHT').inherit('zh-CHS', 'Number'); + +/* +--- + +script: Request.JSONP.js + +name: Request.JSONP + +description: Defines Request.JSONP, a class for cross domain javascript via script injection. + +license: MIT-style license + +authors: + - Aaron Newton + - Guillermo Rauch + - Arian Stolwijk + +requires: + - Core/Element + - Core/Request + - MooTools.More + +provides: [Request.JSONP] + +... +*/ + +Request.JSONP = new Class({ + + Implements: [Chain, Events, Options], + + options: {/* + onRequest: function(src, scriptElement){}, + onComplete: function(data){}, + onSuccess: function(data){}, + onCancel: function(){}, + onTimeout: function(){}, + onError: function(){}, */ + onRequest: function(src){ + if (this.options.log && window.console && console.log){ + console.log('JSONP retrieving script with url:' + src); + } + }, + onError: function(src){ + if (this.options.log && window.console && console.warn){ + console.warn('JSONP '+ src +' will fail in Internet Explorer, which enforces a 2083 bytes length limit on URIs'); + } + }, + url: '', + callbackKey: 'callback', + injectScript: document.head, + data: '', + link: 'ignore', + timeout: 0, + log: false + }, + + initialize: function(options){ + this.setOptions(options); + }, + + send: function(options){ + if (!Request.prototype.check.call(this, options)) return this; + this.running = true; + + var type = typeOf(options); + if (type == 'string' || type == 'element') options = {data: options}; + options = Object.merge(this.options, options || {}); + + var data = options.data; + switch (typeOf(data)){ + case 'element': data = document.id(data).toQueryString(); break; + case 'object': case 'hash': data = Object.toQueryString(data); + } + + var index = this.index = Request.JSONP.counter++; + + var src = options.url + + (options.url.test('\\?') ? '&' :'?') + + (options.callbackKey) + + '=Request.JSONP.request_map.request_'+ index + + (data ? '&' + data : ''); + + if (src.length > 2083) this.fireEvent('error', src); + + Request.JSONP.request_map['request_' + index] = function(){ + this.success(arguments, index); + }.bind(this); + + var script = this.getScript(src).inject(options.injectScript); + this.fireEvent('request', [src, script]); + + if (options.timeout) this.timeout.delay(options.timeout, this); + + return this; + }, + + getScript: function(src){ + if (!this.script) this.script = new Element('script', { + type: 'text/javascript', + async: true, + src: src + }); + return this.script; + }, + + success: function(args, index){ + if (!this.running) return; + this.clear() + .fireEvent('complete', args).fireEvent('success', args) + .callChain(); + }, + + cancel: function(){ + if (this.running) this.clear().fireEvent('cancel'); + return this; + }, + + isRunning: function(){ + return !!this.running; + }, + + clear: function(){ + this.running = false; + if (this.script){ + this.script.destroy(); + this.script = null; + } + return this; + }, + + timeout: function(){ + if (this.running){ + this.running = false; + this.fireEvent('timeout', [this.script.get('src'), this.script]).fireEvent('failure').cancel(); + } + return this; + } + +}); + +Request.JSONP.counter = 0; +Request.JSONP.request_map = {}; + +/* +--- + +script: Request.Periodical.js + +name: Request.Periodical + +description: Requests the same URL to pull data from a server but increases the intervals if no data is returned to reduce the load + +license: MIT-style license + +authors: + - Christoph Pojer + +requires: + - Core/Request + - MooTools.More + +provides: [Request.Periodical] + +... +*/ + +Request.implement({ + + options: { + initialDelay: 5000, + delay: 5000, + limit: 60000 + }, + + startTimer: function(data){ + var fn = function(){ + if (!this.running) this.send({data: data}); + }; + this.lastDelay = this.options.initialDelay; + this.timer = fn.delay(this.lastDelay, this); + this.completeCheck = function(response){ + clearTimeout(this.timer); + this.lastDelay = (response) ? this.options.delay : (this.lastDelay + this.options.delay).min(this.options.limit); + this.timer = fn.delay(this.lastDelay, this); + }; + return this.addEvent('complete', this.completeCheck); + }, + + stopTimer: function(){ + clearTimeout(this.timer); + return this.removeEvent('complete', this.completeCheck); + } + +}); + +/* +--- + +script: Request.Queue.js + +name: Request.Queue + +description: Controls several instances of Request and its variants to run only one request at a time. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Element + - Core/Request + - Class.Binds + +provides: [Request.Queue] + +... +*/ + +Request.Queue = new Class({ + + Implements: [Options, Events], + + Binds: ['attach', 'request', 'complete', 'cancel', 'success', 'failure', 'exception'], + + options: {/* + onRequest: function(argsPassedToOnRequest){}, + onSuccess: function(argsPassedToOnSuccess){}, + onComplete: function(argsPassedToOnComplete){}, + onCancel: function(argsPassedToOnCancel){}, + onException: function(argsPassedToOnException){}, + onFailure: function(argsPassedToOnFailure){}, + onEnd: function(){}, + */ + stopOnFailure: true, + autoAdvance: true, + concurrent: 1, + requests: {} + }, + + initialize: function(options){ + var requests; + if (options){ + requests = options.requests; + delete options.requests; + } + this.setOptions(options); + this.requests = {}; + this.queue = []; + this.reqBinders = {}; + + if (requests) this.addRequests(requests); + }, + + addRequest: function(name, request){ + this.requests[name] = request; + this.attach(name, request); + return this; + }, + + addRequests: function(obj){ + Object.each(obj, function(req, name){ + this.addRequest(name, req); + }, this); + return this; + }, + + getName: function(req){ + return Object.keyOf(this.requests, req); + }, + + attach: function(name, req){ + if (req._groupSend) return this; + ['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){ + if (!this.reqBinders[name]) this.reqBinders[name] = {}; + this.reqBinders[name][evt] = function(){ + this['on' + evt.capitalize()].apply(this, [name, req].append(arguments)); + }.bind(this); + req.addEvent(evt, this.reqBinders[name][evt]); + }, this); + req._groupSend = req.send; + req.send = function(options){ + this.send(name, options); + return req; + }.bind(this); + return this; + }, + + removeRequest: function(req){ + var name = typeOf(req) == 'object' ? this.getName(req) : req; + if (!name && typeOf(name) != 'string') return this; + req = this.requests[name]; + if (!req) return this; + ['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){ + req.removeEvent(evt, this.reqBinders[name][evt]); + }, this); + req.send = req._groupSend; + delete req._groupSend; + return this; + }, + + getRunning: function(){ + return Object.filter(this.requests, function(r){ + return r.running; + }); + }, + + isRunning: function(){ + return !!(Object.keys(this.getRunning()).length); + }, + + send: function(name, options){ + var q = function(){ + this.requests[name]._groupSend(options); + this.queue.erase(q); + }.bind(this); + + q.name = name; + if (Object.keys(this.getRunning()).length >= this.options.concurrent || (this.error && this.options.stopOnFailure)) this.queue.push(q); + else q(); + return this; + }, + + hasNext: function(name){ + return (!name) ? !!this.queue.length : !!this.queue.filter(function(q){ return q.name == name; }).length; + }, + + resume: function(){ + this.error = false; + (this.options.concurrent - Object.keys(this.getRunning()).length).times(this.runNext, this); + return this; + }, + + runNext: function(name){ + if (!this.queue.length) return this; + if (!name){ + this.queue[0](); + } else { + var found; + this.queue.each(function(q){ + if (!found && q.name == name){ + found = true; + q(); + } + }); + } + return this; + }, + + runAll: function(){ + this.queue.each(function(q){ + q(); + }); + return this; + }, + + clear: function(name){ + if (!name){ + this.queue.empty(); + } else { + this.queue = this.queue.map(function(q){ + if (q.name != name) return q; + else return false; + }).filter(function(q){ + return q; + }); + } + return this; + }, + + cancel: function(name){ + this.requests[name].cancel(); + return this; + }, + + onRequest: function(){ + this.fireEvent('request', arguments); + }, + + onComplete: function(){ + this.fireEvent('complete', arguments); + if (!this.queue.length) this.fireEvent('end'); + }, + + onCancel: function(){ + if (this.options.autoAdvance && !this.error) this.runNext(); + this.fireEvent('cancel', arguments); + }, + + onSuccess: function(){ + if (this.options.autoAdvance && !this.error) this.runNext(); + this.fireEvent('success', arguments); + }, + + onFailure: function(){ + this.error = true; + if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext(); + this.fireEvent('failure', arguments); + }, + + onException: function(){ + this.error = true; + if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext(); + this.fireEvent('exception', arguments); + } + +}); + +/* +--- + +script: Array.Extras.js + +name: Array.Extras + +description: Extends the Array native object to include useful methods to work with arrays. + +license: MIT-style license + +authors: + - Christoph Pojer + - Sebastian MarkbÃ¥ge + +requires: + - Core/Array + - MooTools.More + +provides: [Array.Extras] + +... +*/ + +(function(nil){ + +Array.implement({ + + min: function(){ + return Math.min.apply(null, this); + }, + + max: function(){ + return Math.max.apply(null, this); + }, + + average: function(){ + return this.length ? this.sum() / this.length : 0; + }, + + sum: function(){ + var result = 0, l = this.length; + if (l){ + while (l--){ + if (this[l] != null) result += parseFloat(this[l]); + } + } + return result; + }, + + unique: function(){ + return [].combine(this); + }, + + shuffle: function(){ + for (var i = this.length; i && --i;){ + var temp = this[i], r = Math.floor(Math.random() * ( i + 1 )); + this[i] = this[r]; + this[r] = temp; + } + return this; + }, + + reduce: function(fn, value){ + for (var i = 0, l = this.length; i < l; i++){ + if (i in this) value = value === nil ? this[i] : fn.call(null, value, this[i], i, this); + } + return value; + }, + + reduceRight: function(fn, value){ + var i = this.length; + while (i--){ + if (i in this) value = value === nil ? this[i] : fn.call(null, value, this[i], i, this); + } + return value; + }, + + pluck: function(prop){ + return this.map(function(item){ + return item[prop]; + }); + } + +}); + +})(); + +/* +--- + +script: Date.Extras.js + +name: Date.Extras + +description: Extends the Date native object to include extra methods (on top of those in Date.js). + +license: MIT-style license + +authors: + - Aaron Newton + - Scott Kyle + +requires: + - Date + +provides: [Date.Extras] + +... +*/ + +Date.implement({ + + timeDiffInWords: function(to){ + return Date.distanceOfTimeInWords(this, to || new Date); + }, + + timeDiff: function(to, separator){ + if (to == null) to = new Date; + var delta = ((to - this) / 1000).floor().abs(); + + var vals = [], + durations = [60, 60, 24, 365, 0], + names = ['s', 'm', 'h', 'd', 'y'], + value, duration; + + for (var item = 0; item < durations.length; item++){ + if (item && !delta) break; + value = delta; + if ((duration = durations[item])){ + value = (delta % duration); + delta = (delta / duration).floor(); + } + vals.unshift(value + (names[item] || '')); + } + + return vals.join(separator || ':'); + } + +}).extend({ + + distanceOfTimeInWords: function(from, to){ + return Date.getTimePhrase(((to - from) / 1000).toInt()); + }, + + getTimePhrase: function(delta){ + var suffix = (delta < 0) ? 'Until' : 'Ago'; + if (delta < 0) delta *= -1; + + var units = { + minute: 60, + hour: 60, + day: 24, + week: 7, + month: 52 / 12, + year: 12, + eon: Infinity + }; + + var msg = 'lessThanMinute'; + + for (var unit in units){ + var interval = units[unit]; + if (delta < 1.5 * interval){ + if (delta > 0.75 * interval) msg = unit; + break; + } + delta /= interval; + msg = unit + 's'; + } + + delta = delta.round(); + return Date.getMsg(msg + suffix, delta).substitute({delta: delta}); + } + +}).defineParsers( + + { + // "today", "tomorrow", "yesterday" + re: /^(?:tod|tom|yes)/i, + handler: function(bits){ + var d = new Date().clearTime(); + switch (bits[0]){ + case 'tom': return d.increment(); + case 'yes': return d.decrement(); + default: return d; + } + } + }, + + { + // "next Wednesday", "last Thursday" + re: /^(next|last) ([a-z]+)$/i, + handler: function(bits){ + var d = new Date().clearTime(); + var day = d.getDay(); + var newDay = Date.parseDay(bits[2], true); + var addDays = newDay - day; + if (newDay <= day) addDays += 7; + if (bits[1] == 'last') addDays -= 7; + return d.set('date', d.getDate() + addDays); + } + } + +).alias('timeAgoInWords', 'timeDiffInWords'); + +/* +--- + +name: Hash + +description: Contains Hash Prototypes. Provides a means for overcoming the JavaScript practical impossibility of extending native Objects. + +license: MIT-style license. + +requires: + - Core/Object + - MooTools.More + +provides: [Hash] + +... +*/ + +(function(){ + +if (this.Hash) return; + +var Hash = this.Hash = new Type('Hash', function(object){ + if (typeOf(object) == 'hash') object = Object.clone(object.getClean()); + for (var key in object) this[key] = object[key]; + return this; +}); + +this.$H = function(object){ + return new Hash(object); +}; + +Hash.implement({ + + forEach: function(fn, bind){ + Object.forEach(this, fn, bind); + }, + + getClean: function(){ + var clean = {}; + for (var key in this){ + if (this.hasOwnProperty(key)) clean[key] = this[key]; + } + return clean; + }, + + getLength: function(){ + var length = 0; + for (var key in this){ + if (this.hasOwnProperty(key)) length++; + } + return length; + } + +}); + +Hash.alias('each', 'forEach'); + +Hash.implement({ + + has: Object.prototype.hasOwnProperty, + + keyOf: function(value){ + return Object.keyOf(this, value); + }, + + hasValue: function(value){ + return Object.contains(this, value); + }, + + extend: function(properties){ + Hash.each(properties || {}, function(value, key){ + Hash.set(this, key, value); + }, this); + return this; + }, + + combine: function(properties){ + Hash.each(properties || {}, function(value, key){ + Hash.include(this, key, value); + }, this); + return this; + }, + + erase: function(key){ + if (this.hasOwnProperty(key)) delete this[key]; + return this; + }, + + get: function(key){ + return (this.hasOwnProperty(key)) ? this[key] : null; + }, + + set: function(key, value){ + if (!this[key] || this.hasOwnProperty(key)) this[key] = value; + return this; + }, + + empty: function(){ + Hash.each(this, function(value, key){ + delete this[key]; + }, this); + return this; + }, + + include: function(key, value){ + if (this[key] == undefined) this[key] = value; + return this; + }, + + map: function(fn, bind){ + return new Hash(Object.map(this, fn, bind)); + }, + + filter: function(fn, bind){ + return new Hash(Object.filter(this, fn, bind)); + }, + + every: function(fn, bind){ + return Object.every(this, fn, bind); + }, + + some: function(fn, bind){ + return Object.some(this, fn, bind); + }, + + getKeys: function(){ + return Object.keys(this); + }, + + getValues: function(){ + return Object.values(this); + }, + + toQueryString: function(base){ + return Object.toQueryString(this, base); + } + +}); + +Hash.alias({indexOf: 'keyOf', contains: 'hasValue'}); + + +})(); + + +/* +--- + +script: Hash.Extras.js + +name: Hash.Extras + +description: Extends the Hash Type to include getFromPath which allows a path notation to child elements. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Hash + - Object.Extras + +provides: [Hash.Extras] + +... +*/ + +Hash.implement({ + + getFromPath: function(notation){ + return Object.getFromPath(this, notation); + }, + + cleanValues: function(method){ + return new Hash(Object.cleanValues(this, method)); + }, + + run: function(){ + Object.run(arguments); + } + +}); + +/* +--- +name: Number.Format +description: Extends the Number Type object to include a number formatting method. +license: MIT-style license +authors: [Arian Stolwijk] +requires: [Core/Number, Locale.en-US.Number] +# Number.Extras is for compatibility +provides: [Number.Format, Number.Extras] +... +*/ + + +Number.implement({ + + format: function(options){ + // Thanks dojo and YUI for some inspiration + var value = this; + options = options ? Object.clone(options) : {}; + var getOption = function(key){ + if (options[key] != null) return options[key]; + return Locale.get('Number.' + key); + }; + + var negative = value < 0, + decimal = getOption('decimal'), + precision = getOption('precision'), + group = getOption('group'), + decimals = getOption('decimals'); + + if (negative){ + var negativeLocale = getOption('negative') || {}; + if (negativeLocale.prefix == null && negativeLocale.suffix == null) negativeLocale.prefix = '-'; + ['prefix', 'suffix'].each(function(key){ + if (negativeLocale[key]) options[key] = getOption(key) + negativeLocale[key]; + }); + + value = -value; + } + + var prefix = getOption('prefix'), + suffix = getOption('suffix'); + + if (decimals !== '' && decimals >= 0 && decimals <= 20) value = value.toFixed(decimals); + if (precision >= 1 && precision <= 21) value = (+value).toPrecision(precision); + + value += ''; + var index; + if (getOption('scientific') === false && value.indexOf('e') > -1){ + var match = value.split('e'), + zeros = +match[1]; + value = match[0].replace('.', ''); + + if (zeros < 0){ + zeros = -zeros - 1; + index = match[0].indexOf('.'); + if (index > -1) zeros -= index - 1; + while (zeros--) value = '0' + value; + value = '0.' + value; + } else { + index = match[0].lastIndexOf('.'); + if (index > -1) zeros -= match[0].length - index - 1; + while (zeros--) value += '0'; + } + } + + if (decimal != '.') value = value.replace('.', decimal); + + if (group){ + index = value.lastIndexOf(decimal); + index = (index > -1) ? index : value.length; + var newOutput = value.substring(index), + i = index; + + while (i--){ + if ((index - i - 1) % 3 == 0 && i != (index - 1)) newOutput = group + newOutput; + newOutput = value.charAt(i) + newOutput; + } + + value = newOutput; + } + + if (prefix) value = prefix + value; + if (suffix) value += suffix; + + return value; + }, + + formatCurrency: function(decimals){ + var locale = Locale.get('Number.currency') || {}; + if (locale.scientific == null) locale.scientific = false; + locale.decimals = decimals != null ? decimals + : (locale.decimals == null ? 2 : locale.decimals); + + return this.format(locale); + }, + + formatPercentage: function(decimals){ + var locale = Locale.get('Number.percentage') || {}; + if (locale.suffix == null) locale.suffix = '%'; + locale.decimals = decimals != null ? decimals + : (locale.decimals == null ? 2 : locale.decimals); + + return this.format(locale); + } + +}); + +/* +--- + +script: URI.js + +name: URI + +description: Provides methods useful in managing the window location and uris. + +license: MIT-style license + +authors: + - Sebastian MarkbÃ¥ge + - Aaron Newton + +requires: + - Core/Object + - Core/Class + - Core/Class.Extras + - Core/Element + - String.QueryString + +provides: [URI] + +... +*/ + +(function(){ + +var toString = function(){ + return this.get('value'); +}; + +var URI = this.URI = new Class({ + + Implements: Options, + + options: { + /*base: false*/ + }, + + regex: /^(?:(\w+):)?(?:\/\/(?:(?:([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)?(\.\.?$|(?:[^?#\/]*\/)*)([^?#]*)(?:\?([^#]*))?(?:#(.*))?/, + parts: ['scheme', 'user', 'password', 'host', 'port', 'directory', 'file', 'query', 'fragment'], + schemes: {http: 80, https: 443, ftp: 21, rtsp: 554, mms: 1755, file: 0}, + + initialize: function(uri, options){ + this.setOptions(options); + var base = this.options.base || URI.base; + if (!uri) uri = base; + + if (uri && uri.parsed) this.parsed = Object.clone(uri.parsed); + else this.set('value', uri.href || uri.toString(), base ? new URI(base) : false); + }, + + parse: function(value, base){ + var bits = value.match(this.regex); + if (!bits) return false; + bits.shift(); + return this.merge(bits.associate(this.parts), base); + }, + + merge: function(bits, base){ + if ((!bits || !bits.scheme) && (!base || !base.scheme)) return false; + if (base){ + this.parts.every(function(part){ + if (bits[part]) return false; + bits[part] = base[part] || ''; + return true; + }); + } + bits.port = bits.port || this.schemes[bits.scheme.toLowerCase()]; + bits.directory = bits.directory ? this.parseDirectory(bits.directory, base ? base.directory : '') : '/'; + return bits; + }, + + parseDirectory: function(directory, baseDirectory){ + directory = (directory.substr(0, 1) == '/' ? '' : (baseDirectory || '/')) + directory; + if (!directory.test(URI.regs.directoryDot)) return directory; + var result = []; + directory.replace(URI.regs.endSlash, '').split('/').each(function(dir){ + if (dir == '..' && result.length > 0) result.pop(); + else if (dir != '.') result.push(dir); + }); + return result.join('/') + '/'; + }, + + combine: function(bits){ + return bits.value || bits.scheme + '://' + + (bits.user ? bits.user + (bits.password ? ':' + bits.password : '') + '@' : '') + + (bits.host || '') + (bits.port && bits.port != this.schemes[bits.scheme] ? ':' + bits.port : '') + + (bits.directory || '/') + (bits.file || '') + + (bits.query ? '?' + bits.query : '') + + (bits.fragment ? '#' + bits.fragment : ''); + }, + + set: function(part, value, base){ + if (part == 'value'){ + var scheme = value.match(URI.regs.scheme); + if (scheme) scheme = scheme[1]; + if (scheme && this.schemes[scheme.toLowerCase()] == null) this.parsed = { scheme: scheme, value: value }; + else this.parsed = this.parse(value, (base || this).parsed) || (scheme ? { scheme: scheme, value: value } : { value: value }); + } else if (part == 'data'){ + this.setData(value); + } else { + this.parsed[part] = value; + } + return this; + }, + + get: function(part, base){ + switch (part){ + case 'value': return this.combine(this.parsed, base ? base.parsed : false); + case 'data' : return this.getData(); + } + return this.parsed[part] || ''; + }, + + go: function(){ + document.location.href = this.toString(); + }, + + toURI: function(){ + return this; + }, + + getData: function(key, part){ + var qs = this.get(part || 'query'); + if (!(qs || qs === 0)) return key ? null : {}; + var obj = qs.parseQueryString(); + return key ? obj[key] : obj; + }, + + setData: function(values, merge, part){ + if (typeof values == 'string'){ + var data = this.getData(); + data[arguments[0]] = arguments[1]; + values = data; + } else if (merge){ + values = Object.merge(this.getData(null, part), values); + } + return this.set(part || 'query', Object.toQueryString(values)); + }, + + clearData: function(part){ + return this.set(part || 'query', ''); + }, + + toString: toString, + valueOf: toString + +}); + +URI.regs = { + endSlash: /\/$/, + scheme: /^(\w+):/, + directoryDot: /\.\/|\.$/ +}; + +URI.base = new URI(Array.from(document.getElements('base[href]', true)).getLast(), {base: document.location}); + +String.implement({ + + toURI: function(options){ + return new URI(this, options); + } + +}); + +})(); + +/* +--- + +script: URI.Relative.js + +name: URI.Relative + +description: Extends the URI class to add methods for computing relative and absolute urls. + +license: MIT-style license + +authors: + - Sebastian MarkbÃ¥ge + + +requires: + - Class.refactor + - URI + +provides: [URI.Relative] + +... +*/ + +URI = Class.refactor(URI, { + + combine: function(bits, base){ + if (!base || bits.scheme != base.scheme || bits.host != base.host || bits.port != base.port) + return this.previous.apply(this, arguments); + var end = bits.file + (bits.query ? '?' + bits.query : '') + (bits.fragment ? '#' + bits.fragment : ''); + + if (!base.directory) return (bits.directory || (bits.file ? '' : './')) + end; + + var baseDir = base.directory.split('/'), + relDir = bits.directory.split('/'), + path = '', + offset; + + var i = 0; + for (offset = 0; offset < baseDir.length && offset < relDir.length && baseDir[offset] == relDir[offset]; offset++); + for (i = 0; i < baseDir.length - offset - 1; i++) path += '../'; + for (i = offset; i < relDir.length - 1; i++) path += relDir[i] + '/'; + + return (path || (bits.file ? '' : './')) + end; + }, + + toAbsolute: function(base){ + base = new URI(base); + if (base) base.set('directory', '').set('file', ''); + return this.toRelative(base); + }, + + toRelative: function(base){ + return this.get('value', new URI(base)); + } + +}); + +/* +--- + +script: Assets.js + +name: Assets + +description: Provides methods to dynamically load JavaScript, CSS, and Image files into the document. + +license: MIT-style license + +authors: + - Valerio Proietti + +requires: + - Core/Element.Event + - MooTools.More + +provides: [Assets] + +... +*/ + +var Asset = { + + javascript: function(source, properties){ + if (!properties) properties = {}; + + var script = new Element('script', {src: source, type: 'text/javascript'}), + doc = properties.document || document, + load = properties.onload || properties.onLoad; + + delete properties.onload; + delete properties.onLoad; + delete properties.document; + + if (load){ + if (!script.addEventListener){ + script.addEvent('readystatechange', function(){ + if (['loaded', 'complete'].contains(this.readyState)) load.call(this); + }); + } else { + script.addEvent('load', load); + } + } + + return script.set(properties).inject(doc.head); + }, + + css: function(source, properties){ + if (!properties) properties = {}; + + var load = properties.onload || properties.onLoad, + doc = properties.document || document, + timeout = properties.timeout || 3000; + + ['onload', 'onLoad', 'document'].each(function(prop){ + delete properties[prop]; + }); + + var link = new Element('link', { + type: 'text/css', + rel: 'stylesheet', + media: 'screen', + href: source + }).setProperties(properties).inject(doc.head); + + if (load){ + // based on article at http://www.yearofmoo.com/2011/03/cross-browser-stylesheet-preloading.html + var loaded = false, retries = 0; + var check = function(){ + var stylesheets = document.styleSheets; + for (var i = 0; i < stylesheets.length; i++){ + var file = stylesheets[i]; + var owner = file.ownerNode ? file.ownerNode : file.owningElement; + if (owner && owner == link){ + loaded = true; + return load.call(link); + } + } + retries++; + if (!loaded && retries < timeout / 50) return setTimeout(check, 50); + } + setTimeout(check, 0); + } + return link; + }, + + image: function(source, properties){ + if (!properties) properties = {}; + + var image = new Image(), + element = document.id(image) || new Element('img'); + + ['load', 'abort', 'error'].each(function(name){ + var type = 'on' + name, + cap = 'on' + name.capitalize(), + event = properties[type] || properties[cap] || function(){}; + + delete properties[cap]; + delete properties[type]; + + image[type] = function(){ + if (!image) return; + if (!element.parentNode){ + element.width = image.width; + element.height = image.height; + } + image = image.onload = image.onabort = image.onerror = null; + event.delay(1, element, element); + element.fireEvent(name, element, 1); + }; + }); + + image.src = element.src = source; + if (image && image.complete) image.onload.delay(1); + return element.set(properties); + }, + + images: function(sources, options){ + sources = Array.from(sources); + + var fn = function(){}, + counter = 0; + + options = Object.merge({ + onComplete: fn, + onProgress: fn, + onError: fn, + properties: {} + }, options); + + return new Elements(sources.map(function(source, index){ + return Asset.image(source, Object.append(options.properties, { + onload: function(){ + counter++; + options.onProgress.call(this, counter, index, source); + if (counter == sources.length) options.onComplete(); + }, + onerror: function(){ + counter++; + options.onError.call(this, counter, index, source); + if (counter == sources.length) options.onComplete(); + } + })); + })); + } + +}; + +/* +--- + +script: Color.js + +name: Color + +description: Class for creating and manipulating colors in JavaScript. Supports HSB -> RGB Conversions and vice versa. + +license: MIT-style license + +authors: + - Valerio Proietti + +requires: + - Core/Array + - Core/String + - Core/Number + - Core/Hash + - Core/Function + - MooTools.More + +provides: [Color] + +... +*/ + +(function(){ + +var Color = this.Color = new Type('Color', function(color, type){ + if (arguments.length >= 3){ + type = 'rgb'; color = Array.slice(arguments, 0, 3); + } else if (typeof color == 'string'){ + if (color.match(/rgb/)) color = color.rgbToHex().hexToRgb(true); + else if (color.match(/hsb/)) color = color.hsbToRgb(); + else color = color.hexToRgb(true); + } + type = type || 'rgb'; + switch (type){ + case 'hsb': + var old = color; + color = color.hsbToRgb(); + color.hsb = old; + break; + case 'hex': color = color.hexToRgb(true); break; + } + color.rgb = color.slice(0, 3); + color.hsb = color.hsb || color.rgbToHsb(); + color.hex = color.rgbToHex(); + return Object.append(color, this); +}); + +Color.implement({ + + mix: function(){ + var colors = Array.slice(arguments); + var alpha = (typeOf(colors.getLast()) == 'number') ? colors.pop() : 50; + var rgb = this.slice(); + colors.each(function(color){ + color = new Color(color); + for (var i = 0; i < 3; i++) rgb[i] = Math.round((rgb[i] / 100 * (100 - alpha)) + (color[i] / 100 * alpha)); + }); + return new Color(rgb, 'rgb'); + }, + + invert: function(){ + return new Color(this.map(function(value){ + return 255 - value; + })); + }, + + setHue: function(value){ + return new Color([value, this.hsb[1], this.hsb[2]], 'hsb'); + }, + + setSaturation: function(percent){ + return new Color([this.hsb[0], percent, this.hsb[2]], 'hsb'); + }, + + setBrightness: function(percent){ + return new Color([this.hsb[0], this.hsb[1], percent], 'hsb'); + } + +}); + +this.$RGB = function(r, g, b){ + return new Color([r, g, b], 'rgb'); +}; + +this.$HSB = function(h, s, b){ + return new Color([h, s, b], 'hsb'); +}; + +this.$HEX = function(hex){ + return new Color(hex, 'hex'); +}; + +Array.implement({ + + rgbToHsb: function(){ + var red = this[0], + green = this[1], + blue = this[2], + hue = 0; + var max = Math.max(red, green, blue), + min = Math.min(red, green, blue); + var delta = max - min; + var brightness = max / 255, + saturation = (max != 0) ? delta / max : 0; + if (saturation != 0){ + var rr = (max - red) / delta; + var gr = (max - green) / delta; + var br = (max - blue) / delta; + if (red == max) hue = br - gr; + else if (green == max) hue = 2 + rr - br; + else hue = 4 + gr - rr; + hue /= 6; + if (hue < 0) hue++; + } + return [Math.round(hue * 360), Math.round(saturation * 100), Math.round(brightness * 100)]; + }, + + hsbToRgb: function(){ + var br = Math.round(this[2] / 100 * 255); + if (this[1] == 0){ + return [br, br, br]; + } else { + var hue = this[0] % 360; + var f = hue % 60; + var p = Math.round((this[2] * (100 - this[1])) / 10000 * 255); + var q = Math.round((this[2] * (6000 - this[1] * f)) / 600000 * 255); + var t = Math.round((this[2] * (6000 - this[1] * (60 - f))) / 600000 * 255); + switch (Math.floor(hue / 60)){ + case 0: return [br, t, p]; + case 1: return [q, br, p]; + case 2: return [p, br, t]; + case 3: return [p, q, br]; + case 4: return [t, p, br]; + case 5: return [br, p, q]; + } + } + return false; + } + +}); + +String.implement({ + + rgbToHsb: function(){ + var rgb = this.match(/\d{1,3}/g); + return (rgb) ? rgb.rgbToHsb() : null; + }, + + hsbToRgb: function(){ + var hsb = this.match(/\d{1,3}/g); + return (hsb) ? hsb.hsbToRgb() : null; + } + +}); + +})(); + + +/* +--- + +script: Group.js + +name: Group + +description: Class for monitoring collections of events + +license: MIT-style license + +authors: + - Valerio Proietti + +requires: + - Core/Events + - MooTools.More + +provides: [Group] + +... +*/ + +(function(){ + +this.Group = new Class({ + + initialize: function(){ + this.instances = Array.flatten(arguments); + }, + + addEvent: function(type, fn){ + var instances = this.instances, + len = instances.length, + togo = len, + args = new Array(len), + self = this; + + instances.each(function(instance, i){ + instance.addEvent(type, function(){ + if (!args[i]) togo--; + args[i] = arguments; + if (!togo){ + fn.call(self, instances, instance, args); + togo = len; + args = new Array(len); + } + }); + }); + } + +}); + +})(); + +/* +--- + +script: Hash.Cookie.js + +name: Hash.Cookie + +description: Class for creating, reading, and deleting Cookies in JSON format. + +license: MIT-style license + +authors: + - Valerio Proietti + - Aaron Newton + +requires: + - Core/Cookie + - Core/JSON + - MooTools.More + - Hash + +provides: [Hash.Cookie] + +... +*/ + +Hash.Cookie = new Class({ + + Extends: Cookie, + + options: { + autoSave: true + }, + + initialize: function(name, options){ + this.parent(name, options); + this.load(); + }, + + save: function(){ + var value = JSON.encode(this.hash); + if (!value || value.length > 4096) return false; //cookie would be truncated! + if (value == '{}') this.dispose(); + else this.write(value); + return true; + }, + + load: function(){ + this.hash = new Hash(JSON.decode(this.read(), true)); + return this; + } + +}); + +Hash.each(Hash.prototype, function(method, name){ + if (typeof method == 'function') Hash.Cookie.implement(name, function(){ + var value = method.apply(this.hash, arguments); + if (this.options.autoSave) this.save(); + return value; + }); +}); + +/* +--- + +name: Swiff + +description: Wrapper for embedding SWF movies. Supports External Interface Communication. + +license: MIT-style license. + +credits: + - Flash detection & Internet Explorer + Flash Player 9 fix inspired by SWFObject. + +requires: [Core/Options, Core/Object, Core/Element] + +provides: Swiff + +... +*/ + +(function(){ + +var Swiff = this.Swiff = new Class({ + + Implements: Options, + + options: { + id: null, + height: 1, + width: 1, + container: null, + properties: {}, + params: { + quality: 'high', + allowScriptAccess: 'always', + wMode: 'window', + swLiveConnect: true + }, + callBacks: {}, + vars: {} + }, + + toElement: function(){ + return this.object; + }, + + initialize: function(path, options){ + this.instance = 'Swiff_' + String.uniqueID(); + + this.setOptions(options); + options = this.options; + var id = this.id = options.id || this.instance; + var container = document.id(options.container); + + Swiff.CallBacks[this.instance] = {}; + + var params = options.params, vars = options.vars, callBacks = options.callBacks; + var properties = Object.append({height: options.height, width: options.width}, options.properties); + + var self = this; + + for (var callBack in callBacks){ + Swiff.CallBacks[this.instance][callBack] = (function(option){ + return function(){ + return option.apply(self.object, arguments); + }; + })(callBacks[callBack]); + vars[callBack] = 'Swiff.CallBacks.' + this.instance + '.' + callBack; + } + + params.flashVars = Object.toQueryString(vars); + if ('ActiveXObject' in window){ + properties.classid = 'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000'; + params.movie = path; + } else { + properties.type = 'application/x-shockwave-flash'; + } + properties.data = path; + + var build = '<object id="' + id + '"'; + for (var property in properties) build += ' ' + property + '="' + properties[property] + '"'; + build += '>'; + for (var param in params){ + if (params[param]) build += '<param name="' + param + '" value="' + params[param] + '" />'; + } + build += '</object>'; + this.object = ((container) ? container.empty() : new Element('div')).set('html', build).firstChild; + }, + + replaces: function(element){ + element = document.id(element, true); + element.parentNode.replaceChild(this.toElement(), element); + return this; + }, + + inject: function(element){ + document.id(element, true).appendChild(this.toElement()); + return this; + }, + + remote: function(){ + return Swiff.remote.apply(Swiff, [this.toElement()].append(arguments)); + } + +}); + +Swiff.CallBacks = {}; + +Swiff.remote = function(obj, fn){ + var rs = obj.CallFunction('<invoke name="' + fn + '" returntype="javascript">' + __flash__argumentsToXML(arguments, 2) + '</invoke>'); + return eval(rs); +}; + +})(); + +/* +--- +name: Table +description: LUA-Style table implementation. +license: MIT-style license +authors: + - Valerio Proietti +requires: [Core/Array] +provides: [Table] +... +*/ + +(function(){ + +var Table = this.Table = function(){ + + this.length = 0; + var keys = [], + values = []; + + this.set = function(key, value){ + var index = keys.indexOf(key); + if (index == -1){ + var length = keys.length; + keys[length] = key; + values[length] = value; + this.length++; + } else { + values[index] = value; + } + return this; + }; + + this.get = function(key){ + var index = keys.indexOf(key); + return (index == -1) ? null : values[index]; + }; + + this.erase = function(key){ + var index = keys.indexOf(key); + if (index != -1){ + this.length--; + keys.splice(index, 1); + return values.splice(index, 1)[0]; + } + return null; + }; + + this.each = this.forEach = function(fn, bind){ + for (var i = 0, l = this.length; i < l; i++) fn.call(bind, keys[i], values[i], this); + }; + +}; + +if (this.Type) new Type('Table', Table); + +})(); diff --git a/pyload/webui/themes/Default/lib/MooTools/Purr/purr.js b/pyload/webui/themes/Default/lib/MooTools/Purr/purr.js new file mode 100644 index 000000000..9cbc503d9 --- /dev/null +++ b/pyload/webui/themes/Default/lib/MooTools/Purr/purr.js @@ -0,0 +1,309 @@ +/* +--- +script: purr.js + +description: Class to create growl-style popup notifications. + +license: MIT-style + +authors: [atom smith] + +requires: +- core/1.3: [Core, Browser, Array, Function, Number, String, Hash, Event, Class.Extras, Element.Event, Element.Style, Element.Dimensions, Fx.CSS, FX.Tween, Fx.Morph] + +provides: [Purr, Element.alert] +... +*/ + + +var Purr = new Class({ + + 'options': { + 'mode': 'top', + 'position': 'left', + 'elementAlertClass': 'purr-element-alert', + 'elements': { + 'wrapper': 'div', + 'alert': 'div', + 'buttonWrapper': 'div', + 'button': 'button' + }, + 'elementOptions': { + 'wrapper': { + 'styles': { + 'position': 'fixed', + 'z-index': '9999' + }, + 'class': 'purr-wrapper' + }, + 'alert': { + 'class': 'purr-alert', + 'styles': { + 'opacity': '.85' + } + }, + 'buttonWrapper': { + 'class': 'purr-button-wrapper' + }, + 'button': { + 'class': 'purr-button' + } + }, + 'alert': { + 'buttons': [], + 'clickDismiss': true, + 'hoverWait': true, + 'hideAfter': 5000, + 'fx': { + 'duration': 500 + }, + 'highlight': false, + 'highlightRepeat': false, + 'highlight': { + 'start': '#FF0', + 'end': false + } + } + }, + + 'Implements': [Options, Events, Chain], + + 'initialize': function(options){ + this.setOptions(options); + this.createWrapper(); + return this; + }, + + 'bindAlert': function(){ + return this.alert.bind(this); + }, + + 'createWrapper': function(){ + this.wrapper = new Element(this.options.elements.wrapper, this.options.elementOptions.wrapper); + if(this.options.mode == 'top') + { + this.wrapper.setStyle('top', 0); + } + else + { + this.wrapper.setStyle('bottom', 0); + } + document.id(document.body).grab(this.wrapper); + this.positionWrapper(this.options.position); + }, + + 'positionWrapper': function(position){ + if(typeOf(position) == 'object') + { + + var wrapperCoords = this.getWrapperCoords(); + + this.wrapper.setStyles({ + 'bottom': '', + 'left': position.x, + 'top': position.y - wrapperCoords.height, + 'position': 'absolute' + }); + } + else if(position == 'left') + { + this.wrapper.setStyle('left', 0); + } + else if(position == 'right') + { + this.wrapper.setStyle('right', 0); + } + else + { + this.wrapper.setStyle('left', (window.innerWidth / 2) - (this.getWrapperCoords().width / 2)); + } + return this; + }, + + 'getWrapperCoords': function(){ + this.wrapper.setStyle('visibility', 'hidden'); + var measurer = this.alert('need something in here to measure'); + var coords = this.wrapper.getCoordinates(); + measurer.destroy(); + this.wrapper.setStyle('visibility',''); + return coords; + }, + + 'alert': function(msg, options){ + + options = Object.merge({}, this.options.alert, options || {}); + + var alert = new Element(this.options.elements.alert, this.options.elementOptions.alert); + + if(typeOf(msg) == 'string') + { + alert.set('html', msg); + } + else if(typeOf(msg) == 'element') + { + alert.grab(msg); + } + else if(typeOf(msg) == 'array') + { + var alerts = []; + msg.each(function(m){ + alerts.push(this.alert(m, options)); + }, this); + return alerts; + } + + alert.store('options', options); + + if(options.buttons.length > 0) + { + options.clickDismiss = false; + options.hideAfter = false; + options.hoverWait = false; + var buttonWrapper = new Element(this.options.elements.buttonWrapper, this.options.elementOptions.buttonWrapper); + alert.grab(buttonWrapper); + options.buttons.each(function(button){ + if(button.text != undefined) + { + var callbackButton = new Element(this.options.elements.button, this.options.elementOptions.button); + callbackButton.set('html', button.text); + if(button.callback != undefined) + { + callbackButton.addEvent('click', button.callback.pass(alert)); + } + if(button.dismiss != undefined && button.dismiss) + { + callbackButton.addEvent('click', this.dismiss.pass(alert, this)); + } + buttonWrapper.grab(callbackButton); + } + }, this); + } + if(options.className != undefined) + { + alert.addClass(options.className); + } + + this.wrapper.grab(alert, (this.options.mode == 'top') ? 'bottom' : 'top'); + + var fx = Object.merge(this.options.alert.fx, options.fx); + var alertFx = new Fx.Morph(alert, fx); + alert.store('fx', alertFx); + this.fadeIn(alert); + + if(options.highlight) + { + alertFx.addEvent('complete', function(){ + alert.highlight(options.highlight.start, options.highlight.end); + if(options.highlightRepeat) + { + alert.highlight.periodical(options.highlightRepeat, alert, [options.highlight.start, options.highlight.end]); + } + }); + } + if(options.hideAfter) + { + this.dismiss(alert); + } + + if(options.clickDismiss) + { + alert.addEvent('click', function(){ + this.holdUp = false; + this.dismiss(alert, true); + }.bind(this)); + } + + if(options.hoverWait) + { + alert.addEvents({ + 'mouseenter': function(){ + this.holdUp = true; + }.bind(this), + 'mouseleave': function(){ + this.holdUp = false; + }.bind(this) + }); + } + + return alert; + }, + + 'fadeIn': function(alert){ + var alertFx = alert.retrieve('fx'); + alertFx.set({ + 'opacity': 0 + }); + alertFx.start({ + 'opacity': [this.options.elementOptions.alert.styles.opacity, .9].pick(), + }); + }, + + 'dismiss': function(alert, now){ + now = now || false; + var options = alert.retrieve('options'); + if(now) + { + this.fadeOut(alert); + } + else + { + this.fadeOut.delay(options.hideAfter, this, alert); + } + }, + + 'fadeOut': function(alert){ + if(this.holdUp) + { + this.dismiss.delay(100, this, [alert, true]) + return null; + } + var alertFx = alert.retrieve('fx'); + if(!alertFx) + { + return null; + } + var to = { + 'opacity': 0 + } + if(this.options.mode == 'top') + { + to['margin-top'] = '-'+alert.offsetHeight+'px'; + } + else + { + to['margin-bottom'] = '-'+alert.offsetHeight+'px'; + } + alertFx.start(to); + alertFx.addEvent('complete', function(){ + alert.destroy(); + }); + } +}); + +Element.implement({ + + 'alert': function(msg, options){ + var alert = this.retrieve('alert'); + if(!alert) + { + options = options || { + 'mode':'top' + }; + alert = new Purr(options) + this.store('alert', alert); + } + + var coords = this.getCoordinates(); + + alert.alert(msg, options); + + alert.wrapper.setStyles({ + 'bottom': '', + 'left': (coords.left - (alert.wrapper.getWidth() / 2)) + (this.getWidth() / 2), + 'top': coords.top - (alert.wrapper.getHeight()), + 'position': 'absolute' + }); + + } + +});
\ No newline at end of file diff --git a/pyload/webui/themes/Default/lib/MooTools/TinyTab/tinytab.js b/pyload/webui/themes/Default/lib/MooTools/TinyTab/tinytab.js new file mode 100644 index 000000000..de50279fc --- /dev/null +++ b/pyload/webui/themes/Default/lib/MooTools/TinyTab/tinytab.js @@ -0,0 +1,43 @@ +/* +--- +description: TinyTab - Tiny and simple tab handler for Mootools. + +license: MIT-style + +authors: +- Danillo César de O. Melo + +requires: +- core/1.2.4: '*' + +provides: TinyTab + +... +*/ +(function($) { + this.TinyTab = new Class({ + Implements: Events, + initialize: function(tabs, contents, opt) { + this.tabs = tabs; + this.contents = contents; + if(!opt) opt = {}; + this.css = opt.selectedClass || 'selected'; + this.select(this.tabs[0]); + tabs.each(function(el){ + el.addEvent('click',function(e){ + this.select(el); + e.stop(); + }.bind(this)); + }.bind(this)); + }, + + select: function(el) { + this.tabs.removeClass(this.css); + el.addClass(this.css); + this.contents.setStyle('display','none'); + var content = this.contents[this.tabs.indexOf(el)]; + content.setStyle('display','block'); + this.fireEvent('change',[content,el]); + } + }); +})(document.id);
\ No newline at end of file diff --git a/pyload/webui/themes/Default/tml/admin.html b/pyload/webui/themes/Default/tml/admin.html new file mode 100644 index 000000000..c5cdb494b --- /dev/null +++ b/pyload/webui/themes/Default/tml/admin.html @@ -0,0 +1,98 @@ +{% extends '/tml/base.html' %} + +{% block head %} + <script type="text/javascript" src="/js/admin.min.js"></script> +{% endblock %} + + +{% block title %}{{ _("Administrate") }} - {{ super() }} {% endblock %} +{% block subtitle %}{{ _("Administrate") }}{% endblock %} + +{% block content %} + + <a href="#" id="quit-pyload" style="font-size: large; font-weight: bold;">{{_("Quit pyLoad")}}</a> | + <a href="#" id="restart-pyload" style="font-size: large; font-weight: bold;">{{_("Restart pyLoad")}}</a> + <br> + <br> + + {{ _("To add user or change passwords use:") }} <b>python pyload.py -u</b><br> + {{ _("Important: Admin user have always all permissions!") }} + + <form action="" method="POST"> + <table class="settable wide"> + <thead style="font-size: 11px"> + <th> + {{ _("Name") }} + </th> + <th> + {{ _("Change Password") }} + </th> + <th> + {{ _("Admin") }} + </th> + <th> + {{ _("Permissions") }} + </th> + </thead> + + {% for name, data in users.iteritems() %} + <tr> + <td>{{ name }}</td> + <td><a class="change_password" href="#" id="change_pw|{{name}}">{{ _("change") }}</a></td> + <td><input name="{{ name }}|admin" type="checkbox" {% if data.perms.admin %} + checked="True" {% endif %}"></td> + <td> + <select multiple="multiple" size="{{ permlist|length }}" name="{{ name }}|perms"> + {% for perm in permlist %} + {% if data.perms|getitem(perm) %} + <option selected="selected">{{ perm }}</option> + {% else %} + <option>{{ perm }}</option> + {% endif %} + {% endfor %} + </select> + </td> + </tr> + {% endfor %} + + + </table> + + <button class="styled_button" type="submit">{{ _("Submit") }}</button> + </form> +{% endblock %} +{% block hidden %} + <div id="password_box" class="window_box" style="z-index: 2"> + <form id="password_form" action="/json/change_password" method="POST" enctype="multipart/form-data"> + <h1>{{ _("Change Password") }}</h1> + + <p>{{ _("Enter your current and desired Password.") }}</p> + <label for="user_login">{{ _("User") }} + <span class="small">{{ _("Your username.") }}</span> + </label> + <input id="user_login" name="user_login" type="text" size="20"/> + + <label for="login_current_password">{{ _("Current password") }} + <span class="small">{{ _("The password for this account.") }}</span> + </label> + <input id="login_current_password" name="login_current_password" type="password" size="20"/> + + <label for="login_new_password">{{ _("New password") }} + <span class="small">{{ _("The new password.") }}</span> + </label> + <input id="login_new_password" name="login_new_password" type="password" size="20"/> + + <label for="login_new_password2">{{ _("New password (repeat)") }} + <span class="small">{{ _("Please repeat the new password.") }}</span> + </label> + <input id="login_new_password2" name="login_new_password2" type="password" size="20"/> + + + <button id="login_password_button" type="submit">{{ _("Submit") }}</button> + <button id="login_password_reset" style="margin-left: 0" type="reset">{{ _("Reset") }}</button> + <div class="spacer"></div> + + </form> + + </div> +{% endblock %} diff --git a/pyload/webui/themes/Default/tml/base.html b/pyload/webui/themes/Default/tml/base.html new file mode 100644 index 000000000..116fb9b51 --- /dev/null +++ b/pyload/webui/themes/Default/tml/base.html @@ -0,0 +1,177 @@ +<?xml version="1.0" ?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + +<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> +<link rel="stylesheet" type="text/css" href="/css/base.css"/> +<link rel="stylesheet" type="text/css" href="/css/window.css"/> +<link rel="stylesheet" type="text/css" href="/lib/MooTools/MooDialog/css/MooDialog.css"/> + +<script type="text/javascript" src="/lib/MooTools/MooTools-Core.js"></script> +<script type="text/javascript" src="/lib/MooTools/MooTools-More.js"></script> +<script type="text/javascript" src="/lib/MooTools/MooDialog/MooDialog.js"></script> +<script type="text/javascript" src="/lib/MooTools/Purr/purr.js"></script> + + +<script type="text/javascript" src="/js/base.min.js"></script> + +<title>{% block title %}pyLoad {{_("Webinterface")}}{% endblock %}</title> + +{% block head %} +{% endblock %} +</head> +<body> +<a class="anchor" name="top" id="top"></a> + +<div id="head-panel"> + + + <div id="head-search-and-login"> + {% block headpanel %} + + {% if user.is_authenticated %} + + +{% if update %} +<span> +<span style="font-weight: bold; margin: 0 2px 0 2px;">{{_("New pyLoad version %s available!") % update}}</span> +</span> +{% endif %} + + +{% if plugins %} +<span> +<span style="font-weight: bold; margin: 0 2px 0 2px;">{{_("Plugins updated, please restart!")}}</span> +</span> +{% endif %} + +<span id="cap_info" style="display: {% if captcha %}inline{%else%}none{% endif %}"> +<img src="/img/images.png" alt="Captcha:" style="vertical-align:middle; margin:2px" /> +<span style="font-weight: bold; cursor: pointer; margin-right: 2px;">{{_("Captcha waiting")}}</span> +</span> + + <img src="/img/head-login.png" alt="User:" style="vertical-align:middle; margin:2px" /><span style="padding-right: 2px;">{{user.name}}</span> + <ul id="user-actions"> + <li><a href="/logout" class="action logout" rel="nofollow">{{_("Logout")}}</a></li> + {% if user.is_admin %} + <li><a href="/admin" class="action profile" rel="nofollow">{{_("Administrate")}}</a></li> + {% endif %} + <li><a href="/info" class="action info" rel="nofollow">{{_("Info")}}</a></li> + + </ul> +{% else %} + <span style="padding-right: 2px;">{{_("Please Login!")}}</span> +{% endif %} + + {% endblock %} + </div> + + <a href="/"><img id="head-logo" src="/img/pyload-logo.png" alt="pyLoad" /></a> + + <div id="head-menu"> + <ul> + + {% macro selected(name, right=False) -%} + {% if name in url -%}class="{% if right -%}right {% endif %}selected"{%- endif %} + {% if not name in url and right -%}class="right"{%- endif %} + {%- endmacro %} + + + {% block menu %} + <li> + <a href="/" title=""><img src="/img/head-menu-home.png" alt="" /> {{_("Home")}}</a> + </li> + <li {{ selected('queue') }}> + <a href="/queue/" title=""><img src="/img/head-menu-queue.png" alt="" /> {{_("Queue")}}</a> + </li> + <li {{ selected('collector') }}> + <a href="/collector/" title=""><img src="/img/head-menu-collector.png" alt="" /> {{_("Collector")}}</a> + </li> + <li {{ selected('downloads') }}> + <a href="/downloads/" title=""><img src="/img/head-menu-development.png" alt="" /> {{_("Downloads")}}</a> + </li> + <li {{ selected('logs', True) }}> + <a href="/logs/" title=""><img src="/img/head-menu-index.png" alt="" />{{_("Logs")}}</a> + </li> + <li {{ selected('settings', True) }}> + <a href="/settings/" title=""><img src="/img/head-menu-config.png" alt="" />{{_("Config")}}</a> + </li> + {% endblock %} + + </ul> + </div> + + <div style="clear:both;"></div> +</div> + +{% if perms.STATUS %} +<ul id="page-actions2"> + <li id="action_play"><a href="#" class="action play" accesskey="o" rel="nofollow">{{_("Start")}}</a></li> + <li id="action_stop"><a href="#" class="action stop" accesskey="o" rel="nofollow">{{_("Stop")}}</a></li> + <li id="action_cancel"><a href="#" class="action cancel" accesskey="o" rel="nofollow">{{_("Cancel")}}</a></li> + <li id="action_add"><a href="#" class="action add" accesskey="o" rel="nofollow" >{{_("Add")}}</a></li> +</ul> +{% endif %} + +{% if perms.LIST %} +<ul id="page-actions"> + <li><span class="time">{{_("Download:")}}</span><a id="time" style=" background-color: {% if status.download %}#8ffc25{% else %} #fc6e26{% endif %}; padding-left: 0cm; padding-right: 0.1cm; "> {% if status.download %}{{_("on")}}{% else %}{{_("off")}}{% endif %}</a></li> + <li><span class="reconnect">{{_("Reconnect:")}}</span><a id="reconnect" style=" background-color: {% if status.reconnect %}#8ffc25{% else %} #fc6e26{% endif %}; padding-left: 0cm; padding-right: 0.1cm; "> {% if status.reconnect %}{{_("on")}}{% else %}{{_("off")}}{% endif %}</a></li> + <li><a class="action backlink">{{_("Speed:")}} <b id="speed">{{ status.speed }}</b></a></li> + <li><a class="action cog">{{_("Active:")}} <b id="aktiv" title="{{_("Active")}}">{{ status.active }}</b> / <b id="aktiv_from" title="{{_("Queued")}}">{{ status.queue }}</b> / <b id="aktiv_total" title="{{_("Total")}}">{{ status.total }}</b></a></li> + <li><a href="" class="action revisions" accesskey="o" rel="nofollow">{{_("Reload page")}}</a></li> +</ul> +{% endif %} + +{% block pageactions %} +{% endblock %} +<br/> + +<div id="body-wrapper" class="dokuwiki"> + +<div id="content" lang="en" dir="ltr"> + +<h1>{% block subtitle %}pyLoad - {{_("Webinterface")}}{% endblock %}</h1> + +{% block statusbar %} +{% endblock %} + + +<br/> + +<div class="level1" style="clear:both"> +</div> +<noscript><h1>Enable JavaScript to use the webinterface.</h1></noscript> + +{% for message in messages %} + <b><p>{{message}}</p></b> +{% endfor %} + +<div id="load-indicator" style="opacity: 0; float: right; margin-top: -10px;"> + <img src="/img/ajax-loader.gif" alt="" style="padding-right: 5px"/> + {{_("loading")}} +</div> + +{% block content %} +{% endblock content %} + + <hr style="clear: both;" /> + +<div id="foot">© 2008-2015 pyLoad Team +<a href="#top" class="action top" accesskey="x"><span>{{_("Back to top")}}</span></a><br /> +<!--<div class="breadcrumbs"></div>--> + +</div> +</div> +</div> + +<div style="display: none;"> + {% include '/tml/window.html' %} + {% include '/tml/captcha.html' %} + {% block hidden %} + {% endblock %} +</div> +</body> +</html> diff --git a/pyload/webui/themes/Default/tml/captcha.html b/pyload/webui/themes/Default/tml/captcha.html new file mode 100644 index 000000000..3bfa6cbcf --- /dev/null +++ b/pyload/webui/themes/Default/tml/captcha.html @@ -0,0 +1,42 @@ +<!-- Captcha box --> +<div id="cap_box" class="window_box"> + + <form id="cap_form" action="/json/set_captcha" method="POST" enctype="multipart/form-data" onsubmit="return false;"> + + <h1>{{_("Captcha reading")}}</h1> + <p id="cap_title">{{_("Please read the text on the captcha.")}}</p> + + <div id="cap_textual"> + + <input id="cap_id" name="cap_id" type="hidden" value="" /> + + <label>{{_("Captcha")}} + <span class="small">{{_("The captcha.")}}</span> + </label> + <span class="cont"> + <img id="cap_textual_img" src=""> + </span> + + <label>{{_("Text")}} + <span class="small">{{_("Input the text on the captcha.")}}</span> + </label> + <input id="cap_result" name="cap_result" type="text" size="20" /> + + </div> + + <div id="cap_positional" style="text-align: center"> + <img id="cap_positional_img" src="" style="margin: 10px; cursor:pointer"> + </div> + + <div id="button_bar" style="text-align: center"> + <span> + <button id="cap_submit" type="submit" style="margin-left: 0">{{_("Submit")}}</button> + <button id="cap_reset" type="reset" style="margin-left: 0">{{_("Close")}}</button> + </span> + </div> + + <div class="spacer"></div> + + </form> + +</div> diff --git a/pyload/webui/themes/Default/tml/downloads.html b/pyload/webui/themes/Default/tml/downloads.html new file mode 100644 index 000000000..f6e581c5b --- /dev/null +++ b/pyload/webui/themes/Default/tml/downloads.html @@ -0,0 +1,29 @@ +{% extends '/tml/base.html' %} + +{% block title %}Downloads - {{super()}} {% endblock %} + +{% block subtitle %} +{{_("Downloads")}} +{% endblock %} + +{% block content %} + +<ul> + {% for folder in files.folder %} + <li> + {{ folder.name }} + <ul> + {% for file in folder.files %} + <li><a href='get/{{ folder.path|escape }}/{{ file|escape }}'>{{file}}</a></li> + {% endfor %} + </ul> + </li> + {% endfor %} + + {% for file in files.files %} + <li> <a href='get/{{ file|escape }}'>{{ file }}</a></li> + {% endfor %} + +</ul> + +{% endblock %} diff --git a/pyload/webui/themes/Default/tml/filemanager.html b/pyload/webui/themes/Default/tml/filemanager.html new file mode 100644 index 000000000..102d53e7c --- /dev/null +++ b/pyload/webui/themes/Default/tml/filemanager.html @@ -0,0 +1,76 @@ +{% extends '/tml/base.html' %} + +{% block head %} + +<script type="text/javascript"> + +document.addEvent("domready", function(){ + var fmUI = new FilemanagerUI("url",1); +}); +</script> +{% endblock %} + +{% block title %}Downloads - {{super()}} {% endblock %} + + +{% block subtitle %} +{{_("FileManager")}} +{% endblock %} + +{% macro display_file(file) %} + <li class="file"> + <input type="hidden" name="path" class="path" value="{{ file.path }}" /> + <input type="hidden" name="name" class="name" value="{{ file.name }}" /> + <span> + <b>{{ file.name }}</b> + <span class="buttons" style="opacity:0"> + <img title="{{_("Rename Directory")}}" class="rename" style="cursor: pointer" height="12px" src="/img/pencil.png" /> + + <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/delete.png" /> + </span> + </span> + </li> +{%- endmacro %} + +{% macro display_folder(fld, open = false) -%} + <li class="folder"> + <input type="hidden" name="path" class="path" value="{{ fld.path }}" /> + <input type="hidden" name="name" class="name" value="{{ fld.name }}" /> + <span> + <b>{{ fld.name }}</b> + <span class="buttons" style="opacity:0"> + <img title="{{_("Rename Directory")}}" class="rename" style="cursor: pointer" height="12px" src="/img/pencil.png" /> + + <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/delete.png" /> + + <img title="{{_("Add subdirectory")}}" class="mkdir" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/add_folder.png" /> + </span> + </span> + {% if (fld.folders|length + fld.files|length) > 0 %} + {% if open %} + <ul> + {% else %} + <ul style="display:none"> + {% endif %} + {% for child in fld.folders %} + {{ display_folder(child) }} + {% endfor %} + {% for child in fld.files %} + {{ display_file(child) }} + {% endfor %} + </ul> + {% else %} + <div style="display:none">{{ _("Folder is empty") }}</div> + {% endif %} + </li> +{%- endmacro %} + +{% block content %} + +<div style="clear:both"><!-- --></div> + +<ul id="directories-list"> +{{ display_folder(root, true) }} +</ul> + +{% endblock %} diff --git a/pyload/webui/themes/Default/tml/folder.html b/pyload/webui/themes/Default/tml/folder.html new file mode 100644 index 000000000..21f3f4a03 --- /dev/null +++ b/pyload/webui/themes/Default/tml/folder.html @@ -0,0 +1,15 @@ +<li class="folder"> + <input type="hidden" name="path" class="path" value="{{ path }}" /> + <input type="hidden" name="name" class="name" value="{{ name }}" /> + <span> + <b>{{ name }}</b> + <span class="buttons" style="opacity:0"> + <img title="{{_("Rename Directory")}}" class="rename" style="cursor: pointer" height="12px" src="/img/pencil.png" /> + + <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/delete.png" /> + + <img title="{{_("Add subdirectory")}}" class="mkdir" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/add_folder.png" /> + </span> + </span> + <div style="display:none">{{ _("Folder is empty") }}</div> +</li> diff --git a/pyload/webui/themes/Default/tml/home.html b/pyload/webui/themes/Default/tml/home.html new file mode 100644 index 000000000..6ce60de0b --- /dev/null +++ b/pyload/webui/themes/Default/tml/home.html @@ -0,0 +1,263 @@ +{% extends '/tml/base.html' %} +{% block head %} + +<script type="text/javascript"> + +var em; +var operafix = (navigator.userAgent.toLowerCase().search("opera") >= 0); + +document.addEvent("domready", function(){ + em = new EntryManager(); +}); + +var EntryManager = new Class({ + initialize: function(){ + this.json = new Request.JSON({ + url: "json/links", + secure: false, + async: true, + onSuccess: this.update.bind(this), + initialDelay: 0, + delay: 2500, + limit: 30000 + }); + + this.ids = [{% for link in content %} + {% if forloop.last %} + {{ link.id }} + {% else %} + {{ link.id }}, + {% endif %} + {% endfor %}]; + + this.entries = []; + this.container = $('LinksAktiv'); + + this.parseFromContent(); + + this.json.startTimer(); + }, + parseFromContent: function(){ + this.ids.each(function(id,index){ + var entry = new LinkEntry(id); + entry.parse(); + this.entries.push(entry) + }, this); + }, + update: function(data){ + + try{ + this.ids = this.entries.map(function(item){ + return item.fid + }); + + this.ids.filter(function(id){ + return !this.ids.contains(id) + },data).each(function(id){ + var index = this.ids.indexOf(id); + this.entries[index].remove(); + this.entries = this.entries.filter(function(item){return item.fid != this},id); + this.ids = this.ids.erase(id) + }, this); + + data.links.each(function(link, i){ + if (this.ids.contains(link.fid)){ + + var index = this.ids.indexOf(link.fid); + this.entries[index].update(link) + + }else{ + var entry = new LinkEntry(link.fid); + entry.insert(link); + this.entries.push(entry); + this.ids.push(link.fid); + this.container.adopt(entry.elements.tr,entry.elements.pgbTr); + entry.fade.start('opacity', 1); + entry.fadeBar.start('opacity', 1); + + } + }, this) + }catch(e){ + //alert(e) + } + } +}); + + +var LinkEntry = new Class({ + initialize: function(id){ + this.fid = id; + this.id = id; + }, + parse: function(){ + this.elements = { + tr: $("link_{id}".substitute({id: this.id})), + name: $("link_{id}_name".substitute({id: this.id})), + status: $("link_{id}_status".substitute({id: this.id})), + info: $("link_{id}_info".substitute({id: this.id})), + bleft: $("link_{id}_bleft".substitute({id: this.id})), + percent: $("link_{id}_percent".substitute({id: this.id})), + remove: $("link_{id}_remove".substitute({id: this.id})), + pgbTr: $("link_{id}_pgb_tr".substitute({id: this.id})), + pgb: $("link_{id}_pgb".substitute({id: this.id})) + }; + this.initEffects(); + }, + insert: function(item){ + try{ + + this.elements = { + tr: new Element('tr', { + 'html': '', + 'styles':{ + 'opacity': 0 + } + }), + name: new Element('td', { + 'html': item.name + }), + status: new Element('td', { + 'html': item.statusmsg + }), + info: new Element('td', { + 'html': item.info + }), + bleft: new Element('td', { + 'html': humanFileSize(item.size) + }), + percent: new Element('span', { + 'html': item.percent+ '% / '+ humanFileSize(item.size-item.bleft) + }), + remove: new Element('img',{ + 'src': '/img/control_cancel.png', + 'styles':{ + 'vertical-align': 'middle', + 'margin-right': '-20px', + 'margin-left': '5px', + 'margin-top': '-2px', + 'cursor': 'pointer' + } + }), + pgbTr: new Element('tr', { + 'html':'' + }), + pgb: new Element('div', { + 'html': ' ', + 'styles':{ + 'height': '4px', + 'width': item.percent+'%', + 'background-color': '#ddd' + } + }) + }; + + this.elements.tr.adopt(this.elements.name,this.elements.status,this.elements.info,this.elements.bleft,new Element('td').adopt(this.elements.percent,this.elements.remove)); + this.elements.pgbTr.adopt(new Element('td',{'colspan':5}).adopt(this.elements.pgb)); + this.initEffects(); + }catch(e){ + alert(e) + } + }, + initEffects: function(){ + if(!operafix) + this.bar = new Fx.Morph(this.elements.pgb, {unit: '%', duration: 5000, link: 'link', fps:30}); + this.fade = new Fx.Tween(this.elements.tr); + this.fadeBar = new Fx.Tween(this.elements.pgbTr); + + this.elements.remove.addEvent('click', function(){ + new Request({method: 'get', url: '/json/abort_link/'+this.id}).send(); + }.bind(this)); + + }, + update: function(item){ + this.elements.name.set('text', item.name); + this.elements.status.set('text', item.statusmsg); + this.elements.info.set('text', item.info); + this.elements.bleft.set('text', item.format_size); + this.elements.percent.set('text', item.percent+ '% / '+ humanFileSize(item.size-item.bleft)); + if(!operafix) + { + this.bar.start({ + 'width': item.percent, + 'background-color': [Math.round(120/100*item.percent),100,100].hsbToRgb().rgbToHex() + }); + } + else + { + this.elements.pgb.set( + 'styles', { + 'height': '4px', + 'width': item.percent+'%', + 'background-color': [Math.round(120/100*item.percent),100,100].hsbToRgb().rgbToHex(), + }); + } + }, + remove: function(){ + this.fade.start('opacity',0).chain(function(){this.elements.tr.dispose();}.bind(this)); + this.fadeBar.start('opacity',0).chain(function(){this.elements.pgbTr.dispose();}.bind(this)); + + } + }); +</script> + +{% endblock %} + +{% block subtitle %} +{{_("Active Downloads")}} +{% endblock %} + +{% block menu %} +<li class="selected"> + <a href="/" title=""><img src="/img/head-menu-home.png" alt="" /> {{_("Home")}}</a> +</li> +<li> + <a href="/queue/" title=""><img src="/img/head-menu-queue.png" alt="" /> {{_("Queue")}}</a> +</li> +<li> + <a href="/collector/" title=""><img src="/img/head-menu-collector.png" alt="" /> {{_("Collector")}}</a> +</li> +<li> + <a href="/downloads/" title=""><img src="/img/head-menu-development.png" alt="" /> {{_("Downloads")}}</a> +</li> +<li class="right"> + <a href="/logs/" title=""><img src="/img/head-menu-index.png" alt="" />{{_("Logs")}}</a> +</li> +<li class="right"> + <a href="/settings/" title=""><img src="/img/head-menu-config.png" alt="" />{{_("Config")}}</a> +</li> +{% endblock %} + +{% block content %} +<table width="100%" class="queue"> + <thead> + <tr class="header"> + <th>{{_("Name")}}</th> + <th>{{_("Status")}}</th> + <th>{{_("Information")}}</th> + <th>{{_("Size")}}</th> + <th>{{_("Progress")}}</th> + </tr> + </thead> + <tbody id="LinksAktiv"> + + {% for link in content %} + <tr id="link_{{ link.id }}"> + <td id="link_{{ link.id }}_name">{{ link.name }}</td> + <td id="link_{{ link.id }}_status">{{ link.status }}</td> + <td id="link_{{ link.id }}_info">{{ link.info }}</td> + <td id="link_{{ link.id }}_bleft">{{ link.format_size }}</td> + <td> + <span id="link_{{ link.id }}_percent">{{ link.percent }}% /{{ link.bleft }}</span> + <img id="link_{{ link.id }}_remove" style="vertical-align: middle; margin-right: -20px; margin-left: 5px; margin-top: -2px; cursor:pointer;" src="/img/control_cancel.png"/> + </td> + </tr> + <tr id="link_{{ link.id }}_pgb_tr"> + <td colspan="5"> + <div id="link_{{ link.id }}_pgb" class="progressBar" style="background-color: green; height:4px; width: {{ link.percent }}%;"> </div> + </td> + </tr> + {% endfor %} + + </tbody> +</table> +{% endblock %} diff --git a/pyload/webui/themes/Default/tml/info.html b/pyload/webui/themes/Default/tml/info.html new file mode 100644 index 000000000..e62a2d6e8 --- /dev/null +++ b/pyload/webui/themes/Default/tml/info.html @@ -0,0 +1,81 @@ +{% extends '/tml/base.html' %} + +{% block head %} + <script type="text/javascript"> + window.addEvent("domready", function() { + var ul = new Element('ul#twitter_update_list'); + var script1 = new Element('script[src=http://twitter.com/javascripts/blogger.min.js][type=text/javascript]'); + var script2 = new Element('script[src=http://twitter.com/statuses/user_timeline/pyLoad.json?callback=twitterCallback2&count=6][type=text/javascript]'); + $("twitter").adopt(ul, script1, script2); + }); + </script> +{% endblock %} + +{% block title %}{{ _("Information") }} - {{ super() }} {% endblock %} +{% block subtitle %}{{ _("Information") }}{% endblock %} + +{% block content %} + <h3>{{ _("News") }}</h3> + <div id="twitter"></div> + + <h3>{{ _("Support") }}</h3> + + <ul> + <li style="font-weight:bold;"> + <a href="http://pyload.org/wiki" target="_blank">Wiki</a> + | + <a href="http://forum.pyload.org/" target="_blank">Forum</a> + | + <a href="http://pyload.org/irc/" target="_blank">Chat</a> + </li> + <li style="font-weight:bold;"><a href="http://docs.pyload.org" target="_blank">Documentation</a></li> + <li style="font-weight:bold;"><a href="https://bitbucket.org/spoob/pyload/overview" target="_blank">Development</a></li> + <li style="font-weight:bold;"><a href="https://bitbucket.org/spoob/pyload/issues?status=new&status=open" target="_blank">Issue Tracker</a></li> + + </ul> + + <h3>{{ _("System") }}</h3> + <table class="system"> + <tr> + <td>{{ _("Python:") }}</td> + <td>{{ python }}</td> + </tr> + <tr> + <td>{{ _("OS:") }}</td> + <td>{{ os }}</td> + </tr> + <tr> + <td>{{ _("pyLoad version:") }}</td> + <td>{{ version }}</td> + </tr> + <tr> + <td>{{ _("Installation Folder:") }}</td> + <td>{{ folder }}</td> + </tr> + <tr> + <td>{{ _("Config Folder:") }}</td> + <td>{{ config }}</td> + </tr> + <tr> + <td>{{ _("Download Folder:") }}</td> + <td>{{ download }}</td> + </tr> + <tr> + <td>{{ _("Free Space:") }}</td> + <td>{{ freespace }}</td> + </tr> + <tr> + <td>{{ _("Language:") }}</td> + <td>{{ language }}</td> + </tr> + <tr> + <td>{{ _("Webinterface Port:") }}</td> + <td>{{ webif }}</td> + </tr> + <tr> + <td>{{ _("Remote Interface Port:") }}</td> + <td>{{ remote }}</td> + </tr> + </table> + +{% endblock %} diff --git a/pyload/webui/themes/Default/tml/login.html b/pyload/webui/themes/Default/tml/login.html new file mode 100644 index 000000000..74bbb70a4 --- /dev/null +++ b/pyload/webui/themes/Default/tml/login.html @@ -0,0 +1,36 @@ +{% extends '/tml/base.html' %} + +{% block title %}{{_("Login")}} - {{super()}} {% endblock %} + +{% block content %} + +<div class="centeralign"> +<form action="" method="post" accept-charset="utf-8" id="login"> + <div class="no"> + <input type="hidden" name="do" value="login" /> + <fieldset> + <legend>Login</legend> + <label> + <span>{{_("Username")}}</span> + <input type="text" size="20" name="username" /> + </label> + <br /> + <label> + <span>{{_("Password")}}</span> + <input type="password" size="20" name="password" /> + </label> + <br /> + <input type="submit" value="Login" class="button" /> + </fieldset> + </div> +</form> + +{% if errors %} +<p>{{_("Your username and password didn't match. Please try again.")}}</p> + {{ _("To reset your login data or add an user run:") }} <b> python pyload.py -u</b> +{% endif %} + +</div> +<br> + +{% endblock %} diff --git a/pyload/webui/themes/Default/tml/logout.html b/pyload/webui/themes/Default/tml/logout.html new file mode 100644 index 000000000..db7e9290e --- /dev/null +++ b/pyload/webui/themes/Default/tml/logout.html @@ -0,0 +1,9 @@ +{% extends '/tml/base.html' %} + +{% block head %} +<meta http-equiv="refresh" content="3; url=/"> +{% endblock %} + +{% block content %} +<p><b>{{_("You were successfully logged out.")}}</b></p> +{% endblock %} diff --git a/pyload/webui/themes/Default/tml/logs.html b/pyload/webui/themes/Default/tml/logs.html new file mode 100644 index 000000000..429306aae --- /dev/null +++ b/pyload/webui/themes/Default/tml/logs.html @@ -0,0 +1,41 @@ +{% extends '/tml/base.html' %} + +{% block title %}{{_("Logs")}} - {{super()}} {% endblock %} +{% block subtitle %}{{_("Logs")}}{% endblock %} +{% block head %} +<link rel="stylesheet" type="text/css" href="/css/log.css"/> +{% endblock %} + +{% block content %} +<div style="clear: both;"></div> + +<div class="logpaginator"><a href="{{ "/logs/1" }}"><< {{_("Start")}}</a> <a href="{{ "/logs/" + iprev|string }}">< {{_("prev")}}</a> <a href="{{ "/logs/" + inext|string }}">{{_("next")}} ></a> <a href="/logs/">{{_("End")}} >></a></div> +<div class="logperpage"> + <form id="logform1" action="" method="POST"> + <label for="reversed">Reversed:</label> + <input type="checkbox" name="reversed" onchange="this.form.submit();" {% if reversed %} checked="checked" {% endif %} /> + <label for="perpage">Lines per page:</label> + <select name="perpage" onchange="this.form.submit();"> + {% for value in perpage_p %} + <option value="{{value.0}}"{% if value.0 == perpage %} selected="selected" {% endif %}>{{value.1}}</option> + {% endfor %} + </select> + </form> +</div> +<div class="logwarn">{{warning}}</div> +<div style="clear: both;"></div> +<div class="logdiv"> + <table class="logtable" cellpadding="0" cellspacing="0"> + {% for line in log %} + <tr><td class="logline">{{line.line}}</td><td>{{line.date}}</td><td class="loglevel">{{line.level}}</td><td>{{line.message}}</td></tr> + {% endfor %} + </table> +</div> +<div class="logform"> +<form id="logform2" action="" method="POST"> + <label for="from">Jump to time:</label><input type="text" name="from" size="15" value="{{from}}"/> + <input type="submit" value="ok" /> +</form> +</div> +<div style="clear: both; height: 10px;"> </div> +{% endblock %} diff --git a/pyload/webui/themes/Default/tml/pathchooser.html b/pyload/webui/themes/Default/tml/pathchooser.html new file mode 100644 index 000000000..6e58ab536 --- /dev/null +++ b/pyload/webui/themes/Default/tml/pathchooser.html @@ -0,0 +1,76 @@ +<html> +<head> + <script class="javascript"> + function chosen() + { + opener.ifield.value = document.forms[0].p.value; + close(); + } + function exit() + { + close(); + } + function setInvalid() { + document.forms[0].send.disabled = 'disabled'; + document.forms[0].p.style.color = '#FF0000'; + } + function setValid() { + document.forms[0].send.disabled = ''; + document.forms[0].p.style.color = '#000000'; + } + function setFile(file) + { + document.forms[0].p.value = file; + setValid(); + + } + </script> + <link rel="stylesheet" type="text/css" href="/css/pathchooser.css"/> +</head> +<body{% if type == 'file' %}{% if not oldfile %} onload="setInvalid();"{% endif %}{% endif %}> +<center> + <div id="paths"> + <form method="get" action="?" onSubmit="chosen();" onReset="exit();"> + <input type="text" name="p" value="{{ oldfile|default(cwd) }}" size="60" onfocus="setValid();"> + <input type="submit" value="Ok" name="send"> + </form> + + {% if type == 'folder' %} + <span class="path_abs_rel">{{_("Path")}}: <a href="{{ "/pathchooser" + cwd|path_make_absolute|quotepath }}"{% if absolute %} style="text-decoration: underline;"{% endif %}>{{_("absolute")}}</a> | <a href="{{ "/pathchooser/" + cwd|path_make_relative|quotepath }}"{% if not absolute %} style="text-decoration: underline;"{% endif %}>{{_("relative")}}</a></span> + {% else %} + <span class="path_abs_rel">{{_("Path")}}: <a href="{{ "/filechooser/" + cwd|path_make_absolute|quotepath }}"{% if absolute %} style="text-decoration: underline;"{% endif %}>{{_("absolute")}}</a> | <a href="{{ "/filechooser/" + cwd|path_make_relative|quotepath }}"{% if not absolute %} style="text-decoration: underline;"{% endif %}>{{_("relative")}}</a></span> + {% endif %} + </div> + <table border="0" cellspacing="0" cellpadding="3"> + <tr> + <th>{{_("name")}}</th> + <th>{{_("size")}}</th> + <th>{{_("type")}}</th> + <th>{{_("last modified")}}</th> + </tr> + {% if parentdir %} + <tr> + <td colspan="4"> + <a href="{% if type == 'folder' %}{{ "/pathchooser/" + parentdir|quotepath }}{% else %}{{ "/filechooser/" + parentdir|quotepath }}{% endif %}"><span class="parentdir">{{_("parent directory")}}</span></a> + </td> + </tr> + {% endif %} +{% for file in files %} + <tr> + {% if type == 'folder' %} + <td class="name">{% if file.type == 'dir' %}<a href="{{ "/pathchooser/" + file.fullpath|quotepath }}" title="{{ file.fullpath }}"><span class="path_directory">{{ file.name|truncate(25) }}</span></a>{% else %}<span class="path_file" title="{{ file.fullpath }}">{{ file.name|truncate(25) }}{% endif %}</span></td> + {% else %} + <td class="name">{% if file.type == 'dir' %}<a href="{{ "/filechooser/" + file.fullpath|quotepath }}" title="{{ file.fullpath }}"><span class="file_directory">{{ file.name|truncate(25) }}</span></a>{% else %}<a href="#" onclick="setFile('{{ file.fullpath }}');" title="{{ file.fullpath }}"><span class="file_file">{{ file.name|truncate(25) }}{% endif %}</span></a></td> + {% endif %} + <td class="size">{{ file.size|float|filesizeformat }}</td> + <td class="type">{% if file.type == 'dir' %}directory{% else %}{{ file.ext|default("file") }}{% endif %}</td> + <td class="mtime">{{ file.modified|date("d.m.Y - H:i:s") }}</td> + <tr> +<!-- <tr> + <td colspan="4">{{_("no content")}}</td> + </tr> --> +{% endfor %} + </table> + </center> +</body> +</html> diff --git a/pyload/webui/themes/Default/tml/queue.html b/pyload/webui/themes/Default/tml/queue.html new file mode 100644 index 000000000..0affa54e0 --- /dev/null +++ b/pyload/webui/themes/Default/tml/queue.html @@ -0,0 +1,104 @@ +{% extends '/tml/base.html' %} +{% block head %} + +<script type="text/javascript" src="/js/package.js"></script> + +<script type="text/javascript"> + +document.addEvent("domready", function(){ + var pUI = new PackageUI("url", {{ target }}); +}); +</script> +{% endblock %} + +{% if target %} + {% set name = _("Queue") %} +{% else %} + {% set name = _("Collector") %} +{% endif %} + +{% block title %}{{name}} - {{super()}} {% endblock %} +{% block subtitle %}{{name}}{% endblock %} + +{% block pageactions %} +<ul id="page-actions-more"> + <li id="del_finished"><a style="padding: 0; font-weight: bold;" href="#">{{_("Delete Finished")}}</a></li> + <li id="restart_failed"><a style="padding: 0; font-weight: bold;" href="#">{{_("Restart Failed")}}</a></li> +</ul> +{% endblock %} + +{% block content %} +{% autoescape true %} + +<ul id="package-list" style="list-style: none; padding-left: 0; margin-top: -10px;"> +{% for package in content %} + <li> +<div id="package_{{package.pid}}" class="package"> + <div class="order" style="display: none;">{{ package.order }}</div> + + <div class="packagename" style="cursor: pointer"> + <img class="package_drag" src="/img/folder.png" style="cursor: move; margin-bottom: -2px"> + <span class="name">{{package.name}}</span> + + <span class="buttons" style="opacity:0"> + <img title="{{_("Delete Package")}}" style="cursor: pointer" width="12px" height="12px" src="/img/delete.png" /> + + <img title="{{_("Restart Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/img/arrow_refresh.png" /> + + <img title="{{_("Edit Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/img/pencil.png" /> + + <img title="{{_("Move Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/img/package_go.png" /> + </span> + </div> + {% set progress = (package.linksdone * 100) / package.linkstotal %} + + <div id="progress" style="border-radius: 4px; border: 1px solid #AAAAAA; width: 50%; height: 1em"> + <div style="width: {{ progress }}%; height: 100%; background-color: #add8e6;"></div> + <label style="font-size: 0.8em; font-weight: bold; padding-left: 5px; position: relative; top: -17px"> + {{ package.sizedone|formatsize }} / {{ package.sizetotal|formatsize }}</label> + <label style="font-size: 0.8em; font-weight: bold; padding-right: 5px ;float: right; position: relative; top: -17px"> + {{ package.linksdone }} / {{ package.linkstotal }}</label> + </div> + <div style="clear: both; margin-bottom: -10px"></div> + + <div id="children_{{package.pid}}" style="display: none;" class="children"> + <span class="child_secrow">{{_("Folder:")}} <span class="folder">{{package.folder}}</span> | {{_("Password:")}} <span class="password">{{package.password}}</span></span> + <ul id="sort_children_{{package.pid}}" style="list-style: none; padding-left: 0"> + </ul> + </div> +</div> + </li> +{% endfor %} +</ul> +{% endautoescape %} +{% endblock %} + +{% block hidden %} +<div id="pack_box" class="window_box" style="z-index: 2"> + <form id="pack_form" action="/json/edit_package" method="POST" enctype="multipart/form-data"> + <h1>{{_("Edit Package")}}</h1> + <p>{{_("Edit the package detais below.")}}</p> + <input name="pack_id" id="pack_id" type="hidden" value=""/> + <label for="pack_name">{{_("Name")}} + <span class="small">{{_("The name of the package.")}}</span> + </label> + <input id="pack_name" name="pack_name" type="text" size="20" /> + + <label for="pack_folder">{{_("Folder")}} + <span class="small">{{_("Name of subfolder for these downloads.")}}</span> + </label> + <input id="pack_folder" name="pack_folder" type="text" size="20" /> + + <label for="pack_pws">{{_("Password")}} + <span class="small">{{_("List of passwords used for unrar.")}}</span> + </label> + <textarea rows="3" name="pack_pws" id="pack_pws"></textarea> + + <button type="submit">{{_("Submit")}}</button> + <button id="pack_reset" style="margin-left: 0" type="reset" >{{_("Reset")}}</button> + <div class="spacer"></div> + + </form> + +</div> +{% endblock %} diff --git a/pyload/webui/themes/Default/tml/settings.html b/pyload/webui/themes/Default/tml/settings.html new file mode 100644 index 000000000..e96dcf22a --- /dev/null +++ b/pyload/webui/themes/Default/tml/settings.html @@ -0,0 +1,204 @@ +{% extends '/tml/base.html' %} + +{% block title %}{{ _("Config") }} - {{ super() }} {% endblock %} +{% block subtitle %}{{ _("Config") }}{% endblock %} + +{% block head %} + <script type="text/javascript" src="/lib/MooTools/TinyTab/tinytab.js"></script> + <script type="text/javascript" src="/lib/MooTools/MooDropMenu/MooDropMenu.js"></script> + <script type="text/javascript" src="/js/settings.min.js"></script> + +{% endblock %} + +{% block content %} + + <ul id="toptabs" class="tabs"> + <li><a class="selected" href="#">{{ _("General") }}</a></li> + <li><a href="#">{{ _("Plugins") }}</a></li> + <li><a href="#">{{ _("Accounts") }}</a></li> + </ul> + + <div id="tabsback" style="height: 20px; padding-left: 150px; color: white; font-weight: bold;"> + + </div> + + <span id="tabs-body"> + <!-- General --> + <span id="general" class="active tabContent"> + <ul class="nav tabs"> + <li class> + <a>Menu</a> + <ul id="general-menu"> + {% for entry,name in conf.general %} + <nobr> + <li id="general|{{ entry }}">{{ name }}</li> + </nobr> + <br> + {% endfor %} + </ul> + </li> + </ul> + + <form id="general_form" action="" method="POST" autocomplete="off"> + <span id="general_form_content"> + <br> + <h3> {{ _("Choose a section from the menu") }}</h3> + <br> + </span> + + <input id="general|submit" class="styled_button" type="submit" value="{{_("Submit")}}"/> + </form> + </span> + + <!-- Plugins --> + <span id="plugins" class="tabContent"> + <ul class="nav tabs"> + <li class> + <a>Menu</a> + <ul id="plugin-menu"> + {% for entry,name in conf.plugin %} + <nobr> + <li id="plugin|{{ entry }}">{{ name }}</li> + </nobr> + <br> + {% endfor %} + </ul> + </li> + </ul> + + + <form id="plugin_form" action="" method="POST" autocomplete="off"> + + <span id="plugin_form_content"> + <br> + <h3> {{ _("Choose a section from the menu") }}</h3> + <br> + </span> + <input id="plugin|submit" class="styled_button" type="submit" value="{{_("Submit")}}"/> + </form> + + </span> + + <!-- Accounts --> + <span id="accounts" class="tabContent"> + <form id="account_form" action="/json/update_accounts" method="POST"> + + <table class="settable wide"> + + <thead> + <tr> + <th>{{ _("Plugin") }}</th> + <th>{{ _("Name") }}</th> + <th>{{ _("Password") }}</th> + <th>{{ _("Status") }}</th> + <th>{{ _("Premium") }}</th> + <th>{{ _("Valid until") }}</th> + <th>{{ _("Traffic left") }}</th> + <th>{{ _("Time") }}</th> + <th>{{ _("Max Parallel") }}</th> + <th>{{ _("Delete?") }}</th> + </tr> + </thead> + + + {% for account in conf.accs %} + {% set plugin = account.type %} + <tr> + <td> + <span style="padding:5px">{{ plugin }}</span> + </td> + + <td><label for="{{plugin}}|password;{{account.login}}" + style="color:#424242;">{{ account.login }}</label></td> + <td> + <input id="{{plugin}}|password;{{account.login}}" + name="{{plugin}}|password;{{account.login}}" + type="password" size="12"/> + </td> + <td> + {% if account.valid %} + <span style="font-weight: bold; color: #006400;"> + {{ _("valid") }} + {% else %} + <span style="font-weight: bold; color: #8b0000;"> + {{ _("not valid") }} + {% endif %} + </span> + </td> + <td> + {% if account.premium %} + <span style="font-weight: bold; color: #006400;"> + {{ _("yes") }} + {% else %} + <span style="font-weight: bold; color: #8b0000;"> + {{ _("no") }} + {% endif %} + </span> + </td> + <td> + <span style="font-weight: bold;"> + {{ account.validuntil }} + </span> + </td> + <td> + <span style="font-weight: bold;"> + {{ account.trafficleft }} + </span> + </td> + <td> + <input id="{{plugin}}|time;{{account.login}}" + name="{{plugin}}|time;{{account.login}}" type="text" + size="7" value="{{account.options['time']}}"/> + </td> + <td> + <input id="{{plugin}}|limitdl;{{account.login}}" + name="{{plugin}}|limitdl;{{account.login}}" type="text" + size="2" value="{{account.options['limitdl']}}"/> + </td> + <td> + <input id="{{plugin}}|delete;{{account.login}}" + name="{{plugin}}|delete;{{account.login}}" type="checkbox" + value="True"/> + </td> + </tr> + {% endfor %} + </table> + + <button id="account_submit" type="submit" class="styled_button">{{_("Submit")}}</button> + <button id="account_add" style="margin-left: 0" type="submit" class="styled_button">{{_("Add")}}</button> + </form> + </span> + </span> +{% endblock %} +{% block hidden %} +<div id="account_box" class="window_box" style="z-index: 2"> +<form id="add_account_form" action="/json/add_account" method="POST" enctype="multipart/form-data"> +<h1>{{_("Add Account")}}</h1> +<p>{{_("Enter your account data to use premium features.")}}</p> +<label for="account_login">{{_("Login")}} +<span class="small">{{_("Your username.")}}</span> +</label> +<input id="account_login" name="account_login" type="text" size="20" /> + +<label for="account_password">{{_("Password")}} +<span class="small">{{_("The password for this account.")}}</span> +</label> +<input id="account_password" name="account_password" type="password" size="20" /> + +<label for="account_type">{{_("Type")}} +<span class="small">{{_("Choose the hoster for your account.")}}</span> +</label> + <select name=account_type id="account_type"> + {% for type in types|sort %} + <option value="{{ type }}">{{ type }}</option> + {% endfor %} + </select> + +<button id="account_add_button" type="submit">{{_("Add")}}</button> +<button id="account_reset" style="margin-left: 0" type="reset">{{_("Reset")}}</button> +<div class="spacer"></div> + +</form> + +</div> +{% endblock %} diff --git a/pyload/webui/themes/Default/tml/settings_item.html b/pyload/webui/themes/Default/tml/settings_item.html new file mode 100644 index 000000000..83a008619 --- /dev/null +++ b/pyload/webui/themes/Default/tml/settings_item.html @@ -0,0 +1,48 @@ +<table class="settable"> + {% if section.outline %} + <tr><th colspan="2">{{ section.outline }}</th></tr> + {% endif %} + {% for okey, option in sorted_conf(section) %} + {% if okey not in ("desc", "outline") %} + <tr> + <td><label for="{{skey}}|{{okey}}" + style="color:#424242;">{{ option.desc }}:</label></td> + <td> + {% if option.type == "bool" %} + <select id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}"> + <option {% if option.value %} selected="selected" + {% endif %}value="True">{{ _("on") }}</option> + <option {% if not option.value %} selected="selected" + {% endif %}value="False">{{ _("off") }}</option> + </select> + {% elif ";" in option.type %} + <select id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}"> + {% for entry in option.list %} + <option {% if option.value == entry %} + selected="selected" {% endif %}>{{ entry }}</option> + {% endfor %} + </select> + {% elif option.type == "folder" %} + <input name="{{skey}}|{{okey}}" type="text" + id="{{skey}}|{{okey}}" value="{{option.value}}"/> + <input name="browsebutton" type="button" + onclick="ifield = document.getElementById('{{skey}}|{{okey}}'); pathchooser = window.open('{% if option.value %}{{ "/pathchooser/" + option.value|quotepath }}{% else %}{{ pathroot }}{% endif %}', 'pathchooser', 'scrollbars=yes,toolbar=no,menubar=no,statusbar=no,width=650,height=300'); pathchooser.ifield = ifield; window.ifield = ifield;" + value="{{_("Browse")}}"/> + {% elif option.type == "file" %} + <input name="{{skey}}|{{okey}}" type="text" + id="{{skey}}|{{okey}}" value="{{option.value}}"/> + <input name="browsebutton" type="button" + onclick="ifield = document.getElementById('{{skey}}|{{okey}}'); filechooser = window.open('{% if option.value %}{{ "/filechooser/" + option.value|quotepath }}{% else %}{{ fileroot }}{% endif %}', 'filechooser', 'scrollbars=yes,toolbar=no,menubar=no,statusbar=no,width=650,height=300'); filechooser.ifield = ifield; window.ifield = ifield;" + value="{{_("Browse")}}"/> + {% elif option.type == "password" %} + <input id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}" + type="password" value="{{option.value}}"/> + {% else %} + <input id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}" + type="text" value="{{option.value}}"/> + {% endif %} + </td> + </tr> + {% endif %} + {% endfor %} +</table> diff --git a/pyload/webui/themes/Default/tml/window.html b/pyload/webui/themes/Default/tml/window.html new file mode 100644 index 000000000..023441f69 --- /dev/null +++ b/pyload/webui/themes/Default/tml/window.html @@ -0,0 +1,46 @@ +<iframe id="upload_target" name="upload_target" src="" style="display: none; width:0;height:0"></iframe> + +<div id="add_box" class="window_box"> +<form id="add_form" action="/json/add_package" method="POST" enctype="multipart/form-data"> +<h1>{{_("Add Package")}}</h1> +<p>{{_("Paste your links or upload a container.")}}</p> +<label for="add_name">{{_("Name")}} +<span class="small">{{_("The name of the new package.")}}</span> +</label> +<input id="add_name" name="add_name" type="text" size="20" /> + +<label for="add_links">{{_("Links")}} +<span class="small">{{_("Paste your links here or any text and press the filter button.")}}</span> +<span class="small"> {{ _("Filter urls") }} +<img alt="URIParsing" Title="Parse Uri" src="/img/parseUri.png" style="cursor:pointer; vertical-align: text-bottom;" onclick="parseUri()"/> +</span> + +</label> +<textarea rows="5" name="add_links" id="add_links"></textarea> + +<label for="add_password">{{_("Password")}} + <span class="small">{{_("Password for RAR-Archive")}}</span> +</label> +<input id="add_password" name="add_password" type="text" size="20"> + +<label>{{_("File")}} +<span class="small">{{_("Upload a container.")}}</span> +</label> +<input type="file" name="add_file" id="add_file"/> + +<label for="add_dest">{{_("Destination")}} +</label> +<span class="cont"> + {{_("Queue")}} + <input type="radio" name="add_dest" id="add_dest" value="1" checked="checked"/> + {{_("Collector")}} + <input type="radio" name="add_dest" id="add_dest2" value="0"/> +</span> + +<button type="submit">{{_("Add Package")}}</button> +<button id="add_reset" style="margin-left:0;" type="reset">{{_("Reset")}}</button> +<div class="spacer"></div> + +</form> + +</div> diff --git a/pyload/webui/themes/Flat/css/MooDialog.css b/pyload/webui/themes/Flat/css/MooDialog.css new file mode 100644 index 000000000..e7fb7c23b --- /dev/null +++ b/pyload/webui/themes/Flat/css/MooDialog.css @@ -0,0 +1,85 @@ +/* Created by Arian Stolwijk <http://www.aryweb.nl> */ + +.MooDialog { +/* position: fixed;*/ + margin: 0 auto 0 -350px; + width:600px; + padding:14px; + left:50%; + top: 100px; + + position: absolute; + left: 50%; + z-index: 50000; + + background-color: #E44424; + color: black; + opacity:0.9; +} + +.MooDialogTitle { + padding-top: 30px; +} + +.MooDialog .title { + position: absolute; + top: 0; + left: 0; + right: 0; + padding: 3px 20px; + background: #b7c4dc; + border-bottom: 1px solid #a1aec5; + font-weight: bold; + text-shadow: 1px 1px 0 #fff; + color: black; + border-radius: 7px; + -moz-border-radius: 7px; + -webkit-border-radius: 7px; +} + +.MooDialog .close { + background: url(../img/control_cancel.png) no-repeat; + width: 16px; + height: 16px; + display: block; + cursor: pointer; + top: -5px; + left: -5px; + position: absolute; +} + +.MooDialog .buttons { + text-align: right; + margin: 0; + padding: 0; + border: 0; + background: none; +} + +.MooDialog .iframe { + width: 100%; + height: 100%; +} + +.MooDialog .textInput { + width: 200px; + float: left; +} + +.MooDialog .MooDialogAlert, +.MooDialog .MooDialogConfirm, +.MooDialog .MooDialogPrompt, +.MooDialog .MooDialogError { + background: url(../lib/MooTools/MooDialog/css/dialog-warning.png) no-repeat; + padding-left: 40px; + min-height: 40px; +} + +.MooDialog .MooDialogConfirm, +.MooDialog .MooDialogPromt { + background: url(../lib/MooTools/MooDialog/css/dialog-question.png) no-repeat; +} + +.MooDialog .MooDialogError { + background: url(../lib/MooTools/MooDialog/css/dialog-error.png) no-repeat; +} diff --git a/pyload/webui/themes/Flat/css/base.css b/pyload/webui/themes/Flat/css/base.css new file mode 100644 index 000000000..fd73d0a19 --- /dev/null +++ b/pyload/webui/themes/Flat/css/base.css @@ -0,0 +1,866 @@ +.hidden { + display:none; +} +.leftalign { + text-align:left; +} +.centeralign { + text-align:center; +} +.rightalign { + text-align:right; +} +.dokuwiki div.plugin_translation ul li a.wikilink1:link, .dokuwiki div.plugin_translation ul li a.wikilink1:hover, .dokuwiki div.plugin_translation ul li a.wikilink1:active, .dokuwiki div.plugin_translation ul li a.wikilink1:visited { + background-color:#000080; + border:none !important; + color:#FFFFFF !important; + margin:0.1em 0.2em; + padding:0 0.2em; + text-decoration:none; +} +.dokuwiki div.plugin_translation ul li a.wikilink2:link, .dokuwiki div.plugin_translation ul li a.wikilink2:hover, .dokuwiki div.plugin_translation ul li a.wikilink2:active, .dokuwiki div.plugin_translation ul li a.wikilink2:visited { + background-color:#808080; + border:none !important; + color:#FFFFFF !important; + margin:0.1em 0.2em; + padding:0 0.2em; + text-decoration:none; +} +.dokuwiki div.plugin_translation ul li a:hover img { + height:15px; + opacity:1; +} +body { + background-color:white; + color:black; + font-family:'Open Sans', sans-serif; + font-size:12px; + font-style:normal; + font-variant:normal; + font-weight:300; + line-height:normal; + margin:0; + padding:0; +} +hr { + border-bottom-color:#AAAAAA; + border-bottom-style:dotted; +} +img { + border:none; +} +form { + background-color:transparent; + border:none; + display:inline; + margin:0; + padding:0; +} +ul li { + margin:5px; +} +textarea { + font-family:monospace; +} +table { + border-collapse:collapse; + margin:0.5em 0; +} +td { + border:1pt solid #ADB9CC; + padding:0.25em; +} +a { + color:#3465A4; + text-decoration:none; +} +a:hover { + text-decoration:underline; +} +option { + border:0 none #FFFFFF; +} +strong.highlight { + background-color:#FFCC99; + padding:1pt; +} +#pagebottom { + clear:both; +} +hr { + background-color:#C0C0C0; + border:none; + color:#C0C0C0; + margin:0.2em 0; +} +.invisible { + border:0; + height:0; + margin:0; + padding:0; + visibility:hidden; +} +.left { + float:left !important; +} +.right { + float:right !important; +} +.center { + text-align:center; +} +div#body-wrapper { + font-size:127%; + padding:40px 40px 10px; +} +div#content { + color:black; + font-size:14px; + line-height:1.5em; + margin-top:-20px; + padding:0; +} +h1, h2, h3, h4, h5, h6 { + background-attachment:scroll; + background-color:transparent; + background-image:none; + background-position:0 0; + background-repeat:repeat repeat; + color:black; + font-family:'Open Sans', sans-serif; + font-weight:normal; + margin:0; + padding:0.5em 0 0.17em; +} +h1 { + font-family:'Open Sans', sans-serif; + font-weight:300; + line-height:1.2em; + margin-bottom:0.1em; + padding-bottom:0; + margin-left:-25px; +} +h2 { + font-size:150%; +} +h3, h4, h5, h6 { + border-bottom-style:none; + font-weight:bold; +} +h3 { + font-size:132%; +} +h4 { + font-size:116%; +} +h5 { + font-size:100%; +} +h6 { + font-size:80%; +} +ul#page-actions, ul#page-actions-more { + background-color:#ECECEC; + color:black; + float:right; + list-style-type:none; + margin:10px 10px 0; + padding:6px; + white-space:nowrap; +} +ul#user-actions { + background-color:#67BCDB; + color:black; + display:inline; + list-style-type:none; + margin:0; + padding:10px; +} +ul#page-actions li, ul#user-actions li, ul#page-actions-more li { + display:inline; +} +ul#page-actions a, ul#user-actions a, ul#page-actions-more a { + color:black; + display:inline; + margin:0 3px; + padding:2px 0 2px 18px; + text-decoration:none; +} +ul#page-actions a:hover, ul#page-actions a:focus, ul#user-actions a:hover, ul#user-actions a:focus { +} +ul#page-actions2 { + background-color:#ECECEC; + color:black; + float:left; + list-style-type:none; + margin:10px 10px 0; + padding:6px; +} +ul#user-actions2 { + background-color:#ECECEC; + border-bottom-left-radius:3px; + border-bottom-right-radius:3px; + border-top-left-radius:3px; + border-top-right-radius:3px; + color:black; + display:inline; + list-style-type:none; + margin:0; + padding:5px; +} +ul#page-actions2 li, ul#user-actions2 li { + display:inline; +} +ul#page-actions2 a, ul#user-actions2 a { + color:black; + display:inline; + margin:0 3px; + padding:2px 0 2px 18px; + text-decoration:none; +} +ul#page-actions2 a:hover, ul#page-actions2 a:focus, ul#user-actions2 a:hover, ul#user-actions2 a:focus, ul#page-actions-more a:hover, ul#page-actions-more a:focus { + color:#4E7BB4; +} +.hidden { + display:none; +} +a.logout { + background-color:transparent; + background-image:url(../img/user-actions-logout.png); + background-position:0 1px; + background-repeat:no-repeat no-repeat; +} +a.info { + background-color:transparent; + background-image:url(../img/user-info.png); + background-position:0 1px; + background-repeat:no-repeat no-repeat; +} +a.admin { + background-color:transparent; + background-image:url(../img/user-actions-admin.png); + background-position:0 1px; + background-repeat:no-repeat no-repeat; +} +a.profile { + background-color:transparent; + background-image:url(../img/user-actions-profile.png); + background-position:0 1px; + background-repeat:no-repeat no-repeat; +} +a.create, a.edit { + background-color:transparent; + background-image:url(../img/page-tools-edit.png); + background-position:0 1px; + background-repeat:no-repeat no-repeat; +} +a.source, a.show { + background-color:transparent; + background-image:url(../img/page-tools-source.png); + background-position:0 1px; + background-repeat:no-repeat no-repeat; +} +a.revisions { + background-color:transparent; + background-image:url(../img/page-tools-revisions.png); + background-position:0 1px; + background-repeat:no-repeat no-repeat; +} +a.subscribe, a.unsubscribe { + background-color:transparent; + background-image:url(../img/page-tools-subscribe.png); + background-position:0 1px; + background-repeat:no-repeat no-repeat; +} +a.backlink { + background-color:transparent; + background-image:url(../img/page-tools-backlinks.png); + background-position:0 1px; + background-repeat:no-repeat no-repeat; +} +a.play { + background-color:transparent; + background-image:url(../img/control_play.png); + background-position:0 1px; + background-repeat:no-repeat no-repeat; +} +.time { + background-color:transparent; + background-image:url(../img/status_None.png); + background-position:0 1px; + background-repeat:no-repeat no-repeat; + margin:0 3px; + padding:2px 0 2px 18px; +} +.reconnect { + background-color:transparent; + background-image:url(../img/reconnect.png); + background-position:0 1px; + background-repeat:no-repeat no-repeat; + margin:0 3px; + padding:2px 0 2px 18px; +} +a.play:hover { + background-color:transparent; + background-image:url(../img/control_play_blue.png); + background-position:0 1px; + background-repeat:no-repeat no-repeat; +} +a.cancel { + background-color:transparent; + background-image:url(../img/control_cancel.png); + background-position:0 1px; + background-repeat:no-repeat no-repeat; +} +a.cancel:hover { + background-color:transparent; + background-image:url(../img/control_cancel_blue.png); + background-position:0 1px; + background-repeat:no-repeat no-repeat; +} +a.pause { + background-color:transparent; + background-image:url(../img/control_pause.png); + background-position:0 1px; + background-repeat:no-repeat no-repeat; +} +a.pause:hover { + background-color:transparent; + background-image:url(../img/control_pause_blue.png); + background-position:0 1px; + background-repeat:no-repeat no-repeat; + font-weight:bold; +} +a.stop { + background-color:transparent; + background-image:url(../img/control_stop.png); + background-position:0 1px; + background-repeat:no-repeat no-repeat; +} +a.stop:hover { + background-color:transparent; + background-image:url(../img/control_stop_blue.png); + background-position:0 1px; + background-repeat:no-repeat no-repeat; +} +a.add { + background-color:transparent; + background-image:url(../img/control_add.png); + background-position:0 1px; + background-repeat:no-repeat no-repeat; +} +a.add:hover { + background-color:transparent; + background-image:url(../img/control_add_blue.png); + background-position:0 1px; + background-repeat:no-repeat no-repeat; +} +a.cog { + background-color:transparent; + background-image:url(../img/cog.png); + background-position:0 1px; + background-repeat:no-repeat no-repeat; +} +#head-panel { + background-color:#67BCDB; + background-position:0 100%; + background-repeat:repeat no-repeat; +} +#head-panel h1 { + color:#EEEEEC; + display:none; + font-size:2.6em; + margin:0; + padding-left:3.3em; + padding-top:0.8em; + text-decoration:none; +} +#head-panel #head-logo { + float:left; + overflow:visible; + padding:11px 8px 0; +} +#head-menu { + float:left; + margin:0; + padding:1em 0 0; + width:100%; +} +#head-menu ul { + list-style:none; + margin:0 1em 0 2em; +} +#head-menu ul li { + float:left; + font-size:14px; + margin:0 0 4px 0.3em; +} +#head-menu ul li.selected, #head-menu ul li:hover { + margin-bottom:0; +} +#head-menu ul li a img { + height:22px; + padding-right:4px; + vertical-align:middle; + width:22px; +} +#head-menu ul li a, #head-menu ul li a:link { + background-color:#FFFFFF; + background-position:0 100%; + background-repeat:repeat no-repeat; + border-color:#CCCCCC #CCCCCC transparent; + color:#555555; + padding:7px 15px 8px; + text-decoration:none; + opacity:0.7; +} +#head-menu ul li a:hover, #head-menu ul li a:focus { + border-bottom-color:transparent; + border-bottom-left-radius:0; + border-bottom-right-radius:0; + border-bottom-style:none; + border-bottom-width:0; + outline:none; + padding-bottom:7px; + opacity:1; + background-color: #FF9009; + transition: background 0.2s ease-out, opacity 0.5s ease-in; +} +#head-menu ul li a:focus { + margin-bottom:-4px; +} +#head-menu ul li.selected a { + border-bottom-color:transparent; + color:#3566A5; + padding:7px 15px 8px; + opacity:1; +} +#head-menu ul li.selected a:hover, #head-menu ul li.selected a:focus { + color:#111111; +} +div#head-search-and-login { + float:right; + margin:0 12px 0 0; + padding:7px 0px 7px 7px; + white-space:nowrap; + background-color:#FFF056; + opacity:0.9; +} +div#head-search-and-login form { + display:inline; + padding:0 3px; +} +div#head-search-and-login form input { + background-color:#EEEEEE; + border:2px solid #888888; + border-bottom-left-radius:3px; + border-bottom-right-radius:3px; + border-top-left-radius:3px; + border-top-right-radius:3px; + font-size:14px; + padding:2px; +} +div#head-search-and-login form input:focus { + background-color:#FFFFFF; +} +#head-search { + font-size:14px; +} +#head-username, #head-password { + font-size:14px; + width:80px; +} +#pageinfo { + clear:both; + color:#888888; + margin:0; + padding:0.6em 0; +} +#foot { + color:#888888; + font-style:normal; + text-align:center; +} +#foot a { + color:#AAAAFF; +} +#foot img { + vertical-align:middle; +} +div.toc { + background-color:#F0F0F0; + border:1px dotted #888888; + float:right; + font-size:95%; + margin:1em 0 1em 1em; +} +div.toc .tocheader { + font-weight:bold; + margin:0.5em 1em; +} +div.toc ol { + margin:1em 0.5em 1em 1em; + padding:0; +} +div.toc ol li { + margin:0 0 0 1em; + padding:0; +} +div.toc ol ol { + margin:0.5em 0.5em 0.5em 1em; + padding:0; +} +div.recentchanges table { + clear:both; +} +div#editor-help { + background-color:#F7F6F2; + border:1px dotted #888888; + font-size:90%; + padding:0 1ex 1ex; +} +div#preview { + margin-top:1em; +} +label.block { + display:block; + font-weight:bold; + text-align:right; +} +label.simple { + display:block; + font-weight:normal; + text-align:left; +} +label.block input.edit { + width:50%; +} +div.editor { + margin:0; +} +table { + border-collapse:collapse; + margin:0.5em 0; +} +td { + border:1pt solid #ADB9CC; + padding:0.25em; +} +td p { + margin:0; + padding:0; +} +.u { + text-decoration:underline; +} +.footnotes ul { + margin:0 0 1em; + padding:0 2em; +} +.footnotes li { + list-style:none; +} +.userpref table, .userpref td { + border:none; +} +#message { + background-color:#EEEEEE; + border-bottom-color:#CCCCCC; + border-bottom-style:solid; + border-bottom-width:2px; + clear:both; + padding:5px 10px; +} +#message p { + font-weight:bold; + margin:5px 0; + padding:0; +} +#message div.buttons { + font-weight:normal; +} +.diff { + width:99%; +} +.diff-title { + background-color:#C0C0C0; +} +.searchresult dd span { + font-weight:400; +} +.boxtext { + color:#000000; + float:none; + font-family:tahoma, arial, sans-serif; + font-size:11px; + padding:3px 0 0 10px; +} +.statusbutton { + cursor:pointer; + float:left; + height:32px; + margin-left:-32px; + margin-right:5px; + opacity:0; + width:32px; +} +.dlsize { + float:left; + padding-right:8px; +} +.dlspeed { + float:left; + padding-right:8px; +} +.package { + margin-bottom:10px; +} +.packagename { + font-weight:300; +} +.child { + margin-left:20px; +} +.child_status { + margin-right:10px; +} +.child_secrow { + font-size:10px; +} +.header, .header th { + background-color:#ECECEC; + font-weight:300; + text-align:left; +} +.progress_bar { + background-color:#00CC00; + height:5px; +} +.queue { + border:none; +} +.queue tr td { + border:none; +} +.header, .header th { + font-weight:normal; + text-align:left; +} +.clearer { + clear:both; + height:1px; +} +.left { + float:left; +} +.right { + float:right; +} +.setfield { + display:table-cell; +} +ul.tabs li a { + border:none; + border-bottom-left-radius:0; + border-bottom-right-radius:0; + border-top-left-radius:5px; + border-top-right-radius:5px; + font-weight:bold; + padding:5px 16px 4px 15px; +} +#tabs span { + display:none; +} +#tabs span.selected { + display:inline; +} +#tabsback { + background-color:#525252; + border-top-left-radius:3px; + border-top-right-radius:30px; + margin:2px 0 0; + padding:6px 4px 1px; +} +ul.tabs { + list-style-type:none; + margin:0; + padding:0 40px 0 0; +} +ul.tabs li { + display:inline; + margin-left:8px; +} +ul.tabs li a { + background-color:#EAEAEA; + border:1px none #C9C3BA; + border-bottom-left-radius:0; + border-bottom-right-radius:0; + border-top-left-radius:5px; + border-top-right-radius:5px; + color:#42454A; + font-weight:bold; + margin:0; + outline:0; + padding:5px 16px 4px 15px; + text-decoration:none; +} +ul.tabs li a.selected, ul.tabs li a:hover { + background-color:white; + border-bottom-left-radius:0; + border-bottom-right-radius:0; + color:#000000; +} +ul.tabs li a:hover { + background-color:#F1F4EE; +} +ul.tabs li a.selected { + background-color:#525252; + color:white; + font-weight:bold; + padding-bottom:5px; +} +#tabs-body { + overflow:hidden; + position:relative; +} +span.tabContent { + border:2px solid #525252; + margin:0; + padding:0 0 10px; +} +#tabs-body > span { + display:none; +} +#tabs-body > span.active { + display:block; +} +.hide { + display:none; +} +.settable { + border:none; + margin:20px; +} +.settable td { + border:none; + margin:0; + padding:5px; +} +.settable th { + padding-bottom:8px; +} +.settable.wide td, .settable.wide th { + padding-left:15px; + padding-right:15px; +} +ul.nav { + list-style:none; + margin:-30px 0 0; + padding:0; + position:absolute; +} +ul.nav li { + float:left; + padding:5px; + position:relative; +} +ul.nav > li a { + background-color:white; + border-left-color:#C9C3BA; + border-right-color:#C9C3BA; + border-style:solid solid none; + border-top-color:#C9C3BA; + border-width:1px 1px medium; + color:black; +} +ul.nav ul { + -webkit-box-shadow:#AAAAAA 1px 1px 5px; + background-color:#F1F1F1; + border:1px solid #AAAAAA; + box-shadow:#AAAAAA 1px 1px 5px; + cursor:pointer; + left:10px; + list-style:none; + margin:0; + padding:0; + position:absolute; + top:26px; +} +ul.nav .open { + display:block; +} +ul.nav .close { + display:none; +} +ul.nav ul li { + float:none; + padding:0; +} +ul.nav ul li a { + background-color:#F1F1F1; + display:block; + font-weight:normal; + padding:3px; + width:130px; +} +ul.nav ul li a:hover { + background-color:#CDCDCD; +} +ul.nav ul ul { + left:137px; + top:0; +} +.purr-wrapper { + margin:10px; +} +.purr-alert { + background-color:#000000; + border-bottom-left-radius:5px; + border-bottom-right-radius:5px; + border-top-left-radius:5px; + border-top-right-radius:5px; + color:#FFFFFF; + font-size:13px; + font-weight:bold; + margin-bottom:10px; + padding:10px; + width:300px; +} +.purr-alert.error { + background-color:#000000; + background-image:url(../img/error.png); + background-position:7px 10px; + background-repeat:no-repeat no-repeat; + color:#FF5555; + padding-left:30px; + width:280px; +} +.purr-alert.success { + background-color:#000000; + background-image:url(../img/success.png); + background-position:7px 10px; + background-repeat:no-repeat no-repeat; + color:#55FF55; + padding-left:30px; + width:280px; +} +.purr-alert.notice { + background-color:#000000; + background-image:url(../img/notice.png); + background-position:7px 10px; + background-repeat:no-repeat no-repeat; + color:#9999FF; + padding-left:30px; + width:280px; +} +table.system { + border:none; + margin-left:10px; +} +table.system td { + border:none; +} +table.system tr > td:first-child { + font-weight:bold; + padding-right:10px; +}
\ No newline at end of file diff --git a/pyload/webui/themes/Flat/css/log.css b/pyload/webui/themes/Flat/css/log.css new file mode 100644 index 000000000..c2f21b506 --- /dev/null +++ b/pyload/webui/themes/Flat/css/log.css @@ -0,0 +1,72 @@ + +html, body, #content +{ + height: 100%; +} +#body-wrapper +{ + height: 70%; +} +.logdiv +{ + height: 90%; + width: 100%; + overflow: auto; + border: 2px solid #CCC; + outline: 1px solid #666; + background-color: #FFE; + margin-right: auto; + margin-left: auto; +} +.logform +{ + display: table; + margin: 0 auto 0 auto; + padding-top: 5px; +} +.logtable +{ + + margin: 0px; +} +.logtable td +{ + border: none; + white-space: nowrap; + + + font-family: monospace; + font-size: 16px; + margin: 0px; + padding: 0px 10px 0px 10px; + line-height: 110%; +} +td.logline +{ + background-color: #EEE; + text-align:right; + padding: 0px 5px 0px 5px; +} +td.loglevel +{ + text-align:right; +} +.logperpage +{ + float: right; + padding-bottom: 8px; +} +.logpaginator +{ + float: left; + padding-top: 5px; +} +.logpaginator a +{ + padding: 0px 8px 0px 8px; +} +.logwarn +{ + text-align: center; + color: red; +}
\ No newline at end of file diff --git a/pyload/webui/themes/Flat/css/pathchooser.css b/pyload/webui/themes/Flat/css/pathchooser.css new file mode 100644 index 000000000..204812aa1 --- /dev/null +++ b/pyload/webui/themes/Flat/css/pathchooser.css @@ -0,0 +1,68 @@ +table { + width: 90%; + border: 1px dotted #888888; + font-family: sans-serif; + font-size: 10pt; +} + +th { + background-color: #525252; + color: #E0E0E0; +} + +table, tr, td { + background-color: #F0F0F0; +} + +a, a:visited { + text-decoration: none; + font-weight: bold; +} + +#paths { + width: 90%; + text-align: left; +} + +.file_directory { + color: #c0c0c0; +} +.path_directory { + color: #3c3c3c; +} +.file_file { + color: #3c3c3c; +} +.path_file { + color: #c0c0c0; +} + +.parentdir { + color: #000000; + font-size: 10pt; +} +.name { + text-align: left; +} +.size { + text-align: right; +} +.type { + text-align: left; +} +.mtime { + text-align: center; +} + +.path_abs_rel { + color: #3c3c3c; + text-decoration: none; + font-weight: bold; + font-family: sans-serif; + font-size: 10pt; +} + +.path_abs_rel a { + color: #3c3c3c; + font-style: italic; +} diff --git a/pyload/webui/themes/Flat/css/window.css b/pyload/webui/themes/Flat/css/window.css new file mode 100644 index 000000000..40c1aadca --- /dev/null +++ b/pyload/webui/themes/Flat/css/window.css @@ -0,0 +1,70 @@ +/* ----------- stylized ----------- */ +.window_box h1{ + margin-bottom:8px; +} +.window_box p{ + font-size:11px; + color:#FFFFFF; + margin-bottom:20px; + border-bottom:solid 1px #b7ddf2; + padding-bottom:10px; +} +.window_box label{ + display:block; + font-weight:bold; + text-align:right; + width:240px; + float:left; +} +.window_box .small{ + color:#FFFFFF; + display:block; + font-size:11px; + font-weight:normal; + text-align:right; + width:240px; +} +.window_box select, .window_box input{ + float:left; + font-size:12px; + padding:4px 2px; + width:300px; + margin:2px 0 20px 10px; +} +.window_box .cont{ + float:left; + font-size:12px; + padding: 0px 10px 15px 0px; + width:300px; + margin:0px 0px 0px 10px; + color:#FFFFFF; +} +.window_box .cont input{ + float: none; + margin: 0px 15px 0px 1px; +} +.window_box textarea{ + float:left; + font-size:12px; + padding:4px 2px; + width:300px; + margin:2px 0 20px 10px; +} +.window_box button, .styled_button{ + clear:both; + margin-left:150px; + width:125px; + height:31px; + background:url(../img/button.png) no-repeat; + text-align:center; + line-height:31px; + color:#FFFFFF; + font-size:11px; + font-weight:bold; + border: 0px; +} + +.styled_button { + margin-left: 15px; + cursor: pointer; +} diff --git a/pyload/webui/themes/Flat/img/add_folder.png b/pyload/webui/themes/Flat/img/add_folder.png Binary files differnew file mode 100644 index 000000000..8acbc411b --- /dev/null +++ b/pyload/webui/themes/Flat/img/add_folder.png diff --git a/pyload/webui/themes/Flat/img/ajax-loader.gif b/pyload/webui/themes/Flat/img/ajax-loader.gif Binary files differnew file mode 100644 index 000000000..2fd8e0737 --- /dev/null +++ b/pyload/webui/themes/Flat/img/ajax-loader.gif diff --git a/pyload/webui/themes/Flat/img/arrow_refresh.png b/pyload/webui/themes/Flat/img/arrow_refresh.png Binary files differnew file mode 100644 index 000000000..b1b6fa4dc --- /dev/null +++ b/pyload/webui/themes/Flat/img/arrow_refresh.png diff --git a/pyload/webui/themes/Flat/img/arrow_right.png b/pyload/webui/themes/Flat/img/arrow_right.png Binary files differnew file mode 100644 index 000000000..68f379fc7 --- /dev/null +++ b/pyload/webui/themes/Flat/img/arrow_right.png diff --git a/pyload/webui/themes/Flat/img/big_button.gif b/pyload/webui/themes/Flat/img/big_button.gif Binary files differnew file mode 100644 index 000000000..7680490ea --- /dev/null +++ b/pyload/webui/themes/Flat/img/big_button.gif diff --git a/pyload/webui/themes/Flat/img/big_button_over.gif b/pyload/webui/themes/Flat/img/big_button_over.gif Binary files differnew file mode 100644 index 000000000..2e3ee10d2 --- /dev/null +++ b/pyload/webui/themes/Flat/img/big_button_over.gif diff --git a/pyload/webui/themes/Flat/img/body.png b/pyload/webui/themes/Flat/img/body.png Binary files differnew file mode 100644 index 000000000..7ff1043e0 --- /dev/null +++ b/pyload/webui/themes/Flat/img/body.png diff --git a/pyload/webui/themes/Flat/img/button.png b/pyload/webui/themes/Flat/img/button.png Binary files differnew file mode 100644 index 000000000..bb408a7d6 --- /dev/null +++ b/pyload/webui/themes/Flat/img/button.png diff --git a/pyload/webui/themes/Flat/img/closebtn.gif b/pyload/webui/themes/Flat/img/closebtn.gif Binary files differnew file mode 100644 index 000000000..3e27e6030 --- /dev/null +++ b/pyload/webui/themes/Flat/img/closebtn.gif diff --git a/pyload/webui/themes/Flat/img/cog.png b/pyload/webui/themes/Flat/img/cog.png Binary files differnew file mode 100644 index 000000000..833f779ac --- /dev/null +++ b/pyload/webui/themes/Flat/img/cog.png diff --git a/pyload/webui/themes/Flat/img/control_add.png b/pyload/webui/themes/Flat/img/control_add.png Binary files differnew file mode 100644 index 000000000..e3f29fab2 --- /dev/null +++ b/pyload/webui/themes/Flat/img/control_add.png diff --git a/pyload/webui/themes/Flat/img/control_add_blue.png b/pyload/webui/themes/Flat/img/control_add_blue.png Binary files differnew file mode 100644 index 000000000..e3f29fab2 --- /dev/null +++ b/pyload/webui/themes/Flat/img/control_add_blue.png diff --git a/pyload/webui/themes/Flat/img/control_cancel.png b/pyload/webui/themes/Flat/img/control_cancel.png Binary files differnew file mode 100644 index 000000000..07c9cad30 --- /dev/null +++ b/pyload/webui/themes/Flat/img/control_cancel.png diff --git a/pyload/webui/themes/Flat/img/control_cancel_blue.png b/pyload/webui/themes/Flat/img/control_cancel_blue.png Binary files differnew file mode 100644 index 000000000..07c9cad30 --- /dev/null +++ b/pyload/webui/themes/Flat/img/control_cancel_blue.png diff --git a/pyload/webui/themes/Flat/img/control_pause.png b/pyload/webui/themes/Flat/img/control_pause.png Binary files differnew file mode 100644 index 000000000..24e3705fa --- /dev/null +++ b/pyload/webui/themes/Flat/img/control_pause.png diff --git a/pyload/webui/themes/Flat/img/control_pause_blue.png b/pyload/webui/themes/Flat/img/control_pause_blue.png Binary files differnew file mode 100644 index 000000000..24e3705fa --- /dev/null +++ b/pyload/webui/themes/Flat/img/control_pause_blue.png diff --git a/pyload/webui/themes/Flat/img/control_play.png b/pyload/webui/themes/Flat/img/control_play.png Binary files differnew file mode 100644 index 000000000..15ced1e21 --- /dev/null +++ b/pyload/webui/themes/Flat/img/control_play.png diff --git a/pyload/webui/themes/Flat/img/control_play_blue.png b/pyload/webui/themes/Flat/img/control_play_blue.png Binary files differnew file mode 100644 index 000000000..15ced1e21 --- /dev/null +++ b/pyload/webui/themes/Flat/img/control_play_blue.png diff --git a/pyload/webui/themes/Flat/img/control_stop.png b/pyload/webui/themes/Flat/img/control_stop.png Binary files differnew file mode 100644 index 000000000..71215ef67 --- /dev/null +++ b/pyload/webui/themes/Flat/img/control_stop.png diff --git a/pyload/webui/themes/Flat/img/control_stop_blue.png b/pyload/webui/themes/Flat/img/control_stop_blue.png Binary files differnew file mode 100644 index 000000000..71215ef67 --- /dev/null +++ b/pyload/webui/themes/Flat/img/control_stop_blue.png diff --git a/pyload/webui/themes/Flat/img/delete.png b/pyload/webui/themes/Flat/img/delete.png Binary files differnew file mode 100644 index 000000000..4539cff12 --- /dev/null +++ b/pyload/webui/themes/Flat/img/delete.png diff --git a/pyload/webui/themes/Flat/img/drag_corner.gif b/pyload/webui/themes/Flat/img/drag_corner.gif Binary files differnew file mode 100644 index 000000000..befb1adf1 --- /dev/null +++ b/pyload/webui/themes/Flat/img/drag_corner.gif diff --git a/pyload/webui/themes/Flat/img/error.png b/pyload/webui/themes/Flat/img/error.png Binary files differnew file mode 100644 index 000000000..6c565c99c --- /dev/null +++ b/pyload/webui/themes/Flat/img/error.png diff --git a/pyload/webui/themes/Flat/img/folder.png b/pyload/webui/themes/Flat/img/folder.png Binary files differnew file mode 100644 index 000000000..0b067dd3c --- /dev/null +++ b/pyload/webui/themes/Flat/img/folder.png diff --git a/pyload/webui/themes/Flat/img/full.png b/pyload/webui/themes/Flat/img/full.png Binary files differnew file mode 100644 index 000000000..fea52af76 --- /dev/null +++ b/pyload/webui/themes/Flat/img/full.png diff --git a/pyload/webui/themes/Flat/img/head-login.png b/pyload/webui/themes/Flat/img/head-login.png Binary files differnew file mode 100644 index 000000000..6b57515bc --- /dev/null +++ b/pyload/webui/themes/Flat/img/head-login.png diff --git a/pyload/webui/themes/Flat/img/head-menu-collector.png b/pyload/webui/themes/Flat/img/head-menu-collector.png Binary files differnew file mode 100644 index 000000000..bbcbe6406 --- /dev/null +++ b/pyload/webui/themes/Flat/img/head-menu-collector.png diff --git a/pyload/webui/themes/Flat/img/head-menu-config.png b/pyload/webui/themes/Flat/img/head-menu-config.png Binary files differnew file mode 100644 index 000000000..93e8f83ac --- /dev/null +++ b/pyload/webui/themes/Flat/img/head-menu-config.png diff --git a/pyload/webui/themes/Flat/img/head-menu-development.png b/pyload/webui/themes/Flat/img/head-menu-development.png Binary files differnew file mode 100644 index 000000000..33d8b062f --- /dev/null +++ b/pyload/webui/themes/Flat/img/head-menu-development.png diff --git a/pyload/webui/themes/Flat/img/head-menu-download.png b/pyload/webui/themes/Flat/img/head-menu-download.png Binary files differnew file mode 100644 index 000000000..3691deebc --- /dev/null +++ b/pyload/webui/themes/Flat/img/head-menu-download.png diff --git a/pyload/webui/themes/Flat/img/head-menu-home.png b/pyload/webui/themes/Flat/img/head-menu-home.png Binary files differnew file mode 100644 index 000000000..b77bef5eb --- /dev/null +++ b/pyload/webui/themes/Flat/img/head-menu-home.png diff --git a/pyload/webui/themes/Flat/img/head-menu-index.png b/pyload/webui/themes/Flat/img/head-menu-index.png Binary files differnew file mode 100644 index 000000000..8bc6e9604 --- /dev/null +++ b/pyload/webui/themes/Flat/img/head-menu-index.png diff --git a/pyload/webui/themes/Flat/img/head-menu-news.png b/pyload/webui/themes/Flat/img/head-menu-news.png Binary files differnew file mode 100644 index 000000000..44e79a9a9 --- /dev/null +++ b/pyload/webui/themes/Flat/img/head-menu-news.png diff --git a/pyload/webui/themes/Flat/img/head-menu-queue.png b/pyload/webui/themes/Flat/img/head-menu-queue.png Binary files differnew file mode 100644 index 000000000..e4fa41ad8 --- /dev/null +++ b/pyload/webui/themes/Flat/img/head-menu-queue.png diff --git a/pyload/webui/themes/Flat/img/head-menu-recent.png b/pyload/webui/themes/Flat/img/head-menu-recent.png Binary files differnew file mode 100644 index 000000000..fc9b0497f --- /dev/null +++ b/pyload/webui/themes/Flat/img/head-menu-recent.png diff --git a/pyload/webui/themes/Flat/img/head-menu-wiki.png b/pyload/webui/themes/Flat/img/head-menu-wiki.png Binary files differnew file mode 100644 index 000000000..61b0e54ea --- /dev/null +++ b/pyload/webui/themes/Flat/img/head-menu-wiki.png diff --git a/pyload/webui/themes/Flat/img/head-search-noshadow.png b/pyload/webui/themes/Flat/img/head-search-noshadow.png Binary files differnew file mode 100644 index 000000000..16d39bd06 --- /dev/null +++ b/pyload/webui/themes/Flat/img/head-search-noshadow.png diff --git a/pyload/webui/themes/Flat/img/head_bg1.png b/pyload/webui/themes/Flat/img/head_bg1.png Binary files differnew file mode 100644 index 000000000..f2848c3cc --- /dev/null +++ b/pyload/webui/themes/Flat/img/head_bg1.png diff --git a/pyload/webui/themes/Flat/img/images.png b/pyload/webui/themes/Flat/img/images.png Binary files differnew file mode 100644 index 000000000..184860d1e --- /dev/null +++ b/pyload/webui/themes/Flat/img/images.png diff --git a/pyload/webui/themes/Flat/img/notice.png b/pyload/webui/themes/Flat/img/notice.png Binary files differnew file mode 100644 index 000000000..a52b067cb --- /dev/null +++ b/pyload/webui/themes/Flat/img/notice.png diff --git a/pyload/webui/themes/Flat/img/package_go.png b/pyload/webui/themes/Flat/img/package_go.png Binary files differnew file mode 100644 index 000000000..80b2c42ee --- /dev/null +++ b/pyload/webui/themes/Flat/img/package_go.png diff --git a/pyload/webui/themes/Flat/img/page-tools-backlinks.png b/pyload/webui/themes/Flat/img/page-tools-backlinks.png Binary files differnew file mode 100644 index 000000000..fb8f55b38 --- /dev/null +++ b/pyload/webui/themes/Flat/img/page-tools-backlinks.png diff --git a/pyload/webui/themes/Flat/img/page-tools-edit.png b/pyload/webui/themes/Flat/img/page-tools-edit.png Binary files differnew file mode 100644 index 000000000..67177cf89 --- /dev/null +++ b/pyload/webui/themes/Flat/img/page-tools-edit.png diff --git a/pyload/webui/themes/Flat/img/page-tools-revisions.png b/pyload/webui/themes/Flat/img/page-tools-revisions.png Binary files differnew file mode 100644 index 000000000..088fe0087 --- /dev/null +++ b/pyload/webui/themes/Flat/img/page-tools-revisions.png diff --git a/pyload/webui/themes/Flat/img/parseUri.png b/pyload/webui/themes/Flat/img/parseUri.png Binary files differnew file mode 100644 index 000000000..937bded9d --- /dev/null +++ b/pyload/webui/themes/Flat/img/parseUri.png diff --git a/pyload/webui/themes/Flat/img/pencil.png b/pyload/webui/themes/Flat/img/pencil.png Binary files differnew file mode 100644 index 000000000..e39c93cd8 --- /dev/null +++ b/pyload/webui/themes/Flat/img/pencil.png diff --git a/pyload/webui/themes/Flat/img/pyload-logo.png b/pyload/webui/themes/Flat/img/pyload-logo.png Binary files differnew file mode 100644 index 000000000..2443cd8b1 --- /dev/null +++ b/pyload/webui/themes/Flat/img/pyload-logo.png diff --git a/pyload/webui/themes/Flat/img/reconnect.png b/pyload/webui/themes/Flat/img/reconnect.png Binary files differnew file mode 100644 index 000000000..cd35c9325 --- /dev/null +++ b/pyload/webui/themes/Flat/img/reconnect.png diff --git a/pyload/webui/themes/Flat/img/status_None.png b/pyload/webui/themes/Flat/img/status_None.png Binary files differnew file mode 100644 index 000000000..1400d3eb3 --- /dev/null +++ b/pyload/webui/themes/Flat/img/status_None.png diff --git a/pyload/webui/themes/Flat/img/status_downloading.png b/pyload/webui/themes/Flat/img/status_downloading.png Binary files differnew file mode 100644 index 000000000..db8ad8cd6 --- /dev/null +++ b/pyload/webui/themes/Flat/img/status_downloading.png diff --git a/pyload/webui/themes/Flat/img/status_failed.png b/pyload/webui/themes/Flat/img/status_failed.png Binary files differnew file mode 100644 index 000000000..6c565c99c --- /dev/null +++ b/pyload/webui/themes/Flat/img/status_failed.png diff --git a/pyload/webui/themes/Flat/img/status_finished.png b/pyload/webui/themes/Flat/img/status_finished.png Binary files differnew file mode 100644 index 000000000..2c4aca40d --- /dev/null +++ b/pyload/webui/themes/Flat/img/status_finished.png diff --git a/pyload/webui/themes/Flat/img/status_offline.png b/pyload/webui/themes/Flat/img/status_offline.png Binary files differnew file mode 100644 index 000000000..6c565c99c --- /dev/null +++ b/pyload/webui/themes/Flat/img/status_offline.png diff --git a/pyload/webui/themes/Flat/img/status_proc.png b/pyload/webui/themes/Flat/img/status_proc.png Binary files differnew file mode 100644 index 000000000..833f779ac --- /dev/null +++ b/pyload/webui/themes/Flat/img/status_proc.png diff --git a/pyload/webui/themes/Flat/img/status_queue.png b/pyload/webui/themes/Flat/img/status_queue.png Binary files differnew file mode 100644 index 000000000..1400d3eb3 --- /dev/null +++ b/pyload/webui/themes/Flat/img/status_queue.png diff --git a/pyload/webui/themes/Flat/img/status_waiting.png b/pyload/webui/themes/Flat/img/status_waiting.png Binary files differnew file mode 100644 index 000000000..fd038175e --- /dev/null +++ b/pyload/webui/themes/Flat/img/status_waiting.png diff --git a/pyload/webui/themes/Flat/img/success.png b/pyload/webui/themes/Flat/img/success.png Binary files differnew file mode 100644 index 000000000..2c4aca40d --- /dev/null +++ b/pyload/webui/themes/Flat/img/success.png diff --git a/pyload/webui/themes/Flat/img/tab-background.png b/pyload/webui/themes/Flat/img/tab-background.png Binary files differnew file mode 100644 index 000000000..29a5d1991 --- /dev/null +++ b/pyload/webui/themes/Flat/img/tab-background.png diff --git a/pyload/webui/themes/Flat/img/tabs-border-bottom.png b/pyload/webui/themes/Flat/img/tabs-border-bottom.png Binary files differnew file mode 100644 index 000000000..02440f428 --- /dev/null +++ b/pyload/webui/themes/Flat/img/tabs-border-bottom.png diff --git a/pyload/webui/themes/Flat/img/user-actions-logout.png b/pyload/webui/themes/Flat/img/user-actions-logout.png Binary files differnew file mode 100644 index 000000000..d4ef360e8 --- /dev/null +++ b/pyload/webui/themes/Flat/img/user-actions-logout.png diff --git a/pyload/webui/themes/Flat/img/user-actions-profile.png b/pyload/webui/themes/Flat/img/user-actions-profile.png Binary files differnew file mode 100644 index 000000000..9ec410b13 --- /dev/null +++ b/pyload/webui/themes/Flat/img/user-actions-profile.png diff --git a/pyload/webui/themes/Flat/img/user-info.png b/pyload/webui/themes/Flat/img/user-info.png Binary files differnew file mode 100644 index 000000000..345ed52e4 --- /dev/null +++ b/pyload/webui/themes/Flat/img/user-info.png diff --git a/pyload/webui/themes/Flat/js/admin.coffee b/pyload/webui/themes/Flat/js/admin.coffee new file mode 100644 index 000000000..5afbcbb66 --- /dev/null +++ b/pyload/webui/themes/Flat/js/admin.coffee @@ -0,0 +1,58 @@ +root = this + +window.addEvent "domready", -> + + root.passwordDialog = new MooDialog {destroyOnHide: false} + root.passwordDialog.setContent $ 'password_box' + + $("login_password_reset").addEvent "click", (e) -> root.passwordDialog.close() + $("login_password_button").addEvent "click", (e) -> + + newpw = $("login_new_password").get("value") + newpw2 = $("login_new_password2").get("value") + + if newpw is newpw2 + form = $("password_form") + form.set "send", { + onSuccess: (data) -> + root.notify.alert "Success", { + 'className': 'success' + } + onFailure: (data) -> + root.notify.alert "Error", { + 'className': 'error' + } + } + + form.send() + + root.passwordDialog.close() + else + alert '{{_("Passwords did not match.")}}' + + e.stop() + + for item in $$(".change_password") + id = item.get("id") + user = id.split("|")[1] + $("user_login").set("value", user) + item.addEvent "click", (e) -> root.passwordDialog.open() + + $('quit-pyload').addEvent "click", (e) -> + new MooDialog.Confirm "{{_('You are really sure you want to quit pyLoad?')}}", -> + new Request.JSON({ + url: '/api/kill' + method: 'get' + }).send() + , -> + e.stop() + + $('restart-pyload').addEvent "click", (e) -> + new MooDialog.Confirm "{{_('Are you sure you want to restart pyLoad?')}}", -> + new Request.JSON({ + url: '/api/restart' + method: 'get' + onSuccess: (data) -> alert "{{_('pyLoad restarted')}}" + }).send() + , -> + e.stop() diff --git a/pyload/webui/themes/Flat/js/admin.min.js b/pyload/webui/themes/Flat/js/admin.min.js new file mode 100644 index 000000000..94a5e494d --- /dev/null +++ b/pyload/webui/themes/Flat/js/admin.min.js @@ -0,0 +1,3 @@ +{% autoescape true %} +var root;root=this;window.addEvent("domready",function(){var f,c,b,e,a,d;root.passwordDialog=new MooDialog({destroyOnHide:false});root.passwordDialog.setContent($("password_box"));$("login_password_reset").addEvent("click",function(g){return root.passwordDialog.close()});$("login_password_button").addEvent("click",function(j){var h,i,g;i=$("login_new_password").get("value");g=$("login_new_password2").get("value");if(i===g){h=$("password_form");h.set("send",{onSuccess:function(k){return root.notify.alert("Success",{className:"success"})},onFailure:function(k){return root.notify.alert("Error",{className:"error"})}});h.send();root.passwordDialog.close()}else{alert('{{_("Passwords did not match.")}}')}return j.stop()});d=$$(".change_password");for(e=0,a=d.length;e<a;e++){c=d[e];f=c.get("id");b=f.split("|")[1];$("user_login").set("value",b);c.addEvent("click",function(g){return root.passwordDialog.open()})}$("quit-pyload").addEvent("click",function(g){new MooDialog.Confirm("{{_('You are really sure you want to quit pyLoad?')}}",function(){return new Request.JSON({url:"/api/kill",method:"get"}).send()},function(){});return g.stop()});return $("restart-pyload").addEvent("click",function(g){new MooDialog.Confirm("{{_('Are you sure you want to restart pyLoad?')}}",function(){return new Request.JSON({url:"/api/restart",method:"get",onSuccess:function(h){return alert("{{_('pyLoad restarted')}}")}}).send()},function(){});return g.stop()})}); +{% endautoescape %} diff --git a/pyload/webui/themes/Flat/js/base.coffee b/pyload/webui/themes/Flat/js/base.coffee new file mode 100644 index 000000000..07b8bfb6f --- /dev/null +++ b/pyload/webui/themes/Flat/js/base.coffee @@ -0,0 +1,177 @@ +# External scope +root = this + +# helper functions +humanFileSize = (size) -> + filesizename = new Array("B", "KiB", "MiB", "GiB", "TiB", "PiB") + loga = Math.log(size) / Math.log(1024) + i = Math.floor(loga) + a = Math.pow(1024, i) + if size is 0 then "0 B" else (Math.round(size * 100 / a) / 100 + " " + filesizename[i]) + + +parseUri = () -> + oldString = $("add_links").value + regxp = new RegExp('(ht|f)tp(s?):\/\/[a-zA-Z0-9\-\.\/\?=_&%#]+[<| |\"|\'|\r|\n|\t]{1}', 'g') + resu = oldString.match regxp + return if resu == null + res = "" + + for part in resu + if part.indexOf(" ") != -1 + res = res + part.replace(" ", " \n") + else if part.indexOf("\t") != -1 + res = res + part.replace("\t", " \n") + else if part.indexOf("\r") != -1 + res = res + part.replace("\r", " \n") + else if part.indexOf("\"") != -1 + res = res + part.replace("\"", " \n") + else if part.indexOf("<") != -1 + res = res + part.replace("<", " \n") + else if part.indexOf("'") != -1 + res = res + part.replace("'", " \n") + else + res = res + part.replace("\n", " \n") + + $("add_links").value = res + + +Array::remove = (from, to) -> + rest = this.slice((to || from) + 1 || this.length) + this.length = from < 0 ? this.length + from : from + return [] if this.length == 0 + return this.push.apply(this, rest) + + +document.addEvent "domready", -> + + # global notification + root.notify = new Purr { + 'mode': 'top' + 'position': 'center' + } + + root.captchaBox = new MooDialog {destroyOnHide: false} + root.captchaBox.setContent $ 'cap_box' + + root.addBox = new MooDialog {destroyOnHide: false} + root.addBox.setContent $ 'add_box' + + $('add_form').onsubmit = -> + $('add_form').target = 'upload_target' + if $('add_name').value is "" and $('add_file').value is "" + alert '{{_("Please Enter a packagename.")}}' + return false + else + root.addBox.close() + return true + + $('add_reset').addEvent 'click', -> root.addBox.close() + + $('action_add').addEvent 'click', -> $("add_form").reset(); root.addBox.open() + $('action_play').addEvent 'click', -> new Request({method: 'get', url: '/api/unpauseServer'}).send() + $('action_cancel').addEvent 'click', -> new Request({method: 'get', url: '/api/stopAllDownloads'}).send() + $('action_stop').addEvent 'click', -> new Request({method: 'get', url: '/api/pauseServer'}).send() + + + # captcha events + + $('cap_info').addEvent 'click', -> + load_captcha "get", "" + root.captchaBox.open() + $('cap_reset').addEvent 'click', -> root.captchaBox.close() + $('cap_form').addEvent 'submit', (e) -> + submit_captcha() + e.stop() + + $('cap_positional').addEvent 'click', on_captcha_click + + new Request.JSON({ + url: "/json/status" + onSuccess: LoadJsonToContent + secure: false + async: true + initialDelay: 0 + delay: 4000 + limit: 3000 + }).startTimer() + + +LoadJsonToContent = (data) -> + $("speed").set 'text', humanFileSize(data.speed)+"/s" + $("aktiv").set 'text', data.active + $("aktiv_from").set 'text', data.queue + $("aktiv_total").set 'text', data.total + + if data.captcha + if $("cap_info").getStyle("display") != "inline" + $("cap_info").setStyle 'display', 'inline' + root.notify.alert '{{_("New Captcha Request")}}', { + 'className': 'notify' + } + else + $("cap_info").setStyle 'display', 'none' + + + if data.download + $("time").set 'text', ' {{_("on")}}' + $("time").setStyle 'background-color', "#8ffc25" + else + $("time").set 'text', ' {{_("off")}}' + $("time").setStyle 'background-color', "#fc6e26" + + if data.reconnect + $("reconnect").set 'text', ' {{_("on")}}' + $("reconnect").setStyle 'background-color', "#8ffc25" + else + $("reconnect").set 'text', ' {{_("off")}}' + $("reconnect").setStyle 'background-color', "#fc6e26" + + return null + + +set_captcha = (data) -> + $('cap_id').set 'value', data.id + if (data.result_type is 'textual') + $('cap_textual_img').set 'src', data.src + $('cap_title').set 'text', '{{_("Please read the text on the captcha.")}}' + $('cap_submit').setStyle 'display', 'inline' + $('cap_textual').setStyle 'display', 'block' + $('cap_positional').setStyle 'display', 'none' + + else if (data.result_type == 'positional') + $('cap_positional_img').set('src', data.src) + $('cap_title').set('text', '{{_("Please click on the right captcha position.")}}') + $('cap_submit').setStyle('display', 'none') + $('cap_textual').setStyle('display', 'none') + + +load_captcha = (method, post) -> + new Request.JSON({ + url: "/json/set_captcha" + onSuccess: (data) -> set_captcha(data) if data.captcha else clear_captcha() + secure: false + async: true + method: method + }).send(post) + + +clear_captcha = -> + $('cap_textual').setStyle 'display', 'none' + $('cap_textual_img').set 'src', '' + $('cap_positional').setStyle 'display', 'none' + $('cap_positional_img').set 'src', '' + $('cap_title').set 'text', '{{_("No Captchas to read.")}}' + + +submit_captcha = -> + load_captcha("post", "cap_id=" + $('cap_id').get('value') + "&cap_result=" + $('cap_result').get('value') ) + $('cap_result').set('value', '') + + +on_captcha_click = (e) -> + position = e.target.getPosition() + x = e.page.x - position.x + y = e.page.y - position.y + $('cap_result').value = x + "," + y + submit_captcha() diff --git a/pyload/webui/themes/Flat/js/base.min.js b/pyload/webui/themes/Flat/js/base.min.js new file mode 100644 index 000000000..1ba1d73f9 --- /dev/null +++ b/pyload/webui/themes/Flat/js/base.min.js @@ -0,0 +1,3 @@ +{% autoescape true %} +var LoadJsonToContent,clear_captcha,humanFileSize,load_captcha,on_captcha_click,parseUri,root,set_captcha,submit_captcha;root=this;humanFileSize=function(f){var c,d,e,b;d=new Array("B","KiB","MiB","GiB","TiB","PiB");b=Math.log(f)/Math.log(1024);e=Math.floor(b);c=Math.pow(1024,e);if(f===0){return"0 B"}else{return Math.round(f*100/c)/100+" "+d[e]}};parseUri=function(){var b,c,g,e,d,f,a;b=$("add_links").value;g=new RegExp("(ht|f)tp(s?)://[a-zA-Z0-9-./?=_&%#]+[<| |\"|'|\r|\n|\t]{1}","g");d=b.match(g);if(d===null){return}e="";for(f=0,a=d.length;f<a;f++){c=d[f];if(c.indexOf(" ")!==-1){e=e+c.replace(" "," \n")}else{if(c.indexOf("\t")!==-1){e=e+c.replace("\t"," \n")}else{if(c.indexOf("\r")!==-1){e=e+c.replace("\r"," \n")}else{if(c.indexOf('"')!==-1){e=e+c.replace('"'," \n")}else{if(c.indexOf("<")!==-1){e=e+c.replace("<"," \n")}else{if(c.indexOf("'")!==-1){e=e+c.replace("'"," \n")}else{e=e+c.replace("\n"," \n")}}}}}}}return $("add_links").value=e};Array.prototype.remove=function(d,c){var a,b;a=this.slice((c||d)+1||this.length);this.length=(b=d<0)!=null?b:this.length+{from:d};if(this.length===0){return[]}return this.push.apply(this,a)};document.addEvent("domready",function(){root.notify=new Purr({mode:"top",position:"center"});root.captchaBox=new MooDialog({destroyOnHide:false});root.captchaBox.setContent($("cap_box"));root.addBox=new MooDialog({destroyOnHide:false});root.addBox.setContent($("add_box"));$("add_form").onsubmit=function(){$("add_form").target="upload_target";if($("add_name").value===""&&$("add_file").value===""){alert('{{_("Please Enter a packagename.")}}');return false}else{root.addBox.close();return true}};$("add_reset").addEvent("click",function(){return root.addBox.close()});$("action_add").addEvent("click",function(){$("add_form").reset();return root.addBox.open()});$("action_play").addEvent("click",function(){return new Request({method:"get",url:"/api/unpauseServer"}).send()});$("action_cancel").addEvent("click",function(){return new Request({method:"get",url:"/api/stopAllDownloads"}).send()});$("action_stop").addEvent("click",function(){return new Request({method:"get",url:"/api/pauseServer"}).send()});$("cap_info").addEvent("click",function(){load_captcha("get","");return root.captchaBox.open()});$("cap_reset").addEvent("click",function(){return root.captchaBox.close()});$("cap_form").addEvent("submit",function(a){submit_captcha();return a.stop()});$("cap_positional").addEvent("click",on_captcha_click);return new Request.JSON({url:"/json/status",onSuccess:LoadJsonToContent,secure:false,async:true,initialDelay:0,delay:4000,limit:3000}).startTimer()});LoadJsonToContent=function(a){$("speed").set("text",humanFileSize(a.speed)+"/s");$("aktiv").set("text",a.active);$("aktiv_from").set("text",a.queue);$("aktiv_total").set("text",a.total);if(a.captcha){if($("cap_info").getStyle("display")!=="inline"){$("cap_info").setStyle("display","inline");root.notify.alert('{{_("New Captcha Request")}}',{className:"notify"})}}else{$("cap_info").setStyle("display","none")}if(a.download){$("time").set("text",' {{_("on")}}');$("time").setStyle("background-color","#8ffc25")}else{$("time").set("text",' {{_("off")}}');$("time").setStyle("background-color","#fc6e26")}if(a.reconnect){$("reconnect").set("text",' {{_("on")}}');$("reconnect").setStyle("background-color","#8ffc25")}else{$("reconnect").set("text",' {{_("off")}}');$("reconnect").setStyle("background-color","#fc6e26")}return null};set_captcha=function(a){$("cap_id").set("value",a.id);if(a.result_type==="textual"){$("cap_textual_img").set("src",a.src);$("cap_title").set("text",'{{_("Please read the text on the captcha.")}}');$("cap_submit").setStyle("display","inline");$("cap_textual").setStyle("display","block");return $("cap_positional").setStyle("display","none")}else{if(a.result_type==="positional"){$("cap_positional_img").set("src",a.src);$("cap_title").set("text",'{{_("Please click on the right captcha position.")}}');$("cap_submit").setStyle("display","none");return $("cap_textual").setStyle("display","none")}}};load_captcha=function(b,a){return new Request.JSON({url:"/json/set_captcha",onSuccess:function(c){return set_captcha(c)(c.captcha?void 0:clear_captcha())},secure:false,async:true,method:b}).send(a)};clear_captcha=function(){$("cap_textual").setStyle("display","none");$("cap_textual_img").set("src","");$("cap_positional").setStyle("display","none");$("cap_positional_img").set("src","");return $("cap_title").set("text",'{{_("No Captchas to read.")}}')};submit_captcha=function(){load_captcha("post","cap_id="+$("cap_id").get("value")+"&cap_result="+$("cap_result").get("value"));$("cap_result").set("value","");return false};on_captcha_click=function(c){var b,a,d;b=c.target.getPosition();a=c.page.x-b.x;d=c.page.y-b.y;$("cap_result").value=a+","+d;return submit_captcha()}; +{% endautoescape %} diff --git a/pyload/webui/themes/Flat/js/filemanager.js b/pyload/webui/themes/Flat/js/filemanager.js new file mode 100644 index 000000000..ab9b8b492 --- /dev/null +++ b/pyload/webui/themes/Flat/js/filemanager.js @@ -0,0 +1,291 @@ +var load, rename_box, confirm_box; + +document.addEvent("domready", function() { + load = new Fx.Tween($("load-indicator"), {link: "cancel"}); + load.set("opacity", 0); + + rename_box = new Fx.Tween($('rename_box')); + confirm_box = new Fx.Tween($('confirm_box')); + $('rename_reset').addEvent('click', function() { + hide_rename_box() + }); + $('delete_reset').addEvent('click', function() { + hide_confirm_box() + }); + + /*$('filemanager_actions_list').getChildren("li").each(function(action) { + var action_name = action.className; + if(functions[action.className] != undefined) + { + action.addEvent('click', functions[action.className]); + } + });*/ +}); + +function indicateLoad() { + //$("load-indicator").reveal(); + load.start("opacity", 1) +} + +function indicateFinish() { + load.start("opacity", 0) +} + +function indicateSuccess() { + indicateFinish(); + notify.alert('{{_("Success")}}.', { + 'className': 'success' + }); +} + +function indicateFail() { + indicateFinish(); + notify.alert('{{_("Failed")}}.', { + 'className': 'error' + }); +} + +function show_rename_box() { + bg_show(); + $("rename_box").setStyle('display', 'block'); + rename_box.start('opacity', 1) +} + +function hide_rename_box() { + bg_hide(); + rename_box.start('opacity', 0).chain(function() { + $('rename_box').setStyle('display', 'none'); + }); +} + +function show_confirm_box() { + bg_show(); + $("confirm_box").setStyle('display', 'block'); + confirm_box.start('opacity', 1) +} + +function hide_confirm_box() { + bg_hide(); + confirm_box.start('opacity', 0).chain(function() { + $('confirm_box').setStyle('display', 'none'); + }); +} + +var FilemanagerUI = new Class({ + initialize: function(url, type) { + this.url = url; + this.type = type; + this.directories = []; + this.files = []; + this.parseChildren(); + }, + + parseChildren: function() { + $("directories-list").getChildren("li.folder").each(function(ele) { + var path = ele.getElements("input.path")[0].get("value"); + var name = ele.getElements("input.name")[0].get("value"); + this.directories.push(new Item(this, path, name, ele)) + }.bind(this)); + + $("directories-list").getChildren("li.file").each(function(ele) { + var path = ele.getElements("input.path")[0].get("value"); + var name = ele.getElements("input.name")[0].get("value"); + this.files.push(new Item(this, path, name, ele)) + }.bind(this)); + } +}); + +var Item = new Class({ + initialize: function(ui, path, name, ele) { + this.ui = ui; + this.path = path; + this.name = name; + this.ele = ele; + this.directories = []; + this.files = []; + this.actions = new Array(); + this.actions["delete"] = this.del; + this.actions["rename"] = this.rename; + this.actions["mkdir"] = this.mkdir; + this.parseElement(); + + var pname = this.ele.getElements("span")[0]; + this.buttons = new Fx.Tween(this.ele.getElements(".buttons")[0], {link: "cancel"}); + this.buttons.set("opacity", 0); + + pname.addEvent("mouseenter", function(e) { + this.buttons.start("opacity", 1) + }.bind(this)); + + pname.addEvent("mouseleave", function(e) { + this.buttons.start("opacity", 0) + }.bind(this)); + + }, + + parseElement: function() { + this.ele.getChildren('span span.buttons img').each(function(img) { + img.addEvent('click', this.actions[img.className].bind(this)); + }, this); + + //click on the directory name must open the directory itself + this.ele.getElements('b')[0].addEvent('click', this.toggle.bind(this)); + + //iterate over child directories + var uls = this.ele.getElements('ul'); + if(uls.length > 0) + { + uls[0].getChildren("li.folder").each(function(fld) { + var path = fld.getElements("input.path")[0].get("value"); + var name = fld.getElements("input.name")[0].get("value"); + this.directories.push(new Item(this, path, name, fld)); + }.bind(this)); + uls[0].getChildren("li.file").each(function(fld) { + var path = fld.getElements("input.path")[0].get("value"); + var name = fld.getElements("input.name")[0].get("value"); + this.files.push(new Item(this, path, name, fld)); + }.bind(this)); + } + }, + + reorderElements: function() { + //TODO sort the main ul again (to keep data ordered after renaming something) + }, + + del: function(event) { + $("confirm_form").removeEvents("submit"); + $("confirm_form").addEvent("submit", this.deleteDirectory.bind(this)); + + $$("#confirm_form p").set('html', '{{_(("Are you sure you want to delete the selected item?"))}}'); + + show_confirm_box(); + event.stop(); + }, + + deleteDirectory: function(event) { + hide_confirm_box(); + new Request.JSON({ + method: 'POST', + url: "/json/filemanager/delete", + data: {'path': this.path, 'name': this.name}, + onSuccess: function(data) { + if(data.response == "success") + { + new Fx.Tween(this.ele).start('opacity', 0); + var ul = this.ele.parentNode; + this.ele.dispose(); + //if this was the only child, add a "empty folder" div + if(!ul.getChildren('li')[0]) + { + var div = new Element("div", { 'html': '{{ _("Folder is empty") }}' }); + div.replaces(ul); + } + + indicateSuccess(); + } else + { + //error from json code... + indicateFail(); + } + }.bind(this), + onFailure: indicateFail + }).send(); + + event.stop(); + }, + + rename: function(event) { + $("rename_form").removeEvents("submit"); + $("rename_form").addEvent("submit", this.renameDirectory.bind(this)); + + $("path").set("value", this.path); + $("old_name").set("value", this.name); + $("new_name").set("value", this.name); + + show_rename_box(); + event.stop(); + }, + + renameDirectory: function(event) { + hide_rename_box(); + new Request.JSON({ + method: 'POST', + url: "/json/filemanager/rename", + onSuccess: function(data) { + if(data.response == "success") + { + this.name = $("new_name").get("value"); + this.ele.getElements("b")[0].set('html', $("new_name").get("value")); + this.reorderElements(); + indicateSuccess(); + } else + { + //error from json code... + indicateFail(); + } + }.bind(this), + onFailure: indicateFail + }).send($("rename_form").toQueryString()); + + event.stop(); + }, + + mkdir: function(event) { + new Request.JSON({ + method: 'POST', + url: "/json/filemanager/mkdir", + data: {'path': this.path + "/" + this.name, 'name': '{{_("New folder")}}'}, + onSuccess: function(data) { + if(data.response == "success") + { + new Request.HTML({ + method: 'POST', + url: "/filemanager/get_dir", + data: {'path': data.path, 'name': data.name}, + onSuccess: function(li) { + //add node as first child of ul + var ul = this.ele.getChildren('ul')[0]; + if(!ul) + { + //remove the "Folder Empty" div + this.ele.getChildren('div').dispose(); + + //create new ul to contain subfolder + ul = new Element("ul"); + ul.inject(this.ele, 'bottom'); + } + li[0].inject(ul, 'top'); + + //add directory as a subdirectory of the current item + this.directories.push(new Item(this.ui, data.path, data.name, ul.firstChild)); + }.bind(this), + onFailure: indicateFail + }).send(); + indicateSuccess(); + } else + { + //error from json code... + indicateFail(); + } + }.bind(this), + onFailure: indicateFail + }).send(); + + event.stop(); + }, + + toggle: function() { + var child = this.ele.getElement('ul'); + if(child == null) + child = this.ele.getElement('div'); + + if(child != null) + { + if (child.getStyle('display') == "block") { + child.dissolve(); + } else { + child.reveal(); + } + } + } +}); diff --git a/pyload/webui/themes/Flat/js/package.js b/pyload/webui/themes/Flat/js/package.js new file mode 100644 index 000000000..659a8e6fc --- /dev/null +++ b/pyload/webui/themes/Flat/js/package.js @@ -0,0 +1,376 @@ +var root = this; + +document.addEvent("domready", function() { + root.load = new Fx.Tween($("load-indicator"), {link: "cancel"}); + root.load.set("opacity", 0); + + + root.packageBox = new MooDialog({destroyOnHide: false}); + root.packageBox.setContent($('pack_box')); + + $('pack_reset').addEvent('click', function() { + $('pack_form').reset(); + root.packageBox.close(); + }); +}); + +function indicateLoad() { + //$("load-indicator").reveal(); + root.load.start("opacity", 1) +} + +function indicateFinish() { + root.load.start("opacity", 0) +} + +function indicateSuccess() { + indicateFinish(); + root.notify.alert('{{_("Success")}}.', { + 'className': 'success' + }); +} + +function indicateFail() { + indicateFinish(); + root.notify.alert('{{_("Failed")}}.', { + 'className': 'error' + }); +} + +var PackageUI = new Class({ + initialize: function(url, type) { + this.url = url; + this.type = type; + this.packages = []; + this.parsePackages(); + + this.sorts = new Sortables($("package-list"), { + constrain: false, + clone: true, + revert: true, + opacity: 0.4, + handle: ".package_drag", + onComplete: this.saveSort.bind(this) + }); + + $("del_finished").addEvent("click", this.deleteFinished.bind(this)); + $("restart_failed").addEvent("click", this.restartFailed.bind(this)); + + }, + + parsePackages: function() { + $("package-list").getChildren("li").each(function(ele) { + var id = ele.getFirst().get("id").match(/[0-9]+/); + this.packages.push(new Package(this, id, ele)) + }.bind(this)) + }, + + loadPackages: function() { + }, + + deleteFinished: function() { + indicateLoad(); + new Request.JSON({ + method: 'get', + url: '/api/deleteFinished', + onSuccess: function(data) { + if (data.length > 0) { + window.location.reload() + } else { + this.packages.each(function(pack) { + pack.close(); + }); + indicateSuccess(); + } + }.bind(this), + onFailure: indicateFail + }).send(); + }, + + restartFailed: function() { + indicateLoad(); + new Request.JSON({ + method: 'get', + url: '/api/restartFailed', + onSuccess: function(data) { + this.packages.each(function(pack) { + pack.close(); + }); + indicateSuccess(); + }.bind(this), + onFailure: indicateFail + }).send(); + }, + + startSort: function(ele, copy) { + }, + + saveSort: function(ele, copy) { + var order = []; + this.sorts.serialize(function(li, pos) { + if (li == ele && ele.retrieve("order") != pos) { + order.push(ele.retrieve("pid") + "|" + pos) + } + li.store("order", pos) + }); + if (order.length > 0) { + indicateLoad(); + new Request.JSON({ + method: 'get', + url: '/json/package_order/' + order[0], + onSuccess: indicateFinish, + onFailure: indicateFail + }).send(); + } + } + +}); + +var Package = new Class({ + initialize: function(ui, id, ele, data) { + this.ui = ui; + this.id = id; + this.linksLoaded = false; + + if (!ele) { + this.createElement(data); + } else { + this.ele = ele; + this.order = ele.getElements("div.order")[0].get("html"); + this.ele.store("order", this.order); + this.ele.store("pid", this.id); + this.parseElement(); + } + + var pname = this.ele.getElements(".packagename")[0]; + this.buttons = new Fx.Tween(this.ele.getElements(".buttons")[0], {link: "cancel"}); + this.buttons.set("opacity", 0); + + pname.addEvent("mouseenter", function(e) { + this.buttons.start("opacity", 1) + }.bind(this)); + + pname.addEvent("mouseleave", function(e) { + this.buttons.start("opacity", 0) + }.bind(this)); + + + }, + + createElement: function() { + alert("create") + }, + + parseElement: function() { + var imgs = this.ele.getElements('img'); + + this.name = this.ele.getElements('.name')[0]; + this.folder = this.ele.getElements('.folder')[0]; + this.password = this.ele.getElements('.password')[0]; + + imgs[1].addEvent('click', this.deletePackage.bind(this)); + imgs[2].addEvent('click', this.restartPackage.bind(this)); + imgs[3].addEvent('click', this.editPackage.bind(this)); + imgs[4].addEvent('click', this.movePackage.bind(this)); + + this.ele.getElement('.packagename').addEvent('click', this.toggle.bind(this)); + + }, + + loadLinks: function() { + indicateLoad(); + new Request.JSON({ + method: 'get', + url: '/json/package/' + this.id, + onSuccess: this.createLinks.bind(this), + onFailure: indicateFail + }).send(); + }, + + createLinks: function(data) { + var ul = $("sort_children_{id}".substitute({'id': this.id})); + ul.set("html", ""); + data.links.each(function(link) { + link.id = link.fid; + var li = new Element("li", { + 'style': { + 'margin-left': 0 + } + }); + + var html = "<span style='cursor: move' class='child_status sorthandle'><img src='../img/{icon}' style='width: 12px; height:12px;'/></span>\n".substitute({'icon': link.icon}); + html += "<span style='font-size: 15px'><a href=\"{url}\" target=\"_blank\">{name}</a></span><br /><div class='child_secrow'>".substitute({'url': link.url, 'name': link.name}); + html += "<span class='child_status'>{statusmsg}</span>{error} ".substitute({'statusmsg': link.statusmsg, 'error':link.error}); + html += "<span class='child_status'>{format_size}</span>".substitute({'format_size': link.format_size}); + html += "<span class='child_status'>{plugin}</span> ".substitute({'plugin': link.plugin}); + html += "<img title='{{_(\"Delete Link\")}}' style='cursor: pointer;' width='10px' height='10px' src='../img/delete.png' /> "; + html += "<img title='{{_(\"Restart Link\")}}' style='cursor: pointer;margin-left: -4px' width='10px' height='10px' src='../img/arrow_refresh.png' /></div>"; + + var div = new Element("div", { + 'id': "file_" + link.id, + 'class': "child", + 'html': html + }); + + li.store("order", link.order); + li.store("lid", link.id); + + li.adopt(div); + ul.adopt(li); + }); + this.sorts = new Sortables(ul, { + constrain: false, + clone: true, + revert: true, + opacity: 0.4, + handle: ".sorthandle", + onComplete: this.saveSort.bind(this) + }); + this.registerLinkEvents(); + this.linksLoaded = true; + indicateFinish(); + this.toggle(); + }, + + registerLinkEvents: function() { + this.ele.getElements('.child').each(function(child) { + var lid = child.get('id').match(/[0-9]+/); + var imgs = child.getElements('.child_secrow img'); + imgs[0].addEvent('click', function(e) { + new Request({ + method: 'get', + url: '/api/deleteFiles/[' + this + "]", + onSuccess: function() { + $('file_' + this).nix() + }.bind(this), + onFailure: indicateFail + }).send(); + }.bind(lid)); + + imgs[1].addEvent('click', function(e) { + new Request({ + method: 'get', + url: '/api/restartFile/' + this, + onSuccess: function() { + var ele = $('file_' + this); + var imgs = ele.getElements("img"); + imgs[0].set("src", "../img/status_queue.png"); + var spans = ele.getElements(".child_status"); + spans[1].set("html", "queued"); + indicateSuccess(); + }.bind(this), + onFailure: indicateFail + }).send(); + }.bind(lid)); + }); + }, + + toggle: function() { + var child = this.ele.getElement('.children'); + if (child.getStyle('display') == "block") { + child.dissolve(); + } else { + if (!this.linksLoaded) { + this.loadLinks(); + } else { + child.reveal(); + } + } + }, + + + deletePackage: function(event) { + indicateLoad(); + new Request({ + method: 'get', + url: '/api/deletePackages/[' + this.id + "]", + onSuccess: function() { + this.ele.nix(); + indicateFinish(); + }.bind(this), + onFailure: indicateFail + }).send(); + //hide_pack(); + event.stop(); + }, + + restartPackage: function(event) { + indicateLoad(); + new Request({ + method: 'get', + url: '/api/restartPackage/' + this.id, + onSuccess: function() { + this.close(); + indicateSuccess(); + }.bind(this), + onFailure: indicateFail + }).send(); + event.stop(); + }, + + close: function() { + var child = this.ele.getElement('.children'); + if (child.getStyle('display') == "block") { + child.dissolve(); + } + var ul = $("sort_children_{id}".substitute({'id': this.id})); + ul.erase("html"); + this.linksLoaded = false; + }, + + movePackage: function(event) { + indicateLoad(); + new Request({ + method: 'get', + url: '/json/move_package/' + ((this.ui.type + 1) % 2) + "/" + this.id, + onSuccess: function() { + this.ele.nix(); + indicateFinish(); + }.bind(this), + onFailure: indicateFail + }).send(); + event.stop(); + }, + + editPackage: function(event) { + $("pack_form").removeEvents("submit"); + $("pack_form").addEvent("submit", this.savePackage.bind(this)); + + $("pack_id").set("value", this.id); + $("pack_name").set("value", this.name.get("text")); + $("pack_folder").set("value", this.folder.get("text")); + $("pack_pws").set("value", this.password.get("text")); + + root.packageBox.open(); + event.stop(); + }, + + savePackage: function(event) { + $("pack_form").send(); + this.name.set("text", $("pack_name").get("value")); + this.folder.set("text", $("pack_folder").get("value")); + this.password.set("text", $("pack_pws").get("value")); + root.packageBox.close(); + event.stop(); + }, + + saveSort: function(ele, copy) { + var order = []; + this.sorts.serialize(function(li, pos) { + if (li == ele && ele.retrieve("order") != pos) { + order.push(ele.retrieve("lid") + "|" + pos) + } + li.store("order", pos) + }); + if (order.length > 0) { + indicateLoad(); + new Request.JSON({ + method: 'get', + url: '/json/link_order/' + order[0], + onSuccess: indicateFinish, + onFailure: indicateFail + }).send(); + } + } + +}); diff --git a/pyload/webui/themes/Flat/js/settings.coffee b/pyload/webui/themes/Flat/js/settings.coffee new file mode 100644 index 000000000..d522741b9 --- /dev/null +++ b/pyload/webui/themes/Flat/js/settings.coffee @@ -0,0 +1,107 @@ +root = this + +window.addEvent 'domready', -> + root.accountDialog = new MooDialog {destroyOnHide: false} + root.accountDialog.setContent $ 'account_box' + + new TinyTab $$('#toptabs li a'), $$('#tabs-body > span') + + $$('ul.nav').each (nav) -> + new MooDropMenu nav, { + onOpen: (el) -> el.fade 'in' + onClose: (el) -> el.fade 'out' + onInitialize: (el) -> el.fade('hide').set 'tween', {duration:500} + } + + new SettingsUI() + + +class SettingsUI + constructor: -> + @menu = $$ "#general-menu li" + @menu.append $$ "#plugin-menu li" + + @name = $ "tabsback" + @general = $ "general_form_content" + @plugin = $ "plugin_form_content" + + el.addEvent 'click', @menuClick.bind(this) for el in @menu + + $("general|submit").addEvent "click", @configSubmit.bind(this) + $("plugin|submit").addEvent "click", @configSubmit.bind(this) + + $("account_add").addEvent "click", (e) -> + root.accountDialog.open() + e.stop() + + $("account_reset").addEvent "click", (e) -> + root.accountDialog.close() + + $("account_add_button").addEvent "click", @addAccount.bind(this) + $("account_submit").addEvent "click", @submitAccounts.bind(this) + + + menuClick: (e) -> + [category, section] = e.target.get("id").split("|") + name = e.target.get "text" + + + target = if category is "general" then @general else @plugin + target.dissolve() + + new Request({ + "method" : "get" + "url" : "/json/load_config/#{category}/#{section}" + 'onSuccess': (data) => + target.set "html", data + target.reveal() + this.name.set "text", name + }).send() + + + configSubmit: (e) -> + category = e.target.get("id").split("|")[0] + form = $("#{category}_form") + + form.set "send", { + 'method': "post" + 'url': "/json/save_config/#{category}" + "onSuccess" : -> + root.notify.alert '{{ _("Settings saved.")}}', { + 'className': 'success' + } + 'onFailure': -> + root.notify.alert '{{ _("Error occured.")}}', { + 'className': 'error' + } + } + form.send() + e.stop() + + addAccount: (e) -> + form = $ "add_account_form" + form.set "send", { + 'method': "post" + "onSuccess" : -> window.location.reload() + 'onFailure': -> + root.notify.alert '{{_("Error occured.")}}', { + 'className': 'error' + } + } + + form.send() + e.stop() + + submitAccounts: (e) -> + form = $ "account_form" + form.set "send", { + 'method': "post", + "onSuccess" : -> window.location.reload() + 'onFailure': -> + root.notify.alert('{{ _("Error occured.") }}', { + 'className': 'error' + }) + } + + form.send() + e.stop() diff --git a/pyload/webui/themes/Flat/js/settings.min.js b/pyload/webui/themes/Flat/js/settings.min.js new file mode 100644 index 000000000..41d1cb25a --- /dev/null +++ b/pyload/webui/themes/Flat/js/settings.min.js @@ -0,0 +1,3 @@ +{% autoescape true %} +var SettingsUI,root;var __bind=function(a,b){return function(){return a.apply(b,arguments)}};root=this;window.addEvent("domready",function(){root.accountDialog=new MooDialog({destroyOnHide:false});root.accountDialog.setContent($("account_box"));new TinyTab($$("#toptabs li a"),$$("#tabs-body > span"));$$("ul.nav").each(function(a){return new MooDropMenu(a,{onOpen:function(b){return b.fade("in")},onClose:function(b){return b.fade("out")},onInitialize:function(b){return b.fade("hide").set("tween",{duration:500})}})});return new SettingsUI()});SettingsUI=(function(){function a(){var c,e,b,d;this.menu=$$("#general-menu li");this.menu.append($$("#plugin-menu li"));this.name=$("tabsback");this.general=$("general_form_content");this.plugin=$("plugin_form_content");d=this.menu;for(e=0,b=d.length;e<b;e++){c=d[e];c.addEvent("click",this.menuClick.bind(this))}$("general|submit").addEvent("click",this.configSubmit.bind(this));$("plugin|submit").addEvent("click",this.configSubmit.bind(this));$("account_add").addEvent("click",function(f){root.accountDialog.open();return f.stop()});$("account_reset").addEvent("click",function(f){return root.accountDialog.close()});$("account_add_button").addEvent("click",this.addAccount.bind(this));$("account_submit").addEvent("click",this.submitAccounts.bind(this))}a.prototype.menuClick=function(h){var c,b,g,f,d;d=h.target.get("id").split("|"),c=d[0],g=d[1];b=h.target.get("text");f=c==="general"?this.general:this.plugin;f.dissolve();return new Request({method:"get",url:"/json/load_config/"+c+"/"+g,onSuccess:__bind(function(e){f.set("html",e);f.reveal();return this.name.set("text",b)},this)}).send()};a.prototype.configSubmit=function(d){var c,b;c=d.target.get("id").split("|")[0];b=$(""+c+"_form");b.set("send",{method:"post",url:"/json/save_config/"+c,onSuccess:function(){return root.notify.alert('{{ _("Settings saved.")}}',{className:"success"})},onFailure:function(){return root.notify.alert('{{ _("Error occured.")}}',{className:"error"})}});b.send();return d.stop()};a.prototype.addAccount=function(c){var b;b=$("add_account_form");b.set("send",{method:"post",onSuccess:function(){return window.location.reload()},onFailure:function(){return root.notify.alert('{{_("Error occured.")}}',{className:"error"})}});b.send();return c.stop()};a.prototype.submitAccounts=function(c){var b;b=$("account_form");b.set("send",{method:"post",onSuccess:function(){return window.location.reload()},onFailure:function(){return root.notify.alert('{{ _("Error occured.") }}',{className:"error"})}});b.send();return c.stop()};return a})(); +{% endautoescape %} diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Alert.js b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Alert.js new file mode 100644 index 000000000..1e2d4180b --- /dev/null +++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Alert.js @@ -0,0 +1,45 @@ +/* +--- +name: MooDialog.Alert +description: Creates an Alert dialog +authors: Arian Stolwijk +license: MIT-style license +requires: MooDialog +provides: MooDialog.Alert +... +*/ + + +MooDialog.Alert = new Class({ + + Extends: MooDialog, + + options: { + okText: 'Ok', + focus: true, + textPClass: 'MooDialogAlert' + }, + + initialize: function(msg, options){ + this.parent(options); + + var okButton = new Element('button', { + events: { + click: this.close.bind(this) + }, + text: this.options.okText + }); + + this.setContent( + new Element('p.' + this.options.textPClass, {text: msg}), + new Element('div.buttons').adopt(okButton) + ); + if (this.options.autoOpen) this.open(); + + if (this.options.focus) this.addEvent('show', function(){ + okButton.focus() + }); + + } +}); + diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Confirm.js b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Confirm.js new file mode 100644 index 000000000..16f32e290 --- /dev/null +++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Confirm.js @@ -0,0 +1,80 @@ +/* +--- +name: MooDialog.Confirm +description: Creates an Confirm Dialog +authors: Arian Stolwijk +license: MIT-style license +requires: MooDialog +provides: [MooDialog.Confirm, Element.confirmLinkClick, Element.confirmFormSubmit] +... +*/ + + +MooDialog.Confirm = new Class({ + + Extends: MooDialog, + + options: { + okText: 'Ok', + cancelText: 'Cancel', + focus: true, + textPClass: 'MooDialogConfirm' + }, + + initialize: function(msg, fn, fn1, options){ + this.parent(options); + var emptyFn = function(){}, + self = this; + + var buttons = [ + {fn: fn || emptyFn, txt: this.options.okText}, + {fn: fn1 || emptyFn, txt: this.options.cancelText} + ].map(function(button){ + return new Element('button', { + events: { + click: function(){ + button.fn(); + self.close(); + } + }, + text: button.txt + }); + }); + + this.setContent( + new Element('p.' + this.options.textPClass, {text: msg}), + new Element('div.buttons').adopt(buttons) + ); + if (this.options.autoOpen) this.open(); + + if(this.options.focus) this.addEvent('show', function(){ + buttons[1].focus(); + }); + + } +}); + + +Element.implement({ + + confirmLinkClick: function(msg, options){ + this.addEvent('click', function(e){ + e.stop(); + new MooDialog.Confirm(msg, function(){ + location.href = this.get('href'); + }.bind(this), null, options) + }); + return this; + }, + + confirmFormSubmit: function(msg, options){ + this.addEvent('submit', function(e){ + e.stop(); + new MooDialog.Confirm(msg, function(){ + this.submit(); + }.bind(this), null, options) + }.bind(this)); + return this; + } + +}); diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Error.js b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Error.js new file mode 100644 index 000000000..d32e30bce --- /dev/null +++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Error.js @@ -0,0 +1,21 @@ +/* +--- +name: MooDialog.Error +description: Creates an Error dialog +authors: Arian Stolwijk +license: MIT-style license +requires: MooDialog +provides: MooDialog.Error +... +*/ + + +MooDialog.Error = new Class({ + + Extends: MooDialog.Alert, + + options: { + textPClass: 'MooDialogError' + } + +}); diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Fx.js b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Fx.js new file mode 100644 index 000000000..353d947f5 --- /dev/null +++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Fx.js @@ -0,0 +1,47 @@ +/* +--- +name: MooDialog.Fx +description: Overwrite the default events so the Dialogs are using Fx on open and close +authors: Arian Stolwijk +license: MIT-style license +requires: [Core/Fx.Tween, Overlay] +provides: MooDialog.Fx +... +*/ + + +MooDialog.implement('options', { + + duration: 400, + closeOnOverlayClick: true, + + onInitialize: function(wrapper){ + this.fx = new Fx.Tween(wrapper, { + property: 'opacity', + duration: this.options.duration + }).set(0); + this.overlay = new Overlay(this.options.inject, { + duration: this.options.duration + }); + if (this.options.closeOnOverlayClick) this.overlay.addEvent('click', this.close.bind(this)); + + this.addEvent('hide', function(){ + if (this.options.destroyOnHide) this.overlay.overlay.destroy(); + }.bind(this)); + }, + + onBeforeOpen: function(wrapper){ + this.overlay.open(); + this.fx.start(1).chain(function(){ + this.fireEvent('show'); + }.bind(this)); + }, + + onBeforeClose: function(wrapper){ + this.overlay.close(); + this.fx.start(0).chain(function(){ + this.fireEvent('hide'); + }.bind(this)); + } + +}); diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.IFrame.js b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.IFrame.js new file mode 100644 index 000000000..029bf1f09 --- /dev/null +++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.IFrame.js @@ -0,0 +1,33 @@ +/* +--- +name: MooDialog.IFrame +description: Opens an IFrame in a MooDialog +authors: Arian Stolwijk +license: MIT-style license +requires: MooDialog +provides: MooDialog.IFrame +... +*/ + + +MooDialog.IFrame = new Class({ + + Extends: MooDialog, + + options: { + useScrollBar: true + }, + + initialize: function(url, options){ + this.parent(options); + + this.setContent( + new Element('iframe', { + src: url, + frameborder: 0, + scrolling: this.options.useScrollBar ? 'auto' : 'no' + }) + ); + if (this.options.autoOpen) this.open(); + } +}); diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Prompt.js b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Prompt.js new file mode 100644 index 000000000..c693e4a58 --- /dev/null +++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Prompt.js @@ -0,0 +1,48 @@ +/* +--- +name: MooDialog.Prompt +description: Creates a Prompt dialog +authors: Arian Stolwijk +license: MIT-style license +requires: MooDialog +provides: MooDialog.Prompt +... +*/ + + +MooDialog.Prompt = new Class({ + + Extends: MooDialog, + + options: { + okText: 'Ok', + focus: true, + textPClass: 'MooDialogPrompt', + defaultValue: '' + }, + + initialize: function(msg, fn, options){ + this.parent(options); + if (!fn) fn = function(){}; + + var textInput = new Element('input.textInput', {type: 'text', value: this.options.defaultValue}), + submitButton = new Element('input[type=submit]', {value: this.options.okText}), + formEvents = { + submit: function(e){ + e.stop(); + fn(textInput.get('value')); + this.close(); + }.bind(this) + }; + + this.setContent( + new Element('p.' + this.options.textPClass, {text: msg}), + new Element('form.buttons', {events: formEvents}).adopt(textInput, submitButton) + ); + if (this.options.autoOpen) this.open(); + + if (this.options.focus) this.addEvent('show', function(){ + textInput.focus(); + }); + } +}); diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Request.js b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Request.js new file mode 100644 index 000000000..7b8eb23c4 --- /dev/null +++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.Request.js @@ -0,0 +1,37 @@ +/* +--- +name: MooDialog.Request +description: Loads Data into a Dialog with Request +authors: Arian Stolwijk +license: MIT-style license +requires: [MooDialog, Core/Request.HTML] +provides: MooDialog.Request +... +*/ + +MooDialog.Request = new Class({ + + Extends: MooDialog, + + initialize: function(url, requestOptions, options){ + this.parent(options); + this.requestOptions = requestOptions || {}; + + this.addEvent('open', function(){ + var request = new Request.HTML(this.requestOptions).addEvent('success', function(text){ + this.setContent(text); + }.bind(this)).send({ + url: url + }); + }.bind(this)); + + if (this.options.autoOpen) this.open(); + + }, + + setRequestOptions: function(options){ + this.requestOptions = Object.merge(this.requestOptions, options); + return this; + } + +}); diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.js b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.js new file mode 100644 index 000000000..45a52496f --- /dev/null +++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/MooDialog.js @@ -0,0 +1,140 @@ +/* +--- +name: MooDialog +description: The base class of MooDialog +authors: Arian Stolwijk +license: MIT-style license +requires: [Core/Class, Core/Element, Core/Element.Style, Core/Element.Event] +provides: [MooDialog, Element.MooDialog] +... +*/ + + +var MooDialog = new Class({ + + Implements: [Options, Events], + + options: { + 'class': 'MooDialog', + title: null, + scroll: true, // IE + forceScroll: false, + useEscKey: true, + destroyOnHide: true, + autoOpen: true, + closeButton: true, + onInitialize: function(){ + this.wrapper.setStyle('display', 'none'); + }, + onBeforeOpen: function(){ + this.wrapper.setStyle('display', 'block'); + this.fireEvent('show'); + }, + onBeforeClose: function(){ + this.wrapper.setStyle('display', 'none'); + this.fireEvent('hide'); + }/*, + onOpen: function(){}, + onClose: function(){}, + onShow: function(){}, + onHide: function(){}, + onInitialize: function(wrapper){}, + onContentChange: function(content){}*/ + }, + + initialize: function(options){ + this.setOptions(options); + this.options.inject = this.options.inject || document.body; + options = this.options; + + var wrapper = this.wrapper = new Element('div.' + options['class'].replace(' ', '.')).inject(options.inject); + this.content = new Element('div.content').inject(wrapper); + + if (options.title){ + this.title = new Element('div.title').set('text', options.title).inject(wrapper); + wrapper.addClass('MooDialogTitle'); + } + + if (options.closeButton){ + this.closeButton = new Element('a.close', { + events: {click: this.close.bind(this)} + }).inject(wrapper); + } + + + /*<ie6>*/// IE 6 scroll + if ((options.scroll && Browser.ie6) || options.forceScroll){ + wrapper.setStyle('position', 'absolute'); + var position = wrapper.getPosition(options.inject); + window.addEvent('scroll', function(){ + var scroll = document.getScroll(); + wrapper.setPosition({ + x: position.x + scroll.x, + y: position.y + scroll.y + }); + }); + } + /*</ie6>*/ + + if (options.useEscKey){ + // Add event for the esc key + document.addEvent('keydown', function(e){ + if (e.key == 'esc') this.close(); + }.bind(this)); + } + + this.addEvent('hide', function(){ + if (options.destroyOnHide) this.destroy(); + }.bind(this)); + + this.fireEvent('initialize', wrapper); + }, + + setContent: function(){ + var content = Array.from(arguments); + if (content.length == 1) content = content[0]; + + this.content.empty(); + + var type = typeOf(content); + if (['string', 'number'].contains(type)) this.content.set('text', content); + else this.content.adopt(content); + + this.fireEvent('contentChange', this.content); + + return this; + }, + + open: function(){ + this.fireEvent('beforeOpen', this.wrapper).fireEvent('open'); + this.opened = true; + return this; + }, + + close: function(){ + this.fireEvent('beforeClose', this.wrapper).fireEvent('close'); + this.opened = false; + return this; + }, + + destroy: function(){ + this.wrapper.destroy(); + }, + + toElement: function(){ + return this.wrapper; + } + +}); + + +Element.implement({ + + MooDialog: function(options){ + this.store('MooDialog', + new MooDialog(options).setContent(this).open() + ); + return this; + } + +}); diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDialog/Overlay.js b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/Overlay.js new file mode 100644 index 000000000..35ab19c48 --- /dev/null +++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/Overlay.js @@ -0,0 +1,137 @@ +/* +--- + +name: Overlay + +authors: + - David Walsh (http://davidwalsh.name) + +license: + - MIT-style license + +requires: [Core/Class, Core/Element.Style, Core/Element.Event, Core/Element.Dimensions, Core/Fx.Tween] + +provides: + - Overlay +... +*/ + +var Overlay = new Class({ + + Implements: [Options, Events], + + options: { + id: 'overlay', + color: '#000', + duration: 500, + opacity: 0.5, + zIndex: 5000/*, + onClick: function(){}, + onClose: function(){}, + onHide: function(){}, + onOpen: function(){}, + onShow: function(){} + */ + }, + + initialize: function(container, options){ + this.setOptions(options); + this.container = document.id(container); + + this.bound = { + 'window': { + resize: this.resize.bind(this), + scroll: this.scroll.bind(this) + }, + overlayClick: this.overlayClick.bind(this), + tweenStart: this.tweenStart.bind(this), + tweenComplete: this.tweenComplete.bind(this) + }; + + this.build().attach(); + }, + + build: function(){ + this.overlay = new Element('div', { + id: this.options.id, + styles: { + position: (Browser.ie6) ? 'absolute' : 'fixed', + background: this.options.color, + left: 0, + top: 0, + 'z-index': this.options.zIndex, + opacity: 0 + } + }).inject(this.container); + this.tween = new Fx.Tween(this.overlay, { + duration: this.options.duration, + link: 'cancel', + property: 'opacity' + }); + return this; + }.protect(), + + attach: function(){ + window.addEvents(this.bound.window); + this.overlay.addEvent('click', this.bound.overlayClick); + this.tween.addEvents({ + onStart: this.bound.tweenStart, + onComplete: this.bound.tweenComplete + }); + return this; + }, + + detach: function(){ + var args = Array.prototype.slice.call(arguments); + args.each(function(item){ + if(item == 'window') window.removeEvents(this.bound.window); + if(item == 'overlay') this.overlay.removeEvent('click', this.bound.overlayClick); + }, this); + return this; + }, + + overlayClick: function(){ + this.fireEvent('click'); + return this; + }, + + tweenStart: function(){ + this.overlay.setStyles({ + width: '100%', + height: this.container.getScrollSize().y, + visibility: 'visible' + }); + return this; + }, + + tweenComplete: function(){ + var event = this.overlay.getStyle('opacity') == this.options.opacity ? 'show' : 'hide'; + if (event == 'hide') this.overlay.setStyle('visibility', 'hidden'); + return this; + }, + + open: function(){ + this.fireEvent('open'); + this.tween.start(this.options.opacity); + return this; + }, + + close: function(){ + this.fireEvent('close'); + this.tween.start(0); + return this; + }, + + resize: function(){ + this.fireEvent('resize'); + this.overlay.setStyle('height', this.container.getScrollSize().y); + return this; + }, + + scroll: function(){ + this.fireEvent('scroll'); + if (Browser.ie6) this.overlay.setStyle('left', window.getScroll().x); + return this; + } + +}); diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/MooDialog.css b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/MooDialog.css new file mode 100644 index 000000000..c88773ae9 --- /dev/null +++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/MooDialog.css @@ -0,0 +1,95 @@ +/* Created by Arian Stolwijk <http://www.aryweb.nl> */ + +.MooDialog { + position: fixed; + width: 300px; + height: 100px; + top: 50%; + left: 50%; + margin: -150px 0 0 -150px; + padding: 10px; + z-index: 50000; + + background: #eef5f8; + color: black; + border-radius: 7px; + -moz-border-radius: 7px; + -webkit-border-radius: 7px; + border-radius: 7px; + -moz-box-shadow: 1px 1px 5px rgba(0,0,0,0.8); + -webkit-box-shadow: 1px 1px 5px rgba(0,0,0,0.8); + box-shadow: 1px 1px 5px rgba(0,0,0,0.8); +} + +.MooDialogTitle { + padding-top: 30px; +} + +.MooDialog .content { + height: 100px; +} + +.MooDialog .title { + position: absolute; + top: 0; + left: 0; + right: 0; + padding: 3px 20px; + + background: #b7c4dc; + border-bottom: 1px solid #a1aec5; + font-weight: bold; + text-shadow: 1px 1px 0 #fff; + color: black; + border-radius: 7px; + -moz-border-radius: 7px; + -webkit-border-radius: 7px; +} + +.MooDialog .close { + position: absolute; + width: 16px; + height: 16px; + top: -5px; + left: -5px; + + background: url(dialog-close.png) no-repeat; + display: block; + cursor: pointer; +} + +.MooDialog .buttons { + margin: 0; + padding: 0; + border: 0; + background: none; + text-align: right; +} + +.MooDialog .iframe { + width: 100%; + height: 100%; +} + +.MooDialog .textInput { + width: 200px; + float: left; +} + +.MooDialog .MooDialogAlert, +.MooDialog .MooDialogConfirm, +.MooDialog .MooDialogPrompt, +.MooDialog .MooDialogError { + padding-left: 40px; + min-height: 40px; + background: url(dialog-warning.png) no-repeat; +} + +.MooDialog .MooDialogConfirm, +.MooDialog .MooDialogPrompt { + background: url(dialog-question.png) no-repeat; +} + +.MooDialog .MooDialogError { + background: url(dialog-error.png) no-repeat; +} diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-close.png b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-close.png Binary files differnew file mode 100644 index 000000000..81ebb88b2 --- /dev/null +++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-close.png diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-error.png b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-error.png Binary files differnew file mode 100644 index 000000000..d70328403 --- /dev/null +++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-error.png diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-question.png b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-question.png Binary files differnew file mode 100644 index 000000000..b0af3db5b --- /dev/null +++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-question.png diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-warning.png b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-warning.png Binary files differnew file mode 100644 index 000000000..aad64d4be --- /dev/null +++ b/pyload/webui/themes/Flat/lib/MooTools/MooDialog/css/dialog-warning.png diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDropMenu/MooDropMenu.js b/pyload/webui/themes/Flat/lib/MooTools/MooDropMenu/MooDropMenu.js new file mode 100644 index 000000000..ac0fa1874 --- /dev/null +++ b/pyload/webui/themes/Flat/lib/MooTools/MooDropMenu/MooDropMenu.js @@ -0,0 +1,86 @@ +/* +--- +description: This provides a simple Drop Down menu with infinit levels + +license: MIT-style + +authors: +- Arian Stolwijk + +requires: + - Core/Class.Extras + - Core/Element.Event + - Core/Selectors + +provides: [MooDropMenu, Element.MooDropMenu] + +... +*/ + +var MooDropMenu = new Class({ + + Implements: [Options, Events], + + options: { + onOpen: function(el){ + el.removeClass('close').addClass('open'); + }, + onClose: function(el){ + el.removeClass('open').addClass('close'); + }, + onInitialize: function(el){ + el.removeClass('open').addClass('close'); + }, + mouseoutDelay: 200, + mouseoverDelay: 0, + listSelector: 'ul', + itemSelector: 'li', + openEvent: 'mouseenter', + closeEvent: 'mouseleave' + }, + + initialize: function(menu, options, level){ + this.setOptions(options); + options = this.options; + + var menu = this.menu = document.id(menu); + + menu.getElements(options.itemSelector + ' > ' + options.listSelector).each(function(el){ + + this.fireEvent('initialize', el); + + var parent = el.getParent(options.itemSelector), + timer; + + parent.addEvent(options.openEvent, function(){ + parent.store('DropDownOpen', true); + + clearTimeout(timer); + if (options.mouseoverDelay) timer = this.fireEvent.delay(options.mouseoverDelay, this, ['open', el]); + else this.fireEvent('open', el); + + }.bind(this)).addEvent(options.closeEvent, function(){ + parent.store('DropDownOpen', false); + + clearTimeout(timer); + timer = (function(){ + if (!parent.retrieve('DropDownOpen')) this.fireEvent('close', el); + }).delay(options.mouseoutDelay, this); + + }.bind(this)); + + }, this); + }, + + toElement: function(){ + return this.menu + } + +}); + +/* So you can do like this $('nav').MooDropMenu(); or even $('nav').MooDropMenu().setStyle('border',1); */ +Element.implement({ + MooDropMenu: function(options){ + return this.store('MooDropMenu', new MooDropMenu(this, options)); + } +}); diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooDropMenu/css/MooDropMenu.css b/pyload/webui/themes/Flat/lib/MooTools/MooDropMenu/css/MooDropMenu.css new file mode 100644 index 000000000..e08c2f9fa --- /dev/null +++ b/pyload/webui/themes/Flat/lib/MooTools/MooDropMenu/css/MooDropMenu.css @@ -0,0 +1,66 @@ +#nav { + margin: 0; + padding: 0; + list-style: none; +} + +#nav > li { + background: #F5F5F5; + border-bottom: 1px solid #aaa; +} + +#nav li { + position: relative; + float: left; + padding: 5px; +} + + +#nav ul { + position: absolute; + top: 26px; + left: 10px; + margin: 0; + padding: 0; + list-style: none; + width: 136px; + border: 1px solid #AAA; + background: #f1f1f1; + -webkit-box-shadow: 1px 1px 5px #AAA; + -moz-box-shadow: 1px 1px 5px #AAA; + box-shadow: 1px 1px 5px #AAA; +} + + +#nav .open { + display: block; +} + +#nav .close { + display: none; +} + +#nav ul li { + float: none; + padding: 0; +} + +#nav ul li a { + width: 130px; + _width: 127px; + background: #f1f1f1; + padding: 3px; + display: block; + _float: left; + font-weight: normal; +} + +#nav ul li a:hover { + background: #CDCDCD; +} + +#nav ul ul { + left: 137px; + _left: 0; + top: 0; +} diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooTools-Core.js b/pyload/webui/themes/Flat/lib/MooTools/MooTools-Core.js new file mode 100644 index 000000000..e0bea6df6 --- /dev/null +++ b/pyload/webui/themes/Flat/lib/MooTools/MooTools-Core.js @@ -0,0 +1,6068 @@ +/* MooTools: the javascript framework. license: MIT-style license. copyright: Copyright (c) 2006-2015 [Valerio Proietti](http://mad4milk.net/).*/ +/* +Web Build: http://mootools.net/core/builder/e426a9ae7167c5807b173d5deff673fc +*/ +/* +--- + +name: Core + +description: The heart of MooTools. + +license: MIT-style license. + +copyright: Copyright (c) 2006-2014 [Valerio Proietti](http://mad4milk.net/). + +authors: The MooTools production team (http://mootools.net/developers/) + +inspiration: + - Class implementation inspired by [Base.js](http://dean.edwards.name/weblog/2006/03/base/) Copyright (c) 2006 Dean Edwards, [GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php) + - Some functionality inspired by [Prototype.js](http://prototypejs.org) Copyright (c) 2005-2007 Sam Stephenson, [MIT License](http://opensource.org/licenses/mit-license.php) + +provides: [Core, MooTools, Type, typeOf, instanceOf, Native] + +... +*/ +/*! MooTools: the javascript framework. license: MIT-style license. copyright: Copyright (c) 2006-2014 [Valerio Proietti](http://mad4milk.net/).*/ +(function(){ + +this.MooTools = { + version: '1.5.1', + build: '0542c135fdeb7feed7d9917e01447a408f22c876' +}; + +// typeOf, instanceOf + +var typeOf = this.typeOf = function(item){ + if (item == null) return 'null'; + if (item.$family != null) return item.$family(); + + if (item.nodeName){ + if (item.nodeType == 1) return 'element'; + if (item.nodeType == 3) return (/\S/).test(item.nodeValue) ? 'textnode' : 'whitespace'; + } else if (typeof item.length == 'number'){ + if ('callee' in item) return 'arguments'; + if ('item' in item) return 'collection'; + } + + return typeof item; +}; + +var instanceOf = this.instanceOf = function(item, object){ + if (item == null) return false; + var constructor = item.$constructor || item.constructor; + while (constructor){ + if (constructor === object) return true; + constructor = constructor.parent; + } + /*<ltIE8>*/ + if (!item.hasOwnProperty) return false; + /*</ltIE8>*/ + return item instanceof object; +}; + +// Function overloading + +var Function = this.Function; + +var enumerables = true; +for (var i in {toString: 1}) enumerables = null; +if (enumerables) enumerables = ['hasOwnProperty', 'valueOf', 'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString', 'toString', 'constructor']; + +Function.prototype.overloadSetter = function(usePlural){ + var self = this; + return function(a, b){ + if (a == null) return this; + if (usePlural || typeof a != 'string'){ + for (var k in a) self.call(this, k, a[k]); + if (enumerables) for (var i = enumerables.length; i--;){ + k = enumerables[i]; + if (a.hasOwnProperty(k)) self.call(this, k, a[k]); + } + } else { + self.call(this, a, b); + } + return this; + }; +}; + +Function.prototype.overloadGetter = function(usePlural){ + var self = this; + return function(a){ + var args, result; + if (typeof a != 'string') args = a; + else if (arguments.length > 1) args = arguments; + else if (usePlural) args = [a]; + if (args){ + result = {}; + for (var i = 0; i < args.length; i++) result[args[i]] = self.call(this, args[i]); + } else { + result = self.call(this, a); + } + return result; + }; +}; + +Function.prototype.extend = function(key, value){ + this[key] = value; +}.overloadSetter(); + +Function.prototype.implement = function(key, value){ + this.prototype[key] = value; +}.overloadSetter(); + +// From + +var slice = Array.prototype.slice; + +Function.from = function(item){ + return (typeOf(item) == 'function') ? item : function(){ + return item; + }; +}; + +Array.from = function(item){ + if (item == null) return []; + return (Type.isEnumerable(item) && typeof item != 'string') ? (typeOf(item) == 'array') ? item : slice.call(item) : [item]; +}; + +Number.from = function(item){ + var number = parseFloat(item); + return isFinite(number) ? number : null; +}; + +String.from = function(item){ + return item + ''; +}; + +// hide, protect + +Function.implement({ + + hide: function(){ + this.$hidden = true; + return this; + }, + + protect: function(){ + this.$protected = true; + return this; + } + +}); + +// Type + +var Type = this.Type = function(name, object){ + if (name){ + var lower = name.toLowerCase(); + var typeCheck = function(item){ + return (typeOf(item) == lower); + }; + + Type['is' + name] = typeCheck; + if (object != null){ + object.prototype.$family = (function(){ + return lower; + }).hide(); + + } + } + + if (object == null) return null; + + object.extend(this); + object.$constructor = Type; + object.prototype.$constructor = object; + + return object; +}; + +var toString = Object.prototype.toString; + +Type.isEnumerable = function(item){ + return (item != null && typeof item.length == 'number' && toString.call(item) != '[object Function]' ); +}; + +var hooks = {}; + +var hooksOf = function(object){ + var type = typeOf(object.prototype); + return hooks[type] || (hooks[type] = []); +}; + +var implement = function(name, method){ + if (method && method.$hidden) return; + + var hooks = hooksOf(this); + + for (var i = 0; i < hooks.length; i++){ + var hook = hooks[i]; + if (typeOf(hook) == 'type') implement.call(hook, name, method); + else hook.call(this, name, method); + } + + var previous = this.prototype[name]; + if (previous == null || !previous.$protected) this.prototype[name] = method; + + if (this[name] == null && typeOf(method) == 'function') extend.call(this, name, function(item){ + return method.apply(item, slice.call(arguments, 1)); + }); +}; + +var extend = function(name, method){ + if (method && method.$hidden) return; + var previous = this[name]; + if (previous == null || !previous.$protected) this[name] = method; +}; + +Type.implement({ + + implement: implement.overloadSetter(), + + extend: extend.overloadSetter(), + + alias: function(name, existing){ + implement.call(this, name, this.prototype[existing]); + }.overloadSetter(), + + mirror: function(hook){ + hooksOf(this).push(hook); + return this; + } + +}); + +new Type('Type', Type); + +// Default Types + +var force = function(name, object, methods){ + var isType = (object != Object), + prototype = object.prototype; + + if (isType) object = new Type(name, object); + + for (var i = 0, l = methods.length; i < l; i++){ + var key = methods[i], + generic = object[key], + proto = prototype[key]; + + if (generic) generic.protect(); + if (isType && proto) object.implement(key, proto.protect()); + } + + if (isType){ + var methodsEnumerable = prototype.propertyIsEnumerable(methods[0]); + object.forEachMethod = function(fn){ + if (!methodsEnumerable) for (var i = 0, l = methods.length; i < l; i++){ + fn.call(prototype, prototype[methods[i]], methods[i]); + } + for (var key in prototype) fn.call(prototype, prototype[key], key); + }; + } + + return force; +}; + +force('String', String, [ + 'charAt', 'charCodeAt', 'concat', 'contains', 'indexOf', 'lastIndexOf', 'match', 'quote', 'replace', 'search', + 'slice', 'split', 'substr', 'substring', 'trim', 'toLowerCase', 'toUpperCase' +])('Array', Array, [ + 'pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift', 'concat', 'join', 'slice', + 'indexOf', 'lastIndexOf', 'filter', 'forEach', 'every', 'map', 'some', 'reduce', 'reduceRight' +])('Number', Number, [ + 'toExponential', 'toFixed', 'toLocaleString', 'toPrecision' +])('Function', Function, [ + 'apply', 'call', 'bind' +])('RegExp', RegExp, [ + 'exec', 'test' +])('Object', Object, [ + 'create', 'defineProperty', 'defineProperties', 'keys', + 'getPrototypeOf', 'getOwnPropertyDescriptor', 'getOwnPropertyNames', + 'preventExtensions', 'isExtensible', 'seal', 'isSealed', 'freeze', 'isFrozen' +])('Date', Date, ['now']); + +Object.extend = extend.overloadSetter(); + +Date.extend('now', function(){ + return +(new Date); +}); + +new Type('Boolean', Boolean); + +// fixes NaN returning as Number + +Number.prototype.$family = function(){ + return isFinite(this) ? 'number' : 'null'; +}.hide(); + +// Number.random + +Number.extend('random', function(min, max){ + return Math.floor(Math.random() * (max - min + 1) + min); +}); + +// forEach, each + +var hasOwnProperty = Object.prototype.hasOwnProperty; +Object.extend('forEach', function(object, fn, bind){ + for (var key in object){ + if (hasOwnProperty.call(object, key)) fn.call(bind, object[key], key, object); + } +}); + +Object.each = Object.forEach; + +Array.implement({ + + /*<!ES5>*/ + forEach: function(fn, bind){ + for (var i = 0, l = this.length; i < l; i++){ + if (i in this) fn.call(bind, this[i], i, this); + } + }, + /*</!ES5>*/ + + each: function(fn, bind){ + Array.forEach(this, fn, bind); + return this; + } + +}); + +// Array & Object cloning, Object merging and appending + +var cloneOf = function(item){ + switch (typeOf(item)){ + case 'array': return item.clone(); + case 'object': return Object.clone(item); + default: return item; + } +}; + +Array.implement('clone', function(){ + var i = this.length, clone = new Array(i); + while (i--) clone[i] = cloneOf(this[i]); + return clone; +}); + +var mergeOne = function(source, key, current){ + switch (typeOf(current)){ + case 'object': + if (typeOf(source[key]) == 'object') Object.merge(source[key], current); + else source[key] = Object.clone(current); + break; + case 'array': source[key] = current.clone(); break; + default: source[key] = current; + } + return source; +}; + +Object.extend({ + + merge: function(source, k, v){ + if (typeOf(k) == 'string') return mergeOne(source, k, v); + for (var i = 1, l = arguments.length; i < l; i++){ + var object = arguments[i]; + for (var key in object) mergeOne(source, key, object[key]); + } + return source; + }, + + clone: function(object){ + var clone = {}; + for (var key in object) clone[key] = cloneOf(object[key]); + return clone; + }, + + append: function(original){ + for (var i = 1, l = arguments.length; i < l; i++){ + var extended = arguments[i] || {}; + for (var key in extended) original[key] = extended[key]; + } + return original; + } + +}); + +// Object-less types + +['Object', 'WhiteSpace', 'TextNode', 'Collection', 'Arguments'].each(function(name){ + new Type(name); +}); + +// Unique ID + +var UID = Date.now(); + +String.extend('uniqueID', function(){ + return (UID++).toString(36); +}); + + + +})(); + +/* +--- + +name: Array + +description: Contains Array Prototypes like each, contains, and erase. + +license: MIT-style license. + +requires: [Type] + +provides: Array + +... +*/ + +Array.implement({ + + /*<!ES5>*/ + every: function(fn, bind){ + for (var i = 0, l = this.length >>> 0; i < l; i++){ + if ((i in this) && !fn.call(bind, this[i], i, this)) return false; + } + return true; + }, + + filter: function(fn, bind){ + var results = []; + for (var value, i = 0, l = this.length >>> 0; i < l; i++) if (i in this){ + value = this[i]; + if (fn.call(bind, value, i, this)) results.push(value); + } + return results; + }, + + indexOf: function(item, from){ + var length = this.length >>> 0; + for (var i = (from < 0) ? Math.max(0, length + from) : from || 0; i < length; i++){ + if (this[i] === item) return i; + } + return -1; + }, + + map: function(fn, bind){ + var length = this.length >>> 0, results = Array(length); + for (var i = 0; i < length; i++){ + if (i in this) results[i] = fn.call(bind, this[i], i, this); + } + return results; + }, + + some: function(fn, bind){ + for (var i = 0, l = this.length >>> 0; i < l; i++){ + if ((i in this) && fn.call(bind, this[i], i, this)) return true; + } + return false; + }, + /*</!ES5>*/ + + clean: function(){ + return this.filter(function(item){ + return item != null; + }); + }, + + invoke: function(methodName){ + var args = Array.slice(arguments, 1); + return this.map(function(item){ + return item[methodName].apply(item, args); + }); + }, + + associate: function(keys){ + var obj = {}, length = Math.min(this.length, keys.length); + for (var i = 0; i < length; i++) obj[keys[i]] = this[i]; + return obj; + }, + + link: function(object){ + var result = {}; + for (var i = 0, l = this.length; i < l; i++){ + for (var key in object){ + if (object[key](this[i])){ + result[key] = this[i]; + delete object[key]; + break; + } + } + } + return result; + }, + + contains: function(item, from){ + return this.indexOf(item, from) != -1; + }, + + append: function(array){ + this.push.apply(this, array); + return this; + }, + + getLast: function(){ + return (this.length) ? this[this.length - 1] : null; + }, + + getRandom: function(){ + return (this.length) ? this[Number.random(0, this.length - 1)] : null; + }, + + include: function(item){ + if (!this.contains(item)) this.push(item); + return this; + }, + + combine: function(array){ + for (var i = 0, l = array.length; i < l; i++) this.include(array[i]); + return this; + }, + + erase: function(item){ + for (var i = this.length; i--;){ + if (this[i] === item) this.splice(i, 1); + } + return this; + }, + + empty: function(){ + this.length = 0; + return this; + }, + + flatten: function(){ + var array = []; + for (var i = 0, l = this.length; i < l; i++){ + var type = typeOf(this[i]); + if (type == 'null') continue; + array = array.concat((type == 'array' || type == 'collection' || type == 'arguments' || instanceOf(this[i], Array)) ? Array.flatten(this[i]) : this[i]); + } + return array; + }, + + pick: function(){ + for (var i = 0, l = this.length; i < l; i++){ + if (this[i] != null) return this[i]; + } + return null; + }, + + hexToRgb: function(array){ + if (this.length != 3) return null; + var rgb = this.map(function(value){ + if (value.length == 1) value += value; + return parseInt(value, 16); + }); + return (array) ? rgb : 'rgb(' + rgb + ')'; + }, + + rgbToHex: function(array){ + if (this.length < 3) return null; + if (this.length == 4 && this[3] == 0 && !array) return 'transparent'; + var hex = []; + for (var i = 0; i < 3; i++){ + var bit = (this[i] - 0).toString(16); + hex.push((bit.length == 1) ? '0' + bit : bit); + } + return (array) ? hex : '#' + hex.join(''); + } + +}); + + + +/* +--- + +name: Function + +description: Contains Function Prototypes like create, bind, pass, and delay. + +license: MIT-style license. + +requires: Type + +provides: Function + +... +*/ + +Function.extend({ + + attempt: function(){ + for (var i = 0, l = arguments.length; i < l; i++){ + try { + return arguments[i](); + } catch (e){} + } + return null; + } + +}); + +Function.implement({ + + attempt: function(args, bind){ + try { + return this.apply(bind, Array.from(args)); + } catch (e){} + + return null; + }, + + /*<!ES5-bind>*/ + bind: function(that){ + var self = this, + args = arguments.length > 1 ? Array.slice(arguments, 1) : null, + F = function(){}; + + var bound = function(){ + var context = that, length = arguments.length; + if (this instanceof bound){ + F.prototype = self.prototype; + context = new F; + } + var result = (!args && !length) + ? self.call(context) + : self.apply(context, args && length ? args.concat(Array.slice(arguments)) : args || arguments); + return context == that ? result : context; + }; + return bound; + }, + /*</!ES5-bind>*/ + + pass: function(args, bind){ + var self = this; + if (args != null) args = Array.from(args); + return function(){ + return self.apply(bind, args || arguments); + }; + }, + + delay: function(delay, bind, args){ + return setTimeout(this.pass((args == null ? [] : args), bind), delay); + }, + + periodical: function(periodical, bind, args){ + return setInterval(this.pass((args == null ? [] : args), bind), periodical); + } + +}); + + + +/* +--- + +name: Number + +description: Contains Number Prototypes like limit, round, times, and ceil. + +license: MIT-style license. + +requires: Type + +provides: Number + +... +*/ + +Number.implement({ + + limit: function(min, max){ + return Math.min(max, Math.max(min, this)); + }, + + round: function(precision){ + precision = Math.pow(10, precision || 0).toFixed(precision < 0 ? -precision : 0); + return Math.round(this * precision) / precision; + }, + + times: function(fn, bind){ + for (var i = 0; i < this; i++) fn.call(bind, i, this); + }, + + toFloat: function(){ + return parseFloat(this); + }, + + toInt: function(base){ + return parseInt(this, base || 10); + } + +}); + +Number.alias('each', 'times'); + +(function(math){ + var methods = {}; + math.each(function(name){ + if (!Number[name]) methods[name] = function(){ + return Math[name].apply(null, [this].concat(Array.from(arguments))); + }; + }); + Number.implement(methods); +})(['abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', 'log', 'max', 'min', 'pow', 'sin', 'sqrt', 'tan']); + +/* +--- + +name: String + +description: Contains String Prototypes like camelCase, capitalize, test, and toInt. + +license: MIT-style license. + +requires: [Type, Array] + +provides: String + +... +*/ + +String.implement({ + + //<!ES6> + contains: function(string, index){ + return (index ? String(this).slice(index) : String(this)).indexOf(string) > -1; + }, + //</!ES6> + + test: function(regex, params){ + return ((typeOf(regex) == 'regexp') ? regex : new RegExp('' + regex, params)).test(this); + }, + + trim: function(){ + return String(this).replace(/^\s+|\s+$/g, ''); + }, + + clean: function(){ + return String(this).replace(/\s+/g, ' ').trim(); + }, + + camelCase: function(){ + return String(this).replace(/-\D/g, function(match){ + return match.charAt(1).toUpperCase(); + }); + }, + + hyphenate: function(){ + return String(this).replace(/[A-Z]/g, function(match){ + return ('-' + match.charAt(0).toLowerCase()); + }); + }, + + capitalize: function(){ + return String(this).replace(/\b[a-z]/g, function(match){ + return match.toUpperCase(); + }); + }, + + escapeRegExp: function(){ + return String(this).replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1'); + }, + + toInt: function(base){ + return parseInt(this, base || 10); + }, + + toFloat: function(){ + return parseFloat(this); + }, + + hexToRgb: function(array){ + var hex = String(this).match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/); + return (hex) ? hex.slice(1).hexToRgb(array) : null; + }, + + rgbToHex: function(array){ + var rgb = String(this).match(/\d{1,3}/g); + return (rgb) ? rgb.rgbToHex(array) : null; + }, + + substitute: function(object, regexp){ + return String(this).replace(regexp || (/\\?\{([^{}]+)\}/g), function(match, name){ + if (match.charAt(0) == '\\') return match.slice(1); + return (object[name] != null) ? object[name] : ''; + }); + } + +}); + + + +/* +--- + +name: Browser + +description: The Browser Object. Contains Browser initialization, Window and Document, and the Browser Hash. + +license: MIT-style license. + +requires: [Array, Function, Number, String] + +provides: [Browser, Window, Document] + +... +*/ + +(function(){ + +var document = this.document; +var window = document.window = this; + +var parse = function(ua, platform){ + ua = ua.toLowerCase(); + platform = (platform ? platform.toLowerCase() : ''); + + var UA = ua.match(/(opera|ie|firefox|chrome|trident|crios|version)[\s\/:]([\w\d\.]+)?.*?(safari|(?:rv[\s\/:]|version[\s\/:])([\w\d\.]+)|$)/) || [null, 'unknown', 0]; + + if (UA[1] == 'trident'){ + UA[1] = 'ie'; + if (UA[4]) UA[2] = UA[4]; + } else if (UA[1] == 'crios'){ + UA[1] = 'chrome'; + } + + platform = ua.match(/ip(?:ad|od|hone)/) ? 'ios' : (ua.match(/(?:webos|android)/) || platform.match(/mac|win|linux/) || ['other'])[0]; + if (platform == 'win') platform = 'windows'; + + return { + extend: Function.prototype.extend, + name: (UA[1] == 'version') ? UA[3] : UA[1], + version: parseFloat((UA[1] == 'opera' && UA[4]) ? UA[4] : UA[2]), + platform: platform + }; +}; + +var Browser = this.Browser = parse(navigator.userAgent, navigator.platform); + +if (Browser.name == 'ie'){ + Browser.version = document.documentMode; +} + +Browser.extend({ + Features: { + xpath: !!(document.evaluate), + air: !!(window.runtime), + query: !!(document.querySelector), + json: !!(window.JSON) + }, + parseUA: parse +}); + + + +// Request + +Browser.Request = (function(){ + + var XMLHTTP = function(){ + return new XMLHttpRequest(); + }; + + var MSXML2 = function(){ + return new ActiveXObject('MSXML2.XMLHTTP'); + }; + + var MSXML = function(){ + return new ActiveXObject('Microsoft.XMLHTTP'); + }; + + return Function.attempt(function(){ + XMLHTTP(); + return XMLHTTP; + }, function(){ + MSXML2(); + return MSXML2; + }, function(){ + MSXML(); + return MSXML; + }); + +})(); + +Browser.Features.xhr = !!(Browser.Request); + + + +// String scripts + +Browser.exec = function(text){ + if (!text) return text; + if (window.execScript){ + window.execScript(text); + } else { + var script = document.createElement('script'); + script.setAttribute('type', 'text/javascript'); + script.text = text; + document.head.appendChild(script); + document.head.removeChild(script); + } + return text; +}; + +String.implement('stripScripts', function(exec){ + var scripts = ''; + var text = this.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi, function(all, code){ + scripts += code + '\n'; + return ''; + }); + if (exec === true) Browser.exec(scripts); + else if (typeOf(exec) == 'function') exec(scripts, text); + return text; +}); + +// Window, Document + +Browser.extend({ + Document: this.Document, + Window: this.Window, + Element: this.Element, + Event: this.Event +}); + +this.Window = this.$constructor = new Type('Window', function(){}); + +this.$family = Function.from('window').hide(); + +Window.mirror(function(name, method){ + window[name] = method; +}); + +this.Document = document.$constructor = new Type('Document', function(){}); + +document.$family = Function.from('document').hide(); + +Document.mirror(function(name, method){ + document[name] = method; +}); + +document.html = document.documentElement; +if (!document.head) document.head = document.getElementsByTagName('head')[0]; + +if (document.execCommand) try { + document.execCommand("BackgroundImageCache", false, true); +} catch (e){} + +/*<ltIE9>*/ +if (this.attachEvent && !this.addEventListener){ + var unloadEvent = function(){ + this.detachEvent('onunload', unloadEvent); + document.head = document.html = document.window = null; + window = this.Window = document = null; + }; + this.attachEvent('onunload', unloadEvent); +} + +// IE fails on collections and <select>.options (refers to <select>) +var arrayFrom = Array.from; +try { + arrayFrom(document.html.childNodes); +} catch(e){ + Array.from = function(item){ + if (typeof item != 'string' && Type.isEnumerable(item) && typeOf(item) != 'array'){ + var i = item.length, array = new Array(i); + while (i--) array[i] = item[i]; + return array; + } + return arrayFrom(item); + }; + + var prototype = Array.prototype, + slice = prototype.slice; + ['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift', 'concat', 'join', 'slice'].each(function(name){ + var method = prototype[name]; + Array[name] = function(item){ + return method.apply(Array.from(item), slice.call(arguments, 1)); + }; + }); +} +/*</ltIE9>*/ + + + +})(); + +/* +--- + +name: Class + +description: Contains the Class Function for easily creating, extending, and implementing reusable Classes. + +license: MIT-style license. + +requires: [Array, String, Function, Number] + +provides: Class + +... +*/ + +(function(){ + +var Class = this.Class = new Type('Class', function(params){ + if (instanceOf(params, Function)) params = {initialize: params}; + + var newClass = function(){ + reset(this); + if (newClass.$prototyping) return this; + this.$caller = null; + var value = (this.initialize) ? this.initialize.apply(this, arguments) : this; + this.$caller = this.caller = null; + return value; + }.extend(this).implement(params); + + newClass.$constructor = Class; + newClass.prototype.$constructor = newClass; + newClass.prototype.parent = parent; + + return newClass; +}); + +var parent = function(){ + if (!this.$caller) throw new Error('The method "parent" cannot be called.'); + var name = this.$caller.$name, + parent = this.$caller.$owner.parent, + previous = (parent) ? parent.prototype[name] : null; + if (!previous) throw new Error('The method "' + name + '" has no parent.'); + return previous.apply(this, arguments); +}; + +var reset = function(object){ + for (var key in object){ + var value = object[key]; + switch (typeOf(value)){ + case 'object': + var F = function(){}; + F.prototype = value; + object[key] = reset(new F); + break; + case 'array': object[key] = value.clone(); break; + } + } + return object; +}; + +var wrap = function(self, key, method){ + if (method.$origin) method = method.$origin; + var wrapper = function(){ + if (method.$protected && this.$caller == null) throw new Error('The method "' + key + '" cannot be called.'); + var caller = this.caller, current = this.$caller; + this.caller = current; this.$caller = wrapper; + var result = method.apply(this, arguments); + this.$caller = current; this.caller = caller; + return result; + }.extend({$owner: self, $origin: method, $name: key}); + return wrapper; +}; + +var implement = function(key, value, retain){ + if (Class.Mutators.hasOwnProperty(key)){ + value = Class.Mutators[key].call(this, value); + if (value == null) return this; + } + + if (typeOf(value) == 'function'){ + if (value.$hidden) return this; + this.prototype[key] = (retain) ? value : wrap(this, key, value); + } else { + Object.merge(this.prototype, key, value); + } + + return this; +}; + +var getInstance = function(klass){ + klass.$prototyping = true; + var proto = new klass; + delete klass.$prototyping; + return proto; +}; + +Class.implement('implement', implement.overloadSetter()); + +Class.Mutators = { + + Extends: function(parent){ + this.parent = parent; + this.prototype = getInstance(parent); + }, + + Implements: function(items){ + Array.from(items).each(function(item){ + var instance = new item; + for (var key in instance) implement.call(this, key, instance[key], true); + }, this); + } +}; + +})(); + +/* +--- + +name: Class.Extras + +description: Contains Utility Classes that can be implemented into your own Classes to ease the execution of many common tasks. + +license: MIT-style license. + +requires: Class + +provides: [Class.Extras, Chain, Events, Options] + +... +*/ + +(function(){ + +this.Chain = new Class({ + + $chain: [], + + chain: function(){ + this.$chain.append(Array.flatten(arguments)); + return this; + }, + + callChain: function(){ + return (this.$chain.length) ? this.$chain.shift().apply(this, arguments) : false; + }, + + clearChain: function(){ + this.$chain.empty(); + return this; + } + +}); + +var removeOn = function(string){ + return string.replace(/^on([A-Z])/, function(full, first){ + return first.toLowerCase(); + }); +}; + +this.Events = new Class({ + + $events: {}, + + addEvent: function(type, fn, internal){ + type = removeOn(type); + + + + this.$events[type] = (this.$events[type] || []).include(fn); + if (internal) fn.internal = true; + return this; + }, + + addEvents: function(events){ + for (var type in events) this.addEvent(type, events[type]); + return this; + }, + + fireEvent: function(type, args, delay){ + type = removeOn(type); + var events = this.$events[type]; + if (!events) return this; + args = Array.from(args); + events.each(function(fn){ + if (delay) fn.delay(delay, this, args); + else fn.apply(this, args); + }, this); + return this; + }, + + removeEvent: function(type, fn){ + type = removeOn(type); + var events = this.$events[type]; + if (events && !fn.internal){ + var index = events.indexOf(fn); + if (index != -1) delete events[index]; + } + return this; + }, + + removeEvents: function(events){ + var type; + if (typeOf(events) == 'object'){ + for (type in events) this.removeEvent(type, events[type]); + return this; + } + if (events) events = removeOn(events); + for (type in this.$events){ + if (events && events != type) continue; + var fns = this.$events[type]; + for (var i = fns.length; i--;) if (i in fns){ + this.removeEvent(type, fns[i]); + } + } + return this; + } + +}); + +this.Options = new Class({ + + setOptions: function(){ + var options = this.options = Object.merge.apply(null, [{}, this.options].append(arguments)); + if (this.addEvent) for (var option in options){ + if (typeOf(options[option]) != 'function' || !(/^on[A-Z]/).test(option)) continue; + this.addEvent(option, options[option]); + delete options[option]; + } + return this; + } + +}); + +})(); + +/* +--- + +name: Object + +description: Object generic methods + +license: MIT-style license. + +requires: Type + +provides: [Object, Hash] + +... +*/ + +(function(){ + +var hasOwnProperty = Object.prototype.hasOwnProperty; + +Object.extend({ + + subset: function(object, keys){ + var results = {}; + for (var i = 0, l = keys.length; i < l; i++){ + var k = keys[i]; + if (k in object) results[k] = object[k]; + } + return results; + }, + + map: function(object, fn, bind){ + var results = {}; + for (var key in object){ + if (hasOwnProperty.call(object, key)) results[key] = fn.call(bind, object[key], key, object); + } + return results; + }, + + filter: function(object, fn, bind){ + var results = {}; + for (var key in object){ + var value = object[key]; + if (hasOwnProperty.call(object, key) && fn.call(bind, value, key, object)) results[key] = value; + } + return results; + }, + + every: function(object, fn, bind){ + for (var key in object){ + if (hasOwnProperty.call(object, key) && !fn.call(bind, object[key], key)) return false; + } + return true; + }, + + some: function(object, fn, bind){ + for (var key in object){ + if (hasOwnProperty.call(object, key) && fn.call(bind, object[key], key)) return true; + } + return false; + }, + + keys: function(object){ + var keys = []; + for (var key in object){ + if (hasOwnProperty.call(object, key)) keys.push(key); + } + return keys; + }, + + values: function(object){ + var values = []; + for (var key in object){ + if (hasOwnProperty.call(object, key)) values.push(object[key]); + } + return values; + }, + + getLength: function(object){ + return Object.keys(object).length; + }, + + keyOf: function(object, value){ + for (var key in object){ + if (hasOwnProperty.call(object, key) && object[key] === value) return key; + } + return null; + }, + + contains: function(object, value){ + return Object.keyOf(object, value) != null; + }, + + toQueryString: function(object, base){ + var queryString = []; + + Object.each(object, function(value, key){ + if (base) key = base + '[' + key + ']'; + var result; + switch (typeOf(value)){ + case 'object': result = Object.toQueryString(value, key); break; + case 'array': + var qs = {}; + value.each(function(val, i){ + qs[i] = val; + }); + result = Object.toQueryString(qs, key); + break; + default: result = key + '=' + encodeURIComponent(value); + } + if (value != null) queryString.push(result); + }); + + return queryString.join('&'); + } + +}); + +})(); + + + +/* +--- +name: Slick.Parser +description: Standalone CSS3 Selector parser +provides: Slick.Parser +... +*/ + +;(function(){ + +var parsed, + separatorIndex, + combinatorIndex, + reversed, + cache = {}, + reverseCache = {}, + reUnescape = /\\/g; + +var parse = function(expression, isReversed){ + if (expression == null) return null; + if (expression.Slick === true) return expression; + expression = ('' + expression).replace(/^\s+|\s+$/g, ''); + reversed = !!isReversed; + var currentCache = (reversed) ? reverseCache : cache; + if (currentCache[expression]) return currentCache[expression]; + parsed = { + Slick: true, + expressions: [], + raw: expression, + reverse: function(){ + return parse(this.raw, true); + } + }; + separatorIndex = -1; + while (expression != (expression = expression.replace(regexp, parser))); + parsed.length = parsed.expressions.length; + return currentCache[parsed.raw] = (reversed) ? reverse(parsed) : parsed; +}; + +var reverseCombinator = function(combinator){ + if (combinator === '!') return ' '; + else if (combinator === ' ') return '!'; + else if ((/^!/).test(combinator)) return combinator.replace(/^!/, ''); + else return '!' + combinator; +}; + +var reverse = function(expression){ + var expressions = expression.expressions; + for (var i = 0; i < expressions.length; i++){ + var exp = expressions[i]; + var last = {parts: [], tag: '*', combinator: reverseCombinator(exp[0].combinator)}; + + for (var j = 0; j < exp.length; j++){ + var cexp = exp[j]; + if (!cexp.reverseCombinator) cexp.reverseCombinator = ' '; + cexp.combinator = cexp.reverseCombinator; + delete cexp.reverseCombinator; + } + + exp.reverse().push(last); + } + return expression; +}; + +var escapeRegExp = function(string){// Credit: XRegExp 0.6.1 (c) 2007-2008 Steven Levithan <http://stevenlevithan.com/regex/xregexp/> MIT License + return string.replace(/[-[\]{}()*+?.\\^$|,#\s]/g, function(match){ + return '\\' + match; + }); +}; + +var regexp = new RegExp( +/* +#!/usr/bin/env ruby +puts "\t\t" + DATA.read.gsub(/\(\?x\)|\s+#.*$|\s+|\\$|\\n/,'') +__END__ + "(?x)^(?:\ + \\s* ( , ) \\s* # Separator \n\ + | \\s* ( <combinator>+ ) \\s* # Combinator \n\ + | ( \\s+ ) # CombinatorChildren \n\ + | ( <unicode>+ | \\* ) # Tag \n\ + | \\# ( <unicode>+ ) # ID \n\ + | \\. ( <unicode>+ ) # ClassName \n\ + | # Attribute \n\ + \\[ \ + \\s* (<unicode1>+) (?: \ + \\s* ([*^$!~|]?=) (?: \ + \\s* (?:\ + ([\"']?)(.*?)\\9 \ + )\ + ) \ + )? \\s* \ + \\](?!\\]) \n\ + | :+ ( <unicode>+ )(?:\ + \\( (?:\ + (?:([\"'])([^\\12]*)\\12)|((?:\\([^)]+\\)|[^()]*)+)\ + ) \\)\ + )?\ + )" +*/ + "^(?:\\s*(,)\\s*|\\s*(<combinator>+)\\s*|(\\s+)|(<unicode>+|\\*)|\\#(<unicode>+)|\\.(<unicode>+)|\\[\\s*(<unicode1>+)(?:\\s*([*^$!~|]?=)(?:\\s*(?:([\"']?)(.*?)\\9)))?\\s*\\](?!\\])|(:+)(<unicode>+)(?:\\((?:(?:([\"'])([^\\13]*)\\13)|((?:\\([^)]+\\)|[^()]*)+))\\))?)" + .replace(/<combinator>/, '[' + escapeRegExp(">+~`!@$%^&={}\\;</") + ']') + .replace(/<unicode>/g, '(?:[\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])') + .replace(/<unicode1>/g, '(?:[:\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])') +); + +function parser( + rawMatch, + + separator, + combinator, + combinatorChildren, + + tagName, + id, + className, + + attributeKey, + attributeOperator, + attributeQuote, + attributeValue, + + pseudoMarker, + pseudoClass, + pseudoQuote, + pseudoClassQuotedValue, + pseudoClassValue +){ + if (separator || separatorIndex === -1){ + parsed.expressions[++separatorIndex] = []; + combinatorIndex = -1; + if (separator) return ''; + } + + if (combinator || combinatorChildren || combinatorIndex === -1){ + combinator = combinator || ' '; + var currentSeparator = parsed.expressions[separatorIndex]; + if (reversed && currentSeparator[combinatorIndex]) + currentSeparator[combinatorIndex].reverseCombinator = reverseCombinator(combinator); + currentSeparator[++combinatorIndex] = {combinator: combinator, tag: '*'}; + } + + var currentParsed = parsed.expressions[separatorIndex][combinatorIndex]; + + if (tagName){ + currentParsed.tag = tagName.replace(reUnescape, ''); + + } else if (id){ + currentParsed.id = id.replace(reUnescape, ''); + + } else if (className){ + className = className.replace(reUnescape, ''); + + if (!currentParsed.classList) currentParsed.classList = []; + if (!currentParsed.classes) currentParsed.classes = []; + currentParsed.classList.push(className); + currentParsed.classes.push({ + value: className, + regexp: new RegExp('(^|\\s)' + escapeRegExp(className) + '(\\s|$)') + }); + + } else if (pseudoClass){ + pseudoClassValue = pseudoClassValue || pseudoClassQuotedValue; + pseudoClassValue = pseudoClassValue ? pseudoClassValue.replace(reUnescape, '') : null; + + if (!currentParsed.pseudos) currentParsed.pseudos = []; + currentParsed.pseudos.push({ + key: pseudoClass.replace(reUnescape, ''), + value: pseudoClassValue, + type: pseudoMarker.length == 1 ? 'class' : 'element' + }); + + } else if (attributeKey){ + attributeKey = attributeKey.replace(reUnescape, ''); + attributeValue = (attributeValue || '').replace(reUnescape, ''); + + var test, regexp; + + switch (attributeOperator){ + case '^=' : regexp = new RegExp( '^'+ escapeRegExp(attributeValue) ); break; + case '$=' : regexp = new RegExp( escapeRegExp(attributeValue) +'$' ); break; + case '~=' : regexp = new RegExp( '(^|\\s)'+ escapeRegExp(attributeValue) +'(\\s|$)' ); break; + case '|=' : regexp = new RegExp( '^'+ escapeRegExp(attributeValue) +'(-|$)' ); break; + case '=' : test = function(value){ + return attributeValue == value; + }; break; + case '*=' : test = function(value){ + return value && value.indexOf(attributeValue) > -1; + }; break; + case '!=' : test = function(value){ + return attributeValue != value; + }; break; + default : test = function(value){ + return !!value; + }; + } + + if (attributeValue == '' && (/^[*$^]=$/).test(attributeOperator)) test = function(){ + return false; + }; + + if (!test) test = function(value){ + return value && regexp.test(value); + }; + + if (!currentParsed.attributes) currentParsed.attributes = []; + currentParsed.attributes.push({ + key: attributeKey, + operator: attributeOperator, + value: attributeValue, + test: test + }); + + } + + return ''; +}; + +// Slick NS + +var Slick = (this.Slick || {}); + +Slick.parse = function(expression){ + return parse(expression); +}; + +Slick.escapeRegExp = escapeRegExp; + +if (!this.Slick) this.Slick = Slick; + +}).apply(/*<CommonJS>*/(typeof exports != 'undefined') ? exports : /*</CommonJS>*/this); + +/* +--- +name: Slick.Finder +description: The new, superfast css selector engine. +provides: Slick.Finder +requires: Slick.Parser +... +*/ + +;(function(){ + +var local = {}, + featuresCache = {}, + toString = Object.prototype.toString; + +// Feature / Bug detection + +local.isNativeCode = function(fn){ + return (/\{\s*\[native code\]\s*\}/).test('' + fn); +}; + +local.isXML = function(document){ + return (!!document.xmlVersion) || (!!document.xml) || (toString.call(document) == '[object XMLDocument]') || + (document.nodeType == 9 && document.documentElement.nodeName != 'HTML'); +}; + +local.setDocument = function(document){ + + // convert elements / window arguments to document. if document cannot be extrapolated, the function returns. + var nodeType = document.nodeType; + if (nodeType == 9); // document + else if (nodeType) document = document.ownerDocument; // node + else if (document.navigator) document = document.document; // window + else return; + + // check if it's the old document + + if (this.document === document) return; + this.document = document; + + // check if we have done feature detection on this document before + + var root = document.documentElement, + rootUid = this.getUIDXML(root), + features = featuresCache[rootUid], + feature; + + if (features){ + for (feature in features){ + this[feature] = features[feature]; + } + return; + } + + features = featuresCache[rootUid] = {}; + + features.root = root; + features.isXMLDocument = this.isXML(document); + + features.brokenStarGEBTN + = features.starSelectsClosedQSA + = features.idGetsName + = features.brokenMixedCaseQSA + = features.brokenGEBCN + = features.brokenCheckedQSA + = features.brokenEmptyAttributeQSA + = features.isHTMLDocument + = features.nativeMatchesSelector + = false; + + var starSelectsClosed, starSelectsComments, + brokenSecondClassNameGEBCN, cachedGetElementsByClassName, + brokenFormAttributeGetter; + + var selected, id = 'slick_uniqueid'; + var testNode = document.createElement('div'); + + var testRoot = document.body || document.getElementsByTagName('body')[0] || root; + testRoot.appendChild(testNode); + + // on non-HTML documents innerHTML and getElementsById doesnt work properly + try { + testNode.innerHTML = '<a id="'+id+'"></a>'; + features.isHTMLDocument = !!document.getElementById(id); + } catch(e){}; + + if (features.isHTMLDocument){ + + testNode.style.display = 'none'; + + // IE returns comment nodes for getElementsByTagName('*') for some documents + testNode.appendChild(document.createComment('')); + starSelectsComments = (testNode.getElementsByTagName('*').length > 1); + + // IE returns closed nodes (EG:"</foo>") for getElementsByTagName('*') for some documents + try { + testNode.innerHTML = 'foo</foo>'; + selected = testNode.getElementsByTagName('*'); + starSelectsClosed = (selected && !!selected.length && selected[0].nodeName.charAt(0) == '/'); + } catch(e){}; + + features.brokenStarGEBTN = starSelectsComments || starSelectsClosed; + + // IE returns elements with the name instead of just id for getElementsById for some documents + try { + testNode.innerHTML = '<a name="'+ id +'"></a><b id="'+ id +'"></b>'; + features.idGetsName = document.getElementById(id) === testNode.firstChild; + } catch(e){}; + + if (testNode.getElementsByClassName){ + + // Safari 3.2 getElementsByClassName caches results + try { + testNode.innerHTML = '<a class="f"></a><a class="b"></a>'; + testNode.getElementsByClassName('b').length; + testNode.firstChild.className = 'b'; + cachedGetElementsByClassName = (testNode.getElementsByClassName('b').length != 2); + } catch(e){}; + + // Opera 9.6 getElementsByClassName doesnt detects the class if its not the first one + try { + testNode.innerHTML = '<a class="a"></a><a class="f b a"></a>'; + brokenSecondClassNameGEBCN = (testNode.getElementsByClassName('a').length != 2); + } catch(e){}; + + features.brokenGEBCN = cachedGetElementsByClassName || brokenSecondClassNameGEBCN; + } + + if (testNode.querySelectorAll){ + // IE 8 returns closed nodes (EG:"</foo>") for querySelectorAll('*') for some documents + try { + testNode.innerHTML = 'foo</foo>'; + selected = testNode.querySelectorAll('*'); + features.starSelectsClosedQSA = (selected && !!selected.length && selected[0].nodeName.charAt(0) == '/'); + } catch(e){}; + + // Safari 3.2 querySelectorAll doesnt work with mixedcase on quirksmode + try { + testNode.innerHTML = '<a class="MiX"></a>'; + features.brokenMixedCaseQSA = !testNode.querySelectorAll('.MiX').length; + } catch(e){}; + + // Webkit and Opera dont return selected options on querySelectorAll + try { + testNode.innerHTML = '<select><option selected="selected">a</option></select>'; + features.brokenCheckedQSA = (testNode.querySelectorAll(':checked').length == 0); + } catch(e){}; + + // IE returns incorrect results for attr[*^$]="" selectors on querySelectorAll + try { + testNode.innerHTML = '<a class=""></a>'; + features.brokenEmptyAttributeQSA = (testNode.querySelectorAll('[class*=""]').length != 0); + } catch(e){}; + + } + + // IE6-7, if a form has an input of id x, form.getAttribute(x) returns a reference to the input + try { + testNode.innerHTML = '<form action="s"><input id="action"/></form>'; + brokenFormAttributeGetter = (testNode.firstChild.getAttribute('action') != 's'); + } catch(e){}; + + // native matchesSelector function + + features.nativeMatchesSelector = root.matches || /*root.msMatchesSelector ||*/ root.mozMatchesSelector || root.webkitMatchesSelector; + if (features.nativeMatchesSelector) try { + // if matchesSelector trows errors on incorrect sintaxes we can use it + features.nativeMatchesSelector.call(root, ':slick'); + features.nativeMatchesSelector = null; + } catch(e){}; + + } + + try { + root.slick_expando = 1; + delete root.slick_expando; + features.getUID = this.getUIDHTML; + } catch(e){ + features.getUID = this.getUIDXML; + } + + testRoot.removeChild(testNode); + testNode = selected = testRoot = null; + + // getAttribute + + features.getAttribute = (features.isHTMLDocument && brokenFormAttributeGetter) ? function(node, name){ + var method = this.attributeGetters[name]; + if (method) return method.call(node); + var attributeNode = node.getAttributeNode(name); + return (attributeNode) ? attributeNode.nodeValue : null; + } : function(node, name){ + var method = this.attributeGetters[name]; + return (method) ? method.call(node) : node.getAttribute(name); + }; + + // hasAttribute + + features.hasAttribute = (root && this.isNativeCode(root.hasAttribute)) ? function(node, attribute){ + return node.hasAttribute(attribute); + } : function(node, attribute){ + node = node.getAttributeNode(attribute); + return !!(node && (node.specified || node.nodeValue)); + }; + + // contains + // FIXME: Add specs: local.contains should be different for xml and html documents? + var nativeRootContains = root && this.isNativeCode(root.contains), + nativeDocumentContains = document && this.isNativeCode(document.contains); + + features.contains = (nativeRootContains && nativeDocumentContains) ? function(context, node){ + return context.contains(node); + } : (nativeRootContains && !nativeDocumentContains) ? function(context, node){ + // IE8 does not have .contains on document. + return context === node || ((context === document) ? document.documentElement : context).contains(node); + } : (root && root.compareDocumentPosition) ? function(context, node){ + return context === node || !!(context.compareDocumentPosition(node) & 16); + } : function(context, node){ + if (node) do { + if (node === context) return true; + } while ((node = node.parentNode)); + return false; + }; + + // document order sorting + // credits to Sizzle (http://sizzlejs.com/) + + features.documentSorter = (root.compareDocumentPosition) ? function(a, b){ + if (!a.compareDocumentPosition || !b.compareDocumentPosition) return 0; + return a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1; + } : ('sourceIndex' in root) ? function(a, b){ + if (!a.sourceIndex || !b.sourceIndex) return 0; + return a.sourceIndex - b.sourceIndex; + } : (document.createRange) ? function(a, b){ + if (!a.ownerDocument || !b.ownerDocument) return 0; + var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange(); + aRange.setStart(a, 0); + aRange.setEnd(a, 0); + bRange.setStart(b, 0); + bRange.setEnd(b, 0); + return aRange.compareBoundaryPoints(Range.START_TO_END, bRange); + } : null ; + + root = null; + + for (feature in features){ + this[feature] = features[feature]; + } +}; + +// Main Method + +var reSimpleSelector = /^([#.]?)((?:[\w-]+|\*))$/, + reEmptyAttribute = /\[.+[*$^]=(?:""|'')?\]/, + qsaFailExpCache = {}; + +local.search = function(context, expression, append, first){ + + var found = this.found = (first) ? null : (append || []); + + if (!context) return found; + else if (context.navigator) context = context.document; // Convert the node from a window to a document + else if (!context.nodeType) return found; + + // setup + + var parsed, i, + uniques = this.uniques = {}, + hasOthers = !!(append && append.length), + contextIsDocument = (context.nodeType == 9); + + if (this.document !== (contextIsDocument ? context : context.ownerDocument)) this.setDocument(context); + + // avoid duplicating items already in the append array + if (hasOthers) for (i = found.length; i--;) uniques[this.getUID(found[i])] = true; + + // expression checks + + if (typeof expression == 'string'){ // expression is a string + + /*<simple-selectors-override>*/ + var simpleSelector = expression.match(reSimpleSelector); + simpleSelectors: if (simpleSelector){ + + var symbol = simpleSelector[1], + name = simpleSelector[2], + node, nodes; + + if (!symbol){ + + if (name == '*' && this.brokenStarGEBTN) break simpleSelectors; + nodes = context.getElementsByTagName(name); + if (first) return nodes[0] || null; + for (i = 0; node = nodes[i++];){ + if (!(hasOthers && uniques[this.getUID(node)])) found.push(node); + } + + } else if (symbol == '#'){ + + if (!this.isHTMLDocument || !contextIsDocument) break simpleSelectors; + node = context.getElementById(name); + if (!node) return found; + if (this.idGetsName && node.getAttributeNode('id').nodeValue != name) break simpleSelectors; + if (first) return node || null; + if (!(hasOthers && uniques[this.getUID(node)])) found.push(node); + + } else if (symbol == '.'){ + + if (!this.isHTMLDocument || ((!context.getElementsByClassName || this.brokenGEBCN) && context.querySelectorAll)) break simpleSelectors; + if (context.getElementsByClassName && !this.brokenGEBCN){ + nodes = context.getElementsByClassName(name); + if (first) return nodes[0] || null; + for (i = 0; node = nodes[i++];){ + if (!(hasOthers && uniques[this.getUID(node)])) found.push(node); + } + } else { + var matchClass = new RegExp('(^|\\s)'+ Slick.escapeRegExp(name) +'(\\s|$)'); + nodes = context.getElementsByTagName('*'); + for (i = 0; node = nodes[i++];){ + className = node.className; + if (!(className && matchClass.test(className))) continue; + if (first) return node; + if (!(hasOthers && uniques[this.getUID(node)])) found.push(node); + } + } + + } + + if (hasOthers) this.sort(found); + return (first) ? null : found; + + } + /*</simple-selectors-override>*/ + + /*<query-selector-override>*/ + querySelector: if (context.querySelectorAll){ + + if (!this.isHTMLDocument + || qsaFailExpCache[expression] + //TODO: only skip when expression is actually mixed case + || this.brokenMixedCaseQSA + || (this.brokenCheckedQSA && expression.indexOf(':checked') > -1) + || (this.brokenEmptyAttributeQSA && reEmptyAttribute.test(expression)) + || (!contextIsDocument //Abort when !contextIsDocument and... + // there are multiple expressions in the selector + // since we currently only fix non-document rooted QSA for single expression selectors + && expression.indexOf(',') > -1 + ) + || Slick.disableQSA + ) break querySelector; + + var _expression = expression, _context = context; + if (!contextIsDocument){ + // non-document rooted QSA + // credits to Andrew Dupont + var currentId = _context.getAttribute('id'), slickid = 'slickid__'; + _context.setAttribute('id', slickid); + _expression = '#' + slickid + ' ' + _expression; + context = _context.parentNode; + } + + try { + if (first) return context.querySelector(_expression) || null; + else nodes = context.querySelectorAll(_expression); + } catch(e){ + qsaFailExpCache[expression] = 1; + break querySelector; + } finally { + if (!contextIsDocument){ + if (currentId) _context.setAttribute('id', currentId); + else _context.removeAttribute('id'); + context = _context; + } + } + + if (this.starSelectsClosedQSA) for (i = 0; node = nodes[i++];){ + if (node.nodeName > '@' && !(hasOthers && uniques[this.getUID(node)])) found.push(node); + } else for (i = 0; node = nodes[i++];){ + if (!(hasOthers && uniques[this.getUID(node)])) found.push(node); + } + + if (hasOthers) this.sort(found); + return found; + + } + /*</query-selector-override>*/ + + parsed = this.Slick.parse(expression); + if (!parsed.length) return found; + } else if (expression == null){ // there is no expression + return found; + } else if (expression.Slick){ // expression is a parsed Slick object + parsed = expression; + } else if (this.contains(context.documentElement || context, expression)){ // expression is a node + (found) ? found.push(expression) : found = expression; + return found; + } else { // other junk + return found; + } + + /*<pseudo-selectors>*//*<nth-pseudo-selectors>*/ + + // cache elements for the nth selectors + + this.posNTH = {}; + this.posNTHLast = {}; + this.posNTHType = {}; + this.posNTHTypeLast = {}; + + /*</nth-pseudo-selectors>*//*</pseudo-selectors>*/ + + // if append is null and there is only a single selector with one expression use pushArray, else use pushUID + this.push = (!hasOthers && (first || (parsed.length == 1 && parsed.expressions[0].length == 1))) ? this.pushArray : this.pushUID; + + if (found == null) found = []; + + // default engine + + var j, m, n; + var combinator, tag, id, classList, classes, attributes, pseudos; + var currentItems, currentExpression, currentBit, lastBit, expressions = parsed.expressions; + + search: for (i = 0; (currentExpression = expressions[i]); i++) for (j = 0; (currentBit = currentExpression[j]); j++){ + + combinator = 'combinator:' + currentBit.combinator; + if (!this[combinator]) continue search; + + tag = (this.isXMLDocument) ? currentBit.tag : currentBit.tag.toUpperCase(); + id = currentBit.id; + classList = currentBit.classList; + classes = currentBit.classes; + attributes = currentBit.attributes; + pseudos = currentBit.pseudos; + lastBit = (j === (currentExpression.length - 1)); + + this.bitUniques = {}; + + if (lastBit){ + this.uniques = uniques; + this.found = found; + } else { + this.uniques = {}; + this.found = []; + } + + if (j === 0){ + this[combinator](context, tag, id, classes, attributes, pseudos, classList); + if (first && lastBit && found.length) break search; + } else { + if (first && lastBit) for (m = 0, n = currentItems.length; m < n; m++){ + this[combinator](currentItems[m], tag, id, classes, attributes, pseudos, classList); + if (found.length) break search; + } else for (m = 0, n = currentItems.length; m < n; m++) this[combinator](currentItems[m], tag, id, classes, attributes, pseudos, classList); + } + + currentItems = this.found; + } + + // should sort if there are nodes in append and if you pass multiple expressions. + if (hasOthers || (parsed.expressions.length > 1)) this.sort(found); + + return (first) ? (found[0] || null) : found; +}; + +// Utils + +local.uidx = 1; +local.uidk = 'slick-uniqueid'; + +local.getUIDXML = function(node){ + var uid = node.getAttribute(this.uidk); + if (!uid){ + uid = this.uidx++; + node.setAttribute(this.uidk, uid); + } + return uid; +}; + +local.getUIDHTML = function(node){ + return node.uniqueNumber || (node.uniqueNumber = this.uidx++); +}; + +// sort based on the setDocument documentSorter method. + +local.sort = function(results){ + if (!this.documentSorter) return results; + results.sort(this.documentSorter); + return results; +}; + +/*<pseudo-selectors>*//*<nth-pseudo-selectors>*/ + +local.cacheNTH = {}; + +local.matchNTH = /^([+-]?\d*)?([a-z]+)?([+-]\d+)?$/; + +local.parseNTHArgument = function(argument){ + var parsed = argument.match(this.matchNTH); + if (!parsed) return false; + var special = parsed[2] || false; + var a = parsed[1] || 1; + if (a == '-') a = -1; + var b = +parsed[3] || 0; + parsed = + (special == 'n') ? {a: a, b: b} : + (special == 'odd') ? {a: 2, b: 1} : + (special == 'even') ? {a: 2, b: 0} : {a: 0, b: a}; + + return (this.cacheNTH[argument] = parsed); +}; + +local.createNTHPseudo = function(child, sibling, positions, ofType){ + return function(node, argument){ + var uid = this.getUID(node); + if (!this[positions][uid]){ + var parent = node.parentNode; + if (!parent) return false; + var el = parent[child], count = 1; + if (ofType){ + var nodeName = node.nodeName; + do { + if (el.nodeName != nodeName) continue; + this[positions][this.getUID(el)] = count++; + } while ((el = el[sibling])); + } else { + do { + if (el.nodeType != 1) continue; + this[positions][this.getUID(el)] = count++; + } while ((el = el[sibling])); + } + } + argument = argument || 'n'; + var parsed = this.cacheNTH[argument] || this.parseNTHArgument(argument); + if (!parsed) return false; + var a = parsed.a, b = parsed.b, pos = this[positions][uid]; + if (a == 0) return b == pos; + if (a > 0){ + if (pos < b) return false; + } else { + if (b < pos) return false; + } + return ((pos - b) % a) == 0; + }; +}; + +/*</nth-pseudo-selectors>*//*</pseudo-selectors>*/ + +local.pushArray = function(node, tag, id, classes, attributes, pseudos){ + if (this.matchSelector(node, tag, id, classes, attributes, pseudos)) this.found.push(node); +}; + +local.pushUID = function(node, tag, id, classes, attributes, pseudos){ + var uid = this.getUID(node); + if (!this.uniques[uid] && this.matchSelector(node, tag, id, classes, attributes, pseudos)){ + this.uniques[uid] = true; + this.found.push(node); + } +}; + +local.matchNode = function(node, selector){ + if (this.isHTMLDocument && this.nativeMatchesSelector){ + try { + return this.nativeMatchesSelector.call(node, selector.replace(/\[([^=]+)=\s*([^'"\]]+?)\s*\]/g, '[$1="$2"]')); + } catch(matchError){} + } + + var parsed = this.Slick.parse(selector); + if (!parsed) return true; + + // simple (single) selectors + var expressions = parsed.expressions, simpleExpCounter = 0, i, currentExpression; + for (i = 0; (currentExpression = expressions[i]); i++){ + if (currentExpression.length == 1){ + var exp = currentExpression[0]; + if (this.matchSelector(node, (this.isXMLDocument) ? exp.tag : exp.tag.toUpperCase(), exp.id, exp.classes, exp.attributes, exp.pseudos)) return true; + simpleExpCounter++; + } + } + + if (simpleExpCounter == parsed.length) return false; + + var nodes = this.search(this.document, parsed), item; + for (i = 0; item = nodes[i++];){ + if (item === node) return true; + } + return false; +}; + +local.matchPseudo = function(node, name, argument){ + var pseudoName = 'pseudo:' + name; + if (this[pseudoName]) return this[pseudoName](node, argument); + var attribute = this.getAttribute(node, name); + return (argument) ? argument == attribute : !!attribute; +}; + +local.matchSelector = function(node, tag, id, classes, attributes, pseudos){ + if (tag){ + var nodeName = (this.isXMLDocument) ? node.nodeName : node.nodeName.toUpperCase(); + if (tag == '*'){ + if (nodeName < '@') return false; // Fix for comment nodes and closed nodes + } else { + if (nodeName != tag) return false; + } + } + + if (id && node.getAttribute('id') != id) return false; + + var i, part, cls; + if (classes) for (i = classes.length; i--;){ + cls = this.getAttribute(node, 'class'); + if (!(cls && classes[i].regexp.test(cls))) return false; + } + if (attributes) for (i = attributes.length; i--;){ + part = attributes[i]; + if (part.operator ? !part.test(this.getAttribute(node, part.key)) : !this.hasAttribute(node, part.key)) return false; + } + if (pseudos) for (i = pseudos.length; i--;){ + part = pseudos[i]; + if (!this.matchPseudo(node, part.key, part.value)) return false; + } + return true; +}; + +var combinators = { + + ' ': function(node, tag, id, classes, attributes, pseudos, classList){ // all child nodes, any level + + var i, item, children; + + if (this.isHTMLDocument){ + getById: if (id){ + item = this.document.getElementById(id); + if ((!item && node.all) || (this.idGetsName && item && item.getAttributeNode('id').nodeValue != id)){ + // all[id] returns all the elements with that name or id inside node + // if theres just one it will return the element, else it will be a collection + children = node.all[id]; + if (!children) return; + if (!children[0]) children = [children]; + for (i = 0; item = children[i++];){ + var idNode = item.getAttributeNode('id'); + if (idNode && idNode.nodeValue == id){ + this.push(item, tag, null, classes, attributes, pseudos); + break; + } + } + return; + } + if (!item){ + // if the context is in the dom we return, else we will try GEBTN, breaking the getById label + if (this.contains(this.root, node)) return; + else break getById; + } else if (this.document !== node && !this.contains(node, item)) return; + this.push(item, tag, null, classes, attributes, pseudos); + return; + } + getByClass: if (classes && node.getElementsByClassName && !this.brokenGEBCN){ + children = node.getElementsByClassName(classList.join(' ')); + if (!(children && children.length)) break getByClass; + for (i = 0; item = children[i++];) this.push(item, tag, id, null, attributes, pseudos); + return; + } + } + getByTag: { + children = node.getElementsByTagName(tag); + if (!(children && children.length)) break getByTag; + if (!this.brokenStarGEBTN) tag = null; + for (i = 0; item = children[i++];) this.push(item, tag, id, classes, attributes, pseudos); + } + }, + + '>': function(node, tag, id, classes, attributes, pseudos){ // direct children + if ((node = node.firstChild)) do { + if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos); + } while ((node = node.nextSibling)); + }, + + '+': function(node, tag, id, classes, attributes, pseudos){ // next sibling + while ((node = node.nextSibling)) if (node.nodeType == 1){ + this.push(node, tag, id, classes, attributes, pseudos); + break; + } + }, + + '^': function(node, tag, id, classes, attributes, pseudos){ // first child + node = node.firstChild; + if (node){ + if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos); + else this['combinator:+'](node, tag, id, classes, attributes, pseudos); + } + }, + + '~': function(node, tag, id, classes, attributes, pseudos){ // next siblings + while ((node = node.nextSibling)){ + if (node.nodeType != 1) continue; + var uid = this.getUID(node); + if (this.bitUniques[uid]) break; + this.bitUniques[uid] = true; + this.push(node, tag, id, classes, attributes, pseudos); + } + }, + + '++': function(node, tag, id, classes, attributes, pseudos){ // next sibling and previous sibling + this['combinator:+'](node, tag, id, classes, attributes, pseudos); + this['combinator:!+'](node, tag, id, classes, attributes, pseudos); + }, + + '~~': function(node, tag, id, classes, attributes, pseudos){ // next siblings and previous siblings + this['combinator:~'](node, tag, id, classes, attributes, pseudos); + this['combinator:!~'](node, tag, id, classes, attributes, pseudos); + }, + + '!': function(node, tag, id, classes, attributes, pseudos){ // all parent nodes up to document + while ((node = node.parentNode)) if (node !== this.document) this.push(node, tag, id, classes, attributes, pseudos); + }, + + '!>': function(node, tag, id, classes, attributes, pseudos){ // direct parent (one level) + node = node.parentNode; + if (node !== this.document) this.push(node, tag, id, classes, attributes, pseudos); + }, + + '!+': function(node, tag, id, classes, attributes, pseudos){ // previous sibling + while ((node = node.previousSibling)) if (node.nodeType == 1){ + this.push(node, tag, id, classes, attributes, pseudos); + break; + } + }, + + '!^': function(node, tag, id, classes, attributes, pseudos){ // last child + node = node.lastChild; + if (node){ + if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos); + else this['combinator:!+'](node, tag, id, classes, attributes, pseudos); + } + }, + + '!~': function(node, tag, id, classes, attributes, pseudos){ // previous siblings + while ((node = node.previousSibling)){ + if (node.nodeType != 1) continue; + var uid = this.getUID(node); + if (this.bitUniques[uid]) break; + this.bitUniques[uid] = true; + this.push(node, tag, id, classes, attributes, pseudos); + } + } + +}; + +for (var c in combinators) local['combinator:' + c] = combinators[c]; + +var pseudos = { + + /*<pseudo-selectors>*/ + + 'empty': function(node){ + var child = node.firstChild; + return !(child && child.nodeType == 1) && !(node.innerText || node.textContent || '').length; + }, + + 'not': function(node, expression){ + return !this.matchNode(node, expression); + }, + + 'contains': function(node, text){ + return (node.innerText || node.textContent || '').indexOf(text) > -1; + }, + + 'first-child': function(node){ + while ((node = node.previousSibling)) if (node.nodeType == 1) return false; + return true; + }, + + 'last-child': function(node){ + while ((node = node.nextSibling)) if (node.nodeType == 1) return false; + return true; + }, + + 'only-child': function(node){ + var prev = node; + while ((prev = prev.previousSibling)) if (prev.nodeType == 1) return false; + var next = node; + while ((next = next.nextSibling)) if (next.nodeType == 1) return false; + return true; + }, + + /*<nth-pseudo-selectors>*/ + + 'nth-child': local.createNTHPseudo('firstChild', 'nextSibling', 'posNTH'), + + 'nth-last-child': local.createNTHPseudo('lastChild', 'previousSibling', 'posNTHLast'), + + 'nth-of-type': local.createNTHPseudo('firstChild', 'nextSibling', 'posNTHType', true), + + 'nth-last-of-type': local.createNTHPseudo('lastChild', 'previousSibling', 'posNTHTypeLast', true), + + 'index': function(node, index){ + return this['pseudo:nth-child'](node, '' + (index + 1)); + }, + + 'even': function(node){ + return this['pseudo:nth-child'](node, '2n'); + }, + + 'odd': function(node){ + return this['pseudo:nth-child'](node, '2n+1'); + }, + + /*</nth-pseudo-selectors>*/ + + /*<of-type-pseudo-selectors>*/ + + 'first-of-type': function(node){ + var nodeName = node.nodeName; + while ((node = node.previousSibling)) if (node.nodeName == nodeName) return false; + return true; + }, + + 'last-of-type': function(node){ + var nodeName = node.nodeName; + while ((node = node.nextSibling)) if (node.nodeName == nodeName) return false; + return true; + }, + + 'only-of-type': function(node){ + var prev = node, nodeName = node.nodeName; + while ((prev = prev.previousSibling)) if (prev.nodeName == nodeName) return false; + var next = node; + while ((next = next.nextSibling)) if (next.nodeName == nodeName) return false; + return true; + }, + + /*</of-type-pseudo-selectors>*/ + + // custom pseudos + + 'enabled': function(node){ + return !node.disabled; + }, + + 'disabled': function(node){ + return node.disabled; + }, + + 'checked': function(node){ + return node.checked || node.selected; + }, + + 'focus': function(node){ + return this.isHTMLDocument && this.document.activeElement === node && (node.href || node.type || this.hasAttribute(node, 'tabindex')); + }, + + 'root': function(node){ + return (node === this.root); + }, + + 'selected': function(node){ + return node.selected; + } + + /*</pseudo-selectors>*/ +}; + +for (var p in pseudos) local['pseudo:' + p] = pseudos[p]; + +// attributes methods + +var attributeGetters = local.attributeGetters = { + + 'for': function(){ + return ('htmlFor' in this) ? this.htmlFor : this.getAttribute('for'); + }, + + 'href': function(){ + return ('href' in this) ? this.getAttribute('href', 2) : this.getAttribute('href'); + }, + + 'style': function(){ + return (this.style) ? this.style.cssText : this.getAttribute('style'); + }, + + 'tabindex': function(){ + var attributeNode = this.getAttributeNode('tabindex'); + return (attributeNode && attributeNode.specified) ? attributeNode.nodeValue : null; + }, + + 'type': function(){ + return this.getAttribute('type'); + }, + + 'maxlength': function(){ + var attributeNode = this.getAttributeNode('maxLength'); + return (attributeNode && attributeNode.specified) ? attributeNode.nodeValue : null; + } + +}; + +attributeGetters.MAXLENGTH = attributeGetters.maxLength = attributeGetters.maxlength; + +// Slick + +var Slick = local.Slick = (this.Slick || {}); + +Slick.version = '1.1.7'; + +// Slick finder + +Slick.search = function(context, expression, append){ + return local.search(context, expression, append); +}; + +Slick.find = function(context, expression){ + return local.search(context, expression, null, true); +}; + +// Slick containment checker + +Slick.contains = function(container, node){ + local.setDocument(container); + return local.contains(container, node); +}; + +// Slick attribute getter + +Slick.getAttribute = function(node, name){ + local.setDocument(node); + return local.getAttribute(node, name); +}; + +Slick.hasAttribute = function(node, name){ + local.setDocument(node); + return local.hasAttribute(node, name); +}; + +// Slick matcher + +Slick.match = function(node, selector){ + if (!(node && selector)) return false; + if (!selector || selector === node) return true; + local.setDocument(node); + return local.matchNode(node, selector); +}; + +// Slick attribute accessor + +Slick.defineAttributeGetter = function(name, fn){ + local.attributeGetters[name] = fn; + return this; +}; + +Slick.lookupAttributeGetter = function(name){ + return local.attributeGetters[name]; +}; + +// Slick pseudo accessor + +Slick.definePseudo = function(name, fn){ + local['pseudo:' + name] = function(node, argument){ + return fn.call(node, argument); + }; + return this; +}; + +Slick.lookupPseudo = function(name){ + var pseudo = local['pseudo:' + name]; + if (pseudo) return function(argument){ + return pseudo.call(this, argument); + }; + return null; +}; + +// Slick overrides accessor + +Slick.override = function(regexp, fn){ + local.override(regexp, fn); + return this; +}; + +Slick.isXML = local.isXML; + +Slick.uidOf = function(node){ + return local.getUIDHTML(node); +}; + +if (!this.Slick) this.Slick = Slick; + +}).apply(/*<CommonJS>*/(typeof exports != 'undefined') ? exports : /*</CommonJS>*/this); + +/* +--- + +name: Element + +description: One of the most important items in MooTools. Contains the dollar function, the dollars function, and an handful of cross-browser, time-saver methods to let you easily work with HTML Elements. + +license: MIT-style license. + +requires: [Window, Document, Array, String, Function, Object, Number, Slick.Parser, Slick.Finder] + +provides: [Element, Elements, $, $$, IFrame, Selectors] + +... +*/ + +var Element = this.Element = function(tag, props){ + var konstructor = Element.Constructors[tag]; + if (konstructor) return konstructor(props); + if (typeof tag != 'string') return document.id(tag).set(props); + + if (!props) props = {}; + + if (!(/^[\w-]+$/).test(tag)){ + var parsed = Slick.parse(tag).expressions[0][0]; + tag = (parsed.tag == '*') ? 'div' : parsed.tag; + if (parsed.id && props.id == null) props.id = parsed.id; + + var attributes = parsed.attributes; + if (attributes) for (var attr, i = 0, l = attributes.length; i < l; i++){ + attr = attributes[i]; + if (props[attr.key] != null) continue; + + if (attr.value != null && attr.operator == '=') props[attr.key] = attr.value; + else if (!attr.value && !attr.operator) props[attr.key] = true; + } + + if (parsed.classList && props['class'] == null) props['class'] = parsed.classList.join(' '); + } + + return document.newElement(tag, props); +}; + + +if (Browser.Element){ + Element.prototype = Browser.Element.prototype; + // IE8 and IE9 require the wrapping. + Element.prototype._fireEvent = (function(fireEvent){ + return function(type, event){ + return fireEvent.call(this, type, event); + }; + })(Element.prototype.fireEvent); +} + +new Type('Element', Element).mirror(function(name){ + if (Array.prototype[name]) return; + + var obj = {}; + obj[name] = function(){ + var results = [], args = arguments, elements = true; + for (var i = 0, l = this.length; i < l; i++){ + var element = this[i], result = results[i] = element[name].apply(element, args); + elements = (elements && typeOf(result) == 'element'); + } + return (elements) ? new Elements(results) : results; + }; + + Elements.implement(obj); +}); + +if (!Browser.Element){ + Element.parent = Object; + + Element.Prototype = { + '$constructor': Element, + '$family': Function.from('element').hide() + }; + + Element.mirror(function(name, method){ + Element.Prototype[name] = method; + }); +} + +Element.Constructors = {}; + + + +var IFrame = new Type('IFrame', function(){ + var params = Array.link(arguments, { + properties: Type.isObject, + iframe: function(obj){ + return (obj != null); + } + }); + + var props = params.properties || {}, iframe; + if (params.iframe) iframe = document.id(params.iframe); + var onload = props.onload || function(){}; + delete props.onload; + props.id = props.name = [props.id, props.name, iframe ? (iframe.id || iframe.name) : 'IFrame_' + String.uniqueID()].pick(); + iframe = new Element(iframe || 'iframe', props); + + var onLoad = function(){ + onload.call(iframe.contentWindow); + }; + + if (window.frames[props.id]) onLoad(); + else iframe.addListener('load', onLoad); + return iframe; +}); + +var Elements = this.Elements = function(nodes){ + if (nodes && nodes.length){ + var uniques = {}, node; + for (var i = 0; node = nodes[i++];){ + var uid = Slick.uidOf(node); + if (!uniques[uid]){ + uniques[uid] = true; + this.push(node); + } + } + } +}; + +Elements.prototype = {length: 0}; +Elements.parent = Array; + +new Type('Elements', Elements).implement({ + + filter: function(filter, bind){ + if (!filter) return this; + return new Elements(Array.filter(this, (typeOf(filter) == 'string') ? function(item){ + return item.match(filter); + } : filter, bind)); + }.protect(), + + push: function(){ + var length = this.length; + for (var i = 0, l = arguments.length; i < l; i++){ + var item = document.id(arguments[i]); + if (item) this[length++] = item; + } + return (this.length = length); + }.protect(), + + unshift: function(){ + var items = []; + for (var i = 0, l = arguments.length; i < l; i++){ + var item = document.id(arguments[i]); + if (item) items.push(item); + } + return Array.prototype.unshift.apply(this, items); + }.protect(), + + concat: function(){ + var newElements = new Elements(this); + for (var i = 0, l = arguments.length; i < l; i++){ + var item = arguments[i]; + if (Type.isEnumerable(item)) newElements.append(item); + else newElements.push(item); + } + return newElements; + }.protect(), + + append: function(collection){ + for (var i = 0, l = collection.length; i < l; i++) this.push(collection[i]); + return this; + }.protect(), + + empty: function(){ + while (this.length) delete this[--this.length]; + return this; + }.protect() + +}); + + + +(function(){ + +// FF, IE +var splice = Array.prototype.splice, object = {'0': 0, '1': 1, length: 2}; + +splice.call(object, 1, 1); +if (object[1] == 1) Elements.implement('splice', function(){ + var length = this.length; + var result = splice.apply(this, arguments); + while (length >= this.length) delete this[length--]; + return result; +}.protect()); + +Array.forEachMethod(function(method, name){ + Elements.implement(name, method); +}); + +Array.mirror(Elements); + +/*<ltIE8>*/ +var createElementAcceptsHTML; +try { + createElementAcceptsHTML = (document.createElement('<input name=x>').name == 'x'); +} catch (e){} + +var escapeQuotes = function(html){ + return ('' + html).replace(/&/g, '&').replace(/"/g, '"'); +}; +/*</ltIE8>*/ + +/*<ltIE9>*/ +// #2479 - IE8 Cannot set HTML of style element +var canChangeStyleHTML = (function(){ + var div = document.createElement('style'), + flag = false; + try { + div.innerHTML = '#justTesing{margin: 0px;}'; + flag = !!div.innerHTML; + } catch(e){} + return flag; +})(); +/*</ltIE9>*/ + +Document.implement({ + + newElement: function(tag, props){ + if (props){ + if (props.checked != null) props.defaultChecked = props.checked; + if ((props.type == 'checkbox' || props.type == 'radio') && props.value == null) props.value = 'on'; + /*<ltIE9>*/ // IE needs the type to be set before changing content of style element + if (!canChangeStyleHTML && tag == 'style'){ + var styleElement = document.createElement('style'); + styleElement.setAttribute('type', 'text/css'); + if (props.type) delete props.type; + return this.id(styleElement).set(props); + } + /*</ltIE9>*/ + /*<ltIE8>*/// Fix for readonly name and type properties in IE < 8 + if (createElementAcceptsHTML){ + tag = '<' + tag; + if (props.name) tag += ' name="' + escapeQuotes(props.name) + '"'; + if (props.type) tag += ' type="' + escapeQuotes(props.type) + '"'; + tag += '>'; + delete props.name; + delete props.type; + } + /*</ltIE8>*/ + } + return this.id(this.createElement(tag)).set(props); + } + +}); + +})(); + +(function(){ + +Slick.uidOf(window); +Slick.uidOf(document); + +Document.implement({ + + newTextNode: function(text){ + return this.createTextNode(text); + }, + + getDocument: function(){ + return this; + }, + + getWindow: function(){ + return this.window; + }, + + id: (function(){ + + var types = { + + string: function(id, nocash, doc){ + id = Slick.find(doc, '#' + id.replace(/(\W)/g, '\\$1')); + return (id) ? types.element(id, nocash) : null; + }, + + element: function(el, nocash){ + Slick.uidOf(el); + if (!nocash && !el.$family && !(/^(?:object|embed)$/i).test(el.tagName)){ + var fireEvent = el.fireEvent; + // wrapping needed in IE7, or else crash + el._fireEvent = function(type, event){ + return fireEvent(type, event); + }; + Object.append(el, Element.Prototype); + } + return el; + }, + + object: function(obj, nocash, doc){ + if (obj.toElement) return types.element(obj.toElement(doc), nocash); + return null; + } + + }; + + types.textnode = types.whitespace = types.window = types.document = function(zero){ + return zero; + }; + + return function(el, nocash, doc){ + if (el && el.$family && el.uniqueNumber) return el; + var type = typeOf(el); + return (types[type]) ? types[type](el, nocash, doc || document) : null; + }; + + })() + +}); + +if (window.$ == null) Window.implement('$', function(el, nc){ + return document.id(el, nc, this.document); +}); + +Window.implement({ + + getDocument: function(){ + return this.document; + }, + + getWindow: function(){ + return this; + } + +}); + +[Document, Element].invoke('implement', { + + getElements: function(expression){ + return Slick.search(this, expression, new Elements); + }, + + getElement: function(expression){ + return document.id(Slick.find(this, expression)); + } + +}); + +var contains = {contains: function(element){ + return Slick.contains(this, element); +}}; + +if (!document.contains) Document.implement(contains); +if (!document.createElement('div').contains) Element.implement(contains); + + + +// tree walking + +var injectCombinator = function(expression, combinator){ + if (!expression) return combinator; + + expression = Object.clone(Slick.parse(expression)); + + var expressions = expression.expressions; + for (var i = expressions.length; i--;) + expressions[i][0].combinator = combinator; + + return expression; +}; + +Object.forEach({ + getNext: '~', + getPrevious: '!~', + getParent: '!' +}, function(combinator, method){ + Element.implement(method, function(expression){ + return this.getElement(injectCombinator(expression, combinator)); + }); +}); + +Object.forEach({ + getAllNext: '~', + getAllPrevious: '!~', + getSiblings: '~~', + getChildren: '>', + getParents: '!' +}, function(combinator, method){ + Element.implement(method, function(expression){ + return this.getElements(injectCombinator(expression, combinator)); + }); +}); + +Element.implement({ + + getFirst: function(expression){ + return document.id(Slick.search(this, injectCombinator(expression, '>'))[0]); + }, + + getLast: function(expression){ + return document.id(Slick.search(this, injectCombinator(expression, '>')).getLast()); + }, + + getWindow: function(){ + return this.ownerDocument.window; + }, + + getDocument: function(){ + return this.ownerDocument; + }, + + getElementById: function(id){ + return document.id(Slick.find(this, '#' + ('' + id).replace(/(\W)/g, '\\$1'))); + }, + + match: function(expression){ + return !expression || Slick.match(this, expression); + } + +}); + + + +if (window.$$ == null) Window.implement('$$', function(selector){ + if (arguments.length == 1){ + if (typeof selector == 'string') return Slick.search(this.document, selector, new Elements); + else if (Type.isEnumerable(selector)) return new Elements(selector); + } + return new Elements(arguments); +}); + +// Inserters + +var inserters = { + + before: function(context, element){ + var parent = element.parentNode; + if (parent) parent.insertBefore(context, element); + }, + + after: function(context, element){ + var parent = element.parentNode; + if (parent) parent.insertBefore(context, element.nextSibling); + }, + + bottom: function(context, element){ + element.appendChild(context); + }, + + top: function(context, element){ + element.insertBefore(context, element.firstChild); + } + +}; + +inserters.inside = inserters.bottom; + + + +// getProperty / setProperty + +var propertyGetters = {}, propertySetters = {}; + +// properties + +var properties = {}; +Array.forEach([ + 'type', 'value', 'defaultValue', 'accessKey', 'cellPadding', 'cellSpacing', 'colSpan', + 'frameBorder', 'rowSpan', 'tabIndex', 'useMap' +], function(property){ + properties[property.toLowerCase()] = property; +}); + +properties.html = 'innerHTML'; +properties.text = (document.createElement('div').textContent == null) ? 'innerText': 'textContent'; + +Object.forEach(properties, function(real, key){ + propertySetters[key] = function(node, value){ + node[real] = value; + }; + propertyGetters[key] = function(node){ + return node[real]; + }; +}); + +/*<ltIE9>*/ +propertySetters.text = (function(setter){ + return function(node, value){ + if (node.get('tag') == 'style') node.set('html', value); + else node[properties.text] = value; + }; +})(propertySetters.text); + +propertyGetters.text = (function(getter){ + return function(node){ + return (node.get('tag') == 'style') ? node.innerHTML : getter(node); + }; +})(propertyGetters.text); +/*</ltIE9>*/ + +// Booleans + +var bools = [ + 'compact', 'nowrap', 'ismap', 'declare', 'noshade', 'checked', + 'disabled', 'readOnly', 'multiple', 'selected', 'noresize', + 'defer', 'defaultChecked', 'autofocus', 'controls', 'autoplay', + 'loop' +]; + +var booleans = {}; +Array.forEach(bools, function(bool){ + var lower = bool.toLowerCase(); + booleans[lower] = bool; + propertySetters[lower] = function(node, value){ + node[bool] = !!value; + }; + propertyGetters[lower] = function(node){ + return !!node[bool]; + }; +}); + +// Special cases + +Object.append(propertySetters, { + + 'class': function(node, value){ + ('className' in node) ? node.className = (value || '') : node.setAttribute('class', value); + }, + + 'for': function(node, value){ + ('htmlFor' in node) ? node.htmlFor = value : node.setAttribute('for', value); + }, + + 'style': function(node, value){ + (node.style) ? node.style.cssText = value : node.setAttribute('style', value); + }, + + 'value': function(node, value){ + node.value = (value != null) ? value : ''; + } + +}); + +propertyGetters['class'] = function(node){ + return ('className' in node) ? node.className || null : node.getAttribute('class'); +}; + +/* <webkit> */ +var el = document.createElement('button'); +// IE sets type as readonly and throws +try { el.type = 'button'; } catch(e){} +if (el.type != 'button') propertySetters.type = function(node, value){ + node.setAttribute('type', value); +}; +el = null; +/* </webkit> */ + +/*<IE>*/ + +/*<ltIE9>*/ +// #2479 - IE8 Cannot set HTML of style element +var canChangeStyleHTML = (function(){ + var div = document.createElement('style'), + flag = false; + try { + div.innerHTML = '#justTesing{margin: 0px;}'; + flag = !!div.innerHTML; + } catch(e){} + return flag; +})(); +/*</ltIE9>*/ + +var input = document.createElement('input'), volatileInputValue, html5InputSupport; + +// #2178 +input.value = 't'; +input.type = 'submit'; +volatileInputValue = input.value != 't'; + +// #2443 - IE throws "Invalid Argument" when trying to use html5 input types +try { + input.type = 'email'; + html5InputSupport = input.type == 'email'; +} catch(e){} + +input = null; + +if (volatileInputValue || !html5InputSupport) propertySetters.type = function(node, type){ + try { + var value = node.value; + node.type = type; + node.value = value; + } catch (e){} +}; +/*</IE>*/ + +/* getProperty, setProperty */ + +/* <ltIE9> */ +var pollutesGetAttribute = (function(div){ + div.random = 'attribute'; + return (div.getAttribute('random') == 'attribute'); +})(document.createElement('div')); + +var hasCloneBug = (function(test){ + test.innerHTML = '<object><param name="should_fix" value="the unknown" /></object>'; + return test.cloneNode(true).firstChild.childNodes.length != 1; +})(document.createElement('div')); +/* </ltIE9> */ + +var hasClassList = !!document.createElement('div').classList; + +var classes = function(className){ + var classNames = (className || '').clean().split(" "), uniques = {}; + return classNames.filter(function(className){ + if (className !== "" && !uniques[className]) return uniques[className] = className; + }); +}; + +var addToClassList = function(name){ + this.classList.add(name); +}; + +var removeFromClassList = function(name){ + this.classList.remove(name); +}; + +Element.implement({ + + setProperty: function(name, value){ + var setter = propertySetters[name.toLowerCase()]; + if (setter){ + setter(this, value); + } else { + /* <ltIE9> */ + var attributeWhiteList; + if (pollutesGetAttribute) attributeWhiteList = this.retrieve('$attributeWhiteList', {}); + /* </ltIE9> */ + + if (value == null){ + this.removeAttribute(name); + /* <ltIE9> */ + if (pollutesGetAttribute) delete attributeWhiteList[name]; + /* </ltIE9> */ + } else { + this.setAttribute(name, '' + value); + /* <ltIE9> */ + if (pollutesGetAttribute) attributeWhiteList[name] = true; + /* </ltIE9> */ + } + } + return this; + }, + + setProperties: function(attributes){ + for (var attribute in attributes) this.setProperty(attribute, attributes[attribute]); + return this; + }, + + getProperty: function(name){ + var getter = propertyGetters[name.toLowerCase()]; + if (getter) return getter(this); + /* <ltIE9> */ + if (pollutesGetAttribute){ + var attr = this.getAttributeNode(name), attributeWhiteList = this.retrieve('$attributeWhiteList', {}); + if (!attr) return null; + if (attr.expando && !attributeWhiteList[name]){ + var outer = this.outerHTML; + // segment by the opening tag and find mention of attribute name + if (outer.substr(0, outer.search(/\/?['"]?>(?![^<]*<['"])/)).indexOf(name) < 0) return null; + attributeWhiteList[name] = true; + } + } + /* </ltIE9> */ + var result = Slick.getAttribute(this, name); + return (!result && !Slick.hasAttribute(this, name)) ? null : result; + }, + + getProperties: function(){ + var args = Array.from(arguments); + return args.map(this.getProperty, this).associate(args); + }, + + removeProperty: function(name){ + return this.setProperty(name, null); + }, + + removeProperties: function(){ + Array.each(arguments, this.removeProperty, this); + return this; + }, + + set: function(prop, value){ + var property = Element.Properties[prop]; + (property && property.set) ? property.set.call(this, value) : this.setProperty(prop, value); + }.overloadSetter(), + + get: function(prop){ + var property = Element.Properties[prop]; + return (property && property.get) ? property.get.apply(this) : this.getProperty(prop); + }.overloadGetter(), + + erase: function(prop){ + var property = Element.Properties[prop]; + (property && property.erase) ? property.erase.apply(this) : this.removeProperty(prop); + return this; + }, + + hasClass: hasClassList ? function(className){ + return this.classList.contains(className); + } : function(className){ + return classes(this.className).contains(className); + }, + + addClass: hasClassList ? function(className){ + classes(className).forEach(addToClassList, this); + return this; + } : function(className){ + this.className = classes(className + ' ' + this.className).join(' '); + return this; + }, + + removeClass: hasClassList ? function(className){ + classes(className).forEach(removeFromClassList, this); + return this; + } : function(className){ + var classNames = classes(this.className); + classes(className).forEach(classNames.erase, classNames); + this.className = classNames.join(' '); + return this; + }, + + toggleClass: function(className, force){ + if (force == null) force = !this.hasClass(className); + return (force) ? this.addClass(className) : this.removeClass(className); + }, + + adopt: function(){ + var parent = this, fragment, elements = Array.flatten(arguments), length = elements.length; + if (length > 1) parent = fragment = document.createDocumentFragment(); + + for (var i = 0; i < length; i++){ + var element = document.id(elements[i], true); + if (element) parent.appendChild(element); + } + + if (fragment) this.appendChild(fragment); + + return this; + }, + + appendText: function(text, where){ + return this.grab(this.getDocument().newTextNode(text), where); + }, + + grab: function(el, where){ + inserters[where || 'bottom'](document.id(el, true), this); + return this; + }, + + inject: function(el, where){ + inserters[where || 'bottom'](this, document.id(el, true)); + return this; + }, + + replaces: function(el){ + el = document.id(el, true); + el.parentNode.replaceChild(this, el); + return this; + }, + + wraps: function(el, where){ + el = document.id(el, true); + return this.replaces(el).grab(el, where); + }, + + getSelected: function(){ + this.selectedIndex; // Safari 3.2.1 + return new Elements(Array.from(this.options).filter(function(option){ + return option.selected; + })); + }, + + toQueryString: function(){ + var queryString = []; + this.getElements('input, select, textarea').each(function(el){ + var type = el.type; + if (!el.name || el.disabled || type == 'submit' || type == 'reset' || type == 'file' || type == 'image') return; + + var value = (el.get('tag') == 'select') ? el.getSelected().map(function(opt){ + // IE + return document.id(opt).get('value'); + }) : ((type == 'radio' || type == 'checkbox') && !el.checked) ? null : el.get('value'); + + Array.from(value).each(function(val){ + if (typeof val != 'undefined') queryString.push(encodeURIComponent(el.name) + '=' + encodeURIComponent(val)); + }); + }); + return queryString.join('&'); + } + +}); + + +// appendHTML + +var appendInserters = { + before: 'beforeBegin', + after: 'afterEnd', + bottom: 'beforeEnd', + top: 'afterBegin', + inside: 'beforeEnd' +}; + +Element.implement('appendHTML', ('insertAdjacentHTML' in document.createElement('div')) ? function(html, where){ + this.insertAdjacentHTML(appendInserters[where || 'bottom'], html); + return this; +} : function(html, where){ + var temp = new Element('div', {html: html}), + children = temp.childNodes, + fragment = temp.firstChild; + + if (!fragment) return this; + if (children.length > 1){ + fragment = document.createDocumentFragment(); + for (var i = 0, l = children.length; i < l; i++){ + fragment.appendChild(children[i]); + } + } + + inserters[where || 'bottom'](fragment, this); + return this; +}); + +var collected = {}, storage = {}; + +var get = function(uid){ + return (storage[uid] || (storage[uid] = {})); +}; + +var clean = function(item){ + var uid = item.uniqueNumber; + if (item.removeEvents) item.removeEvents(); + if (item.clearAttributes) item.clearAttributes(); + if (uid != null){ + delete collected[uid]; + delete storage[uid]; + } + return item; +}; + +var formProps = {input: 'checked', option: 'selected', textarea: 'value'}; + +Element.implement({ + + destroy: function(){ + var children = clean(this).getElementsByTagName('*'); + Array.each(children, clean); + Element.dispose(this); + return null; + }, + + empty: function(){ + Array.from(this.childNodes).each(Element.dispose); + return this; + }, + + dispose: function(){ + return (this.parentNode) ? this.parentNode.removeChild(this) : this; + }, + + clone: function(contents, keepid){ + contents = contents !== false; + var clone = this.cloneNode(contents), ce = [clone], te = [this], i; + + if (contents){ + ce.append(Array.from(clone.getElementsByTagName('*'))); + te.append(Array.from(this.getElementsByTagName('*'))); + } + + for (i = ce.length; i--;){ + var node = ce[i], element = te[i]; + if (!keepid) node.removeAttribute('id'); + /*<ltIE9>*/ + if (node.clearAttributes){ + node.clearAttributes(); + node.mergeAttributes(element); + node.removeAttribute('uniqueNumber'); + if (node.options){ + var no = node.options, eo = element.options; + for (var j = no.length; j--;) no[j].selected = eo[j].selected; + } + } + /*</ltIE9>*/ + var prop = formProps[element.tagName.toLowerCase()]; + if (prop && element[prop]) node[prop] = element[prop]; + } + + /*<ltIE9>*/ + if (hasCloneBug){ + var co = clone.getElementsByTagName('object'), to = this.getElementsByTagName('object'); + for (i = co.length; i--;) co[i].outerHTML = to[i].outerHTML; + } + /*</ltIE9>*/ + return document.id(clone); + } + +}); + +[Element, Window, Document].invoke('implement', { + + addListener: function(type, fn){ + if (window.attachEvent && !window.addEventListener){ + collected[Slick.uidOf(this)] = this; + } + if (this.addEventListener) this.addEventListener(type, fn, !!arguments[2]); + else this.attachEvent('on' + type, fn); + return this; + }, + + removeListener: function(type, fn){ + if (this.removeEventListener) this.removeEventListener(type, fn, !!arguments[2]); + else this.detachEvent('on' + type, fn); + return this; + }, + + retrieve: function(property, dflt){ + var storage = get(Slick.uidOf(this)), prop = storage[property]; + if (dflt != null && prop == null) prop = storage[property] = dflt; + return prop != null ? prop : null; + }, + + store: function(property, value){ + var storage = get(Slick.uidOf(this)); + storage[property] = value; + return this; + }, + + eliminate: function(property){ + var storage = get(Slick.uidOf(this)); + delete storage[property]; + return this; + } + +}); + +/*<ltIE9>*/ +if (window.attachEvent && !window.addEventListener){ + var gc = function(){ + Object.each(collected, clean); + if (window.CollectGarbage) CollectGarbage(); + window.removeListener('unload', gc); + } + window.addListener('unload', gc); +} +/*</ltIE9>*/ + +Element.Properties = {}; + + + +Element.Properties.style = { + + set: function(style){ + this.style.cssText = style; + }, + + get: function(){ + return this.style.cssText; + }, + + erase: function(){ + this.style.cssText = ''; + } + +}; + +Element.Properties.tag = { + + get: function(){ + return this.tagName.toLowerCase(); + } + +}; + +Element.Properties.html = { + + set: function(html){ + if (html == null) html = ''; + else if (typeOf(html) == 'array') html = html.join(''); + + /*<ltIE9>*/ + if (this.styleSheet && !canChangeStyleHTML) this.styleSheet.cssText = html; + else /*</ltIE9>*/this.innerHTML = html; + }, + erase: function(){ + this.set('html', ''); + } + +}; + +var supportsHTML5Elements = true, supportsTableInnerHTML = true, supportsTRInnerHTML = true; + +/*<ltIE9>*/ +// technique by jdbarlett - http://jdbartlett.com/innershiv/ +var div = document.createElement('div'); +div.innerHTML = '<nav></nav>'; +supportsHTML5Elements = (div.childNodes.length == 1); +if (!supportsHTML5Elements){ + var tags = 'abbr article aside audio canvas datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video'.split(' '), + fragment = document.createDocumentFragment(), l = tags.length; + while (l--) fragment.createElement(tags[l]); +} +div = null; +/*</ltIE9>*/ + +/*<IE>*/ +supportsTableInnerHTML = Function.attempt(function(){ + var table = document.createElement('table'); + table.innerHTML = '<tr><td></td></tr>'; + return true; +}); + +/*<ltFF4>*/ +var tr = document.createElement('tr'), html = '<td></td>'; +tr.innerHTML = html; +supportsTRInnerHTML = (tr.innerHTML == html); +tr = null; +/*</ltFF4>*/ + +if (!supportsTableInnerHTML || !supportsTRInnerHTML || !supportsHTML5Elements){ + + Element.Properties.html.set = (function(set){ + + var translations = { + table: [1, '<table>', '</table>'], + select: [1, '<select>', '</select>'], + tbody: [2, '<table><tbody>', '</tbody></table>'], + tr: [3, '<table><tbody><tr>', '</tr></tbody></table>'] + }; + + translations.thead = translations.tfoot = translations.tbody; + + return function(html){ + + /*<ltIE9>*/ + if (this.styleSheet) return set.call(this, html); + /*</ltIE9>*/ + var wrap = translations[this.get('tag')]; + if (!wrap && !supportsHTML5Elements) wrap = [0, '', '']; + if (!wrap) return set.call(this, html); + + var level = wrap[0], wrapper = document.createElement('div'), target = wrapper; + if (!supportsHTML5Elements) fragment.appendChild(wrapper); + wrapper.innerHTML = [wrap[1], html, wrap[2]].flatten().join(''); + while (level--) target = target.firstChild; + this.empty().adopt(target.childNodes); + if (!supportsHTML5Elements) fragment.removeChild(wrapper); + wrapper = null; + }; + + })(Element.Properties.html.set); +} +/*</IE>*/ + +/*<ltIE9>*/ +var testForm = document.createElement('form'); +testForm.innerHTML = '<select><option>s</option></select>'; + +if (testForm.firstChild.value != 's') Element.Properties.value = { + + set: function(value){ + var tag = this.get('tag'); + if (tag != 'select') return this.setProperty('value', value); + var options = this.getElements('option'); + value = String(value); + for (var i = 0; i < options.length; i++){ + var option = options[i], + attr = option.getAttributeNode('value'), + optionValue = (attr && attr.specified) ? option.value : option.get('text'); + if (optionValue === value) return option.selected = true; + } + }, + + get: function(){ + var option = this, tag = option.get('tag'); + + if (tag != 'select' && tag != 'option') return this.getProperty('value'); + + if (tag == 'select' && !(option = option.getSelected()[0])) return ''; + + var attr = option.getAttributeNode('value'); + return (attr && attr.specified) ? option.value : option.get('text'); + } + +}; +testForm = null; +/*</ltIE9>*/ + +/*<IE>*/ +if (document.createElement('div').getAttributeNode('id')) Element.Properties.id = { + set: function(id){ + this.id = this.getAttributeNode('id').value = id; + }, + get: function(){ + return this.id || null; + }, + erase: function(){ + this.id = this.getAttributeNode('id').value = ''; + } +}; +/*</IE>*/ + +})(); + +/* +--- + +name: Event + +description: Contains the Event Type, to make the event object cross-browser. + +license: MIT-style license. + +requires: [Window, Document, Array, Function, String, Object] + +provides: Event + +... +*/ + +(function(){ + +var _keys = {}; +var normalizeWheelSpeed = function(event){ + var normalized; + if (event.wheelDelta){ + normalized = event.wheelDelta % 120 == 0 ? event.wheelDelta / 120 : event.wheelDelta / 12; + } else { + var rawAmount = event.deltaY || event.detail || 0; + normalized = -(rawAmount % 3 == 0 ? rawAmount / 3 : rawAmount * 10); + } + return normalized; +} + +var DOMEvent = this.DOMEvent = new Type('DOMEvent', function(event, win){ + if (!win) win = window; + event = event || win.event; + if (event.$extended) return event; + this.event = event; + this.$extended = true; + this.shift = event.shiftKey; + this.control = event.ctrlKey; + this.alt = event.altKey; + this.meta = event.metaKey; + var type = this.type = event.type; + var target = event.target || event.srcElement; + while (target && target.nodeType == 3) target = target.parentNode; + this.target = document.id(target); + + if (type.indexOf('key') == 0){ + var code = this.code = (event.which || event.keyCode); + this.key = _keys[code]; + if (type == 'keydown' || type == 'keyup'){ + if (code > 111 && code < 124) this.key = 'f' + (code - 111); + else if (code > 95 && code < 106) this.key = code - 96; + } + if (this.key == null) this.key = String.fromCharCode(code).toLowerCase(); + } else if (type == 'click' || type == 'dblclick' || type == 'contextmenu' || type == 'wheel' || type == 'DOMMouseScroll' || type.indexOf('mouse') == 0){ + var doc = win.document; + doc = (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body; + this.page = { + x: (event.pageX != null) ? event.pageX : event.clientX + doc.scrollLeft, + y: (event.pageY != null) ? event.pageY : event.clientY + doc.scrollTop + }; + this.client = { + x: (event.pageX != null) ? event.pageX - win.pageXOffset : event.clientX, + y: (event.pageY != null) ? event.pageY - win.pageYOffset : event.clientY + }; + if (type == 'DOMMouseScroll' || type == 'wheel' || type == 'mousewheel') this.wheel = normalizeWheelSpeed(event); + this.rightClick = (event.which == 3 || event.button == 2); + if (type == 'mouseover' || type == 'mouseout'){ + var related = event.relatedTarget || event[(type == 'mouseover' ? 'from' : 'to') + 'Element']; + while (related && related.nodeType == 3) related = related.parentNode; + this.relatedTarget = document.id(related); + } + } else if (type.indexOf('touch') == 0 || type.indexOf('gesture') == 0){ + this.rotation = event.rotation; + this.scale = event.scale; + this.targetTouches = event.targetTouches; + this.changedTouches = event.changedTouches; + var touches = this.touches = event.touches; + if (touches && touches[0]){ + var touch = touches[0]; + this.page = {x: touch.pageX, y: touch.pageY}; + this.client = {x: touch.clientX, y: touch.clientY}; + } + } + + if (!this.client) this.client = {}; + if (!this.page) this.page = {}; +}); + +DOMEvent.implement({ + + stop: function(){ + return this.preventDefault().stopPropagation(); + }, + + stopPropagation: function(){ + if (this.event.stopPropagation) this.event.stopPropagation(); + else this.event.cancelBubble = true; + return this; + }, + + preventDefault: function(){ + if (this.event.preventDefault) this.event.preventDefault(); + else this.event.returnValue = false; + return this; + } + +}); + +DOMEvent.defineKey = function(code, key){ + _keys[code] = key; + return this; +}; + +DOMEvent.defineKeys = DOMEvent.defineKey.overloadSetter(true); + +DOMEvent.defineKeys({ + '38': 'up', '40': 'down', '37': 'left', '39': 'right', + '27': 'esc', '32': 'space', '8': 'backspace', '9': 'tab', + '46': 'delete', '13': 'enter' +}); + +})(); + + + + + +/* +--- + +name: Element.Event + +description: Contains Element methods for dealing with events. This file also includes mouseenter and mouseleave custom Element Events, if necessary. + +license: MIT-style license. + +requires: [Element, Event] + +provides: Element.Event + +... +*/ + +(function(){ + +Element.Properties.events = {set: function(events){ + this.addEvents(events); +}}; + +[Element, Window, Document].invoke('implement', { + + addEvent: function(type, fn){ + var events = this.retrieve('events', {}); + if (!events[type]) events[type] = {keys: [], values: []}; + if (events[type].keys.contains(fn)) return this; + events[type].keys.push(fn); + var realType = type, + custom = Element.Events[type], + condition = fn, + self = this; + if (custom){ + if (custom.onAdd) custom.onAdd.call(this, fn, type); + if (custom.condition){ + condition = function(event){ + if (custom.condition.call(this, event, type)) return fn.call(this, event); + return true; + }; + } + if (custom.base) realType = Function.from(custom.base).call(this, type); + } + var defn = function(){ + return fn.call(self); + }; + var nativeEvent = Element.NativeEvents[realType]; + if (nativeEvent){ + if (nativeEvent == 2){ + defn = function(event){ + event = new DOMEvent(event, self.getWindow()); + if (condition.call(self, event) === false) event.stop(); + }; + } + this.addListener(realType, defn, arguments[2]); + } + events[type].values.push(defn); + return this; + }, + + removeEvent: function(type, fn){ + var events = this.retrieve('events'); + if (!events || !events[type]) return this; + var list = events[type]; + var index = list.keys.indexOf(fn); + if (index == -1) return this; + var value = list.values[index]; + delete list.keys[index]; + delete list.values[index]; + var custom = Element.Events[type]; + if (custom){ + if (custom.onRemove) custom.onRemove.call(this, fn, type); + if (custom.base) type = Function.from(custom.base).call(this, type); + } + return (Element.NativeEvents[type]) ? this.removeListener(type, value, arguments[2]) : this; + }, + + addEvents: function(events){ + for (var event in events) this.addEvent(event, events[event]); + return this; + }, + + removeEvents: function(events){ + var type; + if (typeOf(events) == 'object'){ + for (type in events) this.removeEvent(type, events[type]); + return this; + } + var attached = this.retrieve('events'); + if (!attached) return this; + if (!events){ + for (type in attached) this.removeEvents(type); + this.eliminate('events'); + } else if (attached[events]){ + attached[events].keys.each(function(fn){ + this.removeEvent(events, fn); + }, this); + delete attached[events]; + } + return this; + }, + + fireEvent: function(type, args, delay){ + var events = this.retrieve('events'); + if (!events || !events[type]) return this; + args = Array.from(args); + + events[type].keys.each(function(fn){ + if (delay) fn.delay(delay, this, args); + else fn.apply(this, args); + }, this); + return this; + }, + + cloneEvents: function(from, type){ + from = document.id(from); + var events = from.retrieve('events'); + if (!events) return this; + if (!type){ + for (var eventType in events) this.cloneEvents(from, eventType); + } else if (events[type]){ + events[type].keys.each(function(fn){ + this.addEvent(type, fn); + }, this); + } + return this; + } + +}); + +Element.NativeEvents = { + click: 2, dblclick: 2, mouseup: 2, mousedown: 2, contextmenu: 2, //mouse buttons + wheel: 2, mousewheel: 2, DOMMouseScroll: 2, //mouse wheel + mouseover: 2, mouseout: 2, mousemove: 2, selectstart: 2, selectend: 2, //mouse movement + keydown: 2, keypress: 2, keyup: 2, //keyboard + orientationchange: 2, // mobile + touchstart: 2, touchmove: 2, touchend: 2, touchcancel: 2, // touch + gesturestart: 2, gesturechange: 2, gestureend: 2, // gesture + focus: 2, blur: 2, change: 2, reset: 2, select: 2, submit: 2, paste: 2, input: 2, //form elements + load: 2, unload: 1, beforeunload: 2, resize: 1, move: 1, DOMContentLoaded: 1, readystatechange: 1, //window + hashchange: 1, popstate: 2, // history + error: 1, abort: 1, scroll: 1, message: 2 //misc +}; + +Element.Events = { + mousewheel: { + base: 'onwheel' in document ? 'wheel' : 'onmousewheel' in document ? 'mousewheel' : 'DOMMouseScroll' + } +}; + +var check = function(event){ + var related = event.relatedTarget; + if (related == null) return true; + if (!related) return false; + return (related != this && related.prefix != 'xul' && typeOf(this) != 'document' && !this.contains(related)); +}; + +if ('onmouseenter' in document.documentElement){ + Element.NativeEvents.mouseenter = Element.NativeEvents.mouseleave = 2; + Element.MouseenterCheck = check; +} else { + Element.Events.mouseenter = { + base: 'mouseover', + condition: check + }; + + Element.Events.mouseleave = { + base: 'mouseout', + condition: check + }; +} + +/*<ltIE9>*/ +if (!window.addEventListener){ + Element.NativeEvents.propertychange = 2; + Element.Events.change = { + base: function(){ + var type = this.type; + return (this.get('tag') == 'input' && (type == 'radio' || type == 'checkbox')) ? 'propertychange' : 'change'; + }, + condition: function(event){ + return event.type != 'propertychange' || event.event.propertyName == 'checked'; + } + }; +} +/*</ltIE9>*/ + + + +})(); + +/* +--- + +name: Element.Delegation + +description: Extends the Element native object to include the delegate method for more efficient event management. + +license: MIT-style license. + +requires: [Element.Event] + +provides: [Element.Delegation] + +... +*/ + +(function(){ + +var eventListenerSupport = !!window.addEventListener; + +Element.NativeEvents.focusin = Element.NativeEvents.focusout = 2; + +var bubbleUp = function(self, match, fn, event, target){ + while (target && target != self){ + if (match(target, event)) return fn.call(target, event, target); + target = document.id(target.parentNode); + } +}; + +var map = { + mouseenter: { + base: 'mouseover', + condition: Element.MouseenterCheck + }, + mouseleave: { + base: 'mouseout', + condition: Element.MouseenterCheck + }, + focus: { + base: 'focus' + (eventListenerSupport ? '' : 'in'), + capture: true + }, + blur: { + base: eventListenerSupport ? 'blur' : 'focusout', + capture: true + } +}; + +/*<ltIE9>*/ +var _key = '$delegation:'; +var formObserver = function(type){ + + return { + + base: 'focusin', + + remove: function(self, uid){ + var list = self.retrieve(_key + type + 'listeners', {})[uid]; + if (list && list.forms) for (var i = list.forms.length; i--;){ + // the form may have been destroyed, so it won't have the + // removeEvent method anymore. In that case the event was + // removed as well. + if (list.forms[i].removeEvent) list.forms[i].removeEvent(type, list.fns[i]); + } + }, + + listen: function(self, match, fn, event, target, uid){ + var form = (target.get('tag') == 'form') ? target : event.target.getParent('form'); + if (!form) return; + + var listeners = self.retrieve(_key + type + 'listeners', {}), + listener = listeners[uid] || {forms: [], fns: []}, + forms = listener.forms, fns = listener.fns; + + if (forms.indexOf(form) != -1) return; + forms.push(form); + + var _fn = function(event){ + bubbleUp(self, match, fn, event, target); + }; + form.addEvent(type, _fn); + fns.push(_fn); + + listeners[uid] = listener; + self.store(_key + type + 'listeners', listeners); + } + }; +}; + +var inputObserver = function(type){ + return { + base: 'focusin', + listen: function(self, match, fn, event, target){ + var events = {blur: function(){ + this.removeEvents(events); + }}; + events[type] = function(event){ + bubbleUp(self, match, fn, event, target); + }; + event.target.addEvents(events); + } + }; +}; + +if (!eventListenerSupport) Object.append(map, { + submit: formObserver('submit'), + reset: formObserver('reset'), + change: inputObserver('change'), + select: inputObserver('select') +}); +/*</ltIE9>*/ + +var proto = Element.prototype, + addEvent = proto.addEvent, + removeEvent = proto.removeEvent; + +var relay = function(old, method){ + return function(type, fn, useCapture){ + if (type.indexOf(':relay') == -1) return old.call(this, type, fn, useCapture); + var parsed = Slick.parse(type).expressions[0][0]; + if (parsed.pseudos[0].key != 'relay') return old.call(this, type, fn, useCapture); + var newType = parsed.tag; + parsed.pseudos.slice(1).each(function(pseudo){ + newType += ':' + pseudo.key + (pseudo.value ? '(' + pseudo.value + ')' : ''); + }); + old.call(this, type, fn); + return method.call(this, newType, parsed.pseudos[0].value, fn); + }; +}; + +var delegation = { + + addEvent: function(type, match, fn){ + var storage = this.retrieve('$delegates', {}), stored = storage[type]; + if (stored) for (var _uid in stored){ + if (stored[_uid].fn == fn && stored[_uid].match == match) return this; + } + + var _type = type, _match = match, _fn = fn, _map = map[type] || {}; + type = _map.base || _type; + + match = function(target){ + return Slick.match(target, _match); + }; + + var elementEvent = Element.Events[_type]; + if (_map.condition || elementEvent && elementEvent.condition){ + var __match = match, condition = _map.condition || elementEvent.condition; + match = function(target, event){ + return __match(target, event) && condition.call(target, event, type); + }; + } + + var self = this, uid = String.uniqueID(); + var delegator = _map.listen ? function(event, target){ + if (!target && event && event.target) target = event.target; + if (target) _map.listen(self, match, fn, event, target, uid); + } : function(event, target){ + if (!target && event && event.target) target = event.target; + if (target) bubbleUp(self, match, fn, event, target); + }; + + if (!stored) stored = {}; + stored[uid] = { + match: _match, + fn: _fn, + delegator: delegator + }; + storage[_type] = stored; + return addEvent.call(this, type, delegator, _map.capture); + }, + + removeEvent: function(type, match, fn, _uid){ + var storage = this.retrieve('$delegates', {}), stored = storage[type]; + if (!stored) return this; + + if (_uid){ + var _type = type, delegator = stored[_uid].delegator, _map = map[type] || {}; + type = _map.base || _type; + if (_map.remove) _map.remove(this, _uid); + delete stored[_uid]; + storage[_type] = stored; + return removeEvent.call(this, type, delegator, _map.capture); + } + + var __uid, s; + if (fn) for (__uid in stored){ + s = stored[__uid]; + if (s.match == match && s.fn == fn) return delegation.removeEvent.call(this, type, match, fn, __uid); + } else for (__uid in stored){ + s = stored[__uid]; + if (s.match == match) delegation.removeEvent.call(this, type, match, s.fn, __uid); + } + return this; + } + +}; + +[Element, Window, Document].invoke('implement', { + addEvent: relay(addEvent, delegation.addEvent), + removeEvent: relay(removeEvent, delegation.removeEvent) +}); + +})(); + +/* +--- + +name: Element.Style + +description: Contains methods for interacting with the styles of Elements in a fashionable way. + +license: MIT-style license. + +requires: Element + +provides: Element.Style + +... +*/ + +(function(){ + +var html = document.html, el; + +//<ltIE9> +// Check for oldIE, which does not remove styles when they're set to null +el = document.createElement('div'); +el.style.color = 'red'; +el.style.color = null; +var doesNotRemoveStyles = el.style.color == 'red'; + +// check for oldIE, which returns border* shorthand styles in the wrong order (color-width-style instead of width-style-color) +var border = '1px solid #123abc'; +el.style.border = border; +var returnsBordersInWrongOrder = el.style.border != border; +el = null; +//</ltIE9> + +var hasGetComputedStyle = !!window.getComputedStyle, + supportBorderRadius = document.createElement('div').style.borderRadius != null; + +Element.Properties.styles = {set: function(styles){ + this.setStyles(styles); +}}; + +var hasOpacity = (html.style.opacity != null), + hasFilter = (html.style.filter != null), + reAlpha = /alpha\(opacity=([\d.]+)\)/i; + +var setVisibility = function(element, opacity){ + element.store('$opacity', opacity); + element.style.visibility = opacity > 0 || opacity == null ? 'visible' : 'hidden'; +}; + +//<ltIE9> +var setFilter = function(element, regexp, value){ + var style = element.style, + filter = style.filter || element.getComputedStyle('filter') || ''; + style.filter = (regexp.test(filter) ? filter.replace(regexp, value) : filter + ' ' + value).trim(); + if (!style.filter) style.removeAttribute('filter'); +}; +//</ltIE9> + +var setOpacity = (hasOpacity ? function(element, opacity){ + element.style.opacity = opacity; +} : (hasFilter ? function(element, opacity){ + if (!element.currentStyle || !element.currentStyle.hasLayout) element.style.zoom = 1; + if (opacity == null || opacity == 1){ + setFilter(element, reAlpha, ''); + if (opacity == 1 && getOpacity(element) != 1) setFilter(element, reAlpha, 'alpha(opacity=100)'); + } else { + setFilter(element, reAlpha, 'alpha(opacity=' + (opacity * 100).limit(0, 100).round() + ')'); + } +} : setVisibility)); + +var getOpacity = (hasOpacity ? function(element){ + var opacity = element.style.opacity || element.getComputedStyle('opacity'); + return (opacity == '') ? 1 : opacity.toFloat(); +} : (hasFilter ? function(element){ + var filter = (element.style.filter || element.getComputedStyle('filter')), + opacity; + if (filter) opacity = filter.match(reAlpha); + return (opacity == null || filter == null) ? 1 : (opacity[1] / 100); +} : function(element){ + var opacity = element.retrieve('$opacity'); + if (opacity == null) opacity = (element.style.visibility == 'hidden' ? 0 : 1); + return opacity; +})); + +var floatName = (html.style.cssFloat == null) ? 'styleFloat' : 'cssFloat', + namedPositions = {left: '0%', top: '0%', center: '50%', right: '100%', bottom: '100%'}, + hasBackgroundPositionXY = (html.style.backgroundPositionX != null); + +//<ltIE9> +var removeStyle = function(style, property){ + if (property == 'backgroundPosition'){ + style.removeAttribute(property + 'X'); + property += 'Y'; + } + style.removeAttribute(property); +}; +//</ltIE9> + +Element.implement({ + + getComputedStyle: function(property){ + if (!hasGetComputedStyle && this.currentStyle) return this.currentStyle[property.camelCase()]; + var defaultView = Element.getDocument(this).defaultView, + computed = defaultView ? defaultView.getComputedStyle(this, null) : null; + return (computed) ? computed.getPropertyValue((property == floatName) ? 'float' : property.hyphenate()) : ''; + }, + + setStyle: function(property, value){ + if (property == 'opacity'){ + if (value != null) value = parseFloat(value); + setOpacity(this, value); + return this; + } + property = (property == 'float' ? floatName : property).camelCase(); + if (typeOf(value) != 'string'){ + var map = (Element.Styles[property] || '@').split(' '); + value = Array.from(value).map(function(val, i){ + if (!map[i]) return ''; + return (typeOf(val) == 'number') ? map[i].replace('@', Math.round(val)) : val; + }).join(' '); + } else if (value == String(Number(value))){ + value = Math.round(value); + } + this.style[property] = value; + //<ltIE9> + if ((value == '' || value == null) && doesNotRemoveStyles && this.style.removeAttribute){ + removeStyle(this.style, property); + } + //</ltIE9> + return this; + }, + + getStyle: function(property){ + if (property == 'opacity') return getOpacity(this); + property = (property == 'float' ? floatName : property).camelCase(); + if (supportBorderRadius && property.indexOf('borderRadius') != -1){ + return ['borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomRightRadius', 'borderBottomLeftRadius'].map(function(corner){ + return this.style[corner] || '0px'; + }, this).join(' '); + } + var result = this.style[property]; + if (!result || property == 'zIndex'){ + if (Element.ShortStyles.hasOwnProperty(property)){ + result = []; + for (var s in Element.ShortStyles[property]) result.push(this.getStyle(s)); + return result.join(' '); + } + result = this.getComputedStyle(property); + } + if (hasBackgroundPositionXY && /^backgroundPosition[XY]?$/.test(property)){ + return result.replace(/(top|right|bottom|left)/g, function(position){ + return namedPositions[position]; + }) || '0px'; + } + if (!result && property == 'backgroundPosition') return '0px 0px'; + if (result){ + result = String(result); + var color = result.match(/rgba?\([\d\s,]+\)/); + if (color) result = result.replace(color[0], color[0].rgbToHex()); + } + if (!hasGetComputedStyle && !this.style[property]){ + if ((/^(height|width)$/).test(property) && !(/px$/.test(result))){ + var values = (property == 'width') ? ['left', 'right'] : ['top', 'bottom'], size = 0; + values.each(function(value){ + size += this.getStyle('border-' + value + '-width').toInt() + this.getStyle('padding-' + value).toInt(); + }, this); + return this['offset' + property.capitalize()] - size + 'px'; + } + if ((/^border(.+)Width|margin|padding/).test(property) && isNaN(parseFloat(result))){ + return '0px'; + } + } + //<ltIE9> + if (returnsBordersInWrongOrder && /^border(Top|Right|Bottom|Left)?$/.test(property) && /^#/.test(result)){ + return result.replace(/^(.+)\s(.+)\s(.+)$/, '$2 $3 $1'); + } + //</ltIE9> + + return result; + }, + + setStyles: function(styles){ + for (var style in styles) this.setStyle(style, styles[style]); + return this; + }, + + getStyles: function(){ + var result = {}; + Array.flatten(arguments).each(function(key){ + result[key] = this.getStyle(key); + }, this); + return result; + } + +}); + +Element.Styles = { + left: '@px', top: '@px', bottom: '@px', right: '@px', + width: '@px', height: '@px', maxWidth: '@px', maxHeight: '@px', minWidth: '@px', minHeight: '@px', + backgroundColor: 'rgb(@, @, @)', backgroundSize: '@px', backgroundPosition: '@px @px', color: 'rgb(@, @, @)', + fontSize: '@px', letterSpacing: '@px', lineHeight: '@px', clip: 'rect(@px @px @px @px)', + margin: '@px @px @px @px', padding: '@px @px @px @px', border: '@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)', + borderWidth: '@px @px @px @px', borderStyle: '@ @ @ @', borderColor: 'rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)', + zIndex: '@', 'zoom': '@', fontWeight: '@', textIndent: '@px', opacity: '@', borderRadius: '@px @px @px @px' +}; + + + + + +Element.ShortStyles = {margin: {}, padding: {}, border: {}, borderWidth: {}, borderStyle: {}, borderColor: {}}; + +['Top', 'Right', 'Bottom', 'Left'].each(function(direction){ + var Short = Element.ShortStyles; + var All = Element.Styles; + ['margin', 'padding'].each(function(style){ + var sd = style + direction; + Short[style][sd] = All[sd] = '@px'; + }); + var bd = 'border' + direction; + Short.border[bd] = All[bd] = '@px @ rgb(@, @, @)'; + var bdw = bd + 'Width', bds = bd + 'Style', bdc = bd + 'Color'; + Short[bd] = {}; + Short.borderWidth[bdw] = Short[bd][bdw] = All[bdw] = '@px'; + Short.borderStyle[bds] = Short[bd][bds] = All[bds] = '@'; + Short.borderColor[bdc] = Short[bd][bdc] = All[bdc] = 'rgb(@, @, @)'; +}); + +if (hasBackgroundPositionXY) Element.ShortStyles.backgroundPosition = {backgroundPositionX: '@', backgroundPositionY: '@'}; +})(); + +/* +--- + +name: Element.Dimensions + +description: Contains methods to work with size, scroll, or positioning of Elements and the window object. + +license: MIT-style license. + +credits: + - Element positioning based on the [qooxdoo](http://qooxdoo.org/) code and smart browser fixes, [LGPL License](http://www.gnu.org/licenses/lgpl.html). + - Viewport dimensions based on [YUI](http://developer.yahoo.com/yui/) code, [BSD License](http://developer.yahoo.com/yui/license.html). + +requires: [Element, Element.Style] + +provides: [Element.Dimensions] + +... +*/ + +(function(){ + +var element = document.createElement('div'), + child = document.createElement('div'); +element.style.height = '0'; +element.appendChild(child); +var brokenOffsetParent = (child.offsetParent === element); +element = child = null; + +var heightComponents = ['height', 'paddingTop', 'paddingBottom', 'borderTopWidth', 'borderBottomWidth'], + widthComponents = ['width', 'paddingLeft', 'paddingRight', 'borderLeftWidth', 'borderRightWidth']; + +var svgCalculateSize = function(el){ + + var gCS = window.getComputedStyle(el), + bounds = {x: 0, y: 0}; + + heightComponents.each(function(css){ + bounds.y += parseFloat(gCS[css]); + }); + widthComponents.each(function(css){ + bounds.x += parseFloat(gCS[css]); + }); + return bounds; +}; + +var isOffset = function(el){ + return styleString(el, 'position') != 'static' || isBody(el); +}; + +var isOffsetStatic = function(el){ + return isOffset(el) || (/^(?:table|td|th)$/i).test(el.tagName); +}; + +Element.implement({ + + scrollTo: function(x, y){ + if (isBody(this)){ + this.getWindow().scrollTo(x, y); + } else { + this.scrollLeft = x; + this.scrollTop = y; + } + return this; + }, + + getSize: function(){ + if (isBody(this)) return this.getWindow().getSize(); + + //<ltIE9> + // This if clause is because IE8- cannot calculate getBoundingClientRect of elements with visibility hidden. + if (!window.getComputedStyle) return {x: this.offsetWidth, y: this.offsetHeight}; + //</ltIE9> + + // This svg section under, calling `svgCalculateSize()`, can be removed when FF fixed the svg size bug. + // Bug info: https://bugzilla.mozilla.org/show_bug.cgi?id=530985 + if (this.get('tag') == 'svg') return svgCalculateSize(this); + + var bounds = this.getBoundingClientRect(); + return {x: bounds.width, y: bounds.height}; + }, + + getScrollSize: function(){ + if (isBody(this)) return this.getWindow().getScrollSize(); + return {x: this.scrollWidth, y: this.scrollHeight}; + }, + + getScroll: function(){ + if (isBody(this)) return this.getWindow().getScroll(); + return {x: this.scrollLeft, y: this.scrollTop}; + }, + + getScrolls: function(){ + var element = this.parentNode, position = {x: 0, y: 0}; + while (element && !isBody(element)){ + position.x += element.scrollLeft; + position.y += element.scrollTop; + element = element.parentNode; + } + return position; + }, + + getOffsetParent: brokenOffsetParent ? function(){ + var element = this; + if (isBody(element) || styleString(element, 'position') == 'fixed') return null; + + var isOffsetCheck = (styleString(element, 'position') == 'static') ? isOffsetStatic : isOffset; + while ((element = element.parentNode)){ + if (isOffsetCheck(element)) return element; + } + return null; + } : function(){ + var element = this; + if (isBody(element) || styleString(element, 'position') == 'fixed') return null; + + try { + return element.offsetParent; + } catch(e){} + return null; + }, + + getOffsets: function(){ + var hasGetBoundingClientRect = this.getBoundingClientRect; + + if (hasGetBoundingClientRect){ + var bound = this.getBoundingClientRect(), + html = document.id(this.getDocument().documentElement), + htmlScroll = html.getScroll(), + elemScrolls = this.getScrolls(), + isFixed = (styleString(this, 'position') == 'fixed'); + + return { + x: bound.left.toInt() + elemScrolls.x + ((isFixed) ? 0 : htmlScroll.x) - html.clientLeft, + y: bound.top.toInt() + elemScrolls.y + ((isFixed) ? 0 : htmlScroll.y) - html.clientTop + }; + } + + var element = this, position = {x: 0, y: 0}; + if (isBody(this)) return position; + + while (element && !isBody(element)){ + position.x += element.offsetLeft; + position.y += element.offsetTop; + + element = element.offsetParent; + } + + return position; + }, + + getPosition: function(relative){ + var offset = this.getOffsets(), + scroll = this.getScrolls(); + var position = { + x: offset.x - scroll.x, + y: offset.y - scroll.y + }; + + if (relative && (relative = document.id(relative))){ + var relativePosition = relative.getPosition(); + return {x: position.x - relativePosition.x - leftBorder(relative), y: position.y - relativePosition.y - topBorder(relative)}; + } + return position; + }, + + getCoordinates: function(element){ + if (isBody(this)) return this.getWindow().getCoordinates(); + var position = this.getPosition(element), + size = this.getSize(); + var obj = { + left: position.x, + top: position.y, + width: size.x, + height: size.y + }; + obj.right = obj.left + obj.width; + obj.bottom = obj.top + obj.height; + return obj; + }, + + computePosition: function(obj){ + return { + left: obj.x - styleNumber(this, 'margin-left'), + top: obj.y - styleNumber(this, 'margin-top') + }; + }, + + setPosition: function(obj){ + return this.setStyles(this.computePosition(obj)); + } + +}); + + +[Document, Window].invoke('implement', { + + getSize: function(){ + var doc = getCompatElement(this); + return {x: doc.clientWidth, y: doc.clientHeight}; + }, + + getScroll: function(){ + var win = this.getWindow(), doc = getCompatElement(this); + return {x: win.pageXOffset || doc.scrollLeft, y: win.pageYOffset || doc.scrollTop}; + }, + + getScrollSize: function(){ + var doc = getCompatElement(this), + min = this.getSize(), + body = this.getDocument().body; + + return {x: Math.max(doc.scrollWidth, body.scrollWidth, min.x), y: Math.max(doc.scrollHeight, body.scrollHeight, min.y)}; + }, + + getPosition: function(){ + return {x: 0, y: 0}; + }, + + getCoordinates: function(){ + var size = this.getSize(); + return {top: 0, left: 0, bottom: size.y, right: size.x, height: size.y, width: size.x}; + } + +}); + +// private methods + +var styleString = Element.getComputedStyle; + +function styleNumber(element, style){ + return styleString(element, style).toInt() || 0; +} + +function borderBox(element){ + return styleString(element, '-moz-box-sizing') == 'border-box'; +} + +function topBorder(element){ + return styleNumber(element, 'border-top-width'); +} + +function leftBorder(element){ + return styleNumber(element, 'border-left-width'); +} + +function isBody(element){ + return (/^(?:body|html)$/i).test(element.tagName); +} + +function getCompatElement(element){ + var doc = element.getDocument(); + return (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body; +} + +})(); + +//aliases +Element.alias({position: 'setPosition'}); //compatability + +[Window, Document, Element].invoke('implement', { + + getHeight: function(){ + return this.getSize().y; + }, + + getWidth: function(){ + return this.getSize().x; + }, + + getScrollTop: function(){ + return this.getScroll().y; + }, + + getScrollLeft: function(){ + return this.getScroll().x; + }, + + getScrollHeight: function(){ + return this.getScrollSize().y; + }, + + getScrollWidth: function(){ + return this.getScrollSize().x; + }, + + getTop: function(){ + return this.getPosition().y; + }, + + getLeft: function(){ + return this.getPosition().x; + } + +}); + +/* +--- + +name: Fx + +description: Contains the basic animation logic to be extended by all other Fx Classes. + +license: MIT-style license. + +requires: [Chain, Events, Options] + +provides: Fx + +... +*/ + +(function(){ + +var Fx = this.Fx = new Class({ + + Implements: [Chain, Events, Options], + + options: { + /* + onStart: nil, + onCancel: nil, + onComplete: nil, + */ + fps: 60, + unit: false, + duration: 500, + frames: null, + frameSkip: true, + link: 'ignore' + }, + + initialize: function(options){ + this.subject = this.subject || this; + this.setOptions(options); + }, + + getTransition: function(){ + return function(p){ + return -(Math.cos(Math.PI * p) - 1) / 2; + }; + }, + + step: function(now){ + if (this.options.frameSkip){ + var diff = (this.time != null) ? (now - this.time) : 0, frames = diff / this.frameInterval; + this.time = now; + this.frame += frames; + } else { + this.frame++; + } + + if (this.frame < this.frames){ + var delta = this.transition(this.frame / this.frames); + this.set(this.compute(this.from, this.to, delta)); + } else { + this.frame = this.frames; + this.set(this.compute(this.from, this.to, 1)); + this.stop(); + } + }, + + set: function(now){ + return now; + }, + + compute: function(from, to, delta){ + return Fx.compute(from, to, delta); + }, + + check: function(){ + if (!this.isRunning()) return true; + switch (this.options.link){ + case 'cancel': this.cancel(); return true; + case 'chain': this.chain(this.caller.pass(arguments, this)); return false; + } + return false; + }, + + start: function(from, to){ + if (!this.check(from, to)) return this; + this.from = from; + this.to = to; + this.frame = (this.options.frameSkip) ? 0 : -1; + this.time = null; + this.transition = this.getTransition(); + var frames = this.options.frames, fps = this.options.fps, duration = this.options.duration; + this.duration = Fx.Durations[duration] || duration.toInt(); + this.frameInterval = 1000 / fps; + this.frames = frames || Math.round(this.duration / this.frameInterval); + this.fireEvent('start', this.subject); + pushInstance.call(this, fps); + return this; + }, + + stop: function(){ + if (this.isRunning()){ + this.time = null; + pullInstance.call(this, this.options.fps); + if (this.frames == this.frame){ + this.fireEvent('complete', this.subject); + if (!this.callChain()) this.fireEvent('chainComplete', this.subject); + } else { + this.fireEvent('stop', this.subject); + } + } + return this; + }, + + cancel: function(){ + if (this.isRunning()){ + this.time = null; + pullInstance.call(this, this.options.fps); + this.frame = this.frames; + this.fireEvent('cancel', this.subject).clearChain(); + } + return this; + }, + + pause: function(){ + if (this.isRunning()){ + this.time = null; + pullInstance.call(this, this.options.fps); + } + return this; + }, + + resume: function(){ + if (this.isPaused()) pushInstance.call(this, this.options.fps); + return this; + }, + + isRunning: function(){ + var list = instances[this.options.fps]; + return list && list.contains(this); + }, + + isPaused: function(){ + return (this.frame < this.frames) && !this.isRunning(); + } + +}); + +Fx.compute = function(from, to, delta){ + return (to - from) * delta + from; +}; + +Fx.Durations = {'short': 250, 'normal': 500, 'long': 1000}; + +// global timers + +var instances = {}, timers = {}; + +var loop = function(){ + var now = Date.now(); + for (var i = this.length; i--;){ + var instance = this[i]; + if (instance) instance.step(now); + } +}; + +var pushInstance = function(fps){ + var list = instances[fps] || (instances[fps] = []); + list.push(this); + if (!timers[fps]) timers[fps] = loop.periodical(Math.round(1000 / fps), list); +}; + +var pullInstance = function(fps){ + var list = instances[fps]; + if (list){ + list.erase(this); + if (!list.length && timers[fps]){ + delete instances[fps]; + timers[fps] = clearInterval(timers[fps]); + } + } +}; + +})(); + +/* +--- + +name: Fx.CSS + +description: Contains the CSS animation logic. Used by Fx.Tween, Fx.Morph, Fx.Elements. + +license: MIT-style license. + +requires: [Fx, Element.Style] + +provides: Fx.CSS + +... +*/ + +Fx.CSS = new Class({ + + Extends: Fx, + + //prepares the base from/to object + + prepare: function(element, property, values){ + values = Array.from(values); + var from = values[0], to = values[1]; + if (to == null){ + to = from; + from = element.getStyle(property); + var unit = this.options.unit; + // adapted from: https://github.com/ryanmorr/fx/blob/master/fx.js#L299 + if (unit && from && typeof from == 'string' && from.slice(-unit.length) != unit && parseFloat(from) != 0){ + element.setStyle(property, to + unit); + var value = element.getComputedStyle(property); + // IE and Opera support pixelLeft or pixelWidth + if (!(/px$/.test(value))){ + value = element.style[('pixel-' + property).camelCase()]; + if (value == null){ + // adapted from Dean Edwards' http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 + var left = element.style.left; + element.style.left = to + unit; + value = element.style.pixelLeft; + element.style.left = left; + } + } + from = (to || 1) / (parseFloat(value) || 1) * (parseFloat(from) || 0); + element.setStyle(property, from + unit); + } + } + return {from: this.parse(from), to: this.parse(to)}; + }, + + //parses a value into an array + + parse: function(value){ + value = Function.from(value)(); + value = (typeof value == 'string') ? value.split(' ') : Array.from(value); + return value.map(function(val){ + val = String(val); + var found = false; + Object.each(Fx.CSS.Parsers, function(parser, key){ + if (found) return; + var parsed = parser.parse(val); + if (parsed || parsed === 0) found = {value: parsed, parser: parser}; + }); + found = found || {value: val, parser: Fx.CSS.Parsers.String}; + return found; + }); + }, + + //computes by a from and to prepared objects, using their parsers. + + compute: function(from, to, delta){ + var computed = []; + (Math.min(from.length, to.length)).times(function(i){ + computed.push({value: from[i].parser.compute(from[i].value, to[i].value, delta), parser: from[i].parser}); + }); + computed.$family = Function.from('fx:css:value'); + return computed; + }, + + //serves the value as settable + + serve: function(value, unit){ + if (typeOf(value) != 'fx:css:value') value = this.parse(value); + var returned = []; + value.each(function(bit){ + returned = returned.concat(bit.parser.serve(bit.value, unit)); + }); + return returned; + }, + + //renders the change to an element + + render: function(element, property, value, unit){ + element.setStyle(property, this.serve(value, unit)); + }, + + //searches inside the page css to find the values for a selector + + search: function(selector){ + if (Fx.CSS.Cache[selector]) return Fx.CSS.Cache[selector]; + var to = {}, selectorTest = new RegExp('^' + selector.escapeRegExp() + '$'); + + var searchStyles = function(rules){ + Array.each(rules, function(rule, i){ + if (rule.media){ + searchStyles(rule.rules || rule.cssRules); + return; + } + if (!rule.style) return; + var selectorText = (rule.selectorText) ? rule.selectorText.replace(/^\w+/, function(m){ + return m.toLowerCase(); + }) : null; + if (!selectorText || !selectorTest.test(selectorText)) return; + Object.each(Element.Styles, function(value, style){ + if (!rule.style[style] || Element.ShortStyles[style]) return; + value = String(rule.style[style]); + to[style] = ((/^rgb/).test(value)) ? value.rgbToHex() : value; + }); + }); + }; + + Array.each(document.styleSheets, function(sheet, j){ + var href = sheet.href; + if (href && href.indexOf('://') > -1 && href.indexOf(document.domain) == -1) return; + var rules = sheet.rules || sheet.cssRules; + searchStyles(rules); + }); + return Fx.CSS.Cache[selector] = to; + } + +}); + +Fx.CSS.Cache = {}; + +Fx.CSS.Parsers = { + + Color: { + parse: function(value){ + if (value.match(/^#[0-9a-f]{3,6}$/i)) return value.hexToRgb(true); + return ((value = value.match(/(\d+),\s*(\d+),\s*(\d+)/))) ? [value[1], value[2], value[3]] : false; + }, + compute: function(from, to, delta){ + return from.map(function(value, i){ + return Math.round(Fx.compute(from[i], to[i], delta)); + }); + }, + serve: function(value){ + return value.map(Number); + } + }, + + Number: { + parse: parseFloat, + compute: Fx.compute, + serve: function(value, unit){ + return (unit) ? value + unit : value; + } + }, + + String: { + parse: Function.from(false), + compute: function(zero, one){ + return one; + }, + serve: function(zero){ + return zero; + } + } + +}; + + + +/* +--- + +name: Fx.Morph + +description: Formerly Fx.Styles, effect to transition any number of CSS properties for an element using an object of rules, or CSS based selector rules. + +license: MIT-style license. + +requires: Fx.CSS + +provides: Fx.Morph + +... +*/ + +Fx.Morph = new Class({ + + Extends: Fx.CSS, + + initialize: function(element, options){ + this.element = this.subject = document.id(element); + this.parent(options); + }, + + set: function(now){ + if (typeof now == 'string') now = this.search(now); + for (var p in now) this.render(this.element, p, now[p], this.options.unit); + return this; + }, + + compute: function(from, to, delta){ + var now = {}; + for (var p in from) now[p] = this.parent(from[p], to[p], delta); + return now; + }, + + start: function(properties){ + if (!this.check(properties)) return this; + if (typeof properties == 'string') properties = this.search(properties); + var from = {}, to = {}; + for (var p in properties){ + var parsed = this.prepare(this.element, p, properties[p]); + from[p] = parsed.from; + to[p] = parsed.to; + } + return this.parent(from, to); + } + +}); + +Element.Properties.morph = { + + set: function(options){ + this.get('morph').cancel().setOptions(options); + return this; + }, + + get: function(){ + var morph = this.retrieve('morph'); + if (!morph){ + morph = new Fx.Morph(this, {link: 'cancel'}); + this.store('morph', morph); + } + return morph; + } + +}; + +Element.implement({ + + morph: function(props){ + this.get('morph').start(props); + return this; + } + +}); + +/* +--- + +name: Fx.Transitions + +description: Contains a set of advanced transitions to be used with any of the Fx Classes. + +license: MIT-style license. + +credits: + - Easing Equations by Robert Penner, <http://www.robertpenner.com/easing/>, modified and optimized to be used with MooTools. + +requires: Fx + +provides: Fx.Transitions + +... +*/ + +Fx.implement({ + + getTransition: function(){ + var trans = this.options.transition || Fx.Transitions.Sine.easeInOut; + if (typeof trans == 'string'){ + var data = trans.split(':'); + trans = Fx.Transitions; + trans = trans[data[0]] || trans[data[0].capitalize()]; + if (data[1]) trans = trans['ease' + data[1].capitalize() + (data[2] ? data[2].capitalize() : '')]; + } + return trans; + } + +}); + +Fx.Transition = function(transition, params){ + params = Array.from(params); + var easeIn = function(pos){ + return transition(pos, params); + }; + return Object.append(easeIn, { + easeIn: easeIn, + easeOut: function(pos){ + return 1 - transition(1 - pos, params); + }, + easeInOut: function(pos){ + return (pos <= 0.5 ? transition(2 * pos, params) : (2 - transition(2 * (1 - pos), params))) / 2; + } + }); +}; + +Fx.Transitions = { + + linear: function(zero){ + return zero; + } + +}; + + + +Fx.Transitions.extend = function(transitions){ + for (var transition in transitions) Fx.Transitions[transition] = new Fx.Transition(transitions[transition]); +}; + +Fx.Transitions.extend({ + + Pow: function(p, x){ + return Math.pow(p, x && x[0] || 6); + }, + + Expo: function(p){ + return Math.pow(2, 8 * (p - 1)); + }, + + Circ: function(p){ + return 1 - Math.sin(Math.acos(p)); + }, + + Sine: function(p){ + return 1 - Math.cos(p * Math.PI / 2); + }, + + Back: function(p, x){ + x = x && x[0] || 1.618; + return Math.pow(p, 2) * ((x + 1) * p - x); + }, + + Bounce: function(p){ + var value; + for (var a = 0, b = 1; 1; a += b, b /= 2){ + if (p >= (7 - 4 * a) / 11){ + value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2); + break; + } + } + return value; + }, + + Elastic: function(p, x){ + return Math.pow(2, 10 * --p) * Math.cos(20 * p * Math.PI * (x && x[0] || 1) / 3); + } + +}); + +['Quad', 'Cubic', 'Quart', 'Quint'].each(function(transition, i){ + Fx.Transitions[transition] = new Fx.Transition(function(p){ + return Math.pow(p, i + 2); + }); +}); + +/* +--- + +name: Fx.Tween + +description: Formerly Fx.Style, effect to transition any CSS property for an element. + +license: MIT-style license. + +requires: Fx.CSS + +provides: [Fx.Tween, Element.fade, Element.highlight] + +... +*/ + +Fx.Tween = new Class({ + + Extends: Fx.CSS, + + initialize: function(element, options){ + this.element = this.subject = document.id(element); + this.parent(options); + }, + + set: function(property, now){ + if (arguments.length == 1){ + now = property; + property = this.property || this.options.property; + } + this.render(this.element, property, now, this.options.unit); + return this; + }, + + start: function(property, from, to){ + if (!this.check(property, from, to)) return this; + var args = Array.flatten(arguments); + this.property = this.options.property || args.shift(); + var parsed = this.prepare(this.element, this.property, args); + return this.parent(parsed.from, parsed.to); + } + +}); + +Element.Properties.tween = { + + set: function(options){ + this.get('tween').cancel().setOptions(options); + return this; + }, + + get: function(){ + var tween = this.retrieve('tween'); + if (!tween){ + tween = new Fx.Tween(this, {link: 'cancel'}); + this.store('tween', tween); + } + return tween; + } + +}; + +Element.implement({ + + tween: function(property, from, to){ + this.get('tween').start(property, from, to); + return this; + }, + + fade: function(how){ + var fade = this.get('tween'), method, args = ['opacity'].append(arguments), toggle; + if (args[1] == null) args[1] = 'toggle'; + switch (args[1]){ + case 'in': method = 'start'; args[1] = 1; break; + case 'out': method = 'start'; args[1] = 0; break; + case 'show': method = 'set'; args[1] = 1; break; + case 'hide': method = 'set'; args[1] = 0; break; + case 'toggle': + var flag = this.retrieve('fade:flag', this.getStyle('opacity') == 1); + method = 'start'; + args[1] = flag ? 0 : 1; + this.store('fade:flag', !flag); + toggle = true; + break; + default: method = 'start'; + } + if (!toggle) this.eliminate('fade:flag'); + fade[method].apply(fade, args); + var to = args[args.length - 1]; + if (method == 'set' || to != 0) this.setStyle('visibility', to == 0 ? 'hidden' : 'visible'); + else fade.chain(function(){ + this.element.setStyle('visibility', 'hidden'); + this.callChain(); + }); + return this; + }, + + highlight: function(start, end){ + if (!end){ + end = this.retrieve('highlight:original', this.getStyle('background-color')); + end = (end == 'transparent') ? '#fff' : end; + } + var tween = this.get('tween'); + tween.start('background-color', start || '#ffff88', end).chain(function(){ + this.setStyle('background-color', this.retrieve('highlight:original')); + tween.callChain(); + }.bind(this)); + return this; + } + +}); + +/* +--- + +name: Request + +description: Powerful all purpose Request Class. Uses XMLHTTPRequest. + +license: MIT-style license. + +requires: [Object, Element, Chain, Events, Options, Browser] + +provides: Request + +... +*/ + +(function(){ + +var empty = function(){}, + progressSupport = ('onprogress' in new Browser.Request); + +var Request = this.Request = new Class({ + + Implements: [Chain, Events, Options], + + options: {/* + onRequest: function(){}, + onLoadstart: function(event, xhr){}, + onProgress: function(event, xhr){}, + onComplete: function(){}, + onCancel: function(){}, + onSuccess: function(responseText, responseXML){}, + onFailure: function(xhr){}, + onException: function(headerName, value){}, + onTimeout: function(){}, + user: '', + password: '', + withCredentials: false,*/ + url: '', + data: '', + headers: { + 'X-Requested-With': 'XMLHttpRequest', + 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' + }, + async: true, + format: false, + method: 'post', + link: 'ignore', + isSuccess: null, + emulation: true, + urlEncoded: true, + encoding: 'utf-8', + evalScripts: false, + evalResponse: false, + timeout: 0, + noCache: false + }, + + initialize: function(options){ + this.xhr = new Browser.Request(); + this.setOptions(options); + this.headers = this.options.headers; + }, + + onStateChange: function(){ + var xhr = this.xhr; + if (xhr.readyState != 4 || !this.running) return; + this.running = false; + this.status = 0; + Function.attempt(function(){ + var status = xhr.status; + this.status = (status == 1223) ? 204 : status; + }.bind(this)); + xhr.onreadystatechange = empty; + if (progressSupport) xhr.onprogress = xhr.onloadstart = empty; + if (this.timer){ + clearTimeout(this.timer); + delete this.timer; + } + + this.response = {text: this.xhr.responseText || '', xml: this.xhr.responseXML}; + if (this.options.isSuccess.call(this, this.status)) + this.success(this.response.text, this.response.xml); + else + this.failure(); + }, + + isSuccess: function(){ + var status = this.status; + return (status >= 200 && status < 300); + }, + + isRunning: function(){ + return !!this.running; + }, + + processScripts: function(text){ + if (this.options.evalResponse || (/(ecma|java)script/).test(this.getHeader('Content-type'))) return Browser.exec(text); + return text.stripScripts(this.options.evalScripts); + }, + + success: function(text, xml){ + this.onSuccess(this.processScripts(text), xml); + }, + + onSuccess: function(){ + this.fireEvent('complete', arguments).fireEvent('success', arguments).callChain(); + }, + + failure: function(){ + this.onFailure(); + }, + + onFailure: function(){ + this.fireEvent('complete').fireEvent('failure', this.xhr); + }, + + loadstart: function(event){ + this.fireEvent('loadstart', [event, this.xhr]); + }, + + progress: function(event){ + this.fireEvent('progress', [event, this.xhr]); + }, + + timeout: function(){ + this.fireEvent('timeout', this.xhr); + }, + + setHeader: function(name, value){ + this.headers[name] = value; + return this; + }, + + getHeader: function(name){ + return Function.attempt(function(){ + return this.xhr.getResponseHeader(name); + }.bind(this)); + }, + + check: function(){ + if (!this.running) return true; + switch (this.options.link){ + case 'cancel': this.cancel(); return true; + case 'chain': this.chain(this.caller.pass(arguments, this)); return false; + } + return false; + }, + + send: function(options){ + if (!this.check(options)) return this; + + this.options.isSuccess = this.options.isSuccess || this.isSuccess; + this.running = true; + + var type = typeOf(options); + if (type == 'string' || type == 'element') options = {data: options}; + + var old = this.options; + options = Object.append({data: old.data, url: old.url, method: old.method}, options); + var data = options.data, url = String(options.url), method = options.method.toLowerCase(); + + switch (typeOf(data)){ + case 'element': data = document.id(data).toQueryString(); break; + case 'object': case 'hash': data = Object.toQueryString(data); + } + + if (this.options.format){ + var format = 'format=' + this.options.format; + data = (data) ? format + '&' + data : format; + } + + if (this.options.emulation && !['get', 'post'].contains(method)){ + var _method = '_method=' + method; + data = (data) ? _method + '&' + data : _method; + method = 'post'; + } + + if (this.options.urlEncoded && ['post', 'put'].contains(method)){ + var encoding = (this.options.encoding) ? '; charset=' + this.options.encoding : ''; + this.headers['Content-type'] = 'application/x-www-form-urlencoded' + encoding; + } + + if (!url) url = document.location.pathname; + + var trimPosition = url.lastIndexOf('/'); + if (trimPosition > -1 && (trimPosition = url.indexOf('#')) > -1) url = url.substr(0, trimPosition); + + if (this.options.noCache) + url += (url.indexOf('?') > -1 ? '&' : '?') + String.uniqueID(); + + if (data && (method == 'get' || method == 'delete')){ + url += (url.indexOf('?') > -1 ? '&' : '?') + data; + data = null; + } + + var xhr = this.xhr; + if (progressSupport){ + xhr.onloadstart = this.loadstart.bind(this); + xhr.onprogress = this.progress.bind(this); + } + + xhr.open(method.toUpperCase(), url, this.options.async, this.options.user, this.options.password); + if ((this.options.withCredentials) && 'withCredentials' in xhr) xhr.withCredentials = true; + + xhr.onreadystatechange = this.onStateChange.bind(this); + + Object.each(this.headers, function(value, key){ + try { + xhr.setRequestHeader(key, value); + } catch (e){ + this.fireEvent('exception', [key, value]); + } + }, this); + + this.fireEvent('request'); + xhr.send(data); + if (!this.options.async) this.onStateChange(); + else if (this.options.timeout) this.timer = this.timeout.delay(this.options.timeout, this); + return this; + }, + + cancel: function(){ + if (!this.running) return this; + this.running = false; + var xhr = this.xhr; + xhr.abort(); + if (this.timer){ + clearTimeout(this.timer); + delete this.timer; + } + xhr.onreadystatechange = empty; + if (progressSupport) xhr.onprogress = xhr.onloadstart = empty; + this.xhr = new Browser.Request(); + this.fireEvent('cancel'); + return this; + } + +}); + +var methods = {}; +['get', 'post', 'put', 'delete', 'patch', 'head', 'GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD'].each(function(method){ + methods[method] = function(data){ + var object = { + method: method + }; + if (data != null) object.data = data; + return this.send(object); + }; +}); + +Request.implement(methods); + +Element.Properties.send = { + + set: function(options){ + var send = this.get('send').cancel(); + send.setOptions(options); + return this; + }, + + get: function(){ + var send = this.retrieve('send'); + if (!send){ + send = new Request({ + data: this, link: 'cancel', method: this.get('method') || 'post', url: this.get('action') + }); + this.store('send', send); + } + return send; + } + +}; + +Element.implement({ + + send: function(url){ + var sender = this.get('send'); + sender.send({data: this, url: url || sender.options.url}); + return this; + } + +}); + +})(); + +/* +--- + +name: Request.HTML + +description: Extends the basic Request Class with additional methods for interacting with HTML responses. + +license: MIT-style license. + +requires: [Element, Request] + +provides: Request.HTML + +... +*/ + +Request.HTML = new Class({ + + Extends: Request, + + options: { + update: false, + append: false, + evalScripts: true, + filter: false, + headers: { + Accept: 'text/html, application/xml, text/xml, */*' + } + }, + + success: function(text){ + var options = this.options, response = this.response; + + response.html = text.stripScripts(function(script){ + response.javascript = script; + }); + + var match = response.html.match(/<body[^>]*>([\s\S]*?)<\/body>/i); + if (match) response.html = match[1]; + var temp = new Element('div').set('html', response.html); + + response.tree = temp.childNodes; + response.elements = temp.getElements(options.filter || '*'); + + if (options.filter) response.tree = response.elements; + if (options.update){ + var update = document.id(options.update).empty(); + if (options.filter) update.adopt(response.elements); + else update.set('html', response.html); + } else if (options.append){ + var append = document.id(options.append); + if (options.filter) response.elements.reverse().inject(append); + else append.adopt(temp.getChildren()); + } + if (options.evalScripts) Browser.exec(response.javascript); + + this.onSuccess(response.tree, response.elements, response.html, response.javascript); + } + +}); + +Element.Properties.load = { + + set: function(options){ + var load = this.get('load').cancel(); + load.setOptions(options); + return this; + }, + + get: function(){ + var load = this.retrieve('load'); + if (!load){ + load = new Request.HTML({data: this, link: 'cancel', update: this, method: 'get'}); + this.store('load', load); + } + return load; + } + +}; + +Element.implement({ + + load: function(){ + this.get('load').send(Array.link(arguments, {data: Type.isObject, url: Type.isString})); + return this; + } + +}); + +/* +--- + +name: JSON + +description: JSON encoder and decoder. + +license: MIT-style license. + +SeeAlso: <http://www.json.org/> + +requires: [Array, String, Number, Function] + +provides: JSON + +... +*/ + +if (typeof JSON == 'undefined') this.JSON = {}; + + + +(function(){ + +var special = {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\'}; + +var escape = function(chr){ + return special[chr] || '\\u' + ('0000' + chr.charCodeAt(0).toString(16)).slice(-4); +}; + +JSON.validate = function(string){ + string = string.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'). + replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'). + replace(/(?:^|:|,)(?:\s*\[)+/g, ''); + + return (/^[\],:{}\s]*$/).test(string); +}; + +JSON.encode = JSON.stringify ? function(obj){ + return JSON.stringify(obj); +} : function(obj){ + if (obj && obj.toJSON) obj = obj.toJSON(); + + switch (typeOf(obj)){ + case 'string': + return '"' + obj.replace(/[\x00-\x1f\\"]/g, escape) + '"'; + case 'array': + return '[' + obj.map(JSON.encode).clean() + ']'; + case 'object': case 'hash': + var string = []; + Object.each(obj, function(value, key){ + var json = JSON.encode(value); + if (json) string.push(JSON.encode(key) + ':' + json); + }); + return '{' + string + '}'; + case 'number': case 'boolean': return '' + obj; + case 'null': return 'null'; + } + + return null; +}; + +JSON.secure = true; + + +JSON.decode = function(string, secure){ + if (!string || typeOf(string) != 'string') return null; + + if (secure == null) secure = JSON.secure; + if (secure){ + if (JSON.parse) return JSON.parse(string); + if (!JSON.validate(string)) throw new Error('JSON could not decode the input; security is enabled and the value is not secure.'); + } + + return eval('(' + string + ')'); +}; + +})(); + +/* +--- + +name: Request.JSON + +description: Extends the basic Request Class with additional methods for sending and receiving JSON data. + +license: MIT-style license. + +requires: [Request, JSON] + +provides: Request.JSON + +... +*/ + +Request.JSON = new Class({ + + Extends: Request, + + options: { + /*onError: function(text, error){},*/ + secure: true + }, + + initialize: function(options){ + this.parent(options); + Object.append(this.headers, { + 'Accept': 'application/json', + 'X-Request': 'JSON' + }); + }, + + success: function(text){ + var json; + try { + json = this.response.json = JSON.decode(text, this.options.secure); + } catch (error){ + this.fireEvent('error', [text, error]); + return; + } + if (json == null) this.onFailure(); + else this.onSuccess(json, text); + } + +}); + +/* +--- + +name: Cookie + +description: Class for creating, reading, and deleting browser Cookies. + +license: MIT-style license. + +credits: + - Based on the functions by Peter-Paul Koch (http://quirksmode.org). + +requires: [Options, Browser] + +provides: Cookie + +... +*/ + +var Cookie = new Class({ + + Implements: Options, + + options: { + path: '/', + domain: false, + duration: false, + secure: false, + document: document, + encode: true + }, + + initialize: function(key, options){ + this.key = key; + this.setOptions(options); + }, + + write: function(value){ + if (this.options.encode) value = encodeURIComponent(value); + if (this.options.domain) value += '; domain=' + this.options.domain; + if (this.options.path) value += '; path=' + this.options.path; + if (this.options.duration){ + var date = new Date(); + date.setTime(date.getTime() + this.options.duration * 24 * 60 * 60 * 1000); + value += '; expires=' + date.toGMTString(); + } + if (this.options.secure) value += '; secure'; + this.options.document.cookie = this.key + '=' + value; + return this; + }, + + read: function(){ + var value = this.options.document.cookie.match('(?:^|;)\\s*' + this.key.escapeRegExp() + '=([^;]*)'); + return (value) ? decodeURIComponent(value[1]) : null; + }, + + dispose: function(){ + new Cookie(this.key, Object.merge({}, this.options, {duration: -1})).write(''); + return this; + } + +}); + +Cookie.write = function(key, value, options){ + return new Cookie(key, options).write(value); +}; + +Cookie.read = function(key){ + return new Cookie(key).read(); +}; + +Cookie.dispose = function(key, options){ + return new Cookie(key, options).dispose(); +}; + +/* +--- + +name: DOMReady + +description: Contains the custom event domready. + +license: MIT-style license. + +requires: [Browser, Element, Element.Event] + +provides: [DOMReady, DomReady] + +... +*/ + +(function(window, document){ + +var ready, + loaded, + checks = [], + shouldPoll, + timer, + testElement = document.createElement('div'); + +var domready = function(){ + clearTimeout(timer); + if (!ready) { + Browser.loaded = ready = true; + document.removeListener('DOMContentLoaded', domready).removeListener('readystatechange', check); + document.fireEvent('domready'); + window.fireEvent('domready'); + } + // cleanup scope vars + document = window = testElement = null; +}; + +var check = function(){ + for (var i = checks.length; i--;) if (checks[i]()){ + domready(); + return true; + } + return false; +}; + +var poll = function(){ + clearTimeout(timer); + if (!check()) timer = setTimeout(poll, 10); +}; + +document.addListener('DOMContentLoaded', domready); + +/*<ltIE8>*/ +// doScroll technique by Diego Perini http://javascript.nwbox.com/IEContentLoaded/ +// testElement.doScroll() throws when the DOM is not ready, only in the top window +var doScrollWorks = function(){ + try { + testElement.doScroll(); + return true; + } catch (e){} + return false; +}; +// If doScroll works already, it can't be used to determine domready +// e.g. in an iframe +if (testElement.doScroll && !doScrollWorks()){ + checks.push(doScrollWorks); + shouldPoll = true; +} +/*</ltIE8>*/ + +if (document.readyState) checks.push(function(){ + var state = document.readyState; + return (state == 'loaded' || state == 'complete'); +}); + +if ('onreadystatechange' in document) document.addListener('readystatechange', check); +else shouldPoll = true; + +if (shouldPoll) poll(); + +Element.Events.domready = { + onAdd: function(fn){ + if (ready) fn.call(this); + } +}; + +// Make sure that domready fires before load +Element.Events.load = { + base: 'load', + onAdd: function(fn){ + if (loaded && this == window) fn.call(this); + }, + condition: function(){ + if (this == window){ + domready(); + delete Element.Events.load; + } + return true; + } +}; + +// This is based on the custom load event +window.addEvent('load', function(){ + loaded = true; +}); + +})(window, document); diff --git a/pyload/webui/themes/Flat/lib/MooTools/MooTools-More.js b/pyload/webui/themes/Flat/lib/MooTools/MooTools-More.js new file mode 100644 index 000000000..15a277029 --- /dev/null +++ b/pyload/webui/themes/Flat/lib/MooTools/MooTools-More.js @@ -0,0 +1,14345 @@ +/* MooTools: the javascript framework. license: MIT-style license. copyright: Copyright (c) 2006-2015 [Valerio Proietti](http://mad4milk.net/).*/ +/* +Web Build: http://mootools.net/more/builder/a3048f4bfdf603b22a69c141dbd0fca9 +*/ +/* +--- + +script: More.js + +name: More + +description: MooTools More + +license: MIT-style license + +authors: + - Guillermo Rauch + - Thomas Aylott + - Scott Kyle + - Arian Stolwijk + - Tim Wienk + - Christoph Pojer + - Aaron Newton + - Jacob Thornton + +requires: + - Core/MooTools + +provides: [MooTools.More] + +... +*/ + +MooTools.More = { + version: '1.5.1', + build: '2dd695ba957196ae4b0275a690765d6636a61ccd' +}; + +/* +--- + +script: Chain.Wait.js + +name: Chain.Wait + +description: value, Adds a method to inject pauses between chained events. + +license: MIT-style license. + +authors: + - Aaron Newton + +requires: + - Core/Chain + - Core/Element + - Core/Fx + - MooTools.More + +provides: [Chain.Wait] + +... +*/ + +(function(){ + + var wait = { + wait: function(duration){ + return this.chain(function(){ + this.callChain.delay(duration == null ? 500 : duration, this); + return this; + }.bind(this)); + } + }; + + Chain.implement(wait); + + if (this.Fx) Fx.implement(wait); + + if (this.Element && Element.implement && this.Fx){ + Element.implement({ + + chains: function(effects){ + Array.from(effects || ['tween', 'morph', 'reveal']).each(function(effect){ + effect = this.get(effect); + if (!effect) return; + effect.setOptions({ + link:'chain' + }); + }, this); + return this; + }, + + pauseFx: function(duration, effect){ + this.chains(effect).get(effect || 'tween').wait(duration); + return this; + } + + }); + } + +})(); + +/* +--- + +script: Class.Binds.js + +name: Class.Binds + +description: Automagically binds specified methods in a class to the instance of the class. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Class + - MooTools.More + +provides: [Class.Binds] + +... +*/ + +Class.Mutators.Binds = function(binds){ + if (!this.prototype.initialize) this.implement('initialize', function(){}); + return Array.from(binds).concat(this.prototype.Binds || []); +}; + +Class.Mutators.initialize = function(initialize){ + return function(){ + Array.from(this.Binds).each(function(name){ + var original = this[name]; + if (original) this[name] = original.bind(this); + }, this); + return initialize.apply(this, arguments); + }; +}; + +/* +--- + +script: Class.Occlude.js + +name: Class.Occlude + +description: Prevents a class from being applied to a DOM element twice. + +license: MIT-style license. + +authors: + - Aaron Newton + +requires: + - Core/Class + - Core/Element + - MooTools.More + +provides: [Class.Occlude] + +... +*/ + +Class.Occlude = new Class({ + + occlude: function(property, element){ + element = document.id(element || this.element); + var instance = element.retrieve(property || this.property); + if (instance && !this.occluded) + return (this.occluded = instance); + + this.occluded = false; + element.store(property || this.property, this); + return this.occluded; + } + +}); + +/* +--- + +script: Class.Refactor.js + +name: Class.Refactor + +description: Extends a class onto itself with new property, preserving any items attached to the class's namespace. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Class + - MooTools.More + +# Some modules declare themselves dependent on Class.Refactor +provides: [Class.refactor, Class.Refactor] + +... +*/ + +Class.refactor = function(original, refactors){ + + Object.each(refactors, function(item, name){ + var origin = original.prototype[name]; + origin = (origin && origin.$origin) || origin || function(){}; + original.implement(name, (typeof item == 'function') ? function(){ + var old = this.previous; + this.previous = origin; + var value = item.apply(this, arguments); + this.previous = old; + return value; + } : item); + }); + + return original; + +}; + +/* +--- + +name: Events.Pseudos + +description: Adds the functionality to add pseudo events + +license: MIT-style license + +authors: + - Arian Stolwijk + +requires: [Core/Class.Extras, Core/Slick.Parser, MooTools.More] + +provides: [Events.Pseudos] + +... +*/ + +(function(){ + +Events.Pseudos = function(pseudos, addEvent, removeEvent){ + + var storeKey = '_monitorEvents:'; + + var storageOf = function(object){ + return { + store: object.store ? function(key, value){ + object.store(storeKey + key, value); + } : function(key, value){ + (object._monitorEvents || (object._monitorEvents = {}))[key] = value; + }, + retrieve: object.retrieve ? function(key, dflt){ + return object.retrieve(storeKey + key, dflt); + } : function(key, dflt){ + if (!object._monitorEvents) return dflt; + return object._monitorEvents[key] || dflt; + } + }; + }; + + var splitType = function(type){ + if (type.indexOf(':') == -1 || !pseudos) return null; + + var parsed = Slick.parse(type).expressions[0][0], + parsedPseudos = parsed.pseudos, + l = parsedPseudos.length, + splits = []; + + while (l--){ + var pseudo = parsedPseudos[l].key, + listener = pseudos[pseudo]; + if (listener != null) splits.push({ + event: parsed.tag, + value: parsedPseudos[l].value, + pseudo: pseudo, + original: type, + listener: listener + }); + } + return splits.length ? splits : null; + }; + + return { + + addEvent: function(type, fn, internal){ + var split = splitType(type); + if (!split) return addEvent.call(this, type, fn, internal); + + var storage = storageOf(this), + events = storage.retrieve(type, []), + eventType = split[0].event, + args = Array.slice(arguments, 2), + stack = fn, + self = this; + + split.each(function(item){ + var listener = item.listener, + stackFn = stack; + if (listener == false) eventType += ':' + item.pseudo + '(' + item.value + ')'; + else stack = function(){ + listener.call(self, item, stackFn, arguments, stack); + }; + }); + + events.include({type: eventType, event: fn, monitor: stack}); + storage.store(type, events); + + if (type != eventType) addEvent.apply(this, [type, fn].concat(args)); + return addEvent.apply(this, [eventType, stack].concat(args)); + }, + + removeEvent: function(type, fn){ + var split = splitType(type); + if (!split) return removeEvent.call(this, type, fn); + + var storage = storageOf(this), + events = storage.retrieve(type); + if (!events) return this; + + var args = Array.slice(arguments, 2); + + removeEvent.apply(this, [type, fn].concat(args)); + events.each(function(monitor, i){ + if (!fn || monitor.event == fn) removeEvent.apply(this, [monitor.type, monitor.monitor].concat(args)); + delete events[i]; + }, this); + + storage.store(type, events); + return this; + } + + }; + +}; + +var pseudos = { + + once: function(split, fn, args, monitor){ + fn.apply(this, args); + this.removeEvent(split.event, monitor) + .removeEvent(split.original, fn); + }, + + throttle: function(split, fn, args){ + if (!fn._throttled){ + fn.apply(this, args); + fn._throttled = setTimeout(function(){ + fn._throttled = false; + }, split.value || 250); + } + }, + + pause: function(split, fn, args){ + clearTimeout(fn._pause); + fn._pause = fn.delay(split.value || 250, this, args); + } + +}; + +Events.definePseudo = function(key, listener){ + pseudos[key] = listener; + return this; +}; + +Events.lookupPseudo = function(key){ + return pseudos[key]; +}; + +var proto = Events.prototype; +Events.implement(Events.Pseudos(pseudos, proto.addEvent, proto.removeEvent)); + +['Request', 'Fx'].each(function(klass){ + if (this[klass]) this[klass].implement(Events.prototype); +}); + +})(); + +/* +--- + +script: Drag.js + +name: Drag + +description: The base Drag Class. Can be used to drag and resize Elements using mouse events. + +license: MIT-style license + +authors: + - Valerio Proietti + - Tom Occhinno + - Jan Kassens + +requires: + - Core/Events + - Core/Options + - Core/Element.Event + - Core/Element.Style + - Core/Element.Dimensions + - MooTools.More + +provides: [Drag] +... + +*/ + +var Drag = new Class({ + + Implements: [Events, Options], + + options: {/* + onBeforeStart: function(thisElement){}, + onStart: function(thisElement, event){}, + onSnap: function(thisElement){}, + onDrag: function(thisElement, event){}, + onCancel: function(thisElement){}, + onComplete: function(thisElement, event){},*/ + snap: 6, + unit: 'px', + grid: false, + style: true, + limit: false, + handle: false, + invert: false, + preventDefault: false, + stopPropagation: false, + compensateScroll: false, + modifiers: {x: 'left', y: 'top'} + }, + + initialize: function(){ + var params = Array.link(arguments, { + 'options': Type.isObject, + 'element': function(obj){ + return obj != null; + } + }); + + this.element = document.id(params.element); + this.document = this.element.getDocument(); + this.setOptions(params.options || {}); + var htype = typeOf(this.options.handle); + this.handles = ((htype == 'array' || htype == 'collection') ? $$(this.options.handle) : document.id(this.options.handle)) || this.element; + this.mouse = {'now': {}, 'pos': {}}; + this.value = {'start': {}, 'now': {}}; + this.offsetParent = (function(el){ + var offsetParent = el.getOffsetParent(); + var isBody = !offsetParent || (/^(?:body|html)$/i).test(offsetParent.tagName); + return isBody ? window : document.id(offsetParent); + })(this.element); + this.selection = 'selectstart' in document ? 'selectstart' : 'mousedown'; + + this.compensateScroll = {start: {}, diff: {}, last: {}}; + + if ('ondragstart' in document && !('FileReader' in window) && !Drag.ondragstartFixed){ + document.ondragstart = Function.from(false); + Drag.ondragstartFixed = true; + } + + this.bound = { + start: this.start.bind(this), + check: this.check.bind(this), + drag: this.drag.bind(this), + stop: this.stop.bind(this), + cancel: this.cancel.bind(this), + eventStop: Function.from(false), + scrollListener: this.scrollListener.bind(this) + }; + this.attach(); + }, + + attach: function(){ + this.handles.addEvent('mousedown', this.bound.start); + if (this.options.compensateScroll) this.offsetParent.addEvent('scroll', this.bound.scrollListener); + return this; + }, + + detach: function(){ + this.handles.removeEvent('mousedown', this.bound.start); + if (this.options.compensateScroll) this.offsetParent.removeEvent('scroll', this.bound.scrollListener); + return this; + }, + + scrollListener: function(){ + + if (!this.mouse.start) return; + var newScrollValue = this.offsetParent.getScroll(); + + if (this.element.getStyle('position') == 'absolute'){ + var scrollDiff = this.sumValues(newScrollValue, this.compensateScroll.last, -1); + this.mouse.now = this.sumValues(this.mouse.now, scrollDiff, 1); + } else { + this.compensateScroll.diff = this.sumValues(newScrollValue, this.compensateScroll.start, -1); + } + if (this.offsetParent != window) this.compensateScroll.diff = this.sumValues(this.compensateScroll.start, newScrollValue, -1); + this.compensateScroll.last = newScrollValue; + this.render(this.options); + }, + + sumValues: function(alpha, beta, op){ + var sum = {}, options = this.options; + for (z in options.modifiers){ + if (!options.modifiers[z]) continue; + sum[z] = alpha[z] + beta[z] * op; + } + return sum; + }, + + start: function(event){ + var options = this.options; + + if (event.rightClick) return; + + if (options.preventDefault) event.preventDefault(); + if (options.stopPropagation) event.stopPropagation(); + this.compensateScroll.start = this.compensateScroll.last = this.offsetParent.getScroll(); + this.compensateScroll.diff = {x: 0, y: 0}; + this.mouse.start = event.page; + this.fireEvent('beforeStart', this.element); + + var limit = options.limit; + this.limit = {x: [], y: []}; + + var z, coordinates, offsetParent = this.offsetParent == window ? null : this.offsetParent; + for (z in options.modifiers){ + if (!options.modifiers[z]) continue; + + var style = this.element.getStyle(options.modifiers[z]); + + // Some browsers (IE and Opera) don't always return pixels. + if (style && !style.match(/px$/)){ + if (!coordinates) coordinates = this.element.getCoordinates(offsetParent); + style = coordinates[options.modifiers[z]]; + } + + if (options.style) this.value.now[z] = (style || 0).toInt(); + else this.value.now[z] = this.element[options.modifiers[z]]; + + if (options.invert) this.value.now[z] *= -1; + + this.mouse.pos[z] = event.page[z] - this.value.now[z]; + + if (limit && limit[z]){ + var i = 2; + while (i--){ + var limitZI = limit[z][i]; + if (limitZI || limitZI === 0) this.limit[z][i] = (typeof limitZI == 'function') ? limitZI() : limitZI; + } + } + } + + if (typeOf(this.options.grid) == 'number') this.options.grid = { + x: this.options.grid, + y: this.options.grid + }; + + var events = { + mousemove: this.bound.check, + mouseup: this.bound.cancel + }; + events[this.selection] = this.bound.eventStop; + this.document.addEvents(events); + }, + + check: function(event){ + if (this.options.preventDefault) event.preventDefault(); + var distance = Math.round(Math.sqrt(Math.pow(event.page.x - this.mouse.start.x, 2) + Math.pow(event.page.y - this.mouse.start.y, 2))); + if (distance > this.options.snap){ + this.cancel(); + this.document.addEvents({ + mousemove: this.bound.drag, + mouseup: this.bound.stop + }); + this.fireEvent('start', [this.element, event]).fireEvent('snap', this.element); + } + }, + + drag: function(event){ + var options = this.options; + if (options.preventDefault) event.preventDefault(); + this.mouse.now = this.sumValues(event.page, this.compensateScroll.diff, -1); + + this.render(options); + this.fireEvent('drag', [this.element, event]); + }, + + render: function(options){ + for (var z in options.modifiers){ + if (!options.modifiers[z]) continue; + this.value.now[z] = this.mouse.now[z] - this.mouse.pos[z]; + + if (options.invert) this.value.now[z] *= -1; + if (options.limit && this.limit[z]){ + if ((this.limit[z][1] || this.limit[z][1] === 0) && (this.value.now[z] > this.limit[z][1])){ + this.value.now[z] = this.limit[z][1]; + } else if ((this.limit[z][0] || this.limit[z][0] === 0) && (this.value.now[z] < this.limit[z][0])){ + this.value.now[z] = this.limit[z][0]; + } + } + if (options.grid[z]) this.value.now[z] -= ((this.value.now[z] - (this.limit[z][0]||0)) % options.grid[z]); + if (options.style) this.element.setStyle(options.modifiers[z], this.value.now[z] + options.unit); + else this.element[options.modifiers[z]] = this.value.now[z]; + } + }, + + cancel: function(event){ + this.document.removeEvents({ + mousemove: this.bound.check, + mouseup: this.bound.cancel + }); + if (event){ + this.document.removeEvent(this.selection, this.bound.eventStop); + this.fireEvent('cancel', this.element); + } + }, + + stop: function(event){ + var events = { + mousemove: this.bound.drag, + mouseup: this.bound.stop + }; + events[this.selection] = this.bound.eventStop; + this.document.removeEvents(events); + this.mouse.start = null; + if (event) this.fireEvent('complete', [this.element, event]); + } + +}); + +Element.implement({ + + makeResizable: function(options){ + var drag = new Drag(this, Object.merge({ + modifiers: { + x: 'width', + y: 'height' + } + }, options)); + + this.store('resizer', drag); + return drag.addEvent('drag', function(){ + this.fireEvent('resize', drag); + }.bind(this)); + } + +}); + +/* +--- + +script: Drag.Move.js + +name: Drag.Move + +description: A Drag extension that provides support for the constraining of draggables to containers and droppables. + +license: MIT-style license + +authors: + - Valerio Proietti + - Tom Occhinno + - Jan Kassens + - Aaron Newton + - Scott Kyle + +requires: + - Core/Element.Dimensions + - Drag + +provides: [Drag.Move] + +... +*/ + +Drag.Move = new Class({ + + Extends: Drag, + + options: {/* + onEnter: function(thisElement, overed){}, + onLeave: function(thisElement, overed){}, + onDrop: function(thisElement, overed, event){},*/ + droppables: [], + container: false, + precalculate: false, + includeMargins: true, + checkDroppables: true + }, + + initialize: function(element, options){ + this.parent(element, options); + element = this.element; + + this.droppables = $$(this.options.droppables); + this.setContainer(this.options.container); + + if (this.options.style){ + if (this.options.modifiers.x == 'left' && this.options.modifiers.y == 'top'){ + var parent = element.getOffsetParent(), + styles = element.getStyles('left', 'top'); + if (parent && (styles.left == 'auto' || styles.top == 'auto')){ + element.setPosition(element.getPosition(parent)); + } + } + + if (element.getStyle('position') == 'static') element.setStyle('position', 'absolute'); + } + + this.addEvent('start', this.checkDroppables, true); + this.overed = null; + }, + + setContainer: function(container) { + this.container = document.id(container); + if (this.container && typeOf(this.container) != 'element'){ + this.container = document.id(this.container.getDocument().body); + } + }, + + start: function(event){ + if (this.container) this.options.limit = this.calculateLimit(); + + if (this.options.precalculate){ + this.positions = this.droppables.map(function(el){ + return el.getCoordinates(); + }); + } + + this.parent(event); + }, + + calculateLimit: function(){ + var element = this.element, + container = this.container, + + offsetParent = document.id(element.getOffsetParent()) || document.body, + containerCoordinates = container.getCoordinates(offsetParent), + elementMargin = {}, + elementBorder = {}, + containerMargin = {}, + containerBorder = {}, + offsetParentPadding = {}, + offsetScroll = offsetParent.getScroll(); + + ['top', 'right', 'bottom', 'left'].each(function(pad){ + elementMargin[pad] = element.getStyle('margin-' + pad).toInt(); + elementBorder[pad] = element.getStyle('border-' + pad).toInt(); + containerMargin[pad] = container.getStyle('margin-' + pad).toInt(); + containerBorder[pad] = container.getStyle('border-' + pad).toInt(); + offsetParentPadding[pad] = offsetParent.getStyle('padding-' + pad).toInt(); + }, this); + + var width = element.offsetWidth + elementMargin.left + elementMargin.right, + height = element.offsetHeight + elementMargin.top + elementMargin.bottom, + left = 0 + offsetScroll.x, + top = 0 + offsetScroll.y, + right = containerCoordinates.right - containerBorder.right - width + offsetScroll.x, + bottom = containerCoordinates.bottom - containerBorder.bottom - height + offsetScroll.y; + + if (this.options.includeMargins){ + left += elementMargin.left; + top += elementMargin.top; + } else { + right += elementMargin.right; + bottom += elementMargin.bottom; + } + + if (element.getStyle('position') == 'relative'){ + var coords = element.getCoordinates(offsetParent); + coords.left -= element.getStyle('left').toInt(); + coords.top -= element.getStyle('top').toInt(); + + left -= coords.left; + top -= coords.top; + if (container.getStyle('position') != 'relative'){ + left += containerBorder.left; + top += containerBorder.top; + } + right += elementMargin.left - coords.left; + bottom += elementMargin.top - coords.top; + + if (container != offsetParent){ + left += containerMargin.left + offsetParentPadding.left; + if (!offsetParentPadding.left && left < 0) left = 0; + top += offsetParent == document.body ? 0 : containerMargin.top + offsetParentPadding.top; + if (!offsetParentPadding.top && top < 0) top = 0; + } + } else { + left -= elementMargin.left; + top -= elementMargin.top; + if (container != offsetParent){ + left += containerCoordinates.left + containerBorder.left; + top += containerCoordinates.top + containerBorder.top; + } + } + + return { + x: [left, right], + y: [top, bottom] + }; + }, + + getDroppableCoordinates: function(element){ + var position = element.getCoordinates(); + if (element.getStyle('position') == 'fixed'){ + var scroll = window.getScroll(); + position.left += scroll.x; + position.right += scroll.x; + position.top += scroll.y; + position.bottom += scroll.y; + } + return position; + }, + + checkDroppables: function(){ + var overed = this.droppables.filter(function(el, i){ + el = this.positions ? this.positions[i] : this.getDroppableCoordinates(el); + var now = this.mouse.now; + return (now.x > el.left && now.x < el.right && now.y < el.bottom && now.y > el.top); + }, this).getLast(); + + if (this.overed != overed){ + if (this.overed) this.fireEvent('leave', [this.element, this.overed]); + if (overed) this.fireEvent('enter', [this.element, overed]); + this.overed = overed; + } + }, + + drag: function(event){ + this.parent(event); + if (this.options.checkDroppables && this.droppables.length) this.checkDroppables(); + }, + + stop: function(event){ + this.checkDroppables(); + this.fireEvent('drop', [this.element, this.overed, event]); + this.overed = null; + return this.parent(event); + } + +}); + +Element.implement({ + + makeDraggable: function(options){ + var drag = new Drag.Move(this, options); + this.store('dragger', drag); + return drag; + } + +}); + +/* +--- + +script: Element.Measure.js + +name: Element.Measure + +description: Extends the Element native object to include methods useful in measuring dimensions. + +credits: "Element.measure / .expose methods by Daniel Steigerwald License: MIT-style license. Copyright: Copyright (c) 2008 Daniel Steigerwald, daniel.steigerwald.cz" + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Element.Style + - Core/Element.Dimensions + - MooTools.More + +provides: [Element.Measure] + +... +*/ + +(function(){ + +var getStylesList = function(styles, planes){ + var list = []; + Object.each(planes, function(directions){ + Object.each(directions, function(edge){ + styles.each(function(style){ + list.push(style + '-' + edge + (style == 'border' ? '-width' : '')); + }); + }); + }); + return list; +}; + +var calculateEdgeSize = function(edge, styles){ + var total = 0; + Object.each(styles, function(value, style){ + if (style.test(edge)) total = total + value.toInt(); + }); + return total; +}; + +var isVisible = function(el){ + return !!(!el || el.offsetHeight || el.offsetWidth); +}; + + +Element.implement({ + + measure: function(fn){ + if (isVisible(this)) return fn.call(this); + var parent = this.getParent(), + toMeasure = []; + while (!isVisible(parent) && parent != document.body){ + toMeasure.push(parent.expose()); + parent = parent.getParent(); + } + var restore = this.expose(), + result = fn.call(this); + restore(); + toMeasure.each(function(restore){ + restore(); + }); + return result; + }, + + expose: function(){ + if (this.getStyle('display') != 'none') return function(){}; + var before = this.style.cssText; + this.setStyles({ + display: 'block', + position: 'absolute', + visibility: 'hidden' + }); + return function(){ + this.style.cssText = before; + }.bind(this); + }, + + getDimensions: function(options){ + options = Object.merge({computeSize: false}, options); + var dim = {x: 0, y: 0}; + + var getSize = function(el, options){ + return (options.computeSize) ? el.getComputedSize(options) : el.getSize(); + }; + + var parent = this.getParent('body'); + + if (parent && this.getStyle('display') == 'none'){ + dim = this.measure(function(){ + return getSize(this, options); + }); + } else if (parent){ + try { //safari sometimes crashes here, so catch it + dim = getSize(this, options); + }catch(e){} + } + + return Object.append(dim, (dim.x || dim.x === 0) ? { + width: dim.x, + height: dim.y + } : { + x: dim.width, + y: dim.height + } + ); + }, + + getComputedSize: function(options){ + + + options = Object.merge({ + styles: ['padding','border'], + planes: { + height: ['top','bottom'], + width: ['left','right'] + }, + mode: 'both' + }, options); + + var styles = {}, + size = {width: 0, height: 0}, + dimensions; + + if (options.mode == 'vertical'){ + delete size.width; + delete options.planes.width; + } else if (options.mode == 'horizontal'){ + delete size.height; + delete options.planes.height; + } + + getStylesList(options.styles, options.planes).each(function(style){ + styles[style] = this.getStyle(style).toInt(); + }, this); + + Object.each(options.planes, function(edges, plane){ + + var capitalized = plane.capitalize(), + style = this.getStyle(plane); + + if (style == 'auto' && !dimensions) dimensions = this.getDimensions(); + + style = styles[plane] = (style == 'auto') ? dimensions[plane] : style.toInt(); + size['total' + capitalized] = style; + + edges.each(function(edge){ + var edgesize = calculateEdgeSize(edge, styles); + size['computed' + edge.capitalize()] = edgesize; + size['total' + capitalized] += edgesize; + }); + + }, this); + + return Object.append(size, styles); + } + +}); + +})(); + +/* +--- + +script: Slider.js + +name: Slider + +description: Class for creating horizontal and vertical slider controls. + +license: MIT-style license + +authors: + - Valerio Proietti + +requires: + - Core/Element.Dimensions + - Core/Number + - Class.Binds + - Drag + - Element.Measure + +provides: [Slider] + +... +*/ + +var Slider = new Class({ + + Implements: [Events, Options], + + Binds: ['clickedElement', 'draggedKnob', 'scrolledElement'], + + options: {/* + onTick: function(intPosition){}, + onMove: function(){}, + onChange: function(intStep){}, + onComplete: function(strStep){},*/ + onTick: function(position){ + this.setKnobPosition(position); + }, + initialStep: 0, + snap: false, + offset: 0, + range: false, + wheel: false, + steps: 100, + mode: 'horizontal' + }, + + initialize: function(element, knob, options){ + this.setOptions(options); + options = this.options; + this.element = document.id(element); + knob = this.knob = document.id(knob); + this.previousChange = this.previousEnd = this.step = options.initialStep ? options.initialStep : options.range ? options.range[0] : 0; + + var limit = {}, + modifiers = {x: false, y: false}; + + switch (options.mode){ + case 'vertical': + this.axis = 'y'; + this.property = 'top'; + this.offset = 'offsetHeight'; + break; + case 'horizontal': + this.axis = 'x'; + this.property = 'left'; + this.offset = 'offsetWidth'; + } + + this.setSliderDimensions(); + this.setRange(options.range, null, true); + + if (knob.getStyle('position') == 'static') knob.setStyle('position', 'relative'); + knob.setStyle(this.property, -options.offset); + modifiers[this.axis] = this.property; + limit[this.axis] = [-options.offset, this.full - options.offset]; + + var dragOptions = { + snap: 0, + limit: limit, + modifiers: modifiers, + onDrag: this.draggedKnob, + onStart: this.draggedKnob, + onBeforeStart: (function(){ + this.isDragging = true; + }).bind(this), + onCancel: function(){ + this.isDragging = false; + }.bind(this), + onComplete: function(){ + this.isDragging = false; + this.draggedKnob(); + this.end(); + }.bind(this) + }; + if (options.snap) this.setSnap(dragOptions); + + this.drag = new Drag(knob, dragOptions); + if (options.initialStep != null) this.set(options.initialStep, true); + this.attach(); + }, + + attach: function(){ + this.element.addEvent('mousedown', this.clickedElement); + if (this.options.wheel) this.element.addEvent('mousewheel', this.scrolledElement); + this.drag.attach(); + return this; + }, + + detach: function(){ + this.element.removeEvent('mousedown', this.clickedElement) + .removeEvent('mousewheel', this.scrolledElement); + this.drag.detach(); + return this; + }, + + autosize: function(){ + this.setSliderDimensions() + .setKnobPosition(this.toPosition(this.step)); + this.drag.options.limit[this.axis] = [-this.options.offset, this.full - this.options.offset]; + if (this.options.snap) this.setSnap(); + return this; + }, + + setSnap: function(options){ + if (!options) options = this.drag.options; + options.grid = Math.ceil(this.stepWidth); + options.limit[this.axis][1] = this.element[this.offset]; + return this; + }, + + setKnobPosition: function(position){ + if (this.options.snap) position = this.toPosition(this.step); + this.knob.setStyle(this.property, position); + return this; + }, + + setSliderDimensions: function(){ + this.full = this.element.measure(function(){ + this.half = this.knob[this.offset] / 2; + return this.element[this.offset] - this.knob[this.offset] + (this.options.offset * 2); + }.bind(this)); + return this; + }, + + set: function(step, silently){ + if (!((this.range > 0) ^ (step < this.min))) step = this.min; + if (!((this.range > 0) ^ (step > this.max))) step = this.max; + + this.step = (step).round(this.modulus.decimalLength); + if (silently) this.checkStep().setKnobPosition(this.toPosition(this.step)); + else this.checkStep().fireEvent('tick', this.toPosition(this.step)).fireEvent('move').end(); + return this; + }, + + setRange: function(range, pos, silently){ + this.min = Array.pick([range[0], 0]); + this.max = Array.pick([range[1], this.options.steps]); + this.range = this.max - this.min; + this.steps = this.options.steps || this.full; + var stepSize = this.stepSize = Math.abs(this.range) / this.steps; + this.stepWidth = this.stepSize * this.full / Math.abs(this.range); + this.setModulus(); + + if (range) this.set(Array.pick([pos, this.step]).limit(this.min,this.max), silently); + return this; + }, + + setModulus: function(){ + var decimals = ((this.stepSize + '').split('.')[1] || []).length, + modulus = 1 + ''; + while (decimals--) modulus += '0'; + this.modulus = {multiplier: (modulus).toInt(10), decimalLength: modulus.length - 1}; + }, + + clickedElement: function(event){ + if (this.isDragging || event.target == this.knob) return; + + var dir = this.range < 0 ? -1 : 1, + position = event.page[this.axis] - this.element.getPosition()[this.axis] - this.half; + + position = position.limit(-this.options.offset, this.full - this.options.offset); + + this.step = (this.min + dir * this.toStep(position)).round(this.modulus.decimalLength); + + this.checkStep() + .fireEvent('tick', position) + .fireEvent('move') + .end(); + }, + + scrolledElement: function(event){ + var mode = (this.options.mode == 'horizontal') ? (event.wheel < 0) : (event.wheel > 0); + this.set(this.step + (mode ? -1 : 1) * this.stepSize); + event.stop(); + }, + + draggedKnob: function(){ + var dir = this.range < 0 ? -1 : 1, + position = this.drag.value.now[this.axis]; + + position = position.limit(-this.options.offset, this.full -this.options.offset); + + this.step = (this.min + dir * this.toStep(position)).round(this.modulus.decimalLength); + this.checkStep(); + this.fireEvent('move'); + }, + + checkStep: function(){ + var step = this.step; + if (this.previousChange != step){ + this.previousChange = step; + this.fireEvent('change', step); + } + return this; + }, + + end: function(){ + var step = this.step; + if (this.previousEnd !== step){ + this.previousEnd = step; + this.fireEvent('complete', step + ''); + } + return this; + }, + + toStep: function(position){ + var step = (position + this.options.offset) * this.stepSize / this.full * this.steps; + return this.options.steps ? (step - (step * this.modulus.multiplier) % (this.stepSize * this.modulus.multiplier) / this.modulus.multiplier).round(this.modulus.decimalLength) : step; + }, + + toPosition: function(step){ + return (this.full * Math.abs(this.min - step)) / (this.steps * this.stepSize) - this.options.offset || 0; + } + +}); + +/* +--- + +script: Sortables.js + +name: Sortables + +description: Class for creating a drag and drop sorting interface for lists of items. + +license: MIT-style license + +authors: + - Tom Occhino + +requires: + - Core/Fx.Morph + - Drag.Move + +provides: [Sortables] + +... +*/ + +var Sortables = new Class({ + + Implements: [Events, Options], + + options: {/* + onSort: function(element, clone){}, + onStart: function(element, clone){}, + onComplete: function(element){},*/ + opacity: 1, + clone: false, + revert: false, + handle: false, + dragOptions: {}, + unDraggableTags: ['button', 'input', 'a', 'textarea', 'select', 'option'] + }, + + initialize: function(lists, options){ + this.setOptions(options); + + this.elements = []; + this.lists = []; + this.idle = true; + + this.addLists($$(document.id(lists) || lists)); + + if (!this.options.clone) this.options.revert = false; + if (this.options.revert) this.effect = new Fx.Morph(null, Object.merge({ + duration: 250, + link: 'cancel' + }, this.options.revert)); + }, + + attach: function(){ + this.addLists(this.lists); + return this; + }, + + detach: function(){ + this.lists = this.removeLists(this.lists); + return this; + }, + + addItems: function(){ + Array.flatten(arguments).each(function(element){ + this.elements.push(element); + var start = element.retrieve('sortables:start', function(event){ + this.start.call(this, event, element); + }.bind(this)); + (this.options.handle ? element.getElement(this.options.handle) || element : element).addEvent('mousedown', start); + }, this); + return this; + }, + + addLists: function(){ + Array.flatten(arguments).each(function(list){ + this.lists.include(list); + this.addItems(list.getChildren()); + }, this); + return this; + }, + + removeItems: function(){ + return $$(Array.flatten(arguments).map(function(element){ + this.elements.erase(element); + var start = element.retrieve('sortables:start'); + (this.options.handle ? element.getElement(this.options.handle) || element : element).removeEvent('mousedown', start); + + return element; + }, this)); + }, + + removeLists: function(){ + return $$(Array.flatten(arguments).map(function(list){ + this.lists.erase(list); + this.removeItems(list.getChildren()); + + return list; + }, this)); + }, + + getDroppableCoordinates: function (element){ + var offsetParent = element.getOffsetParent(); + var position = element.getPosition(offsetParent); + var scroll = { + w: window.getScroll(), + offsetParent: offsetParent.getScroll() + }; + position.x += scroll.offsetParent.x; + position.y += scroll.offsetParent.y; + + if (offsetParent.getStyle('position') == 'fixed'){ + position.x -= scroll.w.x; + position.y -= scroll.w.y; + } + + return position; + }, + + getClone: function(event, element){ + if (!this.options.clone) return new Element(element.tagName).inject(document.body); + if (typeOf(this.options.clone) == 'function') return this.options.clone.call(this, event, element, this.list); + var clone = element.clone(true).setStyles({ + margin: 0, + position: 'absolute', + visibility: 'hidden', + width: element.getStyle('width') + }).addEvent('mousedown', function(event){ + element.fireEvent('mousedown', event); + }); + //prevent the duplicated radio inputs from unchecking the real one + if (clone.get('html').test('radio')){ + clone.getElements('input[type=radio]').each(function(input, i){ + input.set('name', 'clone_' + i); + if (input.get('checked')) element.getElements('input[type=radio]')[i].set('checked', true); + }); + } + + return clone.inject(this.list).setPosition(this.getDroppableCoordinates(this.element)); + }, + + getDroppables: function(){ + var droppables = this.list.getChildren().erase(this.clone).erase(this.element); + if (!this.options.constrain) droppables.append(this.lists).erase(this.list); + return droppables; + }, + + insert: function(dragging, element){ + var where = 'inside'; + if (this.lists.contains(element)){ + this.list = element; + this.drag.droppables = this.getDroppables(); + } else { + where = this.element.getAllPrevious().contains(element) ? 'before' : 'after'; + } + this.element.inject(element, where); + this.fireEvent('sort', [this.element, this.clone]); + }, + + start: function(event, element){ + if ( + !this.idle || + event.rightClick || + (!this.options.handle && this.options.unDraggableTags.contains(event.target.get('tag'))) + ) return; + + this.idle = false; + this.element = element; + this.opacity = element.getStyle('opacity'); + this.list = element.getParent(); + this.clone = this.getClone(event, element); + + this.drag = new Drag.Move(this.clone, Object.merge({ + + droppables: this.getDroppables() + }, this.options.dragOptions)).addEvents({ + onSnap: function(){ + event.stop(); + this.clone.setStyle('visibility', 'visible'); + this.element.setStyle('opacity', this.options.opacity || 0); + this.fireEvent('start', [this.element, this.clone]); + }.bind(this), + onEnter: this.insert.bind(this), + onCancel: this.end.bind(this), + onComplete: this.end.bind(this) + }); + + this.clone.inject(this.element, 'before'); + this.drag.start(event); + }, + + end: function(){ + this.drag.detach(); + this.element.setStyle('opacity', this.opacity); + var self = this; + if (this.effect){ + var dim = this.element.getStyles('width', 'height'), + clone = this.clone, + pos = clone.computePosition(this.getDroppableCoordinates(clone)); + + var destroy = function(){ + this.removeEvent('cancel', destroy); + clone.destroy(); + self.reset(); + }; + + this.effect.element = clone; + this.effect.start({ + top: pos.top, + left: pos.left, + width: dim.width, + height: dim.height, + opacity: 0.25 + }).addEvent('cancel', destroy).chain(destroy); + } else { + this.clone.destroy(); + self.reset(); + } + + }, + + reset: function(){ + this.idle = true; + this.fireEvent('complete', this.element); + }, + + serialize: function(){ + var params = Array.link(arguments, { + modifier: Type.isFunction, + index: function(obj){ + return obj != null; + } + }); + var serial = this.lists.map(function(list){ + return list.getChildren().map(params.modifier || function(element){ + return element.get('id'); + }, this); + }, this); + + var index = params.index; + if (this.lists.length == 1) index = 0; + return (index || index === 0) && index >= 0 && index < this.lists.length ? serial[index] : serial; + } + +}); + +/* +--- + +name: Element.Event.Pseudos + +description: Adds the functionality to add pseudo events for Elements + +license: MIT-style license + +authors: + - Arian Stolwijk + +requires: [Core/Element.Event, Core/Element.Delegation, Events.Pseudos] + +provides: [Element.Event.Pseudos, Element.Delegation.Pseudo] + +... +*/ + +(function(){ + +var pseudos = {relay: false}, + copyFromEvents = ['once', 'throttle', 'pause'], + count = copyFromEvents.length; + +while (count--) pseudos[copyFromEvents[count]] = Events.lookupPseudo(copyFromEvents[count]); + +DOMEvent.definePseudo = function(key, listener){ + pseudos[key] = listener; + return this; +}; + +var proto = Element.prototype; +[Element, Window, Document].invoke('implement', Events.Pseudos(pseudos, proto.addEvent, proto.removeEvent)); + +})(); + +/* +--- + +name: Element.Event.Pseudos.Keys + +description: Adds functionality fire events if certain keycombinations are pressed + +license: MIT-style license + +authors: + - Arian Stolwijk + +requires: [Element.Event.Pseudos] + +provides: [Element.Event.Pseudos.Keys] + +... +*/ + +(function(){ + +var keysStoreKey = '$moo:keys-pressed', + keysKeyupStoreKey = '$moo:keys-keyup'; + + +DOMEvent.definePseudo('keys', function(split, fn, args){ + + var event = args[0], + keys = [], + pressed = this.retrieve(keysStoreKey, []), + value = split.value; + + if (value != '+') keys.append(value.replace('++', function(){ + keys.push('+'); // shift++ and shift+++a + return ''; + }).split('+')); + else keys = ['+']; + + pressed.include(event.key); + + if (keys.every(function(key){ + return pressed.contains(key); + })) fn.apply(this, args); + + this.store(keysStoreKey, pressed); + + if (!this.retrieve(keysKeyupStoreKey)){ + var keyup = function(event){ + (function(){ + pressed = this.retrieve(keysStoreKey, []).erase(event.key); + this.store(keysStoreKey, pressed); + }).delay(0, this); // Fix for IE + }; + this.store(keysKeyupStoreKey, keyup).addEvent('keyup', keyup); + } + +}); + +DOMEvent.defineKeys({ + '16': 'shift', + '17': 'control', + '18': 'alt', + '20': 'capslock', + '33': 'pageup', + '34': 'pagedown', + '35': 'end', + '36': 'home', + '144': 'numlock', + '145': 'scrolllock', + '186': ';', + '187': '=', + '188': ',', + '190': '.', + '191': '/', + '192': '`', + '219': '[', + '220': '\\', + '221': ']', + '222': "'", + '107': '+', + '109': '-', // subtract + '189': '-' // dash +}) + +})(); + +/* +--- + +script: String.Extras.js + +name: String.Extras + +description: Extends the String native object to include methods useful in managing various kinds of strings (query strings, urls, html, etc). + +license: MIT-style license + +authors: + - Aaron Newton + - Guillermo Rauch + - Christopher Pitt + +requires: + - Core/String + - Core/Array + - MooTools.More + +provides: [String.Extras] + +... +*/ + +(function(){ + +var special = { + 'a': /[à áâãÀåÄÄ
]/g, + 'A': /[ÃÃÃÃÃÃ
ÄÄ]/g, + 'c': /[ÄÄç]/g, + 'C': /[ÄÄÃ]/g, + 'd': /[ÄÄ]/g, + 'D': /[ÄÃ]/g, + 'e': /[ÚéêëÄÄ]/g, + 'E': /[ÃÃÃÃÄÄ]/g, + 'g': /[Ä]/g, + 'G': /[Ä]/g, + 'i': /[ìÃîï]/g, + 'I': /[ÃÃÃÃ]/g, + 'l': /[ĺğÅ]/g, + 'L': /[ĹĜÅ]/g, + 'n': /[ñÅÅ]/g, + 'N': /[ÃÅÅ]/g, + 'o': /[òóÎõöÞÅ]/g, + 'O': /[ÃÃÃÃÃÃ]/g, + 'r': /[ÅÅ]/g, + 'R': /[ÅÅ]/g, + 's': /[ššÅ]/g, + 'S': /[Å ÅÅ]/g, + 't': /[ťţ]/g, + 'T': /[ŀŢ]/g, + 'u': /[ùúûů̵]/g, + 'U': /[ÃÃÃÅ®Ã]/g, + 'y': /[ÿÜ]/g, + 'Y': /[ÅžÃ]/g, + 'z': /[şźŌ]/g, + 'Z': /[ŜŹŻ]/g, + 'th': /[ß]/g, + 'TH': /[Ã]/g, + 'dh': /[ð]/g, + 'DH': /[Ã]/g, + 'ss': /[Ã]/g, + 'oe': /[Å]/g, + 'OE': /[Å]/g, + 'ae': /[Ê]/g, + 'AE': /[Ã]/g +}, + +tidy = { + ' ': /[\xa0\u2002\u2003\u2009]/g, + '*': /[\xb7]/g, + '\'': /[\u2018\u2019]/g, + '"': /[\u201c\u201d]/g, + '...': /[\u2026]/g, + '-': /[\u2013]/g, +// '--': /[\u2014]/g, + '»': /[\uFFFD]/g +}, + +conversions = { + ms: 1, + s: 1000, + m: 6e4, + h: 36e5 +}, + +findUnits = /(\d*.?\d+)([msh]+)/; + +var walk = function(string, replacements){ + var result = string, key; + for (key in replacements) result = result.replace(replacements[key], key); + return result; +}; + +var getRegexForTag = function(tag, contents){ + tag = tag || ''; + var regstr = contents ? "<" + tag + "(?!\\w)[^>]*>([\\s\\S]*?)<\/" + tag + "(?!\\w)>" : "<\/?" + tag + "([^>]+)?>", + reg = new RegExp(regstr, "gi"); + return reg; +}; + +String.implement({ + + standardize: function(){ + return walk(this, special); + }, + + repeat: function(times){ + return new Array(times + 1).join(this); + }, + + pad: function(length, str, direction){ + if (this.length >= length) return this; + + var pad = (str == null ? ' ' : '' + str) + .repeat(length - this.length) + .substr(0, length - this.length); + + if (!direction || direction == 'right') return this + pad; + if (direction == 'left') return pad + this; + + return pad.substr(0, (pad.length / 2).floor()) + this + pad.substr(0, (pad.length / 2).ceil()); + }, + + getTags: function(tag, contents){ + return this.match(getRegexForTag(tag, contents)) || []; + }, + + stripTags: function(tag, contents){ + return this.replace(getRegexForTag(tag, contents), ''); + }, + + tidy: function(){ + return walk(this, tidy); + }, + + truncate: function(max, trail, atChar){ + var string = this; + if (trail == null && arguments.length == 1) trail = 'âŠ'; + if (string.length > max){ + string = string.substring(0, max); + if (atChar){ + var index = string.lastIndexOf(atChar); + if (index != -1) string = string.substr(0, index); + } + if (trail) string += trail; + } + return string; + }, + + ms: function(){ + // "Borrowed" from https://gist.github.com/1503944 + var units = findUnits.exec(this); + if (units == null) return Number(this); + return Number(units[1]) * conversions[units[2]]; + } + +}); + +})(); + +/* +--- + +script: Element.Forms.js + +name: Element.Forms + +description: Extends the Element native object to include methods useful in managing inputs. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Element + - String.Extras + - MooTools.More + +provides: [Element.Forms] + +... +*/ + +Element.implement({ + + tidy: function(){ + this.set('value', this.get('value').tidy()); + }, + + getTextInRange: function(start, end){ + return this.get('value').substring(start, end); + }, + + getSelectedText: function(){ + if (this.setSelectionRange) return this.getTextInRange(this.getSelectionStart(), this.getSelectionEnd()); + return document.selection.createRange().text; + }, + + getSelectedRange: function(){ + if (this.selectionStart != null){ + return { + start: this.selectionStart, + end: this.selectionEnd + }; + } + + var pos = { + start: 0, + end: 0 + }; + var range = this.getDocument().selection.createRange(); + if (!range || range.parentElement() != this) return pos; + var duplicate = range.duplicate(); + + if (this.type == 'text'){ + pos.start = 0 - duplicate.moveStart('character', -100000); + pos.end = pos.start + range.text.length; + } else { + var value = this.get('value'); + var offset = value.length; + duplicate.moveToElementText(this); + duplicate.setEndPoint('StartToEnd', range); + if (duplicate.text.length) offset -= value.match(/[\n\r]*$/)[0].length; + pos.end = offset - duplicate.text.length; + duplicate.setEndPoint('StartToStart', range); + pos.start = offset - duplicate.text.length; + } + return pos; + }, + + getSelectionStart: function(){ + return this.getSelectedRange().start; + }, + + getSelectionEnd: function(){ + return this.getSelectedRange().end; + }, + + setCaretPosition: function(pos){ + if (pos == 'end') pos = this.get('value').length; + this.selectRange(pos, pos); + return this; + }, + + getCaretPosition: function(){ + return this.getSelectedRange().start; + }, + + selectRange: function(start, end){ + if (this.setSelectionRange){ + this.focus(); + this.setSelectionRange(start, end); + } else { + var value = this.get('value'); + var diff = value.substr(start, end - start).replace(/\r/g, '').length; + start = value.substr(0, start).replace(/\r/g, '').length; + var range = this.createTextRange(); + range.collapse(true); + range.moveEnd('character', start + diff); + range.moveStart('character', start); + range.select(); + } + return this; + }, + + insertAtCursor: function(value, select){ + var pos = this.getSelectedRange(); + var text = this.get('value'); + this.set('value', text.substring(0, pos.start) + value + text.substring(pos.end, text.length)); + if (select !== false) this.selectRange(pos.start, pos.start + value.length); + else this.setCaretPosition(pos.start + value.length); + return this; + }, + + insertAroundCursor: function(options, select){ + options = Object.append({ + before: '', + defaultMiddle: '', + after: '' + }, options); + + var value = this.getSelectedText() || options.defaultMiddle; + var pos = this.getSelectedRange(); + var text = this.get('value'); + + if (pos.start == pos.end){ + this.set('value', text.substring(0, pos.start) + options.before + value + options.after + text.substring(pos.end, text.length)); + this.selectRange(pos.start + options.before.length, pos.end + options.before.length + value.length); + } else { + var current = text.substring(pos.start, pos.end); + this.set('value', text.substring(0, pos.start) + options.before + current + options.after + text.substring(pos.end, text.length)); + var selStart = pos.start + options.before.length; + if (select !== false) this.selectRange(selStart, selStart + current.length); + else this.setCaretPosition(selStart + text.length); + } + return this; + } + +}); + +/* +--- + +script: Element.Pin.js + +name: Element.Pin + +description: Extends the Element native object to include the pin method useful for fixed positioning for elements. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Element.Event + - Core/Element.Dimensions + - Core/Element.Style + - MooTools.More + +provides: [Element.Pin] + +... +*/ + +(function(){ + var supportsPositionFixed = false, + supportTested = false; + + var testPositionFixed = function(){ + var test = new Element('div').setStyles({ + position: 'fixed', + top: 0, + right: 0 + }).inject(document.body); + supportsPositionFixed = (test.offsetTop === 0); + test.dispose(); + supportTested = true; + }; + + Element.implement({ + + pin: function(enable, forceScroll){ + if (!supportTested) testPositionFixed(); + if (this.getStyle('display') == 'none') return this; + + var pinnedPosition, + scroll = window.getScroll(), + parent, + scrollFixer; + + if (enable !== false){ + pinnedPosition = this.getPosition(); + if (!this.retrieve('pin:_pinned')) { + var currentPosition = { + top: pinnedPosition.y - scroll.y, + left: pinnedPosition.x - scroll.x, + margin: '0px', + padding: '0px' + }; + + if (supportsPositionFixed && !forceScroll){ + this.setStyle('position', 'fixed').setStyles(currentPosition); + } else { + + parent = this.getOffsetParent(); + var position = this.getPosition(parent), + styles = this.getStyles('left', 'top'); + + if (parent && styles.left == 'auto' || styles.top == 'auto') this.setPosition(position); + if (this.getStyle('position') == 'static') this.setStyle('position', 'absolute'); + + position = { + x: styles.left.toInt() - scroll.x, + y: styles.top.toInt() - scroll.y + }; + + scrollFixer = function(){ + if (!this.retrieve('pin:_pinned')) return; + var scroll = window.getScroll(); + this.setStyles({ + left: position.x + scroll.x, + top: position.y + scroll.y + }); + }.bind(this); + + this.store('pin:_scrollFixer', scrollFixer); + window.addEvent('scroll', scrollFixer); + } + this.store('pin:_pinned', true); + } + + } else { + if (!this.retrieve('pin:_pinned')) return this; + + parent = this.getParent(); + var offsetParent = (parent.getComputedStyle('position') != 'static' ? parent : parent.getOffsetParent()); + + pinnedPosition = this.getPosition(); + + this.store('pin:_pinned', false); + scrollFixer = this.retrieve('pin:_scrollFixer'); + if (!scrollFixer){ + this.setStyles({ + position: 'absolute', + top: pinnedPosition.y + scroll.y, + left: pinnedPosition.x + scroll.x + }); + } else { + this.store('pin:_scrollFixer', null); + window.removeEvent('scroll', scrollFixer); + } + this.removeClass('isPinned'); + } + return this; + }, + + unpin: function(){ + return this.pin(false); + }, + + togglePin: function(){ + return this.pin(!this.retrieve('pin:_pinned')); + } + + }); + + + +})(); + +/* +--- + +script: Element.Position.js + +name: Element.Position + +description: Extends the Element native object to include methods useful positioning elements relative to others. + +license: MIT-style license + +authors: + - Aaron Newton + - Jacob Thornton + +requires: + - Core/Options + - Core/Element.Dimensions + - Element.Measure + +provides: [Element.Position] + +... +*/ + +(function(original){ + +var local = Element.Position = { + + options: {/* + edge: false, + returnPos: false, + minimum: {x: 0, y: 0}, + maximum: {x: 0, y: 0}, + relFixedPosition: false, + ignoreMargins: false, + ignoreScroll: false, + allowNegative: false,*/ + relativeTo: document.body, + position: { + x: 'center', //left, center, right + y: 'center' //top, center, bottom + }, + offset: {x: 0, y: 0} + }, + + getOptions: function(element, options){ + options = Object.merge({}, local.options, options); + local.setPositionOption(options); + local.setEdgeOption(options); + local.setOffsetOption(element, options); + local.setDimensionsOption(element, options); + return options; + }, + + setPositionOption: function(options){ + options.position = local.getCoordinateFromValue(options.position); + }, + + setEdgeOption: function(options){ + var edgeOption = local.getCoordinateFromValue(options.edge); + options.edge = edgeOption ? edgeOption : + (options.position.x == 'center' && options.position.y == 'center') ? {x: 'center', y: 'center'} : + {x: 'left', y: 'top'}; + }, + + setOffsetOption: function(element, options){ + var parentOffset = {x: 0, y: 0}; + var parentScroll = {x: 0, y: 0}; + var offsetParent = element.measure(function(){ + return document.id(this.getOffsetParent()); + }); + + if (!offsetParent || offsetParent == element.getDocument().body) return; + + parentScroll = offsetParent.getScroll(); + parentOffset = offsetParent.measure(function(){ + var position = this.getPosition(); + if (this.getStyle('position') == 'fixed'){ + var scroll = window.getScroll(); + position.x += scroll.x; + position.y += scroll.y; + } + return position; + }); + + options.offset = { + parentPositioned: offsetParent != document.id(options.relativeTo), + x: options.offset.x - parentOffset.x + parentScroll.x, + y: options.offset.y - parentOffset.y + parentScroll.y + }; + }, + + setDimensionsOption: function(element, options){ + options.dimensions = element.getDimensions({ + computeSize: true, + styles: ['padding', 'border', 'margin'] + }); + }, + + getPosition: function(element, options){ + var position = {}; + options = local.getOptions(element, options); + var relativeTo = document.id(options.relativeTo) || document.body; + + local.setPositionCoordinates(options, position, relativeTo); + if (options.edge) local.toEdge(position, options); + + var offset = options.offset; + position.left = ((position.x >= 0 || offset.parentPositioned || options.allowNegative) ? position.x : 0).toInt(); + position.top = ((position.y >= 0 || offset.parentPositioned || options.allowNegative) ? position.y : 0).toInt(); + + local.toMinMax(position, options); + + if (options.relFixedPosition || relativeTo.getStyle('position') == 'fixed') local.toRelFixedPosition(relativeTo, position); + if (options.ignoreScroll) local.toIgnoreScroll(relativeTo, position); + if (options.ignoreMargins) local.toIgnoreMargins(position, options); + + position.left = Math.ceil(position.left); + position.top = Math.ceil(position.top); + delete position.x; + delete position.y; + + return position; + }, + + setPositionCoordinates: function(options, position, relativeTo){ + var offsetY = options.offset.y, + offsetX = options.offset.x, + calc = (relativeTo == document.body) ? window.getScroll() : relativeTo.getPosition(), + top = calc.y, + left = calc.x, + winSize = window.getSize(); + + switch(options.position.x){ + case 'left': position.x = left + offsetX; break; + case 'right': position.x = left + offsetX + relativeTo.offsetWidth; break; + default: position.x = left + ((relativeTo == document.body ? winSize.x : relativeTo.offsetWidth) / 2) + offsetX; break; + } + + switch(options.position.y){ + case 'top': position.y = top + offsetY; break; + case 'bottom': position.y = top + offsetY + relativeTo.offsetHeight; break; + default: position.y = top + ((relativeTo == document.body ? winSize.y : relativeTo.offsetHeight) / 2) + offsetY; break; + } + }, + + toMinMax: function(position, options){ + var xy = {left: 'x', top: 'y'}, value; + ['minimum', 'maximum'].each(function(minmax){ + ['left', 'top'].each(function(lr){ + value = options[minmax] ? options[minmax][xy[lr]] : null; + if (value != null && ((minmax == 'minimum') ? position[lr] < value : position[lr] > value)) position[lr] = value; + }); + }); + }, + + toRelFixedPosition: function(relativeTo, position){ + var winScroll = window.getScroll(); + position.top += winScroll.y; + position.left += winScroll.x; + }, + + toIgnoreScroll: function(relativeTo, position){ + var relScroll = relativeTo.getScroll(); + position.top -= relScroll.y; + position.left -= relScroll.x; + }, + + toIgnoreMargins: function(position, options){ + position.left += options.edge.x == 'right' + ? options.dimensions['margin-right'] + : (options.edge.x != 'center' + ? -options.dimensions['margin-left'] + : -options.dimensions['margin-left'] + ((options.dimensions['margin-right'] + options.dimensions['margin-left']) / 2)); + + position.top += options.edge.y == 'bottom' + ? options.dimensions['margin-bottom'] + : (options.edge.y != 'center' + ? -options.dimensions['margin-top'] + : -options.dimensions['margin-top'] + ((options.dimensions['margin-bottom'] + options.dimensions['margin-top']) / 2)); + }, + + toEdge: function(position, options){ + var edgeOffset = {}, + dimensions = options.dimensions, + edge = options.edge; + + switch(edge.x){ + case 'left': edgeOffset.x = 0; break; + case 'right': edgeOffset.x = -dimensions.x - dimensions.computedRight - dimensions.computedLeft; break; + // center + default: edgeOffset.x = -(Math.round(dimensions.totalWidth / 2)); break; + } + + switch(edge.y){ + case 'top': edgeOffset.y = 0; break; + case 'bottom': edgeOffset.y = -dimensions.y - dimensions.computedTop - dimensions.computedBottom; break; + // center + default: edgeOffset.y = -(Math.round(dimensions.totalHeight / 2)); break; + } + + position.x += edgeOffset.x; + position.y += edgeOffset.y; + }, + + getCoordinateFromValue: function(option){ + if (typeOf(option) != 'string') return option; + option = option.toLowerCase(); + + return { + x: option.test('left') ? 'left' + : (option.test('right') ? 'right' : 'center'), + y: option.test(/upper|top/) ? 'top' + : (option.test('bottom') ? 'bottom' : 'center') + }; + } + +}; + +Element.implement({ + + position: function(options){ + if (options && (options.x != null || options.y != null)){ + return (original ? original.apply(this, arguments) : this); + } + var position = this.setStyle('position', 'absolute').calculatePosition(options); + return (options && options.returnPos) ? position : this.setStyles(position); + }, + + calculatePosition: function(options){ + return local.getPosition(this, options); + } + +}); + +})(Element.prototype.position); + +/* +--- + +script: Element.Shortcuts.js + +name: Element.Shortcuts + +description: Extends the Element native object to include some shortcut methods. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Element.Style + - MooTools.More + +provides: [Element.Shortcuts] + +... +*/ + +Element.implement({ + + isDisplayed: function(){ + return this.getStyle('display') != 'none'; + }, + + isVisible: function(){ + var w = this.offsetWidth, + h = this.offsetHeight; + return (w == 0 && h == 0) ? false : (w > 0 && h > 0) ? true : this.style.display != 'none'; + }, + + toggle: function(){ + return this[this.isDisplayed() ? 'hide' : 'show'](); + }, + + hide: function(){ + var d; + try { + //IE fails here if the element is not in the dom + d = this.getStyle('display'); + } catch(e){} + if (d == 'none') return this; + return this.store('element:_originalDisplay', d || '').setStyle('display', 'none'); + }, + + show: function(display){ + if (!display && this.isDisplayed()) return this; + display = display || this.retrieve('element:_originalDisplay') || 'block'; + return this.setStyle('display', (display == 'none') ? 'block' : display); + }, + + swapClass: function(remove, add){ + return this.removeClass(remove).addClass(add); + } + +}); + +Document.implement({ + + clearSelection: function(){ + if (window.getSelection){ + var selection = window.getSelection(); + if (selection && selection.removeAllRanges) selection.removeAllRanges(); + } else if (document.selection && document.selection.empty){ + try { + //IE fails here if selected element is not in dom + document.selection.empty(); + } catch(e){} + } + } + +}); + +/* +--- + +script: Elements.From.js + +name: Elements.From + +description: Returns a collection of elements from a string of html. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/String + - Core/Element + - MooTools.More + +provides: [Elements.from, Elements.From] + +... +*/ + +Elements.from = function(text, excludeScripts){ + if (excludeScripts || excludeScripts == null) text = text.stripScripts(); + + var container, match = text.match(/^\s*(?:<!--.*?-->\s*)*<(t[dhr]|tbody|tfoot|thead)/i); + + if (match){ + container = new Element('table'); + var tag = match[1].toLowerCase(); + if (['td', 'th', 'tr'].contains(tag)){ + container = new Element('tbody').inject(container); + if (tag != 'tr') container = new Element('tr').inject(container); + } + } + + return (container || new Element('div')).set('html', text).getChildren(); +}; + +/* +--- + +script: IframeShim.js + +name: IframeShim + +description: Defines IframeShim, a class for obscuring select lists and flash objects in IE. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Element.Event + - Core/Element.Style + - Core/Options + - Core/Events + - Element.Position + - Class.Occlude + +provides: [IframeShim] + +... +*/ + +(function(){ + +var browsers = false; + + +this.IframeShim = new Class({ + + Implements: [Options, Events, Class.Occlude], + + options: { + className: 'iframeShim', + src: 'javascript:false;document.write("");', + display: false, + zIndex: null, + margin: 0, + offset: {x: 0, y: 0}, + browsers: browsers + }, + + property: 'IframeShim', + + initialize: function(element, options){ + this.element = document.id(element); + if (this.occlude()) return this.occluded; + this.setOptions(options); + this.makeShim(); + return this; + }, + + makeShim: function(){ + if (this.options.browsers){ + var zIndex = this.element.getStyle('zIndex').toInt(); + + if (!zIndex){ + zIndex = 1; + var pos = this.element.getStyle('position'); + if (pos == 'static' || !pos) this.element.setStyle('position', 'relative'); + this.element.setStyle('zIndex', zIndex); + } + zIndex = ((this.options.zIndex != null || this.options.zIndex === 0) && zIndex > this.options.zIndex) ? this.options.zIndex : zIndex - 1; + if (zIndex < 0) zIndex = 1; + this.shim = new Element('iframe', { + src: this.options.src, + scrolling: 'no', + frameborder: 0, + styles: { + zIndex: zIndex, + position: 'absolute', + border: 'none', + filter: 'progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)' + }, + 'class': this.options.className + }).store('IframeShim', this); + var inject = (function(){ + this.shim.inject(this.element, 'after'); + this[this.options.display ? 'show' : 'hide'](); + this.fireEvent('inject'); + }).bind(this); + if (!IframeShim.ready) window.addEvent('load', inject); + else inject(); + } else { + this.position = this.hide = this.show = this.dispose = Function.from(this); + } + }, + + position: function(){ + if (!IframeShim.ready || !this.shim) return this; + var size = this.element.measure(function(){ + return this.getSize(); + }); + if (this.options.margin != undefined){ + size.x = size.x - (this.options.margin * 2); + size.y = size.y - (this.options.margin * 2); + this.options.offset.x += this.options.margin; + this.options.offset.y += this.options.margin; + } + this.shim.set({width: size.x, height: size.y}).position({ + relativeTo: this.element, + offset: this.options.offset + }); + return this; + }, + + hide: function(){ + if (this.shim) this.shim.setStyle('display', 'none'); + return this; + }, + + show: function(){ + if (this.shim) this.shim.setStyle('display', 'block'); + return this.position(); + }, + + dispose: function(){ + if (this.shim) this.shim.dispose(); + return this; + }, + + destroy: function(){ + if (this.shim) this.shim.destroy(); + return this; + } + +}); + +})(); + +window.addEvent('load', function(){ + IframeShim.ready = true; +}); + +/* +--- + +script: Mask.js + +name: Mask + +description: Creates a mask element to cover another. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Options + - Core/Events + - Core/Element.Event + - Class.Binds + - Element.Position + - IframeShim + +provides: [Mask] + +... +*/ + +var Mask = new Class({ + + Implements: [Options, Events], + + Binds: ['position'], + + options: {/* + onShow: function(){}, + onHide: function(){}, + onDestroy: function(){}, + onClick: function(event){}, + inject: { + where: 'after', + target: null, + }, + hideOnClick: false, + id: null, + destroyOnHide: false,*/ + style: {}, + 'class': 'mask', + maskMargins: false, + useIframeShim: true, + iframeShimOptions: {} + }, + + initialize: function(target, options){ + this.target = document.id(target) || document.id(document.body); + this.target.store('mask', this); + this.setOptions(options); + this.render(); + this.inject(); + }, + + render: function(){ + this.element = new Element('div', { + 'class': this.options['class'], + id: this.options.id || 'mask-' + String.uniqueID(), + styles: Object.merge({}, this.options.style, { + display: 'none' + }), + events: { + click: function(event){ + this.fireEvent('click', event); + if (this.options.hideOnClick) this.hide(); + }.bind(this) + } + }); + + this.hidden = true; + }, + + toElement: function(){ + return this.element; + }, + + inject: function(target, where){ + where = where || (this.options.inject ? this.options.inject.where : '') || (this.target == document.body ? 'inside' : 'after'); + target = target || (this.options.inject && this.options.inject.target) || this.target; + + this.element.inject(target, where); + + if (this.options.useIframeShim){ + this.shim = new IframeShim(this.element, this.options.iframeShimOptions); + + this.addEvents({ + show: this.shim.show.bind(this.shim), + hide: this.shim.hide.bind(this.shim), + destroy: this.shim.destroy.bind(this.shim) + }); + } + }, + + position: function(){ + this.resize(this.options.width, this.options.height); + + this.element.position({ + relativeTo: this.target, + position: 'topLeft', + ignoreMargins: !this.options.maskMargins, + ignoreScroll: this.target == document.body + }); + + return this; + }, + + resize: function(x, y){ + var opt = { + styles: ['padding', 'border'] + }; + if (this.options.maskMargins) opt.styles.push('margin'); + + var dim = this.target.getComputedSize(opt); + if (this.target == document.body){ + this.element.setStyles({width: 0, height: 0}); + var win = window.getScrollSize(); + if (dim.totalHeight < win.y) dim.totalHeight = win.y; + if (dim.totalWidth < win.x) dim.totalWidth = win.x; + } + this.element.setStyles({ + width: Array.pick([x, dim.totalWidth, dim.x]), + height: Array.pick([y, dim.totalHeight, dim.y]) + }); + + return this; + }, + + show: function(){ + if (!this.hidden) return this; + + window.addEvent('resize', this.position); + this.position(); + this.showMask.apply(this, arguments); + + return this; + }, + + showMask: function(){ + this.element.setStyle('display', 'block'); + this.hidden = false; + this.fireEvent('show'); + }, + + hide: function(){ + if (this.hidden) return this; + + window.removeEvent('resize', this.position); + this.hideMask.apply(this, arguments); + if (this.options.destroyOnHide) return this.destroy(); + + return this; + }, + + hideMask: function(){ + this.element.setStyle('display', 'none'); + this.hidden = true; + this.fireEvent('hide'); + }, + + toggle: function(){ + this[this.hidden ? 'show' : 'hide'](); + }, + + destroy: function(){ + this.hide(); + this.element.destroy(); + this.fireEvent('destroy'); + this.target.eliminate('mask'); + } + +}); + +Element.Properties.mask = { + + set: function(options){ + var mask = this.retrieve('mask'); + if (mask) mask.destroy(); + return this.eliminate('mask').store('mask:options', options); + }, + + get: function(){ + var mask = this.retrieve('mask'); + if (!mask){ + mask = new Mask(this, this.retrieve('mask:options')); + this.store('mask', mask); + } + return mask; + } + +}; + +Element.implement({ + + mask: function(options){ + if (options) this.set('mask', options); + this.get('mask').show(); + return this; + }, + + unmask: function(){ + this.get('mask').hide(); + return this; + } + +}); + +/* +--- + +script: Spinner.js + +name: Spinner + +description: Adds a semi-transparent overlay over a dom element with a spinnin ajax icon. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Fx.Tween + - Core/Request + - Class.refactor + - Mask + +provides: [Spinner] + +... +*/ + +var Spinner = new Class({ + + Extends: Mask, + + Implements: Chain, + + options: {/* + message: false,*/ + 'class': 'spinner', + containerPosition: {}, + content: { + 'class': 'spinner-content' + }, + messageContainer: { + 'class': 'spinner-msg' + }, + img: { + 'class': 'spinner-img' + }, + fxOptions: { + link: 'chain' + } + }, + + initialize: function(target, options){ + this.target = document.id(target) || document.id(document.body); + this.target.store('spinner', this); + this.setOptions(options); + this.render(); + this.inject(); + + // Add this to events for when noFx is true; parent methods handle hide/show. + var deactivate = function(){ this.active = false; }.bind(this); + this.addEvents({ + hide: deactivate, + show: deactivate + }); + }, + + render: function(){ + this.parent(); + + this.element.set('id', this.options.id || 'spinner-' + String.uniqueID()); + + this.content = document.id(this.options.content) || new Element('div', this.options.content); + this.content.inject(this.element); + + if (this.options.message){ + this.msg = document.id(this.options.message) || new Element('p', this.options.messageContainer).appendText(this.options.message); + this.msg.inject(this.content); + } + + if (this.options.img){ + this.img = document.id(this.options.img) || new Element('div', this.options.img); + this.img.inject(this.content); + } + + this.element.set('tween', this.options.fxOptions); + }, + + show: function(noFx){ + if (this.active) return this.chain(this.show.bind(this)); + if (!this.hidden){ + this.callChain.delay(20, this); + return this; + } + + this.target.set('aria-busy', 'true'); + this.active = true; + + return this.parent(noFx); + }, + + showMask: function(noFx){ + var pos = function(){ + this.content.position(Object.merge({ + relativeTo: this.element + }, this.options.containerPosition)); + }.bind(this); + + if (noFx){ + this.parent(); + pos(); + } else { + if (!this.options.style.opacity) this.options.style.opacity = this.element.getStyle('opacity').toFloat(); + this.element.setStyles({ + display: 'block', + opacity: 0 + }).tween('opacity', this.options.style.opacity); + pos(); + this.hidden = false; + this.fireEvent('show'); + this.callChain(); + } + }, + + hide: function(noFx){ + if (this.active) return this.chain(this.hide.bind(this)); + if (this.hidden){ + this.callChain.delay(20, this); + return this; + } + + this.target.set('aria-busy', 'false'); + this.active = true; + + return this.parent(noFx); + }, + + hideMask: function(noFx){ + if (noFx) return this.parent(); + this.element.tween('opacity', 0).get('tween').chain(function(){ + this.element.setStyle('display', 'none'); + this.hidden = true; + this.fireEvent('hide'); + this.callChain(); + }.bind(this)); + }, + + destroy: function(){ + this.content.destroy(); + this.parent(); + this.target.eliminate('spinner'); + } + +}); + +Request = Class.refactor(Request, { + + options: { + useSpinner: false, + spinnerOptions: {}, + spinnerTarget: false + }, + + initialize: function(options){ + this._send = this.send; + this.send = function(options){ + var spinner = this.getSpinner(); + if (spinner) spinner.chain(this._send.pass(options, this)).show(); + else this._send(options); + return this; + }; + this.previous(options); + }, + + getSpinner: function(){ + if (!this.spinner){ + var update = document.id(this.options.spinnerTarget) || document.id(this.options.update); + if (this.options.useSpinner && update){ + update.set('spinner', this.options.spinnerOptions); + var spinner = this.spinner = update.get('spinner'); + ['complete', 'exception', 'cancel'].each(function(event){ + this.addEvent(event, spinner.hide.bind(spinner)); + }, this); + } + } + return this.spinner; + } + +}); + +Element.Properties.spinner = { + + set: function(options){ + var spinner = this.retrieve('spinner'); + if (spinner) spinner.destroy(); + return this.eliminate('spinner').store('spinner:options', options); + }, + + get: function(){ + var spinner = this.retrieve('spinner'); + if (!spinner){ + spinner = new Spinner(this, this.retrieve('spinner:options')); + this.store('spinner', spinner); + } + return spinner; + } + +}; + +Element.implement({ + + spin: function(options){ + if (options) this.set('spinner', options); + this.get('spinner').show(); + return this; + }, + + unspin: function(){ + this.get('spinner').hide(); + return this; + } + +}); + +/* +--- + +script: String.QueryString.js + +name: String.QueryString + +description: Methods for dealing with URI query strings. + +license: MIT-style license + +authors: + - Sebastian MarkbÃ¥ge + - Aaron Newton + - Lennart Pilon + - Valerio Proietti + +requires: + - Core/Array + - Core/String + - MooTools.More + +provides: [String.QueryString] + +... +*/ + +String.implement({ + + parseQueryString: function(decodeKeys, decodeValues){ + if (decodeKeys == null) decodeKeys = true; + if (decodeValues == null) decodeValues = true; + + var vars = this.split(/[&;]/), + object = {}; + if (!vars.length) return object; + + vars.each(function(val){ + var index = val.indexOf('=') + 1, + value = index ? val.substr(index) : '', + keys = index ? val.substr(0, index - 1).match(/([^\]\[]+|(\B)(?=\]))/g) : [val], + obj = object; + if (!keys) return; + if (decodeValues) value = decodeURIComponent(value); + keys.each(function(key, i){ + if (decodeKeys) key = decodeURIComponent(key); + var current = obj[key]; + + if (i < keys.length - 1) obj = obj[key] = current || {}; + else if (typeOf(current) == 'array') current.push(value); + else obj[key] = current != null ? [current, value] : value; + }); + }); + + return object; + }, + + cleanQueryString: function(method){ + return this.split('&').filter(function(val){ + var index = val.indexOf('='), + key = index < 0 ? '' : val.substr(0, index), + value = val.substr(index + 1); + + return method ? method.call(null, key, value) : (value || value === 0); + }).join('&'); + } + +}); + +/* +--- + +script: Form.Request.js + +name: Form.Request + +description: Handles the basic functionality of submitting a form and updating a dom element with the result. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Request.HTML + - Class.Binds + - Class.Occlude + - Spinner + - String.QueryString + - Element.Delegation.Pseudo + +provides: [Form.Request] + +... +*/ + +if (!window.Form) window.Form = {}; + +(function(){ + + Form.Request = new Class({ + + Binds: ['onSubmit', 'onFormValidate'], + + Implements: [Options, Events, Class.Occlude], + + options: {/* + onFailure: function(){}, + onSuccess: function(){}, // aliased to onComplete, + onSend: function(){}*/ + requestOptions: { + evalScripts: true, + useSpinner: true, + emulation: false, + link: 'ignore' + }, + sendButtonClicked: true, + extraData: {}, + resetForm: true + }, + + property: 'form.request', + + initialize: function(form, target, options){ + this.element = document.id(form); + if (this.occlude()) return this.occluded; + this.setOptions(options) + .setTarget(target) + .attach(); + }, + + setTarget: function(target){ + this.target = document.id(target); + if (!this.request){ + this.makeRequest(); + } else { + this.request.setOptions({ + update: this.target + }); + } + return this; + }, + + toElement: function(){ + return this.element; + }, + + makeRequest: function(){ + var self = this; + this.request = new Request.HTML(Object.merge({ + update: this.target, + emulation: false, + spinnerTarget: this.element, + method: this.element.get('method') || 'post' + }, this.options.requestOptions)).addEvents({ + success: function(tree, elements, html, javascript){ + ['complete', 'success'].each(function(evt){ + self.fireEvent(evt, [self.target, tree, elements, html, javascript]); + }); + }, + failure: function(){ + self.fireEvent('complete', arguments).fireEvent('failure', arguments); + }, + exception: function(){ + self.fireEvent('failure', arguments); + } + }); + return this.attachReset(); + }, + + attachReset: function(){ + if (!this.options.resetForm) return this; + this.request.addEvent('success', function(){ + Function.attempt(function(){ + this.element.reset(); + }.bind(this)); + if (window.OverText) OverText.update(); + }.bind(this)); + return this; + }, + + attach: function(attach){ + var method = (attach != false) ? 'addEvent' : 'removeEvent'; + this.element[method]('click:relay(button, input[type=submit])', this.saveClickedButton.bind(this)); + + var fv = this.element.retrieve('validator'); + if (fv) fv[method]('onFormValidate', this.onFormValidate); + else this.element[method]('submit', this.onSubmit); + + return this; + }, + + detach: function(){ + return this.attach(false); + }, + + //public method + enable: function(){ + return this.attach(); + }, + + //public method + disable: function(){ + return this.detach(); + }, + + onFormValidate: function(valid, form, event){ + //if there's no event, then this wasn't a submit event + if (!event) return; + var fv = this.element.retrieve('validator'); + if (valid || (fv && !fv.options.stopOnFailure)){ + event.stop(); + this.send(); + } + }, + + onSubmit: function(event){ + var fv = this.element.retrieve('validator'); + if (fv){ + //form validator was created after Form.Request + this.element.removeEvent('submit', this.onSubmit); + fv.addEvent('onFormValidate', this.onFormValidate); + fv.validate(event); + return; + } + if (event) event.stop(); + this.send(); + }, + + saveClickedButton: function(event, target){ + var targetName = target.get('name'); + if (!targetName || !this.options.sendButtonClicked) return; + this.options.extraData[targetName] = target.get('value') || true; + this.clickedCleaner = function(){ + delete this.options.extraData[targetName]; + this.clickedCleaner = function(){}; + }.bind(this); + }, + + clickedCleaner: function(){}, + + send: function(){ + var str = this.element.toQueryString().trim(), + data = Object.toQueryString(this.options.extraData); + + if (str) str += "&" + data; + else str = data; + + this.fireEvent('send', [this.element, str.parseQueryString()]); + this.request.send({ + data: str, + url: this.options.requestOptions.url || this.element.get('action') + }); + this.clickedCleaner(); + return this; + } + + }); + + Element.implement('formUpdate', function(update, options){ + var fq = this.retrieve('form.request'); + if (!fq){ + fq = new Form.Request(this, update, options); + } else { + if (update) fq.setTarget(update); + if (options) fq.setOptions(options).makeRequest(); + } + fq.send(); + return this; + }); + +})(); + +/* +--- + +script: Fx.Reveal.js + +name: Fx.Reveal + +description: Defines Fx.Reveal, a class that shows and hides elements with a transition. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Fx.Morph + - Element.Shortcuts + - Element.Measure + +provides: [Fx.Reveal] + +... +*/ + +(function(){ + + +var hideTheseOf = function(object){ + var hideThese = object.options.hideInputs; + if (window.OverText){ + var otClasses = [null]; + OverText.each(function(ot){ + otClasses.include('.' + ot.options.labelClass); + }); + if (otClasses) hideThese += otClasses.join(', '); + } + return (hideThese) ? object.element.getElements(hideThese) : null; +}; + + +Fx.Reveal = new Class({ + + Extends: Fx.Morph, + + options: {/* + onShow: function(thisElement){}, + onHide: function(thisElement){}, + onComplete: function(thisElement){}, + heightOverride: null, + widthOverride: null,*/ + link: 'cancel', + styles: ['padding', 'border', 'margin'], + transitionOpacity: 'opacity' in document.documentElement, + mode: 'vertical', + display: function(){ + return this.element.get('tag') != 'tr' ? 'block' : 'table-row'; + }, + opacity: 1, + hideInputs: !('opacity' in document.documentElement) ? 'select, input, textarea, object, embed' : null + }, + + dissolve: function(){ + if (!this.hiding && !this.showing){ + if (this.element.getStyle('display') != 'none'){ + this.hiding = true; + this.showing = false; + this.hidden = true; + this.cssText = this.element.style.cssText; + + var startStyles = this.element.getComputedSize({ + styles: this.options.styles, + mode: this.options.mode + }); + if (this.options.transitionOpacity) startStyles.opacity = this.options.opacity; + + var zero = {}; + Object.each(startStyles, function(style, name){ + zero[name] = [style, 0]; + }); + + this.element.setStyles({ + display: Function.from(this.options.display).call(this), + overflow: 'hidden' + }); + + var hideThese = hideTheseOf(this); + if (hideThese) hideThese.setStyle('visibility', 'hidden'); + + this.$chain.unshift(function(){ + if (this.hidden){ + this.hiding = false; + this.element.style.cssText = this.cssText; + this.element.setStyle('display', 'none'); + if (hideThese) hideThese.setStyle('visibility', 'visible'); + } + this.fireEvent('hide', this.element); + this.callChain(); + }.bind(this)); + + this.start(zero); + } else { + this.callChain.delay(10, this); + this.fireEvent('complete', this.element); + this.fireEvent('hide', this.element); + } + } else if (this.options.link == 'chain'){ + this.chain(this.dissolve.bind(this)); + } else if (this.options.link == 'cancel' && !this.hiding){ + this.cancel(); + this.dissolve(); + } + return this; + }, + + reveal: function(){ + if (!this.showing && !this.hiding){ + if (this.element.getStyle('display') == 'none'){ + this.hiding = false; + this.showing = true; + this.hidden = false; + this.cssText = this.element.style.cssText; + + var startStyles; + this.element.measure(function(){ + startStyles = this.element.getComputedSize({ + styles: this.options.styles, + mode: this.options.mode + }); + }.bind(this)); + if (this.options.heightOverride != null) startStyles.height = this.options.heightOverride.toInt(); + if (this.options.widthOverride != null) startStyles.width = this.options.widthOverride.toInt(); + if (this.options.transitionOpacity){ + this.element.setStyle('opacity', 0); + startStyles.opacity = this.options.opacity; + } + + var zero = { + height: 0, + display: Function.from(this.options.display).call(this) + }; + Object.each(startStyles, function(style, name){ + zero[name] = 0; + }); + zero.overflow = 'hidden'; + + this.element.setStyles(zero); + + var hideThese = hideTheseOf(this); + if (hideThese) hideThese.setStyle('visibility', 'hidden'); + + this.$chain.unshift(function(){ + this.element.style.cssText = this.cssText; + this.element.setStyle('display', Function.from(this.options.display).call(this)); + if (!this.hidden) this.showing = false; + if (hideThese) hideThese.setStyle('visibility', 'visible'); + this.callChain(); + this.fireEvent('show', this.element); + }.bind(this)); + + this.start(startStyles); + } else { + this.callChain(); + this.fireEvent('complete', this.element); + this.fireEvent('show', this.element); + } + } else if (this.options.link == 'chain'){ + this.chain(this.reveal.bind(this)); + } else if (this.options.link == 'cancel' && !this.showing){ + this.cancel(); + this.reveal(); + } + return this; + }, + + toggle: function(){ + if (this.element.getStyle('display') == 'none'){ + this.reveal(); + } else { + this.dissolve(); + } + return this; + }, + + cancel: function(){ + this.parent.apply(this, arguments); + if (this.cssText != null) this.element.style.cssText = this.cssText; + this.hiding = false; + this.showing = false; + return this; + } + +}); + +Element.Properties.reveal = { + + set: function(options){ + this.get('reveal').cancel().setOptions(options); + return this; + }, + + get: function(){ + var reveal = this.retrieve('reveal'); + if (!reveal){ + reveal = new Fx.Reveal(this); + this.store('reveal', reveal); + } + return reveal; + } + +}; + +Element.Properties.dissolve = Element.Properties.reveal; + +Element.implement({ + + reveal: function(options){ + this.get('reveal').setOptions(options).reveal(); + return this; + }, + + dissolve: function(options){ + this.get('reveal').setOptions(options).dissolve(); + return this; + }, + + nix: function(options){ + var params = Array.link(arguments, {destroy: Type.isBoolean, options: Type.isObject}); + this.get('reveal').setOptions(options).dissolve().chain(function(){ + this[params.destroy ? 'destroy' : 'dispose'](); + }.bind(this)); + return this; + }, + + wink: function(){ + var params = Array.link(arguments, {duration: Type.isNumber, options: Type.isObject}); + var reveal = this.get('reveal').setOptions(params.options); + reveal.reveal().chain(function(){ + (function(){ + reveal.dissolve(); + }).delay(params.duration || 2000); + }); + } + +}); + +})(); + +/* +--- + +script: Form.Request.Append.js + +name: Form.Request.Append + +description: Handles the basic functionality of submitting a form and updating a dom element with the result. The result is appended to the DOM element instead of replacing its contents. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Form.Request + - Fx.Reveal + - Elements.from + +provides: [Form.Request.Append] + +... +*/ + +Form.Request.Append = new Class({ + + Extends: Form.Request, + + options: { + //onBeforeEffect: function(){}, + useReveal: true, + revealOptions: {}, + inject: 'bottom' + }, + + makeRequest: function(){ + this.request = new Request.HTML(Object.merge({ + url: this.element.get('action'), + method: this.element.get('method') || 'post', + spinnerTarget: this.element + }, this.options.requestOptions, { + evalScripts: false + }) + ).addEvents({ + success: function(tree, elements, html, javascript){ + var container; + var kids = Elements.from(html); + if (kids.length == 1){ + container = kids[0]; + } else { + container = new Element('div', { + styles: { + display: 'none' + } + }).adopt(kids); + } + container.inject(this.target, this.options.inject); + if (this.options.requestOptions.evalScripts) Browser.exec(javascript); + this.fireEvent('beforeEffect', container); + var finish = function(){ + this.fireEvent('success', [container, this.target, tree, elements, html, javascript]); + }.bind(this); + if (this.options.useReveal){ + container.set('reveal', this.options.revealOptions).get('reveal').chain(finish); + container.reveal(); + } else { + finish(); + } + }.bind(this), + failure: function(xhr){ + this.fireEvent('failure', xhr); + }.bind(this) + }); + this.attachReset(); + } + +}); + +/* +--- + +script: Object.Extras.js + +name: Object.Extras + +description: Extra Object generics, like getFromPath which allows a path notation to child elements. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Object + - MooTools.More + +provides: [Object.Extras] + +... +*/ + +(function(){ + +var defined = function(value){ + return value != null; +}; + +var hasOwnProperty = Object.prototype.hasOwnProperty; + +Object.extend({ + + getFromPath: function(source, parts){ + if (typeof parts == 'string') parts = parts.split('.'); + for (var i = 0, l = parts.length; i < l; i++){ + if (hasOwnProperty.call(source, parts[i])) source = source[parts[i]]; + else return null; + } + return source; + }, + + cleanValues: function(object, method){ + method = method || defined; + for (var key in object) if (!method(object[key])){ + delete object[key]; + } + return object; + }, + + erase: function(object, key){ + if (hasOwnProperty.call(object, key)) delete object[key]; + return object; + }, + + run: function(object){ + var args = Array.slice(arguments, 1); + for (var key in object) if (object[key].apply){ + object[key].apply(object, args); + } + return object; + } + +}); + +})(); + +/* +--- + +script: Locale.js + +name: Locale + +description: Provides methods for localization. + +license: MIT-style license + +authors: + - Aaron Newton + - Arian Stolwijk + +requires: + - Core/Events + - Object.Extras + - MooTools.More + +provides: [Locale, Lang] + +... +*/ + +(function(){ + +var current = null, + locales = {}, + inherits = {}; + +var getSet = function(set){ + if (instanceOf(set, Locale.Set)) return set; + else return locales[set]; +}; + +var Locale = this.Locale = { + + define: function(locale, set, key, value){ + var name; + if (instanceOf(locale, Locale.Set)){ + name = locale.name; + if (name) locales[name] = locale; + } else { + name = locale; + if (!locales[name]) locales[name] = new Locale.Set(name); + locale = locales[name]; + } + + if (set) locale.define(set, key, value); + + + + if (!current) current = locale; + + return locale; + }, + + use: function(locale){ + locale = getSet(locale); + + if (locale){ + current = locale; + + this.fireEvent('change', locale); + + + } + + return this; + }, + + getCurrent: function(){ + return current; + }, + + get: function(key, args){ + return (current) ? current.get(key, args) : ''; + }, + + inherit: function(locale, inherits, set){ + locale = getSet(locale); + + if (locale) locale.inherit(inherits, set); + return this; + }, + + list: function(){ + return Object.keys(locales); + } + +}; + +Object.append(Locale, new Events); + +Locale.Set = new Class({ + + sets: {}, + + inherits: { + locales: [], + sets: {} + }, + + initialize: function(name){ + this.name = name || ''; + }, + + define: function(set, key, value){ + var defineData = this.sets[set]; + if (!defineData) defineData = {}; + + if (key){ + if (typeOf(key) == 'object') defineData = Object.merge(defineData, key); + else defineData[key] = value; + } + this.sets[set] = defineData; + + return this; + }, + + get: function(key, args, _base){ + var value = Object.getFromPath(this.sets, key); + if (value != null){ + var type = typeOf(value); + if (type == 'function') value = value.apply(null, Array.from(args)); + else if (type == 'object') value = Object.clone(value); + return value; + } + + // get value of inherited locales + var index = key.indexOf('.'), + set = index < 0 ? key : key.substr(0, index), + names = (this.inherits.sets[set] || []).combine(this.inherits.locales).include('en-US'); + if (!_base) _base = []; + + for (var i = 0, l = names.length; i < l; i++){ + if (_base.contains(names[i])) continue; + _base.include(names[i]); + + var locale = locales[names[i]]; + if (!locale) continue; + + value = locale.get(key, args, _base); + if (value != null) return value; + } + + return ''; + }, + + inherit: function(names, set){ + names = Array.from(names); + + if (set && !this.inherits.sets[set]) this.inherits.sets[set] = []; + + var l = names.length; + while (l--) (set ? this.inherits.sets[set] : this.inherits.locales).unshift(names[l]); + + return this; + } + +}); + + + +})(); + +/* +--- + +name: Locale.en-US.Date + +description: Date messages for US English. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Locale + +provides: [Locale.en-US.Date] + +... +*/ + +Locale.define('en-US', 'Date', { + + months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + months_abbr: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], + days_abbr: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + + // Culture's date order: MM/DD/YYYY + dateOrder: ['month', 'date', 'year'], + shortDate: '%m/%d/%Y', + shortTime: '%I:%M%p', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 0, + + // Date.Extras + ordinal: function(dayOfMonth){ + // 1st, 2nd, 3rd, etc. + return (dayOfMonth > 3 && dayOfMonth < 21) ? 'th' : ['th', 'st', 'nd', 'rd', 'th'][Math.min(dayOfMonth % 10, 4)]; + }, + + lessThanMinuteAgo: 'less than a minute ago', + minuteAgo: 'about a minute ago', + minutesAgo: '{delta} minutes ago', + hourAgo: 'about an hour ago', + hoursAgo: 'about {delta} hours ago', + dayAgo: '1 day ago', + daysAgo: '{delta} days ago', + weekAgo: '1 week ago', + weeksAgo: '{delta} weeks ago', + monthAgo: '1 month ago', + monthsAgo: '{delta} months ago', + yearAgo: '1 year ago', + yearsAgo: '{delta} years ago', + + lessThanMinuteUntil: 'less than a minute from now', + minuteUntil: 'about a minute from now', + minutesUntil: '{delta} minutes from now', + hourUntil: 'about an hour from now', + hoursUntil: 'about {delta} hours from now', + dayUntil: '1 day from now', + daysUntil: '{delta} days from now', + weekUntil: '1 week from now', + weeksUntil: '{delta} weeks from now', + monthUntil: '1 month from now', + monthsUntil: '{delta} months from now', + yearUntil: '1 year from now', + yearsUntil: '{delta} years from now' + +}); + +/* +--- + +script: Date.js + +name: Date + +description: Extends the Date native object to include methods useful in managing dates. + +license: MIT-style license + +authors: + - Aaron Newton + - Nicholas Barthelemy - https://svn.nbarthelemy.com/date-js/ + - Harald Kirshner - mail [at] digitarald.de; http://digitarald.de + - Scott Kyle - scott [at] appden.com; http://appden.com + +requires: + - Core/Array + - Core/String + - Core/Number + - MooTools.More + - Locale + - Locale.en-US.Date + +provides: [Date] + +... +*/ + +(function(){ + +var Date = this.Date; + +var DateMethods = Date.Methods = { + ms: 'Milliseconds', + year: 'FullYear', + min: 'Minutes', + mo: 'Month', + sec: 'Seconds', + hr: 'Hours' +}; + +['Date', 'Day', 'FullYear', 'Hours', 'Milliseconds', 'Minutes', 'Month', 'Seconds', 'Time', 'TimezoneOffset', + 'Week', 'Timezone', 'GMTOffset', 'DayOfYear', 'LastMonth', 'LastDayOfMonth', 'UTCDate', 'UTCDay', 'UTCFullYear', + 'AMPM', 'Ordinal', 'UTCHours', 'UTCMilliseconds', 'UTCMinutes', 'UTCMonth', 'UTCSeconds', 'UTCMilliseconds'].each(function(method){ + Date.Methods[method.toLowerCase()] = method; +}); + +var pad = function(n, digits, string){ + if (digits == 1) return n; + return n < Math.pow(10, digits - 1) ? (string || '0') + pad(n, digits - 1, string) : n; +}; + +Date.implement({ + + set: function(prop, value){ + prop = prop.toLowerCase(); + var method = DateMethods[prop] && 'set' + DateMethods[prop]; + if (method && this[method]) this[method](value); + return this; + }.overloadSetter(), + + get: function(prop){ + prop = prop.toLowerCase(); + var method = DateMethods[prop] && 'get' + DateMethods[prop]; + if (method && this[method]) return this[method](); + return null; + }.overloadGetter(), + + clone: function(){ + return new Date(this.get('time')); + }, + + increment: function(interval, times){ + interval = interval || 'day'; + times = times != null ? times : 1; + + switch (interval){ + case 'year': + return this.increment('month', times * 12); + case 'month': + var d = this.get('date'); + this.set('date', 1).set('mo', this.get('mo') + times); + return this.set('date', d.min(this.get('lastdayofmonth'))); + case 'week': + return this.increment('day', times * 7); + case 'day': + return this.set('date', this.get('date') + times); + } + + if (!Date.units[interval]) throw new Error(interval + ' is not a supported interval'); + + return this.set('time', this.get('time') + times * Date.units[interval]()); + }, + + decrement: function(interval, times){ + return this.increment(interval, -1 * (times != null ? times : 1)); + }, + + isLeapYear: function(){ + return Date.isLeapYear(this.get('year')); + }, + + clearTime: function(){ + return this.set({hr: 0, min: 0, sec: 0, ms: 0}); + }, + + diff: function(date, resolution){ + if (typeOf(date) == 'string') date = Date.parse(date); + + return ((date - this) / Date.units[resolution || 'day'](3, 3)).round(); // non-leap year, 30-day month + }, + + getLastDayOfMonth: function(){ + return Date.daysInMonth(this.get('mo'), this.get('year')); + }, + + getDayOfYear: function(){ + return (Date.UTC(this.get('year'), this.get('mo'), this.get('date') + 1) + - Date.UTC(this.get('year'), 0, 1)) / Date.units.day(); + }, + + setDay: function(day, firstDayOfWeek){ + if (firstDayOfWeek == null){ + firstDayOfWeek = Date.getMsg('firstDayOfWeek'); + if (firstDayOfWeek === '') firstDayOfWeek = 1; + } + + day = (7 + Date.parseDay(day, true) - firstDayOfWeek) % 7; + var currentDay = (7 + this.get('day') - firstDayOfWeek) % 7; + + return this.increment('day', day - currentDay); + }, + + getWeek: function(firstDayOfWeek){ + if (firstDayOfWeek == null){ + firstDayOfWeek = Date.getMsg('firstDayOfWeek'); + if (firstDayOfWeek === '') firstDayOfWeek = 1; + } + + var date = this, + dayOfWeek = (7 + date.get('day') - firstDayOfWeek) % 7, + dividend = 0, + firstDayOfYear; + + if (firstDayOfWeek == 1){ + // ISO-8601, week belongs to year that has the most days of the week (i.e. has the thursday of the week) + var month = date.get('month'), + startOfWeek = date.get('date') - dayOfWeek; + + if (month == 11 && startOfWeek > 28) return 1; // Week 1 of next year + + if (month == 0 && startOfWeek < -2){ + // Use a date from last year to determine the week + date = new Date(date).decrement('day', dayOfWeek); + dayOfWeek = 0; + } + + firstDayOfYear = new Date(date.get('year'), 0, 1).get('day') || 7; + if (firstDayOfYear > 4) dividend = -7; // First week of the year is not week 1 + } else { + // In other cultures the first week of the year is always week 1 and the last week always 53 or 54. + // Days in the same week can have a different weeknumber if the week spreads across two years. + firstDayOfYear = new Date(date.get('year'), 0, 1).get('day'); + } + + dividend += date.get('dayofyear'); + dividend += 6 - dayOfWeek; // Add days so we calculate the current date's week as a full week + dividend += (7 + firstDayOfYear - firstDayOfWeek) % 7; // Make up for first week of the year not being a full week + + return (dividend / 7); + }, + + getOrdinal: function(day){ + return Date.getMsg('ordinal', day || this.get('date')); + }, + + getTimezone: function(){ + return this.toString() + .replace(/^.*? ([A-Z]{3}).[0-9]{4}.*$/, '$1') + .replace(/^.*?\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\)$/, '$1$2$3'); + }, + + getGMTOffset: function(){ + var off = this.get('timezoneOffset'); + return ((off > 0) ? '-' : '+') + pad((off.abs() / 60).floor(), 2) + pad(off % 60, 2); + }, + + setAMPM: function(ampm){ + ampm = ampm.toUpperCase(); + var hr = this.get('hr'); + if (hr > 11 && ampm == 'AM') return this.decrement('hour', 12); + else if (hr < 12 && ampm == 'PM') return this.increment('hour', 12); + return this; + }, + + getAMPM: function(){ + return (this.get('hr') < 12) ? 'AM' : 'PM'; + }, + + parse: function(str){ + this.set('time', Date.parse(str)); + return this; + }, + + isValid: function(date){ + if (!date) date = this; + return typeOf(date) == 'date' && !isNaN(date.valueOf()); + }, + + format: function(format){ + if (!this.isValid()) return 'invalid date'; + + if (!format) format = '%x %X'; + if (typeof format == 'string') format = formats[format.toLowerCase()] || format; + if (typeof format == 'function') return format(this); + + var d = this; + return format.replace(/%([a-z%])/gi, + function($0, $1){ + switch ($1){ + case 'a': return Date.getMsg('days_abbr')[d.get('day')]; + case 'A': return Date.getMsg('days')[d.get('day')]; + case 'b': return Date.getMsg('months_abbr')[d.get('month')]; + case 'B': return Date.getMsg('months')[d.get('month')]; + case 'c': return d.format('%a %b %d %H:%M:%S %Y'); + case 'd': return pad(d.get('date'), 2); + case 'e': return pad(d.get('date'), 2, ' '); + case 'H': return pad(d.get('hr'), 2); + case 'I': return pad((d.get('hr') % 12) || 12, 2); + case 'j': return pad(d.get('dayofyear'), 3); + case 'k': return pad(d.get('hr'), 2, ' '); + case 'l': return pad((d.get('hr') % 12) || 12, 2, ' '); + case 'L': return pad(d.get('ms'), 3); + case 'm': return pad((d.get('mo') + 1), 2); + case 'M': return pad(d.get('min'), 2); + case 'o': return d.get('ordinal'); + case 'p': return Date.getMsg(d.get('ampm')); + case 's': return Math.round(d / 1000); + case 'S': return pad(d.get('seconds'), 2); + case 'T': return d.format('%H:%M:%S'); + case 'U': return pad(d.get('week'), 2); + case 'w': return d.get('day'); + case 'x': return d.format(Date.getMsg('shortDate')); + case 'X': return d.format(Date.getMsg('shortTime')); + case 'y': return d.get('year').toString().substr(2); + case 'Y': return d.get('year'); + case 'z': return d.get('GMTOffset'); + case 'Z': return d.get('Timezone'); + } + return $1; + } + ); + }, + + toISOString: function(){ + return this.format('iso8601'); + } + +}).alias({ + toJSON: 'toISOString', + compare: 'diff', + strftime: 'format' +}); + +// The day and month abbreviations are standardized, so we cannot use simply %a and %b because they will get localized +var rfcDayAbbr = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + rfcMonthAbbr = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; + +var formats = { + db: '%Y-%m-%d %H:%M:%S', + compact: '%Y%m%dT%H%M%S', + 'short': '%d %b %H:%M', + 'long': '%B %d, %Y %H:%M', + rfc822: function(date){ + return rfcDayAbbr[date.get('day')] + date.format(', %d ') + rfcMonthAbbr[date.get('month')] + date.format(' %Y %H:%M:%S %Z'); + }, + rfc2822: function(date){ + return rfcDayAbbr[date.get('day')] + date.format(', %d ') + rfcMonthAbbr[date.get('month')] + date.format(' %Y %H:%M:%S %z'); + }, + iso8601: function(date){ + return ( + date.getUTCFullYear() + '-' + + pad(date.getUTCMonth() + 1, 2) + '-' + + pad(date.getUTCDate(), 2) + 'T' + + pad(date.getUTCHours(), 2) + ':' + + pad(date.getUTCMinutes(), 2) + ':' + + pad(date.getUTCSeconds(), 2) + '.' + + pad(date.getUTCMilliseconds(), 3) + 'Z' + ); + } +}; + +var parsePatterns = [], + nativeParse = Date.parse; + +var parseWord = function(type, word, num){ + var ret = -1, + translated = Date.getMsg(type + 's'); + switch (typeOf(word)){ + case 'object': + ret = translated[word.get(type)]; + break; + case 'number': + ret = translated[word]; + if (!ret) throw new Error('Invalid ' + type + ' index: ' + word); + break; + case 'string': + var match = translated.filter(function(name){ + return this.test(name); + }, new RegExp('^' + word, 'i')); + if (!match.length) throw new Error('Invalid ' + type + ' string'); + if (match.length > 1) throw new Error('Ambiguous ' + type); + ret = match[0]; + } + + return (num) ? translated.indexOf(ret) : ret; +}; + +var startCentury = 1900, + startYear = 70; + +Date.extend({ + + getMsg: function(key, args){ + return Locale.get('Date.' + key, args); + }, + + units: { + ms: Function.from(1), + second: Function.from(1000), + minute: Function.from(60000), + hour: Function.from(3600000), + day: Function.from(86400000), + week: Function.from(608400000), + month: function(month, year){ + var d = new Date; + return Date.daysInMonth(month != null ? month : d.get('mo'), year != null ? year : d.get('year')) * 86400000; + }, + year: function(year){ + year = year || new Date().get('year'); + return Date.isLeapYear(year) ? 31622400000 : 31536000000; + } + }, + + daysInMonth: function(month, year){ + return [31, Date.isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]; + }, + + isLeapYear: function(year){ + return ((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0); + }, + + parse: function(from){ + var t = typeOf(from); + if (t == 'number') return new Date(from); + if (t != 'string') return from; + from = from.clean(); + if (!from.length) return null; + + var parsed; + parsePatterns.some(function(pattern){ + var bits = pattern.re.exec(from); + return (bits) ? (parsed = pattern.handler(bits)) : false; + }); + + if (!(parsed && parsed.isValid())){ + parsed = new Date(nativeParse(from)); + if (!(parsed && parsed.isValid())) parsed = new Date(from.toInt()); + } + return parsed; + }, + + parseDay: function(day, num){ + return parseWord('day', day, num); + }, + + parseMonth: function(month, num){ + return parseWord('month', month, num); + }, + + parseUTC: function(value){ + var localDate = new Date(value); + var utcSeconds = Date.UTC( + localDate.get('year'), + localDate.get('mo'), + localDate.get('date'), + localDate.get('hr'), + localDate.get('min'), + localDate.get('sec'), + localDate.get('ms') + ); + return new Date(utcSeconds); + }, + + orderIndex: function(unit){ + return Date.getMsg('dateOrder').indexOf(unit) + 1; + }, + + defineFormat: function(name, format){ + formats[name] = format; + return this; + }, + + + + defineParser: function(pattern){ + parsePatterns.push((pattern.re && pattern.handler) ? pattern : build(pattern)); + return this; + }, + + defineParsers: function(){ + Array.flatten(arguments).each(Date.defineParser); + return this; + }, + + define2DigitYearStart: function(year){ + startYear = year % 100; + startCentury = year - startYear; + return this; + } + +}).extend({ + defineFormats: Date.defineFormat.overloadSetter() +}); + +var regexOf = function(type){ + return new RegExp('(?:' + Date.getMsg(type).map(function(name){ + return name.substr(0, 3); + }).join('|') + ')[a-z]*'); +}; + +var replacers = function(key){ + switch (key){ + case 'T': + return '%H:%M:%S'; + case 'x': // iso8601 covers yyyy-mm-dd, so just check if month is first + return ((Date.orderIndex('month') == 1) ? '%m[-./]%d' : '%d[-./]%m') + '([-./]%y)?'; + case 'X': + return '%H([.:]%M)?([.:]%S([.:]%s)?)? ?%p? ?%z?'; + } + return null; +}; + +var keys = { + d: /[0-2]?[0-9]|3[01]/, + H: /[01]?[0-9]|2[0-3]/, + I: /0?[1-9]|1[0-2]/, + M: /[0-5]?\d/, + s: /\d+/, + o: /[a-z]*/, + p: /[ap]\.?m\.?/, + y: /\d{2}|\d{4}/, + Y: /\d{4}/, + z: /Z|[+-]\d{2}(?::?\d{2})?/ +}; + +keys.m = keys.I; +keys.S = keys.M; + +var currentLanguage; + +var recompile = function(language){ + currentLanguage = language; + + keys.a = keys.A = regexOf('days'); + keys.b = keys.B = regexOf('months'); + + parsePatterns.each(function(pattern, i){ + if (pattern.format) parsePatterns[i] = build(pattern.format); + }); +}; + +var build = function(format){ + if (!currentLanguage) return {format: format}; + + var parsed = []; + var re = (format.source || format) // allow format to be regex + .replace(/%([a-z])/gi, + function($0, $1){ + return replacers($1) || $0; + } + ).replace(/\((?!\?)/g, '(?:') // make all groups non-capturing + .replace(/ (?!\?|\*)/g, ',? ') // be forgiving with spaces and commas + .replace(/%([a-z%])/gi, + function($0, $1){ + var p = keys[$1]; + if (!p) return $1; + parsed.push($1); + return '(' + p.source + ')'; + } + ).replace(/\[a-z\]/gi, '[a-z\\u00c0-\\uffff;\&]'); // handle unicode words + + return { + format: format, + re: new RegExp('^' + re + '$', 'i'), + handler: function(bits){ + bits = bits.slice(1).associate(parsed); + var date = new Date().clearTime(), + year = bits.y || bits.Y; + + if (year != null) handle.call(date, 'y', year); // need to start in the right year + if ('d' in bits) handle.call(date, 'd', 1); + if ('m' in bits || bits.b || bits.B) handle.call(date, 'm', 1); + + for (var key in bits) handle.call(date, key, bits[key]); + return date; + } + }; +}; + +var handle = function(key, value){ + if (!value) return this; + + switch (key){ + case 'a': case 'A': return this.set('day', Date.parseDay(value, true)); + case 'b': case 'B': return this.set('mo', Date.parseMonth(value, true)); + case 'd': return this.set('date', value); + case 'H': case 'I': return this.set('hr', value); + case 'm': return this.set('mo', value - 1); + case 'M': return this.set('min', value); + case 'p': return this.set('ampm', value.replace(/\./g, '')); + case 'S': return this.set('sec', value); + case 's': return this.set('ms', ('0.' + value) * 1000); + case 'w': return this.set('day', value); + case 'Y': return this.set('year', value); + case 'y': + value = +value; + if (value < 100) value += startCentury + (value < startYear ? 100 : 0); + return this.set('year', value); + case 'z': + if (value == 'Z') value = '+00'; + var offset = value.match(/([+-])(\d{2}):?(\d{2})?/); + offset = (offset[1] + '1') * (offset[2] * 60 + (+offset[3] || 0)) + this.getTimezoneOffset(); + return this.set('time', this - offset * 60000); + } + + return this; +}; + +Date.defineParsers( + '%Y([-./]%m([-./]%d((T| )%X)?)?)?', // "1999-12-31", "1999-12-31 11:59pm", "1999-12-31 23:59:59", ISO8601 + '%Y%m%d(T%H(%M%S?)?)?', // "19991231", "19991231T1159", compact + '%x( %X)?', // "12/31", "12.31.99", "12-31-1999", "12/31/2008 11:59 PM" + '%d%o( %b( %Y)?)?( %X)?', // "31st", "31st December", "31 Dec 1999", "31 Dec 1999 11:59pm" + '%b( %d%o)?( %Y)?( %X)?', // Same as above with month and day switched + '%Y %b( %d%o( %X)?)?', // Same as above with year coming first + '%o %b %d %X %z %Y', // "Thu Oct 22 08:11:23 +0000 2009" + '%T', // %H:%M:%S + '%H:%M( ?%p)?' // "11:05pm", "11:05 am" and "11:05" +); + +Locale.addEvent('change', function(language){ + if (Locale.get('Date')) recompile(language); +}).fireEvent('change', Locale.getCurrent()); + +})(); + +/* +--- + +name: Locale.en-US.Form.Validator + +description: Form Validator messages for English. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Locale + +provides: [Locale.en-US.Form.Validator] + +... +*/ + +Locale.define('en-US', 'FormValidator', { + + required: 'This field is required.', + length: 'Please enter {length} characters (you entered {elLength} characters)', + minLength: 'Please enter at least {minLength} characters (you entered {length} characters).', + maxLength: 'Please enter no more than {maxLength} characters (you entered {length} characters).', + integer: 'Please enter an integer in this field. Numbers with decimals (e.g. 1.25) are not permitted.', + numeric: 'Please enter only numeric values in this field (i.e. "1" or "1.1" or "-1" or "-1.1").', + digits: 'Please use numbers and punctuation only in this field (for example, a phone number with dashes or dots is permitted).', + alpha: 'Please use only letters (a-z) within this field. No spaces or other characters are allowed.', + alphanum: 'Please use only letters (a-z) or numbers (0-9) in this field. No spaces or other characters are allowed.', + dateSuchAs: 'Please enter a valid date such as {date}', + dateInFormatMDY: 'Please enter a valid date such as MM/DD/YYYY (i.e. "12/31/1999")', + email: 'Please enter a valid email address. For example "fred@domain.com".', + url: 'Please enter a valid URL such as http://www.example.com.', + currencyDollar: 'Please enter a valid $ amount. For example $100.00 .', + oneRequired: 'Please enter something for at least one of these inputs.', + errorPrefix: 'Error: ', + warningPrefix: 'Warning: ', + + // Form.Validator.Extras + noSpace: 'There can be no spaces in this input.', + reqChkByNode: 'No items are selected.', + requiredChk: 'This field is required.', + reqChkByName: 'Please select a {label}.', + match: 'This field needs to match the {matchName} field', + startDate: 'the start date', + endDate: 'the end date', + currentDate: 'the current date', + afterDate: 'The date should be the same or after {label}.', + beforeDate: 'The date should be the same or before {label}.', + startMonth: 'Please select a start month', + sameMonth: 'These two dates must be in the same month - you must change one or the other.', + creditcard: 'The credit card number entered is invalid. Please check the number and try again. {length} digits entered.' + +}); + +/* +--- + +script: Form.Validator.js + +name: Form.Validator + +description: A css-class based form validation system. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Options + - Core/Events + - Core/Element.Delegation + - Core/Slick.Finder + - Core/Element.Event + - Core/Element.Style + - Core/JSON + - Locale + - Class.Binds + - Date + - Element.Forms + - Locale.en-US.Form.Validator + - Element.Shortcuts + +provides: [Form.Validator, InputValidator, FormValidator.BaseValidators] + +... +*/ +if (!window.Form) window.Form = {}; + +var InputValidator = this.InputValidator = new Class({ + + Implements: [Options], + + options: { + errorMsg: 'Validation failed.', + test: Function.from(true) + }, + + initialize: function(className, options){ + this.setOptions(options); + this.className = className; + }, + + test: function(field, props){ + field = document.id(field); + return (field) ? this.options.test(field, props || this.getProps(field)) : false; + }, + + getError: function(field, props){ + field = document.id(field); + var err = this.options.errorMsg; + if (typeOf(err) == 'function') err = err(field, props || this.getProps(field)); + return err; + }, + + getProps: function(field){ + field = document.id(field); + return (field) ? field.get('validatorProps') : {}; + } + +}); + +Element.Properties.validators = { + + get: function(){ + return (this.get('data-validators') || this.className).clean().split(' '); + } + +}; + +Element.Properties.validatorProps = { + + set: function(props){ + return this.eliminate('$moo:validatorProps').store('$moo:validatorProps', props); + }, + + get: function(props){ + if (props) this.set(props); + if (this.retrieve('$moo:validatorProps')) return this.retrieve('$moo:validatorProps'); + if (this.getProperty('data-validator-properties') || this.getProperty('validatorProps')){ + try { + this.store('$moo:validatorProps', JSON.decode(this.getProperty('validatorProps') || this.getProperty('data-validator-properties'), false)); + }catch(e){ + return {}; + } + } else { + var vals = this.get('validators').filter(function(cls){ + return cls.test(':'); + }); + if (!vals.length){ + this.store('$moo:validatorProps', {}); + } else { + props = {}; + vals.each(function(cls){ + var split = cls.split(':'); + if (split[1]){ + try { + props[split[0]] = JSON.decode(split[1], false); + } catch(e){} + } + }); + this.store('$moo:validatorProps', props); + } + } + return this.retrieve('$moo:validatorProps'); + } + +}; + +Form.Validator = new Class({ + + Implements: [Options, Events], + + options: {/* + onFormValidate: function(isValid, form, event){}, + onElementValidate: function(isValid, field, className, warn){}, + onElementPass: function(field){}, + onElementFail: function(field, validatorsFailed){}, */ + fieldSelectors: 'input, select, textarea', + ignoreHidden: true, + ignoreDisabled: true, + useTitles: false, + evaluateOnSubmit: true, + evaluateFieldsOnBlur: true, + evaluateFieldsOnChange: true, + serial: true, + stopOnFailure: true, + warningPrefix: function(){ + return Form.Validator.getMsg('warningPrefix') || 'Warning: '; + }, + errorPrefix: function(){ + return Form.Validator.getMsg('errorPrefix') || 'Error: '; + } + }, + + initialize: function(form, options){ + this.setOptions(options); + this.element = document.id(form); + this.warningPrefix = Function.from(this.options.warningPrefix)(); + this.errorPrefix = Function.from(this.options.errorPrefix)(); + this._bound = { + onSubmit: this.onSubmit.bind(this), + blurOrChange: function(event, field){ + this.validationMonitor(field, true); + }.bind(this) + }; + this.enable(); + }, + + toElement: function(){ + return this.element; + }, + + getFields: function(){ + return (this.fields = this.element.getElements(this.options.fieldSelectors)); + }, + + enable: function(){ + this.element.store('validator', this); + if (this.options.evaluateOnSubmit) this.element.addEvent('submit', this._bound.onSubmit); + if (this.options.evaluateFieldsOnBlur){ + this.element.addEvent('blur:relay(input,select,textarea)', this._bound.blurOrChange); + } + if (this.options.evaluateFieldsOnChange){ + this.element.addEvent('change:relay(input,select,textarea)', this._bound.blurOrChange); + } + }, + + disable: function(){ + this.element.eliminate('validator'); + this.element.removeEvents({ + submit: this._bound.onSubmit, + 'blur:relay(input,select,textarea)': this._bound.blurOrChange, + 'change:relay(input,select,textarea)': this._bound.blurOrChange + }); + }, + + validationMonitor: function(){ + clearTimeout(this.timer); + this.timer = this.validateField.delay(50, this, arguments); + }, + + onSubmit: function(event){ + if (this.validate(event)) this.reset(); + }, + + reset: function(){ + this.getFields().each(this.resetField, this); + return this; + }, + + validate: function(event){ + var result = this.getFields().map(function(field){ + return this.validateField(field, true); + }, this).every(function(v){ + return v; + }); + this.fireEvent('formValidate', [result, this.element, event]); + if (this.options.stopOnFailure && !result && event) event.preventDefault(); + return result; + }, + + validateField: function(field, force){ + if (this.paused) return true; + field = document.id(field); + var passed = !field.hasClass('validation-failed'); + var failed, warned; + if (this.options.serial && !force){ + failed = this.element.getElement('.validation-failed'); + warned = this.element.getElement('.warning'); + } + if (field && (!failed || force || field.hasClass('validation-failed') || (failed && !this.options.serial))){ + var validationTypes = field.get('validators'); + var validators = validationTypes.some(function(cn){ + return this.getValidator(cn); + }, this); + var validatorsFailed = []; + validationTypes.each(function(className){ + if (className && !this.test(className, field)) validatorsFailed.include(className); + }, this); + passed = validatorsFailed.length === 0; + if (validators && !this.hasValidator(field, 'warnOnly')){ + if (passed){ + field.addClass('validation-passed').removeClass('validation-failed'); + this.fireEvent('elementPass', [field]); + } else { + field.addClass('validation-failed').removeClass('validation-passed'); + this.fireEvent('elementFail', [field, validatorsFailed]); + } + } + if (!warned){ + var warnings = validationTypes.some(function(cn){ + if (cn.test('^warn')) + return this.getValidator(cn.replace(/^warn-/,'')); + else return null; + }, this); + field.removeClass('warning'); + var warnResult = validationTypes.map(function(cn){ + if (cn.test('^warn')) + return this.test(cn.replace(/^warn-/,''), field, true); + else return null; + }, this); + } + } + return passed; + }, + + test: function(className, field, warn){ + field = document.id(field); + if ((this.options.ignoreHidden && !field.isVisible()) || (this.options.ignoreDisabled && field.get('disabled'))) return true; + var validator = this.getValidator(className); + if (warn != null) warn = false; + if (this.hasValidator(field, 'warnOnly')) warn = true; + var isValid = field.hasClass('ignoreValidation') || (validator ? validator.test(field) : true); + if (validator) this.fireEvent('elementValidate', [isValid, field, className, warn]); + if (warn) return true; + return isValid; + }, + + hasValidator: function(field, value){ + return field.get('validators').contains(value); + }, + + resetField: function(field){ + field = document.id(field); + if (field){ + field.get('validators').each(function(className){ + if (className.test('^warn-')) className = className.replace(/^warn-/, ''); + field.removeClass('validation-failed'); + field.removeClass('warning'); + field.removeClass('validation-passed'); + }, this); + } + return this; + }, + + stop: function(){ + this.paused = true; + return this; + }, + + start: function(){ + this.paused = false; + return this; + }, + + ignoreField: function(field, warn){ + field = document.id(field); + if (field){ + this.enforceField(field); + if (warn) field.addClass('warnOnly'); + else field.addClass('ignoreValidation'); + } + return this; + }, + + enforceField: function(field){ + field = document.id(field); + if (field) field.removeClass('warnOnly').removeClass('ignoreValidation'); + return this; + } + +}); + +Form.Validator.getMsg = function(key){ + return Locale.get('FormValidator.' + key); +}; + +Form.Validator.adders = { + + validators:{}, + + add : function(className, options){ + this.validators[className] = new InputValidator(className, options); + //if this is a class (this method is used by instances of Form.Validator and the Form.Validator namespace) + //extend these validators into it + //this allows validators to be global and/or per instance + if (!this.initialize){ + this.implement({ + validators: this.validators + }); + } + }, + + addAllThese : function(validators){ + Array.from(validators).each(function(validator){ + this.add(validator[0], validator[1]); + }, this); + }, + + getValidator: function(className){ + return this.validators[className.split(':')[0]]; + } + +}; + +Object.append(Form.Validator, Form.Validator.adders); + +Form.Validator.implement(Form.Validator.adders); + +Form.Validator.add('IsEmpty', { + + errorMsg: false, + test: function(element){ + if (element.type == 'select-one' || element.type == 'select') + return !(element.selectedIndex >= 0 && element.options[element.selectedIndex].value != ''); + else + return ((element.get('value') == null) || (element.get('value').length == 0)); + } + +}); + +Form.Validator.addAllThese([ + + ['required', { + errorMsg: function(){ + return Form.Validator.getMsg('required'); + }, + test: function(element){ + return !Form.Validator.getValidator('IsEmpty').test(element); + } + }], + + ['length', { + errorMsg: function(element, props){ + if (typeOf(props.length) != 'null') + return Form.Validator.getMsg('length').substitute({length: props.length, elLength: element.get('value').length}); + else return ''; + }, + test: function(element, props){ + if (typeOf(props.length) != 'null') return (element.get('value').length == props.length || element.get('value').length == 0); + else return true; + } + }], + + ['minLength', { + errorMsg: function(element, props){ + if (typeOf(props.minLength) != 'null') + return Form.Validator.getMsg('minLength').substitute({minLength: props.minLength, length: element.get('value').length}); + else return ''; + }, + test: function(element, props){ + if (typeOf(props.minLength) != 'null') return (element.get('value').length >= (props.minLength || 0)); + else return true; + } + }], + + ['maxLength', { + errorMsg: function(element, props){ + //props is {maxLength:10} + if (typeOf(props.maxLength) != 'null') + return Form.Validator.getMsg('maxLength').substitute({maxLength: props.maxLength, length: element.get('value').length}); + else return ''; + }, + test: function(element, props){ + return element.get('value').length <= (props.maxLength || 10000); + } + }], + + ['validate-integer', { + errorMsg: Form.Validator.getMsg.pass('integer'), + test: function(element){ + return Form.Validator.getValidator('IsEmpty').test(element) || (/^(-?[1-9]\d*|0)$/).test(element.get('value')); + } + }], + + ['validate-numeric', { + errorMsg: Form.Validator.getMsg.pass('numeric'), + test: function(element){ + return Form.Validator.getValidator('IsEmpty').test(element) || + (/^-?(?:0$0(?=\d*\.)|[1-9]|0)\d*(\.\d+)?$/).test(element.get('value')); + } + }], + + ['validate-digits', { + errorMsg: Form.Validator.getMsg.pass('digits'), + test: function(element){ + return Form.Validator.getValidator('IsEmpty').test(element) || (/^[\d() .:\-\+#]+$/.test(element.get('value'))); + } + }], + + ['validate-alpha', { + errorMsg: Form.Validator.getMsg.pass('alpha'), + test: function(element){ + return Form.Validator.getValidator('IsEmpty').test(element) || (/^[a-zA-Z]+$/).test(element.get('value')); + } + }], + + ['validate-alphanum', { + errorMsg: Form.Validator.getMsg.pass('alphanum'), + test: function(element){ + return Form.Validator.getValidator('IsEmpty').test(element) || !(/\W/).test(element.get('value')); + } + }], + + ['validate-date', { + errorMsg: function(element, props){ + if (Date.parse){ + var format = props.dateFormat || '%x'; + return Form.Validator.getMsg('dateSuchAs').substitute({date: new Date().format(format)}); + } else { + return Form.Validator.getMsg('dateInFormatMDY'); + } + }, + test: function(element, props){ + if (Form.Validator.getValidator('IsEmpty').test(element)) return true; + var dateLocale = Locale.get('Date'), + dateNouns = new RegExp([dateLocale.days, dateLocale.days_abbr, dateLocale.months, dateLocale.months_abbr, dateLocale.AM, dateLocale.PM].flatten().join('|'), 'i'), + value = element.get('value'), + wordsInValue = value.match(/[a-z]+/gi); + + if (wordsInValue && !wordsInValue.every(dateNouns.exec, dateNouns)) return false; + + var date = Date.parse(value); + if (!date) return false; + + var format = props.dateFormat || '%x', + formatted = date.format(format); + if (formatted != 'invalid date') element.set('value', formatted); + return date.isValid(); + } + }], + + ['validate-email', { + errorMsg: Form.Validator.getMsg.pass('email'), + test: function(element){ + /* + var chars = "[a-z0-9!#$%&'*+/=?^_`{|}~-]", + local = '(?:' + chars + '\\.?){0,63}' + chars, + + label = '[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?', + hostname = '(?:' + label + '\\.)*' + label; + + octet = '(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)', + ipv4 = '\\[(?:' + octet + '\\.){3}' + octet + '\\]', + + domain = '(?:' + hostname + '|' + ipv4 + ')'; + + var regex = new RegExp('^' + local + '@' + domain + '$', 'i'); + */ + return Form.Validator.getValidator('IsEmpty').test(element) || (/^(?:[a-z0-9!#$%&'*+\/=?^_`{|}~-]\.?){0,63}[a-z0-9!#$%&'*+\/=?^_`{|}~-]@(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\])$/i).test(element.get('value')); + } + }], + + ['validate-url', { + errorMsg: Form.Validator.getMsg.pass('url'), + test: function(element){ + return Form.Validator.getValidator('IsEmpty').test(element) || (/^(https?|ftp|rmtp|mms):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\/?/i).test(element.get('value')); + } + }], + + ['validate-currency-dollar', { + errorMsg: Form.Validator.getMsg.pass('currencyDollar'), + test: function(element){ + return Form.Validator.getValidator('IsEmpty').test(element) || (/^\$?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(element.get('value')); + } + }], + + ['validate-one-required', { + errorMsg: Form.Validator.getMsg.pass('oneRequired'), + test: function(element, props){ + var p = document.id(props['validate-one-required']) || element.getParent(props['validate-one-required']); + return p.getElements('input').some(function(el){ + if (['checkbox', 'radio'].contains(el.get('type'))) return el.get('checked'); + return el.get('value'); + }); + } + }] + +]); + +Element.Properties.validator = { + + set: function(options){ + this.get('validator').setOptions(options); + }, + + get: function(){ + var validator = this.retrieve('validator'); + if (!validator){ + validator = new Form.Validator(this); + this.store('validator', validator); + } + return validator; + } + +}; + +Element.implement({ + + validate: function(options){ + if (options) this.set('validator', options); + return this.get('validator').validate(); + } + +}); + + + + + + +/* +--- + +script: Form.Validator.Extras.js + +name: Form.Validator.Extras + +description: Additional validators for the Form.Validator class. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Form.Validator + +provides: [Form.Validator.Extras] + +... +*/ +Form.Validator.addAllThese([ + + ['validate-enforce-oncheck', { + test: function(element, props){ + var fv = element.getParent('form').retrieve('validator'); + if (!fv) return true; + (props.toEnforce || document.id(props.enforceChildrenOf).getElements('input, select, textarea')).map(function(item){ + if (element.checked){ + fv.enforceField(item); + } else { + fv.ignoreField(item); + fv.resetField(item); + } + }); + return true; + } + }], + + ['validate-ignore-oncheck', { + test: function(element, props){ + var fv = element.getParent('form').retrieve('validator'); + if (!fv) return true; + (props.toIgnore || document.id(props.ignoreChildrenOf).getElements('input, select, textarea')).each(function(item){ + if (element.checked){ + fv.ignoreField(item); + fv.resetField(item); + } else { + fv.enforceField(item); + } + }); + return true; + } + }], + + ['validate-nospace', { + errorMsg: function(){ + return Form.Validator.getMsg('noSpace'); + }, + test: function(element, props){ + return !element.get('value').test(/\s/); + } + }], + + ['validate-toggle-oncheck', { + test: function(element, props){ + var fv = element.getParent('form').retrieve('validator'); + if (!fv) return true; + var eleArr = props.toToggle || document.id(props.toToggleChildrenOf).getElements('input, select, textarea'); + if (!element.checked){ + eleArr.each(function(item){ + fv.ignoreField(item); + fv.resetField(item); + }); + } else { + eleArr.each(function(item){ + fv.enforceField(item); + }); + } + return true; + } + }], + + ['validate-reqchk-bynode', { + errorMsg: function(){ + return Form.Validator.getMsg('reqChkByNode'); + }, + test: function(element, props){ + return (document.id(props.nodeId).getElements(props.selector || 'input[type=checkbox], input[type=radio]')).some(function(item){ + return item.checked; + }); + } + }], + + ['validate-required-check', { + errorMsg: function(element, props){ + return props.useTitle ? element.get('title') : Form.Validator.getMsg('requiredChk'); + }, + test: function(element, props){ + return !!element.checked; + } + }], + + ['validate-reqchk-byname', { + errorMsg: function(element, props){ + return Form.Validator.getMsg('reqChkByName').substitute({label: props.label || element.get('type')}); + }, + test: function(element, props){ + var grpName = props.groupName || element.get('name'); + var oneCheckedItem = $$(document.getElementsByName(grpName)).some(function(item, index){ + return item.checked; + }); + var fv = element.getParent('form').retrieve('validator'); + if (oneCheckedItem && fv) fv.resetField(element); + return oneCheckedItem; + } + }], + + ['validate-match', { + errorMsg: function(element, props){ + return Form.Validator.getMsg('match').substitute({matchName: props.matchName || document.id(props.matchInput).get('name')}); + }, + test: function(element, props){ + var eleVal = element.get('value'); + var matchVal = document.id(props.matchInput) && document.id(props.matchInput).get('value'); + return eleVal && matchVal ? eleVal == matchVal : true; + } + }], + + ['validate-after-date', { + errorMsg: function(element, props){ + return Form.Validator.getMsg('afterDate').substitute({ + label: props.afterLabel || (props.afterElement ? Form.Validator.getMsg('startDate') : Form.Validator.getMsg('currentDate')) + }); + }, + test: function(element, props){ + var start = document.id(props.afterElement) ? Date.parse(document.id(props.afterElement).get('value')) : new Date(); + var end = Date.parse(element.get('value')); + return end && start ? end >= start : true; + } + }], + + ['validate-before-date', { + errorMsg: function(element, props){ + return Form.Validator.getMsg('beforeDate').substitute({ + label: props.beforeLabel || (props.beforeElement ? Form.Validator.getMsg('endDate') : Form.Validator.getMsg('currentDate')) + }); + }, + test: function(element, props){ + var start = Date.parse(element.get('value')); + var end = document.id(props.beforeElement) ? Date.parse(document.id(props.beforeElement).get('value')) : new Date(); + return end && start ? end >= start : true; + } + }], + + ['validate-custom-required', { + errorMsg: function(){ + return Form.Validator.getMsg('required'); + }, + test: function(element, props){ + return element.get('value') != props.emptyValue; + } + }], + + ['validate-same-month', { + errorMsg: function(element, props){ + var startMo = document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value'); + var eleVal = element.get('value'); + if (eleVal != '') return Form.Validator.getMsg(startMo ? 'sameMonth' : 'startMonth'); + }, + test: function(element, props){ + var d1 = Date.parse(element.get('value')); + var d2 = Date.parse(document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value')); + return d1 && d2 ? d1.format('%B') == d2.format('%B') : true; + } + }], + + + ['validate-cc-num', { + errorMsg: function(element){ + var ccNum = element.get('value').replace(/[^0-9]/g, ''); + return Form.Validator.getMsg('creditcard').substitute({length: ccNum.length}); + }, + test: function(element){ + // required is a different test + if (Form.Validator.getValidator('IsEmpty').test(element)) return true; + + // Clean number value + var ccNum = element.get('value'); + ccNum = ccNum.replace(/[^0-9]/g, ''); + + var valid_type = false; + + if (ccNum.test(/^4[0-9]{12}([0-9]{3})?$/)) valid_type = 'Visa'; + else if (ccNum.test(/^5[1-5]([0-9]{14})$/)) valid_type = 'Master Card'; + else if (ccNum.test(/^3[47][0-9]{13}$/)) valid_type = 'American Express'; + else if (ccNum.test(/^6(?:011|5[0-9]{2})[0-9]{12}$/)) valid_type = 'Discover'; + else if (ccNum.test(/^3(?:0[0-5]|[68][0-9])[0-9]{11}$/)) valid_type = 'Diners Club'; + + if (valid_type){ + var sum = 0; + var cur = 0; + + for (var i=ccNum.length-1; i>=0; --i){ + cur = ccNum.charAt(i).toInt(); + if (cur == 0) continue; + + if ((ccNum.length-i) % 2 == 0) cur += cur; + if (cur > 9){ + cur = cur.toString().charAt(0).toInt() + cur.toString().charAt(1).toInt(); + } + + sum += cur; + } + if ((sum % 10) == 0) return true; + } + + var chunks = ''; + while (ccNum != ''){ + chunks += ' ' + ccNum.substr(0,4); + ccNum = ccNum.substr(4); + } + + element.getParent('form').retrieve('validator').ignoreField(element); + element.set('value', chunks.clean()); + element.getParent('form').retrieve('validator').enforceField(element); + return false; + } + }] + + +]); + +/* +--- + +script: Form.Validator.Inline.js + +name: Form.Validator.Inline + +description: Extends Form.Validator to add inline messages. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Form.Validator + +provides: [Form.Validator.Inline] + +... +*/ + +Form.Validator.Inline = new Class({ + + Extends: Form.Validator, + + options: { + showError: function(errorElement){ + if (errorElement.reveal) errorElement.reveal(); + else errorElement.setStyle('display', 'block'); + }, + hideError: function(errorElement){ + if (errorElement.dissolve) errorElement.dissolve(); + else errorElement.setStyle('display', 'none'); + }, + scrollToErrorsOnSubmit: true, + scrollToErrorsOnBlur: false, + scrollToErrorsOnChange: false, + scrollFxOptions: { + transition: 'quad:out', + offset: { + y: -20 + } + } + }, + + initialize: function(form, options){ + this.parent(form, options); + this.addEvent('onElementValidate', function(isValid, field, className, warn){ + var validator = this.getValidator(className); + if (!isValid && validator.getError(field)){ + if (warn) field.addClass('warning'); + var advice = this.makeAdvice(className, field, validator.getError(field), warn); + this.insertAdvice(advice, field); + this.showAdvice(className, field); + } else { + this.hideAdvice(className, field); + } + }); + }, + + makeAdvice: function(className, field, error, warn){ + var errorMsg = (warn) ? this.warningPrefix : this.errorPrefix; + errorMsg += (this.options.useTitles) ? field.title || error:error; + var cssClass = (warn) ? 'warning-advice' : 'validation-advice'; + var advice = this.getAdvice(className, field); + if (advice){ + advice = advice.set('html', errorMsg); + } else { + advice = new Element('div', { + html: errorMsg, + styles: { display: 'none' }, + id: 'advice-' + className.split(':')[0] + '-' + this.getFieldId(field) + }).addClass(cssClass); + } + field.store('$moo:advice-' + className, advice); + return advice; + }, + + getFieldId : function(field){ + return field.id ? field.id : field.id = 'input_' + field.name; + }, + + showAdvice: function(className, field){ + var advice = this.getAdvice(className, field); + if ( + advice && + !field.retrieve('$moo:' + this.getPropName(className)) && + ( + advice.getStyle('display') == 'none' || + advice.getStyle('visibility') == 'hidden' || + advice.getStyle('opacity') == 0 + ) + ){ + field.store('$moo:' + this.getPropName(className), true); + this.options.showError(advice); + this.fireEvent('showAdvice', [field, advice, className]); + } + }, + + hideAdvice: function(className, field){ + var advice = this.getAdvice(className, field); + if (advice && field.retrieve('$moo:' + this.getPropName(className))){ + field.store('$moo:' + this.getPropName(className), false); + this.options.hideError(advice); + this.fireEvent('hideAdvice', [field, advice, className]); + } + }, + + getPropName: function(className){ + return 'advice' + className; + }, + + resetField: function(field){ + field = document.id(field); + if (!field) return this; + this.parent(field); + field.get('validators').each(function(className){ + this.hideAdvice(className, field); + }, this); + return this; + }, + + getAllAdviceMessages: function(field, force){ + var advice = []; + if (field.hasClass('ignoreValidation') && !force) return advice; + var validators = field.get('validators').some(function(cn){ + var warner = cn.test('^warn-') || field.hasClass('warnOnly'); + if (warner) cn = cn.replace(/^warn-/, ''); + var validator = this.getValidator(cn); + if (!validator) return; + advice.push({ + message: validator.getError(field), + warnOnly: warner, + passed: validator.test(), + validator: validator + }); + }, this); + return advice; + }, + + getAdvice: function(className, field){ + return field.retrieve('$moo:advice-' + className); + }, + + insertAdvice: function(advice, field){ + //Check for error position prop + var props = field.get('validatorProps'); + //Build advice + if (!props.msgPos || !document.id(props.msgPos)){ + if (field.type && field.type.toLowerCase() == 'radio') field.getParent().adopt(advice); + else advice.inject(document.id(field), 'after'); + } else { + document.id(props.msgPos).grab(advice); + } + }, + + validateField: function(field, force, scroll){ + var result = this.parent(field, force); + if (((this.options.scrollToErrorsOnSubmit && scroll == null) || scroll) && !result){ + var failed = document.id(this).getElement('.validation-failed'); + var par = document.id(this).getParent(); + while (par != document.body && par.getScrollSize().y == par.getSize().y){ + par = par.getParent(); + } + var fx = par.retrieve('$moo:fvScroller'); + if (!fx && window.Fx && Fx.Scroll){ + fx = new Fx.Scroll(par, this.options.scrollFxOptions); + par.store('$moo:fvScroller', fx); + } + if (failed){ + if (fx) fx.toElement(failed); + else par.scrollTo(par.getScroll().x, failed.getPosition(par).y - 20); + } + } + return result; + }, + + watchFields: function(fields){ + fields.each(function(el){ + if (this.options.evaluateFieldsOnBlur){ + el.addEvent('blur', this.validationMonitor.pass([el, false, this.options.scrollToErrorsOnBlur], this)); + } + if (this.options.evaluateFieldsOnChange){ + el.addEvent('change', this.validationMonitor.pass([el, true, this.options.scrollToErrorsOnChange], this)); + } + }, this); + } + +}); + +/* +--- + +script: OverText.js + +name: OverText + +description: Shows text over an input that disappears when the user clicks into it. The text remains hidden if the user adds a value. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Options + - Core/Events + - Core/Element.Event + - Class.Binds + - Class.Occlude + - Element.Position + - Element.Shortcuts + +provides: [OverText] + +... +*/ + +var OverText = new Class({ + + Implements: [Options, Events, Class.Occlude], + + Binds: ['reposition', 'assert', 'focus', 'hide'], + + options: {/* + textOverride: null, + onFocus: function(){}, + onTextHide: function(textEl, inputEl){}, + onTextShow: function(textEl, inputEl){}, */ + element: 'label', + labelClass: 'overTxtLabel', + positionOptions: { + position: 'upperLeft', + edge: 'upperLeft', + offset: { + x: 4, + y: 2 + } + }, + poll: false, + pollInterval: 250, + wrap: false + }, + + property: 'OverText', + + initialize: function(element, options){ + element = this.element = document.id(element); + + if (this.occlude()) return this.occluded; + this.setOptions(options); + + this.attach(element); + OverText.instances.push(this); + + if (this.options.poll) this.poll(); + }, + + toElement: function(){ + return this.element; + }, + + attach: function(){ + var element = this.element, + options = this.options, + value = options.textOverride || element.get('alt') || element.get('title'); + + if (!value) return this; + + var text = this.text = new Element(options.element, { + 'class': options.labelClass, + styles: { + lineHeight: 'normal', + position: 'absolute', + cursor: 'text' + }, + html: value, + events: { + click: this.hide.pass(options.element == 'label', this) + } + }).inject(element, 'after'); + + if (options.element == 'label'){ + if (!element.get('id')) element.set('id', 'input_' + String.uniqueID()); + text.set('for', element.get('id')); + } + + if (options.wrap){ + this.textHolder = new Element('div.overTxtWrapper', { + styles: { + lineHeight: 'normal', + position: 'relative' + } + }).grab(text).inject(element, 'before'); + } + + return this.enable(); + }, + + destroy: function(){ + this.element.eliminate(this.property); // Class.Occlude storage + this.disable(); + if (this.text) this.text.destroy(); + if (this.textHolder) this.textHolder.destroy(); + return this; + }, + + disable: function(){ + this.element.removeEvents({ + focus: this.focus, + blur: this.assert, + change: this.assert + }); + window.removeEvent('resize', this.reposition); + this.hide(true, true); + return this; + }, + + enable: function(){ + this.element.addEvents({ + focus: this.focus, + blur: this.assert, + change: this.assert + }); + window.addEvent('resize', this.reposition); + this.reposition(); + return this; + }, + + wrap: function(){ + if (this.options.element == 'label'){ + if (!this.element.get('id')) this.element.set('id', 'input_' + String.uniqueID()); + this.text.set('for', this.element.get('id')); + } + }, + + startPolling: function(){ + this.pollingPaused = false; + return this.poll(); + }, + + poll: function(stop){ + //start immediately + //pause on focus + //resumeon blur + if (this.poller && !stop) return this; + if (stop){ + clearInterval(this.poller); + } else { + this.poller = (function(){ + if (!this.pollingPaused) this.assert(true); + }).periodical(this.options.pollInterval, this); + } + + return this; + }, + + stopPolling: function(){ + this.pollingPaused = true; + return this.poll(true); + }, + + focus: function(){ + if (this.text && (!this.text.isDisplayed() || this.element.get('disabled'))) return this; + return this.hide(); + }, + + hide: function(suppressFocus, force){ + if (this.text && (this.text.isDisplayed() && (!this.element.get('disabled') || force))){ + this.text.hide(); + this.fireEvent('textHide', [this.text, this.element]); + this.pollingPaused = true; + if (!suppressFocus){ + try { + this.element.fireEvent('focus'); + this.element.focus(); + } catch(e){} //IE barfs if you call focus on hidden elements + } + } + return this; + }, + + show: function(){ + if (document.id(this.text) && !this.text.isDisplayed()){ + this.text.show(); + this.reposition(); + this.fireEvent('textShow', [this.text, this.element]); + this.pollingPaused = false; + } + return this; + }, + + test: function(){ + return !this.element.get('value'); + }, + + assert: function(suppressFocus){ + return this[this.test() ? 'show' : 'hide'](suppressFocus); + }, + + reposition: function(){ + this.assert(true); + if (!this.element.isVisible()) return this.stopPolling().hide(); + if (this.text && this.test()){ + this.text.position(Object.merge(this.options.positionOptions, { + relativeTo: this.element + })); + } + return this; + } + +}); + +OverText.instances = []; + +Object.append(OverText, { + + each: function(fn){ + return OverText.instances.each(function(ot, i){ + if (ot.element && ot.text) fn.call(OverText, ot, i); + }); + }, + + update: function(){ + + return OverText.each(function(ot){ + return ot.reposition(); + }); + + }, + + hideAll: function(){ + + return OverText.each(function(ot){ + return ot.hide(true, true); + }); + + }, + + showAll: function(){ + return OverText.each(function(ot){ + return ot.show(); + }); + } + +}); + + +/* +--- + +script: Fx.Elements.js + +name: Fx.Elements + +description: Effect to change any number of CSS properties of any number of Elements. + +license: MIT-style license + +authors: + - Valerio Proietti + +requires: + - Core/Fx.CSS + - MooTools.More + +provides: [Fx.Elements] + +... +*/ + +Fx.Elements = new Class({ + + Extends: Fx.CSS, + + initialize: function(elements, options){ + this.elements = this.subject = $$(elements); + this.parent(options); + }, + + compute: function(from, to, delta){ + var now = {}; + + for (var i in from){ + var iFrom = from[i], iTo = to[i], iNow = now[i] = {}; + for (var p in iFrom) iNow[p] = this.parent(iFrom[p], iTo[p], delta); + } + + return now; + }, + + set: function(now){ + for (var i in now){ + if (!this.elements[i]) continue; + + var iNow = now[i]; + for (var p in iNow) this.render(this.elements[i], p, iNow[p], this.options.unit); + } + + return this; + }, + + start: function(obj){ + if (!this.check(obj)) return this; + var from = {}, to = {}; + + for (var i in obj){ + if (!this.elements[i]) continue; + + var iProps = obj[i], iFrom = from[i] = {}, iTo = to[i] = {}; + + for (var p in iProps){ + var parsed = this.prepare(this.elements[i], p, iProps[p]); + iFrom[p] = parsed.from; + iTo[p] = parsed.to; + } + } + + return this.parent(from, to); + } + +}); + +/* +--- + +script: Fx.Accordion.js + +name: Fx.Accordion + +description: An Fx.Elements extension which allows you to easily create accordion type controls. + +license: MIT-style license + +authors: + - Valerio Proietti + +requires: + - Core/Element.Event + - Fx.Elements + +provides: [Fx.Accordion] + +... +*/ + +Fx.Accordion = new Class({ + + Extends: Fx.Elements, + + options: {/* + onActive: function(toggler, section){}, + onBackground: function(toggler, section){},*/ + fixedHeight: false, + fixedWidth: false, + display: 0, + show: false, + height: true, + width: false, + opacity: true, + alwaysHide: false, + trigger: 'click', + initialDisplayFx: true, + resetHeight: true + }, + + initialize: function(){ + var defined = function(obj){ + return obj != null; + }; + + var params = Array.link(arguments, { + 'container': Type.isElement, //deprecated + 'options': Type.isObject, + 'togglers': defined, + 'elements': defined + }); + this.parent(params.elements, params.options); + + var options = this.options, + togglers = this.togglers = $$(params.togglers); + + this.previous = -1; + this.internalChain = new Chain(); + + if (options.alwaysHide) this.options.link = 'chain'; + + if (options.show || this.options.show === 0){ + options.display = false; + this.previous = options.show; + } + + if (options.start){ + options.display = false; + options.show = false; + } + + var effects = this.effects = {}; + + if (options.opacity) effects.opacity = 'fullOpacity'; + if (options.width) effects.width = options.fixedWidth ? 'fullWidth' : 'offsetWidth'; + if (options.height) effects.height = options.fixedHeight ? 'fullHeight' : 'scrollHeight'; + + for (var i = 0, l = togglers.length; i < l; i++) this.addSection(togglers[i], this.elements[i]); + + this.elements.each(function(el, i){ + if (options.show === i){ + this.fireEvent('active', [togglers[i], el]); + } else { + for (var fx in effects) el.setStyle(fx, 0); + } + }, this); + + if (options.display || options.display === 0 || options.initialDisplayFx === false){ + this.display(options.display, options.initialDisplayFx); + } + + if (options.fixedHeight !== false) options.resetHeight = false; + this.addEvent('complete', this.internalChain.callChain.bind(this.internalChain)); + }, + + addSection: function(toggler, element){ + toggler = document.id(toggler); + element = document.id(element); + this.togglers.include(toggler); + this.elements.include(element); + + var togglers = this.togglers, + options = this.options, + test = togglers.contains(toggler), + idx = togglers.indexOf(toggler), + displayer = this.display.pass(idx, this); + + toggler.store('accordion:display', displayer) + .addEvent(options.trigger, displayer); + + if (options.height) element.setStyles({'padding-top': 0, 'border-top': 'none', 'padding-bottom': 0, 'border-bottom': 'none'}); + if (options.width) element.setStyles({'padding-left': 0, 'border-left': 'none', 'padding-right': 0, 'border-right': 'none'}); + + element.fullOpacity = 1; + if (options.fixedWidth) element.fullWidth = options.fixedWidth; + if (options.fixedHeight) element.fullHeight = options.fixedHeight; + element.setStyle('overflow', 'hidden'); + + if (!test) for (var fx in this.effects){ + element.setStyle(fx, 0); + } + return this; + }, + + removeSection: function(toggler, displayIndex){ + var togglers = this.togglers, + idx = togglers.indexOf(toggler), + element = this.elements[idx]; + + var remover = function(){ + togglers.erase(toggler); + this.elements.erase(element); + this.detach(toggler); + }.bind(this); + + if (this.now == idx || displayIndex != null){ + this.display(displayIndex != null ? displayIndex : (idx - 1 >= 0 ? idx - 1 : 0)).chain(remover); + } else { + remover(); + } + return this; + }, + + detach: function(toggler){ + var remove = function(toggler){ + toggler.removeEvent(this.options.trigger, toggler.retrieve('accordion:display')); + }.bind(this); + + if (!toggler) this.togglers.each(remove); + else remove(toggler); + return this; + }, + + display: function(index, useFx){ + if (!this.check(index, useFx)) return this; + + var obj = {}, + elements = this.elements, + options = this.options, + effects = this.effects; + + if (useFx == null) useFx = true; + if (typeOf(index) == 'element') index = elements.indexOf(index); + if (index == this.current && !options.alwaysHide) return this; + + if (options.resetHeight){ + var prev = elements[this.current]; + if (prev && !this.selfHidden){ + for (var fx in effects) prev.setStyle(fx, prev[effects[fx]]); + } + } + + if ((this.timer && options.link == 'chain') || (index === this.current && !options.alwaysHide)) return this; + + if (this.current != null) this.previous = this.current; + this.current = index; + this.selfHidden = false; + + elements.each(function(el, i){ + obj[i] = {}; + var hide; + if (i != index){ + hide = true; + } else if (options.alwaysHide && ((el.offsetHeight > 0 && options.height) || el.offsetWidth > 0 && options.width)){ + hide = true; + this.selfHidden = true; + } + this.fireEvent(hide ? 'background' : 'active', [this.togglers[i], el]); + for (var fx in effects) obj[i][fx] = hide ? 0 : el[effects[fx]]; + if (!useFx && !hide && options.resetHeight) obj[i].height = 'auto'; + }, this); + + this.internalChain.clearChain(); + this.internalChain.chain(function(){ + if (options.resetHeight && !this.selfHidden){ + var el = elements[index]; + if (el) el.setStyle('height', 'auto'); + } + }.bind(this)); + + return useFx ? this.start(obj) : this.set(obj).internalChain.callChain(); + } + +}); + + + +/* +--- + +script: Fx.Move.js + +name: Fx.Move + +description: Defines Fx.Move, a class that works with Element.Position.js to transition an element from one location to another. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Fx.Morph + - Element.Position + +provides: [Fx.Move] + +... +*/ + +Fx.Move = new Class({ + + Extends: Fx.Morph, + + options: { + relativeTo: document.body, + position: 'center', + edge: false, + offset: {x: 0, y: 0} + }, + + start: function(destination){ + var element = this.element, + topLeft = element.getStyles('top', 'left'); + if (topLeft.top == 'auto' || topLeft.left == 'auto'){ + element.setPosition(element.getPosition(element.getOffsetParent())); + } + return this.parent(element.position(Object.merge({}, this.options, destination, {returnPos: true}))); + } + +}); + +Element.Properties.move = { + + set: function(options){ + this.get('move').cancel().setOptions(options); + return this; + }, + + get: function(){ + var move = this.retrieve('move'); + if (!move){ + move = new Fx.Move(this, {link: 'cancel'}); + this.store('move', move); + } + return move; + } + +}; + +Element.implement({ + + move: function(options){ + this.get('move').start(options); + return this; + } + +}); + +/* +--- + +script: Fx.Scroll.js + +name: Fx.Scroll + +description: Effect to smoothly scroll any element, including the window. + +license: MIT-style license + +authors: + - Valerio Proietti + +requires: + - Core/Fx + - Core/Element.Event + - Core/Element.Dimensions + - MooTools.More + +provides: [Fx.Scroll] + +... +*/ + +(function(){ + +Fx.Scroll = new Class({ + + Extends: Fx, + + options: { + offset: {x: 0, y: 0}, + wheelStops: true + }, + + initialize: function(element, options){ + this.element = this.subject = document.id(element); + this.parent(options); + + if (typeOf(this.element) != 'element') this.element = document.id(this.element.getDocument().body); + + if (this.options.wheelStops){ + var stopper = this.element, + cancel = this.cancel.pass(false, this); + this.addEvent('start', function(){ + stopper.addEvent('mousewheel', cancel); + }, true); + this.addEvent('complete', function(){ + stopper.removeEvent('mousewheel', cancel); + }, true); + } + }, + + set: function(){ + var now = Array.flatten(arguments); + this.element.scrollTo(now[0], now[1]); + return this; + }, + + compute: function(from, to, delta){ + return [0, 1].map(function(i){ + return Fx.compute(from[i], to[i], delta); + }); + }, + + start: function(x, y){ + if (!this.check(x, y)) return this; + var scroll = this.element.getScroll(); + return this.parent([scroll.x, scroll.y], [x, y]); + }, + + calculateScroll: function(x, y){ + var element = this.element, + scrollSize = element.getScrollSize(), + scroll = element.getScroll(), + size = element.getSize(), + offset = this.options.offset, + values = {x: x, y: y}; + + for (var z in values){ + if (!values[z] && values[z] !== 0) values[z] = scroll[z]; + if (typeOf(values[z]) != 'number') values[z] = scrollSize[z] - size[z]; + values[z] += offset[z]; + } + + return [values.x, values.y]; + }, + + toTop: function(){ + return this.start.apply(this, this.calculateScroll(false, 0)); + }, + + toLeft: function(){ + return this.start.apply(this, this.calculateScroll(0, false)); + }, + + toRight: function(){ + return this.start.apply(this, this.calculateScroll('right', false)); + }, + + toBottom: function(){ + return this.start.apply(this, this.calculateScroll(false, 'bottom')); + }, + + toElement: function(el, axes){ + axes = axes ? Array.from(axes) : ['x', 'y']; + var scroll = isBody(this.element) ? {x: 0, y: 0} : this.element.getScroll(); + var position = Object.map(document.id(el).getPosition(this.element), function(value, axis){ + return axes.contains(axis) ? value + scroll[axis] : false; + }); + return this.start.apply(this, this.calculateScroll(position.x, position.y)); + }, + + toElementEdge: function(el, axes, offset){ + axes = axes ? Array.from(axes) : ['x', 'y']; + el = document.id(el); + var to = {}, + position = el.getPosition(this.element), + size = el.getSize(), + scroll = this.element.getScroll(), + containerSize = this.element.getSize(), + edge = { + x: position.x + size.x, + y: position.y + size.y + }; + + ['x', 'y'].each(function(axis){ + if (axes.contains(axis)){ + if (edge[axis] > scroll[axis] + containerSize[axis]) to[axis] = edge[axis] - containerSize[axis]; + if (position[axis] < scroll[axis]) to[axis] = position[axis]; + } + if (to[axis] == null) to[axis] = scroll[axis]; + if (offset && offset[axis]) to[axis] = to[axis] + offset[axis]; + }, this); + + if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y); + return this; + }, + + toElementCenter: function(el, axes, offset){ + axes = axes ? Array.from(axes) : ['x', 'y']; + el = document.id(el); + var to = {}, + position = el.getPosition(this.element), + size = el.getSize(), + scroll = this.element.getScroll(), + containerSize = this.element.getSize(); + + ['x', 'y'].each(function(axis){ + if (axes.contains(axis)){ + to[axis] = position[axis] - (containerSize[axis] - size[axis]) / 2; + } + if (to[axis] == null) to[axis] = scroll[axis]; + if (offset && offset[axis]) to[axis] = to[axis] + offset[axis]; + }, this); + + if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y); + return this; + } + +}); + + + +function isBody(element){ + return (/^(?:body|html)$/i).test(element.tagName); +} + +})(); + +/* +--- + +script: Fx.Slide.js + +name: Fx.Slide + +description: Effect to slide an element in and out of view. + +license: MIT-style license + +authors: + - Valerio Proietti + +requires: + - Core/Fx + - Core/Element.Style + - MooTools.More + +provides: [Fx.Slide] + +... +*/ + +Fx.Slide = new Class({ + + Extends: Fx, + + options: { + mode: 'vertical', + wrapper: false, + hideOverflow: true, + resetHeight: false + }, + + initialize: function(element, options){ + element = this.element = this.subject = document.id(element); + this.parent(options); + options = this.options; + + var wrapper = element.retrieve('wrapper'), + styles = element.getStyles('margin', 'position', 'overflow'); + + if (options.hideOverflow) styles = Object.append(styles, {overflow: 'hidden'}); + if (options.wrapper) wrapper = document.id(options.wrapper).setStyles(styles); + + if (!wrapper) wrapper = new Element('div', { + styles: styles + }).wraps(element); + + element.store('wrapper', wrapper).setStyle('margin', 0); + if (element.getStyle('overflow') == 'visible') element.setStyle('overflow', 'hidden'); + + this.now = []; + this.open = true; + this.wrapper = wrapper; + + this.addEvent('complete', function(){ + this.open = (wrapper['offset' + this.layout.capitalize()] != 0); + if (this.open && this.options.resetHeight) wrapper.setStyle('height', ''); + }, true); + }, + + vertical: function(){ + this.margin = 'margin-top'; + this.layout = 'height'; + this.offset = this.element.offsetHeight; + }, + + horizontal: function(){ + this.margin = 'margin-left'; + this.layout = 'width'; + this.offset = this.element.offsetWidth; + }, + + set: function(now){ + this.element.setStyle(this.margin, now[0]); + this.wrapper.setStyle(this.layout, now[1]); + return this; + }, + + compute: function(from, to, delta){ + return [0, 1].map(function(i){ + return Fx.compute(from[i], to[i], delta); + }); + }, + + start: function(how, mode){ + if (!this.check(how, mode)) return this; + this[mode || this.options.mode](); + + var margin = this.element.getStyle(this.margin).toInt(), + layout = this.wrapper.getStyle(this.layout).toInt(), + caseIn = [[margin, layout], [0, this.offset]], + caseOut = [[margin, layout], [-this.offset, 0]], + start; + + switch (how){ + case 'in': start = caseIn; break; + case 'out': start = caseOut; break; + case 'toggle': start = (layout == 0) ? caseIn : caseOut; + } + return this.parent(start[0], start[1]); + }, + + slideIn: function(mode){ + return this.start('in', mode); + }, + + slideOut: function(mode){ + return this.start('out', mode); + }, + + hide: function(mode){ + this[mode || this.options.mode](); + this.open = false; + return this.set([-this.offset, 0]); + }, + + show: function(mode){ + this[mode || this.options.mode](); + this.open = true; + return this.set([0, this.offset]); + }, + + toggle: function(mode){ + return this.start('toggle', mode); + } + +}); + +Element.Properties.slide = { + + set: function(options){ + this.get('slide').cancel().setOptions(options); + return this; + }, + + get: function(){ + var slide = this.retrieve('slide'); + if (!slide){ + slide = new Fx.Slide(this, {link: 'cancel'}); + this.store('slide', slide); + } + return slide; + } + +}; + +Element.implement({ + + slide: function(how, mode){ + how = how || 'toggle'; + var slide = this.get('slide'), toggle; + switch (how){ + case 'hide': slide.hide(mode); break; + case 'show': slide.show(mode); break; + case 'toggle': + var flag = this.retrieve('slide:flag', slide.open); + slide[flag ? 'slideOut' : 'slideIn'](mode); + this.store('slide:flag', !flag); + toggle = true; + break; + default: slide.start(how, mode); + } + if (!toggle) this.eliminate('slide:flag'); + return this; + } + +}); + +/* +--- + +script: Fx.SmoothScroll.js + +name: Fx.SmoothScroll + +description: Class for creating a smooth scrolling effect to all internal links on the page. + +license: MIT-style license + +authors: + - Valerio Proietti + +requires: + - Core/Slick.Finder + - Fx.Scroll + +provides: [Fx.SmoothScroll] + +... +*/ + +Fx.SmoothScroll = new Class({ + + Extends: Fx.Scroll, + + options: { + axes: ['x', 'y'] + }, + + initialize: function(options, context){ + context = context || document; + this.doc = context.getDocument(); + this.parent(this.doc, options); + + var win = context.getWindow(), + location = win.location.href.match(/^[^#]*/)[0] + '#', + links = $$(this.options.links || this.doc.links); + + links.each(function(link){ + if (link.href.indexOf(location) != 0) return; + var anchor = link.href.substr(location.length); + if (anchor) this.useLink(link, anchor); + }, this); + + this.addEvent('complete', function(){ + win.location.hash = this.anchor; + this.element.scrollTo(this.to[0], this.to[1]); + }, true); + }, + + useLink: function(link, anchor){ + + link.addEvent('click', function(event){ + var el = document.id(anchor) || this.doc.getElement('a[name=' + anchor + ']'); + if (!el) return; + + event.preventDefault(); + this.toElement(el, this.options.axes).chain(function(){ + this.fireEvent('scrolledTo', [link, el]); + }.bind(this)); + + this.anchor = anchor; + + }.bind(this)); + + return this; + } +}); + +/* +--- + +script: Fx.Sort.js + +name: Fx.Sort + +description: Defines Fx.Sort, a class that reorders lists with a transition. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Element.Dimensions + - Fx.Elements + - Element.Measure + +provides: [Fx.Sort] + +... +*/ + +Fx.Sort = new Class({ + + Extends: Fx.Elements, + + options: { + mode: 'vertical' + }, + + initialize: function(elements, options){ + this.parent(elements, options); + this.elements.each(function(el){ + if (el.getStyle('position') == 'static') el.setStyle('position', 'relative'); + }); + this.setDefaultOrder(); + }, + + setDefaultOrder: function(){ + this.currentOrder = this.elements.map(function(el, index){ + return index; + }); + }, + + sort: function(){ + if (!this.check(arguments)) return this; + var newOrder = Array.flatten(arguments); + + var top = 0, + left = 0, + next = {}, + zero = {}, + vert = this.options.mode == 'vertical'; + + var current = this.elements.map(function(el, index){ + var size = el.getComputedSize({styles: ['border', 'padding', 'margin']}); + var val; + if (vert){ + val = { + top: top, + margin: size['margin-top'], + height: size.totalHeight + }; + top += val.height - size['margin-top']; + } else { + val = { + left: left, + margin: size['margin-left'], + width: size.totalWidth + }; + left += val.width; + } + var plane = vert ? 'top' : 'left'; + zero[index] = {}; + var start = el.getStyle(plane).toInt(); + zero[index][plane] = start || 0; + return val; + }, this); + + this.set(zero); + newOrder = newOrder.map(function(i){ return i.toInt(); }); + if (newOrder.length != this.elements.length){ + this.currentOrder.each(function(index){ + if (!newOrder.contains(index)) newOrder.push(index); + }); + if (newOrder.length > this.elements.length) + newOrder.splice(this.elements.length-1, newOrder.length - this.elements.length); + } + var margin = 0; + top = left = 0; + newOrder.each(function(item){ + var newPos = {}; + if (vert){ + newPos.top = top - current[item].top - margin; + top += current[item].height; + } else { + newPos.left = left - current[item].left; + left += current[item].width; + } + margin = margin + current[item].margin; + next[item]=newPos; + }, this); + var mapped = {}; + Array.clone(newOrder).sort().each(function(index){ + mapped[index] = next[index]; + }); + this.start(mapped); + this.currentOrder = newOrder; + + return this; + }, + + rearrangeDOM: function(newOrder){ + newOrder = newOrder || this.currentOrder; + var parent = this.elements[0].getParent(); + var rearranged = []; + this.elements.setStyle('opacity', 0); + //move each element and store the new default order + newOrder.each(function(index){ + rearranged.push(this.elements[index].inject(parent).setStyles({ + top: 0, + left: 0 + })); + }, this); + this.elements.setStyle('opacity', 1); + this.elements = $$(rearranged); + this.setDefaultOrder(); + return this; + }, + + getDefaultOrder: function(){ + return this.elements.map(function(el, index){ + return index; + }); + }, + + getCurrentOrder: function(){ + return this.currentOrder; + }, + + forward: function(){ + return this.sort(this.getDefaultOrder()); + }, + + backward: function(){ + return this.sort(this.getDefaultOrder().reverse()); + }, + + reverse: function(){ + return this.sort(this.currentOrder.reverse()); + }, + + sortByElements: function(elements){ + return this.sort(elements.map(function(el){ + return this.elements.indexOf(el); + }, this)); + }, + + swap: function(one, two){ + if (typeOf(one) == 'element') one = this.elements.indexOf(one); + if (typeOf(two) == 'element') two = this.elements.indexOf(two); + + var newOrder = Array.clone(this.currentOrder); + newOrder[this.currentOrder.indexOf(one)] = two; + newOrder[this.currentOrder.indexOf(two)] = one; + + return this.sort(newOrder); + } + +}); + +/* +--- + +script: Keyboard.js + +name: Keyboard + +description: KeyboardEvents used to intercept events on a class for keyboard and format modifiers in a specific order so as to make alt+shift+c the same as shift+alt+c. + +license: MIT-style license + +authors: + - Perrin Westrich + - Aaron Newton + - Scott Kyle + +requires: + - Core/Events + - Core/Options + - Core/Element.Event + - Element.Event.Pseudos.Keys + +provides: [Keyboard] + +... +*/ + +(function(){ + + var Keyboard = this.Keyboard = new Class({ + + Extends: Events, + + Implements: [Options], + + options: {/* + onActivate: function(){}, + onDeactivate: function(){},*/ + defaultEventType: 'keydown', + active: false, + manager: null, + events: {}, + nonParsedEvents: ['activate', 'deactivate', 'onactivate', 'ondeactivate', 'changed', 'onchanged'] + }, + + initialize: function(options){ + if (options && options.manager){ + this._manager = options.manager; + delete options.manager; + } + this.setOptions(options); + this._setup(); + }, + + addEvent: function(type, fn, internal){ + return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn, internal); + }, + + removeEvent: function(type, fn){ + return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn); + }, + + toggleActive: function(){ + return this[this.isActive() ? 'deactivate' : 'activate'](); + }, + + activate: function(instance){ + if (instance){ + if (instance.isActive()) return this; + //if we're stealing focus, store the last keyboard to have it so the relinquish command works + if (this._activeKB && instance != this._activeKB){ + this.previous = this._activeKB; + this.previous.fireEvent('deactivate'); + } + //if we're enabling a child, assign it so that events are now passed to it + this._activeKB = instance.fireEvent('activate'); + Keyboard.manager.fireEvent('changed'); + } else if (this._manager){ + //else we're enabling ourselves, we must ask our parent to do it for us + this._manager.activate(this); + } + return this; + }, + + isActive: function(){ + return this._manager ? (this._manager._activeKB == this) : (Keyboard.manager == this); + }, + + deactivate: function(instance){ + if (instance){ + if (instance === this._activeKB){ + this._activeKB = null; + instance.fireEvent('deactivate'); + Keyboard.manager.fireEvent('changed'); + } + } else if (this._manager){ + this._manager.deactivate(this); + } + return this; + }, + + relinquish: function(){ + if (this.isActive() && this._manager && this._manager.previous) this._manager.activate(this._manager.previous); + else this.deactivate(); + return this; + }, + + //management logic + manage: function(instance){ + if (instance._manager) instance._manager.drop(instance); + this._instances.push(instance); + instance._manager = this; + if (!this._activeKB) this.activate(instance); + return this; + }, + + drop: function(instance){ + instance.relinquish(); + this._instances.erase(instance); + if (this._activeKB == instance){ + if (this.previous && this._instances.contains(this.previous)) this.activate(this.previous); + else this._activeKB = this._instances[0]; + } + return this; + }, + + trace: function(){ + Keyboard.trace(this); + }, + + each: function(fn){ + Keyboard.each(this, fn); + }, + + /* + PRIVATE METHODS + */ + + _instances: [], + + _disable: function(instance){ + if (this._activeKB == instance) this._activeKB = null; + }, + + _setup: function(){ + this.addEvents(this.options.events); + //if this is the root manager, nothing manages it + if (Keyboard.manager && !this._manager) Keyboard.manager.manage(this); + if (this.options.active) this.activate(); + else this.relinquish(); + }, + + _handle: function(event, type){ + //Keyboard.stop(event) prevents key propagation + if (event.preventKeyboardPropagation) return; + + var bubbles = !!this._manager; + if (bubbles && this._activeKB){ + this._activeKB._handle(event, type); + if (event.preventKeyboardPropagation) return; + } + this.fireEvent(type, event); + + if (!bubbles && this._activeKB) this._activeKB._handle(event, type); + } + + }); + + var parsed = {}; + var modifiers = ['shift', 'control', 'alt', 'meta']; + var regex = /^(?:shift|control|ctrl|alt|meta)$/; + + Keyboard.parse = function(type, eventType, ignore){ + if (ignore && ignore.contains(type.toLowerCase())) return type; + + type = type.toLowerCase().replace(/^(keyup|keydown):/, function($0, $1){ + eventType = $1; + return ''; + }); + + if (!parsed[type]){ + if (type != '+'){ + var key, mods = {}; + type.split('+').each(function(part){ + if (regex.test(part)) mods[part] = true; + else key = part; + }); + + mods.control = mods.control || mods.ctrl; // allow both control and ctrl + + var keys = []; + modifiers.each(function(mod){ + if (mods[mod]) keys.push(mod); + }); + + if (key) keys.push(key); + parsed[type] = keys.join('+'); + } else { + parsed[type] = type; + } + } + + return eventType + ':keys(' + parsed[type] + ')'; + }; + + Keyboard.each = function(keyboard, fn){ + var current = keyboard || Keyboard.manager; + while (current){ + fn(current); + current = current._activeKB; + } + }; + + Keyboard.stop = function(event){ + event.preventKeyboardPropagation = true; + }; + + Keyboard.manager = new Keyboard({ + active: true + }); + + Keyboard.trace = function(keyboard){ + keyboard = keyboard || Keyboard.manager; + var hasConsole = window.console && console.log; + if (hasConsole) console.log('the following items have focus: '); + Keyboard.each(keyboard, function(current){ + if (hasConsole) console.log(document.id(current.widget) || current.wiget || current); + }); + }; + + var handler = function(event){ + var keys = []; + modifiers.each(function(mod){ + if (event[mod]) keys.push(mod); + }); + + if (!regex.test(event.key)) keys.push(event.key); + Keyboard.manager._handle(event, event.type + ':keys(' + keys.join('+') + ')'); + }; + + document.addEvents({ + 'keyup': handler, + 'keydown': handler + }); + +})(); + +/* +--- + +script: Keyboard.Extras.js + +name: Keyboard.Extras + +description: Enhances Keyboard by adding the ability to name and describe keyboard shortcuts, and the ability to grab shortcuts by name and bind the shortcut to different keys. + +license: MIT-style license + +authors: + - Perrin Westrich + +requires: + - Keyboard + - MooTools.More + +provides: [Keyboard.Extras] + +... +*/ +Keyboard.prototype.options.nonParsedEvents.combine(['rebound', 'onrebound']); + +Keyboard.implement({ + + /* + shortcut should be in the format of: + { + 'keys': 'shift+s', // the default to add as an event. + 'description': 'blah blah blah', // a brief description of the functionality. + 'handler': function(){} // the event handler to run when keys are pressed. + } + */ + addShortcut: function(name, shortcut){ + this._shortcuts = this._shortcuts || []; + this._shortcutIndex = this._shortcutIndex || {}; + + shortcut.getKeyboard = Function.from(this); + shortcut.name = name; + this._shortcutIndex[name] = shortcut; + this._shortcuts.push(shortcut); + if (shortcut.keys) this.addEvent(shortcut.keys, shortcut.handler); + return this; + }, + + addShortcuts: function(obj){ + for (var name in obj) this.addShortcut(name, obj[name]); + return this; + }, + + removeShortcut: function(name){ + var shortcut = this.getShortcut(name); + if (shortcut && shortcut.keys){ + this.removeEvent(shortcut.keys, shortcut.handler); + delete this._shortcutIndex[name]; + this._shortcuts.erase(shortcut); + } + return this; + }, + + removeShortcuts: function(names){ + names.each(this.removeShortcut, this); + return this; + }, + + getShortcuts: function(){ + return this._shortcuts || []; + }, + + getShortcut: function(name){ + return (this._shortcutIndex || {})[name]; + } + +}); + +Keyboard.rebind = function(newKeys, shortcuts){ + Array.from(shortcuts).each(function(shortcut){ + shortcut.getKeyboard().removeEvent(shortcut.keys, shortcut.handler); + shortcut.getKeyboard().addEvent(newKeys, shortcut.handler); + shortcut.keys = newKeys; + shortcut.getKeyboard().fireEvent('rebound'); + }); +}; + + +Keyboard.getActiveShortcuts = function(keyboard){ + var activeKBS = [], activeSCS = []; + Keyboard.each(keyboard, [].push.bind(activeKBS)); + activeKBS.each(function(kb){ activeSCS.extend(kb.getShortcuts()); }); + return activeSCS; +}; + +Keyboard.getShortcut = function(name, keyboard, opts){ + opts = opts || {}; + var shortcuts = opts.many ? [] : null, + set = opts.many ? function(kb){ + var shortcut = kb.getShortcut(name); + if (shortcut) shortcuts.push(shortcut); + } : function(kb){ + if (!shortcuts) shortcuts = kb.getShortcut(name); + }; + Keyboard.each(keyboard, set); + return shortcuts; +}; + +Keyboard.getShortcuts = function(name, keyboard){ + return Keyboard.getShortcut(name, keyboard, { many: true }); +}; + +/* +--- + +script: HtmlTable.js + +name: HtmlTable + +description: Builds table elements with methods to add rows. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Options + - Core/Events + - Class.Occlude + +provides: [HtmlTable] + +... +*/ + +var HtmlTable = new Class({ + + Implements: [Options, Events, Class.Occlude], + + options: { + properties: { + cellpadding: 0, + cellspacing: 0, + border: 0 + }, + rows: [], + headers: [], + footers: [] + }, + + property: 'HtmlTable', + + initialize: function(){ + var params = Array.link(arguments, {options: Type.isObject, table: Type.isElement, id: Type.isString}); + this.setOptions(params.options); + if (!params.table && params.id) params.table = document.id(params.id); + this.element = params.table || new Element('table', this.options.properties); + if (this.occlude()) return this.occluded; + this.build(); + }, + + build: function(){ + this.element.store('HtmlTable', this); + + this.body = document.id(this.element.tBodies[0]) || new Element('tbody').inject(this.element); + $$(this.body.rows); + + if (this.options.headers.length) this.setHeaders(this.options.headers); + else this.thead = document.id(this.element.tHead); + + if (this.thead) this.head = this.getHead(); + if (this.options.footers.length) this.setFooters(this.options.footers); + + this.tfoot = document.id(this.element.tFoot); + if (this.tfoot) this.foot = document.id(this.tfoot.rows[0]); + + this.options.rows.each(function(row){ + this.push(row); + }, this); + }, + + toElement: function(){ + return this.element; + }, + + empty: function(){ + this.body.empty(); + return this; + }, + + set: function(what, items){ + var target = (what == 'headers') ? 'tHead' : 'tFoot', + lower = target.toLowerCase(); + + this[lower] = (document.id(this.element[target]) || new Element(lower).inject(this.element, 'top')).empty(); + var data = this.push(items, {}, this[lower], what == 'headers' ? 'th' : 'td'); + + if (what == 'headers') this.head = this.getHead(); + else this.foot = this.getHead(); + + return data; + }, + + getHead: function(){ + var rows = this.thead.rows; + return rows.length > 1 ? $$(rows) : rows.length ? document.id(rows[0]) : false; + }, + + setHeaders: function(headers){ + this.set('headers', headers); + return this; + }, + + setFooters: function(footers){ + this.set('footers', footers); + return this; + }, + + update: function(tr, row, tag){ + var tds = tr.getChildren(tag || 'td'), last = tds.length - 1; + + row.each(function(data, index){ + var td = tds[index] || new Element(tag || 'td').inject(tr), + content = ((data && Object.prototype.hasOwnProperty.call(data, 'content')) ? data.content : '') || data, + type = typeOf(content); + + if (data && Object.prototype.hasOwnProperty.call(data, 'properties')) td.set(data.properties); + if (/(element(s?)|array|collection)/.test(type)) td.empty().adopt(content); + else td.set('html', content); + + if (index > last) tds.push(td); + else tds[index] = td; + }); + + return { + tr: tr, + tds: tds + }; + }, + + push: function(row, rowProperties, target, tag, where){ + if (typeOf(row) == 'element' && row.get('tag') == 'tr'){ + row.inject(target || this.body, where); + return { + tr: row, + tds: row.getChildren('td') + }; + } + return this.update(new Element('tr', rowProperties).inject(target || this.body, where), row, tag); + }, + + pushMany: function(rows, rowProperties, target, tag, where){ + return rows.map(function(row){ + return this.push(row, rowProperties, target, tag, where); + }, this); + } + +}); + + +['adopt', 'inject', 'wraps', 'grab', 'replaces', 'dispose'].each(function(method){ + HtmlTable.implement(method, function(){ + this.element[method].apply(this.element, arguments); + return this; + }); +}); + + + +/* +--- + +script: HtmlTable.Select.js + +name: HtmlTable.Select + +description: Builds a stripy, sortable table with methods to add rows. Rows can be selected with the mouse or keyboard navigation. + +license: MIT-style license + +authors: + - Harald Kirschner + - Aaron Newton + +requires: + - Keyboard + - Keyboard.Extras + - HtmlTable + - Class.refactor + - Element.Delegation.Pseudo + - Element.Shortcuts + +provides: [HtmlTable.Select] + +... +*/ + +HtmlTable = Class.refactor(HtmlTable, { + + options: { + /*onRowFocus: function(){}, + onRowUnfocus: function(){},*/ + useKeyboard: true, + classRowSelected: 'table-tr-selected', + classRowHovered: 'table-tr-hovered', + classSelectable: 'table-selectable', + shiftForMultiSelect: true, + allowMultiSelect: true, + selectable: false, + selectHiddenRows: false + }, + + initialize: function(){ + this.previous.apply(this, arguments); + if (this.occluded) return this.occluded; + + this.selectedRows = new Elements(); + + if (!this.bound) this.bound = {}; + this.bound.mouseleave = this.mouseleave.bind(this); + this.bound.clickRow = this.clickRow.bind(this); + this.bound.activateKeyboard = function(){ + if (this.keyboard && this.selectEnabled) this.keyboard.activate(); + }.bind(this); + + if (this.options.selectable) this.enableSelect(); + }, + + empty: function(){ + if (this.body.rows.length) this.selectNone(); + return this.previous(); + }, + + enableSelect: function(){ + this.selectEnabled = true; + this.attachSelects(); + this.element.addClass(this.options.classSelectable); + return this; + }, + + disableSelect: function(){ + this.selectEnabled = false; + this.attachSelects(false); + this.element.removeClass(this.options.classSelectable); + return this; + }, + + push: function(){ + var ret = this.previous.apply(this, arguments); + this.updateSelects(); + return ret; + }, + + toggleRow: function(row){ + return this[(this.isSelected(row) ? 'de' : '') + 'selectRow'](row); + }, + + selectRow: function(row, _nocheck){ + //private variable _nocheck: boolean whether or not to confirm the row is in the table body + //added here for optimization when selecting ranges + if (this.isSelected(row) || (!_nocheck && !this.body.getChildren().contains(row))) return; + if (!this.options.allowMultiSelect) this.selectNone(); + + if (!this.isSelected(row)){ + this.selectedRows.push(row); + row.addClass(this.options.classRowSelected); + this.fireEvent('rowFocus', [row, this.selectedRows]); + this.fireEvent('stateChanged'); + } + + this.focused = row; + document.clearSelection(); + + return this; + }, + + isSelected: function(row){ + return this.selectedRows.contains(row); + }, + + getSelected: function(){ + return this.selectedRows; + }, + + serialize: function(){ + var previousSerialization = this.previous.apply(this, arguments) || {}; + if (this.options.selectable){ + previousSerialization.selectedRows = this.selectedRows.map(function(row){ + return Array.indexOf(this.body.rows, row); + }.bind(this)); + } + return previousSerialization; + }, + + restore: function(tableState){ + if(this.options.selectable && tableState.selectedRows){ + tableState.selectedRows.each(function(index){ + this.selectRow(this.body.rows[index]); + }.bind(this)); + } + this.previous.apply(this, arguments); + }, + + deselectRow: function(row, _nocheck){ + if (!this.isSelected(row) || (!_nocheck && !this.body.getChildren().contains(row))) return; + + this.selectedRows = new Elements(Array.from(this.selectedRows).erase(row)); + row.removeClass(this.options.classRowSelected); + this.fireEvent('rowUnfocus', [row, this.selectedRows]); + this.fireEvent('stateChanged'); + return this; + }, + + selectAll: function(selectNone){ + if (!selectNone && !this.options.allowMultiSelect) return; + this.selectRange(0, this.body.rows.length, selectNone); + return this; + }, + + selectNone: function(){ + return this.selectAll(true); + }, + + selectRange: function(startRow, endRow, _deselect){ + if (!this.options.allowMultiSelect && !_deselect) return; + var method = _deselect ? 'deselectRow' : 'selectRow', + rows = Array.clone(this.body.rows); + + if (typeOf(startRow) == 'element') startRow = rows.indexOf(startRow); + if (typeOf(endRow) == 'element') endRow = rows.indexOf(endRow); + endRow = endRow < rows.length - 1 ? endRow : rows.length - 1; + + if (endRow < startRow){ + var tmp = startRow; + startRow = endRow; + endRow = tmp; + } + + for (var i = startRow; i <= endRow; i++){ + if (this.options.selectHiddenRows || rows[i].isDisplayed()) this[method](rows[i], true); + } + + return this; + }, + + deselectRange: function(startRow, endRow){ + this.selectRange(startRow, endRow, true); + }, + +/* + Private methods: +*/ + + enterRow: function(row){ + if (this.hovered) this.hovered = this.leaveRow(this.hovered); + this.hovered = row.addClass(this.options.classRowHovered); + }, + + leaveRow: function(row){ + row.removeClass(this.options.classRowHovered); + }, + + updateSelects: function(){ + Array.each(this.body.rows, function(row){ + var binders = row.retrieve('binders'); + if (!binders && !this.selectEnabled) return; + if (!binders){ + binders = { + mouseenter: this.enterRow.pass([row], this), + mouseleave: this.leaveRow.pass([row], this) + }; + row.store('binders', binders); + } + if (this.selectEnabled) row.addEvents(binders); + else row.removeEvents(binders); + }, this); + }, + + shiftFocus: function(offset, event){ + if (!this.focused) return this.selectRow(this.body.rows[0], event); + var to = this.getRowByOffset(offset, this.options.selectHiddenRows); + if (to === null || this.focused == this.body.rows[to]) return this; + this.toggleRow(this.body.rows[to], event); + }, + + clickRow: function(event, row){ + var selecting = (event.shift || event.meta || event.control) && this.options.shiftForMultiSelect; + if (!selecting && !(event.rightClick && this.isSelected(row) && this.options.allowMultiSelect)) this.selectNone(); + + if (event.rightClick) this.selectRow(row); + else this.toggleRow(row); + + if (event.shift){ + this.selectRange(this.rangeStart || this.body.rows[0], row, this.rangeStart ? !this.isSelected(row) : true); + this.focused = row; + } + this.rangeStart = row; + }, + + getRowByOffset: function(offset, includeHiddenRows){ + if (!this.focused) return 0; + var index = Array.indexOf(this.body.rows, this.focused); + if ((index == 0 && offset < 0) || (index == this.body.rows.length -1 && offset > 0)) return null; + if (includeHiddenRows){ + index += offset; + } else { + var limit = 0, + count = 0; + if (offset > 0){ + while (count < offset && index < this.body.rows.length -1){ + if (this.body.rows[++index].isDisplayed()) count++; + } + } else { + while (count > offset && index > 0){ + if (this.body.rows[--index].isDisplayed()) count--; + } + } + } + return index; + }, + + attachSelects: function(attach){ + attach = attach != null ? attach : true; + + var method = attach ? 'addEvents' : 'removeEvents'; + this.element[method]({ + mouseleave: this.bound.mouseleave, + click: this.bound.activateKeyboard + }); + + this.body[method]({ + 'click:relay(tr)': this.bound.clickRow, + 'contextmenu:relay(tr)': this.bound.clickRow + }); + + if (this.options.useKeyboard || this.keyboard){ + if (!this.keyboard) this.keyboard = new Keyboard(); + if (!this.selectKeysDefined){ + this.selectKeysDefined = true; + var timer, held; + + var move = function(offset){ + var mover = function(e){ + clearTimeout(timer); + e.preventDefault(); + var to = this.body.rows[this.getRowByOffset(offset, this.options.selectHiddenRows)]; + if (e.shift && to && this.isSelected(to)){ + this.deselectRow(this.focused); + this.focused = to; + } else { + if (to && (!this.options.allowMultiSelect || !e.shift)){ + this.selectNone(); + } + this.shiftFocus(offset, e); + } + + if (held){ + timer = mover.delay(100, this, e); + } else { + timer = (function(){ + held = true; + mover(e); + }).delay(400); + } + }.bind(this); + return mover; + }.bind(this); + + var clear = function(){ + clearTimeout(timer); + held = false; + }; + + this.keyboard.addEvents({ + 'keydown:shift+up': move(-1), + 'keydown:shift+down': move(1), + 'keyup:shift+up': clear, + 'keyup:shift+down': clear, + 'keyup:up': clear, + 'keyup:down': clear + }); + + var shiftHint = ''; + if (this.options.allowMultiSelect && this.options.shiftForMultiSelect && this.options.useKeyboard){ + shiftHint = " (Shift multi-selects)."; + } + + this.keyboard.addShortcuts({ + 'Select Previous Row': { + keys: 'up', + shortcut: 'up arrow', + handler: move(-1), + description: 'Select the previous row in the table.' + shiftHint + }, + 'Select Next Row': { + keys: 'down', + shortcut: 'down arrow', + handler: move(1), + description: 'Select the next row in the table.' + shiftHint + } + }); + + } + this.keyboard[attach ? 'activate' : 'deactivate'](); + } + this.updateSelects(); + }, + + mouseleave: function(){ + if (this.hovered) this.leaveRow(this.hovered); + } + +}); + +/* +--- + +script: HtmlTable.Sort.js + +name: HtmlTable.Sort + +description: Builds a stripy, sortable table with methods to add rows. + +license: MIT-style license + +authors: + - Harald Kirschner + - Aaron Newton + - Jacob Thornton + +requires: + - Core/Hash + - HtmlTable + - Class.refactor + - Element.Delegation.Pseudo + - String.Extras + - Date + +provides: [HtmlTable.Sort] + +... +*/ +(function(){ + +var readOnlyNess = document.createElement('table'); +try { + readOnlyNess.innerHTML = '<tr><td></td></tr>'; + readOnlyNess = readOnlyNess.childNodes.length === 0; +} catch (e){ + readOnlyNess = true; +} + +HtmlTable = Class.refactor(HtmlTable, { + + options: {/* + onSort: function(){}, */ + sortIndex: 0, + sortReverse: false, + parsers: [], + defaultParser: 'string', + classSortable: 'table-sortable', + classHeadSort: 'table-th-sort', + classHeadSortRev: 'table-th-sort-rev', + classNoSort: 'table-th-nosort', + classGroupHead: 'table-tr-group-head', + classGroup: 'table-tr-group', + classCellSort: 'table-td-sort', + classSortSpan: 'table-th-sort-span', + sortable: false, + thSelector: 'th' + }, + + initialize: function (){ + this.previous.apply(this, arguments); + if (this.occluded) return this.occluded; + this.sorted = {index: null, dir: 1}; + if (!this.bound) this.bound = {}; + this.bound.headClick = this.headClick.bind(this); + this.sortSpans = new Elements(); + if (this.options.sortable){ + this.enableSort(); + if (this.options.sortIndex != null) this.sort(this.options.sortIndex, this.options.sortReverse); + } + }, + + attachSorts: function(attach){ + this.detachSorts(); + if (attach !== false) this.element.addEvent('click:relay(' + this.options.thSelector + ')', this.bound.headClick); + }, + + detachSorts: function(){ + this.element.removeEvents('click:relay(' + this.options.thSelector + ')'); + }, + + setHeaders: function(){ + this.previous.apply(this, arguments); + if (this.sortable) this.setParsers(); + }, + + setParsers: function(){ + this.parsers = this.detectParsers(); + }, + + detectParsers: function(){ + return this.head && this.head.getElements(this.options.thSelector).flatten().map(this.detectParser, this); + }, + + detectParser: function(cell, index){ + if (cell.hasClass(this.options.classNoSort) || cell.retrieve('htmltable-parser')) return cell.retrieve('htmltable-parser'); + var thDiv = new Element('div'); + thDiv.adopt(cell.childNodes).inject(cell); + var sortSpan = new Element('span', {'class': this.options.classSortSpan}).inject(thDiv, 'top'); + this.sortSpans.push(sortSpan); + var parser = this.options.parsers[index], + rows = this.body.rows, + cancel; + switch (typeOf(parser)){ + case 'function': parser = {convert: parser}; cancel = true; break; + case 'string': parser = parser; cancel = true; break; + } + if (!cancel){ + HtmlTable.ParserPriority.some(function(parserName){ + var current = HtmlTable.Parsers[parserName], + match = current.match; + if (!match) return false; + for (var i = 0, j = rows.length; i < j; i++){ + var cell = document.id(rows[i].cells[index]), + text = cell ? cell.get('html').clean() : ''; + if (text && match.test(text)){ + parser = current; + return true; + } + } + }); + } + if (!parser) parser = this.options.defaultParser; + cell.store('htmltable-parser', parser); + return parser; + }, + + headClick: function(event, el){ + if (!this.head || el.hasClass(this.options.classNoSort)) return; + return this.sort(Array.indexOf(this.head.getElements(this.options.thSelector).flatten(), el) % this.body.rows[0].cells.length); + }, + + serialize: function(){ + var previousSerialization = this.previous.apply(this, arguments) || {}; + if (this.options.sortable){ + previousSerialization.sortIndex = this.sorted.index; + previousSerialization.sortReverse = this.sorted.reverse; + } + return previousSerialization; + }, + + restore: function(tableState){ + if(this.options.sortable && tableState.sortIndex){ + this.sort(tableState.sortIndex, tableState.sortReverse); + } + this.previous.apply(this, arguments); + }, + + setSortedState: function(index, reverse){ + if (reverse != null) this.sorted.reverse = reverse; + else if (this.sorted.index == index) this.sorted.reverse = !this.sorted.reverse; + else this.sorted.reverse = this.sorted.index == null; + + if (index != null) this.sorted.index = index; + }, + + setHeadSort: function(sorted){ + var head = $$(!this.head.length ? this.head.cells[this.sorted.index] : this.head.map(function(row){ + return row.getElements(this.options.thSelector)[this.sorted.index]; + }, this).clean()); + if (!head.length) return; + if (sorted){ + head.addClass(this.options.classHeadSort); + if (this.sorted.reverse) head.addClass(this.options.classHeadSortRev); + else head.removeClass(this.options.classHeadSortRev); + } else { + head.removeClass(this.options.classHeadSort).removeClass(this.options.classHeadSortRev); + } + }, + + setRowSort: function(data, pre){ + var count = data.length, + body = this.body, + group, + rowIndex; + + while (count){ + var item = data[--count], + position = item.position, + row = body.rows[position]; + + if (row.disabled) continue; + if (!pre){ + group = this.setGroupSort(group, row, item); + this.setRowStyle(row, count); + } + body.appendChild(row); + + for (rowIndex = 0; rowIndex < count; rowIndex++){ + if (data[rowIndex].position > position) data[rowIndex].position--; + } + } + }, + + setRowStyle: function(row, i){ + this.previous(row, i); + row.cells[this.sorted.index].addClass(this.options.classCellSort); + }, + + setGroupSort: function(group, row, item){ + if (group == item.value) row.removeClass(this.options.classGroupHead).addClass(this.options.classGroup); + else row.removeClass(this.options.classGroup).addClass(this.options.classGroupHead); + return item.value; + }, + + getParser: function(){ + var parser = this.parsers[this.sorted.index]; + return typeOf(parser) == 'string' ? HtmlTable.Parsers[parser] : parser; + }, + + sort: function(index, reverse, pre, sortFunction){ + if (!this.head) return; + + if (!pre){ + this.clearSort(); + this.setSortedState(index, reverse); + this.setHeadSort(true); + } + + var parser = this.getParser(); + if (!parser) return; + + var rel; + if (!readOnlyNess){ + rel = this.body.getParent(); + this.body.dispose(); + } + + var data = this.parseData(parser).sort(sortFunction ? sortFunction : function(a, b){ + if (a.value === b.value) return 0; + return a.value > b.value ? 1 : -1; + }); + + if (this.sorted.reverse == (parser == HtmlTable.Parsers['input-checked'])) data.reverse(true); + this.setRowSort(data, pre); + + if (rel) rel.grab(this.body); + this.fireEvent('stateChanged'); + return this.fireEvent('sort', [this.body, this.sorted.index]); + }, + + parseData: function(parser){ + return Array.map(this.body.rows, function(row, i){ + var value = parser.convert.call(document.id(row.cells[this.sorted.index])); + return { + position: i, + value: value + }; + }, this); + }, + + clearSort: function(){ + this.setHeadSort(false); + this.body.getElements('td').removeClass(this.options.classCellSort); + }, + + reSort: function(){ + if (this.sortable) this.sort.call(this, this.sorted.index, this.sorted.reverse); + return this; + }, + + enableSort: function(){ + this.element.addClass(this.options.classSortable); + this.attachSorts(true); + this.setParsers(); + this.sortable = true; + return this; + }, + + disableSort: function(){ + this.element.removeClass(this.options.classSortable); + this.attachSorts(false); + this.sortSpans.each(function(span){ + span.destroy(); + }); + this.sortSpans.empty(); + this.sortable = false; + return this; + } + +}); + +HtmlTable.ParserPriority = ['date', 'input-checked', 'input-value', 'float', 'number']; + +HtmlTable.Parsers = { + + 'date': { + match: /^\d{2}[-\/ ]\d{2}[-\/ ]\d{2,4}$/, + convert: function(){ + var d = Date.parse(this.get('text').stripTags()); + return (typeOf(d) == 'date') ? d.format('db') : ''; + }, + type: 'date' + }, + 'input-checked': { + match: / type="(radio|checkbox)"/, + convert: function(){ + return this.getElement('input').checked; + } + }, + 'input-value': { + match: /<input/, + convert: function(){ + return this.getElement('input').value; + } + }, + 'number': { + match: /^\d+[^\d.,]*$/, + convert: function(){ + return this.get('text').stripTags().toInt(); + }, + number: true + }, + 'numberLax': { + match: /^[^\d]+\d+$/, + convert: function(){ + return this.get('text').replace(/[^-?^0-9]/, '').stripTags().toInt(); + }, + number: true + }, + 'float': { + match: /^[\d]+\.[\d]+/, + convert: function(){ + return this.get('text').replace(/[^-?^\d.e]/, '').stripTags().toFloat(); + }, + number: true + }, + 'floatLax': { + match: /^[^\d]+[\d]+\.[\d]+$/, + convert: function(){ + return this.get('text').replace(/[^-?^\d.]/, '').stripTags().toFloat(); + }, + number: true + }, + 'string': { + match: null, + convert: function(){ + return this.get('text').stripTags().toLowerCase(); + } + }, + 'title': { + match: null, + convert: function(){ + return this.title; + } + } + +}; + + + +HtmlTable.defineParsers = function(parsers){ + HtmlTable.Parsers = Object.append(HtmlTable.Parsers, parsers); + for (var parser in parsers){ + HtmlTable.ParserPriority.unshift(parser); + } +}; + +})(); + + +/* +--- + +script: HtmlTable.Zebra.js + +name: HtmlTable.Zebra + +description: Builds a stripy table with methods to add rows. + +license: MIT-style license + +authors: + - Harald Kirschner + - Aaron Newton + +requires: + - HtmlTable + - Element.Shortcuts + - Class.refactor + +provides: [HtmlTable.Zebra] + +... +*/ + +HtmlTable = Class.refactor(HtmlTable, { + + options: { + classZebra: 'table-tr-odd', + zebra: true, + zebraOnlyVisibleRows: true + }, + + initialize: function(){ + this.previous.apply(this, arguments); + if (this.occluded) return this.occluded; + if (this.options.zebra) this.updateZebras(); + }, + + updateZebras: function(){ + var index = 0; + Array.each(this.body.rows, function(row){ + if (!this.options.zebraOnlyVisibleRows || row.isDisplayed()){ + this.zebra(row, index++); + } + }, this); + }, + + setRowStyle: function(row, i){ + if (this.previous) this.previous(row, i); + this.zebra(row, i); + }, + + zebra: function(row, i){ + return row[((i % 2) ? 'remove' : 'add')+'Class'](this.options.classZebra); + }, + + push: function(){ + var pushed = this.previous.apply(this, arguments); + if (this.options.zebra) this.updateZebras(); + return pushed; + } + +}); + +/* +--- + +script: Scroller.js + +name: Scroller + +description: Class which scrolls the contents of any Element (including the window) when the mouse reaches the Element's boundaries. + +license: MIT-style license + +authors: + - Valerio Proietti + +requires: + - Core/Events + - Core/Options + - Core/Element.Event + - Core/Element.Dimensions + - MooTools.More + +provides: [Scroller] + +... +*/ + +var Scroller = new Class({ + + Implements: [Events, Options], + + options: { + area: 20, + velocity: 1, + onChange: function(x, y){ + this.element.scrollTo(x, y); + }, + fps: 50 + }, + + initialize: function(element, options){ + this.setOptions(options); + this.element = document.id(element); + this.docBody = document.id(this.element.getDocument().body); + this.listener = (typeOf(this.element) != 'element') ? this.docBody : this.element; + this.timer = null; + this.bound = { + attach: this.attach.bind(this), + detach: this.detach.bind(this), + getCoords: this.getCoords.bind(this) + }; + }, + + start: function(){ + this.listener.addEvents({ + mouseover: this.bound.attach, + mouseleave: this.bound.detach + }); + return this; + }, + + stop: function(){ + this.listener.removeEvents({ + mouseover: this.bound.attach, + mouseleave: this.bound.detach + }); + this.detach(); + this.timer = clearInterval(this.timer); + return this; + }, + + attach: function(){ + this.listener.addEvent('mousemove', this.bound.getCoords); + }, + + detach: function(){ + this.listener.removeEvent('mousemove', this.bound.getCoords); + this.timer = clearInterval(this.timer); + }, + + getCoords: function(event){ + this.page = (this.listener.get('tag') == 'body') ? event.client : event.page; + if (!this.timer) this.timer = this.scroll.periodical(Math.round(1000 / this.options.fps), this); + }, + + scroll: function(){ + var size = this.element.getSize(), + scroll = this.element.getScroll(), + pos = ((this.element != this.docBody) && (this.element != window)) ? element.getOffsets() : {x: 0, y: 0}, + scrollSize = this.element.getScrollSize(), + change = {x: 0, y: 0}, + top = this.options.area.top || this.options.area, + bottom = this.options.area.bottom || this.options.area; + for (var z in this.page){ + if (this.page[z] < (top + pos[z]) && scroll[z] != 0){ + change[z] = (this.page[z] - top - pos[z]) * this.options.velocity; + } else if (this.page[z] + bottom > (size[z] + pos[z]) && scroll[z] + size[z] != scrollSize[z]){ + change[z] = (this.page[z] - size[z] + bottom - pos[z]) * this.options.velocity; + } + change[z] = change[z].round(); + } + if (change.y || change.x) this.fireEvent('change', [scroll.x + change.x, scroll.y + change.y]); + } + +}); + +/* +--- + +script: Tips.js + +name: Tips + +description: Class for creating nice tips that follow the mouse cursor when hovering an element. + +license: MIT-style license + +authors: + - Valerio Proietti + - Christoph Pojer + - Luis Merino + +requires: + - Core/Options + - Core/Events + - Core/Element.Event + - Core/Element.Style + - Core/Element.Dimensions + - MooTools.More + +provides: [Tips] + +... +*/ + +(function(){ + +var read = function(option, element){ + return (option) ? (typeOf(option) == 'function' ? option(element) : element.get(option)) : ''; +}; + +this.Tips = new Class({ + + Implements: [Events, Options], + + options: {/* + id: null, + onAttach: function(element){}, + onDetach: function(element){}, + onBound: function(coords){},*/ + onShow: function(){ + this.tip.setStyle('display', 'block'); + }, + onHide: function(){ + this.tip.setStyle('display', 'none'); + }, + title: 'title', + text: function(element){ + return element.get('rel') || element.get('href'); + }, + showDelay: 100, + hideDelay: 100, + className: 'tip-wrap', + offset: {x: 16, y: 16}, + windowPadding: {x:0, y:0}, + fixed: false, + waiAria: true + }, + + initialize: function(){ + var params = Array.link(arguments, { + options: Type.isObject, + elements: function(obj){ + return obj != null; + } + }); + this.setOptions(params.options); + if (params.elements) this.attach(params.elements); + this.container = new Element('div', {'class': 'tip'}); + + if (this.options.id){ + this.container.set('id', this.options.id); + if (this.options.waiAria) this.attachWaiAria(); + } + }, + + toElement: function(){ + if (this.tip) return this.tip; + + this.tip = new Element('div', { + 'class': this.options.className, + styles: { + position: 'absolute', + top: 0, + left: 0 + } + }).adopt( + new Element('div', {'class': 'tip-top'}), + this.container, + new Element('div', {'class': 'tip-bottom'}) + ); + + return this.tip; + }, + + attachWaiAria: function(){ + var id = this.options.id; + this.container.set('role', 'tooltip'); + + if (!this.waiAria){ + this.waiAria = { + show: function(element){ + if (id) element.set('aria-describedby', id); + this.container.set('aria-hidden', 'false'); + }, + hide: function(element){ + if (id) element.erase('aria-describedby'); + this.container.set('aria-hidden', 'true'); + } + }; + } + this.addEvents(this.waiAria); + }, + + detachWaiAria: function(){ + if (this.waiAria){ + this.container.erase('role'); + this.container.erase('aria-hidden'); + this.removeEvents(this.waiAria); + } + }, + + attach: function(elements){ + $$(elements).each(function(element){ + var title = read(this.options.title, element), + text = read(this.options.text, element); + + element.set('title', '').store('tip:native', title).retrieve('tip:title', title); + element.retrieve('tip:text', text); + this.fireEvent('attach', [element]); + + var events = ['enter', 'leave']; + if (!this.options.fixed) events.push('move'); + + events.each(function(value){ + var event = element.retrieve('tip:' + value); + if (!event) event = function(event){ + this['element' + value.capitalize()].apply(this, [event, element]); + }.bind(this); + + element.store('tip:' + value, event).addEvent('mouse' + value, event); + }, this); + }, this); + + return this; + }, + + detach: function(elements){ + $$(elements).each(function(element){ + ['enter', 'leave', 'move'].each(function(value){ + element.removeEvent('mouse' + value, element.retrieve('tip:' + value)).eliminate('tip:' + value); + }); + + this.fireEvent('detach', [element]); + + if (this.options.title == 'title'){ // This is necessary to check if we can revert the title + var original = element.retrieve('tip:native'); + if (original) element.set('title', original); + } + }, this); + + return this; + }, + + elementEnter: function(event, element){ + clearTimeout(this.timer); + this.timer = (function(){ + this.container.empty(); + + ['title', 'text'].each(function(value){ + var content = element.retrieve('tip:' + value); + var div = this['_' + value + 'Element'] = new Element('div', { + 'class': 'tip-' + value + }).inject(this.container); + if (content) this.fill(div, content); + }, this); + this.show(element); + this.position((this.options.fixed) ? {page: element.getPosition()} : event); + }).delay(this.options.showDelay, this); + }, + + elementLeave: function(event, element){ + clearTimeout(this.timer); + this.timer = this.hide.delay(this.options.hideDelay, this, element); + this.fireForParent(event, element); + }, + + setTitle: function(title){ + if (this._titleElement){ + this._titleElement.empty(); + this.fill(this._titleElement, title); + } + return this; + }, + + setText: function(text){ + if (this._textElement){ + this._textElement.empty(); + this.fill(this._textElement, text); + } + return this; + }, + + fireForParent: function(event, element){ + element = element.getParent(); + if (!element || element == document.body) return; + if (element.retrieve('tip:enter')) element.fireEvent('mouseenter', event); + else this.fireForParent(event, element); + }, + + elementMove: function(event, element){ + this.position(event); + }, + + position: function(event){ + if (!this.tip) document.id(this); + + var size = window.getSize(), scroll = window.getScroll(), + tip = {x: this.tip.offsetWidth, y: this.tip.offsetHeight}, + props = {x: 'left', y: 'top'}, + bounds = {y: false, x2: false, y2: false, x: false}, + obj = {}; + + for (var z in props){ + obj[props[z]] = event.page[z] + this.options.offset[z]; + if (obj[props[z]] < 0) bounds[z] = true; + if ((obj[props[z]] + tip[z] - scroll[z]) > size[z] - this.options.windowPadding[z]){ + obj[props[z]] = event.page[z] - this.options.offset[z] - tip[z]; + bounds[z+'2'] = true; + } + } + + this.fireEvent('bound', bounds); + this.tip.setStyles(obj); + }, + + fill: function(element, contents){ + if (typeof contents == 'string') element.set('html', contents); + else element.adopt(contents); + }, + + show: function(element){ + if (!this.tip) document.id(this); + if (!this.tip.getParent()) this.tip.inject(document.body); + this.fireEvent('show', [this.tip, element]); + }, + + hide: function(element){ + if (!this.tip) document.id(this); + this.fireEvent('hide', [this.tip, element]); + } + +}); + +})(); + +/* +--- + +name: Locale.EU.Number + +description: Number messages for Europe. + +license: MIT-style license + +authors: + - Arian Stolwijk + +requires: + - Locale + +provides: [Locale.EU.Number] + +... +*/ + +Locale.define('EU', 'Number', { + + decimal: ',', + group: '.', + + currency: { + prefix: '⬠' + } + +}); + +/* +--- + +script: Locale.Set.From.js + +name: Locale.Set.From + +description: Provides an alternative way to create Locale.Set objects. + +license: MIT-style license + +authors: + - Tim Wienk + +requires: + - Core/JSON + - Locale + +provides: Locale.Set.From + +... +*/ + +(function(){ + +var parsers = { + 'json': JSON.decode +}; + +Locale.Set.defineParser = function(name, fn){ + parsers[name] = fn; +}; + +Locale.Set.from = function(set, type){ + if (instanceOf(set, Locale.Set)) return set; + + if (!type && typeOf(set) == 'string') type = 'json'; + if (parsers[type]) set = parsers[type](set); + + var locale = new Locale.Set; + + locale.sets = set.sets || {}; + + if (set.inherits){ + locale.inherits.locales = Array.from(set.inherits.locales); + locale.inherits.sets = set.inherits.sets || {}; + } + + return locale; +}; + +})(); + +/* +--- + +name: Locale.ZA.Number + +description: Number messages for ZA. + +license: MIT-style license + +authors: + - Werner Mollentze + +requires: + - Locale + +provides: [Locale.ZA.Number] + +... +*/ + +Locale.define('ZA', 'Number', { + + decimal: '.', + group: ',', + + currency: { + prefix: 'R ' + } + +}); + + + +/* +--- + +name: Locale.af-ZA.Date + +description: Date messages for ZA Afrikaans. + +license: MIT-style license + +authors: + - Werner Mollentze + +requires: + - Locale + +provides: [Locale.af-ZA.Date] + +... +*/ + +Locale.define('af-ZA', 'Date', { + + months: ['Januarie', 'Februarie', 'Maart', 'April', 'Mei', 'Junie', 'Julie', 'Augustus', 'September', 'Oktober', 'November', 'Desember'], + months_abbr: ['Jan', 'Feb', 'Mrt', 'Apr', 'Mei', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'], + days: ['Sondag', 'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrydag', 'Saterdag'], + days_abbr: ['Son', 'Maa', 'Din', 'Woe', 'Don', 'Vry', 'Sat'], + + // Culture's date order: MM/DD/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d-%m-%Y', + shortTime: '%H:%M', + AM: 'VM', + PM: 'NM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: function(dayOfMonth){ + return ((dayOfMonth > 1 && dayOfMonth < 20 && dayOfMonth != 8) || (dayOfMonth > 100 && dayOfMonth.toString().substr(-2, 1) == '1')) ? 'de' : 'ste'; + }, + + lessThanMinuteAgo: 'minder as \'n minuut gelede', + minuteAgo: 'ongeveer \'n minuut gelede', + minutesAgo: '{delta} minute gelede', + hourAgo: 'omtret \'n uur gelede', + hoursAgo: 'ongeveer {delta} ure gelede', + dayAgo: '1 dag gelede', + daysAgo: '{delta} dae gelede', + weekAgo: '1 week gelede', + weeksAgo: '{delta} weke gelede', + monthAgo: '1 maand gelede', + monthsAgo: '{delta} maande gelede', + yearAgo: '1 jaar gelede', + yearsAgo: '{delta} jare gelede', + + lessThanMinuteUntil: 'oor minder as \'n minuut', + minuteUntil: 'oor ongeveer \'n minuut', + minutesUntil: 'oor {delta} minute', + hourUntil: 'oor ongeveer \'n uur', + hoursUntil: 'oor {delta} uur', + dayUntil: 'oor ongeveer \'n dag', + daysUntil: 'oor {delta} dae', + weekUntil: 'oor \'n week', + weeksUntil: 'oor {delta} weke', + monthUntil: 'oor \'n maand', + monthsUntil: 'oor {delta} maande', + yearUntil: 'oor \'n jaar', + yearsUntil: 'oor {delta} jaar' + +}); + +/* +--- + +name: Locale.af-ZA.Form.Validator + +description: Form Validator messages for Afrikaans. + +license: MIT-style license + +authors: + - Werner Mollentze + +requires: + - Locale + +provides: [Locale.af-ZA.Form.Validator] + +... +*/ + +Locale.define('af-ZA', 'FormValidator', { + + required: 'Hierdie veld word vereis.', + length: 'Voer asseblief {length} karakters in (u het {elLength} karakters ingevoer)', + minLength: 'Voer asseblief ten minste {minLength} karakters in (u het {length} karakters ingevoer).', + maxLength: 'Moet asseblief nie meer as {maxLength} karakters invoer nie (u het {length} karakters ingevoer).', + integer: 'Voer asseblief \'n heelgetal in hierdie veld in. Getalle met desimale (bv. 1.25) word nie toegelaat nie.', + numeric: 'Voer asseblief slegs numeriese waardes in hierdie veld in (bv. "1" of "1.1" of "-1" of "-1.1").', + digits: 'Gebruik asseblief slegs nommers en punktuasie in hierdie veld. (by voorbeeld, \'n telefoon nommer wat koppeltekens en punte bevat is toelaatbaar).', + alpha: 'Gebruik asseblief slegs letters (a-z) binne-in hierdie veld. Geen spasies of ander karakters word toegelaat nie.', + alphanum: 'Gebruik asseblief slegs letters (a-z) en nommers (0-9) binne-in hierdie veld. Geen spasies of ander karakters word toegelaat nie.', + dateSuchAs: 'Voer asseblief \'n geldige datum soos {date} in', + dateInFormatMDY: 'Voer asseblief \'n geldige datum soos MM/DD/YYYY in (bv. "12/31/1999")', + email: 'Voer asseblief \'n geldige e-pos adres in. Byvoorbeeld "fred@domain.com".', + url: 'Voer asseblief \'n geldige bronadres (URL) soos http://www.example.com in.', + currencyDollar: 'Voer asseblief \'n geldige $ bedrag in. Byvoorbeeld $100.00 .', + oneRequired: 'Voer asseblief iets in vir ten minste een van hierdie velde.', + errorPrefix: 'Fout: ', + warningPrefix: 'Waarskuwing: ', + + // Form.Validator.Extras + noSpace: 'Daar mag geen spasies in hierdie toevoer wees nie.', + reqChkByNode: 'Geen items is gekies nie.', + requiredChk: 'Hierdie veld word vereis.', + reqChkByName: 'Kies asseblief \'n {label}.', + match: 'Hierdie veld moet by die {matchName} veld pas', + startDate: 'die begin datum', + endDate: 'die eind datum', + currentDate: 'die huidige datum', + afterDate: 'Die datum moet dieselfde of na {label} wees.', + beforeDate: 'Die datum moet dieselfde of voor {label} wees.', + startMonth: 'Kies asseblief \'n begin maand', + sameMonth: 'Hierdie twee datums moet in dieselfde maand wees - u moet een of beide verander.', + creditcard: 'Die ingevoerde kredietkaart nommer is ongeldig. Bevestig asseblief die nommer en probeer weer. {length} syfers is ingevoer.' + +}); + +/* +--- + +name: Locale.af-ZA.Number + +description: Number messages for ZA Afrikaans. + +license: MIT-style license + +authors: + - Werner Mollentze + +requires: + - Locale + - Locale.ZA.Number + +provides: [Locale.af-ZA.Number] + +... +*/ + +Locale.define('af-ZA').inherit('ZA', 'Number'); + +/* +--- + +name: Locale.ar.Date + +description: Date messages for Arabic. + +license: MIT-style license + +authors: + - Chafik Barbar + +requires: + - Locale + +provides: [Locale.ar.Date] + +... +*/ + +Locale.define('ar', 'Date', { + + // Culture's date order: DD/MM/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d/%m/%Y', + shortTime: '%H:%M' + +}); + +/* +--- + +name: Locale.ar.Form.Validator + +description: Form Validator messages for Arabic. + +license: MIT-style license + +authors: + - Chafik Barbar + +requires: + - Locale + +provides: [Locale.ar.Form.Validator] + +... +*/ + +Locale.define('ar', 'FormValidator', { + + required: 'ÙØ°Ø§ Ø§ÙØÙÙ Ù
Ø·ÙÙØš.', + minLength: 'رجاء٠إدخا٠{minLength} Ø£ØØ±Ù عÙÙ Ø§ÙØ£ÙÙ (تÙ
إدخا٠{length} Ø£ØØ±Ù).', + maxLength: 'Ø§ÙØ±Ø¬Ø§Ø¡ عدÙ
Ø¥Ø¯Ø®Ø§Ù Ø£ÙØ«Ø± Ù
Ù {maxLength} Ø£ØØ±Ù (تÙ
إدخا٠{length} Ø£ØØ±Ù).', + integer: 'Ø§ÙØ±Ø¬Ø§Ø¡ إدخا٠عدد صØÙØ ÙÙ ÙØ°Ø§ Ø§ÙØÙÙ. أ٠رÙÙ
Ø°Ù ÙØ³Ø± ع؎ر٠أ٠Ù
ØŠÙÙ (Ù
ثا٠1.25 ) ØºÙØ± Ù
سÙ
ÙØ.', + numeric: 'Ø§ÙØ±Ø¬Ø§Ø¡ إدخا٠ÙÙÙ
رÙÙ
ÙØ© ÙÙ ÙØ°Ø§ Ø§ÙØÙÙ (Ù
ثا٠"1" Ø£Ù "1.1" Ø£Ù "-1" Ø£Ù "-1.1").', + digits: 'Ø§ÙØ±Ø¬Ø§Ø¡ أستخداÙ
ÙÙÙ
رÙÙ
ÙØ© ÙØ¹ÙاÙ
ات ترÙÙÙ
ÙØ© ÙÙØ· ÙÙ ÙØ°Ø§ Ø§ÙØÙÙ (Ù
ثاÙ, رÙÙ
ÙØ§ØªÙ Ù
ع ÙÙØ·Ø© Ø£Ù ØŽØØ·Ø©)', + alpha: 'Ø§ÙØ±Ø¬Ø§Ø¡ أستخداÙ
Ø£ØØ±Ù ÙÙØ· (ا-Ù) ÙÙ ÙØ°Ø§ Ø§ÙØÙÙ. Ø£Ù ÙØ±Ø§ØºØ§Øª Ø£Ù Ø¹ÙØ§Ù
ات ØºÙØ± Ù
سÙ
ÙØØ©.', + alphanum: 'Ø§ÙØ±Ø¬Ø§Ø¡ أستخداÙ
Ø£ØØ±Ù ÙÙØ· (ا-Ù) Ø£Ù Ø£Ø±ÙØ§Ù
(0-9) ÙÙØ· ÙÙ ÙØ°Ø§ Ø§ÙØÙÙ. Ø£Ù ÙØ±Ø§ØºØ§Øª Ø£Ù Ø¹ÙØ§Ù
ات ØºÙØ± Ù
سÙ
ÙØØ©.', + dateSuchAs: 'Ø§ÙØ±Ø¬Ø§Ø¡ Ø¥Ø¯Ø®Ø§Ù ØªØ§Ø±ÙØ® صØÙØ ÙØ§ÙتاÙÙ {date}', + dateInFormatMDY: 'Ø§ÙØ±Ø¬Ø§Ø¡ Ø¥Ø¯Ø®Ø§Ù ØªØ§Ø±ÙØ® صØÙØ (Ù
ثاÙ, 31-12-1999)', + email: 'Ø§ÙØ±Ø¬Ø§Ø¡ Ø¥Ø¯Ø®Ø§Ù ØšØ±ÙØ¯ Ø¥ÙÙØªØ±ÙÙ٠صØÙØ.', + url: 'Ø§ÙØ±Ø¬Ø§Ø¡ إدخا٠عÙÙØ§Ù Ø¥ÙÙØªØ±ÙÙ٠صØÙØ Ù
ث٠http://www.example.com', + currencyDollar: 'Ø§ÙØ±Ø¬Ø§Ø¡ إدخا٠ÙÙÙ
Ø© $ صØÙØØ©. Ù
ثاÙ, 100.00$', + oneRequired: 'Ø§ÙØ±Ø¬Ø§Ø¡ إدخا٠ÙÙÙ
Ø© ÙÙ Ø£ØØ¯ ÙØ°Ù Ø§ÙØÙÙ٠عÙÙ Ø§ÙØ£ÙÙ.', + errorPrefix: 'خطأ: ', + warningPrefix: 'ØªØØ°Ùر: ' + +}); + +/* +--- + +name: Locale.ca-CA.Date + +description: Date messages for Catalan. + +license: MIT-style license + +authors: + - Ãlfons Sanchez + +requires: + - Locale + +provides: [Locale.ca-CA.Date] + +... +*/ + +Locale.define('ca-CA', 'Date', { + + months: ['Gener', 'Febrer', 'Març', 'Abril', 'Maig', 'Juny', 'Juli', 'Agost', 'Setembre', 'Octubre', 'Novembre', 'Desembre'], + months_abbr: ['gen.', 'febr.', 'març', 'abr.', 'maig', 'juny', 'jul.', 'ag.', 'set.', 'oct.', 'nov.', 'des.'], + days: ['Diumenge', 'Dilluns', 'Dimarts', 'Dimecres', 'Dijous', 'Divendres', 'Dissabte'], + days_abbr: ['dg', 'dl', 'dt', 'dc', 'dj', 'dv', 'ds'], + + // Culture's date order: DD/MM/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d/%m/%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 0, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: 'fa menys d`un minut', + minuteAgo: 'fa un minut', + minutesAgo: 'fa {delta} minuts', + hourAgo: 'fa un hora', + hoursAgo: 'fa unes {delta} hores', + dayAgo: 'fa un dia', + daysAgo: 'fa {delta} dies', + + lessThanMinuteUntil: 'menys d`un minut des d`ara', + minuteUntil: 'un minut des d`ara', + minutesUntil: '{delta} minuts des d`ara', + hourUntil: 'un hora des d`ara', + hoursUntil: 'unes {delta} hores des d`ara', + dayUntil: '1 dia des d`ara', + daysUntil: '{delta} dies des d`ara' + +}); + +/* +--- + +name: Locale.ca-CA.Form.Validator + +description: Form Validator messages for Catalan. + +license: MIT-style license + +authors: + - Miquel Hudin + - Ãlfons Sanchez + +requires: + - Locale + +provides: [Locale.ca-CA.Form.Validator] + +... +*/ + +Locale.define('ca-CA', 'FormValidator', { + + required: 'Aquest camp es obligatori.', + minLength: 'Per favor introdueix al menys {minLength} caracters (has introduit {length} caracters).', + maxLength: 'Per favor introdueix no mes de {maxLength} caracters (has introduit {length} caracters).', + integer: 'Per favor introdueix un nombre enter en aquest camp. Nombres amb decimals (p.e. 1,25) no estan permesos.', + numeric: 'Per favor introdueix sols valors numerics en aquest camp (p.e. "1" o "1,1" o "-1" o "-1,1").', + digits: 'Per favor usa sols numeros i puntuacio en aquest camp (per exemple, un nombre de telefon amb guions i punts no esta permes).', + alpha: 'Per favor utilitza lletres nomes (a-z) en aquest camp. No sÂŽadmiteixen espais ni altres caracters.', + alphanum: 'Per favor, utilitza nomes lletres (a-z) o numeros (0-9) en aquest camp. No sÂŽadmiteixen espais ni altres caracters.', + dateSuchAs: 'Per favor introdueix una data valida com {date}', + dateInFormatMDY: 'Per favor introdueix una data valida com DD/MM/YYYY (p.e. "31/12/1999")', + email: 'Per favor, introdueix una adreça de correu electronic valida. Per exemple, "fred@domain.com".', + url: 'Per favor introdueix una URL valida com http://www.example.com.', + currencyDollar: 'Per favor introdueix una quantitat valida de â¬. Per exemple â¬100,00 .', + oneRequired: 'Per favor introdueix alguna cosa per al menys una dÂŽaquestes entrades.', + errorPrefix: 'Error: ', + warningPrefix: 'Avis: ', + + // Form.Validator.Extras + noSpace: 'No poden haver espais en aquesta entrada.', + reqChkByNode: 'No hi han elements seleccionats.', + requiredChk: 'Aquest camp es obligatori.', + reqChkByName: 'Per favor selecciona una {label}.', + match: 'Aquest camp necessita coincidir amb el camp {matchName}', + startDate: 'la data de inici', + endDate: 'la data de fi', + currentDate: 'la data actual', + afterDate: 'La data deu ser igual o posterior a {label}.', + beforeDate: 'La data deu ser igual o anterior a {label}.', + startMonth: 'Per favor selecciona un mes dÂŽorige', + sameMonth: 'Aquestes dos dates deuen estar dins del mateix mes - deus canviar una o altra.' + +}); + +/* +--- + +name: Locale.cs-CZ.Date + +description: Date messages for Czech. + +license: MIT-style license + +authors: + - Jan ÄernÜ chemiX + - Christopher Zukowski + +requires: + - Locale + +provides: [Locale.cs-CZ.Date] + +... +*/ +(function(){ + +// Czech language pluralization rules, see http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html +// one -> n is 1; 1 +// few -> n in 2..4; 2-4 +// other -> everything else 0, 5-999, 1.31, 2.31, 5.31... +var pluralize = function (n, one, few, other){ + if (n == 1) return one; + else if (n == 2 || n == 3 || n == 4) return few; + else return other; +}; + +Locale.define('cs-CZ', 'Date', { + + months: ['Leden', 'Ãnor', 'BÅezen', 'Duben', 'KvÄten', 'Äerven', 'Äervenec', 'Srpen', 'ZáÅÃ', 'ÅÃjen', 'Listopad', 'Prosinec'], + months_abbr: ['ledna', 'února', 'bÅezna', 'dubna', 'kvÄtna', 'Äervna', 'Äervence', 'srpna', 'záÅÃ', 'ÅÃjna', 'listopadu', 'prosince'], + days: ['NedÄle', 'PondÄlÃ', 'ÃterÜ', 'StÅeda', 'Ätvrtek', 'Pátek', 'Sobota'], + days_abbr: ['ne', 'po', 'út', 'st', 'Ät', 'pá', 'so'], + + // Culture's date order: DD.MM.YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d.%m.%Y', + shortTime: '%H:%M', + AM: 'dop.', + PM: 'odp.', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '.', + + lessThanMinuteAgo: 'pÅed chvÃlÃ', + minuteAgo: 'pÅibliÅŸnÄ pÅed minutou', + minutesAgo: function(delta){ return 'pÅed {delta} ' + pluralize(delta, 'minutou', 'minutami', 'minutami'); }, + hourAgo: 'pÅibliÅŸnÄ pÅed hodinou', + hoursAgo: function(delta){ return 'pÅed {delta} ' + pluralize(delta, 'hodinou', 'hodinami', 'hodinami'); }, + dayAgo: 'pÅed dnem', + daysAgo: function(delta){ return 'pÅed {delta} ' + pluralize(delta, 'dnem', 'dny', 'dny'); }, + weekAgo: 'pÅed tÜdnem', + weeksAgo: function(delta){ return 'pÅed {delta} ' + pluralize(delta, 'tÜdnem', 'tÜdny', 'tÜdny'); }, + monthAgo: 'pÅed mÄsÃcem', + monthsAgo: function(delta){ return 'pÅed {delta} ' + pluralize(delta, 'mÄsÃcem', 'mÄsÃci', 'mÄsÃci'); }, + yearAgo: 'pÅed rokem', + yearsAgo: function(delta){ return 'pÅed {delta} ' + pluralize(delta, 'rokem', 'lety', 'lety'); }, + + lessThanMinuteUntil: 'za chvÃli', + minuteUntil: 'pÅibliÅŸnÄ za minutu', + minutesUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'minutu', 'minuty', 'minut'); }, + hourUntil: 'pÅibliÅŸnÄ za hodinu', + hoursUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'hodinu', 'hodiny', 'hodin'); }, + dayUntil: 'za den', + daysUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'den', 'dny', 'dnů'); }, + weekUntil: 'za tÜden', + weeksUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'tÜden', 'tÜdny', 'tÜdnů'); }, + monthUntil: 'za mÄsÃc', + monthsUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'mÄsÃc', 'mÄsÃce', 'mÄsÃců'); }, + yearUntil: 'za rok', + yearsUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'rok', 'roky', 'let'); } +}); + +})(); + +/* +--- + +name: Locale.cs-CZ.Form.Validator + +description: Form Validator messages for Czech. + +license: MIT-style license + +authors: + - Jan ÄernÜ chemiX + +requires: + - Locale + +provides: [Locale.cs-CZ.Form.Validator] + +... +*/ + +Locale.define('cs-CZ', 'FormValidator', { + + required: 'Tato poloÅŸka je povinná.', + minLength: 'Zadejte prosÃm alespoÅ {minLength} znaků (napsáno {length} znaků).', + maxLength: 'Zadejte prosÃm ménÄ neÅŸ {maxLength} znaků (nápsáno {length} znaků).', + integer: 'Zadejte prosÃm celé ÄÃslo. Desetinná ÄÃsla (napÅ. 1.25) nejsou povolena.', + numeric: 'Zadejte jen ÄÃselné hodnoty (tj. "1" nebo "1.1" nebo "-1" nebo "-1.1").', + digits: 'Zadejte prosÃm pouze ÄÃsla a interpunkÄnà znaménka(napÅÃklad telefonnà ÄÃslo s pomlÄkami nebo teÄkami je povoleno).', + alpha: 'Zadejte prosÃm pouze pÃsmena (a-z). Mezery nebo jiné znaky nejsou povoleny.', + alphanum: 'Zadejte prosÃm pouze pÃsmena (a-z) nebo ÄÃslice (0-9). Mezery nebo jiné znaky nejsou povoleny.', + dateSuchAs: 'Zadejte prosÃm platné datum jako {date}', + dateInFormatMDY: 'Zadejte prosÃm platné datum jako MM / DD / RRRR (tj. "12/31/1999")', + email: 'Zadejte prosÃm platnou e-mailovou adresu. NapÅÃklad "fred@domain.com".', + url: 'Zadejte prosÃm platnou URL adresu jako http://www.example.com.', + currencyDollar: 'Zadejte prosÃm platnou Äástku. NapÅÃklad $100.00.', + oneRequired: 'Zadejte prosÃm alespoÅ jednu hodnotu pro tyto poloÅŸky.', + errorPrefix: 'Chyba: ', + warningPrefix: 'UpozornÄnÃ: ', + + // Form.Validator.Extras + noSpace: 'V této poloÅŸce nejsou povoleny mezery', + reqChkByNode: 'Nejsou vybrány şádné poloÅŸky.', + requiredChk: 'Tato poloÅŸka je vyÅŸadována.', + reqChkByName: 'ProsÃm vyberte {label}.', + match: 'Tato poloÅŸka se musà shodovat s poloÅŸkou {matchName}', + startDate: 'datum zahájenÃ', + endDate: 'datum ukonÄenÃ', + currentDate: 'aktuálnà datum', + afterDate: 'Datum by mÄlo bÜt stejné nebo vÄtšà neÅŸ {label}.', + beforeDate: 'Datum by mÄlo bÜt stejné nebo menšà neÅŸ {label}.', + startMonth: 'Vyberte poÄáteÄnà mÄsÃc.', + sameMonth: 'Tyto dva datumy musà bÜt ve stejném mÄsÃci - zmÄÅte jeden z nich.', + creditcard: 'Zadané ÄÃslo kreditnà karty je neplatné. ProsÃm opravte ho. Bylo zadáno {length} ÄÃsel.' + +}); + +/* +--- + +name: Locale.da-DK.Date + +description: Date messages for Danish. + +license: MIT-style license + +authors: + - Martin Overgaard + - Henrik Hansen + +requires: + - Locale + +provides: [Locale.da-DK.Date] + +... +*/ + +Locale.define('da-DK', 'Date', { + + months: ['Januar', 'Februar', 'Marts', 'April', 'Maj', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'December'], + months_abbr: ['jan.', 'feb.', 'mar.', 'apr.', 'maj.', 'jun.', 'jul.', 'aug.', 'sep.', 'okt.', 'nov.', 'dec.'], + days: ['SÞndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'LÞrdag'], + days_abbr: ['sÞn', 'man', 'tir', 'ons', 'tor', 'fre', 'lÞr'], + + // Culture's date order: DD-MM-YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d-%m-%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '.', + + lessThanMinuteAgo: 'mindre end et minut siden', + minuteAgo: 'omkring et minut siden', + minutesAgo: '{delta} minutter siden', + hourAgo: 'omkring en time siden', + hoursAgo: 'omkring {delta} timer siden', + dayAgo: '1 dag siden', + daysAgo: '{delta} dage siden', + weekAgo: '1 uge siden', + weeksAgo: '{delta} uger siden', + monthAgo: '1 mÃ¥ned siden', + monthsAgo: '{delta} mÃ¥neder siden', + yearAgo: '1 Ã¥r siden', + yearsAgo: '{delta} Ã¥r siden', + + lessThanMinuteUntil: 'mindre end et minut fra nu', + minuteUntil: 'omkring et minut fra nu', + minutesUntil: '{delta} minutter fra nu', + hourUntil: 'omkring en time fra nu', + hoursUntil: 'omkring {delta} timer fra nu', + dayUntil: '1 dag fra nu', + daysUntil: '{delta} dage fra nu', + weekUntil: '1 uge fra nu', + weeksUntil: '{delta} uger fra nu', + monthUntil: '1 mÃ¥ned fra nu', + monthsUntil: '{delta} mÃ¥neder fra nu', + yearUntil: '1 Ã¥r fra nu', + yearsUntil: '{delta} Ã¥r fra nu' + +}); + +/* +--- + +name: Locale.da-DK.Form.Validator + +description: Form Validator messages for Danish. + +license: MIT-style license + +authors: + - Martin Overgaard + +requires: + - Locale + +provides: [Locale.da-DK.Form.Validator] + +... +*/ + +Locale.define('da-DK', 'FormValidator', { + + required: 'Feltet skal udfyldes.', + minLength: 'Skriv mindst {minLength} tegn (du skrev {length} tegn).', + maxLength: 'Skriv maksimalt {maxLength} tegn (du skrev {length} tegn).', + integer: 'Skriv et tal i dette felt. Decimal tal (f.eks. 1.25) er ikke tilladt.', + numeric: 'Skriv kun tal i dette felt (i.e. "1" eller "1.1" eller "-1" eller "-1.1").', + digits: 'Skriv kun tal og tegnsÊtning i dette felt (eksempel, et telefon nummer med bindestreg eller punktum er tilladt).', + alpha: 'Skriv kun bogstaver (a-z) i dette felt. Mellemrum og andre tegn er ikke tilladt.', + alphanum: 'Skriv kun bogstaver (a-z) eller tal (0-9) i dette felt. Mellemrum og andre tegn er ikke tilladt.', + dateSuchAs: 'Skriv en gyldig dato som {date}', + dateInFormatMDY: 'Skriv dato i formatet DD-MM-YYYY (f.eks. "31-12-1999")', + email: 'Skriv en gyldig e-mail adresse. F.eks "fred@domain.com".', + url: 'Skriv en gyldig URL adresse. F.eks "http://www.example.com".', + currencyDollar: 'Skriv et gldigt belÞb. F.eks Kr.100.00 .', + oneRequired: 'Et eller flere af felterne i denne formular skal udfyldes.', + errorPrefix: 'Fejl: ', + warningPrefix: 'Advarsel: ', + + // Form.Validator.Extras + noSpace: 'Der mÃ¥ ikke benyttes mellemrum i dette felt.', + reqChkByNode: 'Foretag et valg.', + requiredChk: 'Dette felt skal udfyldes.', + reqChkByName: 'VÊlg en {label}.', + match: 'Dette felt skal matche {matchName} feltet', + startDate: 'start dato', + endDate: 'slut dato', + currentDate: 'dags dato', + afterDate: 'Datoen skal vÊre stÞrre end eller lig med {label}.', + beforeDate: 'Datoen skal vÊre mindre end eller lig med {label}.', + startMonth: 'VÊlg en start mÃ¥ned', + sameMonth: 'De valgte datoer skal vÊre i samme mÃ¥ned - skift en af dem.' + +}); + +/* +--- + +name: Locale.de-DE.Date + +description: Date messages for German. + +license: MIT-style license + +authors: + - Christoph Pojer + - Frank Rossi + - Ulrich Petri + - Fabian Beiner + +requires: + - Locale + +provides: [Locale.de-DE.Date] + +... +*/ + +Locale.define('de-DE', 'Date', { + + months: ['Januar', 'Februar', 'MÀrz', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'], + months_abbr: ['Jan', 'Feb', 'MÀr', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'], + days: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'], + days_abbr: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'], + + // Culture's date order: DD.MM.YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d.%m.%Y', + shortTime: '%H:%M', + AM: 'vormittags', + PM: 'nachmittags', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '.', + + lessThanMinuteAgo: 'vor weniger als einer Minute', + minuteAgo: 'vor einer Minute', + minutesAgo: 'vor {delta} Minuten', + hourAgo: 'vor einer Stunde', + hoursAgo: 'vor {delta} Stunden', + dayAgo: 'vor einem Tag', + daysAgo: 'vor {delta} Tagen', + weekAgo: 'vor einer Woche', + weeksAgo: 'vor {delta} Wochen', + monthAgo: 'vor einem Monat', + monthsAgo: 'vor {delta} Monaten', + yearAgo: 'vor einem Jahr', + yearsAgo: 'vor {delta} Jahren', + + lessThanMinuteUntil: 'in weniger als einer Minute', + minuteUntil: 'in einer Minute', + minutesUntil: 'in {delta} Minuten', + hourUntil: 'in ca. einer Stunde', + hoursUntil: 'in ca. {delta} Stunden', + dayUntil: 'in einem Tag', + daysUntil: 'in {delta} Tagen', + weekUntil: 'in einer Woche', + weeksUntil: 'in {delta} Wochen', + monthUntil: 'in einem Monat', + monthsUntil: 'in {delta} Monaten', + yearUntil: 'in einem Jahr', + yearsUntil: 'in {delta} Jahren' + +}); + +/* +--- + +name: Locale.de-CH.Date + +description: Date messages for German (Switzerland). + +license: MIT-style license + +authors: + - Michael van der Weg + +requires: + - Locale + - Locale.de-DE.Date + +provides: [Locale.de-CH.Date] + +... +*/ + +Locale.define('de-CH').inherit('de-DE', 'Date'); + +/* +--- + +name: Locale.de-CH.Form.Validator + +description: Form Validator messages for German (Switzerland). + +license: MIT-style license + +authors: + - Michael van der Weg + +requires: + - Locale + +provides: [Locale.de-CH.Form.Validator] + +... +*/ + +Locale.define('de-CH', 'FormValidator', { + + required: 'Dieses Feld ist obligatorisch.', + minLength: 'Geben Sie bitte mindestens {minLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).', + maxLength: 'Bitte geben Sie nicht mehr als {maxLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).', + integer: 'Geben Sie bitte eine ganze Zahl ein. Dezimalzahlen (z.B. 1.25) sind nicht erlaubt.', + numeric: 'Geben Sie bitte nur Zahlenwerte in dieses Eingabefeld ein (z.B. "1", "1.1", "-1" oder "-1.1").', + digits: 'Benutzen Sie bitte nur Zahlen und Satzzeichen in diesem Eingabefeld (erlaubt ist z.B. eine Telefonnummer mit Bindestrichen und Punkten).', + alpha: 'Benutzen Sie bitte nur Buchstaben (a-z) in diesem Feld. Leerzeichen und andere Zeichen sind nicht erlaubt.', + alphanum: 'Benutzen Sie bitte nur Buchstaben (a-z) und Zahlen (0-9) in diesem Eingabefeld. Leerzeichen und andere Zeichen sind nicht erlaubt.', + dateSuchAs: 'Geben Sie bitte ein gültiges Datum ein. Wie zum Beispiel {date}', + dateInFormatMDY: 'Geben Sie bitte ein gültiges Datum ein. Wie zum Beispiel TT.MM.JJJJ (z.B. "31.12.1999")', + email: 'Geben Sie bitte eine gültige E-Mail Adresse ein. Wie zum Beispiel "maria@bernasconi.ch".', + url: 'Geben Sie bitte eine gültige URL ein. Wie zum Beispiel http://www.example.com.', + currencyDollar: 'Geben Sie bitte einen gültigen Betrag in Schweizer Franken ein. Wie zum Beispiel 100.00 CHF .', + oneRequired: 'Machen Sie für mindestens eines der Eingabefelder einen Eintrag.', + errorPrefix: 'Fehler: ', + warningPrefix: 'Warnung: ', + + // Form.Validator.Extras + noSpace: 'In diesem Eingabefeld darf kein Leerzeichen sein.', + reqChkByNode: 'Es wurden keine Elemente gewählt.', + requiredChk: 'Dieses Feld ist obligatorisch.', + reqChkByName: 'Bitte wählen Sie ein {label}.', + match: 'Dieses Eingabefeld muss mit dem Feld {matchName} übereinstimmen.', + startDate: 'Das Anfangsdatum', + endDate: 'Das Enddatum', + currentDate: 'Das aktuelle Datum', + afterDate: 'Das Datum sollte zur gleichen Zeit oder später sein {label}.', + beforeDate: 'Das Datum sollte zur gleichen Zeit oder früher sein {label}.', + startMonth: 'Wählen Sie bitte einen Anfangsmonat', + sameMonth: 'Diese zwei Datumsangaben müssen im selben Monat sein - Sie müssen eine von beiden verändern.', + creditcard: 'Die eingegebene Kreditkartennummer ist ungültig. Bitte überprüfen Sie diese und versuchen Sie es erneut. {length} Zahlen eingegeben.' + +}); + +/* +--- + +name: Locale.de-DE.Form.Validator + +description: Form Validator messages for German. + +license: MIT-style license + +authors: + - Frank Rossi + - Ulrich Petri + - Fabian Beiner + +requires: + - Locale + +provides: [Locale.de-DE.Form.Validator] + +... +*/ + +Locale.define('de-DE', 'FormValidator', { + + required: 'Dieses Eingabefeld muss ausgefÃŒllt werden.', + minLength: 'Geben Sie bitte mindestens {minLength} Zeichen ein (Sie haben nur {length} Zeichen eingegeben).', + maxLength: 'Geben Sie bitte nicht mehr als {maxLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).', + integer: 'Geben Sie in diesem Eingabefeld bitte eine ganze Zahl ein. Dezimalzahlen (z.B. "1.25") sind nicht erlaubt.', + numeric: 'Geben Sie in diesem Eingabefeld bitte nur Zahlenwerte (z.B. "1", "1.1", "-1" oder "-1.1") ein.', + digits: 'Geben Sie in diesem Eingabefeld bitte nur Zahlen und Satzzeichen ein (z.B. eine Telefonnummer mit Bindestrichen und Punkten ist erlaubt).', + alpha: 'Geben Sie in diesem Eingabefeld bitte nur Buchstaben (a-z) ein. Leerzeichen und andere Zeichen sind nicht erlaubt.', + alphanum: 'Geben Sie in diesem Eingabefeld bitte nur Buchstaben (a-z) und Zahlen (0-9) ein. Leerzeichen oder andere Zeichen sind nicht erlaubt.', + dateSuchAs: 'Geben Sie bitte ein gÃŒltiges Datum ein (z.B. "{date}").', + dateInFormatMDY: 'Geben Sie bitte ein gÃŒltiges Datum im Format TT.MM.JJJJ ein (z.B. "31.12.1999").', + email: 'Geben Sie bitte eine gÃŒltige E-Mail-Adresse ein (z.B. "max@mustermann.de").', + url: 'Geben Sie bitte eine gÃŒltige URL ein (z.B. "http://www.example.com").', + currencyDollar: 'Geben Sie bitte einen gÃŒltigen Betrag in EURO ein (z.B. 100.00â¬).', + oneRequired: 'Bitte fÃŒllen Sie mindestens ein Eingabefeld aus.', + errorPrefix: 'Fehler: ', + warningPrefix: 'Warnung: ', + + // Form.Validator.Extras + noSpace: 'Es darf kein Leerzeichen in diesem Eingabefeld sein.', + reqChkByNode: 'Es wurden keine Elemente gewÀhlt.', + requiredChk: 'Dieses Feld muss ausgefÃŒllt werden.', + reqChkByName: 'Bitte wÀhlen Sie ein {label}.', + match: 'Dieses Eingabefeld muss mit dem {matchName} Eingabefeld ÃŒbereinstimmen.', + startDate: 'Das Anfangsdatum', + endDate: 'Das Enddatum', + currentDate: 'Das aktuelle Datum', + afterDate: 'Das Datum sollte zur gleichen Zeit oder spÀter sein als {label}.', + beforeDate: 'Das Datum sollte zur gleichen Zeit oder frÃŒher sein als {label}.', + startMonth: 'WÀhlen Sie bitte einen Anfangsmonat', + sameMonth: 'Diese zwei Datumsangaben mÃŒssen im selben Monat sein - Sie mÃŒssen eines von beiden verÀndern.', + creditcard: 'Die eingegebene Kreditkartennummer ist ungÃŒltig. Bitte ÃŒberprÃŒfen Sie diese und versuchen Sie es erneut. {length} Zahlen eingegeben.' + +}); + +/* +--- + +name: Locale.de-DE.Number + +description: Number messages for German. + +license: MIT-style license + +authors: + - Christoph Pojer + +requires: + - Locale + - Locale.EU.Number + +provides: [Locale.de-DE.Number] + +... +*/ + +Locale.define('de-DE').inherit('EU', 'Number'); + +/* +--- + +name: Locale.el-GR.Date + +description: Date messages for Greek language. + +license: MIT-style license + +authors: + - Periklis Argiriadis + +requires: + - Locale + +provides: [Locale.el-GR.Date] + +... +*/ + +Locale.define('el-GR', 'Date', { + + months: ['ÎαΜοÏ
άÏιοÏ', 'ΊεβÏοÏ
άÏιοÏ', 'ÎάÏÏιοÏ', 'ÎÏÏίλιοÏ', 'ÎάιοÏ', 'ÎοÏΜιοÏ', 'ÎοÏλιοÏ', 'ÎÏγοÏ
ÏÏοÏ', 'ΣεÏÏÎΌβÏιοÏ', 'ÎκÏÏβÏιοÏ', 'ÎοÎΌβÏιοÏ', 'ÎεκÎΌβÏιοÏ'], + months_abbr: ['ÎαΜ', 'Ίεβ', 'ÎαÏ', 'ÎÏÏ', 'Îάι', 'ÎοÏ
Μ', 'ÎοÏ
λ', 'ÎÏ
γ', 'ΣεÏ', 'ÎκÏ', 'Îοε', 'Îεκ'], + days: ['ÎÏ
Ïιακή', 'ÎεÏ
ÏÎÏα', '΀ÏίÏη', '΀εÏάÏÏη', 'Î ÎÎŒÏÏη', 'ΠαÏαÏκεÏ
ή', 'ΣάββαÏο'], + days_abbr: ['ÎÏ
Ï', 'ÎεÏ
', '΀Ïι', '΀εÏ', 'ΠεΌ', 'ΠαÏ', 'Σαβ'], + + // Culture's date order: DD/MM/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d/%m/%Y', + shortTime: '%I:%M%p', + AM: 'ÏÎŒ', + PM: 'ΌΌ', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: function(dayOfMonth){ + // 1st, 2nd, 3rd, etc. + return (dayOfMonth > 3 && dayOfMonth < 21) ? 'οÏ' : ['οÏ'][Math.min(dayOfMonth % 10, 4)]; + }, + + lessThanMinuteAgo: 'λιγÏÏεÏο αÏÏ ÎΜα λεÏÏÏ ÏÏιΜ', + minuteAgo: 'ÏεÏίÏοÏ
ÎΜα λεÏÏÏ ÏÏιΜ', + minutesAgo: '{delta} λεÏÏά ÏÏιΜ', + hourAgo: 'ÏεÏίÏοÏ
Όια ÏÏα ÏÏιΜ', + hoursAgo: 'ÏεÏίÏοÏ
{delta} ÏÏÎµÏ ÏÏιΜ', + dayAgo: '1 ηΌÎÏα ÏÏιΜ', + daysAgo: '{delta} ηΌÎÏÎµÏ ÏÏιΜ', + weekAgo: '1 εβΎοΌάΎα ÏÏιΜ', + weeksAgo: '{delta} ÎµÎ²ÎŽÎ¿ÎŒÎ¬ÎŽÎµÏ ÏÏιΜ', + monthAgo: '1 ΌήΜα ÏÏιΜ', + monthsAgo: '{delta} ÎŒÎ®ÎœÎµÏ ÏÏιΜ', + yearAgo: '1 ÏÏÏΜο ÏÏιΜ', + yearsAgo: '{delta} ÏÏÏΜια ÏÏιΜ', + + lessThanMinuteUntil: 'λιγÏÏεÏο αÏÏ Î»ÎµÏÏÏ Î±ÏÏ ÏÏÏα', + minuteUntil: 'ÏεÏίÏοÏ
ÎΜα λεÏÏÏ Î±ÏÏ ÏÏÏα', + minutesUntil: '{delta} λεÏÏά αÏÏ ÏÏÏα', + hourUntil: 'ÏεÏίÏοÏ
Όια ÏÏα αÏÏ ÏÏÏα', + hoursUntil: 'ÏεÏίÏοÏ
{delta} ÏÏÎµÏ Î±ÏÏ ÏÏÏα', + dayUntil: '1 ηΌÎÏα αÏÏ ÏÏÏα', + daysUntil: '{delta} ηΌÎÏÎµÏ Î±ÏÏ ÏÏÏα', + weekUntil: '1 εβΎοΌάΎα αÏÏ ÏÏÏα', + weeksUntil: '{delta} ÎµÎ²ÎŽÎ¿ÎŒÎ¬ÎŽÎµÏ Î±ÏÏ ÏÏÏα', + monthUntil: '1 ÎŒÎ®ÎœÎ±Ï Î±ÏÏ ÏÏÏα', + monthsUntil: '{delta} ÎŒÎ®ÎœÎµÏ Î±ÏÏ ÏÏÏα', + yearUntil: '1 ÏÏÏÎœÎ¿Ï Î±ÏÏ ÏÏÏα', + yearsUntil: '{delta} ÏÏÏΜια αÏÏ ÏÏÏα' + +}); + +/* +--- + +name: Locale.el-GR.Form.Validator + +description: Form Validator messages for Greek language. + +license: MIT-style license + +authors: + - Dimitris Tsironis + +requires: + - Locale + +provides: [Locale.el-GR.Form.Validator] + +... +*/ + +Locale.define('el-GR', 'FormValidator', { + + required: 'ÎÏ
ÏÏ Ïο ÏεΎίο είΜαι αÏαÏαίÏηÏο.', + length: 'ΠαÏακαλοÏΌε, ειÏάγεÏε {length} ÏαÏακÏήÏÎµÏ (ÎÏεÏε ήΎη ειÏάγει {elLength} ÏαÏακÏήÏεÏ).', + minLength: 'ΠαÏακαλοÏΌε, ειÏάγεÏε ÏοÏ
λάÏιÏÏοΜ {minLength} ÏαÏακÏήÏÎµÏ (ÎÏεÏε ήΎη ειÏάγε {length} ÏαÏακÏήÏεÏ).', + maxlength: 'ΠαÏακαλοÏΌε, ειÏάγεÏε εÏÏ {maxlength} ÏαÏακÏήÏÎµÏ (ÎÏεÏε ήΎη ειÏάγε {length} ÏαÏακÏήÏεÏ).', + integer: 'ΠαÏακαλοÏΌε, ειÏάγεÏε ÎΜαΜ ακÎÏαιο αÏÎ¹ÎžÎŒÏ Ïε αÏ
ÏÏ Ïο ÏεΎίο. Îι αÏιΞΌοί Όε ΎεκαΎικά ÏηÏία (Ï.Ï. 1.25) ΎεΜ εÏιÏÏÎÏοΜÏαι.', + numeric: 'ΠαÏακαλοÏΌε, ειÏάγεÏε ÎŒÏΜο αÏιΞΌηÏικÎÏ ÏιΌÎÏ Ïε αÏ
ÏÏ Ïο ÏεΎίο (Ï.Ï." 1 " ή " 1.1 " ή " -1 " ή " -1.1 " ).', + digits: 'ΠαÏακαλοÏΌε, ÏÏηÏιΌοÏοιήÏÏε ÎŒÏΜο αÏιΞΌοÏÏ ÎºÎ±Î¹ ÏηΌεία ÏÏÎ¯ÎŸÎ·Ï Ïε αÏ
ÏÏΜ ÏοΜ ÏοΌÎα (Ï.Ï. εÏιÏÏÎÏεÏαι αÏιΞΌÏÏ ÏηλεÏÏΜοÏ
Όε ÏαÏÎ»ÎµÏ Î® ÏελείεÏ).', + alpha: 'ΠαÏακαλοÏΌε, ÏÏηÏιΌοÏοιήÏÏε ÎŒÏΜο γÏάΌΌαÏα (a-z) Ïε αÏ
ÏÏ Ïο ÏεΎίο. ÎεΜ εÏιÏÏÎÏοΜÏαι κεΜά ή άλλοι ÏαÏακÏήÏεÏ.', + alphanum: 'ΠαÏακαλοÏΌε, ÏÏηÏιΌοÏοιήÏÏε ÎŒÏΜο γÏάΌΌαÏα (a-z) ή αÏιΞΌοÏÏ (0-9) Ïε αÏ
ÏÏΜ ÏοΜ ÏοΌÎα. ÎεΜ εÏιÏÏÎÏοΜÏαι κεΜά ή άλλοι ÏαÏακÏήÏεÏ.', + dateSuchAs: 'ΠαÏακαλοÏΌε, ειÏάγεÏε Όια ÎγκÏ
Ïη ηΌεÏοΌηΜία, ÏÏÏÏ {date}', + dateInFormatMDY: 'ΠαÏÎ±ÎºÎ±Î»Ï ÎµÎ¹ÏάγεÏε Όια ÎγκÏ
Ïη ηΌεÏοΌηΜία, ÏÏÏÏ ÎÎ/ÎÎ/ÎÎÎÎ (Ï.Ï. "12/31/1999").', + email: 'ΠαÏακαλοÏΌε, ειÏάγεÏε Όια ÎγκÏ
Ïη ΎιεÏΞÏ
ΜÏη ηλεκÏÏÎ¿ÎœÎ¹ÎºÎ¿Ï ÏαÏÏ
ÎŽÏοΌείοÏ
(Ï.Ï. "fred@domain.com").', + url: 'ΠαÏακαλοÏΌε, ειÏάγεÏε Όια ÎγκÏ
Ïη URL ΎιεÏΞÏ
ΜÏη, ÏÏÏÏ http://www.example.com', + currencyDollar: 'ΠαÏακαλοÏΌε, ειÏάγεÏε ÎΜα ÎγκÏ
Ïο ÏοÏÏ Ïε ΎολλάÏια (Ï.Ï. $100.00).', + oneRequired: 'ΠαÏακαλοÏΌε, ειÏάγεÏε κάÏι για ÏοÏ
λάÏιÏÏοΜ ÎΜα αÏÏ Î±Ï
Ïά Ïα ÏεΎία.', + errorPrefix: 'ΣÏάλΌα: ', + warningPrefix: 'Î ÏοÏοÏή: ', + + // Form.Validator.Extras + noSpace: 'ÎεΜ εÏιÏÏÎÏοΜÏαι Ïα κεΜά Ïε αÏ
ÏÏ Ïο ÏεΎίο.', + reqChkByNode: 'ÎεΜ ÎÏει εÏιλεγεί κάÏοιο αΜÏικείΌεΜο', + requiredChk: 'ÎÏ
ÏÏ Ïο ÏεΎίο είΜαι αÏαÏαίÏηÏο.', + reqChkByName: 'ΠαÏακαλοÏΌε, εÏιλÎΟÏε Όια εÏικÎÏα {label}.', + match: 'ÎÏ
ÏÏ Ïο ÏεΎίο ÏÏÎÏει Μα ÏαιÏιάζει Όε Ïο ÏεΎίο {matchName}.', + startDate: 'η ηΌεÏοΌηΜία ÎΜαÏΟηÏ', + endDate: 'η ηΌεÏοΌηΜία λήΟηÏ', + currentDate: 'η ÏÏÎÏοÏ
Ïα ηΌεÏοΌηΜία', + afterDate: 'ΠηΌεÏοΌηΜία ÏÏÎÏει Μα είΜαι η ίΎια ή ΌεÏά αÏÏ ÏηΜ {label}.', + beforeDate: 'ΠηΌεÏοΌηΜία ÏÏÎÏει Μα είΜαι η ίΎια ή ÏÏιΜ αÏÏ ÏηΜ {label}.', + startMonth: 'ΠαÏÎ±ÎºÎ±Î»Ï ÎµÏιλÎΟÏε ÎΜα ΌήΜα αÏÏήÏ.', + sameMonth: 'ÎÏ
ÏÎÏ Î¿Î¹ ÎŽÏο ηΌεÏÎ¿ÎŒÎ·ÎœÎ¯ÎµÏ ÏÏÎÏει Μα ÎÏοÏ
Μ ÏοΜ ίΎιο ΌήΜα - Ξα ÏÏÎÏει Μα αλλάΟεÏε ή Ïο ÎΜα ή Ïο άλλο', + creditcard: 'ΠαÏιΞΌÏÏ ÏÎ·Ï ÏιÏÏÏÏÎ¹ÎºÎ®Ï ÎºÎ¬ÏÏÎ±Ï ÎŽÎµÎœ είΜαι ÎγκÏ
ÏοÏ. ΠαÏακαλοÏΌε ελÎγΟÏε ÏοΜ αÏÎ¹ÎžÎŒÏ ÎºÎ±Î¹ ΎοκιΌάÏÏε ΟαΜά. {length} ÎŒÎ®ÎºÎ¿Ï ÏηÏίÏΜ.' + +}); + +/* +--- + +name: Locale.en-GB.Date + +description: Date messages for British English. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Locale + - Locale.en-US.Date + +provides: [Locale.en-GB.Date] + +... +*/ + +Locale.define('en-GB', 'Date', { + + // Culture's date order: DD/MM/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d/%m/%Y', + shortTime: '%H:%M' + +}).inherit('en-US', 'Date'); + +/* +--- + +name: Locale.en-US.Number + +description: Number messages for US English. + +license: MIT-style license + +authors: + - Arian Stolwijk + +requires: + - Locale + +provides: [Locale.en-US.Number] + +... +*/ + +Locale.define('en-US', 'Number', { + + decimal: '.', + group: ',', + +/* Commented properties are the defaults for Number.format + decimals: 0, + precision: 0, + scientific: null, + + prefix: null, + suffic: null, + + // Negative/Currency/percentage will mixin Number + negative: { + prefix: '-' + },*/ + + currency: { +// decimals: 2, + prefix: '$ ' + }/*, + + percentage: { + decimals: 2, + suffix: '%' + }*/ + +}); + + + +/* +--- + +name: Locale.es-ES.Date + +description: Date messages for Spanish. + +license: MIT-style license + +authors: + - Ãlfons Sanchez + +requires: + - Locale + +provides: [Locale.es-ES.Date] + +... +*/ + +Locale.define('es-ES', 'Date', { + + months: ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'], + months_abbr: ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic'], + days: ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado'], + days_abbr: ['dom', 'lun', 'mar', 'mié', 'juv', 'vie', 'sáb'], + + // Culture's date order: DD/MM/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d/%m/%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: 'hace menos de un minuto', + minuteAgo: 'hace un minuto', + minutesAgo: 'hace {delta} minutos', + hourAgo: 'hace una hora', + hoursAgo: 'hace unas {delta} horas', + dayAgo: 'hace un dÃa', + daysAgo: 'hace {delta} dÃas', + weekAgo: 'hace una semana', + weeksAgo: 'hace unas {delta} semanas', + monthAgo: 'hace un mes', + monthsAgo: 'hace {delta} meses', + yearAgo: 'hace un año', + yearsAgo: 'hace {delta} años', + + lessThanMinuteUntil: 'menos de un minuto desde ahora', + minuteUntil: 'un minuto desde ahora', + minutesUntil: '{delta} minutos desde ahora', + hourUntil: 'una hora desde ahora', + hoursUntil: 'unas {delta} horas desde ahora', + dayUntil: 'un dÃa desde ahora', + daysUntil: '{delta} dÃas desde ahora', + weekUntil: 'una semana desde ahora', + weeksUntil: 'unas {delta} semanas desde ahora', + monthUntil: 'un mes desde ahora', + monthsUntil: '{delta} meses desde ahora', + yearUntil: 'un año desde ahora', + yearsUntil: '{delta} años desde ahora' + +}); + +/* +--- + +name: Locale.es-AR.Date + +description: Date messages for Spanish (Argentina). + +license: MIT-style license + +authors: + - Ãlfons Sanchez + - Diego Massanti + +requires: + - Locale + - Locale.es-ES.Date + +provides: [Locale.es-AR.Date] + +... +*/ + +Locale.define('es-AR').inherit('es-ES', 'Date'); + +/* +--- + +name: Locale.es-AR.Form.Validator + +description: Form Validator messages for Spanish (Argentina). + +license: MIT-style license + +authors: + - Diego Massanti + +requires: + - Locale + +provides: [Locale.es-AR.Form.Validator] + +... +*/ + +Locale.define('es-AR', 'FormValidator', { + + required: 'Este campo es obligatorio.', + minLength: 'Por favor ingrese al menos {minLength} caracteres (ha ingresado {length} caracteres).', + maxLength: 'Por favor no ingrese más de {maxLength} caracteres (ha ingresado {length} caracteres).', + integer: 'Por favor ingrese un número entero en este campo. Números con decimales (p.e. 1,25) no se permiten.', + numeric: 'Por favor ingrese solo valores numéricos en este campo (p.e. "1" o "1,1" o "-1" o "-1,1").', + digits: 'Por favor use sólo números y puntuación en este campo (por ejemplo, un número de teléfono con guiones y/o puntos no está permitido).', + alpha: 'Por favor use sólo letras (a-z) en este campo. No se permiten espacios ni otros caracteres.', + alphanum: 'Por favor, usa sólo letras (a-z) o números (0-9) en este campo. No se permiten espacios u otros caracteres.', + dateSuchAs: 'Por favor ingrese una fecha válida como {date}', + dateInFormatMDY: 'Por favor ingrese una fecha válida, utulizando el formato DD/MM/YYYY (p.e. "31/12/1999")', + email: 'Por favor, ingrese una dirección de e-mail válida. Por ejemplo, "fred@dominio.com".', + url: 'Por favor ingrese una URL válida como http://www.example.com.', + currencyDollar: 'Por favor ingrese una cantidad válida de pesos. Por ejemplo $100,00 .', + oneRequired: 'Por favor ingrese algo para por lo menos una de estas entradas.', + errorPrefix: 'Error: ', + warningPrefix: 'Advertencia: ', + + // Form.Validator.Extras + noSpace: 'No se permiten espacios en este campo.', + reqChkByNode: 'No hay elementos seleccionados.', + requiredChk: 'Este campo es obligatorio.', + reqChkByName: 'Por favor selecciona una {label}.', + match: 'Este campo necesita coincidir con el campo {matchName}', + startDate: 'la fecha de inicio', + endDate: 'la fecha de fin', + currentDate: 'la fecha actual', + afterDate: 'La fecha debe ser igual o posterior a {label}.', + beforeDate: 'La fecha debe ser igual o anterior a {label}.', + startMonth: 'Por favor selecciona un mes de origen', + sameMonth: 'Estas dos fechas deben estar en el mismo mes - debes cambiar una u otra.' + +}); + +/* +--- + +name: Locale.es-ES.Form.Validator + +description: Form Validator messages for Spanish. + +license: MIT-style license + +authors: + - Ãlfons Sanchez + +requires: + - Locale + +provides: [Locale.es-ES.Form.Validator] + +... +*/ + +Locale.define('es-ES', 'FormValidator', { + + required: 'Este campo es obligatorio.', + minLength: 'Por favor introduce al menos {minLength} caracteres (has introducido {length} caracteres).', + maxLength: 'Por favor introduce no más de {maxLength} caracteres (has introducido {length} caracteres).', + integer: 'Por favor introduce un número entero en este campo. Números con decimales (p.e. 1,25) no se permiten.', + numeric: 'Por favor introduce solo valores numéricos en este campo (p.e. "1" o "1,1" o "-1" o "-1,1").', + digits: 'Por favor usa solo números y puntuación en este campo (por ejemplo, un número de teléfono con guiones y puntos no esta permitido).', + alpha: 'Por favor usa letras solo (a-z) en este campo. No se admiten espacios ni otros caracteres.', + alphanum: 'Por favor, usa solo letras (a-z) o números (0-9) en este campo. No se admiten espacios ni otros caracteres.', + dateSuchAs: 'Por favor introduce una fecha válida como {date}', + dateInFormatMDY: 'Por favor introduce una fecha válida como DD/MM/YYYY (p.e. "31/12/1999")', + email: 'Por favor, introduce una dirección de email válida. Por ejemplo, "fred@domain.com".', + url: 'Por favor introduce una URL válida como http://www.example.com.', + currencyDollar: 'Por favor introduce una cantidad válida de â¬. Por ejemplo â¬100,00 .', + oneRequired: 'Por favor introduce algo para por lo menos una de estas entradas.', + errorPrefix: 'Error: ', + warningPrefix: 'Aviso: ', + + // Form.Validator.Extras + noSpace: 'No pueden haber espacios en esta entrada.', + reqChkByNode: 'No hay elementos seleccionados.', + requiredChk: 'Este campo es obligatorio.', + reqChkByName: 'Por favor selecciona una {label}.', + match: 'Este campo necesita coincidir con el campo {matchName}', + startDate: 'la fecha de inicio', + endDate: 'la fecha de fin', + currentDate: 'la fecha actual', + afterDate: 'La fecha debe ser igual o posterior a {label}.', + beforeDate: 'La fecha debe ser igual o anterior a {label}.', + startMonth: 'Por favor selecciona un mes de origen', + sameMonth: 'Estas dos fechas deben estar en el mismo mes - debes cambiar una u otra.' + +}); + +/* +--- + +name: Locale.es-VE.Date + +description: Date messages for Spanish (Venezuela). + +license: MIT-style license + +authors: + - Daniel Barreto + +requires: + - Locale + - Locale.es-ES.Date + +provides: [Locale.es-VE.Date] + +... +*/ + +Locale.define('es-VE').inherit('es-ES', 'Date'); + +/* +--- + +name: Locale.es-VE.Form.Validator + +description: Form Validator messages for Spanish (Venezuela). + +license: MIT-style license + +authors: + - Daniel Barreto + +requires: + - Locale + - Locale.es-ES.Form.Validator + +provides: [Locale.es-VE.Form.Validator] + +... +*/ + +Locale.define('es-VE', 'FormValidator', { + + digits: 'Por favor usa solo números y puntuación en este campo. Por ejemplo, un número de teléfono con guiones y puntos no esta permitido.', + alpha: 'Por favor usa solo letras (a-z) en este campo. No se admiten espacios ni otros caracteres.', + currencyDollar: 'Por favor introduce una cantidad válida de Bs. Por ejemplo Bs. 100,00 .', + oneRequired: 'Por favor introduce un valor para por lo menos una de estas entradas.', + + // Form.Validator.Extras + startDate: 'La fecha de inicio', + endDate: 'La fecha de fin', + currentDate: 'La fecha actual' + +}).inherit('es-ES', 'FormValidator'); + +/* +--- + +name: Locale.es-VE.Number + +description: Number messages for Spanish (Venezuela). + +license: MIT-style license + +authors: + - Daniel Barreto + +requires: + - Locale + +provides: [Locale.es-VE.Number] + +... +*/ + +Locale.define('es-VE', 'Number', { + + decimal: ',', + group: '.', +/* + decimals: 0, + precision: 0, +*/ + // Negative/Currency/percentage will mixin Number + negative: { + prefix: '-' + }, + + currency: { + decimals: 2, + prefix: 'Bs. ' + }, + + percentage: { + decimals: 2, + suffix: '%' + } + +}); + +/* +--- + +name: Locale.et-EE.Date + +description: Date messages for Estonian. + +license: MIT-style license + +authors: + - Kevin Valdek + +requires: + - Locale + +provides: [Locale.et-EE.Date] + +... +*/ + +Locale.define('et-EE', 'Date', { + + months: ['jaanuar', 'veebruar', 'mÀrts', 'aprill', 'mai', 'juuni', 'juuli', 'august', 'september', 'oktoober', 'november', 'detsember'], + months_abbr: ['jaan', 'veebr', 'mÀrts', 'apr', 'mai', 'juuni', 'juuli', 'aug', 'sept', 'okt', 'nov', 'dets'], + days: ['pÃŒhapÀev', 'esmaspÀev', 'teisipÀev', 'kolmapÀev', 'neljapÀev', 'reede', 'laupÀev'], + days_abbr: ['pÃŒhap', 'esmasp', 'teisip', 'kolmap', 'neljap', 'reede', 'laup'], + + // Culture's date order: MM.DD.YYYY + dateOrder: ['month', 'date', 'year'], + shortDate: '%m.%d.%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: 'vÀhem kui minut aega tagasi', + minuteAgo: 'umbes minut aega tagasi', + minutesAgo: '{delta} minutit tagasi', + hourAgo: 'umbes tund aega tagasi', + hoursAgo: 'umbes {delta} tundi tagasi', + dayAgo: '1 pÀev tagasi', + daysAgo: '{delta} pÀeva tagasi', + weekAgo: '1 nÀdal tagasi', + weeksAgo: '{delta} nÀdalat tagasi', + monthAgo: '1 kuu tagasi', + monthsAgo: '{delta} kuud tagasi', + yearAgo: '1 aasta tagasi', + yearsAgo: '{delta} aastat tagasi', + + lessThanMinuteUntil: 'vÀhem kui minuti aja pÀrast', + minuteUntil: 'umbes minuti aja pÀrast', + minutesUntil: '{delta} minuti pÀrast', + hourUntil: 'umbes tunni aja pÀrast', + hoursUntil: 'umbes {delta} tunni pÀrast', + dayUntil: '1 pÀeva pÀrast', + daysUntil: '{delta} pÀeva pÀrast', + weekUntil: '1 nÀdala pÀrast', + weeksUntil: '{delta} nÀdala pÀrast', + monthUntil: '1 kuu pÀrast', + monthsUntil: '{delta} kuu pÀrast', + yearUntil: '1 aasta pÀrast', + yearsUntil: '{delta} aasta pÀrast' + +}); + +/* +--- + +name: Locale.et-EE.Form.Validator + +description: Form Validator messages for Estonian. + +license: MIT-style license + +authors: + - Kevin Valdek + +requires: + - Locale + +provides: [Locale.et-EE.Form.Validator] + +... +*/ + +Locale.define('et-EE', 'FormValidator', { + + required: 'VÀli peab olema tÀidetud.', + minLength: 'Palun sisestage vÀhemalt {minLength} tÀhte (te sisestasite {length} tÀhte).', + maxLength: 'Palun Àrge sisestage rohkem kui {maxLength} tÀhte (te sisestasite {length} tÀhte).', + integer: 'Palun sisestage vÀljale tÀisarv. KÃŒmnendarvud (nÀiteks 1.25) ei ole lubatud.', + numeric: 'Palun sisestage ainult numbreid vÀljale (nÀiteks "1", "1.1", "-1" või "-1.1").', + digits: 'Palun kasutage ainult numbreid ja kirjavahemÀrke (telefoninumbri sisestamisel on lubatud kasutada kriipse ja punkte).', + alpha: 'Palun kasutage ainult tÀhti (a-z). TÃŒhikud ja teised sÃŒmbolid on keelatud.', + alphanum: 'Palun kasutage ainult tÀhti (a-z) või numbreid (0-9). TÃŒhikud ja teised sÃŒmbolid on keelatud.', + dateSuchAs: 'Palun sisestage kehtiv kuupÀev kujul {date}', + dateInFormatMDY: 'Palun sisestage kehtiv kuupÀev kujul MM.DD.YYYY (nÀiteks: "12.31.1999").', + email: 'Palun sisestage kehtiv e-maili aadress (nÀiteks: "fred@domain.com").', + url: 'Palun sisestage kehtiv URL (nÀiteks: http://www.example.com).', + currencyDollar: 'Palun sisestage kehtiv $ summa (nÀiteks: $100.00).', + oneRequired: 'Palun sisestage midagi vÀhemalt ÃŒhele antud vÀljadest.', + errorPrefix: 'Viga: ', + warningPrefix: 'Hoiatus: ', + + // Form.Validator.Extras + noSpace: 'VÀli ei tohi sisaldada tÃŒhikuid.', + reqChkByNode: 'Ãkski vÀljadest pole valitud.', + requiredChk: 'VÀlja tÀitmine on vajalik.', + reqChkByName: 'Palun valige ÃŒks {label}.', + match: 'VÀli peab sobima {matchName} vÀljaga', + startDate: 'algkuupÀev', + endDate: 'lõppkuupÀev', + currentDate: 'praegune kuupÀev', + afterDate: 'KuupÀev peab olema võrdne või pÀrast {label}.', + beforeDate: 'KuupÀev peab olema võrdne või enne {label}.', + startMonth: 'Palun valige algkuupÀev.', + sameMonth: 'Antud kaks kuupÀeva peavad olema samas kuus - peate muutma ÃŒhte kuupÀeva.' + +}); + +/* +--- + +name: Locale.fa.Date + +description: Date messages for Persian. + +license: MIT-style license + +authors: + - Amir Hossein Hodjaty Pour + +requires: + - Locale + +provides: [Locale.fa.Date] + +... +*/ + +Locale.define('fa', 'Date', { + + months: ['ÚØ§ÙÙÛÙ', 'ÙÙØ±ÛÙ', 'Ù
ارس', 'آٟرÛÙ', 'Ù
Ù', 'ÚÙØŠÙ', 'ÚÙØŠÛÙ', 'Ø¢Ú¯ÙØ³Øª', 'سٟتاÙ
ؚر', 'اکتؚر', 'ÙÙØ§Ù
ؚر', 'دساÙ
ؚر'], + months_abbr: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'], + days: ['ÛÚ©ØŽÙØšÙ', 'Ø¯ÙØŽÙØšÙ', 'Ø³Ù ØŽÙØšÙ', 'ÚÙØ§Ø±ØŽÙØšÙ', 'ÙŸÙØ¬ØŽÙØšÙ', 'جÙ
عÙ', 'ØŽÙØšÙ'], + days_abbr: ['Ù', 'د', 'س', 'Ú', 'ÙŸ', 'ج', 'ØŽ'], + + // Culture's date order: MM/DD/YYYY + dateOrder: ['month', 'date', 'year'], + shortDate: '%m/%d/%Y', + shortTime: '%I:%M%p', + AM: 'Ù.Øž', + PM: 'Øš.Øž', + + // Date.Extras + ordinal: 'اÙ
', + + lessThanMinuteAgo: 'Ú©Ù
تر از ÛÚ© دÙÛÙÙ ÙŸÛØŽ', + minuteAgo: 'ØØ¯Ùد ÛÚ© دÙÛÙÙ ÙŸÛØŽ', + minutesAgo: '{delta} دÙÛÙÙ ÙŸÛØŽ', + hourAgo: 'ØØ¯Ùد ÛÚ© ساعت ÙŸÛØŽ', + hoursAgo: 'ØØ¯Ùد {delta} ساعت ÙŸÛØŽ', + dayAgo: '1 Ø±ÙØ² ÙŸÛØŽ', + daysAgo: '{delta} Ø±ÙØ² ÙŸÛØŽ', + weekAgo: '1 ÙÙØªÙ ÙŸÛØŽ', + weeksAgo: '{delta} ÙÙØªÙ ÙŸÛØŽ', + monthAgo: '1 Ù
Ø§Ù ÙŸÛØŽ', + monthsAgo: '{delta} Ù
Ø§Ù ÙŸÛØŽ', + yearAgo: '1 Ø³Ø§Ù ÙŸÛØŽ', + yearsAgo: '{delta} Ø³Ø§Ù ÙŸÛØŽ', + + lessThanMinuteUntil: 'Ú©Ù
تر از ÛÚ© دÙÛÙ٠از ØØ§Ùا', + minuteUntil: 'ØØ¯Ùد ÛÚ© دÙÛÙ٠از ØØ§Ùا', + minutesUntil: '{delta} دÙÛÙ٠از ØØ§Ùا', + hourUntil: 'ØØ¯Ùد ÛÚ© ساعت از ØØ§Ùا', + hoursUntil: 'ØØ¯Ùد {delta} ساعت از ØØ§Ùا', + dayUntil: '1 Ø±ÙØ² از ØØ§Ùا', + daysUntil: '{delta} Ø±ÙØ² از ØØ§Ùا', + weekUntil: '1 ÙÙØªÙ از ØØ§Ùا', + weeksUntil: '{delta} ÙÙØªÙ از ØØ§Ùا', + monthUntil: '1 Ù
ا٠از ØØ§Ùا', + monthsUntil: '{delta} Ù
ا٠از ØØ§Ùا', + yearUntil: '1 سا٠از ØØ§Ùا', + yearsUntil: '{delta} سا٠از ØØ§Ùا' + +}); + +/* +--- + +name: Locale.fa.Form.Validator + +description: Form Validator messages for Persian. + +license: MIT-style license + +authors: + - Amir Hossein Hodjaty Pour + +requires: + - Locale + +provides: [Locale.fa.Form.Validator] + +... +*/ + +Locale.define('fa', 'FormValidator', { + + required: 'اÛÙ ÙÛÙØ¯ Ø§ÙØ²Ø§Ù
Û Ø§Ø³Øª.', + minLength: 'ØŽÙ
ا ØšØ§ÛØ¯ ØØ¯Ø§ÙÙ {minLength} ØØ±Ù ÙØ§Ø±Ø¯ Ú©ÙÛØ¯ ({length} ØØ±Ù ÙØ§Ø±Ø¯ Ú©Ø±Ø¯Ù Ø§ÛØ¯).', + maxLength: 'ÙØ·Ùا ØØ¯Ø§Ú©Ø«Ø± {maxLength} ØØ±Ù ÙØ§Ø±Ø¯ Ú©ÙÛØ¯ (ØŽÙ
ا {length} ØØ±Ù ÙØ§Ø±Ø¯ Ú©Ø±Ø¯Ù Ø§ÛØ¯).', + integer: 'ÙØ·Ùا از عدد صØÛØ Ø§Ø³ØªÙØ§Ø¯Ù Ú©ÙÛØ¯. اعداد Ø§Ø¹ØŽØ§Ø±Û (Ù
اÙÙØ¯ 1.25) Ù
جاز ÙÛØ³ØªÙد.', + numeric: 'ÙØ·Ùا ÙÙØ· Ø¯Ø§Ø¯Ù Ø¹Ø¯Ø¯Û ÙØ§Ø±Ø¯ Ú©ÙÛØ¯ (Ù
اÙÙØ¯ "1" ÛØ§ "1.1" ÛØ§ "1-" ÛØ§ "1.1-").', + digits: 'ÙØ·Ùا ÙÙØ· از اعداد Ù Ø¹ÙØ§Ù
ØªÙØ§ در اÛÙ ÙÛÙØ¯ Ø§Ø³ØªÙØ§Ø¯Ù Ú©ÙÛØ¯ (ØšØ±Ø§Û Ù
ثا٠؎Ù
ار٠تÙÙ٠ؚا خط ØªÛØ±Ù Ù ÙÙØ·Ù ÙØ§ØšÙ ÙØšÙ٠است).', + alpha: 'ÙØ·Ùا ÙÙØ· از ØØ±Ù٠اÙÙØšØ§Ø¡ ØšØ±Ø§Û Ø§Û٠ؚخ؎ Ø§Ø³ØªÙØ§Ø¯Ù Ú©ÙÛØ¯. Ú©Ø§Ø±Ø§Ú©ØªØ±ÙØ§Û دÛگر Ù ÙØ§ØµÙÙ Ù
جاز ÙÛØ³ØªÙد.', + alphanum: 'ÙØ·Ùا ÙÙØ· از ØØ±Ù٠اÙÙØšØ§Ø¡ ٠اعداد در اÛ٠ؚخ؎ Ø§Ø³ØªÙØ§Ø¯Ù Ú©ÙÛØ¯. Ú©Ø§Ø±Ø§Ú©ØªØ±ÙØ§Û دÛگر Ù ÙØ§ØµÙÙ Ù
جاز ÙÛØ³ØªÙد.', + dateSuchAs: 'ÙØ·Ùا ÛÚ© ØªØ§Ø±ÛØ® Ù
عتؚر Ù
اÙÙØ¯ {date} ÙØ§Ø±Ø¯ Ú©ÙÛØ¯.', + dateInFormatMDY: 'ÙØ·Ùا ÛÚ© ØªØ§Ø±ÛØ® Ù
عتؚر ØšÙ ØŽÚ©Ù MM/DD/YYYY ÙØ§Ø±Ø¯ Ú©ÙÛØ¯ (Ù
اÙÙØ¯ "12/31/1999").', + email: 'ÙØ·Ùا ÛÚ© آدرس اÛÙ
ÛÙ Ù
عتؚر ÙØ§Ø±Ø¯ Ú©ÙÛØ¯. ØšØ±Ø§Û Ù
ثا٠"fred@domain.com".', + url: 'ÙØ·Ùا ÛÚ© URL Ù
عتؚر Ù
اÙÙØ¯ http://www.example.com ÙØ§Ø±Ø¯ Ú©ÙÛØ¯.', + currencyDollar: 'ÙØ·Ùا ÛÚ© Ù
ØØ¯Ùد٠Ù
عتؚر ØšØ±Ø§Û Ø§Û٠ؚخ؎ ÙØ§Ø±Ø¯ Ú©ÙÛØ¯ Ù
اÙÙØ¯ 100.00$ .', + oneRequired: 'ÙØ·Ùا ØØ¯Ø§ÙÙ ÛÚ©Û Ø§Ø² ÙÛÙØ¯Ùا را ٟر Ú©ÙÛØ¯.', + errorPrefix: 'خطا: ', + warningPrefix: 'ÙØŽØ¯Ø§Ø±: ', + + // Form.Validator.Extras + noSpace: 'Ø§Ø³ØªÙØ§Ø¯Ù از ÙØ§ØµÙ٠در اÛ٠ؚخ؎ Ù
جاز ÙÛØ³Øª.', + reqChkByNode: 'Ù
ÙØ±Ø¯Û Ø§ÙØªØ®Ø§Øš ÙØŽØ¯Ù است.', + requiredChk: 'اÛÙ ÙÛÙØ¯ Ø§ÙØ²Ø§Ù
Û Ø§Ø³Øª.', + reqChkByName: 'ÙØ·Ùا ÛÚ© {label} را Ø§ÙØªØ®Ø§Øš Ú©ÙÛØ¯.', + match: 'اÛÙ ÙÛÙØ¯ ØšØ§ÛØ¯ ؚا ÙÛÙØ¯ {matchName} Ù
Ø·Ø§ØšÙØª دا؎ت٠ؚا؎د.', + startDate: 'ØªØ§Ø±ÛØ® ØŽØ±ÙØ¹', + endDate: 'ØªØ§Ø±ÛØ® ÙŸØ§ÛØ§Ù', + currentDate: 'ØªØ§Ø±ÛØ® Ú©ÙÙÙÛ', + afterDate: 'ØªØ§Ø±ÛØ® Ù
ÛØšØ§Ûست ؚراؚر ÛØ§ ؚعد از {label} ؚا؎د', + beforeDate: 'ØªØ§Ø±ÛØ® Ù
ÛØšØ§Ûست ؚراؚر ÛØ§ ÙØšÙ از {label} ؚا؎د', + startMonth: 'ÙØ·Ùا Ù
Ø§Ù ØŽØ±ÙØ¹ را Ø§ÙØªØ®Ø§Øš Ú©ÙÛØ¯', + sameMonth: 'اÛÙ Ø¯Ù ØªØ§Ø±ÛØ® ØšØ§ÛØ¯ در ÛÚ© Ù
Ø§Ù ØšØ§ØŽÙØ¯ - ØŽÙ
ا ØšØ§ÛØ¯ ÛÚ©Û ÛØ§ ÙØ± د٠را تغÛÛØ± دÙÛØ¯.', + creditcard: 'ØŽÙ
ار٠کارت Ø§Ø¹ØªØšØ§Ø±Û Ú©Ù ÙØ§Ø±Ø¯ Ú©Ø±Ø¯Ù Ø§ÛØ¯ Ù
عتؚر ÙÛØ³Øª. ÙØ·Ùا ØŽÙ
ار٠را ØšØ±Ø±Ø³Û Ú©ÙÛØ¯ Ù Ù
جددا ØªÙØ§ØŽ Ú©ÙÛØ¯. {length} رÙÙ
ÙØ§Ø±Ø¯ ؎د٠است.' + +}); + +/* +--- + +name: Locale.fi-FI.Date + +description: Date messages for Finnish. + +license: MIT-style license + +authors: + - ksel + +requires: + - Locale + +provides: [Locale.fi-FI.Date] + +... +*/ + +Locale.define('fi-FI', 'Date', { + + // NOTE: months and days are not capitalized in finnish + months: ['tammikuu', 'helmikuu', 'maaliskuu', 'huhtikuu', 'toukokuu', 'kesÀkuu', 'heinÀkuu', 'elokuu', 'syyskuu', 'lokakuu', 'marraskuu', 'joulukuu'], + + // these abbreviations are really not much used in finnish because they obviously won't abbreviate very much. ;) + // NOTE: sometimes one can see forms such as "tammi", "helmi", etc. but that is not proper finnish. + months_abbr: ['tammik.', 'helmik.', 'maalisk.', 'huhtik.', 'toukok.', 'kesÀk.', 'heinÀk.', 'elok.', 'syysk.', 'lokak.', 'marrask.', 'jouluk.'], + + days: ['sunnuntai', 'maanantai', 'tiistai', 'keskiviikko', 'torstai', 'perjantai', 'lauantai'], + days_abbr: ['su', 'ma', 'ti', 'ke', 'to', 'pe', 'la'], + + // Culture's date order: DD/MM/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d.%m.%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '.', + + lessThanMinuteAgo: 'vajaa minuutti sitten', + minuteAgo: 'noin minuutti sitten', + minutesAgo: '{delta} minuuttia sitten', + hourAgo: 'noin tunti sitten', + hoursAgo: 'noin {delta} tuntia sitten', + dayAgo: 'pÀivÀ sitten', + daysAgo: '{delta} pÀivÀÀ sitten', + weekAgo: 'viikko sitten', + weeksAgo: '{delta} viikkoa sitten', + monthAgo: 'kuukausi sitten', + monthsAgo: '{delta} kuukautta sitten', + yearAgo: 'vuosi sitten', + yearsAgo: '{delta} vuotta sitten', + + lessThanMinuteUntil: 'vajaan minuutin kuluttua', + minuteUntil: 'noin minuutin kuluttua', + minutesUntil: '{delta} minuutin kuluttua', + hourUntil: 'noin tunnin kuluttua', + hoursUntil: 'noin {delta} tunnin kuluttua', + dayUntil: 'pÀivÀn kuluttua', + daysUntil: '{delta} pÀivÀn kuluttua', + weekUntil: 'viikon kuluttua', + weeksUntil: '{delta} viikon kuluttua', + monthUntil: 'kuukauden kuluttua', + monthsUntil: '{delta} kuukauden kuluttua', + yearUntil: 'vuoden kuluttua', + yearsUntil: '{delta} vuoden kuluttua' + +}); + +/* +--- + +name: Locale.fi-FI.Form.Validator + +description: Form Validator messages for Finnish. + +license: MIT-style license + +authors: + - ksel + +requires: + - Locale + +provides: [Locale.fi-FI.Form.Validator] + +... +*/ + +Locale.define('fi-FI', 'FormValidator', { + + required: 'TÀmÀ kenttÀ on pakollinen.', + minLength: 'Ole hyvÀ ja anna vÀhintÀÀn {minLength} merkkiÀ (annoit {length} merkkiÀ).', + maxLength: 'ÃlÀ anna enempÀÀ kuin {maxLength} merkkiÀ (annoit {length} merkkiÀ).', + integer: 'Ole hyvÀ ja anna kokonaisluku. Luvut, joissa on desimaaleja (esim. 1.25) eivÀt ole sallittuja.', + numeric: 'Anna tÀhÀn kenttÀÀn lukuarvo (kuten "1" tai "1.1" tai "-1" tai "-1.1").', + digits: 'KÀytÀ pelkÀstÀÀn numeroita ja vÀlimerkkejÀ tÀssÀ kentÀssÀ (syötteet, kuten esim. puhelinnumero, jossa on vÀliviivoja, pilkkuja tai pisteitÀ, kelpaa).', + alpha: 'Anna tÀhÀn kenttÀÀn vain kirjaimia (a-z). VÀlilyönnit tai muut merkit eivÀt ole sallittuja.', + alphanum: 'Anna tÀhÀn kenttÀÀn vain kirjaimia (a-z) tai numeroita (0-9). VÀlilyönnit tai muut merkit eivÀt ole sallittuja.', + dateSuchAs: 'Ole hyvÀ ja anna kelvollinen pÀivmÀÀrÀ, kuten esimerkiksi {date}', + dateInFormatMDY: 'Ole hyvÀ ja anna kelvollinen pÀivÀmÀÀrÀ muodossa pp/kk/vvvv (kuten "12/31/1999")', + email: 'Ole hyvÀ ja anna kelvollinen sÀhköpostiosoite (kuten esimerkiksi "matti@meikalainen.com").', + url: 'Ole hyvÀ ja anna kelvollinen URL, kuten esimerkiksi http://www.example.com.', + currencyDollar: 'Ole hyvÀ ja anna kelvollinen eurosumma (kuten esimerkiksi 100,00 EUR) .', + oneRequired: 'Ole hyvÀ ja syötÀ jotakin ainakin johonkin nÀistÀ kentistÀ.', + errorPrefix: 'Virhe: ', + warningPrefix: 'Varoitus: ', + + // Form.Validator.Extras + noSpace: 'TÀssÀ syötteessÀ ei voi olla vÀlilyöntejÀ', + reqChkByNode: 'Ei valintoja.', + requiredChk: 'TÀmÀ kenttÀ on pakollinen.', + reqChkByName: 'Ole hyvÀ ja valitse {label}.', + match: 'TÀmÀn kentÀn tulee vastata kenttÀÀ {matchName}', + startDate: 'alkupÀivÀmÀÀrÀ', + endDate: 'loppupÀivÀmÀÀrÀ', + currentDate: 'nykyinen pÀivÀmÀÀrÀ', + afterDate: 'PÀivÀmÀÀrÀn tulisi olla sama tai myöhÀisempi ajankohta kuin {label}.', + beforeDate: 'PÀivÀmÀÀrÀn tulisi olla sama tai aikaisempi ajankohta kuin {label}.', + startMonth: 'Ole hyvÀ ja valitse aloituskuukausi', + sameMonth: 'NÀiden kahden pÀivÀmÀÀrÀn tulee olla saman kuun sisÀllÀ -- sinun pitÀÀ muuttaa jompaa kumpaa.', + creditcard: 'Annettu luottokortin numero ei kelpaa. Ole hyvÀ ja tarkista numero sekÀ yritÀ uudelleen. {length} numeroa syötetty.' + +}); + +/* +--- + +name: Locale.fi-FI.Number + +description: Finnish number messages + +license: MIT-style license + +authors: + - ksel + +requires: + - Locale + - Locale.EU.Number + +provides: [Locale.fi-FI.Number] + +... +*/ + +Locale.define('fi-FI', 'Number', { + + group: ' ' // grouped by space + +}).inherit('EU', 'Number'); + +/* +--- + +name: Locale.fr-FR.Date + +description: Date messages for French. + +license: MIT-style license + +authors: + - Nicolas Sorosac + - Antoine Abt + +requires: + - Locale + +provides: [Locale.fr-FR.Date] + +... +*/ + +Locale.define('fr-FR', 'Date', { + + months: ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'], + months_abbr: ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sept.', 'oct.', 'nov.', 'déc.'], + days: ['Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi'], + days_abbr: ['dim.', 'lun.', 'mar.', 'mer.', 'jeu.', 'ven.', 'sam.'], + + // Culture's date order: DD/MM/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d/%m/%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: function(dayOfMonth){ + return (dayOfMonth > 1) ? '' : 'er'; + }, + + lessThanMinuteAgo: "il y a moins d'une minute", + minuteAgo: 'il y a une minute', + minutesAgo: 'il y a {delta} minutes', + hourAgo: 'il y a une heure', + hoursAgo: 'il y a {delta} heures', + dayAgo: 'il y a un jour', + daysAgo: 'il y a {delta} jours', + weekAgo: 'il y a une semaine', + weeksAgo: 'il y a {delta} semaines', + monthAgo: 'il y a 1 mois', + monthsAgo: 'il y a {delta} mois', + yearthAgo: 'il y a 1 an', + yearsAgo: 'il y a {delta} ans', + + lessThanMinuteUntil: "dans moins d'une minute", + minuteUntil: 'dans une minute', + minutesUntil: 'dans {delta} minutes', + hourUntil: 'dans une heure', + hoursUntil: 'dans {delta} heures', + dayUntil: 'dans un jour', + daysUntil: 'dans {delta} jours', + weekUntil: 'dans 1 semaine', + weeksUntil: 'dans {delta} semaines', + monthUntil: 'dans 1 mois', + monthsUntil: 'dans {delta} mois', + yearUntil: 'dans 1 an', + yearsUntil: 'dans {delta} ans' + +}); + +/* +--- + +name: Locale.fr-FR.Form.Validator + +description: Form Validator messages for French. + +license: MIT-style license + +authors: + - Miquel Hudin + - Nicolas Sorosac + +requires: + - Locale + +provides: [Locale.fr-FR.Form.Validator] + +... +*/ + +Locale.define('fr-FR', 'FormValidator', { + + required: 'Ce champ est obligatoire.', + length: 'Veuillez saisir {length} caractère(s) (vous avez saisi {elLength} caractère(s)', + minLength: 'Veuillez saisir un minimum de {minLength} caractère(s) (vous avez saisi {length} caractère(s)).', + maxLength: 'Veuillez saisir un maximum de {maxLength} caractère(s) (vous avez saisi {length} caractère(s)).', + integer: 'Veuillez saisir un nombre entier dans ce champ. Les nombres décimaux (ex : "1,25") ne sont pas autorisés.', + numeric: 'Veuillez saisir uniquement des chiffres dans ce champ (ex : "1" ou "1,1" ou "-1" ou "-1,1").', + digits: "Veuillez saisir uniquement des chiffres et des signes de ponctuation dans ce champ (ex : un numéro de téléphone avec des traits d'union est autorisé).", + alpha: 'Veuillez saisir uniquement des lettres (a-z) dans ce champ. Les espaces ou autres caractères ne sont pas autorisés.', + alphanum: 'Veuillez saisir uniquement des lettres (a-z) ou des chiffres (0-9) dans ce champ. Les espaces ou autres caractères ne sont pas autorisés.', + dateSuchAs: 'Veuillez saisir une date correcte comme {date}', + dateInFormatMDY: 'Veuillez saisir une date correcte, au format JJ/MM/AAAA (ex : "31/11/1999").', + email: 'Veuillez saisir une adresse de courrier électronique. Par exemple "fred@domaine.com".', + url: 'Veuillez saisir une URL, comme http://www.exemple.com.', + currencyDollar: 'Veuillez saisir une quantité correcte. Par exemple 100,00€.', + oneRequired: 'Veuillez sélectionner au moins une de ces options.', + errorPrefix: 'Erreur : ', + warningPrefix: 'Attention : ', + + // Form.Validator.Extras + noSpace: "Ce champ n'accepte pas les espaces.", + reqChkByNode: "Aucun élément n'est sélectionné.", + requiredChk: 'Ce champ est obligatoire.', + reqChkByName: 'Veuillez sélectionner un(e) {label}.', + match: 'Ce champ doit correspondre avec le champ {matchName}.', + startDate: 'date de début', + endDate: 'date de fin', + currentDate: 'date actuelle', + afterDate: 'La date doit être identique ou postérieure à {label}.', + beforeDate: 'La date doit être identique ou antérieure à {label}.', + startMonth: 'Veuillez sélectionner un mois de début.', + sameMonth: 'Ces deux dates doivent être dans le même mois - vous devez en modifier une.', + creditcard: 'Le numéro de carte de crédit est invalide. Merci de vérifier le numéro et de réessayer. Vous avez entré {length} chiffre(s).' + +}); + +/* +--- + +name: Locale.fr-FR.Number + +description: Number messages for French. + +license: MIT-style license + +authors: + - Arian Stolwijk + - sv1l + +requires: + - Locale + - Locale.EU.Number + +provides: [Locale.fr-FR.Number] + +... +*/ + +Locale.define('fr-FR', 'Number', { + + group: ' ' // In fr-FR localization, group character is a blank space + +}).inherit('EU', 'Number'); + +/* +--- + +name: Locale.he-IL.Date + +description: Date messages for Hebrew. + +license: MIT-style license + +authors: + - Elad Ossadon + +requires: + - Locale + +provides: [Locale.he-IL.Date] + +... +*/ + +Locale.define('he-IL', 'Date', { + + months: ['×× ××ך', '×€×ך××ך', '×ךץ', '×׀ך××', '×××', '××× ×', '××××', '××××ס×', 'ס׀×××ך', '×××§×××ך', '× ××××ך', '×׊××ך'], + months_abbr: ['×× ××ך', '×€×ך××ך', '×ךץ', '×׀ך××', '×××', '××× ×', '××××', '××××ס×', 'ס׀×××ך', '×××§×××ך', '× ××××ך', '×׊××ך'], + days: ['ך×ש××', '×©× ×', 'ש××ש×', 'ך×××¢×', '×××ש×', 'ש×ש×', 'ש×ת'], + days_abbr: ['ך×ש××', '×©× ×', 'ש××ש×', 'ך×××¢×', '×××ש×', 'ש×ש×', 'ש×ת'], + + // Culture's date order: MM/DD/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d/%m/%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 0, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: '××€× × ×€××ת ×××§×', + minuteAgo: '××€× × ×××§×', + minutesAgo: '××€× × {delta} ××§×ת', + hourAgo: '××€× × ×שע×', + hoursAgo: '××€× × {delta} שע×ת', + dayAgo: '××€× × ×××', + daysAgo: '××€× × {delta} ××××', + weekAgo: '××€× × ×©×××¢', + weeksAgo: '××€× × {delta} ש×××¢×ת', + monthAgo: '××€× × ×××ש', + monthsAgo: '××€× × {delta} ×××ש××', + yearAgo: '××€× × ×©× ×', + yearsAgo: '××€× × {delta} ×©× ××', + + lessThanMinuteUntil: '××¢×× ×€××ת ×××§×', + minuteUntil: '××¢×× ×××§×', + minutesUntil: '××¢×× {delta} ××§×ת', + hourUntil: '××¢×× ×שע×', + hoursUntil: '××¢×× {delta} שע×ת', + dayUntil: '××¢×× ×××', + daysUntil: '××¢×× {delta} ××××', + weekUntil: '××¢×× ×©×××¢', + weeksUntil: '××¢×× {delta} ש×××¢×ת', + monthUntil: '××¢×× ×××ש', + monthsUntil: '××¢×× {delta} ×××ש××', + yearUntil: '××¢×× ×©× ×', + yearsUntil: '××¢×× {delta} ×©× ××' + +}); + +/* +--- + +name: Locale.he-IL.Form.Validator + +description: Form Validator messages for Hebrew. + +license: MIT-style license + +authors: + - Elad Ossadon + +requires: + - Locale + +provides: [Locale.he-IL.Form.Validator] + +... +*/ + +Locale.define('he-IL', 'FormValidator', { + + required: '× × ×××× ×©×× ××.', + minLength: '× × ××××× ××€××ת {minLength} ת×××× (××× ×ª {length} ת××××).', + maxLength: '× × ××××× ×¢× {maxLength} ת×××× (××× ×ª {length} ת××××).', + integer: '× × ××××× ×ס׀ך ש×× ×ש×× ××. ×ס׀ך×× ×¢×©×š×× ××× (××× 1.25) ××× × ×××§×××.', + numeric: '× × ××××× ×¢×š× ××¡×€×š× ×××× ×ש×× ×× (××× "1", "1.1", "-1" ×× "-1.1").', + digits: '× × ××××× ×š×§ ס׀ך×ת ×ס××× × ×׀ך×× ×ש×× ×× (××ש×, ×ס׀ך ×××€×× ×¢× ××§×€×× ×× × ×§×××ת ××× ×××§×).', + alpha: '× × ××××× ×š×§ ××ת××ת ××× ×××ת (a-z) ×ש×× ××. ך××××× ×× ×ª×××× ××ך×× ××× × ×××§×××.', + alphanum: '× × ××××× ×š×§ ××תך××ת ××× ×××ת (a-z) ×× ×¡×€×š×ת (0-9) ×ש×× ××. ××××ך×× ×× ×ª×××× ××ך×× ××× × ×××§×××.', + dateSuchAs: '× × ××××× ×ª×ך×× ×××§×, ××× {date}', + dateInFormatMDY: '× × ××××× ×ª×ך×× ×××§× ××€×ך×× MM/DD/YYYY (××× "12/31/1999")', + email: '× × ××××× ×ת××ת ×××××× ×××§×ת. ××××××: "fred@domain.com".', + url: '× × ××××× ×ת××ת ×תך ×××§×ת, ××× http://www.example.com.', + currencyDollar: '× × ××××× ×¡××× ××××š× ×××§×. ×××××× $100.00.', + oneRequired: '× × ××××ך ××€××ת ×ש×× ×××.', + errorPrefix: 'ש××××: ', + warningPrefix: '×××ך×: ', + + // Form.Validator.Extras + noSpace: '××× ××××× ×š××××× ×ש×× ××.', + reqChkByNode: '× × ××××ך ××ת ×××׀שך×××ת.', + requiredChk: 'ש×× ×× × ×ךש.', + reqChkByName: '× × ××××ך {label}.', + match: 'ש×× ×× ×Š×š×× ××ת××× ×ש×× {matchName}', + startDate: 'ת×ך×× ××ת×××', + endDate: 'ת×ך×× ×ס×××', + currentDate: '×ת×ך×× ×× ××××', + afterDate: '×ת×ך×× ×Š×š×× ××××ת ××× ×× ×××š× {label}.', + beforeDate: '×ת×ך×× ×Š×š×× ××××ת ××× ×× ××€× × {label}.', + startMonth: '× × ××××ך ×××ש ×ת×××', + sameMonth: '×©× × ×ª×ך×××× ××× ×Š×š×××× ××××ת ××××ª× ×××ש - × × ××©× ×ת ××× ×ת×ך××××.', + creditcard: '×ס׀ך ×ך××ס ××שך×× ×©×××× ××× × ×××§×. × × ×××××§ ×©× ×ת. ×××× × {length} ס׀ך×ת.' + +}); + +/* +--- + +name: Locale.he-IL.Number + +description: Number messages for Hebrew. + +license: MIT-style license + +authors: + - Elad Ossadon + +requires: + - Locale + +provides: [Locale.he-IL.Number] + +... +*/ + +Locale.define('he-IL', 'Number', { + + decimal: '.', + group: ',', + + currency: { + suffix: ' âª' + } + +}); + +/* +--- + +name: Locale.hu-HU.Date + +description: Date messages for Hungarian. + +license: MIT-style license + +authors: + - Zsolt SzegheÅ + +requires: + - Locale + +provides: [Locale.hu-HU.Date] + +... +*/ + +Locale.define('hu-HU', 'Date', { + + months: ['Január', 'Február', 'Március', 'Ãprilis', 'Május', 'Június', 'Július', 'Augusztus', 'Szeptember', 'Október', 'November', 'December'], + months_abbr: ['jan.', 'febr.', 'márc.', 'ápr.', 'máj.', 'jún.', 'júl.', 'aug.', 'szept.', 'okt.', 'nov.', 'dec.'], + days: ['Vasárnap', 'HétfÅ', 'Kedd', 'Szerda', 'CsÃŒtörtök', 'Péntek', 'Szombat'], + days_abbr: ['V', 'H', 'K', 'Sze', 'Cs', 'P', 'Szo'], + + // Culture's date order: YYYY.MM.DD. + dateOrder: ['year', 'month', 'date'], + shortDate: '%Y.%m.%d.', + shortTime: '%I:%M', + AM: 'de.', + PM: 'du.', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '.', + + lessThanMinuteAgo: 'alig egy perce', + minuteAgo: 'egy perce', + minutesAgo: '{delta} perce', + hourAgo: 'egy órája', + hoursAgo: '{delta} órája', + dayAgo: '1 napja', + daysAgo: '{delta} napja', + weekAgo: '1 hete', + weeksAgo: '{delta} hete', + monthAgo: '1 hónapja', + monthsAgo: '{delta} hónapja', + yearAgo: '1 éve', + yearsAgo: '{delta} éve', + + lessThanMinuteUntil: 'alig egy perc múlva', + minuteUntil: 'egy perc múlva', + minutesUntil: '{delta} perc múlva', + hourUntil: 'egy óra múlva', + hoursUntil: '{delta} óra múlva', + dayUntil: '1 nap múlva', + daysUntil: '{delta} nap múlva', + weekUntil: '1 hét múlva', + weeksUntil: '{delta} hét múlva', + monthUntil: '1 hónap múlva', + monthsUntil: '{delta} hónap múlva', + yearUntil: '1 év múlva', + yearsUntil: '{delta} év múlva' + +}); + +/* +--- + +name: Locale.hu-HU.Form.Validator + +description: Form Validator messages for Hungarian. + +license: MIT-style license + +authors: + - Zsolt SzegheÅ + +requires: + - Locale + +provides: [Locale.hu-HU.Form.Validator] + +... +*/ + +Locale.define('hu-HU', 'FormValidator', { + + required: 'A mezÅ kitöltése kötelezÅ.', + minLength: 'Legalább {minLength} karakter megadása szÃŒkséges (megadva {length} karakter).', + maxLength: 'Legfeljebb {maxLength} karakter megadása lehetséges (megadva {length} karakter).', + integer: 'Egész szám megadása szÃŒkséges. A tizedesjegyek (pl. 1.25) nem engedélyezettek.', + numeric: 'Szám megadása szÃŒkséges (pl. "1" vagy "1.1" vagy "-1" vagy "-1.1").', + digits: 'Csak számok és Ãrásjelek megadása lehetséges (pl. telefonszám kötÅjelek és/vagy perjelekkel).', + alpha: 'Csak betűk (a-z) megadása lehetséges. Szóköz és egyéb karakterek nem engedélyezettek.', + alphanum: 'Csak betűk (a-z) vagy számok (0-9) megadása lehetséges. Szóköz és egyéb karakterek nem engedélyezettek.', + dateSuchAs: 'Valós dátum megadása szÃŒkséges (pl. {date}).', + dateInFormatMDY: 'Valós dátum megadása szÃŒkséges ÃÃÃÃ.HH.NN. formában. (pl. "1999.12.31.")', + email: 'Valós e-mail cÃm megadása szÃŒkséges (pl. "fred@domain.hu").', + url: 'Valós URL megadása szÃŒkséges (pl. http://www.example.com).', + currencyDollar: 'Valós pénzösszeg megadása szÃŒkséges (pl. 100.00 Ft.).', + oneRequired: 'Az alábbi mezÅk legalább egyikének kitöltése kötelezÅ.', + errorPrefix: 'Hiba: ', + warningPrefix: 'Figyelem: ', + + // Form.Validator.Extras + noSpace: 'A mezÅ nem tartalmazhat szóközöket.', + reqChkByNode: 'Nincs egyetlen kijelölt elem sem.', + requiredChk: 'A mezÅ kitöltése kötelezÅ.', + reqChkByName: 'Egy {label} kiválasztása szÃŒkséges.', + match: 'A mezÅnek egyeznie kell a(z) {matchName} mezÅvel.', + startDate: 'a kezdet dátuma', + endDate: 'a vég dátuma', + currentDate: 'jelenlegi dátum', + afterDate: 'A dátum nem lehet kisebb, mint {label}.', + beforeDate: 'A dátum nem lehet nagyobb, mint {label}.', + startMonth: 'Kezdeti hónap megadása szÃŒkséges.', + sameMonth: 'A két dátumnak ugyanazon hónapban kell lennie.', + creditcard: 'A megadott bankkártyaszám nem valódi (megadva {length} számjegy).' + +}); + +/* +--- + +name: Locale.it-IT.Date + +description: Date messages for Italian. + +license: MIT-style license. + +authors: + - Andrea Novero + - Valerio Proietti + +requires: + - Locale + +provides: [Locale.it-IT.Date] + +... +*/ + +Locale.define('it-IT', 'Date', { + + months: ['Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre'], + months_abbr: ['gen', 'feb', 'mar', 'apr', 'mag', 'giu', 'lug', 'ago', 'set', 'ott', 'nov', 'dic'], + days: ['Domenica', 'Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato'], + days_abbr: ['dom', 'lun', 'mar', 'mer', 'gio', 'ven', 'sab'], + + // Culture's date order: DD/MM/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d/%m/%Y', + shortTime: '%H.%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: 'º', + + lessThanMinuteAgo: 'meno di un minuto fa', + minuteAgo: 'circa un minuto fa', + minutesAgo: 'circa {delta} minuti fa', + hourAgo: "circa un'ora fa", + hoursAgo: 'circa {delta} ore fa', + dayAgo: 'circa 1 giorno fa', + daysAgo: 'circa {delta} giorni fa', + weekAgo: 'una settimana fa', + weeksAgo: '{delta} settimane fa', + monthAgo: 'un mese fa', + monthsAgo: '{delta} mesi fa', + yearAgo: 'un anno fa', + yearsAgo: '{delta} anni fa', + + lessThanMinuteUntil: 'tra meno di un minuto', + minuteUntil: 'tra circa un minuto', + minutesUntil: 'tra circa {delta} minuti', + hourUntil: "tra circa un'ora", + hoursUntil: 'tra circa {delta} ore', + dayUntil: 'tra circa un giorno', + daysUntil: 'tra circa {delta} giorni', + weekUntil: 'tra una settimana', + weeksUntil: 'tra {delta} settimane', + monthUntil: 'tra un mese', + monthsUntil: 'tra {delta} mesi', + yearUntil: 'tra un anno', + yearsUntil: 'tra {delta} anni' + +}); + +/* +--- + +name: Locale.it-IT.Form.Validator + +description: Form Validator messages for Italian. + +license: MIT-style license + +authors: + - Leonardo Laureti + - Andrea Novero + +requires: + - Locale + +provides: [Locale.it-IT.Form.Validator] + +... +*/ + +Locale.define('it-IT', 'FormValidator', { + + required: 'Il campo è obbligatorio.', + minLength: 'Inserire almeno {minLength} caratteri (ne sono stati inseriti {length}).', + maxLength: 'Inserire al massimo {maxLength} caratteri (ne sono stati inseriti {length}).', + integer: 'Inserire un numero intero. Non sono consentiti decimali (es.: 1.25).', + numeric: 'Inserire solo valori numerici (es.: "1" oppure "1.1" oppure "-1" oppure "-1.1").', + digits: 'Inserire solo numeri e caratteri di punteggiatura. Per esempio è consentito un numero telefonico con trattini o punti.', + alpha: 'Inserire solo lettere (a-z). Non sono consentiti spazi o altri caratteri.', + alphanum: 'Inserire solo lettere (a-z) o numeri (0-9). Non sono consentiti spazi o altri caratteri.', + dateSuchAs: 'Inserire una data valida del tipo {date}', + dateInFormatMDY: 'Inserire una data valida nel formato MM/GG/AAAA (es.: "12/31/1999")', + email: 'Inserire un indirizzo email valido. Per esempio "nome@dominio.com".', + url: 'Inserire un indirizzo valido. Per esempio "http://www.example.com".', + currencyDollar: 'Inserire un importo valido. Per esempio "$100.00".', + oneRequired: 'Completare almeno uno dei campi richiesti.', + errorPrefix: 'Errore: ', + warningPrefix: 'Attenzione: ', + + // Form.Validator.Extras + noSpace: 'Non sono consentiti spazi.', + reqChkByNode: 'Nessuna voce selezionata.', + requiredChk: 'Il campo è obbligatorio.', + reqChkByName: 'Selezionare un(a) {label}.', + match: 'Il valore deve corrispondere al campo {matchName}', + startDate: "data d'inizio", + endDate: 'data di fine', + currentDate: 'data attuale', + afterDate: 'La data deve corrispondere o essere successiva al {label}.', + beforeDate: 'La data deve corrispondere o essere precedente al {label}.', + startMonth: "Selezionare un mese d'inizio", + sameMonth: 'Le due date devono essere dello stesso mese - occorre modificarne una.' + +}); + +/* +--- + +name: Locale.ja-JP.Date + +description: Date messages for Japanese. + +license: MIT-style license + +authors: + - Noritaka Horio + +requires: + - Locale + +provides: [Locale.ja-JP.Date] + +... +*/ + +Locale.define('ja-JP', 'Date', { + + months: ['1æ', '2æ', '3æ', '4æ', '5æ', '6æ', '7æ', '8æ', '9æ', '10æ', '11æ', '12æ'], + months_abbr: ['1æ', '2æ', '3æ', '4æ', '5æ', '6æ', '7æ', '8æ', '9æ', '10æ', '11æ', '12æ'], + days: ['æ¥ææ¥', 'æææ¥', 'ç«ææ¥', 'æ°Žææ¥', 'æšææ¥', 'éææ¥', 'åææ¥'], + days_abbr: ['æ¥', 'æ', 'ç«', 'æ°Ž', 'æš', 'é', 'å'], + + // Culture's date order: YYYY/MM/DD + dateOrder: ['year', 'month', 'date'], + shortDate: '%Y/%m/%d', + shortTime: '%H:%M', + AM: 'åå', + PM: 'ååŸ', + firstDayOfWeek: 0, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: '1å以å
å', + minuteAgo: 'çŽ1åå', + minutesAgo: 'çŽ{delta}åå', + hourAgo: 'çŽ1æéå', + hoursAgo: 'çŽ{delta}æéå', + dayAgo: '1æ¥å', + daysAgo: '{delta}æ¥å', + weekAgo: '1é±éå', + weeksAgo: '{delta}é±éå', + monthAgo: '1ã¶æå', + monthsAgo: '{delta}ã¶æå', + yearAgo: '1幎å', + yearsAgo: '{delta}幎å', + + lessThanMinuteUntil: 'ä»ããçŽ1å以å
', + minuteUntil: 'ä»ããçŽ1å', + minutesUntil: 'ä»ããçŽ{delta}å', + hourUntil: 'ä»ããçŽ1æé', + hoursUntil: 'ä»ããçŽ{delta}æé', + dayUntil: 'ä»ãã1æ¥é', + daysUntil: 'ä»ãã{delta}æ¥é', + weekUntil: 'ä»ãã1é±é', + weeksUntil: 'ä»ãã{delta}é±é', + monthUntil: 'ä»ãã1ã¶æ', + monthsUntil: 'ä»ãã{delta}ã¶æ', + yearUntil: 'ä»ãã1幎', + yearsUntil: 'ä»ãã{delta}幎' + +}); + +/* +--- + +name: Locale.ja-JP.Form.Validator + +description: Form Validator messages for Japanese. + +license: MIT-style license + +authors: + - Noritaka Horio + +requires: + - Locale + +provides: [Locale.ja-JP.Form.Validator] + +... +*/ + +Locale.define("ja-JP", "FormValidator", { + + required: 'å
¥åã¯å¿
é ã§ãã', + minLength: 'å
¥åæåæ°ã¯{minLength}以äžã«ããŠãã ããã({length}æå)', + maxLength: 'å
¥åæåæ°ã¯{maxLength}以äžã«ããŠãã ããã({length}æå)', + integer: 'æŽæ°ãå
¥åããŠãã ããã', + numeric: 'å
¥åã§ããã®ã¯æ°å€ã ãã§ãã(äŸ: "1", "1.1", "-1", "-1.1"....)', + digits: 'å
¥åã§ããã®ã¯æ°å€ãšå¥èªèšå·ã§ãã (äŸ: -ã+ãå«ãé»è©±çªå·ãªã©).', + alpha: 'å
¥åã§ããã®ã¯åè§è±åã ãã§ãããã以å€ã®æåã¯å
¥åã§ããŸããã', + alphanum: 'å
¥åã§ããã®ã¯åè§è±æ°åã ãã§ãããã以å€ã®æåã¯å
¥åã§ããŸããã', + dateSuchAs: 'æå¹ãªæ¥ä»ãå
¥åããŠãã ããã{date}', + dateInFormatMDY: 'æ¥ä»ã®æžåŒã«èª€ãããããŸããYYYY/MM/DD (i.e. "1999/12/31")', + email: 'ã¡ãŒã«ã¢ãã¬ã¹ã«èª€ãããããŸãã', + url: 'URLã¢ãã¬ã¹ã«èª€ãããããŸãã', + currencyDollar: 'éé¡ã«èª€ãããããŸãã', + oneRequired: 'ã²ãšã€ä»¥äžå
¥åããŠãã ããã', + errorPrefix: 'ãšã©ãŒ: ', + warningPrefix: 'èŠå: ', + + // FormValidator.Extras + noSpace: 'ã¹ããŒã¹ã¯å
¥åã§ããŸããã', + reqChkByNode: 'éžæãããŠããŸããã', + requiredChk: 'ãã®é
ç®ã¯å¿
é ã§ãã', + reqChkByName: '{label}ãéžæããŠãã ããã', + match: '{matchName}ãå
¥åãããŠããå Žåå¿
é ã§ãã', + startDate: 'éå§æ¥', + endDate: 'çµäºæ¥', + currentDate: '仿¥', + afterDate: '{label}以éã®æ¥ä»ã«ããŠãã ããã', + beforeDate: '{label}以åã®æ¥ä»ã«ããŠãã ããã', + startMonth: 'éå§æãéžæããŠãã ããã', + sameMonth: 'æ¥ä»ãåäžã§ããã©ã¡ããã倿ŽããŠãã ããã' + +}); + +/* +--- + +name: Locale.ja-JP.Number + +description: Number messages for Japanese. + +license: MIT-style license + +authors: + - Noritaka Horio + +requires: + - Locale + +provides: [Locale.ja-JP.Number] + +... +*/ + +Locale.define('ja-JP', 'Number', { + + decimal: '.', + group: ',', + + currency: { + decimals: 0, + prefix: '\\' + } + +}); + +/* +--- + +name: Locale.nl-NL.Date + +description: Date messages for Dutch. + +license: MIT-style license + +authors: + - Lennart Pilon + - Tim Wienk + +requires: + - Locale + +provides: [Locale.nl-NL.Date] + +... +*/ + +Locale.define('nl-NL', 'Date', { + + months: ['januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli', 'augustus', 'september', 'oktober', 'november', 'december'], + months_abbr: ['jan', 'feb', 'mrt', 'apr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'], + days: ['zondag', 'maandag', 'dinsdag', 'woensdag', 'donderdag', 'vrijdag', 'zaterdag'], + days_abbr: ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'], + + // Culture's date order: DD-MM-YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d-%m-%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: 'e', + + lessThanMinuteAgo: 'minder dan een minuut geleden', + minuteAgo: 'ongeveer een minuut geleden', + minutesAgo: '{delta} minuten geleden', + hourAgo: 'ongeveer een uur geleden', + hoursAgo: 'ongeveer {delta} uur geleden', + dayAgo: 'een dag geleden', + daysAgo: '{delta} dagen geleden', + weekAgo: 'een week geleden', + weeksAgo: '{delta} weken geleden', + monthAgo: 'een maand geleden', + monthsAgo: '{delta} maanden geleden', + yearAgo: 'een jaar geleden', + yearsAgo: '{delta} jaar geleden', + + lessThanMinuteUntil: 'over minder dan een minuut', + minuteUntil: 'over ongeveer een minuut', + minutesUntil: 'over {delta} minuten', + hourUntil: 'over ongeveer een uur', + hoursUntil: 'over {delta} uur', + dayUntil: 'over ongeveer een dag', + daysUntil: 'over {delta} dagen', + weekUntil: 'over een week', + weeksUntil: 'over {delta} weken', + monthUntil: 'over een maand', + monthsUntil: 'over {delta} maanden', + yearUntil: 'over een jaar', + yearsUntil: 'over {delta} jaar' + +}); + +/* +--- + +name: Locale.nl-NL.Form.Validator + +description: Form Validator messages for Dutch. + +license: MIT-style license + +authors: + - Lennart Pilon + - Arian Stolwijk + - Tim Wienk + +requires: + - Locale + +provides: [Locale.nl-NL.Form.Validator] + +... +*/ + +Locale.define('nl-NL', 'FormValidator', { + + required: 'Dit veld is verplicht.', + length: 'Vul precies {length} karakters in (je hebt {elLength} karakters ingevoerd).', + minLength: 'Vul minimaal {minLength} karakters in (je hebt {length} karakters ingevoerd).', + maxLength: 'Vul niet meer dan {maxLength} karakters in (je hebt {length} karakters ingevoerd).', + integer: 'Vul een getal in. Getallen met decimalen (bijvoorbeeld 1.25) zijn niet toegestaan.', + numeric: 'Vul alleen numerieke waarden in (bijvoorbeeld "1" of "1.1" of "-1" of "-1.1").', + digits: 'Vul alleen nummers en leestekens in (bijvoorbeeld een telefoonnummer met streepjes is toegestaan).', + alpha: 'Vul alleen letters in (a-z). Spaties en andere karakters zijn niet toegestaan.', + alphanum: 'Vul alleen letters (a-z) of nummers (0-9) in. Spaties en andere karakters zijn niet toegestaan.', + dateSuchAs: 'Vul een geldige datum in, zoals {date}', + dateInFormatMDY: 'Vul een geldige datum, in het formaat MM/DD/YYYY (bijvoorbeeld "12/31/1999")', + email: 'Vul een geldig e-mailadres in. Bijvoorbeeld "fred@domein.nl".', + url: 'Vul een geldige URL in, zoals http://www.example.com.', + currencyDollar: 'Vul een geldig $ bedrag in. Bijvoorbeeld $100.00 .', + oneRequired: 'Vul iets in bij in ieder geval een van deze velden.', + warningPrefix: 'Waarschuwing: ', + errorPrefix: 'Fout: ', + + // Form.Validator.Extras + noSpace: 'Spaties zijn niet toegestaan in dit veld.', + reqChkByNode: 'Er zijn geen items geselecteerd.', + requiredChk: 'Dit veld is verplicht.', + reqChkByName: 'Selecteer een {label}.', + match: 'Dit veld moet overeen komen met het {matchName} veld', + startDate: 'de begin datum', + endDate: 'de eind datum', + currentDate: 'de huidige datum', + afterDate: 'De datum moet hetzelfde of na {label} zijn.', + beforeDate: 'De datum moet hetzelfde of voor {label} zijn.', + startMonth: 'Selecteer een begin maand', + sameMonth: 'Deze twee data moeten in dezelfde maand zijn - u moet een van beide aanpassen.', + creditcard: 'Het ingevulde creditcardnummer is niet geldig. Controleer het nummer en probeer opnieuw. {length} getallen ingevuld.' + +}); + +/* +--- + +name: Locale.nl-NL.Number + +description: Number messages for Dutch. + +license: MIT-style license + +authors: + - Arian Stolwijk + +requires: + - Locale + - Locale.EU.Number + +provides: [Locale.nl-NL.Number] + +... +*/ + +Locale.define('nl-NL').inherit('EU', 'Number'); + + + + +/* +--- + +name: Locale.no-NO.Date + +description: Date messages for Norwegian. + +license: MIT-style license + +authors: + - Espen 'Rexxars' Hovlandsdal + - Ole TÞsse Kolvik +requires: + - Locale + +provides: [Locale.no-NO.Date] + +... +*/ + +Locale.define('no-NO', 'Date', { + months: ['Januar', 'Februar', 'Mars', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Desember'], + months_abbr: ['Jan', 'Feb', 'Mar', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'], + days: ['SÞndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'LÞrdag'], + days_abbr: ['SÞn', 'Man', 'Tir', 'Ons', 'Tor', 'Fre', 'LÞr'], + + // Culture's date order: DD.MM.YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d.%m.%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + lessThanMinuteAgo: 'mindre enn et minutt siden', + minuteAgo: 'omtrent et minutt siden', + minutesAgo: '{delta} minutter siden', + hourAgo: 'omtrent en time siden', + hoursAgo: 'omtrent {delta} timer siden', + dayAgo: '{delta} dag siden', + daysAgo: '{delta} dager siden', + weekAgo: 'en uke siden', + weeksAgo: '{delta} uker siden', + monthAgo: 'en mÃ¥ned siden', + monthsAgo: '{delta} mÃ¥neder siden', + yearAgo: 'ett Ã¥r siden', + yearsAgo: '{delta} Ã¥r siden', + + lessThanMinuteUntil: 'mindre enn et minutt til', + minuteUntil: 'omtrent et minutt til', + minutesUntil: '{delta} minutter til', + hourUntil: 'omtrent en time til', + hoursUntil: 'omtrent {delta} timer til', + dayUntil: 'en dag til', + daysUntil: '{delta} dager til', + weekUntil: 'en uke til', + weeksUntil: '{delta} uker til', + monthUntil: 'en mÃ¥ned til', + monthsUntil: '{delta} mÃ¥neder til', + yearUntil: 'et Ã¥r til', + yearsUntil: '{delta} Ã¥r til' +}); + +/* +--- + +name: Locale.no-NO.Form.Validator + +description: Form Validator messages for Norwegian. + +license: MIT-style license + +authors: + - Espen 'Rexxars' Hovlandsdal + +requires: + - Locale + +provides: [Locale.no-NO.Form.Validator] + +... +*/ + +Locale.define('no-NO', 'FormValidator', { + + required: 'Dette feltet er pÃÂ¥krevd.', + minLength: 'Vennligst skriv inn minst {minLength} tegn (du skrev {length} tegn).', + maxLength: 'Vennligst skriv inn maksimalt {maxLength} tegn (du skrev {length} tegn).', + integer: 'Vennligst skriv inn et tall i dette feltet. Tall med desimaler (for eksempel 1,25) er ikke tillat.', + numeric: 'Vennligst skriv inn kun numeriske verdier i dette feltet (for eksempel "1", "1.1", "-1" eller "-1.1").', + digits: 'Vennligst bruk kun nummer og skilletegn i dette feltet.', + alpha: 'Vennligst bruk kun bokstaver (a-z) i dette feltet. Ingen mellomrom eller andre tegn er tillat.', + alphanum: 'Vennligst bruk kun bokstaver (a-z) eller nummer (0-9) i dette feltet. Ingen mellomrom eller andre tegn er tillat.', + dateSuchAs: 'Vennligst skriv inn en gyldig dato, som {date}', + dateInFormatMDY: 'Vennligst skriv inn en gyldig dato, i formatet MM/DD/YYYY (for eksempel "12/31/1999")', + email: 'Vennligst skriv inn en gyldig epost-adresse. For eksempel "espen@domene.no".', + url: 'Vennligst skriv inn en gyldig URL, for eksempel http://www.example.com.', + currencyDollar: 'Vennligst fyll ut et gyldig $ belÞp. For eksempel $100.00 .', + oneRequired: 'Vennligst fyll ut noe i minst ett av disse feltene.', + errorPrefix: 'Feil: ', + warningPrefix: 'Advarsel: ' + +}); + +/* +--- + +name: Locale.pl-PL.Date + +description: Date messages for Polish. + +license: MIT-style license + +authors: + - Oskar Krawczyk + +requires: + - Locale + +provides: [Locale.pl-PL.Date] + +... +*/ + +Locale.define('pl-PL', 'Date', { + + months: ['StyczeÅ', 'Luty', 'Marzec', 'KwiecieÅ', 'Maj', 'Czerwiec', 'Lipiec', 'SierpieÅ', 'WrzesieÅ', 'Październik', 'Listopad', 'GrudzieÅ'], + months_abbr: ['sty', 'lut', 'mar', 'kwi', 'maj', 'cze', 'lip', 'sie', 'wrz', 'paź', 'lis', 'gru'], + days: ['Niedziela', 'PoniedziaÅek', 'Wtorek', 'Åroda', 'Czwartek', 'PiÄ
tek', 'Sobota'], + days_abbr: ['niedz.', 'pon.', 'wt.', 'År.', 'czw.', 'pt.', 'sob.'], + + // Culture's date order: YYYY-MM-DD + dateOrder: ['year', 'month', 'date'], + shortDate: '%Y-%m-%d', + shortTime: '%H:%M', + AM: 'nad ranem', + PM: 'po poÅudniu', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: function(dayOfMonth){ + return (dayOfMonth > 3 && dayOfMonth < 21) ? 'ty' : ['ty', 'szy', 'gi', 'ci', 'ty'][Math.min(dayOfMonth % 10, 4)]; + }, + + lessThanMinuteAgo: 'mniej niÅŒ minute temu', + minuteAgo: 'okoÅo minutÄ temu', + minutesAgo: '{delta} minut temu', + hourAgo: 'okoÅo godzinÄ temu', + hoursAgo: 'okoÅo {delta} godzin temu', + dayAgo: 'Wczoraj', + daysAgo: '{delta} dni temu', + + lessThanMinuteUntil: 'za niecaÅÄ
minutÄ', + minuteUntil: 'za okoÅo minutÄ', + minutesUntil: 'za {delta} minut', + hourUntil: 'za okoÅo godzinÄ', + hoursUntil: 'za okoÅo {delta} godzin', + dayUntil: 'za 1 dzieÅ', + daysUntil: 'za {delta} dni' + +}); + +/* +--- + +name: Locale.pl-PL.Form.Validator + +description: Form Validator messages for Polish. + +license: MIT-style license + +authors: + - Oskar Krawczyk + +requires: + - Locale + +provides: [Locale.pl-PL.Form.Validator] + +... +*/ + +Locale.define('pl-PL', 'FormValidator', { + + required: 'To pole jest wymagane.', + minLength: 'Wymagane jest przynajmniej {minLength} znaków (wpisanych zostaÅo tylko {length}).', + maxLength: 'Dozwolone jest nie wiÄcej niÅŒ {maxLength} znaków (wpisanych zostaÅo {length})', + integer: 'To pole wymaga liczb caÅych. Liczby dziesiÄtne (np. 1.25) sÄ
niedozwolone.', + numeric: 'Prosimy uÅŒywaÄ tylko numerycznych wartoÅci w tym polu (np. "1", "1.1", "-1" lub "-1.1").', + digits: 'Prosimy uÅŒywaÄ liczb oraz zankow punktuacyjnych w typ polu (dla przykÅadu, przy numerze telefonu myÅlniki i kropki sÄ
dozwolone).', + alpha: 'Prosimy uÅŒywaÄ tylko liter (a-z) w tym polu. Spacje oraz inne znaki sÄ
niedozwolone.', + alphanum: 'Prosimy uÅŒywaÄ tylko liter (a-z) lub liczb (0-9) w tym polu. Spacje oraz inne znaki sÄ
niedozwolone.', + dateSuchAs: 'Prosimy podaÄ prawidÅowÄ
datÄ w formacie: {date}', + dateInFormatMDY: 'Prosimy podaÄ poprawnÄ
date w formacie DD.MM.RRRR (i.e. "12.01.2009")', + email: 'Prosimy podaÄ prawidÅowy adres e-mail, np. "jan@domena.pl".', + url: 'Prosimy podaÄ prawidÅowy adres URL, np. http://www.example.com.', + currencyDollar: 'Prosimy podaÄ prawidÅowÄ
sumÄ w PLN. Dla przykÅadu: 100.00 PLN.', + oneRequired: 'Prosimy wypeÅniÄ chociaÅŒ jedno z pól.', + errorPrefix: 'BÅÄ
d: ', + warningPrefix: 'Uwaga: ', + + // Form.Validator.Extras + noSpace: 'W tym polu nie mogÄ
znajdowaÄ siÄ spacje.', + reqChkByNode: 'Brak zaznaczonych elementów.', + requiredChk: 'To pole jest wymagane.', + reqChkByName: 'Prosimy wybraÄ z {label}.', + match: 'To pole musi byÄ takie samo jak {matchName}', + startDate: 'data poczÄ
tkowa', + endDate: 'data koÅcowa', + currentDate: 'aktualna data', + afterDate: 'Podana data poinna byÄ taka sama lub po {label}.', + beforeDate: 'Podana data poinna byÄ taka sama lub przed {label}.', + startMonth: 'Prosimy wybraÄ poczÄ
tkowy miesiÄ
c.', + sameMonth: 'Te dwie daty muszÄ
byÄ w zakresie tego samego miesiÄ
ca - wymagana jest zmiana któregoÅ z pól.' + +}); + +/* +--- + +name: Locale.pt-PT.Date + +description: Date messages for Portuguese. + +license: MIT-style license + +authors: + - Fabio Miranda Costa + +requires: + - Locale + +provides: [Locale.pt-PT.Date] + +... +*/ + +Locale.define('pt-PT', 'Date', { + + months: ['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'], + months_abbr: ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'], + days: ['Domingo', 'Segunda-feira', 'Terça-feira', 'Quarta-feira', 'Quinta-feira', 'Sexta-feira', 'Sábado'], + days_abbr: ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'], + + // Culture's date order: DD-MM-YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d-%m-%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: 'º', + + lessThanMinuteAgo: 'há menos de um minuto', + minuteAgo: 'há cerca de um minuto', + minutesAgo: 'há {delta} minutos', + hourAgo: 'há cerca de uma hora', + hoursAgo: 'há cerca de {delta} horas', + dayAgo: 'há um dia', + daysAgo: 'há {delta} dias', + weekAgo: 'há uma semana', + weeksAgo: 'há {delta} semanas', + monthAgo: 'há um mês', + monthsAgo: 'há {delta} meses', + yearAgo: 'há um ano', + yearsAgo: 'há {delta} anos', + + lessThanMinuteUntil: 'em menos de um minuto', + minuteUntil: 'em um minuto', + minutesUntil: 'em {delta} minutos', + hourUntil: 'em uma hora', + hoursUntil: 'em {delta} horas', + dayUntil: 'em um dia', + daysUntil: 'em {delta} dias', + weekUntil: 'em uma semana', + weeksUntil: 'em {delta} semanas', + monthUntil: 'em um mês', + monthsUntil: 'em {delta} meses', + yearUntil: 'em um ano', + yearsUntil: 'em {delta} anos' + +}); + +/* +--- + +name: Locale.pt-BR.Date + +description: Date messages for Portuguese (Brazil). + +license: MIT-style license + +authors: + - Fabio Miranda Costa + +requires: + - Locale + - Locale.pt-PT.Date + +provides: [Locale.pt-BR.Date] + +... +*/ + +Locale.define('pt-BR', 'Date', { + + // Culture's date order: DD/MM/YYYY + shortDate: '%d/%m/%Y' + +}).inherit('pt-PT', 'Date'); + +/* +--- + +name: Locale.pt-BR.Form.Validator + +description: Form Validator messages for Portuguese (Brazil). + +license: MIT-style license + +authors: + - Fábio Miranda Costa + +requires: + - Locale + +provides: [Locale.pt-BR.Form.Validator] + +... +*/ + +Locale.define('pt-BR', 'FormValidator', { + + required: 'Este campo é obrigatório.', + minLength: 'Digite pelo menos {minLength} caracteres (tamanho atual: {length}).', + maxLength: 'Não digite mais de {maxLength} caracteres (tamanho atual: {length}).', + integer: 'Por favor digite apenas um número inteiro neste campo. Não são permitidos números decimais (por exemplo, 1,25).', + numeric: 'Por favor digite apenas valores numéricos neste campo (por exemplo, "1" ou "1.1" ou "-1" ou "-1,1").', + digits: 'Por favor use apenas números e pontuação neste campo (por exemplo, um número de telefone com traços ou pontos é permitido).', + alpha: 'Por favor use somente letras (a-z). Espaço e outros caracteres não são permitidos.', + alphanum: 'Use somente letras (a-z) ou números (0-9) neste campo. Espaço e outros caracteres não são permitidos.', + dateSuchAs: 'Digite uma data válida, como {date}', + dateInFormatMDY: 'Digite uma data válida, como DD/MM/YYYY (por exemplo, "31/12/1999")', + email: 'Digite um endereço de email válido. Por exemplo "nome@dominio.com".', + url: 'Digite uma URL válida. Exemplo: http://www.example.com.', + currencyDollar: 'Digite um valor em dinheiro válido. Exemplo: R$100,00 .', + oneRequired: 'Digite algo para pelo menos um desses campos.', + errorPrefix: 'Erro: ', + warningPrefix: 'Aviso: ', + + // Form.Validator.Extras + noSpace: 'Não é possÃvel digitar espaços neste campo.', + reqChkByNode: 'Não foi selecionado nenhum item.', + requiredChk: 'Este campo é obrigatório.', + reqChkByName: 'Por favor digite um {label}.', + match: 'Este campo deve ser igual ao campo {matchName}.', + startDate: 'a data inicial', + endDate: 'a data final', + currentDate: 'a data atual', + afterDate: 'A data deve ser igual ou posterior a {label}.', + beforeDate: 'A data deve ser igual ou anterior a {label}.', + startMonth: 'Por favor selecione uma data inicial.', + sameMonth: 'Estas duas datas devem ter o mesmo mês - você deve modificar uma das duas.', + creditcard: 'O número do cartão de crédito informado é inválido. Por favor verifique o valor e tente novamente. {length} números informados.' + +}); + +/* +--- + +name: Locale.pt-BR.Number + +description: Number messages for PT Brazilian. + +license: MIT-style license + +authors: + - Arian Stolwijk + - Danillo César + +requires: + - Locale + +provides: [Locale.pt-BR.Number] + +... +*/ + +Locale.define('pt-BR', 'Number', { + + decimal: ',', + group: '.', + + currency: { + prefix: 'R$ ' + } + +}); + + + +/* +--- + +name: Locale.pt-PT.Form.Validator + +description: Form Validator messages for Portuguese. + +license: MIT-style license + +authors: + - Miquel Hudin + +requires: + - Locale + +provides: [Locale.pt-PT.Form.Validator] + +... +*/ + +Locale.define('pt-PT', 'FormValidator', { + + required: 'Este campo é necessário.', + minLength: 'Digite pelo menos{minLength} caracteres (comprimento {length} caracteres).', + maxLength: 'Não insira mais de {maxLength} caracteres (comprimento {length} caracteres).', + integer: 'Digite um número inteiro neste domÃnio. Com números decimais (por exemplo, 1,25), não são permitidas.', + numeric: 'Digite apenas valores numéricos neste domÃnio (p.ex., "1" ou "1.1" ou "-1" ou "-1,1").', + digits: 'Por favor, use números e pontuação apenas neste campo (p.ex., um número de telefone com traços ou pontos é permitida).', + alpha: 'Por favor use somente letras (a-z), com nesta área. Não utilize espaços nem outros caracteres são permitidos.', + alphanum: 'Use somente letras (a-z) ou números (0-9) neste campo. Não utilize espaços nem outros caracteres são permitidos.', + dateSuchAs: 'Digite uma data válida, como {date}', + dateInFormatMDY: 'Digite uma data válida, como DD/MM/YYYY (p.ex. "31/12/1999")', + email: 'Digite um endereço de email válido. Por exemplo "fred@domain.com".', + url: 'Digite uma URL válida, como http://www.example.com.', + currencyDollar: 'Digite um valor válido $. Por exemplo $ 100,00. ', + oneRequired: 'Digite algo para pelo menos um desses insumos.', + errorPrefix: 'Erro: ', + warningPrefix: 'Aviso: ' + +}); + +/* +--- + +name: Locale.ru-RU-unicode.Date + +description: Date messages for Russian (utf-8). + +license: MIT-style license + +authors: + - Evstigneev Pavel + - Kuryanovich Egor + +requires: + - Locale + +provides: [Locale.ru-RU.Date] + +... +*/ + +(function(){ + +// Russian language pluralization rules, taken from CLDR project, http://unicode.org/cldr/ +// one -> n mod 10 is 1 and n mod 100 is not 11; +// few -> n mod 10 in 2..4 and n mod 100 not in 12..14; +// many -> n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14; +// other -> everything else (example 3.14) +var pluralize = function (n, one, few, many, other){ + var modulo10 = n % 10, + modulo100 = n % 100; + + if (modulo10 == 1 && modulo100 != 11){ + return one; + } else if ((modulo10 == 2 || modulo10 == 3 || modulo10 == 4) && !(modulo100 == 12 || modulo100 == 13 || modulo100 == 14)){ + return few; + } else if (modulo10 == 0 || (modulo10 == 5 || modulo10 == 6 || modulo10 == 7 || modulo10 == 8 || modulo10 == 9) || (modulo100 == 11 || modulo100 == 12 || modulo100 == 13 || modulo100 == 14)){ + return many; + } else { + return other; + } +}; + +Locale.define('ru-RU', 'Date', { + + months: ['ЯМваÑÑ', 'ЀевÑалÑ', 'ÐаÑÑ', 'ÐпÑелÑ', 'Ðай', 'ÐÑМÑ', 'ÐÑлÑ', 'ÐвгÑÑÑ', 'СеМÑÑбÑÑ', 'ÐкÑÑбÑÑ', 'ÐПÑбÑÑ', 'ÐекабÑÑ'], + months_abbr: ['ÑМв', 'ÑевÑ', 'ЌаÑÑ', 'апÑ', 'Ќай','ОÑМÑ','ОÑлÑ','авг','ÑеМÑ','ПкÑ','МПÑб','Ўек'], + days: ['ÐПÑкÑеÑеМÑе', 'ÐПМеЎелÑМОк', 'ÐÑПÑМОк', 'СÑеЎа', 'ЧеÑвеÑг', 'ÐÑÑМОÑа', 'СÑббПÑа'], + days_abbr: ['ÐÑ', 'ÐМ', 'ÐÑ', 'СÑ', 'ЧÑ', 'ÐÑ', 'Сб'], + + // Culture's date order: DD.MM.YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d.%m.%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: 'ЌеМÑÑе ЌОМÑÑÑ ÐœÐ°Ð·Ð°ÐŽ', + minuteAgo: 'ЌОМÑÑÑ ÐœÐ°Ð·Ð°ÐŽ', + minutesAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ЌОМÑÑÑ', 'ЌОМÑÑÑ', 'ЌОМÑÑ') + ' МазаЎ'; }, + hourAgo: 'ÑÐ°Ñ ÐœÐ°Ð·Ð°ÐŽ', + hoursAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ÑаÑ', 'ÑаÑа', 'ÑаÑПв') + ' МазаЎ'; }, + dayAgo: 'вÑеÑа', + daysAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ЎеМÑ', 'ЎМÑ', 'ЎМей') + ' МазаЎ'; }, + weekAgo: 'ÐœÐµÐŽÐµÐ»Ñ ÐœÐ°Ð·Ð°ÐŽ', + weeksAgo: function(delta){ return '{delta} ' + pluralize(delta, 'МеЎелÑ', 'МеЎелО', 'МеЎелÑ') + ' МазаЎ'; }, + monthAgo: 'ЌеÑÑÑ ÐœÐ°Ð·Ð°ÐŽ', + monthsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ЌеÑÑÑ', 'ЌеÑÑÑа', 'ЌеÑÑÑев') + ' МазаЎ'; }, + yearAgo: 'гПЎ МазаЎ', + yearsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'гПЎ', 'гПЎа', 'леÑ') + ' МазаЎ'; }, + + lessThanMinuteUntil: 'ЌеМÑÑе ÑеЌ ÑеÑез ЌОМÑÑÑ', + minuteUntil: 'ÑеÑез ЌОМÑÑÑ', + minutesUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'ЌОМÑÑÑ', 'ЌОМÑÑÑ', 'ЌОМÑÑ') + ''; }, + hourUntil: 'ÑеÑез ÑаÑ', + hoursUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'ÑаÑ', 'ÑаÑа', 'ÑаÑПв') + ''; }, + dayUntil: 'завÑÑа', + daysUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'ЎеМÑ', 'ЎМÑ', 'ЎМей') + ''; }, + weekUntil: 'ÑеÑез МеЎелÑ', + weeksUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'МеЎелÑ', 'МеЎелО', 'МеЎелÑ') + ''; }, + monthUntil: 'ÑеÑез ЌеÑÑÑ', + monthsUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'ЌеÑÑÑ', 'ЌеÑÑÑа', 'ЌеÑÑÑев') + ''; }, + yearUntil: 'ÑеÑез', + yearsUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'гПЎ', 'гПЎа', 'леÑ') + ''; } + +}); + + + +})(); + +/* +--- + +name: Locale.ru-RU-unicode.Form.Validator + +description: Form Validator messages for Russian (utf-8). + +license: MIT-style license + +authors: + - Chernodarov Egor + +requires: + - Locale + +provides: [Locale.ru-RU.Form.Validator] + +... +*/ + +Locale.define('ru-RU', 'FormValidator', { + + required: 'ÐÑП пПле ПбÑзаÑелÑМП к запПлМеМОÑ.', + minLength: 'ÐПжалÑйÑÑа, ввеЎОÑе Ñ
ПÑÑ Ð±Ñ {minLength} ÑОЌвПлПв (ÐÑ Ð²Ð²ÐµÐ»Ðž {length}).', + maxLength: 'ÐПжалÑйÑÑа, ввеЎОÑе Ме бПлÑÑе {maxLength} ÑОЌвПлПв (ÐÑ Ð²Ð²ÐµÐ»Ðž {length}).', + integer: 'ÐПжалÑйÑÑа, ввеЎОÑе в ÑÑП пПле ÑОÑлП. ÐÑПбМÑе ÑОÑла (МапÑÐžÐŒÐµÑ 1.25) ÑÑÑ ÐœÐµ ÑазÑеÑеМÑ.', + numeric: 'ÐПжалÑйÑÑа, ввеЎОÑе в ÑÑП пПле ÑОÑлП (МапÑÐžÐŒÐµÑ "1" ОлО "1.1", ОлО "-1", ОлО "-1.1").', + digits: 'Ð ÑÑПЌ пПле ÐÑ ÐŒÐŸÐ¶ÐµÑе ОÑпПлÑзПваÑÑ ÑПлÑкП ÑОÑÑÑ Ðž зМакО пÑМкÑÑаÑОО (МапÑОЌеÑ, ÑелеÑПММÑй ÐœÐŸÐŒÐµÑ ÑП зМакаЌО ЎеÑОÑа ОлО Ñ ÑПÑкаЌО).', + alpha: 'Ð ÑÑПЌ пПле ЌПжМП ОÑпПлÑзПваÑÑ ÑПлÑкП лаÑОМÑкОе бÑÐºÐ²Ñ (a-z). ÐÑÐŸÐ±ÐµÐ»Ñ Ðž ÐŽÑÑгОе ÑÐžÐŒÐ²ÐŸÐ»Ñ Ð·Ð°Ð¿ÑеÑеМÑ.', + alphanum: 'Ð ÑÑПЌ пПле ЌПжМП ОÑпПлÑзПваÑÑ ÑПлÑкП лаÑОМÑкОе бÑÐºÐ²Ñ (a-z) О ÑОÑÑÑ (0-9). ÐÑÐŸÐ±ÐµÐ»Ñ Ðž ÐŽÑÑгОе ÑÐžÐŒÐ²ÐŸÐ»Ñ Ð·Ð°Ð¿ÑеÑеМÑ.', + dateSuchAs: 'ÐПжалÑйÑÑа, ввеЎОÑе кПÑÑекÑМÑÑ ÐŽÐ°ÑÑ {date}', + dateInFormatMDY: 'ÐПжалÑйÑÑа, ввеЎОÑе ЎаÑÑ Ð² ÑПÑЌаÑе ÐÐ/ÐÐ/ÐÐÐÐ (МапÑÐžÐŒÐµÑ "12/31/1999")', + email: 'ÐПжалÑйÑÑа, ввеЎОÑе кПÑÑекÑМÑй еЌейл-аЎÑеÑ. ÐÐ»Ñ Ð¿ÑОЌеÑа "fred@domain.com".', + url: 'ÐПжалÑйÑÑа, ввеЎОÑе пÑавОлÑМÑÑ ÑÑÑÐ»ÐºÑ Ð²ÐžÐŽÐ° http://www.example.com.', + currencyDollar: 'ÐПжалÑйÑÑа, ввеЎОÑе ÑÑÐŒÐŒÑ Ð² ЎПллаÑаÑ
. ÐапÑОЌеÑ: $100.00 .', + oneRequired: 'ÐПжалÑйÑÑа, вÑбеÑОÑе Ñ
ПÑÑ ÑÑП-МОбÑÐŽÑ Ð² ПЎМПЌ Оз ÑÑОÑ
пПлей.', + errorPrefix: 'ÐÑОбка: ', + warningPrefix: 'ÐМОЌаМОе: ' + +}); + + + +/* +--- + +name: Locale.sk-SK.Date + +description: Date messages for Slovak. + +license: MIT-style license + +authors: + - Ivan Masár + +requires: + - Locale + +provides: [Locale.sk-SK.Date] + +... +*/ +(function(){ + +// Slovak language pluralization rules, see http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html +// one -> n is 1; 1 +// few -> n in 2..4; 2-4 +// other -> everything else 0, 5-999, 1.31, 2.31, 5.31... +var pluralize = function (n, one, few, other){ + if (n == 1) return one; + else if (n == 2 || n == 3 || n == 4) return few; + else return other; +}; + +Locale.define('sk-SK', 'Date', { + + months: ['Január', 'Február', 'Marec', 'AprÃl', 'Máj', 'Jún', 'Júl', 'August', 'September', 'Október', 'November', 'December'], + months_abbr: ['januára', 'februára', 'marca', 'aprÃla', 'mája', 'júna', 'júla', 'augusta', 'septembra', 'októbra', 'novembra', 'decembra'], + days: ['Nedele', 'PondelÃ', 'ÃterÜ', 'Streda', 'Ätvrtek', 'Pátek', 'Sobota'], + days_abbr: ['ne', 'po', 'ut', 'st', 'Å¡t', 'pi', 'so'], + + // Culture's date order: DD.MM.YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d.%m.%Y', + shortTime: '%H:%M', + AM: 'dop.', + PM: 'pop.', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '.', + + lessThanMinuteAgo: 'pred chvÃÄŸou', + minuteAgo: 'pribliÅŸne pred minútou', + minutesAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'minútou', 'minútami', 'minútami'); }, + hourAgo: 'pribliÅŸne pred hodinou', + hoursAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'hodinou', 'hodinami', 'hodinami'); }, + dayAgo: 'pred dÅom', + daysAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'dÅom', 'dÅami', 'dÅami'); }, + weekAgo: 'pred tÜşdÅom', + weeksAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'tÜşdÅom', 'tÜşdÅami', 'tÜşdÅami'); }, + monthAgo: 'pred mesiacom', + monthsAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'mesiacom', 'mesiacmi', 'mesiacmi'); }, + yearAgo: 'pred rokom', + yearsAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'rokom', 'rokmi', 'rokmi'); }, + + lessThanMinuteUntil: 'o chvÃÄŸu', + minuteUntil: 'pribliÅŸne o minútu', + minutesUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'minútu', 'minúty', 'minúty'); }, + hourUntil: 'pribliÅŸne o hodinu', + hoursUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'hodinu', 'hodiny', 'hodÃn'); }, + dayUntil: 'o deÅ', + daysUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'deÅ', 'dni', 'dnÃ'); }, + weekUntil: 'o tÜşdeÅ', + weeksUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'tÜşdeÅ', 'tÜşdne', 'tÜşdÅov'); }, + monthUntil: 'o mesiac', + monthsUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'mesiac', 'mesiace', 'mesiacov'); }, + yearUntil: 'o rok', + yearsUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'rok', 'roky', 'rokov'); } +}); + +})(); + +/* +--- + +name: Locale.sk-SK.Form.Validator + +description: Form Validator messages for Czech. + +license: MIT-style license + +authors: + - Ivan Masár + +requires: + - Locale + +provides: [Locale.sk-SK.Form.Validator] + +... +*/ + +Locale.define('sk-SK', 'FormValidator', { + + required: 'Táto poloÅŸka je povinná.', + minLength: 'Zadajte prosÃm aspoÅ {minLength} znakov (momentálne {length} znakov).', + maxLength: 'Zadajte prosÃm menej ako {maxLength} znakov (momentálne {length} znakov).', + integer: 'Zadajte prosÃm celé ÄÃslo. Desetinné ÄÃsla (napr. 1.25) nie sú povolené.', + numeric: 'Zadajte len ÄÃselné hodnoty (t.j. â1â alebo â1.1â alebo â-1â alebo â-1.1â).', + digits: 'Zadajte prosÃm len ÄÃsla a interpunkÄné znamienka (naprÃklad telefónne ÄÃslo s pomlÄkami albo bodkami je povolené).', + alpha: 'Zadajte prosÃm len pÃsmená (a-z). Medzery alebo iné znaky nie sú povolené.', + alphanum: 'Zadajte prosÃm len pÃsmená (a-z) alebo ÄÃslice (0-9). Medzery alebo iné znaky nie sú povolené.', + dateSuchAs: 'Zadajte prosÃm platnÜ dátum v tvare {date}', + dateInFormatMDY: 'Zadajte prosÃm platnÜ datum v tvare MM / DD / RRRR (t.j. â12/31/1999â)', + email: 'Zadajte prosÃm platnú emailovú adresu. NaprÃklad âfred@domain.comâ.', + url: 'Zadajte prosÃm platnoú adresu URL v tvare http://www.example.com.', + currencyDollar: 'Zadajte prosÃm platnú Äiastku. NaprÃklad $100.00.', + oneRequired: 'Zadajte prosÃm aspoÅ jednu hodnotu z tÜchto poloÅŸiek.', + errorPrefix: 'Chyba: ', + warningPrefix: 'Upozornenie: ', + + // Form.Validator.Extras + noSpace: 'V tejto poloÅŸle nie sú povolené medzery', + reqChkByNode: 'Nie sú vybrané ÅŸiadne poloÅŸky.', + requiredChk: 'Táto poloÅŸka je povinná.', + reqChkByName: 'ProsÃm vyberte {label}.', + match: 'Táto poloÅŸka sa musà zhodovaÅ¥ s poloÅŸkou {matchName}', + startDate: 'dátum zaÄiatku', + endDate: 'dátum ukonÄenia', + currendDate: 'aktuálny dátum', + afterDate: 'Dátum by mal bÜť rovnakÜ alebo vÀÄšà ako {label}.', + beforeDate: 'Dátum by mal byÅ¥ rovnakÜ alebo menšà ako {label}.', + startMonth: 'Vyberte poÄiatoÄnÜ mesiac.', + sameMonth: 'Tieto dva dátumy musia bÜť v rovnakom mesiaci - zmeÅte jeden z nich.', + creditcard: 'Zadané ÄÃslo kreditnej karty je neplatné. ProsÃm, opravte ho. Bolo zadanÜch {length} ÄÃslic.' + +}); + +/* +--- + +name: Locale.si-SI.Date + +description: Date messages for Slovenian. + +license: MIT-style license + +authors: + - Radovan Lozej + +requires: + - Locale + +provides: [Locale.si-SI.Date] + +... +*/ + +(function(){ + +var pluralize = function(n, one, two, three, other){ + return (n >= 1 && n <= 3) ? arguments[n] : other; +}; + +Locale.define('sl-SI', 'Date', { + + months: ['januar', 'februar', 'marec', 'april', 'maj', 'junij', 'julij', 'avgust', 'september', 'oktober', 'november', 'december'], + months_abbr: ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'avg', 'sep', 'okt', 'nov', 'dec'], + days: ['nedelja', 'ponedeljek', 'torek', 'sreda', 'Äetrtek', 'petek', 'sobota'], + days_abbr: ['ned', 'pon', 'tor', 'sre', 'Äet', 'pet', 'sob'], + + // Culture's date order: DD.MM.YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d.%m.%Y', + shortTime: '%H.%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '.', + + lessThanMinuteAgo: 'manj kot minuto nazaj', + minuteAgo: 'minuto nazaj', + minutesAgo: function(delta){ return '{delta} ' + pluralize(delta, 'minuto', 'minuti', 'minute', 'minut') + ' nazaj'; }, + hourAgo: 'uro nazaj', + hoursAgo: function(delta){ return '{delta} ' + pluralize(delta, 'uro', 'uri', 'ure', 'ur') + ' nazaj'; }, + dayAgo: 'dan nazaj', + daysAgo: function(delta){ return '{delta} ' + pluralize(delta, 'dan', 'dneva', 'dni', 'dni') + ' nazaj'; }, + weekAgo: 'teden nazaj', + weeksAgo: function(delta){ return '{delta} ' + pluralize(delta, 'teden', 'tedna', 'tedne', 'tednov') + ' nazaj'; }, + monthAgo: 'mesec nazaj', + monthsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'mesec', 'meseca', 'mesece', 'mesecov') + ' nazaj'; }, + yearthAgo: 'leto nazaj', + yearsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'leto', 'leti', 'leta', 'let') + ' nazaj'; }, + + lessThanMinuteUntil: 'Å¡e manj kot minuto', + minuteUntil: 'Å¡e minuta', + minutesUntil: function(delta){ return 'Å¡e {delta} ' + pluralize(delta, 'minuta', 'minuti', 'minute', 'minut'); }, + hourUntil: 'Å¡e ura', + hoursUntil: function(delta){ return 'Å¡e {delta} ' + pluralize(delta, 'ura', 'uri', 'ure', 'ur'); }, + dayUntil: 'Å¡e dan', + daysUntil: function(delta){ return 'Å¡e {delta} ' + pluralize(delta, 'dan', 'dneva', 'dnevi', 'dni'); }, + weekUntil: 'Å¡e tedn', + weeksUntil: function(delta){ return 'Å¡e {delta} ' + pluralize(delta, 'teden', 'tedna', 'tedni', 'tednov'); }, + monthUntil: 'Å¡e mesec', + monthsUntil: function(delta){ return 'Å¡e {delta} ' + pluralize(delta, 'mesec', 'meseca', 'meseci', 'mesecov'); }, + yearUntil: 'Å¡e leto', + yearsUntil: function(delta){ return 'Å¡e {delta} ' + pluralize(delta, 'leto', 'leti', 'leta', 'let'); } + +}); + +})(); + +/* +--- + +name: Locale.si-SI.Form.Validator + +description: Form Validator messages for Slovenian. + +license: MIT-style license + +authors: + - Radovan Lozej + +requires: + - Locale + +provides: [Locale.si-SI.Form.Validator] + +... +*/ + +Locale.define('sl-SI', 'FormValidator', { + + required: 'To polje je obvezno', + minLength: 'Prosim, vnesite vsaj {minLength} znakov (vnesli ste {length} znakov).', + maxLength: 'Prosim, ne vnesite veÄ kot {maxLength} znakov (vnesli ste {length} znakov).', + integer: 'Prosim, vnesite celo Å¡tevilo. Decimalna Å¡tevila (kot 1,25) niso dovoljena.', + numeric: 'Prosim, vnesite samo numeriÄne vrednosti (kot "1" ali "1.1" ali "-1" ali "-1.1").', + digits: 'Prosim, uporabite Å¡tevilke in loÄila le na tem polju (na primer, dovoljena je telefonska Å¡tevilka z pomiÅ¡laji ali pikami).', + alpha: 'Prosim, uporabite le Ärke v tem plju. Presledki in drugi znaki niso dovoljeni.', + alphanum: 'Prosim, uporabite samo Ärke ali Å¡tevilke v tem polju. Presledki in drugi znaki niso dovoljeni.', + dateSuchAs: 'Prosim, vnesite pravilen datum kot {date}', + dateInFormatMDY: 'Prosim, vnesite pravilen datum kot MM.DD.YYYY (primer "12.31.1999")', + email: 'Prosim, vnesite pravilen email naslov. Na primer "fred@domain.com".', + url: 'Prosim, vnesite pravilen URL kot http://www.example.com.', + currencyDollar: 'Prosim, vnesit epravilno vrednost â¬. Primer 100,00⬠.', + oneRequired: 'Prosimo, vnesite nekaj za vsaj eno izmed teh polj.', + errorPrefix: 'Napaka: ', + warningPrefix: 'Opozorilo: ', + + // Form.Validator.Extras + noSpace: 'To vnosno polje ne dopuÅ¡Äa presledkov.', + reqChkByNode: 'NiÄ niste izbrali.', + requiredChk: 'To polje je obvezno', + reqChkByName: 'Prosim, izberite {label}.', + match: 'To polje se mora ujemati z poljem {matchName}', + startDate: 'datum zaÄetka', + endDate: 'datum konca', + currentDate: 'trenuten datum', + afterDate: 'Datum bi moral biti isti ali po {label}.', + beforeDate: 'Datum bi moral biti isti ali pred {label}.', + startMonth: 'Prosim, vnesite zaÄetni datum', + sameMonth: 'Ta dva datuma morata biti v istem mesecu - premeniti morate eno ali drugo.', + creditcard: 'Å tevilka kreditne kartice ni pravilna. Preverite Å¡tevilko ali poskusite Å¡e enkrat. VneÅ¡enih {length} znakov.' + +}); + +/* +--- + +name: Locale.sv-SE.Date + +description: Date messages for Swedish. + +license: MIT-style license + +authors: + - Martin Lundgren + +requires: + - Locale + +provides: [Locale.sv-SE.Date] + +... +*/ + +Locale.define('sv-SE', 'Date', { + + months: ['januari', 'februari', 'mars', 'april', 'maj', 'juni', 'juli', 'augusti', 'september', 'oktober', 'november', 'december'], + months_abbr: ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'], + days: ['söndag', 'mÃ¥ndag', 'tisdag', 'onsdag', 'torsdag', 'fredag', 'lördag'], + days_abbr: ['sön', 'mÃ¥n', 'tis', 'ons', 'tor', 'fre', 'lör'], + + // Culture's date order: YYYY-MM-DD + dateOrder: ['year', 'month', 'date'], + shortDate: '%Y-%m-%d', + shortTime: '%H:%M', + AM: '', + PM: '', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: 'mindre Àn en minut sedan', + minuteAgo: 'ungefÀr en minut sedan', + minutesAgo: '{delta} minuter sedan', + hourAgo: 'ungefÀr en timme sedan', + hoursAgo: 'ungefÀr {delta} timmar sedan', + dayAgo: '1 dag sedan', + daysAgo: '{delta} dagar sedan', + + lessThanMinuteUntil: 'mindre Àn en minut sedan', + minuteUntil: 'ungefÀr en minut sedan', + minutesUntil: '{delta} minuter sedan', + hourUntil: 'ungefÀr en timme sedan', + hoursUntil: 'ungefÀr {delta} timmar sedan', + dayUntil: '1 dag sedan', + daysUntil: '{delta} dagar sedan' + +}); + +/* +--- + +name: Locale.sv-SE.Form.Validator + +description: Form Validator messages for Swedish. + +license: MIT-style license + +authors: + - Martin Lundgren + +requires: + - Locale + +provides: [Locale.sv-SE.Form.Validator] + +... +*/ + +Locale.define('sv-SE', 'FormValidator', { + + required: 'FÀltet Àr obligatoriskt.', + minLength: 'Ange minst {minLength} tecken (du angav {length} tecken).', + maxLength: 'Ange högst {maxLength} tecken (du angav {length} tecken). ', + integer: 'Ange ett heltal i fÀltet. Tal med decimaler (t.ex. 1,25) Àr inte tillÃ¥tna.', + numeric: 'Ange endast numeriska vÀrden i detta fÀlt (t.ex. "1" eller "1.1" eller "-1" eller "-1,1").', + digits: 'AnvÀnd endast siffror och skiljetecken i detta fÀlt (till exempel ett telefonnummer med bindestreck tillÃ¥tet).', + alpha: 'AnvÀnd endast bokstÀver (a-ö) i detta fÀlt. Inga mellanslag eller andra tecken Àr tillÃ¥tna.', + alphanum: 'AnvÀnd endast bokstÀver (a-ö) och siffror (0-9) i detta fÀlt. Inga mellanslag eller andra tecken Àr tillÃ¥tna.', + dateSuchAs: 'Ange ett giltigt datum som t.ex. {date}', + dateInFormatMDY: 'Ange ett giltigt datum som t.ex. YYYY-MM-DD (i.e. "1999-12-31")', + email: 'Ange en giltig e-postadress. Till exempel "erik@domain.com".', + url: 'Ange en giltig webbadress som http://www.example.com.', + currencyDollar: 'Ange en giltig belopp. Exempelvis 100,00.', + oneRequired: 'VÀnligen ange minst ett av dessa alternativ.', + errorPrefix: 'Fel: ', + warningPrefix: 'Varning: ', + + // Form.Validator.Extras + noSpace: 'Det fÃ¥r inte finnas nÃ¥gra mellanslag i detta fÀlt.', + reqChkByNode: 'Inga objekt Àr valda.', + requiredChk: 'Detta Àr ett obligatoriskt fÀlt.', + reqChkByName: 'VÀlj en {label}.', + match: 'Detta fÀlt mÃ¥ste matcha {matchName}', + startDate: 'startdatumet', + endDate: 'slutdatum', + currentDate: 'dagens datum', + afterDate: 'Datumet bör vara samma eller senare Àn {label}.', + beforeDate: 'Datumet bör vara samma eller tidigare Àn {label}.', + startMonth: 'VÀlj en start mÃ¥nad', + sameMonth: 'Dessa tvÃ¥ datum mÃ¥ste vara i samma mÃ¥nad - du mÃ¥ste Àndra det ena eller det andra.' + +}); + +/* +--- + +name: Locale.sv-SE.Number + +description: Number messages for Swedish. + +license: MIT-style license + +authors: + - Arian Stolwijk + - Martin Lundgren + +requires: + - Locale + - Locale.EU.Number + +provides: [Locale.sv-SE.Number] + +... +*/ + +Locale.define('sv-SE', 'Number', { + + currency: { + prefix: 'SEK ' + } + +}).inherit('EU', 'Number'); + +/* +--- + +name: Locale.tr-TR.Date + +description: Date messages for Turkish. + +license: MIT-style license + +authors: + - Faruk Can Bilir + +requires: + - Locale + +provides: [Locale.tr-TR.Date] + +... +*/ + +Locale.define('tr-TR', 'Date', { + + months: ['Ocak', 'Åubat', 'Mart', 'Nisan', 'Mayıs', 'Haziran', 'Temmuz', 'AÄustos', 'EylÃŒl', 'Ekim', 'Kasım', 'Aralık'], + months_abbr: ['Oca', 'Åub', 'Mar', 'Nis', 'May', 'Haz', 'Tem', 'AÄu', 'Eyl', 'Eki', 'Kas', 'Ara'], + days: ['Pazar', 'Pazartesi', 'Salı', 'ÃarÅamba', 'PerÅembe', 'Cuma', 'Cumartesi'], + days_abbr: ['Pa', 'Pzt', 'Sa', 'Ãa', 'Pe', 'Cu', 'Cmt'], + + // Culture's date order: MM/DD/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d/%m/%Y', + shortTime: '%H.%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: 'bir dakikadan önce', + minuteAgo: 'yaklaÅık bir dakika önce', + minutesAgo: '{delta} dakika önce', + hourAgo: 'bir saat kadar önce', + hoursAgo: '{delta} saat kadar önce', + dayAgo: 'bir gÃŒn önce', + daysAgo: '{delta} gÃŒn önce', + weekAgo: 'bir hafta önce', + weeksAgo: '{delta} hafta önce', + monthAgo: 'bir ay önce', + monthsAgo: '{delta} ay önce', + yearAgo: 'bir yıl önce', + yearsAgo: '{delta} yıl önce', + + lessThanMinuteUntil: 'bir dakikadan az sonra', + minuteUntil: 'bir dakika kadar sonra', + minutesUntil: '{delta} dakika sonra', + hourUntil: 'bir saat kadar sonra', + hoursUntil: '{delta} saat kadar sonra', + dayUntil: 'bir gÃŒn sonra', + daysUntil: '{delta} gÃŒn sonra', + weekUntil: 'bir hafta sonra', + weeksUntil: '{delta} hafta sonra', + monthUntil: 'bir ay sonra', + monthsUntil: '{delta} ay sonra', + yearUntil: 'bir yıl sonra', + yearsUntil: '{delta} yıl sonra' + +}); + +/* +--- + +name: Locale.tr-TR.Form.Validator + +description: Form Validator messages for Turkish. + +license: MIT-style license + +authors: + - Faruk Can Bilir + +requires: + - Locale + +provides: [Locale.tr-TR.Form.Validator] + +... +*/ + +Locale.define('tr-TR', 'FormValidator', { + + required: 'Bu alan zorunlu.', + minLength: 'LÃŒtfen en az {minLength} karakter girin (siz {length} karakter girdiniz).', + maxLength: 'LÃŒtfen en fazla {maxLength} karakter girin (siz {length} karakter girdiniz).', + integer: 'LÃŒtfen bu alana sadece tamsayı girin. Ondalıklı sayılar (ör: 1.25) kullanılamaz.', + numeric: 'LÃŒtfen bu alana sadece sayısal deÄer girin (ör: "1", "1.1", "-1" ya da "-1.1").', + digits: 'LÃŒtfen bu alana sadece sayısal deÄer ve noktalama iÅareti girin (örneÄin, nokta ve tire içeren bir telefon numarası kullanılabilir).', + alpha: 'LÃŒtfen bu alanda yalnızca harf kullanın. BoÅluk ve diÄer karakterler kullanılamaz.', + alphanum: 'LÃŒtfen bu alanda sadece harf ve rakam kullanın. BoÅluk ve diÄer karakterler kullanılamaz.', + dateSuchAs: 'LÃŒtfen geçerli bir tarih girin (Ãr: {date})', + dateInFormatMDY: 'LÃŒtfen geçerli bir tarih girin (GG/AA/YYYY, ör: "31/12/1999")', + email: 'LÃŒtfen geçerli bir email adresi girin. Ãr: "kemal@etikan.com".', + url: 'LÃŒtfen geçerli bir URL girin. Ãr: http://www.example.com.', + currencyDollar: 'LÃŒtfen geçerli bir TL miktarı girin. Ãr: 100,00 TL .', + oneRequired: 'LÃŒtfen en az bir tanesini doldurun.', + errorPrefix: 'Hata: ', + warningPrefix: 'Uyarı: ', + + // Form.Validator.Extras + noSpace: 'Bu alanda boÅluk kullanılamaz.', + reqChkByNode: 'Hiçbir öÄe seçilmemiÅ.', + requiredChk: 'Bu alan zorunlu.', + reqChkByName: 'LÃŒtfen bir {label} girin.', + match: 'Bu alan, {matchName} alanıyla uyuÅmalı', + startDate: 'baÅlangıç tarihi', + endDate: 'bitiÅ tarihi', + currentDate: 'bugÃŒnÃŒn tarihi', + afterDate: 'Tarih, {label} tarihiyle aynı gÃŒn ya da ondan sonra olmalıdır.', + beforeDate: 'Tarih, {label} tarihiyle aynı gÃŒn ya da ondan önce olmalıdır.', + startMonth: 'LÃŒtfen bir baÅlangıç ayı seçin', + sameMonth: 'Bu iki tarih aynı ayda olmalı - bir tanesini deÄiÅtirmeniz gerekiyor.', + creditcard: 'GirdiÄiniz kredi kartı numarası geçersiz. LÃŒtfen kontrol edip tekrar deneyin. {length} hane girildi.' + +}); + +/* +--- + +name: Locale.tr-TR.Number + +description: Number messages for Turkish. + +license: MIT-style license + +authors: + - Faruk Can Bilir + +requires: + - Locale + - Locale.EU.Number + +provides: [Locale.tr-TR.Number] + +... +*/ + +Locale.define('tr-TR', 'Number', { + + currency: { + decimals: 0, + suffix: ' TL' + } + +}).inherit('EU', 'Number'); + +/* +--- + +name: Locale.uk-UA.Date + +description: Date messages for Ukrainian (utf-8). + +license: MIT-style license + +authors: + - Slik + +requires: + - Locale + +provides: [Locale.uk-UA.Date] + +... +*/ + +(function(){ + +var pluralize = function(n, one, few, many, other){ + var d = (n / 10).toInt(), + z = n % 10, + s = (n / 100).toInt(); + + if (d == 1 && n > 10) return many; + if (z == 1) return one; + if (z > 0 && z < 5) return few; + return many; +}; + +Locale.define('uk-UA', 'Date', { + + months: ['СÑÑеМÑ', 'ÐÑÑОй', 'ÐеÑезеМÑ', 'ÐвÑÑеМÑ', 'ТÑавеМÑ', 'ЧеÑвеМÑ', 'ÐОпеМÑ', 'СеÑпеМÑ', 'ÐеÑеÑеМÑ', 'ÐПвÑеМÑ', 'ÐОÑÑПпаЎ', 'ÐÑÑЎеМÑ'], + months_abbr: ['СÑÑ', 'ÐÑÑ', 'ÐеÑ', 'ÐвÑÑ', 'ТÑав', 'ЧеÑв', 'ÐОп', 'СеÑп', 'ÐеÑ', 'ÐПвÑ', 'ÐОÑÑ', 'ÐÑÑÐŽ' ], + days: ['ÐеЎÑлÑ', 'ÐПМеЎÑлПк', 'ÐÑвÑПÑПк', 'СеÑеЎа', 'ЧеÑвеÑ', "Ð'ÑÑМОÑÑ", 'СÑбПÑа'], + days_abbr: ['ÐÐŽ', 'ÐМ', 'ÐÑ', 'СÑ', 'ЧÑ', 'ÐÑ', 'Сб'], + + // Culture's date order: DD/MM/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d/%m/%Y', + shortTime: '%H:%M', + AM: 'ЎП пПлÑЎМÑ', + PM: 'пП пПлÑЎМÑ', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: 'ЌеМÑÑе Ñ
вОлОМО ÑПЌÑ', + minuteAgo: 'Ñ
Ð²ÐžÐ»ÐžÐœÑ ÑПЌÑ', + minutesAgo: function(delta){ return '{delta} ' + pluralize(delta, 'Ñ
вОлОМÑ', 'Ñ
вОлОМО', 'Ñ
вОлОМ') + ' ÑПЌÑ'; }, + hourAgo: 'Ð³ÐŸÐŽÐžÐœÑ ÑПЌÑ', + hoursAgo: function(delta){ return '{delta} ' + pluralize(delta, 'гПЎОМÑ', 'гПЎОМО', 'гПЎОМ') + ' ÑПЌÑ'; }, + dayAgo: 'вÑПÑа', + daysAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ЎеМÑ', 'ЎМÑ', 'ЎМÑв') + ' ÑПЌÑ'; }, + weekAgo: 'ÑÐžÐ¶ÐŽÐµÐœÑ ÑПЌÑ', + weeksAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ÑОжЎеМÑ', 'ÑОжМÑ', 'ÑОжМÑв') + ' ÑПЌÑ'; }, + monthAgo: 'ÐŒÑÑÑÑÑ ÑПЌÑ', + monthsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ÐŒÑÑÑÑÑ', 'ÐŒÑÑÑÑÑ', 'ÐŒÑÑÑÑÑв') + ' ÑПЌÑ'; }, + yearAgo: 'ÑÑк ÑПЌÑ', + yearsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ÑÑк', 'ÑПкО', 'ÑПкÑв') + ' ÑПЌÑ'; }, + + lessThanMinuteUntil: 'за ЌОÑÑ', + minuteUntil: 'ÑеÑез Ñ
вОлОМÑ', + minutesUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'Ñ
вОлОМÑ', 'Ñ
вОлОМО', 'Ñ
вОлОМ'); }, + hourUntil: 'ÑеÑез гПЎОМÑ', + hoursUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'гПЎОМÑ', 'гПЎОМО', 'гПЎОМ'); }, + dayUntil: 'завÑÑа', + daysUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'ЎеМÑ', 'ЎМÑ', 'ЎМÑв'); }, + weekUntil: 'ÑеÑез ÑОжЎеМÑ', + weeksUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'ÑОжЎеМÑ', 'ÑОжМÑ', 'ÑОжМÑв'); }, + monthUntil: 'ÑеÑез ÐŒÑÑÑÑÑ', + monthesUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'ÐŒÑÑÑÑÑ', 'ÐŒÑÑÑÑÑ', 'ÐŒÑÑÑÑÑв'); }, + yearUntil: 'ÑеÑез ÑÑк', + yearsUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'ÑÑк', 'ÑПкО', 'ÑПкÑв'); } + +}); + +})(); + +/* +--- + +name: Locale.uk-UA.Form.Validator + +description: Form Validator messages for Ukrainian (utf-8). + +license: MIT-style license + +authors: + - Slik + +requires: + - Locale + +provides: [Locale.uk-UA.Form.Validator] + +... +*/ + +Locale.define('uk-UA', 'FormValidator', { + + required: 'Ње пПле пПвОММе бÑÑО запПвМеМОЌ.', + minLength: 'ÐвеЎÑÑÑ Ñ
ПÑа б {minLength} ÑОЌвПлÑв (ÐО ввелО {length}).', + maxLength: 'ÐÑлÑкÑÑÑÑ ÑОЌвПлÑв Ме ЌПже бÑÑО бÑлÑÑе {maxLength} (ÐО ввелО {length}).', + integer: 'ÐвеЎÑÑÑ Ð² Ñе пПле ÑОÑлП. ÐÑÐŸÐ±ÐŸÐ²Ñ ÑОÑла (МапÑОклаЎ 1.25) Ме ЎПзвПлеМÑ.', + numeric: 'ÐвеЎÑÑÑ Ð² Ñе пПле ÑОÑлП (МапÑОклаЎ "1" абП "1.1", абП "-1", абП "-1.1").', + digits: 'Ð ÑÑÐŸÐŒÑ Ð¿ÐŸÐ»Ñ Ð²Ðž ЌПжеÑе вОкПÑОÑÑПвÑваÑО лОÑе ÑОÑÑО Ñ Ð·ÐœÐ°ÐºÐž пÑМкÑÑаÑÑÑ (МапÑОклаЎ, ÑелеÑПММОй ÐœÐŸÐŒÐµÑ Ð· зМакаЌО ЎеÑÑÐ·Ñ Ð°Ð±ÐŸ з кÑапкаЌО).', + alpha: 'Ð ÑÑÐŸÐŒÑ Ð¿ÐŸÐ»Ñ ÐŒÐŸÐ¶ÐœÐ° вОкПÑОÑÑПвÑваÑО лОÑе лаÑОМÑÑÐºÑ Ð»ÑÑеÑО (a-z). ÐÑПбÑлО Ñ ÑМÑÑ ÑОЌвПлО забПÑПМеМÑ.', + alphanum: 'Ð ÑÑÐŸÐŒÑ Ð¿ÐŸÐ»Ñ ÐŒÐŸÐ¶ÐœÐ° вОкПÑОÑÑПвÑваÑО лОÑе лаÑОМÑÑÐºÑ Ð»ÑÑеÑО (a-z) Ñ ÑОÑÑО (0-9). ÐÑПбÑлО Ñ ÑМÑÑ ÑОЌвПлО забПÑПМеМÑ.', + dateSuchAs: 'ÐвеЎÑÑÑ ÐºÐŸÑекÑÐœÑ ÐŽÐ°ÑÑ {date}.', + dateInFormatMDY: 'ÐвеЎÑÑÑ ÐŽÐ°ÑÑ Ð² ÑПÑЌаÑÑ ÐÐ/ÐÐ/Ð Ð Ð Ð (МапÑОклаЎ "12/31/2009").', + email: 'ÐвеЎÑÑÑ ÐºÐŸÑекÑÐœÑ Ð°ÐŽÑеÑÑ ÐµÐ»ÐµÐºÑÑÐŸÐœÐœÐŸÑ Ð¿ÐŸÑÑО (МапÑОклаЎ "name@domain.com").', + url: 'ÐвеЎÑÑÑ ÐºÐŸÑекÑМе ÑМÑеÑМеÑ-пПÑÐžÐ»Ð°ÐœÐœÑ (МапÑОклаЎ http://www.example.com).', + currencyDollar: 'ÐвеЎÑÑÑ ÑÑÐŒÑ Ð² ЎПлаÑаÑ
(МапÑОклаЎ "$100.00").', + oneRequired: 'ÐапПвМÑÑÑ ÐŸÐŽÐœÐµ з пПлÑв.', + errorPrefix: 'ÐПЌОлка: ', + warningPrefix: 'Увага: ', + + noSpace: 'ÐÑПбÑлО забПÑПМеМÑ.', + reqChkByNode: 'Ðе вÑÐŽÐŒÑÑеМП жПЎМПгП ваÑÑаМÑÑ.', + requiredChk: 'Ње пПле пПвОММе бÑÑО вÑÐŒÑÑеМОЌ.', + reqChkByName: 'ÐÑÐŽÑ Ð»Ð°Ñка, вÑÐŽÐŒÑÑÑÑе {label}.', + match: 'Ње пПле пПвОММП вÑЎпПвÑЎаÑО {matchName}', + startDate: 'пПÑаÑкПва ЎаÑа', + endDate: 'кÑМÑева ЎаÑа', + currentDate: 'ÑÑПгПЎМÑÑÐœÑ ÐŽÐ°Ñа', + afterDate: 'ÐŠÑ ÐŽÐ°Ñа пПвОММа бÑÑО ÑÐ°ÐºÐŸÑ Ð¶, абП пÑзМÑÑÐŸÑ Ð·Ð° {label}.', + beforeDate: 'ÐŠÑ ÐŽÐ°Ñа пПвОММа бÑÑО ÑÐ°ÐºÐŸÑ Ð¶, абП ÑаМÑÑÐŸÑ Ð·Ð° {label}.', + startMonth: 'ÐÑÐŽÑ Ð»Ð°Ñка, вОбеÑÑÑÑ Ð¿ÐŸÑаÑкПвОй ÐŒÑÑÑÑÑ', + sameMonth: 'ÐŠÑ ÐŽÐ°ÑО Ð¿ÐŸÐ²ÐžÐœÐœÑ Ð²ÑЎМПÑОÑОÑÑ ÐŸÐŽÐœÐŸÐ³ÐŸ Ñ ÑПгП ж ÐŒÑÑÑÑÑ. ÐÑÐŽÑ Ð»Ð°Ñка, зЌÑМÑÑÑ ÐŸÐŽÐœÑ Ð· МОÑ
.', + creditcard: 'ÐÐŸÐŒÐµÑ ÐºÑеЎОÑÐœÐŸÑ ÐºÐ°ÑÑО ввеЎеМОй МепÑавОлÑМП. ÐÑÐŽÑ Ð»Ð°Ñка, пеÑевÑÑÑе йПгП. ÐвеЎеМП {length} ÑОЌвПлÑв.' + +}); + +/* +--- + +name: Locale.zh-CH.Date + +description: Date messages for Chinese (simplified and traditional). + +license: MIT-style license + +authors: + - YMind Chan + +requires: + - Locale + +provides: [Locale.zh-CH.Date] + +... +*/ + +// Simplified Chinese +Locale.define('zh-CHS', 'Date', { + + months: ['äžæ', 'äºæ', 'äžæ', 'åæ', 'äºæ', 'å
æ', 'äžæ', 'å
«æ', '乿', 'åæ', 'åäžæ', 'åäºæ'], + months_abbr: ['äž', 'äº', 'äž', 'å', 'äº', 'å
', 'äž', 'å
«', 'ä¹', 'å', 'åäž', 'åäº'], + days: ['æææ¥', 'ææäž', 'ææäº', 'ææäž', 'ææå', 'ææäº', 'ææå
'], + days_abbr: ['æ¥', 'äž', 'äº', 'äž', 'å', 'äº', 'å
'], + + // Culture's date order: YYYY-MM-DD + dateOrder: ['year', 'month', 'date'], + shortDate: '%Y-%m-%d', + shortTime: '%I:%M%p', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: 'äžå°1åéå', + minuteAgo: '倧纊1åéå', + minutesAgo: '{delta}åéä¹å', + hourAgo: '倧纊1å°æ¶å', + hoursAgo: '倧纊{delta}å°æ¶å', + dayAgo: '1倩å', + daysAgo: '{delta}倩å', + weekAgo: '1ææå', + weeksAgo: '{delta}ææå', + monthAgo: '1䞪æå', + monthsAgo: '{delta}䞪æå', + yearAgo: '1幎å', + yearsAgo: '{delta}幎å', + + lessThanMinuteUntil: 'ä»ç°åšåŒå§äžå°1åé', + minuteUntil: 'ä»ç°åšåŒå§çŽ1åé', + minutesUntil: 'ä»ç°åšåŒå§çºŠ{delta}åé', + hourUntil: 'ä»ç°åšåŒå§1å°æ¶', + hoursUntil: 'ä»ç°åšåŒå§çºŠ{delta}å°æ¶', + dayUntil: 'ä»ç°åšåŒå§1倩', + daysUntil: 'ä»ç°åšåŒå§{delta}倩', + weekUntil: 'ä»ç°åšåŒå§1ææ', + weeksUntil: 'ä»ç°åšåŒå§{delta}ææ', + monthUntil: 'ä»ç°åšåŒå§äžäžªæ', + monthsUntil: 'ä»ç°åšåŒå§{delta}䞪æ', + yearUntil: 'ä»ç°åšåŒå§1幎', + yearsUntil: 'ä»ç°åšåŒå§{delta}幎' + +}); + +// Traditional Chinese +Locale.define('zh-CHT', 'Date', { + + months: ['äžæ', 'äºæ', 'äžæ', 'åæ', 'äºæ', 'å
æ', 'äžæ', 'å
«æ', '乿', 'åæ', 'åäžæ', 'åäºæ'], + months_abbr: ['äž', 'äº', 'äž', 'å', 'äº', 'å
', 'äž', 'å
«', 'ä¹', 'å', 'åäž', 'åäº'], + days: ['æææ¥', 'ææäž', 'ææäº', 'ææäž', 'ææå', 'ææäº', 'ææå
'], + days_abbr: ['æ¥', 'äž', 'äº', 'äž', 'å', 'äº', 'å
'], + + // Culture's date order: YYYY-MM-DD + dateOrder: ['year', 'month', 'date'], + shortDate: '%Y-%m-%d', + shortTime: '%I:%M%p', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: 'äžå°1åéå', + minuteAgo: '倧çŽ1åéå', + minutesAgo: '{delta}åéä¹å', + hourAgo: '倧çŽ1å°æå', + hoursAgo: '倧çŽ{delta}å°æå', + dayAgo: '1倩å', + daysAgo: '{delta}倩å', + weekAgo: '1ææå', + weeksAgo: '{delta}ææå', + monthAgo: '1䞪æå', + monthsAgo: '{delta}䞪æå', + yearAgo: '1幎å', + yearsAgo: '{delta}幎å', + + lessThanMinuteUntil: 'åŸçŸåšéå§äžå°1åé', + minuteUntil: 'åŸçŸåšéå§çŽ1åé', + minutesUntil: 'åŸçŸåšéå§çŽ{delta}åé', + hourUntil: 'åŸçŸåšéå§1å°æ', + hoursUntil: 'åŸçŸåšéå§çŽ{delta}å°æ', + dayUntil: 'åŸçŸåšéå§1倩', + daysUntil: 'åŸçŸåšéå§{delta}倩', + weekUntil: 'åŸçŸåšéå§1ææ', + weeksUntil: 'åŸçŸåšéå§{delta}ææ', + monthUntil: 'åŸçŸåšéå§äžåæ', + monthsUntil: 'åŸçŸåšéå§{delta}åæ', + yearUntil: 'åŸçŸåšéå§1幎', + yearsUntil: 'åŸçŸåšéå§{delta}幎' + +}); + +/* +--- + +name: Locale.zh-CH.Form.Validator + +description: Form Validator messages for Chinese (simplified and traditional). + +license: MIT-style license + +authors: + - YMind Chan + +requires: + - Locale + - Form.Validator + +provides: [Form.zh-CH.Form.Validator, Form.Validator.CurrencyYuanValidator] + +... +*/ + +// Simplified Chinese +Locale.define('zh-CHS', 'FormValidator', { + + required: 'æ€é¡¹å¿
å¡«ã', + minLength: '请è³å°èŸå
¥ {minLength} 䞪å笊 (å·²èŸå
¥ {length} 䞪)ã', + maxLength: 'æå€åªèœèŸå
¥ {maxLength} 䞪å笊 (å·²èŸå
¥ {length} 䞪)ã', + integer: '请èŸå
¥äžäžªæŽæ°ïŒäžèœå
å«å°æ°ç¹ãäŸåŠïŒ"1", "200"ã', + numeric: '请èŸå
¥äžäžªæ°åïŒäŸåŠïŒ"1", "1.1", "-1", "-1.1"ã', + digits: '请èŸå
¥ç±æ°ååæ ç¹ç¬Šå·ç»æçå
容ãäŸåŠçµè¯å·ç ã', + alpha: '请èŸå
¥ A-Z ç 26 äžªåæ¯ïŒäžèœå
å«ç©ºæ Œæä»»äœå
¶ä»å笊ã', + alphanum: '请èŸå
¥ A-Z ç 26 äžªåæ¯æ 0-9 ç 10 䞪æ°åïŒäžèœå
å«ç©ºæ Œæä»»äœå
¶ä»å笊ã', + dateSuchAs: '请èŸå
¥åæ³çæ¥ææ ŒåŒïŒåŠïŒ{date}ã', + dateInFormatMDY: '请èŸå
¥åæ³çæ¥ææ ŒåŒïŒäŸåŠïŒYYYY-MM-DD ("2010-12-31")ã', + email: '请èŸå
¥åæ³ççµåä¿¡ç®±å°åïŒäŸåŠïŒ"fred@domain.com"ã', + url: '请èŸå
¥åæ³ç Url å°åïŒäŸåŠïŒhttp://www.example.comã', + currencyDollar: '请èŸå
¥åæ³ç莧åžç¬Šå·ïŒäŸåŠïŒï¿¥100.0', + oneRequired: '请è³å°éæ©äžé¡¹ã', + errorPrefix: 'é误ïŒ', + warningPrefix: 'èŠåïŒ', + + // Form.Validator.Extras + noSpace: 'äžèœå
å«ç©ºæ Œã', + reqChkByNode: 'æªéæ©ä»»äœå
容ã', + requiredChk: 'æ€é¡¹å¿
å¡«ã', + reqChkByName: 'è¯·éæ© {label}.', + match: 'å¿
é¡»äž{matchName}çžå¹é
', + startDate: 'èµ·å§æ¥æ', + endDate: 'ç»ææ¥æ', + currentDate: 'åœåæ¥æ', + afterDate: 'æ¥æå¿
é¡»çäºææäº {label}.', + beforeDate: 'æ¥æå¿
é¡»æ©äºæçäº {label}.', + startMonth: 'è¯·éæ©èµ·å§æä»œ', + sameMonth: 'æšå¿
须修æ¹äž€äžªæ¥æäžçäžäžªïŒä»¥ç¡®ä¿å®ä»¬åšåäžæä»œã', + creditcard: 'æšèŸå
¥çä¿¡çšå¡å·ç äžæ£ç¡®ãåœåå·²èŸå
¥{length}䞪å笊ã' + +}); + +// Traditional Chinese +Locale.define('zh-CHT', 'FormValidator', { + + required: 'æ€é
å¿
å¡«ã ', + minLength: 'è«è³å°èŒžå
¥{minLength} åå笊(已茞å
¥{length} å)ã ', + maxLength: 'æå€åªèœèŒžå
¥{maxLength} åå笊(已茞å
¥{length} å)ã ', + integer: 'è«èŒžå
¥äžåæŽæžïŒäžèœå
å«å°æžé»ãäŸåŠïŒ"1", "200"ã ', + numeric: 'è«èŒžå
¥äžåæžåïŒäŸåŠïŒ"1", "1.1", "-1", "-1.1"ã ', + digits: 'è«èŒžå
¥ç±æžååæšé»ç¬Šèçµæçå
§å®¹ãäŸåŠé»è©±è碌ã ', + alpha: 'è«èŒžå
¥AZ ç26 å忝ïŒäžèœå
å«ç©ºæ Œæä»»äœå
¶ä»å笊ã ', + alphanum: 'è«èŒžå
¥AZ ç26 å忝æ0-9 ç10 åæžåïŒäžèœå
å«ç©ºæ Œæä»»äœå
¶ä»å笊ã ', + dateSuchAs: 'è«èŒžå
¥åæ³çæ¥ææ ŒåŒïŒåŠïŒ{date}ã ', + dateInFormatMDY: 'è«èŒžå
¥åæ³çæ¥ææ ŒåŒïŒäŸåŠïŒYYYY-MM-DD ("2010-12-31")ã ', + email: 'è«èŒžå
¥åæ³çé»åä¿¡ç®±å°åïŒäŸåŠïŒ"fred@domain.com"ã ', + url: 'è«èŒžå
¥åæ³çUrl å°åïŒäŸåŠïŒhttp://www.example.comã ', + currencyDollar: 'è«èŒžå
¥åæ³ç貚幣笊èïŒäŸåŠïŒï¿¥100.0', + oneRequired: 'è«è³å°éžæäžé
ã ', + errorPrefix: 'é¯èª€ïŒ', + warningPrefix: 'èŠåïŒ', + + // Form.Validator.Extras + noSpace: 'äžèœå
å«ç©ºæ Œã ', + reqChkByNode: 'æªéžæä»»äœå
§å®¹ã ', + requiredChk: 'æ€é
å¿
å¡«ã ', + reqChkByName: 'è«éžæ {label}.', + match: 'å¿
é è{matchName}çžå¹é
', + startDate: 'èµ·å§æ¥æ', + endDate: 'çµææ¥æ', + currentDate: 'ç¶åæ¥æ', + afterDate: 'æ¥æå¿
é çæŒæææŒ{label}.', + beforeDate: 'æ¥æå¿
é æ©æŒæçæŒ{label}.', + startMonth: 'è«éžæèµ·å§æä»œ', + sameMonth: 'æšå¿
é ä¿®æ¹å
©åæ¥æäžçäžåïŒä»¥ç¢ºä¿å®ååšåäžæä»œã ', + creditcard: 'æšèŒžå
¥çä¿¡çšå¡èç¢Œäžæ£ç¢ºãç¶å已茞å
¥{length}åå笊ã ' + +}); + +Form.Validator.add('validate-currency-yuan', { + + errorMsg: function(){ + return Form.Validator.getMsg('currencyYuan'); + }, + + test: function(element){ + // [ï¿¥]1[##][,###]+[.##] + // [ï¿¥]1###+[.##] + // [ï¿¥]0.## + // [ï¿¥].## + return Form.Validator.getValidator('IsEmpty').test(element) || (/^ï¿¥?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(element.get('value')); + } + +}); + +/* +--- + +name: Locale.zh-CH.Number + +description: Number messages for for Chinese (simplified and traditional). + +license: MIT-style license + +authors: + - YMind Chan + +requires: + - Locale + - Locale.en-US.Number + +provides: [Locale.zh-CH.Number] + +... +*/ + +// Simplified Chinese +Locale.define('zh-CHS', 'Number', { + + currency: { + prefix: 'ï¿¥ ' + } + +}).inherit('en-US', 'Number'); + +// Traditional Chinese +Locale.define('zh-CHT').inherit('zh-CHS', 'Number'); + +/* +--- + +script: Request.JSONP.js + +name: Request.JSONP + +description: Defines Request.JSONP, a class for cross domain javascript via script injection. + +license: MIT-style license + +authors: + - Aaron Newton + - Guillermo Rauch + - Arian Stolwijk + +requires: + - Core/Element + - Core/Request + - MooTools.More + +provides: [Request.JSONP] + +... +*/ + +Request.JSONP = new Class({ + + Implements: [Chain, Events, Options], + + options: {/* + onRequest: function(src, scriptElement){}, + onComplete: function(data){}, + onSuccess: function(data){}, + onCancel: function(){}, + onTimeout: function(){}, + onError: function(){}, */ + onRequest: function(src){ + if (this.options.log && window.console && console.log){ + console.log('JSONP retrieving script with url:' + src); + } + }, + onError: function(src){ + if (this.options.log && window.console && console.warn){ + console.warn('JSONP '+ src +' will fail in Internet Explorer, which enforces a 2083 bytes length limit on URIs'); + } + }, + url: '', + callbackKey: 'callback', + injectScript: document.head, + data: '', + link: 'ignore', + timeout: 0, + log: false + }, + + initialize: function(options){ + this.setOptions(options); + }, + + send: function(options){ + if (!Request.prototype.check.call(this, options)) return this; + this.running = true; + + var type = typeOf(options); + if (type == 'string' || type == 'element') options = {data: options}; + options = Object.merge(this.options, options || {}); + + var data = options.data; + switch (typeOf(data)){ + case 'element': data = document.id(data).toQueryString(); break; + case 'object': case 'hash': data = Object.toQueryString(data); + } + + var index = this.index = Request.JSONP.counter++; + + var src = options.url + + (options.url.test('\\?') ? '&' :'?') + + (options.callbackKey) + + '=Request.JSONP.request_map.request_'+ index + + (data ? '&' + data : ''); + + if (src.length > 2083) this.fireEvent('error', src); + + Request.JSONP.request_map['request_' + index] = function(){ + this.success(arguments, index); + }.bind(this); + + var script = this.getScript(src).inject(options.injectScript); + this.fireEvent('request', [src, script]); + + if (options.timeout) this.timeout.delay(options.timeout, this); + + return this; + }, + + getScript: function(src){ + if (!this.script) this.script = new Element('script', { + type: 'text/javascript', + async: true, + src: src + }); + return this.script; + }, + + success: function(args, index){ + if (!this.running) return; + this.clear() + .fireEvent('complete', args).fireEvent('success', args) + .callChain(); + }, + + cancel: function(){ + if (this.running) this.clear().fireEvent('cancel'); + return this; + }, + + isRunning: function(){ + return !!this.running; + }, + + clear: function(){ + this.running = false; + if (this.script){ + this.script.destroy(); + this.script = null; + } + return this; + }, + + timeout: function(){ + if (this.running){ + this.running = false; + this.fireEvent('timeout', [this.script.get('src'), this.script]).fireEvent('failure').cancel(); + } + return this; + } + +}); + +Request.JSONP.counter = 0; +Request.JSONP.request_map = {}; + +/* +--- + +script: Request.Periodical.js + +name: Request.Periodical + +description: Requests the same URL to pull data from a server but increases the intervals if no data is returned to reduce the load + +license: MIT-style license + +authors: + - Christoph Pojer + +requires: + - Core/Request + - MooTools.More + +provides: [Request.Periodical] + +... +*/ + +Request.implement({ + + options: { + initialDelay: 5000, + delay: 5000, + limit: 60000 + }, + + startTimer: function(data){ + var fn = function(){ + if (!this.running) this.send({data: data}); + }; + this.lastDelay = this.options.initialDelay; + this.timer = fn.delay(this.lastDelay, this); + this.completeCheck = function(response){ + clearTimeout(this.timer); + this.lastDelay = (response) ? this.options.delay : (this.lastDelay + this.options.delay).min(this.options.limit); + this.timer = fn.delay(this.lastDelay, this); + }; + return this.addEvent('complete', this.completeCheck); + }, + + stopTimer: function(){ + clearTimeout(this.timer); + return this.removeEvent('complete', this.completeCheck); + } + +}); + +/* +--- + +script: Request.Queue.js + +name: Request.Queue + +description: Controls several instances of Request and its variants to run only one request at a time. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Element + - Core/Request + - Class.Binds + +provides: [Request.Queue] + +... +*/ + +Request.Queue = new Class({ + + Implements: [Options, Events], + + Binds: ['attach', 'request', 'complete', 'cancel', 'success', 'failure', 'exception'], + + options: {/* + onRequest: function(argsPassedToOnRequest){}, + onSuccess: function(argsPassedToOnSuccess){}, + onComplete: function(argsPassedToOnComplete){}, + onCancel: function(argsPassedToOnCancel){}, + onException: function(argsPassedToOnException){}, + onFailure: function(argsPassedToOnFailure){}, + onEnd: function(){}, + */ + stopOnFailure: true, + autoAdvance: true, + concurrent: 1, + requests: {} + }, + + initialize: function(options){ + var requests; + if (options){ + requests = options.requests; + delete options.requests; + } + this.setOptions(options); + this.requests = {}; + this.queue = []; + this.reqBinders = {}; + + if (requests) this.addRequests(requests); + }, + + addRequest: function(name, request){ + this.requests[name] = request; + this.attach(name, request); + return this; + }, + + addRequests: function(obj){ + Object.each(obj, function(req, name){ + this.addRequest(name, req); + }, this); + return this; + }, + + getName: function(req){ + return Object.keyOf(this.requests, req); + }, + + attach: function(name, req){ + if (req._groupSend) return this; + ['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){ + if (!this.reqBinders[name]) this.reqBinders[name] = {}; + this.reqBinders[name][evt] = function(){ + this['on' + evt.capitalize()].apply(this, [name, req].append(arguments)); + }.bind(this); + req.addEvent(evt, this.reqBinders[name][evt]); + }, this); + req._groupSend = req.send; + req.send = function(options){ + this.send(name, options); + return req; + }.bind(this); + return this; + }, + + removeRequest: function(req){ + var name = typeOf(req) == 'object' ? this.getName(req) : req; + if (!name && typeOf(name) != 'string') return this; + req = this.requests[name]; + if (!req) return this; + ['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){ + req.removeEvent(evt, this.reqBinders[name][evt]); + }, this); + req.send = req._groupSend; + delete req._groupSend; + return this; + }, + + getRunning: function(){ + return Object.filter(this.requests, function(r){ + return r.running; + }); + }, + + isRunning: function(){ + return !!(Object.keys(this.getRunning()).length); + }, + + send: function(name, options){ + var q = function(){ + this.requests[name]._groupSend(options); + this.queue.erase(q); + }.bind(this); + + q.name = name; + if (Object.keys(this.getRunning()).length >= this.options.concurrent || (this.error && this.options.stopOnFailure)) this.queue.push(q); + else q(); + return this; + }, + + hasNext: function(name){ + return (!name) ? !!this.queue.length : !!this.queue.filter(function(q){ return q.name == name; }).length; + }, + + resume: function(){ + this.error = false; + (this.options.concurrent - Object.keys(this.getRunning()).length).times(this.runNext, this); + return this; + }, + + runNext: function(name){ + if (!this.queue.length) return this; + if (!name){ + this.queue[0](); + } else { + var found; + this.queue.each(function(q){ + if (!found && q.name == name){ + found = true; + q(); + } + }); + } + return this; + }, + + runAll: function(){ + this.queue.each(function(q){ + q(); + }); + return this; + }, + + clear: function(name){ + if (!name){ + this.queue.empty(); + } else { + this.queue = this.queue.map(function(q){ + if (q.name != name) return q; + else return false; + }).filter(function(q){ + return q; + }); + } + return this; + }, + + cancel: function(name){ + this.requests[name].cancel(); + return this; + }, + + onRequest: function(){ + this.fireEvent('request', arguments); + }, + + onComplete: function(){ + this.fireEvent('complete', arguments); + if (!this.queue.length) this.fireEvent('end'); + }, + + onCancel: function(){ + if (this.options.autoAdvance && !this.error) this.runNext(); + this.fireEvent('cancel', arguments); + }, + + onSuccess: function(){ + if (this.options.autoAdvance && !this.error) this.runNext(); + this.fireEvent('success', arguments); + }, + + onFailure: function(){ + this.error = true; + if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext(); + this.fireEvent('failure', arguments); + }, + + onException: function(){ + this.error = true; + if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext(); + this.fireEvent('exception', arguments); + } + +}); + +/* +--- + +script: Array.Extras.js + +name: Array.Extras + +description: Extends the Array native object to include useful methods to work with arrays. + +license: MIT-style license + +authors: + - Christoph Pojer + - Sebastian MarkbÃ¥ge + +requires: + - Core/Array + - MooTools.More + +provides: [Array.Extras] + +... +*/ + +(function(nil){ + +Array.implement({ + + min: function(){ + return Math.min.apply(null, this); + }, + + max: function(){ + return Math.max.apply(null, this); + }, + + average: function(){ + return this.length ? this.sum() / this.length : 0; + }, + + sum: function(){ + var result = 0, l = this.length; + if (l){ + while (l--){ + if (this[l] != null) result += parseFloat(this[l]); + } + } + return result; + }, + + unique: function(){ + return [].combine(this); + }, + + shuffle: function(){ + for (var i = this.length; i && --i;){ + var temp = this[i], r = Math.floor(Math.random() * ( i + 1 )); + this[i] = this[r]; + this[r] = temp; + } + return this; + }, + + reduce: function(fn, value){ + for (var i = 0, l = this.length; i < l; i++){ + if (i in this) value = value === nil ? this[i] : fn.call(null, value, this[i], i, this); + } + return value; + }, + + reduceRight: function(fn, value){ + var i = this.length; + while (i--){ + if (i in this) value = value === nil ? this[i] : fn.call(null, value, this[i], i, this); + } + return value; + }, + + pluck: function(prop){ + return this.map(function(item){ + return item[prop]; + }); + } + +}); + +})(); + +/* +--- + +script: Date.Extras.js + +name: Date.Extras + +description: Extends the Date native object to include extra methods (on top of those in Date.js). + +license: MIT-style license + +authors: + - Aaron Newton + - Scott Kyle + +requires: + - Date + +provides: [Date.Extras] + +... +*/ + +Date.implement({ + + timeDiffInWords: function(to){ + return Date.distanceOfTimeInWords(this, to || new Date); + }, + + timeDiff: function(to, separator){ + if (to == null) to = new Date; + var delta = ((to - this) / 1000).floor().abs(); + + var vals = [], + durations = [60, 60, 24, 365, 0], + names = ['s', 'm', 'h', 'd', 'y'], + value, duration; + + for (var item = 0; item < durations.length; item++){ + if (item && !delta) break; + value = delta; + if ((duration = durations[item])){ + value = (delta % duration); + delta = (delta / duration).floor(); + } + vals.unshift(value + (names[item] || '')); + } + + return vals.join(separator || ':'); + } + +}).extend({ + + distanceOfTimeInWords: function(from, to){ + return Date.getTimePhrase(((to - from) / 1000).toInt()); + }, + + getTimePhrase: function(delta){ + var suffix = (delta < 0) ? 'Until' : 'Ago'; + if (delta < 0) delta *= -1; + + var units = { + minute: 60, + hour: 60, + day: 24, + week: 7, + month: 52 / 12, + year: 12, + eon: Infinity + }; + + var msg = 'lessThanMinute'; + + for (var unit in units){ + var interval = units[unit]; + if (delta < 1.5 * interval){ + if (delta > 0.75 * interval) msg = unit; + break; + } + delta /= interval; + msg = unit + 's'; + } + + delta = delta.round(); + return Date.getMsg(msg + suffix, delta).substitute({delta: delta}); + } + +}).defineParsers( + + { + // "today", "tomorrow", "yesterday" + re: /^(?:tod|tom|yes)/i, + handler: function(bits){ + var d = new Date().clearTime(); + switch (bits[0]){ + case 'tom': return d.increment(); + case 'yes': return d.decrement(); + default: return d; + } + } + }, + + { + // "next Wednesday", "last Thursday" + re: /^(next|last) ([a-z]+)$/i, + handler: function(bits){ + var d = new Date().clearTime(); + var day = d.getDay(); + var newDay = Date.parseDay(bits[2], true); + var addDays = newDay - day; + if (newDay <= day) addDays += 7; + if (bits[1] == 'last') addDays -= 7; + return d.set('date', d.getDate() + addDays); + } + } + +).alias('timeAgoInWords', 'timeDiffInWords'); + +/* +--- + +name: Hash + +description: Contains Hash Prototypes. Provides a means for overcoming the JavaScript practical impossibility of extending native Objects. + +license: MIT-style license. + +requires: + - Core/Object + - MooTools.More + +provides: [Hash] + +... +*/ + +(function(){ + +if (this.Hash) return; + +var Hash = this.Hash = new Type('Hash', function(object){ + if (typeOf(object) == 'hash') object = Object.clone(object.getClean()); + for (var key in object) this[key] = object[key]; + return this; +}); + +this.$H = function(object){ + return new Hash(object); +}; + +Hash.implement({ + + forEach: function(fn, bind){ + Object.forEach(this, fn, bind); + }, + + getClean: function(){ + var clean = {}; + for (var key in this){ + if (this.hasOwnProperty(key)) clean[key] = this[key]; + } + return clean; + }, + + getLength: function(){ + var length = 0; + for (var key in this){ + if (this.hasOwnProperty(key)) length++; + } + return length; + } + +}); + +Hash.alias('each', 'forEach'); + +Hash.implement({ + + has: Object.prototype.hasOwnProperty, + + keyOf: function(value){ + return Object.keyOf(this, value); + }, + + hasValue: function(value){ + return Object.contains(this, value); + }, + + extend: function(properties){ + Hash.each(properties || {}, function(value, key){ + Hash.set(this, key, value); + }, this); + return this; + }, + + combine: function(properties){ + Hash.each(properties || {}, function(value, key){ + Hash.include(this, key, value); + }, this); + return this; + }, + + erase: function(key){ + if (this.hasOwnProperty(key)) delete this[key]; + return this; + }, + + get: function(key){ + return (this.hasOwnProperty(key)) ? this[key] : null; + }, + + set: function(key, value){ + if (!this[key] || this.hasOwnProperty(key)) this[key] = value; + return this; + }, + + empty: function(){ + Hash.each(this, function(value, key){ + delete this[key]; + }, this); + return this; + }, + + include: function(key, value){ + if (this[key] == undefined) this[key] = value; + return this; + }, + + map: function(fn, bind){ + return new Hash(Object.map(this, fn, bind)); + }, + + filter: function(fn, bind){ + return new Hash(Object.filter(this, fn, bind)); + }, + + every: function(fn, bind){ + return Object.every(this, fn, bind); + }, + + some: function(fn, bind){ + return Object.some(this, fn, bind); + }, + + getKeys: function(){ + return Object.keys(this); + }, + + getValues: function(){ + return Object.values(this); + }, + + toQueryString: function(base){ + return Object.toQueryString(this, base); + } + +}); + +Hash.alias({indexOf: 'keyOf', contains: 'hasValue'}); + + +})(); + + +/* +--- + +script: Hash.Extras.js + +name: Hash.Extras + +description: Extends the Hash Type to include getFromPath which allows a path notation to child elements. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Hash + - Object.Extras + +provides: [Hash.Extras] + +... +*/ + +Hash.implement({ + + getFromPath: function(notation){ + return Object.getFromPath(this, notation); + }, + + cleanValues: function(method){ + return new Hash(Object.cleanValues(this, method)); + }, + + run: function(){ + Object.run(arguments); + } + +}); + +/* +--- +name: Number.Format +description: Extends the Number Type object to include a number formatting method. +license: MIT-style license +authors: [Arian Stolwijk] +requires: [Core/Number, Locale.en-US.Number] +# Number.Extras is for compatibility +provides: [Number.Format, Number.Extras] +... +*/ + + +Number.implement({ + + format: function(options){ + // Thanks dojo and YUI for some inspiration + var value = this; + options = options ? Object.clone(options) : {}; + var getOption = function(key){ + if (options[key] != null) return options[key]; + return Locale.get('Number.' + key); + }; + + var negative = value < 0, + decimal = getOption('decimal'), + precision = getOption('precision'), + group = getOption('group'), + decimals = getOption('decimals'); + + if (negative){ + var negativeLocale = getOption('negative') || {}; + if (negativeLocale.prefix == null && negativeLocale.suffix == null) negativeLocale.prefix = '-'; + ['prefix', 'suffix'].each(function(key){ + if (negativeLocale[key]) options[key] = getOption(key) + negativeLocale[key]; + }); + + value = -value; + } + + var prefix = getOption('prefix'), + suffix = getOption('suffix'); + + if (decimals !== '' && decimals >= 0 && decimals <= 20) value = value.toFixed(decimals); + if (precision >= 1 && precision <= 21) value = (+value).toPrecision(precision); + + value += ''; + var index; + if (getOption('scientific') === false && value.indexOf('e') > -1){ + var match = value.split('e'), + zeros = +match[1]; + value = match[0].replace('.', ''); + + if (zeros < 0){ + zeros = -zeros - 1; + index = match[0].indexOf('.'); + if (index > -1) zeros -= index - 1; + while (zeros--) value = '0' + value; + value = '0.' + value; + } else { + index = match[0].lastIndexOf('.'); + if (index > -1) zeros -= match[0].length - index - 1; + while (zeros--) value += '0'; + } + } + + if (decimal != '.') value = value.replace('.', decimal); + + if (group){ + index = value.lastIndexOf(decimal); + index = (index > -1) ? index : value.length; + var newOutput = value.substring(index), + i = index; + + while (i--){ + if ((index - i - 1) % 3 == 0 && i != (index - 1)) newOutput = group + newOutput; + newOutput = value.charAt(i) + newOutput; + } + + value = newOutput; + } + + if (prefix) value = prefix + value; + if (suffix) value += suffix; + + return value; + }, + + formatCurrency: function(decimals){ + var locale = Locale.get('Number.currency') || {}; + if (locale.scientific == null) locale.scientific = false; + locale.decimals = decimals != null ? decimals + : (locale.decimals == null ? 2 : locale.decimals); + + return this.format(locale); + }, + + formatPercentage: function(decimals){ + var locale = Locale.get('Number.percentage') || {}; + if (locale.suffix == null) locale.suffix = '%'; + locale.decimals = decimals != null ? decimals + : (locale.decimals == null ? 2 : locale.decimals); + + return this.format(locale); + } + +}); + +/* +--- + +script: URI.js + +name: URI + +description: Provides methods useful in managing the window location and uris. + +license: MIT-style license + +authors: + - Sebastian MarkbÃ¥ge + - Aaron Newton + +requires: + - Core/Object + - Core/Class + - Core/Class.Extras + - Core/Element + - String.QueryString + +provides: [URI] + +... +*/ + +(function(){ + +var toString = function(){ + return this.get('value'); +}; + +var URI = this.URI = new Class({ + + Implements: Options, + + options: { + /*base: false*/ + }, + + regex: /^(?:(\w+):)?(?:\/\/(?:(?:([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)?(\.\.?$|(?:[^?#\/]*\/)*)([^?#]*)(?:\?([^#]*))?(?:#(.*))?/, + parts: ['scheme', 'user', 'password', 'host', 'port', 'directory', 'file', 'query', 'fragment'], + schemes: {http: 80, https: 443, ftp: 21, rtsp: 554, mms: 1755, file: 0}, + + initialize: function(uri, options){ + this.setOptions(options); + var base = this.options.base || URI.base; + if (!uri) uri = base; + + if (uri && uri.parsed) this.parsed = Object.clone(uri.parsed); + else this.set('value', uri.href || uri.toString(), base ? new URI(base) : false); + }, + + parse: function(value, base){ + var bits = value.match(this.regex); + if (!bits) return false; + bits.shift(); + return this.merge(bits.associate(this.parts), base); + }, + + merge: function(bits, base){ + if ((!bits || !bits.scheme) && (!base || !base.scheme)) return false; + if (base){ + this.parts.every(function(part){ + if (bits[part]) return false; + bits[part] = base[part] || ''; + return true; + }); + } + bits.port = bits.port || this.schemes[bits.scheme.toLowerCase()]; + bits.directory = bits.directory ? this.parseDirectory(bits.directory, base ? base.directory : '') : '/'; + return bits; + }, + + parseDirectory: function(directory, baseDirectory){ + directory = (directory.substr(0, 1) == '/' ? '' : (baseDirectory || '/')) + directory; + if (!directory.test(URI.regs.directoryDot)) return directory; + var result = []; + directory.replace(URI.regs.endSlash, '').split('/').each(function(dir){ + if (dir == '..' && result.length > 0) result.pop(); + else if (dir != '.') result.push(dir); + }); + return result.join('/') + '/'; + }, + + combine: function(bits){ + return bits.value || bits.scheme + '://' + + (bits.user ? bits.user + (bits.password ? ':' + bits.password : '') + '@' : '') + + (bits.host || '') + (bits.port && bits.port != this.schemes[bits.scheme] ? ':' + bits.port : '') + + (bits.directory || '/') + (bits.file || '') + + (bits.query ? '?' + bits.query : '') + + (bits.fragment ? '#' + bits.fragment : ''); + }, + + set: function(part, value, base){ + if (part == 'value'){ + var scheme = value.match(URI.regs.scheme); + if (scheme) scheme = scheme[1]; + if (scheme && this.schemes[scheme.toLowerCase()] == null) this.parsed = { scheme: scheme, value: value }; + else this.parsed = this.parse(value, (base || this).parsed) || (scheme ? { scheme: scheme, value: value } : { value: value }); + } else if (part == 'data'){ + this.setData(value); + } else { + this.parsed[part] = value; + } + return this; + }, + + get: function(part, base){ + switch (part){ + case 'value': return this.combine(this.parsed, base ? base.parsed : false); + case 'data' : return this.getData(); + } + return this.parsed[part] || ''; + }, + + go: function(){ + document.location.href = this.toString(); + }, + + toURI: function(){ + return this; + }, + + getData: function(key, part){ + var qs = this.get(part || 'query'); + if (!(qs || qs === 0)) return key ? null : {}; + var obj = qs.parseQueryString(); + return key ? obj[key] : obj; + }, + + setData: function(values, merge, part){ + if (typeof values == 'string'){ + var data = this.getData(); + data[arguments[0]] = arguments[1]; + values = data; + } else if (merge){ + values = Object.merge(this.getData(null, part), values); + } + return this.set(part || 'query', Object.toQueryString(values)); + }, + + clearData: function(part){ + return this.set(part || 'query', ''); + }, + + toString: toString, + valueOf: toString + +}); + +URI.regs = { + endSlash: /\/$/, + scheme: /^(\w+):/, + directoryDot: /\.\/|\.$/ +}; + +URI.base = new URI(Array.from(document.getElements('base[href]', true)).getLast(), {base: document.location}); + +String.implement({ + + toURI: function(options){ + return new URI(this, options); + } + +}); + +})(); + +/* +--- + +script: URI.Relative.js + +name: URI.Relative + +description: Extends the URI class to add methods for computing relative and absolute urls. + +license: MIT-style license + +authors: + - Sebastian MarkbÃ¥ge + + +requires: + - Class.refactor + - URI + +provides: [URI.Relative] + +... +*/ + +URI = Class.refactor(URI, { + + combine: function(bits, base){ + if (!base || bits.scheme != base.scheme || bits.host != base.host || bits.port != base.port) + return this.previous.apply(this, arguments); + var end = bits.file + (bits.query ? '?' + bits.query : '') + (bits.fragment ? '#' + bits.fragment : ''); + + if (!base.directory) return (bits.directory || (bits.file ? '' : './')) + end; + + var baseDir = base.directory.split('/'), + relDir = bits.directory.split('/'), + path = '', + offset; + + var i = 0; + for (offset = 0; offset < baseDir.length && offset < relDir.length && baseDir[offset] == relDir[offset]; offset++); + for (i = 0; i < baseDir.length - offset - 1; i++) path += '../'; + for (i = offset; i < relDir.length - 1; i++) path += relDir[i] + '/'; + + return (path || (bits.file ? '' : './')) + end; + }, + + toAbsolute: function(base){ + base = new URI(base); + if (base) base.set('directory', '').set('file', ''); + return this.toRelative(base); + }, + + toRelative: function(base){ + return this.get('value', new URI(base)); + } + +}); + +/* +--- + +script: Assets.js + +name: Assets + +description: Provides methods to dynamically load JavaScript, CSS, and Image files into the document. + +license: MIT-style license + +authors: + - Valerio Proietti + +requires: + - Core/Element.Event + - MooTools.More + +provides: [Assets] + +... +*/ + +var Asset = { + + javascript: function(source, properties){ + if (!properties) properties = {}; + + var script = new Element('script', {src: source, type: 'text/javascript'}), + doc = properties.document || document, + load = properties.onload || properties.onLoad; + + delete properties.onload; + delete properties.onLoad; + delete properties.document; + + if (load){ + if (!script.addEventListener){ + script.addEvent('readystatechange', function(){ + if (['loaded', 'complete'].contains(this.readyState)) load.call(this); + }); + } else { + script.addEvent('load', load); + } + } + + return script.set(properties).inject(doc.head); + }, + + css: function(source, properties){ + if (!properties) properties = {}; + + var load = properties.onload || properties.onLoad, + doc = properties.document || document, + timeout = properties.timeout || 3000; + + ['onload', 'onLoad', 'document'].each(function(prop){ + delete properties[prop]; + }); + + var link = new Element('link', { + type: 'text/css', + rel: 'stylesheet', + media: 'screen', + href: source + }).setProperties(properties).inject(doc.head); + + if (load){ + // based on article at http://www.yearofmoo.com/2011/03/cross-browser-stylesheet-preloading.html + var loaded = false, retries = 0; + var check = function(){ + var stylesheets = document.styleSheets; + for (var i = 0; i < stylesheets.length; i++){ + var file = stylesheets[i]; + var owner = file.ownerNode ? file.ownerNode : file.owningElement; + if (owner && owner == link){ + loaded = true; + return load.call(link); + } + } + retries++; + if (!loaded && retries < timeout / 50) return setTimeout(check, 50); + } + setTimeout(check, 0); + } + return link; + }, + + image: function(source, properties){ + if (!properties) properties = {}; + + var image = new Image(), + element = document.id(image) || new Element('img'); + + ['load', 'abort', 'error'].each(function(name){ + var type = 'on' + name, + cap = 'on' + name.capitalize(), + event = properties[type] || properties[cap] || function(){}; + + delete properties[cap]; + delete properties[type]; + + image[type] = function(){ + if (!image) return; + if (!element.parentNode){ + element.width = image.width; + element.height = image.height; + } + image = image.onload = image.onabort = image.onerror = null; + event.delay(1, element, element); + element.fireEvent(name, element, 1); + }; + }); + + image.src = element.src = source; + if (image && image.complete) image.onload.delay(1); + return element.set(properties); + }, + + images: function(sources, options){ + sources = Array.from(sources); + + var fn = function(){}, + counter = 0; + + options = Object.merge({ + onComplete: fn, + onProgress: fn, + onError: fn, + properties: {} + }, options); + + return new Elements(sources.map(function(source, index){ + return Asset.image(source, Object.append(options.properties, { + onload: function(){ + counter++; + options.onProgress.call(this, counter, index, source); + if (counter == sources.length) options.onComplete(); + }, + onerror: function(){ + counter++; + options.onError.call(this, counter, index, source); + if (counter == sources.length) options.onComplete(); + } + })); + })); + } + +}; + +/* +--- + +script: Color.js + +name: Color + +description: Class for creating and manipulating colors in JavaScript. Supports HSB -> RGB Conversions and vice versa. + +license: MIT-style license + +authors: + - Valerio Proietti + +requires: + - Core/Array + - Core/String + - Core/Number + - Core/Hash + - Core/Function + - MooTools.More + +provides: [Color] + +... +*/ + +(function(){ + +var Color = this.Color = new Type('Color', function(color, type){ + if (arguments.length >= 3){ + type = 'rgb'; color = Array.slice(arguments, 0, 3); + } else if (typeof color == 'string'){ + if (color.match(/rgb/)) color = color.rgbToHex().hexToRgb(true); + else if (color.match(/hsb/)) color = color.hsbToRgb(); + else color = color.hexToRgb(true); + } + type = type || 'rgb'; + switch (type){ + case 'hsb': + var old = color; + color = color.hsbToRgb(); + color.hsb = old; + break; + case 'hex': color = color.hexToRgb(true); break; + } + color.rgb = color.slice(0, 3); + color.hsb = color.hsb || color.rgbToHsb(); + color.hex = color.rgbToHex(); + return Object.append(color, this); +}); + +Color.implement({ + + mix: function(){ + var colors = Array.slice(arguments); + var alpha = (typeOf(colors.getLast()) == 'number') ? colors.pop() : 50; + var rgb = this.slice(); + colors.each(function(color){ + color = new Color(color); + for (var i = 0; i < 3; i++) rgb[i] = Math.round((rgb[i] / 100 * (100 - alpha)) + (color[i] / 100 * alpha)); + }); + return new Color(rgb, 'rgb'); + }, + + invert: function(){ + return new Color(this.map(function(value){ + return 255 - value; + })); + }, + + setHue: function(value){ + return new Color([value, this.hsb[1], this.hsb[2]], 'hsb'); + }, + + setSaturation: function(percent){ + return new Color([this.hsb[0], percent, this.hsb[2]], 'hsb'); + }, + + setBrightness: function(percent){ + return new Color([this.hsb[0], this.hsb[1], percent], 'hsb'); + } + +}); + +this.$RGB = function(r, g, b){ + return new Color([r, g, b], 'rgb'); +}; + +this.$HSB = function(h, s, b){ + return new Color([h, s, b], 'hsb'); +}; + +this.$HEX = function(hex){ + return new Color(hex, 'hex'); +}; + +Array.implement({ + + rgbToHsb: function(){ + var red = this[0], + green = this[1], + blue = this[2], + hue = 0; + var max = Math.max(red, green, blue), + min = Math.min(red, green, blue); + var delta = max - min; + var brightness = max / 255, + saturation = (max != 0) ? delta / max : 0; + if (saturation != 0){ + var rr = (max - red) / delta; + var gr = (max - green) / delta; + var br = (max - blue) / delta; + if (red == max) hue = br - gr; + else if (green == max) hue = 2 + rr - br; + else hue = 4 + gr - rr; + hue /= 6; + if (hue < 0) hue++; + } + return [Math.round(hue * 360), Math.round(saturation * 100), Math.round(brightness * 100)]; + }, + + hsbToRgb: function(){ + var br = Math.round(this[2] / 100 * 255); + if (this[1] == 0){ + return [br, br, br]; + } else { + var hue = this[0] % 360; + var f = hue % 60; + var p = Math.round((this[2] * (100 - this[1])) / 10000 * 255); + var q = Math.round((this[2] * (6000 - this[1] * f)) / 600000 * 255); + var t = Math.round((this[2] * (6000 - this[1] * (60 - f))) / 600000 * 255); + switch (Math.floor(hue / 60)){ + case 0: return [br, t, p]; + case 1: return [q, br, p]; + case 2: return [p, br, t]; + case 3: return [p, q, br]; + case 4: return [t, p, br]; + case 5: return [br, p, q]; + } + } + return false; + } + +}); + +String.implement({ + + rgbToHsb: function(){ + var rgb = this.match(/\d{1,3}/g); + return (rgb) ? rgb.rgbToHsb() : null; + }, + + hsbToRgb: function(){ + var hsb = this.match(/\d{1,3}/g); + return (hsb) ? hsb.hsbToRgb() : null; + } + +}); + +})(); + + +/* +--- + +script: Group.js + +name: Group + +description: Class for monitoring collections of events + +license: MIT-style license + +authors: + - Valerio Proietti + +requires: + - Core/Events + - MooTools.More + +provides: [Group] + +... +*/ + +(function(){ + +this.Group = new Class({ + + initialize: function(){ + this.instances = Array.flatten(arguments); + }, + + addEvent: function(type, fn){ + var instances = this.instances, + len = instances.length, + togo = len, + args = new Array(len), + self = this; + + instances.each(function(instance, i){ + instance.addEvent(type, function(){ + if (!args[i]) togo--; + args[i] = arguments; + if (!togo){ + fn.call(self, instances, instance, args); + togo = len; + args = new Array(len); + } + }); + }); + } + +}); + +})(); + +/* +--- + +script: Hash.Cookie.js + +name: Hash.Cookie + +description: Class for creating, reading, and deleting Cookies in JSON format. + +license: MIT-style license + +authors: + - Valerio Proietti + - Aaron Newton + +requires: + - Core/Cookie + - Core/JSON + - MooTools.More + - Hash + +provides: [Hash.Cookie] + +... +*/ + +Hash.Cookie = new Class({ + + Extends: Cookie, + + options: { + autoSave: true + }, + + initialize: function(name, options){ + this.parent(name, options); + this.load(); + }, + + save: function(){ + var value = JSON.encode(this.hash); + if (!value || value.length > 4096) return false; //cookie would be truncated! + if (value == '{}') this.dispose(); + else this.write(value); + return true; + }, + + load: function(){ + this.hash = new Hash(JSON.decode(this.read(), true)); + return this; + } + +}); + +Hash.each(Hash.prototype, function(method, name){ + if (typeof method == 'function') Hash.Cookie.implement(name, function(){ + var value = method.apply(this.hash, arguments); + if (this.options.autoSave) this.save(); + return value; + }); +}); + +/* +--- + +name: Swiff + +description: Wrapper for embedding SWF movies. Supports External Interface Communication. + +license: MIT-style license. + +credits: + - Flash detection & Internet Explorer + Flash Player 9 fix inspired by SWFObject. + +requires: [Core/Options, Core/Object, Core/Element] + +provides: Swiff + +... +*/ + +(function(){ + +var Swiff = this.Swiff = new Class({ + + Implements: Options, + + options: { + id: null, + height: 1, + width: 1, + container: null, + properties: {}, + params: { + quality: 'high', + allowScriptAccess: 'always', + wMode: 'window', + swLiveConnect: true + }, + callBacks: {}, + vars: {} + }, + + toElement: function(){ + return this.object; + }, + + initialize: function(path, options){ + this.instance = 'Swiff_' + String.uniqueID(); + + this.setOptions(options); + options = this.options; + var id = this.id = options.id || this.instance; + var container = document.id(options.container); + + Swiff.CallBacks[this.instance] = {}; + + var params = options.params, vars = options.vars, callBacks = options.callBacks; + var properties = Object.append({height: options.height, width: options.width}, options.properties); + + var self = this; + + for (var callBack in callBacks){ + Swiff.CallBacks[this.instance][callBack] = (function(option){ + return function(){ + return option.apply(self.object, arguments); + }; + })(callBacks[callBack]); + vars[callBack] = 'Swiff.CallBacks.' + this.instance + '.' + callBack; + } + + params.flashVars = Object.toQueryString(vars); + if ('ActiveXObject' in window){ + properties.classid = 'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000'; + params.movie = path; + } else { + properties.type = 'application/x-shockwave-flash'; + } + properties.data = path; + + var build = '<object id="' + id + '"'; + for (var property in properties) build += ' ' + property + '="' + properties[property] + '"'; + build += '>'; + for (var param in params){ + if (params[param]) build += '<param name="' + param + '" value="' + params[param] + '" />'; + } + build += '</object>'; + this.object = ((container) ? container.empty() : new Element('div')).set('html', build).firstChild; + }, + + replaces: function(element){ + element = document.id(element, true); + element.parentNode.replaceChild(this.toElement(), element); + return this; + }, + + inject: function(element){ + document.id(element, true).appendChild(this.toElement()); + return this; + }, + + remote: function(){ + return Swiff.remote.apply(Swiff, [this.toElement()].append(arguments)); + } + +}); + +Swiff.CallBacks = {}; + +Swiff.remote = function(obj, fn){ + var rs = obj.CallFunction('<invoke name="' + fn + '" returntype="javascript">' + __flash__argumentsToXML(arguments, 2) + '</invoke>'); + return eval(rs); +}; + +})(); + +/* +--- +name: Table +description: LUA-Style table implementation. +license: MIT-style license +authors: + - Valerio Proietti +requires: [Core/Array] +provides: [Table] +... +*/ + +(function(){ + +var Table = this.Table = function(){ + + this.length = 0; + var keys = [], + values = []; + + this.set = function(key, value){ + var index = keys.indexOf(key); + if (index == -1){ + var length = keys.length; + keys[length] = key; + values[length] = value; + this.length++; + } else { + values[index] = value; + } + return this; + }; + + this.get = function(key){ + var index = keys.indexOf(key); + return (index == -1) ? null : values[index]; + }; + + this.erase = function(key){ + var index = keys.indexOf(key); + if (index != -1){ + this.length--; + keys.splice(index, 1); + return values.splice(index, 1)[0]; + } + return null; + }; + + this.each = this.forEach = function(fn, bind){ + for (var i = 0, l = this.length; i < l; i++) fn.call(bind, keys[i], values[i], this); + }; + +}; + +if (this.Type) new Type('Table', Table); + +})(); diff --git a/pyload/webui/themes/Flat/lib/MooTools/Purr/purr.js b/pyload/webui/themes/Flat/lib/MooTools/Purr/purr.js new file mode 100644 index 000000000..9cbc503d9 --- /dev/null +++ b/pyload/webui/themes/Flat/lib/MooTools/Purr/purr.js @@ -0,0 +1,309 @@ +/* +--- +script: purr.js + +description: Class to create growl-style popup notifications. + +license: MIT-style + +authors: [atom smith] + +requires: +- core/1.3: [Core, Browser, Array, Function, Number, String, Hash, Event, Class.Extras, Element.Event, Element.Style, Element.Dimensions, Fx.CSS, FX.Tween, Fx.Morph] + +provides: [Purr, Element.alert] +... +*/ + + +var Purr = new Class({ + + 'options': { + 'mode': 'top', + 'position': 'left', + 'elementAlertClass': 'purr-element-alert', + 'elements': { + 'wrapper': 'div', + 'alert': 'div', + 'buttonWrapper': 'div', + 'button': 'button' + }, + 'elementOptions': { + 'wrapper': { + 'styles': { + 'position': 'fixed', + 'z-index': '9999' + }, + 'class': 'purr-wrapper' + }, + 'alert': { + 'class': 'purr-alert', + 'styles': { + 'opacity': '.85' + } + }, + 'buttonWrapper': { + 'class': 'purr-button-wrapper' + }, + 'button': { + 'class': 'purr-button' + } + }, + 'alert': { + 'buttons': [], + 'clickDismiss': true, + 'hoverWait': true, + 'hideAfter': 5000, + 'fx': { + 'duration': 500 + }, + 'highlight': false, + 'highlightRepeat': false, + 'highlight': { + 'start': '#FF0', + 'end': false + } + } + }, + + 'Implements': [Options, Events, Chain], + + 'initialize': function(options){ + this.setOptions(options); + this.createWrapper(); + return this; + }, + + 'bindAlert': function(){ + return this.alert.bind(this); + }, + + 'createWrapper': function(){ + this.wrapper = new Element(this.options.elements.wrapper, this.options.elementOptions.wrapper); + if(this.options.mode == 'top') + { + this.wrapper.setStyle('top', 0); + } + else + { + this.wrapper.setStyle('bottom', 0); + } + document.id(document.body).grab(this.wrapper); + this.positionWrapper(this.options.position); + }, + + 'positionWrapper': function(position){ + if(typeOf(position) == 'object') + { + + var wrapperCoords = this.getWrapperCoords(); + + this.wrapper.setStyles({ + 'bottom': '', + 'left': position.x, + 'top': position.y - wrapperCoords.height, + 'position': 'absolute' + }); + } + else if(position == 'left') + { + this.wrapper.setStyle('left', 0); + } + else if(position == 'right') + { + this.wrapper.setStyle('right', 0); + } + else + { + this.wrapper.setStyle('left', (window.innerWidth / 2) - (this.getWrapperCoords().width / 2)); + } + return this; + }, + + 'getWrapperCoords': function(){ + this.wrapper.setStyle('visibility', 'hidden'); + var measurer = this.alert('need something in here to measure'); + var coords = this.wrapper.getCoordinates(); + measurer.destroy(); + this.wrapper.setStyle('visibility',''); + return coords; + }, + + 'alert': function(msg, options){ + + options = Object.merge({}, this.options.alert, options || {}); + + var alert = new Element(this.options.elements.alert, this.options.elementOptions.alert); + + if(typeOf(msg) == 'string') + { + alert.set('html', msg); + } + else if(typeOf(msg) == 'element') + { + alert.grab(msg); + } + else if(typeOf(msg) == 'array') + { + var alerts = []; + msg.each(function(m){ + alerts.push(this.alert(m, options)); + }, this); + return alerts; + } + + alert.store('options', options); + + if(options.buttons.length > 0) + { + options.clickDismiss = false; + options.hideAfter = false; + options.hoverWait = false; + var buttonWrapper = new Element(this.options.elements.buttonWrapper, this.options.elementOptions.buttonWrapper); + alert.grab(buttonWrapper); + options.buttons.each(function(button){ + if(button.text != undefined) + { + var callbackButton = new Element(this.options.elements.button, this.options.elementOptions.button); + callbackButton.set('html', button.text); + if(button.callback != undefined) + { + callbackButton.addEvent('click', button.callback.pass(alert)); + } + if(button.dismiss != undefined && button.dismiss) + { + callbackButton.addEvent('click', this.dismiss.pass(alert, this)); + } + buttonWrapper.grab(callbackButton); + } + }, this); + } + if(options.className != undefined) + { + alert.addClass(options.className); + } + + this.wrapper.grab(alert, (this.options.mode == 'top') ? 'bottom' : 'top'); + + var fx = Object.merge(this.options.alert.fx, options.fx); + var alertFx = new Fx.Morph(alert, fx); + alert.store('fx', alertFx); + this.fadeIn(alert); + + if(options.highlight) + { + alertFx.addEvent('complete', function(){ + alert.highlight(options.highlight.start, options.highlight.end); + if(options.highlightRepeat) + { + alert.highlight.periodical(options.highlightRepeat, alert, [options.highlight.start, options.highlight.end]); + } + }); + } + if(options.hideAfter) + { + this.dismiss(alert); + } + + if(options.clickDismiss) + { + alert.addEvent('click', function(){ + this.holdUp = false; + this.dismiss(alert, true); + }.bind(this)); + } + + if(options.hoverWait) + { + alert.addEvents({ + 'mouseenter': function(){ + this.holdUp = true; + }.bind(this), + 'mouseleave': function(){ + this.holdUp = false; + }.bind(this) + }); + } + + return alert; + }, + + 'fadeIn': function(alert){ + var alertFx = alert.retrieve('fx'); + alertFx.set({ + 'opacity': 0 + }); + alertFx.start({ + 'opacity': [this.options.elementOptions.alert.styles.opacity, .9].pick(), + }); + }, + + 'dismiss': function(alert, now){ + now = now || false; + var options = alert.retrieve('options'); + if(now) + { + this.fadeOut(alert); + } + else + { + this.fadeOut.delay(options.hideAfter, this, alert); + } + }, + + 'fadeOut': function(alert){ + if(this.holdUp) + { + this.dismiss.delay(100, this, [alert, true]) + return null; + } + var alertFx = alert.retrieve('fx'); + if(!alertFx) + { + return null; + } + var to = { + 'opacity': 0 + } + if(this.options.mode == 'top') + { + to['margin-top'] = '-'+alert.offsetHeight+'px'; + } + else + { + to['margin-bottom'] = '-'+alert.offsetHeight+'px'; + } + alertFx.start(to); + alertFx.addEvent('complete', function(){ + alert.destroy(); + }); + } +}); + +Element.implement({ + + 'alert': function(msg, options){ + var alert = this.retrieve('alert'); + if(!alert) + { + options = options || { + 'mode':'top' + }; + alert = new Purr(options) + this.store('alert', alert); + } + + var coords = this.getCoordinates(); + + alert.alert(msg, options); + + alert.wrapper.setStyles({ + 'bottom': '', + 'left': (coords.left - (alert.wrapper.getWidth() / 2)) + (this.getWidth() / 2), + 'top': coords.top - (alert.wrapper.getHeight()), + 'position': 'absolute' + }); + + } + +});
\ No newline at end of file diff --git a/pyload/webui/themes/Flat/lib/MooTools/TinyTab/tinytab.js b/pyload/webui/themes/Flat/lib/MooTools/TinyTab/tinytab.js new file mode 100644 index 000000000..de50279fc --- /dev/null +++ b/pyload/webui/themes/Flat/lib/MooTools/TinyTab/tinytab.js @@ -0,0 +1,43 @@ +/* +--- +description: TinyTab - Tiny and simple tab handler for Mootools. + +license: MIT-style + +authors: +- Danillo César de O. Melo + +requires: +- core/1.2.4: '*' + +provides: TinyTab + +... +*/ +(function($) { + this.TinyTab = new Class({ + Implements: Events, + initialize: function(tabs, contents, opt) { + this.tabs = tabs; + this.contents = contents; + if(!opt) opt = {}; + this.css = opt.selectedClass || 'selected'; + this.select(this.tabs[0]); + tabs.each(function(el){ + el.addEvent('click',function(e){ + this.select(el); + e.stop(); + }.bind(this)); + }.bind(this)); + }, + + select: function(el) { + this.tabs.removeClass(this.css); + el.addClass(this.css); + this.contents.setStyle('display','none'); + var content = this.contents[this.tabs.indexOf(el)]; + content.setStyle('display','block'); + this.fireEvent('change',[content,el]); + } + }); +})(document.id);
\ No newline at end of file diff --git a/pyload/webui/themes/Flat/tml/admin.html b/pyload/webui/themes/Flat/tml/admin.html new file mode 100644 index 000000000..c5cdb494b --- /dev/null +++ b/pyload/webui/themes/Flat/tml/admin.html @@ -0,0 +1,98 @@ +{% extends '/tml/base.html' %} + +{% block head %} + <script type="text/javascript" src="/js/admin.min.js"></script> +{% endblock %} + + +{% block title %}{{ _("Administrate") }} - {{ super() }} {% endblock %} +{% block subtitle %}{{ _("Administrate") }}{% endblock %} + +{% block content %} + + <a href="#" id="quit-pyload" style="font-size: large; font-weight: bold;">{{_("Quit pyLoad")}}</a> | + <a href="#" id="restart-pyload" style="font-size: large; font-weight: bold;">{{_("Restart pyLoad")}}</a> + <br> + <br> + + {{ _("To add user or change passwords use:") }} <b>python pyload.py -u</b><br> + {{ _("Important: Admin user have always all permissions!") }} + + <form action="" method="POST"> + <table class="settable wide"> + <thead style="font-size: 11px"> + <th> + {{ _("Name") }} + </th> + <th> + {{ _("Change Password") }} + </th> + <th> + {{ _("Admin") }} + </th> + <th> + {{ _("Permissions") }} + </th> + </thead> + + {% for name, data in users.iteritems() %} + <tr> + <td>{{ name }}</td> + <td><a class="change_password" href="#" id="change_pw|{{name}}">{{ _("change") }}</a></td> + <td><input name="{{ name }}|admin" type="checkbox" {% if data.perms.admin %} + checked="True" {% endif %}"></td> + <td> + <select multiple="multiple" size="{{ permlist|length }}" name="{{ name }}|perms"> + {% for perm in permlist %} + {% if data.perms|getitem(perm) %} + <option selected="selected">{{ perm }}</option> + {% else %} + <option>{{ perm }}</option> + {% endif %} + {% endfor %} + </select> + </td> + </tr> + {% endfor %} + + + </table> + + <button class="styled_button" type="submit">{{ _("Submit") }}</button> + </form> +{% endblock %} +{% block hidden %} + <div id="password_box" class="window_box" style="z-index: 2"> + <form id="password_form" action="/json/change_password" method="POST" enctype="multipart/form-data"> + <h1>{{ _("Change Password") }}</h1> + + <p>{{ _("Enter your current and desired Password.") }}</p> + <label for="user_login">{{ _("User") }} + <span class="small">{{ _("Your username.") }}</span> + </label> + <input id="user_login" name="user_login" type="text" size="20"/> + + <label for="login_current_password">{{ _("Current password") }} + <span class="small">{{ _("The password for this account.") }}</span> + </label> + <input id="login_current_password" name="login_current_password" type="password" size="20"/> + + <label for="login_new_password">{{ _("New password") }} + <span class="small">{{ _("The new password.") }}</span> + </label> + <input id="login_new_password" name="login_new_password" type="password" size="20"/> + + <label for="login_new_password2">{{ _("New password (repeat)") }} + <span class="small">{{ _("Please repeat the new password.") }}</span> + </label> + <input id="login_new_password2" name="login_new_password2" type="password" size="20"/> + + + <button id="login_password_button" type="submit">{{ _("Submit") }}</button> + <button id="login_password_reset" style="margin-left: 0" type="reset">{{ _("Reset") }}</button> + <div class="spacer"></div> + + </form> + + </div> +{% endblock %} diff --git a/pyload/webui/themes/Flat/tml/base.html b/pyload/webui/themes/Flat/tml/base.html new file mode 100644 index 000000000..06061e705 --- /dev/null +++ b/pyload/webui/themes/Flat/tml/base.html @@ -0,0 +1,180 @@ +<?xml version="1.0" ?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + +<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> +<link rel="stylesheet" type="text/css" href="/css/base.css"/> +<link rel="stylesheet" type="text/css" href="/css/window.css"/> +<link rel="stylesheet" type="text/css" href="/css/MooDialog.css"/> + +<script type="text/javascript" src="/lib/MooTools/MooTools-Core.js"></script> +<script type="text/javascript" src="/lib/MooTools/MooTools-More.js"></script> +<script type="text/javascript" src="/lib/MooTools/MooDialog/MooDialog.js"></script> +<script type="text/javascript" src="/lib/MooTools/Purr/purr.js"></script> + + +<script type="text/javascript" src="/js/base.min.js"></script> + +<title>{% block title %}pyLoad {{_("Web UI")}}{% endblock %}</title> + +{% block head %} +{% endblock %} +</head> +<body> +<a class="anchor" name="top" id="top"></a> + +<div id="head-panel"> + + + <div id="head-search-and-login"> + {% block headpanel %} + + {% if user.is_authenticated %} + + +{% if update %} +<span> +<span style="font-weight: 300; margin: 0 2px 0 2px;">{{_("pyLoad Update available!")}}</span> +</span> +{% endif %} + + +{% if plugins %} +<span> +<span style="font-weight: 300; margin: 0 2px 0 2px;">{{_("Plugins updated, please restart!")}}</span> +</span> +{% endif %} + +<span id="cap_info" style="display: {% if captcha %}inline{%else%}none{% endif %}"> +<img src="/img/images.png" alt="Captcha:" style="vertical-align:middle; margin:2px" /> +<span style="font-weight: 300; cursor: pointer; margin-right: 2px;">{{_("Captcha waiting")}}</span> +</span> + + <img src="/img/head-login.png" alt="User:" style="vertical-align:middle; margin:2px" /><span style="padding-right: 2px;">{{user.name}}</span> + <ul id="user-actions"> + <li><a href="/logout" class="action logout" rel="nofollow">{{_("Logout")}}</a></li> + {% if user.is_admin %} + <li><a href="/admin" class="action profile" rel="nofollow">{{_("Administrate")}}</a></li> + {% endif %} + <li><a href="/info" class="action info" rel="nofollow">{{_("Info")}}</a></li> + + </ul> +{% else %} + <span style="padding-right: 2px;">{{_("Please Login!")}}</span> +{% endif %} + + {% endblock %} + </div> + + <a href="/"><img id="head-logo" src="/img/pyload-logo.png" alt="pyLoad" /></a> + + <div id="head-menu"> + <ul> + + {% macro selected(name, right=False) -%} + {% if name in url -%}class="{% if right -%}right {% endif %}selected"{%- endif %} + {% if not name in url and right -%}class="right"{%- endif %} + {%- endmacro %} + + + {% block menu %} + <li> + <a href="/" title=""><img src="/img/head-menu-home.png" alt="" /> {{_("Home")}}</a> + </li> + <li {{ selected('queue') }}> + <a href="/queue/" title=""><img src="/img/head-menu-queue.png" alt="" /> {{_("Queue")}}</a> + </li> + <li {{ selected('collector') }}> + <a href="/collector/" title=""><img src="/img/head-menu-collector.png" alt="" /> {{_("Collector")}}</a> + </li> + <li {{ selected('downloads') }}> + <a href="/downloads/" title=""><img src="/img/head-menu-development.png" alt="" /> {{_("Downloads")}}</a> + </li> + <li {{ selected('filemanager') }}> + <a href="/filemanager/" title=""><img src="/img/head-menu-download.png" alt="" /> {{_("FileManager")}}</a> + </li> + <li {{ selected('logs', True) }}> + <a href="/logs/" title=""><img src="/img/head-menu-index.png" alt="" />{{_("Logs")}}</a> + </li> + <li {{ selected('settings', True) }}> + <a href="/settings/" title=""><img src="/img/head-menu-config.png" alt="" />{{_("Settings")}}</a> + </li> + {% endblock %} + + </ul> + </div> + + <div style="clear:both;"></div> +</div> + +{% if perms.STATUS %} +<ul id="page-actions2"> + <li id="action_play"><a href="#" class="action play" accesskey="o" rel="nofollow">{{_("Start")}}</a></li> + <li id="action_stop"><a href="#" class="action stop" accesskey="o" rel="nofollow">{{_("Stop")}}</a></li> + <li id="action_cancel"><a href="#" class="action cancel" accesskey="o" rel="nofollow">{{_("Cancel")}}</a></li> + <li id="action_add"><a href="#" class="action add" accesskey="o" rel="nofollow" >{{_("Add")}}</a></li> +</ul> +{% endif %} + +{% if perms.LIST %} +<ul id="page-actions"> + <li><span class="time">{{_("Download:")}}</span><a id="time" style=" background-color: {% if status.download %}#8ffc25{% else %} #fc6e26{% endif %}; padding-left: 0cm; padding-right: 0.1cm; "> {% if status.download %}{{_("on")}}{% else %}{{_("off")}}{% endif %}</a></li> + <li><span class="reconnect">{{_("Reconnect:")}}</span><a id="reconnect" style=" background-color: {% if status.reconnect %}#8ffc25{% else %} #fc6e26{% endif %}; padding-left: 0cm; padding-right: 0.1cm;color:black; "> {% if status.reconnect %}{{_("on")}}{% else %}{{_("off")}}{% endif %}</a></li> + <li><a class="action backlink">{{_("Speed:")}} <b id="speed">{{ status.speed }}</b></a></li> + <li><a class="action cog">{{_("Active:")}} <b id="aktiv" title="{{_("Active")}}">{{ status.active }}</b> / <b id="aktiv_from" title="{{_("Queued")}}">{{ status.queue }}</b> / <b id="aktiv_total" title="{{_("Total")}}">{{ status.total }}</b></a></li> + <li><a href="" class="action revisions" accesskey="o" rel="nofollow">{{_("Reload page")}}</a></li> +</ul> +{% endif %} + +{% block pageactions %} +{% endblock %} +<br/> + +<div id="body-wrapper" class="dokuwiki"> + +<div id="content" lang="en" dir="ltr"> + +<h1>{% block subtitle %}pyLoad - {{_("Web UI")}}{% endblock %}</h1> + +{% block statusbar %} +{% endblock %} + + +<br/> + +<div class="level1" style="clear:both"> +</div> +<noscript><h1>Enable JavaScript to use the web ui.</h1></noscript> + +{% for message in messages %} + <b><p>{{message}}</p></b> +{% endfor %} + +<div id="load-indicator" style="opacity: 0; float: right; margin-top: -10px;"> + <img src="/img/ajax-loader.gif" alt="" style="padding-right: 5px"/> + {{_("loading")}} +</div> + +{% block content %} +{% endblock content %} + + <hr style="clear: both;" /> + +<div id="foot">© 2008-2015 pyLoad Team +<a href="#top" class="action top" accesskey="x"><span>{{_("Back to top")}}</span></a><br /> +<!--<div class="breadcrumbs"></div>--> + +</div> +</div> +</div> + +<div style="display: none;"> + {% include '/tml/window.html' %} + {% include '/tml/captcha.html' %} + {% block hidden %} + {% endblock %} +</div> +</body> +</html> diff --git a/pyload/webui/themes/Flat/tml/captcha.html b/pyload/webui/themes/Flat/tml/captcha.html new file mode 100644 index 000000000..3bfa6cbcf --- /dev/null +++ b/pyload/webui/themes/Flat/tml/captcha.html @@ -0,0 +1,42 @@ +<!-- Captcha box --> +<div id="cap_box" class="window_box"> + + <form id="cap_form" action="/json/set_captcha" method="POST" enctype="multipart/form-data" onsubmit="return false;"> + + <h1>{{_("Captcha reading")}}</h1> + <p id="cap_title">{{_("Please read the text on the captcha.")}}</p> + + <div id="cap_textual"> + + <input id="cap_id" name="cap_id" type="hidden" value="" /> + + <label>{{_("Captcha")}} + <span class="small">{{_("The captcha.")}}</span> + </label> + <span class="cont"> + <img id="cap_textual_img" src=""> + </span> + + <label>{{_("Text")}} + <span class="small">{{_("Input the text on the captcha.")}}</span> + </label> + <input id="cap_result" name="cap_result" type="text" size="20" /> + + </div> + + <div id="cap_positional" style="text-align: center"> + <img id="cap_positional_img" src="" style="margin: 10px; cursor:pointer"> + </div> + + <div id="button_bar" style="text-align: center"> + <span> + <button id="cap_submit" type="submit" style="margin-left: 0">{{_("Submit")}}</button> + <button id="cap_reset" type="reset" style="margin-left: 0">{{_("Close")}}</button> + </span> + </div> + + <div class="spacer"></div> + + </form> + +</div> diff --git a/pyload/webui/themes/Flat/tml/downloads.html b/pyload/webui/themes/Flat/tml/downloads.html new file mode 100644 index 000000000..f6e581c5b --- /dev/null +++ b/pyload/webui/themes/Flat/tml/downloads.html @@ -0,0 +1,29 @@ +{% extends '/tml/base.html' %} + +{% block title %}Downloads - {{super()}} {% endblock %} + +{% block subtitle %} +{{_("Downloads")}} +{% endblock %} + +{% block content %} + +<ul> + {% for folder in files.folder %} + <li> + {{ folder.name }} + <ul> + {% for file in folder.files %} + <li><a href='get/{{ folder.path|escape }}/{{ file|escape }}'>{{file}}</a></li> + {% endfor %} + </ul> + </li> + {% endfor %} + + {% for file in files.files %} + <li> <a href='get/{{ file|escape }}'>{{ file }}</a></li> + {% endfor %} + +</ul> + +{% endblock %} diff --git a/pyload/webui/themes/Flat/tml/filemanager.html b/pyload/webui/themes/Flat/tml/filemanager.html new file mode 100644 index 000000000..b4872b084 --- /dev/null +++ b/pyload/webui/themes/Flat/tml/filemanager.html @@ -0,0 +1,78 @@ +{% extends '/tml/base.html' %} + +{% block head %} + +<script type="text/javascript" src="/js/filemanager.min.js"></script> + +<script type="text/javascript"> + +document.addEvent("domready", function(){ + var fmUI = new FilemanagerUI("url",1); +}); +</script> +{% endblock %} + +{% block title %}Downloads - {{super()}} {% endblock %} + + +{% block subtitle %} +{{_("FileManager")}} +{% endblock %} + +{% macro display_file(file) %} + <li class="file"> + <input type="hidden" name="path" class="path" value="{{ file.path }}" /> + <input type="hidden" name="name" class="name" value="{{ file.name }}" /> + <span> + <b>{{ file.name }}</b> + <span class="buttons" style="opacity:0"> + <img title="{{_("Rename Directory")}}" class="rename" style="cursor: pointer" height="12px" src="/img/pencil.png" /> + + <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/delete.png" /> + </span> + </span> + </li> +{%- endmacro %} + +{% macro display_folder(fld, open = false) -%} + <li class="folder"> + <input type="hidden" name="path" class="path" value="{{ fld.path }}" /> + <input type="hidden" name="name" class="name" value="{{ fld.name }}" /> + <span> + <b>{{ fld.name }}</b> + <span class="buttons" style="opacity:0"> + <img title="{{_("Rename Directory")}}" class="rename" style="cursor: pointer" height="12px" src="/img/pencil.png" /> + + <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/delete.png" /> + + <img title="{{_("Add subdirectory")}}" class="mkdir" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/add_folder.png" /> + </span> + </span> + {% if (fld.folders|length + fld.files|length) > 0 %} + {% if open %} + <ul> + {% else %} + <ul style="display:none"> + {% endif %} + {% for child in fld.folders %} + {{ display_folder(child) }} + {% endfor %} + {% for child in fld.files %} + {{ display_file(child) }} + {% endfor %} + </ul> + {% else %} + <div style="display:none">{{ _("Folder is empty") }}</div> + {% endif %} + </li> +{%- endmacro %} + +{% block content %} + +<div style="clear:both"><!-- --></div> + +<ul id="directories-list"> +{{ display_folder(root, true) }} +</ul> + +{% endblock %} diff --git a/pyload/webui/themes/Flat/tml/folder.html b/pyload/webui/themes/Flat/tml/folder.html new file mode 100644 index 000000000..21f3f4a03 --- /dev/null +++ b/pyload/webui/themes/Flat/tml/folder.html @@ -0,0 +1,15 @@ +<li class="folder"> + <input type="hidden" name="path" class="path" value="{{ path }}" /> + <input type="hidden" name="name" class="name" value="{{ name }}" /> + <span> + <b>{{ name }}</b> + <span class="buttons" style="opacity:0"> + <img title="{{_("Rename Directory")}}" class="rename" style="cursor: pointer" height="12px" src="/img/pencil.png" /> + + <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/delete.png" /> + + <img title="{{_("Add subdirectory")}}" class="mkdir" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/add_folder.png" /> + </span> + </span> + <div style="display:none">{{ _("Folder is empty") }}</div> +</li> diff --git a/pyload/webui/themes/Flat/tml/home.html b/pyload/webui/themes/Flat/tml/home.html new file mode 100644 index 000000000..cc0bf330c --- /dev/null +++ b/pyload/webui/themes/Flat/tml/home.html @@ -0,0 +1,266 @@ +{% extends '/tml/base.html' %} +{% block head %} + +<script type="text/javascript"> + +var em; +var operafix = (navigator.userAgent.toLowerCase().search("opera") >= 0); + +document.addEvent("domready", function(){ + em = new EntryManager(); +}); + +var EntryManager = new Class({ + initialize: function(){ + this.json = new Request.JSON({ + url: "json/links", + secure: false, + async: true, + onSuccess: this.update.bind(this), + initialDelay: 0, + delay: 2500, + limit: 30000 + }); + + this.ids = [{% for link in content %} + {% if forloop.last %} + {{ link.id }} + {% else %} + {{ link.id }}, + {% endif %} + {% endfor %}]; + + this.entries = []; + this.container = $('LinksAktiv'); + + this.parseFromContent(); + + this.json.startTimer(); + }, + parseFromContent: function(){ + this.ids.each(function(id,index){ + var entry = new LinkEntry(id); + entry.parse(); + this.entries.push(entry) + }, this); + }, + update: function(data){ + + try{ + this.ids = this.entries.map(function(item){ + return item.fid + }); + + this.ids.filter(function(id){ + return !this.ids.contains(id) + },data).each(function(id){ + var index = this.ids.indexOf(id); + this.entries[index].remove(); + this.entries = this.entries.filter(function(item){return item.fid != this},id); + this.ids = this.ids.erase(id) + }, this); + + data.links.each(function(link, i){ + if (this.ids.contains(link.fid)){ + + var index = this.ids.indexOf(link.fid); + this.entries[index].update(link) + + }else{ + var entry = new LinkEntry(link.fid); + entry.insert(link); + this.entries.push(entry); + this.ids.push(link.fid); + this.container.adopt(entry.elements.tr,entry.elements.pgbTr); + entry.fade.start('opacity', 1); + entry.fadeBar.start('opacity', 1); + + } + }, this) + }catch(e){ + //alert(e) + } + } +}); + + +var LinkEntry = new Class({ + initialize: function(id){ + this.fid = id; + this.id = id; + }, + parse: function(){ + this.elements = { + tr: $("link_{id}".substitute({id: this.id})), + name: $("link_{id}_name".substitute({id: this.id})), + status: $("link_{id}_status".substitute({id: this.id})), + info: $("link_{id}_info".substitute({id: this.id})), + bleft: $("link_{id}_bleft".substitute({id: this.id})), + percent: $("link_{id}_percent".substitute({id: this.id})), + remove: $("link_{id}_remove".substitute({id: this.id})), + pgbTr: $("link_{id}_pgb_tr".substitute({id: this.id})), + pgb: $("link_{id}_pgb".substitute({id: this.id})) + }; + this.initEffects(); + }, + insert: function(item){ + try{ + + this.elements = { + tr: new Element('tr', { + 'html': '', + 'styles':{ + 'opacity': 0 + } + }), + name: new Element('td', { + 'html': item.name + }), + status: new Element('td', { + 'html': item.statusmsg + }), + info: new Element('td', { + 'html': item.info + }), + bleft: new Element('td', { + 'html': humanFileSize(item.size) + }), + percent: new Element('span', { + 'html': item.percent+ '% / '+ humanFileSize(item.size-item.bleft) + }), + remove: new Element('img',{ + 'src': '/img/control_cancel.png', + 'styles':{ + 'vertical-align': 'middle', + 'margin-right': '-20px', + 'margin-left': '5px', + 'margin-top': '-2px', + 'cursor': 'pointer' + } + }), + pgbTr: new Element('tr', { + 'html':'' + }), + pgb: new Element('div', { + 'html': ' ', + 'styles':{ + 'height': '4px', + 'width': item.percent+'%', + 'background-color': '#ddd' + } + }) + }; + + this.elements.tr.adopt(this.elements.name,this.elements.status,this.elements.info,this.elements.bleft,new Element('td').adopt(this.elements.percent,this.elements.remove)); + this.elements.pgbTr.adopt(new Element('td',{'colspan':5}).adopt(this.elements.pgb)); + this.initEffects(); + }catch(e){ + alert(e) + } + }, + initEffects: function(){ + if(!operafix) + this.bar = new Fx.Morph(this.elements.pgb, {unit: '%', duration: 5000, link: 'link', fps:30}); + this.fade = new Fx.Tween(this.elements.tr); + this.fadeBar = new Fx.Tween(this.elements.pgbTr); + + this.elements.remove.addEvent('click', function(){ + new Request({method: 'get', url: '/json/abort_link/'+this.id}).send(); + }.bind(this)); + + }, + update: function(item){ + this.elements.name.set('text', item.name); + this.elements.status.set('text', item.statusmsg); + this.elements.info.set('text', item.info); + this.elements.bleft.set('text', item.format_size); + this.elements.percent.set('text', item.percent+ '% / '+ humanFileSize(item.size-item.bleft)); + if(!operafix) + { + this.bar.start({ + 'width': item.percent, + 'background-color': [Math.round(120/100*item.percent),100,100].hsbToRgb().rgbToHex() + }); + } + else + { + this.elements.pgb.set( + 'styles', { + 'height': '4px', + 'width': item.percent+'%', + 'background-color': [Math.round(120/100*item.percent),100,100].hsbToRgb().rgbToHex(), + }); + } + }, + remove: function(){ + this.fade.start('opacity',0).chain(function(){this.elements.tr.dispose();}.bind(this)); + this.fadeBar.start('opacity',0).chain(function(){this.elements.pgbTr.dispose();}.bind(this)); + + } + }); +</script> + +{% endblock %} + +{% block subtitle %} +{{_("Active Downloads")}} +{% endblock %} + +{% block menu %} +<li class="selected"> + <a href="/" title=""><img src="/img/head-menu-home.png" alt="" /> {{_("Home")}}</a> +</li> +<li> + <a href="/queue/" title=""><img src="/img/head-menu-queue.png" alt="" /> {{_("Queue")}}</a> +</li> +<li> + <a href="/collector/" title=""><img src="/img/head-menu-collector.png" alt="" /> {{_("Collector")}}</a> +</li> +<li> + <a href="/downloads/" title=""><img src="/img/head-menu-development.png" alt="" /> {{_("Downloads")}}</a> +</li> +<li> + <a href="/filemanager/" title=""><img src="/img/head-menu-download.png" alt="" /> {{_("FileManager")}}</a> +</li> +<li class="right"> + <a href="/logs/" title=""><img src="/img/head-menu-index.png" alt="" />{{_("Logs")}}</a> +</li> +<li class="right"> + <a href="/settings/" title=""><img src="/img/head-menu-config.png" alt="" />{{_("Config")}}</a> +</li> +{% endblock %} + +{% block content %} +<table width="100%" class="queue"> + <thead> + <tr class="header"> + <th>{{_("Name")}}</th> + <th>{{_("Status")}}</th> + <th>{{_("Information")}}</th> + <th>{{_("Size")}}</th> + <th>{{_("Progress")}}</th> + </tr> + </thead> + <tbody id="LinksAktiv"> + + {% for link in content %} + <tr id="link_{{ link.id }}"> + <td id="link_{{ link.id }}_name">{{ link.name }}</td> + <td id="link_{{ link.id }}_status">{{ link.status }}</td> + <td id="link_{{ link.id }}_info">{{ link.info }}</td> + <td id="link_{{ link.id }}_bleft">{{ link.format_size }}</td> + <td> + <span id="link_{{ link.id }}_percent">{{ link.percent }}% /{{ link.bleft }}</span> + <img id="link_{{ link.id }}_remove" style="vertical-align: middle; margin-right: -20px; margin-left: 5px; margin-top: -2px; cursor:pointer;" src="/img/control_cancel.png"/> + </td> + </tr> + <tr id="link_{{ link.id }}_pgb_tr"> + <td colspan="5"> + <div id="link_{{ link.id }}_pgb" class="progressBar" style="background-color: green; height:4px; width: {{ link.percent }}%;"> </div> + </td> + </tr> + {% endfor %} + + </tbody> +</table> +{% endblock %} diff --git a/pyload/webui/themes/Flat/tml/info.html b/pyload/webui/themes/Flat/tml/info.html new file mode 100644 index 000000000..57e688e09 --- /dev/null +++ b/pyload/webui/themes/Flat/tml/info.html @@ -0,0 +1,81 @@ +{% extends '/tml/base.html' %} + +{% block head %} + <script type="text/javascript"> + window.addEvent("domready", function() { + var ul = new Element('ul#twitter_update_list'); + var script1 = new Element('script[src=http://twitter.com/javascripts/blogger.min.js][type=text/javascript]'); + var script2 = new Element('script[src=http://twitter.com/statuses/user_timeline/pyLoad.json?callback=twitterCallback2&count=6][type=text/javascript]'); + $("twitter").adopt(ul, script1, script2); + }); + </script> +{% endblock %} + +{% block title %}{{ _("Info") }} - {{ super() }} {% endblock %} +{% block subtitle %}{{ _("Info") }}{% endblock %} + +{% block content %} + <h3>{{ _("News") }}</h3> + <div id="twitter"></div> + + <h3>{{ _("Support") }}</h3> + + <ul> + <li style="font-weight:bold;"> + <a href="http://pyload.org/wiki" target="_blank">Wiki</a> + | + <a href="http://forum.pyload.org/" target="_blank">Forum</a> + | + <a href="http://pyload.org/irc/" target="_blank">Chat</a> + </li> + <li style="font-weight:bold;"><a href="http://docs.pyload.org" target="_blank">Documentation</a></li> + <li style="font-weight:bold;"><a href="https://bitbucket.org/spoob/pyload/overview" target="_blank">Development</a></li> + <li style="font-weight:bold;"><a href="https://bitbucket.org/spoob/pyload/issues?status=new&status=open" target="_blank">Issue Tracker</a></li> + + </ul> + + <h3>{{ _("System") }}</h3> + <table class="system"> + <tr> + <td>{{ _("Python:") }}</td> + <td>{{ python }}</td> + </tr> + <tr> + <td>{{ _("OS:") }}</td> + <td>{{ os }}</td> + </tr> + <tr> + <td>{{ _("pyLoad version:") }}</td> + <td>{{ version }}</td> + </tr> + <tr> + <td>{{ _("Installation Folder:") }}</td> + <td>{{ folder }}</td> + </tr> + <tr> + <td>{{ _("Config Folder:") }}</td> + <td>{{ config }}</td> + </tr> + <tr> + <td>{{ _("Download Folder:") }}</td> + <td>{{ download }}</td> + </tr> + <tr> + <td>{{ _("Free Space:") }}</td> + <td>{{ freespace }}</td> + </tr> + <tr> + <td>{{ _("Language:") }}</td> + <td>{{ language }}</td> + </tr> + <tr> + <td>{{ _("Web UI Port:") }}</td> + <td>{{ webif }}</td> + </tr> + <tr> + <td>{{ _("Remote Interface Port:") }}</td> + <td>{{ remote }}</td> + </tr> + </table> + +{% endblock %} diff --git a/pyload/webui/themes/Flat/tml/login.html b/pyload/webui/themes/Flat/tml/login.html new file mode 100644 index 000000000..74bbb70a4 --- /dev/null +++ b/pyload/webui/themes/Flat/tml/login.html @@ -0,0 +1,36 @@ +{% extends '/tml/base.html' %} + +{% block title %}{{_("Login")}} - {{super()}} {% endblock %} + +{% block content %} + +<div class="centeralign"> +<form action="" method="post" accept-charset="utf-8" id="login"> + <div class="no"> + <input type="hidden" name="do" value="login" /> + <fieldset> + <legend>Login</legend> + <label> + <span>{{_("Username")}}</span> + <input type="text" size="20" name="username" /> + </label> + <br /> + <label> + <span>{{_("Password")}}</span> + <input type="password" size="20" name="password" /> + </label> + <br /> + <input type="submit" value="Login" class="button" /> + </fieldset> + </div> +</form> + +{% if errors %} +<p>{{_("Your username and password didn't match. Please try again.")}}</p> + {{ _("To reset your login data or add an user run:") }} <b> python pyload.py -u</b> +{% endif %} + +</div> +<br> + +{% endblock %} diff --git a/pyload/webui/themes/Flat/tml/logout.html b/pyload/webui/themes/Flat/tml/logout.html new file mode 100644 index 000000000..db7e9290e --- /dev/null +++ b/pyload/webui/themes/Flat/tml/logout.html @@ -0,0 +1,9 @@ +{% extends '/tml/base.html' %} + +{% block head %} +<meta http-equiv="refresh" content="3; url=/"> +{% endblock %} + +{% block content %} +<p><b>{{_("You were successfully logged out.")}}</b></p> +{% endblock %} diff --git a/pyload/webui/themes/Flat/tml/logs.html b/pyload/webui/themes/Flat/tml/logs.html new file mode 100644 index 000000000..429306aae --- /dev/null +++ b/pyload/webui/themes/Flat/tml/logs.html @@ -0,0 +1,41 @@ +{% extends '/tml/base.html' %} + +{% block title %}{{_("Logs")}} - {{super()}} {% endblock %} +{% block subtitle %}{{_("Logs")}}{% endblock %} +{% block head %} +<link rel="stylesheet" type="text/css" href="/css/log.css"/> +{% endblock %} + +{% block content %} +<div style="clear: both;"></div> + +<div class="logpaginator"><a href="{{ "/logs/1" }}"><< {{_("Start")}}</a> <a href="{{ "/logs/" + iprev|string }}">< {{_("prev")}}</a> <a href="{{ "/logs/" + inext|string }}">{{_("next")}} ></a> <a href="/logs/">{{_("End")}} >></a></div> +<div class="logperpage"> + <form id="logform1" action="" method="POST"> + <label for="reversed">Reversed:</label> + <input type="checkbox" name="reversed" onchange="this.form.submit();" {% if reversed %} checked="checked" {% endif %} /> + <label for="perpage">Lines per page:</label> + <select name="perpage" onchange="this.form.submit();"> + {% for value in perpage_p %} + <option value="{{value.0}}"{% if value.0 == perpage %} selected="selected" {% endif %}>{{value.1}}</option> + {% endfor %} + </select> + </form> +</div> +<div class="logwarn">{{warning}}</div> +<div style="clear: both;"></div> +<div class="logdiv"> + <table class="logtable" cellpadding="0" cellspacing="0"> + {% for line in log %} + <tr><td class="logline">{{line.line}}</td><td>{{line.date}}</td><td class="loglevel">{{line.level}}</td><td>{{line.message}}</td></tr> + {% endfor %} + </table> +</div> +<div class="logform"> +<form id="logform2" action="" method="POST"> + <label for="from">Jump to time:</label><input type="text" name="from" size="15" value="{{from}}"/> + <input type="submit" value="ok" /> +</form> +</div> +<div style="clear: both; height: 10px;"> </div> +{% endblock %} diff --git a/pyload/webui/themes/Flat/tml/pathchooser.html b/pyload/webui/themes/Flat/tml/pathchooser.html new file mode 100644 index 000000000..6e58ab536 --- /dev/null +++ b/pyload/webui/themes/Flat/tml/pathchooser.html @@ -0,0 +1,76 @@ +<html> +<head> + <script class="javascript"> + function chosen() + { + opener.ifield.value = document.forms[0].p.value; + close(); + } + function exit() + { + close(); + } + function setInvalid() { + document.forms[0].send.disabled = 'disabled'; + document.forms[0].p.style.color = '#FF0000'; + } + function setValid() { + document.forms[0].send.disabled = ''; + document.forms[0].p.style.color = '#000000'; + } + function setFile(file) + { + document.forms[0].p.value = file; + setValid(); + + } + </script> + <link rel="stylesheet" type="text/css" href="/css/pathchooser.css"/> +</head> +<body{% if type == 'file' %}{% if not oldfile %} onload="setInvalid();"{% endif %}{% endif %}> +<center> + <div id="paths"> + <form method="get" action="?" onSubmit="chosen();" onReset="exit();"> + <input type="text" name="p" value="{{ oldfile|default(cwd) }}" size="60" onfocus="setValid();"> + <input type="submit" value="Ok" name="send"> + </form> + + {% if type == 'folder' %} + <span class="path_abs_rel">{{_("Path")}}: <a href="{{ "/pathchooser" + cwd|path_make_absolute|quotepath }}"{% if absolute %} style="text-decoration: underline;"{% endif %}>{{_("absolute")}}</a> | <a href="{{ "/pathchooser/" + cwd|path_make_relative|quotepath }}"{% if not absolute %} style="text-decoration: underline;"{% endif %}>{{_("relative")}}</a></span> + {% else %} + <span class="path_abs_rel">{{_("Path")}}: <a href="{{ "/filechooser/" + cwd|path_make_absolute|quotepath }}"{% if absolute %} style="text-decoration: underline;"{% endif %}>{{_("absolute")}}</a> | <a href="{{ "/filechooser/" + cwd|path_make_relative|quotepath }}"{% if not absolute %} style="text-decoration: underline;"{% endif %}>{{_("relative")}}</a></span> + {% endif %} + </div> + <table border="0" cellspacing="0" cellpadding="3"> + <tr> + <th>{{_("name")}}</th> + <th>{{_("size")}}</th> + <th>{{_("type")}}</th> + <th>{{_("last modified")}}</th> + </tr> + {% if parentdir %} + <tr> + <td colspan="4"> + <a href="{% if type == 'folder' %}{{ "/pathchooser/" + parentdir|quotepath }}{% else %}{{ "/filechooser/" + parentdir|quotepath }}{% endif %}"><span class="parentdir">{{_("parent directory")}}</span></a> + </td> + </tr> + {% endif %} +{% for file in files %} + <tr> + {% if type == 'folder' %} + <td class="name">{% if file.type == 'dir' %}<a href="{{ "/pathchooser/" + file.fullpath|quotepath }}" title="{{ file.fullpath }}"><span class="path_directory">{{ file.name|truncate(25) }}</span></a>{% else %}<span class="path_file" title="{{ file.fullpath }}">{{ file.name|truncate(25) }}{% endif %}</span></td> + {% else %} + <td class="name">{% if file.type == 'dir' %}<a href="{{ "/filechooser/" + file.fullpath|quotepath }}" title="{{ file.fullpath }}"><span class="file_directory">{{ file.name|truncate(25) }}</span></a>{% else %}<a href="#" onclick="setFile('{{ file.fullpath }}');" title="{{ file.fullpath }}"><span class="file_file">{{ file.name|truncate(25) }}{% endif %}</span></a></td> + {% endif %} + <td class="size">{{ file.size|float|filesizeformat }}</td> + <td class="type">{% if file.type == 'dir' %}directory{% else %}{{ file.ext|default("file") }}{% endif %}</td> + <td class="mtime">{{ file.modified|date("d.m.Y - H:i:s") }}</td> + <tr> +<!-- <tr> + <td colspan="4">{{_("no content")}}</td> + </tr> --> +{% endfor %} + </table> + </center> +</body> +</html> diff --git a/pyload/webui/themes/Flat/tml/queue.html b/pyload/webui/themes/Flat/tml/queue.html new file mode 100644 index 000000000..8038a085b --- /dev/null +++ b/pyload/webui/themes/Flat/tml/queue.html @@ -0,0 +1,104 @@ +{% extends '/tml/base.html' %} +{% block head %} + +<script type="text/javascript" src="/js/package.js"></script> + +<script type="text/javascript"> + +document.addEvent("domready", function(){ + var pUI = new PackageUI("url", {{ target }}); +}); +</script> +{% endblock %} + +{% if target %} + {% set name = _("Queue") %} +{% else %} + {% set name = _("Collector") %} +{% endif %} + +{% block title %}{{name}} - {{super()}} {% endblock %} +{% block subtitle %}{{name}}{% endblock %} + +{% block pageactions %} +<ul id="page-actions-more"> + <li id="del_finished"><a style="padding: 0; font-weight: 300;" href="#">{{_("Delete Finished")}}</a></li> + <li id="restart_failed"><a style="padding: 0; font-weight: 300;" href="#">{{_("Restart Failed")}}</a></li> +</ul> +{% endblock %} + +{% block content %} +{% autoescape true %} + +<ul id="package-list" style="list-style: none; padding-left: 0; margin-top: -10px;"> +{% for package in content %} + <li> +<div id="package_{{package.pid}}" class="package"> + <div class="order" style="display: none;">{{ package.order }}</div> + + <div class="packagename" style="cursor: pointer"> + <img class="package_drag" src="/img/folder.png" style="cursor: move; margin-bottom: -2px"> + <span class="name">{{package.name}}</span> + + <span class="buttons" style="opacity:0"> + <img title="{{_("Delete Package")}}" style="cursor: pointer" width="12px" height="12px" src="/img/delete.png" /> + + <img title="{{_("Restart Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/img/arrow_refresh.png" /> + + <img title="{{_("Edit Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/img/pencil.png" /> + + <img title="{{_("Move Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/img/package_go.png" /> + </span> + </div> + {% set progress = (package.linksdone * 100) / package.linkstotal %} + + <div id="progress" style="border-radius: 0px; border: 1px solid #AAAAAA; width: 50%; height: 1em"> + <div style="width: {{ progress }}%; height: 100%; background-color: #add8e6;"></div> + <label style="font-size: 0.8em; font-weight: 300; padding-left: 5px; position: relative; top: -17px"> + {{ package.sizedone|formatsize }} / {{ package.sizetotal|formatsize }}</label> + <label style="font-size: 0.8em; font-weight: 300; padding-right: 5px ;float: right; position: relative; top: -17px"> + {{ package.linksdone }} / {{ package.linkstotal }}</label> + </div> + <div style="clear: both; margin-bottom: -10px"></div> + + <div id="children_{{package.pid}}" style="display: none;" class="children"> + <span class="child_secrow">{{_("Folder:")}} <span class="folder">{{package.folder}}</span> | {{_("Password:")}} <span class="password">{{package.password}}</span></span> + <ul id="sort_children_{{package.pid}}" style="list-style: none; padding-left: 0"> + </ul> + </div> +</div> + </li> +{% endfor %} +</ul> +{% endautoescape %} +{% endblock %} + +{% block hidden %} +<div id="pack_box" class="window_box" style="z-index: 2"> + <form id="pack_form" action="/json/edit_package" method="POST" enctype="multipart/form-data"> + <h1>{{_("Edit Package")}}</h1> + <p>{{_("Edit the package detais below.")}}</p> + <input name="pack_id" id="pack_id" type="hidden" value=""/> + <label for="pack_name">{{_("Name")}} + <span class="small">{{_("The name of the package.")}}</span> + </label> + <input id="pack_name" name="pack_name" type="text" size="20" /> + + <label for="pack_folder">{{_("Folder")}} + <span class="small">{{_("Name of subfolder for these downloads.")}}</span> + </label> + <input id="pack_folder" name="pack_folder" type="text" size="20" /> + + <label for="pack_pws">{{_("Password")}} + <span class="small">{{_("List of passwords used for unrar.")}}</span> + </label> + <textarea rows="3" name="pack_pws" id="pack_pws"></textarea> + + <button type="submit">{{_("Submit")}}</button> + <button id="pack_reset" style="margin-left: 0" type="reset" >{{_("Reset")}}</button> + <div class="spacer"></div> + + </form> + +</div> +{% endblock %} diff --git a/pyload/webui/themes/Flat/tml/settings.html b/pyload/webui/themes/Flat/tml/settings.html new file mode 100644 index 000000000..47f69b188 --- /dev/null +++ b/pyload/webui/themes/Flat/tml/settings.html @@ -0,0 +1,204 @@ +{% extends '/tml/base.html' %} + +{% block title %}{{ _("Settings") }} - {{ super() }} {% endblock %} +{% block subtitle %}{{ _("Settings") }}{% endblock %} + +{% block head %} + <script type="text/javascript" src="/lib/MooTools/TinyTab/tinytab.js"></script> + <script type="text/javascript" src="/lib/MooTools/MooDropMenu/MooDropMenu.js"></script> + <script type="text/javascript" src="/js/settings.min.js"></script> + +{% endblock %} + +{% block content %} + + <ul id="toptabs" class="tabs"> + <li><a class="selected" href="#">{{ _("General") }}</a></li> + <li><a href="#">{{ _("Plugins") }}</a></li> + <li><a href="#">{{ _("Accounts") }}</a></li> + </ul> + + <div id="tabsback" style="height: 20px; padding-left: 150px; color: white; font-weight: bold;"> + + </div> + + <span id="tabs-body"> + <!-- General --> + <span id="general" class="active tabContent"> + <ul class="nav tabs"> + <li class> + <a>Menu</a> + <ul id="general-menu"> + {% for entry,name in conf.general %} + <nobr> + <li id="general|{{ entry }}">{{ name }}</li> + </nobr> + <br> + {% endfor %} + </ul> + </li> + </ul> + + <form id="general_form" action="" method="POST" autocomplete="off"> + <span id="general_form_content"> + <br> + <h3> {{ _("Choose a section from the menu") }}</h3> + <br> + </span> + + <input id="general|submit" class="styled_button" type="submit" value="{{_("Submit")}}"/> + </form> + </span> + + <!-- Plugins --> + <span id="plugins" class="tabContent"> + <ul class="nav tabs"> + <li class> + <a>Menu</a> + <ul id="plugin-menu"> + {% for entry,name in conf.plugin %} + <nobr> + <li id="plugin|{{ entry }}">{{ name }}</li> + </nobr> + <br> + {% endfor %} + </ul> + </li> + </ul> + + + <form id="plugin_form" action="" method="POST" autocomplete="off"> + + <span id="plugin_form_content"> + <br> + <h3> {{ _("Choose a section from the menu") }}</h3> + <br> + </span> + <input id="plugin|submit" class="styled_button" type="submit" value="{{_("Submit")}}"/> + </form> + + </span> + + <!-- Accounts --> + <span id="accounts" class="tabContent"> + <form id="account_form" action="/json/update_accounts" method="POST"> + + <table class="settable wide"> + + <thead> + <tr> + <th>{{ _("Plugin") }}</th> + <th>{{ _("Name") }}</th> + <th>{{ _("Password") }}</th> + <th>{{ _("Status") }}</th> + <th>{{ _("Premium") }}</th> + <th>{{ _("Valid until") }}</th> + <th>{{ _("Traffic left") }}</th> + <th>{{ _("Time") }}</th> + <th>{{ _("Max Parallel") }}</th> + <th>{{ _("Delete?") }}</th> + </tr> + </thead> + + + {% for account in conf.accs %} + {% set plugin = account.type %} + <tr> + <td> + <span style="padding:5px">{{ plugin }}</span> + </td> + + <td><label for="{{plugin}}|password;{{account.login}}" + style="color:#424242;">{{ account.login }}</label></td> + <td> + <input id="{{plugin}}|password;{{account.login}}" + name="{{plugin}}|password;{{account.login}}" + type="password" value="{{account.password}}" size="12"/> + </td> + <td> + {% if account.valid %} + <span style="font-weight: bold; color: #006400;"> + {{ _("valid") }} + {% else %} + <span style="font-weight: bold; color: #8b0000;"> + {{ _("not valid") }} + {% endif %} + </span> + </td> + <td> + {% if account.premium %} + <span style="font-weight: bold; color: #006400;"> + {{ _("yes") }} + {% else %} + <span style="font-weight: bold; color: #8b0000;"> + {{ _("no") }} + {% endif %} + </span> + </td> + <td> + <span style="font-weight: bold;"> + {{ account.validuntil }} + </span> + </td> + <td> + <span style="font-weight: bold;"> + {{ account.trafficleft }} + </span> + </td> + <td> + <input id="{{plugin}}|time;{{account.login}}" + name="{{plugin}}|time;{{account.login}}" type="text" + size="7" value="{{account.time}}"/> + </td> + <td> + <input id="{{plugin}}|limitdl;{{account.login}}" + name="{{plugin}}|limitdl;{{account.login}}" type="text" + size="2" value="{{account.limitdl}}"/> + </td> + <td> + <input id="{{plugin}}|delete;{{account.login}}" + name="{{plugin}}|delete;{{account.login}}" type="checkbox" + value="True"/> + </td> + </tr> + {% endfor %} + </table> + + <button id="account_submit" type="submit" class="styled_button">{{_("Submit")}}</button> + <button id="account_add" style="margin-left: 0" type="submit" class="styled_button">{{_("Add")}}</button> + </form> + </span> + </span> +{% endblock %} +{% block hidden %} +<div id="account_box" class="window_box" style="z-index: 2"> +<form id="add_account_form" action="/json/add_account" method="POST" enctype="multipart/form-data"> +<h1>{{_("Add Account")}}</h1> +<p>{{_("Enter your account data to use premium features.")}}</p> +<label for="account_login">{{_("Login")}} +<span class="small">{{_("Your username.")}}</span> +</label> +<input id="account_login" name="account_login" type="text" size="20" /> + +<label for="account_password">{{_("Password")}} +<span class="small">{{_("The password for this account.")}}</span> +</label> +<input id="account_password" name="account_password" type="password" size="20" /> + +<label for="account_type">{{_("Type")}} +<span class="small">{{_("Choose the hoster for your account.")}}</span> +</label> + <select name=account_type id="account_type"> + {% for type in types|sort %} + <option value="{{ type }}">{{ type }}</option> + {% endfor %} + </select> + +<button id="account_add_button" type="submit">{{_("Add")}}</button> +<button id="account_reset" style="margin-left: 0" type="reset">{{_("Reset")}}</button> +<div class="spacer"></div> + +</form> + +</div> +{% endblock %} diff --git a/pyload/webui/themes/Flat/tml/settings_item.html b/pyload/webui/themes/Flat/tml/settings_item.html new file mode 100644 index 000000000..71c212304 --- /dev/null +++ b/pyload/webui/themes/Flat/tml/settings_item.html @@ -0,0 +1,48 @@ +<table class="settable"> + {% if section.outline %} + <tr><th colspan="2">{{ section.outline }}</th></tr> + {% endif %} + {% for okey, option in sorted_conf(section) %} + {% if okey not in ("desc","outline") %} + <tr> + <td><label for="{{skey}}|{{okey}}" + style="color:#424242;">{{ option.desc }}:</label></td> + <td> + {% if option.type == "bool" %} + <select id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}"> + <option {% if option.value %} selected="selected" + {% endif %}value="True">{{ _("on") }}</option> + <option {% if not option.value %} selected="selected" + {% endif %}value="False">{{ _("off") }}</option> + </select> + {% elif ";" in option.type %} + <select id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}"> + {% for entry in option.list %} + <option {% if option.value == entry %} + selected="selected" {% endif %}>{{ entry }}</option> + {% endfor %} + </select> + {% elif option.type == "folder" %} + <input name="{{skey}}|{{okey}}" type="text" + id="{{skey}}|{{okey}}" value="{{option.value}}"/> + <input name="browsebutton" type="button" + onclick="ifield = document.getElementById('{{skey}}|{{okey}}'); pathchooser = window.open('{% if option.value %}{{ "/pathchooser/" + option.value|quotepath }}{% else %}{{ pathroot }}{% endif %}', 'pathchooser', 'scrollbars=yes,toolbar=no,menubar=no,statusbar=no,width=650,height=300'); pathchooser.ifield = ifield; window.ifield = ifield;" + value="{{_("Browse")}}"/> + {% elif option.type == "file" %} + <input name="{{skey}}|{{okey}}" type="text" + id="{{skey}}|{{okey}}" value="{{option.value}}"/> + <input name="browsebutton" type="button" + onclick="ifield = document.getElementById('{{skey}}|{{okey}}'); filechooser = window.open('{% if option.value %}{{ "/filechooser/" + option.value|quotepath }}{% else %}{{ fileroot }}{% endif %}', 'filechooser', 'scrollbars=yes,toolbar=no,menubar=no,statusbar=no,width=650,height=300'); filechooser.ifield = ifield; window.ifield = ifield;" + value="{{_("Browse")}}"/> + {% elif option.type == "password" %} + <input id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}" + type="password" value="{{option.value}}"/> + {% else %} + <input id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}" + type="text" value="{{option.value}}"/> + {% endif %} + </td> + </tr> + {% endif %} + {% endfor %} +</table> diff --git a/pyload/webui/themes/Flat/tml/window.html b/pyload/webui/themes/Flat/tml/window.html new file mode 100644 index 000000000..023441f69 --- /dev/null +++ b/pyload/webui/themes/Flat/tml/window.html @@ -0,0 +1,46 @@ +<iframe id="upload_target" name="upload_target" src="" style="display: none; width:0;height:0"></iframe> + +<div id="add_box" class="window_box"> +<form id="add_form" action="/json/add_package" method="POST" enctype="multipart/form-data"> +<h1>{{_("Add Package")}}</h1> +<p>{{_("Paste your links or upload a container.")}}</p> +<label for="add_name">{{_("Name")}} +<span class="small">{{_("The name of the new package.")}}</span> +</label> +<input id="add_name" name="add_name" type="text" size="20" /> + +<label for="add_links">{{_("Links")}} +<span class="small">{{_("Paste your links here or any text and press the filter button.")}}</span> +<span class="small"> {{ _("Filter urls") }} +<img alt="URIParsing" Title="Parse Uri" src="/img/parseUri.png" style="cursor:pointer; vertical-align: text-bottom;" onclick="parseUri()"/> +</span> + +</label> +<textarea rows="5" name="add_links" id="add_links"></textarea> + +<label for="add_password">{{_("Password")}} + <span class="small">{{_("Password for RAR-Archive")}}</span> +</label> +<input id="add_password" name="add_password" type="text" size="20"> + +<label>{{_("File")}} +<span class="small">{{_("Upload a container.")}}</span> +</label> +<input type="file" name="add_file" id="add_file"/> + +<label for="add_dest">{{_("Destination")}} +</label> +<span class="cont"> + {{_("Queue")}} + <input type="radio" name="add_dest" id="add_dest" value="1" checked="checked"/> + {{_("Collector")}} + <input type="radio" name="add_dest" id="add_dest2" value="0"/> +</span> + +<button type="submit">{{_("Add Package")}}</button> +<button id="add_reset" style="margin-left:0;" type="reset">{{_("Reset")}}</button> +<div class="spacer"></div> + +</form> + +</div> diff --git a/pyload/webui/themes/Next/css/MooDialog.css b/pyload/webui/themes/Next/css/MooDialog.css new file mode 100644 index 000000000..ad2583b4b --- /dev/null +++ b/pyload/webui/themes/Next/css/MooDialog.css @@ -0,0 +1,92 @@ +/* Created by Arian Stolwijk <http://www.aryweb.nl> */ + +.MooDialog { +/* position: fixed;*/ + margin: 0 auto 0 -350px; + width:600px; + padding:14px; + left:50%; + top: 100px; + + position: absolute; + left: 50%; + z-index: 50000; + + background: #fff; + color: black; + border-radius: 7px; + -moz-border-radius: 7px; + -webkit-border-radius: 7px; + border-radius: 7px; + -moz-box-shadow: 1px 1px 5px rgba(0,0,0,0.8); + -webkit-box-shadow: 1px 1px 5px rgba(0,0,0,0.8); + box-shadow: 1px 1px 5px rgba(0,0,0,0.8); +} + +.MooDialogTitle { + padding-top: 30px; +} + +.MooDialog .title { + position: absolute; + top: 0; + left: 0; + right: 0; + padding: 3px 20px; + background: #b7c4dc; + border-bottom: 1px solid #a1aec5; + font-weight: bold; + text-shadow: 1px 1px 0 #fff; + color: black; + border-radius: 7px; + -moz-border-radius: 7px; + -webkit-border-radius: 7px; +} + +.MooDialog .close { + background: url(../lib/MooTools/MooDialog/css/dialog-close.png) no-repeat; + width: 16px; + height: 16px; + display: block; + cursor: pointer; + top: -5px; + left: -5px; + position: absolute; +} + +.MooDialog .buttons { + text-align: right; + margin: 0; + padding: 0; + border: 0; + background: none; +} + +.MooDialog .iframe { + width: 100%; + height: 100%; +} + +.MooDialog .textInput { + width: 200px; + float: left; +} + +.MooDialog .MooDialogAlert, +.MooDialog .MooDialogConfirm, +.MooDialog .MooDialogPrompt, +.MooDialog .MooDialogError { + background: url(../lib/MooTools/MooDialog/css/dialog-warning.png) no-repeat; + padding-left: 40px; + min-height: 40px; +} + +.MooDialog .MooDialogConfirm, +.MooDialog .MooDialogPromt { + background: url(../lib/MooTools/MooDialog/css/dialog-question.png) no-repeat; +} + +.MooDialog .MooDialogError { + background: url(../lib/MooTools/MooDialog/css/dialog-error.png) no-repeat; +} + diff --git a/pyload/webui/themes/Next/css/log.css b/pyload/webui/themes/Next/css/log.css new file mode 100644 index 000000000..30e026821 --- /dev/null +++ b/pyload/webui/themes/Next/css/log.css @@ -0,0 +1,112 @@ + +html, body, #content +{ + height: 100%; +} +#body-wrapper +{ + height: 70%; +} +.logdiv +{ + height: 90%; + width: 100%; + overflow: auto; + border: 2px solid #CCC; + outline: 1px solid #666; + background-color: #FFE; + margin-right: auto; + margin-left: auto; +} +.logform +{ + display: table; + margin: 0 auto 0 auto; + padding-top: 5px; +} +.logtable +{ + + margin: 0px; +} +.logtable td +{ + border: none; + white-space: nowrap; + + + font-family: monospace; + font-size: 16px; + margin: 0px; + padding: 0px 10px 0px 10px; + line-height: 110%; +} +td.logline +{ + background-color: #EEE; + text-align:right; + padding: 0px 5px 0px 5px; +} +td.loglevel +{ + text-align:right; +} +td.MixedDEBUG, td.LineDEBUG, td.loglevelLineDEBUG +{ + color: darkcyan; +} +td.MixedWARNING, td.LineWARNING, td.loglevelLineWARNING +{ + color: #660; +} +td.MixedERROR, td.LineERROR, td.loglevelLineERROR +{ + color: red; +} +td.MixedCRITICAL, td.LineCRITICAL, td.loglevelLineCRITICAL +{ + color: purple; +} +td.loglevelMixedDEBUG span, td.loglevelLabelDEBUG span +{ + font-weight: bold; + color: white; + background-color: darkcyan; +} +td.loglevelMixedWARNING span, td.loglevelLabelWARNING span +{ + font-weight: bold; + color: white; + background-color: #660; +} +td.loglevelMixedERROR span, td.loglevelLabelERROR span +{ + font-weight: bold; + color: white; + background-color: red; +} +td.loglevelMixedCRITICAL span, td.loglevelLabelCRITICAL span +{ + font-weight: bold; + color: white; + background-color: purple; +} +.logperpage +{ + float: right; + padding-bottom: 8px; +} +.logpaginator +{ + float: left; + padding-top: 5px; +} +.logpaginator a +{ + padding: 0px 8px 0px 8px; +} +.logwarn +{ + text-align: center; + color: red; +}
\ No newline at end of file diff --git a/pyload/webui/themes/Next/css/pathchooser.css b/pyload/webui/themes/Next/css/pathchooser.css new file mode 100644 index 000000000..bfd84b19b --- /dev/null +++ b/pyload/webui/themes/Next/css/pathchooser.css @@ -0,0 +1,68 @@ +table { + width: 90%; + border: 1px dotted #888888; + font-family: sans-serif; + font-size: 10pt; +} + +th { + background-color: #525252; + color: #E0E0E0; +} + +table, tr, td { + background-color: #fff; +} + +a, a:visited { + text-decoration: none; + font-weight: bold; +} + +#paths { + width: 90%; + text-align: left; +} + +.file_directory { + color: #c0c0c0; +} +.path_directory { + color: #3c3c3c; +} +.file_file { + color: #3c3c3c; +} +.path_file { + color: #c0c0c0; +} + +.parentdir { + color: #000000; + font-size: 10pt; +} +.name { + text-align: left; +} +.size { + text-align: right; +} +.type { + text-align: left; +} +.mtime { + text-align: center; +} + +.path_abs_rel { + color: #3c3c3c; + text-decoration: none; + font-weight: bold; + font-family: sans-serif; + font-size: 10pt; +} + +.path_abs_rel a { + color: #3c3c3c; + font-style: italic; +} diff --git a/pyload/webui/themes/Next/css/window.css b/pyload/webui/themes/Next/css/window.css new file mode 100644 index 000000000..12829868b --- /dev/null +++ b/pyload/webui/themes/Next/css/window.css @@ -0,0 +1,73 @@ +/* ----------- stylized ----------- */ +.window_box h1{ + font-size:14px; + font-weight:bold; + margin-bottom:8px; +} +.window_box p{ + font-size:11px; + color:#666666; + margin-bottom:20px; + border-bottom:solid 1px #b7ddf2; + padding-bottom:10px; +} +.window_box label{ + display:block; + font-weight:bold; + text-align:right; + width:240px; + float:left; +} +.window_box .small{ + color:#666666; + display:block; + font-size:11px; + font-weight:normal; + text-align:right; + width:240px; +} +.window_box select, .window_box input{ + float:left; + font-size:12px; + padding:4px 2px; + border:solid 1px #aacfe4; + width:300px; + margin:2px 0 20px 10px; +} +.window_box .cont{ + float:left; + font-size:12px; + padding: 0px 10px 15px 0px; + width:300px; + margin:0px 0px 0px 10px; +} +.window_box .cont input{ + float: none; + margin: 0px 15px 0px 1px; +} +.window_box textarea{ + float:left; + font-size:12px; + padding:4px 2px; + border:solid 1px #aacfe4; + width:300px; + margin:2px 0 20px 10px; +} +.window_box button, .styled_button{ + clear:both; + margin-left:150px; + width:125px; + height:31px; + background:#666666 url(../img/button.png) no-repeat; + text-align:center; + line-height:31px; + color:#FFFFFF; + font-size:11px; + font-weight:bold; + border: 0px; +} + +.styled_button { + margin-left: 15px; + cursor: pointer; +} diff --git a/pyload/webui/themes/Next/img/add_folder.png b/pyload/webui/themes/Next/img/add_folder.png Binary files differnew file mode 100644 index 000000000..8acbc411b --- /dev/null +++ b/pyload/webui/themes/Next/img/add_folder.png diff --git a/pyload/webui/themes/Next/img/ajax-loader.gif b/pyload/webui/themes/Next/img/ajax-loader.gif Binary files differnew file mode 100644 index 000000000..2fd8e0737 --- /dev/null +++ b/pyload/webui/themes/Next/img/ajax-loader.gif diff --git a/pyload/webui/themes/Next/img/arrow_refresh.png b/pyload/webui/themes/Next/img/arrow_refresh.png Binary files differnew file mode 100644 index 000000000..0de26566d --- /dev/null +++ b/pyload/webui/themes/Next/img/arrow_refresh.png diff --git a/pyload/webui/themes/Next/img/button.png b/pyload/webui/themes/Next/img/button.png Binary files differnew file mode 100644 index 000000000..bb408a7d6 --- /dev/null +++ b/pyload/webui/themes/Next/img/button.png diff --git a/pyload/webui/themes/Next/img/control_cancel.png b/pyload/webui/themes/Next/img/control_cancel.png Binary files differnew file mode 100644 index 000000000..7b9bc3fba --- /dev/null +++ b/pyload/webui/themes/Next/img/control_cancel.png diff --git a/pyload/webui/themes/Next/img/delete.png b/pyload/webui/themes/Next/img/delete.png Binary files differnew file mode 100644 index 000000000..08f249365 --- /dev/null +++ b/pyload/webui/themes/Next/img/delete.png diff --git a/pyload/webui/themes/Next/img/package_go.png b/pyload/webui/themes/Next/img/package_go.png Binary files differnew file mode 100644 index 000000000..aace63ad6 --- /dev/null +++ b/pyload/webui/themes/Next/img/package_go.png diff --git a/pyload/webui/themes/Next/img/pencil.png b/pyload/webui/themes/Next/img/pencil.png Binary files differnew file mode 100644 index 000000000..0bfecd50e --- /dev/null +++ b/pyload/webui/themes/Next/img/pencil.png diff --git a/pyload/webui/themes/Next/img/pyload-logo.png b/pyload/webui/themes/Next/img/pyload-logo.png Binary files differnew file mode 100644 index 000000000..2443cd8b1 --- /dev/null +++ b/pyload/webui/themes/Next/img/pyload-logo.png diff --git a/pyload/webui/themes/Next/js/admin.coffee b/pyload/webui/themes/Next/js/admin.coffee new file mode 100644 index 000000000..82b0dd3ec --- /dev/null +++ b/pyload/webui/themes/Next/js/admin.coffee @@ -0,0 +1,58 @@ +root = this + +window.addEvent "domready", -> + + root.passwordDialog = new MooDialog {destroyOnHide: false} + root.passwordDialog.setContent $ 'password_box' + + $("login_password_reset").addEvent "click", (e) -> root.passwordDialog.close() + $("login_password_button").addEvent "click", (e) -> + + newpw = $("login_new_password").get("value") + newpw2 = $("login_new_password2").get("value") + + if newpw is newpw2 + form = $("password_form") + form.set "send", { + onSuccess: (data) -> + root.notify.alert "Success", { + 'className': 'success' + } + onFailure: (data) -> + root.notify.alert "Error", { + 'className': 'error' + } + } + + form.send() + + root.passwordDialog.close() + else + alert '{{_("Passwords did not match.")}}' + + e.stop() + + for item in $$(".change_password") + id = item.get("id") + user = id.split("|")[1] + $("user_login").set("value", user) + item.addEvent "click", (e) -> root.passwordDialog.open() + + $('quit-pyload').addEvent "click", (e) -> + new MooDialog.Confirm "{{_('You are really sure you want to quit pyLoad?')}}", -> + new Request.JSON({ + url: '/api/kill' + method: 'get' + }).send() + , -> + e.stop() + + $('restart-pyload').addEvent "click", (e) -> + new MooDialog.Confirm "{{_('Are you sure you want to restart pyLoad?')}}", -> + new Request.JSON({ + url: '/api/restart' + method: 'get' + onSuccess: (data) -> alert "{{_('pyLoad restarted')}}" + }).send() + , -> + e.stop()
\ No newline at end of file diff --git a/pyload/webui/themes/Next/js/admin.js b/pyload/webui/themes/Next/js/admin.js new file mode 100644 index 000000000..d34d310a0 --- /dev/null +++ b/pyload/webui/themes/Next/js/admin.js @@ -0,0 +1,3 @@ +{% autoescape true %} +var root;root=this;window.addEvent("domready",function(){var f,c,b,e,a,d;root.passwordDialog=new MooDialog({destroyOnHide:false});root.passwordDialog.setContent($("password_box"));$("login_password_reset").addEvent("click",function(g){return root.passwordDialog.close()});$("login_password_button").addEvent("click",function(j){var h,i,g;i=$("login_new_password").get("value");g=$("login_new_password2").get("value");if(i===g){h=$("password_form");h.set("send",{onSuccess:function(k){return root.notify.alert("Success",{className:"success"})},onFailure:function(k){return root.notify.alert("Error",{className:"error"})}});h.send();root.passwordDialog.close()}else{alert('{{_("Passwords did not match.")}}')}return j.stop()});d=$$(".change_password");for(e=0,a=d.length;e<a;e++){c=d[e];f=c.get("id");b=f.split("|")[1];$("user_login").set("value",b);c.addEvent("click",function(g){return root.passwordDialog.open()})}$("quit-pyload").addEvent("click",function(g){new MooDialog.Confirm("{{_('You are really sure you want to quit pyLoad?')}}",function(){return new Request.JSON({url:"/api/kill",method:"get"}).send()},function(){});return g.stop()});return $("restart-pyload").addEvent("click",function(g){new MooDialog.Confirm("{{_('Are you sure you want to restart pyLoad?')}}",function(){return new Request.JSON({url:"/api/restart",method:"get",onSuccess:function(h){return alert("{{_('pyLoad restarted')}}")}}).send()},function(){});return g.stop()})}); +{% endautoescape %}
\ No newline at end of file diff --git a/pyload/webui/themes/Next/js/base.coffee b/pyload/webui/themes/Next/js/base.coffee new file mode 100644 index 000000000..3b5d33e82 --- /dev/null +++ b/pyload/webui/themes/Next/js/base.coffee @@ -0,0 +1,173 @@ +# External scope +root = this + +# helper functions +humanFileSize = (size) -> + filesizename = new Array("B", "KiB", "MiB", "GiB", "TiB", "PiB") + loga = Math.log(size) / Math.log(1024) + i = Math.floor(loga) + a = Math.pow(1024, i) + if size is 0 then "0 B" else (Math.round(size * 100 / a) / 100 + " " + filesizename[i]) + +parseUri = () -> + oldString = $("add_links").value + regxp = new RegExp('(ht|f)tp(s?):\/\/[a-zA-Z0-9\-\.\/\?=_&%#]+[<| |\"|\'|\r|\n|\t]{1}', 'g') + resu = oldString.match regxp + return if resu == null + res = ""; + + for part in resu + if part.indexOf(" ") != -1 + res = res + part.replace(" ", " \n") + else if part.indexOf("\t") != -1 + res = res + part.replace("\t", " \n") + else if part.indexOf("\r") != -1 + res = res + part.replace("\r", " \n") + else if part.indexOf("\"") != -1 + res = res + part.replace("\"", " \n") + else if part.indexOf("<") != -1 + res = res + part.replace("<", " \n") + else if part.indexOf("'") != -1 + res = res + part.replace("'", " \n") + else + res = res + part.replace("\n", " \n") + + $("add_links").value = res; + + +Array::remove = (from, to) -> + rest = this.slice((to || from) + 1 || this.length) + this.length = from < 0 ? this.length + from : from + return [] if this.length == 0 + return this.push.apply(this, rest) + + +document.addEvent "domready", -> + + # global notification + root.notify = new Purr { + 'mode': 'top' + 'position': 'center' + } + + root.captchaBox = new MooDialog {destroyOnHide: false} + root.captchaBox.setContent $ 'cap_box' + + root.addBox = new MooDialog {destroyOnHide: false} + root.addBox.setContent $ 'add_box' + + $('add_form').onsubmit = -> + $('add_form').target = 'upload_target' + if $('add_name').value is "" and $('add_file').value is "" + alert '{{_("Please Enter a packagename.")}}' + return false + else + root.addBox.close() + return true + + $('add_reset').addEvent 'click', -> root.addBox.close() + + $('action_add').addEvent 'click', -> $("add_form").reset(); root.addBox.open() + $('action_play').addEvent 'click', -> new Request({method: 'get', url: '/api/unpauseServer'}).send() + $('action_cancel').addEvent 'click', -> new Request({method: 'get', url: '/api/stopAllDownloads'}).send() + $('action_stop').addEvent 'click', -> new Request({method: 'get', url: '/api/pauseServer'}).send() + + + # captcha events + + $('cap_info').addEvent 'click', -> + load_captcha "get", "" + root.captchaBox.open() + $('cap_reset').addEvent 'click', -> root.captchaBox.close() + $('cap_form').addEvent 'submit', (e) -> + submit_captcha() + e.stop() + + $('cap_positional').addEvent 'click', on_captcha_click + + new Request.JSON({ + url: "/json/status" + onSuccess: LoadJsonToContent + secure: false + async: true + initialDelay: 0 + delay: 4000 + limit: 3000 + }).startTimer() + +LoadJsonToContent = (data) -> + $("speed").set 'text', humanFileSize(data.speed)+"/s" + $("aktiv").set 'text', data.active + $("aktiv_from").set 'text', data.queue + $("aktiv_total").set 'text', data.total + + if data.captcha + if $("cap_info").getStyle("display") != "inline" + $("cap_info").setStyle 'display', 'inline' + root.notify.alert '{{_("New Captcha Request")}}', { + 'className': 'notify' + } + else + $("cap_info").setStyle 'display', 'none' + + + if data.download + $("time").set 'text', ' {{_("on")}}' + $("time").setStyle 'background-color', "#8ffc25" + else + $("time").set 'text', ' {{_("off")}}' + $("time").setStyle 'background-color', "#fc6e26" + + if data.reconnect + $("reconnect").set 'text', ' {{_("on")}}' + $("reconnect").setStyle 'background-color', "#8ffc25" + else + $("reconnect").set 'text', ' {{_("off")}}' + $("reconnect").setStyle 'background-color', "#fc6e26" + + return null + + +set_captcha = (data) -> + $('cap_id').set 'value', data.id + if (data.result_type is 'textual') + $('cap_textual_img').set 'src', data.src + $('cap_title').set 'text', '{{_("Please read the text on the captcha.")}}' + $('cap_submit').setStyle 'display', 'inline' + $('cap_textual').setStyle 'display', 'block' + $('cap_positional').setStyle 'display', 'none' + + else if (data.result_type == 'positional') + $('cap_positional_img').set('src', data.src) + $('cap_title').set('text', '{{_("Please click on the right captcha position.")}}') + $('cap_submit').setStyle('display', 'none') + $('cap_textual').setStyle('display', 'none') + + +load_captcha = (method, post) -> + new Request.JSON({ + url: "/json/set_captcha" + onSuccess: (data) -> set_captcha(data) if data.captcha else clear_captcha() + secure: false + async: true + method: method + }).send(post) + +clear_captcha = -> + $('cap_textual').setStyle 'display', 'none' + $('cap_textual_img').set 'src', '' + $('cap_positional').setStyle 'display', 'none' + $('cap_positional_img').set 'src', '' + $('cap_title').set 'text', '{{_("No Captchas to read.")}}' + +submit_captcha = -> + load_captcha("post", "cap_id=" + $('cap_id').get('value') + "&cap_result=" + $('cap_result').get('value') ); + $('cap_result').set('value', '') + false + +on_captcha_click = (e) -> + position = e.target.getPosition() + x = e.page.x - position.x + y = e.page.y - position.y + $('cap_result').value = x + "," + y + submit_captcha()
\ No newline at end of file diff --git a/pyload/webui/themes/Next/js/base.js b/pyload/webui/themes/Next/js/base.js new file mode 100644 index 000000000..c68b1047a --- /dev/null +++ b/pyload/webui/themes/Next/js/base.js @@ -0,0 +1,3 @@ +{% autoescape true %} +var LoadJsonToContent,clear_captcha,humanFileSize,load_captcha,on_captcha_click,parseUri,root,set_captcha,submit_captcha;root=this;humanFileSize=function(f){var c,d,e,b;d=new Array("B","KiB","MiB","GiB","TiB","PiB");b=Math.log(f)/Math.log(1024);e=Math.floor(b);c=Math.pow(1024,e);if(f===0){return"0 B"}else{return Math.round(f*100/c)/100+" "+d[e]}};parseUri=function(){var b,c,g,e,d,f,a;b=$("add_links").value;g=new RegExp("(ht|f)tp(s?)://[a-zA-Z0-9-./?=_&%#]+[<| |\"|'|\r|\n|\t]{1}","g");d=b.match(g);if(d===null){return}e="";for(f=0,a=d.length;f<a;f++){c=d[f];if(c.indexOf(" ")!==-1){e=e+c.replace(" "," \n")}else{if(c.indexOf("\t")!==-1){e=e+c.replace("\t"," \n")}else{if(c.indexOf("\r")!==-1){e=e+c.replace("\r"," \n")}else{if(c.indexOf('"')!==-1){e=e+c.replace('"'," \n")}else{if(c.indexOf("<")!==-1){e=e+c.replace("<"," \n")}else{if(c.indexOf("'")!==-1){e=e+c.replace("'"," \n")}else{e=e+c.replace("\n"," \n")}}}}}}}return $("add_links").value=e};Array.prototype.remove=function(d,c){var a,b;a=this.slice((c||d)+1||this.length);this.length=(b=d<0)!=null?b:this.length+{from:d};if(this.length===0){return[]}return this.push.apply(this,a)};document.addEvent("domready",function(){root.notify=new Purr({mode:"top",position:"center"});root.captchaBox=new MooDialog({destroyOnHide:false});root.captchaBox.setContent($("cap_box"));root.addBox=new MooDialog({destroyOnHide:false});root.addBox.setContent($("add_box"));$("add_form").onsubmit=function(){$("add_form").target="upload_target";if($("add_name").value===""&&$("add_file").value===""){alert('{{_("Please Enter a packagename.")}}');return false}else{root.addBox.close();return true}};$("add_reset").addEvent("click",function(){return root.addBox.close()});$("action_add").addEvent("click",function(){$("add_form").reset();return root.addBox.open()});$("action_play").addEvent("click",function(){return new Request({method:"get",url:"/api/unpauseServer"}).send()});$("action_cancel").addEvent("click",function(){return new Request({method:"get",url:"/api/stopAllDownloads"}).send()});$("action_stop").addEvent("click",function(){return new Request({method:"get",url:"/api/pauseServer"}).send()});$("cap_info").addEvent("click",function(){load_captcha("get","");return root.captchaBox.open()});$("cap_reset").addEvent("click",function(){return root.captchaBox.close()});$("cap_form").addEvent("submit",function(a){submit_captcha();return a.stop()});$("cap_positional").addEvent("click",on_captcha_click);return new Request.JSON({url:"/json/status",onSuccess:LoadJsonToContent,secure:false,async:true,initialDelay:0,delay:4000,limit:3000}).startTimer()});LoadJsonToContent=function(a){$("speed").set("text",humanFileSize(a.speed)+"/s");$("aktiv").set("text",a.active);$("aktiv_from").set("text",a.queue);$("aktiv_total").set("text",a.total);if(a.captcha){if($("cap_info").getStyle("display")!=="inline"){$("cap_info").setStyle("display","inline");root.notify.alert('{{_("New Captcha Request")}}',{className:"notify"})}}else{$("cap_info").setStyle("display","none")}if(a.download){$("time").set("text",' {{_("on")}}');$("time").setStyle("background-color","#8ffc25")}else{$("time").set("text",' {{_("off")}}');$("time").setStyle("background-color","#fc6e26")}if(a.reconnect){$("reconnect").set("text",' {{_("on")}}');$("reconnect").setStyle("background-color","#8ffc25")}else{$("reconnect").set("text",' {{_("off")}}');$("reconnect").setStyle("background-color","#fc6e26")}return null};set_captcha=function(a){$("cap_id").set("value",a.id);if(a.result_type==="textual"){$("cap_textual_img").set("src",a.src);$("cap_title").set("text",'{{_("Please read the text on the captcha.")}}');$("cap_submit").setStyle("display","inline");$("cap_textual").setStyle("display","block");return $("cap_positional").setStyle("display","none")}else{if(a.result_type==="positional"){$("cap_positional_img").set("src",a.src);$("cap_title").set("text",'{{_("Please click on the right captcha position.")}}');$("cap_submit").setStyle("display","none");return $("cap_textual").setStyle("display","none")}}};load_captcha=function(b,a){return new Request.JSON({url:"/json/set_captcha",onSuccess:function(c){return set_captcha(c)(c.captcha?void 0:clear_captcha())},secure:false,async:true,method:b}).send(a)};clear_captcha=function(){$("cap_textual").setStyle("display","none");$("cap_textual_img").set("src","");$("cap_positional").setStyle("display","none");$("cap_positional_img").set("src","");return $("cap_title").set("text",'{{_("No Captchas to read.")}}')};submit_captcha=function(){load_captcha("post","cap_id="+$("cap_id").get("value")+"&cap_result="+$("cap_result").get("value"));$("cap_result").set("value","");return false};on_captcha_click=function(c){var b,a,d;b=c.target.getPosition();a=c.page.x-b.x;d=c.page.y-b.y;$("cap_result").value=a+","+d;return submit_captcha()}; +{% endautoescape %}
\ No newline at end of file diff --git a/pyload/webui/themes/Next/js/filemanager.js b/pyload/webui/themes/Next/js/filemanager.js new file mode 100644 index 000000000..ed64ab69d --- /dev/null +++ b/pyload/webui/themes/Next/js/filemanager.js @@ -0,0 +1,291 @@ +var load, rename_box, confirm_box; + +document.addEvent("domready", function() { + load = new Fx.Tween($("load-indicator"), {link: "cancel"}); + load.set("opacity", 0); + + rename_box = new Fx.Tween($('rename_box')); + confirm_box = new Fx.Tween($('confirm_box')); + $('rename_reset').addEvent('click', function() { + hide_rename_box() + }); + $('delete_reset').addEvent('click', function() { + hide_confirm_box() + }); + + /*$('filemanager_actions_list').getChildren("li").each(function(action) { + var action_name = action.className; + if(functions[action.className] != undefined) + { + action.addEvent('click', functions[action.className]); + } + });*/ +}); + +function indicateLoad() { + //$("load-indicator").reveal(); + load.start("opacity", 1) +} + +function indicateFinish() { + load.start("opacity", 0) +} + +function indicateSuccess() { + indicateFinish(); + notify.alert('{{_("Success")}}.', { + 'className': 'success' + }); +} + +function indicateFail() { + indicateFinish(); + notify.alert('{{_("Failed")}}.', { + 'className': 'error' + }); +} + +function show_rename_box() { + bg_show(); + $("rename_box").setStyle('display', 'block'); + rename_box.start('opacity', 1) +} + +function hide_rename_box() { + bg_hide(); + rename_box.start('opacity', 0).chain(function() { + $('rename_box').setStyle('display', 'none'); + }); +} + +function show_confirm_box() { + bg_show(); + $("confirm_box").setStyle('display', 'block'); + confirm_box.start('opacity', 1) +} + +function hide_confirm_box() { + bg_hide(); + confirm_box.start('opacity', 0).chain(function() { + $('confirm_box').setStyle('display', 'none'); + }); +} + +var FilemanagerUI = new Class({ + initialize: function(url, type) { + this.url = url; + this.type = type; + this.directories = []; + this.files = []; + this.parseChildren(); + }, + + parseChildren: function() { + $("directories-list").getChildren("li.folder").each(function(ele) { + var path = ele.getElements("input.path")[0].get("value"); + var name = ele.getElements("input.name")[0].get("value"); + this.directories.push(new Item(this, path, name, ele)) + }.bind(this)); + + $("directories-list").getChildren("li.file").each(function(ele) { + var path = ele.getElements("input.path")[0].get("value"); + var name = ele.getElements("input.name")[0].get("value"); + this.files.push(new Item(this, path, name, ele)) + }.bind(this)); + } +}); + +var Item = new Class({ + initialize: function(ui, path, name, ele) { + this.ui = ui; + this.path = path; + this.name = name; + this.ele = ele; + this.directories = []; + this.files = []; + this.actions = new Array(); + this.actions["delete"] = this.del; + this.actions["rename"] = this.rename; + this.actions["mkdir"] = this.mkdir; + this.parseElement(); + + var pname = this.ele.getElements("span")[0]; + this.buttons = new Fx.Tween(this.ele.getElements(".buttons")[0], {link: "cancel"}); + this.buttons.set("opacity", 0); + + pname.addEvent("mouseenter", function(e) { + this.buttons.start("opacity", 1) + }.bind(this)); + + pname.addEvent("mouseleave", function(e) { + this.buttons.start("opacity", 0) + }.bind(this)); + + }, + + parseElement: function() { + this.ele.getChildren('span span.buttons img').each(function(img) { + img.addEvent('click', this.actions[img.className].bind(this)); + }, this); + + //click on the directory name must open the directory itself + this.ele.getElements('b')[0].addEvent('click', this.toggle.bind(this)); + + //iterate over child directories + var uls = this.ele.getElements('ul'); + if(uls.length > 0) + { + uls[0].getChildren("li.folder").each(function(fld) { + var path = fld.getElements("input.path")[0].get("value"); + var name = fld.getElements("input.name")[0].get("value"); + this.directories.push(new Item(this, path, name, fld)); + }.bind(this)); + uls[0].getChildren("li.file").each(function(fld) { + var path = fld.getElements("input.path")[0].get("value"); + var name = fld.getElements("input.name")[0].get("value"); + this.files.push(new Item(this, path, name, fld)); + }.bind(this)); + } + }, + + reorderElements: function() { + //TODO sort the main ul again (to keep data ordered after renaming something) + }, + + del: function(event) { + $("confirm_form").removeEvents("submit"); + $("confirm_form").addEvent("submit", this.deleteDirectory.bind(this)); + + $$("#confirm_form p").set('html', '{{_(("Are you sure you want to delete the selected item?"))}}'); + + show_confirm_box(); + event.stop(); + }, + + deleteDirectory: function(event) { + hide_confirm_box(); + new Request.JSON({ + method: 'POST', + url: "/json/filemanager/delete", + data: {"path": this.path, "name": this.name}, + onSuccess: function(data) { + if(data.response == "success") + { + new Fx.Tween(this.ele).start('opacity', 0); + var ul = this.ele.parentNode; + this.ele.dispose(); + //if this was the only child, add a "empty folder" div + if(!ul.getChildren('li')[0]) + { + var div = new Element("div", { 'html': '{{ _("Folder is empty") }}' }); + div.replaces(ul); + } + + indicateSuccess(); + } else + { + //error from json code... + indicateFail(); + } + }.bind(this), + onFailure: indicateFail + }).send(); + + event.stop(); + }, + + rename: function(event) { + $("rename_form").removeEvents("submit"); + $("rename_form").addEvent("submit", this.renameDirectory.bind(this)); + + $("path").set("value", this.path); + $("old_name").set("value", this.name); + $("new_name").set("value", this.name); + + show_rename_box(); + event.stop(); + }, + + renameDirectory: function(event) { + hide_rename_box(); + new Request.JSON({ + method: 'POST', + url: "/json/filemanager/rename", + onSuccess: function(data) { + if(data.response == "success") + { + this.name = $("new_name").get("value"); + this.ele.getElements("b")[0].set('html', $("new_name").get("value")); + this.reorderElements(); + indicateSuccess(); + } else + { + //error from json code... + indicateFail(); + } + }.bind(this), + onFailure: indicateFail + }).send($("rename_form").toQueryString()); + + event.stop(); + }, + + mkdir: function(event) { + new Request.JSON({ + method: 'POST', + url: "/json/filemanager/mkdir", + data: {"path": this.path + "/" + this.name, "name": '{{_("New folder")}}'}, + onSuccess: function(data) { + if(data.response == "success") + { + new Request.HTML({ + method: 'POST', + url: "/filemanager/get_dir", + data: {"path": data.path, "name": data.name}, + onSuccess: function(li) { + //add node as first child of ul + var ul = this.ele.getChildren('ul')[0]; + if(!ul) + { + //remove the "Folder Empty" div + this.ele.getChildren('div').dispose(); + + //create new ul to contain subfolder + ul = new Element("ul"); + ul.inject(this.ele, 'bottom'); + } + li[0].inject(ul, 'top'); + + //add directory as a subdirectory of the current item + this.directories.push(new Item(this.ui, data.path, data.name, ul.firstChild)); + }.bind(this), + onFailure: indicateFail + }).send(); + indicateSuccess(); + } else + { + //error from json code... + indicateFail(); + } + }.bind(this), + onFailure: indicateFail + }).send(); + + event.stop(); + }, + + toggle: function() { + var child = this.ele.getElement('ul'); + if(child == null) + child = this.ele.getElement('div'); + + if(child != null) + { + if (child.getStyle('display') == "block") { + child.dissolve(); + } else { + child.reveal(); + } + } + } +}); diff --git a/pyload/webui/themes/Next/js/package.js b/pyload/webui/themes/Next/js/package.js new file mode 100644 index 000000000..384207882 --- /dev/null +++ b/pyload/webui/themes/Next/js/package.js @@ -0,0 +1,397 @@ +var root = this; + +document.addEvent("domready", function() { + root.load = new Fx.Tween($("load-indicator"), {link: "cancel"}); + root.load.set("opacity", 0); + + + root.packageBox = new MooDialog({destroyOnHide: false}); + root.packageBox.setContent($('pack_box')); + + $('pack_reset').addEvent('click', function() { + $('pack_form').reset(); + root.packageBox.close(); + }); +}); + +function indicateLoad() { + //$("load-indicator").reveal(); + root.load.start("opacity", 1) +} + +function indicateFinish() { + root.load.start("opacity", 0) +} + +function indicateSuccess() { + indicateFinish(); + root.notify.alert('{{_("Success")}}.', { + 'className': 'success' + }); +} + +function indicateFail() { + indicateFinish(); + root.notify.alert('{{_("Failed")}}.', { + 'className': 'error' + }); +} + +var PackageUI = new Class({ + initialize: function(url, type) { + this.url = url; + this.type = type; + this.packages = []; + this.parsePackages(); + + this.sorts = new Sortables($("package-list"), { + constrain: false, + clone: true, + revert: true, + opacity: 0.4, + handle: ".package_drag", + onComplete: this.saveSort.bind(this) + }); + + $("del_finished").addEvent("click", this.deleteFinished.bind(this)); + $("restart_failed").addEvent("click", this.restartFailed.bind(this)); + + }, + + parsePackages: function() { + $("package-list").getChildren("li").each(function(ele) { + var id = ele.getFirst().get("id").match(/[0-9]+/); + this.packages.push(new Package(this, id, ele)) + }.bind(this)) + }, + + loadPackages: function() { + }, + + deleteFinished: function() { + indicateLoad(); + new Request.JSON({ + method: 'get', + url: '/api/deleteFinished', + onSuccess: function(data) { + if (data.length > 0) { + window.location.reload() + } else { + this.packages.each(function(pack) { + pack.close(); + }); + indicateSuccess(); + } + }.bind(this), + onFailure: indicateFail + }).send(); + }, + + restartFailed: function() { + indicateLoad(); + new Request.JSON({ + method: 'get', + url: '/api/restartFailed', + onSuccess: function(data) { + this.packages.each(function(pack) { + pack.close(); + }); + indicateSuccess(); + }.bind(this), + onFailure: indicateFail + }).send(); + }, + + startSort: function(ele, copy) { + }, + + saveSort: function(ele, copy) { + var order = []; + this.sorts.serialize(function(li, pos) { + if (li == ele && ele.retrieve("order") != pos) { + order.push(ele.retrieve("pid") + "|" + pos) + } + li.store("order", pos) + }); + if (order.length > 0) { + indicateLoad(); + new Request.JSON({ + method: 'get', + url: '/json/package_order/' + order[0], + onSuccess: indicateFinish, + onFailure: indicateFail + }).send(); + } + } + +}); + +var Package = new Class({ + initialize: function(ui, id, ele, data) { + this.ui = ui; + this.id = id; + this.linksLoaded = false; + + if (!ele) { + this.createElement(data); + } else { + this.ele = ele; + this.order = ele.getElements("div.order")[0].get("html"); + this.ele.store("order", this.order); + this.ele.store("pid", this.id); + this.parseElement(); + } + + var pname = this.ele.getElements(".packagename")[0]; + this.buttons = new Fx.Tween(this.ele.getElements(".buttons")[0], {link: "cancel"}); + this.buttons.set("opacity", 0); + + pname.addEvent("mouseenter", function(e) { + this.buttons.start("opacity", 1) + }.bind(this)); + + pname.addEvent("mouseleave", function(e) { + this.buttons.start("opacity", 0) + }.bind(this)); + + + }, + + createElement: function() { + alert("create") + }, + + parseElement: function() { + var imgs = this.ele.getElements('span'); + + this.name = this.ele.getElements('.name')[0]; + this.folder = this.ele.getElements('.folder')[0]; + this.password = this.ele.getElements('.password')[0]; + + imgs[3].addEvent('click', this.deletePackage.bind(this)); + imgs[4].addEvent('click', this.restartPackage.bind(this)); + imgs[5].addEvent('click', this.editPackage.bind(this)); + imgs[6].addEvent('click', this.movePackage.bind(this)); + + this.ele.getElement('.packagename').addEvent('click', this.toggle.bind(this)); + + }, + + loadLinks: function() { + indicateLoad(); + new Request.JSON({ + method: 'get', + url: '/json/package/' + this.id, + onSuccess: this.createLinks.bind(this), + onFailure: indicateFail + }).send(); + }, + + createLinks: function(data) { + var ul = $("sort_children_{id}".substitute({"id": this.id})); + ul.set("html", ""); + data.links.each(function(link) { + link.id = link.fid; + var li = new Element("li", { + "style": { + "margin-left": 0 + } + }); + + if (link.icon == 'arrow_right.png'){ + link.icon = 'glyphicon glyphicon-arrow-right'; + } + if (link.icon == 'status_downloading.png'){ + link.icon = 'glyphicon glyphicon-cloud-download'; + } + if (link.icon == 'status_failed.png'){ + link.icon = 'glyphicon glyphicon-exclamation-sign'; + } + if (link.icon == 'status_finished.png'){ + link.icon = 'glyphicon glyphicon-ok'; + } + if (link.statusmsg == 'queued'){ + link.icon = 'glyphicon glyphicon-time'; + } + if (link.icon == 'status_offline.png'){ + link.icon = 'glyphicon glyphicon-ban-circle'; + } + + + var html = "<span style='' class='child_status'><span style='margin-right: 2px;' class='{icon} sorthandle'></span></span>\n".substitute({"icon": link.icon}); + html += "<span style='font-size: 18px; text-weight:bold'>{name}</span><br /><div class='child_secrow' style='margin-left: 21px; margin-bottom: 7px;'>".substitute({"name": link.name}); + html += "<span class='child_status' style='font-size: 12px; color:#555'>{statusmsg}</span>{error} ".substitute({"statusmsg": link.statusmsg, "error":link.error}); + html += "<span class='child_status' style='font-size: 12px; color:#555'>{format_size}</span>".substitute({"format_size": link.format_size}); + html += "<span class='child_status' style='font-size: 12px; color:#555'> {plugin}</span> ".substitute({"plugin": link.plugin}); + html += "<span class='glyphicon glyphicon-trash' title='{{_("Delete Link")}}' style='cursor: pointer; font-size: 12px; color:#333;' ></span> "; + html += "<span class='glyphicon glyphicon-repeat' title='{{_("Restart Link")}}' style='cursor: pointer; font-size: 12px; color:#333;' ></span></div>"; + + var div = new Element("div", { + "id": "file_" + link.id, + "class": "child", + "html": html + }); + + li.store("order", link.order); + li.store("lid", link.id); + + li.adopt(div); + ul.adopt(li); + }); + this.sorts = new Sortables(ul, { + constrain: false, + clone: true, + revert: true, + opacity: 0.4, + handle: ".sorthandle", + onComplete: this.saveSort.bind(this) + }); + this.registerLinkEvents(); + this.linksLoaded = true; + indicateFinish(); + this.toggle(); + }, + + registerLinkEvents: function() { + this.ele.getElements('.child').each(function(child) { + var lid = child.get('id').match(/[0-9]+/); + var imgs = child.getElements('.child_secrow span'); + imgs[3].addEvent('click', function(e) { + new Request({ + method: 'get', + url: '/api/deleteFiles/[' + this + "]", + onSuccess: function() { + $('file_' + this).nix() + }.bind(this), + onFailure: indicateFail + }).send(); + }.bind(lid)); + + imgs[4].addEvent('click', function(e) { + new Request({ + method: 'get', + url: '/api/restartFile/' + this, + onSuccess: function() { + var ele = $('file_' + this); + var imgs = ele.getElements(".glyphicon"); + imgs[0].set("class", "glyphicon glyphicon-time"); + var spans = ele.getElements(".child_status"); + spans[1].set("html", "queued"); + indicateSuccess(); + }.bind(this), + onFailure: indicateFail + }).send(); + }.bind(lid)); + }); + }, + + toggle: function() { + var child = this.ele.getElement('.children'); + if (child.getStyle('display') == "block") { + child.dissolve(); + } else { + if (!this.linksLoaded) { + this.loadLinks(); + } else { + child.reveal(); + } + } + }, + + + deletePackage: function(event) { + indicateLoad(); + new Request({ + method: 'get', + url: '/api/deletePackages/[' + this.id + "]", + onSuccess: function() { + this.ele.nix(); + indicateFinish(); + }.bind(this), + onFailure: indicateFail + }).send(); + //hide_pack(); + event.stop(); + }, + + restartPackage: function(event) { + indicateLoad(); + new Request({ + method: 'get', + url: '/api/restartPackage/' + this.id, + onSuccess: function() { + this.close(); + indicateSuccess(); + }.bind(this), + onFailure: indicateFail + }).send(); + event.stop(); + }, + + close: function() { + var child = this.ele.getElement('.children'); + if (child.getStyle('display') == "block") { + child.dissolve(); + } + var ul = $("sort_children_{id}".substitute({"id": this.id})); + ul.erase("html"); + this.linksLoaded = false; + }, + + movePackage: function(event) { + indicateLoad(); + new Request({ + method: 'get', + url: '/json/move_package/' + ((this.ui.type + 1) % 2) + "/" + this.id, + onSuccess: function() { + this.ele.nix(); + indicateFinish(); + }.bind(this), + onFailure: indicateFail + }).send(); + event.stop(); + }, + + editPackage: function(event) { + $("pack_form").removeEvents("submit"); + $("pack_form").addEvent("submit", this.savePackage.bind(this)); + + $("pack_id").set("value", this.id); + $("pack_name").set("value", this.name.get("text")); + $("pack_folder").set("value", this.folder.get("text")); + $("pack_pws").set("value", this.password.get("text")); + + root.packageBox.open(); + event.stop(); + }, + + savePackage: function(event) { + $("pack_form").send(); + this.name.set("text", $("pack_name").get("value")); + this.folder.set("text", $("pack_folder").get("value")); + this.password.set("text", $("pack_pws").get("value")); + root.packageBox.close(); + event.stop(); + }, + + saveSort: function(ele, copy) { + var order = []; + this.sorts.serialize(function(li, pos) { + if (li == ele && ele.retrieve("order") != pos) { + order.push(ele.retrieve("lid") + "|" + pos) + } + li.store("order", pos) + }); + if (order.length > 0) { + indicateLoad(); + new Request.JSON({ + method: 'get', + url: '/json/link_order/' + order[0], + onSuccess: indicateFinish, + onFailure: indicateFail + }).send(); + } + } + +}); + diff --git a/pyload/webui/themes/Next/js/settings.coffee b/pyload/webui/themes/Next/js/settings.coffee new file mode 100644 index 000000000..9205233e3 --- /dev/null +++ b/pyload/webui/themes/Next/js/settings.coffee @@ -0,0 +1,107 @@ +root = this + +window.addEvent 'domready', -> + root.accountDialog = new MooDialog {destroyOnHide: false} + root.accountDialog.setContent $ 'account_box' + + new TinyTab $$('#toptabs li a'), $$('#tabs-body > span') + + $$('ul.nav').each (nav) -> + new MooDropMenu nav, { + onOpen: (el) -> el.fade 'in' + onClose: (el) -> el.fade 'out' + onInitialize: (el) -> el.fade('hide').set 'tween', {duration:500} + } + + new SettingsUI() + + +class SettingsUI + constructor: -> + @menu = $$ "#general-menu li" + @menu.append $$ "#plugin-menu li" + + @name = $ "tabsback" + @general = $ "general_form_content" + @plugin = $ "plugin_form_content" + + el.addEvent 'click', @menuClick.bind(this) for el in @menu + + $("general|submit").addEvent "click", @configSubmit.bind(this) + $("plugin|submit").addEvent "click", @configSubmit.bind(this) + + $("account_add").addEvent "click", (e) -> + root.accountDialog.open() + e.stop() + + $("account_reset").addEvent "click", (e) -> + root.accountDialog.close() + + $("account_add_button").addEvent "click", @addAccount.bind(this) + $("account_submit").addEvent "click", @submitAccounts.bind(this) + + + menuClick: (e) -> + [category, section] = e.target.get("id").split("|") + name = e.target.get "text" + + + target = if category is "general" then @general else @plugin + target.dissolve() + + new Request({ + "method" : "get" + "url" : "/json/load_config/#{category}/#{section}" + "onSuccess": (data) => + target.set "html", data + target.reveal() + this.name.set "text", name + }).send() + + + configSubmit: (e) -> + category = e.target.get("id").split("|")[0]; + form = $("#{category}_form"); + + form.set "send", { + "method": "post" + "url": "/json/save_config/#{category}" + "onSuccess" : -> + root.notify.alert '{{ _("Settings saved.")}}', { + 'className': 'success' + } + "onFailure": -> + root.notify.alert '{{ _("Error occured.")}}', { + 'className': 'error' + } + } + form.send() + e.stop() + + addAccount: (e) -> + form = $ "add_account_form" + form.set "send", { + "method": "post" + "onSuccess" : -> window.location.reload() + "onFailure": -> + root.notify.alert '{{_("Error occured.")}}', { + 'className': 'error' + } + } + + form.send() + e.stop() + + submitAccounts: (e) -> + form = $ "account_form" + form.set "send", { + "method": "post", + "onSuccess" : -> window.location.reload() + "onFailure": -> + root.notify.alert('{{ _("Error occured.") }}', { + 'className': 'error' + }); + } + + form.send() + e.stop()
\ No newline at end of file diff --git a/pyload/webui/themes/Next/js/settings.js b/pyload/webui/themes/Next/js/settings.js new file mode 100644 index 000000000..be694d365 --- /dev/null +++ b/pyload/webui/themes/Next/js/settings.js @@ -0,0 +1,3 @@ +{% autoescape true %} +var SettingsUI,root;var __bind=function(a,b){return function(){return a.apply(b,arguments)}};root=this;window.addEvent("domready",function(){root.accountDialog=new MooDialog({destroyOnHide:false});root.accountDialog.setContent($("account_box"));new TinyTab($$("#toptabs li"),$$("#tabs-body > span"));$$("ul.nav").each(function(a){return new MooDropMenu(a,{onOpen:function(b){return b.fade("in")},onClose:function(b){return b.fade("out")},onInitialize:function(b){return b.fade("show").set("tween",{duration:500})}})});return new SettingsUI()});SettingsUI=(function(){function a(){var c,e,b,d;this.menu=$$("#general-menu li");this.menu.append($$("#plugin-menu li"));this.name=$("tabsback");this.general=$("general_form_content");this.plugin=$("plugin_form_content");d=this.menu;for(e=0,b=d.length;e<b;e++){c=d[e];c.addEvent("click",this.menuClick.bind(this))}$("general|submit").addEvent("click",this.configSubmit.bind(this));$("plugin|submit").addEvent("click",this.configSubmit.bind(this));$("account_add").addEvent("click",function(f){root.accountDialog.open();return f.stop()});$("account_reset").addEvent("click",function(f){return root.accountDialog.close()});$("account_add_button").addEvent("click",this.addAccount.bind(this));$("account_submit").addEvent("click",this.submitAccounts.bind(this))}a.prototype.menuClick=function(h){var c,b,g,f,d;d=h.target.get("id").split("|"),c=d[0],g=d[1];b=h.target.get("text");f=c==="general"?this.general:this.plugin;f.dissolve();return new Request({method:"get",url:"/json/load_config/"+c+"/"+g,onSuccess:__bind(function(e){f.set("html",e);f.reveal();return this.name.set("text",b)},this)}).send()};a.prototype.configSubmit=function(d){var c,b;c=d.target.get("id").split("|")[0];b=$(""+c+"_form");b.set("send",{method:"post",url:"/json/save_config/"+c,onSuccess:function(){return root.notify.alert('{{ _("Settings saved.")}}',{className:"success"})},onFailure:function(){return root.notify.alert('{{ _("Error occured.")}}',{className:"error"})}});b.send();return d.stop()};a.prototype.addAccount=function(c){var b;b=$("add_account_form");b.set("send",{method:"post",onSuccess:function(){return window.location.reload()},onFailure:function(){return root.notify.alert('{{_("Error occured.")}}',{className:"error"})}});b.send();return c.stop()};a.prototype.submitAccounts=function(c){var b;b=$("account_form");b.set("send",{method:"post",onSuccess:function(){return window.location.reload()},onFailure:function(){return root.notify.alert('{{ _("Error occured.") }}',{className:"error"})}});b.send();return c.stop()};return a})(); +{% endautoescape %}
\ No newline at end of file diff --git a/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap-theme.css b/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap-theme.css new file mode 100644 index 000000000..bb663496d --- /dev/null +++ b/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap-theme.css @@ -0,0 +1,476 @@ +/*! + * Bootstrap v3.3.2 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ + +.btn-default, +.btn-primary, +.btn-success, +.btn-info, +.btn-warning, +.btn-danger { + text-shadow: 0 -1px 0 rgba(0, 0, 0, .2); + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075); +} +.btn-default:active, +.btn-primary:active, +.btn-success:active, +.btn-info:active, +.btn-warning:active, +.btn-danger:active, +.btn-default.active, +.btn-primary.active, +.btn-success.active, +.btn-info.active, +.btn-warning.active, +.btn-danger.active { + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); +} +.btn-default .badge, +.btn-primary .badge, +.btn-success .badge, +.btn-info .badge, +.btn-warning .badge, +.btn-danger .badge { + text-shadow: none; +} +.btn:active, +.btn.active { + background-image: none; +} +.btn-default { + text-shadow: 0 1px 0 #fff; + background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%); + background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0)); + background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-color: #dbdbdb; + border-color: #ccc; +} +.btn-default:hover, +.btn-default:focus { + background-color: #e0e0e0; + background-position: 0 -15px; +} +.btn-default:active, +.btn-default.active { + background-color: #e0e0e0; + border-color: #dbdbdb; +} +.btn-default.disabled, +.btn-default:disabled, +.btn-default[disabled] { + background-color: #e0e0e0; + background-image: none; +} +.btn-primary { + background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%); + background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88)); + background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-color: #245580; +} +.btn-primary:hover, +.btn-primary:focus { + background-color: #265a88; + background-position: 0 -15px; +} +.btn-primary:active, +.btn-primary.active { + background-color: #265a88; + border-color: #245580; +} +.btn-primary.disabled, +.btn-primary:disabled, +.btn-primary[disabled] { + background-color: #265a88; + background-image: none; +} +.btn-success { + background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%); + background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641)); + background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-color: #3e8f3e; +} +.btn-success:hover, +.btn-success:focus { + background-color: #419641; + background-position: 0 -15px; +} +.btn-success:active, +.btn-success.active { + background-color: #419641; + border-color: #3e8f3e; +} +.btn-success.disabled, +.btn-success:disabled, +.btn-success[disabled] { + background-color: #419641; + background-image: none; +} +.btn-info { + background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); + background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2)); + background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-color: #28a4c9; +} +.btn-info:hover, +.btn-info:focus { + background-color: #2aabd2; + background-position: 0 -15px; +} +.btn-info:active, +.btn-info.active { + background-color: #2aabd2; + border-color: #28a4c9; +} +.btn-info.disabled, +.btn-info:disabled, +.btn-info[disabled] { + background-color: #2aabd2; + background-image: none; +} +.btn-warning { + background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); + background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316)); + background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-color: #e38d13; +} +.btn-warning:hover, +.btn-warning:focus { + background-color: #eb9316; + background-position: 0 -15px; +} +.btn-warning:active, +.btn-warning.active { + background-color: #eb9316; + border-color: #e38d13; +} +.btn-warning.disabled, +.btn-warning:disabled, +.btn-warning[disabled] { + background-color: #eb9316; + background-image: none; +} +.btn-danger { + background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%); + background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a)); + background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-color: #b92c28; +} +.btn-danger:hover, +.btn-danger:focus { + background-color: #c12e2a; + background-position: 0 -15px; +} +.btn-danger:active, +.btn-danger.active { + background-color: #c12e2a; + border-color: #b92c28; +} +.btn-danger.disabled, +.btn-danger:disabled, +.btn-danger[disabled] { + background-color: #c12e2a; + background-image: none; +} +.thumbnail, +.img-thumbnail { + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); + box-shadow: 0 1px 2px rgba(0, 0, 0, .075); +} +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus { + background-color: #e8e8e8; + background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); + background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8)); + background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); + background-repeat: repeat-x; +} +.dropdown-menu > .active > a, +.dropdown-menu > .active > a:hover, +.dropdown-menu > .active > a:focus { + background-color: #2e6da4; + background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); + background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); + background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); + background-repeat: repeat-x; +} +.navbar-default { + background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%); + background-image: -o-linear-gradient(top, #fff 0%, #f8f8f8 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f8f8f8)); + background-image: linear-gradient(to bottom, #fff 0%, #f8f8f8 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075); +} +.navbar-default .navbar-nav > .open > a, +.navbar-default .navbar-nav > .active > a { + background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%); + background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2)); + background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0); + background-repeat: repeat-x; + -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); + box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075); +} +.navbar-brand, +.navbar-nav > li > a { + text-shadow: 0 1px 0 rgba(255, 255, 255, .25); +} +.navbar-inverse { + background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%); + background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222)); + background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0); + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); + background-repeat: repeat-x; +} +.navbar-inverse .navbar-nav > .open > a, +.navbar-inverse .navbar-nav > .active > a { + background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%); + background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#080808), to(#0f0f0f)); + background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0); + background-repeat: repeat-x; + -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); + box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25); +} +.navbar-inverse .navbar-brand, +.navbar-inverse .navbar-nav > li > a { + text-shadow: 0 -1px 0 rgba(0, 0, 0, .25); +} +.navbar-static-top, +.navbar-fixed-top, +.navbar-fixed-bottom { + border-radius: 0; +} +@media (max-width: 767px) { + .navbar .navbar-nav .open .dropdown-menu > .active > a, + .navbar .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #fff; + background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); + background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); + background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); + background-repeat: repeat-x; + } +} +.alert { + text-shadow: 0 1px 0 rgba(255, 255, 255, .2); + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05); +} +.alert-success { + background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); + background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc)); + background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0); + background-repeat: repeat-x; + border-color: #b2dba1; +} +.alert-info { + background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%); + background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0)); + background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0); + background-repeat: repeat-x; + border-color: #9acfea; +} +.alert-warning { + background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); + background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0)); + background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0); + background-repeat: repeat-x; + border-color: #f5e79e; +} +.alert-danger { + background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); + background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3)); + background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0); + background-repeat: repeat-x; + border-color: #dca7a7; +} +.progress { + background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); + background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5)); + background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0); + background-repeat: repeat-x; +} +.progress-bar { + background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%); + background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#286090)); + background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0); + background-repeat: repeat-x; +} +.progress-bar-success { + background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%); + background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44)); + background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0); + background-repeat: repeat-x; +} +.progress-bar-info { + background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); + background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5)); + background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0); + background-repeat: repeat-x; +} +.progress-bar-warning { + background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); + background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f)); + background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0); + background-repeat: repeat-x; +} +.progress-bar-danger { + background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%); + background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c)); + background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0); + background-repeat: repeat-x; +} +.progress-bar-striped { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); +} +.list-group { + border-radius: 4px; + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075); + box-shadow: 0 1px 2px rgba(0, 0, 0, .075); +} +.list-group-item.active, +.list-group-item.active:hover, +.list-group-item.active:focus { + text-shadow: 0 -1px 0 #286090; + background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%); + background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2b669a)); + background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0); + background-repeat: repeat-x; + border-color: #2b669a; +} +.list-group-item.active .badge, +.list-group-item.active:hover .badge, +.list-group-item.active:focus .badge { + text-shadow: none; +} +.panel { + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05); + box-shadow: 0 1px 2px rgba(0, 0, 0, .05); +} +.panel-default > .panel-heading { + background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); + background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8)); + background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0); + background-repeat: repeat-x; +} +.panel-primary > .panel-heading { + background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%); + background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4)); + background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0); + background-repeat: repeat-x; +} +.panel-success > .panel-heading { + background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); + background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6)); + background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0); + background-repeat: repeat-x; +} +.panel-info > .panel-heading { + background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); + background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3)); + background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0); + background-repeat: repeat-x; +} +.panel-warning > .panel-heading { + background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); + background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc)); + background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0); + background-repeat: repeat-x; +} +.panel-danger > .panel-heading { + background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%); + background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc)); + background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0); + background-repeat: repeat-x; +} +.well { + background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); + background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%); + background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5)); + background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0); + background-repeat: repeat-x; + border-color: #dcdcdc; + -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); + box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1); +} +/*# sourceMappingURL=bootstrap-theme.css.map */ diff --git a/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap-theme.css.map b/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap-theme.css.map new file mode 100644 index 000000000..5a12d6317 --- /dev/null +++ b/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap-theme.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["less/theme.less","less/mixins/vendor-prefixes.less","bootstrap-theme.css","less/mixins/gradients.less","less/mixins/reset-filter.less"],"names":[],"mappings":"AAcA;;;;;;EAME,0CAAA;ECgDA,6FAAA;EACQ,qFAAA;EC5DT;AFgBC;;;;;;;;;;;;EC2CA,0DAAA;EACQ,kDAAA;EC7CT;AFVD;;;;;;EAiBI,mBAAA;EECH;AFiCC;;EAEE,wBAAA;EE/BH;AFoCD;EGnDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJiCA,6BAAA;EACA,uBAAA;EAgC2C,2BAAA;EAA2B,oBAAA;EEzBvE;AFLC;;EAEE,2BAAA;EACA,8BAAA;EEOH;AFJC;;EAEE,2BAAA;EACA,uBAAA;EEMH;AFHC;;;EAGE,2BAAA;EACA,wBAAA;EEKH;AFUD;EGpDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJiCA,6BAAA;EACA,uBAAA;EEgCD;AF9BC;;EAEE,2BAAA;EACA,8BAAA;EEgCH;AF7BC;;EAEE,2BAAA;EACA,uBAAA;EE+BH;AF5BC;;;EAGE,2BAAA;EACA,wBAAA;EE8BH;AFdD;EGrDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJiCA,6BAAA;EACA,uBAAA;EEyDD;AFvDC;;EAEE,2BAAA;EACA,8BAAA;EEyDH;AFtDC;;EAEE,2BAAA;EACA,uBAAA;EEwDH;AFrDC;;;EAGE,2BAAA;EACA,wBAAA;EEuDH;AFtCD;EGtDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJiCA,6BAAA;EACA,uBAAA;EEkFD;AFhFC;;EAEE,2BAAA;EACA,8BAAA;EEkFH;AF/EC;;EAEE,2BAAA;EACA,uBAAA;EEiFH;AF9EC;;;EAGE,2BAAA;EACA,wBAAA;EEgFH;AF9DD;EGvDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJiCA,6BAAA;EACA,uBAAA;EE2GD;AFzGC;;EAEE,2BAAA;EACA,8BAAA;EE2GH;AFxGC;;EAEE,2BAAA;EACA,uBAAA;EE0GH;AFvGC;;;EAGE,2BAAA;EACA,wBAAA;EEyGH;AFtFD;EGxDI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EAEA,wHAAA;ECnBF,qEAAA;EJiCA,6BAAA;EACA,uBAAA;EEoID;AFlIC;;EAEE,2BAAA;EACA,8BAAA;EEoIH;AFjIC;;EAEE,2BAAA;EACA,uBAAA;EEmIH;AFhIC;;;EAGE,2BAAA;EACA,wBAAA;EEkIH;AFxGD;;EChBE,oDAAA;EACQ,4CAAA;EC4HT;AFnGD;;EGzEI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EHwEF,2BAAA;EEyGD;AFvGD;;;EG9EI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH8EF,2BAAA;EE6GD;AFpGD;EG3FI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ECnBF,qEAAA;EJ6GA,oBAAA;EC/CA,6FAAA;EACQ,qFAAA;EC0JT;AF/GD;;EG3FI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EF2CF,0DAAA;EACQ,kDAAA;ECoKT;AF5GD;;EAEE,gDAAA;EE8GD;AF1GD;EG9GI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ECnBF,qEAAA;EF+OD;AFlHD;;EG9GI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EF2CF,yDAAA;EACQ,iDAAA;EC0LT;AF5HD;;EAYI,2CAAA;EEoHH;AF/GD;;;EAGE,kBAAA;EEiHD;AF5FD;EAfI;;;IAGE,aAAA;IG3IF,0EAAA;IACA,qEAAA;IACA,+FAAA;IAAA,wEAAA;IACA,6BAAA;IACA,wHAAA;ID0PD;EACF;AFxGD;EACE,+CAAA;ECzGA,4FAAA;EACQ,oFAAA;ECoNT;AFhGD;EGpKI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH4JF,uBAAA;EE4GD;AFvGD;EGrKI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH4JF,uBAAA;EEoHD;AF9GD;EGtKI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH4JF,uBAAA;EE4HD;AFrHD;EGvKI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH4JF,uBAAA;EEoID;AFrHD;EG/KI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDuSH;AFlHD;EGzLI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED8SH;AFxHD;EG1LI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDqTH;AF9HD;EG3LI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED4TH;AFpID;EG5LI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDmUH;AF1ID;EG7LI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED0UH;AF7ID;EGhKI,+MAAA;EACA,0MAAA;EACA,uMAAA;EDgTH;AFzID;EACE,oBAAA;EC5JA,oDAAA;EACQ,4CAAA;ECwST;AF1ID;;;EAGE,+BAAA;EGjNE,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EH+MF,uBAAA;EEgJD;AFrJD;;;EAQI,mBAAA;EEkJH;AFxID;ECjLE,mDAAA;EACQ,2CAAA;EC4TT;AFlID;EG1OI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED+WH;AFxID;EG3OI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDsXH;AF9ID;EG5OI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED6XH;AFpJD;EG7OI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDoYH;AF1JD;EG9OI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;ED2YH;AFhKD;EG/OI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EDkZH;AFhKD;EGtPI,0EAAA;EACA,qEAAA;EACA,+FAAA;EAAA,wEAAA;EACA,6BAAA;EACA,wHAAA;EHoPF,uBAAA;ECzMA,2FAAA;EACQ,mFAAA;ECgXT","file":"bootstrap-theme.css","sourcesContent":["\n//\n// Load core variables and mixins\n// --------------------------------------------------\n\n@import \"variables.less\";\n@import \"mixins.less\";\n\n\n//\n// Buttons\n// --------------------------------------------------\n\n// Common styles\n.btn-default,\n.btn-primary,\n.btn-success,\n.btn-info,\n.btn-warning,\n.btn-danger {\n text-shadow: 0 -1px 0 rgba(0,0,0,.2);\n @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 1px rgba(0,0,0,.075);\n .box-shadow(@shadow);\n\n // Reset the shadow\n &:active,\n &.active {\n .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n }\n\n .badge {\n text-shadow: none;\n }\n}\n\n// Mixin for generating new styles\n.btn-styles(@btn-color: #555) {\n #gradient > .vertical(@start-color: @btn-color; @end-color: darken(@btn-color, 12%));\n .reset-filter(); // Disable gradients for IE9 because filter bleeds through rounded corners; see https://github.com/twbs/bootstrap/issues/10620\n background-repeat: repeat-x;\n border-color: darken(@btn-color, 14%);\n\n &:hover,\n &:focus {\n background-color: darken(@btn-color, 12%);\n background-position: 0 -15px;\n }\n\n &:active,\n &.active {\n background-color: darken(@btn-color, 12%);\n border-color: darken(@btn-color, 14%);\n }\n\n &.disabled,\n &:disabled,\n &[disabled] {\n background-color: darken(@btn-color, 12%);\n background-image: none;\n }\n}\n\n// Common styles\n.btn {\n // Remove the gradient for the pressed/active state\n &:active,\n &.active {\n background-image: none;\n }\n}\n\n// Apply the mixin to the buttons\n.btn-default { .btn-styles(@btn-default-bg); text-shadow: 0 1px 0 #fff; border-color: #ccc; }\n.btn-primary { .btn-styles(@btn-primary-bg); }\n.btn-success { .btn-styles(@btn-success-bg); }\n.btn-info { .btn-styles(@btn-info-bg); }\n.btn-warning { .btn-styles(@btn-warning-bg); }\n.btn-danger { .btn-styles(@btn-danger-bg); }\n\n\n//\n// Images\n// --------------------------------------------------\n\n.thumbnail,\n.img-thumbnail {\n .box-shadow(0 1px 2px rgba(0,0,0,.075));\n}\n\n\n//\n// Dropdowns\n// --------------------------------------------------\n\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n #gradient > .vertical(@start-color: @dropdown-link-hover-bg; @end-color: darken(@dropdown-link-hover-bg, 5%));\n background-color: darken(@dropdown-link-hover-bg, 5%);\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%));\n background-color: darken(@dropdown-link-active-bg, 5%);\n}\n\n\n//\n// Navbar\n// --------------------------------------------------\n\n// Default navbar\n.navbar-default {\n #gradient > .vertical(@start-color: lighten(@navbar-default-bg, 10%); @end-color: @navbar-default-bg);\n .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered\n border-radius: @navbar-border-radius;\n @shadow: inset 0 1px 0 rgba(255,255,255,.15), 0 1px 5px rgba(0,0,0,.075);\n .box-shadow(@shadow);\n\n .navbar-nav > .open > a,\n .navbar-nav > .active > a {\n #gradient > .vertical(@start-color: darken(@navbar-default-link-active-bg, 5%); @end-color: darken(@navbar-default-link-active-bg, 2%));\n .box-shadow(inset 0 3px 9px rgba(0,0,0,.075));\n }\n}\n.navbar-brand,\n.navbar-nav > li > a {\n text-shadow: 0 1px 0 rgba(255,255,255,.25);\n}\n\n// Inverted navbar\n.navbar-inverse {\n #gradient > .vertical(@start-color: lighten(@navbar-inverse-bg, 10%); @end-color: @navbar-inverse-bg);\n .reset-filter(); // Remove gradient in IE<10 to fix bug where dropdowns don't get triggered; see https://github.com/twbs/bootstrap/issues/10257\n\n .navbar-nav > .open > a,\n .navbar-nav > .active > a {\n #gradient > .vertical(@start-color: @navbar-inverse-link-active-bg; @end-color: lighten(@navbar-inverse-link-active-bg, 2.5%));\n .box-shadow(inset 0 3px 9px rgba(0,0,0,.25));\n }\n\n .navbar-brand,\n .navbar-nav > li > a {\n text-shadow: 0 -1px 0 rgba(0,0,0,.25);\n }\n}\n\n// Undo rounded corners in static and fixed navbars\n.navbar-static-top,\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n border-radius: 0;\n}\n\n// Fix active state of dropdown items in collapsed mode\n@media (max-width: @grid-float-breakpoint-max) {\n .navbar .navbar-nav .open .dropdown-menu > .active > a {\n &,\n &:hover,\n &:focus {\n color: #fff;\n #gradient > .vertical(@start-color: @dropdown-link-active-bg; @end-color: darken(@dropdown-link-active-bg, 5%));\n }\n }\n}\n\n\n//\n// Alerts\n// --------------------------------------------------\n\n// Common styles\n.alert {\n text-shadow: 0 1px 0 rgba(255,255,255,.2);\n @shadow: inset 0 1px 0 rgba(255,255,255,.25), 0 1px 2px rgba(0,0,0,.05);\n .box-shadow(@shadow);\n}\n\n// Mixin for generating new styles\n.alert-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 7.5%));\n border-color: darken(@color, 15%);\n}\n\n// Apply the mixin to the alerts\n.alert-success { .alert-styles(@alert-success-bg); }\n.alert-info { .alert-styles(@alert-info-bg); }\n.alert-warning { .alert-styles(@alert-warning-bg); }\n.alert-danger { .alert-styles(@alert-danger-bg); }\n\n\n//\n// Progress bars\n// --------------------------------------------------\n\n// Give the progress background some depth\n.progress {\n #gradient > .vertical(@start-color: darken(@progress-bg, 4%); @end-color: @progress-bg)\n}\n\n// Mixin for generating new styles\n.progress-bar-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 10%));\n}\n\n// Apply the mixin to the progress bars\n.progress-bar { .progress-bar-styles(@progress-bar-bg); }\n.progress-bar-success { .progress-bar-styles(@progress-bar-success-bg); }\n.progress-bar-info { .progress-bar-styles(@progress-bar-info-bg); }\n.progress-bar-warning { .progress-bar-styles(@progress-bar-warning-bg); }\n.progress-bar-danger { .progress-bar-styles(@progress-bar-danger-bg); }\n\n// Reset the striped class because our mixins don't do multiple gradients and\n// the above custom styles override the new `.progress-bar-striped` in v3.2.0.\n.progress-bar-striped {\n #gradient > .striped();\n}\n\n\n//\n// List groups\n// --------------------------------------------------\n\n.list-group {\n border-radius: @border-radius-base;\n .box-shadow(0 1px 2px rgba(0,0,0,.075));\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n text-shadow: 0 -1px 0 darken(@list-group-active-bg, 10%);\n #gradient > .vertical(@start-color: @list-group-active-bg; @end-color: darken(@list-group-active-bg, 7.5%));\n border-color: darken(@list-group-active-border, 7.5%);\n\n .badge {\n text-shadow: none;\n }\n}\n\n\n//\n// Panels\n// --------------------------------------------------\n\n// Common styles\n.panel {\n .box-shadow(0 1px 2px rgba(0,0,0,.05));\n}\n\n// Mixin for generating new styles\n.panel-heading-styles(@color) {\n #gradient > .vertical(@start-color: @color; @end-color: darken(@color, 5%));\n}\n\n// Apply the mixin to the panel headings only\n.panel-default > .panel-heading { .panel-heading-styles(@panel-default-heading-bg); }\n.panel-primary > .panel-heading { .panel-heading-styles(@panel-primary-heading-bg); }\n.panel-success > .panel-heading { .panel-heading-styles(@panel-success-heading-bg); }\n.panel-info > .panel-heading { .panel-heading-styles(@panel-info-heading-bg); }\n.panel-warning > .panel-heading { .panel-heading-styles(@panel-warning-heading-bg); }\n.panel-danger > .panel-heading { .panel-heading-styles(@panel-danger-heading-bg); }\n\n\n//\n// Wells\n// --------------------------------------------------\n\n.well {\n #gradient > .vertical(@start-color: darken(@well-bg, 5%); @end-color: @well-bg);\n border-color: darken(@well-bg, 10%);\n @shadow: inset 0 1px 3px rgba(0,0,0,.05), 0 1px 0 rgba(255,255,255,.1);\n .box-shadow(@shadow);\n}\n","// Vendor Prefixes\n//\n// All vendor mixins are deprecated as of v3.2.0 due to the introduction of\n// Autoprefixer in our Gruntfile. They will be removed in v4.\n\n// - Animations\n// - Backface visibility\n// - Box shadow\n// - Box sizing\n// - Content columns\n// - Hyphens\n// - Placeholder text\n// - Transformations\n// - Transitions\n// - User Select\n\n\n// Animations\n.animation(@animation) {\n -webkit-animation: @animation;\n -o-animation: @animation;\n animation: @animation;\n}\n.animation-name(@name) {\n -webkit-animation-name: @name;\n animation-name: @name;\n}\n.animation-duration(@duration) {\n -webkit-animation-duration: @duration;\n animation-duration: @duration;\n}\n.animation-timing-function(@timing-function) {\n -webkit-animation-timing-function: @timing-function;\n animation-timing-function: @timing-function;\n}\n.animation-delay(@delay) {\n -webkit-animation-delay: @delay;\n animation-delay: @delay;\n}\n.animation-iteration-count(@iteration-count) {\n -webkit-animation-iteration-count: @iteration-count;\n animation-iteration-count: @iteration-count;\n}\n.animation-direction(@direction) {\n -webkit-animation-direction: @direction;\n animation-direction: @direction;\n}\n.animation-fill-mode(@fill-mode) {\n -webkit-animation-fill-mode: @fill-mode;\n animation-fill-mode: @fill-mode;\n}\n\n// Backface visibility\n// Prevent browsers from flickering when using CSS 3D transforms.\n// Default value is `visible`, but can be changed to `hidden`\n\n.backface-visibility(@visibility){\n -webkit-backface-visibility: @visibility;\n -moz-backface-visibility: @visibility;\n backface-visibility: @visibility;\n}\n\n// Drop shadows\n//\n// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's\n// supported browsers that have box shadow capabilities now support it.\n\n.box-shadow(@shadow) {\n -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1\n box-shadow: @shadow;\n}\n\n// Box sizing\n.box-sizing(@boxmodel) {\n -webkit-box-sizing: @boxmodel;\n -moz-box-sizing: @boxmodel;\n box-sizing: @boxmodel;\n}\n\n// CSS3 Content Columns\n.content-columns(@column-count; @column-gap: @grid-gutter-width) {\n -webkit-column-count: @column-count;\n -moz-column-count: @column-count;\n column-count: @column-count;\n -webkit-column-gap: @column-gap;\n -moz-column-gap: @column-gap;\n column-gap: @column-gap;\n}\n\n// Optional hyphenation\n.hyphens(@mode: auto) {\n word-wrap: break-word;\n -webkit-hyphens: @mode;\n -moz-hyphens: @mode;\n -ms-hyphens: @mode; // IE10+\n -o-hyphens: @mode;\n hyphens: @mode;\n}\n\n// Placeholder text\n.placeholder(@color: @input-color-placeholder) {\n // Firefox\n &::-moz-placeholder {\n color: @color;\n opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526\n }\n &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+\n &::-webkit-input-placeholder { color: @color; } // Safari and Chrome\n}\n\n// Transformations\n.scale(@ratio) {\n -webkit-transform: scale(@ratio);\n -ms-transform: scale(@ratio); // IE9 only\n -o-transform: scale(@ratio);\n transform: scale(@ratio);\n}\n.scale(@ratioX; @ratioY) {\n -webkit-transform: scale(@ratioX, @ratioY);\n -ms-transform: scale(@ratioX, @ratioY); // IE9 only\n -o-transform: scale(@ratioX, @ratioY);\n transform: scale(@ratioX, @ratioY);\n}\n.scaleX(@ratio) {\n -webkit-transform: scaleX(@ratio);\n -ms-transform: scaleX(@ratio); // IE9 only\n -o-transform: scaleX(@ratio);\n transform: scaleX(@ratio);\n}\n.scaleY(@ratio) {\n -webkit-transform: scaleY(@ratio);\n -ms-transform: scaleY(@ratio); // IE9 only\n -o-transform: scaleY(@ratio);\n transform: scaleY(@ratio);\n}\n.skew(@x; @y) {\n -webkit-transform: skewX(@x) skewY(@y);\n -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+\n -o-transform: skewX(@x) skewY(@y);\n transform: skewX(@x) skewY(@y);\n}\n.translate(@x; @y) {\n -webkit-transform: translate(@x, @y);\n -ms-transform: translate(@x, @y); // IE9 only\n -o-transform: translate(@x, @y);\n transform: translate(@x, @y);\n}\n.translate3d(@x; @y; @z) {\n -webkit-transform: translate3d(@x, @y, @z);\n transform: translate3d(@x, @y, @z);\n}\n.rotate(@degrees) {\n -webkit-transform: rotate(@degrees);\n -ms-transform: rotate(@degrees); // IE9 only\n -o-transform: rotate(@degrees);\n transform: rotate(@degrees);\n}\n.rotateX(@degrees) {\n -webkit-transform: rotateX(@degrees);\n -ms-transform: rotateX(@degrees); // IE9 only\n -o-transform: rotateX(@degrees);\n transform: rotateX(@degrees);\n}\n.rotateY(@degrees) {\n -webkit-transform: rotateY(@degrees);\n -ms-transform: rotateY(@degrees); // IE9 only\n -o-transform: rotateY(@degrees);\n transform: rotateY(@degrees);\n}\n.perspective(@perspective) {\n -webkit-perspective: @perspective;\n -moz-perspective: @perspective;\n perspective: @perspective;\n}\n.perspective-origin(@perspective) {\n -webkit-perspective-origin: @perspective;\n -moz-perspective-origin: @perspective;\n perspective-origin: @perspective;\n}\n.transform-origin(@origin) {\n -webkit-transform-origin: @origin;\n -moz-transform-origin: @origin;\n -ms-transform-origin: @origin; // IE9 only\n transform-origin: @origin;\n}\n\n\n// Transitions\n\n.transition(@transition) {\n -webkit-transition: @transition;\n -o-transition: @transition;\n transition: @transition;\n}\n.transition-property(@transition-property) {\n -webkit-transition-property: @transition-property;\n transition-property: @transition-property;\n}\n.transition-delay(@transition-delay) {\n -webkit-transition-delay: @transition-delay;\n transition-delay: @transition-delay;\n}\n.transition-duration(@transition-duration) {\n -webkit-transition-duration: @transition-duration;\n transition-duration: @transition-duration;\n}\n.transition-timing-function(@timing-function) {\n -webkit-transition-timing-function: @timing-function;\n transition-timing-function: @timing-function;\n}\n.transition-transform(@transition) {\n -webkit-transition: -webkit-transform @transition;\n -moz-transition: -moz-transform @transition;\n -o-transition: -o-transform @transition;\n transition: transform @transition;\n}\n\n\n// User select\n// For selecting text on the page\n\n.user-select(@select) {\n -webkit-user-select: @select;\n -moz-user-select: @select;\n -ms-user-select: @select; // IE10+\n user-select: @select;\n}\n",".btn-default,\n.btn-primary,\n.btn-success,\n.btn-info,\n.btn-warning,\n.btn-danger {\n text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.btn-default:active,\n.btn-primary:active,\n.btn-success:active,\n.btn-info:active,\n.btn-warning:active,\n.btn-danger:active,\n.btn-default.active,\n.btn-primary.active,\n.btn-success.active,\n.btn-info.active,\n.btn-warning.active,\n.btn-danger.active {\n -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn-default .badge,\n.btn-primary .badge,\n.btn-success .badge,\n.btn-info .badge,\n.btn-warning .badge,\n.btn-danger .badge {\n text-shadow: none;\n}\n.btn:active,\n.btn.active {\n background-image: none;\n}\n.btn-default {\n background-image: -webkit-linear-gradient(top, #ffffff 0%, #e0e0e0 100%);\n background-image: -o-linear-gradient(top, #ffffff 0%, #e0e0e0 100%);\n background-image: linear-gradient(to bottom, #ffffff 0%, #e0e0e0 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #dbdbdb;\n text-shadow: 0 1px 0 #fff;\n border-color: #ccc;\n}\n.btn-default:hover,\n.btn-default:focus {\n background-color: #e0e0e0;\n background-position: 0 -15px;\n}\n.btn-default:active,\n.btn-default.active {\n background-color: #e0e0e0;\n border-color: #dbdbdb;\n}\n.btn-default.disabled,\n.btn-default:disabled,\n.btn-default[disabled] {\n background-color: #e0e0e0;\n background-image: none;\n}\n.btn-primary {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #245580;\n}\n.btn-primary:hover,\n.btn-primary:focus {\n background-color: #265a88;\n background-position: 0 -15px;\n}\n.btn-primary:active,\n.btn-primary.active {\n background-color: #265a88;\n border-color: #245580;\n}\n.btn-primary.disabled,\n.btn-primary:disabled,\n.btn-primary[disabled] {\n background-color: #265a88;\n background-image: none;\n}\n.btn-success {\n background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);\n background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%);\n background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #3e8f3e;\n}\n.btn-success:hover,\n.btn-success:focus {\n background-color: #419641;\n background-position: 0 -15px;\n}\n.btn-success:active,\n.btn-success.active {\n background-color: #419641;\n border-color: #3e8f3e;\n}\n.btn-success.disabled,\n.btn-success:disabled,\n.btn-success[disabled] {\n background-color: #419641;\n background-image: none;\n}\n.btn-info {\n background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);\n background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);\n background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #28a4c9;\n}\n.btn-info:hover,\n.btn-info:focus {\n background-color: #2aabd2;\n background-position: 0 -15px;\n}\n.btn-info:active,\n.btn-info.active {\n background-color: #2aabd2;\n border-color: #28a4c9;\n}\n.btn-info.disabled,\n.btn-info:disabled,\n.btn-info[disabled] {\n background-color: #2aabd2;\n background-image: none;\n}\n.btn-warning {\n background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);\n background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);\n background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #e38d13;\n}\n.btn-warning:hover,\n.btn-warning:focus {\n background-color: #eb9316;\n background-position: 0 -15px;\n}\n.btn-warning:active,\n.btn-warning.active {\n background-color: #eb9316;\n border-color: #e38d13;\n}\n.btn-warning.disabled,\n.btn-warning:disabled,\n.btn-warning[disabled] {\n background-color: #eb9316;\n background-image: none;\n}\n.btn-danger {\n background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);\n background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%);\n background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n background-repeat: repeat-x;\n border-color: #b92c28;\n}\n.btn-danger:hover,\n.btn-danger:focus {\n background-color: #c12e2a;\n background-position: 0 -15px;\n}\n.btn-danger:active,\n.btn-danger.active {\n background-color: #c12e2a;\n border-color: #b92c28;\n}\n.btn-danger.disabled,\n.btn-danger:disabled,\n.btn-danger[disabled] {\n background-color: #c12e2a;\n background-image: none;\n}\n.thumbnail,\n.img-thumbnail {\n -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n}\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);\n background-color: #e8e8e8;\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n background-color: #2e6da4;\n}\n.navbar-default {\n background-image: -webkit-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);\n background-image: -o-linear-gradient(top, #ffffff 0%, #f8f8f8 100%);\n background-image: linear-gradient(to bottom, #ffffff 0%, #f8f8f8 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.15), 0 1px 5px rgba(0, 0, 0, 0.075);\n}\n.navbar-default .navbar-nav > .open > a,\n.navbar-default .navbar-nav > .active > a {\n background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);\n background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);\n background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);\n -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.075);\n}\n.navbar-brand,\n.navbar-nav > li > a {\n text-shadow: 0 1px 0 rgba(255, 255, 255, 0.25);\n}\n.navbar-inverse {\n background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222222 100%);\n background-image: -o-linear-gradient(top, #3c3c3c 0%, #222222 100%);\n background-image: linear-gradient(to bottom, #3c3c3c 0%, #222222 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n}\n.navbar-inverse .navbar-nav > .open > a,\n.navbar-inverse .navbar-nav > .active > a {\n background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%);\n background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%);\n background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);\n -webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);\n box-shadow: inset 0 3px 9px rgba(0, 0, 0, 0.25);\n}\n.navbar-inverse .navbar-brand,\n.navbar-inverse .navbar-nav > li > a {\n text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);\n}\n.navbar-static-top,\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n border-radius: 0;\n}\n@media (max-width: 767px) {\n .navbar .navbar-nav .open .dropdown-menu > .active > a,\n .navbar .navbar-nav .open .dropdown-menu > .active > a:hover,\n .navbar .navbar-nav .open .dropdown-menu > .active > a:focus {\n color: #fff;\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n }\n}\n.alert {\n text-shadow: 0 1px 0 rgba(255, 255, 255, 0.2);\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 1px 2px rgba(0, 0, 0, 0.05);\n}\n.alert-success {\n background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);\n background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);\n background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);\n border-color: #b2dba1;\n}\n.alert-info {\n background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);\n background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%);\n background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);\n border-color: #9acfea;\n}\n.alert-warning {\n background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);\n background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);\n background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);\n border-color: #f5e79e;\n}\n.alert-danger {\n background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);\n background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);\n background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);\n border-color: #dca7a7;\n}\n.progress {\n background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);\n background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);\n background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);\n}\n.progress-bar {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);\n}\n.progress-bar-success {\n background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);\n background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%);\n background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);\n}\n.progress-bar-info {\n background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);\n background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);\n background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);\n}\n.progress-bar-warning {\n background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);\n background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);\n background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);\n}\n.progress-bar-danger {\n background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);\n background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%);\n background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);\n}\n.progress-bar-striped {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.list-group {\n border-radius: 4px;\n -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.075);\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n text-shadow: 0 -1px 0 #286090;\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);\n border-color: #2b669a;\n}\n.list-group-item.active .badge,\n.list-group-item.active:hover .badge,\n.list-group-item.active:focus .badge {\n text-shadow: none;\n}\n.panel {\n -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);\n}\n.panel-default > .panel-heading {\n background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);\n background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);\n}\n.panel-primary > .panel-heading {\n background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);\n background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);\n}\n.panel-success > .panel-heading {\n background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);\n background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);\n background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);\n}\n.panel-info > .panel-heading {\n background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);\n background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);\n background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);\n}\n.panel-warning > .panel-heading {\n background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);\n background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);\n background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);\n}\n.panel-danger > .panel-heading {\n background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);\n background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%);\n background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);\n}\n.well {\n background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);\n background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);\n background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);\n border-color: #dcdcdc;\n -webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);\n box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.05), 0 1px 0 rgba(255, 255, 255, 0.1);\n}\n/*# sourceMappingURL=bootstrap-theme.css.map */","// Gradients\n\n#gradient {\n\n // Horizontal gradient, from left to right\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n // Vertical gradient, from top to bottom\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n .directional(@start-color: #555; @end-color: #333; @deg: 45deg) {\n background-repeat: repeat-x;\n background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(@deg, @start-color, @end-color); // Opera 12\n background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n }\n .horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .radial(@inner-color: #555; @outer-color: #333) {\n background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color);\n background-image: radial-gradient(circle, @inner-color, @outer-color);\n background-repeat: no-repeat;\n }\n .striped(@color: rgba(255,255,255,.15); @angle: 45deg) {\n background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n }\n}\n","// Reset filters for IE\n//\n// When you need to remove a gradient background, do not forget to use this to reset\n// the IE filter for IE9 and below.\n\n.reset-filter() {\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(enabled = false)\"));\n}\n"]}
\ No newline at end of file diff --git a/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap-theme.min.css b/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap-theme.min.css new file mode 100644 index 000000000..ac8dd5505 --- /dev/null +++ b/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap-theme.min.css @@ -0,0 +1,5 @@ +/*! + * Bootstrap v3.3.2 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */.btn-danger,.btn-default,.btn-info,.btn-primary,.btn-success,.btn-warning{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-success.active,.btn-success:active,.btn-warning.active,.btn-warning:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-danger .badge,.btn-default .badge,.btn-info .badge,.btn-primary .badge,.btn-success .badge,.btn-warning .badge{text-shadow:none}.btn.active,.btn:active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:focus,.btn-default:hover{background-color:#e0e0e0;background-position:0 -15px}.btn-default.active,.btn-default:active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default.disabled,.btn-default:disabled,.btn-default[disabled]{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-o-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#265a88));background-image:linear-gradient(to bottom,#337ab7 0,#265a88 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#245580}.btn-primary:focus,.btn-primary:hover{background-color:#265a88;background-position:0 -15px}.btn-primary.active,.btn-primary:active{background-color:#265a88;border-color:#245580}.btn-primary.disabled,.btn-primary:disabled,.btn-primary[disabled]{background-color:#265a88;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:focus,.btn-success:hover{background-color:#419641;background-position:0 -15px}.btn-success.active,.btn-success:active{background-color:#419641;border-color:#3e8f3e}.btn-success.disabled,.btn-success:disabled,.btn-success[disabled]{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:focus,.btn-info:hover{background-color:#2aabd2;background-position:0 -15px}.btn-info.active,.btn-info:active{background-color:#2aabd2;border-color:#28a4c9}.btn-info.disabled,.btn-info:disabled,.btn-info[disabled]{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:focus,.btn-warning:hover{background-color:#eb9316;background-position:0 -15px}.btn-warning.active,.btn-warning:active{background-color:#eb9316;border-color:#e38d13}.btn-warning.disabled,.btn-warning:disabled,.btn-warning[disabled]{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:focus,.btn-danger:hover{background-color:#c12e2a;background-position:0 -15px}.btn-danger.active,.btn-danger:active{background-color:#c12e2a;border-color:#b92c28}.btn-danger.disabled,.btn-danger:disabled,.btn-danger[disabled]{background-color:#c12e2a;background-image:none}.img-thumbnail,.thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{background-color:#2e6da4;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-o-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dbdbdb),to(#e2e2e2));background-image:linear-gradient(to bottom,#dbdbdb 0,#e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-o-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#080808),to(#0f0f0f));background-image:linear-gradient(to bottom,#080808 0,#0f0f0f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-fixed-bottom,.navbar-fixed-top,.navbar-static-top{border-radius:0}@media (max-width:767px){.navbar .navbar-nav .open .dropdown-menu>.active>a,.navbar .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-o-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#286090));background-image:linear-gradient(to bottom,#337ab7 0,#286090 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{text-shadow:0 -1px 0 #286090;background-image:-webkit-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2b669a));background-image:linear-gradient(to bottom,#337ab7 0,#2b669a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);background-repeat:repeat-x;border-color:#2b669a}.list-group-item.active .badge,.list-group-item.active:focus .badge,.list-group-item.active:hover .badge{text-shadow:none}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)}
\ No newline at end of file diff --git a/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap.css b/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap.css new file mode 100644 index 000000000..c46af7dfb --- /dev/null +++ b/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap.css @@ -0,0 +1,6566 @@ +/*! + * Bootstrap v3.3.2 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ + +/*! normalize.css v3.0.2 | MIT License | git.io/normalize */ +html { + font-family: sans-serif; + -webkit-text-size-adjust: 100%; + -ms-text-size-adjust: 100%; +} +body { + margin: 0; +} +article, +aside, +details, +figcaption, +figure, +footer, +header, +hgroup, +main, +menu, +nav, +section, +summary { + display: block; +} +audio, +canvas, +progress, +video { + display: inline-block; + vertical-align: baseline; +} +audio:not([controls]) { + display: none; + height: 0; +} +[hidden], +template { + display: none; +} +a { + background-color: transparent; +} +a:active, +a:hover { + outline: 0; +} +abbr[title] { + border-bottom: 1px dotted; +} +b, +strong { + font-weight: bold; +} +dfn { + font-style: italic; +} +h1 { + margin: .67em 0; + font-size: 2em; +} +mark { + color: #000; + background: #ff0; +} +small { + font-size: 80%; +} +sub, +sup { + position: relative; + font-size: 75%; + line-height: 0; + vertical-align: baseline; +} +sup { + top: -.5em; +} +sub { + bottom: -.25em; +} +img { + border: 0; +} +svg:not(:root) { + overflow: hidden; +} +figure { + margin: 1em 40px; +} +hr { + height: 0; + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; +} +pre { + overflow: auto; +} +code, +kbd, +pre, +samp { + font-family: monospace, monospace; + font-size: 1em; +} +button, +input, +optgroup, +select, +textarea { + margin: 0; + font: inherit; + color: inherit; +} +button { + overflow: visible; +} +button, +select { + text-transform: none; +} +button, +html input[type="button"], +input[type="reset"], +input[type="submit"] { + -webkit-appearance: button; + cursor: pointer; +} +button[disabled], +html input[disabled] { + cursor: default; +} +button::-moz-focus-inner, +input::-moz-focus-inner { + padding: 0; + border: 0; +} +input { + line-height: normal; +} +input[type="checkbox"], +input[type="radio"] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; + padding: 0; +} +input[type="number"]::-webkit-inner-spin-button, +input[type="number"]::-webkit-outer-spin-button { + height: auto; +} +input[type="search"] { + -webkit-box-sizing: content-box; + -moz-box-sizing: content-box; + box-sizing: content-box; + -webkit-appearance: textfield; +} +input[type="search"]::-webkit-search-cancel-button, +input[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} +fieldset { + padding: .35em .625em .75em; + margin: 0 2px; + border: 1px solid #c0c0c0; +} +legend { + padding: 0; + border: 0; +} +textarea { + overflow: auto; +} +optgroup { + font-weight: bold; +} +table { + border-spacing: 0; + border-collapse: collapse; +} +td, +th { + padding: 0; +} +/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */ +@media print { + *, + *:before, + *:after { + color: #000 !important; + text-shadow: none !important; + background: transparent !important; + -webkit-box-shadow: none !important; + box-shadow: none !important; + } + a, + a:visited { + text-decoration: underline; + } + a[href]:after { + content: " (" attr(href) ")"; + } + abbr[title]:after { + content: " (" attr(title) ")"; + } + a[href^="#"]:after, + a[href^="javascript:"]:after { + content: ""; + } + pre, + blockquote { + border: 1px solid #999; + + page-break-inside: avoid; + } + thead { + display: table-header-group; + } + tr, + img { + page-break-inside: avoid; + } + img { + max-width: 100% !important; + } + p, + h2, + h3 { + orphans: 3; + widows: 3; + } + h2, + h3 { + page-break-after: avoid; + } + select { + background: #fff !important; + } + .navbar { + display: none; + } + .btn > .caret, + .dropup > .btn > .caret { + border-top-color: #000 !important; + } + .label { + border: 1px solid #000; + } + .table { + border-collapse: collapse !important; + } + .table td, + .table th { + background-color: #fff !important; + } + .table-bordered th, + .table-bordered td { + border: 1px solid #ddd !important; + } +} +@font-face { + font-family: 'Glyphicons Halflings'; + + src: url('../fonts/glyphicons-halflings-regular.eot'); + src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg'); +} +.glyphicon { + position: relative; + top: 1px; + display: inline-block; + font-family: 'Glyphicons Halflings'; + font-style: normal; + font-weight: normal; + line-height: 1; + + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} +.glyphicon-asterisk:before { + content: "\2a"; +} +.glyphicon-plus:before { + content: "\2b"; +} +.glyphicon-euro:before, +.glyphicon-eur:before { + content: "\20ac"; +} +.glyphicon-minus:before { + content: "\2212"; +} +.glyphicon-cloud:before { + content: "\2601"; +} +.glyphicon-envelope:before { + content: "\2709"; +} +.glyphicon-pencil:before { + content: "\270f"; +} +.glyphicon-glass:before { + content: "\e001"; +} +.glyphicon-music:before { + content: "\e002"; +} +.glyphicon-search:before { + content: "\e003"; +} +.glyphicon-heart:before { + content: "\e005"; +} +.glyphicon-star:before { + content: "\e006"; +} +.glyphicon-star-empty:before { + content: "\e007"; +} +.glyphicon-user:before { + content: "\e008"; +} +.glyphicon-film:before { + content: "\e009"; +} +.glyphicon-th-large:before { + content: "\e010"; +} +.glyphicon-th:before { + content: "\e011"; +} +.glyphicon-th-list:before { + content: "\e012"; +} +.glyphicon-ok:before { + content: "\e013"; +} +.glyphicon-remove:before { + content: "\e014"; +} +.glyphicon-zoom-in:before { + content: "\e015"; +} +.glyphicon-zoom-out:before { + content: "\e016"; +} +.glyphicon-off:before { + content: "\e017"; +} +.glyphicon-signal:before { + content: "\e018"; +} +.glyphicon-cog:before { + content: "\e019"; +} +.glyphicon-trash:before { + content: "\e020"; +} +.glyphicon-home:before { + content: "\e021"; +} +.glyphicon-file:before { + content: "\e022"; +} +.glyphicon-time:before { + content: "\e023"; +} +.glyphicon-road:before { + content: "\e024"; +} +.glyphicon-download-alt:before { + content: "\e025"; +} +.glyphicon-download:before { + content: "\e026"; +} +.glyphicon-upload:before { + content: "\e027"; +} +.glyphicon-inbox:before { + content: "\e028"; +} +.glyphicon-play-circle:before { + content: "\e029"; +} +.glyphicon-repeat:before { + content: "\e030"; +} +.glyphicon-refresh:before { + content: "\e031"; +} +.glyphicon-list-alt:before { + content: "\e032"; +} +.glyphicon-lock:before { + content: "\e033"; +} +.glyphicon-flag:before { + content: "\e034"; +} +.glyphicon-headphones:before { + content: "\e035"; +} +.glyphicon-volume-off:before { + content: "\e036"; +} +.glyphicon-volume-down:before { + content: "\e037"; +} +.glyphicon-volume-up:before { + content: "\e038"; +} +.glyphicon-qrcode:before { + content: "\e039"; +} +.glyphicon-barcode:before { + content: "\e040"; +} +.glyphicon-tag:before { + content: "\e041"; +} +.glyphicon-tags:before { + content: "\e042"; +} +.glyphicon-book:before { + content: "\e043"; +} +.glyphicon-bookmark:before { + content: "\e044"; +} +.glyphicon-print:before { + content: "\e045"; +} +.glyphicon-camera:before { + content: "\e046"; +} +.glyphicon-font:before { + content: "\e047"; +} +.glyphicon-bold:before { + content: "\e048"; +} +.glyphicon-italic:before { + content: "\e049"; +} +.glyphicon-text-height:before { + content: "\e050"; +} +.glyphicon-text-width:before { + content: "\e051"; +} +.glyphicon-align-left:before { + content: "\e052"; +} +.glyphicon-align-center:before { + content: "\e053"; +} +.glyphicon-align-right:before { + content: "\e054"; +} +.glyphicon-align-justify:before { + content: "\e055"; +} +.glyphicon-list:before { + content: "\e056"; +} +.glyphicon-indent-left:before { + content: "\e057"; +} +.glyphicon-indent-right:before { + content: "\e058"; +} +.glyphicon-facetime-video:before { + content: "\e059"; +} +.glyphicon-picture:before { + content: "\e060"; +} +.glyphicon-map-marker:before { + content: "\e062"; +} +.glyphicon-adjust:before { + content: "\e063"; +} +.glyphicon-tint:before { + content: "\e064"; +} +.glyphicon-edit:before { + content: "\e065"; +} +.glyphicon-share:before { + content: "\e066"; +} +.glyphicon-check:before { + content: "\e067"; +} +.glyphicon-move:before { + content: "\e068"; +} +.glyphicon-step-backward:before { + content: "\e069"; +} +.glyphicon-fast-backward:before { + content: "\e070"; +} +.glyphicon-backward:before { + content: "\e071"; +} +.glyphicon-play:before { + content: "\e072"; +} +.glyphicon-pause:before { + content: "\e073"; +} +.glyphicon-stop:before { + content: "\e074"; +} +.glyphicon-forward:before { + content: "\e075"; +} +.glyphicon-fast-forward:before { + content: "\e076"; +} +.glyphicon-step-forward:before { + content: "\e077"; +} +.glyphicon-eject:before { + content: "\e078"; +} +.glyphicon-chevron-left:before { + content: "\e079"; +} +.glyphicon-chevron-right:before { + content: "\e080"; +} +.glyphicon-plus-sign:before { + content: "\e081"; +} +.glyphicon-minus-sign:before { + content: "\e082"; +} +.glyphicon-remove-sign:before { + content: "\e083"; +} +.glyphicon-ok-sign:before { + content: "\e084"; +} +.glyphicon-question-sign:before { + content: "\e085"; +} +.glyphicon-info-sign:before { + content: "\e086"; +} +.glyphicon-screenshot:before { + content: "\e087"; +} +.glyphicon-remove-circle:before { + content: "\e088"; +} +.glyphicon-ok-circle:before { + content: "\e089"; +} +.glyphicon-ban-circle:before { + content: "\e090"; +} +.glyphicon-arrow-left:before { + content: "\e091"; +} +.glyphicon-arrow-right:before { + content: "\e092"; +} +.glyphicon-arrow-up:before { + content: "\e093"; +} +.glyphicon-arrow-down:before { + content: "\e094"; +} +.glyphicon-share-alt:before { + content: "\e095"; +} +.glyphicon-resize-full:before { + content: "\e096"; +} +.glyphicon-resize-small:before { + content: "\e097"; +} +.glyphicon-exclamation-sign:before { + content: "\e101"; +} +.glyphicon-gift:before { + content: "\e102"; +} +.glyphicon-leaf:before { + content: "\e103"; +} +.glyphicon-fire:before { + content: "\e104"; +} +.glyphicon-eye-open:before { + content: "\e105"; +} +.glyphicon-eye-close:before { + content: "\e106"; +} +.glyphicon-warning-sign:before { + content: "\e107"; +} +.glyphicon-plane:before { + content: "\e108"; +} +.glyphicon-calendar:before { + content: "\e109"; +} +.glyphicon-random:before { + content: "\e110"; +} +.glyphicon-comment:before { + content: "\e111"; +} +.glyphicon-magnet:before { + content: "\e112"; +} +.glyphicon-chevron-up:before { + content: "\e113"; +} +.glyphicon-chevron-down:before { + content: "\e114"; +} +.glyphicon-retweet:before { + content: "\e115"; +} +.glyphicon-shopping-cart:before { + content: "\e116"; +} +.glyphicon-folder-close:before { + content: "\e117"; +} +.glyphicon-folder-open:before { + content: "\e118"; +} +.glyphicon-resize-vertical:before { + content: "\e119"; +} +.glyphicon-resize-horizontal:before { + content: "\e120"; +} +.glyphicon-hdd:before { + content: "\e121"; +} +.glyphicon-bullhorn:before { + content: "\e122"; +} +.glyphicon-bell:before { + content: "\e123"; +} +.glyphicon-certificate:before { + content: "\e124"; +} +.glyphicon-thumbs-up:before { + content: "\e125"; +} +.glyphicon-thumbs-down:before { + content: "\e126"; +} +.glyphicon-hand-right:before { + content: "\e127"; +} +.glyphicon-hand-left:before { + content: "\e128"; +} +.glyphicon-hand-up:before { + content: "\e129"; +} +.glyphicon-hand-down:before { + content: "\e130"; +} +.glyphicon-circle-arrow-right:before { + content: "\e131"; +} +.glyphicon-circle-arrow-left:before { + content: "\e132"; +} +.glyphicon-circle-arrow-up:before { + content: "\e133"; +} +.glyphicon-circle-arrow-down:before { + content: "\e134"; +} +.glyphicon-globe:before { + content: "\e135"; +} +.glyphicon-wrench:before { + content: "\e136"; +} +.glyphicon-tasks:before { + content: "\e137"; +} +.glyphicon-filter:before { + content: "\e138"; +} +.glyphicon-briefcase:before { + content: "\e139"; +} +.glyphicon-fullscreen:before { + content: "\e140"; +} +.glyphicon-dashboard:before { + content: "\e141"; +} +.glyphicon-paperclip:before { + content: "\e142"; +} +.glyphicon-heart-empty:before { + content: "\e143"; +} +.glyphicon-link:before { + content: "\e144"; +} +.glyphicon-phone:before { + content: "\e145"; +} +.glyphicon-pushpin:before { + content: "\e146"; +} +.glyphicon-usd:before { + content: "\e148"; +} +.glyphicon-gbp:before { + content: "\e149"; +} +.glyphicon-sort:before { + content: "\e150"; +} +.glyphicon-sort-by-alphabet:before { + content: "\e151"; +} +.glyphicon-sort-by-alphabet-alt:before { + content: "\e152"; +} +.glyphicon-sort-by-order:before { + content: "\e153"; +} +.glyphicon-sort-by-order-alt:before { + content: "\e154"; +} +.glyphicon-sort-by-attributes:before { + content: "\e155"; +} +.glyphicon-sort-by-attributes-alt:before { + content: "\e156"; +} +.glyphicon-unchecked:before { + content: "\e157"; +} +.glyphicon-expand:before { + content: "\e158"; +} +.glyphicon-collapse-down:before { + content: "\e159"; +} +.glyphicon-collapse-up:before { + content: "\e160"; +} +.glyphicon-log-in:before { + content: "\e161"; +} +.glyphicon-flash:before { + content: "\e162"; +} +.glyphicon-log-out:before { + content: "\e163"; +} +.glyphicon-new-window:before { + content: "\e164"; +} +.glyphicon-record:before { + content: "\e165"; +} +.glyphicon-save:before { + content: "\e166"; +} +.glyphicon-open:before { + content: "\e167"; +} +.glyphicon-saved:before { + content: "\e168"; +} +.glyphicon-import:before { + content: "\e169"; +} +.glyphicon-export:before { + content: "\e170"; +} +.glyphicon-send:before { + content: "\e171"; +} +.glyphicon-floppy-disk:before { + content: "\e172"; +} +.glyphicon-floppy-saved:before { + content: "\e173"; +} +.glyphicon-floppy-remove:before { + content: "\e174"; +} +.glyphicon-floppy-save:before { + content: "\e175"; +} +.glyphicon-floppy-open:before { + content: "\e176"; +} +.glyphicon-credit-card:before { + content: "\e177"; +} +.glyphicon-transfer:before { + content: "\e178"; +} +.glyphicon-cutlery:before { + content: "\e179"; +} +.glyphicon-header:before { + content: "\e180"; +} +.glyphicon-compressed:before { + content: "\e181"; +} +.glyphicon-earphone:before { + content: "\e182"; +} +.glyphicon-phone-alt:before { + content: "\e183"; +} +.glyphicon-tower:before { + content: "\e184"; +} +.glyphicon-stats:before { + content: "\e185"; +} +.glyphicon-sd-video:before { + content: "\e186"; +} +.glyphicon-hd-video:before { + content: "\e187"; +} +.glyphicon-subtitles:before { + content: "\e188"; +} +.glyphicon-sound-stereo:before { + content: "\e189"; +} +.glyphicon-sound-dolby:before { + content: "\e190"; +} +.glyphicon-sound-5-1:before { + content: "\e191"; +} +.glyphicon-sound-6-1:before { + content: "\e192"; +} +.glyphicon-sound-7-1:before { + content: "\e193"; +} +.glyphicon-copyright-mark:before { + content: "\e194"; +} +.glyphicon-registration-mark:before { + content: "\e195"; +} +.glyphicon-cloud-download:before { + content: "\e197"; +} +.glyphicon-cloud-upload:before { + content: "\e198"; +} +.glyphicon-tree-conifer:before { + content: "\e199"; +} +.glyphicon-tree-deciduous:before { + content: "\e200"; +} +.glyphicon-cd:before { + content: "\e201"; +} +.glyphicon-save-file:before { + content: "\e202"; +} +.glyphicon-open-file:before { + content: "\e203"; +} +.glyphicon-level-up:before { + content: "\e204"; +} +.glyphicon-copy:before { + content: "\e205"; +} +.glyphicon-paste:before { + content: "\e206"; +} +.glyphicon-alert:before { + content: "\e209"; +} +.glyphicon-equalizer:before { + content: "\e210"; +} +.glyphicon-king:before { + content: "\e211"; +} +.glyphicon-queen:before { + content: "\e212"; +} +.glyphicon-pawn:before { + content: "\e213"; +} +.glyphicon-bishop:before { + content: "\e214"; +} +.glyphicon-knight:before { + content: "\e215"; +} +.glyphicon-baby-formula:before { + content: "\e216"; +} +.glyphicon-tent:before { + content: "\26fa"; +} +.glyphicon-blackboard:before { + content: "\e218"; +} +.glyphicon-bed:before { + content: "\e219"; +} +.glyphicon-apple:before { + content: "\f8ff"; +} +.glyphicon-erase:before { + content: "\e221"; +} +.glyphicon-hourglass:before { + content: "\231b"; +} +.glyphicon-lamp:before { + content: "\e223"; +} +.glyphicon-duplicate:before { + content: "\e224"; +} +.glyphicon-piggy-bank:before { + content: "\e225"; +} +.glyphicon-scissors:before { + content: "\e226"; +} +.glyphicon-bitcoin:before { + content: "\e227"; +} +.glyphicon-yen:before { + content: "\00a5"; +} +.glyphicon-ruble:before { + content: "\20bd"; +} +.glyphicon-scale:before { + content: "\e230"; +} +.glyphicon-ice-lolly:before { + content: "\e231"; +} +.glyphicon-ice-lolly-tasted:before { + content: "\e232"; +} +.glyphicon-education:before { + content: "\e233"; +} +.glyphicon-option-horizontal:before { + content: "\e234"; +} +.glyphicon-option-vertical:before { + content: "\e235"; +} +.glyphicon-menu-hamburger:before { + content: "\e236"; +} +.glyphicon-modal-window:before { + content: "\e237"; +} +.glyphicon-oil:before { + content: "\e238"; +} +.glyphicon-grain:before { + content: "\e239"; +} +.glyphicon-sunglasses:before { + content: "\e240"; +} +.glyphicon-text-size:before { + content: "\e241"; +} +.glyphicon-text-color:before { + content: "\e242"; +} +.glyphicon-text-background:before { + content: "\e243"; +} +.glyphicon-object-align-top:before { + content: "\e244"; +} +.glyphicon-object-align-bottom:before { + content: "\e245"; +} +.glyphicon-object-align-horizontal:before { + content: "\e246"; +} +.glyphicon-object-align-left:before { + content: "\e247"; +} +.glyphicon-object-align-vertical:before { + content: "\e248"; +} +.glyphicon-object-align-right:before { + content: "\e249"; +} +.glyphicon-triangle-right:before { + content: "\e250"; +} +.glyphicon-triangle-left:before { + content: "\e251"; +} +.glyphicon-triangle-bottom:before { + content: "\e252"; +} +.glyphicon-triangle-top:before { + content: "\e253"; +} +.glyphicon-console:before { + content: "\e254"; +} +.glyphicon-superscript:before { + content: "\e255"; +} +.glyphicon-subscript:before { + content: "\e256"; +} +.glyphicon-menu-left:before { + content: "\e257"; +} +.glyphicon-menu-right:before { + content: "\e258"; +} +.glyphicon-menu-down:before { + content: "\e259"; +} +.glyphicon-menu-up:before { + content: "\e260"; +} +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +*:before, +*:after { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +html { + font-size: 10px; + + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); +} +body { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + line-height: 1.42857143; + color: #333; + background-color: #fff; +} +input, +button, +select, +textarea { + font-family: inherit; + font-size: inherit; + line-height: inherit; +} +a { + color: #337ab7; + text-decoration: none; +} +a:hover, +a:focus { + color: #23527c; + text-decoration: underline; +} +a:focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +figure { + margin: 0; +} +img { + vertical-align: middle; +} +.img-responsive, +.thumbnail > img, +.thumbnail a > img, +.carousel-inner > .item > img, +.carousel-inner > .item > a > img { + display: block; + max-width: 100%; + height: auto; +} +.img-rounded { + border-radius: 6px; +} +.img-thumbnail { + display: inline-block; + max-width: 100%; + height: auto; + padding: 4px; + line-height: 1.42857143; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 4px; + -webkit-transition: all .2s ease-in-out; + -o-transition: all .2s ease-in-out; + transition: all .2s ease-in-out; +} +.img-circle { + border-radius: 50%; +} +hr { + margin-top: 20px; + margin-bottom: 20px; + border: 0; + border-top: 1px solid #eee; +} +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + border: 0; +} +.sr-only-focusable:active, +.sr-only-focusable:focus { + position: static; + width: auto; + height: auto; + margin: 0; + overflow: visible; + clip: auto; +} +h1, +h2, +h3, +h4, +h5, +h6, +.h1, +.h2, +.h3, +.h4, +.h5, +.h6 { + font-family: inherit; + font-weight: 500; + line-height: 1.1; + color: inherit; +} +h1 small, +h2 small, +h3 small, +h4 small, +h5 small, +h6 small, +.h1 small, +.h2 small, +.h3 small, +.h4 small, +.h5 small, +.h6 small, +h1 .small, +h2 .small, +h3 .small, +h4 .small, +h5 .small, +h6 .small, +.h1 .small, +.h2 .small, +.h3 .small, +.h4 .small, +.h5 .small, +.h6 .small { + font-weight: normal; + line-height: 1; + color: #777; +} +h1, +.h1, +h2, +.h2, +h3, +.h3 { + margin-top: 20px; + margin-bottom: 10px; +} +h1 small, +.h1 small, +h2 small, +.h2 small, +h3 small, +.h3 small, +h1 .small, +.h1 .small, +h2 .small, +.h2 .small, +h3 .small, +.h3 .small { + font-size: 65%; +} +h4, +.h4, +h5, +.h5, +h6, +.h6 { + margin-top: 10px; + margin-bottom: 10px; +} +h4 small, +.h4 small, +h5 small, +.h5 small, +h6 small, +.h6 small, +h4 .small, +.h4 .small, +h5 .small, +.h5 .small, +h6 .small, +.h6 .small { + font-size: 75%; +} +h1, +.h1 { + font-size: 36px; +} +h2, +.h2 { + font-size: 30px; +} +h3, +.h3 { + font-size: 24px; +} +h4, +.h4 { + font-size: 18px; +} +h5, +.h5 { + font-size: 14px; +} +h6, +.h6 { + font-size: 12px; +} +p { + margin: 0 0 10px; +} +.lead { + margin-bottom: 20px; + font-size: 16px; + font-weight: 300; + line-height: 1.4; +} +@media (min-width: 768px) { + .lead { + font-size: 21px; + } +} +small, +.small { + font-size: 85%; +} +mark, +.mark { + padding: .2em; + background-color: #fcf8e3; +} +.text-left { + text-align: left; +} +.text-right { + text-align: right; +} +.text-center { + text-align: center; +} +.text-justify { + text-align: justify; +} +.text-nowrap { + white-space: nowrap; +} +.text-lowercase { + text-transform: lowercase; +} +.text-uppercase { + text-transform: uppercase; +} +.text-capitalize { + text-transform: capitalize; +} +.text-muted { + color: #777; +} +.text-primary { + color: #337ab7; +} +a.text-primary:hover { + color: #286090; +} +.text-success { + color: #3c763d; +} +a.text-success:hover { + color: #2b542c; +} +.text-info { + color: #31708f; +} +a.text-info:hover { + color: #245269; +} +.text-warning { + color: #8a6d3b; +} +a.text-warning:hover { + color: #66512c; +} +.text-danger { + color: #a94442; +} +a.text-danger:hover { + color: #843534; +} +.bg-primary { + color: #fff; + background-color: #337ab7; +} +a.bg-primary:hover { + background-color: #286090; +} +.bg-success { + background-color: #dff0d8; +} +a.bg-success:hover { + background-color: #c1e2b3; +} +.bg-info { + background-color: #d9edf7; +} +a.bg-info:hover { + background-color: #afd9ee; +} +.bg-warning { + background-color: #fcf8e3; +} +a.bg-warning:hover { + background-color: #f7ecb5; +} +.bg-danger { + background-color: #f2dede; +} +a.bg-danger:hover { + background-color: #e4b9b9; +} +.page-header { + padding-bottom: 9px; + margin: 40px 0 20px; + border-bottom: 1px solid #eee; +} +ul, +ol { + margin-top: 0; + margin-bottom: 10px; +} +ul ul, +ol ul, +ul ol, +ol ol { + margin-bottom: 0; +} +.list-unstyled { + padding-left: 0; + list-style: none; +} +.list-inline { + padding-left: 0; + margin-left: -5px; + list-style: none; +} +.list-inline > li { + display: inline-block; + padding-right: 5px; + padding-left: 5px; +} +dl { + margin-top: 0; + margin-bottom: 20px; +} +dt, +dd { + line-height: 1.42857143; +} +dt { + font-weight: bold; +} +dd { + margin-left: 0; +} +@media (min-width: 768px) { + .dl-horizontal dt { + float: left; + width: 160px; + overflow: hidden; + clear: left; + text-align: right; + text-overflow: ellipsis; + white-space: nowrap; + } + .dl-horizontal dd { + margin-left: 180px; + } +} +abbr[title], +abbr[data-original-title] { + cursor: help; + border-bottom: 1px dotted #777; +} +.initialism { + font-size: 90%; + text-transform: uppercase; +} +blockquote { + padding: 10px 20px; + margin: 0 0 20px; + font-size: 17.5px; + border-left: 5px solid #eee; +} +blockquote p:last-child, +blockquote ul:last-child, +blockquote ol:last-child { + margin-bottom: 0; +} +blockquote footer, +blockquote small, +blockquote .small { + display: block; + font-size: 80%; + line-height: 1.42857143; + color: #777; +} +blockquote footer:before, +blockquote small:before, +blockquote .small:before { + content: '\2014 \00A0'; +} +.blockquote-reverse, +blockquote.pull-right { + padding-right: 15px; + padding-left: 0; + text-align: right; + border-right: 5px solid #eee; + border-left: 0; +} +.blockquote-reverse footer:before, +blockquote.pull-right footer:before, +.blockquote-reverse small:before, +blockquote.pull-right small:before, +.blockquote-reverse .small:before, +blockquote.pull-right .small:before { + content: ''; +} +.blockquote-reverse footer:after, +blockquote.pull-right footer:after, +.blockquote-reverse small:after, +blockquote.pull-right small:after, +.blockquote-reverse .small:after, +blockquote.pull-right .small:after { + content: '\00A0 \2014'; +} +address { + margin-bottom: 20px; + font-style: normal; + line-height: 1.42857143; +} +code, +kbd, +pre, +samp { + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; +} +code { + padding: 2px 4px; + font-size: 90%; + color: #c7254e; + background-color: #f9f2f4; + border-radius: 4px; +} +kbd { + padding: 2px 4px; + font-size: 90%; + color: #fff; + background-color: #333; + border-radius: 3px; + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .25); +} +kbd kbd { + padding: 0; + font-size: 100%; + font-weight: bold; + -webkit-box-shadow: none; + box-shadow: none; +} +pre { + display: block; + padding: 9.5px; + margin: 0 0 10px; + font-size: 13px; + line-height: 1.42857143; + color: #333; + word-break: break-all; + word-wrap: break-word; + background-color: #f5f5f5; + border: 1px solid #ccc; + border-radius: 4px; +} +pre code { + padding: 0; + font-size: inherit; + color: inherit; + white-space: pre-wrap; + background-color: transparent; + border-radius: 0; +} +.pre-scrollable { + max-height: 340px; + overflow-y: scroll; +} +.container { + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} +@media (min-width: 768px) { + .container { + width: 750px; + } +} +@media (min-width: 992px) { + .container { + width: 970px; + } +} +@media (min-width: 1200px) { + .container { + width: 1170px; + } +} +.container-fluid { + padding-right: 15px; + padding-left: 15px; + margin-right: auto; + margin-left: auto; +} +.row { + margin-right: -15px; + margin-left: -15px; +} +.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 { + position: relative; + min-height: 1px; + padding-right: 15px; + padding-left: 15px; +} +.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 { + float: left; +} +.col-xs-12 { + width: 100%; +} +.col-xs-11 { + width: 91.66666667%; +} +.col-xs-10 { + width: 83.33333333%; +} +.col-xs-9 { + width: 75%; +} +.col-xs-8 { + width: 66.66666667%; +} +.col-xs-7 { + width: 58.33333333%; +} +.col-xs-6 { + width: 50%; +} +.col-xs-5 { + width: 41.66666667%; +} +.col-xs-4 { + width: 33.33333333%; +} +.col-xs-3 { + width: 25%; +} +.col-xs-2 { + width: 16.66666667%; +} +.col-xs-1 { + width: 8.33333333%; +} +.col-xs-pull-12 { + right: 100%; +} +.col-xs-pull-11 { + right: 91.66666667%; +} +.col-xs-pull-10 { + right: 83.33333333%; +} +.col-xs-pull-9 { + right: 75%; +} +.col-xs-pull-8 { + right: 66.66666667%; +} +.col-xs-pull-7 { + right: 58.33333333%; +} +.col-xs-pull-6 { + right: 50%; +} +.col-xs-pull-5 { + right: 41.66666667%; +} +.col-xs-pull-4 { + right: 33.33333333%; +} +.col-xs-pull-3 { + right: 25%; +} +.col-xs-pull-2 { + right: 16.66666667%; +} +.col-xs-pull-1 { + right: 8.33333333%; +} +.col-xs-pull-0 { + right: auto; +} +.col-xs-push-12 { + left: 100%; +} +.col-xs-push-11 { + left: 91.66666667%; +} +.col-xs-push-10 { + left: 83.33333333%; +} +.col-xs-push-9 { + left: 75%; +} +.col-xs-push-8 { + left: 66.66666667%; +} +.col-xs-push-7 { + left: 58.33333333%; +} +.col-xs-push-6 { + left: 50%; +} +.col-xs-push-5 { + left: 41.66666667%; +} +.col-xs-push-4 { + left: 33.33333333%; +} +.col-xs-push-3 { + left: 25%; +} +.col-xs-push-2 { + left: 16.66666667%; +} +.col-xs-push-1 { + left: 8.33333333%; +} +.col-xs-push-0 { + left: auto; +} +.col-xs-offset-12 { + margin-left: 100%; +} +.col-xs-offset-11 { + margin-left: 91.66666667%; +} +.col-xs-offset-10 { + margin-left: 83.33333333%; +} +.col-xs-offset-9 { + margin-left: 75%; +} +.col-xs-offset-8 { + margin-left: 66.66666667%; +} +.col-xs-offset-7 { + margin-left: 58.33333333%; +} +.col-xs-offset-6 { + margin-left: 50%; +} +.col-xs-offset-5 { + margin-left: 41.66666667%; +} +.col-xs-offset-4 { + margin-left: 33.33333333%; +} +.col-xs-offset-3 { + margin-left: 25%; +} +.col-xs-offset-2 { + margin-left: 16.66666667%; +} +.col-xs-offset-1 { + margin-left: 8.33333333%; +} +.col-xs-offset-0 { + margin-left: 0; +} +@media (min-width: 768px) { + .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 { + float: left; + } + .col-sm-12 { + width: 100%; + } + .col-sm-11 { + width: 91.66666667%; + } + .col-sm-10 { + width: 83.33333333%; + } + .col-sm-9 { + width: 75%; + } + .col-sm-8 { + width: 66.66666667%; + } + .col-sm-7 { + width: 58.33333333%; + } + .col-sm-6 { + width: 50%; + } + .col-sm-5 { + width: 41.66666667%; + } + .col-sm-4 { + width: 33.33333333%; + } + .col-sm-3 { + width: 25%; + } + .col-sm-2 { + width: 16.66666667%; + } + .col-sm-1 { + width: 8.33333333%; + } + .col-sm-pull-12 { + right: 100%; + } + .col-sm-pull-11 { + right: 91.66666667%; + } + .col-sm-pull-10 { + right: 83.33333333%; + } + .col-sm-pull-9 { + right: 75%; + } + .col-sm-pull-8 { + right: 66.66666667%; + } + .col-sm-pull-7 { + right: 58.33333333%; + } + .col-sm-pull-6 { + right: 50%; + } + .col-sm-pull-5 { + right: 41.66666667%; + } + .col-sm-pull-4 { + right: 33.33333333%; + } + .col-sm-pull-3 { + right: 25%; + } + .col-sm-pull-2 { + right: 16.66666667%; + } + .col-sm-pull-1 { + right: 8.33333333%; + } + .col-sm-pull-0 { + right: auto; + } + .col-sm-push-12 { + left: 100%; + } + .col-sm-push-11 { + left: 91.66666667%; + } + .col-sm-push-10 { + left: 83.33333333%; + } + .col-sm-push-9 { + left: 75%; + } + .col-sm-push-8 { + left: 66.66666667%; + } + .col-sm-push-7 { + left: 58.33333333%; + } + .col-sm-push-6 { + left: 50%; + } + .col-sm-push-5 { + left: 41.66666667%; + } + .col-sm-push-4 { + left: 33.33333333%; + } + .col-sm-push-3 { + left: 25%; + } + .col-sm-push-2 { + left: 16.66666667%; + } + .col-sm-push-1 { + left: 8.33333333%; + } + .col-sm-push-0 { + left: auto; + } + .col-sm-offset-12 { + margin-left: 100%; + } + .col-sm-offset-11 { + margin-left: 91.66666667%; + } + .col-sm-offset-10 { + margin-left: 83.33333333%; + } + .col-sm-offset-9 { + margin-left: 75%; + } + .col-sm-offset-8 { + margin-left: 66.66666667%; + } + .col-sm-offset-7 { + margin-left: 58.33333333%; + } + .col-sm-offset-6 { + margin-left: 50%; + } + .col-sm-offset-5 { + margin-left: 41.66666667%; + } + .col-sm-offset-4 { + margin-left: 33.33333333%; + } + .col-sm-offset-3 { + margin-left: 25%; + } + .col-sm-offset-2 { + margin-left: 16.66666667%; + } + .col-sm-offset-1 { + margin-left: 8.33333333%; + } + .col-sm-offset-0 { + margin-left: 0; + } +} +@media (min-width: 992px) { + .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 { + float: left; + } + .col-md-12 { + width: 100%; + } + .col-md-11 { + width: 91.66666667%; + } + .col-md-10 { + width: 83.33333333%; + } + .col-md-9 { + width: 75%; + } + .col-md-8 { + width: 66.66666667%; + } + .col-md-7 { + width: 58.33333333%; + } + .col-md-6 { + width: 50%; + } + .col-md-5 { + width: 41.66666667%; + } + .col-md-4 { + width: 33.33333333%; + } + .col-md-3 { + width: 25%; + } + .col-md-2 { + width: 16.66666667%; + } + .col-md-1 { + width: 8.33333333%; + } + .col-md-pull-12 { + right: 100%; + } + .col-md-pull-11 { + right: 91.66666667%; + } + .col-md-pull-10 { + right: 83.33333333%; + } + .col-md-pull-9 { + right: 75%; + } + .col-md-pull-8 { + right: 66.66666667%; + } + .col-md-pull-7 { + right: 58.33333333%; + } + .col-md-pull-6 { + right: 50%; + } + .col-md-pull-5 { + right: 41.66666667%; + } + .col-md-pull-4 { + right: 33.33333333%; + } + .col-md-pull-3 { + right: 25%; + } + .col-md-pull-2 { + right: 16.66666667%; + } + .col-md-pull-1 { + right: 8.33333333%; + } + .col-md-pull-0 { + right: auto; + } + .col-md-push-12 { + left: 100%; + } + .col-md-push-11 { + left: 91.66666667%; + } + .col-md-push-10 { + left: 83.33333333%; + } + .col-md-push-9 { + left: 75%; + } + .col-md-push-8 { + left: 66.66666667%; + } + .col-md-push-7 { + left: 58.33333333%; + } + .col-md-push-6 { + left: 50%; + } + .col-md-push-5 { + left: 41.66666667%; + } + .col-md-push-4 { + left: 33.33333333%; + } + .col-md-push-3 { + left: 25%; + } + .col-md-push-2 { + left: 16.66666667%; + } + .col-md-push-1 { + left: 8.33333333%; + } + .col-md-push-0 { + left: auto; + } + .col-md-offset-12 { + margin-left: 100%; + } + .col-md-offset-11 { + margin-left: 91.66666667%; + } + .col-md-offset-10 { + margin-left: 83.33333333%; + } + .col-md-offset-9 { + margin-left: 75%; + } + .col-md-offset-8 { + margin-left: 66.66666667%; + } + .col-md-offset-7 { + margin-left: 58.33333333%; + } + .col-md-offset-6 { + margin-left: 50%; + } + .col-md-offset-5 { + margin-left: 41.66666667%; + } + .col-md-offset-4 { + margin-left: 33.33333333%; + } + .col-md-offset-3 { + margin-left: 25%; + } + .col-md-offset-2 { + margin-left: 16.66666667%; + } + .col-md-offset-1 { + margin-left: 8.33333333%; + } + .col-md-offset-0 { + margin-left: 0; + } +} +@media (min-width: 1200px) { + .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 { + float: left; + } + .col-lg-12 { + width: 100%; + } + .col-lg-11 { + width: 91.66666667%; + } + .col-lg-10 { + width: 83.33333333%; + } + .col-lg-9 { + width: 75%; + } + .col-lg-8 { + width: 66.66666667%; + } + .col-lg-7 { + width: 58.33333333%; + } + .col-lg-6 { + width: 50%; + } + .col-lg-5 { + width: 41.66666667%; + } + .col-lg-4 { + width: 33.33333333%; + } + .col-lg-3 { + width: 25%; + } + .col-lg-2 { + width: 16.66666667%; + } + .col-lg-1 { + width: 8.33333333%; + } + .col-lg-pull-12 { + right: 100%; + } + .col-lg-pull-11 { + right: 91.66666667%; + } + .col-lg-pull-10 { + right: 83.33333333%; + } + .col-lg-pull-9 { + right: 75%; + } + .col-lg-pull-8 { + right: 66.66666667%; + } + .col-lg-pull-7 { + right: 58.33333333%; + } + .col-lg-pull-6 { + right: 50%; + } + .col-lg-pull-5 { + right: 41.66666667%; + } + .col-lg-pull-4 { + right: 33.33333333%; + } + .col-lg-pull-3 { + right: 25%; + } + .col-lg-pull-2 { + right: 16.66666667%; + } + .col-lg-pull-1 { + right: 8.33333333%; + } + .col-lg-pull-0 { + right: auto; + } + .col-lg-push-12 { + left: 100%; + } + .col-lg-push-11 { + left: 91.66666667%; + } + .col-lg-push-10 { + left: 83.33333333%; + } + .col-lg-push-9 { + left: 75%; + } + .col-lg-push-8 { + left: 66.66666667%; + } + .col-lg-push-7 { + left: 58.33333333%; + } + .col-lg-push-6 { + left: 50%; + } + .col-lg-push-5 { + left: 41.66666667%; + } + .col-lg-push-4 { + left: 33.33333333%; + } + .col-lg-push-3 { + left: 25%; + } + .col-lg-push-2 { + left: 16.66666667%; + } + .col-lg-push-1 { + left: 8.33333333%; + } + .col-lg-push-0 { + left: auto; + } + .col-lg-offset-12 { + margin-left: 100%; + } + .col-lg-offset-11 { + margin-left: 91.66666667%; + } + .col-lg-offset-10 { + margin-left: 83.33333333%; + } + .col-lg-offset-9 { + margin-left: 75%; + } + .col-lg-offset-8 { + margin-left: 66.66666667%; + } + .col-lg-offset-7 { + margin-left: 58.33333333%; + } + .col-lg-offset-6 { + margin-left: 50%; + } + .col-lg-offset-5 { + margin-left: 41.66666667%; + } + .col-lg-offset-4 { + margin-left: 33.33333333%; + } + .col-lg-offset-3 { + margin-left: 25%; + } + .col-lg-offset-2 { + margin-left: 16.66666667%; + } + .col-lg-offset-1 { + margin-left: 8.33333333%; + } + .col-lg-offset-0 { + margin-left: 0; + } +} +table { + background-color: transparent; +} +caption { + padding-top: 8px; + padding-bottom: 8px; + color: #777; + text-align: left; +} +th { + text-align: left; +} +.table { + width: 100%; + max-width: 100%; + margin-bottom: 20px; +} +.table > thead > tr > th, +.table > tbody > tr > th, +.table > tfoot > tr > th, +.table > thead > tr > td, +.table > tbody > tr > td, +.table > tfoot > tr > td { + padding: 8px; + line-height: 1.42857143; + vertical-align: top; + border-top: 1px solid #ddd; +} +.table > thead > tr > th { + vertical-align: bottom; + border-bottom: 2px solid #ddd; +} +.table > caption + thead > tr:first-child > th, +.table > colgroup + thead > tr:first-child > th, +.table > thead:first-child > tr:first-child > th, +.table > caption + thead > tr:first-child > td, +.table > colgroup + thead > tr:first-child > td, +.table > thead:first-child > tr:first-child > td { + border-top: 0; +} +.table > tbody + tbody { + border-top: 2px solid #ddd; +} +.table .table { + background-color: #fff; +} +.table-condensed > thead > tr > th, +.table-condensed > tbody > tr > th, +.table-condensed > tfoot > tr > th, +.table-condensed > thead > tr > td, +.table-condensed > tbody > tr > td, +.table-condensed > tfoot > tr > td { + padding: 5px; +} +.table-bordered { + border: 1px solid #ddd; +} +.table-bordered > thead > tr > th, +.table-bordered > tbody > tr > th, +.table-bordered > tfoot > tr > th, +.table-bordered > thead > tr > td, +.table-bordered > tbody > tr > td, +.table-bordered > tfoot > tr > td { + border: 1px solid #ddd; +} +.table-bordered > thead > tr > th, +.table-bordered > thead > tr > td { + border-bottom-width: 2px; +} +.table-striped > tbody > tr:nth-of-type(odd) { + background-color: #f9f9f9; +} +.table-hover > tbody > tr:hover { + background-color: #f5f5f5; +} +table col[class*="col-"] { + position: static; + display: table-column; + float: none; +} +table td[class*="col-"], +table th[class*="col-"] { + position: static; + display: table-cell; + float: none; +} +.table > thead > tr > td.active, +.table > tbody > tr > td.active, +.table > tfoot > tr > td.active, +.table > thead > tr > th.active, +.table > tbody > tr > th.active, +.table > tfoot > tr > th.active, +.table > thead > tr.active > td, +.table > tbody > tr.active > td, +.table > tfoot > tr.active > td, +.table > thead > tr.active > th, +.table > tbody > tr.active > th, +.table > tfoot > tr.active > th { + background-color: #f5f5f5; +} +.table-hover > tbody > tr > td.active:hover, +.table-hover > tbody > tr > th.active:hover, +.table-hover > tbody > tr.active:hover > td, +.table-hover > tbody > tr:hover > .active, +.table-hover > tbody > tr.active:hover > th { + background-color: #e8e8e8; +} +.table > thead > tr > td.success, +.table > tbody > tr > td.success, +.table > tfoot > tr > td.success, +.table > thead > tr > th.success, +.table > tbody > tr > th.success, +.table > tfoot > tr > th.success, +.table > thead > tr.success > td, +.table > tbody > tr.success > td, +.table > tfoot > tr.success > td, +.table > thead > tr.success > th, +.table > tbody > tr.success > th, +.table > tfoot > tr.success > th { + background-color: #dff0d8; +} +.table-hover > tbody > tr > td.success:hover, +.table-hover > tbody > tr > th.success:hover, +.table-hover > tbody > tr.success:hover > td, +.table-hover > tbody > tr:hover > .success, +.table-hover > tbody > tr.success:hover > th { + background-color: #d0e9c6; +} +.table > thead > tr > td.info, +.table > tbody > tr > td.info, +.table > tfoot > tr > td.info, +.table > thead > tr > th.info, +.table > tbody > tr > th.info, +.table > tfoot > tr > th.info, +.table > thead > tr.info > td, +.table > tbody > tr.info > td, +.table > tfoot > tr.info > td, +.table > thead > tr.info > th, +.table > tbody > tr.info > th, +.table > tfoot > tr.info > th { + background-color: #d9edf7; +} +.table-hover > tbody > tr > td.info:hover, +.table-hover > tbody > tr > th.info:hover, +.table-hover > tbody > tr.info:hover > td, +.table-hover > tbody > tr:hover > .info, +.table-hover > tbody > tr.info:hover > th { + background-color: #c4e3f3; +} +.table > thead > tr > td.warning, +.table > tbody > tr > td.warning, +.table > tfoot > tr > td.warning, +.table > thead > tr > th.warning, +.table > tbody > tr > th.warning, +.table > tfoot > tr > th.warning, +.table > thead > tr.warning > td, +.table > tbody > tr.warning > td, +.table > tfoot > tr.warning > td, +.table > thead > tr.warning > th, +.table > tbody > tr.warning > th, +.table > tfoot > tr.warning > th { + background-color: #fcf8e3; +} +.table-hover > tbody > tr > td.warning:hover, +.table-hover > tbody > tr > th.warning:hover, +.table-hover > tbody > tr.warning:hover > td, +.table-hover > tbody > tr:hover > .warning, +.table-hover > tbody > tr.warning:hover > th { + background-color: #faf2cc; +} +.table > thead > tr > td.danger, +.table > tbody > tr > td.danger, +.table > tfoot > tr > td.danger, +.table > thead > tr > th.danger, +.table > tbody > tr > th.danger, +.table > tfoot > tr > th.danger, +.table > thead > tr.danger > td, +.table > tbody > tr.danger > td, +.table > tfoot > tr.danger > td, +.table > thead > tr.danger > th, +.table > tbody > tr.danger > th, +.table > tfoot > tr.danger > th { + background-color: #f2dede; +} +.table-hover > tbody > tr > td.danger:hover, +.table-hover > tbody > tr > th.danger:hover, +.table-hover > tbody > tr.danger:hover > td, +.table-hover > tbody > tr:hover > .danger, +.table-hover > tbody > tr.danger:hover > th { + background-color: #ebcccc; +} +.table-responsive { + min-height: .01%; + overflow-x: auto; +} +@media screen and (max-width: 767px) { + .table-responsive { + width: 100%; + margin-bottom: 15px; + overflow-y: hidden; + -ms-overflow-style: -ms-autohiding-scrollbar; + border: 1px solid #ddd; + } + .table-responsive > .table { + margin-bottom: 0; + } + .table-responsive > .table > thead > tr > th, + .table-responsive > .table > tbody > tr > th, + .table-responsive > .table > tfoot > tr > th, + .table-responsive > .table > thead > tr > td, + .table-responsive > .table > tbody > tr > td, + .table-responsive > .table > tfoot > tr > td { + white-space: nowrap; + } + .table-responsive > .table-bordered { + border: 0; + } + .table-responsive > .table-bordered > thead > tr > th:first-child, + .table-responsive > .table-bordered > tbody > tr > th:first-child, + .table-responsive > .table-bordered > tfoot > tr > th:first-child, + .table-responsive > .table-bordered > thead > tr > td:first-child, + .table-responsive > .table-bordered > tbody > tr > td:first-child, + .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-left: 0; + } + .table-responsive > .table-bordered > thead > tr > th:last-child, + .table-responsive > .table-bordered > tbody > tr > th:last-child, + .table-responsive > .table-bordered > tfoot > tr > th:last-child, + .table-responsive > .table-bordered > thead > tr > td:last-child, + .table-responsive > .table-bordered > tbody > tr > td:last-child, + .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-right: 0; + } + .table-responsive > .table-bordered > tbody > tr:last-child > th, + .table-responsive > .table-bordered > tfoot > tr:last-child > th, + .table-responsive > .table-bordered > tbody > tr:last-child > td, + .table-responsive > .table-bordered > tfoot > tr:last-child > td { + border-bottom: 0; + } +} +fieldset { + min-width: 0; + padding: 0; + margin: 0; + border: 0; +} +legend { + display: block; + width: 100%; + padding: 0; + margin-bottom: 20px; + font-size: 21px; + line-height: inherit; + color: #333; + border: 0; + border-bottom: 1px solid #e5e5e5; +} +label { + display: inline-block; + max-width: 100%; + margin-bottom: 5px; + font-weight: bold; +} +input[type="search"] { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} +input[type="radio"], +input[type="checkbox"] { + margin: 4px 0 0; + margin-top: 1px \9; + line-height: normal; +} +input[type="file"] { + display: block; +} +input[type="range"] { + display: block; + width: 100%; +} +select[multiple], +select[size] { + height: auto; +} +input[type="file"]:focus, +input[type="radio"]:focus, +input[type="checkbox"]:focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +output { + display: block; + padding-top: 7px; + font-size: 14px; + line-height: 1.42857143; + color: #555; +} +.form-control { + display: block; + width: 100%; + height: 34px; + padding: 6px 12px; + font-size: 14px; + line-height: 1.42857143; + color: #555; + background-color: #fff; + background-image: none; + border: 1px solid #ccc; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + -webkit-transition: border-color ease-in-out .15s, -webkit-box-shadow ease-in-out .15s; + -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; + transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s; +} +.form-control:focus { + border-color: #66afe9; + outline: 0; + -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6); + box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, .6); +} +.form-control::-moz-placeholder { + color: #999; + opacity: 1; +} +.form-control:-ms-input-placeholder { + color: #999; +} +.form-control::-webkit-input-placeholder { + color: #999; +} +.form-control[disabled], +.form-control[readonly], +fieldset[disabled] .form-control { + cursor: not-allowed; + background-color: #eee; + opacity: 1; +} +textarea.form-control { + height: auto; +} +input[type="search"] { + -webkit-appearance: none; +} +@media screen and (-webkit-min-device-pixel-ratio: 0) { + input[type="date"], + input[type="time"], + input[type="datetime-local"], + input[type="month"] { + line-height: 34px; + } + input[type="date"].input-sm, + input[type="time"].input-sm, + input[type="datetime-local"].input-sm, + input[type="month"].input-sm, + .input-group-sm input[type="date"], + .input-group-sm input[type="time"], + .input-group-sm input[type="datetime-local"], + .input-group-sm input[type="month"] { + line-height: 30px; + } + input[type="date"].input-lg, + input[type="time"].input-lg, + input[type="datetime-local"].input-lg, + input[type="month"].input-lg, + .input-group-lg input[type="date"], + .input-group-lg input[type="time"], + .input-group-lg input[type="datetime-local"], + .input-group-lg input[type="month"] { + line-height: 46px; + } +} +.form-group { + margin-bottom: 15px; +} +.radio, +.checkbox { + position: relative; + display: block; + margin-top: 10px; + margin-bottom: 10px; +} +.radio label, +.checkbox label { + min-height: 20px; + padding-left: 20px; + margin-bottom: 0; + font-weight: normal; + cursor: pointer; +} +.radio input[type="radio"], +.radio-inline input[type="radio"], +.checkbox input[type="checkbox"], +.checkbox-inline input[type="checkbox"] { + position: absolute; + margin-top: 4px \9; + margin-left: -20px; +} +.radio + .radio, +.checkbox + .checkbox { + margin-top: -5px; +} +.radio-inline, +.checkbox-inline { + display: inline-block; + padding-left: 20px; + margin-bottom: 0; + font-weight: normal; + vertical-align: middle; + cursor: pointer; +} +.radio-inline + .radio-inline, +.checkbox-inline + .checkbox-inline { + margin-top: 0; + margin-left: 10px; +} +input[type="radio"][disabled], +input[type="checkbox"][disabled], +input[type="radio"].disabled, +input[type="checkbox"].disabled, +fieldset[disabled] input[type="radio"], +fieldset[disabled] input[type="checkbox"] { + cursor: not-allowed; +} +.radio-inline.disabled, +.checkbox-inline.disabled, +fieldset[disabled] .radio-inline, +fieldset[disabled] .checkbox-inline { + cursor: not-allowed; +} +.radio.disabled label, +.checkbox.disabled label, +fieldset[disabled] .radio label, +fieldset[disabled] .checkbox label { + cursor: not-allowed; +} +.form-control-static { + padding-top: 7px; + padding-bottom: 7px; + margin-bottom: 0; +} +.form-control-static.input-lg, +.form-control-static.input-sm { + padding-right: 0; + padding-left: 0; +} +.input-sm { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +select.input-sm { + height: 30px; + line-height: 30px; +} +textarea.input-sm, +select[multiple].input-sm { + height: auto; +} +.form-group-sm .form-control { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +select.form-group-sm .form-control { + height: 30px; + line-height: 30px; +} +textarea.form-group-sm .form-control, +select[multiple].form-group-sm .form-control { + height: auto; +} +.form-group-sm .form-control-static { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; +} +.input-lg { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +select.input-lg { + height: 46px; + line-height: 46px; +} +textarea.input-lg, +select[multiple].input-lg { + height: auto; +} +.form-group-lg .form-control { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +select.form-group-lg .form-control { + height: 46px; + line-height: 46px; +} +textarea.form-group-lg .form-control, +select[multiple].form-group-lg .form-control { + height: auto; +} +.form-group-lg .form-control-static { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; +} +.has-feedback { + position: relative; +} +.has-feedback .form-control { + padding-right: 42.5px; +} +.form-control-feedback { + position: absolute; + top: 0; + right: 0; + z-index: 2; + display: block; + width: 34px; + height: 34px; + line-height: 34px; + text-align: center; + pointer-events: none; +} +.input-lg + .form-control-feedback { + width: 46px; + height: 46px; + line-height: 46px; +} +.input-sm + .form-control-feedback { + width: 30px; + height: 30px; + line-height: 30px; +} +.has-success .help-block, +.has-success .control-label, +.has-success .radio, +.has-success .checkbox, +.has-success .radio-inline, +.has-success .checkbox-inline, +.has-success.radio label, +.has-success.checkbox label, +.has-success.radio-inline label, +.has-success.checkbox-inline label { + color: #3c763d; +} +.has-success .form-control { + border-color: #3c763d; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); +} +.has-success .form-control:focus { + border-color: #2b542c; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #67b168; +} +.has-success .input-group-addon { + color: #3c763d; + background-color: #dff0d8; + border-color: #3c763d; +} +.has-success .form-control-feedback { + color: #3c763d; +} +.has-warning .help-block, +.has-warning .control-label, +.has-warning .radio, +.has-warning .checkbox, +.has-warning .radio-inline, +.has-warning .checkbox-inline, +.has-warning.radio label, +.has-warning.checkbox label, +.has-warning.radio-inline label, +.has-warning.checkbox-inline label { + color: #8a6d3b; +} +.has-warning .form-control { + border-color: #8a6d3b; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); +} +.has-warning .form-control:focus { + border-color: #66512c; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #c0a16b; +} +.has-warning .input-group-addon { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #8a6d3b; +} +.has-warning .form-control-feedback { + color: #8a6d3b; +} +.has-error .help-block, +.has-error .control-label, +.has-error .radio, +.has-error .checkbox, +.has-error .radio-inline, +.has-error .checkbox-inline, +.has-error.radio label, +.has-error.checkbox label, +.has-error.radio-inline label, +.has-error.checkbox-inline label { + color: #a94442; +} +.has-error .form-control { + border-color: #a94442; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075); +} +.has-error .form-control:focus { + border-color: #843534; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483; + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 6px #ce8483; +} +.has-error .input-group-addon { + color: #a94442; + background-color: #f2dede; + border-color: #a94442; +} +.has-error .form-control-feedback { + color: #a94442; +} +.has-feedback label ~ .form-control-feedback { + top: 25px; +} +.has-feedback label.sr-only ~ .form-control-feedback { + top: 0; +} +.help-block { + display: block; + margin-top: 5px; + margin-bottom: 10px; + color: #737373; +} +@media (min-width: 768px) { + .form-inline .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + .form-inline .form-control-static { + display: inline-block; + } + .form-inline .input-group { + display: inline-table; + vertical-align: middle; + } + .form-inline .input-group .input-group-addon, + .form-inline .input-group .input-group-btn, + .form-inline .input-group .form-control { + width: auto; + } + .form-inline .input-group > .form-control { + width: 100%; + } + .form-inline .control-label { + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .radio, + .form-inline .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + } + .form-inline .radio label, + .form-inline .checkbox label { + padding-left: 0; + } + .form-inline .radio input[type="radio"], + .form-inline .checkbox input[type="checkbox"] { + position: relative; + margin-left: 0; + } + .form-inline .has-feedback .form-control-feedback { + top: 0; + } +} +.form-horizontal .radio, +.form-horizontal .checkbox, +.form-horizontal .radio-inline, +.form-horizontal .checkbox-inline { + padding-top: 7px; + margin-top: 0; + margin-bottom: 0; +} +.form-horizontal .radio, +.form-horizontal .checkbox { + min-height: 27px; +} +.form-horizontal .form-group { + margin-right: -15px; + margin-left: -15px; +} +@media (min-width: 768px) { + .form-horizontal .control-label { + padding-top: 7px; + margin-bottom: 0; + text-align: right; + } +} +.form-horizontal .has-feedback .form-control-feedback { + right: 15px; +} +@media (min-width: 768px) { + .form-horizontal .form-group-lg .control-label { + padding-top: 14.333333px; + } +} +@media (min-width: 768px) { + .form-horizontal .form-group-sm .control-label { + padding-top: 6px; + } +} +.btn { + display: inline-block; + padding: 6px 12px; + margin-bottom: 0; + font-size: 14px; + font-weight: normal; + line-height: 1.42857143; + text-align: center; + white-space: nowrap; + vertical-align: middle; + -ms-touch-action: manipulation; + touch-action: manipulation; + cursor: pointer; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + background-image: none; + border: 1px solid transparent; + border-radius: 4px; +} +.btn:focus, +.btn:active:focus, +.btn.active:focus, +.btn.focus, +.btn:active.focus, +.btn.active.focus { + outline: thin dotted; + outline: 5px auto -webkit-focus-ring-color; + outline-offset: -2px; +} +.btn:hover, +.btn:focus, +.btn.focus { + color: #333; + text-decoration: none; +} +.btn:active, +.btn.active { + background-image: none; + outline: 0; + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); +} +.btn.disabled, +.btn[disabled], +fieldset[disabled] .btn { + pointer-events: none; + cursor: not-allowed; + filter: alpha(opacity=65); + -webkit-box-shadow: none; + box-shadow: none; + opacity: .65; +} +.btn-default { + color: #333; + background-color: #fff; + border-color: #ccc; +} +.btn-default:hover, +.btn-default:focus, +.btn-default.focus, +.btn-default:active, +.btn-default.active, +.open > .dropdown-toggle.btn-default { + color: #333; + background-color: #e6e6e6; + border-color: #adadad; +} +.btn-default:active, +.btn-default.active, +.open > .dropdown-toggle.btn-default { + background-image: none; +} +.btn-default.disabled, +.btn-default[disabled], +fieldset[disabled] .btn-default, +.btn-default.disabled:hover, +.btn-default[disabled]:hover, +fieldset[disabled] .btn-default:hover, +.btn-default.disabled:focus, +.btn-default[disabled]:focus, +fieldset[disabled] .btn-default:focus, +.btn-default.disabled.focus, +.btn-default[disabled].focus, +fieldset[disabled] .btn-default.focus, +.btn-default.disabled:active, +.btn-default[disabled]:active, +fieldset[disabled] .btn-default:active, +.btn-default.disabled.active, +.btn-default[disabled].active, +fieldset[disabled] .btn-default.active { + background-color: #fff; + border-color: #ccc; +} +.btn-default .badge { + color: #fff; + background-color: #333; +} +.btn-primary { + color: #fff; + background-color: #337ab7; + border-color: #2e6da4; +} +.btn-primary:hover, +.btn-primary:focus, +.btn-primary.focus, +.btn-primary:active, +.btn-primary.active, +.open > .dropdown-toggle.btn-primary { + color: #fff; + background-color: #286090; + border-color: #204d74; +} +.btn-primary:active, +.btn-primary.active, +.open > .dropdown-toggle.btn-primary { + background-image: none; +} +.btn-primary.disabled, +.btn-primary[disabled], +fieldset[disabled] .btn-primary, +.btn-primary.disabled:hover, +.btn-primary[disabled]:hover, +fieldset[disabled] .btn-primary:hover, +.btn-primary.disabled:focus, +.btn-primary[disabled]:focus, +fieldset[disabled] .btn-primary:focus, +.btn-primary.disabled.focus, +.btn-primary[disabled].focus, +fieldset[disabled] .btn-primary.focus, +.btn-primary.disabled:active, +.btn-primary[disabled]:active, +fieldset[disabled] .btn-primary:active, +.btn-primary.disabled.active, +.btn-primary[disabled].active, +fieldset[disabled] .btn-primary.active { + background-color: #337ab7; + border-color: #2e6da4; +} +.btn-primary .badge { + color: #337ab7; + background-color: #fff; +} +.btn-success { + color: #fff; + background-color: #5cb85c; + border-color: #4cae4c; +} +.btn-success:hover, +.btn-success:focus, +.btn-success.focus, +.btn-success:active, +.btn-success.active, +.open > .dropdown-toggle.btn-success { + color: #fff; + background-color: #449d44; + border-color: #398439; +} +.btn-success:active, +.btn-success.active, +.open > .dropdown-toggle.btn-success { + background-image: none; +} +.btn-success.disabled, +.btn-success[disabled], +fieldset[disabled] .btn-success, +.btn-success.disabled:hover, +.btn-success[disabled]:hover, +fieldset[disabled] .btn-success:hover, +.btn-success.disabled:focus, +.btn-success[disabled]:focus, +fieldset[disabled] .btn-success:focus, +.btn-success.disabled.focus, +.btn-success[disabled].focus, +fieldset[disabled] .btn-success.focus, +.btn-success.disabled:active, +.btn-success[disabled]:active, +fieldset[disabled] .btn-success:active, +.btn-success.disabled.active, +.btn-success[disabled].active, +fieldset[disabled] .btn-success.active { + background-color: #5cb85c; + border-color: #4cae4c; +} +.btn-success .badge { + color: #5cb85c; + background-color: #fff; +} +.btn-info { + color: #fff; + background-color: #5bc0de; + border-color: #46b8da; +} +.btn-info:hover, +.btn-info:focus, +.btn-info.focus, +.btn-info:active, +.btn-info.active, +.open > .dropdown-toggle.btn-info { + color: #fff; + background-color: #31b0d5; + border-color: #269abc; +} +.btn-info:active, +.btn-info.active, +.open > .dropdown-toggle.btn-info { + background-image: none; +} +.btn-info.disabled, +.btn-info[disabled], +fieldset[disabled] .btn-info, +.btn-info.disabled:hover, +.btn-info[disabled]:hover, +fieldset[disabled] .btn-info:hover, +.btn-info.disabled:focus, +.btn-info[disabled]:focus, +fieldset[disabled] .btn-info:focus, +.btn-info.disabled.focus, +.btn-info[disabled].focus, +fieldset[disabled] .btn-info.focus, +.btn-info.disabled:active, +.btn-info[disabled]:active, +fieldset[disabled] .btn-info:active, +.btn-info.disabled.active, +.btn-info[disabled].active, +fieldset[disabled] .btn-info.active { + background-color: #5bc0de; + border-color: #46b8da; +} +.btn-info .badge { + color: #5bc0de; + background-color: #fff; +} +.btn-warning { + color: #fff; + background-color: #f0ad4e; + border-color: #eea236; +} +.btn-warning:hover, +.btn-warning:focus, +.btn-warning.focus, +.btn-warning:active, +.btn-warning.active, +.open > .dropdown-toggle.btn-warning { + color: #fff; + background-color: #ec971f; + border-color: #d58512; +} +.btn-warning:active, +.btn-warning.active, +.open > .dropdown-toggle.btn-warning { + background-image: none; +} +.btn-warning.disabled, +.btn-warning[disabled], +fieldset[disabled] .btn-warning, +.btn-warning.disabled:hover, +.btn-warning[disabled]:hover, +fieldset[disabled] .btn-warning:hover, +.btn-warning.disabled:focus, +.btn-warning[disabled]:focus, +fieldset[disabled] .btn-warning:focus, +.btn-warning.disabled.focus, +.btn-warning[disabled].focus, +fieldset[disabled] .btn-warning.focus, +.btn-warning.disabled:active, +.btn-warning[disabled]:active, +fieldset[disabled] .btn-warning:active, +.btn-warning.disabled.active, +.btn-warning[disabled].active, +fieldset[disabled] .btn-warning.active { + background-color: #f0ad4e; + border-color: #eea236; +} +.btn-warning .badge { + color: #f0ad4e; + background-color: #fff; +} +.btn-danger { + color: #fff; + background-color: #d9534f; + border-color: #d43f3a; +} +.btn-danger:hover, +.btn-danger:focus, +.btn-danger.focus, +.btn-danger:active, +.btn-danger.active, +.open > .dropdown-toggle.btn-danger { + color: #fff; + background-color: #c9302c; + border-color: #ac2925; +} +.btn-danger:active, +.btn-danger.active, +.open > .dropdown-toggle.btn-danger { + background-image: none; +} +.btn-danger.disabled, +.btn-danger[disabled], +fieldset[disabled] .btn-danger, +.btn-danger.disabled:hover, +.btn-danger[disabled]:hover, +fieldset[disabled] .btn-danger:hover, +.btn-danger.disabled:focus, +.btn-danger[disabled]:focus, +fieldset[disabled] .btn-danger:focus, +.btn-danger.disabled.focus, +.btn-danger[disabled].focus, +fieldset[disabled] .btn-danger.focus, +.btn-danger.disabled:active, +.btn-danger[disabled]:active, +fieldset[disabled] .btn-danger:active, +.btn-danger.disabled.active, +.btn-danger[disabled].active, +fieldset[disabled] .btn-danger.active { + background-color: #d9534f; + border-color: #d43f3a; +} +.btn-danger .badge { + color: #d9534f; + background-color: #fff; +} +.btn-link { + font-weight: normal; + color: #337ab7; + border-radius: 0; +} +.btn-link, +.btn-link:active, +.btn-link.active, +.btn-link[disabled], +fieldset[disabled] .btn-link { + background-color: transparent; + -webkit-box-shadow: none; + box-shadow: none; +} +.btn-link, +.btn-link:hover, +.btn-link:focus, +.btn-link:active { + border-color: transparent; +} +.btn-link:hover, +.btn-link:focus { + color: #23527c; + text-decoration: underline; + background-color: transparent; +} +.btn-link[disabled]:hover, +fieldset[disabled] .btn-link:hover, +.btn-link[disabled]:focus, +fieldset[disabled] .btn-link:focus { + color: #777; + text-decoration: none; +} +.btn-lg, +.btn-group-lg > .btn { + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +.btn-sm, +.btn-group-sm > .btn { + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.btn-xs, +.btn-group-xs > .btn { + padding: 1px 5px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +.btn-block { + display: block; + width: 100%; +} +.btn-block + .btn-block { + margin-top: 5px; +} +input[type="submit"].btn-block, +input[type="reset"].btn-block, +input[type="button"].btn-block { + width: 100%; +} +.fade { + opacity: 0; + -webkit-transition: opacity .15s linear; + -o-transition: opacity .15s linear; + transition: opacity .15s linear; +} +.fade.in { + opacity: 1; +} +.collapse { + display: none; + visibility: hidden; +} +.collapse.in { + display: block; + visibility: visible; +} +tr.collapse.in { + display: table-row; +} +tbody.collapse.in { + display: table-row-group; +} +.collapsing { + position: relative; + height: 0; + overflow: hidden; + -webkit-transition-timing-function: ease; + -o-transition-timing-function: ease; + transition-timing-function: ease; + -webkit-transition-duration: .35s; + -o-transition-duration: .35s; + transition-duration: .35s; + -webkit-transition-property: height, visibility; + -o-transition-property: height, visibility; + transition-property: height, visibility; +} +.caret { + display: inline-block; + width: 0; + height: 0; + margin-left: 2px; + vertical-align: middle; + border-top: 4px solid; + border-right: 4px solid transparent; + border-left: 4px solid transparent; +} +.dropup, +.dropdown { + position: relative; +} +.dropdown-toggle:focus { + outline: 0; +} +.dropdown-menu { + position: absolute; + top: 100%; + left: 0; + z-index: 1000; + display: none; + float: left; + min-width: 160px; + padding: 5px 0; + margin: 2px 0 0; + font-size: 14px; + text-align: left; + list-style: none; + background-color: #fff; + -webkit-background-clip: padding-box; + background-clip: padding-box; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, .15); + border-radius: 4px; + -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, .175); + box-shadow: 0 6px 12px rgba(0, 0, 0, .175); +} +.dropdown-menu.pull-right { + right: 0; + left: auto; +} +.dropdown-menu .divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; +} +.dropdown-menu > li > a { + display: block; + padding: 3px 20px; + clear: both; + font-weight: normal; + line-height: 1.42857143; + color: #333; + white-space: nowrap; +} +.dropdown-menu > li > a:hover, +.dropdown-menu > li > a:focus { + color: #262626; + text-decoration: none; + background-color: #f5f5f5; +} +.dropdown-menu > .active > a, +.dropdown-menu > .active > a:hover, +.dropdown-menu > .active > a:focus { + color: #fff; + text-decoration: none; + background-color: #337ab7; + outline: 0; +} +.dropdown-menu > .disabled > a, +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + color: #777; +} +.dropdown-menu > .disabled > a:hover, +.dropdown-menu > .disabled > a:focus { + text-decoration: none; + cursor: not-allowed; + background-color: transparent; + background-image: none; + filter: progid:DXImageTransform.Microsoft.gradient(enabled = false); +} +.open > .dropdown-menu { + display: block; +} +.open > a { + outline: 0; +} +.dropdown-menu-right { + right: 0; + left: auto; +} +.dropdown-menu-left { + right: auto; + left: 0; +} +.dropdown-header { + display: block; + padding: 3px 20px; + font-size: 12px; + line-height: 1.42857143; + color: #777; + white-space: nowrap; +} +.dropdown-backdrop { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 990; +} +.pull-right > .dropdown-menu { + right: 0; + left: auto; +} +.dropup .caret, +.navbar-fixed-bottom .dropdown .caret { + content: ""; + border-top: 0; + border-bottom: 4px solid; +} +.dropup .dropdown-menu, +.navbar-fixed-bottom .dropdown .dropdown-menu { + top: auto; + bottom: 100%; + margin-bottom: 2px; +} +@media (min-width: 768px) { + .navbar-right .dropdown-menu { + right: 0; + left: auto; + } + .navbar-right .dropdown-menu-left { + right: auto; + left: 0; + } +} +.btn-group, +.btn-group-vertical { + position: relative; + display: inline-block; + vertical-align: middle; +} +.btn-group > .btn, +.btn-group-vertical > .btn { + position: relative; + float: left; +} +.btn-group > .btn:hover, +.btn-group-vertical > .btn:hover, +.btn-group > .btn:focus, +.btn-group-vertical > .btn:focus, +.btn-group > .btn:active, +.btn-group-vertical > .btn:active, +.btn-group > .btn.active, +.btn-group-vertical > .btn.active { + z-index: 2; +} +.btn-group .btn + .btn, +.btn-group .btn + .btn-group, +.btn-group .btn-group + .btn, +.btn-group .btn-group + .btn-group { + margin-left: -1px; +} +.btn-toolbar { + margin-left: -5px; +} +.btn-toolbar .btn-group, +.btn-toolbar .input-group { + float: left; +} +.btn-toolbar > .btn, +.btn-toolbar > .btn-group, +.btn-toolbar > .input-group { + margin-left: 5px; +} +.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) { + border-radius: 0; +} +.btn-group > .btn:first-child { + margin-left: 0; +} +.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.btn-group > .btn:last-child:not(:first-child), +.btn-group > .dropdown-toggle:not(:first-child) { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group > .btn-group { + float: left; +} +.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child, +.btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group .dropdown-toggle:active, +.btn-group.open .dropdown-toggle { + outline: 0; +} +.btn-group > .btn + .dropdown-toggle { + padding-right: 8px; + padding-left: 8px; +} +.btn-group > .btn-lg + .dropdown-toggle { + padding-right: 12px; + padding-left: 12px; +} +.btn-group.open .dropdown-toggle { + -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); + box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125); +} +.btn-group.open .dropdown-toggle.btn-link { + -webkit-box-shadow: none; + box-shadow: none; +} +.btn .caret { + margin-left: 0; +} +.btn-lg .caret { + border-width: 5px 5px 0; + border-bottom-width: 0; +} +.dropup .btn-lg .caret { + border-width: 0 5px 5px; +} +.btn-group-vertical > .btn, +.btn-group-vertical > .btn-group, +.btn-group-vertical > .btn-group > .btn { + display: block; + float: none; + width: 100%; + max-width: 100%; +} +.btn-group-vertical > .btn-group > .btn { + float: none; +} +.btn-group-vertical > .btn + .btn, +.btn-group-vertical > .btn + .btn-group, +.btn-group-vertical > .btn-group + .btn, +.btn-group-vertical > .btn-group + .btn-group { + margin-top: -1px; + margin-left: 0; +} +.btn-group-vertical > .btn:not(:first-child):not(:last-child) { + border-radius: 0; +} +.btn-group-vertical > .btn:first-child:not(:last-child) { + border-top-right-radius: 4px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group-vertical > .btn:last-child:not(:first-child) { + border-top-left-radius: 0; + border-top-right-radius: 0; + border-bottom-left-radius: 4px; +} +.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn { + border-radius: 0; +} +.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child, +.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle { + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child { + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.btn-group-justified { + display: table; + width: 100%; + table-layout: fixed; + border-collapse: separate; +} +.btn-group-justified > .btn, +.btn-group-justified > .btn-group { + display: table-cell; + float: none; + width: 1%; +} +.btn-group-justified > .btn-group .btn { + width: 100%; +} +.btn-group-justified > .btn-group .dropdown-menu { + left: auto; +} +[data-toggle="buttons"] > .btn input[type="radio"], +[data-toggle="buttons"] > .btn-group > .btn input[type="radio"], +[data-toggle="buttons"] > .btn input[type="checkbox"], +[data-toggle="buttons"] > .btn-group > .btn input[type="checkbox"] { + position: absolute; + clip: rect(0, 0, 0, 0); + pointer-events: none; +} +.input-group { + position: relative; + display: table; + border-collapse: separate; +} +.input-group[class*="col-"] { + float: none; + padding-right: 0; + padding-left: 0; +} +.input-group .form-control { + position: relative; + z-index: 2; + float: left; + width: 100%; + margin-bottom: 0; +} +.input-group-lg > .form-control, +.input-group-lg > .input-group-addon, +.input-group-lg > .input-group-btn > .btn { + height: 46px; + padding: 10px 16px; + font-size: 18px; + line-height: 1.3333333; + border-radius: 6px; +} +select.input-group-lg > .form-control, +select.input-group-lg > .input-group-addon, +select.input-group-lg > .input-group-btn > .btn { + height: 46px; + line-height: 46px; +} +textarea.input-group-lg > .form-control, +textarea.input-group-lg > .input-group-addon, +textarea.input-group-lg > .input-group-btn > .btn, +select[multiple].input-group-lg > .form-control, +select[multiple].input-group-lg > .input-group-addon, +select[multiple].input-group-lg > .input-group-btn > .btn { + height: auto; +} +.input-group-sm > .form-control, +.input-group-sm > .input-group-addon, +.input-group-sm > .input-group-btn > .btn { + height: 30px; + padding: 5px 10px; + font-size: 12px; + line-height: 1.5; + border-radius: 3px; +} +select.input-group-sm > .form-control, +select.input-group-sm > .input-group-addon, +select.input-group-sm > .input-group-btn > .btn { + height: 30px; + line-height: 30px; +} +textarea.input-group-sm > .form-control, +textarea.input-group-sm > .input-group-addon, +textarea.input-group-sm > .input-group-btn > .btn, +select[multiple].input-group-sm > .form-control, +select[multiple].input-group-sm > .input-group-addon, +select[multiple].input-group-sm > .input-group-btn > .btn { + height: auto; +} +.input-group-addon, +.input-group-btn, +.input-group .form-control { + display: table-cell; +} +.input-group-addon:not(:first-child):not(:last-child), +.input-group-btn:not(:first-child):not(:last-child), +.input-group .form-control:not(:first-child):not(:last-child) { + border-radius: 0; +} +.input-group-addon, +.input-group-btn { + width: 1%; + white-space: nowrap; + vertical-align: middle; +} +.input-group-addon { + padding: 6px 12px; + font-size: 14px; + font-weight: normal; + line-height: 1; + color: #555; + text-align: center; + background-color: #eee; + border: 1px solid #ccc; + border-radius: 4px; +} +.input-group-addon.input-sm { + padding: 5px 10px; + font-size: 12px; + border-radius: 3px; +} +.input-group-addon.input-lg { + padding: 10px 16px; + font-size: 18px; + border-radius: 6px; +} +.input-group-addon input[type="radio"], +.input-group-addon input[type="checkbox"] { + margin-top: 0; +} +.input-group .form-control:first-child, +.input-group-addon:first-child, +.input-group-btn:first-child > .btn, +.input-group-btn:first-child > .btn-group > .btn, +.input-group-btn:first-child > .dropdown-toggle, +.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle), +.input-group-btn:last-child > .btn-group:not(:last-child) > .btn { + border-top-right-radius: 0; + border-bottom-right-radius: 0; +} +.input-group-addon:first-child { + border-right: 0; +} +.input-group .form-control:last-child, +.input-group-addon:last-child, +.input-group-btn:last-child > .btn, +.input-group-btn:last-child > .btn-group > .btn, +.input-group-btn:last-child > .dropdown-toggle, +.input-group-btn:first-child > .btn:not(:first-child), +.input-group-btn:first-child > .btn-group:not(:first-child) > .btn { + border-top-left-radius: 0; + border-bottom-left-radius: 0; +} +.input-group-addon:last-child { + border-left: 0; +} +.input-group-btn { + position: relative; + font-size: 0; + white-space: nowrap; +} +.input-group-btn > .btn { + position: relative; +} +.input-group-btn > .btn + .btn { + margin-left: -1px; +} +.input-group-btn > .btn:hover, +.input-group-btn > .btn:focus, +.input-group-btn > .btn:active { + z-index: 2; +} +.input-group-btn:first-child > .btn, +.input-group-btn:first-child > .btn-group { + margin-right: -1px; +} +.input-group-btn:last-child > .btn, +.input-group-btn:last-child > .btn-group { + margin-left: -1px; +} +.nav { + padding-left: 0; + margin-bottom: 0; + list-style: none; +} +.nav > li { + position: relative; + display: block; +} +.nav > li > a { + position: relative; + display: block; + padding: 10px 15px; +} +.nav > li > a:hover, +.nav > li > a:focus { + text-decoration: none; + background-color: #eee; +} +.nav > li.disabled > a { + color: #777; +} +.nav > li.disabled > a:hover, +.nav > li.disabled > a:focus { + color: #777; + text-decoration: none; + cursor: not-allowed; + background-color: transparent; +} +.nav .open > a, +.nav .open > a:hover, +.nav .open > a:focus { + background-color: #eee; + border-color: #337ab7; +} +.nav .nav-divider { + height: 1px; + margin: 9px 0; + overflow: hidden; + background-color: #e5e5e5; +} +.nav > li > a > img { + max-width: none; +} +.nav-tabs { + border-bottom: 1px solid #ddd; +} +.nav-tabs > li { + float: left; + margin-bottom: -1px; +} +.nav-tabs > li > a { + margin-right: 2px; + line-height: 1.42857143; + border: 1px solid transparent; + border-radius: 4px 4px 0 0; +} +.nav-tabs > li > a:hover { + border-color: #eee #eee #ddd; +} +.nav-tabs > li.active > a, +.nav-tabs > li.active > a:hover, +.nav-tabs > li.active > a:focus { + color: #555; + cursor: default; + background-color: #fff; + border: 1px solid #ddd; + border-bottom-color: transparent; +} +.nav-tabs.nav-justified { + width: 100%; + border-bottom: 0; +} +.nav-tabs.nav-justified > li { + float: none; +} +.nav-tabs.nav-justified > li > a { + margin-bottom: 5px; + text-align: center; +} +.nav-tabs.nav-justified > .dropdown .dropdown-menu { + top: auto; + left: auto; +} +@media (min-width: 768px) { + .nav-tabs.nav-justified > li { + display: table-cell; + width: 1%; + } + .nav-tabs.nav-justified > li > a { + margin-bottom: 0; + } +} +.nav-tabs.nav-justified > li > a { + margin-right: 0; + border-radius: 4px; +} +.nav-tabs.nav-justified > .active > a, +.nav-tabs.nav-justified > .active > a:hover, +.nav-tabs.nav-justified > .active > a:focus { + border: 1px solid #ddd; +} +@media (min-width: 768px) { + .nav-tabs.nav-justified > li > a { + border-bottom: 1px solid #ddd; + border-radius: 4px 4px 0 0; + } + .nav-tabs.nav-justified > .active > a, + .nav-tabs.nav-justified > .active > a:hover, + .nav-tabs.nav-justified > .active > a:focus { + border-bottom-color: #fff; + } +} +.nav-pills > li { + float: left; +} +.nav-pills > li > a { + border-radius: 4px; +} +.nav-pills > li + li { + margin-left: 2px; +} +.nav-pills > li.active > a, +.nav-pills > li.active > a:hover, +.nav-pills > li.active > a:focus { + color: #fff; + background-color: #337ab7; +} +.nav-stacked > li { + float: none; +} +.nav-stacked > li + li { + margin-top: 2px; + margin-left: 0; +} +.nav-justified { + width: 100%; +} +.nav-justified > li { + float: none; +} +.nav-justified > li > a { + margin-bottom: 5px; + text-align: center; +} +.nav-justified > .dropdown .dropdown-menu { + top: auto; + left: auto; +} +@media (min-width: 768px) { + .nav-justified > li { + display: table-cell; + width: 1%; + } + .nav-justified > li > a { + margin-bottom: 0; + } +} +.nav-tabs-justified { + border-bottom: 0; +} +.nav-tabs-justified > li > a { + margin-right: 0; + border-radius: 4px; +} +.nav-tabs-justified > .active > a, +.nav-tabs-justified > .active > a:hover, +.nav-tabs-justified > .active > a:focus { + border: 1px solid #ddd; +} +@media (min-width: 768px) { + .nav-tabs-justified > li > a { + border-bottom: 1px solid #ddd; + border-radius: 4px 4px 0 0; + } + .nav-tabs-justified > .active > a, + .nav-tabs-justified > .active > a:hover, + .nav-tabs-justified > .active > a:focus { + border-bottom-color: #fff; + } +} +.tab-content > .tab-pane { + display: none; + visibility: hidden; +} +.tab-content > .active { + display: block; + visibility: visible; +} +.nav-tabs .dropdown-menu { + margin-top: -1px; + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.navbar { + position: relative; + min-height: 50px; + margin-bottom: 20px; + border: 1px solid transparent; +} +@media (min-width: 768px) { + .navbar { + border-radius: 4px; + } +} +@media (min-width: 768px) { + .navbar-header { + float: left; + } +} +.navbar-collapse { + padding-right: 15px; + padding-left: 15px; + overflow-x: visible; + -webkit-overflow-scrolling: touch; + border-top: 1px solid transparent; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1); +} +.navbar-collapse.in { + overflow-y: auto; +} +@media (min-width: 768px) { + .navbar-collapse { + width: auto; + border-top: 0; + -webkit-box-shadow: none; + box-shadow: none; + } + .navbar-collapse.collapse { + display: block !important; + height: auto !important; + padding-bottom: 0; + overflow: visible !important; + visibility: visible !important; + } + .navbar-collapse.in { + overflow-y: visible; + } + .navbar-fixed-top .navbar-collapse, + .navbar-static-top .navbar-collapse, + .navbar-fixed-bottom .navbar-collapse { + padding-right: 0; + padding-left: 0; + } +} +.navbar-fixed-top .navbar-collapse, +.navbar-fixed-bottom .navbar-collapse { + max-height: 340px; +} +@media (max-device-width: 480px) and (orientation: landscape) { + .navbar-fixed-top .navbar-collapse, + .navbar-fixed-bottom .navbar-collapse { + max-height: 200px; + } +} +.container > .navbar-header, +.container-fluid > .navbar-header, +.container > .navbar-collapse, +.container-fluid > .navbar-collapse { + margin-right: -15px; + margin-left: -15px; +} +@media (min-width: 768px) { + .container > .navbar-header, + .container-fluid > .navbar-header, + .container > .navbar-collapse, + .container-fluid > .navbar-collapse { + margin-right: 0; + margin-left: 0; + } +} +.navbar-static-top { + z-index: 1000; + border-width: 0 0 1px; +} +@media (min-width: 768px) { + .navbar-static-top { + border-radius: 0; + } +} +.navbar-fixed-top, +.navbar-fixed-bottom { + position: fixed; + right: 0; + left: 0; + z-index: 1030; +} +@media (min-width: 768px) { + .navbar-fixed-top, + .navbar-fixed-bottom { + border-radius: 0; + } +} +.navbar-fixed-top { + top: 0; + border-width: 0 0 1px; +} +.navbar-fixed-bottom { + bottom: 0; + margin-bottom: 0; + border-width: 1px 0 0; +} +.navbar-brand { + float: left; + height: 50px; + padding: 15px 15px; + font-size: 18px; + line-height: 20px; +} +.navbar-brand:hover, +.navbar-brand:focus { + text-decoration: none; +} +.navbar-brand > img { + display: block; +} +@media (min-width: 768px) { + .navbar > .container .navbar-brand, + .navbar > .container-fluid .navbar-brand { + margin-left: -15px; + } +} +.navbar-toggle { + position: relative; + float: right; + padding: 9px 10px; + margin-top: 8px; + margin-right: 15px; + margin-bottom: 8px; + background-color: transparent; + background-image: none; + border: 1px solid transparent; + border-radius: 4px; +} +.navbar-toggle:focus { + outline: 0; +} +.navbar-toggle .icon-bar { + display: block; + width: 22px; + height: 2px; + border-radius: 1px; +} +.navbar-toggle .icon-bar + .icon-bar { + margin-top: 4px; +} +@media (min-width: 768px) { + .navbar-toggle { + display: none; + } +} +.navbar-nav { + margin: 7.5px -15px; +} +.navbar-nav > li > a { + padding-top: 10px; + padding-bottom: 10px; + line-height: 20px; +} +@media (max-width: 767px) { + .navbar-nav .open .dropdown-menu { + position: static; + float: none; + width: auto; + margin-top: 0; + background-color: transparent; + border: 0; + -webkit-box-shadow: none; + box-shadow: none; + } + .navbar-nav .open .dropdown-menu > li > a, + .navbar-nav .open .dropdown-menu .dropdown-header { + padding: 5px 15px 5px 25px; + } + .navbar-nav .open .dropdown-menu > li > a { + line-height: 20px; + } + .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-nav .open .dropdown-menu > li > a:focus { + background-image: none; + } +} +@media (min-width: 768px) { + .navbar-nav { + float: left; + margin: 0; + } + .navbar-nav > li { + float: left; + } + .navbar-nav > li > a { + padding-top: 15px; + padding-bottom: 15px; + } +} +.navbar-form { + padding: 10px 15px; + margin-top: 8px; + margin-right: -15px; + margin-bottom: 8px; + margin-left: -15px; + border-top: 1px solid transparent; + border-bottom: 1px solid transparent; + -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, .1), 0 1px 0 rgba(255, 255, 255, .1); +} +@media (min-width: 768px) { + .navbar-form .form-group { + display: inline-block; + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .form-control { + display: inline-block; + width: auto; + vertical-align: middle; + } + .navbar-form .form-control-static { + display: inline-block; + } + .navbar-form .input-group { + display: inline-table; + vertical-align: middle; + } + .navbar-form .input-group .input-group-addon, + .navbar-form .input-group .input-group-btn, + .navbar-form .input-group .form-control { + width: auto; + } + .navbar-form .input-group > .form-control { + width: 100%; + } + .navbar-form .control-label { + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .radio, + .navbar-form .checkbox { + display: inline-block; + margin-top: 0; + margin-bottom: 0; + vertical-align: middle; + } + .navbar-form .radio label, + .navbar-form .checkbox label { + padding-left: 0; + } + .navbar-form .radio input[type="radio"], + .navbar-form .checkbox input[type="checkbox"] { + position: relative; + margin-left: 0; + } + .navbar-form .has-feedback .form-control-feedback { + top: 0; + } +} +@media (max-width: 767px) { + .navbar-form .form-group { + margin-bottom: 5px; + } + .navbar-form .form-group:last-child { + margin-bottom: 0; + } +} +@media (min-width: 768px) { + .navbar-form { + width: auto; + padding-top: 0; + padding-bottom: 0; + margin-right: 0; + margin-left: 0; + border: 0; + -webkit-box-shadow: none; + box-shadow: none; + } +} +.navbar-nav > li > .dropdown-menu { + margin-top: 0; + border-top-left-radius: 0; + border-top-right-radius: 0; +} +.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu { + margin-bottom: 0; + border-top-left-radius: 4px; + border-top-right-radius: 4px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; +} +.navbar-btn { + margin-top: 8px; + margin-bottom: 8px; +} +.navbar-btn.btn-sm { + margin-top: 10px; + margin-bottom: 10px; +} +.navbar-btn.btn-xs { + margin-top: 14px; + margin-bottom: 14px; +} +.navbar-text { + margin-top: 15px; + margin-bottom: 15px; +} +@media (min-width: 768px) { + .navbar-text { + float: left; + margin-right: 15px; + margin-left: 15px; + } +} +@media (min-width: 768px) { + .navbar-left { + float: left !important; + } + .navbar-right { + float: right !important; + margin-right: -15px; + } + .navbar-right ~ .navbar-right { + margin-right: 0; + } +} +.navbar-default { + background-color: #f8f8f8; + border-color: #e7e7e7; +} +.navbar-default .navbar-brand { + color: #777; +} +.navbar-default .navbar-brand:hover, +.navbar-default .navbar-brand:focus { + color: #5e5e5e; + background-color: transparent; +} +.navbar-default .navbar-text { + color: #777; +} +.navbar-default .navbar-nav > li > a { + color: #777; +} +.navbar-default .navbar-nav > li > a:hover, +.navbar-default .navbar-nav > li > a:focus { + color: #333; + background-color: transparent; +} +.navbar-default .navbar-nav > .active > a, +.navbar-default .navbar-nav > .active > a:hover, +.navbar-default .navbar-nav > .active > a:focus { + color: #555; + background-color: #e7e7e7; +} +.navbar-default .navbar-nav > .disabled > a, +.navbar-default .navbar-nav > .disabled > a:hover, +.navbar-default .navbar-nav > .disabled > a:focus { + color: #ccc; + background-color: transparent; +} +.navbar-default .navbar-toggle { + border-color: #ddd; +} +.navbar-default .navbar-toggle:hover, +.navbar-default .navbar-toggle:focus { + background-color: #ddd; +} +.navbar-default .navbar-toggle .icon-bar { + background-color: #888; +} +.navbar-default .navbar-collapse, +.navbar-default .navbar-form { + border-color: #e7e7e7; +} +.navbar-default .navbar-nav > .open > a, +.navbar-default .navbar-nav > .open > a:hover, +.navbar-default .navbar-nav > .open > a:focus { + color: #555; + background-color: #e7e7e7; +} +@media (max-width: 767px) { + .navbar-default .navbar-nav .open .dropdown-menu > li > a { + color: #777; + } + .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus { + color: #333; + background-color: transparent; + } + .navbar-default .navbar-nav .open .dropdown-menu > .active > a, + .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #555; + background-color: #e7e7e7; + } + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a, + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover, + .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus { + color: #ccc; + background-color: transparent; + } +} +.navbar-default .navbar-link { + color: #777; +} +.navbar-default .navbar-link:hover { + color: #333; +} +.navbar-default .btn-link { + color: #777; +} +.navbar-default .btn-link:hover, +.navbar-default .btn-link:focus { + color: #333; +} +.navbar-default .btn-link[disabled]:hover, +fieldset[disabled] .navbar-default .btn-link:hover, +.navbar-default .btn-link[disabled]:focus, +fieldset[disabled] .navbar-default .btn-link:focus { + color: #ccc; +} +.navbar-inverse { + background-color: #222; + border-color: #080808; +} +.navbar-inverse .navbar-brand { + color: #9d9d9d; +} +.navbar-inverse .navbar-brand:hover, +.navbar-inverse .navbar-brand:focus { + color: #fff; + background-color: transparent; +} +.navbar-inverse .navbar-text { + color: #9d9d9d; +} +.navbar-inverse .navbar-nav > li > a { + color: #9d9d9d; +} +.navbar-inverse .navbar-nav > li > a:hover, +.navbar-inverse .navbar-nav > li > a:focus { + color: #fff; + background-color: transparent; +} +.navbar-inverse .navbar-nav > .active > a, +.navbar-inverse .navbar-nav > .active > a:hover, +.navbar-inverse .navbar-nav > .active > a:focus { + color: #fff; + background-color: #080808; +} +.navbar-inverse .navbar-nav > .disabled > a, +.navbar-inverse .navbar-nav > .disabled > a:hover, +.navbar-inverse .navbar-nav > .disabled > a:focus { + color: #444; + background-color: transparent; +} +.navbar-inverse .navbar-toggle { + border-color: #333; +} +.navbar-inverse .navbar-toggle:hover, +.navbar-inverse .navbar-toggle:focus { + background-color: #333; +} +.navbar-inverse .navbar-toggle .icon-bar { + background-color: #fff; +} +.navbar-inverse .navbar-collapse, +.navbar-inverse .navbar-form { + border-color: #101010; +} +.navbar-inverse .navbar-nav > .open > a, +.navbar-inverse .navbar-nav > .open > a:hover, +.navbar-inverse .navbar-nav > .open > a:focus { + color: #fff; + background-color: #080808; +} +@media (max-width: 767px) { + .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header { + border-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu .divider { + background-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a { + color: #9d9d9d; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus { + color: #fff; + background-color: transparent; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus { + color: #fff; + background-color: #080808; + } + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover, + .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus { + color: #444; + background-color: transparent; + } +} +.navbar-inverse .navbar-link { + color: #9d9d9d; +} +.navbar-inverse .navbar-link:hover { + color: #fff; +} +.navbar-inverse .btn-link { + color: #9d9d9d; +} +.navbar-inverse .btn-link:hover, +.navbar-inverse .btn-link:focus { + color: #fff; +} +.navbar-inverse .btn-link[disabled]:hover, +fieldset[disabled] .navbar-inverse .btn-link:hover, +.navbar-inverse .btn-link[disabled]:focus, +fieldset[disabled] .navbar-inverse .btn-link:focus { + color: #444; +} +.breadcrumb { + padding: 8px 15px; + margin-bottom: 20px; + list-style: none; + background-color: #f5f5f5; + border-radius: 4px; +} +.breadcrumb > li { + display: inline-block; +} +.breadcrumb > li + li:before { + padding: 0 5px; + color: #ccc; + content: "/\00a0"; +} +.breadcrumb > .active { + color: #777; +} +.pagination { + display: inline-block; + padding-left: 0; + margin: 20px 0; + border-radius: 4px; +} +.pagination > li { + display: inline; +} +.pagination > li > a, +.pagination > li > span { + position: relative; + float: left; + padding: 6px 12px; + margin-left: -1px; + line-height: 1.42857143; + color: #337ab7; + text-decoration: none; + background-color: #fff; + border: 1px solid #ddd; +} +.pagination > li:first-child > a, +.pagination > li:first-child > span { + margin-left: 0; + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; +} +.pagination > li:last-child > a, +.pagination > li:last-child > span { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; +} +.pagination > li > a:hover, +.pagination > li > span:hover, +.pagination > li > a:focus, +.pagination > li > span:focus { + color: #23527c; + background-color: #eee; + border-color: #ddd; +} +.pagination > .active > a, +.pagination > .active > span, +.pagination > .active > a:hover, +.pagination > .active > span:hover, +.pagination > .active > a:focus, +.pagination > .active > span:focus { + z-index: 2; + color: #fff; + cursor: default; + background-color: #337ab7; + border-color: #337ab7; +} +.pagination > .disabled > span, +.pagination > .disabled > span:hover, +.pagination > .disabled > span:focus, +.pagination > .disabled > a, +.pagination > .disabled > a:hover, +.pagination > .disabled > a:focus { + color: #777; + cursor: not-allowed; + background-color: #fff; + border-color: #ddd; +} +.pagination-lg > li > a, +.pagination-lg > li > span { + padding: 10px 16px; + font-size: 18px; +} +.pagination-lg > li:first-child > a, +.pagination-lg > li:first-child > span { + border-top-left-radius: 6px; + border-bottom-left-radius: 6px; +} +.pagination-lg > li:last-child > a, +.pagination-lg > li:last-child > span { + border-top-right-radius: 6px; + border-bottom-right-radius: 6px; +} +.pagination-sm > li > a, +.pagination-sm > li > span { + padding: 5px 10px; + font-size: 12px; +} +.pagination-sm > li:first-child > a, +.pagination-sm > li:first-child > span { + border-top-left-radius: 3px; + border-bottom-left-radius: 3px; +} +.pagination-sm > li:last-child > a, +.pagination-sm > li:last-child > span { + border-top-right-radius: 3px; + border-bottom-right-radius: 3px; +} +.pager { + padding-left: 0; + margin: 20px 0; + text-align: center; + list-style: none; +} +.pager li { + display: inline; +} +.pager li > a, +.pager li > span { + display: inline-block; + padding: 5px 14px; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 15px; +} +.pager li > a:hover, +.pager li > a:focus { + text-decoration: none; + background-color: #eee; +} +.pager .next > a, +.pager .next > span { + float: right; +} +.pager .previous > a, +.pager .previous > span { + float: left; +} +.pager .disabled > a, +.pager .disabled > a:hover, +.pager .disabled > a:focus, +.pager .disabled > span { + color: #777; + cursor: not-allowed; + background-color: #fff; +} +.label { + display: inline; + padding: .2em .6em .3em; + font-size: 75%; + font-weight: bold; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + border-radius: .25em; +} +a.label:hover, +a.label:focus { + color: #fff; + text-decoration: none; + cursor: pointer; +} +.label:empty { + display: none; +} +.btn .label { + position: relative; + top: -1px; +} +.label-default { + background-color: #777; +} +.label-default[href]:hover, +.label-default[href]:focus { + background-color: #5e5e5e; +} +.label-primary { + background-color: #337ab7; +} +.label-primary[href]:hover, +.label-primary[href]:focus { + background-color: #286090; +} +.label-success { + background-color: #5cb85c; +} +.label-success[href]:hover, +.label-success[href]:focus { + background-color: #449d44; +} +.label-info { + background-color: #5bc0de; +} +.label-info[href]:hover, +.label-info[href]:focus { + background-color: #31b0d5; +} +.label-warning { + background-color: #f0ad4e; +} +.label-warning[href]:hover, +.label-warning[href]:focus { + background-color: #ec971f; +} +.label-danger { + background-color: #d9534f; +} +.label-danger[href]:hover, +.label-danger[href]:focus { + background-color: #c9302c; +} +.badge { + display: inline-block; + min-width: 10px; + padding: 3px 7px; + font-size: 12px; + font-weight: bold; + line-height: 1; + color: #fff; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + background-color: #777; + border-radius: 10px; +} +.badge:empty { + display: none; +} +.btn .badge { + position: relative; + top: -1px; +} +.btn-xs .badge { + top: 0; + padding: 1px 5px; +} +a.badge:hover, +a.badge:focus { + color: #fff; + text-decoration: none; + cursor: pointer; +} +.list-group-item.active > .badge, +.nav-pills > .active > a > .badge { + color: #337ab7; + background-color: #fff; +} +.list-group-item > .badge { + float: right; +} +.list-group-item > .badge + .badge { + margin-right: 5px; +} +.nav-pills > li > a > .badge { + margin-left: 3px; +} +.jumbotron { + padding: 30px 15px; + margin-bottom: 30px; + color: inherit; + background-color: #eee; +} +.jumbotron h1, +.jumbotron .h1 { + color: inherit; +} +.jumbotron p { + margin-bottom: 15px; + font-size: 21px; + font-weight: 200; +} +.jumbotron > hr { + border-top-color: #d5d5d5; +} +.container .jumbotron, +.container-fluid .jumbotron { + border-radius: 6px; +} +.jumbotron .container { + max-width: 100%; +} +@media screen and (min-width: 768px) { + .jumbotron { + padding: 48px 0; + } + .container .jumbotron, + .container-fluid .jumbotron { + padding-right: 60px; + padding-left: 60px; + } + .jumbotron h1, + .jumbotron .h1 { + font-size: 63px; + } +} +.thumbnail { + display: block; + padding: 4px; + margin-bottom: 20px; + line-height: 1.42857143; + background-color: #fff; + border: 1px solid #ddd; + border-radius: 4px; + -webkit-transition: border .2s ease-in-out; + -o-transition: border .2s ease-in-out; + transition: border .2s ease-in-out; +} +.thumbnail > img, +.thumbnail a > img { + margin-right: auto; + margin-left: auto; +} +a.thumbnail:hover, +a.thumbnail:focus, +a.thumbnail.active { + border-color: #337ab7; +} +.thumbnail .caption { + padding: 9px; + color: #333; +} +.alert { + padding: 15px; + margin-bottom: 20px; + border: 1px solid transparent; + border-radius: 4px; +} +.alert h4 { + margin-top: 0; + color: inherit; +} +.alert .alert-link { + font-weight: bold; +} +.alert > p, +.alert > ul { + margin-bottom: 0; +} +.alert > p + p { + margin-top: 5px; +} +.alert-dismissable, +.alert-dismissible { + padding-right: 35px; +} +.alert-dismissable .close, +.alert-dismissible .close { + position: relative; + top: -2px; + right: -21px; + color: inherit; +} +.alert-success { + color: #3c763d; + background-color: #dff0d8; + border-color: #d6e9c6; +} +.alert-success hr { + border-top-color: #c9e2b3; +} +.alert-success .alert-link { + color: #2b542c; +} +.alert-info { + color: #31708f; + background-color: #d9edf7; + border-color: #bce8f1; +} +.alert-info hr { + border-top-color: #a6e1ec; +} +.alert-info .alert-link { + color: #245269; +} +.alert-warning { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #faebcc; +} +.alert-warning hr { + border-top-color: #f7e1b5; +} +.alert-warning .alert-link { + color: #66512c; +} +.alert-danger { + color: #a94442; + background-color: #f2dede; + border-color: #ebccd1; +} +.alert-danger hr { + border-top-color: #e4b9c0; +} +.alert-danger .alert-link { + color: #843534; +} +@-webkit-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +@-o-keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +@keyframes progress-bar-stripes { + from { + background-position: 40px 0; + } + to { + background-position: 0 0; + } +} +.progress { + height: 20px; + margin-bottom: 20px; + overflow: hidden; + background-color: #f5f5f5; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1); + box-shadow: inset 0 1px 2px rgba(0, 0, 0, .1); +} +.progress-bar { + float: left; + width: 0; + height: 100%; + font-size: 12px; + line-height: 20px; + color: #fff; + text-align: center; + background-color: #337ab7; + -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15); + box-shadow: inset 0 -1px 0 rgba(0, 0, 0, .15); + -webkit-transition: width .6s ease; + -o-transition: width .6s ease; + transition: width .6s ease; +} +.progress-striped .progress-bar, +.progress-bar-striped { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + -webkit-background-size: 40px 40px; + background-size: 40px 40px; +} +.progress.active .progress-bar, +.progress-bar.active { + -webkit-animation: progress-bar-stripes 2s linear infinite; + -o-animation: progress-bar-stripes 2s linear infinite; + animation: progress-bar-stripes 2s linear infinite; +} +.progress-bar-success { + background-color: #5cb85c; +} +.progress-striped .progress-bar-success { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); +} +.progress-bar-info { + background-color: #5bc0de; +} +.progress-striped .progress-bar-info { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); +} +.progress-bar-warning { + background-color: #f0ad4e; +} +.progress-striped .progress-bar-warning { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); +} +.progress-bar-danger { + background-color: #d9534f; +} +.progress-striped .progress-bar-danger { + background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); + background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent); +} +.media { + margin-top: 15px; +} +.media:first-child { + margin-top: 0; +} +.media, +.media-body { + overflow: hidden; + zoom: 1; +} +.media-body { + width: 10000px; +} +.media-object { + display: block; +} +.media-right, +.media > .pull-right { + padding-left: 10px; +} +.media-left, +.media > .pull-left { + padding-right: 10px; +} +.media-left, +.media-right, +.media-body { + display: table-cell; + vertical-align: top; +} +.media-middle { + vertical-align: middle; +} +.media-bottom { + vertical-align: bottom; +} +.media-heading { + margin-top: 0; + margin-bottom: 5px; +} +.media-list { + padding-left: 0; + list-style: none; +} +.list-group { + padding-left: 0; + margin-bottom: 20px; +} +.list-group-item { + position: relative; + display: block; + padding: 10px 15px; + margin-bottom: -1px; + background-color: #fff; + border: 1px solid #ddd; +} +.list-group-item:first-child { + border-top-left-radius: 4px; + border-top-right-radius: 4px; +} +.list-group-item:last-child { + margin-bottom: 0; + border-bottom-right-radius: 4px; + border-bottom-left-radius: 4px; +} +a.list-group-item { + color: #555; +} +a.list-group-item .list-group-item-heading { + color: #333; +} +a.list-group-item:hover, +a.list-group-item:focus { + color: #555; + text-decoration: none; + background-color: #f5f5f5; +} +.list-group-item.disabled, +.list-group-item.disabled:hover, +.list-group-item.disabled:focus { + color: #777; + cursor: not-allowed; + background-color: #eee; +} +.list-group-item.disabled .list-group-item-heading, +.list-group-item.disabled:hover .list-group-item-heading, +.list-group-item.disabled:focus .list-group-item-heading { + color: inherit; +} +.list-group-item.disabled .list-group-item-text, +.list-group-item.disabled:hover .list-group-item-text, +.list-group-item.disabled:focus .list-group-item-text { + color: #777; +} +.list-group-item.active, +.list-group-item.active:hover, +.list-group-item.active:focus { + z-index: 2; + color: #fff; + background-color: #337ab7; + border-color: #337ab7; +} +.list-group-item.active .list-group-item-heading, +.list-group-item.active:hover .list-group-item-heading, +.list-group-item.active:focus .list-group-item-heading, +.list-group-item.active .list-group-item-heading > small, +.list-group-item.active:hover .list-group-item-heading > small, +.list-group-item.active:focus .list-group-item-heading > small, +.list-group-item.active .list-group-item-heading > .small, +.list-group-item.active:hover .list-group-item-heading > .small, +.list-group-item.active:focus .list-group-item-heading > .small { + color: inherit; +} +.list-group-item.active .list-group-item-text, +.list-group-item.active:hover .list-group-item-text, +.list-group-item.active:focus .list-group-item-text { + color: #c7ddef; +} +.list-group-item-success { + color: #3c763d; + background-color: #dff0d8; +} +a.list-group-item-success { + color: #3c763d; +} +a.list-group-item-success .list-group-item-heading { + color: inherit; +} +a.list-group-item-success:hover, +a.list-group-item-success:focus { + color: #3c763d; + background-color: #d0e9c6; +} +a.list-group-item-success.active, +a.list-group-item-success.active:hover, +a.list-group-item-success.active:focus { + color: #fff; + background-color: #3c763d; + border-color: #3c763d; +} +.list-group-item-info { + color: #31708f; + background-color: #d9edf7; +} +a.list-group-item-info { + color: #31708f; +} +a.list-group-item-info .list-group-item-heading { + color: inherit; +} +a.list-group-item-info:hover, +a.list-group-item-info:focus { + color: #31708f; + background-color: #c4e3f3; +} +a.list-group-item-info.active, +a.list-group-item-info.active:hover, +a.list-group-item-info.active:focus { + color: #fff; + background-color: #31708f; + border-color: #31708f; +} +.list-group-item-warning { + color: #8a6d3b; + background-color: #fcf8e3; +} +a.list-group-item-warning { + color: #8a6d3b; +} +a.list-group-item-warning .list-group-item-heading { + color: inherit; +} +a.list-group-item-warning:hover, +a.list-group-item-warning:focus { + color: #8a6d3b; + background-color: #faf2cc; +} +a.list-group-item-warning.active, +a.list-group-item-warning.active:hover, +a.list-group-item-warning.active:focus { + color: #fff; + background-color: #8a6d3b; + border-color: #8a6d3b; +} +.list-group-item-danger { + color: #a94442; + background-color: #f2dede; +} +a.list-group-item-danger { + color: #a94442; +} +a.list-group-item-danger .list-group-item-heading { + color: inherit; +} +a.list-group-item-danger:hover, +a.list-group-item-danger:focus { + color: #a94442; + background-color: #ebcccc; +} +a.list-group-item-danger.active, +a.list-group-item-danger.active:hover, +a.list-group-item-danger.active:focus { + color: #fff; + background-color: #a94442; + border-color: #a94442; +} +.list-group-item-heading { + margin-top: 0; + margin-bottom: 5px; +} +.list-group-item-text { + margin-bottom: 0; + line-height: 1.3; +} +.panel { + margin-bottom: 20px; + background-color: #fff; + border: 1px solid transparent; + border-radius: 4px; + -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, .05); + box-shadow: 0 1px 1px rgba(0, 0, 0, .05); +} +.panel-body { + padding: 15px; +} +.panel-heading { + padding: 10px 15px; + border-bottom: 1px solid transparent; + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel-heading > .dropdown .dropdown-toggle { + color: inherit; +} +.panel-title { + margin-top: 0; + margin-bottom: 0; + font-size: 16px; + color: inherit; +} +.panel-title > a, +.panel-title > small, +.panel-title > .small, +.panel-title > small > a, +.panel-title > .small > a { + color: inherit; +} +.panel-footer { + padding: 10px 15px; + background-color: #f5f5f5; + border-top: 1px solid #ddd; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .list-group, +.panel > .panel-collapse > .list-group { + margin-bottom: 0; +} +.panel > .list-group .list-group-item, +.panel > .panel-collapse > .list-group .list-group-item { + border-width: 1px 0; + border-radius: 0; +} +.panel > .list-group:first-child .list-group-item:first-child, +.panel > .panel-collapse > .list-group:first-child .list-group-item:first-child { + border-top: 0; + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel > .list-group:last-child .list-group-item:last-child, +.panel > .panel-collapse > .list-group:last-child .list-group-item:last-child { + border-bottom: 0; + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel-heading + .list-group .list-group-item:first-child { + border-top-width: 0; +} +.list-group + .panel-footer { + border-top-width: 0; +} +.panel > .table, +.panel > .table-responsive > .table, +.panel > .panel-collapse > .table { + margin-bottom: 0; +} +.panel > .table caption, +.panel > .table-responsive > .table caption, +.panel > .panel-collapse > .table caption { + padding-right: 15px; + padding-left: 15px; +} +.panel > .table:first-child, +.panel > .table-responsive:first-child > .table:first-child { + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child { + border-top-left-radius: 3px; + border-top-right-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child td:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child, +.panel > .table:first-child > thead:first-child > tr:first-child th:first-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child, +.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child { + border-top-left-radius: 3px; +} +.panel > .table:first-child > thead:first-child > tr:first-child td:last-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child, +.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child, +.panel > .table:first-child > thead:first-child > tr:first-child th:last-child, +.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child, +.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child, +.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child { + border-top-right-radius: 3px; +} +.panel > .table:last-child, +.panel > .table-responsive:last-child > .table:last-child { + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child { + border-bottom-right-radius: 3px; + border-bottom-left-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child, +.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child { + border-bottom-left-radius: 3px; +} +.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child, +.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child, +.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child, +.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child, +.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child { + border-bottom-right-radius: 3px; +} +.panel > .panel-body + .table, +.panel > .panel-body + .table-responsive, +.panel > .table + .panel-body, +.panel > .table-responsive + .panel-body { + border-top: 1px solid #ddd; +} +.panel > .table > tbody:first-child > tr:first-child th, +.panel > .table > tbody:first-child > tr:first-child td { + border-top: 0; +} +.panel > .table-bordered, +.panel > .table-responsive > .table-bordered { + border: 0; +} +.panel > .table-bordered > thead > tr > th:first-child, +.panel > .table-responsive > .table-bordered > thead > tr > th:first-child, +.panel > .table-bordered > tbody > tr > th:first-child, +.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child, +.panel > .table-bordered > tfoot > tr > th:first-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child, +.panel > .table-bordered > thead > tr > td:first-child, +.panel > .table-responsive > .table-bordered > thead > tr > td:first-child, +.panel > .table-bordered > tbody > tr > td:first-child, +.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child, +.panel > .table-bordered > tfoot > tr > td:first-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child { + border-left: 0; +} +.panel > .table-bordered > thead > tr > th:last-child, +.panel > .table-responsive > .table-bordered > thead > tr > th:last-child, +.panel > .table-bordered > tbody > tr > th:last-child, +.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child, +.panel > .table-bordered > tfoot > tr > th:last-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child, +.panel > .table-bordered > thead > tr > td:last-child, +.panel > .table-responsive > .table-bordered > thead > tr > td:last-child, +.panel > .table-bordered > tbody > tr > td:last-child, +.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child, +.panel > .table-bordered > tfoot > tr > td:last-child, +.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child { + border-right: 0; +} +.panel > .table-bordered > thead > tr:first-child > td, +.panel > .table-responsive > .table-bordered > thead > tr:first-child > td, +.panel > .table-bordered > tbody > tr:first-child > td, +.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td, +.panel > .table-bordered > thead > tr:first-child > th, +.panel > .table-responsive > .table-bordered > thead > tr:first-child > th, +.panel > .table-bordered > tbody > tr:first-child > th, +.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th { + border-bottom: 0; +} +.panel > .table-bordered > tbody > tr:last-child > td, +.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td, +.panel > .table-bordered > tfoot > tr:last-child > td, +.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td, +.panel > .table-bordered > tbody > tr:last-child > th, +.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th, +.panel > .table-bordered > tfoot > tr:last-child > th, +.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th { + border-bottom: 0; +} +.panel > .table-responsive { + margin-bottom: 0; + border: 0; +} +.panel-group { + margin-bottom: 20px; +} +.panel-group .panel { + margin-bottom: 0; + border-radius: 4px; +} +.panel-group .panel + .panel { + margin-top: 5px; +} +.panel-group .panel-heading { + border-bottom: 0; +} +.panel-group .panel-heading + .panel-collapse > .panel-body, +.panel-group .panel-heading + .panel-collapse > .list-group { + border-top: 1px solid #ddd; +} +.panel-group .panel-footer { + border-top: 0; +} +.panel-group .panel-footer + .panel-collapse .panel-body { + border-bottom: 1px solid #ddd; +} +.panel-default { + border-color: #ddd; +} +.panel-default > .panel-heading { + color: #333; + background-color: #f5f5f5; + border-color: #ddd; +} +.panel-default > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #ddd; +} +.panel-default > .panel-heading .badge { + color: #f5f5f5; + background-color: #333; +} +.panel-default > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #ddd; +} +.panel-primary { + border-color: #337ab7; +} +.panel-primary > .panel-heading { + color: #fff; + background-color: #337ab7; + border-color: #337ab7; +} +.panel-primary > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #337ab7; +} +.panel-primary > .panel-heading .badge { + color: #337ab7; + background-color: #fff; +} +.panel-primary > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #337ab7; +} +.panel-success { + border-color: #d6e9c6; +} +.panel-success > .panel-heading { + color: #3c763d; + background-color: #dff0d8; + border-color: #d6e9c6; +} +.panel-success > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #d6e9c6; +} +.panel-success > .panel-heading .badge { + color: #dff0d8; + background-color: #3c763d; +} +.panel-success > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #d6e9c6; +} +.panel-info { + border-color: #bce8f1; +} +.panel-info > .panel-heading { + color: #31708f; + background-color: #d9edf7; + border-color: #bce8f1; +} +.panel-info > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #bce8f1; +} +.panel-info > .panel-heading .badge { + color: #d9edf7; + background-color: #31708f; +} +.panel-info > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #bce8f1; +} +.panel-warning { + border-color: #faebcc; +} +.panel-warning > .panel-heading { + color: #8a6d3b; + background-color: #fcf8e3; + border-color: #faebcc; +} +.panel-warning > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #faebcc; +} +.panel-warning > .panel-heading .badge { + color: #fcf8e3; + background-color: #8a6d3b; +} +.panel-warning > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #faebcc; +} +.panel-danger { + border-color: #ebccd1; +} +.panel-danger > .panel-heading { + color: #a94442; + background-color: #f2dede; + border-color: #ebccd1; +} +.panel-danger > .panel-heading + .panel-collapse > .panel-body { + border-top-color: #ebccd1; +} +.panel-danger > .panel-heading .badge { + color: #f2dede; + background-color: #a94442; +} +.panel-danger > .panel-footer + .panel-collapse > .panel-body { + border-bottom-color: #ebccd1; +} +.embed-responsive { + position: relative; + display: block; + height: 0; + padding: 0; + overflow: hidden; +} +.embed-responsive .embed-responsive-item, +.embed-responsive iframe, +.embed-responsive embed, +.embed-responsive object, +.embed-responsive video { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 100%; + height: 100%; + border: 0; +} +.embed-responsive.embed-responsive-16by9 { + padding-bottom: 56.25%; +} +.embed-responsive.embed-responsive-4by3 { + padding-bottom: 75%; +} +.well { + min-height: 20px; + padding: 19px; + margin-bottom: 20px; + background-color: #f5f5f5; + border: 1px solid #e3e3e3; + border-radius: 4px; + -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); + box-shadow: inset 0 1px 1px rgba(0, 0, 0, .05); +} +.well blockquote { + border-color: #ddd; + border-color: rgba(0, 0, 0, .15); +} +.well-lg { + padding: 24px; + border-radius: 6px; +} +.well-sm { + padding: 9px; + border-radius: 3px; +} +.close { + float: right; + font-size: 21px; + font-weight: bold; + line-height: 1; + color: #000; + text-shadow: 0 1px 0 #fff; + filter: alpha(opacity=20); + opacity: .2; +} +.close:hover, +.close:focus { + color: #000; + text-decoration: none; + cursor: pointer; + filter: alpha(opacity=50); + opacity: .5; +} +button.close { + -webkit-appearance: none; + padding: 0; + cursor: pointer; + background: transparent; + border: 0; +} +.modal-open { + overflow: hidden; +} +.modal { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + z-index: 1040; + display: none; + overflow: hidden; + -webkit-overflow-scrolling: touch; + outline: 0; +} +.modal.fade .modal-dialog { + -webkit-transition: -webkit-transform .3s ease-out; + -o-transition: -o-transform .3s ease-out; + transition: transform .3s ease-out; + -webkit-transform: translate(0, -25%); + -ms-transform: translate(0, -25%); + -o-transform: translate(0, -25%); + transform: translate(0, -25%); +} +.modal.in .modal-dialog { + -webkit-transform: translate(0, 0); + -ms-transform: translate(0, 0); + -o-transform: translate(0, 0); + transform: translate(0, 0); +} +.modal-open .modal { + overflow-x: hidden; + overflow-y: auto; +} +.modal-dialog { + position: relative; + width: auto; + margin: 10px; +} +.modal-content { + position: relative; + background-color: #fff; + -webkit-background-clip: padding-box; + background-clip: padding-box; + border: 1px solid #999; + border: 1px solid rgba(0, 0, 0, .2); + border-radius: 6px; + outline: 0; + -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, .5); + box-shadow: 0 3px 9px rgba(0, 0, 0, .5); +} +.modal-backdrop { + position: absolute; + top: 0; + right: 0; + left: 0; + background-color: #000; +} +.modal-backdrop.fade { + filter: alpha(opacity=0); + opacity: 0; +} +.modal-backdrop.in { + filter: alpha(opacity=50); + opacity: .5; +} +.modal-header { + min-height: 16.42857143px; + padding: 15px; + border-bottom: 1px solid #e5e5e5; +} +.modal-header .close { + margin-top: -2px; +} +.modal-title { + margin: 0; + line-height: 1.42857143; +} +.modal-body { + position: relative; + padding: 15px; +} +.modal-footer { + padding: 15px; + text-align: right; + border-top: 1px solid #e5e5e5; +} +.modal-footer .btn + .btn { + margin-bottom: 0; + margin-left: 5px; +} +.modal-footer .btn-group .btn + .btn { + margin-left: -1px; +} +.modal-footer .btn-block + .btn-block { + margin-left: 0; +} +.modal-scrollbar-measure { + position: absolute; + top: -9999px; + width: 50px; + height: 50px; + overflow: scroll; +} +@media (min-width: 768px) { + .modal-dialog { + width: 600px; + margin: 30px auto; + } + .modal-content { + -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, .5); + box-shadow: 0 5px 15px rgba(0, 0, 0, .5); + } + .modal-sm { + width: 300px; + } +} +@media (min-width: 992px) { + .modal-lg { + width: 900px; + } +} +.tooltip { + position: absolute; + z-index: 1070; + display: block; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 12px; + font-weight: normal; + line-height: 1.4; + visibility: visible; + filter: alpha(opacity=0); + opacity: 0; +} +.tooltip.in { + filter: alpha(opacity=90); + opacity: .9; +} +.tooltip.top { + padding: 5px 0; + margin-top: -3px; +} +.tooltip.right { + padding: 0 5px; + margin-left: 3px; +} +.tooltip.bottom { + padding: 5px 0; + margin-top: 3px; +} +.tooltip.left { + padding: 0 5px; + margin-left: -3px; +} +.tooltip-inner { + max-width: 200px; + padding: 3px 8px; + color: #fff; + text-align: center; + text-decoration: none; + background-color: #000; + border-radius: 4px; +} +.tooltip-arrow { + position: absolute; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.tooltip.top .tooltip-arrow { + bottom: 0; + left: 50%; + margin-left: -5px; + border-width: 5px 5px 0; + border-top-color: #000; +} +.tooltip.top-left .tooltip-arrow { + right: 5px; + bottom: 0; + margin-bottom: -5px; + border-width: 5px 5px 0; + border-top-color: #000; +} +.tooltip.top-right .tooltip-arrow { + bottom: 0; + left: 5px; + margin-bottom: -5px; + border-width: 5px 5px 0; + border-top-color: #000; +} +.tooltip.right .tooltip-arrow { + top: 50%; + left: 0; + margin-top: -5px; + border-width: 5px 5px 5px 0; + border-right-color: #000; +} +.tooltip.left .tooltip-arrow { + top: 50%; + right: 0; + margin-top: -5px; + border-width: 5px 0 5px 5px; + border-left-color: #000; +} +.tooltip.bottom .tooltip-arrow { + top: 0; + left: 50%; + margin-left: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} +.tooltip.bottom-left .tooltip-arrow { + top: 0; + right: 5px; + margin-top: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} +.tooltip.bottom-right .tooltip-arrow { + top: 0; + left: 5px; + margin-top: -5px; + border-width: 0 5px 5px; + border-bottom-color: #000; +} +.popover { + position: absolute; + top: 0; + left: 0; + z-index: 1060; + display: none; + max-width: 276px; + padding: 1px; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + font-weight: normal; + line-height: 1.42857143; + text-align: left; + white-space: normal; + background-color: #fff; + -webkit-background-clip: padding-box; + background-clip: padding-box; + border: 1px solid #ccc; + border: 1px solid rgba(0, 0, 0, .2); + border-radius: 6px; + -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, .2); + box-shadow: 0 5px 10px rgba(0, 0, 0, .2); +} +.popover.top { + margin-top: -10px; +} +.popover.right { + margin-left: 10px; +} +.popover.bottom { + margin-top: 10px; +} +.popover.left { + margin-left: -10px; +} +.popover-title { + padding: 8px 14px; + margin: 0; + font-size: 14px; + background-color: #f7f7f7; + border-bottom: 1px solid #ebebeb; + border-radius: 5px 5px 0 0; +} +.popover-content { + padding: 9px 14px; +} +.popover > .arrow, +.popover > .arrow:after { + position: absolute; + display: block; + width: 0; + height: 0; + border-color: transparent; + border-style: solid; +} +.popover > .arrow { + border-width: 11px; +} +.popover > .arrow:after { + content: ""; + border-width: 10px; +} +.popover.top > .arrow { + bottom: -11px; + left: 50%; + margin-left: -11px; + border-top-color: #999; + border-top-color: rgba(0, 0, 0, .25); + border-bottom-width: 0; +} +.popover.top > .arrow:after { + bottom: 1px; + margin-left: -10px; + content: " "; + border-top-color: #fff; + border-bottom-width: 0; +} +.popover.right > .arrow { + top: 50%; + left: -11px; + margin-top: -11px; + border-right-color: #999; + border-right-color: rgba(0, 0, 0, .25); + border-left-width: 0; +} +.popover.right > .arrow:after { + bottom: -10px; + left: 1px; + content: " "; + border-right-color: #fff; + border-left-width: 0; +} +.popover.bottom > .arrow { + top: -11px; + left: 50%; + margin-left: -11px; + border-top-width: 0; + border-bottom-color: #999; + border-bottom-color: rgba(0, 0, 0, .25); +} +.popover.bottom > .arrow:after { + top: 1px; + margin-left: -10px; + content: " "; + border-top-width: 0; + border-bottom-color: #fff; +} +.popover.left > .arrow { + top: 50%; + right: -11px; + margin-top: -11px; + border-right-width: 0; + border-left-color: #999; + border-left-color: rgba(0, 0, 0, .25); +} +.popover.left > .arrow:after { + right: 1px; + bottom: -10px; + content: " "; + border-right-width: 0; + border-left-color: #fff; +} +.carousel { + position: relative; +} +.carousel-inner { + position: relative; + width: 100%; + overflow: hidden; +} +.carousel-inner > .item { + position: relative; + display: none; + -webkit-transition: .6s ease-in-out left; + -o-transition: .6s ease-in-out left; + transition: .6s ease-in-out left; +} +.carousel-inner > .item > img, +.carousel-inner > .item > a > img { + line-height: 1; +} +@media all and (transform-3d), (-webkit-transform-3d) { + .carousel-inner > .item { + -webkit-transition: -webkit-transform .6s ease-in-out; + -o-transition: -o-transform .6s ease-in-out; + transition: transform .6s ease-in-out; + + -webkit-backface-visibility: hidden; + backface-visibility: hidden; + -webkit-perspective: 1000; + perspective: 1000; + } + .carousel-inner > .item.next, + .carousel-inner > .item.active.right { + left: 0; + -webkit-transform: translate3d(100%, 0, 0); + transform: translate3d(100%, 0, 0); + } + .carousel-inner > .item.prev, + .carousel-inner > .item.active.left { + left: 0; + -webkit-transform: translate3d(-100%, 0, 0); + transform: translate3d(-100%, 0, 0); + } + .carousel-inner > .item.next.left, + .carousel-inner > .item.prev.right, + .carousel-inner > .item.active { + left: 0; + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} +.carousel-inner > .active, +.carousel-inner > .next, +.carousel-inner > .prev { + display: block; +} +.carousel-inner > .active { + left: 0; +} +.carousel-inner > .next, +.carousel-inner > .prev { + position: absolute; + top: 0; + width: 100%; +} +.carousel-inner > .next { + left: 100%; +} +.carousel-inner > .prev { + left: -100%; +} +.carousel-inner > .next.left, +.carousel-inner > .prev.right { + left: 0; +} +.carousel-inner > .active.left { + left: -100%; +} +.carousel-inner > .active.right { + left: 100%; +} +.carousel-control { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 15%; + font-size: 20px; + color: #fff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, .6); + filter: alpha(opacity=50); + opacity: .5; +} +.carousel-control.left { + background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); + background-image: -o-linear-gradient(left, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); + background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .5)), to(rgba(0, 0, 0, .0001))); + background-image: linear-gradient(to right, rgba(0, 0, 0, .5) 0%, rgba(0, 0, 0, .0001) 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1); + background-repeat: repeat-x; +} +.carousel-control.right { + right: 0; + left: auto; + background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); + background-image: -o-linear-gradient(left, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); + background-image: -webkit-gradient(linear, left top, right top, from(rgba(0, 0, 0, .0001)), to(rgba(0, 0, 0, .5))); + background-image: linear-gradient(to right, rgba(0, 0, 0, .0001) 0%, rgba(0, 0, 0, .5) 100%); + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1); + background-repeat: repeat-x; +} +.carousel-control:hover, +.carousel-control:focus { + color: #fff; + text-decoration: none; + filter: alpha(opacity=90); + outline: 0; + opacity: .9; +} +.carousel-control .icon-prev, +.carousel-control .icon-next, +.carousel-control .glyphicon-chevron-left, +.carousel-control .glyphicon-chevron-right { + position: absolute; + top: 50%; + z-index: 5; + display: inline-block; +} +.carousel-control .icon-prev, +.carousel-control .glyphicon-chevron-left { + left: 50%; + margin-left: -10px; +} +.carousel-control .icon-next, +.carousel-control .glyphicon-chevron-right { + right: 50%; + margin-right: -10px; +} +.carousel-control .icon-prev, +.carousel-control .icon-next { + width: 20px; + height: 20px; + margin-top: -10px; + font-family: serif; + line-height: 1; +} +.carousel-control .icon-prev:before { + content: '\2039'; +} +.carousel-control .icon-next:before { + content: '\203a'; +} +.carousel-indicators { + position: absolute; + bottom: 10px; + left: 50%; + z-index: 15; + width: 60%; + padding-left: 0; + margin-left: -30%; + text-align: center; + list-style: none; +} +.carousel-indicators li { + display: inline-block; + width: 10px; + height: 10px; + margin: 1px; + text-indent: -999px; + cursor: pointer; + background-color: #000 \9; + background-color: rgba(0, 0, 0, 0); + border: 1px solid #fff; + border-radius: 10px; +} +.carousel-indicators .active { + width: 12px; + height: 12px; + margin: 0; + background-color: #fff; +} +.carousel-caption { + position: absolute; + right: 15%; + bottom: 20px; + left: 15%; + z-index: 10; + padding-top: 20px; + padding-bottom: 20px; + color: #fff; + text-align: center; + text-shadow: 0 1px 2px rgba(0, 0, 0, .6); +} +.carousel-caption .btn { + text-shadow: none; +} +@media screen and (min-width: 768px) { + .carousel-control .glyphicon-chevron-left, + .carousel-control .glyphicon-chevron-right, + .carousel-control .icon-prev, + .carousel-control .icon-next { + width: 30px; + height: 30px; + margin-top: -15px; + font-size: 30px; + } + .carousel-control .glyphicon-chevron-left, + .carousel-control .icon-prev { + margin-left: -15px; + } + .carousel-control .glyphicon-chevron-right, + .carousel-control .icon-next { + margin-right: -15px; + } + .carousel-caption { + right: 20%; + left: 20%; + padding-bottom: 30px; + } + .carousel-indicators { + bottom: 20px; + } +} +.clearfix:before, +.clearfix:after, +.dl-horizontal dd:before, +.dl-horizontal dd:after, +.container:before, +.container:after, +.container-fluid:before, +.container-fluid:after, +.row:before, +.row:after, +.form-horizontal .form-group:before, +.form-horizontal .form-group:after, +.btn-toolbar:before, +.btn-toolbar:after, +.btn-group-vertical > .btn-group:before, +.btn-group-vertical > .btn-group:after, +.nav:before, +.nav:after, +.navbar:before, +.navbar:after, +.navbar-header:before, +.navbar-header:after, +.navbar-collapse:before, +.navbar-collapse:after, +.pager:before, +.pager:after, +.panel-body:before, +.panel-body:after, +.modal-footer:before, +.modal-footer:after { + display: table; + content: " "; +} +.clearfix:after, +.dl-horizontal dd:after, +.container:after, +.container-fluid:after, +.row:after, +.form-horizontal .form-group:after, +.btn-toolbar:after, +.btn-group-vertical > .btn-group:after, +.nav:after, +.navbar:after, +.navbar-header:after, +.navbar-collapse:after, +.pager:after, +.panel-body:after, +.modal-footer:after { + clear: both; +} +.center-block { + display: block; + margin-right: auto; + margin-left: auto; +} +.pull-right { + float: right !important; +} +.pull-left { + float: left !important; +} +.hide { + display: none !important; +} +.show { + display: block !important; +} +.invisible { + visibility: hidden; +} +.text-hide { + font: 0/0 a; + color: transparent; + text-shadow: none; + background-color: transparent; + border: 0; +} +.hidden { + display: none !important; + visibility: hidden !important; +} +.affix { + position: fixed; +} +@-ms-viewport { + width: device-width; +} +.visible-xs, +.visible-sm, +.visible-md, +.visible-lg { + display: none !important; +} +.visible-xs-block, +.visible-xs-inline, +.visible-xs-inline-block, +.visible-sm-block, +.visible-sm-inline, +.visible-sm-inline-block, +.visible-md-block, +.visible-md-inline, +.visible-md-inline-block, +.visible-lg-block, +.visible-lg-inline, +.visible-lg-inline-block { + display: none !important; +} +@media (max-width: 767px) { + .visible-xs { + display: block !important; + } + table.visible-xs { + display: table; + } + tr.visible-xs { + display: table-row !important; + } + th.visible-xs, + td.visible-xs { + display: table-cell !important; + } +} +@media (max-width: 767px) { + .visible-xs-block { + display: block !important; + } +} +@media (max-width: 767px) { + .visible-xs-inline { + display: inline !important; + } +} +@media (max-width: 767px) { + .visible-xs-inline-block { + display: inline-block !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm { + display: block !important; + } + table.visible-sm { + display: table; + } + tr.visible-sm { + display: table-row !important; + } + th.visible-sm, + td.visible-sm { + display: table-cell !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-block { + display: block !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-inline { + display: inline !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .visible-sm-inline-block { + display: inline-block !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md { + display: block !important; + } + table.visible-md { + display: table; + } + tr.visible-md { + display: table-row !important; + } + th.visible-md, + td.visible-md { + display: table-cell !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-block { + display: block !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-inline { + display: inline !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .visible-md-inline-block { + display: inline-block !important; + } +} +@media (min-width: 1200px) { + .visible-lg { + display: block !important; + } + table.visible-lg { + display: table; + } + tr.visible-lg { + display: table-row !important; + } + th.visible-lg, + td.visible-lg { + display: table-cell !important; + } +} +@media (min-width: 1200px) { + .visible-lg-block { + display: block !important; + } +} +@media (min-width: 1200px) { + .visible-lg-inline { + display: inline !important; + } +} +@media (min-width: 1200px) { + .visible-lg-inline-block { + display: inline-block !important; + } +} +@media (max-width: 767px) { + .hidden-xs { + display: none !important; + } +} +@media (min-width: 768px) and (max-width: 991px) { + .hidden-sm { + display: none !important; + } +} +@media (min-width: 992px) and (max-width: 1199px) { + .hidden-md { + display: none !important; + } +} +@media (min-width: 1200px) { + .hidden-lg { + display: none !important; + } +} +.visible-print { + display: none !important; +} +@media print { + .visible-print { + display: block !important; + } + table.visible-print { + display: table; + } + tr.visible-print { + display: table-row !important; + } + th.visible-print, + td.visible-print { + display: table-cell !important; + } +} +.visible-print-block { + display: none !important; +} +@media print { + .visible-print-block { + display: block !important; + } +} +.visible-print-inline { + display: none !important; +} +@media print { + .visible-print-inline { + display: inline !important; + } +} +.visible-print-inline-block { + display: none !important; +} +@media print { + .visible-print-inline-block { + display: inline-block !important; + } +} +@media print { + .hidden-print { + display: none !important; + } +} +/*# sourceMappingURL=bootstrap.css.map */ diff --git a/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap.css.map b/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap.css.map new file mode 100644 index 000000000..ff579ff56 --- /dev/null +++ b/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["bootstrap.css","less/normalize.less","less/print.less","less/glyphicons.less","less/scaffolding.less","less/mixins/vendor-prefixes.less","less/mixins/tab-focus.less","less/mixins/image.less","less/type.less","less/mixins/text-emphasis.less","less/mixins/background-variant.less","less/mixins/text-overflow.less","less/code.less","less/grid.less","less/mixins/grid.less","less/mixins/grid-framework.less","less/tables.less","less/mixins/table-row.less","less/forms.less","less/mixins/forms.less","less/buttons.less","less/mixins/buttons.less","less/mixins/opacity.less","less/component-animations.less","less/dropdowns.less","less/mixins/nav-divider.less","less/mixins/reset-filter.less","less/button-groups.less","less/mixins/border-radius.less","less/input-groups.less","less/navs.less","less/navbar.less","less/mixins/nav-vertical-align.less","less/utilities.less","less/breadcrumbs.less","less/pagination.less","less/mixins/pagination.less","less/pager.less","less/labels.less","less/mixins/labels.less","less/badges.less","less/jumbotron.less","less/thumbnails.less","less/alerts.less","less/mixins/alerts.less","less/progress-bars.less","less/mixins/gradients.less","less/mixins/progress-bar.less","less/media.less","less/list-group.less","less/mixins/list-group.less","less/panels.less","less/mixins/panels.less","less/responsive-embed.less","less/wells.less","less/close.less","less/modals.less","less/tooltip.less","less/popovers.less","less/carousel.less","less/mixins/clearfix.less","less/mixins/center-block.less","less/mixins/hide-text.less","less/responsive-utilities.less","less/mixins/responsive-visibility.less"],"names":[],"mappings":"AAAA,6DAA4D;ACQ5D;EACE,yBAAA;EACA,4BAAA;EACA,gCAAA;EDND;ACaD;EACE,WAAA;EDXD;ACwBD;;;;;;;;;;;;;EAaE,gBAAA;EDtBD;AC8BD;;;;EAIE,uBAAA;EACA,0BAAA;ED5BD;ACoCD;EACE,eAAA;EACA,WAAA;EDlCD;AC0CD;;EAEE,eAAA;EDxCD;ACkDD;EACE,+BAAA;EDhDD;ACuDD;;EAEE,YAAA;EDrDD;AC+DD;EACE,2BAAA;ED7DD;ACoED;;EAEE,mBAAA;EDlED;ACyED;EACE,oBAAA;EDvED;AC+ED;EACE,gBAAA;EACA,kBAAA;ED7ED;ACoFD;EACE,kBAAA;EACA,aAAA;EDlFD;ACyFD;EACE,gBAAA;EDvFD;AC8FD;;EAEE,gBAAA;EACA,gBAAA;EACA,oBAAA;EACA,0BAAA;ED5FD;AC+FD;EACE,aAAA;ED7FD;ACgGD;EACE,iBAAA;ED9FD;ACwGD;EACE,WAAA;EDtGD;AC6GD;EACE,kBAAA;ED3GD;ACqHD;EACE,kBAAA;EDnHD;AC0HD;EACE,8BAAA;EACA,iCAAA;UAAA,yBAAA;EACA,WAAA;EDxHD;AC+HD;EACE,gBAAA;ED7HD;ACoID;;;;EAIE,mCAAA;EACA,gBAAA;EDlID;ACoJD;;;;;EAKE,gBAAA;EACA,eAAA;EACA,WAAA;EDlJD;ACyJD;EACE,mBAAA;EDvJD;ACiKD;;EAEE,sBAAA;ED/JD;AC0KD;;;;EAIE,4BAAA;EACA,iBAAA;EDxKD;AC+KD;;EAEE,iBAAA;ED7KD;ACoLD;;EAEE,WAAA;EACA,YAAA;EDlLD;AC0LD;EACE,qBAAA;EDxLD;ACmMD;;EAEE,gCAAA;KAAA,6BAAA;UAAA,wBAAA;EACA,YAAA;EDjMD;AC0MD;;EAEE,cAAA;EDxMD;ACiND;EACE,+BAAA;EACA,8BAAA;EACA,iCAAA;EACA,yBAAA;ED/MD;ACwND;;EAEE,0BAAA;EDtND;AC6ND;EACE,2BAAA;EACA,eAAA;EACA,gCAAA;ED3ND;ACmOD;EACE,WAAA;EACA,YAAA;EDjOD;ACwOD;EACE,gBAAA;EDtOD;AC8OD;EACE,mBAAA;ED5OD;ACsPD;EACE,2BAAA;EACA,mBAAA;EDpPD;ACuPD;;EAEE,YAAA;EDrPD;AACD,sFAAqF;AE1ErF;EAnGI;;;IAGI,oCAAA;IACA,wBAAA;IACA,qCAAA;YAAA,6BAAA;IACA,8BAAA;IFgLL;EE7KC;;IAEI,4BAAA;IF+KL;EE5KC;IACI,8BAAA;IF8KL;EE3KC;IACI,+BAAA;IF6KL;EExKC;;IAEI,aAAA;IF0KL;EEvKC;;IAEI,wBAAA;IACA,0BAAA;IFyKL;EEtKC;IACI,6BAAA;IFwKL;EErKC;;IAEI,0BAAA;IFuKL;EEpKC;IACI,4BAAA;IFsKL;EEnKC;;;IAGI,YAAA;IACA,WAAA;IFqKL;EElKC;;IAEI,yBAAA;IFoKL;EE7JC;IACI,6BAAA;IF+JL;EE3JC;IACI,eAAA;IF6JL;EE3JC;;IAGQ,mCAAA;IF4JT;EEzJC;IACI,wBAAA;IF2JL;EExJC;IACI,sCAAA;IF0JL;EE3JC;;IAKQ,mCAAA;IF0JT;EEvJC;;IAGQ,mCAAA;IFwJT;EACF;AGpPD;EACE,qCAAA;EACA,uDAAA;EACA,iYAAA;EHsPD;AG9OD;EACE,oBAAA;EACA,UAAA;EACA,uBAAA;EACA,qCAAA;EACA,oBAAA;EACA,qBAAA;EACA,gBAAA;EACA,qCAAA;EACA,oCAAA;EHgPD;AG5OmC;EAAW,gBAAA;EH+O9C;AG9OmC;EAAW,gBAAA;EHiP9C;AG/OmC;;EAAW,kBAAA;EHmP9C;AGlPmC;EAAW,kBAAA;EHqP9C;AGpPmC;EAAW,kBAAA;EHuP9C;AGtPmC;EAAW,kBAAA;EHyP9C;AGxPmC;EAAW,kBAAA;EH2P9C;AG1PmC;EAAW,kBAAA;EH6P9C;AG5PmC;EAAW,kBAAA;EH+P9C;AG9PmC;EAAW,kBAAA;EHiQ9C;AGhQmC;EAAW,kBAAA;EHmQ9C;AGlQmC;EAAW,kBAAA;EHqQ9C;AGpQmC;EAAW,kBAAA;EHuQ9C;AGtQmC;EAAW,kBAAA;EHyQ9C;AGxQmC;EAAW,kBAAA;EH2Q9C;AG1QmC;EAAW,kBAAA;EH6Q9C;AG5QmC;EAAW,kBAAA;EH+Q9C;AG9QmC;EAAW,kBAAA;EHiR9C;AGhRmC;EAAW,kBAAA;EHmR9C;AGlRmC;EAAW,kBAAA;EHqR9C;AGpRmC;EAAW,kBAAA;EHuR9C;AGtRmC;EAAW,kBAAA;EHyR9C;AGxRmC;EAAW,kBAAA;EH2R9C;AG1RmC;EAAW,kBAAA;EH6R9C;AG5RmC;EAAW,kBAAA;EH+R9C;AG9RmC;EAAW,kBAAA;EHiS9C;AGhSmC;EAAW,kBAAA;EHmS9C;AGlSmC;EAAW,kBAAA;EHqS9C;AGpSmC;EAAW,kBAAA;EHuS9C;AGtSmC;EAAW,kBAAA;EHyS9C;AGxSmC;EAAW,kBAAA;EH2S9C;AG1SmC;EAAW,kBAAA;EH6S9C;AG5SmC;EAAW,kBAAA;EH+S9C;AG9SmC;EAAW,kBAAA;EHiT9C;AGhTmC;EAAW,kBAAA;EHmT9C;AGlTmC;EAAW,kBAAA;EHqT9C;AGpTmC;EAAW,kBAAA;EHuT9C;AGtTmC;EAAW,kBAAA;EHyT9C;AGxTmC;EAAW,kBAAA;EH2T9C;AG1TmC;EAAW,kBAAA;EH6T9C;AG5TmC;EAAW,kBAAA;EH+T9C;AG9TmC;EAAW,kBAAA;EHiU9C;AGhUmC;EAAW,kBAAA;EHmU9C;AGlUmC;EAAW,kBAAA;EHqU9C;AGpUmC;EAAW,kBAAA;EHuU9C;AGtUmC;EAAW,kBAAA;EHyU9C;AGxUmC;EAAW,kBAAA;EH2U9C;AG1UmC;EAAW,kBAAA;EH6U9C;AG5UmC;EAAW,kBAAA;EH+U9C;AG9UmC;EAAW,kBAAA;EHiV9C;AGhVmC;EAAW,kBAAA;EHmV9C;AGlVmC;EAAW,kBAAA;EHqV9C;AGpVmC;EAAW,kBAAA;EHuV9C;AGtVmC;EAAW,kBAAA;EHyV9C;AGxVmC;EAAW,kBAAA;EH2V9C;AG1VmC;EAAW,kBAAA;EH6V9C;AG5VmC;EAAW,kBAAA;EH+V9C;AG9VmC;EAAW,kBAAA;EHiW9C;AGhWmC;EAAW,kBAAA;EHmW9C;AGlWmC;EAAW,kBAAA;EHqW9C;AGpWmC;EAAW,kBAAA;EHuW9C;AGtWmC;EAAW,kBAAA;EHyW9C;AGxWmC;EAAW,kBAAA;EH2W9C;AG1WmC;EAAW,kBAAA;EH6W9C;AG5WmC;EAAW,kBAAA;EH+W9C;AG9WmC;EAAW,kBAAA;EHiX9C;AGhXmC;EAAW,kBAAA;EHmX9C;AGlXmC;EAAW,kBAAA;EHqX9C;AGpXmC;EAAW,kBAAA;EHuX9C;AGtXmC;EAAW,kBAAA;EHyX9C;AGxXmC;EAAW,kBAAA;EH2X9C;AG1XmC;EAAW,kBAAA;EH6X9C;AG5XmC;EAAW,kBAAA;EH+X9C;AG9XmC;EAAW,kBAAA;EHiY9C;AGhYmC;EAAW,kBAAA;EHmY9C;AGlYmC;EAAW,kBAAA;EHqY9C;AGpYmC;EAAW,kBAAA;EHuY9C;AGtYmC;EAAW,kBAAA;EHyY9C;AGxYmC;EAAW,kBAAA;EH2Y9C;AG1YmC;EAAW,kBAAA;EH6Y9C;AG5YmC;EAAW,kBAAA;EH+Y9C;AG9YmC;EAAW,kBAAA;EHiZ9C;AGhZmC;EAAW,kBAAA;EHmZ9C;AGlZmC;EAAW,kBAAA;EHqZ9C;AGpZmC;EAAW,kBAAA;EHuZ9C;AGtZmC;EAAW,kBAAA;EHyZ9C;AGxZmC;EAAW,kBAAA;EH2Z9C;AG1ZmC;EAAW,kBAAA;EH6Z9C;AG5ZmC;EAAW,kBAAA;EH+Z9C;AG9ZmC;EAAW,kBAAA;EHia9C;AGhamC;EAAW,kBAAA;EHma9C;AGlamC;EAAW,kBAAA;EHqa9C;AGpamC;EAAW,kBAAA;EHua9C;AGtamC;EAAW,kBAAA;EHya9C;AGxamC;EAAW,kBAAA;EH2a9C;AG1amC;EAAW,kBAAA;EH6a9C;AG5amC;EAAW,kBAAA;EH+a9C;AG9amC;EAAW,kBAAA;EHib9C;AGhbmC;EAAW,kBAAA;EHmb9C;AGlbmC;EAAW,kBAAA;EHqb9C;AGpbmC;EAAW,kBAAA;EHub9C;AGtbmC;EAAW,kBAAA;EHyb9C;AGxbmC;EAAW,kBAAA;EH2b9C;AG1bmC;EAAW,kBAAA;EH6b9C;AG5bmC;EAAW,kBAAA;EH+b9C;AG9bmC;EAAW,kBAAA;EHic9C;AGhcmC;EAAW,kBAAA;EHmc9C;AGlcmC;EAAW,kBAAA;EHqc9C;AGpcmC;EAAW,kBAAA;EHuc9C;AGtcmC;EAAW,kBAAA;EHyc9C;AGxcmC;EAAW,kBAAA;EH2c9C;AG1cmC;EAAW,kBAAA;EH6c9C;AG5cmC;EAAW,kBAAA;EH+c9C;AG9cmC;EAAW,kBAAA;EHid9C;AGhdmC;EAAW,kBAAA;EHmd9C;AGldmC;EAAW,kBAAA;EHqd9C;AGpdmC;EAAW,kBAAA;EHud9C;AGtdmC;EAAW,kBAAA;EHyd9C;AGxdmC;EAAW,kBAAA;EH2d9C;AG1dmC;EAAW,kBAAA;EH6d9C;AG5dmC;EAAW,kBAAA;EH+d9C;AG9dmC;EAAW,kBAAA;EHie9C;AGhemC;EAAW,kBAAA;EHme9C;AGlemC;EAAW,kBAAA;EHqe9C;AGpemC;EAAW,kBAAA;EHue9C;AGtemC;EAAW,kBAAA;EHye9C;AGxemC;EAAW,kBAAA;EH2e9C;AG1emC;EAAW,kBAAA;EH6e9C;AG5emC;EAAW,kBAAA;EH+e9C;AG9emC;EAAW,kBAAA;EHif9C;AGhfmC;EAAW,kBAAA;EHmf9C;AGlfmC;EAAW,kBAAA;EHqf9C;AGpfmC;EAAW,kBAAA;EHuf9C;AGtfmC;EAAW,kBAAA;EHyf9C;AGxfmC;EAAW,kBAAA;EH2f9C;AG1fmC;EAAW,kBAAA;EH6f9C;AG5fmC;EAAW,kBAAA;EH+f9C;AG9fmC;EAAW,kBAAA;EHigB9C;AGhgBmC;EAAW,kBAAA;EHmgB9C;AGlgBmC;EAAW,kBAAA;EHqgB9C;AGpgBmC;EAAW,kBAAA;EHugB9C;AGtgBmC;EAAW,kBAAA;EHygB9C;AGxgBmC;EAAW,kBAAA;EH2gB9C;AG1gBmC;EAAW,kBAAA;EH6gB9C;AG5gBmC;EAAW,kBAAA;EH+gB9C;AG9gBmC;EAAW,kBAAA;EHihB9C;AGhhBmC;EAAW,kBAAA;EHmhB9C;AGlhBmC;EAAW,kBAAA;EHqhB9C;AGphBmC;EAAW,kBAAA;EHuhB9C;AGthBmC;EAAW,kBAAA;EHyhB9C;AGxhBmC;EAAW,kBAAA;EH2hB9C;AG1hBmC;EAAW,kBAAA;EH6hB9C;AG5hBmC;EAAW,kBAAA;EH+hB9C;AG9hBmC;EAAW,kBAAA;EHiiB9C;AGhiBmC;EAAW,kBAAA;EHmiB9C;AGliBmC;EAAW,kBAAA;EHqiB9C;AGpiBmC;EAAW,kBAAA;EHuiB9C;AGtiBmC;EAAW,kBAAA;EHyiB9C;AGxiBmC;EAAW,kBAAA;EH2iB9C;AG1iBmC;EAAW,kBAAA;EH6iB9C;AG5iBmC;EAAW,kBAAA;EH+iB9C;AG9iBmC;EAAW,kBAAA;EHijB9C;AGhjBmC;EAAW,kBAAA;EHmjB9C;AGljBmC;EAAW,kBAAA;EHqjB9C;AGpjBmC;EAAW,kBAAA;EHujB9C;AGtjBmC;EAAW,kBAAA;EHyjB9C;AGxjBmC;EAAW,kBAAA;EH2jB9C;AG1jBmC;EAAW,kBAAA;EH6jB9C;AG5jBmC;EAAW,kBAAA;EH+jB9C;AG9jBmC;EAAW,kBAAA;EHikB9C;AGhkBmC;EAAW,kBAAA;EHmkB9C;AGlkBmC;EAAW,kBAAA;EHqkB9C;AGpkBmC;EAAW,kBAAA;EHukB9C;AGtkBmC;EAAW,kBAAA;EHykB9C;AGxkBmC;EAAW,kBAAA;EH2kB9C;AG1kBmC;EAAW,kBAAA;EH6kB9C;AG5kBmC;EAAW,kBAAA;EH+kB9C;AG9kBmC;EAAW,kBAAA;EHilB9C;AGhlBmC;EAAW,kBAAA;EHmlB9C;AGllBmC;EAAW,kBAAA;EHqlB9C;AGplBmC;EAAW,kBAAA;EHulB9C;AGtlBmC;EAAW,kBAAA;EHylB9C;AGxlBmC;EAAW,kBAAA;EH2lB9C;AG1lBmC;EAAW,kBAAA;EH6lB9C;AG5lBmC;EAAW,kBAAA;EH+lB9C;AG9lBmC;EAAW,kBAAA;EHimB9C;AGhmBmC;EAAW,kBAAA;EHmmB9C;AGlmBmC;EAAW,kBAAA;EHqmB9C;AGpmBmC;EAAW,kBAAA;EHumB9C;AGtmBmC;EAAW,kBAAA;EHymB9C;AGxmBmC;EAAW,kBAAA;EH2mB9C;AG1mBmC;EAAW,kBAAA;EH6mB9C;AG5mBmC;EAAW,kBAAA;EH+mB9C;AG9mBmC;EAAW,kBAAA;EHinB9C;AGhnBmC;EAAW,kBAAA;EHmnB9C;AGlnBmC;EAAW,kBAAA;EHqnB9C;AGpnBmC;EAAW,kBAAA;EHunB9C;AGtnBmC;EAAW,kBAAA;EHynB9C;AGxnBmC;EAAW,kBAAA;EH2nB9C;AG1nBmC;EAAW,kBAAA;EH6nB9C;AG5nBmC;EAAW,kBAAA;EH+nB9C;AG9nBmC;EAAW,kBAAA;EHioB9C;AGhoBmC;EAAW,kBAAA;EHmoB9C;AGloBmC;EAAW,kBAAA;EHqoB9C;AGpoBmC;EAAW,kBAAA;EHuoB9C;AGtoBmC;EAAW,kBAAA;EHyoB9C;AGhoBmC;EAAW,kBAAA;EHmoB9C;AGloBmC;EAAW,kBAAA;EHqoB9C;AGpoBmC;EAAW,kBAAA;EHuoB9C;AGtoBmC;EAAW,kBAAA;EHyoB9C;AGxoBmC;EAAW,kBAAA;EH2oB9C;AG1oBmC;EAAW,kBAAA;EH6oB9C;AG5oBmC;EAAW,kBAAA;EH+oB9C;AG9oBmC;EAAW,kBAAA;EHipB9C;AGhpBmC;EAAW,kBAAA;EHmpB9C;AGlpBmC;EAAW,kBAAA;EHqpB9C;AGppBmC;EAAW,kBAAA;EHupB9C;AGtpBmC;EAAW,kBAAA;EHypB9C;AGxpBmC;EAAW,kBAAA;EH2pB9C;AG1pBmC;EAAW,kBAAA;EH6pB9C;AG5pBmC;EAAW,kBAAA;EH+pB9C;AG9pBmC;EAAW,kBAAA;EHiqB9C;AGhqBmC;EAAW,kBAAA;EHmqB9C;AGlqBmC;EAAW,kBAAA;EHqqB9C;AGpqBmC;EAAW,kBAAA;EHuqB9C;AGtqBmC;EAAW,kBAAA;EHyqB9C;AGxqBmC;EAAW,kBAAA;EH2qB9C;AG1qBmC;EAAW,kBAAA;EH6qB9C;AG5qBmC;EAAW,kBAAA;EH+qB9C;AG9qBmC;EAAW,kBAAA;EHirB9C;AGhrBmC;EAAW,kBAAA;EHmrB9C;AGlrBmC;EAAW,kBAAA;EHqrB9C;AGprBmC;EAAW,kBAAA;EHurB9C;AGtrBmC;EAAW,kBAAA;EHyrB9C;AGxrBmC;EAAW,kBAAA;EH2rB9C;AG1rBmC;EAAW,kBAAA;EH6rB9C;AG5rBmC;EAAW,kBAAA;EH+rB9C;AG9rBmC;EAAW,kBAAA;EHisB9C;AGhsBmC;EAAW,kBAAA;EHmsB9C;AGlsBmC;EAAW,kBAAA;EHqsB9C;AGpsBmC;EAAW,kBAAA;EHusB9C;AGtsBmC;EAAW,kBAAA;EHysB9C;AGxsBmC;EAAW,kBAAA;EH2sB9C;AG1sBmC;EAAW,kBAAA;EH6sB9C;AG5sBmC;EAAW,kBAAA;EH+sB9C;AG9sBmC;EAAW,kBAAA;EHitB9C;AGhtBmC;EAAW,kBAAA;EHmtB9C;AGltBmC;EAAW,kBAAA;EHqtB9C;AGptBmC;EAAW,kBAAA;EHutB9C;AGttBmC;EAAW,kBAAA;EHytB9C;AGxtBmC;EAAW,kBAAA;EH2tB9C;AG1tBmC;EAAW,kBAAA;EH6tB9C;AG5tBmC;EAAW,kBAAA;EH+tB9C;AG9tBmC;EAAW,kBAAA;EHiuB9C;AGhuBmC;EAAW,kBAAA;EHmuB9C;AGluBmC;EAAW,kBAAA;EHquB9C;AGpuBmC;EAAW,kBAAA;EHuuB9C;AGtuBmC;EAAW,kBAAA;EHyuB9C;AI3gCD;ECgEE,gCAAA;EACG,6BAAA;EACK,wBAAA;EL88BT;AI7gCD;;EC6DE,gCAAA;EACG,6BAAA;EACK,wBAAA;ELo9BT;AI3gCD;EACE,iBAAA;EACA,+CAAA;EJ6gCD;AI1gCD;EACE,6DAAA;EACA,iBAAA;EACA,yBAAA;EACA,gBAAA;EACA,2BAAA;EJ4gCD;AIxgCD;;;;EAIE,sBAAA;EACA,oBAAA;EACA,sBAAA;EJ0gCD;AIpgCD;EACE,gBAAA;EACA,uBAAA;EJsgCD;AIpgCC;;EAEE,gBAAA;EACA,4BAAA;EJsgCH;AIngCC;EErDA,sBAAA;EAEA,4CAAA;EACA,sBAAA;EN0jCD;AI7/BD;EACE,WAAA;EJ+/BD;AIz/BD;EACE,wBAAA;EJ2/BD;AIv/BD;;;;;EGvEE,gBAAA;EACA,iBAAA;EACA,cAAA;EPqkCD;AI3/BD;EACE,oBAAA;EJ6/BD;AIv/BD;EACE,cAAA;EACA,yBAAA;EACA,2BAAA;EACA,2BAAA;EACA,oBAAA;EC6FA,0CAAA;EACK,qCAAA;EACG,kCAAA;EEvLR,uBAAA;EACA,iBAAA;EACA,cAAA;EPqlCD;AIv/BD;EACE,oBAAA;EJy/BD;AIn/BD;EACE,kBAAA;EACA,qBAAA;EACA,WAAA;EACA,+BAAA;EJq/BD;AI7+BD;EACE,oBAAA;EACA,YAAA;EACA,aAAA;EACA,cAAA;EACA,YAAA;EACA,kBAAA;EACA,wBAAA;EACA,WAAA;EJ++BD;AIv+BC;;EAEE,kBAAA;EACA,aAAA;EACA,cAAA;EACA,WAAA;EACA,mBAAA;EACA,YAAA;EJy+BH;AQpnCD;;;;;;;;;;;;EAEE,sBAAA;EACA,kBAAA;EACA,kBAAA;EACA,gBAAA;ERgoCD;AQroCD;;;;;;;;;;;;;;;;;;;;;;;;EASI,qBAAA;EACA,gBAAA;EACA,gBAAA;ERspCH;AQlpCD;;;;;;EAGE,kBAAA;EACA,qBAAA;ERupCD;AQ3pCD;;;;;;;;;;;;EAQI,gBAAA;ERiqCH;AQ9pCD;;;;;;EAGE,kBAAA;EACA,qBAAA;ERmqCD;AQvqCD;;;;;;;;;;;;EAQI,gBAAA;ER6qCH;AQzqCD;;EAAU,iBAAA;ER6qCT;AQ5qCD;;EAAU,iBAAA;ERgrCT;AQ/qCD;;EAAU,iBAAA;ERmrCT;AQlrCD;;EAAU,iBAAA;ERsrCT;AQrrCD;;EAAU,iBAAA;ERyrCT;AQxrCD;;EAAU,iBAAA;ER4rCT;AQtrCD;EACE,kBAAA;ERwrCD;AQrrCD;EACE,qBAAA;EACA,iBAAA;EACA,kBAAA;EACA,kBAAA;ERurCD;AQlrCD;EAAA;IAFI,iBAAA;IRwrCD;EACF;AQhrCD;;EAEE,gBAAA;ERkrCD;AQ/qCD;;EAEE,2BAAA;EACA,eAAA;ERirCD;AQ7qCD;EAAuB,kBAAA;ERgrCtB;AQ/qCD;EAAuB,mBAAA;ERkrCtB;AQjrCD;EAAuB,oBAAA;ERorCtB;AQnrCD;EAAuB,qBAAA;ERsrCtB;AQrrCD;EAAuB,qBAAA;ERwrCtB;AQrrCD;EAAuB,2BAAA;ERwrCtB;AQvrCD;EAAuB,2BAAA;ER0rCtB;AQzrCD;EAAuB,4BAAA;ER4rCtB;AQzrCD;EACE,gBAAA;ER2rCD;AQzrCD;ECrGE,gBAAA;ETiyCD;AShyCC;EACE,gBAAA;ETkyCH;AQ5rCD;ECxGE,gBAAA;ETuyCD;AStyCC;EACE,gBAAA;ETwyCH;AQ/rCD;EC3GE,gBAAA;ET6yCD;AS5yCC;EACE,gBAAA;ET8yCH;AQlsCD;EC9GE,gBAAA;ETmzCD;ASlzCC;EACE,gBAAA;ETozCH;AQrsCD;ECjHE,gBAAA;ETyzCD;ASxzCC;EACE,gBAAA;ET0zCH;AQpsCD;EAGE,aAAA;EE3HA,2BAAA;EVg0CD;AU/zCC;EACE,2BAAA;EVi0CH;AQrsCD;EE9HE,2BAAA;EVs0CD;AUr0CC;EACE,2BAAA;EVu0CH;AQxsCD;EEjIE,2BAAA;EV40CD;AU30CC;EACE,2BAAA;EV60CH;AQ3sCD;EEpIE,2BAAA;EVk1CD;AUj1CC;EACE,2BAAA;EVm1CH;AQ9sCD;EEvIE,2BAAA;EVw1CD;AUv1CC;EACE,2BAAA;EVy1CH;AQ5sCD;EACE,qBAAA;EACA,qBAAA;EACA,kCAAA;ER8sCD;AQtsCD;;EAEE,eAAA;EACA,qBAAA;ERwsCD;AQ3sCD;;;;EAMI,kBAAA;ER2sCH;AQpsCD;EACE,iBAAA;EACA,kBAAA;ERssCD;AQlsCD;EALE,iBAAA;EACA,kBAAA;EAMA,mBAAA;ERqsCD;AQvsCD;EAKI,uBAAA;EACA,mBAAA;EACA,oBAAA;ERqsCH;AQhsCD;EACE,eAAA;EACA,qBAAA;ERksCD;AQhsCD;;EAEE,yBAAA;ERksCD;AQhsCD;EACE,mBAAA;ERksCD;AQhsCD;EACE,gBAAA;ERksCD;AQzqCD;EAAA;IAVM,aAAA;IACA,cAAA;IACA,aAAA;IACA,mBAAA;IGtNJ,kBAAA;IACA,yBAAA;IACA,qBAAA;IX84CC;EQnrCH;IAHM,oBAAA;IRyrCH;EACF;AQhrCD;;EAGE,cAAA;EACA,mCAAA;ERirCD;AQ/qCD;EACE,gBAAA;EACA,2BAAA;ERirCD;AQ7qCD;EACE,oBAAA;EACA,kBAAA;EACA,mBAAA;EACA,gCAAA;ER+qCD;AQ1qCG;;;EACE,kBAAA;ER8qCL;AQxrCD;;;EAmBI,gBAAA;EACA,gBAAA;EACA,yBAAA;EACA,gBAAA;ER0qCH;AQxqCG;;;EACE,wBAAA;ER4qCL;AQpqCD;;EAEE,qBAAA;EACA,iBAAA;EACA,iCAAA;EACA,gBAAA;EACA,mBAAA;ERsqCD;AQhqCG;;;;;;EAAW,aAAA;ERwqCd;AQvqCG;;;;;;EACE,wBAAA;ER8qCL;AQxqCD;EACE,qBAAA;EACA,oBAAA;EACA,yBAAA;ER0qCD;AYh9CD;;;;EAIE,gEAAA;EZk9CD;AY98CD;EACE,kBAAA;EACA,gBAAA;EACA,gBAAA;EACA,2BAAA;EACA,oBAAA;EZg9CD;AY58CD;EACE,kBAAA;EACA,gBAAA;EACA,gBAAA;EACA,2BAAA;EACA,oBAAA;EACA,wDAAA;UAAA,gDAAA;EZ88CD;AYp9CD;EASI,YAAA;EACA,iBAAA;EACA,mBAAA;EACA,0BAAA;UAAA,kBAAA;EZ88CH;AYz8CD;EACE,gBAAA;EACA,gBAAA;EACA,kBAAA;EACA,iBAAA;EACA,yBAAA;EACA,uBAAA;EACA,uBAAA;EACA,gBAAA;EACA,2BAAA;EACA,2BAAA;EACA,oBAAA;EZ28CD;AYt9CD;EAeI,YAAA;EACA,oBAAA;EACA,gBAAA;EACA,uBAAA;EACA,+BAAA;EACA,kBAAA;EZ08CH;AYr8CD;EACE,mBAAA;EACA,oBAAA;EZu8CD;AajgDD;ECHE,oBAAA;EACA,mBAAA;EACA,oBAAA;EACA,qBAAA;EdugDD;AajgDC;EAAA;IAFE,cAAA;IbugDD;EACF;AangDC;EAAA;IAFE,cAAA;IbygDD;EACF;AargDD;EAAA;IAFI,eAAA;Ib2gDD;EACF;AalgDD;ECvBE,oBAAA;EACA,mBAAA;EACA,oBAAA;EACA,qBAAA;Ed4hDD;Aa//CD;ECvBE,oBAAA;EACA,qBAAA;EdyhDD;AezhDG;EACE,oBAAA;EAEA,iBAAA;EAEA,oBAAA;EACA,qBAAA;EfyhDL;AezgDG;EACE,aAAA;Ef2gDL;AepgDC;EACE,aAAA;EfsgDH;AevgDC;EACE,qBAAA;EfygDH;Ae1gDC;EACE,qBAAA;Ef4gDH;Ae7gDC;EACE,YAAA;Ef+gDH;AehhDC;EACE,qBAAA;EfkhDH;AenhDC;EACE,qBAAA;EfqhDH;AethDC;EACE,YAAA;EfwhDH;AezhDC;EACE,qBAAA;Ef2hDH;Ae5hDC;EACE,qBAAA;Ef8hDH;Ae/hDC;EACE,YAAA;EfiiDH;AeliDC;EACE,qBAAA;EfoiDH;AeriDC;EACE,oBAAA;EfuiDH;AezhDC;EACE,aAAA;Ef2hDH;Ae5hDC;EACE,qBAAA;Ef8hDH;Ae/hDC;EACE,qBAAA;EfiiDH;AeliDC;EACE,YAAA;EfoiDH;AeriDC;EACE,qBAAA;EfuiDH;AexiDC;EACE,qBAAA;Ef0iDH;Ae3iDC;EACE,YAAA;Ef6iDH;Ae9iDC;EACE,qBAAA;EfgjDH;AejjDC;EACE,qBAAA;EfmjDH;AepjDC;EACE,YAAA;EfsjDH;AevjDC;EACE,qBAAA;EfyjDH;Ae1jDC;EACE,oBAAA;Ef4jDH;AexjDC;EACE,aAAA;Ef0jDH;Ae1kDC;EACE,YAAA;Ef4kDH;Ae7kDC;EACE,oBAAA;Ef+kDH;AehlDC;EACE,oBAAA;EfklDH;AenlDC;EACE,WAAA;EfqlDH;AetlDC;EACE,oBAAA;EfwlDH;AezlDC;EACE,oBAAA;Ef2lDH;Ae5lDC;EACE,WAAA;Ef8lDH;Ae/lDC;EACE,oBAAA;EfimDH;AelmDC;EACE,oBAAA;EfomDH;AermDC;EACE,WAAA;EfumDH;AexmDC;EACE,oBAAA;Ef0mDH;Ae3mDC;EACE,mBAAA;Ef6mDH;AezmDC;EACE,YAAA;Ef2mDH;Ae7lDC;EACE,mBAAA;Ef+lDH;AehmDC;EACE,2BAAA;EfkmDH;AenmDC;EACE,2BAAA;EfqmDH;AetmDC;EACE,kBAAA;EfwmDH;AezmDC;EACE,2BAAA;Ef2mDH;Ae5mDC;EACE,2BAAA;Ef8mDH;Ae/mDC;EACE,kBAAA;EfinDH;AelnDC;EACE,2BAAA;EfonDH;AernDC;EACE,2BAAA;EfunDH;AexnDC;EACE,kBAAA;Ef0nDH;Ae3nDC;EACE,2BAAA;Ef6nDH;Ae9nDC;EACE,0BAAA;EfgoDH;AejoDC;EACE,iBAAA;EfmoDH;AanoDD;EElCI;IACE,aAAA;IfwqDH;EejqDD;IACE,aAAA;IfmqDD;EepqDD;IACE,qBAAA;IfsqDD;EevqDD;IACE,qBAAA;IfyqDD;Ee1qDD;IACE,YAAA;If4qDD;Ee7qDD;IACE,qBAAA;If+qDD;EehrDD;IACE,qBAAA;IfkrDD;EenrDD;IACE,YAAA;IfqrDD;EetrDD;IACE,qBAAA;IfwrDD;EezrDD;IACE,qBAAA;If2rDD;Ee5rDD;IACE,YAAA;If8rDD;Ee/rDD;IACE,qBAAA;IfisDD;EelsDD;IACE,oBAAA;IfosDD;EetrDD;IACE,aAAA;IfwrDD;EezrDD;IACE,qBAAA;If2rDD;Ee5rDD;IACE,qBAAA;If8rDD;Ee/rDD;IACE,YAAA;IfisDD;EelsDD;IACE,qBAAA;IfosDD;EersDD;IACE,qBAAA;IfusDD;EexsDD;IACE,YAAA;If0sDD;Ee3sDD;IACE,qBAAA;If6sDD;Ee9sDD;IACE,qBAAA;IfgtDD;EejtDD;IACE,YAAA;IfmtDD;EeptDD;IACE,qBAAA;IfstDD;EevtDD;IACE,oBAAA;IfytDD;EertDD;IACE,aAAA;IfutDD;EevuDD;IACE,YAAA;IfyuDD;Ee1uDD;IACE,oBAAA;If4uDD;Ee7uDD;IACE,oBAAA;If+uDD;EehvDD;IACE,WAAA;IfkvDD;EenvDD;IACE,oBAAA;IfqvDD;EetvDD;IACE,oBAAA;IfwvDD;EezvDD;IACE,WAAA;If2vDD;Ee5vDD;IACE,oBAAA;If8vDD;Ee/vDD;IACE,oBAAA;IfiwDD;EelwDD;IACE,WAAA;IfowDD;EerwDD;IACE,oBAAA;IfuwDD;EexwDD;IACE,mBAAA;If0wDD;EetwDD;IACE,YAAA;IfwwDD;Ee1vDD;IACE,mBAAA;If4vDD;Ee7vDD;IACE,2BAAA;If+vDD;EehwDD;IACE,2BAAA;IfkwDD;EenwDD;IACE,kBAAA;IfqwDD;EetwDD;IACE,2BAAA;IfwwDD;EezwDD;IACE,2BAAA;If2wDD;Ee5wDD;IACE,kBAAA;If8wDD;Ee/wDD;IACE,2BAAA;IfixDD;EelxDD;IACE,2BAAA;IfoxDD;EerxDD;IACE,kBAAA;IfuxDD;EexxDD;IACE,2BAAA;If0xDD;Ee3xDD;IACE,0BAAA;If6xDD;Ee9xDD;IACE,iBAAA;IfgyDD;EACF;AaxxDD;EE3CI;IACE,aAAA;Ifs0DH;Ee/zDD;IACE,aAAA;Ifi0DD;Eel0DD;IACE,qBAAA;Ifo0DD;Eer0DD;IACE,qBAAA;Ifu0DD;Eex0DD;IACE,YAAA;If00DD;Ee30DD;IACE,qBAAA;If60DD;Ee90DD;IACE,qBAAA;Ifg1DD;Eej1DD;IACE,YAAA;Ifm1DD;Eep1DD;IACE,qBAAA;Ifs1DD;Eev1DD;IACE,qBAAA;Ify1DD;Ee11DD;IACE,YAAA;If41DD;Ee71DD;IACE,qBAAA;If+1DD;Eeh2DD;IACE,oBAAA;Ifk2DD;Eep1DD;IACE,aAAA;Ifs1DD;Eev1DD;IACE,qBAAA;Ify1DD;Ee11DD;IACE,qBAAA;If41DD;Ee71DD;IACE,YAAA;If+1DD;Eeh2DD;IACE,qBAAA;Ifk2DD;Een2DD;IACE,qBAAA;Ifq2DD;Eet2DD;IACE,YAAA;Ifw2DD;Eez2DD;IACE,qBAAA;If22DD;Ee52DD;IACE,qBAAA;If82DD;Ee/2DD;IACE,YAAA;Ifi3DD;Eel3DD;IACE,qBAAA;Ifo3DD;Eer3DD;IACE,oBAAA;Ifu3DD;Een3DD;IACE,aAAA;Ifq3DD;Eer4DD;IACE,YAAA;Ifu4DD;Eex4DD;IACE,oBAAA;If04DD;Ee34DD;IACE,oBAAA;If64DD;Ee94DD;IACE,WAAA;Ifg5DD;Eej5DD;IACE,oBAAA;Ifm5DD;Eep5DD;IACE,oBAAA;Ifs5DD;Eev5DD;IACE,WAAA;Ify5DD;Ee15DD;IACE,oBAAA;If45DD;Ee75DD;IACE,oBAAA;If+5DD;Eeh6DD;IACE,WAAA;Ifk6DD;Een6DD;IACE,oBAAA;Ifq6DD;Eet6DD;IACE,mBAAA;Ifw6DD;Eep6DD;IACE,YAAA;Ifs6DD;Eex5DD;IACE,mBAAA;If05DD;Ee35DD;IACE,2BAAA;If65DD;Ee95DD;IACE,2BAAA;Ifg6DD;Eej6DD;IACE,kBAAA;Ifm6DD;Eep6DD;IACE,2BAAA;Ifs6DD;Eev6DD;IACE,2BAAA;Ify6DD;Ee16DD;IACE,kBAAA;If46DD;Ee76DD;IACE,2BAAA;If+6DD;Eeh7DD;IACE,2BAAA;Ifk7DD;Een7DD;IACE,kBAAA;Ifq7DD;Eet7DD;IACE,2BAAA;Ifw7DD;Eez7DD;IACE,0BAAA;If27DD;Ee57DD;IACE,iBAAA;If87DD;EACF;Aan7DD;EE9CI;IACE,aAAA;Ifo+DH;Ee79DD;IACE,aAAA;If+9DD;Eeh+DD;IACE,qBAAA;Ifk+DD;Een+DD;IACE,qBAAA;Ifq+DD;Eet+DD;IACE,YAAA;Ifw+DD;Eez+DD;IACE,qBAAA;If2+DD;Ee5+DD;IACE,qBAAA;If8+DD;Ee/+DD;IACE,YAAA;Ifi/DD;Eel/DD;IACE,qBAAA;Ifo/DD;Eer/DD;IACE,qBAAA;Ifu/DD;Eex/DD;IACE,YAAA;If0/DD;Ee3/DD;IACE,qBAAA;If6/DD;Ee9/DD;IACE,oBAAA;IfggED;Eel/DD;IACE,aAAA;Ifo/DD;Eer/DD;IACE,qBAAA;Ifu/DD;Eex/DD;IACE,qBAAA;If0/DD;Ee3/DD;IACE,YAAA;If6/DD;Ee9/DD;IACE,qBAAA;IfggED;EejgED;IACE,qBAAA;IfmgED;EepgED;IACE,YAAA;IfsgED;EevgED;IACE,qBAAA;IfygED;Ee1gED;IACE,qBAAA;If4gED;Ee7gED;IACE,YAAA;If+gED;EehhED;IACE,qBAAA;IfkhED;EenhED;IACE,oBAAA;IfqhED;EejhED;IACE,aAAA;IfmhED;EeniED;IACE,YAAA;IfqiED;EetiED;IACE,oBAAA;IfwiED;EeziED;IACE,oBAAA;If2iED;Ee5iED;IACE,WAAA;If8iED;Ee/iED;IACE,oBAAA;IfijED;EeljED;IACE,oBAAA;IfojED;EerjED;IACE,WAAA;IfujED;EexjED;IACE,oBAAA;If0jED;Ee3jED;IACE,oBAAA;If6jED;Ee9jED;IACE,WAAA;IfgkED;EejkED;IACE,oBAAA;IfmkED;EepkED;IACE,mBAAA;IfskED;EelkED;IACE,YAAA;IfokED;EetjED;IACE,mBAAA;IfwjED;EezjED;IACE,2BAAA;If2jED;Ee5jED;IACE,2BAAA;If8jED;Ee/jED;IACE,kBAAA;IfikED;EelkED;IACE,2BAAA;IfokED;EerkED;IACE,2BAAA;IfukED;EexkED;IACE,kBAAA;If0kED;Ee3kED;IACE,2BAAA;If6kED;Ee9kED;IACE,2BAAA;IfglED;EejlED;IACE,kBAAA;IfmlED;EeplED;IACE,2BAAA;IfslED;EevlED;IACE,0BAAA;IfylED;Ee1lED;IACE,iBAAA;If4lED;EACF;AgBhqED;EACE,+BAAA;EhBkqED;AgBhqED;EACE,kBAAA;EACA,qBAAA;EACA,gBAAA;EACA,kBAAA;EhBkqED;AgBhqED;EACE,kBAAA;EhBkqED;AgB5pED;EACE,aAAA;EACA,iBAAA;EACA,qBAAA;EhB8pED;AgBjqED;;;;;;EAWQ,cAAA;EACA,yBAAA;EACA,qBAAA;EACA,+BAAA;EhB8pEP;AgB5qED;EAoBI,wBAAA;EACA,kCAAA;EhB2pEH;AgBhrED;;;;;;EA8BQ,eAAA;EhB0pEP;AgBxrED;EAoCI,+BAAA;EhBupEH;AgB3rED;EAyCI,2BAAA;EhBqpEH;AgB9oED;;;;;;EAOQ,cAAA;EhB+oEP;AgBpoED;EACE,2BAAA;EhBsoED;AgBvoED;;;;;;EAQQ,2BAAA;EhBuoEP;AgB/oED;;EAeM,0BAAA;EhBooEL;AgB1nED;EAEI,2BAAA;EhB2nEH;AgBlnED;EAEI,2BAAA;EhBmnEH;AgB1mED;EACE,kBAAA;EACA,aAAA;EACA,uBAAA;EhB4mED;AgBvmEG;;EACE,kBAAA;EACA,aAAA;EACA,qBAAA;EhB0mEL;AiBtvEC;;;;;;;;;;;;EAOI,2BAAA;EjB6vEL;AiBvvEC;;;;;EAMI,2BAAA;EjBwvEL;AiB3wEC;;;;;;;;;;;;EAOI,2BAAA;EjBkxEL;AiB5wEC;;;;;EAMI,2BAAA;EjB6wEL;AiBhyEC;;;;;;;;;;;;EAOI,2BAAA;EjBuyEL;AiBjyEC;;;;;EAMI,2BAAA;EjBkyEL;AiBrzEC;;;;;;;;;;;;EAOI,2BAAA;EjB4zEL;AiBtzEC;;;;;EAMI,2BAAA;EjBuzEL;AiB10EC;;;;;;;;;;;;EAOI,2BAAA;EjBi1EL;AiB30EC;;;;;EAMI,2BAAA;EjB40EL;AgB1rED;EACE,kBAAA;EACA,mBAAA;EhB4rED;AgB/nED;EAAA;IA1DI,aAAA;IACA,qBAAA;IACA,oBAAA;IACA,8CAAA;IACA,2BAAA;IhB6rED;EgBvoEH;IAlDM,kBAAA;IhB4rEH;EgB1oEH;;;;;;IAzCY,qBAAA;IhB2rET;EgBlpEH;IAjCM,WAAA;IhBsrEH;EgBrpEH;;;;;;IAxBY,gBAAA;IhBqrET;EgB7pEH;;;;;;IApBY,iBAAA;IhByrET;EgBrqEH;;;;IAPY,kBAAA;IhBkrET;EACF;AkB54ED;EACE,YAAA;EACA,WAAA;EACA,WAAA;EAIA,cAAA;ElB24ED;AkBx4ED;EACE,gBAAA;EACA,aAAA;EACA,YAAA;EACA,qBAAA;EACA,iBAAA;EACA,sBAAA;EACA,gBAAA;EACA,WAAA;EACA,kCAAA;ElB04ED;AkBv4ED;EACE,uBAAA;EACA,iBAAA;EACA,oBAAA;EACA,mBAAA;ElBy4ED;AkB93ED;Eb4BE,gCAAA;EACG,6BAAA;EACK,wBAAA;ELq2ET;AkB93ED;;EAEE,iBAAA;EACA,oBAAA;EACA,qBAAA;ElBg4ED;AkB53ED;EACE,gBAAA;ElB83ED;AkB13ED;EACE,gBAAA;EACA,aAAA;ElB43ED;AkBx3ED;;EAEE,cAAA;ElB03ED;AkBt3ED;;;EZxEE,sBAAA;EAEA,4CAAA;EACA,sBAAA;ENk8ED;AkBt3ED;EACE,gBAAA;EACA,kBAAA;EACA,iBAAA;EACA,yBAAA;EACA,gBAAA;ElBw3ED;AkB91ED;EACE,gBAAA;EACA,aAAA;EACA,cAAA;EACA,mBAAA;EACA,iBAAA;EACA,yBAAA;EACA,gBAAA;EACA,2BAAA;EACA,wBAAA;EACA,2BAAA;EACA,oBAAA;EbzDA,0DAAA;EACQ,kDAAA;EAyHR,wFAAA;EACK,2EAAA;EACG,wEAAA;ELkyET;AmB16EC;EACE,uBAAA;EACA,YAAA;EdUF,wFAAA;EACQ,gFAAA;ELm6ET;AKl4EC;EACE,gBAAA;EACA,YAAA;ELo4EH;AKl4EC;EAA0B,gBAAA;ELq4E3B;AKp4EC;EAAgC,gBAAA;ELu4EjC;AkBt2EC;;;EAGE,qBAAA;EACA,2BAAA;EACA,YAAA;ElBw2EH;AkBp2EC;EACE,cAAA;ElBs2EH;AkB11ED;EACE,0BAAA;ElB41ED;AkBxzED;EAxBE;;;;IAIE,mBAAA;IlBm1ED;EkBj1EC;;;;;;;;IAEE,mBAAA;IlBy1EH;EkBt1EC;;;;;;;;IAEE,mBAAA;IlB81EH;EACF;AkBp1ED;EACE,qBAAA;ElBs1ED;AkB90ED;;EAEE,oBAAA;EACA,gBAAA;EACA,kBAAA;EACA,qBAAA;ElBg1ED;AkBr1ED;;EAQI,kBAAA;EACA,oBAAA;EACA,kBAAA;EACA,qBAAA;EACA,iBAAA;ElBi1EH;AkB90ED;;;;EAIE,oBAAA;EACA,oBAAA;EACA,oBAAA;ElBg1ED;AkB70ED;;EAEE,kBAAA;ElB+0ED;AkB30ED;;EAEE,uBAAA;EACA,oBAAA;EACA,kBAAA;EACA,wBAAA;EACA,qBAAA;EACA,iBAAA;ElB60ED;AkB30ED;;EAEE,eAAA;EACA,mBAAA;ElB60ED;AkBp0EC;;;;;;EAGE,qBAAA;ElBy0EH;AkBn0EC;;;;EAEE,qBAAA;ElBu0EH;AkBj0EC;;;;EAGI,qBAAA;ElBo0EL;AkBzzED;EAEE,kBAAA;EACA,qBAAA;EAEA,kBAAA;ElByzED;AkBvzEC;;EAEE,iBAAA;EACA,kBAAA;ElByzEH;AkB5yED;ECpPE,cAAA;EACA,mBAAA;EACA,iBAAA;EACA,kBAAA;EACA,oBAAA;EnBmiFD;AmBjiFC;EACE,cAAA;EACA,mBAAA;EnBmiFH;AmBhiFC;;EAEE,cAAA;EnBkiFH;AkBxzED;ECvPE,cAAA;EACA,mBAAA;EACA,iBAAA;EACA,kBAAA;EACA,oBAAA;EnBkjFD;AmBhjFC;EACE,cAAA;EACA,mBAAA;EnBkjFH;AmB/iFC;;EAEE,cAAA;EnBijFH;AkBv0ED;EAKI,cAAA;EACA,mBAAA;EACA,iBAAA;EACA,kBAAA;ElBq0EH;AkBj0ED;ECnQE,cAAA;EACA,oBAAA;EACA,iBAAA;EACA,wBAAA;EACA,oBAAA;EnBukFD;AmBrkFC;EACE,cAAA;EACA,mBAAA;EnBukFH;AmBpkFC;;EAEE,cAAA;EnBskFH;AkB70ED;ECtQE,cAAA;EACA,oBAAA;EACA,iBAAA;EACA,wBAAA;EACA,oBAAA;EnBslFD;AmBplFC;EACE,cAAA;EACA,mBAAA;EnBslFH;AmBnlFC;;EAEE,cAAA;EnBqlFH;AkB51ED;EAKI,cAAA;EACA,oBAAA;EACA,iBAAA;EACA,wBAAA;ElB01EH;AkBj1ED;EAEE,oBAAA;ElBk1ED;AkBp1ED;EAMI,uBAAA;ElBi1EH;AkB70ED;EACE,oBAAA;EACA,QAAA;EACA,UAAA;EACA,YAAA;EACA,gBAAA;EACA,aAAA;EACA,cAAA;EACA,mBAAA;EACA,oBAAA;EACA,sBAAA;ElB+0ED;AkB70ED;EACE,aAAA;EACA,cAAA;EACA,mBAAA;ElB+0ED;AkB70ED;EACE,aAAA;EACA,cAAA;EACA,mBAAA;ElB+0ED;AkB30ED;;;;;;;;;;EC7WI,gBAAA;EnBosFH;AkBv1ED;ECzWI,uBAAA;Ed+CF,0DAAA;EACQ,kDAAA;ELqpFT;AmBnsFG;EACE,uBAAA;Ed4CJ,2EAAA;EACQ,mEAAA;EL0pFT;AkBj2ED;EC/VI,gBAAA;EACA,uBAAA;EACA,2BAAA;EnBmsFH;AkBt2ED;ECzVI,gBAAA;EnBksFH;AkBt2ED;;;;;;;;;;EChXI,gBAAA;EnBkuFH;AkBl3ED;EC5WI,uBAAA;Ed+CF,0DAAA;EACQ,kDAAA;ELmrFT;AmBjuFG;EACE,uBAAA;Ed4CJ,2EAAA;EACQ,mEAAA;ELwrFT;AkB53ED;EClWI,gBAAA;EACA,uBAAA;EACA,2BAAA;EnBiuFH;AkBj4ED;EC5VI,gBAAA;EnBguFH;AkBj4ED;;;;;;;;;;ECnXI,gBAAA;EnBgwFH;AkB74ED;EC/WI,uBAAA;Ed+CF,0DAAA;EACQ,kDAAA;ELitFT;AmB/vFG;EACE,uBAAA;Ed4CJ,2EAAA;EACQ,mEAAA;ELstFT;AkBv5ED;ECrWI,gBAAA;EACA,uBAAA;EACA,2BAAA;EnB+vFH;AkB55ED;EC/VI,gBAAA;EnB8vFH;AkBx5EC;EACG,WAAA;ElB05EJ;AkBx5EC;EACG,QAAA;ElB05EJ;AkBh5ED;EACE,gBAAA;EACA,iBAAA;EACA,qBAAA;EACA,gBAAA;ElBk5ED;AkB/zED;EAAA;IA9DM,uBAAA;IACA,kBAAA;IACA,wBAAA;IlBi4EH;EkBr0EH;IAvDM,uBAAA;IACA,aAAA;IACA,wBAAA;IlB+3EH;EkB10EH;IAhDM,uBAAA;IlB63EH;EkB70EH;IA5CM,uBAAA;IACA,wBAAA;IlB43EH;EkBj1EH;;;IAtCQ,aAAA;IlB43EL;EkBt1EH;IAhCM,aAAA;IlBy3EH;EkBz1EH;IA5BM,kBAAA;IACA,wBAAA;IlBw3EH;EkB71EH;;IApBM,uBAAA;IACA,eAAA;IACA,kBAAA;IACA,wBAAA;IlBq3EH;EkBp2EH;;IAdQ,iBAAA;IlBs3EL;EkBx2EH;;IATM,oBAAA;IACA,gBAAA;IlBq3EH;EkB72EH;IAHM,QAAA;IlBm3EH;EACF;AkBz2ED;;;;EASI,eAAA;EACA,kBAAA;EACA,kBAAA;ElBs2EH;AkBj3ED;;EAiBI,kBAAA;ElBo2EH;AkBr3ED;EJzeE,oBAAA;EACA,qBAAA;Edi2FD;AkBl1EC;EAAA;IAVI,mBAAA;IACA,kBAAA;IACA,kBAAA;IlBg2EH;EACF;AkBh4ED;EAwCI,aAAA;ElB21EH;AkB90EC;EAAA;IAHM,0BAAA;IlBq1EL;EACF;AkB50EC;EAAA;IAHM,kBAAA;IlBm1EL;EACF;AoB73FD;EACE,uBAAA;EACA,kBAAA;EACA,qBAAA;EACA,oBAAA;EACA,wBAAA;EACA,gCAAA;MAAA,4BAAA;EACA,iBAAA;EACA,wBAAA;EACA,+BAAA;EACA,qBAAA;EC6BA,mBAAA;EACA,iBAAA;EACA,yBAAA;EACA,oBAAA;EhB4KA,2BAAA;EACG,wBAAA;EACC,uBAAA;EACI,mBAAA;ELwrFT;AoBh4FG;;;;;;EdrBF,sBAAA;EAEA,4CAAA;EACA,sBAAA;EN45FD;AoBp4FC;;;EAGE,gBAAA;EACA,uBAAA;EpBs4FH;AoBn4FC;;EAEE,YAAA;EACA,wBAAA;Ef2BF,0DAAA;EACQ,kDAAA;EL22FT;AoBn4FC;;;EAGE,qBAAA;EACA,sBAAA;EE9CF,eAAA;EAGA,2BAAA;EjB8DA,0BAAA;EACQ,kBAAA;ELq3FT;AoB/3FD;ECrDE,gBAAA;EACA,2BAAA;EACA,uBAAA;ErBu7FD;AqBr7FC;;;;;;EAME,gBAAA;EACA,2BAAA;EACI,uBAAA;ErBu7FP;AqBr7FC;;;EAGE,wBAAA;ErBu7FH;AqBl7FG;;;;;;;;;;;;;;;;;;EAME,2BAAA;EACI,uBAAA;ErBg8FT;AoBx6FD;ECnBI,gBAAA;EACA,2BAAA;ErB87FH;AoBz6FD;ECxDE,gBAAA;EACA,2BAAA;EACA,uBAAA;ErBo+FD;AqBl+FC;;;;;;EAME,gBAAA;EACA,2BAAA;EACI,uBAAA;ErBo+FP;AqBl+FC;;;EAGE,wBAAA;ErBo+FH;AqB/9FG;;;;;;;;;;;;;;;;;;EAME,2BAAA;EACI,uBAAA;ErB6+FT;AoBl9FD;ECtBI,gBAAA;EACA,2BAAA;ErB2+FH;AoBl9FD;EC5DE,gBAAA;EACA,2BAAA;EACA,uBAAA;ErBihGD;AqB/gGC;;;;;;EAME,gBAAA;EACA,2BAAA;EACI,uBAAA;ErBihGP;AqB/gGC;;;EAGE,wBAAA;ErBihGH;AqB5gGG;;;;;;;;;;;;;;;;;;EAME,2BAAA;EACI,uBAAA;ErB0hGT;AoB3/FD;EC1BI,gBAAA;EACA,2BAAA;ErBwhGH;AoB3/FD;EChEE,gBAAA;EACA,2BAAA;EACA,uBAAA;ErB8jGD;AqB5jGC;;;;;;EAME,gBAAA;EACA,2BAAA;EACI,uBAAA;ErB8jGP;AqB5jGC;;;EAGE,wBAAA;ErB8jGH;AqBzjGG;;;;;;;;;;;;;;;;;;EAME,2BAAA;EACI,uBAAA;ErBukGT;AoBpiGD;EC9BI,gBAAA;EACA,2BAAA;ErBqkGH;AoBpiGD;ECpEE,gBAAA;EACA,2BAAA;EACA,uBAAA;ErB2mGD;AqBzmGC;;;;;;EAME,gBAAA;EACA,2BAAA;EACI,uBAAA;ErB2mGP;AqBzmGC;;;EAGE,wBAAA;ErB2mGH;AqBtmGG;;;;;;;;;;;;;;;;;;EAME,2BAAA;EACI,uBAAA;ErBonGT;AoB7kGD;EClCI,gBAAA;EACA,2BAAA;ErBknGH;AoB7kGD;ECxEE,gBAAA;EACA,2BAAA;EACA,uBAAA;ErBwpGD;AqBtpGC;;;;;;EAME,gBAAA;EACA,2BAAA;EACI,uBAAA;ErBwpGP;AqBtpGC;;;EAGE,wBAAA;ErBwpGH;AqBnpGG;;;;;;;;;;;;;;;;;;EAME,2BAAA;EACI,uBAAA;ErBiqGT;AoBtnGD;ECtCI,gBAAA;EACA,2BAAA;ErB+pGH;AoBjnGD;EACE,gBAAA;EACA,qBAAA;EACA,kBAAA;EpBmnGD;AoBjnGC;;;;;EAKE,+BAAA;Ef7BF,0BAAA;EACQ,kBAAA;ELipGT;AoBlnGC;;;;EAIE,2BAAA;EpBonGH;AoBlnGC;;EAEE,gBAAA;EACA,4BAAA;EACA,+BAAA;EpBonGH;AoBhnGG;;;;EAEE,gBAAA;EACA,uBAAA;EpBonGL;AoB3mGD;;EC/EE,oBAAA;EACA,iBAAA;EACA,wBAAA;EACA,oBAAA;ErB8rGD;AoB9mGD;;ECnFE,mBAAA;EACA,iBAAA;EACA,kBAAA;EACA,oBAAA;ErBqsGD;AoBjnGD;;ECvFE,kBAAA;EACA,iBAAA;EACA,kBAAA;EACA,oBAAA;ErB4sGD;AoBhnGD;EACE,gBAAA;EACA,aAAA;EpBknGD;AoB9mGD;EACE,iBAAA;EpBgnGD;AoBzmGC;;;EACE,aAAA;EpB6mGH;AuBjwGD;EACE,YAAA;ElBoLA,0CAAA;EACK,qCAAA;EACG,kCAAA;ELglGT;AuBpwGC;EACE,YAAA;EvBswGH;AuBlwGD;EACE,eAAA;EACA,oBAAA;EvBowGD;AuBlwGC;EAAY,gBAAA;EAAgB,qBAAA;EvBswG7B;AuBrwGC;EAAY,oBAAA;EvBwwGb;AuBvwGC;EAAY,0BAAA;EvB0wGb;AuBvwGD;EACE,oBAAA;EACA,WAAA;EACA,kBAAA;ElBsKA,iDAAA;EACQ,4CAAA;KAAA,yCAAA;EAOR,oCAAA;EACQ,+BAAA;KAAA,4BAAA;EAGR,0CAAA;EACQ,qCAAA;KAAA,kCAAA;EL4lGT;AwBtyGD;EACE,uBAAA;EACA,UAAA;EACA,WAAA;EACA,kBAAA;EACA,wBAAA;EACA,uBAAA;EACA,qCAAA;EACA,oCAAA;ExBwyGD;AwBpyGD;;EAEE,oBAAA;ExBsyGD;AwBlyGD;EACE,YAAA;ExBoyGD;AwBhyGD;EACE,oBAAA;EACA,WAAA;EACA,SAAA;EACA,eAAA;EACA,eAAA;EACA,aAAA;EACA,kBAAA;EACA,gBAAA;EACA,iBAAA;EACA,kBAAA;EACA,iBAAA;EACA,kBAAA;EACA,2BAAA;EACA,2BAAA;EACA,uCAAA;EACA,oBAAA;EnBuBA,qDAAA;EACQ,6CAAA;EmBtBR,sCAAA;UAAA,8BAAA;ExBmyGD;AwB9xGC;EACE,UAAA;EACA,YAAA;ExBgyGH;AwBzzGD;ECxBE,aAAA;EACA,eAAA;EACA,kBAAA;EACA,2BAAA;EzBo1GD;AwB/zGD;EAmCI,gBAAA;EACA,mBAAA;EACA,aAAA;EACA,qBAAA;EACA,yBAAA;EACA,gBAAA;EACA,qBAAA;ExB+xGH;AwBzxGC;;EAEE,uBAAA;EACA,gBAAA;EACA,2BAAA;ExB2xGH;AwBrxGC;;;EAGE,gBAAA;EACA,uBAAA;EACA,YAAA;EACA,2BAAA;ExBuxGH;AwB9wGC;;;EAGE,gBAAA;ExBgxGH;AwB5wGC;;EAEE,uBAAA;EACA,+BAAA;EACA,wBAAA;EE1GF,qEAAA;EF4GE,qBAAA;ExB8wGH;AwBzwGD;EAGI,gBAAA;ExBywGH;AwB5wGD;EAQI,YAAA;ExBuwGH;AwB/vGD;EACE,YAAA;EACA,UAAA;ExBiwGD;AwBzvGD;EACE,SAAA;EACA,aAAA;ExB2vGD;AwBvvGD;EACE,gBAAA;EACA,mBAAA;EACA,iBAAA;EACA,yBAAA;EACA,gBAAA;EACA,qBAAA;ExByvGD;AwBrvGD;EACE,iBAAA;EACA,SAAA;EACA,UAAA;EACA,WAAA;EACA,QAAA;EACA,cAAA;ExBuvGD;AwBnvGD;EACE,UAAA;EACA,YAAA;ExBqvGD;AwB7uGD;;EAII,eAAA;EACA,0BAAA;EACA,aAAA;ExB6uGH;AwBnvGD;;EAUI,WAAA;EACA,cAAA;EACA,oBAAA;ExB6uGH;AwBxtGD;EAXE;IAnEA,YAAA;IACA,UAAA;IxB0yGC;EwBxuGD;IAzDA,SAAA;IACA,aAAA;IxBoyGC;EACF;A2Bn7GD;;EAEE,oBAAA;EACA,uBAAA;EACA,wBAAA;E3Bq7GD;A2Bz7GD;;EAMI,oBAAA;EACA,aAAA;E3Bu7GH;A2Br7GG;;;;;;;;EAIE,YAAA;E3B27GL;A2Br7GD;;;;EAKI,mBAAA;E3Bs7GH;A2Bj7GD;EACE,mBAAA;E3Bm7GD;A2Bp7GD;;EAMI,aAAA;E3Bk7GH;A2Bx7GD;;;EAWI,kBAAA;E3Bk7GH;A2B96GD;EACE,kBAAA;E3Bg7GD;A2B56GD;EACE,gBAAA;E3B86GD;A2B76GC;ECjDA,+BAAA;EACG,4BAAA;E5Bi+GJ;A2B56GD;;EC9CE,8BAAA;EACG,2BAAA;E5B89GJ;A2B36GD;EACE,aAAA;E3B66GD;A2B36GD;EACE,kBAAA;E3B66GD;A2B36GD;;EClEE,+BAAA;EACG,4BAAA;E5Bi/GJ;A2B16GD;EChEE,8BAAA;EACG,2BAAA;E5B6+GJ;A2Bz6GD;;EAEE,YAAA;E3B26GD;A2B15GD;EACE,mBAAA;EACA,oBAAA;E3B45GD;A2B15GD;EACE,oBAAA;EACA,qBAAA;E3B45GD;A2Bv5GD;EtB9CE,0DAAA;EACQ,kDAAA;ELw8GT;A2Bv5GC;EtBlDA,0BAAA;EACQ,kBAAA;EL48GT;A2Bp5GD;EACE,gBAAA;E3Bs5GD;A2Bn5GD;EACE,yBAAA;EACA,wBAAA;E3Bq5GD;A2Bl5GD;EACE,yBAAA;E3Bo5GD;A2B74GD;;;EAII,gBAAA;EACA,aAAA;EACA,aAAA;EACA,iBAAA;E3B84GH;A2Br5GD;EAcM,aAAA;E3B04GL;A2Bx5GD;;;;EAsBI,kBAAA;EACA,gBAAA;E3Bw4GH;A2Bn4GC;EACE,kBAAA;E3Bq4GH;A2Bn4GC;EACE,8BAAA;ECnKF,+BAAA;EACC,8BAAA;E5ByiHF;A2Bp4GC;EACE,gCAAA;EC/KF,4BAAA;EACC,2BAAA;E5BsjHF;A2Bp4GD;EACE,kBAAA;E3Bs4GD;A2Bp4GD;;EC9KE,+BAAA;EACC,8BAAA;E5BsjHF;A2Bn4GD;EC5LE,4BAAA;EACC,2BAAA;E5BkkHF;A2B/3GD;EACE,gBAAA;EACA,aAAA;EACA,qBAAA;EACA,2BAAA;E3Bi4GD;A2Br4GD;;EAOI,aAAA;EACA,qBAAA;EACA,WAAA;E3Bk4GH;A2B34GD;EAYI,aAAA;E3Bk4GH;A2B94GD;EAgBI,YAAA;E3Bi4GH;A2Bh3GD;;;;EAKM,oBAAA;EACA,wBAAA;EACA,sBAAA;E3Bi3GL;A6B1lHD;EACE,oBAAA;EACA,gBAAA;EACA,2BAAA;E7B4lHD;A6BzlHC;EACE,aAAA;EACA,iBAAA;EACA,kBAAA;E7B2lHH;A6BpmHD;EAeI,oBAAA;EACA,YAAA;EAKA,aAAA;EAEA,aAAA;EACA,kBAAA;E7BmlHH;A6B1kHD;;;EV8BE,cAAA;EACA,oBAAA;EACA,iBAAA;EACA,wBAAA;EACA,oBAAA;EnBijHD;AmB/iHC;;;EACE,cAAA;EACA,mBAAA;EnBmjHH;AmBhjHC;;;;;;EAEE,cAAA;EnBsjHH;A6B5lHD;;;EVyBE,cAAA;EACA,mBAAA;EACA,iBAAA;EACA,kBAAA;EACA,oBAAA;EnBwkHD;AmBtkHC;;;EACE,cAAA;EACA,mBAAA;EnB0kHH;AmBvkHC;;;;;;EAEE,cAAA;EnB6kHH;A6B1mHD;;;EAGE,qBAAA;E7B4mHD;A6B1mHC;;;EACE,kBAAA;E7B8mHH;A6B1mHD;;EAEE,WAAA;EACA,qBAAA;EACA,wBAAA;E7B4mHD;A6BvmHD;EACE,mBAAA;EACA,iBAAA;EACA,qBAAA;EACA,gBAAA;EACA,gBAAA;EACA,oBAAA;EACA,2BAAA;EACA,2BAAA;EACA,oBAAA;E7BymHD;A6BtmHC;EACE,mBAAA;EACA,iBAAA;EACA,oBAAA;E7BwmHH;A6BtmHC;EACE,oBAAA;EACA,iBAAA;EACA,oBAAA;E7BwmHH;A6B5nHD;;EA0BI,eAAA;E7BsmHH;A6BjmHD;;;;;;;EDhGE,+BAAA;EACG,4BAAA;E5B0sHJ;A6BlmHD;EACE,iBAAA;E7BomHD;A6BlmHD;;;;;;;EDpGE,8BAAA;EACG,2BAAA;E5B+sHJ;A6BnmHD;EACE,gBAAA;E7BqmHD;A6BhmHD;EACE,oBAAA;EAGA,cAAA;EACA,qBAAA;E7BgmHD;A6BrmHD;EAUI,oBAAA;E7B8lHH;A6BxmHD;EAYM,mBAAA;E7B+lHL;A6B5lHG;;;EAGE,YAAA;E7B8lHL;A6BzlHC;;EAGI,oBAAA;E7B0lHL;A6BvlHC;;EAGI,mBAAA;E7BwlHL;A8BlvHD;EACE,kBAAA;EACA,iBAAA;EACA,kBAAA;E9BovHD;A8BvvHD;EAOI,oBAAA;EACA,gBAAA;E9BmvHH;A8B3vHD;EAWM,oBAAA;EACA,gBAAA;EACA,oBAAA;E9BmvHL;A8BlvHK;;EAEE,uBAAA;EACA,2BAAA;E9BovHP;A8B/uHG;EACE,gBAAA;E9BivHL;A8B/uHK;;EAEE,gBAAA;EACA,uBAAA;EACA,+BAAA;EACA,qBAAA;E9BivHP;A8B1uHG;;;EAGE,2BAAA;EACA,uBAAA;E9B4uHL;A8BrxHD;ELHE,aAAA;EACA,eAAA;EACA,kBAAA;EACA,2BAAA;EzB2xHD;A8B3xHD;EA0DI,iBAAA;E9BouHH;A8B3tHD;EACE,kCAAA;E9B6tHD;A8B9tHD;EAGI,aAAA;EAEA,qBAAA;E9B6tHH;A8BluHD;EASM,mBAAA;EACA,yBAAA;EACA,+BAAA;EACA,4BAAA;E9B4tHL;A8B3tHK;EACE,uCAAA;E9B6tHP;A8BvtHK;;;EAGE,gBAAA;EACA,2BAAA;EACA,2BAAA;EACA,kCAAA;EACA,iBAAA;E9BytHP;A8BptHC;EAqDA,aAAA;EA8BA,kBAAA;E9BqoHD;A8BxtHC;EAwDE,aAAA;E9BmqHH;A8B3tHC;EA0DI,oBAAA;EACA,oBAAA;E9BoqHL;A8B/tHC;EAgEE,WAAA;EACA,YAAA;E9BkqHH;A8BtpHD;EAAA;IAPM,qBAAA;IACA,WAAA;I9BiqHH;E8B3pHH;IAJQ,kBAAA;I9BkqHL;EACF;A8B5uHC;EAuFE,iBAAA;EACA,oBAAA;E9BwpHH;A8BhvHC;;;EA8FE,2BAAA;E9BupHH;A8BzoHD;EAAA;IATM,kCAAA;IACA,4BAAA;I9BspHH;E8B9oHH;;;IAHM,8BAAA;I9BspHH;EACF;A8BvvHD;EAEI,aAAA;E9BwvHH;A8B1vHD;EAMM,oBAAA;E9BuvHL;A8B7vHD;EASM,kBAAA;E9BuvHL;A8BlvHK;;;EAGE,gBAAA;EACA,2BAAA;E9BovHP;A8B5uHD;EAEI,aAAA;E9B6uHH;A8B/uHD;EAIM,iBAAA;EACA,gBAAA;E9B8uHL;A8BluHD;EACE,aAAA;E9BouHD;A8BruHD;EAII,aAAA;E9BouHH;A8BxuHD;EAMM,oBAAA;EACA,oBAAA;E9BquHL;A8B5uHD;EAYI,WAAA;EACA,YAAA;E9BmuHH;A8BvtHD;EAAA;IAPM,qBAAA;IACA,WAAA;I9BkuHH;E8B5tHH;IAJQ,kBAAA;I9BmuHL;EACF;A8B3tHD;EACE,kBAAA;E9B6tHD;A8B9tHD;EAKI,iBAAA;EACA,oBAAA;E9B4tHH;A8BluHD;;;EAYI,2BAAA;E9B2tHH;A8B7sHD;EAAA;IATM,kCAAA;IACA,4BAAA;I9B0tHH;E8BltHH;;;IAHM,8BAAA;I9B0tHH;EACF;A8BjtHD;EAEI,eAAA;EACA,oBAAA;E9BktHH;A8BrtHD;EAMI,gBAAA;EACA,qBAAA;E9BktHH;A8BzsHD;EAEE,kBAAA;EF7OA,4BAAA;EACC,2BAAA;E5Bw7HF;A+Bl7HD;EACE,oBAAA;EACA,kBAAA;EACA,qBAAA;EACA,+BAAA;E/Bo7HD;A+B56HD;EAAA;IAFI,oBAAA;I/Bk7HD;EACF;A+Bn6HD;EAAA;IAFI,aAAA;I/By6HD;EACF;A+B35HD;EACE,qBAAA;EACA,qBAAA;EACA,oBAAA;EACA,mCAAA;EACA,4DAAA;UAAA,oDAAA;EAEA,mCAAA;E/B45HD;A+B15HC;EACE,kBAAA;E/B45HH;A+B/3HD;EAAA;IAzBI,aAAA;IACA,eAAA;IACA,0BAAA;YAAA,kBAAA;I/B45HD;E+B15HC;IACE,2BAAA;IACA,gCAAA;IACA,yBAAA;IACA,mBAAA;IACA,8BAAA;I/B45HH;E+Bz5HC;IACE,qBAAA;I/B25HH;E+Bt5HC;;;IAGE,iBAAA;IACA,kBAAA;I/Bw5HH;EACF;A+Bp5HD;;EAGI,mBAAA;E/Bq5HH;A+Bh5HC;EAAA;;IAFI,mBAAA;I/Bu5HH;EACF;A+B94HD;;;;EAII,qBAAA;EACA,oBAAA;E/Bg5HH;A+B14HC;EAAA;;;;IAHI,iBAAA;IACA,gBAAA;I/Bo5HH;EACF;A+Bx4HD;EACE,eAAA;EACA,uBAAA;E/B04HD;A+Br4HD;EAAA;IAFI,kBAAA;I/B24HD;EACF;A+Bv4HD;;EAEE,iBAAA;EACA,UAAA;EACA,SAAA;EACA,eAAA;E/By4HD;A+Bn4HD;EAAA;;IAFI,kBAAA;I/B04HD;EACF;A+Bx4HD;EACE,QAAA;EACA,uBAAA;E/B04HD;A+Bx4HD;EACE,WAAA;EACA,kBAAA;EACA,uBAAA;E/B04HD;A+Bp4HD;EACE,aAAA;EACA,oBAAA;EACA,iBAAA;EACA,mBAAA;EACA,cAAA;E/Bs4HD;A+Bp4HC;;EAEE,uBAAA;E/Bs4HH;A+B/4HD;EAaI,gBAAA;E/Bq4HH;A+B53HD;EALI;;IAEE,oBAAA;I/Bo4HH;EACF;A+B13HD;EACE,oBAAA;EACA,cAAA;EACA,oBAAA;EACA,mBAAA;EC/LA,iBAAA;EACA,oBAAA;EDgMA,+BAAA;EACA,wBAAA;EACA,+BAAA;EACA,oBAAA;E/B63HD;A+Bz3HC;EACE,YAAA;E/B23HH;A+Bz4HD;EAmBI,gBAAA;EACA,aAAA;EACA,aAAA;EACA,oBAAA;E/By3HH;A+B/4HD;EAyBI,iBAAA;E/By3HH;A+Bn3HD;EAAA;IAFI,eAAA;I/By3HD;EACF;A+Bh3HD;EACE,qBAAA;E/Bk3HD;A+Bn3HD;EAII,mBAAA;EACA,sBAAA;EACA,mBAAA;E/Bk3HH;A+Bt1HC;EAAA;IAtBI,kBAAA;IACA,aAAA;IACA,aAAA;IACA,eAAA;IACA,+BAAA;IACA,WAAA;IACA,0BAAA;YAAA,kBAAA;I/Bg3HH;E+Bh2HD;;IAbM,4BAAA;I/Bi3HL;E+Bp2HD;IAVM,mBAAA;I/Bi3HL;E+Bh3HK;;IAEE,wBAAA;I/Bk3HP;EACF;A+Bh2HD;EAAA;IAXI,aAAA;IACA,WAAA;I/B+2HD;E+Br2HH;IAPM,aAAA;I/B+2HH;E+Bx2HH;IALQ,mBAAA;IACA,sBAAA;I/Bg3HL;EACF;A+Br2HD;EACE,oBAAA;EACA,qBAAA;EACA,oBAAA;EACA,mCAAA;EACA,sCAAA;E1B/NA,8FAAA;EACQ,sFAAA;E2B/DR,iBAAA;EACA,oBAAA;EhCuoID;AkB9pHD;EAAA;IA9DM,uBAAA;IACA,kBAAA;IACA,wBAAA;IlBguHH;EkBpqHH;IAvDM,uBAAA;IACA,aAAA;IACA,wBAAA;IlB8tHH;EkBzqHH;IAhDM,uBAAA;IlB4tHH;EkB5qHH;IA5CM,uBAAA;IACA,wBAAA;IlB2tHH;EkBhrHH;;;IAtCQ,aAAA;IlB2tHL;EkBrrHH;IAhCM,aAAA;IlBwtHH;EkBxrHH;IA5BM,kBAAA;IACA,wBAAA;IlButHH;EkB5rHH;;IApBM,uBAAA;IACA,eAAA;IACA,kBAAA;IACA,wBAAA;IlBotHH;EkBnsHH;;IAdQ,iBAAA;IlBqtHL;EkBvsHH;;IATM,oBAAA;IACA,gBAAA;IlBotHH;EkB5sHH;IAHM,QAAA;IlBktHH;EACF;A+B94HC;EAAA;IANI,oBAAA;I/Bw5HH;E+Bt5HG;IACE,kBAAA;I/Bw5HL;EACF;A+Bv4HD;EAAA;IARI,aAAA;IACA,WAAA;IACA,gBAAA;IACA,iBAAA;IACA,gBAAA;IACA,mBAAA;I1B1PF,0BAAA;IACQ,kBAAA;IL8oIP;EACF;A+B74HD;EACE,eAAA;EHrUA,4BAAA;EACC,2BAAA;E5BqtIF;A+B74HD;EACE,kBAAA;EH1UA,8BAAA;EACC,6BAAA;EAOD,+BAAA;EACC,8BAAA;E5BotIF;A+Bz4HD;ECjVE,iBAAA;EACA,oBAAA;EhC6tID;A+B14HC;ECpVA,kBAAA;EACA,qBAAA;EhCiuID;A+B34HC;ECvVA,kBAAA;EACA,qBAAA;EhCquID;A+Br4HD;ECjWE,kBAAA;EACA,qBAAA;EhCyuID;A+Bj4HD;EAAA;IAJI,aAAA;IACA,mBAAA;IACA,oBAAA;I/By4HD;EACF;A+B52HD;EAhBE;IEzWA,wBAAA;IjCyuIC;E+B/3HD;IE7WA,yBAAA;IF+WE,qBAAA;I/Bi4HD;E+Bn4HD;IAKI,iBAAA;I/Bi4HH;EACF;A+Bx3HD;EACE,2BAAA;EACA,uBAAA;E/B03HD;A+B53HD;EAKI,gBAAA;E/B03HH;A+Bz3HG;;EAEE,gBAAA;EACA,+BAAA;E/B23HL;A+Bp4HD;EAcI,gBAAA;E/By3HH;A+Bv4HD;EAmBM,gBAAA;E/Bu3HL;A+Br3HK;;EAEE,gBAAA;EACA,+BAAA;E/Bu3HP;A+Bn3HK;;;EAGE,gBAAA;EACA,2BAAA;E/Bq3HP;A+Bj3HK;;;EAGE,gBAAA;EACA,+BAAA;E/Bm3HP;A+B35HD;EA8CI,uBAAA;E/Bg3HH;A+B/2HG;;EAEE,2BAAA;E/Bi3HL;A+Bl6HD;EAoDM,2BAAA;E/Bi3HL;A+Br6HD;;EA0DI,uBAAA;E/B+2HH;A+Bx2HK;;;EAGE,2BAAA;EACA,gBAAA;E/B02HP;A+Bz0HC;EAAA;IAzBQ,gBAAA;I/Bs2HP;E+Br2HO;;IAEE,gBAAA;IACA,+BAAA;I/Bu2HT;E+Bn2HO;;;IAGE,gBAAA;IACA,2BAAA;I/Bq2HT;E+Bj2HO;;;IAGE,gBAAA;IACA,+BAAA;I/Bm2HT;EACF;A+Br8HD;EA8GI,gBAAA;E/B01HH;A+Bz1HG;EACE,gBAAA;E/B21HL;A+B38HD;EAqHI,gBAAA;E/By1HH;A+Bx1HG;;EAEE,gBAAA;E/B01HL;A+Bt1HK;;;;EAEE,gBAAA;E/B01HP;A+Bl1HD;EACE,2BAAA;EACA,uBAAA;E/Bo1HD;A+Bt1HD;EAKI,gBAAA;E/Bo1HH;A+Bn1HG;;EAEE,gBAAA;EACA,+BAAA;E/Bq1HL;A+B91HD;EAcI,gBAAA;E/Bm1HH;A+Bj2HD;EAmBM,gBAAA;E/Bi1HL;A+B/0HK;;EAEE,gBAAA;EACA,+BAAA;E/Bi1HP;A+B70HK;;;EAGE,gBAAA;EACA,2BAAA;E/B+0HP;A+B30HK;;;EAGE,gBAAA;EACA,+BAAA;E/B60HP;A+Br3HD;EA+CI,uBAAA;E/By0HH;A+Bx0HG;;EAEE,2BAAA;E/B00HL;A+B53HD;EAqDM,2BAAA;E/B00HL;A+B/3HD;;EA2DI,uBAAA;E/Bw0HH;A+Bl0HK;;;EAGE,2BAAA;EACA,gBAAA;E/Bo0HP;A+B7xHC;EAAA;IA/BQ,uBAAA;I/Bg0HP;E+BjyHD;IA5BQ,2BAAA;I/Bg0HP;E+BpyHD;IAzBQ,gBAAA;I/Bg0HP;E+B/zHO;;IAEE,gBAAA;IACA,+BAAA;I/Bi0HT;E+B7zHO;;;IAGE,gBAAA;IACA,2BAAA;I/B+zHT;E+B3zHO;;;IAGE,gBAAA;IACA,+BAAA;I/B6zHT;EACF;A+Br6HD;EA+GI,gBAAA;E/ByzHH;A+BxzHG;EACE,gBAAA;E/B0zHL;A+B36HD;EAsHI,gBAAA;E/BwzHH;A+BvzHG;;EAEE,gBAAA;E/ByzHL;A+BrzHK;;;;EAEE,gBAAA;E/ByzHP;AkCp8ID;EACE,mBAAA;EACA,qBAAA;EACA,kBAAA;EACA,2BAAA;EACA,oBAAA;ElCs8ID;AkC38ID;EAQI,uBAAA;ElCs8IH;AkC98ID;EAWM,mBAAA;EACA,gBAAA;EACA,gBAAA;ElCs8IL;AkCn9ID;EAkBI,gBAAA;ElCo8IH;AmCx9ID;EACE,uBAAA;EACA,iBAAA;EACA,gBAAA;EACA,oBAAA;EnC09ID;AmC99ID;EAOI,iBAAA;EnC09IH;AmCj+ID;;EAUM,oBAAA;EACA,aAAA;EACA,mBAAA;EACA,yBAAA;EACA,uBAAA;EACA,gBAAA;EACA,2BAAA;EACA,2BAAA;EACA,mBAAA;EnC29IL;AmCz9IG;;EAGI,gBAAA;EPXN,gCAAA;EACG,6BAAA;E5Bs+IJ;AmCx9IG;;EPvBF,iCAAA;EACG,8BAAA;E5Bm/IJ;AmCn9IG;;;;EAEE,gBAAA;EACA,2BAAA;EACA,uBAAA;EnCu9IL;AmCj9IG;;;;;;EAGE,YAAA;EACA,gBAAA;EACA,2BAAA;EACA,uBAAA;EACA,iBAAA;EnCs9IL;AmC5gJD;;;;;;EAiEM,gBAAA;EACA,2BAAA;EACA,uBAAA;EACA,qBAAA;EnCm9IL;AmC18ID;;EC1EM,oBAAA;EACA,iBAAA;EpCwhJL;AoCthJG;;ERMF,gCAAA;EACG,6BAAA;E5BohJJ;AoCrhJG;;ERRF,iCAAA;EACG,8BAAA;E5BiiJJ;AmCp9ID;;EC/EM,mBAAA;EACA,iBAAA;EpCuiJL;AoCriJG;;ERMF,gCAAA;EACG,6BAAA;E5BmiJJ;AoCpiJG;;ERRF,iCAAA;EACG,8BAAA;E5BgjJJ;AqCnjJD;EACE,iBAAA;EACA,gBAAA;EACA,kBAAA;EACA,oBAAA;ErCqjJD;AqCzjJD;EAOI,iBAAA;ErCqjJH;AqC5jJD;;EAUM,uBAAA;EACA,mBAAA;EACA,2BAAA;EACA,2BAAA;EACA,qBAAA;ErCsjJL;AqCpkJD;;EAmBM,uBAAA;EACA,2BAAA;ErCqjJL;AqCzkJD;;EA2BM,cAAA;ErCkjJL;AqC7kJD;;EAkCM,aAAA;ErC+iJL;AqCjlJD;;;;EA2CM,gBAAA;EACA,2BAAA;EACA,qBAAA;ErC4iJL;AsC1lJD;EACE,iBAAA;EACA,yBAAA;EACA,gBAAA;EACA,mBAAA;EACA,gBAAA;EACA,gBAAA;EACA,oBAAA;EACA,qBAAA;EACA,0BAAA;EACA,sBAAA;EtC4lJD;AsCxlJG;;EAEE,gBAAA;EACA,uBAAA;EACA,iBAAA;EtC0lJL;AsCrlJC;EACE,eAAA;EtCulJH;AsCnlJC;EACE,oBAAA;EACA,WAAA;EtCqlJH;AsC9kJD;ECtCE,2BAAA;EvCunJD;AuCpnJG;;EAEE,2BAAA;EvCsnJL;AsCjlJD;EC1CE,2BAAA;EvC8nJD;AuC3nJG;;EAEE,2BAAA;EvC6nJL;AsCplJD;EC9CE,2BAAA;EvCqoJD;AuCloJG;;EAEE,2BAAA;EvCooJL;AsCvlJD;EClDE,2BAAA;EvC4oJD;AuCzoJG;;EAEE,2BAAA;EvC2oJL;AsC1lJD;ECtDE,2BAAA;EvCmpJD;AuChpJG;;EAEE,2BAAA;EvCkpJL;AsC7lJD;EC1DE,2BAAA;EvC0pJD;AuCvpJG;;EAEE,2BAAA;EvCypJL;AwC3pJD;EACE,uBAAA;EACA,iBAAA;EACA,kBAAA;EACA,iBAAA;EACA,mBAAA;EACA,gBAAA;EACA,gBAAA;EACA,0BAAA;EACA,qBAAA;EACA,oBAAA;EACA,2BAAA;EACA,qBAAA;ExC6pJD;AwC1pJC;EACE,eAAA;ExC4pJH;AwCxpJC;EACE,oBAAA;EACA,WAAA;ExC0pJH;AwCvpJC;EACE,QAAA;EACA,kBAAA;ExCypJH;AwCppJG;;EAEE,gBAAA;EACA,uBAAA;EACA,iBAAA;ExCspJL;AwCjpJC;;EAEE,gBAAA;EACA,2BAAA;ExCmpJH;AwChpJC;EACE,cAAA;ExCkpJH;AwC/oJC;EACE,mBAAA;ExCipJH;AwC9oJC;EACE,kBAAA;ExCgpJH;AyCzsJD;EACE,oBAAA;EACA,qBAAA;EACA,gBAAA;EACA,2BAAA;EzC2sJD;AyC/sJD;;EAQI,gBAAA;EzC2sJH;AyCntJD;EAYI,qBAAA;EACA,iBAAA;EACA,kBAAA;EzC0sJH;AyCxtJD;EAkBI,2BAAA;EzCysJH;AyCtsJC;;EAEE,oBAAA;EzCwsJH;AyC/tJD;EA2BI,iBAAA;EzCusJH;AyCtrJD;EAAA;IAbI,iBAAA;IzCusJD;EyCrsJC;;IAEE,oBAAA;IACA,qBAAA;IzCusJH;EyC/rJH;;IAHM,iBAAA;IzCssJH;EACF;A0C/uJD;EACE,gBAAA;EACA,cAAA;EACA,qBAAA;EACA,yBAAA;EACA,2BAAA;EACA,2BAAA;EACA,oBAAA;ErCiLA,6CAAA;EACK,wCAAA;EACG,qCAAA;ELikJT;A0C3vJD;;EAaI,mBAAA;EACA,oBAAA;E1CkvJH;A0C9uJC;;;EAGE,uBAAA;E1CgvJH;A0CrwJD;EA0BI,cAAA;EACA,gBAAA;E1C8uJH;A2CvwJD;EACE,eAAA;EACA,qBAAA;EACA,+BAAA;EACA,oBAAA;E3CywJD;A2C7wJD;EAQI,eAAA;EAEA,gBAAA;E3CuwJH;A2CjxJD;EAeI,mBAAA;E3CqwJH;A2CpxJD;;EAqBI,kBAAA;E3CmwJH;A2CxxJD;EAyBI,iBAAA;E3CkwJH;A2C1vJD;;EAEE,qBAAA;E3C4vJD;A2C9vJD;;EAMI,oBAAA;EACA,WAAA;EACA,cAAA;EACA,gBAAA;E3C4vJH;A2CpvJD;ECvDE,2BAAA;EACA,uBAAA;EACA,gBAAA;E5C8yJD;A2CzvJD;EClDI,2BAAA;E5C8yJH;A2C5vJD;EC/CI,gBAAA;E5C8yJH;A2C3vJD;EC3DE,2BAAA;EACA,uBAAA;EACA,gBAAA;E5CyzJD;A2ChwJD;ECtDI,2BAAA;E5CyzJH;A2CnwJD;ECnDI,gBAAA;E5CyzJH;A2ClwJD;EC/DE,2BAAA;EACA,uBAAA;EACA,gBAAA;E5Co0JD;A2CvwJD;EC1DI,2BAAA;E5Co0JH;A2C1wJD;ECvDI,gBAAA;E5Co0JH;A2CzwJD;ECnEE,2BAAA;EACA,uBAAA;EACA,gBAAA;E5C+0JD;A2C9wJD;EC9DI,2BAAA;E5C+0JH;A2CjxJD;EC3DI,gBAAA;E5C+0JH;A6Cj1JD;EACE;IAAQ,6BAAA;I7Co1JP;E6Cn1JD;IAAQ,0BAAA;I7Cs1JP;EACF;A6Cn1JD;EACE;IAAQ,6BAAA;I7Cs1JP;E6Cr1JD;IAAQ,0BAAA;I7Cw1JP;EACF;A6C31JD;EACE;IAAQ,6BAAA;I7Cs1JP;E6Cr1JD;IAAQ,0BAAA;I7Cw1JP;EACF;A6Cj1JD;EACE,kBAAA;EACA,cAAA;EACA,qBAAA;EACA,2BAAA;EACA,oBAAA;ExCsCA,wDAAA;EACQ,gDAAA;EL8yJT;A6Ch1JD;EACE,aAAA;EACA,WAAA;EACA,cAAA;EACA,iBAAA;EACA,mBAAA;EACA,gBAAA;EACA,oBAAA;EACA,2BAAA;ExCyBA,wDAAA;EACQ,gDAAA;EAyHR,qCAAA;EACK,gCAAA;EACG,6BAAA;ELksJT;A6C70JD;;ECCI,+MAAA;EACA,0MAAA;EACA,uMAAA;EDAF,oCAAA;UAAA,4BAAA;E7Ci1JD;A6C10JD;;ExC5CE,4DAAA;EACK,uDAAA;EACG,oDAAA;EL03JT;A6Cv0JD;EErEE,2BAAA;E/C+4JD;A+C54JC;EDgDE,+MAAA;EACA,0MAAA;EACA,uMAAA;E9C+1JH;A6C30JD;EEzEE,2BAAA;E/Cu5JD;A+Cp5JC;EDgDE,+MAAA;EACA,0MAAA;EACA,uMAAA;E9Cu2JH;A6C/0JD;EE7EE,2BAAA;E/C+5JD;A+C55JC;EDgDE,+MAAA;EACA,0MAAA;EACA,uMAAA;E9C+2JH;A6Cn1JD;EEjFE,2BAAA;E/Cu6JD;A+Cp6JC;EDgDE,+MAAA;EACA,0MAAA;EACA,uMAAA;E9Cu3JH;AgD/6JD;EAEE,kBAAA;EhDg7JD;AgD96JC;EACE,eAAA;EhDg7JH;AgD56JD;;EAEE,SAAA;EACA,kBAAA;EhD86JD;AgD36JD;EACE,gBAAA;EhD66JD;AgD16JD;EACE,gBAAA;EhD46JD;AgDz6JD;;EAEE,oBAAA;EhD26JD;AgDx6JD;;EAEE,qBAAA;EhD06JD;AgDv6JD;;;EAGE,qBAAA;EACA,qBAAA;EhDy6JD;AgDt6JD;EACE,wBAAA;EhDw6JD;AgDr6JD;EACE,wBAAA;EhDu6JD;AgDn6JD;EACE,eAAA;EACA,oBAAA;EhDq6JD;AgD/5JD;EACE,iBAAA;EACA,kBAAA;EhDi6JD;AiDn9JD;EAEE,qBAAA;EACA,iBAAA;EjDo9JD;AiD58JD;EACE,oBAAA;EACA,gBAAA;EACA,oBAAA;EAEA,qBAAA;EACA,2BAAA;EACA,2BAAA;EjD68JD;AiD18JC;ErB3BA,8BAAA;EACC,6BAAA;E5Bw+JF;AiD38JC;EACE,kBAAA;ErBvBF,iCAAA;EACC,gCAAA;E5Bq+JF;AiDp8JD;EACE,gBAAA;EjDs8JD;AiDv8JD;EAII,gBAAA;EjDs8JH;AiDl8JC;;EAEE,uBAAA;EACA,gBAAA;EACA,2BAAA;EjDo8JH;AiD97JC;;;EAGE,2BAAA;EACA,gBAAA;EACA,qBAAA;EjDg8JH;AiDr8JC;;;EASI,gBAAA;EjDi8JL;AiD18JC;;;EAYI,gBAAA;EjDm8JL;AiD97JC;;;EAGE,YAAA;EACA,gBAAA;EACA,2BAAA;EACA,uBAAA;EjDg8JH;AiDt8JC;;;;;;;;;EAYI,gBAAA;EjDq8JL;AiDj9JC;;;EAeI,gBAAA;EjDu8JL;AkDniKC;EACE,gBAAA;EACA,2BAAA;ElDqiKH;AkDniKG;EACE,gBAAA;ElDqiKL;AkDtiKG;EAII,gBAAA;ElDqiKP;AkDliKK;;EAEE,gBAAA;EACA,2BAAA;ElDoiKP;AkDliKK;;;EAGE,aAAA;EACA,2BAAA;EACA,uBAAA;ElDoiKP;AkDzjKC;EACE,gBAAA;EACA,2BAAA;ElD2jKH;AkDzjKG;EACE,gBAAA;ElD2jKL;AkD5jKG;EAII,gBAAA;ElD2jKP;AkDxjKK;;EAEE,gBAAA;EACA,2BAAA;ElD0jKP;AkDxjKK;;;EAGE,aAAA;EACA,2BAAA;EACA,uBAAA;ElD0jKP;AkD/kKC;EACE,gBAAA;EACA,2BAAA;ElDilKH;AkD/kKG;EACE,gBAAA;ElDilKL;AkDllKG;EAII,gBAAA;ElDilKP;AkD9kKK;;EAEE,gBAAA;EACA,2BAAA;ElDglKP;AkD9kKK;;;EAGE,aAAA;EACA,2BAAA;EACA,uBAAA;ElDglKP;AkDrmKC;EACE,gBAAA;EACA,2BAAA;ElDumKH;AkDrmKG;EACE,gBAAA;ElDumKL;AkDxmKG;EAII,gBAAA;ElDumKP;AkDpmKK;;EAEE,gBAAA;EACA,2BAAA;ElDsmKP;AkDpmKK;;;EAGE,aAAA;EACA,2BAAA;EACA,uBAAA;ElDsmKP;AiD1gKD;EACE,eAAA;EACA,oBAAA;EjD4gKD;AiD1gKD;EACE,kBAAA;EACA,kBAAA;EjD4gKD;AmDhoKD;EACE,qBAAA;EACA,2BAAA;EACA,+BAAA;EACA,oBAAA;E9C0DA,mDAAA;EACQ,2CAAA;ELykKT;AmD/nKD;EACE,eAAA;EnDioKD;AmD5nKD;EACE,oBAAA;EACA,sCAAA;EvBpBA,8BAAA;EACC,6BAAA;E5BmpKF;AmDloKD;EAMI,gBAAA;EnD+nKH;AmD1nKD;EACE,eAAA;EACA,kBAAA;EACA,iBAAA;EACA,gBAAA;EnD4nKD;AmDhoKD;;;;;EAWI,gBAAA;EnD4nKH;AmDvnKD;EACE,oBAAA;EACA,2BAAA;EACA,+BAAA;EvBxCA,iCAAA;EACC,gCAAA;E5BkqKF;AmDjnKD;;EAGI,kBAAA;EnDknKH;AmDrnKD;;EAMM,qBAAA;EACA,kBAAA;EnDmnKL;AmD/mKG;;EAEI,eAAA;EvBvEN,8BAAA;EACC,6BAAA;E5ByrKF;AmD9mKG;;EAEI,kBAAA;EvBtEN,iCAAA;EACC,gCAAA;E5BurKF;AmD3mKD;EAEI,qBAAA;EnD4mKH;AmDzmKD;EACE,qBAAA;EnD2mKD;AmDnmKD;;;EAII,kBAAA;EnDomKH;AmDxmKD;;;EAOM,oBAAA;EACA,qBAAA;EnDsmKL;AmD9mKD;;EvBnGE,8BAAA;EACC,6BAAA;E5BqtKF;AmDnnKD;;;;EAmBQ,6BAAA;EACA,8BAAA;EnDsmKP;AmD1nKD;;;;;;;;EAwBU,6BAAA;EnD4mKT;AmDpoKD;;;;;;;;EA4BU,8BAAA;EnDknKT;AmD9oKD;;EvB3FE,iCAAA;EACC,gCAAA;E5B6uKF;AmDnpKD;;;;EAyCQ,gCAAA;EACA,iCAAA;EnDgnKP;AmD1pKD;;;;;;;;EA8CU,gCAAA;EnDsnKT;AmDpqKD;;;;;;;;EAkDU,iCAAA;EnD4nKT;AmD9qKD;;;;EA2DI,+BAAA;EnDynKH;AmDprKD;;EA+DI,eAAA;EnDynKH;AmDxrKD;;EAmEI,WAAA;EnDynKH;AmD5rKD;;;;;;;;;;;;EA0EU,gBAAA;EnDgoKT;AmD1sKD;;;;;;;;;;;;EA8EU,iBAAA;EnD0oKT;AmDxtKD;;;;;;;;EAuFU,kBAAA;EnD2oKT;AmDluKD;;;;;;;;EAgGU,kBAAA;EnD4oKT;AmD5uKD;EAsGI,WAAA;EACA,kBAAA;EnDyoKH;AmD/nKD;EACE,qBAAA;EnDioKD;AmDloKD;EAKI,kBAAA;EACA,oBAAA;EnDgoKH;AmDtoKD;EASM,iBAAA;EnDgoKL;AmDzoKD;EAcI,kBAAA;EnD8nKH;AmD5oKD;;EAkBM,+BAAA;EnD8nKL;AmDhpKD;EAuBI,eAAA;EnD4nKH;AmDnpKD;EAyBM,kCAAA;EnD6nKL;AmDtnKD;ECpPE,uBAAA;EpD62KD;AoD32KC;EACE,gBAAA;EACA,2BAAA;EACA,uBAAA;EpD62KH;AoDh3KC;EAMI,2BAAA;EpD62KL;AoDn3KC;EASI,gBAAA;EACA,2BAAA;EpD62KL;AoD12KC;EAEI,8BAAA;EpD22KL;AmDroKD;ECvPE,uBAAA;EpD+3KD;AoD73KC;EACE,gBAAA;EACA,2BAAA;EACA,uBAAA;EpD+3KH;AoDl4KC;EAMI,2BAAA;EpD+3KL;AoDr4KC;EASI,gBAAA;EACA,2BAAA;EpD+3KL;AoD53KC;EAEI,8BAAA;EpD63KL;AmDppKD;EC1PE,uBAAA;EpDi5KD;AoD/4KC;EACE,gBAAA;EACA,2BAAA;EACA,uBAAA;EpDi5KH;AoDp5KC;EAMI,2BAAA;EpDi5KL;AoDv5KC;EASI,gBAAA;EACA,2BAAA;EpDi5KL;AoD94KC;EAEI,8BAAA;EpD+4KL;AmDnqKD;EC7PE,uBAAA;EpDm6KD;AoDj6KC;EACE,gBAAA;EACA,2BAAA;EACA,uBAAA;EpDm6KH;AoDt6KC;EAMI,2BAAA;EpDm6KL;AoDz6KC;EASI,gBAAA;EACA,2BAAA;EpDm6KL;AoDh6KC;EAEI,8BAAA;EpDi6KL;AmDlrKD;EChQE,uBAAA;EpDq7KD;AoDn7KC;EACE,gBAAA;EACA,2BAAA;EACA,uBAAA;EpDq7KH;AoDx7KC;EAMI,2BAAA;EpDq7KL;AoD37KC;EASI,gBAAA;EACA,2BAAA;EpDq7KL;AoDl7KC;EAEI,8BAAA;EpDm7KL;AmDjsKD;ECnQE,uBAAA;EpDu8KD;AoDr8KC;EACE,gBAAA;EACA,2BAAA;EACA,uBAAA;EpDu8KH;AoD18KC;EAMI,2BAAA;EpDu8KL;AoD78KC;EASI,gBAAA;EACA,2BAAA;EpDu8KL;AoDp8KC;EAEI,8BAAA;EpDq8KL;AqDr9KD;EACE,oBAAA;EACA,gBAAA;EACA,WAAA;EACA,YAAA;EACA,kBAAA;ErDu9KD;AqD59KD;;;;;EAYI,oBAAA;EACA,QAAA;EACA,SAAA;EACA,WAAA;EACA,cAAA;EACA,aAAA;EACA,WAAA;ErDu9KH;AqDn9KC;EACE,wBAAA;ErDq9KH;AqDj9KC;EACE,qBAAA;ErDm9KH;AsD7+KD;EACE,kBAAA;EACA,eAAA;EACA,qBAAA;EACA,2BAAA;EACA,2BAAA;EACA,oBAAA;EjDwDA,yDAAA;EACQ,iDAAA;ELw7KT;AsDv/KD;EASI,oBAAA;EACA,mCAAA;EtDi/KH;AsD5+KD;EACE,eAAA;EACA,oBAAA;EtD8+KD;AsD5+KD;EACE,cAAA;EACA,oBAAA;EtD8+KD;AuDpgLD;EACE,cAAA;EACA,iBAAA;EACA,mBAAA;EACA,gBAAA;EACA,gBAAA;EACA,8BAAA;EjCRA,cAAA;EAGA,2BAAA;EtB6gLD;AuDrgLC;;EAEE,gBAAA;EACA,uBAAA;EACA,iBAAA;EjCfF,cAAA;EAGA,2BAAA;EtBqhLD;AuDjgLC;EACE,YAAA;EACA,iBAAA;EACA,yBAAA;EACA,WAAA;EACA,0BAAA;EvDmgLH;AwDxhLD;EACE,kBAAA;ExD0hLD;AwDthLD;EACE,eAAA;EACA,kBAAA;EACA,iBAAA;EACA,QAAA;EACA,UAAA;EACA,WAAA;EACA,SAAA;EACA,eAAA;EACA,mCAAA;EAIA,YAAA;ExDqhLD;AwDlhLC;EnD+GA,uCAAA;EACI,mCAAA;EACC,kCAAA;EACG,+BAAA;EAkER,qDAAA;EAEK,2CAAA;EACG,qCAAA;ELq2KT;AwDxhLC;EnD2GA,oCAAA;EACI,gCAAA;EACC,+BAAA;EACG,4BAAA;ELg7KT;AwD5hLD;EACE,oBAAA;EACA,kBAAA;ExD8hLD;AwD1hLD;EACE,oBAAA;EACA,aAAA;EACA,cAAA;ExD4hLD;AwDxhLD;EACE,oBAAA;EACA,2BAAA;EACA,2BAAA;EACA,sCAAA;EACA,oBAAA;EnDaA,kDAAA;EACQ,0CAAA;EmDZR,sCAAA;UAAA,8BAAA;EAEA,YAAA;ExD0hLD;AwDthLD;EACE,oBAAA;EACA,QAAA;EACA,UAAA;EACA,SAAA;EACA,2BAAA;ExDwhLD;AwDthLC;ElCnEA,YAAA;EAGA,0BAAA;EtB0lLD;AwDzhLC;ElCpEA,cAAA;EAGA,2BAAA;EtB8lLD;AwDxhLD;EACE,eAAA;EACA,kCAAA;EACA,2BAAA;ExD0hLD;AwDvhLD;EACE,kBAAA;ExDyhLD;AwDrhLD;EACE,WAAA;EACA,yBAAA;ExDuhLD;AwDlhLD;EACE,oBAAA;EACA,eAAA;ExDohLD;AwDhhLD;EACE,eAAA;EACA,mBAAA;EACA,+BAAA;ExDkhLD;AwDrhLD;EAQI,kBAAA;EACA,kBAAA;ExDghLH;AwDzhLD;EAaI,mBAAA;ExD+gLH;AwD5hLD;EAiBI,gBAAA;ExD8gLH;AwDzgLD;EACE,oBAAA;EACA,cAAA;EACA,aAAA;EACA,cAAA;EACA,kBAAA;ExD2gLD;AwDz/KD;EAZE;IACE,cAAA;IACA,mBAAA;IxDwgLD;EwDtgLD;InDrEA,mDAAA;IACQ,2CAAA;IL8kLP;EwDrgLD;IAAY,cAAA;IxDwgLX;EACF;AwDngLD;EAFE;IAAY,cAAA;IxDygLX;EACF;AyDtpLD;EACE,oBAAA;EACA,eAAA;EACA,gBAAA;EACA,qBAAA;EAEA,6DAAA;EACA,iBAAA;EACA,qBAAA;EACA,kBAAA;EnCZA,YAAA;EAGA,0BAAA;EtBkqLD;AyDtpLC;EnCfA,cAAA;EAGA,2BAAA;EtBsqLD;AyDzpLC;EAAW,kBAAA;EAAmB,gBAAA;EzD6pL/B;AyD5pLC;EAAW,kBAAA;EAAmB,gBAAA;EzDgqL/B;AyD/pLC;EAAW,iBAAA;EAAmB,gBAAA;EzDmqL/B;AyDlqLC;EAAW,mBAAA;EAAmB,gBAAA;EzDsqL/B;AyDlqLD;EACE,kBAAA;EACA,kBAAA;EACA,gBAAA;EACA,oBAAA;EACA,uBAAA;EACA,2BAAA;EACA,oBAAA;EzDoqLD;AyDhqLD;EACE,oBAAA;EACA,UAAA;EACA,WAAA;EACA,2BAAA;EACA,qBAAA;EzDkqLD;AyD9pLC;EACE,WAAA;EACA,WAAA;EACA,mBAAA;EACA,yBAAA;EACA,2BAAA;EzDgqLH;AyD9pLC;EACE,WAAA;EACA,YAAA;EACA,qBAAA;EACA,yBAAA;EACA,2BAAA;EzDgqLH;AyD9pLC;EACE,WAAA;EACA,WAAA;EACA,qBAAA;EACA,yBAAA;EACA,2BAAA;EzDgqLH;AyD9pLC;EACE,UAAA;EACA,SAAA;EACA,kBAAA;EACA,6BAAA;EACA,6BAAA;EzDgqLH;AyD9pLC;EACE,UAAA;EACA,UAAA;EACA,kBAAA;EACA,6BAAA;EACA,4BAAA;EzDgqLH;AyD9pLC;EACE,QAAA;EACA,WAAA;EACA,mBAAA;EACA,yBAAA;EACA,8BAAA;EzDgqLH;AyD9pLC;EACE,QAAA;EACA,YAAA;EACA,kBAAA;EACA,yBAAA;EACA,8BAAA;EzDgqLH;AyD9pLC;EACE,QAAA;EACA,WAAA;EACA,kBAAA;EACA,yBAAA;EACA,8BAAA;EzDgqLH;A0D/vLD;EACE,oBAAA;EACA,QAAA;EACA,SAAA;EACA,eAAA;EACA,eAAA;EACA,kBAAA;EACA,cAAA;EAEA,6DAAA;EACA,iBAAA;EACA,qBAAA;EACA,yBAAA;EACA,kBAAA;EACA,2BAAA;EACA,sCAAA;UAAA,8BAAA;EACA,2BAAA;EACA,sCAAA;EACA,oBAAA;ErD6CA,mDAAA;EACQ,2CAAA;EqD1CR,qBAAA;E1D+vLD;A0D5vLC;EAAY,mBAAA;E1D+vLb;A0D9vLC;EAAY,mBAAA;E1DiwLb;A0DhwLC;EAAY,kBAAA;E1DmwLb;A0DlwLC;EAAY,oBAAA;E1DqwLb;A0DlwLD;EACE,WAAA;EACA,mBAAA;EACA,iBAAA;EACA,2BAAA;EACA,kCAAA;EACA,4BAAA;E1DowLD;A0DjwLD;EACE,mBAAA;E1DmwLD;A0D3vLC;;EAEE,oBAAA;EACA,gBAAA;EACA,UAAA;EACA,WAAA;EACA,2BAAA;EACA,qBAAA;E1D6vLH;A0D1vLD;EACE,oBAAA;E1D4vLD;A0D1vLD;EACE,oBAAA;EACA,aAAA;E1D4vLD;A0DxvLC;EACE,WAAA;EACA,oBAAA;EACA,wBAAA;EACA,2BAAA;EACA,uCAAA;EACA,eAAA;E1D0vLH;A0DzvLG;EACE,cAAA;EACA,aAAA;EACA,oBAAA;EACA,wBAAA;EACA,2BAAA;E1D2vLL;A0DxvLC;EACE,UAAA;EACA,aAAA;EACA,mBAAA;EACA,sBAAA;EACA,6BAAA;EACA,yCAAA;E1D0vLH;A0DzvLG;EACE,cAAA;EACA,WAAA;EACA,eAAA;EACA,sBAAA;EACA,6BAAA;E1D2vLL;A0DxvLC;EACE,WAAA;EACA,oBAAA;EACA,qBAAA;EACA,8BAAA;EACA,0CAAA;EACA,YAAA;E1D0vLH;A0DzvLG;EACE,cAAA;EACA,UAAA;EACA,oBAAA;EACA,qBAAA;EACA,8BAAA;E1D2vLL;A0DvvLC;EACE,UAAA;EACA,cAAA;EACA,mBAAA;EACA,uBAAA;EACA,4BAAA;EACA,wCAAA;E1DyvLH;A0DxvLG;EACE,cAAA;EACA,YAAA;EACA,uBAAA;EACA,4BAAA;EACA,eAAA;E1D0vLL;A2Dv3LD;EACE,oBAAA;E3Dy3LD;A2Dt3LD;EACE,oBAAA;EACA,kBAAA;EACA,aAAA;E3Dw3LD;A2D33LD;EAMI,eAAA;EACA,oBAAA;EtD6KF,2CAAA;EACK,sCAAA;EACG,mCAAA;EL4sLT;A2Dl4LD;;EAcM,gBAAA;E3Dw3LL;A2D91LC;EAAA;ItDiKA,wDAAA;IAEK,8CAAA;IACG,wCAAA;IA7JR,qCAAA;IAEQ,6BAAA;IA+GR,2BAAA;IAEQ,mBAAA;ILivLP;E2D53LG;;ItDmHJ,4CAAA;IACQ,oCAAA;IsDjHF,SAAA;I3D+3LL;E2D73LG;;ItD8GJ,6CAAA;IACQ,qCAAA;IsD5GF,SAAA;I3Dg4LL;E2D93LG;;;ItDyGJ,yCAAA;IACQ,iCAAA;IsDtGF,SAAA;I3Di4LL;EACF;A2Dv6LD;;;EA6CI,gBAAA;E3D+3LH;A2D56LD;EAiDI,SAAA;E3D83LH;A2D/6LD;;EAsDI,oBAAA;EACA,QAAA;EACA,aAAA;E3D63LH;A2Dr7LD;EA4DI,YAAA;E3D43LH;A2Dx7LD;EA+DI,aAAA;E3D43LH;A2D37LD;;EAmEI,SAAA;E3D43LH;A2D/7LD;EAuEI,aAAA;E3D23LH;A2Dl8LD;EA0EI,YAAA;E3D23LH;A2Dn3LD;EACE,oBAAA;EACA,QAAA;EACA,SAAA;EACA,WAAA;EACA,YAAA;ErC9FA,cAAA;EAGA,2BAAA;EqC6FA,iBAAA;EACA,gBAAA;EACA,oBAAA;EACA,2CAAA;E3Ds3LD;A2Dj3LC;EblGE,oGAAA;EACA,+FAAA;EACA,sHAAA;EAAA,gGAAA;EACA,6BAAA;EACA,wHAAA;E9Cs9LH;A2Dr3LC;EACE,YAAA;EACA,UAAA;EbvGA,oGAAA;EACA,+FAAA;EACA,sHAAA;EAAA,gGAAA;EACA,6BAAA;EACA,wHAAA;E9C+9LH;A2Dv3LC;;EAEE,YAAA;EACA,gBAAA;EACA,uBAAA;ErCtHF,cAAA;EAGA,2BAAA;EtB8+LD;A2Dx5LD;;;;EAsCI,oBAAA;EACA,UAAA;EACA,YAAA;EACA,uBAAA;E3Dw3LH;A2Dj6LD;;EA6CI,WAAA;EACA,oBAAA;E3Dw3LH;A2Dt6LD;;EAkDI,YAAA;EACA,qBAAA;E3Dw3LH;A2D36LD;;EAuDI,aAAA;EACA,cAAA;EACA,mBAAA;EACA,gBAAA;EACA,oBAAA;E3Dw3LH;A2Dn3LG;EACE,kBAAA;E3Dq3LL;A2Dj3LG;EACE,kBAAA;E3Dm3LL;A2Dz2LD;EACE,oBAAA;EACA,cAAA;EACA,WAAA;EACA,aAAA;EACA,YAAA;EACA,mBAAA;EACA,iBAAA;EACA,kBAAA;EACA,oBAAA;E3D22LD;A2Dp3LD;EAYI,uBAAA;EACA,aAAA;EACA,cAAA;EACA,aAAA;EACA,qBAAA;EACA,2BAAA;EACA,qBAAA;EACA,iBAAA;EAWA,2BAAA;EACA,oCAAA;E3Di2LH;A2Dh4LD;EAkCI,WAAA;EACA,aAAA;EACA,cAAA;EACA,2BAAA;E3Di2LH;A2D11LD;EACE,oBAAA;EACA,WAAA;EACA,YAAA;EACA,cAAA;EACA,aAAA;EACA,mBAAA;EACA,sBAAA;EACA,gBAAA;EACA,oBAAA;EACA,2CAAA;E3D41LD;A2D31LC;EACE,mBAAA;E3D61LH;A2DpzLD;EAhCE;;;;IAKI,aAAA;IACA,cAAA;IACA,mBAAA;IACA,iBAAA;I3Ds1LH;E2D91LD;;IAYI,oBAAA;I3Ds1LH;E2Dl2LD;;IAgBI,qBAAA;I3Ds1LH;E2Dj1LD;IACE,WAAA;IACA,YAAA;IACA,sBAAA;I3Dm1LD;E2D/0LD;IACE,cAAA;I3Di1LD;EACF;A4D/kMC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAEE,cAAA;EACA,gBAAA;E5D6mMH;A4D3mMC;;;;;;;;;;;;;;;EACE,aAAA;E5D2nMH;AiCnoMD;E4BRE,gBAAA;EACA,mBAAA;EACA,oBAAA;E7D8oMD;AiCroMD;EACE,yBAAA;EjCuoMD;AiCroMD;EACE,wBAAA;EjCuoMD;AiC/nMD;EACE,0BAAA;EjCioMD;AiC/nMD;EACE,2BAAA;EjCioMD;AiC/nMD;EACE,oBAAA;EjCioMD;AiC/nMD;E6BzBE,aAAA;EACA,oBAAA;EACA,mBAAA;EACA,+BAAA;EACA,WAAA;E9D2pMD;AiC7nMD;EACE,0BAAA;EACA,+BAAA;EjC+nMD;AiCxnMD;EACE,iBAAA;EjC0nMD;A+D5pMD;EACE,qBAAA;E/D8pMD;A+DxpMD;;;;ECdE,0BAAA;EhE4qMD;A+DvpMD;;;;;;;;;;;;EAYE,0BAAA;E/DypMD;A+DlpMD;EAAA;IChDE,2BAAA;IhEssMC;EgErsMD;IAAU,gBAAA;IhEwsMT;EgEvsMD;IAAU,+BAAA;IhE0sMT;EgEzsMD;;IACU,gCAAA;IhE4sMT;EACF;A+D5pMD;EAAA;IAFI,2BAAA;I/DkqMD;EACF;A+D5pMD;EAAA;IAFI,4BAAA;I/DkqMD;EACF;A+D5pMD;EAAA;IAFI,kCAAA;I/DkqMD;EACF;A+D3pMD;EAAA;ICrEE,2BAAA;IhEouMC;EgEnuMD;IAAU,gBAAA;IhEsuMT;EgEruMD;IAAU,+BAAA;IhEwuMT;EgEvuMD;;IACU,gCAAA;IhE0uMT;EACF;A+DrqMD;EAAA;IAFI,2BAAA;I/D2qMD;EACF;A+DrqMD;EAAA;IAFI,4BAAA;I/D2qMD;EACF;A+DrqMD;EAAA;IAFI,kCAAA;I/D2qMD;EACF;A+DpqMD;EAAA;IC1FE,2BAAA;IhEkwMC;EgEjwMD;IAAU,gBAAA;IhEowMT;EgEnwMD;IAAU,+BAAA;IhEswMT;EgErwMD;;IACU,gCAAA;IhEwwMT;EACF;A+D9qMD;EAAA;IAFI,2BAAA;I/DorMD;EACF;A+D9qMD;EAAA;IAFI,4BAAA;I/DorMD;EACF;A+D9qMD;EAAA;IAFI,kCAAA;I/DorMD;EACF;A+D7qMD;EAAA;IC/GE,2BAAA;IhEgyMC;EgE/xMD;IAAU,gBAAA;IhEkyMT;EgEjyMD;IAAU,+BAAA;IhEoyMT;EgEnyMD;;IACU,gCAAA;IhEsyMT;EACF;A+DvrMD;EAAA;IAFI,2BAAA;I/D6rMD;EACF;A+DvrMD;EAAA;IAFI,4BAAA;I/D6rMD;EACF;A+DvrMD;EAAA;IAFI,kCAAA;I/D6rMD;EACF;A+DtrMD;EAAA;IC5HE,0BAAA;IhEszMC;EACF;A+DtrMD;EAAA;ICjIE,0BAAA;IhE2zMC;EACF;A+DtrMD;EAAA;ICtIE,0BAAA;IhEg0MC;EACF;A+DtrMD;EAAA;IC3IE,0BAAA;IhEq0MC;EACF;A+DnrMD;ECnJE,0BAAA;EhEy0MD;A+DhrMD;EAAA;ICjKE,2BAAA;IhEq1MC;EgEp1MD;IAAU,gBAAA;IhEu1MT;EgEt1MD;IAAU,+BAAA;IhEy1MT;EgEx1MD;;IACU,gCAAA;IhE21MT;EACF;A+D9rMD;EACE,0BAAA;E/DgsMD;A+D3rMD;EAAA;IAFI,2BAAA;I/DisMD;EACF;A+D/rMD;EACE,0BAAA;E/DisMD;A+D5rMD;EAAA;IAFI,4BAAA;I/DksMD;EACF;A+DhsMD;EACE,0BAAA;E/DksMD;A+D7rMD;EAAA;IAFI,kCAAA;I/DmsMD;EACF;A+D5rMD;EAAA;ICpLE,0BAAA;IhEo3MC;EACF","file":"bootstrap.css","sourcesContent":["/*! normalize.css v3.0.2 | MIT License | git.io/normalize */\nhtml {\n font-family: sans-serif;\n -ms-text-size-adjust: 100%;\n -webkit-text-size-adjust: 100%;\n}\nbody {\n margin: 0;\n}\narticle,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nmain,\nmenu,\nnav,\nsection,\nsummary {\n display: block;\n}\naudio,\ncanvas,\nprogress,\nvideo {\n display: inline-block;\n vertical-align: baseline;\n}\naudio:not([controls]) {\n display: none;\n height: 0;\n}\n[hidden],\ntemplate {\n display: none;\n}\na {\n background-color: transparent;\n}\na:active,\na:hover {\n outline: 0;\n}\nabbr[title] {\n border-bottom: 1px dotted;\n}\nb,\nstrong {\n font-weight: bold;\n}\ndfn {\n font-style: italic;\n}\nh1 {\n font-size: 2em;\n margin: 0.67em 0;\n}\nmark {\n background: #ff0;\n color: #000;\n}\nsmall {\n font-size: 80%;\n}\nsub,\nsup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n}\nsup {\n top: -0.5em;\n}\nsub {\n bottom: -0.25em;\n}\nimg {\n border: 0;\n}\nsvg:not(:root) {\n overflow: hidden;\n}\nfigure {\n margin: 1em 40px;\n}\nhr {\n -moz-box-sizing: content-box;\n box-sizing: content-box;\n height: 0;\n}\npre {\n overflow: auto;\n}\ncode,\nkbd,\npre,\nsamp {\n font-family: monospace, monospace;\n font-size: 1em;\n}\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n color: inherit;\n font: inherit;\n margin: 0;\n}\nbutton {\n overflow: visible;\n}\nbutton,\nselect {\n text-transform: none;\n}\nbutton,\nhtml input[type=\"button\"],\ninput[type=\"reset\"],\ninput[type=\"submit\"] {\n -webkit-appearance: button;\n cursor: pointer;\n}\nbutton[disabled],\nhtml input[disabled] {\n cursor: default;\n}\nbutton::-moz-focus-inner,\ninput::-moz-focus-inner {\n border: 0;\n padding: 0;\n}\ninput {\n line-height: normal;\n}\ninput[type=\"checkbox\"],\ninput[type=\"radio\"] {\n box-sizing: border-box;\n padding: 0;\n}\ninput[type=\"number\"]::-webkit-inner-spin-button,\ninput[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\ninput[type=\"search\"] {\n -webkit-appearance: textfield;\n -moz-box-sizing: content-box;\n -webkit-box-sizing: content-box;\n box-sizing: content-box;\n}\ninput[type=\"search\"]::-webkit-search-cancel-button,\ninput[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\nfieldset {\n border: 1px solid #c0c0c0;\n margin: 0 2px;\n padding: 0.35em 0.625em 0.75em;\n}\nlegend {\n border: 0;\n padding: 0;\n}\ntextarea {\n overflow: auto;\n}\noptgroup {\n font-weight: bold;\n}\ntable {\n border-collapse: collapse;\n border-spacing: 0;\n}\ntd,\nth {\n padding: 0;\n}\n/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */\n@media print {\n *,\n *:before,\n *:after {\n background: transparent !important;\n color: #000 !important;\n box-shadow: none !important;\n text-shadow: none !important;\n }\n a,\n a:visited {\n text-decoration: underline;\n }\n a[href]:after {\n content: \" (\" attr(href) \")\";\n }\n abbr[title]:after {\n content: \" (\" attr(title) \")\";\n }\n a[href^=\"#\"]:after,\n a[href^=\"javascript:\"]:after {\n content: \"\";\n }\n pre,\n blockquote {\n border: 1px solid #999;\n page-break-inside: avoid;\n }\n thead {\n display: table-header-group;\n }\n tr,\n img {\n page-break-inside: avoid;\n }\n img {\n max-width: 100% !important;\n }\n p,\n h2,\n h3 {\n orphans: 3;\n widows: 3;\n }\n h2,\n h3 {\n page-break-after: avoid;\n }\n select {\n background: #fff !important;\n }\n .navbar {\n display: none;\n }\n .btn > .caret,\n .dropup > .btn > .caret {\n border-top-color: #000 !important;\n }\n .label {\n border: 1px solid #000;\n }\n .table {\n border-collapse: collapse !important;\n }\n .table td,\n .table th {\n background-color: #fff !important;\n }\n .table-bordered th,\n .table-bordered td {\n border: 1px solid #ddd !important;\n }\n}\n@font-face {\n font-family: 'Glyphicons Halflings';\n src: url('../fonts/glyphicons-halflings-regular.eot');\n src: url('../fonts/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), url('../fonts/glyphicons-halflings-regular.woff2') format('woff2'), url('../fonts/glyphicons-halflings-regular.woff') format('woff'), url('../fonts/glyphicons-halflings-regular.ttf') format('truetype'), url('../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');\n}\n.glyphicon {\n position: relative;\n top: 1px;\n display: inline-block;\n font-family: 'Glyphicons Halflings';\n font-style: normal;\n font-weight: normal;\n line-height: 1;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n.glyphicon-asterisk:before {\n content: \"\\2a\";\n}\n.glyphicon-plus:before {\n content: \"\\2b\";\n}\n.glyphicon-euro:before,\n.glyphicon-eur:before {\n content: \"\\20ac\";\n}\n.glyphicon-minus:before {\n content: \"\\2212\";\n}\n.glyphicon-cloud:before {\n content: \"\\2601\";\n}\n.glyphicon-envelope:before {\n content: \"\\2709\";\n}\n.glyphicon-pencil:before {\n content: \"\\270f\";\n}\n.glyphicon-glass:before {\n content: \"\\e001\";\n}\n.glyphicon-music:before {\n content: \"\\e002\";\n}\n.glyphicon-search:before {\n content: \"\\e003\";\n}\n.glyphicon-heart:before {\n content: \"\\e005\";\n}\n.glyphicon-star:before {\n content: \"\\e006\";\n}\n.glyphicon-star-empty:before {\n content: \"\\e007\";\n}\n.glyphicon-user:before {\n content: \"\\e008\";\n}\n.glyphicon-film:before {\n content: \"\\e009\";\n}\n.glyphicon-th-large:before {\n content: \"\\e010\";\n}\n.glyphicon-th:before {\n content: \"\\e011\";\n}\n.glyphicon-th-list:before {\n content: \"\\e012\";\n}\n.glyphicon-ok:before {\n content: \"\\e013\";\n}\n.glyphicon-remove:before {\n content: \"\\e014\";\n}\n.glyphicon-zoom-in:before {\n content: \"\\e015\";\n}\n.glyphicon-zoom-out:before {\n content: \"\\e016\";\n}\n.glyphicon-off:before {\n content: \"\\e017\";\n}\n.glyphicon-signal:before {\n content: \"\\e018\";\n}\n.glyphicon-cog:before {\n content: \"\\e019\";\n}\n.glyphicon-trash:before {\n content: \"\\e020\";\n}\n.glyphicon-home:before {\n content: \"\\e021\";\n}\n.glyphicon-file:before {\n content: \"\\e022\";\n}\n.glyphicon-time:before {\n content: \"\\e023\";\n}\n.glyphicon-road:before {\n content: \"\\e024\";\n}\n.glyphicon-download-alt:before {\n content: \"\\e025\";\n}\n.glyphicon-download:before {\n content: \"\\e026\";\n}\n.glyphicon-upload:before {\n content: \"\\e027\";\n}\n.glyphicon-inbox:before {\n content: \"\\e028\";\n}\n.glyphicon-play-circle:before {\n content: \"\\e029\";\n}\n.glyphicon-repeat:before {\n content: \"\\e030\";\n}\n.glyphicon-refresh:before {\n content: \"\\e031\";\n}\n.glyphicon-list-alt:before {\n content: \"\\e032\";\n}\n.glyphicon-lock:before {\n content: \"\\e033\";\n}\n.glyphicon-flag:before {\n content: \"\\e034\";\n}\n.glyphicon-headphones:before {\n content: \"\\e035\";\n}\n.glyphicon-volume-off:before {\n content: \"\\e036\";\n}\n.glyphicon-volume-down:before {\n content: \"\\e037\";\n}\n.glyphicon-volume-up:before {\n content: \"\\e038\";\n}\n.glyphicon-qrcode:before {\n content: \"\\e039\";\n}\n.glyphicon-barcode:before {\n content: \"\\e040\";\n}\n.glyphicon-tag:before {\n content: \"\\e041\";\n}\n.glyphicon-tags:before {\n content: \"\\e042\";\n}\n.glyphicon-book:before {\n content: \"\\e043\";\n}\n.glyphicon-bookmark:before {\n content: \"\\e044\";\n}\n.glyphicon-print:before {\n content: \"\\e045\";\n}\n.glyphicon-camera:before {\n content: \"\\e046\";\n}\n.glyphicon-font:before {\n content: \"\\e047\";\n}\n.glyphicon-bold:before {\n content: \"\\e048\";\n}\n.glyphicon-italic:before {\n content: \"\\e049\";\n}\n.glyphicon-text-height:before {\n content: \"\\e050\";\n}\n.glyphicon-text-width:before {\n content: \"\\e051\";\n}\n.glyphicon-align-left:before {\n content: \"\\e052\";\n}\n.glyphicon-align-center:before {\n content: \"\\e053\";\n}\n.glyphicon-align-right:before {\n content: \"\\e054\";\n}\n.glyphicon-align-justify:before {\n content: \"\\e055\";\n}\n.glyphicon-list:before {\n content: \"\\e056\";\n}\n.glyphicon-indent-left:before {\n content: \"\\e057\";\n}\n.glyphicon-indent-right:before {\n content: \"\\e058\";\n}\n.glyphicon-facetime-video:before {\n content: \"\\e059\";\n}\n.glyphicon-picture:before {\n content: \"\\e060\";\n}\n.glyphicon-map-marker:before {\n content: \"\\e062\";\n}\n.glyphicon-adjust:before {\n content: \"\\e063\";\n}\n.glyphicon-tint:before {\n content: \"\\e064\";\n}\n.glyphicon-edit:before {\n content: \"\\e065\";\n}\n.glyphicon-share:before {\n content: \"\\e066\";\n}\n.glyphicon-check:before {\n content: \"\\e067\";\n}\n.glyphicon-move:before {\n content: \"\\e068\";\n}\n.glyphicon-step-backward:before {\n content: \"\\e069\";\n}\n.glyphicon-fast-backward:before {\n content: \"\\e070\";\n}\n.glyphicon-backward:before {\n content: \"\\e071\";\n}\n.glyphicon-play:before {\n content: \"\\e072\";\n}\n.glyphicon-pause:before {\n content: \"\\e073\";\n}\n.glyphicon-stop:before {\n content: \"\\e074\";\n}\n.glyphicon-forward:before {\n content: \"\\e075\";\n}\n.glyphicon-fast-forward:before {\n content: \"\\e076\";\n}\n.glyphicon-step-forward:before {\n content: \"\\e077\";\n}\n.glyphicon-eject:before {\n content: \"\\e078\";\n}\n.glyphicon-chevron-left:before {\n content: \"\\e079\";\n}\n.glyphicon-chevron-right:before {\n content: \"\\e080\";\n}\n.glyphicon-plus-sign:before {\n content: \"\\e081\";\n}\n.glyphicon-minus-sign:before {\n content: \"\\e082\";\n}\n.glyphicon-remove-sign:before {\n content: \"\\e083\";\n}\n.glyphicon-ok-sign:before {\n content: \"\\e084\";\n}\n.glyphicon-question-sign:before {\n content: \"\\e085\";\n}\n.glyphicon-info-sign:before {\n content: \"\\e086\";\n}\n.glyphicon-screenshot:before {\n content: \"\\e087\";\n}\n.glyphicon-remove-circle:before {\n content: \"\\e088\";\n}\n.glyphicon-ok-circle:before {\n content: \"\\e089\";\n}\n.glyphicon-ban-circle:before {\n content: \"\\e090\";\n}\n.glyphicon-arrow-left:before {\n content: \"\\e091\";\n}\n.glyphicon-arrow-right:before {\n content: \"\\e092\";\n}\n.glyphicon-arrow-up:before {\n content: \"\\e093\";\n}\n.glyphicon-arrow-down:before {\n content: \"\\e094\";\n}\n.glyphicon-share-alt:before {\n content: \"\\e095\";\n}\n.glyphicon-resize-full:before {\n content: \"\\e096\";\n}\n.glyphicon-resize-small:before {\n content: \"\\e097\";\n}\n.glyphicon-exclamation-sign:before {\n content: \"\\e101\";\n}\n.glyphicon-gift:before {\n content: \"\\e102\";\n}\n.glyphicon-leaf:before {\n content: \"\\e103\";\n}\n.glyphicon-fire:before {\n content: \"\\e104\";\n}\n.glyphicon-eye-open:before {\n content: \"\\e105\";\n}\n.glyphicon-eye-close:before {\n content: \"\\e106\";\n}\n.glyphicon-warning-sign:before {\n content: \"\\e107\";\n}\n.glyphicon-plane:before {\n content: \"\\e108\";\n}\n.glyphicon-calendar:before {\n content: \"\\e109\";\n}\n.glyphicon-random:before {\n content: \"\\e110\";\n}\n.glyphicon-comment:before {\n content: \"\\e111\";\n}\n.glyphicon-magnet:before {\n content: \"\\e112\";\n}\n.glyphicon-chevron-up:before {\n content: \"\\e113\";\n}\n.glyphicon-chevron-down:before {\n content: \"\\e114\";\n}\n.glyphicon-retweet:before {\n content: \"\\e115\";\n}\n.glyphicon-shopping-cart:before {\n content: \"\\e116\";\n}\n.glyphicon-folder-close:before {\n content: \"\\e117\";\n}\n.glyphicon-folder-open:before {\n content: \"\\e118\";\n}\n.glyphicon-resize-vertical:before {\n content: \"\\e119\";\n}\n.glyphicon-resize-horizontal:before {\n content: \"\\e120\";\n}\n.glyphicon-hdd:before {\n content: \"\\e121\";\n}\n.glyphicon-bullhorn:before {\n content: \"\\e122\";\n}\n.glyphicon-bell:before {\n content: \"\\e123\";\n}\n.glyphicon-certificate:before {\n content: \"\\e124\";\n}\n.glyphicon-thumbs-up:before {\n content: \"\\e125\";\n}\n.glyphicon-thumbs-down:before {\n content: \"\\e126\";\n}\n.glyphicon-hand-right:before {\n content: \"\\e127\";\n}\n.glyphicon-hand-left:before {\n content: \"\\e128\";\n}\n.glyphicon-hand-up:before {\n content: \"\\e129\";\n}\n.glyphicon-hand-down:before {\n content: \"\\e130\";\n}\n.glyphicon-circle-arrow-right:before {\n content: \"\\e131\";\n}\n.glyphicon-circle-arrow-left:before {\n content: \"\\e132\";\n}\n.glyphicon-circle-arrow-up:before {\n content: \"\\e133\";\n}\n.glyphicon-circle-arrow-down:before {\n content: \"\\e134\";\n}\n.glyphicon-globe:before {\n content: \"\\e135\";\n}\n.glyphicon-wrench:before {\n content: \"\\e136\";\n}\n.glyphicon-tasks:before {\n content: \"\\e137\";\n}\n.glyphicon-filter:before {\n content: \"\\e138\";\n}\n.glyphicon-briefcase:before {\n content: \"\\e139\";\n}\n.glyphicon-fullscreen:before {\n content: \"\\e140\";\n}\n.glyphicon-dashboard:before {\n content: \"\\e141\";\n}\n.glyphicon-paperclip:before {\n content: \"\\e142\";\n}\n.glyphicon-heart-empty:before {\n content: \"\\e143\";\n}\n.glyphicon-link:before {\n content: \"\\e144\";\n}\n.glyphicon-phone:before {\n content: \"\\e145\";\n}\n.glyphicon-pushpin:before {\n content: \"\\e146\";\n}\n.glyphicon-usd:before {\n content: \"\\e148\";\n}\n.glyphicon-gbp:before {\n content: \"\\e149\";\n}\n.glyphicon-sort:before {\n content: \"\\e150\";\n}\n.glyphicon-sort-by-alphabet:before {\n content: \"\\e151\";\n}\n.glyphicon-sort-by-alphabet-alt:before {\n content: \"\\e152\";\n}\n.glyphicon-sort-by-order:before {\n content: \"\\e153\";\n}\n.glyphicon-sort-by-order-alt:before {\n content: \"\\e154\";\n}\n.glyphicon-sort-by-attributes:before {\n content: \"\\e155\";\n}\n.glyphicon-sort-by-attributes-alt:before {\n content: \"\\e156\";\n}\n.glyphicon-unchecked:before {\n content: \"\\e157\";\n}\n.glyphicon-expand:before {\n content: \"\\e158\";\n}\n.glyphicon-collapse-down:before {\n content: \"\\e159\";\n}\n.glyphicon-collapse-up:before {\n content: \"\\e160\";\n}\n.glyphicon-log-in:before {\n content: \"\\e161\";\n}\n.glyphicon-flash:before {\n content: \"\\e162\";\n}\n.glyphicon-log-out:before {\n content: \"\\e163\";\n}\n.glyphicon-new-window:before {\n content: \"\\e164\";\n}\n.glyphicon-record:before {\n content: \"\\e165\";\n}\n.glyphicon-save:before {\n content: \"\\e166\";\n}\n.glyphicon-open:before {\n content: \"\\e167\";\n}\n.glyphicon-saved:before {\n content: \"\\e168\";\n}\n.glyphicon-import:before {\n content: \"\\e169\";\n}\n.glyphicon-export:before {\n content: \"\\e170\";\n}\n.glyphicon-send:before {\n content: \"\\e171\";\n}\n.glyphicon-floppy-disk:before {\n content: \"\\e172\";\n}\n.glyphicon-floppy-saved:before {\n content: \"\\e173\";\n}\n.glyphicon-floppy-remove:before {\n content: \"\\e174\";\n}\n.glyphicon-floppy-save:before {\n content: \"\\e175\";\n}\n.glyphicon-floppy-open:before {\n content: \"\\e176\";\n}\n.glyphicon-credit-card:before {\n content: \"\\e177\";\n}\n.glyphicon-transfer:before {\n content: \"\\e178\";\n}\n.glyphicon-cutlery:before {\n content: \"\\e179\";\n}\n.glyphicon-header:before {\n content: \"\\e180\";\n}\n.glyphicon-compressed:before {\n content: \"\\e181\";\n}\n.glyphicon-earphone:before {\n content: \"\\e182\";\n}\n.glyphicon-phone-alt:before {\n content: \"\\e183\";\n}\n.glyphicon-tower:before {\n content: \"\\e184\";\n}\n.glyphicon-stats:before {\n content: \"\\e185\";\n}\n.glyphicon-sd-video:before {\n content: \"\\e186\";\n}\n.glyphicon-hd-video:before {\n content: \"\\e187\";\n}\n.glyphicon-subtitles:before {\n content: \"\\e188\";\n}\n.glyphicon-sound-stereo:before {\n content: \"\\e189\";\n}\n.glyphicon-sound-dolby:before {\n content: \"\\e190\";\n}\n.glyphicon-sound-5-1:before {\n content: \"\\e191\";\n}\n.glyphicon-sound-6-1:before {\n content: \"\\e192\";\n}\n.glyphicon-sound-7-1:before {\n content: \"\\e193\";\n}\n.glyphicon-copyright-mark:before {\n content: \"\\e194\";\n}\n.glyphicon-registration-mark:before {\n content: \"\\e195\";\n}\n.glyphicon-cloud-download:before {\n content: \"\\e197\";\n}\n.glyphicon-cloud-upload:before {\n content: \"\\e198\";\n}\n.glyphicon-tree-conifer:before {\n content: \"\\e199\";\n}\n.glyphicon-tree-deciduous:before {\n content: \"\\e200\";\n}\n.glyphicon-cd:before {\n content: \"\\e201\";\n}\n.glyphicon-save-file:before {\n content: \"\\e202\";\n}\n.glyphicon-open-file:before {\n content: \"\\e203\";\n}\n.glyphicon-level-up:before {\n content: \"\\e204\";\n}\n.glyphicon-copy:before {\n content: \"\\e205\";\n}\n.glyphicon-paste:before {\n content: \"\\e206\";\n}\n.glyphicon-alert:before {\n content: \"\\e209\";\n}\n.glyphicon-equalizer:before {\n content: \"\\e210\";\n}\n.glyphicon-king:before {\n content: \"\\e211\";\n}\n.glyphicon-queen:before {\n content: \"\\e212\";\n}\n.glyphicon-pawn:before {\n content: \"\\e213\";\n}\n.glyphicon-bishop:before {\n content: \"\\e214\";\n}\n.glyphicon-knight:before {\n content: \"\\e215\";\n}\n.glyphicon-baby-formula:before {\n content: \"\\e216\";\n}\n.glyphicon-tent:before {\n content: \"\\26fa\";\n}\n.glyphicon-blackboard:before {\n content: \"\\e218\";\n}\n.glyphicon-bed:before {\n content: \"\\e219\";\n}\n.glyphicon-apple:before {\n content: \"\\f8ff\";\n}\n.glyphicon-erase:before {\n content: \"\\e221\";\n}\n.glyphicon-hourglass:before {\n content: \"\\231b\";\n}\n.glyphicon-lamp:before {\n content: \"\\e223\";\n}\n.glyphicon-duplicate:before {\n content: \"\\e224\";\n}\n.glyphicon-piggy-bank:before {\n content: \"\\e225\";\n}\n.glyphicon-scissors:before {\n content: \"\\e226\";\n}\n.glyphicon-bitcoin:before {\n content: \"\\e227\";\n}\n.glyphicon-yen:before {\n content: \"\\00a5\";\n}\n.glyphicon-ruble:before {\n content: \"\\20bd\";\n}\n.glyphicon-scale:before {\n content: \"\\e230\";\n}\n.glyphicon-ice-lolly:before {\n content: \"\\e231\";\n}\n.glyphicon-ice-lolly-tasted:before {\n content: \"\\e232\";\n}\n.glyphicon-education:before {\n content: \"\\e233\";\n}\n.glyphicon-option-horizontal:before {\n content: \"\\e234\";\n}\n.glyphicon-option-vertical:before {\n content: \"\\e235\";\n}\n.glyphicon-menu-hamburger:before {\n content: \"\\e236\";\n}\n.glyphicon-modal-window:before {\n content: \"\\e237\";\n}\n.glyphicon-oil:before {\n content: \"\\e238\";\n}\n.glyphicon-grain:before {\n content: \"\\e239\";\n}\n.glyphicon-sunglasses:before {\n content: \"\\e240\";\n}\n.glyphicon-text-size:before {\n content: \"\\e241\";\n}\n.glyphicon-text-color:before {\n content: \"\\e242\";\n}\n.glyphicon-text-background:before {\n content: \"\\e243\";\n}\n.glyphicon-object-align-top:before {\n content: \"\\e244\";\n}\n.glyphicon-object-align-bottom:before {\n content: \"\\e245\";\n}\n.glyphicon-object-align-horizontal:before {\n content: \"\\e246\";\n}\n.glyphicon-object-align-left:before {\n content: \"\\e247\";\n}\n.glyphicon-object-align-vertical:before {\n content: \"\\e248\";\n}\n.glyphicon-object-align-right:before {\n content: \"\\e249\";\n}\n.glyphicon-triangle-right:before {\n content: \"\\e250\";\n}\n.glyphicon-triangle-left:before {\n content: \"\\e251\";\n}\n.glyphicon-triangle-bottom:before {\n content: \"\\e252\";\n}\n.glyphicon-triangle-top:before {\n content: \"\\e253\";\n}\n.glyphicon-console:before {\n content: \"\\e254\";\n}\n.glyphicon-superscript:before {\n content: \"\\e255\";\n}\n.glyphicon-subscript:before {\n content: \"\\e256\";\n}\n.glyphicon-menu-left:before {\n content: \"\\e257\";\n}\n.glyphicon-menu-right:before {\n content: \"\\e258\";\n}\n.glyphicon-menu-down:before {\n content: \"\\e259\";\n}\n.glyphicon-menu-up:before {\n content: \"\\e260\";\n}\n* {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\n*:before,\n*:after {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\nhtml {\n font-size: 10px;\n -webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n}\nbody {\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n font-size: 14px;\n line-height: 1.42857143;\n color: #333333;\n background-color: #ffffff;\n}\ninput,\nbutton,\nselect,\ntextarea {\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\na {\n color: #337ab7;\n text-decoration: none;\n}\na:hover,\na:focus {\n color: #23527c;\n text-decoration: underline;\n}\na:focus {\n outline: thin dotted;\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\nfigure {\n margin: 0;\n}\nimg {\n vertical-align: middle;\n}\n.img-responsive,\n.thumbnail > img,\n.thumbnail a > img,\n.carousel-inner > .item > img,\n.carousel-inner > .item > a > img {\n display: block;\n max-width: 100%;\n height: auto;\n}\n.img-rounded {\n border-radius: 6px;\n}\n.img-thumbnail {\n padding: 4px;\n line-height: 1.42857143;\n background-color: #ffffff;\n border: 1px solid #dddddd;\n border-radius: 4px;\n -webkit-transition: all 0.2s ease-in-out;\n -o-transition: all 0.2s ease-in-out;\n transition: all 0.2s ease-in-out;\n display: inline-block;\n max-width: 100%;\n height: auto;\n}\n.img-circle {\n border-radius: 50%;\n}\nhr {\n margin-top: 20px;\n margin-bottom: 20px;\n border: 0;\n border-top: 1px solid #eeeeee;\n}\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip: rect(0, 0, 0, 0);\n border: 0;\n}\n.sr-only-focusable:active,\n.sr-only-focusable:focus {\n position: static;\n width: auto;\n height: auto;\n margin: 0;\n overflow: visible;\n clip: auto;\n}\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\n.h1,\n.h2,\n.h3,\n.h4,\n.h5,\n.h6 {\n font-family: inherit;\n font-weight: 500;\n line-height: 1.1;\n color: inherit;\n}\nh1 small,\nh2 small,\nh3 small,\nh4 small,\nh5 small,\nh6 small,\n.h1 small,\n.h2 small,\n.h3 small,\n.h4 small,\n.h5 small,\n.h6 small,\nh1 .small,\nh2 .small,\nh3 .small,\nh4 .small,\nh5 .small,\nh6 .small,\n.h1 .small,\n.h2 .small,\n.h3 .small,\n.h4 .small,\n.h5 .small,\n.h6 .small {\n font-weight: normal;\n line-height: 1;\n color: #777777;\n}\nh1,\n.h1,\nh2,\n.h2,\nh3,\n.h3 {\n margin-top: 20px;\n margin-bottom: 10px;\n}\nh1 small,\n.h1 small,\nh2 small,\n.h2 small,\nh3 small,\n.h3 small,\nh1 .small,\n.h1 .small,\nh2 .small,\n.h2 .small,\nh3 .small,\n.h3 .small {\n font-size: 65%;\n}\nh4,\n.h4,\nh5,\n.h5,\nh6,\n.h6 {\n margin-top: 10px;\n margin-bottom: 10px;\n}\nh4 small,\n.h4 small,\nh5 small,\n.h5 small,\nh6 small,\n.h6 small,\nh4 .small,\n.h4 .small,\nh5 .small,\n.h5 .small,\nh6 .small,\n.h6 .small {\n font-size: 75%;\n}\nh1,\n.h1 {\n font-size: 36px;\n}\nh2,\n.h2 {\n font-size: 30px;\n}\nh3,\n.h3 {\n font-size: 24px;\n}\nh4,\n.h4 {\n font-size: 18px;\n}\nh5,\n.h5 {\n font-size: 14px;\n}\nh6,\n.h6 {\n font-size: 12px;\n}\np {\n margin: 0 0 10px;\n}\n.lead {\n margin-bottom: 20px;\n font-size: 16px;\n font-weight: 300;\n line-height: 1.4;\n}\n@media (min-width: 768px) {\n .lead {\n font-size: 21px;\n }\n}\nsmall,\n.small {\n font-size: 85%;\n}\nmark,\n.mark {\n background-color: #fcf8e3;\n padding: .2em;\n}\n.text-left {\n text-align: left;\n}\n.text-right {\n text-align: right;\n}\n.text-center {\n text-align: center;\n}\n.text-justify {\n text-align: justify;\n}\n.text-nowrap {\n white-space: nowrap;\n}\n.text-lowercase {\n text-transform: lowercase;\n}\n.text-uppercase {\n text-transform: uppercase;\n}\n.text-capitalize {\n text-transform: capitalize;\n}\n.text-muted {\n color: #777777;\n}\n.text-primary {\n color: #337ab7;\n}\na.text-primary:hover {\n color: #286090;\n}\n.text-success {\n color: #3c763d;\n}\na.text-success:hover {\n color: #2b542c;\n}\n.text-info {\n color: #31708f;\n}\na.text-info:hover {\n color: #245269;\n}\n.text-warning {\n color: #8a6d3b;\n}\na.text-warning:hover {\n color: #66512c;\n}\n.text-danger {\n color: #a94442;\n}\na.text-danger:hover {\n color: #843534;\n}\n.bg-primary {\n color: #fff;\n background-color: #337ab7;\n}\na.bg-primary:hover {\n background-color: #286090;\n}\n.bg-success {\n background-color: #dff0d8;\n}\na.bg-success:hover {\n background-color: #c1e2b3;\n}\n.bg-info {\n background-color: #d9edf7;\n}\na.bg-info:hover {\n background-color: #afd9ee;\n}\n.bg-warning {\n background-color: #fcf8e3;\n}\na.bg-warning:hover {\n background-color: #f7ecb5;\n}\n.bg-danger {\n background-color: #f2dede;\n}\na.bg-danger:hover {\n background-color: #e4b9b9;\n}\n.page-header {\n padding-bottom: 9px;\n margin: 40px 0 20px;\n border-bottom: 1px solid #eeeeee;\n}\nul,\nol {\n margin-top: 0;\n margin-bottom: 10px;\n}\nul ul,\nol ul,\nul ol,\nol ol {\n margin-bottom: 0;\n}\n.list-unstyled {\n padding-left: 0;\n list-style: none;\n}\n.list-inline {\n padding-left: 0;\n list-style: none;\n margin-left: -5px;\n}\n.list-inline > li {\n display: inline-block;\n padding-left: 5px;\n padding-right: 5px;\n}\ndl {\n margin-top: 0;\n margin-bottom: 20px;\n}\ndt,\ndd {\n line-height: 1.42857143;\n}\ndt {\n font-weight: bold;\n}\ndd {\n margin-left: 0;\n}\n@media (min-width: 768px) {\n .dl-horizontal dt {\n float: left;\n width: 160px;\n clear: left;\n text-align: right;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n .dl-horizontal dd {\n margin-left: 180px;\n }\n}\nabbr[title],\nabbr[data-original-title] {\n cursor: help;\n border-bottom: 1px dotted #777777;\n}\n.initialism {\n font-size: 90%;\n text-transform: uppercase;\n}\nblockquote {\n padding: 10px 20px;\n margin: 0 0 20px;\n font-size: 17.5px;\n border-left: 5px solid #eeeeee;\n}\nblockquote p:last-child,\nblockquote ul:last-child,\nblockquote ol:last-child {\n margin-bottom: 0;\n}\nblockquote footer,\nblockquote small,\nblockquote .small {\n display: block;\n font-size: 80%;\n line-height: 1.42857143;\n color: #777777;\n}\nblockquote footer:before,\nblockquote small:before,\nblockquote .small:before {\n content: '\\2014 \\00A0';\n}\n.blockquote-reverse,\nblockquote.pull-right {\n padding-right: 15px;\n padding-left: 0;\n border-right: 5px solid #eeeeee;\n border-left: 0;\n text-align: right;\n}\n.blockquote-reverse footer:before,\nblockquote.pull-right footer:before,\n.blockquote-reverse small:before,\nblockquote.pull-right small:before,\n.blockquote-reverse .small:before,\nblockquote.pull-right .small:before {\n content: '';\n}\n.blockquote-reverse footer:after,\nblockquote.pull-right footer:after,\n.blockquote-reverse small:after,\nblockquote.pull-right small:after,\n.blockquote-reverse .small:after,\nblockquote.pull-right .small:after {\n content: '\\00A0 \\2014';\n}\naddress {\n margin-bottom: 20px;\n font-style: normal;\n line-height: 1.42857143;\n}\ncode,\nkbd,\npre,\nsamp {\n font-family: Menlo, Monaco, Consolas, \"Courier New\", monospace;\n}\ncode {\n padding: 2px 4px;\n font-size: 90%;\n color: #c7254e;\n background-color: #f9f2f4;\n border-radius: 4px;\n}\nkbd {\n padding: 2px 4px;\n font-size: 90%;\n color: #ffffff;\n background-color: #333333;\n border-radius: 3px;\n box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.25);\n}\nkbd kbd {\n padding: 0;\n font-size: 100%;\n font-weight: bold;\n box-shadow: none;\n}\npre {\n display: block;\n padding: 9.5px;\n margin: 0 0 10px;\n font-size: 13px;\n line-height: 1.42857143;\n word-break: break-all;\n word-wrap: break-word;\n color: #333333;\n background-color: #f5f5f5;\n border: 1px solid #cccccc;\n border-radius: 4px;\n}\npre code {\n padding: 0;\n font-size: inherit;\n color: inherit;\n white-space: pre-wrap;\n background-color: transparent;\n border-radius: 0;\n}\n.pre-scrollable {\n max-height: 340px;\n overflow-y: scroll;\n}\n.container {\n margin-right: auto;\n margin-left: auto;\n padding-left: 15px;\n padding-right: 15px;\n}\n@media (min-width: 768px) {\n .container {\n width: 750px;\n }\n}\n@media (min-width: 992px) {\n .container {\n width: 970px;\n }\n}\n@media (min-width: 1200px) {\n .container {\n width: 1170px;\n }\n}\n.container-fluid {\n margin-right: auto;\n margin-left: auto;\n padding-left: 15px;\n padding-right: 15px;\n}\n.row {\n margin-left: -15px;\n margin-right: -15px;\n}\n.col-xs-1, .col-sm-1, .col-md-1, .col-lg-1, .col-xs-2, .col-sm-2, .col-md-2, .col-lg-2, .col-xs-3, .col-sm-3, .col-md-3, .col-lg-3, .col-xs-4, .col-sm-4, .col-md-4, .col-lg-4, .col-xs-5, .col-sm-5, .col-md-5, .col-lg-5, .col-xs-6, .col-sm-6, .col-md-6, .col-lg-6, .col-xs-7, .col-sm-7, .col-md-7, .col-lg-7, .col-xs-8, .col-sm-8, .col-md-8, .col-lg-8, .col-xs-9, .col-sm-9, .col-md-9, .col-lg-9, .col-xs-10, .col-sm-10, .col-md-10, .col-lg-10, .col-xs-11, .col-sm-11, .col-md-11, .col-lg-11, .col-xs-12, .col-sm-12, .col-md-12, .col-lg-12 {\n position: relative;\n min-height: 1px;\n padding-left: 15px;\n padding-right: 15px;\n}\n.col-xs-1, .col-xs-2, .col-xs-3, .col-xs-4, .col-xs-5, .col-xs-6, .col-xs-7, .col-xs-8, .col-xs-9, .col-xs-10, .col-xs-11, .col-xs-12 {\n float: left;\n}\n.col-xs-12 {\n width: 100%;\n}\n.col-xs-11 {\n width: 91.66666667%;\n}\n.col-xs-10 {\n width: 83.33333333%;\n}\n.col-xs-9 {\n width: 75%;\n}\n.col-xs-8 {\n width: 66.66666667%;\n}\n.col-xs-7 {\n width: 58.33333333%;\n}\n.col-xs-6 {\n width: 50%;\n}\n.col-xs-5 {\n width: 41.66666667%;\n}\n.col-xs-4 {\n width: 33.33333333%;\n}\n.col-xs-3 {\n width: 25%;\n}\n.col-xs-2 {\n width: 16.66666667%;\n}\n.col-xs-1 {\n width: 8.33333333%;\n}\n.col-xs-pull-12 {\n right: 100%;\n}\n.col-xs-pull-11 {\n right: 91.66666667%;\n}\n.col-xs-pull-10 {\n right: 83.33333333%;\n}\n.col-xs-pull-9 {\n right: 75%;\n}\n.col-xs-pull-8 {\n right: 66.66666667%;\n}\n.col-xs-pull-7 {\n right: 58.33333333%;\n}\n.col-xs-pull-6 {\n right: 50%;\n}\n.col-xs-pull-5 {\n right: 41.66666667%;\n}\n.col-xs-pull-4 {\n right: 33.33333333%;\n}\n.col-xs-pull-3 {\n right: 25%;\n}\n.col-xs-pull-2 {\n right: 16.66666667%;\n}\n.col-xs-pull-1 {\n right: 8.33333333%;\n}\n.col-xs-pull-0 {\n right: auto;\n}\n.col-xs-push-12 {\n left: 100%;\n}\n.col-xs-push-11 {\n left: 91.66666667%;\n}\n.col-xs-push-10 {\n left: 83.33333333%;\n}\n.col-xs-push-9 {\n left: 75%;\n}\n.col-xs-push-8 {\n left: 66.66666667%;\n}\n.col-xs-push-7 {\n left: 58.33333333%;\n}\n.col-xs-push-6 {\n left: 50%;\n}\n.col-xs-push-5 {\n left: 41.66666667%;\n}\n.col-xs-push-4 {\n left: 33.33333333%;\n}\n.col-xs-push-3 {\n left: 25%;\n}\n.col-xs-push-2 {\n left: 16.66666667%;\n}\n.col-xs-push-1 {\n left: 8.33333333%;\n}\n.col-xs-push-0 {\n left: auto;\n}\n.col-xs-offset-12 {\n margin-left: 100%;\n}\n.col-xs-offset-11 {\n margin-left: 91.66666667%;\n}\n.col-xs-offset-10 {\n margin-left: 83.33333333%;\n}\n.col-xs-offset-9 {\n margin-left: 75%;\n}\n.col-xs-offset-8 {\n margin-left: 66.66666667%;\n}\n.col-xs-offset-7 {\n margin-left: 58.33333333%;\n}\n.col-xs-offset-6 {\n margin-left: 50%;\n}\n.col-xs-offset-5 {\n margin-left: 41.66666667%;\n}\n.col-xs-offset-4 {\n margin-left: 33.33333333%;\n}\n.col-xs-offset-3 {\n margin-left: 25%;\n}\n.col-xs-offset-2 {\n margin-left: 16.66666667%;\n}\n.col-xs-offset-1 {\n margin-left: 8.33333333%;\n}\n.col-xs-offset-0 {\n margin-left: 0%;\n}\n@media (min-width: 768px) {\n .col-sm-1, .col-sm-2, .col-sm-3, .col-sm-4, .col-sm-5, .col-sm-6, .col-sm-7, .col-sm-8, .col-sm-9, .col-sm-10, .col-sm-11, .col-sm-12 {\n float: left;\n }\n .col-sm-12 {\n width: 100%;\n }\n .col-sm-11 {\n width: 91.66666667%;\n }\n .col-sm-10 {\n width: 83.33333333%;\n }\n .col-sm-9 {\n width: 75%;\n }\n .col-sm-8 {\n width: 66.66666667%;\n }\n .col-sm-7 {\n width: 58.33333333%;\n }\n .col-sm-6 {\n width: 50%;\n }\n .col-sm-5 {\n width: 41.66666667%;\n }\n .col-sm-4 {\n width: 33.33333333%;\n }\n .col-sm-3 {\n width: 25%;\n }\n .col-sm-2 {\n width: 16.66666667%;\n }\n .col-sm-1 {\n width: 8.33333333%;\n }\n .col-sm-pull-12 {\n right: 100%;\n }\n .col-sm-pull-11 {\n right: 91.66666667%;\n }\n .col-sm-pull-10 {\n right: 83.33333333%;\n }\n .col-sm-pull-9 {\n right: 75%;\n }\n .col-sm-pull-8 {\n right: 66.66666667%;\n }\n .col-sm-pull-7 {\n right: 58.33333333%;\n }\n .col-sm-pull-6 {\n right: 50%;\n }\n .col-sm-pull-5 {\n right: 41.66666667%;\n }\n .col-sm-pull-4 {\n right: 33.33333333%;\n }\n .col-sm-pull-3 {\n right: 25%;\n }\n .col-sm-pull-2 {\n right: 16.66666667%;\n }\n .col-sm-pull-1 {\n right: 8.33333333%;\n }\n .col-sm-pull-0 {\n right: auto;\n }\n .col-sm-push-12 {\n left: 100%;\n }\n .col-sm-push-11 {\n left: 91.66666667%;\n }\n .col-sm-push-10 {\n left: 83.33333333%;\n }\n .col-sm-push-9 {\n left: 75%;\n }\n .col-sm-push-8 {\n left: 66.66666667%;\n }\n .col-sm-push-7 {\n left: 58.33333333%;\n }\n .col-sm-push-6 {\n left: 50%;\n }\n .col-sm-push-5 {\n left: 41.66666667%;\n }\n .col-sm-push-4 {\n left: 33.33333333%;\n }\n .col-sm-push-3 {\n left: 25%;\n }\n .col-sm-push-2 {\n left: 16.66666667%;\n }\n .col-sm-push-1 {\n left: 8.33333333%;\n }\n .col-sm-push-0 {\n left: auto;\n }\n .col-sm-offset-12 {\n margin-left: 100%;\n }\n .col-sm-offset-11 {\n margin-left: 91.66666667%;\n }\n .col-sm-offset-10 {\n margin-left: 83.33333333%;\n }\n .col-sm-offset-9 {\n margin-left: 75%;\n }\n .col-sm-offset-8 {\n margin-left: 66.66666667%;\n }\n .col-sm-offset-7 {\n margin-left: 58.33333333%;\n }\n .col-sm-offset-6 {\n margin-left: 50%;\n }\n .col-sm-offset-5 {\n margin-left: 41.66666667%;\n }\n .col-sm-offset-4 {\n margin-left: 33.33333333%;\n }\n .col-sm-offset-3 {\n margin-left: 25%;\n }\n .col-sm-offset-2 {\n margin-left: 16.66666667%;\n }\n .col-sm-offset-1 {\n margin-left: 8.33333333%;\n }\n .col-sm-offset-0 {\n margin-left: 0%;\n }\n}\n@media (min-width: 992px) {\n .col-md-1, .col-md-2, .col-md-3, .col-md-4, .col-md-5, .col-md-6, .col-md-7, .col-md-8, .col-md-9, .col-md-10, .col-md-11, .col-md-12 {\n float: left;\n }\n .col-md-12 {\n width: 100%;\n }\n .col-md-11 {\n width: 91.66666667%;\n }\n .col-md-10 {\n width: 83.33333333%;\n }\n .col-md-9 {\n width: 75%;\n }\n .col-md-8 {\n width: 66.66666667%;\n }\n .col-md-7 {\n width: 58.33333333%;\n }\n .col-md-6 {\n width: 50%;\n }\n .col-md-5 {\n width: 41.66666667%;\n }\n .col-md-4 {\n width: 33.33333333%;\n }\n .col-md-3 {\n width: 25%;\n }\n .col-md-2 {\n width: 16.66666667%;\n }\n .col-md-1 {\n width: 8.33333333%;\n }\n .col-md-pull-12 {\n right: 100%;\n }\n .col-md-pull-11 {\n right: 91.66666667%;\n }\n .col-md-pull-10 {\n right: 83.33333333%;\n }\n .col-md-pull-9 {\n right: 75%;\n }\n .col-md-pull-8 {\n right: 66.66666667%;\n }\n .col-md-pull-7 {\n right: 58.33333333%;\n }\n .col-md-pull-6 {\n right: 50%;\n }\n .col-md-pull-5 {\n right: 41.66666667%;\n }\n .col-md-pull-4 {\n right: 33.33333333%;\n }\n .col-md-pull-3 {\n right: 25%;\n }\n .col-md-pull-2 {\n right: 16.66666667%;\n }\n .col-md-pull-1 {\n right: 8.33333333%;\n }\n .col-md-pull-0 {\n right: auto;\n }\n .col-md-push-12 {\n left: 100%;\n }\n .col-md-push-11 {\n left: 91.66666667%;\n }\n .col-md-push-10 {\n left: 83.33333333%;\n }\n .col-md-push-9 {\n left: 75%;\n }\n .col-md-push-8 {\n left: 66.66666667%;\n }\n .col-md-push-7 {\n left: 58.33333333%;\n }\n .col-md-push-6 {\n left: 50%;\n }\n .col-md-push-5 {\n left: 41.66666667%;\n }\n .col-md-push-4 {\n left: 33.33333333%;\n }\n .col-md-push-3 {\n left: 25%;\n }\n .col-md-push-2 {\n left: 16.66666667%;\n }\n .col-md-push-1 {\n left: 8.33333333%;\n }\n .col-md-push-0 {\n left: auto;\n }\n .col-md-offset-12 {\n margin-left: 100%;\n }\n .col-md-offset-11 {\n margin-left: 91.66666667%;\n }\n .col-md-offset-10 {\n margin-left: 83.33333333%;\n }\n .col-md-offset-9 {\n margin-left: 75%;\n }\n .col-md-offset-8 {\n margin-left: 66.66666667%;\n }\n .col-md-offset-7 {\n margin-left: 58.33333333%;\n }\n .col-md-offset-6 {\n margin-left: 50%;\n }\n .col-md-offset-5 {\n margin-left: 41.66666667%;\n }\n .col-md-offset-4 {\n margin-left: 33.33333333%;\n }\n .col-md-offset-3 {\n margin-left: 25%;\n }\n .col-md-offset-2 {\n margin-left: 16.66666667%;\n }\n .col-md-offset-1 {\n margin-left: 8.33333333%;\n }\n .col-md-offset-0 {\n margin-left: 0%;\n }\n}\n@media (min-width: 1200px) {\n .col-lg-1, .col-lg-2, .col-lg-3, .col-lg-4, .col-lg-5, .col-lg-6, .col-lg-7, .col-lg-8, .col-lg-9, .col-lg-10, .col-lg-11, .col-lg-12 {\n float: left;\n }\n .col-lg-12 {\n width: 100%;\n }\n .col-lg-11 {\n width: 91.66666667%;\n }\n .col-lg-10 {\n width: 83.33333333%;\n }\n .col-lg-9 {\n width: 75%;\n }\n .col-lg-8 {\n width: 66.66666667%;\n }\n .col-lg-7 {\n width: 58.33333333%;\n }\n .col-lg-6 {\n width: 50%;\n }\n .col-lg-5 {\n width: 41.66666667%;\n }\n .col-lg-4 {\n width: 33.33333333%;\n }\n .col-lg-3 {\n width: 25%;\n }\n .col-lg-2 {\n width: 16.66666667%;\n }\n .col-lg-1 {\n width: 8.33333333%;\n }\n .col-lg-pull-12 {\n right: 100%;\n }\n .col-lg-pull-11 {\n right: 91.66666667%;\n }\n .col-lg-pull-10 {\n right: 83.33333333%;\n }\n .col-lg-pull-9 {\n right: 75%;\n }\n .col-lg-pull-8 {\n right: 66.66666667%;\n }\n .col-lg-pull-7 {\n right: 58.33333333%;\n }\n .col-lg-pull-6 {\n right: 50%;\n }\n .col-lg-pull-5 {\n right: 41.66666667%;\n }\n .col-lg-pull-4 {\n right: 33.33333333%;\n }\n .col-lg-pull-3 {\n right: 25%;\n }\n .col-lg-pull-2 {\n right: 16.66666667%;\n }\n .col-lg-pull-1 {\n right: 8.33333333%;\n }\n .col-lg-pull-0 {\n right: auto;\n }\n .col-lg-push-12 {\n left: 100%;\n }\n .col-lg-push-11 {\n left: 91.66666667%;\n }\n .col-lg-push-10 {\n left: 83.33333333%;\n }\n .col-lg-push-9 {\n left: 75%;\n }\n .col-lg-push-8 {\n left: 66.66666667%;\n }\n .col-lg-push-7 {\n left: 58.33333333%;\n }\n .col-lg-push-6 {\n left: 50%;\n }\n .col-lg-push-5 {\n left: 41.66666667%;\n }\n .col-lg-push-4 {\n left: 33.33333333%;\n }\n .col-lg-push-3 {\n left: 25%;\n }\n .col-lg-push-2 {\n left: 16.66666667%;\n }\n .col-lg-push-1 {\n left: 8.33333333%;\n }\n .col-lg-push-0 {\n left: auto;\n }\n .col-lg-offset-12 {\n margin-left: 100%;\n }\n .col-lg-offset-11 {\n margin-left: 91.66666667%;\n }\n .col-lg-offset-10 {\n margin-left: 83.33333333%;\n }\n .col-lg-offset-9 {\n margin-left: 75%;\n }\n .col-lg-offset-8 {\n margin-left: 66.66666667%;\n }\n .col-lg-offset-7 {\n margin-left: 58.33333333%;\n }\n .col-lg-offset-6 {\n margin-left: 50%;\n }\n .col-lg-offset-5 {\n margin-left: 41.66666667%;\n }\n .col-lg-offset-4 {\n margin-left: 33.33333333%;\n }\n .col-lg-offset-3 {\n margin-left: 25%;\n }\n .col-lg-offset-2 {\n margin-left: 16.66666667%;\n }\n .col-lg-offset-1 {\n margin-left: 8.33333333%;\n }\n .col-lg-offset-0 {\n margin-left: 0%;\n }\n}\ntable {\n background-color: transparent;\n}\ncaption {\n padding-top: 8px;\n padding-bottom: 8px;\n color: #777777;\n text-align: left;\n}\nth {\n text-align: left;\n}\n.table {\n width: 100%;\n max-width: 100%;\n margin-bottom: 20px;\n}\n.table > thead > tr > th,\n.table > tbody > tr > th,\n.table > tfoot > tr > th,\n.table > thead > tr > td,\n.table > tbody > tr > td,\n.table > tfoot > tr > td {\n padding: 8px;\n line-height: 1.42857143;\n vertical-align: top;\n border-top: 1px solid #dddddd;\n}\n.table > thead > tr > th {\n vertical-align: bottom;\n border-bottom: 2px solid #dddddd;\n}\n.table > caption + thead > tr:first-child > th,\n.table > colgroup + thead > tr:first-child > th,\n.table > thead:first-child > tr:first-child > th,\n.table > caption + thead > tr:first-child > td,\n.table > colgroup + thead > tr:first-child > td,\n.table > thead:first-child > tr:first-child > td {\n border-top: 0;\n}\n.table > tbody + tbody {\n border-top: 2px solid #dddddd;\n}\n.table .table {\n background-color: #ffffff;\n}\n.table-condensed > thead > tr > th,\n.table-condensed > tbody > tr > th,\n.table-condensed > tfoot > tr > th,\n.table-condensed > thead > tr > td,\n.table-condensed > tbody > tr > td,\n.table-condensed > tfoot > tr > td {\n padding: 5px;\n}\n.table-bordered {\n border: 1px solid #dddddd;\n}\n.table-bordered > thead > tr > th,\n.table-bordered > tbody > tr > th,\n.table-bordered > tfoot > tr > th,\n.table-bordered > thead > tr > td,\n.table-bordered > tbody > tr > td,\n.table-bordered > tfoot > tr > td {\n border: 1px solid #dddddd;\n}\n.table-bordered > thead > tr > th,\n.table-bordered > thead > tr > td {\n border-bottom-width: 2px;\n}\n.table-striped > tbody > tr:nth-of-type(odd) {\n background-color: #f9f9f9;\n}\n.table-hover > tbody > tr:hover {\n background-color: #f5f5f5;\n}\ntable col[class*=\"col-\"] {\n position: static;\n float: none;\n display: table-column;\n}\ntable td[class*=\"col-\"],\ntable th[class*=\"col-\"] {\n position: static;\n float: none;\n display: table-cell;\n}\n.table > thead > tr > td.active,\n.table > tbody > tr > td.active,\n.table > tfoot > tr > td.active,\n.table > thead > tr > th.active,\n.table > tbody > tr > th.active,\n.table > tfoot > tr > th.active,\n.table > thead > tr.active > td,\n.table > tbody > tr.active > td,\n.table > tfoot > tr.active > td,\n.table > thead > tr.active > th,\n.table > tbody > tr.active > th,\n.table > tfoot > tr.active > th {\n background-color: #f5f5f5;\n}\n.table-hover > tbody > tr > td.active:hover,\n.table-hover > tbody > tr > th.active:hover,\n.table-hover > tbody > tr.active:hover > td,\n.table-hover > tbody > tr:hover > .active,\n.table-hover > tbody > tr.active:hover > th {\n background-color: #e8e8e8;\n}\n.table > thead > tr > td.success,\n.table > tbody > tr > td.success,\n.table > tfoot > tr > td.success,\n.table > thead > tr > th.success,\n.table > tbody > tr > th.success,\n.table > tfoot > tr > th.success,\n.table > thead > tr.success > td,\n.table > tbody > tr.success > td,\n.table > tfoot > tr.success > td,\n.table > thead > tr.success > th,\n.table > tbody > tr.success > th,\n.table > tfoot > tr.success > th {\n background-color: #dff0d8;\n}\n.table-hover > tbody > tr > td.success:hover,\n.table-hover > tbody > tr > th.success:hover,\n.table-hover > tbody > tr.success:hover > td,\n.table-hover > tbody > tr:hover > .success,\n.table-hover > tbody > tr.success:hover > th {\n background-color: #d0e9c6;\n}\n.table > thead > tr > td.info,\n.table > tbody > tr > td.info,\n.table > tfoot > tr > td.info,\n.table > thead > tr > th.info,\n.table > tbody > tr > th.info,\n.table > tfoot > tr > th.info,\n.table > thead > tr.info > td,\n.table > tbody > tr.info > td,\n.table > tfoot > tr.info > td,\n.table > thead > tr.info > th,\n.table > tbody > tr.info > th,\n.table > tfoot > tr.info > th {\n background-color: #d9edf7;\n}\n.table-hover > tbody > tr > td.info:hover,\n.table-hover > tbody > tr > th.info:hover,\n.table-hover > tbody > tr.info:hover > td,\n.table-hover > tbody > tr:hover > .info,\n.table-hover > tbody > tr.info:hover > th {\n background-color: #c4e3f3;\n}\n.table > thead > tr > td.warning,\n.table > tbody > tr > td.warning,\n.table > tfoot > tr > td.warning,\n.table > thead > tr > th.warning,\n.table > tbody > tr > th.warning,\n.table > tfoot > tr > th.warning,\n.table > thead > tr.warning > td,\n.table > tbody > tr.warning > td,\n.table > tfoot > tr.warning > td,\n.table > thead > tr.warning > th,\n.table > tbody > tr.warning > th,\n.table > tfoot > tr.warning > th {\n background-color: #fcf8e3;\n}\n.table-hover > tbody > tr > td.warning:hover,\n.table-hover > tbody > tr > th.warning:hover,\n.table-hover > tbody > tr.warning:hover > td,\n.table-hover > tbody > tr:hover > .warning,\n.table-hover > tbody > tr.warning:hover > th {\n background-color: #faf2cc;\n}\n.table > thead > tr > td.danger,\n.table > tbody > tr > td.danger,\n.table > tfoot > tr > td.danger,\n.table > thead > tr > th.danger,\n.table > tbody > tr > th.danger,\n.table > tfoot > tr > th.danger,\n.table > thead > tr.danger > td,\n.table > tbody > tr.danger > td,\n.table > tfoot > tr.danger > td,\n.table > thead > tr.danger > th,\n.table > tbody > tr.danger > th,\n.table > tfoot > tr.danger > th {\n background-color: #f2dede;\n}\n.table-hover > tbody > tr > td.danger:hover,\n.table-hover > tbody > tr > th.danger:hover,\n.table-hover > tbody > tr.danger:hover > td,\n.table-hover > tbody > tr:hover > .danger,\n.table-hover > tbody > tr.danger:hover > th {\n background-color: #ebcccc;\n}\n.table-responsive {\n overflow-x: auto;\n min-height: 0.01%;\n}\n@media screen and (max-width: 767px) {\n .table-responsive {\n width: 100%;\n margin-bottom: 15px;\n overflow-y: hidden;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n border: 1px solid #dddddd;\n }\n .table-responsive > .table {\n margin-bottom: 0;\n }\n .table-responsive > .table > thead > tr > th,\n .table-responsive > .table > tbody > tr > th,\n .table-responsive > .table > tfoot > tr > th,\n .table-responsive > .table > thead > tr > td,\n .table-responsive > .table > tbody > tr > td,\n .table-responsive > .table > tfoot > tr > td {\n white-space: nowrap;\n }\n .table-responsive > .table-bordered {\n border: 0;\n }\n .table-responsive > .table-bordered > thead > tr > th:first-child,\n .table-responsive > .table-bordered > tbody > tr > th:first-child,\n .table-responsive > .table-bordered > tfoot > tr > th:first-child,\n .table-responsive > .table-bordered > thead > tr > td:first-child,\n .table-responsive > .table-bordered > tbody > tr > td:first-child,\n .table-responsive > .table-bordered > tfoot > tr > td:first-child {\n border-left: 0;\n }\n .table-responsive > .table-bordered > thead > tr > th:last-child,\n .table-responsive > .table-bordered > tbody > tr > th:last-child,\n .table-responsive > .table-bordered > tfoot > tr > th:last-child,\n .table-responsive > .table-bordered > thead > tr > td:last-child,\n .table-responsive > .table-bordered > tbody > tr > td:last-child,\n .table-responsive > .table-bordered > tfoot > tr > td:last-child {\n border-right: 0;\n }\n .table-responsive > .table-bordered > tbody > tr:last-child > th,\n .table-responsive > .table-bordered > tfoot > tr:last-child > th,\n .table-responsive > .table-bordered > tbody > tr:last-child > td,\n .table-responsive > .table-bordered > tfoot > tr:last-child > td {\n border-bottom: 0;\n }\n}\nfieldset {\n padding: 0;\n margin: 0;\n border: 0;\n min-width: 0;\n}\nlegend {\n display: block;\n width: 100%;\n padding: 0;\n margin-bottom: 20px;\n font-size: 21px;\n line-height: inherit;\n color: #333333;\n border: 0;\n border-bottom: 1px solid #e5e5e5;\n}\nlabel {\n display: inline-block;\n max-width: 100%;\n margin-bottom: 5px;\n font-weight: bold;\n}\ninput[type=\"search\"] {\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n margin: 4px 0 0;\n margin-top: 1px \\9;\n line-height: normal;\n}\ninput[type=\"file\"] {\n display: block;\n}\ninput[type=\"range\"] {\n display: block;\n width: 100%;\n}\nselect[multiple],\nselect[size] {\n height: auto;\n}\ninput[type=\"file\"]:focus,\ninput[type=\"radio\"]:focus,\ninput[type=\"checkbox\"]:focus {\n outline: thin dotted;\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\noutput {\n display: block;\n padding-top: 7px;\n font-size: 14px;\n line-height: 1.42857143;\n color: #555555;\n}\n.form-control {\n display: block;\n width: 100%;\n height: 34px;\n padding: 6px 12px;\n font-size: 14px;\n line-height: 1.42857143;\n color: #555555;\n background-color: #ffffff;\n background-image: none;\n border: 1px solid #cccccc;\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n}\n.form-control:focus {\n border-color: #66afe9;\n outline: 0;\n -webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);\n box-shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px rgba(102, 175, 233, 0.6);\n}\n.form-control::-moz-placeholder {\n color: #999999;\n opacity: 1;\n}\n.form-control:-ms-input-placeholder {\n color: #999999;\n}\n.form-control::-webkit-input-placeholder {\n color: #999999;\n}\n.form-control[disabled],\n.form-control[readonly],\nfieldset[disabled] .form-control {\n cursor: not-allowed;\n background-color: #eeeeee;\n opacity: 1;\n}\ntextarea.form-control {\n height: auto;\n}\ninput[type=\"search\"] {\n -webkit-appearance: none;\n}\n@media screen and (-webkit-min-device-pixel-ratio: 0) {\n input[type=\"date\"],\n input[type=\"time\"],\n input[type=\"datetime-local\"],\n input[type=\"month\"] {\n line-height: 34px;\n }\n input[type=\"date\"].input-sm,\n input[type=\"time\"].input-sm,\n input[type=\"datetime-local\"].input-sm,\n input[type=\"month\"].input-sm,\n .input-group-sm input[type=\"date\"],\n .input-group-sm input[type=\"time\"],\n .input-group-sm input[type=\"datetime-local\"],\n .input-group-sm input[type=\"month\"] {\n line-height: 30px;\n }\n input[type=\"date\"].input-lg,\n input[type=\"time\"].input-lg,\n input[type=\"datetime-local\"].input-lg,\n input[type=\"month\"].input-lg,\n .input-group-lg input[type=\"date\"],\n .input-group-lg input[type=\"time\"],\n .input-group-lg input[type=\"datetime-local\"],\n .input-group-lg input[type=\"month\"] {\n line-height: 46px;\n }\n}\n.form-group {\n margin-bottom: 15px;\n}\n.radio,\n.checkbox {\n position: relative;\n display: block;\n margin-top: 10px;\n margin-bottom: 10px;\n}\n.radio label,\n.checkbox label {\n min-height: 20px;\n padding-left: 20px;\n margin-bottom: 0;\n font-weight: normal;\n cursor: pointer;\n}\n.radio input[type=\"radio\"],\n.radio-inline input[type=\"radio\"],\n.checkbox input[type=\"checkbox\"],\n.checkbox-inline input[type=\"checkbox\"] {\n position: absolute;\n margin-left: -20px;\n margin-top: 4px \\9;\n}\n.radio + .radio,\n.checkbox + .checkbox {\n margin-top: -5px;\n}\n.radio-inline,\n.checkbox-inline {\n display: inline-block;\n padding-left: 20px;\n margin-bottom: 0;\n vertical-align: middle;\n font-weight: normal;\n cursor: pointer;\n}\n.radio-inline + .radio-inline,\n.checkbox-inline + .checkbox-inline {\n margin-top: 0;\n margin-left: 10px;\n}\ninput[type=\"radio\"][disabled],\ninput[type=\"checkbox\"][disabled],\ninput[type=\"radio\"].disabled,\ninput[type=\"checkbox\"].disabled,\nfieldset[disabled] input[type=\"radio\"],\nfieldset[disabled] input[type=\"checkbox\"] {\n cursor: not-allowed;\n}\n.radio-inline.disabled,\n.checkbox-inline.disabled,\nfieldset[disabled] .radio-inline,\nfieldset[disabled] .checkbox-inline {\n cursor: not-allowed;\n}\n.radio.disabled label,\n.checkbox.disabled label,\nfieldset[disabled] .radio label,\nfieldset[disabled] .checkbox label {\n cursor: not-allowed;\n}\n.form-control-static {\n padding-top: 7px;\n padding-bottom: 7px;\n margin-bottom: 0;\n}\n.form-control-static.input-lg,\n.form-control-static.input-sm {\n padding-left: 0;\n padding-right: 0;\n}\n.input-sm {\n height: 30px;\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\nselect.input-sm {\n height: 30px;\n line-height: 30px;\n}\ntextarea.input-sm,\nselect[multiple].input-sm {\n height: auto;\n}\n.form-group-sm .form-control {\n height: 30px;\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\nselect.form-group-sm .form-control {\n height: 30px;\n line-height: 30px;\n}\ntextarea.form-group-sm .form-control,\nselect[multiple].form-group-sm .form-control {\n height: auto;\n}\n.form-group-sm .form-control-static {\n height: 30px;\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n}\n.input-lg {\n height: 46px;\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\nselect.input-lg {\n height: 46px;\n line-height: 46px;\n}\ntextarea.input-lg,\nselect[multiple].input-lg {\n height: auto;\n}\n.form-group-lg .form-control {\n height: 46px;\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\nselect.form-group-lg .form-control {\n height: 46px;\n line-height: 46px;\n}\ntextarea.form-group-lg .form-control,\nselect[multiple].form-group-lg .form-control {\n height: auto;\n}\n.form-group-lg .form-control-static {\n height: 46px;\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n}\n.has-feedback {\n position: relative;\n}\n.has-feedback .form-control {\n padding-right: 42.5px;\n}\n.form-control-feedback {\n position: absolute;\n top: 0;\n right: 0;\n z-index: 2;\n display: block;\n width: 34px;\n height: 34px;\n line-height: 34px;\n text-align: center;\n pointer-events: none;\n}\n.input-lg + .form-control-feedback {\n width: 46px;\n height: 46px;\n line-height: 46px;\n}\n.input-sm + .form-control-feedback {\n width: 30px;\n height: 30px;\n line-height: 30px;\n}\n.has-success .help-block,\n.has-success .control-label,\n.has-success .radio,\n.has-success .checkbox,\n.has-success .radio-inline,\n.has-success .checkbox-inline,\n.has-success.radio label,\n.has-success.checkbox label,\n.has-success.radio-inline label,\n.has-success.checkbox-inline label {\n color: #3c763d;\n}\n.has-success .form-control {\n border-color: #3c763d;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.has-success .form-control:focus {\n border-color: #2b542c;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #67b168;\n}\n.has-success .input-group-addon {\n color: #3c763d;\n border-color: #3c763d;\n background-color: #dff0d8;\n}\n.has-success .form-control-feedback {\n color: #3c763d;\n}\n.has-warning .help-block,\n.has-warning .control-label,\n.has-warning .radio,\n.has-warning .checkbox,\n.has-warning .radio-inline,\n.has-warning .checkbox-inline,\n.has-warning.radio label,\n.has-warning.checkbox label,\n.has-warning.radio-inline label,\n.has-warning.checkbox-inline label {\n color: #8a6d3b;\n}\n.has-warning .form-control {\n border-color: #8a6d3b;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.has-warning .form-control:focus {\n border-color: #66512c;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #c0a16b;\n}\n.has-warning .input-group-addon {\n color: #8a6d3b;\n border-color: #8a6d3b;\n background-color: #fcf8e3;\n}\n.has-warning .form-control-feedback {\n color: #8a6d3b;\n}\n.has-error .help-block,\n.has-error .control-label,\n.has-error .radio,\n.has-error .checkbox,\n.has-error .radio-inline,\n.has-error .checkbox-inline,\n.has-error.radio label,\n.has-error.checkbox label,\n.has-error.radio-inline label,\n.has-error.checkbox-inline label {\n color: #a94442;\n}\n.has-error .form-control {\n border-color: #a94442;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);\n}\n.has-error .form-control:focus {\n border-color: #843534;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #ce8483;\n}\n.has-error .input-group-addon {\n color: #a94442;\n border-color: #a94442;\n background-color: #f2dede;\n}\n.has-error .form-control-feedback {\n color: #a94442;\n}\n.has-feedback label ~ .form-control-feedback {\n top: 25px;\n}\n.has-feedback label.sr-only ~ .form-control-feedback {\n top: 0;\n}\n.help-block {\n display: block;\n margin-top: 5px;\n margin-bottom: 10px;\n color: #737373;\n}\n@media (min-width: 768px) {\n .form-inline .form-group {\n display: inline-block;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .form-inline .form-control {\n display: inline-block;\n width: auto;\n vertical-align: middle;\n }\n .form-inline .form-control-static {\n display: inline-block;\n }\n .form-inline .input-group {\n display: inline-table;\n vertical-align: middle;\n }\n .form-inline .input-group .input-group-addon,\n .form-inline .input-group .input-group-btn,\n .form-inline .input-group .form-control {\n width: auto;\n }\n .form-inline .input-group > .form-control {\n width: 100%;\n }\n .form-inline .control-label {\n margin-bottom: 0;\n vertical-align: middle;\n }\n .form-inline .radio,\n .form-inline .checkbox {\n display: inline-block;\n margin-top: 0;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .form-inline .radio label,\n .form-inline .checkbox label {\n padding-left: 0;\n }\n .form-inline .radio input[type=\"radio\"],\n .form-inline .checkbox input[type=\"checkbox\"] {\n position: relative;\n margin-left: 0;\n }\n .form-inline .has-feedback .form-control-feedback {\n top: 0;\n }\n}\n.form-horizontal .radio,\n.form-horizontal .checkbox,\n.form-horizontal .radio-inline,\n.form-horizontal .checkbox-inline {\n margin-top: 0;\n margin-bottom: 0;\n padding-top: 7px;\n}\n.form-horizontal .radio,\n.form-horizontal .checkbox {\n min-height: 27px;\n}\n.form-horizontal .form-group {\n margin-left: -15px;\n margin-right: -15px;\n}\n@media (min-width: 768px) {\n .form-horizontal .control-label {\n text-align: right;\n margin-bottom: 0;\n padding-top: 7px;\n }\n}\n.form-horizontal .has-feedback .form-control-feedback {\n right: 15px;\n}\n@media (min-width: 768px) {\n .form-horizontal .form-group-lg .control-label {\n padding-top: 14.333333px;\n }\n}\n@media (min-width: 768px) {\n .form-horizontal .form-group-sm .control-label {\n padding-top: 6px;\n }\n}\n.btn {\n display: inline-block;\n margin-bottom: 0;\n font-weight: normal;\n text-align: center;\n vertical-align: middle;\n touch-action: manipulation;\n cursor: pointer;\n background-image: none;\n border: 1px solid transparent;\n white-space: nowrap;\n padding: 6px 12px;\n font-size: 14px;\n line-height: 1.42857143;\n border-radius: 4px;\n -webkit-user-select: none;\n -moz-user-select: none;\n -ms-user-select: none;\n user-select: none;\n}\n.btn:focus,\n.btn:active:focus,\n.btn.active:focus,\n.btn.focus,\n.btn:active.focus,\n.btn.active.focus {\n outline: thin dotted;\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\n.btn:hover,\n.btn:focus,\n.btn.focus {\n color: #333333;\n text-decoration: none;\n}\n.btn:active,\n.btn.active {\n outline: 0;\n background-image: none;\n -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn.disabled,\n.btn[disabled],\nfieldset[disabled] .btn {\n cursor: not-allowed;\n pointer-events: none;\n opacity: 0.65;\n filter: alpha(opacity=65);\n -webkit-box-shadow: none;\n box-shadow: none;\n}\n.btn-default {\n color: #333333;\n background-color: #ffffff;\n border-color: #cccccc;\n}\n.btn-default:hover,\n.btn-default:focus,\n.btn-default.focus,\n.btn-default:active,\n.btn-default.active,\n.open > .dropdown-toggle.btn-default {\n color: #333333;\n background-color: #e6e6e6;\n border-color: #adadad;\n}\n.btn-default:active,\n.btn-default.active,\n.open > .dropdown-toggle.btn-default {\n background-image: none;\n}\n.btn-default.disabled,\n.btn-default[disabled],\nfieldset[disabled] .btn-default,\n.btn-default.disabled:hover,\n.btn-default[disabled]:hover,\nfieldset[disabled] .btn-default:hover,\n.btn-default.disabled:focus,\n.btn-default[disabled]:focus,\nfieldset[disabled] .btn-default:focus,\n.btn-default.disabled.focus,\n.btn-default[disabled].focus,\nfieldset[disabled] .btn-default.focus,\n.btn-default.disabled:active,\n.btn-default[disabled]:active,\nfieldset[disabled] .btn-default:active,\n.btn-default.disabled.active,\n.btn-default[disabled].active,\nfieldset[disabled] .btn-default.active {\n background-color: #ffffff;\n border-color: #cccccc;\n}\n.btn-default .badge {\n color: #ffffff;\n background-color: #333333;\n}\n.btn-primary {\n color: #ffffff;\n background-color: #337ab7;\n border-color: #2e6da4;\n}\n.btn-primary:hover,\n.btn-primary:focus,\n.btn-primary.focus,\n.btn-primary:active,\n.btn-primary.active,\n.open > .dropdown-toggle.btn-primary {\n color: #ffffff;\n background-color: #286090;\n border-color: #204d74;\n}\n.btn-primary:active,\n.btn-primary.active,\n.open > .dropdown-toggle.btn-primary {\n background-image: none;\n}\n.btn-primary.disabled,\n.btn-primary[disabled],\nfieldset[disabled] .btn-primary,\n.btn-primary.disabled:hover,\n.btn-primary[disabled]:hover,\nfieldset[disabled] .btn-primary:hover,\n.btn-primary.disabled:focus,\n.btn-primary[disabled]:focus,\nfieldset[disabled] .btn-primary:focus,\n.btn-primary.disabled.focus,\n.btn-primary[disabled].focus,\nfieldset[disabled] .btn-primary.focus,\n.btn-primary.disabled:active,\n.btn-primary[disabled]:active,\nfieldset[disabled] .btn-primary:active,\n.btn-primary.disabled.active,\n.btn-primary[disabled].active,\nfieldset[disabled] .btn-primary.active {\n background-color: #337ab7;\n border-color: #2e6da4;\n}\n.btn-primary .badge {\n color: #337ab7;\n background-color: #ffffff;\n}\n.btn-success {\n color: #ffffff;\n background-color: #5cb85c;\n border-color: #4cae4c;\n}\n.btn-success:hover,\n.btn-success:focus,\n.btn-success.focus,\n.btn-success:active,\n.btn-success.active,\n.open > .dropdown-toggle.btn-success {\n color: #ffffff;\n background-color: #449d44;\n border-color: #398439;\n}\n.btn-success:active,\n.btn-success.active,\n.open > .dropdown-toggle.btn-success {\n background-image: none;\n}\n.btn-success.disabled,\n.btn-success[disabled],\nfieldset[disabled] .btn-success,\n.btn-success.disabled:hover,\n.btn-success[disabled]:hover,\nfieldset[disabled] .btn-success:hover,\n.btn-success.disabled:focus,\n.btn-success[disabled]:focus,\nfieldset[disabled] .btn-success:focus,\n.btn-success.disabled.focus,\n.btn-success[disabled].focus,\nfieldset[disabled] .btn-success.focus,\n.btn-success.disabled:active,\n.btn-success[disabled]:active,\nfieldset[disabled] .btn-success:active,\n.btn-success.disabled.active,\n.btn-success[disabled].active,\nfieldset[disabled] .btn-success.active {\n background-color: #5cb85c;\n border-color: #4cae4c;\n}\n.btn-success .badge {\n color: #5cb85c;\n background-color: #ffffff;\n}\n.btn-info {\n color: #ffffff;\n background-color: #5bc0de;\n border-color: #46b8da;\n}\n.btn-info:hover,\n.btn-info:focus,\n.btn-info.focus,\n.btn-info:active,\n.btn-info.active,\n.open > .dropdown-toggle.btn-info {\n color: #ffffff;\n background-color: #31b0d5;\n border-color: #269abc;\n}\n.btn-info:active,\n.btn-info.active,\n.open > .dropdown-toggle.btn-info {\n background-image: none;\n}\n.btn-info.disabled,\n.btn-info[disabled],\nfieldset[disabled] .btn-info,\n.btn-info.disabled:hover,\n.btn-info[disabled]:hover,\nfieldset[disabled] .btn-info:hover,\n.btn-info.disabled:focus,\n.btn-info[disabled]:focus,\nfieldset[disabled] .btn-info:focus,\n.btn-info.disabled.focus,\n.btn-info[disabled].focus,\nfieldset[disabled] .btn-info.focus,\n.btn-info.disabled:active,\n.btn-info[disabled]:active,\nfieldset[disabled] .btn-info:active,\n.btn-info.disabled.active,\n.btn-info[disabled].active,\nfieldset[disabled] .btn-info.active {\n background-color: #5bc0de;\n border-color: #46b8da;\n}\n.btn-info .badge {\n color: #5bc0de;\n background-color: #ffffff;\n}\n.btn-warning {\n color: #ffffff;\n background-color: #f0ad4e;\n border-color: #eea236;\n}\n.btn-warning:hover,\n.btn-warning:focus,\n.btn-warning.focus,\n.btn-warning:active,\n.btn-warning.active,\n.open > .dropdown-toggle.btn-warning {\n color: #ffffff;\n background-color: #ec971f;\n border-color: #d58512;\n}\n.btn-warning:active,\n.btn-warning.active,\n.open > .dropdown-toggle.btn-warning {\n background-image: none;\n}\n.btn-warning.disabled,\n.btn-warning[disabled],\nfieldset[disabled] .btn-warning,\n.btn-warning.disabled:hover,\n.btn-warning[disabled]:hover,\nfieldset[disabled] .btn-warning:hover,\n.btn-warning.disabled:focus,\n.btn-warning[disabled]:focus,\nfieldset[disabled] .btn-warning:focus,\n.btn-warning.disabled.focus,\n.btn-warning[disabled].focus,\nfieldset[disabled] .btn-warning.focus,\n.btn-warning.disabled:active,\n.btn-warning[disabled]:active,\nfieldset[disabled] .btn-warning:active,\n.btn-warning.disabled.active,\n.btn-warning[disabled].active,\nfieldset[disabled] .btn-warning.active {\n background-color: #f0ad4e;\n border-color: #eea236;\n}\n.btn-warning .badge {\n color: #f0ad4e;\n background-color: #ffffff;\n}\n.btn-danger {\n color: #ffffff;\n background-color: #d9534f;\n border-color: #d43f3a;\n}\n.btn-danger:hover,\n.btn-danger:focus,\n.btn-danger.focus,\n.btn-danger:active,\n.btn-danger.active,\n.open > .dropdown-toggle.btn-danger {\n color: #ffffff;\n background-color: #c9302c;\n border-color: #ac2925;\n}\n.btn-danger:active,\n.btn-danger.active,\n.open > .dropdown-toggle.btn-danger {\n background-image: none;\n}\n.btn-danger.disabled,\n.btn-danger[disabled],\nfieldset[disabled] .btn-danger,\n.btn-danger.disabled:hover,\n.btn-danger[disabled]:hover,\nfieldset[disabled] .btn-danger:hover,\n.btn-danger.disabled:focus,\n.btn-danger[disabled]:focus,\nfieldset[disabled] .btn-danger:focus,\n.btn-danger.disabled.focus,\n.btn-danger[disabled].focus,\nfieldset[disabled] .btn-danger.focus,\n.btn-danger.disabled:active,\n.btn-danger[disabled]:active,\nfieldset[disabled] .btn-danger:active,\n.btn-danger.disabled.active,\n.btn-danger[disabled].active,\nfieldset[disabled] .btn-danger.active {\n background-color: #d9534f;\n border-color: #d43f3a;\n}\n.btn-danger .badge {\n color: #d9534f;\n background-color: #ffffff;\n}\n.btn-link {\n color: #337ab7;\n font-weight: normal;\n border-radius: 0;\n}\n.btn-link,\n.btn-link:active,\n.btn-link.active,\n.btn-link[disabled],\nfieldset[disabled] .btn-link {\n background-color: transparent;\n -webkit-box-shadow: none;\n box-shadow: none;\n}\n.btn-link,\n.btn-link:hover,\n.btn-link:focus,\n.btn-link:active {\n border-color: transparent;\n}\n.btn-link:hover,\n.btn-link:focus {\n color: #23527c;\n text-decoration: underline;\n background-color: transparent;\n}\n.btn-link[disabled]:hover,\nfieldset[disabled] .btn-link:hover,\n.btn-link[disabled]:focus,\nfieldset[disabled] .btn-link:focus {\n color: #777777;\n text-decoration: none;\n}\n.btn-lg,\n.btn-group-lg > .btn {\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\n.btn-sm,\n.btn-group-sm > .btn {\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\n.btn-xs,\n.btn-group-xs > .btn {\n padding: 1px 5px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\n.btn-block {\n display: block;\n width: 100%;\n}\n.btn-block + .btn-block {\n margin-top: 5px;\n}\ninput[type=\"submit\"].btn-block,\ninput[type=\"reset\"].btn-block,\ninput[type=\"button\"].btn-block {\n width: 100%;\n}\n.fade {\n opacity: 0;\n -webkit-transition: opacity 0.15s linear;\n -o-transition: opacity 0.15s linear;\n transition: opacity 0.15s linear;\n}\n.fade.in {\n opacity: 1;\n}\n.collapse {\n display: none;\n visibility: hidden;\n}\n.collapse.in {\n display: block;\n visibility: visible;\n}\ntr.collapse.in {\n display: table-row;\n}\ntbody.collapse.in {\n display: table-row-group;\n}\n.collapsing {\n position: relative;\n height: 0;\n overflow: hidden;\n -webkit-transition-property: height, visibility;\n transition-property: height, visibility;\n -webkit-transition-duration: 0.35s;\n transition-duration: 0.35s;\n -webkit-transition-timing-function: ease;\n transition-timing-function: ease;\n}\n.caret {\n display: inline-block;\n width: 0;\n height: 0;\n margin-left: 2px;\n vertical-align: middle;\n border-top: 4px solid;\n border-right: 4px solid transparent;\n border-left: 4px solid transparent;\n}\n.dropup,\n.dropdown {\n position: relative;\n}\n.dropdown-toggle:focus {\n outline: 0;\n}\n.dropdown-menu {\n position: absolute;\n top: 100%;\n left: 0;\n z-index: 1000;\n display: none;\n float: left;\n min-width: 160px;\n padding: 5px 0;\n margin: 2px 0 0;\n list-style: none;\n font-size: 14px;\n text-align: left;\n background-color: #ffffff;\n border: 1px solid #cccccc;\n border: 1px solid rgba(0, 0, 0, 0.15);\n border-radius: 4px;\n -webkit-box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);\n box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);\n background-clip: padding-box;\n}\n.dropdown-menu.pull-right {\n right: 0;\n left: auto;\n}\n.dropdown-menu .divider {\n height: 1px;\n margin: 9px 0;\n overflow: hidden;\n background-color: #e5e5e5;\n}\n.dropdown-menu > li > a {\n display: block;\n padding: 3px 20px;\n clear: both;\n font-weight: normal;\n line-height: 1.42857143;\n color: #333333;\n white-space: nowrap;\n}\n.dropdown-menu > li > a:hover,\n.dropdown-menu > li > a:focus {\n text-decoration: none;\n color: #262626;\n background-color: #f5f5f5;\n}\n.dropdown-menu > .active > a,\n.dropdown-menu > .active > a:hover,\n.dropdown-menu > .active > a:focus {\n color: #ffffff;\n text-decoration: none;\n outline: 0;\n background-color: #337ab7;\n}\n.dropdown-menu > .disabled > a,\n.dropdown-menu > .disabled > a:hover,\n.dropdown-menu > .disabled > a:focus {\n color: #777777;\n}\n.dropdown-menu > .disabled > a:hover,\n.dropdown-menu > .disabled > a:focus {\n text-decoration: none;\n background-color: transparent;\n background-image: none;\n filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);\n cursor: not-allowed;\n}\n.open > .dropdown-menu {\n display: block;\n}\n.open > a {\n outline: 0;\n}\n.dropdown-menu-right {\n left: auto;\n right: 0;\n}\n.dropdown-menu-left {\n left: 0;\n right: auto;\n}\n.dropdown-header {\n display: block;\n padding: 3px 20px;\n font-size: 12px;\n line-height: 1.42857143;\n color: #777777;\n white-space: nowrap;\n}\n.dropdown-backdrop {\n position: fixed;\n left: 0;\n right: 0;\n bottom: 0;\n top: 0;\n z-index: 990;\n}\n.pull-right > .dropdown-menu {\n right: 0;\n left: auto;\n}\n.dropup .caret,\n.navbar-fixed-bottom .dropdown .caret {\n border-top: 0;\n border-bottom: 4px solid;\n content: \"\";\n}\n.dropup .dropdown-menu,\n.navbar-fixed-bottom .dropdown .dropdown-menu {\n top: auto;\n bottom: 100%;\n margin-bottom: 2px;\n}\n@media (min-width: 768px) {\n .navbar-right .dropdown-menu {\n left: auto;\n right: 0;\n }\n .navbar-right .dropdown-menu-left {\n left: 0;\n right: auto;\n }\n}\n.btn-group,\n.btn-group-vertical {\n position: relative;\n display: inline-block;\n vertical-align: middle;\n}\n.btn-group > .btn,\n.btn-group-vertical > .btn {\n position: relative;\n float: left;\n}\n.btn-group > .btn:hover,\n.btn-group-vertical > .btn:hover,\n.btn-group > .btn:focus,\n.btn-group-vertical > .btn:focus,\n.btn-group > .btn:active,\n.btn-group-vertical > .btn:active,\n.btn-group > .btn.active,\n.btn-group-vertical > .btn.active {\n z-index: 2;\n}\n.btn-group .btn + .btn,\n.btn-group .btn + .btn-group,\n.btn-group .btn-group + .btn,\n.btn-group .btn-group + .btn-group {\n margin-left: -1px;\n}\n.btn-toolbar {\n margin-left: -5px;\n}\n.btn-toolbar .btn-group,\n.btn-toolbar .input-group {\n float: left;\n}\n.btn-toolbar > .btn,\n.btn-toolbar > .btn-group,\n.btn-toolbar > .input-group {\n margin-left: 5px;\n}\n.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {\n border-radius: 0;\n}\n.btn-group > .btn:first-child {\n margin-left: 0;\n}\n.btn-group > .btn:first-child:not(:last-child):not(.dropdown-toggle) {\n border-bottom-right-radius: 0;\n border-top-right-radius: 0;\n}\n.btn-group > .btn:last-child:not(:first-child),\n.btn-group > .dropdown-toggle:not(:first-child) {\n border-bottom-left-radius: 0;\n border-top-left-radius: 0;\n}\n.btn-group > .btn-group {\n float: left;\n}\n.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group > .btn-group:first-child:not(:last-child) > .btn:last-child,\n.btn-group > .btn-group:first-child:not(:last-child) > .dropdown-toggle {\n border-bottom-right-radius: 0;\n border-top-right-radius: 0;\n}\n.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child {\n border-bottom-left-radius: 0;\n border-top-left-radius: 0;\n}\n.btn-group .dropdown-toggle:active,\n.btn-group.open .dropdown-toggle {\n outline: 0;\n}\n.btn-group > .btn + .dropdown-toggle {\n padding-left: 8px;\n padding-right: 8px;\n}\n.btn-group > .btn-lg + .dropdown-toggle {\n padding-left: 12px;\n padding-right: 12px;\n}\n.btn-group.open .dropdown-toggle {\n -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125);\n}\n.btn-group.open .dropdown-toggle.btn-link {\n -webkit-box-shadow: none;\n box-shadow: none;\n}\n.btn .caret {\n margin-left: 0;\n}\n.btn-lg .caret {\n border-width: 5px 5px 0;\n border-bottom-width: 0;\n}\n.dropup .btn-lg .caret {\n border-width: 0 5px 5px;\n}\n.btn-group-vertical > .btn,\n.btn-group-vertical > .btn-group,\n.btn-group-vertical > .btn-group > .btn {\n display: block;\n float: none;\n width: 100%;\n max-width: 100%;\n}\n.btn-group-vertical > .btn-group > .btn {\n float: none;\n}\n.btn-group-vertical > .btn + .btn,\n.btn-group-vertical > .btn + .btn-group,\n.btn-group-vertical > .btn-group + .btn,\n.btn-group-vertical > .btn-group + .btn-group {\n margin-top: -1px;\n margin-left: 0;\n}\n.btn-group-vertical > .btn:not(:first-child):not(:last-child) {\n border-radius: 0;\n}\n.btn-group-vertical > .btn:first-child:not(:last-child) {\n border-top-right-radius: 4px;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n.btn-group-vertical > .btn:last-child:not(:first-child) {\n border-bottom-left-radius: 4px;\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n}\n.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group-vertical > .btn-group:first-child:not(:last-child) > .btn:last-child,\n.btn-group-vertical > .btn-group:first-child:not(:last-child) > .dropdown-toggle {\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n}\n.btn-group-justified {\n display: table;\n width: 100%;\n table-layout: fixed;\n border-collapse: separate;\n}\n.btn-group-justified > .btn,\n.btn-group-justified > .btn-group {\n float: none;\n display: table-cell;\n width: 1%;\n}\n.btn-group-justified > .btn-group .btn {\n width: 100%;\n}\n.btn-group-justified > .btn-group .dropdown-menu {\n left: auto;\n}\n[data-toggle=\"buttons\"] > .btn input[type=\"radio\"],\n[data-toggle=\"buttons\"] > .btn-group > .btn input[type=\"radio\"],\n[data-toggle=\"buttons\"] > .btn input[type=\"checkbox\"],\n[data-toggle=\"buttons\"] > .btn-group > .btn input[type=\"checkbox\"] {\n position: absolute;\n clip: rect(0, 0, 0, 0);\n pointer-events: none;\n}\n.input-group {\n position: relative;\n display: table;\n border-collapse: separate;\n}\n.input-group[class*=\"col-\"] {\n float: none;\n padding-left: 0;\n padding-right: 0;\n}\n.input-group .form-control {\n position: relative;\n z-index: 2;\n float: left;\n width: 100%;\n margin-bottom: 0;\n}\n.input-group-lg > .form-control,\n.input-group-lg > .input-group-addon,\n.input-group-lg > .input-group-btn > .btn {\n height: 46px;\n padding: 10px 16px;\n font-size: 18px;\n line-height: 1.3333333;\n border-radius: 6px;\n}\nselect.input-group-lg > .form-control,\nselect.input-group-lg > .input-group-addon,\nselect.input-group-lg > .input-group-btn > .btn {\n height: 46px;\n line-height: 46px;\n}\ntextarea.input-group-lg > .form-control,\ntextarea.input-group-lg > .input-group-addon,\ntextarea.input-group-lg > .input-group-btn > .btn,\nselect[multiple].input-group-lg > .form-control,\nselect[multiple].input-group-lg > .input-group-addon,\nselect[multiple].input-group-lg > .input-group-btn > .btn {\n height: auto;\n}\n.input-group-sm > .form-control,\n.input-group-sm > .input-group-addon,\n.input-group-sm > .input-group-btn > .btn {\n height: 30px;\n padding: 5px 10px;\n font-size: 12px;\n line-height: 1.5;\n border-radius: 3px;\n}\nselect.input-group-sm > .form-control,\nselect.input-group-sm > .input-group-addon,\nselect.input-group-sm > .input-group-btn > .btn {\n height: 30px;\n line-height: 30px;\n}\ntextarea.input-group-sm > .form-control,\ntextarea.input-group-sm > .input-group-addon,\ntextarea.input-group-sm > .input-group-btn > .btn,\nselect[multiple].input-group-sm > .form-control,\nselect[multiple].input-group-sm > .input-group-addon,\nselect[multiple].input-group-sm > .input-group-btn > .btn {\n height: auto;\n}\n.input-group-addon,\n.input-group-btn,\n.input-group .form-control {\n display: table-cell;\n}\n.input-group-addon:not(:first-child):not(:last-child),\n.input-group-btn:not(:first-child):not(:last-child),\n.input-group .form-control:not(:first-child):not(:last-child) {\n border-radius: 0;\n}\n.input-group-addon,\n.input-group-btn {\n width: 1%;\n white-space: nowrap;\n vertical-align: middle;\n}\n.input-group-addon {\n padding: 6px 12px;\n font-size: 14px;\n font-weight: normal;\n line-height: 1;\n color: #555555;\n text-align: center;\n background-color: #eeeeee;\n border: 1px solid #cccccc;\n border-radius: 4px;\n}\n.input-group-addon.input-sm {\n padding: 5px 10px;\n font-size: 12px;\n border-radius: 3px;\n}\n.input-group-addon.input-lg {\n padding: 10px 16px;\n font-size: 18px;\n border-radius: 6px;\n}\n.input-group-addon input[type=\"radio\"],\n.input-group-addon input[type=\"checkbox\"] {\n margin-top: 0;\n}\n.input-group .form-control:first-child,\n.input-group-addon:first-child,\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group > .btn,\n.input-group-btn:first-child > .dropdown-toggle,\n.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle),\n.input-group-btn:last-child > .btn-group:not(:last-child) > .btn {\n border-bottom-right-radius: 0;\n border-top-right-radius: 0;\n}\n.input-group-addon:first-child {\n border-right: 0;\n}\n.input-group .form-control:last-child,\n.input-group-addon:last-child,\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group > .btn,\n.input-group-btn:last-child > .dropdown-toggle,\n.input-group-btn:first-child > .btn:not(:first-child),\n.input-group-btn:first-child > .btn-group:not(:first-child) > .btn {\n border-bottom-left-radius: 0;\n border-top-left-radius: 0;\n}\n.input-group-addon:last-child {\n border-left: 0;\n}\n.input-group-btn {\n position: relative;\n font-size: 0;\n white-space: nowrap;\n}\n.input-group-btn > .btn {\n position: relative;\n}\n.input-group-btn > .btn + .btn {\n margin-left: -1px;\n}\n.input-group-btn > .btn:hover,\n.input-group-btn > .btn:focus,\n.input-group-btn > .btn:active {\n z-index: 2;\n}\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group {\n margin-right: -1px;\n}\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group {\n margin-left: -1px;\n}\n.nav {\n margin-bottom: 0;\n padding-left: 0;\n list-style: none;\n}\n.nav > li {\n position: relative;\n display: block;\n}\n.nav > li > a {\n position: relative;\n display: block;\n padding: 10px 15px;\n}\n.nav > li > a:hover,\n.nav > li > a:focus {\n text-decoration: none;\n background-color: #eeeeee;\n}\n.nav > li.disabled > a {\n color: #777777;\n}\n.nav > li.disabled > a:hover,\n.nav > li.disabled > a:focus {\n color: #777777;\n text-decoration: none;\n background-color: transparent;\n cursor: not-allowed;\n}\n.nav .open > a,\n.nav .open > a:hover,\n.nav .open > a:focus {\n background-color: #eeeeee;\n border-color: #337ab7;\n}\n.nav .nav-divider {\n height: 1px;\n margin: 9px 0;\n overflow: hidden;\n background-color: #e5e5e5;\n}\n.nav > li > a > img {\n max-width: none;\n}\n.nav-tabs {\n border-bottom: 1px solid #dddddd;\n}\n.nav-tabs > li {\n float: left;\n margin-bottom: -1px;\n}\n.nav-tabs > li > a {\n margin-right: 2px;\n line-height: 1.42857143;\n border: 1px solid transparent;\n border-radius: 4px 4px 0 0;\n}\n.nav-tabs > li > a:hover {\n border-color: #eeeeee #eeeeee #dddddd;\n}\n.nav-tabs > li.active > a,\n.nav-tabs > li.active > a:hover,\n.nav-tabs > li.active > a:focus {\n color: #555555;\n background-color: #ffffff;\n border: 1px solid #dddddd;\n border-bottom-color: transparent;\n cursor: default;\n}\n.nav-tabs.nav-justified {\n width: 100%;\n border-bottom: 0;\n}\n.nav-tabs.nav-justified > li {\n float: none;\n}\n.nav-tabs.nav-justified > li > a {\n text-align: center;\n margin-bottom: 5px;\n}\n.nav-tabs.nav-justified > .dropdown .dropdown-menu {\n top: auto;\n left: auto;\n}\n@media (min-width: 768px) {\n .nav-tabs.nav-justified > li {\n display: table-cell;\n width: 1%;\n }\n .nav-tabs.nav-justified > li > a {\n margin-bottom: 0;\n }\n}\n.nav-tabs.nav-justified > li > a {\n margin-right: 0;\n border-radius: 4px;\n}\n.nav-tabs.nav-justified > .active > a,\n.nav-tabs.nav-justified > .active > a:hover,\n.nav-tabs.nav-justified > .active > a:focus {\n border: 1px solid #dddddd;\n}\n@media (min-width: 768px) {\n .nav-tabs.nav-justified > li > a {\n border-bottom: 1px solid #dddddd;\n border-radius: 4px 4px 0 0;\n }\n .nav-tabs.nav-justified > .active > a,\n .nav-tabs.nav-justified > .active > a:hover,\n .nav-tabs.nav-justified > .active > a:focus {\n border-bottom-color: #ffffff;\n }\n}\n.nav-pills > li {\n float: left;\n}\n.nav-pills > li > a {\n border-radius: 4px;\n}\n.nav-pills > li + li {\n margin-left: 2px;\n}\n.nav-pills > li.active > a,\n.nav-pills > li.active > a:hover,\n.nav-pills > li.active > a:focus {\n color: #ffffff;\n background-color: #337ab7;\n}\n.nav-stacked > li {\n float: none;\n}\n.nav-stacked > li + li {\n margin-top: 2px;\n margin-left: 0;\n}\n.nav-justified {\n width: 100%;\n}\n.nav-justified > li {\n float: none;\n}\n.nav-justified > li > a {\n text-align: center;\n margin-bottom: 5px;\n}\n.nav-justified > .dropdown .dropdown-menu {\n top: auto;\n left: auto;\n}\n@media (min-width: 768px) {\n .nav-justified > li {\n display: table-cell;\n width: 1%;\n }\n .nav-justified > li > a {\n margin-bottom: 0;\n }\n}\n.nav-tabs-justified {\n border-bottom: 0;\n}\n.nav-tabs-justified > li > a {\n margin-right: 0;\n border-radius: 4px;\n}\n.nav-tabs-justified > .active > a,\n.nav-tabs-justified > .active > a:hover,\n.nav-tabs-justified > .active > a:focus {\n border: 1px solid #dddddd;\n}\n@media (min-width: 768px) {\n .nav-tabs-justified > li > a {\n border-bottom: 1px solid #dddddd;\n border-radius: 4px 4px 0 0;\n }\n .nav-tabs-justified > .active > a,\n .nav-tabs-justified > .active > a:hover,\n .nav-tabs-justified > .active > a:focus {\n border-bottom-color: #ffffff;\n }\n}\n.tab-content > .tab-pane {\n display: none;\n visibility: hidden;\n}\n.tab-content > .active {\n display: block;\n visibility: visible;\n}\n.nav-tabs .dropdown-menu {\n margin-top: -1px;\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n}\n.navbar {\n position: relative;\n min-height: 50px;\n margin-bottom: 20px;\n border: 1px solid transparent;\n}\n@media (min-width: 768px) {\n .navbar {\n border-radius: 4px;\n }\n}\n@media (min-width: 768px) {\n .navbar-header {\n float: left;\n }\n}\n.navbar-collapse {\n overflow-x: visible;\n padding-right: 15px;\n padding-left: 15px;\n border-top: 1px solid transparent;\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);\n -webkit-overflow-scrolling: touch;\n}\n.navbar-collapse.in {\n overflow-y: auto;\n}\n@media (min-width: 768px) {\n .navbar-collapse {\n width: auto;\n border-top: 0;\n box-shadow: none;\n }\n .navbar-collapse.collapse {\n display: block !important;\n visibility: visible !important;\n height: auto !important;\n padding-bottom: 0;\n overflow: visible !important;\n }\n .navbar-collapse.in {\n overflow-y: visible;\n }\n .navbar-fixed-top .navbar-collapse,\n .navbar-static-top .navbar-collapse,\n .navbar-fixed-bottom .navbar-collapse {\n padding-left: 0;\n padding-right: 0;\n }\n}\n.navbar-fixed-top .navbar-collapse,\n.navbar-fixed-bottom .navbar-collapse {\n max-height: 340px;\n}\n@media (max-device-width: 480px) and (orientation: landscape) {\n .navbar-fixed-top .navbar-collapse,\n .navbar-fixed-bottom .navbar-collapse {\n max-height: 200px;\n }\n}\n.container > .navbar-header,\n.container-fluid > .navbar-header,\n.container > .navbar-collapse,\n.container-fluid > .navbar-collapse {\n margin-right: -15px;\n margin-left: -15px;\n}\n@media (min-width: 768px) {\n .container > .navbar-header,\n .container-fluid > .navbar-header,\n .container > .navbar-collapse,\n .container-fluid > .navbar-collapse {\n margin-right: 0;\n margin-left: 0;\n }\n}\n.navbar-static-top {\n z-index: 1000;\n border-width: 0 0 1px;\n}\n@media (min-width: 768px) {\n .navbar-static-top {\n border-radius: 0;\n }\n}\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n position: fixed;\n right: 0;\n left: 0;\n z-index: 1030;\n}\n@media (min-width: 768px) {\n .navbar-fixed-top,\n .navbar-fixed-bottom {\n border-radius: 0;\n }\n}\n.navbar-fixed-top {\n top: 0;\n border-width: 0 0 1px;\n}\n.navbar-fixed-bottom {\n bottom: 0;\n margin-bottom: 0;\n border-width: 1px 0 0;\n}\n.navbar-brand {\n float: left;\n padding: 15px 15px;\n font-size: 18px;\n line-height: 20px;\n height: 50px;\n}\n.navbar-brand:hover,\n.navbar-brand:focus {\n text-decoration: none;\n}\n.navbar-brand > img {\n display: block;\n}\n@media (min-width: 768px) {\n .navbar > .container .navbar-brand,\n .navbar > .container-fluid .navbar-brand {\n margin-left: -15px;\n }\n}\n.navbar-toggle {\n position: relative;\n float: right;\n margin-right: 15px;\n padding: 9px 10px;\n margin-top: 8px;\n margin-bottom: 8px;\n background-color: transparent;\n background-image: none;\n border: 1px solid transparent;\n border-radius: 4px;\n}\n.navbar-toggle:focus {\n outline: 0;\n}\n.navbar-toggle .icon-bar {\n display: block;\n width: 22px;\n height: 2px;\n border-radius: 1px;\n}\n.navbar-toggle .icon-bar + .icon-bar {\n margin-top: 4px;\n}\n@media (min-width: 768px) {\n .navbar-toggle {\n display: none;\n }\n}\n.navbar-nav {\n margin: 7.5px -15px;\n}\n.navbar-nav > li > a {\n padding-top: 10px;\n padding-bottom: 10px;\n line-height: 20px;\n}\n@media (max-width: 767px) {\n .navbar-nav .open .dropdown-menu {\n position: static;\n float: none;\n width: auto;\n margin-top: 0;\n background-color: transparent;\n border: 0;\n box-shadow: none;\n }\n .navbar-nav .open .dropdown-menu > li > a,\n .navbar-nav .open .dropdown-menu .dropdown-header {\n padding: 5px 15px 5px 25px;\n }\n .navbar-nav .open .dropdown-menu > li > a {\n line-height: 20px;\n }\n .navbar-nav .open .dropdown-menu > li > a:hover,\n .navbar-nav .open .dropdown-menu > li > a:focus {\n background-image: none;\n }\n}\n@media (min-width: 768px) {\n .navbar-nav {\n float: left;\n margin: 0;\n }\n .navbar-nav > li {\n float: left;\n }\n .navbar-nav > li > a {\n padding-top: 15px;\n padding-bottom: 15px;\n }\n}\n.navbar-form {\n margin-left: -15px;\n margin-right: -15px;\n padding: 10px 15px;\n border-top: 1px solid transparent;\n border-bottom: 1px solid transparent;\n -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);\n box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.1);\n margin-top: 8px;\n margin-bottom: 8px;\n}\n@media (min-width: 768px) {\n .navbar-form .form-group {\n display: inline-block;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .navbar-form .form-control {\n display: inline-block;\n width: auto;\n vertical-align: middle;\n }\n .navbar-form .form-control-static {\n display: inline-block;\n }\n .navbar-form .input-group {\n display: inline-table;\n vertical-align: middle;\n }\n .navbar-form .input-group .input-group-addon,\n .navbar-form .input-group .input-group-btn,\n .navbar-form .input-group .form-control {\n width: auto;\n }\n .navbar-form .input-group > .form-control {\n width: 100%;\n }\n .navbar-form .control-label {\n margin-bottom: 0;\n vertical-align: middle;\n }\n .navbar-form .radio,\n .navbar-form .checkbox {\n display: inline-block;\n margin-top: 0;\n margin-bottom: 0;\n vertical-align: middle;\n }\n .navbar-form .radio label,\n .navbar-form .checkbox label {\n padding-left: 0;\n }\n .navbar-form .radio input[type=\"radio\"],\n .navbar-form .checkbox input[type=\"checkbox\"] {\n position: relative;\n margin-left: 0;\n }\n .navbar-form .has-feedback .form-control-feedback {\n top: 0;\n }\n}\n@media (max-width: 767px) {\n .navbar-form .form-group {\n margin-bottom: 5px;\n }\n .navbar-form .form-group:last-child {\n margin-bottom: 0;\n }\n}\n@media (min-width: 768px) {\n .navbar-form {\n width: auto;\n border: 0;\n margin-left: 0;\n margin-right: 0;\n padding-top: 0;\n padding-bottom: 0;\n -webkit-box-shadow: none;\n box-shadow: none;\n }\n}\n.navbar-nav > li > .dropdown-menu {\n margin-top: 0;\n border-top-right-radius: 0;\n border-top-left-radius: 0;\n}\n.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {\n margin-bottom: 0;\n border-top-right-radius: 4px;\n border-top-left-radius: 4px;\n border-bottom-right-radius: 0;\n border-bottom-left-radius: 0;\n}\n.navbar-btn {\n margin-top: 8px;\n margin-bottom: 8px;\n}\n.navbar-btn.btn-sm {\n margin-top: 10px;\n margin-bottom: 10px;\n}\n.navbar-btn.btn-xs {\n margin-top: 14px;\n margin-bottom: 14px;\n}\n.navbar-text {\n margin-top: 15px;\n margin-bottom: 15px;\n}\n@media (min-width: 768px) {\n .navbar-text {\n float: left;\n margin-left: 15px;\n margin-right: 15px;\n }\n}\n@media (min-width: 768px) {\n .navbar-left {\n float: left !important;\n }\n .navbar-right {\n float: right !important;\n margin-right: -15px;\n }\n .navbar-right ~ .navbar-right {\n margin-right: 0;\n }\n}\n.navbar-default {\n background-color: #f8f8f8;\n border-color: #e7e7e7;\n}\n.navbar-default .navbar-brand {\n color: #777777;\n}\n.navbar-default .navbar-brand:hover,\n.navbar-default .navbar-brand:focus {\n color: #5e5e5e;\n background-color: transparent;\n}\n.navbar-default .navbar-text {\n color: #777777;\n}\n.navbar-default .navbar-nav > li > a {\n color: #777777;\n}\n.navbar-default .navbar-nav > li > a:hover,\n.navbar-default .navbar-nav > li > a:focus {\n color: #333333;\n background-color: transparent;\n}\n.navbar-default .navbar-nav > .active > a,\n.navbar-default .navbar-nav > .active > a:hover,\n.navbar-default .navbar-nav > .active > a:focus {\n color: #555555;\n background-color: #e7e7e7;\n}\n.navbar-default .navbar-nav > .disabled > a,\n.navbar-default .navbar-nav > .disabled > a:hover,\n.navbar-default .navbar-nav > .disabled > a:focus {\n color: #cccccc;\n background-color: transparent;\n}\n.navbar-default .navbar-toggle {\n border-color: #dddddd;\n}\n.navbar-default .navbar-toggle:hover,\n.navbar-default .navbar-toggle:focus {\n background-color: #dddddd;\n}\n.navbar-default .navbar-toggle .icon-bar {\n background-color: #888888;\n}\n.navbar-default .navbar-collapse,\n.navbar-default .navbar-form {\n border-color: #e7e7e7;\n}\n.navbar-default .navbar-nav > .open > a,\n.navbar-default .navbar-nav > .open > a:hover,\n.navbar-default .navbar-nav > .open > a:focus {\n background-color: #e7e7e7;\n color: #555555;\n}\n@media (max-width: 767px) {\n .navbar-default .navbar-nav .open .dropdown-menu > li > a {\n color: #777777;\n }\n .navbar-default .navbar-nav .open .dropdown-menu > li > a:hover,\n .navbar-default .navbar-nav .open .dropdown-menu > li > a:focus {\n color: #333333;\n background-color: transparent;\n }\n .navbar-default .navbar-nav .open .dropdown-menu > .active > a,\n .navbar-default .navbar-nav .open .dropdown-menu > .active > a:hover,\n .navbar-default .navbar-nav .open .dropdown-menu > .active > a:focus {\n color: #555555;\n background-color: #e7e7e7;\n }\n .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a,\n .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:hover,\n .navbar-default .navbar-nav .open .dropdown-menu > .disabled > a:focus {\n color: #cccccc;\n background-color: transparent;\n }\n}\n.navbar-default .navbar-link {\n color: #777777;\n}\n.navbar-default .navbar-link:hover {\n color: #333333;\n}\n.navbar-default .btn-link {\n color: #777777;\n}\n.navbar-default .btn-link:hover,\n.navbar-default .btn-link:focus {\n color: #333333;\n}\n.navbar-default .btn-link[disabled]:hover,\nfieldset[disabled] .navbar-default .btn-link:hover,\n.navbar-default .btn-link[disabled]:focus,\nfieldset[disabled] .navbar-default .btn-link:focus {\n color: #cccccc;\n}\n.navbar-inverse {\n background-color: #222222;\n border-color: #080808;\n}\n.navbar-inverse .navbar-brand {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-brand:hover,\n.navbar-inverse .navbar-brand:focus {\n color: #ffffff;\n background-color: transparent;\n}\n.navbar-inverse .navbar-text {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-nav > li > a {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-nav > li > a:hover,\n.navbar-inverse .navbar-nav > li > a:focus {\n color: #ffffff;\n background-color: transparent;\n}\n.navbar-inverse .navbar-nav > .active > a,\n.navbar-inverse .navbar-nav > .active > a:hover,\n.navbar-inverse .navbar-nav > .active > a:focus {\n color: #ffffff;\n background-color: #080808;\n}\n.navbar-inverse .navbar-nav > .disabled > a,\n.navbar-inverse .navbar-nav > .disabled > a:hover,\n.navbar-inverse .navbar-nav > .disabled > a:focus {\n color: #444444;\n background-color: transparent;\n}\n.navbar-inverse .navbar-toggle {\n border-color: #333333;\n}\n.navbar-inverse .navbar-toggle:hover,\n.navbar-inverse .navbar-toggle:focus {\n background-color: #333333;\n}\n.navbar-inverse .navbar-toggle .icon-bar {\n background-color: #ffffff;\n}\n.navbar-inverse .navbar-collapse,\n.navbar-inverse .navbar-form {\n border-color: #101010;\n}\n.navbar-inverse .navbar-nav > .open > a,\n.navbar-inverse .navbar-nav > .open > a:hover,\n.navbar-inverse .navbar-nav > .open > a:focus {\n background-color: #080808;\n color: #ffffff;\n}\n@media (max-width: 767px) {\n .navbar-inverse .navbar-nav .open .dropdown-menu > .dropdown-header {\n border-color: #080808;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu .divider {\n background-color: #080808;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > li > a {\n color: #9d9d9d;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:hover,\n .navbar-inverse .navbar-nav .open .dropdown-menu > li > a:focus {\n color: #ffffff;\n background-color: transparent;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:hover,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .active > a:focus {\n color: #ffffff;\n background-color: #080808;\n }\n .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:hover,\n .navbar-inverse .navbar-nav .open .dropdown-menu > .disabled > a:focus {\n color: #444444;\n background-color: transparent;\n }\n}\n.navbar-inverse .navbar-link {\n color: #9d9d9d;\n}\n.navbar-inverse .navbar-link:hover {\n color: #ffffff;\n}\n.navbar-inverse .btn-link {\n color: #9d9d9d;\n}\n.navbar-inverse .btn-link:hover,\n.navbar-inverse .btn-link:focus {\n color: #ffffff;\n}\n.navbar-inverse .btn-link[disabled]:hover,\nfieldset[disabled] .navbar-inverse .btn-link:hover,\n.navbar-inverse .btn-link[disabled]:focus,\nfieldset[disabled] .navbar-inverse .btn-link:focus {\n color: #444444;\n}\n.breadcrumb {\n padding: 8px 15px;\n margin-bottom: 20px;\n list-style: none;\n background-color: #f5f5f5;\n border-radius: 4px;\n}\n.breadcrumb > li {\n display: inline-block;\n}\n.breadcrumb > li + li:before {\n content: \"/\\00a0\";\n padding: 0 5px;\n color: #cccccc;\n}\n.breadcrumb > .active {\n color: #777777;\n}\n.pagination {\n display: inline-block;\n padding-left: 0;\n margin: 20px 0;\n border-radius: 4px;\n}\n.pagination > li {\n display: inline;\n}\n.pagination > li > a,\n.pagination > li > span {\n position: relative;\n float: left;\n padding: 6px 12px;\n line-height: 1.42857143;\n text-decoration: none;\n color: #337ab7;\n background-color: #ffffff;\n border: 1px solid #dddddd;\n margin-left: -1px;\n}\n.pagination > li:first-child > a,\n.pagination > li:first-child > span {\n margin-left: 0;\n border-bottom-left-radius: 4px;\n border-top-left-radius: 4px;\n}\n.pagination > li:last-child > a,\n.pagination > li:last-child > span {\n border-bottom-right-radius: 4px;\n border-top-right-radius: 4px;\n}\n.pagination > li > a:hover,\n.pagination > li > span:hover,\n.pagination > li > a:focus,\n.pagination > li > span:focus {\n color: #23527c;\n background-color: #eeeeee;\n border-color: #dddddd;\n}\n.pagination > .active > a,\n.pagination > .active > span,\n.pagination > .active > a:hover,\n.pagination > .active > span:hover,\n.pagination > .active > a:focus,\n.pagination > .active > span:focus {\n z-index: 2;\n color: #ffffff;\n background-color: #337ab7;\n border-color: #337ab7;\n cursor: default;\n}\n.pagination > .disabled > span,\n.pagination > .disabled > span:hover,\n.pagination > .disabled > span:focus,\n.pagination > .disabled > a,\n.pagination > .disabled > a:hover,\n.pagination > .disabled > a:focus {\n color: #777777;\n background-color: #ffffff;\n border-color: #dddddd;\n cursor: not-allowed;\n}\n.pagination-lg > li > a,\n.pagination-lg > li > span {\n padding: 10px 16px;\n font-size: 18px;\n}\n.pagination-lg > li:first-child > a,\n.pagination-lg > li:first-child > span {\n border-bottom-left-radius: 6px;\n border-top-left-radius: 6px;\n}\n.pagination-lg > li:last-child > a,\n.pagination-lg > li:last-child > span {\n border-bottom-right-radius: 6px;\n border-top-right-radius: 6px;\n}\n.pagination-sm > li > a,\n.pagination-sm > li > span {\n padding: 5px 10px;\n font-size: 12px;\n}\n.pagination-sm > li:first-child > a,\n.pagination-sm > li:first-child > span {\n border-bottom-left-radius: 3px;\n border-top-left-radius: 3px;\n}\n.pagination-sm > li:last-child > a,\n.pagination-sm > li:last-child > span {\n border-bottom-right-radius: 3px;\n border-top-right-radius: 3px;\n}\n.pager {\n padding-left: 0;\n margin: 20px 0;\n list-style: none;\n text-align: center;\n}\n.pager li {\n display: inline;\n}\n.pager li > a,\n.pager li > span {\n display: inline-block;\n padding: 5px 14px;\n background-color: #ffffff;\n border: 1px solid #dddddd;\n border-radius: 15px;\n}\n.pager li > a:hover,\n.pager li > a:focus {\n text-decoration: none;\n background-color: #eeeeee;\n}\n.pager .next > a,\n.pager .next > span {\n float: right;\n}\n.pager .previous > a,\n.pager .previous > span {\n float: left;\n}\n.pager .disabled > a,\n.pager .disabled > a:hover,\n.pager .disabled > a:focus,\n.pager .disabled > span {\n color: #777777;\n background-color: #ffffff;\n cursor: not-allowed;\n}\n.label {\n display: inline;\n padding: .2em .6em .3em;\n font-size: 75%;\n font-weight: bold;\n line-height: 1;\n color: #ffffff;\n text-align: center;\n white-space: nowrap;\n vertical-align: baseline;\n border-radius: .25em;\n}\na.label:hover,\na.label:focus {\n color: #ffffff;\n text-decoration: none;\n cursor: pointer;\n}\n.label:empty {\n display: none;\n}\n.btn .label {\n position: relative;\n top: -1px;\n}\n.label-default {\n background-color: #777777;\n}\n.label-default[href]:hover,\n.label-default[href]:focus {\n background-color: #5e5e5e;\n}\n.label-primary {\n background-color: #337ab7;\n}\n.label-primary[href]:hover,\n.label-primary[href]:focus {\n background-color: #286090;\n}\n.label-success {\n background-color: #5cb85c;\n}\n.label-success[href]:hover,\n.label-success[href]:focus {\n background-color: #449d44;\n}\n.label-info {\n background-color: #5bc0de;\n}\n.label-info[href]:hover,\n.label-info[href]:focus {\n background-color: #31b0d5;\n}\n.label-warning {\n background-color: #f0ad4e;\n}\n.label-warning[href]:hover,\n.label-warning[href]:focus {\n background-color: #ec971f;\n}\n.label-danger {\n background-color: #d9534f;\n}\n.label-danger[href]:hover,\n.label-danger[href]:focus {\n background-color: #c9302c;\n}\n.badge {\n display: inline-block;\n min-width: 10px;\n padding: 3px 7px;\n font-size: 12px;\n font-weight: bold;\n color: #ffffff;\n line-height: 1;\n vertical-align: baseline;\n white-space: nowrap;\n text-align: center;\n background-color: #777777;\n border-radius: 10px;\n}\n.badge:empty {\n display: none;\n}\n.btn .badge {\n position: relative;\n top: -1px;\n}\n.btn-xs .badge {\n top: 0;\n padding: 1px 5px;\n}\na.badge:hover,\na.badge:focus {\n color: #ffffff;\n text-decoration: none;\n cursor: pointer;\n}\n.list-group-item.active > .badge,\n.nav-pills > .active > a > .badge {\n color: #337ab7;\n background-color: #ffffff;\n}\n.list-group-item > .badge {\n float: right;\n}\n.list-group-item > .badge + .badge {\n margin-right: 5px;\n}\n.nav-pills > li > a > .badge {\n margin-left: 3px;\n}\n.jumbotron {\n padding: 30px 15px;\n margin-bottom: 30px;\n color: inherit;\n background-color: #eeeeee;\n}\n.jumbotron h1,\n.jumbotron .h1 {\n color: inherit;\n}\n.jumbotron p {\n margin-bottom: 15px;\n font-size: 21px;\n font-weight: 200;\n}\n.jumbotron > hr {\n border-top-color: #d5d5d5;\n}\n.container .jumbotron,\n.container-fluid .jumbotron {\n border-radius: 6px;\n}\n.jumbotron .container {\n max-width: 100%;\n}\n@media screen and (min-width: 768px) {\n .jumbotron {\n padding: 48px 0;\n }\n .container .jumbotron,\n .container-fluid .jumbotron {\n padding-left: 60px;\n padding-right: 60px;\n }\n .jumbotron h1,\n .jumbotron .h1 {\n font-size: 63px;\n }\n}\n.thumbnail {\n display: block;\n padding: 4px;\n margin-bottom: 20px;\n line-height: 1.42857143;\n background-color: #ffffff;\n border: 1px solid #dddddd;\n border-radius: 4px;\n -webkit-transition: border 0.2s ease-in-out;\n -o-transition: border 0.2s ease-in-out;\n transition: border 0.2s ease-in-out;\n}\n.thumbnail > img,\n.thumbnail a > img {\n margin-left: auto;\n margin-right: auto;\n}\na.thumbnail:hover,\na.thumbnail:focus,\na.thumbnail.active {\n border-color: #337ab7;\n}\n.thumbnail .caption {\n padding: 9px;\n color: #333333;\n}\n.alert {\n padding: 15px;\n margin-bottom: 20px;\n border: 1px solid transparent;\n border-radius: 4px;\n}\n.alert h4 {\n margin-top: 0;\n color: inherit;\n}\n.alert .alert-link {\n font-weight: bold;\n}\n.alert > p,\n.alert > ul {\n margin-bottom: 0;\n}\n.alert > p + p {\n margin-top: 5px;\n}\n.alert-dismissable,\n.alert-dismissible {\n padding-right: 35px;\n}\n.alert-dismissable .close,\n.alert-dismissible .close {\n position: relative;\n top: -2px;\n right: -21px;\n color: inherit;\n}\n.alert-success {\n background-color: #dff0d8;\n border-color: #d6e9c6;\n color: #3c763d;\n}\n.alert-success hr {\n border-top-color: #c9e2b3;\n}\n.alert-success .alert-link {\n color: #2b542c;\n}\n.alert-info {\n background-color: #d9edf7;\n border-color: #bce8f1;\n color: #31708f;\n}\n.alert-info hr {\n border-top-color: #a6e1ec;\n}\n.alert-info .alert-link {\n color: #245269;\n}\n.alert-warning {\n background-color: #fcf8e3;\n border-color: #faebcc;\n color: #8a6d3b;\n}\n.alert-warning hr {\n border-top-color: #f7e1b5;\n}\n.alert-warning .alert-link {\n color: #66512c;\n}\n.alert-danger {\n background-color: #f2dede;\n border-color: #ebccd1;\n color: #a94442;\n}\n.alert-danger hr {\n border-top-color: #e4b9c0;\n}\n.alert-danger .alert-link {\n color: #843534;\n}\n@-webkit-keyframes progress-bar-stripes {\n from {\n background-position: 40px 0;\n }\n to {\n background-position: 0 0;\n }\n}\n@keyframes progress-bar-stripes {\n from {\n background-position: 40px 0;\n }\n to {\n background-position: 0 0;\n }\n}\n.progress {\n overflow: hidden;\n height: 20px;\n margin-bottom: 20px;\n background-color: #f5f5f5;\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);\n box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);\n}\n.progress-bar {\n float: left;\n width: 0%;\n height: 100%;\n font-size: 12px;\n line-height: 20px;\n color: #ffffff;\n text-align: center;\n background-color: #337ab7;\n -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);\n box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);\n -webkit-transition: width 0.6s ease;\n -o-transition: width 0.6s ease;\n transition: width 0.6s ease;\n}\n.progress-striped .progress-bar,\n.progress-bar-striped {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-size: 40px 40px;\n}\n.progress.active .progress-bar,\n.progress-bar.active {\n -webkit-animation: progress-bar-stripes 2s linear infinite;\n -o-animation: progress-bar-stripes 2s linear infinite;\n animation: progress-bar-stripes 2s linear infinite;\n}\n.progress-bar-success {\n background-color: #5cb85c;\n}\n.progress-striped .progress-bar-success {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.progress-bar-info {\n background-color: #5bc0de;\n}\n.progress-striped .progress-bar-info {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.progress-bar-warning {\n background-color: #f0ad4e;\n}\n.progress-striped .progress-bar-warning {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.progress-bar-danger {\n background-color: #d9534f;\n}\n.progress-striped .progress-bar-danger {\n background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);\n}\n.media {\n margin-top: 15px;\n}\n.media:first-child {\n margin-top: 0;\n}\n.media,\n.media-body {\n zoom: 1;\n overflow: hidden;\n}\n.media-body {\n width: 10000px;\n}\n.media-object {\n display: block;\n}\n.media-right,\n.media > .pull-right {\n padding-left: 10px;\n}\n.media-left,\n.media > .pull-left {\n padding-right: 10px;\n}\n.media-left,\n.media-right,\n.media-body {\n display: table-cell;\n vertical-align: top;\n}\n.media-middle {\n vertical-align: middle;\n}\n.media-bottom {\n vertical-align: bottom;\n}\n.media-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n.media-list {\n padding-left: 0;\n list-style: none;\n}\n.list-group {\n margin-bottom: 20px;\n padding-left: 0;\n}\n.list-group-item {\n position: relative;\n display: block;\n padding: 10px 15px;\n margin-bottom: -1px;\n background-color: #ffffff;\n border: 1px solid #dddddd;\n}\n.list-group-item:first-child {\n border-top-right-radius: 4px;\n border-top-left-radius: 4px;\n}\n.list-group-item:last-child {\n margin-bottom: 0;\n border-bottom-right-radius: 4px;\n border-bottom-left-radius: 4px;\n}\na.list-group-item {\n color: #555555;\n}\na.list-group-item .list-group-item-heading {\n color: #333333;\n}\na.list-group-item:hover,\na.list-group-item:focus {\n text-decoration: none;\n color: #555555;\n background-color: #f5f5f5;\n}\n.list-group-item.disabled,\n.list-group-item.disabled:hover,\n.list-group-item.disabled:focus {\n background-color: #eeeeee;\n color: #777777;\n cursor: not-allowed;\n}\n.list-group-item.disabled .list-group-item-heading,\n.list-group-item.disabled:hover .list-group-item-heading,\n.list-group-item.disabled:focus .list-group-item-heading {\n color: inherit;\n}\n.list-group-item.disabled .list-group-item-text,\n.list-group-item.disabled:hover .list-group-item-text,\n.list-group-item.disabled:focus .list-group-item-text {\n color: #777777;\n}\n.list-group-item.active,\n.list-group-item.active:hover,\n.list-group-item.active:focus {\n z-index: 2;\n color: #ffffff;\n background-color: #337ab7;\n border-color: #337ab7;\n}\n.list-group-item.active .list-group-item-heading,\n.list-group-item.active:hover .list-group-item-heading,\n.list-group-item.active:focus .list-group-item-heading,\n.list-group-item.active .list-group-item-heading > small,\n.list-group-item.active:hover .list-group-item-heading > small,\n.list-group-item.active:focus .list-group-item-heading > small,\n.list-group-item.active .list-group-item-heading > .small,\n.list-group-item.active:hover .list-group-item-heading > .small,\n.list-group-item.active:focus .list-group-item-heading > .small {\n color: inherit;\n}\n.list-group-item.active .list-group-item-text,\n.list-group-item.active:hover .list-group-item-text,\n.list-group-item.active:focus .list-group-item-text {\n color: #c7ddef;\n}\n.list-group-item-success {\n color: #3c763d;\n background-color: #dff0d8;\n}\na.list-group-item-success {\n color: #3c763d;\n}\na.list-group-item-success .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-success:hover,\na.list-group-item-success:focus {\n color: #3c763d;\n background-color: #d0e9c6;\n}\na.list-group-item-success.active,\na.list-group-item-success.active:hover,\na.list-group-item-success.active:focus {\n color: #fff;\n background-color: #3c763d;\n border-color: #3c763d;\n}\n.list-group-item-info {\n color: #31708f;\n background-color: #d9edf7;\n}\na.list-group-item-info {\n color: #31708f;\n}\na.list-group-item-info .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-info:hover,\na.list-group-item-info:focus {\n color: #31708f;\n background-color: #c4e3f3;\n}\na.list-group-item-info.active,\na.list-group-item-info.active:hover,\na.list-group-item-info.active:focus {\n color: #fff;\n background-color: #31708f;\n border-color: #31708f;\n}\n.list-group-item-warning {\n color: #8a6d3b;\n background-color: #fcf8e3;\n}\na.list-group-item-warning {\n color: #8a6d3b;\n}\na.list-group-item-warning .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-warning:hover,\na.list-group-item-warning:focus {\n color: #8a6d3b;\n background-color: #faf2cc;\n}\na.list-group-item-warning.active,\na.list-group-item-warning.active:hover,\na.list-group-item-warning.active:focus {\n color: #fff;\n background-color: #8a6d3b;\n border-color: #8a6d3b;\n}\n.list-group-item-danger {\n color: #a94442;\n background-color: #f2dede;\n}\na.list-group-item-danger {\n color: #a94442;\n}\na.list-group-item-danger .list-group-item-heading {\n color: inherit;\n}\na.list-group-item-danger:hover,\na.list-group-item-danger:focus {\n color: #a94442;\n background-color: #ebcccc;\n}\na.list-group-item-danger.active,\na.list-group-item-danger.active:hover,\na.list-group-item-danger.active:focus {\n color: #fff;\n background-color: #a94442;\n border-color: #a94442;\n}\n.list-group-item-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n.list-group-item-text {\n margin-bottom: 0;\n line-height: 1.3;\n}\n.panel {\n margin-bottom: 20px;\n background-color: #ffffff;\n border: 1px solid transparent;\n border-radius: 4px;\n -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);\n box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);\n}\n.panel-body {\n padding: 15px;\n}\n.panel-heading {\n padding: 10px 15px;\n border-bottom: 1px solid transparent;\n border-top-right-radius: 3px;\n border-top-left-radius: 3px;\n}\n.panel-heading > .dropdown .dropdown-toggle {\n color: inherit;\n}\n.panel-title {\n margin-top: 0;\n margin-bottom: 0;\n font-size: 16px;\n color: inherit;\n}\n.panel-title > a,\n.panel-title > small,\n.panel-title > .small,\n.panel-title > small > a,\n.panel-title > .small > a {\n color: inherit;\n}\n.panel-footer {\n padding: 10px 15px;\n background-color: #f5f5f5;\n border-top: 1px solid #dddddd;\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.panel > .list-group,\n.panel > .panel-collapse > .list-group {\n margin-bottom: 0;\n}\n.panel > .list-group .list-group-item,\n.panel > .panel-collapse > .list-group .list-group-item {\n border-width: 1px 0;\n border-radius: 0;\n}\n.panel > .list-group:first-child .list-group-item:first-child,\n.panel > .panel-collapse > .list-group:first-child .list-group-item:first-child {\n border-top: 0;\n border-top-right-radius: 3px;\n border-top-left-radius: 3px;\n}\n.panel > .list-group:last-child .list-group-item:last-child,\n.panel > .panel-collapse > .list-group:last-child .list-group-item:last-child {\n border-bottom: 0;\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.panel-heading + .list-group .list-group-item:first-child {\n border-top-width: 0;\n}\n.list-group + .panel-footer {\n border-top-width: 0;\n}\n.panel > .table,\n.panel > .table-responsive > .table,\n.panel > .panel-collapse > .table {\n margin-bottom: 0;\n}\n.panel > .table caption,\n.panel > .table-responsive > .table caption,\n.panel > .panel-collapse > .table caption {\n padding-left: 15px;\n padding-right: 15px;\n}\n.panel > .table:first-child,\n.panel > .table-responsive:first-child > .table:first-child {\n border-top-right-radius: 3px;\n border-top-left-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child {\n border-top-left-radius: 3px;\n border-top-right-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child td:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child td:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:first-child,\n.panel > .table:first-child > thead:first-child > tr:first-child th:first-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:first-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child th:first-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:first-child {\n border-top-left-radius: 3px;\n}\n.panel > .table:first-child > thead:first-child > tr:first-child td:last-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child td:last-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child td:last-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child td:last-child,\n.panel > .table:first-child > thead:first-child > tr:first-child th:last-child,\n.panel > .table-responsive:first-child > .table:first-child > thead:first-child > tr:first-child th:last-child,\n.panel > .table:first-child > tbody:first-child > tr:first-child th:last-child,\n.panel > .table-responsive:first-child > .table:first-child > tbody:first-child > tr:first-child th:last-child {\n border-top-right-radius: 3px;\n}\n.panel > .table:last-child,\n.panel > .table-responsive:last-child > .table:last-child {\n border-bottom-right-radius: 3px;\n border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child {\n border-bottom-left-radius: 3px;\n border-bottom-right-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child td:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:first-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child td:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:first-child,\n.panel > .table:last-child > tbody:last-child > tr:last-child th:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:first-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child th:first-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:first-child {\n border-bottom-left-radius: 3px;\n}\n.panel > .table:last-child > tbody:last-child > tr:last-child td:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child td:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child td:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child td:last-child,\n.panel > .table:last-child > tbody:last-child > tr:last-child th:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tbody:last-child > tr:last-child th:last-child,\n.panel > .table:last-child > tfoot:last-child > tr:last-child th:last-child,\n.panel > .table-responsive:last-child > .table:last-child > tfoot:last-child > tr:last-child th:last-child {\n border-bottom-right-radius: 3px;\n}\n.panel > .panel-body + .table,\n.panel > .panel-body + .table-responsive,\n.panel > .table + .panel-body,\n.panel > .table-responsive + .panel-body {\n border-top: 1px solid #dddddd;\n}\n.panel > .table > tbody:first-child > tr:first-child th,\n.panel > .table > tbody:first-child > tr:first-child td {\n border-top: 0;\n}\n.panel > .table-bordered,\n.panel > .table-responsive > .table-bordered {\n border: 0;\n}\n.panel > .table-bordered > thead > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > thead > tr > th:first-child,\n.panel > .table-bordered > tbody > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > th:first-child,\n.panel > .table-bordered > tfoot > tr > th:first-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > th:first-child,\n.panel > .table-bordered > thead > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > thead > tr > td:first-child,\n.panel > .table-bordered > tbody > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > td:first-child,\n.panel > .table-bordered > tfoot > tr > td:first-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > td:first-child {\n border-left: 0;\n}\n.panel > .table-bordered > thead > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > thead > tr > th:last-child,\n.panel > .table-bordered > tbody > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > th:last-child,\n.panel > .table-bordered > tfoot > tr > th:last-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > th:last-child,\n.panel > .table-bordered > thead > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > thead > tr > td:last-child,\n.panel > .table-bordered > tbody > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > tbody > tr > td:last-child,\n.panel > .table-bordered > tfoot > tr > td:last-child,\n.panel > .table-responsive > .table-bordered > tfoot > tr > td:last-child {\n border-right: 0;\n}\n.panel > .table-bordered > thead > tr:first-child > td,\n.panel > .table-responsive > .table-bordered > thead > tr:first-child > td,\n.panel > .table-bordered > tbody > tr:first-child > td,\n.panel > .table-responsive > .table-bordered > tbody > tr:first-child > td,\n.panel > .table-bordered > thead > tr:first-child > th,\n.panel > .table-responsive > .table-bordered > thead > tr:first-child > th,\n.panel > .table-bordered > tbody > tr:first-child > th,\n.panel > .table-responsive > .table-bordered > tbody > tr:first-child > th {\n border-bottom: 0;\n}\n.panel > .table-bordered > tbody > tr:last-child > td,\n.panel > .table-responsive > .table-bordered > tbody > tr:last-child > td,\n.panel > .table-bordered > tfoot > tr:last-child > td,\n.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > td,\n.panel > .table-bordered > tbody > tr:last-child > th,\n.panel > .table-responsive > .table-bordered > tbody > tr:last-child > th,\n.panel > .table-bordered > tfoot > tr:last-child > th,\n.panel > .table-responsive > .table-bordered > tfoot > tr:last-child > th {\n border-bottom: 0;\n}\n.panel > .table-responsive {\n border: 0;\n margin-bottom: 0;\n}\n.panel-group {\n margin-bottom: 20px;\n}\n.panel-group .panel {\n margin-bottom: 0;\n border-radius: 4px;\n}\n.panel-group .panel + .panel {\n margin-top: 5px;\n}\n.panel-group .panel-heading {\n border-bottom: 0;\n}\n.panel-group .panel-heading + .panel-collapse > .panel-body,\n.panel-group .panel-heading + .panel-collapse > .list-group {\n border-top: 1px solid #dddddd;\n}\n.panel-group .panel-footer {\n border-top: 0;\n}\n.panel-group .panel-footer + .panel-collapse .panel-body {\n border-bottom: 1px solid #dddddd;\n}\n.panel-default {\n border-color: #dddddd;\n}\n.panel-default > .panel-heading {\n color: #333333;\n background-color: #f5f5f5;\n border-color: #dddddd;\n}\n.panel-default > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #dddddd;\n}\n.panel-default > .panel-heading .badge {\n color: #f5f5f5;\n background-color: #333333;\n}\n.panel-default > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #dddddd;\n}\n.panel-primary {\n border-color: #337ab7;\n}\n.panel-primary > .panel-heading {\n color: #ffffff;\n background-color: #337ab7;\n border-color: #337ab7;\n}\n.panel-primary > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #337ab7;\n}\n.panel-primary > .panel-heading .badge {\n color: #337ab7;\n background-color: #ffffff;\n}\n.panel-primary > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #337ab7;\n}\n.panel-success {\n border-color: #d6e9c6;\n}\n.panel-success > .panel-heading {\n color: #3c763d;\n background-color: #dff0d8;\n border-color: #d6e9c6;\n}\n.panel-success > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #d6e9c6;\n}\n.panel-success > .panel-heading .badge {\n color: #dff0d8;\n background-color: #3c763d;\n}\n.panel-success > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #d6e9c6;\n}\n.panel-info {\n border-color: #bce8f1;\n}\n.panel-info > .panel-heading {\n color: #31708f;\n background-color: #d9edf7;\n border-color: #bce8f1;\n}\n.panel-info > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #bce8f1;\n}\n.panel-info > .panel-heading .badge {\n color: #d9edf7;\n background-color: #31708f;\n}\n.panel-info > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #bce8f1;\n}\n.panel-warning {\n border-color: #faebcc;\n}\n.panel-warning > .panel-heading {\n color: #8a6d3b;\n background-color: #fcf8e3;\n border-color: #faebcc;\n}\n.panel-warning > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #faebcc;\n}\n.panel-warning > .panel-heading .badge {\n color: #fcf8e3;\n background-color: #8a6d3b;\n}\n.panel-warning > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #faebcc;\n}\n.panel-danger {\n border-color: #ebccd1;\n}\n.panel-danger > .panel-heading {\n color: #a94442;\n background-color: #f2dede;\n border-color: #ebccd1;\n}\n.panel-danger > .panel-heading + .panel-collapse > .panel-body {\n border-top-color: #ebccd1;\n}\n.panel-danger > .panel-heading .badge {\n color: #f2dede;\n background-color: #a94442;\n}\n.panel-danger > .panel-footer + .panel-collapse > .panel-body {\n border-bottom-color: #ebccd1;\n}\n.embed-responsive {\n position: relative;\n display: block;\n height: 0;\n padding: 0;\n overflow: hidden;\n}\n.embed-responsive .embed-responsive-item,\n.embed-responsive iframe,\n.embed-responsive embed,\n.embed-responsive object,\n.embed-responsive video {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n height: 100%;\n width: 100%;\n border: 0;\n}\n.embed-responsive.embed-responsive-16by9 {\n padding-bottom: 56.25%;\n}\n.embed-responsive.embed-responsive-4by3 {\n padding-bottom: 75%;\n}\n.well {\n min-height: 20px;\n padding: 19px;\n margin-bottom: 20px;\n background-color: #f5f5f5;\n border: 1px solid #e3e3e3;\n border-radius: 4px;\n -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);\n box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);\n}\n.well blockquote {\n border-color: #ddd;\n border-color: rgba(0, 0, 0, 0.15);\n}\n.well-lg {\n padding: 24px;\n border-radius: 6px;\n}\n.well-sm {\n padding: 9px;\n border-radius: 3px;\n}\n.close {\n float: right;\n font-size: 21px;\n font-weight: bold;\n line-height: 1;\n color: #000000;\n text-shadow: 0 1px 0 #ffffff;\n opacity: 0.2;\n filter: alpha(opacity=20);\n}\n.close:hover,\n.close:focus {\n color: #000000;\n text-decoration: none;\n cursor: pointer;\n opacity: 0.5;\n filter: alpha(opacity=50);\n}\nbutton.close {\n padding: 0;\n cursor: pointer;\n background: transparent;\n border: 0;\n -webkit-appearance: none;\n}\n.modal-open {\n overflow: hidden;\n}\n.modal {\n display: none;\n overflow: hidden;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: 1040;\n -webkit-overflow-scrolling: touch;\n outline: 0;\n}\n.modal.fade .modal-dialog {\n -webkit-transform: translate(0, -25%);\n -ms-transform: translate(0, -25%);\n -o-transform: translate(0, -25%);\n transform: translate(0, -25%);\n -webkit-transition: -webkit-transform 0.3s ease-out;\n -moz-transition: -moz-transform 0.3s ease-out;\n -o-transition: -o-transform 0.3s ease-out;\n transition: transform 0.3s ease-out;\n}\n.modal.in .modal-dialog {\n -webkit-transform: translate(0, 0);\n -ms-transform: translate(0, 0);\n -o-transform: translate(0, 0);\n transform: translate(0, 0);\n}\n.modal-open .modal {\n overflow-x: hidden;\n overflow-y: auto;\n}\n.modal-dialog {\n position: relative;\n width: auto;\n margin: 10px;\n}\n.modal-content {\n position: relative;\n background-color: #ffffff;\n border: 1px solid #999999;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 6px;\n -webkit-box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);\n box-shadow: 0 3px 9px rgba(0, 0, 0, 0.5);\n background-clip: padding-box;\n outline: 0;\n}\n.modal-backdrop {\n position: absolute;\n top: 0;\n right: 0;\n left: 0;\n background-color: #000000;\n}\n.modal-backdrop.fade {\n opacity: 0;\n filter: alpha(opacity=0);\n}\n.modal-backdrop.in {\n opacity: 0.5;\n filter: alpha(opacity=50);\n}\n.modal-header {\n padding: 15px;\n border-bottom: 1px solid #e5e5e5;\n min-height: 16.42857143px;\n}\n.modal-header .close {\n margin-top: -2px;\n}\n.modal-title {\n margin: 0;\n line-height: 1.42857143;\n}\n.modal-body {\n position: relative;\n padding: 15px;\n}\n.modal-footer {\n padding: 15px;\n text-align: right;\n border-top: 1px solid #e5e5e5;\n}\n.modal-footer .btn + .btn {\n margin-left: 5px;\n margin-bottom: 0;\n}\n.modal-footer .btn-group .btn + .btn {\n margin-left: -1px;\n}\n.modal-footer .btn-block + .btn-block {\n margin-left: 0;\n}\n.modal-scrollbar-measure {\n position: absolute;\n top: -9999px;\n width: 50px;\n height: 50px;\n overflow: scroll;\n}\n@media (min-width: 768px) {\n .modal-dialog {\n width: 600px;\n margin: 30px auto;\n }\n .modal-content {\n -webkit-box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);\n box-shadow: 0 5px 15px rgba(0, 0, 0, 0.5);\n }\n .modal-sm {\n width: 300px;\n }\n}\n@media (min-width: 992px) {\n .modal-lg {\n width: 900px;\n }\n}\n.tooltip {\n position: absolute;\n z-index: 1070;\n display: block;\n visibility: visible;\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n font-size: 12px;\n font-weight: normal;\n line-height: 1.4;\n opacity: 0;\n filter: alpha(opacity=0);\n}\n.tooltip.in {\n opacity: 0.9;\n filter: alpha(opacity=90);\n}\n.tooltip.top {\n margin-top: -3px;\n padding: 5px 0;\n}\n.tooltip.right {\n margin-left: 3px;\n padding: 0 5px;\n}\n.tooltip.bottom {\n margin-top: 3px;\n padding: 5px 0;\n}\n.tooltip.left {\n margin-left: -3px;\n padding: 0 5px;\n}\n.tooltip-inner {\n max-width: 200px;\n padding: 3px 8px;\n color: #ffffff;\n text-align: center;\n text-decoration: none;\n background-color: #000000;\n border-radius: 4px;\n}\n.tooltip-arrow {\n position: absolute;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n}\n.tooltip.top .tooltip-arrow {\n bottom: 0;\n left: 50%;\n margin-left: -5px;\n border-width: 5px 5px 0;\n border-top-color: #000000;\n}\n.tooltip.top-left .tooltip-arrow {\n bottom: 0;\n right: 5px;\n margin-bottom: -5px;\n border-width: 5px 5px 0;\n border-top-color: #000000;\n}\n.tooltip.top-right .tooltip-arrow {\n bottom: 0;\n left: 5px;\n margin-bottom: -5px;\n border-width: 5px 5px 0;\n border-top-color: #000000;\n}\n.tooltip.right .tooltip-arrow {\n top: 50%;\n left: 0;\n margin-top: -5px;\n border-width: 5px 5px 5px 0;\n border-right-color: #000000;\n}\n.tooltip.left .tooltip-arrow {\n top: 50%;\n right: 0;\n margin-top: -5px;\n border-width: 5px 0 5px 5px;\n border-left-color: #000000;\n}\n.tooltip.bottom .tooltip-arrow {\n top: 0;\n left: 50%;\n margin-left: -5px;\n border-width: 0 5px 5px;\n border-bottom-color: #000000;\n}\n.tooltip.bottom-left .tooltip-arrow {\n top: 0;\n right: 5px;\n margin-top: -5px;\n border-width: 0 5px 5px;\n border-bottom-color: #000000;\n}\n.tooltip.bottom-right .tooltip-arrow {\n top: 0;\n left: 5px;\n margin-top: -5px;\n border-width: 0 5px 5px;\n border-bottom-color: #000000;\n}\n.popover {\n position: absolute;\n top: 0;\n left: 0;\n z-index: 1060;\n display: none;\n max-width: 276px;\n padding: 1px;\n font-family: \"Helvetica Neue\", Helvetica, Arial, sans-serif;\n font-size: 14px;\n font-weight: normal;\n line-height: 1.42857143;\n text-align: left;\n background-color: #ffffff;\n background-clip: padding-box;\n border: 1px solid #cccccc;\n border: 1px solid rgba(0, 0, 0, 0.2);\n border-radius: 6px;\n -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);\n box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);\n white-space: normal;\n}\n.popover.top {\n margin-top: -10px;\n}\n.popover.right {\n margin-left: 10px;\n}\n.popover.bottom {\n margin-top: 10px;\n}\n.popover.left {\n margin-left: -10px;\n}\n.popover-title {\n margin: 0;\n padding: 8px 14px;\n font-size: 14px;\n background-color: #f7f7f7;\n border-bottom: 1px solid #ebebeb;\n border-radius: 5px 5px 0 0;\n}\n.popover-content {\n padding: 9px 14px;\n}\n.popover > .arrow,\n.popover > .arrow:after {\n position: absolute;\n display: block;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n}\n.popover > .arrow {\n border-width: 11px;\n}\n.popover > .arrow:after {\n border-width: 10px;\n content: \"\";\n}\n.popover.top > .arrow {\n left: 50%;\n margin-left: -11px;\n border-bottom-width: 0;\n border-top-color: #999999;\n border-top-color: rgba(0, 0, 0, 0.25);\n bottom: -11px;\n}\n.popover.top > .arrow:after {\n content: \" \";\n bottom: 1px;\n margin-left: -10px;\n border-bottom-width: 0;\n border-top-color: #ffffff;\n}\n.popover.right > .arrow {\n top: 50%;\n left: -11px;\n margin-top: -11px;\n border-left-width: 0;\n border-right-color: #999999;\n border-right-color: rgba(0, 0, 0, 0.25);\n}\n.popover.right > .arrow:after {\n content: \" \";\n left: 1px;\n bottom: -10px;\n border-left-width: 0;\n border-right-color: #ffffff;\n}\n.popover.bottom > .arrow {\n left: 50%;\n margin-left: -11px;\n border-top-width: 0;\n border-bottom-color: #999999;\n border-bottom-color: rgba(0, 0, 0, 0.25);\n top: -11px;\n}\n.popover.bottom > .arrow:after {\n content: \" \";\n top: 1px;\n margin-left: -10px;\n border-top-width: 0;\n border-bottom-color: #ffffff;\n}\n.popover.left > .arrow {\n top: 50%;\n right: -11px;\n margin-top: -11px;\n border-right-width: 0;\n border-left-color: #999999;\n border-left-color: rgba(0, 0, 0, 0.25);\n}\n.popover.left > .arrow:after {\n content: \" \";\n right: 1px;\n border-right-width: 0;\n border-left-color: #ffffff;\n bottom: -10px;\n}\n.carousel {\n position: relative;\n}\n.carousel-inner {\n position: relative;\n overflow: hidden;\n width: 100%;\n}\n.carousel-inner > .item {\n display: none;\n position: relative;\n -webkit-transition: 0.6s ease-in-out left;\n -o-transition: 0.6s ease-in-out left;\n transition: 0.6s ease-in-out left;\n}\n.carousel-inner > .item > img,\n.carousel-inner > .item > a > img {\n line-height: 1;\n}\n@media all and (transform-3d), (-webkit-transform-3d) {\n .carousel-inner > .item {\n -webkit-transition: -webkit-transform 0.6s ease-in-out;\n -moz-transition: -moz-transform 0.6s ease-in-out;\n -o-transition: -o-transform 0.6s ease-in-out;\n transition: transform 0.6s ease-in-out;\n -webkit-backface-visibility: hidden;\n -moz-backface-visibility: hidden;\n backface-visibility: hidden;\n -webkit-perspective: 1000;\n -moz-perspective: 1000;\n perspective: 1000;\n }\n .carousel-inner > .item.next,\n .carousel-inner > .item.active.right {\n -webkit-transform: translate3d(100%, 0, 0);\n transform: translate3d(100%, 0, 0);\n left: 0;\n }\n .carousel-inner > .item.prev,\n .carousel-inner > .item.active.left {\n -webkit-transform: translate3d(-100%, 0, 0);\n transform: translate3d(-100%, 0, 0);\n left: 0;\n }\n .carousel-inner > .item.next.left,\n .carousel-inner > .item.prev.right,\n .carousel-inner > .item.active {\n -webkit-transform: translate3d(0, 0, 0);\n transform: translate3d(0, 0, 0);\n left: 0;\n }\n}\n.carousel-inner > .active,\n.carousel-inner > .next,\n.carousel-inner > .prev {\n display: block;\n}\n.carousel-inner > .active {\n left: 0;\n}\n.carousel-inner > .next,\n.carousel-inner > .prev {\n position: absolute;\n top: 0;\n width: 100%;\n}\n.carousel-inner > .next {\n left: 100%;\n}\n.carousel-inner > .prev {\n left: -100%;\n}\n.carousel-inner > .next.left,\n.carousel-inner > .prev.right {\n left: 0;\n}\n.carousel-inner > .active.left {\n left: -100%;\n}\n.carousel-inner > .active.right {\n left: 100%;\n}\n.carousel-control {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n width: 15%;\n opacity: 0.5;\n filter: alpha(opacity=50);\n font-size: 20px;\n color: #ffffff;\n text-align: center;\n text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);\n}\n.carousel-control.left {\n background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);\n background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);\n background-image: linear-gradient(to right, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0.0001) 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);\n}\n.carousel-control.right {\n left: auto;\n right: 0;\n background-image: -webkit-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%);\n background-image: -o-linear-gradient(left, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%);\n background-image: linear-gradient(to right, rgba(0, 0, 0, 0.0001) 0%, rgba(0, 0, 0, 0.5) 100%);\n background-repeat: repeat-x;\n filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);\n}\n.carousel-control:hover,\n.carousel-control:focus {\n outline: 0;\n color: #ffffff;\n text-decoration: none;\n opacity: 0.9;\n filter: alpha(opacity=90);\n}\n.carousel-control .icon-prev,\n.carousel-control .icon-next,\n.carousel-control .glyphicon-chevron-left,\n.carousel-control .glyphicon-chevron-right {\n position: absolute;\n top: 50%;\n z-index: 5;\n display: inline-block;\n}\n.carousel-control .icon-prev,\n.carousel-control .glyphicon-chevron-left {\n left: 50%;\n margin-left: -10px;\n}\n.carousel-control .icon-next,\n.carousel-control .glyphicon-chevron-right {\n right: 50%;\n margin-right: -10px;\n}\n.carousel-control .icon-prev,\n.carousel-control .icon-next {\n width: 20px;\n height: 20px;\n margin-top: -10px;\n line-height: 1;\n font-family: serif;\n}\n.carousel-control .icon-prev:before {\n content: '\\2039';\n}\n.carousel-control .icon-next:before {\n content: '\\203a';\n}\n.carousel-indicators {\n position: absolute;\n bottom: 10px;\n left: 50%;\n z-index: 15;\n width: 60%;\n margin-left: -30%;\n padding-left: 0;\n list-style: none;\n text-align: center;\n}\n.carousel-indicators li {\n display: inline-block;\n width: 10px;\n height: 10px;\n margin: 1px;\n text-indent: -999px;\n border: 1px solid #ffffff;\n border-radius: 10px;\n cursor: pointer;\n background-color: #000 \\9;\n background-color: rgba(0, 0, 0, 0);\n}\n.carousel-indicators .active {\n margin: 0;\n width: 12px;\n height: 12px;\n background-color: #ffffff;\n}\n.carousel-caption {\n position: absolute;\n left: 15%;\n right: 15%;\n bottom: 20px;\n z-index: 10;\n padding-top: 20px;\n padding-bottom: 20px;\n color: #ffffff;\n text-align: center;\n text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6);\n}\n.carousel-caption .btn {\n text-shadow: none;\n}\n@media screen and (min-width: 768px) {\n .carousel-control .glyphicon-chevron-left,\n .carousel-control .glyphicon-chevron-right,\n .carousel-control .icon-prev,\n .carousel-control .icon-next {\n width: 30px;\n height: 30px;\n margin-top: -15px;\n font-size: 30px;\n }\n .carousel-control .glyphicon-chevron-left,\n .carousel-control .icon-prev {\n margin-left: -15px;\n }\n .carousel-control .glyphicon-chevron-right,\n .carousel-control .icon-next {\n margin-right: -15px;\n }\n .carousel-caption {\n left: 20%;\n right: 20%;\n padding-bottom: 30px;\n }\n .carousel-indicators {\n bottom: 20px;\n }\n}\n.clearfix:before,\n.clearfix:after,\n.dl-horizontal dd:before,\n.dl-horizontal dd:after,\n.container:before,\n.container:after,\n.container-fluid:before,\n.container-fluid:after,\n.row:before,\n.row:after,\n.form-horizontal .form-group:before,\n.form-horizontal .form-group:after,\n.btn-toolbar:before,\n.btn-toolbar:after,\n.btn-group-vertical > .btn-group:before,\n.btn-group-vertical > .btn-group:after,\n.nav:before,\n.nav:after,\n.navbar:before,\n.navbar:after,\n.navbar-header:before,\n.navbar-header:after,\n.navbar-collapse:before,\n.navbar-collapse:after,\n.pager:before,\n.pager:after,\n.panel-body:before,\n.panel-body:after,\n.modal-footer:before,\n.modal-footer:after {\n content: \" \";\n display: table;\n}\n.clearfix:after,\n.dl-horizontal dd:after,\n.container:after,\n.container-fluid:after,\n.row:after,\n.form-horizontal .form-group:after,\n.btn-toolbar:after,\n.btn-group-vertical > .btn-group:after,\n.nav:after,\n.navbar:after,\n.navbar-header:after,\n.navbar-collapse:after,\n.pager:after,\n.panel-body:after,\n.modal-footer:after {\n clear: both;\n}\n.center-block {\n display: block;\n margin-left: auto;\n margin-right: auto;\n}\n.pull-right {\n float: right !important;\n}\n.pull-left {\n float: left !important;\n}\n.hide {\n display: none !important;\n}\n.show {\n display: block !important;\n}\n.invisible {\n visibility: hidden;\n}\n.text-hide {\n font: 0/0 a;\n color: transparent;\n text-shadow: none;\n background-color: transparent;\n border: 0;\n}\n.hidden {\n display: none !important;\n visibility: hidden !important;\n}\n.affix {\n position: fixed;\n}\n@-ms-viewport {\n width: device-width;\n}\n.visible-xs,\n.visible-sm,\n.visible-md,\n.visible-lg {\n display: none !important;\n}\n.visible-xs-block,\n.visible-xs-inline,\n.visible-xs-inline-block,\n.visible-sm-block,\n.visible-sm-inline,\n.visible-sm-inline-block,\n.visible-md-block,\n.visible-md-inline,\n.visible-md-inline-block,\n.visible-lg-block,\n.visible-lg-inline,\n.visible-lg-inline-block {\n display: none !important;\n}\n@media (max-width: 767px) {\n .visible-xs {\n display: block !important;\n }\n table.visible-xs {\n display: table;\n }\n tr.visible-xs {\n display: table-row !important;\n }\n th.visible-xs,\n td.visible-xs {\n display: table-cell !important;\n }\n}\n@media (max-width: 767px) {\n .visible-xs-block {\n display: block !important;\n }\n}\n@media (max-width: 767px) {\n .visible-xs-inline {\n display: inline !important;\n }\n}\n@media (max-width: 767px) {\n .visible-xs-inline-block {\n display: inline-block !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm {\n display: block !important;\n }\n table.visible-sm {\n display: table;\n }\n tr.visible-sm {\n display: table-row !important;\n }\n th.visible-sm,\n td.visible-sm {\n display: table-cell !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm-block {\n display: block !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm-inline {\n display: inline !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .visible-sm-inline-block {\n display: inline-block !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md {\n display: block !important;\n }\n table.visible-md {\n display: table;\n }\n tr.visible-md {\n display: table-row !important;\n }\n th.visible-md,\n td.visible-md {\n display: table-cell !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md-block {\n display: block !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md-inline {\n display: inline !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .visible-md-inline-block {\n display: inline-block !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg {\n display: block !important;\n }\n table.visible-lg {\n display: table;\n }\n tr.visible-lg {\n display: table-row !important;\n }\n th.visible-lg,\n td.visible-lg {\n display: table-cell !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg-block {\n display: block !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg-inline {\n display: inline !important;\n }\n}\n@media (min-width: 1200px) {\n .visible-lg-inline-block {\n display: inline-block !important;\n }\n}\n@media (max-width: 767px) {\n .hidden-xs {\n display: none !important;\n }\n}\n@media (min-width: 768px) and (max-width: 991px) {\n .hidden-sm {\n display: none !important;\n }\n}\n@media (min-width: 992px) and (max-width: 1199px) {\n .hidden-md {\n display: none !important;\n }\n}\n@media (min-width: 1200px) {\n .hidden-lg {\n display: none !important;\n }\n}\n.visible-print {\n display: none !important;\n}\n@media print {\n .visible-print {\n display: block !important;\n }\n table.visible-print {\n display: table;\n }\n tr.visible-print {\n display: table-row !important;\n }\n th.visible-print,\n td.visible-print {\n display: table-cell !important;\n }\n}\n.visible-print-block {\n display: none !important;\n}\n@media print {\n .visible-print-block {\n display: block !important;\n }\n}\n.visible-print-inline {\n display: none !important;\n}\n@media print {\n .visible-print-inline {\n display: inline !important;\n }\n}\n.visible-print-inline-block {\n display: none !important;\n}\n@media print {\n .visible-print-inline-block {\n display: inline-block !important;\n }\n}\n@media print {\n .hidden-print {\n display: none !important;\n }\n}\n/*# sourceMappingURL=bootstrap.css.map */","/*! normalize.css v3.0.2 | MIT License | git.io/normalize */\n\n//\n// 1. Set default font family to sans-serif.\n// 2. Prevent iOS text size adjust after orientation change, without disabling\n// user zoom.\n//\n\nhtml {\n font-family: sans-serif; // 1\n -ms-text-size-adjust: 100%; // 2\n -webkit-text-size-adjust: 100%; // 2\n}\n\n//\n// Remove default margin.\n//\n\nbody {\n margin: 0;\n}\n\n// HTML5 display definitions\n// ==========================================================================\n\n//\n// Correct `block` display not defined for any HTML5 element in IE 8/9.\n// Correct `block` display not defined for `details` or `summary` in IE 10/11\n// and Firefox.\n// Correct `block` display not defined for `main` in IE 11.\n//\n\narticle,\naside,\ndetails,\nfigcaption,\nfigure,\nfooter,\nheader,\nhgroup,\nmain,\nmenu,\nnav,\nsection,\nsummary {\n display: block;\n}\n\n//\n// 1. Correct `inline-block` display not defined in IE 8/9.\n// 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.\n//\n\naudio,\ncanvas,\nprogress,\nvideo {\n display: inline-block; // 1\n vertical-align: baseline; // 2\n}\n\n//\n// Prevent modern browsers from displaying `audio` without controls.\n// Remove excess height in iOS 5 devices.\n//\n\naudio:not([controls]) {\n display: none;\n height: 0;\n}\n\n//\n// Address `[hidden]` styling not present in IE 8/9/10.\n// Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.\n//\n\n[hidden],\ntemplate {\n display: none;\n}\n\n// Links\n// ==========================================================================\n\n//\n// Remove the gray background color from active links in IE 10.\n//\n\na {\n background-color: transparent;\n}\n\n//\n// Improve readability when focused and also mouse hovered in all browsers.\n//\n\na:active,\na:hover {\n outline: 0;\n}\n\n// Text-level semantics\n// ==========================================================================\n\n//\n// Address styling not present in IE 8/9/10/11, Safari, and Chrome.\n//\n\nabbr[title] {\n border-bottom: 1px dotted;\n}\n\n//\n// Address style set to `bolder` in Firefox 4+, Safari, and Chrome.\n//\n\nb,\nstrong {\n font-weight: bold;\n}\n\n//\n// Address styling not present in Safari and Chrome.\n//\n\ndfn {\n font-style: italic;\n}\n\n//\n// Address variable `h1` font-size and margin within `section` and `article`\n// contexts in Firefox 4+, Safari, and Chrome.\n//\n\nh1 {\n font-size: 2em;\n margin: 0.67em 0;\n}\n\n//\n// Address styling not present in IE 8/9.\n//\n\nmark {\n background: #ff0;\n color: #000;\n}\n\n//\n// Address inconsistent and variable font size in all browsers.\n//\n\nsmall {\n font-size: 80%;\n}\n\n//\n// Prevent `sub` and `sup` affecting `line-height` in all browsers.\n//\n\nsub,\nsup {\n font-size: 75%;\n line-height: 0;\n position: relative;\n vertical-align: baseline;\n}\n\nsup {\n top: -0.5em;\n}\n\nsub {\n bottom: -0.25em;\n}\n\n// Embedded content\n// ==========================================================================\n\n//\n// Remove border when inside `a` element in IE 8/9/10.\n//\n\nimg {\n border: 0;\n}\n\n//\n// Correct overflow not hidden in IE 9/10/11.\n//\n\nsvg:not(:root) {\n overflow: hidden;\n}\n\n// Grouping content\n// ==========================================================================\n\n//\n// Address margin not present in IE 8/9 and Safari.\n//\n\nfigure {\n margin: 1em 40px;\n}\n\n//\n// Address differences between Firefox and other browsers.\n//\n\nhr {\n -moz-box-sizing: content-box;\n box-sizing: content-box;\n height: 0;\n}\n\n//\n// Contain overflow in all browsers.\n//\n\npre {\n overflow: auto;\n}\n\n//\n// Address odd `em`-unit font size rendering in all browsers.\n//\n\ncode,\nkbd,\npre,\nsamp {\n font-family: monospace, monospace;\n font-size: 1em;\n}\n\n// Forms\n// ==========================================================================\n\n//\n// Known limitation: by default, Chrome and Safari on OS X allow very limited\n// styling of `select`, unless a `border` property is set.\n//\n\n//\n// 1. Correct color not being inherited.\n// Known issue: affects color of disabled elements.\n// 2. Correct font properties not being inherited.\n// 3. Address margins set differently in Firefox 4+, Safari, and Chrome.\n//\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n color: inherit; // 1\n font: inherit; // 2\n margin: 0; // 3\n}\n\n//\n// Address `overflow` set to `hidden` in IE 8/9/10/11.\n//\n\nbutton {\n overflow: visible;\n}\n\n//\n// Address inconsistent `text-transform` inheritance for `button` and `select`.\n// All other form control elements do not inherit `text-transform` values.\n// Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.\n// Correct `select` style inheritance in Firefox.\n//\n\nbutton,\nselect {\n text-transform: none;\n}\n\n//\n// 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`\n// and `video` controls.\n// 2. Correct inability to style clickable `input` types in iOS.\n// 3. Improve usability and consistency of cursor style between image-type\n// `input` and others.\n//\n\nbutton,\nhtml input[type=\"button\"], // 1\ninput[type=\"reset\"],\ninput[type=\"submit\"] {\n -webkit-appearance: button; // 2\n cursor: pointer; // 3\n}\n\n//\n// Re-set default cursor for disabled elements.\n//\n\nbutton[disabled],\nhtml input[disabled] {\n cursor: default;\n}\n\n//\n// Remove inner padding and border in Firefox 4+.\n//\n\nbutton::-moz-focus-inner,\ninput::-moz-focus-inner {\n border: 0;\n padding: 0;\n}\n\n//\n// Address Firefox 4+ setting `line-height` on `input` using `!important` in\n// the UA stylesheet.\n//\n\ninput {\n line-height: normal;\n}\n\n//\n// It's recommended that you don't attempt to style these elements.\n// Firefox's implementation doesn't respect box-sizing, padding, or width.\n//\n// 1. Address box sizing set to `content-box` in IE 8/9/10.\n// 2. Remove excess padding in IE 8/9/10.\n//\n\ninput[type=\"checkbox\"],\ninput[type=\"radio\"] {\n box-sizing: border-box; // 1\n padding: 0; // 2\n}\n\n//\n// Fix the cursor style for Chrome's increment/decrement buttons. For certain\n// `font-size` values of the `input`, it causes the cursor style of the\n// decrement button to change from `default` to `text`.\n//\n\ninput[type=\"number\"]::-webkit-inner-spin-button,\ninput[type=\"number\"]::-webkit-outer-spin-button {\n height: auto;\n}\n\n//\n// 1. Address `appearance` set to `searchfield` in Safari and Chrome.\n// 2. Address `box-sizing` set to `border-box` in Safari and Chrome\n// (include `-moz` to future-proof).\n//\n\ninput[type=\"search\"] {\n -webkit-appearance: textfield; // 1\n -moz-box-sizing: content-box;\n -webkit-box-sizing: content-box; // 2\n box-sizing: content-box;\n}\n\n//\n// Remove inner padding and search cancel button in Safari and Chrome on OS X.\n// Safari (but not Chrome) clips the cancel button when the search input has\n// padding (and `textfield` appearance).\n//\n\ninput[type=\"search\"]::-webkit-search-cancel-button,\ninput[type=\"search\"]::-webkit-search-decoration {\n -webkit-appearance: none;\n}\n\n//\n// Define consistent border, margin, and padding.\n//\n\nfieldset {\n border: 1px solid #c0c0c0;\n margin: 0 2px;\n padding: 0.35em 0.625em 0.75em;\n}\n\n//\n// 1. Correct `color` not being inherited in IE 8/9/10/11.\n// 2. Remove padding so people aren't caught out if they zero out fieldsets.\n//\n\nlegend {\n border: 0; // 1\n padding: 0; // 2\n}\n\n//\n// Remove default vertical scrollbar in IE 8/9/10/11.\n//\n\ntextarea {\n overflow: auto;\n}\n\n//\n// Don't inherit the `font-weight` (applied by a rule above).\n// NOTE: the default cannot safely be changed in Chrome and Safari on OS X.\n//\n\noptgroup {\n font-weight: bold;\n}\n\n// Tables\n// ==========================================================================\n\n//\n// Remove most spacing between table cells.\n//\n\ntable {\n border-collapse: collapse;\n border-spacing: 0;\n}\n\ntd,\nth {\n padding: 0;\n}\n","/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */\n\n// ==========================================================================\n// Print styles.\n// Inlined to avoid the additional HTTP request: h5bp.com/r\n// ==========================================================================\n\n@media print {\n *,\n *:before,\n *:after {\n background: transparent !important;\n color: #000 !important; // Black prints faster: h5bp.com/s\n box-shadow: none !important;\n text-shadow: none !important;\n }\n\n a,\n a:visited {\n text-decoration: underline;\n }\n\n a[href]:after {\n content: \" (\" attr(href) \")\";\n }\n\n abbr[title]:after {\n content: \" (\" attr(title) \")\";\n }\n\n // Don't show links that are fragment identifiers,\n // or use the `javascript:` pseudo protocol\n a[href^=\"#\"]:after,\n a[href^=\"javascript:\"]:after {\n content: \"\";\n }\n\n pre,\n blockquote {\n border: 1px solid #999;\n page-break-inside: avoid;\n }\n\n thead {\n display: table-header-group; // h5bp.com/t\n }\n\n tr,\n img {\n page-break-inside: avoid;\n }\n\n img {\n max-width: 100% !important;\n }\n\n p,\n h2,\n h3 {\n orphans: 3;\n widows: 3;\n }\n\n h2,\n h3 {\n page-break-after: avoid;\n }\n\n // Bootstrap specific changes start\n //\n // Chrome (OSX) fix for https://github.com/twbs/bootstrap/issues/11245\n // Once fixed, we can just straight up remove this.\n select {\n background: #fff !important;\n }\n\n // Bootstrap components\n .navbar {\n display: none;\n }\n .btn,\n .dropup > .btn {\n > .caret {\n border-top-color: #000 !important;\n }\n }\n .label {\n border: 1px solid #000;\n }\n\n .table {\n border-collapse: collapse !important;\n\n td,\n th {\n background-color: #fff !important;\n }\n }\n .table-bordered {\n th,\n td {\n border: 1px solid #ddd !important;\n }\n }\n\n // Bootstrap specific changes end\n}\n","//\n// Glyphicons for Bootstrap\n//\n// Since icons are fonts, they can be placed anywhere text is placed and are\n// thus automatically sized to match the surrounding child. To use, create an\n// inline element with the appropriate classes, like so:\n//\n// <a href=\"#\"><span class=\"glyphicon glyphicon-star\"></span> Star</a>\n\n// Import the fonts\n@font-face {\n font-family: 'Glyphicons Halflings';\n src: url('@{icon-font-path}@{icon-font-name}.eot');\n src: url('@{icon-font-path}@{icon-font-name}.eot?#iefix') format('embedded-opentype'),\n url('@{icon-font-path}@{icon-font-name}.woff2') format('woff2'),\n url('@{icon-font-path}@{icon-font-name}.woff') format('woff'),\n url('@{icon-font-path}@{icon-font-name}.ttf') format('truetype'),\n url('@{icon-font-path}@{icon-font-name}.svg#@{icon-font-svg-id}') format('svg');\n}\n\n// Catchall baseclass\n.glyphicon {\n position: relative;\n top: 1px;\n display: inline-block;\n font-family: 'Glyphicons Halflings';\n font-style: normal;\n font-weight: normal;\n line-height: 1;\n -webkit-font-smoothing: antialiased;\n -moz-osx-font-smoothing: grayscale;\n}\n\n// Individual icons\n.glyphicon-asterisk { &:before { content: \"\\2a\"; } }\n.glyphicon-plus { &:before { content: \"\\2b\"; } }\n.glyphicon-euro,\n.glyphicon-eur { &:before { content: \"\\20ac\"; } }\n.glyphicon-minus { &:before { content: \"\\2212\"; } }\n.glyphicon-cloud { &:before { content: \"\\2601\"; } }\n.glyphicon-envelope { &:before { content: \"\\2709\"; } }\n.glyphicon-pencil { &:before { content: \"\\270f\"; } }\n.glyphicon-glass { &:before { content: \"\\e001\"; } }\n.glyphicon-music { &:before { content: \"\\e002\"; } }\n.glyphicon-search { &:before { content: \"\\e003\"; } }\n.glyphicon-heart { &:before { content: \"\\e005\"; } }\n.glyphicon-star { &:before { content: \"\\e006\"; } }\n.glyphicon-star-empty { &:before { content: \"\\e007\"; } }\n.glyphicon-user { &:before { content: \"\\e008\"; } }\n.glyphicon-film { &:before { content: \"\\e009\"; } }\n.glyphicon-th-large { &:before { content: \"\\e010\"; } }\n.glyphicon-th { &:before { content: \"\\e011\"; } }\n.glyphicon-th-list { &:before { content: \"\\e012\"; } }\n.glyphicon-ok { &:before { content: \"\\e013\"; } }\n.glyphicon-remove { &:before { content: \"\\e014\"; } }\n.glyphicon-zoom-in { &:before { content: \"\\e015\"; } }\n.glyphicon-zoom-out { &:before { content: \"\\e016\"; } }\n.glyphicon-off { &:before { content: \"\\e017\"; } }\n.glyphicon-signal { &:before { content: \"\\e018\"; } }\n.glyphicon-cog { &:before { content: \"\\e019\"; } }\n.glyphicon-trash { &:before { content: \"\\e020\"; } }\n.glyphicon-home { &:before { content: \"\\e021\"; } }\n.glyphicon-file { &:before { content: \"\\e022\"; } }\n.glyphicon-time { &:before { content: \"\\e023\"; } }\n.glyphicon-road { &:before { content: \"\\e024\"; } }\n.glyphicon-download-alt { &:before { content: \"\\e025\"; } }\n.glyphicon-download { &:before { content: \"\\e026\"; } }\n.glyphicon-upload { &:before { content: \"\\e027\"; } }\n.glyphicon-inbox { &:before { content: \"\\e028\"; } }\n.glyphicon-play-circle { &:before { content: \"\\e029\"; } }\n.glyphicon-repeat { &:before { content: \"\\e030\"; } }\n.glyphicon-refresh { &:before { content: \"\\e031\"; } }\n.glyphicon-list-alt { &:before { content: \"\\e032\"; } }\n.glyphicon-lock { &:before { content: \"\\e033\"; } }\n.glyphicon-flag { &:before { content: \"\\e034\"; } }\n.glyphicon-headphones { &:before { content: \"\\e035\"; } }\n.glyphicon-volume-off { &:before { content: \"\\e036\"; } }\n.glyphicon-volume-down { &:before { content: \"\\e037\"; } }\n.glyphicon-volume-up { &:before { content: \"\\e038\"; } }\n.glyphicon-qrcode { &:before { content: \"\\e039\"; } }\n.glyphicon-barcode { &:before { content: \"\\e040\"; } }\n.glyphicon-tag { &:before { content: \"\\e041\"; } }\n.glyphicon-tags { &:before { content: \"\\e042\"; } }\n.glyphicon-book { &:before { content: \"\\e043\"; } }\n.glyphicon-bookmark { &:before { content: \"\\e044\"; } }\n.glyphicon-print { &:before { content: \"\\e045\"; } }\n.glyphicon-camera { &:before { content: \"\\e046\"; } }\n.glyphicon-font { &:before { content: \"\\e047\"; } }\n.glyphicon-bold { &:before { content: \"\\e048\"; } }\n.glyphicon-italic { &:before { content: \"\\e049\"; } }\n.glyphicon-text-height { &:before { content: \"\\e050\"; } }\n.glyphicon-text-width { &:before { content: \"\\e051\"; } }\n.glyphicon-align-left { &:before { content: \"\\e052\"; } }\n.glyphicon-align-center { &:before { content: \"\\e053\"; } }\n.glyphicon-align-right { &:before { content: \"\\e054\"; } }\n.glyphicon-align-justify { &:before { content: \"\\e055\"; } }\n.glyphicon-list { &:before { content: \"\\e056\"; } }\n.glyphicon-indent-left { &:before { content: \"\\e057\"; } }\n.glyphicon-indent-right { &:before { content: \"\\e058\"; } }\n.glyphicon-facetime-video { &:before { content: \"\\e059\"; } }\n.glyphicon-picture { &:before { content: \"\\e060\"; } }\n.glyphicon-map-marker { &:before { content: \"\\e062\"; } }\n.glyphicon-adjust { &:before { content: \"\\e063\"; } }\n.glyphicon-tint { &:before { content: \"\\e064\"; } }\n.glyphicon-edit { &:before { content: \"\\e065\"; } }\n.glyphicon-share { &:before { content: \"\\e066\"; } }\n.glyphicon-check { &:before { content: \"\\e067\"; } }\n.glyphicon-move { &:before { content: \"\\e068\"; } }\n.glyphicon-step-backward { &:before { content: \"\\e069\"; } }\n.glyphicon-fast-backward { &:before { content: \"\\e070\"; } }\n.glyphicon-backward { &:before { content: \"\\e071\"; } }\n.glyphicon-play { &:before { content: \"\\e072\"; } }\n.glyphicon-pause { &:before { content: \"\\e073\"; } }\n.glyphicon-stop { &:before { content: \"\\e074\"; } }\n.glyphicon-forward { &:before { content: \"\\e075\"; } }\n.glyphicon-fast-forward { &:before { content: \"\\e076\"; } }\n.glyphicon-step-forward { &:before { content: \"\\e077\"; } }\n.glyphicon-eject { &:before { content: \"\\e078\"; } }\n.glyphicon-chevron-left { &:before { content: \"\\e079\"; } }\n.glyphicon-chevron-right { &:before { content: \"\\e080\"; } }\n.glyphicon-plus-sign { &:before { content: \"\\e081\"; } }\n.glyphicon-minus-sign { &:before { content: \"\\e082\"; } }\n.glyphicon-remove-sign { &:before { content: \"\\e083\"; } }\n.glyphicon-ok-sign { &:before { content: \"\\e084\"; } }\n.glyphicon-question-sign { &:before { content: \"\\e085\"; } }\n.glyphicon-info-sign { &:before { content: \"\\e086\"; } }\n.glyphicon-screenshot { &:before { content: \"\\e087\"; } }\n.glyphicon-remove-circle { &:before { content: \"\\e088\"; } }\n.glyphicon-ok-circle { &:before { content: \"\\e089\"; } }\n.glyphicon-ban-circle { &:before { content: \"\\e090\"; } }\n.glyphicon-arrow-left { &:before { content: \"\\e091\"; } }\n.glyphicon-arrow-right { &:before { content: \"\\e092\"; } }\n.glyphicon-arrow-up { &:before { content: \"\\e093\"; } }\n.glyphicon-arrow-down { &:before { content: \"\\e094\"; } }\n.glyphicon-share-alt { &:before { content: \"\\e095\"; } }\n.glyphicon-resize-full { &:before { content: \"\\e096\"; } }\n.glyphicon-resize-small { &:before { content: \"\\e097\"; } }\n.glyphicon-exclamation-sign { &:before { content: \"\\e101\"; } }\n.glyphicon-gift { &:before { content: \"\\e102\"; } }\n.glyphicon-leaf { &:before { content: \"\\e103\"; } }\n.glyphicon-fire { &:before { content: \"\\e104\"; } }\n.glyphicon-eye-open { &:before { content: \"\\e105\"; } }\n.glyphicon-eye-close { &:before { content: \"\\e106\"; } }\n.glyphicon-warning-sign { &:before { content: \"\\e107\"; } }\n.glyphicon-plane { &:before { content: \"\\e108\"; } }\n.glyphicon-calendar { &:before { content: \"\\e109\"; } }\n.glyphicon-random { &:before { content: \"\\e110\"; } }\n.glyphicon-comment { &:before { content: \"\\e111\"; } }\n.glyphicon-magnet { &:before { content: \"\\e112\"; } }\n.glyphicon-chevron-up { &:before { content: \"\\e113\"; } }\n.glyphicon-chevron-down { &:before { content: \"\\e114\"; } }\n.glyphicon-retweet { &:before { content: \"\\e115\"; } }\n.glyphicon-shopping-cart { &:before { content: \"\\e116\"; } }\n.glyphicon-folder-close { &:before { content: \"\\e117\"; } }\n.glyphicon-folder-open { &:before { content: \"\\e118\"; } }\n.glyphicon-resize-vertical { &:before { content: \"\\e119\"; } }\n.glyphicon-resize-horizontal { &:before { content: \"\\e120\"; } }\n.glyphicon-hdd { &:before { content: \"\\e121\"; } }\n.glyphicon-bullhorn { &:before { content: \"\\e122\"; } }\n.glyphicon-bell { &:before { content: \"\\e123\"; } }\n.glyphicon-certificate { &:before { content: \"\\e124\"; } }\n.glyphicon-thumbs-up { &:before { content: \"\\e125\"; } }\n.glyphicon-thumbs-down { &:before { content: \"\\e126\"; } }\n.glyphicon-hand-right { &:before { content: \"\\e127\"; } }\n.glyphicon-hand-left { &:before { content: \"\\e128\"; } }\n.glyphicon-hand-up { &:before { content: \"\\e129\"; } }\n.glyphicon-hand-down { &:before { content: \"\\e130\"; } }\n.glyphicon-circle-arrow-right { &:before { content: \"\\e131\"; } }\n.glyphicon-circle-arrow-left { &:before { content: \"\\e132\"; } }\n.glyphicon-circle-arrow-up { &:before { content: \"\\e133\"; } }\n.glyphicon-circle-arrow-down { &:before { content: \"\\e134\"; } }\n.glyphicon-globe { &:before { content: \"\\e135\"; } }\n.glyphicon-wrench { &:before { content: \"\\e136\"; } }\n.glyphicon-tasks { &:before { content: \"\\e137\"; } }\n.glyphicon-filter { &:before { content: \"\\e138\"; } }\n.glyphicon-briefcase { &:before { content: \"\\e139\"; } }\n.glyphicon-fullscreen { &:before { content: \"\\e140\"; } }\n.glyphicon-dashboard { &:before { content: \"\\e141\"; } }\n.glyphicon-paperclip { &:before { content: \"\\e142\"; } }\n.glyphicon-heart-empty { &:before { content: \"\\e143\"; } }\n.glyphicon-link { &:before { content: \"\\e144\"; } }\n.glyphicon-phone { &:before { content: \"\\e145\"; } }\n.glyphicon-pushpin { &:before { content: \"\\e146\"; } }\n.glyphicon-usd { &:before { content: \"\\e148\"; } }\n.glyphicon-gbp { &:before { content: \"\\e149\"; } }\n.glyphicon-sort { &:before { content: \"\\e150\"; } }\n.glyphicon-sort-by-alphabet { &:before { content: \"\\e151\"; } }\n.glyphicon-sort-by-alphabet-alt { &:before { content: \"\\e152\"; } }\n.glyphicon-sort-by-order { &:before { content: \"\\e153\"; } }\n.glyphicon-sort-by-order-alt { &:before { content: \"\\e154\"; } }\n.glyphicon-sort-by-attributes { &:before { content: \"\\e155\"; } }\n.glyphicon-sort-by-attributes-alt { &:before { content: \"\\e156\"; } }\n.glyphicon-unchecked { &:before { content: \"\\e157\"; } }\n.glyphicon-expand { &:before { content: \"\\e158\"; } }\n.glyphicon-collapse-down { &:before { content: \"\\e159\"; } }\n.glyphicon-collapse-up { &:before { content: \"\\e160\"; } }\n.glyphicon-log-in { &:before { content: \"\\e161\"; } }\n.glyphicon-flash { &:before { content: \"\\e162\"; } }\n.glyphicon-log-out { &:before { content: \"\\e163\"; } }\n.glyphicon-new-window { &:before { content: \"\\e164\"; } }\n.glyphicon-record { &:before { content: \"\\e165\"; } }\n.glyphicon-save { &:before { content: \"\\e166\"; } }\n.glyphicon-open { &:before { content: \"\\e167\"; } }\n.glyphicon-saved { &:before { content: \"\\e168\"; } }\n.glyphicon-import { &:before { content: \"\\e169\"; } }\n.glyphicon-export { &:before { content: \"\\e170\"; } }\n.glyphicon-send { &:before { content: \"\\e171\"; } }\n.glyphicon-floppy-disk { &:before { content: \"\\e172\"; } }\n.glyphicon-floppy-saved { &:before { content: \"\\e173\"; } }\n.glyphicon-floppy-remove { &:before { content: \"\\e174\"; } }\n.glyphicon-floppy-save { &:before { content: \"\\e175\"; } }\n.glyphicon-floppy-open { &:before { content: \"\\e176\"; } }\n.glyphicon-credit-card { &:before { content: \"\\e177\"; } }\n.glyphicon-transfer { &:before { content: \"\\e178\"; } }\n.glyphicon-cutlery { &:before { content: \"\\e179\"; } }\n.glyphicon-header { &:before { content: \"\\e180\"; } }\n.glyphicon-compressed { &:before { content: \"\\e181\"; } }\n.glyphicon-earphone { &:before { content: \"\\e182\"; } }\n.glyphicon-phone-alt { &:before { content: \"\\e183\"; } }\n.glyphicon-tower { &:before { content: \"\\e184\"; } }\n.glyphicon-stats { &:before { content: \"\\e185\"; } }\n.glyphicon-sd-video { &:before { content: \"\\e186\"; } }\n.glyphicon-hd-video { &:before { content: \"\\e187\"; } }\n.glyphicon-subtitles { &:before { content: \"\\e188\"; } }\n.glyphicon-sound-stereo { &:before { content: \"\\e189\"; } }\n.glyphicon-sound-dolby { &:before { content: \"\\e190\"; } }\n.glyphicon-sound-5-1 { &:before { content: \"\\e191\"; } }\n.glyphicon-sound-6-1 { &:before { content: \"\\e192\"; } }\n.glyphicon-sound-7-1 { &:before { content: \"\\e193\"; } }\n.glyphicon-copyright-mark { &:before { content: \"\\e194\"; } }\n.glyphicon-registration-mark { &:before { content: \"\\e195\"; } }\n.glyphicon-cloud-download { &:before { content: \"\\e197\"; } }\n.glyphicon-cloud-upload { &:before { content: \"\\e198\"; } }\n.glyphicon-tree-conifer { &:before { content: \"\\e199\"; } }\n.glyphicon-tree-deciduous { &:before { content: \"\\e200\"; } }\n.glyphicon-cd { &:before { content: \"\\e201\"; } }\n.glyphicon-save-file { &:before { content: \"\\e202\"; } }\n.glyphicon-open-file { &:before { content: \"\\e203\"; } }\n.glyphicon-level-up { &:before { content: \"\\e204\"; } }\n.glyphicon-copy { &:before { content: \"\\e205\"; } }\n.glyphicon-paste { &:before { content: \"\\e206\"; } }\n// The following 2 Glyphicons are omitted for the time being because\n// they currently use Unicode codepoints that are outside the\n// Basic Multilingual Plane (BMP). Older buggy versions of WebKit can't handle\n// non-BMP codepoints in CSS string escapes, and thus can't display these two icons.\n// Notably, the bug affects some older versions of the Android Browser.\n// More info: https://github.com/twbs/bootstrap/issues/10106\n// .glyphicon-door { &:before { content: \"\\1f6aa\"; } }\n// .glyphicon-key { &:before { content: \"\\1f511\"; } }\n.glyphicon-alert { &:before { content: \"\\e209\"; } }\n.glyphicon-equalizer { &:before { content: \"\\e210\"; } }\n.glyphicon-king { &:before { content: \"\\e211\"; } }\n.glyphicon-queen { &:before { content: \"\\e212\"; } }\n.glyphicon-pawn { &:before { content: \"\\e213\"; } }\n.glyphicon-bishop { &:before { content: \"\\e214\"; } }\n.glyphicon-knight { &:before { content: \"\\e215\"; } }\n.glyphicon-baby-formula { &:before { content: \"\\e216\"; } }\n.glyphicon-tent { &:before { content: \"\\26fa\"; } }\n.glyphicon-blackboard { &:before { content: \"\\e218\"; } }\n.glyphicon-bed { &:before { content: \"\\e219\"; } }\n.glyphicon-apple { &:before { content: \"\\f8ff\"; } }\n.glyphicon-erase { &:before { content: \"\\e221\"; } }\n.glyphicon-hourglass { &:before { content: \"\\231b\"; } }\n.glyphicon-lamp { &:before { content: \"\\e223\"; } }\n.glyphicon-duplicate { &:before { content: \"\\e224\"; } }\n.glyphicon-piggy-bank { &:before { content: \"\\e225\"; } }\n.glyphicon-scissors { &:before { content: \"\\e226\"; } }\n.glyphicon-bitcoin { &:before { content: \"\\e227\"; } }\n.glyphicon-yen { &:before { content: \"\\00a5\"; } }\n.glyphicon-ruble { &:before { content: \"\\20bd\"; } }\n.glyphicon-scale { &:before { content: \"\\e230\"; } }\n.glyphicon-ice-lolly { &:before { content: \"\\e231\"; } }\n.glyphicon-ice-lolly-tasted { &:before { content: \"\\e232\"; } }\n.glyphicon-education { &:before { content: \"\\e233\"; } }\n.glyphicon-option-horizontal { &:before { content: \"\\e234\"; } }\n.glyphicon-option-vertical { &:before { content: \"\\e235\"; } }\n.glyphicon-menu-hamburger { &:before { content: \"\\e236\"; } }\n.glyphicon-modal-window { &:before { content: \"\\e237\"; } }\n.glyphicon-oil { &:before { content: \"\\e238\"; } }\n.glyphicon-grain { &:before { content: \"\\e239\"; } }\n.glyphicon-sunglasses { &:before { content: \"\\e240\"; } }\n.glyphicon-text-size { &:before { content: \"\\e241\"; } }\n.glyphicon-text-color { &:before { content: \"\\e242\"; } }\n.glyphicon-text-background { &:before { content: \"\\e243\"; } }\n.glyphicon-object-align-top { &:before { content: \"\\e244\"; } }\n.glyphicon-object-align-bottom { &:before { content: \"\\e245\"; } }\n.glyphicon-object-align-horizontal{ &:before { content: \"\\e246\"; } }\n.glyphicon-object-align-left { &:before { content: \"\\e247\"; } }\n.glyphicon-object-align-vertical { &:before { content: \"\\e248\"; } }\n.glyphicon-object-align-right { &:before { content: \"\\e249\"; } }\n.glyphicon-triangle-right { &:before { content: \"\\e250\"; } }\n.glyphicon-triangle-left { &:before { content: \"\\e251\"; } }\n.glyphicon-triangle-bottom { &:before { content: \"\\e252\"; } }\n.glyphicon-triangle-top { &:before { content: \"\\e253\"; } }\n.glyphicon-console { &:before { content: \"\\e254\"; } }\n.glyphicon-superscript { &:before { content: \"\\e255\"; } }\n.glyphicon-subscript { &:before { content: \"\\e256\"; } }\n.glyphicon-menu-left { &:before { content: \"\\e257\"; } }\n.glyphicon-menu-right { &:before { content: \"\\e258\"; } }\n.glyphicon-menu-down { &:before { content: \"\\e259\"; } }\n.glyphicon-menu-up { &:before { content: \"\\e260\"; } }\n","//\n// Scaffolding\n// --------------------------------------------------\n\n\n// Reset the box-sizing\n//\n// Heads up! This reset may cause conflicts with some third-party widgets.\n// For recommendations on resolving such conflicts, see\n// http://getbootstrap.com/getting-started/#third-box-sizing\n* {\n .box-sizing(border-box);\n}\n*:before,\n*:after {\n .box-sizing(border-box);\n}\n\n\n// Body reset\n\nhtml {\n font-size: 10px;\n -webkit-tap-highlight-color: rgba(0,0,0,0);\n}\n\nbody {\n font-family: @font-family-base;\n font-size: @font-size-base;\n line-height: @line-height-base;\n color: @text-color;\n background-color: @body-bg;\n}\n\n// Reset fonts for relevant elements\ninput,\nbutton,\nselect,\ntextarea {\n font-family: inherit;\n font-size: inherit;\n line-height: inherit;\n}\n\n\n// Links\n\na {\n color: @link-color;\n text-decoration: none;\n\n &:hover,\n &:focus {\n color: @link-hover-color;\n text-decoration: @link-hover-decoration;\n }\n\n &:focus {\n .tab-focus();\n }\n}\n\n\n// Figures\n//\n// We reset this here because previously Normalize had no `figure` margins. This\n// ensures we don't break anyone's use of the element.\n\nfigure {\n margin: 0;\n}\n\n\n// Images\n\nimg {\n vertical-align: middle;\n}\n\n// Responsive images (ensure images don't scale beyond their parents)\n.img-responsive {\n .img-responsive();\n}\n\n// Rounded corners\n.img-rounded {\n border-radius: @border-radius-large;\n}\n\n// Image thumbnails\n//\n// Heads up! This is mixin-ed into thumbnails.less for `.thumbnail`.\n.img-thumbnail {\n padding: @thumbnail-padding;\n line-height: @line-height-base;\n background-color: @thumbnail-bg;\n border: 1px solid @thumbnail-border;\n border-radius: @thumbnail-border-radius;\n .transition(all .2s ease-in-out);\n\n // Keep them at most 100% wide\n .img-responsive(inline-block);\n}\n\n// Perfect circle\n.img-circle {\n border-radius: 50%; // set radius in percents\n}\n\n\n// Horizontal rules\n\nhr {\n margin-top: @line-height-computed;\n margin-bottom: @line-height-computed;\n border: 0;\n border-top: 1px solid @hr-border;\n}\n\n\n// Only display content to screen readers\n//\n// See: http://a11yproject.com/posts/how-to-hide-content/\n\n.sr-only {\n position: absolute;\n width: 1px;\n height: 1px;\n margin: -1px;\n padding: 0;\n overflow: hidden;\n clip: rect(0,0,0,0);\n border: 0;\n}\n\n// Use in conjunction with .sr-only to only display content when it's focused.\n// Useful for \"Skip to main content\" links; see http://www.w3.org/TR/2013/NOTE-WCAG20-TECHS-20130905/G1\n// Credit: HTML5 Boilerplate\n\n.sr-only-focusable {\n &:active,\n &:focus {\n position: static;\n width: auto;\n height: auto;\n margin: 0;\n overflow: visible;\n clip: auto;\n }\n}\n","// Vendor Prefixes\n//\n// All vendor mixins are deprecated as of v3.2.0 due to the introduction of\n// Autoprefixer in our Gruntfile. They will be removed in v4.\n\n// - Animations\n// - Backface visibility\n// - Box shadow\n// - Box sizing\n// - Content columns\n// - Hyphens\n// - Placeholder text\n// - Transformations\n// - Transitions\n// - User Select\n\n\n// Animations\n.animation(@animation) {\n -webkit-animation: @animation;\n -o-animation: @animation;\n animation: @animation;\n}\n.animation-name(@name) {\n -webkit-animation-name: @name;\n animation-name: @name;\n}\n.animation-duration(@duration) {\n -webkit-animation-duration: @duration;\n animation-duration: @duration;\n}\n.animation-timing-function(@timing-function) {\n -webkit-animation-timing-function: @timing-function;\n animation-timing-function: @timing-function;\n}\n.animation-delay(@delay) {\n -webkit-animation-delay: @delay;\n animation-delay: @delay;\n}\n.animation-iteration-count(@iteration-count) {\n -webkit-animation-iteration-count: @iteration-count;\n animation-iteration-count: @iteration-count;\n}\n.animation-direction(@direction) {\n -webkit-animation-direction: @direction;\n animation-direction: @direction;\n}\n.animation-fill-mode(@fill-mode) {\n -webkit-animation-fill-mode: @fill-mode;\n animation-fill-mode: @fill-mode;\n}\n\n// Backface visibility\n// Prevent browsers from flickering when using CSS 3D transforms.\n// Default value is `visible`, but can be changed to `hidden`\n\n.backface-visibility(@visibility){\n -webkit-backface-visibility: @visibility;\n -moz-backface-visibility: @visibility;\n backface-visibility: @visibility;\n}\n\n// Drop shadows\n//\n// Note: Deprecated `.box-shadow()` as of v3.1.0 since all of Bootstrap's\n// supported browsers that have box shadow capabilities now support it.\n\n.box-shadow(@shadow) {\n -webkit-box-shadow: @shadow; // iOS <4.3 & Android <4.1\n box-shadow: @shadow;\n}\n\n// Box sizing\n.box-sizing(@boxmodel) {\n -webkit-box-sizing: @boxmodel;\n -moz-box-sizing: @boxmodel;\n box-sizing: @boxmodel;\n}\n\n// CSS3 Content Columns\n.content-columns(@column-count; @column-gap: @grid-gutter-width) {\n -webkit-column-count: @column-count;\n -moz-column-count: @column-count;\n column-count: @column-count;\n -webkit-column-gap: @column-gap;\n -moz-column-gap: @column-gap;\n column-gap: @column-gap;\n}\n\n// Optional hyphenation\n.hyphens(@mode: auto) {\n word-wrap: break-word;\n -webkit-hyphens: @mode;\n -moz-hyphens: @mode;\n -ms-hyphens: @mode; // IE10+\n -o-hyphens: @mode;\n hyphens: @mode;\n}\n\n// Placeholder text\n.placeholder(@color: @input-color-placeholder) {\n // Firefox\n &::-moz-placeholder {\n color: @color;\n opacity: 1; // Override Firefox's unusual default opacity; see https://github.com/twbs/bootstrap/pull/11526\n }\n &:-ms-input-placeholder { color: @color; } // Internet Explorer 10+\n &::-webkit-input-placeholder { color: @color; } // Safari and Chrome\n}\n\n// Transformations\n.scale(@ratio) {\n -webkit-transform: scale(@ratio);\n -ms-transform: scale(@ratio); // IE9 only\n -o-transform: scale(@ratio);\n transform: scale(@ratio);\n}\n.scale(@ratioX; @ratioY) {\n -webkit-transform: scale(@ratioX, @ratioY);\n -ms-transform: scale(@ratioX, @ratioY); // IE9 only\n -o-transform: scale(@ratioX, @ratioY);\n transform: scale(@ratioX, @ratioY);\n}\n.scaleX(@ratio) {\n -webkit-transform: scaleX(@ratio);\n -ms-transform: scaleX(@ratio); // IE9 only\n -o-transform: scaleX(@ratio);\n transform: scaleX(@ratio);\n}\n.scaleY(@ratio) {\n -webkit-transform: scaleY(@ratio);\n -ms-transform: scaleY(@ratio); // IE9 only\n -o-transform: scaleY(@ratio);\n transform: scaleY(@ratio);\n}\n.skew(@x; @y) {\n -webkit-transform: skewX(@x) skewY(@y);\n -ms-transform: skewX(@x) skewY(@y); // See https://github.com/twbs/bootstrap/issues/4885; IE9+\n -o-transform: skewX(@x) skewY(@y);\n transform: skewX(@x) skewY(@y);\n}\n.translate(@x; @y) {\n -webkit-transform: translate(@x, @y);\n -ms-transform: translate(@x, @y); // IE9 only\n -o-transform: translate(@x, @y);\n transform: translate(@x, @y);\n}\n.translate3d(@x; @y; @z) {\n -webkit-transform: translate3d(@x, @y, @z);\n transform: translate3d(@x, @y, @z);\n}\n.rotate(@degrees) {\n -webkit-transform: rotate(@degrees);\n -ms-transform: rotate(@degrees); // IE9 only\n -o-transform: rotate(@degrees);\n transform: rotate(@degrees);\n}\n.rotateX(@degrees) {\n -webkit-transform: rotateX(@degrees);\n -ms-transform: rotateX(@degrees); // IE9 only\n -o-transform: rotateX(@degrees);\n transform: rotateX(@degrees);\n}\n.rotateY(@degrees) {\n -webkit-transform: rotateY(@degrees);\n -ms-transform: rotateY(@degrees); // IE9 only\n -o-transform: rotateY(@degrees);\n transform: rotateY(@degrees);\n}\n.perspective(@perspective) {\n -webkit-perspective: @perspective;\n -moz-perspective: @perspective;\n perspective: @perspective;\n}\n.perspective-origin(@perspective) {\n -webkit-perspective-origin: @perspective;\n -moz-perspective-origin: @perspective;\n perspective-origin: @perspective;\n}\n.transform-origin(@origin) {\n -webkit-transform-origin: @origin;\n -moz-transform-origin: @origin;\n -ms-transform-origin: @origin; // IE9 only\n transform-origin: @origin;\n}\n\n\n// Transitions\n\n.transition(@transition) {\n -webkit-transition: @transition;\n -o-transition: @transition;\n transition: @transition;\n}\n.transition-property(@transition-property) {\n -webkit-transition-property: @transition-property;\n transition-property: @transition-property;\n}\n.transition-delay(@transition-delay) {\n -webkit-transition-delay: @transition-delay;\n transition-delay: @transition-delay;\n}\n.transition-duration(@transition-duration) {\n -webkit-transition-duration: @transition-duration;\n transition-duration: @transition-duration;\n}\n.transition-timing-function(@timing-function) {\n -webkit-transition-timing-function: @timing-function;\n transition-timing-function: @timing-function;\n}\n.transition-transform(@transition) {\n -webkit-transition: -webkit-transform @transition;\n -moz-transition: -moz-transform @transition;\n -o-transition: -o-transform @transition;\n transition: transform @transition;\n}\n\n\n// User select\n// For selecting text on the page\n\n.user-select(@select) {\n -webkit-user-select: @select;\n -moz-user-select: @select;\n -ms-user-select: @select; // IE10+\n user-select: @select;\n}\n","// WebKit-style focus\n\n.tab-focus() {\n // Default\n outline: thin dotted;\n // WebKit\n outline: 5px auto -webkit-focus-ring-color;\n outline-offset: -2px;\n}\n","// Image Mixins\n// - Responsive image\n// - Retina image\n\n\n// Responsive image\n//\n// Keep images from scaling beyond the width of their parents.\n.img-responsive(@display: block) {\n display: @display;\n max-width: 100%; // Part 1: Set a maximum relative to the parent\n height: auto; // Part 2: Scale the height according to the width, otherwise you get stretching\n}\n\n\n// Retina image\n//\n// Short retina mixin for setting background-image and -size. Note that the\n// spelling of `min--moz-device-pixel-ratio` is intentional.\n.img-retina(@file-1x; @file-2x; @width-1x; @height-1x) {\n background-image: url(\"@{file-1x}\");\n\n @media\n only screen and (-webkit-min-device-pixel-ratio: 2),\n only screen and ( min--moz-device-pixel-ratio: 2),\n only screen and ( -o-min-device-pixel-ratio: 2/1),\n only screen and ( min-device-pixel-ratio: 2),\n only screen and ( min-resolution: 192dpi),\n only screen and ( min-resolution: 2dppx) {\n background-image: url(\"@{file-2x}\");\n background-size: @width-1x @height-1x;\n }\n}\n","//\n// Typography\n// --------------------------------------------------\n\n\n// Headings\n// -------------------------\n\nh1, h2, h3, h4, h5, h6,\n.h1, .h2, .h3, .h4, .h5, .h6 {\n font-family: @headings-font-family;\n font-weight: @headings-font-weight;\n line-height: @headings-line-height;\n color: @headings-color;\n\n small,\n .small {\n font-weight: normal;\n line-height: 1;\n color: @headings-small-color;\n }\n}\n\nh1, .h1,\nh2, .h2,\nh3, .h3 {\n margin-top: @line-height-computed;\n margin-bottom: (@line-height-computed / 2);\n\n small,\n .small {\n font-size: 65%;\n }\n}\nh4, .h4,\nh5, .h5,\nh6, .h6 {\n margin-top: (@line-height-computed / 2);\n margin-bottom: (@line-height-computed / 2);\n\n small,\n .small {\n font-size: 75%;\n }\n}\n\nh1, .h1 { font-size: @font-size-h1; }\nh2, .h2 { font-size: @font-size-h2; }\nh3, .h3 { font-size: @font-size-h3; }\nh4, .h4 { font-size: @font-size-h4; }\nh5, .h5 { font-size: @font-size-h5; }\nh6, .h6 { font-size: @font-size-h6; }\n\n\n// Body text\n// -------------------------\n\np {\n margin: 0 0 (@line-height-computed / 2);\n}\n\n.lead {\n margin-bottom: @line-height-computed;\n font-size: floor((@font-size-base * 1.15));\n font-weight: 300;\n line-height: 1.4;\n\n @media (min-width: @screen-sm-min) {\n font-size: (@font-size-base * 1.5);\n }\n}\n\n\n// Emphasis & misc\n// -------------------------\n\n// Ex: (12px small font / 14px base font) * 100% = about 85%\nsmall,\n.small {\n font-size: floor((100% * @font-size-small / @font-size-base));\n}\n\nmark,\n.mark {\n background-color: @state-warning-bg;\n padding: .2em;\n}\n\n// Alignment\n.text-left { text-align: left; }\n.text-right { text-align: right; }\n.text-center { text-align: center; }\n.text-justify { text-align: justify; }\n.text-nowrap { white-space: nowrap; }\n\n// Transformation\n.text-lowercase { text-transform: lowercase; }\n.text-uppercase { text-transform: uppercase; }\n.text-capitalize { text-transform: capitalize; }\n\n// Contextual colors\n.text-muted {\n color: @text-muted;\n}\n.text-primary {\n .text-emphasis-variant(@brand-primary);\n}\n.text-success {\n .text-emphasis-variant(@state-success-text);\n}\n.text-info {\n .text-emphasis-variant(@state-info-text);\n}\n.text-warning {\n .text-emphasis-variant(@state-warning-text);\n}\n.text-danger {\n .text-emphasis-variant(@state-danger-text);\n}\n\n// Contextual backgrounds\n// For now we'll leave these alongside the text classes until v4 when we can\n// safely shift things around (per SemVer rules).\n.bg-primary {\n // Given the contrast here, this is the only class to have its color inverted\n // automatically.\n color: #fff;\n .bg-variant(@brand-primary);\n}\n.bg-success {\n .bg-variant(@state-success-bg);\n}\n.bg-info {\n .bg-variant(@state-info-bg);\n}\n.bg-warning {\n .bg-variant(@state-warning-bg);\n}\n.bg-danger {\n .bg-variant(@state-danger-bg);\n}\n\n\n// Page header\n// -------------------------\n\n.page-header {\n padding-bottom: ((@line-height-computed / 2) - 1);\n margin: (@line-height-computed * 2) 0 @line-height-computed;\n border-bottom: 1px solid @page-header-border-color;\n}\n\n\n// Lists\n// -------------------------\n\n// Unordered and Ordered lists\nul,\nol {\n margin-top: 0;\n margin-bottom: (@line-height-computed / 2);\n ul,\n ol {\n margin-bottom: 0;\n }\n}\n\n// List options\n\n// Unstyled keeps list items block level, just removes default browser padding and list-style\n.list-unstyled {\n padding-left: 0;\n list-style: none;\n}\n\n// Inline turns list items into inline-block\n.list-inline {\n .list-unstyled();\n margin-left: -5px;\n\n > li {\n display: inline-block;\n padding-left: 5px;\n padding-right: 5px;\n }\n}\n\n// Description Lists\ndl {\n margin-top: 0; // Remove browser default\n margin-bottom: @line-height-computed;\n}\ndt,\ndd {\n line-height: @line-height-base;\n}\ndt {\n font-weight: bold;\n}\ndd {\n margin-left: 0; // Undo browser default\n}\n\n// Horizontal description lists\n//\n// Defaults to being stacked without any of the below styles applied, until the\n// grid breakpoint is reached (default of ~768px).\n\n.dl-horizontal {\n dd {\n &:extend(.clearfix all); // Clear the floated `dt` if an empty `dd` is present\n }\n\n @media (min-width: @grid-float-breakpoint) {\n dt {\n float: left;\n width: (@dl-horizontal-offset - 20);\n clear: left;\n text-align: right;\n .text-overflow();\n }\n dd {\n margin-left: @dl-horizontal-offset;\n }\n }\n}\n\n\n// Misc\n// -------------------------\n\n// Abbreviations and acronyms\nabbr[title],\n// Add data-* attribute to help out our tooltip plugin, per https://github.com/twbs/bootstrap/issues/5257\nabbr[data-original-title] {\n cursor: help;\n border-bottom: 1px dotted @abbr-border-color;\n}\n.initialism {\n font-size: 90%;\n text-transform: uppercase;\n}\n\n// Blockquotes\nblockquote {\n padding: (@line-height-computed / 2) @line-height-computed;\n margin: 0 0 @line-height-computed;\n font-size: @blockquote-font-size;\n border-left: 5px solid @blockquote-border-color;\n\n p,\n ul,\n ol {\n &:last-child {\n margin-bottom: 0;\n }\n }\n\n // Note: Deprecated small and .small as of v3.1.0\n // Context: https://github.com/twbs/bootstrap/issues/11660\n footer,\n small,\n .small {\n display: block;\n font-size: 80%; // back to default font-size\n line-height: @line-height-base;\n color: @blockquote-small-color;\n\n &:before {\n content: '\\2014 \\00A0'; // em dash, nbsp\n }\n }\n}\n\n// Opposite alignment of blockquote\n//\n// Heads up: `blockquote.pull-right` has been deprecated as of v3.1.0.\n.blockquote-reverse,\nblockquote.pull-right {\n padding-right: 15px;\n padding-left: 0;\n border-right: 5px solid @blockquote-border-color;\n border-left: 0;\n text-align: right;\n\n // Account for citation\n footer,\n small,\n .small {\n &:before { content: ''; }\n &:after {\n content: '\\00A0 \\2014'; // nbsp, em dash\n }\n }\n}\n\n// Addresses\naddress {\n margin-bottom: @line-height-computed;\n font-style: normal;\n line-height: @line-height-base;\n}\n","// Typography\n\n.text-emphasis-variant(@color) {\n color: @color;\n a&:hover {\n color: darken(@color, 10%);\n }\n}\n","// Contextual backgrounds\n\n.bg-variant(@color) {\n background-color: @color;\n a&:hover {\n background-color: darken(@color, 10%);\n }\n}\n","// Text overflow\n// Requires inline-block or block for proper styling\n\n.text-overflow() {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n","//\n// Code (inline and block)\n// --------------------------------------------------\n\n\n// Inline and block code styles\ncode,\nkbd,\npre,\nsamp {\n font-family: @font-family-monospace;\n}\n\n// Inline code\ncode {\n padding: 2px 4px;\n font-size: 90%;\n color: @code-color;\n background-color: @code-bg;\n border-radius: @border-radius-base;\n}\n\n// User input typically entered via keyboard\nkbd {\n padding: 2px 4px;\n font-size: 90%;\n color: @kbd-color;\n background-color: @kbd-bg;\n border-radius: @border-radius-small;\n box-shadow: inset 0 -1px 0 rgba(0,0,0,.25);\n\n kbd {\n padding: 0;\n font-size: 100%;\n font-weight: bold;\n box-shadow: none;\n }\n}\n\n// Blocks of code\npre {\n display: block;\n padding: ((@line-height-computed - 1) / 2);\n margin: 0 0 (@line-height-computed / 2);\n font-size: (@font-size-base - 1); // 14px to 13px\n line-height: @line-height-base;\n word-break: break-all;\n word-wrap: break-word;\n color: @pre-color;\n background-color: @pre-bg;\n border: 1px solid @pre-border-color;\n border-radius: @border-radius-base;\n\n // Account for some code outputs that place code tags in pre tags\n code {\n padding: 0;\n font-size: inherit;\n color: inherit;\n white-space: pre-wrap;\n background-color: transparent;\n border-radius: 0;\n }\n}\n\n// Enable scrollable blocks of code\n.pre-scrollable {\n max-height: @pre-scrollable-max-height;\n overflow-y: scroll;\n}\n","//\n// Grid system\n// --------------------------------------------------\n\n\n// Container widths\n//\n// Set the container width, and override it for fixed navbars in media queries.\n\n.container {\n .container-fixed();\n\n @media (min-width: @screen-sm-min) {\n width: @container-sm;\n }\n @media (min-width: @screen-md-min) {\n width: @container-md;\n }\n @media (min-width: @screen-lg-min) {\n width: @container-lg;\n }\n}\n\n\n// Fluid container\n//\n// Utilizes the mixin meant for fixed width containers, but without any defined\n// width for fluid, full width layouts.\n\n.container-fluid {\n .container-fixed();\n}\n\n\n// Row\n//\n// Rows contain and clear the floats of your columns.\n\n.row {\n .make-row();\n}\n\n\n// Columns\n//\n// Common styles for small and large grid columns\n\n.make-grid-columns();\n\n\n// Extra small grid\n//\n// Columns, offsets, pushes, and pulls for extra small devices like\n// smartphones.\n\n.make-grid(xs);\n\n\n// Small grid\n//\n// Columns, offsets, pushes, and pulls for the small device range, from phones\n// to tablets.\n\n@media (min-width: @screen-sm-min) {\n .make-grid(sm);\n}\n\n\n// Medium grid\n//\n// Columns, offsets, pushes, and pulls for the desktop device range.\n\n@media (min-width: @screen-md-min) {\n .make-grid(md);\n}\n\n\n// Large grid\n//\n// Columns, offsets, pushes, and pulls for the large desktop device range.\n\n@media (min-width: @screen-lg-min) {\n .make-grid(lg);\n}\n","// Grid system\n//\n// Generate semantic grid columns with these mixins.\n\n// Centered container element\n.container-fixed(@gutter: @grid-gutter-width) {\n margin-right: auto;\n margin-left: auto;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n &:extend(.clearfix all);\n}\n\n// Creates a wrapper for a series of columns\n.make-row(@gutter: @grid-gutter-width) {\n margin-left: (@gutter / -2);\n margin-right: (@gutter / -2);\n &:extend(.clearfix all);\n}\n\n// Generate the extra small columns\n.make-xs-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n float: left;\n width: percentage((@columns / @grid-columns));\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n}\n.make-xs-column-offset(@columns) {\n margin-left: percentage((@columns / @grid-columns));\n}\n.make-xs-column-push(@columns) {\n left: percentage((@columns / @grid-columns));\n}\n.make-xs-column-pull(@columns) {\n right: percentage((@columns / @grid-columns));\n}\n\n// Generate the small columns\n.make-sm-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-sm-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-offset(@columns) {\n @media (min-width: @screen-sm-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-push(@columns) {\n @media (min-width: @screen-sm-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-sm-column-pull(@columns) {\n @media (min-width: @screen-sm-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n\n// Generate the medium columns\n.make-md-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-md-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-offset(@columns) {\n @media (min-width: @screen-md-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-push(@columns) {\n @media (min-width: @screen-md-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-md-column-pull(@columns) {\n @media (min-width: @screen-md-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n\n// Generate the large columns\n.make-lg-column(@columns; @gutter: @grid-gutter-width) {\n position: relative;\n min-height: 1px;\n padding-left: (@gutter / 2);\n padding-right: (@gutter / 2);\n\n @media (min-width: @screen-lg-min) {\n float: left;\n width: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-offset(@columns) {\n @media (min-width: @screen-lg-min) {\n margin-left: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-push(@columns) {\n @media (min-width: @screen-lg-min) {\n left: percentage((@columns / @grid-columns));\n }\n}\n.make-lg-column-pull(@columns) {\n @media (min-width: @screen-lg-min) {\n right: percentage((@columns / @grid-columns));\n }\n}\n","// Framework grid generation\n//\n// Used only by Bootstrap to generate the correct number of grid classes given\n// any value of `@grid-columns`.\n\n.make-grid-columns() {\n // Common styles for all sizes of grid columns, widths 1-12\n .col(@index) { // initial\n @item: ~\".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}\";\n .col((@index + 1), @item);\n }\n .col(@index, @list) when (@index =< @grid-columns) { // general; \"=<\" isn't a typo\n @item: ~\".col-xs-@{index}, .col-sm-@{index}, .col-md-@{index}, .col-lg-@{index}\";\n .col((@index + 1), ~\"@{list}, @{item}\");\n }\n .col(@index, @list) when (@index > @grid-columns) { // terminal\n @{list} {\n position: relative;\n // Prevent columns from collapsing when empty\n min-height: 1px;\n // Inner gutter via padding\n padding-left: (@grid-gutter-width / 2);\n padding-right: (@grid-gutter-width / 2);\n }\n }\n .col(1); // kickstart it\n}\n\n.float-grid-columns(@class) {\n .col(@index) { // initial\n @item: ~\".col-@{class}-@{index}\";\n .col((@index + 1), @item);\n }\n .col(@index, @list) when (@index =< @grid-columns) { // general\n @item: ~\".col-@{class}-@{index}\";\n .col((@index + 1), ~\"@{list}, @{item}\");\n }\n .col(@index, @list) when (@index > @grid-columns) { // terminal\n @{list} {\n float: left;\n }\n }\n .col(1); // kickstart it\n}\n\n.calc-grid-column(@index, @class, @type) when (@type = width) and (@index > 0) {\n .col-@{class}-@{index} {\n width: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = push) and (@index > 0) {\n .col-@{class}-push-@{index} {\n left: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = push) and (@index = 0) {\n .col-@{class}-push-0 {\n left: auto;\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = pull) and (@index > 0) {\n .col-@{class}-pull-@{index} {\n right: percentage((@index / @grid-columns));\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = pull) and (@index = 0) {\n .col-@{class}-pull-0 {\n right: auto;\n }\n}\n.calc-grid-column(@index, @class, @type) when (@type = offset) {\n .col-@{class}-offset-@{index} {\n margin-left: percentage((@index / @grid-columns));\n }\n}\n\n// Basic looping in LESS\n.loop-grid-columns(@index, @class, @type) when (@index >= 0) {\n .calc-grid-column(@index, @class, @type);\n // next iteration\n .loop-grid-columns((@index - 1), @class, @type);\n}\n\n// Create grid for specific class\n.make-grid(@class) {\n .float-grid-columns(@class);\n .loop-grid-columns(@grid-columns, @class, width);\n .loop-grid-columns(@grid-columns, @class, pull);\n .loop-grid-columns(@grid-columns, @class, push);\n .loop-grid-columns(@grid-columns, @class, offset);\n}\n","//\n// Tables\n// --------------------------------------------------\n\n\ntable {\n background-color: @table-bg;\n}\ncaption {\n padding-top: @table-cell-padding;\n padding-bottom: @table-cell-padding;\n color: @text-muted;\n text-align: left;\n}\nth {\n text-align: left;\n}\n\n\n// Baseline styles\n\n.table {\n width: 100%;\n max-width: 100%;\n margin-bottom: @line-height-computed;\n // Cells\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n padding: @table-cell-padding;\n line-height: @line-height-base;\n vertical-align: top;\n border-top: 1px solid @table-border-color;\n }\n }\n }\n // Bottom align for column headings\n > thead > tr > th {\n vertical-align: bottom;\n border-bottom: 2px solid @table-border-color;\n }\n // Remove top border from thead by default\n > caption + thead,\n > colgroup + thead,\n > thead:first-child {\n > tr:first-child {\n > th,\n > td {\n border-top: 0;\n }\n }\n }\n // Account for multiple tbody instances\n > tbody + tbody {\n border-top: 2px solid @table-border-color;\n }\n\n // Nesting\n .table {\n background-color: @body-bg;\n }\n}\n\n\n// Condensed table w/ half padding\n\n.table-condensed {\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n padding: @table-condensed-cell-padding;\n }\n }\n }\n}\n\n\n// Bordered version\n//\n// Add borders all around the table and between all the columns.\n\n.table-bordered {\n border: 1px solid @table-border-color;\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n border: 1px solid @table-border-color;\n }\n }\n }\n > thead > tr {\n > th,\n > td {\n border-bottom-width: 2px;\n }\n }\n}\n\n\n// Zebra-striping\n//\n// Default zebra-stripe styles (alternating gray and transparent backgrounds)\n\n.table-striped {\n > tbody > tr:nth-of-type(odd) {\n background-color: @table-bg-accent;\n }\n}\n\n\n// Hover effect\n//\n// Placed here since it has to come after the potential zebra striping\n\n.table-hover {\n > tbody > tr:hover {\n background-color: @table-bg-hover;\n }\n}\n\n\n// Table cell sizing\n//\n// Reset default table behavior\n\ntable col[class*=\"col-\"] {\n position: static; // Prevent border hiding in Firefox and IE9-11 (see https://github.com/twbs/bootstrap/issues/11623)\n float: none;\n display: table-column;\n}\ntable {\n td,\n th {\n &[class*=\"col-\"] {\n position: static; // Prevent border hiding in Firefox and IE9-11 (see https://github.com/twbs/bootstrap/issues/11623)\n float: none;\n display: table-cell;\n }\n }\n}\n\n\n// Table backgrounds\n//\n// Exact selectors below required to override `.table-striped` and prevent\n// inheritance to nested tables.\n\n// Generate the contextual variants\n.table-row-variant(active; @table-bg-active);\n.table-row-variant(success; @state-success-bg);\n.table-row-variant(info; @state-info-bg);\n.table-row-variant(warning; @state-warning-bg);\n.table-row-variant(danger; @state-danger-bg);\n\n\n// Responsive tables\n//\n// Wrap your tables in `.table-responsive` and we'll make them mobile friendly\n// by enabling horizontal scrolling. Only applies <768px. Everything above that\n// will display normally.\n\n.table-responsive {\n overflow-x: auto;\n min-height: 0.01%; // Workaround for IE9 bug (see https://github.com/twbs/bootstrap/issues/14837)\n\n @media screen and (max-width: @screen-xs-max) {\n width: 100%;\n margin-bottom: (@line-height-computed * 0.75);\n overflow-y: hidden;\n -ms-overflow-style: -ms-autohiding-scrollbar;\n border: 1px solid @table-border-color;\n\n // Tighten up spacing\n > .table {\n margin-bottom: 0;\n\n // Ensure the content doesn't wrap\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th,\n > td {\n white-space: nowrap;\n }\n }\n }\n }\n\n // Special overrides for the bordered tables\n > .table-bordered {\n border: 0;\n\n // Nuke the appropriate borders so that the parent can handle them\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th:first-child,\n > td:first-child {\n border-left: 0;\n }\n > th:last-child,\n > td:last-child {\n border-right: 0;\n }\n }\n }\n\n // Only nuke the last row's bottom-border in `tbody` and `tfoot` since\n // chances are there will be only one `tr` in a `thead` and that would\n // remove the border altogether.\n > tbody,\n > tfoot {\n > tr:last-child {\n > th,\n > td {\n border-bottom: 0;\n }\n }\n }\n\n }\n }\n}\n","// Tables\n\n.table-row-variant(@state; @background) {\n // Exact selectors below required to override `.table-striped` and prevent\n // inheritance to nested tables.\n .table > thead > tr,\n .table > tbody > tr,\n .table > tfoot > tr {\n > td.@{state},\n > th.@{state},\n &.@{state} > td,\n &.@{state} > th {\n background-color: @background;\n }\n }\n\n // Hover states for `.table-hover`\n // Note: this is not available for cells or rows within `thead` or `tfoot`.\n .table-hover > tbody > tr {\n > td.@{state}:hover,\n > th.@{state}:hover,\n &.@{state}:hover > td,\n &:hover > .@{state},\n &.@{state}:hover > th {\n background-color: darken(@background, 5%);\n }\n }\n}\n","//\n// Forms\n// --------------------------------------------------\n\n\n// Normalize non-controls\n//\n// Restyle and baseline non-control form elements.\n\nfieldset {\n padding: 0;\n margin: 0;\n border: 0;\n // Chrome and Firefox set a `min-width: min-content;` on fieldsets,\n // so we reset that to ensure it behaves more like a standard block element.\n // See https://github.com/twbs/bootstrap/issues/12359.\n min-width: 0;\n}\n\nlegend {\n display: block;\n width: 100%;\n padding: 0;\n margin-bottom: @line-height-computed;\n font-size: (@font-size-base * 1.5);\n line-height: inherit;\n color: @legend-color;\n border: 0;\n border-bottom: 1px solid @legend-border-color;\n}\n\nlabel {\n display: inline-block;\n max-width: 100%; // Force IE8 to wrap long content (see https://github.com/twbs/bootstrap/issues/13141)\n margin-bottom: 5px;\n font-weight: bold;\n}\n\n\n// Normalize form controls\n//\n// While most of our form styles require extra classes, some basic normalization\n// is required to ensure optimum display with or without those classes to better\n// address browser inconsistencies.\n\n// Override content-box in Normalize (* isn't specific enough)\ninput[type=\"search\"] {\n .box-sizing(border-box);\n}\n\n// Position radios and checkboxes better\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n margin: 4px 0 0;\n margin-top: 1px \\9; // IE8-9\n line-height: normal;\n}\n\n// Set the height of file controls to match text inputs\ninput[type=\"file\"] {\n display: block;\n}\n\n// Make range inputs behave like textual form controls\ninput[type=\"range\"] {\n display: block;\n width: 100%;\n}\n\n// Make multiple select elements height not fixed\nselect[multiple],\nselect[size] {\n height: auto;\n}\n\n// Focus for file, radio, and checkbox\ninput[type=\"file\"]:focus,\ninput[type=\"radio\"]:focus,\ninput[type=\"checkbox\"]:focus {\n .tab-focus();\n}\n\n// Adjust output element\noutput {\n display: block;\n padding-top: (@padding-base-vertical + 1);\n font-size: @font-size-base;\n line-height: @line-height-base;\n color: @input-color;\n}\n\n\n// Common form controls\n//\n// Shared size and type resets for form controls. Apply `.form-control` to any\n// of the following form controls:\n//\n// select\n// textarea\n// input[type=\"text\"]\n// input[type=\"password\"]\n// input[type=\"datetime\"]\n// input[type=\"datetime-local\"]\n// input[type=\"date\"]\n// input[type=\"month\"]\n// input[type=\"time\"]\n// input[type=\"week\"]\n// input[type=\"number\"]\n// input[type=\"email\"]\n// input[type=\"url\"]\n// input[type=\"search\"]\n// input[type=\"tel\"]\n// input[type=\"color\"]\n\n.form-control {\n display: block;\n width: 100%;\n height: @input-height-base; // Make inputs at least the height of their button counterpart (base line-height + padding + border)\n padding: @padding-base-vertical @padding-base-horizontal;\n font-size: @font-size-base;\n line-height: @line-height-base;\n color: @input-color;\n background-color: @input-bg;\n background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214\n border: 1px solid @input-border;\n border-radius: @input-border-radius; // Note: This has no effect on <select>s in some browsers, due to the limited stylability of <select>s in CSS.\n .box-shadow(inset 0 1px 1px rgba(0,0,0,.075));\n .transition(~\"border-color ease-in-out .15s, box-shadow ease-in-out .15s\");\n\n // Customize the `:focus` state to imitate native WebKit styles.\n .form-control-focus();\n\n // Placeholder\n .placeholder();\n\n // Disabled and read-only inputs\n //\n // HTML5 says that controls under a fieldset > legend:first-child won't be\n // disabled if the fieldset is disabled. Due to implementation difficulty, we\n // don't honor that edge case; we style them as disabled anyway.\n &[disabled],\n &[readonly],\n fieldset[disabled] & {\n cursor: @cursor-disabled;\n background-color: @input-bg-disabled;\n opacity: 1; // iOS fix for unreadable disabled content; see https://github.com/twbs/bootstrap/issues/11655\n }\n\n // Reset height for `textarea`s\n textarea& {\n height: auto;\n }\n}\n\n\n// Search inputs in iOS\n//\n// This overrides the extra rounded corners on search inputs in iOS so that our\n// `.form-control` class can properly style them. Note that this cannot simply\n// be added to `.form-control` as it's not specific enough. For details, see\n// https://github.com/twbs/bootstrap/issues/11586.\n\ninput[type=\"search\"] {\n -webkit-appearance: none;\n}\n\n\n// Special styles for iOS temporal inputs\n//\n// In Mobile Safari, setting `display: block` on temporal inputs causes the\n// text within the input to become vertically misaligned. As a workaround, we\n// set a pixel line-height that matches the given height of the input, but only\n// for Safari. See https://bugs.webkit.org/show_bug.cgi?id=139848\n\n@media screen and (-webkit-min-device-pixel-ratio: 0) {\n input[type=\"date\"],\n input[type=\"time\"],\n input[type=\"datetime-local\"],\n input[type=\"month\"] {\n line-height: @input-height-base;\n\n &.input-sm,\n .input-group-sm & {\n line-height: @input-height-small;\n }\n\n &.input-lg,\n .input-group-lg & {\n line-height: @input-height-large;\n }\n }\n}\n\n\n// Form groups\n//\n// Designed to help with the organization and spacing of vertical forms. For\n// horizontal forms, use the predefined grid classes.\n\n.form-group {\n margin-bottom: 15px;\n}\n\n\n// Checkboxes and radios\n//\n// Indent the labels to position radios/checkboxes as hanging controls.\n\n.radio,\n.checkbox {\n position: relative;\n display: block;\n margin-top: 10px;\n margin-bottom: 10px;\n\n label {\n min-height: @line-height-computed; // Ensure the input doesn't jump when there is no text\n padding-left: 20px;\n margin-bottom: 0;\n font-weight: normal;\n cursor: pointer;\n }\n}\n.radio input[type=\"radio\"],\n.radio-inline input[type=\"radio\"],\n.checkbox input[type=\"checkbox\"],\n.checkbox-inline input[type=\"checkbox\"] {\n position: absolute;\n margin-left: -20px;\n margin-top: 4px \\9;\n}\n\n.radio + .radio,\n.checkbox + .checkbox {\n margin-top: -5px; // Move up sibling radios or checkboxes for tighter spacing\n}\n\n// Radios and checkboxes on same line\n.radio-inline,\n.checkbox-inline {\n display: inline-block;\n padding-left: 20px;\n margin-bottom: 0;\n vertical-align: middle;\n font-weight: normal;\n cursor: pointer;\n}\n.radio-inline + .radio-inline,\n.checkbox-inline + .checkbox-inline {\n margin-top: 0;\n margin-left: 10px; // space out consecutive inline controls\n}\n\n// Apply same disabled cursor tweak as for inputs\n// Some special care is needed because <label>s don't inherit their parent's `cursor`.\n//\n// Note: Neither radios nor checkboxes can be readonly.\ninput[type=\"radio\"],\ninput[type=\"checkbox\"] {\n &[disabled],\n &.disabled,\n fieldset[disabled] & {\n cursor: @cursor-disabled;\n }\n}\n// These classes are used directly on <label>s\n.radio-inline,\n.checkbox-inline {\n &.disabled,\n fieldset[disabled] & {\n cursor: @cursor-disabled;\n }\n}\n// These classes are used on elements with <label> descendants\n.radio,\n.checkbox {\n &.disabled,\n fieldset[disabled] & {\n label {\n cursor: @cursor-disabled;\n }\n }\n}\n\n\n// Static form control text\n//\n// Apply class to a `p` element to make any string of text align with labels in\n// a horizontal form layout.\n\n.form-control-static {\n // Size it appropriately next to real form controls\n padding-top: (@padding-base-vertical + 1);\n padding-bottom: (@padding-base-vertical + 1);\n // Remove default margin from `p`\n margin-bottom: 0;\n\n &.input-lg,\n &.input-sm {\n padding-left: 0;\n padding-right: 0;\n }\n}\n\n\n// Form control sizing\n//\n// Build on `.form-control` with modifier classes to decrease or increase the\n// height and font-size of form controls.\n//\n// The `.form-group-* form-control` variations are sadly duplicated to avoid the\n// issue documented in https://github.com/twbs/bootstrap/issues/15074.\n\n.input-sm {\n .input-size(@input-height-small; @padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @input-border-radius-small);\n}\n.form-group-sm {\n .form-control {\n .input-size(@input-height-small; @padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @input-border-radius-small);\n }\n .form-control-static {\n height: @input-height-small;\n padding: @padding-small-vertical @padding-small-horizontal;\n font-size: @font-size-small;\n line-height: @line-height-small;\n }\n}\n\n.input-lg {\n .input-size(@input-height-large; @padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @input-border-radius-large);\n}\n.form-group-lg {\n .form-control {\n .input-size(@input-height-large; @padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @input-border-radius-large);\n }\n .form-control-static {\n height: @input-height-large;\n padding: @padding-large-vertical @padding-large-horizontal;\n font-size: @font-size-large;\n line-height: @line-height-large;\n }\n}\n\n\n// Form control feedback states\n//\n// Apply contextual and semantic states to individual form controls.\n\n.has-feedback {\n // Enable absolute positioning\n position: relative;\n\n // Ensure icons don't overlap text\n .form-control {\n padding-right: (@input-height-base * 1.25);\n }\n}\n// Feedback icon (requires .glyphicon classes)\n.form-control-feedback {\n position: absolute;\n top: 0;\n right: 0;\n z-index: 2; // Ensure icon is above input groups\n display: block;\n width: @input-height-base;\n height: @input-height-base;\n line-height: @input-height-base;\n text-align: center;\n pointer-events: none;\n}\n.input-lg + .form-control-feedback {\n width: @input-height-large;\n height: @input-height-large;\n line-height: @input-height-large;\n}\n.input-sm + .form-control-feedback {\n width: @input-height-small;\n height: @input-height-small;\n line-height: @input-height-small;\n}\n\n// Feedback states\n.has-success {\n .form-control-validation(@state-success-text; @state-success-text; @state-success-bg);\n}\n.has-warning {\n .form-control-validation(@state-warning-text; @state-warning-text; @state-warning-bg);\n}\n.has-error {\n .form-control-validation(@state-danger-text; @state-danger-text; @state-danger-bg);\n}\n\n// Reposition feedback icon if input has visible label above\n.has-feedback label {\n\n & ~ .form-control-feedback {\n top: (@line-height-computed + 5); // Height of the `label` and its margin\n }\n &.sr-only ~ .form-control-feedback {\n top: 0;\n }\n}\n\n\n// Help text\n//\n// Apply to any element you wish to create light text for placement immediately\n// below a form control. Use for general help, formatting, or instructional text.\n\n.help-block {\n display: block; // account for any element using help-block\n margin-top: 5px;\n margin-bottom: 10px;\n color: lighten(@text-color, 25%); // lighten the text some for contrast\n}\n\n\n// Inline forms\n//\n// Make forms appear inline(-block) by adding the `.form-inline` class. Inline\n// forms begin stacked on extra small (mobile) devices and then go inline when\n// viewports reach <768px.\n//\n// Requires wrapping inputs and labels with `.form-group` for proper display of\n// default HTML form controls and our custom form controls (e.g., input groups).\n//\n// Heads up! This is mixin-ed into `.navbar-form` in navbars.less.\n\n.form-inline {\n\n // Kick in the inline\n @media (min-width: @screen-sm-min) {\n // Inline-block all the things for \"inline\"\n .form-group {\n display: inline-block;\n margin-bottom: 0;\n vertical-align: middle;\n }\n\n // In navbar-form, allow folks to *not* use `.form-group`\n .form-control {\n display: inline-block;\n width: auto; // Prevent labels from stacking above inputs in `.form-group`\n vertical-align: middle;\n }\n\n // Make static controls behave like regular ones\n .form-control-static {\n display: inline-block;\n }\n\n .input-group {\n display: inline-table;\n vertical-align: middle;\n\n .input-group-addon,\n .input-group-btn,\n .form-control {\n width: auto;\n }\n }\n\n // Input groups need that 100% width though\n .input-group > .form-control {\n width: 100%;\n }\n\n .control-label {\n margin-bottom: 0;\n vertical-align: middle;\n }\n\n // Remove default margin on radios/checkboxes that were used for stacking, and\n // then undo the floating of radios and checkboxes to match.\n .radio,\n .checkbox {\n display: inline-block;\n margin-top: 0;\n margin-bottom: 0;\n vertical-align: middle;\n\n label {\n padding-left: 0;\n }\n }\n .radio input[type=\"radio\"],\n .checkbox input[type=\"checkbox\"] {\n position: relative;\n margin-left: 0;\n }\n\n // Re-override the feedback icon.\n .has-feedback .form-control-feedback {\n top: 0;\n }\n }\n}\n\n\n// Horizontal forms\n//\n// Horizontal forms are built on grid classes and allow you to create forms with\n// labels on the left and inputs on the right.\n\n.form-horizontal {\n\n // Consistent vertical alignment of radios and checkboxes\n //\n // Labels also get some reset styles, but that is scoped to a media query below.\n .radio,\n .checkbox,\n .radio-inline,\n .checkbox-inline {\n margin-top: 0;\n margin-bottom: 0;\n padding-top: (@padding-base-vertical + 1); // Default padding plus a border\n }\n // Account for padding we're adding to ensure the alignment and of help text\n // and other content below items\n .radio,\n .checkbox {\n min-height: (@line-height-computed + (@padding-base-vertical + 1));\n }\n\n // Make form groups behave like rows\n .form-group {\n .make-row();\n }\n\n // Reset spacing and right align labels, but scope to media queries so that\n // labels on narrow viewports stack the same as a default form example.\n @media (min-width: @screen-sm-min) {\n .control-label {\n text-align: right;\n margin-bottom: 0;\n padding-top: (@padding-base-vertical + 1); // Default padding plus a border\n }\n }\n\n // Validation states\n //\n // Reposition the icon because it's now within a grid column and columns have\n // `position: relative;` on them. Also accounts for the grid gutter padding.\n .has-feedback .form-control-feedback {\n right: (@grid-gutter-width / 2);\n }\n\n // Form group sizes\n //\n // Quick utility class for applying `.input-lg` and `.input-sm` styles to the\n // inputs and labels within a `.form-group`.\n .form-group-lg {\n @media (min-width: @screen-sm-min) {\n .control-label {\n padding-top: ((@padding-large-vertical * @line-height-large) + 1);\n }\n }\n }\n .form-group-sm {\n @media (min-width: @screen-sm-min) {\n .control-label {\n padding-top: (@padding-small-vertical + 1);\n }\n }\n }\n}\n","// Form validation states\n//\n// Used in forms.less to generate the form validation CSS for warnings, errors,\n// and successes.\n\n.form-control-validation(@text-color: #555; @border-color: #ccc; @background-color: #f5f5f5) {\n // Color the label and help text\n .help-block,\n .control-label,\n .radio,\n .checkbox,\n .radio-inline,\n .checkbox-inline,\n &.radio label,\n &.checkbox label,\n &.radio-inline label,\n &.checkbox-inline label {\n color: @text-color;\n }\n // Set the border and box shadow on specific inputs to match\n .form-control {\n border-color: @border-color;\n .box-shadow(inset 0 1px 1px rgba(0,0,0,.075)); // Redeclare so transitions work\n &:focus {\n border-color: darken(@border-color, 10%);\n @shadow: inset 0 1px 1px rgba(0,0,0,.075), 0 0 6px lighten(@border-color, 20%);\n .box-shadow(@shadow);\n }\n }\n // Set validation states also for addons\n .input-group-addon {\n color: @text-color;\n border-color: @border-color;\n background-color: @background-color;\n }\n // Optional feedback icon\n .form-control-feedback {\n color: @text-color;\n }\n}\n\n\n// Form control focus state\n//\n// Generate a customized focus state and for any input with the specified color,\n// which defaults to the `@input-border-focus` variable.\n//\n// We highly encourage you to not customize the default value, but instead use\n// this to tweak colors on an as-needed basis. This aesthetic change is based on\n// WebKit's default styles, but applicable to a wider range of browsers. Its\n// usability and accessibility should be taken into account with any change.\n//\n// Example usage: change the default blue border and shadow to white for better\n// contrast against a dark gray background.\n.form-control-focus(@color: @input-border-focus) {\n @color-rgba: rgba(red(@color), green(@color), blue(@color), .6);\n &:focus {\n border-color: @color;\n outline: 0;\n .box-shadow(~\"inset 0 1px 1px rgba(0,0,0,.075), 0 0 8px @{color-rgba}\");\n }\n}\n\n// Form control sizing\n//\n// Relative text size, padding, and border-radii changes for form controls. For\n// horizontal sizing, wrap controls in the predefined grid classes. `<select>`\n// element gets special love because it's special, and that's a fact!\n.input-size(@input-height; @padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {\n height: @input-height;\n padding: @padding-vertical @padding-horizontal;\n font-size: @font-size;\n line-height: @line-height;\n border-radius: @border-radius;\n\n select& {\n height: @input-height;\n line-height: @input-height;\n }\n\n textarea&,\n select[multiple]& {\n height: auto;\n }\n}\n","//\n// Buttons\n// --------------------------------------------------\n\n\n// Base styles\n// --------------------------------------------------\n\n.btn {\n display: inline-block;\n margin-bottom: 0; // For input.btn\n font-weight: @btn-font-weight;\n text-align: center;\n vertical-align: middle;\n touch-action: manipulation;\n cursor: pointer;\n background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214\n border: 1px solid transparent;\n white-space: nowrap;\n .button-size(@padding-base-vertical; @padding-base-horizontal; @font-size-base; @line-height-base; @border-radius-base);\n .user-select(none);\n\n &,\n &:active,\n &.active {\n &:focus,\n &.focus {\n .tab-focus();\n }\n }\n\n &:hover,\n &:focus,\n &.focus {\n color: @btn-default-color;\n text-decoration: none;\n }\n\n &:active,\n &.active {\n outline: 0;\n background-image: none;\n .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n }\n\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n cursor: @cursor-disabled;\n pointer-events: none; // Future-proof disabling of clicks\n .opacity(.65);\n .box-shadow(none);\n }\n}\n\n\n// Alternate buttons\n// --------------------------------------------------\n\n.btn-default {\n .button-variant(@btn-default-color; @btn-default-bg; @btn-default-border);\n}\n.btn-primary {\n .button-variant(@btn-primary-color; @btn-primary-bg; @btn-primary-border);\n}\n// Success appears as green\n.btn-success {\n .button-variant(@btn-success-color; @btn-success-bg; @btn-success-border);\n}\n// Info appears as blue-green\n.btn-info {\n .button-variant(@btn-info-color; @btn-info-bg; @btn-info-border);\n}\n// Warning appears as orange\n.btn-warning {\n .button-variant(@btn-warning-color; @btn-warning-bg; @btn-warning-border);\n}\n// Danger and error appear as red\n.btn-danger {\n .button-variant(@btn-danger-color; @btn-danger-bg; @btn-danger-border);\n}\n\n\n// Link buttons\n// -------------------------\n\n// Make a button look and behave like a link\n.btn-link {\n color: @link-color;\n font-weight: normal;\n border-radius: 0;\n\n &,\n &:active,\n &.active,\n &[disabled],\n fieldset[disabled] & {\n background-color: transparent;\n .box-shadow(none);\n }\n &,\n &:hover,\n &:focus,\n &:active {\n border-color: transparent;\n }\n &:hover,\n &:focus {\n color: @link-hover-color;\n text-decoration: @link-hover-decoration;\n background-color: transparent;\n }\n &[disabled],\n fieldset[disabled] & {\n &:hover,\n &:focus {\n color: @btn-link-disabled-color;\n text-decoration: none;\n }\n }\n}\n\n\n// Button Sizes\n// --------------------------------------------------\n\n.btn-lg {\n // line-height: ensure even-numbered height of button next to large input\n .button-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @border-radius-large);\n}\n.btn-sm {\n // line-height: ensure proper height of button next to small input\n .button-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @line-height-small; @border-radius-small);\n}\n.btn-xs {\n .button-size(@padding-xs-vertical; @padding-xs-horizontal; @font-size-small; @line-height-small; @border-radius-small);\n}\n\n\n// Block button\n// --------------------------------------------------\n\n.btn-block {\n display: block;\n width: 100%;\n}\n\n// Vertically space out multiple block buttons\n.btn-block + .btn-block {\n margin-top: 5px;\n}\n\n// Specificity overrides\ninput[type=\"submit\"],\ninput[type=\"reset\"],\ninput[type=\"button\"] {\n &.btn-block {\n width: 100%;\n }\n}\n","// Button variants\n//\n// Easily pump out default styles, as well as :hover, :focus, :active,\n// and disabled options for all buttons\n\n.button-variant(@color; @background; @border) {\n color: @color;\n background-color: @background;\n border-color: @border;\n\n &:hover,\n &:focus,\n &.focus,\n &:active,\n &.active,\n .open > .dropdown-toggle& {\n color: @color;\n background-color: darken(@background, 10%);\n border-color: darken(@border, 12%);\n }\n &:active,\n &.active,\n .open > .dropdown-toggle& {\n background-image: none;\n }\n &.disabled,\n &[disabled],\n fieldset[disabled] & {\n &,\n &:hover,\n &:focus,\n &.focus,\n &:active,\n &.active {\n background-color: @background;\n border-color: @border;\n }\n }\n\n .badge {\n color: @background;\n background-color: @color;\n }\n}\n\n// Button sizes\n.button-size(@padding-vertical; @padding-horizontal; @font-size; @line-height; @border-radius) {\n padding: @padding-vertical @padding-horizontal;\n font-size: @font-size;\n line-height: @line-height;\n border-radius: @border-radius;\n}\n","// Opacity\n\n.opacity(@opacity) {\n opacity: @opacity;\n // IE8 filter\n @opacity-ie: (@opacity * 100);\n filter: ~\"alpha(opacity=@{opacity-ie})\";\n}\n","//\n// Component animations\n// --------------------------------------------------\n\n// Heads up!\n//\n// We don't use the `.opacity()` mixin here since it causes a bug with text\n// fields in IE7-8. Source: https://github.com/twbs/bootstrap/pull/3552.\n\n.fade {\n opacity: 0;\n .transition(opacity .15s linear);\n &.in {\n opacity: 1;\n }\n}\n\n.collapse {\n display: none;\n visibility: hidden;\n\n &.in { display: block; visibility: visible; }\n tr&.in { display: table-row; }\n tbody&.in { display: table-row-group; }\n}\n\n.collapsing {\n position: relative;\n height: 0;\n overflow: hidden;\n .transition-property(~\"height, visibility\");\n .transition-duration(.35s);\n .transition-timing-function(ease);\n}\n","//\n// Dropdown menus\n// --------------------------------------------------\n\n\n// Dropdown arrow/caret\n.caret {\n display: inline-block;\n width: 0;\n height: 0;\n margin-left: 2px;\n vertical-align: middle;\n border-top: @caret-width-base solid;\n border-right: @caret-width-base solid transparent;\n border-left: @caret-width-base solid transparent;\n}\n\n// The dropdown wrapper (div)\n.dropup,\n.dropdown {\n position: relative;\n}\n\n// Prevent the focus on the dropdown toggle when closing dropdowns\n.dropdown-toggle:focus {\n outline: 0;\n}\n\n// The dropdown menu (ul)\n.dropdown-menu {\n position: absolute;\n top: 100%;\n left: 0;\n z-index: @zindex-dropdown;\n display: none; // none by default, but block on \"open\" of the menu\n float: left;\n min-width: 160px;\n padding: 5px 0;\n margin: 2px 0 0; // override default ul\n list-style: none;\n font-size: @font-size-base;\n text-align: left; // Ensures proper alignment if parent has it changed (e.g., modal footer)\n background-color: @dropdown-bg;\n border: 1px solid @dropdown-fallback-border; // IE8 fallback\n border: 1px solid @dropdown-border;\n border-radius: @border-radius-base;\n .box-shadow(0 6px 12px rgba(0,0,0,.175));\n background-clip: padding-box;\n\n // Aligns the dropdown menu to right\n //\n // Deprecated as of 3.1.0 in favor of `.dropdown-menu-[dir]`\n &.pull-right {\n right: 0;\n left: auto;\n }\n\n // Dividers (basically an hr) within the dropdown\n .divider {\n .nav-divider(@dropdown-divider-bg);\n }\n\n // Links within the dropdown menu\n > li > a {\n display: block;\n padding: 3px 20px;\n clear: both;\n font-weight: normal;\n line-height: @line-height-base;\n color: @dropdown-link-color;\n white-space: nowrap; // prevent links from randomly breaking onto new lines\n }\n}\n\n// Hover/Focus state\n.dropdown-menu > li > a {\n &:hover,\n &:focus {\n text-decoration: none;\n color: @dropdown-link-hover-color;\n background-color: @dropdown-link-hover-bg;\n }\n}\n\n// Active state\n.dropdown-menu > .active > a {\n &,\n &:hover,\n &:focus {\n color: @dropdown-link-active-color;\n text-decoration: none;\n outline: 0;\n background-color: @dropdown-link-active-bg;\n }\n}\n\n// Disabled state\n//\n// Gray out text and ensure the hover/focus state remains gray\n\n.dropdown-menu > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @dropdown-link-disabled-color;\n }\n\n // Nuke hover/focus effects\n &:hover,\n &:focus {\n text-decoration: none;\n background-color: transparent;\n background-image: none; // Remove CSS gradient\n .reset-filter();\n cursor: @cursor-disabled;\n }\n}\n\n// Open state for the dropdown\n.open {\n // Show the menu\n > .dropdown-menu {\n display: block;\n }\n\n // Remove the outline when :focus is triggered\n > a {\n outline: 0;\n }\n}\n\n// Menu positioning\n//\n// Add extra class to `.dropdown-menu` to flip the alignment of the dropdown\n// menu with the parent.\n.dropdown-menu-right {\n left: auto; // Reset the default from `.dropdown-menu`\n right: 0;\n}\n// With v3, we enabled auto-flipping if you have a dropdown within a right\n// aligned nav component. To enable the undoing of that, we provide an override\n// to restore the default dropdown menu alignment.\n//\n// This is only for left-aligning a dropdown menu within a `.navbar-right` or\n// `.pull-right` nav component.\n.dropdown-menu-left {\n left: 0;\n right: auto;\n}\n\n// Dropdown section headers\n.dropdown-header {\n display: block;\n padding: 3px 20px;\n font-size: @font-size-small;\n line-height: @line-height-base;\n color: @dropdown-header-color;\n white-space: nowrap; // as with > li > a\n}\n\n// Backdrop to catch body clicks on mobile, etc.\n.dropdown-backdrop {\n position: fixed;\n left: 0;\n right: 0;\n bottom: 0;\n top: 0;\n z-index: (@zindex-dropdown - 10);\n}\n\n// Right aligned dropdowns\n.pull-right > .dropdown-menu {\n right: 0;\n left: auto;\n}\n\n// Allow for dropdowns to go bottom up (aka, dropup-menu)\n//\n// Just add .dropup after the standard .dropdown class and you're set, bro.\n// TODO: abstract this so that the navbar fixed styles are not placed here?\n\n.dropup,\n.navbar-fixed-bottom .dropdown {\n // Reverse the caret\n .caret {\n border-top: 0;\n border-bottom: @caret-width-base solid;\n content: \"\";\n }\n // Different positioning for bottom up menu\n .dropdown-menu {\n top: auto;\n bottom: 100%;\n margin-bottom: 2px;\n }\n}\n\n\n// Component alignment\n//\n// Reiterate per navbar.less and the modified component alignment there.\n\n@media (min-width: @grid-float-breakpoint) {\n .navbar-right {\n .dropdown-menu {\n .dropdown-menu-right();\n }\n // Necessary for overrides of the default right aligned menu.\n // Will remove come v4 in all likelihood.\n .dropdown-menu-left {\n .dropdown-menu-left();\n }\n }\n}\n","// Horizontal dividers\n//\n// Dividers (basically an hr) within dropdowns and nav lists\n\n.nav-divider(@color: #e5e5e5) {\n height: 1px;\n margin: ((@line-height-computed / 2) - 1) 0;\n overflow: hidden;\n background-color: @color;\n}\n","// Reset filters for IE\n//\n// When you need to remove a gradient background, do not forget to use this to reset\n// the IE filter for IE9 and below.\n\n.reset-filter() {\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(enabled = false)\"));\n}\n","//\n// Button groups\n// --------------------------------------------------\n\n// Make the div behave like a button\n.btn-group,\n.btn-group-vertical {\n position: relative;\n display: inline-block;\n vertical-align: middle; // match .btn alignment given font-size hack above\n > .btn {\n position: relative;\n float: left;\n // Bring the \"active\" button to the front\n &:hover,\n &:focus,\n &:active,\n &.active {\n z-index: 2;\n }\n }\n}\n\n// Prevent double borders when buttons are next to each other\n.btn-group {\n .btn + .btn,\n .btn + .btn-group,\n .btn-group + .btn,\n .btn-group + .btn-group {\n margin-left: -1px;\n }\n}\n\n// Optional: Group multiple button groups together for a toolbar\n.btn-toolbar {\n margin-left: -5px; // Offset the first child's margin\n &:extend(.clearfix all);\n\n .btn-group,\n .input-group {\n float: left;\n }\n > .btn,\n > .btn-group,\n > .input-group {\n margin-left: 5px;\n }\n}\n\n.btn-group > .btn:not(:first-child):not(:last-child):not(.dropdown-toggle) {\n border-radius: 0;\n}\n\n// Set corners individual because sometimes a single button can be in a .btn-group and we need :first-child and :last-child to both match\n.btn-group > .btn:first-child {\n margin-left: 0;\n &:not(:last-child):not(.dropdown-toggle) {\n .border-right-radius(0);\n }\n}\n// Need .dropdown-toggle since :last-child doesn't apply given a .dropdown-menu immediately after it\n.btn-group > .btn:last-child:not(:first-child),\n.btn-group > .dropdown-toggle:not(:first-child) {\n .border-left-radius(0);\n}\n\n// Custom edits for including btn-groups within btn-groups (useful for including dropdown buttons within a btn-group)\n.btn-group > .btn-group {\n float: left;\n}\n.btn-group > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group > .btn-group:first-child:not(:last-child) {\n > .btn:last-child,\n > .dropdown-toggle {\n .border-right-radius(0);\n }\n}\n.btn-group > .btn-group:last-child:not(:first-child) > .btn:first-child {\n .border-left-radius(0);\n}\n\n// On active and open, don't show outline\n.btn-group .dropdown-toggle:active,\n.btn-group.open .dropdown-toggle {\n outline: 0;\n}\n\n\n// Sizing\n//\n// Remix the default button sizing classes into new ones for easier manipulation.\n\n.btn-group-xs > .btn { &:extend(.btn-xs); }\n.btn-group-sm > .btn { &:extend(.btn-sm); }\n.btn-group-lg > .btn { &:extend(.btn-lg); }\n\n\n// Split button dropdowns\n// ----------------------\n\n// Give the line between buttons some depth\n.btn-group > .btn + .dropdown-toggle {\n padding-left: 8px;\n padding-right: 8px;\n}\n.btn-group > .btn-lg + .dropdown-toggle {\n padding-left: 12px;\n padding-right: 12px;\n}\n\n// The clickable button for toggling the menu\n// Remove the gradient and set the same inset shadow as the :active state\n.btn-group.open .dropdown-toggle {\n .box-shadow(inset 0 3px 5px rgba(0,0,0,.125));\n\n // Show no shadow for `.btn-link` since it has no other button styles.\n &.btn-link {\n .box-shadow(none);\n }\n}\n\n\n// Reposition the caret\n.btn .caret {\n margin-left: 0;\n}\n// Carets in other button sizes\n.btn-lg .caret {\n border-width: @caret-width-large @caret-width-large 0;\n border-bottom-width: 0;\n}\n// Upside down carets for .dropup\n.dropup .btn-lg .caret {\n border-width: 0 @caret-width-large @caret-width-large;\n}\n\n\n// Vertical button groups\n// ----------------------\n\n.btn-group-vertical {\n > .btn,\n > .btn-group,\n > .btn-group > .btn {\n display: block;\n float: none;\n width: 100%;\n max-width: 100%;\n }\n\n // Clear floats so dropdown menus can be properly placed\n > .btn-group {\n &:extend(.clearfix all);\n > .btn {\n float: none;\n }\n }\n\n > .btn + .btn,\n > .btn + .btn-group,\n > .btn-group + .btn,\n > .btn-group + .btn-group {\n margin-top: -1px;\n margin-left: 0;\n }\n}\n\n.btn-group-vertical > .btn {\n &:not(:first-child):not(:last-child) {\n border-radius: 0;\n }\n &:first-child:not(:last-child) {\n border-top-right-radius: @border-radius-base;\n .border-bottom-radius(0);\n }\n &:last-child:not(:first-child) {\n border-bottom-left-radius: @border-radius-base;\n .border-top-radius(0);\n }\n}\n.btn-group-vertical > .btn-group:not(:first-child):not(:last-child) > .btn {\n border-radius: 0;\n}\n.btn-group-vertical > .btn-group:first-child:not(:last-child) {\n > .btn:last-child,\n > .dropdown-toggle {\n .border-bottom-radius(0);\n }\n}\n.btn-group-vertical > .btn-group:last-child:not(:first-child) > .btn:first-child {\n .border-top-radius(0);\n}\n\n\n// Justified button groups\n// ----------------------\n\n.btn-group-justified {\n display: table;\n width: 100%;\n table-layout: fixed;\n border-collapse: separate;\n > .btn,\n > .btn-group {\n float: none;\n display: table-cell;\n width: 1%;\n }\n > .btn-group .btn {\n width: 100%;\n }\n\n > .btn-group .dropdown-menu {\n left: auto;\n }\n}\n\n\n// Checkbox and radio options\n//\n// In order to support the browser's form validation feedback, powered by the\n// `required` attribute, we have to \"hide\" the inputs via `clip`. We cannot use\n// `display: none;` or `visibility: hidden;` as that also hides the popover.\n// Simply visually hiding the inputs via `opacity` would leave them clickable in\n// certain cases which is prevented by using `clip` and `pointer-events`.\n// This way, we ensure a DOM element is visible to position the popover from.\n//\n// See https://github.com/twbs/bootstrap/pull/12794 and\n// https://github.com/twbs/bootstrap/pull/14559 for more information.\n\n[data-toggle=\"buttons\"] {\n > .btn,\n > .btn-group > .btn {\n input[type=\"radio\"],\n input[type=\"checkbox\"] {\n position: absolute;\n clip: rect(0,0,0,0);\n pointer-events: none;\n }\n }\n}\n","// Single side border-radius\n\n.border-top-radius(@radius) {\n border-top-right-radius: @radius;\n border-top-left-radius: @radius;\n}\n.border-right-radius(@radius) {\n border-bottom-right-radius: @radius;\n border-top-right-radius: @radius;\n}\n.border-bottom-radius(@radius) {\n border-bottom-right-radius: @radius;\n border-bottom-left-radius: @radius;\n}\n.border-left-radius(@radius) {\n border-bottom-left-radius: @radius;\n border-top-left-radius: @radius;\n}\n","//\n// Input groups\n// --------------------------------------------------\n\n// Base styles\n// -------------------------\n.input-group {\n position: relative; // For dropdowns\n display: table;\n border-collapse: separate; // prevent input groups from inheriting border styles from table cells when placed within a table\n\n // Undo padding and float of grid classes\n &[class*=\"col-\"] {\n float: none;\n padding-left: 0;\n padding-right: 0;\n }\n\n .form-control {\n // Ensure that the input is always above the *appended* addon button for\n // proper border colors.\n position: relative;\n z-index: 2;\n\n // IE9 fubars the placeholder attribute in text inputs and the arrows on\n // select elements in input groups. To fix it, we float the input. Details:\n // https://github.com/twbs/bootstrap/issues/11561#issuecomment-28936855\n float: left;\n\n width: 100%;\n margin-bottom: 0;\n }\n}\n\n// Sizing options\n//\n// Remix the default form control sizing classes into new ones for easier\n// manipulation.\n\n.input-group-lg > .form-control,\n.input-group-lg > .input-group-addon,\n.input-group-lg > .input-group-btn > .btn {\n .input-lg();\n}\n.input-group-sm > .form-control,\n.input-group-sm > .input-group-addon,\n.input-group-sm > .input-group-btn > .btn {\n .input-sm();\n}\n\n\n// Display as table-cell\n// -------------------------\n.input-group-addon,\n.input-group-btn,\n.input-group .form-control {\n display: table-cell;\n\n &:not(:first-child):not(:last-child) {\n border-radius: 0;\n }\n}\n// Addon and addon wrapper for buttons\n.input-group-addon,\n.input-group-btn {\n width: 1%;\n white-space: nowrap;\n vertical-align: middle; // Match the inputs\n}\n\n// Text input groups\n// -------------------------\n.input-group-addon {\n padding: @padding-base-vertical @padding-base-horizontal;\n font-size: @font-size-base;\n font-weight: normal;\n line-height: 1;\n color: @input-color;\n text-align: center;\n background-color: @input-group-addon-bg;\n border: 1px solid @input-group-addon-border-color;\n border-radius: @border-radius-base;\n\n // Sizing\n &.input-sm {\n padding: @padding-small-vertical @padding-small-horizontal;\n font-size: @font-size-small;\n border-radius: @border-radius-small;\n }\n &.input-lg {\n padding: @padding-large-vertical @padding-large-horizontal;\n font-size: @font-size-large;\n border-radius: @border-radius-large;\n }\n\n // Nuke default margins from checkboxes and radios to vertically center within.\n input[type=\"radio\"],\n input[type=\"checkbox\"] {\n margin-top: 0;\n }\n}\n\n// Reset rounded corners\n.input-group .form-control:first-child,\n.input-group-addon:first-child,\n.input-group-btn:first-child > .btn,\n.input-group-btn:first-child > .btn-group > .btn,\n.input-group-btn:first-child > .dropdown-toggle,\n.input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle),\n.input-group-btn:last-child > .btn-group:not(:last-child) > .btn {\n .border-right-radius(0);\n}\n.input-group-addon:first-child {\n border-right: 0;\n}\n.input-group .form-control:last-child,\n.input-group-addon:last-child,\n.input-group-btn:last-child > .btn,\n.input-group-btn:last-child > .btn-group > .btn,\n.input-group-btn:last-child > .dropdown-toggle,\n.input-group-btn:first-child > .btn:not(:first-child),\n.input-group-btn:first-child > .btn-group:not(:first-child) > .btn {\n .border-left-radius(0);\n}\n.input-group-addon:last-child {\n border-left: 0;\n}\n\n// Button input groups\n// -------------------------\n.input-group-btn {\n position: relative;\n // Jankily prevent input button groups from wrapping with `white-space` and\n // `font-size` in combination with `inline-block` on buttons.\n font-size: 0;\n white-space: nowrap;\n\n // Negative margin for spacing, position for bringing hovered/focused/actived\n // element above the siblings.\n > .btn {\n position: relative;\n + .btn {\n margin-left: -1px;\n }\n // Bring the \"active\" button to the front\n &:hover,\n &:focus,\n &:active {\n z-index: 2;\n }\n }\n\n // Negative margin to only have a 1px border between the two\n &:first-child {\n > .btn,\n > .btn-group {\n margin-right: -1px;\n }\n }\n &:last-child {\n > .btn,\n > .btn-group {\n margin-left: -1px;\n }\n }\n}\n","//\n// Navs\n// --------------------------------------------------\n\n\n// Base class\n// --------------------------------------------------\n\n.nav {\n margin-bottom: 0;\n padding-left: 0; // Override default ul/ol\n list-style: none;\n &:extend(.clearfix all);\n\n > li {\n position: relative;\n display: block;\n\n > a {\n position: relative;\n display: block;\n padding: @nav-link-padding;\n &:hover,\n &:focus {\n text-decoration: none;\n background-color: @nav-link-hover-bg;\n }\n }\n\n // Disabled state sets text to gray and nukes hover/tab effects\n &.disabled > a {\n color: @nav-disabled-link-color;\n\n &:hover,\n &:focus {\n color: @nav-disabled-link-hover-color;\n text-decoration: none;\n background-color: transparent;\n cursor: @cursor-disabled;\n }\n }\n }\n\n // Open dropdowns\n .open > a {\n &,\n &:hover,\n &:focus {\n background-color: @nav-link-hover-bg;\n border-color: @link-color;\n }\n }\n\n // Nav dividers (deprecated with v3.0.1)\n //\n // This should have been removed in v3 with the dropping of `.nav-list`, but\n // we missed it. We don't currently support this anywhere, but in the interest\n // of maintaining backward compatibility in case you use it, it's deprecated.\n .nav-divider {\n .nav-divider();\n }\n\n // Prevent IE8 from misplacing imgs\n //\n // See https://github.com/h5bp/html5-boilerplate/issues/984#issuecomment-3985989\n > li > a > img {\n max-width: none;\n }\n}\n\n\n// Tabs\n// -------------------------\n\n// Give the tabs something to sit on\n.nav-tabs {\n border-bottom: 1px solid @nav-tabs-border-color;\n > li {\n float: left;\n // Make the list-items overlay the bottom border\n margin-bottom: -1px;\n\n // Actual tabs (as links)\n > a {\n margin-right: 2px;\n line-height: @line-height-base;\n border: 1px solid transparent;\n border-radius: @border-radius-base @border-radius-base 0 0;\n &:hover {\n border-color: @nav-tabs-link-hover-border-color @nav-tabs-link-hover-border-color @nav-tabs-border-color;\n }\n }\n\n // Active state, and its :hover to override normal :hover\n &.active > a {\n &,\n &:hover,\n &:focus {\n color: @nav-tabs-active-link-hover-color;\n background-color: @nav-tabs-active-link-hover-bg;\n border: 1px solid @nav-tabs-active-link-hover-border-color;\n border-bottom-color: transparent;\n cursor: default;\n }\n }\n }\n // pulling this in mainly for less shorthand\n &.nav-justified {\n .nav-justified();\n .nav-tabs-justified();\n }\n}\n\n\n// Pills\n// -------------------------\n.nav-pills {\n > li {\n float: left;\n\n // Links rendered as pills\n > a {\n border-radius: @nav-pills-border-radius;\n }\n + li {\n margin-left: 2px;\n }\n\n // Active state\n &.active > a {\n &,\n &:hover,\n &:focus {\n color: @nav-pills-active-link-hover-color;\n background-color: @nav-pills-active-link-hover-bg;\n }\n }\n }\n}\n\n\n// Stacked pills\n.nav-stacked {\n > li {\n float: none;\n + li {\n margin-top: 2px;\n margin-left: 0; // no need for this gap between nav items\n }\n }\n}\n\n\n// Nav variations\n// --------------------------------------------------\n\n// Justified nav links\n// -------------------------\n\n.nav-justified {\n width: 100%;\n\n > li {\n float: none;\n > a {\n text-align: center;\n margin-bottom: 5px;\n }\n }\n\n > .dropdown .dropdown-menu {\n top: auto;\n left: auto;\n }\n\n @media (min-width: @screen-sm-min) {\n > li {\n display: table-cell;\n width: 1%;\n > a {\n margin-bottom: 0;\n }\n }\n }\n}\n\n// Move borders to anchors instead of bottom of list\n//\n// Mixin for adding on top the shared `.nav-justified` styles for our tabs\n.nav-tabs-justified {\n border-bottom: 0;\n\n > li > a {\n // Override margin from .nav-tabs\n margin-right: 0;\n border-radius: @border-radius-base;\n }\n\n > .active > a,\n > .active > a:hover,\n > .active > a:focus {\n border: 1px solid @nav-tabs-justified-link-border-color;\n }\n\n @media (min-width: @screen-sm-min) {\n > li > a {\n border-bottom: 1px solid @nav-tabs-justified-link-border-color;\n border-radius: @border-radius-base @border-radius-base 0 0;\n }\n > .active > a,\n > .active > a:hover,\n > .active > a:focus {\n border-bottom-color: @nav-tabs-justified-active-link-border-color;\n }\n }\n}\n\n\n// Tabbable tabs\n// -------------------------\n\n// Hide tabbable panes to start, show them when `.active`\n.tab-content {\n > .tab-pane {\n display: none;\n visibility: hidden;\n }\n > .active {\n display: block;\n visibility: visible;\n }\n}\n\n\n// Dropdowns\n// -------------------------\n\n// Specific dropdowns\n.nav-tabs .dropdown-menu {\n // make dropdown border overlap tab border\n margin-top: -1px;\n // Remove the top rounded corners here since there is a hard edge above the menu\n .border-top-radius(0);\n}\n","//\n// Navbars\n// --------------------------------------------------\n\n\n// Wrapper and base class\n//\n// Provide a static navbar from which we expand to create full-width, fixed, and\n// other navbar variations.\n\n.navbar {\n position: relative;\n min-height: @navbar-height; // Ensure a navbar always shows (e.g., without a .navbar-brand in collapsed mode)\n margin-bottom: @navbar-margin-bottom;\n border: 1px solid transparent;\n\n // Prevent floats from breaking the navbar\n &:extend(.clearfix all);\n\n @media (min-width: @grid-float-breakpoint) {\n border-radius: @navbar-border-radius;\n }\n}\n\n\n// Navbar heading\n//\n// Groups `.navbar-brand` and `.navbar-toggle` into a single component for easy\n// styling of responsive aspects.\n\n.navbar-header {\n &:extend(.clearfix all);\n\n @media (min-width: @grid-float-breakpoint) {\n float: left;\n }\n}\n\n\n// Navbar collapse (body)\n//\n// Group your navbar content into this for easy collapsing and expanding across\n// various device sizes. By default, this content is collapsed when <768px, but\n// will expand past that for a horizontal display.\n//\n// To start (on mobile devices) the navbar links, forms, and buttons are stacked\n// vertically and include a `max-height` to overflow in case you have too much\n// content for the user's viewport.\n\n.navbar-collapse {\n overflow-x: visible;\n padding-right: @navbar-padding-horizontal;\n padding-left: @navbar-padding-horizontal;\n border-top: 1px solid transparent;\n box-shadow: inset 0 1px 0 rgba(255,255,255,.1);\n &:extend(.clearfix all);\n -webkit-overflow-scrolling: touch;\n\n &.in {\n overflow-y: auto;\n }\n\n @media (min-width: @grid-float-breakpoint) {\n width: auto;\n border-top: 0;\n box-shadow: none;\n\n &.collapse {\n display: block !important;\n visibility: visible !important;\n height: auto !important;\n padding-bottom: 0; // Override default setting\n overflow: visible !important;\n }\n\n &.in {\n overflow-y: visible;\n }\n\n // Undo the collapse side padding for navbars with containers to ensure\n // alignment of right-aligned contents.\n .navbar-fixed-top &,\n .navbar-static-top &,\n .navbar-fixed-bottom & {\n padding-left: 0;\n padding-right: 0;\n }\n }\n}\n\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n .navbar-collapse {\n max-height: @navbar-collapse-max-height;\n\n @media (max-device-width: @screen-xs-min) and (orientation: landscape) {\n max-height: 200px;\n }\n }\n}\n\n\n// Both navbar header and collapse\n//\n// When a container is present, change the behavior of the header and collapse.\n\n.container,\n.container-fluid {\n > .navbar-header,\n > .navbar-collapse {\n margin-right: -@navbar-padding-horizontal;\n margin-left: -@navbar-padding-horizontal;\n\n @media (min-width: @grid-float-breakpoint) {\n margin-right: 0;\n margin-left: 0;\n }\n }\n}\n\n\n//\n// Navbar alignment options\n//\n// Display the navbar across the entirety of the page or fixed it to the top or\n// bottom of the page.\n\n// Static top (unfixed, but 100% wide) navbar\n.navbar-static-top {\n z-index: @zindex-navbar;\n border-width: 0 0 1px;\n\n @media (min-width: @grid-float-breakpoint) {\n border-radius: 0;\n }\n}\n\n// Fix the top/bottom navbars when screen real estate supports it\n.navbar-fixed-top,\n.navbar-fixed-bottom {\n position: fixed;\n right: 0;\n left: 0;\n z-index: @zindex-navbar-fixed;\n\n // Undo the rounded corners\n @media (min-width: @grid-float-breakpoint) {\n border-radius: 0;\n }\n}\n.navbar-fixed-top {\n top: 0;\n border-width: 0 0 1px;\n}\n.navbar-fixed-bottom {\n bottom: 0;\n margin-bottom: 0; // override .navbar defaults\n border-width: 1px 0 0;\n}\n\n\n// Brand/project name\n\n.navbar-brand {\n float: left;\n padding: @navbar-padding-vertical @navbar-padding-horizontal;\n font-size: @font-size-large;\n line-height: @line-height-computed;\n height: @navbar-height;\n\n &:hover,\n &:focus {\n text-decoration: none;\n }\n\n > img {\n display: block;\n }\n\n @media (min-width: @grid-float-breakpoint) {\n .navbar > .container &,\n .navbar > .container-fluid & {\n margin-left: -@navbar-padding-horizontal;\n }\n }\n}\n\n\n// Navbar toggle\n//\n// Custom button for toggling the `.navbar-collapse`, powered by the collapse\n// JavaScript plugin.\n\n.navbar-toggle {\n position: relative;\n float: right;\n margin-right: @navbar-padding-horizontal;\n padding: 9px 10px;\n .navbar-vertical-align(34px);\n background-color: transparent;\n background-image: none; // Reset unusual Firefox-on-Android default style; see https://github.com/necolas/normalize.css/issues/214\n border: 1px solid transparent;\n border-radius: @border-radius-base;\n\n // We remove the `outline` here, but later compensate by attaching `:hover`\n // styles to `:focus`.\n &:focus {\n outline: 0;\n }\n\n // Bars\n .icon-bar {\n display: block;\n width: 22px;\n height: 2px;\n border-radius: 1px;\n }\n .icon-bar + .icon-bar {\n margin-top: 4px;\n }\n\n @media (min-width: @grid-float-breakpoint) {\n display: none;\n }\n}\n\n\n// Navbar nav links\n//\n// Builds on top of the `.nav` components with its own modifier class to make\n// the nav the full height of the horizontal nav (above 768px).\n\n.navbar-nav {\n margin: (@navbar-padding-vertical / 2) -@navbar-padding-horizontal;\n\n > li > a {\n padding-top: 10px;\n padding-bottom: 10px;\n line-height: @line-height-computed;\n }\n\n @media (max-width: @grid-float-breakpoint-max) {\n // Dropdowns get custom display when collapsed\n .open .dropdown-menu {\n position: static;\n float: none;\n width: auto;\n margin-top: 0;\n background-color: transparent;\n border: 0;\n box-shadow: none;\n > li > a,\n .dropdown-header {\n padding: 5px 15px 5px 25px;\n }\n > li > a {\n line-height: @line-height-computed;\n &:hover,\n &:focus {\n background-image: none;\n }\n }\n }\n }\n\n // Uncollapse the nav\n @media (min-width: @grid-float-breakpoint) {\n float: left;\n margin: 0;\n\n > li {\n float: left;\n > a {\n padding-top: @navbar-padding-vertical;\n padding-bottom: @navbar-padding-vertical;\n }\n }\n }\n}\n\n\n// Navbar form\n//\n// Extension of the `.form-inline` with some extra flavor for optimum display in\n// our navbars.\n\n.navbar-form {\n margin-left: -@navbar-padding-horizontal;\n margin-right: -@navbar-padding-horizontal;\n padding: 10px @navbar-padding-horizontal;\n border-top: 1px solid transparent;\n border-bottom: 1px solid transparent;\n @shadow: inset 0 1px 0 rgba(255,255,255,.1), 0 1px 0 rgba(255,255,255,.1);\n .box-shadow(@shadow);\n\n // Mixin behavior for optimum display\n .form-inline();\n\n .form-group {\n @media (max-width: @grid-float-breakpoint-max) {\n margin-bottom: 5px;\n\n &:last-child {\n margin-bottom: 0;\n }\n }\n }\n\n // Vertically center in expanded, horizontal navbar\n .navbar-vertical-align(@input-height-base);\n\n // Undo 100% width for pull classes\n @media (min-width: @grid-float-breakpoint) {\n width: auto;\n border: 0;\n margin-left: 0;\n margin-right: 0;\n padding-top: 0;\n padding-bottom: 0;\n .box-shadow(none);\n }\n}\n\n\n// Dropdown menus\n\n// Menu position and menu carets\n.navbar-nav > li > .dropdown-menu {\n margin-top: 0;\n .border-top-radius(0);\n}\n// Menu position and menu caret support for dropups via extra dropup class\n.navbar-fixed-bottom .navbar-nav > li > .dropdown-menu {\n margin-bottom: 0;\n .border-top-radius(@navbar-border-radius);\n .border-bottom-radius(0);\n}\n\n\n// Buttons in navbars\n//\n// Vertically center a button within a navbar (when *not* in a form).\n\n.navbar-btn {\n .navbar-vertical-align(@input-height-base);\n\n &.btn-sm {\n .navbar-vertical-align(@input-height-small);\n }\n &.btn-xs {\n .navbar-vertical-align(22);\n }\n}\n\n\n// Text in navbars\n//\n// Add a class to make any element properly align itself vertically within the navbars.\n\n.navbar-text {\n .navbar-vertical-align(@line-height-computed);\n\n @media (min-width: @grid-float-breakpoint) {\n float: left;\n margin-left: @navbar-padding-horizontal;\n margin-right: @navbar-padding-horizontal;\n }\n}\n\n\n// Component alignment\n//\n// Repurpose the pull utilities as their own navbar utilities to avoid specificity\n// issues with parents and chaining. Only do this when the navbar is uncollapsed\n// though so that navbar contents properly stack and align in mobile.\n//\n// Declared after the navbar components to ensure more specificity on the margins.\n\n@media (min-width: @grid-float-breakpoint) {\n .navbar-left { .pull-left(); }\n .navbar-right {\n .pull-right();\n margin-right: -@navbar-padding-horizontal;\n\n ~ .navbar-right {\n margin-right: 0;\n }\n }\n}\n\n\n// Alternate navbars\n// --------------------------------------------------\n\n// Default navbar\n.navbar-default {\n background-color: @navbar-default-bg;\n border-color: @navbar-default-border;\n\n .navbar-brand {\n color: @navbar-default-brand-color;\n &:hover,\n &:focus {\n color: @navbar-default-brand-hover-color;\n background-color: @navbar-default-brand-hover-bg;\n }\n }\n\n .navbar-text {\n color: @navbar-default-color;\n }\n\n .navbar-nav {\n > li > a {\n color: @navbar-default-link-color;\n\n &:hover,\n &:focus {\n color: @navbar-default-link-hover-color;\n background-color: @navbar-default-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-active-color;\n background-color: @navbar-default-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-disabled-color;\n background-color: @navbar-default-link-disabled-bg;\n }\n }\n }\n\n .navbar-toggle {\n border-color: @navbar-default-toggle-border-color;\n &:hover,\n &:focus {\n background-color: @navbar-default-toggle-hover-bg;\n }\n .icon-bar {\n background-color: @navbar-default-toggle-icon-bar-bg;\n }\n }\n\n .navbar-collapse,\n .navbar-form {\n border-color: @navbar-default-border;\n }\n\n // Dropdown menu items\n .navbar-nav {\n // Remove background color from open dropdown\n > .open > a {\n &,\n &:hover,\n &:focus {\n background-color: @navbar-default-link-active-bg;\n color: @navbar-default-link-active-color;\n }\n }\n\n @media (max-width: @grid-float-breakpoint-max) {\n // Dropdowns get custom display when collapsed\n .open .dropdown-menu {\n > li > a {\n color: @navbar-default-link-color;\n &:hover,\n &:focus {\n color: @navbar-default-link-hover-color;\n background-color: @navbar-default-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-active-color;\n background-color: @navbar-default-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-default-link-disabled-color;\n background-color: @navbar-default-link-disabled-bg;\n }\n }\n }\n }\n }\n\n\n // Links in navbars\n //\n // Add a class to ensure links outside the navbar nav are colored correctly.\n\n .navbar-link {\n color: @navbar-default-link-color;\n &:hover {\n color: @navbar-default-link-hover-color;\n }\n }\n\n .btn-link {\n color: @navbar-default-link-color;\n &:hover,\n &:focus {\n color: @navbar-default-link-hover-color;\n }\n &[disabled],\n fieldset[disabled] & {\n &:hover,\n &:focus {\n color: @navbar-default-link-disabled-color;\n }\n }\n }\n}\n\n// Inverse navbar\n\n.navbar-inverse {\n background-color: @navbar-inverse-bg;\n border-color: @navbar-inverse-border;\n\n .navbar-brand {\n color: @navbar-inverse-brand-color;\n &:hover,\n &:focus {\n color: @navbar-inverse-brand-hover-color;\n background-color: @navbar-inverse-brand-hover-bg;\n }\n }\n\n .navbar-text {\n color: @navbar-inverse-color;\n }\n\n .navbar-nav {\n > li > a {\n color: @navbar-inverse-link-color;\n\n &:hover,\n &:focus {\n color: @navbar-inverse-link-hover-color;\n background-color: @navbar-inverse-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-active-color;\n background-color: @navbar-inverse-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-disabled-color;\n background-color: @navbar-inverse-link-disabled-bg;\n }\n }\n }\n\n // Darken the responsive nav toggle\n .navbar-toggle {\n border-color: @navbar-inverse-toggle-border-color;\n &:hover,\n &:focus {\n background-color: @navbar-inverse-toggle-hover-bg;\n }\n .icon-bar {\n background-color: @navbar-inverse-toggle-icon-bar-bg;\n }\n }\n\n .navbar-collapse,\n .navbar-form {\n border-color: darken(@navbar-inverse-bg, 7%);\n }\n\n // Dropdowns\n .navbar-nav {\n > .open > a {\n &,\n &:hover,\n &:focus {\n background-color: @navbar-inverse-link-active-bg;\n color: @navbar-inverse-link-active-color;\n }\n }\n\n @media (max-width: @grid-float-breakpoint-max) {\n // Dropdowns get custom display\n .open .dropdown-menu {\n > .dropdown-header {\n border-color: @navbar-inverse-border;\n }\n .divider {\n background-color: @navbar-inverse-border;\n }\n > li > a {\n color: @navbar-inverse-link-color;\n &:hover,\n &:focus {\n color: @navbar-inverse-link-hover-color;\n background-color: @navbar-inverse-link-hover-bg;\n }\n }\n > .active > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-active-color;\n background-color: @navbar-inverse-link-active-bg;\n }\n }\n > .disabled > a {\n &,\n &:hover,\n &:focus {\n color: @navbar-inverse-link-disabled-color;\n background-color: @navbar-inverse-link-disabled-bg;\n }\n }\n }\n }\n }\n\n .navbar-link {\n color: @navbar-inverse-link-color;\n &:hover {\n color: @navbar-inverse-link-hover-color;\n }\n }\n\n .btn-link {\n color: @navbar-inverse-link-color;\n &:hover,\n &:focus {\n color: @navbar-inverse-link-hover-color;\n }\n &[disabled],\n fieldset[disabled] & {\n &:hover,\n &:focus {\n color: @navbar-inverse-link-disabled-color;\n }\n }\n }\n}\n","// Navbar vertical align\n//\n// Vertically center elements in the navbar.\n// Example: an element has a height of 30px, so write out `.navbar-vertical-align(30px);` to calculate the appropriate top margin.\n\n.navbar-vertical-align(@element-height) {\n margin-top: ((@navbar-height - @element-height) / 2);\n margin-bottom: ((@navbar-height - @element-height) / 2);\n}\n","//\n// Utility classes\n// --------------------------------------------------\n\n\n// Floats\n// -------------------------\n\n.clearfix {\n .clearfix();\n}\n.center-block {\n .center-block();\n}\n.pull-right {\n float: right !important;\n}\n.pull-left {\n float: left !important;\n}\n\n\n// Toggling content\n// -------------------------\n\n// Note: Deprecated .hide in favor of .hidden or .sr-only (as appropriate) in v3.0.1\n.hide {\n display: none !important;\n}\n.show {\n display: block !important;\n}\n.invisible {\n visibility: hidden;\n}\n.text-hide {\n .text-hide();\n}\n\n\n// Hide from screenreaders and browsers\n//\n// Credit: HTML5 Boilerplate\n\n.hidden {\n display: none !important;\n visibility: hidden !important;\n}\n\n\n// For Affix plugin\n// -------------------------\n\n.affix {\n position: fixed;\n}\n","//\n// Breadcrumbs\n// --------------------------------------------------\n\n\n.breadcrumb {\n padding: @breadcrumb-padding-vertical @breadcrumb-padding-horizontal;\n margin-bottom: @line-height-computed;\n list-style: none;\n background-color: @breadcrumb-bg;\n border-radius: @border-radius-base;\n\n > li {\n display: inline-block;\n\n + li:before {\n content: \"@{breadcrumb-separator}\\00a0\"; // Unicode space added since inline-block means non-collapsing white-space\n padding: 0 5px;\n color: @breadcrumb-color;\n }\n }\n\n > .active {\n color: @breadcrumb-active-color;\n }\n}\n","//\n// Pagination (multiple pages)\n// --------------------------------------------------\n.pagination {\n display: inline-block;\n padding-left: 0;\n margin: @line-height-computed 0;\n border-radius: @border-radius-base;\n\n > li {\n display: inline; // Remove list-style and block-level defaults\n > a,\n > span {\n position: relative;\n float: left; // Collapse white-space\n padding: @padding-base-vertical @padding-base-horizontal;\n line-height: @line-height-base;\n text-decoration: none;\n color: @pagination-color;\n background-color: @pagination-bg;\n border: 1px solid @pagination-border;\n margin-left: -1px;\n }\n &:first-child {\n > a,\n > span {\n margin-left: 0;\n .border-left-radius(@border-radius-base);\n }\n }\n &:last-child {\n > a,\n > span {\n .border-right-radius(@border-radius-base);\n }\n }\n }\n\n > li > a,\n > li > span {\n &:hover,\n &:focus {\n color: @pagination-hover-color;\n background-color: @pagination-hover-bg;\n border-color: @pagination-hover-border;\n }\n }\n\n > .active > a,\n > .active > span {\n &,\n &:hover,\n &:focus {\n z-index: 2;\n color: @pagination-active-color;\n background-color: @pagination-active-bg;\n border-color: @pagination-active-border;\n cursor: default;\n }\n }\n\n > .disabled {\n > span,\n > span:hover,\n > span:focus,\n > a,\n > a:hover,\n > a:focus {\n color: @pagination-disabled-color;\n background-color: @pagination-disabled-bg;\n border-color: @pagination-disabled-border;\n cursor: @cursor-disabled;\n }\n }\n}\n\n// Sizing\n// --------------------------------------------------\n\n// Large\n.pagination-lg {\n .pagination-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @border-radius-large);\n}\n\n// Small\n.pagination-sm {\n .pagination-size(@padding-small-vertical; @padding-small-horizontal; @font-size-small; @border-radius-small);\n}\n","// Pagination\n\n.pagination-size(@padding-vertical; @padding-horizontal; @font-size; @border-radius) {\n > li {\n > a,\n > span {\n padding: @padding-vertical @padding-horizontal;\n font-size: @font-size;\n }\n &:first-child {\n > a,\n > span {\n .border-left-radius(@border-radius);\n }\n }\n &:last-child {\n > a,\n > span {\n .border-right-radius(@border-radius);\n }\n }\n }\n}\n","//\n// Pager pagination\n// --------------------------------------------------\n\n\n.pager {\n padding-left: 0;\n margin: @line-height-computed 0;\n list-style: none;\n text-align: center;\n &:extend(.clearfix all);\n li {\n display: inline;\n > a,\n > span {\n display: inline-block;\n padding: 5px 14px;\n background-color: @pager-bg;\n border: 1px solid @pager-border;\n border-radius: @pager-border-radius;\n }\n\n > a:hover,\n > a:focus {\n text-decoration: none;\n background-color: @pager-hover-bg;\n }\n }\n\n .next {\n > a,\n > span {\n float: right;\n }\n }\n\n .previous {\n > a,\n > span {\n float: left;\n }\n }\n\n .disabled {\n > a,\n > a:hover,\n > a:focus,\n > span {\n color: @pager-disabled-color;\n background-color: @pager-bg;\n cursor: @cursor-disabled;\n }\n }\n}\n","//\n// Labels\n// --------------------------------------------------\n\n.label {\n display: inline;\n padding: .2em .6em .3em;\n font-size: 75%;\n font-weight: bold;\n line-height: 1;\n color: @label-color;\n text-align: center;\n white-space: nowrap;\n vertical-align: baseline;\n border-radius: .25em;\n\n // Add hover effects, but only for links\n a& {\n &:hover,\n &:focus {\n color: @label-link-hover-color;\n text-decoration: none;\n cursor: pointer;\n }\n }\n\n // Empty labels collapse automatically (not available in IE8)\n &:empty {\n display: none;\n }\n\n // Quick fix for labels in buttons\n .btn & {\n position: relative;\n top: -1px;\n }\n}\n\n// Colors\n// Contextual variations (linked labels get darker on :hover)\n\n.label-default {\n .label-variant(@label-default-bg);\n}\n\n.label-primary {\n .label-variant(@label-primary-bg);\n}\n\n.label-success {\n .label-variant(@label-success-bg);\n}\n\n.label-info {\n .label-variant(@label-info-bg);\n}\n\n.label-warning {\n .label-variant(@label-warning-bg);\n}\n\n.label-danger {\n .label-variant(@label-danger-bg);\n}\n","// Labels\n\n.label-variant(@color) {\n background-color: @color;\n\n &[href] {\n &:hover,\n &:focus {\n background-color: darken(@color, 10%);\n }\n }\n}\n","//\n// Badges\n// --------------------------------------------------\n\n\n// Base class\n.badge {\n display: inline-block;\n min-width: 10px;\n padding: 3px 7px;\n font-size: @font-size-small;\n font-weight: @badge-font-weight;\n color: @badge-color;\n line-height: @badge-line-height;\n vertical-align: baseline;\n white-space: nowrap;\n text-align: center;\n background-color: @badge-bg;\n border-radius: @badge-border-radius;\n\n // Empty badges collapse automatically (not available in IE8)\n &:empty {\n display: none;\n }\n\n // Quick fix for badges in buttons\n .btn & {\n position: relative;\n top: -1px;\n }\n \n .btn-xs & {\n top: 0;\n padding: 1px 5px;\n }\n\n // Hover state, but only for links\n a& {\n &:hover,\n &:focus {\n color: @badge-link-hover-color;\n text-decoration: none;\n cursor: pointer;\n }\n }\n\n // Account for badges in navs\n .list-group-item.active > &,\n .nav-pills > .active > a > & {\n color: @badge-active-color;\n background-color: @badge-active-bg;\n }\n \n .list-group-item > & {\n float: right;\n }\n \n .list-group-item > & + & {\n margin-right: 5px;\n }\n \n .nav-pills > li > a > & {\n margin-left: 3px;\n }\n}\n","//\n// Jumbotron\n// --------------------------------------------------\n\n\n.jumbotron {\n padding: @jumbotron-padding (@jumbotron-padding / 2);\n margin-bottom: @jumbotron-padding;\n color: @jumbotron-color;\n background-color: @jumbotron-bg;\n\n h1,\n .h1 {\n color: @jumbotron-heading-color;\n }\n \n p {\n margin-bottom: (@jumbotron-padding / 2);\n font-size: @jumbotron-font-size;\n font-weight: 200;\n }\n\n > hr {\n border-top-color: darken(@jumbotron-bg, 10%);\n }\n\n .container &,\n .container-fluid & {\n border-radius: @border-radius-large; // Only round corners at higher resolutions if contained in a container\n }\n\n .container {\n max-width: 100%;\n }\n\n @media screen and (min-width: @screen-sm-min) {\n padding: (@jumbotron-padding * 1.6) 0;\n\n .container &,\n .container-fluid & {\n padding-left: (@jumbotron-padding * 2);\n padding-right: (@jumbotron-padding * 2);\n }\n\n h1,\n .h1 {\n font-size: (@font-size-base * 4.5);\n }\n }\n}\n","//\n// Thumbnails\n// --------------------------------------------------\n\n\n// Mixin and adjust the regular image class\n.thumbnail {\n display: block;\n padding: @thumbnail-padding;\n margin-bottom: @line-height-computed;\n line-height: @line-height-base;\n background-color: @thumbnail-bg;\n border: 1px solid @thumbnail-border;\n border-radius: @thumbnail-border-radius;\n .transition(border .2s ease-in-out);\n\n > img,\n a > img {\n &:extend(.img-responsive);\n margin-left: auto;\n margin-right: auto;\n }\n\n // Add a hover state for linked versions only\n a&:hover,\n a&:focus,\n a&.active {\n border-color: @link-color;\n }\n\n // Image captions\n .caption {\n padding: @thumbnail-caption-padding;\n color: @thumbnail-caption-color;\n }\n}\n","//\n// Alerts\n// --------------------------------------------------\n\n\n// Base styles\n// -------------------------\n\n.alert {\n padding: @alert-padding;\n margin-bottom: @line-height-computed;\n border: 1px solid transparent;\n border-radius: @alert-border-radius;\n\n // Headings for larger alerts\n h4 {\n margin-top: 0;\n // Specified for the h4 to prevent conflicts of changing @headings-color\n color: inherit;\n }\n \n // Provide class for links that match alerts\n .alert-link {\n font-weight: @alert-link-font-weight;\n }\n\n // Improve alignment and spacing of inner content\n > p,\n > ul {\n margin-bottom: 0;\n }\n \n > p + p {\n margin-top: 5px;\n }\n}\n\n// Dismissible alerts\n//\n// Expand the right padding and account for the close button's positioning.\n\n.alert-dismissable, // The misspelled .alert-dismissable was deprecated in 3.2.0.\n.alert-dismissible {\n padding-right: (@alert-padding + 20);\n\n // Adjust close link position\n .close {\n position: relative;\n top: -2px;\n right: -21px;\n color: inherit;\n }\n}\n\n// Alternate styles\n//\n// Generate contextual modifier classes for colorizing the alert.\n\n.alert-success {\n .alert-variant(@alert-success-bg; @alert-success-border; @alert-success-text);\n}\n\n.alert-info {\n .alert-variant(@alert-info-bg; @alert-info-border; @alert-info-text);\n}\n\n.alert-warning {\n .alert-variant(@alert-warning-bg; @alert-warning-border; @alert-warning-text);\n}\n\n.alert-danger {\n .alert-variant(@alert-danger-bg; @alert-danger-border; @alert-danger-text);\n}\n","// Alerts\n\n.alert-variant(@background; @border; @text-color) {\n background-color: @background;\n border-color: @border;\n color: @text-color;\n\n hr {\n border-top-color: darken(@border, 5%);\n }\n .alert-link {\n color: darken(@text-color, 10%);\n }\n}\n","//\n// Progress bars\n// --------------------------------------------------\n\n\n// Bar animations\n// -------------------------\n\n// WebKit\n@-webkit-keyframes progress-bar-stripes {\n from { background-position: 40px 0; }\n to { background-position: 0 0; }\n}\n\n// Spec and IE10+\n@keyframes progress-bar-stripes {\n from { background-position: 40px 0; }\n to { background-position: 0 0; }\n}\n\n\n// Bar itself\n// -------------------------\n\n// Outer container\n.progress {\n overflow: hidden;\n height: @line-height-computed;\n margin-bottom: @line-height-computed;\n background-color: @progress-bg;\n border-radius: @progress-border-radius;\n .box-shadow(inset 0 1px 2px rgba(0,0,0,.1));\n}\n\n// Bar of progress\n.progress-bar {\n float: left;\n width: 0%;\n height: 100%;\n font-size: @font-size-small;\n line-height: @line-height-computed;\n color: @progress-bar-color;\n text-align: center;\n background-color: @progress-bar-bg;\n .box-shadow(inset 0 -1px 0 rgba(0,0,0,.15));\n .transition(width .6s ease);\n}\n\n// Striped bars\n//\n// `.progress-striped .progress-bar` is deprecated as of v3.2.0 in favor of the\n// `.progress-bar-striped` class, which you just add to an existing\n// `.progress-bar`.\n.progress-striped .progress-bar,\n.progress-bar-striped {\n #gradient > .striped();\n background-size: 40px 40px;\n}\n\n// Call animation for the active one\n//\n// `.progress.active .progress-bar` is deprecated as of v3.2.0 in favor of the\n// `.progress-bar.active` approach.\n.progress.active .progress-bar,\n.progress-bar.active {\n .animation(progress-bar-stripes 2s linear infinite);\n}\n\n\n// Variations\n// -------------------------\n\n.progress-bar-success {\n .progress-bar-variant(@progress-bar-success-bg);\n}\n\n.progress-bar-info {\n .progress-bar-variant(@progress-bar-info-bg);\n}\n\n.progress-bar-warning {\n .progress-bar-variant(@progress-bar-warning-bg);\n}\n\n.progress-bar-danger {\n .progress-bar-variant(@progress-bar-danger-bg);\n}\n","// Gradients\n\n#gradient {\n\n // Horizontal gradient, from left to right\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .horizontal(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(left, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to right, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n // Vertical gradient, from top to bottom\n //\n // Creates two color stops, start and end, by specifying a color and position for each color stop.\n // Color stops are not available in IE9 and below.\n .vertical(@start-color: #555; @end-color: #333; @start-percent: 0%; @end-percent: 100%) {\n background-image: -webkit-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(top, @start-color @start-percent, @end-color @end-percent); // Opera 12\n background-image: linear-gradient(to bottom, @start-color @start-percent, @end-color @end-percent); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n background-repeat: repeat-x;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down\n }\n\n .directional(@start-color: #555; @end-color: #333; @deg: 45deg) {\n background-repeat: repeat-x;\n background-image: -webkit-linear-gradient(@deg, @start-color, @end-color); // Safari 5.1-6, Chrome 10+\n background-image: -o-linear-gradient(@deg, @start-color, @end-color); // Opera 12\n background-image: linear-gradient(@deg, @start-color, @end-color); // Standard, IE10, Firefox 16+, Opera 12.10+, Safari 7+, Chrome 26+\n }\n .horizontal-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(left, @start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(to right, @start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=1)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .vertical-three-colors(@start-color: #00b3ee; @mid-color: #7a43b6; @color-stop: 50%; @end-color: #c3325f) {\n background-image: -webkit-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: -o-linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-image: linear-gradient(@start-color, @mid-color @color-stop, @end-color);\n background-repeat: no-repeat;\n filter: e(%(\"progid:DXImageTransform.Microsoft.gradient(startColorstr='%d', endColorstr='%d', GradientType=0)\",argb(@start-color),argb(@end-color))); // IE9 and down, gets no color-stop at all for proper fallback\n }\n .radial(@inner-color: #555; @outer-color: #333) {\n background-image: -webkit-radial-gradient(circle, @inner-color, @outer-color);\n background-image: radial-gradient(circle, @inner-color, @outer-color);\n background-repeat: no-repeat;\n }\n .striped(@color: rgba(255,255,255,.15); @angle: 45deg) {\n background-image: -webkit-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: -o-linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n background-image: linear-gradient(@angle, @color 25%, transparent 25%, transparent 50%, @color 50%, @color 75%, transparent 75%, transparent);\n }\n}\n","// Progress bars\n\n.progress-bar-variant(@color) {\n background-color: @color;\n\n // Deprecated parent class requirement as of v3.2.0\n .progress-striped & {\n #gradient > .striped();\n }\n}\n",".media {\n // Proper spacing between instances of .media\n margin-top: 15px;\n\n &:first-child {\n margin-top: 0;\n }\n}\n\n.media,\n.media-body {\n zoom: 1;\n overflow: hidden;\n}\n\n.media-body {\n width: 10000px;\n}\n\n.media-object {\n display: block;\n}\n\n.media-right,\n.media > .pull-right {\n padding-left: 10px;\n}\n\n.media-left,\n.media > .pull-left {\n padding-right: 10px;\n}\n\n.media-left,\n.media-right,\n.media-body {\n display: table-cell;\n vertical-align: top;\n}\n\n.media-middle {\n vertical-align: middle;\n}\n\n.media-bottom {\n vertical-align: bottom;\n}\n\n// Reset margins on headings for tighter default spacing\n.media-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n\n// Media list variation\n//\n// Undo default ul/ol styles\n.media-list {\n padding-left: 0;\n list-style: none;\n}\n","//\n// List groups\n// --------------------------------------------------\n\n\n// Base class\n//\n// Easily usable on <ul>, <ol>, or <div>.\n\n.list-group {\n // No need to set list-style: none; since .list-group-item is block level\n margin-bottom: 20px;\n padding-left: 0; // reset padding because ul and ol\n}\n\n\n// Individual list items\n//\n// Use on `li`s or `div`s within the `.list-group` parent.\n\n.list-group-item {\n position: relative;\n display: block;\n padding: 10px 15px;\n // Place the border on the list items and negative margin up for better styling\n margin-bottom: -1px;\n background-color: @list-group-bg;\n border: 1px solid @list-group-border;\n\n // Round the first and last items\n &:first-child {\n .border-top-radius(@list-group-border-radius);\n }\n &:last-child {\n margin-bottom: 0;\n .border-bottom-radius(@list-group-border-radius);\n }\n}\n\n\n// Linked list items\n//\n// Use anchor elements instead of `li`s or `div`s to create linked list items.\n// Includes an extra `.active` modifier class for showing selected items.\n\na.list-group-item {\n color: @list-group-link-color;\n\n .list-group-item-heading {\n color: @list-group-link-heading-color;\n }\n\n // Hover state\n &:hover,\n &:focus {\n text-decoration: none;\n color: @list-group-link-hover-color;\n background-color: @list-group-hover-bg;\n }\n}\n\n.list-group-item {\n // Disabled state\n &.disabled,\n &.disabled:hover,\n &.disabled:focus {\n background-color: @list-group-disabled-bg;\n color: @list-group-disabled-color;\n cursor: @cursor-disabled;\n\n // Force color to inherit for custom content\n .list-group-item-heading {\n color: inherit;\n }\n .list-group-item-text {\n color: @list-group-disabled-text-color;\n }\n }\n\n // Active class on item itself, not parent\n &.active,\n &.active:hover,\n &.active:focus {\n z-index: 2; // Place active items above their siblings for proper border styling\n color: @list-group-active-color;\n background-color: @list-group-active-bg;\n border-color: @list-group-active-border;\n\n // Force color to inherit for custom content\n .list-group-item-heading,\n .list-group-item-heading > small,\n .list-group-item-heading > .small {\n color: inherit;\n }\n .list-group-item-text {\n color: @list-group-active-text-color;\n }\n }\n}\n\n\n// Contextual variants\n//\n// Add modifier classes to change text and background color on individual items.\n// Organizationally, this must come after the `:hover` states.\n\n.list-group-item-variant(success; @state-success-bg; @state-success-text);\n.list-group-item-variant(info; @state-info-bg; @state-info-text);\n.list-group-item-variant(warning; @state-warning-bg; @state-warning-text);\n.list-group-item-variant(danger; @state-danger-bg; @state-danger-text);\n\n\n// Custom content options\n//\n// Extra classes for creating well-formatted content within `.list-group-item`s.\n\n.list-group-item-heading {\n margin-top: 0;\n margin-bottom: 5px;\n}\n.list-group-item-text {\n margin-bottom: 0;\n line-height: 1.3;\n}\n","// List Groups\n\n.list-group-item-variant(@state; @background; @color) {\n .list-group-item-@{state} {\n color: @color;\n background-color: @background;\n\n a& {\n color: @color;\n\n .list-group-item-heading {\n color: inherit;\n }\n\n &:hover,\n &:focus {\n color: @color;\n background-color: darken(@background, 5%);\n }\n &.active,\n &.active:hover,\n &.active:focus {\n color: #fff;\n background-color: @color;\n border-color: @color;\n }\n }\n }\n}\n","//\n// Panels\n// --------------------------------------------------\n\n\n// Base class\n.panel {\n margin-bottom: @line-height-computed;\n background-color: @panel-bg;\n border: 1px solid transparent;\n border-radius: @panel-border-radius;\n .box-shadow(0 1px 1px rgba(0,0,0,.05));\n}\n\n// Panel contents\n.panel-body {\n padding: @panel-body-padding;\n &:extend(.clearfix all);\n}\n\n// Optional heading\n.panel-heading {\n padding: @panel-heading-padding;\n border-bottom: 1px solid transparent;\n .border-top-radius((@panel-border-radius - 1));\n\n > .dropdown .dropdown-toggle {\n color: inherit;\n }\n}\n\n// Within heading, strip any `h*` tag of its default margins for spacing.\n.panel-title {\n margin-top: 0;\n margin-bottom: 0;\n font-size: ceil((@font-size-base * 1.125));\n color: inherit;\n\n > a,\n > small,\n > .small,\n > small > a,\n > .small > a {\n color: inherit;\n }\n}\n\n// Optional footer (stays gray in every modifier class)\n.panel-footer {\n padding: @panel-footer-padding;\n background-color: @panel-footer-bg;\n border-top: 1px solid @panel-inner-border;\n .border-bottom-radius((@panel-border-radius - 1));\n}\n\n\n// List groups in panels\n//\n// By default, space out list group content from panel headings to account for\n// any kind of custom content between the two.\n\n.panel {\n > .list-group,\n > .panel-collapse > .list-group {\n margin-bottom: 0;\n\n .list-group-item {\n border-width: 1px 0;\n border-radius: 0;\n }\n\n // Add border top radius for first one\n &:first-child {\n .list-group-item:first-child {\n border-top: 0;\n .border-top-radius((@panel-border-radius - 1));\n }\n }\n // Add border bottom radius for last one\n &:last-child {\n .list-group-item:last-child {\n border-bottom: 0;\n .border-bottom-radius((@panel-border-radius - 1));\n }\n }\n }\n}\n// Collapse space between when there's no additional content.\n.panel-heading + .list-group {\n .list-group-item:first-child {\n border-top-width: 0;\n }\n}\n.list-group + .panel-footer {\n border-top-width: 0;\n}\n\n// Tables in panels\n//\n// Place a non-bordered `.table` within a panel (not within a `.panel-body`) and\n// watch it go full width.\n\n.panel {\n > .table,\n > .table-responsive > .table,\n > .panel-collapse > .table {\n margin-bottom: 0;\n\n caption {\n padding-left: @panel-body-padding;\n padding-right: @panel-body-padding;\n }\n }\n // Add border top radius for first one\n > .table:first-child,\n > .table-responsive:first-child > .table:first-child {\n .border-top-radius((@panel-border-radius - 1));\n\n > thead:first-child,\n > tbody:first-child {\n > tr:first-child {\n border-top-left-radius: (@panel-border-radius - 1);\n border-top-right-radius: (@panel-border-radius - 1);\n\n td:first-child,\n th:first-child {\n border-top-left-radius: (@panel-border-radius - 1);\n }\n td:last-child,\n th:last-child {\n border-top-right-radius: (@panel-border-radius - 1);\n }\n }\n }\n }\n // Add border bottom radius for last one\n > .table:last-child,\n > .table-responsive:last-child > .table:last-child {\n .border-bottom-radius((@panel-border-radius - 1));\n\n > tbody:last-child,\n > tfoot:last-child {\n > tr:last-child {\n border-bottom-left-radius: (@panel-border-radius - 1);\n border-bottom-right-radius: (@panel-border-radius - 1);\n\n td:first-child,\n th:first-child {\n border-bottom-left-radius: (@panel-border-radius - 1);\n }\n td:last-child,\n th:last-child {\n border-bottom-right-radius: (@panel-border-radius - 1);\n }\n }\n }\n }\n > .panel-body + .table,\n > .panel-body + .table-responsive,\n > .table + .panel-body,\n > .table-responsive + .panel-body {\n border-top: 1px solid @table-border-color;\n }\n > .table > tbody:first-child > tr:first-child th,\n > .table > tbody:first-child > tr:first-child td {\n border-top: 0;\n }\n > .table-bordered,\n > .table-responsive > .table-bordered {\n border: 0;\n > thead,\n > tbody,\n > tfoot {\n > tr {\n > th:first-child,\n > td:first-child {\n border-left: 0;\n }\n > th:last-child,\n > td:last-child {\n border-right: 0;\n }\n }\n }\n > thead,\n > tbody {\n > tr:first-child {\n > td,\n > th {\n border-bottom: 0;\n }\n }\n }\n > tbody,\n > tfoot {\n > tr:last-child {\n > td,\n > th {\n border-bottom: 0;\n }\n }\n }\n }\n > .table-responsive {\n border: 0;\n margin-bottom: 0;\n }\n}\n\n\n// Collapsable panels (aka, accordion)\n//\n// Wrap a series of panels in `.panel-group` to turn them into an accordion with\n// the help of our collapse JavaScript plugin.\n\n.panel-group {\n margin-bottom: @line-height-computed;\n\n // Tighten up margin so it's only between panels\n .panel {\n margin-bottom: 0;\n border-radius: @panel-border-radius;\n\n + .panel {\n margin-top: 5px;\n }\n }\n\n .panel-heading {\n border-bottom: 0;\n\n + .panel-collapse > .panel-body,\n + .panel-collapse > .list-group {\n border-top: 1px solid @panel-inner-border;\n }\n }\n\n .panel-footer {\n border-top: 0;\n + .panel-collapse .panel-body {\n border-bottom: 1px solid @panel-inner-border;\n }\n }\n}\n\n\n// Contextual variations\n.panel-default {\n .panel-variant(@panel-default-border; @panel-default-text; @panel-default-heading-bg; @panel-default-border);\n}\n.panel-primary {\n .panel-variant(@panel-primary-border; @panel-primary-text; @panel-primary-heading-bg; @panel-primary-border);\n}\n.panel-success {\n .panel-variant(@panel-success-border; @panel-success-text; @panel-success-heading-bg; @panel-success-border);\n}\n.panel-info {\n .panel-variant(@panel-info-border; @panel-info-text; @panel-info-heading-bg; @panel-info-border);\n}\n.panel-warning {\n .panel-variant(@panel-warning-border; @panel-warning-text; @panel-warning-heading-bg; @panel-warning-border);\n}\n.panel-danger {\n .panel-variant(@panel-danger-border; @panel-danger-text; @panel-danger-heading-bg; @panel-danger-border);\n}\n","// Panels\n\n.panel-variant(@border; @heading-text-color; @heading-bg-color; @heading-border) {\n border-color: @border;\n\n & > .panel-heading {\n color: @heading-text-color;\n background-color: @heading-bg-color;\n border-color: @heading-border;\n\n + .panel-collapse > .panel-body {\n border-top-color: @border;\n }\n .badge {\n color: @heading-bg-color;\n background-color: @heading-text-color;\n }\n }\n & > .panel-footer {\n + .panel-collapse > .panel-body {\n border-bottom-color: @border;\n }\n }\n}\n","// Embeds responsive\n//\n// Credit: Nicolas Gallagher and SUIT CSS.\n\n.embed-responsive {\n position: relative;\n display: block;\n height: 0;\n padding: 0;\n overflow: hidden;\n\n .embed-responsive-item,\n iframe,\n embed,\n object,\n video {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n height: 100%;\n width: 100%;\n border: 0;\n }\n\n // Modifier class for 16:9 aspect ratio\n &.embed-responsive-16by9 {\n padding-bottom: 56.25%;\n }\n\n // Modifier class for 4:3 aspect ratio\n &.embed-responsive-4by3 {\n padding-bottom: 75%;\n }\n}\n","//\n// Wells\n// --------------------------------------------------\n\n\n// Base class\n.well {\n min-height: 20px;\n padding: 19px;\n margin-bottom: 20px;\n background-color: @well-bg;\n border: 1px solid @well-border;\n border-radius: @border-radius-base;\n .box-shadow(inset 0 1px 1px rgba(0,0,0,.05));\n blockquote {\n border-color: #ddd;\n border-color: rgba(0,0,0,.15);\n }\n}\n\n// Sizes\n.well-lg {\n padding: 24px;\n border-radius: @border-radius-large;\n}\n.well-sm {\n padding: 9px;\n border-radius: @border-radius-small;\n}\n","//\n// Close icons\n// --------------------------------------------------\n\n\n.close {\n float: right;\n font-size: (@font-size-base * 1.5);\n font-weight: @close-font-weight;\n line-height: 1;\n color: @close-color;\n text-shadow: @close-text-shadow;\n .opacity(.2);\n\n &:hover,\n &:focus {\n color: @close-color;\n text-decoration: none;\n cursor: pointer;\n .opacity(.5);\n }\n\n // Additional properties for button version\n // iOS requires the button element instead of an anchor tag.\n // If you want the anchor version, it requires `href=\"#\"`.\n // See https://developer.mozilla.org/en-US/docs/Web/Events/click#Safari_Mobile\n button& {\n padding: 0;\n cursor: pointer;\n background: transparent;\n border: 0;\n -webkit-appearance: none;\n }\n}\n","//\n// Modals\n// --------------------------------------------------\n\n// .modal-open - body class for killing the scroll\n// .modal - container to scroll within\n// .modal-dialog - positioning shell for the actual modal\n// .modal-content - actual modal w/ bg and corners and shit\n\n// Kill the scroll on the body\n.modal-open {\n overflow: hidden;\n}\n\n// Container that the modal scrolls within\n.modal {\n display: none;\n overflow: hidden;\n position: fixed;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n z-index: @zindex-modal;\n -webkit-overflow-scrolling: touch;\n\n // Prevent Chrome on Windows from adding a focus outline. For details, see\n // https://github.com/twbs/bootstrap/pull/10951.\n outline: 0;\n\n // When fading in the modal, animate it to slide down\n &.fade .modal-dialog {\n .translate(0, -25%);\n .transition-transform(~\"0.3s ease-out\");\n }\n &.in .modal-dialog { .translate(0, 0) }\n}\n.modal-open .modal {\n overflow-x: hidden;\n overflow-y: auto;\n}\n\n// Shell div to position the modal with bottom padding\n.modal-dialog {\n position: relative;\n width: auto;\n margin: 10px;\n}\n\n// Actual modal\n.modal-content {\n position: relative;\n background-color: @modal-content-bg;\n border: 1px solid @modal-content-fallback-border-color; //old browsers fallback (ie8 etc)\n border: 1px solid @modal-content-border-color;\n border-radius: @border-radius-large;\n .box-shadow(0 3px 9px rgba(0,0,0,.5));\n background-clip: padding-box;\n // Remove focus outline from opened modal\n outline: 0;\n}\n\n// Modal background\n.modal-backdrop {\n position: absolute;\n top: 0;\n right: 0;\n left: 0;\n background-color: @modal-backdrop-bg;\n // Fade for backdrop\n &.fade { .opacity(0); }\n &.in { .opacity(@modal-backdrop-opacity); }\n}\n\n// Modal header\n// Top section of the modal w/ title and dismiss\n.modal-header {\n padding: @modal-title-padding;\n border-bottom: 1px solid @modal-header-border-color;\n min-height: (@modal-title-padding + @modal-title-line-height);\n}\n// Close icon\n.modal-header .close {\n margin-top: -2px;\n}\n\n// Title text within header\n.modal-title {\n margin: 0;\n line-height: @modal-title-line-height;\n}\n\n// Modal body\n// Where all modal content resides (sibling of .modal-header and .modal-footer)\n.modal-body {\n position: relative;\n padding: @modal-inner-padding;\n}\n\n// Footer (for actions)\n.modal-footer {\n padding: @modal-inner-padding;\n text-align: right; // right align buttons\n border-top: 1px solid @modal-footer-border-color;\n &:extend(.clearfix all); // clear it in case folks use .pull-* classes on buttons\n\n // Properly space out buttons\n .btn + .btn {\n margin-left: 5px;\n margin-bottom: 0; // account for input[type=\"submit\"] which gets the bottom margin like all other inputs\n }\n // but override that for button groups\n .btn-group .btn + .btn {\n margin-left: -1px;\n }\n // and override it for block buttons as well\n .btn-block + .btn-block {\n margin-left: 0;\n }\n}\n\n// Measure scrollbar width for padding body during modal show/hide\n.modal-scrollbar-measure {\n position: absolute;\n top: -9999px;\n width: 50px;\n height: 50px;\n overflow: scroll;\n}\n\n// Scale up the modal\n@media (min-width: @screen-sm-min) {\n // Automatically set modal's width for larger viewports\n .modal-dialog {\n width: @modal-md;\n margin: 30px auto;\n }\n .modal-content {\n .box-shadow(0 5px 15px rgba(0,0,0,.5));\n }\n\n // Modal sizes\n .modal-sm { width: @modal-sm; }\n}\n\n@media (min-width: @screen-md-min) {\n .modal-lg { width: @modal-lg; }\n}\n","//\n// Tooltips\n// --------------------------------------------------\n\n\n// Base class\n.tooltip {\n position: absolute;\n z-index: @zindex-tooltip;\n display: block;\n visibility: visible;\n // Reset font and text properties given new insertion method\n font-family: @font-family-base;\n font-size: @font-size-small;\n font-weight: normal;\n line-height: 1.4;\n .opacity(0);\n\n &.in { .opacity(@tooltip-opacity); }\n &.top { margin-top: -3px; padding: @tooltip-arrow-width 0; }\n &.right { margin-left: 3px; padding: 0 @tooltip-arrow-width; }\n &.bottom { margin-top: 3px; padding: @tooltip-arrow-width 0; }\n &.left { margin-left: -3px; padding: 0 @tooltip-arrow-width; }\n}\n\n// Wrapper for the tooltip content\n.tooltip-inner {\n max-width: @tooltip-max-width;\n padding: 3px 8px;\n color: @tooltip-color;\n text-align: center;\n text-decoration: none;\n background-color: @tooltip-bg;\n border-radius: @border-radius-base;\n}\n\n// Arrows\n.tooltip-arrow {\n position: absolute;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n}\n// Note: Deprecated .top-left, .top-right, .bottom-left, and .bottom-right as of v3.3.1\n.tooltip {\n &.top .tooltip-arrow {\n bottom: 0;\n left: 50%;\n margin-left: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width @tooltip-arrow-width 0;\n border-top-color: @tooltip-arrow-color;\n }\n &.top-left .tooltip-arrow {\n bottom: 0;\n right: @tooltip-arrow-width;\n margin-bottom: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width @tooltip-arrow-width 0;\n border-top-color: @tooltip-arrow-color;\n }\n &.top-right .tooltip-arrow {\n bottom: 0;\n left: @tooltip-arrow-width;\n margin-bottom: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width @tooltip-arrow-width 0;\n border-top-color: @tooltip-arrow-color;\n }\n &.right .tooltip-arrow {\n top: 50%;\n left: 0;\n margin-top: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width @tooltip-arrow-width @tooltip-arrow-width 0;\n border-right-color: @tooltip-arrow-color;\n }\n &.left .tooltip-arrow {\n top: 50%;\n right: 0;\n margin-top: -@tooltip-arrow-width;\n border-width: @tooltip-arrow-width 0 @tooltip-arrow-width @tooltip-arrow-width;\n border-left-color: @tooltip-arrow-color;\n }\n &.bottom .tooltip-arrow {\n top: 0;\n left: 50%;\n margin-left: -@tooltip-arrow-width;\n border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;\n border-bottom-color: @tooltip-arrow-color;\n }\n &.bottom-left .tooltip-arrow {\n top: 0;\n right: @tooltip-arrow-width;\n margin-top: -@tooltip-arrow-width;\n border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;\n border-bottom-color: @tooltip-arrow-color;\n }\n &.bottom-right .tooltip-arrow {\n top: 0;\n left: @tooltip-arrow-width;\n margin-top: -@tooltip-arrow-width;\n border-width: 0 @tooltip-arrow-width @tooltip-arrow-width;\n border-bottom-color: @tooltip-arrow-color;\n }\n}\n","//\n// Popovers\n// --------------------------------------------------\n\n\n.popover {\n position: absolute;\n top: 0;\n left: 0;\n z-index: @zindex-popover;\n display: none;\n max-width: @popover-max-width;\n padding: 1px;\n // Reset font and text properties given new insertion method\n font-family: @font-family-base;\n font-size: @font-size-base;\n font-weight: normal;\n line-height: @line-height-base;\n text-align: left;\n background-color: @popover-bg;\n background-clip: padding-box;\n border: 1px solid @popover-fallback-border-color;\n border: 1px solid @popover-border-color;\n border-radius: @border-radius-large;\n .box-shadow(0 5px 10px rgba(0,0,0,.2));\n\n // Overrides for proper insertion\n white-space: normal;\n\n // Offset the popover to account for the popover arrow\n &.top { margin-top: -@popover-arrow-width; }\n &.right { margin-left: @popover-arrow-width; }\n &.bottom { margin-top: @popover-arrow-width; }\n &.left { margin-left: -@popover-arrow-width; }\n}\n\n.popover-title {\n margin: 0; // reset heading margin\n padding: 8px 14px;\n font-size: @font-size-base;\n background-color: @popover-title-bg;\n border-bottom: 1px solid darken(@popover-title-bg, 5%);\n border-radius: (@border-radius-large - 1) (@border-radius-large - 1) 0 0;\n}\n\n.popover-content {\n padding: 9px 14px;\n}\n\n// Arrows\n//\n// .arrow is outer, .arrow:after is inner\n\n.popover > .arrow {\n &,\n &:after {\n position: absolute;\n display: block;\n width: 0;\n height: 0;\n border-color: transparent;\n border-style: solid;\n }\n}\n.popover > .arrow {\n border-width: @popover-arrow-outer-width;\n}\n.popover > .arrow:after {\n border-width: @popover-arrow-width;\n content: \"\";\n}\n\n.popover {\n &.top > .arrow {\n left: 50%;\n margin-left: -@popover-arrow-outer-width;\n border-bottom-width: 0;\n border-top-color: @popover-arrow-outer-fallback-color; // IE8 fallback\n border-top-color: @popover-arrow-outer-color;\n bottom: -@popover-arrow-outer-width;\n &:after {\n content: \" \";\n bottom: 1px;\n margin-left: -@popover-arrow-width;\n border-bottom-width: 0;\n border-top-color: @popover-arrow-color;\n }\n }\n &.right > .arrow {\n top: 50%;\n left: -@popover-arrow-outer-width;\n margin-top: -@popover-arrow-outer-width;\n border-left-width: 0;\n border-right-color: @popover-arrow-outer-fallback-color; // IE8 fallback\n border-right-color: @popover-arrow-outer-color;\n &:after {\n content: \" \";\n left: 1px;\n bottom: -@popover-arrow-width;\n border-left-width: 0;\n border-right-color: @popover-arrow-color;\n }\n }\n &.bottom > .arrow {\n left: 50%;\n margin-left: -@popover-arrow-outer-width;\n border-top-width: 0;\n border-bottom-color: @popover-arrow-outer-fallback-color; // IE8 fallback\n border-bottom-color: @popover-arrow-outer-color;\n top: -@popover-arrow-outer-width;\n &:after {\n content: \" \";\n top: 1px;\n margin-left: -@popover-arrow-width;\n border-top-width: 0;\n border-bottom-color: @popover-arrow-color;\n }\n }\n\n &.left > .arrow {\n top: 50%;\n right: -@popover-arrow-outer-width;\n margin-top: -@popover-arrow-outer-width;\n border-right-width: 0;\n border-left-color: @popover-arrow-outer-fallback-color; // IE8 fallback\n border-left-color: @popover-arrow-outer-color;\n &:after {\n content: \" \";\n right: 1px;\n border-right-width: 0;\n border-left-color: @popover-arrow-color;\n bottom: -@popover-arrow-width;\n }\n }\n}\n","//\n// Carousel\n// --------------------------------------------------\n\n\n// Wrapper for the slide container and indicators\n.carousel {\n position: relative;\n}\n\n.carousel-inner {\n position: relative;\n overflow: hidden;\n width: 100%;\n\n > .item {\n display: none;\n position: relative;\n .transition(.6s ease-in-out left);\n\n // Account for jankitude on images\n > img,\n > a > img {\n &:extend(.img-responsive);\n line-height: 1;\n }\n\n // WebKit CSS3 transforms for supported devices\n @media all and (transform-3d), (-webkit-transform-3d) {\n .transition-transform(~'0.6s ease-in-out');\n .backface-visibility(~'hidden');\n .perspective(1000);\n\n &.next,\n &.active.right {\n .translate3d(100%, 0, 0);\n left: 0;\n }\n &.prev,\n &.active.left {\n .translate3d(-100%, 0, 0);\n left: 0;\n }\n &.next.left,\n &.prev.right,\n &.active {\n .translate3d(0, 0, 0);\n left: 0;\n }\n }\n }\n\n > .active,\n > .next,\n > .prev {\n display: block;\n }\n\n > .active {\n left: 0;\n }\n\n > .next,\n > .prev {\n position: absolute;\n top: 0;\n width: 100%;\n }\n\n > .next {\n left: 100%;\n }\n > .prev {\n left: -100%;\n }\n > .next.left,\n > .prev.right {\n left: 0;\n }\n\n > .active.left {\n left: -100%;\n }\n > .active.right {\n left: 100%;\n }\n\n}\n\n// Left/right controls for nav\n// ---------------------------\n\n.carousel-control {\n position: absolute;\n top: 0;\n left: 0;\n bottom: 0;\n width: @carousel-control-width;\n .opacity(@carousel-control-opacity);\n font-size: @carousel-control-font-size;\n color: @carousel-control-color;\n text-align: center;\n text-shadow: @carousel-text-shadow;\n // We can't have this transition here because WebKit cancels the carousel\n // animation if you trip this while in the middle of another animation.\n\n // Set gradients for backgrounds\n &.left {\n #gradient > .horizontal(@start-color: rgba(0,0,0,.5); @end-color: rgba(0,0,0,.0001));\n }\n &.right {\n left: auto;\n right: 0;\n #gradient > .horizontal(@start-color: rgba(0,0,0,.0001); @end-color: rgba(0,0,0,.5));\n }\n\n // Hover/focus state\n &:hover,\n &:focus {\n outline: 0;\n color: @carousel-control-color;\n text-decoration: none;\n .opacity(.9);\n }\n\n // Toggles\n .icon-prev,\n .icon-next,\n .glyphicon-chevron-left,\n .glyphicon-chevron-right {\n position: absolute;\n top: 50%;\n z-index: 5;\n display: inline-block;\n }\n .icon-prev,\n .glyphicon-chevron-left {\n left: 50%;\n margin-left: -10px;\n }\n .icon-next,\n .glyphicon-chevron-right {\n right: 50%;\n margin-right: -10px;\n }\n .icon-prev,\n .icon-next {\n width: 20px;\n height: 20px;\n margin-top: -10px;\n line-height: 1;\n font-family: serif;\n }\n\n\n .icon-prev {\n &:before {\n content: '\\2039';// SINGLE LEFT-POINTING ANGLE QUOTATION MARK (U+2039)\n }\n }\n .icon-next {\n &:before {\n content: '\\203a';// SINGLE RIGHT-POINTING ANGLE QUOTATION MARK (U+203A)\n }\n }\n}\n\n// Optional indicator pips\n//\n// Add an unordered list with the following class and add a list item for each\n// slide your carousel holds.\n\n.carousel-indicators {\n position: absolute;\n bottom: 10px;\n left: 50%;\n z-index: 15;\n width: 60%;\n margin-left: -30%;\n padding-left: 0;\n list-style: none;\n text-align: center;\n\n li {\n display: inline-block;\n width: 10px;\n height: 10px;\n margin: 1px;\n text-indent: -999px;\n border: 1px solid @carousel-indicator-border-color;\n border-radius: 10px;\n cursor: pointer;\n\n // IE8-9 hack for event handling\n //\n // Internet Explorer 8-9 does not support clicks on elements without a set\n // `background-color`. We cannot use `filter` since that's not viewed as a\n // background color by the browser. Thus, a hack is needed.\n // See https://developer.mozilla.org/en-US/docs/Web/Events/click#Internet_Explorer\n //\n // For IE8, we set solid black as it doesn't support `rgba()`. For IE9, we\n // set alpha transparency for the best results possible.\n background-color: #000 \\9; // IE8\n background-color: rgba(0,0,0,0); // IE9\n }\n .active {\n margin: 0;\n width: 12px;\n height: 12px;\n background-color: @carousel-indicator-active-bg;\n }\n}\n\n// Optional captions\n// -----------------------------\n// Hidden by default for smaller viewports\n.carousel-caption {\n position: absolute;\n left: 15%;\n right: 15%;\n bottom: 20px;\n z-index: 10;\n padding-top: 20px;\n padding-bottom: 20px;\n color: @carousel-caption-color;\n text-align: center;\n text-shadow: @carousel-text-shadow;\n & .btn {\n text-shadow: none; // No shadow for button elements in carousel-caption\n }\n}\n\n\n// Scale up controls for tablets and up\n@media screen and (min-width: @screen-sm-min) {\n\n // Scale up the controls a smidge\n .carousel-control {\n .glyphicon-chevron-left,\n .glyphicon-chevron-right,\n .icon-prev,\n .icon-next {\n width: 30px;\n height: 30px;\n margin-top: -15px;\n font-size: 30px;\n }\n .glyphicon-chevron-left,\n .icon-prev {\n margin-left: -15px;\n }\n .glyphicon-chevron-right,\n .icon-next {\n margin-right: -15px;\n }\n }\n\n // Show and left align the captions\n .carousel-caption {\n left: 20%;\n right: 20%;\n padding-bottom: 30px;\n }\n\n // Move up the indicators\n .carousel-indicators {\n bottom: 20px;\n }\n}\n","// Clearfix\n//\n// For modern browsers\n// 1. The space content is one way to avoid an Opera bug when the\n// contenteditable attribute is included anywhere else in the document.\n// Otherwise it causes space to appear at the top and bottom of elements\n// that are clearfixed.\n// 2. The use of `table` rather than `block` is only necessary if using\n// `:before` to contain the top-margins of child elements.\n//\n// Source: http://nicolasgallagher.com/micro-clearfix-hack/\n\n.clearfix() {\n &:before,\n &:after {\n content: \" \"; // 1\n display: table; // 2\n }\n &:after {\n clear: both;\n }\n}\n","// Center-align a block level element\n\n.center-block() {\n display: block;\n margin-left: auto;\n margin-right: auto;\n}\n","// CSS image replacement\n//\n// Heads up! v3 launched with with only `.hide-text()`, but per our pattern for\n// mixins being reused as classes with the same name, this doesn't hold up. As\n// of v3.0.1 we have added `.text-hide()` and deprecated `.hide-text()`.\n//\n// Source: https://github.com/h5bp/html5-boilerplate/commit/aa0396eae757\n\n// Deprecated as of v3.0.1 (will be removed in v4)\n.hide-text() {\n font: ~\"0/0\" a;\n color: transparent;\n text-shadow: none;\n background-color: transparent;\n border: 0;\n}\n\n// New mixin to use as of v3.0.1\n.text-hide() {\n .hide-text();\n}\n","//\n// Responsive: Utility classes\n// --------------------------------------------------\n\n\n// IE10 in Windows (Phone) 8\n//\n// Support for responsive views via media queries is kind of borked in IE10, for\n// Surface/desktop in split view and for Windows Phone 8. This particular fix\n// must be accompanied by a snippet of JavaScript to sniff the user agent and\n// apply some conditional CSS to *only* the Surface/desktop Windows 8. Look at\n// our Getting Started page for more information on this bug.\n//\n// For more information, see the following:\n//\n// Issue: https://github.com/twbs/bootstrap/issues/10497\n// Docs: http://getbootstrap.com/getting-started/#support-ie10-width\n// Source: http://timkadlec.com/2013/01/windows-phone-8-and-device-width/\n// Source: http://timkadlec.com/2012/10/ie10-snap-mode-and-responsive-design/\n\n@-ms-viewport {\n width: device-width;\n}\n\n\n// Visibility utilities\n// Note: Deprecated .visible-xs, .visible-sm, .visible-md, and .visible-lg as of v3.2.0\n.visible-xs,\n.visible-sm,\n.visible-md,\n.visible-lg {\n .responsive-invisibility();\n}\n\n.visible-xs-block,\n.visible-xs-inline,\n.visible-xs-inline-block,\n.visible-sm-block,\n.visible-sm-inline,\n.visible-sm-inline-block,\n.visible-md-block,\n.visible-md-inline,\n.visible-md-inline-block,\n.visible-lg-block,\n.visible-lg-inline,\n.visible-lg-inline-block {\n display: none !important;\n}\n\n.visible-xs {\n @media (max-width: @screen-xs-max) {\n .responsive-visibility();\n }\n}\n.visible-xs-block {\n @media (max-width: @screen-xs-max) {\n display: block !important;\n }\n}\n.visible-xs-inline {\n @media (max-width: @screen-xs-max) {\n display: inline !important;\n }\n}\n.visible-xs-inline-block {\n @media (max-width: @screen-xs-max) {\n display: inline-block !important;\n }\n}\n\n.visible-sm {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n .responsive-visibility();\n }\n}\n.visible-sm-block {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n display: block !important;\n }\n}\n.visible-sm-inline {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n display: inline !important;\n }\n}\n.visible-sm-inline-block {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n display: inline-block !important;\n }\n}\n\n.visible-md {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n .responsive-visibility();\n }\n}\n.visible-md-block {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n display: block !important;\n }\n}\n.visible-md-inline {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n display: inline !important;\n }\n}\n.visible-md-inline-block {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n display: inline-block !important;\n }\n}\n\n.visible-lg {\n @media (min-width: @screen-lg-min) {\n .responsive-visibility();\n }\n}\n.visible-lg-block {\n @media (min-width: @screen-lg-min) {\n display: block !important;\n }\n}\n.visible-lg-inline {\n @media (min-width: @screen-lg-min) {\n display: inline !important;\n }\n}\n.visible-lg-inline-block {\n @media (min-width: @screen-lg-min) {\n display: inline-block !important;\n }\n}\n\n.hidden-xs {\n @media (max-width: @screen-xs-max) {\n .responsive-invisibility();\n }\n}\n.hidden-sm {\n @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {\n .responsive-invisibility();\n }\n}\n.hidden-md {\n @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {\n .responsive-invisibility();\n }\n}\n.hidden-lg {\n @media (min-width: @screen-lg-min) {\n .responsive-invisibility();\n }\n}\n\n\n// Print utilities\n//\n// Media queries are placed on the inside to be mixin-friendly.\n\n// Note: Deprecated .visible-print as of v3.2.0\n.visible-print {\n .responsive-invisibility();\n\n @media print {\n .responsive-visibility();\n }\n}\n.visible-print-block {\n display: none !important;\n\n @media print {\n display: block !important;\n }\n}\n.visible-print-inline {\n display: none !important;\n\n @media print {\n display: inline !important;\n }\n}\n.visible-print-inline-block {\n display: none !important;\n\n @media print {\n display: inline-block !important;\n }\n}\n\n.hidden-print {\n @media print {\n .responsive-invisibility();\n }\n}\n","// Responsive utilities\n\n//\n// More easily include all the states for responsive-utilities.less.\n.responsive-visibility() {\n display: block !important;\n table& { display: table; }\n tr& { display: table-row !important; }\n th&,\n td& { display: table-cell !important; }\n}\n\n.responsive-invisibility() {\n display: none !important;\n}\n"]}
\ No newline at end of file diff --git a/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap.min.css b/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap.min.css new file mode 100644 index 000000000..28f154dec --- /dev/null +++ b/pyload/webui/themes/Next/lib/Bootstrap/css/bootstrap.min.css @@ -0,0 +1,5 @@ +/*! + * Bootstrap v3.3.2 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + *//*! normalize.css v3.0.2 | MIT License | git.io/normalize */html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type=checkbox],input[type=radio]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}/*! Source: https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css */@media print{*,:after,:before{color:#000!important;text-shadow:none!important;background:0 0!important;-webkit-box-shadow:none!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}blockquote,pre{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}img,tr{page-break-inside:avoid}img{max-width:100%!important}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff!important}.navbar{display:none}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table td,.table th{background-color:#fff!important}.table-bordered td,.table-bordered th{border:1px solid #ddd!important}}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff2) format('woff2'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-eur:before,.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.glyphicon-cd:before{content:"\e201"}.glyphicon-save-file:before{content:"\e202"}.glyphicon-open-file:before{content:"\e203"}.glyphicon-level-up:before{content:"\e204"}.glyphicon-copy:before{content:"\e205"}.glyphicon-paste:before{content:"\e206"}.glyphicon-alert:before{content:"\e209"}.glyphicon-equalizer:before{content:"\e210"}.glyphicon-king:before{content:"\e211"}.glyphicon-queen:before{content:"\e212"}.glyphicon-pawn:before{content:"\e213"}.glyphicon-bishop:before{content:"\e214"}.glyphicon-knight:before{content:"\e215"}.glyphicon-baby-formula:before{content:"\e216"}.glyphicon-tent:before{content:"\26fa"}.glyphicon-blackboard:before{content:"\e218"}.glyphicon-bed:before{content:"\e219"}.glyphicon-apple:before{content:"\f8ff"}.glyphicon-erase:before{content:"\e221"}.glyphicon-hourglass:before{content:"\231b"}.glyphicon-lamp:before{content:"\e223"}.glyphicon-duplicate:before{content:"\e224"}.glyphicon-piggy-bank:before{content:"\e225"}.glyphicon-scissors:before{content:"\e226"}.glyphicon-bitcoin:before{content:"\e227"}.glyphicon-yen:before{content:"\00a5"}.glyphicon-ruble:before{content:"\20bd"}.glyphicon-scale:before{content:"\e230"}.glyphicon-ice-lolly:before{content:"\e231"}.glyphicon-ice-lolly-tasted:before{content:"\e232"}.glyphicon-education:before{content:"\e233"}.glyphicon-option-horizontal:before{content:"\e234"}.glyphicon-option-vertical:before{content:"\e235"}.glyphicon-menu-hamburger:before{content:"\e236"}.glyphicon-modal-window:before{content:"\e237"}.glyphicon-oil:before{content:"\e238"}.glyphicon-grain:before{content:"\e239"}.glyphicon-sunglasses:before{content:"\e240"}.glyphicon-text-size:before{content:"\e241"}.glyphicon-text-color:before{content:"\e242"}.glyphicon-text-background:before{content:"\e243"}.glyphicon-object-align-top:before{content:"\e244"}.glyphicon-object-align-bottom:before{content:"\e245"}.glyphicon-object-align-horizontal:before{content:"\e246"}.glyphicon-object-align-left:before{content:"\e247"}.glyphicon-object-align-vertical:before{content:"\e248"}.glyphicon-object-align-right:before{content:"\e249"}.glyphicon-triangle-right:before{content:"\e250"}.glyphicon-triangle-left:before{content:"\e251"}.glyphicon-triangle-bottom:before{content:"\e252"}.glyphicon-triangle-top:before{content:"\e253"}.glyphicon-console:before{content:"\e254"}.glyphicon-superscript:before{content:"\e255"}.glyphicon-subscript:before{content:"\e256"}.glyphicon-menu-left:before{content:"\e257"}.glyphicon-menu-right:before{content:"\e258"}.glyphicon-menu-down:before{content:"\e259"}.glyphicon-menu-up:before{content:"\e260"}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:after,:before{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:10px;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}button,input,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#337ab7;text-decoration:none}a:focus,a:hover{color:#23527c;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.carousel-inner>.item>a>img,.carousel-inner>.item>img,.img-responsive,.thumbnail a>img,.thumbnail>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{display:inline-block;max-width:100%;height:auto;padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-weight:400;line-height:1;color:#777}.h1,.h2,.h3,h1,h2,h3{margin-top:20px;margin-bottom:10px}.h1 .small,.h1 small,.h2 .small,.h2 small,.h3 .small,.h3 small,h1 .small,h1 small,h2 .small,h2 small,h3 .small,h3 small{font-size:65%}.h4,.h5,.h6,h4,h5,h6{margin-top:10px;margin-bottom:10px}.h4 .small,.h4 small,.h5 .small,.h5 small,.h6 .small,.h6 small,h4 .small,h4 small,h5 .small,h5 small,h6 .small,h6 small{font-size:75%}.h1,h1{font-size:36px}.h2,h2{font-size:30px}.h3,h3{font-size:24px}.h4,h4{font-size:18px}.h5,h5{font-size:14px}.h6,h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:300;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}.small,small{font-size:85%}.mark,mark{padding:.2em;background-color:#fcf8e3}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-nowrap{white-space:nowrap}.text-lowercase{text-transform:lowercase}.text-uppercase{text-transform:uppercase}.text-capitalize{text-transform:capitalize}.text-muted{color:#777}.text-primary{color:#337ab7}a.text-primary:hover{color:#286090}.text-success{color:#3c763d}a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#337ab7}a.bg-primary:hover{background-color:#286090}.bg-success{background-color:#dff0d8}a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ol,ul{margin-top:0;margin-bottom:10px}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;margin-left:-5px;list-style:none}.list-inline>li{display:inline-block;padding-right:5px;padding-left:5px}dl{margin-top:0;margin-bottom:20px}dd,dt{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[data-original-title],abbr[title]{cursor:help;border-bottom:1px dotted #777}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote ol:last-child,blockquote p:last-child,blockquote ul:last-child{margin-bottom:0}blockquote .small,blockquote footer,blockquote small{display:block;font-size:80%;line-height:1.42857143;color:#777}blockquote .small:before,blockquote footer:before,blockquote small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;text-align:right;border-right:5px solid #eee;border-left:0}.blockquote-reverse .small:before,.blockquote-reverse footer:before,.blockquote-reverse small:before,blockquote.pull-right .small:before,blockquote.pull-right footer:before,blockquote.pull-right small:before{content:''}.blockquote-reverse .small:after,.blockquote-reverse footer:after,.blockquote-reverse small:after,blockquote.pull-right .small:after,blockquote.pull-right footer:after,blockquote.pull-right small:after{content:'\00A0 \2014'}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.25);box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}kbd kbd{padding:0;font-size:100%;font-weight:700;-webkit-box-shadow:none;box-shadow:none}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;color:#333;word-break:break-all;word-wrap:break-word;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{padding-right:15px;padding-left:15px;margin-right:auto;margin-left:auto}.row{margin-right:-15px;margin-left:-15px}.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{position:relative;min-height:1px;padding-right:15px;padding-left:15px}.col-xs-1,.col-xs-10,.col-xs-11,.col-xs-12,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:auto}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:auto}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-10,.col-sm-11,.col-sm-12,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:auto}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:auto}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-10,.col-md-11,.col-md-12,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:auto}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:auto}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-10,.col-lg-11,.col-lg-12,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:auto}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:auto}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{background-color:transparent}caption{padding-top:8px;padding-bottom:8px;color:#777;text-align:left}th{text-align:left}.table{width:100%;max-width:100%;margin-bottom:20px}.table>tbody>tr>td,.table>tbody>tr>th,.table>tfoot>tr>td,.table>tfoot>tr>th,.table>thead>tr>td,.table>thead>tr>th{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>td,.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>td,.table>thead:first-child>tr:first-child>th{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>tbody>tr>td,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>td,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>thead>tr>th{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>tbody>tr>td,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>td,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border:1px solid #ddd}.table-bordered>thead>tr>td,.table-bordered>thead>tr>th{border-bottom-width:2px}.table-striped>tbody>tr:nth-of-type(odd){background-color:#f9f9f9}.table-hover>tbody>tr:hover{background-color:#f5f5f5}table col[class*=col-]{position:static;display:table-column;float:none}table td[class*=col-],table th[class*=col-]{position:static;display:table-cell;float:none}.table>tbody>tr.active>td,.table>tbody>tr.active>th,.table>tbody>tr>td.active,.table>tbody>tr>th.active,.table>tfoot>tr.active>td,.table>tfoot>tr.active>th,.table>tfoot>tr>td.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>thead>tr.active>th,.table>thead>tr>td.active,.table>thead>tr>th.active{background-color:#f5f5f5}.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th,.table-hover>tbody>tr:hover>.active,.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover{background-color:#e8e8e8}.table>tbody>tr.success>td,.table>tbody>tr.success>th,.table>tbody>tr>td.success,.table>tbody>tr>th.success,.table>tfoot>tr.success>td,.table>tfoot>tr.success>th,.table>tfoot>tr>td.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>thead>tr.success>th,.table>thead>tr>td.success,.table>thead>tr>th.success{background-color:#dff0d8}.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th,.table-hover>tbody>tr:hover>.success,.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover{background-color:#d0e9c6}.table>tbody>tr.info>td,.table>tbody>tr.info>th,.table>tbody>tr>td.info,.table>tbody>tr>th.info,.table>tfoot>tr.info>td,.table>tfoot>tr.info>th,.table>tfoot>tr>td.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>thead>tr.info>th,.table>thead>tr>td.info,.table>thead>tr>th.info{background-color:#d9edf7}.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th,.table-hover>tbody>tr:hover>.info,.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover{background-color:#c4e3f3}.table>tbody>tr.warning>td,.table>tbody>tr.warning>th,.table>tbody>tr>td.warning,.table>tbody>tr>th.warning,.table>tfoot>tr.warning>td,.table>tfoot>tr.warning>th,.table>tfoot>tr>td.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>thead>tr.warning>th,.table>thead>tr>td.warning,.table>thead>tr>th.warning{background-color:#fcf8e3}.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th,.table-hover>tbody>tr:hover>.warning,.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover{background-color:#faf2cc}.table>tbody>tr.danger>td,.table>tbody>tr.danger>th,.table>tbody>tr>td.danger,.table>tbody>tr>th.danger,.table>tfoot>tr.danger>td,.table>tfoot>tr.danger>th,.table>tfoot>tr>td.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>thead>tr.danger>th,.table>thead>tr>td.danger,.table>thead>tr>th.danger{background-color:#f2dede}.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th,.table-hover>tbody>tr:hover>.danger,.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover{background-color:#ebcccc}.table-responsive{min-height:.01%;overflow-x:auto}@media screen and (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>td,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>thead>tr>th{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;max-width:100%;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=checkbox],input[type=radio]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=checkbox]:focus,input[type=radio]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;-o-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eee;opacity:1}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}@media screen and (-webkit-min-device-pixel-ratio:0){input[type=date],input[type=time],input[type=datetime-local],input[type=month]{line-height:34px}.input-group-sm input[type=date],.input-group-sm input[type=time],.input-group-sm input[type=datetime-local],.input-group-sm input[type=month],input[type=date].input-sm,input[type=time].input-sm,input[type=datetime-local].input-sm,input[type=month].input-sm{line-height:30px}.input-group-lg input[type=date],.input-group-lg input[type=time],.input-group-lg input[type=datetime-local],.input-group-lg input[type=month],input[type=date].input-lg,input[type=time].input-lg,input[type=datetime-local].input-lg,input[type=month].input-lg{line-height:46px}}.form-group{margin-bottom:15px}.checkbox,.radio{position:relative;display:block;margin-top:10px;margin-bottom:10px}.checkbox label,.radio label{min-height:20px;padding-left:20px;margin-bottom:0;font-weight:400;cursor:pointer}.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox],.radio input[type=radio],.radio-inline input[type=radio]{position:absolute;margin-top:4px \9;margin-left:-20px}.checkbox+.checkbox,.radio+.radio{margin-top:-5px}.checkbox-inline,.radio-inline{display:inline-block;padding-left:20px;margin-bottom:0;font-weight:400;vertical-align:middle;cursor:pointer}.checkbox-inline+.checkbox-inline,.radio-inline+.radio-inline{margin-top:0;margin-left:10px}fieldset[disabled] input[type=checkbox],fieldset[disabled] input[type=radio],input[type=checkbox].disabled,input[type=checkbox][disabled],input[type=radio].disabled,input[type=radio][disabled]{cursor:not-allowed}.checkbox-inline.disabled,.radio-inline.disabled,fieldset[disabled] .checkbox-inline,fieldset[disabled] .radio-inline{cursor:not-allowed}.checkbox.disabled label,.radio.disabled label,fieldset[disabled] .checkbox label,fieldset[disabled] .radio label{cursor:not-allowed}.form-control-static{padding-top:7px;padding-bottom:7px;margin-bottom:0}.form-control-static.input-lg,.form-control-static.input-sm{padding-right:0;padding-left:0}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}select[multiple].input-sm,textarea.input-sm{height:auto}.form-group-sm .form-control{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.form-group-sm .form-control{height:30px;line-height:30px}select[multiple].form-group-sm .form-control,textarea.form-group-sm .form-control{height:auto}.form-group-sm .form-control-static{height:30px;padding:5px 10px;font-size:12px;line-height:1.5}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-lg{height:46px;line-height:46px}select[multiple].input-lg,textarea.input-lg{height:auto}.form-group-lg .form-control{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.form-group-lg .form-control{height:46px;line-height:46px}select[multiple].form-group-lg .form-control,textarea.form-group-lg .form-control{height:auto}.form-group-lg .form-control-static{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.form-control-feedback{position:absolute;top:0;right:0;z-index:2;display:block;width:34px;height:34px;line-height:34px;text-align:center;pointer-events:none}.input-lg+.form-control-feedback{width:46px;height:46px;line-height:46px}.input-sm+.form-control-feedback{width:30px;height:30px;line-height:30px}.has-success .checkbox,.has-success .checkbox-inline,.has-success .control-label,.has-success .help-block,.has-success .radio,.has-success .radio-inline,.has-success.checkbox label,.has-success.checkbox-inline label,.has-success.radio label,.has-success.radio-inline label{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-success .form-control-feedback{color:#3c763d}.has-warning .checkbox,.has-warning .checkbox-inline,.has-warning .control-label,.has-warning .help-block,.has-warning .radio,.has-warning .radio-inline,.has-warning.checkbox label,.has-warning.checkbox-inline label,.has-warning.radio label,.has-warning.radio-inline label{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;background-color:#fcf8e3;border-color:#8a6d3b}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .checkbox,.has-error .checkbox-inline,.has-error .control-label,.has-error .help-block,.has-error .radio,.has-error .radio-inline,.has-error.checkbox label,.has-error.checkbox-inline label,.has-error.radio label,.has-error.radio-inline label{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-error .form-control-feedback{color:#a94442}.has-feedback label~.form-control-feedback{top:25px}.has-feedback label.sr-only~.form-control-feedback{top:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .form-control-static{display:inline-block}.form-inline .input-group{display:inline-table;vertical-align:middle}.form-inline .input-group .form-control,.form-inline .input-group .input-group-addon,.form-inline .input-group .input-group-btn{width:auto}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .checkbox,.form-inline .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.form-inline .checkbox label,.form-inline .radio label{padding-left:0}.form-inline .checkbox input[type=checkbox],.form-inline .radio input[type=radio]{position:relative;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .checkbox,.form-horizontal .checkbox-inline,.form-horizontal .radio,.form-horizontal .radio-inline{padding-top:7px;margin-top:0;margin-bottom:0}.form-horizontal .checkbox,.form-horizontal .radio{min-height:27px}.form-horizontal .form-group{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.form-horizontal .control-label{padding-top:7px;margin-bottom:0;text-align:right}}.form-horizontal .has-feedback .form-control-feedback{right:15px}@media (min-width:768px){.form-horizontal .form-group-lg .control-label{padding-top:14.33px}}@media (min-width:768px){.form-horizontal .form-group-sm .control-label{padding-top:6px}}.btn{display:inline-block;padding:6px 12px;margin-bottom:0;font-size:14px;font-weight:400;line-height:1.42857143;text-align:center;white-space:nowrap;vertical-align:middle;-ms-touch-action:manipulation;touch-action:manipulation;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-image:none;border:1px solid transparent;border-radius:4px}.btn.active.focus,.btn.active:focus,.btn.focus,.btn:active.focus,.btn:active:focus,.btn:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.focus,.btn:focus,.btn:hover{color:#333;text-decoration:none}.btn.active,.btn:active{background-image:none;outline:0;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{pointer-events:none;cursor:not-allowed;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none;opacity:.65}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default.active,.btn-default.focus,.btn-default:active,.btn-default:focus,.btn-default:hover,.open>.dropdown-toggle.btn-default{color:#333;background-color:#e6e6e6;border-color:#adadad}.btn-default.active,.btn-default:active,.open>.dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#337ab7;border-color:#2e6da4}.btn-primary.active,.btn-primary.focus,.btn-primary:active,.btn-primary:focus,.btn-primary:hover,.open>.dropdown-toggle.btn-primary{color:#fff;background-color:#286090;border-color:#204d74}.btn-primary.active,.btn-primary:active,.open>.dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#337ab7;border-color:#2e6da4}.btn-primary .badge{color:#337ab7;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success.active,.btn-success.focus,.btn-success:active,.btn-success:focus,.btn-success:hover,.open>.dropdown-toggle.btn-success{color:#fff;background-color:#449d44;border-color:#398439}.btn-success.active,.btn-success:active,.open>.dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info.active,.btn-info.focus,.btn-info:active,.btn-info:focus,.btn-info:hover,.open>.dropdown-toggle.btn-info{color:#fff;background-color:#31b0d5;border-color:#269abc}.btn-info.active,.btn-info:active,.open>.dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning.active,.btn-warning.focus,.btn-warning:active,.btn-warning:focus,.btn-warning:hover,.open>.dropdown-toggle.btn-warning{color:#fff;background-color:#ec971f;border-color:#d58512}.btn-warning.active,.btn-warning:active,.open>.dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger.active,.btn-danger.focus,.btn-danger:active,.btn-danger:focus,.btn-danger:hover,.open>.dropdown-toggle.btn-danger{color:#fff;background-color:#c9302c;border-color:#ac2925}.btn-danger.active,.btn-danger:active,.open>.dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{font-weight:400;color:#337ab7;border-radius:0}.btn-link,.btn-link.active,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:active,.btn-link:focus,.btn-link:hover{border-color:transparent}.btn-link:focus,.btn-link:hover{color:#23527c;text-decoration:underline;background-color:transparent}.btn-link[disabled]:focus,.btn-link[disabled]:hover,fieldset[disabled] .btn-link:focus,fieldset[disabled] .btn-link:hover{color:#777;text-decoration:none}.btn-group-lg>.btn,.btn-lg{padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}.btn-group-sm>.btn,.btn-sm{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-group-xs>.btn,.btn-xs{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%}.btn-block+.btn-block{margin-top:5px}input[type=button].btn-block,input[type=reset].btn-block,input[type=submit].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none;visibility:hidden}.collapse.in{display:block;visibility:visible}tr.collapse.in{display:table-row}tbody.collapse.in{display:table-row-group}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition-timing-function:ease;-o-transition-timing-function:ease;transition-timing-function:ease;-webkit-transition-duration:.35s;-o-transition-duration:.35s;transition-duration:.35s;-webkit-transition-property:height,visibility;-o-transition-property:height,visibility;transition-property:height,visibility}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px solid;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown,.dropup{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;font-size:14px;text-align:left;list-style:none;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175)}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{color:#262626;text-decoration:none;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{color:#fff;text-decoration:none;background-color:#337ab7;outline:0}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{color:#777}.dropdown-menu>.disabled>a:focus,.dropdown-menu>.disabled>a:hover{text-decoration:none;cursor:not-allowed;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{right:0;left:auto}.dropdown-menu-left{right:auto;left:0}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#777;white-space:nowrap}.dropdown-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{content:"";border-top:0;border-bottom:4px solid}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:2px}@media (min-width:768px){.navbar-right .dropdown-menu{right:0;left:auto}.navbar-right .dropdown-menu-left{right:auto;left:0}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;float:left}.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:2}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-right:8px;padding-left:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-right:12px;padding-left:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-top-left-radius:0;border-top-right-radius:0;border-bottom-left-radius:4px}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-left-radius:0;border-top-right-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{display:table-cell;float:none;width:1%}.btn-group-justified>.btn-group .btn{width:100%}.btn-group-justified>.btn-group .dropdown-menu{left:auto}[data-toggle=buttons]>.btn input[type=checkbox],[data-toggle=buttons]>.btn input[type=radio],[data-toggle=buttons]>.btn-group>.btn input[type=checkbox],[data-toggle=buttons]>.btn-group>.btn input[type=radio]{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-right:0;padding-left:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.3333333;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn,textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn,textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn{height:auto}.input-group .form-control,.input-group-addon,.input-group-btn{display:table-cell}.input-group .form-control:not(:first-child):not(:last-child),.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=checkbox],.input-group-addon input[type=radio]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn-group:not(:last-child)>.btn,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:first-child>.btn-group:not(:first-child)>.btn,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:active,.input-group-btn>.btn:focus,.input-group-btn>.btn:hover{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{margin-left:-1px}.nav{padding-left:0;margin-bottom:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:focus,.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#777}.nav>li.disabled>a:focus,.nav>li.disabled>a:hover{color:#777;text-decoration:none;cursor:not-allowed;background-color:transparent}.nav .open>a,.nav .open>a:focus,.nav .open>a:hover{background-color:#eee;border-color:#337ab7}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:focus,.nav-tabs>li.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:focus,.nav-tabs.nav-justified>.active>a:hover{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:focus,.nav-pills>li.active>a:hover{color:#fff;background-color:#337ab7}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{margin-bottom:5px;text-align:center}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:focus,.nav-tabs-justified>.active>a:hover{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none;visibility:hidden}.tab-content>.active{display:block;visibility:visible}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{padding-right:15px;padding-left:15px;overflow-x:visible;-webkit-overflow-scrolling:touch;border-top:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1)}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;-webkit-box-shadow:none;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important;visibility:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse{padding-right:0;padding-left:0}}.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:340px}@media (max-device-width:480px) and (orientation:landscape){.navbar-fixed-bottom .navbar-collapse,.navbar-fixed-top .navbar-collapse{max-height:200px}}.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container-fluid>.navbar-collapse,.container-fluid>.navbar-header,.container>.navbar-collapse,.container>.navbar-header{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-bottom,.navbar-fixed-top{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-bottom,.navbar-fixed-top{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;height:50px;padding:15px 15px;font-size:18px;line-height:20px}.navbar-brand:focus,.navbar-brand:hover{text-decoration:none}.navbar-brand>img{display:block}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;padding:9px 10px;margin-top:8px;margin-right:15px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;-webkit-box-shadow:none;box-shadow:none}.navbar-nav .open .dropdown-menu .dropdown-header,.navbar-nav .open .dropdown-menu>li>a{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:focus,.navbar-nav .open .dropdown-menu>li>a:hover{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}}.navbar-form{padding:10px 15px;margin-top:8px;margin-right:-15px;margin-bottom:8px;margin-left:-15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1)}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .form-control-static{display:inline-block}.navbar-form .input-group{display:inline-table;vertical-align:middle}.navbar-form .input-group .form-control,.navbar-form .input-group .input-group-addon,.navbar-form .input-group .input-group-btn{width:auto}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .checkbox,.navbar-form .radio{display:inline-block;margin-top:0;margin-bottom:0;vertical-align:middle}.navbar-form .checkbox label,.navbar-form .radio label{padding-left:0}.navbar-form .checkbox input[type=checkbox],.navbar-form .radio input[type=radio]{position:relative;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}.navbar-form .form-group:last-child{margin-bottom:0}}@media (min-width:768px){.navbar-form{width:auto;padding-top:0;padding-bottom:0;margin-right:0;margin-left:0;border:0;-webkit-box-shadow:none;box-shadow:none}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-left-radius:0;border-top-right-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{margin-bottom:0;border-top-left-radius:4px;border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-right:15px;margin-left:15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important;margin-right:-15px}.navbar-right~.navbar-right{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:focus,.navbar-default .navbar-brand:hover{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:focus,.navbar-default .navbar-nav>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:focus,.navbar-default .navbar-nav>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:focus,.navbar-default .navbar-nav>.disabled>a:hover{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:focus,.navbar-default .navbar-toggle:hover{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:focus,.navbar-default .navbar-nav>.open>a:hover{color:#555;background-color:#e7e7e7}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-default .btn-link{color:#777}.navbar-default .btn-link:focus,.navbar-default .btn-link:hover{color:#333}.navbar-default .btn-link[disabled]:focus,.navbar-default .btn-link[disabled]:hover,fieldset[disabled] .navbar-default .btn-link:focus,fieldset[disabled] .navbar-default .btn-link:hover{color:#ccc}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#9d9d9d}.navbar-inverse .navbar-brand:focus,.navbar-inverse .navbar-brand:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav>li>a:focus,.navbar-inverse .navbar-nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:focus,.navbar-inverse .navbar-nav>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:focus,.navbar-inverse .navbar-nav>.disabled>a:hover{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:focus,.navbar-inverse .navbar-toggle:hover{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:focus,.navbar-inverse .navbar-nav>.open>a:hover{color:#fff;background-color:#080808}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#9d9d9d}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#9d9d9d}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .btn-link{color:#9d9d9d}.navbar-inverse .btn-link:focus,.navbar-inverse .btn-link:hover{color:#fff}.navbar-inverse .btn-link[disabled]:focus,.navbar-inverse .btn-link[disabled]:hover,fieldset[disabled] .navbar-inverse .btn-link:focus,fieldset[disabled] .navbar-inverse .btn-link:hover{color:#444}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{padding:0 5px;color:#ccc;content:"/\00a0"}.breadcrumb>.active{color:#777}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;margin-left:-1px;line-height:1.42857143;color:#337ab7;text-decoration:none;background-color:#fff;border:1px solid #ddd}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-top-left-radius:4px;border-bottom-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-top-right-radius:4px;border-bottom-right-radius:4px}.pagination>li>a:focus,.pagination>li>a:hover,.pagination>li>span:focus,.pagination>li>span:hover{color:#23527c;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>a:focus,.pagination>.active>a:hover,.pagination>.active>span,.pagination>.active>span:focus,.pagination>.active>span:hover{z-index:2;color:#fff;cursor:default;background-color:#337ab7;border-color:#337ab7}.pagination>.disabled>a,.pagination>.disabled>a:focus,.pagination>.disabled>a:hover,.pagination>.disabled>span,.pagination>.disabled>span:focus,.pagination>.disabled>span:hover{color:#777;cursor:not-allowed;background-color:#fff;border-color:#ddd}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-top-left-radius:6px;border-bottom-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-top-right-radius:6px;border-bottom-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-top-right-radius:3px;border-bottom-right-radius:3px}.pager{padding-left:0;margin:20px 0;text-align:center;list-style:none}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:focus,.pager li>a:hover{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:focus,.pager .disabled>a:hover,.pager .disabled>span{color:#777;cursor:not-allowed;background-color:#fff}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}a.label:focus,a.label:hover{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#777}.label-default[href]:focus,.label-default[href]:hover{background-color:#5e5e5e}.label-primary{background-color:#337ab7}.label-primary[href]:focus,.label-primary[href]:hover{background-color:#286090}.label-success{background-color:#5cb85c}.label-success[href]:focus,.label-success[href]:hover{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:focus,.label-info[href]:hover{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:focus,.label-warning[href]:hover{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:focus,.label-danger[href]:hover{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;background-color:#777;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge{top:0;padding:1px 5px}a.badge:focus,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#337ab7;background-color:#fff}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px 15px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron .h1,.jumbotron h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.jumbotron>hr{border-top-color:#d5d5d5}.container .jumbotron,.container-fluid .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding:48px 0}.container .jumbotron,.container-fluid .jumbotron{padding-right:60px;padding-left:60px}.jumbotron .h1,.jumbotron h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:border .2s ease-in-out;-o-transition:border .2s ease-in-out;transition:border .2s ease-in-out}.thumbnail a>img,.thumbnail>img{margin-right:auto;margin-left:auto}a.thumbnail.active,a.thumbnail:focus,a.thumbnail:hover{border-color:#337ab7}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable,.alert-dismissible{padding-right:35px}.alert-dismissable .close,.alert-dismissible .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#337ab7;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress-bar-striped,.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;background-size:40px 40px}.progress-bar.active,.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media{margin-top:15px}.media:first-child{margin-top:0}.media,.media-body{overflow:hidden;zoom:1}.media-body{width:10000px}.media-object{display:block}.media-right,.media>.pull-right{padding-left:10px}.media-left,.media>.pull-left{padding-right:10px}.media-body,.media-left,.media-right{display:table-cell;vertical-align:top}.media-middle{vertical-align:middle}.media-bottom{vertical-align:bottom}.media-heading{margin-top:0;margin-bottom:5px}.media-list{padding-left:0;list-style:none}.list-group{padding-left:0;margin-bottom:20px}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-left-radius:4px;border-top-right-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:focus,a.list-group-item:hover{color:#555;text-decoration:none;background-color:#f5f5f5}.list-group-item.disabled,.list-group-item.disabled:focus,.list-group-item.disabled:hover{color:#777;cursor:not-allowed;background-color:#eee}.list-group-item.disabled .list-group-item-heading,.list-group-item.disabled:focus .list-group-item-heading,.list-group-item.disabled:hover .list-group-item-heading{color:inherit}.list-group-item.disabled .list-group-item-text,.list-group-item.disabled:focus .list-group-item-text,.list-group-item.disabled:hover .list-group-item-text{color:#777}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{z-index:2;color:#fff;background-color:#337ab7;border-color:#337ab7}.list-group-item.active .list-group-item-heading,.list-group-item.active .list-group-item-heading>.small,.list-group-item.active .list-group-item-heading>small,.list-group-item.active:focus .list-group-item-heading,.list-group-item.active:focus .list-group-item-heading>.small,.list-group-item.active:focus .list-group-item-heading>small,.list-group-item.active:hover .list-group-item-heading,.list-group-item.active:hover .list-group-item-heading>.small,.list-group-item.active:hover .list-group-item-heading>small{color:inherit}.list-group-item.active .list-group-item-text,.list-group-item.active:focus .list-group-item-text,.list-group-item.active:hover .list-group-item-text{color:#c7ddef}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:focus,a.list-group-item-success:hover{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:focus,a.list-group-item-success.active:hover{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:focus,a.list-group-item-info:hover{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:focus,a.list-group-item-info.active:hover{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:focus,a.list-group-item-warning:hover{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:focus,a.list-group-item-warning.active:hover{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:focus,a.list-group-item-danger:hover{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:focus,a.list-group-item-danger.active:hover{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-left-radius:3px;border-top-right-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>.small,.panel-title>.small>a,.panel-title>a,.panel-title>small,.panel-title>small>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group,.panel>.panel-collapse>.list-group{margin-bottom:0}.panel>.list-group .list-group-item,.panel>.panel-collapse>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child,.panel>.panel-collapse>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.panel>.list-group:last-child .list-group-item:last-child,.panel>.panel-collapse>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.list-group+.panel-footer{border-top-width:0}.panel>.panel-collapse>.table,.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.panel-collapse>.table caption,.panel>.table caption,.panel>.table-responsive>.table caption{padding-right:15px;padding-left:15px}.panel>.table-responsive:first-child>.table:first-child,.panel>.table:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child,.panel>.table:first-child>thead:first-child>tr:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table-responsive:last-child>.table:last-child,.panel>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive,.panel>.table+.panel-body,.panel>.table-responsive+.panel-body{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child td,.panel>.table>tbody:first-child>tr:first-child th{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child{border-left:0}.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child{border-right:0}.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{margin-bottom:0;border:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse>.list-group,.panel-group .panel-heading+.panel-collapse>.panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ddd}.panel-default>.panel-heading .badge{color:#f5f5f5;background-color:#333}.panel-default>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#337ab7}.panel-primary>.panel-heading{color:#fff;background-color:#337ab7;border-color:#337ab7}.panel-primary>.panel-heading+.panel-collapse>.panel-body{border-top-color:#337ab7}.panel-primary>.panel-heading .badge{color:#337ab7;background-color:#fff}.panel-primary>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#337ab7}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse>.panel-body{border-top-color:#d6e9c6}.panel-success>.panel-heading .badge{color:#dff0d8;background-color:#3c763d}.panel-success>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse>.panel-body{border-top-color:#bce8f1}.panel-info>.panel-heading .badge{color:#d9edf7;background-color:#31708f}.panel-info>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse>.panel-body{border-top-color:#faebcc}.panel-warning>.panel-heading .badge{color:#fcf8e3;background-color:#8a6d3b}.panel-warning>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse>.panel-body{border-top-color:#ebccd1}.panel-danger>.panel-heading .badge{color:#f2dede;background-color:#a94442}.panel-danger>.panel-footer+.panel-collapse>.panel-body{border-bottom-color:#ebccd1}.embed-responsive{position:relative;display:block;height:0;padding:0;overflow:hidden}.embed-responsive .embed-responsive-item,.embed-responsive embed,.embed-responsive iframe,.embed-responsive object,.embed-responsive video{position:absolute;top:0;bottom:0;left:0;width:100%;height:100%;border:0}.embed-responsive.embed-responsive-16by9{padding-bottom:56.25%}.embed-responsive.embed-responsive-4by3{padding-bottom:75%}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;filter:alpha(opacity=20);opacity:.2}.close:focus,.close:hover{color:#000;text-decoration:none;cursor:pointer;filter:alpha(opacity=50);opacity:.5}button.close{-webkit-appearance:none;padding:0;cursor:pointer;background:0 0;border:0}.modal-open{overflow:hidden}.modal{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;display:none;overflow:hidden;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transition:-webkit-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out;-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);-o-transform:translate(0,-25%);transform:translate(0,-25%)}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);-o-transform:translate(0,0);transform:translate(0,0)}.modal-open .modal{overflow-x:hidden;overflow-y:auto}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;outline:0;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5)}.modal-backdrop{position:absolute;top:0;right:0;left:0;background-color:#000}.modal-backdrop.fade{filter:alpha(opacity=0);opacity:0}.modal-backdrop.in{filter:alpha(opacity=50);opacity:.5}.modal-header{min-height:16.43px;padding:15px;border-bottom:1px solid #e5e5e5}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:15px}.modal-footer{padding:15px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.modal-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1070;display:block;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:12px;font-weight:400;line-height:1.4;visibility:visible;filter:alpha(opacity=0);opacity:0}.tooltip.in{filter:alpha(opacity=90);opacity:.9}.tooltip.top{padding:5px 0;margin-top:-3px}.tooltip.right{padding:0 5px;margin-left:3px}.tooltip.bottom{padding:5px 0;margin-top:3px}.tooltip.left{padding:0 5px;margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{right:5px;bottom:0;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;left:5px;margin-bottom:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;right:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;left:5px;margin-top:-5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1060;display:none;max-width:276px;padding:1px;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;font-weight:400;line-height:1.42857143;text-align:left;white-space:normal;background-color:#fff;-webkit-background-clip:padding-box;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2)}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{content:"";border-width:10px}.popover.top>.arrow{bottom:-11px;left:50%;margin-left:-11px;border-top-color:#999;border-top-color:rgba(0,0,0,.25);border-bottom-width:0}.popover.top>.arrow:after{bottom:1px;margin-left:-10px;content:" ";border-top-color:#fff;border-bottom-width:0}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-right-color:#999;border-right-color:rgba(0,0,0,.25);border-left-width:0}.popover.right>.arrow:after{bottom:-10px;left:1px;content:" ";border-right-color:#fff;border-left-width:0}.popover.bottom>.arrow{top:-11px;left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25)}.popover.bottom>.arrow:after{top:1px;margin-left:-10px;content:" ";border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{right:1px;bottom:-10px;content:" ";border-right-width:0;border-left-color:#fff}.carousel{position:relative}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner>.item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>a>img,.carousel-inner>.item>img{line-height:1}@media all and (transform-3d),(-webkit-transform-3d){.carousel-inner>.item{-webkit-transition:-webkit-transform .6s ease-in-out;-o-transition:-o-transform .6s ease-in-out;transition:transform .6s ease-in-out;-webkit-backface-visibility:hidden;backface-visibility:hidden;-webkit-perspective:1000;perspective:1000}.carousel-inner>.item.active.right,.carousel-inner>.item.next{left:0;-webkit-transform:translate3d(100%,0,0);transform:translate3d(100%,0,0)}.carousel-inner>.item.active.left,.carousel-inner>.item.prev{left:0;-webkit-transform:translate3d(-100%,0,0);transform:translate3d(-100%,0,0)}.carousel-inner>.item.active,.carousel-inner>.item.next.left,.carousel-inner>.item.prev.right{left:0;-webkit-transform:translate3d(0,0,0);transform:translate3d(0,0,0)}}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;bottom:0;left:0;width:15%;font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6);filter:alpha(opacity=50);opacity:.5}.carousel-control.left{background-image:-webkit-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.5)),to(rgba(0,0,0,.0001)));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1);background-repeat:repeat-x}.carousel-control.right{right:0;left:auto;background-image:-webkit-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-o-linear-gradient(left,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-image:-webkit-gradient(linear,left top,right top,from(rgba(0,0,0,.0001)),to(rgba(0,0,0,.5)));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1);background-repeat:repeat-x}.carousel-control:focus,.carousel-control:hover{color:#fff;text-decoration:none;filter:alpha(opacity=90);outline:0;opacity:.9}.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{position:absolute;top:50%;z-index:5;display:inline-block}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{left:50%;margin-left:-10px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{right:50%;margin-right:-10px}.carousel-control .icon-next,.carousel-control .icon-prev{width:20px;height:20px;margin-top:-10px;font-family:serif;line-height:1}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;padding-left:0;margin-left:-30%;text-align:center;list-style:none}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0);border:1px solid #fff;border-radius:10px}.carousel-indicators .active{width:12px;height:12px;margin:0;background-color:#fff}.carousel-caption{position:absolute;right:15%;bottom:20px;left:15%;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next,.carousel-control .icon-prev{width:30px;height:30px;margin-top:-15px;font-size:30px}.carousel-control .glyphicon-chevron-left,.carousel-control .icon-prev{margin-left:-15px}.carousel-control .glyphicon-chevron-right,.carousel-control .icon-next{margin-right:-15px}.carousel-caption{right:20%;left:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.btn-group-vertical>.btn-group:after,.btn-group-vertical>.btn-group:before,.btn-toolbar:after,.btn-toolbar:before,.clearfix:after,.clearfix:before,.container-fluid:after,.container-fluid:before,.container:after,.container:before,.dl-horizontal dd:after,.dl-horizontal dd:before,.form-horizontal .form-group:after,.form-horizontal .form-group:before,.modal-footer:after,.modal-footer:before,.nav:after,.nav:before,.navbar-collapse:after,.navbar-collapse:before,.navbar-header:after,.navbar-header:before,.navbar:after,.navbar:before,.pager:after,.pager:before,.panel-body:after,.panel-body:before,.row:after,.row:before{display:table;content:" "}.btn-group-vertical>.btn-group:after,.btn-toolbar:after,.clearfix:after,.container-fluid:after,.container:after,.dl-horizontal dd:after,.form-horizontal .form-group:after,.modal-footer:after,.nav:after,.navbar-collapse:after,.navbar-header:after,.navbar:after,.pager:after,.panel-body:after,.row:after{clear:both}.center-block{display:block;margin-right:auto;margin-left:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important;visibility:hidden!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-lg,.visible-md,.visible-sm,.visible-xs{display:none!important}.visible-lg-block,.visible-lg-inline,.visible-lg-inline-block,.visible-md-block,.visible-md-inline,.visible-md-inline-block,.visible-sm-block,.visible-sm-inline,.visible-sm-inline-block,.visible-xs-block,.visible-xs-inline,.visible-xs-inline-block{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table}tr.visible-xs{display:table-row!important}td.visible-xs,th.visible-xs{display:table-cell!important}}@media (max-width:767px){.visible-xs-block{display:block!important}}@media (max-width:767px){.visible-xs-inline{display:inline!important}}@media (max-width:767px){.visible-xs-inline-block{display:inline-block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table}tr.visible-sm{display:table-row!important}td.visible-sm,th.visible-sm{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-block{display:block!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline{display:inline!important}}@media (min-width:768px) and (max-width:991px){.visible-sm-inline-block{display:inline-block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table}tr.visible-md{display:table-row!important}td.visible-md,th.visible-md{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-block{display:block!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline{display:inline!important}}@media (min-width:992px) and (max-width:1199px){.visible-md-inline-block{display:inline-block!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table}tr.visible-lg{display:table-row!important}td.visible-lg,th.visible-lg{display:table-cell!important}}@media (min-width:1200px){.visible-lg-block{display:block!important}}@media (min-width:1200px){.visible-lg-inline{display:inline!important}}@media (min-width:1200px){.visible-lg-inline-block{display:inline-block!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table}tr.visible-print{display:table-row!important}td.visible-print,th.visible-print{display:table-cell!important}}.visible-print-block{display:none!important}@media print{.visible-print-block{display:block!important}}.visible-print-inline{display:none!important}@media print{.visible-print-inline{display:inline!important}}.visible-print-inline-block{display:none!important}@media print{.visible-print-inline-block{display:inline-block!important}}@media print{.hidden-print{display:none!important}}
\ No newline at end of file diff --git a/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.eot b/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.eot Binary files differnew file mode 100644 index 000000000..b93a4953f --- /dev/null +++ b/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.eot diff --git a/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.svg b/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.svg new file mode 100644 index 000000000..94fb5490a --- /dev/null +++ b/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.svg @@ -0,0 +1,288 @@ +<?xml version="1.0" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" > +<svg xmlns="http://www.w3.org/2000/svg"> +<metadata></metadata> +<defs> +<font id="glyphicons_halflingsregular" horiz-adv-x="1200" > +<font-face units-per-em="1200" ascent="960" descent="-240" /> +<missing-glyph horiz-adv-x="500" /> +<glyph horiz-adv-x="0" /> +<glyph horiz-adv-x="400" /> +<glyph unicode=" " /> +<glyph unicode="*" d="M600 1100q15 0 34 -1.5t30 -3.5l11 -1q10 -2 17.5 -10.5t7.5 -18.5v-224l158 158q7 7 18 8t19 -6l106 -106q7 -8 6 -19t-8 -18l-158 -158h224q10 0 18.5 -7.5t10.5 -17.5q6 -41 6 -75q0 -15 -1.5 -34t-3.5 -30l-1 -11q-2 -10 -10.5 -17.5t-18.5 -7.5h-224l158 -158 q7 -7 8 -18t-6 -19l-106 -106q-8 -7 -19 -6t-18 8l-158 158v-224q0 -10 -7.5 -18.5t-17.5 -10.5q-41 -6 -75 -6q-15 0 -34 1.5t-30 3.5l-11 1q-10 2 -17.5 10.5t-7.5 18.5v224l-158 -158q-7 -7 -18 -8t-19 6l-106 106q-7 8 -6 19t8 18l158 158h-224q-10 0 -18.5 7.5 t-10.5 17.5q-6 41 -6 75q0 15 1.5 34t3.5 30l1 11q2 10 10.5 17.5t18.5 7.5h224l-158 158q-7 7 -8 18t6 19l106 106q8 7 19 6t18 -8l158 -158v224q0 10 7.5 18.5t17.5 10.5q41 6 75 6z" /> +<glyph unicode="+" d="M450 1100h200q21 0 35.5 -14.5t14.5 -35.5v-350h350q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-350v-350q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v350h-350q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5 h350v350q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode=" " /> +<glyph unicode="¥" d="M825 1100h250q10 0 12.5 -5t-5.5 -13l-364 -364q-6 -6 -11 -18h268q10 0 13 -6t-3 -14l-120 -160q-6 -8 -18 -14t-22 -6h-125v-100h275q10 0 13 -6t-3 -14l-120 -160q-6 -8 -18 -14t-22 -6h-125v-174q0 -11 -7.5 -18.5t-18.5 -7.5h-148q-11 0 -18.5 7.5t-7.5 18.5v174 h-275q-10 0 -13 6t3 14l120 160q6 8 18 14t22 6h125v100h-275q-10 0 -13 6t3 14l120 160q6 8 18 14t22 6h118q-5 12 -11 18l-364 364q-8 8 -5.5 13t12.5 5h250q25 0 43 -18l164 -164q8 -8 18 -8t18 8l164 164q18 18 43 18z" /> +<glyph unicode=" " horiz-adv-x="650" /> +<glyph unicode=" " horiz-adv-x="1300" /> +<glyph unicode=" " horiz-adv-x="650" /> +<glyph unicode=" " horiz-adv-x="1300" /> +<glyph unicode=" " horiz-adv-x="433" /> +<glyph unicode=" " horiz-adv-x="325" /> +<glyph unicode=" " horiz-adv-x="216" /> +<glyph unicode=" " horiz-adv-x="216" /> +<glyph unicode=" " horiz-adv-x="162" /> +<glyph unicode=" " horiz-adv-x="260" /> +<glyph unicode=" " horiz-adv-x="72" /> +<glyph unicode=" " horiz-adv-x="260" /> +<glyph unicode=" " horiz-adv-x="325" /> +<glyph unicode="€" d="M744 1198q242 0 354 -189q60 -104 66 -209h-181q0 45 -17.5 82.5t-43.5 61.5t-58 40.5t-60.5 24t-51.5 7.5q-19 0 -40.5 -5.5t-49.5 -20.5t-53 -38t-49 -62.5t-39 -89.5h379l-100 -100h-300q-6 -50 -6 -100h406l-100 -100h-300q9 -74 33 -132t52.5 -91t61.5 -54.5t59 -29 t47 -7.5q22 0 50.5 7.5t60.5 24.5t58 41t43.5 61t17.5 80h174q-30 -171 -128 -278q-107 -117 -274 -117q-206 0 -324 158q-36 48 -69 133t-45 204h-217l100 100h112q1 47 6 100h-218l100 100h134q20 87 51 153.5t62 103.5q117 141 297 141z" /> +<glyph unicode="₽" d="M428 1200h350q67 0 120 -13t86 -31t57 -49.5t35 -56.5t17 -64.5t6.5 -60.5t0.5 -57v-16.5v-16.5q0 -36 -0.5 -57t-6.5 -61t-17 -65t-35 -57t-57 -50.5t-86 -31.5t-120 -13h-178l-2 -100h288q10 0 13 -6t-3 -14l-120 -160q-6 -8 -18 -14t-22 -6h-138v-175q0 -11 -5.5 -18 t-15.5 -7h-149q-10 0 -17.5 7.5t-7.5 17.5v175h-267q-10 0 -13 6t3 14l120 160q6 8 18 14t22 6h117v100h-267q-10 0 -13 6t3 14l120 160q6 8 18 14t22 6h117v475q0 10 7.5 17.5t17.5 7.5zM600 1000v-300h203q64 0 86.5 33t22.5 119q0 84 -22.5 116t-86.5 32h-203z" /> +<glyph unicode="−" d="M250 700h800q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="⌛" d="M1000 1200v-150q0 -21 -14.5 -35.5t-35.5 -14.5h-50v-100q0 -91 -49.5 -165.5t-130.5 -109.5q81 -35 130.5 -109.5t49.5 -165.5v-150h50q21 0 35.5 -14.5t14.5 -35.5v-150h-800v150q0 21 14.5 35.5t35.5 14.5h50v150q0 91 49.5 165.5t130.5 109.5q-81 35 -130.5 109.5 t-49.5 165.5v100h-50q-21 0 -35.5 14.5t-14.5 35.5v150h800zM400 1000v-100q0 -60 32.5 -109.5t87.5 -73.5q28 -12 44 -37t16 -55t-16 -55t-44 -37q-55 -24 -87.5 -73.5t-32.5 -109.5v-150h400v150q0 60 -32.5 109.5t-87.5 73.5q-28 12 -44 37t-16 55t16 55t44 37 q55 24 87.5 73.5t32.5 109.5v100h-400z" /> +<glyph unicode="◼" horiz-adv-x="500" d="M0 0z" /> +<glyph unicode="☁" d="M503 1089q110 0 200.5 -59.5t134.5 -156.5q44 14 90 14q120 0 205 -86.5t85 -206.5q0 -121 -85 -207.5t-205 -86.5h-750q-79 0 -135.5 57t-56.5 137q0 69 42.5 122.5t108.5 67.5q-2 12 -2 37q0 153 108 260.5t260 107.5z" /> +<glyph unicode="⛺" d="M774 1193.5q16 -9.5 20.5 -27t-5.5 -33.5l-136 -187l467 -746h30q20 0 35 -18.5t15 -39.5v-42h-1200v42q0 21 15 39.5t35 18.5h30l468 746l-135 183q-10 16 -5.5 34t20.5 28t34 5.5t28 -20.5l111 -148l112 150q9 16 27 20.5t34 -5zM600 200h377l-182 112l-195 534v-646z " /> +<glyph unicode="✉" d="M25 1100h1150q10 0 12.5 -5t-5.5 -13l-564 -567q-8 -8 -18 -8t-18 8l-564 567q-8 8 -5.5 13t12.5 5zM18 882l264 -264q8 -8 8 -18t-8 -18l-264 -264q-8 -8 -13 -5.5t-5 12.5v550q0 10 5 12.5t13 -5.5zM918 618l264 264q8 8 13 5.5t5 -12.5v-550q0 -10 -5 -12.5t-13 5.5 l-264 264q-8 8 -8 18t8 18zM818 482l364 -364q8 -8 5.5 -13t-12.5 -5h-1150q-10 0 -12.5 5t5.5 13l364 364q8 8 18 8t18 -8l164 -164q8 -8 18 -8t18 8l164 164q8 8 18 8t18 -8z" /> +<glyph unicode="✏" d="M1011 1210q19 0 33 -13l153 -153q13 -14 13 -33t-13 -33l-99 -92l-214 214l95 96q13 14 32 14zM1013 800l-615 -614l-214 214l614 614zM317 96l-333 -112l110 335z" /> +<glyph unicode="" d="M700 650v-550h250q21 0 35.5 -14.5t14.5 -35.5v-50h-800v50q0 21 14.5 35.5t35.5 14.5h250v550l-500 550h1200z" /> +<glyph unicode="" d="M368 1017l645 163q39 15 63 0t24 -49v-831q0 -55 -41.5 -95.5t-111.5 -63.5q-79 -25 -147 -4.5t-86 75t25.5 111.5t122.5 82q72 24 138 8v521l-600 -155v-606q0 -42 -44 -90t-109 -69q-79 -26 -147 -5.5t-86 75.5t25.5 111.5t122.5 82.5q72 24 138 7v639q0 38 14.5 59 t53.5 34z" /> +<glyph unicode="" d="M500 1191q100 0 191 -39t156.5 -104.5t104.5 -156.5t39 -191l-1 -2l1 -5q0 -141 -78 -262l275 -274q23 -26 22.5 -44.5t-22.5 -42.5l-59 -58q-26 -20 -46.5 -20t-39.5 20l-275 274q-119 -77 -261 -77l-5 1l-2 -1q-100 0 -191 39t-156.5 104.5t-104.5 156.5t-39 191 t39 191t104.5 156.5t156.5 104.5t191 39zM500 1022q-88 0 -162 -43t-117 -117t-43 -162t43 -162t117 -117t162 -43t162 43t117 117t43 162t-43 162t-117 117t-162 43z" /> +<glyph unicode="" d="M649 949q48 68 109.5 104t121.5 38.5t118.5 -20t102.5 -64t71 -100.5t27 -123q0 -57 -33.5 -117.5t-94 -124.5t-126.5 -127.5t-150 -152.5t-146 -174q-62 85 -145.5 174t-150 152.5t-126.5 127.5t-93.5 124.5t-33.5 117.5q0 64 28 123t73 100.5t104 64t119 20 t120.5 -38.5t104.5 -104z" /> +<glyph unicode="" d="M407 800l131 353q7 19 17.5 19t17.5 -19l129 -353h421q21 0 24 -8.5t-14 -20.5l-342 -249l130 -401q7 -20 -0.5 -25.5t-24.5 6.5l-343 246l-342 -247q-17 -12 -24.5 -6.5t-0.5 25.5l130 400l-347 251q-17 12 -14 20.5t23 8.5h429z" /> +<glyph unicode="" d="M407 800l131 353q7 19 17.5 19t17.5 -19l129 -353h421q21 0 24 -8.5t-14 -20.5l-342 -249l130 -401q7 -20 -0.5 -25.5t-24.5 6.5l-343 246l-342 -247q-17 -12 -24.5 -6.5t-0.5 25.5l130 400l-347 251q-17 12 -14 20.5t23 8.5h429zM477 700h-240l197 -142l-74 -226 l193 139l195 -140l-74 229l192 140h-234l-78 211z" /> +<glyph unicode="" d="M600 1200q124 0 212 -88t88 -212v-250q0 -46 -31 -98t-69 -52v-75q0 -10 6 -21.5t15 -17.5l358 -230q9 -5 15 -16.5t6 -21.5v-93q0 -10 -7.5 -17.5t-17.5 -7.5h-1150q-10 0 -17.5 7.5t-7.5 17.5v93q0 10 6 21.5t15 16.5l358 230q9 6 15 17.5t6 21.5v75q-38 0 -69 52 t-31 98v250q0 124 88 212t212 88z" /> +<glyph unicode="" d="M25 1100h1150q10 0 17.5 -7.5t7.5 -17.5v-1050q0 -10 -7.5 -17.5t-17.5 -7.5h-1150q-10 0 -17.5 7.5t-7.5 17.5v1050q0 10 7.5 17.5t17.5 7.5zM100 1000v-100h100v100h-100zM875 1000h-550q-10 0 -17.5 -7.5t-7.5 -17.5v-350q0 -10 7.5 -17.5t17.5 -7.5h550 q10 0 17.5 7.5t7.5 17.5v350q0 10 -7.5 17.5t-17.5 7.5zM1000 1000v-100h100v100h-100zM100 800v-100h100v100h-100zM1000 800v-100h100v100h-100zM100 600v-100h100v100h-100zM1000 600v-100h100v100h-100zM875 500h-550q-10 0 -17.5 -7.5t-7.5 -17.5v-350q0 -10 7.5 -17.5 t17.5 -7.5h550q10 0 17.5 7.5t7.5 17.5v350q0 10 -7.5 17.5t-17.5 7.5zM100 400v-100h100v100h-100zM1000 400v-100h100v100h-100zM100 200v-100h100v100h-100zM1000 200v-100h100v100h-100z" /> +<glyph unicode="" d="M50 1100h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM650 1100h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400 q0 21 14.5 35.5t35.5 14.5zM50 500h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM650 500h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400 q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M50 1100h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 1100h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200 q0 21 14.5 35.5t35.5 14.5zM850 1100h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM50 700h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200 q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 700h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM850 700h200q21 0 35.5 -14.5t14.5 -35.5v-200 q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM50 300h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 300h200 q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM850 300h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5 t35.5 14.5z" /> +<glyph unicode="" d="M50 1100h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 1100h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5v200 q0 21 14.5 35.5t35.5 14.5zM50 700h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 700h700q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-700 q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM50 300h200q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5zM450 300h700q21 0 35.5 -14.5t14.5 -35.5v-200 q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M465 477l571 571q8 8 18 8t17 -8l177 -177q8 -7 8 -17t-8 -18l-783 -784q-7 -8 -17.5 -8t-17.5 8l-384 384q-8 8 -8 18t8 17l177 177q7 8 17 8t18 -8l171 -171q7 -7 18 -7t18 7z" /> +<glyph unicode="" d="M904 1083l178 -179q8 -8 8 -18.5t-8 -17.5l-267 -268l267 -268q8 -7 8 -17.5t-8 -18.5l-178 -178q-8 -8 -18.5 -8t-17.5 8l-268 267l-268 -267q-7 -8 -17.5 -8t-18.5 8l-178 178q-8 8 -8 18.5t8 17.5l267 268l-267 268q-8 7 -8 17.5t8 18.5l178 178q8 8 18.5 8t17.5 -8 l268 -267l268 268q7 7 17.5 7t18.5 -7z" /> +<glyph unicode="" d="M507 1177q98 0 187.5 -38.5t154.5 -103.5t103.5 -154.5t38.5 -187.5q0 -141 -78 -262l300 -299q8 -8 8 -18.5t-8 -18.5l-109 -108q-7 -8 -17.5 -8t-18.5 8l-300 299q-119 -77 -261 -77q-98 0 -188 38.5t-154.5 103t-103 154.5t-38.5 188t38.5 187.5t103 154.5 t154.5 103.5t188 38.5zM506.5 1023q-89.5 0 -165.5 -44t-120 -120.5t-44 -166t44 -165.5t120 -120t165.5 -44t166 44t120.5 120t44 165.5t-44 166t-120.5 120.5t-166 44zM425 900h150q10 0 17.5 -7.5t7.5 -17.5v-75h75q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5 t-17.5 -7.5h-75v-75q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v75h-75q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5h75v75q0 10 7.5 17.5t17.5 7.5z" /> +<glyph unicode="" d="M507 1177q98 0 187.5 -38.5t154.5 -103.5t103.5 -154.5t38.5 -187.5q0 -141 -78 -262l300 -299q8 -8 8 -18.5t-8 -18.5l-109 -108q-7 -8 -17.5 -8t-18.5 8l-300 299q-119 -77 -261 -77q-98 0 -188 38.5t-154.5 103t-103 154.5t-38.5 188t38.5 187.5t103 154.5 t154.5 103.5t188 38.5zM506.5 1023q-89.5 0 -165.5 -44t-120 -120.5t-44 -166t44 -165.5t120 -120t165.5 -44t166 44t120.5 120t44 165.5t-44 166t-120.5 120.5t-166 44zM325 800h350q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-350q-10 0 -17.5 7.5 t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5z" /> +<glyph unicode="" d="M550 1200h100q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM800 975v166q167 -62 272 -209.5t105 -331.5q0 -117 -45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5 t-184.5 123t-123 184.5t-45.5 224q0 184 105 331.5t272 209.5v-166q-103 -55 -165 -155t-62 -220q0 -116 57 -214.5t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5q0 120 -62 220t-165 155z" /> +<glyph unicode="" d="M1025 1200h150q10 0 17.5 -7.5t7.5 -17.5v-1150q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v1150q0 10 7.5 17.5t17.5 7.5zM725 800h150q10 0 17.5 -7.5t7.5 -17.5v-750q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v750 q0 10 7.5 17.5t17.5 7.5zM425 500h150q10 0 17.5 -7.5t7.5 -17.5v-450q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v450q0 10 7.5 17.5t17.5 7.5zM125 300h150q10 0 17.5 -7.5t7.5 -17.5v-250q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5 v250q0 10 7.5 17.5t17.5 7.5z" /> +<glyph unicode="" d="M600 1174q33 0 74 -5l38 -152l5 -1q49 -14 94 -39l5 -2l134 80q61 -48 104 -105l-80 -134l3 -5q25 -44 39 -93l1 -6l152 -38q5 -43 5 -73q0 -34 -5 -74l-152 -38l-1 -6q-15 -49 -39 -93l-3 -5l80 -134q-48 -61 -104 -105l-134 81l-5 -3q-44 -25 -94 -39l-5 -2l-38 -151 q-43 -5 -74 -5q-33 0 -74 5l-38 151l-5 2q-49 14 -94 39l-5 3l-134 -81q-60 48 -104 105l80 134l-3 5q-25 45 -38 93l-2 6l-151 38q-6 42 -6 74q0 33 6 73l151 38l2 6q13 48 38 93l3 5l-80 134q47 61 105 105l133 -80l5 2q45 25 94 39l5 1l38 152q43 5 74 5zM600 815 q-89 0 -152 -63t-63 -151.5t63 -151.5t152 -63t152 63t63 151.5t-63 151.5t-152 63z" /> +<glyph unicode="" d="M500 1300h300q41 0 70.5 -29.5t29.5 -70.5v-100h275q10 0 17.5 -7.5t7.5 -17.5v-75h-1100v75q0 10 7.5 17.5t17.5 7.5h275v100q0 41 29.5 70.5t70.5 29.5zM500 1200v-100h300v100h-300zM1100 900v-800q0 -41 -29.5 -70.5t-70.5 -29.5h-700q-41 0 -70.5 29.5t-29.5 70.5 v800h900zM300 800v-700h100v700h-100zM500 800v-700h100v700h-100zM700 800v-700h100v700h-100zM900 800v-700h100v700h-100z" /> +<glyph unicode="" d="M18 618l620 608q8 7 18.5 7t17.5 -7l608 -608q8 -8 5.5 -13t-12.5 -5h-175v-575q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v375h-300v-375q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v575h-175q-10 0 -12.5 5t5.5 13z" /> +<glyph unicode="" d="M600 1200v-400q0 -41 29.5 -70.5t70.5 -29.5h300v-650q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v1100q0 21 14.5 35.5t35.5 14.5h450zM1000 800h-250q-21 0 -35.5 14.5t-14.5 35.5v250z" /> +<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM525 900h50q10 0 17.5 -7.5t7.5 -17.5v-275h175q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v350q0 10 7.5 17.5t17.5 7.5z" /> +<glyph unicode="" d="M1300 0h-538l-41 400h-242l-41 -400h-538l431 1200h209l-21 -300h162l-20 300h208zM515 800l-27 -300h224l-27 300h-170z" /> +<glyph unicode="" d="M550 1200h200q21 0 35.5 -14.5t14.5 -35.5v-450h191q20 0 25.5 -11.5t-7.5 -27.5l-327 -400q-13 -16 -32 -16t-32 16l-327 400q-13 16 -7.5 27.5t25.5 11.5h191v450q0 21 14.5 35.5t35.5 14.5zM1125 400h50q10 0 17.5 -7.5t7.5 -17.5v-350q0 -10 -7.5 -17.5t-17.5 -7.5 h-1050q-10 0 -17.5 7.5t-7.5 17.5v350q0 10 7.5 17.5t17.5 7.5h50q10 0 17.5 -7.5t7.5 -17.5v-175h900v175q0 10 7.5 17.5t17.5 7.5z" /> +<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM525 900h150q10 0 17.5 -7.5t7.5 -17.5v-275h137q21 0 26 -11.5t-8 -27.5l-223 -275q-13 -16 -32 -16t-32 16l-223 275q-13 16 -8 27.5t26 11.5h137v275q0 10 7.5 17.5t17.5 7.5z " /> +<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM632 914l223 -275q13 -16 8 -27.5t-26 -11.5h-137v-275q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v275h-137q-21 0 -26 11.5t8 27.5l223 275q13 16 32 16 t32 -16z" /> +<glyph unicode="" d="M225 1200h750q10 0 19.5 -7t12.5 -17l186 -652q7 -24 7 -49v-425q0 -12 -4 -27t-9 -17q-12 -6 -37 -6h-1100q-12 0 -27 4t-17 8q-6 13 -6 38l1 425q0 25 7 49l185 652q3 10 12.5 17t19.5 7zM878 1000h-556q-10 0 -19 -7t-11 -18l-87 -450q-2 -11 4 -18t16 -7h150 q10 0 19.5 -7t11.5 -17l38 -152q2 -10 11.5 -17t19.5 -7h250q10 0 19.5 7t11.5 17l38 152q2 10 11.5 17t19.5 7h150q10 0 16 7t4 18l-87 450q-2 11 -11 18t-19 7z" /> +<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM540 820l253 -190q17 -12 17 -30t-17 -30l-253 -190q-16 -12 -28 -6.5t-12 26.5v400q0 21 12 26.5t28 -6.5z" /> +<glyph unicode="" d="M947 1060l135 135q7 7 12.5 5t5.5 -13v-362q0 -10 -7.5 -17.5t-17.5 -7.5h-362q-11 0 -13 5.5t5 12.5l133 133q-109 76 -238 76q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5h150q0 -117 -45.5 -224 t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5q192 0 347 -117z" /> +<glyph unicode="" d="M947 1060l135 135q7 7 12.5 5t5.5 -13v-361q0 -11 -7.5 -18.5t-18.5 -7.5h-361q-11 0 -13 5.5t5 12.5l134 134q-110 75 -239 75q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5h-150q0 117 45.5 224t123 184.5t184.5 123t224 45.5q192 0 347 -117zM1027 600h150 q0 -117 -45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5q-192 0 -348 118l-134 -134q-7 -8 -12.5 -5.5t-5.5 12.5v360q0 11 7.5 18.5t18.5 7.5h360q10 0 12.5 -5.5t-5.5 -12.5l-133 -133q110 -76 240 -76q116 0 214.5 57t155.5 155.5t57 214.5z" /> +<glyph unicode="" d="M125 1200h1050q10 0 17.5 -7.5t7.5 -17.5v-1150q0 -10 -7.5 -17.5t-17.5 -7.5h-1050q-10 0 -17.5 7.5t-7.5 17.5v1150q0 10 7.5 17.5t17.5 7.5zM1075 1000h-850q-10 0 -17.5 -7.5t-7.5 -17.5v-850q0 -10 7.5 -17.5t17.5 -7.5h850q10 0 17.5 7.5t7.5 17.5v850 q0 10 -7.5 17.5t-17.5 7.5zM325 900h50q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5zM525 900h450q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-450q-10 0 -17.5 7.5t-7.5 17.5v50 q0 10 7.5 17.5t17.5 7.5zM325 700h50q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5zM525 700h450q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-450q-10 0 -17.5 7.5t-7.5 17.5v50 q0 10 7.5 17.5t17.5 7.5zM325 500h50q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5zM525 500h450q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-450q-10 0 -17.5 7.5t-7.5 17.5v50 q0 10 7.5 17.5t17.5 7.5zM325 300h50q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5zM525 300h450q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-450q-10 0 -17.5 7.5t-7.5 17.5v50 q0 10 7.5 17.5t17.5 7.5z" /> +<glyph unicode="" d="M900 800v200q0 83 -58.5 141.5t-141.5 58.5h-300q-82 0 -141 -59t-59 -141v-200h-100q-41 0 -70.5 -29.5t-29.5 -70.5v-600q0 -41 29.5 -70.5t70.5 -29.5h900q41 0 70.5 29.5t29.5 70.5v600q0 41 -29.5 70.5t-70.5 29.5h-100zM400 800v150q0 21 15 35.5t35 14.5h200 q20 0 35 -14.5t15 -35.5v-150h-300z" /> +<glyph unicode="" d="M125 1100h50q10 0 17.5 -7.5t7.5 -17.5v-1075h-100v1075q0 10 7.5 17.5t17.5 7.5zM1075 1052q4 0 9 -2q16 -6 16 -23v-421q0 -6 -3 -12q-33 -59 -66.5 -99t-65.5 -58t-56.5 -24.5t-52.5 -6.5q-26 0 -57.5 6.5t-52.5 13.5t-60 21q-41 15 -63 22.5t-57.5 15t-65.5 7.5 q-85 0 -160 -57q-7 -5 -15 -5q-6 0 -11 3q-14 7 -14 22v438q22 55 82 98.5t119 46.5q23 2 43 0.5t43 -7t32.5 -8.5t38 -13t32.5 -11q41 -14 63.5 -21t57 -14t63.5 -7q103 0 183 87q7 8 18 8z" /> +<glyph unicode="" d="M600 1175q116 0 227 -49.5t192.5 -131t131 -192.5t49.5 -227v-300q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v300q0 127 -70.5 231.5t-184.5 161.5t-245 57t-245 -57t-184.5 -161.5t-70.5 -231.5v-300q0 -10 -7.5 -17.5t-17.5 -7.5h-50 q-10 0 -17.5 7.5t-7.5 17.5v300q0 116 49.5 227t131 192.5t192.5 131t227 49.5zM220 500h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14v460q0 8 6 14t14 6zM820 500h160q8 0 14 -6t6 -14v-460q0 -8 -6 -14t-14 -6h-160q-8 0 -14 6t-6 14v460 q0 8 6 14t14 6z" /> +<glyph unicode="" d="M321 814l258 172q9 6 15 2.5t6 -13.5v-750q0 -10 -6 -13.5t-15 2.5l-258 172q-21 14 -46 14h-250q-10 0 -17.5 7.5t-7.5 17.5v350q0 10 7.5 17.5t17.5 7.5h250q25 0 46 14zM900 668l120 120q7 7 17 7t17 -7l34 -34q7 -7 7 -17t-7 -17l-120 -120l120 -120q7 -7 7 -17 t-7 -17l-34 -34q-7 -7 -17 -7t-17 7l-120 119l-120 -119q-7 -7 -17 -7t-17 7l-34 34q-7 7 -7 17t7 17l119 120l-119 120q-7 7 -7 17t7 17l34 34q7 8 17 8t17 -8z" /> +<glyph unicode="" d="M321 814l258 172q9 6 15 2.5t6 -13.5v-750q0 -10 -6 -13.5t-15 2.5l-258 172q-21 14 -46 14h-250q-10 0 -17.5 7.5t-7.5 17.5v350q0 10 7.5 17.5t17.5 7.5h250q25 0 46 14zM766 900h4q10 -1 16 -10q96 -129 96 -290q0 -154 -90 -281q-6 -9 -17 -10l-3 -1q-9 0 -16 6 l-29 23q-7 7 -8.5 16.5t4.5 17.5q72 103 72 229q0 132 -78 238q-6 8 -4.5 18t9.5 17l29 22q7 5 15 5z" /> +<glyph unicode="" d="M967 1004h3q11 -1 17 -10q135 -179 135 -396q0 -105 -34 -206.5t-98 -185.5q-7 -9 -17 -10h-3q-9 0 -16 6l-42 34q-8 6 -9 16t5 18q111 150 111 328q0 90 -29.5 176t-84.5 157q-6 9 -5 19t10 16l42 33q7 5 15 5zM321 814l258 172q9 6 15 2.5t6 -13.5v-750q0 -10 -6 -13.5 t-15 2.5l-258 172q-21 14 -46 14h-250q-10 0 -17.5 7.5t-7.5 17.5v350q0 10 7.5 17.5t17.5 7.5h250q25 0 46 14zM766 900h4q10 -1 16 -10q96 -129 96 -290q0 -154 -90 -281q-6 -9 -17 -10l-3 -1q-9 0 -16 6l-29 23q-7 7 -8.5 16.5t4.5 17.5q72 103 72 229q0 132 -78 238 q-6 8 -4.5 18.5t9.5 16.5l29 22q7 5 15 5z" /> +<glyph unicode="" d="M500 900h100v-100h-100v-100h-400v-100h-100v600h500v-300zM1200 700h-200v-100h200v-200h-300v300h-200v300h-100v200h600v-500zM100 1100v-300h300v300h-300zM800 1100v-300h300v300h-300zM300 900h-100v100h100v-100zM1000 900h-100v100h100v-100zM300 500h200v-500 h-500v500h200v100h100v-100zM800 300h200v-100h-100v-100h-200v100h-100v100h100v200h-200v100h300v-300zM100 400v-300h300v300h-300zM300 200h-100v100h100v-100zM1200 200h-100v100h100v-100zM700 0h-100v100h100v-100zM1200 0h-300v100h300v-100z" /> +<glyph unicode="" d="M100 200h-100v1000h100v-1000zM300 200h-100v1000h100v-1000zM700 200h-200v1000h200v-1000zM900 200h-100v1000h100v-1000zM1200 200h-200v1000h200v-1000zM400 0h-300v100h300v-100zM600 0h-100v91h100v-91zM800 0h-100v91h100v-91zM1100 0h-200v91h200v-91z" /> +<glyph unicode="" d="M500 1200l682 -682q8 -8 8 -18t-8 -18l-464 -464q-8 -8 -18 -8t-18 8l-682 682l1 475q0 10 7.5 17.5t17.5 7.5h474zM319.5 1024.5q-29.5 29.5 -71 29.5t-71 -29.5t-29.5 -71.5t29.5 -71.5t71 -29.5t71 29.5t29.5 71.5t-29.5 71.5z" /> +<glyph unicode="" d="M500 1200l682 -682q8 -8 8 -18t-8 -18l-464 -464q-8 -8 -18 -8t-18 8l-682 682l1 475q0 10 7.5 17.5t17.5 7.5h474zM800 1200l682 -682q8 -8 8 -18t-8 -18l-464 -464q-8 -8 -18 -8t-18 8l-56 56l424 426l-700 700h150zM319.5 1024.5q-29.5 29.5 -71 29.5t-71 -29.5 t-29.5 -71.5t29.5 -71.5t71 -29.5t71 29.5t29.5 71.5t-29.5 71.5z" /> +<glyph unicode="" d="M300 1200h825q75 0 75 -75v-900q0 -25 -18 -43l-64 -64q-8 -8 -13 -5.5t-5 12.5v950q0 10 -7.5 17.5t-17.5 7.5h-700q-25 0 -43 -18l-64 -64q-8 -8 -5.5 -13t12.5 -5h700q10 0 17.5 -7.5t7.5 -17.5v-950q0 -10 -7.5 -17.5t-17.5 -7.5h-850q-10 0 -17.5 7.5t-7.5 17.5v975 q0 25 18 43l139 139q18 18 43 18z" /> +<glyph unicode="" d="M250 1200h800q21 0 35.5 -14.5t14.5 -35.5v-1150l-450 444l-450 -445v1151q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M822 1200h-444q-11 0 -19 -7.5t-9 -17.5l-78 -301q-7 -24 7 -45l57 -108q6 -9 17.5 -15t21.5 -6h450q10 0 21.5 6t17.5 15l62 108q14 21 7 45l-83 301q-1 10 -9 17.5t-19 7.5zM1175 800h-150q-10 0 -21 -6.5t-15 -15.5l-78 -156q-4 -9 -15 -15.5t-21 -6.5h-550 q-10 0 -21 6.5t-15 15.5l-78 156q-4 9 -15 15.5t-21 6.5h-150q-10 0 -17.5 -7.5t-7.5 -17.5v-650q0 -10 7.5 -17.5t17.5 -7.5h150q10 0 17.5 7.5t7.5 17.5v150q0 10 7.5 17.5t17.5 7.5h750q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 7.5 -17.5t17.5 -7.5h150q10 0 17.5 7.5 t7.5 17.5v650q0 10 -7.5 17.5t-17.5 7.5zM850 200h-500q-10 0 -19.5 -7t-11.5 -17l-38 -152q-2 -10 3.5 -17t15.5 -7h600q10 0 15.5 7t3.5 17l-38 152q-2 10 -11.5 17t-19.5 7z" /> +<glyph unicode="" d="M500 1100h200q56 0 102.5 -20.5t72.5 -50t44 -59t25 -50.5l6 -20h150q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5v600q0 41 29.5 70.5t70.5 29.5h150q2 8 6.5 21.5t24 48t45 61t72 48t102.5 21.5zM900 800v-100 h100v100h-100zM600 730q-95 0 -162.5 -67.5t-67.5 -162.5t67.5 -162.5t162.5 -67.5t162.5 67.5t67.5 162.5t-67.5 162.5t-162.5 67.5zM600 603q43 0 73 -30t30 -73t-30 -73t-73 -30t-73 30t-30 73t30 73t73 30z" /> +<glyph unicode="" d="M681 1199l385 -998q20 -50 60 -92q18 -19 36.5 -29.5t27.5 -11.5l10 -2v-66h-417v66q53 0 75 43.5t5 88.5l-82 222h-391q-58 -145 -92 -234q-11 -34 -6.5 -57t25.5 -37t46 -20t55 -6v-66h-365v66q56 24 84 52q12 12 25 30.5t20 31.5l7 13l399 1006h93zM416 521h340 l-162 457z" /> +<glyph unicode="" d="M753 641q5 -1 14.5 -4.5t36 -15.5t50.5 -26.5t53.5 -40t50.5 -54.5t35.5 -70t14.5 -87q0 -67 -27.5 -125.5t-71.5 -97.5t-98.5 -66.5t-108.5 -40.5t-102 -13h-500v89q41 7 70.5 32.5t29.5 65.5v827q0 24 -0.5 34t-3.5 24t-8.5 19.5t-17 13.5t-28 12.5t-42.5 11.5v71 l471 -1q57 0 115.5 -20.5t108 -57t80.5 -94t31 -124.5q0 -51 -15.5 -96.5t-38 -74.5t-45 -50.5t-38.5 -30.5zM400 700h139q78 0 130.5 48.5t52.5 122.5q0 41 -8.5 70.5t-29.5 55.5t-62.5 39.5t-103.5 13.5h-118v-350zM400 200h216q80 0 121 50.5t41 130.5q0 90 -62.5 154.5 t-156.5 64.5h-159v-400z" /> +<glyph unicode="" d="M877 1200l2 -57q-83 -19 -116 -45.5t-40 -66.5l-132 -839q-9 -49 13 -69t96 -26v-97h-500v97q186 16 200 98l173 832q3 17 3 30t-1.5 22.5t-9 17.5t-13.5 12.5t-21.5 10t-26 8.5t-33.5 10q-13 3 -19 5v57h425z" /> +<glyph unicode="" d="M1300 900h-50q0 21 -4 37t-9.5 26.5t-18 17.5t-22 11t-28.5 5.5t-31 2t-37 0.5h-200v-850q0 -22 25 -34.5t50 -13.5l25 -2v-100h-400v100q4 0 11 0.5t24 3t30 7t24 15t11 24.5v850h-200q-25 0 -37 -0.5t-31 -2t-28.5 -5.5t-22 -11t-18 -17.5t-9.5 -26.5t-4 -37h-50v300 h1000v-300zM175 1000h-75v-800h75l-125 -167l-125 167h75v800h-75l125 167z" /> +<glyph unicode="" d="M1100 900h-50q0 21 -4 37t-9.5 26.5t-18 17.5t-22 11t-28.5 5.5t-31 2t-37 0.5h-200v-650q0 -22 25 -34.5t50 -13.5l25 -2v-100h-400v100q4 0 11 0.5t24 3t30 7t24 15t11 24.5v650h-200q-25 0 -37 -0.5t-31 -2t-28.5 -5.5t-22 -11t-18 -17.5t-9.5 -26.5t-4 -37h-50v300 h1000v-300zM1167 50l-167 -125v75h-800v-75l-167 125l167 125v-75h800v75z" /> +<glyph unicode="" d="M50 1100h600q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-600q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 800h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5v100 q0 21 14.5 35.5t35.5 14.5zM50 500h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 200h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M250 1100h700q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 800h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v100 q0 21 14.5 35.5t35.5 14.5zM250 500h700q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-700q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 200h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M500 950v100q0 21 14.5 35.5t35.5 14.5h600q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-600q-21 0 -35.5 14.5t-14.5 35.5zM100 650v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000 q-21 0 -35.5 14.5t-14.5 35.5zM300 350v100q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5zM0 50v100q0 21 14.5 35.5t35.5 14.5h1100q21 0 35.5 -14.5t14.5 -35.5v-100 q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5z" /> +<glyph unicode="" d="M50 1100h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 800h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v100 q0 21 14.5 35.5t35.5 14.5zM50 500h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 200h1100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M50 1100h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM350 1100h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v100 q0 21 14.5 35.5t35.5 14.5zM50 800h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM350 800h800q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-800 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 500h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM350 500h800q21 0 35.5 -14.5t14.5 -35.5v-100 q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 200h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM350 200h800 q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M400 0h-100v1100h100v-1100zM550 1100h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM550 800h500q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-500 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM267 550l-167 -125v75h-200v100h200v75zM550 500h300q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-300q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM550 200h600 q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-600q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M50 1100h100q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM900 0h-100v1100h100v-1100zM50 800h500q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-500 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM1100 600h200v-100h-200v-75l-167 125l167 125v-75zM50 500h300q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-300q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5zM50 200h600 q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-600q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M75 1000h750q31 0 53 -22t22 -53v-650q0 -31 -22 -53t-53 -22h-750q-31 0 -53 22t-22 53v650q0 31 22 53t53 22zM1200 300l-300 300l300 300v-600z" /> +<glyph unicode="" d="M44 1100h1112q18 0 31 -13t13 -31v-1012q0 -18 -13 -31t-31 -13h-1112q-18 0 -31 13t-13 31v1012q0 18 13 31t31 13zM100 1000v-737l247 182l298 -131l-74 156l293 318l236 -288v500h-1000zM342 884q56 0 95 -39t39 -94.5t-39 -95t-95 -39.5t-95 39.5t-39 95t39 94.5 t95 39z" /> +<glyph unicode="" d="M648 1169q117 0 216 -60t156.5 -161t57.5 -218q0 -115 -70 -258q-69 -109 -158 -225.5t-143 -179.5l-54 -62q-9 8 -25.5 24.5t-63.5 67.5t-91 103t-98.5 128t-95.5 148q-60 132 -60 249q0 88 34 169.5t91.5 142t137 96.5t166.5 36zM652.5 974q-91.5 0 -156.5 -65 t-65 -157t65 -156.5t156.5 -64.5t156.5 64.5t65 156.5t-65 157t-156.5 65z" /> +<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 173v854q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57z" /> +<glyph unicode="" d="M554 1295q21 -72 57.5 -143.5t76 -130t83 -118t82.5 -117t70 -116t49.5 -126t18.5 -136.5q0 -71 -25.5 -135t-68.5 -111t-99 -82t-118.5 -54t-125.5 -23q-84 5 -161.5 34t-139.5 78.5t-99 125t-37 164.5q0 69 18 136.5t49.5 126.5t69.5 116.5t81.5 117.5t83.5 119 t76.5 131t58.5 143zM344 710q-23 -33 -43.5 -70.5t-40.5 -102.5t-17 -123q1 -37 14.5 -69.5t30 -52t41 -37t38.5 -24.5t33 -15q21 -7 32 -1t13 22l6 34q2 10 -2.5 22t-13.5 19q-5 4 -14 12t-29.5 40.5t-32.5 73.5q-26 89 6 271q2 11 -6 11q-8 1 -15 -10z" /> +<glyph unicode="" d="M1000 1013l108 115q2 1 5 2t13 2t20.5 -1t25 -9.5t28.5 -21.5q22 -22 27 -43t0 -32l-6 -10l-108 -115zM350 1100h400q50 0 105 -13l-187 -187h-368q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v182l200 200v-332 q0 -165 -93.5 -257.5t-256.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 165 92.5 257.5t257.5 92.5zM1009 803l-362 -362l-161 -50l55 170l355 355z" /> +<glyph unicode="" d="M350 1100h361q-164 -146 -216 -200h-195q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5l200 153v-103q0 -165 -92.5 -257.5t-257.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 165 92.5 257.5t257.5 92.5z M824 1073l339 -301q8 -7 8 -17.5t-8 -17.5l-340 -306q-7 -6 -12.5 -4t-6.5 11v203q-26 1 -54.5 0t-78.5 -7.5t-92 -17.5t-86 -35t-70 -57q10 59 33 108t51.5 81.5t65 58.5t68.5 40.5t67 24.5t56 13.5t40 4.5v210q1 10 6.5 12.5t13.5 -4.5z" /> +<glyph unicode="" d="M350 1100h350q60 0 127 -23l-178 -177h-349q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v69l200 200v-219q0 -165 -92.5 -257.5t-257.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 165 92.5 257.5t257.5 92.5z M643 639l395 395q7 7 17.5 7t17.5 -7l101 -101q7 -7 7 -17.5t-7 -17.5l-531 -532q-7 -7 -17.5 -7t-17.5 7l-248 248q-7 7 -7 17.5t7 17.5l101 101q7 7 17.5 7t17.5 -7l111 -111q8 -7 18 -7t18 7z" /> +<glyph unicode="" d="M318 918l264 264q8 8 18 8t18 -8l260 -264q7 -8 4.5 -13t-12.5 -5h-170v-200h200v173q0 10 5 12t13 -5l264 -260q8 -7 8 -17.5t-8 -17.5l-264 -265q-8 -7 -13 -5t-5 12v173h-200v-200h170q10 0 12.5 -5t-4.5 -13l-260 -264q-8 -8 -18 -8t-18 8l-264 264q-8 8 -5.5 13 t12.5 5h175v200h-200v-173q0 -10 -5 -12t-13 5l-264 265q-8 7 -8 17.5t8 17.5l264 260q8 7 13 5t5 -12v-173h200v200h-175q-10 0 -12.5 5t5.5 13z" /> +<glyph unicode="" d="M250 1100h100q21 0 35.5 -14.5t14.5 -35.5v-438l464 453q15 14 25.5 10t10.5 -25v-1000q0 -21 -10.5 -25t-25.5 10l-464 453v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v1000q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M50 1100h100q21 0 35.5 -14.5t14.5 -35.5v-438l464 453q15 14 25.5 10t10.5 -25v-438l464 453q15 14 25.5 10t10.5 -25v-1000q0 -21 -10.5 -25t-25.5 10l-464 453v-438q0 -21 -10.5 -25t-25.5 10l-464 453v-438q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5 t-14.5 35.5v1000q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M1200 1050v-1000q0 -21 -10.5 -25t-25.5 10l-464 453v-438q0 -21 -10.5 -25t-25.5 10l-492 480q-15 14 -15 35t15 35l492 480q15 14 25.5 10t10.5 -25v-438l464 453q15 14 25.5 10t10.5 -25z" /> +<glyph unicode="" d="M243 1074l814 -498q18 -11 18 -26t-18 -26l-814 -498q-18 -11 -30.5 -4t-12.5 28v1000q0 21 12.5 28t30.5 -4z" /> +<glyph unicode="" d="M250 1000h200q21 0 35.5 -14.5t14.5 -35.5v-800q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v800q0 21 14.5 35.5t35.5 14.5zM650 1000h200q21 0 35.5 -14.5t14.5 -35.5v-800q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v800 q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M1100 950v-800q0 -21 -14.5 -35.5t-35.5 -14.5h-800q-21 0 -35.5 14.5t-14.5 35.5v800q0 21 14.5 35.5t35.5 14.5h800q21 0 35.5 -14.5t14.5 -35.5z" /> +<glyph unicode="" d="M500 612v438q0 21 10.5 25t25.5 -10l492 -480q15 -14 15 -35t-15 -35l-492 -480q-15 -14 -25.5 -10t-10.5 25v438l-464 -453q-15 -14 -25.5 -10t-10.5 25v1000q0 21 10.5 25t25.5 -10z" /> +<glyph unicode="" d="M1048 1102l100 1q20 0 35 -14.5t15 -35.5l5 -1000q0 -21 -14.5 -35.5t-35.5 -14.5l-100 -1q-21 0 -35.5 14.5t-14.5 35.5l-2 437l-463 -454q-14 -15 -24.5 -10.5t-10.5 25.5l-2 437l-462 -455q-15 -14 -25.5 -9.5t-10.5 24.5l-5 1000q0 21 10.5 25.5t25.5 -10.5l466 -450 l-2 438q0 20 10.5 24.5t25.5 -9.5l466 -451l-2 438q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M850 1100h100q21 0 35.5 -14.5t14.5 -35.5v-1000q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v438l-464 -453q-15 -14 -25.5 -10t-10.5 25v1000q0 21 10.5 25t25.5 -10l464 -453v438q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M686 1081l501 -540q15 -15 10.5 -26t-26.5 -11h-1042q-22 0 -26.5 11t10.5 26l501 540q15 15 36 15t36 -15zM150 400h1000q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M885 900l-352 -353l352 -353l-197 -198l-552 552l552 550z" /> +<glyph unicode="" d="M1064 547l-551 -551l-198 198l353 353l-353 353l198 198z" /> +<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM650 900h-100q-21 0 -35.5 -14.5t-14.5 -35.5v-150h-150 q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -21 14.5 -35.5t35.5 -14.5h150v-150q0 -21 14.5 -35.5t35.5 -14.5h100q21 0 35.5 14.5t14.5 35.5v150h150q21 0 35.5 14.5t14.5 35.5v100q0 21 -14.5 35.5t-35.5 14.5h-150v150q0 21 -14.5 35.5t-35.5 14.5z" /> +<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM850 700h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -21 14.5 -35.5 t35.5 -14.5h500q21 0 35.5 14.5t14.5 35.5v100q0 21 -14.5 35.5t-35.5 14.5z" /> +<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM741.5 913q-12.5 0 -21.5 -9l-120 -120l-120 120q-9 9 -21.5 9 t-21.5 -9l-141 -141q-9 -9 -9 -21.5t9 -21.5l120 -120l-120 -120q-9 -9 -9 -21.5t9 -21.5l141 -141q9 -9 21.5 -9t21.5 9l120 120l120 -120q9 -9 21.5 -9t21.5 9l141 141q9 9 9 21.5t-9 21.5l-120 120l120 120q9 9 9 21.5t-9 21.5l-141 141q-9 9 -21.5 9z" /> +<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM546 623l-84 85q-7 7 -17.5 7t-18.5 -7l-139 -139q-7 -8 -7 -18t7 -18 l242 -241q7 -8 17.5 -8t17.5 8l375 375q7 7 7 17.5t-7 18.5l-139 139q-7 7 -17.5 7t-17.5 -7z" /> +<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM588 941q-29 0 -59 -5.5t-63 -20.5t-58 -38.5t-41.5 -63t-16.5 -89.5 q0 -25 20 -25h131q30 -5 35 11q6 20 20.5 28t45.5 8q20 0 31.5 -10.5t11.5 -28.5q0 -23 -7 -34t-26 -18q-1 0 -13.5 -4t-19.5 -7.5t-20 -10.5t-22 -17t-18.5 -24t-15.5 -35t-8 -46q-1 -8 5.5 -16.5t20.5 -8.5h173q7 0 22 8t35 28t37.5 48t29.5 74t12 100q0 47 -17 83 t-42.5 57t-59.5 34.5t-64 18t-59 4.5zM675 400h-150q-10 0 -17.5 -7.5t-7.5 -17.5v-150q0 -10 7.5 -17.5t17.5 -7.5h150q10 0 17.5 7.5t7.5 17.5v150q0 10 -7.5 17.5t-17.5 7.5z" /> +<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM675 1000h-150q-10 0 -17.5 -7.5t-7.5 -17.5v-150q0 -10 7.5 -17.5 t17.5 -7.5h150q10 0 17.5 7.5t7.5 17.5v150q0 10 -7.5 17.5t-17.5 7.5zM675 700h-250q-10 0 -17.5 -7.5t-7.5 -17.5v-50q0 -10 7.5 -17.5t17.5 -7.5h75v-200h-75q-10 0 -17.5 -7.5t-7.5 -17.5v-50q0 -10 7.5 -17.5t17.5 -7.5h350q10 0 17.5 7.5t7.5 17.5v50q0 10 -7.5 17.5 t-17.5 7.5h-75v275q0 10 -7.5 17.5t-17.5 7.5z" /> +<glyph unicode="" d="M525 1200h150q10 0 17.5 -7.5t7.5 -17.5v-194q103 -27 178.5 -102.5t102.5 -178.5h194q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-194q-27 -103 -102.5 -178.5t-178.5 -102.5v-194q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v194 q-103 27 -178.5 102.5t-102.5 178.5h-194q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5h194q27 103 102.5 178.5t178.5 102.5v194q0 10 7.5 17.5t17.5 7.5zM700 893v-168q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v168q-68 -23 -119 -74 t-74 -119h168q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-168q23 -68 74 -119t119 -74v168q0 10 7.5 17.5t17.5 7.5h150q10 0 17.5 -7.5t7.5 -17.5v-168q68 23 119 74t74 119h-168q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5h168 q-23 68 -74 119t-119 74z" /> +<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM759 823l64 -64q7 -7 7 -17.5t-7 -17.5l-124 -124l124 -124q7 -7 7 -17.5t-7 -17.5l-64 -64q-7 -7 -17.5 -7t-17.5 7l-124 124l-124 -124q-7 -7 -17.5 -7t-17.5 7l-64 64 q-7 7 -7 17.5t7 17.5l124 124l-124 124q-7 7 -7 17.5t7 17.5l64 64q7 7 17.5 7t17.5 -7l124 -124l124 124q7 7 17.5 7t17.5 -7z" /> +<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5t57 -214.5 t155.5 -155.5t214.5 -57t214.5 57t155.5 155.5t57 214.5t-57 214.5t-155.5 155.5t-214.5 57zM782 788l106 -106q7 -7 7 -17.5t-7 -17.5l-320 -321q-8 -7 -18 -7t-18 7l-202 203q-8 7 -8 17.5t8 17.5l106 106q7 8 17.5 8t17.5 -8l79 -79l197 197q7 7 17.5 7t17.5 -7z" /> +<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM600 1027q-116 0 -214.5 -57t-155.5 -155.5t-57 -214.5q0 -120 65 -225 l587 587q-105 65 -225 65zM965 819l-584 -584q104 -62 219 -62q116 0 214.5 57t155.5 155.5t57 214.5q0 115 -62 219z" /> +<glyph unicode="" d="M39 582l522 427q16 13 27.5 8t11.5 -26v-291h550q21 0 35.5 -14.5t14.5 -35.5v-200q0 -21 -14.5 -35.5t-35.5 -14.5h-550v-291q0 -21 -11.5 -26t-27.5 8l-522 427q-16 13 -16 32t16 32z" /> +<glyph unicode="" d="M639 1009l522 -427q16 -13 16 -32t-16 -32l-522 -427q-16 -13 -27.5 -8t-11.5 26v291h-550q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h550v291q0 21 11.5 26t27.5 -8z" /> +<glyph unicode="" d="M682 1161l427 -522q13 -16 8 -27.5t-26 -11.5h-291v-550q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v550h-291q-21 0 -26 11.5t8 27.5l427 522q13 16 32 16t32 -16z" /> +<glyph unicode="" d="M550 1200h200q21 0 35.5 -14.5t14.5 -35.5v-550h291q21 0 26 -11.5t-8 -27.5l-427 -522q-13 -16 -32 -16t-32 16l-427 522q-13 16 -8 27.5t26 11.5h291v550q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M639 1109l522 -427q16 -13 16 -32t-16 -32l-522 -427q-16 -13 -27.5 -8t-11.5 26v291q-94 -2 -182 -20t-170.5 -52t-147 -92.5t-100.5 -135.5q5 105 27 193.5t67.5 167t113 135t167 91.5t225.5 42v262q0 21 11.5 26t27.5 -8z" /> +<glyph unicode="" d="M850 1200h300q21 0 35.5 -14.5t14.5 -35.5v-300q0 -21 -10.5 -25t-24.5 10l-94 94l-249 -249q-8 -7 -18 -7t-18 7l-106 106q-7 8 -7 18t7 18l249 249l-94 94q-14 14 -10 24.5t25 10.5zM350 0h-300q-21 0 -35.5 14.5t-14.5 35.5v300q0 21 10.5 25t24.5 -10l94 -94l249 249 q8 7 18 7t18 -7l106 -106q7 -8 7 -18t-7 -18l-249 -249l94 -94q14 -14 10 -24.5t-25 -10.5z" /> +<glyph unicode="" d="M1014 1120l106 -106q7 -8 7 -18t-7 -18l-249 -249l94 -94q14 -14 10 -24.5t-25 -10.5h-300q-21 0 -35.5 14.5t-14.5 35.5v300q0 21 10.5 25t24.5 -10l94 -94l249 249q8 7 18 7t18 -7zM250 600h300q21 0 35.5 -14.5t14.5 -35.5v-300q0 -21 -10.5 -25t-24.5 10l-94 94 l-249 -249q-8 -7 -18 -7t-18 7l-106 106q-7 8 -7 18t7 18l249 249l-94 94q-14 14 -10 24.5t25 10.5z" /> +<glyph unicode="" d="M600 1177q117 0 224 -45.5t184.5 -123t123 -184.5t45.5 -224t-45.5 -224t-123 -184.5t-184.5 -123t-224 -45.5t-224 45.5t-184.5 123t-123 184.5t-45.5 224t45.5 224t123 184.5t184.5 123t224 45.5zM704 900h-208q-20 0 -32 -14.5t-8 -34.5l58 -302q4 -20 21.5 -34.5 t37.5 -14.5h54q20 0 37.5 14.5t21.5 34.5l58 302q4 20 -8 34.5t-32 14.5zM675 400h-150q-10 0 -17.5 -7.5t-7.5 -17.5v-150q0 -10 7.5 -17.5t17.5 -7.5h150q10 0 17.5 7.5t7.5 17.5v150q0 10 -7.5 17.5t-17.5 7.5z" /> +<glyph unicode="" d="M260 1200q9 0 19 -2t15 -4l5 -2q22 -10 44 -23l196 -118q21 -13 36 -24q29 -21 37 -12q11 13 49 35l196 118q22 13 45 23q17 7 38 7q23 0 47 -16.5t37 -33.5l13 -16q14 -21 18 -45l25 -123l8 -44q1 -9 8.5 -14.5t17.5 -5.5h61q10 0 17.5 -7.5t7.5 -17.5v-50 q0 -10 -7.5 -17.5t-17.5 -7.5h-50q-10 0 -17.5 -7.5t-7.5 -17.5v-175h-400v300h-200v-300h-400v175q0 10 -7.5 17.5t-17.5 7.5h-50q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5h61q11 0 18 3t7 8q0 4 9 52l25 128q5 25 19 45q2 3 5 7t13.5 15t21.5 19.5t26.5 15.5 t29.5 7zM915 1079l-166 -162q-7 -7 -5 -12t12 -5h219q10 0 15 7t2 17l-51 149q-3 10 -11 12t-15 -6zM463 917l-177 157q-8 7 -16 5t-11 -12l-51 -143q-3 -10 2 -17t15 -7h231q11 0 12.5 5t-5.5 12zM500 0h-375q-10 0 -17.5 7.5t-7.5 17.5v375h400v-400zM1100 400v-375 q0 -10 -7.5 -17.5t-17.5 -7.5h-375v400h400z" /> +<glyph unicode="" d="M1165 1190q8 3 21 -6.5t13 -17.5q-2 -178 -24.5 -323.5t-55.5 -245.5t-87 -174.5t-102.5 -118.5t-118 -68.5t-118.5 -33t-120 -4.5t-105 9.5t-90 16.5q-61 12 -78 11q-4 1 -12.5 0t-34 -14.5t-52.5 -40.5l-153 -153q-26 -24 -37 -14.5t-11 43.5q0 64 42 102q8 8 50.5 45 t66.5 58q19 17 35 47t13 61q-9 55 -10 102.5t7 111t37 130t78 129.5q39 51 80 88t89.5 63.5t94.5 45t113.5 36t129 31t157.5 37t182 47.5zM1116 1098q-8 9 -22.5 -3t-45.5 -50q-38 -47 -119 -103.5t-142 -89.5l-62 -33q-56 -30 -102 -57t-104 -68t-102.5 -80.5t-85.5 -91 t-64 -104.5q-24 -56 -31 -86t2 -32t31.5 17.5t55.5 59.5q25 30 94 75.5t125.5 77.5t147.5 81q70 37 118.5 69t102 79.5t99 111t86.5 148.5q22 50 24 60t-6 19z" /> +<glyph unicode="" d="M653 1231q-39 -67 -54.5 -131t-10.5 -114.5t24.5 -96.5t47.5 -80t63.5 -62.5t68.5 -46.5t65 -30q-4 7 -17.5 35t-18.5 39.5t-17 39.5t-17 43t-13 42t-9.5 44.5t-2 42t4 43t13.5 39t23 38.5q96 -42 165 -107.5t105 -138t52 -156t13 -159t-19 -149.5q-13 -55 -44 -106.5 t-68 -87t-78.5 -64.5t-72.5 -45t-53 -22q-72 -22 -127 -11q-31 6 -13 19q6 3 17 7q13 5 32.5 21t41 44t38.5 63.5t21.5 81.5t-6.5 94.5t-50 107t-104 115.5q10 -104 -0.5 -189t-37 -140.5t-65 -93t-84 -52t-93.5 -11t-95 24.5q-80 36 -131.5 114t-53.5 171q-2 23 0 49.5 t4.5 52.5t13.5 56t27.5 60t46 64.5t69.5 68.5q-8 -53 -5 -102.5t17.5 -90t34 -68.5t44.5 -39t49 -2q31 13 38.5 36t-4.5 55t-29 64.5t-36 75t-26 75.5q-15 85 2 161.5t53.5 128.5t85.5 92.5t93.5 61t81.5 25.5z" /> +<glyph unicode="" d="M600 1094q82 0 160.5 -22.5t140 -59t116.5 -82.5t94.5 -95t68 -95t42.5 -82.5t14 -57.5t-14 -57.5t-43 -82.5t-68.5 -95t-94.5 -95t-116.5 -82.5t-140 -59t-159.5 -22.5t-159.5 22.5t-140 59t-116.5 82.5t-94.5 95t-68.5 95t-43 82.5t-14 57.5t14 57.5t42.5 82.5t68 95 t94.5 95t116.5 82.5t140 59t160.5 22.5zM888 829q-15 15 -18 12t5 -22q25 -57 25 -119q0 -124 -88 -212t-212 -88t-212 88t-88 212q0 59 23 114q8 19 4.5 22t-17.5 -12q-70 -69 -160 -184q-13 -16 -15 -40.5t9 -42.5q22 -36 47 -71t70 -82t92.5 -81t113 -58.5t133.5 -24.5 t133.5 24t113 58.5t92.5 81.5t70 81.5t47 70.5q11 18 9 42.5t-14 41.5q-90 117 -163 189zM448 727l-35 -36q-15 -15 -19.5 -38.5t4.5 -41.5q37 -68 93 -116q16 -13 38.5 -11t36.5 17l35 34q14 15 12.5 33.5t-16.5 33.5q-44 44 -89 117q-11 18 -28 20t-32 -12z" /> +<glyph unicode="" d="M592 0h-148l31 120q-91 20 -175.5 68.5t-143.5 106.5t-103.5 119t-66.5 110t-22 76q0 21 14 57.5t42.5 82.5t68 95t94.5 95t116.5 82.5t140 59t160.5 22.5q61 0 126 -15l32 121h148zM944 770l47 181q108 -85 176.5 -192t68.5 -159q0 -26 -19.5 -71t-59.5 -102t-93 -112 t-129 -104.5t-158 -75.5l46 173q77 49 136 117t97 131q11 18 9 42.5t-14 41.5q-54 70 -107 130zM310 824q-70 -69 -160 -184q-13 -16 -15 -40.5t9 -42.5q18 -30 39 -60t57 -70.5t74 -73t90 -61t105 -41.5l41 154q-107 18 -178.5 101.5t-71.5 193.5q0 59 23 114q8 19 4.5 22 t-17.5 -12zM448 727l-35 -36q-15 -15 -19.5 -38.5t4.5 -41.5q37 -68 93 -116q16 -13 38.5 -11t36.5 17l12 11l22 86l-3 4q-44 44 -89 117q-11 18 -28 20t-32 -12z" /> +<glyph unicode="" d="M-90 100l642 1066q20 31 48 28.5t48 -35.5l642 -1056q21 -32 7.5 -67.5t-50.5 -35.5h-1294q-37 0 -50.5 34t7.5 66zM155 200h345v75q0 10 7.5 17.5t17.5 7.5h150q10 0 17.5 -7.5t7.5 -17.5v-75h345l-445 723zM496 700h208q20 0 32 -14.5t8 -34.5l-58 -252 q-4 -20 -21.5 -34.5t-37.5 -14.5h-54q-20 0 -37.5 14.5t-21.5 34.5l-58 252q-4 20 8 34.5t32 14.5z" /> +<glyph unicode="" d="M650 1200q62 0 106 -44t44 -106v-339l363 -325q15 -14 26 -38.5t11 -44.5v-41q0 -20 -12 -26.5t-29 5.5l-359 249v-263q100 -93 100 -113v-64q0 -21 -13 -29t-32 1l-205 128l-205 -128q-19 -9 -32 -1t-13 29v64q0 20 100 113v263l-359 -249q-17 -12 -29 -5.5t-12 26.5v41 q0 20 11 44.5t26 38.5l363 325v339q0 62 44 106t106 44z" /> +<glyph unicode="" d="M850 1200h100q21 0 35.5 -14.5t14.5 -35.5v-50h50q21 0 35.5 -14.5t14.5 -35.5v-150h-1100v150q0 21 14.5 35.5t35.5 14.5h50v50q0 21 14.5 35.5t35.5 14.5h100q21 0 35.5 -14.5t14.5 -35.5v-50h500v50q0 21 14.5 35.5t35.5 14.5zM1100 800v-750q0 -21 -14.5 -35.5 t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5v750h1100zM100 600v-100h100v100h-100zM300 600v-100h100v100h-100zM500 600v-100h100v100h-100zM700 600v-100h100v100h-100zM900 600v-100h100v100h-100zM100 400v-100h100v100h-100zM300 400v-100h100v100h-100zM500 400 v-100h100v100h-100zM700 400v-100h100v100h-100zM900 400v-100h100v100h-100zM100 200v-100h100v100h-100zM300 200v-100h100v100h-100zM500 200v-100h100v100h-100zM700 200v-100h100v100h-100zM900 200v-100h100v100h-100z" /> +<glyph unicode="" d="M1135 1165l249 -230q15 -14 15 -35t-15 -35l-249 -230q-14 -14 -24.5 -10t-10.5 25v150h-159l-600 -600h-291q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h209l600 600h241v150q0 21 10.5 25t24.5 -10zM522 819l-141 -141l-122 122h-209q-21 0 -35.5 14.5 t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h291zM1135 565l249 -230q15 -14 15 -35t-15 -35l-249 -230q-14 -14 -24.5 -10t-10.5 25v150h-241l-181 181l141 141l122 -122h159v150q0 21 10.5 25t24.5 -10z" /> +<glyph unicode="" d="M100 1100h1000q41 0 70.5 -29.5t29.5 -70.5v-600q0 -41 -29.5 -70.5t-70.5 -29.5h-596l-304 -300v300h-100q-41 0 -70.5 29.5t-29.5 70.5v600q0 41 29.5 70.5t70.5 29.5z" /> +<glyph unicode="" d="M150 1200h200q21 0 35.5 -14.5t14.5 -35.5v-250h-300v250q0 21 14.5 35.5t35.5 14.5zM850 1200h200q21 0 35.5 -14.5t14.5 -35.5v-250h-300v250q0 21 14.5 35.5t35.5 14.5zM1100 800v-300q0 -41 -3 -77.5t-15 -89.5t-32 -96t-58 -89t-89 -77t-129 -51t-174 -20t-174 20 t-129 51t-89 77t-58 89t-32 96t-15 89.5t-3 77.5v300h300v-250v-27v-42.5t1.5 -41t5 -38t10 -35t16.5 -30t25.5 -24.5t35 -19t46.5 -12t60 -4t60 4.5t46.5 12.5t35 19.5t25 25.5t17 30.5t10 35t5 38t2 40.5t-0.5 42v25v250h300z" /> +<glyph unicode="" d="M1100 411l-198 -199l-353 353l-353 -353l-197 199l551 551z" /> +<glyph unicode="" d="M1101 789l-550 -551l-551 551l198 199l353 -353l353 353z" /> +<glyph unicode="" d="M404 1000h746q21 0 35.5 -14.5t14.5 -35.5v-551h150q21 0 25 -10.5t-10 -24.5l-230 -249q-14 -15 -35 -15t-35 15l-230 249q-14 14 -10 24.5t25 10.5h150v401h-381zM135 984l230 -249q14 -14 10 -24.5t-25 -10.5h-150v-400h385l215 -200h-750q-21 0 -35.5 14.5 t-14.5 35.5v550h-150q-21 0 -25 10.5t10 24.5l230 249q14 15 35 15t35 -15z" /> +<glyph unicode="" d="M56 1200h94q17 0 31 -11t18 -27l38 -162h896q24 0 39 -18.5t10 -42.5l-100 -475q-5 -21 -27 -42.5t-55 -21.5h-633l48 -200h535q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-50v-50q0 -21 -14.5 -35.5t-35.5 -14.5t-35.5 14.5t-14.5 35.5v50h-300v-50 q0 -21 -14.5 -35.5t-35.5 -14.5t-35.5 14.5t-14.5 35.5v50h-31q-18 0 -32.5 10t-20.5 19l-5 10l-201 961h-54q-20 0 -35 14.5t-15 35.5t15 35.5t35 14.5z" /> +<glyph unicode="" d="M1200 1000v-100h-1200v100h200q0 41 29.5 70.5t70.5 29.5h300q41 0 70.5 -29.5t29.5 -70.5h500zM0 800h1200v-800h-1200v800z" /> +<glyph unicode="" d="M200 800l-200 -400v600h200q0 41 29.5 70.5t70.5 29.5h300q42 0 71 -29.5t29 -70.5h500v-200h-1000zM1500 700l-300 -700h-1200l300 700h1200z" /> +<glyph unicode="" d="M635 1184l230 -249q14 -14 10 -24.5t-25 -10.5h-150v-601h150q21 0 25 -10.5t-10 -24.5l-230 -249q-14 -15 -35 -15t-35 15l-230 249q-14 14 -10 24.5t25 10.5h150v601h-150q-21 0 -25 10.5t10 24.5l230 249q14 15 35 15t35 -15z" /> +<glyph unicode="" d="M936 864l249 -229q14 -15 14 -35.5t-14 -35.5l-249 -229q-15 -15 -25.5 -10.5t-10.5 24.5v151h-600v-151q0 -20 -10.5 -24.5t-25.5 10.5l-249 229q-14 15 -14 35.5t14 35.5l249 229q15 15 25.5 10.5t10.5 -25.5v-149h600v149q0 21 10.5 25.5t25.5 -10.5z" /> +<glyph unicode="" d="M1169 400l-172 732q-5 23 -23 45.5t-38 22.5h-672q-20 0 -38 -20t-23 -41l-172 -739h1138zM1100 300h-1000q-41 0 -70.5 -29.5t-29.5 -70.5v-100q0 -41 29.5 -70.5t70.5 -29.5h1000q41 0 70.5 29.5t29.5 70.5v100q0 41 -29.5 70.5t-70.5 29.5zM800 100v100h100v-100h-100 zM1000 100v100h100v-100h-100z" /> +<glyph unicode="" d="M1150 1100q21 0 35.5 -14.5t14.5 -35.5v-850q0 -21 -14.5 -35.5t-35.5 -14.5t-35.5 14.5t-14.5 35.5v850q0 21 14.5 35.5t35.5 14.5zM1000 200l-675 200h-38l47 -276q3 -16 -5.5 -20t-29.5 -4h-7h-84q-20 0 -34.5 14t-18.5 35q-55 337 -55 351v250v6q0 16 1 23.5t6.5 14 t17.5 6.5h200l675 250v-850zM0 750v-250q-4 0 -11 0.5t-24 6t-30 15t-24 30t-11 48.5v50q0 26 10.5 46t25 30t29 16t25.5 7z" /> +<glyph unicode="" d="M553 1200h94q20 0 29 -10.5t3 -29.5l-18 -37q83 -19 144 -82.5t76 -140.5l63 -327l118 -173h17q19 0 33 -14.5t14 -35t-13 -40.5t-31 -27q-8 -4 -23 -9.5t-65 -19.5t-103 -25t-132.5 -20t-158.5 -9q-57 0 -115 5t-104 12t-88.5 15.5t-73.5 17.5t-54.5 16t-35.5 12l-11 4 q-18 8 -31 28t-13 40.5t14 35t33 14.5h17l118 173l63 327q15 77 76 140t144 83l-18 32q-6 19 3.5 32t28.5 13zM498 110q50 -6 102 -6q53 0 102 6q-12 -49 -39.5 -79.5t-62.5 -30.5t-63 30.5t-39 79.5z" /> +<glyph unicode="" d="M800 946l224 78l-78 -224l234 -45l-180 -155l180 -155l-234 -45l78 -224l-224 78l-45 -234l-155 180l-155 -180l-45 234l-224 -78l78 224l-234 45l180 155l-180 155l234 45l-78 224l224 -78l45 234l155 -180l155 180z" /> +<glyph unicode="" d="M650 1200h50q40 0 70 -40.5t30 -84.5v-150l-28 -125h328q40 0 70 -40.5t30 -84.5v-100q0 -45 -29 -74l-238 -344q-16 -24 -38 -40.5t-45 -16.5h-250q-7 0 -42 25t-66 50l-31 25h-61q-45 0 -72.5 18t-27.5 57v400q0 36 20 63l145 196l96 198q13 28 37.5 48t51.5 20z M650 1100l-100 -212l-150 -213v-375h100l136 -100h214l250 375v125h-450l50 225v175h-50zM50 800h100q21 0 35.5 -14.5t14.5 -35.5v-500q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v500q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M600 1100h250q23 0 45 -16.5t38 -40.5l238 -344q29 -29 29 -74v-100q0 -44 -30 -84.5t-70 -40.5h-328q28 -118 28 -125v-150q0 -44 -30 -84.5t-70 -40.5h-50q-27 0 -51.5 20t-37.5 48l-96 198l-145 196q-20 27 -20 63v400q0 39 27.5 57t72.5 18h61q124 100 139 100z M50 1000h100q21 0 35.5 -14.5t14.5 -35.5v-500q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v500q0 21 14.5 35.5t35.5 14.5zM636 1000l-136 -100h-100v-375l150 -213l100 -212h50v175l-50 225h450v125l-250 375h-214z" /> +<glyph unicode="" d="M356 873l363 230q31 16 53 -6l110 -112q13 -13 13.5 -32t-11.5 -34l-84 -121h302q84 0 138 -38t54 -110t-55 -111t-139 -39h-106l-131 -339q-6 -21 -19.5 -41t-28.5 -20h-342q-7 0 -90 81t-83 94v525q0 17 14 35.5t28 28.5zM400 792v-503l100 -89h293l131 339 q6 21 19.5 41t28.5 20h203q21 0 30.5 25t0.5 50t-31 25h-456h-7h-6h-5.5t-6 0.5t-5 1.5t-5 2t-4 2.5t-4 4t-2.5 4.5q-12 25 5 47l146 183l-86 83zM50 800h100q21 0 35.5 -14.5t14.5 -35.5v-500q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v500 q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M475 1103l366 -230q2 -1 6 -3.5t14 -10.5t18 -16.5t14.5 -20t6.5 -22.5v-525q0 -13 -86 -94t-93 -81h-342q-15 0 -28.5 20t-19.5 41l-131 339h-106q-85 0 -139.5 39t-54.5 111t54 110t138 38h302l-85 121q-11 15 -10.5 34t13.5 32l110 112q22 22 53 6zM370 945l146 -183 q17 -22 5 -47q-2 -2 -3.5 -4.5t-4 -4t-4 -2.5t-5 -2t-5 -1.5t-6 -0.5h-6h-6.5h-6h-475v-100h221q15 0 29 -20t20 -41l130 -339h294l106 89v503l-342 236zM1050 800h100q21 0 35.5 -14.5t14.5 -35.5v-500q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5 v500q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M550 1294q72 0 111 -55t39 -139v-106l339 -131q21 -6 41 -19.5t20 -28.5v-342q0 -7 -81 -90t-94 -83h-525q-17 0 -35.5 14t-28.5 28l-9 14l-230 363q-16 31 6 53l112 110q13 13 32 13.5t34 -11.5l121 -84v302q0 84 38 138t110 54zM600 972v203q0 21 -25 30.5t-50 0.5 t-25 -31v-456v-7v-6v-5.5t-0.5 -6t-1.5 -5t-2 -5t-2.5 -4t-4 -4t-4.5 -2.5q-25 -12 -47 5l-183 146l-83 -86l236 -339h503l89 100v293l-339 131q-21 6 -41 19.5t-20 28.5zM450 200h500q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-500 q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M350 1100h500q21 0 35.5 14.5t14.5 35.5v100q0 21 -14.5 35.5t-35.5 14.5h-500q-21 0 -35.5 -14.5t-14.5 -35.5v-100q0 -21 14.5 -35.5t35.5 -14.5zM600 306v-106q0 -84 -39 -139t-111 -55t-110 54t-38 138v302l-121 -84q-15 -12 -34 -11.5t-32 13.5l-112 110 q-22 22 -6 53l230 363q1 2 3.5 6t10.5 13.5t16.5 17t20 13.5t22.5 6h525q13 0 94 -83t81 -90v-342q0 -15 -20 -28.5t-41 -19.5zM308 900l-236 -339l83 -86l183 146q22 17 47 5q2 -1 4.5 -2.5t4 -4t2.5 -4t2 -5t1.5 -5t0.5 -6v-5.5v-6v-7v-456q0 -22 25 -31t50 0.5t25 30.5 v203q0 15 20 28.5t41 19.5l339 131v293l-89 100h-503z" /> +<glyph unicode="" d="M600 1178q118 0 225 -45.5t184.5 -123t123 -184.5t45.5 -225t-45.5 -225t-123 -184.5t-184.5 -123t-225 -45.5t-225 45.5t-184.5 123t-123 184.5t-45.5 225t45.5 225t123 184.5t184.5 123t225 45.5zM914 632l-275 223q-16 13 -27.5 8t-11.5 -26v-137h-275 q-10 0 -17.5 -7.5t-7.5 -17.5v-150q0 -10 7.5 -17.5t17.5 -7.5h275v-137q0 -21 11.5 -26t27.5 8l275 223q16 13 16 32t-16 32z" /> +<glyph unicode="" d="M600 1178q118 0 225 -45.5t184.5 -123t123 -184.5t45.5 -225t-45.5 -225t-123 -184.5t-184.5 -123t-225 -45.5t-225 45.5t-184.5 123t-123 184.5t-45.5 225t45.5 225t123 184.5t184.5 123t225 45.5zM561 855l-275 -223q-16 -13 -16 -32t16 -32l275 -223q16 -13 27.5 -8 t11.5 26v137h275q10 0 17.5 7.5t7.5 17.5v150q0 10 -7.5 17.5t-17.5 7.5h-275v137q0 21 -11.5 26t-27.5 -8z" /> +<glyph unicode="" d="M600 1178q118 0 225 -45.5t184.5 -123t123 -184.5t45.5 -225t-45.5 -225t-123 -184.5t-184.5 -123t-225 -45.5t-225 45.5t-184.5 123t-123 184.5t-45.5 225t45.5 225t123 184.5t184.5 123t225 45.5zM855 639l-223 275q-13 16 -32 16t-32 -16l-223 -275q-13 -16 -8 -27.5 t26 -11.5h137v-275q0 -10 7.5 -17.5t17.5 -7.5h150q10 0 17.5 7.5t7.5 17.5v275h137q21 0 26 11.5t-8 27.5z" /> +<glyph unicode="" d="M600 1178q118 0 225 -45.5t184.5 -123t123 -184.5t45.5 -225t-45.5 -225t-123 -184.5t-184.5 -123t-225 -45.5t-225 45.5t-184.5 123t-123 184.5t-45.5 225t45.5 225t123 184.5t184.5 123t225 45.5zM675 900h-150q-10 0 -17.5 -7.5t-7.5 -17.5v-275h-137q-21 0 -26 -11.5 t8 -27.5l223 -275q13 -16 32 -16t32 16l223 275q13 16 8 27.5t-26 11.5h-137v275q0 10 -7.5 17.5t-17.5 7.5z" /> +<glyph unicode="" d="M600 1176q116 0 222.5 -46t184 -123.5t123.5 -184t46 -222.5t-46 -222.5t-123.5 -184t-184 -123.5t-222.5 -46t-222.5 46t-184 123.5t-123.5 184t-46 222.5t46 222.5t123.5 184t184 123.5t222.5 46zM627 1101q-15 -12 -36.5 -20.5t-35.5 -12t-43 -8t-39 -6.5 q-15 -3 -45.5 0t-45.5 -2q-20 -7 -51.5 -26.5t-34.5 -34.5q-3 -11 6.5 -22.5t8.5 -18.5q-3 -34 -27.5 -91t-29.5 -79q-9 -34 5 -93t8 -87q0 -9 17 -44.5t16 -59.5q12 0 23 -5t23.5 -15t19.5 -14q16 -8 33 -15t40.5 -15t34.5 -12q21 -9 52.5 -32t60 -38t57.5 -11 q7 -15 -3 -34t-22.5 -40t-9.5 -38q13 -21 23 -34.5t27.5 -27.5t36.5 -18q0 -7 -3.5 -16t-3.5 -14t5 -17q104 -2 221 112q30 29 46.5 47t34.5 49t21 63q-13 8 -37 8.5t-36 7.5q-15 7 -49.5 15t-51.5 19q-18 0 -41 -0.5t-43 -1.5t-42 -6.5t-38 -16.5q-51 -35 -66 -12 q-4 1 -3.5 25.5t0.5 25.5q-6 13 -26.5 17.5t-24.5 6.5q1 15 -0.5 30.5t-7 28t-18.5 11.5t-31 -21q-23 -25 -42 4q-19 28 -8 58q6 16 22 22q6 -1 26 -1.5t33.5 -4t19.5 -13.5q7 -12 18 -24t21.5 -20.5t20 -15t15.5 -10.5l5 -3q2 12 7.5 30.5t8 34.5t-0.5 32q-3 18 3.5 29 t18 22.5t15.5 24.5q6 14 10.5 35t8 31t15.5 22.5t34 22.5q-6 18 10 36q8 0 24 -1.5t24.5 -1.5t20 4.5t20.5 15.5q-10 23 -31 42.5t-37.5 29.5t-49 27t-43.5 23q0 1 2 8t3 11.5t1.5 10.5t-1 9.5t-4.5 4.5q31 -13 58.5 -14.5t38.5 2.5l12 5q5 28 -9.5 46t-36.5 24t-50 15 t-41 20q-18 -4 -37 0zM613 994q0 -17 8 -42t17 -45t9 -23q-8 1 -39.5 5.5t-52.5 10t-37 16.5q3 11 16 29.5t16 25.5q10 -10 19 -10t14 6t13.5 14.5t16.5 12.5z" /> +<glyph unicode="" d="M756 1157q164 92 306 -9l-259 -138l145 -232l251 126q6 -89 -34 -156.5t-117 -110.5q-60 -34 -127 -39.5t-126 16.5l-596 -596q-15 -16 -36.5 -16t-36.5 16l-111 110q-15 15 -15 36.5t15 37.5l600 599q-34 101 5.5 201.5t135.5 154.5z" /> +<glyph unicode="" horiz-adv-x="1220" d="M100 1196h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5v100q0 41 29.5 70.5t70.5 29.5zM1100 1096h-200v-100h200v100zM100 796h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000 q-41 0 -70.5 29.5t-29.5 70.5v100q0 41 29.5 70.5t70.5 29.5zM1100 696h-500v-100h500v100zM100 396h1000q41 0 70.5 -29.5t29.5 -70.5v-100q0 -41 -29.5 -70.5t-70.5 -29.5h-1000q-41 0 -70.5 29.5t-29.5 70.5v100q0 41 29.5 70.5t70.5 29.5zM1100 296h-300v-100h300v100z " /> +<glyph unicode="" d="M150 1200h900q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-900q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM700 500v-300l-200 -200v500l-350 500h900z" /> +<glyph unicode="" d="M500 1200h200q41 0 70.5 -29.5t29.5 -70.5v-100h300q41 0 70.5 -29.5t29.5 -70.5v-400h-500v100h-200v-100h-500v400q0 41 29.5 70.5t70.5 29.5h300v100q0 41 29.5 70.5t70.5 29.5zM500 1100v-100h200v100h-200zM1200 400v-200q0 -41 -29.5 -70.5t-70.5 -29.5h-1000 q-41 0 -70.5 29.5t-29.5 70.5v200h1200z" /> +<glyph unicode="" d="M50 1200h300q21 0 25 -10.5t-10 -24.5l-94 -94l199 -199q7 -8 7 -18t-7 -18l-106 -106q-8 -7 -18 -7t-18 7l-199 199l-94 -94q-14 -14 -24.5 -10t-10.5 25v300q0 21 14.5 35.5t35.5 14.5zM850 1200h300q21 0 35.5 -14.5t14.5 -35.5v-300q0 -21 -10.5 -25t-24.5 10l-94 94 l-199 -199q-8 -7 -18 -7t-18 7l-106 106q-7 8 -7 18t7 18l199 199l-94 94q-14 14 -10 24.5t25 10.5zM364 470l106 -106q7 -8 7 -18t-7 -18l-199 -199l94 -94q14 -14 10 -24.5t-25 -10.5h-300q-21 0 -35.5 14.5t-14.5 35.5v300q0 21 10.5 25t24.5 -10l94 -94l199 199 q8 7 18 7t18 -7zM1071 271l94 94q14 14 24.5 10t10.5 -25v-300q0 -21 -14.5 -35.5t-35.5 -14.5h-300q-21 0 -25 10.5t10 24.5l94 94l-199 199q-7 8 -7 18t7 18l106 106q8 7 18 7t18 -7z" /> +<glyph unicode="" d="M596 1192q121 0 231.5 -47.5t190 -127t127 -190t47.5 -231.5t-47.5 -231.5t-127 -190.5t-190 -127t-231.5 -47t-231.5 47t-190.5 127t-127 190.5t-47 231.5t47 231.5t127 190t190.5 127t231.5 47.5zM596 1010q-112 0 -207.5 -55.5t-151 -151t-55.5 -207.5t55.5 -207.5 t151 -151t207.5 -55.5t207.5 55.5t151 151t55.5 207.5t-55.5 207.5t-151 151t-207.5 55.5zM454.5 905q22.5 0 38.5 -16t16 -38.5t-16 -39t-38.5 -16.5t-38.5 16.5t-16 39t16 38.5t38.5 16zM754.5 905q22.5 0 38.5 -16t16 -38.5t-16 -39t-38 -16.5q-14 0 -29 10l-55 -145 q17 -23 17 -51q0 -36 -25.5 -61.5t-61.5 -25.5t-61.5 25.5t-25.5 61.5q0 32 20.5 56.5t51.5 29.5l122 126l1 1q-9 14 -9 28q0 23 16 39t38.5 16zM345.5 709q22.5 0 38.5 -16t16 -38.5t-16 -38.5t-38.5 -16t-38.5 16t-16 38.5t16 38.5t38.5 16zM854.5 709q22.5 0 38.5 -16 t16 -38.5t-16 -38.5t-38.5 -16t-38.5 16t-16 38.5t16 38.5t38.5 16z" /> +<glyph unicode="" d="M546 173l469 470q91 91 99 192q7 98 -52 175.5t-154 94.5q-22 4 -47 4q-34 0 -66.5 -10t-56.5 -23t-55.5 -38t-48 -41.5t-48.5 -47.5q-376 -375 -391 -390q-30 -27 -45 -41.5t-37.5 -41t-32 -46.5t-16 -47.5t-1.5 -56.5q9 -62 53.5 -95t99.5 -33q74 0 125 51l548 548 q36 36 20 75q-7 16 -21.5 26t-32.5 10q-26 0 -50 -23q-13 -12 -39 -38l-341 -338q-15 -15 -35.5 -15.5t-34.5 13.5t-14 34.5t14 34.5q327 333 361 367q35 35 67.5 51.5t78.5 16.5q14 0 29 -1q44 -8 74.5 -35.5t43.5 -68.5q14 -47 2 -96.5t-47 -84.5q-12 -11 -32 -32 t-79.5 -81t-114.5 -115t-124.5 -123.5t-123 -119.5t-96.5 -89t-57 -45q-56 -27 -120 -27q-70 0 -129 32t-93 89q-48 78 -35 173t81 163l511 511q71 72 111 96q91 55 198 55q80 0 152 -33q78 -36 129.5 -103t66.5 -154q17 -93 -11 -183.5t-94 -156.5l-482 -476 q-15 -15 -36 -16t-37 14t-17.5 34t14.5 35z" /> +<glyph unicode="" d="M649 949q48 68 109.5 104t121.5 38.5t118.5 -20t102.5 -64t71 -100.5t27 -123q0 -57 -33.5 -117.5t-94 -124.5t-126.5 -127.5t-150 -152.5t-146 -174q-62 85 -145.5 174t-150 152.5t-126.5 127.5t-93.5 124.5t-33.5 117.5q0 64 28 123t73 100.5t104 64t119 20 t120.5 -38.5t104.5 -104zM896 972q-33 0 -64.5 -19t-56.5 -46t-47.5 -53.5t-43.5 -45.5t-37.5 -19t-36 19t-40 45.5t-43 53.5t-54 46t-65.5 19q-67 0 -122.5 -55.5t-55.5 -132.5q0 -23 13.5 -51t46 -65t57.5 -63t76 -75l22 -22q15 -14 44 -44t50.5 -51t46 -44t41 -35t23 -12 t23.5 12t42.5 36t46 44t52.5 52t44 43q4 4 12 13q43 41 63.5 62t52 55t46 55t26 46t11.5 44q0 79 -53 133.5t-120 54.5z" /> +<glyph unicode="" d="M776.5 1214q93.5 0 159.5 -66l141 -141q66 -66 66 -160q0 -42 -28 -95.5t-62 -87.5l-29 -29q-31 53 -77 99l-18 18l95 95l-247 248l-389 -389l212 -212l-105 -106l-19 18l-141 141q-66 66 -66 159t66 159l283 283q65 66 158.5 66zM600 706l105 105q10 -8 19 -17l141 -141 q66 -66 66 -159t-66 -159l-283 -283q-66 -66 -159 -66t-159 66l-141 141q-66 66 -66 159.5t66 159.5l55 55q29 -55 75 -102l18 -17l-95 -95l247 -248l389 389z" /> +<glyph unicode="" d="M603 1200q85 0 162 -15t127 -38t79 -48t29 -46v-953q0 -41 -29.5 -70.5t-70.5 -29.5h-600q-41 0 -70.5 29.5t-29.5 70.5v953q0 21 30 46.5t81 48t129 37.5t163 15zM300 1000v-700h600v700h-600zM600 254q-43 0 -73.5 -30.5t-30.5 -73.5t30.5 -73.5t73.5 -30.5t73.5 30.5 t30.5 73.5t-30.5 73.5t-73.5 30.5z" /> +<glyph unicode="" d="M902 1185l283 -282q15 -15 15 -36t-14.5 -35.5t-35.5 -14.5t-35 15l-36 35l-279 -267v-300l-212 210l-308 -307l-280 -203l203 280l307 308l-210 212h300l267 279l-35 36q-15 14 -15 35t14.5 35.5t35.5 14.5t35 -15z" /> +<glyph unicode="" d="M700 1248v-78q38 -5 72.5 -14.5t75.5 -31.5t71 -53.5t52 -84t24 -118.5h-159q-4 36 -10.5 59t-21 45t-40 35.5t-64.5 20.5v-307l64 -13q34 -7 64 -16.5t70 -32t67.5 -52.5t47.5 -80t20 -112q0 -139 -89 -224t-244 -97v-77h-100v79q-150 16 -237 103q-40 40 -52.5 93.5 t-15.5 139.5h139q5 -77 48.5 -126t117.5 -65v335l-27 8q-46 14 -79 26.5t-72 36t-63 52t-40 72.5t-16 98q0 70 25 126t67.5 92t94.5 57t110 27v77h100zM600 754v274q-29 -4 -50 -11t-42 -21.5t-31.5 -41.5t-10.5 -65q0 -29 7 -50.5t16.5 -34t28.5 -22.5t31.5 -14t37.5 -10 q9 -3 13 -4zM700 547v-310q22 2 42.5 6.5t45 15.5t41.5 27t29 42t12 59.5t-12.5 59.5t-38 44.5t-53 31t-66.5 24.5z" /> +<glyph unicode="" d="M561 1197q84 0 160.5 -40t123.5 -109.5t47 -147.5h-153q0 40 -19.5 71.5t-49.5 48.5t-59.5 26t-55.5 9q-37 0 -79 -14.5t-62 -35.5q-41 -44 -41 -101q0 -26 13.5 -63t26.5 -61t37 -66q6 -9 9 -14h241v-100h-197q8 -50 -2.5 -115t-31.5 -95q-45 -62 -99 -112 q34 10 83 17.5t71 7.5q32 1 102 -16t104 -17q83 0 136 30l50 -147q-31 -19 -58 -30.5t-55 -15.5t-42 -4.5t-46 -0.5q-23 0 -76 17t-111 32.5t-96 11.5q-39 -3 -82 -16t-67 -25l-23 -11l-55 145q4 3 16 11t15.5 10.5t13 9t15.5 12t14.5 14t17.5 18.5q48 55 54 126.5 t-30 142.5h-221v100h166q-23 47 -44 104q-7 20 -12 41.5t-6 55.5t6 66.5t29.5 70.5t58.5 71q97 88 263 88z" /> +<glyph unicode="" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM935 1184l230 -249q14 -14 10 -24.5t-25 -10.5h-150v-900h-200v900h-150q-21 0 -25 10.5t10 24.5l230 249q14 15 35 15t35 -15z" /> +<glyph unicode="" d="M1000 700h-100v100h-100v-100h-100v500h300v-500zM400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM801 1100v-200h100v200h-100zM1000 350l-200 -250h200v-100h-300v150l200 250h-200v100h300v-150z " /> +<glyph unicode="" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM1000 1050l-200 -250h200v-100h-300v150l200 250h-200v100h300v-150zM1000 0h-100v100h-100v-100h-100v500h300v-500zM801 400v-200h100v200h-100z " /> +<glyph unicode="" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM1000 700h-100v400h-100v100h200v-500zM1100 0h-100v100h-200v400h300v-500zM901 400v-200h100v200h-100z" /> +<glyph unicode="" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM1100 700h-100v100h-200v400h300v-500zM901 1100v-200h100v200h-100zM1000 0h-100v400h-100v100h200v-500z" /> +<glyph unicode="" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM900 1000h-200v200h200v-200zM1000 700h-300v200h300v-200zM1100 400h-400v200h400v-200zM1200 100h-500v200h500v-200z" /> +<glyph unicode="" d="M400 300h150q21 0 25 -11t-10 -25l-230 -250q-14 -15 -35 -15t-35 15l-230 250q-14 14 -10 25t25 11h150v900h200v-900zM1200 1000h-500v200h500v-200zM1100 700h-400v200h400v-200zM1000 400h-300v200h300v-200zM900 100h-200v200h200v-200z" /> +<glyph unicode="" d="M350 1100h400q162 0 256 -93.5t94 -256.5v-400q0 -165 -93.5 -257.5t-256.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 165 92.5 257.5t257.5 92.5zM800 900h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5 v500q0 41 -29.5 70.5t-70.5 29.5z" /> +<glyph unicode="" d="M350 1100h400q165 0 257.5 -92.5t92.5 -257.5v-400q0 -165 -92.5 -257.5t-257.5 -92.5h-400q-163 0 -256.5 92.5t-93.5 257.5v400q0 163 94 256.5t256 93.5zM800 900h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5 v500q0 41 -29.5 70.5t-70.5 29.5zM440 770l253 -190q17 -12 17 -30t-17 -30l-253 -190q-16 -12 -28 -6.5t-12 26.5v400q0 21 12 26.5t28 -6.5z" /> +<glyph unicode="" d="M350 1100h400q163 0 256.5 -94t93.5 -256v-400q0 -165 -92.5 -257.5t-257.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 163 92.5 256.5t257.5 93.5zM800 900h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5 v500q0 41 -29.5 70.5t-70.5 29.5zM350 700h400q21 0 26.5 -12t-6.5 -28l-190 -253q-12 -17 -30 -17t-30 17l-190 253q-12 16 -6.5 28t26.5 12z" /> +<glyph unicode="" d="M350 1100h400q165 0 257.5 -92.5t92.5 -257.5v-400q0 -163 -92.5 -256.5t-257.5 -93.5h-400q-163 0 -256.5 94t-93.5 256v400q0 165 92.5 257.5t257.5 92.5zM800 900h-500q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5 v500q0 41 -29.5 70.5t-70.5 29.5zM580 693l190 -253q12 -16 6.5 -28t-26.5 -12h-400q-21 0 -26.5 12t6.5 28l190 253q12 17 30 17t30 -17z" /> +<glyph unicode="" d="M550 1100h400q165 0 257.5 -92.5t92.5 -257.5v-400q0 -165 -92.5 -257.5t-257.5 -92.5h-400q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h450q41 0 70.5 29.5t29.5 70.5v500q0 41 -29.5 70.5t-70.5 29.5h-450q-21 0 -35.5 14.5t-14.5 35.5v100 q0 21 14.5 35.5t35.5 14.5zM338 867l324 -284q16 -14 16 -33t-16 -33l-324 -284q-16 -14 -27 -9t-11 26v150h-250q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h250v150q0 21 11 26t27 -9z" /> +<glyph unicode="" d="M793 1182l9 -9q8 -10 5 -27q-3 -11 -79 -225.5t-78 -221.5l300 1q24 0 32.5 -17.5t-5.5 -35.5q-1 0 -133.5 -155t-267 -312.5t-138.5 -162.5q-12 -15 -26 -15h-9l-9 8q-9 11 -4 32q2 9 42 123.5t79 224.5l39 110h-302q-23 0 -31 19q-10 21 6 41q75 86 209.5 237.5 t228 257t98.5 111.5q9 16 25 16h9z" /> +<glyph unicode="" d="M350 1100h400q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-450q-41 0 -70.5 -29.5t-29.5 -70.5v-500q0 -41 29.5 -70.5t70.5 -29.5h450q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400 q0 165 92.5 257.5t257.5 92.5zM938 867l324 -284q16 -14 16 -33t-16 -33l-324 -284q-16 -14 -27 -9t-11 26v150h-250q-21 0 -35.5 14.5t-14.5 35.5v200q0 21 14.5 35.5t35.5 14.5h250v150q0 21 11 26t27 -9z" /> +<glyph unicode="" d="M750 1200h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -10.5 -25t-24.5 10l-109 109l-312 -312q-15 -15 -35.5 -15t-35.5 15l-141 141q-15 15 -15 35.5t15 35.5l312 312l-109 109q-14 14 -10 24.5t25 10.5zM456 900h-156q-41 0 -70.5 -29.5t-29.5 -70.5v-500 q0 -41 29.5 -70.5t70.5 -29.5h500q41 0 70.5 29.5t29.5 70.5v148l200 200v-298q0 -165 -93.5 -257.5t-256.5 -92.5h-400q-165 0 -257.5 92.5t-92.5 257.5v400q0 165 92.5 257.5t257.5 92.5h300z" /> +<glyph unicode="" d="M600 1186q119 0 227.5 -46.5t187 -125t125 -187t46.5 -227.5t-46.5 -227.5t-125 -187t-187 -125t-227.5 -46.5t-227.5 46.5t-187 125t-125 187t-46.5 227.5t46.5 227.5t125 187t187 125t227.5 46.5zM600 1022q-115 0 -212 -56.5t-153.5 -153.5t-56.5 -212t56.5 -212 t153.5 -153.5t212 -56.5t212 56.5t153.5 153.5t56.5 212t-56.5 212t-153.5 153.5t-212 56.5zM600 794q80 0 137 -57t57 -137t-57 -137t-137 -57t-137 57t-57 137t57 137t137 57z" /> +<glyph unicode="" d="M450 1200h200q21 0 35.5 -14.5t14.5 -35.5v-350h245q20 0 25 -11t-9 -26l-383 -426q-14 -15 -33.5 -15t-32.5 15l-379 426q-13 15 -8.5 26t25.5 11h250v350q0 21 14.5 35.5t35.5 14.5zM50 300h1000q21 0 35.5 -14.5t14.5 -35.5v-250h-1100v250q0 21 14.5 35.5t35.5 14.5z M900 200v-50h100v50h-100z" /> +<glyph unicode="" d="M583 1182l378 -435q14 -15 9 -31t-26 -16h-244v-250q0 -20 -17 -35t-39 -15h-200q-20 0 -32 14.5t-12 35.5v250h-250q-20 0 -25.5 16.5t8.5 31.5l383 431q14 16 33.5 17t33.5 -14zM50 300h1000q21 0 35.5 -14.5t14.5 -35.5v-250h-1100v250q0 21 14.5 35.5t35.5 14.5z M900 200v-50h100v50h-100z" /> +<glyph unicode="" d="M396 723l369 369q7 7 17.5 7t17.5 -7l139 -139q7 -8 7 -18.5t-7 -17.5l-525 -525q-7 -8 -17.5 -8t-17.5 8l-292 291q-7 8 -7 18t7 18l139 139q8 7 18.5 7t17.5 -7zM50 300h1000q21 0 35.5 -14.5t14.5 -35.5v-250h-1100v250q0 21 14.5 35.5t35.5 14.5zM900 200v-50h100v50 h-100z" /> +<glyph unicode="" d="M135 1023l142 142q14 14 35 14t35 -14l77 -77l-212 -212l-77 76q-14 15 -14 36t14 35zM655 855l210 210q14 14 24.5 10t10.5 -25l-2 -599q-1 -20 -15.5 -35t-35.5 -15l-597 -1q-21 0 -25 10.5t10 24.5l208 208l-154 155l212 212zM50 300h1000q21 0 35.5 -14.5t14.5 -35.5 v-250h-1100v250q0 21 14.5 35.5t35.5 14.5zM900 200v-50h100v50h-100z" /> +<glyph unicode="" d="M350 1200l599 -2q20 -1 35 -15.5t15 -35.5l1 -597q0 -21 -10.5 -25t-24.5 10l-208 208l-155 -154l-212 212l155 154l-210 210q-14 14 -10 24.5t25 10.5zM524 512l-76 -77q-15 -14 -36 -14t-35 14l-142 142q-14 14 -14 35t14 35l77 77zM50 300h1000q21 0 35.5 -14.5 t14.5 -35.5v-250h-1100v250q0 21 14.5 35.5t35.5 14.5zM900 200v-50h100v50h-100z" /> +<glyph unicode="" d="M1200 103l-483 276l-314 -399v423h-399l1196 796v-1096zM483 424v-230l683 953z" /> +<glyph unicode="" d="M1100 1000v-850q0 -21 -14.5 -35.5t-35.5 -14.5h-150v400h-700v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1000h-100v200h100v-200z" /> +<glyph unicode="" d="M1100 1000l-2 -149l-299 -299l-95 95q-9 9 -21.5 9t-21.5 -9l-149 -147h-312v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1000h-100v200h100v-200zM1132 638l106 -106q7 -7 7 -17.5t-7 -17.5l-420 -421q-8 -7 -18 -7 t-18 7l-202 203q-8 7 -8 17.5t8 17.5l106 106q7 8 17.5 8t17.5 -8l79 -79l297 297q7 7 17.5 7t17.5 -7z" /> +<glyph unicode="" d="M1100 1000v-269l-103 -103l-134 134q-15 15 -33.5 16.5t-34.5 -12.5l-266 -266h-329v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1000h-100v200h100v-200zM1202 572l70 -70q15 -15 15 -35.5t-15 -35.5l-131 -131 l131 -131q15 -15 15 -35.5t-15 -35.5l-70 -70q-15 -15 -35.5 -15t-35.5 15l-131 131l-131 -131q-15 -15 -35.5 -15t-35.5 15l-70 70q-15 15 -15 35.5t15 35.5l131 131l-131 131q-15 15 -15 35.5t15 35.5l70 70q15 15 35.5 15t35.5 -15l131 -131l131 131q15 15 35.5 15 t35.5 -15z" /> +<glyph unicode="" d="M1100 1000v-300h-350q-21 0 -35.5 -14.5t-14.5 -35.5v-150h-500v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1000h-100v200h100v-200zM850 600h100q21 0 35.5 -14.5t14.5 -35.5v-250h150q21 0 25 -10.5t-10 -24.5 l-230 -230q-14 -14 -35 -14t-35 14l-230 230q-14 14 -10 24.5t25 10.5h150v250q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M1100 1000v-400l-165 165q-14 15 -35 15t-35 -15l-263 -265h-402v-400h-150q-21 0 -35.5 14.5t-14.5 35.5v1000q0 20 14.5 35t35.5 15h250v-300h500v300h100zM700 1000h-100v200h100v-200zM935 565l230 -229q14 -15 10 -25.5t-25 -10.5h-150v-250q0 -20 -14.5 -35 t-35.5 -15h-100q-21 0 -35.5 15t-14.5 35v250h-150q-21 0 -25 10.5t10 25.5l230 229q14 15 35 15t35 -15z" /> +<glyph unicode="" d="M50 1100h1100q21 0 35.5 -14.5t14.5 -35.5v-150h-1200v150q0 21 14.5 35.5t35.5 14.5zM1200 800v-550q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v550h1200zM100 500v-200h400v200h-400z" /> +<glyph unicode="" d="M935 1165l248 -230q14 -14 14 -35t-14 -35l-248 -230q-14 -14 -24.5 -10t-10.5 25v150h-400v200h400v150q0 21 10.5 25t24.5 -10zM200 800h-50q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h50v-200zM400 800h-100v200h100v-200zM18 435l247 230 q14 14 24.5 10t10.5 -25v-150h400v-200h-400v-150q0 -21 -10.5 -25t-24.5 10l-247 230q-15 14 -15 35t15 35zM900 300h-100v200h100v-200zM1000 500h51q20 0 34.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-34.5 -14.5h-51v200z" /> +<glyph unicode="" d="M862 1073l276 116q25 18 43.5 8t18.5 -41v-1106q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v397q-4 1 -11 5t-24 17.5t-30 29t-24 42t-11 56.5v359q0 31 18.5 65t43.5 52zM550 1200q22 0 34.5 -12.5t14.5 -24.5l1 -13v-450q0 -28 -10.5 -59.5 t-25 -56t-29 -45t-25.5 -31.5l-10 -11v-447q0 -21 -14.5 -35.5t-35.5 -14.5h-200q-21 0 -35.5 14.5t-14.5 35.5v447q-4 4 -11 11.5t-24 30.5t-30 46t-24 55t-11 60v450q0 2 0.5 5.5t4 12t8.5 15t14.5 12t22.5 5.5q20 0 32.5 -12.5t14.5 -24.5l3 -13v-350h100v350v5.5t2.5 12 t7 15t15 12t25.5 5.5q23 0 35.5 -12.5t13.5 -24.5l1 -13v-350h100v350q0 2 0.5 5.5t3 12t7 15t15 12t24.5 5.5z" /> +<glyph unicode="" d="M1200 1100v-56q-4 0 -11 -0.5t-24 -3t-30 -7.5t-24 -15t-11 -24v-888q0 -22 25 -34.5t50 -13.5l25 -2v-56h-400v56q75 0 87.5 6.5t12.5 43.5v394h-500v-394q0 -37 12.5 -43.5t87.5 -6.5v-56h-400v56q4 0 11 0.5t24 3t30 7.5t24 15t11 24v888q0 22 -25 34.5t-50 13.5 l-25 2v56h400v-56q-75 0 -87.5 -6.5t-12.5 -43.5v-394h500v394q0 37 -12.5 43.5t-87.5 6.5v56h400z" /> +<glyph unicode="" d="M675 1000h375q21 0 35.5 -14.5t14.5 -35.5v-150h-105l-295 -98v98l-200 200h-400l100 100h375zM100 900h300q41 0 70.5 -29.5t29.5 -70.5v-500q0 -41 -29.5 -70.5t-70.5 -29.5h-300q-41 0 -70.5 29.5t-29.5 70.5v500q0 41 29.5 70.5t70.5 29.5zM100 800v-200h300v200 h-300zM1100 535l-400 -133v163l400 133v-163zM100 500v-200h300v200h-300zM1100 398v-248q0 -21 -14.5 -35.5t-35.5 -14.5h-375l-100 -100h-375l-100 100h400l200 200h105z" /> +<glyph unicode="" d="M17 1007l162 162q17 17 40 14t37 -22l139 -194q14 -20 11 -44.5t-20 -41.5l-119 -118q102 -142 228 -268t267 -227l119 118q17 17 42.5 19t44.5 -12l192 -136q19 -14 22.5 -37.5t-13.5 -40.5l-163 -162q-3 -1 -9.5 -1t-29.5 2t-47.5 6t-62.5 14.5t-77.5 26.5t-90 42.5 t-101.5 60t-111 83t-119 108.5q-74 74 -133.5 150.5t-94.5 138.5t-60 119.5t-34.5 100t-15 74.5t-4.5 48z" /> +<glyph unicode="" d="M600 1100q92 0 175 -10.5t141.5 -27t108.5 -36.5t81.5 -40t53.5 -37t31 -27l9 -10v-200q0 -21 -14.5 -33t-34.5 -9l-202 34q-20 3 -34.5 20t-14.5 38v146q-141 24 -300 24t-300 -24v-146q0 -21 -14.5 -38t-34.5 -20l-202 -34q-20 -3 -34.5 9t-14.5 33v200q3 4 9.5 10.5 t31 26t54 37.5t80.5 39.5t109 37.5t141 26.5t175 10.5zM600 795q56 0 97 -9.5t60 -23.5t30 -28t12 -24l1 -10v-50l365 -303q14 -15 24.5 -40t10.5 -45v-212q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v212q0 20 10.5 45t24.5 40l365 303v50 q0 4 1 10.5t12 23t30 29t60 22.5t97 10z" /> +<glyph unicode="" d="M1100 700l-200 -200h-600l-200 200v500h200v-200h200v200h200v-200h200v200h200v-500zM250 400h700q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-12l137 -100h-950l137 100h-12q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5 t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M700 1100h-100q-41 0 -70.5 -29.5t-29.5 -70.5v-1000h300v1000q0 41 -29.5 70.5t-70.5 29.5zM1100 800h-100q-41 0 -70.5 -29.5t-29.5 -70.5v-700h300v700q0 41 -29.5 70.5t-70.5 29.5zM400 0h-300v400q0 41 29.5 70.5t70.5 29.5h100q41 0 70.5 -29.5t29.5 -70.5v-400z " /> +<glyph unicode="" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 700h-200v-100h200v-300h-300v100h200v100h-200v300h300v-100zM900 700v-300l-100 -100h-200v500h200z M700 700v-300h100v300h-100z" /> +<glyph unicode="" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 300h-100v200h-100v-200h-100v500h100v-200h100v200h100v-500zM900 700v-300l-100 -100h-200v500h200z M700 700v-300h100v300h-100z" /> +<glyph unicode="" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 700h-200v-300h200v-100h-300v500h300v-100zM900 700h-200v-300h200v-100h-300v500h300v-100z" /> +<glyph unicode="" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 400l-300 150l300 150v-300zM900 550l-300 -150v300z" /> +<glyph unicode="" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM900 300h-700v500h700v-500zM800 700h-130q-38 0 -66.5 -43t-28.5 -108t27 -107t68 -42h130v300zM300 700v-300 h130q41 0 68 42t27 107t-28.5 108t-66.5 43h-130z" /> +<glyph unicode="" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 700h-200v-100h200v-300h-300v100h200v100h-200v300h300v-100zM900 300h-100v400h-100v100h200v-500z M700 300h-100v100h100v-100z" /> +<glyph unicode="" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM300 700h200v-400h-300v500h100v-100zM900 300h-100v400h-100v100h200v-500zM300 600v-200h100v200h-100z M700 300h-100v100h100v-100z" /> +<glyph unicode="" d="M200 1100h700q124 0 212 -88t88 -212v-500q0 -124 -88 -212t-212 -88h-700q-124 0 -212 88t-88 212v500q0 124 88 212t212 88zM100 900v-700h900v700h-900zM500 500l-199 -200h-100v50l199 200v150h-200v100h300v-300zM900 300h-100v400h-100v100h200v-500zM701 300h-100 v100h100v-100z" /> +<glyph unicode="" d="M600 1191q120 0 229.5 -47t188.5 -126t126 -188.5t47 -229.5t-47 -229.5t-126 -188.5t-188.5 -126t-229.5 -47t-229.5 47t-188.5 126t-126 188.5t-47 229.5t47 229.5t126 188.5t188.5 126t229.5 47zM600 1021q-114 0 -211 -56.5t-153.5 -153.5t-56.5 -211t56.5 -211 t153.5 -153.5t211 -56.5t211 56.5t153.5 153.5t56.5 211t-56.5 211t-153.5 153.5t-211 56.5zM800 700h-300v-200h300v-100h-300l-100 100v200l100 100h300v-100z" /> +<glyph unicode="" d="M600 1191q120 0 229.5 -47t188.5 -126t126 -188.5t47 -229.5t-47 -229.5t-126 -188.5t-188.5 -126t-229.5 -47t-229.5 47t-188.5 126t-126 188.5t-47 229.5t47 229.5t126 188.5t188.5 126t229.5 47zM600 1021q-114 0 -211 -56.5t-153.5 -153.5t-56.5 -211t56.5 -211 t153.5 -153.5t211 -56.5t211 56.5t153.5 153.5t56.5 211t-56.5 211t-153.5 153.5t-211 56.5zM800 700v-100l-50 -50l100 -100v-50h-100l-100 100h-150v-100h-100v400h300zM500 700v-100h200v100h-200z" /> +<glyph unicode="" d="M503 1089q110 0 200.5 -59.5t134.5 -156.5q44 14 90 14q120 0 205 -86.5t85 -207t-85 -207t-205 -86.5h-128v250q0 21 -14.5 35.5t-35.5 14.5h-300q-21 0 -35.5 -14.5t-14.5 -35.5v-250h-222q-80 0 -136 57.5t-56 136.5q0 69 43 122.5t108 67.5q-2 19 -2 37q0 100 49 185 t134 134t185 49zM525 500h150q10 0 17.5 -7.5t7.5 -17.5v-275h137q21 0 26 -11.5t-8 -27.5l-223 -244q-13 -16 -32 -16t-32 16l-223 244q-13 16 -8 27.5t26 11.5h137v275q0 10 7.5 17.5t17.5 7.5z" /> +<glyph unicode="" d="M502 1089q110 0 201 -59.5t135 -156.5q43 15 89 15q121 0 206 -86.5t86 -206.5q0 -99 -60 -181t-150 -110l-378 360q-13 16 -31.5 16t-31.5 -16l-381 -365h-9q-79 0 -135.5 57.5t-56.5 136.5q0 69 43 122.5t108 67.5q-2 19 -2 38q0 100 49 184.5t133.5 134t184.5 49.5z M632 467l223 -228q13 -16 8 -27.5t-26 -11.5h-137v-275q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v275h-137q-21 0 -26 11.5t8 27.5q199 204 223 228q19 19 31.5 19t32.5 -19z" /> +<glyph unicode="" d="M700 100v100h400l-270 300h170l-270 300h170l-300 333l-300 -333h170l-270 -300h170l-270 -300h400v-100h-50q-21 0 -35.5 -14.5t-14.5 -35.5v-50h400v50q0 21 -14.5 35.5t-35.5 14.5h-50z" /> +<glyph unicode="" d="M600 1179q94 0 167.5 -56.5t99.5 -145.5q89 -6 150.5 -71.5t61.5 -155.5q0 -61 -29.5 -112.5t-79.5 -82.5q9 -29 9 -55q0 -74 -52.5 -126.5t-126.5 -52.5q-55 0 -100 30v-251q21 0 35.5 -14.5t14.5 -35.5v-50h-300v50q0 21 14.5 35.5t35.5 14.5v251q-45 -30 -100 -30 q-74 0 -126.5 52.5t-52.5 126.5q0 18 4 38q-47 21 -75.5 65t-28.5 97q0 74 52.5 126.5t126.5 52.5q5 0 23 -2q0 2 -1 10t-1 13q0 116 81.5 197.5t197.5 81.5z" /> +<glyph unicode="" d="M1010 1010q111 -111 150.5 -260.5t0 -299t-150.5 -260.5q-83 -83 -191.5 -126.5t-218.5 -43.5t-218.5 43.5t-191.5 126.5q-111 111 -150.5 260.5t0 299t150.5 260.5q83 83 191.5 126.5t218.5 43.5t218.5 -43.5t191.5 -126.5zM476 1065q-4 0 -8 -1q-121 -34 -209.5 -122.5 t-122.5 -209.5q-4 -12 2.5 -23t18.5 -14l36 -9q3 -1 7 -1q23 0 29 22q27 96 98 166q70 71 166 98q11 3 17.5 13.5t3.5 22.5l-9 35q-3 13 -14 19q-7 4 -15 4zM512 920q-4 0 -9 -2q-80 -24 -138.5 -82.5t-82.5 -138.5q-4 -13 2 -24t19 -14l34 -9q4 -1 8 -1q22 0 28 21 q18 58 58.5 98.5t97.5 58.5q12 3 18 13.5t3 21.5l-9 35q-3 12 -14 19q-7 4 -15 4zM719.5 719.5q-49.5 49.5 -119.5 49.5t-119.5 -49.5t-49.5 -119.5t49.5 -119.5t119.5 -49.5t119.5 49.5t49.5 119.5t-49.5 119.5zM855 551q-22 0 -28 -21q-18 -58 -58.5 -98.5t-98.5 -57.5 q-11 -4 -17 -14.5t-3 -21.5l9 -35q3 -12 14 -19q7 -4 15 -4q4 0 9 2q80 24 138.5 82.5t82.5 138.5q4 13 -2.5 24t-18.5 14l-34 9q-4 1 -8 1zM1000 515q-23 0 -29 -22q-27 -96 -98 -166q-70 -71 -166 -98q-11 -3 -17.5 -13.5t-3.5 -22.5l9 -35q3 -13 14 -19q7 -4 15 -4 q4 0 8 1q121 34 209.5 122.5t122.5 209.5q4 12 -2.5 23t-18.5 14l-36 9q-3 1 -7 1z" /> +<glyph unicode="" d="M700 800h300v-380h-180v200h-340v-200h-380v755q0 10 7.5 17.5t17.5 7.5h575v-400zM1000 900h-200v200zM700 300h162l-212 -212l-212 212h162v200h100v-200zM520 0h-395q-10 0 -17.5 7.5t-7.5 17.5v395zM1000 220v-195q0 -10 -7.5 -17.5t-17.5 -7.5h-195z" /> +<glyph unicode="" d="M700 800h300v-520l-350 350l-550 -550v1095q0 10 7.5 17.5t17.5 7.5h575v-400zM1000 900h-200v200zM862 200h-162v-200h-100v200h-162l212 212zM480 0h-355q-10 0 -17.5 7.5t-7.5 17.5v55h380v-80zM1000 80v-55q0 -10 -7.5 -17.5t-17.5 -7.5h-155v80h180z" /> +<glyph unicode="" d="M1162 800h-162v-200h100l100 -100h-300v300h-162l212 212zM200 800h200q27 0 40 -2t29.5 -10.5t23.5 -30t7 -57.5h300v-100h-600l-200 -350v450h100q0 36 7 57.5t23.5 30t29.5 10.5t40 2zM800 400h240l-240 -400h-800l300 500h500v-100z" /> +<glyph unicode="" d="M650 1100h100q21 0 35.5 -14.5t14.5 -35.5v-50h50q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-300q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h50v50q0 21 14.5 35.5t35.5 14.5zM1000 850v150q41 0 70.5 -29.5t29.5 -70.5v-800 q0 -41 -29.5 -70.5t-70.5 -29.5h-600q-1 0 -20 4l246 246l-326 326v324q0 41 29.5 70.5t70.5 29.5v-150q0 -62 44 -106t106 -44h300q62 0 106 44t44 106zM412 250l-212 -212v162h-200v100h200v162z" /> +<glyph unicode="" d="M450 1100h100q21 0 35.5 -14.5t14.5 -35.5v-50h50q21 0 35.5 -14.5t14.5 -35.5v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-300q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h50v50q0 21 14.5 35.5t35.5 14.5zM800 850v150q41 0 70.5 -29.5t29.5 -70.5v-500 h-200v-300h200q0 -36 -7 -57.5t-23.5 -30t-29.5 -10.5t-40 -2h-600q-41 0 -70.5 29.5t-29.5 70.5v800q0 41 29.5 70.5t70.5 29.5v-150q0 -62 44 -106t106 -44h300q62 0 106 44t44 106zM1212 250l-212 -212v162h-200v100h200v162z" /> +<glyph unicode="" d="M658 1197l637 -1104q23 -38 7 -65.5t-60 -27.5h-1276q-44 0 -60 27.5t7 65.5l637 1104q22 39 54 39t54 -39zM704 800h-208q-20 0 -32 -14.5t-8 -34.5l58 -302q4 -20 21.5 -34.5t37.5 -14.5h54q20 0 37.5 14.5t21.5 34.5l58 302q4 20 -8 34.5t-32 14.5zM500 300v-100h200 v100h-200z" /> +<glyph unicode="" d="M425 1100h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM425 800h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5 t17.5 7.5zM825 800h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM25 500h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150 q0 10 7.5 17.5t17.5 7.5zM425 500h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM825 500h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5 v150q0 10 7.5 17.5t17.5 7.5zM25 200h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM425 200h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5 t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM825 200h250q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-250q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5z" /> +<glyph unicode="" d="M700 1200h100v-200h-100v-100h350q62 0 86.5 -39.5t-3.5 -94.5l-66 -132q-41 -83 -81 -134h-772q-40 51 -81 134l-66 132q-28 55 -3.5 94.5t86.5 39.5h350v100h-100v200h100v100h200v-100zM250 400h700q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-12l137 -100 h-950l138 100h-13q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M600 1300q40 0 68.5 -29.5t28.5 -70.5h-194q0 41 28.5 70.5t68.5 29.5zM443 1100h314q18 -37 18 -75q0 -8 -3 -25h328q41 0 44.5 -16.5t-30.5 -38.5l-175 -145h-678l-178 145q-34 22 -29 38.5t46 16.5h328q-3 17 -3 25q0 38 18 75zM250 700h700q21 0 35.5 -14.5 t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-150v-200l275 -200h-950l275 200v200h-150q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M600 1181q75 0 128 -53t53 -128t-53 -128t-128 -53t-128 53t-53 128t53 128t128 53zM602 798h46q34 0 55.5 -28.5t21.5 -86.5q0 -76 39 -183h-324q39 107 39 183q0 58 21.5 86.5t56.5 28.5h45zM250 400h700q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-13 l138 -100h-950l137 100h-12q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M600 1300q47 0 92.5 -53.5t71 -123t25.5 -123.5q0 -78 -55.5 -133.5t-133.5 -55.5t-133.5 55.5t-55.5 133.5q0 62 34 143l144 -143l111 111l-163 163q34 26 63 26zM602 798h46q34 0 55.5 -28.5t21.5 -86.5q0 -76 39 -183h-324q39 107 39 183q0 58 21.5 86.5t56.5 28.5h45 zM250 400h700q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-13l138 -100h-950l137 100h-12q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M600 1200l300 -161v-139h-300q0 -57 18.5 -108t50 -91.5t63 -72t70 -67.5t57.5 -61h-530q-60 83 -90.5 177.5t-30.5 178.5t33 164.5t87.5 139.5t126 96.5t145.5 41.5v-98zM250 400h700q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-13l138 -100h-950l137 100 h-12q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5zM50 100h1100q21 0 35.5 -14.5t14.5 -35.5v-50h-1200v50q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M600 1300q41 0 70.5 -29.5t29.5 -70.5v-78q46 -26 73 -72t27 -100v-50h-400v50q0 54 27 100t73 72v78q0 41 29.5 70.5t70.5 29.5zM400 800h400q54 0 100 -27t72 -73h-172v-100h200v-100h-200v-100h200v-100h-200v-100h200q0 -83 -58.5 -141.5t-141.5 -58.5h-400 q-83 0 -141.5 58.5t-58.5 141.5v400q0 83 58.5 141.5t141.5 58.5z" /> +<glyph unicode="" d="M150 1100h900q21 0 35.5 -14.5t14.5 -35.5v-500q0 -21 -14.5 -35.5t-35.5 -14.5h-900q-21 0 -35.5 14.5t-14.5 35.5v500q0 21 14.5 35.5t35.5 14.5zM125 400h950q10 0 17.5 -7.5t7.5 -17.5v-50q0 -10 -7.5 -17.5t-17.5 -7.5h-283l224 -224q13 -13 13 -31.5t-13 -32 t-31.5 -13.5t-31.5 13l-88 88h-524l-87 -88q-13 -13 -32 -13t-32 13.5t-13 32t13 31.5l224 224h-289q-10 0 -17.5 7.5t-7.5 17.5v50q0 10 7.5 17.5t17.5 7.5zM541 300l-100 -100h324l-100 100h-124z" /> +<glyph unicode="" d="M200 1100h800q83 0 141.5 -58.5t58.5 -141.5v-200h-100q0 41 -29.5 70.5t-70.5 29.5h-250q-41 0 -70.5 -29.5t-29.5 -70.5h-100q0 41 -29.5 70.5t-70.5 29.5h-250q-41 0 -70.5 -29.5t-29.5 -70.5h-100v200q0 83 58.5 141.5t141.5 58.5zM100 600h1000q41 0 70.5 -29.5 t29.5 -70.5v-300h-1200v300q0 41 29.5 70.5t70.5 29.5zM300 100v-50q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v50h200zM1100 100v-50q0 -21 -14.5 -35.5t-35.5 -14.5h-100q-21 0 -35.5 14.5t-14.5 35.5v50h200z" /> +<glyph unicode="" d="M480 1165l682 -683q31 -31 31 -75.5t-31 -75.5l-131 -131h-481l-517 518q-32 31 -32 75.5t32 75.5l295 296q31 31 75.5 31t76.5 -31zM108 794l342 -342l303 304l-341 341zM250 100h800q21 0 35.5 -14.5t14.5 -35.5v-50h-900v50q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M1057 647l-189 506q-8 19 -27.5 33t-40.5 14h-400q-21 0 -40.5 -14t-27.5 -33l-189 -506q-8 -19 1.5 -33t30.5 -14h625v-150q0 -21 14.5 -35.5t35.5 -14.5t35.5 14.5t14.5 35.5v150h125q21 0 30.5 14t1.5 33zM897 0h-595v50q0 21 14.5 35.5t35.5 14.5h50v50 q0 21 14.5 35.5t35.5 14.5h48v300h200v-300h47q21 0 35.5 -14.5t14.5 -35.5v-50h50q21 0 35.5 -14.5t14.5 -35.5v-50z" /> +<glyph unicode="" d="M900 800h300v-575q0 -10 -7.5 -17.5t-17.5 -7.5h-375v591l-300 300v84q0 10 7.5 17.5t17.5 7.5h375v-400zM1200 900h-200v200zM400 600h300v-575q0 -10 -7.5 -17.5t-17.5 -7.5h-650q-10 0 -17.5 7.5t-7.5 17.5v950q0 10 7.5 17.5t17.5 7.5h375v-400zM700 700h-200v200z " /> +<glyph unicode="" d="M484 1095h195q75 0 146 -32.5t124 -86t89.5 -122.5t48.5 -142q18 -14 35 -20q31 -10 64.5 6.5t43.5 48.5q10 34 -15 71q-19 27 -9 43q5 8 12.5 11t19 -1t23.5 -16q41 -44 39 -105q-3 -63 -46 -106.5t-104 -43.5h-62q-7 -55 -35 -117t-56 -100l-39 -234q-3 -20 -20 -34.5 t-38 -14.5h-100q-21 0 -33 14.5t-9 34.5l12 70q-49 -14 -91 -14h-195q-24 0 -65 8l-11 -64q-3 -20 -20 -34.5t-38 -14.5h-100q-21 0 -33 14.5t-9 34.5l26 157q-84 74 -128 175l-159 53q-19 7 -33 26t-14 40v50q0 21 14.5 35.5t35.5 14.5h124q11 87 56 166l-111 95 q-16 14 -12.5 23.5t24.5 9.5h203q116 101 250 101zM675 1000h-250q-10 0 -17.5 -7.5t-7.5 -17.5v-50q0 -10 7.5 -17.5t17.5 -7.5h250q10 0 17.5 7.5t7.5 17.5v50q0 10 -7.5 17.5t-17.5 7.5z" /> +<glyph unicode="" d="M641 900l423 247q19 8 42 2.5t37 -21.5l32 -38q14 -15 12.5 -36t-17.5 -34l-139 -120h-390zM50 1100h106q67 0 103 -17t66 -71l102 -212h823q21 0 35.5 -14.5t14.5 -35.5v-50q0 -21 -14 -40t-33 -26l-737 -132q-23 -4 -40 6t-26 25q-42 67 -100 67h-300q-62 0 -106 44 t-44 106v200q0 62 44 106t106 44zM173 928h-80q-19 0 -28 -14t-9 -35v-56q0 -51 42 -51h134q16 0 21.5 8t5.5 24q0 11 -16 45t-27 51q-18 28 -43 28zM550 727q-32 0 -54.5 -22.5t-22.5 -54.5t22.5 -54.5t54.5 -22.5t54.5 22.5t22.5 54.5t-22.5 54.5t-54.5 22.5zM130 389 l152 130q18 19 34 24t31 -3.5t24.5 -17.5t25.5 -28q28 -35 50.5 -51t48.5 -13l63 5l48 -179q13 -61 -3.5 -97.5t-67.5 -79.5l-80 -69q-47 -40 -109 -35.5t-103 51.5l-130 151q-40 47 -35.5 109.5t51.5 102.5zM380 377l-102 -88q-31 -27 2 -65l37 -43q13 -15 27.5 -19.5 t31.5 6.5l61 53q19 16 14 49q-2 20 -12 56t-17 45q-11 12 -19 14t-23 -8z" /> +<glyph unicode="" d="M625 1200h150q10 0 17.5 -7.5t7.5 -17.5v-109q79 -33 131 -87.5t53 -128.5q1 -46 -15 -84.5t-39 -61t-46 -38t-39 -21.5l-17 -6q6 0 15 -1.5t35 -9t50 -17.5t53 -30t50 -45t35.5 -64t14.5 -84q0 -59 -11.5 -105.5t-28.5 -76.5t-44 -51t-49.5 -31.5t-54.5 -16t-49.5 -6.5 t-43.5 -1v-75q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v75h-100v-75q0 -10 -7.5 -17.5t-17.5 -7.5h-150q-10 0 -17.5 7.5t-7.5 17.5v75h-175q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5h75v600h-75q-10 0 -17.5 7.5t-7.5 17.5v150 q0 10 7.5 17.5t17.5 7.5h175v75q0 10 7.5 17.5t17.5 7.5h150q10 0 17.5 -7.5t7.5 -17.5v-75h100v75q0 10 7.5 17.5t17.5 7.5zM400 900v-200h263q28 0 48.5 10.5t30 25t15 29t5.5 25.5l1 10q0 4 -0.5 11t-6 24t-15 30t-30 24t-48.5 11h-263zM400 500v-200h363q28 0 48.5 10.5 t30 25t15 29t5.5 25.5l1 10q0 4 -0.5 11t-6 24t-15 30t-30 24t-48.5 11h-363z" /> +<glyph unicode="" d="M212 1198h780q86 0 147 -61t61 -147v-416q0 -51 -18 -142.5t-36 -157.5l-18 -66q-29 -87 -93.5 -146.5t-146.5 -59.5h-572q-82 0 -147 59t-93 147q-8 28 -20 73t-32 143.5t-20 149.5v416q0 86 61 147t147 61zM600 1045q-70 0 -132.5 -11.5t-105.5 -30.5t-78.5 -41.5 t-57 -45t-36 -41t-20.5 -30.5l-6 -12l156 -243h560l156 243q-2 5 -6 12.5t-20 29.5t-36.5 42t-57 44.5t-79 42t-105 29.5t-132.5 12zM762 703h-157l195 261z" /> +<glyph unicode="" d="M475 1300h150q103 0 189 -86t86 -189v-500q0 -41 -42 -83t-83 -42h-450q-41 0 -83 42t-42 83v500q0 103 86 189t189 86zM700 300v-225q0 -21 -27 -48t-48 -27h-150q-21 0 -48 27t-27 48v225h300z" /> +<glyph unicode="" d="M475 1300h96q0 -150 89.5 -239.5t239.5 -89.5v-446q0 -41 -42 -83t-83 -42h-450q-41 0 -83 42t-42 83v500q0 103 86 189t189 86zM700 300v-225q0 -21 -27 -48t-48 -27h-150q-21 0 -48 27t-27 48v225h300z" /> +<glyph unicode="" d="M1294 767l-638 -283l-378 170l-78 -60v-224l100 -150v-199l-150 148l-150 -149v200l100 150v250q0 4 -0.5 10.5t0 9.5t1 8t3 8t6.5 6l47 40l-147 65l642 283zM1000 380l-350 -166l-350 166v147l350 -165l350 165v-147z" /> +<glyph unicode="" d="M250 800q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44zM650 800q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44zM1050 800q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44z" /> +<glyph unicode="" d="M550 1100q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44zM550 700q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44zM550 300q62 0 106 -44t44 -106t-44 -106t-106 -44t-106 44t-44 106t44 106t106 44z" /> +<glyph unicode="" d="M125 1100h950q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-950q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5zM125 700h950q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-950q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5 t17.5 7.5zM125 300h950q10 0 17.5 -7.5t7.5 -17.5v-150q0 -10 -7.5 -17.5t-17.5 -7.5h-950q-10 0 -17.5 7.5t-7.5 17.5v150q0 10 7.5 17.5t17.5 7.5z" /> +<glyph unicode="" d="M350 1200h500q162 0 256 -93.5t94 -256.5v-500q0 -165 -93.5 -257.5t-256.5 -92.5h-500q-165 0 -257.5 92.5t-92.5 257.5v500q0 165 92.5 257.5t257.5 92.5zM900 1000h-600q-41 0 -70.5 -29.5t-29.5 -70.5v-600q0 -41 29.5 -70.5t70.5 -29.5h600q41 0 70.5 29.5 t29.5 70.5v600q0 41 -29.5 70.5t-70.5 29.5zM350 900h500q21 0 35.5 -14.5t14.5 -35.5v-300q0 -21 -14.5 -35.5t-35.5 -14.5h-500q-21 0 -35.5 14.5t-14.5 35.5v300q0 21 14.5 35.5t35.5 14.5zM400 800v-200h400v200h-400z" /> +<glyph unicode="" d="M150 1100h1000q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-50v-200h50q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-50v-200h50q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5t-35.5 -14.5h-50v-200h50q21 0 35.5 -14.5t14.5 -35.5t-14.5 -35.5 t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5h50v200h-50q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5h50v200h-50q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5h50v200h-50q-21 0 -35.5 14.5t-14.5 35.5t14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M650 1187q87 -67 118.5 -156t0 -178t-118.5 -155q-87 66 -118.5 155t0 178t118.5 156zM300 800q124 0 212 -88t88 -212q-124 0 -212 88t-88 212zM1000 800q0 -124 -88 -212t-212 -88q0 124 88 212t212 88zM300 500q124 0 212 -88t88 -212q-124 0 -212 88t-88 212z M1000 500q0 -124 -88 -212t-212 -88q0 124 88 212t212 88zM700 199v-144q0 -21 -14.5 -35.5t-35.5 -14.5t-35.5 14.5t-14.5 35.5v142q40 -4 43 -4q17 0 57 6z" /> +<glyph unicode="" d="M745 878l69 19q25 6 45 -12l298 -295q11 -11 15 -26.5t-2 -30.5q-5 -14 -18 -23.5t-28 -9.5h-8q1 0 1 -13q0 -29 -2 -56t-8.5 -62t-20 -63t-33 -53t-51 -39t-72.5 -14h-146q-184 0 -184 288q0 24 10 47q-20 4 -62 4t-63 -4q11 -24 11 -47q0 -288 -184 -288h-142 q-48 0 -84.5 21t-56 51t-32 71.5t-16 75t-3.5 68.5q0 13 2 13h-7q-15 0 -27.5 9.5t-18.5 23.5q-6 15 -2 30.5t15 25.5l298 296q20 18 46 11l76 -19q20 -5 30.5 -22.5t5.5 -37.5t-22.5 -31t-37.5 -5l-51 12l-182 -193h891l-182 193l-44 -12q-20 -5 -37.5 6t-22.5 31t6 37.5 t31 22.5z" /> +<glyph unicode="" d="M1200 900h-50q0 21 -4 37t-9.5 26.5t-18 17.5t-22 11t-28.5 5.5t-31 2t-37 0.5h-200v-850q0 -22 25 -34.5t50 -13.5l25 -2v-100h-400v100q4 0 11 0.5t24 3t30 7t24 15t11 24.5v850h-200q-25 0 -37 -0.5t-31 -2t-28.5 -5.5t-22 -11t-18 -17.5t-9.5 -26.5t-4 -37h-50v300 h1000v-300zM500 450h-25q0 15 -4 24.5t-9 14.5t-17 7.5t-20 3t-25 0.5h-100v-425q0 -11 12.5 -17.5t25.5 -7.5h12v-50h-200v50q50 0 50 25v425h-100q-17 0 -25 -0.5t-20 -3t-17 -7.5t-9 -14.5t-4 -24.5h-25v150h500v-150z" /> +<glyph unicode="" d="M1000 300v50q-25 0 -55 32q-14 14 -25 31t-16 27l-4 11l-289 747h-69l-300 -754q-18 -35 -39 -56q-9 -9 -24.5 -18.5t-26.5 -14.5l-11 -5v-50h273v50q-49 0 -78.5 21.5t-11.5 67.5l69 176h293l61 -166q13 -34 -3.5 -66.5t-55.5 -32.5v-50h312zM412 691l134 342l121 -342 h-255zM1100 150v-100q0 -21 -14.5 -35.5t-35.5 -14.5h-1000q-21 0 -35.5 14.5t-14.5 35.5v100q0 21 14.5 35.5t35.5 14.5h1000q21 0 35.5 -14.5t14.5 -35.5z" /> +<glyph unicode="" d="M50 1200h1100q21 0 35.5 -14.5t14.5 -35.5v-1100q0 -21 -14.5 -35.5t-35.5 -14.5h-1100q-21 0 -35.5 14.5t-14.5 35.5v1100q0 21 14.5 35.5t35.5 14.5zM611 1118h-70q-13 0 -18 -12l-299 -753q-17 -32 -35 -51q-18 -18 -56 -34q-12 -5 -12 -18v-50q0 -8 5.5 -14t14.5 -6 h273q8 0 14 6t6 14v50q0 8 -6 14t-14 6q-55 0 -71 23q-10 14 0 39l63 163h266l57 -153q11 -31 -6 -55q-12 -17 -36 -17q-8 0 -14 -6t-6 -14v-50q0 -8 6 -14t14 -6h313q8 0 14 6t6 14v50q0 7 -5.5 13t-13.5 7q-17 0 -42 25q-25 27 -40 63h-1l-288 748q-5 12 -19 12zM639 611 h-197l103 264z" /> +<glyph unicode="" d="M1200 1100h-1200v100h1200v-100zM50 1000h400q21 0 35.5 -14.5t14.5 -35.5v-900q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v900q0 21 14.5 35.5t35.5 14.5zM650 1000h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400 q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM700 900v-300h300v300h-300z" /> +<glyph unicode="" d="M50 1200h400q21 0 35.5 -14.5t14.5 -35.5v-900q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v900q0 21 14.5 35.5t35.5 14.5zM650 700h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400 q0 21 14.5 35.5t35.5 14.5zM700 600v-300h300v300h-300zM1200 0h-1200v100h1200v-100z" /> +<glyph unicode="" d="M50 1000h400q21 0 35.5 -14.5t14.5 -35.5v-350h100v150q0 21 14.5 35.5t35.5 14.5h400q21 0 35.5 -14.5t14.5 -35.5v-150h100v-100h-100v-150q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v150h-100v-350q0 -21 -14.5 -35.5t-35.5 -14.5h-400 q-21 0 -35.5 14.5t-14.5 35.5v800q0 21 14.5 35.5t35.5 14.5zM700 700v-300h300v300h-300z" /> +<glyph unicode="" d="M100 0h-100v1200h100v-1200zM250 1100h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM300 1000v-300h300v300h-300zM250 500h900q21 0 35.5 -14.5t14.5 -35.5v-400 q0 -21 -14.5 -35.5t-35.5 -14.5h-900q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M600 1100h150q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-150v-100h450q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-900q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5h350v100h-150q-21 0 -35.5 14.5 t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5h150v100h100v-100zM400 1000v-300h300v300h-300z" /> +<glyph unicode="" d="M1200 0h-100v1200h100v-1200zM550 1100h400q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-400q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM600 1000v-300h300v300h-300zM50 500h900q21 0 35.5 -14.5t14.5 -35.5v-400 q0 -21 -14.5 -35.5t-35.5 -14.5h-900q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5z" /> +<glyph unicode="" d="M865 565l-494 -494q-23 -23 -41 -23q-14 0 -22 13.5t-8 38.5v1000q0 25 8 38.5t22 13.5q18 0 41 -23l494 -494q14 -14 14 -35t-14 -35z" /> +<glyph unicode="" d="M335 635l494 494q29 29 50 20.5t21 -49.5v-1000q0 -41 -21 -49.5t-50 20.5l-494 494q-14 14 -14 35t14 35z" /> +<glyph unicode="" d="M100 900h1000q41 0 49.5 -21t-20.5 -50l-494 -494q-14 -14 -35 -14t-35 14l-494 494q-29 29 -20.5 50t49.5 21z" /> +<glyph unicode="" d="M635 865l494 -494q29 -29 20.5 -50t-49.5 -21h-1000q-41 0 -49.5 21t20.5 50l494 494q14 14 35 14t35 -14z" /> +<glyph unicode="" d="M700 741v-182l-692 -323v221l413 193l-413 193v221zM1200 0h-800v200h800v-200z" /> +<glyph unicode="" d="M1200 900h-200v-100h200v-100h-300v300h200v100h-200v100h300v-300zM0 700h50q0 21 4 37t9.5 26.5t18 17.5t22 11t28.5 5.5t31 2t37 0.5h100v-550q0 -22 -25 -34.5t-50 -13.5l-25 -2v-100h400v100q-4 0 -11 0.5t-24 3t-30 7t-24 15t-11 24.5v550h100q25 0 37 -0.5t31 -2 t28.5 -5.5t22 -11t18 -17.5t9.5 -26.5t4 -37h50v300h-800v-300z" /> +<glyph unicode="" d="M800 700h-50q0 21 -4 37t-9.5 26.5t-18 17.5t-22 11t-28.5 5.5t-31 2t-37 0.5h-100v-550q0 -22 25 -34.5t50 -14.5l25 -1v-100h-400v100q4 0 11 0.5t24 3t30 7t24 15t11 24.5v550h-100q-25 0 -37 -0.5t-31 -2t-28.5 -5.5t-22 -11t-18 -17.5t-9.5 -26.5t-4 -37h-50v300 h800v-300zM1100 200h-200v-100h200v-100h-300v300h200v100h-200v100h300v-300z" /> +<glyph unicode="" d="M701 1098h160q16 0 21 -11t-7 -23l-464 -464l464 -464q12 -12 7 -23t-21 -11h-160q-13 0 -23 9l-471 471q-7 8 -7 18t7 18l471 471q10 9 23 9z" /> +<glyph unicode="" d="M339 1098h160q13 0 23 -9l471 -471q7 -8 7 -18t-7 -18l-471 -471q-10 -9 -23 -9h-160q-16 0 -21 11t7 23l464 464l-464 464q-12 12 -7 23t21 11z" /> +<glyph unicode="" d="M1087 882q11 -5 11 -21v-160q0 -13 -9 -23l-471 -471q-8 -7 -18 -7t-18 7l-471 471q-9 10 -9 23v160q0 16 11 21t23 -7l464 -464l464 464q12 12 23 7z" /> +<glyph unicode="" d="M618 993l471 -471q9 -10 9 -23v-160q0 -16 -11 -21t-23 7l-464 464l-464 -464q-12 -12 -23 -7t-11 21v160q0 13 9 23l471 471q8 7 18 7t18 -7z" /> +<glyph unicode="" d="M1000 1200q0 -124 -88 -212t-212 -88q0 124 88 212t212 88zM450 1000h100q21 0 40 -14t26 -33l79 -194q5 1 16 3q34 6 54 9.5t60 7t65.5 1t61 -10t56.5 -23t42.5 -42t29 -64t5 -92t-19.5 -121.5q-1 -7 -3 -19.5t-11 -50t-20.5 -73t-32.5 -81.5t-46.5 -83t-64 -70 t-82.5 -50q-13 -5 -42 -5t-65.5 2.5t-47.5 2.5q-14 0 -49.5 -3.5t-63 -3.5t-43.5 7q-57 25 -104.5 78.5t-75 111.5t-46.5 112t-26 90l-7 35q-15 63 -18 115t4.5 88.5t26 64t39.5 43.5t52 25.5t58.5 13t62.5 2t59.5 -4.5t55.5 -8l-147 192q-12 18 -5.5 30t27.5 12z" /> +<glyph unicode="🔑" d="M250 1200h600q21 0 35.5 -14.5t14.5 -35.5v-400q0 -21 -14.5 -35.5t-35.5 -14.5h-150v-500l-255 -178q-19 -9 -32 -1t-13 29v650h-150q-21 0 -35.5 14.5t-14.5 35.5v400q0 21 14.5 35.5t35.5 14.5zM400 1100v-100h300v100h-300z" /> +<glyph unicode="🚪" d="M250 1200h750q39 0 69.5 -40.5t30.5 -84.5v-933l-700 -117v950l600 125h-700v-1000h-100v1025q0 23 15.5 49t34.5 26zM500 525v-100l100 20v100z" /> +</font> +</defs></svg>
\ No newline at end of file diff --git a/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.ttf b/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.ttf Binary files differnew file mode 100644 index 000000000..1413fc609 --- /dev/null +++ b/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.ttf diff --git a/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.woff b/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.woff Binary files differnew file mode 100644 index 000000000..9e612858f --- /dev/null +++ b/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.woff diff --git a/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.woff2 b/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.woff2 Binary files differnew file mode 100644 index 000000000..64539b54c --- /dev/null +++ b/pyload/webui/themes/Next/lib/Bootstrap/fonts/glyphicons-halflings-regular.woff2 diff --git a/pyload/webui/themes/Next/lib/Bootstrap/js/bootstrap.js b/pyload/webui/themes/Next/lib/Bootstrap/js/bootstrap.js new file mode 100644 index 000000000..4139b6fc3 --- /dev/null +++ b/pyload/webui/themes/Next/lib/Bootstrap/js/bootstrap.js @@ -0,0 +1,2306 @@ +/*! + * Bootstrap v3.3.2 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ + +if (typeof jQuery === 'undefined') { + throw new Error('Bootstrap\'s JavaScript requires jQuery') +} + ++function ($) { + 'use strict'; + var version = $.fn.jquery.split(' ')[0].split('.') + if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1)) { + throw new Error('Bootstrap\'s JavaScript requires jQuery version 1.9.1 or higher') + } +}(jQuery); + +/* ======================================================================== + * Bootstrap: transition.js v3.3.2 + * http://getbootstrap.com/javascript/#transitions + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // CSS TRANSITION SUPPORT (Shoutout: http://www.modernizr.com/) + // ============================================================ + + function transitionEnd() { + var el = document.createElement('bootstrap') + + var transEndEventNames = { + WebkitTransition : 'webkitTransitionEnd', + MozTransition : 'transitionend', + OTransition : 'oTransitionEnd otransitionend', + transition : 'transitionend' + } + + for (var name in transEndEventNames) { + if (el.style[name] !== undefined) { + return { end: transEndEventNames[name] } + } + } + + return false // explicit for ie8 ( ._.) + } + + // http://blog.alexmaccaw.com/css-transitions + $.fn.emulateTransitionEnd = function (duration) { + var called = false + var $el = this + $(this).one('bsTransitionEnd', function () { called = true }) + var callback = function () { if (!called) $($el).trigger($.support.transition.end) } + setTimeout(callback, duration) + return this + } + + $(function () { + $.support.transition = transitionEnd() + + if (!$.support.transition) return + + $.event.special.bsTransitionEnd = { + bindType: $.support.transition.end, + delegateType: $.support.transition.end, + handle: function (e) { + if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments) + } + } + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: alert.js v3.3.2 + * http://getbootstrap.com/javascript/#alerts + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // ALERT CLASS DEFINITION + // ====================== + + var dismiss = '[data-dismiss="alert"]' + var Alert = function (el) { + $(el).on('click', dismiss, this.close) + } + + Alert.VERSION = '3.3.2' + + Alert.TRANSITION_DURATION = 150 + + Alert.prototype.close = function (e) { + var $this = $(this) + var selector = $this.attr('data-target') + + if (!selector) { + selector = $this.attr('href') + selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 + } + + var $parent = $(selector) + + if (e) e.preventDefault() + + if (!$parent.length) { + $parent = $this.closest('.alert') + } + + $parent.trigger(e = $.Event('close.bs.alert')) + + if (e.isDefaultPrevented()) return + + $parent.removeClass('in') + + function removeElement() { + // detach from parent, fire event then clean up data + $parent.detach().trigger('closed.bs.alert').remove() + } + + $.support.transition && $parent.hasClass('fade') ? + $parent + .one('bsTransitionEnd', removeElement) + .emulateTransitionEnd(Alert.TRANSITION_DURATION) : + removeElement() + } + + + // ALERT PLUGIN DEFINITION + // ======================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.alert') + + if (!data) $this.data('bs.alert', (data = new Alert(this))) + if (typeof option == 'string') data[option].call($this) + }) + } + + var old = $.fn.alert + + $.fn.alert = Plugin + $.fn.alert.Constructor = Alert + + + // ALERT NO CONFLICT + // ================= + + $.fn.alert.noConflict = function () { + $.fn.alert = old + return this + } + + + // ALERT DATA-API + // ============== + + $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: button.js v3.3.2 + * http://getbootstrap.com/javascript/#buttons + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // BUTTON PUBLIC CLASS DEFINITION + // ============================== + + var Button = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, Button.DEFAULTS, options) + this.isLoading = false + } + + Button.VERSION = '3.3.2' + + Button.DEFAULTS = { + loadingText: 'loading...' + } + + Button.prototype.setState = function (state) { + var d = 'disabled' + var $el = this.$element + var val = $el.is('input') ? 'val' : 'html' + var data = $el.data() + + state = state + 'Text' + + if (data.resetText == null) $el.data('resetText', $el[val]()) + + // push to event loop to allow forms to submit + setTimeout($.proxy(function () { + $el[val](data[state] == null ? this.options[state] : data[state]) + + if (state == 'loadingText') { + this.isLoading = true + $el.addClass(d).attr(d, d) + } else if (this.isLoading) { + this.isLoading = false + $el.removeClass(d).removeAttr(d) + } + }, this), 0) + } + + Button.prototype.toggle = function () { + var changed = true + var $parent = this.$element.closest('[data-toggle="buttons"]') + + if ($parent.length) { + var $input = this.$element.find('input') + if ($input.prop('type') == 'radio') { + if ($input.prop('checked') && this.$element.hasClass('active')) changed = false + else $parent.find('.active').removeClass('active') + } + if (changed) $input.prop('checked', !this.$element.hasClass('active')).trigger('change') + } else { + this.$element.attr('aria-pressed', !this.$element.hasClass('active')) + } + + if (changed) this.$element.toggleClass('active') + } + + + // BUTTON PLUGIN DEFINITION + // ======================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.button') + var options = typeof option == 'object' && option + + if (!data) $this.data('bs.button', (data = new Button(this, options))) + + if (option == 'toggle') data.toggle() + else if (option) data.setState(option) + }) + } + + var old = $.fn.button + + $.fn.button = Plugin + $.fn.button.Constructor = Button + + + // BUTTON NO CONFLICT + // ================== + + $.fn.button.noConflict = function () { + $.fn.button = old + return this + } + + + // BUTTON DATA-API + // =============== + + $(document) + .on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) { + var $btn = $(e.target) + if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn') + Plugin.call($btn, 'toggle') + e.preventDefault() + }) + .on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) { + $(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type)) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: carousel.js v3.3.2 + * http://getbootstrap.com/javascript/#carousel + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // CAROUSEL CLASS DEFINITION + // ========================= + + var Carousel = function (element, options) { + this.$element = $(element) + this.$indicators = this.$element.find('.carousel-indicators') + this.options = options + this.paused = + this.sliding = + this.interval = + this.$active = + this.$items = null + + this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this)) + + this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element + .on('mouseenter.bs.carousel', $.proxy(this.pause, this)) + .on('mouseleave.bs.carousel', $.proxy(this.cycle, this)) + } + + Carousel.VERSION = '3.3.2' + + Carousel.TRANSITION_DURATION = 600 + + Carousel.DEFAULTS = { + interval: 5000, + pause: 'hover', + wrap: true, + keyboard: true + } + + Carousel.prototype.keydown = function (e) { + if (/input|textarea/i.test(e.target.tagName)) return + switch (e.which) { + case 37: this.prev(); break + case 39: this.next(); break + default: return + } + + e.preventDefault() + } + + Carousel.prototype.cycle = function (e) { + e || (this.paused = false) + + this.interval && clearInterval(this.interval) + + this.options.interval + && !this.paused + && (this.interval = setInterval($.proxy(this.next, this), this.options.interval)) + + return this + } + + Carousel.prototype.getItemIndex = function (item) { + this.$items = item.parent().children('.item') + return this.$items.index(item || this.$active) + } + + Carousel.prototype.getItemForDirection = function (direction, active) { + var activeIndex = this.getItemIndex(active) + var willWrap = (direction == 'prev' && activeIndex === 0) + || (direction == 'next' && activeIndex == (this.$items.length - 1)) + if (willWrap && !this.options.wrap) return active + var delta = direction == 'prev' ? -1 : 1 + var itemIndex = (activeIndex + delta) % this.$items.length + return this.$items.eq(itemIndex) + } + + Carousel.prototype.to = function (pos) { + var that = this + var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active')) + + if (pos > (this.$items.length - 1) || pos < 0) return + + if (this.sliding) return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, "slid" + if (activeIndex == pos) return this.pause().cycle() + + return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos)) + } + + Carousel.prototype.pause = function (e) { + e || (this.paused = true) + + if (this.$element.find('.next, .prev').length && $.support.transition) { + this.$element.trigger($.support.transition.end) + this.cycle(true) + } + + this.interval = clearInterval(this.interval) + + return this + } + + Carousel.prototype.next = function () { + if (this.sliding) return + return this.slide('next') + } + + Carousel.prototype.prev = function () { + if (this.sliding) return + return this.slide('prev') + } + + Carousel.prototype.slide = function (type, next) { + var $active = this.$element.find('.item.active') + var $next = next || this.getItemForDirection(type, $active) + var isCycling = this.interval + var direction = type == 'next' ? 'left' : 'right' + var that = this + + if ($next.hasClass('active')) return (this.sliding = false) + + var relatedTarget = $next[0] + var slideEvent = $.Event('slide.bs.carousel', { + relatedTarget: relatedTarget, + direction: direction + }) + this.$element.trigger(slideEvent) + if (slideEvent.isDefaultPrevented()) return + + this.sliding = true + + isCycling && this.pause() + + if (this.$indicators.length) { + this.$indicators.find('.active').removeClass('active') + var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)]) + $nextIndicator && $nextIndicator.addClass('active') + } + + var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid" + if ($.support.transition && this.$element.hasClass('slide')) { + $next.addClass(type) + $next[0].offsetWidth // force reflow + $active.addClass(direction) + $next.addClass(direction) + $active + .one('bsTransitionEnd', function () { + $next.removeClass([type, direction].join(' ')).addClass('active') + $active.removeClass(['active', direction].join(' ')) + that.sliding = false + setTimeout(function () { + that.$element.trigger(slidEvent) + }, 0) + }) + .emulateTransitionEnd(Carousel.TRANSITION_DURATION) + } else { + $active.removeClass('active') + $next.addClass('active') + this.sliding = false + this.$element.trigger(slidEvent) + } + + isCycling && this.cycle() + + return this + } + + + // CAROUSEL PLUGIN DEFINITION + // ========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.carousel') + var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option) + var action = typeof option == 'string' ? option : options.slide + + if (!data) $this.data('bs.carousel', (data = new Carousel(this, options))) + if (typeof option == 'number') data.to(option) + else if (action) data[action]() + else if (options.interval) data.pause().cycle() + }) + } + + var old = $.fn.carousel + + $.fn.carousel = Plugin + $.fn.carousel.Constructor = Carousel + + + // CAROUSEL NO CONFLICT + // ==================== + + $.fn.carousel.noConflict = function () { + $.fn.carousel = old + return this + } + + + // CAROUSEL DATA-API + // ================= + + var clickHandler = function (e) { + var href + var $this = $(this) + var $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7 + if (!$target.hasClass('carousel')) return + var options = $.extend({}, $target.data(), $this.data()) + var slideIndex = $this.attr('data-slide-to') + if (slideIndex) options.interval = false + + Plugin.call($target, options) + + if (slideIndex) { + $target.data('bs.carousel').to(slideIndex) + } + + e.preventDefault() + } + + $(document) + .on('click.bs.carousel.data-api', '[data-slide]', clickHandler) + .on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler) + + $(window).on('load', function () { + $('[data-ride="carousel"]').each(function () { + var $carousel = $(this) + Plugin.call($carousel, $carousel.data()) + }) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: collapse.js v3.3.2 + * http://getbootstrap.com/javascript/#collapse + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // COLLAPSE PUBLIC CLASS DEFINITION + // ================================ + + var Collapse = function (element, options) { + this.$element = $(element) + this.options = $.extend({}, Collapse.DEFAULTS, options) + this.$trigger = $(this.options.trigger).filter('[href="#' + element.id + '"], [data-target="#' + element.id + '"]') + this.transitioning = null + + if (this.options.parent) { + this.$parent = this.getParent() + } else { + this.addAriaAndCollapsedClass(this.$element, this.$trigger) + } + + if (this.options.toggle) this.toggle() + } + + Collapse.VERSION = '3.3.2' + + Collapse.TRANSITION_DURATION = 350 + + Collapse.DEFAULTS = { + toggle: true, + trigger: '[data-toggle="collapse"]' + } + + Collapse.prototype.dimension = function () { + var hasWidth = this.$element.hasClass('width') + return hasWidth ? 'width' : 'height' + } + + Collapse.prototype.show = function () { + if (this.transitioning || this.$element.hasClass('in')) return + + var activesData + var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing') + + if (actives && actives.length) { + activesData = actives.data('bs.collapse') + if (activesData && activesData.transitioning) return + } + + var startEvent = $.Event('show.bs.collapse') + this.$element.trigger(startEvent) + if (startEvent.isDefaultPrevented()) return + + if (actives && actives.length) { + Plugin.call(actives, 'hide') + activesData || actives.data('bs.collapse', null) + } + + var dimension = this.dimension() + + this.$element + .removeClass('collapse') + .addClass('collapsing')[dimension](0) + .attr('aria-expanded', true) + + this.$trigger + .removeClass('collapsed') + .attr('aria-expanded', true) + + this.transitioning = 1 + + var complete = function () { + this.$element + .removeClass('collapsing') + .addClass('collapse in')[dimension]('') + this.transitioning = 0 + this.$element + .trigger('shown.bs.collapse') + } + + if (!$.support.transition) return complete.call(this) + + var scrollSize = $.camelCase(['scroll', dimension].join('-')) + + this.$element + .one('bsTransitionEnd', $.proxy(complete, this)) + .emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize]) + } + + Collapse.prototype.hide = function () { + if (this.transitioning || !this.$element.hasClass('in')) return + + var startEvent = $.Event('hide.bs.collapse') + this.$element.trigger(startEvent) + if (startEvent.isDefaultPrevented()) return + + var dimension = this.dimension() + + this.$element[dimension](this.$element[dimension]())[0].offsetHeight + + this.$element + .addClass('collapsing') + .removeClass('collapse in') + .attr('aria-expanded', false) + + this.$trigger + .addClass('collapsed') + .attr('aria-expanded', false) + + this.transitioning = 1 + + var complete = function () { + this.transitioning = 0 + this.$element + .removeClass('collapsing') + .addClass('collapse') + .trigger('hidden.bs.collapse') + } + + if (!$.support.transition) return complete.call(this) + + this.$element + [dimension](0) + .one('bsTransitionEnd', $.proxy(complete, this)) + .emulateTransitionEnd(Collapse.TRANSITION_DURATION) + } + + Collapse.prototype.toggle = function () { + this[this.$element.hasClass('in') ? 'hide' : 'show']() + } + + Collapse.prototype.getParent = function () { + return $(this.options.parent) + .find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]') + .each($.proxy(function (i, element) { + var $element = $(element) + this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element) + }, this)) + .end() + } + + Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) { + var isOpen = $element.hasClass('in') + + $element.attr('aria-expanded', isOpen) + $trigger + .toggleClass('collapsed', !isOpen) + .attr('aria-expanded', isOpen) + } + + function getTargetFromTrigger($trigger) { + var href + var target = $trigger.attr('data-target') + || (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7 + + return $(target) + } + + + // COLLAPSE PLUGIN DEFINITION + // ========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.collapse') + var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option) + + if (!data && options.toggle && option == 'show') options.toggle = false + if (!data) $this.data('bs.collapse', (data = new Collapse(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.collapse + + $.fn.collapse = Plugin + $.fn.collapse.Constructor = Collapse + + + // COLLAPSE NO CONFLICT + // ==================== + + $.fn.collapse.noConflict = function () { + $.fn.collapse = old + return this + } + + + // COLLAPSE DATA-API + // ================= + + $(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) { + var $this = $(this) + + if (!$this.attr('data-target')) e.preventDefault() + + var $target = getTargetFromTrigger($this) + var data = $target.data('bs.collapse') + var option = data ? 'toggle' : $.extend({}, $this.data(), { trigger: this }) + + Plugin.call($target, option) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: dropdown.js v3.3.2 + * http://getbootstrap.com/javascript/#dropdowns + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // DROPDOWN CLASS DEFINITION + // ========================= + + var backdrop = '.dropdown-backdrop' + var toggle = '[data-toggle="dropdown"]' + var Dropdown = function (element) { + $(element).on('click.bs.dropdown', this.toggle) + } + + Dropdown.VERSION = '3.3.2' + + Dropdown.prototype.toggle = function (e) { + var $this = $(this) + + if ($this.is('.disabled, :disabled')) return + + var $parent = getParent($this) + var isActive = $parent.hasClass('open') + + clearMenus() + + if (!isActive) { + if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) { + // if mobile we use a backdrop because click events don't delegate + $('<div class="dropdown-backdrop"/>').insertAfter($(this)).on('click', clearMenus) + } + + var relatedTarget = { relatedTarget: this } + $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget)) + + if (e.isDefaultPrevented()) return + + $this + .trigger('focus') + .attr('aria-expanded', 'true') + + $parent + .toggleClass('open') + .trigger('shown.bs.dropdown', relatedTarget) + } + + return false + } + + Dropdown.prototype.keydown = function (e) { + if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return + + var $this = $(this) + + e.preventDefault() + e.stopPropagation() + + if ($this.is('.disabled, :disabled')) return + + var $parent = getParent($this) + var isActive = $parent.hasClass('open') + + if ((!isActive && e.which != 27) || (isActive && e.which == 27)) { + if (e.which == 27) $parent.find(toggle).trigger('focus') + return $this.trigger('click') + } + + var desc = ' li:not(.divider):visible a' + var $items = $parent.find('[role="menu"]' + desc + ', [role="listbox"]' + desc) + + if (!$items.length) return + + var index = $items.index(e.target) + + if (e.which == 38 && index > 0) index-- // up + if (e.which == 40 && index < $items.length - 1) index++ // down + if (!~index) index = 0 + + $items.eq(index).trigger('focus') + } + + function clearMenus(e) { + if (e && e.which === 3) return + $(backdrop).remove() + $(toggle).each(function () { + var $this = $(this) + var $parent = getParent($this) + var relatedTarget = { relatedTarget: this } + + if (!$parent.hasClass('open')) return + + $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget)) + + if (e.isDefaultPrevented()) return + + $this.attr('aria-expanded', 'false') + $parent.removeClass('open').trigger('hidden.bs.dropdown', relatedTarget) + }) + } + + function getParent($this) { + var selector = $this.attr('data-target') + + if (!selector) { + selector = $this.attr('href') + selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 + } + + var $parent = selector && $(selector) + + return $parent && $parent.length ? $parent : $this.parent() + } + + + // DROPDOWN PLUGIN DEFINITION + // ========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.dropdown') + + if (!data) $this.data('bs.dropdown', (data = new Dropdown(this))) + if (typeof option == 'string') data[option].call($this) + }) + } + + var old = $.fn.dropdown + + $.fn.dropdown = Plugin + $.fn.dropdown.Constructor = Dropdown + + + // DROPDOWN NO CONFLICT + // ==================== + + $.fn.dropdown.noConflict = function () { + $.fn.dropdown = old + return this + } + + + // APPLY TO STANDARD DROPDOWN ELEMENTS + // =================================== + + $(document) + .on('click.bs.dropdown.data-api', clearMenus) + .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() }) + .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle) + .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown) + .on('keydown.bs.dropdown.data-api', '[role="menu"]', Dropdown.prototype.keydown) + .on('keydown.bs.dropdown.data-api', '[role="listbox"]', Dropdown.prototype.keydown) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: modal.js v3.3.2 + * http://getbootstrap.com/javascript/#modals + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // MODAL CLASS DEFINITION + // ====================== + + var Modal = function (element, options) { + this.options = options + this.$body = $(document.body) + this.$element = $(element) + this.$backdrop = + this.isShown = null + this.scrollbarWidth = 0 + + if (this.options.remote) { + this.$element + .find('.modal-content') + .load(this.options.remote, $.proxy(function () { + this.$element.trigger('loaded.bs.modal') + }, this)) + } + } + + Modal.VERSION = '3.3.2' + + Modal.TRANSITION_DURATION = 300 + Modal.BACKDROP_TRANSITION_DURATION = 150 + + Modal.DEFAULTS = { + backdrop: true, + keyboard: true, + show: true + } + + Modal.prototype.toggle = function (_relatedTarget) { + return this.isShown ? this.hide() : this.show(_relatedTarget) + } + + Modal.prototype.show = function (_relatedTarget) { + var that = this + var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget }) + + this.$element.trigger(e) + + if (this.isShown || e.isDefaultPrevented()) return + + this.isShown = true + + this.checkScrollbar() + this.setScrollbar() + this.$body.addClass('modal-open') + + this.escape() + this.resize() + + this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this)) + + this.backdrop(function () { + var transition = $.support.transition && that.$element.hasClass('fade') + + if (!that.$element.parent().length) { + that.$element.appendTo(that.$body) // don't move modals dom position + } + + that.$element + .show() + .scrollTop(0) + + if (that.options.backdrop) that.adjustBackdrop() + that.adjustDialog() + + if (transition) { + that.$element[0].offsetWidth // force reflow + } + + that.$element + .addClass('in') + .attr('aria-hidden', false) + + that.enforceFocus() + + var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget }) + + transition ? + that.$element.find('.modal-dialog') // wait for modal to slide in + .one('bsTransitionEnd', function () { + that.$element.trigger('focus').trigger(e) + }) + .emulateTransitionEnd(Modal.TRANSITION_DURATION) : + that.$element.trigger('focus').trigger(e) + }) + } + + Modal.prototype.hide = function (e) { + if (e) e.preventDefault() + + e = $.Event('hide.bs.modal') + + this.$element.trigger(e) + + if (!this.isShown || e.isDefaultPrevented()) return + + this.isShown = false + + this.escape() + this.resize() + + $(document).off('focusin.bs.modal') + + this.$element + .removeClass('in') + .attr('aria-hidden', true) + .off('click.dismiss.bs.modal') + + $.support.transition && this.$element.hasClass('fade') ? + this.$element + .one('bsTransitionEnd', $.proxy(this.hideModal, this)) + .emulateTransitionEnd(Modal.TRANSITION_DURATION) : + this.hideModal() + } + + Modal.prototype.enforceFocus = function () { + $(document) + .off('focusin.bs.modal') // guard against infinite focus loop + .on('focusin.bs.modal', $.proxy(function (e) { + if (this.$element[0] !== e.target && !this.$element.has(e.target).length) { + this.$element.trigger('focus') + } + }, this)) + } + + Modal.prototype.escape = function () { + if (this.isShown && this.options.keyboard) { + this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) { + e.which == 27 && this.hide() + }, this)) + } else if (!this.isShown) { + this.$element.off('keydown.dismiss.bs.modal') + } + } + + Modal.prototype.resize = function () { + if (this.isShown) { + $(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this)) + } else { + $(window).off('resize.bs.modal') + } + } + + Modal.prototype.hideModal = function () { + var that = this + this.$element.hide() + this.backdrop(function () { + that.$body.removeClass('modal-open') + that.resetAdjustments() + that.resetScrollbar() + that.$element.trigger('hidden.bs.modal') + }) + } + + Modal.prototype.removeBackdrop = function () { + this.$backdrop && this.$backdrop.remove() + this.$backdrop = null + } + + Modal.prototype.backdrop = function (callback) { + var that = this + var animate = this.$element.hasClass('fade') ? 'fade' : '' + + if (this.isShown && this.options.backdrop) { + var doAnimate = $.support.transition && animate + + this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />') + .prependTo(this.$element) + .on('click.dismiss.bs.modal', $.proxy(function (e) { + if (e.target !== e.currentTarget) return + this.options.backdrop == 'static' + ? this.$element[0].focus.call(this.$element[0]) + : this.hide.call(this) + }, this)) + + if (doAnimate) this.$backdrop[0].offsetWidth // force reflow + + this.$backdrop.addClass('in') + + if (!callback) return + + doAnimate ? + this.$backdrop + .one('bsTransitionEnd', callback) + .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : + callback() + + } else if (!this.isShown && this.$backdrop) { + this.$backdrop.removeClass('in') + + var callbackRemove = function () { + that.removeBackdrop() + callback && callback() + } + $.support.transition && this.$element.hasClass('fade') ? + this.$backdrop + .one('bsTransitionEnd', callbackRemove) + .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) : + callbackRemove() + + } else if (callback) { + callback() + } + } + + // these following methods are used to handle overflowing modals + + Modal.prototype.handleUpdate = function () { + if (this.options.backdrop) this.adjustBackdrop() + this.adjustDialog() + } + + Modal.prototype.adjustBackdrop = function () { + this.$backdrop + .css('height', 0) + .css('height', this.$element[0].scrollHeight) + } + + Modal.prototype.adjustDialog = function () { + var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight + + this.$element.css({ + paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '', + paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : '' + }) + } + + Modal.prototype.resetAdjustments = function () { + this.$element.css({ + paddingLeft: '', + paddingRight: '' + }) + } + + Modal.prototype.checkScrollbar = function () { + this.bodyIsOverflowing = document.body.scrollHeight > document.documentElement.clientHeight + this.scrollbarWidth = this.measureScrollbar() + } + + Modal.prototype.setScrollbar = function () { + var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10) + if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth) + } + + Modal.prototype.resetScrollbar = function () { + this.$body.css('padding-right', '') + } + + Modal.prototype.measureScrollbar = function () { // thx walsh + var scrollDiv = document.createElement('div') + scrollDiv.className = 'modal-scrollbar-measure' + this.$body.append(scrollDiv) + var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth + this.$body[0].removeChild(scrollDiv) + return scrollbarWidth + } + + + // MODAL PLUGIN DEFINITION + // ======================= + + function Plugin(option, _relatedTarget) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.modal') + var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option) + + if (!data) $this.data('bs.modal', (data = new Modal(this, options))) + if (typeof option == 'string') data[option](_relatedTarget) + else if (options.show) data.show(_relatedTarget) + }) + } + + var old = $.fn.modal + + $.fn.modal = Plugin + $.fn.modal.Constructor = Modal + + + // MODAL NO CONFLICT + // ================= + + $.fn.modal.noConflict = function () { + $.fn.modal = old + return this + } + + + // MODAL DATA-API + // ============== + + $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) { + var $this = $(this) + var href = $this.attr('href') + var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7 + var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data()) + + if ($this.is('a')) e.preventDefault() + + $target.one('show.bs.modal', function (showEvent) { + if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown + $target.one('hidden.bs.modal', function () { + $this.is(':visible') && $this.trigger('focus') + }) + }) + Plugin.call($target, option, this) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: tooltip.js v3.3.2 + * http://getbootstrap.com/javascript/#tooltip + * Inspired by the original jQuery.tipsy by Jason Frame + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // TOOLTIP PUBLIC CLASS DEFINITION + // =============================== + + var Tooltip = function (element, options) { + this.type = + this.options = + this.enabled = + this.timeout = + this.hoverState = + this.$element = null + + this.init('tooltip', element, options) + } + + Tooltip.VERSION = '3.3.2' + + Tooltip.TRANSITION_DURATION = 150 + + Tooltip.DEFAULTS = { + animation: true, + placement: 'top', + selector: false, + template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>', + trigger: 'hover focus', + title: '', + delay: 0, + html: false, + container: false, + viewport: { + selector: 'body', + padding: 0 + } + } + + Tooltip.prototype.init = function (type, element, options) { + this.enabled = true + this.type = type + this.$element = $(element) + this.options = this.getOptions(options) + this.$viewport = this.options.viewport && $(this.options.viewport.selector || this.options.viewport) + + var triggers = this.options.trigger.split(' ') + + for (var i = triggers.length; i--;) { + var trigger = triggers[i] + + if (trigger == 'click') { + this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this)) + } else if (trigger != 'manual') { + var eventIn = trigger == 'hover' ? 'mouseenter' : 'focusin' + var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout' + + this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this)) + this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this)) + } + } + + this.options.selector ? + (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) : + this.fixTitle() + } + + Tooltip.prototype.getDefaults = function () { + return Tooltip.DEFAULTS + } + + Tooltip.prototype.getOptions = function (options) { + options = $.extend({}, this.getDefaults(), this.$element.data(), options) + + if (options.delay && typeof options.delay == 'number') { + options.delay = { + show: options.delay, + hide: options.delay + } + } + + return options + } + + Tooltip.prototype.getDelegateOptions = function () { + var options = {} + var defaults = this.getDefaults() + + this._options && $.each(this._options, function (key, value) { + if (defaults[key] != value) options[key] = value + }) + + return options + } + + Tooltip.prototype.enter = function (obj) { + var self = obj instanceof this.constructor ? + obj : $(obj.currentTarget).data('bs.' + this.type) + + if (self && self.$tip && self.$tip.is(':visible')) { + self.hoverState = 'in' + return + } + + if (!self) { + self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) + $(obj.currentTarget).data('bs.' + this.type, self) + } + + clearTimeout(self.timeout) + + self.hoverState = 'in' + + if (!self.options.delay || !self.options.delay.show) return self.show() + + self.timeout = setTimeout(function () { + if (self.hoverState == 'in') self.show() + }, self.options.delay.show) + } + + Tooltip.prototype.leave = function (obj) { + var self = obj instanceof this.constructor ? + obj : $(obj.currentTarget).data('bs.' + this.type) + + if (!self) { + self = new this.constructor(obj.currentTarget, this.getDelegateOptions()) + $(obj.currentTarget).data('bs.' + this.type, self) + } + + clearTimeout(self.timeout) + + self.hoverState = 'out' + + if (!self.options.delay || !self.options.delay.hide) return self.hide() + + self.timeout = setTimeout(function () { + if (self.hoverState == 'out') self.hide() + }, self.options.delay.hide) + } + + Tooltip.prototype.show = function () { + var e = $.Event('show.bs.' + this.type) + + if (this.hasContent() && this.enabled) { + this.$element.trigger(e) + + var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0]) + if (e.isDefaultPrevented() || !inDom) return + var that = this + + var $tip = this.tip() + + var tipId = this.getUID(this.type) + + this.setContent() + $tip.attr('id', tipId) + this.$element.attr('aria-describedby', tipId) + + if (this.options.animation) $tip.addClass('fade') + + var placement = typeof this.options.placement == 'function' ? + this.options.placement.call(this, $tip[0], this.$element[0]) : + this.options.placement + + var autoToken = /\s?auto?\s?/i + var autoPlace = autoToken.test(placement) + if (autoPlace) placement = placement.replace(autoToken, '') || 'top' + + $tip + .detach() + .css({ top: 0, left: 0, display: 'block' }) + .addClass(placement) + .data('bs.' + this.type, this) + + this.options.container ? $tip.appendTo(this.options.container) : $tip.insertAfter(this.$element) + + var pos = this.getPosition() + var actualWidth = $tip[0].offsetWidth + var actualHeight = $tip[0].offsetHeight + + if (autoPlace) { + var orgPlacement = placement + var $container = this.options.container ? $(this.options.container) : this.$element.parent() + var containerDim = this.getPosition($container) + + placement = placement == 'bottom' && pos.bottom + actualHeight > containerDim.bottom ? 'top' : + placement == 'top' && pos.top - actualHeight < containerDim.top ? 'bottom' : + placement == 'right' && pos.right + actualWidth > containerDim.width ? 'left' : + placement == 'left' && pos.left - actualWidth < containerDim.left ? 'right' : + placement + + $tip + .removeClass(orgPlacement) + .addClass(placement) + } + + var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight) + + this.applyPlacement(calculatedOffset, placement) + + var complete = function () { + var prevHoverState = that.hoverState + that.$element.trigger('shown.bs.' + that.type) + that.hoverState = null + + if (prevHoverState == 'out') that.leave(that) + } + + $.support.transition && this.$tip.hasClass('fade') ? + $tip + .one('bsTransitionEnd', complete) + .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) : + complete() + } + } + + Tooltip.prototype.applyPlacement = function (offset, placement) { + var $tip = this.tip() + var width = $tip[0].offsetWidth + var height = $tip[0].offsetHeight + + // manually read margins because getBoundingClientRect includes difference + var marginTop = parseInt($tip.css('margin-top'), 10) + var marginLeft = parseInt($tip.css('margin-left'), 10) + + // we must check for NaN for ie 8/9 + if (isNaN(marginTop)) marginTop = 0 + if (isNaN(marginLeft)) marginLeft = 0 + + offset.top = offset.top + marginTop + offset.left = offset.left + marginLeft + + // $.fn.offset doesn't round pixel values + // so we use setOffset directly with our own function B-0 + $.offset.setOffset($tip[0], $.extend({ + using: function (props) { + $tip.css({ + top: Math.round(props.top), + left: Math.round(props.left) + }) + } + }, offset), 0) + + $tip.addClass('in') + + // check to see if placing tip in new offset caused the tip to resize itself + var actualWidth = $tip[0].offsetWidth + var actualHeight = $tip[0].offsetHeight + + if (placement == 'top' && actualHeight != height) { + offset.top = offset.top + height - actualHeight + } + + var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight) + + if (delta.left) offset.left += delta.left + else offset.top += delta.top + + var isVertical = /top|bottom/.test(placement) + var arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight + var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight' + + $tip.offset(offset) + this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical) + } + + Tooltip.prototype.replaceArrow = function (delta, dimension, isHorizontal) { + this.arrow() + .css(isHorizontal ? 'left' : 'top', 50 * (1 - delta / dimension) + '%') + .css(isHorizontal ? 'top' : 'left', '') + } + + Tooltip.prototype.setContent = function () { + var $tip = this.tip() + var title = this.getTitle() + + $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title) + $tip.removeClass('fade in top bottom left right') + } + + Tooltip.prototype.hide = function (callback) { + var that = this + var $tip = this.tip() + var e = $.Event('hide.bs.' + this.type) + + function complete() { + if (that.hoverState != 'in') $tip.detach() + that.$element + .removeAttr('aria-describedby') + .trigger('hidden.bs.' + that.type) + callback && callback() + } + + this.$element.trigger(e) + + if (e.isDefaultPrevented()) return + + $tip.removeClass('in') + + $.support.transition && this.$tip.hasClass('fade') ? + $tip + .one('bsTransitionEnd', complete) + .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) : + complete() + + this.hoverState = null + + return this + } + + Tooltip.prototype.fixTitle = function () { + var $e = this.$element + if ($e.attr('title') || typeof ($e.attr('data-original-title')) != 'string') { + $e.attr('data-original-title', $e.attr('title') || '').attr('title', '') + } + } + + Tooltip.prototype.hasContent = function () { + return this.getTitle() + } + + Tooltip.prototype.getPosition = function ($element) { + $element = $element || this.$element + + var el = $element[0] + var isBody = el.tagName == 'BODY' + + var elRect = el.getBoundingClientRect() + if (elRect.width == null) { + // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093 + elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top }) + } + var elOffset = isBody ? { top: 0, left: 0 } : $element.offset() + var scroll = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() } + var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null + + return $.extend({}, elRect, scroll, outerDims, elOffset) + } + + Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) { + return placement == 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } : + placement == 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } : + placement == 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } : + /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width } + + } + + Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) { + var delta = { top: 0, left: 0 } + if (!this.$viewport) return delta + + var viewportPadding = this.options.viewport && this.options.viewport.padding || 0 + var viewportDimensions = this.getPosition(this.$viewport) + + if (/right|left/.test(placement)) { + var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll + var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight + if (topEdgeOffset < viewportDimensions.top) { // top overflow + delta.top = viewportDimensions.top - topEdgeOffset + } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow + delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset + } + } else { + var leftEdgeOffset = pos.left - viewportPadding + var rightEdgeOffset = pos.left + viewportPadding + actualWidth + if (leftEdgeOffset < viewportDimensions.left) { // left overflow + delta.left = viewportDimensions.left - leftEdgeOffset + } else if (rightEdgeOffset > viewportDimensions.width) { // right overflow + delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset + } + } + + return delta + } + + Tooltip.prototype.getTitle = function () { + var title + var $e = this.$element + var o = this.options + + title = $e.attr('data-original-title') + || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title) + + return title + } + + Tooltip.prototype.getUID = function (prefix) { + do prefix += ~~(Math.random() * 1000000) + while (document.getElementById(prefix)) + return prefix + } + + Tooltip.prototype.tip = function () { + return (this.$tip = this.$tip || $(this.options.template)) + } + + Tooltip.prototype.arrow = function () { + return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow')) + } + + Tooltip.prototype.enable = function () { + this.enabled = true + } + + Tooltip.prototype.disable = function () { + this.enabled = false + } + + Tooltip.prototype.toggleEnabled = function () { + this.enabled = !this.enabled + } + + Tooltip.prototype.toggle = function (e) { + var self = this + if (e) { + self = $(e.currentTarget).data('bs.' + this.type) + if (!self) { + self = new this.constructor(e.currentTarget, this.getDelegateOptions()) + $(e.currentTarget).data('bs.' + this.type, self) + } + } + + self.tip().hasClass('in') ? self.leave(self) : self.enter(self) + } + + Tooltip.prototype.destroy = function () { + var that = this + clearTimeout(this.timeout) + this.hide(function () { + that.$element.off('.' + that.type).removeData('bs.' + that.type) + }) + } + + + // TOOLTIP PLUGIN DEFINITION + // ========================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.tooltip') + var options = typeof option == 'object' && option + + if (!data && option == 'destroy') return + if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.tooltip + + $.fn.tooltip = Plugin + $.fn.tooltip.Constructor = Tooltip + + + // TOOLTIP NO CONFLICT + // =================== + + $.fn.tooltip.noConflict = function () { + $.fn.tooltip = old + return this + } + +}(jQuery); + +/* ======================================================================== + * Bootstrap: popover.js v3.3.2 + * http://getbootstrap.com/javascript/#popovers + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // POPOVER PUBLIC CLASS DEFINITION + // =============================== + + var Popover = function (element, options) { + this.init('popover', element, options) + } + + if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js') + + Popover.VERSION = '3.3.2' + + Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, { + placement: 'right', + trigger: 'click', + content: '', + template: '<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>' + }) + + + // NOTE: POPOVER EXTENDS tooltip.js + // ================================ + + Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype) + + Popover.prototype.constructor = Popover + + Popover.prototype.getDefaults = function () { + return Popover.DEFAULTS + } + + Popover.prototype.setContent = function () { + var $tip = this.tip() + var title = this.getTitle() + var content = this.getContent() + + $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title) + $tip.find('.popover-content').children().detach().end()[ // we use append for html objects to maintain js events + this.options.html ? (typeof content == 'string' ? 'html' : 'append') : 'text' + ](content) + + $tip.removeClass('fade top bottom left right in') + + // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do + // this manually by checking the contents. + if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide() + } + + Popover.prototype.hasContent = function () { + return this.getTitle() || this.getContent() + } + + Popover.prototype.getContent = function () { + var $e = this.$element + var o = this.options + + return $e.attr('data-content') + || (typeof o.content == 'function' ? + o.content.call($e[0]) : + o.content) + } + + Popover.prototype.arrow = function () { + return (this.$arrow = this.$arrow || this.tip().find('.arrow')) + } + + Popover.prototype.tip = function () { + if (!this.$tip) this.$tip = $(this.options.template) + return this.$tip + } + + + // POPOVER PLUGIN DEFINITION + // ========================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.popover') + var options = typeof option == 'object' && option + + if (!data && option == 'destroy') return + if (!data) $this.data('bs.popover', (data = new Popover(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.popover + + $.fn.popover = Plugin + $.fn.popover.Constructor = Popover + + + // POPOVER NO CONFLICT + // =================== + + $.fn.popover.noConflict = function () { + $.fn.popover = old + return this + } + +}(jQuery); + +/* ======================================================================== + * Bootstrap: scrollspy.js v3.3.2 + * http://getbootstrap.com/javascript/#scrollspy + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // SCROLLSPY CLASS DEFINITION + // ========================== + + function ScrollSpy(element, options) { + var process = $.proxy(this.process, this) + + this.$body = $('body') + this.$scrollElement = $(element).is('body') ? $(window) : $(element) + this.options = $.extend({}, ScrollSpy.DEFAULTS, options) + this.selector = (this.options.target || '') + ' .nav li > a' + this.offsets = [] + this.targets = [] + this.activeTarget = null + this.scrollHeight = 0 + + this.$scrollElement.on('scroll.bs.scrollspy', process) + this.refresh() + this.process() + } + + ScrollSpy.VERSION = '3.3.2' + + ScrollSpy.DEFAULTS = { + offset: 10 + } + + ScrollSpy.prototype.getScrollHeight = function () { + return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight) + } + + ScrollSpy.prototype.refresh = function () { + var offsetMethod = 'offset' + var offsetBase = 0 + + if (!$.isWindow(this.$scrollElement[0])) { + offsetMethod = 'position' + offsetBase = this.$scrollElement.scrollTop() + } + + this.offsets = [] + this.targets = [] + this.scrollHeight = this.getScrollHeight() + + var self = this + + this.$body + .find(this.selector) + .map(function () { + var $el = $(this) + var href = $el.data('target') || $el.attr('href') + var $href = /^#./.test(href) && $(href) + + return ($href + && $href.length + && $href.is(':visible') + && [[$href[offsetMethod]().top + offsetBase, href]]) || null + }) + .sort(function (a, b) { return a[0] - b[0] }) + .each(function () { + self.offsets.push(this[0]) + self.targets.push(this[1]) + }) + } + + ScrollSpy.prototype.process = function () { + var scrollTop = this.$scrollElement.scrollTop() + this.options.offset + var scrollHeight = this.getScrollHeight() + var maxScroll = this.options.offset + scrollHeight - this.$scrollElement.height() + var offsets = this.offsets + var targets = this.targets + var activeTarget = this.activeTarget + var i + + if (this.scrollHeight != scrollHeight) { + this.refresh() + } + + if (scrollTop >= maxScroll) { + return activeTarget != (i = targets[targets.length - 1]) && this.activate(i) + } + + if (activeTarget && scrollTop < offsets[0]) { + this.activeTarget = null + return this.clear() + } + + for (i = offsets.length; i--;) { + activeTarget != targets[i] + && scrollTop >= offsets[i] + && (!offsets[i + 1] || scrollTop <= offsets[i + 1]) + && this.activate(targets[i]) + } + } + + ScrollSpy.prototype.activate = function (target) { + this.activeTarget = target + + this.clear() + + var selector = this.selector + + '[data-target="' + target + '"],' + + this.selector + '[href="' + target + '"]' + + var active = $(selector) + .parents('li') + .addClass('active') + + if (active.parent('.dropdown-menu').length) { + active = active + .closest('li.dropdown') + .addClass('active') + } + + active.trigger('activate.bs.scrollspy') + } + + ScrollSpy.prototype.clear = function () { + $(this.selector) + .parentsUntil(this.options.target, '.active') + .removeClass('active') + } + + + // SCROLLSPY PLUGIN DEFINITION + // =========================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.scrollspy') + var options = typeof option == 'object' && option + + if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.scrollspy + + $.fn.scrollspy = Plugin + $.fn.scrollspy.Constructor = ScrollSpy + + + // SCROLLSPY NO CONFLICT + // ===================== + + $.fn.scrollspy.noConflict = function () { + $.fn.scrollspy = old + return this + } + + + // SCROLLSPY DATA-API + // ================== + + $(window).on('load.bs.scrollspy.data-api', function () { + $('[data-spy="scroll"]').each(function () { + var $spy = $(this) + Plugin.call($spy, $spy.data()) + }) + }) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: tab.js v3.3.2 + * http://getbootstrap.com/javascript/#tabs + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // TAB CLASS DEFINITION + // ==================== + + var Tab = function (element) { + this.element = $(element) + } + + Tab.VERSION = '3.3.2' + + Tab.TRANSITION_DURATION = 150 + + Tab.prototype.show = function () { + var $this = this.element + var $ul = $this.closest('ul:not(.dropdown-menu)') + var selector = $this.data('target') + + if (!selector) { + selector = $this.attr('href') + selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7 + } + + if ($this.parent('li').hasClass('active')) return + + var $previous = $ul.find('.active:last a') + var hideEvent = $.Event('hide.bs.tab', { + relatedTarget: $this[0] + }) + var showEvent = $.Event('show.bs.tab', { + relatedTarget: $previous[0] + }) + + $previous.trigger(hideEvent) + $this.trigger(showEvent) + + if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return + + var $target = $(selector) + + this.activate($this.closest('li'), $ul) + this.activate($target, $target.parent(), function () { + $previous.trigger({ + type: 'hidden.bs.tab', + relatedTarget: $this[0] + }) + $this.trigger({ + type: 'shown.bs.tab', + relatedTarget: $previous[0] + }) + }) + } + + Tab.prototype.activate = function (element, container, callback) { + var $active = container.find('> .active') + var transition = callback + && $.support.transition + && (($active.length && $active.hasClass('fade')) || !!container.find('> .fade').length) + + function next() { + $active + .removeClass('active') + .find('> .dropdown-menu > .active') + .removeClass('active') + .end() + .find('[data-toggle="tab"]') + .attr('aria-expanded', false) + + element + .addClass('active') + .find('[data-toggle="tab"]') + .attr('aria-expanded', true) + + if (transition) { + element[0].offsetWidth // reflow for transition + element.addClass('in') + } else { + element.removeClass('fade') + } + + if (element.parent('.dropdown-menu')) { + element + .closest('li.dropdown') + .addClass('active') + .end() + .find('[data-toggle="tab"]') + .attr('aria-expanded', true) + } + + callback && callback() + } + + $active.length && transition ? + $active + .one('bsTransitionEnd', next) + .emulateTransitionEnd(Tab.TRANSITION_DURATION) : + next() + + $active.removeClass('in') + } + + + // TAB PLUGIN DEFINITION + // ===================== + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.tab') + + if (!data) $this.data('bs.tab', (data = new Tab(this))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.tab + + $.fn.tab = Plugin + $.fn.tab.Constructor = Tab + + + // TAB NO CONFLICT + // =============== + + $.fn.tab.noConflict = function () { + $.fn.tab = old + return this + } + + + // TAB DATA-API + // ============ + + var clickHandler = function (e) { + e.preventDefault() + Plugin.call($(this), 'show') + } + + $(document) + .on('click.bs.tab.data-api', '[data-toggle="tab"]', clickHandler) + .on('click.bs.tab.data-api', '[data-toggle="pill"]', clickHandler) + +}(jQuery); + +/* ======================================================================== + * Bootstrap: affix.js v3.3.2 + * http://getbootstrap.com/javascript/#affix + * ======================================================================== + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + * ======================================================================== */ + + ++function ($) { + 'use strict'; + + // AFFIX CLASS DEFINITION + // ====================== + + var Affix = function (element, options) { + this.options = $.extend({}, Affix.DEFAULTS, options) + + this.$target = $(this.options.target) + .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this)) + .on('click.bs.affix.data-api', $.proxy(this.checkPositionWithEventLoop, this)) + + this.$element = $(element) + this.affixed = + this.unpin = + this.pinnedOffset = null + + this.checkPosition() + } + + Affix.VERSION = '3.3.2' + + Affix.RESET = 'affix affix-top affix-bottom' + + Affix.DEFAULTS = { + offset: 0, + target: window + } + + Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) { + var scrollTop = this.$target.scrollTop() + var position = this.$element.offset() + var targetHeight = this.$target.height() + + if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false + + if (this.affixed == 'bottom') { + if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom' + return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom' + } + + var initializing = this.affixed == null + var colliderTop = initializing ? scrollTop : position.top + var colliderHeight = initializing ? targetHeight : height + + if (offsetTop != null && scrollTop <= offsetTop) return 'top' + if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom' + + return false + } + + Affix.prototype.getPinnedOffset = function () { + if (this.pinnedOffset) return this.pinnedOffset + this.$element.removeClass(Affix.RESET).addClass('affix') + var scrollTop = this.$target.scrollTop() + var position = this.$element.offset() + return (this.pinnedOffset = position.top - scrollTop) + } + + Affix.prototype.checkPositionWithEventLoop = function () { + setTimeout($.proxy(this.checkPosition, this), 1) + } + + Affix.prototype.checkPosition = function () { + if (!this.$element.is(':visible')) return + + var height = this.$element.height() + var offset = this.options.offset + var offsetTop = offset.top + var offsetBottom = offset.bottom + var scrollHeight = $('body').height() + + if (typeof offset != 'object') offsetBottom = offsetTop = offset + if (typeof offsetTop == 'function') offsetTop = offset.top(this.$element) + if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element) + + var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom) + + if (this.affixed != affix) { + if (this.unpin != null) this.$element.css('top', '') + + var affixType = 'affix' + (affix ? '-' + affix : '') + var e = $.Event(affixType + '.bs.affix') + + this.$element.trigger(e) + + if (e.isDefaultPrevented()) return + + this.affixed = affix + this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null + + this.$element + .removeClass(Affix.RESET) + .addClass(affixType) + .trigger(affixType.replace('affix', 'affixed') + '.bs.affix') + } + + if (affix == 'bottom') { + this.$element.offset({ + top: scrollHeight - height - offsetBottom + }) + } + } + + + // AFFIX PLUGIN DEFINITION + // ======================= + + function Plugin(option) { + return this.each(function () { + var $this = $(this) + var data = $this.data('bs.affix') + var options = typeof option == 'object' && option + + if (!data) $this.data('bs.affix', (data = new Affix(this, options))) + if (typeof option == 'string') data[option]() + }) + } + + var old = $.fn.affix + + $.fn.affix = Plugin + $.fn.affix.Constructor = Affix + + + // AFFIX NO CONFLICT + // ================= + + $.fn.affix.noConflict = function () { + $.fn.affix = old + return this + } + + + // AFFIX DATA-API + // ============== + + $(window).on('load', function () { + $('[data-spy="affix"]').each(function () { + var $spy = $(this) + var data = $spy.data() + + data.offset = data.offset || {} + + if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom + if (data.offsetTop != null) data.offset.top = data.offsetTop + + Plugin.call($spy, data) + }) + }) + +}(jQuery); diff --git a/pyload/webui/themes/Next/lib/Bootstrap/js/bootstrap.min.js b/pyload/webui/themes/Next/lib/Bootstrap/js/bootstrap.min.js new file mode 100644 index 000000000..c6d36920b --- /dev/null +++ b/pyload/webui/themes/Next/lib/Bootstrap/js/bootstrap.min.js @@ -0,0 +1,7 @@ +/*! + * Bootstrap v3.3.2 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.2",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.2",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active"));a&&this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.2",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&"show"==b&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a(this.options.trigger).filter('[href="#'+b.id+'"], [data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.2",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0,trigger:'[data-toggle="collapse"]'},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":a.extend({},e.data(),{trigger:this});c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){b&&3===b.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=c(d),f={relatedTarget:this};e.hasClass("open")&&(e.trigger(b=a.Event("hide.bs.dropdown",f)),b.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger("hidden.bs.dropdown",f)))}))}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.2",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('<div class="dropdown-backdrop"/>').insertAfter(a(this)).on("click",b);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger("shown.bs.dropdown",h)}return!1}},g.prototype.keydown=function(b){if(/(38|40|27|32)/.test(b.which)&&!/input|textarea/i.test(b.target.tagName)){var d=a(this);if(b.preventDefault(),b.stopPropagation(),!d.is(".disabled, :disabled")){var e=c(d),g=e.hasClass("open");if(!g&&27!=b.which||g&&27==b.which)return 27==b.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.divider):visible a",i=e.find('[role="menu"]'+h+', [role="listbox"]'+h);if(i.length){var j=i.index(b.target);38==b.which&&j>0&&j--,40==b.which&&j<i.length-1&&j++,~j||(j=0),i.eq(j).trigger("focus")}}}};var h=a.fn.dropdown;a.fn.dropdown=d,a.fn.dropdown.Constructor=g,a.fn.dropdown.noConflict=function(){return a.fn.dropdown=h,this},a(document).on("click.bs.dropdown.data-api",b).on("click.bs.dropdown.data-api",".dropdown form",function(a){a.stopPropagation()}).on("click.bs.dropdown.data-api",f,g.prototype.toggle).on("keydown.bs.dropdown.data-api",f,g.prototype.keydown).on("keydown.bs.dropdown.data-api",'[role="menu"]',g.prototype.keydown).on("keydown.bs.dropdown.data-api",'[role="listbox"]',g.prototype.keydown)}(jQuery),+function(a){"use strict";function b(b,d){return this.each(function(){var e=a(this),f=e.data("bs.modal"),g=a.extend({},c.DEFAULTS,e.data(),"object"==typeof b&&b);f||e.data("bs.modal",f=new c(this,g)),"string"==typeof b?f[b](d):g.show&&f.show(d)})}var c=function(b,c){this.options=c,this.$body=a(document.body),this.$element=a(b),this.$backdrop=this.isShown=null,this.scrollbarWidth=0,this.options.remote&&this.$element.find(".modal-content").load(this.options.remote,a.proxy(function(){this.$element.trigger("loaded.bs.modal")},this))};c.VERSION="3.3.2",c.TRANSITION_DURATION=300,c.BACKDROP_TRANSITION_DURATION=150,c.DEFAULTS={backdrop:!0,keyboard:!0,show:!0},c.prototype.toggle=function(a){return this.isShown?this.hide():this.show(a)},c.prototype.show=function(b){var d=this,e=a.Event("show.bs.modal",{relatedTarget:b});this.$element.trigger(e),this.isShown||e.isDefaultPrevented()||(this.isShown=!0,this.checkScrollbar(),this.setScrollbar(),this.$body.addClass("modal-open"),this.escape(),this.resize(),this.$element.on("click.dismiss.bs.modal",'[data-dismiss="modal"]',a.proxy(this.hide,this)),this.backdrop(function(){var e=a.support.transition&&d.$element.hasClass("fade");d.$element.parent().length||d.$element.appendTo(d.$body),d.$element.show().scrollTop(0),d.options.backdrop&&d.adjustBackdrop(),d.adjustDialog(),e&&d.$element[0].offsetWidth,d.$element.addClass("in").attr("aria-hidden",!1),d.enforceFocus();var f=a.Event("shown.bs.modal",{relatedTarget:b});e?d.$element.find(".modal-dialog").one("bsTransitionEnd",function(){d.$element.trigger("focus").trigger(f)}).emulateTransitionEnd(c.TRANSITION_DURATION):d.$element.trigger("focus").trigger(f)}))},c.prototype.hide=function(b){b&&b.preventDefault(),b=a.Event("hide.bs.modal"),this.$element.trigger(b),this.isShown&&!b.isDefaultPrevented()&&(this.isShown=!1,this.escape(),this.resize(),a(document).off("focusin.bs.modal"),this.$element.removeClass("in").attr("aria-hidden",!0).off("click.dismiss.bs.modal"),a.support.transition&&this.$element.hasClass("fade")?this.$element.one("bsTransitionEnd",a.proxy(this.hideModal,this)).emulateTransitionEnd(c.TRANSITION_DURATION):this.hideModal())},c.prototype.enforceFocus=function(){a(document).off("focusin.bs.modal").on("focusin.bs.modal",a.proxy(function(a){this.$element[0]===a.target||this.$element.has(a.target).length||this.$element.trigger("focus")},this))},c.prototype.escape=function(){this.isShown&&this.options.keyboard?this.$element.on("keydown.dismiss.bs.modal",a.proxy(function(a){27==a.which&&this.hide()},this)):this.isShown||this.$element.off("keydown.dismiss.bs.modal")},c.prototype.resize=function(){this.isShown?a(window).on("resize.bs.modal",a.proxy(this.handleUpdate,this)):a(window).off("resize.bs.modal")},c.prototype.hideModal=function(){var a=this;this.$element.hide(),this.backdrop(function(){a.$body.removeClass("modal-open"),a.resetAdjustments(),a.resetScrollbar(),a.$element.trigger("hidden.bs.modal")})},c.prototype.removeBackdrop=function(){this.$backdrop&&this.$backdrop.remove(),this.$backdrop=null},c.prototype.backdrop=function(b){var d=this,e=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var f=a.support.transition&&e;if(this.$backdrop=a('<div class="modal-backdrop '+e+'" />').prependTo(this.$element).on("click.dismiss.bs.modal",a.proxy(function(a){a.target===a.currentTarget&&("static"==this.options.backdrop?this.$element[0].focus.call(this.$element[0]):this.hide.call(this))},this)),f&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),!b)return;f?this.$backdrop.one("bsTransitionEnd",b).emulateTransitionEnd(c.BACKDROP_TRANSITION_DURATION):b()}else if(!this.isShown&&this.$backdrop){this.$backdrop.removeClass("in");var g=function(){d.removeBackdrop(),b&&b()};a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one("bsTransitionEnd",g).emulateTransitionEnd(c.BACKDROP_TRANSITION_DURATION):g()}else b&&b()},c.prototype.handleUpdate=function(){this.options.backdrop&&this.adjustBackdrop(),this.adjustDialog()},c.prototype.adjustBackdrop=function(){this.$backdrop.css("height",0).css("height",this.$element[0].scrollHeight)},c.prototype.adjustDialog=function(){var a=this.$element[0].scrollHeight>document.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){this.bodyIsOverflowing=document.body.scrollHeight>document.documentElement.clientHeight,this.scrollbarWidth=this.measureScrollbar()},c.prototype.setScrollbar=function(){var a=parseInt(this.$body.css("padding-right")||0,10);this.bodyIsOverflowing&&this.$body.css("padding-right",a+this.scrollbarWidth)},c.prototype.resetScrollbar=function(){this.$body.css("padding-right","")},c.prototype.measureScrollbar=function(){var a=document.createElement("div");a.className="modal-scrollbar-measure",this.$body.append(a);var b=a.offsetWidth-a.clientWidth;return this.$body[0].removeChild(a),b};var d=a.fn.modal;a.fn.modal=b,a.fn.modal.Constructor=c,a.fn.modal.noConflict=function(){return a.fn.modal=d,this},a(document).on("click.bs.modal.data-api",'[data-toggle="modal"]',function(c){var d=a(this),e=d.attr("href"),f=a(d.attr("data-target")||e&&e.replace(/.*(?=#[^\s]+$)/,"")),g=f.data("bs.modal")?"toggle":a.extend({remote:!/#/.test(e)&&e},f.data(),d.data());d.is("a")&&c.preventDefault(),f.one("show.bs.modal",function(a){a.isDefaultPrevented()||f.one("hidden.bs.modal",function(){d.is(":visible")&&d.trigger("focus")})}),b.call(f,g,this)})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tooltip"),f="object"==typeof b&&b;(e||"destroy"!=b)&&(e||d.data("bs.tooltip",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.type=this.options=this.enabled=this.timeout=this.hoverState=this.$element=null,this.init("tooltip",a,b)};c.VERSION="3.3.2",c.TRANSITION_DURATION=150,c.DEFAULTS={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(this.options.viewport.selector||this.options.viewport);for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c&&c.$tip&&c.$tip.is(":visible")?void(c.hoverState="in"):(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.options.container?a(this.options.container):this.$element.parent(),p=this.getPosition(o);h="bottom"==h&&k.bottom+m>p.bottom?"top":"top"==h&&k.top-m<p.top?"bottom":"right"==h&&k.right+l>p.width?"left":"left"==h&&k.left-l<p.left?"right":h,f.removeClass(n).addClass(h)}var q=this.getCalculatedOffset(h,k,l,m);this.applyPlacement(q,h);var r=function(){var a=e.hoverState;e.$element.trigger("shown.bs."+e.type),e.hoverState=null,"out"==a&&e.leave(e)};a.support.transition&&this.$tip.hasClass("fade")?f.one("bsTransitionEnd",r).emulateTransitionEnd(c.TRANSITION_DURATION):r()}},c.prototype.applyPlacement=function(b,c){var d=this.tip(),e=d[0].offsetWidth,f=d[0].offsetHeight,g=parseInt(d.css("margin-top"),10),h=parseInt(d.css("margin-left"),10);isNaN(g)&&(g=0),isNaN(h)&&(h=0),b.top=b.top+g,b.left=b.left+h,a.offset.setOffset(d[0],a.extend({using:function(a){d.css({top:Math.round(a.top),left:Math.round(a.left)})}},b),0),d.addClass("in");var i=d[0].offsetWidth,j=d[0].offsetHeight;"top"==c&&j!=f&&(b.top=b.top+f-j);var k=this.getViewportAdjustedDelta(c,b,i,j);k.left?b.left+=k.left:b.top+=k.top;var l=/top|bottom/.test(c),m=l?2*k.left-e+i:2*k.top-f+j,n=l?"offsetWidth":"offsetHeight";d.offset(b),this.replaceArrow(m,d[0][n],l)},c.prototype.replaceArrow=function(a,b,c){this.arrow().css(c?"left":"top",50*(1-a/b)+"%").css(c?"top":"left","")},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle();a.find(".tooltip-inner")[this.options.html?"html":"text"](b),a.removeClass("fade in top bottom left right")},c.prototype.hide=function(b){function d(){"in"!=e.hoverState&&f.detach(),e.$element.removeAttr("aria-describedby").trigger("hidden.bs."+e.type),b&&b()}var e=this,f=this.tip(),g=a.Event("hide.bs."+this.type);return this.$element.trigger(g),g.isDefaultPrevented()?void 0:(f.removeClass("in"),a.support.transition&&this.$tip.hasClass("fade")?f.one("bsTransitionEnd",d).emulateTransitionEnd(c.TRANSITION_DURATION):d(),this.hoverState=null,this)},c.prototype.fixTitle=function(){var a=this.$element;(a.attr("title")||"string"!=typeof a.attr("data-original-title"))&&a.attr("data-original-title",a.attr("title")||"").attr("title","")},c.prototype.hasContent=function(){return this.getTitle()},c.prototype.getPosition=function(b){b=b||this.$element;var c=b[0],d="BODY"==c.tagName,e=c.getBoundingClientRect();null==e.width&&(e=a.extend({},e,{width:e.right-e.left,height:e.bottom-e.top}));var f=d?{top:0,left:0}:b.offset(),g={scroll:d?document.documentElement.scrollTop||document.body.scrollTop:b.scrollTop()},h=d?{width:a(window).width(),height:a(window).height()}:null;return a.extend({},e,g,h,f)},c.prototype.getCalculatedOffset=function(a,b,c,d){return"bottom"==a?{top:b.top+b.height,left:b.left+b.width/2-c/2}:"top"==a?{top:b.top-d,left:b.left+b.width/2-c/2}:"left"==a?{top:b.top+b.height/2-d/2,left:b.left-c}:{top:b.top+b.height/2-d/2,left:b.left+b.width}},c.prototype.getViewportAdjustedDelta=function(a,b,c,d){var e={top:0,left:0};if(!this.$viewport)return e;var f=this.options.viewport&&this.options.viewport.padding||0,g=this.getPosition(this.$viewport);if(/right|left/.test(a)){var h=b.top-f-g.scroll,i=b.top+f-g.scroll+d;h<g.top?e.top=g.top-h:i>g.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;j<g.left?e.left=g.left-j:k>g.width&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){return this.$tip=this.$tip||a(this.options.template)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type)})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||"destroy"!=b)&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.2",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:'<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},c.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){var e=a.proxy(this.process,this);this.$body=a("body"),this.$scrollElement=a(a(c).is("body")?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",e),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.2",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b="offset",c=0;a.isWindow(this.$scrollElement[0])||(b="position",c=this.$scrollElement.scrollTop()),this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight();var d=this;this.$body.find(this.selector).map(function(){var d=a(this),e=d.data("target")||d.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[b]().top+c,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){d.offsets.push(this[0]),d.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b<e[0])return this.activeTarget=null,this.clear();for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(!e[a+1]||b<=e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,this.clear();var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate.bs.scrollspy")},b.prototype.clear=function(){a(this.selector).parentsUntil(this.options.target,".active").removeClass("active")};var d=a.fn.scrollspy;a.fn.scrollspy=c,a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=d,this},a(window).on("load.bs.scrollspy.data-api",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);c.call(b,b.data())})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new c(this)),"string"==typeof b&&e[b]()})}var c=function(b){this.element=a(b)};c.VERSION="3.3.2",c.TRANSITION_DURATION=150,c.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a"),f=a.Event("hide.bs.tab",{relatedTarget:b[0]}),g=a.Event("show.bs.tab",{relatedTarget:e[0]});if(e.trigger(f),b.trigger(g),!g.isDefaultPrevented()&&!f.isDefaultPrevented()){var h=a(d);this.activate(b.closest("li"),c),this.activate(h,h.parent(),function(){e.trigger({type:"hidden.bs.tab",relatedTarget:b[0]}),b.trigger({type:"shown.bs.tab",relatedTarget:e[0]})})}}},c.prototype.activate=function(b,d,e){function f(){g.removeClass("active").find("> .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e() +}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=this.unpin=this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.2",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return c>e?"top":!1;if("bottom"==this.affixed)return null!=c?e+this.unpin<=f.top?!1:"bottom":a-d>=e+g?!1:"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&c>=e?"top":null!=d&&i+j>=a-d?"bottom":!1},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=a("body").height();"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery);
\ No newline at end of file diff --git a/pyload/webui/themes/Next/lib/Bootstrap/js/npm.js b/pyload/webui/themes/Next/lib/Bootstrap/js/npm.js new file mode 100644 index 000000000..bf6aa8060 --- /dev/null +++ b/pyload/webui/themes/Next/lib/Bootstrap/js/npm.js @@ -0,0 +1,13 @@ +// This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. +require('../../js/transition.js') +require('../../js/alert.js') +require('../../js/button.js') +require('../../js/carousel.js') +require('../../js/collapse.js') +require('../../js/dropdown.js') +require('../../js/modal.js') +require('../../js/tooltip.js') +require('../../js/popover.js') +require('../../js/scrollspy.js') +require('../../js/tab.js') +require('../../js/affix.js')
\ No newline at end of file diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Alert.js b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Alert.js new file mode 100644 index 000000000..1e2d4180b --- /dev/null +++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Alert.js @@ -0,0 +1,45 @@ +/* +--- +name: MooDialog.Alert +description: Creates an Alert dialog +authors: Arian Stolwijk +license: MIT-style license +requires: MooDialog +provides: MooDialog.Alert +... +*/ + + +MooDialog.Alert = new Class({ + + Extends: MooDialog, + + options: { + okText: 'Ok', + focus: true, + textPClass: 'MooDialogAlert' + }, + + initialize: function(msg, options){ + this.parent(options); + + var okButton = new Element('button', { + events: { + click: this.close.bind(this) + }, + text: this.options.okText + }); + + this.setContent( + new Element('p.' + this.options.textPClass, {text: msg}), + new Element('div.buttons').adopt(okButton) + ); + if (this.options.autoOpen) this.open(); + + if (this.options.focus) this.addEvent('show', function(){ + okButton.focus() + }); + + } +}); + diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Confirm.js b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Confirm.js new file mode 100644 index 000000000..16f32e290 --- /dev/null +++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Confirm.js @@ -0,0 +1,80 @@ +/* +--- +name: MooDialog.Confirm +description: Creates an Confirm Dialog +authors: Arian Stolwijk +license: MIT-style license +requires: MooDialog +provides: [MooDialog.Confirm, Element.confirmLinkClick, Element.confirmFormSubmit] +... +*/ + + +MooDialog.Confirm = new Class({ + + Extends: MooDialog, + + options: { + okText: 'Ok', + cancelText: 'Cancel', + focus: true, + textPClass: 'MooDialogConfirm' + }, + + initialize: function(msg, fn, fn1, options){ + this.parent(options); + var emptyFn = function(){}, + self = this; + + var buttons = [ + {fn: fn || emptyFn, txt: this.options.okText}, + {fn: fn1 || emptyFn, txt: this.options.cancelText} + ].map(function(button){ + return new Element('button', { + events: { + click: function(){ + button.fn(); + self.close(); + } + }, + text: button.txt + }); + }); + + this.setContent( + new Element('p.' + this.options.textPClass, {text: msg}), + new Element('div.buttons').adopt(buttons) + ); + if (this.options.autoOpen) this.open(); + + if(this.options.focus) this.addEvent('show', function(){ + buttons[1].focus(); + }); + + } +}); + + +Element.implement({ + + confirmLinkClick: function(msg, options){ + this.addEvent('click', function(e){ + e.stop(); + new MooDialog.Confirm(msg, function(){ + location.href = this.get('href'); + }.bind(this), null, options) + }); + return this; + }, + + confirmFormSubmit: function(msg, options){ + this.addEvent('submit', function(e){ + e.stop(); + new MooDialog.Confirm(msg, function(){ + this.submit(); + }.bind(this), null, options) + }.bind(this)); + return this; + } + +}); diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Error.js b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Error.js new file mode 100644 index 000000000..d32e30bce --- /dev/null +++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Error.js @@ -0,0 +1,21 @@ +/* +--- +name: MooDialog.Error +description: Creates an Error dialog +authors: Arian Stolwijk +license: MIT-style license +requires: MooDialog +provides: MooDialog.Error +... +*/ + + +MooDialog.Error = new Class({ + + Extends: MooDialog.Alert, + + options: { + textPClass: 'MooDialogError' + } + +}); diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Fx.js b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Fx.js new file mode 100644 index 000000000..353d947f5 --- /dev/null +++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Fx.js @@ -0,0 +1,47 @@ +/* +--- +name: MooDialog.Fx +description: Overwrite the default events so the Dialogs are using Fx on open and close +authors: Arian Stolwijk +license: MIT-style license +requires: [Core/Fx.Tween, Overlay] +provides: MooDialog.Fx +... +*/ + + +MooDialog.implement('options', { + + duration: 400, + closeOnOverlayClick: true, + + onInitialize: function(wrapper){ + this.fx = new Fx.Tween(wrapper, { + property: 'opacity', + duration: this.options.duration + }).set(0); + this.overlay = new Overlay(this.options.inject, { + duration: this.options.duration + }); + if (this.options.closeOnOverlayClick) this.overlay.addEvent('click', this.close.bind(this)); + + this.addEvent('hide', function(){ + if (this.options.destroyOnHide) this.overlay.overlay.destroy(); + }.bind(this)); + }, + + onBeforeOpen: function(wrapper){ + this.overlay.open(); + this.fx.start(1).chain(function(){ + this.fireEvent('show'); + }.bind(this)); + }, + + onBeforeClose: function(wrapper){ + this.overlay.close(); + this.fx.start(0).chain(function(){ + this.fireEvent('hide'); + }.bind(this)); + } + +}); diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.IFrame.js b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.IFrame.js new file mode 100644 index 000000000..029bf1f09 --- /dev/null +++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.IFrame.js @@ -0,0 +1,33 @@ +/* +--- +name: MooDialog.IFrame +description: Opens an IFrame in a MooDialog +authors: Arian Stolwijk +license: MIT-style license +requires: MooDialog +provides: MooDialog.IFrame +... +*/ + + +MooDialog.IFrame = new Class({ + + Extends: MooDialog, + + options: { + useScrollBar: true + }, + + initialize: function(url, options){ + this.parent(options); + + this.setContent( + new Element('iframe', { + src: url, + frameborder: 0, + scrolling: this.options.useScrollBar ? 'auto' : 'no' + }) + ); + if (this.options.autoOpen) this.open(); + } +}); diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Prompt.js b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Prompt.js new file mode 100644 index 000000000..c693e4a58 --- /dev/null +++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Prompt.js @@ -0,0 +1,48 @@ +/* +--- +name: MooDialog.Prompt +description: Creates a Prompt dialog +authors: Arian Stolwijk +license: MIT-style license +requires: MooDialog +provides: MooDialog.Prompt +... +*/ + + +MooDialog.Prompt = new Class({ + + Extends: MooDialog, + + options: { + okText: 'Ok', + focus: true, + textPClass: 'MooDialogPrompt', + defaultValue: '' + }, + + initialize: function(msg, fn, options){ + this.parent(options); + if (!fn) fn = function(){}; + + var textInput = new Element('input.textInput', {type: 'text', value: this.options.defaultValue}), + submitButton = new Element('input[type=submit]', {value: this.options.okText}), + formEvents = { + submit: function(e){ + e.stop(); + fn(textInput.get('value')); + this.close(); + }.bind(this) + }; + + this.setContent( + new Element('p.' + this.options.textPClass, {text: msg}), + new Element('form.buttons', {events: formEvents}).adopt(textInput, submitButton) + ); + if (this.options.autoOpen) this.open(); + + if (this.options.focus) this.addEvent('show', function(){ + textInput.focus(); + }); + } +}); diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Request.js b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Request.js new file mode 100644 index 000000000..7b8eb23c4 --- /dev/null +++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.Request.js @@ -0,0 +1,37 @@ +/* +--- +name: MooDialog.Request +description: Loads Data into a Dialog with Request +authors: Arian Stolwijk +license: MIT-style license +requires: [MooDialog, Core/Request.HTML] +provides: MooDialog.Request +... +*/ + +MooDialog.Request = new Class({ + + Extends: MooDialog, + + initialize: function(url, requestOptions, options){ + this.parent(options); + this.requestOptions = requestOptions || {}; + + this.addEvent('open', function(){ + var request = new Request.HTML(this.requestOptions).addEvent('success', function(text){ + this.setContent(text); + }.bind(this)).send({ + url: url + }); + }.bind(this)); + + if (this.options.autoOpen) this.open(); + + }, + + setRequestOptions: function(options){ + this.requestOptions = Object.merge(this.requestOptions, options); + return this; + } + +}); diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.js b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.js new file mode 100644 index 000000000..45a52496f --- /dev/null +++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/MooDialog.js @@ -0,0 +1,140 @@ +/* +--- +name: MooDialog +description: The base class of MooDialog +authors: Arian Stolwijk +license: MIT-style license +requires: [Core/Class, Core/Element, Core/Element.Style, Core/Element.Event] +provides: [MooDialog, Element.MooDialog] +... +*/ + + +var MooDialog = new Class({ + + Implements: [Options, Events], + + options: { + 'class': 'MooDialog', + title: null, + scroll: true, // IE + forceScroll: false, + useEscKey: true, + destroyOnHide: true, + autoOpen: true, + closeButton: true, + onInitialize: function(){ + this.wrapper.setStyle('display', 'none'); + }, + onBeforeOpen: function(){ + this.wrapper.setStyle('display', 'block'); + this.fireEvent('show'); + }, + onBeforeClose: function(){ + this.wrapper.setStyle('display', 'none'); + this.fireEvent('hide'); + }/*, + onOpen: function(){}, + onClose: function(){}, + onShow: function(){}, + onHide: function(){}, + onInitialize: function(wrapper){}, + onContentChange: function(content){}*/ + }, + + initialize: function(options){ + this.setOptions(options); + this.options.inject = this.options.inject || document.body; + options = this.options; + + var wrapper = this.wrapper = new Element('div.' + options['class'].replace(' ', '.')).inject(options.inject); + this.content = new Element('div.content').inject(wrapper); + + if (options.title){ + this.title = new Element('div.title').set('text', options.title).inject(wrapper); + wrapper.addClass('MooDialogTitle'); + } + + if (options.closeButton){ + this.closeButton = new Element('a.close', { + events: {click: this.close.bind(this)} + }).inject(wrapper); + } + + + /*<ie6>*/// IE 6 scroll + if ((options.scroll && Browser.ie6) || options.forceScroll){ + wrapper.setStyle('position', 'absolute'); + var position = wrapper.getPosition(options.inject); + window.addEvent('scroll', function(){ + var scroll = document.getScroll(); + wrapper.setPosition({ + x: position.x + scroll.x, + y: position.y + scroll.y + }); + }); + } + /*</ie6>*/ + + if (options.useEscKey){ + // Add event for the esc key + document.addEvent('keydown', function(e){ + if (e.key == 'esc') this.close(); + }.bind(this)); + } + + this.addEvent('hide', function(){ + if (options.destroyOnHide) this.destroy(); + }.bind(this)); + + this.fireEvent('initialize', wrapper); + }, + + setContent: function(){ + var content = Array.from(arguments); + if (content.length == 1) content = content[0]; + + this.content.empty(); + + var type = typeOf(content); + if (['string', 'number'].contains(type)) this.content.set('text', content); + else this.content.adopt(content); + + this.fireEvent('contentChange', this.content); + + return this; + }, + + open: function(){ + this.fireEvent('beforeOpen', this.wrapper).fireEvent('open'); + this.opened = true; + return this; + }, + + close: function(){ + this.fireEvent('beforeClose', this.wrapper).fireEvent('close'); + this.opened = false; + return this; + }, + + destroy: function(){ + this.wrapper.destroy(); + }, + + toElement: function(){ + return this.wrapper; + } + +}); + + +Element.implement({ + + MooDialog: function(options){ + this.store('MooDialog', + new MooDialog(options).setContent(this).open() + ); + return this; + } + +}); diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDialog/Overlay.js b/pyload/webui/themes/Next/lib/MooTools/MooDialog/Overlay.js new file mode 100644 index 000000000..35ab19c48 --- /dev/null +++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/Overlay.js @@ -0,0 +1,137 @@ +/* +--- + +name: Overlay + +authors: + - David Walsh (http://davidwalsh.name) + +license: + - MIT-style license + +requires: [Core/Class, Core/Element.Style, Core/Element.Event, Core/Element.Dimensions, Core/Fx.Tween] + +provides: + - Overlay +... +*/ + +var Overlay = new Class({ + + Implements: [Options, Events], + + options: { + id: 'overlay', + color: '#000', + duration: 500, + opacity: 0.5, + zIndex: 5000/*, + onClick: function(){}, + onClose: function(){}, + onHide: function(){}, + onOpen: function(){}, + onShow: function(){} + */ + }, + + initialize: function(container, options){ + this.setOptions(options); + this.container = document.id(container); + + this.bound = { + 'window': { + resize: this.resize.bind(this), + scroll: this.scroll.bind(this) + }, + overlayClick: this.overlayClick.bind(this), + tweenStart: this.tweenStart.bind(this), + tweenComplete: this.tweenComplete.bind(this) + }; + + this.build().attach(); + }, + + build: function(){ + this.overlay = new Element('div', { + id: this.options.id, + styles: { + position: (Browser.ie6) ? 'absolute' : 'fixed', + background: this.options.color, + left: 0, + top: 0, + 'z-index': this.options.zIndex, + opacity: 0 + } + }).inject(this.container); + this.tween = new Fx.Tween(this.overlay, { + duration: this.options.duration, + link: 'cancel', + property: 'opacity' + }); + return this; + }.protect(), + + attach: function(){ + window.addEvents(this.bound.window); + this.overlay.addEvent('click', this.bound.overlayClick); + this.tween.addEvents({ + onStart: this.bound.tweenStart, + onComplete: this.bound.tweenComplete + }); + return this; + }, + + detach: function(){ + var args = Array.prototype.slice.call(arguments); + args.each(function(item){ + if(item == 'window') window.removeEvents(this.bound.window); + if(item == 'overlay') this.overlay.removeEvent('click', this.bound.overlayClick); + }, this); + return this; + }, + + overlayClick: function(){ + this.fireEvent('click'); + return this; + }, + + tweenStart: function(){ + this.overlay.setStyles({ + width: '100%', + height: this.container.getScrollSize().y, + visibility: 'visible' + }); + return this; + }, + + tweenComplete: function(){ + var event = this.overlay.getStyle('opacity') == this.options.opacity ? 'show' : 'hide'; + if (event == 'hide') this.overlay.setStyle('visibility', 'hidden'); + return this; + }, + + open: function(){ + this.fireEvent('open'); + this.tween.start(this.options.opacity); + return this; + }, + + close: function(){ + this.fireEvent('close'); + this.tween.start(0); + return this; + }, + + resize: function(){ + this.fireEvent('resize'); + this.overlay.setStyle('height', this.container.getScrollSize().y); + return this; + }, + + scroll: function(){ + this.fireEvent('scroll'); + if (Browser.ie6) this.overlay.setStyle('left', window.getScroll().x); + return this; + } + +}); diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/MooDialog.css b/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/MooDialog.css new file mode 100644 index 000000000..c88773ae9 --- /dev/null +++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/MooDialog.css @@ -0,0 +1,95 @@ +/* Created by Arian Stolwijk <http://www.aryweb.nl> */ + +.MooDialog { + position: fixed; + width: 300px; + height: 100px; + top: 50%; + left: 50%; + margin: -150px 0 0 -150px; + padding: 10px; + z-index: 50000; + + background: #eef5f8; + color: black; + border-radius: 7px; + -moz-border-radius: 7px; + -webkit-border-radius: 7px; + border-radius: 7px; + -moz-box-shadow: 1px 1px 5px rgba(0,0,0,0.8); + -webkit-box-shadow: 1px 1px 5px rgba(0,0,0,0.8); + box-shadow: 1px 1px 5px rgba(0,0,0,0.8); +} + +.MooDialogTitle { + padding-top: 30px; +} + +.MooDialog .content { + height: 100px; +} + +.MooDialog .title { + position: absolute; + top: 0; + left: 0; + right: 0; + padding: 3px 20px; + + background: #b7c4dc; + border-bottom: 1px solid #a1aec5; + font-weight: bold; + text-shadow: 1px 1px 0 #fff; + color: black; + border-radius: 7px; + -moz-border-radius: 7px; + -webkit-border-radius: 7px; +} + +.MooDialog .close { + position: absolute; + width: 16px; + height: 16px; + top: -5px; + left: -5px; + + background: url(dialog-close.png) no-repeat; + display: block; + cursor: pointer; +} + +.MooDialog .buttons { + margin: 0; + padding: 0; + border: 0; + background: none; + text-align: right; +} + +.MooDialog .iframe { + width: 100%; + height: 100%; +} + +.MooDialog .textInput { + width: 200px; + float: left; +} + +.MooDialog .MooDialogAlert, +.MooDialog .MooDialogConfirm, +.MooDialog .MooDialogPrompt, +.MooDialog .MooDialogError { + padding-left: 40px; + min-height: 40px; + background: url(dialog-warning.png) no-repeat; +} + +.MooDialog .MooDialogConfirm, +.MooDialog .MooDialogPrompt { + background: url(dialog-question.png) no-repeat; +} + +.MooDialog .MooDialogError { + background: url(dialog-error.png) no-repeat; +} diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-close.png b/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-close.png Binary files differnew file mode 100644 index 000000000..81ebb88b2 --- /dev/null +++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-close.png diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-error.png b/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-error.png Binary files differnew file mode 100644 index 000000000..d70328403 --- /dev/null +++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-error.png diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-question.png b/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-question.png Binary files differnew file mode 100644 index 000000000..b0af3db5b --- /dev/null +++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-question.png diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-warning.png b/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-warning.png Binary files differnew file mode 100644 index 000000000..aad64d4be --- /dev/null +++ b/pyload/webui/themes/Next/lib/MooTools/MooDialog/css/dialog-warning.png diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDropMenu/MooDropMenu.js b/pyload/webui/themes/Next/lib/MooTools/MooDropMenu/MooDropMenu.js new file mode 100644 index 000000000..ac0fa1874 --- /dev/null +++ b/pyload/webui/themes/Next/lib/MooTools/MooDropMenu/MooDropMenu.js @@ -0,0 +1,86 @@ +/* +--- +description: This provides a simple Drop Down menu with infinit levels + +license: MIT-style + +authors: +- Arian Stolwijk + +requires: + - Core/Class.Extras + - Core/Element.Event + - Core/Selectors + +provides: [MooDropMenu, Element.MooDropMenu] + +... +*/ + +var MooDropMenu = new Class({ + + Implements: [Options, Events], + + options: { + onOpen: function(el){ + el.removeClass('close').addClass('open'); + }, + onClose: function(el){ + el.removeClass('open').addClass('close'); + }, + onInitialize: function(el){ + el.removeClass('open').addClass('close'); + }, + mouseoutDelay: 200, + mouseoverDelay: 0, + listSelector: 'ul', + itemSelector: 'li', + openEvent: 'mouseenter', + closeEvent: 'mouseleave' + }, + + initialize: function(menu, options, level){ + this.setOptions(options); + options = this.options; + + var menu = this.menu = document.id(menu); + + menu.getElements(options.itemSelector + ' > ' + options.listSelector).each(function(el){ + + this.fireEvent('initialize', el); + + var parent = el.getParent(options.itemSelector), + timer; + + parent.addEvent(options.openEvent, function(){ + parent.store('DropDownOpen', true); + + clearTimeout(timer); + if (options.mouseoverDelay) timer = this.fireEvent.delay(options.mouseoverDelay, this, ['open', el]); + else this.fireEvent('open', el); + + }.bind(this)).addEvent(options.closeEvent, function(){ + parent.store('DropDownOpen', false); + + clearTimeout(timer); + timer = (function(){ + if (!parent.retrieve('DropDownOpen')) this.fireEvent('close', el); + }).delay(options.mouseoutDelay, this); + + }.bind(this)); + + }, this); + }, + + toElement: function(){ + return this.menu + } + +}); + +/* So you can do like this $('nav').MooDropMenu(); or even $('nav').MooDropMenu().setStyle('border',1); */ +Element.implement({ + MooDropMenu: function(options){ + return this.store('MooDropMenu', new MooDropMenu(this, options)); + } +}); diff --git a/pyload/webui/themes/Next/lib/MooTools/MooDropMenu/css/MooDropMenu.css b/pyload/webui/themes/Next/lib/MooTools/MooDropMenu/css/MooDropMenu.css new file mode 100644 index 000000000..e08c2f9fa --- /dev/null +++ b/pyload/webui/themes/Next/lib/MooTools/MooDropMenu/css/MooDropMenu.css @@ -0,0 +1,66 @@ +#nav { + margin: 0; + padding: 0; + list-style: none; +} + +#nav > li { + background: #F5F5F5; + border-bottom: 1px solid #aaa; +} + +#nav li { + position: relative; + float: left; + padding: 5px; +} + + +#nav ul { + position: absolute; + top: 26px; + left: 10px; + margin: 0; + padding: 0; + list-style: none; + width: 136px; + border: 1px solid #AAA; + background: #f1f1f1; + -webkit-box-shadow: 1px 1px 5px #AAA; + -moz-box-shadow: 1px 1px 5px #AAA; + box-shadow: 1px 1px 5px #AAA; +} + + +#nav .open { + display: block; +} + +#nav .close { + display: none; +} + +#nav ul li { + float: none; + padding: 0; +} + +#nav ul li a { + width: 130px; + _width: 127px; + background: #f1f1f1; + padding: 3px; + display: block; + _float: left; + font-weight: normal; +} + +#nav ul li a:hover { + background: #CDCDCD; +} + +#nav ul ul { + left: 137px; + _left: 0; + top: 0; +} diff --git a/pyload/webui/themes/Next/lib/MooTools/MooTools-Core.js b/pyload/webui/themes/Next/lib/MooTools/MooTools-Core.js new file mode 100644 index 000000000..e0bea6df6 --- /dev/null +++ b/pyload/webui/themes/Next/lib/MooTools/MooTools-Core.js @@ -0,0 +1,6068 @@ +/* MooTools: the javascript framework. license: MIT-style license. copyright: Copyright (c) 2006-2015 [Valerio Proietti](http://mad4milk.net/).*/ +/* +Web Build: http://mootools.net/core/builder/e426a9ae7167c5807b173d5deff673fc +*/ +/* +--- + +name: Core + +description: The heart of MooTools. + +license: MIT-style license. + +copyright: Copyright (c) 2006-2014 [Valerio Proietti](http://mad4milk.net/). + +authors: The MooTools production team (http://mootools.net/developers/) + +inspiration: + - Class implementation inspired by [Base.js](http://dean.edwards.name/weblog/2006/03/base/) Copyright (c) 2006 Dean Edwards, [GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php) + - Some functionality inspired by [Prototype.js](http://prototypejs.org) Copyright (c) 2005-2007 Sam Stephenson, [MIT License](http://opensource.org/licenses/mit-license.php) + +provides: [Core, MooTools, Type, typeOf, instanceOf, Native] + +... +*/ +/*! MooTools: the javascript framework. license: MIT-style license. copyright: Copyright (c) 2006-2014 [Valerio Proietti](http://mad4milk.net/).*/ +(function(){ + +this.MooTools = { + version: '1.5.1', + build: '0542c135fdeb7feed7d9917e01447a408f22c876' +}; + +// typeOf, instanceOf + +var typeOf = this.typeOf = function(item){ + if (item == null) return 'null'; + if (item.$family != null) return item.$family(); + + if (item.nodeName){ + if (item.nodeType == 1) return 'element'; + if (item.nodeType == 3) return (/\S/).test(item.nodeValue) ? 'textnode' : 'whitespace'; + } else if (typeof item.length == 'number'){ + if ('callee' in item) return 'arguments'; + if ('item' in item) return 'collection'; + } + + return typeof item; +}; + +var instanceOf = this.instanceOf = function(item, object){ + if (item == null) return false; + var constructor = item.$constructor || item.constructor; + while (constructor){ + if (constructor === object) return true; + constructor = constructor.parent; + } + /*<ltIE8>*/ + if (!item.hasOwnProperty) return false; + /*</ltIE8>*/ + return item instanceof object; +}; + +// Function overloading + +var Function = this.Function; + +var enumerables = true; +for (var i in {toString: 1}) enumerables = null; +if (enumerables) enumerables = ['hasOwnProperty', 'valueOf', 'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString', 'toString', 'constructor']; + +Function.prototype.overloadSetter = function(usePlural){ + var self = this; + return function(a, b){ + if (a == null) return this; + if (usePlural || typeof a != 'string'){ + for (var k in a) self.call(this, k, a[k]); + if (enumerables) for (var i = enumerables.length; i--;){ + k = enumerables[i]; + if (a.hasOwnProperty(k)) self.call(this, k, a[k]); + } + } else { + self.call(this, a, b); + } + return this; + }; +}; + +Function.prototype.overloadGetter = function(usePlural){ + var self = this; + return function(a){ + var args, result; + if (typeof a != 'string') args = a; + else if (arguments.length > 1) args = arguments; + else if (usePlural) args = [a]; + if (args){ + result = {}; + for (var i = 0; i < args.length; i++) result[args[i]] = self.call(this, args[i]); + } else { + result = self.call(this, a); + } + return result; + }; +}; + +Function.prototype.extend = function(key, value){ + this[key] = value; +}.overloadSetter(); + +Function.prototype.implement = function(key, value){ + this.prototype[key] = value; +}.overloadSetter(); + +// From + +var slice = Array.prototype.slice; + +Function.from = function(item){ + return (typeOf(item) == 'function') ? item : function(){ + return item; + }; +}; + +Array.from = function(item){ + if (item == null) return []; + return (Type.isEnumerable(item) && typeof item != 'string') ? (typeOf(item) == 'array') ? item : slice.call(item) : [item]; +}; + +Number.from = function(item){ + var number = parseFloat(item); + return isFinite(number) ? number : null; +}; + +String.from = function(item){ + return item + ''; +}; + +// hide, protect + +Function.implement({ + + hide: function(){ + this.$hidden = true; + return this; + }, + + protect: function(){ + this.$protected = true; + return this; + } + +}); + +// Type + +var Type = this.Type = function(name, object){ + if (name){ + var lower = name.toLowerCase(); + var typeCheck = function(item){ + return (typeOf(item) == lower); + }; + + Type['is' + name] = typeCheck; + if (object != null){ + object.prototype.$family = (function(){ + return lower; + }).hide(); + + } + } + + if (object == null) return null; + + object.extend(this); + object.$constructor = Type; + object.prototype.$constructor = object; + + return object; +}; + +var toString = Object.prototype.toString; + +Type.isEnumerable = function(item){ + return (item != null && typeof item.length == 'number' && toString.call(item) != '[object Function]' ); +}; + +var hooks = {}; + +var hooksOf = function(object){ + var type = typeOf(object.prototype); + return hooks[type] || (hooks[type] = []); +}; + +var implement = function(name, method){ + if (method && method.$hidden) return; + + var hooks = hooksOf(this); + + for (var i = 0; i < hooks.length; i++){ + var hook = hooks[i]; + if (typeOf(hook) == 'type') implement.call(hook, name, method); + else hook.call(this, name, method); + } + + var previous = this.prototype[name]; + if (previous == null || !previous.$protected) this.prototype[name] = method; + + if (this[name] == null && typeOf(method) == 'function') extend.call(this, name, function(item){ + return method.apply(item, slice.call(arguments, 1)); + }); +}; + +var extend = function(name, method){ + if (method && method.$hidden) return; + var previous = this[name]; + if (previous == null || !previous.$protected) this[name] = method; +}; + +Type.implement({ + + implement: implement.overloadSetter(), + + extend: extend.overloadSetter(), + + alias: function(name, existing){ + implement.call(this, name, this.prototype[existing]); + }.overloadSetter(), + + mirror: function(hook){ + hooksOf(this).push(hook); + return this; + } + +}); + +new Type('Type', Type); + +// Default Types + +var force = function(name, object, methods){ + var isType = (object != Object), + prototype = object.prototype; + + if (isType) object = new Type(name, object); + + for (var i = 0, l = methods.length; i < l; i++){ + var key = methods[i], + generic = object[key], + proto = prototype[key]; + + if (generic) generic.protect(); + if (isType && proto) object.implement(key, proto.protect()); + } + + if (isType){ + var methodsEnumerable = prototype.propertyIsEnumerable(methods[0]); + object.forEachMethod = function(fn){ + if (!methodsEnumerable) for (var i = 0, l = methods.length; i < l; i++){ + fn.call(prototype, prototype[methods[i]], methods[i]); + } + for (var key in prototype) fn.call(prototype, prototype[key], key); + }; + } + + return force; +}; + +force('String', String, [ + 'charAt', 'charCodeAt', 'concat', 'contains', 'indexOf', 'lastIndexOf', 'match', 'quote', 'replace', 'search', + 'slice', 'split', 'substr', 'substring', 'trim', 'toLowerCase', 'toUpperCase' +])('Array', Array, [ + 'pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift', 'concat', 'join', 'slice', + 'indexOf', 'lastIndexOf', 'filter', 'forEach', 'every', 'map', 'some', 'reduce', 'reduceRight' +])('Number', Number, [ + 'toExponential', 'toFixed', 'toLocaleString', 'toPrecision' +])('Function', Function, [ + 'apply', 'call', 'bind' +])('RegExp', RegExp, [ + 'exec', 'test' +])('Object', Object, [ + 'create', 'defineProperty', 'defineProperties', 'keys', + 'getPrototypeOf', 'getOwnPropertyDescriptor', 'getOwnPropertyNames', + 'preventExtensions', 'isExtensible', 'seal', 'isSealed', 'freeze', 'isFrozen' +])('Date', Date, ['now']); + +Object.extend = extend.overloadSetter(); + +Date.extend('now', function(){ + return +(new Date); +}); + +new Type('Boolean', Boolean); + +// fixes NaN returning as Number + +Number.prototype.$family = function(){ + return isFinite(this) ? 'number' : 'null'; +}.hide(); + +// Number.random + +Number.extend('random', function(min, max){ + return Math.floor(Math.random() * (max - min + 1) + min); +}); + +// forEach, each + +var hasOwnProperty = Object.prototype.hasOwnProperty; +Object.extend('forEach', function(object, fn, bind){ + for (var key in object){ + if (hasOwnProperty.call(object, key)) fn.call(bind, object[key], key, object); + } +}); + +Object.each = Object.forEach; + +Array.implement({ + + /*<!ES5>*/ + forEach: function(fn, bind){ + for (var i = 0, l = this.length; i < l; i++){ + if (i in this) fn.call(bind, this[i], i, this); + } + }, + /*</!ES5>*/ + + each: function(fn, bind){ + Array.forEach(this, fn, bind); + return this; + } + +}); + +// Array & Object cloning, Object merging and appending + +var cloneOf = function(item){ + switch (typeOf(item)){ + case 'array': return item.clone(); + case 'object': return Object.clone(item); + default: return item; + } +}; + +Array.implement('clone', function(){ + var i = this.length, clone = new Array(i); + while (i--) clone[i] = cloneOf(this[i]); + return clone; +}); + +var mergeOne = function(source, key, current){ + switch (typeOf(current)){ + case 'object': + if (typeOf(source[key]) == 'object') Object.merge(source[key], current); + else source[key] = Object.clone(current); + break; + case 'array': source[key] = current.clone(); break; + default: source[key] = current; + } + return source; +}; + +Object.extend({ + + merge: function(source, k, v){ + if (typeOf(k) == 'string') return mergeOne(source, k, v); + for (var i = 1, l = arguments.length; i < l; i++){ + var object = arguments[i]; + for (var key in object) mergeOne(source, key, object[key]); + } + return source; + }, + + clone: function(object){ + var clone = {}; + for (var key in object) clone[key] = cloneOf(object[key]); + return clone; + }, + + append: function(original){ + for (var i = 1, l = arguments.length; i < l; i++){ + var extended = arguments[i] || {}; + for (var key in extended) original[key] = extended[key]; + } + return original; + } + +}); + +// Object-less types + +['Object', 'WhiteSpace', 'TextNode', 'Collection', 'Arguments'].each(function(name){ + new Type(name); +}); + +// Unique ID + +var UID = Date.now(); + +String.extend('uniqueID', function(){ + return (UID++).toString(36); +}); + + + +})(); + +/* +--- + +name: Array + +description: Contains Array Prototypes like each, contains, and erase. + +license: MIT-style license. + +requires: [Type] + +provides: Array + +... +*/ + +Array.implement({ + + /*<!ES5>*/ + every: function(fn, bind){ + for (var i = 0, l = this.length >>> 0; i < l; i++){ + if ((i in this) && !fn.call(bind, this[i], i, this)) return false; + } + return true; + }, + + filter: function(fn, bind){ + var results = []; + for (var value, i = 0, l = this.length >>> 0; i < l; i++) if (i in this){ + value = this[i]; + if (fn.call(bind, value, i, this)) results.push(value); + } + return results; + }, + + indexOf: function(item, from){ + var length = this.length >>> 0; + for (var i = (from < 0) ? Math.max(0, length + from) : from || 0; i < length; i++){ + if (this[i] === item) return i; + } + return -1; + }, + + map: function(fn, bind){ + var length = this.length >>> 0, results = Array(length); + for (var i = 0; i < length; i++){ + if (i in this) results[i] = fn.call(bind, this[i], i, this); + } + return results; + }, + + some: function(fn, bind){ + for (var i = 0, l = this.length >>> 0; i < l; i++){ + if ((i in this) && fn.call(bind, this[i], i, this)) return true; + } + return false; + }, + /*</!ES5>*/ + + clean: function(){ + return this.filter(function(item){ + return item != null; + }); + }, + + invoke: function(methodName){ + var args = Array.slice(arguments, 1); + return this.map(function(item){ + return item[methodName].apply(item, args); + }); + }, + + associate: function(keys){ + var obj = {}, length = Math.min(this.length, keys.length); + for (var i = 0; i < length; i++) obj[keys[i]] = this[i]; + return obj; + }, + + link: function(object){ + var result = {}; + for (var i = 0, l = this.length; i < l; i++){ + for (var key in object){ + if (object[key](this[i])){ + result[key] = this[i]; + delete object[key]; + break; + } + } + } + return result; + }, + + contains: function(item, from){ + return this.indexOf(item, from) != -1; + }, + + append: function(array){ + this.push.apply(this, array); + return this; + }, + + getLast: function(){ + return (this.length) ? this[this.length - 1] : null; + }, + + getRandom: function(){ + return (this.length) ? this[Number.random(0, this.length - 1)] : null; + }, + + include: function(item){ + if (!this.contains(item)) this.push(item); + return this; + }, + + combine: function(array){ + for (var i = 0, l = array.length; i < l; i++) this.include(array[i]); + return this; + }, + + erase: function(item){ + for (var i = this.length; i--;){ + if (this[i] === item) this.splice(i, 1); + } + return this; + }, + + empty: function(){ + this.length = 0; + return this; + }, + + flatten: function(){ + var array = []; + for (var i = 0, l = this.length; i < l; i++){ + var type = typeOf(this[i]); + if (type == 'null') continue; + array = array.concat((type == 'array' || type == 'collection' || type == 'arguments' || instanceOf(this[i], Array)) ? Array.flatten(this[i]) : this[i]); + } + return array; + }, + + pick: function(){ + for (var i = 0, l = this.length; i < l; i++){ + if (this[i] != null) return this[i]; + } + return null; + }, + + hexToRgb: function(array){ + if (this.length != 3) return null; + var rgb = this.map(function(value){ + if (value.length == 1) value += value; + return parseInt(value, 16); + }); + return (array) ? rgb : 'rgb(' + rgb + ')'; + }, + + rgbToHex: function(array){ + if (this.length < 3) return null; + if (this.length == 4 && this[3] == 0 && !array) return 'transparent'; + var hex = []; + for (var i = 0; i < 3; i++){ + var bit = (this[i] - 0).toString(16); + hex.push((bit.length == 1) ? '0' + bit : bit); + } + return (array) ? hex : '#' + hex.join(''); + } + +}); + + + +/* +--- + +name: Function + +description: Contains Function Prototypes like create, bind, pass, and delay. + +license: MIT-style license. + +requires: Type + +provides: Function + +... +*/ + +Function.extend({ + + attempt: function(){ + for (var i = 0, l = arguments.length; i < l; i++){ + try { + return arguments[i](); + } catch (e){} + } + return null; + } + +}); + +Function.implement({ + + attempt: function(args, bind){ + try { + return this.apply(bind, Array.from(args)); + } catch (e){} + + return null; + }, + + /*<!ES5-bind>*/ + bind: function(that){ + var self = this, + args = arguments.length > 1 ? Array.slice(arguments, 1) : null, + F = function(){}; + + var bound = function(){ + var context = that, length = arguments.length; + if (this instanceof bound){ + F.prototype = self.prototype; + context = new F; + } + var result = (!args && !length) + ? self.call(context) + : self.apply(context, args && length ? args.concat(Array.slice(arguments)) : args || arguments); + return context == that ? result : context; + }; + return bound; + }, + /*</!ES5-bind>*/ + + pass: function(args, bind){ + var self = this; + if (args != null) args = Array.from(args); + return function(){ + return self.apply(bind, args || arguments); + }; + }, + + delay: function(delay, bind, args){ + return setTimeout(this.pass((args == null ? [] : args), bind), delay); + }, + + periodical: function(periodical, bind, args){ + return setInterval(this.pass((args == null ? [] : args), bind), periodical); + } + +}); + + + +/* +--- + +name: Number + +description: Contains Number Prototypes like limit, round, times, and ceil. + +license: MIT-style license. + +requires: Type + +provides: Number + +... +*/ + +Number.implement({ + + limit: function(min, max){ + return Math.min(max, Math.max(min, this)); + }, + + round: function(precision){ + precision = Math.pow(10, precision || 0).toFixed(precision < 0 ? -precision : 0); + return Math.round(this * precision) / precision; + }, + + times: function(fn, bind){ + for (var i = 0; i < this; i++) fn.call(bind, i, this); + }, + + toFloat: function(){ + return parseFloat(this); + }, + + toInt: function(base){ + return parseInt(this, base || 10); + } + +}); + +Number.alias('each', 'times'); + +(function(math){ + var methods = {}; + math.each(function(name){ + if (!Number[name]) methods[name] = function(){ + return Math[name].apply(null, [this].concat(Array.from(arguments))); + }; + }); + Number.implement(methods); +})(['abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', 'log', 'max', 'min', 'pow', 'sin', 'sqrt', 'tan']); + +/* +--- + +name: String + +description: Contains String Prototypes like camelCase, capitalize, test, and toInt. + +license: MIT-style license. + +requires: [Type, Array] + +provides: String + +... +*/ + +String.implement({ + + //<!ES6> + contains: function(string, index){ + return (index ? String(this).slice(index) : String(this)).indexOf(string) > -1; + }, + //</!ES6> + + test: function(regex, params){ + return ((typeOf(regex) == 'regexp') ? regex : new RegExp('' + regex, params)).test(this); + }, + + trim: function(){ + return String(this).replace(/^\s+|\s+$/g, ''); + }, + + clean: function(){ + return String(this).replace(/\s+/g, ' ').trim(); + }, + + camelCase: function(){ + return String(this).replace(/-\D/g, function(match){ + return match.charAt(1).toUpperCase(); + }); + }, + + hyphenate: function(){ + return String(this).replace(/[A-Z]/g, function(match){ + return ('-' + match.charAt(0).toLowerCase()); + }); + }, + + capitalize: function(){ + return String(this).replace(/\b[a-z]/g, function(match){ + return match.toUpperCase(); + }); + }, + + escapeRegExp: function(){ + return String(this).replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1'); + }, + + toInt: function(base){ + return parseInt(this, base || 10); + }, + + toFloat: function(){ + return parseFloat(this); + }, + + hexToRgb: function(array){ + var hex = String(this).match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/); + return (hex) ? hex.slice(1).hexToRgb(array) : null; + }, + + rgbToHex: function(array){ + var rgb = String(this).match(/\d{1,3}/g); + return (rgb) ? rgb.rgbToHex(array) : null; + }, + + substitute: function(object, regexp){ + return String(this).replace(regexp || (/\\?\{([^{}]+)\}/g), function(match, name){ + if (match.charAt(0) == '\\') return match.slice(1); + return (object[name] != null) ? object[name] : ''; + }); + } + +}); + + + +/* +--- + +name: Browser + +description: The Browser Object. Contains Browser initialization, Window and Document, and the Browser Hash. + +license: MIT-style license. + +requires: [Array, Function, Number, String] + +provides: [Browser, Window, Document] + +... +*/ + +(function(){ + +var document = this.document; +var window = document.window = this; + +var parse = function(ua, platform){ + ua = ua.toLowerCase(); + platform = (platform ? platform.toLowerCase() : ''); + + var UA = ua.match(/(opera|ie|firefox|chrome|trident|crios|version)[\s\/:]([\w\d\.]+)?.*?(safari|(?:rv[\s\/:]|version[\s\/:])([\w\d\.]+)|$)/) || [null, 'unknown', 0]; + + if (UA[1] == 'trident'){ + UA[1] = 'ie'; + if (UA[4]) UA[2] = UA[4]; + } else if (UA[1] == 'crios'){ + UA[1] = 'chrome'; + } + + platform = ua.match(/ip(?:ad|od|hone)/) ? 'ios' : (ua.match(/(?:webos|android)/) || platform.match(/mac|win|linux/) || ['other'])[0]; + if (platform == 'win') platform = 'windows'; + + return { + extend: Function.prototype.extend, + name: (UA[1] == 'version') ? UA[3] : UA[1], + version: parseFloat((UA[1] == 'opera' && UA[4]) ? UA[4] : UA[2]), + platform: platform + }; +}; + +var Browser = this.Browser = parse(navigator.userAgent, navigator.platform); + +if (Browser.name == 'ie'){ + Browser.version = document.documentMode; +} + +Browser.extend({ + Features: { + xpath: !!(document.evaluate), + air: !!(window.runtime), + query: !!(document.querySelector), + json: !!(window.JSON) + }, + parseUA: parse +}); + + + +// Request + +Browser.Request = (function(){ + + var XMLHTTP = function(){ + return new XMLHttpRequest(); + }; + + var MSXML2 = function(){ + return new ActiveXObject('MSXML2.XMLHTTP'); + }; + + var MSXML = function(){ + return new ActiveXObject('Microsoft.XMLHTTP'); + }; + + return Function.attempt(function(){ + XMLHTTP(); + return XMLHTTP; + }, function(){ + MSXML2(); + return MSXML2; + }, function(){ + MSXML(); + return MSXML; + }); + +})(); + +Browser.Features.xhr = !!(Browser.Request); + + + +// String scripts + +Browser.exec = function(text){ + if (!text) return text; + if (window.execScript){ + window.execScript(text); + } else { + var script = document.createElement('script'); + script.setAttribute('type', 'text/javascript'); + script.text = text; + document.head.appendChild(script); + document.head.removeChild(script); + } + return text; +}; + +String.implement('stripScripts', function(exec){ + var scripts = ''; + var text = this.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi, function(all, code){ + scripts += code + '\n'; + return ''; + }); + if (exec === true) Browser.exec(scripts); + else if (typeOf(exec) == 'function') exec(scripts, text); + return text; +}); + +// Window, Document + +Browser.extend({ + Document: this.Document, + Window: this.Window, + Element: this.Element, + Event: this.Event +}); + +this.Window = this.$constructor = new Type('Window', function(){}); + +this.$family = Function.from('window').hide(); + +Window.mirror(function(name, method){ + window[name] = method; +}); + +this.Document = document.$constructor = new Type('Document', function(){}); + +document.$family = Function.from('document').hide(); + +Document.mirror(function(name, method){ + document[name] = method; +}); + +document.html = document.documentElement; +if (!document.head) document.head = document.getElementsByTagName('head')[0]; + +if (document.execCommand) try { + document.execCommand("BackgroundImageCache", false, true); +} catch (e){} + +/*<ltIE9>*/ +if (this.attachEvent && !this.addEventListener){ + var unloadEvent = function(){ + this.detachEvent('onunload', unloadEvent); + document.head = document.html = document.window = null; + window = this.Window = document = null; + }; + this.attachEvent('onunload', unloadEvent); +} + +// IE fails on collections and <select>.options (refers to <select>) +var arrayFrom = Array.from; +try { + arrayFrom(document.html.childNodes); +} catch(e){ + Array.from = function(item){ + if (typeof item != 'string' && Type.isEnumerable(item) && typeOf(item) != 'array'){ + var i = item.length, array = new Array(i); + while (i--) array[i] = item[i]; + return array; + } + return arrayFrom(item); + }; + + var prototype = Array.prototype, + slice = prototype.slice; + ['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift', 'concat', 'join', 'slice'].each(function(name){ + var method = prototype[name]; + Array[name] = function(item){ + return method.apply(Array.from(item), slice.call(arguments, 1)); + }; + }); +} +/*</ltIE9>*/ + + + +})(); + +/* +--- + +name: Class + +description: Contains the Class Function for easily creating, extending, and implementing reusable Classes. + +license: MIT-style license. + +requires: [Array, String, Function, Number] + +provides: Class + +... +*/ + +(function(){ + +var Class = this.Class = new Type('Class', function(params){ + if (instanceOf(params, Function)) params = {initialize: params}; + + var newClass = function(){ + reset(this); + if (newClass.$prototyping) return this; + this.$caller = null; + var value = (this.initialize) ? this.initialize.apply(this, arguments) : this; + this.$caller = this.caller = null; + return value; + }.extend(this).implement(params); + + newClass.$constructor = Class; + newClass.prototype.$constructor = newClass; + newClass.prototype.parent = parent; + + return newClass; +}); + +var parent = function(){ + if (!this.$caller) throw new Error('The method "parent" cannot be called.'); + var name = this.$caller.$name, + parent = this.$caller.$owner.parent, + previous = (parent) ? parent.prototype[name] : null; + if (!previous) throw new Error('The method "' + name + '" has no parent.'); + return previous.apply(this, arguments); +}; + +var reset = function(object){ + for (var key in object){ + var value = object[key]; + switch (typeOf(value)){ + case 'object': + var F = function(){}; + F.prototype = value; + object[key] = reset(new F); + break; + case 'array': object[key] = value.clone(); break; + } + } + return object; +}; + +var wrap = function(self, key, method){ + if (method.$origin) method = method.$origin; + var wrapper = function(){ + if (method.$protected && this.$caller == null) throw new Error('The method "' + key + '" cannot be called.'); + var caller = this.caller, current = this.$caller; + this.caller = current; this.$caller = wrapper; + var result = method.apply(this, arguments); + this.$caller = current; this.caller = caller; + return result; + }.extend({$owner: self, $origin: method, $name: key}); + return wrapper; +}; + +var implement = function(key, value, retain){ + if (Class.Mutators.hasOwnProperty(key)){ + value = Class.Mutators[key].call(this, value); + if (value == null) return this; + } + + if (typeOf(value) == 'function'){ + if (value.$hidden) return this; + this.prototype[key] = (retain) ? value : wrap(this, key, value); + } else { + Object.merge(this.prototype, key, value); + } + + return this; +}; + +var getInstance = function(klass){ + klass.$prototyping = true; + var proto = new klass; + delete klass.$prototyping; + return proto; +}; + +Class.implement('implement', implement.overloadSetter()); + +Class.Mutators = { + + Extends: function(parent){ + this.parent = parent; + this.prototype = getInstance(parent); + }, + + Implements: function(items){ + Array.from(items).each(function(item){ + var instance = new item; + for (var key in instance) implement.call(this, key, instance[key], true); + }, this); + } +}; + +})(); + +/* +--- + +name: Class.Extras + +description: Contains Utility Classes that can be implemented into your own Classes to ease the execution of many common tasks. + +license: MIT-style license. + +requires: Class + +provides: [Class.Extras, Chain, Events, Options] + +... +*/ + +(function(){ + +this.Chain = new Class({ + + $chain: [], + + chain: function(){ + this.$chain.append(Array.flatten(arguments)); + return this; + }, + + callChain: function(){ + return (this.$chain.length) ? this.$chain.shift().apply(this, arguments) : false; + }, + + clearChain: function(){ + this.$chain.empty(); + return this; + } + +}); + +var removeOn = function(string){ + return string.replace(/^on([A-Z])/, function(full, first){ + return first.toLowerCase(); + }); +}; + +this.Events = new Class({ + + $events: {}, + + addEvent: function(type, fn, internal){ + type = removeOn(type); + + + + this.$events[type] = (this.$events[type] || []).include(fn); + if (internal) fn.internal = true; + return this; + }, + + addEvents: function(events){ + for (var type in events) this.addEvent(type, events[type]); + return this; + }, + + fireEvent: function(type, args, delay){ + type = removeOn(type); + var events = this.$events[type]; + if (!events) return this; + args = Array.from(args); + events.each(function(fn){ + if (delay) fn.delay(delay, this, args); + else fn.apply(this, args); + }, this); + return this; + }, + + removeEvent: function(type, fn){ + type = removeOn(type); + var events = this.$events[type]; + if (events && !fn.internal){ + var index = events.indexOf(fn); + if (index != -1) delete events[index]; + } + return this; + }, + + removeEvents: function(events){ + var type; + if (typeOf(events) == 'object'){ + for (type in events) this.removeEvent(type, events[type]); + return this; + } + if (events) events = removeOn(events); + for (type in this.$events){ + if (events && events != type) continue; + var fns = this.$events[type]; + for (var i = fns.length; i--;) if (i in fns){ + this.removeEvent(type, fns[i]); + } + } + return this; + } + +}); + +this.Options = new Class({ + + setOptions: function(){ + var options = this.options = Object.merge.apply(null, [{}, this.options].append(arguments)); + if (this.addEvent) for (var option in options){ + if (typeOf(options[option]) != 'function' || !(/^on[A-Z]/).test(option)) continue; + this.addEvent(option, options[option]); + delete options[option]; + } + return this; + } + +}); + +})(); + +/* +--- + +name: Object + +description: Object generic methods + +license: MIT-style license. + +requires: Type + +provides: [Object, Hash] + +... +*/ + +(function(){ + +var hasOwnProperty = Object.prototype.hasOwnProperty; + +Object.extend({ + + subset: function(object, keys){ + var results = {}; + for (var i = 0, l = keys.length; i < l; i++){ + var k = keys[i]; + if (k in object) results[k] = object[k]; + } + return results; + }, + + map: function(object, fn, bind){ + var results = {}; + for (var key in object){ + if (hasOwnProperty.call(object, key)) results[key] = fn.call(bind, object[key], key, object); + } + return results; + }, + + filter: function(object, fn, bind){ + var results = {}; + for (var key in object){ + var value = object[key]; + if (hasOwnProperty.call(object, key) && fn.call(bind, value, key, object)) results[key] = value; + } + return results; + }, + + every: function(object, fn, bind){ + for (var key in object){ + if (hasOwnProperty.call(object, key) && !fn.call(bind, object[key], key)) return false; + } + return true; + }, + + some: function(object, fn, bind){ + for (var key in object){ + if (hasOwnProperty.call(object, key) && fn.call(bind, object[key], key)) return true; + } + return false; + }, + + keys: function(object){ + var keys = []; + for (var key in object){ + if (hasOwnProperty.call(object, key)) keys.push(key); + } + return keys; + }, + + values: function(object){ + var values = []; + for (var key in object){ + if (hasOwnProperty.call(object, key)) values.push(object[key]); + } + return values; + }, + + getLength: function(object){ + return Object.keys(object).length; + }, + + keyOf: function(object, value){ + for (var key in object){ + if (hasOwnProperty.call(object, key) && object[key] === value) return key; + } + return null; + }, + + contains: function(object, value){ + return Object.keyOf(object, value) != null; + }, + + toQueryString: function(object, base){ + var queryString = []; + + Object.each(object, function(value, key){ + if (base) key = base + '[' + key + ']'; + var result; + switch (typeOf(value)){ + case 'object': result = Object.toQueryString(value, key); break; + case 'array': + var qs = {}; + value.each(function(val, i){ + qs[i] = val; + }); + result = Object.toQueryString(qs, key); + break; + default: result = key + '=' + encodeURIComponent(value); + } + if (value != null) queryString.push(result); + }); + + return queryString.join('&'); + } + +}); + +})(); + + + +/* +--- +name: Slick.Parser +description: Standalone CSS3 Selector parser +provides: Slick.Parser +... +*/ + +;(function(){ + +var parsed, + separatorIndex, + combinatorIndex, + reversed, + cache = {}, + reverseCache = {}, + reUnescape = /\\/g; + +var parse = function(expression, isReversed){ + if (expression == null) return null; + if (expression.Slick === true) return expression; + expression = ('' + expression).replace(/^\s+|\s+$/g, ''); + reversed = !!isReversed; + var currentCache = (reversed) ? reverseCache : cache; + if (currentCache[expression]) return currentCache[expression]; + parsed = { + Slick: true, + expressions: [], + raw: expression, + reverse: function(){ + return parse(this.raw, true); + } + }; + separatorIndex = -1; + while (expression != (expression = expression.replace(regexp, parser))); + parsed.length = parsed.expressions.length; + return currentCache[parsed.raw] = (reversed) ? reverse(parsed) : parsed; +}; + +var reverseCombinator = function(combinator){ + if (combinator === '!') return ' '; + else if (combinator === ' ') return '!'; + else if ((/^!/).test(combinator)) return combinator.replace(/^!/, ''); + else return '!' + combinator; +}; + +var reverse = function(expression){ + var expressions = expression.expressions; + for (var i = 0; i < expressions.length; i++){ + var exp = expressions[i]; + var last = {parts: [], tag: '*', combinator: reverseCombinator(exp[0].combinator)}; + + for (var j = 0; j < exp.length; j++){ + var cexp = exp[j]; + if (!cexp.reverseCombinator) cexp.reverseCombinator = ' '; + cexp.combinator = cexp.reverseCombinator; + delete cexp.reverseCombinator; + } + + exp.reverse().push(last); + } + return expression; +}; + +var escapeRegExp = function(string){// Credit: XRegExp 0.6.1 (c) 2007-2008 Steven Levithan <http://stevenlevithan.com/regex/xregexp/> MIT License + return string.replace(/[-[\]{}()*+?.\\^$|,#\s]/g, function(match){ + return '\\' + match; + }); +}; + +var regexp = new RegExp( +/* +#!/usr/bin/env ruby +puts "\t\t" + DATA.read.gsub(/\(\?x\)|\s+#.*$|\s+|\\$|\\n/,'') +__END__ + "(?x)^(?:\ + \\s* ( , ) \\s* # Separator \n\ + | \\s* ( <combinator>+ ) \\s* # Combinator \n\ + | ( \\s+ ) # CombinatorChildren \n\ + | ( <unicode>+ | \\* ) # Tag \n\ + | \\# ( <unicode>+ ) # ID \n\ + | \\. ( <unicode>+ ) # ClassName \n\ + | # Attribute \n\ + \\[ \ + \\s* (<unicode1>+) (?: \ + \\s* ([*^$!~|]?=) (?: \ + \\s* (?:\ + ([\"']?)(.*?)\\9 \ + )\ + ) \ + )? \\s* \ + \\](?!\\]) \n\ + | :+ ( <unicode>+ )(?:\ + \\( (?:\ + (?:([\"'])([^\\12]*)\\12)|((?:\\([^)]+\\)|[^()]*)+)\ + ) \\)\ + )?\ + )" +*/ + "^(?:\\s*(,)\\s*|\\s*(<combinator>+)\\s*|(\\s+)|(<unicode>+|\\*)|\\#(<unicode>+)|\\.(<unicode>+)|\\[\\s*(<unicode1>+)(?:\\s*([*^$!~|]?=)(?:\\s*(?:([\"']?)(.*?)\\9)))?\\s*\\](?!\\])|(:+)(<unicode>+)(?:\\((?:(?:([\"'])([^\\13]*)\\13)|((?:\\([^)]+\\)|[^()]*)+))\\))?)" + .replace(/<combinator>/, '[' + escapeRegExp(">+~`!@$%^&={}\\;</") + ']') + .replace(/<unicode>/g, '(?:[\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])') + .replace(/<unicode1>/g, '(?:[:\\w\\u00a1-\\uFFFF-]|\\\\[^\\s0-9a-f])') +); + +function parser( + rawMatch, + + separator, + combinator, + combinatorChildren, + + tagName, + id, + className, + + attributeKey, + attributeOperator, + attributeQuote, + attributeValue, + + pseudoMarker, + pseudoClass, + pseudoQuote, + pseudoClassQuotedValue, + pseudoClassValue +){ + if (separator || separatorIndex === -1){ + parsed.expressions[++separatorIndex] = []; + combinatorIndex = -1; + if (separator) return ''; + } + + if (combinator || combinatorChildren || combinatorIndex === -1){ + combinator = combinator || ' '; + var currentSeparator = parsed.expressions[separatorIndex]; + if (reversed && currentSeparator[combinatorIndex]) + currentSeparator[combinatorIndex].reverseCombinator = reverseCombinator(combinator); + currentSeparator[++combinatorIndex] = {combinator: combinator, tag: '*'}; + } + + var currentParsed = parsed.expressions[separatorIndex][combinatorIndex]; + + if (tagName){ + currentParsed.tag = tagName.replace(reUnescape, ''); + + } else if (id){ + currentParsed.id = id.replace(reUnescape, ''); + + } else if (className){ + className = className.replace(reUnescape, ''); + + if (!currentParsed.classList) currentParsed.classList = []; + if (!currentParsed.classes) currentParsed.classes = []; + currentParsed.classList.push(className); + currentParsed.classes.push({ + value: className, + regexp: new RegExp('(^|\\s)' + escapeRegExp(className) + '(\\s|$)') + }); + + } else if (pseudoClass){ + pseudoClassValue = pseudoClassValue || pseudoClassQuotedValue; + pseudoClassValue = pseudoClassValue ? pseudoClassValue.replace(reUnescape, '') : null; + + if (!currentParsed.pseudos) currentParsed.pseudos = []; + currentParsed.pseudos.push({ + key: pseudoClass.replace(reUnescape, ''), + value: pseudoClassValue, + type: pseudoMarker.length == 1 ? 'class' : 'element' + }); + + } else if (attributeKey){ + attributeKey = attributeKey.replace(reUnescape, ''); + attributeValue = (attributeValue || '').replace(reUnescape, ''); + + var test, regexp; + + switch (attributeOperator){ + case '^=' : regexp = new RegExp( '^'+ escapeRegExp(attributeValue) ); break; + case '$=' : regexp = new RegExp( escapeRegExp(attributeValue) +'$' ); break; + case '~=' : regexp = new RegExp( '(^|\\s)'+ escapeRegExp(attributeValue) +'(\\s|$)' ); break; + case '|=' : regexp = new RegExp( '^'+ escapeRegExp(attributeValue) +'(-|$)' ); break; + case '=' : test = function(value){ + return attributeValue == value; + }; break; + case '*=' : test = function(value){ + return value && value.indexOf(attributeValue) > -1; + }; break; + case '!=' : test = function(value){ + return attributeValue != value; + }; break; + default : test = function(value){ + return !!value; + }; + } + + if (attributeValue == '' && (/^[*$^]=$/).test(attributeOperator)) test = function(){ + return false; + }; + + if (!test) test = function(value){ + return value && regexp.test(value); + }; + + if (!currentParsed.attributes) currentParsed.attributes = []; + currentParsed.attributes.push({ + key: attributeKey, + operator: attributeOperator, + value: attributeValue, + test: test + }); + + } + + return ''; +}; + +// Slick NS + +var Slick = (this.Slick || {}); + +Slick.parse = function(expression){ + return parse(expression); +}; + +Slick.escapeRegExp = escapeRegExp; + +if (!this.Slick) this.Slick = Slick; + +}).apply(/*<CommonJS>*/(typeof exports != 'undefined') ? exports : /*</CommonJS>*/this); + +/* +--- +name: Slick.Finder +description: The new, superfast css selector engine. +provides: Slick.Finder +requires: Slick.Parser +... +*/ + +;(function(){ + +var local = {}, + featuresCache = {}, + toString = Object.prototype.toString; + +// Feature / Bug detection + +local.isNativeCode = function(fn){ + return (/\{\s*\[native code\]\s*\}/).test('' + fn); +}; + +local.isXML = function(document){ + return (!!document.xmlVersion) || (!!document.xml) || (toString.call(document) == '[object XMLDocument]') || + (document.nodeType == 9 && document.documentElement.nodeName != 'HTML'); +}; + +local.setDocument = function(document){ + + // convert elements / window arguments to document. if document cannot be extrapolated, the function returns. + var nodeType = document.nodeType; + if (nodeType == 9); // document + else if (nodeType) document = document.ownerDocument; // node + else if (document.navigator) document = document.document; // window + else return; + + // check if it's the old document + + if (this.document === document) return; + this.document = document; + + // check if we have done feature detection on this document before + + var root = document.documentElement, + rootUid = this.getUIDXML(root), + features = featuresCache[rootUid], + feature; + + if (features){ + for (feature in features){ + this[feature] = features[feature]; + } + return; + } + + features = featuresCache[rootUid] = {}; + + features.root = root; + features.isXMLDocument = this.isXML(document); + + features.brokenStarGEBTN + = features.starSelectsClosedQSA + = features.idGetsName + = features.brokenMixedCaseQSA + = features.brokenGEBCN + = features.brokenCheckedQSA + = features.brokenEmptyAttributeQSA + = features.isHTMLDocument + = features.nativeMatchesSelector + = false; + + var starSelectsClosed, starSelectsComments, + brokenSecondClassNameGEBCN, cachedGetElementsByClassName, + brokenFormAttributeGetter; + + var selected, id = 'slick_uniqueid'; + var testNode = document.createElement('div'); + + var testRoot = document.body || document.getElementsByTagName('body')[0] || root; + testRoot.appendChild(testNode); + + // on non-HTML documents innerHTML and getElementsById doesnt work properly + try { + testNode.innerHTML = '<a id="'+id+'"></a>'; + features.isHTMLDocument = !!document.getElementById(id); + } catch(e){}; + + if (features.isHTMLDocument){ + + testNode.style.display = 'none'; + + // IE returns comment nodes for getElementsByTagName('*') for some documents + testNode.appendChild(document.createComment('')); + starSelectsComments = (testNode.getElementsByTagName('*').length > 1); + + // IE returns closed nodes (EG:"</foo>") for getElementsByTagName('*') for some documents + try { + testNode.innerHTML = 'foo</foo>'; + selected = testNode.getElementsByTagName('*'); + starSelectsClosed = (selected && !!selected.length && selected[0].nodeName.charAt(0) == '/'); + } catch(e){}; + + features.brokenStarGEBTN = starSelectsComments || starSelectsClosed; + + // IE returns elements with the name instead of just id for getElementsById for some documents + try { + testNode.innerHTML = '<a name="'+ id +'"></a><b id="'+ id +'"></b>'; + features.idGetsName = document.getElementById(id) === testNode.firstChild; + } catch(e){}; + + if (testNode.getElementsByClassName){ + + // Safari 3.2 getElementsByClassName caches results + try { + testNode.innerHTML = '<a class="f"></a><a class="b"></a>'; + testNode.getElementsByClassName('b').length; + testNode.firstChild.className = 'b'; + cachedGetElementsByClassName = (testNode.getElementsByClassName('b').length != 2); + } catch(e){}; + + // Opera 9.6 getElementsByClassName doesnt detects the class if its not the first one + try { + testNode.innerHTML = '<a class="a"></a><a class="f b a"></a>'; + brokenSecondClassNameGEBCN = (testNode.getElementsByClassName('a').length != 2); + } catch(e){}; + + features.brokenGEBCN = cachedGetElementsByClassName || brokenSecondClassNameGEBCN; + } + + if (testNode.querySelectorAll){ + // IE 8 returns closed nodes (EG:"</foo>") for querySelectorAll('*') for some documents + try { + testNode.innerHTML = 'foo</foo>'; + selected = testNode.querySelectorAll('*'); + features.starSelectsClosedQSA = (selected && !!selected.length && selected[0].nodeName.charAt(0) == '/'); + } catch(e){}; + + // Safari 3.2 querySelectorAll doesnt work with mixedcase on quirksmode + try { + testNode.innerHTML = '<a class="MiX"></a>'; + features.brokenMixedCaseQSA = !testNode.querySelectorAll('.MiX').length; + } catch(e){}; + + // Webkit and Opera dont return selected options on querySelectorAll + try { + testNode.innerHTML = '<select><option selected="selected">a</option></select>'; + features.brokenCheckedQSA = (testNode.querySelectorAll(':checked').length == 0); + } catch(e){}; + + // IE returns incorrect results for attr[*^$]="" selectors on querySelectorAll + try { + testNode.innerHTML = '<a class=""></a>'; + features.brokenEmptyAttributeQSA = (testNode.querySelectorAll('[class*=""]').length != 0); + } catch(e){}; + + } + + // IE6-7, if a form has an input of id x, form.getAttribute(x) returns a reference to the input + try { + testNode.innerHTML = '<form action="s"><input id="action"/></form>'; + brokenFormAttributeGetter = (testNode.firstChild.getAttribute('action') != 's'); + } catch(e){}; + + // native matchesSelector function + + features.nativeMatchesSelector = root.matches || /*root.msMatchesSelector ||*/ root.mozMatchesSelector || root.webkitMatchesSelector; + if (features.nativeMatchesSelector) try { + // if matchesSelector trows errors on incorrect sintaxes we can use it + features.nativeMatchesSelector.call(root, ':slick'); + features.nativeMatchesSelector = null; + } catch(e){}; + + } + + try { + root.slick_expando = 1; + delete root.slick_expando; + features.getUID = this.getUIDHTML; + } catch(e){ + features.getUID = this.getUIDXML; + } + + testRoot.removeChild(testNode); + testNode = selected = testRoot = null; + + // getAttribute + + features.getAttribute = (features.isHTMLDocument && brokenFormAttributeGetter) ? function(node, name){ + var method = this.attributeGetters[name]; + if (method) return method.call(node); + var attributeNode = node.getAttributeNode(name); + return (attributeNode) ? attributeNode.nodeValue : null; + } : function(node, name){ + var method = this.attributeGetters[name]; + return (method) ? method.call(node) : node.getAttribute(name); + }; + + // hasAttribute + + features.hasAttribute = (root && this.isNativeCode(root.hasAttribute)) ? function(node, attribute){ + return node.hasAttribute(attribute); + } : function(node, attribute){ + node = node.getAttributeNode(attribute); + return !!(node && (node.specified || node.nodeValue)); + }; + + // contains + // FIXME: Add specs: local.contains should be different for xml and html documents? + var nativeRootContains = root && this.isNativeCode(root.contains), + nativeDocumentContains = document && this.isNativeCode(document.contains); + + features.contains = (nativeRootContains && nativeDocumentContains) ? function(context, node){ + return context.contains(node); + } : (nativeRootContains && !nativeDocumentContains) ? function(context, node){ + // IE8 does not have .contains on document. + return context === node || ((context === document) ? document.documentElement : context).contains(node); + } : (root && root.compareDocumentPosition) ? function(context, node){ + return context === node || !!(context.compareDocumentPosition(node) & 16); + } : function(context, node){ + if (node) do { + if (node === context) return true; + } while ((node = node.parentNode)); + return false; + }; + + // document order sorting + // credits to Sizzle (http://sizzlejs.com/) + + features.documentSorter = (root.compareDocumentPosition) ? function(a, b){ + if (!a.compareDocumentPosition || !b.compareDocumentPosition) return 0; + return a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1; + } : ('sourceIndex' in root) ? function(a, b){ + if (!a.sourceIndex || !b.sourceIndex) return 0; + return a.sourceIndex - b.sourceIndex; + } : (document.createRange) ? function(a, b){ + if (!a.ownerDocument || !b.ownerDocument) return 0; + var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange(); + aRange.setStart(a, 0); + aRange.setEnd(a, 0); + bRange.setStart(b, 0); + bRange.setEnd(b, 0); + return aRange.compareBoundaryPoints(Range.START_TO_END, bRange); + } : null ; + + root = null; + + for (feature in features){ + this[feature] = features[feature]; + } +}; + +// Main Method + +var reSimpleSelector = /^([#.]?)((?:[\w-]+|\*))$/, + reEmptyAttribute = /\[.+[*$^]=(?:""|'')?\]/, + qsaFailExpCache = {}; + +local.search = function(context, expression, append, first){ + + var found = this.found = (first) ? null : (append || []); + + if (!context) return found; + else if (context.navigator) context = context.document; // Convert the node from a window to a document + else if (!context.nodeType) return found; + + // setup + + var parsed, i, + uniques = this.uniques = {}, + hasOthers = !!(append && append.length), + contextIsDocument = (context.nodeType == 9); + + if (this.document !== (contextIsDocument ? context : context.ownerDocument)) this.setDocument(context); + + // avoid duplicating items already in the append array + if (hasOthers) for (i = found.length; i--;) uniques[this.getUID(found[i])] = true; + + // expression checks + + if (typeof expression == 'string'){ // expression is a string + + /*<simple-selectors-override>*/ + var simpleSelector = expression.match(reSimpleSelector); + simpleSelectors: if (simpleSelector){ + + var symbol = simpleSelector[1], + name = simpleSelector[2], + node, nodes; + + if (!symbol){ + + if (name == '*' && this.brokenStarGEBTN) break simpleSelectors; + nodes = context.getElementsByTagName(name); + if (first) return nodes[0] || null; + for (i = 0; node = nodes[i++];){ + if (!(hasOthers && uniques[this.getUID(node)])) found.push(node); + } + + } else if (symbol == '#'){ + + if (!this.isHTMLDocument || !contextIsDocument) break simpleSelectors; + node = context.getElementById(name); + if (!node) return found; + if (this.idGetsName && node.getAttributeNode('id').nodeValue != name) break simpleSelectors; + if (first) return node || null; + if (!(hasOthers && uniques[this.getUID(node)])) found.push(node); + + } else if (symbol == '.'){ + + if (!this.isHTMLDocument || ((!context.getElementsByClassName || this.brokenGEBCN) && context.querySelectorAll)) break simpleSelectors; + if (context.getElementsByClassName && !this.brokenGEBCN){ + nodes = context.getElementsByClassName(name); + if (first) return nodes[0] || null; + for (i = 0; node = nodes[i++];){ + if (!(hasOthers && uniques[this.getUID(node)])) found.push(node); + } + } else { + var matchClass = new RegExp('(^|\\s)'+ Slick.escapeRegExp(name) +'(\\s|$)'); + nodes = context.getElementsByTagName('*'); + for (i = 0; node = nodes[i++];){ + className = node.className; + if (!(className && matchClass.test(className))) continue; + if (first) return node; + if (!(hasOthers && uniques[this.getUID(node)])) found.push(node); + } + } + + } + + if (hasOthers) this.sort(found); + return (first) ? null : found; + + } + /*</simple-selectors-override>*/ + + /*<query-selector-override>*/ + querySelector: if (context.querySelectorAll){ + + if (!this.isHTMLDocument + || qsaFailExpCache[expression] + //TODO: only skip when expression is actually mixed case + || this.brokenMixedCaseQSA + || (this.brokenCheckedQSA && expression.indexOf(':checked') > -1) + || (this.brokenEmptyAttributeQSA && reEmptyAttribute.test(expression)) + || (!contextIsDocument //Abort when !contextIsDocument and... + // there are multiple expressions in the selector + // since we currently only fix non-document rooted QSA for single expression selectors + && expression.indexOf(',') > -1 + ) + || Slick.disableQSA + ) break querySelector; + + var _expression = expression, _context = context; + if (!contextIsDocument){ + // non-document rooted QSA + // credits to Andrew Dupont + var currentId = _context.getAttribute('id'), slickid = 'slickid__'; + _context.setAttribute('id', slickid); + _expression = '#' + slickid + ' ' + _expression; + context = _context.parentNode; + } + + try { + if (first) return context.querySelector(_expression) || null; + else nodes = context.querySelectorAll(_expression); + } catch(e){ + qsaFailExpCache[expression] = 1; + break querySelector; + } finally { + if (!contextIsDocument){ + if (currentId) _context.setAttribute('id', currentId); + else _context.removeAttribute('id'); + context = _context; + } + } + + if (this.starSelectsClosedQSA) for (i = 0; node = nodes[i++];){ + if (node.nodeName > '@' && !(hasOthers && uniques[this.getUID(node)])) found.push(node); + } else for (i = 0; node = nodes[i++];){ + if (!(hasOthers && uniques[this.getUID(node)])) found.push(node); + } + + if (hasOthers) this.sort(found); + return found; + + } + /*</query-selector-override>*/ + + parsed = this.Slick.parse(expression); + if (!parsed.length) return found; + } else if (expression == null){ // there is no expression + return found; + } else if (expression.Slick){ // expression is a parsed Slick object + parsed = expression; + } else if (this.contains(context.documentElement || context, expression)){ // expression is a node + (found) ? found.push(expression) : found = expression; + return found; + } else { // other junk + return found; + } + + /*<pseudo-selectors>*//*<nth-pseudo-selectors>*/ + + // cache elements for the nth selectors + + this.posNTH = {}; + this.posNTHLast = {}; + this.posNTHType = {}; + this.posNTHTypeLast = {}; + + /*</nth-pseudo-selectors>*//*</pseudo-selectors>*/ + + // if append is null and there is only a single selector with one expression use pushArray, else use pushUID + this.push = (!hasOthers && (first || (parsed.length == 1 && parsed.expressions[0].length == 1))) ? this.pushArray : this.pushUID; + + if (found == null) found = []; + + // default engine + + var j, m, n; + var combinator, tag, id, classList, classes, attributes, pseudos; + var currentItems, currentExpression, currentBit, lastBit, expressions = parsed.expressions; + + search: for (i = 0; (currentExpression = expressions[i]); i++) for (j = 0; (currentBit = currentExpression[j]); j++){ + + combinator = 'combinator:' + currentBit.combinator; + if (!this[combinator]) continue search; + + tag = (this.isXMLDocument) ? currentBit.tag : currentBit.tag.toUpperCase(); + id = currentBit.id; + classList = currentBit.classList; + classes = currentBit.classes; + attributes = currentBit.attributes; + pseudos = currentBit.pseudos; + lastBit = (j === (currentExpression.length - 1)); + + this.bitUniques = {}; + + if (lastBit){ + this.uniques = uniques; + this.found = found; + } else { + this.uniques = {}; + this.found = []; + } + + if (j === 0){ + this[combinator](context, tag, id, classes, attributes, pseudos, classList); + if (first && lastBit && found.length) break search; + } else { + if (first && lastBit) for (m = 0, n = currentItems.length; m < n; m++){ + this[combinator](currentItems[m], tag, id, classes, attributes, pseudos, classList); + if (found.length) break search; + } else for (m = 0, n = currentItems.length; m < n; m++) this[combinator](currentItems[m], tag, id, classes, attributes, pseudos, classList); + } + + currentItems = this.found; + } + + // should sort if there are nodes in append and if you pass multiple expressions. + if (hasOthers || (parsed.expressions.length > 1)) this.sort(found); + + return (first) ? (found[0] || null) : found; +}; + +// Utils + +local.uidx = 1; +local.uidk = 'slick-uniqueid'; + +local.getUIDXML = function(node){ + var uid = node.getAttribute(this.uidk); + if (!uid){ + uid = this.uidx++; + node.setAttribute(this.uidk, uid); + } + return uid; +}; + +local.getUIDHTML = function(node){ + return node.uniqueNumber || (node.uniqueNumber = this.uidx++); +}; + +// sort based on the setDocument documentSorter method. + +local.sort = function(results){ + if (!this.documentSorter) return results; + results.sort(this.documentSorter); + return results; +}; + +/*<pseudo-selectors>*//*<nth-pseudo-selectors>*/ + +local.cacheNTH = {}; + +local.matchNTH = /^([+-]?\d*)?([a-z]+)?([+-]\d+)?$/; + +local.parseNTHArgument = function(argument){ + var parsed = argument.match(this.matchNTH); + if (!parsed) return false; + var special = parsed[2] || false; + var a = parsed[1] || 1; + if (a == '-') a = -1; + var b = +parsed[3] || 0; + parsed = + (special == 'n') ? {a: a, b: b} : + (special == 'odd') ? {a: 2, b: 1} : + (special == 'even') ? {a: 2, b: 0} : {a: 0, b: a}; + + return (this.cacheNTH[argument] = parsed); +}; + +local.createNTHPseudo = function(child, sibling, positions, ofType){ + return function(node, argument){ + var uid = this.getUID(node); + if (!this[positions][uid]){ + var parent = node.parentNode; + if (!parent) return false; + var el = parent[child], count = 1; + if (ofType){ + var nodeName = node.nodeName; + do { + if (el.nodeName != nodeName) continue; + this[positions][this.getUID(el)] = count++; + } while ((el = el[sibling])); + } else { + do { + if (el.nodeType != 1) continue; + this[positions][this.getUID(el)] = count++; + } while ((el = el[sibling])); + } + } + argument = argument || 'n'; + var parsed = this.cacheNTH[argument] || this.parseNTHArgument(argument); + if (!parsed) return false; + var a = parsed.a, b = parsed.b, pos = this[positions][uid]; + if (a == 0) return b == pos; + if (a > 0){ + if (pos < b) return false; + } else { + if (b < pos) return false; + } + return ((pos - b) % a) == 0; + }; +}; + +/*</nth-pseudo-selectors>*//*</pseudo-selectors>*/ + +local.pushArray = function(node, tag, id, classes, attributes, pseudos){ + if (this.matchSelector(node, tag, id, classes, attributes, pseudos)) this.found.push(node); +}; + +local.pushUID = function(node, tag, id, classes, attributes, pseudos){ + var uid = this.getUID(node); + if (!this.uniques[uid] && this.matchSelector(node, tag, id, classes, attributes, pseudos)){ + this.uniques[uid] = true; + this.found.push(node); + } +}; + +local.matchNode = function(node, selector){ + if (this.isHTMLDocument && this.nativeMatchesSelector){ + try { + return this.nativeMatchesSelector.call(node, selector.replace(/\[([^=]+)=\s*([^'"\]]+?)\s*\]/g, '[$1="$2"]')); + } catch(matchError){} + } + + var parsed = this.Slick.parse(selector); + if (!parsed) return true; + + // simple (single) selectors + var expressions = parsed.expressions, simpleExpCounter = 0, i, currentExpression; + for (i = 0; (currentExpression = expressions[i]); i++){ + if (currentExpression.length == 1){ + var exp = currentExpression[0]; + if (this.matchSelector(node, (this.isXMLDocument) ? exp.tag : exp.tag.toUpperCase(), exp.id, exp.classes, exp.attributes, exp.pseudos)) return true; + simpleExpCounter++; + } + } + + if (simpleExpCounter == parsed.length) return false; + + var nodes = this.search(this.document, parsed), item; + for (i = 0; item = nodes[i++];){ + if (item === node) return true; + } + return false; +}; + +local.matchPseudo = function(node, name, argument){ + var pseudoName = 'pseudo:' + name; + if (this[pseudoName]) return this[pseudoName](node, argument); + var attribute = this.getAttribute(node, name); + return (argument) ? argument == attribute : !!attribute; +}; + +local.matchSelector = function(node, tag, id, classes, attributes, pseudos){ + if (tag){ + var nodeName = (this.isXMLDocument) ? node.nodeName : node.nodeName.toUpperCase(); + if (tag == '*'){ + if (nodeName < '@') return false; // Fix for comment nodes and closed nodes + } else { + if (nodeName != tag) return false; + } + } + + if (id && node.getAttribute('id') != id) return false; + + var i, part, cls; + if (classes) for (i = classes.length; i--;){ + cls = this.getAttribute(node, 'class'); + if (!(cls && classes[i].regexp.test(cls))) return false; + } + if (attributes) for (i = attributes.length; i--;){ + part = attributes[i]; + if (part.operator ? !part.test(this.getAttribute(node, part.key)) : !this.hasAttribute(node, part.key)) return false; + } + if (pseudos) for (i = pseudos.length; i--;){ + part = pseudos[i]; + if (!this.matchPseudo(node, part.key, part.value)) return false; + } + return true; +}; + +var combinators = { + + ' ': function(node, tag, id, classes, attributes, pseudos, classList){ // all child nodes, any level + + var i, item, children; + + if (this.isHTMLDocument){ + getById: if (id){ + item = this.document.getElementById(id); + if ((!item && node.all) || (this.idGetsName && item && item.getAttributeNode('id').nodeValue != id)){ + // all[id] returns all the elements with that name or id inside node + // if theres just one it will return the element, else it will be a collection + children = node.all[id]; + if (!children) return; + if (!children[0]) children = [children]; + for (i = 0; item = children[i++];){ + var idNode = item.getAttributeNode('id'); + if (idNode && idNode.nodeValue == id){ + this.push(item, tag, null, classes, attributes, pseudos); + break; + } + } + return; + } + if (!item){ + // if the context is in the dom we return, else we will try GEBTN, breaking the getById label + if (this.contains(this.root, node)) return; + else break getById; + } else if (this.document !== node && !this.contains(node, item)) return; + this.push(item, tag, null, classes, attributes, pseudos); + return; + } + getByClass: if (classes && node.getElementsByClassName && !this.brokenGEBCN){ + children = node.getElementsByClassName(classList.join(' ')); + if (!(children && children.length)) break getByClass; + for (i = 0; item = children[i++];) this.push(item, tag, id, null, attributes, pseudos); + return; + } + } + getByTag: { + children = node.getElementsByTagName(tag); + if (!(children && children.length)) break getByTag; + if (!this.brokenStarGEBTN) tag = null; + for (i = 0; item = children[i++];) this.push(item, tag, id, classes, attributes, pseudos); + } + }, + + '>': function(node, tag, id, classes, attributes, pseudos){ // direct children + if ((node = node.firstChild)) do { + if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos); + } while ((node = node.nextSibling)); + }, + + '+': function(node, tag, id, classes, attributes, pseudos){ // next sibling + while ((node = node.nextSibling)) if (node.nodeType == 1){ + this.push(node, tag, id, classes, attributes, pseudos); + break; + } + }, + + '^': function(node, tag, id, classes, attributes, pseudos){ // first child + node = node.firstChild; + if (node){ + if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos); + else this['combinator:+'](node, tag, id, classes, attributes, pseudos); + } + }, + + '~': function(node, tag, id, classes, attributes, pseudos){ // next siblings + while ((node = node.nextSibling)){ + if (node.nodeType != 1) continue; + var uid = this.getUID(node); + if (this.bitUniques[uid]) break; + this.bitUniques[uid] = true; + this.push(node, tag, id, classes, attributes, pseudos); + } + }, + + '++': function(node, tag, id, classes, attributes, pseudos){ // next sibling and previous sibling + this['combinator:+'](node, tag, id, classes, attributes, pseudos); + this['combinator:!+'](node, tag, id, classes, attributes, pseudos); + }, + + '~~': function(node, tag, id, classes, attributes, pseudos){ // next siblings and previous siblings + this['combinator:~'](node, tag, id, classes, attributes, pseudos); + this['combinator:!~'](node, tag, id, classes, attributes, pseudos); + }, + + '!': function(node, tag, id, classes, attributes, pseudos){ // all parent nodes up to document + while ((node = node.parentNode)) if (node !== this.document) this.push(node, tag, id, classes, attributes, pseudos); + }, + + '!>': function(node, tag, id, classes, attributes, pseudos){ // direct parent (one level) + node = node.parentNode; + if (node !== this.document) this.push(node, tag, id, classes, attributes, pseudos); + }, + + '!+': function(node, tag, id, classes, attributes, pseudos){ // previous sibling + while ((node = node.previousSibling)) if (node.nodeType == 1){ + this.push(node, tag, id, classes, attributes, pseudos); + break; + } + }, + + '!^': function(node, tag, id, classes, attributes, pseudos){ // last child + node = node.lastChild; + if (node){ + if (node.nodeType == 1) this.push(node, tag, id, classes, attributes, pseudos); + else this['combinator:!+'](node, tag, id, classes, attributes, pseudos); + } + }, + + '!~': function(node, tag, id, classes, attributes, pseudos){ // previous siblings + while ((node = node.previousSibling)){ + if (node.nodeType != 1) continue; + var uid = this.getUID(node); + if (this.bitUniques[uid]) break; + this.bitUniques[uid] = true; + this.push(node, tag, id, classes, attributes, pseudos); + } + } + +}; + +for (var c in combinators) local['combinator:' + c] = combinators[c]; + +var pseudos = { + + /*<pseudo-selectors>*/ + + 'empty': function(node){ + var child = node.firstChild; + return !(child && child.nodeType == 1) && !(node.innerText || node.textContent || '').length; + }, + + 'not': function(node, expression){ + return !this.matchNode(node, expression); + }, + + 'contains': function(node, text){ + return (node.innerText || node.textContent || '').indexOf(text) > -1; + }, + + 'first-child': function(node){ + while ((node = node.previousSibling)) if (node.nodeType == 1) return false; + return true; + }, + + 'last-child': function(node){ + while ((node = node.nextSibling)) if (node.nodeType == 1) return false; + return true; + }, + + 'only-child': function(node){ + var prev = node; + while ((prev = prev.previousSibling)) if (prev.nodeType == 1) return false; + var next = node; + while ((next = next.nextSibling)) if (next.nodeType == 1) return false; + return true; + }, + + /*<nth-pseudo-selectors>*/ + + 'nth-child': local.createNTHPseudo('firstChild', 'nextSibling', 'posNTH'), + + 'nth-last-child': local.createNTHPseudo('lastChild', 'previousSibling', 'posNTHLast'), + + 'nth-of-type': local.createNTHPseudo('firstChild', 'nextSibling', 'posNTHType', true), + + 'nth-last-of-type': local.createNTHPseudo('lastChild', 'previousSibling', 'posNTHTypeLast', true), + + 'index': function(node, index){ + return this['pseudo:nth-child'](node, '' + (index + 1)); + }, + + 'even': function(node){ + return this['pseudo:nth-child'](node, '2n'); + }, + + 'odd': function(node){ + return this['pseudo:nth-child'](node, '2n+1'); + }, + + /*</nth-pseudo-selectors>*/ + + /*<of-type-pseudo-selectors>*/ + + 'first-of-type': function(node){ + var nodeName = node.nodeName; + while ((node = node.previousSibling)) if (node.nodeName == nodeName) return false; + return true; + }, + + 'last-of-type': function(node){ + var nodeName = node.nodeName; + while ((node = node.nextSibling)) if (node.nodeName == nodeName) return false; + return true; + }, + + 'only-of-type': function(node){ + var prev = node, nodeName = node.nodeName; + while ((prev = prev.previousSibling)) if (prev.nodeName == nodeName) return false; + var next = node; + while ((next = next.nextSibling)) if (next.nodeName == nodeName) return false; + return true; + }, + + /*</of-type-pseudo-selectors>*/ + + // custom pseudos + + 'enabled': function(node){ + return !node.disabled; + }, + + 'disabled': function(node){ + return node.disabled; + }, + + 'checked': function(node){ + return node.checked || node.selected; + }, + + 'focus': function(node){ + return this.isHTMLDocument && this.document.activeElement === node && (node.href || node.type || this.hasAttribute(node, 'tabindex')); + }, + + 'root': function(node){ + return (node === this.root); + }, + + 'selected': function(node){ + return node.selected; + } + + /*</pseudo-selectors>*/ +}; + +for (var p in pseudos) local['pseudo:' + p] = pseudos[p]; + +// attributes methods + +var attributeGetters = local.attributeGetters = { + + 'for': function(){ + return ('htmlFor' in this) ? this.htmlFor : this.getAttribute('for'); + }, + + 'href': function(){ + return ('href' in this) ? this.getAttribute('href', 2) : this.getAttribute('href'); + }, + + 'style': function(){ + return (this.style) ? this.style.cssText : this.getAttribute('style'); + }, + + 'tabindex': function(){ + var attributeNode = this.getAttributeNode('tabindex'); + return (attributeNode && attributeNode.specified) ? attributeNode.nodeValue : null; + }, + + 'type': function(){ + return this.getAttribute('type'); + }, + + 'maxlength': function(){ + var attributeNode = this.getAttributeNode('maxLength'); + return (attributeNode && attributeNode.specified) ? attributeNode.nodeValue : null; + } + +}; + +attributeGetters.MAXLENGTH = attributeGetters.maxLength = attributeGetters.maxlength; + +// Slick + +var Slick = local.Slick = (this.Slick || {}); + +Slick.version = '1.1.7'; + +// Slick finder + +Slick.search = function(context, expression, append){ + return local.search(context, expression, append); +}; + +Slick.find = function(context, expression){ + return local.search(context, expression, null, true); +}; + +// Slick containment checker + +Slick.contains = function(container, node){ + local.setDocument(container); + return local.contains(container, node); +}; + +// Slick attribute getter + +Slick.getAttribute = function(node, name){ + local.setDocument(node); + return local.getAttribute(node, name); +}; + +Slick.hasAttribute = function(node, name){ + local.setDocument(node); + return local.hasAttribute(node, name); +}; + +// Slick matcher + +Slick.match = function(node, selector){ + if (!(node && selector)) return false; + if (!selector || selector === node) return true; + local.setDocument(node); + return local.matchNode(node, selector); +}; + +// Slick attribute accessor + +Slick.defineAttributeGetter = function(name, fn){ + local.attributeGetters[name] = fn; + return this; +}; + +Slick.lookupAttributeGetter = function(name){ + return local.attributeGetters[name]; +}; + +// Slick pseudo accessor + +Slick.definePseudo = function(name, fn){ + local['pseudo:' + name] = function(node, argument){ + return fn.call(node, argument); + }; + return this; +}; + +Slick.lookupPseudo = function(name){ + var pseudo = local['pseudo:' + name]; + if (pseudo) return function(argument){ + return pseudo.call(this, argument); + }; + return null; +}; + +// Slick overrides accessor + +Slick.override = function(regexp, fn){ + local.override(regexp, fn); + return this; +}; + +Slick.isXML = local.isXML; + +Slick.uidOf = function(node){ + return local.getUIDHTML(node); +}; + +if (!this.Slick) this.Slick = Slick; + +}).apply(/*<CommonJS>*/(typeof exports != 'undefined') ? exports : /*</CommonJS>*/this); + +/* +--- + +name: Element + +description: One of the most important items in MooTools. Contains the dollar function, the dollars function, and an handful of cross-browser, time-saver methods to let you easily work with HTML Elements. + +license: MIT-style license. + +requires: [Window, Document, Array, String, Function, Object, Number, Slick.Parser, Slick.Finder] + +provides: [Element, Elements, $, $$, IFrame, Selectors] + +... +*/ + +var Element = this.Element = function(tag, props){ + var konstructor = Element.Constructors[tag]; + if (konstructor) return konstructor(props); + if (typeof tag != 'string') return document.id(tag).set(props); + + if (!props) props = {}; + + if (!(/^[\w-]+$/).test(tag)){ + var parsed = Slick.parse(tag).expressions[0][0]; + tag = (parsed.tag == '*') ? 'div' : parsed.tag; + if (parsed.id && props.id == null) props.id = parsed.id; + + var attributes = parsed.attributes; + if (attributes) for (var attr, i = 0, l = attributes.length; i < l; i++){ + attr = attributes[i]; + if (props[attr.key] != null) continue; + + if (attr.value != null && attr.operator == '=') props[attr.key] = attr.value; + else if (!attr.value && !attr.operator) props[attr.key] = true; + } + + if (parsed.classList && props['class'] == null) props['class'] = parsed.classList.join(' '); + } + + return document.newElement(tag, props); +}; + + +if (Browser.Element){ + Element.prototype = Browser.Element.prototype; + // IE8 and IE9 require the wrapping. + Element.prototype._fireEvent = (function(fireEvent){ + return function(type, event){ + return fireEvent.call(this, type, event); + }; + })(Element.prototype.fireEvent); +} + +new Type('Element', Element).mirror(function(name){ + if (Array.prototype[name]) return; + + var obj = {}; + obj[name] = function(){ + var results = [], args = arguments, elements = true; + for (var i = 0, l = this.length; i < l; i++){ + var element = this[i], result = results[i] = element[name].apply(element, args); + elements = (elements && typeOf(result) == 'element'); + } + return (elements) ? new Elements(results) : results; + }; + + Elements.implement(obj); +}); + +if (!Browser.Element){ + Element.parent = Object; + + Element.Prototype = { + '$constructor': Element, + '$family': Function.from('element').hide() + }; + + Element.mirror(function(name, method){ + Element.Prototype[name] = method; + }); +} + +Element.Constructors = {}; + + + +var IFrame = new Type('IFrame', function(){ + var params = Array.link(arguments, { + properties: Type.isObject, + iframe: function(obj){ + return (obj != null); + } + }); + + var props = params.properties || {}, iframe; + if (params.iframe) iframe = document.id(params.iframe); + var onload = props.onload || function(){}; + delete props.onload; + props.id = props.name = [props.id, props.name, iframe ? (iframe.id || iframe.name) : 'IFrame_' + String.uniqueID()].pick(); + iframe = new Element(iframe || 'iframe', props); + + var onLoad = function(){ + onload.call(iframe.contentWindow); + }; + + if (window.frames[props.id]) onLoad(); + else iframe.addListener('load', onLoad); + return iframe; +}); + +var Elements = this.Elements = function(nodes){ + if (nodes && nodes.length){ + var uniques = {}, node; + for (var i = 0; node = nodes[i++];){ + var uid = Slick.uidOf(node); + if (!uniques[uid]){ + uniques[uid] = true; + this.push(node); + } + } + } +}; + +Elements.prototype = {length: 0}; +Elements.parent = Array; + +new Type('Elements', Elements).implement({ + + filter: function(filter, bind){ + if (!filter) return this; + return new Elements(Array.filter(this, (typeOf(filter) == 'string') ? function(item){ + return item.match(filter); + } : filter, bind)); + }.protect(), + + push: function(){ + var length = this.length; + for (var i = 0, l = arguments.length; i < l; i++){ + var item = document.id(arguments[i]); + if (item) this[length++] = item; + } + return (this.length = length); + }.protect(), + + unshift: function(){ + var items = []; + for (var i = 0, l = arguments.length; i < l; i++){ + var item = document.id(arguments[i]); + if (item) items.push(item); + } + return Array.prototype.unshift.apply(this, items); + }.protect(), + + concat: function(){ + var newElements = new Elements(this); + for (var i = 0, l = arguments.length; i < l; i++){ + var item = arguments[i]; + if (Type.isEnumerable(item)) newElements.append(item); + else newElements.push(item); + } + return newElements; + }.protect(), + + append: function(collection){ + for (var i = 0, l = collection.length; i < l; i++) this.push(collection[i]); + return this; + }.protect(), + + empty: function(){ + while (this.length) delete this[--this.length]; + return this; + }.protect() + +}); + + + +(function(){ + +// FF, IE +var splice = Array.prototype.splice, object = {'0': 0, '1': 1, length: 2}; + +splice.call(object, 1, 1); +if (object[1] == 1) Elements.implement('splice', function(){ + var length = this.length; + var result = splice.apply(this, arguments); + while (length >= this.length) delete this[length--]; + return result; +}.protect()); + +Array.forEachMethod(function(method, name){ + Elements.implement(name, method); +}); + +Array.mirror(Elements); + +/*<ltIE8>*/ +var createElementAcceptsHTML; +try { + createElementAcceptsHTML = (document.createElement('<input name=x>').name == 'x'); +} catch (e){} + +var escapeQuotes = function(html){ + return ('' + html).replace(/&/g, '&').replace(/"/g, '"'); +}; +/*</ltIE8>*/ + +/*<ltIE9>*/ +// #2479 - IE8 Cannot set HTML of style element +var canChangeStyleHTML = (function(){ + var div = document.createElement('style'), + flag = false; + try { + div.innerHTML = '#justTesing{margin: 0px;}'; + flag = !!div.innerHTML; + } catch(e){} + return flag; +})(); +/*</ltIE9>*/ + +Document.implement({ + + newElement: function(tag, props){ + if (props){ + if (props.checked != null) props.defaultChecked = props.checked; + if ((props.type == 'checkbox' || props.type == 'radio') && props.value == null) props.value = 'on'; + /*<ltIE9>*/ // IE needs the type to be set before changing content of style element + if (!canChangeStyleHTML && tag == 'style'){ + var styleElement = document.createElement('style'); + styleElement.setAttribute('type', 'text/css'); + if (props.type) delete props.type; + return this.id(styleElement).set(props); + } + /*</ltIE9>*/ + /*<ltIE8>*/// Fix for readonly name and type properties in IE < 8 + if (createElementAcceptsHTML){ + tag = '<' + tag; + if (props.name) tag += ' name="' + escapeQuotes(props.name) + '"'; + if (props.type) tag += ' type="' + escapeQuotes(props.type) + '"'; + tag += '>'; + delete props.name; + delete props.type; + } + /*</ltIE8>*/ + } + return this.id(this.createElement(tag)).set(props); + } + +}); + +})(); + +(function(){ + +Slick.uidOf(window); +Slick.uidOf(document); + +Document.implement({ + + newTextNode: function(text){ + return this.createTextNode(text); + }, + + getDocument: function(){ + return this; + }, + + getWindow: function(){ + return this.window; + }, + + id: (function(){ + + var types = { + + string: function(id, nocash, doc){ + id = Slick.find(doc, '#' + id.replace(/(\W)/g, '\\$1')); + return (id) ? types.element(id, nocash) : null; + }, + + element: function(el, nocash){ + Slick.uidOf(el); + if (!nocash && !el.$family && !(/^(?:object|embed)$/i).test(el.tagName)){ + var fireEvent = el.fireEvent; + // wrapping needed in IE7, or else crash + el._fireEvent = function(type, event){ + return fireEvent(type, event); + }; + Object.append(el, Element.Prototype); + } + return el; + }, + + object: function(obj, nocash, doc){ + if (obj.toElement) return types.element(obj.toElement(doc), nocash); + return null; + } + + }; + + types.textnode = types.whitespace = types.window = types.document = function(zero){ + return zero; + }; + + return function(el, nocash, doc){ + if (el && el.$family && el.uniqueNumber) return el; + var type = typeOf(el); + return (types[type]) ? types[type](el, nocash, doc || document) : null; + }; + + })() + +}); + +if (window.$ == null) Window.implement('$', function(el, nc){ + return document.id(el, nc, this.document); +}); + +Window.implement({ + + getDocument: function(){ + return this.document; + }, + + getWindow: function(){ + return this; + } + +}); + +[Document, Element].invoke('implement', { + + getElements: function(expression){ + return Slick.search(this, expression, new Elements); + }, + + getElement: function(expression){ + return document.id(Slick.find(this, expression)); + } + +}); + +var contains = {contains: function(element){ + return Slick.contains(this, element); +}}; + +if (!document.contains) Document.implement(contains); +if (!document.createElement('div').contains) Element.implement(contains); + + + +// tree walking + +var injectCombinator = function(expression, combinator){ + if (!expression) return combinator; + + expression = Object.clone(Slick.parse(expression)); + + var expressions = expression.expressions; + for (var i = expressions.length; i--;) + expressions[i][0].combinator = combinator; + + return expression; +}; + +Object.forEach({ + getNext: '~', + getPrevious: '!~', + getParent: '!' +}, function(combinator, method){ + Element.implement(method, function(expression){ + return this.getElement(injectCombinator(expression, combinator)); + }); +}); + +Object.forEach({ + getAllNext: '~', + getAllPrevious: '!~', + getSiblings: '~~', + getChildren: '>', + getParents: '!' +}, function(combinator, method){ + Element.implement(method, function(expression){ + return this.getElements(injectCombinator(expression, combinator)); + }); +}); + +Element.implement({ + + getFirst: function(expression){ + return document.id(Slick.search(this, injectCombinator(expression, '>'))[0]); + }, + + getLast: function(expression){ + return document.id(Slick.search(this, injectCombinator(expression, '>')).getLast()); + }, + + getWindow: function(){ + return this.ownerDocument.window; + }, + + getDocument: function(){ + return this.ownerDocument; + }, + + getElementById: function(id){ + return document.id(Slick.find(this, '#' + ('' + id).replace(/(\W)/g, '\\$1'))); + }, + + match: function(expression){ + return !expression || Slick.match(this, expression); + } + +}); + + + +if (window.$$ == null) Window.implement('$$', function(selector){ + if (arguments.length == 1){ + if (typeof selector == 'string') return Slick.search(this.document, selector, new Elements); + else if (Type.isEnumerable(selector)) return new Elements(selector); + } + return new Elements(arguments); +}); + +// Inserters + +var inserters = { + + before: function(context, element){ + var parent = element.parentNode; + if (parent) parent.insertBefore(context, element); + }, + + after: function(context, element){ + var parent = element.parentNode; + if (parent) parent.insertBefore(context, element.nextSibling); + }, + + bottom: function(context, element){ + element.appendChild(context); + }, + + top: function(context, element){ + element.insertBefore(context, element.firstChild); + } + +}; + +inserters.inside = inserters.bottom; + + + +// getProperty / setProperty + +var propertyGetters = {}, propertySetters = {}; + +// properties + +var properties = {}; +Array.forEach([ + 'type', 'value', 'defaultValue', 'accessKey', 'cellPadding', 'cellSpacing', 'colSpan', + 'frameBorder', 'rowSpan', 'tabIndex', 'useMap' +], function(property){ + properties[property.toLowerCase()] = property; +}); + +properties.html = 'innerHTML'; +properties.text = (document.createElement('div').textContent == null) ? 'innerText': 'textContent'; + +Object.forEach(properties, function(real, key){ + propertySetters[key] = function(node, value){ + node[real] = value; + }; + propertyGetters[key] = function(node){ + return node[real]; + }; +}); + +/*<ltIE9>*/ +propertySetters.text = (function(setter){ + return function(node, value){ + if (node.get('tag') == 'style') node.set('html', value); + else node[properties.text] = value; + }; +})(propertySetters.text); + +propertyGetters.text = (function(getter){ + return function(node){ + return (node.get('tag') == 'style') ? node.innerHTML : getter(node); + }; +})(propertyGetters.text); +/*</ltIE9>*/ + +// Booleans + +var bools = [ + 'compact', 'nowrap', 'ismap', 'declare', 'noshade', 'checked', + 'disabled', 'readOnly', 'multiple', 'selected', 'noresize', + 'defer', 'defaultChecked', 'autofocus', 'controls', 'autoplay', + 'loop' +]; + +var booleans = {}; +Array.forEach(bools, function(bool){ + var lower = bool.toLowerCase(); + booleans[lower] = bool; + propertySetters[lower] = function(node, value){ + node[bool] = !!value; + }; + propertyGetters[lower] = function(node){ + return !!node[bool]; + }; +}); + +// Special cases + +Object.append(propertySetters, { + + 'class': function(node, value){ + ('className' in node) ? node.className = (value || '') : node.setAttribute('class', value); + }, + + 'for': function(node, value){ + ('htmlFor' in node) ? node.htmlFor = value : node.setAttribute('for', value); + }, + + 'style': function(node, value){ + (node.style) ? node.style.cssText = value : node.setAttribute('style', value); + }, + + 'value': function(node, value){ + node.value = (value != null) ? value : ''; + } + +}); + +propertyGetters['class'] = function(node){ + return ('className' in node) ? node.className || null : node.getAttribute('class'); +}; + +/* <webkit> */ +var el = document.createElement('button'); +// IE sets type as readonly and throws +try { el.type = 'button'; } catch(e){} +if (el.type != 'button') propertySetters.type = function(node, value){ + node.setAttribute('type', value); +}; +el = null; +/* </webkit> */ + +/*<IE>*/ + +/*<ltIE9>*/ +// #2479 - IE8 Cannot set HTML of style element +var canChangeStyleHTML = (function(){ + var div = document.createElement('style'), + flag = false; + try { + div.innerHTML = '#justTesing{margin: 0px;}'; + flag = !!div.innerHTML; + } catch(e){} + return flag; +})(); +/*</ltIE9>*/ + +var input = document.createElement('input'), volatileInputValue, html5InputSupport; + +// #2178 +input.value = 't'; +input.type = 'submit'; +volatileInputValue = input.value != 't'; + +// #2443 - IE throws "Invalid Argument" when trying to use html5 input types +try { + input.type = 'email'; + html5InputSupport = input.type == 'email'; +} catch(e){} + +input = null; + +if (volatileInputValue || !html5InputSupport) propertySetters.type = function(node, type){ + try { + var value = node.value; + node.type = type; + node.value = value; + } catch (e){} +}; +/*</IE>*/ + +/* getProperty, setProperty */ + +/* <ltIE9> */ +var pollutesGetAttribute = (function(div){ + div.random = 'attribute'; + return (div.getAttribute('random') == 'attribute'); +})(document.createElement('div')); + +var hasCloneBug = (function(test){ + test.innerHTML = '<object><param name="should_fix" value="the unknown" /></object>'; + return test.cloneNode(true).firstChild.childNodes.length != 1; +})(document.createElement('div')); +/* </ltIE9> */ + +var hasClassList = !!document.createElement('div').classList; + +var classes = function(className){ + var classNames = (className || '').clean().split(" "), uniques = {}; + return classNames.filter(function(className){ + if (className !== "" && !uniques[className]) return uniques[className] = className; + }); +}; + +var addToClassList = function(name){ + this.classList.add(name); +}; + +var removeFromClassList = function(name){ + this.classList.remove(name); +}; + +Element.implement({ + + setProperty: function(name, value){ + var setter = propertySetters[name.toLowerCase()]; + if (setter){ + setter(this, value); + } else { + /* <ltIE9> */ + var attributeWhiteList; + if (pollutesGetAttribute) attributeWhiteList = this.retrieve('$attributeWhiteList', {}); + /* </ltIE9> */ + + if (value == null){ + this.removeAttribute(name); + /* <ltIE9> */ + if (pollutesGetAttribute) delete attributeWhiteList[name]; + /* </ltIE9> */ + } else { + this.setAttribute(name, '' + value); + /* <ltIE9> */ + if (pollutesGetAttribute) attributeWhiteList[name] = true; + /* </ltIE9> */ + } + } + return this; + }, + + setProperties: function(attributes){ + for (var attribute in attributes) this.setProperty(attribute, attributes[attribute]); + return this; + }, + + getProperty: function(name){ + var getter = propertyGetters[name.toLowerCase()]; + if (getter) return getter(this); + /* <ltIE9> */ + if (pollutesGetAttribute){ + var attr = this.getAttributeNode(name), attributeWhiteList = this.retrieve('$attributeWhiteList', {}); + if (!attr) return null; + if (attr.expando && !attributeWhiteList[name]){ + var outer = this.outerHTML; + // segment by the opening tag and find mention of attribute name + if (outer.substr(0, outer.search(/\/?['"]?>(?![^<]*<['"])/)).indexOf(name) < 0) return null; + attributeWhiteList[name] = true; + } + } + /* </ltIE9> */ + var result = Slick.getAttribute(this, name); + return (!result && !Slick.hasAttribute(this, name)) ? null : result; + }, + + getProperties: function(){ + var args = Array.from(arguments); + return args.map(this.getProperty, this).associate(args); + }, + + removeProperty: function(name){ + return this.setProperty(name, null); + }, + + removeProperties: function(){ + Array.each(arguments, this.removeProperty, this); + return this; + }, + + set: function(prop, value){ + var property = Element.Properties[prop]; + (property && property.set) ? property.set.call(this, value) : this.setProperty(prop, value); + }.overloadSetter(), + + get: function(prop){ + var property = Element.Properties[prop]; + return (property && property.get) ? property.get.apply(this) : this.getProperty(prop); + }.overloadGetter(), + + erase: function(prop){ + var property = Element.Properties[prop]; + (property && property.erase) ? property.erase.apply(this) : this.removeProperty(prop); + return this; + }, + + hasClass: hasClassList ? function(className){ + return this.classList.contains(className); + } : function(className){ + return classes(this.className).contains(className); + }, + + addClass: hasClassList ? function(className){ + classes(className).forEach(addToClassList, this); + return this; + } : function(className){ + this.className = classes(className + ' ' + this.className).join(' '); + return this; + }, + + removeClass: hasClassList ? function(className){ + classes(className).forEach(removeFromClassList, this); + return this; + } : function(className){ + var classNames = classes(this.className); + classes(className).forEach(classNames.erase, classNames); + this.className = classNames.join(' '); + return this; + }, + + toggleClass: function(className, force){ + if (force == null) force = !this.hasClass(className); + return (force) ? this.addClass(className) : this.removeClass(className); + }, + + adopt: function(){ + var parent = this, fragment, elements = Array.flatten(arguments), length = elements.length; + if (length > 1) parent = fragment = document.createDocumentFragment(); + + for (var i = 0; i < length; i++){ + var element = document.id(elements[i], true); + if (element) parent.appendChild(element); + } + + if (fragment) this.appendChild(fragment); + + return this; + }, + + appendText: function(text, where){ + return this.grab(this.getDocument().newTextNode(text), where); + }, + + grab: function(el, where){ + inserters[where || 'bottom'](document.id(el, true), this); + return this; + }, + + inject: function(el, where){ + inserters[where || 'bottom'](this, document.id(el, true)); + return this; + }, + + replaces: function(el){ + el = document.id(el, true); + el.parentNode.replaceChild(this, el); + return this; + }, + + wraps: function(el, where){ + el = document.id(el, true); + return this.replaces(el).grab(el, where); + }, + + getSelected: function(){ + this.selectedIndex; // Safari 3.2.1 + return new Elements(Array.from(this.options).filter(function(option){ + return option.selected; + })); + }, + + toQueryString: function(){ + var queryString = []; + this.getElements('input, select, textarea').each(function(el){ + var type = el.type; + if (!el.name || el.disabled || type == 'submit' || type == 'reset' || type == 'file' || type == 'image') return; + + var value = (el.get('tag') == 'select') ? el.getSelected().map(function(opt){ + // IE + return document.id(opt).get('value'); + }) : ((type == 'radio' || type == 'checkbox') && !el.checked) ? null : el.get('value'); + + Array.from(value).each(function(val){ + if (typeof val != 'undefined') queryString.push(encodeURIComponent(el.name) + '=' + encodeURIComponent(val)); + }); + }); + return queryString.join('&'); + } + +}); + + +// appendHTML + +var appendInserters = { + before: 'beforeBegin', + after: 'afterEnd', + bottom: 'beforeEnd', + top: 'afterBegin', + inside: 'beforeEnd' +}; + +Element.implement('appendHTML', ('insertAdjacentHTML' in document.createElement('div')) ? function(html, where){ + this.insertAdjacentHTML(appendInserters[where || 'bottom'], html); + return this; +} : function(html, where){ + var temp = new Element('div', {html: html}), + children = temp.childNodes, + fragment = temp.firstChild; + + if (!fragment) return this; + if (children.length > 1){ + fragment = document.createDocumentFragment(); + for (var i = 0, l = children.length; i < l; i++){ + fragment.appendChild(children[i]); + } + } + + inserters[where || 'bottom'](fragment, this); + return this; +}); + +var collected = {}, storage = {}; + +var get = function(uid){ + return (storage[uid] || (storage[uid] = {})); +}; + +var clean = function(item){ + var uid = item.uniqueNumber; + if (item.removeEvents) item.removeEvents(); + if (item.clearAttributes) item.clearAttributes(); + if (uid != null){ + delete collected[uid]; + delete storage[uid]; + } + return item; +}; + +var formProps = {input: 'checked', option: 'selected', textarea: 'value'}; + +Element.implement({ + + destroy: function(){ + var children = clean(this).getElementsByTagName('*'); + Array.each(children, clean); + Element.dispose(this); + return null; + }, + + empty: function(){ + Array.from(this.childNodes).each(Element.dispose); + return this; + }, + + dispose: function(){ + return (this.parentNode) ? this.parentNode.removeChild(this) : this; + }, + + clone: function(contents, keepid){ + contents = contents !== false; + var clone = this.cloneNode(contents), ce = [clone], te = [this], i; + + if (contents){ + ce.append(Array.from(clone.getElementsByTagName('*'))); + te.append(Array.from(this.getElementsByTagName('*'))); + } + + for (i = ce.length; i--;){ + var node = ce[i], element = te[i]; + if (!keepid) node.removeAttribute('id'); + /*<ltIE9>*/ + if (node.clearAttributes){ + node.clearAttributes(); + node.mergeAttributes(element); + node.removeAttribute('uniqueNumber'); + if (node.options){ + var no = node.options, eo = element.options; + for (var j = no.length; j--;) no[j].selected = eo[j].selected; + } + } + /*</ltIE9>*/ + var prop = formProps[element.tagName.toLowerCase()]; + if (prop && element[prop]) node[prop] = element[prop]; + } + + /*<ltIE9>*/ + if (hasCloneBug){ + var co = clone.getElementsByTagName('object'), to = this.getElementsByTagName('object'); + for (i = co.length; i--;) co[i].outerHTML = to[i].outerHTML; + } + /*</ltIE9>*/ + return document.id(clone); + } + +}); + +[Element, Window, Document].invoke('implement', { + + addListener: function(type, fn){ + if (window.attachEvent && !window.addEventListener){ + collected[Slick.uidOf(this)] = this; + } + if (this.addEventListener) this.addEventListener(type, fn, !!arguments[2]); + else this.attachEvent('on' + type, fn); + return this; + }, + + removeListener: function(type, fn){ + if (this.removeEventListener) this.removeEventListener(type, fn, !!arguments[2]); + else this.detachEvent('on' + type, fn); + return this; + }, + + retrieve: function(property, dflt){ + var storage = get(Slick.uidOf(this)), prop = storage[property]; + if (dflt != null && prop == null) prop = storage[property] = dflt; + return prop != null ? prop : null; + }, + + store: function(property, value){ + var storage = get(Slick.uidOf(this)); + storage[property] = value; + return this; + }, + + eliminate: function(property){ + var storage = get(Slick.uidOf(this)); + delete storage[property]; + return this; + } + +}); + +/*<ltIE9>*/ +if (window.attachEvent && !window.addEventListener){ + var gc = function(){ + Object.each(collected, clean); + if (window.CollectGarbage) CollectGarbage(); + window.removeListener('unload', gc); + } + window.addListener('unload', gc); +} +/*</ltIE9>*/ + +Element.Properties = {}; + + + +Element.Properties.style = { + + set: function(style){ + this.style.cssText = style; + }, + + get: function(){ + return this.style.cssText; + }, + + erase: function(){ + this.style.cssText = ''; + } + +}; + +Element.Properties.tag = { + + get: function(){ + return this.tagName.toLowerCase(); + } + +}; + +Element.Properties.html = { + + set: function(html){ + if (html == null) html = ''; + else if (typeOf(html) == 'array') html = html.join(''); + + /*<ltIE9>*/ + if (this.styleSheet && !canChangeStyleHTML) this.styleSheet.cssText = html; + else /*</ltIE9>*/this.innerHTML = html; + }, + erase: function(){ + this.set('html', ''); + } + +}; + +var supportsHTML5Elements = true, supportsTableInnerHTML = true, supportsTRInnerHTML = true; + +/*<ltIE9>*/ +// technique by jdbarlett - http://jdbartlett.com/innershiv/ +var div = document.createElement('div'); +div.innerHTML = '<nav></nav>'; +supportsHTML5Elements = (div.childNodes.length == 1); +if (!supportsHTML5Elements){ + var tags = 'abbr article aside audio canvas datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video'.split(' '), + fragment = document.createDocumentFragment(), l = tags.length; + while (l--) fragment.createElement(tags[l]); +} +div = null; +/*</ltIE9>*/ + +/*<IE>*/ +supportsTableInnerHTML = Function.attempt(function(){ + var table = document.createElement('table'); + table.innerHTML = '<tr><td></td></tr>'; + return true; +}); + +/*<ltFF4>*/ +var tr = document.createElement('tr'), html = '<td></td>'; +tr.innerHTML = html; +supportsTRInnerHTML = (tr.innerHTML == html); +tr = null; +/*</ltFF4>*/ + +if (!supportsTableInnerHTML || !supportsTRInnerHTML || !supportsHTML5Elements){ + + Element.Properties.html.set = (function(set){ + + var translations = { + table: [1, '<table>', '</table>'], + select: [1, '<select>', '</select>'], + tbody: [2, '<table><tbody>', '</tbody></table>'], + tr: [3, '<table><tbody><tr>', '</tr></tbody></table>'] + }; + + translations.thead = translations.tfoot = translations.tbody; + + return function(html){ + + /*<ltIE9>*/ + if (this.styleSheet) return set.call(this, html); + /*</ltIE9>*/ + var wrap = translations[this.get('tag')]; + if (!wrap && !supportsHTML5Elements) wrap = [0, '', '']; + if (!wrap) return set.call(this, html); + + var level = wrap[0], wrapper = document.createElement('div'), target = wrapper; + if (!supportsHTML5Elements) fragment.appendChild(wrapper); + wrapper.innerHTML = [wrap[1], html, wrap[2]].flatten().join(''); + while (level--) target = target.firstChild; + this.empty().adopt(target.childNodes); + if (!supportsHTML5Elements) fragment.removeChild(wrapper); + wrapper = null; + }; + + })(Element.Properties.html.set); +} +/*</IE>*/ + +/*<ltIE9>*/ +var testForm = document.createElement('form'); +testForm.innerHTML = '<select><option>s</option></select>'; + +if (testForm.firstChild.value != 's') Element.Properties.value = { + + set: function(value){ + var tag = this.get('tag'); + if (tag != 'select') return this.setProperty('value', value); + var options = this.getElements('option'); + value = String(value); + for (var i = 0; i < options.length; i++){ + var option = options[i], + attr = option.getAttributeNode('value'), + optionValue = (attr && attr.specified) ? option.value : option.get('text'); + if (optionValue === value) return option.selected = true; + } + }, + + get: function(){ + var option = this, tag = option.get('tag'); + + if (tag != 'select' && tag != 'option') return this.getProperty('value'); + + if (tag == 'select' && !(option = option.getSelected()[0])) return ''; + + var attr = option.getAttributeNode('value'); + return (attr && attr.specified) ? option.value : option.get('text'); + } + +}; +testForm = null; +/*</ltIE9>*/ + +/*<IE>*/ +if (document.createElement('div').getAttributeNode('id')) Element.Properties.id = { + set: function(id){ + this.id = this.getAttributeNode('id').value = id; + }, + get: function(){ + return this.id || null; + }, + erase: function(){ + this.id = this.getAttributeNode('id').value = ''; + } +}; +/*</IE>*/ + +})(); + +/* +--- + +name: Event + +description: Contains the Event Type, to make the event object cross-browser. + +license: MIT-style license. + +requires: [Window, Document, Array, Function, String, Object] + +provides: Event + +... +*/ + +(function(){ + +var _keys = {}; +var normalizeWheelSpeed = function(event){ + var normalized; + if (event.wheelDelta){ + normalized = event.wheelDelta % 120 == 0 ? event.wheelDelta / 120 : event.wheelDelta / 12; + } else { + var rawAmount = event.deltaY || event.detail || 0; + normalized = -(rawAmount % 3 == 0 ? rawAmount / 3 : rawAmount * 10); + } + return normalized; +} + +var DOMEvent = this.DOMEvent = new Type('DOMEvent', function(event, win){ + if (!win) win = window; + event = event || win.event; + if (event.$extended) return event; + this.event = event; + this.$extended = true; + this.shift = event.shiftKey; + this.control = event.ctrlKey; + this.alt = event.altKey; + this.meta = event.metaKey; + var type = this.type = event.type; + var target = event.target || event.srcElement; + while (target && target.nodeType == 3) target = target.parentNode; + this.target = document.id(target); + + if (type.indexOf('key') == 0){ + var code = this.code = (event.which || event.keyCode); + this.key = _keys[code]; + if (type == 'keydown' || type == 'keyup'){ + if (code > 111 && code < 124) this.key = 'f' + (code - 111); + else if (code > 95 && code < 106) this.key = code - 96; + } + if (this.key == null) this.key = String.fromCharCode(code).toLowerCase(); + } else if (type == 'click' || type == 'dblclick' || type == 'contextmenu' || type == 'wheel' || type == 'DOMMouseScroll' || type.indexOf('mouse') == 0){ + var doc = win.document; + doc = (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body; + this.page = { + x: (event.pageX != null) ? event.pageX : event.clientX + doc.scrollLeft, + y: (event.pageY != null) ? event.pageY : event.clientY + doc.scrollTop + }; + this.client = { + x: (event.pageX != null) ? event.pageX - win.pageXOffset : event.clientX, + y: (event.pageY != null) ? event.pageY - win.pageYOffset : event.clientY + }; + if (type == 'DOMMouseScroll' || type == 'wheel' || type == 'mousewheel') this.wheel = normalizeWheelSpeed(event); + this.rightClick = (event.which == 3 || event.button == 2); + if (type == 'mouseover' || type == 'mouseout'){ + var related = event.relatedTarget || event[(type == 'mouseover' ? 'from' : 'to') + 'Element']; + while (related && related.nodeType == 3) related = related.parentNode; + this.relatedTarget = document.id(related); + } + } else if (type.indexOf('touch') == 0 || type.indexOf('gesture') == 0){ + this.rotation = event.rotation; + this.scale = event.scale; + this.targetTouches = event.targetTouches; + this.changedTouches = event.changedTouches; + var touches = this.touches = event.touches; + if (touches && touches[0]){ + var touch = touches[0]; + this.page = {x: touch.pageX, y: touch.pageY}; + this.client = {x: touch.clientX, y: touch.clientY}; + } + } + + if (!this.client) this.client = {}; + if (!this.page) this.page = {}; +}); + +DOMEvent.implement({ + + stop: function(){ + return this.preventDefault().stopPropagation(); + }, + + stopPropagation: function(){ + if (this.event.stopPropagation) this.event.stopPropagation(); + else this.event.cancelBubble = true; + return this; + }, + + preventDefault: function(){ + if (this.event.preventDefault) this.event.preventDefault(); + else this.event.returnValue = false; + return this; + } + +}); + +DOMEvent.defineKey = function(code, key){ + _keys[code] = key; + return this; +}; + +DOMEvent.defineKeys = DOMEvent.defineKey.overloadSetter(true); + +DOMEvent.defineKeys({ + '38': 'up', '40': 'down', '37': 'left', '39': 'right', + '27': 'esc', '32': 'space', '8': 'backspace', '9': 'tab', + '46': 'delete', '13': 'enter' +}); + +})(); + + + + + +/* +--- + +name: Element.Event + +description: Contains Element methods for dealing with events. This file also includes mouseenter and mouseleave custom Element Events, if necessary. + +license: MIT-style license. + +requires: [Element, Event] + +provides: Element.Event + +... +*/ + +(function(){ + +Element.Properties.events = {set: function(events){ + this.addEvents(events); +}}; + +[Element, Window, Document].invoke('implement', { + + addEvent: function(type, fn){ + var events = this.retrieve('events', {}); + if (!events[type]) events[type] = {keys: [], values: []}; + if (events[type].keys.contains(fn)) return this; + events[type].keys.push(fn); + var realType = type, + custom = Element.Events[type], + condition = fn, + self = this; + if (custom){ + if (custom.onAdd) custom.onAdd.call(this, fn, type); + if (custom.condition){ + condition = function(event){ + if (custom.condition.call(this, event, type)) return fn.call(this, event); + return true; + }; + } + if (custom.base) realType = Function.from(custom.base).call(this, type); + } + var defn = function(){ + return fn.call(self); + }; + var nativeEvent = Element.NativeEvents[realType]; + if (nativeEvent){ + if (nativeEvent == 2){ + defn = function(event){ + event = new DOMEvent(event, self.getWindow()); + if (condition.call(self, event) === false) event.stop(); + }; + } + this.addListener(realType, defn, arguments[2]); + } + events[type].values.push(defn); + return this; + }, + + removeEvent: function(type, fn){ + var events = this.retrieve('events'); + if (!events || !events[type]) return this; + var list = events[type]; + var index = list.keys.indexOf(fn); + if (index == -1) return this; + var value = list.values[index]; + delete list.keys[index]; + delete list.values[index]; + var custom = Element.Events[type]; + if (custom){ + if (custom.onRemove) custom.onRemove.call(this, fn, type); + if (custom.base) type = Function.from(custom.base).call(this, type); + } + return (Element.NativeEvents[type]) ? this.removeListener(type, value, arguments[2]) : this; + }, + + addEvents: function(events){ + for (var event in events) this.addEvent(event, events[event]); + return this; + }, + + removeEvents: function(events){ + var type; + if (typeOf(events) == 'object'){ + for (type in events) this.removeEvent(type, events[type]); + return this; + } + var attached = this.retrieve('events'); + if (!attached) return this; + if (!events){ + for (type in attached) this.removeEvents(type); + this.eliminate('events'); + } else if (attached[events]){ + attached[events].keys.each(function(fn){ + this.removeEvent(events, fn); + }, this); + delete attached[events]; + } + return this; + }, + + fireEvent: function(type, args, delay){ + var events = this.retrieve('events'); + if (!events || !events[type]) return this; + args = Array.from(args); + + events[type].keys.each(function(fn){ + if (delay) fn.delay(delay, this, args); + else fn.apply(this, args); + }, this); + return this; + }, + + cloneEvents: function(from, type){ + from = document.id(from); + var events = from.retrieve('events'); + if (!events) return this; + if (!type){ + for (var eventType in events) this.cloneEvents(from, eventType); + } else if (events[type]){ + events[type].keys.each(function(fn){ + this.addEvent(type, fn); + }, this); + } + return this; + } + +}); + +Element.NativeEvents = { + click: 2, dblclick: 2, mouseup: 2, mousedown: 2, contextmenu: 2, //mouse buttons + wheel: 2, mousewheel: 2, DOMMouseScroll: 2, //mouse wheel + mouseover: 2, mouseout: 2, mousemove: 2, selectstart: 2, selectend: 2, //mouse movement + keydown: 2, keypress: 2, keyup: 2, //keyboard + orientationchange: 2, // mobile + touchstart: 2, touchmove: 2, touchend: 2, touchcancel: 2, // touch + gesturestart: 2, gesturechange: 2, gestureend: 2, // gesture + focus: 2, blur: 2, change: 2, reset: 2, select: 2, submit: 2, paste: 2, input: 2, //form elements + load: 2, unload: 1, beforeunload: 2, resize: 1, move: 1, DOMContentLoaded: 1, readystatechange: 1, //window + hashchange: 1, popstate: 2, // history + error: 1, abort: 1, scroll: 1, message: 2 //misc +}; + +Element.Events = { + mousewheel: { + base: 'onwheel' in document ? 'wheel' : 'onmousewheel' in document ? 'mousewheel' : 'DOMMouseScroll' + } +}; + +var check = function(event){ + var related = event.relatedTarget; + if (related == null) return true; + if (!related) return false; + return (related != this && related.prefix != 'xul' && typeOf(this) != 'document' && !this.contains(related)); +}; + +if ('onmouseenter' in document.documentElement){ + Element.NativeEvents.mouseenter = Element.NativeEvents.mouseleave = 2; + Element.MouseenterCheck = check; +} else { + Element.Events.mouseenter = { + base: 'mouseover', + condition: check + }; + + Element.Events.mouseleave = { + base: 'mouseout', + condition: check + }; +} + +/*<ltIE9>*/ +if (!window.addEventListener){ + Element.NativeEvents.propertychange = 2; + Element.Events.change = { + base: function(){ + var type = this.type; + return (this.get('tag') == 'input' && (type == 'radio' || type == 'checkbox')) ? 'propertychange' : 'change'; + }, + condition: function(event){ + return event.type != 'propertychange' || event.event.propertyName == 'checked'; + } + }; +} +/*</ltIE9>*/ + + + +})(); + +/* +--- + +name: Element.Delegation + +description: Extends the Element native object to include the delegate method for more efficient event management. + +license: MIT-style license. + +requires: [Element.Event] + +provides: [Element.Delegation] + +... +*/ + +(function(){ + +var eventListenerSupport = !!window.addEventListener; + +Element.NativeEvents.focusin = Element.NativeEvents.focusout = 2; + +var bubbleUp = function(self, match, fn, event, target){ + while (target && target != self){ + if (match(target, event)) return fn.call(target, event, target); + target = document.id(target.parentNode); + } +}; + +var map = { + mouseenter: { + base: 'mouseover', + condition: Element.MouseenterCheck + }, + mouseleave: { + base: 'mouseout', + condition: Element.MouseenterCheck + }, + focus: { + base: 'focus' + (eventListenerSupport ? '' : 'in'), + capture: true + }, + blur: { + base: eventListenerSupport ? 'blur' : 'focusout', + capture: true + } +}; + +/*<ltIE9>*/ +var _key = '$delegation:'; +var formObserver = function(type){ + + return { + + base: 'focusin', + + remove: function(self, uid){ + var list = self.retrieve(_key + type + 'listeners', {})[uid]; + if (list && list.forms) for (var i = list.forms.length; i--;){ + // the form may have been destroyed, so it won't have the + // removeEvent method anymore. In that case the event was + // removed as well. + if (list.forms[i].removeEvent) list.forms[i].removeEvent(type, list.fns[i]); + } + }, + + listen: function(self, match, fn, event, target, uid){ + var form = (target.get('tag') == 'form') ? target : event.target.getParent('form'); + if (!form) return; + + var listeners = self.retrieve(_key + type + 'listeners', {}), + listener = listeners[uid] || {forms: [], fns: []}, + forms = listener.forms, fns = listener.fns; + + if (forms.indexOf(form) != -1) return; + forms.push(form); + + var _fn = function(event){ + bubbleUp(self, match, fn, event, target); + }; + form.addEvent(type, _fn); + fns.push(_fn); + + listeners[uid] = listener; + self.store(_key + type + 'listeners', listeners); + } + }; +}; + +var inputObserver = function(type){ + return { + base: 'focusin', + listen: function(self, match, fn, event, target){ + var events = {blur: function(){ + this.removeEvents(events); + }}; + events[type] = function(event){ + bubbleUp(self, match, fn, event, target); + }; + event.target.addEvents(events); + } + }; +}; + +if (!eventListenerSupport) Object.append(map, { + submit: formObserver('submit'), + reset: formObserver('reset'), + change: inputObserver('change'), + select: inputObserver('select') +}); +/*</ltIE9>*/ + +var proto = Element.prototype, + addEvent = proto.addEvent, + removeEvent = proto.removeEvent; + +var relay = function(old, method){ + return function(type, fn, useCapture){ + if (type.indexOf(':relay') == -1) return old.call(this, type, fn, useCapture); + var parsed = Slick.parse(type).expressions[0][0]; + if (parsed.pseudos[0].key != 'relay') return old.call(this, type, fn, useCapture); + var newType = parsed.tag; + parsed.pseudos.slice(1).each(function(pseudo){ + newType += ':' + pseudo.key + (pseudo.value ? '(' + pseudo.value + ')' : ''); + }); + old.call(this, type, fn); + return method.call(this, newType, parsed.pseudos[0].value, fn); + }; +}; + +var delegation = { + + addEvent: function(type, match, fn){ + var storage = this.retrieve('$delegates', {}), stored = storage[type]; + if (stored) for (var _uid in stored){ + if (stored[_uid].fn == fn && stored[_uid].match == match) return this; + } + + var _type = type, _match = match, _fn = fn, _map = map[type] || {}; + type = _map.base || _type; + + match = function(target){ + return Slick.match(target, _match); + }; + + var elementEvent = Element.Events[_type]; + if (_map.condition || elementEvent && elementEvent.condition){ + var __match = match, condition = _map.condition || elementEvent.condition; + match = function(target, event){ + return __match(target, event) && condition.call(target, event, type); + }; + } + + var self = this, uid = String.uniqueID(); + var delegator = _map.listen ? function(event, target){ + if (!target && event && event.target) target = event.target; + if (target) _map.listen(self, match, fn, event, target, uid); + } : function(event, target){ + if (!target && event && event.target) target = event.target; + if (target) bubbleUp(self, match, fn, event, target); + }; + + if (!stored) stored = {}; + stored[uid] = { + match: _match, + fn: _fn, + delegator: delegator + }; + storage[_type] = stored; + return addEvent.call(this, type, delegator, _map.capture); + }, + + removeEvent: function(type, match, fn, _uid){ + var storage = this.retrieve('$delegates', {}), stored = storage[type]; + if (!stored) return this; + + if (_uid){ + var _type = type, delegator = stored[_uid].delegator, _map = map[type] || {}; + type = _map.base || _type; + if (_map.remove) _map.remove(this, _uid); + delete stored[_uid]; + storage[_type] = stored; + return removeEvent.call(this, type, delegator, _map.capture); + } + + var __uid, s; + if (fn) for (__uid in stored){ + s = stored[__uid]; + if (s.match == match && s.fn == fn) return delegation.removeEvent.call(this, type, match, fn, __uid); + } else for (__uid in stored){ + s = stored[__uid]; + if (s.match == match) delegation.removeEvent.call(this, type, match, s.fn, __uid); + } + return this; + } + +}; + +[Element, Window, Document].invoke('implement', { + addEvent: relay(addEvent, delegation.addEvent), + removeEvent: relay(removeEvent, delegation.removeEvent) +}); + +})(); + +/* +--- + +name: Element.Style + +description: Contains methods for interacting with the styles of Elements in a fashionable way. + +license: MIT-style license. + +requires: Element + +provides: Element.Style + +... +*/ + +(function(){ + +var html = document.html, el; + +//<ltIE9> +// Check for oldIE, which does not remove styles when they're set to null +el = document.createElement('div'); +el.style.color = 'red'; +el.style.color = null; +var doesNotRemoveStyles = el.style.color == 'red'; + +// check for oldIE, which returns border* shorthand styles in the wrong order (color-width-style instead of width-style-color) +var border = '1px solid #123abc'; +el.style.border = border; +var returnsBordersInWrongOrder = el.style.border != border; +el = null; +//</ltIE9> + +var hasGetComputedStyle = !!window.getComputedStyle, + supportBorderRadius = document.createElement('div').style.borderRadius != null; + +Element.Properties.styles = {set: function(styles){ + this.setStyles(styles); +}}; + +var hasOpacity = (html.style.opacity != null), + hasFilter = (html.style.filter != null), + reAlpha = /alpha\(opacity=([\d.]+)\)/i; + +var setVisibility = function(element, opacity){ + element.store('$opacity', opacity); + element.style.visibility = opacity > 0 || opacity == null ? 'visible' : 'hidden'; +}; + +//<ltIE9> +var setFilter = function(element, regexp, value){ + var style = element.style, + filter = style.filter || element.getComputedStyle('filter') || ''; + style.filter = (regexp.test(filter) ? filter.replace(regexp, value) : filter + ' ' + value).trim(); + if (!style.filter) style.removeAttribute('filter'); +}; +//</ltIE9> + +var setOpacity = (hasOpacity ? function(element, opacity){ + element.style.opacity = opacity; +} : (hasFilter ? function(element, opacity){ + if (!element.currentStyle || !element.currentStyle.hasLayout) element.style.zoom = 1; + if (opacity == null || opacity == 1){ + setFilter(element, reAlpha, ''); + if (opacity == 1 && getOpacity(element) != 1) setFilter(element, reAlpha, 'alpha(opacity=100)'); + } else { + setFilter(element, reAlpha, 'alpha(opacity=' + (opacity * 100).limit(0, 100).round() + ')'); + } +} : setVisibility)); + +var getOpacity = (hasOpacity ? function(element){ + var opacity = element.style.opacity || element.getComputedStyle('opacity'); + return (opacity == '') ? 1 : opacity.toFloat(); +} : (hasFilter ? function(element){ + var filter = (element.style.filter || element.getComputedStyle('filter')), + opacity; + if (filter) opacity = filter.match(reAlpha); + return (opacity == null || filter == null) ? 1 : (opacity[1] / 100); +} : function(element){ + var opacity = element.retrieve('$opacity'); + if (opacity == null) opacity = (element.style.visibility == 'hidden' ? 0 : 1); + return opacity; +})); + +var floatName = (html.style.cssFloat == null) ? 'styleFloat' : 'cssFloat', + namedPositions = {left: '0%', top: '0%', center: '50%', right: '100%', bottom: '100%'}, + hasBackgroundPositionXY = (html.style.backgroundPositionX != null); + +//<ltIE9> +var removeStyle = function(style, property){ + if (property == 'backgroundPosition'){ + style.removeAttribute(property + 'X'); + property += 'Y'; + } + style.removeAttribute(property); +}; +//</ltIE9> + +Element.implement({ + + getComputedStyle: function(property){ + if (!hasGetComputedStyle && this.currentStyle) return this.currentStyle[property.camelCase()]; + var defaultView = Element.getDocument(this).defaultView, + computed = defaultView ? defaultView.getComputedStyle(this, null) : null; + return (computed) ? computed.getPropertyValue((property == floatName) ? 'float' : property.hyphenate()) : ''; + }, + + setStyle: function(property, value){ + if (property == 'opacity'){ + if (value != null) value = parseFloat(value); + setOpacity(this, value); + return this; + } + property = (property == 'float' ? floatName : property).camelCase(); + if (typeOf(value) != 'string'){ + var map = (Element.Styles[property] || '@').split(' '); + value = Array.from(value).map(function(val, i){ + if (!map[i]) return ''; + return (typeOf(val) == 'number') ? map[i].replace('@', Math.round(val)) : val; + }).join(' '); + } else if (value == String(Number(value))){ + value = Math.round(value); + } + this.style[property] = value; + //<ltIE9> + if ((value == '' || value == null) && doesNotRemoveStyles && this.style.removeAttribute){ + removeStyle(this.style, property); + } + //</ltIE9> + return this; + }, + + getStyle: function(property){ + if (property == 'opacity') return getOpacity(this); + property = (property == 'float' ? floatName : property).camelCase(); + if (supportBorderRadius && property.indexOf('borderRadius') != -1){ + return ['borderTopLeftRadius', 'borderTopRightRadius', 'borderBottomRightRadius', 'borderBottomLeftRadius'].map(function(corner){ + return this.style[corner] || '0px'; + }, this).join(' '); + } + var result = this.style[property]; + if (!result || property == 'zIndex'){ + if (Element.ShortStyles.hasOwnProperty(property)){ + result = []; + for (var s in Element.ShortStyles[property]) result.push(this.getStyle(s)); + return result.join(' '); + } + result = this.getComputedStyle(property); + } + if (hasBackgroundPositionXY && /^backgroundPosition[XY]?$/.test(property)){ + return result.replace(/(top|right|bottom|left)/g, function(position){ + return namedPositions[position]; + }) || '0px'; + } + if (!result && property == 'backgroundPosition') return '0px 0px'; + if (result){ + result = String(result); + var color = result.match(/rgba?\([\d\s,]+\)/); + if (color) result = result.replace(color[0], color[0].rgbToHex()); + } + if (!hasGetComputedStyle && !this.style[property]){ + if ((/^(height|width)$/).test(property) && !(/px$/.test(result))){ + var values = (property == 'width') ? ['left', 'right'] : ['top', 'bottom'], size = 0; + values.each(function(value){ + size += this.getStyle('border-' + value + '-width').toInt() + this.getStyle('padding-' + value).toInt(); + }, this); + return this['offset' + property.capitalize()] - size + 'px'; + } + if ((/^border(.+)Width|margin|padding/).test(property) && isNaN(parseFloat(result))){ + return '0px'; + } + } + //<ltIE9> + if (returnsBordersInWrongOrder && /^border(Top|Right|Bottom|Left)?$/.test(property) && /^#/.test(result)){ + return result.replace(/^(.+)\s(.+)\s(.+)$/, '$2 $3 $1'); + } + //</ltIE9> + + return result; + }, + + setStyles: function(styles){ + for (var style in styles) this.setStyle(style, styles[style]); + return this; + }, + + getStyles: function(){ + var result = {}; + Array.flatten(arguments).each(function(key){ + result[key] = this.getStyle(key); + }, this); + return result; + } + +}); + +Element.Styles = { + left: '@px', top: '@px', bottom: '@px', right: '@px', + width: '@px', height: '@px', maxWidth: '@px', maxHeight: '@px', minWidth: '@px', minHeight: '@px', + backgroundColor: 'rgb(@, @, @)', backgroundSize: '@px', backgroundPosition: '@px @px', color: 'rgb(@, @, @)', + fontSize: '@px', letterSpacing: '@px', lineHeight: '@px', clip: 'rect(@px @px @px @px)', + margin: '@px @px @px @px', padding: '@px @px @px @px', border: '@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)', + borderWidth: '@px @px @px @px', borderStyle: '@ @ @ @', borderColor: 'rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)', + zIndex: '@', 'zoom': '@', fontWeight: '@', textIndent: '@px', opacity: '@', borderRadius: '@px @px @px @px' +}; + + + + + +Element.ShortStyles = {margin: {}, padding: {}, border: {}, borderWidth: {}, borderStyle: {}, borderColor: {}}; + +['Top', 'Right', 'Bottom', 'Left'].each(function(direction){ + var Short = Element.ShortStyles; + var All = Element.Styles; + ['margin', 'padding'].each(function(style){ + var sd = style + direction; + Short[style][sd] = All[sd] = '@px'; + }); + var bd = 'border' + direction; + Short.border[bd] = All[bd] = '@px @ rgb(@, @, @)'; + var bdw = bd + 'Width', bds = bd + 'Style', bdc = bd + 'Color'; + Short[bd] = {}; + Short.borderWidth[bdw] = Short[bd][bdw] = All[bdw] = '@px'; + Short.borderStyle[bds] = Short[bd][bds] = All[bds] = '@'; + Short.borderColor[bdc] = Short[bd][bdc] = All[bdc] = 'rgb(@, @, @)'; +}); + +if (hasBackgroundPositionXY) Element.ShortStyles.backgroundPosition = {backgroundPositionX: '@', backgroundPositionY: '@'}; +})(); + +/* +--- + +name: Element.Dimensions + +description: Contains methods to work with size, scroll, or positioning of Elements and the window object. + +license: MIT-style license. + +credits: + - Element positioning based on the [qooxdoo](http://qooxdoo.org/) code and smart browser fixes, [LGPL License](http://www.gnu.org/licenses/lgpl.html). + - Viewport dimensions based on [YUI](http://developer.yahoo.com/yui/) code, [BSD License](http://developer.yahoo.com/yui/license.html). + +requires: [Element, Element.Style] + +provides: [Element.Dimensions] + +... +*/ + +(function(){ + +var element = document.createElement('div'), + child = document.createElement('div'); +element.style.height = '0'; +element.appendChild(child); +var brokenOffsetParent = (child.offsetParent === element); +element = child = null; + +var heightComponents = ['height', 'paddingTop', 'paddingBottom', 'borderTopWidth', 'borderBottomWidth'], + widthComponents = ['width', 'paddingLeft', 'paddingRight', 'borderLeftWidth', 'borderRightWidth']; + +var svgCalculateSize = function(el){ + + var gCS = window.getComputedStyle(el), + bounds = {x: 0, y: 0}; + + heightComponents.each(function(css){ + bounds.y += parseFloat(gCS[css]); + }); + widthComponents.each(function(css){ + bounds.x += parseFloat(gCS[css]); + }); + return bounds; +}; + +var isOffset = function(el){ + return styleString(el, 'position') != 'static' || isBody(el); +}; + +var isOffsetStatic = function(el){ + return isOffset(el) || (/^(?:table|td|th)$/i).test(el.tagName); +}; + +Element.implement({ + + scrollTo: function(x, y){ + if (isBody(this)){ + this.getWindow().scrollTo(x, y); + } else { + this.scrollLeft = x; + this.scrollTop = y; + } + return this; + }, + + getSize: function(){ + if (isBody(this)) return this.getWindow().getSize(); + + //<ltIE9> + // This if clause is because IE8- cannot calculate getBoundingClientRect of elements with visibility hidden. + if (!window.getComputedStyle) return {x: this.offsetWidth, y: this.offsetHeight}; + //</ltIE9> + + // This svg section under, calling `svgCalculateSize()`, can be removed when FF fixed the svg size bug. + // Bug info: https://bugzilla.mozilla.org/show_bug.cgi?id=530985 + if (this.get('tag') == 'svg') return svgCalculateSize(this); + + var bounds = this.getBoundingClientRect(); + return {x: bounds.width, y: bounds.height}; + }, + + getScrollSize: function(){ + if (isBody(this)) return this.getWindow().getScrollSize(); + return {x: this.scrollWidth, y: this.scrollHeight}; + }, + + getScroll: function(){ + if (isBody(this)) return this.getWindow().getScroll(); + return {x: this.scrollLeft, y: this.scrollTop}; + }, + + getScrolls: function(){ + var element = this.parentNode, position = {x: 0, y: 0}; + while (element && !isBody(element)){ + position.x += element.scrollLeft; + position.y += element.scrollTop; + element = element.parentNode; + } + return position; + }, + + getOffsetParent: brokenOffsetParent ? function(){ + var element = this; + if (isBody(element) || styleString(element, 'position') == 'fixed') return null; + + var isOffsetCheck = (styleString(element, 'position') == 'static') ? isOffsetStatic : isOffset; + while ((element = element.parentNode)){ + if (isOffsetCheck(element)) return element; + } + return null; + } : function(){ + var element = this; + if (isBody(element) || styleString(element, 'position') == 'fixed') return null; + + try { + return element.offsetParent; + } catch(e){} + return null; + }, + + getOffsets: function(){ + var hasGetBoundingClientRect = this.getBoundingClientRect; + + if (hasGetBoundingClientRect){ + var bound = this.getBoundingClientRect(), + html = document.id(this.getDocument().documentElement), + htmlScroll = html.getScroll(), + elemScrolls = this.getScrolls(), + isFixed = (styleString(this, 'position') == 'fixed'); + + return { + x: bound.left.toInt() + elemScrolls.x + ((isFixed) ? 0 : htmlScroll.x) - html.clientLeft, + y: bound.top.toInt() + elemScrolls.y + ((isFixed) ? 0 : htmlScroll.y) - html.clientTop + }; + } + + var element = this, position = {x: 0, y: 0}; + if (isBody(this)) return position; + + while (element && !isBody(element)){ + position.x += element.offsetLeft; + position.y += element.offsetTop; + + element = element.offsetParent; + } + + return position; + }, + + getPosition: function(relative){ + var offset = this.getOffsets(), + scroll = this.getScrolls(); + var position = { + x: offset.x - scroll.x, + y: offset.y - scroll.y + }; + + if (relative && (relative = document.id(relative))){ + var relativePosition = relative.getPosition(); + return {x: position.x - relativePosition.x - leftBorder(relative), y: position.y - relativePosition.y - topBorder(relative)}; + } + return position; + }, + + getCoordinates: function(element){ + if (isBody(this)) return this.getWindow().getCoordinates(); + var position = this.getPosition(element), + size = this.getSize(); + var obj = { + left: position.x, + top: position.y, + width: size.x, + height: size.y + }; + obj.right = obj.left + obj.width; + obj.bottom = obj.top + obj.height; + return obj; + }, + + computePosition: function(obj){ + return { + left: obj.x - styleNumber(this, 'margin-left'), + top: obj.y - styleNumber(this, 'margin-top') + }; + }, + + setPosition: function(obj){ + return this.setStyles(this.computePosition(obj)); + } + +}); + + +[Document, Window].invoke('implement', { + + getSize: function(){ + var doc = getCompatElement(this); + return {x: doc.clientWidth, y: doc.clientHeight}; + }, + + getScroll: function(){ + var win = this.getWindow(), doc = getCompatElement(this); + return {x: win.pageXOffset || doc.scrollLeft, y: win.pageYOffset || doc.scrollTop}; + }, + + getScrollSize: function(){ + var doc = getCompatElement(this), + min = this.getSize(), + body = this.getDocument().body; + + return {x: Math.max(doc.scrollWidth, body.scrollWidth, min.x), y: Math.max(doc.scrollHeight, body.scrollHeight, min.y)}; + }, + + getPosition: function(){ + return {x: 0, y: 0}; + }, + + getCoordinates: function(){ + var size = this.getSize(); + return {top: 0, left: 0, bottom: size.y, right: size.x, height: size.y, width: size.x}; + } + +}); + +// private methods + +var styleString = Element.getComputedStyle; + +function styleNumber(element, style){ + return styleString(element, style).toInt() || 0; +} + +function borderBox(element){ + return styleString(element, '-moz-box-sizing') == 'border-box'; +} + +function topBorder(element){ + return styleNumber(element, 'border-top-width'); +} + +function leftBorder(element){ + return styleNumber(element, 'border-left-width'); +} + +function isBody(element){ + return (/^(?:body|html)$/i).test(element.tagName); +} + +function getCompatElement(element){ + var doc = element.getDocument(); + return (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body; +} + +})(); + +//aliases +Element.alias({position: 'setPosition'}); //compatability + +[Window, Document, Element].invoke('implement', { + + getHeight: function(){ + return this.getSize().y; + }, + + getWidth: function(){ + return this.getSize().x; + }, + + getScrollTop: function(){ + return this.getScroll().y; + }, + + getScrollLeft: function(){ + return this.getScroll().x; + }, + + getScrollHeight: function(){ + return this.getScrollSize().y; + }, + + getScrollWidth: function(){ + return this.getScrollSize().x; + }, + + getTop: function(){ + return this.getPosition().y; + }, + + getLeft: function(){ + return this.getPosition().x; + } + +}); + +/* +--- + +name: Fx + +description: Contains the basic animation logic to be extended by all other Fx Classes. + +license: MIT-style license. + +requires: [Chain, Events, Options] + +provides: Fx + +... +*/ + +(function(){ + +var Fx = this.Fx = new Class({ + + Implements: [Chain, Events, Options], + + options: { + /* + onStart: nil, + onCancel: nil, + onComplete: nil, + */ + fps: 60, + unit: false, + duration: 500, + frames: null, + frameSkip: true, + link: 'ignore' + }, + + initialize: function(options){ + this.subject = this.subject || this; + this.setOptions(options); + }, + + getTransition: function(){ + return function(p){ + return -(Math.cos(Math.PI * p) - 1) / 2; + }; + }, + + step: function(now){ + if (this.options.frameSkip){ + var diff = (this.time != null) ? (now - this.time) : 0, frames = diff / this.frameInterval; + this.time = now; + this.frame += frames; + } else { + this.frame++; + } + + if (this.frame < this.frames){ + var delta = this.transition(this.frame / this.frames); + this.set(this.compute(this.from, this.to, delta)); + } else { + this.frame = this.frames; + this.set(this.compute(this.from, this.to, 1)); + this.stop(); + } + }, + + set: function(now){ + return now; + }, + + compute: function(from, to, delta){ + return Fx.compute(from, to, delta); + }, + + check: function(){ + if (!this.isRunning()) return true; + switch (this.options.link){ + case 'cancel': this.cancel(); return true; + case 'chain': this.chain(this.caller.pass(arguments, this)); return false; + } + return false; + }, + + start: function(from, to){ + if (!this.check(from, to)) return this; + this.from = from; + this.to = to; + this.frame = (this.options.frameSkip) ? 0 : -1; + this.time = null; + this.transition = this.getTransition(); + var frames = this.options.frames, fps = this.options.fps, duration = this.options.duration; + this.duration = Fx.Durations[duration] || duration.toInt(); + this.frameInterval = 1000 / fps; + this.frames = frames || Math.round(this.duration / this.frameInterval); + this.fireEvent('start', this.subject); + pushInstance.call(this, fps); + return this; + }, + + stop: function(){ + if (this.isRunning()){ + this.time = null; + pullInstance.call(this, this.options.fps); + if (this.frames == this.frame){ + this.fireEvent('complete', this.subject); + if (!this.callChain()) this.fireEvent('chainComplete', this.subject); + } else { + this.fireEvent('stop', this.subject); + } + } + return this; + }, + + cancel: function(){ + if (this.isRunning()){ + this.time = null; + pullInstance.call(this, this.options.fps); + this.frame = this.frames; + this.fireEvent('cancel', this.subject).clearChain(); + } + return this; + }, + + pause: function(){ + if (this.isRunning()){ + this.time = null; + pullInstance.call(this, this.options.fps); + } + return this; + }, + + resume: function(){ + if (this.isPaused()) pushInstance.call(this, this.options.fps); + return this; + }, + + isRunning: function(){ + var list = instances[this.options.fps]; + return list && list.contains(this); + }, + + isPaused: function(){ + return (this.frame < this.frames) && !this.isRunning(); + } + +}); + +Fx.compute = function(from, to, delta){ + return (to - from) * delta + from; +}; + +Fx.Durations = {'short': 250, 'normal': 500, 'long': 1000}; + +// global timers + +var instances = {}, timers = {}; + +var loop = function(){ + var now = Date.now(); + for (var i = this.length; i--;){ + var instance = this[i]; + if (instance) instance.step(now); + } +}; + +var pushInstance = function(fps){ + var list = instances[fps] || (instances[fps] = []); + list.push(this); + if (!timers[fps]) timers[fps] = loop.periodical(Math.round(1000 / fps), list); +}; + +var pullInstance = function(fps){ + var list = instances[fps]; + if (list){ + list.erase(this); + if (!list.length && timers[fps]){ + delete instances[fps]; + timers[fps] = clearInterval(timers[fps]); + } + } +}; + +})(); + +/* +--- + +name: Fx.CSS + +description: Contains the CSS animation logic. Used by Fx.Tween, Fx.Morph, Fx.Elements. + +license: MIT-style license. + +requires: [Fx, Element.Style] + +provides: Fx.CSS + +... +*/ + +Fx.CSS = new Class({ + + Extends: Fx, + + //prepares the base from/to object + + prepare: function(element, property, values){ + values = Array.from(values); + var from = values[0], to = values[1]; + if (to == null){ + to = from; + from = element.getStyle(property); + var unit = this.options.unit; + // adapted from: https://github.com/ryanmorr/fx/blob/master/fx.js#L299 + if (unit && from && typeof from == 'string' && from.slice(-unit.length) != unit && parseFloat(from) != 0){ + element.setStyle(property, to + unit); + var value = element.getComputedStyle(property); + // IE and Opera support pixelLeft or pixelWidth + if (!(/px$/.test(value))){ + value = element.style[('pixel-' + property).camelCase()]; + if (value == null){ + // adapted from Dean Edwards' http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 + var left = element.style.left; + element.style.left = to + unit; + value = element.style.pixelLeft; + element.style.left = left; + } + } + from = (to || 1) / (parseFloat(value) || 1) * (parseFloat(from) || 0); + element.setStyle(property, from + unit); + } + } + return {from: this.parse(from), to: this.parse(to)}; + }, + + //parses a value into an array + + parse: function(value){ + value = Function.from(value)(); + value = (typeof value == 'string') ? value.split(' ') : Array.from(value); + return value.map(function(val){ + val = String(val); + var found = false; + Object.each(Fx.CSS.Parsers, function(parser, key){ + if (found) return; + var parsed = parser.parse(val); + if (parsed || parsed === 0) found = {value: parsed, parser: parser}; + }); + found = found || {value: val, parser: Fx.CSS.Parsers.String}; + return found; + }); + }, + + //computes by a from and to prepared objects, using their parsers. + + compute: function(from, to, delta){ + var computed = []; + (Math.min(from.length, to.length)).times(function(i){ + computed.push({value: from[i].parser.compute(from[i].value, to[i].value, delta), parser: from[i].parser}); + }); + computed.$family = Function.from('fx:css:value'); + return computed; + }, + + //serves the value as settable + + serve: function(value, unit){ + if (typeOf(value) != 'fx:css:value') value = this.parse(value); + var returned = []; + value.each(function(bit){ + returned = returned.concat(bit.parser.serve(bit.value, unit)); + }); + return returned; + }, + + //renders the change to an element + + render: function(element, property, value, unit){ + element.setStyle(property, this.serve(value, unit)); + }, + + //searches inside the page css to find the values for a selector + + search: function(selector){ + if (Fx.CSS.Cache[selector]) return Fx.CSS.Cache[selector]; + var to = {}, selectorTest = new RegExp('^' + selector.escapeRegExp() + '$'); + + var searchStyles = function(rules){ + Array.each(rules, function(rule, i){ + if (rule.media){ + searchStyles(rule.rules || rule.cssRules); + return; + } + if (!rule.style) return; + var selectorText = (rule.selectorText) ? rule.selectorText.replace(/^\w+/, function(m){ + return m.toLowerCase(); + }) : null; + if (!selectorText || !selectorTest.test(selectorText)) return; + Object.each(Element.Styles, function(value, style){ + if (!rule.style[style] || Element.ShortStyles[style]) return; + value = String(rule.style[style]); + to[style] = ((/^rgb/).test(value)) ? value.rgbToHex() : value; + }); + }); + }; + + Array.each(document.styleSheets, function(sheet, j){ + var href = sheet.href; + if (href && href.indexOf('://') > -1 && href.indexOf(document.domain) == -1) return; + var rules = sheet.rules || sheet.cssRules; + searchStyles(rules); + }); + return Fx.CSS.Cache[selector] = to; + } + +}); + +Fx.CSS.Cache = {}; + +Fx.CSS.Parsers = { + + Color: { + parse: function(value){ + if (value.match(/^#[0-9a-f]{3,6}$/i)) return value.hexToRgb(true); + return ((value = value.match(/(\d+),\s*(\d+),\s*(\d+)/))) ? [value[1], value[2], value[3]] : false; + }, + compute: function(from, to, delta){ + return from.map(function(value, i){ + return Math.round(Fx.compute(from[i], to[i], delta)); + }); + }, + serve: function(value){ + return value.map(Number); + } + }, + + Number: { + parse: parseFloat, + compute: Fx.compute, + serve: function(value, unit){ + return (unit) ? value + unit : value; + } + }, + + String: { + parse: Function.from(false), + compute: function(zero, one){ + return one; + }, + serve: function(zero){ + return zero; + } + } + +}; + + + +/* +--- + +name: Fx.Morph + +description: Formerly Fx.Styles, effect to transition any number of CSS properties for an element using an object of rules, or CSS based selector rules. + +license: MIT-style license. + +requires: Fx.CSS + +provides: Fx.Morph + +... +*/ + +Fx.Morph = new Class({ + + Extends: Fx.CSS, + + initialize: function(element, options){ + this.element = this.subject = document.id(element); + this.parent(options); + }, + + set: function(now){ + if (typeof now == 'string') now = this.search(now); + for (var p in now) this.render(this.element, p, now[p], this.options.unit); + return this; + }, + + compute: function(from, to, delta){ + var now = {}; + for (var p in from) now[p] = this.parent(from[p], to[p], delta); + return now; + }, + + start: function(properties){ + if (!this.check(properties)) return this; + if (typeof properties == 'string') properties = this.search(properties); + var from = {}, to = {}; + for (var p in properties){ + var parsed = this.prepare(this.element, p, properties[p]); + from[p] = parsed.from; + to[p] = parsed.to; + } + return this.parent(from, to); + } + +}); + +Element.Properties.morph = { + + set: function(options){ + this.get('morph').cancel().setOptions(options); + return this; + }, + + get: function(){ + var morph = this.retrieve('morph'); + if (!morph){ + morph = new Fx.Morph(this, {link: 'cancel'}); + this.store('morph', morph); + } + return morph; + } + +}; + +Element.implement({ + + morph: function(props){ + this.get('morph').start(props); + return this; + } + +}); + +/* +--- + +name: Fx.Transitions + +description: Contains a set of advanced transitions to be used with any of the Fx Classes. + +license: MIT-style license. + +credits: + - Easing Equations by Robert Penner, <http://www.robertpenner.com/easing/>, modified and optimized to be used with MooTools. + +requires: Fx + +provides: Fx.Transitions + +... +*/ + +Fx.implement({ + + getTransition: function(){ + var trans = this.options.transition || Fx.Transitions.Sine.easeInOut; + if (typeof trans == 'string'){ + var data = trans.split(':'); + trans = Fx.Transitions; + trans = trans[data[0]] || trans[data[0].capitalize()]; + if (data[1]) trans = trans['ease' + data[1].capitalize() + (data[2] ? data[2].capitalize() : '')]; + } + return trans; + } + +}); + +Fx.Transition = function(transition, params){ + params = Array.from(params); + var easeIn = function(pos){ + return transition(pos, params); + }; + return Object.append(easeIn, { + easeIn: easeIn, + easeOut: function(pos){ + return 1 - transition(1 - pos, params); + }, + easeInOut: function(pos){ + return (pos <= 0.5 ? transition(2 * pos, params) : (2 - transition(2 * (1 - pos), params))) / 2; + } + }); +}; + +Fx.Transitions = { + + linear: function(zero){ + return zero; + } + +}; + + + +Fx.Transitions.extend = function(transitions){ + for (var transition in transitions) Fx.Transitions[transition] = new Fx.Transition(transitions[transition]); +}; + +Fx.Transitions.extend({ + + Pow: function(p, x){ + return Math.pow(p, x && x[0] || 6); + }, + + Expo: function(p){ + return Math.pow(2, 8 * (p - 1)); + }, + + Circ: function(p){ + return 1 - Math.sin(Math.acos(p)); + }, + + Sine: function(p){ + return 1 - Math.cos(p * Math.PI / 2); + }, + + Back: function(p, x){ + x = x && x[0] || 1.618; + return Math.pow(p, 2) * ((x + 1) * p - x); + }, + + Bounce: function(p){ + var value; + for (var a = 0, b = 1; 1; a += b, b /= 2){ + if (p >= (7 - 4 * a) / 11){ + value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2); + break; + } + } + return value; + }, + + Elastic: function(p, x){ + return Math.pow(2, 10 * --p) * Math.cos(20 * p * Math.PI * (x && x[0] || 1) / 3); + } + +}); + +['Quad', 'Cubic', 'Quart', 'Quint'].each(function(transition, i){ + Fx.Transitions[transition] = new Fx.Transition(function(p){ + return Math.pow(p, i + 2); + }); +}); + +/* +--- + +name: Fx.Tween + +description: Formerly Fx.Style, effect to transition any CSS property for an element. + +license: MIT-style license. + +requires: Fx.CSS + +provides: [Fx.Tween, Element.fade, Element.highlight] + +... +*/ + +Fx.Tween = new Class({ + + Extends: Fx.CSS, + + initialize: function(element, options){ + this.element = this.subject = document.id(element); + this.parent(options); + }, + + set: function(property, now){ + if (arguments.length == 1){ + now = property; + property = this.property || this.options.property; + } + this.render(this.element, property, now, this.options.unit); + return this; + }, + + start: function(property, from, to){ + if (!this.check(property, from, to)) return this; + var args = Array.flatten(arguments); + this.property = this.options.property || args.shift(); + var parsed = this.prepare(this.element, this.property, args); + return this.parent(parsed.from, parsed.to); + } + +}); + +Element.Properties.tween = { + + set: function(options){ + this.get('tween').cancel().setOptions(options); + return this; + }, + + get: function(){ + var tween = this.retrieve('tween'); + if (!tween){ + tween = new Fx.Tween(this, {link: 'cancel'}); + this.store('tween', tween); + } + return tween; + } + +}; + +Element.implement({ + + tween: function(property, from, to){ + this.get('tween').start(property, from, to); + return this; + }, + + fade: function(how){ + var fade = this.get('tween'), method, args = ['opacity'].append(arguments), toggle; + if (args[1] == null) args[1] = 'toggle'; + switch (args[1]){ + case 'in': method = 'start'; args[1] = 1; break; + case 'out': method = 'start'; args[1] = 0; break; + case 'show': method = 'set'; args[1] = 1; break; + case 'hide': method = 'set'; args[1] = 0; break; + case 'toggle': + var flag = this.retrieve('fade:flag', this.getStyle('opacity') == 1); + method = 'start'; + args[1] = flag ? 0 : 1; + this.store('fade:flag', !flag); + toggle = true; + break; + default: method = 'start'; + } + if (!toggle) this.eliminate('fade:flag'); + fade[method].apply(fade, args); + var to = args[args.length - 1]; + if (method == 'set' || to != 0) this.setStyle('visibility', to == 0 ? 'hidden' : 'visible'); + else fade.chain(function(){ + this.element.setStyle('visibility', 'hidden'); + this.callChain(); + }); + return this; + }, + + highlight: function(start, end){ + if (!end){ + end = this.retrieve('highlight:original', this.getStyle('background-color')); + end = (end == 'transparent') ? '#fff' : end; + } + var tween = this.get('tween'); + tween.start('background-color', start || '#ffff88', end).chain(function(){ + this.setStyle('background-color', this.retrieve('highlight:original')); + tween.callChain(); + }.bind(this)); + return this; + } + +}); + +/* +--- + +name: Request + +description: Powerful all purpose Request Class. Uses XMLHTTPRequest. + +license: MIT-style license. + +requires: [Object, Element, Chain, Events, Options, Browser] + +provides: Request + +... +*/ + +(function(){ + +var empty = function(){}, + progressSupport = ('onprogress' in new Browser.Request); + +var Request = this.Request = new Class({ + + Implements: [Chain, Events, Options], + + options: {/* + onRequest: function(){}, + onLoadstart: function(event, xhr){}, + onProgress: function(event, xhr){}, + onComplete: function(){}, + onCancel: function(){}, + onSuccess: function(responseText, responseXML){}, + onFailure: function(xhr){}, + onException: function(headerName, value){}, + onTimeout: function(){}, + user: '', + password: '', + withCredentials: false,*/ + url: '', + data: '', + headers: { + 'X-Requested-With': 'XMLHttpRequest', + 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' + }, + async: true, + format: false, + method: 'post', + link: 'ignore', + isSuccess: null, + emulation: true, + urlEncoded: true, + encoding: 'utf-8', + evalScripts: false, + evalResponse: false, + timeout: 0, + noCache: false + }, + + initialize: function(options){ + this.xhr = new Browser.Request(); + this.setOptions(options); + this.headers = this.options.headers; + }, + + onStateChange: function(){ + var xhr = this.xhr; + if (xhr.readyState != 4 || !this.running) return; + this.running = false; + this.status = 0; + Function.attempt(function(){ + var status = xhr.status; + this.status = (status == 1223) ? 204 : status; + }.bind(this)); + xhr.onreadystatechange = empty; + if (progressSupport) xhr.onprogress = xhr.onloadstart = empty; + if (this.timer){ + clearTimeout(this.timer); + delete this.timer; + } + + this.response = {text: this.xhr.responseText || '', xml: this.xhr.responseXML}; + if (this.options.isSuccess.call(this, this.status)) + this.success(this.response.text, this.response.xml); + else + this.failure(); + }, + + isSuccess: function(){ + var status = this.status; + return (status >= 200 && status < 300); + }, + + isRunning: function(){ + return !!this.running; + }, + + processScripts: function(text){ + if (this.options.evalResponse || (/(ecma|java)script/).test(this.getHeader('Content-type'))) return Browser.exec(text); + return text.stripScripts(this.options.evalScripts); + }, + + success: function(text, xml){ + this.onSuccess(this.processScripts(text), xml); + }, + + onSuccess: function(){ + this.fireEvent('complete', arguments).fireEvent('success', arguments).callChain(); + }, + + failure: function(){ + this.onFailure(); + }, + + onFailure: function(){ + this.fireEvent('complete').fireEvent('failure', this.xhr); + }, + + loadstart: function(event){ + this.fireEvent('loadstart', [event, this.xhr]); + }, + + progress: function(event){ + this.fireEvent('progress', [event, this.xhr]); + }, + + timeout: function(){ + this.fireEvent('timeout', this.xhr); + }, + + setHeader: function(name, value){ + this.headers[name] = value; + return this; + }, + + getHeader: function(name){ + return Function.attempt(function(){ + return this.xhr.getResponseHeader(name); + }.bind(this)); + }, + + check: function(){ + if (!this.running) return true; + switch (this.options.link){ + case 'cancel': this.cancel(); return true; + case 'chain': this.chain(this.caller.pass(arguments, this)); return false; + } + return false; + }, + + send: function(options){ + if (!this.check(options)) return this; + + this.options.isSuccess = this.options.isSuccess || this.isSuccess; + this.running = true; + + var type = typeOf(options); + if (type == 'string' || type == 'element') options = {data: options}; + + var old = this.options; + options = Object.append({data: old.data, url: old.url, method: old.method}, options); + var data = options.data, url = String(options.url), method = options.method.toLowerCase(); + + switch (typeOf(data)){ + case 'element': data = document.id(data).toQueryString(); break; + case 'object': case 'hash': data = Object.toQueryString(data); + } + + if (this.options.format){ + var format = 'format=' + this.options.format; + data = (data) ? format + '&' + data : format; + } + + if (this.options.emulation && !['get', 'post'].contains(method)){ + var _method = '_method=' + method; + data = (data) ? _method + '&' + data : _method; + method = 'post'; + } + + if (this.options.urlEncoded && ['post', 'put'].contains(method)){ + var encoding = (this.options.encoding) ? '; charset=' + this.options.encoding : ''; + this.headers['Content-type'] = 'application/x-www-form-urlencoded' + encoding; + } + + if (!url) url = document.location.pathname; + + var trimPosition = url.lastIndexOf('/'); + if (trimPosition > -1 && (trimPosition = url.indexOf('#')) > -1) url = url.substr(0, trimPosition); + + if (this.options.noCache) + url += (url.indexOf('?') > -1 ? '&' : '?') + String.uniqueID(); + + if (data && (method == 'get' || method == 'delete')){ + url += (url.indexOf('?') > -1 ? '&' : '?') + data; + data = null; + } + + var xhr = this.xhr; + if (progressSupport){ + xhr.onloadstart = this.loadstart.bind(this); + xhr.onprogress = this.progress.bind(this); + } + + xhr.open(method.toUpperCase(), url, this.options.async, this.options.user, this.options.password); + if ((this.options.withCredentials) && 'withCredentials' in xhr) xhr.withCredentials = true; + + xhr.onreadystatechange = this.onStateChange.bind(this); + + Object.each(this.headers, function(value, key){ + try { + xhr.setRequestHeader(key, value); + } catch (e){ + this.fireEvent('exception', [key, value]); + } + }, this); + + this.fireEvent('request'); + xhr.send(data); + if (!this.options.async) this.onStateChange(); + else if (this.options.timeout) this.timer = this.timeout.delay(this.options.timeout, this); + return this; + }, + + cancel: function(){ + if (!this.running) return this; + this.running = false; + var xhr = this.xhr; + xhr.abort(); + if (this.timer){ + clearTimeout(this.timer); + delete this.timer; + } + xhr.onreadystatechange = empty; + if (progressSupport) xhr.onprogress = xhr.onloadstart = empty; + this.xhr = new Browser.Request(); + this.fireEvent('cancel'); + return this; + } + +}); + +var methods = {}; +['get', 'post', 'put', 'delete', 'patch', 'head', 'GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'HEAD'].each(function(method){ + methods[method] = function(data){ + var object = { + method: method + }; + if (data != null) object.data = data; + return this.send(object); + }; +}); + +Request.implement(methods); + +Element.Properties.send = { + + set: function(options){ + var send = this.get('send').cancel(); + send.setOptions(options); + return this; + }, + + get: function(){ + var send = this.retrieve('send'); + if (!send){ + send = new Request({ + data: this, link: 'cancel', method: this.get('method') || 'post', url: this.get('action') + }); + this.store('send', send); + } + return send; + } + +}; + +Element.implement({ + + send: function(url){ + var sender = this.get('send'); + sender.send({data: this, url: url || sender.options.url}); + return this; + } + +}); + +})(); + +/* +--- + +name: Request.HTML + +description: Extends the basic Request Class with additional methods for interacting with HTML responses. + +license: MIT-style license. + +requires: [Element, Request] + +provides: Request.HTML + +... +*/ + +Request.HTML = new Class({ + + Extends: Request, + + options: { + update: false, + append: false, + evalScripts: true, + filter: false, + headers: { + Accept: 'text/html, application/xml, text/xml, */*' + } + }, + + success: function(text){ + var options = this.options, response = this.response; + + response.html = text.stripScripts(function(script){ + response.javascript = script; + }); + + var match = response.html.match(/<body[^>]*>([\s\S]*?)<\/body>/i); + if (match) response.html = match[1]; + var temp = new Element('div').set('html', response.html); + + response.tree = temp.childNodes; + response.elements = temp.getElements(options.filter || '*'); + + if (options.filter) response.tree = response.elements; + if (options.update){ + var update = document.id(options.update).empty(); + if (options.filter) update.adopt(response.elements); + else update.set('html', response.html); + } else if (options.append){ + var append = document.id(options.append); + if (options.filter) response.elements.reverse().inject(append); + else append.adopt(temp.getChildren()); + } + if (options.evalScripts) Browser.exec(response.javascript); + + this.onSuccess(response.tree, response.elements, response.html, response.javascript); + } + +}); + +Element.Properties.load = { + + set: function(options){ + var load = this.get('load').cancel(); + load.setOptions(options); + return this; + }, + + get: function(){ + var load = this.retrieve('load'); + if (!load){ + load = new Request.HTML({data: this, link: 'cancel', update: this, method: 'get'}); + this.store('load', load); + } + return load; + } + +}; + +Element.implement({ + + load: function(){ + this.get('load').send(Array.link(arguments, {data: Type.isObject, url: Type.isString})); + return this; + } + +}); + +/* +--- + +name: JSON + +description: JSON encoder and decoder. + +license: MIT-style license. + +SeeAlso: <http://www.json.org/> + +requires: [Array, String, Number, Function] + +provides: JSON + +... +*/ + +if (typeof JSON == 'undefined') this.JSON = {}; + + + +(function(){ + +var special = {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\'}; + +var escape = function(chr){ + return special[chr] || '\\u' + ('0000' + chr.charCodeAt(0).toString(16)).slice(-4); +}; + +JSON.validate = function(string){ + string = string.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@'). + replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']'). + replace(/(?:^|:|,)(?:\s*\[)+/g, ''); + + return (/^[\],:{}\s]*$/).test(string); +}; + +JSON.encode = JSON.stringify ? function(obj){ + return JSON.stringify(obj); +} : function(obj){ + if (obj && obj.toJSON) obj = obj.toJSON(); + + switch (typeOf(obj)){ + case 'string': + return '"' + obj.replace(/[\x00-\x1f\\"]/g, escape) + '"'; + case 'array': + return '[' + obj.map(JSON.encode).clean() + ']'; + case 'object': case 'hash': + var string = []; + Object.each(obj, function(value, key){ + var json = JSON.encode(value); + if (json) string.push(JSON.encode(key) + ':' + json); + }); + return '{' + string + '}'; + case 'number': case 'boolean': return '' + obj; + case 'null': return 'null'; + } + + return null; +}; + +JSON.secure = true; + + +JSON.decode = function(string, secure){ + if (!string || typeOf(string) != 'string') return null; + + if (secure == null) secure = JSON.secure; + if (secure){ + if (JSON.parse) return JSON.parse(string); + if (!JSON.validate(string)) throw new Error('JSON could not decode the input; security is enabled and the value is not secure.'); + } + + return eval('(' + string + ')'); +}; + +})(); + +/* +--- + +name: Request.JSON + +description: Extends the basic Request Class with additional methods for sending and receiving JSON data. + +license: MIT-style license. + +requires: [Request, JSON] + +provides: Request.JSON + +... +*/ + +Request.JSON = new Class({ + + Extends: Request, + + options: { + /*onError: function(text, error){},*/ + secure: true + }, + + initialize: function(options){ + this.parent(options); + Object.append(this.headers, { + 'Accept': 'application/json', + 'X-Request': 'JSON' + }); + }, + + success: function(text){ + var json; + try { + json = this.response.json = JSON.decode(text, this.options.secure); + } catch (error){ + this.fireEvent('error', [text, error]); + return; + } + if (json == null) this.onFailure(); + else this.onSuccess(json, text); + } + +}); + +/* +--- + +name: Cookie + +description: Class for creating, reading, and deleting browser Cookies. + +license: MIT-style license. + +credits: + - Based on the functions by Peter-Paul Koch (http://quirksmode.org). + +requires: [Options, Browser] + +provides: Cookie + +... +*/ + +var Cookie = new Class({ + + Implements: Options, + + options: { + path: '/', + domain: false, + duration: false, + secure: false, + document: document, + encode: true + }, + + initialize: function(key, options){ + this.key = key; + this.setOptions(options); + }, + + write: function(value){ + if (this.options.encode) value = encodeURIComponent(value); + if (this.options.domain) value += '; domain=' + this.options.domain; + if (this.options.path) value += '; path=' + this.options.path; + if (this.options.duration){ + var date = new Date(); + date.setTime(date.getTime() + this.options.duration * 24 * 60 * 60 * 1000); + value += '; expires=' + date.toGMTString(); + } + if (this.options.secure) value += '; secure'; + this.options.document.cookie = this.key + '=' + value; + return this; + }, + + read: function(){ + var value = this.options.document.cookie.match('(?:^|;)\\s*' + this.key.escapeRegExp() + '=([^;]*)'); + return (value) ? decodeURIComponent(value[1]) : null; + }, + + dispose: function(){ + new Cookie(this.key, Object.merge({}, this.options, {duration: -1})).write(''); + return this; + } + +}); + +Cookie.write = function(key, value, options){ + return new Cookie(key, options).write(value); +}; + +Cookie.read = function(key){ + return new Cookie(key).read(); +}; + +Cookie.dispose = function(key, options){ + return new Cookie(key, options).dispose(); +}; + +/* +--- + +name: DOMReady + +description: Contains the custom event domready. + +license: MIT-style license. + +requires: [Browser, Element, Element.Event] + +provides: [DOMReady, DomReady] + +... +*/ + +(function(window, document){ + +var ready, + loaded, + checks = [], + shouldPoll, + timer, + testElement = document.createElement('div'); + +var domready = function(){ + clearTimeout(timer); + if (!ready) { + Browser.loaded = ready = true; + document.removeListener('DOMContentLoaded', domready).removeListener('readystatechange', check); + document.fireEvent('domready'); + window.fireEvent('domready'); + } + // cleanup scope vars + document = window = testElement = null; +}; + +var check = function(){ + for (var i = checks.length; i--;) if (checks[i]()){ + domready(); + return true; + } + return false; +}; + +var poll = function(){ + clearTimeout(timer); + if (!check()) timer = setTimeout(poll, 10); +}; + +document.addListener('DOMContentLoaded', domready); + +/*<ltIE8>*/ +// doScroll technique by Diego Perini http://javascript.nwbox.com/IEContentLoaded/ +// testElement.doScroll() throws when the DOM is not ready, only in the top window +var doScrollWorks = function(){ + try { + testElement.doScroll(); + return true; + } catch (e){} + return false; +}; +// If doScroll works already, it can't be used to determine domready +// e.g. in an iframe +if (testElement.doScroll && !doScrollWorks()){ + checks.push(doScrollWorks); + shouldPoll = true; +} +/*</ltIE8>*/ + +if (document.readyState) checks.push(function(){ + var state = document.readyState; + return (state == 'loaded' || state == 'complete'); +}); + +if ('onreadystatechange' in document) document.addListener('readystatechange', check); +else shouldPoll = true; + +if (shouldPoll) poll(); + +Element.Events.domready = { + onAdd: function(fn){ + if (ready) fn.call(this); + } +}; + +// Make sure that domready fires before load +Element.Events.load = { + base: 'load', + onAdd: function(fn){ + if (loaded && this == window) fn.call(this); + }, + condition: function(){ + if (this == window){ + domready(); + delete Element.Events.load; + } + return true; + } +}; + +// This is based on the custom load event +window.addEvent('load', function(){ + loaded = true; +}); + +})(window, document); diff --git a/pyload/webui/themes/Next/lib/MooTools/MooTools-More.js b/pyload/webui/themes/Next/lib/MooTools/MooTools-More.js new file mode 100644 index 000000000..15a277029 --- /dev/null +++ b/pyload/webui/themes/Next/lib/MooTools/MooTools-More.js @@ -0,0 +1,14345 @@ +/* MooTools: the javascript framework. license: MIT-style license. copyright: Copyright (c) 2006-2015 [Valerio Proietti](http://mad4milk.net/).*/ +/* +Web Build: http://mootools.net/more/builder/a3048f4bfdf603b22a69c141dbd0fca9 +*/ +/* +--- + +script: More.js + +name: More + +description: MooTools More + +license: MIT-style license + +authors: + - Guillermo Rauch + - Thomas Aylott + - Scott Kyle + - Arian Stolwijk + - Tim Wienk + - Christoph Pojer + - Aaron Newton + - Jacob Thornton + +requires: + - Core/MooTools + +provides: [MooTools.More] + +... +*/ + +MooTools.More = { + version: '1.5.1', + build: '2dd695ba957196ae4b0275a690765d6636a61ccd' +}; + +/* +--- + +script: Chain.Wait.js + +name: Chain.Wait + +description: value, Adds a method to inject pauses between chained events. + +license: MIT-style license. + +authors: + - Aaron Newton + +requires: + - Core/Chain + - Core/Element + - Core/Fx + - MooTools.More + +provides: [Chain.Wait] + +... +*/ + +(function(){ + + var wait = { + wait: function(duration){ + return this.chain(function(){ + this.callChain.delay(duration == null ? 500 : duration, this); + return this; + }.bind(this)); + } + }; + + Chain.implement(wait); + + if (this.Fx) Fx.implement(wait); + + if (this.Element && Element.implement && this.Fx){ + Element.implement({ + + chains: function(effects){ + Array.from(effects || ['tween', 'morph', 'reveal']).each(function(effect){ + effect = this.get(effect); + if (!effect) return; + effect.setOptions({ + link:'chain' + }); + }, this); + return this; + }, + + pauseFx: function(duration, effect){ + this.chains(effect).get(effect || 'tween').wait(duration); + return this; + } + + }); + } + +})(); + +/* +--- + +script: Class.Binds.js + +name: Class.Binds + +description: Automagically binds specified methods in a class to the instance of the class. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Class + - MooTools.More + +provides: [Class.Binds] + +... +*/ + +Class.Mutators.Binds = function(binds){ + if (!this.prototype.initialize) this.implement('initialize', function(){}); + return Array.from(binds).concat(this.prototype.Binds || []); +}; + +Class.Mutators.initialize = function(initialize){ + return function(){ + Array.from(this.Binds).each(function(name){ + var original = this[name]; + if (original) this[name] = original.bind(this); + }, this); + return initialize.apply(this, arguments); + }; +}; + +/* +--- + +script: Class.Occlude.js + +name: Class.Occlude + +description: Prevents a class from being applied to a DOM element twice. + +license: MIT-style license. + +authors: + - Aaron Newton + +requires: + - Core/Class + - Core/Element + - MooTools.More + +provides: [Class.Occlude] + +... +*/ + +Class.Occlude = new Class({ + + occlude: function(property, element){ + element = document.id(element || this.element); + var instance = element.retrieve(property || this.property); + if (instance && !this.occluded) + return (this.occluded = instance); + + this.occluded = false; + element.store(property || this.property, this); + return this.occluded; + } + +}); + +/* +--- + +script: Class.Refactor.js + +name: Class.Refactor + +description: Extends a class onto itself with new property, preserving any items attached to the class's namespace. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Class + - MooTools.More + +# Some modules declare themselves dependent on Class.Refactor +provides: [Class.refactor, Class.Refactor] + +... +*/ + +Class.refactor = function(original, refactors){ + + Object.each(refactors, function(item, name){ + var origin = original.prototype[name]; + origin = (origin && origin.$origin) || origin || function(){}; + original.implement(name, (typeof item == 'function') ? function(){ + var old = this.previous; + this.previous = origin; + var value = item.apply(this, arguments); + this.previous = old; + return value; + } : item); + }); + + return original; + +}; + +/* +--- + +name: Events.Pseudos + +description: Adds the functionality to add pseudo events + +license: MIT-style license + +authors: + - Arian Stolwijk + +requires: [Core/Class.Extras, Core/Slick.Parser, MooTools.More] + +provides: [Events.Pseudos] + +... +*/ + +(function(){ + +Events.Pseudos = function(pseudos, addEvent, removeEvent){ + + var storeKey = '_monitorEvents:'; + + var storageOf = function(object){ + return { + store: object.store ? function(key, value){ + object.store(storeKey + key, value); + } : function(key, value){ + (object._monitorEvents || (object._monitorEvents = {}))[key] = value; + }, + retrieve: object.retrieve ? function(key, dflt){ + return object.retrieve(storeKey + key, dflt); + } : function(key, dflt){ + if (!object._monitorEvents) return dflt; + return object._monitorEvents[key] || dflt; + } + }; + }; + + var splitType = function(type){ + if (type.indexOf(':') == -1 || !pseudos) return null; + + var parsed = Slick.parse(type).expressions[0][0], + parsedPseudos = parsed.pseudos, + l = parsedPseudos.length, + splits = []; + + while (l--){ + var pseudo = parsedPseudos[l].key, + listener = pseudos[pseudo]; + if (listener != null) splits.push({ + event: parsed.tag, + value: parsedPseudos[l].value, + pseudo: pseudo, + original: type, + listener: listener + }); + } + return splits.length ? splits : null; + }; + + return { + + addEvent: function(type, fn, internal){ + var split = splitType(type); + if (!split) return addEvent.call(this, type, fn, internal); + + var storage = storageOf(this), + events = storage.retrieve(type, []), + eventType = split[0].event, + args = Array.slice(arguments, 2), + stack = fn, + self = this; + + split.each(function(item){ + var listener = item.listener, + stackFn = stack; + if (listener == false) eventType += ':' + item.pseudo + '(' + item.value + ')'; + else stack = function(){ + listener.call(self, item, stackFn, arguments, stack); + }; + }); + + events.include({type: eventType, event: fn, monitor: stack}); + storage.store(type, events); + + if (type != eventType) addEvent.apply(this, [type, fn].concat(args)); + return addEvent.apply(this, [eventType, stack].concat(args)); + }, + + removeEvent: function(type, fn){ + var split = splitType(type); + if (!split) return removeEvent.call(this, type, fn); + + var storage = storageOf(this), + events = storage.retrieve(type); + if (!events) return this; + + var args = Array.slice(arguments, 2); + + removeEvent.apply(this, [type, fn].concat(args)); + events.each(function(monitor, i){ + if (!fn || monitor.event == fn) removeEvent.apply(this, [monitor.type, monitor.monitor].concat(args)); + delete events[i]; + }, this); + + storage.store(type, events); + return this; + } + + }; + +}; + +var pseudos = { + + once: function(split, fn, args, monitor){ + fn.apply(this, args); + this.removeEvent(split.event, monitor) + .removeEvent(split.original, fn); + }, + + throttle: function(split, fn, args){ + if (!fn._throttled){ + fn.apply(this, args); + fn._throttled = setTimeout(function(){ + fn._throttled = false; + }, split.value || 250); + } + }, + + pause: function(split, fn, args){ + clearTimeout(fn._pause); + fn._pause = fn.delay(split.value || 250, this, args); + } + +}; + +Events.definePseudo = function(key, listener){ + pseudos[key] = listener; + return this; +}; + +Events.lookupPseudo = function(key){ + return pseudos[key]; +}; + +var proto = Events.prototype; +Events.implement(Events.Pseudos(pseudos, proto.addEvent, proto.removeEvent)); + +['Request', 'Fx'].each(function(klass){ + if (this[klass]) this[klass].implement(Events.prototype); +}); + +})(); + +/* +--- + +script: Drag.js + +name: Drag + +description: The base Drag Class. Can be used to drag and resize Elements using mouse events. + +license: MIT-style license + +authors: + - Valerio Proietti + - Tom Occhinno + - Jan Kassens + +requires: + - Core/Events + - Core/Options + - Core/Element.Event + - Core/Element.Style + - Core/Element.Dimensions + - MooTools.More + +provides: [Drag] +... + +*/ + +var Drag = new Class({ + + Implements: [Events, Options], + + options: {/* + onBeforeStart: function(thisElement){}, + onStart: function(thisElement, event){}, + onSnap: function(thisElement){}, + onDrag: function(thisElement, event){}, + onCancel: function(thisElement){}, + onComplete: function(thisElement, event){},*/ + snap: 6, + unit: 'px', + grid: false, + style: true, + limit: false, + handle: false, + invert: false, + preventDefault: false, + stopPropagation: false, + compensateScroll: false, + modifiers: {x: 'left', y: 'top'} + }, + + initialize: function(){ + var params = Array.link(arguments, { + 'options': Type.isObject, + 'element': function(obj){ + return obj != null; + } + }); + + this.element = document.id(params.element); + this.document = this.element.getDocument(); + this.setOptions(params.options || {}); + var htype = typeOf(this.options.handle); + this.handles = ((htype == 'array' || htype == 'collection') ? $$(this.options.handle) : document.id(this.options.handle)) || this.element; + this.mouse = {'now': {}, 'pos': {}}; + this.value = {'start': {}, 'now': {}}; + this.offsetParent = (function(el){ + var offsetParent = el.getOffsetParent(); + var isBody = !offsetParent || (/^(?:body|html)$/i).test(offsetParent.tagName); + return isBody ? window : document.id(offsetParent); + })(this.element); + this.selection = 'selectstart' in document ? 'selectstart' : 'mousedown'; + + this.compensateScroll = {start: {}, diff: {}, last: {}}; + + if ('ondragstart' in document && !('FileReader' in window) && !Drag.ondragstartFixed){ + document.ondragstart = Function.from(false); + Drag.ondragstartFixed = true; + } + + this.bound = { + start: this.start.bind(this), + check: this.check.bind(this), + drag: this.drag.bind(this), + stop: this.stop.bind(this), + cancel: this.cancel.bind(this), + eventStop: Function.from(false), + scrollListener: this.scrollListener.bind(this) + }; + this.attach(); + }, + + attach: function(){ + this.handles.addEvent('mousedown', this.bound.start); + if (this.options.compensateScroll) this.offsetParent.addEvent('scroll', this.bound.scrollListener); + return this; + }, + + detach: function(){ + this.handles.removeEvent('mousedown', this.bound.start); + if (this.options.compensateScroll) this.offsetParent.removeEvent('scroll', this.bound.scrollListener); + return this; + }, + + scrollListener: function(){ + + if (!this.mouse.start) return; + var newScrollValue = this.offsetParent.getScroll(); + + if (this.element.getStyle('position') == 'absolute'){ + var scrollDiff = this.sumValues(newScrollValue, this.compensateScroll.last, -1); + this.mouse.now = this.sumValues(this.mouse.now, scrollDiff, 1); + } else { + this.compensateScroll.diff = this.sumValues(newScrollValue, this.compensateScroll.start, -1); + } + if (this.offsetParent != window) this.compensateScroll.diff = this.sumValues(this.compensateScroll.start, newScrollValue, -1); + this.compensateScroll.last = newScrollValue; + this.render(this.options); + }, + + sumValues: function(alpha, beta, op){ + var sum = {}, options = this.options; + for (z in options.modifiers){ + if (!options.modifiers[z]) continue; + sum[z] = alpha[z] + beta[z] * op; + } + return sum; + }, + + start: function(event){ + var options = this.options; + + if (event.rightClick) return; + + if (options.preventDefault) event.preventDefault(); + if (options.stopPropagation) event.stopPropagation(); + this.compensateScroll.start = this.compensateScroll.last = this.offsetParent.getScroll(); + this.compensateScroll.diff = {x: 0, y: 0}; + this.mouse.start = event.page; + this.fireEvent('beforeStart', this.element); + + var limit = options.limit; + this.limit = {x: [], y: []}; + + var z, coordinates, offsetParent = this.offsetParent == window ? null : this.offsetParent; + for (z in options.modifiers){ + if (!options.modifiers[z]) continue; + + var style = this.element.getStyle(options.modifiers[z]); + + // Some browsers (IE and Opera) don't always return pixels. + if (style && !style.match(/px$/)){ + if (!coordinates) coordinates = this.element.getCoordinates(offsetParent); + style = coordinates[options.modifiers[z]]; + } + + if (options.style) this.value.now[z] = (style || 0).toInt(); + else this.value.now[z] = this.element[options.modifiers[z]]; + + if (options.invert) this.value.now[z] *= -1; + + this.mouse.pos[z] = event.page[z] - this.value.now[z]; + + if (limit && limit[z]){ + var i = 2; + while (i--){ + var limitZI = limit[z][i]; + if (limitZI || limitZI === 0) this.limit[z][i] = (typeof limitZI == 'function') ? limitZI() : limitZI; + } + } + } + + if (typeOf(this.options.grid) == 'number') this.options.grid = { + x: this.options.grid, + y: this.options.grid + }; + + var events = { + mousemove: this.bound.check, + mouseup: this.bound.cancel + }; + events[this.selection] = this.bound.eventStop; + this.document.addEvents(events); + }, + + check: function(event){ + if (this.options.preventDefault) event.preventDefault(); + var distance = Math.round(Math.sqrt(Math.pow(event.page.x - this.mouse.start.x, 2) + Math.pow(event.page.y - this.mouse.start.y, 2))); + if (distance > this.options.snap){ + this.cancel(); + this.document.addEvents({ + mousemove: this.bound.drag, + mouseup: this.bound.stop + }); + this.fireEvent('start', [this.element, event]).fireEvent('snap', this.element); + } + }, + + drag: function(event){ + var options = this.options; + if (options.preventDefault) event.preventDefault(); + this.mouse.now = this.sumValues(event.page, this.compensateScroll.diff, -1); + + this.render(options); + this.fireEvent('drag', [this.element, event]); + }, + + render: function(options){ + for (var z in options.modifiers){ + if (!options.modifiers[z]) continue; + this.value.now[z] = this.mouse.now[z] - this.mouse.pos[z]; + + if (options.invert) this.value.now[z] *= -1; + if (options.limit && this.limit[z]){ + if ((this.limit[z][1] || this.limit[z][1] === 0) && (this.value.now[z] > this.limit[z][1])){ + this.value.now[z] = this.limit[z][1]; + } else if ((this.limit[z][0] || this.limit[z][0] === 0) && (this.value.now[z] < this.limit[z][0])){ + this.value.now[z] = this.limit[z][0]; + } + } + if (options.grid[z]) this.value.now[z] -= ((this.value.now[z] - (this.limit[z][0]||0)) % options.grid[z]); + if (options.style) this.element.setStyle(options.modifiers[z], this.value.now[z] + options.unit); + else this.element[options.modifiers[z]] = this.value.now[z]; + } + }, + + cancel: function(event){ + this.document.removeEvents({ + mousemove: this.bound.check, + mouseup: this.bound.cancel + }); + if (event){ + this.document.removeEvent(this.selection, this.bound.eventStop); + this.fireEvent('cancel', this.element); + } + }, + + stop: function(event){ + var events = { + mousemove: this.bound.drag, + mouseup: this.bound.stop + }; + events[this.selection] = this.bound.eventStop; + this.document.removeEvents(events); + this.mouse.start = null; + if (event) this.fireEvent('complete', [this.element, event]); + } + +}); + +Element.implement({ + + makeResizable: function(options){ + var drag = new Drag(this, Object.merge({ + modifiers: { + x: 'width', + y: 'height' + } + }, options)); + + this.store('resizer', drag); + return drag.addEvent('drag', function(){ + this.fireEvent('resize', drag); + }.bind(this)); + } + +}); + +/* +--- + +script: Drag.Move.js + +name: Drag.Move + +description: A Drag extension that provides support for the constraining of draggables to containers and droppables. + +license: MIT-style license + +authors: + - Valerio Proietti + - Tom Occhinno + - Jan Kassens + - Aaron Newton + - Scott Kyle + +requires: + - Core/Element.Dimensions + - Drag + +provides: [Drag.Move] + +... +*/ + +Drag.Move = new Class({ + + Extends: Drag, + + options: {/* + onEnter: function(thisElement, overed){}, + onLeave: function(thisElement, overed){}, + onDrop: function(thisElement, overed, event){},*/ + droppables: [], + container: false, + precalculate: false, + includeMargins: true, + checkDroppables: true + }, + + initialize: function(element, options){ + this.parent(element, options); + element = this.element; + + this.droppables = $$(this.options.droppables); + this.setContainer(this.options.container); + + if (this.options.style){ + if (this.options.modifiers.x == 'left' && this.options.modifiers.y == 'top'){ + var parent = element.getOffsetParent(), + styles = element.getStyles('left', 'top'); + if (parent && (styles.left == 'auto' || styles.top == 'auto')){ + element.setPosition(element.getPosition(parent)); + } + } + + if (element.getStyle('position') == 'static') element.setStyle('position', 'absolute'); + } + + this.addEvent('start', this.checkDroppables, true); + this.overed = null; + }, + + setContainer: function(container) { + this.container = document.id(container); + if (this.container && typeOf(this.container) != 'element'){ + this.container = document.id(this.container.getDocument().body); + } + }, + + start: function(event){ + if (this.container) this.options.limit = this.calculateLimit(); + + if (this.options.precalculate){ + this.positions = this.droppables.map(function(el){ + return el.getCoordinates(); + }); + } + + this.parent(event); + }, + + calculateLimit: function(){ + var element = this.element, + container = this.container, + + offsetParent = document.id(element.getOffsetParent()) || document.body, + containerCoordinates = container.getCoordinates(offsetParent), + elementMargin = {}, + elementBorder = {}, + containerMargin = {}, + containerBorder = {}, + offsetParentPadding = {}, + offsetScroll = offsetParent.getScroll(); + + ['top', 'right', 'bottom', 'left'].each(function(pad){ + elementMargin[pad] = element.getStyle('margin-' + pad).toInt(); + elementBorder[pad] = element.getStyle('border-' + pad).toInt(); + containerMargin[pad] = container.getStyle('margin-' + pad).toInt(); + containerBorder[pad] = container.getStyle('border-' + pad).toInt(); + offsetParentPadding[pad] = offsetParent.getStyle('padding-' + pad).toInt(); + }, this); + + var width = element.offsetWidth + elementMargin.left + elementMargin.right, + height = element.offsetHeight + elementMargin.top + elementMargin.bottom, + left = 0 + offsetScroll.x, + top = 0 + offsetScroll.y, + right = containerCoordinates.right - containerBorder.right - width + offsetScroll.x, + bottom = containerCoordinates.bottom - containerBorder.bottom - height + offsetScroll.y; + + if (this.options.includeMargins){ + left += elementMargin.left; + top += elementMargin.top; + } else { + right += elementMargin.right; + bottom += elementMargin.bottom; + } + + if (element.getStyle('position') == 'relative'){ + var coords = element.getCoordinates(offsetParent); + coords.left -= element.getStyle('left').toInt(); + coords.top -= element.getStyle('top').toInt(); + + left -= coords.left; + top -= coords.top; + if (container.getStyle('position') != 'relative'){ + left += containerBorder.left; + top += containerBorder.top; + } + right += elementMargin.left - coords.left; + bottom += elementMargin.top - coords.top; + + if (container != offsetParent){ + left += containerMargin.left + offsetParentPadding.left; + if (!offsetParentPadding.left && left < 0) left = 0; + top += offsetParent == document.body ? 0 : containerMargin.top + offsetParentPadding.top; + if (!offsetParentPadding.top && top < 0) top = 0; + } + } else { + left -= elementMargin.left; + top -= elementMargin.top; + if (container != offsetParent){ + left += containerCoordinates.left + containerBorder.left; + top += containerCoordinates.top + containerBorder.top; + } + } + + return { + x: [left, right], + y: [top, bottom] + }; + }, + + getDroppableCoordinates: function(element){ + var position = element.getCoordinates(); + if (element.getStyle('position') == 'fixed'){ + var scroll = window.getScroll(); + position.left += scroll.x; + position.right += scroll.x; + position.top += scroll.y; + position.bottom += scroll.y; + } + return position; + }, + + checkDroppables: function(){ + var overed = this.droppables.filter(function(el, i){ + el = this.positions ? this.positions[i] : this.getDroppableCoordinates(el); + var now = this.mouse.now; + return (now.x > el.left && now.x < el.right && now.y < el.bottom && now.y > el.top); + }, this).getLast(); + + if (this.overed != overed){ + if (this.overed) this.fireEvent('leave', [this.element, this.overed]); + if (overed) this.fireEvent('enter', [this.element, overed]); + this.overed = overed; + } + }, + + drag: function(event){ + this.parent(event); + if (this.options.checkDroppables && this.droppables.length) this.checkDroppables(); + }, + + stop: function(event){ + this.checkDroppables(); + this.fireEvent('drop', [this.element, this.overed, event]); + this.overed = null; + return this.parent(event); + } + +}); + +Element.implement({ + + makeDraggable: function(options){ + var drag = new Drag.Move(this, options); + this.store('dragger', drag); + return drag; + } + +}); + +/* +--- + +script: Element.Measure.js + +name: Element.Measure + +description: Extends the Element native object to include methods useful in measuring dimensions. + +credits: "Element.measure / .expose methods by Daniel Steigerwald License: MIT-style license. Copyright: Copyright (c) 2008 Daniel Steigerwald, daniel.steigerwald.cz" + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Element.Style + - Core/Element.Dimensions + - MooTools.More + +provides: [Element.Measure] + +... +*/ + +(function(){ + +var getStylesList = function(styles, planes){ + var list = []; + Object.each(planes, function(directions){ + Object.each(directions, function(edge){ + styles.each(function(style){ + list.push(style + '-' + edge + (style == 'border' ? '-width' : '')); + }); + }); + }); + return list; +}; + +var calculateEdgeSize = function(edge, styles){ + var total = 0; + Object.each(styles, function(value, style){ + if (style.test(edge)) total = total + value.toInt(); + }); + return total; +}; + +var isVisible = function(el){ + return !!(!el || el.offsetHeight || el.offsetWidth); +}; + + +Element.implement({ + + measure: function(fn){ + if (isVisible(this)) return fn.call(this); + var parent = this.getParent(), + toMeasure = []; + while (!isVisible(parent) && parent != document.body){ + toMeasure.push(parent.expose()); + parent = parent.getParent(); + } + var restore = this.expose(), + result = fn.call(this); + restore(); + toMeasure.each(function(restore){ + restore(); + }); + return result; + }, + + expose: function(){ + if (this.getStyle('display') != 'none') return function(){}; + var before = this.style.cssText; + this.setStyles({ + display: 'block', + position: 'absolute', + visibility: 'hidden' + }); + return function(){ + this.style.cssText = before; + }.bind(this); + }, + + getDimensions: function(options){ + options = Object.merge({computeSize: false}, options); + var dim = {x: 0, y: 0}; + + var getSize = function(el, options){ + return (options.computeSize) ? el.getComputedSize(options) : el.getSize(); + }; + + var parent = this.getParent('body'); + + if (parent && this.getStyle('display') == 'none'){ + dim = this.measure(function(){ + return getSize(this, options); + }); + } else if (parent){ + try { //safari sometimes crashes here, so catch it + dim = getSize(this, options); + }catch(e){} + } + + return Object.append(dim, (dim.x || dim.x === 0) ? { + width: dim.x, + height: dim.y + } : { + x: dim.width, + y: dim.height + } + ); + }, + + getComputedSize: function(options){ + + + options = Object.merge({ + styles: ['padding','border'], + planes: { + height: ['top','bottom'], + width: ['left','right'] + }, + mode: 'both' + }, options); + + var styles = {}, + size = {width: 0, height: 0}, + dimensions; + + if (options.mode == 'vertical'){ + delete size.width; + delete options.planes.width; + } else if (options.mode == 'horizontal'){ + delete size.height; + delete options.planes.height; + } + + getStylesList(options.styles, options.planes).each(function(style){ + styles[style] = this.getStyle(style).toInt(); + }, this); + + Object.each(options.planes, function(edges, plane){ + + var capitalized = plane.capitalize(), + style = this.getStyle(plane); + + if (style == 'auto' && !dimensions) dimensions = this.getDimensions(); + + style = styles[plane] = (style == 'auto') ? dimensions[plane] : style.toInt(); + size['total' + capitalized] = style; + + edges.each(function(edge){ + var edgesize = calculateEdgeSize(edge, styles); + size['computed' + edge.capitalize()] = edgesize; + size['total' + capitalized] += edgesize; + }); + + }, this); + + return Object.append(size, styles); + } + +}); + +})(); + +/* +--- + +script: Slider.js + +name: Slider + +description: Class for creating horizontal and vertical slider controls. + +license: MIT-style license + +authors: + - Valerio Proietti + +requires: + - Core/Element.Dimensions + - Core/Number + - Class.Binds + - Drag + - Element.Measure + +provides: [Slider] + +... +*/ + +var Slider = new Class({ + + Implements: [Events, Options], + + Binds: ['clickedElement', 'draggedKnob', 'scrolledElement'], + + options: {/* + onTick: function(intPosition){}, + onMove: function(){}, + onChange: function(intStep){}, + onComplete: function(strStep){},*/ + onTick: function(position){ + this.setKnobPosition(position); + }, + initialStep: 0, + snap: false, + offset: 0, + range: false, + wheel: false, + steps: 100, + mode: 'horizontal' + }, + + initialize: function(element, knob, options){ + this.setOptions(options); + options = this.options; + this.element = document.id(element); + knob = this.knob = document.id(knob); + this.previousChange = this.previousEnd = this.step = options.initialStep ? options.initialStep : options.range ? options.range[0] : 0; + + var limit = {}, + modifiers = {x: false, y: false}; + + switch (options.mode){ + case 'vertical': + this.axis = 'y'; + this.property = 'top'; + this.offset = 'offsetHeight'; + break; + case 'horizontal': + this.axis = 'x'; + this.property = 'left'; + this.offset = 'offsetWidth'; + } + + this.setSliderDimensions(); + this.setRange(options.range, null, true); + + if (knob.getStyle('position') == 'static') knob.setStyle('position', 'relative'); + knob.setStyle(this.property, -options.offset); + modifiers[this.axis] = this.property; + limit[this.axis] = [-options.offset, this.full - options.offset]; + + var dragOptions = { + snap: 0, + limit: limit, + modifiers: modifiers, + onDrag: this.draggedKnob, + onStart: this.draggedKnob, + onBeforeStart: (function(){ + this.isDragging = true; + }).bind(this), + onCancel: function(){ + this.isDragging = false; + }.bind(this), + onComplete: function(){ + this.isDragging = false; + this.draggedKnob(); + this.end(); + }.bind(this) + }; + if (options.snap) this.setSnap(dragOptions); + + this.drag = new Drag(knob, dragOptions); + if (options.initialStep != null) this.set(options.initialStep, true); + this.attach(); + }, + + attach: function(){ + this.element.addEvent('mousedown', this.clickedElement); + if (this.options.wheel) this.element.addEvent('mousewheel', this.scrolledElement); + this.drag.attach(); + return this; + }, + + detach: function(){ + this.element.removeEvent('mousedown', this.clickedElement) + .removeEvent('mousewheel', this.scrolledElement); + this.drag.detach(); + return this; + }, + + autosize: function(){ + this.setSliderDimensions() + .setKnobPosition(this.toPosition(this.step)); + this.drag.options.limit[this.axis] = [-this.options.offset, this.full - this.options.offset]; + if (this.options.snap) this.setSnap(); + return this; + }, + + setSnap: function(options){ + if (!options) options = this.drag.options; + options.grid = Math.ceil(this.stepWidth); + options.limit[this.axis][1] = this.element[this.offset]; + return this; + }, + + setKnobPosition: function(position){ + if (this.options.snap) position = this.toPosition(this.step); + this.knob.setStyle(this.property, position); + return this; + }, + + setSliderDimensions: function(){ + this.full = this.element.measure(function(){ + this.half = this.knob[this.offset] / 2; + return this.element[this.offset] - this.knob[this.offset] + (this.options.offset * 2); + }.bind(this)); + return this; + }, + + set: function(step, silently){ + if (!((this.range > 0) ^ (step < this.min))) step = this.min; + if (!((this.range > 0) ^ (step > this.max))) step = this.max; + + this.step = (step).round(this.modulus.decimalLength); + if (silently) this.checkStep().setKnobPosition(this.toPosition(this.step)); + else this.checkStep().fireEvent('tick', this.toPosition(this.step)).fireEvent('move').end(); + return this; + }, + + setRange: function(range, pos, silently){ + this.min = Array.pick([range[0], 0]); + this.max = Array.pick([range[1], this.options.steps]); + this.range = this.max - this.min; + this.steps = this.options.steps || this.full; + var stepSize = this.stepSize = Math.abs(this.range) / this.steps; + this.stepWidth = this.stepSize * this.full / Math.abs(this.range); + this.setModulus(); + + if (range) this.set(Array.pick([pos, this.step]).limit(this.min,this.max), silently); + return this; + }, + + setModulus: function(){ + var decimals = ((this.stepSize + '').split('.')[1] || []).length, + modulus = 1 + ''; + while (decimals--) modulus += '0'; + this.modulus = {multiplier: (modulus).toInt(10), decimalLength: modulus.length - 1}; + }, + + clickedElement: function(event){ + if (this.isDragging || event.target == this.knob) return; + + var dir = this.range < 0 ? -1 : 1, + position = event.page[this.axis] - this.element.getPosition()[this.axis] - this.half; + + position = position.limit(-this.options.offset, this.full - this.options.offset); + + this.step = (this.min + dir * this.toStep(position)).round(this.modulus.decimalLength); + + this.checkStep() + .fireEvent('tick', position) + .fireEvent('move') + .end(); + }, + + scrolledElement: function(event){ + var mode = (this.options.mode == 'horizontal') ? (event.wheel < 0) : (event.wheel > 0); + this.set(this.step + (mode ? -1 : 1) * this.stepSize); + event.stop(); + }, + + draggedKnob: function(){ + var dir = this.range < 0 ? -1 : 1, + position = this.drag.value.now[this.axis]; + + position = position.limit(-this.options.offset, this.full -this.options.offset); + + this.step = (this.min + dir * this.toStep(position)).round(this.modulus.decimalLength); + this.checkStep(); + this.fireEvent('move'); + }, + + checkStep: function(){ + var step = this.step; + if (this.previousChange != step){ + this.previousChange = step; + this.fireEvent('change', step); + } + return this; + }, + + end: function(){ + var step = this.step; + if (this.previousEnd !== step){ + this.previousEnd = step; + this.fireEvent('complete', step + ''); + } + return this; + }, + + toStep: function(position){ + var step = (position + this.options.offset) * this.stepSize / this.full * this.steps; + return this.options.steps ? (step - (step * this.modulus.multiplier) % (this.stepSize * this.modulus.multiplier) / this.modulus.multiplier).round(this.modulus.decimalLength) : step; + }, + + toPosition: function(step){ + return (this.full * Math.abs(this.min - step)) / (this.steps * this.stepSize) - this.options.offset || 0; + } + +}); + +/* +--- + +script: Sortables.js + +name: Sortables + +description: Class for creating a drag and drop sorting interface for lists of items. + +license: MIT-style license + +authors: + - Tom Occhino + +requires: + - Core/Fx.Morph + - Drag.Move + +provides: [Sortables] + +... +*/ + +var Sortables = new Class({ + + Implements: [Events, Options], + + options: {/* + onSort: function(element, clone){}, + onStart: function(element, clone){}, + onComplete: function(element){},*/ + opacity: 1, + clone: false, + revert: false, + handle: false, + dragOptions: {}, + unDraggableTags: ['button', 'input', 'a', 'textarea', 'select', 'option'] + }, + + initialize: function(lists, options){ + this.setOptions(options); + + this.elements = []; + this.lists = []; + this.idle = true; + + this.addLists($$(document.id(lists) || lists)); + + if (!this.options.clone) this.options.revert = false; + if (this.options.revert) this.effect = new Fx.Morph(null, Object.merge({ + duration: 250, + link: 'cancel' + }, this.options.revert)); + }, + + attach: function(){ + this.addLists(this.lists); + return this; + }, + + detach: function(){ + this.lists = this.removeLists(this.lists); + return this; + }, + + addItems: function(){ + Array.flatten(arguments).each(function(element){ + this.elements.push(element); + var start = element.retrieve('sortables:start', function(event){ + this.start.call(this, event, element); + }.bind(this)); + (this.options.handle ? element.getElement(this.options.handle) || element : element).addEvent('mousedown', start); + }, this); + return this; + }, + + addLists: function(){ + Array.flatten(arguments).each(function(list){ + this.lists.include(list); + this.addItems(list.getChildren()); + }, this); + return this; + }, + + removeItems: function(){ + return $$(Array.flatten(arguments).map(function(element){ + this.elements.erase(element); + var start = element.retrieve('sortables:start'); + (this.options.handle ? element.getElement(this.options.handle) || element : element).removeEvent('mousedown', start); + + return element; + }, this)); + }, + + removeLists: function(){ + return $$(Array.flatten(arguments).map(function(list){ + this.lists.erase(list); + this.removeItems(list.getChildren()); + + return list; + }, this)); + }, + + getDroppableCoordinates: function (element){ + var offsetParent = element.getOffsetParent(); + var position = element.getPosition(offsetParent); + var scroll = { + w: window.getScroll(), + offsetParent: offsetParent.getScroll() + }; + position.x += scroll.offsetParent.x; + position.y += scroll.offsetParent.y; + + if (offsetParent.getStyle('position') == 'fixed'){ + position.x -= scroll.w.x; + position.y -= scroll.w.y; + } + + return position; + }, + + getClone: function(event, element){ + if (!this.options.clone) return new Element(element.tagName).inject(document.body); + if (typeOf(this.options.clone) == 'function') return this.options.clone.call(this, event, element, this.list); + var clone = element.clone(true).setStyles({ + margin: 0, + position: 'absolute', + visibility: 'hidden', + width: element.getStyle('width') + }).addEvent('mousedown', function(event){ + element.fireEvent('mousedown', event); + }); + //prevent the duplicated radio inputs from unchecking the real one + if (clone.get('html').test('radio')){ + clone.getElements('input[type=radio]').each(function(input, i){ + input.set('name', 'clone_' + i); + if (input.get('checked')) element.getElements('input[type=radio]')[i].set('checked', true); + }); + } + + return clone.inject(this.list).setPosition(this.getDroppableCoordinates(this.element)); + }, + + getDroppables: function(){ + var droppables = this.list.getChildren().erase(this.clone).erase(this.element); + if (!this.options.constrain) droppables.append(this.lists).erase(this.list); + return droppables; + }, + + insert: function(dragging, element){ + var where = 'inside'; + if (this.lists.contains(element)){ + this.list = element; + this.drag.droppables = this.getDroppables(); + } else { + where = this.element.getAllPrevious().contains(element) ? 'before' : 'after'; + } + this.element.inject(element, where); + this.fireEvent('sort', [this.element, this.clone]); + }, + + start: function(event, element){ + if ( + !this.idle || + event.rightClick || + (!this.options.handle && this.options.unDraggableTags.contains(event.target.get('tag'))) + ) return; + + this.idle = false; + this.element = element; + this.opacity = element.getStyle('opacity'); + this.list = element.getParent(); + this.clone = this.getClone(event, element); + + this.drag = new Drag.Move(this.clone, Object.merge({ + + droppables: this.getDroppables() + }, this.options.dragOptions)).addEvents({ + onSnap: function(){ + event.stop(); + this.clone.setStyle('visibility', 'visible'); + this.element.setStyle('opacity', this.options.opacity || 0); + this.fireEvent('start', [this.element, this.clone]); + }.bind(this), + onEnter: this.insert.bind(this), + onCancel: this.end.bind(this), + onComplete: this.end.bind(this) + }); + + this.clone.inject(this.element, 'before'); + this.drag.start(event); + }, + + end: function(){ + this.drag.detach(); + this.element.setStyle('opacity', this.opacity); + var self = this; + if (this.effect){ + var dim = this.element.getStyles('width', 'height'), + clone = this.clone, + pos = clone.computePosition(this.getDroppableCoordinates(clone)); + + var destroy = function(){ + this.removeEvent('cancel', destroy); + clone.destroy(); + self.reset(); + }; + + this.effect.element = clone; + this.effect.start({ + top: pos.top, + left: pos.left, + width: dim.width, + height: dim.height, + opacity: 0.25 + }).addEvent('cancel', destroy).chain(destroy); + } else { + this.clone.destroy(); + self.reset(); + } + + }, + + reset: function(){ + this.idle = true; + this.fireEvent('complete', this.element); + }, + + serialize: function(){ + var params = Array.link(arguments, { + modifier: Type.isFunction, + index: function(obj){ + return obj != null; + } + }); + var serial = this.lists.map(function(list){ + return list.getChildren().map(params.modifier || function(element){ + return element.get('id'); + }, this); + }, this); + + var index = params.index; + if (this.lists.length == 1) index = 0; + return (index || index === 0) && index >= 0 && index < this.lists.length ? serial[index] : serial; + } + +}); + +/* +--- + +name: Element.Event.Pseudos + +description: Adds the functionality to add pseudo events for Elements + +license: MIT-style license + +authors: + - Arian Stolwijk + +requires: [Core/Element.Event, Core/Element.Delegation, Events.Pseudos] + +provides: [Element.Event.Pseudos, Element.Delegation.Pseudo] + +... +*/ + +(function(){ + +var pseudos = {relay: false}, + copyFromEvents = ['once', 'throttle', 'pause'], + count = copyFromEvents.length; + +while (count--) pseudos[copyFromEvents[count]] = Events.lookupPseudo(copyFromEvents[count]); + +DOMEvent.definePseudo = function(key, listener){ + pseudos[key] = listener; + return this; +}; + +var proto = Element.prototype; +[Element, Window, Document].invoke('implement', Events.Pseudos(pseudos, proto.addEvent, proto.removeEvent)); + +})(); + +/* +--- + +name: Element.Event.Pseudos.Keys + +description: Adds functionality fire events if certain keycombinations are pressed + +license: MIT-style license + +authors: + - Arian Stolwijk + +requires: [Element.Event.Pseudos] + +provides: [Element.Event.Pseudos.Keys] + +... +*/ + +(function(){ + +var keysStoreKey = '$moo:keys-pressed', + keysKeyupStoreKey = '$moo:keys-keyup'; + + +DOMEvent.definePseudo('keys', function(split, fn, args){ + + var event = args[0], + keys = [], + pressed = this.retrieve(keysStoreKey, []), + value = split.value; + + if (value != '+') keys.append(value.replace('++', function(){ + keys.push('+'); // shift++ and shift+++a + return ''; + }).split('+')); + else keys = ['+']; + + pressed.include(event.key); + + if (keys.every(function(key){ + return pressed.contains(key); + })) fn.apply(this, args); + + this.store(keysStoreKey, pressed); + + if (!this.retrieve(keysKeyupStoreKey)){ + var keyup = function(event){ + (function(){ + pressed = this.retrieve(keysStoreKey, []).erase(event.key); + this.store(keysStoreKey, pressed); + }).delay(0, this); // Fix for IE + }; + this.store(keysKeyupStoreKey, keyup).addEvent('keyup', keyup); + } + +}); + +DOMEvent.defineKeys({ + '16': 'shift', + '17': 'control', + '18': 'alt', + '20': 'capslock', + '33': 'pageup', + '34': 'pagedown', + '35': 'end', + '36': 'home', + '144': 'numlock', + '145': 'scrolllock', + '186': ';', + '187': '=', + '188': ',', + '190': '.', + '191': '/', + '192': '`', + '219': '[', + '220': '\\', + '221': ']', + '222': "'", + '107': '+', + '109': '-', // subtract + '189': '-' // dash +}) + +})(); + +/* +--- + +script: String.Extras.js + +name: String.Extras + +description: Extends the String native object to include methods useful in managing various kinds of strings (query strings, urls, html, etc). + +license: MIT-style license + +authors: + - Aaron Newton + - Guillermo Rauch + - Christopher Pitt + +requires: + - Core/String + - Core/Array + - MooTools.More + +provides: [String.Extras] + +... +*/ + +(function(){ + +var special = { + 'a': /[à áâãÀåÄÄ
]/g, + 'A': /[ÃÃÃÃÃÃ
ÄÄ]/g, + 'c': /[ÄÄç]/g, + 'C': /[ÄÄÃ]/g, + 'd': /[ÄÄ]/g, + 'D': /[ÄÃ]/g, + 'e': /[ÚéêëÄÄ]/g, + 'E': /[ÃÃÃÃÄÄ]/g, + 'g': /[Ä]/g, + 'G': /[Ä]/g, + 'i': /[ìÃîï]/g, + 'I': /[ÃÃÃÃ]/g, + 'l': /[ĺğÅ]/g, + 'L': /[ĹĜÅ]/g, + 'n': /[ñÅÅ]/g, + 'N': /[ÃÅÅ]/g, + 'o': /[òóÎõöÞÅ]/g, + 'O': /[ÃÃÃÃÃÃ]/g, + 'r': /[ÅÅ]/g, + 'R': /[ÅÅ]/g, + 's': /[ššÅ]/g, + 'S': /[Å ÅÅ]/g, + 't': /[ťţ]/g, + 'T': /[ŀŢ]/g, + 'u': /[ùúûů̵]/g, + 'U': /[ÃÃÃÅ®Ã]/g, + 'y': /[ÿÜ]/g, + 'Y': /[ÅžÃ]/g, + 'z': /[şźŌ]/g, + 'Z': /[ŜŹŻ]/g, + 'th': /[ß]/g, + 'TH': /[Ã]/g, + 'dh': /[ð]/g, + 'DH': /[Ã]/g, + 'ss': /[Ã]/g, + 'oe': /[Å]/g, + 'OE': /[Å]/g, + 'ae': /[Ê]/g, + 'AE': /[Ã]/g +}, + +tidy = { + ' ': /[\xa0\u2002\u2003\u2009]/g, + '*': /[\xb7]/g, + '\'': /[\u2018\u2019]/g, + '"': /[\u201c\u201d]/g, + '...': /[\u2026]/g, + '-': /[\u2013]/g, +// '--': /[\u2014]/g, + '»': /[\uFFFD]/g +}, + +conversions = { + ms: 1, + s: 1000, + m: 6e4, + h: 36e5 +}, + +findUnits = /(\d*.?\d+)([msh]+)/; + +var walk = function(string, replacements){ + var result = string, key; + for (key in replacements) result = result.replace(replacements[key], key); + return result; +}; + +var getRegexForTag = function(tag, contents){ + tag = tag || ''; + var regstr = contents ? "<" + tag + "(?!\\w)[^>]*>([\\s\\S]*?)<\/" + tag + "(?!\\w)>" : "<\/?" + tag + "([^>]+)?>", + reg = new RegExp(regstr, "gi"); + return reg; +}; + +String.implement({ + + standardize: function(){ + return walk(this, special); + }, + + repeat: function(times){ + return new Array(times + 1).join(this); + }, + + pad: function(length, str, direction){ + if (this.length >= length) return this; + + var pad = (str == null ? ' ' : '' + str) + .repeat(length - this.length) + .substr(0, length - this.length); + + if (!direction || direction == 'right') return this + pad; + if (direction == 'left') return pad + this; + + return pad.substr(0, (pad.length / 2).floor()) + this + pad.substr(0, (pad.length / 2).ceil()); + }, + + getTags: function(tag, contents){ + return this.match(getRegexForTag(tag, contents)) || []; + }, + + stripTags: function(tag, contents){ + return this.replace(getRegexForTag(tag, contents), ''); + }, + + tidy: function(){ + return walk(this, tidy); + }, + + truncate: function(max, trail, atChar){ + var string = this; + if (trail == null && arguments.length == 1) trail = 'âŠ'; + if (string.length > max){ + string = string.substring(0, max); + if (atChar){ + var index = string.lastIndexOf(atChar); + if (index != -1) string = string.substr(0, index); + } + if (trail) string += trail; + } + return string; + }, + + ms: function(){ + // "Borrowed" from https://gist.github.com/1503944 + var units = findUnits.exec(this); + if (units == null) return Number(this); + return Number(units[1]) * conversions[units[2]]; + } + +}); + +})(); + +/* +--- + +script: Element.Forms.js + +name: Element.Forms + +description: Extends the Element native object to include methods useful in managing inputs. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Element + - String.Extras + - MooTools.More + +provides: [Element.Forms] + +... +*/ + +Element.implement({ + + tidy: function(){ + this.set('value', this.get('value').tidy()); + }, + + getTextInRange: function(start, end){ + return this.get('value').substring(start, end); + }, + + getSelectedText: function(){ + if (this.setSelectionRange) return this.getTextInRange(this.getSelectionStart(), this.getSelectionEnd()); + return document.selection.createRange().text; + }, + + getSelectedRange: function(){ + if (this.selectionStart != null){ + return { + start: this.selectionStart, + end: this.selectionEnd + }; + } + + var pos = { + start: 0, + end: 0 + }; + var range = this.getDocument().selection.createRange(); + if (!range || range.parentElement() != this) return pos; + var duplicate = range.duplicate(); + + if (this.type == 'text'){ + pos.start = 0 - duplicate.moveStart('character', -100000); + pos.end = pos.start + range.text.length; + } else { + var value = this.get('value'); + var offset = value.length; + duplicate.moveToElementText(this); + duplicate.setEndPoint('StartToEnd', range); + if (duplicate.text.length) offset -= value.match(/[\n\r]*$/)[0].length; + pos.end = offset - duplicate.text.length; + duplicate.setEndPoint('StartToStart', range); + pos.start = offset - duplicate.text.length; + } + return pos; + }, + + getSelectionStart: function(){ + return this.getSelectedRange().start; + }, + + getSelectionEnd: function(){ + return this.getSelectedRange().end; + }, + + setCaretPosition: function(pos){ + if (pos == 'end') pos = this.get('value').length; + this.selectRange(pos, pos); + return this; + }, + + getCaretPosition: function(){ + return this.getSelectedRange().start; + }, + + selectRange: function(start, end){ + if (this.setSelectionRange){ + this.focus(); + this.setSelectionRange(start, end); + } else { + var value = this.get('value'); + var diff = value.substr(start, end - start).replace(/\r/g, '').length; + start = value.substr(0, start).replace(/\r/g, '').length; + var range = this.createTextRange(); + range.collapse(true); + range.moveEnd('character', start + diff); + range.moveStart('character', start); + range.select(); + } + return this; + }, + + insertAtCursor: function(value, select){ + var pos = this.getSelectedRange(); + var text = this.get('value'); + this.set('value', text.substring(0, pos.start) + value + text.substring(pos.end, text.length)); + if (select !== false) this.selectRange(pos.start, pos.start + value.length); + else this.setCaretPosition(pos.start + value.length); + return this; + }, + + insertAroundCursor: function(options, select){ + options = Object.append({ + before: '', + defaultMiddle: '', + after: '' + }, options); + + var value = this.getSelectedText() || options.defaultMiddle; + var pos = this.getSelectedRange(); + var text = this.get('value'); + + if (pos.start == pos.end){ + this.set('value', text.substring(0, pos.start) + options.before + value + options.after + text.substring(pos.end, text.length)); + this.selectRange(pos.start + options.before.length, pos.end + options.before.length + value.length); + } else { + var current = text.substring(pos.start, pos.end); + this.set('value', text.substring(0, pos.start) + options.before + current + options.after + text.substring(pos.end, text.length)); + var selStart = pos.start + options.before.length; + if (select !== false) this.selectRange(selStart, selStart + current.length); + else this.setCaretPosition(selStart + text.length); + } + return this; + } + +}); + +/* +--- + +script: Element.Pin.js + +name: Element.Pin + +description: Extends the Element native object to include the pin method useful for fixed positioning for elements. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Element.Event + - Core/Element.Dimensions + - Core/Element.Style + - MooTools.More + +provides: [Element.Pin] + +... +*/ + +(function(){ + var supportsPositionFixed = false, + supportTested = false; + + var testPositionFixed = function(){ + var test = new Element('div').setStyles({ + position: 'fixed', + top: 0, + right: 0 + }).inject(document.body); + supportsPositionFixed = (test.offsetTop === 0); + test.dispose(); + supportTested = true; + }; + + Element.implement({ + + pin: function(enable, forceScroll){ + if (!supportTested) testPositionFixed(); + if (this.getStyle('display') == 'none') return this; + + var pinnedPosition, + scroll = window.getScroll(), + parent, + scrollFixer; + + if (enable !== false){ + pinnedPosition = this.getPosition(); + if (!this.retrieve('pin:_pinned')) { + var currentPosition = { + top: pinnedPosition.y - scroll.y, + left: pinnedPosition.x - scroll.x, + margin: '0px', + padding: '0px' + }; + + if (supportsPositionFixed && !forceScroll){ + this.setStyle('position', 'fixed').setStyles(currentPosition); + } else { + + parent = this.getOffsetParent(); + var position = this.getPosition(parent), + styles = this.getStyles('left', 'top'); + + if (parent && styles.left == 'auto' || styles.top == 'auto') this.setPosition(position); + if (this.getStyle('position') == 'static') this.setStyle('position', 'absolute'); + + position = { + x: styles.left.toInt() - scroll.x, + y: styles.top.toInt() - scroll.y + }; + + scrollFixer = function(){ + if (!this.retrieve('pin:_pinned')) return; + var scroll = window.getScroll(); + this.setStyles({ + left: position.x + scroll.x, + top: position.y + scroll.y + }); + }.bind(this); + + this.store('pin:_scrollFixer', scrollFixer); + window.addEvent('scroll', scrollFixer); + } + this.store('pin:_pinned', true); + } + + } else { + if (!this.retrieve('pin:_pinned')) return this; + + parent = this.getParent(); + var offsetParent = (parent.getComputedStyle('position') != 'static' ? parent : parent.getOffsetParent()); + + pinnedPosition = this.getPosition(); + + this.store('pin:_pinned', false); + scrollFixer = this.retrieve('pin:_scrollFixer'); + if (!scrollFixer){ + this.setStyles({ + position: 'absolute', + top: pinnedPosition.y + scroll.y, + left: pinnedPosition.x + scroll.x + }); + } else { + this.store('pin:_scrollFixer', null); + window.removeEvent('scroll', scrollFixer); + } + this.removeClass('isPinned'); + } + return this; + }, + + unpin: function(){ + return this.pin(false); + }, + + togglePin: function(){ + return this.pin(!this.retrieve('pin:_pinned')); + } + + }); + + + +})(); + +/* +--- + +script: Element.Position.js + +name: Element.Position + +description: Extends the Element native object to include methods useful positioning elements relative to others. + +license: MIT-style license + +authors: + - Aaron Newton + - Jacob Thornton + +requires: + - Core/Options + - Core/Element.Dimensions + - Element.Measure + +provides: [Element.Position] + +... +*/ + +(function(original){ + +var local = Element.Position = { + + options: {/* + edge: false, + returnPos: false, + minimum: {x: 0, y: 0}, + maximum: {x: 0, y: 0}, + relFixedPosition: false, + ignoreMargins: false, + ignoreScroll: false, + allowNegative: false,*/ + relativeTo: document.body, + position: { + x: 'center', //left, center, right + y: 'center' //top, center, bottom + }, + offset: {x: 0, y: 0} + }, + + getOptions: function(element, options){ + options = Object.merge({}, local.options, options); + local.setPositionOption(options); + local.setEdgeOption(options); + local.setOffsetOption(element, options); + local.setDimensionsOption(element, options); + return options; + }, + + setPositionOption: function(options){ + options.position = local.getCoordinateFromValue(options.position); + }, + + setEdgeOption: function(options){ + var edgeOption = local.getCoordinateFromValue(options.edge); + options.edge = edgeOption ? edgeOption : + (options.position.x == 'center' && options.position.y == 'center') ? {x: 'center', y: 'center'} : + {x: 'left', y: 'top'}; + }, + + setOffsetOption: function(element, options){ + var parentOffset = {x: 0, y: 0}; + var parentScroll = {x: 0, y: 0}; + var offsetParent = element.measure(function(){ + return document.id(this.getOffsetParent()); + }); + + if (!offsetParent || offsetParent == element.getDocument().body) return; + + parentScroll = offsetParent.getScroll(); + parentOffset = offsetParent.measure(function(){ + var position = this.getPosition(); + if (this.getStyle('position') == 'fixed'){ + var scroll = window.getScroll(); + position.x += scroll.x; + position.y += scroll.y; + } + return position; + }); + + options.offset = { + parentPositioned: offsetParent != document.id(options.relativeTo), + x: options.offset.x - parentOffset.x + parentScroll.x, + y: options.offset.y - parentOffset.y + parentScroll.y + }; + }, + + setDimensionsOption: function(element, options){ + options.dimensions = element.getDimensions({ + computeSize: true, + styles: ['padding', 'border', 'margin'] + }); + }, + + getPosition: function(element, options){ + var position = {}; + options = local.getOptions(element, options); + var relativeTo = document.id(options.relativeTo) || document.body; + + local.setPositionCoordinates(options, position, relativeTo); + if (options.edge) local.toEdge(position, options); + + var offset = options.offset; + position.left = ((position.x >= 0 || offset.parentPositioned || options.allowNegative) ? position.x : 0).toInt(); + position.top = ((position.y >= 0 || offset.parentPositioned || options.allowNegative) ? position.y : 0).toInt(); + + local.toMinMax(position, options); + + if (options.relFixedPosition || relativeTo.getStyle('position') == 'fixed') local.toRelFixedPosition(relativeTo, position); + if (options.ignoreScroll) local.toIgnoreScroll(relativeTo, position); + if (options.ignoreMargins) local.toIgnoreMargins(position, options); + + position.left = Math.ceil(position.left); + position.top = Math.ceil(position.top); + delete position.x; + delete position.y; + + return position; + }, + + setPositionCoordinates: function(options, position, relativeTo){ + var offsetY = options.offset.y, + offsetX = options.offset.x, + calc = (relativeTo == document.body) ? window.getScroll() : relativeTo.getPosition(), + top = calc.y, + left = calc.x, + winSize = window.getSize(); + + switch(options.position.x){ + case 'left': position.x = left + offsetX; break; + case 'right': position.x = left + offsetX + relativeTo.offsetWidth; break; + default: position.x = left + ((relativeTo == document.body ? winSize.x : relativeTo.offsetWidth) / 2) + offsetX; break; + } + + switch(options.position.y){ + case 'top': position.y = top + offsetY; break; + case 'bottom': position.y = top + offsetY + relativeTo.offsetHeight; break; + default: position.y = top + ((relativeTo == document.body ? winSize.y : relativeTo.offsetHeight) / 2) + offsetY; break; + } + }, + + toMinMax: function(position, options){ + var xy = {left: 'x', top: 'y'}, value; + ['minimum', 'maximum'].each(function(minmax){ + ['left', 'top'].each(function(lr){ + value = options[minmax] ? options[minmax][xy[lr]] : null; + if (value != null && ((minmax == 'minimum') ? position[lr] < value : position[lr] > value)) position[lr] = value; + }); + }); + }, + + toRelFixedPosition: function(relativeTo, position){ + var winScroll = window.getScroll(); + position.top += winScroll.y; + position.left += winScroll.x; + }, + + toIgnoreScroll: function(relativeTo, position){ + var relScroll = relativeTo.getScroll(); + position.top -= relScroll.y; + position.left -= relScroll.x; + }, + + toIgnoreMargins: function(position, options){ + position.left += options.edge.x == 'right' + ? options.dimensions['margin-right'] + : (options.edge.x != 'center' + ? -options.dimensions['margin-left'] + : -options.dimensions['margin-left'] + ((options.dimensions['margin-right'] + options.dimensions['margin-left']) / 2)); + + position.top += options.edge.y == 'bottom' + ? options.dimensions['margin-bottom'] + : (options.edge.y != 'center' + ? -options.dimensions['margin-top'] + : -options.dimensions['margin-top'] + ((options.dimensions['margin-bottom'] + options.dimensions['margin-top']) / 2)); + }, + + toEdge: function(position, options){ + var edgeOffset = {}, + dimensions = options.dimensions, + edge = options.edge; + + switch(edge.x){ + case 'left': edgeOffset.x = 0; break; + case 'right': edgeOffset.x = -dimensions.x - dimensions.computedRight - dimensions.computedLeft; break; + // center + default: edgeOffset.x = -(Math.round(dimensions.totalWidth / 2)); break; + } + + switch(edge.y){ + case 'top': edgeOffset.y = 0; break; + case 'bottom': edgeOffset.y = -dimensions.y - dimensions.computedTop - dimensions.computedBottom; break; + // center + default: edgeOffset.y = -(Math.round(dimensions.totalHeight / 2)); break; + } + + position.x += edgeOffset.x; + position.y += edgeOffset.y; + }, + + getCoordinateFromValue: function(option){ + if (typeOf(option) != 'string') return option; + option = option.toLowerCase(); + + return { + x: option.test('left') ? 'left' + : (option.test('right') ? 'right' : 'center'), + y: option.test(/upper|top/) ? 'top' + : (option.test('bottom') ? 'bottom' : 'center') + }; + } + +}; + +Element.implement({ + + position: function(options){ + if (options && (options.x != null || options.y != null)){ + return (original ? original.apply(this, arguments) : this); + } + var position = this.setStyle('position', 'absolute').calculatePosition(options); + return (options && options.returnPos) ? position : this.setStyles(position); + }, + + calculatePosition: function(options){ + return local.getPosition(this, options); + } + +}); + +})(Element.prototype.position); + +/* +--- + +script: Element.Shortcuts.js + +name: Element.Shortcuts + +description: Extends the Element native object to include some shortcut methods. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Element.Style + - MooTools.More + +provides: [Element.Shortcuts] + +... +*/ + +Element.implement({ + + isDisplayed: function(){ + return this.getStyle('display') != 'none'; + }, + + isVisible: function(){ + var w = this.offsetWidth, + h = this.offsetHeight; + return (w == 0 && h == 0) ? false : (w > 0 && h > 0) ? true : this.style.display != 'none'; + }, + + toggle: function(){ + return this[this.isDisplayed() ? 'hide' : 'show'](); + }, + + hide: function(){ + var d; + try { + //IE fails here if the element is not in the dom + d = this.getStyle('display'); + } catch(e){} + if (d == 'none') return this; + return this.store('element:_originalDisplay', d || '').setStyle('display', 'none'); + }, + + show: function(display){ + if (!display && this.isDisplayed()) return this; + display = display || this.retrieve('element:_originalDisplay') || 'block'; + return this.setStyle('display', (display == 'none') ? 'block' : display); + }, + + swapClass: function(remove, add){ + return this.removeClass(remove).addClass(add); + } + +}); + +Document.implement({ + + clearSelection: function(){ + if (window.getSelection){ + var selection = window.getSelection(); + if (selection && selection.removeAllRanges) selection.removeAllRanges(); + } else if (document.selection && document.selection.empty){ + try { + //IE fails here if selected element is not in dom + document.selection.empty(); + } catch(e){} + } + } + +}); + +/* +--- + +script: Elements.From.js + +name: Elements.From + +description: Returns a collection of elements from a string of html. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/String + - Core/Element + - MooTools.More + +provides: [Elements.from, Elements.From] + +... +*/ + +Elements.from = function(text, excludeScripts){ + if (excludeScripts || excludeScripts == null) text = text.stripScripts(); + + var container, match = text.match(/^\s*(?:<!--.*?-->\s*)*<(t[dhr]|tbody|tfoot|thead)/i); + + if (match){ + container = new Element('table'); + var tag = match[1].toLowerCase(); + if (['td', 'th', 'tr'].contains(tag)){ + container = new Element('tbody').inject(container); + if (tag != 'tr') container = new Element('tr').inject(container); + } + } + + return (container || new Element('div')).set('html', text).getChildren(); +}; + +/* +--- + +script: IframeShim.js + +name: IframeShim + +description: Defines IframeShim, a class for obscuring select lists and flash objects in IE. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Element.Event + - Core/Element.Style + - Core/Options + - Core/Events + - Element.Position + - Class.Occlude + +provides: [IframeShim] + +... +*/ + +(function(){ + +var browsers = false; + + +this.IframeShim = new Class({ + + Implements: [Options, Events, Class.Occlude], + + options: { + className: 'iframeShim', + src: 'javascript:false;document.write("");', + display: false, + zIndex: null, + margin: 0, + offset: {x: 0, y: 0}, + browsers: browsers + }, + + property: 'IframeShim', + + initialize: function(element, options){ + this.element = document.id(element); + if (this.occlude()) return this.occluded; + this.setOptions(options); + this.makeShim(); + return this; + }, + + makeShim: function(){ + if (this.options.browsers){ + var zIndex = this.element.getStyle('zIndex').toInt(); + + if (!zIndex){ + zIndex = 1; + var pos = this.element.getStyle('position'); + if (pos == 'static' || !pos) this.element.setStyle('position', 'relative'); + this.element.setStyle('zIndex', zIndex); + } + zIndex = ((this.options.zIndex != null || this.options.zIndex === 0) && zIndex > this.options.zIndex) ? this.options.zIndex : zIndex - 1; + if (zIndex < 0) zIndex = 1; + this.shim = new Element('iframe', { + src: this.options.src, + scrolling: 'no', + frameborder: 0, + styles: { + zIndex: zIndex, + position: 'absolute', + border: 'none', + filter: 'progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=0)' + }, + 'class': this.options.className + }).store('IframeShim', this); + var inject = (function(){ + this.shim.inject(this.element, 'after'); + this[this.options.display ? 'show' : 'hide'](); + this.fireEvent('inject'); + }).bind(this); + if (!IframeShim.ready) window.addEvent('load', inject); + else inject(); + } else { + this.position = this.hide = this.show = this.dispose = Function.from(this); + } + }, + + position: function(){ + if (!IframeShim.ready || !this.shim) return this; + var size = this.element.measure(function(){ + return this.getSize(); + }); + if (this.options.margin != undefined){ + size.x = size.x - (this.options.margin * 2); + size.y = size.y - (this.options.margin * 2); + this.options.offset.x += this.options.margin; + this.options.offset.y += this.options.margin; + } + this.shim.set({width: size.x, height: size.y}).position({ + relativeTo: this.element, + offset: this.options.offset + }); + return this; + }, + + hide: function(){ + if (this.shim) this.shim.setStyle('display', 'none'); + return this; + }, + + show: function(){ + if (this.shim) this.shim.setStyle('display', 'block'); + return this.position(); + }, + + dispose: function(){ + if (this.shim) this.shim.dispose(); + return this; + }, + + destroy: function(){ + if (this.shim) this.shim.destroy(); + return this; + } + +}); + +})(); + +window.addEvent('load', function(){ + IframeShim.ready = true; +}); + +/* +--- + +script: Mask.js + +name: Mask + +description: Creates a mask element to cover another. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Options + - Core/Events + - Core/Element.Event + - Class.Binds + - Element.Position + - IframeShim + +provides: [Mask] + +... +*/ + +var Mask = new Class({ + + Implements: [Options, Events], + + Binds: ['position'], + + options: {/* + onShow: function(){}, + onHide: function(){}, + onDestroy: function(){}, + onClick: function(event){}, + inject: { + where: 'after', + target: null, + }, + hideOnClick: false, + id: null, + destroyOnHide: false,*/ + style: {}, + 'class': 'mask', + maskMargins: false, + useIframeShim: true, + iframeShimOptions: {} + }, + + initialize: function(target, options){ + this.target = document.id(target) || document.id(document.body); + this.target.store('mask', this); + this.setOptions(options); + this.render(); + this.inject(); + }, + + render: function(){ + this.element = new Element('div', { + 'class': this.options['class'], + id: this.options.id || 'mask-' + String.uniqueID(), + styles: Object.merge({}, this.options.style, { + display: 'none' + }), + events: { + click: function(event){ + this.fireEvent('click', event); + if (this.options.hideOnClick) this.hide(); + }.bind(this) + } + }); + + this.hidden = true; + }, + + toElement: function(){ + return this.element; + }, + + inject: function(target, where){ + where = where || (this.options.inject ? this.options.inject.where : '') || (this.target == document.body ? 'inside' : 'after'); + target = target || (this.options.inject && this.options.inject.target) || this.target; + + this.element.inject(target, where); + + if (this.options.useIframeShim){ + this.shim = new IframeShim(this.element, this.options.iframeShimOptions); + + this.addEvents({ + show: this.shim.show.bind(this.shim), + hide: this.shim.hide.bind(this.shim), + destroy: this.shim.destroy.bind(this.shim) + }); + } + }, + + position: function(){ + this.resize(this.options.width, this.options.height); + + this.element.position({ + relativeTo: this.target, + position: 'topLeft', + ignoreMargins: !this.options.maskMargins, + ignoreScroll: this.target == document.body + }); + + return this; + }, + + resize: function(x, y){ + var opt = { + styles: ['padding', 'border'] + }; + if (this.options.maskMargins) opt.styles.push('margin'); + + var dim = this.target.getComputedSize(opt); + if (this.target == document.body){ + this.element.setStyles({width: 0, height: 0}); + var win = window.getScrollSize(); + if (dim.totalHeight < win.y) dim.totalHeight = win.y; + if (dim.totalWidth < win.x) dim.totalWidth = win.x; + } + this.element.setStyles({ + width: Array.pick([x, dim.totalWidth, dim.x]), + height: Array.pick([y, dim.totalHeight, dim.y]) + }); + + return this; + }, + + show: function(){ + if (!this.hidden) return this; + + window.addEvent('resize', this.position); + this.position(); + this.showMask.apply(this, arguments); + + return this; + }, + + showMask: function(){ + this.element.setStyle('display', 'block'); + this.hidden = false; + this.fireEvent('show'); + }, + + hide: function(){ + if (this.hidden) return this; + + window.removeEvent('resize', this.position); + this.hideMask.apply(this, arguments); + if (this.options.destroyOnHide) return this.destroy(); + + return this; + }, + + hideMask: function(){ + this.element.setStyle('display', 'none'); + this.hidden = true; + this.fireEvent('hide'); + }, + + toggle: function(){ + this[this.hidden ? 'show' : 'hide'](); + }, + + destroy: function(){ + this.hide(); + this.element.destroy(); + this.fireEvent('destroy'); + this.target.eliminate('mask'); + } + +}); + +Element.Properties.mask = { + + set: function(options){ + var mask = this.retrieve('mask'); + if (mask) mask.destroy(); + return this.eliminate('mask').store('mask:options', options); + }, + + get: function(){ + var mask = this.retrieve('mask'); + if (!mask){ + mask = new Mask(this, this.retrieve('mask:options')); + this.store('mask', mask); + } + return mask; + } + +}; + +Element.implement({ + + mask: function(options){ + if (options) this.set('mask', options); + this.get('mask').show(); + return this; + }, + + unmask: function(){ + this.get('mask').hide(); + return this; + } + +}); + +/* +--- + +script: Spinner.js + +name: Spinner + +description: Adds a semi-transparent overlay over a dom element with a spinnin ajax icon. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Fx.Tween + - Core/Request + - Class.refactor + - Mask + +provides: [Spinner] + +... +*/ + +var Spinner = new Class({ + + Extends: Mask, + + Implements: Chain, + + options: {/* + message: false,*/ + 'class': 'spinner', + containerPosition: {}, + content: { + 'class': 'spinner-content' + }, + messageContainer: { + 'class': 'spinner-msg' + }, + img: { + 'class': 'spinner-img' + }, + fxOptions: { + link: 'chain' + } + }, + + initialize: function(target, options){ + this.target = document.id(target) || document.id(document.body); + this.target.store('spinner', this); + this.setOptions(options); + this.render(); + this.inject(); + + // Add this to events for when noFx is true; parent methods handle hide/show. + var deactivate = function(){ this.active = false; }.bind(this); + this.addEvents({ + hide: deactivate, + show: deactivate + }); + }, + + render: function(){ + this.parent(); + + this.element.set('id', this.options.id || 'spinner-' + String.uniqueID()); + + this.content = document.id(this.options.content) || new Element('div', this.options.content); + this.content.inject(this.element); + + if (this.options.message){ + this.msg = document.id(this.options.message) || new Element('p', this.options.messageContainer).appendText(this.options.message); + this.msg.inject(this.content); + } + + if (this.options.img){ + this.img = document.id(this.options.img) || new Element('div', this.options.img); + this.img.inject(this.content); + } + + this.element.set('tween', this.options.fxOptions); + }, + + show: function(noFx){ + if (this.active) return this.chain(this.show.bind(this)); + if (!this.hidden){ + this.callChain.delay(20, this); + return this; + } + + this.target.set('aria-busy', 'true'); + this.active = true; + + return this.parent(noFx); + }, + + showMask: function(noFx){ + var pos = function(){ + this.content.position(Object.merge({ + relativeTo: this.element + }, this.options.containerPosition)); + }.bind(this); + + if (noFx){ + this.parent(); + pos(); + } else { + if (!this.options.style.opacity) this.options.style.opacity = this.element.getStyle('opacity').toFloat(); + this.element.setStyles({ + display: 'block', + opacity: 0 + }).tween('opacity', this.options.style.opacity); + pos(); + this.hidden = false; + this.fireEvent('show'); + this.callChain(); + } + }, + + hide: function(noFx){ + if (this.active) return this.chain(this.hide.bind(this)); + if (this.hidden){ + this.callChain.delay(20, this); + return this; + } + + this.target.set('aria-busy', 'false'); + this.active = true; + + return this.parent(noFx); + }, + + hideMask: function(noFx){ + if (noFx) return this.parent(); + this.element.tween('opacity', 0).get('tween').chain(function(){ + this.element.setStyle('display', 'none'); + this.hidden = true; + this.fireEvent('hide'); + this.callChain(); + }.bind(this)); + }, + + destroy: function(){ + this.content.destroy(); + this.parent(); + this.target.eliminate('spinner'); + } + +}); + +Request = Class.refactor(Request, { + + options: { + useSpinner: false, + spinnerOptions: {}, + spinnerTarget: false + }, + + initialize: function(options){ + this._send = this.send; + this.send = function(options){ + var spinner = this.getSpinner(); + if (spinner) spinner.chain(this._send.pass(options, this)).show(); + else this._send(options); + return this; + }; + this.previous(options); + }, + + getSpinner: function(){ + if (!this.spinner){ + var update = document.id(this.options.spinnerTarget) || document.id(this.options.update); + if (this.options.useSpinner && update){ + update.set('spinner', this.options.spinnerOptions); + var spinner = this.spinner = update.get('spinner'); + ['complete', 'exception', 'cancel'].each(function(event){ + this.addEvent(event, spinner.hide.bind(spinner)); + }, this); + } + } + return this.spinner; + } + +}); + +Element.Properties.spinner = { + + set: function(options){ + var spinner = this.retrieve('spinner'); + if (spinner) spinner.destroy(); + return this.eliminate('spinner').store('spinner:options', options); + }, + + get: function(){ + var spinner = this.retrieve('spinner'); + if (!spinner){ + spinner = new Spinner(this, this.retrieve('spinner:options')); + this.store('spinner', spinner); + } + return spinner; + } + +}; + +Element.implement({ + + spin: function(options){ + if (options) this.set('spinner', options); + this.get('spinner').show(); + return this; + }, + + unspin: function(){ + this.get('spinner').hide(); + return this; + } + +}); + +/* +--- + +script: String.QueryString.js + +name: String.QueryString + +description: Methods for dealing with URI query strings. + +license: MIT-style license + +authors: + - Sebastian MarkbÃ¥ge + - Aaron Newton + - Lennart Pilon + - Valerio Proietti + +requires: + - Core/Array + - Core/String + - MooTools.More + +provides: [String.QueryString] + +... +*/ + +String.implement({ + + parseQueryString: function(decodeKeys, decodeValues){ + if (decodeKeys == null) decodeKeys = true; + if (decodeValues == null) decodeValues = true; + + var vars = this.split(/[&;]/), + object = {}; + if (!vars.length) return object; + + vars.each(function(val){ + var index = val.indexOf('=') + 1, + value = index ? val.substr(index) : '', + keys = index ? val.substr(0, index - 1).match(/([^\]\[]+|(\B)(?=\]))/g) : [val], + obj = object; + if (!keys) return; + if (decodeValues) value = decodeURIComponent(value); + keys.each(function(key, i){ + if (decodeKeys) key = decodeURIComponent(key); + var current = obj[key]; + + if (i < keys.length - 1) obj = obj[key] = current || {}; + else if (typeOf(current) == 'array') current.push(value); + else obj[key] = current != null ? [current, value] : value; + }); + }); + + return object; + }, + + cleanQueryString: function(method){ + return this.split('&').filter(function(val){ + var index = val.indexOf('='), + key = index < 0 ? '' : val.substr(0, index), + value = val.substr(index + 1); + + return method ? method.call(null, key, value) : (value || value === 0); + }).join('&'); + } + +}); + +/* +--- + +script: Form.Request.js + +name: Form.Request + +description: Handles the basic functionality of submitting a form and updating a dom element with the result. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Request.HTML + - Class.Binds + - Class.Occlude + - Spinner + - String.QueryString + - Element.Delegation.Pseudo + +provides: [Form.Request] + +... +*/ + +if (!window.Form) window.Form = {}; + +(function(){ + + Form.Request = new Class({ + + Binds: ['onSubmit', 'onFormValidate'], + + Implements: [Options, Events, Class.Occlude], + + options: {/* + onFailure: function(){}, + onSuccess: function(){}, // aliased to onComplete, + onSend: function(){}*/ + requestOptions: { + evalScripts: true, + useSpinner: true, + emulation: false, + link: 'ignore' + }, + sendButtonClicked: true, + extraData: {}, + resetForm: true + }, + + property: 'form.request', + + initialize: function(form, target, options){ + this.element = document.id(form); + if (this.occlude()) return this.occluded; + this.setOptions(options) + .setTarget(target) + .attach(); + }, + + setTarget: function(target){ + this.target = document.id(target); + if (!this.request){ + this.makeRequest(); + } else { + this.request.setOptions({ + update: this.target + }); + } + return this; + }, + + toElement: function(){ + return this.element; + }, + + makeRequest: function(){ + var self = this; + this.request = new Request.HTML(Object.merge({ + update: this.target, + emulation: false, + spinnerTarget: this.element, + method: this.element.get('method') || 'post' + }, this.options.requestOptions)).addEvents({ + success: function(tree, elements, html, javascript){ + ['complete', 'success'].each(function(evt){ + self.fireEvent(evt, [self.target, tree, elements, html, javascript]); + }); + }, + failure: function(){ + self.fireEvent('complete', arguments).fireEvent('failure', arguments); + }, + exception: function(){ + self.fireEvent('failure', arguments); + } + }); + return this.attachReset(); + }, + + attachReset: function(){ + if (!this.options.resetForm) return this; + this.request.addEvent('success', function(){ + Function.attempt(function(){ + this.element.reset(); + }.bind(this)); + if (window.OverText) OverText.update(); + }.bind(this)); + return this; + }, + + attach: function(attach){ + var method = (attach != false) ? 'addEvent' : 'removeEvent'; + this.element[method]('click:relay(button, input[type=submit])', this.saveClickedButton.bind(this)); + + var fv = this.element.retrieve('validator'); + if (fv) fv[method]('onFormValidate', this.onFormValidate); + else this.element[method]('submit', this.onSubmit); + + return this; + }, + + detach: function(){ + return this.attach(false); + }, + + //public method + enable: function(){ + return this.attach(); + }, + + //public method + disable: function(){ + return this.detach(); + }, + + onFormValidate: function(valid, form, event){ + //if there's no event, then this wasn't a submit event + if (!event) return; + var fv = this.element.retrieve('validator'); + if (valid || (fv && !fv.options.stopOnFailure)){ + event.stop(); + this.send(); + } + }, + + onSubmit: function(event){ + var fv = this.element.retrieve('validator'); + if (fv){ + //form validator was created after Form.Request + this.element.removeEvent('submit', this.onSubmit); + fv.addEvent('onFormValidate', this.onFormValidate); + fv.validate(event); + return; + } + if (event) event.stop(); + this.send(); + }, + + saveClickedButton: function(event, target){ + var targetName = target.get('name'); + if (!targetName || !this.options.sendButtonClicked) return; + this.options.extraData[targetName] = target.get('value') || true; + this.clickedCleaner = function(){ + delete this.options.extraData[targetName]; + this.clickedCleaner = function(){}; + }.bind(this); + }, + + clickedCleaner: function(){}, + + send: function(){ + var str = this.element.toQueryString().trim(), + data = Object.toQueryString(this.options.extraData); + + if (str) str += "&" + data; + else str = data; + + this.fireEvent('send', [this.element, str.parseQueryString()]); + this.request.send({ + data: str, + url: this.options.requestOptions.url || this.element.get('action') + }); + this.clickedCleaner(); + return this; + } + + }); + + Element.implement('formUpdate', function(update, options){ + var fq = this.retrieve('form.request'); + if (!fq){ + fq = new Form.Request(this, update, options); + } else { + if (update) fq.setTarget(update); + if (options) fq.setOptions(options).makeRequest(); + } + fq.send(); + return this; + }); + +})(); + +/* +--- + +script: Fx.Reveal.js + +name: Fx.Reveal + +description: Defines Fx.Reveal, a class that shows and hides elements with a transition. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Fx.Morph + - Element.Shortcuts + - Element.Measure + +provides: [Fx.Reveal] + +... +*/ + +(function(){ + + +var hideTheseOf = function(object){ + var hideThese = object.options.hideInputs; + if (window.OverText){ + var otClasses = [null]; + OverText.each(function(ot){ + otClasses.include('.' + ot.options.labelClass); + }); + if (otClasses) hideThese += otClasses.join(', '); + } + return (hideThese) ? object.element.getElements(hideThese) : null; +}; + + +Fx.Reveal = new Class({ + + Extends: Fx.Morph, + + options: {/* + onShow: function(thisElement){}, + onHide: function(thisElement){}, + onComplete: function(thisElement){}, + heightOverride: null, + widthOverride: null,*/ + link: 'cancel', + styles: ['padding', 'border', 'margin'], + transitionOpacity: 'opacity' in document.documentElement, + mode: 'vertical', + display: function(){ + return this.element.get('tag') != 'tr' ? 'block' : 'table-row'; + }, + opacity: 1, + hideInputs: !('opacity' in document.documentElement) ? 'select, input, textarea, object, embed' : null + }, + + dissolve: function(){ + if (!this.hiding && !this.showing){ + if (this.element.getStyle('display') != 'none'){ + this.hiding = true; + this.showing = false; + this.hidden = true; + this.cssText = this.element.style.cssText; + + var startStyles = this.element.getComputedSize({ + styles: this.options.styles, + mode: this.options.mode + }); + if (this.options.transitionOpacity) startStyles.opacity = this.options.opacity; + + var zero = {}; + Object.each(startStyles, function(style, name){ + zero[name] = [style, 0]; + }); + + this.element.setStyles({ + display: Function.from(this.options.display).call(this), + overflow: 'hidden' + }); + + var hideThese = hideTheseOf(this); + if (hideThese) hideThese.setStyle('visibility', 'hidden'); + + this.$chain.unshift(function(){ + if (this.hidden){ + this.hiding = false; + this.element.style.cssText = this.cssText; + this.element.setStyle('display', 'none'); + if (hideThese) hideThese.setStyle('visibility', 'visible'); + } + this.fireEvent('hide', this.element); + this.callChain(); + }.bind(this)); + + this.start(zero); + } else { + this.callChain.delay(10, this); + this.fireEvent('complete', this.element); + this.fireEvent('hide', this.element); + } + } else if (this.options.link == 'chain'){ + this.chain(this.dissolve.bind(this)); + } else if (this.options.link == 'cancel' && !this.hiding){ + this.cancel(); + this.dissolve(); + } + return this; + }, + + reveal: function(){ + if (!this.showing && !this.hiding){ + if (this.element.getStyle('display') == 'none'){ + this.hiding = false; + this.showing = true; + this.hidden = false; + this.cssText = this.element.style.cssText; + + var startStyles; + this.element.measure(function(){ + startStyles = this.element.getComputedSize({ + styles: this.options.styles, + mode: this.options.mode + }); + }.bind(this)); + if (this.options.heightOverride != null) startStyles.height = this.options.heightOverride.toInt(); + if (this.options.widthOverride != null) startStyles.width = this.options.widthOverride.toInt(); + if (this.options.transitionOpacity){ + this.element.setStyle('opacity', 0); + startStyles.opacity = this.options.opacity; + } + + var zero = { + height: 0, + display: Function.from(this.options.display).call(this) + }; + Object.each(startStyles, function(style, name){ + zero[name] = 0; + }); + zero.overflow = 'hidden'; + + this.element.setStyles(zero); + + var hideThese = hideTheseOf(this); + if (hideThese) hideThese.setStyle('visibility', 'hidden'); + + this.$chain.unshift(function(){ + this.element.style.cssText = this.cssText; + this.element.setStyle('display', Function.from(this.options.display).call(this)); + if (!this.hidden) this.showing = false; + if (hideThese) hideThese.setStyle('visibility', 'visible'); + this.callChain(); + this.fireEvent('show', this.element); + }.bind(this)); + + this.start(startStyles); + } else { + this.callChain(); + this.fireEvent('complete', this.element); + this.fireEvent('show', this.element); + } + } else if (this.options.link == 'chain'){ + this.chain(this.reveal.bind(this)); + } else if (this.options.link == 'cancel' && !this.showing){ + this.cancel(); + this.reveal(); + } + return this; + }, + + toggle: function(){ + if (this.element.getStyle('display') == 'none'){ + this.reveal(); + } else { + this.dissolve(); + } + return this; + }, + + cancel: function(){ + this.parent.apply(this, arguments); + if (this.cssText != null) this.element.style.cssText = this.cssText; + this.hiding = false; + this.showing = false; + return this; + } + +}); + +Element.Properties.reveal = { + + set: function(options){ + this.get('reveal').cancel().setOptions(options); + return this; + }, + + get: function(){ + var reveal = this.retrieve('reveal'); + if (!reveal){ + reveal = new Fx.Reveal(this); + this.store('reveal', reveal); + } + return reveal; + } + +}; + +Element.Properties.dissolve = Element.Properties.reveal; + +Element.implement({ + + reveal: function(options){ + this.get('reveal').setOptions(options).reveal(); + return this; + }, + + dissolve: function(options){ + this.get('reveal').setOptions(options).dissolve(); + return this; + }, + + nix: function(options){ + var params = Array.link(arguments, {destroy: Type.isBoolean, options: Type.isObject}); + this.get('reveal').setOptions(options).dissolve().chain(function(){ + this[params.destroy ? 'destroy' : 'dispose'](); + }.bind(this)); + return this; + }, + + wink: function(){ + var params = Array.link(arguments, {duration: Type.isNumber, options: Type.isObject}); + var reveal = this.get('reveal').setOptions(params.options); + reveal.reveal().chain(function(){ + (function(){ + reveal.dissolve(); + }).delay(params.duration || 2000); + }); + } + +}); + +})(); + +/* +--- + +script: Form.Request.Append.js + +name: Form.Request.Append + +description: Handles the basic functionality of submitting a form and updating a dom element with the result. The result is appended to the DOM element instead of replacing its contents. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Form.Request + - Fx.Reveal + - Elements.from + +provides: [Form.Request.Append] + +... +*/ + +Form.Request.Append = new Class({ + + Extends: Form.Request, + + options: { + //onBeforeEffect: function(){}, + useReveal: true, + revealOptions: {}, + inject: 'bottom' + }, + + makeRequest: function(){ + this.request = new Request.HTML(Object.merge({ + url: this.element.get('action'), + method: this.element.get('method') || 'post', + spinnerTarget: this.element + }, this.options.requestOptions, { + evalScripts: false + }) + ).addEvents({ + success: function(tree, elements, html, javascript){ + var container; + var kids = Elements.from(html); + if (kids.length == 1){ + container = kids[0]; + } else { + container = new Element('div', { + styles: { + display: 'none' + } + }).adopt(kids); + } + container.inject(this.target, this.options.inject); + if (this.options.requestOptions.evalScripts) Browser.exec(javascript); + this.fireEvent('beforeEffect', container); + var finish = function(){ + this.fireEvent('success', [container, this.target, tree, elements, html, javascript]); + }.bind(this); + if (this.options.useReveal){ + container.set('reveal', this.options.revealOptions).get('reveal').chain(finish); + container.reveal(); + } else { + finish(); + } + }.bind(this), + failure: function(xhr){ + this.fireEvent('failure', xhr); + }.bind(this) + }); + this.attachReset(); + } + +}); + +/* +--- + +script: Object.Extras.js + +name: Object.Extras + +description: Extra Object generics, like getFromPath which allows a path notation to child elements. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Object + - MooTools.More + +provides: [Object.Extras] + +... +*/ + +(function(){ + +var defined = function(value){ + return value != null; +}; + +var hasOwnProperty = Object.prototype.hasOwnProperty; + +Object.extend({ + + getFromPath: function(source, parts){ + if (typeof parts == 'string') parts = parts.split('.'); + for (var i = 0, l = parts.length; i < l; i++){ + if (hasOwnProperty.call(source, parts[i])) source = source[parts[i]]; + else return null; + } + return source; + }, + + cleanValues: function(object, method){ + method = method || defined; + for (var key in object) if (!method(object[key])){ + delete object[key]; + } + return object; + }, + + erase: function(object, key){ + if (hasOwnProperty.call(object, key)) delete object[key]; + return object; + }, + + run: function(object){ + var args = Array.slice(arguments, 1); + for (var key in object) if (object[key].apply){ + object[key].apply(object, args); + } + return object; + } + +}); + +})(); + +/* +--- + +script: Locale.js + +name: Locale + +description: Provides methods for localization. + +license: MIT-style license + +authors: + - Aaron Newton + - Arian Stolwijk + +requires: + - Core/Events + - Object.Extras + - MooTools.More + +provides: [Locale, Lang] + +... +*/ + +(function(){ + +var current = null, + locales = {}, + inherits = {}; + +var getSet = function(set){ + if (instanceOf(set, Locale.Set)) return set; + else return locales[set]; +}; + +var Locale = this.Locale = { + + define: function(locale, set, key, value){ + var name; + if (instanceOf(locale, Locale.Set)){ + name = locale.name; + if (name) locales[name] = locale; + } else { + name = locale; + if (!locales[name]) locales[name] = new Locale.Set(name); + locale = locales[name]; + } + + if (set) locale.define(set, key, value); + + + + if (!current) current = locale; + + return locale; + }, + + use: function(locale){ + locale = getSet(locale); + + if (locale){ + current = locale; + + this.fireEvent('change', locale); + + + } + + return this; + }, + + getCurrent: function(){ + return current; + }, + + get: function(key, args){ + return (current) ? current.get(key, args) : ''; + }, + + inherit: function(locale, inherits, set){ + locale = getSet(locale); + + if (locale) locale.inherit(inherits, set); + return this; + }, + + list: function(){ + return Object.keys(locales); + } + +}; + +Object.append(Locale, new Events); + +Locale.Set = new Class({ + + sets: {}, + + inherits: { + locales: [], + sets: {} + }, + + initialize: function(name){ + this.name = name || ''; + }, + + define: function(set, key, value){ + var defineData = this.sets[set]; + if (!defineData) defineData = {}; + + if (key){ + if (typeOf(key) == 'object') defineData = Object.merge(defineData, key); + else defineData[key] = value; + } + this.sets[set] = defineData; + + return this; + }, + + get: function(key, args, _base){ + var value = Object.getFromPath(this.sets, key); + if (value != null){ + var type = typeOf(value); + if (type == 'function') value = value.apply(null, Array.from(args)); + else if (type == 'object') value = Object.clone(value); + return value; + } + + // get value of inherited locales + var index = key.indexOf('.'), + set = index < 0 ? key : key.substr(0, index), + names = (this.inherits.sets[set] || []).combine(this.inherits.locales).include('en-US'); + if (!_base) _base = []; + + for (var i = 0, l = names.length; i < l; i++){ + if (_base.contains(names[i])) continue; + _base.include(names[i]); + + var locale = locales[names[i]]; + if (!locale) continue; + + value = locale.get(key, args, _base); + if (value != null) return value; + } + + return ''; + }, + + inherit: function(names, set){ + names = Array.from(names); + + if (set && !this.inherits.sets[set]) this.inherits.sets[set] = []; + + var l = names.length; + while (l--) (set ? this.inherits.sets[set] : this.inherits.locales).unshift(names[l]); + + return this; + } + +}); + + + +})(); + +/* +--- + +name: Locale.en-US.Date + +description: Date messages for US English. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Locale + +provides: [Locale.en-US.Date] + +... +*/ + +Locale.define('en-US', 'Date', { + + months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'], + months_abbr: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'], + days: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'], + days_abbr: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + + // Culture's date order: MM/DD/YYYY + dateOrder: ['month', 'date', 'year'], + shortDate: '%m/%d/%Y', + shortTime: '%I:%M%p', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 0, + + // Date.Extras + ordinal: function(dayOfMonth){ + // 1st, 2nd, 3rd, etc. + return (dayOfMonth > 3 && dayOfMonth < 21) ? 'th' : ['th', 'st', 'nd', 'rd', 'th'][Math.min(dayOfMonth % 10, 4)]; + }, + + lessThanMinuteAgo: 'less than a minute ago', + minuteAgo: 'about a minute ago', + minutesAgo: '{delta} minutes ago', + hourAgo: 'about an hour ago', + hoursAgo: 'about {delta} hours ago', + dayAgo: '1 day ago', + daysAgo: '{delta} days ago', + weekAgo: '1 week ago', + weeksAgo: '{delta} weeks ago', + monthAgo: '1 month ago', + monthsAgo: '{delta} months ago', + yearAgo: '1 year ago', + yearsAgo: '{delta} years ago', + + lessThanMinuteUntil: 'less than a minute from now', + minuteUntil: 'about a minute from now', + minutesUntil: '{delta} minutes from now', + hourUntil: 'about an hour from now', + hoursUntil: 'about {delta} hours from now', + dayUntil: '1 day from now', + daysUntil: '{delta} days from now', + weekUntil: '1 week from now', + weeksUntil: '{delta} weeks from now', + monthUntil: '1 month from now', + monthsUntil: '{delta} months from now', + yearUntil: '1 year from now', + yearsUntil: '{delta} years from now' + +}); + +/* +--- + +script: Date.js + +name: Date + +description: Extends the Date native object to include methods useful in managing dates. + +license: MIT-style license + +authors: + - Aaron Newton + - Nicholas Barthelemy - https://svn.nbarthelemy.com/date-js/ + - Harald Kirshner - mail [at] digitarald.de; http://digitarald.de + - Scott Kyle - scott [at] appden.com; http://appden.com + +requires: + - Core/Array + - Core/String + - Core/Number + - MooTools.More + - Locale + - Locale.en-US.Date + +provides: [Date] + +... +*/ + +(function(){ + +var Date = this.Date; + +var DateMethods = Date.Methods = { + ms: 'Milliseconds', + year: 'FullYear', + min: 'Minutes', + mo: 'Month', + sec: 'Seconds', + hr: 'Hours' +}; + +['Date', 'Day', 'FullYear', 'Hours', 'Milliseconds', 'Minutes', 'Month', 'Seconds', 'Time', 'TimezoneOffset', + 'Week', 'Timezone', 'GMTOffset', 'DayOfYear', 'LastMonth', 'LastDayOfMonth', 'UTCDate', 'UTCDay', 'UTCFullYear', + 'AMPM', 'Ordinal', 'UTCHours', 'UTCMilliseconds', 'UTCMinutes', 'UTCMonth', 'UTCSeconds', 'UTCMilliseconds'].each(function(method){ + Date.Methods[method.toLowerCase()] = method; +}); + +var pad = function(n, digits, string){ + if (digits == 1) return n; + return n < Math.pow(10, digits - 1) ? (string || '0') + pad(n, digits - 1, string) : n; +}; + +Date.implement({ + + set: function(prop, value){ + prop = prop.toLowerCase(); + var method = DateMethods[prop] && 'set' + DateMethods[prop]; + if (method && this[method]) this[method](value); + return this; + }.overloadSetter(), + + get: function(prop){ + prop = prop.toLowerCase(); + var method = DateMethods[prop] && 'get' + DateMethods[prop]; + if (method && this[method]) return this[method](); + return null; + }.overloadGetter(), + + clone: function(){ + return new Date(this.get('time')); + }, + + increment: function(interval, times){ + interval = interval || 'day'; + times = times != null ? times : 1; + + switch (interval){ + case 'year': + return this.increment('month', times * 12); + case 'month': + var d = this.get('date'); + this.set('date', 1).set('mo', this.get('mo') + times); + return this.set('date', d.min(this.get('lastdayofmonth'))); + case 'week': + return this.increment('day', times * 7); + case 'day': + return this.set('date', this.get('date') + times); + } + + if (!Date.units[interval]) throw new Error(interval + ' is not a supported interval'); + + return this.set('time', this.get('time') + times * Date.units[interval]()); + }, + + decrement: function(interval, times){ + return this.increment(interval, -1 * (times != null ? times : 1)); + }, + + isLeapYear: function(){ + return Date.isLeapYear(this.get('year')); + }, + + clearTime: function(){ + return this.set({hr: 0, min: 0, sec: 0, ms: 0}); + }, + + diff: function(date, resolution){ + if (typeOf(date) == 'string') date = Date.parse(date); + + return ((date - this) / Date.units[resolution || 'day'](3, 3)).round(); // non-leap year, 30-day month + }, + + getLastDayOfMonth: function(){ + return Date.daysInMonth(this.get('mo'), this.get('year')); + }, + + getDayOfYear: function(){ + return (Date.UTC(this.get('year'), this.get('mo'), this.get('date') + 1) + - Date.UTC(this.get('year'), 0, 1)) / Date.units.day(); + }, + + setDay: function(day, firstDayOfWeek){ + if (firstDayOfWeek == null){ + firstDayOfWeek = Date.getMsg('firstDayOfWeek'); + if (firstDayOfWeek === '') firstDayOfWeek = 1; + } + + day = (7 + Date.parseDay(day, true) - firstDayOfWeek) % 7; + var currentDay = (7 + this.get('day') - firstDayOfWeek) % 7; + + return this.increment('day', day - currentDay); + }, + + getWeek: function(firstDayOfWeek){ + if (firstDayOfWeek == null){ + firstDayOfWeek = Date.getMsg('firstDayOfWeek'); + if (firstDayOfWeek === '') firstDayOfWeek = 1; + } + + var date = this, + dayOfWeek = (7 + date.get('day') - firstDayOfWeek) % 7, + dividend = 0, + firstDayOfYear; + + if (firstDayOfWeek == 1){ + // ISO-8601, week belongs to year that has the most days of the week (i.e. has the thursday of the week) + var month = date.get('month'), + startOfWeek = date.get('date') - dayOfWeek; + + if (month == 11 && startOfWeek > 28) return 1; // Week 1 of next year + + if (month == 0 && startOfWeek < -2){ + // Use a date from last year to determine the week + date = new Date(date).decrement('day', dayOfWeek); + dayOfWeek = 0; + } + + firstDayOfYear = new Date(date.get('year'), 0, 1).get('day') || 7; + if (firstDayOfYear > 4) dividend = -7; // First week of the year is not week 1 + } else { + // In other cultures the first week of the year is always week 1 and the last week always 53 or 54. + // Days in the same week can have a different weeknumber if the week spreads across two years. + firstDayOfYear = new Date(date.get('year'), 0, 1).get('day'); + } + + dividend += date.get('dayofyear'); + dividend += 6 - dayOfWeek; // Add days so we calculate the current date's week as a full week + dividend += (7 + firstDayOfYear - firstDayOfWeek) % 7; // Make up for first week of the year not being a full week + + return (dividend / 7); + }, + + getOrdinal: function(day){ + return Date.getMsg('ordinal', day || this.get('date')); + }, + + getTimezone: function(){ + return this.toString() + .replace(/^.*? ([A-Z]{3}).[0-9]{4}.*$/, '$1') + .replace(/^.*?\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\)$/, '$1$2$3'); + }, + + getGMTOffset: function(){ + var off = this.get('timezoneOffset'); + return ((off > 0) ? '-' : '+') + pad((off.abs() / 60).floor(), 2) + pad(off % 60, 2); + }, + + setAMPM: function(ampm){ + ampm = ampm.toUpperCase(); + var hr = this.get('hr'); + if (hr > 11 && ampm == 'AM') return this.decrement('hour', 12); + else if (hr < 12 && ampm == 'PM') return this.increment('hour', 12); + return this; + }, + + getAMPM: function(){ + return (this.get('hr') < 12) ? 'AM' : 'PM'; + }, + + parse: function(str){ + this.set('time', Date.parse(str)); + return this; + }, + + isValid: function(date){ + if (!date) date = this; + return typeOf(date) == 'date' && !isNaN(date.valueOf()); + }, + + format: function(format){ + if (!this.isValid()) return 'invalid date'; + + if (!format) format = '%x %X'; + if (typeof format == 'string') format = formats[format.toLowerCase()] || format; + if (typeof format == 'function') return format(this); + + var d = this; + return format.replace(/%([a-z%])/gi, + function($0, $1){ + switch ($1){ + case 'a': return Date.getMsg('days_abbr')[d.get('day')]; + case 'A': return Date.getMsg('days')[d.get('day')]; + case 'b': return Date.getMsg('months_abbr')[d.get('month')]; + case 'B': return Date.getMsg('months')[d.get('month')]; + case 'c': return d.format('%a %b %d %H:%M:%S %Y'); + case 'd': return pad(d.get('date'), 2); + case 'e': return pad(d.get('date'), 2, ' '); + case 'H': return pad(d.get('hr'), 2); + case 'I': return pad((d.get('hr') % 12) || 12, 2); + case 'j': return pad(d.get('dayofyear'), 3); + case 'k': return pad(d.get('hr'), 2, ' '); + case 'l': return pad((d.get('hr') % 12) || 12, 2, ' '); + case 'L': return pad(d.get('ms'), 3); + case 'm': return pad((d.get('mo') + 1), 2); + case 'M': return pad(d.get('min'), 2); + case 'o': return d.get('ordinal'); + case 'p': return Date.getMsg(d.get('ampm')); + case 's': return Math.round(d / 1000); + case 'S': return pad(d.get('seconds'), 2); + case 'T': return d.format('%H:%M:%S'); + case 'U': return pad(d.get('week'), 2); + case 'w': return d.get('day'); + case 'x': return d.format(Date.getMsg('shortDate')); + case 'X': return d.format(Date.getMsg('shortTime')); + case 'y': return d.get('year').toString().substr(2); + case 'Y': return d.get('year'); + case 'z': return d.get('GMTOffset'); + case 'Z': return d.get('Timezone'); + } + return $1; + } + ); + }, + + toISOString: function(){ + return this.format('iso8601'); + } + +}).alias({ + toJSON: 'toISOString', + compare: 'diff', + strftime: 'format' +}); + +// The day and month abbreviations are standardized, so we cannot use simply %a and %b because they will get localized +var rfcDayAbbr = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'], + rfcMonthAbbr = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; + +var formats = { + db: '%Y-%m-%d %H:%M:%S', + compact: '%Y%m%dT%H%M%S', + 'short': '%d %b %H:%M', + 'long': '%B %d, %Y %H:%M', + rfc822: function(date){ + return rfcDayAbbr[date.get('day')] + date.format(', %d ') + rfcMonthAbbr[date.get('month')] + date.format(' %Y %H:%M:%S %Z'); + }, + rfc2822: function(date){ + return rfcDayAbbr[date.get('day')] + date.format(', %d ') + rfcMonthAbbr[date.get('month')] + date.format(' %Y %H:%M:%S %z'); + }, + iso8601: function(date){ + return ( + date.getUTCFullYear() + '-' + + pad(date.getUTCMonth() + 1, 2) + '-' + + pad(date.getUTCDate(), 2) + 'T' + + pad(date.getUTCHours(), 2) + ':' + + pad(date.getUTCMinutes(), 2) + ':' + + pad(date.getUTCSeconds(), 2) + '.' + + pad(date.getUTCMilliseconds(), 3) + 'Z' + ); + } +}; + +var parsePatterns = [], + nativeParse = Date.parse; + +var parseWord = function(type, word, num){ + var ret = -1, + translated = Date.getMsg(type + 's'); + switch (typeOf(word)){ + case 'object': + ret = translated[word.get(type)]; + break; + case 'number': + ret = translated[word]; + if (!ret) throw new Error('Invalid ' + type + ' index: ' + word); + break; + case 'string': + var match = translated.filter(function(name){ + return this.test(name); + }, new RegExp('^' + word, 'i')); + if (!match.length) throw new Error('Invalid ' + type + ' string'); + if (match.length > 1) throw new Error('Ambiguous ' + type); + ret = match[0]; + } + + return (num) ? translated.indexOf(ret) : ret; +}; + +var startCentury = 1900, + startYear = 70; + +Date.extend({ + + getMsg: function(key, args){ + return Locale.get('Date.' + key, args); + }, + + units: { + ms: Function.from(1), + second: Function.from(1000), + minute: Function.from(60000), + hour: Function.from(3600000), + day: Function.from(86400000), + week: Function.from(608400000), + month: function(month, year){ + var d = new Date; + return Date.daysInMonth(month != null ? month : d.get('mo'), year != null ? year : d.get('year')) * 86400000; + }, + year: function(year){ + year = year || new Date().get('year'); + return Date.isLeapYear(year) ? 31622400000 : 31536000000; + } + }, + + daysInMonth: function(month, year){ + return [31, Date.isLeapYear(year) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]; + }, + + isLeapYear: function(year){ + return ((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0); + }, + + parse: function(from){ + var t = typeOf(from); + if (t == 'number') return new Date(from); + if (t != 'string') return from; + from = from.clean(); + if (!from.length) return null; + + var parsed; + parsePatterns.some(function(pattern){ + var bits = pattern.re.exec(from); + return (bits) ? (parsed = pattern.handler(bits)) : false; + }); + + if (!(parsed && parsed.isValid())){ + parsed = new Date(nativeParse(from)); + if (!(parsed && parsed.isValid())) parsed = new Date(from.toInt()); + } + return parsed; + }, + + parseDay: function(day, num){ + return parseWord('day', day, num); + }, + + parseMonth: function(month, num){ + return parseWord('month', month, num); + }, + + parseUTC: function(value){ + var localDate = new Date(value); + var utcSeconds = Date.UTC( + localDate.get('year'), + localDate.get('mo'), + localDate.get('date'), + localDate.get('hr'), + localDate.get('min'), + localDate.get('sec'), + localDate.get('ms') + ); + return new Date(utcSeconds); + }, + + orderIndex: function(unit){ + return Date.getMsg('dateOrder').indexOf(unit) + 1; + }, + + defineFormat: function(name, format){ + formats[name] = format; + return this; + }, + + + + defineParser: function(pattern){ + parsePatterns.push((pattern.re && pattern.handler) ? pattern : build(pattern)); + return this; + }, + + defineParsers: function(){ + Array.flatten(arguments).each(Date.defineParser); + return this; + }, + + define2DigitYearStart: function(year){ + startYear = year % 100; + startCentury = year - startYear; + return this; + } + +}).extend({ + defineFormats: Date.defineFormat.overloadSetter() +}); + +var regexOf = function(type){ + return new RegExp('(?:' + Date.getMsg(type).map(function(name){ + return name.substr(0, 3); + }).join('|') + ')[a-z]*'); +}; + +var replacers = function(key){ + switch (key){ + case 'T': + return '%H:%M:%S'; + case 'x': // iso8601 covers yyyy-mm-dd, so just check if month is first + return ((Date.orderIndex('month') == 1) ? '%m[-./]%d' : '%d[-./]%m') + '([-./]%y)?'; + case 'X': + return '%H([.:]%M)?([.:]%S([.:]%s)?)? ?%p? ?%z?'; + } + return null; +}; + +var keys = { + d: /[0-2]?[0-9]|3[01]/, + H: /[01]?[0-9]|2[0-3]/, + I: /0?[1-9]|1[0-2]/, + M: /[0-5]?\d/, + s: /\d+/, + o: /[a-z]*/, + p: /[ap]\.?m\.?/, + y: /\d{2}|\d{4}/, + Y: /\d{4}/, + z: /Z|[+-]\d{2}(?::?\d{2})?/ +}; + +keys.m = keys.I; +keys.S = keys.M; + +var currentLanguage; + +var recompile = function(language){ + currentLanguage = language; + + keys.a = keys.A = regexOf('days'); + keys.b = keys.B = regexOf('months'); + + parsePatterns.each(function(pattern, i){ + if (pattern.format) parsePatterns[i] = build(pattern.format); + }); +}; + +var build = function(format){ + if (!currentLanguage) return {format: format}; + + var parsed = []; + var re = (format.source || format) // allow format to be regex + .replace(/%([a-z])/gi, + function($0, $1){ + return replacers($1) || $0; + } + ).replace(/\((?!\?)/g, '(?:') // make all groups non-capturing + .replace(/ (?!\?|\*)/g, ',? ') // be forgiving with spaces and commas + .replace(/%([a-z%])/gi, + function($0, $1){ + var p = keys[$1]; + if (!p) return $1; + parsed.push($1); + return '(' + p.source + ')'; + } + ).replace(/\[a-z\]/gi, '[a-z\\u00c0-\\uffff;\&]'); // handle unicode words + + return { + format: format, + re: new RegExp('^' + re + '$', 'i'), + handler: function(bits){ + bits = bits.slice(1).associate(parsed); + var date = new Date().clearTime(), + year = bits.y || bits.Y; + + if (year != null) handle.call(date, 'y', year); // need to start in the right year + if ('d' in bits) handle.call(date, 'd', 1); + if ('m' in bits || bits.b || bits.B) handle.call(date, 'm', 1); + + for (var key in bits) handle.call(date, key, bits[key]); + return date; + } + }; +}; + +var handle = function(key, value){ + if (!value) return this; + + switch (key){ + case 'a': case 'A': return this.set('day', Date.parseDay(value, true)); + case 'b': case 'B': return this.set('mo', Date.parseMonth(value, true)); + case 'd': return this.set('date', value); + case 'H': case 'I': return this.set('hr', value); + case 'm': return this.set('mo', value - 1); + case 'M': return this.set('min', value); + case 'p': return this.set('ampm', value.replace(/\./g, '')); + case 'S': return this.set('sec', value); + case 's': return this.set('ms', ('0.' + value) * 1000); + case 'w': return this.set('day', value); + case 'Y': return this.set('year', value); + case 'y': + value = +value; + if (value < 100) value += startCentury + (value < startYear ? 100 : 0); + return this.set('year', value); + case 'z': + if (value == 'Z') value = '+00'; + var offset = value.match(/([+-])(\d{2}):?(\d{2})?/); + offset = (offset[1] + '1') * (offset[2] * 60 + (+offset[3] || 0)) + this.getTimezoneOffset(); + return this.set('time', this - offset * 60000); + } + + return this; +}; + +Date.defineParsers( + '%Y([-./]%m([-./]%d((T| )%X)?)?)?', // "1999-12-31", "1999-12-31 11:59pm", "1999-12-31 23:59:59", ISO8601 + '%Y%m%d(T%H(%M%S?)?)?', // "19991231", "19991231T1159", compact + '%x( %X)?', // "12/31", "12.31.99", "12-31-1999", "12/31/2008 11:59 PM" + '%d%o( %b( %Y)?)?( %X)?', // "31st", "31st December", "31 Dec 1999", "31 Dec 1999 11:59pm" + '%b( %d%o)?( %Y)?( %X)?', // Same as above with month and day switched + '%Y %b( %d%o( %X)?)?', // Same as above with year coming first + '%o %b %d %X %z %Y', // "Thu Oct 22 08:11:23 +0000 2009" + '%T', // %H:%M:%S + '%H:%M( ?%p)?' // "11:05pm", "11:05 am" and "11:05" +); + +Locale.addEvent('change', function(language){ + if (Locale.get('Date')) recompile(language); +}).fireEvent('change', Locale.getCurrent()); + +})(); + +/* +--- + +name: Locale.en-US.Form.Validator + +description: Form Validator messages for English. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Locale + +provides: [Locale.en-US.Form.Validator] + +... +*/ + +Locale.define('en-US', 'FormValidator', { + + required: 'This field is required.', + length: 'Please enter {length} characters (you entered {elLength} characters)', + minLength: 'Please enter at least {minLength} characters (you entered {length} characters).', + maxLength: 'Please enter no more than {maxLength} characters (you entered {length} characters).', + integer: 'Please enter an integer in this field. Numbers with decimals (e.g. 1.25) are not permitted.', + numeric: 'Please enter only numeric values in this field (i.e. "1" or "1.1" or "-1" or "-1.1").', + digits: 'Please use numbers and punctuation only in this field (for example, a phone number with dashes or dots is permitted).', + alpha: 'Please use only letters (a-z) within this field. No spaces or other characters are allowed.', + alphanum: 'Please use only letters (a-z) or numbers (0-9) in this field. No spaces or other characters are allowed.', + dateSuchAs: 'Please enter a valid date such as {date}', + dateInFormatMDY: 'Please enter a valid date such as MM/DD/YYYY (i.e. "12/31/1999")', + email: 'Please enter a valid email address. For example "fred@domain.com".', + url: 'Please enter a valid URL such as http://www.example.com.', + currencyDollar: 'Please enter a valid $ amount. For example $100.00 .', + oneRequired: 'Please enter something for at least one of these inputs.', + errorPrefix: 'Error: ', + warningPrefix: 'Warning: ', + + // Form.Validator.Extras + noSpace: 'There can be no spaces in this input.', + reqChkByNode: 'No items are selected.', + requiredChk: 'This field is required.', + reqChkByName: 'Please select a {label}.', + match: 'This field needs to match the {matchName} field', + startDate: 'the start date', + endDate: 'the end date', + currentDate: 'the current date', + afterDate: 'The date should be the same or after {label}.', + beforeDate: 'The date should be the same or before {label}.', + startMonth: 'Please select a start month', + sameMonth: 'These two dates must be in the same month - you must change one or the other.', + creditcard: 'The credit card number entered is invalid. Please check the number and try again. {length} digits entered.' + +}); + +/* +--- + +script: Form.Validator.js + +name: Form.Validator + +description: A css-class based form validation system. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Options + - Core/Events + - Core/Element.Delegation + - Core/Slick.Finder + - Core/Element.Event + - Core/Element.Style + - Core/JSON + - Locale + - Class.Binds + - Date + - Element.Forms + - Locale.en-US.Form.Validator + - Element.Shortcuts + +provides: [Form.Validator, InputValidator, FormValidator.BaseValidators] + +... +*/ +if (!window.Form) window.Form = {}; + +var InputValidator = this.InputValidator = new Class({ + + Implements: [Options], + + options: { + errorMsg: 'Validation failed.', + test: Function.from(true) + }, + + initialize: function(className, options){ + this.setOptions(options); + this.className = className; + }, + + test: function(field, props){ + field = document.id(field); + return (field) ? this.options.test(field, props || this.getProps(field)) : false; + }, + + getError: function(field, props){ + field = document.id(field); + var err = this.options.errorMsg; + if (typeOf(err) == 'function') err = err(field, props || this.getProps(field)); + return err; + }, + + getProps: function(field){ + field = document.id(field); + return (field) ? field.get('validatorProps') : {}; + } + +}); + +Element.Properties.validators = { + + get: function(){ + return (this.get('data-validators') || this.className).clean().split(' '); + } + +}; + +Element.Properties.validatorProps = { + + set: function(props){ + return this.eliminate('$moo:validatorProps').store('$moo:validatorProps', props); + }, + + get: function(props){ + if (props) this.set(props); + if (this.retrieve('$moo:validatorProps')) return this.retrieve('$moo:validatorProps'); + if (this.getProperty('data-validator-properties') || this.getProperty('validatorProps')){ + try { + this.store('$moo:validatorProps', JSON.decode(this.getProperty('validatorProps') || this.getProperty('data-validator-properties'), false)); + }catch(e){ + return {}; + } + } else { + var vals = this.get('validators').filter(function(cls){ + return cls.test(':'); + }); + if (!vals.length){ + this.store('$moo:validatorProps', {}); + } else { + props = {}; + vals.each(function(cls){ + var split = cls.split(':'); + if (split[1]){ + try { + props[split[0]] = JSON.decode(split[1], false); + } catch(e){} + } + }); + this.store('$moo:validatorProps', props); + } + } + return this.retrieve('$moo:validatorProps'); + } + +}; + +Form.Validator = new Class({ + + Implements: [Options, Events], + + options: {/* + onFormValidate: function(isValid, form, event){}, + onElementValidate: function(isValid, field, className, warn){}, + onElementPass: function(field){}, + onElementFail: function(field, validatorsFailed){}, */ + fieldSelectors: 'input, select, textarea', + ignoreHidden: true, + ignoreDisabled: true, + useTitles: false, + evaluateOnSubmit: true, + evaluateFieldsOnBlur: true, + evaluateFieldsOnChange: true, + serial: true, + stopOnFailure: true, + warningPrefix: function(){ + return Form.Validator.getMsg('warningPrefix') || 'Warning: '; + }, + errorPrefix: function(){ + return Form.Validator.getMsg('errorPrefix') || 'Error: '; + } + }, + + initialize: function(form, options){ + this.setOptions(options); + this.element = document.id(form); + this.warningPrefix = Function.from(this.options.warningPrefix)(); + this.errorPrefix = Function.from(this.options.errorPrefix)(); + this._bound = { + onSubmit: this.onSubmit.bind(this), + blurOrChange: function(event, field){ + this.validationMonitor(field, true); + }.bind(this) + }; + this.enable(); + }, + + toElement: function(){ + return this.element; + }, + + getFields: function(){ + return (this.fields = this.element.getElements(this.options.fieldSelectors)); + }, + + enable: function(){ + this.element.store('validator', this); + if (this.options.evaluateOnSubmit) this.element.addEvent('submit', this._bound.onSubmit); + if (this.options.evaluateFieldsOnBlur){ + this.element.addEvent('blur:relay(input,select,textarea)', this._bound.blurOrChange); + } + if (this.options.evaluateFieldsOnChange){ + this.element.addEvent('change:relay(input,select,textarea)', this._bound.blurOrChange); + } + }, + + disable: function(){ + this.element.eliminate('validator'); + this.element.removeEvents({ + submit: this._bound.onSubmit, + 'blur:relay(input,select,textarea)': this._bound.blurOrChange, + 'change:relay(input,select,textarea)': this._bound.blurOrChange + }); + }, + + validationMonitor: function(){ + clearTimeout(this.timer); + this.timer = this.validateField.delay(50, this, arguments); + }, + + onSubmit: function(event){ + if (this.validate(event)) this.reset(); + }, + + reset: function(){ + this.getFields().each(this.resetField, this); + return this; + }, + + validate: function(event){ + var result = this.getFields().map(function(field){ + return this.validateField(field, true); + }, this).every(function(v){ + return v; + }); + this.fireEvent('formValidate', [result, this.element, event]); + if (this.options.stopOnFailure && !result && event) event.preventDefault(); + return result; + }, + + validateField: function(field, force){ + if (this.paused) return true; + field = document.id(field); + var passed = !field.hasClass('validation-failed'); + var failed, warned; + if (this.options.serial && !force){ + failed = this.element.getElement('.validation-failed'); + warned = this.element.getElement('.warning'); + } + if (field && (!failed || force || field.hasClass('validation-failed') || (failed && !this.options.serial))){ + var validationTypes = field.get('validators'); + var validators = validationTypes.some(function(cn){ + return this.getValidator(cn); + }, this); + var validatorsFailed = []; + validationTypes.each(function(className){ + if (className && !this.test(className, field)) validatorsFailed.include(className); + }, this); + passed = validatorsFailed.length === 0; + if (validators && !this.hasValidator(field, 'warnOnly')){ + if (passed){ + field.addClass('validation-passed').removeClass('validation-failed'); + this.fireEvent('elementPass', [field]); + } else { + field.addClass('validation-failed').removeClass('validation-passed'); + this.fireEvent('elementFail', [field, validatorsFailed]); + } + } + if (!warned){ + var warnings = validationTypes.some(function(cn){ + if (cn.test('^warn')) + return this.getValidator(cn.replace(/^warn-/,'')); + else return null; + }, this); + field.removeClass('warning'); + var warnResult = validationTypes.map(function(cn){ + if (cn.test('^warn')) + return this.test(cn.replace(/^warn-/,''), field, true); + else return null; + }, this); + } + } + return passed; + }, + + test: function(className, field, warn){ + field = document.id(field); + if ((this.options.ignoreHidden && !field.isVisible()) || (this.options.ignoreDisabled && field.get('disabled'))) return true; + var validator = this.getValidator(className); + if (warn != null) warn = false; + if (this.hasValidator(field, 'warnOnly')) warn = true; + var isValid = field.hasClass('ignoreValidation') || (validator ? validator.test(field) : true); + if (validator) this.fireEvent('elementValidate', [isValid, field, className, warn]); + if (warn) return true; + return isValid; + }, + + hasValidator: function(field, value){ + return field.get('validators').contains(value); + }, + + resetField: function(field){ + field = document.id(field); + if (field){ + field.get('validators').each(function(className){ + if (className.test('^warn-')) className = className.replace(/^warn-/, ''); + field.removeClass('validation-failed'); + field.removeClass('warning'); + field.removeClass('validation-passed'); + }, this); + } + return this; + }, + + stop: function(){ + this.paused = true; + return this; + }, + + start: function(){ + this.paused = false; + return this; + }, + + ignoreField: function(field, warn){ + field = document.id(field); + if (field){ + this.enforceField(field); + if (warn) field.addClass('warnOnly'); + else field.addClass('ignoreValidation'); + } + return this; + }, + + enforceField: function(field){ + field = document.id(field); + if (field) field.removeClass('warnOnly').removeClass('ignoreValidation'); + return this; + } + +}); + +Form.Validator.getMsg = function(key){ + return Locale.get('FormValidator.' + key); +}; + +Form.Validator.adders = { + + validators:{}, + + add : function(className, options){ + this.validators[className] = new InputValidator(className, options); + //if this is a class (this method is used by instances of Form.Validator and the Form.Validator namespace) + //extend these validators into it + //this allows validators to be global and/or per instance + if (!this.initialize){ + this.implement({ + validators: this.validators + }); + } + }, + + addAllThese : function(validators){ + Array.from(validators).each(function(validator){ + this.add(validator[0], validator[1]); + }, this); + }, + + getValidator: function(className){ + return this.validators[className.split(':')[0]]; + } + +}; + +Object.append(Form.Validator, Form.Validator.adders); + +Form.Validator.implement(Form.Validator.adders); + +Form.Validator.add('IsEmpty', { + + errorMsg: false, + test: function(element){ + if (element.type == 'select-one' || element.type == 'select') + return !(element.selectedIndex >= 0 && element.options[element.selectedIndex].value != ''); + else + return ((element.get('value') == null) || (element.get('value').length == 0)); + } + +}); + +Form.Validator.addAllThese([ + + ['required', { + errorMsg: function(){ + return Form.Validator.getMsg('required'); + }, + test: function(element){ + return !Form.Validator.getValidator('IsEmpty').test(element); + } + }], + + ['length', { + errorMsg: function(element, props){ + if (typeOf(props.length) != 'null') + return Form.Validator.getMsg('length').substitute({length: props.length, elLength: element.get('value').length}); + else return ''; + }, + test: function(element, props){ + if (typeOf(props.length) != 'null') return (element.get('value').length == props.length || element.get('value').length == 0); + else return true; + } + }], + + ['minLength', { + errorMsg: function(element, props){ + if (typeOf(props.minLength) != 'null') + return Form.Validator.getMsg('minLength').substitute({minLength: props.minLength, length: element.get('value').length}); + else return ''; + }, + test: function(element, props){ + if (typeOf(props.minLength) != 'null') return (element.get('value').length >= (props.minLength || 0)); + else return true; + } + }], + + ['maxLength', { + errorMsg: function(element, props){ + //props is {maxLength:10} + if (typeOf(props.maxLength) != 'null') + return Form.Validator.getMsg('maxLength').substitute({maxLength: props.maxLength, length: element.get('value').length}); + else return ''; + }, + test: function(element, props){ + return element.get('value').length <= (props.maxLength || 10000); + } + }], + + ['validate-integer', { + errorMsg: Form.Validator.getMsg.pass('integer'), + test: function(element){ + return Form.Validator.getValidator('IsEmpty').test(element) || (/^(-?[1-9]\d*|0)$/).test(element.get('value')); + } + }], + + ['validate-numeric', { + errorMsg: Form.Validator.getMsg.pass('numeric'), + test: function(element){ + return Form.Validator.getValidator('IsEmpty').test(element) || + (/^-?(?:0$0(?=\d*\.)|[1-9]|0)\d*(\.\d+)?$/).test(element.get('value')); + } + }], + + ['validate-digits', { + errorMsg: Form.Validator.getMsg.pass('digits'), + test: function(element){ + return Form.Validator.getValidator('IsEmpty').test(element) || (/^[\d() .:\-\+#]+$/.test(element.get('value'))); + } + }], + + ['validate-alpha', { + errorMsg: Form.Validator.getMsg.pass('alpha'), + test: function(element){ + return Form.Validator.getValidator('IsEmpty').test(element) || (/^[a-zA-Z]+$/).test(element.get('value')); + } + }], + + ['validate-alphanum', { + errorMsg: Form.Validator.getMsg.pass('alphanum'), + test: function(element){ + return Form.Validator.getValidator('IsEmpty').test(element) || !(/\W/).test(element.get('value')); + } + }], + + ['validate-date', { + errorMsg: function(element, props){ + if (Date.parse){ + var format = props.dateFormat || '%x'; + return Form.Validator.getMsg('dateSuchAs').substitute({date: new Date().format(format)}); + } else { + return Form.Validator.getMsg('dateInFormatMDY'); + } + }, + test: function(element, props){ + if (Form.Validator.getValidator('IsEmpty').test(element)) return true; + var dateLocale = Locale.get('Date'), + dateNouns = new RegExp([dateLocale.days, dateLocale.days_abbr, dateLocale.months, dateLocale.months_abbr, dateLocale.AM, dateLocale.PM].flatten().join('|'), 'i'), + value = element.get('value'), + wordsInValue = value.match(/[a-z]+/gi); + + if (wordsInValue && !wordsInValue.every(dateNouns.exec, dateNouns)) return false; + + var date = Date.parse(value); + if (!date) return false; + + var format = props.dateFormat || '%x', + formatted = date.format(format); + if (formatted != 'invalid date') element.set('value', formatted); + return date.isValid(); + } + }], + + ['validate-email', { + errorMsg: Form.Validator.getMsg.pass('email'), + test: function(element){ + /* + var chars = "[a-z0-9!#$%&'*+/=?^_`{|}~-]", + local = '(?:' + chars + '\\.?){0,63}' + chars, + + label = '[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?', + hostname = '(?:' + label + '\\.)*' + label; + + octet = '(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)', + ipv4 = '\\[(?:' + octet + '\\.){3}' + octet + '\\]', + + domain = '(?:' + hostname + '|' + ipv4 + ')'; + + var regex = new RegExp('^' + local + '@' + domain + '$', 'i'); + */ + return Form.Validator.getValidator('IsEmpty').test(element) || (/^(?:[a-z0-9!#$%&'*+\/=?^_`{|}~-]\.?){0,63}[a-z0-9!#$%&'*+\/=?^_`{|}~-]@(?:(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)*[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\])$/i).test(element.get('value')); + } + }], + + ['validate-url', { + errorMsg: Form.Validator.getMsg.pass('url'), + test: function(element){ + return Form.Validator.getValidator('IsEmpty').test(element) || (/^(https?|ftp|rmtp|mms):\/\/(([A-Z0-9][A-Z0-9_-]*)(\.[A-Z0-9][A-Z0-9_-]*)+)(:(\d+))?\/?/i).test(element.get('value')); + } + }], + + ['validate-currency-dollar', { + errorMsg: Form.Validator.getMsg.pass('currencyDollar'), + test: function(element){ + return Form.Validator.getValidator('IsEmpty').test(element) || (/^\$?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(element.get('value')); + } + }], + + ['validate-one-required', { + errorMsg: Form.Validator.getMsg.pass('oneRequired'), + test: function(element, props){ + var p = document.id(props['validate-one-required']) || element.getParent(props['validate-one-required']); + return p.getElements('input').some(function(el){ + if (['checkbox', 'radio'].contains(el.get('type'))) return el.get('checked'); + return el.get('value'); + }); + } + }] + +]); + +Element.Properties.validator = { + + set: function(options){ + this.get('validator').setOptions(options); + }, + + get: function(){ + var validator = this.retrieve('validator'); + if (!validator){ + validator = new Form.Validator(this); + this.store('validator', validator); + } + return validator; + } + +}; + +Element.implement({ + + validate: function(options){ + if (options) this.set('validator', options); + return this.get('validator').validate(); + } + +}); + + + + + + +/* +--- + +script: Form.Validator.Extras.js + +name: Form.Validator.Extras + +description: Additional validators for the Form.Validator class. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Form.Validator + +provides: [Form.Validator.Extras] + +... +*/ +Form.Validator.addAllThese([ + + ['validate-enforce-oncheck', { + test: function(element, props){ + var fv = element.getParent('form').retrieve('validator'); + if (!fv) return true; + (props.toEnforce || document.id(props.enforceChildrenOf).getElements('input, select, textarea')).map(function(item){ + if (element.checked){ + fv.enforceField(item); + } else { + fv.ignoreField(item); + fv.resetField(item); + } + }); + return true; + } + }], + + ['validate-ignore-oncheck', { + test: function(element, props){ + var fv = element.getParent('form').retrieve('validator'); + if (!fv) return true; + (props.toIgnore || document.id(props.ignoreChildrenOf).getElements('input, select, textarea')).each(function(item){ + if (element.checked){ + fv.ignoreField(item); + fv.resetField(item); + } else { + fv.enforceField(item); + } + }); + return true; + } + }], + + ['validate-nospace', { + errorMsg: function(){ + return Form.Validator.getMsg('noSpace'); + }, + test: function(element, props){ + return !element.get('value').test(/\s/); + } + }], + + ['validate-toggle-oncheck', { + test: function(element, props){ + var fv = element.getParent('form').retrieve('validator'); + if (!fv) return true; + var eleArr = props.toToggle || document.id(props.toToggleChildrenOf).getElements('input, select, textarea'); + if (!element.checked){ + eleArr.each(function(item){ + fv.ignoreField(item); + fv.resetField(item); + }); + } else { + eleArr.each(function(item){ + fv.enforceField(item); + }); + } + return true; + } + }], + + ['validate-reqchk-bynode', { + errorMsg: function(){ + return Form.Validator.getMsg('reqChkByNode'); + }, + test: function(element, props){ + return (document.id(props.nodeId).getElements(props.selector || 'input[type=checkbox], input[type=radio]')).some(function(item){ + return item.checked; + }); + } + }], + + ['validate-required-check', { + errorMsg: function(element, props){ + return props.useTitle ? element.get('title') : Form.Validator.getMsg('requiredChk'); + }, + test: function(element, props){ + return !!element.checked; + } + }], + + ['validate-reqchk-byname', { + errorMsg: function(element, props){ + return Form.Validator.getMsg('reqChkByName').substitute({label: props.label || element.get('type')}); + }, + test: function(element, props){ + var grpName = props.groupName || element.get('name'); + var oneCheckedItem = $$(document.getElementsByName(grpName)).some(function(item, index){ + return item.checked; + }); + var fv = element.getParent('form').retrieve('validator'); + if (oneCheckedItem && fv) fv.resetField(element); + return oneCheckedItem; + } + }], + + ['validate-match', { + errorMsg: function(element, props){ + return Form.Validator.getMsg('match').substitute({matchName: props.matchName || document.id(props.matchInput).get('name')}); + }, + test: function(element, props){ + var eleVal = element.get('value'); + var matchVal = document.id(props.matchInput) && document.id(props.matchInput).get('value'); + return eleVal && matchVal ? eleVal == matchVal : true; + } + }], + + ['validate-after-date', { + errorMsg: function(element, props){ + return Form.Validator.getMsg('afterDate').substitute({ + label: props.afterLabel || (props.afterElement ? Form.Validator.getMsg('startDate') : Form.Validator.getMsg('currentDate')) + }); + }, + test: function(element, props){ + var start = document.id(props.afterElement) ? Date.parse(document.id(props.afterElement).get('value')) : new Date(); + var end = Date.parse(element.get('value')); + return end && start ? end >= start : true; + } + }], + + ['validate-before-date', { + errorMsg: function(element, props){ + return Form.Validator.getMsg('beforeDate').substitute({ + label: props.beforeLabel || (props.beforeElement ? Form.Validator.getMsg('endDate') : Form.Validator.getMsg('currentDate')) + }); + }, + test: function(element, props){ + var start = Date.parse(element.get('value')); + var end = document.id(props.beforeElement) ? Date.parse(document.id(props.beforeElement).get('value')) : new Date(); + return end && start ? end >= start : true; + } + }], + + ['validate-custom-required', { + errorMsg: function(){ + return Form.Validator.getMsg('required'); + }, + test: function(element, props){ + return element.get('value') != props.emptyValue; + } + }], + + ['validate-same-month', { + errorMsg: function(element, props){ + var startMo = document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value'); + var eleVal = element.get('value'); + if (eleVal != '') return Form.Validator.getMsg(startMo ? 'sameMonth' : 'startMonth'); + }, + test: function(element, props){ + var d1 = Date.parse(element.get('value')); + var d2 = Date.parse(document.id(props.sameMonthAs) && document.id(props.sameMonthAs).get('value')); + return d1 && d2 ? d1.format('%B') == d2.format('%B') : true; + } + }], + + + ['validate-cc-num', { + errorMsg: function(element){ + var ccNum = element.get('value').replace(/[^0-9]/g, ''); + return Form.Validator.getMsg('creditcard').substitute({length: ccNum.length}); + }, + test: function(element){ + // required is a different test + if (Form.Validator.getValidator('IsEmpty').test(element)) return true; + + // Clean number value + var ccNum = element.get('value'); + ccNum = ccNum.replace(/[^0-9]/g, ''); + + var valid_type = false; + + if (ccNum.test(/^4[0-9]{12}([0-9]{3})?$/)) valid_type = 'Visa'; + else if (ccNum.test(/^5[1-5]([0-9]{14})$/)) valid_type = 'Master Card'; + else if (ccNum.test(/^3[47][0-9]{13}$/)) valid_type = 'American Express'; + else if (ccNum.test(/^6(?:011|5[0-9]{2})[0-9]{12}$/)) valid_type = 'Discover'; + else if (ccNum.test(/^3(?:0[0-5]|[68][0-9])[0-9]{11}$/)) valid_type = 'Diners Club'; + + if (valid_type){ + var sum = 0; + var cur = 0; + + for (var i=ccNum.length-1; i>=0; --i){ + cur = ccNum.charAt(i).toInt(); + if (cur == 0) continue; + + if ((ccNum.length-i) % 2 == 0) cur += cur; + if (cur > 9){ + cur = cur.toString().charAt(0).toInt() + cur.toString().charAt(1).toInt(); + } + + sum += cur; + } + if ((sum % 10) == 0) return true; + } + + var chunks = ''; + while (ccNum != ''){ + chunks += ' ' + ccNum.substr(0,4); + ccNum = ccNum.substr(4); + } + + element.getParent('form').retrieve('validator').ignoreField(element); + element.set('value', chunks.clean()); + element.getParent('form').retrieve('validator').enforceField(element); + return false; + } + }] + + +]); + +/* +--- + +script: Form.Validator.Inline.js + +name: Form.Validator.Inline + +description: Extends Form.Validator to add inline messages. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Form.Validator + +provides: [Form.Validator.Inline] + +... +*/ + +Form.Validator.Inline = new Class({ + + Extends: Form.Validator, + + options: { + showError: function(errorElement){ + if (errorElement.reveal) errorElement.reveal(); + else errorElement.setStyle('display', 'block'); + }, + hideError: function(errorElement){ + if (errorElement.dissolve) errorElement.dissolve(); + else errorElement.setStyle('display', 'none'); + }, + scrollToErrorsOnSubmit: true, + scrollToErrorsOnBlur: false, + scrollToErrorsOnChange: false, + scrollFxOptions: { + transition: 'quad:out', + offset: { + y: -20 + } + } + }, + + initialize: function(form, options){ + this.parent(form, options); + this.addEvent('onElementValidate', function(isValid, field, className, warn){ + var validator = this.getValidator(className); + if (!isValid && validator.getError(field)){ + if (warn) field.addClass('warning'); + var advice = this.makeAdvice(className, field, validator.getError(field), warn); + this.insertAdvice(advice, field); + this.showAdvice(className, field); + } else { + this.hideAdvice(className, field); + } + }); + }, + + makeAdvice: function(className, field, error, warn){ + var errorMsg = (warn) ? this.warningPrefix : this.errorPrefix; + errorMsg += (this.options.useTitles) ? field.title || error:error; + var cssClass = (warn) ? 'warning-advice' : 'validation-advice'; + var advice = this.getAdvice(className, field); + if (advice){ + advice = advice.set('html', errorMsg); + } else { + advice = new Element('div', { + html: errorMsg, + styles: { display: 'none' }, + id: 'advice-' + className.split(':')[0] + '-' + this.getFieldId(field) + }).addClass(cssClass); + } + field.store('$moo:advice-' + className, advice); + return advice; + }, + + getFieldId : function(field){ + return field.id ? field.id : field.id = 'input_' + field.name; + }, + + showAdvice: function(className, field){ + var advice = this.getAdvice(className, field); + if ( + advice && + !field.retrieve('$moo:' + this.getPropName(className)) && + ( + advice.getStyle('display') == 'none' || + advice.getStyle('visibility') == 'hidden' || + advice.getStyle('opacity') == 0 + ) + ){ + field.store('$moo:' + this.getPropName(className), true); + this.options.showError(advice); + this.fireEvent('showAdvice', [field, advice, className]); + } + }, + + hideAdvice: function(className, field){ + var advice = this.getAdvice(className, field); + if (advice && field.retrieve('$moo:' + this.getPropName(className))){ + field.store('$moo:' + this.getPropName(className), false); + this.options.hideError(advice); + this.fireEvent('hideAdvice', [field, advice, className]); + } + }, + + getPropName: function(className){ + return 'advice' + className; + }, + + resetField: function(field){ + field = document.id(field); + if (!field) return this; + this.parent(field); + field.get('validators').each(function(className){ + this.hideAdvice(className, field); + }, this); + return this; + }, + + getAllAdviceMessages: function(field, force){ + var advice = []; + if (field.hasClass('ignoreValidation') && !force) return advice; + var validators = field.get('validators').some(function(cn){ + var warner = cn.test('^warn-') || field.hasClass('warnOnly'); + if (warner) cn = cn.replace(/^warn-/, ''); + var validator = this.getValidator(cn); + if (!validator) return; + advice.push({ + message: validator.getError(field), + warnOnly: warner, + passed: validator.test(), + validator: validator + }); + }, this); + return advice; + }, + + getAdvice: function(className, field){ + return field.retrieve('$moo:advice-' + className); + }, + + insertAdvice: function(advice, field){ + //Check for error position prop + var props = field.get('validatorProps'); + //Build advice + if (!props.msgPos || !document.id(props.msgPos)){ + if (field.type && field.type.toLowerCase() == 'radio') field.getParent().adopt(advice); + else advice.inject(document.id(field), 'after'); + } else { + document.id(props.msgPos).grab(advice); + } + }, + + validateField: function(field, force, scroll){ + var result = this.parent(field, force); + if (((this.options.scrollToErrorsOnSubmit && scroll == null) || scroll) && !result){ + var failed = document.id(this).getElement('.validation-failed'); + var par = document.id(this).getParent(); + while (par != document.body && par.getScrollSize().y == par.getSize().y){ + par = par.getParent(); + } + var fx = par.retrieve('$moo:fvScroller'); + if (!fx && window.Fx && Fx.Scroll){ + fx = new Fx.Scroll(par, this.options.scrollFxOptions); + par.store('$moo:fvScroller', fx); + } + if (failed){ + if (fx) fx.toElement(failed); + else par.scrollTo(par.getScroll().x, failed.getPosition(par).y - 20); + } + } + return result; + }, + + watchFields: function(fields){ + fields.each(function(el){ + if (this.options.evaluateFieldsOnBlur){ + el.addEvent('blur', this.validationMonitor.pass([el, false, this.options.scrollToErrorsOnBlur], this)); + } + if (this.options.evaluateFieldsOnChange){ + el.addEvent('change', this.validationMonitor.pass([el, true, this.options.scrollToErrorsOnChange], this)); + } + }, this); + } + +}); + +/* +--- + +script: OverText.js + +name: OverText + +description: Shows text over an input that disappears when the user clicks into it. The text remains hidden if the user adds a value. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Options + - Core/Events + - Core/Element.Event + - Class.Binds + - Class.Occlude + - Element.Position + - Element.Shortcuts + +provides: [OverText] + +... +*/ + +var OverText = new Class({ + + Implements: [Options, Events, Class.Occlude], + + Binds: ['reposition', 'assert', 'focus', 'hide'], + + options: {/* + textOverride: null, + onFocus: function(){}, + onTextHide: function(textEl, inputEl){}, + onTextShow: function(textEl, inputEl){}, */ + element: 'label', + labelClass: 'overTxtLabel', + positionOptions: { + position: 'upperLeft', + edge: 'upperLeft', + offset: { + x: 4, + y: 2 + } + }, + poll: false, + pollInterval: 250, + wrap: false + }, + + property: 'OverText', + + initialize: function(element, options){ + element = this.element = document.id(element); + + if (this.occlude()) return this.occluded; + this.setOptions(options); + + this.attach(element); + OverText.instances.push(this); + + if (this.options.poll) this.poll(); + }, + + toElement: function(){ + return this.element; + }, + + attach: function(){ + var element = this.element, + options = this.options, + value = options.textOverride || element.get('alt') || element.get('title'); + + if (!value) return this; + + var text = this.text = new Element(options.element, { + 'class': options.labelClass, + styles: { + lineHeight: 'normal', + position: 'absolute', + cursor: 'text' + }, + html: value, + events: { + click: this.hide.pass(options.element == 'label', this) + } + }).inject(element, 'after'); + + if (options.element == 'label'){ + if (!element.get('id')) element.set('id', 'input_' + String.uniqueID()); + text.set('for', element.get('id')); + } + + if (options.wrap){ + this.textHolder = new Element('div.overTxtWrapper', { + styles: { + lineHeight: 'normal', + position: 'relative' + } + }).grab(text).inject(element, 'before'); + } + + return this.enable(); + }, + + destroy: function(){ + this.element.eliminate(this.property); // Class.Occlude storage + this.disable(); + if (this.text) this.text.destroy(); + if (this.textHolder) this.textHolder.destroy(); + return this; + }, + + disable: function(){ + this.element.removeEvents({ + focus: this.focus, + blur: this.assert, + change: this.assert + }); + window.removeEvent('resize', this.reposition); + this.hide(true, true); + return this; + }, + + enable: function(){ + this.element.addEvents({ + focus: this.focus, + blur: this.assert, + change: this.assert + }); + window.addEvent('resize', this.reposition); + this.reposition(); + return this; + }, + + wrap: function(){ + if (this.options.element == 'label'){ + if (!this.element.get('id')) this.element.set('id', 'input_' + String.uniqueID()); + this.text.set('for', this.element.get('id')); + } + }, + + startPolling: function(){ + this.pollingPaused = false; + return this.poll(); + }, + + poll: function(stop){ + //start immediately + //pause on focus + //resumeon blur + if (this.poller && !stop) return this; + if (stop){ + clearInterval(this.poller); + } else { + this.poller = (function(){ + if (!this.pollingPaused) this.assert(true); + }).periodical(this.options.pollInterval, this); + } + + return this; + }, + + stopPolling: function(){ + this.pollingPaused = true; + return this.poll(true); + }, + + focus: function(){ + if (this.text && (!this.text.isDisplayed() || this.element.get('disabled'))) return this; + return this.hide(); + }, + + hide: function(suppressFocus, force){ + if (this.text && (this.text.isDisplayed() && (!this.element.get('disabled') || force))){ + this.text.hide(); + this.fireEvent('textHide', [this.text, this.element]); + this.pollingPaused = true; + if (!suppressFocus){ + try { + this.element.fireEvent('focus'); + this.element.focus(); + } catch(e){} //IE barfs if you call focus on hidden elements + } + } + return this; + }, + + show: function(){ + if (document.id(this.text) && !this.text.isDisplayed()){ + this.text.show(); + this.reposition(); + this.fireEvent('textShow', [this.text, this.element]); + this.pollingPaused = false; + } + return this; + }, + + test: function(){ + return !this.element.get('value'); + }, + + assert: function(suppressFocus){ + return this[this.test() ? 'show' : 'hide'](suppressFocus); + }, + + reposition: function(){ + this.assert(true); + if (!this.element.isVisible()) return this.stopPolling().hide(); + if (this.text && this.test()){ + this.text.position(Object.merge(this.options.positionOptions, { + relativeTo: this.element + })); + } + return this; + } + +}); + +OverText.instances = []; + +Object.append(OverText, { + + each: function(fn){ + return OverText.instances.each(function(ot, i){ + if (ot.element && ot.text) fn.call(OverText, ot, i); + }); + }, + + update: function(){ + + return OverText.each(function(ot){ + return ot.reposition(); + }); + + }, + + hideAll: function(){ + + return OverText.each(function(ot){ + return ot.hide(true, true); + }); + + }, + + showAll: function(){ + return OverText.each(function(ot){ + return ot.show(); + }); + } + +}); + + +/* +--- + +script: Fx.Elements.js + +name: Fx.Elements + +description: Effect to change any number of CSS properties of any number of Elements. + +license: MIT-style license + +authors: + - Valerio Proietti + +requires: + - Core/Fx.CSS + - MooTools.More + +provides: [Fx.Elements] + +... +*/ + +Fx.Elements = new Class({ + + Extends: Fx.CSS, + + initialize: function(elements, options){ + this.elements = this.subject = $$(elements); + this.parent(options); + }, + + compute: function(from, to, delta){ + var now = {}; + + for (var i in from){ + var iFrom = from[i], iTo = to[i], iNow = now[i] = {}; + for (var p in iFrom) iNow[p] = this.parent(iFrom[p], iTo[p], delta); + } + + return now; + }, + + set: function(now){ + for (var i in now){ + if (!this.elements[i]) continue; + + var iNow = now[i]; + for (var p in iNow) this.render(this.elements[i], p, iNow[p], this.options.unit); + } + + return this; + }, + + start: function(obj){ + if (!this.check(obj)) return this; + var from = {}, to = {}; + + for (var i in obj){ + if (!this.elements[i]) continue; + + var iProps = obj[i], iFrom = from[i] = {}, iTo = to[i] = {}; + + for (var p in iProps){ + var parsed = this.prepare(this.elements[i], p, iProps[p]); + iFrom[p] = parsed.from; + iTo[p] = parsed.to; + } + } + + return this.parent(from, to); + } + +}); + +/* +--- + +script: Fx.Accordion.js + +name: Fx.Accordion + +description: An Fx.Elements extension which allows you to easily create accordion type controls. + +license: MIT-style license + +authors: + - Valerio Proietti + +requires: + - Core/Element.Event + - Fx.Elements + +provides: [Fx.Accordion] + +... +*/ + +Fx.Accordion = new Class({ + + Extends: Fx.Elements, + + options: {/* + onActive: function(toggler, section){}, + onBackground: function(toggler, section){},*/ + fixedHeight: false, + fixedWidth: false, + display: 0, + show: false, + height: true, + width: false, + opacity: true, + alwaysHide: false, + trigger: 'click', + initialDisplayFx: true, + resetHeight: true + }, + + initialize: function(){ + var defined = function(obj){ + return obj != null; + }; + + var params = Array.link(arguments, { + 'container': Type.isElement, //deprecated + 'options': Type.isObject, + 'togglers': defined, + 'elements': defined + }); + this.parent(params.elements, params.options); + + var options = this.options, + togglers = this.togglers = $$(params.togglers); + + this.previous = -1; + this.internalChain = new Chain(); + + if (options.alwaysHide) this.options.link = 'chain'; + + if (options.show || this.options.show === 0){ + options.display = false; + this.previous = options.show; + } + + if (options.start){ + options.display = false; + options.show = false; + } + + var effects = this.effects = {}; + + if (options.opacity) effects.opacity = 'fullOpacity'; + if (options.width) effects.width = options.fixedWidth ? 'fullWidth' : 'offsetWidth'; + if (options.height) effects.height = options.fixedHeight ? 'fullHeight' : 'scrollHeight'; + + for (var i = 0, l = togglers.length; i < l; i++) this.addSection(togglers[i], this.elements[i]); + + this.elements.each(function(el, i){ + if (options.show === i){ + this.fireEvent('active', [togglers[i], el]); + } else { + for (var fx in effects) el.setStyle(fx, 0); + } + }, this); + + if (options.display || options.display === 0 || options.initialDisplayFx === false){ + this.display(options.display, options.initialDisplayFx); + } + + if (options.fixedHeight !== false) options.resetHeight = false; + this.addEvent('complete', this.internalChain.callChain.bind(this.internalChain)); + }, + + addSection: function(toggler, element){ + toggler = document.id(toggler); + element = document.id(element); + this.togglers.include(toggler); + this.elements.include(element); + + var togglers = this.togglers, + options = this.options, + test = togglers.contains(toggler), + idx = togglers.indexOf(toggler), + displayer = this.display.pass(idx, this); + + toggler.store('accordion:display', displayer) + .addEvent(options.trigger, displayer); + + if (options.height) element.setStyles({'padding-top': 0, 'border-top': 'none', 'padding-bottom': 0, 'border-bottom': 'none'}); + if (options.width) element.setStyles({'padding-left': 0, 'border-left': 'none', 'padding-right': 0, 'border-right': 'none'}); + + element.fullOpacity = 1; + if (options.fixedWidth) element.fullWidth = options.fixedWidth; + if (options.fixedHeight) element.fullHeight = options.fixedHeight; + element.setStyle('overflow', 'hidden'); + + if (!test) for (var fx in this.effects){ + element.setStyle(fx, 0); + } + return this; + }, + + removeSection: function(toggler, displayIndex){ + var togglers = this.togglers, + idx = togglers.indexOf(toggler), + element = this.elements[idx]; + + var remover = function(){ + togglers.erase(toggler); + this.elements.erase(element); + this.detach(toggler); + }.bind(this); + + if (this.now == idx || displayIndex != null){ + this.display(displayIndex != null ? displayIndex : (idx - 1 >= 0 ? idx - 1 : 0)).chain(remover); + } else { + remover(); + } + return this; + }, + + detach: function(toggler){ + var remove = function(toggler){ + toggler.removeEvent(this.options.trigger, toggler.retrieve('accordion:display')); + }.bind(this); + + if (!toggler) this.togglers.each(remove); + else remove(toggler); + return this; + }, + + display: function(index, useFx){ + if (!this.check(index, useFx)) return this; + + var obj = {}, + elements = this.elements, + options = this.options, + effects = this.effects; + + if (useFx == null) useFx = true; + if (typeOf(index) == 'element') index = elements.indexOf(index); + if (index == this.current && !options.alwaysHide) return this; + + if (options.resetHeight){ + var prev = elements[this.current]; + if (prev && !this.selfHidden){ + for (var fx in effects) prev.setStyle(fx, prev[effects[fx]]); + } + } + + if ((this.timer && options.link == 'chain') || (index === this.current && !options.alwaysHide)) return this; + + if (this.current != null) this.previous = this.current; + this.current = index; + this.selfHidden = false; + + elements.each(function(el, i){ + obj[i] = {}; + var hide; + if (i != index){ + hide = true; + } else if (options.alwaysHide && ((el.offsetHeight > 0 && options.height) || el.offsetWidth > 0 && options.width)){ + hide = true; + this.selfHidden = true; + } + this.fireEvent(hide ? 'background' : 'active', [this.togglers[i], el]); + for (var fx in effects) obj[i][fx] = hide ? 0 : el[effects[fx]]; + if (!useFx && !hide && options.resetHeight) obj[i].height = 'auto'; + }, this); + + this.internalChain.clearChain(); + this.internalChain.chain(function(){ + if (options.resetHeight && !this.selfHidden){ + var el = elements[index]; + if (el) el.setStyle('height', 'auto'); + } + }.bind(this)); + + return useFx ? this.start(obj) : this.set(obj).internalChain.callChain(); + } + +}); + + + +/* +--- + +script: Fx.Move.js + +name: Fx.Move + +description: Defines Fx.Move, a class that works with Element.Position.js to transition an element from one location to another. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Fx.Morph + - Element.Position + +provides: [Fx.Move] + +... +*/ + +Fx.Move = new Class({ + + Extends: Fx.Morph, + + options: { + relativeTo: document.body, + position: 'center', + edge: false, + offset: {x: 0, y: 0} + }, + + start: function(destination){ + var element = this.element, + topLeft = element.getStyles('top', 'left'); + if (topLeft.top == 'auto' || topLeft.left == 'auto'){ + element.setPosition(element.getPosition(element.getOffsetParent())); + } + return this.parent(element.position(Object.merge({}, this.options, destination, {returnPos: true}))); + } + +}); + +Element.Properties.move = { + + set: function(options){ + this.get('move').cancel().setOptions(options); + return this; + }, + + get: function(){ + var move = this.retrieve('move'); + if (!move){ + move = new Fx.Move(this, {link: 'cancel'}); + this.store('move', move); + } + return move; + } + +}; + +Element.implement({ + + move: function(options){ + this.get('move').start(options); + return this; + } + +}); + +/* +--- + +script: Fx.Scroll.js + +name: Fx.Scroll + +description: Effect to smoothly scroll any element, including the window. + +license: MIT-style license + +authors: + - Valerio Proietti + +requires: + - Core/Fx + - Core/Element.Event + - Core/Element.Dimensions + - MooTools.More + +provides: [Fx.Scroll] + +... +*/ + +(function(){ + +Fx.Scroll = new Class({ + + Extends: Fx, + + options: { + offset: {x: 0, y: 0}, + wheelStops: true + }, + + initialize: function(element, options){ + this.element = this.subject = document.id(element); + this.parent(options); + + if (typeOf(this.element) != 'element') this.element = document.id(this.element.getDocument().body); + + if (this.options.wheelStops){ + var stopper = this.element, + cancel = this.cancel.pass(false, this); + this.addEvent('start', function(){ + stopper.addEvent('mousewheel', cancel); + }, true); + this.addEvent('complete', function(){ + stopper.removeEvent('mousewheel', cancel); + }, true); + } + }, + + set: function(){ + var now = Array.flatten(arguments); + this.element.scrollTo(now[0], now[1]); + return this; + }, + + compute: function(from, to, delta){ + return [0, 1].map(function(i){ + return Fx.compute(from[i], to[i], delta); + }); + }, + + start: function(x, y){ + if (!this.check(x, y)) return this; + var scroll = this.element.getScroll(); + return this.parent([scroll.x, scroll.y], [x, y]); + }, + + calculateScroll: function(x, y){ + var element = this.element, + scrollSize = element.getScrollSize(), + scroll = element.getScroll(), + size = element.getSize(), + offset = this.options.offset, + values = {x: x, y: y}; + + for (var z in values){ + if (!values[z] && values[z] !== 0) values[z] = scroll[z]; + if (typeOf(values[z]) != 'number') values[z] = scrollSize[z] - size[z]; + values[z] += offset[z]; + } + + return [values.x, values.y]; + }, + + toTop: function(){ + return this.start.apply(this, this.calculateScroll(false, 0)); + }, + + toLeft: function(){ + return this.start.apply(this, this.calculateScroll(0, false)); + }, + + toRight: function(){ + return this.start.apply(this, this.calculateScroll('right', false)); + }, + + toBottom: function(){ + return this.start.apply(this, this.calculateScroll(false, 'bottom')); + }, + + toElement: function(el, axes){ + axes = axes ? Array.from(axes) : ['x', 'y']; + var scroll = isBody(this.element) ? {x: 0, y: 0} : this.element.getScroll(); + var position = Object.map(document.id(el).getPosition(this.element), function(value, axis){ + return axes.contains(axis) ? value + scroll[axis] : false; + }); + return this.start.apply(this, this.calculateScroll(position.x, position.y)); + }, + + toElementEdge: function(el, axes, offset){ + axes = axes ? Array.from(axes) : ['x', 'y']; + el = document.id(el); + var to = {}, + position = el.getPosition(this.element), + size = el.getSize(), + scroll = this.element.getScroll(), + containerSize = this.element.getSize(), + edge = { + x: position.x + size.x, + y: position.y + size.y + }; + + ['x', 'y'].each(function(axis){ + if (axes.contains(axis)){ + if (edge[axis] > scroll[axis] + containerSize[axis]) to[axis] = edge[axis] - containerSize[axis]; + if (position[axis] < scroll[axis]) to[axis] = position[axis]; + } + if (to[axis] == null) to[axis] = scroll[axis]; + if (offset && offset[axis]) to[axis] = to[axis] + offset[axis]; + }, this); + + if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y); + return this; + }, + + toElementCenter: function(el, axes, offset){ + axes = axes ? Array.from(axes) : ['x', 'y']; + el = document.id(el); + var to = {}, + position = el.getPosition(this.element), + size = el.getSize(), + scroll = this.element.getScroll(), + containerSize = this.element.getSize(); + + ['x', 'y'].each(function(axis){ + if (axes.contains(axis)){ + to[axis] = position[axis] - (containerSize[axis] - size[axis]) / 2; + } + if (to[axis] == null) to[axis] = scroll[axis]; + if (offset && offset[axis]) to[axis] = to[axis] + offset[axis]; + }, this); + + if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y); + return this; + } + +}); + + + +function isBody(element){ + return (/^(?:body|html)$/i).test(element.tagName); +} + +})(); + +/* +--- + +script: Fx.Slide.js + +name: Fx.Slide + +description: Effect to slide an element in and out of view. + +license: MIT-style license + +authors: + - Valerio Proietti + +requires: + - Core/Fx + - Core/Element.Style + - MooTools.More + +provides: [Fx.Slide] + +... +*/ + +Fx.Slide = new Class({ + + Extends: Fx, + + options: { + mode: 'vertical', + wrapper: false, + hideOverflow: true, + resetHeight: false + }, + + initialize: function(element, options){ + element = this.element = this.subject = document.id(element); + this.parent(options); + options = this.options; + + var wrapper = element.retrieve('wrapper'), + styles = element.getStyles('margin', 'position', 'overflow'); + + if (options.hideOverflow) styles = Object.append(styles, {overflow: 'hidden'}); + if (options.wrapper) wrapper = document.id(options.wrapper).setStyles(styles); + + if (!wrapper) wrapper = new Element('div', { + styles: styles + }).wraps(element); + + element.store('wrapper', wrapper).setStyle('margin', 0); + if (element.getStyle('overflow') == 'visible') element.setStyle('overflow', 'hidden'); + + this.now = []; + this.open = true; + this.wrapper = wrapper; + + this.addEvent('complete', function(){ + this.open = (wrapper['offset' + this.layout.capitalize()] != 0); + if (this.open && this.options.resetHeight) wrapper.setStyle('height', ''); + }, true); + }, + + vertical: function(){ + this.margin = 'margin-top'; + this.layout = 'height'; + this.offset = this.element.offsetHeight; + }, + + horizontal: function(){ + this.margin = 'margin-left'; + this.layout = 'width'; + this.offset = this.element.offsetWidth; + }, + + set: function(now){ + this.element.setStyle(this.margin, now[0]); + this.wrapper.setStyle(this.layout, now[1]); + return this; + }, + + compute: function(from, to, delta){ + return [0, 1].map(function(i){ + return Fx.compute(from[i], to[i], delta); + }); + }, + + start: function(how, mode){ + if (!this.check(how, mode)) return this; + this[mode || this.options.mode](); + + var margin = this.element.getStyle(this.margin).toInt(), + layout = this.wrapper.getStyle(this.layout).toInt(), + caseIn = [[margin, layout], [0, this.offset]], + caseOut = [[margin, layout], [-this.offset, 0]], + start; + + switch (how){ + case 'in': start = caseIn; break; + case 'out': start = caseOut; break; + case 'toggle': start = (layout == 0) ? caseIn : caseOut; + } + return this.parent(start[0], start[1]); + }, + + slideIn: function(mode){ + return this.start('in', mode); + }, + + slideOut: function(mode){ + return this.start('out', mode); + }, + + hide: function(mode){ + this[mode || this.options.mode](); + this.open = false; + return this.set([-this.offset, 0]); + }, + + show: function(mode){ + this[mode || this.options.mode](); + this.open = true; + return this.set([0, this.offset]); + }, + + toggle: function(mode){ + return this.start('toggle', mode); + } + +}); + +Element.Properties.slide = { + + set: function(options){ + this.get('slide').cancel().setOptions(options); + return this; + }, + + get: function(){ + var slide = this.retrieve('slide'); + if (!slide){ + slide = new Fx.Slide(this, {link: 'cancel'}); + this.store('slide', slide); + } + return slide; + } + +}; + +Element.implement({ + + slide: function(how, mode){ + how = how || 'toggle'; + var slide = this.get('slide'), toggle; + switch (how){ + case 'hide': slide.hide(mode); break; + case 'show': slide.show(mode); break; + case 'toggle': + var flag = this.retrieve('slide:flag', slide.open); + slide[flag ? 'slideOut' : 'slideIn'](mode); + this.store('slide:flag', !flag); + toggle = true; + break; + default: slide.start(how, mode); + } + if (!toggle) this.eliminate('slide:flag'); + return this; + } + +}); + +/* +--- + +script: Fx.SmoothScroll.js + +name: Fx.SmoothScroll + +description: Class for creating a smooth scrolling effect to all internal links on the page. + +license: MIT-style license + +authors: + - Valerio Proietti + +requires: + - Core/Slick.Finder + - Fx.Scroll + +provides: [Fx.SmoothScroll] + +... +*/ + +Fx.SmoothScroll = new Class({ + + Extends: Fx.Scroll, + + options: { + axes: ['x', 'y'] + }, + + initialize: function(options, context){ + context = context || document; + this.doc = context.getDocument(); + this.parent(this.doc, options); + + var win = context.getWindow(), + location = win.location.href.match(/^[^#]*/)[0] + '#', + links = $$(this.options.links || this.doc.links); + + links.each(function(link){ + if (link.href.indexOf(location) != 0) return; + var anchor = link.href.substr(location.length); + if (anchor) this.useLink(link, anchor); + }, this); + + this.addEvent('complete', function(){ + win.location.hash = this.anchor; + this.element.scrollTo(this.to[0], this.to[1]); + }, true); + }, + + useLink: function(link, anchor){ + + link.addEvent('click', function(event){ + var el = document.id(anchor) || this.doc.getElement('a[name=' + anchor + ']'); + if (!el) return; + + event.preventDefault(); + this.toElement(el, this.options.axes).chain(function(){ + this.fireEvent('scrolledTo', [link, el]); + }.bind(this)); + + this.anchor = anchor; + + }.bind(this)); + + return this; + } +}); + +/* +--- + +script: Fx.Sort.js + +name: Fx.Sort + +description: Defines Fx.Sort, a class that reorders lists with a transition. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Element.Dimensions + - Fx.Elements + - Element.Measure + +provides: [Fx.Sort] + +... +*/ + +Fx.Sort = new Class({ + + Extends: Fx.Elements, + + options: { + mode: 'vertical' + }, + + initialize: function(elements, options){ + this.parent(elements, options); + this.elements.each(function(el){ + if (el.getStyle('position') == 'static') el.setStyle('position', 'relative'); + }); + this.setDefaultOrder(); + }, + + setDefaultOrder: function(){ + this.currentOrder = this.elements.map(function(el, index){ + return index; + }); + }, + + sort: function(){ + if (!this.check(arguments)) return this; + var newOrder = Array.flatten(arguments); + + var top = 0, + left = 0, + next = {}, + zero = {}, + vert = this.options.mode == 'vertical'; + + var current = this.elements.map(function(el, index){ + var size = el.getComputedSize({styles: ['border', 'padding', 'margin']}); + var val; + if (vert){ + val = { + top: top, + margin: size['margin-top'], + height: size.totalHeight + }; + top += val.height - size['margin-top']; + } else { + val = { + left: left, + margin: size['margin-left'], + width: size.totalWidth + }; + left += val.width; + } + var plane = vert ? 'top' : 'left'; + zero[index] = {}; + var start = el.getStyle(plane).toInt(); + zero[index][plane] = start || 0; + return val; + }, this); + + this.set(zero); + newOrder = newOrder.map(function(i){ return i.toInt(); }); + if (newOrder.length != this.elements.length){ + this.currentOrder.each(function(index){ + if (!newOrder.contains(index)) newOrder.push(index); + }); + if (newOrder.length > this.elements.length) + newOrder.splice(this.elements.length-1, newOrder.length - this.elements.length); + } + var margin = 0; + top = left = 0; + newOrder.each(function(item){ + var newPos = {}; + if (vert){ + newPos.top = top - current[item].top - margin; + top += current[item].height; + } else { + newPos.left = left - current[item].left; + left += current[item].width; + } + margin = margin + current[item].margin; + next[item]=newPos; + }, this); + var mapped = {}; + Array.clone(newOrder).sort().each(function(index){ + mapped[index] = next[index]; + }); + this.start(mapped); + this.currentOrder = newOrder; + + return this; + }, + + rearrangeDOM: function(newOrder){ + newOrder = newOrder || this.currentOrder; + var parent = this.elements[0].getParent(); + var rearranged = []; + this.elements.setStyle('opacity', 0); + //move each element and store the new default order + newOrder.each(function(index){ + rearranged.push(this.elements[index].inject(parent).setStyles({ + top: 0, + left: 0 + })); + }, this); + this.elements.setStyle('opacity', 1); + this.elements = $$(rearranged); + this.setDefaultOrder(); + return this; + }, + + getDefaultOrder: function(){ + return this.elements.map(function(el, index){ + return index; + }); + }, + + getCurrentOrder: function(){ + return this.currentOrder; + }, + + forward: function(){ + return this.sort(this.getDefaultOrder()); + }, + + backward: function(){ + return this.sort(this.getDefaultOrder().reverse()); + }, + + reverse: function(){ + return this.sort(this.currentOrder.reverse()); + }, + + sortByElements: function(elements){ + return this.sort(elements.map(function(el){ + return this.elements.indexOf(el); + }, this)); + }, + + swap: function(one, two){ + if (typeOf(one) == 'element') one = this.elements.indexOf(one); + if (typeOf(two) == 'element') two = this.elements.indexOf(two); + + var newOrder = Array.clone(this.currentOrder); + newOrder[this.currentOrder.indexOf(one)] = two; + newOrder[this.currentOrder.indexOf(two)] = one; + + return this.sort(newOrder); + } + +}); + +/* +--- + +script: Keyboard.js + +name: Keyboard + +description: KeyboardEvents used to intercept events on a class for keyboard and format modifiers in a specific order so as to make alt+shift+c the same as shift+alt+c. + +license: MIT-style license + +authors: + - Perrin Westrich + - Aaron Newton + - Scott Kyle + +requires: + - Core/Events + - Core/Options + - Core/Element.Event + - Element.Event.Pseudos.Keys + +provides: [Keyboard] + +... +*/ + +(function(){ + + var Keyboard = this.Keyboard = new Class({ + + Extends: Events, + + Implements: [Options], + + options: {/* + onActivate: function(){}, + onDeactivate: function(){},*/ + defaultEventType: 'keydown', + active: false, + manager: null, + events: {}, + nonParsedEvents: ['activate', 'deactivate', 'onactivate', 'ondeactivate', 'changed', 'onchanged'] + }, + + initialize: function(options){ + if (options && options.manager){ + this._manager = options.manager; + delete options.manager; + } + this.setOptions(options); + this._setup(); + }, + + addEvent: function(type, fn, internal){ + return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn, internal); + }, + + removeEvent: function(type, fn){ + return this.parent(Keyboard.parse(type, this.options.defaultEventType, this.options.nonParsedEvents), fn); + }, + + toggleActive: function(){ + return this[this.isActive() ? 'deactivate' : 'activate'](); + }, + + activate: function(instance){ + if (instance){ + if (instance.isActive()) return this; + //if we're stealing focus, store the last keyboard to have it so the relinquish command works + if (this._activeKB && instance != this._activeKB){ + this.previous = this._activeKB; + this.previous.fireEvent('deactivate'); + } + //if we're enabling a child, assign it so that events are now passed to it + this._activeKB = instance.fireEvent('activate'); + Keyboard.manager.fireEvent('changed'); + } else if (this._manager){ + //else we're enabling ourselves, we must ask our parent to do it for us + this._manager.activate(this); + } + return this; + }, + + isActive: function(){ + return this._manager ? (this._manager._activeKB == this) : (Keyboard.manager == this); + }, + + deactivate: function(instance){ + if (instance){ + if (instance === this._activeKB){ + this._activeKB = null; + instance.fireEvent('deactivate'); + Keyboard.manager.fireEvent('changed'); + } + } else if (this._manager){ + this._manager.deactivate(this); + } + return this; + }, + + relinquish: function(){ + if (this.isActive() && this._manager && this._manager.previous) this._manager.activate(this._manager.previous); + else this.deactivate(); + return this; + }, + + //management logic + manage: function(instance){ + if (instance._manager) instance._manager.drop(instance); + this._instances.push(instance); + instance._manager = this; + if (!this._activeKB) this.activate(instance); + return this; + }, + + drop: function(instance){ + instance.relinquish(); + this._instances.erase(instance); + if (this._activeKB == instance){ + if (this.previous && this._instances.contains(this.previous)) this.activate(this.previous); + else this._activeKB = this._instances[0]; + } + return this; + }, + + trace: function(){ + Keyboard.trace(this); + }, + + each: function(fn){ + Keyboard.each(this, fn); + }, + + /* + PRIVATE METHODS + */ + + _instances: [], + + _disable: function(instance){ + if (this._activeKB == instance) this._activeKB = null; + }, + + _setup: function(){ + this.addEvents(this.options.events); + //if this is the root manager, nothing manages it + if (Keyboard.manager && !this._manager) Keyboard.manager.manage(this); + if (this.options.active) this.activate(); + else this.relinquish(); + }, + + _handle: function(event, type){ + //Keyboard.stop(event) prevents key propagation + if (event.preventKeyboardPropagation) return; + + var bubbles = !!this._manager; + if (bubbles && this._activeKB){ + this._activeKB._handle(event, type); + if (event.preventKeyboardPropagation) return; + } + this.fireEvent(type, event); + + if (!bubbles && this._activeKB) this._activeKB._handle(event, type); + } + + }); + + var parsed = {}; + var modifiers = ['shift', 'control', 'alt', 'meta']; + var regex = /^(?:shift|control|ctrl|alt|meta)$/; + + Keyboard.parse = function(type, eventType, ignore){ + if (ignore && ignore.contains(type.toLowerCase())) return type; + + type = type.toLowerCase().replace(/^(keyup|keydown):/, function($0, $1){ + eventType = $1; + return ''; + }); + + if (!parsed[type]){ + if (type != '+'){ + var key, mods = {}; + type.split('+').each(function(part){ + if (regex.test(part)) mods[part] = true; + else key = part; + }); + + mods.control = mods.control || mods.ctrl; // allow both control and ctrl + + var keys = []; + modifiers.each(function(mod){ + if (mods[mod]) keys.push(mod); + }); + + if (key) keys.push(key); + parsed[type] = keys.join('+'); + } else { + parsed[type] = type; + } + } + + return eventType + ':keys(' + parsed[type] + ')'; + }; + + Keyboard.each = function(keyboard, fn){ + var current = keyboard || Keyboard.manager; + while (current){ + fn(current); + current = current._activeKB; + } + }; + + Keyboard.stop = function(event){ + event.preventKeyboardPropagation = true; + }; + + Keyboard.manager = new Keyboard({ + active: true + }); + + Keyboard.trace = function(keyboard){ + keyboard = keyboard || Keyboard.manager; + var hasConsole = window.console && console.log; + if (hasConsole) console.log('the following items have focus: '); + Keyboard.each(keyboard, function(current){ + if (hasConsole) console.log(document.id(current.widget) || current.wiget || current); + }); + }; + + var handler = function(event){ + var keys = []; + modifiers.each(function(mod){ + if (event[mod]) keys.push(mod); + }); + + if (!regex.test(event.key)) keys.push(event.key); + Keyboard.manager._handle(event, event.type + ':keys(' + keys.join('+') + ')'); + }; + + document.addEvents({ + 'keyup': handler, + 'keydown': handler + }); + +})(); + +/* +--- + +script: Keyboard.Extras.js + +name: Keyboard.Extras + +description: Enhances Keyboard by adding the ability to name and describe keyboard shortcuts, and the ability to grab shortcuts by name and bind the shortcut to different keys. + +license: MIT-style license + +authors: + - Perrin Westrich + +requires: + - Keyboard + - MooTools.More + +provides: [Keyboard.Extras] + +... +*/ +Keyboard.prototype.options.nonParsedEvents.combine(['rebound', 'onrebound']); + +Keyboard.implement({ + + /* + shortcut should be in the format of: + { + 'keys': 'shift+s', // the default to add as an event. + 'description': 'blah blah blah', // a brief description of the functionality. + 'handler': function(){} // the event handler to run when keys are pressed. + } + */ + addShortcut: function(name, shortcut){ + this._shortcuts = this._shortcuts || []; + this._shortcutIndex = this._shortcutIndex || {}; + + shortcut.getKeyboard = Function.from(this); + shortcut.name = name; + this._shortcutIndex[name] = shortcut; + this._shortcuts.push(shortcut); + if (shortcut.keys) this.addEvent(shortcut.keys, shortcut.handler); + return this; + }, + + addShortcuts: function(obj){ + for (var name in obj) this.addShortcut(name, obj[name]); + return this; + }, + + removeShortcut: function(name){ + var shortcut = this.getShortcut(name); + if (shortcut && shortcut.keys){ + this.removeEvent(shortcut.keys, shortcut.handler); + delete this._shortcutIndex[name]; + this._shortcuts.erase(shortcut); + } + return this; + }, + + removeShortcuts: function(names){ + names.each(this.removeShortcut, this); + return this; + }, + + getShortcuts: function(){ + return this._shortcuts || []; + }, + + getShortcut: function(name){ + return (this._shortcutIndex || {})[name]; + } + +}); + +Keyboard.rebind = function(newKeys, shortcuts){ + Array.from(shortcuts).each(function(shortcut){ + shortcut.getKeyboard().removeEvent(shortcut.keys, shortcut.handler); + shortcut.getKeyboard().addEvent(newKeys, shortcut.handler); + shortcut.keys = newKeys; + shortcut.getKeyboard().fireEvent('rebound'); + }); +}; + + +Keyboard.getActiveShortcuts = function(keyboard){ + var activeKBS = [], activeSCS = []; + Keyboard.each(keyboard, [].push.bind(activeKBS)); + activeKBS.each(function(kb){ activeSCS.extend(kb.getShortcuts()); }); + return activeSCS; +}; + +Keyboard.getShortcut = function(name, keyboard, opts){ + opts = opts || {}; + var shortcuts = opts.many ? [] : null, + set = opts.many ? function(kb){ + var shortcut = kb.getShortcut(name); + if (shortcut) shortcuts.push(shortcut); + } : function(kb){ + if (!shortcuts) shortcuts = kb.getShortcut(name); + }; + Keyboard.each(keyboard, set); + return shortcuts; +}; + +Keyboard.getShortcuts = function(name, keyboard){ + return Keyboard.getShortcut(name, keyboard, { many: true }); +}; + +/* +--- + +script: HtmlTable.js + +name: HtmlTable + +description: Builds table elements with methods to add rows. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Options + - Core/Events + - Class.Occlude + +provides: [HtmlTable] + +... +*/ + +var HtmlTable = new Class({ + + Implements: [Options, Events, Class.Occlude], + + options: { + properties: { + cellpadding: 0, + cellspacing: 0, + border: 0 + }, + rows: [], + headers: [], + footers: [] + }, + + property: 'HtmlTable', + + initialize: function(){ + var params = Array.link(arguments, {options: Type.isObject, table: Type.isElement, id: Type.isString}); + this.setOptions(params.options); + if (!params.table && params.id) params.table = document.id(params.id); + this.element = params.table || new Element('table', this.options.properties); + if (this.occlude()) return this.occluded; + this.build(); + }, + + build: function(){ + this.element.store('HtmlTable', this); + + this.body = document.id(this.element.tBodies[0]) || new Element('tbody').inject(this.element); + $$(this.body.rows); + + if (this.options.headers.length) this.setHeaders(this.options.headers); + else this.thead = document.id(this.element.tHead); + + if (this.thead) this.head = this.getHead(); + if (this.options.footers.length) this.setFooters(this.options.footers); + + this.tfoot = document.id(this.element.tFoot); + if (this.tfoot) this.foot = document.id(this.tfoot.rows[0]); + + this.options.rows.each(function(row){ + this.push(row); + }, this); + }, + + toElement: function(){ + return this.element; + }, + + empty: function(){ + this.body.empty(); + return this; + }, + + set: function(what, items){ + var target = (what == 'headers') ? 'tHead' : 'tFoot', + lower = target.toLowerCase(); + + this[lower] = (document.id(this.element[target]) || new Element(lower).inject(this.element, 'top')).empty(); + var data = this.push(items, {}, this[lower], what == 'headers' ? 'th' : 'td'); + + if (what == 'headers') this.head = this.getHead(); + else this.foot = this.getHead(); + + return data; + }, + + getHead: function(){ + var rows = this.thead.rows; + return rows.length > 1 ? $$(rows) : rows.length ? document.id(rows[0]) : false; + }, + + setHeaders: function(headers){ + this.set('headers', headers); + return this; + }, + + setFooters: function(footers){ + this.set('footers', footers); + return this; + }, + + update: function(tr, row, tag){ + var tds = tr.getChildren(tag || 'td'), last = tds.length - 1; + + row.each(function(data, index){ + var td = tds[index] || new Element(tag || 'td').inject(tr), + content = ((data && Object.prototype.hasOwnProperty.call(data, 'content')) ? data.content : '') || data, + type = typeOf(content); + + if (data && Object.prototype.hasOwnProperty.call(data, 'properties')) td.set(data.properties); + if (/(element(s?)|array|collection)/.test(type)) td.empty().adopt(content); + else td.set('html', content); + + if (index > last) tds.push(td); + else tds[index] = td; + }); + + return { + tr: tr, + tds: tds + }; + }, + + push: function(row, rowProperties, target, tag, where){ + if (typeOf(row) == 'element' && row.get('tag') == 'tr'){ + row.inject(target || this.body, where); + return { + tr: row, + tds: row.getChildren('td') + }; + } + return this.update(new Element('tr', rowProperties).inject(target || this.body, where), row, tag); + }, + + pushMany: function(rows, rowProperties, target, tag, where){ + return rows.map(function(row){ + return this.push(row, rowProperties, target, tag, where); + }, this); + } + +}); + + +['adopt', 'inject', 'wraps', 'grab', 'replaces', 'dispose'].each(function(method){ + HtmlTable.implement(method, function(){ + this.element[method].apply(this.element, arguments); + return this; + }); +}); + + + +/* +--- + +script: HtmlTable.Select.js + +name: HtmlTable.Select + +description: Builds a stripy, sortable table with methods to add rows. Rows can be selected with the mouse or keyboard navigation. + +license: MIT-style license + +authors: + - Harald Kirschner + - Aaron Newton + +requires: + - Keyboard + - Keyboard.Extras + - HtmlTable + - Class.refactor + - Element.Delegation.Pseudo + - Element.Shortcuts + +provides: [HtmlTable.Select] + +... +*/ + +HtmlTable = Class.refactor(HtmlTable, { + + options: { + /*onRowFocus: function(){}, + onRowUnfocus: function(){},*/ + useKeyboard: true, + classRowSelected: 'table-tr-selected', + classRowHovered: 'table-tr-hovered', + classSelectable: 'table-selectable', + shiftForMultiSelect: true, + allowMultiSelect: true, + selectable: false, + selectHiddenRows: false + }, + + initialize: function(){ + this.previous.apply(this, arguments); + if (this.occluded) return this.occluded; + + this.selectedRows = new Elements(); + + if (!this.bound) this.bound = {}; + this.bound.mouseleave = this.mouseleave.bind(this); + this.bound.clickRow = this.clickRow.bind(this); + this.bound.activateKeyboard = function(){ + if (this.keyboard && this.selectEnabled) this.keyboard.activate(); + }.bind(this); + + if (this.options.selectable) this.enableSelect(); + }, + + empty: function(){ + if (this.body.rows.length) this.selectNone(); + return this.previous(); + }, + + enableSelect: function(){ + this.selectEnabled = true; + this.attachSelects(); + this.element.addClass(this.options.classSelectable); + return this; + }, + + disableSelect: function(){ + this.selectEnabled = false; + this.attachSelects(false); + this.element.removeClass(this.options.classSelectable); + return this; + }, + + push: function(){ + var ret = this.previous.apply(this, arguments); + this.updateSelects(); + return ret; + }, + + toggleRow: function(row){ + return this[(this.isSelected(row) ? 'de' : '') + 'selectRow'](row); + }, + + selectRow: function(row, _nocheck){ + //private variable _nocheck: boolean whether or not to confirm the row is in the table body + //added here for optimization when selecting ranges + if (this.isSelected(row) || (!_nocheck && !this.body.getChildren().contains(row))) return; + if (!this.options.allowMultiSelect) this.selectNone(); + + if (!this.isSelected(row)){ + this.selectedRows.push(row); + row.addClass(this.options.classRowSelected); + this.fireEvent('rowFocus', [row, this.selectedRows]); + this.fireEvent('stateChanged'); + } + + this.focused = row; + document.clearSelection(); + + return this; + }, + + isSelected: function(row){ + return this.selectedRows.contains(row); + }, + + getSelected: function(){ + return this.selectedRows; + }, + + serialize: function(){ + var previousSerialization = this.previous.apply(this, arguments) || {}; + if (this.options.selectable){ + previousSerialization.selectedRows = this.selectedRows.map(function(row){ + return Array.indexOf(this.body.rows, row); + }.bind(this)); + } + return previousSerialization; + }, + + restore: function(tableState){ + if(this.options.selectable && tableState.selectedRows){ + tableState.selectedRows.each(function(index){ + this.selectRow(this.body.rows[index]); + }.bind(this)); + } + this.previous.apply(this, arguments); + }, + + deselectRow: function(row, _nocheck){ + if (!this.isSelected(row) || (!_nocheck && !this.body.getChildren().contains(row))) return; + + this.selectedRows = new Elements(Array.from(this.selectedRows).erase(row)); + row.removeClass(this.options.classRowSelected); + this.fireEvent('rowUnfocus', [row, this.selectedRows]); + this.fireEvent('stateChanged'); + return this; + }, + + selectAll: function(selectNone){ + if (!selectNone && !this.options.allowMultiSelect) return; + this.selectRange(0, this.body.rows.length, selectNone); + return this; + }, + + selectNone: function(){ + return this.selectAll(true); + }, + + selectRange: function(startRow, endRow, _deselect){ + if (!this.options.allowMultiSelect && !_deselect) return; + var method = _deselect ? 'deselectRow' : 'selectRow', + rows = Array.clone(this.body.rows); + + if (typeOf(startRow) == 'element') startRow = rows.indexOf(startRow); + if (typeOf(endRow) == 'element') endRow = rows.indexOf(endRow); + endRow = endRow < rows.length - 1 ? endRow : rows.length - 1; + + if (endRow < startRow){ + var tmp = startRow; + startRow = endRow; + endRow = tmp; + } + + for (var i = startRow; i <= endRow; i++){ + if (this.options.selectHiddenRows || rows[i].isDisplayed()) this[method](rows[i], true); + } + + return this; + }, + + deselectRange: function(startRow, endRow){ + this.selectRange(startRow, endRow, true); + }, + +/* + Private methods: +*/ + + enterRow: function(row){ + if (this.hovered) this.hovered = this.leaveRow(this.hovered); + this.hovered = row.addClass(this.options.classRowHovered); + }, + + leaveRow: function(row){ + row.removeClass(this.options.classRowHovered); + }, + + updateSelects: function(){ + Array.each(this.body.rows, function(row){ + var binders = row.retrieve('binders'); + if (!binders && !this.selectEnabled) return; + if (!binders){ + binders = { + mouseenter: this.enterRow.pass([row], this), + mouseleave: this.leaveRow.pass([row], this) + }; + row.store('binders', binders); + } + if (this.selectEnabled) row.addEvents(binders); + else row.removeEvents(binders); + }, this); + }, + + shiftFocus: function(offset, event){ + if (!this.focused) return this.selectRow(this.body.rows[0], event); + var to = this.getRowByOffset(offset, this.options.selectHiddenRows); + if (to === null || this.focused == this.body.rows[to]) return this; + this.toggleRow(this.body.rows[to], event); + }, + + clickRow: function(event, row){ + var selecting = (event.shift || event.meta || event.control) && this.options.shiftForMultiSelect; + if (!selecting && !(event.rightClick && this.isSelected(row) && this.options.allowMultiSelect)) this.selectNone(); + + if (event.rightClick) this.selectRow(row); + else this.toggleRow(row); + + if (event.shift){ + this.selectRange(this.rangeStart || this.body.rows[0], row, this.rangeStart ? !this.isSelected(row) : true); + this.focused = row; + } + this.rangeStart = row; + }, + + getRowByOffset: function(offset, includeHiddenRows){ + if (!this.focused) return 0; + var index = Array.indexOf(this.body.rows, this.focused); + if ((index == 0 && offset < 0) || (index == this.body.rows.length -1 && offset > 0)) return null; + if (includeHiddenRows){ + index += offset; + } else { + var limit = 0, + count = 0; + if (offset > 0){ + while (count < offset && index < this.body.rows.length -1){ + if (this.body.rows[++index].isDisplayed()) count++; + } + } else { + while (count > offset && index > 0){ + if (this.body.rows[--index].isDisplayed()) count--; + } + } + } + return index; + }, + + attachSelects: function(attach){ + attach = attach != null ? attach : true; + + var method = attach ? 'addEvents' : 'removeEvents'; + this.element[method]({ + mouseleave: this.bound.mouseleave, + click: this.bound.activateKeyboard + }); + + this.body[method]({ + 'click:relay(tr)': this.bound.clickRow, + 'contextmenu:relay(tr)': this.bound.clickRow + }); + + if (this.options.useKeyboard || this.keyboard){ + if (!this.keyboard) this.keyboard = new Keyboard(); + if (!this.selectKeysDefined){ + this.selectKeysDefined = true; + var timer, held; + + var move = function(offset){ + var mover = function(e){ + clearTimeout(timer); + e.preventDefault(); + var to = this.body.rows[this.getRowByOffset(offset, this.options.selectHiddenRows)]; + if (e.shift && to && this.isSelected(to)){ + this.deselectRow(this.focused); + this.focused = to; + } else { + if (to && (!this.options.allowMultiSelect || !e.shift)){ + this.selectNone(); + } + this.shiftFocus(offset, e); + } + + if (held){ + timer = mover.delay(100, this, e); + } else { + timer = (function(){ + held = true; + mover(e); + }).delay(400); + } + }.bind(this); + return mover; + }.bind(this); + + var clear = function(){ + clearTimeout(timer); + held = false; + }; + + this.keyboard.addEvents({ + 'keydown:shift+up': move(-1), + 'keydown:shift+down': move(1), + 'keyup:shift+up': clear, + 'keyup:shift+down': clear, + 'keyup:up': clear, + 'keyup:down': clear + }); + + var shiftHint = ''; + if (this.options.allowMultiSelect && this.options.shiftForMultiSelect && this.options.useKeyboard){ + shiftHint = " (Shift multi-selects)."; + } + + this.keyboard.addShortcuts({ + 'Select Previous Row': { + keys: 'up', + shortcut: 'up arrow', + handler: move(-1), + description: 'Select the previous row in the table.' + shiftHint + }, + 'Select Next Row': { + keys: 'down', + shortcut: 'down arrow', + handler: move(1), + description: 'Select the next row in the table.' + shiftHint + } + }); + + } + this.keyboard[attach ? 'activate' : 'deactivate'](); + } + this.updateSelects(); + }, + + mouseleave: function(){ + if (this.hovered) this.leaveRow(this.hovered); + } + +}); + +/* +--- + +script: HtmlTable.Sort.js + +name: HtmlTable.Sort + +description: Builds a stripy, sortable table with methods to add rows. + +license: MIT-style license + +authors: + - Harald Kirschner + - Aaron Newton + - Jacob Thornton + +requires: + - Core/Hash + - HtmlTable + - Class.refactor + - Element.Delegation.Pseudo + - String.Extras + - Date + +provides: [HtmlTable.Sort] + +... +*/ +(function(){ + +var readOnlyNess = document.createElement('table'); +try { + readOnlyNess.innerHTML = '<tr><td></td></tr>'; + readOnlyNess = readOnlyNess.childNodes.length === 0; +} catch (e){ + readOnlyNess = true; +} + +HtmlTable = Class.refactor(HtmlTable, { + + options: {/* + onSort: function(){}, */ + sortIndex: 0, + sortReverse: false, + parsers: [], + defaultParser: 'string', + classSortable: 'table-sortable', + classHeadSort: 'table-th-sort', + classHeadSortRev: 'table-th-sort-rev', + classNoSort: 'table-th-nosort', + classGroupHead: 'table-tr-group-head', + classGroup: 'table-tr-group', + classCellSort: 'table-td-sort', + classSortSpan: 'table-th-sort-span', + sortable: false, + thSelector: 'th' + }, + + initialize: function (){ + this.previous.apply(this, arguments); + if (this.occluded) return this.occluded; + this.sorted = {index: null, dir: 1}; + if (!this.bound) this.bound = {}; + this.bound.headClick = this.headClick.bind(this); + this.sortSpans = new Elements(); + if (this.options.sortable){ + this.enableSort(); + if (this.options.sortIndex != null) this.sort(this.options.sortIndex, this.options.sortReverse); + } + }, + + attachSorts: function(attach){ + this.detachSorts(); + if (attach !== false) this.element.addEvent('click:relay(' + this.options.thSelector + ')', this.bound.headClick); + }, + + detachSorts: function(){ + this.element.removeEvents('click:relay(' + this.options.thSelector + ')'); + }, + + setHeaders: function(){ + this.previous.apply(this, arguments); + if (this.sortable) this.setParsers(); + }, + + setParsers: function(){ + this.parsers = this.detectParsers(); + }, + + detectParsers: function(){ + return this.head && this.head.getElements(this.options.thSelector).flatten().map(this.detectParser, this); + }, + + detectParser: function(cell, index){ + if (cell.hasClass(this.options.classNoSort) || cell.retrieve('htmltable-parser')) return cell.retrieve('htmltable-parser'); + var thDiv = new Element('div'); + thDiv.adopt(cell.childNodes).inject(cell); + var sortSpan = new Element('span', {'class': this.options.classSortSpan}).inject(thDiv, 'top'); + this.sortSpans.push(sortSpan); + var parser = this.options.parsers[index], + rows = this.body.rows, + cancel; + switch (typeOf(parser)){ + case 'function': parser = {convert: parser}; cancel = true; break; + case 'string': parser = parser; cancel = true; break; + } + if (!cancel){ + HtmlTable.ParserPriority.some(function(parserName){ + var current = HtmlTable.Parsers[parserName], + match = current.match; + if (!match) return false; + for (var i = 0, j = rows.length; i < j; i++){ + var cell = document.id(rows[i].cells[index]), + text = cell ? cell.get('html').clean() : ''; + if (text && match.test(text)){ + parser = current; + return true; + } + } + }); + } + if (!parser) parser = this.options.defaultParser; + cell.store('htmltable-parser', parser); + return parser; + }, + + headClick: function(event, el){ + if (!this.head || el.hasClass(this.options.classNoSort)) return; + return this.sort(Array.indexOf(this.head.getElements(this.options.thSelector).flatten(), el) % this.body.rows[0].cells.length); + }, + + serialize: function(){ + var previousSerialization = this.previous.apply(this, arguments) || {}; + if (this.options.sortable){ + previousSerialization.sortIndex = this.sorted.index; + previousSerialization.sortReverse = this.sorted.reverse; + } + return previousSerialization; + }, + + restore: function(tableState){ + if(this.options.sortable && tableState.sortIndex){ + this.sort(tableState.sortIndex, tableState.sortReverse); + } + this.previous.apply(this, arguments); + }, + + setSortedState: function(index, reverse){ + if (reverse != null) this.sorted.reverse = reverse; + else if (this.sorted.index == index) this.sorted.reverse = !this.sorted.reverse; + else this.sorted.reverse = this.sorted.index == null; + + if (index != null) this.sorted.index = index; + }, + + setHeadSort: function(sorted){ + var head = $$(!this.head.length ? this.head.cells[this.sorted.index] : this.head.map(function(row){ + return row.getElements(this.options.thSelector)[this.sorted.index]; + }, this).clean()); + if (!head.length) return; + if (sorted){ + head.addClass(this.options.classHeadSort); + if (this.sorted.reverse) head.addClass(this.options.classHeadSortRev); + else head.removeClass(this.options.classHeadSortRev); + } else { + head.removeClass(this.options.classHeadSort).removeClass(this.options.classHeadSortRev); + } + }, + + setRowSort: function(data, pre){ + var count = data.length, + body = this.body, + group, + rowIndex; + + while (count){ + var item = data[--count], + position = item.position, + row = body.rows[position]; + + if (row.disabled) continue; + if (!pre){ + group = this.setGroupSort(group, row, item); + this.setRowStyle(row, count); + } + body.appendChild(row); + + for (rowIndex = 0; rowIndex < count; rowIndex++){ + if (data[rowIndex].position > position) data[rowIndex].position--; + } + } + }, + + setRowStyle: function(row, i){ + this.previous(row, i); + row.cells[this.sorted.index].addClass(this.options.classCellSort); + }, + + setGroupSort: function(group, row, item){ + if (group == item.value) row.removeClass(this.options.classGroupHead).addClass(this.options.classGroup); + else row.removeClass(this.options.classGroup).addClass(this.options.classGroupHead); + return item.value; + }, + + getParser: function(){ + var parser = this.parsers[this.sorted.index]; + return typeOf(parser) == 'string' ? HtmlTable.Parsers[parser] : parser; + }, + + sort: function(index, reverse, pre, sortFunction){ + if (!this.head) return; + + if (!pre){ + this.clearSort(); + this.setSortedState(index, reverse); + this.setHeadSort(true); + } + + var parser = this.getParser(); + if (!parser) return; + + var rel; + if (!readOnlyNess){ + rel = this.body.getParent(); + this.body.dispose(); + } + + var data = this.parseData(parser).sort(sortFunction ? sortFunction : function(a, b){ + if (a.value === b.value) return 0; + return a.value > b.value ? 1 : -1; + }); + + if (this.sorted.reverse == (parser == HtmlTable.Parsers['input-checked'])) data.reverse(true); + this.setRowSort(data, pre); + + if (rel) rel.grab(this.body); + this.fireEvent('stateChanged'); + return this.fireEvent('sort', [this.body, this.sorted.index]); + }, + + parseData: function(parser){ + return Array.map(this.body.rows, function(row, i){ + var value = parser.convert.call(document.id(row.cells[this.sorted.index])); + return { + position: i, + value: value + }; + }, this); + }, + + clearSort: function(){ + this.setHeadSort(false); + this.body.getElements('td').removeClass(this.options.classCellSort); + }, + + reSort: function(){ + if (this.sortable) this.sort.call(this, this.sorted.index, this.sorted.reverse); + return this; + }, + + enableSort: function(){ + this.element.addClass(this.options.classSortable); + this.attachSorts(true); + this.setParsers(); + this.sortable = true; + return this; + }, + + disableSort: function(){ + this.element.removeClass(this.options.classSortable); + this.attachSorts(false); + this.sortSpans.each(function(span){ + span.destroy(); + }); + this.sortSpans.empty(); + this.sortable = false; + return this; + } + +}); + +HtmlTable.ParserPriority = ['date', 'input-checked', 'input-value', 'float', 'number']; + +HtmlTable.Parsers = { + + 'date': { + match: /^\d{2}[-\/ ]\d{2}[-\/ ]\d{2,4}$/, + convert: function(){ + var d = Date.parse(this.get('text').stripTags()); + return (typeOf(d) == 'date') ? d.format('db') : ''; + }, + type: 'date' + }, + 'input-checked': { + match: / type="(radio|checkbox)"/, + convert: function(){ + return this.getElement('input').checked; + } + }, + 'input-value': { + match: /<input/, + convert: function(){ + return this.getElement('input').value; + } + }, + 'number': { + match: /^\d+[^\d.,]*$/, + convert: function(){ + return this.get('text').stripTags().toInt(); + }, + number: true + }, + 'numberLax': { + match: /^[^\d]+\d+$/, + convert: function(){ + return this.get('text').replace(/[^-?^0-9]/, '').stripTags().toInt(); + }, + number: true + }, + 'float': { + match: /^[\d]+\.[\d]+/, + convert: function(){ + return this.get('text').replace(/[^-?^\d.e]/, '').stripTags().toFloat(); + }, + number: true + }, + 'floatLax': { + match: /^[^\d]+[\d]+\.[\d]+$/, + convert: function(){ + return this.get('text').replace(/[^-?^\d.]/, '').stripTags().toFloat(); + }, + number: true + }, + 'string': { + match: null, + convert: function(){ + return this.get('text').stripTags().toLowerCase(); + } + }, + 'title': { + match: null, + convert: function(){ + return this.title; + } + } + +}; + + + +HtmlTable.defineParsers = function(parsers){ + HtmlTable.Parsers = Object.append(HtmlTable.Parsers, parsers); + for (var parser in parsers){ + HtmlTable.ParserPriority.unshift(parser); + } +}; + +})(); + + +/* +--- + +script: HtmlTable.Zebra.js + +name: HtmlTable.Zebra + +description: Builds a stripy table with methods to add rows. + +license: MIT-style license + +authors: + - Harald Kirschner + - Aaron Newton + +requires: + - HtmlTable + - Element.Shortcuts + - Class.refactor + +provides: [HtmlTable.Zebra] + +... +*/ + +HtmlTable = Class.refactor(HtmlTable, { + + options: { + classZebra: 'table-tr-odd', + zebra: true, + zebraOnlyVisibleRows: true + }, + + initialize: function(){ + this.previous.apply(this, arguments); + if (this.occluded) return this.occluded; + if (this.options.zebra) this.updateZebras(); + }, + + updateZebras: function(){ + var index = 0; + Array.each(this.body.rows, function(row){ + if (!this.options.zebraOnlyVisibleRows || row.isDisplayed()){ + this.zebra(row, index++); + } + }, this); + }, + + setRowStyle: function(row, i){ + if (this.previous) this.previous(row, i); + this.zebra(row, i); + }, + + zebra: function(row, i){ + return row[((i % 2) ? 'remove' : 'add')+'Class'](this.options.classZebra); + }, + + push: function(){ + var pushed = this.previous.apply(this, arguments); + if (this.options.zebra) this.updateZebras(); + return pushed; + } + +}); + +/* +--- + +script: Scroller.js + +name: Scroller + +description: Class which scrolls the contents of any Element (including the window) when the mouse reaches the Element's boundaries. + +license: MIT-style license + +authors: + - Valerio Proietti + +requires: + - Core/Events + - Core/Options + - Core/Element.Event + - Core/Element.Dimensions + - MooTools.More + +provides: [Scroller] + +... +*/ + +var Scroller = new Class({ + + Implements: [Events, Options], + + options: { + area: 20, + velocity: 1, + onChange: function(x, y){ + this.element.scrollTo(x, y); + }, + fps: 50 + }, + + initialize: function(element, options){ + this.setOptions(options); + this.element = document.id(element); + this.docBody = document.id(this.element.getDocument().body); + this.listener = (typeOf(this.element) != 'element') ? this.docBody : this.element; + this.timer = null; + this.bound = { + attach: this.attach.bind(this), + detach: this.detach.bind(this), + getCoords: this.getCoords.bind(this) + }; + }, + + start: function(){ + this.listener.addEvents({ + mouseover: this.bound.attach, + mouseleave: this.bound.detach + }); + return this; + }, + + stop: function(){ + this.listener.removeEvents({ + mouseover: this.bound.attach, + mouseleave: this.bound.detach + }); + this.detach(); + this.timer = clearInterval(this.timer); + return this; + }, + + attach: function(){ + this.listener.addEvent('mousemove', this.bound.getCoords); + }, + + detach: function(){ + this.listener.removeEvent('mousemove', this.bound.getCoords); + this.timer = clearInterval(this.timer); + }, + + getCoords: function(event){ + this.page = (this.listener.get('tag') == 'body') ? event.client : event.page; + if (!this.timer) this.timer = this.scroll.periodical(Math.round(1000 / this.options.fps), this); + }, + + scroll: function(){ + var size = this.element.getSize(), + scroll = this.element.getScroll(), + pos = ((this.element != this.docBody) && (this.element != window)) ? element.getOffsets() : {x: 0, y: 0}, + scrollSize = this.element.getScrollSize(), + change = {x: 0, y: 0}, + top = this.options.area.top || this.options.area, + bottom = this.options.area.bottom || this.options.area; + for (var z in this.page){ + if (this.page[z] < (top + pos[z]) && scroll[z] != 0){ + change[z] = (this.page[z] - top - pos[z]) * this.options.velocity; + } else if (this.page[z] + bottom > (size[z] + pos[z]) && scroll[z] + size[z] != scrollSize[z]){ + change[z] = (this.page[z] - size[z] + bottom - pos[z]) * this.options.velocity; + } + change[z] = change[z].round(); + } + if (change.y || change.x) this.fireEvent('change', [scroll.x + change.x, scroll.y + change.y]); + } + +}); + +/* +--- + +script: Tips.js + +name: Tips + +description: Class for creating nice tips that follow the mouse cursor when hovering an element. + +license: MIT-style license + +authors: + - Valerio Proietti + - Christoph Pojer + - Luis Merino + +requires: + - Core/Options + - Core/Events + - Core/Element.Event + - Core/Element.Style + - Core/Element.Dimensions + - MooTools.More + +provides: [Tips] + +... +*/ + +(function(){ + +var read = function(option, element){ + return (option) ? (typeOf(option) == 'function' ? option(element) : element.get(option)) : ''; +}; + +this.Tips = new Class({ + + Implements: [Events, Options], + + options: {/* + id: null, + onAttach: function(element){}, + onDetach: function(element){}, + onBound: function(coords){},*/ + onShow: function(){ + this.tip.setStyle('display', 'block'); + }, + onHide: function(){ + this.tip.setStyle('display', 'none'); + }, + title: 'title', + text: function(element){ + return element.get('rel') || element.get('href'); + }, + showDelay: 100, + hideDelay: 100, + className: 'tip-wrap', + offset: {x: 16, y: 16}, + windowPadding: {x:0, y:0}, + fixed: false, + waiAria: true + }, + + initialize: function(){ + var params = Array.link(arguments, { + options: Type.isObject, + elements: function(obj){ + return obj != null; + } + }); + this.setOptions(params.options); + if (params.elements) this.attach(params.elements); + this.container = new Element('div', {'class': 'tip'}); + + if (this.options.id){ + this.container.set('id', this.options.id); + if (this.options.waiAria) this.attachWaiAria(); + } + }, + + toElement: function(){ + if (this.tip) return this.tip; + + this.tip = new Element('div', { + 'class': this.options.className, + styles: { + position: 'absolute', + top: 0, + left: 0 + } + }).adopt( + new Element('div', {'class': 'tip-top'}), + this.container, + new Element('div', {'class': 'tip-bottom'}) + ); + + return this.tip; + }, + + attachWaiAria: function(){ + var id = this.options.id; + this.container.set('role', 'tooltip'); + + if (!this.waiAria){ + this.waiAria = { + show: function(element){ + if (id) element.set('aria-describedby', id); + this.container.set('aria-hidden', 'false'); + }, + hide: function(element){ + if (id) element.erase('aria-describedby'); + this.container.set('aria-hidden', 'true'); + } + }; + } + this.addEvents(this.waiAria); + }, + + detachWaiAria: function(){ + if (this.waiAria){ + this.container.erase('role'); + this.container.erase('aria-hidden'); + this.removeEvents(this.waiAria); + } + }, + + attach: function(elements){ + $$(elements).each(function(element){ + var title = read(this.options.title, element), + text = read(this.options.text, element); + + element.set('title', '').store('tip:native', title).retrieve('tip:title', title); + element.retrieve('tip:text', text); + this.fireEvent('attach', [element]); + + var events = ['enter', 'leave']; + if (!this.options.fixed) events.push('move'); + + events.each(function(value){ + var event = element.retrieve('tip:' + value); + if (!event) event = function(event){ + this['element' + value.capitalize()].apply(this, [event, element]); + }.bind(this); + + element.store('tip:' + value, event).addEvent('mouse' + value, event); + }, this); + }, this); + + return this; + }, + + detach: function(elements){ + $$(elements).each(function(element){ + ['enter', 'leave', 'move'].each(function(value){ + element.removeEvent('mouse' + value, element.retrieve('tip:' + value)).eliminate('tip:' + value); + }); + + this.fireEvent('detach', [element]); + + if (this.options.title == 'title'){ // This is necessary to check if we can revert the title + var original = element.retrieve('tip:native'); + if (original) element.set('title', original); + } + }, this); + + return this; + }, + + elementEnter: function(event, element){ + clearTimeout(this.timer); + this.timer = (function(){ + this.container.empty(); + + ['title', 'text'].each(function(value){ + var content = element.retrieve('tip:' + value); + var div = this['_' + value + 'Element'] = new Element('div', { + 'class': 'tip-' + value + }).inject(this.container); + if (content) this.fill(div, content); + }, this); + this.show(element); + this.position((this.options.fixed) ? {page: element.getPosition()} : event); + }).delay(this.options.showDelay, this); + }, + + elementLeave: function(event, element){ + clearTimeout(this.timer); + this.timer = this.hide.delay(this.options.hideDelay, this, element); + this.fireForParent(event, element); + }, + + setTitle: function(title){ + if (this._titleElement){ + this._titleElement.empty(); + this.fill(this._titleElement, title); + } + return this; + }, + + setText: function(text){ + if (this._textElement){ + this._textElement.empty(); + this.fill(this._textElement, text); + } + return this; + }, + + fireForParent: function(event, element){ + element = element.getParent(); + if (!element || element == document.body) return; + if (element.retrieve('tip:enter')) element.fireEvent('mouseenter', event); + else this.fireForParent(event, element); + }, + + elementMove: function(event, element){ + this.position(event); + }, + + position: function(event){ + if (!this.tip) document.id(this); + + var size = window.getSize(), scroll = window.getScroll(), + tip = {x: this.tip.offsetWidth, y: this.tip.offsetHeight}, + props = {x: 'left', y: 'top'}, + bounds = {y: false, x2: false, y2: false, x: false}, + obj = {}; + + for (var z in props){ + obj[props[z]] = event.page[z] + this.options.offset[z]; + if (obj[props[z]] < 0) bounds[z] = true; + if ((obj[props[z]] + tip[z] - scroll[z]) > size[z] - this.options.windowPadding[z]){ + obj[props[z]] = event.page[z] - this.options.offset[z] - tip[z]; + bounds[z+'2'] = true; + } + } + + this.fireEvent('bound', bounds); + this.tip.setStyles(obj); + }, + + fill: function(element, contents){ + if (typeof contents == 'string') element.set('html', contents); + else element.adopt(contents); + }, + + show: function(element){ + if (!this.tip) document.id(this); + if (!this.tip.getParent()) this.tip.inject(document.body); + this.fireEvent('show', [this.tip, element]); + }, + + hide: function(element){ + if (!this.tip) document.id(this); + this.fireEvent('hide', [this.tip, element]); + } + +}); + +})(); + +/* +--- + +name: Locale.EU.Number + +description: Number messages for Europe. + +license: MIT-style license + +authors: + - Arian Stolwijk + +requires: + - Locale + +provides: [Locale.EU.Number] + +... +*/ + +Locale.define('EU', 'Number', { + + decimal: ',', + group: '.', + + currency: { + prefix: '⬠' + } + +}); + +/* +--- + +script: Locale.Set.From.js + +name: Locale.Set.From + +description: Provides an alternative way to create Locale.Set objects. + +license: MIT-style license + +authors: + - Tim Wienk + +requires: + - Core/JSON + - Locale + +provides: Locale.Set.From + +... +*/ + +(function(){ + +var parsers = { + 'json': JSON.decode +}; + +Locale.Set.defineParser = function(name, fn){ + parsers[name] = fn; +}; + +Locale.Set.from = function(set, type){ + if (instanceOf(set, Locale.Set)) return set; + + if (!type && typeOf(set) == 'string') type = 'json'; + if (parsers[type]) set = parsers[type](set); + + var locale = new Locale.Set; + + locale.sets = set.sets || {}; + + if (set.inherits){ + locale.inherits.locales = Array.from(set.inherits.locales); + locale.inherits.sets = set.inherits.sets || {}; + } + + return locale; +}; + +})(); + +/* +--- + +name: Locale.ZA.Number + +description: Number messages for ZA. + +license: MIT-style license + +authors: + - Werner Mollentze + +requires: + - Locale + +provides: [Locale.ZA.Number] + +... +*/ + +Locale.define('ZA', 'Number', { + + decimal: '.', + group: ',', + + currency: { + prefix: 'R ' + } + +}); + + + +/* +--- + +name: Locale.af-ZA.Date + +description: Date messages for ZA Afrikaans. + +license: MIT-style license + +authors: + - Werner Mollentze + +requires: + - Locale + +provides: [Locale.af-ZA.Date] + +... +*/ + +Locale.define('af-ZA', 'Date', { + + months: ['Januarie', 'Februarie', 'Maart', 'April', 'Mei', 'Junie', 'Julie', 'Augustus', 'September', 'Oktober', 'November', 'Desember'], + months_abbr: ['Jan', 'Feb', 'Mrt', 'Apr', 'Mei', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'], + days: ['Sondag', 'Maandag', 'Dinsdag', 'Woensdag', 'Donderdag', 'Vrydag', 'Saterdag'], + days_abbr: ['Son', 'Maa', 'Din', 'Woe', 'Don', 'Vry', 'Sat'], + + // Culture's date order: MM/DD/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d-%m-%Y', + shortTime: '%H:%M', + AM: 'VM', + PM: 'NM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: function(dayOfMonth){ + return ((dayOfMonth > 1 && dayOfMonth < 20 && dayOfMonth != 8) || (dayOfMonth > 100 && dayOfMonth.toString().substr(-2, 1) == '1')) ? 'de' : 'ste'; + }, + + lessThanMinuteAgo: 'minder as \'n minuut gelede', + minuteAgo: 'ongeveer \'n minuut gelede', + minutesAgo: '{delta} minute gelede', + hourAgo: 'omtret \'n uur gelede', + hoursAgo: 'ongeveer {delta} ure gelede', + dayAgo: '1 dag gelede', + daysAgo: '{delta} dae gelede', + weekAgo: '1 week gelede', + weeksAgo: '{delta} weke gelede', + monthAgo: '1 maand gelede', + monthsAgo: '{delta} maande gelede', + yearAgo: '1 jaar gelede', + yearsAgo: '{delta} jare gelede', + + lessThanMinuteUntil: 'oor minder as \'n minuut', + minuteUntil: 'oor ongeveer \'n minuut', + minutesUntil: 'oor {delta} minute', + hourUntil: 'oor ongeveer \'n uur', + hoursUntil: 'oor {delta} uur', + dayUntil: 'oor ongeveer \'n dag', + daysUntil: 'oor {delta} dae', + weekUntil: 'oor \'n week', + weeksUntil: 'oor {delta} weke', + monthUntil: 'oor \'n maand', + monthsUntil: 'oor {delta} maande', + yearUntil: 'oor \'n jaar', + yearsUntil: 'oor {delta} jaar' + +}); + +/* +--- + +name: Locale.af-ZA.Form.Validator + +description: Form Validator messages for Afrikaans. + +license: MIT-style license + +authors: + - Werner Mollentze + +requires: + - Locale + +provides: [Locale.af-ZA.Form.Validator] + +... +*/ + +Locale.define('af-ZA', 'FormValidator', { + + required: 'Hierdie veld word vereis.', + length: 'Voer asseblief {length} karakters in (u het {elLength} karakters ingevoer)', + minLength: 'Voer asseblief ten minste {minLength} karakters in (u het {length} karakters ingevoer).', + maxLength: 'Moet asseblief nie meer as {maxLength} karakters invoer nie (u het {length} karakters ingevoer).', + integer: 'Voer asseblief \'n heelgetal in hierdie veld in. Getalle met desimale (bv. 1.25) word nie toegelaat nie.', + numeric: 'Voer asseblief slegs numeriese waardes in hierdie veld in (bv. "1" of "1.1" of "-1" of "-1.1").', + digits: 'Gebruik asseblief slegs nommers en punktuasie in hierdie veld. (by voorbeeld, \'n telefoon nommer wat koppeltekens en punte bevat is toelaatbaar).', + alpha: 'Gebruik asseblief slegs letters (a-z) binne-in hierdie veld. Geen spasies of ander karakters word toegelaat nie.', + alphanum: 'Gebruik asseblief slegs letters (a-z) en nommers (0-9) binne-in hierdie veld. Geen spasies of ander karakters word toegelaat nie.', + dateSuchAs: 'Voer asseblief \'n geldige datum soos {date} in', + dateInFormatMDY: 'Voer asseblief \'n geldige datum soos MM/DD/YYYY in (bv. "12/31/1999")', + email: 'Voer asseblief \'n geldige e-pos adres in. Byvoorbeeld "fred@domain.com".', + url: 'Voer asseblief \'n geldige bronadres (URL) soos http://www.example.com in.', + currencyDollar: 'Voer asseblief \'n geldige $ bedrag in. Byvoorbeeld $100.00 .', + oneRequired: 'Voer asseblief iets in vir ten minste een van hierdie velde.', + errorPrefix: 'Fout: ', + warningPrefix: 'Waarskuwing: ', + + // Form.Validator.Extras + noSpace: 'Daar mag geen spasies in hierdie toevoer wees nie.', + reqChkByNode: 'Geen items is gekies nie.', + requiredChk: 'Hierdie veld word vereis.', + reqChkByName: 'Kies asseblief \'n {label}.', + match: 'Hierdie veld moet by die {matchName} veld pas', + startDate: 'die begin datum', + endDate: 'die eind datum', + currentDate: 'die huidige datum', + afterDate: 'Die datum moet dieselfde of na {label} wees.', + beforeDate: 'Die datum moet dieselfde of voor {label} wees.', + startMonth: 'Kies asseblief \'n begin maand', + sameMonth: 'Hierdie twee datums moet in dieselfde maand wees - u moet een of beide verander.', + creditcard: 'Die ingevoerde kredietkaart nommer is ongeldig. Bevestig asseblief die nommer en probeer weer. {length} syfers is ingevoer.' + +}); + +/* +--- + +name: Locale.af-ZA.Number + +description: Number messages for ZA Afrikaans. + +license: MIT-style license + +authors: + - Werner Mollentze + +requires: + - Locale + - Locale.ZA.Number + +provides: [Locale.af-ZA.Number] + +... +*/ + +Locale.define('af-ZA').inherit('ZA', 'Number'); + +/* +--- + +name: Locale.ar.Date + +description: Date messages for Arabic. + +license: MIT-style license + +authors: + - Chafik Barbar + +requires: + - Locale + +provides: [Locale.ar.Date] + +... +*/ + +Locale.define('ar', 'Date', { + + // Culture's date order: DD/MM/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d/%m/%Y', + shortTime: '%H:%M' + +}); + +/* +--- + +name: Locale.ar.Form.Validator + +description: Form Validator messages for Arabic. + +license: MIT-style license + +authors: + - Chafik Barbar + +requires: + - Locale + +provides: [Locale.ar.Form.Validator] + +... +*/ + +Locale.define('ar', 'FormValidator', { + + required: 'ÙØ°Ø§ Ø§ÙØÙÙ Ù
Ø·ÙÙØš.', + minLength: 'رجاء٠إدخا٠{minLength} Ø£ØØ±Ù عÙÙ Ø§ÙØ£ÙÙ (تÙ
إدخا٠{length} Ø£ØØ±Ù).', + maxLength: 'Ø§ÙØ±Ø¬Ø§Ø¡ عدÙ
Ø¥Ø¯Ø®Ø§Ù Ø£ÙØ«Ø± Ù
Ù {maxLength} Ø£ØØ±Ù (تÙ
إدخا٠{length} Ø£ØØ±Ù).', + integer: 'Ø§ÙØ±Ø¬Ø§Ø¡ إدخا٠عدد صØÙØ ÙÙ ÙØ°Ø§ Ø§ÙØÙÙ. أ٠رÙÙ
Ø°Ù ÙØ³Ø± ع؎ر٠أ٠Ù
ØŠÙÙ (Ù
ثا٠1.25 ) ØºÙØ± Ù
سÙ
ÙØ.', + numeric: 'Ø§ÙØ±Ø¬Ø§Ø¡ إدخا٠ÙÙÙ
رÙÙ
ÙØ© ÙÙ ÙØ°Ø§ Ø§ÙØÙÙ (Ù
ثا٠"1" Ø£Ù "1.1" Ø£Ù "-1" Ø£Ù "-1.1").', + digits: 'Ø§ÙØ±Ø¬Ø§Ø¡ أستخداÙ
ÙÙÙ
رÙÙ
ÙØ© ÙØ¹ÙاÙ
ات ترÙÙÙ
ÙØ© ÙÙØ· ÙÙ ÙØ°Ø§ Ø§ÙØÙÙ (Ù
ثاÙ, رÙÙ
ÙØ§ØªÙ Ù
ع ÙÙØ·Ø© Ø£Ù ØŽØØ·Ø©)', + alpha: 'Ø§ÙØ±Ø¬Ø§Ø¡ أستخداÙ
Ø£ØØ±Ù ÙÙØ· (ا-Ù) ÙÙ ÙØ°Ø§ Ø§ÙØÙÙ. Ø£Ù ÙØ±Ø§ØºØ§Øª Ø£Ù Ø¹ÙØ§Ù
ات ØºÙØ± Ù
سÙ
ÙØØ©.', + alphanum: 'Ø§ÙØ±Ø¬Ø§Ø¡ أستخداÙ
Ø£ØØ±Ù ÙÙØ· (ا-Ù) Ø£Ù Ø£Ø±ÙØ§Ù
(0-9) ÙÙØ· ÙÙ ÙØ°Ø§ Ø§ÙØÙÙ. Ø£Ù ÙØ±Ø§ØºØ§Øª Ø£Ù Ø¹ÙØ§Ù
ات ØºÙØ± Ù
سÙ
ÙØØ©.', + dateSuchAs: 'Ø§ÙØ±Ø¬Ø§Ø¡ Ø¥Ø¯Ø®Ø§Ù ØªØ§Ø±ÙØ® صØÙØ ÙØ§ÙتاÙÙ {date}', + dateInFormatMDY: 'Ø§ÙØ±Ø¬Ø§Ø¡ Ø¥Ø¯Ø®Ø§Ù ØªØ§Ø±ÙØ® صØÙØ (Ù
ثاÙ, 31-12-1999)', + email: 'Ø§ÙØ±Ø¬Ø§Ø¡ Ø¥Ø¯Ø®Ø§Ù ØšØ±ÙØ¯ Ø¥ÙÙØªØ±ÙÙ٠صØÙØ.', + url: 'Ø§ÙØ±Ø¬Ø§Ø¡ إدخا٠عÙÙØ§Ù Ø¥ÙÙØªØ±ÙÙ٠صØÙØ Ù
ث٠http://www.example.com', + currencyDollar: 'Ø§ÙØ±Ø¬Ø§Ø¡ إدخا٠ÙÙÙ
Ø© $ صØÙØØ©. Ù
ثاÙ, 100.00$', + oneRequired: 'Ø§ÙØ±Ø¬Ø§Ø¡ إدخا٠ÙÙÙ
Ø© ÙÙ Ø£ØØ¯ ÙØ°Ù Ø§ÙØÙÙ٠عÙÙ Ø§ÙØ£ÙÙ.', + errorPrefix: 'خطأ: ', + warningPrefix: 'ØªØØ°Ùر: ' + +}); + +/* +--- + +name: Locale.ca-CA.Date + +description: Date messages for Catalan. + +license: MIT-style license + +authors: + - Ãlfons Sanchez + +requires: + - Locale + +provides: [Locale.ca-CA.Date] + +... +*/ + +Locale.define('ca-CA', 'Date', { + + months: ['Gener', 'Febrer', 'Març', 'Abril', 'Maig', 'Juny', 'Juli', 'Agost', 'Setembre', 'Octubre', 'Novembre', 'Desembre'], + months_abbr: ['gen.', 'febr.', 'març', 'abr.', 'maig', 'juny', 'jul.', 'ag.', 'set.', 'oct.', 'nov.', 'des.'], + days: ['Diumenge', 'Dilluns', 'Dimarts', 'Dimecres', 'Dijous', 'Divendres', 'Dissabte'], + days_abbr: ['dg', 'dl', 'dt', 'dc', 'dj', 'dv', 'ds'], + + // Culture's date order: DD/MM/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d/%m/%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 0, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: 'fa menys d`un minut', + minuteAgo: 'fa un minut', + minutesAgo: 'fa {delta} minuts', + hourAgo: 'fa un hora', + hoursAgo: 'fa unes {delta} hores', + dayAgo: 'fa un dia', + daysAgo: 'fa {delta} dies', + + lessThanMinuteUntil: 'menys d`un minut des d`ara', + minuteUntil: 'un minut des d`ara', + minutesUntil: '{delta} minuts des d`ara', + hourUntil: 'un hora des d`ara', + hoursUntil: 'unes {delta} hores des d`ara', + dayUntil: '1 dia des d`ara', + daysUntil: '{delta} dies des d`ara' + +}); + +/* +--- + +name: Locale.ca-CA.Form.Validator + +description: Form Validator messages for Catalan. + +license: MIT-style license + +authors: + - Miquel Hudin + - Ãlfons Sanchez + +requires: + - Locale + +provides: [Locale.ca-CA.Form.Validator] + +... +*/ + +Locale.define('ca-CA', 'FormValidator', { + + required: 'Aquest camp es obligatori.', + minLength: 'Per favor introdueix al menys {minLength} caracters (has introduit {length} caracters).', + maxLength: 'Per favor introdueix no mes de {maxLength} caracters (has introduit {length} caracters).', + integer: 'Per favor introdueix un nombre enter en aquest camp. Nombres amb decimals (p.e. 1,25) no estan permesos.', + numeric: 'Per favor introdueix sols valors numerics en aquest camp (p.e. "1" o "1,1" o "-1" o "-1,1").', + digits: 'Per favor usa sols numeros i puntuacio en aquest camp (per exemple, un nombre de telefon amb guions i punts no esta permes).', + alpha: 'Per favor utilitza lletres nomes (a-z) en aquest camp. No sÂŽadmiteixen espais ni altres caracters.', + alphanum: 'Per favor, utilitza nomes lletres (a-z) o numeros (0-9) en aquest camp. No sÂŽadmiteixen espais ni altres caracters.', + dateSuchAs: 'Per favor introdueix una data valida com {date}', + dateInFormatMDY: 'Per favor introdueix una data valida com DD/MM/YYYY (p.e. "31/12/1999")', + email: 'Per favor, introdueix una adreça de correu electronic valida. Per exemple, "fred@domain.com".', + url: 'Per favor introdueix una URL valida com http://www.example.com.', + currencyDollar: 'Per favor introdueix una quantitat valida de â¬. Per exemple â¬100,00 .', + oneRequired: 'Per favor introdueix alguna cosa per al menys una dÂŽaquestes entrades.', + errorPrefix: 'Error: ', + warningPrefix: 'Avis: ', + + // Form.Validator.Extras + noSpace: 'No poden haver espais en aquesta entrada.', + reqChkByNode: 'No hi han elements seleccionats.', + requiredChk: 'Aquest camp es obligatori.', + reqChkByName: 'Per favor selecciona una {label}.', + match: 'Aquest camp necessita coincidir amb el camp {matchName}', + startDate: 'la data de inici', + endDate: 'la data de fi', + currentDate: 'la data actual', + afterDate: 'La data deu ser igual o posterior a {label}.', + beforeDate: 'La data deu ser igual o anterior a {label}.', + startMonth: 'Per favor selecciona un mes dÂŽorige', + sameMonth: 'Aquestes dos dates deuen estar dins del mateix mes - deus canviar una o altra.' + +}); + +/* +--- + +name: Locale.cs-CZ.Date + +description: Date messages for Czech. + +license: MIT-style license + +authors: + - Jan ÄernÜ chemiX + - Christopher Zukowski + +requires: + - Locale + +provides: [Locale.cs-CZ.Date] + +... +*/ +(function(){ + +// Czech language pluralization rules, see http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html +// one -> n is 1; 1 +// few -> n in 2..4; 2-4 +// other -> everything else 0, 5-999, 1.31, 2.31, 5.31... +var pluralize = function (n, one, few, other){ + if (n == 1) return one; + else if (n == 2 || n == 3 || n == 4) return few; + else return other; +}; + +Locale.define('cs-CZ', 'Date', { + + months: ['Leden', 'Ãnor', 'BÅezen', 'Duben', 'KvÄten', 'Äerven', 'Äervenec', 'Srpen', 'ZáÅÃ', 'ÅÃjen', 'Listopad', 'Prosinec'], + months_abbr: ['ledna', 'února', 'bÅezna', 'dubna', 'kvÄtna', 'Äervna', 'Äervence', 'srpna', 'záÅÃ', 'ÅÃjna', 'listopadu', 'prosince'], + days: ['NedÄle', 'PondÄlÃ', 'ÃterÜ', 'StÅeda', 'Ätvrtek', 'Pátek', 'Sobota'], + days_abbr: ['ne', 'po', 'út', 'st', 'Ät', 'pá', 'so'], + + // Culture's date order: DD.MM.YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d.%m.%Y', + shortTime: '%H:%M', + AM: 'dop.', + PM: 'odp.', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '.', + + lessThanMinuteAgo: 'pÅed chvÃlÃ', + minuteAgo: 'pÅibliÅŸnÄ pÅed minutou', + minutesAgo: function(delta){ return 'pÅed {delta} ' + pluralize(delta, 'minutou', 'minutami', 'minutami'); }, + hourAgo: 'pÅibliÅŸnÄ pÅed hodinou', + hoursAgo: function(delta){ return 'pÅed {delta} ' + pluralize(delta, 'hodinou', 'hodinami', 'hodinami'); }, + dayAgo: 'pÅed dnem', + daysAgo: function(delta){ return 'pÅed {delta} ' + pluralize(delta, 'dnem', 'dny', 'dny'); }, + weekAgo: 'pÅed tÜdnem', + weeksAgo: function(delta){ return 'pÅed {delta} ' + pluralize(delta, 'tÜdnem', 'tÜdny', 'tÜdny'); }, + monthAgo: 'pÅed mÄsÃcem', + monthsAgo: function(delta){ return 'pÅed {delta} ' + pluralize(delta, 'mÄsÃcem', 'mÄsÃci', 'mÄsÃci'); }, + yearAgo: 'pÅed rokem', + yearsAgo: function(delta){ return 'pÅed {delta} ' + pluralize(delta, 'rokem', 'lety', 'lety'); }, + + lessThanMinuteUntil: 'za chvÃli', + minuteUntil: 'pÅibliÅŸnÄ za minutu', + minutesUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'minutu', 'minuty', 'minut'); }, + hourUntil: 'pÅibliÅŸnÄ za hodinu', + hoursUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'hodinu', 'hodiny', 'hodin'); }, + dayUntil: 'za den', + daysUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'den', 'dny', 'dnů'); }, + weekUntil: 'za tÜden', + weeksUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'tÜden', 'tÜdny', 'tÜdnů'); }, + monthUntil: 'za mÄsÃc', + monthsUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'mÄsÃc', 'mÄsÃce', 'mÄsÃců'); }, + yearUntil: 'za rok', + yearsUntil: function(delta){ return 'za {delta} ' + pluralize(delta, 'rok', 'roky', 'let'); } +}); + +})(); + +/* +--- + +name: Locale.cs-CZ.Form.Validator + +description: Form Validator messages for Czech. + +license: MIT-style license + +authors: + - Jan ÄernÜ chemiX + +requires: + - Locale + +provides: [Locale.cs-CZ.Form.Validator] + +... +*/ + +Locale.define('cs-CZ', 'FormValidator', { + + required: 'Tato poloÅŸka je povinná.', + minLength: 'Zadejte prosÃm alespoÅ {minLength} znaků (napsáno {length} znaků).', + maxLength: 'Zadejte prosÃm ménÄ neÅŸ {maxLength} znaků (nápsáno {length} znaků).', + integer: 'Zadejte prosÃm celé ÄÃslo. Desetinná ÄÃsla (napÅ. 1.25) nejsou povolena.', + numeric: 'Zadejte jen ÄÃselné hodnoty (tj. "1" nebo "1.1" nebo "-1" nebo "-1.1").', + digits: 'Zadejte prosÃm pouze ÄÃsla a interpunkÄnà znaménka(napÅÃklad telefonnà ÄÃslo s pomlÄkami nebo teÄkami je povoleno).', + alpha: 'Zadejte prosÃm pouze pÃsmena (a-z). Mezery nebo jiné znaky nejsou povoleny.', + alphanum: 'Zadejte prosÃm pouze pÃsmena (a-z) nebo ÄÃslice (0-9). Mezery nebo jiné znaky nejsou povoleny.', + dateSuchAs: 'Zadejte prosÃm platné datum jako {date}', + dateInFormatMDY: 'Zadejte prosÃm platné datum jako MM / DD / RRRR (tj. "12/31/1999")', + email: 'Zadejte prosÃm platnou e-mailovou adresu. NapÅÃklad "fred@domain.com".', + url: 'Zadejte prosÃm platnou URL adresu jako http://www.example.com.', + currencyDollar: 'Zadejte prosÃm platnou Äástku. NapÅÃklad $100.00.', + oneRequired: 'Zadejte prosÃm alespoÅ jednu hodnotu pro tyto poloÅŸky.', + errorPrefix: 'Chyba: ', + warningPrefix: 'UpozornÄnÃ: ', + + // Form.Validator.Extras + noSpace: 'V této poloÅŸce nejsou povoleny mezery', + reqChkByNode: 'Nejsou vybrány şádné poloÅŸky.', + requiredChk: 'Tato poloÅŸka je vyÅŸadována.', + reqChkByName: 'ProsÃm vyberte {label}.', + match: 'Tato poloÅŸka se musà shodovat s poloÅŸkou {matchName}', + startDate: 'datum zahájenÃ', + endDate: 'datum ukonÄenÃ', + currentDate: 'aktuálnà datum', + afterDate: 'Datum by mÄlo bÜt stejné nebo vÄtšà neÅŸ {label}.', + beforeDate: 'Datum by mÄlo bÜt stejné nebo menšà neÅŸ {label}.', + startMonth: 'Vyberte poÄáteÄnà mÄsÃc.', + sameMonth: 'Tyto dva datumy musà bÜt ve stejném mÄsÃci - zmÄÅte jeden z nich.', + creditcard: 'Zadané ÄÃslo kreditnà karty je neplatné. ProsÃm opravte ho. Bylo zadáno {length} ÄÃsel.' + +}); + +/* +--- + +name: Locale.da-DK.Date + +description: Date messages for Danish. + +license: MIT-style license + +authors: + - Martin Overgaard + - Henrik Hansen + +requires: + - Locale + +provides: [Locale.da-DK.Date] + +... +*/ + +Locale.define('da-DK', 'Date', { + + months: ['Januar', 'Februar', 'Marts', 'April', 'Maj', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'December'], + months_abbr: ['jan.', 'feb.', 'mar.', 'apr.', 'maj.', 'jun.', 'jul.', 'aug.', 'sep.', 'okt.', 'nov.', 'dec.'], + days: ['SÞndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'LÞrdag'], + days_abbr: ['sÞn', 'man', 'tir', 'ons', 'tor', 'fre', 'lÞr'], + + // Culture's date order: DD-MM-YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d-%m-%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '.', + + lessThanMinuteAgo: 'mindre end et minut siden', + minuteAgo: 'omkring et minut siden', + minutesAgo: '{delta} minutter siden', + hourAgo: 'omkring en time siden', + hoursAgo: 'omkring {delta} timer siden', + dayAgo: '1 dag siden', + daysAgo: '{delta} dage siden', + weekAgo: '1 uge siden', + weeksAgo: '{delta} uger siden', + monthAgo: '1 mÃ¥ned siden', + monthsAgo: '{delta} mÃ¥neder siden', + yearAgo: '1 Ã¥r siden', + yearsAgo: '{delta} Ã¥r siden', + + lessThanMinuteUntil: 'mindre end et minut fra nu', + minuteUntil: 'omkring et minut fra nu', + minutesUntil: '{delta} minutter fra nu', + hourUntil: 'omkring en time fra nu', + hoursUntil: 'omkring {delta} timer fra nu', + dayUntil: '1 dag fra nu', + daysUntil: '{delta} dage fra nu', + weekUntil: '1 uge fra nu', + weeksUntil: '{delta} uger fra nu', + monthUntil: '1 mÃ¥ned fra nu', + monthsUntil: '{delta} mÃ¥neder fra nu', + yearUntil: '1 Ã¥r fra nu', + yearsUntil: '{delta} Ã¥r fra nu' + +}); + +/* +--- + +name: Locale.da-DK.Form.Validator + +description: Form Validator messages for Danish. + +license: MIT-style license + +authors: + - Martin Overgaard + +requires: + - Locale + +provides: [Locale.da-DK.Form.Validator] + +... +*/ + +Locale.define('da-DK', 'FormValidator', { + + required: 'Feltet skal udfyldes.', + minLength: 'Skriv mindst {minLength} tegn (du skrev {length} tegn).', + maxLength: 'Skriv maksimalt {maxLength} tegn (du skrev {length} tegn).', + integer: 'Skriv et tal i dette felt. Decimal tal (f.eks. 1.25) er ikke tilladt.', + numeric: 'Skriv kun tal i dette felt (i.e. "1" eller "1.1" eller "-1" eller "-1.1").', + digits: 'Skriv kun tal og tegnsÊtning i dette felt (eksempel, et telefon nummer med bindestreg eller punktum er tilladt).', + alpha: 'Skriv kun bogstaver (a-z) i dette felt. Mellemrum og andre tegn er ikke tilladt.', + alphanum: 'Skriv kun bogstaver (a-z) eller tal (0-9) i dette felt. Mellemrum og andre tegn er ikke tilladt.', + dateSuchAs: 'Skriv en gyldig dato som {date}', + dateInFormatMDY: 'Skriv dato i formatet DD-MM-YYYY (f.eks. "31-12-1999")', + email: 'Skriv en gyldig e-mail adresse. F.eks "fred@domain.com".', + url: 'Skriv en gyldig URL adresse. F.eks "http://www.example.com".', + currencyDollar: 'Skriv et gldigt belÞb. F.eks Kr.100.00 .', + oneRequired: 'Et eller flere af felterne i denne formular skal udfyldes.', + errorPrefix: 'Fejl: ', + warningPrefix: 'Advarsel: ', + + // Form.Validator.Extras + noSpace: 'Der mÃ¥ ikke benyttes mellemrum i dette felt.', + reqChkByNode: 'Foretag et valg.', + requiredChk: 'Dette felt skal udfyldes.', + reqChkByName: 'VÊlg en {label}.', + match: 'Dette felt skal matche {matchName} feltet', + startDate: 'start dato', + endDate: 'slut dato', + currentDate: 'dags dato', + afterDate: 'Datoen skal vÊre stÞrre end eller lig med {label}.', + beforeDate: 'Datoen skal vÊre mindre end eller lig med {label}.', + startMonth: 'VÊlg en start mÃ¥ned', + sameMonth: 'De valgte datoer skal vÊre i samme mÃ¥ned - skift en af dem.' + +}); + +/* +--- + +name: Locale.de-DE.Date + +description: Date messages for German. + +license: MIT-style license + +authors: + - Christoph Pojer + - Frank Rossi + - Ulrich Petri + - Fabian Beiner + +requires: + - Locale + +provides: [Locale.de-DE.Date] + +... +*/ + +Locale.define('de-DE', 'Date', { + + months: ['Januar', 'Februar', 'MÀrz', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'], + months_abbr: ['Jan', 'Feb', 'MÀr', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'], + days: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'], + days_abbr: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'], + + // Culture's date order: DD.MM.YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d.%m.%Y', + shortTime: '%H:%M', + AM: 'vormittags', + PM: 'nachmittags', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '.', + + lessThanMinuteAgo: 'vor weniger als einer Minute', + minuteAgo: 'vor einer Minute', + minutesAgo: 'vor {delta} Minuten', + hourAgo: 'vor einer Stunde', + hoursAgo: 'vor {delta} Stunden', + dayAgo: 'vor einem Tag', + daysAgo: 'vor {delta} Tagen', + weekAgo: 'vor einer Woche', + weeksAgo: 'vor {delta} Wochen', + monthAgo: 'vor einem Monat', + monthsAgo: 'vor {delta} Monaten', + yearAgo: 'vor einem Jahr', + yearsAgo: 'vor {delta} Jahren', + + lessThanMinuteUntil: 'in weniger als einer Minute', + minuteUntil: 'in einer Minute', + minutesUntil: 'in {delta} Minuten', + hourUntil: 'in ca. einer Stunde', + hoursUntil: 'in ca. {delta} Stunden', + dayUntil: 'in einem Tag', + daysUntil: 'in {delta} Tagen', + weekUntil: 'in einer Woche', + weeksUntil: 'in {delta} Wochen', + monthUntil: 'in einem Monat', + monthsUntil: 'in {delta} Monaten', + yearUntil: 'in einem Jahr', + yearsUntil: 'in {delta} Jahren' + +}); + +/* +--- + +name: Locale.de-CH.Date + +description: Date messages for German (Switzerland). + +license: MIT-style license + +authors: + - Michael van der Weg + +requires: + - Locale + - Locale.de-DE.Date + +provides: [Locale.de-CH.Date] + +... +*/ + +Locale.define('de-CH').inherit('de-DE', 'Date'); + +/* +--- + +name: Locale.de-CH.Form.Validator + +description: Form Validator messages for German (Switzerland). + +license: MIT-style license + +authors: + - Michael van der Weg + +requires: + - Locale + +provides: [Locale.de-CH.Form.Validator] + +... +*/ + +Locale.define('de-CH', 'FormValidator', { + + required: 'Dieses Feld ist obligatorisch.', + minLength: 'Geben Sie bitte mindestens {minLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).', + maxLength: 'Bitte geben Sie nicht mehr als {maxLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).', + integer: 'Geben Sie bitte eine ganze Zahl ein. Dezimalzahlen (z.B. 1.25) sind nicht erlaubt.', + numeric: 'Geben Sie bitte nur Zahlenwerte in dieses Eingabefeld ein (z.B. "1", "1.1", "-1" oder "-1.1").', + digits: 'Benutzen Sie bitte nur Zahlen und Satzzeichen in diesem Eingabefeld (erlaubt ist z.B. eine Telefonnummer mit Bindestrichen und Punkten).', + alpha: 'Benutzen Sie bitte nur Buchstaben (a-z) in diesem Feld. Leerzeichen und andere Zeichen sind nicht erlaubt.', + alphanum: 'Benutzen Sie bitte nur Buchstaben (a-z) und Zahlen (0-9) in diesem Eingabefeld. Leerzeichen und andere Zeichen sind nicht erlaubt.', + dateSuchAs: 'Geben Sie bitte ein gültiges Datum ein. Wie zum Beispiel {date}', + dateInFormatMDY: 'Geben Sie bitte ein gültiges Datum ein. Wie zum Beispiel TT.MM.JJJJ (z.B. "31.12.1999")', + email: 'Geben Sie bitte eine gültige E-Mail Adresse ein. Wie zum Beispiel "maria@bernasconi.ch".', + url: 'Geben Sie bitte eine gültige URL ein. Wie zum Beispiel http://www.example.com.', + currencyDollar: 'Geben Sie bitte einen gültigen Betrag in Schweizer Franken ein. Wie zum Beispiel 100.00 CHF .', + oneRequired: 'Machen Sie für mindestens eines der Eingabefelder einen Eintrag.', + errorPrefix: 'Fehler: ', + warningPrefix: 'Warnung: ', + + // Form.Validator.Extras + noSpace: 'In diesem Eingabefeld darf kein Leerzeichen sein.', + reqChkByNode: 'Es wurden keine Elemente gewählt.', + requiredChk: 'Dieses Feld ist obligatorisch.', + reqChkByName: 'Bitte wählen Sie ein {label}.', + match: 'Dieses Eingabefeld muss mit dem Feld {matchName} übereinstimmen.', + startDate: 'Das Anfangsdatum', + endDate: 'Das Enddatum', + currentDate: 'Das aktuelle Datum', + afterDate: 'Das Datum sollte zur gleichen Zeit oder später sein {label}.', + beforeDate: 'Das Datum sollte zur gleichen Zeit oder früher sein {label}.', + startMonth: 'Wählen Sie bitte einen Anfangsmonat', + sameMonth: 'Diese zwei Datumsangaben müssen im selben Monat sein - Sie müssen eine von beiden verändern.', + creditcard: 'Die eingegebene Kreditkartennummer ist ungültig. Bitte überprüfen Sie diese und versuchen Sie es erneut. {length} Zahlen eingegeben.' + +}); + +/* +--- + +name: Locale.de-DE.Form.Validator + +description: Form Validator messages for German. + +license: MIT-style license + +authors: + - Frank Rossi + - Ulrich Petri + - Fabian Beiner + +requires: + - Locale + +provides: [Locale.de-DE.Form.Validator] + +... +*/ + +Locale.define('de-DE', 'FormValidator', { + + required: 'Dieses Eingabefeld muss ausgefÃŒllt werden.', + minLength: 'Geben Sie bitte mindestens {minLength} Zeichen ein (Sie haben nur {length} Zeichen eingegeben).', + maxLength: 'Geben Sie bitte nicht mehr als {maxLength} Zeichen ein (Sie haben {length} Zeichen eingegeben).', + integer: 'Geben Sie in diesem Eingabefeld bitte eine ganze Zahl ein. Dezimalzahlen (z.B. "1.25") sind nicht erlaubt.', + numeric: 'Geben Sie in diesem Eingabefeld bitte nur Zahlenwerte (z.B. "1", "1.1", "-1" oder "-1.1") ein.', + digits: 'Geben Sie in diesem Eingabefeld bitte nur Zahlen und Satzzeichen ein (z.B. eine Telefonnummer mit Bindestrichen und Punkten ist erlaubt).', + alpha: 'Geben Sie in diesem Eingabefeld bitte nur Buchstaben (a-z) ein. Leerzeichen und andere Zeichen sind nicht erlaubt.', + alphanum: 'Geben Sie in diesem Eingabefeld bitte nur Buchstaben (a-z) und Zahlen (0-9) ein. Leerzeichen oder andere Zeichen sind nicht erlaubt.', + dateSuchAs: 'Geben Sie bitte ein gÃŒltiges Datum ein (z.B. "{date}").', + dateInFormatMDY: 'Geben Sie bitte ein gÃŒltiges Datum im Format TT.MM.JJJJ ein (z.B. "31.12.1999").', + email: 'Geben Sie bitte eine gÃŒltige E-Mail-Adresse ein (z.B. "max@mustermann.de").', + url: 'Geben Sie bitte eine gÃŒltige URL ein (z.B. "http://www.example.com").', + currencyDollar: 'Geben Sie bitte einen gÃŒltigen Betrag in EURO ein (z.B. 100.00â¬).', + oneRequired: 'Bitte fÃŒllen Sie mindestens ein Eingabefeld aus.', + errorPrefix: 'Fehler: ', + warningPrefix: 'Warnung: ', + + // Form.Validator.Extras + noSpace: 'Es darf kein Leerzeichen in diesem Eingabefeld sein.', + reqChkByNode: 'Es wurden keine Elemente gewÀhlt.', + requiredChk: 'Dieses Feld muss ausgefÃŒllt werden.', + reqChkByName: 'Bitte wÀhlen Sie ein {label}.', + match: 'Dieses Eingabefeld muss mit dem {matchName} Eingabefeld ÃŒbereinstimmen.', + startDate: 'Das Anfangsdatum', + endDate: 'Das Enddatum', + currentDate: 'Das aktuelle Datum', + afterDate: 'Das Datum sollte zur gleichen Zeit oder spÀter sein als {label}.', + beforeDate: 'Das Datum sollte zur gleichen Zeit oder frÃŒher sein als {label}.', + startMonth: 'WÀhlen Sie bitte einen Anfangsmonat', + sameMonth: 'Diese zwei Datumsangaben mÃŒssen im selben Monat sein - Sie mÃŒssen eines von beiden verÀndern.', + creditcard: 'Die eingegebene Kreditkartennummer ist ungÃŒltig. Bitte ÃŒberprÃŒfen Sie diese und versuchen Sie es erneut. {length} Zahlen eingegeben.' + +}); + +/* +--- + +name: Locale.de-DE.Number + +description: Number messages for German. + +license: MIT-style license + +authors: + - Christoph Pojer + +requires: + - Locale + - Locale.EU.Number + +provides: [Locale.de-DE.Number] + +... +*/ + +Locale.define('de-DE').inherit('EU', 'Number'); + +/* +--- + +name: Locale.el-GR.Date + +description: Date messages for Greek language. + +license: MIT-style license + +authors: + - Periklis Argiriadis + +requires: + - Locale + +provides: [Locale.el-GR.Date] + +... +*/ + +Locale.define('el-GR', 'Date', { + + months: ['ÎαΜοÏ
άÏιοÏ', 'ΊεβÏοÏ
άÏιοÏ', 'ÎάÏÏιοÏ', 'ÎÏÏίλιοÏ', 'ÎάιοÏ', 'ÎοÏΜιοÏ', 'ÎοÏλιοÏ', 'ÎÏγοÏ
ÏÏοÏ', 'ΣεÏÏÎΌβÏιοÏ', 'ÎκÏÏβÏιοÏ', 'ÎοÎΌβÏιοÏ', 'ÎεκÎΌβÏιοÏ'], + months_abbr: ['ÎαΜ', 'Ίεβ', 'ÎαÏ', 'ÎÏÏ', 'Îάι', 'ÎοÏ
Μ', 'ÎοÏ
λ', 'ÎÏ
γ', 'ΣεÏ', 'ÎκÏ', 'Îοε', 'Îεκ'], + days: ['ÎÏ
Ïιακή', 'ÎεÏ
ÏÎÏα', '΀ÏίÏη', '΀εÏάÏÏη', 'Î ÎÎŒÏÏη', 'ΠαÏαÏκεÏ
ή', 'ΣάββαÏο'], + days_abbr: ['ÎÏ
Ï', 'ÎεÏ
', '΀Ïι', '΀εÏ', 'ΠεΌ', 'ΠαÏ', 'Σαβ'], + + // Culture's date order: DD/MM/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d/%m/%Y', + shortTime: '%I:%M%p', + AM: 'ÏÎŒ', + PM: 'ΌΌ', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: function(dayOfMonth){ + // 1st, 2nd, 3rd, etc. + return (dayOfMonth > 3 && dayOfMonth < 21) ? 'οÏ' : ['οÏ'][Math.min(dayOfMonth % 10, 4)]; + }, + + lessThanMinuteAgo: 'λιγÏÏεÏο αÏÏ ÎΜα λεÏÏÏ ÏÏιΜ', + minuteAgo: 'ÏεÏίÏοÏ
ÎΜα λεÏÏÏ ÏÏιΜ', + minutesAgo: '{delta} λεÏÏά ÏÏιΜ', + hourAgo: 'ÏεÏίÏοÏ
Όια ÏÏα ÏÏιΜ', + hoursAgo: 'ÏεÏίÏοÏ
{delta} ÏÏÎµÏ ÏÏιΜ', + dayAgo: '1 ηΌÎÏα ÏÏιΜ', + daysAgo: '{delta} ηΌÎÏÎµÏ ÏÏιΜ', + weekAgo: '1 εβΎοΌάΎα ÏÏιΜ', + weeksAgo: '{delta} ÎµÎ²ÎŽÎ¿ÎŒÎ¬ÎŽÎµÏ ÏÏιΜ', + monthAgo: '1 ΌήΜα ÏÏιΜ', + monthsAgo: '{delta} ÎŒÎ®ÎœÎµÏ ÏÏιΜ', + yearAgo: '1 ÏÏÏΜο ÏÏιΜ', + yearsAgo: '{delta} ÏÏÏΜια ÏÏιΜ', + + lessThanMinuteUntil: 'λιγÏÏεÏο αÏÏ Î»ÎµÏÏÏ Î±ÏÏ ÏÏÏα', + minuteUntil: 'ÏεÏίÏοÏ
ÎΜα λεÏÏÏ Î±ÏÏ ÏÏÏα', + minutesUntil: '{delta} λεÏÏά αÏÏ ÏÏÏα', + hourUntil: 'ÏεÏίÏοÏ
Όια ÏÏα αÏÏ ÏÏÏα', + hoursUntil: 'ÏεÏίÏοÏ
{delta} ÏÏÎµÏ Î±ÏÏ ÏÏÏα', + dayUntil: '1 ηΌÎÏα αÏÏ ÏÏÏα', + daysUntil: '{delta} ηΌÎÏÎµÏ Î±ÏÏ ÏÏÏα', + weekUntil: '1 εβΎοΌάΎα αÏÏ ÏÏÏα', + weeksUntil: '{delta} ÎµÎ²ÎŽÎ¿ÎŒÎ¬ÎŽÎµÏ Î±ÏÏ ÏÏÏα', + monthUntil: '1 ÎŒÎ®ÎœÎ±Ï Î±ÏÏ ÏÏÏα', + monthsUntil: '{delta} ÎŒÎ®ÎœÎµÏ Î±ÏÏ ÏÏÏα', + yearUntil: '1 ÏÏÏÎœÎ¿Ï Î±ÏÏ ÏÏÏα', + yearsUntil: '{delta} ÏÏÏΜια αÏÏ ÏÏÏα' + +}); + +/* +--- + +name: Locale.el-GR.Form.Validator + +description: Form Validator messages for Greek language. + +license: MIT-style license + +authors: + - Dimitris Tsironis + +requires: + - Locale + +provides: [Locale.el-GR.Form.Validator] + +... +*/ + +Locale.define('el-GR', 'FormValidator', { + + required: 'ÎÏ
ÏÏ Ïο ÏεΎίο είΜαι αÏαÏαίÏηÏο.', + length: 'ΠαÏακαλοÏΌε, ειÏάγεÏε {length} ÏαÏακÏήÏÎµÏ (ÎÏεÏε ήΎη ειÏάγει {elLength} ÏαÏακÏήÏεÏ).', + minLength: 'ΠαÏακαλοÏΌε, ειÏάγεÏε ÏοÏ
λάÏιÏÏοΜ {minLength} ÏαÏακÏήÏÎµÏ (ÎÏεÏε ήΎη ειÏάγε {length} ÏαÏακÏήÏεÏ).', + maxlength: 'ΠαÏακαλοÏΌε, ειÏάγεÏε εÏÏ {maxlength} ÏαÏακÏήÏÎµÏ (ÎÏεÏε ήΎη ειÏάγε {length} ÏαÏακÏήÏεÏ).', + integer: 'ΠαÏακαλοÏΌε, ειÏάγεÏε ÎΜαΜ ακÎÏαιο αÏÎ¹ÎžÎŒÏ Ïε αÏ
ÏÏ Ïο ÏεΎίο. Îι αÏιΞΌοί Όε ΎεκαΎικά ÏηÏία (Ï.Ï. 1.25) ΎεΜ εÏιÏÏÎÏοΜÏαι.', + numeric: 'ΠαÏακαλοÏΌε, ειÏάγεÏε ÎŒÏΜο αÏιΞΌηÏικÎÏ ÏιΌÎÏ Ïε αÏ
ÏÏ Ïο ÏεΎίο (Ï.Ï." 1 " ή " 1.1 " ή " -1 " ή " -1.1 " ).', + digits: 'ΠαÏακαλοÏΌε, ÏÏηÏιΌοÏοιήÏÏε ÎŒÏΜο αÏιΞΌοÏÏ ÎºÎ±Î¹ ÏηΌεία ÏÏÎ¯ÎŸÎ·Ï Ïε αÏ
ÏÏΜ ÏοΜ ÏοΌÎα (Ï.Ï. εÏιÏÏÎÏεÏαι αÏιΞΌÏÏ ÏηλεÏÏΜοÏ
Όε ÏαÏÎ»ÎµÏ Î® ÏελείεÏ).', + alpha: 'ΠαÏακαλοÏΌε, ÏÏηÏιΌοÏοιήÏÏε ÎŒÏΜο γÏάΌΌαÏα (a-z) Ïε αÏ
ÏÏ Ïο ÏεΎίο. ÎεΜ εÏιÏÏÎÏοΜÏαι κεΜά ή άλλοι ÏαÏακÏήÏεÏ.', + alphanum: 'ΠαÏακαλοÏΌε, ÏÏηÏιΌοÏοιήÏÏε ÎŒÏΜο γÏάΌΌαÏα (a-z) ή αÏιΞΌοÏÏ (0-9) Ïε αÏ
ÏÏΜ ÏοΜ ÏοΌÎα. ÎεΜ εÏιÏÏÎÏοΜÏαι κεΜά ή άλλοι ÏαÏακÏήÏεÏ.', + dateSuchAs: 'ΠαÏακαλοÏΌε, ειÏάγεÏε Όια ÎγκÏ
Ïη ηΌεÏοΌηΜία, ÏÏÏÏ {date}', + dateInFormatMDY: 'ΠαÏÎ±ÎºÎ±Î»Ï ÎµÎ¹ÏάγεÏε Όια ÎγκÏ
Ïη ηΌεÏοΌηΜία, ÏÏÏÏ ÎÎ/ÎÎ/ÎÎÎÎ (Ï.Ï. "12/31/1999").', + email: 'ΠαÏακαλοÏΌε, ειÏάγεÏε Όια ÎγκÏ
Ïη ΎιεÏΞÏ
ΜÏη ηλεκÏÏÎ¿ÎœÎ¹ÎºÎ¿Ï ÏαÏÏ
ÎŽÏοΌείοÏ
(Ï.Ï. "fred@domain.com").', + url: 'ΠαÏακαλοÏΌε, ειÏάγεÏε Όια ÎγκÏ
Ïη URL ΎιεÏΞÏ
ΜÏη, ÏÏÏÏ http://www.example.com', + currencyDollar: 'ΠαÏακαλοÏΌε, ειÏάγεÏε ÎΜα ÎγκÏ
Ïο ÏοÏÏ Ïε ΎολλάÏια (Ï.Ï. $100.00).', + oneRequired: 'ΠαÏακαλοÏΌε, ειÏάγεÏε κάÏι για ÏοÏ
λάÏιÏÏοΜ ÎΜα αÏÏ Î±Ï
Ïά Ïα ÏεΎία.', + errorPrefix: 'ΣÏάλΌα: ', + warningPrefix: 'Î ÏοÏοÏή: ', + + // Form.Validator.Extras + noSpace: 'ÎεΜ εÏιÏÏÎÏοΜÏαι Ïα κεΜά Ïε αÏ
ÏÏ Ïο ÏεΎίο.', + reqChkByNode: 'ÎεΜ ÎÏει εÏιλεγεί κάÏοιο αΜÏικείΌεΜο', + requiredChk: 'ÎÏ
ÏÏ Ïο ÏεΎίο είΜαι αÏαÏαίÏηÏο.', + reqChkByName: 'ΠαÏακαλοÏΌε, εÏιλÎΟÏε Όια εÏικÎÏα {label}.', + match: 'ÎÏ
ÏÏ Ïο ÏεΎίο ÏÏÎÏει Μα ÏαιÏιάζει Όε Ïο ÏεΎίο {matchName}.', + startDate: 'η ηΌεÏοΌηΜία ÎΜαÏΟηÏ', + endDate: 'η ηΌεÏοΌηΜία λήΟηÏ', + currentDate: 'η ÏÏÎÏοÏ
Ïα ηΌεÏοΌηΜία', + afterDate: 'ΠηΌεÏοΌηΜία ÏÏÎÏει Μα είΜαι η ίΎια ή ΌεÏά αÏÏ ÏηΜ {label}.', + beforeDate: 'ΠηΌεÏοΌηΜία ÏÏÎÏει Μα είΜαι η ίΎια ή ÏÏιΜ αÏÏ ÏηΜ {label}.', + startMonth: 'ΠαÏÎ±ÎºÎ±Î»Ï ÎµÏιλÎΟÏε ÎΜα ΌήΜα αÏÏήÏ.', + sameMonth: 'ÎÏ
ÏÎÏ Î¿Î¹ ÎŽÏο ηΌεÏÎ¿ÎŒÎ·ÎœÎ¯ÎµÏ ÏÏÎÏει Μα ÎÏοÏ
Μ ÏοΜ ίΎιο ΌήΜα - Ξα ÏÏÎÏει Μα αλλάΟεÏε ή Ïο ÎΜα ή Ïο άλλο', + creditcard: 'ΠαÏιΞΌÏÏ ÏÎ·Ï ÏιÏÏÏÏÎ¹ÎºÎ®Ï ÎºÎ¬ÏÏÎ±Ï ÎŽÎµÎœ είΜαι ÎγκÏ
ÏοÏ. ΠαÏακαλοÏΌε ελÎγΟÏε ÏοΜ αÏÎ¹ÎžÎŒÏ ÎºÎ±Î¹ ΎοκιΌάÏÏε ΟαΜά. {length} ÎŒÎ®ÎºÎ¿Ï ÏηÏίÏΜ.' + +}); + +/* +--- + +name: Locale.en-GB.Date + +description: Date messages for British English. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Locale + - Locale.en-US.Date + +provides: [Locale.en-GB.Date] + +... +*/ + +Locale.define('en-GB', 'Date', { + + // Culture's date order: DD/MM/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d/%m/%Y', + shortTime: '%H:%M' + +}).inherit('en-US', 'Date'); + +/* +--- + +name: Locale.en-US.Number + +description: Number messages for US English. + +license: MIT-style license + +authors: + - Arian Stolwijk + +requires: + - Locale + +provides: [Locale.en-US.Number] + +... +*/ + +Locale.define('en-US', 'Number', { + + decimal: '.', + group: ',', + +/* Commented properties are the defaults for Number.format + decimals: 0, + precision: 0, + scientific: null, + + prefix: null, + suffic: null, + + // Negative/Currency/percentage will mixin Number + negative: { + prefix: '-' + },*/ + + currency: { +// decimals: 2, + prefix: '$ ' + }/*, + + percentage: { + decimals: 2, + suffix: '%' + }*/ + +}); + + + +/* +--- + +name: Locale.es-ES.Date + +description: Date messages for Spanish. + +license: MIT-style license + +authors: + - Ãlfons Sanchez + +requires: + - Locale + +provides: [Locale.es-ES.Date] + +... +*/ + +Locale.define('es-ES', 'Date', { + + months: ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre'], + months_abbr: ['ene', 'feb', 'mar', 'abr', 'may', 'jun', 'jul', 'ago', 'sep', 'oct', 'nov', 'dic'], + days: ['Domingo', 'Lunes', 'Martes', 'Miércoles', 'Jueves', 'Viernes', 'Sábado'], + days_abbr: ['dom', 'lun', 'mar', 'mié', 'juv', 'vie', 'sáb'], + + // Culture's date order: DD/MM/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d/%m/%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: 'hace menos de un minuto', + minuteAgo: 'hace un minuto', + minutesAgo: 'hace {delta} minutos', + hourAgo: 'hace una hora', + hoursAgo: 'hace unas {delta} horas', + dayAgo: 'hace un dÃa', + daysAgo: 'hace {delta} dÃas', + weekAgo: 'hace una semana', + weeksAgo: 'hace unas {delta} semanas', + monthAgo: 'hace un mes', + monthsAgo: 'hace {delta} meses', + yearAgo: 'hace un año', + yearsAgo: 'hace {delta} años', + + lessThanMinuteUntil: 'menos de un minuto desde ahora', + minuteUntil: 'un minuto desde ahora', + minutesUntil: '{delta} minutos desde ahora', + hourUntil: 'una hora desde ahora', + hoursUntil: 'unas {delta} horas desde ahora', + dayUntil: 'un dÃa desde ahora', + daysUntil: '{delta} dÃas desde ahora', + weekUntil: 'una semana desde ahora', + weeksUntil: 'unas {delta} semanas desde ahora', + monthUntil: 'un mes desde ahora', + monthsUntil: '{delta} meses desde ahora', + yearUntil: 'un año desde ahora', + yearsUntil: '{delta} años desde ahora' + +}); + +/* +--- + +name: Locale.es-AR.Date + +description: Date messages for Spanish (Argentina). + +license: MIT-style license + +authors: + - Ãlfons Sanchez + - Diego Massanti + +requires: + - Locale + - Locale.es-ES.Date + +provides: [Locale.es-AR.Date] + +... +*/ + +Locale.define('es-AR').inherit('es-ES', 'Date'); + +/* +--- + +name: Locale.es-AR.Form.Validator + +description: Form Validator messages for Spanish (Argentina). + +license: MIT-style license + +authors: + - Diego Massanti + +requires: + - Locale + +provides: [Locale.es-AR.Form.Validator] + +... +*/ + +Locale.define('es-AR', 'FormValidator', { + + required: 'Este campo es obligatorio.', + minLength: 'Por favor ingrese al menos {minLength} caracteres (ha ingresado {length} caracteres).', + maxLength: 'Por favor no ingrese más de {maxLength} caracteres (ha ingresado {length} caracteres).', + integer: 'Por favor ingrese un número entero en este campo. Números con decimales (p.e. 1,25) no se permiten.', + numeric: 'Por favor ingrese solo valores numéricos en este campo (p.e. "1" o "1,1" o "-1" o "-1,1").', + digits: 'Por favor use sólo números y puntuación en este campo (por ejemplo, un número de teléfono con guiones y/o puntos no está permitido).', + alpha: 'Por favor use sólo letras (a-z) en este campo. No se permiten espacios ni otros caracteres.', + alphanum: 'Por favor, usa sólo letras (a-z) o números (0-9) en este campo. No se permiten espacios u otros caracteres.', + dateSuchAs: 'Por favor ingrese una fecha válida como {date}', + dateInFormatMDY: 'Por favor ingrese una fecha válida, utulizando el formato DD/MM/YYYY (p.e. "31/12/1999")', + email: 'Por favor, ingrese una dirección de e-mail válida. Por ejemplo, "fred@dominio.com".', + url: 'Por favor ingrese una URL válida como http://www.example.com.', + currencyDollar: 'Por favor ingrese una cantidad válida de pesos. Por ejemplo $100,00 .', + oneRequired: 'Por favor ingrese algo para por lo menos una de estas entradas.', + errorPrefix: 'Error: ', + warningPrefix: 'Advertencia: ', + + // Form.Validator.Extras + noSpace: 'No se permiten espacios en este campo.', + reqChkByNode: 'No hay elementos seleccionados.', + requiredChk: 'Este campo es obligatorio.', + reqChkByName: 'Por favor selecciona una {label}.', + match: 'Este campo necesita coincidir con el campo {matchName}', + startDate: 'la fecha de inicio', + endDate: 'la fecha de fin', + currentDate: 'la fecha actual', + afterDate: 'La fecha debe ser igual o posterior a {label}.', + beforeDate: 'La fecha debe ser igual o anterior a {label}.', + startMonth: 'Por favor selecciona un mes de origen', + sameMonth: 'Estas dos fechas deben estar en el mismo mes - debes cambiar una u otra.' + +}); + +/* +--- + +name: Locale.es-ES.Form.Validator + +description: Form Validator messages for Spanish. + +license: MIT-style license + +authors: + - Ãlfons Sanchez + +requires: + - Locale + +provides: [Locale.es-ES.Form.Validator] + +... +*/ + +Locale.define('es-ES', 'FormValidator', { + + required: 'Este campo es obligatorio.', + minLength: 'Por favor introduce al menos {minLength} caracteres (has introducido {length} caracteres).', + maxLength: 'Por favor introduce no más de {maxLength} caracteres (has introducido {length} caracteres).', + integer: 'Por favor introduce un número entero en este campo. Números con decimales (p.e. 1,25) no se permiten.', + numeric: 'Por favor introduce solo valores numéricos en este campo (p.e. "1" o "1,1" o "-1" o "-1,1").', + digits: 'Por favor usa solo números y puntuación en este campo (por ejemplo, un número de teléfono con guiones y puntos no esta permitido).', + alpha: 'Por favor usa letras solo (a-z) en este campo. No se admiten espacios ni otros caracteres.', + alphanum: 'Por favor, usa solo letras (a-z) o números (0-9) en este campo. No se admiten espacios ni otros caracteres.', + dateSuchAs: 'Por favor introduce una fecha válida como {date}', + dateInFormatMDY: 'Por favor introduce una fecha válida como DD/MM/YYYY (p.e. "31/12/1999")', + email: 'Por favor, introduce una dirección de email válida. Por ejemplo, "fred@domain.com".', + url: 'Por favor introduce una URL válida como http://www.example.com.', + currencyDollar: 'Por favor introduce una cantidad válida de â¬. Por ejemplo â¬100,00 .', + oneRequired: 'Por favor introduce algo para por lo menos una de estas entradas.', + errorPrefix: 'Error: ', + warningPrefix: 'Aviso: ', + + // Form.Validator.Extras + noSpace: 'No pueden haber espacios en esta entrada.', + reqChkByNode: 'No hay elementos seleccionados.', + requiredChk: 'Este campo es obligatorio.', + reqChkByName: 'Por favor selecciona una {label}.', + match: 'Este campo necesita coincidir con el campo {matchName}', + startDate: 'la fecha de inicio', + endDate: 'la fecha de fin', + currentDate: 'la fecha actual', + afterDate: 'La fecha debe ser igual o posterior a {label}.', + beforeDate: 'La fecha debe ser igual o anterior a {label}.', + startMonth: 'Por favor selecciona un mes de origen', + sameMonth: 'Estas dos fechas deben estar en el mismo mes - debes cambiar una u otra.' + +}); + +/* +--- + +name: Locale.es-VE.Date + +description: Date messages for Spanish (Venezuela). + +license: MIT-style license + +authors: + - Daniel Barreto + +requires: + - Locale + - Locale.es-ES.Date + +provides: [Locale.es-VE.Date] + +... +*/ + +Locale.define('es-VE').inherit('es-ES', 'Date'); + +/* +--- + +name: Locale.es-VE.Form.Validator + +description: Form Validator messages for Spanish (Venezuela). + +license: MIT-style license + +authors: + - Daniel Barreto + +requires: + - Locale + - Locale.es-ES.Form.Validator + +provides: [Locale.es-VE.Form.Validator] + +... +*/ + +Locale.define('es-VE', 'FormValidator', { + + digits: 'Por favor usa solo números y puntuación en este campo. Por ejemplo, un número de teléfono con guiones y puntos no esta permitido.', + alpha: 'Por favor usa solo letras (a-z) en este campo. No se admiten espacios ni otros caracteres.', + currencyDollar: 'Por favor introduce una cantidad válida de Bs. Por ejemplo Bs. 100,00 .', + oneRequired: 'Por favor introduce un valor para por lo menos una de estas entradas.', + + // Form.Validator.Extras + startDate: 'La fecha de inicio', + endDate: 'La fecha de fin', + currentDate: 'La fecha actual' + +}).inherit('es-ES', 'FormValidator'); + +/* +--- + +name: Locale.es-VE.Number + +description: Number messages for Spanish (Venezuela). + +license: MIT-style license + +authors: + - Daniel Barreto + +requires: + - Locale + +provides: [Locale.es-VE.Number] + +... +*/ + +Locale.define('es-VE', 'Number', { + + decimal: ',', + group: '.', +/* + decimals: 0, + precision: 0, +*/ + // Negative/Currency/percentage will mixin Number + negative: { + prefix: '-' + }, + + currency: { + decimals: 2, + prefix: 'Bs. ' + }, + + percentage: { + decimals: 2, + suffix: '%' + } + +}); + +/* +--- + +name: Locale.et-EE.Date + +description: Date messages for Estonian. + +license: MIT-style license + +authors: + - Kevin Valdek + +requires: + - Locale + +provides: [Locale.et-EE.Date] + +... +*/ + +Locale.define('et-EE', 'Date', { + + months: ['jaanuar', 'veebruar', 'mÀrts', 'aprill', 'mai', 'juuni', 'juuli', 'august', 'september', 'oktoober', 'november', 'detsember'], + months_abbr: ['jaan', 'veebr', 'mÀrts', 'apr', 'mai', 'juuni', 'juuli', 'aug', 'sept', 'okt', 'nov', 'dets'], + days: ['pÃŒhapÀev', 'esmaspÀev', 'teisipÀev', 'kolmapÀev', 'neljapÀev', 'reede', 'laupÀev'], + days_abbr: ['pÃŒhap', 'esmasp', 'teisip', 'kolmap', 'neljap', 'reede', 'laup'], + + // Culture's date order: MM.DD.YYYY + dateOrder: ['month', 'date', 'year'], + shortDate: '%m.%d.%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: 'vÀhem kui minut aega tagasi', + minuteAgo: 'umbes minut aega tagasi', + minutesAgo: '{delta} minutit tagasi', + hourAgo: 'umbes tund aega tagasi', + hoursAgo: 'umbes {delta} tundi tagasi', + dayAgo: '1 pÀev tagasi', + daysAgo: '{delta} pÀeva tagasi', + weekAgo: '1 nÀdal tagasi', + weeksAgo: '{delta} nÀdalat tagasi', + monthAgo: '1 kuu tagasi', + monthsAgo: '{delta} kuud tagasi', + yearAgo: '1 aasta tagasi', + yearsAgo: '{delta} aastat tagasi', + + lessThanMinuteUntil: 'vÀhem kui minuti aja pÀrast', + minuteUntil: 'umbes minuti aja pÀrast', + minutesUntil: '{delta} minuti pÀrast', + hourUntil: 'umbes tunni aja pÀrast', + hoursUntil: 'umbes {delta} tunni pÀrast', + dayUntil: '1 pÀeva pÀrast', + daysUntil: '{delta} pÀeva pÀrast', + weekUntil: '1 nÀdala pÀrast', + weeksUntil: '{delta} nÀdala pÀrast', + monthUntil: '1 kuu pÀrast', + monthsUntil: '{delta} kuu pÀrast', + yearUntil: '1 aasta pÀrast', + yearsUntil: '{delta} aasta pÀrast' + +}); + +/* +--- + +name: Locale.et-EE.Form.Validator + +description: Form Validator messages for Estonian. + +license: MIT-style license + +authors: + - Kevin Valdek + +requires: + - Locale + +provides: [Locale.et-EE.Form.Validator] + +... +*/ + +Locale.define('et-EE', 'FormValidator', { + + required: 'VÀli peab olema tÀidetud.', + minLength: 'Palun sisestage vÀhemalt {minLength} tÀhte (te sisestasite {length} tÀhte).', + maxLength: 'Palun Àrge sisestage rohkem kui {maxLength} tÀhte (te sisestasite {length} tÀhte).', + integer: 'Palun sisestage vÀljale tÀisarv. KÃŒmnendarvud (nÀiteks 1.25) ei ole lubatud.', + numeric: 'Palun sisestage ainult numbreid vÀljale (nÀiteks "1", "1.1", "-1" või "-1.1").', + digits: 'Palun kasutage ainult numbreid ja kirjavahemÀrke (telefoninumbri sisestamisel on lubatud kasutada kriipse ja punkte).', + alpha: 'Palun kasutage ainult tÀhti (a-z). TÃŒhikud ja teised sÃŒmbolid on keelatud.', + alphanum: 'Palun kasutage ainult tÀhti (a-z) või numbreid (0-9). TÃŒhikud ja teised sÃŒmbolid on keelatud.', + dateSuchAs: 'Palun sisestage kehtiv kuupÀev kujul {date}', + dateInFormatMDY: 'Palun sisestage kehtiv kuupÀev kujul MM.DD.YYYY (nÀiteks: "12.31.1999").', + email: 'Palun sisestage kehtiv e-maili aadress (nÀiteks: "fred@domain.com").', + url: 'Palun sisestage kehtiv URL (nÀiteks: http://www.example.com).', + currencyDollar: 'Palun sisestage kehtiv $ summa (nÀiteks: $100.00).', + oneRequired: 'Palun sisestage midagi vÀhemalt ÃŒhele antud vÀljadest.', + errorPrefix: 'Viga: ', + warningPrefix: 'Hoiatus: ', + + // Form.Validator.Extras + noSpace: 'VÀli ei tohi sisaldada tÃŒhikuid.', + reqChkByNode: 'Ãkski vÀljadest pole valitud.', + requiredChk: 'VÀlja tÀitmine on vajalik.', + reqChkByName: 'Palun valige ÃŒks {label}.', + match: 'VÀli peab sobima {matchName} vÀljaga', + startDate: 'algkuupÀev', + endDate: 'lõppkuupÀev', + currentDate: 'praegune kuupÀev', + afterDate: 'KuupÀev peab olema võrdne või pÀrast {label}.', + beforeDate: 'KuupÀev peab olema võrdne või enne {label}.', + startMonth: 'Palun valige algkuupÀev.', + sameMonth: 'Antud kaks kuupÀeva peavad olema samas kuus - peate muutma ÃŒhte kuupÀeva.' + +}); + +/* +--- + +name: Locale.fa.Date + +description: Date messages for Persian. + +license: MIT-style license + +authors: + - Amir Hossein Hodjaty Pour + +requires: + - Locale + +provides: [Locale.fa.Date] + +... +*/ + +Locale.define('fa', 'Date', { + + months: ['ÚØ§ÙÙÛÙ', 'ÙÙØ±ÛÙ', 'Ù
ارس', 'آٟرÛÙ', 'Ù
Ù', 'ÚÙØŠÙ', 'ÚÙØŠÛÙ', 'Ø¢Ú¯ÙØ³Øª', 'سٟتاÙ
ؚر', 'اکتؚر', 'ÙÙØ§Ù
ؚر', 'دساÙ
ؚر'], + months_abbr: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'], + days: ['ÛÚ©ØŽÙØšÙ', 'Ø¯ÙØŽÙØšÙ', 'Ø³Ù ØŽÙØšÙ', 'ÚÙØ§Ø±ØŽÙØšÙ', 'ÙŸÙØ¬ØŽÙØšÙ', 'جÙ
عÙ', 'ØŽÙØšÙ'], + days_abbr: ['Ù', 'د', 'س', 'Ú', 'ÙŸ', 'ج', 'ØŽ'], + + // Culture's date order: MM/DD/YYYY + dateOrder: ['month', 'date', 'year'], + shortDate: '%m/%d/%Y', + shortTime: '%I:%M%p', + AM: 'Ù.Øž', + PM: 'Øš.Øž', + + // Date.Extras + ordinal: 'اÙ
', + + lessThanMinuteAgo: 'Ú©Ù
تر از ÛÚ© دÙÛÙÙ ÙŸÛØŽ', + minuteAgo: 'ØØ¯Ùد ÛÚ© دÙÛÙÙ ÙŸÛØŽ', + minutesAgo: '{delta} دÙÛÙÙ ÙŸÛØŽ', + hourAgo: 'ØØ¯Ùد ÛÚ© ساعت ÙŸÛØŽ', + hoursAgo: 'ØØ¯Ùد {delta} ساعت ÙŸÛØŽ', + dayAgo: '1 Ø±ÙØ² ÙŸÛØŽ', + daysAgo: '{delta} Ø±ÙØ² ÙŸÛØŽ', + weekAgo: '1 ÙÙØªÙ ÙŸÛØŽ', + weeksAgo: '{delta} ÙÙØªÙ ÙŸÛØŽ', + monthAgo: '1 Ù
Ø§Ù ÙŸÛØŽ', + monthsAgo: '{delta} Ù
Ø§Ù ÙŸÛØŽ', + yearAgo: '1 Ø³Ø§Ù ÙŸÛØŽ', + yearsAgo: '{delta} Ø³Ø§Ù ÙŸÛØŽ', + + lessThanMinuteUntil: 'Ú©Ù
تر از ÛÚ© دÙÛÙ٠از ØØ§Ùا', + minuteUntil: 'ØØ¯Ùد ÛÚ© دÙÛÙ٠از ØØ§Ùا', + minutesUntil: '{delta} دÙÛÙ٠از ØØ§Ùا', + hourUntil: 'ØØ¯Ùد ÛÚ© ساعت از ØØ§Ùا', + hoursUntil: 'ØØ¯Ùد {delta} ساعت از ØØ§Ùا', + dayUntil: '1 Ø±ÙØ² از ØØ§Ùا', + daysUntil: '{delta} Ø±ÙØ² از ØØ§Ùا', + weekUntil: '1 ÙÙØªÙ از ØØ§Ùا', + weeksUntil: '{delta} ÙÙØªÙ از ØØ§Ùا', + monthUntil: '1 Ù
ا٠از ØØ§Ùا', + monthsUntil: '{delta} Ù
ا٠از ØØ§Ùا', + yearUntil: '1 سا٠از ØØ§Ùا', + yearsUntil: '{delta} سا٠از ØØ§Ùا' + +}); + +/* +--- + +name: Locale.fa.Form.Validator + +description: Form Validator messages for Persian. + +license: MIT-style license + +authors: + - Amir Hossein Hodjaty Pour + +requires: + - Locale + +provides: [Locale.fa.Form.Validator] + +... +*/ + +Locale.define('fa', 'FormValidator', { + + required: 'اÛÙ ÙÛÙØ¯ Ø§ÙØ²Ø§Ù
Û Ø§Ø³Øª.', + minLength: 'ØŽÙ
ا ØšØ§ÛØ¯ ØØ¯Ø§ÙÙ {minLength} ØØ±Ù ÙØ§Ø±Ø¯ Ú©ÙÛØ¯ ({length} ØØ±Ù ÙØ§Ø±Ø¯ Ú©Ø±Ø¯Ù Ø§ÛØ¯).', + maxLength: 'ÙØ·Ùا ØØ¯Ø§Ú©Ø«Ø± {maxLength} ØØ±Ù ÙØ§Ø±Ø¯ Ú©ÙÛØ¯ (ØŽÙ
ا {length} ØØ±Ù ÙØ§Ø±Ø¯ Ú©Ø±Ø¯Ù Ø§ÛØ¯).', + integer: 'ÙØ·Ùا از عدد صØÛØ Ø§Ø³ØªÙØ§Ø¯Ù Ú©ÙÛØ¯. اعداد Ø§Ø¹ØŽØ§Ø±Û (Ù
اÙÙØ¯ 1.25) Ù
جاز ÙÛØ³ØªÙد.', + numeric: 'ÙØ·Ùا ÙÙØ· Ø¯Ø§Ø¯Ù Ø¹Ø¯Ø¯Û ÙØ§Ø±Ø¯ Ú©ÙÛØ¯ (Ù
اÙÙØ¯ "1" ÛØ§ "1.1" ÛØ§ "1-" ÛØ§ "1.1-").', + digits: 'ÙØ·Ùا ÙÙØ· از اعداد Ù Ø¹ÙØ§Ù
ØªÙØ§ در اÛÙ ÙÛÙØ¯ Ø§Ø³ØªÙØ§Ø¯Ù Ú©ÙÛØ¯ (ØšØ±Ø§Û Ù
ثا٠؎Ù
ار٠تÙÙ٠ؚا خط ØªÛØ±Ù Ù ÙÙØ·Ù ÙØ§ØšÙ ÙØšÙ٠است).', + alpha: 'ÙØ·Ùا ÙÙØ· از ØØ±Ù٠اÙÙØšØ§Ø¡ ØšØ±Ø§Û Ø§Û٠ؚخ؎ Ø§Ø³ØªÙØ§Ø¯Ù Ú©ÙÛØ¯. Ú©Ø§Ø±Ø§Ú©ØªØ±ÙØ§Û دÛگر Ù ÙØ§ØµÙÙ Ù
جاز ÙÛØ³ØªÙد.', + alphanum: 'ÙØ·Ùا ÙÙØ· از ØØ±Ù٠اÙÙØšØ§Ø¡ ٠اعداد در اÛ٠ؚخ؎ Ø§Ø³ØªÙØ§Ø¯Ù Ú©ÙÛØ¯. Ú©Ø§Ø±Ø§Ú©ØªØ±ÙØ§Û دÛگر Ù ÙØ§ØµÙÙ Ù
جاز ÙÛØ³ØªÙد.', + dateSuchAs: 'ÙØ·Ùا ÛÚ© ØªØ§Ø±ÛØ® Ù
عتؚر Ù
اÙÙØ¯ {date} ÙØ§Ø±Ø¯ Ú©ÙÛØ¯.', + dateInFormatMDY: 'ÙØ·Ùا ÛÚ© ØªØ§Ø±ÛØ® Ù
عتؚر ØšÙ ØŽÚ©Ù MM/DD/YYYY ÙØ§Ø±Ø¯ Ú©ÙÛØ¯ (Ù
اÙÙØ¯ "12/31/1999").', + email: 'ÙØ·Ùا ÛÚ© آدرس اÛÙ
ÛÙ Ù
عتؚر ÙØ§Ø±Ø¯ Ú©ÙÛØ¯. ØšØ±Ø§Û Ù
ثا٠"fred@domain.com".', + url: 'ÙØ·Ùا ÛÚ© URL Ù
عتؚر Ù
اÙÙØ¯ http://www.example.com ÙØ§Ø±Ø¯ Ú©ÙÛØ¯.', + currencyDollar: 'ÙØ·Ùا ÛÚ© Ù
ØØ¯Ùد٠Ù
عتؚر ØšØ±Ø§Û Ø§Û٠ؚخ؎ ÙØ§Ø±Ø¯ Ú©ÙÛØ¯ Ù
اÙÙØ¯ 100.00$ .', + oneRequired: 'ÙØ·Ùا ØØ¯Ø§ÙÙ ÛÚ©Û Ø§Ø² ÙÛÙØ¯Ùا را ٟر Ú©ÙÛØ¯.', + errorPrefix: 'خطا: ', + warningPrefix: 'ÙØŽØ¯Ø§Ø±: ', + + // Form.Validator.Extras + noSpace: 'Ø§Ø³ØªÙØ§Ø¯Ù از ÙØ§ØµÙ٠در اÛ٠ؚخ؎ Ù
جاز ÙÛØ³Øª.', + reqChkByNode: 'Ù
ÙØ±Ø¯Û Ø§ÙØªØ®Ø§Øš ÙØŽØ¯Ù است.', + requiredChk: 'اÛÙ ÙÛÙØ¯ Ø§ÙØ²Ø§Ù
Û Ø§Ø³Øª.', + reqChkByName: 'ÙØ·Ùا ÛÚ© {label} را Ø§ÙØªØ®Ø§Øš Ú©ÙÛØ¯.', + match: 'اÛÙ ÙÛÙØ¯ ØšØ§ÛØ¯ ؚا ÙÛÙØ¯ {matchName} Ù
Ø·Ø§ØšÙØª دا؎ت٠ؚا؎د.', + startDate: 'ØªØ§Ø±ÛØ® ØŽØ±ÙØ¹', + endDate: 'ØªØ§Ø±ÛØ® ÙŸØ§ÛØ§Ù', + currentDate: 'ØªØ§Ø±ÛØ® Ú©ÙÙÙÛ', + afterDate: 'ØªØ§Ø±ÛØ® Ù
ÛØšØ§Ûست ؚراؚر ÛØ§ ؚعد از {label} ؚا؎د', + beforeDate: 'ØªØ§Ø±ÛØ® Ù
ÛØšØ§Ûست ؚراؚر ÛØ§ ÙØšÙ از {label} ؚا؎د', + startMonth: 'ÙØ·Ùا Ù
Ø§Ù ØŽØ±ÙØ¹ را Ø§ÙØªØ®Ø§Øš Ú©ÙÛØ¯', + sameMonth: 'اÛÙ Ø¯Ù ØªØ§Ø±ÛØ® ØšØ§ÛØ¯ در ÛÚ© Ù
Ø§Ù ØšØ§ØŽÙØ¯ - ØŽÙ
ا ØšØ§ÛØ¯ ÛÚ©Û ÛØ§ ÙØ± د٠را تغÛÛØ± دÙÛØ¯.', + creditcard: 'ØŽÙ
ار٠کارت Ø§Ø¹ØªØšØ§Ø±Û Ú©Ù ÙØ§Ø±Ø¯ Ú©Ø±Ø¯Ù Ø§ÛØ¯ Ù
عتؚر ÙÛØ³Øª. ÙØ·Ùا ØŽÙ
ار٠را ØšØ±Ø±Ø³Û Ú©ÙÛØ¯ Ù Ù
جددا ØªÙØ§ØŽ Ú©ÙÛØ¯. {length} رÙÙ
ÙØ§Ø±Ø¯ ؎د٠است.' + +}); + +/* +--- + +name: Locale.fi-FI.Date + +description: Date messages for Finnish. + +license: MIT-style license + +authors: + - ksel + +requires: + - Locale + +provides: [Locale.fi-FI.Date] + +... +*/ + +Locale.define('fi-FI', 'Date', { + + // NOTE: months and days are not capitalized in finnish + months: ['tammikuu', 'helmikuu', 'maaliskuu', 'huhtikuu', 'toukokuu', 'kesÀkuu', 'heinÀkuu', 'elokuu', 'syyskuu', 'lokakuu', 'marraskuu', 'joulukuu'], + + // these abbreviations are really not much used in finnish because they obviously won't abbreviate very much. ;) + // NOTE: sometimes one can see forms such as "tammi", "helmi", etc. but that is not proper finnish. + months_abbr: ['tammik.', 'helmik.', 'maalisk.', 'huhtik.', 'toukok.', 'kesÀk.', 'heinÀk.', 'elok.', 'syysk.', 'lokak.', 'marrask.', 'jouluk.'], + + days: ['sunnuntai', 'maanantai', 'tiistai', 'keskiviikko', 'torstai', 'perjantai', 'lauantai'], + days_abbr: ['su', 'ma', 'ti', 'ke', 'to', 'pe', 'la'], + + // Culture's date order: DD/MM/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d.%m.%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '.', + + lessThanMinuteAgo: 'vajaa minuutti sitten', + minuteAgo: 'noin minuutti sitten', + minutesAgo: '{delta} minuuttia sitten', + hourAgo: 'noin tunti sitten', + hoursAgo: 'noin {delta} tuntia sitten', + dayAgo: 'pÀivÀ sitten', + daysAgo: '{delta} pÀivÀÀ sitten', + weekAgo: 'viikko sitten', + weeksAgo: '{delta} viikkoa sitten', + monthAgo: 'kuukausi sitten', + monthsAgo: '{delta} kuukautta sitten', + yearAgo: 'vuosi sitten', + yearsAgo: '{delta} vuotta sitten', + + lessThanMinuteUntil: 'vajaan minuutin kuluttua', + minuteUntil: 'noin minuutin kuluttua', + minutesUntil: '{delta} minuutin kuluttua', + hourUntil: 'noin tunnin kuluttua', + hoursUntil: 'noin {delta} tunnin kuluttua', + dayUntil: 'pÀivÀn kuluttua', + daysUntil: '{delta} pÀivÀn kuluttua', + weekUntil: 'viikon kuluttua', + weeksUntil: '{delta} viikon kuluttua', + monthUntil: 'kuukauden kuluttua', + monthsUntil: '{delta} kuukauden kuluttua', + yearUntil: 'vuoden kuluttua', + yearsUntil: '{delta} vuoden kuluttua' + +}); + +/* +--- + +name: Locale.fi-FI.Form.Validator + +description: Form Validator messages for Finnish. + +license: MIT-style license + +authors: + - ksel + +requires: + - Locale + +provides: [Locale.fi-FI.Form.Validator] + +... +*/ + +Locale.define('fi-FI', 'FormValidator', { + + required: 'TÀmÀ kenttÀ on pakollinen.', + minLength: 'Ole hyvÀ ja anna vÀhintÀÀn {minLength} merkkiÀ (annoit {length} merkkiÀ).', + maxLength: 'ÃlÀ anna enempÀÀ kuin {maxLength} merkkiÀ (annoit {length} merkkiÀ).', + integer: 'Ole hyvÀ ja anna kokonaisluku. Luvut, joissa on desimaaleja (esim. 1.25) eivÀt ole sallittuja.', + numeric: 'Anna tÀhÀn kenttÀÀn lukuarvo (kuten "1" tai "1.1" tai "-1" tai "-1.1").', + digits: 'KÀytÀ pelkÀstÀÀn numeroita ja vÀlimerkkejÀ tÀssÀ kentÀssÀ (syötteet, kuten esim. puhelinnumero, jossa on vÀliviivoja, pilkkuja tai pisteitÀ, kelpaa).', + alpha: 'Anna tÀhÀn kenttÀÀn vain kirjaimia (a-z). VÀlilyönnit tai muut merkit eivÀt ole sallittuja.', + alphanum: 'Anna tÀhÀn kenttÀÀn vain kirjaimia (a-z) tai numeroita (0-9). VÀlilyönnit tai muut merkit eivÀt ole sallittuja.', + dateSuchAs: 'Ole hyvÀ ja anna kelvollinen pÀivmÀÀrÀ, kuten esimerkiksi {date}', + dateInFormatMDY: 'Ole hyvÀ ja anna kelvollinen pÀivÀmÀÀrÀ muodossa pp/kk/vvvv (kuten "12/31/1999")', + email: 'Ole hyvÀ ja anna kelvollinen sÀhköpostiosoite (kuten esimerkiksi "matti@meikalainen.com").', + url: 'Ole hyvÀ ja anna kelvollinen URL, kuten esimerkiksi http://www.example.com.', + currencyDollar: 'Ole hyvÀ ja anna kelvollinen eurosumma (kuten esimerkiksi 100,00 EUR) .', + oneRequired: 'Ole hyvÀ ja syötÀ jotakin ainakin johonkin nÀistÀ kentistÀ.', + errorPrefix: 'Virhe: ', + warningPrefix: 'Varoitus: ', + + // Form.Validator.Extras + noSpace: 'TÀssÀ syötteessÀ ei voi olla vÀlilyöntejÀ', + reqChkByNode: 'Ei valintoja.', + requiredChk: 'TÀmÀ kenttÀ on pakollinen.', + reqChkByName: 'Ole hyvÀ ja valitse {label}.', + match: 'TÀmÀn kentÀn tulee vastata kenttÀÀ {matchName}', + startDate: 'alkupÀivÀmÀÀrÀ', + endDate: 'loppupÀivÀmÀÀrÀ', + currentDate: 'nykyinen pÀivÀmÀÀrÀ', + afterDate: 'PÀivÀmÀÀrÀn tulisi olla sama tai myöhÀisempi ajankohta kuin {label}.', + beforeDate: 'PÀivÀmÀÀrÀn tulisi olla sama tai aikaisempi ajankohta kuin {label}.', + startMonth: 'Ole hyvÀ ja valitse aloituskuukausi', + sameMonth: 'NÀiden kahden pÀivÀmÀÀrÀn tulee olla saman kuun sisÀllÀ -- sinun pitÀÀ muuttaa jompaa kumpaa.', + creditcard: 'Annettu luottokortin numero ei kelpaa. Ole hyvÀ ja tarkista numero sekÀ yritÀ uudelleen. {length} numeroa syötetty.' + +}); + +/* +--- + +name: Locale.fi-FI.Number + +description: Finnish number messages + +license: MIT-style license + +authors: + - ksel + +requires: + - Locale + - Locale.EU.Number + +provides: [Locale.fi-FI.Number] + +... +*/ + +Locale.define('fi-FI', 'Number', { + + group: ' ' // grouped by space + +}).inherit('EU', 'Number'); + +/* +--- + +name: Locale.fr-FR.Date + +description: Date messages for French. + +license: MIT-style license + +authors: + - Nicolas Sorosac + - Antoine Abt + +requires: + - Locale + +provides: [Locale.fr-FR.Date] + +... +*/ + +Locale.define('fr-FR', 'Date', { + + months: ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'], + months_abbr: ['janv.', 'févr.', 'mars', 'avr.', 'mai', 'juin', 'juil.', 'août', 'sept.', 'oct.', 'nov.', 'déc.'], + days: ['Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi'], + days_abbr: ['dim.', 'lun.', 'mar.', 'mer.', 'jeu.', 'ven.', 'sam.'], + + // Culture's date order: DD/MM/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d/%m/%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: function(dayOfMonth){ + return (dayOfMonth > 1) ? '' : 'er'; + }, + + lessThanMinuteAgo: "il y a moins d'une minute", + minuteAgo: 'il y a une minute', + minutesAgo: 'il y a {delta} minutes', + hourAgo: 'il y a une heure', + hoursAgo: 'il y a {delta} heures', + dayAgo: 'il y a un jour', + daysAgo: 'il y a {delta} jours', + weekAgo: 'il y a une semaine', + weeksAgo: 'il y a {delta} semaines', + monthAgo: 'il y a 1 mois', + monthsAgo: 'il y a {delta} mois', + yearthAgo: 'il y a 1 an', + yearsAgo: 'il y a {delta} ans', + + lessThanMinuteUntil: "dans moins d'une minute", + minuteUntil: 'dans une minute', + minutesUntil: 'dans {delta} minutes', + hourUntil: 'dans une heure', + hoursUntil: 'dans {delta} heures', + dayUntil: 'dans un jour', + daysUntil: 'dans {delta} jours', + weekUntil: 'dans 1 semaine', + weeksUntil: 'dans {delta} semaines', + monthUntil: 'dans 1 mois', + monthsUntil: 'dans {delta} mois', + yearUntil: 'dans 1 an', + yearsUntil: 'dans {delta} ans' + +}); + +/* +--- + +name: Locale.fr-FR.Form.Validator + +description: Form Validator messages for French. + +license: MIT-style license + +authors: + - Miquel Hudin + - Nicolas Sorosac + +requires: + - Locale + +provides: [Locale.fr-FR.Form.Validator] + +... +*/ + +Locale.define('fr-FR', 'FormValidator', { + + required: 'Ce champ est obligatoire.', + length: 'Veuillez saisir {length} caractère(s) (vous avez saisi {elLength} caractère(s)', + minLength: 'Veuillez saisir un minimum de {minLength} caractère(s) (vous avez saisi {length} caractère(s)).', + maxLength: 'Veuillez saisir un maximum de {maxLength} caractère(s) (vous avez saisi {length} caractère(s)).', + integer: 'Veuillez saisir un nombre entier dans ce champ. Les nombres décimaux (ex : "1,25") ne sont pas autorisés.', + numeric: 'Veuillez saisir uniquement des chiffres dans ce champ (ex : "1" ou "1,1" ou "-1" ou "-1,1").', + digits: "Veuillez saisir uniquement des chiffres et des signes de ponctuation dans ce champ (ex : un numéro de téléphone avec des traits d'union est autorisé).", + alpha: 'Veuillez saisir uniquement des lettres (a-z) dans ce champ. Les espaces ou autres caractères ne sont pas autorisés.', + alphanum: 'Veuillez saisir uniquement des lettres (a-z) ou des chiffres (0-9) dans ce champ. Les espaces ou autres caractères ne sont pas autorisés.', + dateSuchAs: 'Veuillez saisir une date correcte comme {date}', + dateInFormatMDY: 'Veuillez saisir une date correcte, au format JJ/MM/AAAA (ex : "31/11/1999").', + email: 'Veuillez saisir une adresse de courrier électronique. Par exemple "fred@domaine.com".', + url: 'Veuillez saisir une URL, comme http://www.exemple.com.', + currencyDollar: 'Veuillez saisir une quantité correcte. Par exemple 100,00€.', + oneRequired: 'Veuillez sélectionner au moins une de ces options.', + errorPrefix: 'Erreur : ', + warningPrefix: 'Attention : ', + + // Form.Validator.Extras + noSpace: "Ce champ n'accepte pas les espaces.", + reqChkByNode: "Aucun élément n'est sélectionné.", + requiredChk: 'Ce champ est obligatoire.', + reqChkByName: 'Veuillez sélectionner un(e) {label}.', + match: 'Ce champ doit correspondre avec le champ {matchName}.', + startDate: 'date de début', + endDate: 'date de fin', + currentDate: 'date actuelle', + afterDate: 'La date doit être identique ou postérieure à {label}.', + beforeDate: 'La date doit être identique ou antérieure à {label}.', + startMonth: 'Veuillez sélectionner un mois de début.', + sameMonth: 'Ces deux dates doivent être dans le même mois - vous devez en modifier une.', + creditcard: 'Le numéro de carte de crédit est invalide. Merci de vérifier le numéro et de réessayer. Vous avez entré {length} chiffre(s).' + +}); + +/* +--- + +name: Locale.fr-FR.Number + +description: Number messages for French. + +license: MIT-style license + +authors: + - Arian Stolwijk + - sv1l + +requires: + - Locale + - Locale.EU.Number + +provides: [Locale.fr-FR.Number] + +... +*/ + +Locale.define('fr-FR', 'Number', { + + group: ' ' // In fr-FR localization, group character is a blank space + +}).inherit('EU', 'Number'); + +/* +--- + +name: Locale.he-IL.Date + +description: Date messages for Hebrew. + +license: MIT-style license + +authors: + - Elad Ossadon + +requires: + - Locale + +provides: [Locale.he-IL.Date] + +... +*/ + +Locale.define('he-IL', 'Date', { + + months: ['×× ××ך', '×€×ך××ך', '×ךץ', '×׀ך××', '×××', '××× ×', '××××', '××××ס×', 'ס׀×××ך', '×××§×××ך', '× ××××ך', '×׊××ך'], + months_abbr: ['×× ××ך', '×€×ך××ך', '×ךץ', '×׀ך××', '×××', '××× ×', '××××', '××××ס×', 'ס׀×××ך', '×××§×××ך', '× ××××ך', '×׊××ך'], + days: ['ך×ש××', '×©× ×', 'ש××ש×', 'ך×××¢×', '×××ש×', 'ש×ש×', 'ש×ת'], + days_abbr: ['ך×ש××', '×©× ×', 'ש××ש×', 'ך×××¢×', '×××ש×', 'ש×ש×', 'ש×ת'], + + // Culture's date order: MM/DD/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d/%m/%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 0, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: '××€× × ×€××ת ×××§×', + minuteAgo: '××€× × ×××§×', + minutesAgo: '××€× × {delta} ××§×ת', + hourAgo: '××€× × ×שע×', + hoursAgo: '××€× × {delta} שע×ת', + dayAgo: '××€× × ×××', + daysAgo: '××€× × {delta} ××××', + weekAgo: '××€× × ×©×××¢', + weeksAgo: '××€× × {delta} ש×××¢×ת', + monthAgo: '××€× × ×××ש', + monthsAgo: '××€× × {delta} ×××ש××', + yearAgo: '××€× × ×©× ×', + yearsAgo: '××€× × {delta} ×©× ××', + + lessThanMinuteUntil: '××¢×× ×€××ת ×××§×', + minuteUntil: '××¢×× ×××§×', + minutesUntil: '××¢×× {delta} ××§×ת', + hourUntil: '××¢×× ×שע×', + hoursUntil: '××¢×× {delta} שע×ת', + dayUntil: '××¢×× ×××', + daysUntil: '××¢×× {delta} ××××', + weekUntil: '××¢×× ×©×××¢', + weeksUntil: '××¢×× {delta} ש×××¢×ת', + monthUntil: '××¢×× ×××ש', + monthsUntil: '××¢×× {delta} ×××ש××', + yearUntil: '××¢×× ×©× ×', + yearsUntil: '××¢×× {delta} ×©× ××' + +}); + +/* +--- + +name: Locale.he-IL.Form.Validator + +description: Form Validator messages for Hebrew. + +license: MIT-style license + +authors: + - Elad Ossadon + +requires: + - Locale + +provides: [Locale.he-IL.Form.Validator] + +... +*/ + +Locale.define('he-IL', 'FormValidator', { + + required: '× × ×××× ×©×× ××.', + minLength: '× × ××××× ××€××ת {minLength} ת×××× (××× ×ª {length} ת××××).', + maxLength: '× × ××××× ×¢× {maxLength} ת×××× (××× ×ª {length} ת××××).', + integer: '× × ××××× ×ס׀ך ש×× ×ש×× ××. ×ס׀ך×× ×¢×©×š×× ××× (××× 1.25) ××× × ×××§×××.', + numeric: '× × ××××× ×¢×š× ××¡×€×š× ×××× ×ש×× ×× (××× "1", "1.1", "-1" ×× "-1.1").', + digits: '× × ××××× ×š×§ ס׀ך×ת ×ס××× × ×׀ך×× ×ש×× ×× (××ש×, ×ס׀ך ×××€×× ×¢× ××§×€×× ×× × ×§×××ת ××× ×××§×).', + alpha: '× × ××××× ×š×§ ××ת××ת ××× ×××ת (a-z) ×ש×× ××. ך××××× ×× ×ª×××× ××ך×× ××× × ×××§×××.', + alphanum: '× × ××××× ×š×§ ××תך××ת ××× ×××ת (a-z) ×× ×¡×€×š×ת (0-9) ×ש×× ××. ××××ך×× ×× ×ª×××× ××ך×× ××× × ×××§×××.', + dateSuchAs: '× × ××××× ×ª×ך×× ×××§×, ××× {date}', + dateInFormatMDY: '× × ××××× ×ª×ך×× ×××§× ××€×ך×× MM/DD/YYYY (××× "12/31/1999")', + email: '× × ××××× ×ת××ת ×××××× ×××§×ת. ××××××: "fred@domain.com".', + url: '× × ××××× ×ת××ת ×תך ×××§×ת, ××× http://www.example.com.', + currencyDollar: '× × ××××× ×¡××× ××××š× ×××§×. ×××××× $100.00.', + oneRequired: '× × ××××ך ××€××ת ×ש×× ×××.', + errorPrefix: 'ש××××: ', + warningPrefix: '×××ך×: ', + + // Form.Validator.Extras + noSpace: '××× ××××× ×š××××× ×ש×× ××.', + reqChkByNode: '× × ××××ך ××ת ×××׀שך×××ת.', + requiredChk: 'ש×× ×× × ×ךש.', + reqChkByName: '× × ××××ך {label}.', + match: 'ש×× ×× ×Š×š×× ××ת××× ×ש×× {matchName}', + startDate: 'ת×ך×× ××ת×××', + endDate: 'ת×ך×× ×ס×××', + currentDate: '×ת×ך×× ×× ××××', + afterDate: '×ת×ך×× ×Š×š×× ××××ת ××× ×× ×××š× {label}.', + beforeDate: '×ת×ך×× ×Š×š×× ××××ת ××× ×× ××€× × {label}.', + startMonth: '× × ××××ך ×××ש ×ת×××', + sameMonth: '×©× × ×ª×ך×××× ××× ×Š×š×××× ××××ת ××××ª× ×××ש - × × ××©× ×ת ××× ×ת×ך××××.', + creditcard: '×ס׀ך ×ך××ס ××שך×× ×©×××× ××× × ×××§×. × × ×××××§ ×©× ×ת. ×××× × {length} ס׀ך×ת.' + +}); + +/* +--- + +name: Locale.he-IL.Number + +description: Number messages for Hebrew. + +license: MIT-style license + +authors: + - Elad Ossadon + +requires: + - Locale + +provides: [Locale.he-IL.Number] + +... +*/ + +Locale.define('he-IL', 'Number', { + + decimal: '.', + group: ',', + + currency: { + suffix: ' âª' + } + +}); + +/* +--- + +name: Locale.hu-HU.Date + +description: Date messages for Hungarian. + +license: MIT-style license + +authors: + - Zsolt SzegheÅ + +requires: + - Locale + +provides: [Locale.hu-HU.Date] + +... +*/ + +Locale.define('hu-HU', 'Date', { + + months: ['Január', 'Február', 'Március', 'Ãprilis', 'Május', 'Június', 'Július', 'Augusztus', 'Szeptember', 'Október', 'November', 'December'], + months_abbr: ['jan.', 'febr.', 'márc.', 'ápr.', 'máj.', 'jún.', 'júl.', 'aug.', 'szept.', 'okt.', 'nov.', 'dec.'], + days: ['Vasárnap', 'HétfÅ', 'Kedd', 'Szerda', 'CsÃŒtörtök', 'Péntek', 'Szombat'], + days_abbr: ['V', 'H', 'K', 'Sze', 'Cs', 'P', 'Szo'], + + // Culture's date order: YYYY.MM.DD. + dateOrder: ['year', 'month', 'date'], + shortDate: '%Y.%m.%d.', + shortTime: '%I:%M', + AM: 'de.', + PM: 'du.', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '.', + + lessThanMinuteAgo: 'alig egy perce', + minuteAgo: 'egy perce', + minutesAgo: '{delta} perce', + hourAgo: 'egy órája', + hoursAgo: '{delta} órája', + dayAgo: '1 napja', + daysAgo: '{delta} napja', + weekAgo: '1 hete', + weeksAgo: '{delta} hete', + monthAgo: '1 hónapja', + monthsAgo: '{delta} hónapja', + yearAgo: '1 éve', + yearsAgo: '{delta} éve', + + lessThanMinuteUntil: 'alig egy perc múlva', + minuteUntil: 'egy perc múlva', + minutesUntil: '{delta} perc múlva', + hourUntil: 'egy óra múlva', + hoursUntil: '{delta} óra múlva', + dayUntil: '1 nap múlva', + daysUntil: '{delta} nap múlva', + weekUntil: '1 hét múlva', + weeksUntil: '{delta} hét múlva', + monthUntil: '1 hónap múlva', + monthsUntil: '{delta} hónap múlva', + yearUntil: '1 év múlva', + yearsUntil: '{delta} év múlva' + +}); + +/* +--- + +name: Locale.hu-HU.Form.Validator + +description: Form Validator messages for Hungarian. + +license: MIT-style license + +authors: + - Zsolt SzegheÅ + +requires: + - Locale + +provides: [Locale.hu-HU.Form.Validator] + +... +*/ + +Locale.define('hu-HU', 'FormValidator', { + + required: 'A mezÅ kitöltése kötelezÅ.', + minLength: 'Legalább {minLength} karakter megadása szÃŒkséges (megadva {length} karakter).', + maxLength: 'Legfeljebb {maxLength} karakter megadása lehetséges (megadva {length} karakter).', + integer: 'Egész szám megadása szÃŒkséges. A tizedesjegyek (pl. 1.25) nem engedélyezettek.', + numeric: 'Szám megadása szÃŒkséges (pl. "1" vagy "1.1" vagy "-1" vagy "-1.1").', + digits: 'Csak számok és Ãrásjelek megadása lehetséges (pl. telefonszám kötÅjelek és/vagy perjelekkel).', + alpha: 'Csak betűk (a-z) megadása lehetséges. Szóköz és egyéb karakterek nem engedélyezettek.', + alphanum: 'Csak betűk (a-z) vagy számok (0-9) megadása lehetséges. Szóköz és egyéb karakterek nem engedélyezettek.', + dateSuchAs: 'Valós dátum megadása szÃŒkséges (pl. {date}).', + dateInFormatMDY: 'Valós dátum megadása szÃŒkséges ÃÃÃÃ.HH.NN. formában. (pl. "1999.12.31.")', + email: 'Valós e-mail cÃm megadása szÃŒkséges (pl. "fred@domain.hu").', + url: 'Valós URL megadása szÃŒkséges (pl. http://www.example.com).', + currencyDollar: 'Valós pénzösszeg megadása szÃŒkséges (pl. 100.00 Ft.).', + oneRequired: 'Az alábbi mezÅk legalább egyikének kitöltése kötelezÅ.', + errorPrefix: 'Hiba: ', + warningPrefix: 'Figyelem: ', + + // Form.Validator.Extras + noSpace: 'A mezÅ nem tartalmazhat szóközöket.', + reqChkByNode: 'Nincs egyetlen kijelölt elem sem.', + requiredChk: 'A mezÅ kitöltése kötelezÅ.', + reqChkByName: 'Egy {label} kiválasztása szÃŒkséges.', + match: 'A mezÅnek egyeznie kell a(z) {matchName} mezÅvel.', + startDate: 'a kezdet dátuma', + endDate: 'a vég dátuma', + currentDate: 'jelenlegi dátum', + afterDate: 'A dátum nem lehet kisebb, mint {label}.', + beforeDate: 'A dátum nem lehet nagyobb, mint {label}.', + startMonth: 'Kezdeti hónap megadása szÃŒkséges.', + sameMonth: 'A két dátumnak ugyanazon hónapban kell lennie.', + creditcard: 'A megadott bankkártyaszám nem valódi (megadva {length} számjegy).' + +}); + +/* +--- + +name: Locale.it-IT.Date + +description: Date messages for Italian. + +license: MIT-style license. + +authors: + - Andrea Novero + - Valerio Proietti + +requires: + - Locale + +provides: [Locale.it-IT.Date] + +... +*/ + +Locale.define('it-IT', 'Date', { + + months: ['Gennaio', 'Febbraio', 'Marzo', 'Aprile', 'Maggio', 'Giugno', 'Luglio', 'Agosto', 'Settembre', 'Ottobre', 'Novembre', 'Dicembre'], + months_abbr: ['gen', 'feb', 'mar', 'apr', 'mag', 'giu', 'lug', 'ago', 'set', 'ott', 'nov', 'dic'], + days: ['Domenica', 'Lunedì', 'Martedì', 'Mercoledì', 'Giovedì', 'Venerdì', 'Sabato'], + days_abbr: ['dom', 'lun', 'mar', 'mer', 'gio', 'ven', 'sab'], + + // Culture's date order: DD/MM/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d/%m/%Y', + shortTime: '%H.%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: 'º', + + lessThanMinuteAgo: 'meno di un minuto fa', + minuteAgo: 'circa un minuto fa', + minutesAgo: 'circa {delta} minuti fa', + hourAgo: "circa un'ora fa", + hoursAgo: 'circa {delta} ore fa', + dayAgo: 'circa 1 giorno fa', + daysAgo: 'circa {delta} giorni fa', + weekAgo: 'una settimana fa', + weeksAgo: '{delta} settimane fa', + monthAgo: 'un mese fa', + monthsAgo: '{delta} mesi fa', + yearAgo: 'un anno fa', + yearsAgo: '{delta} anni fa', + + lessThanMinuteUntil: 'tra meno di un minuto', + minuteUntil: 'tra circa un minuto', + minutesUntil: 'tra circa {delta} minuti', + hourUntil: "tra circa un'ora", + hoursUntil: 'tra circa {delta} ore', + dayUntil: 'tra circa un giorno', + daysUntil: 'tra circa {delta} giorni', + weekUntil: 'tra una settimana', + weeksUntil: 'tra {delta} settimane', + monthUntil: 'tra un mese', + monthsUntil: 'tra {delta} mesi', + yearUntil: 'tra un anno', + yearsUntil: 'tra {delta} anni' + +}); + +/* +--- + +name: Locale.it-IT.Form.Validator + +description: Form Validator messages for Italian. + +license: MIT-style license + +authors: + - Leonardo Laureti + - Andrea Novero + +requires: + - Locale + +provides: [Locale.it-IT.Form.Validator] + +... +*/ + +Locale.define('it-IT', 'FormValidator', { + + required: 'Il campo è obbligatorio.', + minLength: 'Inserire almeno {minLength} caratteri (ne sono stati inseriti {length}).', + maxLength: 'Inserire al massimo {maxLength} caratteri (ne sono stati inseriti {length}).', + integer: 'Inserire un numero intero. Non sono consentiti decimali (es.: 1.25).', + numeric: 'Inserire solo valori numerici (es.: "1" oppure "1.1" oppure "-1" oppure "-1.1").', + digits: 'Inserire solo numeri e caratteri di punteggiatura. Per esempio è consentito un numero telefonico con trattini o punti.', + alpha: 'Inserire solo lettere (a-z). Non sono consentiti spazi o altri caratteri.', + alphanum: 'Inserire solo lettere (a-z) o numeri (0-9). Non sono consentiti spazi o altri caratteri.', + dateSuchAs: 'Inserire una data valida del tipo {date}', + dateInFormatMDY: 'Inserire una data valida nel formato MM/GG/AAAA (es.: "12/31/1999")', + email: 'Inserire un indirizzo email valido. Per esempio "nome@dominio.com".', + url: 'Inserire un indirizzo valido. Per esempio "http://www.example.com".', + currencyDollar: 'Inserire un importo valido. Per esempio "$100.00".', + oneRequired: 'Completare almeno uno dei campi richiesti.', + errorPrefix: 'Errore: ', + warningPrefix: 'Attenzione: ', + + // Form.Validator.Extras + noSpace: 'Non sono consentiti spazi.', + reqChkByNode: 'Nessuna voce selezionata.', + requiredChk: 'Il campo è obbligatorio.', + reqChkByName: 'Selezionare un(a) {label}.', + match: 'Il valore deve corrispondere al campo {matchName}', + startDate: "data d'inizio", + endDate: 'data di fine', + currentDate: 'data attuale', + afterDate: 'La data deve corrispondere o essere successiva al {label}.', + beforeDate: 'La data deve corrispondere o essere precedente al {label}.', + startMonth: "Selezionare un mese d'inizio", + sameMonth: 'Le due date devono essere dello stesso mese - occorre modificarne una.' + +}); + +/* +--- + +name: Locale.ja-JP.Date + +description: Date messages for Japanese. + +license: MIT-style license + +authors: + - Noritaka Horio + +requires: + - Locale + +provides: [Locale.ja-JP.Date] + +... +*/ + +Locale.define('ja-JP', 'Date', { + + months: ['1æ', '2æ', '3æ', '4æ', '5æ', '6æ', '7æ', '8æ', '9æ', '10æ', '11æ', '12æ'], + months_abbr: ['1æ', '2æ', '3æ', '4æ', '5æ', '6æ', '7æ', '8æ', '9æ', '10æ', '11æ', '12æ'], + days: ['æ¥ææ¥', 'æææ¥', 'ç«ææ¥', 'æ°Žææ¥', 'æšææ¥', 'éææ¥', 'åææ¥'], + days_abbr: ['æ¥', 'æ', 'ç«', 'æ°Ž', 'æš', 'é', 'å'], + + // Culture's date order: YYYY/MM/DD + dateOrder: ['year', 'month', 'date'], + shortDate: '%Y/%m/%d', + shortTime: '%H:%M', + AM: 'åå', + PM: 'ååŸ', + firstDayOfWeek: 0, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: '1å以å
å', + minuteAgo: 'çŽ1åå', + minutesAgo: 'çŽ{delta}åå', + hourAgo: 'çŽ1æéå', + hoursAgo: 'çŽ{delta}æéå', + dayAgo: '1æ¥å', + daysAgo: '{delta}æ¥å', + weekAgo: '1é±éå', + weeksAgo: '{delta}é±éå', + monthAgo: '1ã¶æå', + monthsAgo: '{delta}ã¶æå', + yearAgo: '1幎å', + yearsAgo: '{delta}幎å', + + lessThanMinuteUntil: 'ä»ããçŽ1å以å
', + minuteUntil: 'ä»ããçŽ1å', + minutesUntil: 'ä»ããçŽ{delta}å', + hourUntil: 'ä»ããçŽ1æé', + hoursUntil: 'ä»ããçŽ{delta}æé', + dayUntil: 'ä»ãã1æ¥é', + daysUntil: 'ä»ãã{delta}æ¥é', + weekUntil: 'ä»ãã1é±é', + weeksUntil: 'ä»ãã{delta}é±é', + monthUntil: 'ä»ãã1ã¶æ', + monthsUntil: 'ä»ãã{delta}ã¶æ', + yearUntil: 'ä»ãã1幎', + yearsUntil: 'ä»ãã{delta}幎' + +}); + +/* +--- + +name: Locale.ja-JP.Form.Validator + +description: Form Validator messages for Japanese. + +license: MIT-style license + +authors: + - Noritaka Horio + +requires: + - Locale + +provides: [Locale.ja-JP.Form.Validator] + +... +*/ + +Locale.define("ja-JP", "FormValidator", { + + required: 'å
¥åã¯å¿
é ã§ãã', + minLength: 'å
¥åæåæ°ã¯{minLength}以äžã«ããŠãã ããã({length}æå)', + maxLength: 'å
¥åæåæ°ã¯{maxLength}以äžã«ããŠãã ããã({length}æå)', + integer: 'æŽæ°ãå
¥åããŠãã ããã', + numeric: 'å
¥åã§ããã®ã¯æ°å€ã ãã§ãã(äŸ: "1", "1.1", "-1", "-1.1"....)', + digits: 'å
¥åã§ããã®ã¯æ°å€ãšå¥èªèšå·ã§ãã (äŸ: -ã+ãå«ãé»è©±çªå·ãªã©).', + alpha: 'å
¥åã§ããã®ã¯åè§è±åã ãã§ãããã以å€ã®æåã¯å
¥åã§ããŸããã', + alphanum: 'å
¥åã§ããã®ã¯åè§è±æ°åã ãã§ãããã以å€ã®æåã¯å
¥åã§ããŸããã', + dateSuchAs: 'æå¹ãªæ¥ä»ãå
¥åããŠãã ããã{date}', + dateInFormatMDY: 'æ¥ä»ã®æžåŒã«èª€ãããããŸããYYYY/MM/DD (i.e. "1999/12/31")', + email: 'ã¡ãŒã«ã¢ãã¬ã¹ã«èª€ãããããŸãã', + url: 'URLã¢ãã¬ã¹ã«èª€ãããããŸãã', + currencyDollar: 'éé¡ã«èª€ãããããŸãã', + oneRequired: 'ã²ãšã€ä»¥äžå
¥åããŠãã ããã', + errorPrefix: 'ãšã©ãŒ: ', + warningPrefix: 'èŠå: ', + + // FormValidator.Extras + noSpace: 'ã¹ããŒã¹ã¯å
¥åã§ããŸããã', + reqChkByNode: 'éžæãããŠããŸããã', + requiredChk: 'ãã®é
ç®ã¯å¿
é ã§ãã', + reqChkByName: '{label}ãéžæããŠãã ããã', + match: '{matchName}ãå
¥åãããŠããå Žåå¿
é ã§ãã', + startDate: 'éå§æ¥', + endDate: 'çµäºæ¥', + currentDate: '仿¥', + afterDate: '{label}以éã®æ¥ä»ã«ããŠãã ããã', + beforeDate: '{label}以åã®æ¥ä»ã«ããŠãã ããã', + startMonth: 'éå§æãéžæããŠãã ããã', + sameMonth: 'æ¥ä»ãåäžã§ããã©ã¡ããã倿ŽããŠãã ããã' + +}); + +/* +--- + +name: Locale.ja-JP.Number + +description: Number messages for Japanese. + +license: MIT-style license + +authors: + - Noritaka Horio + +requires: + - Locale + +provides: [Locale.ja-JP.Number] + +... +*/ + +Locale.define('ja-JP', 'Number', { + + decimal: '.', + group: ',', + + currency: { + decimals: 0, + prefix: '\\' + } + +}); + +/* +--- + +name: Locale.nl-NL.Date + +description: Date messages for Dutch. + +license: MIT-style license + +authors: + - Lennart Pilon + - Tim Wienk + +requires: + - Locale + +provides: [Locale.nl-NL.Date] + +... +*/ + +Locale.define('nl-NL', 'Date', { + + months: ['januari', 'februari', 'maart', 'april', 'mei', 'juni', 'juli', 'augustus', 'september', 'oktober', 'november', 'december'], + months_abbr: ['jan', 'feb', 'mrt', 'apr', 'mei', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'], + days: ['zondag', 'maandag', 'dinsdag', 'woensdag', 'donderdag', 'vrijdag', 'zaterdag'], + days_abbr: ['zo', 'ma', 'di', 'wo', 'do', 'vr', 'za'], + + // Culture's date order: DD-MM-YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d-%m-%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: 'e', + + lessThanMinuteAgo: 'minder dan een minuut geleden', + minuteAgo: 'ongeveer een minuut geleden', + minutesAgo: '{delta} minuten geleden', + hourAgo: 'ongeveer een uur geleden', + hoursAgo: 'ongeveer {delta} uur geleden', + dayAgo: 'een dag geleden', + daysAgo: '{delta} dagen geleden', + weekAgo: 'een week geleden', + weeksAgo: '{delta} weken geleden', + monthAgo: 'een maand geleden', + monthsAgo: '{delta} maanden geleden', + yearAgo: 'een jaar geleden', + yearsAgo: '{delta} jaar geleden', + + lessThanMinuteUntil: 'over minder dan een minuut', + minuteUntil: 'over ongeveer een minuut', + minutesUntil: 'over {delta} minuten', + hourUntil: 'over ongeveer een uur', + hoursUntil: 'over {delta} uur', + dayUntil: 'over ongeveer een dag', + daysUntil: 'over {delta} dagen', + weekUntil: 'over een week', + weeksUntil: 'over {delta} weken', + monthUntil: 'over een maand', + monthsUntil: 'over {delta} maanden', + yearUntil: 'over een jaar', + yearsUntil: 'over {delta} jaar' + +}); + +/* +--- + +name: Locale.nl-NL.Form.Validator + +description: Form Validator messages for Dutch. + +license: MIT-style license + +authors: + - Lennart Pilon + - Arian Stolwijk + - Tim Wienk + +requires: + - Locale + +provides: [Locale.nl-NL.Form.Validator] + +... +*/ + +Locale.define('nl-NL', 'FormValidator', { + + required: 'Dit veld is verplicht.', + length: 'Vul precies {length} karakters in (je hebt {elLength} karakters ingevoerd).', + minLength: 'Vul minimaal {minLength} karakters in (je hebt {length} karakters ingevoerd).', + maxLength: 'Vul niet meer dan {maxLength} karakters in (je hebt {length} karakters ingevoerd).', + integer: 'Vul een getal in. Getallen met decimalen (bijvoorbeeld 1.25) zijn niet toegestaan.', + numeric: 'Vul alleen numerieke waarden in (bijvoorbeeld "1" of "1.1" of "-1" of "-1.1").', + digits: 'Vul alleen nummers en leestekens in (bijvoorbeeld een telefoonnummer met streepjes is toegestaan).', + alpha: 'Vul alleen letters in (a-z). Spaties en andere karakters zijn niet toegestaan.', + alphanum: 'Vul alleen letters (a-z) of nummers (0-9) in. Spaties en andere karakters zijn niet toegestaan.', + dateSuchAs: 'Vul een geldige datum in, zoals {date}', + dateInFormatMDY: 'Vul een geldige datum, in het formaat MM/DD/YYYY (bijvoorbeeld "12/31/1999")', + email: 'Vul een geldig e-mailadres in. Bijvoorbeeld "fred@domein.nl".', + url: 'Vul een geldige URL in, zoals http://www.example.com.', + currencyDollar: 'Vul een geldig $ bedrag in. Bijvoorbeeld $100.00 .', + oneRequired: 'Vul iets in bij in ieder geval een van deze velden.', + warningPrefix: 'Waarschuwing: ', + errorPrefix: 'Fout: ', + + // Form.Validator.Extras + noSpace: 'Spaties zijn niet toegestaan in dit veld.', + reqChkByNode: 'Er zijn geen items geselecteerd.', + requiredChk: 'Dit veld is verplicht.', + reqChkByName: 'Selecteer een {label}.', + match: 'Dit veld moet overeen komen met het {matchName} veld', + startDate: 'de begin datum', + endDate: 'de eind datum', + currentDate: 'de huidige datum', + afterDate: 'De datum moet hetzelfde of na {label} zijn.', + beforeDate: 'De datum moet hetzelfde of voor {label} zijn.', + startMonth: 'Selecteer een begin maand', + sameMonth: 'Deze twee data moeten in dezelfde maand zijn - u moet een van beide aanpassen.', + creditcard: 'Het ingevulde creditcardnummer is niet geldig. Controleer het nummer en probeer opnieuw. {length} getallen ingevuld.' + +}); + +/* +--- + +name: Locale.nl-NL.Number + +description: Number messages for Dutch. + +license: MIT-style license + +authors: + - Arian Stolwijk + +requires: + - Locale + - Locale.EU.Number + +provides: [Locale.nl-NL.Number] + +... +*/ + +Locale.define('nl-NL').inherit('EU', 'Number'); + + + + +/* +--- + +name: Locale.no-NO.Date + +description: Date messages for Norwegian. + +license: MIT-style license + +authors: + - Espen 'Rexxars' Hovlandsdal + - Ole TÞsse Kolvik +requires: + - Locale + +provides: [Locale.no-NO.Date] + +... +*/ + +Locale.define('no-NO', 'Date', { + months: ['Januar', 'Februar', 'Mars', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Desember'], + months_abbr: ['Jan', 'Feb', 'Mar', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Des'], + days: ['SÞndag', 'Mandag', 'Tirsdag', 'Onsdag', 'Torsdag', 'Fredag', 'LÞrdag'], + days_abbr: ['SÞn', 'Man', 'Tir', 'Ons', 'Tor', 'Fre', 'LÞr'], + + // Culture's date order: DD.MM.YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d.%m.%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + lessThanMinuteAgo: 'mindre enn et minutt siden', + minuteAgo: 'omtrent et minutt siden', + minutesAgo: '{delta} minutter siden', + hourAgo: 'omtrent en time siden', + hoursAgo: 'omtrent {delta} timer siden', + dayAgo: '{delta} dag siden', + daysAgo: '{delta} dager siden', + weekAgo: 'en uke siden', + weeksAgo: '{delta} uker siden', + monthAgo: 'en mÃ¥ned siden', + monthsAgo: '{delta} mÃ¥neder siden', + yearAgo: 'ett Ã¥r siden', + yearsAgo: '{delta} Ã¥r siden', + + lessThanMinuteUntil: 'mindre enn et minutt til', + minuteUntil: 'omtrent et minutt til', + minutesUntil: '{delta} minutter til', + hourUntil: 'omtrent en time til', + hoursUntil: 'omtrent {delta} timer til', + dayUntil: 'en dag til', + daysUntil: '{delta} dager til', + weekUntil: 'en uke til', + weeksUntil: '{delta} uker til', + monthUntil: 'en mÃ¥ned til', + monthsUntil: '{delta} mÃ¥neder til', + yearUntil: 'et Ã¥r til', + yearsUntil: '{delta} Ã¥r til' +}); + +/* +--- + +name: Locale.no-NO.Form.Validator + +description: Form Validator messages for Norwegian. + +license: MIT-style license + +authors: + - Espen 'Rexxars' Hovlandsdal + +requires: + - Locale + +provides: [Locale.no-NO.Form.Validator] + +... +*/ + +Locale.define('no-NO', 'FormValidator', { + + required: 'Dette feltet er pÃÂ¥krevd.', + minLength: 'Vennligst skriv inn minst {minLength} tegn (du skrev {length} tegn).', + maxLength: 'Vennligst skriv inn maksimalt {maxLength} tegn (du skrev {length} tegn).', + integer: 'Vennligst skriv inn et tall i dette feltet. Tall med desimaler (for eksempel 1,25) er ikke tillat.', + numeric: 'Vennligst skriv inn kun numeriske verdier i dette feltet (for eksempel "1", "1.1", "-1" eller "-1.1").', + digits: 'Vennligst bruk kun nummer og skilletegn i dette feltet.', + alpha: 'Vennligst bruk kun bokstaver (a-z) i dette feltet. Ingen mellomrom eller andre tegn er tillat.', + alphanum: 'Vennligst bruk kun bokstaver (a-z) eller nummer (0-9) i dette feltet. Ingen mellomrom eller andre tegn er tillat.', + dateSuchAs: 'Vennligst skriv inn en gyldig dato, som {date}', + dateInFormatMDY: 'Vennligst skriv inn en gyldig dato, i formatet MM/DD/YYYY (for eksempel "12/31/1999")', + email: 'Vennligst skriv inn en gyldig epost-adresse. For eksempel "espen@domene.no".', + url: 'Vennligst skriv inn en gyldig URL, for eksempel http://www.example.com.', + currencyDollar: 'Vennligst fyll ut et gyldig $ belÞp. For eksempel $100.00 .', + oneRequired: 'Vennligst fyll ut noe i minst ett av disse feltene.', + errorPrefix: 'Feil: ', + warningPrefix: 'Advarsel: ' + +}); + +/* +--- + +name: Locale.pl-PL.Date + +description: Date messages for Polish. + +license: MIT-style license + +authors: + - Oskar Krawczyk + +requires: + - Locale + +provides: [Locale.pl-PL.Date] + +... +*/ + +Locale.define('pl-PL', 'Date', { + + months: ['StyczeÅ', 'Luty', 'Marzec', 'KwiecieÅ', 'Maj', 'Czerwiec', 'Lipiec', 'SierpieÅ', 'WrzesieÅ', 'Październik', 'Listopad', 'GrudzieÅ'], + months_abbr: ['sty', 'lut', 'mar', 'kwi', 'maj', 'cze', 'lip', 'sie', 'wrz', 'paź', 'lis', 'gru'], + days: ['Niedziela', 'PoniedziaÅek', 'Wtorek', 'Åroda', 'Czwartek', 'PiÄ
tek', 'Sobota'], + days_abbr: ['niedz.', 'pon.', 'wt.', 'År.', 'czw.', 'pt.', 'sob.'], + + // Culture's date order: YYYY-MM-DD + dateOrder: ['year', 'month', 'date'], + shortDate: '%Y-%m-%d', + shortTime: '%H:%M', + AM: 'nad ranem', + PM: 'po poÅudniu', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: function(dayOfMonth){ + return (dayOfMonth > 3 && dayOfMonth < 21) ? 'ty' : ['ty', 'szy', 'gi', 'ci', 'ty'][Math.min(dayOfMonth % 10, 4)]; + }, + + lessThanMinuteAgo: 'mniej niÅŒ minute temu', + minuteAgo: 'okoÅo minutÄ temu', + minutesAgo: '{delta} minut temu', + hourAgo: 'okoÅo godzinÄ temu', + hoursAgo: 'okoÅo {delta} godzin temu', + dayAgo: 'Wczoraj', + daysAgo: '{delta} dni temu', + + lessThanMinuteUntil: 'za niecaÅÄ
minutÄ', + minuteUntil: 'za okoÅo minutÄ', + minutesUntil: 'za {delta} minut', + hourUntil: 'za okoÅo godzinÄ', + hoursUntil: 'za okoÅo {delta} godzin', + dayUntil: 'za 1 dzieÅ', + daysUntil: 'za {delta} dni' + +}); + +/* +--- + +name: Locale.pl-PL.Form.Validator + +description: Form Validator messages for Polish. + +license: MIT-style license + +authors: + - Oskar Krawczyk + +requires: + - Locale + +provides: [Locale.pl-PL.Form.Validator] + +... +*/ + +Locale.define('pl-PL', 'FormValidator', { + + required: 'To pole jest wymagane.', + minLength: 'Wymagane jest przynajmniej {minLength} znaków (wpisanych zostaÅo tylko {length}).', + maxLength: 'Dozwolone jest nie wiÄcej niÅŒ {maxLength} znaków (wpisanych zostaÅo {length})', + integer: 'To pole wymaga liczb caÅych. Liczby dziesiÄtne (np. 1.25) sÄ
niedozwolone.', + numeric: 'Prosimy uÅŒywaÄ tylko numerycznych wartoÅci w tym polu (np. "1", "1.1", "-1" lub "-1.1").', + digits: 'Prosimy uÅŒywaÄ liczb oraz zankow punktuacyjnych w typ polu (dla przykÅadu, przy numerze telefonu myÅlniki i kropki sÄ
dozwolone).', + alpha: 'Prosimy uÅŒywaÄ tylko liter (a-z) w tym polu. Spacje oraz inne znaki sÄ
niedozwolone.', + alphanum: 'Prosimy uÅŒywaÄ tylko liter (a-z) lub liczb (0-9) w tym polu. Spacje oraz inne znaki sÄ
niedozwolone.', + dateSuchAs: 'Prosimy podaÄ prawidÅowÄ
datÄ w formacie: {date}', + dateInFormatMDY: 'Prosimy podaÄ poprawnÄ
date w formacie DD.MM.RRRR (i.e. "12.01.2009")', + email: 'Prosimy podaÄ prawidÅowy adres e-mail, np. "jan@domena.pl".', + url: 'Prosimy podaÄ prawidÅowy adres URL, np. http://www.example.com.', + currencyDollar: 'Prosimy podaÄ prawidÅowÄ
sumÄ w PLN. Dla przykÅadu: 100.00 PLN.', + oneRequired: 'Prosimy wypeÅniÄ chociaÅŒ jedno z pól.', + errorPrefix: 'BÅÄ
d: ', + warningPrefix: 'Uwaga: ', + + // Form.Validator.Extras + noSpace: 'W tym polu nie mogÄ
znajdowaÄ siÄ spacje.', + reqChkByNode: 'Brak zaznaczonych elementów.', + requiredChk: 'To pole jest wymagane.', + reqChkByName: 'Prosimy wybraÄ z {label}.', + match: 'To pole musi byÄ takie samo jak {matchName}', + startDate: 'data poczÄ
tkowa', + endDate: 'data koÅcowa', + currentDate: 'aktualna data', + afterDate: 'Podana data poinna byÄ taka sama lub po {label}.', + beforeDate: 'Podana data poinna byÄ taka sama lub przed {label}.', + startMonth: 'Prosimy wybraÄ poczÄ
tkowy miesiÄ
c.', + sameMonth: 'Te dwie daty muszÄ
byÄ w zakresie tego samego miesiÄ
ca - wymagana jest zmiana któregoÅ z pól.' + +}); + +/* +--- + +name: Locale.pt-PT.Date + +description: Date messages for Portuguese. + +license: MIT-style license + +authors: + - Fabio Miranda Costa + +requires: + - Locale + +provides: [Locale.pt-PT.Date] + +... +*/ + +Locale.define('pt-PT', 'Date', { + + months: ['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'], + months_abbr: ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'], + days: ['Domingo', 'Segunda-feira', 'Terça-feira', 'Quarta-feira', 'Quinta-feira', 'Sexta-feira', 'Sábado'], + days_abbr: ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'], + + // Culture's date order: DD-MM-YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d-%m-%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: 'º', + + lessThanMinuteAgo: 'há menos de um minuto', + minuteAgo: 'há cerca de um minuto', + minutesAgo: 'há {delta} minutos', + hourAgo: 'há cerca de uma hora', + hoursAgo: 'há cerca de {delta} horas', + dayAgo: 'há um dia', + daysAgo: 'há {delta} dias', + weekAgo: 'há uma semana', + weeksAgo: 'há {delta} semanas', + monthAgo: 'há um mês', + monthsAgo: 'há {delta} meses', + yearAgo: 'há um ano', + yearsAgo: 'há {delta} anos', + + lessThanMinuteUntil: 'em menos de um minuto', + minuteUntil: 'em um minuto', + minutesUntil: 'em {delta} minutos', + hourUntil: 'em uma hora', + hoursUntil: 'em {delta} horas', + dayUntil: 'em um dia', + daysUntil: 'em {delta} dias', + weekUntil: 'em uma semana', + weeksUntil: 'em {delta} semanas', + monthUntil: 'em um mês', + monthsUntil: 'em {delta} meses', + yearUntil: 'em um ano', + yearsUntil: 'em {delta} anos' + +}); + +/* +--- + +name: Locale.pt-BR.Date + +description: Date messages for Portuguese (Brazil). + +license: MIT-style license + +authors: + - Fabio Miranda Costa + +requires: + - Locale + - Locale.pt-PT.Date + +provides: [Locale.pt-BR.Date] + +... +*/ + +Locale.define('pt-BR', 'Date', { + + // Culture's date order: DD/MM/YYYY + shortDate: '%d/%m/%Y' + +}).inherit('pt-PT', 'Date'); + +/* +--- + +name: Locale.pt-BR.Form.Validator + +description: Form Validator messages for Portuguese (Brazil). + +license: MIT-style license + +authors: + - Fábio Miranda Costa + +requires: + - Locale + +provides: [Locale.pt-BR.Form.Validator] + +... +*/ + +Locale.define('pt-BR', 'FormValidator', { + + required: 'Este campo é obrigatório.', + minLength: 'Digite pelo menos {minLength} caracteres (tamanho atual: {length}).', + maxLength: 'Não digite mais de {maxLength} caracteres (tamanho atual: {length}).', + integer: 'Por favor digite apenas um número inteiro neste campo. Não são permitidos números decimais (por exemplo, 1,25).', + numeric: 'Por favor digite apenas valores numéricos neste campo (por exemplo, "1" ou "1.1" ou "-1" ou "-1,1").', + digits: 'Por favor use apenas números e pontuação neste campo (por exemplo, um número de telefone com traços ou pontos é permitido).', + alpha: 'Por favor use somente letras (a-z). Espaço e outros caracteres não são permitidos.', + alphanum: 'Use somente letras (a-z) ou números (0-9) neste campo. Espaço e outros caracteres não são permitidos.', + dateSuchAs: 'Digite uma data válida, como {date}', + dateInFormatMDY: 'Digite uma data válida, como DD/MM/YYYY (por exemplo, "31/12/1999")', + email: 'Digite um endereço de email válido. Por exemplo "nome@dominio.com".', + url: 'Digite uma URL válida. Exemplo: http://www.example.com.', + currencyDollar: 'Digite um valor em dinheiro válido. Exemplo: R$100,00 .', + oneRequired: 'Digite algo para pelo menos um desses campos.', + errorPrefix: 'Erro: ', + warningPrefix: 'Aviso: ', + + // Form.Validator.Extras + noSpace: 'Não é possÃvel digitar espaços neste campo.', + reqChkByNode: 'Não foi selecionado nenhum item.', + requiredChk: 'Este campo é obrigatório.', + reqChkByName: 'Por favor digite um {label}.', + match: 'Este campo deve ser igual ao campo {matchName}.', + startDate: 'a data inicial', + endDate: 'a data final', + currentDate: 'a data atual', + afterDate: 'A data deve ser igual ou posterior a {label}.', + beforeDate: 'A data deve ser igual ou anterior a {label}.', + startMonth: 'Por favor selecione uma data inicial.', + sameMonth: 'Estas duas datas devem ter o mesmo mês - você deve modificar uma das duas.', + creditcard: 'O número do cartão de crédito informado é inválido. Por favor verifique o valor e tente novamente. {length} números informados.' + +}); + +/* +--- + +name: Locale.pt-BR.Number + +description: Number messages for PT Brazilian. + +license: MIT-style license + +authors: + - Arian Stolwijk + - Danillo César + +requires: + - Locale + +provides: [Locale.pt-BR.Number] + +... +*/ + +Locale.define('pt-BR', 'Number', { + + decimal: ',', + group: '.', + + currency: { + prefix: 'R$ ' + } + +}); + + + +/* +--- + +name: Locale.pt-PT.Form.Validator + +description: Form Validator messages for Portuguese. + +license: MIT-style license + +authors: + - Miquel Hudin + +requires: + - Locale + +provides: [Locale.pt-PT.Form.Validator] + +... +*/ + +Locale.define('pt-PT', 'FormValidator', { + + required: 'Este campo é necessário.', + minLength: 'Digite pelo menos{minLength} caracteres (comprimento {length} caracteres).', + maxLength: 'Não insira mais de {maxLength} caracteres (comprimento {length} caracteres).', + integer: 'Digite um número inteiro neste domÃnio. Com números decimais (por exemplo, 1,25), não são permitidas.', + numeric: 'Digite apenas valores numéricos neste domÃnio (p.ex., "1" ou "1.1" ou "-1" ou "-1,1").', + digits: 'Por favor, use números e pontuação apenas neste campo (p.ex., um número de telefone com traços ou pontos é permitida).', + alpha: 'Por favor use somente letras (a-z), com nesta área. Não utilize espaços nem outros caracteres são permitidos.', + alphanum: 'Use somente letras (a-z) ou números (0-9) neste campo. Não utilize espaços nem outros caracteres são permitidos.', + dateSuchAs: 'Digite uma data válida, como {date}', + dateInFormatMDY: 'Digite uma data válida, como DD/MM/YYYY (p.ex. "31/12/1999")', + email: 'Digite um endereço de email válido. Por exemplo "fred@domain.com".', + url: 'Digite uma URL válida, como http://www.example.com.', + currencyDollar: 'Digite um valor válido $. Por exemplo $ 100,00. ', + oneRequired: 'Digite algo para pelo menos um desses insumos.', + errorPrefix: 'Erro: ', + warningPrefix: 'Aviso: ' + +}); + +/* +--- + +name: Locale.ru-RU-unicode.Date + +description: Date messages for Russian (utf-8). + +license: MIT-style license + +authors: + - Evstigneev Pavel + - Kuryanovich Egor + +requires: + - Locale + +provides: [Locale.ru-RU.Date] + +... +*/ + +(function(){ + +// Russian language pluralization rules, taken from CLDR project, http://unicode.org/cldr/ +// one -> n mod 10 is 1 and n mod 100 is not 11; +// few -> n mod 10 in 2..4 and n mod 100 not in 12..14; +// many -> n mod 10 is 0 or n mod 10 in 5..9 or n mod 100 in 11..14; +// other -> everything else (example 3.14) +var pluralize = function (n, one, few, many, other){ + var modulo10 = n % 10, + modulo100 = n % 100; + + if (modulo10 == 1 && modulo100 != 11){ + return one; + } else if ((modulo10 == 2 || modulo10 == 3 || modulo10 == 4) && !(modulo100 == 12 || modulo100 == 13 || modulo100 == 14)){ + return few; + } else if (modulo10 == 0 || (modulo10 == 5 || modulo10 == 6 || modulo10 == 7 || modulo10 == 8 || modulo10 == 9) || (modulo100 == 11 || modulo100 == 12 || modulo100 == 13 || modulo100 == 14)){ + return many; + } else { + return other; + } +}; + +Locale.define('ru-RU', 'Date', { + + months: ['ЯМваÑÑ', 'ЀевÑалÑ', 'ÐаÑÑ', 'ÐпÑелÑ', 'Ðай', 'ÐÑМÑ', 'ÐÑлÑ', 'ÐвгÑÑÑ', 'СеМÑÑбÑÑ', 'ÐкÑÑбÑÑ', 'ÐПÑбÑÑ', 'ÐекабÑÑ'], + months_abbr: ['ÑМв', 'ÑевÑ', 'ЌаÑÑ', 'апÑ', 'Ќай','ОÑМÑ','ОÑлÑ','авг','ÑеМÑ','ПкÑ','МПÑб','Ўек'], + days: ['ÐПÑкÑеÑеМÑе', 'ÐПМеЎелÑМОк', 'ÐÑПÑМОк', 'СÑеЎа', 'ЧеÑвеÑг', 'ÐÑÑМОÑа', 'СÑббПÑа'], + days_abbr: ['ÐÑ', 'ÐМ', 'ÐÑ', 'СÑ', 'ЧÑ', 'ÐÑ', 'Сб'], + + // Culture's date order: DD.MM.YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d.%m.%Y', + shortTime: '%H:%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: 'ЌеМÑÑе ЌОМÑÑÑ ÐœÐ°Ð·Ð°ÐŽ', + minuteAgo: 'ЌОМÑÑÑ ÐœÐ°Ð·Ð°ÐŽ', + minutesAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ЌОМÑÑÑ', 'ЌОМÑÑÑ', 'ЌОМÑÑ') + ' МазаЎ'; }, + hourAgo: 'ÑÐ°Ñ ÐœÐ°Ð·Ð°ÐŽ', + hoursAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ÑаÑ', 'ÑаÑа', 'ÑаÑПв') + ' МазаЎ'; }, + dayAgo: 'вÑеÑа', + daysAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ЎеМÑ', 'ЎМÑ', 'ЎМей') + ' МазаЎ'; }, + weekAgo: 'ÐœÐµÐŽÐµÐ»Ñ ÐœÐ°Ð·Ð°ÐŽ', + weeksAgo: function(delta){ return '{delta} ' + pluralize(delta, 'МеЎелÑ', 'МеЎелО', 'МеЎелÑ') + ' МазаЎ'; }, + monthAgo: 'ЌеÑÑÑ ÐœÐ°Ð·Ð°ÐŽ', + monthsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ЌеÑÑÑ', 'ЌеÑÑÑа', 'ЌеÑÑÑев') + ' МазаЎ'; }, + yearAgo: 'гПЎ МазаЎ', + yearsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'гПЎ', 'гПЎа', 'леÑ') + ' МазаЎ'; }, + + lessThanMinuteUntil: 'ЌеМÑÑе ÑеЌ ÑеÑез ЌОМÑÑÑ', + minuteUntil: 'ÑеÑез ЌОМÑÑÑ', + minutesUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'ЌОМÑÑÑ', 'ЌОМÑÑÑ', 'ЌОМÑÑ') + ''; }, + hourUntil: 'ÑеÑез ÑаÑ', + hoursUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'ÑаÑ', 'ÑаÑа', 'ÑаÑПв') + ''; }, + dayUntil: 'завÑÑа', + daysUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'ЎеМÑ', 'ЎМÑ', 'ЎМей') + ''; }, + weekUntil: 'ÑеÑез МеЎелÑ', + weeksUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'МеЎелÑ', 'МеЎелО', 'МеЎелÑ') + ''; }, + monthUntil: 'ÑеÑез ЌеÑÑÑ', + monthsUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'ЌеÑÑÑ', 'ЌеÑÑÑа', 'ЌеÑÑÑев') + ''; }, + yearUntil: 'ÑеÑез', + yearsUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'гПЎ', 'гПЎа', 'леÑ') + ''; } + +}); + + + +})(); + +/* +--- + +name: Locale.ru-RU-unicode.Form.Validator + +description: Form Validator messages for Russian (utf-8). + +license: MIT-style license + +authors: + - Chernodarov Egor + +requires: + - Locale + +provides: [Locale.ru-RU.Form.Validator] + +... +*/ + +Locale.define('ru-RU', 'FormValidator', { + + required: 'ÐÑП пПле ПбÑзаÑелÑМП к запПлМеМОÑ.', + minLength: 'ÐПжалÑйÑÑа, ввеЎОÑе Ñ
ПÑÑ Ð±Ñ {minLength} ÑОЌвПлПв (ÐÑ Ð²Ð²ÐµÐ»Ðž {length}).', + maxLength: 'ÐПжалÑйÑÑа, ввеЎОÑе Ме бПлÑÑе {maxLength} ÑОЌвПлПв (ÐÑ Ð²Ð²ÐµÐ»Ðž {length}).', + integer: 'ÐПжалÑйÑÑа, ввеЎОÑе в ÑÑП пПле ÑОÑлП. ÐÑПбМÑе ÑОÑла (МапÑÐžÐŒÐµÑ 1.25) ÑÑÑ ÐœÐµ ÑазÑеÑеМÑ.', + numeric: 'ÐПжалÑйÑÑа, ввеЎОÑе в ÑÑП пПле ÑОÑлП (МапÑÐžÐŒÐµÑ "1" ОлО "1.1", ОлО "-1", ОлО "-1.1").', + digits: 'Ð ÑÑПЌ пПле ÐÑ ÐŒÐŸÐ¶ÐµÑе ОÑпПлÑзПваÑÑ ÑПлÑкП ÑОÑÑÑ Ðž зМакО пÑМкÑÑаÑОО (МапÑОЌеÑ, ÑелеÑПММÑй ÐœÐŸÐŒÐµÑ ÑП зМакаЌО ЎеÑОÑа ОлО Ñ ÑПÑкаЌО).', + alpha: 'Ð ÑÑПЌ пПле ЌПжМП ОÑпПлÑзПваÑÑ ÑПлÑкП лаÑОМÑкОе бÑÐºÐ²Ñ (a-z). ÐÑÐŸÐ±ÐµÐ»Ñ Ðž ÐŽÑÑгОе ÑÐžÐŒÐ²ÐŸÐ»Ñ Ð·Ð°Ð¿ÑеÑеМÑ.', + alphanum: 'Ð ÑÑПЌ пПле ЌПжМП ОÑпПлÑзПваÑÑ ÑПлÑкП лаÑОМÑкОе бÑÐºÐ²Ñ (a-z) О ÑОÑÑÑ (0-9). ÐÑÐŸÐ±ÐµÐ»Ñ Ðž ÐŽÑÑгОе ÑÐžÐŒÐ²ÐŸÐ»Ñ Ð·Ð°Ð¿ÑеÑеМÑ.', + dateSuchAs: 'ÐПжалÑйÑÑа, ввеЎОÑе кПÑÑекÑМÑÑ ÐŽÐ°ÑÑ {date}', + dateInFormatMDY: 'ÐПжалÑйÑÑа, ввеЎОÑе ЎаÑÑ Ð² ÑПÑЌаÑе ÐÐ/ÐÐ/ÐÐÐÐ (МапÑÐžÐŒÐµÑ "12/31/1999")', + email: 'ÐПжалÑйÑÑа, ввеЎОÑе кПÑÑекÑМÑй еЌейл-аЎÑеÑ. ÐÐ»Ñ Ð¿ÑОЌеÑа "fred@domain.com".', + url: 'ÐПжалÑйÑÑа, ввеЎОÑе пÑавОлÑМÑÑ ÑÑÑÐ»ÐºÑ Ð²ÐžÐŽÐ° http://www.example.com.', + currencyDollar: 'ÐПжалÑйÑÑа, ввеЎОÑе ÑÑÐŒÐŒÑ Ð² ЎПллаÑаÑ
. ÐапÑОЌеÑ: $100.00 .', + oneRequired: 'ÐПжалÑйÑÑа, вÑбеÑОÑе Ñ
ПÑÑ ÑÑП-МОбÑÐŽÑ Ð² ПЎМПЌ Оз ÑÑОÑ
пПлей.', + errorPrefix: 'ÐÑОбка: ', + warningPrefix: 'ÐМОЌаМОе: ' + +}); + + + +/* +--- + +name: Locale.sk-SK.Date + +description: Date messages for Slovak. + +license: MIT-style license + +authors: + - Ivan Masár + +requires: + - Locale + +provides: [Locale.sk-SK.Date] + +... +*/ +(function(){ + +// Slovak language pluralization rules, see http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html +// one -> n is 1; 1 +// few -> n in 2..4; 2-4 +// other -> everything else 0, 5-999, 1.31, 2.31, 5.31... +var pluralize = function (n, one, few, other){ + if (n == 1) return one; + else if (n == 2 || n == 3 || n == 4) return few; + else return other; +}; + +Locale.define('sk-SK', 'Date', { + + months: ['Január', 'Február', 'Marec', 'AprÃl', 'Máj', 'Jún', 'Júl', 'August', 'September', 'Október', 'November', 'December'], + months_abbr: ['januára', 'februára', 'marca', 'aprÃla', 'mája', 'júna', 'júla', 'augusta', 'septembra', 'októbra', 'novembra', 'decembra'], + days: ['Nedele', 'PondelÃ', 'ÃterÜ', 'Streda', 'Ätvrtek', 'Pátek', 'Sobota'], + days_abbr: ['ne', 'po', 'ut', 'st', 'Å¡t', 'pi', 'so'], + + // Culture's date order: DD.MM.YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d.%m.%Y', + shortTime: '%H:%M', + AM: 'dop.', + PM: 'pop.', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '.', + + lessThanMinuteAgo: 'pred chvÃÄŸou', + minuteAgo: 'pribliÅŸne pred minútou', + minutesAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'minútou', 'minútami', 'minútami'); }, + hourAgo: 'pribliÅŸne pred hodinou', + hoursAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'hodinou', 'hodinami', 'hodinami'); }, + dayAgo: 'pred dÅom', + daysAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'dÅom', 'dÅami', 'dÅami'); }, + weekAgo: 'pred tÜşdÅom', + weeksAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'tÜşdÅom', 'tÜşdÅami', 'tÜşdÅami'); }, + monthAgo: 'pred mesiacom', + monthsAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'mesiacom', 'mesiacmi', 'mesiacmi'); }, + yearAgo: 'pred rokom', + yearsAgo: function(delta){ return 'pred {delta} ' + pluralize(delta, 'rokom', 'rokmi', 'rokmi'); }, + + lessThanMinuteUntil: 'o chvÃÄŸu', + minuteUntil: 'pribliÅŸne o minútu', + minutesUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'minútu', 'minúty', 'minúty'); }, + hourUntil: 'pribliÅŸne o hodinu', + hoursUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'hodinu', 'hodiny', 'hodÃn'); }, + dayUntil: 'o deÅ', + daysUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'deÅ', 'dni', 'dnÃ'); }, + weekUntil: 'o tÜşdeÅ', + weeksUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'tÜşdeÅ', 'tÜşdne', 'tÜşdÅov'); }, + monthUntil: 'o mesiac', + monthsUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'mesiac', 'mesiace', 'mesiacov'); }, + yearUntil: 'o rok', + yearsUntil: function(delta){ return 'o {delta} ' + pluralize(delta, 'rok', 'roky', 'rokov'); } +}); + +})(); + +/* +--- + +name: Locale.sk-SK.Form.Validator + +description: Form Validator messages for Czech. + +license: MIT-style license + +authors: + - Ivan Masár + +requires: + - Locale + +provides: [Locale.sk-SK.Form.Validator] + +... +*/ + +Locale.define('sk-SK', 'FormValidator', { + + required: 'Táto poloÅŸka je povinná.', + minLength: 'Zadajte prosÃm aspoÅ {minLength} znakov (momentálne {length} znakov).', + maxLength: 'Zadajte prosÃm menej ako {maxLength} znakov (momentálne {length} znakov).', + integer: 'Zadajte prosÃm celé ÄÃslo. Desetinné ÄÃsla (napr. 1.25) nie sú povolené.', + numeric: 'Zadajte len ÄÃselné hodnoty (t.j. â1â alebo â1.1â alebo â-1â alebo â-1.1â).', + digits: 'Zadajte prosÃm len ÄÃsla a interpunkÄné znamienka (naprÃklad telefónne ÄÃslo s pomlÄkami albo bodkami je povolené).', + alpha: 'Zadajte prosÃm len pÃsmená (a-z). Medzery alebo iné znaky nie sú povolené.', + alphanum: 'Zadajte prosÃm len pÃsmená (a-z) alebo ÄÃslice (0-9). Medzery alebo iné znaky nie sú povolené.', + dateSuchAs: 'Zadajte prosÃm platnÜ dátum v tvare {date}', + dateInFormatMDY: 'Zadajte prosÃm platnÜ datum v tvare MM / DD / RRRR (t.j. â12/31/1999â)', + email: 'Zadajte prosÃm platnú emailovú adresu. NaprÃklad âfred@domain.comâ.', + url: 'Zadajte prosÃm platnoú adresu URL v tvare http://www.example.com.', + currencyDollar: 'Zadajte prosÃm platnú Äiastku. NaprÃklad $100.00.', + oneRequired: 'Zadajte prosÃm aspoÅ jednu hodnotu z tÜchto poloÅŸiek.', + errorPrefix: 'Chyba: ', + warningPrefix: 'Upozornenie: ', + + // Form.Validator.Extras + noSpace: 'V tejto poloÅŸle nie sú povolené medzery', + reqChkByNode: 'Nie sú vybrané ÅŸiadne poloÅŸky.', + requiredChk: 'Táto poloÅŸka je povinná.', + reqChkByName: 'ProsÃm vyberte {label}.', + match: 'Táto poloÅŸka sa musà zhodovaÅ¥ s poloÅŸkou {matchName}', + startDate: 'dátum zaÄiatku', + endDate: 'dátum ukonÄenia', + currendDate: 'aktuálny dátum', + afterDate: 'Dátum by mal bÜť rovnakÜ alebo vÀÄšà ako {label}.', + beforeDate: 'Dátum by mal byÅ¥ rovnakÜ alebo menšà ako {label}.', + startMonth: 'Vyberte poÄiatoÄnÜ mesiac.', + sameMonth: 'Tieto dva dátumy musia bÜť v rovnakom mesiaci - zmeÅte jeden z nich.', + creditcard: 'Zadané ÄÃslo kreditnej karty je neplatné. ProsÃm, opravte ho. Bolo zadanÜch {length} ÄÃslic.' + +}); + +/* +--- + +name: Locale.si-SI.Date + +description: Date messages for Slovenian. + +license: MIT-style license + +authors: + - Radovan Lozej + +requires: + - Locale + +provides: [Locale.si-SI.Date] + +... +*/ + +(function(){ + +var pluralize = function(n, one, two, three, other){ + return (n >= 1 && n <= 3) ? arguments[n] : other; +}; + +Locale.define('sl-SI', 'Date', { + + months: ['januar', 'februar', 'marec', 'april', 'maj', 'junij', 'julij', 'avgust', 'september', 'oktober', 'november', 'december'], + months_abbr: ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'avg', 'sep', 'okt', 'nov', 'dec'], + days: ['nedelja', 'ponedeljek', 'torek', 'sreda', 'Äetrtek', 'petek', 'sobota'], + days_abbr: ['ned', 'pon', 'tor', 'sre', 'Äet', 'pet', 'sob'], + + // Culture's date order: DD.MM.YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d.%m.%Y', + shortTime: '%H.%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '.', + + lessThanMinuteAgo: 'manj kot minuto nazaj', + minuteAgo: 'minuto nazaj', + minutesAgo: function(delta){ return '{delta} ' + pluralize(delta, 'minuto', 'minuti', 'minute', 'minut') + ' nazaj'; }, + hourAgo: 'uro nazaj', + hoursAgo: function(delta){ return '{delta} ' + pluralize(delta, 'uro', 'uri', 'ure', 'ur') + ' nazaj'; }, + dayAgo: 'dan nazaj', + daysAgo: function(delta){ return '{delta} ' + pluralize(delta, 'dan', 'dneva', 'dni', 'dni') + ' nazaj'; }, + weekAgo: 'teden nazaj', + weeksAgo: function(delta){ return '{delta} ' + pluralize(delta, 'teden', 'tedna', 'tedne', 'tednov') + ' nazaj'; }, + monthAgo: 'mesec nazaj', + monthsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'mesec', 'meseca', 'mesece', 'mesecov') + ' nazaj'; }, + yearthAgo: 'leto nazaj', + yearsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'leto', 'leti', 'leta', 'let') + ' nazaj'; }, + + lessThanMinuteUntil: 'Å¡e manj kot minuto', + minuteUntil: 'Å¡e minuta', + minutesUntil: function(delta){ return 'Å¡e {delta} ' + pluralize(delta, 'minuta', 'minuti', 'minute', 'minut'); }, + hourUntil: 'Å¡e ura', + hoursUntil: function(delta){ return 'Å¡e {delta} ' + pluralize(delta, 'ura', 'uri', 'ure', 'ur'); }, + dayUntil: 'Å¡e dan', + daysUntil: function(delta){ return 'Å¡e {delta} ' + pluralize(delta, 'dan', 'dneva', 'dnevi', 'dni'); }, + weekUntil: 'Å¡e tedn', + weeksUntil: function(delta){ return 'Å¡e {delta} ' + pluralize(delta, 'teden', 'tedna', 'tedni', 'tednov'); }, + monthUntil: 'Å¡e mesec', + monthsUntil: function(delta){ return 'Å¡e {delta} ' + pluralize(delta, 'mesec', 'meseca', 'meseci', 'mesecov'); }, + yearUntil: 'Å¡e leto', + yearsUntil: function(delta){ return 'Å¡e {delta} ' + pluralize(delta, 'leto', 'leti', 'leta', 'let'); } + +}); + +})(); + +/* +--- + +name: Locale.si-SI.Form.Validator + +description: Form Validator messages for Slovenian. + +license: MIT-style license + +authors: + - Radovan Lozej + +requires: + - Locale + +provides: [Locale.si-SI.Form.Validator] + +... +*/ + +Locale.define('sl-SI', 'FormValidator', { + + required: 'To polje je obvezno', + minLength: 'Prosim, vnesite vsaj {minLength} znakov (vnesli ste {length} znakov).', + maxLength: 'Prosim, ne vnesite veÄ kot {maxLength} znakov (vnesli ste {length} znakov).', + integer: 'Prosim, vnesite celo Å¡tevilo. Decimalna Å¡tevila (kot 1,25) niso dovoljena.', + numeric: 'Prosim, vnesite samo numeriÄne vrednosti (kot "1" ali "1.1" ali "-1" ali "-1.1").', + digits: 'Prosim, uporabite Å¡tevilke in loÄila le na tem polju (na primer, dovoljena je telefonska Å¡tevilka z pomiÅ¡laji ali pikami).', + alpha: 'Prosim, uporabite le Ärke v tem plju. Presledki in drugi znaki niso dovoljeni.', + alphanum: 'Prosim, uporabite samo Ärke ali Å¡tevilke v tem polju. Presledki in drugi znaki niso dovoljeni.', + dateSuchAs: 'Prosim, vnesite pravilen datum kot {date}', + dateInFormatMDY: 'Prosim, vnesite pravilen datum kot MM.DD.YYYY (primer "12.31.1999")', + email: 'Prosim, vnesite pravilen email naslov. Na primer "fred@domain.com".', + url: 'Prosim, vnesite pravilen URL kot http://www.example.com.', + currencyDollar: 'Prosim, vnesit epravilno vrednost â¬. Primer 100,00⬠.', + oneRequired: 'Prosimo, vnesite nekaj za vsaj eno izmed teh polj.', + errorPrefix: 'Napaka: ', + warningPrefix: 'Opozorilo: ', + + // Form.Validator.Extras + noSpace: 'To vnosno polje ne dopuÅ¡Äa presledkov.', + reqChkByNode: 'NiÄ niste izbrali.', + requiredChk: 'To polje je obvezno', + reqChkByName: 'Prosim, izberite {label}.', + match: 'To polje se mora ujemati z poljem {matchName}', + startDate: 'datum zaÄetka', + endDate: 'datum konca', + currentDate: 'trenuten datum', + afterDate: 'Datum bi moral biti isti ali po {label}.', + beforeDate: 'Datum bi moral biti isti ali pred {label}.', + startMonth: 'Prosim, vnesite zaÄetni datum', + sameMonth: 'Ta dva datuma morata biti v istem mesecu - premeniti morate eno ali drugo.', + creditcard: 'Å tevilka kreditne kartice ni pravilna. Preverite Å¡tevilko ali poskusite Å¡e enkrat. VneÅ¡enih {length} znakov.' + +}); + +/* +--- + +name: Locale.sv-SE.Date + +description: Date messages for Swedish. + +license: MIT-style license + +authors: + - Martin Lundgren + +requires: + - Locale + +provides: [Locale.sv-SE.Date] + +... +*/ + +Locale.define('sv-SE', 'Date', { + + months: ['januari', 'februari', 'mars', 'april', 'maj', 'juni', 'juli', 'augusti', 'september', 'oktober', 'november', 'december'], + months_abbr: ['jan', 'feb', 'mar', 'apr', 'maj', 'jun', 'jul', 'aug', 'sep', 'okt', 'nov', 'dec'], + days: ['söndag', 'mÃ¥ndag', 'tisdag', 'onsdag', 'torsdag', 'fredag', 'lördag'], + days_abbr: ['sön', 'mÃ¥n', 'tis', 'ons', 'tor', 'fre', 'lör'], + + // Culture's date order: YYYY-MM-DD + dateOrder: ['year', 'month', 'date'], + shortDate: '%Y-%m-%d', + shortTime: '%H:%M', + AM: '', + PM: '', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: 'mindre Àn en minut sedan', + minuteAgo: 'ungefÀr en minut sedan', + minutesAgo: '{delta} minuter sedan', + hourAgo: 'ungefÀr en timme sedan', + hoursAgo: 'ungefÀr {delta} timmar sedan', + dayAgo: '1 dag sedan', + daysAgo: '{delta} dagar sedan', + + lessThanMinuteUntil: 'mindre Àn en minut sedan', + minuteUntil: 'ungefÀr en minut sedan', + minutesUntil: '{delta} minuter sedan', + hourUntil: 'ungefÀr en timme sedan', + hoursUntil: 'ungefÀr {delta} timmar sedan', + dayUntil: '1 dag sedan', + daysUntil: '{delta} dagar sedan' + +}); + +/* +--- + +name: Locale.sv-SE.Form.Validator + +description: Form Validator messages for Swedish. + +license: MIT-style license + +authors: + - Martin Lundgren + +requires: + - Locale + +provides: [Locale.sv-SE.Form.Validator] + +... +*/ + +Locale.define('sv-SE', 'FormValidator', { + + required: 'FÀltet Àr obligatoriskt.', + minLength: 'Ange minst {minLength} tecken (du angav {length} tecken).', + maxLength: 'Ange högst {maxLength} tecken (du angav {length} tecken). ', + integer: 'Ange ett heltal i fÀltet. Tal med decimaler (t.ex. 1,25) Àr inte tillÃ¥tna.', + numeric: 'Ange endast numeriska vÀrden i detta fÀlt (t.ex. "1" eller "1.1" eller "-1" eller "-1,1").', + digits: 'AnvÀnd endast siffror och skiljetecken i detta fÀlt (till exempel ett telefonnummer med bindestreck tillÃ¥tet).', + alpha: 'AnvÀnd endast bokstÀver (a-ö) i detta fÀlt. Inga mellanslag eller andra tecken Àr tillÃ¥tna.', + alphanum: 'AnvÀnd endast bokstÀver (a-ö) och siffror (0-9) i detta fÀlt. Inga mellanslag eller andra tecken Àr tillÃ¥tna.', + dateSuchAs: 'Ange ett giltigt datum som t.ex. {date}', + dateInFormatMDY: 'Ange ett giltigt datum som t.ex. YYYY-MM-DD (i.e. "1999-12-31")', + email: 'Ange en giltig e-postadress. Till exempel "erik@domain.com".', + url: 'Ange en giltig webbadress som http://www.example.com.', + currencyDollar: 'Ange en giltig belopp. Exempelvis 100,00.', + oneRequired: 'VÀnligen ange minst ett av dessa alternativ.', + errorPrefix: 'Fel: ', + warningPrefix: 'Varning: ', + + // Form.Validator.Extras + noSpace: 'Det fÃ¥r inte finnas nÃ¥gra mellanslag i detta fÀlt.', + reqChkByNode: 'Inga objekt Àr valda.', + requiredChk: 'Detta Àr ett obligatoriskt fÀlt.', + reqChkByName: 'VÀlj en {label}.', + match: 'Detta fÀlt mÃ¥ste matcha {matchName}', + startDate: 'startdatumet', + endDate: 'slutdatum', + currentDate: 'dagens datum', + afterDate: 'Datumet bör vara samma eller senare Àn {label}.', + beforeDate: 'Datumet bör vara samma eller tidigare Àn {label}.', + startMonth: 'VÀlj en start mÃ¥nad', + sameMonth: 'Dessa tvÃ¥ datum mÃ¥ste vara i samma mÃ¥nad - du mÃ¥ste Àndra det ena eller det andra.' + +}); + +/* +--- + +name: Locale.sv-SE.Number + +description: Number messages for Swedish. + +license: MIT-style license + +authors: + - Arian Stolwijk + - Martin Lundgren + +requires: + - Locale + - Locale.EU.Number + +provides: [Locale.sv-SE.Number] + +... +*/ + +Locale.define('sv-SE', 'Number', { + + currency: { + prefix: 'SEK ' + } + +}).inherit('EU', 'Number'); + +/* +--- + +name: Locale.tr-TR.Date + +description: Date messages for Turkish. + +license: MIT-style license + +authors: + - Faruk Can Bilir + +requires: + - Locale + +provides: [Locale.tr-TR.Date] + +... +*/ + +Locale.define('tr-TR', 'Date', { + + months: ['Ocak', 'Åubat', 'Mart', 'Nisan', 'Mayıs', 'Haziran', 'Temmuz', 'AÄustos', 'EylÃŒl', 'Ekim', 'Kasım', 'Aralık'], + months_abbr: ['Oca', 'Åub', 'Mar', 'Nis', 'May', 'Haz', 'Tem', 'AÄu', 'Eyl', 'Eki', 'Kas', 'Ara'], + days: ['Pazar', 'Pazartesi', 'Salı', 'ÃarÅamba', 'PerÅembe', 'Cuma', 'Cumartesi'], + days_abbr: ['Pa', 'Pzt', 'Sa', 'Ãa', 'Pe', 'Cu', 'Cmt'], + + // Culture's date order: MM/DD/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d/%m/%Y', + shortTime: '%H.%M', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: 'bir dakikadan önce', + minuteAgo: 'yaklaÅık bir dakika önce', + minutesAgo: '{delta} dakika önce', + hourAgo: 'bir saat kadar önce', + hoursAgo: '{delta} saat kadar önce', + dayAgo: 'bir gÃŒn önce', + daysAgo: '{delta} gÃŒn önce', + weekAgo: 'bir hafta önce', + weeksAgo: '{delta} hafta önce', + monthAgo: 'bir ay önce', + monthsAgo: '{delta} ay önce', + yearAgo: 'bir yıl önce', + yearsAgo: '{delta} yıl önce', + + lessThanMinuteUntil: 'bir dakikadan az sonra', + minuteUntil: 'bir dakika kadar sonra', + minutesUntil: '{delta} dakika sonra', + hourUntil: 'bir saat kadar sonra', + hoursUntil: '{delta} saat kadar sonra', + dayUntil: 'bir gÃŒn sonra', + daysUntil: '{delta} gÃŒn sonra', + weekUntil: 'bir hafta sonra', + weeksUntil: '{delta} hafta sonra', + monthUntil: 'bir ay sonra', + monthsUntil: '{delta} ay sonra', + yearUntil: 'bir yıl sonra', + yearsUntil: '{delta} yıl sonra' + +}); + +/* +--- + +name: Locale.tr-TR.Form.Validator + +description: Form Validator messages for Turkish. + +license: MIT-style license + +authors: + - Faruk Can Bilir + +requires: + - Locale + +provides: [Locale.tr-TR.Form.Validator] + +... +*/ + +Locale.define('tr-TR', 'FormValidator', { + + required: 'Bu alan zorunlu.', + minLength: 'LÃŒtfen en az {minLength} karakter girin (siz {length} karakter girdiniz).', + maxLength: 'LÃŒtfen en fazla {maxLength} karakter girin (siz {length} karakter girdiniz).', + integer: 'LÃŒtfen bu alana sadece tamsayı girin. Ondalıklı sayılar (ör: 1.25) kullanılamaz.', + numeric: 'LÃŒtfen bu alana sadece sayısal deÄer girin (ör: "1", "1.1", "-1" ya da "-1.1").', + digits: 'LÃŒtfen bu alana sadece sayısal deÄer ve noktalama iÅareti girin (örneÄin, nokta ve tire içeren bir telefon numarası kullanılabilir).', + alpha: 'LÃŒtfen bu alanda yalnızca harf kullanın. BoÅluk ve diÄer karakterler kullanılamaz.', + alphanum: 'LÃŒtfen bu alanda sadece harf ve rakam kullanın. BoÅluk ve diÄer karakterler kullanılamaz.', + dateSuchAs: 'LÃŒtfen geçerli bir tarih girin (Ãr: {date})', + dateInFormatMDY: 'LÃŒtfen geçerli bir tarih girin (GG/AA/YYYY, ör: "31/12/1999")', + email: 'LÃŒtfen geçerli bir email adresi girin. Ãr: "kemal@etikan.com".', + url: 'LÃŒtfen geçerli bir URL girin. Ãr: http://www.example.com.', + currencyDollar: 'LÃŒtfen geçerli bir TL miktarı girin. Ãr: 100,00 TL .', + oneRequired: 'LÃŒtfen en az bir tanesini doldurun.', + errorPrefix: 'Hata: ', + warningPrefix: 'Uyarı: ', + + // Form.Validator.Extras + noSpace: 'Bu alanda boÅluk kullanılamaz.', + reqChkByNode: 'Hiçbir öÄe seçilmemiÅ.', + requiredChk: 'Bu alan zorunlu.', + reqChkByName: 'LÃŒtfen bir {label} girin.', + match: 'Bu alan, {matchName} alanıyla uyuÅmalı', + startDate: 'baÅlangıç tarihi', + endDate: 'bitiÅ tarihi', + currentDate: 'bugÃŒnÃŒn tarihi', + afterDate: 'Tarih, {label} tarihiyle aynı gÃŒn ya da ondan sonra olmalıdır.', + beforeDate: 'Tarih, {label} tarihiyle aynı gÃŒn ya da ondan önce olmalıdır.', + startMonth: 'LÃŒtfen bir baÅlangıç ayı seçin', + sameMonth: 'Bu iki tarih aynı ayda olmalı - bir tanesini deÄiÅtirmeniz gerekiyor.', + creditcard: 'GirdiÄiniz kredi kartı numarası geçersiz. LÃŒtfen kontrol edip tekrar deneyin. {length} hane girildi.' + +}); + +/* +--- + +name: Locale.tr-TR.Number + +description: Number messages for Turkish. + +license: MIT-style license + +authors: + - Faruk Can Bilir + +requires: + - Locale + - Locale.EU.Number + +provides: [Locale.tr-TR.Number] + +... +*/ + +Locale.define('tr-TR', 'Number', { + + currency: { + decimals: 0, + suffix: ' TL' + } + +}).inherit('EU', 'Number'); + +/* +--- + +name: Locale.uk-UA.Date + +description: Date messages for Ukrainian (utf-8). + +license: MIT-style license + +authors: + - Slik + +requires: + - Locale + +provides: [Locale.uk-UA.Date] + +... +*/ + +(function(){ + +var pluralize = function(n, one, few, many, other){ + var d = (n / 10).toInt(), + z = n % 10, + s = (n / 100).toInt(); + + if (d == 1 && n > 10) return many; + if (z == 1) return one; + if (z > 0 && z < 5) return few; + return many; +}; + +Locale.define('uk-UA', 'Date', { + + months: ['СÑÑеМÑ', 'ÐÑÑОй', 'ÐеÑезеМÑ', 'ÐвÑÑеМÑ', 'ТÑавеМÑ', 'ЧеÑвеМÑ', 'ÐОпеМÑ', 'СеÑпеМÑ', 'ÐеÑеÑеМÑ', 'ÐПвÑеМÑ', 'ÐОÑÑПпаЎ', 'ÐÑÑЎеМÑ'], + months_abbr: ['СÑÑ', 'ÐÑÑ', 'ÐеÑ', 'ÐвÑÑ', 'ТÑав', 'ЧеÑв', 'ÐОп', 'СеÑп', 'ÐеÑ', 'ÐПвÑ', 'ÐОÑÑ', 'ÐÑÑÐŽ' ], + days: ['ÐеЎÑлÑ', 'ÐПМеЎÑлПк', 'ÐÑвÑПÑПк', 'СеÑеЎа', 'ЧеÑвеÑ', "Ð'ÑÑМОÑÑ", 'СÑбПÑа'], + days_abbr: ['ÐÐŽ', 'ÐМ', 'ÐÑ', 'СÑ', 'ЧÑ', 'ÐÑ', 'Сб'], + + // Culture's date order: DD/MM/YYYY + dateOrder: ['date', 'month', 'year'], + shortDate: '%d/%m/%Y', + shortTime: '%H:%M', + AM: 'ЎП пПлÑЎМÑ', + PM: 'пП пПлÑЎМÑ', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: 'ЌеМÑÑе Ñ
вОлОМО ÑПЌÑ', + minuteAgo: 'Ñ
Ð²ÐžÐ»ÐžÐœÑ ÑПЌÑ', + minutesAgo: function(delta){ return '{delta} ' + pluralize(delta, 'Ñ
вОлОМÑ', 'Ñ
вОлОМО', 'Ñ
вОлОМ') + ' ÑПЌÑ'; }, + hourAgo: 'Ð³ÐŸÐŽÐžÐœÑ ÑПЌÑ', + hoursAgo: function(delta){ return '{delta} ' + pluralize(delta, 'гПЎОМÑ', 'гПЎОМО', 'гПЎОМ') + ' ÑПЌÑ'; }, + dayAgo: 'вÑПÑа', + daysAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ЎеМÑ', 'ЎМÑ', 'ЎМÑв') + ' ÑПЌÑ'; }, + weekAgo: 'ÑÐžÐ¶ÐŽÐµÐœÑ ÑПЌÑ', + weeksAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ÑОжЎеМÑ', 'ÑОжМÑ', 'ÑОжМÑв') + ' ÑПЌÑ'; }, + monthAgo: 'ÐŒÑÑÑÑÑ ÑПЌÑ', + monthsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ÐŒÑÑÑÑÑ', 'ÐŒÑÑÑÑÑ', 'ÐŒÑÑÑÑÑв') + ' ÑПЌÑ'; }, + yearAgo: 'ÑÑк ÑПЌÑ', + yearsAgo: function(delta){ return '{delta} ' + pluralize(delta, 'ÑÑк', 'ÑПкО', 'ÑПкÑв') + ' ÑПЌÑ'; }, + + lessThanMinuteUntil: 'за ЌОÑÑ', + minuteUntil: 'ÑеÑез Ñ
вОлОМÑ', + minutesUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'Ñ
вОлОМÑ', 'Ñ
вОлОМО', 'Ñ
вОлОМ'); }, + hourUntil: 'ÑеÑез гПЎОМÑ', + hoursUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'гПЎОМÑ', 'гПЎОМО', 'гПЎОМ'); }, + dayUntil: 'завÑÑа', + daysUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'ЎеМÑ', 'ЎМÑ', 'ЎМÑв'); }, + weekUntil: 'ÑеÑез ÑОжЎеМÑ', + weeksUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'ÑОжЎеМÑ', 'ÑОжМÑ', 'ÑОжМÑв'); }, + monthUntil: 'ÑеÑез ÐŒÑÑÑÑÑ', + monthesUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'ÐŒÑÑÑÑÑ', 'ÐŒÑÑÑÑÑ', 'ÐŒÑÑÑÑÑв'); }, + yearUntil: 'ÑеÑез ÑÑк', + yearsUntil: function(delta){ return 'ÑеÑез {delta} ' + pluralize(delta, 'ÑÑк', 'ÑПкО', 'ÑПкÑв'); } + +}); + +})(); + +/* +--- + +name: Locale.uk-UA.Form.Validator + +description: Form Validator messages for Ukrainian (utf-8). + +license: MIT-style license + +authors: + - Slik + +requires: + - Locale + +provides: [Locale.uk-UA.Form.Validator] + +... +*/ + +Locale.define('uk-UA', 'FormValidator', { + + required: 'Ње пПле пПвОММе бÑÑО запПвМеМОЌ.', + minLength: 'ÐвеЎÑÑÑ Ñ
ПÑа б {minLength} ÑОЌвПлÑв (ÐО ввелО {length}).', + maxLength: 'ÐÑлÑкÑÑÑÑ ÑОЌвПлÑв Ме ЌПже бÑÑО бÑлÑÑе {maxLength} (ÐО ввелО {length}).', + integer: 'ÐвеЎÑÑÑ Ð² Ñе пПле ÑОÑлП. ÐÑÐŸÐ±ÐŸÐ²Ñ ÑОÑла (МапÑОклаЎ 1.25) Ме ЎПзвПлеМÑ.', + numeric: 'ÐвеЎÑÑÑ Ð² Ñе пПле ÑОÑлП (МапÑОклаЎ "1" абП "1.1", абП "-1", абП "-1.1").', + digits: 'Ð ÑÑÐŸÐŒÑ Ð¿ÐŸÐ»Ñ Ð²Ðž ЌПжеÑе вОкПÑОÑÑПвÑваÑО лОÑе ÑОÑÑО Ñ Ð·ÐœÐ°ÐºÐž пÑМкÑÑаÑÑÑ (МапÑОклаЎ, ÑелеÑПММОй ÐœÐŸÐŒÐµÑ Ð· зМакаЌО ЎеÑÑÐ·Ñ Ð°Ð±ÐŸ з кÑапкаЌО).', + alpha: 'Ð ÑÑÐŸÐŒÑ Ð¿ÐŸÐ»Ñ ÐŒÐŸÐ¶ÐœÐ° вОкПÑОÑÑПвÑваÑО лОÑе лаÑОМÑÑÐºÑ Ð»ÑÑеÑО (a-z). ÐÑПбÑлО Ñ ÑМÑÑ ÑОЌвПлО забПÑПМеМÑ.', + alphanum: 'Ð ÑÑÐŸÐŒÑ Ð¿ÐŸÐ»Ñ ÐŒÐŸÐ¶ÐœÐ° вОкПÑОÑÑПвÑваÑО лОÑе лаÑОМÑÑÐºÑ Ð»ÑÑеÑО (a-z) Ñ ÑОÑÑО (0-9). ÐÑПбÑлО Ñ ÑМÑÑ ÑОЌвПлО забПÑПМеМÑ.', + dateSuchAs: 'ÐвеЎÑÑÑ ÐºÐŸÑекÑÐœÑ ÐŽÐ°ÑÑ {date}.', + dateInFormatMDY: 'ÐвеЎÑÑÑ ÐŽÐ°ÑÑ Ð² ÑПÑЌаÑÑ ÐÐ/ÐÐ/Ð Ð Ð Ð (МапÑОклаЎ "12/31/2009").', + email: 'ÐвеЎÑÑÑ ÐºÐŸÑекÑÐœÑ Ð°ÐŽÑеÑÑ ÐµÐ»ÐµÐºÑÑÐŸÐœÐœÐŸÑ Ð¿ÐŸÑÑО (МапÑОклаЎ "name@domain.com").', + url: 'ÐвеЎÑÑÑ ÐºÐŸÑекÑМе ÑМÑеÑМеÑ-пПÑÐžÐ»Ð°ÐœÐœÑ (МапÑОклаЎ http://www.example.com).', + currencyDollar: 'ÐвеЎÑÑÑ ÑÑÐŒÑ Ð² ЎПлаÑаÑ
(МапÑОклаЎ "$100.00").', + oneRequired: 'ÐапПвМÑÑÑ ÐŸÐŽÐœÐµ з пПлÑв.', + errorPrefix: 'ÐПЌОлка: ', + warningPrefix: 'Увага: ', + + noSpace: 'ÐÑПбÑлО забПÑПМеМÑ.', + reqChkByNode: 'Ðе вÑÐŽÐŒÑÑеМП жПЎМПгП ваÑÑаМÑÑ.', + requiredChk: 'Ње пПле пПвОММе бÑÑО вÑÐŒÑÑеМОЌ.', + reqChkByName: 'ÐÑÐŽÑ Ð»Ð°Ñка, вÑÐŽÐŒÑÑÑÑе {label}.', + match: 'Ње пПле пПвОММП вÑЎпПвÑЎаÑО {matchName}', + startDate: 'пПÑаÑкПва ЎаÑа', + endDate: 'кÑМÑева ЎаÑа', + currentDate: 'ÑÑПгПЎМÑÑÐœÑ ÐŽÐ°Ñа', + afterDate: 'ÐŠÑ ÐŽÐ°Ñа пПвОММа бÑÑО ÑÐ°ÐºÐŸÑ Ð¶, абП пÑзМÑÑÐŸÑ Ð·Ð° {label}.', + beforeDate: 'ÐŠÑ ÐŽÐ°Ñа пПвОММа бÑÑО ÑÐ°ÐºÐŸÑ Ð¶, абП ÑаМÑÑÐŸÑ Ð·Ð° {label}.', + startMonth: 'ÐÑÐŽÑ Ð»Ð°Ñка, вОбеÑÑÑÑ Ð¿ÐŸÑаÑкПвОй ÐŒÑÑÑÑÑ', + sameMonth: 'ÐŠÑ ÐŽÐ°ÑО Ð¿ÐŸÐ²ÐžÐœÐœÑ Ð²ÑЎМПÑОÑОÑÑ ÐŸÐŽÐœÐŸÐ³ÐŸ Ñ ÑПгП ж ÐŒÑÑÑÑÑ. ÐÑÐŽÑ Ð»Ð°Ñка, зЌÑМÑÑÑ ÐŸÐŽÐœÑ Ð· МОÑ
.', + creditcard: 'ÐÐŸÐŒÐµÑ ÐºÑеЎОÑÐœÐŸÑ ÐºÐ°ÑÑО ввеЎеМОй МепÑавОлÑМП. ÐÑÐŽÑ Ð»Ð°Ñка, пеÑевÑÑÑе йПгП. ÐвеЎеМП {length} ÑОЌвПлÑв.' + +}); + +/* +--- + +name: Locale.zh-CH.Date + +description: Date messages for Chinese (simplified and traditional). + +license: MIT-style license + +authors: + - YMind Chan + +requires: + - Locale + +provides: [Locale.zh-CH.Date] + +... +*/ + +// Simplified Chinese +Locale.define('zh-CHS', 'Date', { + + months: ['äžæ', 'äºæ', 'äžæ', 'åæ', 'äºæ', 'å
æ', 'äžæ', 'å
«æ', '乿', 'åæ', 'åäžæ', 'åäºæ'], + months_abbr: ['äž', 'äº', 'äž', 'å', 'äº', 'å
', 'äž', 'å
«', 'ä¹', 'å', 'åäž', 'åäº'], + days: ['æææ¥', 'ææäž', 'ææäº', 'ææäž', 'ææå', 'ææäº', 'ææå
'], + days_abbr: ['æ¥', 'äž', 'äº', 'äž', 'å', 'äº', 'å
'], + + // Culture's date order: YYYY-MM-DD + dateOrder: ['year', 'month', 'date'], + shortDate: '%Y-%m-%d', + shortTime: '%I:%M%p', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: 'äžå°1åéå', + minuteAgo: '倧纊1åéå', + minutesAgo: '{delta}åéä¹å', + hourAgo: '倧纊1å°æ¶å', + hoursAgo: '倧纊{delta}å°æ¶å', + dayAgo: '1倩å', + daysAgo: '{delta}倩å', + weekAgo: '1ææå', + weeksAgo: '{delta}ææå', + monthAgo: '1䞪æå', + monthsAgo: '{delta}䞪æå', + yearAgo: '1幎å', + yearsAgo: '{delta}幎å', + + lessThanMinuteUntil: 'ä»ç°åšåŒå§äžå°1åé', + minuteUntil: 'ä»ç°åšåŒå§çŽ1åé', + minutesUntil: 'ä»ç°åšåŒå§çºŠ{delta}åé', + hourUntil: 'ä»ç°åšåŒå§1å°æ¶', + hoursUntil: 'ä»ç°åšåŒå§çºŠ{delta}å°æ¶', + dayUntil: 'ä»ç°åšåŒå§1倩', + daysUntil: 'ä»ç°åšåŒå§{delta}倩', + weekUntil: 'ä»ç°åšåŒå§1ææ', + weeksUntil: 'ä»ç°åšåŒå§{delta}ææ', + monthUntil: 'ä»ç°åšåŒå§äžäžªæ', + monthsUntil: 'ä»ç°åšåŒå§{delta}䞪æ', + yearUntil: 'ä»ç°åšåŒå§1幎', + yearsUntil: 'ä»ç°åšåŒå§{delta}幎' + +}); + +// Traditional Chinese +Locale.define('zh-CHT', 'Date', { + + months: ['äžæ', 'äºæ', 'äžæ', 'åæ', 'äºæ', 'å
æ', 'äžæ', 'å
«æ', '乿', 'åæ', 'åäžæ', 'åäºæ'], + months_abbr: ['äž', 'äº', 'äž', 'å', 'äº', 'å
', 'äž', 'å
«', 'ä¹', 'å', 'åäž', 'åäº'], + days: ['æææ¥', 'ææäž', 'ææäº', 'ææäž', 'ææå', 'ææäº', 'ææå
'], + days_abbr: ['æ¥', 'äž', 'äº', 'äž', 'å', 'äº', 'å
'], + + // Culture's date order: YYYY-MM-DD + dateOrder: ['year', 'month', 'date'], + shortDate: '%Y-%m-%d', + shortTime: '%I:%M%p', + AM: 'AM', + PM: 'PM', + firstDayOfWeek: 1, + + // Date.Extras + ordinal: '', + + lessThanMinuteAgo: 'äžå°1åéå', + minuteAgo: '倧çŽ1åéå', + minutesAgo: '{delta}åéä¹å', + hourAgo: '倧çŽ1å°æå', + hoursAgo: '倧çŽ{delta}å°æå', + dayAgo: '1倩å', + daysAgo: '{delta}倩å', + weekAgo: '1ææå', + weeksAgo: '{delta}ææå', + monthAgo: '1䞪æå', + monthsAgo: '{delta}䞪æå', + yearAgo: '1幎å', + yearsAgo: '{delta}幎å', + + lessThanMinuteUntil: 'åŸçŸåšéå§äžå°1åé', + minuteUntil: 'åŸçŸåšéå§çŽ1åé', + minutesUntil: 'åŸçŸåšéå§çŽ{delta}åé', + hourUntil: 'åŸçŸåšéå§1å°æ', + hoursUntil: 'åŸçŸåšéå§çŽ{delta}å°æ', + dayUntil: 'åŸçŸåšéå§1倩', + daysUntil: 'åŸçŸåšéå§{delta}倩', + weekUntil: 'åŸçŸåšéå§1ææ', + weeksUntil: 'åŸçŸåšéå§{delta}ææ', + monthUntil: 'åŸçŸåšéå§äžåæ', + monthsUntil: 'åŸçŸåšéå§{delta}åæ', + yearUntil: 'åŸçŸåšéå§1幎', + yearsUntil: 'åŸçŸåšéå§{delta}幎' + +}); + +/* +--- + +name: Locale.zh-CH.Form.Validator + +description: Form Validator messages for Chinese (simplified and traditional). + +license: MIT-style license + +authors: + - YMind Chan + +requires: + - Locale + - Form.Validator + +provides: [Form.zh-CH.Form.Validator, Form.Validator.CurrencyYuanValidator] + +... +*/ + +// Simplified Chinese +Locale.define('zh-CHS', 'FormValidator', { + + required: 'æ€é¡¹å¿
å¡«ã', + minLength: '请è³å°èŸå
¥ {minLength} 䞪å笊 (å·²èŸå
¥ {length} 䞪)ã', + maxLength: 'æå€åªèœèŸå
¥ {maxLength} 䞪å笊 (å·²èŸå
¥ {length} 䞪)ã', + integer: '请èŸå
¥äžäžªæŽæ°ïŒäžèœå
å«å°æ°ç¹ãäŸåŠïŒ"1", "200"ã', + numeric: '请èŸå
¥äžäžªæ°åïŒäŸåŠïŒ"1", "1.1", "-1", "-1.1"ã', + digits: '请èŸå
¥ç±æ°ååæ ç¹ç¬Šå·ç»æçå
容ãäŸåŠçµè¯å·ç ã', + alpha: '请èŸå
¥ A-Z ç 26 äžªåæ¯ïŒäžèœå
å«ç©ºæ Œæä»»äœå
¶ä»å笊ã', + alphanum: '请èŸå
¥ A-Z ç 26 äžªåæ¯æ 0-9 ç 10 䞪æ°åïŒäžèœå
å«ç©ºæ Œæä»»äœå
¶ä»å笊ã', + dateSuchAs: '请èŸå
¥åæ³çæ¥ææ ŒåŒïŒåŠïŒ{date}ã', + dateInFormatMDY: '请èŸå
¥åæ³çæ¥ææ ŒåŒïŒäŸåŠïŒYYYY-MM-DD ("2010-12-31")ã', + email: '请èŸå
¥åæ³ççµåä¿¡ç®±å°åïŒäŸåŠïŒ"fred@domain.com"ã', + url: '请èŸå
¥åæ³ç Url å°åïŒäŸåŠïŒhttp://www.example.comã', + currencyDollar: '请èŸå
¥åæ³ç莧åžç¬Šå·ïŒäŸåŠïŒï¿¥100.0', + oneRequired: '请è³å°éæ©äžé¡¹ã', + errorPrefix: 'é误ïŒ', + warningPrefix: 'èŠåïŒ', + + // Form.Validator.Extras + noSpace: 'äžèœå
å«ç©ºæ Œã', + reqChkByNode: 'æªéæ©ä»»äœå
容ã', + requiredChk: 'æ€é¡¹å¿
å¡«ã', + reqChkByName: 'è¯·éæ© {label}.', + match: 'å¿
é¡»äž{matchName}çžå¹é
', + startDate: 'èµ·å§æ¥æ', + endDate: 'ç»ææ¥æ', + currentDate: 'åœåæ¥æ', + afterDate: 'æ¥æå¿
é¡»çäºææäº {label}.', + beforeDate: 'æ¥æå¿
é¡»æ©äºæçäº {label}.', + startMonth: 'è¯·éæ©èµ·å§æä»œ', + sameMonth: 'æšå¿
须修æ¹äž€äžªæ¥æäžçäžäžªïŒä»¥ç¡®ä¿å®ä»¬åšåäžæä»œã', + creditcard: 'æšèŸå
¥çä¿¡çšå¡å·ç äžæ£ç¡®ãåœåå·²èŸå
¥{length}䞪å笊ã' + +}); + +// Traditional Chinese +Locale.define('zh-CHT', 'FormValidator', { + + required: 'æ€é
å¿
å¡«ã ', + minLength: 'è«è³å°èŒžå
¥{minLength} åå笊(已茞å
¥{length} å)ã ', + maxLength: 'æå€åªèœèŒžå
¥{maxLength} åå笊(已茞å
¥{length} å)ã ', + integer: 'è«èŒžå
¥äžåæŽæžïŒäžèœå
å«å°æžé»ãäŸåŠïŒ"1", "200"ã ', + numeric: 'è«èŒžå
¥äžåæžåïŒäŸåŠïŒ"1", "1.1", "-1", "-1.1"ã ', + digits: 'è«èŒžå
¥ç±æžååæšé»ç¬Šèçµæçå
§å®¹ãäŸåŠé»è©±è碌ã ', + alpha: 'è«èŒžå
¥AZ ç26 å忝ïŒäžèœå
å«ç©ºæ Œæä»»äœå
¶ä»å笊ã ', + alphanum: 'è«èŒžå
¥AZ ç26 å忝æ0-9 ç10 åæžåïŒäžèœå
å«ç©ºæ Œæä»»äœå
¶ä»å笊ã ', + dateSuchAs: 'è«èŒžå
¥åæ³çæ¥ææ ŒåŒïŒåŠïŒ{date}ã ', + dateInFormatMDY: 'è«èŒžå
¥åæ³çæ¥ææ ŒåŒïŒäŸåŠïŒYYYY-MM-DD ("2010-12-31")ã ', + email: 'è«èŒžå
¥åæ³çé»åä¿¡ç®±å°åïŒäŸåŠïŒ"fred@domain.com"ã ', + url: 'è«èŒžå
¥åæ³çUrl å°åïŒäŸåŠïŒhttp://www.example.comã ', + currencyDollar: 'è«èŒžå
¥åæ³ç貚幣笊èïŒäŸåŠïŒï¿¥100.0', + oneRequired: 'è«è³å°éžæäžé
ã ', + errorPrefix: 'é¯èª€ïŒ', + warningPrefix: 'èŠåïŒ', + + // Form.Validator.Extras + noSpace: 'äžèœå
å«ç©ºæ Œã ', + reqChkByNode: 'æªéžæä»»äœå
§å®¹ã ', + requiredChk: 'æ€é
å¿
å¡«ã ', + reqChkByName: 'è«éžæ {label}.', + match: 'å¿
é è{matchName}çžå¹é
', + startDate: 'èµ·å§æ¥æ', + endDate: 'çµææ¥æ', + currentDate: 'ç¶åæ¥æ', + afterDate: 'æ¥æå¿
é çæŒæææŒ{label}.', + beforeDate: 'æ¥æå¿
é æ©æŒæçæŒ{label}.', + startMonth: 'è«éžæèµ·å§æä»œ', + sameMonth: 'æšå¿
é ä¿®æ¹å
©åæ¥æäžçäžåïŒä»¥ç¢ºä¿å®ååšåäžæä»œã ', + creditcard: 'æšèŒžå
¥çä¿¡çšå¡èç¢Œäžæ£ç¢ºãç¶å已茞å
¥{length}åå笊ã ' + +}); + +Form.Validator.add('validate-currency-yuan', { + + errorMsg: function(){ + return Form.Validator.getMsg('currencyYuan'); + }, + + test: function(element){ + // [ï¿¥]1[##][,###]+[.##] + // [ï¿¥]1###+[.##] + // [ï¿¥]0.## + // [ï¿¥].## + return Form.Validator.getValidator('IsEmpty').test(element) || (/^ï¿¥?\-?([1-9]{1}[0-9]{0,2}(\,[0-9]{3})*(\.[0-9]{0,2})?|[1-9]{1}\d*(\.[0-9]{0,2})?|0(\.[0-9]{0,2})?|(\.[0-9]{1,2})?)$/).test(element.get('value')); + } + +}); + +/* +--- + +name: Locale.zh-CH.Number + +description: Number messages for for Chinese (simplified and traditional). + +license: MIT-style license + +authors: + - YMind Chan + +requires: + - Locale + - Locale.en-US.Number + +provides: [Locale.zh-CH.Number] + +... +*/ + +// Simplified Chinese +Locale.define('zh-CHS', 'Number', { + + currency: { + prefix: 'ï¿¥ ' + } + +}).inherit('en-US', 'Number'); + +// Traditional Chinese +Locale.define('zh-CHT').inherit('zh-CHS', 'Number'); + +/* +--- + +script: Request.JSONP.js + +name: Request.JSONP + +description: Defines Request.JSONP, a class for cross domain javascript via script injection. + +license: MIT-style license + +authors: + - Aaron Newton + - Guillermo Rauch + - Arian Stolwijk + +requires: + - Core/Element + - Core/Request + - MooTools.More + +provides: [Request.JSONP] + +... +*/ + +Request.JSONP = new Class({ + + Implements: [Chain, Events, Options], + + options: {/* + onRequest: function(src, scriptElement){}, + onComplete: function(data){}, + onSuccess: function(data){}, + onCancel: function(){}, + onTimeout: function(){}, + onError: function(){}, */ + onRequest: function(src){ + if (this.options.log && window.console && console.log){ + console.log('JSONP retrieving script with url:' + src); + } + }, + onError: function(src){ + if (this.options.log && window.console && console.warn){ + console.warn('JSONP '+ src +' will fail in Internet Explorer, which enforces a 2083 bytes length limit on URIs'); + } + }, + url: '', + callbackKey: 'callback', + injectScript: document.head, + data: '', + link: 'ignore', + timeout: 0, + log: false + }, + + initialize: function(options){ + this.setOptions(options); + }, + + send: function(options){ + if (!Request.prototype.check.call(this, options)) return this; + this.running = true; + + var type = typeOf(options); + if (type == 'string' || type == 'element') options = {data: options}; + options = Object.merge(this.options, options || {}); + + var data = options.data; + switch (typeOf(data)){ + case 'element': data = document.id(data).toQueryString(); break; + case 'object': case 'hash': data = Object.toQueryString(data); + } + + var index = this.index = Request.JSONP.counter++; + + var src = options.url + + (options.url.test('\\?') ? '&' :'?') + + (options.callbackKey) + + '=Request.JSONP.request_map.request_'+ index + + (data ? '&' + data : ''); + + if (src.length > 2083) this.fireEvent('error', src); + + Request.JSONP.request_map['request_' + index] = function(){ + this.success(arguments, index); + }.bind(this); + + var script = this.getScript(src).inject(options.injectScript); + this.fireEvent('request', [src, script]); + + if (options.timeout) this.timeout.delay(options.timeout, this); + + return this; + }, + + getScript: function(src){ + if (!this.script) this.script = new Element('script', { + type: 'text/javascript', + async: true, + src: src + }); + return this.script; + }, + + success: function(args, index){ + if (!this.running) return; + this.clear() + .fireEvent('complete', args).fireEvent('success', args) + .callChain(); + }, + + cancel: function(){ + if (this.running) this.clear().fireEvent('cancel'); + return this; + }, + + isRunning: function(){ + return !!this.running; + }, + + clear: function(){ + this.running = false; + if (this.script){ + this.script.destroy(); + this.script = null; + } + return this; + }, + + timeout: function(){ + if (this.running){ + this.running = false; + this.fireEvent('timeout', [this.script.get('src'), this.script]).fireEvent('failure').cancel(); + } + return this; + } + +}); + +Request.JSONP.counter = 0; +Request.JSONP.request_map = {}; + +/* +--- + +script: Request.Periodical.js + +name: Request.Periodical + +description: Requests the same URL to pull data from a server but increases the intervals if no data is returned to reduce the load + +license: MIT-style license + +authors: + - Christoph Pojer + +requires: + - Core/Request + - MooTools.More + +provides: [Request.Periodical] + +... +*/ + +Request.implement({ + + options: { + initialDelay: 5000, + delay: 5000, + limit: 60000 + }, + + startTimer: function(data){ + var fn = function(){ + if (!this.running) this.send({data: data}); + }; + this.lastDelay = this.options.initialDelay; + this.timer = fn.delay(this.lastDelay, this); + this.completeCheck = function(response){ + clearTimeout(this.timer); + this.lastDelay = (response) ? this.options.delay : (this.lastDelay + this.options.delay).min(this.options.limit); + this.timer = fn.delay(this.lastDelay, this); + }; + return this.addEvent('complete', this.completeCheck); + }, + + stopTimer: function(){ + clearTimeout(this.timer); + return this.removeEvent('complete', this.completeCheck); + } + +}); + +/* +--- + +script: Request.Queue.js + +name: Request.Queue + +description: Controls several instances of Request and its variants to run only one request at a time. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Core/Element + - Core/Request + - Class.Binds + +provides: [Request.Queue] + +... +*/ + +Request.Queue = new Class({ + + Implements: [Options, Events], + + Binds: ['attach', 'request', 'complete', 'cancel', 'success', 'failure', 'exception'], + + options: {/* + onRequest: function(argsPassedToOnRequest){}, + onSuccess: function(argsPassedToOnSuccess){}, + onComplete: function(argsPassedToOnComplete){}, + onCancel: function(argsPassedToOnCancel){}, + onException: function(argsPassedToOnException){}, + onFailure: function(argsPassedToOnFailure){}, + onEnd: function(){}, + */ + stopOnFailure: true, + autoAdvance: true, + concurrent: 1, + requests: {} + }, + + initialize: function(options){ + var requests; + if (options){ + requests = options.requests; + delete options.requests; + } + this.setOptions(options); + this.requests = {}; + this.queue = []; + this.reqBinders = {}; + + if (requests) this.addRequests(requests); + }, + + addRequest: function(name, request){ + this.requests[name] = request; + this.attach(name, request); + return this; + }, + + addRequests: function(obj){ + Object.each(obj, function(req, name){ + this.addRequest(name, req); + }, this); + return this; + }, + + getName: function(req){ + return Object.keyOf(this.requests, req); + }, + + attach: function(name, req){ + if (req._groupSend) return this; + ['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){ + if (!this.reqBinders[name]) this.reqBinders[name] = {}; + this.reqBinders[name][evt] = function(){ + this['on' + evt.capitalize()].apply(this, [name, req].append(arguments)); + }.bind(this); + req.addEvent(evt, this.reqBinders[name][evt]); + }, this); + req._groupSend = req.send; + req.send = function(options){ + this.send(name, options); + return req; + }.bind(this); + return this; + }, + + removeRequest: function(req){ + var name = typeOf(req) == 'object' ? this.getName(req) : req; + if (!name && typeOf(name) != 'string') return this; + req = this.requests[name]; + if (!req) return this; + ['request', 'complete', 'cancel', 'success', 'failure', 'exception'].each(function(evt){ + req.removeEvent(evt, this.reqBinders[name][evt]); + }, this); + req.send = req._groupSend; + delete req._groupSend; + return this; + }, + + getRunning: function(){ + return Object.filter(this.requests, function(r){ + return r.running; + }); + }, + + isRunning: function(){ + return !!(Object.keys(this.getRunning()).length); + }, + + send: function(name, options){ + var q = function(){ + this.requests[name]._groupSend(options); + this.queue.erase(q); + }.bind(this); + + q.name = name; + if (Object.keys(this.getRunning()).length >= this.options.concurrent || (this.error && this.options.stopOnFailure)) this.queue.push(q); + else q(); + return this; + }, + + hasNext: function(name){ + return (!name) ? !!this.queue.length : !!this.queue.filter(function(q){ return q.name == name; }).length; + }, + + resume: function(){ + this.error = false; + (this.options.concurrent - Object.keys(this.getRunning()).length).times(this.runNext, this); + return this; + }, + + runNext: function(name){ + if (!this.queue.length) return this; + if (!name){ + this.queue[0](); + } else { + var found; + this.queue.each(function(q){ + if (!found && q.name == name){ + found = true; + q(); + } + }); + } + return this; + }, + + runAll: function(){ + this.queue.each(function(q){ + q(); + }); + return this; + }, + + clear: function(name){ + if (!name){ + this.queue.empty(); + } else { + this.queue = this.queue.map(function(q){ + if (q.name != name) return q; + else return false; + }).filter(function(q){ + return q; + }); + } + return this; + }, + + cancel: function(name){ + this.requests[name].cancel(); + return this; + }, + + onRequest: function(){ + this.fireEvent('request', arguments); + }, + + onComplete: function(){ + this.fireEvent('complete', arguments); + if (!this.queue.length) this.fireEvent('end'); + }, + + onCancel: function(){ + if (this.options.autoAdvance && !this.error) this.runNext(); + this.fireEvent('cancel', arguments); + }, + + onSuccess: function(){ + if (this.options.autoAdvance && !this.error) this.runNext(); + this.fireEvent('success', arguments); + }, + + onFailure: function(){ + this.error = true; + if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext(); + this.fireEvent('failure', arguments); + }, + + onException: function(){ + this.error = true; + if (!this.options.stopOnFailure && this.options.autoAdvance) this.runNext(); + this.fireEvent('exception', arguments); + } + +}); + +/* +--- + +script: Array.Extras.js + +name: Array.Extras + +description: Extends the Array native object to include useful methods to work with arrays. + +license: MIT-style license + +authors: + - Christoph Pojer + - Sebastian MarkbÃ¥ge + +requires: + - Core/Array + - MooTools.More + +provides: [Array.Extras] + +... +*/ + +(function(nil){ + +Array.implement({ + + min: function(){ + return Math.min.apply(null, this); + }, + + max: function(){ + return Math.max.apply(null, this); + }, + + average: function(){ + return this.length ? this.sum() / this.length : 0; + }, + + sum: function(){ + var result = 0, l = this.length; + if (l){ + while (l--){ + if (this[l] != null) result += parseFloat(this[l]); + } + } + return result; + }, + + unique: function(){ + return [].combine(this); + }, + + shuffle: function(){ + for (var i = this.length; i && --i;){ + var temp = this[i], r = Math.floor(Math.random() * ( i + 1 )); + this[i] = this[r]; + this[r] = temp; + } + return this; + }, + + reduce: function(fn, value){ + for (var i = 0, l = this.length; i < l; i++){ + if (i in this) value = value === nil ? this[i] : fn.call(null, value, this[i], i, this); + } + return value; + }, + + reduceRight: function(fn, value){ + var i = this.length; + while (i--){ + if (i in this) value = value === nil ? this[i] : fn.call(null, value, this[i], i, this); + } + return value; + }, + + pluck: function(prop){ + return this.map(function(item){ + return item[prop]; + }); + } + +}); + +})(); + +/* +--- + +script: Date.Extras.js + +name: Date.Extras + +description: Extends the Date native object to include extra methods (on top of those in Date.js). + +license: MIT-style license + +authors: + - Aaron Newton + - Scott Kyle + +requires: + - Date + +provides: [Date.Extras] + +... +*/ + +Date.implement({ + + timeDiffInWords: function(to){ + return Date.distanceOfTimeInWords(this, to || new Date); + }, + + timeDiff: function(to, separator){ + if (to == null) to = new Date; + var delta = ((to - this) / 1000).floor().abs(); + + var vals = [], + durations = [60, 60, 24, 365, 0], + names = ['s', 'm', 'h', 'd', 'y'], + value, duration; + + for (var item = 0; item < durations.length; item++){ + if (item && !delta) break; + value = delta; + if ((duration = durations[item])){ + value = (delta % duration); + delta = (delta / duration).floor(); + } + vals.unshift(value + (names[item] || '')); + } + + return vals.join(separator || ':'); + } + +}).extend({ + + distanceOfTimeInWords: function(from, to){ + return Date.getTimePhrase(((to - from) / 1000).toInt()); + }, + + getTimePhrase: function(delta){ + var suffix = (delta < 0) ? 'Until' : 'Ago'; + if (delta < 0) delta *= -1; + + var units = { + minute: 60, + hour: 60, + day: 24, + week: 7, + month: 52 / 12, + year: 12, + eon: Infinity + }; + + var msg = 'lessThanMinute'; + + for (var unit in units){ + var interval = units[unit]; + if (delta < 1.5 * interval){ + if (delta > 0.75 * interval) msg = unit; + break; + } + delta /= interval; + msg = unit + 's'; + } + + delta = delta.round(); + return Date.getMsg(msg + suffix, delta).substitute({delta: delta}); + } + +}).defineParsers( + + { + // "today", "tomorrow", "yesterday" + re: /^(?:tod|tom|yes)/i, + handler: function(bits){ + var d = new Date().clearTime(); + switch (bits[0]){ + case 'tom': return d.increment(); + case 'yes': return d.decrement(); + default: return d; + } + } + }, + + { + // "next Wednesday", "last Thursday" + re: /^(next|last) ([a-z]+)$/i, + handler: function(bits){ + var d = new Date().clearTime(); + var day = d.getDay(); + var newDay = Date.parseDay(bits[2], true); + var addDays = newDay - day; + if (newDay <= day) addDays += 7; + if (bits[1] == 'last') addDays -= 7; + return d.set('date', d.getDate() + addDays); + } + } + +).alias('timeAgoInWords', 'timeDiffInWords'); + +/* +--- + +name: Hash + +description: Contains Hash Prototypes. Provides a means for overcoming the JavaScript practical impossibility of extending native Objects. + +license: MIT-style license. + +requires: + - Core/Object + - MooTools.More + +provides: [Hash] + +... +*/ + +(function(){ + +if (this.Hash) return; + +var Hash = this.Hash = new Type('Hash', function(object){ + if (typeOf(object) == 'hash') object = Object.clone(object.getClean()); + for (var key in object) this[key] = object[key]; + return this; +}); + +this.$H = function(object){ + return new Hash(object); +}; + +Hash.implement({ + + forEach: function(fn, bind){ + Object.forEach(this, fn, bind); + }, + + getClean: function(){ + var clean = {}; + for (var key in this){ + if (this.hasOwnProperty(key)) clean[key] = this[key]; + } + return clean; + }, + + getLength: function(){ + var length = 0; + for (var key in this){ + if (this.hasOwnProperty(key)) length++; + } + return length; + } + +}); + +Hash.alias('each', 'forEach'); + +Hash.implement({ + + has: Object.prototype.hasOwnProperty, + + keyOf: function(value){ + return Object.keyOf(this, value); + }, + + hasValue: function(value){ + return Object.contains(this, value); + }, + + extend: function(properties){ + Hash.each(properties || {}, function(value, key){ + Hash.set(this, key, value); + }, this); + return this; + }, + + combine: function(properties){ + Hash.each(properties || {}, function(value, key){ + Hash.include(this, key, value); + }, this); + return this; + }, + + erase: function(key){ + if (this.hasOwnProperty(key)) delete this[key]; + return this; + }, + + get: function(key){ + return (this.hasOwnProperty(key)) ? this[key] : null; + }, + + set: function(key, value){ + if (!this[key] || this.hasOwnProperty(key)) this[key] = value; + return this; + }, + + empty: function(){ + Hash.each(this, function(value, key){ + delete this[key]; + }, this); + return this; + }, + + include: function(key, value){ + if (this[key] == undefined) this[key] = value; + return this; + }, + + map: function(fn, bind){ + return new Hash(Object.map(this, fn, bind)); + }, + + filter: function(fn, bind){ + return new Hash(Object.filter(this, fn, bind)); + }, + + every: function(fn, bind){ + return Object.every(this, fn, bind); + }, + + some: function(fn, bind){ + return Object.some(this, fn, bind); + }, + + getKeys: function(){ + return Object.keys(this); + }, + + getValues: function(){ + return Object.values(this); + }, + + toQueryString: function(base){ + return Object.toQueryString(this, base); + } + +}); + +Hash.alias({indexOf: 'keyOf', contains: 'hasValue'}); + + +})(); + + +/* +--- + +script: Hash.Extras.js + +name: Hash.Extras + +description: Extends the Hash Type to include getFromPath which allows a path notation to child elements. + +license: MIT-style license + +authors: + - Aaron Newton + +requires: + - Hash + - Object.Extras + +provides: [Hash.Extras] + +... +*/ + +Hash.implement({ + + getFromPath: function(notation){ + return Object.getFromPath(this, notation); + }, + + cleanValues: function(method){ + return new Hash(Object.cleanValues(this, method)); + }, + + run: function(){ + Object.run(arguments); + } + +}); + +/* +--- +name: Number.Format +description: Extends the Number Type object to include a number formatting method. +license: MIT-style license +authors: [Arian Stolwijk] +requires: [Core/Number, Locale.en-US.Number] +# Number.Extras is for compatibility +provides: [Number.Format, Number.Extras] +... +*/ + + +Number.implement({ + + format: function(options){ + // Thanks dojo and YUI for some inspiration + var value = this; + options = options ? Object.clone(options) : {}; + var getOption = function(key){ + if (options[key] != null) return options[key]; + return Locale.get('Number.' + key); + }; + + var negative = value < 0, + decimal = getOption('decimal'), + precision = getOption('precision'), + group = getOption('group'), + decimals = getOption('decimals'); + + if (negative){ + var negativeLocale = getOption('negative') || {}; + if (negativeLocale.prefix == null && negativeLocale.suffix == null) negativeLocale.prefix = '-'; + ['prefix', 'suffix'].each(function(key){ + if (negativeLocale[key]) options[key] = getOption(key) + negativeLocale[key]; + }); + + value = -value; + } + + var prefix = getOption('prefix'), + suffix = getOption('suffix'); + + if (decimals !== '' && decimals >= 0 && decimals <= 20) value = value.toFixed(decimals); + if (precision >= 1 && precision <= 21) value = (+value).toPrecision(precision); + + value += ''; + var index; + if (getOption('scientific') === false && value.indexOf('e') > -1){ + var match = value.split('e'), + zeros = +match[1]; + value = match[0].replace('.', ''); + + if (zeros < 0){ + zeros = -zeros - 1; + index = match[0].indexOf('.'); + if (index > -1) zeros -= index - 1; + while (zeros--) value = '0' + value; + value = '0.' + value; + } else { + index = match[0].lastIndexOf('.'); + if (index > -1) zeros -= match[0].length - index - 1; + while (zeros--) value += '0'; + } + } + + if (decimal != '.') value = value.replace('.', decimal); + + if (group){ + index = value.lastIndexOf(decimal); + index = (index > -1) ? index : value.length; + var newOutput = value.substring(index), + i = index; + + while (i--){ + if ((index - i - 1) % 3 == 0 && i != (index - 1)) newOutput = group + newOutput; + newOutput = value.charAt(i) + newOutput; + } + + value = newOutput; + } + + if (prefix) value = prefix + value; + if (suffix) value += suffix; + + return value; + }, + + formatCurrency: function(decimals){ + var locale = Locale.get('Number.currency') || {}; + if (locale.scientific == null) locale.scientific = false; + locale.decimals = decimals != null ? decimals + : (locale.decimals == null ? 2 : locale.decimals); + + return this.format(locale); + }, + + formatPercentage: function(decimals){ + var locale = Locale.get('Number.percentage') || {}; + if (locale.suffix == null) locale.suffix = '%'; + locale.decimals = decimals != null ? decimals + : (locale.decimals == null ? 2 : locale.decimals); + + return this.format(locale); + } + +}); + +/* +--- + +script: URI.js + +name: URI + +description: Provides methods useful in managing the window location and uris. + +license: MIT-style license + +authors: + - Sebastian MarkbÃ¥ge + - Aaron Newton + +requires: + - Core/Object + - Core/Class + - Core/Class.Extras + - Core/Element + - String.QueryString + +provides: [URI] + +... +*/ + +(function(){ + +var toString = function(){ + return this.get('value'); +}; + +var URI = this.URI = new Class({ + + Implements: Options, + + options: { + /*base: false*/ + }, + + regex: /^(?:(\w+):)?(?:\/\/(?:(?:([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)?(\.\.?$|(?:[^?#\/]*\/)*)([^?#]*)(?:\?([^#]*))?(?:#(.*))?/, + parts: ['scheme', 'user', 'password', 'host', 'port', 'directory', 'file', 'query', 'fragment'], + schemes: {http: 80, https: 443, ftp: 21, rtsp: 554, mms: 1755, file: 0}, + + initialize: function(uri, options){ + this.setOptions(options); + var base = this.options.base || URI.base; + if (!uri) uri = base; + + if (uri && uri.parsed) this.parsed = Object.clone(uri.parsed); + else this.set('value', uri.href || uri.toString(), base ? new URI(base) : false); + }, + + parse: function(value, base){ + var bits = value.match(this.regex); + if (!bits) return false; + bits.shift(); + return this.merge(bits.associate(this.parts), base); + }, + + merge: function(bits, base){ + if ((!bits || !bits.scheme) && (!base || !base.scheme)) return false; + if (base){ + this.parts.every(function(part){ + if (bits[part]) return false; + bits[part] = base[part] || ''; + return true; + }); + } + bits.port = bits.port || this.schemes[bits.scheme.toLowerCase()]; + bits.directory = bits.directory ? this.parseDirectory(bits.directory, base ? base.directory : '') : '/'; + return bits; + }, + + parseDirectory: function(directory, baseDirectory){ + directory = (directory.substr(0, 1) == '/' ? '' : (baseDirectory || '/')) + directory; + if (!directory.test(URI.regs.directoryDot)) return directory; + var result = []; + directory.replace(URI.regs.endSlash, '').split('/').each(function(dir){ + if (dir == '..' && result.length > 0) result.pop(); + else if (dir != '.') result.push(dir); + }); + return result.join('/') + '/'; + }, + + combine: function(bits){ + return bits.value || bits.scheme + '://' + + (bits.user ? bits.user + (bits.password ? ':' + bits.password : '') + '@' : '') + + (bits.host || '') + (bits.port && bits.port != this.schemes[bits.scheme] ? ':' + bits.port : '') + + (bits.directory || '/') + (bits.file || '') + + (bits.query ? '?' + bits.query : '') + + (bits.fragment ? '#' + bits.fragment : ''); + }, + + set: function(part, value, base){ + if (part == 'value'){ + var scheme = value.match(URI.regs.scheme); + if (scheme) scheme = scheme[1]; + if (scheme && this.schemes[scheme.toLowerCase()] == null) this.parsed = { scheme: scheme, value: value }; + else this.parsed = this.parse(value, (base || this).parsed) || (scheme ? { scheme: scheme, value: value } : { value: value }); + } else if (part == 'data'){ + this.setData(value); + } else { + this.parsed[part] = value; + } + return this; + }, + + get: function(part, base){ + switch (part){ + case 'value': return this.combine(this.parsed, base ? base.parsed : false); + case 'data' : return this.getData(); + } + return this.parsed[part] || ''; + }, + + go: function(){ + document.location.href = this.toString(); + }, + + toURI: function(){ + return this; + }, + + getData: function(key, part){ + var qs = this.get(part || 'query'); + if (!(qs || qs === 0)) return key ? null : {}; + var obj = qs.parseQueryString(); + return key ? obj[key] : obj; + }, + + setData: function(values, merge, part){ + if (typeof values == 'string'){ + var data = this.getData(); + data[arguments[0]] = arguments[1]; + values = data; + } else if (merge){ + values = Object.merge(this.getData(null, part), values); + } + return this.set(part || 'query', Object.toQueryString(values)); + }, + + clearData: function(part){ + return this.set(part || 'query', ''); + }, + + toString: toString, + valueOf: toString + +}); + +URI.regs = { + endSlash: /\/$/, + scheme: /^(\w+):/, + directoryDot: /\.\/|\.$/ +}; + +URI.base = new URI(Array.from(document.getElements('base[href]', true)).getLast(), {base: document.location}); + +String.implement({ + + toURI: function(options){ + return new URI(this, options); + } + +}); + +})(); + +/* +--- + +script: URI.Relative.js + +name: URI.Relative + +description: Extends the URI class to add methods for computing relative and absolute urls. + +license: MIT-style license + +authors: + - Sebastian MarkbÃ¥ge + + +requires: + - Class.refactor + - URI + +provides: [URI.Relative] + +... +*/ + +URI = Class.refactor(URI, { + + combine: function(bits, base){ + if (!base || bits.scheme != base.scheme || bits.host != base.host || bits.port != base.port) + return this.previous.apply(this, arguments); + var end = bits.file + (bits.query ? '?' + bits.query : '') + (bits.fragment ? '#' + bits.fragment : ''); + + if (!base.directory) return (bits.directory || (bits.file ? '' : './')) + end; + + var baseDir = base.directory.split('/'), + relDir = bits.directory.split('/'), + path = '', + offset; + + var i = 0; + for (offset = 0; offset < baseDir.length && offset < relDir.length && baseDir[offset] == relDir[offset]; offset++); + for (i = 0; i < baseDir.length - offset - 1; i++) path += '../'; + for (i = offset; i < relDir.length - 1; i++) path += relDir[i] + '/'; + + return (path || (bits.file ? '' : './')) + end; + }, + + toAbsolute: function(base){ + base = new URI(base); + if (base) base.set('directory', '').set('file', ''); + return this.toRelative(base); + }, + + toRelative: function(base){ + return this.get('value', new URI(base)); + } + +}); + +/* +--- + +script: Assets.js + +name: Assets + +description: Provides methods to dynamically load JavaScript, CSS, and Image files into the document. + +license: MIT-style license + +authors: + - Valerio Proietti + +requires: + - Core/Element.Event + - MooTools.More + +provides: [Assets] + +... +*/ + +var Asset = { + + javascript: function(source, properties){ + if (!properties) properties = {}; + + var script = new Element('script', {src: source, type: 'text/javascript'}), + doc = properties.document || document, + load = properties.onload || properties.onLoad; + + delete properties.onload; + delete properties.onLoad; + delete properties.document; + + if (load){ + if (!script.addEventListener){ + script.addEvent('readystatechange', function(){ + if (['loaded', 'complete'].contains(this.readyState)) load.call(this); + }); + } else { + script.addEvent('load', load); + } + } + + return script.set(properties).inject(doc.head); + }, + + css: function(source, properties){ + if (!properties) properties = {}; + + var load = properties.onload || properties.onLoad, + doc = properties.document || document, + timeout = properties.timeout || 3000; + + ['onload', 'onLoad', 'document'].each(function(prop){ + delete properties[prop]; + }); + + var link = new Element('link', { + type: 'text/css', + rel: 'stylesheet', + media: 'screen', + href: source + }).setProperties(properties).inject(doc.head); + + if (load){ + // based on article at http://www.yearofmoo.com/2011/03/cross-browser-stylesheet-preloading.html + var loaded = false, retries = 0; + var check = function(){ + var stylesheets = document.styleSheets; + for (var i = 0; i < stylesheets.length; i++){ + var file = stylesheets[i]; + var owner = file.ownerNode ? file.ownerNode : file.owningElement; + if (owner && owner == link){ + loaded = true; + return load.call(link); + } + } + retries++; + if (!loaded && retries < timeout / 50) return setTimeout(check, 50); + } + setTimeout(check, 0); + } + return link; + }, + + image: function(source, properties){ + if (!properties) properties = {}; + + var image = new Image(), + element = document.id(image) || new Element('img'); + + ['load', 'abort', 'error'].each(function(name){ + var type = 'on' + name, + cap = 'on' + name.capitalize(), + event = properties[type] || properties[cap] || function(){}; + + delete properties[cap]; + delete properties[type]; + + image[type] = function(){ + if (!image) return; + if (!element.parentNode){ + element.width = image.width; + element.height = image.height; + } + image = image.onload = image.onabort = image.onerror = null; + event.delay(1, element, element); + element.fireEvent(name, element, 1); + }; + }); + + image.src = element.src = source; + if (image && image.complete) image.onload.delay(1); + return element.set(properties); + }, + + images: function(sources, options){ + sources = Array.from(sources); + + var fn = function(){}, + counter = 0; + + options = Object.merge({ + onComplete: fn, + onProgress: fn, + onError: fn, + properties: {} + }, options); + + return new Elements(sources.map(function(source, index){ + return Asset.image(source, Object.append(options.properties, { + onload: function(){ + counter++; + options.onProgress.call(this, counter, index, source); + if (counter == sources.length) options.onComplete(); + }, + onerror: function(){ + counter++; + options.onError.call(this, counter, index, source); + if (counter == sources.length) options.onComplete(); + } + })); + })); + } + +}; + +/* +--- + +script: Color.js + +name: Color + +description: Class for creating and manipulating colors in JavaScript. Supports HSB -> RGB Conversions and vice versa. + +license: MIT-style license + +authors: + - Valerio Proietti + +requires: + - Core/Array + - Core/String + - Core/Number + - Core/Hash + - Core/Function + - MooTools.More + +provides: [Color] + +... +*/ + +(function(){ + +var Color = this.Color = new Type('Color', function(color, type){ + if (arguments.length >= 3){ + type = 'rgb'; color = Array.slice(arguments, 0, 3); + } else if (typeof color == 'string'){ + if (color.match(/rgb/)) color = color.rgbToHex().hexToRgb(true); + else if (color.match(/hsb/)) color = color.hsbToRgb(); + else color = color.hexToRgb(true); + } + type = type || 'rgb'; + switch (type){ + case 'hsb': + var old = color; + color = color.hsbToRgb(); + color.hsb = old; + break; + case 'hex': color = color.hexToRgb(true); break; + } + color.rgb = color.slice(0, 3); + color.hsb = color.hsb || color.rgbToHsb(); + color.hex = color.rgbToHex(); + return Object.append(color, this); +}); + +Color.implement({ + + mix: function(){ + var colors = Array.slice(arguments); + var alpha = (typeOf(colors.getLast()) == 'number') ? colors.pop() : 50; + var rgb = this.slice(); + colors.each(function(color){ + color = new Color(color); + for (var i = 0; i < 3; i++) rgb[i] = Math.round((rgb[i] / 100 * (100 - alpha)) + (color[i] / 100 * alpha)); + }); + return new Color(rgb, 'rgb'); + }, + + invert: function(){ + return new Color(this.map(function(value){ + return 255 - value; + })); + }, + + setHue: function(value){ + return new Color([value, this.hsb[1], this.hsb[2]], 'hsb'); + }, + + setSaturation: function(percent){ + return new Color([this.hsb[0], percent, this.hsb[2]], 'hsb'); + }, + + setBrightness: function(percent){ + return new Color([this.hsb[0], this.hsb[1], percent], 'hsb'); + } + +}); + +this.$RGB = function(r, g, b){ + return new Color([r, g, b], 'rgb'); +}; + +this.$HSB = function(h, s, b){ + return new Color([h, s, b], 'hsb'); +}; + +this.$HEX = function(hex){ + return new Color(hex, 'hex'); +}; + +Array.implement({ + + rgbToHsb: function(){ + var red = this[0], + green = this[1], + blue = this[2], + hue = 0; + var max = Math.max(red, green, blue), + min = Math.min(red, green, blue); + var delta = max - min; + var brightness = max / 255, + saturation = (max != 0) ? delta / max : 0; + if (saturation != 0){ + var rr = (max - red) / delta; + var gr = (max - green) / delta; + var br = (max - blue) / delta; + if (red == max) hue = br - gr; + else if (green == max) hue = 2 + rr - br; + else hue = 4 + gr - rr; + hue /= 6; + if (hue < 0) hue++; + } + return [Math.round(hue * 360), Math.round(saturation * 100), Math.round(brightness * 100)]; + }, + + hsbToRgb: function(){ + var br = Math.round(this[2] / 100 * 255); + if (this[1] == 0){ + return [br, br, br]; + } else { + var hue = this[0] % 360; + var f = hue % 60; + var p = Math.round((this[2] * (100 - this[1])) / 10000 * 255); + var q = Math.round((this[2] * (6000 - this[1] * f)) / 600000 * 255); + var t = Math.round((this[2] * (6000 - this[1] * (60 - f))) / 600000 * 255); + switch (Math.floor(hue / 60)){ + case 0: return [br, t, p]; + case 1: return [q, br, p]; + case 2: return [p, br, t]; + case 3: return [p, q, br]; + case 4: return [t, p, br]; + case 5: return [br, p, q]; + } + } + return false; + } + +}); + +String.implement({ + + rgbToHsb: function(){ + var rgb = this.match(/\d{1,3}/g); + return (rgb) ? rgb.rgbToHsb() : null; + }, + + hsbToRgb: function(){ + var hsb = this.match(/\d{1,3}/g); + return (hsb) ? hsb.hsbToRgb() : null; + } + +}); + +})(); + + +/* +--- + +script: Group.js + +name: Group + +description: Class for monitoring collections of events + +license: MIT-style license + +authors: + - Valerio Proietti + +requires: + - Core/Events + - MooTools.More + +provides: [Group] + +... +*/ + +(function(){ + +this.Group = new Class({ + + initialize: function(){ + this.instances = Array.flatten(arguments); + }, + + addEvent: function(type, fn){ + var instances = this.instances, + len = instances.length, + togo = len, + args = new Array(len), + self = this; + + instances.each(function(instance, i){ + instance.addEvent(type, function(){ + if (!args[i]) togo--; + args[i] = arguments; + if (!togo){ + fn.call(self, instances, instance, args); + togo = len; + args = new Array(len); + } + }); + }); + } + +}); + +})(); + +/* +--- + +script: Hash.Cookie.js + +name: Hash.Cookie + +description: Class for creating, reading, and deleting Cookies in JSON format. + +license: MIT-style license + +authors: + - Valerio Proietti + - Aaron Newton + +requires: + - Core/Cookie + - Core/JSON + - MooTools.More + - Hash + +provides: [Hash.Cookie] + +... +*/ + +Hash.Cookie = new Class({ + + Extends: Cookie, + + options: { + autoSave: true + }, + + initialize: function(name, options){ + this.parent(name, options); + this.load(); + }, + + save: function(){ + var value = JSON.encode(this.hash); + if (!value || value.length > 4096) return false; //cookie would be truncated! + if (value == '{}') this.dispose(); + else this.write(value); + return true; + }, + + load: function(){ + this.hash = new Hash(JSON.decode(this.read(), true)); + return this; + } + +}); + +Hash.each(Hash.prototype, function(method, name){ + if (typeof method == 'function') Hash.Cookie.implement(name, function(){ + var value = method.apply(this.hash, arguments); + if (this.options.autoSave) this.save(); + return value; + }); +}); + +/* +--- + +name: Swiff + +description: Wrapper for embedding SWF movies. Supports External Interface Communication. + +license: MIT-style license. + +credits: + - Flash detection & Internet Explorer + Flash Player 9 fix inspired by SWFObject. + +requires: [Core/Options, Core/Object, Core/Element] + +provides: Swiff + +... +*/ + +(function(){ + +var Swiff = this.Swiff = new Class({ + + Implements: Options, + + options: { + id: null, + height: 1, + width: 1, + container: null, + properties: {}, + params: { + quality: 'high', + allowScriptAccess: 'always', + wMode: 'window', + swLiveConnect: true + }, + callBacks: {}, + vars: {} + }, + + toElement: function(){ + return this.object; + }, + + initialize: function(path, options){ + this.instance = 'Swiff_' + String.uniqueID(); + + this.setOptions(options); + options = this.options; + var id = this.id = options.id || this.instance; + var container = document.id(options.container); + + Swiff.CallBacks[this.instance] = {}; + + var params = options.params, vars = options.vars, callBacks = options.callBacks; + var properties = Object.append({height: options.height, width: options.width}, options.properties); + + var self = this; + + for (var callBack in callBacks){ + Swiff.CallBacks[this.instance][callBack] = (function(option){ + return function(){ + return option.apply(self.object, arguments); + }; + })(callBacks[callBack]); + vars[callBack] = 'Swiff.CallBacks.' + this.instance + '.' + callBack; + } + + params.flashVars = Object.toQueryString(vars); + if ('ActiveXObject' in window){ + properties.classid = 'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000'; + params.movie = path; + } else { + properties.type = 'application/x-shockwave-flash'; + } + properties.data = path; + + var build = '<object id="' + id + '"'; + for (var property in properties) build += ' ' + property + '="' + properties[property] + '"'; + build += '>'; + for (var param in params){ + if (params[param]) build += '<param name="' + param + '" value="' + params[param] + '" />'; + } + build += '</object>'; + this.object = ((container) ? container.empty() : new Element('div')).set('html', build).firstChild; + }, + + replaces: function(element){ + element = document.id(element, true); + element.parentNode.replaceChild(this.toElement(), element); + return this; + }, + + inject: function(element){ + document.id(element, true).appendChild(this.toElement()); + return this; + }, + + remote: function(){ + return Swiff.remote.apply(Swiff, [this.toElement()].append(arguments)); + } + +}); + +Swiff.CallBacks = {}; + +Swiff.remote = function(obj, fn){ + var rs = obj.CallFunction('<invoke name="' + fn + '" returntype="javascript">' + __flash__argumentsToXML(arguments, 2) + '</invoke>'); + return eval(rs); +}; + +})(); + +/* +--- +name: Table +description: LUA-Style table implementation. +license: MIT-style license +authors: + - Valerio Proietti +requires: [Core/Array] +provides: [Table] +... +*/ + +(function(){ + +var Table = this.Table = function(){ + + this.length = 0; + var keys = [], + values = []; + + this.set = function(key, value){ + var index = keys.indexOf(key); + if (index == -1){ + var length = keys.length; + keys[length] = key; + values[length] = value; + this.length++; + } else { + values[index] = value; + } + return this; + }; + + this.get = function(key){ + var index = keys.indexOf(key); + return (index == -1) ? null : values[index]; + }; + + this.erase = function(key){ + var index = keys.indexOf(key); + if (index != -1){ + this.length--; + keys.splice(index, 1); + return values.splice(index, 1)[0]; + } + return null; + }; + + this.each = this.forEach = function(fn, bind){ + for (var i = 0, l = this.length; i < l; i++) fn.call(bind, keys[i], values[i], this); + }; + +}; + +if (this.Type) new Type('Table', Table); + +})(); diff --git a/pyload/webui/themes/Next/lib/MooTools/Purr/purr.js b/pyload/webui/themes/Next/lib/MooTools/Purr/purr.js new file mode 100644 index 000000000..9cbc503d9 --- /dev/null +++ b/pyload/webui/themes/Next/lib/MooTools/Purr/purr.js @@ -0,0 +1,309 @@ +/* +--- +script: purr.js + +description: Class to create growl-style popup notifications. + +license: MIT-style + +authors: [atom smith] + +requires: +- core/1.3: [Core, Browser, Array, Function, Number, String, Hash, Event, Class.Extras, Element.Event, Element.Style, Element.Dimensions, Fx.CSS, FX.Tween, Fx.Morph] + +provides: [Purr, Element.alert] +... +*/ + + +var Purr = new Class({ + + 'options': { + 'mode': 'top', + 'position': 'left', + 'elementAlertClass': 'purr-element-alert', + 'elements': { + 'wrapper': 'div', + 'alert': 'div', + 'buttonWrapper': 'div', + 'button': 'button' + }, + 'elementOptions': { + 'wrapper': { + 'styles': { + 'position': 'fixed', + 'z-index': '9999' + }, + 'class': 'purr-wrapper' + }, + 'alert': { + 'class': 'purr-alert', + 'styles': { + 'opacity': '.85' + } + }, + 'buttonWrapper': { + 'class': 'purr-button-wrapper' + }, + 'button': { + 'class': 'purr-button' + } + }, + 'alert': { + 'buttons': [], + 'clickDismiss': true, + 'hoverWait': true, + 'hideAfter': 5000, + 'fx': { + 'duration': 500 + }, + 'highlight': false, + 'highlightRepeat': false, + 'highlight': { + 'start': '#FF0', + 'end': false + } + } + }, + + 'Implements': [Options, Events, Chain], + + 'initialize': function(options){ + this.setOptions(options); + this.createWrapper(); + return this; + }, + + 'bindAlert': function(){ + return this.alert.bind(this); + }, + + 'createWrapper': function(){ + this.wrapper = new Element(this.options.elements.wrapper, this.options.elementOptions.wrapper); + if(this.options.mode == 'top') + { + this.wrapper.setStyle('top', 0); + } + else + { + this.wrapper.setStyle('bottom', 0); + } + document.id(document.body).grab(this.wrapper); + this.positionWrapper(this.options.position); + }, + + 'positionWrapper': function(position){ + if(typeOf(position) == 'object') + { + + var wrapperCoords = this.getWrapperCoords(); + + this.wrapper.setStyles({ + 'bottom': '', + 'left': position.x, + 'top': position.y - wrapperCoords.height, + 'position': 'absolute' + }); + } + else if(position == 'left') + { + this.wrapper.setStyle('left', 0); + } + else if(position == 'right') + { + this.wrapper.setStyle('right', 0); + } + else + { + this.wrapper.setStyle('left', (window.innerWidth / 2) - (this.getWrapperCoords().width / 2)); + } + return this; + }, + + 'getWrapperCoords': function(){ + this.wrapper.setStyle('visibility', 'hidden'); + var measurer = this.alert('need something in here to measure'); + var coords = this.wrapper.getCoordinates(); + measurer.destroy(); + this.wrapper.setStyle('visibility',''); + return coords; + }, + + 'alert': function(msg, options){ + + options = Object.merge({}, this.options.alert, options || {}); + + var alert = new Element(this.options.elements.alert, this.options.elementOptions.alert); + + if(typeOf(msg) == 'string') + { + alert.set('html', msg); + } + else if(typeOf(msg) == 'element') + { + alert.grab(msg); + } + else if(typeOf(msg) == 'array') + { + var alerts = []; + msg.each(function(m){ + alerts.push(this.alert(m, options)); + }, this); + return alerts; + } + + alert.store('options', options); + + if(options.buttons.length > 0) + { + options.clickDismiss = false; + options.hideAfter = false; + options.hoverWait = false; + var buttonWrapper = new Element(this.options.elements.buttonWrapper, this.options.elementOptions.buttonWrapper); + alert.grab(buttonWrapper); + options.buttons.each(function(button){ + if(button.text != undefined) + { + var callbackButton = new Element(this.options.elements.button, this.options.elementOptions.button); + callbackButton.set('html', button.text); + if(button.callback != undefined) + { + callbackButton.addEvent('click', button.callback.pass(alert)); + } + if(button.dismiss != undefined && button.dismiss) + { + callbackButton.addEvent('click', this.dismiss.pass(alert, this)); + } + buttonWrapper.grab(callbackButton); + } + }, this); + } + if(options.className != undefined) + { + alert.addClass(options.className); + } + + this.wrapper.grab(alert, (this.options.mode == 'top') ? 'bottom' : 'top'); + + var fx = Object.merge(this.options.alert.fx, options.fx); + var alertFx = new Fx.Morph(alert, fx); + alert.store('fx', alertFx); + this.fadeIn(alert); + + if(options.highlight) + { + alertFx.addEvent('complete', function(){ + alert.highlight(options.highlight.start, options.highlight.end); + if(options.highlightRepeat) + { + alert.highlight.periodical(options.highlightRepeat, alert, [options.highlight.start, options.highlight.end]); + } + }); + } + if(options.hideAfter) + { + this.dismiss(alert); + } + + if(options.clickDismiss) + { + alert.addEvent('click', function(){ + this.holdUp = false; + this.dismiss(alert, true); + }.bind(this)); + } + + if(options.hoverWait) + { + alert.addEvents({ + 'mouseenter': function(){ + this.holdUp = true; + }.bind(this), + 'mouseleave': function(){ + this.holdUp = false; + }.bind(this) + }); + } + + return alert; + }, + + 'fadeIn': function(alert){ + var alertFx = alert.retrieve('fx'); + alertFx.set({ + 'opacity': 0 + }); + alertFx.start({ + 'opacity': [this.options.elementOptions.alert.styles.opacity, .9].pick(), + }); + }, + + 'dismiss': function(alert, now){ + now = now || false; + var options = alert.retrieve('options'); + if(now) + { + this.fadeOut(alert); + } + else + { + this.fadeOut.delay(options.hideAfter, this, alert); + } + }, + + 'fadeOut': function(alert){ + if(this.holdUp) + { + this.dismiss.delay(100, this, [alert, true]) + return null; + } + var alertFx = alert.retrieve('fx'); + if(!alertFx) + { + return null; + } + var to = { + 'opacity': 0 + } + if(this.options.mode == 'top') + { + to['margin-top'] = '-'+alert.offsetHeight+'px'; + } + else + { + to['margin-bottom'] = '-'+alert.offsetHeight+'px'; + } + alertFx.start(to); + alertFx.addEvent('complete', function(){ + alert.destroy(); + }); + } +}); + +Element.implement({ + + 'alert': function(msg, options){ + var alert = this.retrieve('alert'); + if(!alert) + { + options = options || { + 'mode':'top' + }; + alert = new Purr(options) + this.store('alert', alert); + } + + var coords = this.getCoordinates(); + + alert.alert(msg, options); + + alert.wrapper.setStyles({ + 'bottom': '', + 'left': (coords.left - (alert.wrapper.getWidth() / 2)) + (this.getWidth() / 2), + 'top': coords.top - (alert.wrapper.getHeight()), + 'position': 'absolute' + }); + + } + +});
\ No newline at end of file diff --git a/pyload/webui/themes/Next/lib/MooTools/TinyTab/tinytab.js b/pyload/webui/themes/Next/lib/MooTools/TinyTab/tinytab.js new file mode 100644 index 000000000..de50279fc --- /dev/null +++ b/pyload/webui/themes/Next/lib/MooTools/TinyTab/tinytab.js @@ -0,0 +1,43 @@ +/* +--- +description: TinyTab - Tiny and simple tab handler for Mootools. + +license: MIT-style + +authors: +- Danillo César de O. Melo + +requires: +- core/1.2.4: '*' + +provides: TinyTab + +... +*/ +(function($) { + this.TinyTab = new Class({ + Implements: Events, + initialize: function(tabs, contents, opt) { + this.tabs = tabs; + this.contents = contents; + if(!opt) opt = {}; + this.css = opt.selectedClass || 'selected'; + this.select(this.tabs[0]); + tabs.each(function(el){ + el.addEvent('click',function(e){ + this.select(el); + e.stop(); + }.bind(this)); + }.bind(this)); + }, + + select: function(el) { + this.tabs.removeClass(this.css); + el.addClass(this.css); + this.contents.setStyle('display','none'); + var content = this.contents[this.tabs.indexOf(el)]; + content.setStyle('display','block'); + this.fireEvent('change',[content,el]); + } + }); +})(document.id);
\ No newline at end of file diff --git a/pyload/webui/themes/Next/tml/admin.html b/pyload/webui/themes/Next/tml/admin.html new file mode 100644 index 000000000..5c71cbac5 --- /dev/null +++ b/pyload/webui/themes/Next/tml/admin.html @@ -0,0 +1,100 @@ +{% extends '/tml/base.html' %} + +{% block head %} + <script type="text/javascript" src="/js/admin.js"></script> +{% endblock %} + + +{% block title %}{{ _("Administrate") }} - {{ super() }} {% endblock %} +{% block subtitle %}{{ _("Administrate") }}{% endblock %} + +{% block content %} + <div class="btn-group"> + <a href="#" id="quit-pyload" class="btn btn-default"><span class="glyphicon glyphicon-off"></span> {{_("Quit pyLoad")}}</a> + <a href="#" id="restart-pyload" class="btn btn-default"><span class="glyphicon glyphicon-repeat"></span> {{_("Restart pyLoad")}}</a> +</div> + <br> + <br> + + {{ _("To add user or change passwords use:") }} <b>python pyLoadCore.py -u</b><br> + {{ _("Important: Admin user have always all permissions!") }} +<br> +<br> + <form action="" method="POST" > + <table class="settable table" style="width:50%;"> + <thead> + <th> + {{ _("Name") }} + </th> + <th> + {{ _("Change Password") }} + </th> + <th> + {{ _("Admin") }} + </th> + <th> + {{ _("Permissions") }} + </th> + </thead> + + {% for name, data in users.iteritems() %} + <tr> + <td>{{ name }}</td> + <td><a class="change_password btn btn-default btn-xs" href="#" id="change_pw|{{name}}"><span class="glyphicon glyphicon-pencil"></span> {{ _("change") }}</a></td> + <td><input name="{{ name }}|admin" type="checkbox" {% if data.perms.admin %} checked="True" {% endif %}></td> + <td> + <select multiple="multiple" size="{{ permlist|length }}" name="{{ name }}|perms"> + {% for perm in permlist %} + {% if data.perms|getitem(perm) %} + <option selected="selected">{{ perm }}</option> + {% else %} + <option>{{ perm }}</option> + {% endif %} + {% endfor %} + </select> + </td> + </tr> + {% endfor %} + + + </table> + + <button class="btn btn-primary" type="submit">{{ _("Submit") }}</button> + </form> +{% endblock %} +{% block hidden %} + <div id="password_box" style="z-index: 2"> + <form id="password_form" class="from-group" action="/json/change_password" method="POST" enctype="multipart/form-data"> + <h3>{{ _("Change Password") }}</h3> + <p>{{ _("Enter your current and desired Password.") }}</p> + +<div class="form-group"> + <label for="user_login">{{ _("User") }}</label> + <input class="form-control" id="user_login" name="user_login" type="text"/> + <p class="help-block">{{ _("Your username.") }}</p> + </div> + <div class="form-group"> + <label for="login_current_password">{{ _("Current password") }}</label> + <input class="form-control" id="login_current_password" name="login_current_password" type="password"/> + <p class="help-block">{{ _("The password for this account.") }}</p> + </div> + <div class="form-group"> + <label for="login_new_password">{{ _("New password") }}</label> + <input class="form-control" id="login_new_password" name="login_new_password" type="password"/> + <p class="help-block">{{ _("The new password.") }}</p> + </div> + <div class="form-group"> + <label for="login_new_password2">{{ _("New password (repeat)") }}</label> + <input class="form-control" id="login_new_password2" name="login_new_password2" type="password" /> + <p class="help-block">{{ _("Please repeat the new password.") }}</p> + </div> + + + + <button class="btn btn-primary" id="login_password_button" type="submit" style="float: right">{{ _("Submit") }}</button> + <button class="btn btn-default" id="login_password_reset" style="margin-right: 5px; float: right" type="reset">{{ _("Reset") }}</button> + <div class="spacer"></div> + </form> + + </div> +{% endblock %} diff --git a/pyload/webui/themes/Next/tml/base.html b/pyload/webui/themes/Next/tml/base.html new file mode 100644 index 000000000..81c1f3008 --- /dev/null +++ b/pyload/webui/themes/Next/tml/base.html @@ -0,0 +1,199 @@ +<?xml version="1.0" ?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + +<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> + +<link rel="stylesheet" type="text/css" href="/css/window.css"/> +<link rel="stylesheet" type="text/css" href="/css/MooDialog.css"/> +<link rel="stylesheet" href="/lib/Bootstrap/css/bootstrap.css"> + +<script type="text/javascript" src="/lib/MooTools/MooTools-Core.js"></script> +<script type="text/javascript" src="/lib/MooTools/MooTools-More.js"></script> +<script type="text/javascript" src="/lib/MooTools/MooDialog/MooDialog.js"></script> +<script type="text/javascript" src="/lib/MooTools/Purr/purr.js"></script> + + +<script type="text/javascript" src="/js/base.js"></script> + + + +<title>{% block title %}pyLoad {{_("Webinterface")}}{% endblock %}</title> + +{% block head %} +{% endblock %} +</head> +<body> +<a class="anchor" name="top" id="top"></a> + +<div id="head-panel"> + + + <div id="head-search-and-login"> + {% block headpanel %} + + {% if user.is_authenticated %} + + +{% if update %} +<span> +<span style="font-weight: bold; margin: 0 2px 0 2px;">{{_("pyLoad Update available!")}}</span> +</span> +{% endif %} + + +{% if plugins %} +<span> +<span style="font-weight: bold; margin: 0 2px 0 2px;">{{_("Plugins updated, please restart!")}}</span> +</span> +{% endif %} + + + + + + </ul> +{% else %} + <span style="padding-right: 2px;">{{_("Please Login!")}}</span> +{% endif %} + + {% endblock %} + </div> + + <nav class="navbar navbar-default"> + <div class="container-fluid"> + <!-- Brand and toggle get grouped for better mobile display --> + <div class="navbar-header"> + <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1"> + <span class="sr-only">Toggle navigation</span> + <span class="icon-bar"></span> + <span class="icon-bar"></span> + <span class="icon-bar"></span> + </button> + <a class="navbar-brand" href="#"><img id="head-logo" src="/img/pyload-logo.png" alt="pyLoad" style="height:30px;"/></a> + </div> + + <a href="/"></a> + + <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> + <ul class="nav navbar-nav"> + + {% macro selected(name, right=False) -%} + {% if name in url -%}class="{% if right -%}right {% endif %}selected"{%- endif %} + {% if not name in url and right -%}class="right"{%- endif %} + {%- endmacro %} + + + {% block menu %} + <li> + <a href="/" title=""><span class="glyphicon glyphicon-home"></span> {{_("Home")}}</a> + </li> + <li {{ selected('queue') }}> + <a href="/queue/" title=""><span class="glyphicon glyphicon-tasks"></span> {{_("Queue")}}</a> + </li> + <li {{ selected('collector') }}> + <a href="/collector/" title=""><span class="glyphicon glyphicon-magnet"></span> {{_("Collector")}}</a> + </li> + <li {{ selected('downloads') }}> + <a href="/downloads/" title=""> <span class="glyphicon glyphicon-download"></span> {{_("Downloads")}}</a> + </li> +{# <li {{ selected('filemanager') }}>#} +{# <a href="/filemanager/" title=""><span class="glyphicon glyphicon-magnet"></span> {{_("FileManager")}}</a>#} +{# </li>#} + <li {{ selected('logs', True) }}> + <a href="/logs/" class="action index" accesskey="x" rel="nofollow"><span class="glyphicon glyphicon-list"></span> {{_("Logs")}}</a> + </li> + <li {{ selected('settings', True) }}> + <a href="/settings/" class="action index" accesskey="x" rel="nofollow"><span class="glyphicon glyphicon-wrench"></span> {{_("Config")}}</a> + </li> + {% endblock %} + + + </ul> + <ul class="nav navbar-nav navbar-right"> + <li><a href="/info" class="action info" rel="nofollow"><span class="glyphicon glyphicon-user"></span> {{user.name}}</a></li> + {% if user.is_admin %} + <li><a href="/admin" class="action profile" rel="nofollow"><span class="glyphicon glyphicon-cog"></span></a></li> + {% endif %} + <li><a href="/info" class="action info" rel="nofollow"><span class="glyphicon glyphicon-info-sign"></span></a></li> + + </ul> + </div><!-- /.navbar-collapse --> + </div><!-- /.container-fluid --> + + </div> +</nav> + <div style="clear:both;"></div> +</div> + +{% if perms.STATUS %} +<div class="btn-group btn-group-sm" role="group" aria-label="..." style="margin-left:10px;"> + <button id="action_play" class="btn btn-default" href="#"><span class="glyphicon glyphicon-play"></span> </button> + <button id="action_stop" type="button" class="btn btn-default"><span class="glyphicon glyphicon-stop"></span> </button> + <button id="action_cancel" type="button" class="btn btn-default"><span class="glyphicon glyphicon-remove"></span> </button> + <button id="action_add" type="button" class="btn btn-default"><span class="glyphicon glyphicon-plus"></span> </button> +</div> + + +{% endif %} +<span id="cap_info" style="display: {% if captcha %}inline{%else%}none{% endif %}"> +<button id="action_add" type="button" class="btn btn-default btn-sm"><span class="glyphicon glyphicon-barcode"></span><span> {{_("Captcha waiting")}}</span></button> +</span> + + +{% if perms.LIST %} + +<div class="btn-group btn-group-sm" role="group" aria-label="..." style="margin-right:10px; float:right;"> + <button id="action_play" class="btn btn-default"><span >{{_("Download:")}}</span> <span class="label label-{% if status.download %}success{% else %}danger{% endif %}">{% if status.download %}{{_("on")}}{% else %}{{_("off")}}{% endif %}</span></button> + <button id="action_stop" type="button" class="btn btn-default"><span>{{_("Reconnect:")}}</span> <span class="label label-{% if status.reconnect %}success{% else %}danger{% endif %}">{% if status.reconnect %}{{_("on")}}{% else %}{{_("off")}}{% endif %}</span></button> + <button id="action_cancel" type="button" class="btn btn-default"><span class="action backlink">{{_("Speed:")}} <b id="speed">{{ status.speed }}</b></span></button> + <button id="action_add" type="button" class="btn btn-default"><span class="action cog">{{_("Active:")}} <b id="aktiv" title="{{_("Active")}}">{{ status.active }}</b> / <b id="aktiv_from" title="{{_("Queued")}}">{{ status.queue }}</b> / <b id="aktiv_total" title="{{_("Total")}}">{{ status.total }}</b></span></button> +</div> + +{% endif %} + +{% block pageactions %} +{% endblock %} +<br/> + +<div id="body-wrapper" class="dokuwiki"> + +<div id="content" style="width: 98%; margin-left:10px; margin-rigth:10px;" lang="en" dir="ltr"> + +<h3>{% block subtitle %}pyLoad - {{_("Webinterface")}}{% endblock %}</h3> + +{% block statusbar %} +{% endblock %} +{% for message in messages %} + <b><p>{{message}}</p></b> +{% endfor %} + +<div id="load-indicator" style="opacity: 0; float: right; margin-top: -10px;"> + <img src="/img/ajax-loader.gif" alt="" style="padding-right: 5px"/> + {{_("loading")}} +</div> + +{% block content %} +{% endblock content %} + + <hr style="clear: both;" /> + +<div id="foot" style="with: 98%; margin-left: 10px; margin-right:10px">© 2008-2015 pyLoad Team +<a href="#top" class="action top" accesskey="x"><span>{{_("Back to top")}}</span></a><br /> +<!--<div class="breadcrumbs"></div>--> + +</div> +</div> +</div> + +<div style="display: none;"> + {% include "/tml/window.html" %} + {% include "/tml/captcha.html" %} + {% block hidden %} + {% endblock %} +</div> +<noscript><h1>Enable JavaScript to use the webinterface.</h1></noscript> +</body> +</html> diff --git a/pyload/webui/themes/Next/tml/captcha.html b/pyload/webui/themes/Next/tml/captcha.html new file mode 100644 index 000000000..2b449aa86 --- /dev/null +++ b/pyload/webui/themes/Next/tml/captcha.html @@ -0,0 +1,40 @@ +<!-- Captcha box --> +<div id="cap_box" > + + <form id="cap_form" class="form-group" action="/json/set_captcha" method="POST" enctype="multipart/form-data" onsubmit="return false;"> + + <h3>{{_("Captcha reading")}}</h3> + <p id="cap_title">{{_("Please read the text on the captcha.")}}</p> + + <div id="cap_textual"> + + <input id="cap_id" name="cap_id" type="hidden" value="" /> + + <div class="form-group"> + <label>{{_("Captcha")}}</label> + <span ></br> + <img id="cap_textual_img" style="border: 1px solid #bbb; padding: 3px 3px 3px 3px;" src=""> + </span> + </div> + <div class="form-group"> + <label>{{_("Text")}}</label> + <input class="form-control" id="cap_result" name="cap_result" type="text" size="20" /> + <p class="small">{{_("Input the text on the captcha.")}}</p> + </div> + + <div id="cap_positional" style="text-align: center"> + <img id="cap_positional_img" src="" style="margin: 10px; cursor:pointer"> + </div> + + <div id="button_bar" style="text-align: center"> + <span> + <button class="btn btn-primary" id="cap_submit" type="submit" style="float: right; margin-left: 5px;">{{_("Submit")}}</button> + <button class="btn btn-default" id="cap_reset" type="reset" style="float: right">{{_("Close")}}</button> + </span> + </div> + + <div class="spacer"></div> + + </form> + +</div> diff --git a/pyload/webui/themes/Next/tml/downloads.html b/pyload/webui/themes/Next/tml/downloads.html new file mode 100644 index 000000000..0643ed2f8 --- /dev/null +++ b/pyload/webui/themes/Next/tml/downloads.html @@ -0,0 +1,29 @@ +{% extends '/tml/base.html' %} + +{% block title %}Downloads - {{super()}} {% endblock %} + +{% block subtitle %} +{{_("Downloads")}} +{% endblock %} + +{% block content %} + +<ul style="list-style-type: none;"> + {% for folder in files.folder %} + <li style="list-style-type: none;"> + <span style="margin-right: 5px" class="glyphicon glyphicon-folder-close"></span>{{ folder.name }} + <ul> + {% for file in folder.files %} + <li style="list-style-type: none;"><span style="margin-right: 5px" class="glyphicon glyphicon-file"></span><a href='get/{{ folder.path|escape }}/{{ file|escape }}'>{{file}}</a></li> + {% endfor %} + </ul> + </li> + {% endfor %} + + {% for file in files.files %} + <li style="list-style-type: none;"> <span style="margin-right: 5px" class="glyphicon glyphicon-file"></span><a href='get/{{ file|escape }}'>{{ file }}</a></li> + {% endfor %} + +</ul> + +{% endblock %} diff --git a/pyload/webui/themes/Next/tml/filemanager.html b/pyload/webui/themes/Next/tml/filemanager.html new file mode 100644 index 000000000..c940f3f79 --- /dev/null +++ b/pyload/webui/themes/Next/tml/filemanager.html @@ -0,0 +1,80 @@ +{% extends '/tml/base.html' %} + +{% block head %} + +<script type="text/javascript" src="/js/filemanager.js"></script> + +<script type="text/javascript"> + +document.addEvent("domready", function(){ + var fmUI = new FilemanagerUI("url",1); +}); +</script> +{% endblock %} + +{% block title %}Downloads - {{super()}} {% endblock %} + + +{% block subtitle %} +{{_("FileManager")}} +{% endblock %} + +{% macro display_file(file) %} + <li class="file"> + <input type="hidden" name="path" class="path" value="{{ file.path }}" /> + <input type="hidden" name="name" class="name" value="{{ file.name }}" /> + <span> + <b>{{ file.name }}</b> + <span class="buttons" style="opacity:0"> + <img title="{{_("Rename Directory")}}" class="rename" style="cursor: pointer" height="12px" src="/img/pencil.png" /> + + <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/delete.png" /> + </span> + </span> + </li> +{%- endmacro %} + +{% macro display_folder(fld, open = false) -%} + <li class="folder"> + <input type="hidden" name="path" class="path" value="{{ fld.path }}" /> + <input type="hidden" name="name" class="name" value="{{ fld.name }}" /> + <span> + <b>{{ fld.name }}</b> + <span class="buttons" style="opacity:0"> + <img title="{{_("Rename Directory")}}" class="rename" style="cursor: pointer" height="12px" src="/img/pencil.png" /> + + <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/delete.png" /> + + <img title="{{_("Add subdirectory")}}" class="mkdir" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/add_folder.png" /> + </span> + </span> + {% if (fld.folders|length + fld.files|length) > 0 %} + {% if open %} + <ul> + {% else %} + <ul style="display:none"> + {% endif %} + {% for child in fld.folders %} + {{ display_folder(child) }} + {% endfor %} + {% for child in fld.files %} + {{ display_file(child) }} + {% endfor %} + </ul> + {% else %} + <div style="display:none">{{ _("Folder is empty") }}</div> + {% endif %} + </li> +{%- endmacro %} + +{% block content %} + +<div style="clear:both"><!-- --></div> + +<ul id="directories-list"> +{{ display_folder(root, true) }} +</ul> + +{% include "/tml/rename_directory.html" %} + +{% endblock %} diff --git a/pyload/webui/themes/Next/tml/folder.html b/pyload/webui/themes/Next/tml/folder.html new file mode 100644 index 000000000..d01fc4972 --- /dev/null +++ b/pyload/webui/themes/Next/tml/folder.html @@ -0,0 +1,15 @@ +<li class="folder"> + <input type="hidden" name="path" class="path" value="{{ path }}" /> + <input type="hidden" name="name" class="name" value="{{ name }}" /> + <span> + <b>{{ name }}</b> + <span class="buttons" style="opacity:0"> + <img title="{{_("Rename Directory")}}" class="rename" style="cursor: pointer" height="12px" src="/img/pencil.png" /> + + <img title="{{_("Delete Directory")}}" class="delete" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/delete.png" /> + + <img title="{{_("Add subdirectory")}}" class="mkdir" style="margin-left: -10px; cursor: pointer" width="12px" height="12px" src="/img/add_folder.png" /> + </span> + </span> + <div style="display:none">{{ _("Folder is empty") }}</div> +</li> diff --git a/pyload/webui/themes/Next/tml/home.html b/pyload/webui/themes/Next/tml/home.html new file mode 100644 index 000000000..2ff1ad58b --- /dev/null +++ b/pyload/webui/themes/Next/tml/home.html @@ -0,0 +1,277 @@ +{% extends '/tml/base.html' %} +{% block head %} + +<script type="text/javascript"> + +var em; +var operafix = (navigator.userAgent.toLowerCase().search("opera") >= 0); + +document.addEvent("domready", function(){ + em = new EntryManager(); +}); + +var EntryManager = new Class({ + initialize: function(){ + this.json = new Request.JSON({ + url: "json/links", + secure: false, + async: true, + onSuccess: this.update.bind(this), + initialDelay: 0, + delay: 2500, + limit: 30000 + }); + + this.ids = [{% for link in content %} + {% if forloop.last %} + {{ link.id }} + {% else %} + {{ link.id }}, + {% endif %} + {% endfor %}]; + + this.entries = []; + this.container = $('LinksAktiv'); + + this.parseFromContent(); + + this.json.startTimer(); + }, + parseFromContent: function(){ + this.ids.each(function(id,index){ + var entry = new LinkEntry(id); + entry.parse(); + this.entries.push(entry) + }, this); + }, + update: function(data){ + + try{ + this.ids = this.entries.map(function(item){ + return item.fid + }); + + this.ids.filter(function(id){ + return !this.ids.contains(id) + },data).each(function(id){ + var index = this.ids.indexOf(id); + this.entries[index].remove(); + this.entries = this.entries.filter(function(item){return item.fid != this},id); + this.ids = this.ids.erase(id) + }, this); + + data.links.each(function(link, i){ + if (this.ids.contains(link.fid)){ + + var index = this.ids.indexOf(link.fid); + this.entries[index].update(link) + + }else{ + var entry = new LinkEntry(link.fid); + entry.insert(link); + this.entries.push(entry); + this.ids.push(link.fid); + this.container.adopt(entry.elements.tr,entry.elements.pgbTr); + entry.fade.start('opacity', 1); + entry.fadeBar.start('opacity', 1); + + } + }, this) + + }catch(e){ + //alert(e) + } + } +}); + + +var LinkEntry = new Class({ + initialize: function(id){ + this.fid = id; + this.id = id; + }, + parse: function(){ + this.elements = { + tr: $("link_{id}".substitute({id: this.id})), + name: $("link_{id}_name".substitute({id: this.id})), + status: $("link_{id}_status".substitute({id: this.id})), + info: $("link_{id}_info".substitute({id: this.id})), + bleft: $("link_{id}_bleft".substitute({id: this.id})), + percent: $("link_{id}_percent".substitute({id: this.id})), + remove: $("link_{id}_remove".substitute({id: this.id})), + pgbTr: $("link_{id}_pgb_tr".substitute({id: this.id})), + pgb: $("link_{id}_pgb".substitute({id: this.id})) + }; + this.initEffects(); + }, + insert: function(item){ + try{ + + + + this.elements = { + tr: new Element('tr', { + 'html': '', + 'styles':{ + 'opacity': 0, + } + }), + status: new Element('td', { + 'html': ' ', + }), + statusspan: new Element('span', { + 'html': item.statusmsg, + 'class': 'label label-default', + 'styles':{ + + } + }), + name: new Element('td', { + 'html': item.name + }), + info: new Element('td', { + 'html': item.info + }), + bleft: new Element('td', { + 'html': humanFileSize(item.size) + }), + percent: new Element('span', { + 'html': item.percent+ '% / '+ humanFileSize(item.size-item.bleft) + }), + remove: new Element('span',{ + 'html': '', + 'class': 'glyphicon glyphicon-remove', + 'styles':{ + 'margin-left': '3px', + } + }), + pgbTr: new Element('tr', { + 'html':'', + 'styles':{ + 'border-top-color': '#fff', + } + }), + progress: new Element('div', { + 'html':'', + 'class':'progress', + 'styles':{ + 'margin-bottom': '0px', + } + }), + pgb: new Element('div', { + 'html':'', + 'class':'progress-bar progress-bar-striped active', + 'role':'progress', + 'styles':{ + 'width': item.percent+'%', + 'background-color': '#ddd' + } + }) + + }; + + + this.elements.status.adopt(this.elements.statusspan); + this.elements.progress.adopt(this.elements.pgb); + this.elements.tr.adopt(this.elements.status,this.elements.name,this.elements.info,this.elements.bleft,new Element('td').adopt(this.elements.percent,this.elements.remove)); + this.elements.pgbTr.adopt(new Element('td',{'colspan':5}).adopt(this.elements.progress)); + this.initEffects(); + }catch(e){ + alert(e) + } + }, + initEffects: function(){ + if(!operafix) + this.bar = new Fx.Morph(this.elements.pgb, {unit: '%', duration: 5000, link: 'link', fps:30}); + this.fade = new Fx.Tween(this.elements.tr); + this.fadeBar = new Fx.Tween(this.elements.pgbTr); + + this.elements.remove.addEvent('click', function(){ + new Request({method: 'get', url: '/json/abort_link/'+this.id}).send(); + }.bind(this)); + + }, + update: function(item){ + this.elements.name.set('text', item.name); + this.elements.statusspan.set('text', item.statusmsg); + this.elements.info.set('text', item.info); + this.elements.bleft.set('text', item.format_size); + this.elements.percent.set('text', item.percent+ '% / '+ humanFileSize(item.size-item.bleft)); + if (item.statusmsg == "waiting") { + this.elements.statusspan.set('class', 'label label-warning') + } else if (item.statusmsg == "starting") { + this.elements.statusspan.set('class', 'label label-info') + } else if (item.statusmsg == "downloading") { + this.elements.statusspan.set('class', 'label label-success') + } else if (item.stausmsg == "extracting") { + this.elements.statusspan.set('class', 'label label-primary') + } else { + this.elements.statusspan.set('class', 'label label-default') + } + if(!operafix) + { + + this.bar.start({ + 'width': item.percent, + 'background-color': [Math.round(120/100*item.percent),80,70].hsbToRgb().rgbToHex() + }); + } + else + { + this.elements.pgb.set( + 'styles', { + 'height': '4px', + 'width': item.percent+'%', + 'background-color': [Math.round(50/200*item.percent),0,200].hsbToRgb().rgbToHex(), + }); + } + + }, + remove: function(){ + this.fade.start('opacity',0).chain(function(){this.elements.tr.dispose();}.bind(this)); + this.fadeBar.start('opacity',0).chain(function(){this.elements.pgbTr.dispose();}.bind(this)); + + } + }); + + +</script> +{% endblock %} + +{% block subtitle %}{{_("Active Downloads")}}{% endblock %} +{% block content %} +<table class="table" style="width:100%;"> + <thead> + <tr class="header"> + <th>{{_("Status")}}</th> + <th>{{_("Name")}}</th> + <th>{{_("Information")}}</th> + <th>{{_("Size")}}</th> + <th>{{_("Progress")}}</th> + </tr> + </thead> +</br> + <tbody id="LinksAktiv"> + + {% for link in content %} + <tr id="link_{{ link.id }}"> + <td id="link_{{ link.id }}_status"><span class="label label-{% if link.status == 'downloading' %}success{% endif %}{% if link.status == 'extracting' %}primary{% endif %}{% if link.status == 'starting' %}warning{% else %}default{% endif %}">{{ link.status }}</span></td> + <td id="link_{{ link.id }}_name">{{ link.name }}</td> + <td id="link_{{ link.id }}_info">{{ link.info }}</td> + <td id="link_{{ link.id }}_bleft">{{ link.format_size }}</td> + <td> + <span id="link_{{ link.id }}_percent">{{ link.percent }}% /{{ link.bleft }}</span> + <img id="link_{{ link.id }}_remove" style="vertical-align: middle; margin-right: -20px; margin-left: 5px; margin-top: -2px; cursor:pointer;" src="/img/control_cancel.png"/> + </td> + </tr> + <tr id="link_{{ link.id }}_pgb_tr"> + <td colspan="5"> + <div id="link_{{ link.id }}_pgb" class="progressBar" style="background-color: green; height:4px; width: {{ link.percent }}%;"> </div> + </td> + </tr> + {% endfor %} + + </tbody> +</table> +</div> +{% endblock %} diff --git a/pyload/webui/themes/Next/tml/info.html b/pyload/webui/themes/Next/tml/info.html new file mode 100644 index 000000000..f6c3a91e1 --- /dev/null +++ b/pyload/webui/themes/Next/tml/info.html @@ -0,0 +1,81 @@ +{% extends '/tml/base.html' %} + +{% block head %} + <script type="text/javascript"> + window.addEvent("domready", function() { + var ul = new Element('ul#twitter_update_list'); + var script1 = new Element('script[src=http://twitter.com/javascripts/blogger.js][type=text/javascript]'); + var script2 = new Element('script[src=http://twitter.com/statuses/user_timeline/pyLoad.json?callback=twitterCallback2&count=6][type=text/javascript]'); + $("twitter").adopt(ul, script1, script2); + }); + </script> +{% endblock %} + +{% block title %}{{ _("Information") }} - {{ super() }} {% endblock %} +{% block subtitle %}{{ _("Information") }}{% endblock %} + +{% block content %} + <h3>{{ _("News") }}</h3> + <div id="twitter"></div> + + <h3>{{ _("Support") }}</h3> + + <ul> + <li style="font-weight:bold;"> + <a href="http://pyload.org/wiki" target="_blank">Wiki</a> + | + <a href="http://forum.pyload.org/" target="_blank">Forum</a> + | + <a href="http://pyload.org/irc/" target="_blank">Chat</a> + </li> + <li style="font-weight:bold;"><a href="http://docs.pyload.org" target="_blank">Documentation</a></li> + <li style="font-weight:bold;"><a href="https://bitbucket.org/spoob/pyload/overview" target="_blank">Development</a></li> + <li style="font-weight:bold;"><a href="https://bitbucket.org/spoob/pyload/issues?status=new&status=open" target="_blank">Issue Tracker</a></li> + + </ul> + + <h3>{{ _("System") }}</h3> + <table class="system"> + <tr> + <td><b>{{ _("Python:") }}</b></td> + <td>{{ python }}</td> + </tr> + <tr> + <td><b>{{ _("OS:") }}</b></td> + <td>{{ os }}</td> + </tr> + <tr> + <td><b>{{ _("pyLoad version:") }}</b></td> + <td>{{ version }}</td> + </tr> + <tr> + <td><b>{{ _("Installation Folder:") }}</b></td> + <td>{{ folder }}</td> + </tr> + <tr> + <td><b>{{ _("Config Folder:") }}</b></td> + <td>{{ config }}</td> + </tr> + <tr> + <td><b>{{ _("Download Folder:") }}</b></td> + <td>{{ download }}</td> + </tr> + <tr> + <td><b>{{ _("Free Space:") }}</b></td> + <td>{{ freespace }}</td> + </tr> + <tr> + <td><b>{{ _("Language:") }}</b></td> + <td>{{ language }}</td> + </tr> + <tr> + <td><b>{{ _("Webinterface Port:") }}</b></td> + <td>{{ webif }}</td> + </tr> + <tr> + <td><b>{{ _("Remote Interface Port:") }}</b></td> + <td>{{ remote }}</td> + </tr> + </table> + +{% endblock %} diff --git a/pyload/webui/themes/Next/tml/login.html b/pyload/webui/themes/Next/tml/login.html new file mode 100644 index 000000000..93d50d64d --- /dev/null +++ b/pyload/webui/themes/Next/tml/login.html @@ -0,0 +1,36 @@ +{% extends '/tml/base.html' %} + +{% block title %}{{_("Login")}} - {{super()}} {% endblock %} + +{% block content %} + +<div class="centeralign"> +<form action="" method="post" accept-charset="utf-8" id="login"> + <div class="no"> + <input type="hidden" name="do" value="login" /> + <fieldset> + <legend>Login</legend> + <label> + <span>{{_("Username")}}</span> + <input type="text" size="20" name="username" /> + </label> + <br /> + <label> + <span>{{_("Password")}}</span> + <input type="password" size="20" name="password" /> + </label> + <br /> + <input type="submit" value="Login" class="button" /> + </fieldset> + </div> +</form> + +{% if errors %} +<p>{{_("Your username and password didn't match. Please try again.")}}</p> + {{ _("To reset your login data or add an user run:") }} <b> python pyLoadCore.py -u</b> +{% endif %} + +</div> +<br> + +{% endblock %} diff --git a/pyload/webui/themes/Next/tml/logout.html b/pyload/webui/themes/Next/tml/logout.html new file mode 100644 index 000000000..db7e9290e --- /dev/null +++ b/pyload/webui/themes/Next/tml/logout.html @@ -0,0 +1,9 @@ +{% extends '/tml/base.html' %} + +{% block head %} +<meta http-equiv="refresh" content="3; url=/"> +{% endblock %} + +{% block content %} +<p><b>{{_("You were successfully logged out.")}}</b></p> +{% endblock %} diff --git a/pyload/webui/themes/Next/tml/logs.html b/pyload/webui/themes/Next/tml/logs.html new file mode 100644 index 000000000..f0f25a9dd --- /dev/null +++ b/pyload/webui/themes/Next/tml/logs.html @@ -0,0 +1,41 @@ +{% extends '/tml/base.html' %} + +{% block title %}{{_("Logs")}} - {{super()}} {% endblock %} +{% block subtitle %}{{_("Logs")}}{% endblock %} +{% block head %} +<link rel="stylesheet" type="text/css" href="/css/log.css"/> +{% endblock %} + +{% block content %} +<div style="clear: both;"></div> + +<div class="logpaginator"><a href="{{ "/logs/1" }}"><span class="glyphicon glyphicon-fast-backward"></span></a> <a href="{{ "/logs/" + iprev|string }}"><span class="glyphicon glyphicon-step-backward"></span></a> <a href="{{ "/logs/" + inext|string }}"><span class="glyphicon glyphicon-step-forward"></span></a> <a href="/logs/"><span class="glyphicon glyphicon-fast-forward"></span></a></div> +<div class="logperpage"> + <form id="logform1" action="" method="POST"> + <label for="reversed">Reversed:</label> + <input type="checkbox" name="reversed" onchange="this.form.submit();" {% if reversed %} checked="checked" {% endif %} /> + <label for="perpage">Lines per page:</label> + <select name="perpage" onchange="this.form.submit();"> + {% for value in perpage_p %} + <option value="{{value.0}}"{% if value.0 == perpage %} selected="selected" {% endif %}>{{value.1}}</option> + {% endfor %} + </select> + </form> +</div> +<div class="logwarn">{{warning}}</div> +<div style="clear: both;"></div> +<div class="logdiv"> + <table class="logtable" cellpadding="0" cellspacing="0"> + {% for line in log %} + <tr><td class="logline">{{line.line}}</td><td class="{{color_template}}{{line.level}}">{{line.date}}</td><td class="loglevel loglevel{{color_template}}{{line.level}}"><span>{{line.level}}</span></td><td class="{{color_template}}{{line.level}}">{{line.message}}</td></tr> + {% endfor %} + </table> +</div> +<div class="logform"> +<form id="logform2" action="" method="POST"> + <label for="from">Jump to time:</label><input style="float:left; width:80%;" class="form-control" type="text" name="from" size="15" value="{{from}}"/> + <input style="float:left; width:19%; margin-left: 1%;" class="btn btn-primary" type="submit" value="ok" /> +</form> +</div> +<div style="clear: both; height: 10px;"> </div> +{% endblock %} diff --git a/pyload/webui/themes/Next/tml/pathchooser.html b/pyload/webui/themes/Next/tml/pathchooser.html new file mode 100644 index 000000000..9f61d282d --- /dev/null +++ b/pyload/webui/themes/Next/tml/pathchooser.html @@ -0,0 +1,76 @@ +<html> +<head> + <script class="javascript"> + function chosen() + { + opener.ifield.value = document.forms[0].p.value; + close(); + } + function exit() + { + close(); + } + function setInvalid() { + document.forms[0].send.disabled = 'disabled'; + document.forms[0].p.style.color = '#FF0000'; + } + function setValid() { + document.forms[0].send.disabled = ''; + document.forms[0].p.style.color = '#000000'; + } + function setFile(file) + { + document.forms[0].p.value = file; + setValid(); + + } + </script> + <link rel="stylesheet" type="text/css" href="/css/pathchooser.css"/> +</head> +<body{% if type == 'file' %}{% if not oldfile %} onload="setInvalid();"{% endif %}{% endif %}> +<center> + <div id="paths"> + <form method="get" action="?" onSubmit="chosen();" onReset="exit();"> + <input type="text" name="p" value="{{ oldfile|default(cwd) }}" size="60" onfocus="setValid();"> + <input type="submit" value="Ok" name="send"> + </form> + + {% if type == 'folder' %} + <span class="path_abs_rel">{{_("Path")}}: <a href="{{ "/pathchooser" + cwd|path_make_absolute|quotepath }}"{% if absolute %} style="text-decoration: underline;"{% endif %}>{{_("absolute")}}</a> | <a href="{{ "/pathchooser/" + cwd|path_make_relative|quotepath }}"{% if not absolute %} style="text-decoration: underline;"{% endif %}>{{_("relative")}}</a></span> + {% else %} + <span class="path_abs_rel">{{_("Path")}}: <a href="{{ "/filechooser/" + cwd|path_make_absolute|quotepath }}"{% if absolute %} style="text-decoration: underline;"{% endif %}>{{_("absolute")}}</a> | <a href="{{ "/filechooser/" + cwd|path_make_relative|quotepath }}"{% if not absolute %} style="text-decoration: underline;"{% endif %}>{{_("relative")}}</a></span> + {% endif %} + </div> + <table border="0" cellspacing="0" cellpadding="3"> + <tr> + <th>{{_("name")}}</th> + <th>{{_("size")}}</th> + <th>{{_("type")}}</th> + <th>{{_("last modified")}}</th> + </tr> + {% if parentdir %} + <tr> + <td colspan="4"> + <a href="{% if type == 'folder' %}{{ "/pathchooser/" + parentdir|quotepath }}{% else %}{{ "/filechooser/" + parentdir|quotepath }}{% endif %}"><span class="parentdir">{{_("parent directory")}}</span></a> + </td> + </tr> + {% endif %} +{% for file in files %} + <tr> + {% if type == 'folder' %} + <td class="name">{% if file.type == 'dir' %}<a href="{{ "/pathchooser/" + file.fullpath|quotepath }}" title="{{ file.fullpath }}"><span class="path_directory">{{ file.name|truncate(25) }}</span></a>{% else %}<span class="path_file" title="{{ file.fullpath }}">{{ file.name|truncate(25) }}{% endif %}</span></td> + {% else %} + <td class="name">{% if file.type == 'dir' %}<a href="{{ "/filechooser/" + file.fullpath|quotepath }}" title="{{ file.fullpath }}"><span class="file_directory">{{ file.name|truncate(25) }}</span></a>{% else %}<a href="#" onclick="setFile('{{ file.fullpath }}');" title="{{ file.fullpath }}"><span class="file_file">{{ file.name|truncate(25) }}{% endif %}</span></a></td> + {% endif %} + <td class="size">{{ file.size|float|filesizeformat }}</td> + <td class="type">{% if file.type == 'dir' %}directory{% else %}{{ file.ext|default("file") }}{% endif %}</td> + <td class="mtime">{{ file.modified|date("d.m.Y - H:i:s") }}</td> + <tr> +<!-- <tr> + <td colspan="4">{{_("no content")}}</td> + </tr> --> +{% endfor %} + </table> + </center> +</body> +</html> diff --git a/pyload/webui/themes/Next/tml/queue.html b/pyload/webui/themes/Next/tml/queue.html new file mode 100644 index 000000000..0e343a754 --- /dev/null +++ b/pyload/webui/themes/Next/tml/queue.html @@ -0,0 +1,109 @@ +{% extends '/tml/base.html' %} +{% block head %} + +<script type="text/javascript" src="/js/package.js"></script> + +<script type="text/javascript"> + +document.addEvent("domready", function(){ + var pUI = new PackageUI("url", {{ target }}); +}); +</script> +{% endblock %} + +{% if target %} + {% set name = _("Queue") %} +{% else %} + {% set name = _("Collector") %} +{% endif %} + +{% block pageactions %} +<div class="btn-group btn-group-sm" role="group" aria-label="..." style="margin-left:10px;"> + <button id="del_finished" class="btn btn-default"><span>{{_("Delete Finished")}}</span></button> + <button id="restart_failed" class="btn btn-default"><span>{{_("Restart Failed")}}</span></button> +</div> +{% endblock %} + +{% block title %}{{name}} - {{super()}} {% endblock %} +{% block subtitle %}{{name}}{% endblock %} + +{% block content %} +{% autoescape true %} + +<ul id="package-list" style="list-style: none; padding-left: 0; margin-top: -10px;"> +{% for package in content %} + <li> +<div id="package_{{package.pid}}" > + <div class="order" style="display: none;">{{ package.order }}</div> + + <div class="packagename" style="float:left; width: 50%; cursor: pointer"> + <span class="glyphicon glyphicon-folder-close"></span> + <span class="name" style="font-size: 16px; font-weight: bold;"><em class="package_drag" style="font-style:normal">{{package.name }}</em></span> + + <span class="buttons" style="opacity:0"> + <span class="glyphicon glyphicon-trash" title="{{_("Delete Package")}}" style="cursor: pointer" width="12px" height="12px" src="/img/delete.png" /></span> + + <span class="glyphicon glyphicon-repeat" title="{{_("Restart Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/img/arrow_refresh.png" /></span> + + <span class="glyphicon glyphicon-pencil" title="{{_("Edit Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/img/pencil.png" /></span> + + <span class="glyphicon glyphicon-transfer" title="{{_("Move Package")}}" style="margin-left: -10px; cursor: pointer" height="12px" src="/img/package_go.png" /></span> + </span> + </div> + {% set progress = (package.linksdone * 100) / package.linkstotal %} + + <div id="progress" class="progress" style="float:left; width: 50%; margin-top: -5px; color:#fff; font-weight: 700; font-size:12px;"> + <div class="progress-bar" role="progressbar" style="width: {{ progress }}%; height: 100%; position: relative; z-index: 1;"> + <label>{{ package.linksdone }} / {{ package.linkstotal }}</label> + </div> + <label style="right: 30px; position: absolute; z-index: 2; color:#fff;"> + {{ package.sizedone|formatsize }} / {{ package.sizetotal|formatsize }}</label> + </div> + <div style="clear: both; margin-bottom: -10px"></div> + + + <div id="children_{{package.pid}}" style="display: none; margin-bottom: 15px;" class="children"> + <span class="child_secrow" style="margin-bottom: 30px; margin-top: 5px;">{{_("Folder:")}} <span class="folder">{{package.folder}}</span> | {{_("Password:")}} <span class="password">{{package.password}}</span></span> + <ul id="sort_children_{{package.pid}}" style="list-style: none; padding-left: 0"> + </ul> + </div> +</div> + </li> +{% endfor %} +</ul> +{% endautoescape %} +{% endblock %} + +{% block hidden %} +<div id="pack_box" style="z-index: 2"> + <form id="pack_form" class="from-group" action="/json/edit_package" method="POST" enctype="multipart/form-data"> + <h3>{{_("Edit Package")}}</h3> + <p>{{_("Edit the package detais below.")}}</p> + + <input name="pack_id" id="pack_id" type="hidden" value=""/> + + <div class="form-group"> + <label for="pack_name">{{_("Name")}}</label> + <input class="form-control" id="pack_name" name="pack_name" type="text" /> + <p class="help-block">{{_("The name of the package.")}}</p> + </div> + <div class="form-group"> + <label for="pack_folder">{{_("Folder")}}</label> + <input class="form-control" id="pack_folder" name="pack_folder" type="text" /> + <p class="help-block">{{_("Name of subfolder for these downloads.")}}</p> + </div> + <div class="form-group"> + <label for="pack_pws">{{_("Password")}}</label> + <textarea class="form-control" style=" width: 100%;" rows="3" name="pack_pws" id="pack_pws"></textarea> + <p class="help-block">{{_("List of passwords used for unrar.")}}</p> + </div> + <button class="btn btn-primary" style="float: right; margin-left: 5px;" type="submit">{{_("Submit")}}</button> + <button class="btn btn-default" id="pack_reset" style="float: right;" type="reset" >{{_("Reset")}}</button> + + + <div class="spacer"></div> + + </form> + +</div> +{% endblock %} diff --git a/pyload/webui/themes/Next/tml/settings.html b/pyload/webui/themes/Next/tml/settings.html new file mode 100644 index 000000000..5acab4f1f --- /dev/null +++ b/pyload/webui/themes/Next/tml/settings.html @@ -0,0 +1,212 @@ +{% extends '/tml/base.html' %} + +{% block title %}{{ _("Config") }} - {{ super() }} {% endblock %} +{% block subtitle %}{{ _("Config") }}{% endblock %} + +{% block head %} + <script type="text/javascript" src="/lib/MooTools/TinyTab/tinytab.js"></script> + <script type="text/javascript" src="/lib/MooTools/MooDropMenu/MooDropMenu.js"></script> + <script type="text/javascript" src="/js/settings.js"></script> + +{% endblock %} + +{% block content %} + + <ul id="toptabs" class="nav nav-tabs"> + <li role="presentation" class"active"><a href="#">{{ _("General") }}</a></li> + <li role="presentation"><a href="#">{{ _("Plugins") }}</a></li> + <li role="presentation"><a href="#">{{ _("Accounts") }}</a></li> + </ul> + + <div id="tabsback" style="height: 20px; padding-left: 150px; color: white; font-weight: bold;"> + + </div> + + <span id="tabs-body"> + <!-- General --> + <span id="general" class="active tabContent"> + <ul class="nav tabs" style="width: 20%; float:left;"> + <li class> + <div class="panel panel-default" > + <div class="panel-body"> + <ul id="general-menu" style="float: left; height: 600px; overflow: auto; overflow-x: hidden; width: 100%"> + {% for entry,name in conf.general %} + <nobr> + <li style="list-style-type: none; cursor: pointer; margin-top: 10px;" id="general|{{ entry }}">{{ name }}</li> + </nobr> + {% endfor %} + </ul> + </div> + </div> + </li> + </ul> + + <form style="float: left; width:40%; margin-left: 20%; diplay:block; position: fixed; overflow: auto;" id="general_form" action="" method="POST" autocomplete="off"> + <span id="general_form_content"> + <br> + <h3> {{ _("Choose a section from the menu") }}</h3> + <br> + </span> + + <input class="btn btn-primary" style="float:right; margin-right: 10px;" id="general|submit" type="submit" value="{{_("Submit")}}"/> + </form> + </span> + + <!-- Plugins --> + <span id="plugins" class="tabContent"> + <ul class="nav tabs" style="width: 20%; float:left; hight:300px;"> + <li class> + <div class="panel panel-default"> + <div class="panel-body"> + <ul id="plugin-menu" style="float: left; height: 600px; overflow: auto; overflow-x: hidden; width: 100%"> + {% for entry,name in conf.plugin %} + <nobr> + <li style="list-style-type: none; cursor: pointer; margin-top: 10px;" id="plugin|{{ entry }}">{{ name }}</li> + </nobr> + {% endfor %} + </ul> + <div> + </div> + </li> + </ul> + + + <form style="float: left; width:40%; margin-left: 10px;" id="plugin_form" action="" method="POST" autocomplete="off"> + + <span id="plugin_form_content" style:"position: static;"> + <br> + <h3> {{ _("Choose a section from the menu") }}</h3> + <br> + </span> + <input class="btn btn-primary" style="float:right; margin-right: 10px;" id="plugin|submit" class="styled_button" type="submit" value="{{_("Submit")}}"/> + </form> + + </span> + + <!-- Accounts --> + <span id="accounts" class="tabContent"> + <form id="account_form" action="/json/update_accounts" method="POST"> + + <table class="settable wide table"> + + <thead> + <tr> + <th>{{ _("Plugin") }}</th> + <th>{{ _("Name") }}</th> + <th>{{ _("Password") }}</th> + <th>{{ _("Status") }}</th> + <th>{{ _("Premium") }}</th> + <th>{{ _("Valid until") }}</th> + <th>{{ _("Traffic left") }}</th> + <th>{{ _("Time") }}</th> + <th>{{ _("Max Parallel") }}</th> + <th>{{ _("Delete?") }}</th> + </tr> + </thead> + + + {% for account in conf.accs %} + {% set plugin = account.type %} + <tr> + <td> + <span style="padding:5px">{{ plugin }}</span> + </td> + + <td><label for="{{plugin}}|password;{{account.login}}" + style="color:#424242;">{{ account.login }}</label></td> + <td> + <input id="{{plugin}}|password;{{account.login}}" + name="{{plugin}}|password;{{account.login}}" + type="password" value="{{account.password}}" size="12"/> + </td> + <td> + {% if account.valid %} + <span style="font-weight: bold; color: #006400;"> + {{ _("valid") }} + {% else %} + <span style="font-weight: bold; color: #8b0000;"> + {{ _("not valid") }} + {% endif %} + </span> + </td> + <td> + {% if account.premium %} + <span style="font-weight: bold; color: #006400;"> + {{ _("yes") }} + {% else %} + <span style="font-weight: bold; color: #8b0000;"> + {{ _("no") }} + {% endif %} + </span> + </td> + <td> + <span style="font-weight: bold;"> + {{ account.validuntil }} + </span> + </td> + <td> + <span style="font-weight: bold;"> + {{ account.trafficleft }} + </span> + </td> + <td> + <input id="{{plugin}}|time;{{account.login}}" + name="{{plugin}}|time;{{account.login}}" type="text" + size="7" value="{{account.time}}"/> + </td> + <td> + <input id="{{plugin}}|limitdl;{{account.login}}" + name="{{plugin}}|limitdl;{{account.login}}" type="text" + size="2" value="{{account.limitdl}}"/> + </td> + <td> + <input id="{{plugin}}|delete;{{account.login}}" + name="{{plugin}}|delete;{{account.login}}" type="checkbox" + value="True"/> + </td> + </tr> + {% endfor %} + </table> + <button id="account_add" style="margin-left: 5px;" type="submit" class="btn btn-default"> <span class="glyphicon glyphicon-plus"></button> + + <button id="account_submit" type="submit" class="btn btn-primary" >{{_("Submit")}}</button> + + </form> + </span> + </span> +{% endblock %} +{% block hidden %} +<div id="account_box" style="z-index: 2"> +<form id="add_account_form" action="/json/add_account" method="POST" enctype="multipart/form-data"> +<h3>{{_("Add Account")}}</h3> +<p>{{_("Enter your account data to use premium features.")}}</p> + +<div class="form-group"> +<label for="account_login">{{_("Login")}}</label> +<input class="form-control" id="account_login" name="account_login" type="text" /> +<p >{{_("Your username.")}}</p> +</div> +<div class="form-group"> +<label for="account_password">{{_("Password")}}</label> +<input class="form-control" id="account_password" name="account_password" type="password" size="20" /> +<p >{{_("The password for this account.")}}</p> +</div> +<div class="form-group"> +<label for="account_type">{{_("Type")}}</label> +<p>{{_("Choose the hoster for your account.")}}</p> +</div> +<div class="form-group"> + <select name=account_type id="account_type"> + {% for type in types|sort %} + <option value="{{ type }}">{{ type }}</option> + {% endfor %} + </select> +</div> +<button class="btn btn-primary" style="float: right; margin-left: 5px;" id="account_add_button" type="submit">{{_("Add")}}</button> +<button class="btn btn-default" style="float: right;" id="account_reset" style="margin-left: 0" type="reset">{{_("Reset")}}</button> +<div class="spacer"></div> + +</form> + +</div> +{% endblock %} diff --git a/pyload/webui/themes/Next/tml/settings_item.html b/pyload/webui/themes/Next/tml/settings_item.html new file mode 100644 index 000000000..72566950f --- /dev/null +++ b/pyload/webui/themes/Next/tml/settings_item.html @@ -0,0 +1,50 @@ +<table class="settable table"> + + {% if section.outline %} + <tr><th colspan="2">{{ section.outline }}</th></tr> + {% endif %} + {% for okey, option in sorted_conf(section) %} + {% if okey not in ("desc","outline") %} + <tr> + <td><label for="{{skey}}|{{okey}}" + style="color:#424242;">{{ option.desc }}:</label></td> + <td> + {% if option.type == "bool" %} + <select id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}"> + <option {% if option.value %} selected="selected" + {% endif %}value="True">{{ _("on") }}</option> + <option {% if not option.value %} selected="selected" + {% endif %}value="False">{{ _("off") }}</option> + </select> + {% elif ";" in option.type %} + <select id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}"> + {% for entry in option.list %} + <option {% if option.value == entry %} + selected="selected" {% endif %}>{{ entry }}</option> + {% endfor %} + </select> + {% elif option.type == "folder" %} + <input style="float: right; margin-bottom: 5px;" class="form-control" name="{{skey}}|{{okey}}" type="text" + id="{{skey}}|{{okey}}" value="{{option.value}}"/> + <input style="float: right;" class="form-control btn btn-primary" name="browsebutton" type="button" + onclick="ifield = document.getElementById('{{skey}}|{{okey}}'); pathchooser = window.open('{% if option.value %}{{ "/pathchooser/" + option.value|quotepath }}{% else %}{{ pathroot }}{% endif %}', 'pathchooser', 'scrollbars=yes,toolbar=no,menubar=no,statusbar=no,width=650,height=300'); pathchooser.ifield = ifield; window.ifield = ifield;" + value="{{_("Browse")}}"/> + {% elif option.type == "file" %} + <input style="float: right; margin-bottom: 5px;" class="form-control" name="{{skey}}|{{okey}}" type="text" + id="{{skey}}|{{okey}}" value="{{option.value}}"/> + <input style="float: right;" class="form-control btn btn-primary" name="browsebutton" type="button" + onclick="ifield = document.getElementById('{{skey}}|{{okey}}'); filechooser = window.open('{% if option.value %}{{ "/filechooser/" + option.value|quotepath }}{% else %}{{ fileroot }}{% endif %}', 'filechooser', 'scrollbars=yes,toolbar=no,menubar=no,statusbar=no,width=650,height=300'); filechooser.ifield = ifield; window.ifield = ifield;" + value="{{_("Browse")}}"/> + {% elif option.type == "password" %} + <input style="float: right;" class="form-control" id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}" + type="password" value="{{option.value}}"/> + {% else %} + <input style="float: right;" class="form-control" id="{{skey}}|{{okey}}" name="{{skey}}|{{okey}}" + type="text" value="{{option.value}}"/> + {% endif %} + </td> + </tr> + {% endif %} + {% endfor %} + +</table> diff --git a/pyload/webui/themes/Next/tml/window.html b/pyload/webui/themes/Next/tml/window.html new file mode 100644 index 000000000..87c78f96c --- /dev/null +++ b/pyload/webui/themes/Next/tml/window.html @@ -0,0 +1,46 @@ +<iframe id="upload_target" name="upload_target" src="" style="display: none; width:0;height:0"></iframe> + +<div id="add_box" class="from-group"> +<form id="add_form" action="/json/add_package" method="POST" enctype="multipart/form-data"> +<h3>{{_("Add Package")}}</h3> +<p>{{_("Paste your links or upload a container.")}}</p> +<div class="form-group"> + <label for="add_name">{{_("Name")}}</label> + <input id="add_name" class="form-control" name="add_name" type="text" /> + <p class="help-block">{{_("The name of the new package.")}}</p> + </div> + <div class="form-group"> + <label for="add_links">{{_("Links")}}</label> + </div> + <div> + <textarea class="form-control" rows="5" style="width: 100%" name="add_links" id="add_links"></textarea> +</div> +<div class="form-group"> + <p class="help-block">{{_("The name of the new package.")}} {{ _("Filter urls") }} <span class=" glyphicon glyphicon-filter" onclick="parseUri()"></span></p> + </div> + <div class="form-group"> + <label for="add_password">{{_("Password")}}</label> + <input id="add_password" class="form-control" name="add_password" type="text"> + <p class="help-block">{{_("Password for RAR-Archive")}}</p> + </div> + <div class="form-group"> + <label>{{_("File")}}</label> + <input type="file" name="add_file" id="add_file"/> + <p class="help-block">{{_("Upload a container.")}}</p> + </div> + <div class="form-group"> + <label for="add_dest">{{_("Destination")}}</label> + <span class="cont"> + {{_("Queue")}} + <input type="radio" name="add_dest" id="add_dest" value="1" checked="checked"/> + {{_("Collector")}} + <input type="radio" name="add_dest" id="add_dest2" value="0"/> + </span> + </div> + <button type="submit" class="btn btn-primary" style="float: right; margin-right: 5px;">{{_("Add Package")}}</button> + <button id="add_reset" class="btn btn-default" style="float: right; margin-right: 5px;" type="reset">{{_("Reset")}}</button> +</form> + + + +</div> |