diff --git a/couchpotato/core/_base/updater/main.py b/couchpotato/core/_base/updater/main.py index cf9150f4..8bf26359 100644 --- a/couchpotato/core/_base/updater/main.py +++ b/couchpotato/core/_base/updater/main.py @@ -5,6 +5,7 @@ from couchpotato.core.logger import CPLog from couchpotato.core.plugins.base import Plugin from couchpotato.environment import Env from git.repository import LocalRepository +from datetime import datetime import os import time import traceback @@ -48,12 +49,15 @@ class Updater(Plugin): def getInfo(self): - return jsonified({ + return jsonified(self.info()) + + def info(self): + return { 'repo_name': self.repo_name, 'last_check': self.last_check, 'update_version': self.update_version, 'version': self.getVersion() - }) + } def getVersion(self): @@ -91,14 +95,14 @@ class Updater(Plugin): log.info('Versions, local:%s, remote:%s' % (local.hash[:8], remote.hash[:8])) if local.getDate() < remote.getDate(): + self.update_version = { + 'hash': remote.hash[:8], + 'date': remote.getDate(), + } if self.conf('automatic') and not self.update_failed: if self.doUpdate(): fireEventAsync('app.crappy_restart') else: - self.update_version = { - 'hash': remote.hash[:8], - 'date': remote.getDate(), - } if self.conf('notification'): fireEvent('updater.available', message = 'A new update is available', data = self.getVersion()) @@ -119,11 +123,16 @@ class Updater(Plugin): self.repo.saveStash() log.info('Updating to latest version') + info = self.info() self.repo.pull() # Delete leftover .pyc files self.deletePyc() + # Notify before returning and restarting + version_date = datetime.fromtimestamp(info['update_version']['date']) + fireEvent('updater.updated', 'Updated to a new version with hash "%s", this version is from %s' % (info['update_version']['hash'], version_date), data = info) + return True except: log.error('Failed updating via GIT: %s' % traceback.format_exc()) diff --git a/couchpotato/core/notifications/core/main.py b/couchpotato/core/notifications/core/main.py index a5a478b2..df53347d 100644 --- a/couchpotato/core/notifications/core/main.py +++ b/couchpotato/core/notifications/core/main.py @@ -1,8 +1,13 @@ +from couchpotato import get_session from couchpotato.api import addApiView from couchpotato.core.event import addEvent, fireEvent -from couchpotato.core.helpers.request import jsonified +from couchpotato.core.helpers.encoding import toUnicode +from couchpotato.core.helpers.request import jsonified, getParam +from couchpotato.core.helpers.variable import tryInt from couchpotato.core.logger import CPLog from couchpotato.core.notifications.base import Notification +from couchpotato.core.settings.model import Notification as Notif +from sqlalchemy.sql.expression import or_ import time log = CPLog(__name__) @@ -11,28 +16,65 @@ log = CPLog(__name__) class CoreNotifier(Notification): messages = [] + listen_to = [ + 'movie.downloaded', 'movie.snatched', + 'updater.available', 'updater.updated', + ] def __init__(self): + super(CoreNotifier, self).__init__() addEvent('notify', self.notify) addEvent('notify.frontend', self.frontend) - addApiView('core_notifier.listener', self.listener) + addApiView('notification.markread', self.markAsRead, docs = { + 'desc': 'Mark notifications as read', + 'params': { + 'id': {'desc': 'Notification id you want to mark as read.', 'type': 'int (comma separated)'}, + }, + }) + addApiView('notification.listener', self.listener) self.registerEvents() - def registerEvents(self): # Library update, frontend refresh addEvent('library.update_finish', lambda data: fireEvent('notify.frontend', type = 'library.update', data = data)) - def notify(self, message = '', data = {}): - self.add(data = { - 'message': message, - 'raw': data, + def markAsRead(self): + ids = getParam('ids').split(',') + + db = get_session() + + q = db.query(Notif) \ + .filter(or_(*[Notif.id == tryInt(s) for s in ids])) + q.update({Notif.read: True}) + + db.commit() + + return jsonified({ + 'success': True }) + def notify(self, message = '', data = {}): + + db = get_session() + + n = Notif( + message = toUnicode(message), + data = data + ) + db.add(n) + db.commit() + + ndict = n.to_dict() + ndict['type'] = 'notification' + ndict['time'] = time.time() + self.messages.append(ndict) + + db.remove() + def frontend(self, type = 'notification', data = {}): self.messages.append({ 'time': time.time(), @@ -48,6 +90,15 @@ class CoreNotifier(Notification): if message['time'] > (time.time() - 15): messages.append(message) + # Get unread + if getParam('init'): + db = get_session() + notifications = db.query(Notif).filter_by(read = False).all() + for n in notifications: + ndict = n.to_dict() + ndict['type'] = 'notification' + messages.append(ndict) + self.messages = [] return jsonified({ 'success': True, diff --git a/couchpotato/core/notifications/core/static/notification.js b/couchpotato/core/notifications/core/static/notification.js index e96fdba2..617ef2ac 100644 --- a/couchpotato/core/notifications/core/static/notification.js +++ b/couchpotato/core/notifications/core/static/notification.js @@ -10,19 +10,90 @@ var NotificationBase = new Class({ // Listener App.addEvent('load', self.startInterval.bind(self)); App.addEvent('unload', self.stopTimer.bind(self)); - self.addEvent('notification', self.notify.bind(self)) + App.addEvent('notification', self.notify.bind(self)); // Add test buttons to settings page App.addEvent('load', self.addTestButtons.bind(self)); + // Notification bar + self.notifications = [] + App.addEvent('load', function(){ + + App.block.notification = new Block.Menu(self, { + 'class': 'notification_menu', + 'onOpen': self.markAsRead.bind(self) + }) + $(App.block.notification).inject(App.getBlock('search'), 'after'); + self.badge = new Element('div.badge').inject(App.block.notification, 'top').hide(); + + App.getBlock('notification').addLink(new Element('a.more', { + 'href': App.createUrl('notifications'), + 'text': 'See more notifications' + })); + }) + + }, + + notify: function(result){ + var self = this; + + var added = new Date(); + added.setTime(result.added*1000) + + result.el = App.getBlock('notification').addLink( + new Element('span').adopt( + new Element('span.message', {'text': result.message}), + new Element('span.added', {'text': added.timeDiffInWords(), 'title': added}) + ) + , 'top'); + self.notifications.include(result); + + if(!result.read) + self.setBadge(self.notifications.filter(function(n){ return !n.read}).length) + + if(self.notifications.length >= 5){ + var n = self.notifications[self.notifications.length-5]; + n.el.destroy(); + } + + }, + + setBadge: function(value){ + var self = this; + self.badge.set('text', value) + self.badge[value ? 'show' : 'hide']() + }, + + markAsRead: function(){ + var self = this; + + var rn = self.notifications.filter(function(n){ + return !n.read + }) + + var ids = [] + rn.each(function(n){ + ids.include(n.id) + }) + + Api.request('notification.markread', { + 'data': { + 'ids': ids.join(',') + }, + 'onSuccess': function(){ + self.setBadge('') + } + }) + }, startInterval: function(){ var self = this; - self.request = Api.request('core_notifier.listener', { + self.request = Api.request('notification.listener', { 'initialDelay': 100, 'delay': 3000, + 'data': {'init':true}, 'onSuccess': self.processData.bind(self) }) @@ -40,16 +111,12 @@ var NotificationBase = new Class({ this.request.stopTimer() }, - notify: function(data){ - var self = this; - - }, - processData: function(json){ var self = this; + self.request.options.data = {} Array.each(json.result, function(result){ - App.fireEvent(result.type, result.data) + App.fireEvent(result.type, result) }) }, diff --git a/couchpotato/core/plugins/movie/static/list.js b/couchpotato/core/plugins/movie/static/list.js index e13fe2ee..9ddffd97 100644 --- a/couchpotato/core/plugins/movie/static/list.js +++ b/couchpotato/core/plugins/movie/static/list.js @@ -116,25 +116,7 @@ var MovieList = new Class({ 'change': self.search.bind(self) } }), - self.navigation_menu = new Element('div.more_menu').adopt( - self.navigation_menu_ul = new Element('ul'), - self.navigation_menu_toggle = new Element('a.button.onlay', { - 'events': { - 'click': function(){ - self.navigation_menu.toggleClass('show') - - if(self.navigation_menu.hasClass('show')) - this.addEvent('outerClick', function(){ - self.navigation_menu.removeClass('show') - this.removeEvents('outerClick'); - }) - else - this.removeEvents('outerClick'); - - } - } - }) - ), + self.navigation_menu = new Block.Menu(self), self.mass_edit_form = new Element('div.mass_edit_form').adopt( new Element('span.select').adopt( self.mass_edit_select = new Element('input[type=checkbox].inlay', { @@ -222,7 +204,7 @@ var MovieList = new Class({ // Add menu or hide if (self.options.menu.length > 0) self.options.menu.each(function(menu_item){ - self.navigation_menu_ul.adopt(new Element('li').adopt(menu_item)); + self.navigation_menu.addLink(menu_item); }) else self.navigation_menu.hide() @@ -268,7 +250,7 @@ var MovieList = new Class({ 'class': 'delete', 'events': { 'click': function(e){ - (e).stop(); + (e).preventDefault(); Api.request('movie.delete', { 'data': { 'id': ids.join(',') diff --git a/couchpotato/core/plugins/movie/static/movie.css b/couchpotato/core/plugins/movie/static/movie.css index cdef5f22..36ff17c3 100644 --- a/couchpotato/core/plugins/movie/static/movie.css +++ b/couchpotato/core/plugins/movie/static/movie.css @@ -1,9 +1,3 @@ -/* @override - http://localhost:5000/static/movie_plugin/movie.css - http://192.168.1.20:5000/static/movie_plugin/movie.css - http://127.0.0.1:5000/static/movie_plugin/movie.css -*/ - .movies { padding: 60px 0 20px; } @@ -454,7 +448,7 @@ } .movies .alph_nav .more_menu { - float: right; + margin-left: 48px; } .movies .alph_nav .more_menu > a { diff --git a/couchpotato/core/plugins/movie/static/movie.js b/couchpotato/core/plugins/movie/static/movie.js index cd81b652..53cd9e31 100644 --- a/couchpotato/core/plugins/movie/static/movie.js +++ b/couchpotato/core/plugins/movie/static/movie.js @@ -230,7 +230,7 @@ var IMDBAction = new Class({ gotoIMDB: function(e){ var self = this; - (e).stop(); + (e).preventDefault(); window.open('http://www.imdb.com/title/'+self.id+'/'); } @@ -258,7 +258,7 @@ var ReleaseAction = new Class({ show: function(e){ var self = this; - (e).stop(); + (e).preventDefault(); if(!self.options_container){ self.options_container = new Element('div.options').adopt( @@ -292,7 +292,7 @@ var ReleaseAction = new Class({ new Element('a.download.icon', { 'events': { 'click': function(e){ - (e).stop(); + (e).preventDefault(); self.download(release); } } @@ -300,7 +300,7 @@ var ReleaseAction = new Class({ new Element('a.delete.icon', { 'events': { 'click': function(e){ - (e).stop(); + (e).preventDefault(); self.ignore(release); this.getParent('.item').toggleClass('ignored') } diff --git a/couchpotato/core/plugins/movie/static/search.css b/couchpotato/core/plugins/movie/static/search.css index b3ef1e50..2be6ce1e 100644 --- a/couchpotato/core/plugins/movie/static/search.css +++ b/couchpotato/core/plugins/movie/static/search.css @@ -1,11 +1,6 @@ -/* @override - http://localhost:5000/static/movie_plugin/search.css - http://192.168.1.20:5000/static/movie_plugin/search.css - http://127.0.0.1:5000/static/movie_plugin/search.css -*/ - .search_form { display: inline-block; + vertical-align: middle; width: 25%; } diff --git a/couchpotato/core/plugins/movie/static/search.js b/couchpotato/core/plugins/movie/static/search.js index 63d55dd3..a94b3438 100644 --- a/couchpotato/core/plugins/movie/static/search.js +++ b/couchpotato/core/plugins/movie/static/search.js @@ -53,7 +53,7 @@ Block.Search = new Class({ clear: function(e){ var self = this; - (e).stop(); + (e).preventDefault(); self.input.set('value', ''); self.input.focus() @@ -283,7 +283,7 @@ Block.Search.Item = new Class({ add: function(e){ var self = this; - (e).stop(); + (e).preventDefault(); Api.request('movie.add', { 'data': { diff --git a/couchpotato/core/plugins/profile/static/profile.css b/couchpotato/core/plugins/profile/static/profile.css index c1f57196..b763cd80 100644 --- a/couchpotato/core/plugins/profile/static/profile.css +++ b/couchpotato/core/plugins/profile/static/profile.css @@ -1,5 +1,3 @@ -/* @override http://192.168.1.20:5000/static/profile_plugin/profile.css */ - .add_new_profile { padding: 20px; display: block; diff --git a/couchpotato/core/plugins/profile/static/profile.js b/couchpotato/core/plugins/profile/static/profile.js index 7a6571e1..470fda8f 100644 --- a/couchpotato/core/plugins/profile/static/profile.js +++ b/couchpotato/core/plugins/profile/static/profile.js @@ -156,7 +156,7 @@ var Profile = new Class({ 'class': 'delete', 'events': { 'click': function(e){ - (e).stop(); + (e).preventDefault(); Api.request('profile.delete', { 'data': { 'id': self.data.id diff --git a/couchpotato/core/plugins/quality/static/quality.css b/couchpotato/core/plugins/quality/static/quality.css index e2081738..a66fefc4 100644 --- a/couchpotato/core/plugins/quality/static/quality.css +++ b/couchpotato/core/plugins/quality/static/quality.css @@ -1,5 +1,3 @@ -/* @override http://127.0.0.1:5000/static/quality_plugin/quality.css */ - .group_sizes { } diff --git a/couchpotato/core/plugins/wizard/static/wizard.css b/couchpotato/core/plugins/wizard/static/wizard.css index 8197b58a..3b19cf19 100644 --- a/couchpotato/core/plugins/wizard/static/wizard.css +++ b/couchpotato/core/plugins/wizard/static/wizard.css @@ -1,5 +1,3 @@ -/* @override http://127.0.0.1:5000/static/wizard/wizard.css */ - .page.wizard h1 { padding: 10px 30px; margin: 0; diff --git a/couchpotato/core/plugins/wizard/static/wizard.js b/couchpotato/core/plugins/wizard/static/wizard.js index c4edd0d5..e1e07a87 100644 --- a/couchpotato/core/plugins/wizard/static/wizard.js +++ b/couchpotato/core/plugins/wizard/static/wizard.js @@ -35,7 +35,7 @@ Page.Wizard = new Class({ 'text': 'I\'m ready to start the awesomeness, wow this button is big and green!', 'events': { 'click': function(e){ - (e).stop(); + (e).preventDefault(); Api.request('settings.save', { 'data': { 'section': 'core', diff --git a/couchpotato/core/settings/model.py b/couchpotato/core/settings/model.py index 886b3584..92d3ecdb 100644 --- a/couchpotato/core/settings/model.py +++ b/couchpotato/core/settings/model.py @@ -7,6 +7,7 @@ from libs.elixir.relationships import OneToOne from sqlalchemy.types import Integer, Unicode, UnicodeText, Boolean, Float, \ String, TypeDecorator import json +import time options_defaults["shortnames"] = True @@ -33,7 +34,7 @@ class Movie(Entity): The files belonging to the movie object are global for the whole movie such as trailers, nfo, thumbnails""" - last_edit = Field(Integer) + last_edit = Field(Integer, default = lambda: int(time.time())) library = ManyToOne('Library') status = ManyToOne('Status') @@ -191,8 +192,8 @@ class History(Entity): """History of actions that are connected to a certain release, such as, renamed to, downloaded, deleted, download subtitles etc""" - added = Field(Integer) - message = Field(UnicodeText()) + added = Field(Integer, default = lambda: int(time.time())) + message = Field(UnicodeText) type = Field(Unicode(50)) release = ManyToOne('Release') @@ -207,6 +208,15 @@ class RenameHistory(Entity): file = ManyToOne('File') +class Notification(Entity): + using_options(order_by = 'added') + + added = Field(Integer, default = lambda: int(time.time())) + read = Field(Boolean, default = False) + message = Field(Unicode(255)) + data = Field(JsonType) + + class Folder(Entity): """Renamer destination folders.""" diff --git a/couchpotato/static/images/sprite.png b/couchpotato/static/images/sprite.png index 8a61b25e..6af04a7e 100644 Binary files a/couchpotato/static/images/sprite.png and b/couchpotato/static/images/sprite.png differ diff --git a/couchpotato/static/scripts/block/menu.js b/couchpotato/static/scripts/block/menu.js new file mode 100644 index 00000000..f8ca6f11 --- /dev/null +++ b/couchpotato/static/scripts/block/menu.js @@ -0,0 +1,43 @@ +Block.Menu = new Class({ + + Extends: BlockBase, + + options: { + 'class': 'menu' + }, + + create: function(){ + var self = this; + + self.el = new Element('div', { + 'class': 'more_menu '+self.options['class'] + }).adopt( + self.more_option_ul = new Element('ul'), + new Element('a.button.onlay', { + 'events': { + 'click': function(){ + self.el.toggleClass('show') + self.fireEvent(self.el.hasClass('show') ? 'open' : 'close') + + if(self.el.hasClass('show')) + this.addEvent('outerClick', function(){ + self.el.removeClass('show') + this.removeEvents('outerClick'); + }) + else + this.removeEvents('outerClick'); + + } + } + }) + ) + + }, + + addLink: function(tab, position){ + var self = this; + var el = new Element('li').adopt(tab).inject(self.more_option_ul, position || 'bottom'); + return el; + } + +}); \ No newline at end of file diff --git a/couchpotato/static/scripts/block/more.js b/couchpotato/static/scripts/block/more.js deleted file mode 100644 index c9cd11b1..00000000 --- a/couchpotato/static/scripts/block/more.js +++ /dev/null @@ -1,54 +0,0 @@ -Block.More = new Class({ - - Extends: BlockBase, - - create: function(){ - var self = this; - - self.el = new Element('div.more_menu').adopt( - self.more_option_ul = new Element('ul').adopt( - new Element('li').adopt( - new Element('a.orange', { - 'text': 'Restart', - 'events': { - 'click': App.restart.bind(App) - } - }) - ), - new Element('li').adopt( - new Element('a.red', { - 'text': 'Shutdown', - 'events': { - 'click': App.shutdown.bind(App) - } - }) - ) - ), - new Element('a.button.onlay', { - 'events': { - 'click': function(){ - self.el.toggleClass('show') - - if(self.el.hasClass('show')) - this.addEvent('outerClick', function(){ - self.el.removeClass('show') - this.removeEvents('outerClick'); - }) - else - this.removeEvents('outerClick'); - - } - } - }) - ) - - }, - - addLink: function(tab, position){ - var self = this - - return new Element('li').adopt(tab).inject(self.more_option_ul, position || 'bottom') - - } - -}); \ No newline at end of file diff --git a/couchpotato/static/scripts/couchpotato.js b/couchpotato/static/scripts/couchpotato.js index c0f6a6a0..e4263884 100644 --- a/couchpotato/static/scripts/couchpotato.js +++ b/couchpotato/static/scripts/couchpotato.js @@ -43,7 +43,7 @@ var CouchPotato = new Class({ pushState: function(e){ var self = this; if((!e.meta && Browser.Platform.mac) || (!e.control && !Browser.Platform.mac)){ - (e).stop(); + (e).preventDefault(); var url = e.target.get('href'); if(History.getPath() != url) History.push(url); @@ -60,19 +60,34 @@ var CouchPotato = new Class({ new Element('div').adopt( self.block.navigation = new Block.Navigation(self, {}), self.block.search = new Block.Search(self, {}), - self.block.more = new Block.More(self, {}) + self.block.more = new Block.Menu(self, {}) ) ), self.content = new Element('div.content'), self.block.footer = new Block.Footer(self, {}) ); - self.block.more.addLink(new Element('a', { + [new Element('a.orange', { + 'text': 'Restart', + 'events': { + 'click': App.restart.bind(App) + } + }), + new Element('a.red', { + 'text': 'Shutdown', + 'events': { + 'click': App.shutdown.bind(App) + } + }), + new Element('a', { 'text': 'Check for updates', 'events': { 'click': self.checkForUpdate.bind(self) } - })) + })].each(function(a){ + self.block.more.addLink(a) + }) + new ScrollSpy({ min: 10, @@ -207,6 +222,13 @@ var CouchPotato = new Class({ createUrl: function(action, params){ return this.options.base_url + (action ? action+'/' : '') + (params ? '?'+Object.toQueryString(params) : '') + }, + + notify: function(options){ + return this.growl.notify({ + title: "this scrolls away", + text: "test - hello there. mouseover to pause away action" + }); } }); diff --git a/couchpotato/static/scripts/library/mootools_more.js b/couchpotato/static/scripts/library/mootools_more.js index 4ad1700f..d2d70369 100644 --- a/couchpotato/static/scripts/library/mootools_more.js +++ b/couchpotato/static/scripts/library/mootools_more.js @@ -1,6 +1,6 @@ // MooTools: the javascript framework. -// Load this file's selection again by visiting: http://mootools.net/more/d7fedf16aa88d2a757ac66071dd56b3a -// Or build this file again with packager using: packager build More/Events.Pseudos More/Element.Forms More/Element.Position More/Element.Shortcuts More/Fx.Scroll More/Fx.Slide More/Sortables More/Request.JSONP More/Request.Periodical +// Load this file's selection again by visiting: http://mootools.net/more/43db227db7a621ebb062ee621432ae3d +// Or build this file again with packager using: packager build More/Events.Pseudos More/Date More/Date.Extras More/Element.Forms More/Element.Position More/Element.Shortcuts More/Fx.Scroll More/Fx.Slide More/Sortables More/Request.JSONP More/Request.Periodical /* --- @@ -195,6 +195,993 @@ Events.implement(Events.Pseudos(pseudos, proto.addEvent, proto.removeEvent)); })(); +/* +--- + +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()); + +})(); + + +/* +--- + +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'); + + /* --- diff --git a/couchpotato/static/scripts/page/settings.js b/couchpotato/static/scripts/page/settings.js index 0a15efe6..442d61af 100644 --- a/couchpotato/static/scripts/page/settings.js +++ b/couchpotato/static/scripts/page/settings.js @@ -628,7 +628,7 @@ Option.Directory = new Class({ self.dir_list = new Element('ul', { 'events': { 'click:relay(li)': function(e, el){ - (e).stop(); + (e).preventDefault(); self.selectDirectory(el.get('data-value')) }, 'mousewheel': function(e){ @@ -678,7 +678,7 @@ Option.Directory = new Class({ hideBrowser: function(e, save){ var self = this; - (e).stop(); + (e).preventDefault(); if(save) self.save() @@ -1241,7 +1241,7 @@ Option.Combined = new Class({ deleteCombinedItem: function(e){ var self = this; - (e).stop(); + (e).preventDefault(); var item = e.target.getParent(); diff --git a/couchpotato/static/scripts/page/wanted.js b/couchpotato/static/scripts/page/wanted.js index 9bf5e48a..8d1dd931 100644 --- a/couchpotato/static/scripts/page/wanted.js +++ b/couchpotato/static/scripts/page/wanted.js @@ -49,7 +49,7 @@ window.addEvent('domready', function(){ editMovie: function(e){ var self = this; - (e).stop(); + (e).preventDefault(); if(!self.options_container){ self.options_container = new Element('div.options').adopt( @@ -89,7 +89,7 @@ window.addEvent('domready', function(){ }, save: function(e){ - (e).stop(); + (e).preventDefault(); var self = this; Api.request('movie.edit', { @@ -129,7 +129,7 @@ window.addEvent('domready', function(){ doRefresh: function(e){ var self = this; - (e).stop(); + (e).preventDefault(); Api.request('movie.refresh', { 'data': { @@ -160,7 +160,7 @@ window.addEvent('domready', function(){ showConfirm: function(e){ var self = this; - (e).stop(); + (e).preventDefault(); if(!self.delete_container){ self.delete_container = new Element('div.delete_container').adopt( @@ -188,13 +188,13 @@ window.addEvent('domready', function(){ hideConfirm: function(e){ var self = this; - (e).stop(); + (e).preventDefault(); self.movie.slide('out'); }, del: function(e){ - (e).stop(); + (e).preventDefault(); var self = this; var movie = $(self.movie); @@ -253,7 +253,7 @@ window.addEvent('domready', function(){ showFiles: function(e){ var self = this; - (e).stop(); + (e).preventDefault(); if(!self.options_container){ self.options_container = new Element('div.options').adopt( diff --git a/couchpotato/static/style/api.css b/couchpotato/static/style/api.css index afba2b5b..c6354098 100644 --- a/couchpotato/static/style/api.css +++ b/couchpotato/static/style/api.css @@ -1,4 +1,3 @@ - html { font-size: 12px; line-height: 1.5; diff --git a/couchpotato/static/style/main.css b/couchpotato/static/style/main.css index 96058721..fd66f0eb 100644 --- a/couchpotato/static/style/main.css +++ b/couchpotato/static/style/main.css @@ -1,10 +1,3 @@ -/* @override - http://localhost:5000/static/style/main.css - http://192.168.1.20:5000/static/style/main.css - http://127.0.0.1:5000/static/style/main.css - http://127.0.0.1:5000/v2/_api_/static/style/main.css -*/ - html { color: #fff; font-size: 12px; @@ -184,6 +177,7 @@ body > .spinner, .mask{ } .header .navigation { display: inline-block; + vertical-align: middle; width: 67.2%; } .header .navigation ul { @@ -239,8 +233,7 @@ body > .spinner, .mask{ .header:hover .navigation .backtotop { color: #fff; } .header .more_menu { - float: right; - margin-top: 20px; + margin-left: 12px; } .header .more_menu ul { width: 150px; @@ -252,6 +245,51 @@ body > .spinner, .mask{ .header .more_menu .red { color: red; } .header .more_menu .orange { color: orange; } + + .badge { + position: absolute; + width: 14px; + height: 14px; + text-align: center; + line-height: 14px; + border-radius: 50%; + font-size: 8px; + margin: -5px 0 0 15px; + box-shadow: inset 0 1px 0 rgba(255,255,255,.6), 0 0 3px rgba(0,0,0,.7); + background: -webkit-gradient(linear, left bottom, left top, from(rgba(255,255,255,.3)), to(rgba(255,255,255,.1))); + background: -moz-linear-gradient(center bottom, rgba(255,255,255,.3) 0%, rgba(255,255,255,.1) 100%); + background-color: #1b79b8; + text-shadow: none; + } + + .header .notification_menu ul { + width: 300px; + margin-left: -260px; + text-align: left; + } + .header .notification_menu ul:before { + left: 296px; + } + + .header .notification_menu > a { + background-position: center -209px; + } + + .header .notification_menu li > span { + padding: 5px; + display: block; + border-bottom: 1px solid rgba(0,0,0,0.2); + } + .header .notification_menu li .added { + display: block; + font-size: 10px; + color: #aaa; + text-align: ; + } + + .header .notification_menu li .more { + text-align: center; + } .header .message.update { text-align: center; @@ -454,6 +492,10 @@ body > .spinner, .mask{ background-color: #4c5766; } + .more_menu { + display: inline-block; + vertical-align: middle; + } .more_menu > a { display: block; @@ -470,16 +512,30 @@ body > .spinner, .mask{ .more_menu ul { display: none; border: 1px solid #333; - background: rgba(0,0,0,0.8); + background: rgba(255,255,255,0.98); border-radius: 3px; padding: 4px !important; position: absolute; z-index: 9; margin: 32px 0 0 -145px; width: 185px; - box-shadow: 0 10px 20px -10px rgba(0,0,0,0.4); + box-shadow: 0 10px 10px -5px rgba(0,0,0,0.4); list-style: none; text-align: center; + color: #000; + text-shadow: none; + background-image: -webkit-gradient( + linear, + left bottom, + right top, + color-stop(0, rgb(200,200,200)), + color-stop(1, rgb(255,255,255)) + ); + background-image: -moz-linear-gradient( + left bottom, + rgb(200,200,200) 0%, + rgb(255,255,255) 100% + ); } .more_menu ul:before { @@ -488,7 +544,7 @@ body > .spinner, .mask{ position: relative; width: 0; border: 6px solid transparent; - border-bottom-color: rgba(0,0,0,0.8); + border-bottom-color: #fff; display: block; top: -16px; left: 146px; @@ -510,7 +566,7 @@ body > .spinner, .mask{ text-transform: uppercase; letter-spacing: 1px; padding: 3px 0; - color: #fff; + color: #000; } .more_menu ul li:first-child { @@ -519,8 +575,7 @@ body > .spinner, .mask{ .more_menu ul li:last-child a { border: none; - color: #fff; } .more_menu ul li a:hover { - background: rgba(255,255,255,0.1); + background: rgba(0,0,0,0.05); } \ No newline at end of file diff --git a/couchpotato/static/style/page/settings.css b/couchpotato/static/style/page/settings.css index 40ae4700..98ac0332 100644 --- a/couchpotato/static/style/page/settings.css +++ b/couchpotato/static/style/page/settings.css @@ -1,9 +1,3 @@ -/* @override - http://localhost:5000/static/style/page/settings.css - http://192.168.1.20:5000/static/style/page/settings.css - http://127.0.0.1:5000/static/style/page/settings.css -*/ - .page.settings:after { content: "."; display: block; diff --git a/couchpotato/templates/_desktop.html b/couchpotato/templates/_desktop.html index 8df06c4b..751850f7 100644 --- a/couchpotato/templates/_desktop.html +++ b/couchpotato/templates/_desktop.html @@ -30,7 +30,7 @@ - +