diff --git a/CouchPotato.py b/CouchPotato.py
index 090a5c3..83fac59 100755
--- a/CouchPotato.py
+++ b/CouchPotato.py
@@ -2,11 +2,9 @@
# -*- coding: utf-8 -*-
'''Wrapper for the command line interface.'''
-from os.path import dirname, isfile
+from os.path import dirname
import os
-import subprocess
import sys
-import traceback
# Root path
base_path = dirname(os.path.abspath(__file__))
@@ -17,37 +15,8 @@ sys.path.insert(0, os.path.join(base_path, 'libs'))
from couchpotato.core.logger import CPLog
log = CPLog(__name__)
-try:
- from couchpotato import cli
-except ImportError, e:
- log.info('Checking local dependencies...')
- if isfile(__file__):
- cwd = dirname(__file__)
- log.info('Updating libraries...')
- stdout, stderr = subprocess.Popen(['git', 'submodule', 'init'],
- stderr = subprocess.PIPE,
- stdout = subprocess.PIPE).communicate()
- if stderr:
- log.info('[WARNING] Git is complaining:')
- log.info(stderr)
- stdout, stderr = subprocess.Popen(['git', 'submodule', 'update'],
- stderr = subprocess.PIPE,
- stdout = subprocess.PIPE).communicate()
- if stderr:
- log.info('[WARNING] Git is complaining:')
- log.info(stderr)
-
- log.info('Passing execution to couchpotato...')
- try:
- from couchpotato import cli
- except ImportError:
- log.error("[ERROR]: Something's seriously wrong.")
- log.error(traceback.print_exc())
- sys.exit(1)
- else:
- # Running from Titanium
- raise NotImplementedError("Don't know how to do that.")
+from couchpotato import cli
if __name__ == '__main__':
try:
cli.cmd_couchpotato(base_path, sys.argv[1:])
diff --git a/couchpotato/cli.py b/couchpotato/cli.py
index 270dc19..b35bdf6 100644
--- a/couchpotato/cli.py
+++ b/couchpotato/cli.py
@@ -1,7 +1,6 @@
from argparse import ArgumentParser
-from couchpotato import get_engine, web
+from couchpotato import web
from couchpotato.api import api
-from couchpotato.core.settings.model import *
from libs.daemon import createDaemon
from logging import handlers
import logging
@@ -110,9 +109,8 @@ def cmd_couchpotato(base_path, args):
upgrade(db, repo)
# Configure Database
- from elixir import setup_all, create_all
- setup_all()
- create_all(get_engine())
+ from couchpotato.core.settings.model import setup
+ setup()
# Create app
diff --git a/couchpotato/core/__init__.py b/couchpotato/core/__init__.py
index 954a651..fdb92bd 100644
--- a/couchpotato/core/__init__.py
+++ b/couchpotato/core/__init__.py
@@ -4,59 +4,81 @@ def start():
pass
config = [{
- 'name': 'global',
- 'tab': 'general',
- 'options': {
- 'debug': {
- 'advanced': True,
- 'default': False,
- 'type': 'bool',
- 'label': 'Debug',
- 'description': 'Enable debugging.',
- },
- 'host': {
- 'advanced': True,
- 'default': '0.0.0.0',
- 'type': 'string',
- 'label': 'Host',
- 'description': 'Host that I should listen to 0.0.0.0 listens to everything.',
- },
- 'port': {
- 'default': 5000,
- 'type': 'int',
- 'label': 'Port',
- 'description': 'The port I should listen to.',
- },
- 'username': {
- 'default': '',
- 'type': 'string',
- 'label': 'Username',
- },
- 'password': {
- 'default': '',
- 'password': True,
- 'type': 'string',
- 'label': 'Password',
- },
- 'launch_browser': {
- 'default': True,
- 'type': 'bool',
- 'label': 'Launch Browser',
- 'description': 'Launch the browser when I start.',
+ 'name': 'core',
+ 'groups': [
+ {
+ 'tab': 'general',
+ 'name': 'basics',
+ 'label': 'Basics',
+ 'description': 'Needs restart before changes take effect.',
+ 'options': [
+ {
+ 'name': 'username',
+ 'default': '',
+ 'type': 'string',
+ 'label': 'Username',
+ },
+ {
+ 'name': 'password',
+ 'default': '',
+ 'password': True,
+ 'type': 'string',
+ 'label': 'Password',
+ },
+ {
+ 'name': 'host',
+ 'advanced': True,
+ 'default': '0.0.0.0',
+ 'type': 'string',
+ 'label': 'Host',
+ 'description': 'Host that I should listen to. "0.0.0.0" listens to all ips.',
+ },
+ {
+ 'name': 'port',
+ 'default': 5000,
+ 'type': 'int',
+ 'label': 'Port',
+ 'description': 'The port I should listen to.',
+ },
+ {
+ 'name': 'launch_browser',
+ 'default': True,
+ 'type': 'bool',
+ 'label': 'Launch Browser',
+ 'description': 'Launch the browser when I start.',
+ },
+ ],
},
- 'url_base': {
+ {
+ 'tab': 'general',
+ 'name': 'advanced',
+ 'label': 'Advanced',
+ 'description': "For those who know what the're doing",
'advanced': True,
- 'default': '',
- 'type': 'string',
- 'label': 'Url Base',
- 'description': 'When using mod_proxy use this to prepend the url with this.',
+ 'options': [
+ {
+ 'name': 'api_key',
+ 'default': uuid4().hex,
+ 'type': 'string',
+ 'readonly': True,
+ 'label': 'Api Key',
+ 'description': "This is top-secret! Don't share this!",
+ },
+ {
+ 'name': 'debug',
+ 'default': False,
+ 'type': 'bool',
+ 'label': 'Debug',
+ 'description': 'Enable debugging.',
+ },
+ {
+ 'name': 'url_base',
+ 'default': '',
+ 'type': 'string',
+ 'label': 'Url Base',
+ 'description': 'When using mod_proxy use this to prepend the url with this.',
+ },
+ ],
},
- 'api_key': {
- 'default': uuid4().hex,
- 'type': 'string',
- 'readonly': True,
- 'label': 'Api Key',
- 'description': 'This is top-secret! Don\'t share this!',
- }
- }
+ ],
}]
diff --git a/couchpotato/core/loader.py b/couchpotato/core/loader.py
index 1806366..1d9eaf4 100644
--- a/couchpotato/core/loader.py
+++ b/couchpotato/core/loader.py
@@ -53,8 +53,9 @@ class Loader:
for section in module.config:
fireEventAsync('settings.options', section['name'], section)
options = {}
- for key, option in section['options'].iteritems():
- options[key] = option['default']
+ for group in section['groups']:
+ for option in group['options']:
+ options[option['name']] = option['default']
fireEventAsync('settings.register', section_name = section['name'], options = options, save = save)
return True
except Exception, e:
diff --git a/couchpotato/core/plugins/renamer/__init__.py b/couchpotato/core/plugins/renamer/__init__.py
index a5d6e26..49296d4 100644
--- a/couchpotato/core/plugins/renamer/__init__.py
+++ b/couchpotato/core/plugins/renamer/__init__.py
@@ -1,33 +1,33 @@
def start():
pass
-config = [{
- 'name': 'Renamer',
- 'tab': 'renaming',
- 'options': {
- 'enabled': {
- 'default': False,
- 'type': 'bool',
- 'description': 'Enable renaming',
- },
- 'from': {
- 'default': '',
- 'type': 'directory',
- 'label': 'From',
- 'description': 'Folder where the movies are downloaded to.',
- },
- 'to': {
- 'default': '',
- 'type': 'directory',
- 'label': 'To',
- 'description': 'Folder where the movies will be moved to.',
- },
- 'run_every': {
- 'default': 1,
- 'type': 'int',
- 'unit': 'min(s)',
- 'description': 'Search for new movies inside the folder every X minutes.',
- }
- }
-}]
-
+#config = [{
+# 'name': 'Renamer',
+# 'tab': 'renaming',
+# 'options': {
+# 'enabled': {
+# 'default': False,
+# 'type': 'bool',
+# 'description': 'Enable renaming',
+# },
+# 'from': {
+# 'default': '',
+# 'type': 'directory',
+# 'label': 'From',
+# 'description': 'Folder where the movies are downloaded to.',
+# },
+# 'to': {
+# 'default': '',
+# 'type': 'directory',
+# 'label': 'To',
+# 'description': 'Folder where the movies will be moved to.',
+# },
+# 'run_every': {
+# 'default': 1,
+# 'type': 'int',
+# 'unit': 'min(s)',
+# 'description': 'Search for new movies inside the folder every X minutes.',
+# }
+# }
+#}]
+config = []
diff --git a/couchpotato/core/providers/tmdb/__init__.py b/couchpotato/core/providers/tmdb/__init__.py
index 96647aa..b08ec8f 100644
--- a/couchpotato/core/providers/tmdb/__init__.py
+++ b/couchpotato/core/providers/tmdb/__init__.py
@@ -4,14 +4,22 @@ def start():
return TMDB()
config = [{
- 'name': 'TheMovieDB',
- 'tab': 'providers',
- 'options': {
- 'api_key': {
+ 'name': 'themoviedb',
+ 'groups': [
+ {
+ 'tab': 'providers',
+ 'name': 'tmdb',
+ 'label': 'TheMovieDB',
'advanced': True,
- 'default': '9b939aee0aaafc12a65bf448e4af9543',
- 'type': 'string',
- 'description': 'Api key to use for calls to TheMovieDB.',
- }
- }
+ 'options': [
+ {
+ 'name': 'api_key',
+ 'default': '9b939aee0aaafc12a65bf448e4af9543',
+ 'type': 'string',
+ 'label': 'Api Key',
+ 'description': 'Used for all calls to TheMovieDB.',
+ },
+ ],
+ },
+ ],
}]
diff --git a/couchpotato/core/providers/tmdb/main.py b/couchpotato/core/providers/tmdb/main.py
index edfed97..35abaa7 100644
--- a/couchpotato/core/providers/tmdb/main.py
+++ b/couchpotato/core/providers/tmdb/main.py
@@ -5,8 +5,9 @@ from couchpotato.core.logger import CPLog
from couchpotato.core.providers.base import Provider
from couchpotato.environment import Env
from urllib import quote_plus
-import urllib2
+import copy
import simplejson as json
+import urllib2
log = CPLog(__name__)
@@ -21,7 +22,7 @@ class TMDB(Provider):
addEvent('provider.movie.search', self.search)
def conf(self, attr):
- return Env.setting(attr, 'TheMovieDB')
+ return Env.setting(attr, 'themoviedb')
def search(self, q, limit = 12, alternative = True):
''' Find movie by name '''
@@ -48,7 +49,7 @@ class TMDB(Provider):
nr = 0
for movie in data:
- year = movie['released'][:4]
+ year = str(movie['released'])[:4]
# Poster url
poster = ''
@@ -59,7 +60,7 @@ class TMDB(Provider):
break
# 1900 is the same as None
- if year == '1900':
+ if year == '1900' or year.lower() == 'none':
year = None
movie_data = {
@@ -70,14 +71,13 @@ class TMDB(Provider):
'year': year,
'tagline': 'This is the tagline of the movie',
}
- results.append(movie_data)
+ results.append(copy.deepcopy(movie_data))
alternativeName = movie['alternative_name']
if alternativeName and alternative:
if alternativeName.lower() != movie['name'].lower() and alternativeName.lower() != 'none' and alternativeName != None:
movie_data['name'] = toUnicode(alternativeName)
- results.append(movie_data)
-
+ results.append(copy.deepcopy(movie_data))
nr += 1
if nr == limit:
break
diff --git a/couchpotato/core/settings/__init__.py b/couchpotato/core/settings/__init__.py
index 2d76e05..acd26c7 100644
--- a/couchpotato/core/settings/__init__.py
+++ b/couchpotato/core/settings/__init__.py
@@ -52,7 +52,7 @@ class Settings():
def set(self, section, option, value):
return self.p.set(section, option, self.cleanValue(value))
- def get(self, option = '', section = 'global', default = ''):
+ def get(self, option = '', section = 'core', default = ''):
try:
value = self.p.get(section, option)
return self.cleanValue(value)
diff --git a/couchpotato/core/settings/model.py b/couchpotato/core/settings/model.py
index 1cea72c..d07cbb2 100644
--- a/couchpotato/core/settings/model.py
+++ b/couchpotato/core/settings/model.py
@@ -121,3 +121,12 @@ class RenameHistory(Entity):
new = Field(String(255))
file = ManyToOne('File')
+
+
+def setup():
+ """ Setup the database and create the tables that don't exists yet """
+ from elixir import setup_all, create_all
+ from couchpotato import get_engine
+
+ setup_all()
+ create_all(get_engine())
diff --git a/couchpotato/environment.py b/couchpotato/environment.py
index bf02023..b295eaa 100644
--- a/couchpotato/environment.py
+++ b/couchpotato/environment.py
@@ -31,7 +31,7 @@ class Env:
return setattr(Env, '_' + attr, value)
@staticmethod
- def setting(attr, section = 'global', value = None, default = ''):
+ def setting(attr, section = 'core', value = None, default = ''):
# Return setting
if value == None:
diff --git a/couchpotato/static/images/close_button.png b/couchpotato/static/images/close_button.png
new file mode 100644
index 0000000..d11e72b
Binary files /dev/null and b/couchpotato/static/images/close_button.png differ
diff --git a/couchpotato/static/scripts/block.js b/couchpotato/static/scripts/block.js
index 851db63..e77d006 100644
--- a/couchpotato/static/scripts/block.js
+++ b/couchpotato/static/scripts/block.js
@@ -17,10 +17,6 @@ var BlockBase = new Class({
this.el = new Element('div.block');
},
- api: function(){
- return this.getParent().getApi()
- },
-
getParent: function(){
return this.page
},
diff --git a/couchpotato/static/scripts/block/search.js b/couchpotato/static/scripts/block/search.js
index 2f5b47a..bdf05e8 100644
--- a/couchpotato/static/scripts/block/search.js
+++ b/couchpotato/static/scripts/block/search.js
@@ -8,17 +8,57 @@ Block.Search = new Class({
var self = this;
self.el = new Element('div.search_form').adopt(
- self.input = new Element('input', {
- 'events': {
- 'keyup': self.keyup.bind(self)
+ new Element('div.input').adopt(
+ self.input = new Element('input', {
+ 'events': {
+ 'keyup': self.keyup.bind(self),
+ 'focus': self.hideResults.bind(self, false)
+ }
+ }),
+ new Element('a', {
+ 'events': {
+ 'click': self.clear.bind(self)
+ }
+ })
+ ),
+ self.result_container = new Element('div.results_container', {
+ 'tween': {
+ 'duration': 200
}
- }),
- self.results = new Element('div.results')
+ }).adopt(
+ new Element('div.pointer'),
+ self.results = new Element('div.results')
+ ).fade('hide')
);
- // Debug
- self.input.set('value', 'iron man');
- self.autocomplete(0)
+ self.spinner = new Spinner(self.result_container);
+
+ self.OuterClickStack = new EventStack.OuterClick();
+
+ },
+
+ clear: function(e){
+ var self = this;
+ (e).stop();
+
+ self.input.set('value', '');
+ self.input.focus()
+
+ self.movies = []
+ self.results.empty()
+ },
+
+ hideResults: function(bool){
+ var self = this;
+
+ if(self.hidden == bool) return;
+
+ self.result_container.fade(bool ? 0 : 1)
+
+ if(!bool && self.OuterClickStack.stack.length == 0)
+ self.OuterClickStack.push(self.hideResults.bind(self, true), self.el);
+
+ self.hidden = bool;
},
keyup: function(e){
@@ -36,6 +76,13 @@ Block.Search = new Class({
autocomplete: function(delay){
var self = this;
+ if(!self.q()){
+ self.hideResults(true)
+ return
+ }
+
+ self.spinner.show()
+
if(self.autocomplete_timer) clearTimeout(self.autocomplete_timer)
self.autocomplete_timer = self.list.delay((delay || 300), self)
},
@@ -48,13 +95,16 @@ Block.Search = new Class({
var q = self.q();
var cache = self.cache[q];
- if(!cache)
- self.api_request = self.api().request('movie.add.search', {
+ self.hideResults(false)
+
+ if(!cache){
+ self.api_request = Api.request('movie.add.search', {
'data': {
'q': q
},
'onComplete': self.fill.bind(self, q)
})
+ }
else
self.fill(q, cache)
@@ -65,20 +115,28 @@ Block.Search = new Class({
fill: function(q, json){
var self = this;
+ self.spinner.hide();
self.cache[q] = json
self.movies = []
self.results.empty()
Object.each(json.movies, function(movie){
- var m = new Block.Search.Item(movie);
- $(m).inject(self.results)
+
+ if(!movie.imdb || (movie.imdb && !self.results.getElement('#'+movie.imdb))){
+ var m = new Block.Search.Item(movie);
+ $(m).inject(self.results)
+ }
self.movies.include(m)
});
},
+ loading: function(bool){
+ this.el[bool ? 'addClass' : 'removeClass']('loading')
+ },
+
q: function(){
return this.input.get('value').trim();
}
@@ -93,31 +151,58 @@ Block.Search.Item = new Class({
self.info = info;
self.create();
+
+ self.OuterClickStack = new EventStack.OuterClick();
},
create: function(){
var self = this;
- self.el = new Element('div.movie').adopt(
- self.name = new Element('h2', {
- 'text': self.info.name
- }),
- self.tagline = new Element('span', {
- 'text': self.info.tagline
- }),
- self.year = self.info.year ? new Element('span', {
- 'text': self.info.year
- }) : null,
- self.director = self.info.director ? new Element('span', {
- 'text': 'Director:' + self.info.director
- }) : null,
- self.starring = self.info.actors ? new Element('span', {
- 'text': 'Starring:'
- }) : null
+ var info = self.info
+
+ self.el = new Element('div.movie', {
+ 'id': info.imdb
+ }).adopt(
+ new Element('div.add').adopt(
+ new Element('span', {
+ 'text': 'test'
+ })
+ ),
+ self.data_container = new Element('div.data', {
+ 'tween': {
+ duration: 400,
+ transition: 'quint:in:out'
+ },
+ 'events': {
+ 'click': self.showOptions.bind(self)
+ }
+ }).adopt(
+ self.thumbnail = info.poster ? new Element('img.thumbnail', {
+ 'src': info.poster
+ }) : null,
+ new Element('div.info').adopt(
+ self.name = new Element('h2', {
+ 'text': info.name
+ }).adopt(
+ self.year = info.year ? new Element('span', {
+ 'text': info.year
+ }) : null
+ ),
+ self.tagline = new Element('span', {
+ 'text': info.tagline
+ }),
+ self.director = self.info.director ? new Element('span', {
+ 'text': 'Director:' + info.director
+ }) : null,
+ self.starring = info.actors ? new Element('span', {
+ 'text': 'Starring:'
+ }) : null
+ )
+ )
)
-
- if(self.info.actors){
- Object.each(self.info.actors, function(actor){
+
+ if(info.actors){
+ Object.each(info.actors, function(actor){
new Element('span', {
'text': actor.name
}).inject(self.starring)
@@ -125,6 +210,24 @@ Block.Search.Item = new Class({
}
},
+ showOptions: function(){
+ var self = this;
+
+ if(!self.width)
+ self.width = self.data_container.getCoordinates().width
+
+ self.data_container.tween('margin-left', 0, self.width);
+
+ self.OuterClickStack.push(self.closeOptions.bind(self), self.el);
+
+ },
+
+ closeOptions: function(){
+ var self = this;
+
+ self.data_container.tween('margin-left', self.width, 0);
+ },
+
toElement: function(){
return this.el
}
diff --git a/couchpotato/static/scripts/couchpotato.js b/couchpotato/static/scripts/couchpotato.js
index c4601e9..f495bd1 100644
--- a/couchpotato/static/scripts/couchpotato.js
+++ b/couchpotato/static/scripts/couchpotato.js
@@ -18,7 +18,6 @@ var CouchPotato = new Class({
self.c = $(document.body)
self.route = new Route(self.defaults);
- self.api = new Api(self.options.api)
self.createLayout();
self.createPages();
@@ -43,8 +42,10 @@ var CouchPotato = new Class({
self.c.adopt(
self.header = new Element('div.header').adopt(
- self.block.navigation = new Block.Navigation(self, {}),
- self.block.search = new Block.Search(self, {})
+ new Element('div').adopt(
+ self.block.navigation = new Block.Navigation(self, {}),
+ self.block.search = new Block.Search(self, {})
+ )
),
self.content = new Element('div.content'),
self.block.footer = new Block.Footer(self, {})
@@ -71,36 +72,29 @@ var CouchPotato = new Class({
var action = self.route.getAction();
var params = self.route.getParams();
+ if(self.current_page)
+ self.current_page.hide()
+
var page = self.pages[page_name];
page.open(action, params);
page.show();
- if(self.current_page)
- self.current_page.hide()
-
self.current_page = page;
},
getBlock: function(block_name){
return this.block[block_name]
- },
-
- getApi: function(){
- return this.api
}
});
-var Api = new Class({
-
- url: '',
+var ApiClass = new Class({
- initialize: function(options){
+ setup: function(options){
var self = this
self.options = options;
-
},
request: function(type, options){
@@ -123,6 +117,7 @@ var Api = new Class({
}
});
+window.Api = new ApiClass()
var Route = new Class({
@@ -183,4 +178,44 @@ var Route = new Class({
var p = function(){
if(typeof(console) !== 'undefined' && console != null)
console.log(arguments)
-}
\ No newline at end of file
+};
+
+(function(){
+
+ var keyPaths = [];
+
+ var saveKeyPath = function(path) {
+ keyPaths.push({
+ sign: (path[0] === '+' || path[0] === '-')? parseInt(path.shift()+1) : 1,
+ path: path
+ });
+ };
+
+ var valueOf = function(object, path) {
+ var ptr = object;
+ path.each(function(key) { ptr = ptr[key] });
+ return ptr;
+ };
+
+ var comparer = function(a, b) {
+ for (var i = 0, l = keyPaths.length; i < l; i++) {
+ aVal = valueOf(a, keyPaths[i].path);
+ bVal = valueOf(b, keyPaths[i].path);
+ if (aVal > bVal) return keyPaths[i].sign;
+ if (aVal < bVal) return -keyPaths[i].sign;
+ }
+ return 0;
+ };
+
+ Array.implement('sortBy', function(){
+ keyPaths.empty();
+ Array.each(arguments, function(argument) {
+ switch (typeOf(argument)) {
+ case "array": saveKeyPath(argument); break;
+ case "string": saveKeyPath(argument.match(/[+-]|[^.]+/g)); break;
+ }
+ });
+ return this.sort(comparer);
+ });
+
+})();
\ No newline at end of file
diff --git a/couchpotato/static/scripts/eventstack.js b/couchpotato/static/scripts/eventstack.js
new file mode 100644
index 0000000..748db7e
--- /dev/null
+++ b/couchpotato/static/scripts/eventstack.js
@@ -0,0 +1,71 @@
+/*
+---
+
+name: EventStack
+
+description: Helps you Escape.
+
+authors: Christoph Pojer (@cpojer)
+
+license: MIT-style license.
+
+requires: [Core/Class.Extras, Core/Element.Event, Class-Extras/Class.Binds]
+
+provides: EventStack
+
+...
+*/
+
+(function(){
+
+this.EventStack = new Class({
+
+ Implements: [Options, Class.Binds],
+
+ options: {
+ event: 'keyup',
+ condition: function(event){
+ return (event.key == 'esc');
+ }
+ },
+
+ initialize: function(options){
+ this.setOptions(options);
+ this.stack = [];
+ this.data = [];
+
+ document.addEvent(this.options.event, this.bound('condition'));
+ },
+
+ condition: function(event){
+ if (this.options.condition.call(this, event, this.data.getLast()))
+ this.pop(event);
+ },
+
+ erase: function(fn){
+ this.data.erase(this.data[this.stack.indexOf(fn)]);
+ this.stack.erase(fn);
+
+ return this;
+ },
+
+ push: function(fn, data){
+ this.erase(fn);
+ this.data.push(data || null);
+ this.stack.push(fn);
+
+ return this;
+ },
+
+ pop: function(event){
+ var fn = this.stack.pop(),
+ data = this.data.pop();
+
+ if (fn) fn.call(this, event, data);
+
+ return this;
+ }
+
+});
+
+}).call(this);
diff --git a/couchpotato/static/scripts/eventstack_outerclick.js b/couchpotato/static/scripts/eventstack_outerclick.js
new file mode 100644
index 0000000..5d4eede
--- /dev/null
+++ b/couchpotato/static/scripts/eventstack_outerclick.js
@@ -0,0 +1,30 @@
+/*
+---
+
+name: EventStack.OuterClick
+
+description: Helps you escape from clicks outside of a certain area.
+
+authors: Christoph Pojer (@cpojer)
+
+license: MIT-style license.
+
+requires: [EventStack]
+
+provides: EventStack.OuterClick
+
+...
+*/
+
+EventStack.OuterClick = new Class({
+
+ Extends: EventStack,
+
+ options: {
+ event: 'click',
+ condition: function(event, element){
+ return element && !element.contains(event.target);
+ }
+ }
+
+});
diff --git a/couchpotato/static/scripts/mootools.js b/couchpotato/static/scripts/mootools.js
index 541b984..6ccaf03 100644
--- a/couchpotato/static/scripts/mootools.js
+++ b/couchpotato/static/scripts/mootools.js
@@ -3,360 +3,5136 @@
MooTools: the javascript framework
web build:
- - http://mootools.net/core/efcfcd2923b4129a00a22580c45b1d75
+ - http://mootools.net/core/bd6349d3fbc489736e5aefb01157c8a8
packager build:
- - packager build Core/Class Core/Class.Extras Core/Element Core/Element.Style Core/Request.JSON Core/DOMReady
+ - packager build Core/Class Core/Class.Extras Core/Element Core/Element.Style Core/Element.Dimensions Core/Fx.Tween Core/Fx.Transitions Core/Request.JSON Core/DOMReady
-copyrights:
- - [MooTools](http://mootools.net)
+/*
+---
+
+name: Core
+
+description: The heart of MooTools.
+
+license: MIT-style license.
+
+copyright: Copyright (c) 2006-2010 [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]
+
+...
+*/
+
+(function(){
+
+this.MooTools = {
+ version: '1.3.1',
+ build: 'af48c8d589f43f32212f9bb8ff68a127e6a3ba6c'
+};
+
+// typeOf, instanceOf
+
+var typeOf = this.typeOf = function(item){
+ if (item == null) return 'null';
+ if (item.$family) 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 (item.callee) 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;
+ }
+ 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 (usePlural || typeof a != 'string') args = a;
+ else if (arguments.length > 1) args = arguments;
+ 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){
+ delete prototype[key];
+ prototype[key] = proto.protect();
+ }
+ }
+
+ if (isType) object.implement(prototype);
+
+ return force;
+};
+
+force('String', String, [
+ 'charAt', 'charCodeAt', 'concat', 'indexOf', 'lastIndexOf', 'match', 'quote', 'replace', 'search',
+ 'slice', 'split', 'substr', 'substring', '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({
+
+ 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);
+ }
+ },
+
+ 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);
+});
+
+
+
+}).call(this);
+
+
+/*
+---
+
+name: Array
+
+description: Contains Array Prototypes like each, contains, and erase.
+
+license: MIT-style license.
+
+requires: Type
+
+provides: Array
+
+...
+*/
+
+Array.implement({
+
+ invoke: function(methodName){
+ var args = Array.slice(arguments, 1);
+ return this.map(function(item){
+ return item[methodName].apply(item, args);
+ });
+ },
+
+ every: function(fn, bind){
+ for (var i = 0, l = this.length; 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 i = 0, l = this.length; i < l; i++){
+ if ((i in this) && fn.call(bind, this[i], i, this)) results.push(this[i]);
+ }
+ return results;
+ },
+
+ clean: function(){
+ return this.filter(function(item){
+ return item != null;
+ });
+ },
+
+ indexOf: function(item, from){
+ var len = this.length;
+ for (var i = (from < 0) ? Math.max(0, len + from) : from || 0; i < len; i++){
+ if (this[i] === item) return i;
+ }
+ return -1;
+ },
+
+ map: function(fn, bind){
+ var results = [];
+ for (var i = 0, l = this.length; i < l; 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; i < l; i++){
+ if ((i in this) && fn.call(bind, this[i], i, this)) return true;
+ }
+ return false;
+ },
+
+ 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 value.toInt(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: String
+
+description: Contains String Prototypes like camelCase, capitalize, test, and toInt.
+
+license: MIT-style license.
+
+requires: Type
+
+provides: String
+
+...
+*/
+
+String.implement({
+
+ test: function(regex, params){
+ return ((typeOf(regex) == 'regexp') ? regex : new RegExp('' + regex, params)).test(this);
+ },
+
+ contains: function(string, separator){
+ return (separator) ? (separator + this + separator).indexOf(separator + string + separator) > -1 : this.indexOf(string) > -1;
+ },
+
+ trim: function(){
+ return this.replace(/^\s+|\s+$/g, '');
+ },
+
+ clean: function(){
+ return this.replace(/\s+/g, ' ').trim();
+ },
+
+ camelCase: function(){
+ return this.replace(/-\D/g, function(match){
+ return match.charAt(1).toUpperCase();
+ });
+ },
+
+ hyphenate: function(){
+ return this.replace(/[A-Z]/g, function(match){
+ return ('-' + match.charAt(0).toLowerCase());
+ });
+ },
+
+ capitalize: function(){
+ return this.replace(/\b[a-z]/g, function(match){
+ return match.toUpperCase();
+ });
+ },
+
+ escapeRegExp: function(){
+ return this.replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1');
+ },
+
+ toInt: function(base){
+ return parseInt(this, base || 10);
+ },
+
+ toFloat: function(){
+ return parseFloat(this);
+ },
+
+ hexToRgb: function(array){
+ var hex = this.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
+ return (hex) ? hex.slice(1).hexToRgb(array) : null;
+ },
+
+ rgbToHex: function(array){
+ var rgb = this.match(/\d{1,3}/g);
+ return (rgb) ? rgb.rgbToHex(array) : null;
+ },
+
+ substitute: function(object, regexp){
+ return this.replace(regexp || (/\\?\{([^{}]+)\}/g), function(match, name){
+ if (match.charAt(0) == '\\') return match.slice(1);
+ return (object[name] != null) ? object[name] : '';
+ });
+ }
+
+});
+
+
+/*
+---
+
+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;
+ },
+
+ bind: function(bind){
+ var self = this,
+ args = (arguments.length > 1) ? Array.slice(arguments, 1) : null;
+
+ return function(){
+ if (!args && !arguments.length) return self.call(bind);
+ if (args && arguments.length) return self.apply(bind, args.concat(Array.from(arguments)));
+ return self.apply(bind, args || arguments);
+ };
+ },
+
+ 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: 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);
+ }
+};
+
+}).call(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;
+ }
+
+});
+
+}).call(this);
+
+
+/*
+---
+
+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 UID = 1;
+
+this.$uid = (window.ActiveXObject) ? function(item){
+ return (item.uid || (item.uid = [UID++]))[0];
+} : function(item){
+ return item.uid || (item.uid = UID++);
+};
+
+$uid(window);
+$uid(document);
+
+var ua = navigator.userAgent.toLowerCase(),
+ platform = navigator.platform.toLowerCase(),
+ UA = ua.match(/(opera|ie|firefox|chrome|version)[\s\/:]([\w\d\.]+)?.*?(safari|version[\s\/:]([\w\d\.]+)|$)/) || [null, 'unknown', 0],
+ mode = UA[1] == 'ie' && document.documentMode;
+
+var Browser = this.Browser = {
+
+ extend: Function.prototype.extend,
+
+ name: (UA[1] == 'version') ? UA[3] : UA[1],
+
+ version: mode || parseFloat((UA[1] == 'opera' && UA[4]) ? UA[4] : UA[2]),
+
+ Platform: {
+ name: ua.match(/ip(?:ad|od|hone)/) ? 'ios' : (ua.match(/(?:webos|android)/) || platform.match(/mac|win|linux/) || ['other'])[0]
+ },
+
+ Features: {
+ xpath: !!(document.evaluate),
+ air: !!(window.runtime),
+ query: !!(document.querySelector),
+ json: !!(window.JSON)
+ },
+
+ Plugins: {}
+
+};
+
+Browser[Browser.name] = true;
+Browser[Browser.name + parseInt(Browser.version, 10)] = true;
+Browser.Platform[Browser.Platform.name] = true;
+
+// 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);
+
+// Flash detection
+
+var version = (Function.attempt(function(){
+ return navigator.plugins['Shockwave Flash'].description;
+}, function(){
+ return new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
+}) || '0 r0').match(/\d+/g);
+
+Browser.Plugins.Flash = {
+ version: Number(version[0] || '0.' + version[1]) || 0,
+ build: Number(version[2]) || 0
+};
+
+// 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(/
+
+
@@ -29,13 +34,15 @@