From e8fe9da6026fb818982311ab87ab5abfc0f013d2 Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 9 Apr 2014 16:07:59 +0200 Subject: [PATCH 001/301] Livereload css --- couchpotato/core/_base/clientscript.py | 129 +-- libs/livereload/__init__.py | 16 + libs/livereload/_compat.py | 43 + libs/livereload/handlers.py | 209 ++++ libs/livereload/livereload.js | 1055 +++++++++++++++++++ libs/livereload/server.py | 224 +++++ libs/livereload/watcher.py | 132 +++ libs/scss/__init__.py | 1617 ++++++++++++++++++++++++++++++ libs/scss/__main__.py | 3 + libs/scss/_native.py | 262 +++++ libs/scss/config.py | 36 + libs/scss/cssdefs.py | 384 +++++++ libs/scss/errors.py | 169 ++++ libs/scss/expression.py | 905 +++++++++++++++++ libs/scss/functions/__init__.py | 25 + libs/scss/functions/compass/__init__.py | 2 + libs/scss/functions/compass/gradients.py | 400 ++++++++ libs/scss/functions/compass/helpers.py | 654 ++++++++++++ libs/scss/functions/compass/images.py | 285 ++++++ libs/scss/functions/compass/layouts.py | 346 +++++++ libs/scss/functions/compass/sprites.py | 524 ++++++++++ libs/scss/functions/core.py | 784 +++++++++++++++ libs/scss/functions/extra.py | 471 +++++++++ libs/scss/functions/library.py | 81 ++ libs/scss/rule.py | 524 ++++++++++ libs/scss/scss_meta.py | 67 ++ libs/scss/selector.py | 607 +++++++++++ libs/scss/setup.py | 14 + libs/scss/src/_speedups.c | 554 ++++++++++ libs/scss/src/block_locator.c | 547 ++++++++++ libs/scss/src/block_locator.h | 52 + libs/scss/src/scanner.c | 463 +++++++++ libs/scss/src/scanner.h | 68 ++ libs/scss/src/utils.h | 98 ++ libs/scss/tool.py | 403 ++++++++ libs/scss/types.py | 1128 +++++++++++++++++++++ libs/scss/util.py | 146 +++ 37 files changed, 13370 insertions(+), 57 deletions(-) create mode 100644 libs/livereload/__init__.py create mode 100644 libs/livereload/_compat.py create mode 100644 libs/livereload/handlers.py create mode 100644 libs/livereload/livereload.js create mode 100644 libs/livereload/server.py create mode 100644 libs/livereload/watcher.py create mode 100644 libs/scss/__init__.py create mode 100644 libs/scss/__main__.py create mode 100644 libs/scss/_native.py create mode 100644 libs/scss/config.py create mode 100644 libs/scss/cssdefs.py create mode 100644 libs/scss/errors.py create mode 100644 libs/scss/expression.py create mode 100644 libs/scss/functions/__init__.py create mode 100644 libs/scss/functions/compass/__init__.py create mode 100644 libs/scss/functions/compass/gradients.py create mode 100644 libs/scss/functions/compass/helpers.py create mode 100644 libs/scss/functions/compass/images.py create mode 100644 libs/scss/functions/compass/layouts.py create mode 100644 libs/scss/functions/compass/sprites.py create mode 100644 libs/scss/functions/core.py create mode 100644 libs/scss/functions/extra.py create mode 100644 libs/scss/functions/library.py create mode 100644 libs/scss/rule.py create mode 100644 libs/scss/scss_meta.py create mode 100644 libs/scss/selector.py create mode 100644 libs/scss/setup.py create mode 100644 libs/scss/src/_speedups.c create mode 100644 libs/scss/src/block_locator.c create mode 100644 libs/scss/src/block_locator.h create mode 100644 libs/scss/src/scanner.c create mode 100644 libs/scss/src/scanner.h create mode 100644 libs/scss/src/utils.h create mode 100644 libs/scss/tool.py create mode 100644 libs/scss/types.py create mode 100644 libs/scss/util.py diff --git a/couchpotato/core/_base/clientscript.py b/couchpotato/core/_base/clientscript.py index e5dbd8f..58d1ffd 100644 --- a/couchpotato/core/_base/clientscript.py +++ b/couchpotato/core/_base/clientscript.py @@ -1,15 +1,14 @@ import os import re -import traceback +import time -from couchpotato.core.event import addEvent +from couchpotato.core.event import addEvent, fireEvent from couchpotato.core.helpers.encoding import ss from couchpotato.core.helpers.variable import tryInt from couchpotato.core.logger import CPLog from couchpotato.core.plugins.base import Plugin from couchpotato.environment import Env -from minify.cssmin import cssmin -from minify.jsmin import jsmin +from scss import Scss from tornado.web import StaticFileHandler @@ -22,7 +21,7 @@ class ClientScript(Plugin): core_static = { 'style': [ - 'style/main.css', + 'style/main.scss', 'style/uniform.generic.css', 'style/uniform.css', 'style/settings.css', @@ -54,8 +53,8 @@ class ClientScript(Plugin): ], } - urls = {'style': {}, 'script': {}} - minified = {'style': {}, 'script': {}} + watcher = None + paths = {'style': {}, 'script': {}} comment = { 'style': '/*** %s:%d ***/\n', @@ -74,11 +73,26 @@ class ClientScript(Plugin): addEvent('clientscript.get_styles', self.getStyles) addEvent('clientscript.get_scripts', self.getScripts) - if not Env.get('dev'): - addEvent('app.load', self.minify) + addEvent('app.load', self.livereload, priority = 1) + addEvent('app.load', self.compile) self.addCore() + def livereload(self): + + if Env.get('dev'): + from livereload import Server + from livereload.watcher import Watcher + + self.livereload_server = Server() + self.livereload_server.watch('%s/minified/*.css' % Env.get('cache_dir')) + self.livereload_server.watch('%s/*.css' % os.path.join(Env.get('app_dir'), 'couchpotato', 'static', 'style')) + + self.watcher = Watcher() + fireEvent('schedule.interval', 'livereload.watcher', self.watcher.examine, seconds = .5) + + self.livereload_server.serve(port = 35729) + def addCore(self): for static_type in self.core_static: @@ -91,7 +105,7 @@ class ClientScript(Plugin): else: self.registerStyle(core_url, file_path, position = 'front') - def minify(self): + def compile(self): # Create cache dir cache = Env.get('cache_dir') @@ -105,67 +119,72 @@ class ClientScript(Plugin): positions = self.paths.get(file_type, {}) for position in positions: files = positions.get(position) - self._minify(file_type, files, position, position + '.' + ext) + self._compile(file_type, files, position, position + '.' + ext) - def _minify(self, file_type, files, position, out): + def _compile(self, file_type, paths, position, out): cache = Env.get('cache_dir') out_name = out - out = os.path.join(cache, 'minified', out_name) + minified_dir = os.path.join(cache, 'minified') + + data_combined = '' raw = [] - for file_path in files: + for file_path in paths: + f = open(file_path, 'r').read() - if file_type == 'script': - data = jsmin(f) - else: - data = self.prefix(f) - data = cssmin(data) - data = data.replace('../images/', '../static/images/') - data = data.replace('../fonts/', '../static/fonts/') - data = data.replace('../../static/', '../static/') # Replace inside plugins + # Compile scss + if file_path[-5:] == '.scss': - raw.append({'file': file_path, 'date': int(os.path.getmtime(file_path)), 'data': data}) + # Compile to css + compiler = Scss(live_errors = True, search_paths = [os.path.dirname(file_path)]) + f = compiler.compile(f) - # Combine all files together with some comments - data = '' - for r in raw: - data += self.comment.get(file_type) % (ss(r.get('file')), r.get('date')) - data += r.get('data') + '\n\n' + # Reload watcher + if Env.get('dev'): + self.watcher.watch(file_path, self.compile) - self.createFile(out, data.strip()) + url_path = paths[file_path].get('original_url') + compiled_file_name = position + '_%s.css' % url_path.replace('/', '_').split('.scss')[0] + compiled_file_path = os.path.join(minified_dir, compiled_file_name) + self.createFile(compiled_file_path, f.strip()) - if not self.minified.get(file_type): - self.minified[file_type] = {} - if not self.minified[file_type].get(position): - self.minified[file_type][position] = [] + # Remove scss path + paths[file_path]['url'] = 'minified/%s?%s' % (compiled_file_name, tryInt(time.time())) - minified_url = 'minified/%s?%s' % (out_name, tryInt(os.path.getmtime(out))) - self.minified[file_type][position].append(minified_url) + if not Env.get('dev'): - def getStyles(self, *args, **kwargs): - return self.get('style', *args, **kwargs) + if file_type == 'script': + data = f + else: + data = self.prefix(f) + data = data.replace('../images/', '../static/images/') + data = data.replace('../fonts/', '../static/fonts/') + data = data.replace('../../static/', '../static/') # Replace inside plugins - def getScripts(self, *args, **kwargs): - return self.get('script', *args, **kwargs) + data_combined += self.comment.get(file_type) % (ss(file_path), int(os.path.getmtime(file_path))) + data_combined += data + '\n\n' + + del paths[file_path] + + # Combine all files together with some comments + if not Env.get('dev'): - def get(self, type, as_html = False, location = 'head'): + self.createFile(os.path.join(minified_dir, out_name), data_combined.strip()) - data = '' if as_html else [] + minified_url = 'minified/%s?%s' % (out_name, tryInt(os.path.getmtime(out))) + self.minified[file_type][position].append(minified_url) - try: - try: - if not Env.get('dev'): - return self.minified[type][location] - except: - pass + def getStyles(self, *args, **kwargs): + return self.get('style', *args, **kwargs) - return self.urls[type][location] - except: - log.error('Error getting minified %s, %s: %s', (type, location, traceback.format_exc())) + def getScripts(self, *args, **kwargs): + return self.get('script', *args, **kwargs) - return data + def get(self, type, location = 'head'): + paths = self.paths[type][location] + return [paths[x].get('url', paths[x].get('original_url')) for x in paths] def registerStyle(self, api_path, file_path, position = 'head'): self.register(api_path, file_path, 'style', position) @@ -177,13 +196,9 @@ class ClientScript(Plugin): api_path = '%s?%s' % (api_path, tryInt(os.path.getmtime(file_path))) - if not self.urls[type].get(location): - self.urls[type][location] = [] - self.urls[type][location].append(api_path) - if not self.paths[type].get(location): - self.paths[type][location] = [] - self.paths[type][location].append(file_path) + self.paths[type][location] = {} + self.paths[type][location][file_path] = {'original_url': api_path} prefix_properties = ['border-radius', 'transform', 'transition', 'box-shadow'] prefix_tags = ['ms', 'moz', 'webkit'] diff --git a/libs/livereload/__init__.py b/libs/livereload/__init__.py new file mode 100644 index 0000000..f40c427 --- /dev/null +++ b/libs/livereload/__init__.py @@ -0,0 +1,16 @@ +""" + livereload + ~~~~~~~~~~ + + A python version of livereload. + + :copyright: (c) 2013 by Hsiaoming Yang +""" + +__version__ = '2.2.0' +__author__ = 'Hsiaoming Yang ' +__homepage__ = 'https://github.com/lepture/python-livereload' + +from .server import Server, shell + +__all__ = ('Server', 'shell') diff --git a/libs/livereload/_compat.py b/libs/livereload/_compat.py new file mode 100644 index 0000000..284a569 --- /dev/null +++ b/libs/livereload/_compat.py @@ -0,0 +1,43 @@ +# coding: utf-8 +""" + livereload._compat + ~~~~~~~~~~~~~~~~~~ + + Compatible module for python2 and python3. + + :copyright: (c) 2013 by Hsiaoming Yang +""" + + +import sys +PY3 = sys.version_info[0] == 3 + +if PY3: + unicode_type = str + bytes_type = bytes + text_types = (str,) +else: + unicode_type = unicode + bytes_type = str + text_types = (str, unicode) + + +def to_unicode(value, encoding='utf-8'): + """Convert different types of objects to unicode.""" + if isinstance(value, unicode_type): + return value + + if isinstance(value, bytes_type): + return unicode_type(value, encoding=encoding) + + if isinstance(value, int): + return unicode_type(str(value)) + + return value + + +def to_bytes(value, encoding='utf-8'): + """Convert different types of objects to bytes.""" + if isinstance(value, bytes_type): + return value + return value.encode(encoding) diff --git a/libs/livereload/handlers.py b/libs/livereload/handlers.py new file mode 100644 index 0000000..bb86fa8 --- /dev/null +++ b/libs/livereload/handlers.py @@ -0,0 +1,209 @@ +# -*- coding: utf-8 -*- +""" + livereload.handlers + ~~~~~~~~~~~~~~~~~~~ + + HTTP and WebSocket handlers for livereload. + + :copyright: (c) 2013 by Hsiaoming Yang +""" + +import os +import time +import hashlib +import logging +import mimetypes +from tornado import ioloop +from tornado import escape +from tornado.websocket import WebSocketHandler +from tornado.web import RequestHandler +from tornado.util import ObjectDict +from ._compat import to_bytes + + +class LiveReloadHandler(WebSocketHandler): + waiters = set() + watcher = None + _last_reload_time = None + + def allow_draft76(self): + return True + + def on_close(self): + if self in LiveReloadHandler.waiters: + LiveReloadHandler.waiters.remove(self) + + def send_message(self, message): + if isinstance(message, dict): + message = escape.json_encode(message) + + try: + self.write_message(message) + except: + logging.error('Error sending message', exc_info=True) + + def poll_tasks(self): + filepath = self.watcher.examine() + if not filepath: + return + logging.info('File %s changed', filepath) + self.watch_tasks() + + def watch_tasks(self): + if time.time() - self._last_reload_time < 3: + # if you changed lot of files in one time + # it will refresh too many times + logging.info('ignore this reload action') + return + + logging.info('Reload %s waiters', len(self.waiters)) + + msg = { + 'command': 'reload', + 'path': self.watcher.filepath or '*', + 'liveCSS': True + } + + self._last_reload_time = time.time() + for waiter in LiveReloadHandler.waiters: + try: + waiter.write_message(msg) + except: + logging.error('Error sending message', exc_info=True) + LiveReloadHandler.waiters.remove(waiter) + + def on_message(self, message): + """Handshake with livereload.js + + 1. client send 'hello' + 2. server reply 'hello' + 3. client send 'info' + + http://feedback.livereload.com/knowledgebase/articles/86174-livereload-protocol + """ + message = ObjectDict(escape.json_decode(message)) + if message.command == 'hello': + handshake = {} + handshake['command'] = 'hello' + handshake['protocols'] = [ + 'http://livereload.com/protocols/official-7', + 'http://livereload.com/protocols/official-8', + 'http://livereload.com/protocols/official-9', + 'http://livereload.com/protocols/2.x-origin-version-negotiation', + 'http://livereload.com/protocols/2.x-remote-control' + ] + handshake['serverName'] = 'livereload-tornado' + self.send_message(handshake) + + if message.command == 'info' and 'url' in message: + logging.info('Browser Connected: %s' % message.url) + LiveReloadHandler.waiters.add(self) + + if not LiveReloadHandler._last_reload_time: + if not self.watcher._tasks: + logging.info('Watch current working directory') + self.watcher.watch(os.getcwd()) + + LiveReloadHandler._last_reload_time = time.time() + logging.info('Start watching changes') + if not self.watcher.start(self.poll_tasks): + ioloop.PeriodicCallback(self.poll_tasks, 800).start() + + +class LiveReloadJSHandler(RequestHandler): + def initialize(self, port): + self._port = port + + def get(self): + js = os.path.join( + os.path.abspath(os.path.dirname(__file__)), 'livereload.js', + ) + self.set_header('Content-Type', 'application/javascript') + with open(js, 'r') as f: + content = f.read() + content = content.replace('{{port}}', str(self._port)) + self.write(content) + + +class ForceReloadHandler(RequestHandler): + def get(self): + msg = { + 'command': 'reload', + 'path': self.get_argument('path', default=None) or '*', + 'liveCSS': True, + 'liveImg': True + } + for waiter in LiveReloadHandler.waiters: + try: + waiter.write_message(msg) + except: + logging.error('Error sending message', exc_info=True) + LiveReloadHandler.waiters.remove(waiter) + self.write('ok') + + +class StaticHandler(RequestHandler): + def initialize(self, root, fallback=None): + self._root = os.path.abspath(root) + self._fallback = fallback + + def filepath(self, url): + url = url.lstrip('/') + url = os.path.join(self._root, url) + + if url.endswith('/'): + url += 'index.html' + elif not os.path.exists(url) and not url.endswith('.html'): + url += '.html' + + if not os.path.isfile(url): + return None + return url + + def get(self, path='/'): + filepath = self.filepath(path) + if not filepath and path.endswith('/'): + rootdir = os.path.join(self._root, path.lstrip('/')) + return self.create_index(rootdir) + + if not filepath: + if self._fallback: + self._fallback(self.request) + self._finished = True + return + return self.send_error(404) + + mime_type, encoding = mimetypes.guess_type(filepath) + if not mime_type: + mime_type = 'text/html' + + self.mime_type = mime_type + self.set_header('Content-Type', mime_type) + + with open(filepath, 'r') as f: + data = f.read() + + hasher = hashlib.sha1() + hasher.update(to_bytes(data)) + self.set_header('Etag', '"%s"' % hasher.hexdigest()) + + ua = self.request.headers.get('User-Agent', 'bot').lower() + if mime_type == 'text/html' and 'msie' not in ua: + data = data.replace( + '', + '' + ) + self.write(data) + + def create_index(self, root): + files = os.listdir(root) + self.write('') diff --git a/libs/livereload/livereload.js b/libs/livereload/livereload.js new file mode 100644 index 0000000..491ddcc --- /dev/null +++ b/libs/livereload/livereload.js @@ -0,0 +1,1055 @@ +(function() { +var __customevents = {}, __protocol = {}, __connector = {}, __timer = {}, __options = {}, __reloader = {}, __livereload = {}, __less = {}, __startup = {}; + +// customevents +var CustomEvents; +CustomEvents = { + bind: function(element, eventName, handler) { + if (element.addEventListener) { + return element.addEventListener(eventName, handler, false); + } else if (element.attachEvent) { + element[eventName] = 1; + return element.attachEvent('onpropertychange', function(event) { + if (event.propertyName === eventName) { + return handler(); + } + }); + } else { + throw new Error("Attempt to attach custom event " + eventName + " to something which isn't a DOMElement"); + } + }, + fire: function(element, eventName) { + var event; + if (element.addEventListener) { + event = document.createEvent('HTMLEvents'); + event.initEvent(eventName, true, true); + return document.dispatchEvent(event); + } else if (element.attachEvent) { + if (element[eventName]) { + return element[eventName]++; + } + } else { + throw new Error("Attempt to fire custom event " + eventName + " on something which isn't a DOMElement"); + } + } +}; +__customevents.bind = CustomEvents.bind; +__customevents.fire = CustomEvents.fire; + +// protocol +var PROTOCOL_6, PROTOCOL_7, Parser, ProtocolError; +var __indexOf = Array.prototype.indexOf || function(item) { + for (var i = 0, l = this.length; i < l; i++) { + if (this[i] === item) return i; + } + return -1; +}; +__protocol.PROTOCOL_6 = PROTOCOL_6 = 'http://livereload.com/protocols/official-6'; +__protocol.PROTOCOL_7 = PROTOCOL_7 = 'http://livereload.com/protocols/official-7'; +__protocol.ProtocolError = ProtocolError = (function() { + function ProtocolError(reason, data) { + this.message = "LiveReload protocol error (" + reason + ") after receiving data: \"" + data + "\"."; + } + return ProtocolError; +})(); +__protocol.Parser = Parser = (function() { + function Parser(handlers) { + this.handlers = handlers; + this.reset(); + } + Parser.prototype.reset = function() { + return this.protocol = null; + }; + Parser.prototype.process = function(data) { + var command, message, options, _ref; + try { + if (!(this.protocol != null)) { + if (data.match(/^!!ver:([\d.]+)$/)) { + this.protocol = 6; + } else if (message = this._parseMessage(data, ['hello'])) { + if (!message.protocols.length) { + throw new ProtocolError("no protocols specified in handshake message"); + } else if (__indexOf.call(message.protocols, PROTOCOL_7) >= 0) { + this.protocol = 7; + } else if (__indexOf.call(message.protocols, PROTOCOL_6) >= 0) { + this.protocol = 6; + } else { + throw new ProtocolError("no supported protocols found"); + } + } + return this.handlers.connected(this.protocol); + } else if (this.protocol === 6) { + message = JSON.parse(data); + if (!message.length) { + throw new ProtocolError("protocol 6 messages must be arrays"); + } + command = message[0], options = message[1]; + if (command !== 'refresh') { + throw new ProtocolError("unknown protocol 6 command"); + } + return this.handlers.message({ + command: 'reload', + path: options.path, + liveCSS: (_ref = options.apply_css_live) != null ? _ref : true + }); + } else { + message = this._parseMessage(data, ['reload', 'alert']); + return this.handlers.message(message); + } + } catch (e) { + if (e instanceof ProtocolError) { + return this.handlers.error(e); + } else { + throw e; + } + } + }; + Parser.prototype._parseMessage = function(data, validCommands) { + var message, _ref; + try { + message = JSON.parse(data); + } catch (e) { + throw new ProtocolError('unparsable JSON', data); + } + if (!message.command) { + throw new ProtocolError('missing "command" key', data); + } + if (_ref = message.command, __indexOf.call(validCommands, _ref) < 0) { + throw new ProtocolError("invalid command '" + message.command + "', only valid commands are: " + (validCommands.join(', ')) + ")", data); + } + return message; + }; + return Parser; +})(); + +// connector +// Generated by CoffeeScript 1.3.3 +var Connector, PROTOCOL_6, PROTOCOL_7, Parser, Version, _ref; + +_ref = __protocol, Parser = _ref.Parser, PROTOCOL_6 = _ref.PROTOCOL_6, PROTOCOL_7 = _ref.PROTOCOL_7; + +Version = '2.0.8'; + +__connector.Connector = Connector = (function() { + + function Connector(options, WebSocket, Timer, handlers) { + var _this = this; + this.options = options; + this.WebSocket = WebSocket; + this.Timer = Timer; + this.handlers = handlers; + this._uri = "ws://" + this.options.host + ":" + this.options.port + "/livereload"; + this._nextDelay = this.options.mindelay; + this._connectionDesired = false; + this.protocol = 0; + this.protocolParser = new Parser({ + connected: function(protocol) { + _this.protocol = protocol; + _this._handshakeTimeout.stop(); + _this._nextDelay = _this.options.mindelay; + _this._disconnectionReason = 'broken'; + return _this.handlers.connected(protocol); + }, + error: function(e) { + _this.handlers.error(e); + return _this._closeOnError(); + }, + message: function(message) { + return _this.handlers.message(message); + } + }); + this._handshakeTimeout = new Timer(function() { + if (!_this._isSocketConnected()) { + return; + } + _this._disconnectionReason = 'handshake-timeout'; + return _this.socket.close(); + }); + this._reconnectTimer = new Timer(function() { + if (!_this._connectionDesired) { + return; + } + return _this.connect(); + }); + this.connect(); + } + + Connector.prototype._isSocketConnected = function() { + return this.socket && this.socket.readyState === this.WebSocket.OPEN; + }; + + Connector.prototype.connect = function() { + var _this = this; + this._connectionDesired = true; + if (this._isSocketConnected()) { + return; + } + this._reconnectTimer.stop(); + this._disconnectionReason = 'cannot-connect'; + this.protocolParser.reset(); + this.handlers.connecting(); + this.socket = new this.WebSocket(this._uri); + this.socket.onopen = function(e) { + return _this._onopen(e); + }; + this.socket.onclose = function(e) { + return _this._onclose(e); + }; + this.socket.onmessage = function(e) { + return _this._onmessage(e); + }; + return this.socket.onerror = function(e) { + return _this._onerror(e); + }; + }; + + Connector.prototype.disconnect = function() { + this._connectionDesired = false; + this._reconnectTimer.stop(); + if (!this._isSocketConnected()) { + return; + } + this._disconnectionReason = 'manual'; + return this.socket.close(); + }; + + Connector.prototype._scheduleReconnection = function() { + if (!this._connectionDesired) { + return; + } + if (!this._reconnectTimer.running) { + this._reconnectTimer.start(this._nextDelay); + return this._nextDelay = Math.min(this.options.maxdelay, this._nextDelay * 2); + } + }; + + Connector.prototype.sendCommand = function(command) { + if (this.protocol == null) { + return; + } + return this._sendCommand(command); + }; + + Connector.prototype._sendCommand = function(command) { + return this.socket.send(JSON.stringify(command)); + }; + + Connector.prototype._closeOnError = function() { + this._handshakeTimeout.stop(); + this._disconnectionReason = 'error'; + return this.socket.close(); + }; + + Connector.prototype._onopen = function(e) { + var hello; + this.handlers.socketConnected(); + this._disconnectionReason = 'handshake-failed'; + hello = { + command: 'hello', + protocols: [PROTOCOL_6, PROTOCOL_7] + }; + hello.ver = Version; + if (this.options.ext) { + hello.ext = this.options.ext; + } + if (this.options.extver) { + hello.extver = this.options.extver; + } + if (this.options.snipver) { + hello.snipver = this.options.snipver; + } + this._sendCommand(hello); + return this._handshakeTimeout.start(this.options.handshake_timeout); + }; + + Connector.prototype._onclose = function(e) { + this.protocol = 0; + this.handlers.disconnected(this._disconnectionReason, this._nextDelay); + return this._scheduleReconnection(); + }; + + Connector.prototype._onerror = function(e) {}; + + Connector.prototype._onmessage = function(e) { + return this.protocolParser.process(e.data); + }; + + return Connector; + +})(); + +// timer +var Timer; +var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; +__timer.Timer = Timer = (function() { + function Timer(func) { + this.func = func; + this.running = false; + this.id = null; + this._handler = __bind(function() { + this.running = false; + this.id = null; + return this.func(); + }, this); + } + Timer.prototype.start = function(timeout) { + if (this.running) { + clearTimeout(this.id); + } + this.id = setTimeout(this._handler, timeout); + return this.running = true; + }; + Timer.prototype.stop = function() { + if (this.running) { + clearTimeout(this.id); + this.running = false; + return this.id = null; + } + }; + return Timer; +})(); +Timer.start = function(timeout, func) { + return setTimeout(func, timeout); +}; + +// options +var Options; +__options.Options = Options = (function() { + function Options() { + this.host = null; + this.port = {{port}}; + this.snipver = null; + this.ext = null; + this.extver = null; + this.mindelay = 1000; + this.maxdelay = 60000; + this.handshake_timeout = 5000; + } + Options.prototype.set = function(name, value) { + switch (typeof this[name]) { + case 'undefined': + break; + case 'number': + return this[name] = +value; + default: + return this[name] = value; + } + }; + return Options; +})(); +Options.extract = function(document) { + var element, keyAndValue, m, mm, options, pair, src, _i, _j, _len, _len2, _ref, _ref2; + _ref = document.getElementsByTagName('script'); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + element = _ref[_i]; + if ((src = element.src) && (m = src.match(/^[^:]+:\/\/(.*)\/z?livereload\.js(?:\?(.*))?$/))) { + options = new Options(); + if (mm = m[1].match(/^([^\/:]+)(?::(\d+))?$/)) { + options.host = mm[1]; + if (mm[2]) { + options.port = parseInt(mm[2], 10); + } + } + if (m[2]) { + _ref2 = m[2].split('&'); + for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) { + pair = _ref2[_j]; + if ((keyAndValue = pair.split('=')).length > 1) { + options.set(keyAndValue[0].replace(/-/g, '_'), keyAndValue.slice(1).join('=')); + } + } + } + return options; + } + } + return null; +}; + +// reloader +// Generated by CoffeeScript 1.3.1 +(function() { + var IMAGE_STYLES, Reloader, numberOfMatchingSegments, pathFromUrl, pathsMatch, pickBestMatch, splitUrl; + + splitUrl = function(url) { + var hash, index, params; + if ((index = url.indexOf('#')) >= 0) { + hash = url.slice(index); + url = url.slice(0, index); + } else { + hash = ''; + } + if ((index = url.indexOf('?')) >= 0) { + params = url.slice(index); + url = url.slice(0, index); + } else { + params = ''; + } + return { + url: url, + params: params, + hash: hash + }; + }; + + pathFromUrl = function(url) { + var path; + url = splitUrl(url).url; + if (url.indexOf('file://') === 0) { + path = url.replace(/^file:\/\/(localhost)?/, ''); + } else { + path = url.replace(/^([^:]+:)?\/\/([^:\/]+)(:\d*)?\//, '/'); + } + return decodeURIComponent(path); + }; + + pickBestMatch = function(path, objects, pathFunc) { + var bestMatch, object, score, _i, _len; + bestMatch = { + score: 0 + }; + for (_i = 0, _len = objects.length; _i < _len; _i++) { + object = objects[_i]; + score = numberOfMatchingSegments(path, pathFunc(object)); + if (score > bestMatch.score) { + bestMatch = { + object: object, + score: score + }; + } + } + if (bestMatch.score > 0) { + return bestMatch; + } else { + return null; + } + }; + + numberOfMatchingSegments = function(path1, path2) { + var comps1, comps2, eqCount, len; + path1 = path1.replace(/^\/+/, '').toLowerCase(); + path2 = path2.replace(/^\/+/, '').toLowerCase(); + if (path1 === path2) { + return 10000; + } + comps1 = path1.split('/').reverse(); + comps2 = path2.split('/').reverse(); + len = Math.min(comps1.length, comps2.length); + eqCount = 0; + while (eqCount < len && comps1[eqCount] === comps2[eqCount]) { + ++eqCount; + } + return eqCount; + }; + + pathsMatch = function(path1, path2) { + return numberOfMatchingSegments(path1, path2) > 0; + }; + + IMAGE_STYLES = [ + { + selector: 'background', + styleNames: ['backgroundImage'] + }, { + selector: 'border', + styleNames: ['borderImage', 'webkitBorderImage', 'MozBorderImage'] + } + ]; + + __reloader.Reloader = Reloader = (function() { + + Reloader.name = 'Reloader'; + + function Reloader(window, console, Timer) { + this.window = window; + this.console = console; + this.Timer = Timer; + this.document = this.window.document; + this.importCacheWaitPeriod = 200; + this.plugins = []; + } + + Reloader.prototype.addPlugin = function(plugin) { + return this.plugins.push(plugin); + }; + + Reloader.prototype.analyze = function(callback) { + return results; + }; + + Reloader.prototype.reload = function(path, options) { + var plugin, _base, _i, _len, _ref; + this.options = options; + if ((_base = this.options).stylesheetReloadTimeout == null) { + _base.stylesheetReloadTimeout = 15000; + } + _ref = this.plugins; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + plugin = _ref[_i]; + if (plugin.reload && plugin.reload(path, options)) { + return; + } + } + if (options.liveCSS) { + if (path.match(/\.css$/i)) { + if (this.reloadStylesheet(path)) { + return; + } + } + } + if (options.liveImg) { + if (path.match(/\.(jpe?g|png|gif)$/i)) { + this.reloadImages(path); + return; + } + } + return this.reloadPage(); + }; + + Reloader.prototype.reloadPage = function() { + return this.window.document.location.reload(); + }; + + Reloader.prototype.reloadImages = function(path) { + var expando, img, selector, styleNames, styleSheet, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1, _ref2, _ref3, _results; + expando = this.generateUniqueString(); + _ref = this.document.images; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + img = _ref[_i]; + if (pathsMatch(path, pathFromUrl(img.src))) { + img.src = this.generateCacheBustUrl(img.src, expando); + } + } + if (this.document.querySelectorAll) { + for (_j = 0, _len1 = IMAGE_STYLES.length; _j < _len1; _j++) { + _ref1 = IMAGE_STYLES[_j], selector = _ref1.selector, styleNames = _ref1.styleNames; + _ref2 = this.document.querySelectorAll("[style*=" + selector + "]"); + for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) { + img = _ref2[_k]; + this.reloadStyleImages(img.style, styleNames, path, expando); + } + } + } + if (this.document.styleSheets) { + _ref3 = this.document.styleSheets; + _results = []; + for (_l = 0, _len3 = _ref3.length; _l < _len3; _l++) { + styleSheet = _ref3[_l]; + _results.push(this.reloadStylesheetImages(styleSheet, path, expando)); + } + return _results; + } + }; + + Reloader.prototype.reloadStylesheetImages = function(styleSheet, path, expando) { + var rule, rules, styleNames, _i, _j, _len, _len1; + try { + rules = styleSheet != null ? styleSheet.cssRules : void 0; + } catch (e) { + + } + if (!rules) { + return; + } + for (_i = 0, _len = rules.length; _i < _len; _i++) { + rule = rules[_i]; + switch (rule.type) { + case CSSRule.IMPORT_RULE: + this.reloadStylesheetImages(rule.styleSheet, path, expando); + break; + case CSSRule.STYLE_RULE: + for (_j = 0, _len1 = IMAGE_STYLES.length; _j < _len1; _j++) { + styleNames = IMAGE_STYLES[_j].styleNames; + this.reloadStyleImages(rule.style, styleNames, path, expando); + } + break; + case CSSRule.MEDIA_RULE: + this.reloadStylesheetImages(rule, path, expando); + } + } + }; + + Reloader.prototype.reloadStyleImages = function(style, styleNames, path, expando) { + var newValue, styleName, value, _i, _len, + _this = this; + for (_i = 0, _len = styleNames.length; _i < _len; _i++) { + styleName = styleNames[_i]; + value = style[styleName]; + if (typeof value === 'string') { + newValue = value.replace(/\burl\s*\(([^)]*)\)/, function(match, src) { + if (pathsMatch(path, pathFromUrl(src))) { + return "url(" + (_this.generateCacheBustUrl(src, expando)) + ")"; + } else { + return match; + } + }); + if (newValue !== value) { + style[styleName] = newValue; + } + } + } + }; + + Reloader.prototype.reloadStylesheet = function(path) { + var imported, link, links, match, style, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1, + _this = this; + links = (function() { + var _i, _len, _ref, _results; + _ref = this.document.getElementsByTagName('link'); + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + link = _ref[_i]; + if (link.rel === 'stylesheet' && !link.__LiveReload_pendingRemoval) { + _results.push(link); + } + } + return _results; + }).call(this); + imported = []; + _ref = this.document.getElementsByTagName('style'); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + style = _ref[_i]; + if (style.sheet) { + this.collectImportedStylesheets(style, style.sheet, imported); + } + } + for (_j = 0, _len1 = links.length; _j < _len1; _j++) { + link = links[_j]; + this.collectImportedStylesheets(link, link.sheet, imported); + } + if (this.window.StyleFix && this.document.querySelectorAll) { + _ref1 = this.document.querySelectorAll('style[data-href]'); + for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) { + style = _ref1[_k]; + links.push(style); + } + } + this.console.log("LiveReload found " + links.length + " LINKed stylesheets, " + imported.length + " @imported stylesheets"); + match = pickBestMatch(path, links.concat(imported), function(l) { + return pathFromUrl(_this.linkHref(l)); + }); + if (match) { + if (match.object.rule) { + this.console.log("LiveReload is reloading imported stylesheet: " + match.object.href); + this.reattachImportedRule(match.object); + } else { + this.console.log("LiveReload is reloading stylesheet: " + (this.linkHref(match.object))); + this.reattachStylesheetLink(match.object); + } + } else { + this.console.log("LiveReload will reload all stylesheets because path '" + path + "' did not match any specific one"); + for (_l = 0, _len3 = links.length; _l < _len3; _l++) { + link = links[_l]; + this.reattachStylesheetLink(link); + } + } + return true; + }; + + Reloader.prototype.collectImportedStylesheets = function(link, styleSheet, result) { + var index, rule, rules, _i, _len; + try { + rules = styleSheet != null ? styleSheet.cssRules : void 0; + } catch (e) { + + } + if (rules && rules.length) { + for (index = _i = 0, _len = rules.length; _i < _len; index = ++_i) { + rule = rules[index]; + switch (rule.type) { + case CSSRule.CHARSET_RULE: + continue; + case CSSRule.IMPORT_RULE: + result.push({ + link: link, + rule: rule, + index: index, + href: rule.href + }); + this.collectImportedStylesheets(link, rule.styleSheet, result); + break; + default: + break; + } + } + } + }; + + Reloader.prototype.waitUntilCssLoads = function(clone, func) { + var callbackExecuted, executeCallback, poll, + _this = this; + callbackExecuted = false; + executeCallback = function() { + if (callbackExecuted) { + return; + } + callbackExecuted = true; + return func(); + }; + clone.onload = function() { + console.log("onload!"); + _this.knownToSupportCssOnLoad = true; + return executeCallback(); + }; + if (!this.knownToSupportCssOnLoad) { + (poll = function() { + if (clone.sheet) { + console.log("polling!"); + return executeCallback(); + } else { + return _this.Timer.start(50, poll); + } + })(); + } + return this.Timer.start(this.options.stylesheetReloadTimeout, executeCallback); + }; + + Reloader.prototype.linkHref = function(link) { + return link.href || link.getAttribute('data-href'); + }; + + Reloader.prototype.reattachStylesheetLink = function(link) { + var clone, parent, + _this = this; + if (link.__LiveReload_pendingRemoval) { + return; + } + link.__LiveReload_pendingRemoval = true; + if (link.tagName === 'STYLE') { + clone = this.document.createElement('link'); + clone.rel = 'stylesheet'; + clone.media = link.media; + clone.disabled = link.disabled; + } else { + clone = link.cloneNode(false); + } + clone.href = this.generateCacheBustUrl(this.linkHref(link)); + parent = link.parentNode; + if (parent.lastChild === link) { + parent.appendChild(clone); + } else { + parent.insertBefore(clone, link.nextSibling); + } + return this.waitUntilCssLoads(clone, function() { + var additionalWaitingTime; + if (/AppleWebKit/.test(navigator.userAgent)) { + additionalWaitingTime = 5; + } else { + additionalWaitingTime = 200; + } + return _this.Timer.start(additionalWaitingTime, function() { + var _ref; + if (!link.parentNode) { + return; + } + link.parentNode.removeChild(link); + clone.onreadystatechange = null; + return (_ref = _this.window.StyleFix) != null ? _ref.link(clone) : void 0; + }); + }); + }; + + Reloader.prototype.reattachImportedRule = function(_arg) { + var href, index, link, media, newRule, parent, rule, tempLink, + _this = this; + rule = _arg.rule, index = _arg.index, link = _arg.link; + parent = rule.parentStyleSheet; + href = this.generateCacheBustUrl(rule.href); + media = rule.media.length ? [].join.call(rule.media, ', ') : ''; + newRule = "@import url(\"" + href + "\") " + media + ";"; + rule.__LiveReload_newHref = href; + tempLink = this.document.createElement("link"); + tempLink.rel = 'stylesheet'; + tempLink.href = href; + tempLink.__LiveReload_pendingRemoval = true; + if (link.parentNode) { + link.parentNode.insertBefore(tempLink, link); + } + return this.Timer.start(this.importCacheWaitPeriod, function() { + if (tempLink.parentNode) { + tempLink.parentNode.removeChild(tempLink); + } + if (rule.__LiveReload_newHref !== href) { + return; + } + parent.insertRule(newRule, index); + parent.deleteRule(index + 1); + rule = parent.cssRules[index]; + rule.__LiveReload_newHref = href; + return _this.Timer.start(_this.importCacheWaitPeriod, function() { + if (rule.__LiveReload_newHref !== href) { + return; + } + parent.insertRule(newRule, index); + return parent.deleteRule(index + 1); + }); + }); + }; + + Reloader.prototype.generateUniqueString = function() { + return 'livereload=' + Date.now(); + }; + + Reloader.prototype.generateCacheBustUrl = function(url, expando) { + var hash, oldParams, params, _ref; + if (expando == null) { + expando = this.generateUniqueString(); + } + _ref = splitUrl(url), url = _ref.url, hash = _ref.hash, oldParams = _ref.params; + if (this.options.overrideURL) { + if (url.indexOf(this.options.serverURL) < 0) { + url = this.options.serverURL + this.options.overrideURL + "?url=" + encodeURIComponent(url); + } + } + params = oldParams.replace(/(\?|&)livereload=(\d+)/, function(match, sep) { + return "" + sep + expando; + }); + if (params === oldParams) { + if (oldParams.length === 0) { + params = "?" + expando; + } else { + params = "" + oldParams + "&" + expando; + } + } + return url + params + hash; + }; + + return Reloader; + + })(); + +}).call(this); + +// livereload +var Connector, LiveReload, Options, Reloader, Timer; + +Connector = __connector.Connector; + +Timer = __timer.Timer; + +Options = __options.Options; + +Reloader = __reloader.Reloader; + +__livereload.LiveReload = LiveReload = (function() { + + function LiveReload(window) { + var _this = this; + this.window = window; + this.listeners = {}; + this.plugins = []; + this.pluginIdentifiers = {}; + this.console = this.window.location.href.match(/LR-verbose/) && this.window.console && this.window.console.log && this.window.console.error ? this.window.console : { + log: function() {}, + error: function() {} + }; + if (!(this.WebSocket = this.window.WebSocket || this.window.MozWebSocket)) { + console.error("LiveReload disabled because the browser does not seem to support web sockets"); + return; + } + if (!(this.options = Options.extract(this.window.document))) { + console.error("LiveReload disabled because it could not find its own ' + ) + + if status_code != 304: + if "content-length" not in header_set: + headers.append(("Content-Length", str(len(body)))) + if "content-type" not in header_set: + headers.append(("Content-Type", "text/html; charset=UTF-8")) + if "server" not in header_set: + headers.append(("Server", "livereload-tornado")) + + parts = [escape.utf8("HTTP/1.1 " + data["status"] + "\r\n")] + for key, value in headers: + if key.lower() == 'content-length': + value = str(len(body)) + parts.append( + escape.utf8(key) + b": " + escape.utf8(value) + b"\r\n" + ) + parts.append(b"\r\n") + parts.append(body) + request.write(b"".join(parts)) + request.finish() + self._log(status_code, request) + + +class Server(object): + """Livereload server interface. + + Initialize a server and watch file changes:: + + server = Server(wsgi_app) + server.serve() + + :param app: a wsgi application instance + :param watcher: A Watcher instance, you don't have to initialize + it by yourself. Under Linux, you will want to install + pyinotify and use INotifyWatcher() to avoid wasted + CPU usage. + """ + def __init__(self, app=None, watcher=None): + self.app = app + self.port = 5500 + self.root = None + if not watcher: + watcher = Watcher() + self.watcher = watcher + + def watch(self, filepath, func=None): + """Add the given filepath for watcher list. + + Once you have intialized a server, watch file changes before + serve the server:: + + server.watch('static/*.stylus', 'make static') + def alert(): + print('foo') + server.watch('foo.txt', alert) + server.serve() + + :param filepath: files to be watched, it can be a filepath, + a directory, or a glob pattern + :param func: the function to be called, it can be a string of + shell command, or any callable object without + parameters + """ + if isinstance(func, text_types): + func = shell(func) + + self.watcher.watch(filepath, func) + + def application(self, debug=True): + LiveReloadHandler.watcher = self.watcher + handlers = [ + (r'/livereload', LiveReloadHandler), + (r'/forcereload', ForceReloadHandler), + (r'/livereload.js', LiveReloadJSHandler, dict(port=self.port)), + ] + + if self.app: + self.app = WSGIWrapper(self.app) + handlers.append( + (r'.*', FallbackHandler, dict(fallback=self.app)) + ) + else: + handlers.append( + (r'(.*)', StaticHandler, dict(root=self.root or '.')), + ) + return Application(handlers=handlers, debug=debug) + + def serve(self, port=None, host=None, root=None, debug=True, open_url=False): + """Start serve the server with the given port. + + :param port: serve on this port, default is 5500 + :param host: serve on this hostname, default is 0.0.0.0 + :param root: serve static on this root directory + :param open_url: open system browser + """ + if root: + self.root = root + if port: + self.port = port + if host is None: + host = '' + + self.application(debug=debug).listen(self.port, address=host) + logging.getLogger().setLevel(logging.INFO) + + host = host or '127.0.0.1' + print('Serving on %s:%s' % (host, self.port)) + + # Async open web browser after 5 sec timeout + if open_url: + def opener(): + time.sleep(5) + webbrowser.open('http://%s:%s' % (host, self.port)) + threading.Thread(target=opener).start() + + try: + IOLoop.instance().start() + except KeyboardInterrupt: + print('Shutting down...') diff --git a/libs/livereload/watcher.py b/libs/livereload/watcher.py new file mode 100644 index 0000000..2f15cb1 --- /dev/null +++ b/libs/livereload/watcher.py @@ -0,0 +1,132 @@ +# -*- coding: utf-8 -*- +""" + livereload.watcher + ~~~~~~~~~~~~~~~~~~ + + A file watch management for LiveReload Server. + + :copyright: (c) 2013 by Hsiaoming Yang +""" + +import os +import glob +import time + + +class Watcher(object): + """A file watcher registery.""" + def __init__(self): + self._tasks = {} + self._mtimes = {} + + # filepath that is changed + self.filepath = None + self._start = time.time() + + def ignore(self, filename): + """Ignore a given filename or not.""" + _, ext = os.path.splitext(filename) + return ext in ['.pyc', '.pyo', '.o', '.swp'] + + def watch(self, path, func=None): + """Add a task to watcher.""" + self._tasks[path] = func + + def start(self, callback): + """Start the watcher running, calling callback when changes are observed. If this returns False, + regular polling will be used.""" + return False + + def examine(self): + """Check if there are changes, if true, run the given task.""" + # clean filepath + self.filepath = None + for path in self._tasks: + if self.is_changed(path): + func = self._tasks[path] + # run function + func and func() + return self.filepath + + def is_changed(self, path): + if os.path.isfile(path): + return self.is_file_changed(path) + elif os.path.isdir(path): + return self.is_folder_changed(path) + return self.is_glob_changed(path) + + def is_file_changed(self, path): + if not os.path.isfile(path): + return False + + if self.ignore(path): + return False + + mtime = os.path.getmtime(path) + + if path not in self._mtimes: + self._mtimes[path] = mtime + self.filepath = path + return mtime > self._start + + if self._mtimes[path] != mtime: + self._mtimes[path] = mtime + self.filepath = path + return True + + self._mtimes[path] = mtime + return False + + def is_folder_changed(self, path): + for root, dirs, files in os.walk(path, followlinks=True): + if '.git' in dirs: + dirs.remove('.git') + if '.hg' in dirs: + dirs.remove('.hg') + if '.svn' in dirs: + dirs.remove('.svn') + if '.cvs' in dirs: + dirs.remove('.cvs') + + for f in files: + if self.is_file_changed(os.path.join(root, f)): + return True + return False + + def is_glob_changed(self, path): + for f in glob.glob(path): + if self.is_file_changed(f): + return True + return False + + +class INotifyWatcher(Watcher): + def __init__(self): + Watcher.__init__(self) + + import pyinotify + self.wm = pyinotify.WatchManager() + self.notifier = None + self.callback = None + + def watch(self, path, func=None): + import pyinotify + flag = pyinotify.IN_CREATE | pyinotify.IN_DELETE | pyinotify.IN_MODIFY + self.wm.add_watch(path, flag, rec=True, do_glob=True, auto_add=True) + Watcher.watch(self, path, func) + + def inotify_event(self, event): + self.callback() + + def start(self, callback): + if not self.notifier: + self.callback = callback + + import pyinotify + from tornado import ioloop + self.notifier = pyinotify.TornadoAsyncNotifier( + self.wm, ioloop.IOLoop.instance(), + default_proc_fun=self.inotify_event + ) + callback() + return True diff --git a/libs/scss/__init__.py b/libs/scss/__init__.py new file mode 100644 index 0000000..00a366f --- /dev/null +++ b/libs/scss/__init__.py @@ -0,0 +1,1617 @@ +#!/usr/bin/env python +#-*- coding: utf-8 -*- +""" +pyScss, a Scss compiler for Python + +@author German M. Bravo (Kronuz) +@version 1.2.0 alpha +@see https://github.com/Kronuz/pyScss +@copyright (c) 2012-2013 German M. Bravo (Kronuz) +@license MIT License + http://www.opensource.org/licenses/mit-license.php + +pyScss compiles Scss, a superset of CSS that is more powerful, elegant and +easier to maintain than plain-vanilla CSS. The library acts as a CSS source code +preprocesor which allows you to use variables, nested rules, mixins, andhave +inheritance of rules, all with a CSS-compatible syntax which the preprocessor +then compiles to standard CSS. + +Scss, as an extension of CSS, helps keep large stylesheets well-organized. It +borrows concepts and functionality from projects such as OOCSS and other similar +frameworks like as Sass. It's build on top of the original PHP xCSS codebase +structure but it's been completely rewritten, many bugs have been fixed and it +has been extensively extended to support almost the full range of Sass' Scss +syntax and functionality. + +Bits of code in pyScss come from various projects: +Compass: + (c) 2009 Christopher M. Eppstein + http://compass-style.org/ +Sass: + (c) 2006-2009 Hampton Catlin and Nathan Weizenbaum + http://sass-lang.com/ +xCSS: + (c) 2010 Anton Pawlik + http://xcss.antpaw.org/docs/ + +""" +from __future__ import absolute_import +from __future__ import print_function + +from scss.scss_meta import BUILD_INFO, PROJECT, VERSION, REVISION, URL, AUTHOR, AUTHOR_EMAIL, LICENSE + +__project__ = PROJECT +__version__ = VERSION +__author__ = AUTHOR + ' <' + AUTHOR_EMAIL + '>' +__license__ = LICENSE + + +from collections import defaultdict +import glob +from itertools import product +import logging +import os.path +import re +import sys + +import six + +from scss import config +from scss.cssdefs import ( + SEPARATOR, + _ml_comment_re, _sl_comment_re, + _escape_chars_re, + _spaces_re, _expand_rules_space_re, _collapse_properties_space_re, + _strings_re, _prop_split_re, +) +from scss.errors import SassError +from scss.expression import Calculator +from scss.functions import ALL_BUILTINS_LIBRARY +from scss.functions.compass.sprites import sprite_map +from scss.rule import Namespace, SassRule, UnparsedBlock +from scss.types import Boolean, List, Null, Number, String, Undefined +from scss.util import dequote, normalize_var, print_timing # profile + +log = logging.getLogger(__name__) + +################################################################################ +# Load C acceleration modules +locate_blocks = None +try: + from scss._speedups import locate_blocks +except ImportError: + import warnings + warnings.warn( + "Scanning acceleration disabled (_speedups not found)!", + RuntimeWarning + ) + from scss._native import locate_blocks + +################################################################################ + + +_safe_strings = { + '^doubleslash^': '//', + '^bigcopen^': '/*', + '^bigcclose^': '*/', + '^doubledot^': ':', + '^semicolon^': ';', + '^curlybracketopen^': '{', + '^curlybracketclosed^': '}', +} +_reverse_safe_strings = dict((v, k) for k, v in _safe_strings.items()) +_safe_strings_re = re.compile('|'.join(map(re.escape, _safe_strings))) +_reverse_safe_strings_re = re.compile('|'.join(map(re.escape, _reverse_safe_strings))) + +_default_scss_vars = { + '$BUILD-INFO': String.unquoted(BUILD_INFO), + '$PROJECT': String.unquoted(PROJECT), + '$VERSION': String.unquoted(VERSION), + '$REVISION': String.unquoted(REVISION), + '$URL': String.unquoted(URL), + '$AUTHOR': String.unquoted(AUTHOR), + '$AUTHOR-EMAIL': String.unquoted(AUTHOR_EMAIL), + '$LICENSE': String.unquoted(LICENSE), + + # unsafe chars will be hidden as vars + '$--doubleslash': String.unquoted('//'), + '$--bigcopen': String.unquoted('/*'), + '$--bigcclose': String.unquoted('*/'), + '$--doubledot': String.unquoted(':'), + '$--semicolon': String.unquoted(';'), + '$--curlybracketopen': String.unquoted('{'), + '$--curlybracketclosed': String.unquoted('}'), + + # shortcuts (it's "a hidden feature" for now) + 'bg:': String.unquoted('background:'), + 'bgc:': String.unquoted('background-color:'), +} + + +################################################################################ + + +class SourceFile(object): + def __init__(self, filename, contents, parent_dir='.', is_string=False, is_sass=None, line_numbers=True, line_strip=True): + self.filename = filename + self.sass = filename.endswith('.sass') if is_sass is None else is_sass + self.line_numbers = line_numbers + self.line_strip = line_strip + self.contents = self.prepare_source(contents) + self.parent_dir = os.path.realpath(parent_dir) + self.is_string = is_string + + def __repr__(self): + return "" % ( + self.filename, + id(self), + ) + + @classmethod + def from_filename(cls, fn, filename=None, is_sass=None, line_numbers=True): + if filename is None: + _, filename = os.path.split(fn) + + with open(fn) as f: + contents = f.read() + + return cls(filename, contents, is_sass=is_sass, line_numbers=line_numbers) + + @classmethod + def from_string(cls, string, filename=None, is_sass=None, line_numbers=True): + if filename is None: + filename = "" % string[:50] + + return cls(filename, string, is_string=True, is_sass=is_sass, line_numbers=line_numbers) + + def parse_scss_line(self, line_no, line, state): + ret = '' + + if line is None: + line = '' + + line = state['line_buffer'] + line.rstrip() # remove EOL character + + if line and line[-1] == '\\': + state['line_buffer'] = line[:-1] + return '' + else: + state['line_buffer'] = '' + + output = state['prev_line'] + if self.line_strip: + output = output.strip() + output_line_no = state['prev_line_no'] + + state['prev_line'] = line + state['prev_line_no'] = line_no + + if output: + if self.line_numbers: + output = str(output_line_no + 1) + SEPARATOR + output + output += '\n' + ret += output + + return ret + + def parse_sass_line(self, line_no, line, state): + ret = '' + + if line is None: + line = '' + + line = state['line_buffer'] + line.rstrip() # remove EOL character + + if line and line[-1] == '\\': + state['line_buffer'] = line[:-1] + return ret + else: + state['line_buffer'] = '' + + indent = len(line) - len(line.lstrip()) + + # make sure we support multi-space indent as long as indent is consistent + if indent and not state['indent_marker']: + state['indent_marker'] = indent + + if state['indent_marker']: + indent /= state['indent_marker'] + + if indent == state['prev_indent']: + # same indentation as previous line + if state['prev_line']: + state['prev_line'] += ';' + elif indent > state['prev_indent']: + # new indentation is greater than previous, we just entered a new block + state['prev_line'] += ' {' + state['nested_blocks'] += 1 + else: + # indentation is reset, we exited a block + block_diff = state['prev_indent'] - indent + if state['prev_line']: + state['prev_line'] += ';' + state['prev_line'] += ' }' * block_diff + state['nested_blocks'] -= block_diff + + output = state['prev_line'] + if self.line_strip: + output = output.strip() + output_line_no = state['prev_line_no'] + + state['prev_indent'] = indent + state['prev_line'] = line + state['prev_line_no'] = line_no + + if output: + if self.line_numbers: + output = str(output_line_no + 1) + SEPARATOR + output + output += '\n' + ret += output + return ret + + def prepare_source(self, codestr, sass=False): + # Decorate lines with their line numbers and a delimiting NUL and remove empty lines + state = { + 'line_buffer': '', + 'prev_line': '', + 'prev_line_no': 0, + 'prev_indent': 0, + 'nested_blocks': 0, + 'indent_marker': 0, + } + if self.sass: + parse_line = self.parse_sass_line + else: + parse_line = self.parse_scss_line + _codestr = codestr + codestr = '' + for line_no, line in enumerate(_codestr.splitlines()): + codestr += parse_line(line_no, line, state) + codestr += parse_line(None, None, state) # parse the last line stored in prev_line buffer + + # protects codestr: "..." strings + codestr = _strings_re.sub(lambda m: _reverse_safe_strings_re.sub(lambda n: _reverse_safe_strings[n.group(0)], m.group(0)), codestr) + + # removes multiple line comments + codestr = _ml_comment_re.sub('', codestr) + + # removes inline comments, but not :// (protocol) + codestr = _sl_comment_re.sub('', codestr) + + codestr = _safe_strings_re.sub(lambda m: _safe_strings[m.group(0)], codestr) + + # expand the space in rules + codestr = _expand_rules_space_re.sub(' {', codestr) + + # collapse the space in properties blocks + codestr = _collapse_properties_space_re.sub(r'\1{', codestr) + + return codestr + + +class Scss(object): + def __init__(self, + scss_vars=None, scss_opts=None, scss_files=None, super_selector=None, + live_errors=False, library=ALL_BUILTINS_LIBRARY, func_registry=None, search_paths=None): + + if super_selector: + self.super_selector = super_selector + ' ' + else: + self.super_selector = '' + + self._scss_vars = {} + if scss_vars: + calculator = Calculator() + for var_name, value in scss_vars.items(): + if isinstance(value, six.string_types): + scss_value = calculator.evaluate_expression(value) + if scss_value is None: + # TODO warning? + scss_value = String.unquoted(value) + else: + scss_value = value + self._scss_vars[var_name] = scss_value + + self._scss_opts = scss_opts + self._scss_files = scss_files + # NOTE: func_registry is backwards-compatibility for only one user and + # has never existed in a real release + self._library = func_registry or library + self._search_paths = search_paths + + # If true, swallow compile errors and embed them in the output instead + self.live_errors = live_errors + + self.reset() + + def get_scss_constants(self): + scss_vars = self.root_namespace.variables + return dict((k, v) for k, v in scss_vars.items() if k and (not k.startswith('$') or k.startswith('$') and k[1].isupper())) + + def get_scss_vars(self): + scss_vars = self.root_namespace.variables + return dict((k, v) for k, v in scss_vars.items() if k and not (not k.startswith('$') or k.startswith('$') and k[1].isupper())) + + def reset(self, input_scss=None): + # Initialize + self.scss_vars = _default_scss_vars.copy() + if self._scss_vars is not None: + self.scss_vars.update(self._scss_vars) + + self.scss_opts = self._scss_opts.copy() if self._scss_opts else {} + + self.root_namespace = Namespace(variables=self.scss_vars, functions=self._library) + + # Figure out search paths. Fall back from provided explicitly to + # defined globally to just searching the current directory + self.search_paths = ['.'] + if self._search_paths is not None: + assert not isinstance(self._search_paths, six.string_types), \ + "`search_paths` should be an iterable, not a string" + self.search_paths.extend(self._search_paths) + else: + if config.LOAD_PATHS: + if isinstance(config.LOAD_PATHS, six.string_types): + # Back-compat: allow comma-delimited + self.search_paths.extend(config.LOAD_PATHS.split(',')) + else: + self.search_paths.extend(config.LOAD_PATHS) + + self.search_paths.extend(self.scss_opts.get('load_paths', [])) + + self.source_files = [] + self.source_file_index = {} + if self._scss_files is not None: + for name, contents in list(self._scss_files.items()): + if name in self.source_file_index: + raise KeyError("Duplicate filename %r" % name) + source_file = SourceFile(name, contents) + self.source_files.append(source_file) + self.source_file_index[name] = source_file + + self.rules = [] + + #@profile + #@print_timing(2) + def Compilation(self, scss_string=None, scss_file=None, super_selector=None, filename=None, is_sass=None, line_numbers=True): + if super_selector: + self.super_selector = super_selector + ' ' + self.reset() + + source_file = None + if scss_string is not None: + source_file = SourceFile.from_string(scss_string, filename, is_sass, line_numbers) + elif scss_file is not None: + source_file = SourceFile.from_filename(scss_file, filename, is_sass, line_numbers) + + if source_file is not None: + # Clear the existing list of files + self.source_files = [] + self.source_file_index = dict() + + self.source_files.append(source_file) + self.source_file_index[source_file.filename] = source_file + + # this will compile and manage rule: child objects inside of a node + self.parse_children() + + # this will manage @extends + self.apply_extends() + + rules_by_file, css_files = self.parse_properties() + + all_rules = 0 + all_selectors = 0 + exceeded = '' + final_cont = '' + files = len(css_files) + for source_file in css_files: + rules = rules_by_file[source_file] + fcont, total_rules, total_selectors = self.create_css(rules) + all_rules += total_rules + all_selectors += total_selectors + if not exceeded and all_selectors > 4095: + exceeded = " (IE exceeded!)" + log.error("Maximum number of supported selectors in Internet Explorer (4095) exceeded!") + if files > 1 and self.scss_opts.get('debug_info', False): + if source_file.is_string: + final_cont += "/* %s %s generated add up to a total of %s %s accumulated%s */\n" % ( + total_selectors, + 'selector' if total_selectors == 1 else 'selectors', + all_selectors, + 'selector' if all_selectors == 1 else 'selectors', + exceeded) + else: + final_cont += "/* %s %s generated from '%s' add up to a total of %s %s accumulated%s */\n" % ( + total_selectors, + 'selector' if total_selectors == 1 else 'selectors', + source_file.filename, + all_selectors, + 'selector' if all_selectors == 1 else 'selectors', + exceeded) + final_cont += fcont + + return final_cont + + def compile(self, *args, **kwargs): + try: + return self.Compilation(*args, **kwargs) + except SassError as e: + if self.live_errors: + # TODO should this setting also capture and display warnings? + return e.to_css() + else: + raise + + def parse_selectors(self, raw_selectors): + """ + Parses out the old xCSS "foo extends bar" syntax. + + Returns a 2-tuple: a set of selectors, and a set of extended selectors. + """ + # Fix tabs and spaces in selectors + raw_selectors = _spaces_re.sub(' ', raw_selectors) + + import re + + from scss.selector import Selector + + parts = re.split(r'\s+extends\s+', raw_selectors, 1) + if len(parts) > 1: + unparsed_selectors, unsplit_parents = parts + # Multiple `extends` are delimited by `&` + unparsed_parents = unsplit_parents.split('&') + else: + unparsed_selectors, = parts + unparsed_parents = () + + selectors = Selector.parse_many(unparsed_selectors) + parents = [Selector.parse_one(parent) for parent in unparsed_parents] + + return selectors, parents + + @print_timing(3) + def parse_children(self, scope=None): + children = [] + root_namespace = self.root_namespace + for source_file in self.source_files: + rule = SassRule( + source_file=source_file, + + unparsed_contents=source_file.contents, + namespace=root_namespace, + options=self.scss_opts, + ) + self.rules.append(rule) + children.append(rule) + + for rule in children: + self.manage_children(rule, scope) + + if self.scss_opts.get('warn_unused'): + for name, file_and_line in root_namespace.unused_imports(): + log.warn("Unused @import: '%s' (%s)", name, file_and_line) + + @print_timing(4) + def manage_children(self, rule, scope): + try: + self._manage_children_impl(rule, scope) + except SassReturn: + raise + except SassError as e: + e.add_rule(rule) + raise + except Exception as e: + raise SassError(e, rule=rule) + + def _manage_children_impl(self, rule, scope): + calculator = Calculator(rule.namespace) + + for c_lineno, c_property, c_codestr in locate_blocks(rule.unparsed_contents): + block = UnparsedBlock(rule, c_lineno, c_property, c_codestr) + + if block.is_atrule: + code = block.directive + code = code.lower() + if code == '@warn': + value = calculator.calculate(block.argument) + log.warn(repr(value)) + elif code == '@print': + value = calculator.calculate(block.argument) + sys.stderr.write("%s\n" % value) + elif code == '@raw': + value = calculator.calculate(block.argument) + sys.stderr.write("%s\n" % repr(value)) + elif code == '@dump_context': + sys.stderr.write("%s\n" % repr(rule.namespace._variables)) + elif code == '@dump_functions': + sys.stderr.write("%s\n" % repr(rule.namespace._functions)) + elif code == '@dump_mixins': + sys.stderr.write("%s\n" % repr(rule.namespace._mixins)) + elif code == '@dump_imports': + sys.stderr.write("%s\n" % repr(rule.namespace._imports)) + elif code == '@dump_options': + sys.stderr.write("%s\n" % repr(rule.options)) + elif code == '@debug': + setting = block.argument.strip() + if setting.lower() in ('1', 'true', 't', 'yes', 'y', 'on'): + setting = True + elif setting.lower() in ('0', 'false', 'f', 'no', 'n', 'off', 'undefined'): + setting = False + config.DEBUG = setting + log.info("Debug mode is %s", 'On' if config.DEBUG else 'Off') + elif code == '@option': + self._settle_options(rule, scope, block) + elif code == '@content': + self._do_content(rule, scope, block) + elif code == '@import': + self._do_import(rule, scope, block) + elif code == '@extend': + from scss.selector import Selector + selectors = calculator.apply_vars(block.argument) + # XXX this no longer handles `&`, which is from xcss + rule.extends_selectors.extend(Selector.parse_many(selectors)) + #rule.extends_selectors.update(p.strip() for p in selectors.replace(',', '&').split('&')) + #rule.extends_selectors.discard('') + elif code == '@return': + # TODO should assert this only happens within a @function + ret = calculator.calculate(block.argument) + raise SassReturn(ret) + elif code == '@include': + self._do_include(rule, scope, block) + elif code in ('@mixin', '@function'): + self._do_functions(rule, scope, block) + elif code in ('@if', '@else if'): + self._do_if(rule, scope, block) + elif code == '@else': + self._do_else(rule, scope, block) + elif code == '@for': + self._do_for(rule, scope, block) + elif code == '@each': + self._do_each(rule, scope, block) + elif code == '@while': + self._do_while(rule, scope, block) + elif code in ('@variables', '@vars'): + self._get_variables(rule, scope, block) + elif block.unparsed_contents is None: + rule.properties.append((block.prop, None)) + elif scope is None: # needs to have no scope to crawl down the nested rules + self._nest_at_rules(rule, scope, block) + #################################################################### + # Properties + elif block.unparsed_contents is None: + self._get_properties(rule, scope, block) + # Nested properties + elif block.is_scope: + if block.header.unscoped_value: + # Possibly deal with default unscoped value + self._get_properties(rule, scope, block) + + rule.unparsed_contents = block.unparsed_contents + subscope = (scope or '') + block.header.scope + '-' + self.manage_children(rule, subscope) + #################################################################### + # Nested rules + elif scope is None: # needs to have no scope to crawl down the nested rules + self._nest_rules(rule, scope, block) + + @print_timing(10) + def _settle_options(self, rule, scope, block): + for option in block.argument.split(','): + option, value = (option.split(':', 1) + [''])[:2] + option = option.strip().lower() + value = value.strip() + if option: + if value.lower() in ('1', 'true', 't', 'yes', 'y', 'on'): + value = True + elif value.lower() in ('0', 'false', 'f', 'no', 'n', 'off', 'undefined'): + value = False + option = option.replace('-', '_') + if option == 'compress': + option = 'style' + log.warn("The option 'compress' is deprecated. Please use 'style' instead.") + rule.options[option] = value + + def _get_funct_def(self, rule, calculator, argument): + funct, lpar, argstr = argument.partition('(') + funct = calculator.do_glob_math(funct) + funct = normalize_var(funct.strip()) + argstr = argstr.strip() + + # Parse arguments with the argspec rule + if lpar: + if not argstr.endswith(')'): + raise SyntaxError("Expected ')', found end of line for %s (%s)" % (funct, rule.file_and_line)) + argstr = argstr[:-1].strip() + else: + # Whoops, no parens at all. That's like calling with no arguments. + argstr = '' + + argstr = calculator.do_glob_math(argstr) + argspec_node = calculator.parse_expression(argstr, target='goal_argspec') + return funct, argspec_node + + def _populate_namespace_from_call(self, name, callee_namespace, mixin, args, kwargs): + # Mutation protection + args = list(args) + kwargs = dict(kwargs) + + #m_params = mixin[0] + #m_defaults = mixin[1] + #m_codestr = mixin[2] + pristine_callee_namespace = mixin[3] + callee_argspec = mixin[4] + import_key = mixin[5] + + callee_calculator = Calculator(callee_namespace) + + # Populate the mixin/function's namespace with its arguments + for var_name, node in callee_argspec.iter_def_argspec(): + if args: + # If there are positional arguments left, use the first + value = args.pop(0) + elif var_name in kwargs: + # Try keyword arguments + value = kwargs.pop(var_name) + elif node is not None: + # OK, there's a default argument; try that + # DEVIATION: this allows argument defaults to refer to earlier + # argument values + value = node.evaluate(callee_calculator, divide=True) + else: + # TODO this should raise + value = Undefined() + + callee_namespace.set_variable(var_name, value, local_only=True) + + if callee_argspec.slurp: + # Slurpy var gets whatever is left + callee_namespace.set_variable( + callee_argspec.slurp.name, + List(args, use_comma=True)) + args = [] + elif callee_argspec.inject: + # Callee namespace gets all the extra kwargs whether declared or + # not + for var_name, value in kwargs.items(): + callee_namespace.set_variable(var_name, value, local_only=True) + kwargs = {} + + # TODO would be nice to say where the mixin/function came from + if kwargs: + raise NameError("%s has no such argument %s" % (name, kwargs.keys()[0])) + + if args: + raise NameError("%s received extra arguments: %r" % (name, args)) + + pristine_callee_namespace.use_import(import_key) + return callee_namespace + + @print_timing(10) + def _do_functions(self, rule, scope, block): + """ + Implements @mixin and @function + """ + if not block.argument: + raise SyntaxError("%s requires a function name (%s)" % (block.directive, rule.file_and_line)) + + calculator = Calculator(rule.namespace) + funct, argspec_node = self._get_funct_def(rule, calculator, block.argument) + + defaults = {} + new_params = [] + + for var_name, default in argspec_node.iter_def_argspec(): + new_params.append(var_name) + if default is not None: + defaults[var_name] = default + + mixin = [rule.source_file, block.lineno, block.unparsed_contents, rule.namespace, argspec_node, rule.import_key] + if block.directive == '@function': + def _call(mixin): + def __call(namespace, *args, **kwargs): + source_file = mixin[0] + lineno = mixin[1] + m_codestr = mixin[2] + pristine_callee_namespace = mixin[3] + callee_namespace = pristine_callee_namespace.derive() + + # TODO CallOp converts Sass names to Python names, so we + # have to convert them back to Sass names. would be nice + # to avoid this back-and-forth somehow + kwargs = dict( + (normalize_var('$' + key), value) + for (key, value) in kwargs.items()) + + self._populate_namespace_from_call( + "Function {0}".format(funct), + callee_namespace, mixin, args, kwargs) + + _rule = SassRule( + source_file=source_file, + lineno=lineno, + unparsed_contents=m_codestr, + namespace=callee_namespace, + + # rule + import_key=rule.import_key, + options=rule.options, + properties=rule.properties, + extends_selectors=rule.extends_selectors, + ancestry=rule.ancestry, + nested=rule.nested, + ) + try: + self.manage_children(_rule, scope) + except SassReturn as e: + return e.retval + else: + return Null() + return __call + _mixin = _call(mixin) + _mixin.mixin = mixin + mixin = _mixin + + if block.directive == '@mixin': + add = rule.namespace.set_mixin + elif block.directive == '@function': + add = rule.namespace.set_function + + # Register the mixin for every possible arity it takes + if argspec_node.slurp or argspec_node.inject: + add(funct, None, mixin) + else: + while len(new_params): + add(funct, len(new_params), mixin) + param = new_params.pop() + if param not in defaults: + break + if not new_params: + add(funct, 0, mixin) + + @print_timing(10) + def _do_include(self, rule, scope, block): + """ + Implements @include, for @mixins + """ + caller_namespace = rule.namespace + caller_calculator = Calculator(caller_namespace) + funct, caller_argspec = self._get_funct_def(rule, caller_calculator, block.argument) + + # Render the passed arguments, using the caller's namespace + args, kwargs = caller_argspec.evaluate_call_args(caller_calculator) + + argc = len(args) + len(kwargs) + try: + mixin = caller_namespace.mixin(funct, argc) + except KeyError: + try: + # TODO maybe? don't do this, once '...' works + # Fallback to single parameter: + mixin = caller_namespace.mixin(funct, 1) + except KeyError: + log.error("Mixin not found: %s:%d (%s)", funct, argc, rule.file_and_line, extra={'stack': True}) + return + else: + args = [List(args, use_comma=True)] + # TODO what happens to kwargs? + + source_file = mixin[0] + lineno = mixin[1] + m_codestr = mixin[2] + pristine_callee_namespace = mixin[3] + callee_argspec = mixin[4] + if caller_argspec.inject and callee_argspec.inject: + # DEVIATION: Pass the ENTIRE local namespace to the mixin (yikes) + callee_namespace = Namespace.derive_from( + caller_namespace, + pristine_callee_namespace) + else: + callee_namespace = pristine_callee_namespace.derive() + + self._populate_namespace_from_call( + "Mixin {0}".format(funct), + callee_namespace, mixin, args, kwargs) + + _rule = SassRule( + source_file=source_file, + lineno=lineno, + unparsed_contents=m_codestr, + namespace=callee_namespace, + + # rule + import_key=rule.import_key, + options=rule.options, + properties=rule.properties, + extends_selectors=rule.extends_selectors, + ancestry=rule.ancestry, + nested=rule.nested, + ) + + _rule.options['@content'] = block.unparsed_contents + self.manage_children(_rule, scope) + + @print_timing(10) + def _do_content(self, rule, scope, block): + """ + Implements @content + """ + if '@content' not in rule.options: + log.error("Content string not found for @content (%s)", rule.file_and_line) + rule.unparsed_contents = rule.options.pop('@content', '') + self.manage_children(rule, scope) + + @print_timing(10) + def _do_import(self, rule, scope, block): + """ + Implements @import + Load and import mixins and functions and rules + """ + # Protect against going to prohibited places... + if any(scary_token in block.argument for scary_token in ('..', '://', 'url(')): + rule.properties.append((block.prop, None)) + return + + full_filename = None + names = block.argument.split(',') + for name in names: + name = dequote(name.strip()) + + source_file = None + full_filename, seen_paths = self._find_import(rule, name) + + if full_filename is None: + i_codestr = self._do_magic_import(rule, scope, block) + + if i_codestr is not None: + source_file = SourceFile.from_string(i_codestr) + + elif full_filename in self.source_file_index: + source_file = self.source_file_index[full_filename] + + else: + with open(full_filename) as f: + source = f.read() + source_file = SourceFile( + full_filename, + source, + parent_dir=os.path.realpath(os.path.dirname(full_filename)), + ) + + self.source_files.append(source_file) + self.source_file_index[full_filename] = source_file + + if source_file is None: + load_paths_msg = "\nLoad paths:\n\t%s" % "\n\t".join(seen_paths) + log.warn("File to import not found or unreadable: '%s' (%s)%s", name, rule.file_and_line, load_paths_msg) + continue + + import_key = (name, source_file.parent_dir) + if rule.namespace.has_import(import_key): + # If already imported in this scope, skip + continue + + _rule = SassRule( + source_file=source_file, + lineno=block.lineno, + import_key=import_key, + unparsed_contents=source_file.contents, + + # rule + options=rule.options, + properties=rule.properties, + extends_selectors=rule.extends_selectors, + ancestry=rule.ancestry, + namespace=rule.namespace, + ) + rule.namespace.add_import(import_key, rule.import_key, rule.file_and_line) + self.manage_children(_rule, scope) + + def _find_import(self, rule, name): + """Find the file referred to by an @import. + + Takes a name from an @import and returns an absolute path, or None. + """ + name, ext = os.path.splitext(name) + if ext: + search_exts = [ext] + else: + search_exts = ['.scss', '.sass'] + + dirname, name = os.path.split(name) + + seen_paths = [] + + for path in self.search_paths: + for basepath in [rule.source_file.parent_dir, '.']: + full_path = os.path.realpath(os.path.join(basepath, path, dirname)) + + if full_path in seen_paths: + continue + seen_paths.append(full_path) + + for prefix, suffix in product(('_', ''), search_exts): + full_filename = os.path.join(full_path, prefix + name + suffix) + if os.path.exists(full_filename): + return full_filename, seen_paths + + return None, seen_paths + + @print_timing(10) + def _do_magic_import(self, rule, scope, block): + """ + Implements @import for sprite-maps + Imports magic sprite map directories + """ + if callable(config.STATIC_ROOT): + files = sorted(config.STATIC_ROOT(block.argument)) + else: + glob_path = os.path.join(config.STATIC_ROOT, block.argument) + files = glob.glob(glob_path) + files = sorted((file[len(config.STATIC_ROOT):], None) for file in files) + + if not files: + return + + # Build magic context + map_name = os.path.normpath(os.path.dirname(block.argument)).replace('\\', '_').replace('/', '_') + kwargs = {} + + calculator = Calculator(rule.namespace) + + def setdefault(var, val): + _var = '$' + map_name + '-' + var + if _var in rule.context: + kwargs[var] = calculator.interpolate(rule.context[_var], rule, self._library) + else: + rule.context[_var] = val + kwargs[var] = calculator.interpolate(val, rule, self._library) + return rule.context[_var] + + setdefault('sprite-base-class', String('.' + map_name + '-sprite', quotes=None)) + setdefault('sprite-dimensions', Boolean(False)) + position = setdefault('position', Number(0, '%')) + spacing = setdefault('spacing', Number(0)) + repeat = setdefault('repeat', String('no-repeat', quotes=None)) + names = tuple(os.path.splitext(os.path.basename(file))[0] for file, storage in files) + for n in names: + setdefault(n + '-position', position) + setdefault(n + '-spacing', spacing) + setdefault(n + '-repeat', repeat) + rule.context['$' + map_name + '-' + 'sprites'] = sprite_map(block.argument, **kwargs) + ret = ''' + @import "compass/utilities/sprites/base"; + + // All sprites should extend this class + // The %(map_name)s-sprite mixin will do so for you. + #{$%(map_name)s-sprite-base-class} { + background: $%(map_name)s-sprites; + } + + // Use this to set the dimensions of an element + // based on the size of the original image. + @mixin %(map_name)s-sprite-dimensions($name) { + @include sprite-dimensions($%(map_name)s-sprites, $name); + } + + // Move the background position to display the sprite. + @mixin %(map_name)s-sprite-position($name, $offset-x: 0, $offset-y: 0) { + @include sprite-position($%(map_name)s-sprites, $name, $offset-x, $offset-y); + } + + // Extends the sprite base class and set the background position for the desired sprite. + // It will also apply the image dimensions if $dimensions is true. + @mixin %(map_name)s-sprite($name, $dimensions: $%(map_name)s-sprite-dimensions, $offset-x: 0, $offset-y: 0) { + @extend #{$%(map_name)s-sprite-base-class}; + @include sprite($%(map_name)s-sprites, $name, $dimensions, $offset-x, $offset-y); + } + + @mixin %(map_name)s-sprites($sprite-names, $dimensions: $%(map_name)s-sprite-dimensions) { + @include sprites($%(map_name)s-sprites, $sprite-names, $%(map_name)s-sprite-base-class, $dimensions); + } + + // Generates a class for each sprited image. + @mixin all-%(map_name)s-sprites($dimensions: $%(map_name)s-sprite-dimensions) { + @include %(map_name)s-sprites(%(sprites)s, $dimensions); + } + ''' % {'map_name': map_name, 'sprites': ' '.join(names)} + return ret + + @print_timing(10) + def _do_if(self, rule, scope, block): + """ + Implements @if and @else if + """ + # "@if" indicates whether any kind of `if` since the last `@else` has + # succeeded, in which case `@else if` should be skipped + if block.directive != '@if': + if '@if' not in rule.options: + raise SyntaxError("@else with no @if (%s)" % (rule.file_and_line,)) + if rule.options['@if']: + # Last @if succeeded; stop here + return + + calculator = Calculator(rule.namespace) + condition = calculator.calculate(block.argument) + if condition: + inner_rule = rule.copy() + inner_rule.unparsed_contents = block.unparsed_contents + if not rule.options.get('control_scoping', config.CONTROL_SCOPING): # TODO: maybe make this scoping mode for contol structures as the default as a default deviation + # DEVIATION: Allow not creating a new namespace + inner_rule.namespace = rule.namespace + self.manage_children(inner_rule, scope) + rule.options['@if'] = condition + + @print_timing(10) + def _do_else(self, rule, scope, block): + """ + Implements @else + """ + if '@if' not in rule.options: + log.error("@else with no @if (%s)", rule.file_and_line) + val = rule.options.pop('@if', True) + if not val: + inner_rule = rule.copy() + inner_rule.unparsed_contents = block.unparsed_contents + inner_rule.namespace = rule.namespace # DEVIATION: Commenting this line gives the Sass bahavior + inner_rule.unparsed_contents = block.unparsed_contents + self.manage_children(inner_rule, scope) + + @print_timing(10) + def _do_for(self, rule, scope, block): + """ + Implements @for + """ + var, _, name = block.argument.partition(' from ') + frm, _, through = name.partition(' through ') + if not through: + frm, _, through = frm.partition(' to ') + calculator = Calculator(rule.namespace) + frm = calculator.calculate(frm) + through = calculator.calculate(through) + try: + frm = int(float(frm)) + through = int(float(through)) + except ValueError: + return + + if frm > through: + # DEVIATION: allow reversed '@for .. from .. through' (same as enumerate() and range()) + frm, through = through, frm + rev = reversed + else: + rev = lambda x: x + var = var.strip() + var = calculator.do_glob_math(var) + var = normalize_var(var) + + inner_rule = rule.copy() + inner_rule.unparsed_contents = block.unparsed_contents + if not rule.options.get('control_scoping', config.CONTROL_SCOPING): # TODO: maybe make this scoping mode for contol structures as the default as a default deviation + # DEVIATION: Allow not creating a new namespace + inner_rule.namespace = rule.namespace + + for i in rev(range(frm, through + 1)): + inner_rule.namespace.set_variable(var, Number(i)) + self.manage_children(inner_rule, scope) + + @print_timing(10) + def _do_each(self, rule, scope, block): + """ + Implements @each + """ + varstring, _, valuestring = block.argument.partition(' in ') + calculator = Calculator(rule.namespace) + values = calculator.calculate(valuestring) + if not values: + return + + varlist = varstring.split(",") + varlist = [ + normalize_var(calculator.do_glob_math(var.strip())) + for var in varlist + ] + + inner_rule = rule.copy() + inner_rule.unparsed_contents = block.unparsed_contents + if not rule.options.get('control_scoping', config.CONTROL_SCOPING): # TODO: maybe make this scoping mode for contol structures as the default as a default deviation + # DEVIATION: Allow not creating a new namespace + inner_rule.namespace = rule.namespace + + for v in List.from_maybe(values): + v = List.from_maybe(v) + for i, var in enumerate(varlist): + if i >= len(v): + value = Null() + else: + value = v[i] + inner_rule.namespace.set_variable(var, value) + self.manage_children(inner_rule, scope) + + @print_timing(10) + def _do_while(self, rule, scope, block): + """ + Implements @while + """ + calculator = Calculator(rule.namespace) + first_condition = condition = calculator.calculate(block.argument) + while condition: + inner_rule = rule.copy() + inner_rule.unparsed_contents = block.unparsed_contents + if not rule.options.get('control_scoping', config.CONTROL_SCOPING): # TODO: maybe make this scoping mode for contol structures as the default as a default deviation + # DEVIATION: Allow not creating a new namespace + inner_rule.namespace = rule.namespace + self.manage_children(inner_rule, scope) + condition = calculator.calculate(block.argument) + rule.options['@if'] = first_condition + + @print_timing(10) + def _get_variables(self, rule, scope, block): + """ + Implements @variables and @vars + """ + _rule = rule.copy() + _rule.unparsed_contents = block.unparsed_contents + _rule.namespace = rule.namespace + _rule.properties = {} + self.manage_children(_rule, scope) + for name, value in _rule.properties.items(): + rule.namespace.set_variable(name, value) + + @print_timing(10) + def _get_properties(self, rule, scope, block): + """ + Implements properties and variables extraction and assignment + """ + prop, raw_value = (_prop_split_re.split(block.prop, 1) + [None])[:2] + try: + is_var = (block.prop[len(prop)] == '=') + except IndexError: + is_var = False + calculator = Calculator(rule.namespace) + prop = prop.strip() + prop = calculator.do_glob_math(prop) + if not prop: + return + + # Parse the value and determine whether it's a default assignment + is_default = False + if raw_value is not None: + raw_value = raw_value.strip() + if prop.startswith('$'): + raw_value, subs = re.subn(r'(?i)\s+!default\Z', '', raw_value) + if subs: + is_default = True + + _prop = (scope or '') + prop + if is_var or prop.startswith('$') and raw_value is not None: + # Variable assignment + _prop = normalize_var(_prop) + try: + existing_value = rule.namespace.variable(_prop) + except KeyError: + existing_value = None + + is_defined = existing_value is not None and not existing_value.is_null + if is_default and is_defined: + pass + else: + if is_defined and prop.startswith('$') and prop[1].isupper(): + log.warn("Constant %r redefined", prop) + + # Variable assignment is an expression, so it always performs + # real division + value = calculator.calculate(raw_value, divide=True) + rule.namespace.set_variable(_prop, value) + else: + # Regular property destined for output + _prop = calculator.apply_vars(_prop) + if raw_value is None: + value = None + else: + value = calculator.calculate(raw_value) + + if value is None: + pass + elif isinstance(value, six.string_types): + # TODO kill this branch + pass + else: + style = self.scss_opts.get('style', config.STYLE) + compress = style in (True, 'compressed') + value = value.render(compress=compress) + + rule.properties.append((_prop, value)) + + @print_timing(10) + def _nest_at_rules(self, rule, scope, block): + """ + Implements @-blocks + """ + # Interpolate the current block + # TODO this seems like it should be done in the block header. and more + # generally? + calculator = Calculator(rule.namespace) + block.header.argument = calculator.apply_vars(block.header.argument) + + # TODO merge into RuleAncestry + new_ancestry = list(rule.ancestry.headers) + if block.directive == '@media' and new_ancestry: + for i, header in reversed(list(enumerate(new_ancestry))): + if header.is_selector: + continue + elif header.directive == '@media': + from scss.rule import BlockAtRuleHeader + new_ancestry[i] = BlockAtRuleHeader( + '@media', + "%s and %s" % (header.argument, block.argument)) + break + else: + new_ancestry.insert(i, block.header) + else: + new_ancestry.insert(0, block.header) + else: + new_ancestry.append(block.header) + + from scss.rule import RuleAncestry + rule.descendants += 1 + new_rule = SassRule( + source_file=rule.source_file, + import_key=rule.import_key, + lineno=block.lineno, + unparsed_contents=block.unparsed_contents, + + options=rule.options.copy(), + #properties + #extends_selectors + ancestry=RuleAncestry(new_ancestry), + + namespace=rule.namespace.derive(), + nested=rule.nested + 1, + ) + self.rules.append(new_rule) + rule.namespace.use_import(rule.import_key) + self.manage_children(new_rule, scope) + + if new_rule.options.get('warn_unused'): + for name, file_and_line in new_rule.namespace.unused_imports(): + log.warn("Unused @import: '%s' (%s)", name, file_and_line) + + @print_timing(10) + def _nest_rules(self, rule, scope, block): + """ + Implements Nested CSS rules + """ + calculator = Calculator(rule.namespace) + raw_selectors = calculator.do_glob_math(block.prop) + # DEVIATION: ruby sass doesn't support bare variables in selectors + raw_selectors = calculator.apply_vars(raw_selectors) + c_selectors, c_parents = self.parse_selectors(raw_selectors) + + new_ancestry = rule.ancestry.with_nested_selectors(c_selectors) + + rule.descendants += 1 + new_rule = SassRule( + source_file=rule.source_file, + import_key=rule.import_key, + lineno=block.lineno, + unparsed_contents=block.unparsed_contents, + + options=rule.options.copy(), + #properties + extends_selectors=c_parents, + ancestry=new_ancestry, + + namespace=rule.namespace.derive(), + nested=rule.nested + 1, + ) + self.rules.append(new_rule) + rule.namespace.use_import(rule.import_key) + self.manage_children(new_rule, scope) + + if new_rule.options.get('warn_unused'): + for name, file_and_line in new_rule.namespace.unused_imports(): + log.warn("Unused @import: '%s' (%s)", name, file_and_line) + + @print_timing(3) + def apply_extends(self): + """Run through the given rules and translate all the pending @extends + declarations into real selectors on parent rules. + + The list is modified in-place and also sorted in dependency order. + """ + # Game plan: for each rule that has an @extend, add its selectors to + # every rule that matches that @extend. + # First, rig a way to find arbitrary selectors quickly. Most selectors + # revolve around elements, classes, and IDs, so parse those out and use + # them as a rough key. Ignore order and duplication for now. + key_to_selectors = defaultdict(set) + selector_to_rules = defaultdict(list) + # DEVIATION: These are used to rearrange rules in dependency order, so + # an @extended parent appears in the output before a child. Sass does + # not do this, and the results may be unexpected. Pending removal. + rule_order = dict() + rule_dependencies = dict() + order = 0 + for rule in self.rules: + rule_order[rule] = order + # Rules are ultimately sorted by the earliest rule they must + # *precede*, so every rule should "depend" on the next one + rule_dependencies[rule] = [order + 1] + order += 1 + + for selector in rule.selectors: + for key in selector.lookup_key(): + key_to_selectors[key].add(selector) + selector_to_rules[selector].append(rule) + + # Now go through all the rules with an @extends and find their parent + # rules. + for rule in self.rules: + for selector in rule.extends_selectors: + # This is a little dirty. intersection isn't a class method. + # Don't think about it too much. + candidates = set.intersection(*( + key_to_selectors[key] for key in selector.lookup_key())) + extendable_selectors = [ + candidate for candidate in candidates + if candidate.is_superset_of(selector)] + + if not extendable_selectors: + log.warn( + "Can't find any matching rules to extend: %s" + % selector.render()) + continue + + # Armed with a set of selectors that this rule can extend, do + # some substitution and modify the appropriate parent rules + for extendable_selector in extendable_selectors: + # list() shields us from problems mutating the list within + # this loop, which can happen in the case of @extend loops + parent_rules = list(selector_to_rules[extendable_selector]) + for parent_rule in parent_rules: + if parent_rule is rule: + # Don't extend oneself + continue + + more_parent_selectors = [] + + for rule_selector in rule.selectors: + more_parent_selectors.extend( + extendable_selector.substitute( + selector, rule_selector)) + + for parent in more_parent_selectors: + # Update indices, in case any later rules try to + # extend this one + for key in parent.lookup_key(): + key_to_selectors[key].add(parent) + # TODO this could lead to duplicates? maybe should + # be a set too + selector_to_rules[parent].append(parent_rule) + + parent_rule.ancestry = ( + parent_rule.ancestry.with_more_selectors( + more_parent_selectors)) + rule_dependencies[parent_rule].append(rule_order[rule]) + + self.rules.sort(key=lambda rule: min(rule_dependencies[rule])) + + @print_timing(3) + def parse_properties(self): + css_files = [] + seen_files = set() + rules_by_file = {} + + for rule in self.rules: + source_file = rule.source_file + rules_by_file.setdefault(source_file, []).append(rule) + + if rule.is_empty: + continue + + if source_file not in seen_files: + seen_files.add(source_file) + css_files.append(source_file) + + return rules_by_file, css_files + + @print_timing(3) + def create_css(self, rules): + """ + Generate the final CSS string + """ + style = self.scss_opts.get('style', config.STYLE) + debug_info = self.scss_opts.get('debug_info', False) + + if style == 'legacy' or style is False: + sc, sp, tb, nst, srnl, nl, rnl, lnl, dbg = True, ' ', ' ', False, '', '\n', '\n', '\n', debug_info + elif style == 'compressed' or style is True: + sc, sp, tb, nst, srnl, nl, rnl, lnl, dbg = False, '', '', False, '', '', '', '', False + elif style == 'compact': + sc, sp, tb, nst, srnl, nl, rnl, lnl, dbg = True, ' ', '', False, '\n', ' ', '\n', ' ', debug_info + elif style == 'expanded': + sc, sp, tb, nst, srnl, nl, rnl, lnl, dbg = True, ' ', ' ', False, '\n', '\n', '\n', '\n', debug_info + else: # if style == 'nested': + sc, sp, tb, nst, srnl, nl, rnl, lnl, dbg = True, ' ', ' ', True, '\n', '\n', '\n', ' ', debug_info + + return self._create_css(rules, sc, sp, tb, nst, srnl, nl, rnl, lnl, dbg) + + def _textwrap(self, txt, width=70): + if not hasattr(self, '_textwrap_wordsep_re'): + self._textwrap_wordsep_re = re.compile(r'(?<=,)\s+') + self._textwrap_strings_re = re.compile(r'''(["'])(?:(?!\1)[^\\]|\\.)*\1''') + + # First, remove commas from anything within strings (marking commas as \0): + def _repl(m): + ori = m.group(0) + fin = ori.replace(',', '\0') + if ori != fin: + subs[fin] = ori + return fin + subs = {} + txt = self._textwrap_strings_re.sub(_repl, txt) + + # Mark split points for word separators using (marking spaces with \1): + txt = self._textwrap_wordsep_re.sub('\1', txt) + + # Replace all the strings back: + for fin, ori in subs.items(): + txt = txt.replace(fin, ori) + + # Split in chunks: + chunks = txt.split('\1') + + # Break in lines of at most long_width width appending chunks: + ln = '' + lines = [] + long_width = int(width * 1.2) + for chunk in chunks: + _ln = ln + ' ' if ln else '' + _ln += chunk + if len(ln) >= width or len(_ln) >= long_width: + if ln: + lines.append(ln) + _ln = chunk + ln = _ln + if ln: + lines.append(ln) + + return lines + + def _create_css(self, rules, sc=True, sp=' ', tb=' ', nst=True, srnl='\n', nl='\n', rnl='\n', lnl='', debug_info=False): + skip_selectors = False + + prev_ancestry_headers = [] + + total_rules = 0 + total_selectors = 0 + + result = '' + dangling_property = False + separate = False + nesting = current_nesting = last_nesting = -1 if nst else 0 + nesting_stack = [] + for rule in rules: + nested = rule.nested + if nested <= 1: + separate = True + + if nst: + last_nesting = current_nesting + current_nesting = nested + + delta_nesting = current_nesting - last_nesting + if delta_nesting > 0: + nesting_stack += [nesting] * delta_nesting + elif delta_nesting < 0: + nesting_stack = nesting_stack[:delta_nesting] + nesting = nesting_stack[-1] + + if rule.is_empty: + continue + + if nst: + nesting += 1 + + ancestry = rule.ancestry + ancestry_len = len(ancestry) + + first_mismatch = 0 + for i, (old_header, new_header) in enumerate(zip(prev_ancestry_headers, ancestry.headers)): + if old_header != new_header: + first_mismatch = i + break + + # When sc is False, sets of properties are printed without a + # trailing semicolon. If the previous block isn't being closed, + # that trailing semicolon needs adding in to separate the last + # property from the next rule. + if not sc and dangling_property and first_mismatch >= len(prev_ancestry_headers): + result += ';' + + # Close blocks and outdent as necessary + for i in range(len(prev_ancestry_headers), first_mismatch, -1): + result += tb * (i - 1) + '}' + rnl + + # Open new blocks as necessary + for i in range(first_mismatch, ancestry_len): + header = ancestry.headers[i] + + if separate: + if result: + result += srnl + separate = False + if debug_info: + if not rule.source_file.is_string: + filename = rule.source_file.filename + lineno = str(rule.lineno) + if debug_info == 'comments': + result += tb * (i + nesting) + "/* file: %s, line: %s */" % (filename, lineno) + nl + else: + filename = _escape_chars_re.sub(r'\\\1', filename) + result += tb * (i + nesting) + "@media -sass-debug-info{filename{font-family:file\:\/\/%s}line{font-family:\\00003%s}}" % (filename, lineno) + nl + + if header.is_selector: + header_string = header.render(sep=',' + sp, super_selector=self.super_selector) + if nl: + header_string = (nl + tb * (i + nesting)).join(self._textwrap(header_string)) + else: + header_string = header.render() + result += tb * (i + nesting) + header_string + sp + '{' + nl + + total_rules += 1 + if header.is_selector: + total_selectors += 1 + + prev_ancestry_headers = ancestry.headers + dangling_property = False + + if not skip_selectors: + result += self._print_properties(rule.properties, sc, sp, tb * (ancestry_len + nesting), nl, lnl) + dangling_property = True + + # Close all remaining blocks + for i in reversed(range(len(prev_ancestry_headers))): + result += tb * i + '}' + rnl + + return (result, total_rules, total_selectors) + + def _print_properties(self, properties, sc=True, sp=' ', tb='', nl='\n', lnl=' '): + result = '' + last_prop_index = len(properties) - 1 + for i, (name, value) in enumerate(properties): + if value is None: + prop = name + elif value: + if nl: + value = (nl + tb + tb).join(self._textwrap(value)) + prop = name + ':' + sp + value + else: + # Empty string means there's supposed to be a value but it + # evaluated to nothing; skip this + # TODO interacts poorly with last_prop_index + continue + + if i == last_prop_index: + if sc: + result += tb + prop + ';' + lnl + else: + result += tb + prop + lnl + else: + result += tb + prop + ';' + nl + return result + + +# TODO: this should inherit from SassError, but can't, because that assumes +# it's wrapping another error. fix this with the exception hierarchy +class SassReturn(Exception): + """Special control-flow exception used to hop up the stack from a Sass + function's ``@return``. + """ + def __init__(self, retval): + self.retval = retval + Exception.__init__(self) + + def __str__(self): + return "Returning {0!r}".format(self.retval) diff --git a/libs/scss/__main__.py b/libs/scss/__main__.py new file mode 100644 index 0000000..792ead7 --- /dev/null +++ b/libs/scss/__main__.py @@ -0,0 +1,3 @@ +import scss.tool + +scss.tool.main() diff --git a/libs/scss/_native.py b/libs/scss/_native.py new file mode 100644 index 0000000..7dafe54 --- /dev/null +++ b/libs/scss/_native.py @@ -0,0 +1,262 @@ +"""Pure-Python scanner and parser, used if _speedups is not available.""" +from __future__ import print_function + +from scss.cssdefs import SEPARATOR +import re + +DEBUG = False + +# TODO copied from __init__ +_nl_num_re = re.compile(r'\n.+' + SEPARATOR, re.MULTILINE) +_blocks_re = re.compile(r'[{},;()\'"\n]') + + +def _strip_selprop(selprop, lineno): + # Get the line number of the selector or property and strip all other + # line numbers that might still be there (from multiline selectors) + _lineno, _sep, selprop = selprop.partition(SEPARATOR) + if _sep == SEPARATOR: + _lineno = _lineno.strip(' \t\n;') + try: + lineno = int(_lineno) + except ValueError: + pass + else: + selprop = _lineno + selprop = _nl_num_re.sub('\n', selprop) + selprop = selprop.strip() + return selprop, lineno + + +def _strip(selprop): + # Strip all line numbers, ignoring them in the way + selprop, _ = _strip_selprop(selprop, None) + return selprop + + +def locate_blocks(codestr): + """ + For processing CSS like strings. + + Either returns all selectors (that can be "smart" multi-lined, as + long as it's joined by `,`, or enclosed in `(` and `)`) with its code block + (the one between `{` and `}`, which can be nested), or the "lose" code + (properties) that doesn't have any blocks. + """ + lineno = 0 + + par = 0 + instr = None + depth = 0 + skip = False + thin = None + i = init = safe = lose = 0 + start = end = None + + for m in _blocks_re.finditer(codestr): + i = m.start(0) + c = codestr[i] + if instr is not None: + if c == instr: + instr = None # A string ends (FIXME: needs to accept escaped characters) + elif c in ('"', "'"): + instr = c # A string starts + elif c == '(': # parenthesis begins: + par += 1 + thin = None + safe = i + 1 + elif c == ')': # parenthesis ends: + par -= 1 + elif not par and not instr: + if c == '{': # block begins: + if depth == 0: + if i > 0 and codestr[i - 1] == '#': # Do not process #{...} as blocks! + skip = True + else: + start = i + if thin is not None and _strip(codestr[thin:i]): + init = thin + if lose < init: + _property, lineno = _strip_selprop(codestr[lose:init], lineno) + if _property: + yield lineno, _property, None + lose = init + thin = None + depth += 1 + elif c == '}': # block ends: + if depth > 0: + depth -= 1 + if depth == 0: + if not skip: + end = i + _selectors, lineno = _strip_selprop(codestr[init:start], lineno) + _codestr = codestr[start + 1:end].strip() + if _selectors: + yield lineno, _selectors, _codestr + init = safe = lose = end + 1 + thin = None + skip = False + elif depth == 0: + if c == ';': # End of property (or block): + init = i + if lose < init: + _property, lineno = _strip_selprop(codestr[lose:init], lineno) + if _property: + yield lineno, _property, None + init = safe = lose = i + 1 + thin = None + elif c == ',': + if thin is not None and _strip(codestr[thin:i]): + init = thin + thin = None + safe = i + 1 + elif c == '\n': + if thin is not None and _strip(codestr[thin:i]): + init = thin + thin = i + 1 + elif thin is None and _strip(codestr[safe:i]): + thin = i + 1 # Step on thin ice, if it breaks, it breaks here + if depth > 0: + if not skip: + _selectors, lineno = _strip_selprop(codestr[init:start], lineno) + _codestr = codestr[start + 1:].strip() + if _selectors: + yield lineno, _selectors, _codestr + if par: + raise Exception("Missing closing parenthesis somewhere in block: '%s'" % _selectors) + elif instr: + raise Exception("Missing closing string somewhere in block: '%s'" % _selectors) + else: + raise Exception("Block never closed: '%s'" % _selectors) + losestr = codestr[lose:] + for _property in losestr.split(';'): + _property, lineno = _strip_selprop(_property, lineno) + if _property: + yield lineno, _property, None + + +################################################################################ +# Parser + +class NoMoreTokens(Exception): + """ + Another exception object, for when we run out of tokens + """ + pass + + +class Scanner(object): + def __init__(self, patterns, ignore, input=None): + """ + Patterns is [(terminal,regex)...] + Ignore is [terminal,...]; + Input is a string + """ + self.reset(input) + self.ignore = ignore + # The stored patterns are a pair (compiled regex,source + # regex). If the patterns variable passed in to the + # constructor is None, we assume that the class already has a + # proper .patterns list constructed + if patterns is not None: + self.patterns = [] + for k, r in patterns: + self.patterns.append((k, re.compile(r))) + + def reset(self, input): + self.tokens = [] + self.restrictions = [] + self.input = input + self.pos = 0 + + def __repr__(self): + """ + Print the last 10 tokens that have been scanned in + """ + output = '' + for t in self.tokens[-10:]: + output = "%s\n (@%s) %s = %s" % (output, t[0], t[2], repr(t[3])) + return output + + def _scan(self, restrict): + """ + Should scan another token and add it to the list, self.tokens, + and add the restriction to self.restrictions + """ + # Keep looking for a token, ignoring any in self.ignore + token = None + while True: + best_pat = None + # Search the patterns for a match, with earlier + # tokens in the list having preference + best_pat_len = 0 + for tok, regex in self.patterns: + if DEBUG: + print("\tTrying %s: %s at pos %d -> %s" % (repr(tok), repr(regex.pattern), self.pos, repr(self.input))) + # First check to see if we're restricting to this token + if restrict and tok not in restrict and tok not in self.ignore: + if DEBUG: + print("\tSkipping %r!" % (tok,)) + continue + m = regex.match(self.input, self.pos) + if m: + # We got a match + best_pat = tok + best_pat_len = len(m.group(0)) + if DEBUG: + print("Match OK! %s: %s at pos %d" % (repr(tok), repr(regex.pattern), self.pos)) + break + + # If we didn't find anything, raise an error + if best_pat is None: + msg = "Bad token found" + if restrict: + msg = "Bad token found while trying to find one of the restricted tokens: %s" % (", ".join(repr(r) for r in restrict)) + raise SyntaxError("SyntaxError[@ char %s: %s]" % (repr(self.pos), msg)) + + # If we found something that isn't to be ignored, return it + if best_pat in self.ignore: + # This token should be ignored... + self.pos += best_pat_len + else: + end_pos = self.pos + best_pat_len + # Create a token with this data + token = ( + self.pos, + end_pos, + best_pat, + self.input[self.pos:end_pos] + ) + break + if token is not None: + self.pos = token[1] + # Only add this token if it's not in the list + # (to prevent looping) + if not self.tokens or token != self.tokens[-1]: + self.tokens.append(token) + self.restrictions.append(restrict) + return 1 + return 0 + + def token(self, i, restrict=None): + """ + Get the i'th token, and if i is one past the end, then scan + for another token; restrict is a list of tokens that + are allowed, or 0 for any token. + """ + tokens_len = len(self.tokens) + if i == tokens_len: # We are at the end, get the next... + tokens_len += self._scan(restrict) + if i < tokens_len: + if restrict and self.restrictions[i] and restrict > self.restrictions[i]: + raise NotImplementedError("Unimplemented: restriction set changed") + return self.tokens[i] + raise NoMoreTokens + + def rewind(self, i): + tokens_len = len(self.tokens) + if i <= tokens_len: + token = self.tokens[i] + self.tokens = self.tokens[:i] + self.restrictions = self.restrictions[:i] + self.pos = token[0] diff --git a/libs/scss/config.py b/libs/scss/config.py new file mode 100644 index 0000000..7836b65 --- /dev/null +++ b/libs/scss/config.py @@ -0,0 +1,36 @@ +################################################################################ +# Configuration: +DEBUG = False +VERBOSITY = 1 + +import os +PROJECT_ROOT = os.path.normpath(os.path.dirname(os.path.abspath(__file__))) + +# Sass @import load_paths: +LOAD_PATHS = os.path.join(PROJECT_ROOT, 'sass/frameworks') + +# Assets path, where new sprite files are created (defaults to STATIC_ROOT + '/assets'): +ASSETS_ROOT = None +# Cache files path, where cache files are saved (defaults to ASSETS_ROOT): +CACHE_ROOT = None +# Assets path, where new sprite files are created: +STATIC_ROOT = os.path.join(PROJECT_ROOT, 'static') +FONTS_ROOT = None # default: STATIC_ROOT +IMAGES_ROOT = None # default: STATIC_ROOT + +# Urls for the static and assets: +ASSETS_URL = 'static/assets/' +STATIC_URL = 'static/' +FONTS_URL = None # default: STATIC_URL +IMAGES_URL = None # default: STATIC_URL + +# Rendering style. Available values are 'nested', 'expanded', 'compact', 'compressed' and 'legacy' (defaults to 'nested'): +STYLE = 'nested' + +# Use a different scope inside control structures create a scope (defaults to create new scopes for control structures, same as Sass): +CONTROL_SCOPING = True + +# Throw fatal errors when finding undefined variables: +FATAL_UNDEFINED = True + +SPRTE_MAP_DIRECTION = 'vertical' diff --git a/libs/scss/cssdefs.py b/libs/scss/cssdefs.py new file mode 100644 index 0000000..7b73805 --- /dev/null +++ b/libs/scss/cssdefs.py @@ -0,0 +1,384 @@ +from math import pi +import re + +# ------------------------------------------------------------------------------ +# Built-in CSS color names +# See: http://www.w3.org/TR/css3-color/#svg-color + +COLOR_NAMES = { + 'aliceblue': (240, 248, 255, 1), + 'antiquewhite': (250, 235, 215, 1), + 'aqua': (0, 255, 255, 1), + 'aquamarine': (127, 255, 212, 1), + 'azure': (240, 255, 255, 1), + 'beige': (245, 245, 220, 1), + 'bisque': (255, 228, 196, 1), + 'black': (0, 0, 0, 1), + 'blanchedalmond': (255, 235, 205, 1), + 'blue': (0, 0, 255, 1), + 'blueviolet': (138, 43, 226, 1), + 'brown': (165, 42, 42, 1), + 'burlywood': (222, 184, 135, 1), + 'cadetblue': (95, 158, 160, 1), + 'chartreuse': (127, 255, 0, 1), + 'chocolate': (210, 105, 30, 1), + 'coral': (255, 127, 80, 1), + 'cornflowerblue': (100, 149, 237, 1), + 'cornsilk': (255, 248, 220, 1), + 'crimson': (220, 20, 60, 1), + 'cyan': (0, 255, 255, 1), + 'darkblue': (0, 0, 139, 1), + 'darkcyan': (0, 139, 139, 1), + 'darkgoldenrod': (184, 134, 11, 1), + 'darkgray': (169, 169, 169, 1), + 'darkgreen': (0, 100, 0, 1), + 'darkkhaki': (189, 183, 107, 1), + 'darkmagenta': (139, 0, 139, 1), + 'darkolivegreen': (85, 107, 47, 1), + 'darkorange': (255, 140, 0, 1), + 'darkorchid': (153, 50, 204, 1), + 'darkred': (139, 0, 0, 1), + 'darksalmon': (233, 150, 122, 1), + 'darkseagreen': (143, 188, 143, 1), + 'darkslateblue': (72, 61, 139, 1), + 'darkslategray': (47, 79, 79, 1), + 'darkturquoise': (0, 206, 209, 1), + 'darkviolet': (148, 0, 211, 1), + 'deeppink': (255, 20, 147, 1), + 'deepskyblue': (0, 191, 255, 1), + 'dimgray': (105, 105, 105, 1), + 'dodgerblue': (30, 144, 255, 1), + 'firebrick': (178, 34, 34, 1), + 'floralwhite': (255, 250, 240, 1), + 'forestgreen': (34, 139, 34, 1), + 'fuchsia': (255, 0, 255, 1), + 'gainsboro': (220, 220, 220, 1), + 'ghostwhite': (248, 248, 255, 1), + 'gold': (255, 215, 0, 1), + 'goldenrod': (218, 165, 32, 1), + 'gray': (128, 128, 128, 1), + 'green': (0, 128, 0, 1), + 'greenyellow': (173, 255, 47, 1), + 'honeydew': (240, 255, 240, 1), + 'hotpink': (255, 105, 180, 1), + 'indianred': (205, 92, 92, 1), + 'indigo': (75, 0, 130, 1), + 'ivory': (255, 255, 240, 1), + 'khaki': (240, 230, 140, 1), + 'lavender': (230, 230, 250, 1), + 'lavenderblush': (255, 240, 245, 1), + 'lawngreen': (124, 252, 0, 1), + 'lemonchiffon': (255, 250, 205, 1), + 'lightblue': (173, 216, 230, 1), + 'lightcoral': (240, 128, 128, 1), + 'lightcyan': (224, 255, 255, 1), + 'lightgoldenrodyellow': (250, 250, 210, 1), + 'lightgreen': (144, 238, 144, 1), + 'lightgrey': (211, 211, 211, 1), + 'lightpink': (255, 182, 193, 1), + 'lightsalmon': (255, 160, 122, 1), + 'lightseagreen': (32, 178, 170, 1), + 'lightskyblue': (135, 206, 250, 1), + 'lightslategray': (119, 136, 153, 1), + 'lightsteelblue': (176, 196, 222, 1), + 'lightyellow': (255, 255, 224, 1), + 'lime': (0, 255, 0, 1), + 'limegreen': (50, 205, 50, 1), + 'linen': (250, 240, 230, 1), + 'magenta': (255, 0, 255, 1), + 'maroon': (128, 0, 0, 1), + 'mediumaquamarine': (102, 205, 170, 1), + 'mediumblue': (0, 0, 205, 1), + 'mediumorchid': (186, 85, 211, 1), + 'mediumpurple': (147, 112, 219, 1), + 'mediumseagreen': (60, 179, 113, 1), + 'mediumslateblue': (123, 104, 238, 1), + 'mediumspringgreen': (0, 250, 154, 1), + 'mediumturquoise': (72, 209, 204, 1), + 'mediumvioletred': (199, 21, 133, 1), + 'midnightblue': (25, 25, 112, 1), + 'mintcream': (245, 255, 250, 1), + 'mistyrose': (255, 228, 225, 1), + 'moccasin': (255, 228, 181, 1), + 'navajowhite': (255, 222, 173, 1), + 'navy': (0, 0, 128, 1), + 'oldlace': (253, 245, 230, 1), + 'olive': (128, 128, 0, 1), + 'olivedrab': (107, 142, 35, 1), + 'orange': (255, 165, 0, 1), + 'orangered': (255, 69, 0, 1), + 'orchid': (218, 112, 214, 1), + 'palegoldenrod': (238, 232, 170, 1), + 'palegreen': (152, 251, 152, 1), + 'paleturquoise': (175, 238, 238, 1), + 'palevioletred': (219, 112, 147, 1), + 'papayawhip': (255, 239, 213, 1), + 'peachpuff': (255, 218, 185, 1), + 'peru': (205, 133, 63, 1), + 'pink': (255, 192, 203, 1), + 'plum': (221, 160, 221, 1), + 'powderblue': (176, 224, 230, 1), + 'purple': (128, 0, 128, 1), + 'red': (255, 0, 0, 1), + 'rosybrown': (188, 143, 143, 1), + 'royalblue': (65, 105, 225, 1), + 'saddlebrown': (139, 69, 19, 1), + 'salmon': (250, 128, 114, 1), + 'sandybrown': (244, 164, 96, 1), + 'seagreen': (46, 139, 87, 1), + 'seashell': (255, 245, 238, 1), + 'sienna': (160, 82, 45, 1), + 'silver': (192, 192, 192, 1), + 'skyblue': (135, 206, 235, 1), + 'slateblue': (106, 90, 205, 1), + 'slategray': (112, 128, 144, 1), + 'snow': (255, 250, 250, 1), + 'springgreen': (0, 255, 127, 1), + 'steelblue': (70, 130, 180, 1), + 'tan': (210, 180, 140, 1), + 'teal': (0, 128, 128, 1), + 'thistle': (216, 191, 216, 1), + 'tomato': (255, 99, 71, 1), + 'transparent': (0, 0, 0, 0), + 'turquoise': (64, 224, 208, 1), + 'violet': (238, 130, 238, 1), + 'wheat': (245, 222, 179, 1), + 'white': (255, 255, 255, 1), + 'whitesmoke': (245, 245, 245, 1), + 'yellow': (255, 255, 0, 1), + 'yellowgreen': (154, 205, 50, 1), +} +COLOR_LOOKUP = dict((v, k) for (k, v) in COLOR_NAMES.items()) + +# ------------------------------------------------------------------------------ +# Built-in CSS units +# See: http://www.w3.org/TR/2013/CR-css3-values-20130730/#numeric-types + +# Maps units to a set of common units per type, with conversion factors +BASE_UNIT_CONVERSIONS = { + # Lengths + 'mm': (1, 'mm'), + 'cm': (10, 'mm'), + 'in': (25.4, 'mm'), + 'px': (25.4 / 96, 'mm'), + 'pt': (25.4 / 72, 'mm'), + 'pc': (25.4 / 6, 'mm'), + + # Angles + 'deg': (1 / 360, 'turn'), + 'grad': (1 / 400, 'turn'), + 'rad': (pi / 2, 'turn'), + 'turn': (1, 'turn'), + + # Times + 'ms': (1, 'ms'), + 's': (1000, 'ms'), + + # Frequencies + 'hz': (1, 'hz'), + 'khz': (1000, 'hz'), + + # Resolutions + 'dpi': (1, 'dpi'), + 'dpcm': (2.54, 'dpi'), + 'dppx': (96, 'dpi'), +} + + +def get_conversion_factor(unit): + """Look up the "base" unit for this unit and the factor for converting to + it. + + Returns a 2-tuple of `factor, base_unit`. + """ + if unit in BASE_UNIT_CONVERSIONS: + return BASE_UNIT_CONVERSIONS[unit] + else: + return 1, unit + + +def convert_units_to_base_units(units): + """Convert a set of units into a set of "base" units. + + Returns a 2-tuple of `factor, new_units`. + """ + total_factor = 1 + new_units = [] + for unit in units: + if unit not in BASE_UNIT_CONVERSIONS: + continue + + factor, new_unit = BASE_UNIT_CONVERSIONS[unit] + total_factor *= factor + new_units.append(new_unit) + + new_units.sort() + return total_factor, tuple(new_units) + + +def count_base_units(units): + """Returns a dict mapping names of base units to how many times they + appear in the given iterable of units. Effectively this counts how + many length units you have, how many time units, and so forth. + """ + ret = {} + for unit in units: + factor, base_unit = get_conversion_factor(unit) + + ret.setdefault(base_unit, 0) + ret[base_unit] += 1 + + return ret + + +def cancel_base_units(units, to_remove): + """Given a list of units, remove a specified number of each base unit. + + Arguments: + units: an iterable of units + to_remove: a mapping of base_unit => count, such as that returned from + count_base_units + + Returns a 2-tuple of (factor, remaining_units). + """ + + # Copy the dict since we're about to mutate it + to_remove = to_remove.copy() + remaining_units = [] + total_factor = 1 + + for unit in units: + factor, base_unit = get_conversion_factor(unit) + if not to_remove.get(base_unit, 0): + remaining_units.append(unit) + continue + + total_factor *= factor + to_remove[base_unit] -= 1 + + return total_factor, remaining_units + + +# A fixed set of units can be omitted when the value is 0 +# See: http://www.w3.org/TR/2013/CR-css3-values-20130730/#lengths +ZEROABLE_UNITS = frozenset(( + # Relative lengths + 'em', 'ex', 'ch', 'rem', + # Viewport + 'vw', 'vh', 'vmin', 'vmax', + # Absolute lengths + 'cm', 'mm', 'in', 'px', 'pt', 'pc', +)) + + +# ------------------------------------------------------------------------------ +# Built-in CSS function reference + +# Known function names +BUILTIN_FUNCTIONS = frozenset([ + # CSS2 + 'attr', 'counter', 'counters', 'url', 'rgb', 'rect', + + # CSS3 values: http://www.w3.org/TR/css3-values/ + 'calc', 'min', 'max', 'cycle', + + # CSS3 colors: http://www.w3.org/TR/css3-color/ + 'rgba', 'hsl', 'hsla', + + # CSS3 fonts: http://www.w3.org/TR/css3-fonts/ + 'local', 'format', + + # CSS3 images: http://www.w3.org/TR/css3-images/ + 'image', 'element', + 'linear-gradient', 'radial-gradient', + 'repeating-linear-gradient', 'repeating-radial-gradient', + + # CSS3 transforms: http://www.w3.org/TR/css3-transforms/ + 'perspective', + 'matrix', 'matrix3d', + 'rotate', 'rotateX', 'rotateY', 'rotateZ', 'rotate3d', + 'translate', 'translateX', 'translateY', 'translateZ', 'translate3d', + 'scale', 'scaleX', 'scaleY', 'scaleZ', 'scale3d', + 'skew', 'skewX', 'skewY', + + # CSS3 transitions: http://www.w3.org/TR/css3-transitions/ + 'cubic-bezier', 'steps', + + # CSS filter effects: + # https://dvcs.w3.org/hg/FXTF/raw-file/tip/filters/index.html + 'grayscale', 'sepia', 'saturate', 'hue-rotate', 'invert', 'opacity', + 'brightness', 'contrast', 'blur', 'drop-shadow', 'custom', + + # CSS4 image module: + # http://dev.w3.org/csswg/css-images/ + 'image-set', 'cross-fade', + 'conic-gradient', 'repeating-conic-gradient', + + # Others + 'color-stop', # Older version of CSS3 gradients + 'mask', # ??? + 'from', 'to', # Very old WebKit gradient syntax +]) + + +def is_builtin_css_function(name): + """Returns whether the given `name` looks like the name of a builtin CSS + function. + + Unrecognized functions not in this list produce warnings. + """ + name = name.replace('_', '-') + + if name in BUILTIN_FUNCTIONS: + return True + + # Vendor-specific functions (-foo-bar) are always okay + if name[0] == '-' and '-' in name[1:]: + return True + + return False + +# ------------------------------------------------------------------------------ +# Bits and pieces of grammar, as regexen + +SEPARATOR = '\x00' + +_expr_glob_re = re.compile(r''' + \#\{(.*?)\} # Global Interpolation only +''', re.VERBOSE) + +# XXX these still need to be fixed; the //-in-functions thing is a chumpy hack +_ml_comment_re = re.compile(r'\/\*(.*?)\*\/', re.DOTALL) +_sl_comment_re = re.compile(r'(? 1: + log.error("Undefined variable '%s'", n, extra={'stack': True}) + return n + else: + if v: + if not isinstance(v, six.string_types): + v = v.render() + # TODO this used to test for _dequote + if m.group(1): + v = dequote(v) + else: + v = m.group(0) + return v + + cont = _interpolate_re.sub(_av, cont) + # TODO this is surprising and shouldn't be here + cont = self.do_glob_math(cont) + return cont + + def calculate(self, _base_str, divide=False): + better_expr_str = _base_str + + better_expr_str = self.do_glob_math(better_expr_str) + + better_expr_str = self.evaluate_expression(better_expr_str, divide=divide) + + if better_expr_str is None: + better_expr_str = String.unquoted(self.apply_vars(_base_str)) + + return better_expr_str + + # TODO only used by magic-import...? + def interpolate(self, var): + value = self.namespace.variable(var) + if var != value and isinstance(value, six.string_types): + _vi = self.evaluate_expression(value) + if _vi is not None: + value = _vi + return value + + def evaluate_expression(self, expr, divide=False): + try: + ast = self.parse_expression(expr) + except SassError: + if config.DEBUG: + raise + else: + return None + + try: + return ast.evaluate(self, divide=divide) + except Exception as e: + raise SassEvaluationError(e, expression=expr) + + def parse_expression(self, expr, target='goal'): + if not isinstance(expr, six.string_types): + raise TypeError("Expected string, got %r" % (expr,)) + + key = (target, expr) + if key in self.ast_cache: + return self.ast_cache[key] + + try: + parser = SassExpression(SassExpressionScanner(expr)) + ast = getattr(parser, target)() + except SyntaxError as e: + raise SassParseError(e, expression=expr, expression_pos=parser._char_pos) + + self.ast_cache[key] = ast + return ast + + +# ------------------------------------------------------------------------------ +# Expression classes -- the AST resulting from a parse + +class Expression(object): + def __repr__(self): + return '<%s()>' % (self.__class__.__name__) + + def evaluate(self, calculator, divide=False): + """Evaluate this AST node, and return a Sass value. + + `divide` indicates whether a descendant node representing a division + should be forcibly treated as a division. See the commentary in + `BinaryOp`. + """ + raise NotImplementedError + + +class Parentheses(Expression): + """An expression of the form `(foo)`. + + Only exists to force a slash to be interpreted as division when contained + within parentheses. + """ + def __repr__(self): + return '<%s(%s)>' % (self.__class__.__name__, repr(self.contents)) + + def __init__(self, contents): + self.contents = contents + + def evaluate(self, calculator, divide=False): + return self.contents.evaluate(calculator, divide=True) + + +class UnaryOp(Expression): + def __repr__(self): + return '<%s(%s, %s)>' % (self.__class__.__name__, repr(self.op), repr(self.operand)) + + def __init__(self, op, operand): + self.op = op + self.operand = operand + + def evaluate(self, calculator, divide=False): + return self.op(self.operand.evaluate(calculator, divide=True)) + + +class BinaryOp(Expression): + def __repr__(self): + return '<%s(%s, %s, %s)>' % (self.__class__.__name__, repr(self.op), repr(self.left), repr(self.right)) + + def __init__(self, op, left, right): + self.op = op + self.left = left + self.right = right + + def evaluate(self, calculator, divide=False): + left = self.left.evaluate(calculator, divide=True) + right = self.right.evaluate(calculator, divide=True) + + # Special handling of division: treat it as a literal slash if both + # operands are literals, there are parentheses, or this is part of a + # bigger expression. + # The first condition is covered by the type check. The other two are + # covered by the `divide` argument: other nodes that perform arithmetic + # will pass in True, indicating that this should always be a division. + if ( + self.op is operator.truediv + and not divide + and isinstance(self.left, Literal) + and isinstance(self.right, Literal) + ): + return String(left.render() + ' / ' + right.render(), quotes=None) + + return self.op(left, right) + + +class AnyOp(Expression): + def __repr__(self): + return '<%s(*%s)>' % (self.__class__.__name__, repr(self.op), repr(self.operands)) + + def __init__(self, *operands): + self.operands = operands + + def evaluate(self, calculator, divide=False): + for operand in self.operands: + value = operand.evaluate(calculator, divide=True) + if value: + return value + return value + + +class AllOp(Expression): + def __repr__(self): + return '<%s(*%s)>' % (self.__class__.__name__, repr(self.operands)) + + def __init__(self, *operands): + self.operands = operands + + def evaluate(self, calculator, divide=False): + for operand in self.operands: + value = operand.evaluate(calculator, divide=True) + if not value: + return value + return value + + +class NotOp(Expression): + def __repr__(self): + return '<%s(%s)>' % (self.__class__.__name__, repr(self.operand)) + + def __init__(self, operand): + self.operand = operand + + def evaluate(self, calculator, divide=False): + operand = self.operand.evaluate(calculator, divide=True) + return Boolean(not(operand)) + + +class CallOp(Expression): + def __repr__(self): + return '<%s(%s, %s)>' % (self.__class__.__name__, repr(self.func_name), repr(self.argspec)) + + def __init__(self, func_name, argspec): + self.func_name = func_name + self.argspec = argspec + + def evaluate(self, calculator, divide=False): + # TODO bake this into the context and options "dicts", plus library + func_name = normalize_var(self.func_name) + + argspec_node = self.argspec + + # Turn the pairs of arg tuples into *args and **kwargs + # TODO unclear whether this is correct -- how does arg, kwarg, arg + # work? + args, kwargs = argspec_node.evaluate_call_args(calculator) + argspec_len = len(args) + len(kwargs) + + # Translate variable names to Python identifiers + # TODO what about duplicate kw names? should this happen in argspec? + # how does that affect mixins? + kwargs = dict( + (key.lstrip('$').replace('-', '_'), value) + for key, value in kwargs.items()) + + # TODO merge this with the library + funct = None + try: + funct = calculator.namespace.function(func_name, argspec_len) + # @functions take a ns as first arg. TODO: Python functions possibly + # should too + if getattr(funct, '__name__', None) == '__call': + funct = partial(funct, calculator.namespace) + except KeyError: + try: + # DEVIATION: Fall back to single parameter + funct = calculator.namespace.function(func_name, 1) + args = [List(args, use_comma=True)] + except KeyError: + if not is_builtin_css_function(func_name): + log.error("Function not found: %s:%s", func_name, argspec_len, extra={'stack': True}) + + if funct: + ret = funct(*args, **kwargs) + if not isinstance(ret, Value): + raise TypeError("Expected Sass type as return value, got %r" % (ret,)) + return ret + + # No matching function found, so render the computed values as a CSS + # function call. Slurpy arguments are expanded and named arguments are + # unsupported. + if kwargs: + raise TypeError("The CSS function %s doesn't support keyword arguments." % (func_name,)) + + # TODO another candidate for a "function call" sass type + rendered_args = [arg.render() for arg in args] + + return String( + u"%s(%s)" % (func_name, u", ".join(rendered_args)), + quotes=None) + + +class Literal(Expression): + def __repr__(self): + return '<%s(%s)>' % (self.__class__.__name__, repr(self.value)) + + def __init__(self, value): + if isinstance(value, Undefined) and config.FATAL_UNDEFINED: + raise SyntaxError("Undefined literal.") + else: + self.value = value + + def evaluate(self, calculator, divide=False): + return self.value + + +class Variable(Expression): + def __repr__(self): + return '<%s(%s)>' % (self.__class__.__name__, repr(self.name)) + + def __init__(self, name): + self.name = name + + def evaluate(self, calculator, divide=False): + try: + value = calculator.namespace.variable(self.name) + except KeyError: + if config.FATAL_UNDEFINED: + raise SyntaxError("Undefined variable: '%s'." % self.name) + else: + if config.VERBOSITY > 1: + log.error("Undefined variable '%s'", self.name, extra={'stack': True}) + return Undefined() + else: + if isinstance(value, six.string_types): + evald = calculator.evaluate_expression(value) + if evald is not None: + return evald + return value + + +class ListLiteral(Expression): + def __repr__(self): + return '<%s(%s, comma=%s)>' % (self.__class__.__name__, repr(self.items), repr(self.comma)) + + def __init__(self, items, comma=True): + self.items = items + self.comma = comma + + def evaluate(self, calculator, divide=False): + items = [item.evaluate(calculator, divide=divide) for item in self.items] + + # Whether this is a "plain" literal matters for null removal: nulls are + # left alone if this is a completely vanilla CSS property + is_literal = True + if divide: + # TODO sort of overloading "divide" here... rename i think + is_literal = False + elif not all(isinstance(item, Literal) for item in self.items): + is_literal = False + + return List(items, use_comma=self.comma, is_literal=is_literal) + + +class MapLiteral(Expression): + def __repr__(self): + return '<%s(%s)>' % (self.__class__.__name__, repr(self.pairs)) + + def __init__(self, pairs): + self.pairs = tuple((var, value) for var, value in pairs if value is not None) + + def evaluate(self, calculator, divide=False): + scss_pairs = [] + for key, value in self.pairs: + scss_pairs.append(( + key.evaluate(calculator), + value.evaluate(calculator), + )) + + return Map(scss_pairs) + + +class ArgspecLiteral(Expression): + """Contains pairs of argument names and values, as parsed from a function + definition or function call. + + Note that the semantics are somewhat ambiguous. Consider parsing: + + $foo, $bar: 3 + + If this appeared in a function call, $foo would refer to a value; if it + appeared in a function definition, $foo would refer to an existing + variable. This it's up to the caller to use the right iteration function. + """ + def __repr__(self): + return '<%s(%s)>' % (self.__class__.__name__, repr(self.argpairs)) + + def __init__(self, argpairs, slurp=None): + # argpairs is a list of 2-tuples, parsed as though this were a function + # call, so (variable name as string or None, default value as AST + # node). + # slurp is the name of a variable to receive slurpy arguments. + self.argpairs = tuple(argpairs) + if slurp is all: + # DEVIATION: special syntax to allow injecting arbitrary arguments + # from the caller to the callee + self.inject = True + self.slurp = None + elif slurp: + self.inject = False + self.slurp = Variable(slurp) + else: + self.inject = False + self.slurp = None + + def iter_list_argspec(self): + yield None, ListLiteral(zip(*self.argpairs)[1]) + + def iter_def_argspec(self): + """Interpreting this literal as a function definition, yields pairs of + (variable name as a string, default value as an AST node or None). + """ + started_kwargs = False + seen_vars = set() + + for var, value in self.argpairs: + if var is None: + # value is actually the name + var = value + value = None + + if started_kwargs: + raise SyntaxError( + "Required argument %r must precede optional arguments" + % (var.name,)) + + else: + started_kwargs = True + + if not isinstance(var, Variable): + raise SyntaxError("Expected variable name, got %r" % (var,)) + + if var.name in seen_vars: + raise SyntaxError("Duplicate argument %r" % (var.name,)) + seen_vars.add(var.name) + + yield var.name, value + + def evaluate_call_args(self, calculator): + """Interpreting this literal as a function call, return a 2-tuple of + ``(args, kwargs)``. + """ + args = [] + kwargs = {} + for var_node, value_node in self.argpairs: + value = value_node.evaluate(calculator, divide=True) + if var_node is None: + # Positional + args.append(value) + else: + # Named + if not isinstance(var_node, Variable): + raise SyntaxError("Expected variable name, got %r" % (var_node,)) + kwargs[var_node.name] = value + + # Slurpy arguments go on the end of the args + if self.slurp: + args.extend(self.slurp.evaluate(calculator, divide=True)) + + return args, kwargs + + +def parse_bareword(word): + if word in COLOR_NAMES: + return Color.from_name(word) + elif word == 'null': + return Null() + elif word == 'undefined': + return Undefined() + elif word == 'true': + return Boolean(True) + elif word == 'false': + return Boolean(False) + else: + return String(word, quotes=None) + + +class Parser(object): + def __init__(self, scanner): + self._scanner = scanner + self._pos = 0 + self._char_pos = 0 + + def reset(self, input): + self._scanner.reset(input) + self._pos = 0 + self._char_pos = 0 + + def _peek(self, types): + """ + Returns the token type for lookahead; if there are any args + then the list of args is the set of token types to allow + """ + try: + tok = self._scanner.token(self._pos, types) + return tok[2] + except SyntaxError: + return None + + def _scan(self, type): + """ + Returns the matched text, and moves to the next token + """ + tok = self._scanner.token(self._pos, set([type])) + self._char_pos = tok[0] + if tok[2] != type: + raise SyntaxError("SyntaxError[@ char %s: %s]" % (repr(tok[0]), "Trying to find " + type)) + self._pos += 1 + return tok[3] + + +################################################################################ +## Grammar compiled using Yapps: + +class SassExpressionScanner(Scanner): + patterns = None + _patterns = [ + ('":"', ':'), + ('","', ','), + ('[ \r\t\n]+', '[ \r\t\n]+'), + ('LPAR', '\\(|\\['), + ('RPAR', '\\)|\\]'), + ('END', '$'), + ('MUL', '[*]'), + ('DIV', '/'), + ('ADD', '[+]'), + ('SUB', '-\\s'), + ('SIGN', '-(?![a-zA-Z_])'), + ('AND', '(?='), + ('LT', '<'), + ('GT', '>'), + ('DOTDOTDOT', '[.]{3}'), + ('KWSTR', "'[^']*'(?=\\s*:)"), + ('STR', "'[^']*'"), + ('KWQSTR', '"[^"]*"(?=\\s*:)'), + ('QSTR', '"[^"]*"'), + ('UNITS', '(? 1 else v[0] + + def expr_slst(self): + or_expr = self.or_expr() + v = [or_expr] + while self._peek(self.expr_slst_rsts) not in self.argspec_items_rsts: + or_expr = self.or_expr() + v.append(or_expr) + return ListLiteral(v, comma=False) if len(v) > 1 else v[0] + + def or_expr(self): + and_expr = self.and_expr() + v = and_expr + while self._peek(self.or_expr_rsts) == 'OR': + OR = self._scan('OR') + and_expr = self.and_expr() + v = AnyOp(v, and_expr) + return v + + def and_expr(self): + not_expr = self.not_expr() + v = not_expr + while self._peek(self.and_expr_rsts) == 'AND': + AND = self._scan('AND') + not_expr = self.not_expr() + v = AllOp(v, not_expr) + return v + + def not_expr(self): + _token_ = self._peek(self.argspec_item_chks) + if _token_ != 'NOT': + comparison = self.comparison() + return comparison + else: # == 'NOT' + NOT = self._scan('NOT') + not_expr = self.not_expr() + return NotOp(not_expr) + + def comparison(self): + a_expr = self.a_expr() + v = a_expr + while self._peek(self.comparison_rsts) in self.comparison_chks: + _token_ = self._peek(self.comparison_chks) + if _token_ == 'LT': + LT = self._scan('LT') + a_expr = self.a_expr() + v = BinaryOp(operator.lt, v, a_expr) + elif _token_ == 'GT': + GT = self._scan('GT') + a_expr = self.a_expr() + v = BinaryOp(operator.gt, v, a_expr) + elif _token_ == 'LE': + LE = self._scan('LE') + a_expr = self.a_expr() + v = BinaryOp(operator.le, v, a_expr) + elif _token_ == 'GE': + GE = self._scan('GE') + a_expr = self.a_expr() + v = BinaryOp(operator.ge, v, a_expr) + elif _token_ == 'EQ': + EQ = self._scan('EQ') + a_expr = self.a_expr() + v = BinaryOp(operator.eq, v, a_expr) + else: # == 'NE' + NE = self._scan('NE') + a_expr = self.a_expr() + v = BinaryOp(operator.ne, v, a_expr) + return v + + def a_expr(self): + m_expr = self.m_expr() + v = m_expr + while self._peek(self.a_expr_rsts) in self.a_expr_chks: + _token_ = self._peek(self.a_expr_chks) + if _token_ == 'ADD': + ADD = self._scan('ADD') + m_expr = self.m_expr() + v = BinaryOp(operator.add, v, m_expr) + else: # == 'SUB' + SUB = self._scan('SUB') + m_expr = self.m_expr() + v = BinaryOp(operator.sub, v, m_expr) + return v + + def m_expr(self): + u_expr = self.u_expr() + v = u_expr + while self._peek(self.m_expr_rsts) in self.m_expr_chks: + _token_ = self._peek(self.m_expr_chks) + if _token_ == 'MUL': + MUL = self._scan('MUL') + u_expr = self.u_expr() + v = BinaryOp(operator.mul, v, u_expr) + else: # == 'DIV' + DIV = self._scan('DIV') + u_expr = self.u_expr() + v = BinaryOp(operator.truediv, v, u_expr) + return v + + def u_expr(self): + _token_ = self._peek(self.u_expr_rsts) + if _token_ == 'SIGN': + SIGN = self._scan('SIGN') + u_expr = self.u_expr() + return UnaryOp(operator.neg, u_expr) + elif _token_ == 'ADD': + ADD = self._scan('ADD') + u_expr = self.u_expr() + return UnaryOp(operator.pos, u_expr) + else: # in self.u_expr_chks + atom = self.atom() + return atom + + def atom(self): + _token_ = self._peek(self.u_expr_chks) + if _token_ == 'LPAR': + LPAR = self._scan('LPAR') + _token_ = self._peek(self.atom_rsts) + if _token_ not in self.argspec_item_chks: + expr_map = self.expr_map() + v = expr_map + else: # in self.argspec_item_chks + expr_lst = self.expr_lst() + v = expr_lst + RPAR = self._scan('RPAR') + return Parentheses(v) + elif _token_ == 'FNCT': + FNCT = self._scan('FNCT') + LPAR = self._scan('LPAR') + argspec = self.argspec() + RPAR = self._scan('RPAR') + return CallOp(FNCT, argspec) + elif _token_ == 'BANG_IMPORTANT': + BANG_IMPORTANT = self._scan('BANG_IMPORTANT') + return Literal(String(BANG_IMPORTANT, quotes=None)) + elif _token_ == 'ID': + ID = self._scan('ID') + return Literal(parse_bareword(ID)) + elif _token_ == 'NUM': + NUM = self._scan('NUM') + UNITS = None + if self._peek(self.atom_rsts_) == 'UNITS': + UNITS = self._scan('UNITS') + return Literal(Number(float(NUM), unit=UNITS)) + elif _token_ == 'STR': + STR = self._scan('STR') + return Literal(String(STR[1:-1], quotes="'")) + elif _token_ == 'QSTR': + QSTR = self._scan('QSTR') + return Literal(String(QSTR[1:-1], quotes='"')) + elif _token_ == 'COLOR': + COLOR = self._scan('COLOR') + return Literal(Color.from_hex(COLOR, literal=True)) + else: # == 'VAR' + VAR = self._scan('VAR') + return Variable(VAR) + + def kwatom(self): + _token_ = self._peek(self.kwatom_rsts) + if _token_ == '":"': + pass + elif _token_ == 'KWID': + KWID = self._scan('KWID') + return Literal(parse_bareword(KWID)) + elif _token_ == 'KWNUM': + KWNUM = self._scan('KWNUM') + UNITS = None + if self._peek(self.kwatom_rsts_) == 'UNITS': + UNITS = self._scan('UNITS') + return Literal(Number(float(KWNUM), unit=UNITS)) + elif _token_ == 'KWSTR': + KWSTR = self._scan('KWSTR') + return Literal(String(KWSTR[1:-1], quotes="'")) + elif _token_ == 'KWQSTR': + KWQSTR = self._scan('KWQSTR') + return Literal(String(KWQSTR[1:-1], quotes='"')) + elif _token_ == 'KWCOLOR': + KWCOLOR = self._scan('KWCOLOR') + return Literal(Color.from_hex(COLOR, literal=True)) + else: # == 'KWVAR' + KWVAR = self._scan('KWVAR') + return Variable(KWVAR) + + u_expr_chks = set(['LPAR', 'COLOR', 'QSTR', 'NUM', 'FNCT', 'STR', 'VAR', 'BANG_IMPORTANT', 'ID']) + m_expr_rsts = set(['LPAR', 'SUB', 'QSTR', 'RPAR', 'MUL', 'DIV', 'BANG_IMPORTANT', 'LE', 'COLOR', 'NE', 'LT', 'NUM', 'GT', 'END', 'SIGN', 'GE', 'FNCT', 'STR', 'VAR', 'EQ', 'ID', 'AND', 'ADD', 'NOT', 'OR', '","']) + argspec_items_rsts = set(['RPAR', 'END', '","']) + expr_map_rsts = set(['RPAR', '","']) + argspec_items_rsts__ = set(['KWVAR', 'LPAR', 'QSTR', 'SLURPYVAR', 'COLOR', 'DOTDOTDOT', 'SIGN', 'VAR', 'ADD', 'NUM', 'FNCT', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID']) + kwatom_rsts = set(['KWVAR', 'KWID', 'KWSTR', 'KWQSTR', 'KWCOLOR', '":"', 'KWNUM']) + argspec_item_chks = set(['LPAR', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'FNCT', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID']) + a_expr_chks = set(['ADD', 'SUB']) + expr_slst_rsts = set(['LPAR', 'END', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'RPAR', 'FNCT', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID', '","']) + or_expr_rsts = set(['LPAR', 'END', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'RPAR', 'FNCT', 'STR', 'NOT', 'ID', 'BANG_IMPORTANT', 'OR', '","']) + and_expr_rsts = set(['AND', 'LPAR', 'END', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'RPAR', 'FNCT', 'STR', 'NOT', 'ID', 'BANG_IMPORTANT', 'OR', '","']) + comparison_rsts = set(['LPAR', 'QSTR', 'RPAR', 'BANG_IMPORTANT', 'LE', 'COLOR', 'NE', 'LT', 'NUM', 'GT', 'END', 'SIGN', 'ADD', 'FNCT', 'STR', 'VAR', 'EQ', 'ID', 'AND', 'GE', 'NOT', 'OR', '","']) + argspec_chks = set(['DOTDOTDOT', 'SLURPYVAR']) + atom_rsts_ = set(['LPAR', 'SUB', 'QSTR', 'RPAR', 'VAR', 'MUL', 'DIV', 'BANG_IMPORTANT', 'LE', 'COLOR', 'NE', 'LT', 'NUM', 'GT', 'END', 'SIGN', 'GE', 'FNCT', 'STR', 'UNITS', 'EQ', 'ID', 'AND', 'ADD', 'NOT', 'OR', '","']) + expr_map_rsts_ = set(['KWVAR', 'KWID', 'KWSTR', 'KWQSTR', 'RPAR', 'KWCOLOR', '":"', 'KWNUM', '","']) + u_expr_rsts = set(['LPAR', 'COLOR', 'QSTR', 'SIGN', 'ADD', 'NUM', 'FNCT', 'STR', 'VAR', 'BANG_IMPORTANT', 'ID']) + comparison_chks = set(['GT', 'GE', 'NE', 'LT', 'LE', 'EQ']) + argspec_items_rsts_ = set(['KWVAR', 'LPAR', 'QSTR', 'END', 'SLURPYVAR', 'COLOR', 'DOTDOTDOT', 'SIGN', 'VAR', 'ADD', 'NUM', 'RPAR', 'FNCT', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID']) + a_expr_rsts = set(['LPAR', 'SUB', 'QSTR', 'RPAR', 'BANG_IMPORTANT', 'LE', 'COLOR', 'NE', 'LT', 'NUM', 'GT', 'END', 'SIGN', 'GE', 'FNCT', 'STR', 'VAR', 'EQ', 'ID', 'AND', 'ADD', 'NOT', 'OR', '","']) + m_expr_chks = set(['MUL', 'DIV']) + kwatom_rsts_ = set(['UNITS', '":"']) + argspec_items_chks = set(['KWVAR', 'LPAR', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'FNCT', 'STR', 'NOT', 'BANG_IMPORTANT', 'ID']) + argspec_rsts = set(['KWVAR', 'LPAR', 'BANG_IMPORTANT', 'END', 'SLURPYVAR', 'COLOR', 'DOTDOTDOT', 'RPAR', 'VAR', 'ADD', 'NUM', 'FNCT', 'STR', 'NOT', 'QSTR', 'SIGN', 'ID']) + atom_rsts = set(['KWVAR', 'KWID', 'KWSTR', 'BANG_IMPORTANT', 'LPAR', 'COLOR', 'KWQSTR', 'SIGN', 'KWCOLOR', 'VAR', 'ADD', 'NUM', '":"', 'STR', 'NOT', 'QSTR', 'KWNUM', 'ID', 'FNCT']) + argspec_chks_ = set(['END', 'RPAR']) + argspec_rsts_ = set(['KWVAR', 'LPAR', 'BANG_IMPORTANT', 'END', 'COLOR', 'QSTR', 'SIGN', 'VAR', 'ADD', 'NUM', 'FNCT', 'STR', 'NOT', 'RPAR', 'ID']) + + +### Grammar ends. +################################################################################ + +__all__ = ('Calculator',) diff --git a/libs/scss/functions/__init__.py b/libs/scss/functions/__init__.py new file mode 100644 index 0000000..0d76eaf --- /dev/null +++ b/libs/scss/functions/__init__.py @@ -0,0 +1,25 @@ +from __future__ import absolute_import + +from scss.functions.library import FunctionLibrary + +from scss.functions.core import CORE_LIBRARY +from scss.functions.extra import EXTRA_LIBRARY +from scss.functions.compass.sprites import COMPASS_SPRITES_LIBRARY +from scss.functions.compass.gradients import COMPASS_GRADIENTS_LIBRARY +from scss.functions.compass.helpers import COMPASS_HELPERS_LIBRARY +from scss.functions.compass.images import COMPASS_IMAGES_LIBRARY + + +ALL_BUILTINS_LIBRARY = FunctionLibrary() +ALL_BUILTINS_LIBRARY.inherit( + CORE_LIBRARY, + EXTRA_LIBRARY, + COMPASS_GRADIENTS_LIBRARY, + COMPASS_HELPERS_LIBRARY, + COMPASS_IMAGES_LIBRARY, + COMPASS_SPRITES_LIBRARY, +) + +# TODO back-compat for the only codebase still using the old name :) +FunctionRegistry = FunctionLibrary +scss_builtins = ALL_BUILTINS_LIBRARY diff --git a/libs/scss/functions/compass/__init__.py b/libs/scss/functions/compass/__init__.py new file mode 100644 index 0000000..73cb9df --- /dev/null +++ b/libs/scss/functions/compass/__init__.py @@ -0,0 +1,2 @@ +# Global cache of image sizes, shared between sprites and images libraries. +_image_size_cache = {} diff --git a/libs/scss/functions/compass/gradients.py b/libs/scss/functions/compass/gradients.py new file mode 100644 index 0000000..6f2b5b8 --- /dev/null +++ b/libs/scss/functions/compass/gradients.py @@ -0,0 +1,400 @@ +"""Utilities for working with gradients. Inspired by Compass, but not quite +the same. +""" + +from __future__ import absolute_import + +import base64 +import logging + +import six + +from scss.functions.library import FunctionLibrary +from scss.functions.compass.helpers import opposite_position, position +from scss.types import Color, List, Number, String +from scss.util import escape, split_params, to_float, to_str + +log = logging.getLogger(__name__) + +COMPASS_GRADIENTS_LIBRARY = FunctionLibrary() +register = COMPASS_GRADIENTS_LIBRARY.register + + +# ------------------------------------------------------------------------------ + +def __color_stops(percentages, *args): + if len(args) == 1: + if isinstance(args[0], (list, tuple, List)): + list(args[0]) + elif isinstance(args[0], (String, six.string_types)): + color_stops = [] + colors = split_params(getattr(args[0], 'value', args[0])) + for color in colors: + color = color.strip() + if color.startswith('color-stop('): + s, c = split_params(color[11:].rstrip(')')) + s = s.strip() + c = c.strip() + else: + c, s = color.split() + color_stops.append((to_float(s), c)) + return color_stops + + colors = [] + stops = [] + prev_color = False + for c in args: + for c in List.from_maybe(c): + if isinstance(c, Color): + if prev_color: + stops.append(None) + colors.append(c) + prev_color = True + elif isinstance(c, Number): + stops.append(c) + prev_color = False + + if prev_color: + stops.append(None) + stops = stops[:len(colors)] + if stops[0] is None: + stops[0] = Number(0, '%') + if stops[-1] is None: + stops[-1] = Number(100, '%') + + maxable_stops = [s for s in stops if s and not s.is_simple_unit('%')] + if maxable_stops: + max_stops = max(maxable_stops) + else: + max_stops = None + + stops = [_s / max_stops if _s and not _s.is_simple_unit('%') else _s for _s in stops] + + init = 0 + start = None + for i, s in enumerate(stops + [1.0]): + if s is None: + if start is None: + start = i + end = i + else: + final = s + if start is not None: + stride = (final - init) / Number(end - start + 1 + (1 if i < len(stops) else 0)) + for j in range(start, end + 1): + stops[j] = init + stride * Number(j - start + 1) + init = final + start = None + + if not max_stops or percentages: + pass + else: + stops = [s if s.is_simple_unit('%') else s * max_stops for s in stops] + + return list(zip(stops, colors)) + + +def _render_standard_color_stops(color_stops): + pairs = [] + for i, (stop, color) in enumerate(color_stops): + if ((i == 0 and stop == Number(0, '%')) or + (i == len(color_stops) - 1 and stop == Number(100, '%'))): + pairs.append(color) + else: + pairs.append(List([color, stop], use_comma=False)) + + return List(pairs, use_comma=True) + + +@register('grad-color-stops') +def grad_color_stops(*args): + args = List.from_maybe_starargs(args) + color_stops = __color_stops(True, *args) + ret = ', '.join(['color-stop(%s, %s)' % (s.render(), c.render()) for s, c in color_stops]) + return String.unquoted(ret) + + +def __grad_end_position(radial, color_stops): + return __grad_position(-1, 100, radial, color_stops) + + +@register('grad-point') +def grad_point(*p): + pos = set() + hrz = vrt = Number(0.5, '%') + for _p in p: + pos.update(String.unquoted(_p).value.split()) + if 'left' in pos: + hrz = Number(0, '%') + elif 'right' in pos: + hrz = Number(1, '%') + if 'top' in pos: + vrt = Number(0, '%') + elif 'bottom' in pos: + vrt = Number(1, '%') + return List([v for v in (hrz, vrt) if v is not None]) + + +def __grad_position(index, default, radial, color_stops): + try: + stops = Number(color_stops[index][0]) + if radial and not stops.is_simple_unit('px') and (index == 0 or index == -1 or index == len(color_stops) - 1): + log.warn("Webkit only supports pixels for the start and end stops for radial gradients. Got %s", stops) + except IndexError: + stops = Number(default) + return stops + + +@register('grad-end-position') +def grad_end_position(*color_stops): + color_stops = __color_stops(False, *color_stops) + return Number(__grad_end_position(False, color_stops)) + + +@register('color-stops') +def color_stops(*args): + args = List.from_maybe_starargs(args) + color_stops = __color_stops(False, *args) + ret = ', '.join(['%s %s' % (c.render(), s.render()) for s, c in color_stops]) + return String.unquoted(ret) + + +@register('color-stops-in-percentages') +def color_stops_in_percentages(*args): + args = List.from_maybe_starargs(args) + color_stops = __color_stops(True, *args) + ret = ', '.join(['%s %s' % (c.render(), s.render()) for s, c in color_stops]) + return String.unquoted(ret) + + +def _get_gradient_position_and_angle(args): + for arg in args: + ret = None + skip = False + for a in arg: + if isinstance(a, Color): + skip = True + break + elif isinstance(a, Number): + ret = arg + if skip: + continue + if ret is not None: + return ret + for seek in ( + 'center', + 'top', 'bottom', + 'left', 'right', + ): + if String(seek) in arg: + return arg + return None + + +def _get_gradient_shape_and_size(args): + for arg in args: + for seek in ( + 'circle', 'ellipse', + 'closest-side', 'closest-corner', + 'farthest-side', 'farthest-corner', + 'contain', 'cover', + ): + if String(seek) in arg: + return arg + return None + + +def _get_gradient_color_stops(args): + color_stops = [] + for arg in args: + for a in List.from_maybe(arg): + if isinstance(a, Color): + color_stops.append(arg) + break + return color_stops or None + + +@register('radial-gradient') +def radial_gradient(*args): + args = List.from_maybe_starargs(args) + + position_and_angle = _get_gradient_position_and_angle(args) + shape_and_size = _get_gradient_shape_and_size(args) + color_stops = _get_gradient_color_stops(args) + if color_stops is None: + raise Exception('No color stops provided to radial-gradient function') + color_stops = __color_stops(False, *color_stops) + + args = [ + position(position_and_angle) if position_and_angle is not None else None, + shape_and_size if shape_and_size is not None else None, + ] + args.extend(_render_standard_color_stops(color_stops)) + + to__s = 'radial-gradient(' + ', '.join(to_str(a) for a in args or [] if a is not None) + ')' + ret = String.unquoted(to__s) + + def to__css2(): + return String.unquoted('') + ret.to__css2 = to__css2 + + def to__moz(): + return String.unquoted('-moz-' + to__s) + ret.to__moz = to__moz + + def to__pie(): + log.warn("PIE does not support radial-gradient.") + return String.unquoted('-pie-radial-gradient(unsupported)') + ret.to__pie = to__pie + + def to__webkit(): + return String.unquoted('-webkit-' + to__s) + ret.to__webkit = to__webkit + + def to__owg(): + args = [ + 'radial', + grad_point(position_and_angle) if position_and_angle is not None else 'center', + '0', + grad_point(position_and_angle) if position_and_angle is not None else 'center', + __grad_end_position(True, color_stops), + ] + args.extend('color-stop(%s, %s)' % (s.render(), c.render()) for s, c in color_stops) + ret = '-webkit-gradient(' + ', '.join(to_str(a) for a in args or [] if a is not None) + ')' + return String.unquoted(ret) + ret.to__owg = to__owg + + def to__svg(): + return radial_svg_gradient(color_stops, position_and_angle or 'center') + ret.to__svg = to__svg + + return ret + + +@register('linear-gradient') +def linear_gradient(*args): + args = List.from_maybe_starargs(args) + + position_and_angle = _get_gradient_position_and_angle(args) + color_stops = _get_gradient_color_stops(args) + if color_stops is None: + raise Exception('No color stops provided to linear-gradient function') + color_stops = __color_stops(False, *color_stops) + + args = [ + position(position_and_angle) if position_and_angle is not None else None, + ] + args.extend(_render_standard_color_stops(color_stops)) + + to__s = 'linear-gradient(' + ', '.join(to_str(a) for a in args or [] if a is not None) + ')' + ret = String.unquoted(to__s) + + def to__css2(): + return String.unquoted('') + ret.to__css2 = to__css2 + + def to__moz(): + return String.unquoted('-moz-' + to__s) + ret.to__moz = to__moz + + def to__pie(): + return String.unquoted('-pie-' + to__s) + ret.to__pie = to__pie + + def to__ms(): + return String.unquoted('-ms-' + to__s) + ret.to__ms = to__ms + + def to__o(): + return String.unquoted('-o-' + to__s) + ret.to__o = to__o + + def to__webkit(): + return String.unquoted('-webkit-' + to__s) + ret.to__webkit = to__webkit + + def to__owg(): + args = [ + 'linear', + position(position_and_angle or None), + opposite_position(position_and_angle or None), + ] + args.extend('color-stop(%s, %s)' % (s.render(), c.render()) for s, c in color_stops) + ret = '-webkit-gradient(' + ', '.join(to_str(a) for a in args if a is not None) + ')' + return String.unquoted(ret) + ret.to__owg = to__owg + + def to__svg(): + return linear_svg_gradient(color_stops, position_and_angle or 'top') + ret.to__svg = to__svg + + return ret + + +@register('radial-svg-gradient') +def radial_svg_gradient(*args): + args = List.from_maybe_starargs(args) + color_stops = args + center = None + if isinstance(args[-1], (String, Number, six.string_types)): + center = args[-1] + color_stops = args[:-1] + color_stops = __color_stops(False, *color_stops) + cx, cy = zip(*grad_point(center).items())[1] + r = __grad_end_position(True, color_stops) + svg = __radial_svg(color_stops, cx, cy, r) + url = 'data:' + 'image/svg+xml' + ';base64,' + base64.b64encode(svg) + inline = 'url("%s")' % escape(url) + return String.unquoted(inline) + + +@register('linear-svg-gradient') +def linear_svg_gradient(*args): + args = List.from_maybe_starargs(args) + color_stops = args + start = None + if isinstance(args[-1], (String, Number, six.string_types)): + start = args[-1] + color_stops = args[:-1] + color_stops = __color_stops(False, *color_stops) + x1, y1 = zip(*grad_point(start).items())[1] + x2, y2 = zip(*grad_point(opposite_position(start)).items())[1] + svg = _linear_svg(color_stops, x1, y1, x2, y2) + url = 'data:' + 'image/svg+xml' + ';base64,' + base64.b64encode(svg) + inline = 'url("%s")' % escape(url) + return String.unquoted(inline) + + +def __color_stops_svg(color_stops): + ret = ''.join('' % (to_str(s), c) for s, c in color_stops) + return ret + + +def __svg_template(gradient): + ret = '\ +\ +%s\ +\ +' % gradient + return ret + + +def _linear_svg(color_stops, x1, y1, x2, y2): + gradient = '%s' % ( + to_str(Number(x1)), + to_str(Number(y1)), + to_str(Number(x2)), + to_str(Number(y2)), + __color_stops_svg(color_stops) + ) + return __svg_template(gradient) + + +def __radial_svg(color_stops, cx, cy, r): + gradient = '%s' % ( + to_str(Number(cx)), + to_str(Number(cy)), + to_str(Number(r)), + __color_stops_svg(color_stops) + ) + return __svg_template(gradient) diff --git a/libs/scss/functions/compass/helpers.py b/libs/scss/functions/compass/helpers.py new file mode 100644 index 0000000..fe1ef6c --- /dev/null +++ b/libs/scss/functions/compass/helpers.py @@ -0,0 +1,654 @@ +"""Miscellaneous helper functions ported from Compass. + +See: http://compass-style.org/reference/compass/helpers/ + +This collection is not necessarily complete or up-to-date. +""" + +from __future__ import absolute_import + +import base64 +import logging +import math +import os.path +import time + +import six + +from scss import config +from scss.functions.library import FunctionLibrary +from scss.types import Boolean, List, Null, Number, String +from scss.util import escape, to_str +import re + +log = logging.getLogger(__name__) + + +COMPASS_HELPERS_LIBRARY = FunctionLibrary() +register = COMPASS_HELPERS_LIBRARY.register + +FONT_TYPES = { + 'woff': 'woff', + 'otf': 'opentype', + 'opentype': 'opentype', + 'ttf': 'truetype', + 'truetype': 'truetype', + 'svg': 'svg', + 'eot': 'embedded-opentype' +} + + +def add_cache_buster(url, mtime): + fragment = url.split('#') + query = fragment[0].split('?') + if len(query) > 1 and query[1] != '': + cb = '&_=%s' % (mtime) + url = '?'.join(query) + cb + else: + cb = '?_=%s' % (mtime) + url = query[0] + cb + if len(fragment) > 1: + url += '#' + fragment[1] + return url + + +# ------------------------------------------------------------------------------ +# Data manipulation + +@register('blank') +def blank(*objs): + """Returns true when the object is false, an empty string, or an empty list""" + for o in objs: + if isinstance(o, Boolean): + is_blank = not o + elif isinstance(o, String): + is_blank = not len(o.value.strip()) + elif isinstance(o, List): + is_blank = all(blank(el) for el in o) + else: + is_blank = False + + if not is_blank: + return Boolean(False) + + return Boolean(True) + + +@register('compact') +def compact(*args): + """Returns a new list after removing any non-true values""" + use_comma = True + if len(args) == 1 and isinstance(args[0], List): + use_comma = args[0].use_comma + args = args[0] + + return List( + [arg for arg in args if arg], + use_comma=use_comma, + ) + + +@register('reject') +def reject(lst, *values): + """Removes the given values from the list""" + lst = List.from_maybe(lst) + values = frozenset(List.from_maybe_starargs(values)) + + ret = [] + for item in lst: + if item not in values: + ret.append(item) + return List(ret, use_comma=lst.use_comma) + + +@register('first-value-of') +def first_value_of(*args): + if len(args) == 1 and isinstance(args[0], String): + first = args[0].value.split()[0] + return type(args[0])(first) + + args = List.from_maybe_starargs(args) + if len(args): + return args[0] + else: + return Null() + + +@register('-compass-list') +def dash_compass_list(*args): + return List.from_maybe_starargs(args) + + +@register('-compass-space-list') +def dash_compass_space_list(*lst): + """ + If the argument is a list, it will return a new list that is space delimited + Otherwise it returns a new, single element, space-delimited list. + """ + ret = dash_compass_list(*lst) + ret.value.pop('_', None) + return ret + + +@register('-compass-slice', 3) +def dash_compass_slice(lst, start_index, end_index=None): + start_index = Number(start_index).value + end_index = Number(end_index).value if end_index is not None else None + ret = {} + lst = List(lst) + if end_index: + # This function has an inclusive end, but Python slicing is exclusive + end_index += 1 + ret = lst.value[start_index:end_index] + return List(ret, use_comma=lst.use_comma) + + +# ------------------------------------------------------------------------------ +# Property prefixing + +@register('prefixed') +def prefixed(prefix, *args): + to_fnct_str = 'to_' + to_str(prefix).replace('-', '_') + for arg in List.from_maybe_starargs(args): + if hasattr(arg, to_fnct_str): + return Boolean(True) + return Boolean(False) + + +@register('prefix') +def prefix(prefix, *args): + to_fnct_str = 'to_' + to_str(prefix).replace('-', '_') + args = list(args) + for i, arg in enumerate(args): + if isinstance(arg, List): + _value = [] + for iarg in arg: + to_fnct = getattr(iarg, to_fnct_str, None) + if to_fnct: + _value.append(to_fnct()) + else: + _value.append(iarg) + args[i] = List(_value) + else: + to_fnct = getattr(arg, to_fnct_str, None) + if to_fnct: + args[i] = to_fnct() + + return List.maybe_new(args, use_comma=True) + + +@register('-moz') +def dash_moz(*args): + return prefix('_moz', *args) + + +@register('-svg') +def dash_svg(*args): + return prefix('_svg', *args) + + +@register('-css2') +def dash_css2(*args): + return prefix('_css2', *args) + + +@register('-pie') +def dash_pie(*args): + return prefix('_pie', *args) + + +@register('-webkit') +def dash_webkit(*args): + return prefix('_webkit', *args) + + +@register('-owg') +def dash_owg(*args): + return prefix('_owg', *args) + + +@register('-khtml') +def dash_khtml(*args): + return prefix('_khtml', *args) + + +@register('-ms') +def dash_ms(*args): + return prefix('_ms', *args) + + +@register('-o') +def dash_o(*args): + return prefix('_o', *args) + + +# ------------------------------------------------------------------------------ +# Selector generation + +@register('append-selector', 2) +def append_selector(selector, to_append): + if isinstance(selector, List): + lst = selector.value + else: + lst = String.unquoted(selector).value.split(',') + to_append = String.unquoted(to_append).value.strip() + ret = sorted(set(s.strip() + to_append for s in lst if s.strip())) + ret = dict(enumerate(ret)) + ret['_'] = ',' + return ret + + +_elements_of_type_block = 'address, article, aside, blockquote, center, dd, details, dir, div, dl, dt, fieldset, figcaption, figure, footer, form, frameset, h1, h2, h3, h4, h5, h6, header, hgroup, hr, isindex, menu, nav, noframes, noscript, ol, p, pre, section, summary, ul' +_elements_of_type_inline = 'a, abbr, acronym, audio, b, basefont, bdo, big, br, canvas, cite, code, command, datalist, dfn, em, embed, font, i, img, input, kbd, keygen, label, mark, meter, output, progress, q, rp, rt, ruby, s, samp, select, small, span, strike, strong, sub, sup, textarea, time, tt, u, var, video, wbr' +_elements_of_type_table = 'table' +_elements_of_type_list_item = 'li' +_elements_of_type_table_row_group = 'tbody' +_elements_of_type_table_header_group = 'thead' +_elements_of_type_table_footer_group = 'tfoot' +_elements_of_type_table_row = 'tr' +_elements_of_type_table_cel = 'td, th' +_elements_of_type_html5_block = 'article, aside, details, figcaption, figure, footer, header, hgroup, menu, nav, section, summary' +_elements_of_type_html5_inline = 'audio, canvas, command, datalist, embed, keygen, mark, meter, output, progress, rp, rt, ruby, time, video, wbr' +_elements_of_type_html5 = 'article, aside, audio, canvas, command, datalist, details, embed, figcaption, figure, footer, header, hgroup, keygen, mark, menu, meter, nav, output, progress, rp, rt, ruby, section, summary, time, video, wbr' +_elements_of_type = { + 'block': sorted(_elements_of_type_block.replace(' ', '').split(',')), + 'inline': sorted(_elements_of_type_inline.replace(' ', '').split(',')), + 'table': sorted(_elements_of_type_table.replace(' ', '').split(',')), + 'list-item': sorted(_elements_of_type_list_item.replace(' ', '').split(',')), + 'table-row-group': sorted(_elements_of_type_table_row_group.replace(' ', '').split(',')), + 'table-header-group': sorted(_elements_of_type_table_header_group.replace(' ', '').split(',')), + 'table-footer-group': sorted(_elements_of_type_table_footer_group.replace(' ', '').split(',')), + 'table-row': sorted(_elements_of_type_table_footer_group.replace(' ', '').split(',')), + 'table-cell': sorted(_elements_of_type_table_footer_group.replace(' ', '').split(',')), + 'html5-block': sorted(_elements_of_type_html5_block.replace(' ', '').split(',')), + 'html5-inline': sorted(_elements_of_type_html5_inline.replace(' ', '').split(',')), + 'html5': sorted(_elements_of_type_html5.replace(' ', '').split(',')), +} + + +@register('elements-of-type', 1) +def elements_of_type(display): + d = String.unquoted(display) + ret = _elements_of_type.get(d.value, None) + if ret is None: + raise Exception("Elements of type '%s' not found!" % d.value) + return List(ret, use_comma=True) + + +@register('enumerate', 3) +@register('enumerate', 4) +def enumerate_(prefix, frm, through, separator='-'): + separator = String.unquoted(separator).value + try: + frm = int(getattr(frm, 'value', frm)) + except ValueError: + frm = 1 + try: + through = int(getattr(through, 'value', through)) + except ValueError: + through = frm + if frm > through: + # DEVIATION: allow reversed enumerations (and ranges as range() uses enumerate, like '@for .. from .. through') + frm, through = through, frm + rev = reversed + else: + rev = lambda x: x + + ret = [] + for i in rev(range(frm, through + 1)): + if prefix and prefix.value: + ret.append(String.unquoted(prefix.value + separator + str(i))) + else: + ret.append(Number(i)) + + return List(ret, use_comma=True) + + +@register('headers', 0) +@register('headers', 1) +@register('headers', 2) +@register('headings', 0) +@register('headings', 1) +@register('headings', 2) +def headers(frm=None, to=None): + if frm and to is None: + if isinstance(frm, String) and frm.value.lower() == 'all': + frm = 1 + to = 6 + else: + try: + to = int(getattr(frm, 'value', frm)) + except ValueError: + to = 6 + frm = 1 + else: + try: + frm = 1 if frm is None else int(getattr(frm, 'value', frm)) + except ValueError: + frm = 1 + try: + to = 6 if to is None else int(getattr(to, 'value', to)) + except ValueError: + to = 6 + ret = [String.unquoted('h' + str(i)) for i in range(frm, to + 1)] + return List(ret, use_comma=True) + + +@register('nest') +def nest(*arguments): + if isinstance(arguments[0], List): + lst = arguments[0] + elif isinstance(arguments[0], String): + lst = arguments[0].value.split(',') + else: + raise TypeError("Expected list or string, got %r" % (arguments[0],)) + + ret = [] + for s in lst: + if isinstance(s, String): + s = s.value + elif isinstance(s, six.string_types): + s = s + else: + raise TypeError("Expected string, got %r" % (s,)) + + s = s.strip() + if not s: + continue + + ret.append(s) + + for arg in arguments[1:]: + if isinstance(arg, List): + lst = arg + elif isinstance(arg, String): + lst = arg.value.split(',') + else: + raise TypeError("Expected list or string, got %r" % (arg,)) + + new_ret = [] + for s in lst: + if isinstance(s, String): + s = s.value + elif isinstance(s, six.string_types): + s = s + else: + raise TypeError("Expected string, got %r" % (s,)) + + s = s.strip() + if not s: + continue + + for r in ret: + if '&' in s: + new_ret.append(s.replace('&', r)) + else: + if not r or r[-1] in ('.', ':', '#'): + new_ret.append(r + s) + else: + new_ret.append(r + ' ' + s) + ret = new_ret + + ret = [String.unquoted(s) for s in sorted(set(ret))] + return List(ret, use_comma=True) + + +# This isn't actually from Compass, but it's just a shortcut for enumerate(). +# DEVIATION: allow reversed ranges (range() uses enumerate() which allows reversed values, like '@for .. from .. through') +@register('range', 1) +@register('range', 2) +def range_(frm, through=None): + if through is None: + through = frm + frm = 1 + return enumerate_(None, frm, through) + +# ------------------------------------------------------------------------------ +# Working with CSS constants + +OPPOSITE_POSITIONS = { + 'top': String.unquoted('bottom'), + 'bottom': String.unquoted('top'), + 'left': String.unquoted('right'), + 'right': String.unquoted('left'), + 'center': String.unquoted('center'), +} +DEFAULT_POSITION = [String.unquoted('center'), String.unquoted('top')] + + +def _position(opposite, positions): + if positions is None: + positions = DEFAULT_POSITION + else: + positions = List.from_maybe(positions) + + ret = [] + for pos in positions: + if isinstance(pos, (String, six.string_types)): + pos_value = getattr(pos, 'value', pos) + if pos_value in OPPOSITE_POSITIONS: + if opposite: + ret.append(OPPOSITE_POSITIONS[pos_value]) + else: + ret.append(pos) + continue + elif pos_value == 'to': + # Gradient syntax keyword; leave alone + ret.append(pos) + continue + + elif isinstance(pos, Number): + if pos.is_simple_unit('%'): + if opposite: + ret.append(Number(100 - pos.value, '%')) + else: + ret.append(pos) + continue + elif pos.is_simple_unit('deg'): + # TODO support other angle types? + if opposite: + ret.append(Number((pos.value + 180) % 360, 'deg')) + else: + ret.append(pos) + continue + + if opposite: + log.warn("Can't find opposite for position %r" % (pos,)) + ret.append(pos) + + return List(ret, use_comma=False).maybe() + + +@register('position') +def position(p): + return _position(False, p) + + +@register('opposite-position') +def opposite_position(p): + return _position(True, p) + + +# ------------------------------------------------------------------------------ +# Math + +@register('pi', 0) +def pi(): + return Number(math.pi) + + +@register('e', 0) +def e(): + return Number(math.e) + + +@register('log', 1) +@register('log', 2) +def log_(number, base=None): + if not isinstance(number, Number): + raise TypeError("Expected number, got %r" % (number,)) + elif not number.is_unitless: + raise ValueError("Expected unitless number, got %r" % (number,)) + + if base is None: + pass + elif not isinstance(base, Number): + raise TypeError("Expected number, got %r" % (base,)) + elif not base.is_unitless: + raise ValueError("Expected unitless number, got %r" % (base,)) + + if base is None: + ret = math.log(number.value) + else: + ret = math.log(number.value, base.value) + + return Number(ret) + + +@register('pow', 2) +def pow(number, exponent): + return number ** exponent + + +COMPASS_HELPERS_LIBRARY.add(Number.wrap_python_function(math.sqrt), 'sqrt', 1) +COMPASS_HELPERS_LIBRARY.add(Number.wrap_python_function(math.sin), 'sin', 1) +COMPASS_HELPERS_LIBRARY.add(Number.wrap_python_function(math.cos), 'cos', 1) +COMPASS_HELPERS_LIBRARY.add(Number.wrap_python_function(math.tan), 'tan', 1) + + +# ------------------------------------------------------------------------------ +# Fonts + +def _fonts_root(): + return config.STATIC_ROOT if config.FONTS_ROOT is None else config.FONTS_ROOT + + +def _font_url(path, only_path=False, cache_buster=True, inline=False): + filepath = String.unquoted(path).value + file = None + FONTS_ROOT = _fonts_root() + if callable(FONTS_ROOT): + try: + _file, _storage = list(FONTS_ROOT(filepath))[0] + d_obj = _storage.modified_time(_file) + filetime = int(time.mktime(d_obj.timetuple())) + if inline: + file = _storage.open(_file) + except: + filetime = 'NA' + else: + _path = os.path.join(FONTS_ROOT, filepath.strip('/')) + if os.path.exists(_path): + filetime = int(os.path.getmtime(_path)) + if inline: + file = open(_path, 'rb') + else: + filetime = 'NA' + + BASE_URL = config.FONTS_URL or config.STATIC_URL + if file and inline: + font_type = None + if re.match(r'^([^?]+)[.](.*)([?].*)?$', path.value): + font_type = String.unquoted(re.match(r'^([^?]+)[.](.*)([?].*)?$', path.value).groups()[1]).value + + if not FONT_TYPES.get(font_type): + raise Exception('Could not determine font type for "%s"' % path.value) + + mime = FONT_TYPES.get(font_type) + if font_type == 'woff': + mime = 'application/font-woff' + elif font_type == 'eot': + mime = 'application/vnd.ms-fontobject' + url = 'data:' + (mime if '/' in mime else 'font/%s' % mime) + ';base64,' + base64.b64encode(file.read()) + file.close() + else: + url = '%s/%s' % (BASE_URL.rstrip('/'), filepath.lstrip('/')) + if cache_buster and filetime != 'NA': + url = add_cache_buster(url, filetime) + + if not only_path: + url = 'url(%s)' % escape(url) + return String.unquoted(url) + + +def _font_files(args, inline): + if args == (): + return String.unquoted("") + + fonts = [] + args_len = len(args) + skip_next = False + for index in range(len(args)): + arg = args[index] + if not skip_next: + font_type = args[index + 1] if args_len > (index + 1) else None + if font_type and font_type.value in FONT_TYPES: + skip_next = True + else: + if re.match(r'^([^?]+)[.](.*)([?].*)?$', arg.value): + font_type = String.unquoted(re.match(r'^([^?]+)[.](.*)([?].*)?$', arg.value).groups()[1]) + + if font_type.value in FONT_TYPES: + fonts.append(String.unquoted('%s format("%s")' % (_font_url(arg, inline=inline), String.unquoted(FONT_TYPES[font_type.value]).value))) + else: + raise Exception('Could not determine font type for "%s"' % arg.value) + else: + skip_next = False + + return List(fonts, separator=',') + + +@register('font-url', 1) +@register('font-url', 2) +def font_url(path, only_path=False, cache_buster=True): + """ + Generates a path to an asset found relative to the project's font directory. + Passing a true value as the second argument will cause the only the path to + be returned instead of a `url()` function + """ + return _font_url(path, only_path, cache_buster, False) + + +@register('font-files') +def font_files(*args): + return _font_files(args, inline=False) + + +@register('inline-font-files') +def inline_font_files(*args): + return _font_files(args, inline=True) + + +# ------------------------------------------------------------------------------ +# External stylesheets + +@register('stylesheet-url', 1) +@register('stylesheet-url', 2) +def stylesheet_url(path, only_path=False, cache_buster=True): + """ + Generates a path to an asset found relative to the project's css directory. + Passing a true value as the second argument will cause the only the path to + be returned instead of a `url()` function + """ + filepath = String.unquoted(path).value + if callable(config.STATIC_ROOT): + try: + _file, _storage = list(config.STATIC_ROOT(filepath))[0] + d_obj = _storage.modified_time(_file) + filetime = int(time.mktime(d_obj.timetuple())) + except: + filetime = 'NA' + else: + _path = os.path.join(config.STATIC_ROOT, filepath.strip('/')) + if os.path.exists(_path): + filetime = int(os.path.getmtime(_path)) + else: + filetime = 'NA' + BASE_URL = config.STATIC_URL + + url = '%s%s' % (BASE_URL, filepath) + if cache_buster: + url = add_cache_buster(url, filetime) + if not only_path: + url = 'url("%s")' % (url) + return String.unquoted(url) diff --git a/libs/scss/functions/compass/images.py b/libs/scss/functions/compass/images.py new file mode 100644 index 0000000..3eab26c --- /dev/null +++ b/libs/scss/functions/compass/images.py @@ -0,0 +1,285 @@ +"""Image utilities ported from Compass.""" + +from __future__ import absolute_import +from __future__ import print_function + +import base64 +import hashlib +import logging +import mimetypes +import os.path +import time + +import six +from six.moves import xrange + +from scss import config +from scss.functions.compass import _image_size_cache +from scss.functions.compass.helpers import add_cache_buster +from scss.functions.library import FunctionLibrary +from scss.types import Color, List, Number, String +from scss.util import escape + +try: + from PIL import Image +except ImportError: + try: + import Image + except: + Image = None + +log = logging.getLogger(__name__) + +COMPASS_IMAGES_LIBRARY = FunctionLibrary() +register = COMPASS_IMAGES_LIBRARY.register + + +# ------------------------------------------------------------------------------ + +def _images_root(): + return config.STATIC_ROOT if config.IMAGES_ROOT is None else config.IMAGES_ROOT + + +def _image_url(path, only_path=False, cache_buster=True, dst_color=None, src_color=None, inline=False, mime_type=None, spacing=None, collapse_x=None, collapse_y=None): + """ + src_color - a list of or a single color to be replaced by each corresponding dst_color colors + spacing - spaces to be added to the image + collapse_x, collapse_y - collapsable (layered) image of the given size (x, y) + """ + if inline or dst_color or spacing: + if not Image: + raise Exception("Images manipulation require PIL") + filepath = String.unquoted(path).value + fileext = os.path.splitext(filepath)[1].lstrip('.').lower() + if mime_type: + mime_type = String.unquoted(mime_type).value + if not mime_type: + mime_type = mimetypes.guess_type(filepath)[0] + if not mime_type: + mime_type = 'image/%s' % fileext + path = None + IMAGES_ROOT = _images_root() + if callable(IMAGES_ROOT): + try: + _file, _storage = list(IMAGES_ROOT(filepath))[0] + d_obj = _storage.modified_time(_file) + filetime = int(time.mktime(d_obj.timetuple())) + if inline or dst_color or spacing: + path = _storage.open(_file) + except: + filetime = 'NA' + else: + _path = os.path.join(IMAGES_ROOT.rstrip('/'), filepath.strip('/')) + if os.path.exists(_path): + filetime = int(os.path.getmtime(_path)) + if inline or dst_color or spacing: + path = open(_path, 'rb') + else: + filetime = 'NA' + + BASE_URL = config.IMAGES_URL or config.STATIC_URL + if path: + dst_colors = [list(Color(v).value[:3]) for v in List.from_maybe(dst_color) if v] + + src_color = Color.from_name('black') if src_color is None else src_color + src_colors = [tuple(Color(v).value[:3]) for v in List.from_maybe(src_color)] + + len_colors = max(len(dst_colors), len(src_colors)) + dst_colors = (dst_colors * len_colors)[:len_colors] + src_colors = (src_colors * len_colors)[:len_colors] + + spacing = Number(0) if spacing is None else spacing + spacing = [int(Number(v).value) for v in List.from_maybe(spacing)] + spacing = (spacing * 4)[:4] + + file_name, file_ext = os.path.splitext(os.path.normpath(filepath).replace('\\', '_').replace('/', '_')) + key = (filetime, src_color, dst_color, spacing) + key = file_name + '-' + base64.urlsafe_b64encode(hashlib.md5(repr(key)).digest()).rstrip('=').replace('-', '_') + asset_file = key + file_ext + ASSETS_ROOT = config.ASSETS_ROOT or os.path.join(config.STATIC_ROOT, 'assets') + asset_path = os.path.join(ASSETS_ROOT, asset_file) + + if os.path.exists(asset_path): + filepath = asset_file + BASE_URL = config.ASSETS_URL + if inline: + path = open(asset_path, 'rb') + url = 'data:' + mime_type + ';base64,' + base64.b64encode(path.read()) + else: + url = '%s%s' % (BASE_URL, filepath) + if cache_buster: + filetime = int(os.path.getmtime(asset_path)) + url = add_cache_buster(url, filetime) + else: + simply_process = False + image = None + + if fileext in ('cur',): + simply_process = True + else: + try: + image = Image.open(path) + except IOError: + if not collapse_x and not collapse_y and not dst_colors: + simply_process = True + + if simply_process: + if inline: + url = 'data:' + mime_type + ';base64,' + base64.b64encode(path.read()) + else: + url = '%s%s' % (BASE_URL, filepath) + if cache_buster: + filetime = int(os.path.getmtime(asset_path)) + url = add_cache_buster(url, filetime) + else: + width, height = collapse_x or image.size[0], collapse_y or image.size[1] + new_image = Image.new( + mode='RGBA', + size=(width + spacing[1] + spacing[3], height + spacing[0] + spacing[2]), + color=(0, 0, 0, 0) + ) + for i, dst_color in enumerate(dst_colors): + src_color = src_colors[i] + pixdata = image.load() + for _y in xrange(image.size[1]): + for _x in xrange(image.size[0]): + pixel = pixdata[_x, _y] + if pixel[:3] == src_color: + pixdata[_x, _y] = tuple([int(c) for c in dst_color] + [pixel[3] if len(pixel) == 4 else 255]) + iwidth, iheight = image.size + if iwidth != width or iheight != height: + cy = 0 + while cy < iheight: + cx = 0 + while cx < iwidth: + cropped_image = image.crop((cx, cy, cx + width, cy + height)) + new_image.paste(cropped_image, (int(spacing[3]), int(spacing[0])), cropped_image) + cx += width + cy += height + else: + new_image.paste(image, (int(spacing[3]), int(spacing[0]))) + + if not inline: + try: + new_image.save(asset_path) + filepath = asset_file + BASE_URL = config.ASSETS_URL + if cache_buster: + filetime = int(os.path.getmtime(asset_path)) + except IOError: + log.exception("Error while saving image") + inline = True # Retry inline version + url = os.path.join(config.ASSETS_URL.rstrip('/'), asset_file.lstrip('/')) + if cache_buster: + url = add_cache_buster(url, filetime) + if inline: + output = six.BytesIO() + new_image.save(output, format='PNG') + contents = output.getvalue() + output.close() + url = 'data:' + mime_type + ';base64,' + base64.b64encode(contents) + else: + url = os.path.join(BASE_URL.rstrip('/'), filepath.lstrip('/')) + if cache_buster and filetime != 'NA': + url = add_cache_buster(url, filetime) + + if not only_path: + url = 'url(%s)' % escape(url) + return String.unquoted(url) + + +@register('inline-image', 1) +@register('inline-image', 2) +@register('inline-image', 3) +@register('inline-image', 4) +@register('inline-image', 5) +def inline_image(image, mime_type=None, dst_color=None, src_color=None, spacing=None, collapse_x=None, collapse_y=None): + """ + Embeds the contents of a file directly inside your stylesheet, eliminating + the need for another HTTP request. For small files such images or fonts, + this can be a performance benefit at the cost of a larger generated CSS + file. + """ + return _image_url(image, False, False, dst_color, src_color, True, mime_type, spacing, collapse_x, collapse_y) + + +@register('image-url', 1) +@register('image-url', 2) +@register('image-url', 3) +@register('image-url', 4) +@register('image-url', 5) +@register('image-url', 6) +def image_url(path, only_path=False, cache_buster=True, dst_color=None, src_color=None, spacing=None, collapse_x=None, collapse_y=None): + """ + Generates a path to an asset found relative to the project's images + directory. + Passing a true value as the second argument will cause the only the path to + be returned instead of a `url()` function + """ + return _image_url(path, only_path, cache_buster, dst_color, src_color, False, None, spacing, collapse_x, collapse_y) + + +@register('image-width', 1) +def image_width(image): + """ + Returns the width of the image found at the path supplied by `image` + relative to your project's images directory. + """ + if not Image: + raise Exception("Images manipulation require PIL") + filepath = String.unquoted(image).value + path = None + try: + width = _image_size_cache[filepath][0] + except KeyError: + width = 0 + IMAGES_ROOT = _images_root() + if callable(IMAGES_ROOT): + try: + _file, _storage = list(IMAGES_ROOT(filepath))[0] + path = _storage.open(_file) + except: + pass + else: + _path = os.path.join(IMAGES_ROOT, filepath.strip('/')) + if os.path.exists(_path): + path = open(_path, 'rb') + if path: + image = Image.open(path) + size = image.size + width = size[0] + _image_size_cache[filepath] = size + return Number(width, 'px') + + +@register('image-height', 1) +def image_height(image): + """ + Returns the height of the image found at the path supplied by `image` + relative to your project's images directory. + """ + if not Image: + raise Exception("Images manipulation require PIL") + filepath = String.unquoted(image).value + path = None + try: + height = _image_size_cache[filepath][1] + except KeyError: + height = 0 + IMAGES_ROOT = _images_root() + if callable(IMAGES_ROOT): + try: + _file, _storage = list(IMAGES_ROOT(filepath))[0] + path = _storage.open(_file) + except: + pass + else: + _path = os.path.join(IMAGES_ROOT, filepath.strip('/')) + if os.path.exists(_path): + path = open(_path, 'rb') + if path: + image = Image.open(path) + size = image.size + height = size[1] + _image_size_cache[filepath] = size + return Number(height, 'px') diff --git a/libs/scss/functions/compass/layouts.py b/libs/scss/functions/compass/layouts.py new file mode 100644 index 0000000..06e67c8 --- /dev/null +++ b/libs/scss/functions/compass/layouts.py @@ -0,0 +1,346 @@ +"""Functions used for generating packed CSS sprite maps. + + +These are ported from the Binary Tree Bin Packing Algorithm: +http://codeincomplete.com/posts/2011/5/7/bin_packing/ +""" + +# Copyright (c) 2011, 2012, 2013 Jake Gordon and contributors +# Copyright (c) 2013 German M. Bravo + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + + +class LayoutNode(object): + def __init__(self, x, y, w, h, down=None, right=None, used=False): + self.x = x + self.y = y + self.w = w + self.h = h + self.down = down + self.right = right + self.used = used + self.width = 0 + self.height = 0 + + @property + def area(self): + return self.width * self.height + + def __repr__(self): + return '<%s (%s, %s) [%sx%s]>' % (self.__class__.__name__, self.x, self.y, self.w, self.h) + + +class SpritesLayout(object): + def __init__(self, blocks, padding=None, margin=None, ppadding=None, pmargin=None): + self.num_blocks = len(blocks) + + if margin is None: + margin = [[0] * 4] * self.num_blocks + elif not isinstance(margin, (tuple, list)): + margin = [[margin] * 4] * self.num_blocks + elif not isinstance(margin[0], (tuple, list)): + margin = [margin] * self.num_blocks + + if padding is None: + padding = [[0] * 4] * self.num_blocks + elif not isinstance(padding, (tuple, list)): + padding = [[padding] * 4] * self.num_blocks + elif not isinstance(padding[0], (tuple, list)): + padding = [padding] * self.num_blocks + + if pmargin is None: + pmargin = [[0.0] * 4] * self.num_blocks + elif not isinstance(pmargin, (tuple, list)): + pmargin = [[pmargin] * 4] * self.num_blocks + elif not isinstance(pmargin[0], (tuple, list)): + pmargin = [pmargin] * self.num_blocks + + if ppadding is None: + ppadding = [[0.0] * 4] * self.num_blocks + elif not isinstance(ppadding, (tuple, list)): + ppadding = [[ppadding] * 4] * self.num_blocks + elif not isinstance(ppadding[0], (tuple, list)): + ppadding = [ppadding] * self.num_blocks + + self.blocks = tuple(( + b[0] + padding[i][3] + padding[i][1] + margin[i][3] + margin[i][1] + int(round(b[0] * (ppadding[i][3] + ppadding[i][1] + pmargin[i][3] + pmargin[i][1]))), + b[1] + padding[i][0] + padding[i][2] + margin[i][0] + margin[i][2] + int(round(b[1] * (ppadding[i][0] + ppadding[i][2] + pmargin[i][0] + pmargin[i][2]))), + b[0], + b[1], + i + ) for i, b in enumerate(blocks)) + + self.margin = margin + self.padding = padding + self.pmargin = pmargin + self.ppadding = ppadding + + +class PackedSpritesLayout(SpritesLayout): + @staticmethod + def MAXSIDE(a, b): + """maxside: Sort pack by maximum sides""" + return cmp(max(b[0], b[1]), max(a[0], a[1])) or cmp(min(b[0], b[1]), min(a[0], a[1])) or cmp(b[1], a[1]) or cmp(b[0], a[0]) + + @staticmethod + def WIDTH(a, b): + """width: Sort pack by width""" + return cmp(b[0], a[0]) or cmp(b[1], a[1]) + + @staticmethod + def HEIGHT(a, b): + """height: Sort pack by height""" + return cmp(b[1], a[1]) or cmp(b[0], a[0]) + + @staticmethod + def AREA(a, b): + """area: Sort pack by area""" + return cmp(b[0] * b[1], a[0] * a[1]) or cmp(b[1], a[1]) or cmp(b[0], a[0]) + + def __init__(self, blocks, padding=None, margin=None, ppadding=None, pmargin=None, methods=None): + super(PackedSpritesLayout, self).__init__(blocks, padding, margin, ppadding, pmargin) + + ratio = 0 + + if methods is None: + methods = (self.MAXSIDE, self.WIDTH, self.HEIGHT, self.AREA) + + for method in methods: + sorted_blocks = sorted( + self.blocks, + cmp=method, + ) + root = LayoutNode( + x=0, + y=0, + w=sorted_blocks[0][0] if sorted_blocks else 0, + h=sorted_blocks[0][1] if sorted_blocks else 0 + ) + + area = 0 + nodes = [None] * self.num_blocks + + for block in sorted_blocks: + w, h, width, height, i = block + node = self._findNode(root, w, h) + if node: + node = self._splitNode(node, w, h) + else: + root = self._growNode(root, w, h) + node = self._findNode(root, w, h) + if node: + node = self._splitNode(node, w, h) + else: + node = None + nodes[i] = node + node.width = width + node.height = height + area += node.area + + this_ratio = area / float(root.w * root.h) + # print method.__doc__, "%g%%" % (this_ratio * 100) + if ratio < this_ratio: + self.root = root + self.nodes = nodes + self.method = method + ratio = this_ratio + if ratio > 0.96: + break + # print self.method.__doc__, "%g%%" % (ratio * 100) + + def __iter__(self): + for i, node in enumerate(self.nodes): + margin, padding = self.margin[i], self.padding[i] + pmargin, ppadding = self.pmargin[i], self.ppadding[i] + cssw = node.width + padding[3] + padding[1] + int(round(node.width * (ppadding[3] + ppadding[1]))) # image width plus padding + cssh = node.height + padding[0] + padding[2] + int(round(node.height * (ppadding[0] + ppadding[2]))) # image height plus padding + cssx = node.x + margin[3] + int(round(node.width * pmargin[3])) + cssy = node.y + margin[0] + int(round(node.height * pmargin[0])) + x = cssx + padding[3] + int(round(node.width * ppadding[3])) + y = cssy + padding[0] + int(round(node.height * ppadding[0])) + yield x, y, node.width, node.height, cssx, cssy, cssw, cssh + + @property + def width(self): + return self.root.w + + @property + def height(self): + return self.root.h + + def _findNode(self, root, w, h): + if root.used: + return self._findNode(root.right, w, h) or self._findNode(root.down, w, h) + elif w <= root.w and h <= root.h: + return root + else: + return None + + def _splitNode(self, node, w, h): + node.used = True + node.down = LayoutNode( + x=node.x, + y=node.y + h, + w=node.w, + h=node.h - h + ) + node.right = LayoutNode( + x=node.x + w, + y=node.y, + w=node.w - w, + h=h + ) + return node + + def _growNode(self, root, w, h): + canGrowDown = w <= root.w + canGrowRight = h <= root.h + + shouldGrowRight = canGrowRight and (root.h >= root.w + w) # attempt to keep square-ish by growing right when height is much greater than width + shouldGrowDown = canGrowDown and (root.w >= root.h + h) # attempt to keep square-ish by growing down when width is much greater than height + + if shouldGrowRight: + return self._growRight(root, w, h) + elif shouldGrowDown: + return self._growDown(root, w, h) + elif canGrowRight: + return self._growRight(root, w, h) + elif canGrowDown: + return self._growDown(root, w, h) + else: + # need to ensure sensible root starting size to avoid this happening + assert False, "Blocks must be properly sorted!" + + def _growRight(self, root, w, h): + root = LayoutNode( + used=True, + x=0, + y=0, + w=root.w + w, + h=root.h, + down=root, + right=LayoutNode( + x=root.w, + y=0, + w=w, + h=root.h + ) + ) + return root + + def _growDown(self, root, w, h): + root = LayoutNode( + used=True, + x=0, + y=0, + w=root.w, + h=root.h + h, + down=LayoutNode( + x=0, + y=root.h, + w=root.w, + h=h + ), + right=root + ) + return root + + +class HorizontalSpritesLayout(SpritesLayout): + def __init__(self, blocks, padding=None, margin=None, ppadding=None, pmargin=None, position=None): + super(HorizontalSpritesLayout, self).__init__(blocks, padding, margin, ppadding, pmargin) + + self.width = sum(zip(*self.blocks)[0]) + self.height = max(zip(*self.blocks)[1]) + + if position is None: + position = [0.0] * self.num_blocks + elif not isinstance(position, (tuple, list)): + position = [position] * self.num_blocks + self.position = position + + def __iter__(self): + cx = 0 + for i, block in enumerate(self.blocks): + w, h, width, height, i = block + margin, padding = self.margin[i], self.padding[i] + pmargin, ppadding = self.pmargin[i], self.ppadding[i] + position = self.position[i] + cssw = width + padding[3] + padding[1] + int(round(width * (ppadding[3] + ppadding[1]))) # image width plus padding + cssh = height + padding[0] + padding[2] + int(round(height * (ppadding[0] + ppadding[2]))) # image height plus padding + cssx = cx + margin[3] + int(round(width * pmargin[3])) # anchored at x + cssy = int(round((self.height - cssh) * position)) # centered vertically + x = cssx + padding[3] + int(round(width * ppadding[3])) # image drawn offset to account for padding + y = cssy + padding[0] + int(round(height * ppadding[0])) # image drawn offset to account for padding + yield x, y, width, height, cssx, cssy, cssw, cssh + cx += cssw + margin[3] + margin[1] + int(round(width * (pmargin[3] + pmargin[1]))) + + +class VerticalSpritesLayout(SpritesLayout): + def __init__(self, blocks, padding=None, margin=None, ppadding=None, pmargin=None, position=None): + super(VerticalSpritesLayout, self).__init__(blocks, padding, margin, ppadding, pmargin) + + self.width = max(zip(*self.blocks)[0]) + self.height = sum(zip(*self.blocks)[1]) + + if position is None: + position = [0.0] * self.num_blocks + elif not isinstance(position, (tuple, list)): + position = [position] * self.num_blocks + self.position = position + + def __iter__(self): + cy = 0 + for i, block in enumerate(self.blocks): + w, h, width, height, i = block + margin, padding = self.margin[i], self.padding[i] + pmargin, ppadding = self.pmargin[i], self.ppadding[i] + position = self.position[i] + cssw = width + padding[3] + padding[1] + int(round(width * (ppadding[3] + ppadding[1]))) # image width plus padding + cssh = height + padding[0] + padding[2] + int(round(height * (ppadding[0] + ppadding[2]))) # image height plus padding + cssx = int(round((self.width - cssw) * position)) # centered horizontally + cssy = cy + margin[0] + int(round(height * pmargin[0])) # anchored at y + x = cssx + padding[3] + int(round(width * ppadding[3])) # image drawn offset to account for padding + y = cssy + padding[0] + int(round(height * ppadding[0])) # image drawn offset to account for padding + yield x, y, width, height, cssx, cssy, cssw, cssh + cy += cssh + margin[0] + margin[2] + int(round(height * (pmargin[0] + pmargin[2]))) + + +class DiagonalSpritesLayout(SpritesLayout): + def __init__(self, blocks, padding=None, margin=None, ppadding=None, pmargin=None): + super(DiagonalSpritesLayout, self).__init__(blocks, padding, margin, ppadding, pmargin) + self.width = sum(zip(*self.blocks)[0]) + self.height = sum(zip(*self.blocks)[1]) + + def __iter__(self): + cx, cy = 0, 0 + for i, block in enumerate(self.blocks): + w, h, width, height, i = block + margin, padding = self.margin[i], self.padding[i] + pmargin, ppadding = self.pmargin[i], self.ppadding[i] + cssw = width + padding[3] + padding[1] + int(round(width * (ppadding[3] + ppadding[1]))) # image width plus padding + cssh = height + padding[0] + padding[2] + int(round(height * (ppadding[0] + ppadding[2]))) # image height plus padding + cssx = cx + margin[3] + int(round(width * pmargin[3])) # anchored at x + cssy = cy + margin[0] + int(round(height * pmargin[0])) # anchored at y + x = cssx + padding[3] + int(round(width * ppadding[3])) # image drawn offset to account for padding + y = cssy + padding[0] + int(round(height * ppadding[0])) # image drawn offset to account for padding + yield x, y, width, height, cssx, cssy, cssw, cssh + cx += cssw + margin[3] + margin[1] + int(round(width * (pmargin[3] + pmargin[1]))) + cy += cssh + margin[0] + margin[2] + int(round(height * (pmargin[0] + pmargin[2]))) diff --git a/libs/scss/functions/compass/sprites.py b/libs/scss/functions/compass/sprites.py new file mode 100644 index 0000000..2549cb3 --- /dev/null +++ b/libs/scss/functions/compass/sprites.py @@ -0,0 +1,524 @@ +"""Functions used for generating CSS sprites. + +These are ported from the Compass sprite library: +http://compass-style.org/reference/compass/utilities/sprites/ +""" + +from __future__ import absolute_import + +import six + +import base64 +import glob +import hashlib +import logging +import os.path +import tempfile +import time + +try: + import cPickle as pickle +except ImportError: + import pickle + +try: + from PIL import Image +except ImportError: + try: + import Image + except: + Image = None + +from six.moves import xrange + +from scss import config +from scss.functions.compass import _image_size_cache +from scss.functions.compass.layouts import PackedSpritesLayout, HorizontalSpritesLayout, VerticalSpritesLayout, DiagonalSpritesLayout +from scss.functions.library import FunctionLibrary +from scss.types import Color, List, Number, String, Boolean +from scss.util import escape + +log = logging.getLogger(__name__) + +MAX_SPRITE_MAPS = 4096 +KEEP_SPRITE_MAPS = int(MAX_SPRITE_MAPS * 0.8) + +COMPASS_SPRITES_LIBRARY = FunctionLibrary() +register = COMPASS_SPRITES_LIBRARY.register + + +# ------------------------------------------------------------------------------ +# Compass-like functionality for sprites and images + +sprite_maps = {} + + +def alpha_composite(im1, im2, offset=None, box=None, opacity=1): + im1size = im1.size + im2size = im2.size + if offset is None: + offset = (0, 0) + if box is None: + box = (0, 0) + im2size + o1x, o1y = offset + o2x, o2y, o2w, o2h = box + width = o2w - o2x + height = o2h - o2y + im1_data = im1.load() + im2_data = im2.load() + for y in xrange(height): + for x in xrange(width): + pos1 = o1x + x, o1y + y + if pos1[0] >= im1size[0] or pos1[1] >= im1size[1]: + continue + pos2 = o2x + x, o2y + y + if pos2[0] >= im2size[0] or pos2[1] >= im2size[1]: + continue + dr, dg, db, da = im1_data[pos1] + sr, sg, sb, sa = im2_data[pos2] + da /= 255.0 + sa /= 255.0 + sa *= opacity + ida = da * (1 - sa) + oa = (sa + ida) + if oa: + pixel = ( + int(round((sr * sa + dr * ida)) / oa), + int(round((sg * sa + dg * ida)) / oa), + int(round((sb * sa + db * ida)) / oa), + int(round(255 * oa)) + ) + else: + pixel = (0, 0, 0, 0) + im1_data[pos1] = pixel + return im1 + + +@register('sprite-map') +def sprite_map(g, **kwargs): + """ + Generates a sprite map from the files matching the glob pattern. + Uses the keyword-style arguments passed in to control the placement. + + $direction - Sprite map layout. Can be `vertical` (default), `horizontal`, `diagonal` or `smart`. + + $position - For `horizontal` and `vertical` directions, the position of the sprite. (defaults to `0`) + $-position - Position of a given sprite. + + $padding, $spacing - Adds paddings to sprites (top, right, bottom, left). (defaults to `0, 0, 0, 0`) + $-padding, $-spacing - Padding for a given sprite. + + $dst-color - Together with `$src-color`, forms a map of source colors to be converted to destiny colors (same index of `$src-color` changed to `$dst-color`). + $-dst-color - Destiny colors for a given sprite. (defaults to `$dst-color`) + + $src-color - Selects source colors to be converted to the corresponding destiny colors. (defaults to `black`) + $-dst-color - Source colors for a given sprite. (defaults to `$src-color`) + + $collapse - Collapses every image in the sprite map to a fixed size (`x` and `y`). + $collapse-x - Collapses a size for `x`. + $collapse-y - Collapses a size for `y`. + """ + if not Image: + raise Exception("Images manipulation require PIL") + + now_time = time.time() + + g = String(g, quotes=None).value + + if g in sprite_maps: + sprite_maps[glob]['*'] = now_time + elif '..' not in g: # Protect against going to prohibited places... + if callable(config.STATIC_ROOT): + glob_path = g + rfiles = files = sorted(config.STATIC_ROOT(g)) + else: + glob_path = os.path.join(config.STATIC_ROOT, g) + files = glob.glob(glob_path) + files = sorted((f, None) for f in files) + rfiles = [(rf[len(config.STATIC_ROOT):], s) for rf, s in files] + + if not files: + log.error("Nothing found at '%s'", glob_path) + return String.unquoted('') + + map_name = os.path.normpath(os.path.dirname(g)).replace('\\', '_').replace('/', '_') + key = list(zip(*files)[0]) + [repr(kwargs), config.ASSETS_URL] + key = map_name + '-' + base64.urlsafe_b64encode(hashlib.md5(repr(key)).digest()).rstrip('=').replace('-', '_') + asset_file = key + '.png' + ASSETS_ROOT = config.ASSETS_ROOT or os.path.join(config.STATIC_ROOT, 'assets') + asset_path = os.path.join(ASSETS_ROOT, asset_file) + cache_path = os.path.join(config.CACHE_ROOT or ASSETS_ROOT, asset_file + '.cache') + + inline = Boolean(kwargs.get('inline', False)) + + sprite_map = None + asset = None + file_asset = None + inline_asset = None + if os.path.exists(asset_path) or inline: + try: + save_time, file_asset, inline_asset, sprite_map, sizes = pickle.load(open(cache_path)) + if file_asset: + sprite_maps[file_asset.render()] = sprite_map + if inline_asset: + sprite_maps[inline_asset.render()] = sprite_map + if inline: + asset = inline_asset + else: + asset = file_asset + except: + pass + + if sprite_map: + for file_, storage in files: + if storage is not None: + d_obj = storage.modified_time(file_) + _time = time.mktime(d_obj.timetuple()) + else: + _time = os.path.getmtime(file_) + if save_time < _time: + if _time > now_time: + log.warning("File '%s' has a date in the future (cache ignored)" % file_) + sprite_map = None # Invalidate cached sprite map + break + + if sprite_map is None or asset is None: + cache_buster = Boolean(kwargs.get('cache_buster', True)) + direction = String.unquoted(kwargs.get('direction', config.SPRTE_MAP_DIRECTION)).value + repeat = String.unquoted(kwargs.get('repeat', 'no-repeat')).value + collapse = kwargs.get('collapse', Number(0)) + if isinstance(collapse, List): + collapse_x = int(Number(collapse[0]).value) + collapse_y = int(Number(collapse[-1]).value) + else: + collapse_x = collapse_y = int(Number(collapse).value) + if 'collapse_x' in kwargs: + collapse_x = int(Number(kwargs['collapse_x']).value) + if 'collapse_y' in kwargs: + collapse_y = int(Number(kwargs['collapse_y']).value) + + position = Number(kwargs.get('position', 0)) + if not position.is_simple_unit('%') and position.value > 1: + position = position.value / 100.0 + else: + position = position.value + if position < 0: + position = 0.0 + elif position > 1: + position = 1.0 + + padding = kwargs.get('padding', kwargs.get('spacing', Number(0))) + padding = [int(Number(v).value) for v in List.from_maybe(padding)] + padding = (padding * 4)[:4] + + dst_colors = kwargs.get('dst_color') + dst_colors = [list(Color(v).value[:3]) for v in List.from_maybe(dst_colors) if v] + src_colors = kwargs.get('src_color', Color.from_name('black')) + src_colors = [tuple(Color(v).value[:3]) for v in List.from_maybe(src_colors)] + len_colors = max(len(dst_colors), len(src_colors)) + dst_colors = (dst_colors * len_colors)[:len_colors] + src_colors = (src_colors * len_colors)[:len_colors] + + def images(f=lambda x: x): + for file_, storage in f(files): + if storage is not None: + _file = storage.open(file_) + else: + _file = file_ + _image = Image.open(_file) + yield _image + + names = tuple(os.path.splitext(os.path.basename(file_))[0] for file_, storage in files) + + has_dst_colors = False + all_dst_colors = [] + all_src_colors = [] + all_positions = [] + all_paddings = [] + + for name in names: + name = name.replace('-', '_') + + _position = kwargs.get(name + '_position') + if _position is None: + _position = position + else: + _position = Number(_position) + if not _position.is_simple_unit('%') and _position.value > 1: + _position = _position.value / 100.0 + else: + _position = _position.value + if _position < 0: + _position = 0.0 + elif _position > 1: + _position = 1.0 + all_positions.append(_position) + + _padding = kwargs.get(name + '_padding', kwargs.get(name + '_spacing')) + if _padding is None: + _padding = padding + else: + _padding = [int(Number(v).value) for v in List.from_maybe(_padding)] + _padding = (_padding * 4)[:4] + all_paddings.append(_padding) + + _dst_colors = kwargs.get(name + '_dst_color') + if _dst_colors is None: + _dst_colors = dst_colors + if dst_colors: + has_dst_colors = True + else: + has_dst_colors = True + _dst_colors = [list(Color(v).value[:3]) for v in List.from_maybe(_dst_colors) if v] + _src_colors = kwargs.get(name + '_src_color', Color.from_name('black')) + if _src_colors is None: + _src_colors = src_colors + else: + _src_colors = [tuple(Color(v).value[:3]) for v in List.from_maybe(_src_colors)] + _len_colors = max(len(_dst_colors), len(_src_colors)) + _dst_colors = (_dst_colors * _len_colors)[:_len_colors] + _src_colors = (_src_colors * _len_colors)[:_len_colors] + all_dst_colors.append(_dst_colors) + all_src_colors.append(_src_colors) + + sizes = tuple((collapse_x or i.size[0], collapse_y or i.size[1]) for i in images()) + + if direction == 'horizontal': + layout = HorizontalSpritesLayout(sizes, all_paddings, position=all_positions) + elif direction == 'vertical': + layout = VerticalSpritesLayout(sizes, all_paddings, position=all_positions) + elif direction == 'diagonal': + layout = DiagonalSpritesLayout(sizes, all_paddings) + elif direction == 'smart': + layout = PackedSpritesLayout(sizes, all_paddings) + else: + raise Exception("Invalid direction %r" % (direction,)) + layout_positions = list(layout) + + new_image = Image.new( + mode='RGBA', + size=(layout.width, layout.height), + color=(0, 0, 0, 0) + ) + + useless_dst_color = has_dst_colors + + offsets_x = [] + offsets_y = [] + for i, image in enumerate(images()): + x, y, width, height, cssx, cssy, cssw, cssh = layout_positions[i] + iwidth, iheight = image.size + + if has_dst_colors: + pixdata = image.load() + for _y in xrange(iheight): + for _x in xrange(iwidth): + pixel = pixdata[_x, _y] + a = pixel[3] if len(pixel) == 4 else 255 + if a: + rgb = pixel[:3] + for j, dst_color in enumerate(all_dst_colors[i]): + if rgb == all_src_colors[i][j]: + new_color = tuple([int(c) for c in dst_color] + [a]) + if pixel != new_color: + pixdata[_x, _y] = new_color + useless_dst_color = False + break + + if iwidth != width or iheight != height: + cy = 0 + while cy < iheight: + cx = 0 + while cx < iwidth: + new_image = alpha_composite(new_image, image, (x, y), (cx, cy, cx + width, cy + height)) + cx += width + cy += height + else: + new_image.paste(image, (x, y)) + offsets_x.append(cssx) + offsets_y.append(cssy) + + if useless_dst_color: + log.warning("Useless use of $dst-color in sprite map for files at '%s' (never used for)" % glob_path) + + filetime = int(now_time) + + if not inline: + try: + new_image.save(asset_path) + url = '%s%s' % (config.ASSETS_URL, asset_file) + if cache_buster: + url += '?_=%s' % filetime + except IOError: + log.exception("Error while saving image") + inline = True + if inline: + output = six.BytesIO() + new_image.save(output, format='PNG') + contents = output.getvalue() + output.close() + mime_type = 'image/png' + url = 'data:' + mime_type + ';base64,' + base64.b64encode(contents) + + url = 'url(%s)' % escape(url) + if inline: + asset = inline_asset = List([String.unquoted(url), String.unquoted(repeat)]) + else: + asset = file_asset = List([String.unquoted(url), String.unquoted(repeat)]) + + # Add the new object: + sprite_map = dict(zip(names, zip(sizes, rfiles, offsets_x, offsets_y))) + sprite_map['*'] = now_time + sprite_map['*f*'] = asset_file + sprite_map['*k*'] = key + sprite_map['*n*'] = map_name + sprite_map['*t*'] = filetime + + cache_tmp = tempfile.NamedTemporaryFile(delete=False, dir=ASSETS_ROOT) + pickle.dump((now_time, file_asset, inline_asset, sprite_map, zip(files, sizes)), cache_tmp) + cache_tmp.close() + os.rename(cache_tmp.name, cache_path) + + # Use the sorted list to remove older elements (keep only 500 objects): + if len(sprite_maps) > MAX_SPRITE_MAPS: + for a in sorted(sprite_maps, key=lambda a: sprite_maps[a]['*'], reverse=True)[KEEP_SPRITE_MAPS:]: + del sprite_maps[a] + log.warning("Exceeded maximum number of sprite maps (%s)" % MAX_SPRITE_MAPS) + sprite_maps[asset.render()] = sprite_map + for file_, size in sizes: + _image_size_cache[file_] = size + # TODO this sometimes returns an empty list, or is never assigned to + return asset + + +@register('sprite-map-name', 1) +def sprite_map_name(map): + """ + Returns the name of a sprite map The name is derived from the folder than + contains the sprites. + """ + map = map.render() + sprite_map = sprite_maps.get(map) + if not sprite_map: + log.error("No sprite map found: %s", map, extra={'stack': True}) + if sprite_map: + return String.unquoted(sprite_map['*n*']) + return String.unquoted('') + + +@register('sprite-file', 2) +def sprite_file(map, sprite): + """ + Returns the relative path (from the images directory) to the original file + used when construction the sprite. This is suitable for passing to the + image_width and image_height helpers. + """ + map = map.render() + sprite_map = sprite_maps.get(map) + sprite_name = String.unquoted(sprite).value + sprite = sprite_map and sprite_map.get(sprite_name) + if not sprite_map: + log.error("No sprite map found: %s", map, extra={'stack': True}) + elif not sprite: + log.error("No sprite found: %s in %s", sprite_name, sprite_map['*n*'], extra={'stack': True}) + if sprite: + return String(sprite[1][0]) + return String.unquoted('') + + +@register('sprites', 1) +@register('sprite-names', 1) +def sprites(map): + map = map.render() + sprite_map = sprite_maps.get(map, {}) + return List(list(String.unquoted(s) for s in sorted(s for s in sprite_map if not s.startswith('*')))) + + +@register('sprite', 2) +@register('sprite', 3) +@register('sprite', 4) +@register('sprite', 5) +def sprite(map, sprite, offset_x=None, offset_y=None, cache_buster=True): + """ + Returns the image and background position for use in a single shorthand + property + """ + map = map.render() + sprite_map = sprite_maps.get(map) + sprite_name = String.unquoted(sprite).value + sprite = sprite_map and sprite_map.get(sprite_name) + if not sprite_map: + log.error("No sprite map found: %s", map, extra={'stack': True}) + elif not sprite: + log.error("No sprite found: %s in %s", sprite_name, sprite_map['*n*'], extra={'stack': True}) + if sprite: + url = '%s%s' % (config.ASSETS_URL, sprite_map['*f*']) + if cache_buster: + url += '?_=%s' % sprite_map['*t*'] + x = Number(offset_x or 0, 'px') + y = Number(offset_y or 0, 'px') + if not x.value or (x.value <= -1 or x.value >= 1) and not x.is_simple_unit('%'): + x -= Number(sprite[2], 'px') + if not y.value or (y.value <= -1 or y.value >= 1) and not y.is_simple_unit('%'): + y -= Number(sprite[3], 'px') + url = "url(%s)" % escape(url) + return List([String.unquoted(url), x, y]) + return List([Number(0), Number(0)]) + + +@register('sprite-url', 1) +@register('sprite-url', 2) +def sprite_url(map, cache_buster=True): + """ + Returns a url to the sprite image. + """ + map = map.render() + sprite_map = sprite_maps.get(map) + if not sprite_map: + log.error("No sprite map found: %s", map, extra={'stack': True}) + if sprite_map: + url = '%s%s' % (config.ASSETS_URL, sprite_map['*f*']) + if cache_buster: + url += '?_=%s' % sprite_map['*t*'] + url = "url(%s)" % escape(url) + return String.unquoted(url) + return String.unquoted('') + + +@register('sprite-position', 2) +@register('sprite-position', 3) +@register('sprite-position', 4) +def sprite_position(map, sprite, offset_x=None, offset_y=None): + """ + Returns the position for the original image in the sprite. + This is suitable for use as a value to background-position. + """ + map = map.render() + sprite_map = sprite_maps.get(map) + sprite_name = String.unquoted(sprite).value + sprite = sprite_map and sprite_map.get(sprite_name) + if not sprite_map: + log.error("No sprite map found: %s", map, extra={'stack': True}) + elif not sprite: + log.error("No sprite found: %s in %s", sprite_name, sprite_map['*n*'], extra={'stack': True}) + if sprite: + x = None + if offset_x is not None and not isinstance(offset_x, Number): + x = offset_x + if not x or x.value not in ('left', 'right', 'center'): + if x: + offset_x = None + x = Number(offset_x or 0, 'px') + if not x.value or (x.value <= -1 or x.value >= 1) and not x.is_simple_unit('%'): + x -= Number(sprite[2], 'px') + y = None + if offset_y is not None and not isinstance(offset_y, Number): + y = offset_y + if not y or y.value not in ('top', 'bottom', 'center'): + if y: + offset_y = None + y = Number(offset_y or 0, 'px') + if not y.value or (y.value <= -1 or y.value >= 1) and not y.is_simple_unit('%'): + y -= Number(sprite[3], 'px') + return List([x, y]) + return List([Number(0), Number(0)]) diff --git a/libs/scss/functions/core.py b/libs/scss/functions/core.py new file mode 100644 index 0000000..e916aca --- /dev/null +++ b/libs/scss/functions/core.py @@ -0,0 +1,784 @@ +"""Functions from the Sass "standard library", i.e., built into the original +Ruby implementation. +""" + +from __future__ import absolute_import +from __future__ import division + +import logging +import math + +from six.moves import xrange + +from scss.functions.library import FunctionLibrary +from scss.types import Boolean, Color, List, Null, Number, String, Map, expect_type + +log = logging.getLogger(__name__) + +CORE_LIBRARY = FunctionLibrary() +register = CORE_LIBRARY.register + + +# ------------------------------------------------------------------------------ +# Color creation + +def _interpret_percentage(n, relto=1., clamp=True): + expect_type(n, Number, unit='%') + + if n.is_unitless: + ret = n.value / relto + else: + ret = n.value / 100 + + if clamp: + if ret < 0: + return 0 + elif ret > 1: + return 1 + + return ret + + +@register('rgba', 4) +def rgba(r, g, b, a): + r = _interpret_percentage(r, relto=255) + g = _interpret_percentage(g, relto=255) + b = _interpret_percentage(b, relto=255) + a = _interpret_percentage(a, relto=1) + + return Color.from_rgb(r, g, b, a) + + +@register('rgb', 3) +def rgb(r, g, b, type='rgb'): + return rgba(r, g, b, Number(1.0)) + + +@register('rgba', 1) +@register('rgba', 2) +def rgba2(color, a=None): + if a is None: + alpha = 1 + else: + alpha = _interpret_percentage(a) + + return Color.from_rgb(*color.rgba[:3], alpha=alpha) + + +@register('rgb', 1) +def rgb1(color): + return rgba2(color, a=Number(1)) + + +@register('hsla', 4) +def hsla(h, s, l, a): + return Color.from_hsl( + h.value / 360 % 1, + # Ruby sass treats plain numbers for saturation and lightness as though + # they were percentages, just without the % + _interpret_percentage(s, relto=100), + _interpret_percentage(l, relto=100), + alpha=a.value, + ) + + +@register('hsl', 3) +def hsl(h, s, l): + return hsla(h, s, l, Number(1)) + + +@register('hsla', 1) +@register('hsla', 2) +def hsla2(color, a=None): + return rgba2(color, a) + + +@register('hsl', 1) +def hsl1(color): + return rgba2(color, a=Number(1)) + + +@register('mix', 2) +@register('mix', 3) +def mix(color1, color2, weight=Number(50, "%")): + """ + Mixes together two colors. Specifically, takes the average of each of the + RGB components, optionally weighted by the given percentage. + The opacity of the colors is also considered when weighting the components. + + Specifically, takes the average of each of the RGB components, + optionally weighted by the given percentage. + The opacity of the colors is also considered when weighting the components. + + The weight specifies the amount of the first color that should be included + in the returned color. + 50%, means that half the first color + and half the second color should be used. + 25% means that a quarter of the first color + and three quarters of the second color should be used. + + For example: + + mix(#f00, #00f) => #7f007f + mix(#f00, #00f, 25%) => #3f00bf + mix(rgba(255, 0, 0, 0.5), #00f) => rgba(63, 0, 191, 0.75) + """ + # This algorithm factors in both the user-provided weight + # and the difference between the alpha values of the two colors + # to decide how to perform the weighted average of the two RGB values. + # + # It works by first normalizing both parameters to be within [-1, 1], + # where 1 indicates "only use color1", -1 indicates "only use color 0", + # and all values in between indicated a proportionately weighted average. + # + # Once we have the normalized variables w and a, + # we apply the formula (w + a)/(1 + w*a) + # to get the combined weight (in [-1, 1]) of color1. + # This formula has two especially nice properties: + # + # * When either w or a are -1 or 1, the combined weight is also that number + # (cases where w * a == -1 are undefined, and handled as a special case). + # + # * When a is 0, the combined weight is w, and vice versa + # + # Finally, the weight of color1 is renormalized to be within [0, 1] + # and the weight of color2 is given by 1 minus the weight of color1. + # + # Algorithm from the Sass project: http://sass-lang.com/ + + p = _interpret_percentage(weight) + + # Scale weight to [-1, 1] + w = p * 2 - 1 + # Compute difference in alpha channels + a = color1.alpha - color2.alpha + + # Weight of first color + if w * a == -1: + # Avoid zero-div case + scaled_weight1 = w + else: + scaled_weight1 = (w + a) / (1 + w * a) + + # Unscale back to [0, 1] and get the weight of the other color + w1 = (scaled_weight1 + 1) / 2 + w2 = 1 - w1 + + # Do the scaling. Note that alpha isn't scaled by alpha, as that wouldn't + # make much sense; it uses the original untwiddled weight, p. + channels = [ + ch1 * w1 + ch2 * w2 + for (ch1, ch2) in zip(color1.rgba[:3], color2.rgba[:3])] + alpha = color1.alpha * p + color2.alpha * (1 - p) + return Color.from_rgb(*channels, alpha=alpha) + + +# ------------------------------------------------------------------------------ +# Color inspection + +@register('red', 1) +def red(color): + r, g, b, a = color.rgba + return Number(r * 255) + + +@register('green', 1) +def green(color): + r, g, b, a = color.rgba + return Number(g * 255) + + +@register('blue', 1) +def blue(color): + r, g, b, a = color.rgba + return Number(b * 255) + + +@register('opacity', 1) +@register('alpha', 1) +def alpha(color): + return Number(color.alpha) + + +@register('hue', 1) +def hue(color): + h, s, l = color.hsl + return Number(h * 360, "deg") + + +@register('saturation', 1) +def saturation(color): + h, s, l = color.hsl + return Number(s * 100, "%") + + +@register('lightness', 1) +def lightness(color): + h, s, l = color.hsl + return Number(l * 100, "%") + + +@register('ie-hex-str', 1) +def ie_hex_str(color): + c = Color(color).value + return String(u'#%02X%02X%02X%02X' % (round(c[3] * 255), round(c[0]), round(c[1]), round(c[2]))) + + +# ------------------------------------------------------------------------------ +# Color modification + +@register('fade-in', 2) +@register('fadein', 2) +@register('opacify', 2) +def opacify(color, amount): + r, g, b, a = color.rgba + return Color.from_rgb( + r, g, b, + alpha=color.alpha + amount.value) + + +@register('fade-out', 2) +@register('fadeout', 2) +@register('transparentize', 2) +def transparentize(color, amount): + r, g, b, a = color.rgba + return Color.from_rgb( + r, g, b, + alpha=color.alpha - amount.value) + + +@register('lighten', 2) +def lighten(color, amount): + return adjust_color(color, lightness=amount) + + +@register('darken', 2) +def darken(color, amount): + return adjust_color(color, lightness=-amount) + + +@register('saturate', 2) +def saturate(color, amount): + return adjust_color(color, saturation=amount) + + +@register('desaturate', 2) +def desaturate(color, amount): + return adjust_color(color, saturation=-amount) + + +@register('greyscale', 1) +def greyscale(color): + h, s, l = color.hsl + return Color.from_hsl(h, 0, l, alpha=color.alpha) + + +@register('grayscale', 1) +def grayscale(color): + if isinstance(color, Number) and color.is_unitless: + # grayscale(n) is a CSS3 filter and should be left intact, but only + # when using the "a" spelling + return String.unquoted("grayscale(%d)" % (color.value,)) + else: + return greyscale(color) + + +@register('spin', 2) +@register('adjust-hue', 2) +def adjust_hue(color, degrees): + h, s, l = color.hsl + delta = degrees.value / 360 + return Color.from_hsl((h + delta) % 1, s, l, alpha=color.alpha) + + +@register('complement', 1) +def complement(color): + h, s, l = color.hsl + return Color.from_hsl((h + 0.5) % 1, s, l, alpha=color.alpha) + + +@register('invert', 1) +def invert(color): + """ + Returns the inverse (negative) of a color. + The red, green, and blue values are inverted, while the opacity is left alone. + """ + r, g, b, a = color.rgba + return Color.from_rgb(1 - r, 1 - g, 1 - b, alpha=a) + + +@register('adjust-lightness', 2) +def adjust_lightness(color, amount): + return adjust_color(color, lightness=amount) + + +@register('adjust-saturation', 2) +def adjust_saturation(color, amount): + return adjust_color(color, saturation=amount) + + +@register('scale-lightness', 2) +def scale_lightness(color, amount): + return scale_color(color, lightness=amount) + + +@register('scale-saturation', 2) +def scale_saturation(color, amount): + return scale_color(color, saturation=amount) + + +@register('adjust-color') +def adjust_color(color, red=None, green=None, blue=None, hue=None, saturation=None, lightness=None, alpha=None): + do_rgb = red or green or blue + do_hsl = hue or saturation or lightness + if do_rgb and do_hsl: + raise ValueError("Can't adjust both RGB and HSL channels at the same time") + + zero = Number(0) + a = color.alpha + (alpha or zero).value + + if do_rgb: + r, g, b = color.rgba[:3] + channels = [ + current + (adjustment or zero).value / 255 + for (current, adjustment) in zip(color.rgba, (red, green, blue))] + return Color.from_rgb(*channels, alpha=a) + + else: + h, s, l = color.hsl + h = (h + (hue or zero).value / 360) % 1 + s += _interpret_percentage(saturation or zero, relto=100, clamp=False) + l += _interpret_percentage(lightness or zero, relto=100, clamp=False) + return Color.from_hsl(h, s, l, a) + + +def _scale_channel(channel, scaleby): + if scaleby is None: + return channel + + expect_type(scaleby, Number) + if not scaleby.is_simple_unit('%'): + raise ValueError("Expected percentage, got %r" % (scaleby,)) + + factor = scaleby.value / 100 + if factor > 0: + # Add x% of the remaining range, up to 1 + return channel + (1 - channel) * factor + else: + # Subtract x% of the existing channel. We add here because the factor + # is already negative + return channel * (1 + factor) + + +@register('scale-color') +def scale_color(color, red=None, green=None, blue=None, saturation=None, lightness=None, alpha=None): + do_rgb = red or green or blue + do_hsl = saturation or lightness + if do_rgb and do_hsl: + raise ValueError("Can't scale both RGB and HSL channels at the same time") + + scaled_alpha = _scale_channel(color.alpha, alpha) + + if do_rgb: + channels = [ + _scale_channel(channel, scaleby) + for channel, scaleby in zip(color.rgba, (red, green, blue))] + return Color.from_rgb(*channels, alpha=scaled_alpha) + + else: + channels = [ + _scale_channel(channel, scaleby) + for channel, scaleby in zip(color.hsl, (None, saturation, lightness))] + return Color.from_hsl(*channels, alpha=scaled_alpha) + + +@register('change-color') +def change_color(color, red=None, green=None, blue=None, hue=None, saturation=None, lightness=None, alpha=None): + do_rgb = red or green or blue + do_hsl = hue or saturation or lightness + if do_rgb and do_hsl: + raise ValueError("Can't change both RGB and HSL channels at the same time") + + if alpha is None: + alpha = color.alpha + else: + alpha = alpha.value + + if do_rgb: + channels = list(color.rgba[:3]) + if red: + channels[0] = _interpret_percentage(red, relto=255) + if green: + channels[1] = _interpret_percentage(green, relto=255) + if blue: + channels[2] = _interpret_percentage(blue, relto=255) + + return Color.from_rgb(*channels, alpha=alpha) + + else: + channels = list(color.hsl) + if hue: + expect_type(hue, Number, unit=None) + channels[0] = (hue.value / 360) % 1 + # Ruby sass treats plain numbers for saturation and lightness as though + # they were percentages, just without the % + if saturation: + channels[1] = _interpret_percentage(saturation, relto=100) + if lightness: + channels[2] = _interpret_percentage(lightness, relto=100) + + return Color.from_hsl(*channels, alpha=alpha) + + +# ------------------------------------------------------------------------------ +# String functions + +@register('e', 1) +@register('escape', 1) +@register('unquote') +def unquote(*args): + arg = List.from_maybe_starargs(args).maybe() + + if isinstance(arg, String): + return String(arg.value, quotes=None) + else: + return String(arg.render(), quotes=None) + + +@register('quote') +def quote(*args): + arg = List.from_maybe_starargs(args).maybe() + + if isinstance(arg, String): + return String(arg.value, quotes='"') + else: + return String(arg.render(), quotes='"') + + +@register('str-length', 1) +def str_length(string): + expect_type(string, String) + + # nb: can't use `len(string)`, because that gives the Sass list length, + # which is 1 + return Number(len(string.value)) + + +# TODO this and several others should probably also require integers +# TODO and assert that the indexes are valid +@register('str-insert', 3) +def str_insert(string, insert, index): + expect_type(string, String) + expect_type(insert, String) + expect_type(index, Number, unit=None) + + py_index = index.to_python_index(len(string.value), check_bounds=False) + return String( + string.value[:py_index] + + insert.value + + string.value[py_index:], + quotes=string.quotes) + + +@register('str-index', 2) +def str_index(string, substring): + expect_type(string, String) + expect_type(substring, String) + + # 1-based indexing, with 0 for failure + return Number(string.value.find(substring.value) + 1) + + +@register('str-slice', 2) +@register('str-slice', 3) +def str_slice(string, start_at, end_at=None): + expect_type(string, String) + expect_type(start_at, Number, unit=None) + py_start_at = start_at.to_python_index(len(string.value)) + + if end_at is None: + py_end_at = None + else: + expect_type(end_at, Number, unit=None) + # Endpoint is inclusive, unlike Python + py_end_at = end_at.to_python_index(len(string.value)) + 1 + + return String( + string.value[py_start_at:py_end_at], + quotes=string.quotes) + + +@register('to-upper-case', 1) +def to_upper_case(string): + expect_type(string, String) + + return String(string.value.upper(), quotes=string.quotes) + + +@register('to-lower-case', 1) +def to_lower_case(string): + expect_type(string, String) + + return String(string.value.lower(), quotes=string.quotes) + + +# ------------------------------------------------------------------------------ +# Number functions + +@register('percentage', 1) +def percentage(value): + expect_type(value, Number, unit=None) + return value * Number(100, unit='%') + +CORE_LIBRARY.add(Number.wrap_python_function(abs), 'abs', 1) +CORE_LIBRARY.add(Number.wrap_python_function(round), 'round', 1) +CORE_LIBRARY.add(Number.wrap_python_function(math.ceil), 'ceil', 1) +CORE_LIBRARY.add(Number.wrap_python_function(math.floor), 'floor', 1) + + +# ------------------------------------------------------------------------------ +# List functions + +def __parse_separator(separator, default_from=None): + if separator is None: + separator = 'auto' + separator = String.unquoted(separator).value + + if separator == 'comma': + return True + elif separator == 'space': + return False + elif separator == 'auto': + if not default_from: + return True + elif len(default_from) < 2: + return True + else: + return default_from.use_comma + else: + raise ValueError('Separator must be auto, comma, or space') + + +# TODO get the compass bit outta here +@register('-compass-list-size') +@register('length') +def _length(*lst): + if len(lst) == 1 and isinstance(lst[0], (list, tuple, List)): + lst = lst[0] + return Number(len(lst)) + + +@register('set-nth', 3) +def set_nth(list, n, value): + expect_type(n, Number, unit=None) + + py_n = n.to_python_index(len(list)) + return List( + tuple(list[:py_n]) + (value,) + tuple(list[py_n + 1:]), + use_comma=list.use_comma) + + +# TODO get the compass bit outta here +@register('-compass-nth', 2) +@register('nth', 2) +def nth(lst, n): + """Return the nth item in the list.""" + expect_type(n, (String, Number), unit=None) + + if isinstance(n, String): + if n.value.lower() == 'first': + i = 0 + elif n.value.lower() == 'last': + i = -1 + else: + raise ValueError("Invalid index %r" % (n,)) + else: + # DEVIATION: nth treats lists as circular lists + i = n.to_python_index(len(lst), circular=True) + + return lst[i] + + +@register('join', 2) +@register('join', 3) +def join(lst1, lst2, separator=None): + ret = [] + ret.extend(List.from_maybe(lst1)) + ret.extend(List.from_maybe(lst2)) + + use_comma = __parse_separator(separator, default_from=lst1) + return List(ret, use_comma=use_comma) + + +@register('min') +def min_(*lst): + if len(lst) == 1 and isinstance(lst[0], (list, tuple, List)): + lst = lst[0] + return min(lst) + + +@register('max') +def max_(*lst): + if len(lst) == 1 and isinstance(lst[0], (list, tuple, List)): + lst = lst[0] + return max(lst) + + +@register('append', 2) +@register('append', 3) +def append(lst, val, separator=None): + ret = [] + ret.extend(List.from_maybe(lst)) + ret.append(val) + + use_comma = __parse_separator(separator, default_from=lst) + return List(ret, use_comma=use_comma) + + +@register('index', 2) +def index(lst, val): + for i in xrange(len(lst)): + if lst.value[i] == val: + return Number(i + 1) + return Boolean(False) + + +@register('zip') +def zip_(*lists): + return List( + [List(zipped) for zipped in zip(*lists)], + use_comma=True) + + +# TODO need a way to use "list" as the arg name without shadowing the builtin +@register('list-separator', 1) +def list_separator(list): + if list.use_comma: + return String.unquoted('comma') + else: + return String.unquoted('space') + + +# ------------------------------------------------------------------------------ +# Map functions + +@register('map-get', 2) +def map_get(map, key): + return map.to_dict().get(key, Null()) + + +@register('map-merge', 2) +def map_merge(*maps): + key_order = [] + index = {} + for map in maps: + for key, value in map.to_pairs(): + if key not in index: + key_order.append(key) + + index[key] = value + + pairs = [(key, index[key]) for key in key_order] + return Map(pairs, index=index) + + +@register('map-keys', 1) +def map_keys(map): + return List( + [k for (k, v) in map.to_pairs()], + use_comma=True) + + +@register('map-values', 1) +def map_values(map): + return List( + [v for (k, v) in map.to_pairs()], + use_comma=True) + + +@register('map-has-key', 2) +def map_has_key(map, key): + return Boolean(key in map.to_dict()) + + +# DEVIATIONS: these do not exist in ruby sass + +@register('map-get', 3) +def map_get3(map, key, default): + return map.to_dict().get(key, default) + + +@register('map-get-nested', 2) +@register('map-get-nested', 3) +def map_get_nested3(map, keys, default=Null()): + for key in keys: + map = map.to_dict().get(key, None) + if map is None: + return default + + return map + + +@register('map-merge-deep', 2) +def map_merge_deep(*maps): + pairs = [] + keys = set() + for map in maps: + for key, value in map.to_pairs(): + keys.add(key) + + for key in keys: + values = [map.to_dict().get(key, None) for map in maps] + values = [v for v in values if v is not None] + if all(isinstance(v, Map) for v in values): + pairs.append((key, map_merge_deep(*values))) + else: + pairs.append((key, values[-1])) + + return Map(pairs) + + +# ------------------------------------------------------------------------------ +# Meta functions + +@register('type-of', 1) +def _type_of(obj): # -> bool, number, string, color, list + return String(obj.sass_type_name) + + +@register('unit', 1) +def unit(number): # -> px, em, cm, etc. + numer = '*'.join(sorted(number.unit_numer)) + denom = '*'.join(sorted(number.unit_denom)) + + if denom: + ret = numer + '/' + denom + else: + ret = numer + return String.unquoted(ret) + + +@register('unitless', 1) +def unitless(value): + if not isinstance(value, Number): + raise TypeError("Expected number, got %r" % (value,)) + + return Boolean(value.is_unitless) + + +@register('comparable', 2) +def comparable(number1, number2): + left = number1.to_base_units() + right = number2.to_base_units() + return Boolean( + left.unit_numer == right.unit_numer + and left.unit_denom == right.unit_denom) + + +# ------------------------------------------------------------------------------ +# Miscellaneous + +@register('if', 2) +@register('if', 3) +def if_(condition, if_true, if_false=Null()): + return if_true if condition else if_false diff --git a/libs/scss/functions/extra.py b/libs/scss/functions/extra.py new file mode 100644 index 0000000..da317a6 --- /dev/null +++ b/libs/scss/functions/extra.py @@ -0,0 +1,471 @@ +"""Functions new to the pyScss library.""" + +from __future__ import absolute_import + +import base64 +import hashlib +import logging +import os.path +import random + +import six +from six.moves import xrange + +from scss import config +from scss.functions.library import FunctionLibrary +from scss.types import Color, Number, String, List +from scss.util import escape + +try: + from PIL import Image, ImageDraw +except ImportError: + try: + import Image + import ImageDraw + except: + Image = None + +log = logging.getLogger(__name__) + +EXTRA_LIBRARY = FunctionLibrary() +register = EXTRA_LIBRARY.register + + +# ------------------------------------------------------------------------------ +# Image stuff +def _image_noise(pixdata, size, density=None, intensity=None, color=None, opacity=None, monochrome=None, background=None): + if not density: + density = [0.8] + elif not isinstance(density, (tuple, list)): + density = [density] + + if not intensity: + intensity = [0.5] + elif not isinstance(intensity, (tuple, list)): + intensity = [intensity] + + if not color: + color = [(0, 0, 0, 0)] + elif not isinstance(color, (tuple, list)) or not isinstance(color[0], (tuple, list)): + color = [color] + + if not opacity: + opacity = [0.2] + elif not isinstance(opacity, (tuple, list)): + opacity = [opacity] + + if not monochrome: + monochrome = [False] + elif not isinstance(monochrome, (tuple, list)): + monochrome = [monochrome] + + pixels = {} + + if background: + for y in xrange(size): + for x in xrange(size): + ca = float(background[3]) + pixels[(x, y)] = (background[0] * ca, background[1] * ca, background[2] * ca, ca) + + loops = max(map(len, (density, intensity, color, opacity, monochrome))) + for l in range(loops): + _density = density[l % len(density)] + _intensity = intensity[l % len(intensity)] + _color = color[l % len(color)] + _opacity = opacity[l % len(opacity)] + _monochrome = monochrome[l % len(monochrome)] + _intensity = 1 - _intensity + if _intensity < 0.5: + cx = 255 * _intensity + cm = cx + else: + cx = 255 * (1 - _intensity) + cm = 255 * _intensity + xa = int(cm - cx) + xb = int(cm + cx) + if xa > 0: + xa &= 255 + else: + xa = 0 + if xb > 0: + xb &= 255 + else: + xb = 0 + r, g, b, a = _color + for i in xrange(int(round(_density * size ** 2))): + x = random.randint(1, size) + y = random.randint(1, size) + cc = random.randint(xa, xb) + cr = (cc) * (1 - a) + a * r + cg = (cc if _monochrome else random.randint(xa, xb)) * (1 - a) + a * g + cb = (cc if _monochrome else random.randint(xa, xb)) * (1 - a) + a * b + ca = random.random() * _opacity + ica = 1 - ca + pos = (x - 1, y - 1) + dst = pixels.get(pos, (0, 0, 0, 0)) + src = (cr * ca, cg * ca, cb * ca, ca) + pixels[pos] = (src[0] + dst[0] * ica, src[1] + dst[1] * ica, src[2] + dst[2] * ica, src[3] + dst[3] * ica) + + for pos, col in pixels.items(): + ca = col[3] + if ca: + pixdata[pos] = tuple(int(round(c)) for c in (col[0] / ca, col[1] / ca, col[2] / ca, ca * 255)) + + +def _image_brushed(pixdata, size, density=None, intensity=None, color=None, opacity=None, monochrome=None, direction=None, spread=None, background=None): + if not density: + density = [0.8] + elif not isinstance(density, (tuple, list)): + density = [density] + + if not intensity: + intensity = [0.5] + elif not isinstance(intensity, (tuple, list)): + intensity = [intensity] + + if not color: + color = [(0, 0, 0, 0)] + elif not isinstance(color, (tuple, list)) or not isinstance(color[0], (tuple, list)): + color = [color] + + if not opacity: + opacity = [0.2] + elif not isinstance(opacity, (tuple, list)): + opacity = [opacity] + + if not monochrome: + monochrome = [False] + elif not isinstance(monochrome, (tuple, list)): + monochrome = [monochrome] + + if not direction: + direction = [0] + elif not isinstance(direction, (tuple, list)): + direction = [direction] + + if not spread: + spread = [0] + elif not isinstance(spread, (tuple, list)): + spread = [spread] + + def ppgen(d): + if d is None: + return + d = d % 4 + if d == 0: + pp = lambda x, y, o: ((x - o) % size, y) + elif d == 1: + pp = lambda x, y, o: ((x - o) % size, (y + x - o) % size) + elif d == 2: + pp = lambda x, y, o: (y, (x - o) % size) + else: + pp = lambda x, y, o: ((x - o) % size, (y - x - o) % size) + return pp + + pixels = {} + + if background: + for y in xrange(size): + for x in xrange(size): + ca = float(background[3]) + pixels[(x, y)] = (background[0] * ca, background[1] * ca, background[2] * ca, ca) + + loops = max(map(len, (density, intensity, color, opacity, monochrome, direction, spread))) + for l in range(loops): + _density = density[l % len(density)] + _intensity = intensity[l % len(intensity)] + _color = color[l % len(color)] + _opacity = opacity[l % len(opacity)] + _monochrome = monochrome[l % len(monochrome)] + _direction = direction[l % len(direction)] + _spread = spread[l % len(spread)] + _intensity = 1 - _intensity + if _intensity < 0.5: + cx = 255 * _intensity + cm = cx + else: + cx = 255 * (1 - _intensity) + cm = 255 * _intensity + xa = int(cm - cx) + xb = int(cm + cx) + if xa > 0: + xa &= 255 + else: + xa = 0 + if xb > 0: + xb &= 255 + else: + xb = 0 + r, g, b, a = _color + pp = ppgen(_direction) + if pp: + for y in xrange(size): + if _spread and (y + (l % 2)) % _spread: + continue + o = random.randint(1, size) + cc = random.randint(xa, xb) + cr = (cc) * (1 - a) + a * r + cg = (cc if _monochrome else random.randint(xa, xb)) * (1 - a) + a * g + cb = (cc if _monochrome else random.randint(xa, xb)) * (1 - a) + a * b + da = random.randint(0, 255) * _opacity + ip = round((size / 2.0 * _density) / int(1 / _density)) + iq = round((size / 2.0 * (1 - _density)) / int(1 / _density)) + if ip: + i = da / ip + aa = 0 + else: + i = 0 + aa = da + d = 0 + p = ip + for x in xrange(size): + if d == 0: + if p > 0: + p -= 1 + aa += i + else: + d = 1 + q = iq + elif d == 1: + if q > 0: + q -= 1 + else: + d = 2 + p = ip + elif d == 2: + if p > 0: + p -= 1 + aa -= i + else: + d = 3 + q = iq + elif d == 3: + if q > 0: + q -= 1 + else: + d = 0 + p = ip + if aa > 0: + ca = aa / 255.0 + else: + ca = 0.0 + ica = 1 - ca + pos = pp(x, y, o) + dst = pixels.get(pos, (0, 0, 0, 0)) + src = (cr * ca, cg * ca, cb * ca, ca) + pixels[pos] = (src[0] + dst[0] * ica, src[1] + dst[1] * ica, src[2] + dst[2] * ica, src[3] + dst[3] * ica) + + for pos, col in pixels.items(): + ca = col[3] + if ca: + pixdata[pos] = tuple(int(round(c)) for c in (col[0] / ca, col[1] / ca, col[2] / ca, ca * 255)) + + +@register('background-noise', 0) +@register('background-noise', 1) +@register('background-noise', 2) +@register('background-noise', 3) +@register('background-noise', 4) +@register('background-noise', 5) +@register('background-noise', 6) +@register('background-noise', 7) +def background_noise(density=None, opacity=None, size=None, monochrome=False, intensity=(), color=None, background=None, inline=False): + if not Image: + raise Exception("Images manipulation require PIL") + + density = [Number(v).value for v in List.from_maybe(density)] + intensity = [Number(v).value for v in List.from_maybe(intensity)] + color = [Color(v).value for v in List.from_maybe(color) if v] + opacity = [Number(v).value for v in List.from_maybe(opacity)] + + size = int(Number(size).value) if size else 0 + if size < 1 or size > 512: + size = 200 + + monochrome = bool(monochrome) + + background = Color(background).value if background else None + + new_image = Image.new( + mode='RGBA', + size=(size, size) + ) + + pixdata = new_image.load() + _image_noise(pixdata, size, density, intensity, color, opacity, monochrome) + + if not inline: + key = (size, density, intensity, color, opacity, monochrome) + asset_file = 'noise-%s%sx%s' % ('mono-' if monochrome else '', size, size) + # asset_file += '-[%s][%s]' % ('-'.join(to_str(s).replace('.', '_') for s in density or []), '-'.join(to_str(s).replace('.', '_') for s in opacity or [])) + asset_file += '-' + base64.urlsafe_b64encode(hashlib.md5(repr(key)).digest()).rstrip('=').replace('-', '_') + asset_file += '.png' + asset_path = os.path.join(config.ASSETS_ROOT or os.path.join(config.STATIC_ROOT, 'assets'), asset_file) + try: + new_image.save(asset_path) + except IOError: + log.exception("Error while saving image") + inline = True # Retry inline version + url = '%s%s' % (config.ASSETS_URL, asset_file) + if inline: + output = six.BytesIO() + new_image.save(output, format='PNG') + contents = output.getvalue() + output.close() + url = 'data:image/png;base64,' + base64.b64encode(contents) + + inline = 'url("%s")' % escape(url) + return String.unquoted(inline) + + +@register('background-brushed', 0) +@register('background-brushed', 1) +@register('background-brushed', 2) +@register('background-brushed', 3) +@register('background-brushed', 4) +@register('background-brushed', 5) +@register('background-brushed', 6) +@register('background-brushed', 7) +@register('background-brushed', 8) +@register('background-brushed', 9) +def background_brushed(density=None, intensity=None, color=None, opacity=None, size=None, monochrome=False, direction=(), spread=(), background=None, inline=False): + if not Image: + raise Exception("Images manipulation require PIL") + + density = [Number(v).value for v in List.from_maybe(density)] + intensity = [Number(v).value for v in List.from_maybe(intensity)] + color = [Color(v).value for v in List.from_maybe(color) if v] + opacity = [Number(v).value for v in List.from_maybe(opacity)] + + size = int(Number(size).value) if size else -1 + if size < 0 or size > 512: + size = 200 + + monochrome = bool(monochrome) + + direction = [Number(v).value for v in List.from_maybe(direction)] + spread = [Number(v).value for v in List.from_maybe(spread)] + + background = Color(background).value if background else None + + new_image = Image.new( + mode='RGBA', + size=(size, size) + ) + + pixdata = new_image.load() + _image_brushed(pixdata, size, density, intensity, color, opacity, monochrome, direction, spread, background) + + if not inline: + key = (size, density, intensity, color, opacity, monochrome, direction, spread, background) + asset_file = 'brushed-%s%sx%s' % ('mono-' if monochrome else '', size, size) + # asset_file += '-[%s][%s][%s]' % ('-'.join(to_str(s).replace('.', '_') for s in density or []), '-'.join(to_str(s).replace('.', '_') for s in opacity or []), '-'.join(to_str(s).replace('.', '_') for s in direction or [])) + asset_file += '-' + base64.urlsafe_b64encode(hashlib.md5(repr(key)).digest()).rstrip('=').replace('-', '_') + asset_file += '.png' + asset_path = os.path.join(config.ASSETS_ROOT or os.path.join(config.STATIC_ROOT, 'assets'), asset_file) + try: + new_image.save(asset_path) + except IOError: + log.exception("Error while saving image") + inline = True # Retry inline version + url = '%s%s' % (config.ASSETS_URL, asset_file) + if inline: + output = six.BytesIO() + new_image.save(output, format='PNG') + contents = output.getvalue() + output.close() + url = 'data:image/png;base64,' + base64.b64encode(contents) + + inline = 'url("%s")' % escape(url) + return String.unquoted(inline) + + +@register('grid-image', 4) +@register('grid-image', 5) +def _grid_image(left_gutter, width, right_gutter, height, columns=1, grid_color=None, baseline_color=None, background_color=None, inline=False): + if not Image: + raise Exception("Images manipulation require PIL") + if grid_color is None: + grid_color = (120, 170, 250, 15) + else: + c = Color(grid_color).value + grid_color = (c[0], c[1], c[2], int(c[3] * 255.0)) + if baseline_color is None: + baseline_color = (120, 170, 250, 30) + else: + c = Color(baseline_color).value + baseline_color = (c[0], c[1], c[2], int(c[3] * 255.0)) + if background_color is None: + background_color = (0, 0, 0, 0) + else: + c = Color(background_color).value + background_color = (c[0], c[1], c[2], int(c[3] * 255.0)) + _height = int(height) if height >= 1 else int(height * 1000.0) + _width = int(width) if width >= 1 else int(width * 1000.0) + _left_gutter = int(left_gutter) if left_gutter >= 1 else int(left_gutter * 1000.0) + _right_gutter = int(right_gutter) if right_gutter >= 1 else int(right_gutter * 1000.0) + if _height <= 0 or _width <= 0 or _left_gutter <= 0 or _right_gutter <= 0: + raise ValueError + _full_width = (_left_gutter + _width + _right_gutter) + new_image = Image.new( + mode='RGBA', + size=(_full_width * int(columns), _height), + color=background_color + ) + draw = ImageDraw.Draw(new_image) + for i in range(int(columns)): + draw.rectangle((i * _full_width + _left_gutter, 0, i * _full_width + _left_gutter + _width - 1, _height - 1), fill=grid_color) + if _height > 1: + draw.rectangle((0, _height - 1, _full_width * int(columns) - 1, _height - 1), fill=baseline_color) + if not inline: + grid_name = 'grid_' + if left_gutter: + grid_name += str(int(left_gutter)) + '+' + grid_name += str(int(width)) + if right_gutter: + grid_name += '+' + str(int(right_gutter)) + if height and height > 1: + grid_name += 'x' + str(int(height)) + key = (columns, grid_color, baseline_color, background_color) + key = grid_name + '-' + base64.urlsafe_b64encode(hashlib.md5(repr(key)).digest()).rstrip('=').replace('-', '_') + asset_file = key + '.png' + asset_path = os.path.join(config.ASSETS_ROOT or os.path.join(config.STATIC_ROOT, 'assets'), asset_file) + try: + new_image.save(asset_path) + except IOError: + log.exception("Error while saving image") + inline = True # Retry inline version + url = '%s%s' % (config.ASSETS_URL, asset_file) + if inline: + output = six.BytesIO() + new_image.save(output, format='PNG') + contents = output.getvalue() + output.close() + url = 'data:image/png;base64,' + base64.b64encode(contents) + inline = 'url("%s")' % escape(url) + return String.unquoted(inline) + + +@register('image-color', 1) +@register('image-color', 2) +@register('image-color', 3) +def image_color(color, width=1, height=1): + if not Image: + raise Exception("Images manipulation require PIL") + w = int(Number(width).value) + h = int(Number(height).value) + if w <= 0 or h <= 0: + raise ValueError + new_image = Image.new( + mode='RGB' if color.alpha == 1 else 'RGBA', + size=(w, h), + color=color.rgba255, + ) + output = six.BytesIO() + new_image.save(output, format='PNG') + contents = output.getvalue() + output.close() + mime_type = 'image/png' + url = 'data:' + mime_type + ';base64,' + base64.b64encode(contents) + inline = 'url("%s")' % escape(url) + return String.unquoted(inline) diff --git a/libs/scss/functions/library.py b/libs/scss/functions/library.py new file mode 100644 index 0000000..ce4a45c --- /dev/null +++ b/libs/scss/functions/library.py @@ -0,0 +1,81 @@ +"""Defines `FunctionLibrary`, a class that collects functions to be exposed to +SCSS during compilation. +""" + +# TODO the constant versions of this should be frozen +class FunctionLibrary(object): + """Contains a set of functions to be exposed to SCSS. + + Functions are keyed by both their name _and_ arity; this allows for + function overloading somewhat beyond what Python easily allows, and is + required for several functions in the standard Sass library. + + Functions may also have arbitrary arity, in which case they accept any + number of arguments, though a function of the same name with a specific + arity takes precedence. + """ + + def __init__(self): + self._functions = {} + + def derive(self): + """Returns a new registry object, pre-populated with all the functions + in this registry. + """ + new = type(self)() + new.inherit(self) + return new + + def inherit(self, *other_libraries): + """Import all the functions from the given other libraries into this one. + + Note that existing functions ARE NOT replaced -- which also means that + functions from the first library take precedence over functions from + the second library, etc. + """ + new_functions = dict() + + # dict.update replaces keys; go through the other libraries in reverse, + # so the first one wins + for library in reversed(other_libraries): + new_functions.update(library._functions) + + new_functions.update(self._functions) + self._functions = new_functions + + def register(self, name, argc=None): + """Decorator for adding a function to this library.""" + # XXX: this should allow specifying names of keyword arguments, as + # well. currently none of these functions support kwargs, i think. + # XXX automatically inferring the name and args would be... + # interesting + # XXX also it would be nice if a function which EXISTS but has the + # wrong number of args threw a useful error; atm i think it'll become + # literal (yikes!) + + key = (name, argc) + + def decorator(f): + self._functions[key] = f + return f + + return decorator + + def add(self, function, name, argc=None): + """Add a function to this library imperatively.""" + + key = (name, argc) + self._functions[key] = function + + def lookup(self, name, argc=None): + """Find a function given its name and the number of arguments it takes. + Falls back to a function with the same name that takes any number of + arguments. + """ + # Try the given arity first + key = name, argc + if key in self._functions: + return self._functions[key] + + # Fall back to arbitrary-arity (or KeyError if neither exists) + return self._functions[name, None] diff --git a/libs/scss/rule.py b/libs/scss/rule.py new file mode 100644 index 0000000..60fec68 --- /dev/null +++ b/libs/scss/rule.py @@ -0,0 +1,524 @@ +from __future__ import absolute_import +from __future__ import print_function + +import logging +import six + +from scss.types import Value, Undefined + +log = logging.getLogger(__name__) + +SORTED_SELECTORS = False + +sort = sorted if SORTED_SELECTORS else lambda it: it + + +def normalize_var(name): + if isinstance(name, six.string_types): + return name.replace('_', '-') + else: + log.warn("Variable name doesn't look like a string: %r", name) + return name + + +def extend_unique(seq, more): + """Return a new sequence containing the items in `seq` plus any items in + `more` that aren't already in `seq`, preserving the order of both. + """ + seen = set(seq) + new = [] + for item in more: + if item not in seen: + seen.add(item) + new.append(item) + + return seq + type(seq)(new) + + +class Scope(object): + """Implements Sass variable scoping. + + Similar to `ChainMap`, except that assigning a new value will replace an + existing value, not mask it. + """ + def __init__(self, maps=()): + maps = list(maps) + assert all(isinstance(m, dict) or isinstance(m, type(self)) for m in maps) # Check all passed maps are compatible with the current Scope + self.maps = [dict()] + maps + + def __repr__(self): + return "<%s(%s) at 0x%x>" % (type(self).__name__, ', '.join(repr(map) for map in self.maps), id(self)) + + def __getitem__(self, key): + for map in self.maps: + if key in map: + return map[key] + + raise KeyError(key) + + def __setitem__(self, key, value): + self.set(key, value) + + def __contains__(self, key): + try: + self[key] + except KeyError: + return False + else: + return True + + def keys(self): + # For mapping interface + keys = set() + for map in self.maps: + keys.update(map.keys()) + return list(keys) + + def set(self, key, value, force_local=False): + if not force_local: + for map in self.maps: + if key in map: + if isinstance(map[key], Undefined): + break + map[key] = value + return + + self.maps[0][key] = value + + def new_child(self): + return type(self)(self.maps) + + +class VariableScope(Scope): + pass + + +class FunctionScope(Scope): + def __repr__(self): + return "<%s(%s) at 0x%x>" % (type(self).__name__, ', '.join('[%s]' % ', '.join('%s:%s' % (f, n) for f, n in sorted(map.keys())) for map in self.maps), id(self)) + + +class MixinScope(Scope): + def __repr__(self): + return "<%s(%s) at 0x%x>" % (type(self).__name__, ', '.join('[%s]' % ', '.join('%s:%s' % (f, n) for f, n in sorted(map.keys())) for map in self.maps), id(self)) + + +class ImportScope(Scope): + pass + + +class Namespace(object): + """...""" + _mutable = True + + def __init__(self, variables=None, functions=None, mixins=None, mutable=True): + self._mutable = mutable + + if variables is None: + self._variables = VariableScope() + else: + # TODO parse into sass values once that's a thing, or require them + # all to be + self._variables = VariableScope([variables]) + + if functions is None: + self._functions = FunctionScope() + else: + self._functions = FunctionScope([functions._functions]) + + self._mixins = MixinScope() + + self._imports = ImportScope() + + def _assert_mutable(self): + if not self._mutable: + raise AttributeError("This Namespace instance is immutable") + + @classmethod + def derive_from(cls, *others): + self = cls() + if len(others) == 1: + self._variables = others[0]._variables.new_child() + self._functions = others[0]._functions.new_child() + self._mixins = others[0]._mixins.new_child() + self._imports = others[0]._imports.new_child() + else: + # Note that this will create a 2-dimensional scope where each of + # these scopes is checked first in order. TODO is this right? + self._variables = VariableScope(other._variables for other in others) + self._functions = FunctionScope(other._functions for other in others) + self._mixins = MixinScope(other._mixins for other in others) + self._imports = ImportScope(other._imports for other in others) + return self + + def derive(self): + """Return a new child namespace. All existing variables are still + readable and writeable, but any new variables will only exist within a + new scope. + """ + return type(self).derive_from(self) + + @property + def variables(self): + return dict((k, self._variables[k]) for k in self._variables.keys()) + + def variable(self, name, throw=False): + name = normalize_var(name) + return self._variables[name] + + def set_variable(self, name, value, local_only=False): + self._assert_mutable() + name = normalize_var(name) + if not isinstance(value, Value): + raise TypeError("Expected a Sass type, while setting %s got %r" % (name, value,)) + self._variables.set(name, value, force_local=local_only) + + def has_import(self, import_key): + return import_key in self._imports + + def add_import(self, import_key, parent_import_key, file_and_line): + self._assert_mutable() + if import_key: + imports = [0, parent_import_key, file_and_line] + self._imports[import_key] = imports + + def use_import(self, import_key): + self._assert_mutable() + if import_key and import_key in self._imports: + imports = self._imports[import_key] + imports[0] += 1 + self.use_import(imports[1]) + + def unused_imports(self): + unused = [] + for import_key in self._imports.keys(): + imports = self._imports[import_key] + if not imports[0]: + unused.append((import_key[0], imports[2])) + return unused + + def _get_callable(self, chainmap, name, arity): + name = normalize_var(name) + if arity is not None: + # With explicit arity, try the particular arity before falling back + # to the general case (None) + try: + return chainmap[name, arity] + except KeyError: + pass + + return chainmap[name, None] + + def _set_callable(self, chainmap, name, arity, cb): + name = normalize_var(name) + chainmap[name, arity] = cb + + def mixin(self, name, arity): + return self._get_callable(self._mixins, name, arity) + + def set_mixin(self, name, arity, cb): + self._assert_mutable() + self._set_callable(self._mixins, name, arity, cb) + + def function(self, name, arity): + return self._get_callable(self._functions, name, arity) + + def set_function(self, name, arity, cb): + self._assert_mutable() + self._set_callable(self._functions, name, arity, cb) + + +class SassRule(object): + """At its heart, a CSS rule: combination of a selector and zero or more + properties. But this is Sass, so it also tracks some Sass-flavored + metadata, like `@extend` rules and `@media` nesting. + """ + + def __init__(self, source_file, import_key=None, unparsed_contents=None, + options=None, properties=None, + namespace=None, + lineno=0, extends_selectors=frozenset(), + ancestry=None, + nested=0): + + self.source_file = source_file + self.import_key = import_key + self.lineno = lineno + + self.unparsed_contents = unparsed_contents + self.options = options + self.extends_selectors = extends_selectors + + if namespace is None: + assert False + self.namespace = Namespace() + else: + self.namespace = namespace + + if properties is None: + self.properties = [] + else: + self.properties = properties + + if ancestry is None: + self.ancestry = RuleAncestry() + else: + self.ancestry = ancestry + + self.nested = nested + + self.descendants = 0 + + def __repr__(self): + return "" % ( + self.ancestry, + len(self.properties), + ) + + @property + def selectors(self): + # TEMPORARY + if self.ancestry.headers and self.ancestry.headers[-1].is_selector: + return self.ancestry.headers[-1].selectors + else: + return () + + @property + def file_and_line(self): + """Return the filename and line number where this rule originally + appears, in the form "foo.scss:3". Used for error messages. + """ + return "%s:%d" % (self.source_file.filename, self.lineno) + + @property + def is_empty(self): + """Return whether this rule is considered "empty" -- i.e., has no + contents that should end up in the final CSS. + """ + if self.properties: + # Rules containing CSS properties are never empty + return False + + if not self.descendants: + for header in self.ancestry.headers: + if header.is_atrule and header.directive != '@media': + # At-rules should always be preserved, UNLESS they are @media + # blocks, which are known to be noise if they don't have any + # contents of their own + return False + + return True + + def copy(self): + return type(self)( + source_file=self.source_file, + lineno=self.lineno, + unparsed_contents=self.unparsed_contents, + + options=self.options, + #properties=list(self.properties), + properties=self.properties, + extends_selectors=self.extends_selectors, + #ancestry=list(self.ancestry), + ancestry=self.ancestry, + + namespace=self.namespace.derive(), + nested=self.nested, + ) + + +class RuleAncestry(object): + def __init__(self, headers=()): + self.headers = tuple(headers) + + def __repr__(self): + return "<%s %r>" % (type(self).__name__, self.headers) + + def __len__(self): + return len(self.headers) + + def with_nested_selectors(self, c_selectors): + if self.headers and self.headers[-1].is_selector: + # Need to merge with parent selectors + p_selectors = self.headers[-1].selectors + + new_selectors = [] + for p_selector in p_selectors: + for c_selector in c_selectors: + new_selectors.append(c_selector.with_parent(p_selector)) + + # Replace the last header with the new merged selectors + new_headers = self.headers[:-1] + (BlockSelectorHeader(new_selectors),) + return RuleAncestry(new_headers) + + else: + # Whoops, no parent selectors. Just need to double-check that + # there are no uses of `&`. + for c_selector in c_selectors: + if c_selector.has_parent_reference: + raise ValueError("Can't use parent selector '&' in top-level rules") + + # Add the children as a new header + new_headers = self.headers + (BlockSelectorHeader(c_selectors),) + return RuleAncestry(new_headers) + + def with_more_selectors(self, selectors): + """Return a new ancestry that also matches the given selectors. No + nesting is done. + """ + if self.headers and self.headers[-1].is_selector: + new_selectors = extend_unique( + self.headers[-1].selectors, + selectors) + new_headers = self.headers[:-1] + ( + BlockSelectorHeader(new_selectors),) + return RuleAncestry(new_headers) + else: + new_headers = self.headers + (BlockSelectorHeader(selectors),) + return RuleAncestry(new_headers) + + +class BlockHeader(object): + """...""" + # TODO doc me depending on how UnparsedBlock is handled... + + is_atrule = False + is_scope = False + is_selector = False + + @classmethod + def parse(cls, prop, has_contents=False): + # Simple pre-processing + if prop.startswith('+') and not has_contents: + # Expand '+' at the beginning of a rule as @include. But not if + # there's a block, because that's probably a CSS selector. + # DEVIATION: this is some semi hybrid of Sass and xCSS syntax + prop = '@include ' + prop[1:] + try: + if '(' not in prop or prop.index(':') < prop.index('('): + prop = prop.replace(':', '(', 1) + if '(' in prop: + prop += ')' + except ValueError: + pass + elif prop.startswith('='): + # Expand '=' at the beginning of a rule as @mixin + prop = '@mixin ' + prop[1:] + elif prop.startswith('@prototype '): + # Remove '@prototype ' + # TODO what is @prototype?? + prop = prop[11:] + + # Minor parsing + if prop.startswith('@'): + if prop.lower().startswith('@else if '): + directive = '@else if' + argument = prop[9:] + else: + directive, _, argument = prop.partition(' ') + directive = directive.lower() + + return BlockAtRuleHeader(directive, argument) + else: + if prop.endswith(':') or ': ' in prop: + # Syntax is ": [prop]" -- if the optional prop exists, + # it becomes the first rule with no suffix + scope, unscoped_value = prop.split(':', 1) + scope = scope.rstrip() + unscoped_value = unscoped_value.lstrip() + return BlockScopeHeader(scope, unscoped_value) + else: + return BlockSelectorHeader(prop) + + +class BlockAtRuleHeader(BlockHeader): + is_atrule = True + + def __init__(self, directive, argument): + self.directive = directive + self.argument = argument + + def __repr__(self): + return "<%s %r %r>" % (type(self).__name__, self.directive, self.argument) + + def render(self): + if self.argument: + return "%s %s" % (self.directive, self.argument) + else: + return self.directive + + +class BlockSelectorHeader(BlockHeader): + is_selector = True + + def __init__(self, selectors): + self.selectors = tuple(selectors) + + def __repr__(self): + return "<%s %r>" % (type(self).__name__, self.selectors) + + def render(self, sep=', ', super_selector=''): + return sep.join(sort( + super_selector + s.render() + for s in self.selectors + if not s.has_placeholder)) + + +class BlockScopeHeader(BlockHeader): + is_scope = True + + def __init__(self, scope, unscoped_value): + self.scope = scope + + if unscoped_value: + self.unscoped_value = unscoped_value + else: + self.unscoped_value = None + + +class UnparsedBlock(object): + """A Sass block whose contents have not yet been parsed. + + At the top level, CSS (and Sass) documents consist of a sequence of blocks. + A block may be a ruleset: + + selector { block; block; block... } + + Or it may be an @-rule: + + @rule arguments { block; block; block... } + + Or it may be only a single property declaration: + + property: value + + pyScss's first parsing pass breaks the document into these blocks, and each + block becomes an instance of this class. + """ + + def __init__(self, parent_rule, lineno, prop, unparsed_contents): + self.parent_rule = parent_rule + self.header = BlockHeader.parse(prop, has_contents=bool(unparsed_contents)) + + # Basic properties + self.lineno = lineno + self.prop = prop + self.unparsed_contents = unparsed_contents + + @property + def directive(self): + return self.header.directive + + @property + def argument(self): + return self.header.argument + + ### What kind of thing is this? + + @property + def is_atrule(self): + return self.header.is_atrule + + @property + def is_scope(self): + return self.header.is_scope diff --git a/libs/scss/scss_meta.py b/libs/scss/scss_meta.py new file mode 100644 index 0000000..481eba4 --- /dev/null +++ b/libs/scss/scss_meta.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python +#-*- coding: utf-8 -*- +""" +pyScss, a Scss compiler for Python + +@author German M. Bravo (Kronuz) +@version 1.2.0 alpha +@see https://github.com/Kronuz/pyScss +@copyright (c) 2012-2013 German M. Bravo (Kronuz) +@license MIT License + http://www.opensource.org/licenses/mit-license.php + +pyScss compiles Scss, a superset of CSS that is more powerful, elegant and +easier to maintain than plain-vanilla CSS. The library acts as a CSS source code +preprocesor which allows you to use variables, nested rules, mixins, andhave +inheritance of rules, all with a CSS-compatible syntax which the preprocessor +then compiles to standard CSS. + +Scss, as an extension of CSS, helps keep large stylesheets well-organized. It +borrows concepts and functionality from projects such as OOCSS and other similar +frameworks like as Sass. It's build on top of the original PHP xCSS codebase +structure but it's been completely rewritten, many bugs have been fixed and it +has been extensively extended to support almost the full range of Sass' Scss +syntax and functionality. + +Bits of code in pyScss come from various projects: +Compass: + (c) 2009 Christopher M. Eppstein + http://compass-style.org/ +Sass: + (c) 2006-2009 Hampton Catlin and Nathan Weizenbaum + http://sass-lang.com/ +xCSS: + (c) 2010 Anton Pawlik + http://xcss.antpaw.org/docs/ + + This file defines Meta data, according to PEP314 + (http://www.python.org/dev/peps/pep-0314/) which is common to both pyScss + and setup.py distutils. + + We create this here so this information can be compatible with BOTH + Python 2.x and Python 3.x so setup.py can use it when building pyScss + for both Py3.x and Py2.x + +""" + +VERSION_INFO = (1, 2, 0, 'post3') +DATE_INFO = (2013, 10, 8) # YEAR, MONTH, DAY +VERSION = '.'.join(str(i) for i in VERSION_INFO) +REVISION = '%04d%02d%02d' % DATE_INFO +BUILD_INFO = "pyScss v" + VERSION + " (" + REVISION + ")" +AUTHOR = "German M. Bravo (Kronuz)" +AUTHOR_EMAIL = 'german.mb@gmail.com' +URL = 'http://github.com/Kronuz/pyScss' +DOWNLOAD_URL = 'http://github.com/Kronuz/pyScss/tarball/v' + VERSION +LICENSE = "MIT" +PROJECT = "pyScss" + +if __name__ == "__main__": + print('VERSION = ' + VERSION) + print('REVISION = ' + REVISION) + print('BUILD_INFO = ' + BUILD_INFO) + print('AUTHOR = ' + AUTHOR) + print('AUTHOR_EMAIL = ' + AUTHOR_EMAIL) + print('URL = ' + URL) + print('LICENSE = ' + LICENSE) + print('PROJECT = ' + PROJECT) diff --git a/libs/scss/selector.py b/libs/scss/selector.py new file mode 100644 index 0000000..ebaed3c --- /dev/null +++ b/libs/scss/selector.py @@ -0,0 +1,607 @@ +from __future__ import print_function + +import re + +# Super dumb little selector parser. + +# Yes, yes, this is a regex tokenizer. The actual meaning of the +# selector doesn't matter; the parts are just important for matching up +# during @extend. + +# Selectors have three levels: simple, combinator, comma-delimited. +# Each combinator can only appear once as a delimiter between simple +# selectors, so it can be thought of as a prefix. +# So this: +# a.b + c, d#e +# parses into two Selectors with these structures: +# [[' ', 'a', '.b'], ['+', 'c']] +# [[' ', 'd', '#e']] +# Note that the first simple selector has an implied descendant +# combinator -- i.e., it is a descendant of the root element. +# TODO `*html` is incorrectly parsed as a single selector +# TODO this oughta be touched up for css4 selectors +SELECTOR_TOKENIZER = re.compile(r''' + # Colons introduce pseudo-selectors, sometimes with parens + # TODO doesn't handle quoted ) + [:]+ [-\w]+ (?: [(] .+? [)] )? + + # These guys are combinators -- note that a single space counts too + | \s* [ +>~,] \s* + + # Square brackets are attribute tests + # TODO: this doesn't handle ] within a string + | [[] .+? []] + + # Dot and pound start class/id selectors. Percent starts a Sass + # extend-target faux selector. + | [.#%] [-\w]+ + + # Percentages are used for @keyframes + | [-.\d]+ [%] + + # Plain identifiers, or single asterisks, are element names + | [-\w]+ + | [*] + + # & is the sass replacement token + | [&] + + # And as a last-ditch effort, just eat up to whitespace + | (\S+) +''', re.VERBOSE | re.MULTILINE) + + +# Maps the first character of a token to a rough ordering. The default +# (element names) is zero. +TOKEN_TYPE_ORDER = { + '#': 2, + '.': 3, + '[': 3, + ':': 3, + '%': 4, +} +TOKEN_SORT_KEY = lambda token: TOKEN_TYPE_ORDER.get(token[0], 0) + + +def _is_combinator_subset_of(specific, general, is_first=True): + """Return whether `specific` matches a non-strict subset of what `general` + matches. + """ + if is_first and general == ' ': + # First selector always has a space to mean "descendent of root", which + # still holds if any other selector appears above it + return True + + if specific == general: + return True + + if specific == '>' and general == ' ': + return True + + if specific == '+' and general == '~': + return True + + return False + + +class SimpleSelector(object): + """A simple selector, by CSS 2.1 terminology: a combination of element + name, class selectors, id selectors, and other criteria that all apply to a + single element. + + Note that CSS 3 considers EACH of those parts to be a "simple selector", + and calls a group of them a "sequence of simple selectors". That's a + terrible class name, so we're going with 2.1 here. + + For lack of a better name, each of the individual parts is merely called a + "token". + """ + def __init__(self, combinator, tokens): + self.combinator = combinator + # TODO enforce that only one element name (including *) appears in a + # selector + # TODO remove duplicates + self.tokens = tuple(sorted(tokens, key=TOKEN_SORT_KEY)) + + def __repr__(self): + return "<%s: %r>" % (type(self).__name__, self.render()) + + def __hash__(self): + return hash((self.combinator, self.tokens)) + + def __eq__(self, other): + if not isinstance(other, SimpleSelector): + return NotImplemented + + return ( + self.combinator == other.combinator and + self.tokens == other.tokens) + + @property + def has_parent_reference(self): + return '&' in self.tokens or 'self' in self.tokens + + @property + def has_placeholder(self): + return any( + token[0] == '%' + for token in self.tokens) + + def is_superset_of(self, other, soft_combinator=False): + """Return True iff this selector matches the same elements as `other`, + and perhaps others. + + That is, ``.foo`` is a superset of ``.foo.bar``, because the latter is + more specific. + + Set `soft_combinator` true to ignore the specific case of this selector + having a descendent combinator and `other` having anything else. This + is for superset checking for ``@extend``, where a space combinator + really means "none". + """ + # Combinators must match, OR be compatible -- space is a superset of >, + # ~ is a superset of + + if soft_combinator and self.combinator == ' ': + combinator_superset = True + else: + combinator_superset = ( + self.combinator == other.combinator or + (self.combinator == ' ' and other.combinator == '>') or + (self.combinator == '~' and other.combinator == '+')) + + return ( + combinator_superset and + set(self.tokens) <= set(other.tokens)) + + def replace_parent(self, parent_simples): + """If ``&`` (or the legacy xCSS equivalent ``self``) appears in this + selector, replace it with the given iterable of parent selectors. + + Returns a tuple of simple selectors. + """ + assert parent_simples + + ancestors = parent_simples[:-1] + parent = parent_simples[-1] + + did_replace = False + new_tokens = [] + for token in self.tokens: + if not did_replace and token in ('&', 'self'): + did_replace = True + new_tokens.extend(parent.tokens) + else: + new_tokens.append(token) + + if not did_replace: + # This simple selector doesn't contain a parent reference so just + # stick it on the end + return parent_simples + (self,) + + # This simple selector was merged into the direct parent. + merged_self = type(self)(parent.combinator, new_tokens) + selector = ancestors + (merged_self,) + # Our combinator goes on the first ancestor, i.e., substituting "foo + # bar baz" into "+ &.quux" produces "+ foo bar baz.quux". This means a + # potential conflict with the first ancestor's combinator! + root = selector[0] + if not _is_combinator_subset_of(self.combinator, root.combinator): + raise ValueError( + "Can't sub parent {0!r} into {1!r}: " + "combinators {2!r} and {3!r} conflict!" + .format( + parent_simples, self, self.combinator, root.combinator)) + + root = type(self)(self.combinator, root.tokens) + selector = (root,) + selector[1:] + return tuple(selector) + + # TODO just use set ops for these, once the constructor removes dupes + def merge_with(self, other): + new_tokens = self.tokens + tuple(token for token in other.tokens if token not in set(self.tokens)) + return type(self)(self.combinator, new_tokens) + + def difference(self, other): + new_tokens = tuple(token for token in self.tokens if token not in set(other.tokens)) + return type(self)(self.combinator, new_tokens) + + def render(self): + # TODO fail if there are no tokens, or if one is a placeholder? + rendered = ''.join(self.tokens) + if self.combinator != ' ': + rendered = ' '.join((self.combinator, rendered)) + + return rendered + + +class Selector(object): + """A single CSS selector.""" + + def __init__(self, simples): + """Return a selector containing a sequence of `SimpleSelector`s. + + You probably want to use `parse_many` or `parse_one` instead. + """ + # TODO enforce uniqueness + self.simple_selectors = tuple(simples) + + @classmethod + def parse_many(cls, selector): + selector = selector.strip() + ret = [] + + pending = dict( + simples=[], + combinator=' ', + tokens=[], + ) + + def promote_simple(): + if pending['tokens']: + pending['simples'].append( + SimpleSelector(pending['combinator'], pending['tokens'])) + pending['combinator'] = ' ' + pending['tokens'] = [] + + def promote_selector(): + promote_simple() + if pending['simples']: + ret.append(cls(pending['simples'])) + pending['simples'] = [] + + pos = 0 + while pos < len(selector): + # TODO i don't think this deals with " + " correctly. anywhere. + # TODO this used to turn "1.5%" into empty string; why does error + # not work? + m = SELECTOR_TOKENIZER.match(selector, pos) + if not m: + # TODO prettify me + raise SyntaxError("Couldn't parse selector: %r" % (selector,)) + + token = m.group(0) + pos += len(token) + + # Kill any extraneous space, BUT make sure not to turn a lone space + # into an empty string + token = token.strip() or ' ' + + if token == ',': + # End current selector + # TODO what about "+ ,"? what do i even do with that + promote_selector() + elif token in ' +>~': + # End current simple selector + promote_simple() + pending['combinator'] = token + else: + # Add to pending simple selector + pending['tokens'].append(token) + + # Deal with any remaining pending bits + promote_selector() + + return ret + + @classmethod + def parse_one(cls, selector_string): + selectors = cls.parse_many(selector_string) + if len(selectors) != 1: + # TODO better error + raise ValueError + + return selectors[0] + + def __repr__(self): + return "<%s: %r>" % (type(self).__name__, self.render()) + + def __hash__(self): + return hash(self.simple_selectors) + + def __eq__(self, other): + if not isinstance(other, Selector): + return NotImplemented + + return self.simple_selectors == other.simple_selectors + + @property + def has_parent_reference(self): + return any( + simple.has_parent_reference + for simple in self.simple_selectors) + + @property + def has_placeholder(self): + return any( + simple.has_placeholder + for simple in self.simple_selectors) + + def with_parent(self, parent): + saw_parent_ref = False + + new_simples = [] + for simple in self.simple_selectors: + if simple.has_parent_reference: + new_simples.extend(simple.replace_parent(parent.simple_selectors)) + saw_parent_ref = True + else: + new_simples.append(simple) + + if not saw_parent_ref: + new_simples = parent.simple_selectors + tuple(new_simples) + + return type(self)(new_simples) + + def lookup_key(self): + """Build a key from the "important" parts of a selector: elements, + classes, ids. + """ + parts = set() + for node in self.simple_selectors: + for token in node.tokens: + if token[0] not in ':[': + parts.add(token) + + if not parts: + # Should always have at least ONE key; selectors with no elements, + # no classes, and no ids can be indexed as None to avoid a scan of + # every selector in the entire document + parts.add(None) + + return frozenset(parts) + + def is_superset_of(self, other): + assert isinstance(other, Selector) + + idx = 0 + for other_node in other.simple_selectors: + if idx >= len(self.simple_selectors): + return False + + while idx < len(self.simple_selectors): + node = self.simple_selectors[idx] + idx += 1 + + if node.is_superset_of(other_node): + break + + return True + + def substitute(self, target, replacement): + """Return a list of selectors obtained by replacing the `target` + selector with `replacement`. + + Herein lie the guts of the Sass @extend directive. + + In general, for a selector ``a X b Y c``, a target ``X Y``, and a + replacement ``q Z``, return the selectors ``a q X b Z c`` and ``q a X b + Z c``. Note in particular that no more than two selectors will be + returned, and the permutation of ancestors will never insert new simple + selectors "inside" the target selector. + """ + + # Find the target in the parent selector, and split it into + # before/after + p_before, p_extras, p_after = self.break_around(target.simple_selectors) + + # The replacement has no hinge; it only has the most specific simple + # selector (which is the part that replaces "self" in the parent) and + # whatever preceding simple selectors there may be + r_trail = replacement.simple_selectors[:-1] + r_extras = replacement.simple_selectors[-1] + + # TODO what if the prefix doesn't match? who wins? should we even get + # this far? + focal_nodes = (p_extras.merge_with(r_extras),) + + befores = _merge_selectors(p_before, r_trail) + + cls = type(self) + return [ + cls(before + focal_nodes + p_after) + for before in befores] + + def break_around(self, hinge): + """Given a simple selector node contained within this one (a "hinge"), + break it in half and return a parent selector, extra specifiers for the + hinge, and a child selector. + + That is, given a hinge X, break the selector A + X.y B into A, + .y, + and B. + """ + hinge_start = hinge[0] + for i, node in enumerate(self.simple_selectors): + # In this particular case, a ' ' combinator actually means "no" (or + # any) combinator, so it should be ignored + if hinge_start.is_superset_of(node, soft_combinator=True): + start_idx = i + break + else: + raise ValueError( + "Couldn't find hinge %r in compound selector %r" % + (hinge_start, self.simple_selectors)) + + for i, hinge_node in enumerate(hinge): + if i == 0: + # We just did this + continue + + self_node = self.simple_selectors[start_idx + i] + if hinge_node.is_superset_of(self_node): + continue + + # TODO this isn't true; consider finding `a b` in `a c a b` + raise ValueError( + "Couldn't find hinge %r in compound selector %r" % + (hinge_node, self.simple_selectors)) + + end_idx = start_idx + len(hinge) - 1 + + focal_node = self.simple_selectors[end_idx] + extras = focal_node.difference(hinge[-1]) + + return ( + self.simple_selectors[:start_idx], + extras, + self.simple_selectors[end_idx + 1:]) + + def render(self): + return ' '.join(simple.render() for simple in self.simple_selectors) + + +def _merge_selectors(left, right): + """Given two selector chains (lists of simple selectors), return a list of + selector chains representing elements matched by both of them. + + This operation is not exact, and involves some degree of fudging -- the + wackier and more divergent the input, the more fudging. It's meant to be + what a human might expect rather than a precise covering of all possible + cases. Most notably, when the two input chains have absolutely nothing in + common, the output is merely ``left + right`` and ``right + left`` rather + than all possible interleavings. + """ + + if not left or not right: + # At least one is empty, so there are no conflicts; just return + # whichever isn't empty. Remember to return a LIST, though + return [left or right] + + lcs = longest_common_subsequence(left, right, _merge_simple_selectors) + + ret = [()] # start with a dummy empty chain or weaving won't work + + left_last = 0 + right_last = 0 + for left_next, right_next, merged in lcs: + ret = _weave_conflicting_selectors( + ret, + left[left_last:left_next], + right[right_last:right_next], + (merged,)) + + left_last = left_next + 1 + right_last = right_next + 1 + + ret = _weave_conflicting_selectors( + ret, + left[left_last:], + right[right_last:]) + + return ret + + +def _weave_conflicting_selectors(prefixes, a, b, suffix=()): + """Part of the selector merge algorithm above. Not useful on its own. Pay + no attention to the man behind the curtain. + """ + # OK, what this actually does: given a list of selector chains, two + # "conflicting" selector chains, and an optional suffix, return a new list + # of chains like this: + # prefix[0] + a + b + suffix, + # prefix[0] + b + a + suffix, + # prefix[1] + a + b + suffix, + # ... + # In other words, this just appends a new chain to each of a list of given + # chains, except that the new chain might be the superposition of two + # other incompatible chains. + both = a and b + for prefix in prefixes: + yield prefix + a + b + suffix + if both: + # Only use both orderings if there's an actual conflict! + yield prefix + b + a + suffix + + +def _merge_simple_selectors(a, b): + """Merge two simple selectors, for the purposes of the LCS algorithm below. + + In practice this returns the more specific selector if one is a subset of + the other, else it returns None. + """ + # TODO what about combinators + if a.is_superset_of(b): + return b + elif b.is_superset_of(a): + return a + else: + return None + + +def longest_common_subsequence(a, b, mergefunc=None): + """Find the longest common subsequence between two iterables. + + The longest common subsequence is the core of any diff algorithm: it's the + longest sequence of elements that appears in both parent sequences in the + same order, but NOT necessarily consecutively. + + Original algorithm borrowed from Wikipedia: + http://en.wikipedia.org/wiki/Longest_common_subsequence_problem#Code_for_the_dynamic_programming_solution + + This function is used only to implement @extend, largely because that's + what the Ruby implementation does. Thus it's been extended slightly from + the simple diff-friendly algorithm given above. + + What @extend wants to know is whether two simple selectors are compatible, + not just equal. To that end, you must pass in a "merge" function to + compare a pair of elements manually. It should return `None` if they are + incompatible, and a MERGED element if they are compatible -- in the case of + selectors, this is whichever one is more specific. + + Because of this fuzzier notion of equality, the return value is a list of + ``(a_index, b_index, value)`` tuples rather than items alone. + """ + if mergefunc is None: + # Stupid default, just in case + def mergefunc(a, b): + if a == b: + return a + return None + + # Precalculate equality, since it can be a tad expensive and every pair is + # compared at least once + eq = {} + for ai, aval in enumerate(a): + for bi, bval in enumerate(b): + eq[ai, bi] = mergefunc(aval, bval) + + # Build the "length" matrix, which provides the length of the LCS for + # arbitrary-length prefixes. -1 exists only to support the base case + prefix_lcs_length = {} + for ai in range(-1, len(a)): + for bi in range(-1, len(b)): + if ai == -1 or bi == -1: + l = 0 + elif eq[ai, bi]: + l = prefix_lcs_length[ai - 1, bi - 1] + 1 + else: + l = max( + prefix_lcs_length[ai, bi - 1], + prefix_lcs_length[ai - 1, bi]) + + prefix_lcs_length[ai, bi] = l + + # The interesting part. The key insight is that the bottom-right value in + # the length matrix must be the length of the LCS because of how the matrix + # is defined, so all that's left to do is backtrack from the ends of both + # sequences in whatever way keeps the LCS as long as possible, and keep + # track of the equal pairs of elements we see along the way. + # Wikipedia does this with recursion, but the algorithm is trivial to + # rewrite as a loop, as below. + ai = len(a) - 1 + bi = len(b) - 1 + + ret = [] + while ai >= 0 and bi >= 0: + merged = eq[ai, bi] + if merged is not None: + ret.append((ai, bi, merged)) + ai -= 1 + bi -= 1 + elif prefix_lcs_length[ai, bi - 1] > prefix_lcs_length[ai - 1, bi]: + bi -= 1 + else: + ai -= 1 + + # ret has the latest items first, which is backwards + ret.reverse() + return ret diff --git a/libs/scss/setup.py b/libs/scss/setup.py new file mode 100644 index 0000000..7112a18 --- /dev/null +++ b/libs/scss/setup.py @@ -0,0 +1,14 @@ +from distutils.core import setup, Extension + +setup(name='pyScss', + version='1.1.1', + description='pyScss', + ext_modules=[ + Extension( + '_scss', + sources=['src/_scss.c', 'src/block_locator.c', 'src/scanner.c'], + libraries=['pcre'], + optional=True + ) + ] +) diff --git a/libs/scss/src/_speedups.c b/libs/scss/src/_speedups.c new file mode 100644 index 0000000..b4b990c --- /dev/null +++ b/libs/scss/src/_speedups.c @@ -0,0 +1,554 @@ +/* +* pyScss, a Scss compiler for Python +* SCSS blocks scanner. +* +* German M. Bravo (Kronuz) +* https://github.com/Kronuz/pyScss +* +* MIT license (http://www.opensource.org/licenses/mit-license.php) +* Copyright (c) 2011 German M. Bravo (Kronuz), All rights reserved. +*/ +#include +#include "block_locator.h" +#include "scanner.h" + +/* BlockLocator */ +staticforward PyTypeObject scss_BlockLocatorType; + +typedef struct { + PyObject_HEAD + BlockLocator *locator; +} scss_BlockLocator; + +static int +scss_BlockLocator_init(scss_BlockLocator *self, PyObject *args, PyObject *kwds) +{ + char *codestr; + int codestr_sz; + + self->locator = NULL; + + if (!PyArg_ParseTuple(args, "s#", &codestr, &codestr_sz)) { + return -1; + } + + self->locator = BlockLocator_new(codestr, codestr_sz); + + #ifdef DEBUG + PySys_WriteStderr("Scss BlockLocator object initialized! (%lu bytes)\n", sizeof(scss_BlockLocator)); + #endif + + return 0; +} + +static void +scss_BlockLocator_dealloc(scss_BlockLocator *self) +{ + if (self->locator != NULL) BlockLocator_del(self->locator); + + self->ob_type->tp_free((PyObject*)self); + + #ifdef DEBUG + PySys_WriteStderr("Scss BlockLocator object destroyed!\n"); + #endif +} + +scss_BlockLocator* +scss_BlockLocator_iter(scss_BlockLocator *self) +{ + Py_INCREF(self); + return self; +} + +PyObject* +scss_BlockLocator_iternext(scss_BlockLocator *self) +{ + Block *block; + + if (self->locator != NULL) { + block = BlockLocator_iternext(self->locator); + + if (block->error > 0) { + return Py_BuildValue( + "is#s#", + block->lineno, + block->selprop, + block->selprop_sz, + block->codestr, + block->codestr_sz + ); + } + + if (block->error > 0) { + PyErr_SetString(PyExc_Exception, self->locator->exc); + return NULL; + } + } + + /* Raising of standard StopIteration exception with empty value. */ + PyErr_SetNone(PyExc_StopIteration); + return NULL; +} + +/* Type definition */ + +static PyTypeObject scss_BlockLocatorType = { + PyObject_HEAD_INIT(NULL) + 0, /* ob_size */ + "scss._BlockLocator", /* tp_name */ + sizeof(scss_BlockLocator), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)scss_BlockLocator_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_ITER, /* tp_flags */ + "Internal BlockLocator iterator object.", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + (getiterfunc)scss_BlockLocator_iter, /* tp_iter: __iter__() method */ + (iternextfunc)scss_BlockLocator_iternext, /* tp_iternext: next() method */ + 0, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc)scss_BlockLocator_init, /* tp_init */ +}; + + +/* Scanner */ +static PyObject *PyExc_scss_NoMoreTokens; + +staticforward PyTypeObject scss_ScannerType; + +typedef struct { + PyObject_HEAD + Scanner *scanner; +} scss_Scanner; + + +static PyObject * +scss_Scanner_rewind(scss_Scanner *self, PyObject *args) +{ + int token_num; + if (self->scanner != NULL) { + if (PyArg_ParseTuple(args, "i", &token_num)) { + Scanner_rewind(self->scanner, token_num); + } + } + Py_INCREF(Py_None); + return (PyObject *)Py_None; +} + + +static PyObject * +scss_Scanner_token(scss_Scanner *self, PyObject *args) +{ + PyObject *iter; + PyObject *item; + long size; + + Token *p_token; + + int token_num; + PyObject *restrictions = NULL; + Pattern *_restrictions = NULL; + int restrictions_sz = 0; + if (self->scanner != NULL) { + if (PyArg_ParseTuple(args, "i|O", &token_num, &restrictions)) { + if (restrictions != NULL) { + size = PySequence_Size(restrictions); + if (size != -1) { + _restrictions = PyMem_New(Pattern, size); + iter = PyObject_GetIter(restrictions); + while ((item = PyIter_Next(iter))) { + if (PyString_Check(item)) { + _restrictions[restrictions_sz].tok = PyString_AsString(item); + _restrictions[restrictions_sz].expr = NULL; + restrictions_sz++; + } + Py_DECREF(item); + } + Py_DECREF(iter); + } + } + p_token = Scanner_token(self->scanner, token_num, _restrictions, restrictions_sz); + + if (_restrictions != NULL) PyMem_Del(_restrictions); + + if (p_token == (Token *)SCANNER_EXC_BAD_TOKEN) { + PyErr_SetString(PyExc_SyntaxError, self->scanner->exc); + return NULL; + } + if (p_token == (Token *)SCANNER_EXC_RESTRICTED) { + PyErr_SetString(PyExc_SyntaxError, self->scanner->exc); + return NULL; + } + if (p_token == (Token *)SCANNER_EXC_UNIMPLEMENTED) { + PyErr_SetString(PyExc_NotImplementedError, self->scanner->exc); + return NULL; + } + if (p_token == (Token *)SCANNER_EXC_NO_MORE_TOKENS) { + PyErr_SetNone(PyExc_scss_NoMoreTokens); + return NULL; + } + if (p_token < 0) { + PyErr_SetNone(PyExc_Exception); + return NULL; + } + return Py_BuildValue( + "iiss#", + p_token->string - self->scanner->input, + p_token->string - self->scanner->input + p_token->string_sz, + p_token->regex->tok, + p_token->string, + p_token->string_sz + ); + } + } + Py_INCREF(Py_None); + return (PyObject *)Py_None; +} + +static PyObject * +scss_Scanner_reset(scss_Scanner *self, PyObject *args, PyObject *kwds) +{ + char *input = NULL; + int input_sz = 0; + + if (self->scanner != NULL) { + if (PyArg_ParseTuple(args, "|z#", &input, &input_sz)) { + Scanner_reset(self->scanner, input, input_sz); + } + } + + Py_INCREF(Py_None); + return (PyObject *)Py_None; +} + +static PyObject * +scss_Scanner_setup_patterns(PyObject *self, PyObject *patterns) +{ + PyObject *item, *item0, *item1; + int i, is_tuple, _is_tuple; + long size; + + Pattern *_patterns = NULL; + int patterns_sz = 0; + if (!Scanner_initialized()) { + is_tuple = PyTuple_Check(patterns); + if (is_tuple || PyList_Check(patterns)) { + size = is_tuple ? PyTuple_Size(patterns) : PyList_Size(patterns); + _patterns = PyMem_New(Pattern, size); + for (i = 0; i < size; ++i) { + item = is_tuple ? PyTuple_GetItem(patterns, i) : PyList_GetItem(patterns, i); + _is_tuple = PyTuple_Check(item); + if (_is_tuple || PyList_Check(item)) { + item0 = _is_tuple ? PyTuple_GetItem(item, 0) : PyList_GetItem(item, 0); + item1 = _is_tuple ? PyTuple_GetItem(item, 1) : PyList_GetItem(item, 1); + if (PyString_Check(item0) && PyString_Check(item1)) { + _patterns[patterns_sz].tok = PyString_AsString(item0); + _patterns[patterns_sz].expr = PyString_AsString(item1); + patterns_sz++; + } + } + } + } + Scanner_initialize(_patterns, patterns_sz); + if (_patterns != NULL) PyMem_Del(_patterns); + } + Py_INCREF(Py_None); + return (PyObject *)Py_None; +} + +static int +scss_Scanner_init(scss_Scanner *self, PyObject *args, PyObject *kwds) +{ + PyObject *item, *item0, *item1; + int i, is_tuple, _is_tuple; + long size; + + PyObject *patterns, *ignore; + Pattern *_patterns = NULL; + int patterns_sz = 0; + Pattern *_ignore = NULL; + int ignore_sz = 0; + char *input = NULL; + int input_sz = 0; + + self->scanner = NULL; + + if (!PyArg_ParseTuple(args, "OO|z#", &patterns, &ignore, &input, &input_sz)) { + return -1; + } + + if (!Scanner_initialized()) { + is_tuple = PyTuple_Check(patterns); + if (is_tuple || PyList_Check(patterns)) { + size = is_tuple ? PyTuple_Size(patterns) : PyList_Size(patterns); + _patterns = PyMem_New(Pattern, size); + for (i = 0; i < size; ++i) { + item = is_tuple ? PyTuple_GetItem(patterns, i) : PyList_GetItem(patterns, i); + _is_tuple = PyTuple_Check(item); + if (_is_tuple || PyList_Check(item)) { + item0 = _is_tuple ? PyTuple_GetItem(item, 0) : PyList_GetItem(item, 0); + item1 = _is_tuple ? PyTuple_GetItem(item, 1) : PyList_GetItem(item, 1); + if (PyString_Check(item0) && PyString_Check(item1)) { + _patterns[patterns_sz].tok = PyString_AsString(item0); + _patterns[patterns_sz].expr = PyString_AsString(item1); + patterns_sz++; + } + } + } + } + Scanner_initialize(_patterns, patterns_sz); + } + + is_tuple = PyTuple_Check(ignore); + if (is_tuple || PyList_Check(ignore)) { + size = is_tuple ? PyTuple_Size(ignore) : PyList_Size(ignore); + _ignore = PyMem_New(Pattern, size); + for (i = 0; i < size; ++i) { + item = is_tuple ? PyTuple_GetItem(ignore, i) : PyList_GetItem(ignore, i); + if (PyString_Check(item)) { + _ignore[ignore_sz].tok = PyString_AsString(item); + _ignore[ignore_sz].expr = NULL; + ignore_sz++; + } + } + } + + self->scanner = Scanner_new(_patterns, patterns_sz, _ignore, ignore_sz, input, input_sz); + + if (_patterns != NULL) PyMem_Del(_patterns); + if (_ignore != NULL) PyMem_Del(_ignore); + + #ifdef DEBUG + PySys_WriteStderr("Scss Scanner object initialized! (%lu bytes)\n", sizeof(scss_Scanner)); + #endif + + return 0; +} + +static PyObject * +scss_Scanner_repr(scss_Scanner *self) +{ + /* Print the last 10 tokens that have been scanned in */ + PyObject *repr, *tmp; + Token *p_token; + int i, start, pos; + + if (self->scanner != NULL && self->scanner->tokens_sz) { + start = self->scanner->tokens_sz - 10; + repr = PyString_FromString(""); + for (i = (start < 0) ? 0 : start; i < self->scanner->tokens_sz; i++) { + p_token = &self->scanner->tokens[i]; + PyString_ConcatAndDel(&repr, PyString_FromString("\n")); + pos = (int)(p_token->string - self->scanner->input); + PyString_ConcatAndDel(&repr, PyString_FromFormat(" (@%d) %s = ", + pos, p_token->regex->tok)); + tmp = PyString_FromStringAndSize(p_token->string, p_token->string_sz); + PyString_ConcatAndDel(&repr, PyObject_Repr(tmp)); + Py_XDECREF(tmp); + } + } else { + repr = PyString_FromString("None"); + } + + return (PyObject *)repr; + +/* + PyObject *repr, *tmp, *tmp2; + Token *p_token; + char *tok; + int i, start, first = 1, cur, max=0, pos; + + if (self->scanner != NULL && self->scanner->tokens_sz) { + start = self->scanner->tokens_sz - 10; + repr = PyString_FromString(""); + for (i = (start < 0) ? 0 : start; i < self->scanner->tokens_sz; i++) { + p_token = self->scanner->tokens[i]; + PyString_ConcatAndDel(&repr, PyString_FromString("\n")); + pos = (int)(p_token->string - self->scanner->input); + PyString_ConcatAndDel(&repr, PyString_FromFormat(" (@%d) %s = ", + pos, p_token->regex->tok)); + tmp = PyString_FromString(p_token->string); + PyString_ConcatAndDel(&repr, PyObject_Repr(tmp)); + Py_XDECREF(tmp); + } + + start = self->scanner->tokens_sz - 10; + for (i = (start < 0) ? 0 : start; i < self->scanner->tokens_sz; i++) { + p_token = self->scanner->tokens[i]; + cur = strlen(p_token->regex->tok) * 2; + if (cur > max) max = cur; + } + tok = PyMem_New(char, max + 4); + repr = PyString_FromString(""); + for (i = (start < 0) ? 0 : start; i < self->scanner->tokens_sz; i++) { + p_token = self->scanner->tokens[i]; + if (!first) PyString_ConcatAndDel(&repr, PyString_FromString("\n")); + + pos = (int)(p_token->string - self->scanner->input); + if (pos < 10) PyString_ConcatAndDel(&repr, PyString_FromString(" ")); + if (pos < 100) PyString_ConcatAndDel(&repr, PyString_FromString(" ")); + if (pos < 1000) PyString_ConcatAndDel(&repr, PyString_FromString(" ")); + PyString_ConcatAndDel(&repr, PyString_FromFormat("(@%d) ", + pos)); + + tmp = PyString_FromString(p_token->regex->tok); + tmp2 = PyObject_Repr(tmp); + memset(tok, ' ', max + 4); + tok[max + 3 - PyString_Size(tmp2)] = '\0'; + PyString_ConcatAndDel(&repr, PyString_FromString(tok)); + PyString_ConcatAndDel(&repr, tmp2); + Py_XDECREF(tmp); + + PyString_ConcatAndDel(&repr, PyString_FromString(" = ")); + tmp = PyString_FromString(p_token->string); + PyString_ConcatAndDel(&repr, PyObject_Repr(tmp)); + Py_XDECREF(tmp); + + first = 0; + } + PyMem_Del(tok); + } else { + repr = PyString_FromString("None"); + } + + return (PyObject *)repr; +*/ +} + +static void +scss_Scanner_dealloc(scss_Scanner *self) +{ + if (self->scanner != NULL) Scanner_del(self->scanner); + + self->ob_type->tp_free((PyObject*)self); + + #ifdef DEBUG + PySys_WriteStderr("Scss Scanner object destroyed!\n"); + #endif +} + +static PyMethodDef scss_Scanner_methods[] = { + {"reset", (PyCFunction)scss_Scanner_reset, METH_VARARGS, "Scan the next token"}, + {"token", (PyCFunction)scss_Scanner_token, METH_VARARGS, "Get the nth token"}, + {"rewind", (PyCFunction)scss_Scanner_rewind, METH_VARARGS, "Rewind scanner"}, + {"setup_patterns", (PyCFunction)scss_Scanner_setup_patterns, METH_O | METH_STATIC, "Initialize patterns."}, + {NULL, NULL, 0, NULL} /* Sentinel */ +}; + +static PyTypeObject scss_ScannerType = { + PyObject_HEAD_INIT(NULL) + 0, /* ob_size */ + "scss.Scanner", /* tp_name */ + sizeof(scss_Scanner), /* tp_basicsize */ + 0, /* tp_itemsize */ + (destructor)scss_Scanner_dealloc, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + (reprfunc)scss_Scanner_repr, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + 0, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /* tp_flags */ + "Scanner object.", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter: __iter__() method */ + 0, /* tp_iternext: next() method */ + scss_Scanner_methods, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + (initproc)scss_Scanner_init, /* tp_init */ +}; + + +/* Python constructor */ + +static PyObject * +scss_locate_blocks(PyObject *self, PyObject *args) +{ + scss_BlockLocator *result = NULL; + + result = PyObject_New(scss_BlockLocator, &scss_BlockLocatorType); + if (result) { + scss_BlockLocator_init(result, args, NULL); + } + + return (PyObject *)result; +} + + +/* Module functions */ + +static PyMethodDef scss_methods[] = { + {"locate_blocks", (PyCFunction)scss_locate_blocks, METH_VARARGS, "Locate Scss blocks."}, + {NULL, NULL, 0, NULL} /* Sentinel */ +}; + + +/* Module init function */ + +PyMODINIT_FUNC +init_speedups(void) +{ + PyObject* m; + + scss_BlockLocatorType.tp_new = PyType_GenericNew; + if (PyType_Ready(&scss_BlockLocatorType) < 0) + return; + + scss_ScannerType.tp_new = PyType_GenericNew; + if (PyType_Ready(&scss_ScannerType) < 0) + return; + + BlockLocator_initialize(); + Scanner_initialize(NULL, 0); + + m = Py_InitModule("_speedups", scss_methods); + + Py_INCREF(&scss_BlockLocatorType); + PyModule_AddObject(m, "_BlockLocator", (PyObject *)&scss_BlockLocatorType); + + Py_INCREF(&scss_ScannerType); + PyModule_AddObject(m, "Scanner", (PyObject *)&scss_ScannerType); + + PyExc_scss_NoMoreTokens = PyErr_NewException("_speedups.NoMoreTokens", NULL, NULL); + Py_INCREF(PyExc_scss_NoMoreTokens); + PyModule_AddObject(m, "NoMoreTokens", (PyObject *)PyExc_scss_NoMoreTokens); +} diff --git a/libs/scss/src/block_locator.c b/libs/scss/src/block_locator.c new file mode 100644 index 0000000..f35d6d3 --- /dev/null +++ b/libs/scss/src/block_locator.c @@ -0,0 +1,547 @@ +/* +* pyScss, a Scss compiler for Python +* SCSS blocks scanner. +* +* German M. Bravo (Kronuz) +* https://github.com/Kronuz/pyScss +* +* MIT license (http://www.opensource.org/licenses/mit-license.php) +* Copyright (c) 2011 German M. Bravo (Kronuz), All rights reserved. +*/ +#include + +#include +#include +#include +#include "block_locator.h" + +int _strip(char *begin, char *end, int *lineno) { + // " 1\0 some, \n 2\0 aca " + int _cnt, + cnt = 0, + pass = 1, + addnl = 0; + char c, + *line = NULL, + *first = begin, + *last = begin, + *write = lineno ? begin : NULL; + while (begin < end) { + c = *begin; + if (c == '\0') { + if (lineno && line == NULL) { + line = first - 1; + do { + c = *++line; + } while (c == ' ' || c == '\t' || c == '\n' || c == ';'); + if (c != '\0') { + sscanf(line, "%d", lineno); + } + } + first = last = begin + 1; + pass = 1; + } else if (c == '\n') { + _cnt = (int)(last - first); + if (_cnt > 0) { + cnt += _cnt + addnl; + if (write != NULL) { + if (addnl) { + *write++ = '\n'; + } + while (first < last) { + *write++ = *first++; + } + addnl = 1; + } + } + first = last = begin + 1; + pass = 1; + } else if (c == ' ' || c == '\t') { + if (pass) { + first = last = begin + 1; + } + } else { + last = begin + 1; + pass = 0; + } + begin++; + } + _cnt = (int)(last - first); + if (_cnt > 0) { + cnt += _cnt + addnl; + if (write != NULL) { + if (addnl) { + *write++ = '\n'; + } + while (first < last) { + *write++ = *first++; + } + } + } + return cnt; +} + + +/* BlockLocator */ + +typedef void _BlockLocator_Callback(BlockLocator*); + +static void +_BlockLocator_start_string(BlockLocator *self) { + #ifdef DEBUG + fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); + #endif + + // A string starts + self->instr = *(self->codestr_ptr); +} + +static void +_BlockLocator_end_string(BlockLocator *self) { + #ifdef DEBUG + fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); + #endif + + // A string ends (FIXME: needs to accept escaped characters) + self->instr = 0; +} + +static void +_BlockLocator_start_parenthesis(BlockLocator *self) { + #ifdef DEBUG + fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); + #endif + + // parenthesis begins: + self->par++; + self->thin = NULL; + self->safe = self->codestr_ptr + 1; +} + +static void +_BlockLocator_end_parenthesis(BlockLocator *self) { + #ifdef DEBUG + fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); + #endif + + self->par--; +} + +static void +_BlockLocator_flush_properties(BlockLocator *self) { + int len, lineno = -1; + + #ifdef DEBUG + fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); + #endif + + // Flush properties + if (self->lose <= self->init) { + len = _strip(self->lose, self->init, &lineno); + if (len) { + if (lineno != -1) { + self->lineno = lineno; + } + + self->block.selprop = self->lose; + self->block.selprop_sz = len; + self->block.codestr = NULL; + self->block.codestr_sz = 0; + self->block.lineno = self->lineno; + self->block.error = 1; + } + self->lose = self->init; + } +} + +static void +_BlockLocator_start_block1(BlockLocator *self) { + #ifdef DEBUG + fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); + #endif + + // Start block: + if (self->codestr_ptr > self->codestr && *(self->codestr_ptr - 1) == '#') { + self->skip = 1; + } else { + self->start = self->codestr_ptr; + if (self->thin != NULL && _strip(self->thin, self->codestr_ptr, NULL)) { + self->init = self->thin; + } + _BlockLocator_flush_properties(self); + self->thin = NULL; + } + self->depth++; +} + +static void +_BlockLocator_start_block(BlockLocator *self) { + #ifdef DEBUG + fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); + #endif + + // Start block: + self->depth++; +} + +static void +_BlockLocator_end_block1(BlockLocator *self) { + int len, lineno = -1; + + #ifdef DEBUG + fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); + #endif + + // Block ends: + self->depth--; + if (!self->skip) { + self->end = self->codestr_ptr; + len = _strip(self->init, self->start, &lineno); + if (lineno != -1) { + self->lineno = lineno; + } + + self->block.selprop = self->init; + self->block.selprop_sz = len; + self->block.codestr = (self->start + 1); + self->block.codestr_sz = (int)(self->end - (self->start + 1)); + self->block.lineno = self->lineno; + self->block.error = 1; + + self->init = self->safe = self->lose = self->end + 1; + self->thin = NULL; + } + self->skip = 0; +} + +static void +_BlockLocator_end_block(BlockLocator *self) { + #ifdef DEBUG + fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); + #endif + + // Block ends: + self->depth--; +} + +static void +_BlockLocator_end_property(BlockLocator *self) { + int len, lineno = -1; + + #ifdef DEBUG + fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); + #endif + + // End of property (or block): + self->init = self->codestr_ptr; + if (self->lose <= self->init) { + len = _strip(self->lose, self->init, &lineno); + if (len) { + if (lineno != -1) { + self->lineno = lineno; + } + + self->block.selprop = self->lose; + self->block.selprop_sz = len; + self->block.codestr = NULL; + self->block.codestr_sz = 0; + self->block.lineno = self->lineno; + self->block.error = 1; + } + self->init = self->safe = self->lose = self->codestr_ptr + 1; + } + self->thin = NULL; +} + +static void +_BlockLocator_mark_safe(BlockLocator *self) { + #ifdef DEBUG + fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); + #endif + + // We are on a safe zone + if (self->thin != NULL && _strip(self->thin, self->codestr_ptr, NULL)) { + self->init = self->thin; + } + self->thin = NULL; + self->safe = self->codestr_ptr + 1; +} + +static void +_BlockLocator_mark_thin(BlockLocator *self) { + #ifdef DEBUG + fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); + #endif + + // Step on thin ice, if it breaks, it breaks here + if (self->thin != NULL && _strip(self->thin, self->codestr_ptr, NULL)) { + self->init = self->thin; + self->thin = self->codestr_ptr + 1; + } else if (self->thin == NULL && _strip(self->safe, self->codestr_ptr, NULL)) { + self->thin = self->codestr_ptr + 1; + } +} + +int function_map_initialized = 0; +_BlockLocator_Callback* scss_function_map[256 * 256 * 2 * 3]; // (c, instr, par, depth) + +static void +init_function_map(void) { + int i; + + #ifdef DEBUG + fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); + #endif + + if (function_map_initialized) { + return; + } + function_map_initialized = 1; + + for (i = 0; i < 256 * 256 * 2 * 3; i++) { + scss_function_map[i] = NULL; + } + scss_function_map[(int)'\"' + 256*0 + 256*256*0 + 256*256*2*0] = _BlockLocator_start_string; + scss_function_map[(int)'\'' + 256*0 + 256*256*0 + 256*256*2*0] = _BlockLocator_start_string; + scss_function_map[(int)'\"' + 256*0 + 256*256*1 + 256*256*2*0] = _BlockLocator_start_string; + scss_function_map[(int)'\'' + 256*0 + 256*256*1 + 256*256*2*0] = _BlockLocator_start_string; + scss_function_map[(int)'\"' + 256*0 + 256*256*0 + 256*256*2*1] = _BlockLocator_start_string; + scss_function_map[(int)'\'' + 256*0 + 256*256*0 + 256*256*2*1] = _BlockLocator_start_string; + scss_function_map[(int)'\"' + 256*0 + 256*256*1 + 256*256*2*1] = _BlockLocator_start_string; + scss_function_map[(int)'\'' + 256*0 + 256*256*1 + 256*256*2*1] = _BlockLocator_start_string; + scss_function_map[(int)'\"' + 256*0 + 256*256*0 + 256*256*2*2] = _BlockLocator_start_string; + scss_function_map[(int)'\'' + 256*0 + 256*256*0 + 256*256*2*2] = _BlockLocator_start_string; + scss_function_map[(int)'\"' + 256*0 + 256*256*1 + 256*256*2*2] = _BlockLocator_start_string; + scss_function_map[(int)'\'' + 256*0 + 256*256*1 + 256*256*2*2] = _BlockLocator_start_string; + + scss_function_map[(int)'\"' + 256*(int)'\"' + 256*256*0 + 256*256*2*0] = _BlockLocator_end_string; + scss_function_map[(int)'\'' + 256*(int)'\'' + 256*256*0 + 256*256*2*0] = _BlockLocator_end_string; + scss_function_map[(int)'\"' + 256*(int)'\"' + 256*256*1 + 256*256*2*0] = _BlockLocator_end_string; + scss_function_map[(int)'\'' + 256*(int)'\'' + 256*256*1 + 256*256*2*0] = _BlockLocator_end_string; + scss_function_map[(int)'\"' + 256*(int)'\"' + 256*256*0 + 256*256*2*1] = _BlockLocator_end_string; + scss_function_map[(int)'\'' + 256*(int)'\'' + 256*256*0 + 256*256*2*1] = _BlockLocator_end_string; + scss_function_map[(int)'\"' + 256*(int)'\"' + 256*256*1 + 256*256*2*1] = _BlockLocator_end_string; + scss_function_map[(int)'\'' + 256*(int)'\'' + 256*256*1 + 256*256*2*1] = _BlockLocator_end_string; + scss_function_map[(int)'\"' + 256*(int)'\"' + 256*256*0 + 256*256*2*2] = _BlockLocator_end_string; + scss_function_map[(int)'\'' + 256*(int)'\'' + 256*256*0 + 256*256*2*2] = _BlockLocator_end_string; + scss_function_map[(int)'\"' + 256*(int)'\"' + 256*256*1 + 256*256*2*2] = _BlockLocator_end_string; + scss_function_map[(int)'\'' + 256*(int)'\'' + 256*256*1 + 256*256*2*2] = _BlockLocator_end_string; + + scss_function_map[(int)'(' + 256*0 + 256*256*0 + 256*256*2*0] = _BlockLocator_start_parenthesis; + scss_function_map[(int)'(' + 256*0 + 256*256*1 + 256*256*2*0] = _BlockLocator_start_parenthesis; + scss_function_map[(int)'(' + 256*0 + 256*256*0 + 256*256*2*1] = _BlockLocator_start_parenthesis; + scss_function_map[(int)'(' + 256*0 + 256*256*1 + 256*256*2*1] = _BlockLocator_start_parenthesis; + scss_function_map[(int)'(' + 256*0 + 256*256*0 + 256*256*2*2] = _BlockLocator_start_parenthesis; + scss_function_map[(int)'(' + 256*0 + 256*256*1 + 256*256*2*2] = _BlockLocator_start_parenthesis; + + scss_function_map[(int)')' + 256*0 + 256*256*1 + 256*256*2*0] = _BlockLocator_end_parenthesis; + scss_function_map[(int)')' + 256*0 + 256*256*1 + 256*256*2*1] = _BlockLocator_end_parenthesis; + scss_function_map[(int)')' + 256*0 + 256*256*1 + 256*256*2*2] = _BlockLocator_end_parenthesis; + + scss_function_map[(int)'{' + 256*0 + 256*256*0 + 256*256*2*0] = _BlockLocator_start_block1; + scss_function_map[(int)'{' + 256*0 + 256*256*0 + 256*256*2*1] = _BlockLocator_start_block; + scss_function_map[(int)'{' + 256*0 + 256*256*0 + 256*256*2*2] = _BlockLocator_start_block; + + scss_function_map[(int)'}' + 256*0 + 256*256*0 + 256*256*2*1] = _BlockLocator_end_block1; + scss_function_map[(int)'}' + 256*0 + 256*256*0 + 256*256*2*2] = _BlockLocator_end_block; + + scss_function_map[(int)';' + 256*0 + 256*256*0 + 256*256*2*0] = _BlockLocator_end_property; + + scss_function_map[(int)',' + 256*0 + 256*256*0 + 256*256*2*0] = _BlockLocator_mark_safe; + + scss_function_map[(int)'\n' + 256*0 + 256*256*0 + 256*256*2*0] = _BlockLocator_mark_thin; + + scss_function_map[0 + 256*0 + 256*256*0 + 256*256*2*0] = _BlockLocator_flush_properties; + scss_function_map[0 + 256*0 + 256*256*0 + 256*256*2*1] = _BlockLocator_flush_properties; + scss_function_map[0 + 256*0 + 256*256*0 + 256*256*2*2] = _BlockLocator_flush_properties; + + #ifdef DEBUG + fprintf(stderr, "\tScss function maps initialized!\n"); + #endif +} + + +/* BlockLocator public interface */ + +void +BlockLocator_initialize(void) +{ + #ifdef DEBUG + fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); + #endif + + init_function_map(); +} + +void +BlockLocator_finalize(void) +{ + #ifdef DEBUG + fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); + #endif +} + +BlockLocator * +BlockLocator_new(char *codestr, int codestr_sz) +{ + BlockLocator *self; + + #ifdef DEBUG + fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); + #endif + + self = PyMem_New(BlockLocator, 1); + if (self) { + memset(self, 0, sizeof(BlockLocator)); + self->_codestr = PyMem_New(char, codestr_sz); + memcpy(self->_codestr, codestr, codestr_sz); + self->codestr_sz = codestr_sz; + self->codestr = PyMem_New(char, self->codestr_sz); + memcpy(self->codestr, self->_codestr, self->codestr_sz); + self->codestr_ptr = self->codestr; + self->lineno = 0; + self->par = 0; + self->instr = 0; + self->depth = 0; + self->skip = 0; + self->thin = self->codestr; + self->init = self->codestr; + self->safe = self->codestr; + self->lose = self->codestr; + self->start = NULL; + self->end = NULL; + #ifdef DEBUG + fprintf(stderr, "\tScss BlockLocator object created (%d bytes)!\n", codestr_sz); + #endif + } + return self; +} + +void +BlockLocator_del(BlockLocator *self) +{ + #ifdef DEBUG + fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); + #endif + + free(self->codestr); + free(self->_codestr); + free(self); +} + +void +BlockLocator_rewind(BlockLocator *self) +{ + #ifdef DEBUG + fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); + #endif + + free(self->codestr); + self->codestr = PyMem_New(char, self->codestr_sz); + memcpy(self->codestr, self->_codestr, self->codestr_sz); + self->codestr_ptr = self->codestr; + self->lineno = 0; + self->par = 0; + self->instr = 0; + self->depth = 0; + self->skip = 0; + self->thin = self->codestr; + self->init = self->codestr; + self->safe = self->codestr; + self->lose = self->codestr; + self->start = NULL; + self->end = NULL; + + #ifdef DEBUG + fprintf(stderr, "\tScss BlockLocator object rewound!\n"); + #endif +} + +Block* +BlockLocator_iternext(BlockLocator *self) +{ + _BlockLocator_Callback *fn; + unsigned char c = 0; + char *codestr_end = self->codestr + self->codestr_sz; + + #ifdef DEBUG + fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); + #endif + + memset(&self->block, 0, sizeof(Block)); + + while (self->codestr_ptr < codestr_end) { + c = *(self->codestr_ptr); + if (!c) { + self->codestr_ptr++; + continue; + } + + repeat: + + fn = scss_function_map[ + (int)c + + 256 * self->instr + + 256 * 256 * (int)(self->par != 0) + + 256 * 256 * 2 * (int)(self->depth > 1 ? 2 : self->depth) + ]; + + if (fn != NULL) { + fn(self); + } + + self->codestr_ptr++; + if (self->codestr_ptr > codestr_end) { + self->codestr_ptr = codestr_end; + } + + if (self->block.error) { + #ifdef DEBUG + if (self->block.error > 0) { + fprintf(stderr, "\tBlock found!\n"); + } else { + fprintf(stderr, "\tException!\n"); + } + #endif + return &self->block; + } + } + if (self->par > 0) { + if (self->block.error >= 0) { + self->block.error = -1; + sprintf(self->exc, "Missing closing parenthesis somewhere in block"); + #ifdef DEBUG + fprintf(stderr, "\t%s\n", self->exc); + #endif + } + } else if (self->instr != 0) { + if (self->block.error >= 0) { + self->block.error = -2; + sprintf(self->exc, "Missing closing string somewhere in block"); + #ifdef DEBUG + fprintf(stderr, "\t%s\n", self->exc); + #endif + } + } else if (self->depth > 0) { + if (self->block.error >= 0) { + self->block.error = -3; + sprintf(self->exc, "Missing closing string somewhere in block"); + #ifdef DEBUG + fprintf(stderr, "\t%s\n", self->exc); + #endif + } + if (self->init < codestr_end) { + c = '}'; + goto repeat; + } + } + if (self->init < codestr_end) { + self->init = codestr_end; + c = 0; + goto repeat; + } + + BlockLocator_rewind(self); + + return &self->block; +} diff --git a/libs/scss/src/block_locator.h b/libs/scss/src/block_locator.h new file mode 100644 index 0000000..c59afbc --- /dev/null +++ b/libs/scss/src/block_locator.h @@ -0,0 +1,52 @@ +/* +* pyScss, a Scss compiler for Python +* SCSS blocks scanner. +* +* German M. Bravo (Kronuz) +* https://github.com/Kronuz/pyScss +* +* MIT license (http://www.opensource.org/licenses/mit-license.php) +* Copyright (c) 2011 German M. Bravo (Kronuz), All rights reserved. +*/ +#ifndef BLOCK_LOCATOR_H +#define BLOCK_LOCATOR_H + +#define MAX_EXC_STRING 4096 + +typedef struct { + int error; + int lineno; + char *selprop; + int selprop_sz; + char *codestr; + int codestr_sz; +} Block; + +typedef struct { + char exc[MAX_EXC_STRING]; + char *_codestr; + char *codestr; + char *codestr_ptr; + int codestr_sz; + int lineno; + int par; + char instr; + int depth; + int skip; + char *thin; + char *init; + char *safe; + char *lose; + char *start; + char *end; + Block block; +} BlockLocator; + +void BlockLocator_initialize(void); +void BlockLocator_finalize(void); + +Block* BlockLocator_iternext(BlockLocator *self); +BlockLocator *BlockLocator_new(char *codestr, int codestr_sz); +void BlockLocator_del(BlockLocator *self); + +#endif diff --git a/libs/scss/src/scanner.c b/libs/scss/src/scanner.c new file mode 100644 index 0000000..b8fedef --- /dev/null +++ b/libs/scss/src/scanner.c @@ -0,0 +1,463 @@ +/* +* pyScss, a Scss compiler for Python +* SCSS blocks scanner. +* +* German M. Bravo (Kronuz) +* https://github.com/Kronuz/pyScss +* +* MIT license (http://www.opensource.org/licenses/mit-license.php) +* Copyright (c) 2011 German M. Bravo (Kronuz), All rights reserved. +*/ +#include + +#include +#include +#include "scanner.h" + +#include "utils.h" + +int Pattern_patterns_sz = 0; +int Pattern_patterns_bsz = 0; +Pattern *Pattern_patterns = NULL; +int Pattern_patterns_initialized = 0; + +Pattern* +Pattern_regex(char *tok, char *expr) { + int j; + + #ifdef DEBUG + fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); + #endif + + for (j = 0; j < Pattern_patterns_sz; j++) { + if (strcmp(Pattern_patterns[j].tok, tok) == 0) { + return &Pattern_patterns[j]; + } + } + if (expr) { + if (j >= Pattern_patterns_bsz) { + /* Needs to expand block */ + Pattern_patterns_bsz = Pattern_patterns_bsz + BLOCK_SIZE_PATTERNS; + PyMem_Resize(Pattern_patterns, Pattern, Pattern_patterns_bsz); + } + Pattern_patterns[j].tok = PyMem_Strdup(tok); + Pattern_patterns[j].expr = PyMem_Strdup(expr); + Pattern_patterns[j].pattern = NULL; + Pattern_patterns_sz = j + 1; + return &Pattern_patterns[j]; + } + return NULL; +} + +static int +Pattern_match(Pattern *regex, char *string, int string_sz, int start_at, Token *p_token) { + int options = PCRE_ANCHORED; + const char *errptr; + int ret, erroffset, ovector[3]; + pcre *p_pattern = regex->pattern; + + #ifdef DEBUG + fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); + #endif + + if (p_pattern == NULL) { + #ifdef DEBUG + fprintf(stderr, "\tpcre_compile %s\n", repr(regex->expr)); + #endif + p_pattern = regex->pattern = pcre_compile(regex->expr, options, &errptr, &erroffset, NULL); + } + ret = pcre_exec( + p_pattern, + NULL, /* no extra data */ + string, + string_sz, + start_at, + PCRE_ANCHORED, /* default options */ + ovector, /* output vector for substring information */ + 3 /* number of elements in the output vector */ + ); + if (ret >= 0) { + if (p_token) { + p_token->regex = regex; + p_token->string = string + ovector[0]; + p_token->string_sz = ovector[1] - ovector[0]; + } + return 1; + } + return 0; +} + +static void Pattern_initialize(Pattern *, int); +static void Pattern_setup(Pattern *, int); +static void Pattern_finalize(void); + + +static void +Pattern_initialize(Pattern *patterns, int patterns_sz) { + #ifdef DEBUG + fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); + #endif + + if (!Pattern_patterns_initialized) { + if (patterns_sz) { + Pattern_patterns_initialized = 1; + Pattern_setup(patterns, patterns_sz); + } + } +} + +static void +Pattern_setup(Pattern *patterns, int patterns_sz) { + int i; + Pattern *regex; + + #ifdef DEBUG + fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); + #endif + + if (!Pattern_patterns_initialized) { + Pattern_initialize(patterns, patterns_sz); + } else { + for (i = 0; i < patterns_sz; i++) { + regex = Pattern_regex(patterns[i].tok, patterns[i].expr); + #ifdef DEBUG + if (regex) { + fprintf(stderr, "\tAdded regex pattern %s: %s\n", repr(regex->tok), repr(regex->expr)); + } + #endif + } + } +} + +static void +Pattern_finalize(void) { + int j; + + #ifdef DEBUG + fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); + #endif + + if (Pattern_patterns_initialized) { + for (j = 0; j < Pattern_patterns_sz; j++) { + PyMem_Del(Pattern_patterns[j].tok); + PyMem_Del(Pattern_patterns[j].expr); + if (Pattern_patterns[j].pattern != NULL) { + pcre_free(Pattern_patterns[j].pattern); + } + } + PyMem_Del(Pattern_patterns); + Pattern_patterns = NULL; + Pattern_patterns_sz = 0; + Pattern_patterns_bsz = 0; + Pattern_patterns_initialized = 0; + } +} + +/* Scanner */ + + +static long +_Scanner_scan(Scanner *self, Pattern *restrictions, int restrictions_sz) +{ + Token best_token, *p_token; + Restriction *p_restriction; + Pattern *regex; + int j, k, max, skip; + size_t len; + char *aux; + + #ifdef DEBUG + fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); + #endif + + while (1) { + regex = NULL; + best_token.regex = NULL; + /* Search the patterns for a match, with earlier + tokens in the list having preference */ + for (j = 0; j < Pattern_patterns_sz; j++) { + regex = &Pattern_patterns[j]; + #ifdef DEBUG + fprintf(stderr, "\tTrying %s: %s at pos %d -> %s\n", repr(regex->tok), repr(regex->expr), self->pos, repr(self->input)); + #endif + /* First check to see if we're restricting to this token */ + skip = restrictions_sz; + if (skip) { + max = (restrictions_sz > self->ignore_sz) ? restrictions_sz : self->ignore_sz; + for (k = 0; k < max; k++) { + if (k < restrictions_sz && strcmp(regex->tok, restrictions[k].tok) == 0) { + skip = 0; + break; + } + if (k < self->ignore_sz && regex == self->ignore[k]) { + skip = 0; + break; + } + } + if (skip) { + continue; + #ifdef DEBUG + fprintf(stderr, "\tSkipping %s!\n", repr(regex->tok)); + #endif + } + } + if (Pattern_match( + regex, + self->input, + self->input_sz, + self->pos, + &best_token + )) { + #ifdef DEBUG + fprintf(stderr, "Match OK! %s: %s at pos %d\n", repr(regex->tok), repr(regex->expr), self->pos); + #endif + break; + } + } + /* If we didn't find anything, raise an error */ + if (best_token.regex == NULL) { + if (restrictions_sz) { + sprintf(self->exc, "SyntaxError[@ char %d: Bad token found while trying to find one of the restricted tokens: ", self->pos); + aux = self->exc + strlen(self->exc); + for (k=0; k self->exc + sizeof(self->exc) - 10) { + sprintf(aux, (k > 0) ? ", ..." : "..."); + break; + } + sprintf(aux, (k > 0) ? ", %s" : "%s", repr(restrictions[k].tok)); + aux += len + 2; + } + sprintf(aux, "]"); + return SCANNER_EXC_RESTRICTED; + } + sprintf(self->exc, "SyntaxError[@ char %d: Bad token found]", self->pos); + return SCANNER_EXC_BAD_TOKEN; + } + /* If we found something that isn't to be ignored, return it */ + skip = 0; + for (k = 0; k < self->ignore_sz; k++) { + if (best_token.regex == self->ignore[k]) { + /* This token should be ignored... */ + self->pos += best_token.string_sz; + skip = 1; + break; + } + } + if (!skip) { + break; + } + } + if (best_token.regex) { + self->pos = (int)(best_token.string - self->input + best_token.string_sz); + /* Only add this token if it's not in the list (to prevent looping) */ + p_token = &self->tokens[self->tokens_sz - 1]; + if (self->tokens_sz == 0 || + p_token->regex != best_token.regex || + p_token->string != best_token.string || + p_token->string_sz != best_token.string_sz + ) { + if (self->tokens_sz >= self->tokens_bsz) { + /* Needs to expand block */ + self->tokens_bsz = self->tokens_bsz + BLOCK_SIZE_PATTERNS; + PyMem_Resize(self->tokens, Token, self->tokens_bsz); + PyMem_Resize(self->restrictions, Restriction, self->tokens_bsz); + } + memcpy(&self->tokens[self->tokens_sz], &best_token, sizeof(Token)); + p_restriction = &self->restrictions[self->tokens_sz]; + if (restrictions_sz) { + p_restriction->patterns = PyMem_New(Pattern *, restrictions_sz); + p_restriction->patterns_sz = 0; + for (j = 0; j < restrictions_sz; j++) { + regex = Pattern_regex(restrictions[j].tok, restrictions[j].expr); + if (regex) { + p_restriction->patterns[p_restriction->patterns_sz++] = regex; + } + } + } else { + p_restriction->patterns = NULL; + p_restriction->patterns_sz = 0; + } + self->tokens_sz++; + return 1; + } + } + return 0; +} + + +/* Scanner public interface */ + +void +Scanner_reset(Scanner *self, char *input, int input_sz) { + int i; + + #ifdef DEBUG + fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); + #endif + + for (i = 0; i < self->tokens_sz; i++) { + PyMem_Del(self->restrictions[i].patterns); + self->restrictions[i].patterns = NULL; + self->restrictions[i].patterns_sz = 0; + } + self->tokens_sz = 0; + + if (self->input != NULL) { + PyMem_Del(self->input); + } + self->input = PyMem_Strndup(input, input_sz); + self->input_sz = input_sz; + #ifdef DEBUG + fprintf(stderr, "Scanning in %s\n", repr(self->input)); + #endif + + self->pos = 0; +} + +void +Scanner_del(Scanner *self) { + int i; + + #ifdef DEBUG + fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); + #endif + + if (self->ignore != NULL) { + PyMem_Del(self->ignore); + } + + if (self->tokens != NULL) { + for (i = 0; i < self->tokens_sz; i++) { + PyMem_Del(self->restrictions[i].patterns); + } + PyMem_Del(self->tokens); + PyMem_Del(self->restrictions); + } + + if (self->input != NULL) { + PyMem_Del(self->input); + } + + PyMem_Del(self); +} + +Scanner* +Scanner_new(Pattern patterns[], int patterns_sz, Pattern ignore[], int ignore_sz, char *input, int input_sz) +{ + int i; + Scanner *self; + Pattern *regex; + + #ifdef DEBUG + fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); + #endif + + self = PyMem_New(Scanner, 1); + memset(self, 0, sizeof(Scanner)); + if (self) { + for (i = 0; i < patterns_sz; i++) { + regex = Pattern_regex(patterns[i].tok, patterns[i].expr); + #ifdef DEBUG + if (regex) { + fprintf(stderr, "\tAdded regex pattern %s: %s\n", repr(regex->tok), repr(regex->expr)); + } + #endif + } + if (ignore_sz) { + self->ignore = PyMem_New(Pattern *, ignore_sz); + for (i = 0; i < ignore_sz; i++) { + regex = Pattern_regex(ignore[i].tok, ignore[i].expr); + if (regex) { + self->ignore[self->ignore_sz++] = regex; + #ifdef DEBUG + fprintf(stderr, "\tIgnoring token %s\n", repr(regex->tok)); + #endif + } + } + } else { + self->ignore = NULL; + } + Scanner_reset(self, input, input_sz); + } + return self; +} + +int +Scanner_initialized(void) +{ + return Pattern_patterns_initialized; +} + +void +Scanner_initialize(Pattern patterns[], int patterns_sz) +{ + #ifdef DEBUG + fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); + #endif + + Pattern_initialize(patterns, patterns_sz); +} + +void +Scanner_finalize(void) +{ + #ifdef DEBUG + fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); + #endif + + Pattern_finalize(); +} + +Token* +Scanner_token(Scanner *self, int i, Pattern restrictions[], int restrictions_sz) +{ + int j, k, found; + Pattern *regex; + long result; + + #ifdef DEBUG + fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); + #endif + + if (i == self->tokens_sz) { + result = _Scanner_scan(self, restrictions, restrictions_sz); + if (result < 0) { + return (Token *)result; + } + } else if (i >= 0 && i < self->tokens_sz) { + if (self->restrictions[i].patterns_sz) { + for (j = 0; j < restrictions_sz; j++) { + found = 0; + for (k = 0; k < self->restrictions[i].patterns_sz; k++) { + regex = Pattern_regex(restrictions[j].tok, restrictions[j].expr); + if (strcmp(restrictions[j].tok, self->restrictions[i].patterns[k]->tok) == 0) { + found = 1; + break; + } + } + if (!found) { + sprintf(self->exc, "Unimplemented: restriction set changed"); + return (Token *)SCANNER_EXC_UNIMPLEMENTED; + } + } + } + } + if (i >= 0 && i < self->tokens_sz) { + return &self->tokens[i]; + } + return (Token *)SCANNER_EXC_NO_MORE_TOKENS; +} + +void +Scanner_rewind(Scanner *self, int i) +{ + #ifdef DEBUG + fprintf(stderr, "%s\n", __PRETTY_FUNCTION__); + #endif + + if (i >= 0 && i < self->tokens_sz) { + self->tokens_sz = i; + self->pos = (int)(self->tokens[i].string - self->input); + } +} diff --git a/libs/scss/src/scanner.h b/libs/scss/src/scanner.h new file mode 100644 index 0000000..df9fb62 --- /dev/null +++ b/libs/scss/src/scanner.h @@ -0,0 +1,68 @@ +/* +* pyScss, a Scss compiler for Python +* SCSS blocks scanner. +* +* German M. Bravo (Kronuz) +* https://github.com/Kronuz/pyScss +* +* MIT license (http://www.opensource.org/licenses/mit-license.php) +* Copyright (c) 2011 German M. Bravo (Kronuz), All rights reserved. +*/ +#ifndef SCANNER_H +#define SCANNER_H + +#define PCRE_STATIC +#include + +#define BLOCK_SIZE_PATTERNS 50 +#define BLOCK_SIZE_TOKENS 50 + +#define MAX_EXC_STRING 4096 + +#define SCANNER_EXC_BAD_TOKEN (long)-1 +#define SCANNER_EXC_RESTRICTED (long)-2 +#define SCANNER_EXC_UNIMPLEMENTED (long)-3 +#define SCANNER_EXC_NO_MORE_TOKENS (long)-4 + +typedef struct { + char *tok; + char *expr; + pcre *pattern; +} Pattern; + +typedef struct { + Pattern *regex; + char *string; + int string_sz; +} Token; + +typedef struct { + int patterns_sz; + Pattern **patterns; +} Restriction; + +typedef struct { + char exc[MAX_EXC_STRING]; + int ignore_sz; + Pattern **ignore; + int tokens_sz; + int tokens_bsz; + Token *tokens; + Restriction *restrictions; + int input_sz; + char *input; + int pos; +} Scanner; + +int Scanner_initialized(void); +void Scanner_initialize(Pattern *, int); +void Scanner_finalize(void); + +void Scanner_reset(Scanner *self, char *input, int input_sz); +Scanner *Scanner_new(Pattern *, int, Pattern *, int, char *, int); +void Scanner_del(Scanner *); + +Token* Scanner_token(Scanner *, int, Pattern *, int); +void Scanner_rewind(Scanner *, int); + +#endif diff --git a/libs/scss/src/utils.h b/libs/scss/src/utils.h new file mode 100644 index 0000000..ee5401e --- /dev/null +++ b/libs/scss/src/utils.h @@ -0,0 +1,98 @@ +#include + +#include + +char * +PyMem_Strndup(const char *str, size_t len) +{ + if (str != NULL) { + char *copy = PyMem_New(char, len + 1); + if (copy != NULL) + memcpy(copy, str, len); + copy[len] = '\0'; + return copy; + } + return NULL; +} + +char * +PyMem_Strdup(const char *str) +{ + return PyMem_Strndup(str, strlen(str)); +} + +char * +reprn(char *str, size_t len) { + static char strings[10240]; + static size_t current = 0; + size_t reqlen = 2; + char c, + *out, + *write, + *begin = str, + *end = str + len; + while (begin < end) { + c = *begin; + if (c == '\'') { + reqlen += 2; + } else if (c == '\r') { + reqlen += 2; + } else if (c == '\n') { + reqlen += 2; + } else if (c == '\t') { + reqlen += 2; + } else if (c < ' ') { + reqlen += 3; + } else { + reqlen++; + } + begin++; + } + if (reqlen > 10240) { + reqlen = 10240; + } + if (current + reqlen > 10240) { + current = 0; + } + begin = str; + end = str + len; + out = write = strings + current; + *write++ = '\''; + while (begin < end) { + c = *begin; + if (c == '\'') { + if (write + 5 >= strings + 10240) break; + sprintf(write, "\\'"); + write += 2; + } else if (c == '\r') { + if (write + 5 >= strings + 10240) break; + sprintf(write, "\\r"); + write += 2; + } else if (c == '\n') { + if (write + 5 >= strings + 10240) break; + sprintf(write, "\\n"); + write += 2; + } else if (c == '\t') { + if (write + 5 >= strings + 10240) break; + sprintf(write, "\\t"); + write += 2; + } else if (c < ' ') { + if (write + 6 >= strings + 10240) break; + sprintf(write, "\\x%02x", c); + write += 3; + } else { + if (write + 4 >= strings + 10240) break; + *write++ = c; + } + begin++; + } + *write++ = '\''; + *write++ = '\0'; + current += (size_t)(write - out); + return out; +} + +char * +repr(char *str) { + return reprn(str, strlen(str)); +} diff --git a/libs/scss/tool.py b/libs/scss/tool.py new file mode 100644 index 0000000..205bb81 --- /dev/null +++ b/libs/scss/tool.py @@ -0,0 +1,403 @@ +#!/usr/bin/env python +from __future__ import absolute_import +from __future__ import print_function + +from contextlib import contextmanager +import logging +import os +import re +import sys +from collections import deque + +from scss import config +from scss.util import profiling +from scss import Scss, SourceFile, log +from scss import _prop_split_re +from scss.rule import SassRule +from scss.rule import UnparsedBlock +from scss.expression import Calculator +from scss.scss_meta import BUILD_INFO +from scss.errors import SassEvaluationError + +try: + raw_input +except NameError: + raw_input = input + +log.setLevel(logging.INFO) + + +def main(): + logging.basicConfig(format="%(levelname)s: %(message)s") + + from optparse import OptionGroup, OptionParser, SUPPRESS_HELP + + parser = OptionParser(usage="Usage: %prog [options] [file]", + description="Converts Scss files to CSS.", + add_help_option=False) + parser.add_option("-i", "--interactive", action="store_true", + help="Run an interactive Scss shell") + parser.add_option("-w", "--watch", metavar="DIR", + help="Watch the files in DIR, and recompile when they change") + parser.add_option("-r", "--recursive", action="store_true", default=False, + help="Also watch directories inside of the watch directory") + parser.add_option("-o", "--output", metavar="PATH", + help="Write output to PATH (a directory if using watch, a file otherwise)") + parser.add_option("-s", "--suffix", metavar="STRING", + help="If using watch, a suffix added to the output filename (i.e. filename.STRING.css)") + parser.add_option("--time", action="store_true", + help="Display compliation times") + parser.add_option("--debug-info", action="store_true", + help="Turns on scss's debugging information") + parser.add_option("--no-debug-info", action="store_false", + dest="debug_info", default=False, + help="Turns off scss's debugging information") + parser.add_option("-T", "--test", action="store_true", help=SUPPRESS_HELP) + parser.add_option("-t", "--style", metavar="NAME", + dest="style", default='nested', + help="Output style. Can be nested (default), compact, compressed, or expanded.") + parser.add_option("-C", "--no-compress", action="store_false", dest="style", default=True, + help="Don't minify outputted CSS") + parser.add_option("-?", action="help", help=SUPPRESS_HELP) + parser.add_option("-h", "--help", action="help", + help="Show this message and exit") + parser.add_option("-v", "--version", action="store_true", + help="Print version and exit") + + paths_group = OptionGroup(parser, "Resource Paths") + paths_group.add_option("-I", "--load-path", metavar="PATH", + action="append", dest="load_paths", + help="Add a scss import path, may be given multiple times") + paths_group.add_option("-S", "--static-root", metavar="PATH", dest="static_root", + help="Static root path (Where images and static resources are located)") + paths_group.add_option("-A", "--assets-root", metavar="PATH", dest="assets_root", + help="Assets root path (Sprite images will be created here)") + paths_group.add_option("-a", "--assets-url", metavar="URL", dest="assets_url", + help="URL to reach the files in your assets_root") + paths_group.add_option("-F", "--fonts-root", metavar="PATH", dest="fonts_root", + help="Fonts root path (Where fonts are located)") + paths_group.add_option("-f", "--fonts-url", metavar="PATH", dest="fonts_url", + help="URL to reach the fonts in your fonts_root") + paths_group.add_option("--images-root", metavar="PATH", dest="images_root", + help="Images root path (Where images are located)") + paths_group.add_option("--images-url", metavar="PATH", dest="images_url", + help="URL to reach the images in your images_root") + paths_group.add_option("--cache-root", metavar="PATH", dest="cache_root", + help="Cache root path (Cache files will be created here)") + parser.add_option_group(paths_group) + + parser.add_option("--sass", action="store_true", + dest="is_sass", default=None, + help="Sass mode") + + (options, args) = parser.parse_args() + + # General runtime configuration + config.VERBOSITY = 0 + if options.time: + config.VERBOSITY = 2 + + if options.static_root is not None: + config.STATIC_ROOT = options.static_root + if options.assets_root is not None: + config.ASSETS_ROOT = options.assets_root + + if options.fonts_root is not None: + config.FONTS_ROOT = options.fonts_root + if options.fonts_url is not None: + config.FONTS_URL = options.fonts_url + + if options.images_root is not None: + config.IMAGES_ROOT = options.images_root + if options.images_url is not None: + config.IMAGES_URL = options.images_url + + if options.cache_root is not None: + config.CACHE_ROOT = options.cache_root + if options.load_paths is not None: + # TODO: Convert global LOAD_PATHS to a list. Use it directly. + # Doing the above will break backwards compatibility! + if hasattr(config.LOAD_PATHS, 'split'): + load_path_list = [p.strip() for p in config.LOAD_PATHS.split(',')] + else: + load_path_list = list(config.LOAD_PATHS) + + for path_param in options.load_paths: + for p in path_param.replace(os.pathsep, ',').replace(';', ',').split(','): + p = p.strip() + if p and p not in load_path_list: + load_path_list.append(p) + + # TODO: Remove this once global LOAD_PATHS is a list. + if hasattr(config.LOAD_PATHS, 'split'): + config.LOAD_PATHS = ','.join(load_path_list) + else: + config.LOAD_PATHS = load_path_list + if options.assets_url is not None: + config.ASSETS_URL = options.assets_url + + # Execution modes + if options.test: + run_tests() + elif options.version: + print_version() + elif options.interactive: + run_repl(options) + elif options.watch: + watch_sources(options) + else: + do_build(options, args) + + +def print_version(): + print(BUILD_INFO) + + +def run_tests(): + try: + import pytest + except ImportError: + raise ImportError("You need py.test installed to run the test suite.") + pytest.main("") # don't let py.test re-consume our arguments + + +def do_build(options, args): + if options.output is not None: + output = open(options.output, 'wt') + else: + output = sys.stdout + + css = Scss(scss_opts={ + 'style': options.style, + 'debug_info': options.debug_info, + }) + if args: + for path in args: + output.write(css.compile(scss_file=path, is_sass=options.is_sass)) + else: + output.write(css.compile(sys.stdin.read(), is_sass=options.is_sass)) + + for f, t in profiling.items(): + sys.stderr.write("%s took %03fs" % (f, t)) + + +def watch_sources(options): + import time + try: + from watchdog.observers import Observer + from watchdog.events import PatternMatchingEventHandler + except ImportError: + sys.stderr.write("Using watch functionality requires the `watchdog` library: http://pypi.python.org/pypi/watchdog/") + sys.exit(1) + if options.output and not os.path.isdir(options.output): + sys.stderr.write("watch file output directory is invalid: '%s'" % (options.output)) + sys.exit(2) + + class ScssEventHandler(PatternMatchingEventHandler): + def __init__(self, *args, **kwargs): + super(ScssEventHandler, self).__init__(*args, **kwargs) + self.css = Scss(scss_opts={ + 'style': options.style, + 'debug_info': options.debug_info, + }) + self.output = options.output + self.suffix = options.suffix + + def is_valid(self, path): + return os.path.isfile(path) and (path.endswith('.scss') or path.endswith('.sass')) and not os.path.basename(path).startswith('_') + + def process(self, path): + if os.path.isdir(path): + for f in os.listdir(path): + full = os.path.join(path, f) + if self.is_valid(full): + self.compile(full) + elif self.is_valid(path): + self.compile(path) + + def compile(self, src_path): + fname = os.path.basename(src_path) + if fname.endswith('.scss') or fname.endswith('.sass'): + fname = fname[:-5] + if self.suffix: + fname += '.' + self.suffix + fname += '.css' + else: + # you didn't give me a file of the correct type! + return False + + if self.output: + dest_path = os.path.join(self.output, fname) + else: + dest_path = os.path.join(os.path.dirname(src_path), fname) + + print("Compiling %s => %s" % (src_path, dest_path)) + dest_file = open(dest_path, 'w') + dest_file.write(self.css.compile(scss_file=src_path)) + + def on_moved(self, event): + super(ScssEventHandler, self).on_moved(event) + self.process(event.dest_path) + + def on_created(self, event): + super(ScssEventHandler, self).on_created(event) + self.process(event.src_path) + + def on_modified(self, event): + super(ScssEventHandler, self).on_modified(event) + self.process(event.src_path) + + event_handler = ScssEventHandler(patterns=['*.scss', '*.sass']) + observer = Observer() + observer.schedule(event_handler, path=options.watch, recursive=options.recursive) + observer.start() + try: + while True: + time.sleep(1) + except KeyboardInterrupt: + observer.stop() + observer.join() + + +@contextmanager +def readline_history(fn): + try: + import readline + except ImportError: + yield + return + + try: + readline.read_history_file(fn) + except IOError: + pass + + try: + yield + finally: + try: + readline.write_history_file(fn) + except IOError: + pass + + +def run_repl(is_sass=False): + repl = SassRepl() + + with readline_history(os.path.expanduser('~/.scss-history')): + print("Welcome to %s interactive shell" % (BUILD_INFO,)) + while True: + try: + in_ = raw_input('>>> ').strip() + for output in repl(in_): + print(output) + except (EOFError, KeyboardInterrupt): + print("Bye!") + return + + +class SassRepl(object): + def __init__(self, is_sass=False): + self.css = Scss() + self.namespace = self.css.root_namespace + self.options = self.css.scss_opts + self.source_file = SourceFile.from_string('', '', line_numbers=False, is_sass=is_sass) + self.calculator = Calculator(self.namespace) + + def __call__(self, s): + from pprint import pformat + + if s in ('exit', 'quit'): + raise KeyboardInterrupt + + for s in s.split(';'): + s = self.source_file.prepare_source(s.strip()) + if not s: + continue + elif s.startswith('@'): + scope = None + properties = [] + children = deque() + rule = SassRule(self.source_file, namespace=self.namespace, options=self.options, properties=properties) + block = UnparsedBlock(rule, 1, s, None) + code, name = (s.split(None, 1) + [''])[:2] + if code == '@option': + self.css._settle_options(rule, children, scope, block) + continue + elif code == '@import': + self.css._do_import(rule, children, scope, block) + continue + elif code == '@include': + final_cont = '' + self.css._do_include(rule, children, scope, block) + code = self.css._print_properties(properties).rstrip('\n') + if code: + final_cont += code + if children: + self.css.children.extendleft(children) + self.css.parse_children() + code = self.css._create_css(self.css.rules).rstrip('\n') + if code: + final_cont += code + yield final_cont + continue + elif s == 'ls' or s.startswith('show(') or s.startswith('show ') or s.startswith('ls(') or s.startswith('ls '): + m = re.match(r'(?:show|ls)(\()?\s*([^,/\\) ]*)(?:[,/\\ ]([^,/\\ )]+))*(?(1)\))', s, re.IGNORECASE) + if m: + name = m.group(2) + code = m.group(3) + name = name and name.strip().rstrip('s') # remove last 's' as in functions + code = code and code.strip() + ns = self.namespace + if not name: + yield pformat(sorted(['vars', 'options', 'mixins', 'functions'])) + elif name in ('v', 'var', 'variable'): + variables = dict(ns._variables) + if code == '*': + pass + elif code: + variables = dict((k, v) for k, v in variables.items() if code in k) + else: + variables = dict((k, v) for k, v in variables.items() if not k.startswith('$--')) + yield pformat(variables) + + elif name in ('o', 'opt', 'option'): + opts = self.options + if code == '*': + pass + elif code: + opts = dict((k, v) for k, v in opts.items() if code in k) + else: + opts = dict((k, v) for k, v in opts.items() if not k.startswith('@')) + yield pformat(opts) + + elif name in ('m', 'mix', 'mixin', 'f', 'func', 'funct', 'function'): + if name.startswith('m'): + funcs = dict(ns._mixins) + elif name.startswith('f'): + funcs = dict(ns._functions) + if code == '*': + pass + elif code: + funcs = dict((k, v) for k, v in funcs.items() if code in k[0]) + else: + pass + # TODO print source when possible + yield pformat(funcs) + continue + elif s.startswith('$') and (':' in s or '=' in s): + prop, value = [a.strip() for a in _prop_split_re.split(s, 1)] + prop = self.calculator.do_glob_math(prop) + value = self.calculator.calculate(value) + self.namespace.set_variable(prop, value) + continue + + # TODO respect compress? + try: + yield(self.calculator.calculate(s).render()) + except (SyntaxError, SassEvaluationError) as e: + print("%s" % e, file=sys.stderr) + + +if __name__ == "__main__": + main() diff --git a/libs/scss/types.py b/libs/scss/types.py new file mode 100644 index 0000000..aafbab4 --- /dev/null +++ b/libs/scss/types.py @@ -0,0 +1,1128 @@ +from __future__ import absolute_import +from __future__ import division +from __future__ import print_function + +import colorsys +import operator + +import six + +from scss.cssdefs import COLOR_LOOKUP, COLOR_NAMES, ZEROABLE_UNITS, convert_units_to_base_units, cancel_base_units, count_base_units +from scss.util import escape + + +################################################################################ +# pyScss data types: + +class Value(object): + is_null = False + sass_type_name = u'unknown' + + def __repr__(self): + return '<%s(%s)>' % (self.__class__.__name__, repr(self.value)) + + # Sass values are all true, except for booleans and nulls + def __bool__(self): + return True + + def __nonzero__(self): + # Py 2's name for __bool__ + return self.__bool__() + + # All Sass scalars also act like one-element spaced lists + use_comma = False + + def __iter__(self): + return iter((self,)) + + def __len__(self): + return 1 + + def __getitem__(self, key): + if key not in (-1, 0): + raise IndexError(key) + + return self + + def __contains__(self, item): + return self == item + + ### NOTE: From here on down, the operators are exposed to Sass code and + ### thus should ONLY return Sass types + + # Reasonable default for equality + def __eq__(self, other): + return Boolean( + type(self) == type(other) and self.value == other.value) + + def __ne__(self, other): + return Boolean(not self.__eq__(other)) + + # Only numbers support ordering + def __lt__(self, other): + raise TypeError("Can't compare %r with %r" % (self, other)) + + def __le__(self, other): + raise TypeError("Can't compare %r with %r" % (self, other)) + + def __gt__(self, other): + raise TypeError("Can't compare %r with %r" % (self, other)) + + def __ge__(self, other): + raise TypeError("Can't compare %r with %r" % (self, other)) + + # Math ops + def __add__(self, other): + # Default behavior is to treat both sides like strings + if isinstance(other, String): + return String(self.render() + other.value, quotes=other.quotes) + return String(self.render() + other.render()) + + def __sub__(self, other): + # Default behavior is to treat the whole expression like one string + return String(self.render() + "-" + other.render()) + + def __div__(self, other): + return String(self.render() + "/" + other.render()) + + # Sass types have no notion of floor vs true division + def __truediv__(self, other): + return self.__div__(other) + + def __floordiv__(self, other): + return self.__div__(other) + + def __mul__(self, other): + return NotImplemented + + def __pos__(self): + return String("+" + self.render()) + + def __neg__(self): + return String("-" + self.render()) + + def to_dict(self): + """Return the Python dict equivalent of this map. + + If this type can't be expressed as a map, raise. + """ + return dict(self.to_pairs()) + + def to_pairs(self): + """Return the Python list-of-tuples equivalent of this map. Note that + this is different from ``self.to_dict().items()``, because Sass maps + preserve order. + + If this type can't be expressed as a map, raise. + """ + raise ValueError("Not a map: {0!r}".format(self)) + + def render(self, compress=False): + return self.__str__() + + +class Null(Value): + is_null = True + sass_type_name = u'null' + + def __init__(self, value=None): + pass + + def __str__(self): + return self.sass_type_name + + def __repr__(self): + return "<%s()>" % (self.__class__.__name__,) + + def __hash__(self): + return hash(None) + + def __bool__(self): + return False + + def __eq__(self, other): + return Boolean(isinstance(other, Null)) + + def __ne__(self, other): + return Boolean(not self.__eq__(other)) + + def render(self, compress=False): + return self.sass_type_name + + +class Undefined(Null): + sass_type_name = u'undefined' + + def __init__(self, value=None): + pass + + def __add__(self, other): + return self + + def __radd__(self, other): + return self + + def __sub__(self, other): + return self + + def __rsub__(self, other): + return self + + def __div__(self, other): + return self + + def __rdiv__(self, other): + return self + + def __truediv__(self, other): + return self + + def __rtruediv__(self, other): + return self + + def __floordiv__(self, other): + return self + + def __rfloordiv__(self, other): + return self + + def __mul__(self, other): + return self + + def __rmul__(self, other): + return self + + def __pos__(self): + return self + + def __neg__(self): + return self + + +class Boolean(Value): + sass_type_name = u'bool' + + def __init__(self, value): + self.value = bool(value) + + def __str__(self): + return 'true' if self.value else 'false' + + def __hash__(self): + return hash(self.value) + + def __bool__(self): + return self.value + + def render(self, compress=False): + if self.value: + return 'true' + else: + return 'false' + + +class Number(Value): + sass_type_name = u'number' + + def __init__(self, amount, unit=None, unit_numer=(), unit_denom=()): + if isinstance(amount, Number): + assert not unit and not unit_numer and not unit_denom + self.value = amount.value + self.unit_numer = amount.unit_numer + self.unit_denom = amount.unit_denom + return + + if not isinstance(amount, (int, float)): + raise TypeError("Expected number, got %r" % (amount,)) + + if unit is not None: + unit_numer = unit_numer + (unit.lower(),) + + # Cancel out any convertable units on the top and bottom + numerator_base_units = count_base_units(unit_numer) + denominator_base_units = count_base_units(unit_denom) + + # Count which base units appear both on top and bottom + cancelable_base_units = {} + for unit, count in numerator_base_units.items(): + cancelable_base_units[unit] = min( + count, denominator_base_units.get(unit, 0)) + + # Actually remove the units + numer_factor, unit_numer = cancel_base_units(unit_numer, cancelable_base_units) + denom_factor, unit_denom = cancel_base_units(unit_denom, cancelable_base_units) + + # And we're done + self.unit_numer = tuple(unit_numer) + self.unit_denom = tuple(unit_denom) + self.value = amount * (numer_factor / denom_factor) + + def __repr__(self): + full_unit = ' * '.join(self.unit_numer) + if self.unit_denom: + full_unit += ' / ' + full_unit += ' * '.join(self.unit_denom) + + if full_unit: + full_unit = ' ' + full_unit + + return '<%s(%r%s)>' % (self.__class__.__name__, self.value, full_unit) + + def __hash__(self): + return hash((self.value, self.unit_numer, self.unit_denom)) + + def __int__(self): + return int(self.value) + + def __float__(self): + return float(self.value) + + def __pos__(self): + return self + + def __neg__(self): + return self * Number(-1) + + def __str__(self): + return self.render() + + def __eq__(self, other): + if not isinstance(other, Number): + return Boolean(False) + return self._compare(other, operator.__eq__, soft_fail=True) + + def __lt__(self, other): + return self._compare(other, operator.__lt__) + + def __le__(self, other): + return self._compare(other, operator.__le__) + + def __gt__(self, other): + return self._compare(other, operator.__gt__) + + def __ge__(self, other): + return self._compare(other, operator.__ge__) + + def _compare(self, other, op, soft_fail=False): + if not isinstance(other, Number): + raise TypeError("Can't compare %r and %r" % (self, other)) + + # A unitless operand is treated as though it had the other operand's + # units, and zero values can cast to anything, so in both cases the + # units can be ignored + if (self.is_unitless or other.is_unitless or + self.value == 0 or other.value == 0): + left = self + right = other + else: + left = self.to_base_units() + right = other.to_base_units() + + if left.unit_numer != right.unit_numer or left.unit_denom != right.unit_denom: + if soft_fail: + # Used for equality only, where == should never fail + return Boolean(False) + else: + raise ValueError("Can't reconcile units: %r and %r" % (self, other)) + + return Boolean(op(round(left.value, 5), round(right.value, 5))) + + def __pow__(self, exp): + if not isinstance(exp, Number): + raise TypeError("Can't raise %r to power %r" % (self, exp)) + if not exp.is_unitless: + raise TypeError("Exponent %r cannot have units" % (exp,)) + + if self.is_unitless: + return Number(self.value ** exp.value) + + # Units can only be exponentiated to integral powers -- what's the + # square root of 'px'? (Well, it's sqrt(px), but supporting that is + # a bit out of scope.) + if exp.value != int(exp.value): + raise ValueError("Can't raise units of %r to non-integral power %r" % (self, exp)) + + return Number( + self.value ** int(exp.value), + unit_numer=self.unit_numer * int(exp.value), + unit_denom=self.unit_denom * int(exp.value), + ) + + def __mul__(self, other): + if not isinstance(other, Number): + return NotImplemented + + amount = self.value * other.value + numer = self.unit_numer + other.unit_numer + denom = self.unit_denom + other.unit_denom + + return Number(amount, unit_numer=numer, unit_denom=denom) + + def __div__(self, other): + if not isinstance(other, Number): + return NotImplemented + + amount = self.value / other.value + numer = self.unit_numer + other.unit_denom + denom = self.unit_denom + other.unit_numer + + return Number(amount, unit_numer=numer, unit_denom=denom) + + def __add__(self, other): + # Numbers auto-cast to strings when added to other strings + if isinstance(other, String): + return String(self.render(), quotes=None) + other + + return self._add_sub(other, operator.add) + + def __sub__(self, other): + return self._add_sub(other, operator.sub) + + def _add_sub(self, other, op): + """Implements both addition and subtraction.""" + if not isinstance(other, Number): + return NotImplemented + + # If either side is unitless, inherit the other side's units. Skip all + # the rest of the conversion math, too. + if self.is_unitless or other.is_unitless: + return Number( + op(self.value, other.value), + unit_numer=self.unit_numer or other.unit_numer, + unit_denom=self.unit_denom or other.unit_denom, + ) + + # Likewise, if either side is zero, it can auto-cast to any units + if self.value == 0: + return Number( + op(self.value, other.value), + unit_numer=other.unit_numer, + unit_denom=other.unit_denom, + ) + elif other.value == 0: + return Number( + op(self.value, other.value), + unit_numer=self.unit_numer, + unit_denom=self.unit_denom, + ) + + # Reduce both operands to the same units + left = self.to_base_units() + right = other.to_base_units() + + if left.unit_numer != right.unit_numer or left.unit_denom != right.unit_denom: + raise ValueError("Can't reconcile units: %r and %r" % (self, other)) + + new_amount = op(left.value, right.value) + + # Convert back to the left side's units + if left.value != 0: + new_amount = new_amount * self.value / left.value + + return Number(new_amount, unit_numer=self.unit_numer, unit_denom=self.unit_denom) + + ### Helper methods, mostly used internally + + def to_base_units(self): + """Convert to a fixed set of "base" units. The particular units are + arbitrary; what's important is that they're consistent. + + Used for addition and comparisons. + """ + # Convert to "standard" units, as defined by the conversions dict above + amount = self.value + + numer_factor, numer_units = convert_units_to_base_units(self.unit_numer) + denom_factor, denom_units = convert_units_to_base_units(self.unit_denom) + + return Number( + amount * numer_factor / denom_factor, + unit_numer=numer_units, + unit_denom=denom_units, + ) + + ### Utilities for public consumption + + @classmethod + def wrap_python_function(cls, fn): + """Wraps an unary Python math function, translating the argument from + Sass to Python on the way in, and vice versa for the return value. + + Used to wrap simple Python functions like `ceil`, `floor`, etc. + """ + def wrapped(sass_arg): + # TODO enforce no units for trig? + python_arg = sass_arg.value + python_ret = fn(python_arg) + sass_ret = cls( + python_ret, + unit_numer=sass_arg.unit_numer, + unit_denom=sass_arg.unit_denom) + return sass_ret + + return wrapped + + def to_python_index(self, length, check_bounds=True, circular=False): + """Return a plain Python integer appropriate for indexing a sequence of + the given length. Raise if this is impossible for any reason + whatsoever. + """ + if not self.is_unitless: + raise ValueError("Index cannot have units: {0!r}".format(self)) + + ret = int(self.value) + if ret != self.value: + raise ValueError("Index must be an integer: {0!r}".format(ret)) + + if ret == 0: + raise ValueError("Index cannot be zero") + + if check_bounds and not circular and abs(ret) > length: + raise ValueError("Index {0!r} out of bounds for length {1}".format(ret, length)) + + if ret > 0: + ret -= 1 + + if circular: + ret = ret % length + + return ret + + @property + def has_simple_unit(self): + """Returns True iff the unit is expressible in CSS, i.e., has no + denominator and at most one unit in the numerator. + """ + return len(self.unit_numer) <= 1 and not self.unit_denom + + def is_simple_unit(self, unit): + """Return True iff the unit is simple (as above) and matches the given + unit. + """ + if self.unit_denom or len(self.unit_numer) > 1: + return False + + if not self.unit_numer: + # Empty string historically means no unit + return unit == '' + + return self.unit_numer[0] == unit + + @property + def is_unitless(self): + return not self.unit_numer and not self.unit_denom + + def render(self, compress=False): + if not self.has_simple_unit: + raise ValueError("Can't express compound units in CSS: %r" % (self,)) + + if self.unit_numer: + unit = self.unit_numer[0] + else: + unit = '' + + value = self.value + if compress and unit in ZEROABLE_UNITS and value == 0: + return '0' + + if value == 0: # -0.0 is plain 0 + value = 0 + + val = "%0.05f" % round(value, 5) + val = val.rstrip('0').rstrip('.') + + if compress and val.startswith('0.'): + # Strip off leading zero when compressing + val = val[1:] + + return val + unit + + +class List(Value): + """A list of other values. May be delimited by commas or spaces. + + Lists of one item don't make much sense in CSS, but can exist in Sass. Use ...... + + Lists may also contain zero items, but these are forbidden from appearing + in CSS output. + """ + + sass_type_name = u'list' + + def __init__(self, iterable, separator=None, use_comma=None, is_literal=False): + if isinstance(iterable, List): + iterable = iterable.value + + if not isinstance(iterable, (list, tuple)): + raise TypeError("Expected list, got %r" % (iterable,)) + + self.value = list(iterable) + + for item in self.value: + if not isinstance(item, Value): + raise TypeError("Expected a Sass type, got %r" % (item,)) + + # TODO remove separator argument entirely + if use_comma is None: + self.use_comma = separator == "," + else: + self.use_comma = use_comma + + self.is_literal = is_literal + + @classmethod + def maybe_new(cls, values, use_comma=True): + """If `values` contains only one item, return that item. Otherwise, + return a List as normal. + """ + if len(values) == 1: + return values[0] + else: + return cls(values, use_comma=use_comma) + + def maybe(self): + """If this List contains only one item, return it. Otherwise, return + the List. + """ + if len(self.value) == 1: + return self.value[0] + else: + return self + + @classmethod + def from_maybe(cls, values, use_comma=True): + """If `values` appears to not be a list, return a list containing it. + Otherwise, return a List as normal. + """ + if values is None: + values = [] + return values + + @classmethod + def from_maybe_starargs(cls, args, use_comma=True): + """If `args` has one element which appears to be a list, return it. + Otherwise, return a list as normal. + + Mainly used by Sass function implementations that predate `...` + support, so they can accept both a list of arguments and a single list + stored in a variable. + """ + if len(args) == 1: + if isinstance(args[0], cls): + return args[0] + elif isinstance(args[0], (list, tuple)): + return cls(args[0], use_comma=use_comma) + + return cls(args, use_comma=use_comma) + + def __repr__(self): + return "" % ( + self.value, + self.delimiter(compress=True), + ) + + def __hash__(self): + return hash((tuple(self.value), self.use_comma)) + + def delimiter(self, compress=False): + if self.use_comma: + if compress: + return ',' + else: + return ', ' + else: + return ' ' + + def __len__(self): + return len(self.value) + + def __str__(self): + return self.render() + + def __iter__(self): + return iter(self.value) + + def __contains__(self, item): + return item in self.value + + def __getitem__(self, key): + return self.value[key] + + def to_pairs(self): + pairs = [] + for item in self: + if len(item) != 2: + return super(List, self).to_pairs() + + pairs.append(tuple(item)) + + return pairs + + def render(self, compress=False): + if not self.value: + raise ValueError("Can't render empty list as CSS") + + delim = self.delimiter(compress) + + if self.is_literal: + value = self.value + else: + # Non-literal lists have nulls stripped + value = [item for item in self.value if not item.is_null] + # Non-empty lists containing only nulls become nothing, just like + # single nulls + if not value: + return '' + + return delim.join( + item.render(compress=compress) + for item in value + ) + + # DEVIATION: binary ops on lists and scalars act element-wise + def __add__(self, other): + if isinstance(other, List): + max_list, min_list = (self, other) if len(self) > len(other) else (other, self) + return List([item + max_list[i] for i, item in enumerate(min_list)], use_comma=self.use_comma) + + elif isinstance(other, String): + # UN-DEVIATION: adding a string should fall back to canonical + # behavior of string addition + return super(List, self).__add__(other) + + else: + return List([item + other for item in self], use_comma=self.use_comma) + + def __sub__(self, other): + if isinstance(other, List): + max_list, min_list = (self, other) if len(self) > len(other) else (other, self) + return List([item - max_list[i] for i, item in enumerate(min_list)], use_comma=self.use_comma) + + return List([item - other for item in self], use_comma=self.use_comma) + + def __mul__(self, other): + if isinstance(other, List): + max_list, min_list = (self, other) if len(self) > len(other) else (other, self) + max_list, min_list = (self, other) if len(self) > len(other) else (other, self) + return List([item * max_list[i] for i, item in enumerate(min_list)], use_comma=self.use_comma) + + return List([item * other for item in self], use_comma=self.use_comma) + + def __div__(self, other): + if isinstance(other, List): + max_list, min_list = (self, other) if len(self) > len(other) else (other, self) + return List([item / max_list[i] for i, item in enumerate(min_list)], use_comma=self.use_comma) + + return List([item / other for item in self], use_comma=self.use_comma) + + def __pos__(self): + return self + + def __neg__(self): + return List([-item for item in self], use_comma=self.use_comma) + + +def _constrain(value, lb=0, ub=1): + """Helper for Color constructors. Constrains a value to a range.""" + if value < lb: + return lb + elif value > ub: + return ub + else: + return value + + +class Color(Value): + sass_type_name = u'color' + original_literal = None + + def __init__(self, tokens): + self.tokens = tokens + self.value = (0, 0, 0, 1) + if tokens is None: + self.value = (0, 0, 0, 1) + elif isinstance(tokens, Color): + self.value = tokens.value + else: + raise TypeError("Can't make Color from %r" % (tokens,)) + + ### Alternate constructors + + @classmethod + def from_rgb(cls, red, green, blue, alpha=1.0, original_literal=None): + red = _constrain(red) + green = _constrain(green) + blue = _constrain(blue) + alpha = _constrain(alpha) + + self = cls.__new__(cls) # TODO + self.tokens = None + # TODO really should store these things internally as 0-1, but can't + # until stuff stops examining .value directly + self.value = (red * 255.0, green * 255.0, blue * 255.0, alpha) + + if original_literal is not None: + self.original_literal = original_literal + + return self + + @classmethod + def from_hsl(cls, hue, saturation, lightness, alpha=1.0): + hue = _constrain(hue) + saturation = _constrain(saturation) + lightness = _constrain(lightness) + alpha = _constrain(alpha) + + r, g, b = colorsys.hls_to_rgb(hue, lightness, saturation) + return cls.from_rgb(r, g, b, alpha) + + @classmethod + def from_hex(cls, hex_string, literal=False): + if not hex_string.startswith('#'): + raise ValueError("Expected #abcdef, got %r" % (hex_string,)) + + if literal: + original_literal = hex_string + else: + original_literal = None + + hex_string = hex_string[1:] + + # Always include the alpha channel + if len(hex_string) == 3: + hex_string += 'f' + elif len(hex_string) == 6: + hex_string += 'ff' + + # Now there should be only two possibilities. Normalize to a list of + # two hex digits + if len(hex_string) == 4: + chunks = [ch * 2 for ch in hex_string] + elif len(hex_string) == 8: + chunks = [ + hex_string[0:2], hex_string[2:4], hex_string[4:6], hex_string[6:8] + ] + + rgba = [int(ch, 16) / 255 for ch in chunks] + return cls.from_rgb(*rgba, original_literal=original_literal) + + @classmethod + def from_name(cls, name): + """Build a Color from a CSS color name.""" + self = cls.__new__(cls) # TODO + self.original_literal = name + + r, g, b, a = COLOR_NAMES[name] + + self.value = r, g, b, a + return self + + ### Accessors + + @property + def rgb(self): + # TODO: deprecate, relies on internals + return tuple(self.value[:3]) + + @property + def rgba(self): + return ( + self.value[0] / 255, + self.value[1] / 255, + self.value[2] / 255, + self.value[3], + ) + + @property + def hsl(self): + rgba = self.rgba + h, l, s = colorsys.rgb_to_hls(*rgba[:3]) + return h, s, l + + @property + def alpha(self): + return self.value[3] + + @property + def rgba255(self): + return ( + int(self.value[0] * 1 + 0.5), + int(self.value[1] * 1 + 0.5), + int(self.value[2] * 1 + 0.5), + int(self.value[3] * 255 + 0.5), + ) + + def __repr__(self): + return '<%s(%s)>' % (self.__class__.__name__, repr(self.value)) + + def __hash__(self): + return hash(self.value) + + def __eq__(self, other): + if not isinstance(other, Color): + return Boolean(False) + + # Scale channels to 255 and round to integers; this allows only 8-bit + # color, but Ruby sass makes the same assumption, and otherwise it's + # easy to get lots of float errors for HSL colors. + left = tuple(round(n) for n in self.rgba255) + right = tuple(round(n) for n in other.rgba255) + return Boolean(left == right) + + def __add__(self, other): + if isinstance(other, (Color, Number)): + return self._operate(other, operator.add) + else: + return super(Color, self).__add__(other) + + def __sub__(self, other): + if isinstance(other, (Color, Number)): + return self._operate(other, operator.sub) + else: + return super(Color, self).__sub__(other) + + def __mul__(self, other): + if isinstance(other, (Color, Number)): + return self._operate(other, operator.mul) + else: + return super(Color, self).__mul__(other) + + def __div__(self, other): + if isinstance(other, (Color, Number)): + return self._operate(other, operator.div) + else: + return super(Color, self).__div__(other) + + def _operate(self, other, op): + if isinstance(other, Number): + if not other.is_unitless: + raise ValueError("Expected unitless Number, got %r" % (other,)) + + other_rgb = (other.value,) * 3 + elif isinstance(other, Color): + if self.alpha != other.alpha: + raise ValueError("Alpha channels must match between %r and %r" + % (self, other)) + + other_rgb = other.rgb + else: + raise TypeError("Expected Color or Number, got %r" % (other,)) + + new_rgb = [ + min(255., max(0., op(left, right))) + # for from_rgb + / 255. + for (left, right) in zip(self.rgb, other_rgb) + ] + + return Color.from_rgb(*new_rgb, alpha=self.alpha) + + def render(self, compress=False): + """Return a rendered representation of the color. If `compress` is + true, the shortest possible representation is used; otherwise, named + colors are rendered as names and all others are rendered as hex (or + with the rgba function). + """ + + if not compress and self.original_literal: + return self.original_literal + + candidates = [] + + # TODO this assumes CSS resolution is 8-bit per channel, but so does + # Ruby. + r, g, b, a = self.value + r, g, b = int(round(r)), int(round(g)), int(round(b)) + + # Build a candidate list in order of preference. If `compress` is + # True, the shortest candidate is used; otherwise, the first candidate + # is used. + + # Try color name + key = r, g, b, a + if key in COLOR_LOOKUP: + candidates.append(COLOR_LOOKUP[key]) + + if a == 1: + # Hex is always shorter than function notation + if all(ch % 17 == 0 for ch in (r, g, b)): + candidates.append("#%1x%1x%1x" % (r // 17, g // 17, b // 17)) + else: + candidates.append("#%02x%02x%02x" % (r, g, b)) + else: + # Can't use hex notation for RGBA + if compress: + sp = '' + else: + sp = ' ' + candidates.append("rgba(%d,%s%d,%s%d,%s%.2g)" % (r, sp, g, sp, b, sp, a)) + + if compress: + return min(candidates, key=len) + else: + return candidates[0] + + +# TODO be unicode-clean and delete this nonsense +DEFAULT_STRING_ENCODING = "utf8" + + +class String(Value): + """Represents both CSS quoted string values and CSS identifiers (such as + `left`). + + Makes no distinction between single and double quotes, except that the same + quotes are preserved on string literals that pass through unmodified. + Otherwise, double quotes are used. + """ + + sass_type_name = u'string' + + def __init__(self, value, quotes='"'): + if isinstance(value, String): + # TODO unclear if this should be here, but many functions rely on + # it + value = value.value + elif isinstance(value, Number): + # TODO this may only be necessary in the case of __radd__ and + # number values + value = str(value) + + if isinstance(value, six.binary_type): + value = value.decode(DEFAULT_STRING_ENCODING) + + if not isinstance(value, six.text_type): + raise TypeError("Expected string, got {0!r}".format(value)) + + # TODO probably disallow creating an unquoted string outside a + # set of chars like [-a-zA-Z0-9]+ + + if six.PY3: + self.value = value + else: + # TODO well, at least 3 uses unicode everywhere + self.value = value.encode(DEFAULT_STRING_ENCODING) + self.quotes = quotes + + @classmethod + def unquoted(cls, value): + """Helper to create a string with no quotes.""" + return cls(value, quotes=None) + + def __hash__(self): + return hash(self.value) + + def __str__(self): + if self.quotes: + return self.quotes + escape(self.value) + self.quotes + else: + return self.value + + def __repr__(self): + if self.quotes != '"': + quotes = ', quotes=%r' % self.quotes + else: + quotes = '' + return '<%s(%s%s)>' % (self.__class__.__name__, repr(self.value), quotes) + + def __eq__(self, other): + return Boolean(isinstance(other, String) and self.value == other.value) + + def __add__(self, other): + if isinstance(other, String): + other_value = other.value + else: + other_value = other.render() + + return String( + self.value + other_value, + quotes='"' if self.quotes else None) + + def __mul__(self, other): + # DEVIATION: Ruby Sass doesn't do this, because Ruby doesn't. But + # Python does, and in Ruby Sass it's just fatal anyway. + if not isinstance(other, Number): + return super(String, self).__mul__(other) + + if not other.is_unitless: + raise TypeError("Can only multiply strings by unitless numbers") + + n = other.value + if n != int(n): + raise ValueError("Can only multiply strings by integers") + + return String(self.value * int(other.value), quotes=self.quotes) + + def render(self, compress=False): + return self.__str__() + + +class Map(Value): + sass_type_name = u'map' + + def __init__(self, pairs, index=None): + self.pairs = tuple(pairs) + + if index is None: + self.index = {} + for key, value in pairs: + self.index[key] = value + else: + self.index = index + + def __repr__(self): + return "" % (", ".join("%s: %s" % pair for pair in self.pairs),) + + def __hash__(self): + return hash(self.pairs) + + def __len__(self): + return len(self.pairs) + + def __iter__(self): + return iter(self.pairs) + + def __getitem__(self, index): + return List(self.pairs[index], use_comma=True) + + def __eq__(self, other): + try: + return self.pairs == other.to_pairs() + except ValueError: + return NotImplemented + + def to_dict(self): + return self.index + + def to_pairs(self): + return self.pairs + + def render(self, compress=False): + raise TypeError("Cannot render map %r as CSS" % (self,)) + + +def expect_type(value, types, unit=any): + if not isinstance(value, types): + if isinstance(types, type): + types = (type,) + sass_type_names = list(set(t.sass_type_name for t in types)) + sass_type_names.sort() + + # Join with commas in English fashion + if len(sass_type_names) == 1: + sass_type = sass_type_names[0] + elif len(sass_type_names) == 2: + sass_type = u' or '.join(sass_type_names) + else: + sass_type = u', '.join(sass_type_names[:-1]) + sass_type += u', or ' + sass_type_names[-1] + + raise TypeError("Expected %s, got %r" % (sass_type, value)) + + if unit is not any and isinstance(value, Number): + if unit is None and not value.is_unitless: + raise ValueError("Expected unitless number, got %r" % (value,)) + + elif unit == '%' and not ( + value.is_unitless or value.is_simple_unit('%')): + raise ValueError("Expected unitless number or percentage, got %r" % (value,)) diff --git a/libs/scss/util.py b/libs/scss/util.py new file mode 100644 index 0000000..0bcb13e --- /dev/null +++ b/libs/scss/util.py @@ -0,0 +1,146 @@ +from __future__ import absolute_import +from __future__ import print_function + +import re +import sys +import time + +import six + +from scss import config + + +def split_params(params): + params = params.split(',') or [] + if params: + final_params = [] + param = params.pop(0) + try: + while True: + while param.count('(') != param.count(')'): + try: + param = param + ',' + params.pop(0) + except IndexError: + break + final_params.append(param) + param = params.pop(0) + except IndexError: + pass + params = final_params + return params + + +def dequote(s): + if s and s[0] in ('"', "'") and s[-1] == s[0]: + s = s[1:-1] + s = unescape(s) + return s + + +def depar(s): + while s and s[0] == '(' and s[-1] == ')': + s = s[1:-1] + return s + + +def to_str(num): + try: + render = num.render + except AttributeError: + pass + else: + return render() + + if isinstance(num, dict): + s = sorted(num.items()) + sp = num.get('_', '') + return (sp + ' ').join(to_str(v) for n, v in s if n != '_') + elif isinstance(num, float): + num = ('%0.05f' % round(num, 5)).rstrip('0').rstrip('.') + return num + elif isinstance(num, bool): + return 'true' if num else 'false' + elif num is None: + return '' + return str(num) + + +def to_float(num): + if isinstance(num, (float, int)): + return float(num) + num = to_str(num) + if num and num[-1] == '%': + return float(num[:-1]) / 100.0 + else: + return float(num) + + +def escape(s): + return re.sub(r'''(["'])''', r'\\\1', s) + + +def unescape(s): + return re.sub(r'''\\(['"])''', r'\1', s) + + +def normalize_var(var): + """Sass defines `foo_bar` and `foo-bar` as being identical, both in + variable names and functions/mixins. This normalizes everything to use + dashes. + """ + return var.replace('_', '-') + + +################################################################################ +# Function timing decorator +profiling = {} + + +def print_timing(level=0): + def _print_timing(func): + if config.VERBOSITY: + def wrapper(*args, **kwargs): + if config.VERBOSITY >= level: + t1 = time.time() + res = func(*args, **kwargs) + t2 = time.time() + profiling.setdefault(func.func_name, 0) + profiling[func.func_name] += (t2 - t1) + return res + else: + return func(*args, **kwargs) + return wrapper + else: + return func + return _print_timing + + +################################################################################ +# Profiler decorator +def profile(fn): + import cProfile + import pstats + def wrapper(*args, **kwargs): + profiler = cProfile.Profile() + stream = six.StringIO() + profiler.enable() + try: + res = fn(*args, **kwargs) + finally: + profiler.disable() + stats = pstats.Stats(profiler, stream=stream) + stats.sort_stats('time') + print >>stream, "" + print >>stream, "=" * 100 + print >>stream, "Stats:" + stats.print_stats() + print >>stream, "=" * 100 + print >>stream, "Callers:" + stats.print_callers() + print >>stream, "=" * 100 + print >>stream, "Callees:" + stats.print_callees() + print >>sys.stderr, stream.getvalue() + stream.close() + return res + return wrapper From 4776cef473b9f32ee8e46e360aa6208151ce74f6 Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 9 Apr 2014 16:08:10 +0200 Subject: [PATCH 002/301] Reinit css --- couchpotato/static/style/fonts.scss | 69 +++++++++++++++++++++++++++++++++++++ couchpotato/static/style/main.scss | 19 ++++++++++ couchpotato/templates/index.html | 8 ++--- couchpotato/templates/login.html | 14 ++++---- 4 files changed, 99 insertions(+), 11 deletions(-) create mode 100644 couchpotato/static/style/fonts.scss create mode 100644 couchpotato/static/style/main.scss diff --git a/couchpotato/static/style/fonts.scss b/couchpotato/static/style/fonts.scss new file mode 100644 index 0000000..ae592a7 --- /dev/null +++ b/couchpotato/static/style/fonts.scss @@ -0,0 +1,69 @@ + +/* Fonts */ +@font-face { + font-family: 'Elusive-Icons'; + src:url('../fonts/Elusive-Icons.eot'); + src:url('../fonts/Elusive-Icons.eot?#iefix') format('embedded-opentype'), + url('../fonts/Elusive-Icons.woff') format('woff'), + url('../fonts/Elusive-Icons.ttf') format('truetype'), + url('../fonts/Elusive-Icons.svg#Elusive-Icons') format('svg'); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: 'OpenSans'; + src: url('../fonts/OpenSans-Regular-webfont.eot'); + src: url('../fonts/OpenSans-Regular-webfont.eot?#iefix') format('embedded-opentype'), + url('../fonts/OpenSans-Regular-webfont.woff') format('woff'), + url('../fonts/OpenSans-Regular-webfont.ttf') format('truetype'), + url('../fonts/OpenSans-Regular-webfont.svg#OpenSansRegular') format('svg'); + font-weight: normal; + font-style: normal; + +} + +@font-face { + font-family: 'OpenSans'; + src: url('../fonts/OpenSans-Italic-webfont.eot'); + src: url('../fonts/OpenSans-Italic-webfont.eot?#iefix') format('embedded-opentype'), + url('../fonts/OpenSans-Italic-webfont.woff') format('woff'), + url('../fonts/OpenSans-Italic-webfont.ttf') format('truetype'), + url('../fonts/OpenSans-Italic-webfont.svg#OpenSansItalic') format('svg'); + font-weight: normal; + font-style: italic; + +} + +@font-face { + font-family: 'OpenSans'; + src: url('../fonts/OpenSans-Bold-webfont.eot'); + src: url('../fonts/OpenSans-Bold-webfont.eot?#iefix') format('embedded-opentype'), + url('../fonts/OpenSans-Bold-webfont.woff') format('woff'), + url('../fonts/OpenSans-Bold-webfont.ttf') format('truetype'), + url('../fonts/OpenSans-Bold-webfont.svg#OpenSansBold') format('svg'); + font-weight: bold; + font-style: normal; +} + +@font-face { + font-family: 'OpenSans'; + src: url('../fonts/OpenSans-BoldItalic-webfont.eot'); + src: url('../fonts/OpenSans-BoldItalic-webfont.eot?#iefix') format('embedded-opentype'), + url('../fonts/OpenSans-BoldItalic-webfont.woff') format('woff'), + url('../fonts/OpenSans-BoldItalic-webfont.ttf') format('truetype'), + url('../fonts/OpenSans-BoldItalic-webfont.svg#OpenSansBoldItalic') format('svg'); + font-weight: bold; + font-style: italic; +} + +@font-face { + font-family: 'Lobster'; + src: url('../fonts/Lobster-webfont.eot'); + src: url('../fonts/Lobster-webfont.eot?#iefix') format('embedded-opentype'), + url('../fonts/Lobster-webfont.woff') format('woff'), + url('../fonts/Lobster-webfont.ttf') format('truetype'), + url('../fonts/Lobster-webfont.svg#lobster_1.4regular') format('svg'); + font-weight: normal; + font-style: normal; +} diff --git a/couchpotato/static/style/main.scss b/couchpotato/static/style/main.scss new file mode 100644 index 0000000..889a2b0 --- /dev/null +++ b/couchpotato/static/style/main.scss @@ -0,0 +1,19 @@ +@import "fonts"; + +body, html { + font-size: 12px; + line-height: 1.5; + font-family: OpenSans, "Helvetica Neue", Helvetica, Arial, Geneva, sans-serif; + height: 100%; + margin: 0; + padding: 0; + background: #FFF; + -webkit-font-smoothing: subpixel-antialiased; + + &.noscroll { + overflow: hidden; + } +} + +body { overflow-y: scroll; } + diff --git a/couchpotato/templates/index.html b/couchpotato/templates/index.html index b432800..211bcf6 100644 --- a/couchpotato/templates/index.html +++ b/couchpotato/templates/index.html @@ -7,14 +7,14 @@ - {% for url in fireEvent('clientscript.get_styles', as_html = True, location = 'front', single = True) %} + {% for url in fireEvent('clientscript.get_styles', location = 'front', single = True) %} {% end %} - {% for url in fireEvent('clientscript.get_scripts', as_html = True, location = 'front', single = True) %} + {% for url in fireEvent('clientscript.get_scripts', location = 'front', single = True) %} {% end %} - {% for url in fireEvent('clientscript.get_scripts', as_html = True, location = 'head', single = True) %} + {% for url in fireEvent('clientscript.get_scripts', location = 'head', single = True) %} {% end %} - {% for url in fireEvent('clientscript.get_styles', as_html = True, location = 'head', single = True) %} + {% for url in fireEvent('clientscript.get_styles', location = 'head', single = True) %} {% end %} diff --git a/couchpotato/templates/login.html b/couchpotato/templates/login.html index 3562622..2b19648 100644 --- a/couchpotato/templates/login.html +++ b/couchpotato/templates/login.html @@ -5,21 +5,21 @@ - {% for url in fireEvent('clientscript.get_styles', as_html = True, location = 'front', single = True) %} + {% for url in fireEvent('clientscript.get_styles', location = 'front', single = True) %} {% end %} - - {% for url in fireEvent('clientscript.get_scripts', as_html = True, location = 'front', single = True) %} + + {% for url in fireEvent('clientscript.get_scripts', location = 'front', single = True) %} {% end %} - + CouchPotato @@ -35,4 +35,4 @@ - \ No newline at end of file + From 6ea49405f4e76dcb5520b9d8caeda8dbb2dd9dca Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 9 Apr 2014 19:29:30 +0200 Subject: [PATCH 003/301] Make clientside ordered --- couchpotato/core/_base/clientscript.py | 26 +++++++++++++--------- .../core/media/_base/search/static/search.js | 4 ++-- couchpotato/core/media/movie/_base/static/list.js | 4 ++-- .../core/media/movie/_base/static/search.js | 2 +- .../core/media/movie/charts/static/charts.js | 2 +- .../core/media/movie/suggestion/static/suggest.js | 2 +- .../core/notifications/core/static/notification.js | 2 +- .../core/plugins/userscript/static/userscript.js | 2 +- couchpotato/static/scripts/block.js | 2 -- couchpotato/static/scripts/block/footer.js | 4 ++-- couchpotato/static/scripts/block/menu.js | 4 ++-- couchpotato/static/scripts/block/navigation.js | 8 +++---- couchpotato/static/scripts/couchpotato.js | 10 ++++----- 13 files changed, 38 insertions(+), 34 deletions(-) diff --git a/couchpotato/core/_base/clientscript.py b/couchpotato/core/_base/clientscript.py index 58d1ffd..c71185b 100644 --- a/couchpotato/core/_base/clientscript.py +++ b/couchpotato/core/_base/clientscript.py @@ -130,7 +130,9 @@ class ClientScript(Plugin): data_combined = '' raw = [] - for file_path in paths: + new_paths = [] + for x in paths: + file_path, urls = x f = open(file_path, 'r').read() @@ -145,13 +147,14 @@ class ClientScript(Plugin): if Env.get('dev'): self.watcher.watch(file_path, self.compile) - url_path = paths[file_path].get('original_url') + url_path = urls.get('original_url') compiled_file_name = position + '_%s.css' % url_path.replace('/', '_').split('.scss')[0] compiled_file_path = os.path.join(minified_dir, compiled_file_name) self.createFile(compiled_file_path, f.strip()) # Remove scss path - paths[file_path]['url'] = 'minified/%s?%s' % (compiled_file_name, tryInt(time.time())) + urls['url'] = 'minified/%s?%s' % (compiled_file_name, tryInt(time.time())) + new_paths.append((file_path, urls)) if not Env.get('dev'): @@ -165,16 +168,19 @@ class ClientScript(Plugin): data_combined += self.comment.get(file_type) % (ss(file_path), int(os.path.getmtime(file_path))) data_combined += data + '\n\n' - - del paths[file_path] + else: + new_paths.append(x) # Combine all files together with some comments if not Env.get('dev'): - self.createFile(os.path.join(minified_dir, out_name), data_combined.strip()) + out_path = os.path.join(minified_dir, out_name) + self.createFile(out_path, data_combined.strip()) minified_url = 'minified/%s?%s' % (out_name, tryInt(os.path.getmtime(out))) - self.minified[file_type][position].append(minified_url) + new_paths.append((out_path, {'url': minified_url})) + + self.paths[file_type][position] = new_paths def getStyles(self, *args, **kwargs): return self.get('style', *args, **kwargs) @@ -184,7 +190,7 @@ class ClientScript(Plugin): def get(self, type, location = 'head'): paths = self.paths[type][location] - return [paths[x].get('url', paths[x].get('original_url')) for x in paths] + return [x[1].get('url', x[1].get('original_url')) for x in paths] def registerStyle(self, api_path, file_path, position = 'head'): self.register(api_path, file_path, 'style', position) @@ -197,8 +203,8 @@ class ClientScript(Plugin): api_path = '%s?%s' % (api_path, tryInt(os.path.getmtime(file_path))) if not self.paths[type].get(location): - self.paths[type][location] = {} - self.paths[type][location][file_path] = {'original_url': api_path} + self.paths[type][location] = [] + self.paths[type][location].append((file_path, {'original_url': api_path})) prefix_properties = ['border-radius', 'transform', 'transition', 'box-shadow'] prefix_tags = ['ms', 'moz', 'webkit'] diff --git a/couchpotato/core/media/_base/search/static/search.js b/couchpotato/core/media/_base/search/static/search.js index 1892ad1..8b8f2a5 100644 --- a/couchpotato/core/media/_base/search/static/search.js +++ b/couchpotato/core/media/_base/search/static/search.js @@ -1,4 +1,4 @@ -Block.Search = new Class({ +var BlockSearch = new Class({ Extends: BlockBase, @@ -157,7 +157,7 @@ Block.Search = new Class({ if(typeOf(media) == 'array'){ Object.each(media, function(m){ - var m = new Block.Search[m.type.capitalize() + 'Item'](m); + var m = new BlockSearch[m.type.capitalize() + 'Item'](m); $(m).inject(self.results); self.media[m.imdb || 'r-'+Math.floor(Math.random()*10000)] = m; diff --git a/couchpotato/core/media/movie/_base/static/list.js b/couchpotato/core/media/movie/_base/static/list.js index 9120750..9310ce1 100644 --- a/couchpotato/core/media/movie/_base/static/list.js +++ b/couchpotato/core/media/movie/_base/static/list.js @@ -230,7 +230,7 @@ var MovieList = new Class({ ), new Element('div.menus').adopt( self.navigation_counter = new Element('span.counter[title=Total]'), - self.filter_menu = new Block.Menu(self, { + self.filter_menu = new BlockMenu(self, { 'class': 'filter' }), self.navigation_actions = new Element('ul.actions', { @@ -249,7 +249,7 @@ var MovieList = new Class({ } } }), - self.navigation_menu = new Block.Menu(self, { + self.navigation_menu = new BlockMenu(self, { 'class': 'extra' }) ) diff --git a/couchpotato/core/media/movie/_base/static/search.js b/couchpotato/core/media/movie/_base/static/search.js index 3b42676..0fa1741 100644 --- a/couchpotato/core/media/movie/_base/static/search.js +++ b/couchpotato/core/media/movie/_base/static/search.js @@ -1,4 +1,4 @@ -Block.Search.MovieItem = new Class({ +var BlockSearchMovieItem = new Class({ Implements: [Options, Events], diff --git a/couchpotato/core/media/movie/charts/static/charts.js b/couchpotato/core/media/movie/charts/static/charts.js index 00033f4..079eabf 100644 --- a/couchpotato/core/media/movie/charts/static/charts.js +++ b/couchpotato/core/media/movie/charts/static/charts.js @@ -83,7 +83,7 @@ var Charts = new Class({ Object.each(chart.list, function(movie){ - var m = new Block.Search.MovieItem(movie, { + var m = new BlockSearchMovieItem(movie, { 'onAdded': function(){ self.afterAdded(m, movie) } diff --git a/couchpotato/core/media/movie/suggestion/static/suggest.js b/couchpotato/core/media/movie/suggestion/static/suggest.js index 494f045..5156337 100644 --- a/couchpotato/core/media/movie/suggestion/static/suggest.js +++ b/couchpotato/core/media/movie/suggestion/static/suggest.js @@ -64,7 +64,7 @@ var SuggestList = new Class({ Object.each(json.suggestions, function(movie){ - var m = new Block.Search.MovieItem(movie, { + var m = new BlockSearchMovieItem(movie, { 'onAdded': function(){ self.afterAdded(m, movie) } diff --git a/couchpotato/core/notifications/core/static/notification.js b/couchpotato/core/notifications/core/static/notification.js index 81bf950..3b2bc61 100644 --- a/couchpotato/core/notifications/core/static/notification.js +++ b/couchpotato/core/notifications/core/static/notification.js @@ -20,7 +20,7 @@ var NotificationBase = new Class({ self.notifications = []; App.addEvent('load', function(){ - App.block.notification = new Block.Menu(self, { + App.block.notification = new BlockMenu(self, { 'button_class': 'icon2.eye-open', 'class': 'notification_menu', 'onOpen': self.markAsRead.bind(self) diff --git a/couchpotato/core/plugins/userscript/static/userscript.js b/couchpotato/core/plugins/userscript/static/userscript.js index 2ef1ea6..71543d5 100644 --- a/couchpotato/core/plugins/userscript/static/userscript.js +++ b/couchpotato/core/plugins/userscript/static/userscript.js @@ -34,7 +34,7 @@ Page.Userscript = new Class({ if(json.error) self.frame.set('html', json.error); else { - var item = new Block.Search.MovieItem(json.movie); + var item = new BlockSearchMovieItem(json.movie); self.frame.adopt(item); item.showOptions(); } diff --git a/couchpotato/static/scripts/block.js b/couchpotato/static/scripts/block.js index 7407b7f..487c384 100644 --- a/couchpotato/static/scripts/block.js +++ b/couchpotato/static/scripts/block.js @@ -35,5 +35,3 @@ var BlockBase = new Class({ } }); - -var Block = BlockBase; \ No newline at end of file diff --git a/couchpotato/static/scripts/block/footer.js b/couchpotato/static/scripts/block/footer.js index acec158..619922f 100644 --- a/couchpotato/static/scripts/block/footer.js +++ b/couchpotato/static/scripts/block/footer.js @@ -1,4 +1,4 @@ -Block.Footer = new Class({ +var BlockFooter = new Class({ Extends: BlockBase, @@ -8,4 +8,4 @@ Block.Footer = new Class({ self.el = new Element('div.footer'); } -}); \ No newline at end of file +}); diff --git a/couchpotato/static/scripts/block/menu.js b/couchpotato/static/scripts/block/menu.js index 91e29a2..b36c4e8 100644 --- a/couchpotato/static/scripts/block/menu.js +++ b/couchpotato/static/scripts/block/menu.js @@ -1,4 +1,4 @@ -Block.Menu = new Class({ +var BlockMenu = new Class({ Extends: BlockBase, @@ -52,4 +52,4 @@ Block.Menu = new Class({ 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/block/navigation.js b/couchpotato/static/scripts/block/navigation.js index f5642df..a9e9f51 100644 --- a/couchpotato/static/scripts/block/navigation.js +++ b/couchpotato/static/scripts/block/navigation.js @@ -1,4 +1,4 @@ -Block.Navigation = new Class({ +var BlockNavigation = new Class({ Extends: BlockBase, @@ -38,7 +38,7 @@ Block.Navigation = new Class({ self.backtotop.fade('in') } }); - + self.nav.addEvents({ 'click:relay(a)': function(){ if($(document.body).getParent().hasClass('menu_shown')) @@ -70,7 +70,7 @@ Block.Navigation = new Class({ if(nr <= 2) return; if(el.get('tag') == 'a') self.nav.grab(new Element('li').grab(el.clone().cloneEvents(el))); - else + else self.nav.grab(new Element('li.separator')); }); @@ -89,4 +89,4 @@ Block.Navigation = new Class({ } -}); \ No newline at end of file +}); diff --git a/couchpotato/static/scripts/couchpotato.js b/couchpotato/static/scripts/couchpotato.js index ffc0e9a..f6f7db8 100644 --- a/couchpotato/static/scripts/couchpotato.js +++ b/couchpotato/static/scripts/couchpotato.js @@ -69,18 +69,18 @@ createLayout: function(){ var self = this; - self.block.header = new Block(); + self.block.header = new BlockBase(); self.c.adopt( $(self.block.header).addClass('header').adopt( new Element('div').adopt( - self.block.navigation = new Block.Navigation(self, {}), - self.block.search = new Block.Search(self, {}), - self.block.more = new Block.Menu(self, {'button_class': 'icon2.cog'}) + self.block.navigation = new BlockNavigation(self, {}), + self.block.search = new BlockSearch(self, {}), + self.block.more = new BlockMenu(self, {'button_class': 'icon2.cog'}) ) ), self.content = new Element('div.content'), - self.block.footer = new Block.Footer(self, {}) + self.block.footer = new BlockFooter(self, {}) ); var setting_links = [ From 85163443e3a58b4e7fa4ce65bea6f2ab1e10730f Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 9 Apr 2014 19:54:01 +0200 Subject: [PATCH 004/301] Re-use original paths --- couchpotato/core/_base/clientscript.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/couchpotato/core/_base/clientscript.py b/couchpotato/core/_base/clientscript.py index c71185b..5f153a4 100644 --- a/couchpotato/core/_base/clientscript.py +++ b/couchpotato/core/_base/clientscript.py @@ -55,6 +55,7 @@ class ClientScript(Plugin): watcher = None + original_paths = {'style': {}, 'script': {}} paths = {'style': {}, 'script': {}} comment = { 'style': '/*** %s:%d ***/\n', @@ -116,7 +117,7 @@ class ClientScript(Plugin): for file_type in ['style', 'script']: ext = 'js' if file_type is 'script' else 'css' - positions = self.paths.get(file_type, {}) + positions = self.original_paths.get(file_type, {}) for position in positions: files = positions.get(position) self._compile(file_type, files, position, position + '.' + ext) @@ -132,7 +133,7 @@ class ClientScript(Plugin): raw = [] new_paths = [] for x in paths: - file_path, urls = x + file_path, url_path = x f = open(file_path, 'r').read() @@ -147,14 +148,12 @@ class ClientScript(Plugin): if Env.get('dev'): self.watcher.watch(file_path, self.compile) - url_path = urls.get('original_url') compiled_file_name = position + '_%s.css' % url_path.replace('/', '_').split('.scss')[0] compiled_file_path = os.path.join(minified_dir, compiled_file_name) self.createFile(compiled_file_path, f.strip()) # Remove scss path - urls['url'] = 'minified/%s?%s' % (compiled_file_name, tryInt(time.time())) - new_paths.append((file_path, urls)) + x = (file_path, 'minified/%s?%s' % (compiled_file_name, tryInt(time.time()))) if not Env.get('dev'): @@ -190,7 +189,7 @@ class ClientScript(Plugin): def get(self, type, location = 'head'): paths = self.paths[type][location] - return [x[1].get('url', x[1].get('original_url')) for x in paths] + return [x[1] for x in paths] def registerStyle(self, api_path, file_path, position = 'head'): self.register(api_path, file_path, 'style', position) @@ -202,9 +201,13 @@ class ClientScript(Plugin): api_path = '%s?%s' % (api_path, tryInt(os.path.getmtime(file_path))) + if not self.original_paths[type].get(location): + self.original_paths[type][location] = [] + self.original_paths[type][location].append((file_path, api_path)) + if not self.paths[type].get(location): self.paths[type][location] = [] - self.paths[type][location].append((file_path, {'original_url': api_path})) + self.paths[type][location].append((file_path, api_path)) prefix_properties = ['border-radius', 'transform', 'transition', 'box-shadow'] prefix_tags = ['ms', 'moz', 'webkit'] From db23f5cdef50afa7b44bd6372fb6594eaf995fda Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 5 May 2014 20:40:55 +0200 Subject: [PATCH 005/301] Update --- .../core/media/_base/providers/torrent/bitsoup.py | 9 +-- .../core/media/_base/search/static/search.css | 4 - .../core/media/movie/_base/static/1_wanted.js | 2 +- .../core/media/movie/_base/static/2_manage.js | 2 +- .../core/media/movie/providers/torrent/bitsoup.py | 8 +- couchpotato/static/scripts/block/navigation.js | 2 +- couchpotato/static/scripts/couchpotato.js | 16 ++-- couchpotato/static/style/fonts.scss | 72 +++++++++-------- couchpotato/static/style/main.scss | 90 ++++++++++++++++++++-- 9 files changed, 141 insertions(+), 64 deletions(-) diff --git a/couchpotato/core/media/_base/providers/torrent/bitsoup.py b/couchpotato/core/media/_base/providers/torrent/bitsoup.py index 9aeb624..1c762b7 100644 --- a/couchpotato/core/media/_base/providers/torrent/bitsoup.py +++ b/couchpotato/core/media/_base/providers/torrent/bitsoup.py @@ -1,7 +1,6 @@ import traceback from bs4 import BeautifulSoup -from couchpotato.core.helpers.encoding import simplifyString, tryUrlencode from couchpotato.core.helpers.variable import tryInt from couchpotato.core.logger import CPLog from couchpotato.core.media._base.providers.torrent.base import TorrentProvider @@ -24,13 +23,7 @@ class Base(TorrentProvider): def _searchOnTitle(self, title, movie, quality, results): - q = '"%s" %s' % (simplifyString(title), movie['info']['year']) - arguments = tryUrlencode({ - 'search': q, - }) - url = "%s&%s" % (self.urls['search'], arguments) - - url = self.urls['search'] % self.buildUrl(movie, quality) + url = self.urls['search'] % self.buildUrl(title, movie, quality) data = self.getHTMLData(url) if data: diff --git a/couchpotato/core/media/_base/search/static/search.css b/couchpotato/core/media/_base/search/static/search.css index 2defe24..eb8b66c 100644 --- a/couchpotato/core/media/_base/search/static/search.css +++ b/couchpotato/core/media/_base/search/static/search.css @@ -1,11 +1,7 @@ .search_form { display: inline-block; vertical-align: middle; - position: absolute; - right: 105px; - top: 0; text-align: right; - height: 100%; transition: all .4s cubic-bezier(0.9,0,0.1,1); position: absolute; z-index: 20; diff --git a/couchpotato/core/media/movie/_base/static/1_wanted.js b/couchpotato/core/media/movie/_base/static/1_wanted.js index ca57f95..eee156d 100644 --- a/couchpotato/core/media/movie/_base/static/1_wanted.js +++ b/couchpotato/core/media/movie/_base/static/1_wanted.js @@ -1,4 +1,4 @@ -Page.Wanted = new Class({ +Page.Movies.Wanted = new Class({ Extends: PageBase, diff --git a/couchpotato/core/media/movie/_base/static/2_manage.js b/couchpotato/core/media/movie/_base/static/2_manage.js index be99a84..9383c72 100644 --- a/couchpotato/core/media/movie/_base/static/2_manage.js +++ b/couchpotato/core/media/movie/_base/static/2_manage.js @@ -1,4 +1,4 @@ -Page.Manage = new Class({ +Page.Movies.Manage = new Class({ Extends: PageBase, diff --git a/couchpotato/core/media/movie/providers/torrent/bitsoup.py b/couchpotato/core/media/movie/providers/torrent/bitsoup.py index c351ab9..e9d69fe 100644 --- a/couchpotato/core/media/movie/providers/torrent/bitsoup.py +++ b/couchpotato/core/media/movie/providers/torrent/bitsoup.py @@ -1,6 +1,5 @@ from couchpotato.core.helpers.encoding import tryUrlencode from couchpotato.core.logger import CPLog -from couchpotato.core.event import fireEvent from couchpotato.core.media._base.providers.torrent.bitsoup import Base from couchpotato.core.media.movie.providers.base import MovieProvider @@ -18,12 +17,9 @@ class Bitsoup(MovieProvider, Base): ] cat_backup_id = 0 - def buildUrl(self, media, quality): + def buildUrl(self, title, media, quality): query = tryUrlencode({ - 'search': '"%s" %s' % ( - fireEvent('library.query', media, include_year = False, single = True), - media['info']['year'] - ), + 'search': '"%s" %s' % (title, media['info']['year']), 'cat': self.getCatId(quality)[0], }) return query diff --git a/couchpotato/static/scripts/block/navigation.js b/couchpotato/static/scripts/block/navigation.js index a9e9f51..d1b332a 100644 --- a/couchpotato/static/scripts/block/navigation.js +++ b/couchpotato/static/scripts/block/navigation.js @@ -12,7 +12,7 @@ var BlockNavigation = new Class({ } }).grab(new Element('span.overlay')), self.logo = new Element('a.logo', { - 'text': 'CouchPotato', + 'html': 'CouchPotato', 'href': App.createUrl('') }), self.nav = new Element('ul'), diff --git a/couchpotato/static/scripts/couchpotato.js b/couchpotato/static/scripts/couchpotato.js index f6f7db8..f446277 100644 --- a/couchpotato/static/scripts/couchpotato.js +++ b/couchpotato/static/scripts/couchpotato.js @@ -73,14 +73,14 @@ self.c.adopt( $(self.block.header).addClass('header').adopt( - new Element('div').adopt( - self.block.navigation = new BlockNavigation(self, {}), - self.block.search = new BlockSearch(self, {}), - self.block.more = new BlockMenu(self, {'button_class': 'icon2.cog'}) - ) + self.block.navigation = new BlockNavigation(self, {}), + self.block.search = new BlockSearch(self, {}), + self.block.more = new BlockMenu(self, {'button_class': 'icon2.cog'}) ), - self.content = new Element('div.content'), - self.block.footer = new BlockFooter(self, {}) + self.content = new Element('div.content').adopt( + self.pages = new Element('div.pages'), + self.block.footer = new BlockFooter(self, {}) + ) ); var setting_links = [ @@ -143,7 +143,7 @@ self.fireEvent('load'+class_name); - $(pg).inject(self.content); + $(pg).inject(self.pages); }); self.fireEvent('load'); diff --git a/couchpotato/static/style/fonts.scss b/couchpotato/static/style/fonts.scss index ae592a7..ce2f628 100644 --- a/couchpotato/static/style/fonts.scss +++ b/couchpotato/static/style/fonts.scss @@ -2,22 +2,34 @@ /* Fonts */ @font-face { font-family: 'Elusive-Icons'; - src:url('../fonts/Elusive-Icons.eot'); - src:url('../fonts/Elusive-Icons.eot?#iefix') format('embedded-opentype'), - url('../fonts/Elusive-Icons.woff') format('woff'), - url('../fonts/Elusive-Icons.ttf') format('truetype'), - url('../fonts/Elusive-Icons.svg#Elusive-Icons') format('svg'); + src:url('../static/fonts/Elusive-Icons.eot'); + src:url('../static/fonts/Elusive-Icons.eot?#iefix') format('embedded-opentype'), + url('../static/fonts/Elusive-Icons.woff') format('woff'), + url('../static/fonts/Elusive-Icons.ttf') format('truetype'), + url('../static/fonts/Elusive-Icons.svg#Elusive-Icons') format('svg'); font-weight: normal; font-style: normal; } @font-face { font-family: 'OpenSans'; - src: url('../fonts/OpenSans-Regular-webfont.eot'); - src: url('../fonts/OpenSans-Regular-webfont.eot?#iefix') format('embedded-opentype'), - url('../fonts/OpenSans-Regular-webfont.woff') format('woff'), - url('../fonts/OpenSans-Regular-webfont.ttf') format('truetype'), - url('../fonts/OpenSans-Regular-webfont.svg#OpenSansRegular') format('svg'); + src: url('../static/fonts/OpenSans-Light-webfont.eot'); + src: url('../static/fonts/OpenSans-Light-webfont.eot?#iefix') format('embedded-opentype'), + url('../static/fonts/OpenSans-Light-webfont.woff') format('woff'), + url('../static/fonts/OpenSans-Light-webfont.ttf') format('truetype'), + url('../static/fonts/OpenSans-Light-webfont.svg#OpenSansRegular') format('svg'); + font-weight: 200; + font-style: normal; + +} + +@font-face { + font-family: 'OpenSans'; + src: url('../static/fonts/OpenSans-Regular-webfont.eot'); + src: url('../static/fonts/OpenSans-Regular-webfont.eot?#iefix') format('embedded-opentype'), + url('../static/fonts/OpenSans-Regular-webfont.woff') format('woff'), + url('../static/fonts/OpenSans-Regular-webfont.ttf') format('truetype'), + url('../static/fonts/OpenSans-Regular-webfont.svg#OpenSansRegular') format('svg'); font-weight: normal; font-style: normal; @@ -25,11 +37,11 @@ @font-face { font-family: 'OpenSans'; - src: url('../fonts/OpenSans-Italic-webfont.eot'); - src: url('../fonts/OpenSans-Italic-webfont.eot?#iefix') format('embedded-opentype'), - url('../fonts/OpenSans-Italic-webfont.woff') format('woff'), - url('../fonts/OpenSans-Italic-webfont.ttf') format('truetype'), - url('../fonts/OpenSans-Italic-webfont.svg#OpenSansItalic') format('svg'); + src: url('../static/fonts/OpenSans-Italic-webfont.eot'); + src: url('../static/fonts/OpenSans-Italic-webfont.eot?#iefix') format('embedded-opentype'), + url('../static/fonts/OpenSans-Italic-webfont.woff') format('woff'), + url('../static/fonts/OpenSans-Italic-webfont.ttf') format('truetype'), + url('../static/fonts/OpenSans-Italic-webfont.svg#OpenSansItalic') format('svg'); font-weight: normal; font-style: italic; @@ -37,33 +49,33 @@ @font-face { font-family: 'OpenSans'; - src: url('../fonts/OpenSans-Bold-webfont.eot'); - src: url('../fonts/OpenSans-Bold-webfont.eot?#iefix') format('embedded-opentype'), - url('../fonts/OpenSans-Bold-webfont.woff') format('woff'), - url('../fonts/OpenSans-Bold-webfont.ttf') format('truetype'), - url('../fonts/OpenSans-Bold-webfont.svg#OpenSansBold') format('svg'); + src: url('../static/fonts/OpenSans-Bold-webfont.eot'); + src: url('../static/fonts/OpenSans-Bold-webfont.eot?#iefix') format('embedded-opentype'), + url('../static/fonts/OpenSans-Bold-webfont.woff') format('woff'), + url('../static/fonts/OpenSans-Bold-webfont.ttf') format('truetype'), + url('../static/fonts/OpenSans-Bold-webfont.svg#OpenSansBold') format('svg'); font-weight: bold; font-style: normal; } @font-face { font-family: 'OpenSans'; - src: url('../fonts/OpenSans-BoldItalic-webfont.eot'); - src: url('../fonts/OpenSans-BoldItalic-webfont.eot?#iefix') format('embedded-opentype'), - url('../fonts/OpenSans-BoldItalic-webfont.woff') format('woff'), - url('../fonts/OpenSans-BoldItalic-webfont.ttf') format('truetype'), - url('../fonts/OpenSans-BoldItalic-webfont.svg#OpenSansBoldItalic') format('svg'); + src: url('../static/fonts/OpenSans-BoldItalic-webfont.eot'); + src: url('../static/fonts/OpenSans-BoldItalic-webfont.eot?#iefix') format('embedded-opentype'), + url('../static/fonts/OpenSans-BoldItalic-webfont.woff') format('woff'), + url('../static/fonts/OpenSans-BoldItalic-webfont.ttf') format('truetype'), + url('../static/fonts/OpenSans-BoldItalic-webfont.svg#OpenSansBoldItalic') format('svg'); font-weight: bold; font-style: italic; } @font-face { font-family: 'Lobster'; - src: url('../fonts/Lobster-webfont.eot'); - src: url('../fonts/Lobster-webfont.eot?#iefix') format('embedded-opentype'), - url('../fonts/Lobster-webfont.woff') format('woff'), - url('../fonts/Lobster-webfont.ttf') format('truetype'), - url('../fonts/Lobster-webfont.svg#lobster_1.4regular') format('svg'); + src: url('../static/fonts/Lobster-webfont.eot'); + src: url('../static/fonts/Lobster-webfont.eot?#iefix') format('embedded-opentype'), + url('../static/fonts/Lobster-webfont.woff') format('woff'), + url('../static/fonts/Lobster-webfont.ttf') format('truetype'), + url('../static/fonts/Lobster-webfont.svg#lobster_1.4regular') format('svg'); font-weight: normal; font-style: normal; } diff --git a/couchpotato/static/style/main.scss b/couchpotato/static/style/main.scss index 889a2b0..68e3595 100644 --- a/couchpotato/static/style/main.scss +++ b/couchpotato/static/style/main.scss @@ -1,19 +1,99 @@ @import "fonts"; +@import "mixins"; body, html { - font-size: 12px; + font-size: 14px; line-height: 1.5; font-family: OpenSans, "Helvetica Neue", Helvetica, Arial, Geneva, sans-serif; height: 100%; margin: 0; padding: 0; - background: #FFF; + background: #111; -webkit-font-smoothing: subpixel-antialiased; + overflow: hidden; +} + +body { + @include flexbox(); + flex-flow: row nowrap; +} + +a { + text-decoration: none; +} + +.header { + width: 130px; + + font-weight: 200; + + a { + color: #FFF; + letter-spacing: 1px; + } + + .navigation { + + .logo { + background: #ac0000; + display: block; + text-align: center; + + font-family: Lobster; + color: #FFF; + font-size: 38px; + line-height: 80px; + height: 80px; + + span:nth-child(odd){ + display: block; + } + span:nth-child(even){ + display: none; + + } + } - &.noscroll { - overflow: hidden; + ul { + padding: 0; + margin: 0; + + li a { + padding: 10px 20px; + display: block; + } + } + + } +} + + +.content { + @include flex(1 auto); + + background: #FFF; + border-radius: 3px 0 0 3px; + overflow: hidden; + + .pages { + height: 100%; + widows: 100%; + } + + .footer { + position: fixed; + bottom: 0; + height: 20px; + width: 100%; } } -body { overflow-y: scroll; } +.page { + + display: none; + &.active { + display: block; + } + +} From 05a97a19ab83813de9c25d1bb9379203136793a4 Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 5 May 2014 20:41:29 +0200 Subject: [PATCH 006/301] Mixins --- couchpotato/core/media/movie/_base/static/page.js | 7 + .../static/fonts/OpenSans-Light-webfont.eot | Bin 0 -> 19514 bytes .../static/fonts/OpenSans-Light-webfont.svg | 1831 ++++++++++++++++++++ .../static/fonts/OpenSans-Light-webfont.ttf | Bin 0 -> 37336 bytes .../static/fonts/OpenSans-Light-webfont.woff | Bin 0 -> 22248 bytes couchpotato/static/style/mixins.scss | 23 + 6 files changed, 1861 insertions(+) create mode 100644 couchpotato/core/media/movie/_base/static/page.js create mode 100644 couchpotato/static/fonts/OpenSans-Light-webfont.eot create mode 100644 couchpotato/static/fonts/OpenSans-Light-webfont.svg create mode 100644 couchpotato/static/fonts/OpenSans-Light-webfont.ttf create mode 100644 couchpotato/static/fonts/OpenSans-Light-webfont.woff create mode 100644 couchpotato/static/style/mixins.scss diff --git a/couchpotato/core/media/movie/_base/static/page.js b/couchpotato/core/media/movie/_base/static/page.js new file mode 100644 index 0000000..8a7bf17 --- /dev/null +++ b/couchpotato/core/media/movie/_base/static/page.js @@ -0,0 +1,7 @@ +Page.Movies = new Class({ + + extend: PageBase, + + subPages: [] + +}) diff --git a/couchpotato/static/fonts/OpenSans-Light-webfont.eot b/couchpotato/static/fonts/OpenSans-Light-webfont.eot new file mode 100644 index 0000000000000000000000000000000000000000..14868406aa7d728a88d63963f119635813b5d30e GIT binary patch literal 19514 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 literal 0 HcmV?d00001 diff --git a/couchpotato/static/fonts/OpenSans-Light-webfont.svg b/couchpotato/static/fonts/OpenSans-Light-webfont.svg new file mode 100644 index 0000000..11a472c --- /dev/null +++ b/couchpotato/static/fonts/OpenSans-Light-webfont.svg @@ -0,0 +1,1831 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/couchpotato/static/fonts/OpenSans-Light-webfont.ttf b/couchpotato/static/fonts/OpenSans-Light-webfont.ttf new file mode 100644 index 0000000000000000000000000000000000000000..63af664cde6abb0083a37fb7f9d561c461997f49 GIT binary patch literal 37336 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 literal 0 HcmV?d00001 diff --git a/couchpotato/static/fonts/OpenSans-Light-webfont.woff b/couchpotato/static/fonts/OpenSans-Light-webfont.woff new file mode 100644 index 0000000000000000000000000000000000000000..e786074813a27d0a7a249047832988d5bf0fe756 GIT binary patch literal 22248 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 literal 0 HcmV?d00001 diff --git a/couchpotato/static/style/mixins.scss b/couchpotato/static/style/mixins.scss new file mode 100644 index 0000000..315fa3c --- /dev/null +++ b/couchpotato/static/style/mixins.scss @@ -0,0 +1,23 @@ +@mixin flexbox() { + display: -webkit-box; + display: -moz-box; + display: -ms-flexbox; + display: -webkit-flex; + display: flex; +} + +@mixin flex($values) { + -webkit-box-flex: $values; + -moz-box-flex: $values; + -webkit-flex: $values; + -ms-flex: $values; + flex: $values; +} + +@mixin order($val) { + -webkit-box-ordinal-group: $val; + -moz-box-ordinal-group: $val; + -ms-flex-order: $val; + -webkit-order: $val; + order: $val; +} From 43b6e3ac072f9f360c5e5b415a764c86392974c6 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 11 May 2014 20:46:00 +0200 Subject: [PATCH 007/301] Use proper extend --- couchpotato/core/media/movie/_base/static/page.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/core/media/movie/_base/static/page.js b/couchpotato/core/media/movie/_base/static/page.js index 8a7bf17..74d3e82 100644 --- a/couchpotato/core/media/movie/_base/static/page.js +++ b/couchpotato/core/media/movie/_base/static/page.js @@ -1,6 +1,6 @@ Page.Movies = new Class({ - extend: PageBase, + Extends: PageBase, subPages: [] From d80fe99609d0bdcb0268c8b8d540b61829abce03 Mon Sep 17 00:00:00 2001 From: Ruud Date: Tue, 13 May 2014 22:37:00 +0200 Subject: [PATCH 008/301] Load subpages --- .../core/media/movie/_base/static/manage.js | 2 +- couchpotato/core/media/movie/_base/static/page.js | 9 ++++-- .../core/media/movie/_base/static/wanted.js | 2 +- couchpotato/static/scripts/couchpotato.js | 12 ++++---- couchpotato/static/scripts/page.js | 34 ++++++++++++++++++++++ 5 files changed, 49 insertions(+), 10 deletions(-) diff --git a/couchpotato/core/media/movie/_base/static/manage.js b/couchpotato/core/media/movie/_base/static/manage.js index 295983c..c967c45 100644 --- a/couchpotato/core/media/movie/_base/static/manage.js +++ b/couchpotato/core/media/movie/_base/static/manage.js @@ -1,4 +1,4 @@ -Page.Movies.Manage = new Class({ +var MoviesManage = new Class({ Extends: PageBase, diff --git a/couchpotato/core/media/movie/_base/static/page.js b/couchpotato/core/media/movie/_base/static/page.js index 74d3e82..00736ab 100644 --- a/couchpotato/core/media/movie/_base/static/page.js +++ b/couchpotato/core/media/movie/_base/static/page.js @@ -2,6 +2,11 @@ Page.Movies = new Class({ Extends: PageBase, - subPages: [] + name: 'movies', + sub_pages: ['Wanted', 'Manage'], -}) + indexAction: function(){ + p('test'); + } + +}); diff --git a/couchpotato/core/media/movie/_base/static/wanted.js b/couchpotato/core/media/movie/_base/static/wanted.js index 0f74cd1..d761351 100644 --- a/couchpotato/core/media/movie/_base/static/wanted.js +++ b/couchpotato/core/media/movie/_base/static/wanted.js @@ -1,4 +1,4 @@ -Page.Movies.Wanted = new Class({ +var MoviesWanted = new Class({ Extends: PageBase, diff --git a/couchpotato/static/scripts/couchpotato.js b/couchpotato/static/scripts/couchpotato.js index 8a8f1e7..57f1e07 100644 --- a/couchpotato/static/scripts/couchpotato.js +++ b/couchpotato/static/scripts/couchpotato.js @@ -1,4 +1,4 @@ -var CouchPotato = new Class({ +var CouchPotato = new Class({ Implements: [Events, Options], @@ -152,7 +152,7 @@ var CouchPotato = new Class({ pages.stableSort(self.sortPageByOrder).each(function(page){ page['class'].load(); self.fireEvent('load'+page.name); - $(page['class']).inject(self.content); + $(page['class']).inject(self.pages); }); delete pages; @@ -169,11 +169,11 @@ var CouchPotato = new Class({ var self = this; self.route.parse(); - var page_name = self.route.getPage().capitalize(); - var action = self.route.getAction(); - var params = self.route.getParams(); + var page_name = self.route.getPage().capitalize(), + action = self.route.getAction(), + params = self.route.getParams(), + current_url = self.route.getCurrentUrl(); - var current_url = self.route.getCurrentUrl(); if(current_url == self.current_url) return; diff --git a/couchpotato/static/scripts/page.js b/couchpotato/static/scripts/page.js index 57b8b10..480a451 100644 --- a/couchpotato/static/scripts/page.js +++ b/couchpotato/static/scripts/page.js @@ -10,6 +10,8 @@ var PageBase = new Class({ has_tab: true, name: '', + sub_pages: null, + initialize: function(options) { var self = this; @@ -32,6 +34,38 @@ var PageBase = new Class({ }); } + if(self.sub_pages){ + self.loadSubPages(); + } + + }, + + loadSubPages: function(){ + var self = this; + + var sub_pages = self.sub_pages; + + self.pages = new Element('div.pages').inject(self.el); + + self.sub_pages = []; + sub_pages.each(function(class_name){ + var pg = new window[self.name.capitalize()+class_name](self, {}); + self.sub_pages[class_name] = pg; + + self.sub_pages.include({ + 'order': pg.order, + 'name': class_name, + 'class': pg + }); + }); + + self.sub_pages.stableSort(self.sortPageByOrder).each(function(page){ + page['class'].load(); + self.fireEvent('load'+page.name); + + $(page['class']).inject(self.pages); + }); + }, open: function(action, params){ From 6ccbad031f1bafbe18d1b18cbc156d4cb63203ff Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 13 Jul 2014 12:23:14 +0200 Subject: [PATCH 009/301] Use own style reloader --- couchpotato/core/_base/clientscript.py | 52 +- couchpotato/static/style/main.scss | 8 +- couchpotato/templates/index.html | 8 +- libs/livereload/__init__.py | 16 - libs/livereload/_compat.py | 43 -- libs/livereload/handlers.py | 209 ------- libs/livereload/livereload.js | 1055 -------------------------------- libs/livereload/server.py | 224 ------- libs/livereload/watcher.py | 132 ---- 9 files changed, 52 insertions(+), 1695 deletions(-) delete mode 100644 libs/livereload/__init__.py delete mode 100644 libs/livereload/_compat.py delete mode 100644 libs/livereload/handlers.py delete mode 100644 libs/livereload/livereload.js delete mode 100644 libs/livereload/server.py delete mode 100644 libs/livereload/watcher.py diff --git a/couchpotato/core/_base/clientscript.py b/couchpotato/core/_base/clientscript.py index 5f153a4..df637b4 100644 --- a/couchpotato/core/_base/clientscript.py +++ b/couchpotato/core/_base/clientscript.py @@ -53,7 +53,7 @@ class ClientScript(Plugin): ], } - watcher = None + watches = {} original_paths = {'style': {}, 'script': {}} paths = {'style': {}, 'script': {}} @@ -82,17 +82,7 @@ class ClientScript(Plugin): def livereload(self): if Env.get('dev'): - from livereload import Server - from livereload.watcher import Watcher - - self.livereload_server = Server() - self.livereload_server.watch('%s/minified/*.css' % Env.get('cache_dir')) - self.livereload_server.watch('%s/*.css' % os.path.join(Env.get('app_dir'), 'couchpotato', 'static', 'style')) - - self.watcher = Watcher() - fireEvent('schedule.interval', 'livereload.watcher', self.watcher.examine, seconds = .5) - - self.livereload_server.serve(port = 35729) + fireEvent('schedule.interval', 'livereload.watcher', self.watcher, seconds = .5) def addCore(self): @@ -106,6 +96,39 @@ class ClientScript(Plugin): else: self.registerStyle(core_url, file_path, position = 'front') + def watcher(self): + + changed = [] + + for file_path in self.watches: + info = self.watches[file_path] + old_time = info['file_time'] + file_time = os.path.getmtime(file_path) + if file_time > old_time: + changed.append(info['api_path']) + + if info['compiled_path']: + compiler = Scss(live_errors = True, search_paths = [os.path.dirname(file_path)]) + f = open(file_path, 'r').read() + f = compiler.compile(f) + + self.createFile(info['compiled_path'], f.strip()) + + + # Add file to watchlist again, with current filetime + self.watches[file_path]['file_time'] = file_time + + # Notify fronted with changes + if changed: + fireEvent('notify.frontend', type = 'watcher.changed', data = changed) + + def watchFile(self, file_path, api_path, compiled_path = False): + self.watches[file_path] = { + 'file_time': os.path.getmtime(file_path), + 'api_path': api_path, + 'compiled_path': compiled_path, + } + def compile(self): # Create cache dir @@ -146,7 +169,6 @@ class ClientScript(Plugin): # Reload watcher if Env.get('dev'): - self.watcher.watch(file_path, self.compile) compiled_file_name = position + '_%s.css' % url_path.replace('/', '_').split('.scss')[0] compiled_file_path = os.path.join(minified_dir, compiled_file_name) @@ -155,6 +177,8 @@ class ClientScript(Plugin): # Remove scss path x = (file_path, 'minified/%s?%s' % (compiled_file_name, tryInt(time.time()))) + self.watchFile(file_path, x[1], compiled_path = compiled_file_path) + if not Env.get('dev'): if file_type == 'script': @@ -199,6 +223,8 @@ class ClientScript(Plugin): def register(self, api_path, file_path, type, location): + self.watchFile(file_path, api_path) + api_path = '%s?%s' % (api_path, tryInt(os.path.getmtime(file_path))) if not self.original_paths[type].get(location): diff --git a/couchpotato/static/style/main.scss b/couchpotato/static/style/main.scss index 68e3595..a9439b7 100644 --- a/couchpotato/static/style/main.scss +++ b/couchpotato/static/style/main.scss @@ -5,7 +5,7 @@ body, html { font-size: 14px; line-height: 1.5; font-family: OpenSans, "Helvetica Neue", Helvetica, Arial, Geneva, sans-serif; - height: 100%; + height: 100%; margin: 0; padding: 0; background: #111; @@ -75,6 +75,11 @@ a { border-radius: 3px 0 0 3px; overflow: hidden; + h1, h2, h3 { + padding: 0; + margin: 0; + } + .pages { height: 100%; widows: 100%; @@ -91,6 +96,7 @@ a { .page { display: none; + padding: 20px; &.active { display: block; diff --git a/couchpotato/templates/index.html b/couchpotato/templates/index.html index 4d685fb..3249dc5 100644 --- a/couchpotato/templates/index.html +++ b/couchpotato/templates/index.html @@ -8,18 +8,22 @@ {% for url in fireEvent('clientscript.get_styles', location = 'front', single = True) %} - {% end %} + {% end %} {% for url in fireEvent('clientscript.get_scripts', location = 'front', single = True) %} {% end %} {% for url in fireEvent('clientscript.get_scripts', location = 'head', single = True) %} {% end %} {% for url in fireEvent('clientscript.get_styles', location = 'head', single = True) %} - {% end %} + {% end %} + {% if Env.get('dev') %} + + {% end %} + ' - ) - self.write(data) - - def create_index(self, root): - files = os.listdir(root) - self.write('
    ') - for f in files: - path = os.path.join(root, f) - self.write('
  • ') - if os.path.isdir(path): - self.write('%s' % (f, f)) - else: - self.write('%s' % (f, f)) - self.write('
  • ') - self.write('
') diff --git a/libs/livereload/livereload.js b/libs/livereload/livereload.js deleted file mode 100644 index 491ddcc..0000000 --- a/libs/livereload/livereload.js +++ /dev/null @@ -1,1055 +0,0 @@ -(function() { -var __customevents = {}, __protocol = {}, __connector = {}, __timer = {}, __options = {}, __reloader = {}, __livereload = {}, __less = {}, __startup = {}; - -// customevents -var CustomEvents; -CustomEvents = { - bind: function(element, eventName, handler) { - if (element.addEventListener) { - return element.addEventListener(eventName, handler, false); - } else if (element.attachEvent) { - element[eventName] = 1; - return element.attachEvent('onpropertychange', function(event) { - if (event.propertyName === eventName) { - return handler(); - } - }); - } else { - throw new Error("Attempt to attach custom event " + eventName + " to something which isn't a DOMElement"); - } - }, - fire: function(element, eventName) { - var event; - if (element.addEventListener) { - event = document.createEvent('HTMLEvents'); - event.initEvent(eventName, true, true); - return document.dispatchEvent(event); - } else if (element.attachEvent) { - if (element[eventName]) { - return element[eventName]++; - } - } else { - throw new Error("Attempt to fire custom event " + eventName + " on something which isn't a DOMElement"); - } - } -}; -__customevents.bind = CustomEvents.bind; -__customevents.fire = CustomEvents.fire; - -// protocol -var PROTOCOL_6, PROTOCOL_7, Parser, ProtocolError; -var __indexOf = Array.prototype.indexOf || function(item) { - for (var i = 0, l = this.length; i < l; i++) { - if (this[i] === item) return i; - } - return -1; -}; -__protocol.PROTOCOL_6 = PROTOCOL_6 = 'http://livereload.com/protocols/official-6'; -__protocol.PROTOCOL_7 = PROTOCOL_7 = 'http://livereload.com/protocols/official-7'; -__protocol.ProtocolError = ProtocolError = (function() { - function ProtocolError(reason, data) { - this.message = "LiveReload protocol error (" + reason + ") after receiving data: \"" + data + "\"."; - } - return ProtocolError; -})(); -__protocol.Parser = Parser = (function() { - function Parser(handlers) { - this.handlers = handlers; - this.reset(); - } - Parser.prototype.reset = function() { - return this.protocol = null; - }; - Parser.prototype.process = function(data) { - var command, message, options, _ref; - try { - if (!(this.protocol != null)) { - if (data.match(/^!!ver:([\d.]+)$/)) { - this.protocol = 6; - } else if (message = this._parseMessage(data, ['hello'])) { - if (!message.protocols.length) { - throw new ProtocolError("no protocols specified in handshake message"); - } else if (__indexOf.call(message.protocols, PROTOCOL_7) >= 0) { - this.protocol = 7; - } else if (__indexOf.call(message.protocols, PROTOCOL_6) >= 0) { - this.protocol = 6; - } else { - throw new ProtocolError("no supported protocols found"); - } - } - return this.handlers.connected(this.protocol); - } else if (this.protocol === 6) { - message = JSON.parse(data); - if (!message.length) { - throw new ProtocolError("protocol 6 messages must be arrays"); - } - command = message[0], options = message[1]; - if (command !== 'refresh') { - throw new ProtocolError("unknown protocol 6 command"); - } - return this.handlers.message({ - command: 'reload', - path: options.path, - liveCSS: (_ref = options.apply_css_live) != null ? _ref : true - }); - } else { - message = this._parseMessage(data, ['reload', 'alert']); - return this.handlers.message(message); - } - } catch (e) { - if (e instanceof ProtocolError) { - return this.handlers.error(e); - } else { - throw e; - } - } - }; - Parser.prototype._parseMessage = function(data, validCommands) { - var message, _ref; - try { - message = JSON.parse(data); - } catch (e) { - throw new ProtocolError('unparsable JSON', data); - } - if (!message.command) { - throw new ProtocolError('missing "command" key', data); - } - if (_ref = message.command, __indexOf.call(validCommands, _ref) < 0) { - throw new ProtocolError("invalid command '" + message.command + "', only valid commands are: " + (validCommands.join(', ')) + ")", data); - } - return message; - }; - return Parser; -})(); - -// connector -// Generated by CoffeeScript 1.3.3 -var Connector, PROTOCOL_6, PROTOCOL_7, Parser, Version, _ref; - -_ref = __protocol, Parser = _ref.Parser, PROTOCOL_6 = _ref.PROTOCOL_6, PROTOCOL_7 = _ref.PROTOCOL_7; - -Version = '2.0.8'; - -__connector.Connector = Connector = (function() { - - function Connector(options, WebSocket, Timer, handlers) { - var _this = this; - this.options = options; - this.WebSocket = WebSocket; - this.Timer = Timer; - this.handlers = handlers; - this._uri = "ws://" + this.options.host + ":" + this.options.port + "/livereload"; - this._nextDelay = this.options.mindelay; - this._connectionDesired = false; - this.protocol = 0; - this.protocolParser = new Parser({ - connected: function(protocol) { - _this.protocol = protocol; - _this._handshakeTimeout.stop(); - _this._nextDelay = _this.options.mindelay; - _this._disconnectionReason = 'broken'; - return _this.handlers.connected(protocol); - }, - error: function(e) { - _this.handlers.error(e); - return _this._closeOnError(); - }, - message: function(message) { - return _this.handlers.message(message); - } - }); - this._handshakeTimeout = new Timer(function() { - if (!_this._isSocketConnected()) { - return; - } - _this._disconnectionReason = 'handshake-timeout'; - return _this.socket.close(); - }); - this._reconnectTimer = new Timer(function() { - if (!_this._connectionDesired) { - return; - } - return _this.connect(); - }); - this.connect(); - } - - Connector.prototype._isSocketConnected = function() { - return this.socket && this.socket.readyState === this.WebSocket.OPEN; - }; - - Connector.prototype.connect = function() { - var _this = this; - this._connectionDesired = true; - if (this._isSocketConnected()) { - return; - } - this._reconnectTimer.stop(); - this._disconnectionReason = 'cannot-connect'; - this.protocolParser.reset(); - this.handlers.connecting(); - this.socket = new this.WebSocket(this._uri); - this.socket.onopen = function(e) { - return _this._onopen(e); - }; - this.socket.onclose = function(e) { - return _this._onclose(e); - }; - this.socket.onmessage = function(e) { - return _this._onmessage(e); - }; - return this.socket.onerror = function(e) { - return _this._onerror(e); - }; - }; - - Connector.prototype.disconnect = function() { - this._connectionDesired = false; - this._reconnectTimer.stop(); - if (!this._isSocketConnected()) { - return; - } - this._disconnectionReason = 'manual'; - return this.socket.close(); - }; - - Connector.prototype._scheduleReconnection = function() { - if (!this._connectionDesired) { - return; - } - if (!this._reconnectTimer.running) { - this._reconnectTimer.start(this._nextDelay); - return this._nextDelay = Math.min(this.options.maxdelay, this._nextDelay * 2); - } - }; - - Connector.prototype.sendCommand = function(command) { - if (this.protocol == null) { - return; - } - return this._sendCommand(command); - }; - - Connector.prototype._sendCommand = function(command) { - return this.socket.send(JSON.stringify(command)); - }; - - Connector.prototype._closeOnError = function() { - this._handshakeTimeout.stop(); - this._disconnectionReason = 'error'; - return this.socket.close(); - }; - - Connector.prototype._onopen = function(e) { - var hello; - this.handlers.socketConnected(); - this._disconnectionReason = 'handshake-failed'; - hello = { - command: 'hello', - protocols: [PROTOCOL_6, PROTOCOL_7] - }; - hello.ver = Version; - if (this.options.ext) { - hello.ext = this.options.ext; - } - if (this.options.extver) { - hello.extver = this.options.extver; - } - if (this.options.snipver) { - hello.snipver = this.options.snipver; - } - this._sendCommand(hello); - return this._handshakeTimeout.start(this.options.handshake_timeout); - }; - - Connector.prototype._onclose = function(e) { - this.protocol = 0; - this.handlers.disconnected(this._disconnectionReason, this._nextDelay); - return this._scheduleReconnection(); - }; - - Connector.prototype._onerror = function(e) {}; - - Connector.prototype._onmessage = function(e) { - return this.protocolParser.process(e.data); - }; - - return Connector; - -})(); - -// timer -var Timer; -var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; -__timer.Timer = Timer = (function() { - function Timer(func) { - this.func = func; - this.running = false; - this.id = null; - this._handler = __bind(function() { - this.running = false; - this.id = null; - return this.func(); - }, this); - } - Timer.prototype.start = function(timeout) { - if (this.running) { - clearTimeout(this.id); - } - this.id = setTimeout(this._handler, timeout); - return this.running = true; - }; - Timer.prototype.stop = function() { - if (this.running) { - clearTimeout(this.id); - this.running = false; - return this.id = null; - } - }; - return Timer; -})(); -Timer.start = function(timeout, func) { - return setTimeout(func, timeout); -}; - -// options -var Options; -__options.Options = Options = (function() { - function Options() { - this.host = null; - this.port = {{port}}; - this.snipver = null; - this.ext = null; - this.extver = null; - this.mindelay = 1000; - this.maxdelay = 60000; - this.handshake_timeout = 5000; - } - Options.prototype.set = function(name, value) { - switch (typeof this[name]) { - case 'undefined': - break; - case 'number': - return this[name] = +value; - default: - return this[name] = value; - } - }; - return Options; -})(); -Options.extract = function(document) { - var element, keyAndValue, m, mm, options, pair, src, _i, _j, _len, _len2, _ref, _ref2; - _ref = document.getElementsByTagName('script'); - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - element = _ref[_i]; - if ((src = element.src) && (m = src.match(/^[^:]+:\/\/(.*)\/z?livereload\.js(?:\?(.*))?$/))) { - options = new Options(); - if (mm = m[1].match(/^([^\/:]+)(?::(\d+))?$/)) { - options.host = mm[1]; - if (mm[2]) { - options.port = parseInt(mm[2], 10); - } - } - if (m[2]) { - _ref2 = m[2].split('&'); - for (_j = 0, _len2 = _ref2.length; _j < _len2; _j++) { - pair = _ref2[_j]; - if ((keyAndValue = pair.split('=')).length > 1) { - options.set(keyAndValue[0].replace(/-/g, '_'), keyAndValue.slice(1).join('=')); - } - } - } - return options; - } - } - return null; -}; - -// reloader -// Generated by CoffeeScript 1.3.1 -(function() { - var IMAGE_STYLES, Reloader, numberOfMatchingSegments, pathFromUrl, pathsMatch, pickBestMatch, splitUrl; - - splitUrl = function(url) { - var hash, index, params; - if ((index = url.indexOf('#')) >= 0) { - hash = url.slice(index); - url = url.slice(0, index); - } else { - hash = ''; - } - if ((index = url.indexOf('?')) >= 0) { - params = url.slice(index); - url = url.slice(0, index); - } else { - params = ''; - } - return { - url: url, - params: params, - hash: hash - }; - }; - - pathFromUrl = function(url) { - var path; - url = splitUrl(url).url; - if (url.indexOf('file://') === 0) { - path = url.replace(/^file:\/\/(localhost)?/, ''); - } else { - path = url.replace(/^([^:]+:)?\/\/([^:\/]+)(:\d*)?\//, '/'); - } - return decodeURIComponent(path); - }; - - pickBestMatch = function(path, objects, pathFunc) { - var bestMatch, object, score, _i, _len; - bestMatch = { - score: 0 - }; - for (_i = 0, _len = objects.length; _i < _len; _i++) { - object = objects[_i]; - score = numberOfMatchingSegments(path, pathFunc(object)); - if (score > bestMatch.score) { - bestMatch = { - object: object, - score: score - }; - } - } - if (bestMatch.score > 0) { - return bestMatch; - } else { - return null; - } - }; - - numberOfMatchingSegments = function(path1, path2) { - var comps1, comps2, eqCount, len; - path1 = path1.replace(/^\/+/, '').toLowerCase(); - path2 = path2.replace(/^\/+/, '').toLowerCase(); - if (path1 === path2) { - return 10000; - } - comps1 = path1.split('/').reverse(); - comps2 = path2.split('/').reverse(); - len = Math.min(comps1.length, comps2.length); - eqCount = 0; - while (eqCount < len && comps1[eqCount] === comps2[eqCount]) { - ++eqCount; - } - return eqCount; - }; - - pathsMatch = function(path1, path2) { - return numberOfMatchingSegments(path1, path2) > 0; - }; - - IMAGE_STYLES = [ - { - selector: 'background', - styleNames: ['backgroundImage'] - }, { - selector: 'border', - styleNames: ['borderImage', 'webkitBorderImage', 'MozBorderImage'] - } - ]; - - __reloader.Reloader = Reloader = (function() { - - Reloader.name = 'Reloader'; - - function Reloader(window, console, Timer) { - this.window = window; - this.console = console; - this.Timer = Timer; - this.document = this.window.document; - this.importCacheWaitPeriod = 200; - this.plugins = []; - } - - Reloader.prototype.addPlugin = function(plugin) { - return this.plugins.push(plugin); - }; - - Reloader.prototype.analyze = function(callback) { - return results; - }; - - Reloader.prototype.reload = function(path, options) { - var plugin, _base, _i, _len, _ref; - this.options = options; - if ((_base = this.options).stylesheetReloadTimeout == null) { - _base.stylesheetReloadTimeout = 15000; - } - _ref = this.plugins; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - plugin = _ref[_i]; - if (plugin.reload && plugin.reload(path, options)) { - return; - } - } - if (options.liveCSS) { - if (path.match(/\.css$/i)) { - if (this.reloadStylesheet(path)) { - return; - } - } - } - if (options.liveImg) { - if (path.match(/\.(jpe?g|png|gif)$/i)) { - this.reloadImages(path); - return; - } - } - return this.reloadPage(); - }; - - Reloader.prototype.reloadPage = function() { - return this.window.document.location.reload(); - }; - - Reloader.prototype.reloadImages = function(path) { - var expando, img, selector, styleNames, styleSheet, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1, _ref2, _ref3, _results; - expando = this.generateUniqueString(); - _ref = this.document.images; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - img = _ref[_i]; - if (pathsMatch(path, pathFromUrl(img.src))) { - img.src = this.generateCacheBustUrl(img.src, expando); - } - } - if (this.document.querySelectorAll) { - for (_j = 0, _len1 = IMAGE_STYLES.length; _j < _len1; _j++) { - _ref1 = IMAGE_STYLES[_j], selector = _ref1.selector, styleNames = _ref1.styleNames; - _ref2 = this.document.querySelectorAll("[style*=" + selector + "]"); - for (_k = 0, _len2 = _ref2.length; _k < _len2; _k++) { - img = _ref2[_k]; - this.reloadStyleImages(img.style, styleNames, path, expando); - } - } - } - if (this.document.styleSheets) { - _ref3 = this.document.styleSheets; - _results = []; - for (_l = 0, _len3 = _ref3.length; _l < _len3; _l++) { - styleSheet = _ref3[_l]; - _results.push(this.reloadStylesheetImages(styleSheet, path, expando)); - } - return _results; - } - }; - - Reloader.prototype.reloadStylesheetImages = function(styleSheet, path, expando) { - var rule, rules, styleNames, _i, _j, _len, _len1; - try { - rules = styleSheet != null ? styleSheet.cssRules : void 0; - } catch (e) { - - } - if (!rules) { - return; - } - for (_i = 0, _len = rules.length; _i < _len; _i++) { - rule = rules[_i]; - switch (rule.type) { - case CSSRule.IMPORT_RULE: - this.reloadStylesheetImages(rule.styleSheet, path, expando); - break; - case CSSRule.STYLE_RULE: - for (_j = 0, _len1 = IMAGE_STYLES.length; _j < _len1; _j++) { - styleNames = IMAGE_STYLES[_j].styleNames; - this.reloadStyleImages(rule.style, styleNames, path, expando); - } - break; - case CSSRule.MEDIA_RULE: - this.reloadStylesheetImages(rule, path, expando); - } - } - }; - - Reloader.prototype.reloadStyleImages = function(style, styleNames, path, expando) { - var newValue, styleName, value, _i, _len, - _this = this; - for (_i = 0, _len = styleNames.length; _i < _len; _i++) { - styleName = styleNames[_i]; - value = style[styleName]; - if (typeof value === 'string') { - newValue = value.replace(/\burl\s*\(([^)]*)\)/, function(match, src) { - if (pathsMatch(path, pathFromUrl(src))) { - return "url(" + (_this.generateCacheBustUrl(src, expando)) + ")"; - } else { - return match; - } - }); - if (newValue !== value) { - style[styleName] = newValue; - } - } - } - }; - - Reloader.prototype.reloadStylesheet = function(path) { - var imported, link, links, match, style, _i, _j, _k, _l, _len, _len1, _len2, _len3, _ref, _ref1, - _this = this; - links = (function() { - var _i, _len, _ref, _results; - _ref = this.document.getElementsByTagName('link'); - _results = []; - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - link = _ref[_i]; - if (link.rel === 'stylesheet' && !link.__LiveReload_pendingRemoval) { - _results.push(link); - } - } - return _results; - }).call(this); - imported = []; - _ref = this.document.getElementsByTagName('style'); - for (_i = 0, _len = _ref.length; _i < _len; _i++) { - style = _ref[_i]; - if (style.sheet) { - this.collectImportedStylesheets(style, style.sheet, imported); - } - } - for (_j = 0, _len1 = links.length; _j < _len1; _j++) { - link = links[_j]; - this.collectImportedStylesheets(link, link.sheet, imported); - } - if (this.window.StyleFix && this.document.querySelectorAll) { - _ref1 = this.document.querySelectorAll('style[data-href]'); - for (_k = 0, _len2 = _ref1.length; _k < _len2; _k++) { - style = _ref1[_k]; - links.push(style); - } - } - this.console.log("LiveReload found " + links.length + " LINKed stylesheets, " + imported.length + " @imported stylesheets"); - match = pickBestMatch(path, links.concat(imported), function(l) { - return pathFromUrl(_this.linkHref(l)); - }); - if (match) { - if (match.object.rule) { - this.console.log("LiveReload is reloading imported stylesheet: " + match.object.href); - this.reattachImportedRule(match.object); - } else { - this.console.log("LiveReload is reloading stylesheet: " + (this.linkHref(match.object))); - this.reattachStylesheetLink(match.object); - } - } else { - this.console.log("LiveReload will reload all stylesheets because path '" + path + "' did not match any specific one"); - for (_l = 0, _len3 = links.length; _l < _len3; _l++) { - link = links[_l]; - this.reattachStylesheetLink(link); - } - } - return true; - }; - - Reloader.prototype.collectImportedStylesheets = function(link, styleSheet, result) { - var index, rule, rules, _i, _len; - try { - rules = styleSheet != null ? styleSheet.cssRules : void 0; - } catch (e) { - - } - if (rules && rules.length) { - for (index = _i = 0, _len = rules.length; _i < _len; index = ++_i) { - rule = rules[index]; - switch (rule.type) { - case CSSRule.CHARSET_RULE: - continue; - case CSSRule.IMPORT_RULE: - result.push({ - link: link, - rule: rule, - index: index, - href: rule.href - }); - this.collectImportedStylesheets(link, rule.styleSheet, result); - break; - default: - break; - } - } - } - }; - - Reloader.prototype.waitUntilCssLoads = function(clone, func) { - var callbackExecuted, executeCallback, poll, - _this = this; - callbackExecuted = false; - executeCallback = function() { - if (callbackExecuted) { - return; - } - callbackExecuted = true; - return func(); - }; - clone.onload = function() { - console.log("onload!"); - _this.knownToSupportCssOnLoad = true; - return executeCallback(); - }; - if (!this.knownToSupportCssOnLoad) { - (poll = function() { - if (clone.sheet) { - console.log("polling!"); - return executeCallback(); - } else { - return _this.Timer.start(50, poll); - } - })(); - } - return this.Timer.start(this.options.stylesheetReloadTimeout, executeCallback); - }; - - Reloader.prototype.linkHref = function(link) { - return link.href || link.getAttribute('data-href'); - }; - - Reloader.prototype.reattachStylesheetLink = function(link) { - var clone, parent, - _this = this; - if (link.__LiveReload_pendingRemoval) { - return; - } - link.__LiveReload_pendingRemoval = true; - if (link.tagName === 'STYLE') { - clone = this.document.createElement('link'); - clone.rel = 'stylesheet'; - clone.media = link.media; - clone.disabled = link.disabled; - } else { - clone = link.cloneNode(false); - } - clone.href = this.generateCacheBustUrl(this.linkHref(link)); - parent = link.parentNode; - if (parent.lastChild === link) { - parent.appendChild(clone); - } else { - parent.insertBefore(clone, link.nextSibling); - } - return this.waitUntilCssLoads(clone, function() { - var additionalWaitingTime; - if (/AppleWebKit/.test(navigator.userAgent)) { - additionalWaitingTime = 5; - } else { - additionalWaitingTime = 200; - } - return _this.Timer.start(additionalWaitingTime, function() { - var _ref; - if (!link.parentNode) { - return; - } - link.parentNode.removeChild(link); - clone.onreadystatechange = null; - return (_ref = _this.window.StyleFix) != null ? _ref.link(clone) : void 0; - }); - }); - }; - - Reloader.prototype.reattachImportedRule = function(_arg) { - var href, index, link, media, newRule, parent, rule, tempLink, - _this = this; - rule = _arg.rule, index = _arg.index, link = _arg.link; - parent = rule.parentStyleSheet; - href = this.generateCacheBustUrl(rule.href); - media = rule.media.length ? [].join.call(rule.media, ', ') : ''; - newRule = "@import url(\"" + href + "\") " + media + ";"; - rule.__LiveReload_newHref = href; - tempLink = this.document.createElement("link"); - tempLink.rel = 'stylesheet'; - tempLink.href = href; - tempLink.__LiveReload_pendingRemoval = true; - if (link.parentNode) { - link.parentNode.insertBefore(tempLink, link); - } - return this.Timer.start(this.importCacheWaitPeriod, function() { - if (tempLink.parentNode) { - tempLink.parentNode.removeChild(tempLink); - } - if (rule.__LiveReload_newHref !== href) { - return; - } - parent.insertRule(newRule, index); - parent.deleteRule(index + 1); - rule = parent.cssRules[index]; - rule.__LiveReload_newHref = href; - return _this.Timer.start(_this.importCacheWaitPeriod, function() { - if (rule.__LiveReload_newHref !== href) { - return; - } - parent.insertRule(newRule, index); - return parent.deleteRule(index + 1); - }); - }); - }; - - Reloader.prototype.generateUniqueString = function() { - return 'livereload=' + Date.now(); - }; - - Reloader.prototype.generateCacheBustUrl = function(url, expando) { - var hash, oldParams, params, _ref; - if (expando == null) { - expando = this.generateUniqueString(); - } - _ref = splitUrl(url), url = _ref.url, hash = _ref.hash, oldParams = _ref.params; - if (this.options.overrideURL) { - if (url.indexOf(this.options.serverURL) < 0) { - url = this.options.serverURL + this.options.overrideURL + "?url=" + encodeURIComponent(url); - } - } - params = oldParams.replace(/(\?|&)livereload=(\d+)/, function(match, sep) { - return "" + sep + expando; - }); - if (params === oldParams) { - if (oldParams.length === 0) { - params = "?" + expando; - } else { - params = "" + oldParams + "&" + expando; - } - } - return url + params + hash; - }; - - return Reloader; - - })(); - -}).call(this); - -// livereload -var Connector, LiveReload, Options, Reloader, Timer; - -Connector = __connector.Connector; - -Timer = __timer.Timer; - -Options = __options.Options; - -Reloader = __reloader.Reloader; - -__livereload.LiveReload = LiveReload = (function() { - - function LiveReload(window) { - var _this = this; - this.window = window; - this.listeners = {}; - this.plugins = []; - this.pluginIdentifiers = {}; - this.console = this.window.location.href.match(/LR-verbose/) && this.window.console && this.window.console.log && this.window.console.error ? this.window.console : { - log: function() {}, - error: function() {} - }; - if (!(this.WebSocket = this.window.WebSocket || this.window.MozWebSocket)) { - console.error("LiveReload disabled because the browser does not seem to support web sockets"); - return; - } - if (!(this.options = Options.extract(this.window.document))) { - console.error("LiveReload disabled because it could not find its own ' - ) - - if status_code != 304: - if "content-length" not in header_set: - headers.append(("Content-Length", str(len(body)))) - if "content-type" not in header_set: - headers.append(("Content-Type", "text/html; charset=UTF-8")) - if "server" not in header_set: - headers.append(("Server", "livereload-tornado")) - - parts = [escape.utf8("HTTP/1.1 " + data["status"] + "\r\n")] - for key, value in headers: - if key.lower() == 'content-length': - value = str(len(body)) - parts.append( - escape.utf8(key) + b": " + escape.utf8(value) + b"\r\n" - ) - parts.append(b"\r\n") - parts.append(body) - request.write(b"".join(parts)) - request.finish() - self._log(status_code, request) - - -class Server(object): - """Livereload server interface. - - Initialize a server and watch file changes:: - - server = Server(wsgi_app) - server.serve() - - :param app: a wsgi application instance - :param watcher: A Watcher instance, you don't have to initialize - it by yourself. Under Linux, you will want to install - pyinotify and use INotifyWatcher() to avoid wasted - CPU usage. - """ - def __init__(self, app=None, watcher=None): - self.app = app - self.port = 5500 - self.root = None - if not watcher: - watcher = Watcher() - self.watcher = watcher - - def watch(self, filepath, func=None): - """Add the given filepath for watcher list. - - Once you have intialized a server, watch file changes before - serve the server:: - - server.watch('static/*.stylus', 'make static') - def alert(): - print('foo') - server.watch('foo.txt', alert) - server.serve() - - :param filepath: files to be watched, it can be a filepath, - a directory, or a glob pattern - :param func: the function to be called, it can be a string of - shell command, or any callable object without - parameters - """ - if isinstance(func, text_types): - func = shell(func) - - self.watcher.watch(filepath, func) - - def application(self, debug=True): - LiveReloadHandler.watcher = self.watcher - handlers = [ - (r'/livereload', LiveReloadHandler), - (r'/forcereload', ForceReloadHandler), - (r'/livereload.js', LiveReloadJSHandler, dict(port=self.port)), - ] - - if self.app: - self.app = WSGIWrapper(self.app) - handlers.append( - (r'.*', FallbackHandler, dict(fallback=self.app)) - ) - else: - handlers.append( - (r'(.*)', StaticHandler, dict(root=self.root or '.')), - ) - return Application(handlers=handlers, debug=debug) - - def serve(self, port=None, host=None, root=None, debug=True, open_url=False): - """Start serve the server with the given port. - - :param port: serve on this port, default is 5500 - :param host: serve on this hostname, default is 0.0.0.0 - :param root: serve static on this root directory - :param open_url: open system browser - """ - if root: - self.root = root - if port: - self.port = port - if host is None: - host = '' - - self.application(debug=debug).listen(self.port, address=host) - logging.getLogger().setLevel(logging.INFO) - - host = host or '127.0.0.1' - print('Serving on %s:%s' % (host, self.port)) - - # Async open web browser after 5 sec timeout - if open_url: - def opener(): - time.sleep(5) - webbrowser.open('http://%s:%s' % (host, self.port)) - threading.Thread(target=opener).start() - - try: - IOLoop.instance().start() - except KeyboardInterrupt: - print('Shutting down...') diff --git a/libs/livereload/watcher.py b/libs/livereload/watcher.py deleted file mode 100644 index 2f15cb1..0000000 --- a/libs/livereload/watcher.py +++ /dev/null @@ -1,132 +0,0 @@ -# -*- coding: utf-8 -*- -""" - livereload.watcher - ~~~~~~~~~~~~~~~~~~ - - A file watch management for LiveReload Server. - - :copyright: (c) 2013 by Hsiaoming Yang -""" - -import os -import glob -import time - - -class Watcher(object): - """A file watcher registery.""" - def __init__(self): - self._tasks = {} - self._mtimes = {} - - # filepath that is changed - self.filepath = None - self._start = time.time() - - def ignore(self, filename): - """Ignore a given filename or not.""" - _, ext = os.path.splitext(filename) - return ext in ['.pyc', '.pyo', '.o', '.swp'] - - def watch(self, path, func=None): - """Add a task to watcher.""" - self._tasks[path] = func - - def start(self, callback): - """Start the watcher running, calling callback when changes are observed. If this returns False, - regular polling will be used.""" - return False - - def examine(self): - """Check if there are changes, if true, run the given task.""" - # clean filepath - self.filepath = None - for path in self._tasks: - if self.is_changed(path): - func = self._tasks[path] - # run function - func and func() - return self.filepath - - def is_changed(self, path): - if os.path.isfile(path): - return self.is_file_changed(path) - elif os.path.isdir(path): - return self.is_folder_changed(path) - return self.is_glob_changed(path) - - def is_file_changed(self, path): - if not os.path.isfile(path): - return False - - if self.ignore(path): - return False - - mtime = os.path.getmtime(path) - - if path not in self._mtimes: - self._mtimes[path] = mtime - self.filepath = path - return mtime > self._start - - if self._mtimes[path] != mtime: - self._mtimes[path] = mtime - self.filepath = path - return True - - self._mtimes[path] = mtime - return False - - def is_folder_changed(self, path): - for root, dirs, files in os.walk(path, followlinks=True): - if '.git' in dirs: - dirs.remove('.git') - if '.hg' in dirs: - dirs.remove('.hg') - if '.svn' in dirs: - dirs.remove('.svn') - if '.cvs' in dirs: - dirs.remove('.cvs') - - for f in files: - if self.is_file_changed(os.path.join(root, f)): - return True - return False - - def is_glob_changed(self, path): - for f in glob.glob(path): - if self.is_file_changed(f): - return True - return False - - -class INotifyWatcher(Watcher): - def __init__(self): - Watcher.__init__(self) - - import pyinotify - self.wm = pyinotify.WatchManager() - self.notifier = None - self.callback = None - - def watch(self, path, func=None): - import pyinotify - flag = pyinotify.IN_CREATE | pyinotify.IN_DELETE | pyinotify.IN_MODIFY - self.wm.add_watch(path, flag, rec=True, do_glob=True, auto_add=True) - Watcher.watch(self, path, func) - - def inotify_event(self, event): - self.callback() - - def start(self, callback): - if not self.notifier: - self.callback = callback - - import pyinotify - from tornado import ioloop - self.notifier = pyinotify.TornadoAsyncNotifier( - self.wm, ioloop.IOLoop.instance(), - default_proc_fun=self.inotify_event - ) - callback() - return True From 4291e2233d8f9918805dc42469489ad3d630eccb Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 13 Jul 2014 12:23:27 +0200 Subject: [PATCH 010/301] Releader class --- couchpotato/static/scripts/reloader.js | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 couchpotato/static/scripts/reloader.js diff --git a/couchpotato/static/scripts/reloader.js b/couchpotato/static/scripts/reloader.js new file mode 100644 index 0000000..3a4eec8 --- /dev/null +++ b/couchpotato/static/scripts/reloader.js @@ -0,0 +1,24 @@ +var ReloaderBase = new Class({ + + initialize: function(){ + var self = this; + + App.on('watcher.changed', self.reloadFile.bind(self)) + + }, + + reloadFile: function(data){ + var self = this, + urls = data.data; + + urls.each(function(url){ + var without_timestamp = url.split('?')[0], + old_links = document.getElement('[data-url^=\''+without_timestamp+'\']'); + + old_links.set('href', old_links.get('href') + 1) + }) + } + +}); + +var Reloader = new ReloaderBase(); From 2a0e46fe00b6f82a0df4215e5f48beb3fc6f5493 Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 3 Dec 2014 23:19:22 +0100 Subject: [PATCH 011/301] Dev tools --- .gitignore | 2 + Gruntfile.js | 120 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ config.rb | 45 ++++++++++++++++++++++ package.json | 25 +++++++++++++ 4 files changed, 192 insertions(+) create mode 100644 Gruntfile.js create mode 100644 config.rb create mode 100644 package.json diff --git a/.gitignore b/.gitignore index e156f87..873da1c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ /_source/ .project .pydevproject +node_modules +.tmp \ No newline at end of file diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..27bba8b --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,120 @@ +'use strict'; + +module.exports = function(grunt){ + + // Configurable paths + var config = { + tmp: '.tmp', + base: 'couchpotato' + }; + + grunt.initConfig({ + + // Project settings + config: config, + + // Make sure code styles are up to par and there are no obvious mistakes + jshint: { + options: { + reporter: require('jshint-stylish'), + unused: false, + camelcase: false, + devel: true + }, + all: [ + '<%= config.base %>/{,**/}*.js' + ] + }, + + // Compiles Sass to CSS and generates necessary files if requested + sass: { + options: { + compass: true + }, + dist: { + files: [{ + expand: true, + cwd: '<%= config.base %>/styles', + src: ['*.scss'], + dest: '<%= config.tmp %>/styles', + ext: '.css' + }] + }, + server: { + files: [{ + expand: true, + cwd: '<%= config.base %>/', + src: ['**/*.scss'], + dest: '<%= config.tmp %>/styles', + ext: '.css' + }] + } + }, + + // Add vendor prefixed styles + autoprefixer: { + options: { + browsers: ['> 1%', 'Android >= 2.1', 'Chrome >= 21', 'Explorer >= 7', 'Firefox >= 17', 'Opera >= 12.1', 'Safari >= 6.0'] + }, + dist: { + files: [{ + expand: true, + cwd: '<%= config.tmp %>/styles/', + src: '{,**/}*.css', + dest: '<%= config.tmp %>/styles/' + }] + } + }, + + // COOL TASKS ============================================================== + watch: { + scss: { + files: ['**/*.{scss,sass}'], + tasks: ['sass:server', 'autoprefixer'], + options: { + 'livereload': true + } + }, + js: { + files: [ + '<%= config.base %>/scripts/**/*.js' + ], + tasks: ['jshint'], + options: { + 'livereload': true + } + }, + livereload: { + options: { + livereload: 35729 + }, + files: [ + '<%= config.base %>/{,*/}*.html', + '<%= config.tmp %>/styles/{,*/}*.css', + '<%= config.base %>/react/{,*/}*.js', + '<%= config.base %>/images/{,*/}*' + ] + } + }, + + concurrent: { + options: { + logConcurrentOutput: true + }, + tasks: ['sass:server', 'watch'] + } + + }); + + grunt.loadNpmTasks('grunt-contrib-jshint'); + //grunt.loadNpmTasks('grunt-contrib-uglify'); + grunt.loadNpmTasks('grunt-contrib-sass'); + //grunt.loadNpmTasks('grunt-contrib-cssmin'); + grunt.loadNpmTasks('grunt-contrib-watch'); + grunt.loadNpmTasks('grunt-autoprefixer'); + grunt.loadNpmTasks('grunt-concurrent'); + grunt.loadNpmTasks('grunt-contrib-concat'); + + grunt.registerTask('default', ['sass', 'concurrent']); + +}; diff --git a/config.rb b/config.rb new file mode 100644 index 0000000..b9b7e67 --- /dev/null +++ b/config.rb @@ -0,0 +1,45 @@ +# First, require any additional compass plugins installed on your system. +# require 'zen-grids' +require 'susy' +# require 'breakpoint' + + +# Toggle this between :development and :production when deploying the CSS to the +# live server. Development mode will retain comments and spacing from the +# original Sass source and adds line numbering comments for easier debugging. +environment = :development +# environment = :development + +# In development, we can turn on the FireSass-compatible debug_info. +firesass = false +# firesass = true + + +# Location of the your project's resources. + + +# Set this to the root of your project. All resource locations above are +# considered to be relative to this path. +http_path = "/" + +# To use relative paths to assets in your compiled CSS files, set this to true. +# relative_assets = true + + +## +## You probably don't need to edit anything below this. +## + +sass_dir = "./" +css_dir = "./static/style_compiled" + +# You can select your preferred output style here (can be overridden via the command line): +# output_style = :expanded or :nested or :compact or :compressed +output_style = (environment == :development) ? :expanded : :compressed + +# To disable debugging comments that display the original location of your selectors. Uncomment: +# line_comments = false + +# Pass options to sass. For development, we turn on the FireSass-compatible +# debug_info if the firesass config variable above is true. +sass_options = (environment == :development && firesass == true) ? {:debug_info => true} : {} diff --git a/package.json b/package.json new file mode 100644 index 0000000..5f9a19c --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "couchpotato_develop", + "repository": { + "type": "git", + "url": "" + }, + "scripts": { + "start": "grunt" + }, + "dependencies": { + }, + "devDependencies": { + "grunt": "~0.4.5", + "grunt-autoprefixer": "^2.0.0", + "grunt-concurrent": "~1.0.0", + "grunt-contrib-concat": "^0.5.0", + "grunt-contrib-cssmin": "~0.10.0", + "grunt-contrib-jshint": "~0.10.0", + "grunt-contrib-less": "~0.12.0", + "grunt-contrib-sass": "^0.8.1", + "grunt-contrib-uglify": "~0.6.0", + "grunt-contrib-watch": "~0.6.1", + "jshint-stylish": "^1.0.0" + } +} From 62cb57f217ef48c7c905933734f76a4379647066 Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 3 Dec 2014 23:30:14 +0100 Subject: [PATCH 012/301] Concat --- Gruntfile.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/Gruntfile.js b/Gruntfile.js index 27bba8b..7746464 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -66,11 +66,21 @@ module.exports = function(grunt){ } }, + concat: { + options: { + separator: '' + }, + dist: { + src: ['<%= config.tmp %>/styles/**/*.css'], + dest: '<%= config.tmp %>/test.css' + } + }, + // COOL TASKS ============================================================== watch: { scss: { files: ['**/*.{scss,sass}'], - tasks: ['sass:server', 'autoprefixer'], + tasks: ['sass:server', 'autoprefixer', 'concat'], options: { 'livereload': true } From 87086a0336533b37b39b409c13d8a9a0295229ab Mon Sep 17 00:00:00 2001 From: Ruud Date: Thu, 4 Dec 2014 23:22:14 +0100 Subject: [PATCH 013/301] Rename to scss --- Gruntfile.js | 37 +- .../core/media/_base/search/static/search.css | 273 ----- .../core/media/_base/search/static/search.scss | 273 +++++ .../core/media/movie/_base/static/movie.css | 1074 -------------------- .../core/media/movie/_base/static/movie.scss | 1074 ++++++++++++++++++++ .../core/media/movie/charts/static/charts.css | 274 ----- .../core/media/movie/charts/static/charts.scss | 274 +++++ .../core/media/movie/suggestion/static/suggest.css | 162 --- .../media/movie/suggestion/static/suggest.scss | 162 +++ .../core/plugins/category/static/category.css | 82 -- .../core/plugins/category/static/category.scss | 82 ++ couchpotato/core/plugins/log/static/log.css | 199 ---- couchpotato/core/plugins/log/static/log.scss | 199 ++++ .../core/plugins/profile/static/profile.css | 197 ---- .../core/plugins/profile/static/profile.scss | 197 ++++ .../core/plugins/quality/static/quality.css | 26 - .../core/plugins/quality/static/quality.scss | 26 + .../core/plugins/userscript/static/userscript.css | 38 - .../core/plugins/userscript/static/userscript.scss | 38 + couchpotato/core/plugins/wizard/static/wizard.css | 84 -- couchpotato/core/plugins/wizard/static/wizard.scss | 84 ++ couchpotato/static/style/api.css | 162 --- couchpotato/static/style/api.scss | 162 +++ couchpotato/static/style/main.css | 965 ------------------ couchpotato/static/style/main_old.scss | 965 ++++++++++++++++++ couchpotato/static/style/settings.css | 820 --------------- couchpotato/static/style/settings.scss | 820 +++++++++++++++ couchpotato/static/style/uniform.css | 154 --- couchpotato/static/style/uniform.generic.css | 139 --- couchpotato/static/style/uniform.generic.scss | 139 +++ couchpotato/static/style/uniform.scss | 154 +++ 31 files changed, 4662 insertions(+), 4673 deletions(-) delete mode 100644 couchpotato/core/media/_base/search/static/search.css create mode 100644 couchpotato/core/media/_base/search/static/search.scss delete mode 100644 couchpotato/core/media/movie/_base/static/movie.css create mode 100644 couchpotato/core/media/movie/_base/static/movie.scss delete mode 100644 couchpotato/core/media/movie/charts/static/charts.css create mode 100644 couchpotato/core/media/movie/charts/static/charts.scss delete mode 100644 couchpotato/core/media/movie/suggestion/static/suggest.css create mode 100644 couchpotato/core/media/movie/suggestion/static/suggest.scss delete mode 100644 couchpotato/core/plugins/category/static/category.css create mode 100644 couchpotato/core/plugins/category/static/category.scss delete mode 100644 couchpotato/core/plugins/log/static/log.css create mode 100644 couchpotato/core/plugins/log/static/log.scss delete mode 100644 couchpotato/core/plugins/profile/static/profile.css create mode 100644 couchpotato/core/plugins/profile/static/profile.scss delete mode 100644 couchpotato/core/plugins/quality/static/quality.css create mode 100644 couchpotato/core/plugins/quality/static/quality.scss delete mode 100644 couchpotato/core/plugins/userscript/static/userscript.css create mode 100644 couchpotato/core/plugins/userscript/static/userscript.scss delete mode 100644 couchpotato/core/plugins/wizard/static/wizard.css create mode 100644 couchpotato/core/plugins/wizard/static/wizard.scss delete mode 100644 couchpotato/static/style/api.css create mode 100644 couchpotato/static/style/api.scss delete mode 100644 couchpotato/static/style/main.css create mode 100644 couchpotato/static/style/main_old.scss delete mode 100644 couchpotato/static/style/settings.css create mode 100644 couchpotato/static/style/settings.scss delete mode 100644 couchpotato/static/style/uniform.css delete mode 100644 couchpotato/static/style/uniform.generic.css create mode 100644 couchpotato/static/style/uniform.generic.scss create mode 100644 couchpotato/static/style/uniform.scss diff --git a/Gruntfile.js b/Gruntfile.js index 7746464..b95b9e6 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -5,7 +5,8 @@ module.exports = function(grunt){ // Configurable paths var config = { tmp: '.tmp', - base: 'couchpotato' + base: 'couchpotato', + css_dest: 'couchpotato/static/style/combined.min.css' }; grunt.initConfig({ @@ -29,23 +30,15 @@ module.exports = function(grunt){ // Compiles Sass to CSS and generates necessary files if requested sass: { options: { - compass: true - }, - dist: { - files: [{ - expand: true, - cwd: '<%= config.base %>/styles', - src: ['*.scss'], - dest: '<%= config.tmp %>/styles', - ext: '.css' - }] + compass: true, + update: true }, server: { files: [{ expand: true, cwd: '<%= config.base %>/', src: ['**/*.scss'], - dest: '<%= config.tmp %>/styles', + dest: '<%= config.tmp %>/styles/', ext: '.css' }] } @@ -66,13 +59,11 @@ module.exports = function(grunt){ } }, - concat: { - options: { - separator: '' - }, + cssmin: { dist: { - src: ['<%= config.tmp %>/styles/**/*.css'], - dest: '<%= config.tmp %>/test.css' + files: { + '<%= config.css_dest %>': ['<%= config.tmp %>/styles/**/*.css'] + } } }, @@ -80,7 +71,7 @@ module.exports = function(grunt){ watch: { scss: { files: ['**/*.{scss,sass}'], - tasks: ['sass:server', 'autoprefixer', 'concat'], + tasks: ['sass:server', 'autoprefixer', 'cssmin'], options: { 'livereload': true } @@ -101,7 +92,6 @@ module.exports = function(grunt){ files: [ '<%= config.base %>/{,*/}*.html', '<%= config.tmp %>/styles/{,*/}*.css', - '<%= config.base %>/react/{,*/}*.js', '<%= config.base %>/images/{,*/}*' ] } @@ -111,7 +101,7 @@ module.exports = function(grunt){ options: { logConcurrentOutput: true }, - tasks: ['sass:server', 'watch'] + tasks: ['sass:server', 'autoprefixer', 'cssmin', 'watch'] } }); @@ -119,12 +109,11 @@ module.exports = function(grunt){ grunt.loadNpmTasks('grunt-contrib-jshint'); //grunt.loadNpmTasks('grunt-contrib-uglify'); grunt.loadNpmTasks('grunt-contrib-sass'); - //grunt.loadNpmTasks('grunt-contrib-cssmin'); + grunt.loadNpmTasks('grunt-contrib-cssmin'); grunt.loadNpmTasks('grunt-contrib-watch'); grunt.loadNpmTasks('grunt-autoprefixer'); grunt.loadNpmTasks('grunt-concurrent'); - grunt.loadNpmTasks('grunt-contrib-concat'); - grunt.registerTask('default', ['sass', 'concurrent']); + grunt.registerTask('default', ['concurrent']); }; diff --git a/couchpotato/core/media/_base/search/static/search.css b/couchpotato/core/media/_base/search/static/search.css deleted file mode 100644 index def985f..0000000 --- a/couchpotato/core/media/_base/search/static/search.css +++ /dev/null @@ -1,273 +0,0 @@ -.search_form { - display: inline-block; - vertical-align: middle; - text-align: right; - transition: all .4s cubic-bezier(0.9,0,0.1,1); - z-index: 20; - border: 0 solid transparent; - border-bottom-width: 4px; -} - .search_form:hover { - border-color: #047792; - } - - @media all and (max-width: 480px) { - .search_form { - right: 44px; - } - } - - .search_form.focused, - .search_form.shown { - border-color: #04bce6; - } - - .search_form .input { - height: 100%; - overflow: hidden; - width: 45px; - transition: all .4s cubic-bezier(0.9,0,0.1,1); - } - - .search_form.focused .input, - .search_form.shown .input { - width: 380px; - background: #4e5969; - } - - .search_form .input input { - border-radius: 0; - display: block; - border: 0; - background: none; - color: #FFF; - font-size: 25px; - height: 100%; - width: 100%; - opacity: 0; - padding: 0 40px 0 10px; - transition: all .4s ease-in-out .2s; - } - .search_form.focused .input input, - .search_form.shown .input input { - opacity: 1; - } - - .search_form input::-ms-clear { - width : 0; - height: 0; - } - - @media all and (max-width: 480px) { - .search_form .input input { - font-size: 15px; - } - - .search_form.focused .input, - .search_form.shown .input { - width: 277px; - } - } - - .search_form .input a { - position: absolute; - top: 0; - right: 0; - width: 44px; - height: 100%; - cursor: pointer; - vertical-align: middle; - text-align: center; - line-height: 66px; - font-size: 15px; - color: #FFF; - } - - .search_form .input a:after { - content: "\e03e"; - } - - .search_form.shown.filled .input a:after { - content: "\e04e"; - } - - @media all and (max-width: 480px) { - .search_form .input a { - line-height: 44px; - } - } - - .search_form .results_container { - text-align: left; - position: absolute; - background: #5c697b; - margin: 4px 0 0; - width: 470px; - min-height: 50px; - box-shadow: 0 20px 20px -10px rgba(0,0,0,0.55); - display: none; - } - @media all and (max-width: 480px) { - .search_form .results_container { - width: 320px; - } - } - .search_form.focused.filled .results_container, - .search_form.shown.filled .results_container { - display: block; - } - - .search_form .results { - max-height: 570px; - overflow-x: hidden; - } - - .media_result { - overflow: hidden; - height: 50px; - position: relative; - } - - .media_result .options { - position: absolute; - height: 100%; - top: 0; - left: 30px; - right: 0; - padding: 13px; - border: 1px solid transparent; - border-width: 1px 0; - border-radius: 0; - box-shadow: inset 0 1px 8px rgba(0,0,0,0.25); - } - .media_result .options > .in_library_wanted { - margin-top: -7px; - } - - .media_result .options > div { - border: 0; - } - - .media_result .options .thumbnail { - vertical-align: middle; - } - - .media_result .options select { - vertical-align: middle; - display: inline-block; - margin-right: 10px; - } - .media_result .options select[name=title] { width: 170px; } - .media_result .options select[name=profile] { width: 90px; } - .media_result .options select[name=category] { width: 80px; } - - @media all and (max-width: 480px) { - - .media_result .options select[name=title] { width: 90px; } - .media_result .options select[name=profile] { width: 50px; } - .media_result .options select[name=category] { width: 50px; } - - } - - .media_result .options .button { - vertical-align: middle; - display: inline-block; - } - - .media_result .options .message { - height: 100%; - font-size: 20px; - color: #fff; - line-height: 20px; - } - - .media_result .data { - position: absolute; - height: 100%; - top: 0; - left: 30px; - right: 0; - background: #5c697b; - cursor: pointer; - border-top: 1px solid rgba(255,255,255, 0.08); - transition: all .4s cubic-bezier(0.9,0,0.1,1); - } - .media_result .data.open { - left: 100% !important; - } - - .media_result:last-child .data { border-bottom: 0; } - - .media_result .in_wanted, .media_result .in_library { - position: absolute; - bottom: 2px; - left: 14px; - font-size: 11px; - } - - .media_result .thumbnail { - width: 34px; - min-height: 100%; - display: block; - margin: 0; - vertical-align: top; - } - - .media_result .info { - position: absolute; - top: 20%; - left: 15px; - right: 7px; - vertical-align: middle; - } - - .media_result .info h2 { - margin: 0; - font-weight: normal; - font-size: 20px; - padding: 0; - } - - .search_form .info h2 { - position: absolute; - width: 100%; - } - - .media_result .info h2 .title { - display: block; - margin: 0; - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - } - - .search_form .info h2 .title { - position: absolute; - width: 88%; - } - - .media_result .info h2 .year { - padding: 0 5px; - text-align: center; - position: absolute; - width: 12%; - right: 0; - } - - @media all and (max-width: 480px) { - - .search_form .info h2 .year { - font-size: 12px; - margin-top: 7px; - } - - } - -.search_form .mask, -.media_result .mask { - position: absolute; - height: 100%; - width: 100%; - left: 0; - top: 0; -} diff --git a/couchpotato/core/media/_base/search/static/search.scss b/couchpotato/core/media/_base/search/static/search.scss new file mode 100644 index 0000000..def985f --- /dev/null +++ b/couchpotato/core/media/_base/search/static/search.scss @@ -0,0 +1,273 @@ +.search_form { + display: inline-block; + vertical-align: middle; + text-align: right; + transition: all .4s cubic-bezier(0.9,0,0.1,1); + z-index: 20; + border: 0 solid transparent; + border-bottom-width: 4px; +} + .search_form:hover { + border-color: #047792; + } + + @media all and (max-width: 480px) { + .search_form { + right: 44px; + } + } + + .search_form.focused, + .search_form.shown { + border-color: #04bce6; + } + + .search_form .input { + height: 100%; + overflow: hidden; + width: 45px; + transition: all .4s cubic-bezier(0.9,0,0.1,1); + } + + .search_form.focused .input, + .search_form.shown .input { + width: 380px; + background: #4e5969; + } + + .search_form .input input { + border-radius: 0; + display: block; + border: 0; + background: none; + color: #FFF; + font-size: 25px; + height: 100%; + width: 100%; + opacity: 0; + padding: 0 40px 0 10px; + transition: all .4s ease-in-out .2s; + } + .search_form.focused .input input, + .search_form.shown .input input { + opacity: 1; + } + + .search_form input::-ms-clear { + width : 0; + height: 0; + } + + @media all and (max-width: 480px) { + .search_form .input input { + font-size: 15px; + } + + .search_form.focused .input, + .search_form.shown .input { + width: 277px; + } + } + + .search_form .input a { + position: absolute; + top: 0; + right: 0; + width: 44px; + height: 100%; + cursor: pointer; + vertical-align: middle; + text-align: center; + line-height: 66px; + font-size: 15px; + color: #FFF; + } + + .search_form .input a:after { + content: "\e03e"; + } + + .search_form.shown.filled .input a:after { + content: "\e04e"; + } + + @media all and (max-width: 480px) { + .search_form .input a { + line-height: 44px; + } + } + + .search_form .results_container { + text-align: left; + position: absolute; + background: #5c697b; + margin: 4px 0 0; + width: 470px; + min-height: 50px; + box-shadow: 0 20px 20px -10px rgba(0,0,0,0.55); + display: none; + } + @media all and (max-width: 480px) { + .search_form .results_container { + width: 320px; + } + } + .search_form.focused.filled .results_container, + .search_form.shown.filled .results_container { + display: block; + } + + .search_form .results { + max-height: 570px; + overflow-x: hidden; + } + + .media_result { + overflow: hidden; + height: 50px; + position: relative; + } + + .media_result .options { + position: absolute; + height: 100%; + top: 0; + left: 30px; + right: 0; + padding: 13px; + border: 1px solid transparent; + border-width: 1px 0; + border-radius: 0; + box-shadow: inset 0 1px 8px rgba(0,0,0,0.25); + } + .media_result .options > .in_library_wanted { + margin-top: -7px; + } + + .media_result .options > div { + border: 0; + } + + .media_result .options .thumbnail { + vertical-align: middle; + } + + .media_result .options select { + vertical-align: middle; + display: inline-block; + margin-right: 10px; + } + .media_result .options select[name=title] { width: 170px; } + .media_result .options select[name=profile] { width: 90px; } + .media_result .options select[name=category] { width: 80px; } + + @media all and (max-width: 480px) { + + .media_result .options select[name=title] { width: 90px; } + .media_result .options select[name=profile] { width: 50px; } + .media_result .options select[name=category] { width: 50px; } + + } + + .media_result .options .button { + vertical-align: middle; + display: inline-block; + } + + .media_result .options .message { + height: 100%; + font-size: 20px; + color: #fff; + line-height: 20px; + } + + .media_result .data { + position: absolute; + height: 100%; + top: 0; + left: 30px; + right: 0; + background: #5c697b; + cursor: pointer; + border-top: 1px solid rgba(255,255,255, 0.08); + transition: all .4s cubic-bezier(0.9,0,0.1,1); + } + .media_result .data.open { + left: 100% !important; + } + + .media_result:last-child .data { border-bottom: 0; } + + .media_result .in_wanted, .media_result .in_library { + position: absolute; + bottom: 2px; + left: 14px; + font-size: 11px; + } + + .media_result .thumbnail { + width: 34px; + min-height: 100%; + display: block; + margin: 0; + vertical-align: top; + } + + .media_result .info { + position: absolute; + top: 20%; + left: 15px; + right: 7px; + vertical-align: middle; + } + + .media_result .info h2 { + margin: 0; + font-weight: normal; + font-size: 20px; + padding: 0; + } + + .search_form .info h2 { + position: absolute; + width: 100%; + } + + .media_result .info h2 .title { + display: block; + margin: 0; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + } + + .search_form .info h2 .title { + position: absolute; + width: 88%; + } + + .media_result .info h2 .year { + padding: 0 5px; + text-align: center; + position: absolute; + width: 12%; + right: 0; + } + + @media all and (max-width: 480px) { + + .search_form .info h2 .year { + font-size: 12px; + margin-top: 7px; + } + + } + +.search_form .mask, +.media_result .mask { + position: absolute; + height: 100%; + width: 100%; + left: 0; + top: 0; +} diff --git a/couchpotato/core/media/movie/_base/static/movie.css b/couchpotato/core/media/movie/_base/static/movie.css deleted file mode 100644 index 311b111..0000000 --- a/couchpotato/core/media/movie/_base/static/movie.css +++ /dev/null @@ -1,1074 +0,0 @@ -.movies { - padding: 10px 0 20px; - position: relative; - z-index: 3; - width: 100%; -} - - .movies > div { - clear: both; - } - - .movies > div .message { - display: block; - padding: 20px; - font-size: 20px; - color: white; - text-align: center; - } - .movies > div .message a { - padding: 20px; - display: block; - } - - .movies.thumbs_list > div:not(.description) { - margin-right: -4px; - } - - .movies .loading { - display: block; - padding: 20px 0 0 0; - width: 100%; - z-index: 3; - transition: all .4s cubic-bezier(0.9,0,0.1,1); - height: 40px; - opacity: 1; - position: absolute; - text-align: center; - } - .movies .loading.hide { - height: 0; - padding: 0; - opacity: 0; - margin-top: -20px; - overflow: hidden; - } - - .movies .loading .spinner { - display: inline-block; - } - - .movies .loading .message { - margin: 0 20px; - } - - .movies h2 { - margin-bottom: 20px; - } - - @media all and (max-width: 480px) { - .movies h2 { - font-size: 25px; - margin-bottom: 10px; - } - } - - .movies > .description { - position: absolute; - top: 30px; - right: 0; - font-style: italic; - opacity: 0.8; - } - .movies:hover > .description { - opacity: 1; - } - - @media all and (max-width: 860px) { - .movies > .description { - display: none; - } - } - - .movies.thumbs_list { - padding: 20px 0 20px; - } - - .home .movies { - padding-top: 6px; - } - - .movies .movie { - position: relative; - margin: 10px 0; - padding-left: 20px; - overflow: hidden; - width: 100%; - height: 180px; - transition: all 0.6s cubic-bezier(0.9,0,0.1,1); - transition-property: width, height; - background: rgba(0,0,0,.2); - } - - .movies.mass_edit_list .movie { - padding-left: 22px; - background: none; - } - - .movies.details_list .movie { - padding-left: 120px; - } - - .movies.list_list .movie:not(.details_view), - .movies.mass_edit_list .movie { - height: 30px; - border-bottom: 1px solid rgba(255,255,255,.15); - } - - .movies.list_list .movie:last-child, - .movies.mass_edit_list .movie:last-child { - border: none; - } - - .movies.thumbs_list .movie { - width: 16.66667%; - height: auto; - min-height: 200px; - display: inline-block; - margin: 0; - padding: 0; - vertical-align: top; - line-height: 0; - } - - @media all and (max-width: 800px) { - .movies.thumbs_list .movie { - width: 25%; - min-height: 100px; - } - } - - .movies .movie .mask { - position: absolute; - top: 0; - left: 0; - height: 100%; - width: 100%; - } - - .movies.list_list .movie:not(.details_view), - .movies.mass_edit_list .movie { - margin: 0; - } - - .movies .data { - padding: 20px; - height: 100%; - width: 100%; - position: relative; - transition: all .6s cubic-bezier(0.9,0,0.1,1); - right: 0; - } - .movies.list_list .movie:not(.details_view) .data, - .movies.mass_edit_list .movie .data { - padding: 0 0 0 10px; - border: 0; - background: #4e5969; - } - .movies.mass_edit_list .movie .data { - padding-left: 8px; - } - - .movies.thumbs_list .data { - position: absolute; - left: 0; - top: 0; - width: 100%; - padding: 10px; - height: 100%; - background: none; - transition: none; - } - - .movies.thumbs_list .movie:hover .data { - background: rgba(0,0,0,0.9); - } - - .movies .data.hide_right { - right: -100%; - } - - .movies .movie .check { - display: none; - } - - .movies.mass_edit_list .movie .check { - position: absolute; - left: 0; - top: 0; - display: block; - margin: 7px 0 0 5px; - } - - .movies .poster { - position: absolute; - left: 0; - width: 120px; - line-height: 0; - overflow: hidden; - height: 100%; - transition: all .6s cubic-bezier(0.9,0,0.1,1); - background: rgba(0,0,0,.1); - } - .movies.thumbs_list .poster { - position: relative; - } - .movies.list_list .movie:not(.details_view) .poster, - .movies.mass_edit_list .poster { - width: 20px; - height: 30px; - } - .movies.mass_edit_list .poster { - display: none; - } - - .movies.thumbs_list .poster { - width: 100%; - height: 100%; - transition: none; - background: no-repeat center; - background-size: cover; - } - .movies.thumbs_list .no_thumbnail .empty_file { - width: 100%; - height: 100%; - } - - .movies .poster img, - .options .poster img { - width: 100%; - height: 100%; - } - .movies.thumbs_list .poster img { - height: auto; - width: 100%; - top: 0; - bottom: 0; - opacity: 0; - } - - .movies .info { - position: relative; - height: 100%; - width: 100%; - } - - .movies .info .title { - font-size: 28px; - font-weight: bold; - margin-bottom: 10px; - margin-top: 2px; - width: 100%; - padding-right: 80px; - transition: all 0.2s linear; - height: 35px; - top: -5px; - position: relative; - } - .movies.list_list .info .title, - .movies.mass_edit_list .info .title { - height: 100%; - top: 0; - margin: 0; - } - .touch_enabled .movies.list_list .info .title { - display: inline-block; - padding-right: 55px; - } - - .movies .info .title span { - display: inline-block; - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - width: 100%; - height: 100%; - line-height: 30px; - } - - .movies.thumbs_list .info .title span { - white-space: normal; - overflow: auto; - height: auto; - text-align: left; - } - - @media all and (max-width: 480px) { - .movies.thumbs_list .movie .info .title span, - .movies.thumbs_list .movie .info .year { - font-size: 15px; - line-height: 15px; - overflow: hidden; - } - } - - .movies.list_list .movie:not(.details_view) .info .title, - .movies.mass_edit_list .info .title { - font-size: 16px; - font-weight: normal; - width: auto; - } - - .movies.thumbs_list .movie:not(.no_thumbnail) .info { - display: none; - } - .movies.thumbs_list .movie:hover .info { - display: block; - } - - .movies.thumbs_list .info .title { - font-size: 21px; - word-wrap: break-word; - padding: 0; - height: 100%; - } - - .movies .info .year { - position: absolute; - color: #bbb; - right: 0; - top: 6px; - text-align: right; - transition: all 0.2s linear; - font-weight: normal; - } - .movies.list_list .movie:not(.details_view) .info .year, - .movies.mass_edit_list .info .year { - font-size: 1.25em; - right: 10px; - } - - .movies.thumbs_list .info .year { - font-size: 23px; - margin: 0; - bottom: 0; - left: 0; - top: auto; - right: auto; - color: #FFF; - line-height: 18px; - } - - .touch_enabled .movies.list_list .movie .info .year { - font-size: 1em; - } - - .movies .info .description { - top: 30px; - clear: both; - bottom: 30px; - position: absolute; - } - .movies.list_list .movie:not(.details_view) .info .description, - .movies.mass_edit_list .info .description, - .movies.thumbs_list .info .description { - display: none; - } - - .movies .data .eta { - display: none; - } - - .movies.details_list .data .eta { - position: absolute; - bottom: 0; - right: 0; - display: block; - min-height: 20px; - text-align: right; - font-style: italic; - opacity: .8; - font-size: 11px; - } - - .movies.details_list .movie:hover .data .eta { - display: none; - } - - .movies.thumbs_list .data .eta { - display: block; - position: absolute; - bottom: 40px; - } - - .movies .data .quality { - position: absolute; - bottom: 2px; - display: block; - min-height: 20px; - } - - .movies.list_list .movie:hover .data .quality { - display: none; - } - - .touch_enabled .movies.list_list .movie .data .quality { - position: relative; - display: inline-block; - margin: 0; - top: -4px; - } - - @media all and (max-width: 480px) { - .movies .data .quality { - display: none; - } - } - - .movies .status_suggest .data .quality, - .movies.thumbs_list .data .quality { - display: none; - } - - .movies .data .quality span { - padding: 2px 3px; - opacity: 0.5; - font-size: 10px; - height: 16px; - line-height: 12px; - vertical-align: middle; - display: inline-block; - text-transform: uppercase; - font-weight: normal; - margin: 0 4px 0 0; - border-radius: 2px; - background-color: rgba(255,255,255,0.1); - } - .movies.list_list .data .quality, - .movies.mass_edit_list .data .quality { - text-align: right; - right: 0; - margin-right: 60px; - z-index: 1; - top: 5px; - } - - .movies .data .quality .available, - .movies .data .quality .snatched, - .movies .data .quality .seeding { - opacity: 1; - cursor: pointer; - } - - .movies .data .quality .available { background-color: #578bc3; } - .movies .data .quality .failed, - .movies .data .quality .missing, - .movies .data .quality .ignored { background-color: #a43d34; } - .movies .data .quality .snatched { background-color: #a2a232; } - .movies .data .quality .done { - background-color: #369545; - opacity: 1; - } - .movies .data .quality .seeding { background-color: #0a6819; } - .movies .data .quality .finish { - background-image: url('../../images/sprite.png'); - background-repeat: no-repeat; - background-position: 0 2px; - padding-left: 14px; - background-size: 14px - } - - .movies .data .actions { - position: absolute; - bottom: 17px; - right: 20px; - line-height: 0; - top: 0; - width: auto; - opacity: 0; - display: none; - } - @media all and (max-width: 480px) { - .movies .data .actions { - display: none !important; - } - } - - .movies .movie:hover .data .actions, - .touch_enabled .movies .movie .data .actions { - opacity: 1; - display: inline-block; - } - - .movies.details_list .data .actions { - top: auto; - bottom: 18px; - } - - .movies .movie:hover .actions { - opacity: 1; - display: inline-block; - } - .movies.thumbs_list .data .actions { - bottom: 12px; - right: 10px; - top: auto; - } - - .movies .movie:hover .action { opacity: 0.6; } - .movies .movie:hover .action:hover { opacity: 1; } - - .movies .data .action { - display: inline-block; - height: 22px; - min-width: 33px; - padding: 0 5px; - line-height: 26px; - text-align: center; - font-size: 13px; - color: #FFF; - margin-left: 1px; - } - .movies .data .action.trailer { color: #FFF; } - .movies .data .action.download { color: #b9dec0; } - .movies .data .action.edit { color: #c6b589; } - .movies .data .action.refresh { color: #cbeecc; } - .movies .data .action.delete { color: #e9b0b0; } - .movies .data .action.directory { color: #ffed92; } - .movies .data .action.readd { color: #c2fac5; } - - .movies.mass_edit_list .movie .data .actions { - display: none; - } - - .movies.list_list .movie:not(.details_view):hover .actions, - .movies.mass_edit_list .movie:hover .actions, - .touch_enabled .movies.list_list .movie:not(.details_view) .actions { - margin: 0; - background: #4e5969; - top: 2px; - bottom: 2px; - right: 5px; - z-index: 3; - } - - .movies .delete_container { - clear: both; - text-align: center; - font-size: 20px; - position: absolute; - padding: 80px 0 0; - left: 120px; - right: 0; - } - .movies .delete_container .or { - padding: 10px; - } - .movies .delete_container .delete { - background-color: #ff321c; - font-weight: normal; - } - .movies .delete_container .delete:hover { - color: #fff; - background-color: #d32917; - } - - .movies .options { - position: absolute; - right: 0; - left: 120px; - } - - .movies .options .form { - margin: 80px 0 0; - font-size: 20px; - text-align: center; - } - - .movies .options .form select { - margin-right: 20px; - } - - .movies .options .table { - height: 180px; - overflow: auto; - line-height: 2em; - } - .movies .options .table .item { - border-bottom: 1px solid rgba(255,255,255,0.1); - } - .movies .options .table .item.ignored span, - .movies .options .table .item.failed span { - text-decoration: line-through; - color: rgba(255,255,255,0.4); - } - .movies .options .table .item.ignored .delete:before, - .movies .options .table .item.failed .delete:before { - display: inline-block; - content: "\e04b"; - transform: scale(-1, 1); - } - - .movies .options .table .item:last-child { border: 0; } - .movies .options .table .item:nth-child(even) { - background: rgba(255,255,255,0.05); - } - .movies .options .table .item:not(.head):hover { - background: rgba(255,255,255,0.03); - } - - .movies .options .table .item > * { - display: inline-block; - padding: 0 5px; - width: 60px; - min-height: 24px; - white-space: nowrap; - text-overflow: ellipsis; - text-align: center; - vertical-align: top; - border-left: 1px solid rgba(255, 255, 255, 0.1); - } - .movies .options .table .item > *:first-child { - border: 0; - } - .movies .options .table .provider { - width: 120px; - text-overflow: ellipsis; - overflow: hidden; - } - .movies .options .table .name { - width: 340px; - overflow: hidden; - text-align: left; - padding: 0 10px; - } - .movies .options .table.files .name { width: 590px; } - .movies .options .table .type { width: 130px; } - .movies .options .table .is_available { width: 90px; } - .movies .options .table .age, - .movies .options .table .size { width: 40px; } - - .movies .options .table a { - width: 30px !important; - height: 20px; - opacity: 0.8; - line-height: 25px; - } - .movies .options .table a:hover { opacity: 1; } - .movies .options .table a.download { color: #a7fbaf; } - .movies .options .table a.delete { color: #fda3a3; } - .movies .options .table .ignored a.delete, - .movies .options .table .failed a.delete { color: #b5fda3; } - - .movies .options .table .head > * { - font-weight: bold; - font-size: 14px; - padding-top: 4px; - padding-bottom: 4px; - height: auto; - } - - .trailer_container { - width: 100%; - background: #000; - text-align: center; - transition: all .6s cubic-bezier(0.9,0,0.1,1); - overflow: hidden; - left: 0; - position: absolute; - z-index: 10; - } - @media only screen and (device-width: 768px) { - .trailer_container iframe { - margin-top: 25px; - } - } - - .trailer_container.hide { - height: 0 !important; - } - - .hide_trailer { - position: absolute; - top: 0; - left: 50%; - margin-left: -50px; - width: 100px; - text-align: center; - padding: 3px 10px; - background: #4e5969; - transition: all .2s cubic-bezier(0.9,0,0.1,1) .2s; - z-index: 11; - } - .hide_trailer.hide { - top: -30px; - } - - .movies .movie .try_container { - padding: 5px 10px; - text-align: center; - } - - .movies .movie .try_container a { - margin: 0 5px; - padding: 2px 5px; - } - - .movies .movie .releases .next_release { - border-left: 6px solid #2aa300; - } - - .movies .movie .releases .next_release > :first-child { - margin-left: -6px; - } - - .movies .movie .releases .last_release { - border-left: 6px solid #ffa200; - } - - .movies .movie .releases .last_release > :first-child { - margin-left: -6px; - } - .movies .movie .trynext { - display: inline; - position: absolute; - right: 180px; - z-index: 2; - opacity: 0; - background: #4e5969; - text-align: right; - height: 100%; - top: 0; - } - .touch_enabled .movies .movie .trynext { - display: none; - } - - @media all and (max-width: 480px) { - .movies .movie .trynext { - display: none; - } - } - .movies.mass_edit_list .trynext { display: none; } - .wanted .movies .movie .trynext { - padding-right: 30px; - } - .movies .movie:hover .trynext, - .touch_enabled .movies.details_list .movie .trynext { - opacity: 1; - } - - .movies.details_list .movie .trynext { - background: #47515f; - padding: 0; - right: 0; - height: 25px; - } - - .movies .movie .trynext a { - background-position: 5px center; - padding: 0 5px 0 25px; - margin-right: 10px; - color: #FFF; - height: 100%; - line-height: 27px; - font-family: OpenSans, "Helvetica Neue", Helvetica, Arial, Geneva, sans-serif; - } - .movies .movie .trynext a:before { - margin: 2px 0 0 -20px; - position: absolute; - font-family: 'Elusive-Icons'; - } - .movies.details_list .movie .trynext a { - line-height: 23px; - } - .movies .movie .trynext a:last-child { - margin: 0; - } - .movies .movie .trynext a:hover, - .touch_enabled .movies .movie .trynext a { - background-color: #369545; - } - - .movies .load_more { - display: block; - padding: 10px; - text-align: center; - font-size: 20px; - } - .movies .load_more.loading { - opacity: .5; - } - -.movies .alph_nav { - height: 44px; -} - - @media all and (max-width: 480px) { - .movies .alph_nav { - display: none; - } - } - - .movies .alph_nav .menus { - display: inline-block; - float: right; - } - -.movies .alph_nav .numbers, -.movies .alph_nav .counter, -.movies .alph_nav .actions { - list-style: none; - padding: 0 0 1px; - margin: 0; - user-select: none; -} - - .movies .alph_nav .counter { - display: inline-block; - text-align: right; - padding: 0 10px; - height: 100%; - line-height: 43px; - border-right: 1px solid rgba(255,255,255,.07); - } - - .movies .alph_nav .numbers li, - .movies .alph_nav .actions li { - display: inline-block; - vertical-align: top; - height: 100%; - line-height: 30px; - text-align: center; - border: 1px solid transparent; - transition: all 0.1s ease-in-out; - } - - .movies .alph_nav .numbers li { - width: 30px; - height: 30px; - opacity: 0.3; - } - .movies .alph_nav .numbers li.letter_all { - width: 60px; - } - - .movies .alph_nav li.available { - font-weight: bold; - cursor: pointer; - opacity: 1; - - } - .movies .alph_nav li.active.available, - .movies .alph_nav li.available:hover { - background: rgba(0,0,0,.1); - } - - .movies .alph_nav .search input { - width: 100%; - height: 44px; - display: inline-block; - border: 0; - background: none; - color: #444; - font-size: 14px; - padding: 0 10px 0 30px; - border-bottom: 1px solid rgba(0,0,0,.08); - } - .movies .alph_nav .search input:focus { - background: rgba(0,0,0,.08); - } - - .movies .alph_nav .search input::-webkit-input-placeholder { - color: #444; - opacity: .6; - } - - .movies .alph_nav .search:before { - font-family: 'Elusive-Icons'; - content: "\e03e"; - position: absolute; - height: 20px; - line-height: 45px; - font-size: 12px; - margin: 0 0 0 10px; - opacity: .6; - color: #444; - } - - .movies .alph_nav .actions { - -moz-user-select: none; - width: 44px; - height: 44px; - display: inline-block; - vertical-align: top; - z-index: 200; - position: relative; - border: 1px solid rgba(255,255,255,.07); - border-width: 0 1px; - } - .movies .alph_nav .actions:hover { - box-shadow: 0 100px 20px -10px rgba(0,0,0,0.55); - } - .movies .alph_nav .actions li { - width: 100%; - height: 45px; - line-height: 40px; - position: relative; - z-index: 20; - display: none; - cursor: pointer; - } - .movies .alph_nav .actions:hover li:not(.active) { - display: block; - background: #FFF; - color: #444; - } - .movies .alph_nav .actions li:hover:not(.active) { - background: #ccc; - } - .movies .alph_nav .actions li.active { - display: block; - } - - .movies .alph_nav .actions li.mass_edit:before { - content: "\e070"; - } - - .movies .alph_nav .actions li.list:before { - content: "\e0d8"; - } - - .movies .alph_nav .actions li.details:before { - content: "\e022"; - } - - .movies .alph_nav .mass_edit_form { - clear: both; - text-align: center; - display: none; - overflow: hidden; - float: left; - height: 44px; - line-height: 44px; - } - .movies.mass_edit_list .mass_edit_form { - display: inline-block; - } - .movies.mass_edit_list .mass_edit_form .select { - font-size: 14px; - display: inline-block; - } - .movies.mass_edit_list .mass_edit_form .select .check { - display: inline-block; - vertical-align: middle; - margin: -4px 0 0 5px; - } - .movies.mass_edit_list .mass_edit_form .select span { - opacity: 0.7; - } - .movies.mass_edit_list .mass_edit_form .select .count { - font-weight: bold; - margin: 0 3px 0 10px; - } - - .movies .alph_nav .mass_edit_form .quality { - display: inline-block; - margin: 0 0 0 16px; - } - .movies .alph_nav .mass_edit_form .quality select { - width: 120px; - margin-right: 5px; - } - .movies .alph_nav .mass_edit_form .button { - padding: 3px 7px; - } - - .movies .alph_nav .mass_edit_form .refresh, - .movies .alph_nav .mass_edit_form .delete { - display: inline-block; - margin-left: 8px; - } - - .movies .alph_nav .mass_edit_form .refresh span, - .movies .alph_nav .mass_edit_form .delete span { - margin: 0 10px 0 0; - } - - .movies .alph_nav .more_menu > a { - background: none; - } - - .movies .alph_nav .more_menu.extra > a:before { - content: '...'; - font-size: 1.7em; - line-height: 23px; - text-align: center; - display: block; - } - - .movies .alph_nav .more_menu.filter { - } - - .movies .alph_nav .more_menu.filter > a:before { - content: "\e0e8"; - font-family: 'Elusive-Icons'; - line-height: 33px; - display: block; - text-align: center; - } - - .movies .alph_nav .more_menu.filter .wrapper { - right: 88px; - width: 300px; - } - -.movies .empty_wanted { - background-image: url('../../images/emptylist.png'); - background-position: 80% 0; - height: 750px; - width: 100%; - max-width: 900px; - padding-top: 260px; -} - -.movies .empty_manage { - text-align: center; - font-size: 25px; - line-height: 150%; - padding: 40px 0; -} - - .movies .empty_manage .after_manage { - margin-top: 30px; - font-size: 16px; - } - - .movies .progress { - padding: 10px; - margin: 5px 0; - text-align: left; - } - - .movies .progress > div { - padding: 5px 10px; - font-size: 12px; - line-height: 12px; - text-align: left; - display: inline-block; - width: 49%; - background: rgba(255, 255, 255, 0.05); - margin: 2px 0.5%; - } - - .movies .progress > div .folder { - display: inline-block; - padding: 5px 20px 5px 0; - white-space: nowrap; - text-overflow: ellipsis; - overflow: hidden; - width: 85%; - direction: ltr; - vertical-align: middle; - } - - .movies .progress > div .percentage { - display: inline-block; - text-transform: uppercase; - font-weight: normal; - font-size: 20px; - border-left: 1px solid rgba(255, 255, 255, .2); - width: 15%; - text-align: right; - vertical-align: middle; - } diff --git a/couchpotato/core/media/movie/_base/static/movie.scss b/couchpotato/core/media/movie/_base/static/movie.scss new file mode 100644 index 0000000..311b111 --- /dev/null +++ b/couchpotato/core/media/movie/_base/static/movie.scss @@ -0,0 +1,1074 @@ +.movies { + padding: 10px 0 20px; + position: relative; + z-index: 3; + width: 100%; +} + + .movies > div { + clear: both; + } + + .movies > div .message { + display: block; + padding: 20px; + font-size: 20px; + color: white; + text-align: center; + } + .movies > div .message a { + padding: 20px; + display: block; + } + + .movies.thumbs_list > div:not(.description) { + margin-right: -4px; + } + + .movies .loading { + display: block; + padding: 20px 0 0 0; + width: 100%; + z-index: 3; + transition: all .4s cubic-bezier(0.9,0,0.1,1); + height: 40px; + opacity: 1; + position: absolute; + text-align: center; + } + .movies .loading.hide { + height: 0; + padding: 0; + opacity: 0; + margin-top: -20px; + overflow: hidden; + } + + .movies .loading .spinner { + display: inline-block; + } + + .movies .loading .message { + margin: 0 20px; + } + + .movies h2 { + margin-bottom: 20px; + } + + @media all and (max-width: 480px) { + .movies h2 { + font-size: 25px; + margin-bottom: 10px; + } + } + + .movies > .description { + position: absolute; + top: 30px; + right: 0; + font-style: italic; + opacity: 0.8; + } + .movies:hover > .description { + opacity: 1; + } + + @media all and (max-width: 860px) { + .movies > .description { + display: none; + } + } + + .movies.thumbs_list { + padding: 20px 0 20px; + } + + .home .movies { + padding-top: 6px; + } + + .movies .movie { + position: relative; + margin: 10px 0; + padding-left: 20px; + overflow: hidden; + width: 100%; + height: 180px; + transition: all 0.6s cubic-bezier(0.9,0,0.1,1); + transition-property: width, height; + background: rgba(0,0,0,.2); + } + + .movies.mass_edit_list .movie { + padding-left: 22px; + background: none; + } + + .movies.details_list .movie { + padding-left: 120px; + } + + .movies.list_list .movie:not(.details_view), + .movies.mass_edit_list .movie { + height: 30px; + border-bottom: 1px solid rgba(255,255,255,.15); + } + + .movies.list_list .movie:last-child, + .movies.mass_edit_list .movie:last-child { + border: none; + } + + .movies.thumbs_list .movie { + width: 16.66667%; + height: auto; + min-height: 200px; + display: inline-block; + margin: 0; + padding: 0; + vertical-align: top; + line-height: 0; + } + + @media all and (max-width: 800px) { + .movies.thumbs_list .movie { + width: 25%; + min-height: 100px; + } + } + + .movies .movie .mask { + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 100%; + } + + .movies.list_list .movie:not(.details_view), + .movies.mass_edit_list .movie { + margin: 0; + } + + .movies .data { + padding: 20px; + height: 100%; + width: 100%; + position: relative; + transition: all .6s cubic-bezier(0.9,0,0.1,1); + right: 0; + } + .movies.list_list .movie:not(.details_view) .data, + .movies.mass_edit_list .movie .data { + padding: 0 0 0 10px; + border: 0; + background: #4e5969; + } + .movies.mass_edit_list .movie .data { + padding-left: 8px; + } + + .movies.thumbs_list .data { + position: absolute; + left: 0; + top: 0; + width: 100%; + padding: 10px; + height: 100%; + background: none; + transition: none; + } + + .movies.thumbs_list .movie:hover .data { + background: rgba(0,0,0,0.9); + } + + .movies .data.hide_right { + right: -100%; + } + + .movies .movie .check { + display: none; + } + + .movies.mass_edit_list .movie .check { + position: absolute; + left: 0; + top: 0; + display: block; + margin: 7px 0 0 5px; + } + + .movies .poster { + position: absolute; + left: 0; + width: 120px; + line-height: 0; + overflow: hidden; + height: 100%; + transition: all .6s cubic-bezier(0.9,0,0.1,1); + background: rgba(0,0,0,.1); + } + .movies.thumbs_list .poster { + position: relative; + } + .movies.list_list .movie:not(.details_view) .poster, + .movies.mass_edit_list .poster { + width: 20px; + height: 30px; + } + .movies.mass_edit_list .poster { + display: none; + } + + .movies.thumbs_list .poster { + width: 100%; + height: 100%; + transition: none; + background: no-repeat center; + background-size: cover; + } + .movies.thumbs_list .no_thumbnail .empty_file { + width: 100%; + height: 100%; + } + + .movies .poster img, + .options .poster img { + width: 100%; + height: 100%; + } + .movies.thumbs_list .poster img { + height: auto; + width: 100%; + top: 0; + bottom: 0; + opacity: 0; + } + + .movies .info { + position: relative; + height: 100%; + width: 100%; + } + + .movies .info .title { + font-size: 28px; + font-weight: bold; + margin-bottom: 10px; + margin-top: 2px; + width: 100%; + padding-right: 80px; + transition: all 0.2s linear; + height: 35px; + top: -5px; + position: relative; + } + .movies.list_list .info .title, + .movies.mass_edit_list .info .title { + height: 100%; + top: 0; + margin: 0; + } + .touch_enabled .movies.list_list .info .title { + display: inline-block; + padding-right: 55px; + } + + .movies .info .title span { + display: inline-block; + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + width: 100%; + height: 100%; + line-height: 30px; + } + + .movies.thumbs_list .info .title span { + white-space: normal; + overflow: auto; + height: auto; + text-align: left; + } + + @media all and (max-width: 480px) { + .movies.thumbs_list .movie .info .title span, + .movies.thumbs_list .movie .info .year { + font-size: 15px; + line-height: 15px; + overflow: hidden; + } + } + + .movies.list_list .movie:not(.details_view) .info .title, + .movies.mass_edit_list .info .title { + font-size: 16px; + font-weight: normal; + width: auto; + } + + .movies.thumbs_list .movie:not(.no_thumbnail) .info { + display: none; + } + .movies.thumbs_list .movie:hover .info { + display: block; + } + + .movies.thumbs_list .info .title { + font-size: 21px; + word-wrap: break-word; + padding: 0; + height: 100%; + } + + .movies .info .year { + position: absolute; + color: #bbb; + right: 0; + top: 6px; + text-align: right; + transition: all 0.2s linear; + font-weight: normal; + } + .movies.list_list .movie:not(.details_view) .info .year, + .movies.mass_edit_list .info .year { + font-size: 1.25em; + right: 10px; + } + + .movies.thumbs_list .info .year { + font-size: 23px; + margin: 0; + bottom: 0; + left: 0; + top: auto; + right: auto; + color: #FFF; + line-height: 18px; + } + + .touch_enabled .movies.list_list .movie .info .year { + font-size: 1em; + } + + .movies .info .description { + top: 30px; + clear: both; + bottom: 30px; + position: absolute; + } + .movies.list_list .movie:not(.details_view) .info .description, + .movies.mass_edit_list .info .description, + .movies.thumbs_list .info .description { + display: none; + } + + .movies .data .eta { + display: none; + } + + .movies.details_list .data .eta { + position: absolute; + bottom: 0; + right: 0; + display: block; + min-height: 20px; + text-align: right; + font-style: italic; + opacity: .8; + font-size: 11px; + } + + .movies.details_list .movie:hover .data .eta { + display: none; + } + + .movies.thumbs_list .data .eta { + display: block; + position: absolute; + bottom: 40px; + } + + .movies .data .quality { + position: absolute; + bottom: 2px; + display: block; + min-height: 20px; + } + + .movies.list_list .movie:hover .data .quality { + display: none; + } + + .touch_enabled .movies.list_list .movie .data .quality { + position: relative; + display: inline-block; + margin: 0; + top: -4px; + } + + @media all and (max-width: 480px) { + .movies .data .quality { + display: none; + } + } + + .movies .status_suggest .data .quality, + .movies.thumbs_list .data .quality { + display: none; + } + + .movies .data .quality span { + padding: 2px 3px; + opacity: 0.5; + font-size: 10px; + height: 16px; + line-height: 12px; + vertical-align: middle; + display: inline-block; + text-transform: uppercase; + font-weight: normal; + margin: 0 4px 0 0; + border-radius: 2px; + background-color: rgba(255,255,255,0.1); + } + .movies.list_list .data .quality, + .movies.mass_edit_list .data .quality { + text-align: right; + right: 0; + margin-right: 60px; + z-index: 1; + top: 5px; + } + + .movies .data .quality .available, + .movies .data .quality .snatched, + .movies .data .quality .seeding { + opacity: 1; + cursor: pointer; + } + + .movies .data .quality .available { background-color: #578bc3; } + .movies .data .quality .failed, + .movies .data .quality .missing, + .movies .data .quality .ignored { background-color: #a43d34; } + .movies .data .quality .snatched { background-color: #a2a232; } + .movies .data .quality .done { + background-color: #369545; + opacity: 1; + } + .movies .data .quality .seeding { background-color: #0a6819; } + .movies .data .quality .finish { + background-image: url('../../images/sprite.png'); + background-repeat: no-repeat; + background-position: 0 2px; + padding-left: 14px; + background-size: 14px + } + + .movies .data .actions { + position: absolute; + bottom: 17px; + right: 20px; + line-height: 0; + top: 0; + width: auto; + opacity: 0; + display: none; + } + @media all and (max-width: 480px) { + .movies .data .actions { + display: none !important; + } + } + + .movies .movie:hover .data .actions, + .touch_enabled .movies .movie .data .actions { + opacity: 1; + display: inline-block; + } + + .movies.details_list .data .actions { + top: auto; + bottom: 18px; + } + + .movies .movie:hover .actions { + opacity: 1; + display: inline-block; + } + .movies.thumbs_list .data .actions { + bottom: 12px; + right: 10px; + top: auto; + } + + .movies .movie:hover .action { opacity: 0.6; } + .movies .movie:hover .action:hover { opacity: 1; } + + .movies .data .action { + display: inline-block; + height: 22px; + min-width: 33px; + padding: 0 5px; + line-height: 26px; + text-align: center; + font-size: 13px; + color: #FFF; + margin-left: 1px; + } + .movies .data .action.trailer { color: #FFF; } + .movies .data .action.download { color: #b9dec0; } + .movies .data .action.edit { color: #c6b589; } + .movies .data .action.refresh { color: #cbeecc; } + .movies .data .action.delete { color: #e9b0b0; } + .movies .data .action.directory { color: #ffed92; } + .movies .data .action.readd { color: #c2fac5; } + + .movies.mass_edit_list .movie .data .actions { + display: none; + } + + .movies.list_list .movie:not(.details_view):hover .actions, + .movies.mass_edit_list .movie:hover .actions, + .touch_enabled .movies.list_list .movie:not(.details_view) .actions { + margin: 0; + background: #4e5969; + top: 2px; + bottom: 2px; + right: 5px; + z-index: 3; + } + + .movies .delete_container { + clear: both; + text-align: center; + font-size: 20px; + position: absolute; + padding: 80px 0 0; + left: 120px; + right: 0; + } + .movies .delete_container .or { + padding: 10px; + } + .movies .delete_container .delete { + background-color: #ff321c; + font-weight: normal; + } + .movies .delete_container .delete:hover { + color: #fff; + background-color: #d32917; + } + + .movies .options { + position: absolute; + right: 0; + left: 120px; + } + + .movies .options .form { + margin: 80px 0 0; + font-size: 20px; + text-align: center; + } + + .movies .options .form select { + margin-right: 20px; + } + + .movies .options .table { + height: 180px; + overflow: auto; + line-height: 2em; + } + .movies .options .table .item { + border-bottom: 1px solid rgba(255,255,255,0.1); + } + .movies .options .table .item.ignored span, + .movies .options .table .item.failed span { + text-decoration: line-through; + color: rgba(255,255,255,0.4); + } + .movies .options .table .item.ignored .delete:before, + .movies .options .table .item.failed .delete:before { + display: inline-block; + content: "\e04b"; + transform: scale(-1, 1); + } + + .movies .options .table .item:last-child { border: 0; } + .movies .options .table .item:nth-child(even) { + background: rgba(255,255,255,0.05); + } + .movies .options .table .item:not(.head):hover { + background: rgba(255,255,255,0.03); + } + + .movies .options .table .item > * { + display: inline-block; + padding: 0 5px; + width: 60px; + min-height: 24px; + white-space: nowrap; + text-overflow: ellipsis; + text-align: center; + vertical-align: top; + border-left: 1px solid rgba(255, 255, 255, 0.1); + } + .movies .options .table .item > *:first-child { + border: 0; + } + .movies .options .table .provider { + width: 120px; + text-overflow: ellipsis; + overflow: hidden; + } + .movies .options .table .name { + width: 340px; + overflow: hidden; + text-align: left; + padding: 0 10px; + } + .movies .options .table.files .name { width: 590px; } + .movies .options .table .type { width: 130px; } + .movies .options .table .is_available { width: 90px; } + .movies .options .table .age, + .movies .options .table .size { width: 40px; } + + .movies .options .table a { + width: 30px !important; + height: 20px; + opacity: 0.8; + line-height: 25px; + } + .movies .options .table a:hover { opacity: 1; } + .movies .options .table a.download { color: #a7fbaf; } + .movies .options .table a.delete { color: #fda3a3; } + .movies .options .table .ignored a.delete, + .movies .options .table .failed a.delete { color: #b5fda3; } + + .movies .options .table .head > * { + font-weight: bold; + font-size: 14px; + padding-top: 4px; + padding-bottom: 4px; + height: auto; + } + + .trailer_container { + width: 100%; + background: #000; + text-align: center; + transition: all .6s cubic-bezier(0.9,0,0.1,1); + overflow: hidden; + left: 0; + position: absolute; + z-index: 10; + } + @media only screen and (device-width: 768px) { + .trailer_container iframe { + margin-top: 25px; + } + } + + .trailer_container.hide { + height: 0 !important; + } + + .hide_trailer { + position: absolute; + top: 0; + left: 50%; + margin-left: -50px; + width: 100px; + text-align: center; + padding: 3px 10px; + background: #4e5969; + transition: all .2s cubic-bezier(0.9,0,0.1,1) .2s; + z-index: 11; + } + .hide_trailer.hide { + top: -30px; + } + + .movies .movie .try_container { + padding: 5px 10px; + text-align: center; + } + + .movies .movie .try_container a { + margin: 0 5px; + padding: 2px 5px; + } + + .movies .movie .releases .next_release { + border-left: 6px solid #2aa300; + } + + .movies .movie .releases .next_release > :first-child { + margin-left: -6px; + } + + .movies .movie .releases .last_release { + border-left: 6px solid #ffa200; + } + + .movies .movie .releases .last_release > :first-child { + margin-left: -6px; + } + .movies .movie .trynext { + display: inline; + position: absolute; + right: 180px; + z-index: 2; + opacity: 0; + background: #4e5969; + text-align: right; + height: 100%; + top: 0; + } + .touch_enabled .movies .movie .trynext { + display: none; + } + + @media all and (max-width: 480px) { + .movies .movie .trynext { + display: none; + } + } + .movies.mass_edit_list .trynext { display: none; } + .wanted .movies .movie .trynext { + padding-right: 30px; + } + .movies .movie:hover .trynext, + .touch_enabled .movies.details_list .movie .trynext { + opacity: 1; + } + + .movies.details_list .movie .trynext { + background: #47515f; + padding: 0; + right: 0; + height: 25px; + } + + .movies .movie .trynext a { + background-position: 5px center; + padding: 0 5px 0 25px; + margin-right: 10px; + color: #FFF; + height: 100%; + line-height: 27px; + font-family: OpenSans, "Helvetica Neue", Helvetica, Arial, Geneva, sans-serif; + } + .movies .movie .trynext a:before { + margin: 2px 0 0 -20px; + position: absolute; + font-family: 'Elusive-Icons'; + } + .movies.details_list .movie .trynext a { + line-height: 23px; + } + .movies .movie .trynext a:last-child { + margin: 0; + } + .movies .movie .trynext a:hover, + .touch_enabled .movies .movie .trynext a { + background-color: #369545; + } + + .movies .load_more { + display: block; + padding: 10px; + text-align: center; + font-size: 20px; + } + .movies .load_more.loading { + opacity: .5; + } + +.movies .alph_nav { + height: 44px; +} + + @media all and (max-width: 480px) { + .movies .alph_nav { + display: none; + } + } + + .movies .alph_nav .menus { + display: inline-block; + float: right; + } + +.movies .alph_nav .numbers, +.movies .alph_nav .counter, +.movies .alph_nav .actions { + list-style: none; + padding: 0 0 1px; + margin: 0; + user-select: none; +} + + .movies .alph_nav .counter { + display: inline-block; + text-align: right; + padding: 0 10px; + height: 100%; + line-height: 43px; + border-right: 1px solid rgba(255,255,255,.07); + } + + .movies .alph_nav .numbers li, + .movies .alph_nav .actions li { + display: inline-block; + vertical-align: top; + height: 100%; + line-height: 30px; + text-align: center; + border: 1px solid transparent; + transition: all 0.1s ease-in-out; + } + + .movies .alph_nav .numbers li { + width: 30px; + height: 30px; + opacity: 0.3; + } + .movies .alph_nav .numbers li.letter_all { + width: 60px; + } + + .movies .alph_nav li.available { + font-weight: bold; + cursor: pointer; + opacity: 1; + + } + .movies .alph_nav li.active.available, + .movies .alph_nav li.available:hover { + background: rgba(0,0,0,.1); + } + + .movies .alph_nav .search input { + width: 100%; + height: 44px; + display: inline-block; + border: 0; + background: none; + color: #444; + font-size: 14px; + padding: 0 10px 0 30px; + border-bottom: 1px solid rgba(0,0,0,.08); + } + .movies .alph_nav .search input:focus { + background: rgba(0,0,0,.08); + } + + .movies .alph_nav .search input::-webkit-input-placeholder { + color: #444; + opacity: .6; + } + + .movies .alph_nav .search:before { + font-family: 'Elusive-Icons'; + content: "\e03e"; + position: absolute; + height: 20px; + line-height: 45px; + font-size: 12px; + margin: 0 0 0 10px; + opacity: .6; + color: #444; + } + + .movies .alph_nav .actions { + -moz-user-select: none; + width: 44px; + height: 44px; + display: inline-block; + vertical-align: top; + z-index: 200; + position: relative; + border: 1px solid rgba(255,255,255,.07); + border-width: 0 1px; + } + .movies .alph_nav .actions:hover { + box-shadow: 0 100px 20px -10px rgba(0,0,0,0.55); + } + .movies .alph_nav .actions li { + width: 100%; + height: 45px; + line-height: 40px; + position: relative; + z-index: 20; + display: none; + cursor: pointer; + } + .movies .alph_nav .actions:hover li:not(.active) { + display: block; + background: #FFF; + color: #444; + } + .movies .alph_nav .actions li:hover:not(.active) { + background: #ccc; + } + .movies .alph_nav .actions li.active { + display: block; + } + + .movies .alph_nav .actions li.mass_edit:before { + content: "\e070"; + } + + .movies .alph_nav .actions li.list:before { + content: "\e0d8"; + } + + .movies .alph_nav .actions li.details:before { + content: "\e022"; + } + + .movies .alph_nav .mass_edit_form { + clear: both; + text-align: center; + display: none; + overflow: hidden; + float: left; + height: 44px; + line-height: 44px; + } + .movies.mass_edit_list .mass_edit_form { + display: inline-block; + } + .movies.mass_edit_list .mass_edit_form .select { + font-size: 14px; + display: inline-block; + } + .movies.mass_edit_list .mass_edit_form .select .check { + display: inline-block; + vertical-align: middle; + margin: -4px 0 0 5px; + } + .movies.mass_edit_list .mass_edit_form .select span { + opacity: 0.7; + } + .movies.mass_edit_list .mass_edit_form .select .count { + font-weight: bold; + margin: 0 3px 0 10px; + } + + .movies .alph_nav .mass_edit_form .quality { + display: inline-block; + margin: 0 0 0 16px; + } + .movies .alph_nav .mass_edit_form .quality select { + width: 120px; + margin-right: 5px; + } + .movies .alph_nav .mass_edit_form .button { + padding: 3px 7px; + } + + .movies .alph_nav .mass_edit_form .refresh, + .movies .alph_nav .mass_edit_form .delete { + display: inline-block; + margin-left: 8px; + } + + .movies .alph_nav .mass_edit_form .refresh span, + .movies .alph_nav .mass_edit_form .delete span { + margin: 0 10px 0 0; + } + + .movies .alph_nav .more_menu > a { + background: none; + } + + .movies .alph_nav .more_menu.extra > a:before { + content: '...'; + font-size: 1.7em; + line-height: 23px; + text-align: center; + display: block; + } + + .movies .alph_nav .more_menu.filter { + } + + .movies .alph_nav .more_menu.filter > a:before { + content: "\e0e8"; + font-family: 'Elusive-Icons'; + line-height: 33px; + display: block; + text-align: center; + } + + .movies .alph_nav .more_menu.filter .wrapper { + right: 88px; + width: 300px; + } + +.movies .empty_wanted { + background-image: url('../../images/emptylist.png'); + background-position: 80% 0; + height: 750px; + width: 100%; + max-width: 900px; + padding-top: 260px; +} + +.movies .empty_manage { + text-align: center; + font-size: 25px; + line-height: 150%; + padding: 40px 0; +} + + .movies .empty_manage .after_manage { + margin-top: 30px; + font-size: 16px; + } + + .movies .progress { + padding: 10px; + margin: 5px 0; + text-align: left; + } + + .movies .progress > div { + padding: 5px 10px; + font-size: 12px; + line-height: 12px; + text-align: left; + display: inline-block; + width: 49%; + background: rgba(255, 255, 255, 0.05); + margin: 2px 0.5%; + } + + .movies .progress > div .folder { + display: inline-block; + padding: 5px 20px 5px 0; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + width: 85%; + direction: ltr; + vertical-align: middle; + } + + .movies .progress > div .percentage { + display: inline-block; + text-transform: uppercase; + font-weight: normal; + font-size: 20px; + border-left: 1px solid rgba(255, 255, 255, .2); + width: 15%; + text-align: right; + vertical-align: middle; + } diff --git a/couchpotato/core/media/movie/charts/static/charts.css b/couchpotato/core/media/movie/charts/static/charts.css deleted file mode 100644 index 0084ed9..0000000 --- a/couchpotato/core/media/movie/charts/static/charts.css +++ /dev/null @@ -1,274 +0,0 @@ -.charts { - clear: both; - margin-bottom: 30px; -} - - .charts > h2 { - height: 40px; - } - - .charts .chart { - display: inline-block; - width: 50%; - vertical-align: top; - max-height: 510px; - scrollbar-base-color: #4e5969; - } - - .charts .chart .media_result.hidden { - display: none; - } - -.charts .refresh { - clear:both; - position: relative; -} - - .charts .refresh .refreshing { - display: block; - padding: 20px; - font-size: 20px; - text-align:center; - } - - .charts .refresh a { - text-align: center; - padding: 0; - display: none; - width: 30px; - height: 30px; - position: absolute; - right: 10px; - top: -40px; - opacity: .7; - } - - .charts .refresh a:hover { - opacity: 1; - } - - .charts p.no_charts_enabled { - padding: 0.7em 1em; - display: none; - } - - .charts .chart h3 a { - color: #fff; - } - - -.charts .chart .media_result { - display: inline-block; - width: 100%; - height: 150px; -} - -@media all and (max-width: 960px) { - .charts .chart { - width: 50%; - } -} - -@media all and (max-width: 600px) { - .charts .chart { - width: 100%; - } -} - -.charts .chart .media_result .data { - left: 150px; - background: #4e5969; - border: none; -} - - .charts .chart .media_result .data .info { - top: 10px; - left: 15px; - right: 15px; - bottom: 10px; - overflow: hidden; - } - - .charts .chart .media_result .data .info h2 { - white-space: normal; - max-height: 120px; - font-size: 18px; - line-height: 18px; - } - - .charts .chart .media_result .data .info .rating, - .charts .chart .media_result .data .info .genres, - .charts .chart .media_result .data .info .year { - position: static; - display: block; - padding: 0; - opacity: .6; - } - - .charts .chart .media_result .data .info .year { - margin: 10px 0 0; - } - - .charts .chart .media_result .data .info .rating { - font-size: 20px; - float: right; - margin-top: -20px; - } - .charts .chart .media_result .data .info .rating:before { - content: "\e031"; - font-family: 'Elusive-Icons'; - font-size: 14px; - margin: 0 5px 0 0; - vertical-align: bottom; - } - - .charts .chart .media_result .data .info .genres { - font-size: 11px; - font-style: italic; - text-align: right; - } - - .charts .chart .media_result .data .info .plot { - display: block; - font-size: 11px; - overflow: hidden; - text-align: justify; - height: 100%; - z-index: 2; - top: 64px; - position: absolute; - background: #4e5969; - cursor: pointer; - transition: all .4s ease-in-out; - padding: 0 3px 10px 0; - } - .charts .chart .media_result .data:before { - content: ''; - display: block; - height: 10px; - right: 0; - left: 0; - bottom: 10px; - position: absolute; - background: linear-gradient( - 0deg, - rgba(78, 89, 105, 1) 0%, - rgba(78, 89, 105, 0) 100% - ); - z-index: 3; - pointer-events: none; - } - - .charts .chart .media_result .data .info .plot.full { - top: 0; - overflow: auto; - } - -.charts .chart .media_result .data { - cursor: default; -} - -.charts .chart .media_result .options { - left: 150px; -} - .charts .chart .media_result .options select[name=title] { width: 100%; } - .charts .chart .media_result .options select[name=profile] { width: 100%; } - .charts .chart .media_result .options select[name=category] { width: 100%; } - - .charts .chart .media_result .button { - position: absolute; - margin: 2px 0 0 0; - right: 15px; - bottom: 15px; - } - - -.charts .chart .media_result .thumbnail { - width: 100px; - position: absolute; - left: 50px; -} - -.charts .chart .media_result .chart_number { - color: white; - position: absolute; - top: 0; - padding: 10px; - font: bold 2em/1em Helvetica, Sans-Serif; - width: 50px; - height: 100%; - text-align: center; - border-left: 8px solid transparent; -} - - .charts .chart .media_result.chart_in_wanted .chart_number { - border-color: rgba(0, 255, 40, 0.3); - } - - .charts .chart .media_result.chart_in_library .chart_number { - border-color: rgba(0, 202, 32, 0.3); - } - - -.charts .chart .media_result .actions { - position: absolute; - top: 10px; - right: 10px; - display: none; - width: 90px; -} - .charts .chart .media_result:hover .actions { - display: block; - } - .charts .chart .media_result:hover h2 .title { - opacity: 0; - } - .charts .chart .media_result .data.open .actions { - display: none; - } - - .charts .chart .media_result .actions a { - margin-left: 10px; - vertical-align: middle; - } - - -.toggle_menu { - height: 50px; -} - -.toggle_menu a { - display: block; - width: 50%; - float: left; - color: rgba(255,255,255,.6); - border-bottom: 1px solid rgba(255, 255, 255, 0.0666667); -} - -.toggle_menu a:hover { - border-color: #047792; - border-width: 4px; - color: #fff; -} - -.toggle_menu a.active { - border-bottom: 4px solid #04bce6; - color: #fff; -} - -.toggle_menu a:last-child { - float: right; -} - -.toggle_menu h2 { - height: 40px; -} - -@media all and (max-width: 480px) { - .toggle_menu h2 { - font-size: 16px; - text-align: center; - height: 30px; - } -} - diff --git a/couchpotato/core/media/movie/charts/static/charts.scss b/couchpotato/core/media/movie/charts/static/charts.scss new file mode 100644 index 0000000..0084ed9 --- /dev/null +++ b/couchpotato/core/media/movie/charts/static/charts.scss @@ -0,0 +1,274 @@ +.charts { + clear: both; + margin-bottom: 30px; +} + + .charts > h2 { + height: 40px; + } + + .charts .chart { + display: inline-block; + width: 50%; + vertical-align: top; + max-height: 510px; + scrollbar-base-color: #4e5969; + } + + .charts .chart .media_result.hidden { + display: none; + } + +.charts .refresh { + clear:both; + position: relative; +} + + .charts .refresh .refreshing { + display: block; + padding: 20px; + font-size: 20px; + text-align:center; + } + + .charts .refresh a { + text-align: center; + padding: 0; + display: none; + width: 30px; + height: 30px; + position: absolute; + right: 10px; + top: -40px; + opacity: .7; + } + + .charts .refresh a:hover { + opacity: 1; + } + + .charts p.no_charts_enabled { + padding: 0.7em 1em; + display: none; + } + + .charts .chart h3 a { + color: #fff; + } + + +.charts .chart .media_result { + display: inline-block; + width: 100%; + height: 150px; +} + +@media all and (max-width: 960px) { + .charts .chart { + width: 50%; + } +} + +@media all and (max-width: 600px) { + .charts .chart { + width: 100%; + } +} + +.charts .chart .media_result .data { + left: 150px; + background: #4e5969; + border: none; +} + + .charts .chart .media_result .data .info { + top: 10px; + left: 15px; + right: 15px; + bottom: 10px; + overflow: hidden; + } + + .charts .chart .media_result .data .info h2 { + white-space: normal; + max-height: 120px; + font-size: 18px; + line-height: 18px; + } + + .charts .chart .media_result .data .info .rating, + .charts .chart .media_result .data .info .genres, + .charts .chart .media_result .data .info .year { + position: static; + display: block; + padding: 0; + opacity: .6; + } + + .charts .chart .media_result .data .info .year { + margin: 10px 0 0; + } + + .charts .chart .media_result .data .info .rating { + font-size: 20px; + float: right; + margin-top: -20px; + } + .charts .chart .media_result .data .info .rating:before { + content: "\e031"; + font-family: 'Elusive-Icons'; + font-size: 14px; + margin: 0 5px 0 0; + vertical-align: bottom; + } + + .charts .chart .media_result .data .info .genres { + font-size: 11px; + font-style: italic; + text-align: right; + } + + .charts .chart .media_result .data .info .plot { + display: block; + font-size: 11px; + overflow: hidden; + text-align: justify; + height: 100%; + z-index: 2; + top: 64px; + position: absolute; + background: #4e5969; + cursor: pointer; + transition: all .4s ease-in-out; + padding: 0 3px 10px 0; + } + .charts .chart .media_result .data:before { + content: ''; + display: block; + height: 10px; + right: 0; + left: 0; + bottom: 10px; + position: absolute; + background: linear-gradient( + 0deg, + rgba(78, 89, 105, 1) 0%, + rgba(78, 89, 105, 0) 100% + ); + z-index: 3; + pointer-events: none; + } + + .charts .chart .media_result .data .info .plot.full { + top: 0; + overflow: auto; + } + +.charts .chart .media_result .data { + cursor: default; +} + +.charts .chart .media_result .options { + left: 150px; +} + .charts .chart .media_result .options select[name=title] { width: 100%; } + .charts .chart .media_result .options select[name=profile] { width: 100%; } + .charts .chart .media_result .options select[name=category] { width: 100%; } + + .charts .chart .media_result .button { + position: absolute; + margin: 2px 0 0 0; + right: 15px; + bottom: 15px; + } + + +.charts .chart .media_result .thumbnail { + width: 100px; + position: absolute; + left: 50px; +} + +.charts .chart .media_result .chart_number { + color: white; + position: absolute; + top: 0; + padding: 10px; + font: bold 2em/1em Helvetica, Sans-Serif; + width: 50px; + height: 100%; + text-align: center; + border-left: 8px solid transparent; +} + + .charts .chart .media_result.chart_in_wanted .chart_number { + border-color: rgba(0, 255, 40, 0.3); + } + + .charts .chart .media_result.chart_in_library .chart_number { + border-color: rgba(0, 202, 32, 0.3); + } + + +.charts .chart .media_result .actions { + position: absolute; + top: 10px; + right: 10px; + display: none; + width: 90px; +} + .charts .chart .media_result:hover .actions { + display: block; + } + .charts .chart .media_result:hover h2 .title { + opacity: 0; + } + .charts .chart .media_result .data.open .actions { + display: none; + } + + .charts .chart .media_result .actions a { + margin-left: 10px; + vertical-align: middle; + } + + +.toggle_menu { + height: 50px; +} + +.toggle_menu a { + display: block; + width: 50%; + float: left; + color: rgba(255,255,255,.6); + border-bottom: 1px solid rgba(255, 255, 255, 0.0666667); +} + +.toggle_menu a:hover { + border-color: #047792; + border-width: 4px; + color: #fff; +} + +.toggle_menu a.active { + border-bottom: 4px solid #04bce6; + color: #fff; +} + +.toggle_menu a:last-child { + float: right; +} + +.toggle_menu h2 { + height: 40px; +} + +@media all and (max-width: 480px) { + .toggle_menu h2 { + font-size: 16px; + text-align: center; + height: 30px; + } +} + diff --git a/couchpotato/core/media/movie/suggestion/static/suggest.css b/couchpotato/core/media/movie/suggestion/static/suggest.css deleted file mode 100644 index 8e74784..0000000 --- a/couchpotato/core/media/movie/suggestion/static/suggest.css +++ /dev/null @@ -1,162 +0,0 @@ -.suggestions { - clear: both; - padding-top: 10px; - margin-bottom: 30px; -} - - .suggestions > h2 { - height: 40px; - } - -.suggestions .media_result { - display: inline-block; - width: 33.333%; - height: 150px; -} - - @media all and (max-width: 960px) { - .suggestions .media_result { - width: 50%; - } - } - - @media all and (max-width: 600px) { - .suggestions .media_result { - width: 100%; - } - } - - .suggestions .media_result .data { - left: 100px; - background: #4e5969; - border: none; - } - - .suggestions .media_result .data .info { - top: 10px; - left: 15px; - right: 15px; - bottom: 10px; - overflow: hidden; - } - - .suggestions .media_result .data .info h2 { - white-space: normal; - max-height: 120px; - font-size: 18px; - line-height: 18px; - } - - .suggestions .media_result .data .info .rating, - .suggestions .media_result .data .info .genres, - .suggestions .media_result .data .info .year { - position: static; - display: block; - padding: 0; - opacity: .6; - } - - .suggestions .media_result .data .info .year { - margin: 10px 0 0; - } - - .suggestions .media_result .data .info .rating { - font-size: 20px; - float: right; - margin-top: -20px; - } - .suggestions .media_result .data .info .rating:before { - content: "\e031"; - font-family: 'Elusive-Icons'; - font-size: 14px; - margin: 0 5px 0 0; - vertical-align: bottom; - } - - .suggestions .media_result .data .info .genres { - font-size: 11px; - font-style: italic; - text-align: right; - } - - .suggestions .media_result .data .info .plot { - display: block; - font-size: 11px; - overflow: hidden; - text-align: justify; - height: 100%; - z-index: 2; - top: 64px; - position: absolute; - background: #4e5969; - cursor: pointer; - transition: all .4s ease-in-out; - padding: 0 3px 10px 0; - } - .suggestions .media_result .data:before { - content: ''; - display: block; - height: 10px; - right: 0; - left: 0; - bottom: 10px; - position: absolute; - background: linear-gradient( - 0deg, - rgba(78, 89, 105, 1) 0%, - rgba(78, 89, 105, 0) 100% - ); - z-index: 3; - pointer-events: none; - } - - .suggestions .media_result .data .info .plot.full { - top: 0; - overflow: auto; - } - - .suggestions .media_result .data { - cursor: default; - } - - .suggestions .media_result .options { - left: 100px; - } - .suggestions .media_result .options select[name=title] { width: 100%; } - .suggestions .media_result .options select[name=profile] { width: 100%; } - .suggestions .media_result .options select[name=category] { width: 100%; } - - .suggestions .media_result .button { - position: absolute; - margin: 2px 0 0 0; - right: 15px; - bottom: 15px; - } - - - .suggestions .media_result .thumbnail { - width: 100px; - } - - .suggestions .media_result .actions { - position: absolute; - top: 10px; - right: 10px; - display: none; - width: 140px; - } - .suggestions .media_result:hover .actions { - display: block; - } - .suggestions .media_result:hover h2 .title { - opacity: 0; - } - .suggestions .media_result .data.open .actions { - display: none; - } - - .suggestions .media_result .actions a { - margin-left: 10px; - vertical-align: middle; - } - diff --git a/couchpotato/core/media/movie/suggestion/static/suggest.scss b/couchpotato/core/media/movie/suggestion/static/suggest.scss new file mode 100644 index 0000000..8e74784 --- /dev/null +++ b/couchpotato/core/media/movie/suggestion/static/suggest.scss @@ -0,0 +1,162 @@ +.suggestions { + clear: both; + padding-top: 10px; + margin-bottom: 30px; +} + + .suggestions > h2 { + height: 40px; + } + +.suggestions .media_result { + display: inline-block; + width: 33.333%; + height: 150px; +} + + @media all and (max-width: 960px) { + .suggestions .media_result { + width: 50%; + } + } + + @media all and (max-width: 600px) { + .suggestions .media_result { + width: 100%; + } + } + + .suggestions .media_result .data { + left: 100px; + background: #4e5969; + border: none; + } + + .suggestions .media_result .data .info { + top: 10px; + left: 15px; + right: 15px; + bottom: 10px; + overflow: hidden; + } + + .suggestions .media_result .data .info h2 { + white-space: normal; + max-height: 120px; + font-size: 18px; + line-height: 18px; + } + + .suggestions .media_result .data .info .rating, + .suggestions .media_result .data .info .genres, + .suggestions .media_result .data .info .year { + position: static; + display: block; + padding: 0; + opacity: .6; + } + + .suggestions .media_result .data .info .year { + margin: 10px 0 0; + } + + .suggestions .media_result .data .info .rating { + font-size: 20px; + float: right; + margin-top: -20px; + } + .suggestions .media_result .data .info .rating:before { + content: "\e031"; + font-family: 'Elusive-Icons'; + font-size: 14px; + margin: 0 5px 0 0; + vertical-align: bottom; + } + + .suggestions .media_result .data .info .genres { + font-size: 11px; + font-style: italic; + text-align: right; + } + + .suggestions .media_result .data .info .plot { + display: block; + font-size: 11px; + overflow: hidden; + text-align: justify; + height: 100%; + z-index: 2; + top: 64px; + position: absolute; + background: #4e5969; + cursor: pointer; + transition: all .4s ease-in-out; + padding: 0 3px 10px 0; + } + .suggestions .media_result .data:before { + content: ''; + display: block; + height: 10px; + right: 0; + left: 0; + bottom: 10px; + position: absolute; + background: linear-gradient( + 0deg, + rgba(78, 89, 105, 1) 0%, + rgba(78, 89, 105, 0) 100% + ); + z-index: 3; + pointer-events: none; + } + + .suggestions .media_result .data .info .plot.full { + top: 0; + overflow: auto; + } + + .suggestions .media_result .data { + cursor: default; + } + + .suggestions .media_result .options { + left: 100px; + } + .suggestions .media_result .options select[name=title] { width: 100%; } + .suggestions .media_result .options select[name=profile] { width: 100%; } + .suggestions .media_result .options select[name=category] { width: 100%; } + + .suggestions .media_result .button { + position: absolute; + margin: 2px 0 0 0; + right: 15px; + bottom: 15px; + } + + + .suggestions .media_result .thumbnail { + width: 100px; + } + + .suggestions .media_result .actions { + position: absolute; + top: 10px; + right: 10px; + display: none; + width: 140px; + } + .suggestions .media_result:hover .actions { + display: block; + } + .suggestions .media_result:hover h2 .title { + opacity: 0; + } + .suggestions .media_result .data.open .actions { + display: none; + } + + .suggestions .media_result .actions a { + margin-left: 10px; + vertical-align: middle; + } + diff --git a/couchpotato/core/plugins/category/static/category.css b/couchpotato/core/plugins/category/static/category.css deleted file mode 100644 index 3218a79..0000000 --- a/couchpotato/core/plugins/category/static/category.css +++ /dev/null @@ -1,82 +0,0 @@ -.add_new_category { - padding: 20px; - display: block; - text-align: center; - font-size: 20px; - border-bottom: 1px solid rgba(255,255,255,0.2); -} - -.category { - border-bottom: 1px solid rgba(255,255,255,0.2); - position: relative; -} - - .category > .delete { - position: absolute; - padding: 16px; - right: 0; - cursor: pointer; - opacity: 0.6; - color: #fd5353; - } - .category > .delete:hover { - opacity: 1; - } - - .category .ctrlHolder:hover { - background: none; - } - - .category .formHint { - width: 250px !important; - margin: 0 !important; - opacity: 0.1; - } - .category:hover .formHint { - opacity: 1; - } - -#category_ordering { - -} - - #category_ordering ul { - float: left; - margin: 0; - width: 275px; - padding: 0; - } - - #category_ordering li { - cursor: -webkit-grab; - cursor: -moz-grab; - cursor: grab; - border-bottom: 1px solid rgba(255,255,255,0.2); - padding: 0 5px; - } - #category_ordering li:last-child { border: 0; } - - #category_ordering li .check { - margin: 2px 10px 0 0; - vertical-align: top; - } - - #category_ordering li > span { - display: inline-block; - height: 20px; - vertical-align: top; - line-height: 20px; - } - - #category_ordering li .handle { - background: url('../../images/handle.png') center; - width: 20px; - float: right; - } - - #category_ordering .formHint { - clear: none; - float: right; - width: 250px; - margin: 0; - } diff --git a/couchpotato/core/plugins/category/static/category.scss b/couchpotato/core/plugins/category/static/category.scss new file mode 100644 index 0000000..3218a79 --- /dev/null +++ b/couchpotato/core/plugins/category/static/category.scss @@ -0,0 +1,82 @@ +.add_new_category { + padding: 20px; + display: block; + text-align: center; + font-size: 20px; + border-bottom: 1px solid rgba(255,255,255,0.2); +} + +.category { + border-bottom: 1px solid rgba(255,255,255,0.2); + position: relative; +} + + .category > .delete { + position: absolute; + padding: 16px; + right: 0; + cursor: pointer; + opacity: 0.6; + color: #fd5353; + } + .category > .delete:hover { + opacity: 1; + } + + .category .ctrlHolder:hover { + background: none; + } + + .category .formHint { + width: 250px !important; + margin: 0 !important; + opacity: 0.1; + } + .category:hover .formHint { + opacity: 1; + } + +#category_ordering { + +} + + #category_ordering ul { + float: left; + margin: 0; + width: 275px; + padding: 0; + } + + #category_ordering li { + cursor: -webkit-grab; + cursor: -moz-grab; + cursor: grab; + border-bottom: 1px solid rgba(255,255,255,0.2); + padding: 0 5px; + } + #category_ordering li:last-child { border: 0; } + + #category_ordering li .check { + margin: 2px 10px 0 0; + vertical-align: top; + } + + #category_ordering li > span { + display: inline-block; + height: 20px; + vertical-align: top; + line-height: 20px; + } + + #category_ordering li .handle { + background: url('../../images/handle.png') center; + width: 20px; + float: right; + } + + #category_ordering .formHint { + clear: none; + float: right; + width: 250px; + margin: 0; + } diff --git a/couchpotato/core/plugins/log/static/log.css b/couchpotato/core/plugins/log/static/log.css deleted file mode 100644 index c7aace6..0000000 --- a/couchpotato/core/plugins/log/static/log.css +++ /dev/null @@ -1,199 +0,0 @@ -.page.log .nav { - display: block; - text-align: center; - padding: 0 0 30px; - margin: 0; - font-size: 20px; - position: fixed; - width: 100%; - bottom: 0; - left: 0; - background: #4E5969; - z-index: 100; -} - - .page.log .nav li { - display: inline-block; - padding: 5px 10px; - margin: 0; - } - - .page.log .nav li.select, - .page.log .nav li.clear { - cursor: pointer; - } - - .page.log .nav li:hover:not(.active):not(.filter) { - background: rgba(255, 255, 255, 0.1); - } - - .page.log .nav li.active { - font-weight: bold; - cursor: default; - background: rgba(255,255,255,.1); - } - - @media all and (max-width: 480px) { - .page.log .nav { - font-size: 14px; - } - - .page.log .nav li { - padding: 5px; - } - } - - .page.log .nav li.hint { - text-align: center; - width: 400px; - left: 50%; - margin-left: -200px; - font-style: italic; - font-size: 11px; - position: absolute; - right: 20px; - opacity: .5; - bottom: 5px; - } - -.page.log .loading { - text-align: center; - font-size: 20px; - padding: 50px; -} - -.page.log .container { - padding: 30px 0 60px; - overflow: hidden; - line-height: 150%; - font-size: 11px; - color: #FFF; -} - - .page.log .container select { - vertical-align: top; - } - - .page.log .container .time { - clear: both; - color: lightgrey; - font-size: 10px; - border-top: 1px solid rgba(255, 255, 255, 0.1); - position: relative; - overflow: hidden; - padding: 0 3px; - font-family: Lucida Console, Monaco, Nimbus Mono L, monospace, serif; - } - .page.log .container .time.highlight { - background: rgba(255, 255, 255, 0.1); - } - .page.log .container .time span { - padding: 5px 0 3px; - display: inline-block; - vertical-align: middle; - } - - .page.log[data-filter=INFO] .error, - .page.log[data-filter=INFO] .debug, - .page.log[data-filter=ERROR] .debug, - .page.log[data-filter=ERROR] .info, - .page.log[data-filter=DEBUG] .info, - .page.log[data-filter=DEBUG] .error { - display: none; - } - - .page.log .container .type { - margin-left: 10px; - } - - .page.log .container .message { - float: right; - width: 86%; - white-space: pre-wrap; - } - - .page.log .container .error { color: #FFA4A4; } - .page.log .container .debug span { opacity: .6; } - -.do_report { - position: absolute; - padding: 10px; -} - -.page.log .report { - position: fixed; - width: 100%; - height: 100%; - background: rgba(0,0,0,.7); - left: 0; - top: 0; - z-index: 99999; - font-size: 14px; -} - - .page.log .report .button { - display: inline-block; - margin: 10px 0; - padding: 10px; - } - - .page.log .report .bug { - width: 800px; - height: 80%; - position: absolute; - left: 50%; - top: 50%; - margin: 0 0 0 -400px; - transform: translate(0, -50%); - } - - .page.log .report .bug textarea { - display: block; - width: 100%; - background: #FFF; - padding: 20px; - overflow: auto; - color: #666; - height: 70%; - font-size: 12px; - } - -.page.log .container .time ::-webkit-selection { - background-color: #000; - color: #FFF; -} - -.page.log .container .time ::-moz-selection { - background-color: #000; - color: #FFF; -} - -.page.log .container .time ::-ms-selection { - background-color: #000; - color: #FFF; -} - -.page.log .container .time.highlight ::selection { - background-color: transparent; - color: inherit; -} - -.page.log .container .time.highlight ::-webkit-selection { - background-color: transparent; - color: inherit; -} - -.page.log .container .time.highlight ::-moz-selection { - background-color: transparent; - color: inherit; -} - -.page.log .container .time.highlight ::-ms-selection { - background-color: transparent; - color: inherit; -} - -.page.log .container .time.highlight ::selection { - background-color: transparent; - color: inherit; -} diff --git a/couchpotato/core/plugins/log/static/log.scss b/couchpotato/core/plugins/log/static/log.scss new file mode 100644 index 0000000..c7aace6 --- /dev/null +++ b/couchpotato/core/plugins/log/static/log.scss @@ -0,0 +1,199 @@ +.page.log .nav { + display: block; + text-align: center; + padding: 0 0 30px; + margin: 0; + font-size: 20px; + position: fixed; + width: 100%; + bottom: 0; + left: 0; + background: #4E5969; + z-index: 100; +} + + .page.log .nav li { + display: inline-block; + padding: 5px 10px; + margin: 0; + } + + .page.log .nav li.select, + .page.log .nav li.clear { + cursor: pointer; + } + + .page.log .nav li:hover:not(.active):not(.filter) { + background: rgba(255, 255, 255, 0.1); + } + + .page.log .nav li.active { + font-weight: bold; + cursor: default; + background: rgba(255,255,255,.1); + } + + @media all and (max-width: 480px) { + .page.log .nav { + font-size: 14px; + } + + .page.log .nav li { + padding: 5px; + } + } + + .page.log .nav li.hint { + text-align: center; + width: 400px; + left: 50%; + margin-left: -200px; + font-style: italic; + font-size: 11px; + position: absolute; + right: 20px; + opacity: .5; + bottom: 5px; + } + +.page.log .loading { + text-align: center; + font-size: 20px; + padding: 50px; +} + +.page.log .container { + padding: 30px 0 60px; + overflow: hidden; + line-height: 150%; + font-size: 11px; + color: #FFF; +} + + .page.log .container select { + vertical-align: top; + } + + .page.log .container .time { + clear: both; + color: lightgrey; + font-size: 10px; + border-top: 1px solid rgba(255, 255, 255, 0.1); + position: relative; + overflow: hidden; + padding: 0 3px; + font-family: Lucida Console, Monaco, Nimbus Mono L, monospace, serif; + } + .page.log .container .time.highlight { + background: rgba(255, 255, 255, 0.1); + } + .page.log .container .time span { + padding: 5px 0 3px; + display: inline-block; + vertical-align: middle; + } + + .page.log[data-filter=INFO] .error, + .page.log[data-filter=INFO] .debug, + .page.log[data-filter=ERROR] .debug, + .page.log[data-filter=ERROR] .info, + .page.log[data-filter=DEBUG] .info, + .page.log[data-filter=DEBUG] .error { + display: none; + } + + .page.log .container .type { + margin-left: 10px; + } + + .page.log .container .message { + float: right; + width: 86%; + white-space: pre-wrap; + } + + .page.log .container .error { color: #FFA4A4; } + .page.log .container .debug span { opacity: .6; } + +.do_report { + position: absolute; + padding: 10px; +} + +.page.log .report { + position: fixed; + width: 100%; + height: 100%; + background: rgba(0,0,0,.7); + left: 0; + top: 0; + z-index: 99999; + font-size: 14px; +} + + .page.log .report .button { + display: inline-block; + margin: 10px 0; + padding: 10px; + } + + .page.log .report .bug { + width: 800px; + height: 80%; + position: absolute; + left: 50%; + top: 50%; + margin: 0 0 0 -400px; + transform: translate(0, -50%); + } + + .page.log .report .bug textarea { + display: block; + width: 100%; + background: #FFF; + padding: 20px; + overflow: auto; + color: #666; + height: 70%; + font-size: 12px; + } + +.page.log .container .time ::-webkit-selection { + background-color: #000; + color: #FFF; +} + +.page.log .container .time ::-moz-selection { + background-color: #000; + color: #FFF; +} + +.page.log .container .time ::-ms-selection { + background-color: #000; + color: #FFF; +} + +.page.log .container .time.highlight ::selection { + background-color: transparent; + color: inherit; +} + +.page.log .container .time.highlight ::-webkit-selection { + background-color: transparent; + color: inherit; +} + +.page.log .container .time.highlight ::-moz-selection { + background-color: transparent; + color: inherit; +} + +.page.log .container .time.highlight ::-ms-selection { + background-color: transparent; + color: inherit; +} + +.page.log .container .time.highlight ::selection { + background-color: transparent; + color: inherit; +} diff --git a/couchpotato/core/plugins/profile/static/profile.css b/couchpotato/core/plugins/profile/static/profile.css deleted file mode 100644 index df93944..0000000 --- a/couchpotato/core/plugins/profile/static/profile.css +++ /dev/null @@ -1,197 +0,0 @@ -.add_new_profile { - padding: 20px; - display: block; - text-align: center; - font-size: 20px; - border-bottom: 1px solid rgba(255,255,255,0.2); -} - -.profile { - border-bottom: 1px solid rgba(255,255,255,0.2); - position: relative; -} - - .profile > .delete { - position: absolute; - padding: 16px; - right: 0; - cursor: pointer; - opacity: 0.6; - color: #fd5353; - } - .profile > .delete:hover { - opacity: 1; - } - - .profile .ctrlHolder:hover { - background: none; - } - - .profile .qualities { - min-height: 80px; - } - - .profile .formHint { - width: 210px !important; - vertical-align: top !important; - margin: 0 !important; - padding-left: 3px !important; - opacity: 0.1; - } - .profile:hover .formHint { - opacity: 1; - } - - .profile .wait_for { - padding-top: 0; - padding-bottom: 20px; - } - - .profile .wait_for input { - margin: 0 5px !important; - } - - .profile .wait_for .minimum_score_input { - width: 40px !important; - text-align: left; - } - - .profile .types { - padding: 0; - margin: 0 20px 0 -4px; - display: inline-block; - } - - .profile .types li { - padding: 3px 5px; - border-bottom: 1px solid rgba(255,255,255,0.2); - list-style: none; - } - .profile .types li:last-child { border: 0; } - - .profile .types li > * { - display: inline-block; - vertical-align: middle; - line-height: 0; - margin-right: 10px; - } - - .profile .type .check { - margin-top: -1px; - } - - .profile .quality_type select { - width: 120px; - margin-left: -1px; - } - - .profile .types li.is_empty .check, - .profile .types li.is_empty .delete, - .profile .types li.is_empty .handle, - .profile .types li.is_empty .check_label { - visibility: hidden; - } - - .profile .types .type label { - display: inline-block; - width: auto; - float: none; - text-transform: uppercase; - font-size: 11px; - font-weight: normal; - margin-right: 20px; - text-shadow: none; - vertical-align: bottom; - padding: 0; - height: 17px; - } - .profile .types .type label .check { - margin-right: 5px; - } - .profile .types .type label .check_label { - display: inline-block; - vertical-align: top; - height: 16px; - line-height: 13px; - } - - .profile .types .type .threed { - display: none; - } - - .profile .types .type.allow_3d .threed { - display: inline-block; - } - - .profile .types .type .handle { - background: url('../../images/handle.png') center; - display: inline-block; - height: 20px; - width: 20px; - cursor: -moz-grab; - cursor: -webkit-grab; - cursor: grab; - margin: 0; - } - - .profile .types .type .delete { - height: 20px; - width: 20px; - line-height: 20px; - visibility: hidden; - cursor: pointer; - font-size: 13px; - color: #fd5353; - } - .profile .types .type:not(.allow_3d) .delete { - margin-left: 55px; - } - - .profile .types .type:hover:not(.is_empty) .delete { - visibility: visible; - } - -#profile_ordering { - -} - - #profile_ordering ul { - float: left; - margin: 0; - width: 275px; - padding: 0; - } - - #profile_ordering li { - border-bottom: 1px solid rgba(255,255,255,0.2); - padding: 0 5px; - } - #profile_ordering li:last-child { border: 0; } - - #profile_ordering li .check { - margin: 2px 10px 0 0; - vertical-align: top; - } - - #profile_ordering li > span { - display: inline-block; - height: 20px; - vertical-align: top; - line-height: 20px; - } - - #profile_ordering li .handle { - background: url('../../images/handle.png') center; - width: 20px; - float: right; - cursor: -webkit-grab; - cursor: -moz-grab; - cursor: grab; - } - - #profile_ordering .formHint { - clear: none; - float: right; - width: 250px; - margin: 0; - } diff --git a/couchpotato/core/plugins/profile/static/profile.scss b/couchpotato/core/plugins/profile/static/profile.scss new file mode 100644 index 0000000..df93944 --- /dev/null +++ b/couchpotato/core/plugins/profile/static/profile.scss @@ -0,0 +1,197 @@ +.add_new_profile { + padding: 20px; + display: block; + text-align: center; + font-size: 20px; + border-bottom: 1px solid rgba(255,255,255,0.2); +} + +.profile { + border-bottom: 1px solid rgba(255,255,255,0.2); + position: relative; +} + + .profile > .delete { + position: absolute; + padding: 16px; + right: 0; + cursor: pointer; + opacity: 0.6; + color: #fd5353; + } + .profile > .delete:hover { + opacity: 1; + } + + .profile .ctrlHolder:hover { + background: none; + } + + .profile .qualities { + min-height: 80px; + } + + .profile .formHint { + width: 210px !important; + vertical-align: top !important; + margin: 0 !important; + padding-left: 3px !important; + opacity: 0.1; + } + .profile:hover .formHint { + opacity: 1; + } + + .profile .wait_for { + padding-top: 0; + padding-bottom: 20px; + } + + .profile .wait_for input { + margin: 0 5px !important; + } + + .profile .wait_for .minimum_score_input { + width: 40px !important; + text-align: left; + } + + .profile .types { + padding: 0; + margin: 0 20px 0 -4px; + display: inline-block; + } + + .profile .types li { + padding: 3px 5px; + border-bottom: 1px solid rgba(255,255,255,0.2); + list-style: none; + } + .profile .types li:last-child { border: 0; } + + .profile .types li > * { + display: inline-block; + vertical-align: middle; + line-height: 0; + margin-right: 10px; + } + + .profile .type .check { + margin-top: -1px; + } + + .profile .quality_type select { + width: 120px; + margin-left: -1px; + } + + .profile .types li.is_empty .check, + .profile .types li.is_empty .delete, + .profile .types li.is_empty .handle, + .profile .types li.is_empty .check_label { + visibility: hidden; + } + + .profile .types .type label { + display: inline-block; + width: auto; + float: none; + text-transform: uppercase; + font-size: 11px; + font-weight: normal; + margin-right: 20px; + text-shadow: none; + vertical-align: bottom; + padding: 0; + height: 17px; + } + .profile .types .type label .check { + margin-right: 5px; + } + .profile .types .type label .check_label { + display: inline-block; + vertical-align: top; + height: 16px; + line-height: 13px; + } + + .profile .types .type .threed { + display: none; + } + + .profile .types .type.allow_3d .threed { + display: inline-block; + } + + .profile .types .type .handle { + background: url('../../images/handle.png') center; + display: inline-block; + height: 20px; + width: 20px; + cursor: -moz-grab; + cursor: -webkit-grab; + cursor: grab; + margin: 0; + } + + .profile .types .type .delete { + height: 20px; + width: 20px; + line-height: 20px; + visibility: hidden; + cursor: pointer; + font-size: 13px; + color: #fd5353; + } + .profile .types .type:not(.allow_3d) .delete { + margin-left: 55px; + } + + .profile .types .type:hover:not(.is_empty) .delete { + visibility: visible; + } + +#profile_ordering { + +} + + #profile_ordering ul { + float: left; + margin: 0; + width: 275px; + padding: 0; + } + + #profile_ordering li { + border-bottom: 1px solid rgba(255,255,255,0.2); + padding: 0 5px; + } + #profile_ordering li:last-child { border: 0; } + + #profile_ordering li .check { + margin: 2px 10px 0 0; + vertical-align: top; + } + + #profile_ordering li > span { + display: inline-block; + height: 20px; + vertical-align: top; + line-height: 20px; + } + + #profile_ordering li .handle { + background: url('../../images/handle.png') center; + width: 20px; + float: right; + cursor: -webkit-grab; + cursor: -moz-grab; + cursor: grab; + } + + #profile_ordering .formHint { + clear: none; + float: right; + width: 250px; + margin: 0; + } diff --git a/couchpotato/core/plugins/quality/static/quality.css b/couchpotato/core/plugins/quality/static/quality.css deleted file mode 100644 index f71f007..0000000 --- a/couchpotato/core/plugins/quality/static/quality.css +++ /dev/null @@ -1,26 +0,0 @@ -.group_sizes { - -} - - .group_sizes .head { - font-weight: bold; - } - - .group_sizes .ctrlHolder { - padding-top: 4px !important; - padding-bottom: 4px !important; - font-size: 12px; - } - - .group_sizes .label { - max-width: 120px; - } - - .group_sizes .min, .group_sizes .max { - text-align: center; - width: 50px; - max-width: 50px; - margin: 0 5px !important; - padding: 0 3px; - display: inline-block; - } \ No newline at end of file diff --git a/couchpotato/core/plugins/quality/static/quality.scss b/couchpotato/core/plugins/quality/static/quality.scss new file mode 100644 index 0000000..f71f007 --- /dev/null +++ b/couchpotato/core/plugins/quality/static/quality.scss @@ -0,0 +1,26 @@ +.group_sizes { + +} + + .group_sizes .head { + font-weight: bold; + } + + .group_sizes .ctrlHolder { + padding-top: 4px !important; + padding-bottom: 4px !important; + font-size: 12px; + } + + .group_sizes .label { + max-width: 120px; + } + + .group_sizes .min, .group_sizes .max { + text-align: center; + width: 50px; + max-width: 50px; + margin: 0 5px !important; + padding: 0 3px; + display: inline-block; + } \ No newline at end of file diff --git a/couchpotato/core/plugins/userscript/static/userscript.css b/couchpotato/core/plugins/userscript/static/userscript.css deleted file mode 100644 index d816101..0000000 --- a/couchpotato/core/plugins/userscript/static/userscript.css +++ /dev/null @@ -1,38 +0,0 @@ -.page.userscript { - position: absolute; - width: 100%; - top: 0; - bottom: 0; - left: 0; - right: 0; - padding: 0; -} - - .page.userscript .frame.loading { - text-align: center; - font-size: 20px; - padding: 20px; - } - - .page.userscript .media_result { - height: 140px; - } - .page.userscript .media_result .thumbnail { - width: 90px; - } - .page.userscript .media_result .options { - left: 90px; - padding: 54px 15px; - } - - .page.userscript .media_result .year { - display: none; - } - - .page.userscript .media_result .options select[name="title"] { - width: 190px; - } - - .page.userscript .media_result .options select[name="profile"] { - width: 70px; - } diff --git a/couchpotato/core/plugins/userscript/static/userscript.scss b/couchpotato/core/plugins/userscript/static/userscript.scss new file mode 100644 index 0000000..d816101 --- /dev/null +++ b/couchpotato/core/plugins/userscript/static/userscript.scss @@ -0,0 +1,38 @@ +.page.userscript { + position: absolute; + width: 100%; + top: 0; + bottom: 0; + left: 0; + right: 0; + padding: 0; +} + + .page.userscript .frame.loading { + text-align: center; + font-size: 20px; + padding: 20px; + } + + .page.userscript .media_result { + height: 140px; + } + .page.userscript .media_result .thumbnail { + width: 90px; + } + .page.userscript .media_result .options { + left: 90px; + padding: 54px 15px; + } + + .page.userscript .media_result .year { + display: none; + } + + .page.userscript .media_result .options select[name="title"] { + width: 190px; + } + + .page.userscript .media_result .options select[name="profile"] { + width: 70px; + } diff --git a/couchpotato/core/plugins/wizard/static/wizard.css b/couchpotato/core/plugins/wizard/static/wizard.css deleted file mode 100644 index 9af32ed..0000000 --- a/couchpotato/core/plugins/wizard/static/wizard.css +++ /dev/null @@ -1,84 +0,0 @@ -.page.wizard .uniForm { - margin: 0 0 30px; - width: 83%; -} - -.page.wizard h1 { - padding: 10px 0; - display: block; - font-size: 30px; - margin: 80px 5px 0; -} - -.page.wizard .description { - padding: 10px 5px; - font-size: 1.45em; - line-height: 1.4em; - display: block; -} - -.page.wizard .tab_wrapper { - background: #5c697b; - height: 65px; - font-size: 1.75em; - position: fixed; - top: 0; - margin: 0; - width: 100%; - left: 0; - z-index: 2; - box-shadow: 0 0 10px rgba(0,0,0,0.1); -} - - .page.wizard .tab_wrapper .tabs { - padding: 0; - margin: 0 auto; - display: block; - height: 100%; - width: 100%; - max-width: 960px; - } - - .page.wizard .tabs li { - display: inline-block; - height: 100%; - } - .page.wizard .tabs li a { - padding: 20px 10px; - height: 100%; - display: block; - color: #FFF; - font-weight: normal; - border-bottom: 4px solid transparent; - } - - .page.wizard .tabs li:hover a { border-color: #047792; } - .page.wizard .tabs li.done a { border-color: #04bce6; } - - .page.wizard .tab_wrapper .pointer { - border-right: 10px solid transparent; - border-left: 10px solid transparent; - border-top: 10px solid #5c697b; - display: block; - position: absolute; - top: 44px; - } - -.page.wizard .tab_content { - margin: 20px 0 160px; -} - -.page.wizard form > div { - min-height: 300px; -} - -.page.wizard .button.green { - padding: 20px; - font-size: 25px; - margin: 10px 0 80px; - display: block; -} - -.page.wizard .tab_nzb_providers { - margin: 20px 0 0 0; -} diff --git a/couchpotato/core/plugins/wizard/static/wizard.scss b/couchpotato/core/plugins/wizard/static/wizard.scss new file mode 100644 index 0000000..9af32ed --- /dev/null +++ b/couchpotato/core/plugins/wizard/static/wizard.scss @@ -0,0 +1,84 @@ +.page.wizard .uniForm { + margin: 0 0 30px; + width: 83%; +} + +.page.wizard h1 { + padding: 10px 0; + display: block; + font-size: 30px; + margin: 80px 5px 0; +} + +.page.wizard .description { + padding: 10px 5px; + font-size: 1.45em; + line-height: 1.4em; + display: block; +} + +.page.wizard .tab_wrapper { + background: #5c697b; + height: 65px; + font-size: 1.75em; + position: fixed; + top: 0; + margin: 0; + width: 100%; + left: 0; + z-index: 2; + box-shadow: 0 0 10px rgba(0,0,0,0.1); +} + + .page.wizard .tab_wrapper .tabs { + padding: 0; + margin: 0 auto; + display: block; + height: 100%; + width: 100%; + max-width: 960px; + } + + .page.wizard .tabs li { + display: inline-block; + height: 100%; + } + .page.wizard .tabs li a { + padding: 20px 10px; + height: 100%; + display: block; + color: #FFF; + font-weight: normal; + border-bottom: 4px solid transparent; + } + + .page.wizard .tabs li:hover a { border-color: #047792; } + .page.wizard .tabs li.done a { border-color: #04bce6; } + + .page.wizard .tab_wrapper .pointer { + border-right: 10px solid transparent; + border-left: 10px solid transparent; + border-top: 10px solid #5c697b; + display: block; + position: absolute; + top: 44px; + } + +.page.wizard .tab_content { + margin: 20px 0 160px; +} + +.page.wizard form > div { + min-height: 300px; +} + +.page.wizard .button.green { + padding: 20px; + font-size: 25px; + margin: 10px 0 80px; + display: block; +} + +.page.wizard .tab_nzb_providers { + margin: 20px 0 0 0; +} diff --git a/couchpotato/static/style/api.css b/couchpotato/static/style/api.css deleted file mode 100644 index 3d6952d..0000000 --- a/couchpotato/static/style/api.css +++ /dev/null @@ -1,162 +0,0 @@ -html { - line-height: 1.5; - font-family: "Helvetica Neue", Helvetica, Arial, Geneva, sans-serif; - font-size: 14px; -} - -* { - margin: 0; - padding: 0; - -moz-box-sizing: border-box; - -webkit-box-sizing: border-box; - box-sizing: border-box; -} - -h1, h2, h3, h4, h5 { - clear: both; - font-size: 14px; -} - -h1 { - font-size: 25px; - padding: 20px 40px; -} - -h2 { - font-size: 20px; -} - -pre { - background: #eee; - font-family: monospace; - margin: 0; - padding: 10px; - width: 100%; - display: block; - font-size: 12px; -} - -.api, .missing { - overflow: hidden; - border-bottom: 1px solid #eee; - padding: 40px; -} - .api:hover { - color: #000; - } - - .api .description { - color: #333; - padding: 0 0 5px; - } - - .api .params { - background: #fafafa; - width: 100%; - } - .api h3 { - clear: both; - float: left; - width: 100px; - } - - .api .params { - float: left; - width: 700px; - } - - .api .params td, .api .params th { - padding: 3px 5px; - border-bottom: 1px solid #eee; - } - .api .params tr:last-child td, .api .params tr:last-child th { - border: 0; - } - - .api .params .param { - vertical-align: top; - } - - .api .params .param th { - text-align: left; - width: 100px; - } - - .api .param .type { - font-style: italic; - margin-right: 10px; - width: 100px; - color: #666; - } - - .api .return { - float: left; - width: 700px; - } - -.database { - padding: 20px; - margin: 0; -} - - .database * { - margin: 0; - padding: 0; - } - - .database .nav { - } - .database .nav li { - display: inline-block; - } - .database .nav li a { - padding: 5px; - } - - .database table { - font-size: 11px; - } - - .database table th { - text-align: left; - } - - .database table tr:hover { - position: relative; - z-index: 20; - } - - .database table td { - vertical-align: top; - position: relative; - } - - .database table .id { - width: 100px; - } - - .database table ._rev { - width: 60px; - } - - .database table ._t { - width: 60px; - } - - .database table .form { - width: 600px; - } - - .database table form { - width: 600px; - } - - .database textarea { - font-size: 12px; - width: 100%; - height: 200px; - } - - .database input[type=submit] { - display: block; - } diff --git a/couchpotato/static/style/api.scss b/couchpotato/static/style/api.scss new file mode 100644 index 0000000..3d6952d --- /dev/null +++ b/couchpotato/static/style/api.scss @@ -0,0 +1,162 @@ +html { + line-height: 1.5; + font-family: "Helvetica Neue", Helvetica, Arial, Geneva, sans-serif; + font-size: 14px; +} + +* { + margin: 0; + padding: 0; + -moz-box-sizing: border-box; + -webkit-box-sizing: border-box; + box-sizing: border-box; +} + +h1, h2, h3, h4, h5 { + clear: both; + font-size: 14px; +} + +h1 { + font-size: 25px; + padding: 20px 40px; +} + +h2 { + font-size: 20px; +} + +pre { + background: #eee; + font-family: monospace; + margin: 0; + padding: 10px; + width: 100%; + display: block; + font-size: 12px; +} + +.api, .missing { + overflow: hidden; + border-bottom: 1px solid #eee; + padding: 40px; +} + .api:hover { + color: #000; + } + + .api .description { + color: #333; + padding: 0 0 5px; + } + + .api .params { + background: #fafafa; + width: 100%; + } + .api h3 { + clear: both; + float: left; + width: 100px; + } + + .api .params { + float: left; + width: 700px; + } + + .api .params td, .api .params th { + padding: 3px 5px; + border-bottom: 1px solid #eee; + } + .api .params tr:last-child td, .api .params tr:last-child th { + border: 0; + } + + .api .params .param { + vertical-align: top; + } + + .api .params .param th { + text-align: left; + width: 100px; + } + + .api .param .type { + font-style: italic; + margin-right: 10px; + width: 100px; + color: #666; + } + + .api .return { + float: left; + width: 700px; + } + +.database { + padding: 20px; + margin: 0; +} + + .database * { + margin: 0; + padding: 0; + } + + .database .nav { + } + .database .nav li { + display: inline-block; + } + .database .nav li a { + padding: 5px; + } + + .database table { + font-size: 11px; + } + + .database table th { + text-align: left; + } + + .database table tr:hover { + position: relative; + z-index: 20; + } + + .database table td { + vertical-align: top; + position: relative; + } + + .database table .id { + width: 100px; + } + + .database table ._rev { + width: 60px; + } + + .database table ._t { + width: 60px; + } + + .database table .form { + width: 600px; + } + + .database table form { + width: 600px; + } + + .database textarea { + font-size: 12px; + width: 100%; + height: 200px; + } + + .database input[type=submit] { + display: block; + } diff --git a/couchpotato/static/style/main.css b/couchpotato/static/style/main.css deleted file mode 100644 index c7850b2..0000000 --- a/couchpotato/static/style/main.css +++ /dev/null @@ -1,965 +0,0 @@ -body, html { - color: #fff; - font-size: 12px; - line-height: 1.5; - font-family: OpenSans, "Helvetica Neue", Helvetica, Arial, Geneva, sans-serif; - height: 100%; - margin: 0; - padding: 0; - background: #4e5969; - -webkit-font-smoothing: subpixel-antialiased; - -moz-osx-font-smoothing: grayscale; -} - body { overflow-y: scroll; } - body.noscroll { overflow: hidden; } - - #clean { - background: transparent !important; - } - -* { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -pre { - white-space: pre-wrap; - word-wrap: break-word; -} - -input, textarea { - font-size: 1em; - font-family: "Helvetica Neue", Helvetica, Arial, Geneva, sans-serif; -} -input:focus, textarea:focus { - outline: none; -} - -::-moz-placeholder { - color: rgba(255, 255, 255, 0.5); - font-style: italic; -} -input:-moz-placeholder { - color: rgba(255, 255, 255, 0.5); - font-style: italic; -} -::-webkit-input-placeholder { - color: rgba(255, 255, 255, 0.5); - font-style: italic; -} -:-ms-input-placeholder { - color: rgba(255, 255, 255, 0.5) !important; - font-style: italic; -} - -.tiny_scroll { - overflow: hidden; -} - - .tiny_scroll:hover { - overflow-y: auto; - } - - .tiny_scroll::-webkit-scrollbar { - width: 5px; - } - - .tiny_scroll::-webkit-scrollbar-track { - -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.4); - -webkit-border-radius: 5px; - border-radius: 5px; - } - - .tiny_scroll::-webkit-scrollbar-thumb { - -webkit-border-radius: 5px; - border-radius: 5px; - background: rgba(255,255,255,0.3); - } - -a img { - border:none; -} - -a { - text-decoration:none; - color: #ebfcbc; - outline: 0; - cursor: pointer; - font-weight: bold; -} -a:hover { color: #f3f3f3; } - -.page { - display: none; - width: 100%; - max-width: 980px; - margin: 0 auto; - line-height: 1.5em; - padding: 0 15px 20px; -} - .page.active { display: block; } - -.content { - clear:both; - padding: 65px 0 10px; - background: #4e5969; -} - - @media all and (max-width: 480px) { - .content { - padding-top: 40px; - } - } - -h2 { - font-size: 2.5em; - padding: 0; - margin: 20px 0 0 0; -} - -form { - padding:0; - margin:0; -} - -body > .spinner, .mask{ - background: rgba(0,0,0, 0.9); - z-index: 100; - text-align: center; -} - body > .mask { - position: fixed; - top: 0; - left: 0; - height: 100%; - width: 100%; - padding: 200px; - } - - @media all and (max-width: 480px) { - body > .mask { - padding: 20px; - } - } - -.button { - background: #5082bc; - padding: 5px 10px 6px; - color: #fff; - text-decoration: none; - font-weight: bold; - line-height: 1; - border-radius: 2px; - cursor: pointer; - border: none; - -webkit-appearance: none; -} - .button.red { background-color: #ff0000; } - .button.green { background-color: #2aa300; } - .button.orange { background-color: #ffa200; } - .button.yellow { background-color: #ffe400; } - -/*** Icons ***/ -.icon { - display: inline-block; - background: center no-repeat; -} -.icon.delete { background-image: url('../images/icon.delete.png'); } -.icon.download { background-image: url('../images/icon.download.png'); } -.icon.edit { background-image: url('../images/icon.edit.png'); } -.icon.completed { background-image: url('../images/icon.check.png'); } -.icon.folder { background-image: url('../images/icon.folder.gif'); } -.icon.imdb { background-image: url('../images/icon.imdb.png'); } -.icon.refresh { background-image: url('../images/icon.refresh.png'); } -.icon.readd { background-image: url('../images/icon.readd.png'); } -.icon.rating { background-image: url('../images/icon.rating.png'); } -.icon.files { background-image: url('../images/icon.files.png'); } -.icon.info { background-image: url('../images/icon.info.png'); } -.icon.trailer { background-image: url('../images/icon.trailer.png'); } -.icon.spinner { background-image: url('../images/icon.spinner.gif'); } -.icon.attention { background-image: url('../images/icon.attention.png'); } - -.icon2 { - display: inline-block; - background: center no-repeat; - font-family: 'Elusive-Icons'; - speak: none; - font-style: normal; - font-weight: normal; - font-variant: normal; - text-transform: none; - line-height: 1; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - font-size: 15px; - color: #FFF; -} - -.icon2.add:before { content: "\e05a"; color: #c2fac5; } -.icon2.cog:before { content: "\e109"; } -.icon2.eye-open:before { content: "\e09d"; } -.icon2.search:before { content: "\e03e"; } -.icon2.return-key:before { content: "\e111"; } -.icon2.close:before { content: "\e04e"; } -.icon2.trailer:before { content: "\e0e9"; } -.icon2.download:before { content: "\e0c3"; } -.icon2.edit:before { content: "\e068"; } -.icon2.refresh:before { content: "\e04f"; font-weight: bold; } -.icon2.delete:before { content: "\e04e"; } -.icon2.directory:before { content: "\e097"; } -.icon2.completed:before { content: "\e070"; } -.icon2.info:before { content: "\e089"; } -.icon2.attention:before { content: "\e009"; } -.icon2.readd:before { - display: inline-block; - content: "\e04b"; - transform: scale(-1, 1); -} -.icon2.imdb:before { - content: "IMDb"; - color: #444; - padding: 2px; - border-radius: 3px; - background-color: #fbec98; - font: bold 8px Arial; - position: relative; - top: -3px; -} -.icon2.menu:before { - content: "\e076\00a0 \e076\00a0 \e076\00a0"; - line-height: 6px; - transform: scaleX(2); - width: 20px; - font-size: 10px; - display: inline-block; - vertical-align: middle; - word-wrap: break-word; - text-align:center; - margin-left: 5px; -} - @media screen and (-webkit-min-device-pixel-ratio:0) { - .icon2.menu:before { - margin-top: -7px; - } - } - -/*** Navigation ***/ -.header { - height: 66px; - position: fixed; - margin: 0; - width: 100%; - z-index: 5; - background: #5c697b; - box-shadow: 0 0 10px rgba(0,0,0,.1); - transition: all .4s ease-in-out; -} - - @media all and (max-width: 480px) { - .header { - height: 44px; - } - } - -.header > div { - width: 100%; - max-width: 980px; - margin: 0 auto; - position: relative; - height: 100%; - padding: 0 15px; -} - - .header .navigation { - display: inline-block; - vertical-align: middle; - position: absolute; - height: 100%; - left: 0; - bottom: 0; - } - - .header .foldout { - width: 44px; - height: 100%; - text-align: center; - border-right: 1px solid rgba(255,255,255,.07); - display: none; - vertical-align: top; - line-height: 42px; - color: #FFF; - } - - .header .logo { - display: inline-block; - font-size: 3em; - padding: 4px 30px 0 15px; - height: 100%; - border-right: 1px solid rgba(255,255,255,.07); - color: #FFF; - font-weight: normal; - vertical-align: top; - font-family: Lobster, sans-serif; - } - - @media all and (max-width: 480px) { - .header .foldout { - display: inline-block; - } - - .header .logo { - padding-top: 7px; - border: 0; - font-size: 1.7em; - } - } - - @media all and (min-width: 481px) and (max-width: 640px) { - - .header .logo { - display: none; - } - - } - - .header .navigation ul { - display: inline-block; - margin: 0; - padding: 0; - height: 100%; - } - - .header .navigation li { - color: #fff; - display: inline-block; - font-size: 1.75em; - margin: 0; - text-align: center; - height: 100%; - border: 1px solid rgba(255,255,255,.07); - border-width: 0 0 0 1px; - } - .header .navigation li:first-child { - border: none; - } - - .header .navigation li a { - display: block; - padding: 15px; - position: relative; - height: 100%; - border: 1px solid transparent; - border-width: 0 0 4px 0; - font-weight: normal; - } - - .header .navigation li:hover a { border-color: #047792; } - .header .navigation li.active a { border-color: #04bce6; } - - .header .navigation li.disabled { color: #e5e5e5; } - .header .navigation li a { color: #fff; } - - .header .navigation .backtotop { - opacity: 0; - display: block; - width: 80px; - left: 50%; - position: fixed; - bottom: 0; - text-align: center; - margin: -10px 0 0 -40px; - background: #4e5969; - padding: 5px 0; - color: rgba(255,255,255,.4); - font-weight: normal; - } - .header:hover .navigation .backtotop { color: #fff; } - - @media all and (max-width: 480px) { - - body { - position: absolute; - width: 100%; - transition: all .5s cubic-bezier(0.9,0,0.1,1); - left: 0; - } - - .menu_shown body { - left: 240px; - } - - .header .navigation { - height: 100%; - } - - .menu_shown .header .navigation .overlay { - position: fixed; - right: 0; - top: 0; - bottom: 0; - left: 240px; - } - - .header .navigation ul { - width: 240px; - position: fixed; - left: -240px; - background: rgba(0,0,0,.5); - transition: all .5s cubic-bezier(0.9,0,0.1,1); - } - - .menu_shown .header .navigation ul { - left: 0; - } - - .header .navigation ul li { - display: block; - text-align: left; - border-width: 1px 0 0 0; - height: 44px; - } - .header .navigation ul li a { - border-width: 0 4px 0 0; - padding: 5px 20px; - } - - .header .navigation ul li.separator { - background-color: rgba(255,255,255, .07); - height: 5px; - } - } - - .header .more_menu { - position: absolute; - right: 15px; - height: 100%; - border-left: 1px solid rgba(255,255,255,.07); - } - - @media all and (max-width: 480px) { - .header .more_menu { - display: none; - } - } - - .header .more_menu .button { - height: 100%; - line-height: 66px; - text-align: center; - padding: 0; - } - - .header .more_menu .wrapper { - width: 200px; - margin-left: -106px; - margin-top: 22px; - } - - @media all and (max-width: 480px) { - .header .more_menu .button { - line-height: 44px; - } - - .header .more_menu .wrapper { - margin-top: 0; - } - } - - .header .more_menu .red { color: red; } - .header .more_menu .orange { color: orange; } - - .badge { - position: absolute; - width: 20px; - height: 20px; - text-align: center; - line-height: 20px; - margin: 0; - background-color: #1b79b8; - top: 0; - right: 0; - } - - .header .notification_menu { - right: 60px; - display: block; - } - - @media all and (max-width: 480px) { - .header .notification_menu { - right: 0; - } - } - - .header .notification_menu .wrapper { - width: 300px; - margin-left: -255px; - text-align: left; - } - - .header .notification_menu ul { - min-height: 60px; - max-height: 300px; - overflow: auto; - } - .header .notification_menu ul:empty:after { - content: 'No notifications (yet)'; - text-align: center; - width: 100%; - position: absolute; - padding: 18px 0; - font-size: 15px; - font-style: italic; - opacity: .4; - } - - .header .notification_menu li > span { - padding: 5px; - display: block; - border-bottom: 1px solid rgba(0,0,0,0.2); - word-wrap: break-word; - } - .header .notification_menu li > span { color: #777; } - .header .notification_menu li:last-child > span { border: 0; } - .header .notification_menu li .added { - display: block; - font-size: .85em; - color: #aaa; - } - - .header .notification_menu li .more { - text-align: center; - } - - .message.update { - text-align: center; - position: fixed; - padding: 10px; - background: #ff6134; - font-size: 15px; - bottom: 0; - left: 0; - width: 100%; - z-index: 19; - } - - .message.update a { - padding: 0 5px; - } - -/*** Global Styles ***/ -.check { - display: inline-block; - vertical-align: top; - margin-top: 4px; - height: 16px; - width: 16px; - cursor: pointer; - background: url('../images/sprite.png') no-repeat -200px; - border-radius: 3px; -} - .check.highlighted { background-color: #424c59; } - .check.checked { background-position: -2px 0; } - .check.indeterminate { background-position: -1px -119px; } - .check input { - display: none !important; - } - -.select { - cursor: pointer; - display: inline-block; - color: #fff; -} - - .select .selection { - display: inline-block; - padding: 0 30px 0 20px; - background: #5b9bd1 url('../images/sprite.png') no-repeat 94% -53px; - } - - .select .selection .selectionDisplay { - display: inline-block; - padding-right: 15px; - border-right: 1px solid rgba(0,0,0,0.2); - - box-shadow: 1px 0 0 rgba(255,255,255,0.15); - } - - .select .menu { - clear: both; - overflow: hidden; - font-weight: bold; - } - - .select .list { - display: none; - background: #282d34; - position: absolute; - margin: 25px 0 0 0; - box-shadow: 0 20px 20px -10px rgba(0,0,0,0.4); - z-index: 3; - } - .select.active .list { - display: block; - } - .select .list ul { - display: block; - width: 100% !important; - } - .select .list li { - padding: 0 33px 0 20px; - margin: 0 !important; - display: block; - border-top: 1px solid rgba(255,255,255,0.1); - white-space: nowrap; - } - .select .list li.highlighted { - background: rgba(255,255,255,0.1); - border-color: transparent; - } - - .select input { display: none; } - -.inlay { - color: #fff; - border: 0; - background-color: #282d34; - box-shadow: inset 0 1px 8px rgba(0,0,0,0.25); -} - - .inlay.light { - background-color: #47515f; - outline: none; - box-shadow: none; - } - - .inlay:focus { - background-color: #3a4350; - outline: none; - } - -.onlay, .inlay .selected, .inlay:not(.reversed) > li:hover, .inlay > li.active, .inlay.reversed > li { - border-radius:3px; - border: 1px solid #252930; - box-shadow: inset 0 1px 0 rgba(255,255,255,0.20); - background: rgb(55,62,74); - background-image: linear-gradient( - 0, - rgb(55,62,74) 0%, - rgb(73,83,98) 100% - ); -} -.onlay:active, .inlay.reversed > li:active { - color: #fff; - border: 1px solid transparent; - background-color: #282d34; - box-shadow: inset 0 1px 8px rgba(0,0,0,0.25); -} - -.question { - display: block; - width: 600px; - padding: 20px; - position:fixed; - z-index: 101; - text-align: center; -} - - .question h3 { - font-size: 25px; - padding: 0; - margin: 0 0 20px; - } - - .question .hint { - font-size: 14px; - color: #ccc; - } - - .question .answer { - font-size: 17px; - display: inline-block; - padding: 10px; - margin: 5px 1%; - cursor: pointer; - width: auto; - } - .question .answer:hover { - background: #000; - } - - .question .answer.delete { - background-color: #a82f12; - } - .question .answer.cancel { - margin-top: 20px; - background-color: #4c5766; - } - -.more_menu { - display: inline-block; - vertical-align: middle; - overflow: visible; -} - - .more_menu > a { - display: block; - height: 44px; - width: 44px; - transition: all 0.3s ease-in-out; - border-bottom: 4px solid transparent; - border-radius: 0; - } - - .more_menu .button:hover { - border-color: #047792; - } - - .more_menu.show .button { - border-color: #04bce6; - } - - .more_menu .wrapper { - display: none; - top: 0; - right: 0; - margin: 11px 0 0 0; - position: absolute; - z-index: 90; - width: 185px; - box-shadow: 0 20px 20px -10px rgba(0,0,0,0.55); - color: #444; - background: #fff; - } - - .more_menu.show .wrapper { - display: block; - top: 44px; - } - - .more_menu ul { - padding: 0; - margin: 0; - list-style: none; - } - - .more_menu .wrapper li { - width: 100%; - height: auto; - } - - .more_menu .wrapper li a { - display: block; - border-bottom: 1px solid rgba(255,255,255,0.2); - box-shadow: none; - font-weight: normal; - font-size: 1.2em; - letter-spacing: 1px; - padding: 2px 10px; - color: #444; - } - .more_menu .wrapper li:first-child a { padding-top: 5px; } - .more_menu .wrapper li:last-child a { padding-bottom: 5px; } - - .more_menu .wrapper li .separator { - border-bottom: 1px solid rgba(0,0,0,.1); - display: block; - height: 1px; - margin: 5px 0; - } - - .more_menu .wrapper li:last-child a { - border: none; - } - .more_menu .wrapper li a:hover { - background: rgba(0,0,0,0.05); - } - -.messages { - position: fixed; - right: 0; - bottom: 0; - width: 320px; - z-index: 20; - overflow: hidden; - font-size: 14px; - font-weight: bold; -} - @media all and (max-width: 480px) { - .messages { - width: 100%; - } - } - - .messages .message { - overflow: hidden; - transition: all .6s cubic-bezier(0.9,0,0.1,1); - background: #5b9bd1; - width: 100%; - position: relative; - margin: 1px 0 0; - max-height: 0; - padding: 0 30px 0 20px; - font-size: 1.1em; - font-weight: normal; - transform: scale(0); - } - .messages .message.sticky { - background-color: #c84040; - } - .messages .message.show { - max-height: 100px; - padding: 15px 30px 15px 20px; - transform: scale(1); - } - .messages .message.hide { - max-height: 0; - padding: 0 20px; - margin: 0; - transform: scale(0); - } - .messages .close { - position: absolute; - padding: 10px 8px; - top: 0; - right: 0; - color: #FFF; - } - -/*** Login ***/ -.page.login { - display: block; -} - - .login h1 { - padding: 0 0 10px; - font-size: 60px; - font-family: Lobster; - font-weight: normal; - } - - .login form { - padding: 0; - height: 300px; - width: 400px; - position: fixed; - left: 50%; - top: 50%; - margin: -200px 0 0 -200px; - } - @media all and (max-width: 480px) { - - .login form { - padding: 0; - height: 300px; - width: 90%; - position: absolute; - left: 5%; - top: 10px; - margin: 0; - } - - } - - .page.login .ctrlHolder { - padding: 0; - margin: 0 0 20px; - } - .page.login .ctrlHolder:hover { - background: none; - } - - .page.login input[type=text], - .page.login input[type=password] { - width: 100% !important; - font-size: 25px; - padding: 14px !important; - } - - .page.login .remember_me { - font-size: 15px; - float: left; - width: 150px; - padding: 20px 0; - } - - .page.login .remember_me .check { - margin: 5px 5px 0 0; - } - - .page.login .button { - font-size: 25px; - padding: 20px; - float: right; - } - -/* Fonts */ -@font-face { - font-family: 'Elusive-Icons'; - src:url('../fonts/Elusive-Icons.eot'); - src:url('../fonts/Elusive-Icons.eot?#iefix') format('embedded-opentype'), - url('../fonts/Elusive-Icons.woff') format('woff'), - url('../fonts/Elusive-Icons.ttf') format('truetype'), - url('../fonts/Elusive-Icons.svg#Elusive-Icons') format('svg'); - font-weight: normal; - font-style: normal; -} - -@font-face { - font-family: 'OpenSans'; - src: url('../fonts/OpenSans-Regular-webfont.eot'); - src: url('../fonts/OpenSans-Regular-webfont.eot?#iefix') format('embedded-opentype'), - url('../fonts/OpenSans-Regular-webfont.woff') format('woff'), - url('../fonts/OpenSans-Regular-webfont.ttf') format('truetype'), - url('../fonts/OpenSans-Regular-webfont.svg#OpenSansRegular') format('svg'); - font-weight: normal; - font-style: normal; - -} - -@font-face { - font-family: 'OpenSans'; - src: url('../fonts/OpenSans-Italic-webfont.eot'); - src: url('../fonts/OpenSans-Italic-webfont.eot?#iefix') format('embedded-opentype'), - url('../fonts/OpenSans-Italic-webfont.woff') format('woff'), - url('../fonts/OpenSans-Italic-webfont.ttf') format('truetype'), - url('../fonts/OpenSans-Italic-webfont.svg#OpenSansItalic') format('svg'); - font-weight: normal; - font-style: italic; - -} - -@font-face { - font-family: 'OpenSans'; - src: url('../fonts/OpenSans-Bold-webfont.eot'); - src: url('../fonts/OpenSans-Bold-webfont.eot?#iefix') format('embedded-opentype'), - url('../fonts/OpenSans-Bold-webfont.woff') format('woff'), - url('../fonts/OpenSans-Bold-webfont.ttf') format('truetype'), - url('../fonts/OpenSans-Bold-webfont.svg#OpenSansBold') format('svg'); - font-weight: bold; - font-style: normal; - -} - -@font-face { - font-family: 'OpenSans'; - src: url('../fonts/OpenSans-BoldItalic-webfont.eot'); - src: url('../fonts/OpenSans-BoldItalic-webfont.eot?#iefix') format('embedded-opentype'), - url('../fonts/OpenSans-BoldItalic-webfont.woff') format('woff'), - url('../fonts/OpenSans-BoldItalic-webfont.ttf') format('truetype'), - url('../fonts/OpenSans-BoldItalic-webfont.svg#OpenSansBoldItalic') format('svg'); - font-weight: bold; - font-style: italic; -} - -@font-face { - font-family: 'Lobster'; - src: url('../fonts/Lobster-webfont.eot'); - src: url('../fonts/Lobster-webfont.eot?#iefix') format('embedded-opentype'), - url('../fonts/Lobster-webfont.woff') format('woff'), - url('../fonts/Lobster-webfont.ttf') format('truetype'), - url('../fonts/Lobster-webfont.svg#lobster_1.4regular') format('svg'); - font-weight: normal; - font-style: normal; -} diff --git a/couchpotato/static/style/main_old.scss b/couchpotato/static/style/main_old.scss new file mode 100644 index 0000000..c7850b2 --- /dev/null +++ b/couchpotato/static/style/main_old.scss @@ -0,0 +1,965 @@ +body, html { + color: #fff; + font-size: 12px; + line-height: 1.5; + font-family: OpenSans, "Helvetica Neue", Helvetica, Arial, Geneva, sans-serif; + height: 100%; + margin: 0; + padding: 0; + background: #4e5969; + -webkit-font-smoothing: subpixel-antialiased; + -moz-osx-font-smoothing: grayscale; +} + body { overflow-y: scroll; } + body.noscroll { overflow: hidden; } + + #clean { + background: transparent !important; + } + +* { + -webkit-box-sizing: border-box; + -moz-box-sizing: border-box; + box-sizing: border-box; +} + +pre { + white-space: pre-wrap; + word-wrap: break-word; +} + +input, textarea { + font-size: 1em; + font-family: "Helvetica Neue", Helvetica, Arial, Geneva, sans-serif; +} +input:focus, textarea:focus { + outline: none; +} + +::-moz-placeholder { + color: rgba(255, 255, 255, 0.5); + font-style: italic; +} +input:-moz-placeholder { + color: rgba(255, 255, 255, 0.5); + font-style: italic; +} +::-webkit-input-placeholder { + color: rgba(255, 255, 255, 0.5); + font-style: italic; +} +:-ms-input-placeholder { + color: rgba(255, 255, 255, 0.5) !important; + font-style: italic; +} + +.tiny_scroll { + overflow: hidden; +} + + .tiny_scroll:hover { + overflow-y: auto; + } + + .tiny_scroll::-webkit-scrollbar { + width: 5px; + } + + .tiny_scroll::-webkit-scrollbar-track { + -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.4); + -webkit-border-radius: 5px; + border-radius: 5px; + } + + .tiny_scroll::-webkit-scrollbar-thumb { + -webkit-border-radius: 5px; + border-radius: 5px; + background: rgba(255,255,255,0.3); + } + +a img { + border:none; +} + +a { + text-decoration:none; + color: #ebfcbc; + outline: 0; + cursor: pointer; + font-weight: bold; +} +a:hover { color: #f3f3f3; } + +.page { + display: none; + width: 100%; + max-width: 980px; + margin: 0 auto; + line-height: 1.5em; + padding: 0 15px 20px; +} + .page.active { display: block; } + +.content { + clear:both; + padding: 65px 0 10px; + background: #4e5969; +} + + @media all and (max-width: 480px) { + .content { + padding-top: 40px; + } + } + +h2 { + font-size: 2.5em; + padding: 0; + margin: 20px 0 0 0; +} + +form { + padding:0; + margin:0; +} + +body > .spinner, .mask{ + background: rgba(0,0,0, 0.9); + z-index: 100; + text-align: center; +} + body > .mask { + position: fixed; + top: 0; + left: 0; + height: 100%; + width: 100%; + padding: 200px; + } + + @media all and (max-width: 480px) { + body > .mask { + padding: 20px; + } + } + +.button { + background: #5082bc; + padding: 5px 10px 6px; + color: #fff; + text-decoration: none; + font-weight: bold; + line-height: 1; + border-radius: 2px; + cursor: pointer; + border: none; + -webkit-appearance: none; +} + .button.red { background-color: #ff0000; } + .button.green { background-color: #2aa300; } + .button.orange { background-color: #ffa200; } + .button.yellow { background-color: #ffe400; } + +/*** Icons ***/ +.icon { + display: inline-block; + background: center no-repeat; +} +.icon.delete { background-image: url('../images/icon.delete.png'); } +.icon.download { background-image: url('../images/icon.download.png'); } +.icon.edit { background-image: url('../images/icon.edit.png'); } +.icon.completed { background-image: url('../images/icon.check.png'); } +.icon.folder { background-image: url('../images/icon.folder.gif'); } +.icon.imdb { background-image: url('../images/icon.imdb.png'); } +.icon.refresh { background-image: url('../images/icon.refresh.png'); } +.icon.readd { background-image: url('../images/icon.readd.png'); } +.icon.rating { background-image: url('../images/icon.rating.png'); } +.icon.files { background-image: url('../images/icon.files.png'); } +.icon.info { background-image: url('../images/icon.info.png'); } +.icon.trailer { background-image: url('../images/icon.trailer.png'); } +.icon.spinner { background-image: url('../images/icon.spinner.gif'); } +.icon.attention { background-image: url('../images/icon.attention.png'); } + +.icon2 { + display: inline-block; + background: center no-repeat; + font-family: 'Elusive-Icons'; + speak: none; + font-style: normal; + font-weight: normal; + font-variant: normal; + text-transform: none; + line-height: 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + font-size: 15px; + color: #FFF; +} + +.icon2.add:before { content: "\e05a"; color: #c2fac5; } +.icon2.cog:before { content: "\e109"; } +.icon2.eye-open:before { content: "\e09d"; } +.icon2.search:before { content: "\e03e"; } +.icon2.return-key:before { content: "\e111"; } +.icon2.close:before { content: "\e04e"; } +.icon2.trailer:before { content: "\e0e9"; } +.icon2.download:before { content: "\e0c3"; } +.icon2.edit:before { content: "\e068"; } +.icon2.refresh:before { content: "\e04f"; font-weight: bold; } +.icon2.delete:before { content: "\e04e"; } +.icon2.directory:before { content: "\e097"; } +.icon2.completed:before { content: "\e070"; } +.icon2.info:before { content: "\e089"; } +.icon2.attention:before { content: "\e009"; } +.icon2.readd:before { + display: inline-block; + content: "\e04b"; + transform: scale(-1, 1); +} +.icon2.imdb:before { + content: "IMDb"; + color: #444; + padding: 2px; + border-radius: 3px; + background-color: #fbec98; + font: bold 8px Arial; + position: relative; + top: -3px; +} +.icon2.menu:before { + content: "\e076\00a0 \e076\00a0 \e076\00a0"; + line-height: 6px; + transform: scaleX(2); + width: 20px; + font-size: 10px; + display: inline-block; + vertical-align: middle; + word-wrap: break-word; + text-align:center; + margin-left: 5px; +} + @media screen and (-webkit-min-device-pixel-ratio:0) { + .icon2.menu:before { + margin-top: -7px; + } + } + +/*** Navigation ***/ +.header { + height: 66px; + position: fixed; + margin: 0; + width: 100%; + z-index: 5; + background: #5c697b; + box-shadow: 0 0 10px rgba(0,0,0,.1); + transition: all .4s ease-in-out; +} + + @media all and (max-width: 480px) { + .header { + height: 44px; + } + } + +.header > div { + width: 100%; + max-width: 980px; + margin: 0 auto; + position: relative; + height: 100%; + padding: 0 15px; +} + + .header .navigation { + display: inline-block; + vertical-align: middle; + position: absolute; + height: 100%; + left: 0; + bottom: 0; + } + + .header .foldout { + width: 44px; + height: 100%; + text-align: center; + border-right: 1px solid rgba(255,255,255,.07); + display: none; + vertical-align: top; + line-height: 42px; + color: #FFF; + } + + .header .logo { + display: inline-block; + font-size: 3em; + padding: 4px 30px 0 15px; + height: 100%; + border-right: 1px solid rgba(255,255,255,.07); + color: #FFF; + font-weight: normal; + vertical-align: top; + font-family: Lobster, sans-serif; + } + + @media all and (max-width: 480px) { + .header .foldout { + display: inline-block; + } + + .header .logo { + padding-top: 7px; + border: 0; + font-size: 1.7em; + } + } + + @media all and (min-width: 481px) and (max-width: 640px) { + + .header .logo { + display: none; + } + + } + + .header .navigation ul { + display: inline-block; + margin: 0; + padding: 0; + height: 100%; + } + + .header .navigation li { + color: #fff; + display: inline-block; + font-size: 1.75em; + margin: 0; + text-align: center; + height: 100%; + border: 1px solid rgba(255,255,255,.07); + border-width: 0 0 0 1px; + } + .header .navigation li:first-child { + border: none; + } + + .header .navigation li a { + display: block; + padding: 15px; + position: relative; + height: 100%; + border: 1px solid transparent; + border-width: 0 0 4px 0; + font-weight: normal; + } + + .header .navigation li:hover a { border-color: #047792; } + .header .navigation li.active a { border-color: #04bce6; } + + .header .navigation li.disabled { color: #e5e5e5; } + .header .navigation li a { color: #fff; } + + .header .navigation .backtotop { + opacity: 0; + display: block; + width: 80px; + left: 50%; + position: fixed; + bottom: 0; + text-align: center; + margin: -10px 0 0 -40px; + background: #4e5969; + padding: 5px 0; + color: rgba(255,255,255,.4); + font-weight: normal; + } + .header:hover .navigation .backtotop { color: #fff; } + + @media all and (max-width: 480px) { + + body { + position: absolute; + width: 100%; + transition: all .5s cubic-bezier(0.9,0,0.1,1); + left: 0; + } + + .menu_shown body { + left: 240px; + } + + .header .navigation { + height: 100%; + } + + .menu_shown .header .navigation .overlay { + position: fixed; + right: 0; + top: 0; + bottom: 0; + left: 240px; + } + + .header .navigation ul { + width: 240px; + position: fixed; + left: -240px; + background: rgba(0,0,0,.5); + transition: all .5s cubic-bezier(0.9,0,0.1,1); + } + + .menu_shown .header .navigation ul { + left: 0; + } + + .header .navigation ul li { + display: block; + text-align: left; + border-width: 1px 0 0 0; + height: 44px; + } + .header .navigation ul li a { + border-width: 0 4px 0 0; + padding: 5px 20px; + } + + .header .navigation ul li.separator { + background-color: rgba(255,255,255, .07); + height: 5px; + } + } + + .header .more_menu { + position: absolute; + right: 15px; + height: 100%; + border-left: 1px solid rgba(255,255,255,.07); + } + + @media all and (max-width: 480px) { + .header .more_menu { + display: none; + } + } + + .header .more_menu .button { + height: 100%; + line-height: 66px; + text-align: center; + padding: 0; + } + + .header .more_menu .wrapper { + width: 200px; + margin-left: -106px; + margin-top: 22px; + } + + @media all and (max-width: 480px) { + .header .more_menu .button { + line-height: 44px; + } + + .header .more_menu .wrapper { + margin-top: 0; + } + } + + .header .more_menu .red { color: red; } + .header .more_menu .orange { color: orange; } + + .badge { + position: absolute; + width: 20px; + height: 20px; + text-align: center; + line-height: 20px; + margin: 0; + background-color: #1b79b8; + top: 0; + right: 0; + } + + .header .notification_menu { + right: 60px; + display: block; + } + + @media all and (max-width: 480px) { + .header .notification_menu { + right: 0; + } + } + + .header .notification_menu .wrapper { + width: 300px; + margin-left: -255px; + text-align: left; + } + + .header .notification_menu ul { + min-height: 60px; + max-height: 300px; + overflow: auto; + } + .header .notification_menu ul:empty:after { + content: 'No notifications (yet)'; + text-align: center; + width: 100%; + position: absolute; + padding: 18px 0; + font-size: 15px; + font-style: italic; + opacity: .4; + } + + .header .notification_menu li > span { + padding: 5px; + display: block; + border-bottom: 1px solid rgba(0,0,0,0.2); + word-wrap: break-word; + } + .header .notification_menu li > span { color: #777; } + .header .notification_menu li:last-child > span { border: 0; } + .header .notification_menu li .added { + display: block; + font-size: .85em; + color: #aaa; + } + + .header .notification_menu li .more { + text-align: center; + } + + .message.update { + text-align: center; + position: fixed; + padding: 10px; + background: #ff6134; + font-size: 15px; + bottom: 0; + left: 0; + width: 100%; + z-index: 19; + } + + .message.update a { + padding: 0 5px; + } + +/*** Global Styles ***/ +.check { + display: inline-block; + vertical-align: top; + margin-top: 4px; + height: 16px; + width: 16px; + cursor: pointer; + background: url('../images/sprite.png') no-repeat -200px; + border-radius: 3px; +} + .check.highlighted { background-color: #424c59; } + .check.checked { background-position: -2px 0; } + .check.indeterminate { background-position: -1px -119px; } + .check input { + display: none !important; + } + +.select { + cursor: pointer; + display: inline-block; + color: #fff; +} + + .select .selection { + display: inline-block; + padding: 0 30px 0 20px; + background: #5b9bd1 url('../images/sprite.png') no-repeat 94% -53px; + } + + .select .selection .selectionDisplay { + display: inline-block; + padding-right: 15px; + border-right: 1px solid rgba(0,0,0,0.2); + + box-shadow: 1px 0 0 rgba(255,255,255,0.15); + } + + .select .menu { + clear: both; + overflow: hidden; + font-weight: bold; + } + + .select .list { + display: none; + background: #282d34; + position: absolute; + margin: 25px 0 0 0; + box-shadow: 0 20px 20px -10px rgba(0,0,0,0.4); + z-index: 3; + } + .select.active .list { + display: block; + } + .select .list ul { + display: block; + width: 100% !important; + } + .select .list li { + padding: 0 33px 0 20px; + margin: 0 !important; + display: block; + border-top: 1px solid rgba(255,255,255,0.1); + white-space: nowrap; + } + .select .list li.highlighted { + background: rgba(255,255,255,0.1); + border-color: transparent; + } + + .select input { display: none; } + +.inlay { + color: #fff; + border: 0; + background-color: #282d34; + box-shadow: inset 0 1px 8px rgba(0,0,0,0.25); +} + + .inlay.light { + background-color: #47515f; + outline: none; + box-shadow: none; + } + + .inlay:focus { + background-color: #3a4350; + outline: none; + } + +.onlay, .inlay .selected, .inlay:not(.reversed) > li:hover, .inlay > li.active, .inlay.reversed > li { + border-radius:3px; + border: 1px solid #252930; + box-shadow: inset 0 1px 0 rgba(255,255,255,0.20); + background: rgb(55,62,74); + background-image: linear-gradient( + 0, + rgb(55,62,74) 0%, + rgb(73,83,98) 100% + ); +} +.onlay:active, .inlay.reversed > li:active { + color: #fff; + border: 1px solid transparent; + background-color: #282d34; + box-shadow: inset 0 1px 8px rgba(0,0,0,0.25); +} + +.question { + display: block; + width: 600px; + padding: 20px; + position:fixed; + z-index: 101; + text-align: center; +} + + .question h3 { + font-size: 25px; + padding: 0; + margin: 0 0 20px; + } + + .question .hint { + font-size: 14px; + color: #ccc; + } + + .question .answer { + font-size: 17px; + display: inline-block; + padding: 10px; + margin: 5px 1%; + cursor: pointer; + width: auto; + } + .question .answer:hover { + background: #000; + } + + .question .answer.delete { + background-color: #a82f12; + } + .question .answer.cancel { + margin-top: 20px; + background-color: #4c5766; + } + +.more_menu { + display: inline-block; + vertical-align: middle; + overflow: visible; +} + + .more_menu > a { + display: block; + height: 44px; + width: 44px; + transition: all 0.3s ease-in-out; + border-bottom: 4px solid transparent; + border-radius: 0; + } + + .more_menu .button:hover { + border-color: #047792; + } + + .more_menu.show .button { + border-color: #04bce6; + } + + .more_menu .wrapper { + display: none; + top: 0; + right: 0; + margin: 11px 0 0 0; + position: absolute; + z-index: 90; + width: 185px; + box-shadow: 0 20px 20px -10px rgba(0,0,0,0.55); + color: #444; + background: #fff; + } + + .more_menu.show .wrapper { + display: block; + top: 44px; + } + + .more_menu ul { + padding: 0; + margin: 0; + list-style: none; + } + + .more_menu .wrapper li { + width: 100%; + height: auto; + } + + .more_menu .wrapper li a { + display: block; + border-bottom: 1px solid rgba(255,255,255,0.2); + box-shadow: none; + font-weight: normal; + font-size: 1.2em; + letter-spacing: 1px; + padding: 2px 10px; + color: #444; + } + .more_menu .wrapper li:first-child a { padding-top: 5px; } + .more_menu .wrapper li:last-child a { padding-bottom: 5px; } + + .more_menu .wrapper li .separator { + border-bottom: 1px solid rgba(0,0,0,.1); + display: block; + height: 1px; + margin: 5px 0; + } + + .more_menu .wrapper li:last-child a { + border: none; + } + .more_menu .wrapper li a:hover { + background: rgba(0,0,0,0.05); + } + +.messages { + position: fixed; + right: 0; + bottom: 0; + width: 320px; + z-index: 20; + overflow: hidden; + font-size: 14px; + font-weight: bold; +} + @media all and (max-width: 480px) { + .messages { + width: 100%; + } + } + + .messages .message { + overflow: hidden; + transition: all .6s cubic-bezier(0.9,0,0.1,1); + background: #5b9bd1; + width: 100%; + position: relative; + margin: 1px 0 0; + max-height: 0; + padding: 0 30px 0 20px; + font-size: 1.1em; + font-weight: normal; + transform: scale(0); + } + .messages .message.sticky { + background-color: #c84040; + } + .messages .message.show { + max-height: 100px; + padding: 15px 30px 15px 20px; + transform: scale(1); + } + .messages .message.hide { + max-height: 0; + padding: 0 20px; + margin: 0; + transform: scale(0); + } + .messages .close { + position: absolute; + padding: 10px 8px; + top: 0; + right: 0; + color: #FFF; + } + +/*** Login ***/ +.page.login { + display: block; +} + + .login h1 { + padding: 0 0 10px; + font-size: 60px; + font-family: Lobster; + font-weight: normal; + } + + .login form { + padding: 0; + height: 300px; + width: 400px; + position: fixed; + left: 50%; + top: 50%; + margin: -200px 0 0 -200px; + } + @media all and (max-width: 480px) { + + .login form { + padding: 0; + height: 300px; + width: 90%; + position: absolute; + left: 5%; + top: 10px; + margin: 0; + } + + } + + .page.login .ctrlHolder { + padding: 0; + margin: 0 0 20px; + } + .page.login .ctrlHolder:hover { + background: none; + } + + .page.login input[type=text], + .page.login input[type=password] { + width: 100% !important; + font-size: 25px; + padding: 14px !important; + } + + .page.login .remember_me { + font-size: 15px; + float: left; + width: 150px; + padding: 20px 0; + } + + .page.login .remember_me .check { + margin: 5px 5px 0 0; + } + + .page.login .button { + font-size: 25px; + padding: 20px; + float: right; + } + +/* Fonts */ +@font-face { + font-family: 'Elusive-Icons'; + src:url('../fonts/Elusive-Icons.eot'); + src:url('../fonts/Elusive-Icons.eot?#iefix') format('embedded-opentype'), + url('../fonts/Elusive-Icons.woff') format('woff'), + url('../fonts/Elusive-Icons.ttf') format('truetype'), + url('../fonts/Elusive-Icons.svg#Elusive-Icons') format('svg'); + font-weight: normal; + font-style: normal; +} + +@font-face { + font-family: 'OpenSans'; + src: url('../fonts/OpenSans-Regular-webfont.eot'); + src: url('../fonts/OpenSans-Regular-webfont.eot?#iefix') format('embedded-opentype'), + url('../fonts/OpenSans-Regular-webfont.woff') format('woff'), + url('../fonts/OpenSans-Regular-webfont.ttf') format('truetype'), + url('../fonts/OpenSans-Regular-webfont.svg#OpenSansRegular') format('svg'); + font-weight: normal; + font-style: normal; + +} + +@font-face { + font-family: 'OpenSans'; + src: url('../fonts/OpenSans-Italic-webfont.eot'); + src: url('../fonts/OpenSans-Italic-webfont.eot?#iefix') format('embedded-opentype'), + url('../fonts/OpenSans-Italic-webfont.woff') format('woff'), + url('../fonts/OpenSans-Italic-webfont.ttf') format('truetype'), + url('../fonts/OpenSans-Italic-webfont.svg#OpenSansItalic') format('svg'); + font-weight: normal; + font-style: italic; + +} + +@font-face { + font-family: 'OpenSans'; + src: url('../fonts/OpenSans-Bold-webfont.eot'); + src: url('../fonts/OpenSans-Bold-webfont.eot?#iefix') format('embedded-opentype'), + url('../fonts/OpenSans-Bold-webfont.woff') format('woff'), + url('../fonts/OpenSans-Bold-webfont.ttf') format('truetype'), + url('../fonts/OpenSans-Bold-webfont.svg#OpenSansBold') format('svg'); + font-weight: bold; + font-style: normal; + +} + +@font-face { + font-family: 'OpenSans'; + src: url('../fonts/OpenSans-BoldItalic-webfont.eot'); + src: url('../fonts/OpenSans-BoldItalic-webfont.eot?#iefix') format('embedded-opentype'), + url('../fonts/OpenSans-BoldItalic-webfont.woff') format('woff'), + url('../fonts/OpenSans-BoldItalic-webfont.ttf') format('truetype'), + url('../fonts/OpenSans-BoldItalic-webfont.svg#OpenSansBoldItalic') format('svg'); + font-weight: bold; + font-style: italic; +} + +@font-face { + font-family: 'Lobster'; + src: url('../fonts/Lobster-webfont.eot'); + src: url('../fonts/Lobster-webfont.eot?#iefix') format('embedded-opentype'), + url('../fonts/Lobster-webfont.woff') format('woff'), + url('../fonts/Lobster-webfont.ttf') format('truetype'), + url('../fonts/Lobster-webfont.svg#lobster_1.4regular') format('svg'); + font-weight: normal; + font-style: normal; +} diff --git a/couchpotato/static/style/settings.css b/couchpotato/static/style/settings.css deleted file mode 100644 index e84d381..0000000 --- a/couchpotato/static/style/settings.css +++ /dev/null @@ -1,820 +0,0 @@ -.page.settings { - min-width: 960px; -} - -.page.settings:after { - content: ""; - display: block; - clear: both; - visibility: hidden; - line-height: 0; - height: 0; -} - - .page.settings .tabs { - float: left; - width: 14.7%; - font-size: 17px; - text-align: right; - list-style: none; - padding: 35px 0; - margin: 0; - min-height: 470px; - background-image: linear-gradient( - 76deg, - rgba(0,0,0,0) 50%, - rgba(0,0,0,0.3) 100% - ); - } - .page.settings .tabs a { - display: block; - padding: 7px 15px; - font-weight: normal; - transition: all 0.3s ease-in-out; - color: rgba(255, 255, 255, 0.8); - text-shadow: none; - } - .page.settings .tabs a:hover, - .page.settings .tabs .active a { - background: rgb(78, 89, 105); - color: #fff; - } - .page.settings .tabs > li { - border-bottom: 1px solid rgb(78, 89, 105); - } - - .page.settings .tabs .subtabs { - list-style: none; - padding: 0; - margin: -5px 0 10px; - } - - .page.settings .tabs .subtabs a { - font-size: 13px; - padding: 0 15px; - font-weight: normal; - transition: all .3s ease-in-out; - color: rgba(255, 255, 255, 0.7); - } - - .page.settings .tabs .subtabs .active a { - color: #fff; - background: rgb(78, 89, 105); - } - - - .page.settings .containers { - width: 84%; - float: left; - padding: 40px 0 40px 2%; - min-height: 300px; - } - - .page .advanced { - display: none; - color: #edc07f; - } - .page.show_advanced .advanced { display: block; } - .page.show_advanced span.advanced, - .page.show_advanced input.advanced { display: inline; } - - .page.settings .tab_content { - display: none; - } - .page.settings .tab_content.active { display: block; } - - .page fieldset { - padding: 10px 0; - } - .page fieldset h2 { - font-weight: normal; - font-size: 25px; - padding: 0 9px 10px 30px; - margin: 0; - border-bottom: 1px solid #333; - box-shadow: 0 1px 0 rgba(255,255,255, 0.15); - } - - .page fieldset h2 .icon { - vertical-align: bottom; - position: absolute; - left: -25px; - top: 3px; - background: #FFF; - border-radius: 2.5px; - line-height: 0; - overflow: hidden; - } - - .page fieldset.enabler:hover h2 .icon { - display: none; - } - - .page fieldset h2 .hint { - font-size: 12px; - margin-left: 10px; - } - .page fieldset h2 .hint a { - margin: 0 !important; - padding: 0; - } - - .page fieldset.disabled .ctrlHolder { - display: none; - } - .page fieldset > .ctrlHolder:first-child { - display: block; - padding: 0; - position: relative; - margin: 0 0 -23px; - border: none; - width: 20px; - } - - .Scan_folder { padding: 0 !important; } - - .page .ctrlHolder { - line-height: 25px; - padding: 10px 10px 10px 30px; - font-size: 14px; - border: 0; - } - .page .ctrlHolder.save_success:not(:first-child) { - background: url('../images/icon.check.png') no-repeat 7px center; - } - .page .ctrlHolder:last-child { border: none; } - .page .ctrlHolder:hover { background-color: rgba(255,255,255,0.05); } - .page .ctrlHolder.focused { background-color: rgba(255,255,255,0.2); } - .page .ctrlHolder.focused:first-child, .page .ctrlHolder:first-child{ background-color: transparent; } - - .page .ctrlHolder .formHint { - width: 46%; - margin: -18px 0; - color: #fff !important; - display: inline-block; - vertical-align: middle; - padding: 0 0 0 2%; - line-height: 14px; - } - - .page .check { - margin-top: 6px; - } - - .page .check + .formHint { - float: none; - width: auto; - display: inline-block; - padding-left: 1% !important; - height: 24px; - vertical-align: middle; - line-height: 24px; - } - - .page .ctrlHolder label { - font-weight: bold; - width: 20%; - margin: 0; - padding: 6px 0 0; - } - - .page .xsmall { width: 25px !important; text-align: center; } - - .page .enabler { - display: block; - } - - .page .option_list { - margin-bottom: 20px; - } - - .page .option_list .check { - margin-top: 5px; - } - - .page .option_list .enabler { - padding: 0; - margin-left: 5px !important; - } - - .page .option_list .enabler:not(.disabled) { - margin: 0 0 0 30px; - } - - .page .option_list .enabler:not(.disabled) .ctrlHolder:first-child { - margin: 10px 0 -33px 0; - } - - .page .option_list h3 { - padding: 0; - margin: 10px 5px 0; - text-align: center; - font-weight: normal; - text-shadow: none; - text-transform: uppercase; - font-size: 12px; - background: rgba(255,255,255,0.03); - } - - .page .option_list .enabler.disabled { - display: inline-block; - padding: 4px 0 5px; - width: 24%; - vertical-align: top; - } - .page .option_list .enabler:not(.disabled) .icon { - display: none; - } - - .page .option_list .enabler.disabled h2 { - cursor: pointer; - border: none; - box-shadow: none; - padding: 0 10px 0 0; - font-size: 16px; - position: relative; - left: 25px; - margin-right: 25px; - } - - .page .option_list .enabler:not(.disabled) h2 { - font-size: 16px; - font-weight: bold; - border: none; - border-top: 1px solid rgba(255,255,255, 0.15); - box-shadow: 0 -1px 0 #333; - margin: 0; - padding: 10px 0 5px 25px; - } - .page .option_list .enabler:not(.disabled):first-child h2 { - border: none; - box-shadow: none; - } - - .page .option_list .enabler.disabled h2 .hint { - display: none; - } - .page .option_list .enabler h2 .hint { - font-weight: normal; - } - - .page input[type=text], .page input[type=password] { - padding: 5px 3px; - margin: 0; - width: 30%; - border-radius: 3px; - } - .page .input.xsmall { width: 5% } - .page .input.small { width: 10% } - .page .input.medium { width: 15% } - .page .input.large { width: 25% } - .page .input.xlarge { width: 30% } - - .page .advanced_toggle { - clear: both; - display: block; - text-align: right; - height: 20px; - margin: 0 0 -38px; - } - .page .advanced_toggle .check { - margin: 0; - } - .page .advanced_toggle span { padding: 0 5px; } - .page.show_advanced .advanced_toggle { - color: #edc07f; - } - - .page form .directory { - display: inline-block; - padding: 0 4% 0 4px; - font-size: 13px; - width: 30%; - overflow: hidden; - vertical-align: top; - position: relative; - } - .page form .directory:after { - content: "\e097"; - position: absolute; - right: 7px; - top: 2px; - font-family: 'Elusive-Icons'; - color: #f5e39c; - } - .page form .directory > input { - height: 25px; - display: inline-block; - float: right; - text-align: right; - white-space: nowrap; - cursor: pointer; - background: none; - border: 0; - color: #FFF; - width: 100%; - } - .page form .directory input:empty:before { - content: 'No folder selected'; - font-style: italic; - opacity: .3; - } - - .page .directory_list { - z-index: 2; - position: absolute; - width: 450px; - margin: 28px 0 20px 18.4%; - background: #5c697b; - box-shadow: 0 20px 40px -20px rgba(0,0,0,0.55); - } - - .page .directory_list .pointer { - border-right: 6px solid transparent; - border-left: 6px solid transparent; - border-bottom: 6px solid #5c697b; - display: block; - position: absolute; - width: 0; - margin: -6px 0 0 45%; - } - - .page .directory_list ul { - width: 92%; - height: 300px; - overflow: auto; - margin: 0 4%; - font-size: 16px; - } - - .page .directory_list li { - padding: 4px 30px 4px 10px; - cursor: pointer; - margin: 0 !important; - border-top: 1px solid rgba(255,255,255,0.1); - background: url('../images/right.arrow.png') no-repeat 98% center; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - - .page .directory_list li.blur { - opacity: .3; - } - - .page .directory_list li:last-child { - border-bottom: 1px solid rgba(255,255,255,0.1); - } - - .page .directory_list li:hover { - background-color: #515c68; - } - - .page .directory_list li.empty { - background: none; - height: 100px; - text-align: center; - font-style: italic; - border: none; - line-height: 100px; - cursor: default; - color: #BBB; - text-shadow: none; - font-size: 12px; - } - - .page .directory_list .actions { - clear: both; - padding: 4% 4% 2%; - min-height: 45px; - position: relative; - width: 100%; - text-align: right; - } - - .page .directory_list .actions label { - float: right; - width: auto; - padding: 0; - } - .page .directory_list .actions .inlay { - margin: 0 0 0 7px; - } - - .page .directory_list .actions .back { - font-weight: bold; - width: 160px; - display: inline-block; - padding: 0; - line-height: 120%; - vertical-align: top; - position: absolute; - text-align: left; - left: 4%; - } - - .page .directory_list .actions:last-child { - float: right; - padding: 4%; - } - - .page .directory_list .actions:last-child > span { - padding: 0 5px; - text-shadow: none; - } - - .page .directory_list .actions:last-child > .clear { - left: 4%; - position: absolute; - background-color: #af3128; - } - - .page .directory_list .actions:last-child > .cancel { - font-weight: bold; - color: #ddd; - } - - .page .directory_list .actions:last-child > .save { - background: #9dc156; - } - - - .page .multi_directory.is_empty .delete { - visibility: hidden; - } - - .page .multi_directory .delete { - display: none; - } - .page .multi_directory:hover .delete { - display: inline-block; - width: 22px; - height: 24px; - vertical-align: top; - background-position: center; - margin-left: 5px; - } - - - .page .tag_input select { - width: 20%; - display: inline-block; - } - - .page .tag_input .selection { - border-radius: 0 10px 10px 0; - height: 26px; - } - - .page .tag_input > input { - display: none; - } - - .page .tag_input > ul { - list-style: none; - border-radius: 3px; - cursor: text; - width: 30%; - margin: 0 !important; - min-height: 27px; - line-height: 0; - display: inline-block; - } - .page .tag_input:hover > ul { - border-radius: 3px 0 0 3px; - } - .page .tag_input:hover .formHint { display: none; } - - .page .tag_input > ul > li { - display: inline-block; - min-height: 20px; - min-width: 2px; - font-size: 12px; - padding: 0; - margin: 4px 0 0 !important; - border-width: 0; - background: 0; - line-height: 20px; - } - .page .tag_input > ul > li:first-child { min-width: 4px; } - .page .tag_input li.choice { - cursor: -moz-grab; - cursor: -webkit-grab; - cursor: grab; - padding: 0; - border-radius: 2px; - } - .page .tag_input > ul:hover > li.choice { - background: linear-gradient( - 180deg, - rgba(255,255,255,0.3) 0%, - rgba(255,255,255,0.1) 100% - ); - } - .page .tag_input > ul > li.choice:hover, - .page .tag_input > ul > li.choice.selected { - background: linear-gradient( - 180deg, - #5b9bd1 0%, - #406db8 100% - ); - } - - .page .tag_input .select { - display: none; - } - .page .tag_input:hover .select { display: inline-block; } - - .page .tag_input li input { - background: 0; - border: 0; - color: #fff; - outline-width: 0; - padding: 0; - min-width: 2px; - } - .page .tag_input li:first-child input { - padding-left: 2px; - min-width: 0; - } - - .page .tag_input li:not(.choice) span { - white-space: pre; - position: absolute; - top: -9999px; - } - - .page .tag_input .delete { - display: none; - height: 10px; - width: 16px; - position: absolute; - margin: -9px 0 0 -16px; - border-radius: 30px 30px 0 0; - cursor: pointer; - background: url('../images/icon.delete.png') no-repeat center 2px, linear-gradient( - 180deg, - #5b9bd1 0%, - #5b9bd1 100% - ); - background-size: 65%; - } - .page .tag_input .choice:hover .delete, - .page .tag_input .choice.selected .delete { display: inline-block; } - .page .tag_input .choice .delete:hover { - height: 14px; - margin-top: -13px; - } - - .page .combined_table .head { - margin: 0 0 0 60px; - } - .page .disabled .head { display: none; } - .page .combined_table .head abbr { - display: inline-block; - font-weight: bold; - border-bottom: 1px dotted #fff; - line-height: 140%; - cursor: help; - } - .page .combined_table .head abbr:first-child { - display: none; - } - .page .combined_table .head abbr.host { margin-right: 120px; } - .page .combined_table input.host { width: 140px; } - .page .section_newznab .combined_table .head abbr.host { margin-right: 180px; } - .page .section_newznab .combined_table input.host { width: 200px; } - - .page .combined_table .head abbr.name { margin-right: 57px; } - .page .combined_table input.name { width: 120px; } - .page .combined_table .head abbr.api_key { margin-right: 75px; } - - .page .combined_table .head abbr.pass_key { margin-right: 71px; } - .page .combined_table input.pass_key { width: 113px; } - - .page .section_newznab .combined_table .head abbr.api_key { margin-right: 170px; } - .page .section_newznab .combined_table input.api_key { width: 203px; } - - .page .combined_table .head abbr.extra_score { - margin-right: 15px; - display: none; - } - .page .combined_table input.extra_score { - width: 75px; - display: none; - } - .page.show_advanced .combined_table .head .extra_score, - .page.show_advanced .combined_table .extra_score { - display: inline-block; - } - - .page .combined_table .head abbr.custom_tag { - margin-right: 15px; - display: none; - } - .page .combined_table input.custom_tag { - width: 140px; - display: none; - } - .page.show_advanced .combined_table .head .custom_tag, - .page.show_advanced .combined_table .custom_tag { - display: inline-block; - } - - - .page .combined_table .seed_ratio, - .page .combined_table .seed_time { - width: 70px; - text-align: center; - margin-left: 10px; - } - .page .combined_table .seed_time { - margin-right: 10px; - } - - .page .combined_table .ctrlHolder { - padding-top: 2px; - padding-bottom: 3px; - } - .page .combined_table .ctrlHolder.hide { display: none; } - - .page .combined_table .ctrlHolder > * { - margin: 0 10px 0 0; - } - .page .combined_table .ctrlHolder > .check { - margin-top: 6px; - } - - .page .combined_table .ctrlHolder .delete { - display: none; - width: 22px; - height: 22px; - line-height: 22px; - text-align: center; - vertical-align: middle; - color: #fe3d3d; - } - .page .combined_table .ctrlHolder:hover .delete { - display: inline-block; - } - - .page .combined_table .ctrlHolder.is_empty .delete, .page.settings .combined_table .ctrlHolder.is_empty .check { - visibility: hidden; -} - - .page .tab_about .usenet { - padding: 20px 30px 0; - font-size: 1.5em; - line-height: 1.3em; - } - - .page .tab_about .usenet a { - padding: 0 5px; - } - - .page .tab_about .usenet ul { - float: left; - width: 50%; - margin: 10px 0; - padding: 0; - } - - .page .tab_about .usenet li { - background: url('../images/icon.check.png') no-repeat left center; - padding: 0 0 0 25px; - } - - .page .tab_about .donate { - float: left; - width: 42%; - text-align: center; - font-size: 17px; - padding: 0 0 0 4%; - margin: 20px 0 0; - border-left: 1px solid #333; - box-shadow: -1px 0 0 rgba(255,255,255, 0.15); - } - .page .tab_about .donate form { - padding: 10px 0 0; - } - - .page .tab_about .info { - padding: 20px 30px; - margin: 0; - overflow: hidden; - } - - .page .tab_about .info dt { - clear: both; - float: left; - width: 17%; - font-weight: bold; - } - - .page .tab_about .info dd { - float: right; - width: 80%; - padding: 0; - margin: 0; - font-style: italic; - } - .page .tab_about .info dd.version { cursor: pointer; } - - .page .tab_about .group_actions > div { - padding: 30px; - text-align: center; - } - - .page .tab_about .group_actions a { - margin: 0 10px; - font-size: 20px; - } - -.group_userscript { - background: 5px 75px no-repeat; - min-height: 460px; - font-size: 20px; - font-weight: normal; -} - - .settings .group_userscript { - background-position: center 120px; - background-size: auto 70%; - min-height: 360px; - } - - .group_userscript h2 .hint { - display: block; - margin: 0 !important; - } - - .group_userscript .userscript { - float: left; - margin: 14px 0 0 25px; - height: 36px; - line-height: 25px; - } - - .group_userscript .or { - float: left; - margin: 20px -10px 0 10px; - } - - .group_userscript .bookmarklet { - display: block; - float: left; - padding: 20px 15px 0 25px; - border-radius: 5px; - } - - .group_userscript .bookmarklet span { - margin-left: 10px; - display: inline-block; - } - -.active .group_imdb_automation:not(.disabled) { - background: url('../images/imdb_watchlist.png') no-repeat right 50px; - min-height: 210px; -} - - -.tooltip { - position: absolute; - right: 0; - width: 30px; - height: 30px; -} - - .tooltip > a { - opacity: .3; - font-size: 11px; - cursor: pointer; - } - - .tooltip:hover > a { - opacity: 1; - } - - .tooltip div { - background: #FFF; - color: #000; - padding: 10px; - width: 380px; - z-index: 200; - position: absolute; - transition: all .4s cubic-bezier(0.9,0,0.1,1); - margin-top: 40px; - right: 0; - opacity: 0; - visibility: hidden; - } - - .tooltip.shown div { - margin-top: 10px; - opacity: 1; - visibility: visible; - } - - .tooltip div a { - color: #5b9bd1; - } diff --git a/couchpotato/static/style/settings.scss b/couchpotato/static/style/settings.scss new file mode 100644 index 0000000..e84d381 --- /dev/null +++ b/couchpotato/static/style/settings.scss @@ -0,0 +1,820 @@ +.page.settings { + min-width: 960px; +} + +.page.settings:after { + content: ""; + display: block; + clear: both; + visibility: hidden; + line-height: 0; + height: 0; +} + + .page.settings .tabs { + float: left; + width: 14.7%; + font-size: 17px; + text-align: right; + list-style: none; + padding: 35px 0; + margin: 0; + min-height: 470px; + background-image: linear-gradient( + 76deg, + rgba(0,0,0,0) 50%, + rgba(0,0,0,0.3) 100% + ); + } + .page.settings .tabs a { + display: block; + padding: 7px 15px; + font-weight: normal; + transition: all 0.3s ease-in-out; + color: rgba(255, 255, 255, 0.8); + text-shadow: none; + } + .page.settings .tabs a:hover, + .page.settings .tabs .active a { + background: rgb(78, 89, 105); + color: #fff; + } + .page.settings .tabs > li { + border-bottom: 1px solid rgb(78, 89, 105); + } + + .page.settings .tabs .subtabs { + list-style: none; + padding: 0; + margin: -5px 0 10px; + } + + .page.settings .tabs .subtabs a { + font-size: 13px; + padding: 0 15px; + font-weight: normal; + transition: all .3s ease-in-out; + color: rgba(255, 255, 255, 0.7); + } + + .page.settings .tabs .subtabs .active a { + color: #fff; + background: rgb(78, 89, 105); + } + + + .page.settings .containers { + width: 84%; + float: left; + padding: 40px 0 40px 2%; + min-height: 300px; + } + + .page .advanced { + display: none; + color: #edc07f; + } + .page.show_advanced .advanced { display: block; } + .page.show_advanced span.advanced, + .page.show_advanced input.advanced { display: inline; } + + .page.settings .tab_content { + display: none; + } + .page.settings .tab_content.active { display: block; } + + .page fieldset { + padding: 10px 0; + } + .page fieldset h2 { + font-weight: normal; + font-size: 25px; + padding: 0 9px 10px 30px; + margin: 0; + border-bottom: 1px solid #333; + box-shadow: 0 1px 0 rgba(255,255,255, 0.15); + } + + .page fieldset h2 .icon { + vertical-align: bottom; + position: absolute; + left: -25px; + top: 3px; + background: #FFF; + border-radius: 2.5px; + line-height: 0; + overflow: hidden; + } + + .page fieldset.enabler:hover h2 .icon { + display: none; + } + + .page fieldset h2 .hint { + font-size: 12px; + margin-left: 10px; + } + .page fieldset h2 .hint a { + margin: 0 !important; + padding: 0; + } + + .page fieldset.disabled .ctrlHolder { + display: none; + } + .page fieldset > .ctrlHolder:first-child { + display: block; + padding: 0; + position: relative; + margin: 0 0 -23px; + border: none; + width: 20px; + } + + .Scan_folder { padding: 0 !important; } + + .page .ctrlHolder { + line-height: 25px; + padding: 10px 10px 10px 30px; + font-size: 14px; + border: 0; + } + .page .ctrlHolder.save_success:not(:first-child) { + background: url('../images/icon.check.png') no-repeat 7px center; + } + .page .ctrlHolder:last-child { border: none; } + .page .ctrlHolder:hover { background-color: rgba(255,255,255,0.05); } + .page .ctrlHolder.focused { background-color: rgba(255,255,255,0.2); } + .page .ctrlHolder.focused:first-child, .page .ctrlHolder:first-child{ background-color: transparent; } + + .page .ctrlHolder .formHint { + width: 46%; + margin: -18px 0; + color: #fff !important; + display: inline-block; + vertical-align: middle; + padding: 0 0 0 2%; + line-height: 14px; + } + + .page .check { + margin-top: 6px; + } + + .page .check + .formHint { + float: none; + width: auto; + display: inline-block; + padding-left: 1% !important; + height: 24px; + vertical-align: middle; + line-height: 24px; + } + + .page .ctrlHolder label { + font-weight: bold; + width: 20%; + margin: 0; + padding: 6px 0 0; + } + + .page .xsmall { width: 25px !important; text-align: center; } + + .page .enabler { + display: block; + } + + .page .option_list { + margin-bottom: 20px; + } + + .page .option_list .check { + margin-top: 5px; + } + + .page .option_list .enabler { + padding: 0; + margin-left: 5px !important; + } + + .page .option_list .enabler:not(.disabled) { + margin: 0 0 0 30px; + } + + .page .option_list .enabler:not(.disabled) .ctrlHolder:first-child { + margin: 10px 0 -33px 0; + } + + .page .option_list h3 { + padding: 0; + margin: 10px 5px 0; + text-align: center; + font-weight: normal; + text-shadow: none; + text-transform: uppercase; + font-size: 12px; + background: rgba(255,255,255,0.03); + } + + .page .option_list .enabler.disabled { + display: inline-block; + padding: 4px 0 5px; + width: 24%; + vertical-align: top; + } + .page .option_list .enabler:not(.disabled) .icon { + display: none; + } + + .page .option_list .enabler.disabled h2 { + cursor: pointer; + border: none; + box-shadow: none; + padding: 0 10px 0 0; + font-size: 16px; + position: relative; + left: 25px; + margin-right: 25px; + } + + .page .option_list .enabler:not(.disabled) h2 { + font-size: 16px; + font-weight: bold; + border: none; + border-top: 1px solid rgba(255,255,255, 0.15); + box-shadow: 0 -1px 0 #333; + margin: 0; + padding: 10px 0 5px 25px; + } + .page .option_list .enabler:not(.disabled):first-child h2 { + border: none; + box-shadow: none; + } + + .page .option_list .enabler.disabled h2 .hint { + display: none; + } + .page .option_list .enabler h2 .hint { + font-weight: normal; + } + + .page input[type=text], .page input[type=password] { + padding: 5px 3px; + margin: 0; + width: 30%; + border-radius: 3px; + } + .page .input.xsmall { width: 5% } + .page .input.small { width: 10% } + .page .input.medium { width: 15% } + .page .input.large { width: 25% } + .page .input.xlarge { width: 30% } + + .page .advanced_toggle { + clear: both; + display: block; + text-align: right; + height: 20px; + margin: 0 0 -38px; + } + .page .advanced_toggle .check { + margin: 0; + } + .page .advanced_toggle span { padding: 0 5px; } + .page.show_advanced .advanced_toggle { + color: #edc07f; + } + + .page form .directory { + display: inline-block; + padding: 0 4% 0 4px; + font-size: 13px; + width: 30%; + overflow: hidden; + vertical-align: top; + position: relative; + } + .page form .directory:after { + content: "\e097"; + position: absolute; + right: 7px; + top: 2px; + font-family: 'Elusive-Icons'; + color: #f5e39c; + } + .page form .directory > input { + height: 25px; + display: inline-block; + float: right; + text-align: right; + white-space: nowrap; + cursor: pointer; + background: none; + border: 0; + color: #FFF; + width: 100%; + } + .page form .directory input:empty:before { + content: 'No folder selected'; + font-style: italic; + opacity: .3; + } + + .page .directory_list { + z-index: 2; + position: absolute; + width: 450px; + margin: 28px 0 20px 18.4%; + background: #5c697b; + box-shadow: 0 20px 40px -20px rgba(0,0,0,0.55); + } + + .page .directory_list .pointer { + border-right: 6px solid transparent; + border-left: 6px solid transparent; + border-bottom: 6px solid #5c697b; + display: block; + position: absolute; + width: 0; + margin: -6px 0 0 45%; + } + + .page .directory_list ul { + width: 92%; + height: 300px; + overflow: auto; + margin: 0 4%; + font-size: 16px; + } + + .page .directory_list li { + padding: 4px 30px 4px 10px; + cursor: pointer; + margin: 0 !important; + border-top: 1px solid rgba(255,255,255,0.1); + background: url('../images/right.arrow.png') no-repeat 98% center; + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + } + + .page .directory_list li.blur { + opacity: .3; + } + + .page .directory_list li:last-child { + border-bottom: 1px solid rgba(255,255,255,0.1); + } + + .page .directory_list li:hover { + background-color: #515c68; + } + + .page .directory_list li.empty { + background: none; + height: 100px; + text-align: center; + font-style: italic; + border: none; + line-height: 100px; + cursor: default; + color: #BBB; + text-shadow: none; + font-size: 12px; + } + + .page .directory_list .actions { + clear: both; + padding: 4% 4% 2%; + min-height: 45px; + position: relative; + width: 100%; + text-align: right; + } + + .page .directory_list .actions label { + float: right; + width: auto; + padding: 0; + } + .page .directory_list .actions .inlay { + margin: 0 0 0 7px; + } + + .page .directory_list .actions .back { + font-weight: bold; + width: 160px; + display: inline-block; + padding: 0; + line-height: 120%; + vertical-align: top; + position: absolute; + text-align: left; + left: 4%; + } + + .page .directory_list .actions:last-child { + float: right; + padding: 4%; + } + + .page .directory_list .actions:last-child > span { + padding: 0 5px; + text-shadow: none; + } + + .page .directory_list .actions:last-child > .clear { + left: 4%; + position: absolute; + background-color: #af3128; + } + + .page .directory_list .actions:last-child > .cancel { + font-weight: bold; + color: #ddd; + } + + .page .directory_list .actions:last-child > .save { + background: #9dc156; + } + + + .page .multi_directory.is_empty .delete { + visibility: hidden; + } + + .page .multi_directory .delete { + display: none; + } + .page .multi_directory:hover .delete { + display: inline-block; + width: 22px; + height: 24px; + vertical-align: top; + background-position: center; + margin-left: 5px; + } + + + .page .tag_input select { + width: 20%; + display: inline-block; + } + + .page .tag_input .selection { + border-radius: 0 10px 10px 0; + height: 26px; + } + + .page .tag_input > input { + display: none; + } + + .page .tag_input > ul { + list-style: none; + border-radius: 3px; + cursor: text; + width: 30%; + margin: 0 !important; + min-height: 27px; + line-height: 0; + display: inline-block; + } + .page .tag_input:hover > ul { + border-radius: 3px 0 0 3px; + } + .page .tag_input:hover .formHint { display: none; } + + .page .tag_input > ul > li { + display: inline-block; + min-height: 20px; + min-width: 2px; + font-size: 12px; + padding: 0; + margin: 4px 0 0 !important; + border-width: 0; + background: 0; + line-height: 20px; + } + .page .tag_input > ul > li:first-child { min-width: 4px; } + .page .tag_input li.choice { + cursor: -moz-grab; + cursor: -webkit-grab; + cursor: grab; + padding: 0; + border-radius: 2px; + } + .page .tag_input > ul:hover > li.choice { + background: linear-gradient( + 180deg, + rgba(255,255,255,0.3) 0%, + rgba(255,255,255,0.1) 100% + ); + } + .page .tag_input > ul > li.choice:hover, + .page .tag_input > ul > li.choice.selected { + background: linear-gradient( + 180deg, + #5b9bd1 0%, + #406db8 100% + ); + } + + .page .tag_input .select { + display: none; + } + .page .tag_input:hover .select { display: inline-block; } + + .page .tag_input li input { + background: 0; + border: 0; + color: #fff; + outline-width: 0; + padding: 0; + min-width: 2px; + } + .page .tag_input li:first-child input { + padding-left: 2px; + min-width: 0; + } + + .page .tag_input li:not(.choice) span { + white-space: pre; + position: absolute; + top: -9999px; + } + + .page .tag_input .delete { + display: none; + height: 10px; + width: 16px; + position: absolute; + margin: -9px 0 0 -16px; + border-radius: 30px 30px 0 0; + cursor: pointer; + background: url('../images/icon.delete.png') no-repeat center 2px, linear-gradient( + 180deg, + #5b9bd1 0%, + #5b9bd1 100% + ); + background-size: 65%; + } + .page .tag_input .choice:hover .delete, + .page .tag_input .choice.selected .delete { display: inline-block; } + .page .tag_input .choice .delete:hover { + height: 14px; + margin-top: -13px; + } + + .page .combined_table .head { + margin: 0 0 0 60px; + } + .page .disabled .head { display: none; } + .page .combined_table .head abbr { + display: inline-block; + font-weight: bold; + border-bottom: 1px dotted #fff; + line-height: 140%; + cursor: help; + } + .page .combined_table .head abbr:first-child { + display: none; + } + .page .combined_table .head abbr.host { margin-right: 120px; } + .page .combined_table input.host { width: 140px; } + .page .section_newznab .combined_table .head abbr.host { margin-right: 180px; } + .page .section_newznab .combined_table input.host { width: 200px; } + + .page .combined_table .head abbr.name { margin-right: 57px; } + .page .combined_table input.name { width: 120px; } + .page .combined_table .head abbr.api_key { margin-right: 75px; } + + .page .combined_table .head abbr.pass_key { margin-right: 71px; } + .page .combined_table input.pass_key { width: 113px; } + + .page .section_newznab .combined_table .head abbr.api_key { margin-right: 170px; } + .page .section_newznab .combined_table input.api_key { width: 203px; } + + .page .combined_table .head abbr.extra_score { + margin-right: 15px; + display: none; + } + .page .combined_table input.extra_score { + width: 75px; + display: none; + } + .page.show_advanced .combined_table .head .extra_score, + .page.show_advanced .combined_table .extra_score { + display: inline-block; + } + + .page .combined_table .head abbr.custom_tag { + margin-right: 15px; + display: none; + } + .page .combined_table input.custom_tag { + width: 140px; + display: none; + } + .page.show_advanced .combined_table .head .custom_tag, + .page.show_advanced .combined_table .custom_tag { + display: inline-block; + } + + + .page .combined_table .seed_ratio, + .page .combined_table .seed_time { + width: 70px; + text-align: center; + margin-left: 10px; + } + .page .combined_table .seed_time { + margin-right: 10px; + } + + .page .combined_table .ctrlHolder { + padding-top: 2px; + padding-bottom: 3px; + } + .page .combined_table .ctrlHolder.hide { display: none; } + + .page .combined_table .ctrlHolder > * { + margin: 0 10px 0 0; + } + .page .combined_table .ctrlHolder > .check { + margin-top: 6px; + } + + .page .combined_table .ctrlHolder .delete { + display: none; + width: 22px; + height: 22px; + line-height: 22px; + text-align: center; + vertical-align: middle; + color: #fe3d3d; + } + .page .combined_table .ctrlHolder:hover .delete { + display: inline-block; + } + + .page .combined_table .ctrlHolder.is_empty .delete, .page.settings .combined_table .ctrlHolder.is_empty .check { + visibility: hidden; +} + + .page .tab_about .usenet { + padding: 20px 30px 0; + font-size: 1.5em; + line-height: 1.3em; + } + + .page .tab_about .usenet a { + padding: 0 5px; + } + + .page .tab_about .usenet ul { + float: left; + width: 50%; + margin: 10px 0; + padding: 0; + } + + .page .tab_about .usenet li { + background: url('../images/icon.check.png') no-repeat left center; + padding: 0 0 0 25px; + } + + .page .tab_about .donate { + float: left; + width: 42%; + text-align: center; + font-size: 17px; + padding: 0 0 0 4%; + margin: 20px 0 0; + border-left: 1px solid #333; + box-shadow: -1px 0 0 rgba(255,255,255, 0.15); + } + .page .tab_about .donate form { + padding: 10px 0 0; + } + + .page .tab_about .info { + padding: 20px 30px; + margin: 0; + overflow: hidden; + } + + .page .tab_about .info dt { + clear: both; + float: left; + width: 17%; + font-weight: bold; + } + + .page .tab_about .info dd { + float: right; + width: 80%; + padding: 0; + margin: 0; + font-style: italic; + } + .page .tab_about .info dd.version { cursor: pointer; } + + .page .tab_about .group_actions > div { + padding: 30px; + text-align: center; + } + + .page .tab_about .group_actions a { + margin: 0 10px; + font-size: 20px; + } + +.group_userscript { + background: 5px 75px no-repeat; + min-height: 460px; + font-size: 20px; + font-weight: normal; +} + + .settings .group_userscript { + background-position: center 120px; + background-size: auto 70%; + min-height: 360px; + } + + .group_userscript h2 .hint { + display: block; + margin: 0 !important; + } + + .group_userscript .userscript { + float: left; + margin: 14px 0 0 25px; + height: 36px; + line-height: 25px; + } + + .group_userscript .or { + float: left; + margin: 20px -10px 0 10px; + } + + .group_userscript .bookmarklet { + display: block; + float: left; + padding: 20px 15px 0 25px; + border-radius: 5px; + } + + .group_userscript .bookmarklet span { + margin-left: 10px; + display: inline-block; + } + +.active .group_imdb_automation:not(.disabled) { + background: url('../images/imdb_watchlist.png') no-repeat right 50px; + min-height: 210px; +} + + +.tooltip { + position: absolute; + right: 0; + width: 30px; + height: 30px; +} + + .tooltip > a { + opacity: .3; + font-size: 11px; + cursor: pointer; + } + + .tooltip:hover > a { + opacity: 1; + } + + .tooltip div { + background: #FFF; + color: #000; + padding: 10px; + width: 380px; + z-index: 200; + position: absolute; + transition: all .4s cubic-bezier(0.9,0,0.1,1); + margin-top: 40px; + right: 0; + opacity: 0; + visibility: hidden; + } + + .tooltip.shown div { + margin-top: 10px; + opacity: 1; + visibility: visible; + } + + .tooltip div a { + color: #5b9bd1; + } diff --git a/couchpotato/static/style/uniform.css b/couchpotato/static/style/uniform.css deleted file mode 100644 index 91bc83f..0000000 --- a/couchpotato/static/style/uniform.css +++ /dev/null @@ -1,154 +0,0 @@ -/* ------------------------------------------------------------------------------ - - Copyright (c) 2010, Dragan Babic - - Permission is hereby granted, free of charge, to any person - obtaining a copy of this software and associated documentation - files (the "Software"), to deal in the Software without - restriction, including without limitation the rights to use, - copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following - conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - OTHER DEALINGS IN THE SOFTWARE. - - ------------------------------------------------------------------------------ */ -/* ############################# GENERALS ################################### */ -/* ------------------------------------------------------------------------------ */ - -.uniForm{ margin: 0; padding: 0; position: relative; z-index: 1; } /* reset stuff */ - - /* Some generals and more resets */ - .uniForm fieldset{ border: none; margin: 0; padding: 0; } - .uniForm fieldset legend{ margin: 0; padding: 0; } - - /* This are the main units that contain form elements */ - .uniForm .ctrlHolder, - .uniForm .buttonHolder{ margin: 0; padding: 0; clear: both; } - - /* Clear all floats */ - .uniForm:after, - .uniForm .buttonHolder:after, - .uniForm .ctrlHolder:after, - .uniForm .ctrlHolder .multiField:after, - .uniForm .inlineLabel:after{ content: "."; display: block; height: 0; line-height: 0; font-size: 0; clear: both; min-height: 0; visibility: hidden; } - - .uniForm label, - .uniForm button{ cursor: pointer; } - -/* ------------------------------------------------------------------------------ */ -/* ########################## DEFAULT LAYOUT ################################ */ -/* ------------------------------------------------------------------------------ */ -/* Styles for form controls where labels are above the input elements */ -/* ------------------------------------------------------------------------------ */ - - .uniForm label, - .uniForm .label{ display: block; float: none; margin: 0 0 .5em 0; padding: 0; line-height: 100%; width: auto; } - - /* Float the input elements */ - .uniForm .textInput, - .uniForm .fileUpload, - .uniForm .selectInput, - .uniForm select, - .uniForm textarea{ float: left; width: 53%; margin: 0; } - - /* Postition the hints */ - .uniForm .formHint{ float: right; width: 43%; margin: 0; clear: none; } - - /* Position the elements inside combo boxes (multiple inputs/selects/checkboxes/radio buttons per unit) */ - .uniForm ul{ float: left; width: 53%; margin: 0; padding: 0; } - .uniForm ul li{ margin: 0 0 .5em 0; list-style: none; } - .uniForm ul li label{ margin: 0; float: none; display: block; overflow: visible; } - /* Alternate layout */ - .uniForm ul.alternate li{ float: left; width: 30%; margin-right: 3%; } - .uniForm ul.alternate li label{ float: none; display: block; width: 98%; } - .uniForm ul .textInput, - .uniForm ul .selectInput, - .uniForm ul select, - .uniForm ul.alternate .textInput, - .uniForm ul.alternate .selectInput, - .uniForm ul.alternate select{ width: 98%; margin-top: .5em; display: block; float: none; } - - /* Required fields asterisk styling */ - .uniForm label em, - .uniForm .label em{ float: left; width: 1em; margin: 0 0 0 -1em; } - -/* ------------------------------------------------------------------------------ */ -/* ######################### ALTERNATE LAYOUT ############################### */ -/* ------------------------------------------------------------------------------ */ -/* Styles for form controls where labels are in line with the input elements */ -/* Set the class of the parent (preferably to a fieldset) to .inlineLabels */ -/* ------------------------------------------------------------------------------ */ - - .uniForm .inlineLabels label, - .uniForm .inlineLabels .label{ float: left; margin: .3em 2% 0 0; padding: 0; line-height: 1; position: relative; width: 32%; } - - /* Float the input elements */ - .uniForm .inlineLabels .textInput, - .uniForm .inlineLabels .fileUpload, - .uniForm .inlineLabels .selectInput, - .uniForm .inlineLabels select, - .uniForm .inlineLabels textarea{ float: left; width: 64%; } - - /* Postition the hints */ - .uniForm .inlineLabels .formHint{ clear: both; float: none; width: auto; margin-left: 34%; position: static; } - - /* Position the elements inside combo boxes (multiple inputs/selects/checkboxes/radio buttons per unit) */ - .uniForm .inlineLabels ul{ float: left; width: 66%; } - .uniForm .inlineLabels ul li{ margin: .5em 0; } - .uniForm .inlineLabels ul li label{ float: none; display: block; width: 100%; } - /* Alternate layout */ - .uniForm .inlineLabels ul.alternate li{ margin-right: 3%; margin-top: .25em; } - .uniForm .inlineLabels ul li label .textInput, - .uniForm .inlineLabels ul li label textarea, - .uniForm .inlineLabels ul li label select{ float: none; display: block; width: 98%; } - - /* Required fields asterisk styling */ - .uniForm .inlineLabels label em, - .uniForm .inlineLabels .label em{ display: block; float: none; margin: 0; position: absolute; right: 0; } - -/* ----------------------------------------------------------------------------- */ -/* ########################### Additional Stuff ################################ */ -/* ----------------------------------------------------------------------------- */ - - /* Generals */ - .uniForm legend{ color: inherit; } - - .uniForm .secondaryAction{ float: left; } - - /* .inlineLabel is used for inputs within labels - checkboxes and radio buttons */ - .uniForm .inlineLabel input, - .uniForm .inlineLabels .inlineLabel input, - .uniForm .blockLabels .inlineLabel input, - /* class .inlineLabel is depreciated */ - .uniForm label input{ float: none; display: inline; margin: 0; padding: 0; border: none; } - - .uniForm .buttonHolder .inlineLabel, - .uniForm .buttonHolder label{ float: left; margin: .5em 0 0 0; width: auto; max-width: 60%; text-align: left; } - - /* When you don't want to use a label */ - .uniForm .inlineLabels .noLabel ul{ margin-left: 34%; /* Match to width of label + gap to field */ } - - /* Classes for control of the widths of the fields */ - .uniForm .small { width: 30% !important; } - .uniForm .medium{ width: 45% !important; } - .uniForm .large { } /* Large is default and should match the value you set for .textInput, textarea or select */ - .uniForm .auto { width: auto !important; } - .uniForm .small, - .uniForm .medium, - .uniForm .auto{ margin-right: 4px; } - -/* Columns */ -.uniForm .col{ float: left; } -.uniForm .col{ width: 50%; } \ No newline at end of file diff --git a/couchpotato/static/style/uniform.generic.css b/couchpotato/static/style/uniform.generic.css deleted file mode 100644 index 8ac4136..0000000 --- a/couchpotato/static/style/uniform.generic.css +++ /dev/null @@ -1,139 +0,0 @@ -/* ------------------------------------------------------------------------------ - - UNI-FORM DEFAULT by DRAGAN BABIC (v2) | Wed, 31 Mar 10 - - ------------------------------------------------------------------------------ - - Copyright (c) 2010, Dragan Babic - - Permission is hereby granted, free of charge, to any person - obtaining a copy of this software and associated documentation - files (the "Software"), to deal in the Software without - restriction, including without limitation the rights to use, - copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following - conditions: - - The above copyright notice and this permission notice shall be - included in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING - FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - OTHER DEALINGS IN THE SOFTWARE. - - ------------------------------------------------------------------------------ */ - -.uniForm{} - - .uniForm legend{ font-weight: bold; font-size: 100%; margin: 0; padding: 1.5em 0; } - - .uniForm .ctrlHolder{ padding: 1em; border-bottom: 1px solid #efefef; } - .uniForm .ctrlHolder.focused{ background: #fffcdf; } - - .uniForm .inlineLabels .noLabel{} - - .uniForm .buttonHolder{ background: #efefef; text-align: right; margin: 1.5em 0 0 0; padding: 1.5em; - /* CSS3 */ - border-radius: 4px; - } - .uniForm .buttonHolder .primaryAction{ padding: 10px 22px; line-height: 1; background: #254a86; border: 1px solid #163362; font-size: 12px; font-weight: bold; color: #fff; - /* CSS3 */ - border-radius: 4px; - box-shadow: 1px 1px 0 #fff; - text-shadow: -1px -1px 0 rgba(0,0,0,.25); - } - .uniForm .buttonHolder .primaryAction:active{ position: relative; top: 1px; } - .uniForm .secondaryAction { text-align: left; } - .uniForm button.secondaryAction { background: transparent; border: none; color: #777; margin: 1.25em 0 0 0; padding: 0; } - - .uniForm .inlineLabels label em, - .uniForm .inlineLabels .label em{ font-style: normal; font-weight: bold; } - .uniForm label small{ font-size: .75em; color: #777; } - - .uniForm .textInput, - .uniForm textarea { padding: 4px 2px; border: 1px solid #aaa; background: #fff; } - .uniForm textarea { height: 12em; } - .uniForm select {} - .uniForm .fileUpload {} - - .uniForm ul{} - .uniForm li{} - .uniForm ul li label{ font-size: .85em; } - - .uniForm .small {} - .uniForm .medium{} - .uniForm .large {} /* Large is default and should match the value you set for .textInput, textarea or select */ - .uniForm .auto {} - .uniForm .small, - .uniForm .medium, - .uniForm .auto{} - - /* Get rid of the 'glow' effect in WebKit, optional */ - .uniForm .ctrlHolder .textInput:focus, - .uniForm .ctrlHolder textarea:focus{ outline: none; } - - .uniForm .formHint { font-size: .85em; color: #777; } - .uniForm .inlineLabels .formHint { padding-top: .5em; } - .uniForm .ctrlHolder.focused .formHint{ color: #333; } - -/* ----------------------------------------------------------------------------- */ -/* ############################### Messages #################################### */ -/* ----------------------------------------------------------------------------- */ - - /* Error message at the top of the form */ - .uniForm #errorMsg{ background: #ffdfdf; border: 1px solid #f3afb5; margin: 0 0 1.5em 0; padding: 0 1.5em; - /* CSS3 */ - border-radius: 4px; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - - } - .uniForm #errorMsg h3{} /* Feel free to use a heading level suitable to your page structure */ - .uniForm #errorMsg ol{ margin: 0 0 1.5em 0; padding: 0; } - .uniForm #errorMsg ol li{ margin: 0 0 3px 1.5em; padding: 7px; background: #f6bec1; position: relative; font-size: .85em; - /* CSS3 */ - border-radius: 4px; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - - } - - .uniForm .ctrlHolder.error, - .uniForm .ctrlHolder.focused.error{ background: #ffdfdf; border: 1px solid #f3afb5; - /* CSS3 */ - border-radius: 4px; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - - } - .uniForm .ctrlHolder.error input.error, - .uniForm .ctrlHolder.error select.error, - .uniForm .ctrlHolder.error textarea.error{ color: #af4c4c; margin: 0 0 6px 0; padding: 4px; } - - /* Success messages at the top of the form */ - .uniForm #okMsg{ background: #c8ffbf; border: 1px solid #a2ef95; margin: 0 0 1.5em 0; padding: 0 1.5em; text-align: center; - /* CSS3 */ - border-radius: 4px; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - - } - .uniForm #OKMsg p{ margin: 0; } - -/* ----------------------------------------------------------------------------- */ -/* ############################### Columns ##################################### */ -/* ----------------------------------------------------------------------------- */ - - .uniForm .col{} - .uniForm .col.first{} - .uniForm .col.last{} - .uniForm .col{ margin-bottom: 1.5em; } - /* Use .first and .last classes to control the layout/spacing of your columns */ - .uniForm .col.first{ width: 49%; float: left; clear: none; } - .uniForm .col.last { width: 49%; float: right; clear: none; margin-right: 0; } \ No newline at end of file diff --git a/couchpotato/static/style/uniform.generic.scss b/couchpotato/static/style/uniform.generic.scss new file mode 100644 index 0000000..8ac4136 --- /dev/null +++ b/couchpotato/static/style/uniform.generic.scss @@ -0,0 +1,139 @@ +/* ------------------------------------------------------------------------------ + + UNI-FORM DEFAULT by DRAGAN BABIC (v2) | Wed, 31 Mar 10 + + ------------------------------------------------------------------------------ + + Copyright (c) 2010, Dragan Babic + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + ------------------------------------------------------------------------------ */ + +.uniForm{} + + .uniForm legend{ font-weight: bold; font-size: 100%; margin: 0; padding: 1.5em 0; } + + .uniForm .ctrlHolder{ padding: 1em; border-bottom: 1px solid #efefef; } + .uniForm .ctrlHolder.focused{ background: #fffcdf; } + + .uniForm .inlineLabels .noLabel{} + + .uniForm .buttonHolder{ background: #efefef; text-align: right; margin: 1.5em 0 0 0; padding: 1.5em; + /* CSS3 */ + border-radius: 4px; + } + .uniForm .buttonHolder .primaryAction{ padding: 10px 22px; line-height: 1; background: #254a86; border: 1px solid #163362; font-size: 12px; font-weight: bold; color: #fff; + /* CSS3 */ + border-radius: 4px; + box-shadow: 1px 1px 0 #fff; + text-shadow: -1px -1px 0 rgba(0,0,0,.25); + } + .uniForm .buttonHolder .primaryAction:active{ position: relative; top: 1px; } + .uniForm .secondaryAction { text-align: left; } + .uniForm button.secondaryAction { background: transparent; border: none; color: #777; margin: 1.25em 0 0 0; padding: 0; } + + .uniForm .inlineLabels label em, + .uniForm .inlineLabels .label em{ font-style: normal; font-weight: bold; } + .uniForm label small{ font-size: .75em; color: #777; } + + .uniForm .textInput, + .uniForm textarea { padding: 4px 2px; border: 1px solid #aaa; background: #fff; } + .uniForm textarea { height: 12em; } + .uniForm select {} + .uniForm .fileUpload {} + + .uniForm ul{} + .uniForm li{} + .uniForm ul li label{ font-size: .85em; } + + .uniForm .small {} + .uniForm .medium{} + .uniForm .large {} /* Large is default and should match the value you set for .textInput, textarea or select */ + .uniForm .auto {} + .uniForm .small, + .uniForm .medium, + .uniForm .auto{} + + /* Get rid of the 'glow' effect in WebKit, optional */ + .uniForm .ctrlHolder .textInput:focus, + .uniForm .ctrlHolder textarea:focus{ outline: none; } + + .uniForm .formHint { font-size: .85em; color: #777; } + .uniForm .inlineLabels .formHint { padding-top: .5em; } + .uniForm .ctrlHolder.focused .formHint{ color: #333; } + +/* ----------------------------------------------------------------------------- */ +/* ############################### Messages #################################### */ +/* ----------------------------------------------------------------------------- */ + + /* Error message at the top of the form */ + .uniForm #errorMsg{ background: #ffdfdf; border: 1px solid #f3afb5; margin: 0 0 1.5em 0; padding: 0 1.5em; + /* CSS3 */ + border-radius: 4px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + + } + .uniForm #errorMsg h3{} /* Feel free to use a heading level suitable to your page structure */ + .uniForm #errorMsg ol{ margin: 0 0 1.5em 0; padding: 0; } + .uniForm #errorMsg ol li{ margin: 0 0 3px 1.5em; padding: 7px; background: #f6bec1; position: relative; font-size: .85em; + /* CSS3 */ + border-radius: 4px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + + } + + .uniForm .ctrlHolder.error, + .uniForm .ctrlHolder.focused.error{ background: #ffdfdf; border: 1px solid #f3afb5; + /* CSS3 */ + border-radius: 4px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + + } + .uniForm .ctrlHolder.error input.error, + .uniForm .ctrlHolder.error select.error, + .uniForm .ctrlHolder.error textarea.error{ color: #af4c4c; margin: 0 0 6px 0; padding: 4px; } + + /* Success messages at the top of the form */ + .uniForm #okMsg{ background: #c8ffbf; border: 1px solid #a2ef95; margin: 0 0 1.5em 0; padding: 0 1.5em; text-align: center; + /* CSS3 */ + border-radius: 4px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + + } + .uniForm #OKMsg p{ margin: 0; } + +/* ----------------------------------------------------------------------------- */ +/* ############################### Columns ##################################### */ +/* ----------------------------------------------------------------------------- */ + + .uniForm .col{} + .uniForm .col.first{} + .uniForm .col.last{} + .uniForm .col{ margin-bottom: 1.5em; } + /* Use .first and .last classes to control the layout/spacing of your columns */ + .uniForm .col.first{ width: 49%; float: left; clear: none; } + .uniForm .col.last { width: 49%; float: right; clear: none; margin-right: 0; } \ No newline at end of file diff --git a/couchpotato/static/style/uniform.scss b/couchpotato/static/style/uniform.scss new file mode 100644 index 0000000..91bc83f --- /dev/null +++ b/couchpotato/static/style/uniform.scss @@ -0,0 +1,154 @@ +/* ------------------------------------------------------------------------------ + + Copyright (c) 2010, Dragan Babic + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + + ------------------------------------------------------------------------------ */ +/* ############################# GENERALS ################################### */ +/* ------------------------------------------------------------------------------ */ + +.uniForm{ margin: 0; padding: 0; position: relative; z-index: 1; } /* reset stuff */ + + /* Some generals and more resets */ + .uniForm fieldset{ border: none; margin: 0; padding: 0; } + .uniForm fieldset legend{ margin: 0; padding: 0; } + + /* This are the main units that contain form elements */ + .uniForm .ctrlHolder, + .uniForm .buttonHolder{ margin: 0; padding: 0; clear: both; } + + /* Clear all floats */ + .uniForm:after, + .uniForm .buttonHolder:after, + .uniForm .ctrlHolder:after, + .uniForm .ctrlHolder .multiField:after, + .uniForm .inlineLabel:after{ content: "."; display: block; height: 0; line-height: 0; font-size: 0; clear: both; min-height: 0; visibility: hidden; } + + .uniForm label, + .uniForm button{ cursor: pointer; } + +/* ------------------------------------------------------------------------------ */ +/* ########################## DEFAULT LAYOUT ################################ */ +/* ------------------------------------------------------------------------------ */ +/* Styles for form controls where labels are above the input elements */ +/* ------------------------------------------------------------------------------ */ + + .uniForm label, + .uniForm .label{ display: block; float: none; margin: 0 0 .5em 0; padding: 0; line-height: 100%; width: auto; } + + /* Float the input elements */ + .uniForm .textInput, + .uniForm .fileUpload, + .uniForm .selectInput, + .uniForm select, + .uniForm textarea{ float: left; width: 53%; margin: 0; } + + /* Postition the hints */ + .uniForm .formHint{ float: right; width: 43%; margin: 0; clear: none; } + + /* Position the elements inside combo boxes (multiple inputs/selects/checkboxes/radio buttons per unit) */ + .uniForm ul{ float: left; width: 53%; margin: 0; padding: 0; } + .uniForm ul li{ margin: 0 0 .5em 0; list-style: none; } + .uniForm ul li label{ margin: 0; float: none; display: block; overflow: visible; } + /* Alternate layout */ + .uniForm ul.alternate li{ float: left; width: 30%; margin-right: 3%; } + .uniForm ul.alternate li label{ float: none; display: block; width: 98%; } + .uniForm ul .textInput, + .uniForm ul .selectInput, + .uniForm ul select, + .uniForm ul.alternate .textInput, + .uniForm ul.alternate .selectInput, + .uniForm ul.alternate select{ width: 98%; margin-top: .5em; display: block; float: none; } + + /* Required fields asterisk styling */ + .uniForm label em, + .uniForm .label em{ float: left; width: 1em; margin: 0 0 0 -1em; } + +/* ------------------------------------------------------------------------------ */ +/* ######################### ALTERNATE LAYOUT ############################### */ +/* ------------------------------------------------------------------------------ */ +/* Styles for form controls where labels are in line with the input elements */ +/* Set the class of the parent (preferably to a fieldset) to .inlineLabels */ +/* ------------------------------------------------------------------------------ */ + + .uniForm .inlineLabels label, + .uniForm .inlineLabels .label{ float: left; margin: .3em 2% 0 0; padding: 0; line-height: 1; position: relative; width: 32%; } + + /* Float the input elements */ + .uniForm .inlineLabels .textInput, + .uniForm .inlineLabels .fileUpload, + .uniForm .inlineLabels .selectInput, + .uniForm .inlineLabels select, + .uniForm .inlineLabels textarea{ float: left; width: 64%; } + + /* Postition the hints */ + .uniForm .inlineLabels .formHint{ clear: both; float: none; width: auto; margin-left: 34%; position: static; } + + /* Position the elements inside combo boxes (multiple inputs/selects/checkboxes/radio buttons per unit) */ + .uniForm .inlineLabels ul{ float: left; width: 66%; } + .uniForm .inlineLabels ul li{ margin: .5em 0; } + .uniForm .inlineLabels ul li label{ float: none; display: block; width: 100%; } + /* Alternate layout */ + .uniForm .inlineLabels ul.alternate li{ margin-right: 3%; margin-top: .25em; } + .uniForm .inlineLabels ul li label .textInput, + .uniForm .inlineLabels ul li label textarea, + .uniForm .inlineLabels ul li label select{ float: none; display: block; width: 98%; } + + /* Required fields asterisk styling */ + .uniForm .inlineLabels label em, + .uniForm .inlineLabels .label em{ display: block; float: none; margin: 0; position: absolute; right: 0; } + +/* ----------------------------------------------------------------------------- */ +/* ########################### Additional Stuff ################################ */ +/* ----------------------------------------------------------------------------- */ + + /* Generals */ + .uniForm legend{ color: inherit; } + + .uniForm .secondaryAction{ float: left; } + + /* .inlineLabel is used for inputs within labels - checkboxes and radio buttons */ + .uniForm .inlineLabel input, + .uniForm .inlineLabels .inlineLabel input, + .uniForm .blockLabels .inlineLabel input, + /* class .inlineLabel is depreciated */ + .uniForm label input{ float: none; display: inline; margin: 0; padding: 0; border: none; } + + .uniForm .buttonHolder .inlineLabel, + .uniForm .buttonHolder label{ float: left; margin: .5em 0 0 0; width: auto; max-width: 60%; text-align: left; } + + /* When you don't want to use a label */ + .uniForm .inlineLabels .noLabel ul{ margin-left: 34%; /* Match to width of label + gap to field */ } + + /* Classes for control of the widths of the fields */ + .uniForm .small { width: 30% !important; } + .uniForm .medium{ width: 45% !important; } + .uniForm .large { } /* Large is default and should match the value you set for .textInput, textarea or select */ + .uniForm .auto { width: auto !important; } + .uniForm .small, + .uniForm .medium, + .uniForm .auto{ margin-right: 4px; } + +/* Columns */ +.uniForm .col{ float: left; } +.uniForm .col{ width: 50%; } \ No newline at end of file From f992c00eb7eab5e86585ee89ce53225d305309f1 Mon Sep 17 00:00:00 2001 From: Ruud Date: Thu, 4 Dec 2014 23:31:45 +0100 Subject: [PATCH 014/301] Remove unused --- Gruntfile.js | 6 +++--- package.json | 2 -- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index b95b9e6..cac33a4 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -70,7 +70,7 @@ module.exports = function(grunt){ // COOL TASKS ============================================================== watch: { scss: { - files: ['**/*.{scss,sass}'], + files: ['<%= config.base %>/**/*.{scss,sass}'], tasks: ['sass:server', 'autoprefixer', 'cssmin'], options: { 'livereload': true @@ -78,9 +78,9 @@ module.exports = function(grunt){ }, js: { files: [ - '<%= config.base %>/scripts/**/*.js' + '<%= config.base %>/**/*.js' ], - tasks: ['jshint'], + tasks: ['jshint', 'uglify'], options: { 'livereload': true } diff --git a/package.json b/package.json index 5f9a19c..538f90c 100644 --- a/package.json +++ b/package.json @@ -13,10 +13,8 @@ "grunt": "~0.4.5", "grunt-autoprefixer": "^2.0.0", "grunt-concurrent": "~1.0.0", - "grunt-contrib-concat": "^0.5.0", "grunt-contrib-cssmin": "~0.10.0", "grunt-contrib-jshint": "~0.10.0", - "grunt-contrib-less": "~0.12.0", "grunt-contrib-sass": "^0.8.1", "grunt-contrib-uglify": "~0.6.0", "grunt-contrib-watch": "~0.6.1", From 5609536f46dbd3e8f11bf96b1d7d2069577aa346 Mon Sep 17 00:00:00 2001 From: Ruud Date: Fri, 5 Dec 2014 11:19:00 +0100 Subject: [PATCH 015/301] Cleanup --- couchpotato/static/scripts/api.js | 2 +- couchpotato/static/scripts/page.js | 4 +- couchpotato/static/scripts/page/about.js | 8 +- couchpotato/static/scripts/page/settings.js | 132 ++++++++++++++-------------- couchpotato/static/scripts/reloader.js | 6 +- 5 files changed, 76 insertions(+), 76 deletions(-) diff --git a/couchpotato/static/scripts/api.js b/couchpotato/static/scripts/api.js index 410b488..28c0c51 100644 --- a/couchpotato/static/scripts/api.js +++ b/couchpotato/static/scripts/api.js @@ -26,4 +26,4 @@ var ApiClass = new Class({ } }); -window.Api = new ApiClass(); \ No newline at end of file +window.Api = new ApiClass(); diff --git a/couchpotato/static/scripts/page.js b/couchpotato/static/scripts/page.js index 480a451..b4530fa 100644 --- a/couchpotato/static/scripts/page.js +++ b/couchpotato/static/scripts/page.js @@ -98,7 +98,7 @@ var PageBase = new Class({ }, getName: function(){ - return this.name + return this.name; }, show: function(){ @@ -110,7 +110,7 @@ var PageBase = new Class({ }, toElement: function(){ - return this.el + return this.el; } }); diff --git a/couchpotato/static/scripts/page/about.js b/couchpotato/static/scripts/page/about.js index a2482f8..3d53b20 100644 --- a/couchpotato/static/scripts/page/about.js +++ b/couchpotato/static/scripts/page/about.js @@ -6,7 +6,7 @@ var AboutSettingTab = new Class({ initialize: function(){ var self = this; - App.addEvent('loadSettings', self.addSettings.bind(self)) + App.addEvent('loadSettings', self.addSettings.bind(self)); }, @@ -48,13 +48,13 @@ var AboutSettingTab = new Class({ 'text': 'Getting version...', 'events': { 'click': App.checkForUpdate.bind(App, function(json){ - self.fillVersion(json.info) + self.fillVersion(json.info); }), 'mouseenter': function(){ - this.set('text', 'Check for updates') + this.set('text', 'Check for updates'); }, 'mouseleave': function(){ - self.fillVersion(Updater.getInfo()) + self.fillVersion(Updater.getInfo()); } } }), diff --git a/couchpotato/static/scripts/page/settings.js b/couchpotato/static/scripts/page/settings.js index 9d083a1..19e60f6 100644 --- a/couchpotato/static/scripts/page/settings.js +++ b/couchpotato/static/scripts/page/settings.js @@ -27,8 +27,8 @@ Page.Settings = new Class({ }, openTab: function(action){ - var self = this, - action = (action == 'index' ? 'about' : action) || self.action; + var self = this; + action = (action == 'index' ? 'about' : action) || self.action; if(self.current) self.toggleTab(self.current, true); @@ -72,7 +72,7 @@ Page.Settings = new Class({ t.content.fireEvent('activate'); } - return t + return t; }, getData: function(onComplete){ @@ -99,7 +99,7 @@ Page.Settings = new Class({ return self.data.values[section][name]; } catch(e){ - return '' + return ''; } }, @@ -113,7 +113,7 @@ Page.Settings = new Class({ }, sortByOrder: function(a, b){ - return (a.order || 100) - (b.order || 100) + return (a.order || 100) - (b.order || 100); }, create: function(json){ @@ -146,7 +146,7 @@ Page.Settings = new Class({ // Add content to tabs var options = []; Object.each(json.options, function(section, section_name){ - section['section_name'] = section_name; + section.section_name = section_name; options.include(section); }); @@ -169,7 +169,7 @@ Page.Settings = new Class({ if(group.subtab){ if(!self.tabs[group.tab].subtabs[group.subtab]) self.createSubTab(group.subtab, group, self.tabs[group.tab], group.tab); - content_container = self.tabs[group.tab].subtabs[group.subtab].content + content_container = self.tabs[group.tab].subtabs[group.subtab].content; } if(group.list && !self.lists[group.list]){ @@ -240,7 +240,7 @@ Page.Settings = new Class({ 'groups': {} }); - return self.tabs[tab_name] + return self.tabs[tab_name]; }, @@ -272,23 +272,24 @@ Page.Settings = new Class({ 'groups': {} }); - return parent_tab.subtabs[tab_name] + return parent_tab.subtabs[tab_name]; }, createGroup: function(group){ + var hint; if((typeOf(group.description) == 'array')){ - var hint = new Element('span.hint.more_hint', { + hint = new Element('span.hint.more_hint', { 'html': group.description[0] }); createTooltip(group.description[1]).inject(hint, 'top'); } else { - var hint = new Element('span.hint', { + hint = new Element('span.hint', { 'html': group.description || '' - }) + }); } var icon; @@ -300,7 +301,7 @@ Page.Settings = new Class({ var label = new Element('span.group_label', { 'text': group.label || (group.name).capitalize() - }) + }); return new Element('fieldset', { 'class': (group.advanced ? 'inlineLabels advanced' : 'inlineLabels') + ' group_' + (group.name || '') + ' subtab_' + (group.subtab || '') @@ -315,7 +316,7 @@ Page.Settings = new Class({ new Element('h3', { 'text': 'Enable another' }) - ).inject(content_container) + ).inject(content_container); } }); @@ -347,7 +348,7 @@ var OptionBase = new Class({ 'keyup': self.changed.bind(self) }); - self.addEvent('injected', self.afterInject.bind(self)) + self.addEvent('injected', self.afterInject.bind(self)); }, @@ -356,7 +357,7 @@ var OptionBase = new Class({ */ createBase: function(){ var self = this; - self.el = new Element('div.ctrlHolder.' + self.section + '_' + self.name) + self.el = new Element('div.ctrlHolder.' + self.section + '_' + self.name); }, create: function(){ @@ -366,18 +367,17 @@ var OptionBase = new Class({ var self = this; return new Element('label', { 'text': (self.options.label || self.options.name.replace('_', ' ')).capitalize() - }) + }); }, setAdvanced: function(){ - this.el.addClass(this.options.advanced ? 'advanced' : '') + this.el.addClass(this.options.advanced ? 'advanced' : ''); }, createHint: function(){ var self = this; if(self.options.description){ - if((typeOf(self.options.description) == 'array')){ var hint = new Element('p.formHint.more_hint', { 'html': self.options.description[0] @@ -388,7 +388,7 @@ var OptionBase = new Class({ else { new Element('p.formHint', { 'html': self.options.description || '' - }).inject(self.el) + }).inject(self.el); } } }, @@ -405,7 +405,7 @@ var OptionBase = new Class({ if(self.changed_timer) clearTimeout(self.changed_timer); self.changed_timer = self.save.delay(300, self); } - self.fireEvent('change') + self.fireEvent('change'); } }, @@ -490,7 +490,7 @@ Option.String = new Class({ }, getPlaceholder: function(){ - return this.options.placeholder + return this.options.placeholder; } }); @@ -511,7 +511,7 @@ Option.Dropdown = new Class({ new Element('option', { 'text': value[0], 'value': value[1] - }).inject(self.input) + }).inject(self.input); }); self.input.set('value', self.getSettingValue()); @@ -573,7 +573,7 @@ Option.Password = new Class({ self.input.addEvent('focus', function(){ self.input.set('value', ''); self.input.set('type', 'password'); - }) + }); } }); @@ -621,7 +621,7 @@ Option.Enabler = new Class({ self.parentFieldset = self.el.getParent('fieldset').addClass('enabler'); self.parentList = self.parentFieldset.getParent('.option_list'); self.el.inject(self.parentFieldset, 'top'); - self.checkState() + self.checkState(); } }); @@ -676,12 +676,13 @@ Option.Directory = new Class({ var self = this, value = self.getValue(), path_sep = Api.getOption('path_sep'), - active_selector = 'li:not(.blur):not(.empty)'; + active_selector = 'li:not(.blur):not(.empty)', + first; if(e.key == 'enter' || e.key == 'tab'){ (e).stop(); - var first = self.dir_list.getElement(active_selector); + first = self.dir_list.getElement(active_selector); if(first){ self.selectDirectory(first.get('data-value')); } @@ -691,20 +692,20 @@ Option.Directory = new Class({ // New folder if(value.substr(-1) == path_sep){ if(self.current_dir != value) - self.selectDirectory(value) + self.selectDirectory(value); } else { var pd = self.getParentDir(value); if(self.current_dir != pd) self.getDirs(pd); - var folder_filter = value.split(path_sep).getLast() + var folder_filter = value.split(path_sep).getLast(); self.dir_list.getElements('li').each(function(li){ - var valid = li.get('text').substr(0, folder_filter.length).toLowerCase() != folder_filter.toLowerCase() - li[valid ? 'addClass' : 'removeClass']('blur') + var valid = li.get('text').substr(0, folder_filter.length).toLowerCase() != folder_filter.toLowerCase(); + li[valid ? 'addClass' : 'removeClass']('blur'); }); - var first = self.dir_list.getElement(active_selector); + first = self.dir_list.getElement(active_selector); if(first){ if(!self.dir_list_scroll) self.dir_list_scroll = new Fx.Scroll(self.dir_list, { @@ -722,13 +723,13 @@ Option.Directory = new Class({ self.input.set('value', dir); - self.getDirs() + self.getDirs(); }, previousDirectory: function(){ var self = this; - self.selectDirectory(self.getParentDir()) + self.selectDirectory(self.getParentDir()); }, caretAtEnd: function(){ @@ -751,7 +752,7 @@ Option.Directory = new Class({ // Move caret to back of the input if(!self.browser || self.browser && !self.browser.isVisible()) - self.caretAtEnd() + self.caretAtEnd(); if(!self.browser){ self.browser = new Element('div.directory_list').adopt( @@ -769,7 +770,7 @@ Option.Directory = new Class({ self.show_hidden = new Element('input[type=checkbox].inlay', { 'events': { 'change': function(){ - self.getDirs() + self.getDirs(); } } }) @@ -779,7 +780,7 @@ Option.Directory = new Class({ 'events': { 'click:relay(li:not(.empty))': function(e, el){ (e).preventDefault(); - self.selectDirectory(el.get('data-value')) + self.selectDirectory(el.get('data-value')); }, 'mousewheel': function(e){ (e).stopPropagation(); @@ -809,7 +810,7 @@ Option.Directory = new Class({ 'text': 'Save', 'events': { 'click': function(e){ - self.hideBrowser(e, true) + self.hideBrowser(e, true); } } }) @@ -823,7 +824,7 @@ Option.Directory = new Class({ self.getDirs(); self.browser.show(); - self.el.addEvent('outerClick', self.hideBrowser.bind(self)) + self.el.addEvent('outerClick', self.hideBrowser.bind(self)); }, hideBrowser: function(e, save){ @@ -836,7 +837,7 @@ Option.Directory = new Class({ self.input.set('value', self.initial_directory); self.browser.hide(); - self.el.removeEvents('outerClick') + self.el.removeEvents('outerClick'); }, @@ -848,7 +849,7 @@ Option.Directory = new Class({ var previous_dir = json.parent; - if(v == '') + if(v === '') self.input.set('value', json.home); if(previous_dir.length >= 1 && !json.is_root){ @@ -861,10 +862,10 @@ Option.Directory = new Class({ self.back_button.set('data-value', previous_dir); self.back_button.set('html', '« ' + prev_dirname); - self.back_button.show() + self.back_button.show(); } else { - self.back_button.hide() + self.back_button.hide(); } if(self.use_cache) @@ -879,12 +880,12 @@ Option.Directory = new Class({ new Element('li', { 'data-value': dir, 'text': self.getCurrentDirname(dir) - }).inject(self.dir_list) + }).inject(self.dir_list); }); else new Element('li.empty', { 'text': 'Selected folder is empty' - }).inject(self.dir_list) + }).inject(self.dir_list); //fix for webkit type browsers to refresh the dom for the file browser //http://stackoverflow.com/questions/3485365/how-can-i-force-webkit-to-redraw-repaint-to-propagate-style-changes @@ -897,7 +898,7 @@ Option.Directory = new Class({ c = dir || self.getValue(); if(self.cached[c] && self.use_cache){ - self.fillBrowser() + self.fillBrowser(); } else { Api.request('directory.list', { @@ -909,7 +910,7 @@ Option.Directory = new Class({ self.current_dir = c; self.fillBrowser(json); } - }) + }); } }, @@ -922,16 +923,16 @@ Option.Directory = new Class({ var v = dir || self.getValue(); var sep = Api.getOption('path_sep'); var dirs = v.split(sep); - if(dirs.pop() == '') + if(dirs.pop() === '') dirs.pop(); - return dirs.join(sep) + sep + return dirs.join(sep) + sep; }, getCurrentDirname: function(dir){ var dir_split = dir.split(Api.getOption('path_sep')); - return dir_split[dir_split.length-2] || Api.getOption('path_sep') + return dir_split[dir_split.length-2] || Api.getOption('path_sep'); }, getValue: function(){ @@ -976,7 +977,7 @@ Option.Directories = new Class({ var parent = self.el.getParent('fieldset'); var dirs = parent.getElements('.multi_directory'); - if(dirs.length == 0) + if(dirs.length === 0) $(dir).inject(parent); else $(dir).inject(dirs.getLast(), 'after'); @@ -1080,7 +1081,7 @@ Option.Choice = new Class({ self.el.addEvent('outerClick', function(){ self.reset(); self.el.removeEvents('outerClick'); - }) + }); } } }).inject(self.input, 'after'); @@ -1105,7 +1106,7 @@ Option.Choice = new Class({ }); }); - if(mtches.length == 0 && value != '') + if(mtches.length === 0 && value !== '') mtches.include(value); mtches.each(self.addTag.bind(self)); @@ -1141,10 +1142,10 @@ Option.Choice = new Class({ self.addLastTag(); }, 'onGoLeft': function(){ - self.goLeft(this) + self.goLeft(this); }, 'onGoRight': function(){ - self.goRight(this) + self.goRight(this); } }); $(tag).inject(self.tag_input); @@ -1412,7 +1413,7 @@ Option.Combined = new Class({ }); self.inputs[name].getParent('.ctrlHolder').setStyle('display', 'none'); - self.inputs[name].addEvent('change', self.addEmpty.bind(self)) + self.inputs[name].addEvent('change', self.addEmpty.bind(self)); }); @@ -1427,7 +1428,7 @@ Option.Combined = new Class({ 'class': name, 'text': self.labels[name], 'title': self.descriptions[name] - }).inject(head) + }).inject(head); }); @@ -1450,8 +1451,8 @@ Option.Combined = new Class({ var empty_count = 0; self.options.combine.each(function(name){ var input = ctrl_holder.getElement('input.' + name); - if(input.get('value') == '' || input.get('type') == 'checkbox') - empty_count++ + if(input.get('value') === '' || input.get('type') == 'checkbox') + empty_count++; }); has_empty += (empty_count == self.options.combine.length) ? 1 : 0; ctrl_holder[(empty_count == self.options.combine.length) ? 'addClass' : 'removeClass']('is_empty'); @@ -1516,10 +1517,9 @@ Option.Combined = new Class({ }, saveCombined: function(){ - var self = this; - + var self = this, + temp = {}; - var temp = {}; self.items.each(function(item, nr){ self.options.combine.each(function(name){ var input = item.getElement('input.'+name); @@ -1528,7 +1528,7 @@ Option.Combined = new Class({ if(!temp[name]) temp[name] = []; temp[name][nr] = input.get('type') == 'checkbox' ? +input.get('checked') : input.get('value').trim(); - }) + }); }); self.options.combine.each(function(name){ @@ -1536,7 +1536,7 @@ Option.Combined = new Class({ self.inputs[name].fireEvent('change'); }); - self.addEmpty() + self.addEmpty(); }, @@ -1559,10 +1559,10 @@ var createTooltip = function(description){ var tip = new Element('div.tooltip', { 'events': { 'mouseenter': function(){ - tip.addClass('shown') + tip.addClass('shown'); }, 'mouseleave': function(){ - tip.removeClass('shown') + tip.removeClass('shown'); } } }).adopt( diff --git a/couchpotato/static/scripts/reloader.js b/couchpotato/static/scripts/reloader.js index 3a4eec8..3f35f9e 100644 --- a/couchpotato/static/scripts/reloader.js +++ b/couchpotato/static/scripts/reloader.js @@ -3,7 +3,7 @@ var ReloaderBase = new Class({ initialize: function(){ var self = this; - App.on('watcher.changed', self.reloadFile.bind(self)) + App.on('watcher.changed', self.reloadFile.bind(self)); }, @@ -15,8 +15,8 @@ var ReloaderBase = new Class({ var without_timestamp = url.split('?')[0], old_links = document.getElement('[data-url^=\''+without_timestamp+'\']'); - old_links.set('href', old_links.get('href') + 1) - }) + old_links.set('href', old_links.get('href') + 1); + }); } }); From d6cfcae45bceab6789c76a7ddd8685f2a51d9ae6 Mon Sep 17 00:00:00 2001 From: Ruud Date: Fri, 5 Dec 2014 11:29:25 +0100 Subject: [PATCH 016/301] Move to vendor folder --- .../static/scripts/library/Array.stableSort.js | 56 - couchpotato/static/scripts/library/async.js | 955 ---- .../scripts/library/form_replacement/form_check.js | 126 - .../library/form_replacement/form_checkgroup.js | 51 - .../library/form_replacement/form_dropdown.js | 325 -- .../scripts/library/form_replacement/form_radio.js | 34 - .../library/form_replacement/form_radiogroup.js | 59 - .../library/form_replacement/form_selectoption.js | 93 - couchpotato/static/scripts/library/history.js | 176 - couchpotato/static/scripts/library/mootools.js | 5887 -------------------- .../static/scripts/library/mootools_more.js | 3216 ----------- .../static/scripts/library/mootools_tween_css3.js | 141 - couchpotato/static/scripts/library/spin.js | 301 - .../static/scripts/vendor/Array.stableSort.js | 56 + couchpotato/static/scripts/vendor/async.js | 955 ++++ .../scripts/vendor/form_replacement/form_check.js | 126 + .../vendor/form_replacement/form_checkgroup.js | 51 + .../vendor/form_replacement/form_dropdown.js | 325 ++ .../scripts/vendor/form_replacement/form_radio.js | 34 + .../vendor/form_replacement/form_radiogroup.js | 59 + .../vendor/form_replacement/form_selectoption.js | 93 + couchpotato/static/scripts/vendor/history.js | 176 + couchpotato/static/scripts/vendor/mootools.js | 5887 ++++++++++++++++++++ couchpotato/static/scripts/vendor/mootools_more.js | 3216 +++++++++++ .../static/scripts/vendor/mootools_tween_css3.js | 141 + couchpotato/static/scripts/vendor/spin.js | 301 + 26 files changed, 11420 insertions(+), 11420 deletions(-) delete mode 100644 couchpotato/static/scripts/library/Array.stableSort.js delete mode 100644 couchpotato/static/scripts/library/async.js delete mode 100644 couchpotato/static/scripts/library/form_replacement/form_check.js delete mode 100644 couchpotato/static/scripts/library/form_replacement/form_checkgroup.js delete mode 100644 couchpotato/static/scripts/library/form_replacement/form_dropdown.js delete mode 100644 couchpotato/static/scripts/library/form_replacement/form_radio.js delete mode 100644 couchpotato/static/scripts/library/form_replacement/form_radiogroup.js delete mode 100644 couchpotato/static/scripts/library/form_replacement/form_selectoption.js delete mode 100644 couchpotato/static/scripts/library/history.js delete mode 100644 couchpotato/static/scripts/library/mootools.js delete mode 100644 couchpotato/static/scripts/library/mootools_more.js delete mode 100644 couchpotato/static/scripts/library/mootools_tween_css3.js delete mode 100644 couchpotato/static/scripts/library/spin.js create mode 100644 couchpotato/static/scripts/vendor/Array.stableSort.js create mode 100644 couchpotato/static/scripts/vendor/async.js create mode 100644 couchpotato/static/scripts/vendor/form_replacement/form_check.js create mode 100644 couchpotato/static/scripts/vendor/form_replacement/form_checkgroup.js create mode 100644 couchpotato/static/scripts/vendor/form_replacement/form_dropdown.js create mode 100644 couchpotato/static/scripts/vendor/form_replacement/form_radio.js create mode 100644 couchpotato/static/scripts/vendor/form_replacement/form_radiogroup.js create mode 100644 couchpotato/static/scripts/vendor/form_replacement/form_selectoption.js create mode 100644 couchpotato/static/scripts/vendor/history.js create mode 100644 couchpotato/static/scripts/vendor/mootools.js create mode 100644 couchpotato/static/scripts/vendor/mootools_more.js create mode 100644 couchpotato/static/scripts/vendor/mootools_tween_css3.js create mode 100644 couchpotato/static/scripts/vendor/spin.js diff --git a/couchpotato/static/scripts/library/Array.stableSort.js b/couchpotato/static/scripts/library/Array.stableSort.js deleted file mode 100644 index 062c756..0000000 --- a/couchpotato/static/scripts/library/Array.stableSort.js +++ /dev/null @@ -1,56 +0,0 @@ -/* ---- - -script: Array.stableSort.js - -description: Add a stable sort algorithm for all browsers - -license: MIT-style license. - -authors: - - Yorick Sijsling - -requires: - core/1.3: '*' - -provides: - - [Array.stableSort, Array.mergeSort] - -... -*/ - -(function() { - - var defaultSortFunction = function(a, b) { - return a > b ? 1 : (a < b ? -1 : 0); - } - - Array.implement({ - - stableSort: function(compare) { - // I would love some real feature recognition. Problem is that an unstable algorithm sometimes/often gives the same result as an unstable algorithm. - return (Browser.chrome || Browser.firefox2 || Browser.opera9) ? this.mergeSort(compare) : this.sort(compare); - }, - - mergeSort: function(compare, token) { - compare = compare || defaultSortFunction; - if (this.length > 1) { - // Split and sort both parts - var right = this.splice(Math.floor(this.length / 2)).mergeSort(compare); - var left = this.splice(0).mergeSort(compare); // 'this' is now empty. - - // Merge parts together - while (left.length > 0 || right.length > 0) { - this.push( - right.length === 0 ? left.shift() - : left.length === 0 ? right.shift() - : compare(left[0], right[0]) > 0 ? right.shift() - : left.shift()); - } - } - return this; - } - - }); -})(); - diff --git a/couchpotato/static/scripts/library/async.js b/couchpotato/static/scripts/library/async.js deleted file mode 100644 index cb6320d..0000000 --- a/couchpotato/static/scripts/library/async.js +++ /dev/null @@ -1,955 +0,0 @@ -/*global setImmediate: false, setTimeout: false, console: false */ -(function () { - - var async = {}; - - // global on the server, window in the browser - var root, previous_async; - - root = this; - if (root != null) { - previous_async = root.async; - } - - async.noConflict = function () { - root.async = previous_async; - return async; - }; - - function only_once(fn) { - var called = false; - return function() { - if (called) throw new Error("Callback was already called."); - called = true; - fn.apply(root, arguments); - } - } - - //// cross-browser compatiblity functions //// - - var _each = function (arr, iterator) { - if (arr.forEach) { - return arr.forEach(iterator); - } - for (var i = 0; i < arr.length; i += 1) { - iterator(arr[i], i, arr); - } - }; - - var _map = function (arr, iterator) { - if (arr.map) { - return arr.map(iterator); - } - var results = []; - _each(arr, function (x, i, a) { - results.push(iterator(x, i, a)); - }); - return results; - }; - - var _reduce = function (arr, iterator, memo) { - if (arr.reduce) { - return arr.reduce(iterator, memo); - } - _each(arr, function (x, i, a) { - memo = iterator(memo, x, i, a); - }); - return memo; - }; - - var _keys = function (obj) { - if (Object.keys) { - return Object.keys(obj); - } - var keys = []; - for (var k in obj) { - if (obj.hasOwnProperty(k)) { - keys.push(k); - } - } - return keys; - }; - - //// exported async module functions //// - - //// nextTick implementation with browser-compatible fallback //// - if (typeof process === 'undefined' || !(process.nextTick)) { - if (typeof setImmediate === 'function') { - async.nextTick = function (fn) { - // not a direct alias for IE10 compatibility - setImmediate(fn); - }; - async.setImmediate = async.nextTick; - } - else { - async.nextTick = function (fn) { - setTimeout(fn, 0); - }; - async.setImmediate = async.nextTick; - } - } - else { - async.nextTick = process.nextTick; - if (typeof setImmediate !== 'undefined') { - async.setImmediate = setImmediate; - } - else { - async.setImmediate = async.nextTick; - } - } - - async.each = function (arr, iterator, callback) { - callback = callback || function () {}; - if (!arr.length) { - return callback(); - } - var completed = 0; - _each(arr, function (x) { - iterator(x, only_once(function (err) { - if (err) { - callback(err); - callback = function () {}; - } - else { - completed += 1; - if (completed >= arr.length) { - callback(null); - } - } - })); - }); - }; - async.forEach = async.each; - - async.eachSeries = function (arr, iterator, callback) { - callback = callback || function () {}; - if (!arr.length) { - return callback(); - } - var completed = 0; - var iterate = function () { - iterator(arr[completed], function (err) { - if (err) { - callback(err); - callback = function () {}; - } - else { - completed += 1; - if (completed >= arr.length) { - callback(null); - } - else { - iterate(); - } - } - }); - }; - iterate(); - }; - async.forEachSeries = async.eachSeries; - - async.eachLimit = function (arr, limit, iterator, callback) { - var fn = _eachLimit(limit); - fn.apply(null, [arr, iterator, callback]); - }; - async.forEachLimit = async.eachLimit; - - var _eachLimit = function (limit) { - - return function (arr, iterator, callback) { - callback = callback || function () {}; - if (!arr.length || limit <= 0) { - return callback(); - } - var completed = 0; - var started = 0; - var running = 0; - - (function replenish () { - if (completed >= arr.length) { - return callback(); - } - - while (running < limit && started < arr.length) { - started += 1; - running += 1; - iterator(arr[started - 1], function (err) { - if (err) { - callback(err); - callback = function () {}; - } - else { - completed += 1; - running -= 1; - if (completed >= arr.length) { - callback(); - } - else { - replenish(); - } - } - }); - } - })(); - }; - }; - - - var doParallel = function (fn) { - return function () { - var args = Array.prototype.slice.call(arguments); - return fn.apply(null, [async.each].concat(args)); - }; - }; - var doParallelLimit = function(limit, fn) { - return function () { - var args = Array.prototype.slice.call(arguments); - return fn.apply(null, [_eachLimit(limit)].concat(args)); - }; - }; - var doSeries = function (fn) { - return function () { - var args = Array.prototype.slice.call(arguments); - return fn.apply(null, [async.eachSeries].concat(args)); - }; - }; - - - var _asyncMap = function (eachfn, arr, iterator, callback) { - var results = []; - arr = _map(arr, function (x, i) { - return {index: i, value: x}; - }); - eachfn(arr, function (x, callback) { - iterator(x.value, function (err, v) { - results[x.index] = v; - callback(err); - }); - }, function (err) { - callback(err, results); - }); - }; - async.map = doParallel(_asyncMap); - async.mapSeries = doSeries(_asyncMap); - async.mapLimit = function (arr, limit, iterator, callback) { - return _mapLimit(limit)(arr, iterator, callback); - }; - - var _mapLimit = function(limit) { - return doParallelLimit(limit, _asyncMap); - }; - - // reduce only has a series version, as doing reduce in parallel won't - // work in many situations. - async.reduce = function (arr, memo, iterator, callback) { - async.eachSeries(arr, function (x, callback) { - iterator(memo, x, function (err, v) { - memo = v; - callback(err); - }); - }, function (err) { - callback(err, memo); - }); - }; - // inject alias - async.inject = async.reduce; - // foldl alias - async.foldl = async.reduce; - - async.reduceRight = function (arr, memo, iterator, callback) { - var reversed = _map(arr, function (x) { - return x; - }).reverse(); - async.reduce(reversed, memo, iterator, callback); - }; - // foldr alias - async.foldr = async.reduceRight; - - var _filter = function (eachfn, arr, iterator, callback) { - var results = []; - arr = _map(arr, function (x, i) { - return {index: i, value: x}; - }); - eachfn(arr, function (x, callback) { - iterator(x.value, function (v) { - if (v) { - results.push(x); - } - callback(); - }); - }, function (err) { - callback(_map(results.sort(function (a, b) { - return a.index - b.index; - }), function (x) { - return x.value; - })); - }); - }; - async.filter = doParallel(_filter); - async.filterSeries = doSeries(_filter); - // select alias - async.select = async.filter; - async.selectSeries = async.filterSeries; - - var _reject = function (eachfn, arr, iterator, callback) { - var results = []; - arr = _map(arr, function (x, i) { - return {index: i, value: x}; - }); - eachfn(arr, function (x, callback) { - iterator(x.value, function (v) { - if (!v) { - results.push(x); - } - callback(); - }); - }, function (err) { - callback(_map(results.sort(function (a, b) { - return a.index - b.index; - }), function (x) { - return x.value; - })); - }); - }; - async.reject = doParallel(_reject); - async.rejectSeries = doSeries(_reject); - - var _detect = function (eachfn, arr, iterator, main_callback) { - eachfn(arr, function (x, callback) { - iterator(x, function (result) { - if (result) { - main_callback(x); - main_callback = function () {}; - } - else { - callback(); - } - }); - }, function (err) { - main_callback(); - }); - }; - async.detect = doParallel(_detect); - async.detectSeries = doSeries(_detect); - - async.some = function (arr, iterator, main_callback) { - async.each(arr, function (x, callback) { - iterator(x, function (v) { - if (v) { - main_callback(true); - main_callback = function () {}; - } - callback(); - }); - }, function (err) { - main_callback(false); - }); - }; - // any alias - async.any = async.some; - - async.every = function (arr, iterator, main_callback) { - async.each(arr, function (x, callback) { - iterator(x, function (v) { - if (!v) { - main_callback(false); - main_callback = function () {}; - } - callback(); - }); - }, function (err) { - main_callback(true); - }); - }; - // all alias - async.all = async.every; - - async.sortBy = function (arr, iterator, callback) { - async.map(arr, function (x, callback) { - iterator(x, function (err, criteria) { - if (err) { - callback(err); - } - else { - callback(null, {value: x, criteria: criteria}); - } - }); - }, function (err, results) { - if (err) { - return callback(err); - } - else { - var fn = function (left, right) { - var a = left.criteria, b = right.criteria; - return a < b ? -1 : a > b ? 1 : 0; - }; - callback(null, _map(results.sort(fn), function (x) { - return x.value; - })); - } - }); - }; - - async.auto = function (tasks, callback) { - callback = callback || function () {}; - var keys = _keys(tasks); - if (!keys.length) { - return callback(null); - } - - var results = {}; - - var listeners = []; - var addListener = function (fn) { - listeners.unshift(fn); - }; - var removeListener = function (fn) { - for (var i = 0; i < listeners.length; i += 1) { - if (listeners[i] === fn) { - listeners.splice(i, 1); - return; - } - } - }; - var taskComplete = function () { - _each(listeners.slice(0), function (fn) { - fn(); - }); - }; - - addListener(function () { - if (_keys(results).length === keys.length) { - callback(null, results); - callback = function () {}; - } - }); - - _each(keys, function (k) { - var task = (tasks[k] instanceof Function) ? [tasks[k]]: tasks[k]; - var taskCallback = function (err) { - var args = Array.prototype.slice.call(arguments, 1); - if (args.length <= 1) { - args = args[0]; - } - if (err) { - var safeResults = {}; - _each(_keys(results), function(rkey) { - safeResults[rkey] = results[rkey]; - }); - safeResults[k] = args; - callback(err, safeResults); - // stop subsequent errors hitting callback multiple times - callback = function () {}; - } - else { - results[k] = args; - async.setImmediate(taskComplete); - } - }; - var requires = task.slice(0, Math.abs(task.length - 1)) || []; - var ready = function () { - return _reduce(requires, function (a, x) { - return (a && results.hasOwnProperty(x)); - }, true) && !results.hasOwnProperty(k); - }; - if (ready()) { - task[task.length - 1](taskCallback, results); - } - else { - var listener = function () { - if (ready()) { - removeListener(listener); - task[task.length - 1](taskCallback, results); - } - }; - addListener(listener); - } - }); - }; - - async.waterfall = function (tasks, callback) { - callback = callback || function () {}; - if (tasks.constructor !== Array) { - var err = new Error('First argument to waterfall must be an array of functions'); - return callback(err); - } - if (!tasks.length) { - return callback(); - } - var wrapIterator = function (iterator) { - return function (err) { - if (err) { - callback.apply(null, arguments); - callback = function () {}; - } - else { - var args = Array.prototype.slice.call(arguments, 1); - var next = iterator.next(); - if (next) { - args.push(wrapIterator(next)); - } - else { - args.push(callback); - } - async.setImmediate(function () { - iterator.apply(null, args); - }); - } - }; - }; - wrapIterator(async.iterator(tasks))(); - }; - - var _parallel = function(eachfn, tasks, callback) { - callback = callback || function () {}; - if (tasks.constructor === Array) { - eachfn.map(tasks, function (fn, callback) { - if (fn) { - fn(function (err) { - var args = Array.prototype.slice.call(arguments, 1); - if (args.length <= 1) { - args = args[0]; - } - callback.call(null, err, args); - }); - } - }, callback); - } - else { - var results = {}; - eachfn.each(_keys(tasks), function (k, callback) { - tasks[k](function (err) { - var args = Array.prototype.slice.call(arguments, 1); - if (args.length <= 1) { - args = args[0]; - } - results[k] = args; - callback(err); - }); - }, function (err) { - callback(err, results); - }); - } - }; - - async.parallel = function (tasks, callback) { - _parallel({ map: async.map, each: async.each }, tasks, callback); - }; - - async.parallelLimit = function(tasks, limit, callback) { - _parallel({ map: _mapLimit(limit), each: _eachLimit(limit) }, tasks, callback); - }; - - async.series = function (tasks, callback) { - callback = callback || function () {}; - if (tasks.constructor === Array) { - async.mapSeries(tasks, function (fn, callback) { - if (fn) { - fn(function (err) { - var args = Array.prototype.slice.call(arguments, 1); - if (args.length <= 1) { - args = args[0]; - } - callback.call(null, err, args); - }); - } - }, callback); - } - else { - var results = {}; - async.eachSeries(_keys(tasks), function (k, callback) { - tasks[k](function (err) { - var args = Array.prototype.slice.call(arguments, 1); - if (args.length <= 1) { - args = args[0]; - } - results[k] = args; - callback(err); - }); - }, function (err) { - callback(err, results); - }); - } - }; - - async.iterator = function (tasks) { - var makeCallback = function (index) { - var fn = function () { - if (tasks.length) { - tasks[index].apply(null, arguments); - } - return fn.next(); - }; - fn.next = function () { - return (index < tasks.length - 1) ? makeCallback(index + 1): null; - }; - return fn; - }; - return makeCallback(0); - }; - - async.apply = function (fn) { - var args = Array.prototype.slice.call(arguments, 1); - return function () { - return fn.apply( - null, args.concat(Array.prototype.slice.call(arguments)) - ); - }; - }; - - var _concat = function (eachfn, arr, fn, callback) { - var r = []; - eachfn(arr, function (x, cb) { - fn(x, function (err, y) { - r = r.concat(y || []); - cb(err); - }); - }, function (err) { - callback(err, r); - }); - }; - async.concat = doParallel(_concat); - async.concatSeries = doSeries(_concat); - - async.whilst = function (test, iterator, callback) { - if (test()) { - iterator(function (err) { - if (err) { - return callback(err); - } - async.whilst(test, iterator, callback); - }); - } - else { - callback(); - } - }; - - async.doWhilst = function (iterator, test, callback) { - iterator(function (err) { - if (err) { - return callback(err); - } - if (test()) { - async.doWhilst(iterator, test, callback); - } - else { - callback(); - } - }); - }; - - async.until = function (test, iterator, callback) { - if (!test()) { - iterator(function (err) { - if (err) { - return callback(err); - } - async.until(test, iterator, callback); - }); - } - else { - callback(); - } - }; - - async.doUntil = function (iterator, test, callback) { - iterator(function (err) { - if (err) { - return callback(err); - } - if (!test()) { - async.doUntil(iterator, test, callback); - } - else { - callback(); - } - }); - }; - - async.queue = function (worker, concurrency) { - if (concurrency === undefined) { - concurrency = 1; - } - function _insert(q, data, pos, callback) { - if(data.constructor !== Array) { - data = [data]; - } - _each(data, function(task) { - var item = { - data: task, - callback: typeof callback === 'function' ? callback : null - }; - - if (pos) { - q.tasks.unshift(item); - } else { - q.tasks.push(item); - } - - if (q.saturated && q.tasks.length === concurrency) { - q.saturated(); - } - async.setImmediate(q.process); - }); - } - - var workers = 0; - var q = { - tasks: [], - concurrency: concurrency, - saturated: null, - empty: null, - drain: null, - push: function (data, callback) { - _insert(q, data, false, callback); - }, - unshift: function (data, callback) { - _insert(q, data, true, callback); - }, - process: function () { - if (workers < q.concurrency && q.tasks.length) { - var task = q.tasks.shift(); - if (q.empty && q.tasks.length === 0) { - q.empty(); - } - workers += 1; - var next = function () { - workers -= 1; - if (task.callback) { - task.callback.apply(task, arguments); - } - if (q.drain && q.tasks.length + workers === 0) { - q.drain(); - } - q.process(); - }; - var cb = only_once(next); - worker(task.data, cb); - } - }, - length: function () { - return q.tasks.length; - }, - running: function () { - return workers; - } - }; - return q; - }; - - async.cargo = function (worker, payload) { - var working = false, - tasks = []; - - var cargo = { - tasks: tasks, - payload: payload, - saturated: null, - empty: null, - drain: null, - push: function (data, callback) { - if(data.constructor !== Array) { - data = [data]; - } - _each(data, function(task) { - tasks.push({ - data: task, - callback: typeof callback === 'function' ? callback : null - }); - if (cargo.saturated && tasks.length === payload) { - cargo.saturated(); - } - }); - async.setImmediate(cargo.process); - }, - process: function process() { - if (working) return; - if (tasks.length === 0) { - if(cargo.drain) cargo.drain(); - return; - } - - var ts = typeof payload === 'number' - ? tasks.splice(0, payload) - : tasks.splice(0); - - var ds = _map(ts, function (task) { - return task.data; - }); - - if(cargo.empty) cargo.empty(); - working = true; - worker(ds, function () { - working = false; - - var args = arguments; - _each(ts, function (data) { - if (data.callback) { - data.callback.apply(null, args); - } - }); - - process(); - }); - }, - length: function () { - return tasks.length; - }, - running: function () { - return working; - } - }; - return cargo; - }; - - var _console_fn = function (name) { - return function (fn) { - var args = Array.prototype.slice.call(arguments, 1); - fn.apply(null, args.concat([function (err) { - var args = Array.prototype.slice.call(arguments, 1); - if (typeof console !== 'undefined') { - if (err) { - if (console.error) { - console.error(err); - } - } - else if (console[name]) { - _each(args, function (x) { - console[name](x); - }); - } - } - }])); - }; - }; - async.log = _console_fn('log'); - async.dir = _console_fn('dir'); - /*async.info = _console_fn('info'); - async.warn = _console_fn('warn'); - async.error = _console_fn('error');*/ - - async.memoize = function (fn, hasher) { - var memo = {}; - var queues = {}; - hasher = hasher || function (x) { - return x; - }; - var memoized = function () { - var args = Array.prototype.slice.call(arguments); - var callback = args.pop(); - var key = hasher.apply(null, args); - if (key in memo) { - callback.apply(null, memo[key]); - } - else if (key in queues) { - queues[key].push(callback); - } - else { - queues[key] = [callback]; - fn.apply(null, args.concat([function () { - memo[key] = arguments; - var q = queues[key]; - delete queues[key]; - for (var i = 0, l = q.length; i < l; i++) { - q[i].apply(null, arguments); - } - }])); - } - }; - memoized.memo = memo; - memoized.unmemoized = fn; - return memoized; - }; - - async.unmemoize = function (fn) { - return function () { - return (fn.unmemoized || fn).apply(null, arguments); - }; - }; - - async.times = function (count, iterator, callback) { - var counter = []; - for (var i = 0; i < count; i++) { - counter.push(i); - } - return async.map(counter, iterator, callback); - }; - - async.timesSeries = function (count, iterator, callback) { - var counter = []; - for (var i = 0; i < count; i++) { - counter.push(i); - } - return async.mapSeries(counter, iterator, callback); - }; - - async.compose = function (/* functions... */) { - var fns = Array.prototype.reverse.call(arguments); - return function () { - var that = this; - var args = Array.prototype.slice.call(arguments); - var callback = args.pop(); - async.reduce(fns, args, function (newargs, fn, cb) { - fn.apply(that, newargs.concat([function () { - var err = arguments[0]; - var nextargs = Array.prototype.slice.call(arguments, 1); - cb(err, nextargs); - }])) - }, - function (err, results) { - callback.apply(that, [err].concat(results)); - }); - }; - }; - - var _applyEach = function (eachfn, fns /*args...*/) { - var go = function () { - var that = this; - var args = Array.prototype.slice.call(arguments); - var callback = args.pop(); - return eachfn(fns, function (fn, cb) { - fn.apply(that, args.concat([cb])); - }, - callback); - }; - if (arguments.length > 2) { - var args = Array.prototype.slice.call(arguments, 2); - return go.apply(this, args); - } - else { - return go; - } - }; - async.applyEach = doParallel(_applyEach); - async.applyEachSeries = doSeries(_applyEach); - - async.forever = function (fn, callback) { - function next(err) { - if (err) { - if (callback) { - return callback(err); - } - throw err; - } - fn(next); - } - next(); - }; - - // AMD / RequireJS - if (typeof define !== 'undefined' && define.amd) { - define([], function () { - return async; - }); - } - // Node.js - else if (typeof module !== 'undefined' && module.exports) { - module.exports = async; - } - // included directly via + {% end %} - + {% end %} + {% for url in fireEvent('clientscript.get_styles', location = 'head', single = True) %} + {% end %} + + {% if Env.get('dev') %} + + {% end %} + {% end %} - - {% end %} - - {% for url in fireEvent('clientscript.get_styles', location = 'head', single = True) %} - {% end %} + {% for url in fireEvent('clientscript.get_scripts', location = 'front', single = True) %}{% if 'combined.plugins' not in url %} + {% end %}{% end %} @@ -23,23 +20,15 @@ {% end %} - - CouchPotato

CouchPotato

-
-
+
+
- +
From 0e7b78746599c790992b17d87fa327a719597cae Mon Sep 17 00:00:00 2001 From: Ruud Date: Fri, 17 Jul 2015 12:16:20 +0200 Subject: [PATCH 203/301] Simplify mixin loading --- config.rb | 5 ++--- couchpotato/core/media/_base/search/static/search.scss | 2 +- couchpotato/core/media/movie/_base/static/movie.scss | 2 +- couchpotato/core/plugins/category/static/category.scss | 2 +- couchpotato/core/plugins/log/static/log.scss | 2 +- couchpotato/core/plugins/profile/static/profile.scss | 2 +- couchpotato/core/plugins/quality/static/quality.scss | 4 ++-- 7 files changed, 9 insertions(+), 10 deletions(-) diff --git a/config.rb b/config.rb index d201eee..a26a2ee 100644 --- a/config.rb +++ b/config.rb @@ -29,9 +29,8 @@ http_path = "/" ## ## You probably don't need to edit anything below this. ## - -sass_dir = "./" -css_dir = "./static/style_compiled" +sass_dir = "./couchpotato/static/style" +css_dir = "./couchpotato/static/style" # You can select your preferred output style here (can be overridden via the command line): # output_style = :expanded or :nested or :compact or :compressed diff --git a/couchpotato/core/media/_base/search/static/search.scss b/couchpotato/core/media/_base/search/static/search.scss index c98b382..4ff6225 100644 --- a/couchpotato/core/media/_base/search/static/search.scss +++ b/couchpotato/core/media/_base/search/static/search.scss @@ -1,4 +1,4 @@ -@import "couchpotato/static/style/_mixins"; +@import "_mixins"; .search_form { display: inline-block; diff --git a/couchpotato/core/media/movie/_base/static/movie.scss b/couchpotato/core/media/movie/_base/static/movie.scss index 55186a9..be92895 100644 --- a/couchpotato/core/media/movie/_base/static/movie.scss +++ b/couchpotato/core/media/movie/_base/static/movie.scss @@ -1,4 +1,4 @@ -@import "couchpotato/static/style/_mixins"; +@import "_mixins"; $mass_edit_height: 44px; diff --git a/couchpotato/core/plugins/category/static/category.scss b/couchpotato/core/plugins/category/static/category.scss index dfc9497..24ba16e 100644 --- a/couchpotato/core/plugins/category/static/category.scss +++ b/couchpotato/core/plugins/category/static/category.scss @@ -1,4 +1,4 @@ -@import "couchpotato/static/style/_mixins"; +@import "_mixins"; .add_new_category { padding: 20px; diff --git a/couchpotato/core/plugins/log/static/log.scss b/couchpotato/core/plugins/log/static/log.scss index 4b4d51f..9a3dd35 100644 --- a/couchpotato/core/plugins/log/static/log.scss +++ b/couchpotato/core/plugins/log/static/log.scss @@ -1,4 +1,4 @@ -@import "couchpotato/static/style/_mixins"; +@import "_mixins"; .page.log { diff --git a/couchpotato/core/plugins/profile/static/profile.scss b/couchpotato/core/plugins/profile/static/profile.scss index 1cb9af7..6fd4577 100644 --- a/couchpotato/core/plugins/profile/static/profile.scss +++ b/couchpotato/core/plugins/profile/static/profile.scss @@ -1,4 +1,4 @@ -@import "couchpotato/static/style/_mixins"; +@import "_mixins"; .add_new_profile { padding: 20px; diff --git a/couchpotato/core/plugins/quality/static/quality.scss b/couchpotato/core/plugins/quality/static/quality.scss index cef35ec..c2aa9f9 100644 --- a/couchpotato/core/plugins/quality/static/quality.scss +++ b/couchpotato/core/plugins/quality/static/quality.scss @@ -1,4 +1,4 @@ -@import "couchpotato/static/style/_mixins"; +@import "_mixins"; .group_sizes { @@ -16,4 +16,4 @@ } } -} \ No newline at end of file +} From 8bbe4b3908caaad500f009da5bca0fda0ad30b32 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 19 Jul 2015 20:10:32 +0200 Subject: [PATCH 204/301] Charts listing on dashboard --- couchpotato/core/media/movie/_base/static/list.js | 2 +- .../core/media/movie/_base/static/movie.actions.js | 49 +++++++- .../core/media/movie/_base/static/movie.scss | 6 + couchpotato/core/media/movie/charts/main.py | 66 +++++++---- .../core/media/movie/charts/static/charts.js | 14 +-- couchpotato/core/media/movie/suggestion/main.py | 9 +- couchpotato/static/scripts/combined.base.min.js | 2 +- couchpotato/static/scripts/combined.plugins.min.js | 51 +++++++-- couchpotato/static/scripts/page/home.js | 2 +- couchpotato/static/style/combined.min.css | 124 +++++++++++---------- 10 files changed, 214 insertions(+), 111 deletions(-) diff --git a/couchpotato/core/media/movie/_base/static/list.js b/couchpotato/core/media/movie/_base/static/list.js index 68cfba4..eab834a 100644 --- a/couchpotato/core/media/movie/_base/static/list.js +++ b/couchpotato/core/media/movie/_base/static/list.js @@ -527,7 +527,7 @@ var MovieList = new Class({ getSavedView: function(){ var self = this; - return Cookie.read(self.options.identifier+'_view'); + return self.options.force_view ? self.options.view : Cookie.read(self.options.identifier+'_view'); }, search: function(){ diff --git a/couchpotato/core/media/movie/_base/static/movie.actions.js b/couchpotato/core/media/movie/_base/static/movie.actions.js index 265d682..3bd5392 100644 --- a/couchpotato/core/media/movie/_base/static/movie.actions.js +++ b/couchpotato/core/media/movie/_base/static/movie.actions.js @@ -733,7 +733,7 @@ MA.SuggestSeen = new Class({ createButton: function(){ var self = this; - return new Element('a.add', { + return new Element('a.seen', { 'text': 'Already seen', 'title': 'Already seen it!', 'events': { @@ -778,7 +778,7 @@ MA.SuggestIgnore = new Class({ createButton: function(){ var self = this; - return new Element('a.add', { + return new Element('a.ignore', { 'text': 'Ignore', 'title': 'Don\'t suggest this movie anymore', 'events': { @@ -807,6 +807,51 @@ MA.SuggestIgnore = new Class({ }); + +MA.ChartIgnore = new Class({ + + Extends: SuggestBase, + icon: 'error', + + create: function() { + var self = this; + + self.button = self.createButton(); + self.detail_button = self.createButton(); + }, + + createButton: function(){ + var self = this; + + return new Element('a.ignore', { + 'text': 'Hide', + 'title': 'Don\'t show this movie in charts', + 'events': { + 'click': self.markAsHidden.bind(self) + } + }); + + }, + + markAsHidden: function(e){ + var self = this; + (e).preventDefault(); + + Api.request('charts.ignore', { + 'data': { + 'imdb': self.getIMDB() + }, + 'onComplete': function(json){ + if(self.movie.details){ + self.movie.details.close(); + } + self.movie.destroy(); + } + }); + } + +}); + MA.Readd = new Class({ Extends: MovieAction, diff --git a/couchpotato/core/media/movie/_base/static/movie.scss b/couchpotato/core/media/movie/_base/static/movie.scss index be92895..52f6bf4 100644 --- a/couchpotato/core/media/movie/_base/static/movie.scss +++ b/couchpotato/core/media/movie/_base/static/movie.scss @@ -224,6 +224,12 @@ $mass_edit_height: 44px; } } } + + .rating .vote { + display: inline-block; + min-width: 60px; + text-align: right; + } } } diff --git a/couchpotato/core/media/movie/charts/main.py b/couchpotato/core/media/movie/charts/main.py index 5aa5679..e949a8a 100644 --- a/couchpotato/core/media/movie/charts/main.py +++ b/couchpotato/core/media/movie/charts/main.py @@ -1,5 +1,6 @@ import time -from couchpotato.core.helpers.variable import getTitle +from couchpotato import Env +from couchpotato.core.helpers.variable import getTitle, splitString from couchpotato.core.logger import CPLog from couchpotato.api import addApiView @@ -17,10 +18,7 @@ class Charts(Plugin): def __init__(self): addApiView('charts.view', self.automationView) - addEvent('app.load', self.setCrons) - - def setCrons(self): - fireEvent('schedule.interval', 'charts.update_cache', self.updateViewCache, hours = self.update_interval) + addApiView('charts.ignore', self.ignoreView) def automationView(self, force_update = False, **kwargs): @@ -31,10 +29,40 @@ class Charts(Plugin): if not charts: charts = self.updateViewCache() + ignored = splitString(Env.prop('charts_ignore', default = '')) + + # Create a list the movie/list.js can use + for chart in charts: + medias = [] + for media in chart.get('list', []): + + identifier = media.get('imdb') + if identifier in ignored: + continue + + # Cache poster + poster = media.get('images', {}).get('poster', []) + cached_poster = fireEvent('file.download', url = poster[0], single = True) if len(poster) > 0 else False + files = {'image_poster': [cached_poster] } if cached_poster else {} + + medias.append({ + 'status': 'suggested', + 'title': getTitle(media), + 'type': 'movie', + 'info': media, + 'files': files, + 'identifiers': { + 'imdb': identifier + } + }) + + chart['list'] = medias + return { 'success': True, 'count': len(charts), - 'charts': charts + 'charts': charts, + 'ignored': ignored, } def updateViewCache(self): @@ -54,20 +82,6 @@ class Charts(Plugin): chart['hide_wanted'] = self.conf('hide_wanted') chart['hide_library'] = self.conf('hide_library') - # Create a list the movie/list.js can use - medias = [] - for media in chart.get('list'): - medias.append({ - 'status': 'suggested', - 'title': getTitle(media), - 'type': 'movie', - 'info': media, - 'identifiers': { - 'imdb': media.get('imdb') - } - }) - chart['list'] = medias - self.setCache('charts_cached', charts, timeout = self.update_interval * 3600) except: log.error('Failed refreshing charts') @@ -75,3 +89,15 @@ class Charts(Plugin): self.update_in_progress = False return charts + + def ignoreView(self, imdb = None, **kwargs): + + ignored = splitString(Env.prop('charts_ignore', default = '')) + + if imdb: + ignored.append(imdb) + Env.prop('charts_ignore', ','.join(set(ignored))) + + return { + 'result': True + } diff --git a/couchpotato/core/media/movie/charts/static/charts.js b/couchpotato/core/media/movie/charts/static/charts.js index 1036840..b011613 100644 --- a/couchpotato/core/media/movie/charts/static/charts.js +++ b/couchpotato/core/media/movie/charts/static/charts.js @@ -73,7 +73,7 @@ var Charts = new Class({ 'identifier': chart.name.toLowerCase().replace(/[^a-z0-9]+/g, '_'), 'title': chart.name, 'description': 'See source', - 'actions': [MA.Add, MA.SuggestIgnore, MA.SuggestSeen, MA.IMDB, MA.Trailer], + 'actions': [MA.Add, MA.ChartIgnore, MA.IMDB, MA.Trailer], 'load_more': false, 'view': 'thumb', 'force_view': true, @@ -112,18 +112,6 @@ var Charts = new Class({ } }, - hide: function(){ - this.el.hide(); - }, - - afterAdded: function(m){ - - $(m).getElement('div.chart_number') - .addClass('chart_in_wanted') - .set('title', 'Movie in wanted list'); - - }, - toElement: function(){ return this.el; } diff --git a/couchpotato/core/media/movie/suggestion/main.py b/couchpotato/core/media/movie/suggestion/main.py index bd16f1a..5cb68a3 100755 --- a/couchpotato/core/media/movie/suggestion/main.py +++ b/couchpotato/core/media/movie/suggestion/main.py @@ -47,15 +47,18 @@ class Suggestion(Plugin): medias = [] for suggestion in suggestions[:int(limit)]: + + # Cache poster poster = suggestion.get('images', {}).get('poster', []) + cached_poster = fireEvent('file.download', url = poster[0], single = True) if len(poster) > 0 else False + files = {'image_poster': [cached_poster] } if cached_poster else {} + medias.append({ 'status': 'suggested', 'title': getTitle(suggestion), 'type': 'movie', 'info': suggestion, - 'files': { - 'image_poster': [fireEvent('file.download', url = poster[0], single = True)] - } if len(poster) > 0 else {}, + 'files': files, 'identifiers': { 'imdb': suggestion.get('imdb') } diff --git a/couchpotato/static/scripts/combined.base.min.js b/couchpotato/static/scripts/combined.base.min.js index 2963332..7dd249b 100644 --- a/couchpotato/static/scripts/combined.base.min.js +++ b/couchpotato/static/scripts/combined.base.min.js @@ -970,7 +970,7 @@ Page.Home = new Class({ return; } self.chain = new Chain(); - self.chain.chain(self.createAvailable.bind(self), self.createSoon.bind(self), self.createSuggestions.bind(self), self.createLate.bind(self)); + self.chain.chain(self.createAvailable.bind(self), self.createSoon.bind(self), self.createSuggestions.bind(self), self.createCharts.bind(self), self.createLate.bind(self)); self.chain.callChain(); }, createAvailable: function() { diff --git a/couchpotato/static/scripts/combined.plugins.min.js b/couchpotato/static/scripts/combined.plugins.min.js index 8946311..19adefd 100644 --- a/couchpotato/static/scripts/combined.plugins.min.js +++ b/couchpotato/static/scripts/combined.plugins.min.js @@ -461,7 +461,7 @@ var MovieList = new Class({ click: self.loadMore.bind(self) } }) : null); - self.changeView(self.getSavedView() || self.options.view || "thumb"); + self.changeView((self.options.force_view ? self.options.view : self.getSavedView() || self.options.view) || "thumb"); if (self.options.navigation) self.createNavigation(); if (self.options.api_call) self.getMovies(); App.on("movie.added", self.movieAdded.bind(self)); @@ -801,7 +801,7 @@ var MovieList = new Class({ }, getSavedView: function() { var self = this; - return Cookie.read(self.options.identifier + "_view"); + return self.options.force_view ? self.options.view : Cookie.read(self.options.identifier + "_view"); }, search: function() { var self = this; @@ -1533,7 +1533,7 @@ MA.SuggestSeen = new Class({ }, createButton: function() { var self = this; - return new Element("a.add", { + return new Element("a.seen", { text: "Already seen", title: "Already seen it!", events: { @@ -1569,7 +1569,7 @@ MA.SuggestIgnore = new Class({ }, createButton: function() { var self = this; - return new Element("a.add", { + return new Element("a.ignore", { text: "Ignore", title: "Don't suggest this movie anymore", events: { @@ -1594,6 +1594,41 @@ MA.SuggestIgnore = new Class({ } }); +MA.ChartIgnore = new Class({ + Extends: SuggestBase, + icon: "error", + create: function() { + var self = this; + self.button = self.createButton(); + self.detail_button = self.createButton(); + }, + createButton: function() { + var self = this; + return new Element("a.ignore", { + text: "Hide", + title: "Don't show this movie in charts", + events: { + click: self.markAsHidden.bind(self) + } + }); + }, + markAsHidden: function(e) { + var self = this; + e.preventDefault(); + Api.request("charts.ignore", { + data: { + imdb: self.getIMDB() + }, + onComplete: function(json) { + if (self.movie.details) { + self.movie.details.close(); + } + self.movie.destroy(); + } + }); + } +}); + MA.Readd = new Class({ Extends: MovieAction, create: function() { @@ -2368,7 +2403,7 @@ var Charts = new Class({ identifier: chart.name.toLowerCase().replace(/[^a-z0-9]+/g, "_"), title: chart.name, description: 'See source', - actions: [ MA.Add, MA.SuggestIgnore, MA.SuggestSeen, MA.IMDB, MA.Trailer ], + actions: [ MA.Add, MA.ChartIgnore, MA.IMDB, MA.Trailer ], load_more: false, view: "thumb", force_view: true, @@ -2395,12 +2430,6 @@ var Charts = new Class({ self.shown_once = true; } }, - hide: function() { - this.el.hide(); - }, - afterAdded: function(m) { - $(m).getElement("div.chart_number").addClass("chart_in_wanted").set("title", "Movie in wanted list"); - }, toElement: function() { return this.el; } diff --git a/couchpotato/static/scripts/page/home.js b/couchpotato/static/scripts/page/home.js index 59bbecf..d37cbc2 100644 --- a/couchpotato/static/scripts/page/home.js +++ b/couchpotato/static/scripts/page/home.js @@ -25,7 +25,7 @@ Page.Home = new Class({ self.createAvailable.bind(self), self.createSoon.bind(self), self.createSuggestions.bind(self), - //self.createCharts.bind(self), + self.createCharts.bind(self), self.createLate.bind(self) ); diff --git a/couchpotato/static/style/combined.min.css b/couchpotato/static/style/combined.min.css index 406aa23..73cd80a 100644 --- a/couchpotato/static/style/combined.min.css +++ b/couchpotato/static/style/combined.min.css @@ -1,4 +1,4 @@ -.movies>.description a:hover,.page.movie_details .releases .buttons a:hover{text-decoration:underline} +.movies>.description a:hover,.page.movie_details .releases .buttons a:hover,.page.settings fieldset h2 .hint a{text-decoration:underline} .search_form{display:inline-block;z-index:11;width:44px;position:relative} .search_form .icon-search{position:absolute;z-index:2;top:50%;left:0;height:100%;text-align:center;color:#FFF;font-size:20px;-webkit-transform:translateY(-50%);transform:translateY(-50%)} .search_form .wrapper{position:absolute;left:44px;bottom:0;background:#ac0000;border-radius:3px 0 0 3px;display:none;box-shadow:0 0 15px 2px rgba(0,0,0,.15)} @@ -86,6 +86,7 @@ .list_list .movie .data .info .eta{font-size:.8em;opacity:.5;margin-right:4px} .list_list .movie .data .info .quality{clear:both;overflow:hidden} .list_list .movie .data .info .quality span{float:left;font-size:.7em;margin:2px 0 0 2px} +.list_list .movie .data .info .rating .vote{display:inline-block;min-width:60px;text-align:right} .list_list .movie .actions{position:absolute;right:10px;top:0;bottom:0;display:none;z-index:10} .list_list .movie .actions .action{display:inline-block} .list_list .movie .actions a{height:100%;display:block;background:#FFF;padding:10px;width:auto;float:right;color:#000} @@ -139,7 +140,7 @@ @media (max-width:480px){.page.movie_details .overlay{left:0;border-radius:0} .page.movie_details .overlay .close{width:44px} } -.page.movie_details .content{position:fixed;z-index:2;top:0;bottom:0;right:0;left:176px;background:#FFF;border-radius:3px 0 0 3px;overflow-y:auto;-webkit-transform:translateX(100%) translateZ(0);transform:translateX(100%) translateZ(0);transition:-webkit-transform 350ms cubic-bezier(.9,0,.1,1);transition:transform 350ms cubic-bezier(.9,0,.1,1)} +.page.movie_details .content{position:fixed;z-index:2;top:0;bottom:0;right:0;left:176px;background:#FFF;border-radius:3px 0 0 3px;overflow-y:auto;-webkit-transform:translateX(100%)translateZ(0);transform:translateX(100%)translateZ(0);transition:-webkit-transform 350ms cubic-bezier(.9,0,.1,1);transition:transform 350ms cubic-bezier(.9,0,.1,1)} .page.movie_details .content>.head{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row nowrap;-ms-flex-flow:row nowrap;flex-flow:row nowrap;padding:0 20px;position:relative;z-index:2} @media (max-width:480px){.page.movie_details .content{left:44px} .page.movie_details .content>.head{-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:0 10px;line-height:1em} @@ -173,7 +174,7 @@ .page.movie_details .content>.head .buttons{margin-left:auto} .page.movie_details .content .section{padding:10px} } -.page.movie_details .files span,.page.movie_details .releases .item span{white-space:nowrap;text-overflow:ellipsis;overflow:hidden;padding:6.67px 0} +.page.movie_details .files span,.page.movie_details .releases .item span{text-overflow:ellipsis;overflow:hidden;white-space:nowrap;padding:6.67px 0} .page.movie_details.show{pointer-events:auto} .page.movie_details.show .overlay{opacity:1;transition-delay:0s} .page.movie_details.show .overlay .close{opacity:1;transition-delay:300ms} @@ -230,7 +231,7 @@ .page.movie_details .releases .actions{min-width:80px;max-width:80px} .page.movie_details .trailer_container{background:#000;position:relative;padding-bottom:56.25%;height:0;overflow:hidden;max-width:100%;cursor:pointer} .alph_nav .menus .button,.alph_nav .menus .counter{line-height:80px;padding:0 10px} -.page.movie_details .trailer_container .background{opacity:.3;transition:all 300ms;-webkit-transform:scale(1.05) translateZ(0);transform:scale(1.05) translateZ(0);background:center no-repeat;background-size:cover;position:absolute;top:0;right:0;bottom:0;left:0;z-index:1} +.page.movie_details .trailer_container .background{opacity:.3;transition:all 300ms;-webkit-transform:scale(1.05)translateZ(0);transform:scale(1.05)translateZ(0);background:center no-repeat;background-size:cover;position:absolute;top:0;right:0;bottom:0;left:0;z-index:1} .page.movie_details .trailer_container .icon-play{opacity:.9;position:absolute;z-index:2;text-align:center;width:100%;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);transition:all 300ms;color:#FFF;font-size:110px} @media (max-width:1024px){.page.movie_details .trailer_container .icon-play{font-size:55px} } @@ -291,6 +292,7 @@ @media all and (max-width:600px){.charts .chart{width:100%} } .charts .chart .media_result .data{left:150px;background:#4e5969;border:none} +#category_ordering li:last-child,#profile_ordering li:last-child,.api_docs .api .params tr:last-child td,.api_docs .api .params tr:last-child th{border:0} .charts .chart .media_result .data .info{top:10px;left:15px;right:15px;bottom:10px;overflow:hidden} .charts .chart .media_result .data .info h2{white-space:normal;max-height:120px;font-size:18px;line-height:18px} .charts .chart .media_result .data .info .genres,.charts .chart .media_result .data .info .rating,.charts .chart .media_result .data .info .year{position:static;display:block;padding:0;opacity:.6} @@ -309,6 +311,7 @@ .charts .chart .media_result .chart_number{color:#fff;position:absolute;top:0;padding:10px;font:700 2em/1em Helvetica,Sans-Serif;width:50px;height:100%;text-align:center;border-left:8px solid transparent} .charts .chart .media_result.chart_in_wanted .chart_number{border-color:rgba(0,255,40,.3)} .charts .chart .media_result.chart_in_library .chart_number{border-color:rgba(0,202,32,.3)} +.page.wizard .tabs li:hover a,.toggle_menu a:hover{border-color:#047792} .charts .chart .media_result .actions{position:absolute;top:10px;right:10px;display:none;width:90px} .charts .chart .media_result:hover .actions{display:block} .charts .chart .media_result:hover h2 .title{opacity:0} @@ -316,40 +319,13 @@ .charts .chart .media_result .actions a{margin-left:10px;vertical-align:middle} .toggle_menu{height:50px} .toggle_menu a{display:block;width:50%;float:left;color:rgba(255,255,255,.6);border-bottom:1px solid rgba(255,255,255,.06667)} -.toggle_menu a:hover{border-color:#047792;border-width:4px;color:#fff} +.toggle_menu a:hover{border-width:4px;color:#fff} .toggle_menu a.active{border-bottom:4px solid #04bce6;color:#fff} +#category_ordering li,#profile_ordering li,.add_new_profile{border-bottom:1px solid #eaeaea} .toggle_menu a:last-child{float:right} .toggle_menu h2{height:40px} @media all and (max-width:480px){.toggle_menu h2{font-size:16px;text-align:center;height:30px} } -.suggestions{clear:both;padding-top:10px;margin-bottom:30px} -.suggestions>h2{height:40px} -.suggestions .media_result{display:inline-block;width:33.333%;height:150px} -@media all and (max-width:960px){.suggestions .media_result{width:50%} -} -@media all and (max-width:600px){.suggestions .media_result{width:100%} -} -.suggestions .media_result .data{left:100px;background:#4e5969;border:none} -.suggestions .media_result .data .info{top:10px;left:15px;right:15px;bottom:10px;overflow:hidden} -.suggestions .media_result .data .info h2{white-space:normal;max-height:120px;font-size:18px;line-height:18px} -.suggestions .media_result .data .info .genres,.suggestions .media_result .data .info .rating,.suggestions .media_result .data .info .year{position:static;display:block;padding:0;opacity:.6} -.suggestions .media_result .data .info .year{margin:10px 0 0} -.suggestions .media_result .data .info .rating{font-size:20px;float:right;margin-top:-20px} -.suggestions .media_result .data .info .rating:before{content:"\e031";font-family:Elusive-Icons;font-size:14px;margin:0 5px 0 0;vertical-align:bottom} -.suggestions .media_result .data .info .genres{font-size:11px;font-style:italic;text-align:right} -.suggestions .media_result .data .info .plot{display:block;font-size:11px;overflow:hidden;text-align:justify;height:100%;z-index:2;top:64px;position:absolute;background:#4e5969;cursor:pointer;transition:all .4s ease-in-out;padding:0 3px 10px 0} -.suggestions .media_result .data:before{content:'';display:block;height:10px;right:0;left:0;bottom:10px;position:absolute;background:linear-gradient(0deg,#4e5969 0,rgba(78,89,105,0) 100%);z-index:3;pointer-events:none} -.suggestions .media_result .data .info .plot.full{top:0;overflow:auto} -.suggestions .media_result .data{cursor:default} -.suggestions .media_result .options{left:100px} -.suggestions .media_result .options select[name=category],.suggestions .media_result .options select[name=profile],.suggestions .media_result .options select[name=title]{width:100%} -.suggestions .media_result .button{position:absolute;margin:2px 0 0;right:15px;bottom:15px} -.suggestions .media_result .thumbnail{width:100px} -.suggestions .media_result .actions{position:absolute;top:10px;right:10px;display:none;width:140px} -.suggestions .media_result:hover .actions{display:block} -.suggestions .media_result:hover h2 .title{opacity:0} -.suggestions .media_result .data.open .actions{display:none} -.suggestions .media_result .actions a{margin-left:10px;vertical-align:middle} .add_new_category{padding:20px;display:block;text-align:center;font-size:20px} .category{margin-bottom:20px;position:relative} .category>.delete{position:absolute;padding:16px;right:0;cursor:pointer;opacity:.6;color:#fd5353} @@ -359,8 +335,7 @@ .category .formHint{opacity:.1} .category:hover .formHint{opacity:1} #category_ordering ul{float:left;margin:0;width:275px;padding:0} -#category_ordering li{cursor:-webkit-grab;cursor:grab;border-bottom:1px solid #eaeaea;padding:5px;list-style:none} -#category_ordering li:last-child{border:0} +#category_ordering li{cursor:-webkit-grab;cursor:grab;padding:5px;list-style:none} #category_ordering li .check{margin:2px 10px 0 0;vertical-align:top} #category_ordering li>span{display:inline-block;height:20px;vertical-align:top;line-height:20px} #category_ordering li .handle{width:20px;float:right} @@ -388,7 +363,7 @@ .report_popup.report_popup .bug{width:80%;height:80%;max-height:800px;max-width:800px;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:column nowrap;-ms-flex-flow:column nowrap;flex-flow:column nowrap} .report_popup.report_popup .bug textarea{display:block;width:100%;background:#FFF;padding:20px;overflow:auto;color:#666;height:70%;font-size:12px} .do_report.do_report{z-index:10000;position:absolute;padding:10px;background:#ac0000;color:#FFF} -.add_new_profile{padding:20px;display:block;text-align:center;font-size:20px;border-bottom:1px solid #eaeaea} +.add_new_profile{padding:20px;display:block;text-align:center;font-size:20px} .profile{margin-bottom:20px} .profile .quality_label input{font-weight:700} .profile .ctrlHolder .types{-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto;min-width:360px} @@ -405,9 +380,8 @@ .profile .ctrlHolder.wait_for.wait_for .advanced{display:none;color:#ac0000} .show_advanced .profile .ctrlHolder.wait_for.wait_for .advanced{display:inline} #profile_ordering ul{list-style:none;margin:0;width:275px;padding:0} -#profile_ordering li{border-bottom:1px solid #eaeaea;padding:5px;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center} +#profile_ordering li{padding:5px;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center} #profile_ordering li:hover{background:#eaeaea} -#profile_ordering li:last-child{border:0} #profile_ordering li input[type=checkbox]{margin:2px 10px 0 0;vertical-align:top} #profile_ordering li>span{display:inline-block;height:20px;vertical-align:top;line-height:20px} #profile_ordering li>span.profile_label{-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto} @@ -431,7 +405,6 @@ .page.wizard .tab_wrapper .tabs{padding:0;margin:0 auto;display:block;height:100%;width:100%;max-width:960px} .page.wizard .tabs li{display:inline-block;height:100%} .page.wizard .tabs li a{padding:20px 10px;height:100%;display:block;color:#FFF;font-weight:400;border-bottom:4px solid transparent} -.page.wizard .tabs li:hover a{border-color:#047792} .page.wizard .tabs li.done a{border-color:#04bce6} .page.wizard .tab_wrapper .pointer{border-right:10px solid transparent;border-left:10px solid transparent;border-top:10px solid #5c697b;display:block;position:absolute;top:44px} .page.wizard .tab_content{margin:20px 0 160px} @@ -447,7 +420,6 @@ .api_docs .api .params{background:#fafafa;width:100%} .api_docs .api .params h3{clear:both;float:left;width:100px} .api_docs .api .params td,.api_docs .api .params th{padding:3px 5px;border-bottom:1px solid #eee} -.api_docs .api .params tr:last-child td,.api_docs .api .params tr:last-child th{border:0} .api_docs .api .params .param{vertical-align:top} .api_docs .api .params .param th{text-align:left;width:100px} .api_docs .api .params .param .type{font-style:italic;margin-right:10px;width:100px;color:#666} @@ -466,6 +438,13 @@ .api_docs .database table textarea{font-size:12px;width:100%;height:200px} .api_docs .database table input[type=submit]{display:block} .page.login,body{display:-webkit-flex;display:-ms-flexbox} +@font-face{font-family:icons;src:url(../fonts/icons.eot?74719538);src:url(../fonts/icons.eot?74719538#iefix)format("embedded-opentype"),url(../fonts/icons.woff?74719538)format("woff"),url(../fonts/icons.ttf?74719538)format("truetype"),url(../fonts/icons.svg?74719538#icons)format("svg");font-weight:400;font-style:normal} +@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Light-webfont.eot);src:url(../fonts/OpenSans-Light-webfont.eot?#iefix)format("embedded-opentype"),url(../fonts/OpenSans-Light-webfont.woff)format("woff"),url(../fonts/OpenSans-Light-webfont.ttf)format("truetype"),url(../fonts/OpenSans-Light-webfont.svg#OpenSansRegular)format("svg");font-weight:200;font-style:normal} +@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Regular-webfont.eot);src:url(../fonts/OpenSans-Regular-webfont.eot?#iefix)format("embedded-opentype"),url(../fonts/OpenSans-Regular-webfont.woff)format("woff"),url(../fonts/OpenSans-Regular-webfont.ttf)format("truetype"),url(../fonts/OpenSans-Regular-webfont.svg#OpenSansRegular)format("svg");font-weight:400;font-style:normal} +@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Italic-webfont.eot);src:url(../fonts/OpenSans-Italic-webfont.eot?#iefix)format("embedded-opentype"),url(../fonts/OpenSans-Italic-webfont.woff)format("woff"),url(../fonts/OpenSans-Italic-webfont.ttf)format("truetype"),url(../fonts/OpenSans-Italic-webfont.svg#OpenSansItalic)format("svg");font-weight:400;font-style:italic} +@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Bold-webfont.eot);src:url(../fonts/OpenSans-Bold-webfont.eot?#iefix)format("embedded-opentype"),url(../fonts/OpenSans-Bold-webfont.woff)format("woff"),url(../fonts/OpenSans-Bold-webfont.ttf)format("truetype"),url(../fonts/OpenSans-Bold-webfont.svg#OpenSansBold)format("svg");font-weight:700;font-style:normal} +@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-BoldItalic-webfont.eot);src:url(../fonts/OpenSans-BoldItalic-webfont.eot?#iefix)format("embedded-opentype"),url(../fonts/OpenSans-BoldItalic-webfont.woff)format("woff"),url(../fonts/OpenSans-BoldItalic-webfont.ttf)format("truetype"),url(../fonts/OpenSans-BoldItalic-webfont.svg#OpenSansBoldItalic)format("svg");font-weight:700;font-style:italic} +@font-face{font-family:Lobster;src:url(../fonts/Lobster-webfont.eot);src:url(../fonts/Lobster-webfont.eot?#iefix)format("embedded-opentype"),url(../fonts/Lobster-webfont.woff2)format("woff2"),url(../fonts/Lobster-webfont.woff)format("woff"),url(../fonts/Lobster-webfont.ttf)format("truetype"),url(../fonts/Lobster-webfont.svg#lobster_14regular)format("svg");font-weight:400;font-style:normal} .page.login{background:#FFF;display:flex;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;font-size:1.25em} .page.login h1{padding:0 0 10px;font-size:60px;font-family:Lobster;font-weight:400;color:#ac0000;text-align:center} .page.login form{padding:0;width:300px} @@ -474,7 +453,7 @@ .page.login input[type=password],.page.login input[type=text]{width:100%!important} .page.login .remember_me{font-size:15px;float:left;width:150px} .page.login .button{float:right;margin:0;transition:none} -@font-face{font-family:icons;src:url(../fonts/icons.eot?74719542);src:url(../fonts/icons.eot?74719542#iefix) format("embedded-opentype"),url(../fonts/icons.woff?747195412) format("woff"),url(../fonts/icons.ttf?74719542) format("truetype"),url(../fonts/icons.svg?74719542#icons) format("svg");font-weight:400;font-style:normal} +@font-face{font-family:icons;src:url(../fonts/icons.eot?74719542);src:url(../fonts/icons.eot?74719542#iefix)format("embedded-opentype"),url(../fonts/icons.woff?747195412)format("woff"),url(../fonts/icons.ttf?74719542)format("truetype"),url(../fonts/icons.svg?74719542#icons)format("svg");font-weight:400;font-style:normal} [class*=" icon-"]:before,[class^=icon-]:before{font-family:icons;font-style:normal;font-weight:400;speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale} .icon-left-arrow:before{content:'\e800'} .icon-settings:before{content:'\e801'} @@ -505,12 +484,12 @@ .icon-star:before{content:'\e81a'} .icon-star-empty:before{content:'\e81b'} .icon-star-half:before{content:'\e81c'} -@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Light-webfont.eot);src:url(../fonts/OpenSans-Light-webfont.eot?#iefix) format("embedded-opentype"),url(../fonts/OpenSans-Light-webfont.woff) format("woff"),url(../fonts/OpenSans-Light-webfont.ttf) format("truetype"),url(../fonts/OpenSans-Light-webfont.svg#OpenSansRegular) format("svg");font-weight:200;font-style:normal} -@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Regular-webfont.eot);src:url(../fonts/OpenSans-Regular-webfont.eot?#iefix) format("embedded-opentype"),url(../fonts/OpenSans-Regular-webfont.woff) format("woff"),url(../fonts/OpenSans-Regular-webfont.ttf) format("truetype"),url(../fonts/OpenSans-Regular-webfont.svg#OpenSansRegular) format("svg");font-weight:400;font-style:normal} -@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Italic-webfont.eot);src:url(../fonts/OpenSans-Italic-webfont.eot?#iefix) format("embedded-opentype"),url(../fonts/OpenSans-Italic-webfont.woff) format("woff"),url(../fonts/OpenSans-Italic-webfont.ttf) format("truetype"),url(../fonts/OpenSans-Italic-webfont.svg#OpenSansItalic) format("svg");font-weight:400;font-style:italic} -@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Bold-webfont.eot);src:url(../fonts/OpenSans-Bold-webfont.eot?#iefix) format("embedded-opentype"),url(../fonts/OpenSans-Bold-webfont.woff) format("woff"),url(../fonts/OpenSans-Bold-webfont.ttf) format("truetype"),url(../fonts/OpenSans-Bold-webfont.svg#OpenSansBold) format("svg");font-weight:700;font-style:normal} -@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-BoldItalic-webfont.eot);src:url(../fonts/OpenSans-BoldItalic-webfont.eot?#iefix) format("embedded-opentype"),url(../fonts/OpenSans-BoldItalic-webfont.woff) format("woff"),url(../fonts/OpenSans-BoldItalic-webfont.ttf) format("truetype"),url(../fonts/OpenSans-BoldItalic-webfont.svg#OpenSansBoldItalic) format("svg");font-weight:700;font-style:italic} -@font-face{font-family:Lobster;src:url(../fonts/Lobster-webfont.eot);src:url(../fonts/Lobster-webfont.eot?#iefix) format("embedded-opentype"),url(../fonts/Lobster-webfont.woff2) format("woff2"),url(../fonts/Lobster-webfont.woff) format("woff"),url(../fonts/Lobster-webfont.ttf) format("truetype"),url(../fonts/Lobster-webfont.svg#lobster_14regular) format("svg");font-weight:400;font-style:normal} +@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Light-webfont.eot);src:url(../fonts/OpenSans-Light-webfont.eot?#iefix)format("embedded-opentype"),url(../fonts/OpenSans-Light-webfont.woff)format("woff"),url(../fonts/OpenSans-Light-webfont.ttf)format("truetype"),url(../fonts/OpenSans-Light-webfont.svg#OpenSansRegular)format("svg");font-weight:200;font-style:normal} +@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Regular-webfont.eot);src:url(../fonts/OpenSans-Regular-webfont.eot?#iefix)format("embedded-opentype"),url(../fonts/OpenSans-Regular-webfont.woff)format("woff"),url(../fonts/OpenSans-Regular-webfont.ttf)format("truetype"),url(../fonts/OpenSans-Regular-webfont.svg#OpenSansRegular)format("svg");font-weight:400;font-style:normal} +@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Italic-webfont.eot);src:url(../fonts/OpenSans-Italic-webfont.eot?#iefix)format("embedded-opentype"),url(../fonts/OpenSans-Italic-webfont.woff)format("woff"),url(../fonts/OpenSans-Italic-webfont.ttf)format("truetype"),url(../fonts/OpenSans-Italic-webfont.svg#OpenSansItalic)format("svg");font-weight:400;font-style:italic} +@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Bold-webfont.eot);src:url(../fonts/OpenSans-Bold-webfont.eot?#iefix)format("embedded-opentype"),url(../fonts/OpenSans-Bold-webfont.woff)format("woff"),url(../fonts/OpenSans-Bold-webfont.ttf)format("truetype"),url(../fonts/OpenSans-Bold-webfont.svg#OpenSansBold)format("svg");font-weight:700;font-style:normal} +@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-BoldItalic-webfont.eot);src:url(../fonts/OpenSans-BoldItalic-webfont.eot?#iefix)format("embedded-opentype"),url(../fonts/OpenSans-BoldItalic-webfont.woff)format("woff"),url(../fonts/OpenSans-BoldItalic-webfont.ttf)format("truetype"),url(../fonts/OpenSans-BoldItalic-webfont.svg#OpenSansBoldItalic)format("svg");font-weight:700;font-style:italic} +@font-face{font-family:Lobster;src:url(../fonts/Lobster-webfont.eot);src:url(../fonts/Lobster-webfont.eot?#iefix)format("embedded-opentype"),url(../fonts/Lobster-webfont.woff2)format("woff2"),url(../fonts/Lobster-webfont.woff)format("woff"),url(../fonts/Lobster-webfont.ttf)format("truetype"),url(../fonts/Lobster-webfont.svg#lobster_14regular)format("svg");font-weight:400;font-style:normal} *{margin:0;padding:0;box-sizing:border-box;text-rendering:optimizeSpeed} body,html{font-size:14px;line-height:1.5;font-family:OpenSans,"Helvetica Neue",Helvetica,Arial,Geneva,sans-serif;font-weight:300;height:100%;margin:0;padding:0;background:#111;overflow:hidden} body{display:flex;-webkit-flex-flow:row nowrap;-ms-flex-flow:row nowrap;flex-flow:row nowrap} @@ -518,8 +497,8 @@ a{display:inline-block;position:relative;overflow:hidden;text-decoration:none;cu input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#FFF;border:1px solid #b7b7b7} .button{color:#ac0000;font-weight:300;padding:5px;cursor:pointer;border:1px solid #ac0000;border-radius:3px;margin:0 5px;transition:all 150ms} .button:hover{background:#ac0000;color:#FFF} -.ripple{position:absolute;height:10px;width:10px;border-radius:50%;background:#ac0000;-webkit-transform:translate(-50%,-50%) scale(1);transform:translate(-50%,-50%) scale(1);opacity:.2;transition:all 1.5s ease} -.ripple.animate{-webkit-transform:translate(-50%,-50%) scale(100);transform:translate(-50%,-50%) scale(100);opacity:0} +.ripple{position:absolute;height:10px;width:10px;border-radius:50%;background:#ac0000;-webkit-transform:translate(-50%,-50%)scale(1);transform:translate(-50%,-50%)scale(1);opacity:.2;transition:all 1.5s ease} +.ripple.animate{-webkit-transform:translate(-50%,-50%)scale(100);transform:translate(-50%,-50%)scale(100);opacity:0} .header{width:132px;min-width:132px;position:relative;z-index:100} @media (max-width:480px){.header{width:44px;min-width:44px;z-index:21} } @@ -597,7 +576,7 @@ input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#F .more_menu{line-height:1em} .more_menu .button{font-size:24px;cursor:pointer} .more_menu .wrapper{display:none;position:absolute;right:0;background:#ac0000;z-index:5000;border-radius:3px 0 0 3px;-webkit-transform-origin:80% 0;transform-origin:80% 0} -.more_menu .wrapper:before{-webkit-transform:rotate(45deg) translateY(-60%);transform:rotate(45deg) translateY(-60%);content:'';display:block;position:absolute;background:#ac0000;height:10px;width:10px;left:-9px;bottom:11px;z-index:1;opacity:1;border-radius:3px} +.more_menu .wrapper:before{-webkit-transform:rotate(45deg)translateY(-60%);transform:rotate(45deg)translateY(-60%);content:'';display:block;position:absolute;background:#ac0000;height:10px;width:10px;left:-9px;bottom:11px;z-index:1;opacity:1;border-radius:3px} .mask,.messages{right:0;bottom:0} .more_menu .wrapper ul{background:#FFF;position:relative;z-index:2;overflow:hidden;border-radius:3px 0 0 3px} .more_menu .wrapper ul li{display:block;line-height:1em;border-top:1px solid #eaeaea} @@ -623,20 +602,20 @@ input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#F .mask .message,.mask .spinner{position:absolute;top:50%;left:50%} .mask .message{color:#FFF;text-align:center;width:320px;margin:-49px 0 0 -160px;font-size:16px} .mask .message h1{font-size:1.5em} -.mask .spinner{width:22px;height:22px;display:block;background:#fff;margin-top:-11px;margin-left:-11px;outline:transparent solid 1px;-webkit-animation:rotating 2.5s cubic-bezier(.9,0,.1,1) infinite normal;animation:rotating 2.5s cubic-bezier(.9,0,.1,1) infinite normal;-webkit-transform:scale(0);transform:scale(0)} +.mask .spinner{width:22px;height:22px;display:block;background:#fff;margin-top:-11px;margin-left:-11px;outline:transparent solid 1px;-webkit-animation:rotating 2.5s cubic-bezier(.9,0,.1,1)infinite normal;animation:rotating 2.5s cubic-bezier(.9,0,.1,1)infinite normal;-webkit-transform:scale(0);transform:scale(0)} .page.settings.active,.table .item{display:-webkit-flex;display:-ms-flexbox} .mask.with_message .spinner{margin-top:-88px} .mask.show{pointer-events:auto;opacity:1} .mask.show .spinner{-webkit-transform:scale(1);transform:scale(1)} .mask.hide{opacity:0} .mask.hide .spinner{-webkit-transform:scale(0);transform:scale(0)} -@-webkit-keyframes rotating{0%{-webkit-transform:rotate(0) scale(1.6);transform:rotate(0) scale(1.6);border-radius:1px} -48%{-webkit-transform:rotate(360deg) scale(1);transform:rotate(360deg) scale(1);border-radius:50%} -100%{-webkit-transform:rotate(720deg) scale(1.6);transform:rotate(720deg) scale(1.6);border-radius:1px} +@-webkit-keyframes rotating{0%{-webkit-transform:rotate(0)scale(1.6);transform:rotate(0)scale(1.6);border-radius:1px} +48%{-webkit-transform:rotate(360deg)scale(1);transform:rotate(360deg)scale(1);border-radius:50%} +100%{-webkit-transform:rotate(720deg)scale(1.6);transform:rotate(720deg)scale(1.6);border-radius:1px} } -@keyframes rotating{0%{-webkit-transform:rotate(0) scale(1.6);transform:rotate(0) scale(1.6);border-radius:1px} -48%{-webkit-transform:rotate(360deg) scale(1);transform:rotate(360deg) scale(1);border-radius:50%} -100%{-webkit-transform:rotate(720deg) scale(1.6);transform:rotate(720deg) scale(1.6);border-radius:1px} +@keyframes rotating{0%{-webkit-transform:rotate(0)scale(1.6);transform:rotate(0)scale(1.6);border-radius:1px} +48%{-webkit-transform:rotate(360deg)scale(1);transform:rotate(360deg)scale(1);border-radius:50%} +100%{-webkit-transform:rotate(720deg)scale(1.6);transform:rotate(720deg)scale(1.6);border-radius:1px} } .table .head{font-weight:700} .table .item{display:flex;border-bottom:1px solid rgba(0,0,0,.2)} @@ -667,7 +646,7 @@ input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#F @media (max-width:480px){.page.settings fieldset h2{display:block} .page.settings fieldset h2 .hint{margin:0;display:block} } -.page.settings fieldset h2 .hint a{font-weight:400;color:#ac0000;text-decoration:underline} +.page.settings fieldset h2 .hint a{font-weight:400;color:#ac0000} .page.settings fieldset .ctrlHolder{padding:6.67px 20px;border-bottom:1px solid #eaeaea;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row nowrap;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-align-items:center;-ms-flex-align:center;align-items:center} .page.settings fieldset .ctrlHolder:last-child{border-bottom:0} .page.settings fieldset .ctrlHolder label{display:inline-block;min-width:150px} @@ -763,4 +742,31 @@ input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#F .page.settings .directory_list .actions:last-child>span{padding:0 5px;text-shadow:none} .page.settings .directory_list .actions:last-child>.clear{left:20px;position:absolute;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);margin:0} .page.settings .directory_list .actions:last-child>.cancel{color:rgba(0,0,0,.4)} -.page.settings .directory_list .actions:last-child>.save{margin-right:0} \ No newline at end of file +.page.settings .directory_list .actions:last-child>.save{margin-right:0} +.uniForm legend{font-weight:700;font-size:100%;margin:0;padding:1.5em 0} +.uniForm .ctrlHolder{padding:1em;border-bottom:1px solid #efefef} +.uniForm .ctrlHolder.focused{background:#fffcdf} +.uniForm .buttonHolder{background:#efefef;text-align:right;margin:1.5em 0 0;padding:1.5em;border-radius:4px} +.uniForm .buttonHolder .primaryAction{padding:10px 22px;line-height:1;background:#254a86;border:1px solid #163362;font-size:12px;font-weight:700;color:#fff;border-radius:4px;box-shadow:1px 1px 0 #fff;text-shadow:-1px -1px 0 rgba(0,0,0,.25)} +.uniForm .buttonHolder .primaryAction:active{position:relative;top:1px} +.uniForm .secondaryAction{text-align:left} +.uniForm button.secondaryAction{background:0 0;border:none;color:#777;margin:1.25em 0 0;padding:0} +.uniForm .inlineLabels .label em,.uniForm .inlineLabels label em{font-style:normal;font-weight:700} +.uniForm label small{font-size:.75em;color:#777} +.uniForm .textInput,.uniForm textarea{padding:4px 2px;border:1px solid #aaa;background:#fff} +.uniForm textarea{height:12em} +.uniForm ul li label{font-size:.85em} +.uniForm .ctrlHolder .textInput:focus,.uniForm .ctrlHolder textarea:focus{outline:0} +.uniForm .formHint{font-size:.85em;color:#777} +.uniForm .inlineLabels .formHint{padding-top:.5em} +.uniForm .ctrlHolder.focused .formHint{color:#333} +.uniForm #errorMsg{background:#ffdfdf;border:1px solid #f3afb5;margin:0 0 1.5em;padding:0 1.5em;border-radius:4px;-webkit-border-radius:4px;-moz-border-radius:4px} +.uniForm #errorMsg ol{margin:0 0 1.5em;padding:0} +.uniForm #errorMsg ol li{margin:0 0 3px 1.5em;padding:7px;background:#f6bec1;position:relative;font-size:.85em;border-radius:4px;-webkit-border-radius:4px;-moz-border-radius:4px} +.uniForm .ctrlHolder.error,.uniForm .ctrlHolder.focused.error{background:#ffdfdf;border:1px solid #f3afb5;border-radius:4px;-webkit-border-radius:4px;-moz-border-radius:4px} +.uniForm .ctrlHolder.error input.error,.uniForm .ctrlHolder.error select.error,.uniForm .ctrlHolder.error textarea.error{color:#af4c4c;margin:0 0 6px;padding:4px} +.uniForm #okMsg{background:#c8ffbf;border:1px solid #a2ef95;margin:0 0 1.5em;padding:0 1.5em;text-align:center;border-radius:4px;-webkit-border-radius:4px;-moz-border-radius:4px} +.uniForm #OKMsg p{margin:0} +.uniForm .col{margin-bottom:1.5em} +.uniForm .col.first{width:49%;float:left;clear:none} +.uniForm .col.last{width:49%;float:right;clear:none;margin-right:0} \ No newline at end of file From 0d9eec259116403a6eb447f147913a70b1b9318c Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 19 Jul 2015 20:43:09 +0200 Subject: [PATCH 205/301] Don't refresh suggestion on add from chart listings --- .../core/media/movie/_base/static/movie.actions.js | 4 +++- couchpotato/core/media/movie/charts/main.py | 17 +++++++++++++++-- couchpotato/static/scripts/combined.plugins.min.js | 6 ++++-- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/couchpotato/core/media/movie/_base/static/movie.actions.js b/couchpotato/core/media/movie/_base/static/movie.actions.js index 3bd5392..2228e9b 100644 --- a/couchpotato/core/media/movie/_base/static/movie.actions.js +++ b/couchpotato/core/media/movie/_base/static/movie.actions.js @@ -699,7 +699,7 @@ MA.Add = new Class({ var self = this; var m = new BlockSearchMovieItem(self.movie.data.info, { - 'onAdded': function(){ + 'onAdded': self.movie.data.status == 'suggested' ? function(){ Api.request('suggestion.ignore', { 'data': { @@ -709,6 +709,8 @@ MA.Add = new Class({ 'onComplete': self.refresh.bind(self) }); + } : function(){ + self.movie.destroy(); } }); m.showOptions(); diff --git a/couchpotato/core/media/movie/charts/main.py b/couchpotato/core/media/movie/charts/main.py index e949a8a..b3dc965 100644 --- a/couchpotato/core/media/movie/charts/main.py +++ b/couchpotato/core/media/movie/charts/main.py @@ -1,5 +1,6 @@ import time -from couchpotato import Env +from CodernityDB.database import RecordNotFound +from couchpotato import Env, get_db from couchpotato.core.helpers.variable import getTitle, splitString from couchpotato.core.logger import CPLog @@ -22,6 +23,8 @@ class Charts(Plugin): def automationView(self, force_update = False, **kwargs): + db = get_db() + if force_update: charts = self.updateViewCache() else: @@ -40,13 +43,23 @@ class Charts(Plugin): if identifier in ignored: continue + try: + try: + in_library = db.get('media', 'imdb-%s' % identifier) + if in_library: + continue + except RecordNotFound: + pass + except: + pass + # Cache poster poster = media.get('images', {}).get('poster', []) cached_poster = fireEvent('file.download', url = poster[0], single = True) if len(poster) > 0 else False files = {'image_poster': [cached_poster] } if cached_poster else {} medias.append({ - 'status': 'suggested', + 'status': 'chart', 'title': getTitle(media), 'type': 'movie', 'info': media, diff --git a/couchpotato/static/scripts/combined.plugins.min.js b/couchpotato/static/scripts/combined.plugins.min.js index 19adefd..2622954 100644 --- a/couchpotato/static/scripts/combined.plugins.min.js +++ b/couchpotato/static/scripts/combined.plugins.min.js @@ -461,7 +461,7 @@ var MovieList = new Class({ click: self.loadMore.bind(self) } }) : null); - self.changeView((self.options.force_view ? self.options.view : self.getSavedView() || self.options.view) || "thumb"); + self.changeView(self.getSavedView() || self.options.view || "thumb"); if (self.options.navigation) self.createNavigation(); if (self.options.api_call) self.getMovies(); App.on("movie.added", self.movieAdded.bind(self)); @@ -1508,7 +1508,7 @@ MA.Add = new Class({ getDetails: function() { var self = this; var m = new BlockSearchMovieItem(self.movie.data.info, { - onAdded: function() { + onAdded: self.movie.data.status == "suggested" ? function() { Api.request("suggestion.ignore", { data: { imdb: self.movie.data.info.imdb, @@ -1516,6 +1516,8 @@ MA.Add = new Class({ }, onComplete: self.refresh.bind(self) }); + } : function() { + self.movie.destroy(); } }); m.showOptions(); From 13dce85c14f26c5d44074ac6cea13c955992f424 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 19 Jul 2015 21:06:38 +0200 Subject: [PATCH 206/301] Var typo --- couchpotato/core/media/movie/charts/main.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/couchpotato/core/media/movie/charts/main.py b/couchpotato/core/media/movie/charts/main.py index b3dc965..19e88fa 100644 --- a/couchpotato/core/media/movie/charts/main.py +++ b/couchpotato/core/media/movie/charts/main.py @@ -83,9 +83,9 @@ class Charts(Plugin): if self.update_in_progress: while self.update_in_progress: time.sleep(1) - catched_charts = self.getCache('charts_cached') - if catched_charts: - return catched_charts + cached_charts = self.getCache('charts_cached') + if cached_charts: + return cached_charts charts = [] try: From 866896faf7bbe2fa6110c9610e71403579da9a27 Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 20 Jul 2015 22:08:32 +0200 Subject: [PATCH 207/301] Cache chart listing per provider --- couchpotato/core/media/movie/charts/__init__.py | 14 ----- couchpotato/core/media/movie/charts/main.py | 39 +------------ .../media/movie/providers/automation/bluray.py | 56 ++++++++++-------- .../core/media/movie/providers/automation/imdb.py | 68 +++++++++++++--------- 4 files changed, 74 insertions(+), 103 deletions(-) diff --git a/couchpotato/core/media/movie/charts/__init__.py b/couchpotato/core/media/movie/charts/__init__.py index cc17d97..1888f85 100644 --- a/couchpotato/core/media/movie/charts/__init__.py +++ b/couchpotato/core/media/movie/charts/__init__.py @@ -21,20 +21,6 @@ config = [{ 'type': 'int', 'description': 'Maximum number of items displayed from each chart.', }, - { - 'name': 'hide_wanted', - 'default': False, - 'type': 'bool', - 'advanced': True, - 'description': 'Hide the chart movies that are already in your wanted list.', - }, - { - 'name': 'hide_library', - 'default': False, - 'type': 'bool', - 'advanced': True, - 'description': 'Hide the chart movies that are already in your library.', - }, ], }, ], diff --git a/couchpotato/core/media/movie/charts/main.py b/couchpotato/core/media/movie/charts/main.py index 19e88fa..6573eaa 100644 --- a/couchpotato/core/media/movie/charts/main.py +++ b/couchpotato/core/media/movie/charts/main.py @@ -1,11 +1,10 @@ -import time from CodernityDB.database import RecordNotFound from couchpotato import Env, get_db from couchpotato.core.helpers.variable import getTitle, splitString from couchpotato.core.logger import CPLog from couchpotato.api import addApiView -from couchpotato.core.event import addEvent,fireEvent +from couchpotato.core.event import fireEvent from couchpotato.core.plugins.base import Plugin @@ -14,9 +13,6 @@ log = CPLog(__name__) class Charts(Plugin): - update_in_progress = False - update_interval = 72 # hours - def __init__(self): addApiView('charts.view', self.automationView) addApiView('charts.ignore', self.ignoreView) @@ -25,13 +21,7 @@ class Charts(Plugin): db = get_db() - if force_update: - charts = self.updateViewCache() - else: - charts = self.getCache('charts_cached') - if not charts: - charts = self.updateViewCache() - + charts = fireEvent('automation.get_chart_list', merge = True) ignored = splitString(Env.prop('charts_ignore', default = '')) # Create a list the movie/list.js can use @@ -78,31 +68,6 @@ class Charts(Plugin): 'ignored': ignored, } - def updateViewCache(self): - - if self.update_in_progress: - while self.update_in_progress: - time.sleep(1) - cached_charts = self.getCache('charts_cached') - if cached_charts: - return cached_charts - - charts = [] - try: - self.update_in_progress = True - charts = fireEvent('automation.get_chart_list', merge = True) - for chart in charts: - chart['hide_wanted'] = self.conf('hide_wanted') - chart['hide_library'] = self.conf('hide_library') - - self.setCache('charts_cached', charts, timeout = self.update_interval * 3600) - except: - log.error('Failed refreshing charts') - - self.update_in_progress = False - - return charts - def ignoreView(self, imdb = None, **kwargs): ignored = splitString(Env.prop('charts_ignore', default = '')) diff --git a/couchpotato/core/media/movie/providers/automation/bluray.py b/couchpotato/core/media/movie/providers/automation/bluray.py index 9ae904e..ab312c0 100644 --- a/couchpotato/core/media/movie/providers/automation/bluray.py +++ b/couchpotato/core/media/movie/providers/automation/bluray.py @@ -105,39 +105,47 @@ class Bluray(Automation, RSS): return movies - def getChartList(self): - # Nearly identical to 'getIMDBids', but we don't care about minimalMovie and return all movie data (not just id) - movie_list = {'name': 'Blu-ray.com - New Releases', 'url': self.display_url, 'order': self.chart_order, 'list': []} - movie_ids = [] - max_items = int(self.conf('max_items', section='charts', default=5)) - rss_movies = self.getRSSData(self.rss_url) + cache_key = 'bluray.charts' + movie_list = { + 'name': 'Blu-ray.com - New Releases', + 'url': self.display_url, + 'order': self.chart_order, + 'list': self.getCache(cache_key) or [] + } - for movie in rss_movies: - name = self.getTextElement(movie, 'title').lower().split('blu-ray')[0].strip('(').rstrip() - year = self.getTextElement(movie, 'description').split('|')[1].strip('(').strip() + if not movie_list['list']: + movie_ids = [] + max_items = int(self.conf('max_items', section='charts', default=5)) + rss_movies = self.getRSSData(self.rss_url) - if not name.find('/') == -1: # make sure it is not a double movie release - continue + for movie in rss_movies: + name = self.getTextElement(movie, 'title').lower().split('blu-ray')[0].strip('(').rstrip() + year = self.getTextElement(movie, 'description').split('|')[1].strip('(').strip() - movie = self.search(name, year) + if not name.find('/') == -1: # make sure it is not a double movie release + continue - if movie: + movie = self.search(name, year) - if movie.get('imdb') in movie_ids: - continue + if movie: - is_movie = fireEvent('movie.is_movie', identifier = movie.get('imdb'), single = True) - if not is_movie: - continue + if movie.get('imdb') in movie_ids: + continue - movie_ids.append(movie.get('imdb')) - movie_list['list'].append( movie ) - if len(movie_list['list']) >= max_items: - break + is_movie = fireEvent('movie.is_movie', identifier = movie.get('imdb'), single = True) + if not is_movie: + continue - if not movie_list['list']: - return + movie_ids.append(movie.get('imdb')) + movie_list['list'].append( movie ) + if len(movie_list['list']) >= max_items: + break + + if not movie_list['list']: + return + + self.setCache(cache_key, movie_list['list'], timeout = 259200) return [movie_list] diff --git a/couchpotato/core/media/movie/providers/automation/imdb.py b/couchpotato/core/media/movie/providers/automation/imdb.py index 783e8f5..8e9db63 100644 --- a/couchpotato/core/media/movie/providers/automation/imdb.py +++ b/couchpotato/core/media/movie/providers/automation/imdb.py @@ -19,7 +19,7 @@ autoload = 'IMDB' class IMDB(MultiProvider): def getTypes(self): - return [IMDBWatchlist, IMDBAutomation] + return [IMDBWatchlist, IMDBAutomation, IMDBCharts] class IMDBBase(Automation, RSS): @@ -126,30 +126,6 @@ class IMDBAutomation(IMDBBase): enabled_option = 'automation_providers_enabled' - charts = { - 'theater': { - 'order': 1, - 'name': 'IMDB - Movies in Theaters', - 'url': 'http://www.imdb.com/movies-in-theaters/', - }, - 'boxoffice': { - 'order': 2, - 'name': 'IMDB - Box Office', - 'url': 'http://www.imdb.com/boxoffice/', - }, - 'rentals': { - 'order': 3, - 'name': 'IMDB - Top DVD rentals', - 'url': 'http://www.imdb.com/boxoffice/rentals', - 'type': 'json', - }, - 'top250': { - 'order': 4, - 'name': 'IMDB - Top 250 Movies', - 'url': 'http://www.imdb.com/chart/top', - }, - } - def getIMDBids(self): movies = [] @@ -175,7 +151,35 @@ class IMDBAutomation(IMDBBase): return movies + +class IMDBCharts(IMDBBase): + + charts = { + 'theater': { + 'order': 1, + 'name': 'IMDB - Movies in Theaters', + 'url': 'http://www.imdb.com/movies-in-theaters/', + }, + 'boxoffice': { + 'order': 2, + 'name': 'IMDB - Box Office', + 'url': 'http://www.imdb.com/boxoffice/', + }, + 'rentals': { + 'order': 3, + 'name': 'IMDB - Top DVD rentals', + 'url': 'http://www.imdb.com/boxoffice/rentals', + 'type': 'json', + }, + 'top250': { + 'order': 4, + 'name': 'IMDB - Top 250 Movies', + 'url': 'http://www.imdb.com/chart/top', + }, + } + def getChartList(self): + log.info('getChartList') # Nearly identical to 'getIMDBids', but we don't care about minimalMovie and return all movie data (not just id) movie_lists = [] @@ -183,12 +187,19 @@ class IMDBAutomation(IMDBBase): for name in self.charts: chart = self.charts[name].copy() - url = chart.get('url') + cache_key = 'imdb.chart_display_%s' % name if self.conf('chart_display_%s' % name): - chart['list'] = [] + cached = self.getCache(cache_key) + if cached: + chart['list'] = cached + movie_lists.append(chart) + continue + + url = chart.get('url') + chart['list'] = [] imdb_ids = self.getFromURL(url) try: @@ -206,10 +217,11 @@ class IMDBAutomation(IMDBBase): except: log.error('Failed loading IMDB chart results from %s: %s', (url, traceback.format_exc())) + self.setCache(cache_key, chart['list'], timeout = 259200) + if chart['list']: movie_lists.append(chart) - return movie_lists From 56100103ed27a9838934f4fc152fa2d05639aa85 Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 20 Jul 2015 22:23:01 +0200 Subject: [PATCH 208/301] Use different shell runner --- Gruntfile.js | 7 ++++++- package.json | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 4d7b1b5..b4683aa 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -4,6 +4,8 @@ module.exports = function(grunt){ require('jit-grunt')(grunt); require('time-grunt')(grunt); + grunt.loadNpmTasks('grunt-shell-spawn'); + // Configurable paths var config = { tmp: '.tmp', @@ -137,7 +139,10 @@ module.exports = function(grunt){ shell: { runCouchPotato: { command: 'python CouchPotato.py', - maxBuffer: 1048576 + options: { + stdout: true, + stderr: true + } } }, diff --git a/package.json b/package.json index 1bd816f..c73d92a 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "grunt-contrib-sass": "^0.9.2", "grunt-contrib-uglify": "~0.9.1", "grunt-contrib-watch": "~0.6.1", - "grunt-shell": "^1.1.2", + "grunt-shell-spawn": "^0.3.8", "jit-grunt": "^0.9.1", "jshint-stylish": "^2.0.1", "time-grunt": "^1.2.1" From 4ee48ba01151094e9e0dd33d9534ad15d3304ae4 Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 20 Jul 2015 22:23:38 +0200 Subject: [PATCH 209/301] Remove log --- couchpotato/core/media/movie/providers/automation/imdb.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/couchpotato/core/media/movie/providers/automation/imdb.py b/couchpotato/core/media/movie/providers/automation/imdb.py index 8e9db63..4a5d07a 100644 --- a/couchpotato/core/media/movie/providers/automation/imdb.py +++ b/couchpotato/core/media/movie/providers/automation/imdb.py @@ -179,8 +179,6 @@ class IMDBCharts(IMDBBase): } def getChartList(self): - log.info('getChartList') - # Nearly identical to 'getIMDBids', but we don't care about minimalMovie and return all movie data (not just id) movie_lists = [] max_items = int(self.conf('max_items', section = 'charts', default=5)) From 96e8496a6e85ea89f85a07d2718bfecdd4d1947d Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 20 Jul 2015 22:28:23 +0200 Subject: [PATCH 210/301] Loading flashing when not needed --- couchpotato/core/media/movie/_base/static/list.js | 4 +++- couchpotato/static/scripts/combined.plugins.min.js | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/couchpotato/core/media/movie/_base/static/list.js b/couchpotato/core/media/movie/_base/static/list.js index eab834a..2644304 100644 --- a/couchpotato/core/media/movie/_base/static/list.js +++ b/couchpotato/core/media/movie/_base/static/list.js @@ -566,6 +566,7 @@ var MovieList = new Class({ self.load_more.set('text', 'loading...'); } + var loader_timeout; if(self.movies.length === 0 && self.options.loader){ self.loader_first = new Element('div.mask.loading.with_message').adopt( @@ -574,7 +575,7 @@ var MovieList = new Class({ createSpinner(self.loader_first); var lfc = self.loader_first; - setTimeout(function(){ + loader_timeout = setTimeout(function(){ lfc.addClass('show'); }, 10); @@ -593,6 +594,7 @@ var MovieList = new Class({ if(reset) self.movie_list.empty(); + if(loader_timeout) clearTimeout(loader_timeout); if(self.loader_first){ var lf = self.loader_first; self.loader_first = null; diff --git a/couchpotato/static/scripts/combined.plugins.min.js b/couchpotato/static/scripts/combined.plugins.min.js index 2622954..1b4fa07 100644 --- a/couchpotato/static/scripts/combined.plugins.min.js +++ b/couchpotato/static/scripts/combined.plugins.min.js @@ -827,13 +827,14 @@ var MovieList = new Class({ self.scrollspy.stop(); self.load_more.set("text", "loading..."); } + var loader_timeout; if (self.movies.length === 0 && self.options.loader) { self.loader_first = new Element("div.mask.loading.with_message").adopt(new Element("div.message", { text: self.options.title ? "Loading '" + self.options.title + "'" : "Loading..." })).inject(self.el, "top"); createSpinner(self.loader_first); var lfc = self.loader_first; - setTimeout(function() { + loader_timeout = setTimeout(function() { lfc.addClass("show"); }, 10); self.el.setStyle("min-height", 220); @@ -846,6 +847,7 @@ var MovieList = new Class({ }, self.filter), onSuccess: function(json) { if (reset) self.movie_list.empty(); + if (loader_timeout) clearTimeout(loader_timeout); if (self.loader_first) { var lf = self.loader_first; self.loader_first = null; From 2bb1c1022824098054ebf0948c2e4a4fcea01c58 Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 22 Jul 2015 23:01:50 +0200 Subject: [PATCH 211/301] Search in_wanted styling --- .../core/media/_base/search/static/search.scss | 78 +++++++++++++------ .../core/media/movie/_base/static/search.js | 90 ++++++++++++---------- couchpotato/static/scripts/combined.plugins.min.js | 50 ++++++------ couchpotato/static/style/combined.min.css | 28 ++++--- 4 files changed, 147 insertions(+), 99 deletions(-) diff --git a/couchpotato/core/media/_base/search/static/search.scss b/couchpotato/core/media/_base/search/static/search.scss index 4ff6225..6aad6ac 100644 --- a/couchpotato/core/media/_base/search/static/search.scss +++ b/couchpotato/core/media/_base/search/static/search.scss @@ -120,12 +120,12 @@ top: 0; left: 30px; right: 0; - padding: 10px; background: rgba(0,0,0,.3); + display: flex; + align-items: center; @include media-phablet { left: 0; - padding: 10px 2px; } > .in_library_wanted { @@ -135,18 +135,19 @@ > div { border: 0; display: flex; - } + padding: 10px; + align-items: stretch; + justify-content: space-between; - .thumbnail { - vertical-align: middle; + @include media-phablet { + padding: 3px; + } } select { - vertical-align: middle; - display: inline-block; - margin-right: 10px; - min-width: 70px; - flex: 1 auto; + display: block; + height: 100%; + width: 100%; @include media-phablet { min-width: 0; @@ -154,19 +155,39 @@ } } - .button { + .title { + margin-right: 5px; + width: 210px; + + @include media-phablet { + width: 140px; + margin-right: 2px; + } + } + + .profile, .category { + margin: 0 5px 0 0; + + @include media-phablet { + margin-right: 2px; + } + } + + .add { width: 42px; - vertical-align: middle; - display: inline-block; + flex: 1 auto; + } + + .button { + display: block; + background: $primary_color; text-align: center; margin: 0; } .message { - height: 100%; font-size: 20px; color: #fff; - line-height: 20px; } } @@ -199,16 +220,12 @@ left: 0; } - &.open { - transform: translateX(100%); + &:hover { + transform: translateX(2%); } - .in_wanted, - .in_library { - position: absolute; - bottom: 2px; - left: 14px; - font-size: 11px; + &.open { + transform: translateX(100%); } .info { @@ -242,6 +259,21 @@ width: auto; display: none; } + + .in_wanted, + .in_library { + position: absolute; + top: 15px; + left: 0; + font-size: 11px; + color: $primary_color; + } + + &.in_library_wanted { + .title { + margin-top: -7px; + } + } } } } diff --git a/couchpotato/core/media/movie/_base/static/search.js b/couchpotato/core/media/movie/_base/static/search.js index 864955b..8bcc50f 100644 --- a/couchpotato/core/media/movie/_base/static/search.js +++ b/couchpotato/core/media/movie/_base/static/search.js @@ -16,8 +16,21 @@ var BlockSearchMovieItem = new Class({ var self = this, info = self.info; + var in_library; + if(info.in_library){ + in_library = []; + (info.in_library.releases || []).each(function(release){ + in_library.include(release.quality); + }); + } + self.el = new Element('div.media_result', { - 'id': info.imdb + 'id': info.imdb, + 'events': { + 'click': self.showOptions.bind(self)//, + //'mouseenter': self.showOptions.bind(self), + //'mouseleave': self.closeOptions.bind(self) + } }).adopt( self.thumbnail = info.images && info.images.poster.length > 0 ? new Element('img.thumbnail', { 'src': info.images.poster[0], @@ -25,13 +38,10 @@ var BlockSearchMovieItem = new Class({ 'width': null }) : null, self.options_el = new Element('div.options'), - self.data_container = new Element('div.data', { - 'events': { - 'click': self.showOptions.bind(self) - } - }).adopt( + self.data_container = new Element('div.data').adopt( self.info_container = new Element('div.info').adopt( new Element('h2', { + 'class': info.in_wanted && info.in_wanted.profile_id || in_library ? 'in_library_wanted' : '', 'title': self.getTitle() }).adopt( self.title = new Element('span.title', { @@ -39,7 +49,12 @@ var BlockSearchMovieItem = new Class({ }), self.year = info.year ? new Element('span.year', { 'text': info.year - }) : null + }) : null, + info.in_wanted && info.in_wanted.profile_id ? new Element('span.in_wanted', { + 'text': 'Already in wanted list: ' + Quality.getProfile(info.in_wanted.profile_id).get('label') + }) : (in_library ? new Element('span.in_library', { + 'text': 'Already in library: ' + in_library.join(', ') + }) : null) ) ) ) @@ -134,40 +149,33 @@ var BlockSearchMovieItem = new Class({ if(!self.options_el.hasClass('set')){ - var in_library; - if(info.in_library){ - in_library = []; - (info.in_library.releases || []).each(function(release){ - in_library.include(release.quality); - }); - } - self.options_el.grab( - new Element('div', { - 'class': info.in_wanted && info.in_wanted.profile_id || in_library ? 'in_library_wanted' : '' - }).adopt( - info.in_wanted && info.in_wanted.profile_id ? new Element('span.in_wanted', { - 'text': 'Already in wanted list: ' + Quality.getProfile(info.in_wanted.profile_id).get('label') - }) : (in_library ? new Element('span.in_library', { - 'text': 'Already in library: ' + in_library.join(', ') - }) : null), - self.title_select = new Element('select', { - 'name': 'title' - }), - self.profile_select = new Element('select', { - 'name': 'profile' - }), - self.category_select = new Element('select', { - 'name': 'category' - }).grab( - new Element('option', {'value': -1, 'text': 'None'}) + new Element('div').adopt( + new Element('div.title').grab( + self.title_select = new Element('select', { + 'name': 'title' + }) ), - self.add_button = new Element('a.button', { - 'text': 'Add', - 'events': { - 'click': self.add.bind(self) - } - }) + new Element('div.profile').grab( + self.profile_select = new Element('select', { + 'name': 'profile' + }) + ), + self.category_select_container = new Element('div.category').grab( + self.category_select = new Element('select', { + 'name': 'category' + }).grab( + new Element('option', {'value': -1, 'text': 'None'}) + ) + ), + new Element('div.add').grab( + self.add_button = new Element('a.button', { + 'text': 'Add', + 'events': { + 'click': self.add.bind(self) + } + }) + ) ) ); @@ -182,9 +190,9 @@ var BlockSearchMovieItem = new Class({ var categories = CategoryList.getAll(); if(categories.length === 0) - self.category_select.hide(); + self.category_select_container.hide(); else { - self.category_select.show(); + self.category_select_container.show(); categories.each(function(category){ new Element('option', { 'value': category.data._id, diff --git a/couchpotato/static/scripts/combined.plugins.min.js b/couchpotato/static/scripts/combined.plugins.min.js index 1b4fa07..e372230 100644 --- a/couchpotato/static/scripts/combined.plugins.min.js +++ b/couchpotato/static/scripts/combined.plugins.min.js @@ -2116,22 +2116,33 @@ var BlockSearchMovieItem = new Class({ }, create: function() { var self = this, info = self.info; + var in_library; + if (info.in_library) { + in_library = []; + (info.in_library.releases || []).each(function(release) { + in_library.include(release.quality); + }); + } self.el = new Element("div.media_result", { - id: info.imdb + id: info.imdb, + events: { + click: self.showOptions.bind(self) + } }).adopt(self.thumbnail = info.images && info.images.poster.length > 0 ? new Element("img.thumbnail", { src: info.images.poster[0], height: null, width: null - }) : null, self.options_el = new Element("div.options"), self.data_container = new Element("div.data", { - events: { - click: self.showOptions.bind(self) - } - }).adopt(self.info_container = new Element("div.info").adopt(new Element("h2", { + }) : null, self.options_el = new Element("div.options"), self.data_container = new Element("div.data").adopt(self.info_container = new Element("div.info").adopt(new Element("h2", { + class: info.in_wanted && info.in_wanted.profile_id || in_library ? "in_library_wanted" : "", title: self.getTitle() }).adopt(self.title = new Element("span.title", { text: self.getTitle() }), self.year = info.year ? new Element("span.year", { text: info.year + }) : null, info.in_wanted && info.in_wanted.profile_id ? new Element("span.in_wanted", { + text: "Already in wanted list: " + Quality.getProfile(info.in_wanted.profile_id).get("label") + }) : in_library ? new Element("span.in_library", { + text: "Already in library: " + in_library.join(", ") }) : null)))); if (info.titles) info.titles.each(function(title) { self.alternativeTitle({ @@ -2196,42 +2207,29 @@ var BlockSearchMovieItem = new Class({ createOptions: function() { var self = this, info = self.info; if (!self.options_el.hasClass("set")) { - var in_library; - if (info.in_library) { - in_library = []; - (info.in_library.releases || []).each(function(release) { - in_library.include(release.quality); - }); - } - self.options_el.grab(new Element("div", { - class: info.in_wanted && info.in_wanted.profile_id || in_library ? "in_library_wanted" : "" - }).adopt(info.in_wanted && info.in_wanted.profile_id ? new Element("span.in_wanted", { - text: "Already in wanted list: " + Quality.getProfile(info.in_wanted.profile_id).get("label") - }) : in_library ? new Element("span.in_library", { - text: "Already in library: " + in_library.join(", ") - }) : null, self.title_select = new Element("select", { + self.options_el.grab(new Element("div").adopt(new Element("div.title").grab(self.title_select = new Element("select", { name: "title" - }), self.profile_select = new Element("select", { + })), new Element("div.profile").grab(self.profile_select = new Element("select", { name: "profile" - }), self.category_select = new Element("select", { + })), self.category_select_container = new Element("div.category").grab(self.category_select = new Element("select", { name: "category" }).grab(new Element("option", { value: -1, text: "None" - })), self.add_button = new Element("a.button", { + }))), new Element("div.add").grab(self.add_button = new Element("a.button", { text: "Add", events: { click: self.add.bind(self) } - }))); + })))); Array.each(self.alternative_titles, function(alt) { new Element("option", { text: alt.title }).inject(self.title_select); }); var categories = CategoryList.getAll(); - if (categories.length === 0) self.category_select.hide(); else { - self.category_select.show(); + if (categories.length === 0) self.category_select_container.hide(); else { + self.category_select_container.show(); categories.each(function(category) { new Element("option", { value: category.data._id, diff --git a/couchpotato/static/style/combined.min.css b/couchpotato/static/style/combined.min.css index 73cd80a..c38ef51 100644 --- a/couchpotato/static/style/combined.min.css +++ b/couchpotato/static/style/combined.min.css @@ -13,29 +13,39 @@ .search_form .results_container{min-height:50px;text-align:left;position:relative;left:4px;display:none;background:#FFF;border-radius:3px 0 0;overflow:hidden} .search_form .results_container .results{max-height:280px;overflow-x:hidden} .search_form .results_container .results .media_result{overflow:hidden;height:50px;position:relative} -.search_form .results_container .results .media_result .options{position:absolute;height:100%;top:0;left:30px;right:0;padding:10px;background:rgba(0,0,0,.3)} +.search_form .results_container .results .media_result .options{position:absolute;height:100%;top:0;left:30px;right:0;background:rgba(0,0,0,.3);display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center} .search_form .results_container .results .media_result .options>.in_library_wanted{margin-top:-7px} -.search_form .results_container .results .media_result .options>div{border:0;display:-webkit-flex;display:-ms-flexbox;display:flex} -.search_form .results_container .results .media_result .options .thumbnail{vertical-align:middle} -.search_form .results_container .results .media_result .options select{vertical-align:middle;display:inline-block;margin-right:10px;min-width:70px;-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto} +.search_form .results_container .results .media_result .options>div{border:0;display:-webkit-flex;display:-ms-flexbox;display:flex;padding:10px;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between} @media (max-width:480px){.search_form.focused .wrapper,.search_form.shown .wrapper{width:260px} .search_form .results_container .results .media_result{font-size:12px} -.search_form .results_container .results .media_result .options{left:0;padding:10px 2px} -.search_form .results_container .results .media_result .options select{min-width:0;margin-right:2px} +.search_form .results_container .results .media_result .options{left:0} +.search_form .results_container .results .media_result .options>div{padding:3px} } -.search_form .results_container .results .media_result .options .button{width:42px;vertical-align:middle;display:inline-block;text-align:center;margin:0} -.search_form .results_container .results .media_result .options .message{height:100%;font-size:20px;color:#fff;line-height:20px} +.search_form .results_container .results .media_result .options select{display:block;height:100%;width:100%} +@media (max-width:480px){.search_form .results_container .results .media_result .options select{min-width:0;margin-right:2px} +} +.search_form .results_container .results .media_result .options .title{margin-right:5px;width:210px} +@media (max-width:480px){.search_form .results_container .results .media_result .options .title{width:140px;margin-right:2px} +} +.search_form .results_container .results .media_result .options .category,.search_form .results_container .results .media_result .options .profile{margin:0 5px 0 0} +@media (max-width:480px){.search_form .results_container .results .media_result .options .category,.search_form .results_container .results .media_result .options .profile{margin-right:2px} +} +.search_form .results_container .results .media_result .options .add{width:42px;-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto} +.search_form .results_container .results .media_result .options .button{display:block;background:#ac0000;text-align:center;margin:0} +.search_form .results_container .results .media_result .options .message{font-size:20px;color:#fff} .search_form .results_container .results .media_result .thumbnail{width:30px;min-height:100%;display:block;margin:0;vertical-align:top} .search_form .results_container .results .media_result .data{position:absolute;height:100%;top:0;left:30px;right:0;cursor:pointer;border-top:1px solid rgba(255,255,255,.08);transition:all .4s cubic-bezier(.9,0,.1,1);-webkit-transform:translateX(0);transform:translateX(0);background:#FFF} @media (max-width:480px){.search_form .results_container .results .media_result .thumbnail{display:none} .search_form .results_container .results .media_result .data{left:0} } +.search_form .results_container .results .media_result .data:hover{-webkit-transform:translateX(2%);transform:translateX(2%)} .search_form .results_container .results .media_result .data.open{-webkit-transform:translateX(100%);transform:translateX(100%)} -.search_form .results_container .results .media_result .data .in_library,.search_form .results_container .results .media_result .data .in_wanted{position:absolute;bottom:2px;left:14px;font-size:11px} .search_form .results_container .results .media_result .data .info{position:absolute;top:20%;left:15px;right:7px;vertical-align:middle} .search_form .results_container .results .media_result .data .info h2{margin:0;font-weight:300;font-size:1.25em;padding:0;position:absolute;width:100%;display:-webkit-flex;display:-ms-flexbox;display:flex} .search_form .results_container .results .media_result .data .info h2 .title{display:inline-block;margin:0;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto} .search_form .results_container .results .media_result .data .info h2 .year{opacity:.4;padding:0 5px;width:auto;display:none} +.search_form .results_container .results .media_result .data .info h2 .in_library,.search_form .results_container .results .media_result .data .info h2 .in_wanted{position:absolute;top:15px;left:0;font-size:11px;color:#ac0000} +.search_form .results_container .results .media_result .data .info h2.in_library_wanted .title{margin-top:-7px} .search_form .results_container .results .media_result:hover .info h2 .year{display:inline-block} .search_form .results_container .results .media_result:last-child .data{border-bottom:0} .search_form.focused.filled .results_container,.search_form.shown.filled .results_container{display:block} From 947961101a429364c36250f5d04ba7d5c20ddb34 Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 22 Jul 2015 23:11:09 +0200 Subject: [PATCH 212/301] Add movie styling in movie details --- .../core/media/movie/_base/static/movie.scss | 44 ++++++++++++++-------- couchpotato/static/style/combined.min.css | 13 ++++--- 2 files changed, 35 insertions(+), 22 deletions(-) diff --git a/couchpotato/core/media/movie/_base/static/movie.scss b/couchpotato/core/media/movie/_base/static/movie.scss index 52f6bf4..ffeb36c 100644 --- a/couchpotato/core/media/movie/_base/static/movie.scss +++ b/couchpotato/core/media/movie/_base/static/movie.scss @@ -706,29 +706,41 @@ $mass_edit_height: 44px; .options > div { display: flex; + align-items: center; select { - vertical-align: middle; - display: inline-block; - margin-right: 10px; - min-width: 70px; - flex: 1 auto; + display: block; + width: 100%; + } - @include media-phablet { - min-width: 0; - } + .title { + min-width: 75px; + width: 2000px; + margin: 0 10px 0 0; } - .button { - background: #FFF; - flex: 1 auto; - vertical-align: middle; - display: inline-block; - text-align: center; + .profile, .category { + width: 200px; + min-width: 50px; + margin: 0 10px 0 0; + } - &:hover { - background: $primary_color; + .add { + width: 200px; + + .button { + background: #FFF; + flex: 1 auto; + display: block; + text-align: center; + width: 100%; + margin: 0; + + &:hover { + background: $primary_color; + } } + } } diff --git a/couchpotato/static/style/combined.min.css b/couchpotato/static/style/combined.min.css index c38ef51..428a0aa 100644 --- a/couchpotato/static/style/combined.min.css +++ b/couchpotato/static/style/combined.min.css @@ -190,12 +190,13 @@ .page.movie_details.show .overlay .close{opacity:1;transition-delay:300ms} .page.movie_details.show .content{transition-delay:200ms;-webkit-transform:translateX(0);transform:translateX(0)} .page.movie_details .section_add{background:#eaeaea} -.page.movie_details .section_add .options>div{display:-webkit-flex;display:-ms-flexbox;display:flex} -.page.movie_details .section_add .options>div select{vertical-align:middle;display:inline-block;margin-right:10px;min-width:70px;-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto} -@media (max-width:480px){.page.movie_details .section_add .options>div select{min-width:0} -} -.page.movie_details .section_add .options>div .button{background:#FFF;-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto;vertical-align:middle;display:inline-block;text-align:center} -.page.movie_details .section_add .options>div .button:hover{background:#ac0000} +.page.movie_details .section_add .options>div{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center} +.page.movie_details .section_add .options>div select{display:block;width:100%} +.page.movie_details .section_add .options>div .title{min-width:75px;width:2000px;margin:0 10px 0 0} +.page.movie_details .section_add .options>div .category,.page.movie_details .section_add .options>div .profile{width:200px;min-width:50px;margin:0 10px 0 0} +.page.movie_details .section_add .options>div .add{width:200px} +.page.movie_details .section_add .options>div .add .button{background:#FFF;-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto;display:block;text-align:center;width:100%;margin:0} +.page.movie_details .section_add .options>div .add .button:hover{background:#ac0000} .page.movie_details .section_add .data,.page.movie_details .section_add .thumbnail{display:none} .page.movie_details .files span{text-align:center} .page.movie_details .files .name{text-align:left;-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto} From c2120ad0f87c1c936565674c456cf2267cd87953 Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 22 Jul 2015 23:46:41 +0200 Subject: [PATCH 213/301] Mobile IE fixes --- couchpotato/core/media/_base/search/static/search.scss | 4 ++++ couchpotato/static/style/combined.min.css | 10 ++++++---- couchpotato/static/style/main.scss | 5 +++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/couchpotato/core/media/_base/search/static/search.scss b/couchpotato/core/media/_base/search/static/search.scss index 6aad6ac..a4c26f2 100644 --- a/couchpotato/core/media/_base/search/static/search.scss +++ b/couchpotato/core/media/_base/search/static/search.scss @@ -6,6 +6,10 @@ width: 44px; position: relative; + * { + transform: translateZ(0); + } + .icon-search { position: absolute; z-index: 2; diff --git a/couchpotato/static/style/combined.min.css b/couchpotato/static/style/combined.min.css index 428a0aa..1f83237 100644 --- a/couchpotato/static/style/combined.min.css +++ b/couchpotato/static/style/combined.min.css @@ -1,5 +1,6 @@ .movies>.description a:hover,.page.movie_details .releases .buttons a:hover,.page.settings fieldset h2 .hint a{text-decoration:underline} .search_form{display:inline-block;z-index:11;width:44px;position:relative} +.search_form *{-webkit-transform:translateZ(0);transform:translateZ(0)} .search_form .icon-search{position:absolute;z-index:2;top:50%;left:0;height:100%;text-align:center;color:#FFF;font-size:20px;-webkit-transform:translateY(-50%);transform:translateY(-50%)} .search_form .wrapper{position:absolute;left:44px;bottom:0;background:#ac0000;border-radius:3px 0 0 3px;display:none;box-shadow:0 0 15px 2px rgba(0,0,0,.15)} .search_form .wrapper:before{-webkit-transform:rotate(45deg);transform:rotate(45deg);content:'';display:block;position:absolute;height:10px;width:10px;background:#ac0000;left:-6px;bottom:16px;z-index:1} @@ -143,7 +144,8 @@ @media (max-width:480px){.thumb_list .movie:hover .actions{display:none} .page.movie_details{left:0} } -.page.movie_details .overlay{position:fixed;top:0;bottom:0;right:0;left:132px;background:rgba(0,0,0,.6);border-radius:3px 0 0 3px;opacity:0;-webkit-transform:translateZ(0);transform:translateZ(0);-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:opacity 300ms ease 400ms;z-index:1} +.page.movie_details .overlay{position:fixed;top:0;bottom:0;right:0;left:132px;background:rgba(0,0,0,.6);border-radius:3px 0 0 3px;opacity:0;-webkit-transform:translateZ(0);transform:translateZ(0);backface-visibility:hidden;transition:opacity 300ms ease 400ms;z-index:1} +*,.page.movie_details .overlay{-webkit-backface-visibility:hidden} .page.movie_details .overlay .ripple{background:#FFF} .page.movie_details .overlay .close{display:inline-block;text-align:center;font-size:60px;line-height:80px;color:#FFF;width:100%;height:100%;opacity:0;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:opacity 300ms ease 200ms} .page.movie_details .overlay .close:before{display:block;width:44px} @@ -501,15 +503,15 @@ @font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Bold-webfont.eot);src:url(../fonts/OpenSans-Bold-webfont.eot?#iefix)format("embedded-opentype"),url(../fonts/OpenSans-Bold-webfont.woff)format("woff"),url(../fonts/OpenSans-Bold-webfont.ttf)format("truetype"),url(../fonts/OpenSans-Bold-webfont.svg#OpenSansBold)format("svg");font-weight:700;font-style:normal} @font-face{font-family:OpenSans;src:url(../fonts/OpenSans-BoldItalic-webfont.eot);src:url(../fonts/OpenSans-BoldItalic-webfont.eot?#iefix)format("embedded-opentype"),url(../fonts/OpenSans-BoldItalic-webfont.woff)format("woff"),url(../fonts/OpenSans-BoldItalic-webfont.ttf)format("truetype"),url(../fonts/OpenSans-BoldItalic-webfont.svg#OpenSansBoldItalic)format("svg");font-weight:700;font-style:italic} @font-face{font-family:Lobster;src:url(../fonts/Lobster-webfont.eot);src:url(../fonts/Lobster-webfont.eot?#iefix)format("embedded-opentype"),url(../fonts/Lobster-webfont.woff2)format("woff2"),url(../fonts/Lobster-webfont.woff)format("woff"),url(../fonts/Lobster-webfont.ttf)format("truetype"),url(../fonts/Lobster-webfont.svg#lobster_14regular)format("svg");font-weight:400;font-style:normal} -*{margin:0;padding:0;box-sizing:border-box;text-rendering:optimizeSpeed} +*{margin:0;padding:0;box-sizing:border-box;text-rendering:optimizeSpeed;backface-visibility:hidden} body,html{font-size:14px;line-height:1.5;font-family:OpenSans,"Helvetica Neue",Helvetica,Arial,Geneva,sans-serif;font-weight:300;height:100%;margin:0;padding:0;background:#111;overflow:hidden} body{display:flex;-webkit-flex-flow:row nowrap;-ms-flex-flow:row nowrap;flex-flow:row nowrap} a{display:inline-block;position:relative;overflow:hidden;text-decoration:none;cursor:pointer;-webkit-tap-highlight-color:transparent} input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#FFF;border:1px solid #b7b7b7} .button{color:#ac0000;font-weight:300;padding:5px;cursor:pointer;border:1px solid #ac0000;border-radius:3px;margin:0 5px;transition:all 150ms} .button:hover{background:#ac0000;color:#FFF} -.ripple{position:absolute;height:10px;width:10px;border-radius:50%;background:#ac0000;-webkit-transform:translate(-50%,-50%)scale(1);transform:translate(-50%,-50%)scale(1);opacity:.2;transition:all 1.5s ease} -.ripple.animate{-webkit-transform:translate(-50%,-50%)scale(100);transform:translate(-50%,-50%)scale(100);opacity:0} +.ripple{position:absolute;height:10px;width:10px;border-radius:50%;background:#ac0000;-webkit-transform:translate(-50%,-50%)scale(1)translateZ(0);transform:translate(-50%,-50%)scale(1)translateZ(0);opacity:.2;transition:all 1.5s ease} +.ripple.animate{-webkit-transform:translate(-50%,-50%)scale(100)translateZ(0);transform:translate(-50%,-50%)scale(100)translateZ(0);opacity:0} .header{width:132px;min-width:132px;position:relative;z-index:100} @media (max-width:480px){.header{width:44px;min-width:44px;z-index:21} } diff --git a/couchpotato/static/style/main.scss b/couchpotato/static/style/main.scss index fdb2791..aaf288f 100644 --- a/couchpotato/static/style/main.scss +++ b/couchpotato/static/style/main.scss @@ -6,6 +6,7 @@ padding: 0; box-sizing: border-box; text-rendering: optimizeSpeed; + backface-visibility: hidden; } body, html { @@ -65,13 +66,13 @@ input, textarea, select { width: 10px; border-radius: 50%; background: $primary_color; - transform: translate(-50%, -50%) scale(1); + transform: translate(-50%, -50%) scale(1) translateZ(0); opacity: 0.2; transition: all 1.5s ease; pointer-events: none; &.animate { - transform: translate(-50%, -50%) scale(100); + transform: translate(-50%, -50%) scale(100) translateZ(0); opacity: 0; } } From 1b3e837ef04c5d7564c4d14faa4b806eff30cf7b Mon Sep 17 00:00:00 2001 From: Ruud Date: Thu, 23 Jul 2015 11:40:48 +0200 Subject: [PATCH 214/301] Chrome and iOS fixes --- Gruntfile.js | 3 +- .../core/media/movie/_base/static/movie.scss | 5 + couchpotato/static/scripts/combined.base.min.js | 5 +- couchpotato/static/scripts/combined.vendor.min.js | 410 +++++++++- couchpotato/static/scripts/couchpotato.js | 4 +- couchpotato/static/scripts/vendor/fastclick.js | 841 +++++++++++++++++++++ couchpotato/static/style/combined.min.css | 5 +- 7 files changed, 1267 insertions(+), 6 deletions(-) create mode 100644 couchpotato/static/scripts/vendor/fastclick.js diff --git a/Gruntfile.js b/Gruntfile.js index b4683aa..bb6f230 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -25,7 +25,8 @@ module.exports = function(grunt){ 'couchpotato/static/scripts/vendor/form_replacement/form_selectoption.js', 'couchpotato/static/scripts/vendor/Array.stableSort.js', 'couchpotato/static/scripts/vendor/history.js', - 'couchpotato/static/scripts/vendor/dynamics.js' + 'couchpotato/static/scripts/vendor/dynamics.js', + 'couchpotato/static/scripts/vendor/fastclick.js' ]; var scripts_files = [ diff --git a/couchpotato/core/media/movie/_base/static/movie.scss b/couchpotato/core/media/movie/_base/static/movie.scss index ffeb36c..0c10685 100644 --- a/couchpotato/core/media/movie/_base/static/movie.scss +++ b/couchpotato/core/media/movie/_base/static/movie.scss @@ -5,6 +5,11 @@ $mass_edit_height: 44px; .page.movies { bottom: auto; z-index: 21; + height: $header_height; + + @include media-phablet { + height: $header_width_mobile; + } } .page.movies_wanted, diff --git a/couchpotato/static/scripts/combined.base.min.js b/couchpotato/static/scripts/combined.base.min.js index 7dd249b..2098b97 100644 --- a/couchpotato/static/scripts/combined.base.min.js +++ b/couchpotato/static/scripts/combined.base.min.js @@ -181,7 +181,10 @@ var CouchPotato = new Class({ self.c.addEvent("click:relay(a[href^=/]:not([target]))", self.pushState.bind(self)); self.c.addEvent("click:relay(a[href^=http])", self.openDerefered.bind(self)); self.touch_device = "ontouchstart" in window || navigator.msMaxTouchPoints; - if (self.touch_device) self.c.addClass("touch_enabled"); + if (self.touch_device) { + self.c.addClass("touch_enabled"); + FastClick.attach(document.body); + } window.addEvent("resize", self.resize.bind(self)); self.resize(); }, diff --git a/couchpotato/static/scripts/combined.vendor.min.js b/couchpotato/static/scripts/combined.vendor.min.js index e914a77..742c5e4 100644 --- a/couchpotato/static/scripts/combined.vendor.min.js +++ b/couchpotato/static/scripts/combined.vendor.min.js @@ -9256,4 +9256,412 @@ History.handleInitialState = function(base) { } else { window.dynamics = dynamics; } -}).call(this); \ No newline at end of file +}).call(this); + +(function() { + "use strict"; + function FastClick(layer, options) { + var oldOnClick; + options = options || {}; + this.trackingClick = false; + this.trackingClickStart = 0; + this.targetElement = null; + this.touchStartX = 0; + this.touchStartY = 0; + this.lastTouchIdentifier = 0; + this.touchBoundary = options.touchBoundary || 10; + this.layer = layer; + this.tapDelay = options.tapDelay || 200; + this.tapTimeout = options.tapTimeout || 700; + if (FastClick.notNeeded(layer)) { + return; + } + function bind(method, context) { + return function() { + return method.apply(context, arguments); + }; + } + var methods = [ "onMouse", "onClick", "onTouchStart", "onTouchMove", "onTouchEnd", "onTouchCancel" ]; + var context = this; + for (var i = 0, l = methods.length; i < l; i++) { + context[methods[i]] = bind(context[methods[i]], context); + } + if (deviceIsAndroid) { + layer.addEventListener("mouseover", this.onMouse, true); + layer.addEventListener("mousedown", this.onMouse, true); + layer.addEventListener("mouseup", this.onMouse, true); + } + layer.addEventListener("click", this.onClick, true); + layer.addEventListener("touchstart", this.onTouchStart, false); + layer.addEventListener("touchmove", this.onTouchMove, false); + layer.addEventListener("touchend", this.onTouchEnd, false); + layer.addEventListener("touchcancel", this.onTouchCancel, false); + if (!Event.prototype.stopImmediatePropagation) { + layer.removeEventListener = function(type, callback, capture) { + var rmv = Node.prototype.removeEventListener; + if (type === "click") { + rmv.call(layer, type, callback.hijacked || callback, capture); + } else { + rmv.call(layer, type, callback, capture); + } + }; + layer.addEventListener = function(type, callback, capture) { + var adv = Node.prototype.addEventListener; + if (type === "click") { + adv.call(layer, type, callback.hijacked || (callback.hijacked = function(event) { + if (!event.propagationStopped) { + callback(event); + } + }), capture); + } else { + adv.call(layer, type, callback, capture); + } + }; + } + if (typeof layer.onclick === "function") { + oldOnClick = layer.onclick; + layer.addEventListener("click", function(event) { + oldOnClick(event); + }, false); + layer.onclick = null; + } + } + var deviceIsWindowsPhone = navigator.userAgent.indexOf("Windows Phone") >= 0; + var deviceIsAndroid = navigator.userAgent.indexOf("Android") > 0 && !deviceIsWindowsPhone; + var deviceIsIOS = /iP(ad|hone|od)/.test(navigator.userAgent) && !deviceIsWindowsPhone; + var deviceIsIOS4 = deviceIsIOS && /OS 4_\d(_\d)?/.test(navigator.userAgent); + var deviceIsIOSWithBadTarget = deviceIsIOS && /OS [6-7]_\d/.test(navigator.userAgent); + var deviceIsBlackBerry10 = navigator.userAgent.indexOf("BB10") > 0; + FastClick.prototype.needsClick = function(target) { + switch (target.nodeName.toLowerCase()) { + case "button": + case "select": + case "textarea": + if (target.disabled) { + return true; + } + break; + + case "input": + if (deviceIsIOS && target.type === "file" || target.disabled) { + return true; + } + break; + + case "label": + case "iframe": + case "video": + return true; + } + return /\bneedsclick\b/.test(target.className); + }; + FastClick.prototype.needsFocus = function(target) { + switch (target.nodeName.toLowerCase()) { + case "textarea": + return true; + + case "select": + return !deviceIsAndroid; + + case "input": + switch (target.type) { + case "button": + case "checkbox": + case "file": + case "image": + case "radio": + case "submit": + return false; + } + return !target.disabled && !target.readOnly; + + default: + return /\bneedsfocus\b/.test(target.className); + } + }; + FastClick.prototype.sendClick = function(targetElement, event) { + var clickEvent, touch; + if (document.activeElement && document.activeElement !== targetElement) { + document.activeElement.blur(); + } + touch = event.changedTouches[0]; + clickEvent = document.createEvent("MouseEvents"); + clickEvent.initMouseEvent(this.determineEventType(targetElement), true, true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null); + clickEvent.forwardedTouchEvent = true; + targetElement.dispatchEvent(clickEvent); + }; + FastClick.prototype.determineEventType = function(targetElement) { + if (deviceIsAndroid && targetElement.tagName.toLowerCase() === "select") { + return "mousedown"; + } + return "click"; + }; + FastClick.prototype.focus = function(targetElement) { + var length; + if (deviceIsIOS && targetElement.setSelectionRange && targetElement.type.indexOf("date") !== 0 && targetElement.type !== "time" && targetElement.type !== "month") { + length = targetElement.value.length; + targetElement.setSelectionRange(length, length); + } else { + targetElement.focus(); + } + }; + FastClick.prototype.updateScrollParent = function(targetElement) { + var scrollParent, parentElement; + scrollParent = targetElement.fastClickScrollParent; + if (!scrollParent || !scrollParent.contains(targetElement)) { + parentElement = targetElement; + do { + if (parentElement.scrollHeight > parentElement.offsetHeight) { + scrollParent = parentElement; + targetElement.fastClickScrollParent = parentElement; + break; + } + parentElement = parentElement.parentElement; + } while (parentElement); + } + if (scrollParent) { + scrollParent.fastClickLastScrollTop = scrollParent.scrollTop; + } + }; + FastClick.prototype.getTargetElementFromEventTarget = function(eventTarget) { + if (eventTarget.nodeType === Node.TEXT_NODE) { + return eventTarget.parentNode; + } + return eventTarget; + }; + FastClick.prototype.onTouchStart = function(event) { + var targetElement, touch, selection; + if (event.targetTouches.length > 1) { + return true; + } + targetElement = this.getTargetElementFromEventTarget(event.target); + touch = event.targetTouches[0]; + if (deviceIsIOS) { + selection = window.getSelection(); + if (selection.rangeCount && !selection.isCollapsed) { + return true; + } + if (!deviceIsIOS4) { + if (touch.identifier && touch.identifier === this.lastTouchIdentifier) { + event.preventDefault(); + return false; + } + this.lastTouchIdentifier = touch.identifier; + this.updateScrollParent(targetElement); + } + } + this.trackingClick = true; + this.trackingClickStart = event.timeStamp; + this.targetElement = targetElement; + this.touchStartX = touch.pageX; + this.touchStartY = touch.pageY; + if (event.timeStamp - this.lastClickTime < this.tapDelay) { + event.preventDefault(); + } + return true; + }; + FastClick.prototype.touchHasMoved = function(event) { + var touch = event.changedTouches[0], boundary = this.touchBoundary; + if (Math.abs(touch.pageX - this.touchStartX) > boundary || Math.abs(touch.pageY - this.touchStartY) > boundary) { + return true; + } + return false; + }; + FastClick.prototype.onTouchMove = function(event) { + if (!this.trackingClick) { + return true; + } + if (this.targetElement !== this.getTargetElementFromEventTarget(event.target) || this.touchHasMoved(event)) { + this.trackingClick = false; + this.targetElement = null; + } + return true; + }; + FastClick.prototype.findControl = function(labelElement) { + if (labelElement.control !== undefined) { + return labelElement.control; + } + if (labelElement.htmlFor) { + return document.getElementById(labelElement.htmlFor); + } + return labelElement.querySelector("button, input:not([type=hidden]), keygen, meter, output, progress, select, textarea"); + }; + FastClick.prototype.onTouchEnd = function(event) { + var forElement, trackingClickStart, targetTagName, scrollParent, touch, targetElement = this.targetElement; + if (!this.trackingClick) { + return true; + } + if (event.timeStamp - this.lastClickTime < this.tapDelay) { + this.cancelNextClick = true; + return true; + } + if (event.timeStamp - this.trackingClickStart > this.tapTimeout) { + return true; + } + this.cancelNextClick = false; + this.lastClickTime = event.timeStamp; + trackingClickStart = this.trackingClickStart; + this.trackingClick = false; + this.trackingClickStart = 0; + if (deviceIsIOSWithBadTarget) { + touch = event.changedTouches[0]; + targetElement = document.elementFromPoint(touch.pageX - window.pageXOffset, touch.pageY - window.pageYOffset) || targetElement; + targetElement.fastClickScrollParent = this.targetElement.fastClickScrollParent; + } + targetTagName = targetElement.tagName.toLowerCase(); + if (targetTagName === "label") { + forElement = this.findControl(targetElement); + if (forElement) { + this.focus(targetElement); + if (deviceIsAndroid) { + return false; + } + targetElement = forElement; + } + } else if (this.needsFocus(targetElement)) { + if (event.timeStamp - trackingClickStart > 100 || deviceIsIOS && window.top !== window && targetTagName === "input") { + this.targetElement = null; + return false; + } + this.focus(targetElement); + this.sendClick(targetElement, event); + if (!deviceIsIOS || targetTagName !== "select") { + this.targetElement = null; + event.preventDefault(); + } + return false; + } + if (deviceIsIOS && !deviceIsIOS4) { + scrollParent = targetElement.fastClickScrollParent; + if (scrollParent && scrollParent.fastClickLastScrollTop !== scrollParent.scrollTop) { + return true; + } + } + if (!this.needsClick(targetElement)) { + event.preventDefault(); + this.sendClick(targetElement, event); + } + return false; + }; + FastClick.prototype.onTouchCancel = function() { + this.trackingClick = false; + this.targetElement = null; + }; + FastClick.prototype.onMouse = function(event) { + if (!this.targetElement) { + return true; + } + if (event.forwardedTouchEvent) { + return true; + } + if (!event.cancelable) { + return true; + } + if (!this.needsClick(this.targetElement) || this.cancelNextClick) { + if (event.stopImmediatePropagation) { + event.stopImmediatePropagation(); + } else { + event.propagationStopped = true; + } + event.stopPropagation(); + event.preventDefault(); + return false; + } + return true; + }; + FastClick.prototype.onClick = function(event) { + var permitted; + if (this.trackingClick) { + this.targetElement = null; + this.trackingClick = false; + return true; + } + if (event.target.type === "submit" && event.detail === 0) { + return true; + } + permitted = this.onMouse(event); + if (!permitted) { + this.targetElement = null; + } + return permitted; + }; + FastClick.prototype.destroy = function() { + var layer = this.layer; + if (deviceIsAndroid) { + layer.removeEventListener("mouseover", this.onMouse, true); + layer.removeEventListener("mousedown", this.onMouse, true); + layer.removeEventListener("mouseup", this.onMouse, true); + } + layer.removeEventListener("click", this.onClick, true); + layer.removeEventListener("touchstart", this.onTouchStart, false); + layer.removeEventListener("touchmove", this.onTouchMove, false); + layer.removeEventListener("touchend", this.onTouchEnd, false); + layer.removeEventListener("touchcancel", this.onTouchCancel, false); + }; + FastClick.notNeeded = function(layer) { + var metaViewport; + var chromeVersion; + var blackberryVersion; + var firefoxVersion; + if (typeof window.ontouchstart === "undefined") { + return true; + } + chromeVersion = +(/Chrome\/([0-9]+)/.exec(navigator.userAgent) || [ , 0 ])[1]; + if (chromeVersion) { + if (deviceIsAndroid) { + metaViewport = document.querySelector("meta[name=viewport]"); + if (metaViewport) { + if (metaViewport.content.indexOf("user-scalable=no") !== -1) { + return true; + } + if (chromeVersion > 31 && document.documentElement.scrollWidth <= window.outerWidth) { + return true; + } + } + } else { + return true; + } + } + if (deviceIsBlackBerry10) { + blackberryVersion = navigator.userAgent.match(/Version\/([0-9]*)\.([0-9]*)/); + if (blackberryVersion[1] >= 10 && blackberryVersion[2] >= 3) { + metaViewport = document.querySelector("meta[name=viewport]"); + if (metaViewport) { + if (metaViewport.content.indexOf("user-scalable=no") !== -1) { + return true; + } + if (document.documentElement.scrollWidth <= window.outerWidth) { + return true; + } + } + } + } + if (layer.style.msTouchAction === "none" || layer.style.touchAction === "manipulation") { + return true; + } + firefoxVersion = +(/Firefox\/([0-9]+)/.exec(navigator.userAgent) || [ , 0 ])[1]; + if (firefoxVersion >= 27) { + metaViewport = document.querySelector("meta[name=viewport]"); + if (metaViewport && (metaViewport.content.indexOf("user-scalable=no") !== -1 || document.documentElement.scrollWidth <= window.outerWidth)) { + return true; + } + } + if (layer.style.touchAction === "none" || layer.style.touchAction === "manipulation") { + return true; + } + return false; + }; + FastClick.attach = function(layer, options) { + return new FastClick(layer, options); + }; + if (typeof define === "function" && typeof define.amd === "object" && define.amd) { + define(function() { + return FastClick; + }); + } else if (typeof module !== "undefined" && module.exports) { + module.exports = FastClick.attach; + module.exports.FastClick = FastClick; + } else { + window.FastClick = FastClick; + } +})(); \ No newline at end of file diff --git a/couchpotato/static/scripts/couchpotato.js b/couchpotato/static/scripts/couchpotato.js index 86470d6..dc2c25e 100644 --- a/couchpotato/static/scripts/couchpotato.js +++ b/couchpotato/static/scripts/couchpotato.js @@ -38,8 +38,10 @@ // Check if device is touchenabled self.touch_device = 'ontouchstart' in window || navigator.msMaxTouchPoints; - if(self.touch_device) + if(self.touch_device){ self.c.addClass('touch_enabled'); + FastClick.attach(document.body); + } window.addEvent('resize', self.resize.bind(self)); self.resize(); diff --git a/couchpotato/static/scripts/vendor/fastclick.js b/couchpotato/static/scripts/vendor/fastclick.js new file mode 100644 index 0000000..3af4f9d --- /dev/null +++ b/couchpotato/static/scripts/vendor/fastclick.js @@ -0,0 +1,841 @@ +;(function () { + 'use strict'; + + /** + * @preserve FastClick: polyfill to remove click delays on browsers with touch UIs. + * + * @codingstandard ftlabs-jsv2 + * @copyright The Financial Times Limited [All Rights Reserved] + * @license MIT License (see LICENSE.txt) + */ + + /*jslint browser:true, node:true*/ + /*global define, Event, Node*/ + + + /** + * Instantiate fast-clicking listeners on the specified layer. + * + * @constructor + * @param {Element} layer The layer to listen on + * @param {Object} [options={}] The options to override the defaults + */ + function FastClick(layer, options) { + var oldOnClick; + + options = options || {}; + + /** + * Whether a click is currently being tracked. + * + * @type boolean + */ + this.trackingClick = false; + + + /** + * Timestamp for when click tracking started. + * + * @type number + */ + this.trackingClickStart = 0; + + + /** + * The element being tracked for a click. + * + * @type EventTarget + */ + this.targetElement = null; + + + /** + * X-coordinate of touch start event. + * + * @type number + */ + this.touchStartX = 0; + + + /** + * Y-coordinate of touch start event. + * + * @type number + */ + this.touchStartY = 0; + + + /** + * ID of the last touch, retrieved from Touch.identifier. + * + * @type number + */ + this.lastTouchIdentifier = 0; + + + /** + * Touchmove boundary, beyond which a click will be cancelled. + * + * @type number + */ + this.touchBoundary = options.touchBoundary || 10; + + + /** + * The FastClick layer. + * + * @type Element + */ + this.layer = layer; + + /** + * The minimum time between tap(touchstart and touchend) events + * + * @type number + */ + this.tapDelay = options.tapDelay || 200; + + /** + * The maximum time for a tap + * + * @type number + */ + this.tapTimeout = options.tapTimeout || 700; + + if (FastClick.notNeeded(layer)) { + return; + } + + // Some old versions of Android don't have Function.prototype.bind + function bind(method, context) { + return function() { return method.apply(context, arguments); }; + } + + + var methods = ['onMouse', 'onClick', 'onTouchStart', 'onTouchMove', 'onTouchEnd', 'onTouchCancel']; + var context = this; + for (var i = 0, l = methods.length; i < l; i++) { + context[methods[i]] = bind(context[methods[i]], context); + } + + // Set up event handlers as required + if (deviceIsAndroid) { + layer.addEventListener('mouseover', this.onMouse, true); + layer.addEventListener('mousedown', this.onMouse, true); + layer.addEventListener('mouseup', this.onMouse, true); + } + + layer.addEventListener('click', this.onClick, true); + layer.addEventListener('touchstart', this.onTouchStart, false); + layer.addEventListener('touchmove', this.onTouchMove, false); + layer.addEventListener('touchend', this.onTouchEnd, false); + layer.addEventListener('touchcancel', this.onTouchCancel, false); + + // Hack is required for browsers that don't support Event#stopImmediatePropagation (e.g. Android 2) + // which is how FastClick normally stops click events bubbling to callbacks registered on the FastClick + // layer when they are cancelled. + if (!Event.prototype.stopImmediatePropagation) { + layer.removeEventListener = function(type, callback, capture) { + var rmv = Node.prototype.removeEventListener; + if (type === 'click') { + rmv.call(layer, type, callback.hijacked || callback, capture); + } else { + rmv.call(layer, type, callback, capture); + } + }; + + layer.addEventListener = function(type, callback, capture) { + var adv = Node.prototype.addEventListener; + if (type === 'click') { + adv.call(layer, type, callback.hijacked || (callback.hijacked = function(event) { + if (!event.propagationStopped) { + callback(event); + } + }), capture); + } else { + adv.call(layer, type, callback, capture); + } + }; + } + + // If a handler is already declared in the element's onclick attribute, it will be fired before + // FastClick's onClick handler. Fix this by pulling out the user-defined handler function and + // adding it as listener. + if (typeof layer.onclick === 'function') { + + // Android browser on at least 3.2 requires a new reference to the function in layer.onclick + // - the old one won't work if passed to addEventListener directly. + oldOnClick = layer.onclick; + layer.addEventListener('click', function(event) { + oldOnClick(event); + }, false); + layer.onclick = null; + } + } + + /** + * Windows Phone 8.1 fakes user agent string to look like Android and iPhone. + * + * @type boolean + */ + var deviceIsWindowsPhone = navigator.userAgent.indexOf("Windows Phone") >= 0; + + /** + * Android requires exceptions. + * + * @type boolean + */ + var deviceIsAndroid = navigator.userAgent.indexOf('Android') > 0 && !deviceIsWindowsPhone; + + + /** + * iOS requires exceptions. + * + * @type boolean + */ + var deviceIsIOS = /iP(ad|hone|od)/.test(navigator.userAgent) && !deviceIsWindowsPhone; + + + /** + * iOS 4 requires an exception for select elements. + * + * @type boolean + */ + var deviceIsIOS4 = deviceIsIOS && (/OS 4_\d(_\d)?/).test(navigator.userAgent); + + + /** + * iOS 6.0-7.* requires the target element to be manually derived + * + * @type boolean + */ + var deviceIsIOSWithBadTarget = deviceIsIOS && (/OS [6-7]_\d/).test(navigator.userAgent); + + /** + * BlackBerry requires exceptions. + * + * @type boolean + */ + var deviceIsBlackBerry10 = navigator.userAgent.indexOf('BB10') > 0; + + /** + * Determine whether a given element requires a native click. + * + * @param {EventTarget|Element} target Target DOM element + * @returns {boolean} Returns true if the element needs a native click + */ + FastClick.prototype.needsClick = function(target) { + switch (target.nodeName.toLowerCase()) { + + // Don't send a synthetic click to disabled inputs (issue #62) + case 'button': + case 'select': + case 'textarea': + if (target.disabled) { + return true; + } + + break; + case 'input': + + // File inputs need real clicks on iOS 6 due to a browser bug (issue #68) + if ((deviceIsIOS && target.type === 'file') || target.disabled) { + return true; + } + + break; + case 'label': + case 'iframe': // iOS8 homescreen apps can prevent events bubbling into frames + case 'video': + return true; + } + + return (/\bneedsclick\b/).test(target.className); + }; + + + /** + * Determine whether a given element requires a call to focus to simulate click into element. + * + * @param {EventTarget|Element} target Target DOM element + * @returns {boolean} Returns true if the element requires a call to focus to simulate native click. + */ + FastClick.prototype.needsFocus = function(target) { + switch (target.nodeName.toLowerCase()) { + case 'textarea': + return true; + case 'select': + return !deviceIsAndroid; + case 'input': + switch (target.type) { + case 'button': + case 'checkbox': + case 'file': + case 'image': + case 'radio': + case 'submit': + return false; + } + + // No point in attempting to focus disabled inputs + return !target.disabled && !target.readOnly; + default: + return (/\bneedsfocus\b/).test(target.className); + } + }; + + + /** + * Send a click event to the specified element. + * + * @param {EventTarget|Element} targetElement + * @param {Event} event + */ + FastClick.prototype.sendClick = function(targetElement, event) { + var clickEvent, touch; + + // On some Android devices activeElement needs to be blurred otherwise the synthetic click will have no effect (#24) + if (document.activeElement && document.activeElement !== targetElement) { + document.activeElement.blur(); + } + + touch = event.changedTouches[0]; + + // Synthesise a click event, with an extra attribute so it can be tracked + clickEvent = document.createEvent('MouseEvents'); + clickEvent.initMouseEvent(this.determineEventType(targetElement), true, true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null); + clickEvent.forwardedTouchEvent = true; + targetElement.dispatchEvent(clickEvent); + }; + + FastClick.prototype.determineEventType = function(targetElement) { + + //Issue #159: Android Chrome Select Box does not open with a synthetic click event + if (deviceIsAndroid && targetElement.tagName.toLowerCase() === 'select') { + return 'mousedown'; + } + + return 'click'; + }; + + + /** + * @param {EventTarget|Element} targetElement + */ + FastClick.prototype.focus = function(targetElement) { + var length; + + // Issue #160: on iOS 7, some input elements (e.g. date datetime month) throw a vague TypeError on setSelectionRange. These elements don't have an integer value for the selectionStart and selectionEnd properties, but unfortunately that can't be used for detection because accessing the properties also throws a TypeError. Just check the type instead. Filed as Apple bug #15122724. + if (deviceIsIOS && targetElement.setSelectionRange && targetElement.type.indexOf('date') !== 0 && targetElement.type !== 'time' && targetElement.type !== 'month') { + length = targetElement.value.length; + targetElement.setSelectionRange(length, length); + } else { + targetElement.focus(); + } + }; + + + /** + * Check whether the given target element is a child of a scrollable layer and if so, set a flag on it. + * + * @param {EventTarget|Element} targetElement + */ + FastClick.prototype.updateScrollParent = function(targetElement) { + var scrollParent, parentElement; + + scrollParent = targetElement.fastClickScrollParent; + + // Attempt to discover whether the target element is contained within a scrollable layer. Re-check if the + // target element was moved to another parent. + if (!scrollParent || !scrollParent.contains(targetElement)) { + parentElement = targetElement; + do { + if (parentElement.scrollHeight > parentElement.offsetHeight) { + scrollParent = parentElement; + targetElement.fastClickScrollParent = parentElement; + break; + } + + parentElement = parentElement.parentElement; + } while (parentElement); + } + + // Always update the scroll top tracker if possible. + if (scrollParent) { + scrollParent.fastClickLastScrollTop = scrollParent.scrollTop; + } + }; + + + /** + * @param {EventTarget} targetElement + * @returns {Element|EventTarget} + */ + FastClick.prototype.getTargetElementFromEventTarget = function(eventTarget) { + + // On some older browsers (notably Safari on iOS 4.1 - see issue #56) the event target may be a text node. + if (eventTarget.nodeType === Node.TEXT_NODE) { + return eventTarget.parentNode; + } + + return eventTarget; + }; + + + /** + * On touch start, record the position and scroll offset. + * + * @param {Event} event + * @returns {boolean} + */ + FastClick.prototype.onTouchStart = function(event) { + var targetElement, touch, selection; + + // Ignore multiple touches, otherwise pinch-to-zoom is prevented if both fingers are on the FastClick element (issue #111). + if (event.targetTouches.length > 1) { + return true; + } + + targetElement = this.getTargetElementFromEventTarget(event.target); + touch = event.targetTouches[0]; + + if (deviceIsIOS) { + + // Only trusted events will deselect text on iOS (issue #49) + selection = window.getSelection(); + if (selection.rangeCount && !selection.isCollapsed) { + return true; + } + + if (!deviceIsIOS4) { + + // Weird things happen on iOS when an alert or confirm dialog is opened from a click event callback (issue #23): + // when the user next taps anywhere else on the page, new touchstart and touchend events are dispatched + // with the same identifier as the touch event that previously triggered the click that triggered the alert. + // Sadly, there is an issue on iOS 4 that causes some normal touch events to have the same identifier as an + // immediately preceeding touch event (issue #52), so this fix is unavailable on that platform. + // Issue 120: touch.identifier is 0 when Chrome dev tools 'Emulate touch events' is set with an iOS device UA string, + // which causes all touch events to be ignored. As this block only applies to iOS, and iOS identifiers are always long, + // random integers, it's safe to to continue if the identifier is 0 here. + if (touch.identifier && touch.identifier === this.lastTouchIdentifier) { + event.preventDefault(); + return false; + } + + this.lastTouchIdentifier = touch.identifier; + + // If the target element is a child of a scrollable layer (using -webkit-overflow-scrolling: touch) and: + // 1) the user does a fling scroll on the scrollable layer + // 2) the user stops the fling scroll with another tap + // then the event.target of the last 'touchend' event will be the element that was under the user's finger + // when the fling scroll was started, causing FastClick to send a click event to that layer - unless a check + // is made to ensure that a parent layer was not scrolled before sending a synthetic click (issue #42). + this.updateScrollParent(targetElement); + } + } + + this.trackingClick = true; + this.trackingClickStart = event.timeStamp; + this.targetElement = targetElement; + + this.touchStartX = touch.pageX; + this.touchStartY = touch.pageY; + + // Prevent phantom clicks on fast double-tap (issue #36) + if ((event.timeStamp - this.lastClickTime) < this.tapDelay) { + event.preventDefault(); + } + + return true; + }; + + + /** + * Based on a touchmove event object, check whether the touch has moved past a boundary since it started. + * + * @param {Event} event + * @returns {boolean} + */ + FastClick.prototype.touchHasMoved = function(event) { + var touch = event.changedTouches[0], boundary = this.touchBoundary; + + if (Math.abs(touch.pageX - this.touchStartX) > boundary || Math.abs(touch.pageY - this.touchStartY) > boundary) { + return true; + } + + return false; + }; + + + /** + * Update the last position. + * + * @param {Event} event + * @returns {boolean} + */ + FastClick.prototype.onTouchMove = function(event) { + if (!this.trackingClick) { + return true; + } + + // If the touch has moved, cancel the click tracking + if (this.targetElement !== this.getTargetElementFromEventTarget(event.target) || this.touchHasMoved(event)) { + this.trackingClick = false; + this.targetElement = null; + } + + return true; + }; + + + /** + * Attempt to find the labelled control for the given label element. + * + * @param {EventTarget|HTMLLabelElement} labelElement + * @returns {Element|null} + */ + FastClick.prototype.findControl = function(labelElement) { + + // Fast path for newer browsers supporting the HTML5 control attribute + if (labelElement.control !== undefined) { + return labelElement.control; + } + + // All browsers under test that support touch events also support the HTML5 htmlFor attribute + if (labelElement.htmlFor) { + return document.getElementById(labelElement.htmlFor); + } + + // If no for attribute exists, attempt to retrieve the first labellable descendant element + // the list of which is defined here: http://www.w3.org/TR/html5/forms.html#category-label + return labelElement.querySelector('button, input:not([type=hidden]), keygen, meter, output, progress, select, textarea'); + }; + + + /** + * On touch end, determine whether to send a click event at once. + * + * @param {Event} event + * @returns {boolean} + */ + FastClick.prototype.onTouchEnd = function(event) { + var forElement, trackingClickStart, targetTagName, scrollParent, touch, targetElement = this.targetElement; + + if (!this.trackingClick) { + return true; + } + + // Prevent phantom clicks on fast double-tap (issue #36) + if ((event.timeStamp - this.lastClickTime) < this.tapDelay) { + this.cancelNextClick = true; + return true; + } + + if ((event.timeStamp - this.trackingClickStart) > this.tapTimeout) { + return true; + } + + // Reset to prevent wrong click cancel on input (issue #156). + this.cancelNextClick = false; + + this.lastClickTime = event.timeStamp; + + trackingClickStart = this.trackingClickStart; + this.trackingClick = false; + this.trackingClickStart = 0; + + // On some iOS devices, the targetElement supplied with the event is invalid if the layer + // is performing a transition or scroll, and has to be re-detected manually. Note that + // for this to function correctly, it must be called *after* the event target is checked! + // See issue #57; also filed as rdar://13048589 . + if (deviceIsIOSWithBadTarget) { + touch = event.changedTouches[0]; + + // In certain cases arguments of elementFromPoint can be negative, so prevent setting targetElement to null + targetElement = document.elementFromPoint(touch.pageX - window.pageXOffset, touch.pageY - window.pageYOffset) || targetElement; + targetElement.fastClickScrollParent = this.targetElement.fastClickScrollParent; + } + + targetTagName = targetElement.tagName.toLowerCase(); + if (targetTagName === 'label') { + forElement = this.findControl(targetElement); + if (forElement) { + this.focus(targetElement); + if (deviceIsAndroid) { + return false; + } + + targetElement = forElement; + } + } else if (this.needsFocus(targetElement)) { + + // Case 1: If the touch started a while ago (best guess is 100ms based on tests for issue #36) then focus will be triggered anyway. Return early and unset the target element reference so that the subsequent click will be allowed through. + // Case 2: Without this exception for input elements tapped when the document is contained in an iframe, then any inputted text won't be visible even though the value attribute is updated as the user types (issue #37). + if ((event.timeStamp - trackingClickStart) > 100 || (deviceIsIOS && window.top !== window && targetTagName === 'input')) { + this.targetElement = null; + return false; + } + + this.focus(targetElement); + this.sendClick(targetElement, event); + + // Select elements need the event to go through on iOS 4, otherwise the selector menu won't open. + // Also this breaks opening selects when VoiceOver is active on iOS6, iOS7 (and possibly others) + if (!deviceIsIOS || targetTagName !== 'select') { + this.targetElement = null; + event.preventDefault(); + } + + return false; + } + + if (deviceIsIOS && !deviceIsIOS4) { + + // Don't send a synthetic click event if the target element is contained within a parent layer that was scrolled + // and this tap is being used to stop the scrolling (usually initiated by a fling - issue #42). + scrollParent = targetElement.fastClickScrollParent; + if (scrollParent && scrollParent.fastClickLastScrollTop !== scrollParent.scrollTop) { + return true; + } + } + + // Prevent the actual click from going though - unless the target node is marked as requiring + // real clicks or if it is in the whitelist in which case only non-programmatic clicks are permitted. + if (!this.needsClick(targetElement)) { + event.preventDefault(); + this.sendClick(targetElement, event); + } + + return false; + }; + + + /** + * On touch cancel, stop tracking the click. + * + * @returns {void} + */ + FastClick.prototype.onTouchCancel = function() { + this.trackingClick = false; + this.targetElement = null; + }; + + + /** + * Determine mouse events which should be permitted. + * + * @param {Event} event + * @returns {boolean} + */ + FastClick.prototype.onMouse = function(event) { + + // If a target element was never set (because a touch event was never fired) allow the event + if (!this.targetElement) { + return true; + } + + if (event.forwardedTouchEvent) { + return true; + } + + // Programmatically generated events targeting a specific element should be permitted + if (!event.cancelable) { + return true; + } + + // Derive and check the target element to see whether the mouse event needs to be permitted; + // unless explicitly enabled, prevent non-touch click events from triggering actions, + // to prevent ghost/doubleclicks. + if (!this.needsClick(this.targetElement) || this.cancelNextClick) { + + // Prevent any user-added listeners declared on FastClick element from being fired. + if (event.stopImmediatePropagation) { + event.stopImmediatePropagation(); + } else { + + // Part of the hack for browsers that don't support Event#stopImmediatePropagation (e.g. Android 2) + event.propagationStopped = true; + } + + // Cancel the event + event.stopPropagation(); + event.preventDefault(); + + return false; + } + + // If the mouse event is permitted, return true for the action to go through. + return true; + }; + + + /** + * On actual clicks, determine whether this is a touch-generated click, a click action occurring + * naturally after a delay after a touch (which needs to be cancelled to avoid duplication), or + * an actual click which should be permitted. + * + * @param {Event} event + * @returns {boolean} + */ + FastClick.prototype.onClick = function(event) { + var permitted; + + // It's possible for another FastClick-like library delivered with third-party code to fire a click event before FastClick does (issue #44). In that case, set the click-tracking flag back to false and return early. This will cause onTouchEnd to return early. + if (this.trackingClick) { + this.targetElement = null; + this.trackingClick = false; + return true; + } + + // Very odd behaviour on iOS (issue #18): if a submit element is present inside a form and the user hits enter in the iOS simulator or clicks the Go button on the pop-up OS keyboard the a kind of 'fake' click event will be triggered with the submit-type input element as the target. + if (event.target.type === 'submit' && event.detail === 0) { + return true; + } + + permitted = this.onMouse(event); + + // Only unset targetElement if the click is not permitted. This will ensure that the check for !targetElement in onMouse fails and the browser's click doesn't go through. + if (!permitted) { + this.targetElement = null; + } + + // If clicks are permitted, return true for the action to go through. + return permitted; + }; + + + /** + * Remove all FastClick's event listeners. + * + * @returns {void} + */ + FastClick.prototype.destroy = function() { + var layer = this.layer; + + if (deviceIsAndroid) { + layer.removeEventListener('mouseover', this.onMouse, true); + layer.removeEventListener('mousedown', this.onMouse, true); + layer.removeEventListener('mouseup', this.onMouse, true); + } + + layer.removeEventListener('click', this.onClick, true); + layer.removeEventListener('touchstart', this.onTouchStart, false); + layer.removeEventListener('touchmove', this.onTouchMove, false); + layer.removeEventListener('touchend', this.onTouchEnd, false); + layer.removeEventListener('touchcancel', this.onTouchCancel, false); + }; + + + /** + * Check whether FastClick is needed. + * + * @param {Element} layer The layer to listen on + */ + FastClick.notNeeded = function(layer) { + var metaViewport; + var chromeVersion; + var blackberryVersion; + var firefoxVersion; + + // Devices that don't support touch don't need FastClick + if (typeof window.ontouchstart === 'undefined') { + return true; + } + + // Chrome version - zero for other browsers + chromeVersion = +(/Chrome\/([0-9]+)/.exec(navigator.userAgent) || [,0])[1]; + + if (chromeVersion) { + + if (deviceIsAndroid) { + metaViewport = document.querySelector('meta[name=viewport]'); + + if (metaViewport) { + // Chrome on Android with user-scalable="no" doesn't need FastClick (issue #89) + if (metaViewport.content.indexOf('user-scalable=no') !== -1) { + return true; + } + // Chrome 32 and above with width=device-width or less don't need FastClick + if (chromeVersion > 31 && document.documentElement.scrollWidth <= window.outerWidth) { + return true; + } + } + + // Chrome desktop doesn't need FastClick (issue #15) + } else { + return true; + } + } + + if (deviceIsBlackBerry10) { + blackberryVersion = navigator.userAgent.match(/Version\/([0-9]*)\.([0-9]*)/); + + // BlackBerry 10.3+ does not require Fastclick library. + // https://github.com/ftlabs/fastclick/issues/251 + if (blackberryVersion[1] >= 10 && blackberryVersion[2] >= 3) { + metaViewport = document.querySelector('meta[name=viewport]'); + + if (metaViewport) { + // user-scalable=no eliminates click delay. + if (metaViewport.content.indexOf('user-scalable=no') !== -1) { + return true; + } + // width=device-width (or less than device-width) eliminates click delay. + if (document.documentElement.scrollWidth <= window.outerWidth) { + return true; + } + } + } + } + + // IE10 with -ms-touch-action: none or manipulation, which disables double-tap-to-zoom (issue #97) + if (layer.style.msTouchAction === 'none' || layer.style.touchAction === 'manipulation') { + return true; + } + + // Firefox version - zero for other browsers + firefoxVersion = +(/Firefox\/([0-9]+)/.exec(navigator.userAgent) || [,0])[1]; + + if (firefoxVersion >= 27) { + // Firefox 27+ does not have tap delay if the content is not zoomable - https://bugzilla.mozilla.org/show_bug.cgi?id=922896 + + metaViewport = document.querySelector('meta[name=viewport]'); + if (metaViewport && (metaViewport.content.indexOf('user-scalable=no') !== -1 || document.documentElement.scrollWidth <= window.outerWidth)) { + return true; + } + } + + // IE11: prefixed -ms-touch-action is no longer supported and it's recomended to use non-prefixed version + // http://msdn.microsoft.com/en-us/library/windows/apps/Hh767313.aspx + if (layer.style.touchAction === 'none' || layer.style.touchAction === 'manipulation') { + return true; + } + + return false; + }; + + + /** + * Factory method for creating a FastClick object + * + * @param {Element} layer The layer to listen on + * @param {Object} [options={}] The options to override the defaults + */ + FastClick.attach = function(layer, options) { + return new FastClick(layer, options); + }; + + + if (typeof define === 'function' && typeof define.amd === 'object' && define.amd) { + + // AMD. Register as an anonymous module. + define(function() { + return FastClick; + }); + } else if (typeof module !== 'undefined' && module.exports) { + module.exports = FastClick.attach; + module.exports.FastClick = FastClick; + } else { + window.FastClick = FastClick; + } +}()); diff --git a/couchpotato/static/style/combined.min.css b/couchpotato/static/style/combined.min.css index 1f83237..2f01e05 100644 --- a/couchpotato/static/style/combined.min.css +++ b/couchpotato/static/style/combined.min.css @@ -51,9 +51,10 @@ .search_form .results_container .results .media_result:last-child .data{border-bottom:0} .search_form.focused.filled .results_container,.search_form.shown.filled .results_container{display:block} .search_form.focused.filled .input,.search_form.shown.filled .input{border-radius:0 0 0 3px} -.page.movies{bottom:auto;z-index:21} +.page.movies{bottom:auto;z-index:21;height:80px} .page.movies_manage,.page.movies_wanted{top:80px;padding:0;transition:top 300ms cubic-bezier(.9,0,.1,1)} -@media (max-width:480px){.page.movies_manage,.page.movies_wanted{top:44px} +@media (max-width:480px){.page.movies{height:44px} +.page.movies_manage,.page.movies_wanted{top:44px} } .mass_editing .page.movies_manage,.mass_editing .page.movies_wanted{top:124px} .page.movies_manage .load_more,.page.movies_wanted .load_more{text-align:center;padding:20px;font-size:2em;display:block} From b1b269e6093c94856ed8c89d15253fa0c7709fec Mon Sep 17 00:00:00 2001 From: Ruud Date: Thu, 23 Jul 2015 11:46:59 +0200 Subject: [PATCH 215/301] ETA date not working on Safari --- couchpotato/core/media/movie/_base/static/movie.js | 2 +- couchpotato/static/scripts/combined.plugins.min.js | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/couchpotato/core/media/movie/_base/static/movie.js b/couchpotato/core/media/movie/_base/static/movie.js index 77f3ed4..d144ba5 100644 --- a/couchpotato/core/media/movie/_base/static/movie.js +++ b/couchpotato/core/media/movie/_base/static/movie.js @@ -267,7 +267,7 @@ var Movie = new Class({ eta_date = null; } else { - eta_date = eta_date.toLocaleString('en-us', { month: "short" }) + (d.getFullYear() != eta_date.getFullYear() ? ' ' + eta_date.getFullYear() : ''); + eta_date = eta_date.format('%b') + (d.getFullYear() != eta_date.getFullYear() ? ' ' + eta_date.getFullYear() : ''); } } diff --git a/couchpotato/static/scripts/combined.plugins.min.js b/couchpotato/static/scripts/combined.plugins.min.js index e372230..88f8452 100644 --- a/couchpotato/static/scripts/combined.plugins.min.js +++ b/couchpotato/static/scripts/combined.plugins.min.js @@ -1957,9 +1957,7 @@ var Movie = new Class({ if (+eta_date / 1e3 < now) { eta_date = null; } else { - eta_date = eta_date.toLocaleString("en-us", { - month: "short" - }) + (d.getFullYear() != eta_date.getFullYear() ? " " + eta_date.getFullYear() : ""); + eta_date = eta_date.format("%b") + (d.getFullYear() != eta_date.getFullYear() ? " " + eta_date.getFullYear() : ""); } } var rating, stars; From a52a6868aadeac1851668c1ec9e3ac04f45aee69 Mon Sep 17 00:00:00 2001 From: Ruud Date: Thu, 23 Jul 2015 11:57:27 +0200 Subject: [PATCH 216/301] Menu icons cut-off on iOS --- couchpotato/static/scripts/combined.base.min.js | 2 +- couchpotato/static/scripts/page.js | 2 +- couchpotato/static/style/combined.min.css | 6 +++--- couchpotato/static/style/main.scss | 7 ++++++- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/couchpotato/static/scripts/combined.base.min.js b/couchpotato/static/scripts/combined.base.min.js index 2098b97..24e102c 100644 --- a/couchpotato/static/scripts/combined.base.min.js +++ b/couchpotato/static/scripts/combined.base.min.js @@ -685,7 +685,7 @@ var PageBase = new Class({ self.tab = nav.addTab(self.name, { href: App.createUrl(self.getPageUrl()), title: self.title, - text: self.name.capitalize(), + html: "" + self.name.capitalize() + "", class: self.icon ? "icon-" + self.icon : null }); } diff --git a/couchpotato/static/scripts/page.js b/couchpotato/static/scripts/page.js index 6a42405..9a2cf4c 100644 --- a/couchpotato/static/scripts/page.js +++ b/couchpotato/static/scripts/page.js @@ -52,7 +52,7 @@ var PageBase = new Class({ self.tab = nav.addTab(self.name, { 'href': App.createUrl(self.getPageUrl()), 'title': self.title, - 'text': self.name.capitalize(), + 'html': '' + self.name.capitalize() + '', 'class': self.icon ? 'icon-' + self.icon : null }); } diff --git a/couchpotato/static/style/combined.min.css b/couchpotato/static/style/combined.min.css index 2f01e05..e25a1b5 100644 --- a/couchpotato/static/style/combined.min.css +++ b/couchpotato/static/style/combined.min.css @@ -528,12 +528,12 @@ input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#F .header .navigation ul li{line-height:0} } .header .navigation ul li a{padding:10px 20px;display:block;position:relative} -@media (max-width:480px){.header .navigation ul li a{line-height:24px;height:44px;padding:10px 0;text-align:center;text-indent:-1000px} -} .header .navigation ul li a:before{position:absolute;width:100%;display:none;text-align:center;font-size:18px;text-indent:0} .header .navigation ul li a.icon-home:before{font-size:24px} .header .menu,.header .notification_menu,.header .search_form{position:absolute;z-index:21;bottom:6.67px;width:44px;height:44px} -@media (max-width:480px){.header .navigation ul li a:before{display:block} +@media (max-width:480px){.header .navigation ul li a{line-height:24px;height:44px;padding:10px 0;text-align:center} +.header .navigation ul li a span{display:none} +.header .navigation ul li a:before{display:block} .header .menu,.header .notification_menu,.header .search_form{bottom:0} } .header .menu .wrapper,.header .notification_menu .wrapper,.header .search_form .wrapper{min-width:170px;-webkit-transform-origin:0 90%;transform-origin:0 90%} diff --git a/couchpotato/static/style/main.scss b/couchpotato/static/style/main.scss index aaf288f..2abe5f9 100644 --- a/couchpotato/static/style/main.scss +++ b/couchpotato/static/style/main.scss @@ -164,7 +164,12 @@ input, textarea, select { height: $header_width_mobile; padding: $padding/2 0; text-align: center; - text-indent: -1000px; + } + + span { + @include media-phablet { + display: none; + } } &:before { From a514e7d9adbb2a017d58e2ecd13615d912fc2354 Mon Sep 17 00:00:00 2001 From: Ruud Date: Thu, 23 Jul 2015 14:32:57 +0200 Subject: [PATCH 217/301] Content overflow hiding back button on webkit --- .../core/media/movie/_base/static/manage.js | 2 +- couchpotato/core/media/movie/_base/static/page.js | 2 +- .../core/media/movie/_base/static/wanted.js | 4 ++-- couchpotato/core/plugins/log/static/log.js | 6 ++--- .../core/plugins/userscript/static/userscript.js | 2 +- couchpotato/static/scripts/combined.base.min.js | 26 +++++++++++----------- couchpotato/static/scripts/combined.plugins.min.js | 16 ++++++------- couchpotato/static/scripts/page.js | 10 +++++---- couchpotato/static/scripts/page/home.js | 10 ++++----- couchpotato/static/scripts/page/settings.js | 8 +++---- couchpotato/static/style/combined.min.css | 3 ++- couchpotato/static/style/main.scss | 7 ++++-- 12 files changed, 51 insertions(+), 45 deletions(-) diff --git a/couchpotato/core/media/movie/_base/static/manage.js b/couchpotato/core/media/movie/_base/static/manage.js index 77f07e3..4b9f342 100644 --- a/couchpotato/core/media/movie/_base/static/manage.js +++ b/couchpotato/core/media/movie/_base/static/manage.js @@ -59,7 +59,7 @@ var MoviesManage = new Class({ ) ) }); - $(self.list).inject(self.el); + $(self.list).inject(self.content); // Check if search is in progress self.startProgressInterval(); diff --git a/couchpotato/core/media/movie/_base/static/page.js b/couchpotato/core/media/movie/_base/static/page.js index 9d8b3e5..5dee981 100644 --- a/couchpotato/core/media/movie/_base/static/page.js +++ b/couchpotato/core/media/movie/_base/static/page.js @@ -13,7 +13,7 @@ Page.Movies = new Class({ self.parent(parent, options); self.navigation = new BlockNavigation(); - $(self.navigation).inject(self.el, 'top'); + $(self.navigation).inject(self.content, 'top'); }, diff --git a/couchpotato/core/media/movie/_base/static/wanted.js b/couchpotato/core/media/movie/_base/static/wanted.js index 86052e3..1ff06ba 100644 --- a/couchpotato/core/media/movie/_base/static/wanted.js +++ b/couchpotato/core/media/movie/_base/static/wanted.js @@ -37,7 +37,7 @@ var MoviesWanted = new Class({ 'menu': [self.manual_search, self.scan_folder], 'on_empty_element': App.createUserscriptButtons().addClass('empty_wanted') }); - $(self.list).inject(self.el); + $(self.list).inject(self.content); // Check if search is in progress self.startProgressInterval.delay(4000, self); @@ -101,7 +101,7 @@ var MoviesWanted = new Class({ }); }; - self.folder_browser.inject(self.el, 'top'); + self.folder_browser.inject(self.content, 'top'); self.folder_browser.fireEvent('injected'); // Hide the settings box diff --git a/couchpotato/core/plugins/log/static/log.js b/couchpotato/core/plugins/log/static/log.js index 915f297..5cc2f50 100644 --- a/couchpotato/core/plugins/log/static/log.js +++ b/couchpotato/core/plugins/log/static/log.js @@ -41,7 +41,7 @@ Page.Log = new Class({ self.showSelectionButton.delay(100, self, e); } } - }).inject(self.el); + }).inject(self.content); Api.request('logging.get', { 'data': { @@ -74,7 +74,7 @@ Page.Log = new Class({ 'events': { 'change': function () { var type_filter = this.getSelected()[0].get('value'); - self.el.set('data-filter', type_filter); + self.content.set('data-filter', type_filter); self.scrollToBottom(); } } @@ -110,7 +110,7 @@ Page.Log = new Class({ }).inject(nav); // Add to page - navigation.inject(self.el, 'top'); + navigation.inject(self.content, 'top'); self.scrollToBottom(); } diff --git a/couchpotato/core/plugins/userscript/static/userscript.js b/couchpotato/core/plugins/userscript/static/userscript.js index e02fc87..7bb6055 100644 --- a/couchpotato/core/plugins/userscript/static/userscript.js +++ b/couchpotato/core/plugins/userscript/static/userscript.js @@ -16,7 +16,7 @@ Page.Userscript = new Class({ indexAction: function(){ var self = this; - self.el.adopt( + self.content.grab( self.frame = new Element('div.frame.loading', { 'text': 'Loading...' }) diff --git a/couchpotato/static/scripts/combined.base.min.js b/couchpotato/static/scripts/combined.base.min.js index 24e102c..86e55db 100644 --- a/couchpotato/static/scripts/combined.base.min.js +++ b/couchpotato/static/scripts/combined.base.min.js @@ -664,11 +664,11 @@ var PageBase = new Class({ self.setOptions(options); self.el = new Element("div", { class: "page " + self.getPageClass() + (" level_" + (options.level || 0)) - }); + }).grab(self.content = new Element("div.content")); App.addEvent("load", function() { setTimeout(function() { if (!App.mobile_screen) { - self.el.addEvent("scroll", self.preventHover.bind(self)); + self.content.addEvent("scroll", self.preventHover.bind(self)); } }, 100); }); @@ -724,8 +724,8 @@ var PageBase = new Class({ elements = self[action + "Action"](params); } if (elements !== undefined) { - self.el.empty(); - self.el.adopt(elements); + self.content.empty(); + self.content.adopt(elements); } App.getBlock("navigation").activate(self.name); self.fireEvent("opened"); @@ -1014,7 +1014,7 @@ Page.Home = new Class({ App.on("movie.searcher.ended", after_search); } }); - $(self.available_list).inject(self.el); + $(self.available_list).inject(self.content); }, createSoon: function() { var self = this; @@ -1036,7 +1036,7 @@ Page.Home = new Class({ self.chain.callChain(); } }); - $(self.soon_list).inject(self.el); + $(self.soon_list).inject(self.content); }, createSuggestions: function() { var self = this; @@ -1055,7 +1055,7 @@ Page.Home = new Class({ self.chain.callChain(); } }); - $(self.suggestions_list).inject(self.el); + $(self.suggestions_list).inject(self.content); }, createCharts: function() { var self = this; @@ -1064,7 +1064,7 @@ Page.Home = new Class({ self.chain.callChain(); } }); - $(self.charts_list).inject(self.el); + $(self.charts_list).inject(self.content); }, createLate: function() { var self = this; @@ -1086,7 +1086,7 @@ Page.Home = new Class({ self.chain.callChain(); } }); - $(self.late_list).inject(self.el); + $(self.late_list).inject(self.content); } }); @@ -1126,7 +1126,7 @@ Page.Settings = new Class({ Object.each(self.params, function(param, subtab_name) { subtab = param; }); - self.el.getElements("li." + c + " , .tab_content." + c).each(function(active) { + self.content.getElements("li." + c + " , .tab_content." + c).each(function(active) { active.removeClass(c); }); if (t.subtabs[subtab]) { @@ -1146,7 +1146,7 @@ Page.Settings = new Class({ if (onComplete) Api.request("settings", { useSpinner: true, spinnerOptions: { - target: self.el + target: self.content }, onComplete: function(json) { self.data = json; @@ -1166,7 +1166,7 @@ Page.Settings = new Class({ showAdvanced: function() { var self = this; var c = self.advanced_toggle.checked ? "addClass" : "removeClass"; - self.el[c]("show_advanced"); + self.content[c]("show_advanced"); Cookie.write("advanced_toggle_checked", +self.advanced_toggle.checked, { duration: 365 }); @@ -1226,7 +1226,7 @@ Page.Settings = new Class({ }); }); setTimeout(function() { - self.el.adopt(self.navigation, self.tabs_container, self.containers); + self.content.adopt(self.navigation, self.tabs_container, self.containers); self.fireEvent("create"); self.openTab(); }, 0); diff --git a/couchpotato/static/scripts/combined.plugins.min.js b/couchpotato/static/scripts/combined.plugins.min.js index 88f8452..07401a7 100644 --- a/couchpotato/static/scripts/combined.plugins.min.js +++ b/couchpotato/static/scripts/combined.plugins.min.js @@ -942,7 +942,7 @@ var MoviesManage = new Class({ } }))) }); - $(self.list).inject(self.el); + $(self.list).inject(self.content); self.startProgressInterval(); } }, @@ -2081,7 +2081,7 @@ Page.Movies = new Class({ var self = this; self.parent(parent, options); self.navigation = new BlockNavigation(); - $(self.navigation).inject(self.el, "top"); + $(self.navigation).inject(self.content, "top"); }, defaultAction: function(action, params) { var self = this; @@ -2289,7 +2289,7 @@ var MoviesWanted = new Class({ menu: [ self.manual_search, self.scan_folder ], on_empty_element: App.createUserscriptButtons().addClass("empty_wanted") }); - $(self.list).inject(self.el); + $(self.list).inject(self.content); self.startProgressInterval.delay(4e3, self); } }, @@ -2336,7 +2336,7 @@ var MoviesWanted = new Class({ } }); }; - self.folder_browser.inject(self.el, "top"); + self.folder_browser.inject(self.content, "top"); self.folder_browser.fireEvent("injected"); self.folder_browser.directory_inlay.hide(); self.folder_browser.el.removeChild(self.folder_browser.el.firstChild); @@ -2982,7 +2982,7 @@ Page.Log = new Class({ self.showSelectionButton.delay(100, self, e); } } - }).inject(self.el); + }).inject(self.content); Api.request("logging.get", { data: { nr: nr @@ -3006,7 +3006,7 @@ Page.Log = new Class({ events: { change: function() { var type_filter = this.getSelected()[0].get("value"); - self.el.set("data-filter", type_filter); + self.content.set("data-filter", type_filter); self.scrollToBottom(); } } @@ -3041,7 +3041,7 @@ Page.Log = new Class({ } } }).inject(nav); - navigation.inject(self.el, "top"); + navigation.inject(self.content, "top"); self.scrollToBottom(); } }); @@ -3626,7 +3626,7 @@ Page.Userscript = new Class({ }, indexAction: function() { var self = this; - self.el.adopt(self.frame = new Element("div.frame.loading", { + self.content.grab(self.frame = new Element("div.frame.loading", { text: "Loading..." })); var url = window.location.href.split("url=")[1]; diff --git a/couchpotato/static/scripts/page.js b/couchpotato/static/scripts/page.js index 9a2cf4c..d68f895 100644 --- a/couchpotato/static/scripts/page.js +++ b/couchpotato/static/scripts/page.js @@ -23,13 +23,15 @@ var PageBase = new Class({ // Create main page container self.el = new Element('div', { 'class': 'page ' + self.getPageClass() + (' level_' + (options.level || 0)) - }); + }).grab( + self.content = new Element('div.content') + ); // Stop hover events while scrolling App.addEvent('load', function(){ setTimeout(function(){ if(!App.mobile_screen){ - self.el.addEvent('scroll', self.preventHover.bind(self)); + self.content.addEvent('scroll', self.preventHover.bind(self)); } }, 100); }); @@ -104,8 +106,8 @@ var PageBase = new Class({ elements = self[action+'Action'](params); } if(elements !== undefined){ - self.el.empty(); - self.el.adopt(elements); + self.content.empty(); + self.content.adopt(elements); } App.getBlock('navigation').activate(self.name); diff --git a/couchpotato/static/scripts/page/home.js b/couchpotato/static/scripts/page/home.js index d37cbc2..9090300 100644 --- a/couchpotato/static/scripts/page/home.js +++ b/couchpotato/static/scripts/page/home.js @@ -79,7 +79,7 @@ Page.Home = new Class({ } }); - $(self.available_list).inject(self.el); + $(self.available_list).inject(self.content); }, @@ -106,7 +106,7 @@ Page.Home = new Class({ } }); - $(self.soon_list).inject(self.el); + $(self.soon_list).inject(self.content); }, @@ -129,7 +129,7 @@ Page.Home = new Class({ } }); - $(self.suggestions_list).inject(self.el); + $(self.suggestions_list).inject(self.content); }, @@ -143,7 +143,7 @@ Page.Home = new Class({ } }); - $(self.charts_list).inject(self.el); + $(self.charts_list).inject(self.content); }, @@ -170,7 +170,7 @@ Page.Home = new Class({ } }); - $(self.late_list).inject(self.el); + $(self.late_list).inject(self.content); } diff --git a/couchpotato/static/scripts/page/settings.js b/couchpotato/static/scripts/page/settings.js index 2ebb442..35842f9 100644 --- a/couchpotato/static/scripts/page/settings.js +++ b/couchpotato/static/scripts/page/settings.js @@ -53,7 +53,7 @@ Page.Settings = new Class({ subtab = param; }); - self.el.getElements('li.'+c+' , .tab_content.'+c).each(function(active){ + self.content.getElements('li.'+c+' , .tab_content.'+c).each(function(active){ active.removeClass(c); }); @@ -83,7 +83,7 @@ Page.Settings = new Class({ Api.request('settings', { 'useSpinner': true, 'spinnerOptions': { - 'target': self.el + 'target': self.content }, 'onComplete': function(json){ self.data = json; @@ -108,7 +108,7 @@ Page.Settings = new Class({ var self = this; var c = self.advanced_toggle.checked ? 'addClass' : 'removeClass'; - self.el[c]('show_advanced'); + self.content[c]('show_advanced'); Cookie.write('advanced_toggle_checked', +self.advanced_toggle.checked, {'duration': 365}); }, @@ -209,7 +209,7 @@ Page.Settings = new Class({ }); setTimeout(function(){ - self.el.adopt( + self.content.adopt( self.navigation, self.tabs_container, self.containers diff --git a/couchpotato/static/style/combined.min.css b/couchpotato/static/style/combined.min.css index e25a1b5..98a406e 100644 --- a/couchpotato/static/style/combined.min.css +++ b/couchpotato/static/style/combined.min.css @@ -565,9 +565,10 @@ input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#F .content h1,.content h2,.content h3{padding:0;margin:0} .content .pages{width:100%} .content .footer{position:fixed;bottom:0;height:20px;width:100%} -.page{position:absolute;top:0;left:0;right:0;bottom:0;display:none;padding:20px 0;overflow:hidden;overflow-y:auto;-webkit-overflow-scrolling:touch} +.page{position:absolute;top:0;left:0;right:0;bottom:0;display:none;padding:20px 0;-webkit-overflow-scrolling:touch} .page.home{padding:0 0 20px} .page.active{display:block} +.page>.content{overflow:hidden;overflow-y:auto} .page h1,.page h2,.page h3,.page h4{font-weight:300} .page h2{font-size:24px;padding:20px} .page .navigation{z-index:2;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center;position:fixed;top:0;height:80px;left:152px;right:20px;background:#FFF;border-radius:3px 0 0} diff --git a/couchpotato/static/style/main.scss b/couchpotato/static/style/main.scss index 2abe5f9..6e1d84e 100644 --- a/couchpotato/static/style/main.scss +++ b/couchpotato/static/style/main.scss @@ -386,8 +386,6 @@ input, textarea, select { bottom: 0; display: none; padding: $padding 0; - overflow: hidden; - overflow-y: auto; -webkit-overflow-scrolling:touch; &.home { @@ -398,6 +396,11 @@ input, textarea, select { display: block; } + > .content { + overflow: hidden; + overflow-y: auto; + } + h1, h2, h3, h4 { font-weight: 300; } From 96a5c2389f96dc86a94ed8bf0a83c3ec1fa71eda Mon Sep 17 00:00:00 2001 From: Ruud Date: Thu, 23 Jul 2015 22:54:18 +0200 Subject: [PATCH 218/301] Use element for delegated click event --- couchpotato/static/scripts/combined.base.min.js | 8 ++++---- couchpotato/static/scripts/couchpotato.js | 8 ++++---- couchpotato/static/style/combined.min.css | 7 +++---- couchpotato/static/style/settings.scss | 2 +- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/couchpotato/static/scripts/combined.base.min.js b/couchpotato/static/scripts/combined.base.min.js index 86e55db..35f56d0 100644 --- a/couchpotato/static/scripts/combined.base.min.js +++ b/couchpotato/static/scripts/combined.base.min.js @@ -215,12 +215,12 @@ var CouchPotato = new Class({ return null; } }, - pushState: function(e) { + pushState: function(e, el) { var self = this; - if (!e.meta && self.isMac() || !e.control && !self.isMac()) { + if (!e.meta && App.isMac() || !e.control && !App.isMac()) { e.preventDefault(); - var url = e.target.get("href"); - if (e.event && e.event.button == 1) window.open(url); else if (History.getPath() != url) History.push(url); + var url = el.get("href"); + if (e.event && e.event.button === 1) window.open(url); else if (History.getPath() != url) History.push(url); } }, isMac: function() { diff --git a/couchpotato/static/scripts/couchpotato.js b/couchpotato/static/scripts/couchpotato.js index dc2c25e..91327d0 100644 --- a/couchpotato/static/scripts/couchpotato.js +++ b/couchpotato/static/scripts/couchpotato.js @@ -82,15 +82,15 @@ } }, - pushState: function(e){ + pushState: function(e, el){ var self = this; - if((!e.meta && self.isMac()) || (!e.control && !self.isMac())){ + if((!e.meta && App.isMac()) || (!e.control && !App.isMac())){ (e).preventDefault(); - var url = e.target.get('href'); + var url = el.get('href'); // Middle click - if(e.event && e.event.button == 1) + if(e.event && e.event.button === 1) window.open(url); else if(History.getPath() != url) History.push(url); diff --git a/couchpotato/static/style/combined.min.css b/couchpotato/static/style/combined.min.css index 98a406e..4c326a9 100644 --- a/couchpotato/static/style/combined.min.css +++ b/couchpotato/static/style/combined.min.css @@ -618,7 +618,6 @@ input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#F .mask .message{color:#FFF;text-align:center;width:320px;margin:-49px 0 0 -160px;font-size:16px} .mask .message h1{font-size:1.5em} .mask .spinner{width:22px;height:22px;display:block;background:#fff;margin-top:-11px;margin-left:-11px;outline:transparent solid 1px;-webkit-animation:rotating 2.5s cubic-bezier(.9,0,.1,1)infinite normal;animation:rotating 2.5s cubic-bezier(.9,0,.1,1)infinite normal;-webkit-transform:scale(0);transform:scale(0)} -.page.settings.active,.table .item{display:-webkit-flex;display:-ms-flexbox} .mask.with_message .spinner{margin-top:-88px} .mask.show{pointer-events:auto;opacity:1} .mask.show .spinner{-webkit-transform:scale(1);transform:scale(1)} @@ -633,15 +632,15 @@ input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#F 100%{-webkit-transform:rotate(720deg)scale(1.6);transform:rotate(720deg)scale(1.6);border-radius:1px} } .table .head{font-weight:700} -.table .item{display:flex;border-bottom:1px solid rgba(0,0,0,.2)} +.table .item{display:-webkit-flex;display:-ms-flexbox;display:flex;border-bottom:1px solid rgba(0,0,0,.2)} .table .item:last-child{border-bottom:none} .table .item span{padding:1px 2px} .table .item span:first-child{padding-left:0} .table .item span:last-child{padding-right:0} .page.settings{top:80px} -.page.settings.active{display:flex} +.page.settings.active .content{display:-webkit-flex;display:-ms-flexbox;display:flex} @media (max-width:480px){.page.settings{top:44px} -.page.settings.active{display:block} +.page.settings.active .content{display:block} } .page.settings .navigation{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between} .page.settings .navigation .advanced_toggle{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center} diff --git a/couchpotato/static/style/settings.scss b/couchpotato/static/style/settings.scss index 8e9cb64..ce83366 100644 --- a/couchpotato/static/style/settings.scss +++ b/couchpotato/static/style/settings.scss @@ -7,7 +7,7 @@ top: $header_width_mobile; } - &.active { + &.active .content { display: flex; @include media-phablet { From 0bd8eb91bc961a59bd37a0620869f80d85c1991a Mon Sep 17 00:00:00 2001 From: Ruud Date: Thu, 23 Jul 2015 23:06:01 +0200 Subject: [PATCH 219/301] Disable hover on correct element --- couchpotato/static/style/combined.min.css | 6 +++--- couchpotato/static/style/main.scss | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/couchpotato/static/style/combined.min.css b/couchpotato/static/style/combined.min.css index 4c326a9..e874888 100644 --- a/couchpotato/static/style/combined.min.css +++ b/couchpotato/static/style/combined.min.css @@ -372,7 +372,7 @@ .page.log .container .debug span{opacity:.6} .page.log[data-filter=DEBUG] .error,.page.log[data-filter=DEBUG] .info,.page.log[data-filter=ERROR] .debug,.page.log[data-filter=ERROR] .info,.page.log[data-filter=INFO] .debug,.page.log[data-filter=INFO] .error{display:none} .report_popup.report_popup{position:fixed;left:0;right:0;bottom:0;top:0;z-index:99999;font-size:14px;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;opacity:1;color:#FFF;pointer-events:auto} -.disable_hover>*,.mask,.ripple{pointer-events:none} +.disable_hover .content>*,.mask,.ripple{pointer-events:none} .report_popup.report_popup .button{display:inline-block;margin:10px 0;padding:10px;color:#FFF} .report_popup.report_popup .bug{width:80%;height:80%;max-height:800px;max-width:800px;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:column nowrap;-ms-flex-flow:column nowrap;flex-flow:column nowrap} .report_popup.report_popup .bug textarea{display:block;width:100%;background:#FFF;padding:20px;overflow:auto;color:#666;height:70%;font-size:12px} @@ -565,10 +565,10 @@ input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#F .content h1,.content h2,.content h3{padding:0;margin:0} .content .pages{width:100%} .content .footer{position:fixed;bottom:0;height:20px;width:100%} -.page{position:absolute;top:0;left:0;right:0;bottom:0;display:none;padding:20px 0;-webkit-overflow-scrolling:touch} +.page{position:absolute;top:0;left:0;right:0;bottom:0;display:none;padding:20px 0} .page.home{padding:0 0 20px} .page.active{display:block} -.page>.content{overflow:hidden;overflow-y:auto} +.page>.content{overflow:hidden;overflow-y:auto;-webkit-overflow-scrolling:touch} .page h1,.page h2,.page h3,.page h4{font-weight:300} .page h2{font-size:24px;padding:20px} .page .navigation{z-index:2;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center;position:fixed;top:0;height:80px;left:152px;right:20px;background:#FFF;border-radius:3px 0 0} diff --git a/couchpotato/static/style/main.scss b/couchpotato/static/style/main.scss index 6e1d84e..09c2d77 100644 --- a/couchpotato/static/style/main.scss +++ b/couchpotato/static/style/main.scss @@ -77,7 +77,7 @@ input, textarea, select { } } -.disable_hover > * { +.disable_hover .content > * { pointer-events: none; } @@ -386,7 +386,6 @@ input, textarea, select { bottom: 0; display: none; padding: $padding 0; - -webkit-overflow-scrolling:touch; &.home { padding: 0 0 $padding; @@ -399,6 +398,7 @@ input, textarea, select { > .content { overflow: hidden; overflow-y: auto; + -webkit-overflow-scrolling:touch; } h1, h2, h3, h4 { From f4f1fd78619247f4b5c4f05abcc6d2bcc173a52a Mon Sep 17 00:00:00 2001 From: Ruud Date: Fri, 24 Jul 2015 21:19:41 +0200 Subject: [PATCH 220/301] Content cutoff on scroll --- couchpotato/static/style/combined.min.css | 7 ++++--- couchpotato/static/style/main.scss | 15 ++++++++++----- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/couchpotato/static/style/combined.min.css b/couchpotato/static/style/combined.min.css index e874888..37c0731 100644 --- a/couchpotato/static/style/combined.min.css +++ b/couchpotato/static/style/combined.min.css @@ -565,10 +565,11 @@ input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#F .content h1,.content h2,.content h3{padding:0;margin:0} .content .pages{width:100%} .content .footer{position:fixed;bottom:0;height:20px;width:100%} -.page{position:absolute;top:0;left:0;right:0;bottom:0;display:none;padding:20px 0} -.page.home{padding:0 0 20px} +.page,.page>.content{top:0;position:absolute;left:0;right:0;bottom:0} +.page{display:none} .page.active{display:block} -.page>.content{overflow:hidden;overflow-y:auto;-webkit-overflow-scrolling:touch} +.page>.content{padding:20px 0;overflow:hidden;overflow-y:auto;-webkit-overflow-scrolling:touch} +.page.home .content{padding:0 0 20px} .page h1,.page h2,.page h3,.page h4{font-weight:300} .page h2{font-size:24px;padding:20px} .page .navigation{z-index:2;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center;position:fixed;top:0;height:80px;left:152px;right:20px;background:#FFF;border-radius:3px 0 0} diff --git a/couchpotato/static/style/main.scss b/couchpotato/static/style/main.scss index 09c2d77..9442c09 100644 --- a/couchpotato/static/style/main.scss +++ b/couchpotato/static/style/main.scss @@ -385,22 +385,27 @@ input, textarea, select { right: 0; bottom: 0; display: none; - padding: $padding 0; - - &.home { - padding: 0 0 $padding; - } &.active { display: block; } > .content { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + padding: $padding 0; overflow: hidden; overflow-y: auto; -webkit-overflow-scrolling:touch; } + &.home .content { + padding: 0 0 $padding; + } + h1, h2, h3, h4 { font-weight: 300; } From c8e411126a3f6b6a06a7534d3b272c79f0359693 Mon Sep 17 00:00:00 2001 From: Ruud Date: Fri, 24 Jul 2015 21:47:18 +0200 Subject: [PATCH 221/301] Content scrolling slow on mobiel --- .../core/media/movie/_base/static/details.js | 2 +- .../core/media/movie/_base/static/movie.scss | 4 +- couchpotato/static/scripts/combined.base.min.js | 2 +- couchpotato/static/scripts/combined.plugins.min.js | 2 +- couchpotato/static/scripts/page.js | 2 +- couchpotato/static/style/combined.min.css | 81 +++++++++++----------- couchpotato/static/style/main.scss | 13 ++-- couchpotato/static/style/settings.scss | 2 +- 8 files changed, 52 insertions(+), 56 deletions(-) diff --git a/couchpotato/core/media/movie/_base/static/details.js b/couchpotato/core/media/movie/_base/static/details.js index 33f65d0..c6230bd 100644 --- a/couchpotato/core/media/movie/_base/static/details.js +++ b/couchpotato/core/media/movie/_base/static/details.js @@ -23,7 +23,7 @@ var MovieDetails = new Class({ }).grab( new Element('a.close.icon-left-arrow') ), - self.content = new Element('div.content').grab( + self.content = new Element('div.scroll_content').grab( new Element('div.head').adopt( new Element('h1').grab( self.title_dropdown = new BlockMenu(self, { diff --git a/couchpotato/core/media/movie/_base/static/movie.scss b/couchpotato/core/media/movie/_base/static/movie.scss index 0c10685..9c26eea 100644 --- a/couchpotato/core/media/movie/_base/static/movie.scss +++ b/couchpotato/core/media/movie/_base/static/movie.scss @@ -512,7 +512,7 @@ $mass_edit_height: 44px; } } - .content { + .scroll_content { position: fixed; z-index: 2; top: 0; @@ -700,7 +700,7 @@ $mass_edit_height: 44px; } } - .content { + .scroll_content { transition-delay: 200ms; transform: translateX(0); } diff --git a/couchpotato/static/scripts/combined.base.min.js b/couchpotato/static/scripts/combined.base.min.js index 35f56d0..accc82a 100644 --- a/couchpotato/static/scripts/combined.base.min.js +++ b/couchpotato/static/scripts/combined.base.min.js @@ -664,7 +664,7 @@ var PageBase = new Class({ self.setOptions(options); self.el = new Element("div", { class: "page " + self.getPageClass() + (" level_" + (options.level || 0)) - }).grab(self.content = new Element("div.content")); + }).grab(self.content = new Element("div.scroll_content")); App.addEvent("load", function() { setTimeout(function() { if (!App.mobile_screen) { diff --git a/couchpotato/static/scripts/combined.plugins.min.js b/couchpotato/static/scripts/combined.plugins.min.js index 07401a7..236f312 100644 --- a/couchpotato/static/scripts/combined.plugins.min.js +++ b/couchpotato/static/scripts/combined.plugins.min.js @@ -328,7 +328,7 @@ var MovieDetails = new Class({ events: { click: self.close.bind(self) } - }).grab(new Element("a.close.icon-left-arrow")), self.content = new Element("div.content").grab(new Element("div.head").adopt(new Element("h1").grab(self.title_dropdown = new BlockMenu(self, { + }).grab(new Element("a.close.icon-left-arrow")), self.content = new Element("div.scroll_content").grab(new Element("div.head").adopt(new Element("h1").grab(self.title_dropdown = new BlockMenu(self, { class: "title", button_text: parent.getTitle() + (parent.get("year") ? " (" + parent.get("year") + ")" : ""), button_class: "icon-dropdown" diff --git a/couchpotato/static/scripts/page.js b/couchpotato/static/scripts/page.js index d68f895..42bdaac 100644 --- a/couchpotato/static/scripts/page.js +++ b/couchpotato/static/scripts/page.js @@ -24,7 +24,7 @@ var PageBase = new Class({ self.el = new Element('div', { 'class': 'page ' + self.getPageClass() + (' level_' + (options.level || 0)) }).grab( - self.content = new Element('div.content') + self.content = new Element('div.scroll_content') ); // Stop hover events while scrolling diff --git a/couchpotato/static/style/combined.min.css b/couchpotato/static/style/combined.min.css index 37c0731..a1e34dd 100644 --- a/couchpotato/static/style/combined.min.css +++ b/couchpotato/static/style/combined.min.css @@ -153,45 +153,45 @@ @media (max-width:480px){.page.movie_details .overlay{left:0;border-radius:0} .page.movie_details .overlay .close{width:44px} } -.page.movie_details .content{position:fixed;z-index:2;top:0;bottom:0;right:0;left:176px;background:#FFF;border-radius:3px 0 0 3px;overflow-y:auto;-webkit-transform:translateX(100%)translateZ(0);transform:translateX(100%)translateZ(0);transition:-webkit-transform 350ms cubic-bezier(.9,0,.1,1);transition:transform 350ms cubic-bezier(.9,0,.1,1)} -.page.movie_details .content>.head{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row nowrap;-ms-flex-flow:row nowrap;flex-flow:row nowrap;padding:0 20px;position:relative;z-index:2} -@media (max-width:480px){.page.movie_details .content{left:44px} -.page.movie_details .content>.head{-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:0 10px;line-height:1em} -} -.page.movie_details .content>.head h1{-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto;margin:0;font-size:24px;color:rgba(0,0,0,.5);font-weight:300;max-width:100%} -@media (max-width:480px){.page.movie_details .content>.head h1{min-width:100%;line-height:44px} -.page.movie_details .content>.head h1 .more_menu{width:100%} -} -.page.movie_details .content>.head .more_menu{display:inline-block;vertical-align:top;max-width:100%;margin-bottom:0} -.page.movie_details .content>.head .more_menu>a{line-height:80px} -.page.movie_details .content>.head .more_menu .icon-dropdown{position:relative;padding:0 20px 0 10px} -.page.movie_details .content>.head .more_menu .icon-dropdown:before{position:absolute;right:0;opacity:.2} -.page.movie_details .content>.head .more_menu .icon-dropdown:hover:before{opacity:1} -.page.movie_details .content>.head .more_menu .wrapper{top:70px;padding-top:4px;border-radius:3px 3px 0 0;font-size:14px} -@media (max-width:480px){.page.movie_details .content>.head .more_menu>a{line-height:1em} -.page.movie_details .content>.head .more_menu .wrapper{top:25px} -} -.page.movie_details .content>.head .more_menu .wrapper:before{top:0;left:auto;right:22px} -.page.movie_details .content>.head .more_menu .wrapper ul{border-radius:3px 3px 0 0;max-height:215px;overflow-y:auto} -.page.movie_details .content>.head .more_menu .wrapper a{padding-right:30px} -.page.movie_details .content>.head .more_menu .wrapper a:before{position:absolute;right:10px} -.page.movie_details .content>.head .more_menu .wrapper a.icon-ok,.page.movie_details .content>.head .more_menu .wrapper a:hover{color:#ac0000} -.page.movie_details .content>.head .more_menu.title>a{display:inline-block;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;width:100%} -.page.movie_details .content>.head .more_menu.title .wrapper{-webkit-transform-origin:0 0;transform-origin:0 0;left:0;right:auto} -.page.movie_details .content>.head .more_menu.title .wrapper:before{left:22px;right:auto} -.page.movie_details .content>.head .buttons{display:-webkit-flex;display:-ms-flexbox;display:flex} -.page.movie_details .content>.head .buttons>a{display:inline-block;padding:0 10px;color:#ac0000;line-height:80px} -.page.movie_details .content>.head .buttons>a:hover{color:#000} -.page.movie_details .content .section{padding:20px;border-top:1px solid rgba(0,0,0,.1)} -@media (max-width:480px){.page.movie_details .content>.head .more_menu.title .wrapper{top:30px;max-width:240px} -.page.movie_details .content>.head .buttons{margin-left:auto} -.page.movie_details .content .section{padding:10px} +.page.movie_details .scroll_content{position:fixed;z-index:2;top:0;bottom:0;right:0;left:176px;background:#FFF;border-radius:3px 0 0 3px;overflow-y:auto;-webkit-transform:translateX(100%)translateZ(0);transform:translateX(100%)translateZ(0);transition:-webkit-transform 350ms cubic-bezier(.9,0,.1,1);transition:transform 350ms cubic-bezier(.9,0,.1,1)} +.page.movie_details .scroll_content>.head{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row nowrap;-ms-flex-flow:row nowrap;flex-flow:row nowrap;padding:0 20px;position:relative;z-index:2} +@media (max-width:480px){.page.movie_details .scroll_content{left:44px} +.page.movie_details .scroll_content>.head{-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:0 10px;line-height:1em} +} +.page.movie_details .scroll_content>.head h1{-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto;margin:0;font-size:24px;color:rgba(0,0,0,.5);font-weight:300;max-width:100%} +@media (max-width:480px){.page.movie_details .scroll_content>.head h1{min-width:100%;line-height:44px} +.page.movie_details .scroll_content>.head h1 .more_menu{width:100%} +} +.page.movie_details .scroll_content>.head .more_menu{display:inline-block;vertical-align:top;max-width:100%;margin-bottom:0} +.page.movie_details .scroll_content>.head .more_menu>a{line-height:80px} +.page.movie_details .scroll_content>.head .more_menu .icon-dropdown{position:relative;padding:0 20px 0 10px} +.page.movie_details .scroll_content>.head .more_menu .icon-dropdown:before{position:absolute;right:0;opacity:.2} +.page.movie_details .scroll_content>.head .more_menu .icon-dropdown:hover:before{opacity:1} +.page.movie_details .scroll_content>.head .more_menu .wrapper{top:70px;padding-top:4px;border-radius:3px 3px 0 0;font-size:14px} +@media (max-width:480px){.page.movie_details .scroll_content>.head .more_menu>a{line-height:1em} +.page.movie_details .scroll_content>.head .more_menu .wrapper{top:25px} +} +.page.movie_details .scroll_content>.head .more_menu .wrapper:before{top:0;left:auto;right:22px} +.page.movie_details .scroll_content>.head .more_menu .wrapper ul{border-radius:3px 3px 0 0;max-height:215px;overflow-y:auto} +.page.movie_details .scroll_content>.head .more_menu .wrapper a{padding-right:30px} +.page.movie_details .scroll_content>.head .more_menu .wrapper a:before{position:absolute;right:10px} +.page.movie_details .scroll_content>.head .more_menu .wrapper a.icon-ok,.page.movie_details .scroll_content>.head .more_menu .wrapper a:hover{color:#ac0000} +.page.movie_details .scroll_content>.head .more_menu.title>a{display:inline-block;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;width:100%} +.page.movie_details .scroll_content>.head .more_menu.title .wrapper{-webkit-transform-origin:0 0;transform-origin:0 0;left:0;right:auto} +.page.movie_details .scroll_content>.head .more_menu.title .wrapper:before{left:22px;right:auto} +.page.movie_details .scroll_content>.head .buttons{display:-webkit-flex;display:-ms-flexbox;display:flex} +.page.movie_details .scroll_content>.head .buttons>a{display:inline-block;padding:0 10px;color:#ac0000;line-height:80px} +.page.movie_details .scroll_content>.head .buttons>a:hover{color:#000} +.page.movie_details .scroll_content .section{padding:20px;border-top:1px solid rgba(0,0,0,.1)} +@media (max-width:480px){.page.movie_details .scroll_content>.head .more_menu.title .wrapper{top:30px;max-width:240px} +.page.movie_details .scroll_content>.head .buttons{margin-left:auto} +.page.movie_details .scroll_content .section{padding:10px} } .page.movie_details .files span,.page.movie_details .releases .item span{text-overflow:ellipsis;overflow:hidden;white-space:nowrap;padding:6.67px 0} .page.movie_details.show{pointer-events:auto} .page.movie_details.show .overlay{opacity:1;transition-delay:0s} .page.movie_details.show .overlay .close{opacity:1;transition-delay:300ms} -.page.movie_details.show .content{transition-delay:200ms;-webkit-transform:translateX(0);transform:translateX(0)} +.page.movie_details.show .scroll_content{transition-delay:200ms;-webkit-transform:translateX(0);transform:translateX(0)} .page.movie_details .section_add{background:#eaeaea} .page.movie_details .section_add .options>div{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center} .page.movie_details .section_add .options>div select{display:block;width:100%} @@ -372,7 +372,7 @@ .page.log .container .debug span{opacity:.6} .page.log[data-filter=DEBUG] .error,.page.log[data-filter=DEBUG] .info,.page.log[data-filter=ERROR] .debug,.page.log[data-filter=ERROR] .info,.page.log[data-filter=INFO] .debug,.page.log[data-filter=INFO] .error{display:none} .report_popup.report_popup{position:fixed;left:0;right:0;bottom:0;top:0;z-index:99999;font-size:14px;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;opacity:1;color:#FFF;pointer-events:auto} -.disable_hover .content>*,.mask,.ripple{pointer-events:none} +.disable_hover .scroll_content>*,.mask,.ripple{pointer-events:none} .report_popup.report_popup .button{display:inline-block;margin:10px 0;padding:10px;color:#FFF} .report_popup.report_popup .bug{width:80%;height:80%;max-height:800px;max-width:800px;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:column nowrap;-ms-flex-flow:column nowrap;flex-flow:column nowrap} .report_popup.report_popup .bug textarea{display:block;width:100%;background:#FFF;padding:20px;overflow:auto;color:#666;height:70%;font-size:12px} @@ -565,11 +565,10 @@ input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#F .content h1,.content h2,.content h3{padding:0;margin:0} .content .pages{width:100%} .content .footer{position:fixed;bottom:0;height:20px;width:100%} -.page,.page>.content{top:0;position:absolute;left:0;right:0;bottom:0} -.page{display:none} +.page{position:absolute;top:0;left:0;right:0;bottom:0;display:none} .page.active{display:block} -.page>.content{padding:20px 0;overflow:hidden;overflow-y:auto;-webkit-overflow-scrolling:touch} -.page.home .content{padding:0 0 20px} +.page>.scroll_content{position:relative;height:100%;padding:20px 0;overflow:hidden;overflow-y:auto;-webkit-overflow-scrolling:touch} +.page.home .scroll_content{padding:0 0 20px} .page h1,.page h2,.page h3,.page h4{font-weight:300} .page h2{font-size:24px;padding:20px} .page .navigation{z-index:2;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center;position:fixed;top:0;height:80px;left:152px;right:20px;background:#FFF;border-radius:3px 0 0} @@ -639,9 +638,9 @@ input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#F .table .item span:first-child{padding-left:0} .table .item span:last-child{padding-right:0} .page.settings{top:80px} -.page.settings.active .content{display:-webkit-flex;display:-ms-flexbox;display:flex} +.page.settings.active .scroll_content{display:-webkit-flex;display:-ms-flexbox;display:flex} @media (max-width:480px){.page.settings{top:44px} -.page.settings.active .content{display:block} +.page.settings.active .scroll_content{display:block} } .page.settings .navigation{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between} .page.settings .navigation .advanced_toggle{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center} diff --git a/couchpotato/static/style/main.scss b/couchpotato/static/style/main.scss index 9442c09..53cab9f 100644 --- a/couchpotato/static/style/main.scss +++ b/couchpotato/static/style/main.scss @@ -77,7 +77,7 @@ input, textarea, select { } } -.disable_hover .content > * { +.disable_hover .scroll_content > * { pointer-events: none; } @@ -390,19 +390,16 @@ input, textarea, select { display: block; } - > .content { - position: absolute; - top: 0; - left: 0; - right: 0; - bottom: 0; + > .scroll_content { + position: relative; + height: 100%; padding: $padding 0; overflow: hidden; overflow-y: auto; -webkit-overflow-scrolling:touch; } - &.home .content { + &.home .scroll_content { padding: 0 0 $padding; } diff --git a/couchpotato/static/style/settings.scss b/couchpotato/static/style/settings.scss index ce83366..e56dadb 100644 --- a/couchpotato/static/style/settings.scss +++ b/couchpotato/static/style/settings.scss @@ -7,7 +7,7 @@ top: $header_width_mobile; } - &.active .content { + &.active .scroll_content { display: flex; @include media-phablet { From 8f54638cc59ed14b07f8c46590a9cf441df3af71 Mon Sep 17 00:00:00 2001 From: Ruud Date: Fri, 24 Jul 2015 22:48:32 +0200 Subject: [PATCH 222/301] Search not triggered on mobile --- couchpotato/core/media/_base/search/static/search.js | 3 +-- couchpotato/core/media/movie/_base/static/movie.scss | 5 +++++ couchpotato/static/scripts/combined.plugins.min.js | 3 +-- couchpotato/static/style/combined.min.css | 10 +++++----- couchpotato/static/style/main.scss | 6 +++--- 5 files changed, 15 insertions(+), 12 deletions(-) diff --git a/couchpotato/core/media/_base/search/static/search.js b/couchpotato/core/media/_base/search/static/search.js index 733147e..7c2bc24 100644 --- a/couchpotato/core/media/_base/search/static/search.js +++ b/couchpotato/core/media/_base/search/static/search.js @@ -11,8 +11,7 @@ var BlockSearch = new Class({ self.el = new Element('div.search_form').adopt( new Element('a.icon-search', { 'events': { - 'click': self.clear.bind(self), - 'touchend': self.clear.bind(self) + 'click': self.clear.bind(self) } }), self.wrapper = new Element('div.wrapper').adopt( diff --git a/couchpotato/core/media/movie/_base/static/movie.scss b/couchpotato/core/media/movie/_base/static/movie.scss index 9c26eea..15aa2e4 100644 --- a/couchpotato/core/media/movie/_base/static/movie.scss +++ b/couchpotato/core/media/movie/_base/static/movie.scss @@ -35,6 +35,10 @@ $mass_edit_height: 44px; } .movie { + .ripple { + display: none; + } + input[type=checkbox] { display: none; } @@ -343,6 +347,7 @@ $mass_edit_height: 44px; @include media-phablet { max-width: 50%; + width: 50%; border-width: 0 $padding/5; } diff --git a/couchpotato/static/scripts/combined.plugins.min.js b/couchpotato/static/scripts/combined.plugins.min.js index 236f312..8a04857 100644 --- a/couchpotato/static/scripts/combined.plugins.min.js +++ b/couchpotato/static/scripts/combined.plugins.min.js @@ -184,8 +184,7 @@ var BlockSearch = new Class({ var focus_timer = 0; self.el = new Element("div.search_form").adopt(new Element("a.icon-search", { events: { - click: self.clear.bind(self), - touchend: self.clear.bind(self) + click: self.clear.bind(self) } }), self.wrapper = new Element("div.wrapper").adopt(self.result_container = new Element("div.results_container", { events: { diff --git a/couchpotato/static/style/combined.min.css b/couchpotato/static/style/combined.min.css index a1e34dd..96d97d1 100644 --- a/couchpotato/static/style/combined.min.css +++ b/couchpotato/static/style/combined.min.css @@ -58,7 +58,7 @@ } .mass_editing .page.movies_manage,.mass_editing .page.movies_wanted{top:124px} .page.movies_manage .load_more,.page.movies_wanted .load_more{text-align:center;padding:20px;font-size:2em;display:block} -.movie input[type=checkbox]{display:none} +.movie .ripple,.movie input[type=checkbox]{display:none} .with_navigation .movie input[type=checkbox]{display:inline-block;position:absolute;transition:opacity 200ms;opacity:0;z-index:2;cursor:pointer} .with_navigation .movie input[type=checkbox]:hover{opacity:1!important} .with_navigation .movie:hover input[type=checkbox]{opacity:.5} @@ -123,7 +123,7 @@ .thumb_list .movie{max-width:33.333%;border-width:0 5px} } @media (max-width:480px){.thumb_list{padding:0 3.33px} -.thumb_list .movie{max-width:50%;border-width:0 4px} +.thumb_list .movie{max-width:50%;width:50%;border-width:0 4px} } .thumb_list .movie input[type=checkbox]{top:10px;left:10px} .thumb_list .movie .poster{position:relative;border-radius:3px;background:center no-repeat #eaeaea;background-size:cover;overflow:hidden;width:100%;padding-bottom:150%} @@ -511,8 +511,8 @@ a{display:inline-block;position:relative;overflow:hidden;text-decoration:none;cu input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#FFF;border:1px solid #b7b7b7} .button{color:#ac0000;font-weight:300;padding:5px;cursor:pointer;border:1px solid #ac0000;border-radius:3px;margin:0 5px;transition:all 150ms} .button:hover{background:#ac0000;color:#FFF} -.ripple{position:absolute;height:10px;width:10px;border-radius:50%;background:#ac0000;-webkit-transform:translate(-50%,-50%)scale(1)translateZ(0);transform:translate(-50%,-50%)scale(1)translateZ(0);opacity:.2;transition:all 1.5s ease} -.ripple.animate{-webkit-transform:translate(-50%,-50%)scale(100)translateZ(0);transform:translate(-50%,-50%)scale(100)translateZ(0);opacity:0} +.ripple{position:absolute;height:10px;width:10px;border-radius:50%;background:#ac0000;-webkit-transform:translate(-50%,-50%)scale(1)rotateZ(360deg);transform:translate(-50%,-50%)scale(1)rotateZ(360deg);opacity:.2;transition:all 1.5s ease;transition-property:opacity,-webkit-transform;transition-property:opacity,transform} +.ripple.animate{-webkit-transform:translate(-50%,-50%)scale(100)rotateZ(360deg);transform:translate(-50%,-50%)scale(100)rotateZ(360deg);opacity:0} .header{width:132px;min-width:132px;position:relative;z-index:100} @media (max-width:480px){.header{width:44px;min-width:44px;z-index:21} } @@ -567,7 +567,7 @@ input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#F .content .footer{position:fixed;bottom:0;height:20px;width:100%} .page{position:absolute;top:0;left:0;right:0;bottom:0;display:none} .page.active{display:block} -.page>.scroll_content{position:relative;height:100%;padding:20px 0;overflow:hidden;overflow-y:auto;-webkit-overflow-scrolling:touch} +.page>.scroll_content{position:relative;height:100%;overflow:hidden;overflow-y:auto;-webkit-overflow-scrolling:touch} .page.home .scroll_content{padding:0 0 20px} .page h1,.page h2,.page h3,.page h4{font-weight:300} .page h2{font-size:24px;padding:20px} diff --git a/couchpotato/static/style/main.scss b/couchpotato/static/style/main.scss index 53cab9f..3895af2 100644 --- a/couchpotato/static/style/main.scss +++ b/couchpotato/static/style/main.scss @@ -66,13 +66,14 @@ input, textarea, select { width: 10px; border-radius: 50%; background: $primary_color; - transform: translate(-50%, -50%) scale(1) translateZ(0); + transform: translate(-50%, -50%) scale(1) rotateZ(360deg); opacity: 0.2; transition: all 1.5s ease; + transition-property: opacity, transform; pointer-events: none; &.animate { - transform: translate(-50%, -50%) scale(100) translateZ(0); + transform: translate(-50%, -50%) scale(100) rotateZ(360deg); opacity: 0; } } @@ -393,7 +394,6 @@ input, textarea, select { > .scroll_content { position: relative; height: 100%; - padding: $padding 0; overflow: hidden; overflow-y: auto; -webkit-overflow-scrolling:touch; From b44a18a1de174c842a5f52ddbcfa0b8e8b44f0c3 Mon Sep 17 00:00:00 2001 From: josh4trunks Date: Sat, 1 Aug 2015 22:23:39 -0700 Subject: [PATCH 223/301] Add Emby notifications --- couchpotato/core/notifications/emby.py | 89 ++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 couchpotato/core/notifications/emby.py diff --git a/couchpotato/core/notifications/emby.py b/couchpotato/core/notifications/emby.py new file mode 100644 index 0000000..15bd64d --- /dev/null +++ b/couchpotato/core/notifications/emby.py @@ -0,0 +1,89 @@ +import json +import urllib, urllib2 + +from couchpotato.core.helpers.variable import cleanHost +from couchpotato.core.logger import CPLog +from couchpotato.core.notifications.base import Notification + + +log = CPLog(__name__) + +autoload = 'Emby' + + +class Emby(Notification): + + def notify(self, message = '', data = None, listener = None): + host = self.conf('host') + apikey = self.conf('apikey') + + host = cleanHost(host) + url = '%semby/Library/Series/Updated' % (host) + values = {} + data = urllib.urlencode(values) + + try: + req = urllib2.Request(url, data) + req.add_header('X-MediaBrowser-Token', apikey) + + response = urllib2.urlopen(req) + result = response.read() + response.close() + return True + + except (urllib2.URLError, IOError), e: + return False + + def test(self, **kwargs): + host = self.conf('host') + apikey = self.conf('apikey') + message = self.test_message + + host = cleanHost(host) + url = '%semby/Notifications/Admin' % (host) + values = {'Name': 'CouchPotato', 'Description': message, 'ImageUrl': 'https://raw.githubusercontent.com/RuudBurger/CouchPotatoServer/master/couchpotato/static/images/notify.couch.small.png'} + data = json.dumps(values) + + try: + req = urllib2.Request(url, data) + req.add_header('X-MediaBrowser-Token', apikey) + req.add_header('Content-Type', 'application/json') + + response = urllib2.urlopen(req) + result = response.read() + response.close() + return { + 'success': True + } + + except (urllib2.URLError, IOError), e: + return False + + +config = [{ + 'name': 'emby', + 'groups': [ + { + 'tab': 'notifications', + 'list': 'notification_providers', + 'name': 'emby', + 'options': [ + { + 'name': 'enabled', + 'default': 0, + 'type': 'enabler', + }, + { + 'name': 'host', + 'default': 'localhost:8096', + 'description': 'IP:Port, default localhost:8096' + }, + { + 'name': 'apikey', + 'label': 'API Key', + 'default': '', + }, + ], + } + ], +}] From 49f6eb4be205d7fcf0bf18dd8c4538614797a7aa Mon Sep 17 00:00:00 2001 From: Ruud Date: Fri, 7 Aug 2015 13:07:11 +0200 Subject: [PATCH 224/301] Package update --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index c73d92a..785f5a8 100644 --- a/package.json +++ b/package.json @@ -11,8 +11,8 @@ "devDependencies": { "grunt": "~0.4.5", "grunt-autoprefixer": "^3.0.3", - "grunt-concurrent": "~2.0.0", - "grunt-contrib-cssmin": "~0.12.3", + "grunt-concurrent": "~2.0.1", + "grunt-contrib-cssmin": "~0.13.0", "grunt-contrib-jshint": "~0.11.2", "grunt-contrib-sass": "^0.9.2", "grunt-contrib-uglify": "~0.9.1", From 2abd0c636f602467f1a3adb60b30346f27d4ce03 Mon Sep 17 00:00:00 2001 From: Ruud Date: Fri, 7 Aug 2015 13:07:40 +0200 Subject: [PATCH 225/301] Nicer size --- .../core/media/movie/_base/static/movie.actions.js | 5 +- couchpotato/static/scripts/combined.plugins.min.js | 4 +- couchpotato/static/style/combined.min.css | 147 ++++++++++----------- 3 files changed, 78 insertions(+), 78 deletions(-) diff --git a/couchpotato/core/media/movie/_base/static/movie.actions.js b/couchpotato/core/media/movie/_base/static/movie.actions.js index 2228e9b..9131ea2 100644 --- a/couchpotato/core/media/movie/_base/static/movie.actions.js +++ b/couchpotato/core/media/movie/_base/static/movie.actions.js @@ -194,6 +194,9 @@ MA.Release = new Class({ catch(e){} } + var size = info.size ? Math.floor(self.get(release, 'size')) : 0; + size = size ? ((size < 1000) ? size + 'MB' : Math.round(size*10/1024)/10 + 'GB') : 'n/a'; + // Create release release.el = new Element('div', { 'class': 'item '+release.status, @@ -202,7 +205,7 @@ MA.Release = new Class({ new Element('span.name', {'text': release_name, 'title': release_name}), new Element('span.status', {'text': release.status, 'class': 'status '+release.status}), new Element('span.quality', {'text': quality.label + (release.is_3d ? ' 3D' : '') || 'n/a'}), - new Element('span.size', {'text': info.size ? Math.floor(self.get(release, 'size')) : 'n/a'}), + new Element('span.size', {'text': size}), new Element('span.age', {'text': self.get(release, 'age')}), new Element('span.score', {'text': self.get(release, 'score')}), new Element('span.provider', { 'text': provider, 'title': provider }), diff --git a/couchpotato/static/scripts/combined.plugins.min.js b/couchpotato/static/scripts/combined.plugins.min.js index 8a04857..3282432 100644 --- a/couchpotato/static/scripts/combined.plugins.min.js +++ b/couchpotato/static/scripts/combined.plugins.min.js @@ -1147,6 +1147,8 @@ MA.Release = new Class({ release_name = movie_file.path.split(Api.getOption("path_sep")).getLast(); } catch (e) {} } + var size = info.size ? Math.floor(self.get(release, "size")) : 0; + size = size ? size < 1e3 ? size + "MB" : Math.round(size * 10 / 1024) / 10 + "GB" : "n/a"; release.el = new Element("div", { class: "item " + release.status, id: "release_" + release._id @@ -1159,7 +1161,7 @@ MA.Release = new Class({ }), new Element("span.quality", { text: quality.label + (release.is_3d ? " 3D" : "") || "n/a" }), new Element("span.size", { - text: info.size ? Math.floor(self.get(release, "size")) : "n/a" + text: size }), new Element("span.age", { text: self.get(release, "age") }), new Element("span.score", { diff --git a/couchpotato/static/style/combined.min.css b/couchpotato/static/style/combined.min.css index 96d97d1..5b9b3d5 100644 --- a/couchpotato/static/style/combined.min.css +++ b/couchpotato/static/style/combined.min.css @@ -1,4 +1,4 @@ -.movies>.description a:hover,.page.movie_details .releases .buttons a:hover,.page.settings fieldset h2 .hint a{text-decoration:underline} +.movies>.description a:hover,.page.movie_details .releases .buttons a:hover{text-decoration:underline} .search_form{display:inline-block;z-index:11;width:44px;position:relative} .search_form *{-webkit-transform:translateZ(0);transform:translateZ(0)} .search_form .icon-search{position:absolute;z-index:2;top:50%;left:0;height:100%;text-align:center;color:#FFF;font-size:20px;-webkit-transform:translateY(-50%);transform:translateY(-50%)} @@ -145,15 +145,14 @@ @media (max-width:480px){.thumb_list .movie:hover .actions{display:none} .page.movie_details{left:0} } -.page.movie_details .overlay{position:fixed;top:0;bottom:0;right:0;left:132px;background:rgba(0,0,0,.6);border-radius:3px 0 0 3px;opacity:0;-webkit-transform:translateZ(0);transform:translateZ(0);backface-visibility:hidden;transition:opacity 300ms ease 400ms;z-index:1} -*,.page.movie_details .overlay{-webkit-backface-visibility:hidden} +.page.movie_details .overlay{position:fixed;top:0;bottom:0;right:0;left:132px;background:rgba(0,0,0,.6);border-radius:3px 0 0 3px;opacity:0;-webkit-transform:translateZ(0);transform:translateZ(0);-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:opacity 300ms ease 400ms;z-index:1} .page.movie_details .overlay .ripple{background:#FFF} .page.movie_details .overlay .close{display:inline-block;text-align:center;font-size:60px;line-height:80px;color:#FFF;width:100%;height:100%;opacity:0;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:opacity 300ms ease 200ms} .page.movie_details .overlay .close:before{display:block;width:44px} @media (max-width:480px){.page.movie_details .overlay{left:0;border-radius:0} .page.movie_details .overlay .close{width:44px} } -.page.movie_details .scroll_content{position:fixed;z-index:2;top:0;bottom:0;right:0;left:176px;background:#FFF;border-radius:3px 0 0 3px;overflow-y:auto;-webkit-transform:translateX(100%)translateZ(0);transform:translateX(100%)translateZ(0);transition:-webkit-transform 350ms cubic-bezier(.9,0,.1,1);transition:transform 350ms cubic-bezier(.9,0,.1,1)} +.page.movie_details .scroll_content{position:fixed;z-index:2;top:0;bottom:0;right:0;left:176px;background:#FFF;border-radius:3px 0 0 3px;overflow-y:auto;-webkit-transform:translateX(100%) translateZ(0);transform:translateX(100%) translateZ(0);transition:-webkit-transform 350ms cubic-bezier(.9,0,.1,1);transition:transform 350ms cubic-bezier(.9,0,.1,1)} .page.movie_details .scroll_content>.head{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row nowrap;-ms-flex-flow:row nowrap;flex-flow:row nowrap;padding:0 20px;position:relative;z-index:2} @media (max-width:480px){.page.movie_details .scroll_content{left:44px} .page.movie_details .scroll_content>.head{-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:0 10px;line-height:1em} @@ -187,7 +186,7 @@ .page.movie_details .scroll_content>.head .buttons{margin-left:auto} .page.movie_details .scroll_content .section{padding:10px} } -.page.movie_details .files span,.page.movie_details .releases .item span{text-overflow:ellipsis;overflow:hidden;white-space:nowrap;padding:6.67px 0} +.page.movie_details .files span,.page.movie_details .releases .item span{white-space:nowrap;padding:6.67px 0;overflow:hidden;text-overflow:ellipsis} .page.movie_details.show{pointer-events:auto} .page.movie_details.show .overlay{opacity:1;transition-delay:0s} .page.movie_details.show .overlay .close{opacity:1;transition-delay:300ms} @@ -245,7 +244,7 @@ .page.movie_details .releases .actions{min-width:80px;max-width:80px} .page.movie_details .trailer_container{background:#000;position:relative;padding-bottom:56.25%;height:0;overflow:hidden;max-width:100%;cursor:pointer} .alph_nav .menus .button,.alph_nav .menus .counter{line-height:80px;padding:0 10px} -.page.movie_details .trailer_container .background{opacity:.3;transition:all 300ms;-webkit-transform:scale(1.05)translateZ(0);transform:scale(1.05)translateZ(0);background:center no-repeat;background-size:cover;position:absolute;top:0;right:0;bottom:0;left:0;z-index:1} +.page.movie_details .trailer_container .background{opacity:.3;transition:all 300ms;-webkit-transform:scale(1.05) translateZ(0);transform:scale(1.05) translateZ(0);background:center no-repeat;background-size:cover;position:absolute;top:0;right:0;bottom:0;left:0;z-index:1} .page.movie_details .trailer_container .icon-play{opacity:.9;position:absolute;z-index:2;text-align:center;width:100%;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);transition:all 300ms;color:#FFF;font-size:110px} @media (max-width:1024px){.page.movie_details .trailer_container .icon-play{font-size:55px} } @@ -306,7 +305,6 @@ @media all and (max-width:600px){.charts .chart{width:100%} } .charts .chart .media_result .data{left:150px;background:#4e5969;border:none} -#category_ordering li:last-child,#profile_ordering li:last-child,.api_docs .api .params tr:last-child td,.api_docs .api .params tr:last-child th{border:0} .charts .chart .media_result .data .info{top:10px;left:15px;right:15px;bottom:10px;overflow:hidden} .charts .chart .media_result .data .info h2{white-space:normal;max-height:120px;font-size:18px;line-height:18px} .charts .chart .media_result .data .info .genres,.charts .chart .media_result .data .info .rating,.charts .chart .media_result .data .info .year{position:static;display:block;padding:0;opacity:.6} @@ -325,7 +323,6 @@ .charts .chart .media_result .chart_number{color:#fff;position:absolute;top:0;padding:10px;font:700 2em/1em Helvetica,Sans-Serif;width:50px;height:100%;text-align:center;border-left:8px solid transparent} .charts .chart .media_result.chart_in_wanted .chart_number{border-color:rgba(0,255,40,.3)} .charts .chart .media_result.chart_in_library .chart_number{border-color:rgba(0,202,32,.3)} -.page.wizard .tabs li:hover a,.toggle_menu a:hover{border-color:#047792} .charts .chart .media_result .actions{position:absolute;top:10px;right:10px;display:none;width:90px} .charts .chart .media_result:hover .actions{display:block} .charts .chart .media_result:hover h2 .title{opacity:0} @@ -333,13 +330,40 @@ .charts .chart .media_result .actions a{margin-left:10px;vertical-align:middle} .toggle_menu{height:50px} .toggle_menu a{display:block;width:50%;float:left;color:rgba(255,255,255,.6);border-bottom:1px solid rgba(255,255,255,.06667)} -.toggle_menu a:hover{border-width:4px;color:#fff} +.toggle_menu a:hover{border-color:#047792;border-width:4px;color:#fff} .toggle_menu a.active{border-bottom:4px solid #04bce6;color:#fff} -#category_ordering li,#profile_ordering li,.add_new_profile{border-bottom:1px solid #eaeaea} .toggle_menu a:last-child{float:right} .toggle_menu h2{height:40px} @media all and (max-width:480px){.toggle_menu h2{font-size:16px;text-align:center;height:30px} } +.suggestions{clear:both;padding-top:10px;margin-bottom:30px} +.suggestions>h2{height:40px} +.suggestions .media_result{display:inline-block;width:33.333%;height:150px} +@media all and (max-width:960px){.suggestions .media_result{width:50%} +} +@media all and (max-width:600px){.suggestions .media_result{width:100%} +} +.suggestions .media_result .data{left:100px;background:#4e5969;border:none} +.suggestions .media_result .data .info{top:10px;left:15px;right:15px;bottom:10px;overflow:hidden} +.suggestions .media_result .data .info h2{white-space:normal;max-height:120px;font-size:18px;line-height:18px} +.suggestions .media_result .data .info .genres,.suggestions .media_result .data .info .rating,.suggestions .media_result .data .info .year{position:static;display:block;padding:0;opacity:.6} +.suggestions .media_result .data .info .year{margin:10px 0 0} +.suggestions .media_result .data .info .rating{font-size:20px;float:right;margin-top:-20px} +.suggestions .media_result .data .info .rating:before{content:"\e031";font-family:Elusive-Icons;font-size:14px;margin:0 5px 0 0;vertical-align:bottom} +.suggestions .media_result .data .info .genres{font-size:11px;font-style:italic;text-align:right} +.suggestions .media_result .data .info .plot{display:block;font-size:11px;overflow:hidden;text-align:justify;height:100%;z-index:2;top:64px;position:absolute;background:#4e5969;cursor:pointer;transition:all .4s ease-in-out;padding:0 3px 10px 0} +.suggestions .media_result .data:before{content:'';display:block;height:10px;right:0;left:0;bottom:10px;position:absolute;background:linear-gradient(0deg,#4e5969 0,rgba(78,89,105,0) 100%);z-index:3;pointer-events:none} +.suggestions .media_result .data .info .plot.full{top:0;overflow:auto} +.suggestions .media_result .data{cursor:default} +.suggestions .media_result .options{left:100px} +.suggestions .media_result .options select[name=category],.suggestions .media_result .options select[name=profile],.suggestions .media_result .options select[name=title]{width:100%} +.suggestions .media_result .button{position:absolute;margin:2px 0 0;right:15px;bottom:15px} +.suggestions .media_result .thumbnail{width:100px} +.suggestions .media_result .actions{position:absolute;top:10px;right:10px;display:none;width:140px} +.suggestions .media_result:hover .actions{display:block} +.suggestions .media_result:hover h2 .title{opacity:0} +.suggestions .media_result .data.open .actions{display:none} +.suggestions .media_result .actions a{margin-left:10px;vertical-align:middle} .add_new_category{padding:20px;display:block;text-align:center;font-size:20px} .category{margin-bottom:20px;position:relative} .category>.delete{position:absolute;padding:16px;right:0;cursor:pointer;opacity:.6;color:#fd5353} @@ -349,7 +373,8 @@ .category .formHint{opacity:.1} .category:hover .formHint{opacity:1} #category_ordering ul{float:left;margin:0;width:275px;padding:0} -#category_ordering li{cursor:-webkit-grab;cursor:grab;padding:5px;list-style:none} +#category_ordering li{cursor:-webkit-grab;cursor:grab;border-bottom:1px solid #eaeaea;padding:5px;list-style:none} +#category_ordering li:last-child{border:0} #category_ordering li .check{margin:2px 10px 0 0;vertical-align:top} #category_ordering li>span{display:inline-block;height:20px;vertical-align:top;line-height:20px} #category_ordering li .handle{width:20px;float:right} @@ -377,7 +402,7 @@ .report_popup.report_popup .bug{width:80%;height:80%;max-height:800px;max-width:800px;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:column nowrap;-ms-flex-flow:column nowrap;flex-flow:column nowrap} .report_popup.report_popup .bug textarea{display:block;width:100%;background:#FFF;padding:20px;overflow:auto;color:#666;height:70%;font-size:12px} .do_report.do_report{z-index:10000;position:absolute;padding:10px;background:#ac0000;color:#FFF} -.add_new_profile{padding:20px;display:block;text-align:center;font-size:20px} +.add_new_profile{padding:20px;display:block;text-align:center;font-size:20px;border-bottom:1px solid #eaeaea} .profile{margin-bottom:20px} .profile .quality_label input{font-weight:700} .profile .ctrlHolder .types{-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto;min-width:360px} @@ -393,9 +418,11 @@ .profile .ctrlHolder.wait_for.wait_for input{min-width:0;width:40px;text-align:center;margin:0 2px} .profile .ctrlHolder.wait_for.wait_for .advanced{display:none;color:#ac0000} .show_advanced .profile .ctrlHolder.wait_for.wait_for .advanced{display:inline} +#profile_ordering li,.page.login{display:-webkit-flex;display:-ms-flexbox} #profile_ordering ul{list-style:none;margin:0;width:275px;padding:0} -#profile_ordering li{padding:5px;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center} +#profile_ordering li{border-bottom:1px solid #eaeaea;padding:5px;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center} #profile_ordering li:hover{background:#eaeaea} +#profile_ordering li:last-child{border:0} #profile_ordering li input[type=checkbox]{margin:2px 10px 0 0;vertical-align:top} #profile_ordering li>span{display:inline-block;height:20px;vertical-align:top;line-height:20px} #profile_ordering li>span.profile_label{-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto} @@ -419,6 +446,7 @@ .page.wizard .tab_wrapper .tabs{padding:0;margin:0 auto;display:block;height:100%;width:100%;max-width:960px} .page.wizard .tabs li{display:inline-block;height:100%} .page.wizard .tabs li a{padding:20px 10px;height:100%;display:block;color:#FFF;font-weight:400;border-bottom:4px solid transparent} +.page.wizard .tabs li:hover a{border-color:#047792} .page.wizard .tabs li.done a{border-color:#04bce6} .page.wizard .tab_wrapper .pointer{border-right:10px solid transparent;border-left:10px solid transparent;border-top:10px solid #5c697b;display:block;position:absolute;top:44px} .page.wizard .tab_content{margin:20px 0 160px} @@ -434,6 +462,7 @@ .api_docs .api .params{background:#fafafa;width:100%} .api_docs .api .params h3{clear:both;float:left;width:100px} .api_docs .api .params td,.api_docs .api .params th{padding:3px 5px;border-bottom:1px solid #eee} +.api_docs .api .params tr:last-child td,.api_docs .api .params tr:last-child th{border:0} .api_docs .api .params .param{vertical-align:top} .api_docs .api .params .param th{text-align:left;width:100px} .api_docs .api .params .param .type{font-style:italic;margin-right:10px;width:100px;color:#666} @@ -451,14 +480,6 @@ .api_docs .database table .form,.api_docs .database table form{width:600px} .api_docs .database table textarea{font-size:12px;width:100%;height:200px} .api_docs .database table input[type=submit]{display:block} -.page.login,body{display:-webkit-flex;display:-ms-flexbox} -@font-face{font-family:icons;src:url(../fonts/icons.eot?74719538);src:url(../fonts/icons.eot?74719538#iefix)format("embedded-opentype"),url(../fonts/icons.woff?74719538)format("woff"),url(../fonts/icons.ttf?74719538)format("truetype"),url(../fonts/icons.svg?74719538#icons)format("svg");font-weight:400;font-style:normal} -@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Light-webfont.eot);src:url(../fonts/OpenSans-Light-webfont.eot?#iefix)format("embedded-opentype"),url(../fonts/OpenSans-Light-webfont.woff)format("woff"),url(../fonts/OpenSans-Light-webfont.ttf)format("truetype"),url(../fonts/OpenSans-Light-webfont.svg#OpenSansRegular)format("svg");font-weight:200;font-style:normal} -@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Regular-webfont.eot);src:url(../fonts/OpenSans-Regular-webfont.eot?#iefix)format("embedded-opentype"),url(../fonts/OpenSans-Regular-webfont.woff)format("woff"),url(../fonts/OpenSans-Regular-webfont.ttf)format("truetype"),url(../fonts/OpenSans-Regular-webfont.svg#OpenSansRegular)format("svg");font-weight:400;font-style:normal} -@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Italic-webfont.eot);src:url(../fonts/OpenSans-Italic-webfont.eot?#iefix)format("embedded-opentype"),url(../fonts/OpenSans-Italic-webfont.woff)format("woff"),url(../fonts/OpenSans-Italic-webfont.ttf)format("truetype"),url(../fonts/OpenSans-Italic-webfont.svg#OpenSansItalic)format("svg");font-weight:400;font-style:italic} -@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Bold-webfont.eot);src:url(../fonts/OpenSans-Bold-webfont.eot?#iefix)format("embedded-opentype"),url(../fonts/OpenSans-Bold-webfont.woff)format("woff"),url(../fonts/OpenSans-Bold-webfont.ttf)format("truetype"),url(../fonts/OpenSans-Bold-webfont.svg#OpenSansBold)format("svg");font-weight:700;font-style:normal} -@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-BoldItalic-webfont.eot);src:url(../fonts/OpenSans-BoldItalic-webfont.eot?#iefix)format("embedded-opentype"),url(../fonts/OpenSans-BoldItalic-webfont.woff)format("woff"),url(../fonts/OpenSans-BoldItalic-webfont.ttf)format("truetype"),url(../fonts/OpenSans-BoldItalic-webfont.svg#OpenSansBoldItalic)format("svg");font-weight:700;font-style:italic} -@font-face{font-family:Lobster;src:url(../fonts/Lobster-webfont.eot);src:url(../fonts/Lobster-webfont.eot?#iefix)format("embedded-opentype"),url(../fonts/Lobster-webfont.woff2)format("woff2"),url(../fonts/Lobster-webfont.woff)format("woff"),url(../fonts/Lobster-webfont.ttf)format("truetype"),url(../fonts/Lobster-webfont.svg#lobster_14regular)format("svg");font-weight:400;font-style:normal} .page.login{background:#FFF;display:flex;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;font-size:1.25em} .page.login h1{padding:0 0 10px;font-size:60px;font-family:Lobster;font-weight:400;color:#ac0000;text-align:center} .page.login form{padding:0;width:300px} @@ -467,7 +488,7 @@ .page.login input[type=password],.page.login input[type=text]{width:100%!important} .page.login .remember_me{font-size:15px;float:left;width:150px} .page.login .button{float:right;margin:0;transition:none} -@font-face{font-family:icons;src:url(../fonts/icons.eot?74719542);src:url(../fonts/icons.eot?74719542#iefix)format("embedded-opentype"),url(../fonts/icons.woff?747195412)format("woff"),url(../fonts/icons.ttf?74719542)format("truetype"),url(../fonts/icons.svg?74719542#icons)format("svg");font-weight:400;font-style:normal} +@font-face{font-family:icons;src:url(../fonts/icons.eot?74719542);src:url(../fonts/icons.eot?74719542#iefix) format("embedded-opentype"),url(../fonts/icons.woff?747195412) format("woff"),url(../fonts/icons.ttf?74719542) format("truetype"),url(../fonts/icons.svg?74719542#icons) format("svg");font-weight:400;font-style:normal} [class*=" icon-"]:before,[class^=icon-]:before{font-family:icons;font-style:normal;font-weight:400;speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale} .icon-left-arrow:before{content:'\e800'} .icon-settings:before{content:'\e801'} @@ -498,21 +519,21 @@ .icon-star:before{content:'\e81a'} .icon-star-empty:before{content:'\e81b'} .icon-star-half:before{content:'\e81c'} -@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Light-webfont.eot);src:url(../fonts/OpenSans-Light-webfont.eot?#iefix)format("embedded-opentype"),url(../fonts/OpenSans-Light-webfont.woff)format("woff"),url(../fonts/OpenSans-Light-webfont.ttf)format("truetype"),url(../fonts/OpenSans-Light-webfont.svg#OpenSansRegular)format("svg");font-weight:200;font-style:normal} -@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Regular-webfont.eot);src:url(../fonts/OpenSans-Regular-webfont.eot?#iefix)format("embedded-opentype"),url(../fonts/OpenSans-Regular-webfont.woff)format("woff"),url(../fonts/OpenSans-Regular-webfont.ttf)format("truetype"),url(../fonts/OpenSans-Regular-webfont.svg#OpenSansRegular)format("svg");font-weight:400;font-style:normal} -@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Italic-webfont.eot);src:url(../fonts/OpenSans-Italic-webfont.eot?#iefix)format("embedded-opentype"),url(../fonts/OpenSans-Italic-webfont.woff)format("woff"),url(../fonts/OpenSans-Italic-webfont.ttf)format("truetype"),url(../fonts/OpenSans-Italic-webfont.svg#OpenSansItalic)format("svg");font-weight:400;font-style:italic} -@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Bold-webfont.eot);src:url(../fonts/OpenSans-Bold-webfont.eot?#iefix)format("embedded-opentype"),url(../fonts/OpenSans-Bold-webfont.woff)format("woff"),url(../fonts/OpenSans-Bold-webfont.ttf)format("truetype"),url(../fonts/OpenSans-Bold-webfont.svg#OpenSansBold)format("svg");font-weight:700;font-style:normal} -@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-BoldItalic-webfont.eot);src:url(../fonts/OpenSans-BoldItalic-webfont.eot?#iefix)format("embedded-opentype"),url(../fonts/OpenSans-BoldItalic-webfont.woff)format("woff"),url(../fonts/OpenSans-BoldItalic-webfont.ttf)format("truetype"),url(../fonts/OpenSans-BoldItalic-webfont.svg#OpenSansBoldItalic)format("svg");font-weight:700;font-style:italic} -@font-face{font-family:Lobster;src:url(../fonts/Lobster-webfont.eot);src:url(../fonts/Lobster-webfont.eot?#iefix)format("embedded-opentype"),url(../fonts/Lobster-webfont.woff2)format("woff2"),url(../fonts/Lobster-webfont.woff)format("woff"),url(../fonts/Lobster-webfont.ttf)format("truetype"),url(../fonts/Lobster-webfont.svg#lobster_14regular)format("svg");font-weight:400;font-style:normal} -*{margin:0;padding:0;box-sizing:border-box;text-rendering:optimizeSpeed;backface-visibility:hidden} +@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Light-webfont.eot);src:url(../fonts/OpenSans-Light-webfont.eot?#iefix) format("embedded-opentype"),url(../fonts/OpenSans-Light-webfont.woff) format("woff"),url(../fonts/OpenSans-Light-webfont.ttf) format("truetype"),url(../fonts/OpenSans-Light-webfont.svg#OpenSansRegular) format("svg");font-weight:200;font-style:normal} +@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Regular-webfont.eot);src:url(../fonts/OpenSans-Regular-webfont.eot?#iefix) format("embedded-opentype"),url(../fonts/OpenSans-Regular-webfont.woff) format("woff"),url(../fonts/OpenSans-Regular-webfont.ttf) format("truetype"),url(../fonts/OpenSans-Regular-webfont.svg#OpenSansRegular) format("svg");font-weight:400;font-style:normal} +@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Italic-webfont.eot);src:url(../fonts/OpenSans-Italic-webfont.eot?#iefix) format("embedded-opentype"),url(../fonts/OpenSans-Italic-webfont.woff) format("woff"),url(../fonts/OpenSans-Italic-webfont.ttf) format("truetype"),url(../fonts/OpenSans-Italic-webfont.svg#OpenSansItalic) format("svg");font-weight:400;font-style:italic} +@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Bold-webfont.eot);src:url(../fonts/OpenSans-Bold-webfont.eot?#iefix) format("embedded-opentype"),url(../fonts/OpenSans-Bold-webfont.woff) format("woff"),url(../fonts/OpenSans-Bold-webfont.ttf) format("truetype"),url(../fonts/OpenSans-Bold-webfont.svg#OpenSansBold) format("svg");font-weight:700;font-style:normal} +@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-BoldItalic-webfont.eot);src:url(../fonts/OpenSans-BoldItalic-webfont.eot?#iefix) format("embedded-opentype"),url(../fonts/OpenSans-BoldItalic-webfont.woff) format("woff"),url(../fonts/OpenSans-BoldItalic-webfont.ttf) format("truetype"),url(../fonts/OpenSans-BoldItalic-webfont.svg#OpenSansBoldItalic) format("svg");font-weight:700;font-style:italic} +@font-face{font-family:Lobster;src:url(../fonts/Lobster-webfont.eot);src:url(../fonts/Lobster-webfont.eot?#iefix) format("embedded-opentype"),url(../fonts/Lobster-webfont.woff2) format("woff2"),url(../fonts/Lobster-webfont.woff) format("woff"),url(../fonts/Lobster-webfont.ttf) format("truetype"),url(../fonts/Lobster-webfont.svg#lobster_14regular) format("svg");font-weight:400;font-style:normal} +*{margin:0;padding:0;box-sizing:border-box;text-rendering:optimizeSpeed;-webkit-backface-visibility:hidden;backface-visibility:hidden} body,html{font-size:14px;line-height:1.5;font-family:OpenSans,"Helvetica Neue",Helvetica,Arial,Geneva,sans-serif;font-weight:300;height:100%;margin:0;padding:0;background:#111;overflow:hidden} -body{display:flex;-webkit-flex-flow:row nowrap;-ms-flex-flow:row nowrap;flex-flow:row nowrap} +body{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row nowrap;-ms-flex-flow:row nowrap;flex-flow:row nowrap} a{display:inline-block;position:relative;overflow:hidden;text-decoration:none;cursor:pointer;-webkit-tap-highlight-color:transparent} input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#FFF;border:1px solid #b7b7b7} .button{color:#ac0000;font-weight:300;padding:5px;cursor:pointer;border:1px solid #ac0000;border-radius:3px;margin:0 5px;transition:all 150ms} .button:hover{background:#ac0000;color:#FFF} -.ripple{position:absolute;height:10px;width:10px;border-radius:50%;background:#ac0000;-webkit-transform:translate(-50%,-50%)scale(1)rotateZ(360deg);transform:translate(-50%,-50%)scale(1)rotateZ(360deg);opacity:.2;transition:all 1.5s ease;transition-property:opacity,-webkit-transform;transition-property:opacity,transform} -.ripple.animate{-webkit-transform:translate(-50%,-50%)scale(100)rotateZ(360deg);transform:translate(-50%,-50%)scale(100)rotateZ(360deg);opacity:0} +.ripple{position:absolute;height:10px;width:10px;border-radius:50%;background:#ac0000;-webkit-transform:translate(-50%,-50%) scale(1) rotateZ(360deg);transform:translate(-50%,-50%) scale(1) rotateZ(360deg);opacity:.2;transition:all 1.5s ease;transition-property:opacity,-webkit-transform;transition-property:opacity,transform} +.ripple.animate{-webkit-transform:translate(-50%,-50%) scale(100) rotateZ(360deg);transform:translate(-50%,-50%) scale(100) rotateZ(360deg);opacity:0} .header{width:132px;min-width:132px;position:relative;z-index:100} @media (max-width:480px){.header{width:44px;min-width:44px;z-index:21} } @@ -572,8 +593,8 @@ input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#F .page h1,.page h2,.page h3,.page h4{font-weight:300} .page h2{font-size:24px;padding:20px} .page .navigation{z-index:2;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center;position:fixed;top:0;height:80px;left:152px;right:20px;background:#FFF;border-radius:3px 0 0} -.more_menu,.more_menu .button:before{position:relative} .more_menu .button,.page .navigation ul li{display:inline-block} +.more_menu,.more_menu .button:before{position:relative} @media (max-width:480px){.page h2{font-size:18px;padding:10px} .page .navigation{height:44px;left:64px} } @@ -591,48 +612,49 @@ input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#F .more_menu{line-height:1em} .more_menu .button{font-size:24px;cursor:pointer} .more_menu .wrapper{display:none;position:absolute;right:0;background:#ac0000;z-index:5000;border-radius:3px 0 0 3px;-webkit-transform-origin:80% 0;transform-origin:80% 0} -.more_menu .wrapper:before{-webkit-transform:rotate(45deg)translateY(-60%);transform:rotate(45deg)translateY(-60%);content:'';display:block;position:absolute;background:#ac0000;height:10px;width:10px;left:-9px;bottom:11px;z-index:1;opacity:1;border-radius:3px} -.mask,.messages{right:0;bottom:0} +.more_menu .wrapper:before{-webkit-transform:rotate(45deg) translateY(-60%);transform:rotate(45deg) translateY(-60%);content:'';display:block;position:absolute;background:#ac0000;height:10px;width:10px;left:-9px;bottom:11px;z-index:1;opacity:1;border-radius:3px} .more_menu .wrapper ul{background:#FFF;position:relative;z-index:2;overflow:hidden;border-radius:3px 0 0 3px} .more_menu .wrapper ul li{display:block;line-height:1em;border-top:1px solid #eaeaea} .more_menu .wrapper ul li:first-child{border-top:0} .more_menu .wrapper ul li a{display:block;color:#000;padding:5px 10px;font-size:1em;line-height:22px} +.question,.table .item{display:-webkit-flex;display:-ms-flexbox} .more_menu .wrapper ul li:first-child a{padding-top:10px} .more_menu .wrapper ul li:last-child a{padding-bottom:10px} -.messages{position:fixed;width:320px;z-index:2000;overflow:hidden;font-size:14px;font-weight:700;padding:5px} +.messages{position:fixed;right:0;bottom:0;width:320px;z-index:2000;overflow:hidden;font-size:14px;font-weight:700;padding:5px} .messages .message{overflow:hidden;transition:all .6s cubic-bezier(.9,0,.1,1);width:100%;position:relative;max-height:0;font-size:1.1em;font-weight:400;-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:100% 50%;transform-origin:100% 50%;background:#ac0000;margin-bottom:4px;border-radius:3px} +.mask,.messages .close{position:absolute;top:0;right:0} .messages .message .inner{padding:15px 30px 15px 20px;background:#FFF;margin-bottom:4px;border-radius:3px} .messages .message.sticky{background-color:#ac0000} .messages .message.show{max-height:100px;-webkit-transform:scale(1);transform:scale(1)} .messages .message.hide{max-height:0;padding:0 20px;margin:0;-webkit-transform:scale(0);transform:scale(0)} -.messages .close{position:absolute;padding:10px 8px;top:0;right:0;color:#FFF} -.question{position:fixed;z-index:20000;color:#FFF;padding:20px;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center} +.messages .close{padding:10px 8px;color:#FFF} +.question{position:fixed;z-index:20000;color:#FFF;padding:20px;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center} .question.show{opacity:1} .question .inner{width:100%;max-width:500px} .question h3{display:block;margin-bottom:20px;font-size:1.4em;font-weight:lighter} .question .hint{margin:-20px 0 20px} .question a{border-color:#FFF;color:#FFF;transition:none} .question a:hover{background:#FFF;color:#ac0000} -.mask{background:rgba(0,0,0,.8);z-index:1000;text-align:center;position:absolute;top:0;left:0;opacity:0;transition:opacity 500ms} +.mask{background:rgba(0,0,0,.8);z-index:1000;text-align:center;bottom:0;left:0;opacity:0;transition:opacity 500ms} .mask .message,.mask .spinner{position:absolute;top:50%;left:50%} .mask .message{color:#FFF;text-align:center;width:320px;margin:-49px 0 0 -160px;font-size:16px} .mask .message h1{font-size:1.5em} -.mask .spinner{width:22px;height:22px;display:block;background:#fff;margin-top:-11px;margin-left:-11px;outline:transparent solid 1px;-webkit-animation:rotating 2.5s cubic-bezier(.9,0,.1,1)infinite normal;animation:rotating 2.5s cubic-bezier(.9,0,.1,1)infinite normal;-webkit-transform:scale(0);transform:scale(0)} +.mask .spinner{width:22px;height:22px;display:block;background:#fff;margin-top:-11px;margin-left:-11px;outline:transparent solid 1px;-webkit-animation:rotating 2.5s cubic-bezier(.9,0,.1,1) infinite normal;animation:rotating 2.5s cubic-bezier(.9,0,.1,1) infinite normal;-webkit-transform:scale(0);transform:scale(0)} .mask.with_message .spinner{margin-top:-88px} .mask.show{pointer-events:auto;opacity:1} .mask.show .spinner{-webkit-transform:scale(1);transform:scale(1)} .mask.hide{opacity:0} .mask.hide .spinner{-webkit-transform:scale(0);transform:scale(0)} -@-webkit-keyframes rotating{0%{-webkit-transform:rotate(0)scale(1.6);transform:rotate(0)scale(1.6);border-radius:1px} -48%{-webkit-transform:rotate(360deg)scale(1);transform:rotate(360deg)scale(1);border-radius:50%} -100%{-webkit-transform:rotate(720deg)scale(1.6);transform:rotate(720deg)scale(1.6);border-radius:1px} +@-webkit-keyframes rotating{0%{-webkit-transform:rotate(0) scale(1.6);transform:rotate(0) scale(1.6);border-radius:1px} +48%{-webkit-transform:rotate(360deg) scale(1);transform:rotate(360deg) scale(1);border-radius:50%} +100%{-webkit-transform:rotate(720deg) scale(1.6);transform:rotate(720deg) scale(1.6);border-radius:1px} } -@keyframes rotating{0%{-webkit-transform:rotate(0)scale(1.6);transform:rotate(0)scale(1.6);border-radius:1px} -48%{-webkit-transform:rotate(360deg)scale(1);transform:rotate(360deg)scale(1);border-radius:50%} -100%{-webkit-transform:rotate(720deg)scale(1.6);transform:rotate(720deg)scale(1.6);border-radius:1px} +@keyframes rotating{0%{-webkit-transform:rotate(0) scale(1.6);transform:rotate(0) scale(1.6);border-radius:1px} +48%{-webkit-transform:rotate(360deg) scale(1);transform:rotate(360deg) scale(1);border-radius:50%} +100%{-webkit-transform:rotate(720deg) scale(1.6);transform:rotate(720deg) scale(1.6);border-radius:1px} } .table .head{font-weight:700} -.table .item{display:-webkit-flex;display:-ms-flexbox;display:flex;border-bottom:1px solid rgba(0,0,0,.2)} +.table .item{display:flex;border-bottom:1px solid rgba(0,0,0,.2)} .table .item:last-child{border-bottom:none} .table .item span{padding:1px 2px} .table .item span:first-child{padding-left:0} @@ -660,7 +682,7 @@ input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#F @media (max-width:480px){.page.settings fieldset h2{display:block} .page.settings fieldset h2 .hint{margin:0;display:block} } -.page.settings fieldset h2 .hint a{font-weight:400;color:#ac0000} +.page.settings fieldset h2 .hint a{font-weight:400;color:#ac0000;text-decoration:underline} .page.settings fieldset .ctrlHolder{padding:6.67px 20px;border-bottom:1px solid #eaeaea;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row nowrap;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-align-items:center;-ms-flex-align:center;align-items:center} .page.settings fieldset .ctrlHolder:last-child{border-bottom:0} .page.settings fieldset .ctrlHolder label{display:inline-block;min-width:150px} @@ -756,31 +778,4 @@ input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#F .page.settings .directory_list .actions:last-child>span{padding:0 5px;text-shadow:none} .page.settings .directory_list .actions:last-child>.clear{left:20px;position:absolute;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);margin:0} .page.settings .directory_list .actions:last-child>.cancel{color:rgba(0,0,0,.4)} -.page.settings .directory_list .actions:last-child>.save{margin-right:0} -.uniForm legend{font-weight:700;font-size:100%;margin:0;padding:1.5em 0} -.uniForm .ctrlHolder{padding:1em;border-bottom:1px solid #efefef} -.uniForm .ctrlHolder.focused{background:#fffcdf} -.uniForm .buttonHolder{background:#efefef;text-align:right;margin:1.5em 0 0;padding:1.5em;border-radius:4px} -.uniForm .buttonHolder .primaryAction{padding:10px 22px;line-height:1;background:#254a86;border:1px solid #163362;font-size:12px;font-weight:700;color:#fff;border-radius:4px;box-shadow:1px 1px 0 #fff;text-shadow:-1px -1px 0 rgba(0,0,0,.25)} -.uniForm .buttonHolder .primaryAction:active{position:relative;top:1px} -.uniForm .secondaryAction{text-align:left} -.uniForm button.secondaryAction{background:0 0;border:none;color:#777;margin:1.25em 0 0;padding:0} -.uniForm .inlineLabels .label em,.uniForm .inlineLabels label em{font-style:normal;font-weight:700} -.uniForm label small{font-size:.75em;color:#777} -.uniForm .textInput,.uniForm textarea{padding:4px 2px;border:1px solid #aaa;background:#fff} -.uniForm textarea{height:12em} -.uniForm ul li label{font-size:.85em} -.uniForm .ctrlHolder .textInput:focus,.uniForm .ctrlHolder textarea:focus{outline:0} -.uniForm .formHint{font-size:.85em;color:#777} -.uniForm .inlineLabels .formHint{padding-top:.5em} -.uniForm .ctrlHolder.focused .formHint{color:#333} -.uniForm #errorMsg{background:#ffdfdf;border:1px solid #f3afb5;margin:0 0 1.5em;padding:0 1.5em;border-radius:4px;-webkit-border-radius:4px;-moz-border-radius:4px} -.uniForm #errorMsg ol{margin:0 0 1.5em;padding:0} -.uniForm #errorMsg ol li{margin:0 0 3px 1.5em;padding:7px;background:#f6bec1;position:relative;font-size:.85em;border-radius:4px;-webkit-border-radius:4px;-moz-border-radius:4px} -.uniForm .ctrlHolder.error,.uniForm .ctrlHolder.focused.error{background:#ffdfdf;border:1px solid #f3afb5;border-radius:4px;-webkit-border-radius:4px;-moz-border-radius:4px} -.uniForm .ctrlHolder.error input.error,.uniForm .ctrlHolder.error select.error,.uniForm .ctrlHolder.error textarea.error{color:#af4c4c;margin:0 0 6px;padding:4px} -.uniForm #okMsg{background:#c8ffbf;border:1px solid #a2ef95;margin:0 0 1.5em;padding:0 1.5em;text-align:center;border-radius:4px;-webkit-border-radius:4px;-moz-border-radius:4px} -.uniForm #OKMsg p{margin:0} -.uniForm .col{margin-bottom:1.5em} -.uniForm .col.first{width:49%;float:left;clear:none} -.uniForm .col.last{width:49%;float:right;clear:none;margin-right:0} \ No newline at end of file +.page.settings .directory_list .actions:last-child>.save{margin-right:0} \ No newline at end of file From 839086903498571bd4e8a6d76439439df263b755 Mon Sep 17 00:00:00 2001 From: Ruud Date: Fri, 7 Aug 2015 13:12:13 +0200 Subject: [PATCH 226/301] Allow bigger size label --- couchpotato/core/media/movie/_base/static/movie.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/core/media/movie/_base/static/movie.scss b/couchpotato/core/media/movie/_base/static/movie.scss index 15aa2e4..0fe0970 100644 --- a/couchpotato/core/media/movie/_base/static/movie.scss +++ b/couchpotato/core/media/movie/_base/static/movie.scss @@ -908,7 +908,7 @@ $mass_edit_height: 44px; .status { min-width: 70px; max-width: 70px; &:before { content: "Status:"; } } .quality { min-width: 60px; max-width: 60px; &:before { content: "Quality:"; } } - .size { min-width: 40px; max-width: 40px; &:before { content: "Size:"; } } + .size { min-width: 50px; max-width: 50px; &:before { content: "Size:"; } } .age { min-width: 40px; max-width: 40px; &:before { content: "Age:"; } } .score { min-width: 45px; max-width: 45px; &:before { content: "Score:"; } } .provider { min-width: 110px; max-width: 110px; &:before { content: "Provider:"; } } From 2a2d05e203b5ddc151bdb70f25e9977b0c8d004c Mon Sep 17 00:00:00 2001 From: Ruud Date: Fri, 7 Aug 2015 13:15:55 +0200 Subject: [PATCH 227/301] Show advanced toggle not working anymore --- couchpotato/static/scripts/combined.base.min.js | 2 +- couchpotato/static/scripts/page/settings.js | 2 +- couchpotato/static/style/combined.min.css | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/couchpotato/static/scripts/combined.base.min.js b/couchpotato/static/scripts/combined.base.min.js index accc82a..9ad6abd 100644 --- a/couchpotato/static/scripts/combined.base.min.js +++ b/couchpotato/static/scripts/combined.base.min.js @@ -1166,7 +1166,7 @@ Page.Settings = new Class({ showAdvanced: function() { var self = this; var c = self.advanced_toggle.checked ? "addClass" : "removeClass"; - self.content[c]("show_advanced"); + self.el[c]("show_advanced"); Cookie.write("advanced_toggle_checked", +self.advanced_toggle.checked, { duration: 365 }); diff --git a/couchpotato/static/scripts/page/settings.js b/couchpotato/static/scripts/page/settings.js index 35842f9..8593f92 100644 --- a/couchpotato/static/scripts/page/settings.js +++ b/couchpotato/static/scripts/page/settings.js @@ -108,7 +108,7 @@ Page.Settings = new Class({ var self = this; var c = self.advanced_toggle.checked ? 'addClass' : 'removeClass'; - self.content[c]('show_advanced'); + self.el[c]('show_advanced'); Cookie.write('advanced_toggle_checked', +self.advanced_toggle.checked, {'duration': 365}); }, diff --git a/couchpotato/static/style/combined.min.css b/couchpotato/static/style/combined.min.css index 5b9b3d5..6ced42c 100644 --- a/couchpotato/static/style/combined.min.css +++ b/couchpotato/static/style/combined.min.css @@ -233,9 +233,10 @@ .page.movie_details .releases .status{min-width:70px;max-width:70px} .page.movie_details .releases .status:before{content:"Status:"} .page.movie_details .releases .quality{min-width:60px;max-width:60px} -.page.movie_details .releases .age,.page.movie_details .releases .size{min-width:40px;max-width:40px} .page.movie_details .releases .quality:before{content:"Quality:"} +.page.movie_details .releases .size{min-width:50px;max-width:50px} .page.movie_details .releases .size:before{content:"Size:"} +.page.movie_details .releases .age{min-width:40px;max-width:40px} .page.movie_details .releases .age:before{content:"Age:"} .page.movie_details .releases .score{min-width:45px;max-width:45px} .page.movie_details .releases .score:before{content:"Score:"} From ba5483cb6c7457138852c65071c5a3f42fd16bd0 Mon Sep 17 00:00:00 2001 From: Ruud Date: Fri, 7 Aug 2015 17:01:23 +0200 Subject: [PATCH 228/301] Simplify the wizard --- couchpotato/core/plugins/wizard/static/wizard.js | 448 ++++++++++----------- couchpotato/core/plugins/wizard/static/wizard.scss | 116 +++--- couchpotato/static/scripts/combined.base.min.js | 2 +- couchpotato/static/scripts/combined.plugins.min.js | 46 +-- couchpotato/static/style/combined.min.css | 26 +- 5 files changed, 266 insertions(+), 372 deletions(-) diff --git a/couchpotato/core/plugins/wizard/static/wizard.js b/couchpotato/core/plugins/wizard/static/wizard.js index 2f2ff4f..ed2d59f 100644 --- a/couchpotato/core/plugins/wizard/static/wizard.js +++ b/couchpotato/core/plugins/wizard/static/wizard.js @@ -1,246 +1,202 @@ -Page.Wizard = new Class({ - - Extends: Page.Settings, - - order: 70, - name: 'wizard', - has_tab: false, - wizard_only: true, - - headers: { - 'welcome': { - 'title': 'Welcome to the new CouchPotato', - 'description': 'To get started, fill in each of the following settings as much as you can.', - 'content': new Element('div', { - 'styles': { - 'margin': '0 0 0 30px' - } - }) - }, - 'general': { - 'title': 'General', - 'description': 'If you want to access CP from outside your local network, you better secure it a bit with a username & password.' - }, - 'downloaders': { - 'title': 'What download apps are you using?', - 'description': 'CP needs an external download app to work with. Choose one below. For more downloaders check settings after you have filled in the wizard. If your download app isn\'t in the list, use the default Blackhole.' - }, - 'searcher': { - 'label': 'Providers', - 'title': 'Are you registered at any of these sites?', - 'description': 'CP uses these sites to search for movies. A few free are enabled by default, but it\'s always better to have more.' - }, - 'renamer': { - 'title': 'Move & rename the movies after downloading?', - 'description': 'The coolest part of CP is that it can move and organize your downloaded movies automagically. Check settings and you can even download trailers, subtitles and other data when it has finished downloading. It\'s awesome!' - }, - 'automation': { - 'title': 'Easily add movies to your wanted list!', - 'description': 'You can easily add movies from your favorite movie site, like IMDB, Rotten Tomatoes, Apple Trailers and more. Just install the extension or drag the bookmarklet to your bookmarks.' + - '
Once installed, just click the bookmarklet on a movie page and watch the magic happen ;)', - 'content': function(){ - return App.createUserscriptButtons().setStyles({ - 'background-image': "url('https://couchpota.to/media/images/userscript.gif')" - }); - } - }, - 'finish': { - 'title': 'Finishing Up', - 'description': 'Are you done? Did you fill in everything as much as possible?' + - '
Be sure to check the settings to see what more CP can do!

' + - '
After you\'ve used CP for a while, and you like it (which of course you will), consider supporting CP. Maybe even by writing some code.
Or by getting a subscription at Usenet Server or Newshosting.
', - 'content': new Element('div').adopt( - new Element('a.button.green', { - 'styles': { - 'margin-top': 20 - }, - 'text': 'I\'m ready to start the awesomeness, wow this button is big and green!', - 'events': { - 'click': function(e){ - (e).preventDefault(); - Api.request('settings.save', { - 'data': { - 'section': 'core', - 'name': 'show_wizard', - 'value': 0 - }, - 'useSpinner': true, - 'spinnerOptions': { - 'target': self.el - }, - 'onComplete': function(){ - window.location = App.createUrl('wanted'); - } - }); - } - } - }) - ) - } - }, - groups: ['welcome', 'general', 'downloaders', 'searcher', 'renamer', 'automation', 'finish'], - - open: function(action, params){ - var self = this; - - if(!self.initialized){ - App.fireEvent('unload'); - App.getBlock('header').hide(); - - self.parent(action, params); - - self.addEvent('create', function(){ - self.orderGroups(); - }); - - self.initialized = true; - - self.scroll = new Fx.Scroll(document.body, { - 'transition': 'quint:in:out' - }); - } - else - (function(){ - var sc = self.el.getElement('.wgroup_'+action); - self.scroll.start(0, sc.getCoordinates().top-80); - }).delay(1); - }, - - orderGroups: function(){ - var self = this; - - var form = self.el.getElement('.uniForm'); - var tabs = self.el.getElement('.tabs'); - - self.groups.each(function(group){ - - var group_container; - if(self.headers[group]){ - group_container = new Element('.wgroup_'+group, { - 'styles': { - 'opacity': 0.2 - }, - 'tween': { - 'duration': 350 - } - }); - - if(self.headers[group].include){ - self.headers[group].include.each(function(inc){ - group_container.addClass('wgroup_'+inc); - }); - } - - var content = self.headers[group].content; - group_container.adopt( - new Element('h1', { - 'text': self.headers[group].title - }), - self.headers[group].description ? new Element('span.description', { - 'html': self.headers[group].description - }) : null, - content ? (typeOf(content) == 'function' ? content() : content) : null - ).inject(form); - } - - var tab_navigation = tabs.getElement('.t_'+group); - - if(!tab_navigation && self.headers[group] && self.headers[group].include){ - tab_navigation = []; - self.headers[group].include.each(function(inc){ - tab_navigation.include(tabs.getElement('.t_'+inc)); - }); - } - - if(tab_navigation && group_container){ - tabs.adopt(tab_navigation); // Tab navigation - - if(self.headers[group] && self.headers[group].include){ - - self.headers[group].include.each(function(inc){ - self.el.getElement('.tab_'+inc).inject(group_container); - }); - - new Element('li.t_'+group).adopt( - new Element('a', { - 'href': App.createUrl('wizard/'+group), - 'text': (self.headers[group].label || group).capitalize() - }) - ).inject(tabs); - - } - else - self.el.getElement('.tab_'+group).inject(group_container); // Tab content - - if(tab_navigation.getElement && self.headers[group]){ - var a = tab_navigation.getElement('a'); - a.set('text', (self.headers[group].label || group).capitalize()); - var url_split = a.get('href').split('wizard')[1].split('/'); - if(url_split.length > 3) - a.set('href', a.get('href').replace(url_split[url_split.length-3]+'/', '')); - - } - } - else { - new Element('li.t_'+group).adopt( - new Element('a', { - 'href': App.createUrl('wizard/'+group), - 'text': (self.headers[group].label || group).capitalize() - }) - ).inject(tabs); - } - - if(self.headers[group] && self.headers[group].event) - self.headers[group].event.call(); - }); - - // Remove toggle - self.el.getElement('.advanced_toggle').destroy(); - - // Hide retention - self.el.getElement('.section_nzb').hide(); - - // Add pointer - new Element('.tab_wrapper').wraps(tabs); - - // Add nav - var minimum = self.el.getSize().y-window.getSize().y; - self.groups.each(function(group, nr){ - - var g = self.el.getElement('.wgroup_'+group); - if(!g || !g.isVisible()) return; - var t = self.el.getElement('.t_'+group); - if(!t) return; - - var func = function(){ - // Activate all previous ones - self.groups.each(function(groups2, nr2){ - var t2 = self.el.getElement('.t_'+groups2); - t2[nr2 > nr ? 'removeClass' : 'addClass' ]('done'); - }); - g.tween('opacity', 1); - }; - - if(nr === 0) - func(); - - new ScrollSpy( { - min: function(){ - var c = g.getCoordinates(); - var top = c.top-(window.getSize().y/2); - return top > minimum ? minimum : top; - }, - max: function(){ - var c = g.getCoordinates(); - return c.top+(c.height/2); - }, - onEnter: func, - onLeave: function(){ - g.tween('opacity', 0.2); - } - }); - }); - - } - -}); +Page.Wizard = new Class({ + + Extends: Page.Settings, + + order: 70, + name: 'wizard', + current: 'welcome', + has_tab: false, + wizard_only: true, + + headers: { + 'welcome': { + 'title': 'Welcome to the new CouchPotato', + 'description': 'To get started, fill in each of the following settings as much as you can.', + 'content': new Element('div', { + 'styles': { + 'margin': '0 0 0 30px' + } + }) + }, + 'general': { + 'title': 'General', + 'description': 'If you want to access CP from outside your local network, you better secure it a bit with a username & password.' + }, + 'downloaders': { + 'title': 'What download apps are you using?', + 'description': 'CP needs an external download app to work with. Choose one below. For more downloaders check settings after you have filled in the wizard. If your download app isn\'t in the list, use the default Blackhole.' + }, + 'searcher': { + 'label': 'Providers', + 'title': 'Are you registered at any of these sites?', + 'description': 'CP uses these sites to search for movies. A few free are enabled by default, but it\'s always better to have more.' + }, + 'renamer': { + 'title': 'Move & rename the movies after downloading?', + 'description': 'The coolest part of CP is that it can move and organize your downloaded movies automagically. Check settings and you can even download trailers, subtitles and other data when it has finished downloading. It\'s awesome!' + }, + 'automation': { + 'title': 'Easily add movies to your wanted list!', + 'description': 'You can easily add movies from your favorite movie site, like IMDB, Rotten Tomatoes, Apple Trailers and more. Just install the extension or drag the bookmarklet to your bookmarks.' + + '
Once installed, just click the bookmarklet on a movie page and watch the magic happen ;)', + 'content': function(){ + return App.createUserscriptButtons().setStyles({ + 'background-image': "url('https://couchpota.to/media/images/userscript.gif')" + }); + } + }, + 'finish': { + 'title': 'Finishing Up', + 'description': 'Are you done? Did you fill in everything as much as possible?' + + '
Be sure to check the settings to see what more CP can do!

' + + '
After you\'ve used CP for a while, and you like it (which of course you will), consider supporting CP. Maybe even by writing some code.
Or by getting a subscription at Usenet Server or Newshosting.
', + 'content': new Element('div').adopt( + new Element('a.button.green', { + 'styles': { + 'margin-top': 20 + }, + 'text': 'I\'m ready to start the awesomeness!', + 'events': { + 'click': function(e){ + (e).preventDefault(); + Api.request('settings.save', { + 'data': { + 'section': 'core', + 'name': 'show_wizard', + 'value': 0 + }, + 'useSpinner': true, + 'spinnerOptions': { + 'target': self.el + }, + 'onComplete': function(){ + window.location = App.createUrl('wanted'); + } + }); + } + } + }) + ) + } + }, + + groups: ['welcome', 'general', 'downloaders', 'searcher', 'renamer', 'automation', 'finish'], + + open: function(action, params){ + var self = this; + + if(!self.initialized){ + App.fireEvent('unload'); + App.getBlock('header').hide(); + + self.parent(action, params); + + self.el.addClass('settings'); + + self.addEvent('create', function(){ + self.orderGroups(); + }); + + self.initialized = true; + + self.scroll = new Fx.Scroll(document.body, { + 'transition': 'quint:in:out' + }); + } + else + (function(){ + var sc = self.el.getElement('.wgroup_'+action); + self.scroll.start(0, sc.getCoordinates().top-80); + }).delay(1); + }, + + orderGroups: function(){ + var self = this; + + var form = self.el.getElement('.uniForm'); + var tabs = self.el.getElement('.tabs').hide(); + + self.groups.each(function(group){ + + var group_container; + if(self.headers[group]){ + group_container = new Element('.wgroup_'+group); + + if(self.headers[group].include){ + self.headers[group].include.each(function(inc){ + group_container.addClass('wgroup_'+inc); + }); + } + + var content = self.headers[group].content; + group_container.adopt( + new Element('h1', { + 'text': self.headers[group].title + }), + self.headers[group].description ? new Element('span.description', { + 'html': self.headers[group].description + }) : null, + content ? (typeOf(content) == 'function' ? content() : content) : null + ).inject(form); + } + + var tab_navigation = tabs.getElement('.t_'+group); + + if(!tab_navigation && self.headers[group] && self.headers[group].include){ + tab_navigation = []; + self.headers[group].include.each(function(inc){ + tab_navigation.include(tabs.getElement('.t_'+inc)); + }); + } + + if(tab_navigation && group_container){ + tabs.adopt(tab_navigation); // Tab navigation + + if(self.headers[group] && self.headers[group].include){ + + self.headers[group].include.each(function(inc){ + self.el.getElement('.tab_'+inc).inject(group_container); + }); + + new Element('li.t_'+group).adopt( + new Element('a', { + 'href': App.createUrl('wizard/'+group), + 'text': (self.headers[group].label || group).capitalize() + }) + ).inject(tabs); + + } + else + self.el.getElement('.tab_'+group).inject(group_container); // Tab content + + if(tab_navigation.getElement && self.headers[group]){ + var a = tab_navigation.getElement('a'); + a.set('text', (self.headers[group].label || group).capitalize()); + var url_split = a.get('href').split('wizard')[1].split('/'); + if(url_split.length > 3) + a.set('href', a.get('href').replace(url_split[url_split.length-3]+'/', '')); + + } + } + else { + new Element('li.t_'+group).adopt( + new Element('a', { + 'href': App.createUrl('wizard/'+group), + 'text': (self.headers[group].label || group).capitalize() + }) + ).inject(tabs); + } + + if(self.headers[group] && self.headers[group].event) + self.headers[group].event.call(); + }); + + // Remove toggle + self.el.getElement('.advanced_toggle').destroy(); + + // Hide retention + self.el.getElement('.section_nzb').hide(); + + } + +}); diff --git a/couchpotato/core/plugins/wizard/static/wizard.scss b/couchpotato/core/plugins/wizard/static/wizard.scss index 9af32ed..779c515 100644 --- a/couchpotato/core/plugins/wizard/static/wizard.scss +++ b/couchpotato/core/plugins/wizard/static/wizard.scss @@ -1,84 +1,62 @@ -.page.wizard .uniForm { - margin: 0 0 30px; - width: 83%; -} - -.page.wizard h1 { - padding: 10px 0; - display: block; - font-size: 30px; - margin: 80px 5px 0; -} +@import "_mixins"; -.page.wizard .description { - padding: 10px 5px; - font-size: 1.45em; - line-height: 1.4em; - display: block; -} +.page.wizard { -.page.wizard .tab_wrapper { - background: #5c697b; - height: 65px; - font-size: 1.75em; - position: fixed; - top: 0; - margin: 0; - width: 100%; - left: 0; - z-index: 2; - box-shadow: 0 0 10px rgba(0,0,0,0.1); -} + .navigation.navigation { + display: none; + } - .page.wizard .tab_wrapper .tabs { - padding: 0; - margin: 0 auto; + .tab_content.tab_content { display: block; - height: 100%; - width: 100%; - max-width: 960px; - } - .page.wizard .tabs li { - display: inline-block; - height: 100%; - } - .page.wizard .tabs li a { - padding: 20px 10px; - height: 100%; - display: block; - color: #FFF; - font-weight: normal; - border-bottom: 4px solid transparent; + fieldset { + + .ctrlHolder, h2 { + padding: $padding/4; + } + } + } - .page.wizard .tabs li:hover a { border-color: #047792; } - .page.wizard .tabs li.done a { border-color: #04bce6; } + h1 { + padding: 10px 0; + display: block; + font-size: 30px; + margin: 80px 5px 0; + font-weight: 300; + } - .page.wizard .tab_wrapper .pointer { - border-right: 10px solid transparent; - border-left: 10px solid transparent; - border-top: 10px solid #5c697b; + .description { + padding: $padding/2 $padding/4; + font-size: 1.45em; + line-height: 1.4em; display: block; - position: absolute; - top: 44px; } -.page.wizard .tab_content { - margin: 20px 0 160px; -} + form.uniForm.containers { + margin: 0; + } -.page.wizard form > div { - min-height: 300px; -} + form > div { + min-height: 300px; + max-width: $mq-desktop; + padding: $padding; + margin: 0 auto; -.page.wizard .button.green { - padding: 20px; - font-size: 25px; - margin: 10px 0 80px; - display: block; -} + @include media-phablet { + padding: $padding/2; + } + } + + .button.green { + padding: 20px; + font-size: 25px; + margin: 10px 0 80px; + display: inline-block; + } + + .tab_nzb_providers { + margin: 20px 0 0 0; + } -.page.wizard .tab_nzb_providers { - margin: 20px 0 0 0; } diff --git a/couchpotato/static/scripts/combined.base.min.js b/couchpotato/static/scripts/combined.base.min.js index 9ad6abd..efa2ba1 100644 --- a/couchpotato/static/scripts/combined.base.min.js +++ b/couchpotato/static/scripts/combined.base.min.js @@ -667,7 +667,7 @@ var PageBase = new Class({ }).grab(self.content = new Element("div.scroll_content")); App.addEvent("load", function() { setTimeout(function() { - if (!App.mobile_screen) { + if (!App.mobile_screen && !App.getOption("dev")) { self.content.addEvent("scroll", self.preventHover.bind(self)); } }, 100); diff --git a/couchpotato/static/scripts/combined.plugins.min.js b/couchpotato/static/scripts/combined.plugins.min.js index 3282432..a3f35b9 100644 --- a/couchpotato/static/scripts/combined.plugins.min.js +++ b/couchpotato/static/scripts/combined.plugins.min.js @@ -3707,6 +3707,7 @@ Page.Wizard = new Class({ Extends: Page.Settings, order: 70, name: "wizard", + current: "welcome", has_tab: false, wizard_only: true, headers: { @@ -3752,7 +3753,7 @@ Page.Wizard = new Class({ styles: { "margin-top": 20 }, - text: "I'm ready to start the awesomeness, wow this button is big and green!", + text: "I'm ready to start the awesomeness!", events: { click: function(e) { e.preventDefault(); @@ -3782,6 +3783,7 @@ Page.Wizard = new Class({ App.fireEvent("unload"); App.getBlock("header").hide(); self.parent(action, params); + self.el.addClass("settings"); self.addEvent("create", function() { self.orderGroups(); }); @@ -3797,18 +3799,11 @@ Page.Wizard = new Class({ orderGroups: function() { var self = this; var form = self.el.getElement(".uniForm"); - var tabs = self.el.getElement(".tabs"); + var tabs = self.el.getElement(".tabs").hide(); self.groups.each(function(group) { var group_container; if (self.headers[group]) { - group_container = new Element(".wgroup_" + group, { - styles: { - opacity: .2 - }, - tween: { - duration: 350 - } - }); + group_container = new Element(".wgroup_" + group); if (self.headers[group].include) { self.headers[group].include.each(function(inc) { group_container.addClass("wgroup_" + inc); @@ -3855,36 +3850,5 @@ Page.Wizard = new Class({ }); self.el.getElement(".advanced_toggle").destroy(); self.el.getElement(".section_nzb").hide(); - new Element(".tab_wrapper").wraps(tabs); - var minimum = self.el.getSize().y - window.getSize().y; - self.groups.each(function(group, nr) { - var g = self.el.getElement(".wgroup_" + group); - if (!g || !g.isVisible()) return; - var t = self.el.getElement(".t_" + group); - if (!t) return; - var func = function() { - self.groups.each(function(groups2, nr2) { - var t2 = self.el.getElement(".t_" + groups2); - t2[nr2 > nr ? "removeClass" : "addClass"]("done"); - }); - g.tween("opacity", 1); - }; - if (nr === 0) func(); - new ScrollSpy({ - min: function() { - var c = g.getCoordinates(); - var top = c.top - window.getSize().y / 2; - return top > minimum ? minimum : top; - }, - max: function() { - var c = g.getCoordinates(); - return c.top + c.height / 2; - }, - onEnter: func, - onLeave: function() { - g.tween("opacity", .2); - } - }); - }); } }); \ No newline at end of file diff --git a/couchpotato/static/style/combined.min.css b/couchpotato/static/style/combined.min.css index 6ced42c..98baef1 100644 --- a/couchpotato/static/style/combined.min.css +++ b/couchpotato/static/style/combined.min.css @@ -1,8 +1,9 @@ .movies>.description a:hover,.page.movie_details .releases .buttons a:hover{text-decoration:underline} +.messages .message,.more_menu .wrapper,.search_form .wrapper{box-shadow:0 0 15px 2px rgba(0,0,0,.15)} .search_form{display:inline-block;z-index:11;width:44px;position:relative} .search_form *{-webkit-transform:translateZ(0);transform:translateZ(0)} .search_form .icon-search{position:absolute;z-index:2;top:50%;left:0;height:100%;text-align:center;color:#FFF;font-size:20px;-webkit-transform:translateY(-50%);transform:translateY(-50%)} -.search_form .wrapper{position:absolute;left:44px;bottom:0;background:#ac0000;border-radius:3px 0 0 3px;display:none;box-shadow:0 0 15px 2px rgba(0,0,0,.15)} +.search_form .wrapper{position:absolute;left:44px;bottom:0;background:#ac0000;border-radius:3px 0 0 3px;display:none} .search_form .wrapper:before{-webkit-transform:rotate(45deg);transform:rotate(45deg);content:'';display:block;position:absolute;height:10px;width:10px;background:#ac0000;left:-6px;bottom:16px;z-index:1} .search_form .input{background:#FFF;border-radius:3px 0 0 3px;position:relative;left:4px;height:44px;overflow:hidden;width:100%} .search_form .input input{position:absolute;top:0;left:0;height:100%;width:100%;z-index:1} @@ -431,28 +432,23 @@ #profile_ordering li .handle:hover{opacity:1} .group_sizes .item .label{min-width:150px} .group_sizes .item .max,.group_sizes .item .min{display:inline-block;width:70px!important;min-width:0!important;margin-right:10px;text-align:center} +.page.userscript .media_result .year,.page.wizard .navigation.navigation{display:none} .page.userscript{position:absolute;width:100%;top:0;bottom:0;left:0;right:0;padding:0} .page.userscript .frame.loading{text-align:center;font-size:20px;padding:20px} .page.userscript .media_result{height:140px} .page.userscript .media_result .thumbnail{width:90px} .page.userscript .media_result .options{left:90px;padding:54px 15px} -.page.userscript .media_result .year{display:none} .page.userscript .media_result .options select[name=title]{width:190px} .page.userscript .media_result .options select[name=profile]{width:70px} -.page.wizard .uniForm{margin:0 0 30px;width:83%} -.page.wizard h1{padding:10px 0;display:block;font-size:30px;margin:80px 5px 0} +.page.wizard .tab_content.tab_content{display:block} +.page.wizard .tab_content.tab_content fieldset .ctrlHolder,.page.wizard .tab_content.tab_content fieldset h2{padding:5px} +.page.wizard h1{padding:10px 0;display:block;font-size:30px;margin:80px 5px 0;font-weight:300} .page.wizard .description{padding:10px 5px;font-size:1.45em;line-height:1.4em;display:block} -.page.wizard .tab_wrapper{background:#5c697b;height:65px;font-size:1.75em;position:fixed;top:0;margin:0;width:100%;left:0;z-index:2;box-shadow:0 0 10px rgba(0,0,0,.1)} -.messages .message,.more_menu .wrapper{box-shadow:0 0 15px 2px rgba(0,0,0,.15)} -.page.wizard .tab_wrapper .tabs{padding:0;margin:0 auto;display:block;height:100%;width:100%;max-width:960px} -.page.wizard .tabs li{display:inline-block;height:100%} -.page.wizard .tabs li a{padding:20px 10px;height:100%;display:block;color:#FFF;font-weight:400;border-bottom:4px solid transparent} -.page.wizard .tabs li:hover a{border-color:#047792} -.page.wizard .tabs li.done a{border-color:#04bce6} -.page.wizard .tab_wrapper .pointer{border-right:10px solid transparent;border-left:10px solid transparent;border-top:10px solid #5c697b;display:block;position:absolute;top:44px} -.page.wizard .tab_content{margin:20px 0 160px} -.page.wizard form>div{min-height:300px} -.page.wizard .button.green{padding:20px;font-size:25px;margin:10px 0 80px;display:block} +.page.wizard form.uniForm.containers{margin:0} +.page.wizard form>div{min-height:300px;max-width:1024px;padding:20px;margin:0 auto} +@media (max-width:480px){.page.wizard form>div{padding:10px} +} +.page.wizard .button.green{padding:20px;font-size:25px;margin:10px 0 80px;display:inline-block} .page.wizard .tab_nzb_providers{margin:20px 0 0} .api_docs h1{font-size:25px;padding:20px 40px} .api_docs pre{background:#eee;font-family:monospace;margin:0;padding:10px;width:100%;display:block;font-size:12px} From 5b4b08c2b33678a11fb75a0d9f84b4e147da8117 Mon Sep 17 00:00:00 2001 From: Ruud Date: Fri, 7 Aug 2015 17:02:05 +0200 Subject: [PATCH 229/301] Don't do scroll detection in dev mode --- couchpotato/static/scripts/page.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/static/scripts/page.js b/couchpotato/static/scripts/page.js index 42bdaac..6644549 100644 --- a/couchpotato/static/scripts/page.js +++ b/couchpotato/static/scripts/page.js @@ -30,7 +30,7 @@ var PageBase = new Class({ // Stop hover events while scrolling App.addEvent('load', function(){ setTimeout(function(){ - if(!App.mobile_screen){ + if(!App.mobile_screen && !App.getOption('dev')){ self.content.addEvent('scroll', self.preventHover.bind(self)); } }, 100); From a2544965311ab9780aad9baf3e5a6a7715a4da75 Mon Sep 17 00:00:00 2001 From: mescon Date: Sat, 8 Aug 2015 17:51:32 +0200 Subject: [PATCH 230/301] Add support for HTTP proxies Closes [#5195](https://github.com/RuudBurger/CouchPotatoServer/issues/5195). --- couchpotato/core/_base/_core.py | 19 +++++++++++++++++++ couchpotato/core/plugins/base.py | 19 ++++++++++++++++++- 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/couchpotato/core/_base/_core.py b/couchpotato/core/_base/_core.py index f455340..81f5fa5 100644 --- a/couchpotato/core/_base/_core.py +++ b/couchpotato/core/_base/_core.py @@ -276,6 +276,25 @@ config = [{ 'description': 'Let 3rd party app do stuff. Docs', }, { + 'name': 'use_proxy', + 'default': 0, + 'type': 'bool', + 'description': 'Route outbound connections via proxy. Currently, only HTTP(S) proxies are supported. ', + }, + { + 'name': 'proxy_server', + 'description': 'Override system default proxy server. Currently, only HTTP(S) proxies are supported. Ex. \"127.0.0.1:8080\". Keep empty to use system default proxy server.', + }, + { + 'name': 'proxy_username', + 'description': 'Only HTTP Basic Auth is supported. Leave blank to disable authentication.', + }, + { + 'name': 'proxy_password', + 'type': 'password', + 'description': 'Leave blank for no password.', + }, + { 'name': 'debug', 'default': 0, 'type': 'bool', diff --git a/couchpotato/core/plugins/base.py b/couchpotato/core/plugins/base.py index 57a31e2..d82473d 100644 --- a/couchpotato/core/plugins/base.py +++ b/couchpotato/core/plugins/base.py @@ -1,5 +1,5 @@ import threading -from urllib import quote +from urllib import quote, getproxies from urlparse import urlparse import glob import inspect @@ -200,6 +200,22 @@ class Plugin(object): headers['Connection'] = headers.get('Connection', 'keep-alive') headers['Cache-Control'] = headers.get('Cache-Control', 'max-age=0') + use_proxy = Env.setting('use_proxy') + proxy_server = Env.setting('proxy_server') + proxy_username = Env.setting('proxy_username') + proxy_password = Env.setting('proxy_password') + proxy_url = None + + if use_proxy: + if proxy_server: + loc = "{0}:{1}@{2}".format(proxy_username, proxy_password, proxy_server) if proxy_username else proxy_server + proxy_url = { + "http": "http://"+loc, + "https": "https://"+loc, + } + else: + proxy_url = getproxies() + r = Env.get('http_opener') # Don't try for failed requests @@ -225,6 +241,7 @@ class Plugin(object): 'files': files, 'verify': False, #verify_ssl, Disable for now as to many wrongly implemented certificates.. 'stream': stream, + 'proxies': proxy_url, } method = 'post' if len(data) > 0 or files else 'get' From 6c0e1ab794d2dd6906581cc411e3449a67179ef0 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 8 Aug 2015 21:50:06 +0200 Subject: [PATCH 231/301] Settings update --- couchpotato/static/scripts/page/settings.js | 345 ++-------------------------- couchpotato/static/style/settings.scss | 47 ++-- 2 files changed, 48 insertions(+), 344 deletions(-) diff --git a/couchpotato/static/scripts/page/settings.js b/couchpotato/static/scripts/page/settings.js index 35842f9..a86232b 100644 --- a/couchpotato/static/scripts/page/settings.js +++ b/couchpotato/static/scripts/page/settings.js @@ -328,7 +328,7 @@ var OptionBase = new Class({ Implements: [Options, Events], - klass: 'textInput', + klass: '', focused_class: 'focused', save_on_change: true, @@ -360,7 +360,7 @@ var OptionBase = new Class({ */ createBase: function(){ var self = this; - self.el = new Element('div.ctrlHolder.' + self.section + '_' + self.name); + self.el = new Element('div.ctrlHolder.' + self.section + '_' + self.name + (self.klass ? '.' + self.klass : '')); }, create: function(){ @@ -1037,16 +1037,20 @@ Option.Directories = new Class({ Option.Choice = new Class({ Extends: Option.String, + klass: 'choice', afterInject: function(){ var self = this; - self.tags = []; - self.replaceInput(); - - self.select = new Element('select').adopt( - new Element('option[text=Add option]') - ).inject(self.tag_input, 'after'); + var wrapper = new Element('div.select_wrapper.icon-dropdown').grab( + self.select = new Element('select.select', { + 'events': { + 'change': self.addSelection.bind(self) + } + }).grab( + new Element('option[text=Add option]') + ) + ); var o = self.options.options; Object.each(o.choices, function(label, choice){ @@ -1056,333 +1060,14 @@ Option.Choice = new Class({ }).inject(self.select); }); - self.select = new Form.Dropdown(self.select, { - 'onChange': self.addSelection.bind(self) - }); - }, - - replaceInput: function(){ - var self = this; - self.initialized = self.initialized ? self.initialized+1 : 1; - - var value = self.getValue(); - var matches = value.match(/<([^>]*)>/g); - - self.tag_input = new Element('ul', { - 'events': { - 'click': function(e){ - if(e.target == self.tag_input){ - var input = self.tag_input.getElement('li:last-child input'); - input.fireEvent('focus'); - input.focus(); - input.setCaretPosition(input.get('value').length); - } - - self.el.addEvent('outerClick', function(){ - self.reset(); - self.el.removeEvents('outerClick'); - }); - } - } - }).inject(self.input, 'after'); - self.el.addClass('tag_input'); - - var mtches = []; - if(matches) - matches.each(function(match, mnr){ - var pos = value.indexOf(match), - msplit = [value.substr(0, pos), value.substr(pos, match.length), value.substr(pos+match.length)]; - - msplit.each(function(matchsplit, snr){ - if(msplit.length-1 == snr){ - value = matchsplit; - - if(matches.length-1 == mnr) - mtches.append([value]); - - return; - } - mtches.append([value == matchsplit ? match : matchsplit]); - }); - }); - - if(mtches.length === 0 && value !== '') - mtches.include(value); - - mtches.each(self.addTag.bind(self)); - - self.addLastTag(); - - // Sortable - self.sortable = new Sortables(self.tag_input, { - 'revert': true, - 'handle': '', - 'opacity': 0.5, - 'onComplete': function(){ - self.setOrder(); - self.reset(); - } - }); - - // Calc width on show - var input_group = self.tag_input.getParent('.tab_content'); - input_group.addEvent('activate', self.setAllWidth.bind(self)); - }, - - addLastTag: function(){ - if(this.tag_input.getElement('li.choice:last-child') || !this.tag_input.getElement('li')) - this.addTag(''); - }, - - addTag: function(tag){ - var self = this; - tag = new Option.Choice.Tag(tag, { - 'onChange': self.setOrder.bind(self), - 'onBlur': function(){ - self.addLastTag(); - }, - 'onGoLeft': function(){ - self.goLeft(this); - }, - 'onGoRight': function(){ - self.goRight(this); - } - }); - $(tag).inject(self.tag_input); - - if(self.initialized > 1) - tag.setWidth(); - else - (function(){ tag.setWidth(); }).delay(10, self); - - self.tags.include(tag); - - return tag; - }, - - goLeft: function(from_tag){ - var self = this; - - from_tag.blur(); - - var prev_index = self.tags.indexOf(from_tag)-1; - if(prev_index >= 0) - self.tags[prev_index].selectFrom('right'); - else - from_tag.focus(); - - }, - goRight: function(from_tag){ - var self = this; - - from_tag.blur(); - - var next_index = self.tags.indexOf(from_tag)+1; - if(next_index < self.tags.length) - self.tags[next_index].selectFrom('left'); - else - from_tag.focus(); - }, - - setOrder: function(){ - var self = this; - - var value = ''; - self.tag_input.getElements('li').each(function(el){ - value += el.getElement('span').get('text'); - }); - self.addLastTag(); + wrapper.inject(self.input, 'after'); - self.input.set('value', value); - self.input.fireEvent('change'); - self.setAllWidth(); }, addSelection: function(){ var self = this; - - var tag = self.addTag(self.el.getElement('.selection input').get('value')); - self.sortable.addItems($(tag)); - self.setOrder(); - self.setAllWidth(); - }, - - reset: function(){ - var self = this; - - self.tag_input.destroy(); - self.sortable.detach(); - - self.replaceInput(); - self.setAllWidth(); - }, - - setAllWidth: function(){ - var self = this; - self.tags.each(function(tag){ - tag.setWidth.delay(10, tag); - }); - } - -}); - -Option.Choice.Tag = new Class({ - - Implements: [Options, Events], - - options: { - 'pre': '<', - 'post': '>' - }, - - initialize: function(tag, options){ - var self = this; - self.setOptions(options); - - self.tag = tag; - self.is_choice = tag.substr(0, 1) == self.options.pre && tag.substr(-1) == self.options.post; - - self.create(); - }, - - create: function(){ - var self = this; - - self.el = new Element('li', { - 'class': self.is_choice ? 'choice' : '', - 'styles': { - 'border': 0 - }, - 'events': { - 'mouseover': !self.is_choice ? self.fireEvent.bind(self, 'focus') : function(){} - } - }).adopt( - self.input = new Element(self.is_choice ? 'span' : 'input', { - 'text': self.tag, - 'value': self.tag, - 'styles': { - 'width': 0 - }, - 'events': { - 'keyup': self.is_choice ? null : function(e){ - var current_caret_pos = self.input.getCaretPosition(); - if(e.key == 'left' && current_caret_pos == self.last_caret_pos){ - self.fireEvent('goLeft'); - } - else if(e.key == 'right' && self.last_caret_pos === current_caret_pos){ - self.fireEvent('goRight'); - } - self.last_caret_pos = self.input.getCaretPosition(); - - self.setWidth(); - self.fireEvent('change'); - }, - 'focus': self.fireEvent.bind(self, 'focus'), - 'blur': self.fireEvent.bind(self, 'blur') - } - }), - self.span = !self.is_choice ? new Element('span', { - 'text': self.tag - }) : null, - self.del_button = new Element('a.delete', { - 'events': { - 'click': self.del.bind(self) - } - }) - ); - - self.addEvent('focus', self.setWidth.bind(self)); - - }, - - blur: function(){ - var self = this; - - self.input.blur(); - - self.selected = false; - self.el.removeClass('selected'); - self.input.removeEvents('outerClick'); - }, - - focus: function(){ - var self = this; - if(!self.is_choice){ - this.input.focus(); - } - else { - if(self.selected) return; - self.selected = true; - self.el.addClass('selected'); - self.input.addEvent('outerClick', self.blur.bind(self)); - - var temp_input = new Element('input', { - 'events': { - 'keydown': function(e){ - e.stop(); - - if(e.key == 'right'){ - self.fireEvent('goRight'); - this.destroy(); - } - else if(e.key == 'left'){ - self.fireEvent('goLeft'); - this.destroy(); - } - else if(e.key == 'backspace'){ - self.del(); - this.destroy(); - self.fireEvent('goLeft'); - } - } - }, - 'styles': { - 'height': 0, - 'width': 0, - 'position': 'absolute', - 'top': -200 - } - }); - self.el.adopt(temp_input); - temp_input.focus(); - } - }, - - selectFrom: function(direction){ - var self = this; - - if(!direction || self.is_choice){ - self.focus(); - } - else { - self.focus(); - var position = direction == 'left' ? 0 : self.input.get('value').length; - self.input.setCaretPosition(position); - } - - }, - - setWidth: function(){ - var self = this; - - if(self.span && self.input){ - self.span.set('text', self.input.get('value')); - self.input.setStyle('width', self.span.getSize().x+2); - } - }, - - del: function(){ - var self = this; - self.el.destroy(); - self.fireEvent('change'); - }, - - getValue: function(){ - return this.span.get('text'); - }, - - toElement: function(){ - return this.el; + self.input.set('value', self.input.get('value') + self.select.get('value')); + self.input.fireEvent('change'); } }); diff --git a/couchpotato/static/style/settings.scss b/couchpotato/static/style/settings.scss index e56dadb..e91c6c0 100644 --- a/couchpotato/static/style/settings.scss +++ b/couchpotato/static/style/settings.scss @@ -103,6 +103,7 @@ font-weight: 400; color: $primary_color; text-decoration: underline; + display: inline; } } } @@ -161,16 +162,20 @@ select { cursor: pointer; - -moz-appearance: none; + appearance: none; + width: 100%; + min-width: 200px; + border-radius: 0; } &:before { + vertical-align: top; pointer-events: none; position: absolute; + top: 0; + line-height: 2em; right: $padding/2; height: 100%; - line-height: 0; - margin-top: -3px; } } @@ -220,7 +225,6 @@ right: 0; border: 0; padding: 0; - height: 46px; ~ h2 { margin-right: 66px + $padding; @@ -247,18 +251,16 @@ } .switch { + $switch_height: 24px; $switch_active: $primary_color; display: inline-block; background: $switch_active; - border: 1px solid $switch_active; - box-shadow: inset 0 0 0 1px #FFF; - height: 22px; - width: 66px; + height: $switch_height; + width: $switch_height * 2.5; min-width: 0 !important; - padding: 2px; transition: all 250ms; cursor: pointer; - border-radius: $border_radius; + border-radius: $switch_height; input { display: none; @@ -266,11 +268,12 @@ .toggle { background: #FFF; - height: 100%; - width: 40%; + margin: 1px; + height: $switch_height - 2px; + width: $switch_height - 2px; transition: transform 250ms; - transform: translateX(150%); - border-radius: $border_radius - 1px; + transform: translateX($switch_height*1.5); + border-radius: $switch_height; } } @@ -654,4 +657,20 @@ } } + .choice { + .select_wrapper { + margin-left: $padding; + width: 120px; + min-width: 120px; + + @include media-phablet { + margin: $padding/2 0 0; + } + + select { + min-width: 0 !important; + } + } + } + } From 38fa045e88347e1d6055be8c4215ad7a576fb466 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sat, 8 Aug 2015 22:00:04 +0200 Subject: [PATCH 232/301] Build css and js --- couchpotato/static/scripts/combined.base.min.js | 259 +----------------------- couchpotato/static/style/combined.min.css | 120 +++++------ couchpotato/static/style/main.scss | 1 - couchpotato/static/style/settings.scss | 2 +- 4 files changed, 59 insertions(+), 323 deletions(-) diff --git a/couchpotato/static/scripts/combined.base.min.js b/couchpotato/static/scripts/combined.base.min.js index efa2ba1..2428285 100644 --- a/couchpotato/static/scripts/combined.base.min.js +++ b/couchpotato/static/scripts/combined.base.min.js @@ -1301,7 +1301,7 @@ Page.Settings = new Class({ var OptionBase = new Class({ Implements: [ Options, Events ], - klass: "textInput", + klass: "", focused_class: "focused", save_on_change: true, initialize: function(section, name, value, options) { @@ -1322,7 +1322,7 @@ var OptionBase = new Class({ }, createBase: function() { var self = this; - self.el = new Element("div.ctrlHolder." + self.section + "_" + self.name); + self.el = new Element("div.ctrlHolder." + self.section + "_" + self.name + (self.klass ? "." + self.klass : "")); }, create: function() {}, createLabel: function() { @@ -1781,11 +1781,14 @@ Option.Directories = new Class({ Option.Choice = new Class({ Extends: Option.String, + klass: "choice", afterInject: function() { var self = this; - self.tags = []; - self.replaceInput(); - self.select = new Element("select").adopt(new Element("option[text=Add option]")).inject(self.tag_input, "after"); + var wrapper = new Element("div.select_wrapper.icon-dropdown").grab(self.select = new Element("select.select", { + events: { + change: self.addSelection.bind(self) + } + }).grab(new Element("option[text=Add option]"))); var o = self.options.options; Object.each(o.choices, function(label, choice) { new Element("option", { @@ -1793,252 +1796,12 @@ Option.Choice = new Class({ value: o.pre + choice + o.post }).inject(self.select); }); - self.select = new Form.Dropdown(self.select, { - onChange: self.addSelection.bind(self) - }); - }, - replaceInput: function() { - var self = this; - self.initialized = self.initialized ? self.initialized + 1 : 1; - var value = self.getValue(); - var matches = value.match(/<([^>]*)>/g); - self.tag_input = new Element("ul", { - events: { - click: function(e) { - if (e.target == self.tag_input) { - var input = self.tag_input.getElement("li:last-child input"); - input.fireEvent("focus"); - input.focus(); - input.setCaretPosition(input.get("value").length); - } - self.el.addEvent("outerClick", function() { - self.reset(); - self.el.removeEvents("outerClick"); - }); - } - } - }).inject(self.input, "after"); - self.el.addClass("tag_input"); - var mtches = []; - if (matches) matches.each(function(match, mnr) { - var pos = value.indexOf(match), msplit = [ value.substr(0, pos), value.substr(pos, match.length), value.substr(pos + match.length) ]; - msplit.each(function(matchsplit, snr) { - if (msplit.length - 1 == snr) { - value = matchsplit; - if (matches.length - 1 == mnr) mtches.append([ value ]); - return; - } - mtches.append([ value == matchsplit ? match : matchsplit ]); - }); - }); - if (mtches.length === 0 && value !== "") mtches.include(value); - mtches.each(self.addTag.bind(self)); - self.addLastTag(); - self.sortable = new Sortables(self.tag_input, { - revert: true, - handle: "", - opacity: .5, - onComplete: function() { - self.setOrder(); - self.reset(); - } - }); - var input_group = self.tag_input.getParent(".tab_content"); - input_group.addEvent("activate", self.setAllWidth.bind(self)); - }, - addLastTag: function() { - if (this.tag_input.getElement("li.choice:last-child") || !this.tag_input.getElement("li")) this.addTag(""); - }, - addTag: function(tag) { - var self = this; - tag = new Option.Choice.Tag(tag, { - onChange: self.setOrder.bind(self), - onBlur: function() { - self.addLastTag(); - }, - onGoLeft: function() { - self.goLeft(this); - }, - onGoRight: function() { - self.goRight(this); - } - }); - $(tag).inject(self.tag_input); - if (self.initialized > 1) tag.setWidth(); else (function() { - tag.setWidth(); - }).delay(10, self); - self.tags.include(tag); - return tag; - }, - goLeft: function(from_tag) { - var self = this; - from_tag.blur(); - var prev_index = self.tags.indexOf(from_tag) - 1; - if (prev_index >= 0) self.tags[prev_index].selectFrom("right"); else from_tag.focus(); - }, - goRight: function(from_tag) { - var self = this; - from_tag.blur(); - var next_index = self.tags.indexOf(from_tag) + 1; - if (next_index < self.tags.length) self.tags[next_index].selectFrom("left"); else from_tag.focus(); - }, - setOrder: function() { - var self = this; - var value = ""; - self.tag_input.getElements("li").each(function(el) { - value += el.getElement("span").get("text"); - }); - self.addLastTag(); - self.input.set("value", value); - self.input.fireEvent("change"); - self.setAllWidth(); + wrapper.inject(self.input, "after"); }, addSelection: function() { var self = this; - var tag = self.addTag(self.el.getElement(".selection input").get("value")); - self.sortable.addItems($(tag)); - self.setOrder(); - self.setAllWidth(); - }, - reset: function() { - var self = this; - self.tag_input.destroy(); - self.sortable.detach(); - self.replaceInput(); - self.setAllWidth(); - }, - setAllWidth: function() { - var self = this; - self.tags.each(function(tag) { - tag.setWidth.delay(10, tag); - }); - } -}); - -Option.Choice.Tag = new Class({ - Implements: [ Options, Events ], - options: { - pre: "<", - post: ">" - }, - initialize: function(tag, options) { - var self = this; - self.setOptions(options); - self.tag = tag; - self.is_choice = tag.substr(0, 1) == self.options.pre && tag.substr(-1) == self.options.post; - self.create(); - }, - create: function() { - var self = this; - self.el = new Element("li", { - class: self.is_choice ? "choice" : "", - styles: { - border: 0 - }, - events: { - mouseover: !self.is_choice ? self.fireEvent.bind(self, "focus") : function() {} - } - }).adopt(self.input = new Element(self.is_choice ? "span" : "input", { - text: self.tag, - value: self.tag, - styles: { - width: 0 - }, - events: { - keyup: self.is_choice ? null : function(e) { - var current_caret_pos = self.input.getCaretPosition(); - if (e.key == "left" && current_caret_pos == self.last_caret_pos) { - self.fireEvent("goLeft"); - } else if (e.key == "right" && self.last_caret_pos === current_caret_pos) { - self.fireEvent("goRight"); - } - self.last_caret_pos = self.input.getCaretPosition(); - self.setWidth(); - self.fireEvent("change"); - }, - focus: self.fireEvent.bind(self, "focus"), - blur: self.fireEvent.bind(self, "blur") - } - }), self.span = !self.is_choice ? new Element("span", { - text: self.tag - }) : null, self.del_button = new Element("a.delete", { - events: { - click: self.del.bind(self) - } - })); - self.addEvent("focus", self.setWidth.bind(self)); - }, - blur: function() { - var self = this; - self.input.blur(); - self.selected = false; - self.el.removeClass("selected"); - self.input.removeEvents("outerClick"); - }, - focus: function() { - var self = this; - if (!self.is_choice) { - this.input.focus(); - } else { - if (self.selected) return; - self.selected = true; - self.el.addClass("selected"); - self.input.addEvent("outerClick", self.blur.bind(self)); - var temp_input = new Element("input", { - events: { - keydown: function(e) { - e.stop(); - if (e.key == "right") { - self.fireEvent("goRight"); - this.destroy(); - } else if (e.key == "left") { - self.fireEvent("goLeft"); - this.destroy(); - } else if (e.key == "backspace") { - self.del(); - this.destroy(); - self.fireEvent("goLeft"); - } - } - }, - styles: { - height: 0, - width: 0, - position: "absolute", - top: -200 - } - }); - self.el.adopt(temp_input); - temp_input.focus(); - } - }, - selectFrom: function(direction) { - var self = this; - if (!direction || self.is_choice) { - self.focus(); - } else { - self.focus(); - var position = direction == "left" ? 0 : self.input.get("value").length; - self.input.setCaretPosition(position); - } - }, - setWidth: function() { - var self = this; - if (self.span && self.input) { - self.span.set("text", self.input.get("value")); - self.input.setStyle("width", self.span.getSize().x + 2); - } - }, - del: function() { - var self = this; - self.el.destroy(); - self.fireEvent("change"); - }, - getValue: function() { - return this.span.get("text"); - }, - toElement: function() { - return this.el; + self.input.set("value", self.input.get("value") + self.select.get("value")); + self.input.fireEvent("change"); } }); diff --git a/couchpotato/static/style/combined.min.css b/couchpotato/static/style/combined.min.css index 98baef1..0421144 100644 --- a/couchpotato/static/style/combined.min.css +++ b/couchpotato/static/style/combined.min.css @@ -1,5 +1,5 @@ +.messages .message,.more_menu .wrapper,.page.settings .directory_list,.search_form .wrapper{box-shadow:0 0 15px 2px rgba(0,0,0,.15)} .movies>.description a:hover,.page.movie_details .releases .buttons a:hover{text-decoration:underline} -.messages .message,.more_menu .wrapper,.search_form .wrapper{box-shadow:0 0 15px 2px rgba(0,0,0,.15)} .search_form{display:inline-block;z-index:11;width:44px;position:relative} .search_form *{-webkit-transform:translateZ(0);transform:translateZ(0)} .search_form .icon-search{position:absolute;z-index:2;top:50%;left:0;height:100%;text-align:center;color:#FFF;font-size:20px;-webkit-transform:translateY(-50%);transform:translateY(-50%)} @@ -153,7 +153,7 @@ @media (max-width:480px){.page.movie_details .overlay{left:0;border-radius:0} .page.movie_details .overlay .close{width:44px} } -.page.movie_details .scroll_content{position:fixed;z-index:2;top:0;bottom:0;right:0;left:176px;background:#FFF;border-radius:3px 0 0 3px;overflow-y:auto;-webkit-transform:translateX(100%) translateZ(0);transform:translateX(100%) translateZ(0);transition:-webkit-transform 350ms cubic-bezier(.9,0,.1,1);transition:transform 350ms cubic-bezier(.9,0,.1,1)} +.page.movie_details .scroll_content{position:fixed;z-index:2;top:0;bottom:0;right:0;left:176px;background:#FFF;border-radius:3px 0 0 3px;overflow-y:auto;-webkit-transform:translateX(100%)translateZ(0);transform:translateX(100%)translateZ(0);transition:-webkit-transform 350ms cubic-bezier(.9,0,.1,1);transition:transform 350ms cubic-bezier(.9,0,.1,1)} .page.movie_details .scroll_content>.head{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row nowrap;-ms-flex-flow:row nowrap;flex-flow:row nowrap;padding:0 20px;position:relative;z-index:2} @media (max-width:480px){.page.movie_details .scroll_content{left:44px} .page.movie_details .scroll_content>.head{-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:0 10px;line-height:1em} @@ -187,7 +187,7 @@ .page.movie_details .scroll_content>.head .buttons{margin-left:auto} .page.movie_details .scroll_content .section{padding:10px} } -.page.movie_details .files span,.page.movie_details .releases .item span{white-space:nowrap;padding:6.67px 0;overflow:hidden;text-overflow:ellipsis} +.page.movie_details .files span,.page.movie_details .releases .item span{text-overflow:ellipsis;overflow:hidden;white-space:nowrap;padding:6.67px 0} .page.movie_details.show{pointer-events:auto} .page.movie_details.show .overlay{opacity:1;transition-delay:0s} .page.movie_details.show .overlay .close{opacity:1;transition-delay:300ms} @@ -246,7 +246,7 @@ .page.movie_details .releases .actions{min-width:80px;max-width:80px} .page.movie_details .trailer_container{background:#000;position:relative;padding-bottom:56.25%;height:0;overflow:hidden;max-width:100%;cursor:pointer} .alph_nav .menus .button,.alph_nav .menus .counter{line-height:80px;padding:0 10px} -.page.movie_details .trailer_container .background{opacity:.3;transition:all 300ms;-webkit-transform:scale(1.05) translateZ(0);transform:scale(1.05) translateZ(0);background:center no-repeat;background-size:cover;position:absolute;top:0;right:0;bottom:0;left:0;z-index:1} +.page.movie_details .trailer_container .background{opacity:.3;transition:all 300ms;-webkit-transform:scale(1.05)translateZ(0);transform:scale(1.05)translateZ(0);background:center no-repeat;background-size:cover;position:absolute;top:0;right:0;bottom:0;left:0;z-index:1} .page.movie_details .trailer_container .icon-play{opacity:.9;position:absolute;z-index:2;text-align:center;width:100%;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);transition:all 300ms;color:#FFF;font-size:110px} @media (max-width:1024px){.page.movie_details .trailer_container .icon-play{font-size:55px} } @@ -307,6 +307,7 @@ @media all and (max-width:600px){.charts .chart{width:100%} } .charts .chart .media_result .data{left:150px;background:#4e5969;border:none} +#category_ordering li:last-child,#profile_ordering li:last-child,.api_docs .api .params tr:last-child td,.api_docs .api .params tr:last-child th{border:0} .charts .chart .media_result .data .info{top:10px;left:15px;right:15px;bottom:10px;overflow:hidden} .charts .chart .media_result .data .info h2{white-space:normal;max-height:120px;font-size:18px;line-height:18px} .charts .chart .media_result .data .info .genres,.charts .chart .media_result .data .info .rating,.charts .chart .media_result .data .info .year{position:static;display:block;padding:0;opacity:.6} @@ -334,38 +335,11 @@ .toggle_menu a{display:block;width:50%;float:left;color:rgba(255,255,255,.6);border-bottom:1px solid rgba(255,255,255,.06667)} .toggle_menu a:hover{border-color:#047792;border-width:4px;color:#fff} .toggle_menu a.active{border-bottom:4px solid #04bce6;color:#fff} +#category_ordering li,#profile_ordering li,.add_new_profile{border-bottom:1px solid #eaeaea} .toggle_menu a:last-child{float:right} .toggle_menu h2{height:40px} @media all and (max-width:480px){.toggle_menu h2{font-size:16px;text-align:center;height:30px} } -.suggestions{clear:both;padding-top:10px;margin-bottom:30px} -.suggestions>h2{height:40px} -.suggestions .media_result{display:inline-block;width:33.333%;height:150px} -@media all and (max-width:960px){.suggestions .media_result{width:50%} -} -@media all and (max-width:600px){.suggestions .media_result{width:100%} -} -.suggestions .media_result .data{left:100px;background:#4e5969;border:none} -.suggestions .media_result .data .info{top:10px;left:15px;right:15px;bottom:10px;overflow:hidden} -.suggestions .media_result .data .info h2{white-space:normal;max-height:120px;font-size:18px;line-height:18px} -.suggestions .media_result .data .info .genres,.suggestions .media_result .data .info .rating,.suggestions .media_result .data .info .year{position:static;display:block;padding:0;opacity:.6} -.suggestions .media_result .data .info .year{margin:10px 0 0} -.suggestions .media_result .data .info .rating{font-size:20px;float:right;margin-top:-20px} -.suggestions .media_result .data .info .rating:before{content:"\e031";font-family:Elusive-Icons;font-size:14px;margin:0 5px 0 0;vertical-align:bottom} -.suggestions .media_result .data .info .genres{font-size:11px;font-style:italic;text-align:right} -.suggestions .media_result .data .info .plot{display:block;font-size:11px;overflow:hidden;text-align:justify;height:100%;z-index:2;top:64px;position:absolute;background:#4e5969;cursor:pointer;transition:all .4s ease-in-out;padding:0 3px 10px 0} -.suggestions .media_result .data:before{content:'';display:block;height:10px;right:0;left:0;bottom:10px;position:absolute;background:linear-gradient(0deg,#4e5969 0,rgba(78,89,105,0) 100%);z-index:3;pointer-events:none} -.suggestions .media_result .data .info .plot.full{top:0;overflow:auto} -.suggestions .media_result .data{cursor:default} -.suggestions .media_result .options{left:100px} -.suggestions .media_result .options select[name=category],.suggestions .media_result .options select[name=profile],.suggestions .media_result .options select[name=title]{width:100%} -.suggestions .media_result .button{position:absolute;margin:2px 0 0;right:15px;bottom:15px} -.suggestions .media_result .thumbnail{width:100px} -.suggestions .media_result .actions{position:absolute;top:10px;right:10px;display:none;width:140px} -.suggestions .media_result:hover .actions{display:block} -.suggestions .media_result:hover h2 .title{opacity:0} -.suggestions .media_result .data.open .actions{display:none} -.suggestions .media_result .actions a{margin-left:10px;vertical-align:middle} .add_new_category{padding:20px;display:block;text-align:center;font-size:20px} .category{margin-bottom:20px;position:relative} .category>.delete{position:absolute;padding:16px;right:0;cursor:pointer;opacity:.6;color:#fd5353} @@ -375,8 +349,7 @@ .category .formHint{opacity:.1} .category:hover .formHint{opacity:1} #category_ordering ul{float:left;margin:0;width:275px;padding:0} -#category_ordering li{cursor:-webkit-grab;cursor:grab;border-bottom:1px solid #eaeaea;padding:5px;list-style:none} -#category_ordering li:last-child{border:0} +#category_ordering li{cursor:-webkit-grab;cursor:grab;padding:5px;list-style:none} #category_ordering li .check{margin:2px 10px 0 0;vertical-align:top} #category_ordering li>span{display:inline-block;height:20px;vertical-align:top;line-height:20px} #category_ordering li .handle{width:20px;float:right} @@ -404,7 +377,7 @@ .report_popup.report_popup .bug{width:80%;height:80%;max-height:800px;max-width:800px;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:column nowrap;-ms-flex-flow:column nowrap;flex-flow:column nowrap} .report_popup.report_popup .bug textarea{display:block;width:100%;background:#FFF;padding:20px;overflow:auto;color:#666;height:70%;font-size:12px} .do_report.do_report{z-index:10000;position:absolute;padding:10px;background:#ac0000;color:#FFF} -.add_new_profile{padding:20px;display:block;text-align:center;font-size:20px;border-bottom:1px solid #eaeaea} +.add_new_profile{padding:20px;display:block;text-align:center;font-size:20px} .profile{margin-bottom:20px} .profile .quality_label input{font-weight:700} .profile .ctrlHolder .types{-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto;min-width:360px} @@ -420,11 +393,9 @@ .profile .ctrlHolder.wait_for.wait_for input{min-width:0;width:40px;text-align:center;margin:0 2px} .profile .ctrlHolder.wait_for.wait_for .advanced{display:none;color:#ac0000} .show_advanced .profile .ctrlHolder.wait_for.wait_for .advanced{display:inline} -#profile_ordering li,.page.login{display:-webkit-flex;display:-ms-flexbox} #profile_ordering ul{list-style:none;margin:0;width:275px;padding:0} -#profile_ordering li{border-bottom:1px solid #eaeaea;padding:5px;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center} +#profile_ordering li{padding:5px;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center} #profile_ordering li:hover{background:#eaeaea} -#profile_ordering li:last-child{border:0} #profile_ordering li input[type=checkbox]{margin:2px 10px 0 0;vertical-align:top} #profile_ordering li>span{display:inline-block;height:20px;vertical-align:top;line-height:20px} #profile_ordering li>span.profile_label{-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto} @@ -459,7 +430,6 @@ .api_docs .api .params{background:#fafafa;width:100%} .api_docs .api .params h3{clear:both;float:left;width:100px} .api_docs .api .params td,.api_docs .api .params th{padding:3px 5px;border-bottom:1px solid #eee} -.api_docs .api .params tr:last-child td,.api_docs .api .params tr:last-child th{border:0} .api_docs .api .params .param{vertical-align:top} .api_docs .api .params .param th{text-align:left;width:100px} .api_docs .api .params .param .type{font-style:italic;margin-right:10px;width:100px;color:#666} @@ -477,6 +447,7 @@ .api_docs .database table .form,.api_docs .database table form{width:600px} .api_docs .database table textarea{font-size:12px;width:100%;height:200px} .api_docs .database table input[type=submit]{display:block} +.page.login,body{display:-webkit-flex;display:-ms-flexbox} .page.login{background:#FFF;display:flex;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;font-size:1.25em} .page.login h1{padding:0 0 10px;font-size:60px;font-family:Lobster;font-weight:400;color:#ac0000;text-align:center} .page.login form{padding:0;width:300px} @@ -485,7 +456,7 @@ .page.login input[type=password],.page.login input[type=text]{width:100%!important} .page.login .remember_me{font-size:15px;float:left;width:150px} .page.login .button{float:right;margin:0;transition:none} -@font-face{font-family:icons;src:url(../fonts/icons.eot?74719542);src:url(../fonts/icons.eot?74719542#iefix) format("embedded-opentype"),url(../fonts/icons.woff?747195412) format("woff"),url(../fonts/icons.ttf?74719542) format("truetype"),url(../fonts/icons.svg?74719542#icons) format("svg");font-weight:400;font-style:normal} +@font-face{font-family:icons;src:url(../fonts/icons.eot?74719542);src:url(../fonts/icons.eot?74719542#iefix)format("embedded-opentype"),url(../fonts/icons.woff?747195412)format("woff"),url(../fonts/icons.ttf?74719542)format("truetype"),url(../fonts/icons.svg?74719542#icons)format("svg");font-weight:400;font-style:normal} [class*=" icon-"]:before,[class^=icon-]:before{font-family:icons;font-style:normal;font-weight:400;speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale} .icon-left-arrow:before{content:'\e800'} .icon-settings:before{content:'\e801'} @@ -516,21 +487,21 @@ .icon-star:before{content:'\e81a'} .icon-star-empty:before{content:'\e81b'} .icon-star-half:before{content:'\e81c'} -@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Light-webfont.eot);src:url(../fonts/OpenSans-Light-webfont.eot?#iefix) format("embedded-opentype"),url(../fonts/OpenSans-Light-webfont.woff) format("woff"),url(../fonts/OpenSans-Light-webfont.ttf) format("truetype"),url(../fonts/OpenSans-Light-webfont.svg#OpenSansRegular) format("svg");font-weight:200;font-style:normal} -@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Regular-webfont.eot);src:url(../fonts/OpenSans-Regular-webfont.eot?#iefix) format("embedded-opentype"),url(../fonts/OpenSans-Regular-webfont.woff) format("woff"),url(../fonts/OpenSans-Regular-webfont.ttf) format("truetype"),url(../fonts/OpenSans-Regular-webfont.svg#OpenSansRegular) format("svg");font-weight:400;font-style:normal} -@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Italic-webfont.eot);src:url(../fonts/OpenSans-Italic-webfont.eot?#iefix) format("embedded-opentype"),url(../fonts/OpenSans-Italic-webfont.woff) format("woff"),url(../fonts/OpenSans-Italic-webfont.ttf) format("truetype"),url(../fonts/OpenSans-Italic-webfont.svg#OpenSansItalic) format("svg");font-weight:400;font-style:italic} -@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Bold-webfont.eot);src:url(../fonts/OpenSans-Bold-webfont.eot?#iefix) format("embedded-opentype"),url(../fonts/OpenSans-Bold-webfont.woff) format("woff"),url(../fonts/OpenSans-Bold-webfont.ttf) format("truetype"),url(../fonts/OpenSans-Bold-webfont.svg#OpenSansBold) format("svg");font-weight:700;font-style:normal} -@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-BoldItalic-webfont.eot);src:url(../fonts/OpenSans-BoldItalic-webfont.eot?#iefix) format("embedded-opentype"),url(../fonts/OpenSans-BoldItalic-webfont.woff) format("woff"),url(../fonts/OpenSans-BoldItalic-webfont.ttf) format("truetype"),url(../fonts/OpenSans-BoldItalic-webfont.svg#OpenSansBoldItalic) format("svg");font-weight:700;font-style:italic} -@font-face{font-family:Lobster;src:url(../fonts/Lobster-webfont.eot);src:url(../fonts/Lobster-webfont.eot?#iefix) format("embedded-opentype"),url(../fonts/Lobster-webfont.woff2) format("woff2"),url(../fonts/Lobster-webfont.woff) format("woff"),url(../fonts/Lobster-webfont.ttf) format("truetype"),url(../fonts/Lobster-webfont.svg#lobster_14regular) format("svg");font-weight:400;font-style:normal} +@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Light-webfont.eot);src:url(../fonts/OpenSans-Light-webfont.eot?#iefix)format("embedded-opentype"),url(../fonts/OpenSans-Light-webfont.woff)format("woff"),url(../fonts/OpenSans-Light-webfont.ttf)format("truetype"),url(../fonts/OpenSans-Light-webfont.svg#OpenSansRegular)format("svg");font-weight:200;font-style:normal} +@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Regular-webfont.eot);src:url(../fonts/OpenSans-Regular-webfont.eot?#iefix)format("embedded-opentype"),url(../fonts/OpenSans-Regular-webfont.woff)format("woff"),url(../fonts/OpenSans-Regular-webfont.ttf)format("truetype"),url(../fonts/OpenSans-Regular-webfont.svg#OpenSansRegular)format("svg");font-weight:400;font-style:normal} +@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Italic-webfont.eot);src:url(../fonts/OpenSans-Italic-webfont.eot?#iefix)format("embedded-opentype"),url(../fonts/OpenSans-Italic-webfont.woff)format("woff"),url(../fonts/OpenSans-Italic-webfont.ttf)format("truetype"),url(../fonts/OpenSans-Italic-webfont.svg#OpenSansItalic)format("svg");font-weight:400;font-style:italic} +@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Bold-webfont.eot);src:url(../fonts/OpenSans-Bold-webfont.eot?#iefix)format("embedded-opentype"),url(../fonts/OpenSans-Bold-webfont.woff)format("woff"),url(../fonts/OpenSans-Bold-webfont.ttf)format("truetype"),url(../fonts/OpenSans-Bold-webfont.svg#OpenSansBold)format("svg");font-weight:700;font-style:normal} +@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-BoldItalic-webfont.eot);src:url(../fonts/OpenSans-BoldItalic-webfont.eot?#iefix)format("embedded-opentype"),url(../fonts/OpenSans-BoldItalic-webfont.woff)format("woff"),url(../fonts/OpenSans-BoldItalic-webfont.ttf)format("truetype"),url(../fonts/OpenSans-BoldItalic-webfont.svg#OpenSansBoldItalic)format("svg");font-weight:700;font-style:italic} +@font-face{font-family:Lobster;src:url(../fonts/Lobster-webfont.eot);src:url(../fonts/Lobster-webfont.eot?#iefix)format("embedded-opentype"),url(../fonts/Lobster-webfont.woff2)format("woff2"),url(../fonts/Lobster-webfont.woff)format("woff"),url(../fonts/Lobster-webfont.ttf)format("truetype"),url(../fonts/Lobster-webfont.svg#lobster_14regular)format("svg");font-weight:400;font-style:normal} *{margin:0;padding:0;box-sizing:border-box;text-rendering:optimizeSpeed;-webkit-backface-visibility:hidden;backface-visibility:hidden} body,html{font-size:14px;line-height:1.5;font-family:OpenSans,"Helvetica Neue",Helvetica,Arial,Geneva,sans-serif;font-weight:300;height:100%;margin:0;padding:0;background:#111;overflow:hidden} -body{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row nowrap;-ms-flex-flow:row nowrap;flex-flow:row nowrap} +body{display:flex;-webkit-flex-flow:row nowrap;-ms-flex-flow:row nowrap;flex-flow:row nowrap} a{display:inline-block;position:relative;overflow:hidden;text-decoration:none;cursor:pointer;-webkit-tap-highlight-color:transparent} input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#FFF;border:1px solid #b7b7b7} .button{color:#ac0000;font-weight:300;padding:5px;cursor:pointer;border:1px solid #ac0000;border-radius:3px;margin:0 5px;transition:all 150ms} .button:hover{background:#ac0000;color:#FFF} -.ripple{position:absolute;height:10px;width:10px;border-radius:50%;background:#ac0000;-webkit-transform:translate(-50%,-50%) scale(1) rotateZ(360deg);transform:translate(-50%,-50%) scale(1) rotateZ(360deg);opacity:.2;transition:all 1.5s ease;transition-property:opacity,-webkit-transform;transition-property:opacity,transform} -.ripple.animate{-webkit-transform:translate(-50%,-50%) scale(100) rotateZ(360deg);transform:translate(-50%,-50%) scale(100) rotateZ(360deg);opacity:0} +.ripple{position:absolute;height:10px;width:10px;border-radius:50%;background:#ac0000;-webkit-transform:translate(-50%,-50%)scale(1)rotateZ(360deg);transform:translate(-50%,-50%)scale(1)rotateZ(360deg);opacity:.2;transition:all 1.5s ease;transition-property:opacity,-webkit-transform;transition-property:opacity,transform} +.ripple.animate{-webkit-transform:translate(-50%,-50%)scale(100)rotateZ(360deg);transform:translate(-50%,-50%)scale(100)rotateZ(360deg);opacity:0} .header{width:132px;min-width:132px;position:relative;z-index:100} @media (max-width:480px){.header{width:44px;min-width:44px;z-index:21} } @@ -590,8 +561,8 @@ input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#F .page h1,.page h2,.page h3,.page h4{font-weight:300} .page h2{font-size:24px;padding:20px} .page .navigation{z-index:2;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center;position:fixed;top:0;height:80px;left:152px;right:20px;background:#FFF;border-radius:3px 0 0} -.more_menu .button,.page .navigation ul li{display:inline-block} .more_menu,.more_menu .button:before{position:relative} +.more_menu .button,.page .navigation ul li{display:inline-block} @media (max-width:480px){.page h2{font-size:18px;padding:10px} .page .navigation{height:44px;left:64px} } @@ -609,49 +580,48 @@ input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#F .more_menu{line-height:1em} .more_menu .button{font-size:24px;cursor:pointer} .more_menu .wrapper{display:none;position:absolute;right:0;background:#ac0000;z-index:5000;border-radius:3px 0 0 3px;-webkit-transform-origin:80% 0;transform-origin:80% 0} -.more_menu .wrapper:before{-webkit-transform:rotate(45deg) translateY(-60%);transform:rotate(45deg) translateY(-60%);content:'';display:block;position:absolute;background:#ac0000;height:10px;width:10px;left:-9px;bottom:11px;z-index:1;opacity:1;border-radius:3px} +.more_menu .wrapper:before{-webkit-transform:rotate(45deg)translateY(-60%);transform:rotate(45deg)translateY(-60%);content:'';display:block;position:absolute;background:#ac0000;height:10px;width:10px;left:-9px;bottom:11px;z-index:1;opacity:1;border-radius:3px} +.mask,.messages{right:0;bottom:0} .more_menu .wrapper ul{background:#FFF;position:relative;z-index:2;overflow:hidden;border-radius:3px 0 0 3px} .more_menu .wrapper ul li{display:block;line-height:1em;border-top:1px solid #eaeaea} .more_menu .wrapper ul li:first-child{border-top:0} .more_menu .wrapper ul li a{display:block;color:#000;padding:5px 10px;font-size:1em;line-height:22px} -.question,.table .item{display:-webkit-flex;display:-ms-flexbox} .more_menu .wrapper ul li:first-child a{padding-top:10px} .more_menu .wrapper ul li:last-child a{padding-bottom:10px} -.messages{position:fixed;right:0;bottom:0;width:320px;z-index:2000;overflow:hidden;font-size:14px;font-weight:700;padding:5px} +.messages{position:fixed;width:320px;z-index:2000;overflow:hidden;font-size:14px;font-weight:700;padding:5px} .messages .message{overflow:hidden;transition:all .6s cubic-bezier(.9,0,.1,1);width:100%;position:relative;max-height:0;font-size:1.1em;font-weight:400;-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:100% 50%;transform-origin:100% 50%;background:#ac0000;margin-bottom:4px;border-radius:3px} -.mask,.messages .close{position:absolute;top:0;right:0} .messages .message .inner{padding:15px 30px 15px 20px;background:#FFF;margin-bottom:4px;border-radius:3px} .messages .message.sticky{background-color:#ac0000} .messages .message.show{max-height:100px;-webkit-transform:scale(1);transform:scale(1)} .messages .message.hide{max-height:0;padding:0 20px;margin:0;-webkit-transform:scale(0);transform:scale(0)} -.messages .close{padding:10px 8px;color:#FFF} -.question{position:fixed;z-index:20000;color:#FFF;padding:20px;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center} +.messages .close{position:absolute;padding:10px 8px;top:0;right:0;color:#FFF} +.question{position:fixed;z-index:20000;color:#FFF;padding:20px;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center} .question.show{opacity:1} .question .inner{width:100%;max-width:500px} .question h3{display:block;margin-bottom:20px;font-size:1.4em;font-weight:lighter} .question .hint{margin:-20px 0 20px} .question a{border-color:#FFF;color:#FFF;transition:none} .question a:hover{background:#FFF;color:#ac0000} -.mask{background:rgba(0,0,0,.8);z-index:1000;text-align:center;bottom:0;left:0;opacity:0;transition:opacity 500ms} +.mask{background:rgba(0,0,0,.8);z-index:1000;text-align:center;position:absolute;top:0;left:0;opacity:0;transition:opacity 500ms} .mask .message,.mask .spinner{position:absolute;top:50%;left:50%} .mask .message{color:#FFF;text-align:center;width:320px;margin:-49px 0 0 -160px;font-size:16px} .mask .message h1{font-size:1.5em} -.mask .spinner{width:22px;height:22px;display:block;background:#fff;margin-top:-11px;margin-left:-11px;outline:transparent solid 1px;-webkit-animation:rotating 2.5s cubic-bezier(.9,0,.1,1) infinite normal;animation:rotating 2.5s cubic-bezier(.9,0,.1,1) infinite normal;-webkit-transform:scale(0);transform:scale(0)} +.mask .spinner{width:22px;height:22px;display:block;background:#fff;margin-top:-11px;margin-left:-11px;outline:transparent solid 1px;-webkit-animation:rotating 2.5s cubic-bezier(.9,0,.1,1)infinite normal;animation:rotating 2.5s cubic-bezier(.9,0,.1,1)infinite normal;-webkit-transform:scale(0);transform:scale(0)} .mask.with_message .spinner{margin-top:-88px} .mask.show{pointer-events:auto;opacity:1} .mask.show .spinner{-webkit-transform:scale(1);transform:scale(1)} .mask.hide{opacity:0} .mask.hide .spinner{-webkit-transform:scale(0);transform:scale(0)} -@-webkit-keyframes rotating{0%{-webkit-transform:rotate(0) scale(1.6);transform:rotate(0) scale(1.6);border-radius:1px} -48%{-webkit-transform:rotate(360deg) scale(1);transform:rotate(360deg) scale(1);border-radius:50%} -100%{-webkit-transform:rotate(720deg) scale(1.6);transform:rotate(720deg) scale(1.6);border-radius:1px} +@-webkit-keyframes rotating{0%{-webkit-transform:rotate(0)scale(1.6);transform:rotate(0)scale(1.6);border-radius:1px} +48%{-webkit-transform:rotate(360deg)scale(1);transform:rotate(360deg)scale(1);border-radius:50%} +100%{-webkit-transform:rotate(720deg)scale(1.6);transform:rotate(720deg)scale(1.6);border-radius:1px} } -@keyframes rotating{0%{-webkit-transform:rotate(0) scale(1.6);transform:rotate(0) scale(1.6);border-radius:1px} -48%{-webkit-transform:rotate(360deg) scale(1);transform:rotate(360deg) scale(1);border-radius:50%} -100%{-webkit-transform:rotate(720deg) scale(1.6);transform:rotate(720deg) scale(1.6);border-radius:1px} +@keyframes rotating{0%{-webkit-transform:rotate(0)scale(1.6);transform:rotate(0)scale(1.6);border-radius:1px} +48%{-webkit-transform:rotate(360deg)scale(1);transform:rotate(360deg)scale(1);border-radius:50%} +100%{-webkit-transform:rotate(720deg)scale(1.6);transform:rotate(720deg)scale(1.6);border-radius:1px} } .table .head{font-weight:700} -.table .item{display:flex;border-bottom:1px solid rgba(0,0,0,.2)} +.table .item{display:-webkit-flex;display:-ms-flexbox;display:flex;border-bottom:1px solid rgba(0,0,0,.2)} .table .item:last-child{border-bottom:none} .table .item span{padding:1px 2px} .table .item span:first-child{padding-left:0} @@ -679,7 +649,7 @@ input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#F @media (max-width:480px){.page.settings fieldset h2{display:block} .page.settings fieldset h2 .hint{margin:0;display:block} } -.page.settings fieldset h2 .hint a{font-weight:400;color:#ac0000;text-decoration:underline} +.page.settings fieldset h2 .hint a{font-weight:400;color:#ac0000;text-decoration:underline;display:inline} .page.settings fieldset .ctrlHolder{padding:6.67px 20px;border-bottom:1px solid #eaeaea;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row nowrap;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-align-items:center;-ms-flex-align:center;align-items:center} .page.settings fieldset .ctrlHolder:last-child{border-bottom:0} .page.settings fieldset .ctrlHolder label{display:inline-block;min-width:150px} @@ -690,8 +660,8 @@ input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#F .page.settings fieldset .ctrlHolder input[type=checkbox]{margin-right:20px;-webkit-flex:none;-ms-flex:none;flex:none} } .page.settings fieldset .ctrlHolder .select_wrapper{position:relative;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center} -.page.settings fieldset .ctrlHolder .select_wrapper select{cursor:pointer;-moz-appearance:none} -.page.settings fieldset .ctrlHolder .select_wrapper:before{pointer-events:none;position:absolute;right:10px;height:100%;line-height:0;margin-top:-3px} +.page.settings fieldset .ctrlHolder .select_wrapper select{cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;width:100%;min-width:200px;border-radius:0} +.page.settings fieldset .ctrlHolder .select_wrapper:before{vertical-align:top;pointer-events:none;position:absolute;top:0;line-height:2em;right:10px;height:100%} .page.settings fieldset .ctrlHolder .formHint{opacity:.8;margin-left:20px} .page.settings fieldset .ctrlHolder .formHint a{font-weight:400;color:#ac0000;text-decoration:underline} @media (max-width:480px){.page.settings fieldset .ctrlHolder .select_wrapper{width:100%} @@ -702,15 +672,15 @@ input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#F .page.settings fieldset.disabled .ctrlHolder{display:none} .page.settings fieldset.disabled>.ctrlHolder:first-child{display:-webkit-flex;display:-ms-flexbox;display:flex} .page.settings fieldset.enabler{display:block} -.page.settings fieldset.enabler>:first-child{position:absolute;right:0;border:0;padding:0;height:46px} +.page.settings fieldset.enabler>:first-child{position:absolute;right:0;border:0;padding:0} .page.settings fieldset.enabler>:first-child~h2{margin-right:86px} @media (max-width:480px){.page.settings fieldset.enabler>:first-child~h2{margin-right:0} } .page.settings fieldset .ctrlHolder.advanced,.page.settings fieldset.advanced{display:none;color:#ac0000} .page.settings.show_advanced fieldset.advanced,.page.settings.show_advanced fieldset:not(.disabled)>.ctrlHolder.advanced{display:-webkit-flex;display:-ms-flexbox;display:flex} -.page.settings .switch{display:inline-block;background:#ac0000;border:1px solid #ac0000;box-shadow:inset 0 0 0 1px #FFF;height:22px;width:66px;min-width:0!important;padding:2px;transition:all 250ms;cursor:pointer;border-radius:3px} +.page.settings .switch{display:inline-block;background:#ac0000;height:20px;width:50px;min-width:0!important;transition:all 250ms;cursor:pointer;border-radius:20px} .page.settings .switch input{display:none} -.page.settings .switch .toggle{background:#FFF;height:100%;width:40%;transition:-webkit-transform 250ms;transition:transform 250ms;-webkit-transform:translateX(150%);transform:translateX(150%);border-radius:2px} +.page.settings .switch .toggle{background:#FFF;margin:1px;height:18px;width:18px;transition:-webkit-transform 250ms;transition:transform 250ms;-webkit-transform:translateX(30px);transform:translateX(30px);border-radius:20px} .page.settings fieldset.enabler.disabled .switch,.page.settings:not(.show_advanced) .advanced_toggle .switch{background:#eaeaea;border-color:#eaeaea} .page.settings fieldset.enabler.disabled .switch .toggle,.page.settings:not(.show_advanced) .advanced_toggle .switch .toggle{-webkit-transform:translateX(0);transform:translateX(0)} .page.settings fieldset.enabler.disabled .switch:hover,.page.settings:not(.show_advanced) .advanced_toggle .switch:hover{background:#b7b7b7;border-color:#b7b7b7} @@ -758,7 +728,7 @@ input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#F @media (max-width:480px){.page.settings .directory{width:100%} } .page.settings .directory input{width:100%} -.page.settings .directory_list{z-index:2;position:absolute;width:450px;margin:28px 0 20px;background:#ac0000;box-shadow:0 0 15px 2px rgba(0,0,0,.15);border-radius:3px 3px 0 0} +.page.settings .directory_list{z-index:2;position:absolute;width:450px;margin:28px 0 20px;background:#ac0000;border-radius:3px 3px 0 0} .page.settings .directory_list .pointer{border-right:6px solid transparent;border-left:6px solid transparent;border-bottom:6px solid #ac0000;display:block;position:absolute;width:0;margin:-6px 0 0 100px} .page.settings .directory_list .wrapper{background:#FFF;border-radius:3px 3px 0 0;margin-top:5px} .page.settings .directory_list ul{width:92%;height:300px;overflow:auto;margin:0 20px} @@ -775,4 +745,8 @@ input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#F .page.settings .directory_list .actions:last-child>span{padding:0 5px;text-shadow:none} .page.settings .directory_list .actions:last-child>.clear{left:20px;position:absolute;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);margin:0} .page.settings .directory_list .actions:last-child>.cancel{color:rgba(0,0,0,.4)} -.page.settings .directory_list .actions:last-child>.save{margin-right:0} \ No newline at end of file +.page.settings .directory_list .actions:last-child>.save{margin-right:0} +.page.settings .choice .select_wrapper{margin-left:20px;width:120px;min-width:120px} +@media (max-width:480px){.page.settings .choice .select_wrapper{margin:10px 0 0} +} +.page.settings .choice .select_wrapper select{min-width:0!important} \ No newline at end of file diff --git a/couchpotato/static/style/main.scss b/couchpotato/static/style/main.scss index 3895af2..50f636e 100644 --- a/couchpotato/static/style/main.scss +++ b/couchpotato/static/style/main.scss @@ -413,7 +413,6 @@ input, textarea, select { @include media-phablet { font-size: 18px; - //line-height: $header_width_mobile; padding: $padding/2; } } diff --git a/couchpotato/static/style/settings.scss b/couchpotato/static/style/settings.scss index e91c6c0..44249ec 100644 --- a/couchpotato/static/style/settings.scss +++ b/couchpotato/static/style/settings.scss @@ -251,7 +251,7 @@ } .switch { - $switch_height: 24px; + $switch_height: 20px; $switch_active: $primary_color; display: inline-block; background: $switch_active; From 4e57311251a60bd393a24deae7b9ccf8d924dc22 Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 10 Aug 2015 13:16:25 +0200 Subject: [PATCH 233/301] Show rating on charts also --- couchpotato/core/media/movie/_base/static/movie.js | 4 ++-- couchpotato/static/scripts/combined.plugins.min.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/couchpotato/core/media/movie/_base/static/movie.js b/couchpotato/core/media/movie/_base/static/movie.js index d144ba5..9c4a07e 100644 --- a/couchpotato/core/media/movie/_base/static/movie.js +++ b/couchpotato/core/media/movie/_base/static/movie.js @@ -272,7 +272,7 @@ var Movie = new Class({ } var rating, stars; - if(self.data.status == 'suggested' && self.data.info && self.data.info.rating && self.data.info.rating.imdb){ + if(['suggested','chart'].indexOf(self.data.status) > -1 && self.data.info && self.data.info.rating && self.data.info.rating.imdb){ rating = self.data.info.rating.imdb; stars = []; @@ -344,7 +344,7 @@ var Movie = new Class({ self.quality = new Element('div.quality'), self.rating = rating ? new Element('div.rating[title='+rating[0]+']').adopt( stars, - new Element('span.votes[text=('+rating[1]+')][title=Votes]') + new Element('span.votes[text=('+rating.join(' / ')+')][title=Votes]') ) : null ) ) diff --git a/couchpotato/static/scripts/combined.plugins.min.js b/couchpotato/static/scripts/combined.plugins.min.js index a3f35b9..d224463 100644 --- a/couchpotato/static/scripts/combined.plugins.min.js +++ b/couchpotato/static/scripts/combined.plugins.min.js @@ -1962,7 +1962,7 @@ var Movie = new Class({ } } var rating, stars; - if (self.data.status == "suggested" && self.data.info && self.data.info.rating && self.data.info.rating.imdb) { + if ([ "suggested", "chart" ].indexOf(self.data.status) > -1 && self.data.info && self.data.info.rating && self.data.info.rating.imdb) { rating = self.data.info.rating.imdb; stars = []; var half_rating = rating[0] / 2; @@ -2007,7 +2007,7 @@ var Movie = new Class({ })), self.eta = eta_date && now + 8035200 > eta ? new Element("div.eta", { text: eta_date, title: "ETA" - }) : null, self.quality = new Element("div.quality"), self.rating = rating ? new Element("div.rating[title=" + rating[0] + "]").adopt(stars, new Element("span.votes[text=(" + rating[1] + ")][title=Votes]")) : null))); + }) : null, self.quality = new Element("div.quality"), self.rating = rating ? new Element("div.rating[title=" + rating[0] + "]").adopt(stars, new Element("span.votes[text=(" + rating.join(" / ") + ")][title=Votes]")) : null))); if (!self.thumbnail) self.el.addClass("no_thumbnail"); if (self.profile.data) self.profile.getTypes().each(function(type) { var q = self.addQuality(type.get("quality"), type.get("3d")); From 24ab4437df34e05c937dc5540bf4259f372a7eff Mon Sep 17 00:00:00 2001 From: The Gitter Badger Date: Mon, 10 Aug 2015 11:26:02 +0000 Subject: [PATCH 234/301] Added Gitter badge --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 1ccf689..31aadb7 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ CouchPotato ===== +[![Join the chat at https://gitter.im/RuudBurger/CouchPotatoServer](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/RuudBurger/CouchPotatoServer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + CouchPotato (CP) is an automatic NZB and torrent downloader. You can keep a "movies I want"-list and it will search for NZBs/torrents of these movies every X hours. Once a movie is found, it will send it to SABnzbd or download the torrent to a specified directory. From 3f3cd07fcd9461da26ba9e5b711dd6a81944e251 Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 10 Aug 2015 13:51:52 +0200 Subject: [PATCH 235/301] Whitespace fixes --- couchpotato/static/style/main.scss | 1 - couchpotato/static/style/settings.scss | 7 ++++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/couchpotato/static/style/main.scss b/couchpotato/static/style/main.scss index 50f636e..ddb6928 100644 --- a/couchpotato/static/style/main.scss +++ b/couchpotato/static/style/main.scss @@ -27,7 +27,6 @@ body { } a { - display: inline-block; position: relative; overflow: hidden; text-decoration: none; diff --git a/couchpotato/static/style/settings.scss b/couchpotato/static/style/settings.scss index 44249ec..f314a1c 100644 --- a/couchpotato/static/style/settings.scss +++ b/couchpotato/static/style/settings.scss @@ -74,7 +74,7 @@ h2 { display: flex; align-items: baseline; - padding: 0 0 $padding/2 $padding; + padding: 0 0 0 $padding; @include media-phablet { display: block; @@ -234,6 +234,11 @@ } } } + + > :nth-child(3){ + margin-top: $padding; + } + } } From 3551db8c0b257094335abf91dfaa3c1bbcb06048 Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 10 Aug 2015 13:53:58 +0200 Subject: [PATCH 236/301] Mark as done --- .../core/media/movie/_base/static/movie.actions.js | 125 +++++++-------------- .../core/media/movie/_base/static/movie.scss | 1 - couchpotato/static/scripts/combined.base.min.js | 2 +- couchpotato/static/scripts/combined.plugins.min.js | 89 +++++---------- couchpotato/static/scripts/page/home.js | 2 +- couchpotato/static/style/combined.min.css | 7 +- 6 files changed, 79 insertions(+), 147 deletions(-) diff --git a/couchpotato/core/media/movie/_base/static/movie.actions.js b/couchpotato/core/media/movie/_base/static/movie.actions.js index 9131ea2..c7a68dc 100644 --- a/couchpotato/core/media/movie/_base/static/movie.actions.js +++ b/couchpotato/core/media/movie/_base/static/movie.actions.js @@ -322,56 +322,6 @@ MA.Release = new Class({ }, - showHelper: function(e){ - var self = this; - if(e) - (e).preventDefault(); - - var has_available = false, - has_snatched = false; - - if(self.movie.data.releases) - self.movie.data.releases.each(function(release){ - if(has_available && has_snatched) return; - - if(['snatched', 'downloaded', 'seeding', 'done'].contains(release.status)) - has_snatched = true; - - if(['available'].contains(release.status)) - has_available = true; - - }); - - if(has_available || has_snatched){ - - self.trynext_container = new Element('div.buttons.trynext').inject(self.movie.info_container); - - self.trynext_container.adopt( - has_available ? [new Element('a.icon-redo', { - 'text': has_snatched ? 'Download another release' : 'Download the best release', - 'events': { - 'click': self.tryNextRelease.bind(self) - } - }), - new Element('a.icon-download', { - 'text': 'pick one yourself', - 'events': { - 'click': function(){ - self.movie.quality.fireEvent('click'); - } - } - })] : null, - new Element('a.icon-ok', { - 'text': 'mark this movie done', - 'events': { - 'click': self.markMovieDone.bind(self) - } - }) - ); - } - - }, - get: function(release, type){ return (release.info && release.info[type] !== undefined) ? release.info[type] : 'n/a'; }, @@ -413,39 +363,6 @@ MA.Release = new Class({ } }); - }, - - markMovieDone: function(){ - var self = this; - - Api.request('media.delete', { - 'data': { - 'id': self.movie.get('_id'), - 'delete_from': 'wanted' - }, - 'onComplete': function(){ - var movie = $(self.movie); - movie.set('tween', { - 'duration': 300, - 'onComplete': function(){ - self.movie.destroy(); - } - }); - movie.tween('height', 0); - } - }); - - }, - - tryNextRelease: function(){ - var self = this; - - Api.request('movie.searcher.try_next', { - 'data': { - 'media_id': self.movie.get('_id') - } - }); - } }); @@ -1013,3 +930,45 @@ MA.Files = new Class({ } }); + + +MA.MarkAsDone = new Class({ + + Extends: MovieAction, + + create: function(){ + var self = this; + + self.button = self.createButton(); + self.detail_button = self.createButton(); + + }, + + createButton: function(){ + var self = this; + + return new Element('a.mark_as_done', { + 'text': 'Mark as done', + 'title': 'Remove from available list and move to managed movies', + 'events': { + 'click': self.markMovieDone.bind(self) + } + }); + }, + + markMovieDone: function(){ + var self = this; + + Api.request('media.delete', { + 'data': { + 'id': self.movie.get('_id'), + 'delete_from': 'wanted' + }, + 'onComplete': function(){ + self.movie.destroy(); + } + }); + + } + +}); diff --git a/couchpotato/core/media/movie/_base/static/movie.scss b/couchpotato/core/media/movie/_base/static/movie.scss index 0fe0970..6b6ffcd 100644 --- a/couchpotato/core/media/movie/_base/static/movie.scss +++ b/couchpotato/core/media/movie/_base/static/movie.scss @@ -347,7 +347,6 @@ $mass_edit_height: 44px; @include media-phablet { max-width: 50%; - width: 50%; border-width: 0 $padding/5; } diff --git a/couchpotato/static/scripts/combined.base.min.js b/couchpotato/static/scripts/combined.base.min.js index 2428285..f95282b 100644 --- a/couchpotato/static/scripts/combined.base.min.js +++ b/couchpotato/static/scripts/combined.base.min.js @@ -983,7 +983,7 @@ Page.Home = new Class({ identifier: "snatched", load_more: false, view: "list", - actions: [ MA.IMDB, MA.Release, MA.Trailer, MA.Refresh, MA.Readd, MA.Delete, MA.Category, MA.Profile ], + actions: [ MA.MarkAsDone, MA.IMDB, MA.Release, MA.Trailer, MA.Refresh, MA.Readd, MA.Delete, MA.Category, MA.Profile ], title: "Snatched & Available", description: "These movies have been snatched or have finished downloading", on_empty_element: new Element("div").adopt(new Element("h2", { diff --git a/couchpotato/static/scripts/combined.plugins.min.js b/couchpotato/static/scripts/combined.plugins.min.js index d224463..71744f7 100644 --- a/couchpotato/static/scripts/combined.plugins.min.js +++ b/couchpotato/static/scripts/combined.plugins.min.js @@ -1242,37 +1242,6 @@ MA.Release = new Class({ } return self.options_container; }, - showHelper: function(e) { - var self = this; - if (e) e.preventDefault(); - var has_available = false, has_snatched = false; - if (self.movie.data.releases) self.movie.data.releases.each(function(release) { - if (has_available && has_snatched) return; - if ([ "snatched", "downloaded", "seeding", "done" ].contains(release.status)) has_snatched = true; - if ([ "available" ].contains(release.status)) has_available = true; - }); - if (has_available || has_snatched) { - self.trynext_container = new Element("div.buttons.trynext").inject(self.movie.info_container); - self.trynext_container.adopt(has_available ? [ new Element("a.icon-redo", { - text: has_snatched ? "Download another release" : "Download the best release", - events: { - click: self.tryNextRelease.bind(self) - } - }), new Element("a.icon-download", { - text: "pick one yourself", - events: { - click: function() { - self.movie.quality.fireEvent("click"); - } - } - }) ] : null, new Element("a.icon-ok", { - text: "mark this movie done", - events: { - click: self.markMovieDone.bind(self) - } - })); - } - }, get: function(release, type) { return release.info && release.info[type] !== undefined ? release.info[type] : "n/a"; }, @@ -1299,33 +1268,6 @@ MA.Release = new Class({ id: release._id } }); - }, - markMovieDone: function() { - var self = this; - Api.request("media.delete", { - data: { - id: self.movie.get("_id"), - delete_from: "wanted" - }, - onComplete: function() { - var movie = $(self.movie); - movie.set("tween", { - duration: 300, - onComplete: function() { - self.movie.destroy(); - } - }); - movie.tween("height", 0); - } - }); - }, - tryNextRelease: function() { - var self = this; - Api.request("movie.searcher.try_next", { - data: { - media_id: self.movie.get("_id") - } - }); } }); @@ -1765,6 +1707,37 @@ MA.Files = new Class({ } }); +MA.MarkAsDone = new Class({ + Extends: MovieAction, + create: function() { + var self = this; + self.button = self.createButton(); + self.detail_button = self.createButton(); + }, + createButton: function() { + var self = this; + return new Element("a.mark_as_done", { + text: "Mark as done", + title: "Remove from available list and move to managed movies", + events: { + click: self.markMovieDone.bind(self) + } + }); + }, + markMovieDone: function() { + var self = this; + Api.request("media.delete", { + data: { + id: self.movie.get("_id"), + delete_from: "wanted" + }, + onComplete: function() { + self.movie.destroy(); + } + }); + } +}); + var Movie = new Class({ Extends: BlockBase, actions: null, diff --git a/couchpotato/static/scripts/page/home.js b/couchpotato/static/scripts/page/home.js index 9090300..b16b8c4 100644 --- a/couchpotato/static/scripts/page/home.js +++ b/couchpotato/static/scripts/page/home.js @@ -41,7 +41,7 @@ Page.Home = new Class({ 'identifier': 'snatched', 'load_more': false, 'view': 'list', - 'actions': [MA.IMDB, MA.Release, MA.Trailer, MA.Refresh, MA.Readd, MA.Delete, MA.Category, MA.Profile], + 'actions': [MA.MarkAsDone, MA.IMDB, MA.Release, MA.Trailer, MA.Refresh, MA.Readd, MA.Delete, MA.Category, MA.Profile], 'title': 'Snatched & Available', 'description': 'These movies have been snatched or have finished downloading', 'on_empty_element': new Element('div').adopt( diff --git a/couchpotato/static/style/combined.min.css b/couchpotato/static/style/combined.min.css index 0421144..a0fbf5f 100644 --- a/couchpotato/static/style/combined.min.css +++ b/couchpotato/static/style/combined.min.css @@ -124,7 +124,7 @@ .thumb_list .movie{max-width:33.333%;border-width:0 5px} } @media (max-width:480px){.thumb_list{padding:0 3.33px} -.thumb_list .movie{max-width:50%;width:50%;border-width:0 4px} +.thumb_list .movie{max-width:50%;border-width:0 4px} } .thumb_list .movie input[type=checkbox]{top:10px;left:10px} .thumb_list .movie .poster{position:relative;border-radius:3px;background:center no-repeat #eaeaea;background-size:cover;overflow:hidden;width:100%;padding-bottom:150%} @@ -496,7 +496,7 @@ *{margin:0;padding:0;box-sizing:border-box;text-rendering:optimizeSpeed;-webkit-backface-visibility:hidden;backface-visibility:hidden} body,html{font-size:14px;line-height:1.5;font-family:OpenSans,"Helvetica Neue",Helvetica,Arial,Geneva,sans-serif;font-weight:300;height:100%;margin:0;padding:0;background:#111;overflow:hidden} body{display:flex;-webkit-flex-flow:row nowrap;-ms-flex-flow:row nowrap;flex-flow:row nowrap} -a{display:inline-block;position:relative;overflow:hidden;text-decoration:none;cursor:pointer;-webkit-tap-highlight-color:transparent} +a{position:relative;overflow:hidden;text-decoration:none;cursor:pointer;-webkit-tap-highlight-color:transparent} input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#FFF;border:1px solid #b7b7b7} .button{color:#ac0000;font-weight:300;padding:5px;cursor:pointer;border:1px solid #ac0000;border-radius:3px;margin:0 5px;transition:all 150ms} .button:hover{background:#ac0000;color:#FFF} @@ -642,7 +642,7 @@ input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#F .page.settings .tabs li.active a{color:#000} .page.settings form.containers{margin:0 20px 0 0;-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto} .page.settings fieldset{border:0;margin-bottom:20px;position:relative} -.page.settings fieldset h2{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:baseline;-ms-flex-align:baseline;align-items:baseline;padding:0 0 10px 20px} +.page.settings fieldset h2{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:baseline;-ms-flex-align:baseline;align-items:baseline;padding:0 0 0 20px} .page.settings fieldset h2 .icon{margin-right:10px} .page.settings fieldset h2 .icon img{vertical-align:middle;position:relative;top:-1px} .page.settings fieldset h2 .hint{margin-left:10px;font-size:1rem} @@ -676,6 +676,7 @@ input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#F .page.settings fieldset.enabler>:first-child~h2{margin-right:86px} @media (max-width:480px){.page.settings fieldset.enabler>:first-child~h2{margin-right:0} } +.page.settings fieldset.enabler>:nth-child(3){margin-top:20px} .page.settings fieldset .ctrlHolder.advanced,.page.settings fieldset.advanced{display:none;color:#ac0000} .page.settings.show_advanced fieldset.advanced,.page.settings.show_advanced fieldset:not(.disabled)>.ctrlHolder.advanced{display:-webkit-flex;display:-ms-flexbox;display:flex} .page.settings .switch{display:inline-block;background:#ac0000;height:20px;width:50px;min-width:0!important;transition:all 250ms;cursor:pointer;border-radius:20px} From 58a90c0ffede87b1cf2ce5a862a457103ee91a60 Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 10 Aug 2015 14:04:45 +0200 Subject: [PATCH 237/301] Only get proxy settings when enabled --- couchpotato/core/plugins/base.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/couchpotato/core/plugins/base.py b/couchpotato/core/plugins/base.py index d82473d..8b76e11 100644 --- a/couchpotato/core/plugins/base.py +++ b/couchpotato/core/plugins/base.py @@ -201,12 +201,13 @@ class Plugin(object): headers['Cache-Control'] = headers.get('Cache-Control', 'max-age=0') use_proxy = Env.setting('use_proxy') - proxy_server = Env.setting('proxy_server') - proxy_username = Env.setting('proxy_username') - proxy_password = Env.setting('proxy_password') proxy_url = None if use_proxy: + proxy_server = Env.setting('proxy_server') + proxy_username = Env.setting('proxy_username') + proxy_password = Env.setting('proxy_password') + if proxy_server: loc = "{0}:{1}@{2}".format(proxy_username, proxy_password, proxy_server) if proxy_username else proxy_server proxy_url = { From 8bce15e555221422897a9c480539223c1ea9446d Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 10 Aug 2015 14:12:00 +0200 Subject: [PATCH 238/301] Navigation ripple contain --- couchpotato/static/style/combined.min.css | 2 +- couchpotato/static/style/main.scss | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/couchpotato/static/style/combined.min.css b/couchpotato/static/style/combined.min.css index a0fbf5f..056d8a4 100644 --- a/couchpotato/static/style/combined.min.css +++ b/couchpotato/static/style/combined.min.css @@ -567,7 +567,7 @@ input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#F .page .navigation{height:44px;left:64px} } .page .navigation ul{-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto;list-style:none} -.page .navigation ul li a{font-size:24px;line-height:80px;padding:0 20px;color:rgba(0,0,0,.5)} +.page .navigation ul li a{display:inline-block;font-size:24px;line-height:80px;padding:0 20px;color:rgba(0,0,0,.5)} @media (max-width:480px){.page .navigation ul li a{font-size:18px;line-height:44px;padding:10px} } .page .navigation ul .active a{color:#000} diff --git a/couchpotato/static/style/main.scss b/couchpotato/static/style/main.scss index ddb6928..4de10db 100644 --- a/couchpotato/static/style/main.scss +++ b/couchpotato/static/style/main.scss @@ -441,6 +441,7 @@ input, textarea, select { display: inline-block; a { + display: inline-block; font-size: 24px; line-height: $header_height; padding: 0 $padding; From aef2f3f99b64c568196a413345633d98a05ddbc8 Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 10 Aug 2015 15:08:11 +0200 Subject: [PATCH 239/301] IE Fixes --- couchpotato/core/media/movie/_base/static/movie.scss | 3 +-- couchpotato/static/style/_mixins.scss | 1 + couchpotato/static/style/combined.min.css | 14 +++++++------- couchpotato/static/style/main.scss | 8 +++++++- couchpotato/static/style/settings.scss | 2 +- 5 files changed, 17 insertions(+), 11 deletions(-) diff --git a/couchpotato/core/media/movie/_base/static/movie.scss b/couchpotato/core/media/movie/_base/static/movie.scss index 6b6ffcd..af1d018 100644 --- a/couchpotato/core/media/movie/_base/static/movie.scss +++ b/couchpotato/core/media/movie/_base/static/movie.scss @@ -297,8 +297,6 @@ $mass_edit_height: 44px; .thumb_list { - font-size: 12px; - > div:last-child { padding: 0 ($padding/2)+2px; @include media-phablet { @@ -367,6 +365,7 @@ $mass_edit_height: 44px; .data { clear: both; + font-size: .85em; .info { diff --git a/couchpotato/static/style/_mixins.scss b/couchpotato/static/style/_mixins.scss index e53b1f0..25ac79b 100644 --- a/couchpotato/static/style/_mixins.scss +++ b/couchpotato/static/style/_mixins.scss @@ -5,6 +5,7 @@ $menu_color: #111; $theme_off: #eaeaea; $text_color: #000; +$font_size: 14px; $header_height: 80px; $header_width: 132px; $header_width_mobile: 44px; diff --git a/couchpotato/static/style/combined.min.css b/couchpotato/static/style/combined.min.css index 056d8a4..e31e658 100644 --- a/couchpotato/static/style/combined.min.css +++ b/couchpotato/static/style/combined.min.css @@ -110,7 +110,6 @@ .list_list .movie:hover .actions{display:none} } .list_list.with_navigation .movie.checked .data .info .title span,.list_list.with_navigation .movie:hover .data .info .title span{margin-left:20px} -.thumb_list{font-size:12px} .thumb_list>div:last-child{padding:0 12px} @media (max-width:480px){.thumb_list>div:last-child{padding:0 3.33px} } @@ -128,7 +127,7 @@ } .thumb_list .movie input[type=checkbox]{top:10px;left:10px} .thumb_list .movie .poster{position:relative;border-radius:3px;background:center no-repeat #eaeaea;background-size:cover;overflow:hidden;width:100%;padding-bottom:150%} -.thumb_list .movie .data{clear:both} +.thumb_list .movie .data{clear:both;font-size:.85em} .thumb_list .movie .data .info .title{display:-webkit-flex;display:-ms-flexbox;display:flex;padding:3px 0} .thumb_list .movie .data .info .title span{-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} .thumb_list .movie .data .info .title .year{display:inline-block;margin-left:5px;opacity:.5} @@ -510,17 +509,18 @@ input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#F .header .navigation .logo{background:#ac0000;display:block;text-align:center;position:relative;overflow:hidden;font-family:Lobster,serif;color:#FFF;font-size:38px;line-height:80px;height:80px} .header .navigation .logo span{position:absolute;display:block;height:100%;width:100%;text-align:center;left:0} .header .navigation .logo span:nth-child(even){-webkit-transform:translateX(100%);transform:translateX(100%)} -.header .navigation ul{padding:0;margin:0} @media (max-width:480px){.header .navigation .logo{font-size:28px;line-height:44px;height:44px} .header .navigation .logo:after{content:'CP'} .header .navigation .logo span{display:none} -.header .navigation ul li{line-height:0} } +.header .navigation ul{padding:0;margin:0} +.header .navigation ul li{display:block} .header .navigation ul li a{padding:10px 20px;display:block;position:relative} .header .navigation ul li a:before{position:absolute;width:100%;display:none;text-align:center;font-size:18px;text-indent:0} .header .navigation ul li a.icon-home:before{font-size:24px} .header .menu,.header .notification_menu,.header .search_form{position:absolute;z-index:21;bottom:6.67px;width:44px;height:44px} -@media (max-width:480px){.header .navigation ul li a{line-height:24px;height:44px;padding:10px 0;text-align:center} +@media (max-width:480px){.header .navigation ul li{line-height:0} +.header .navigation ul li a{line-height:24px;height:44px;padding:10px 0;text-align:center} .header .navigation ul li a span{display:none} .header .navigation ul li a:before{display:block} .header .menu,.header .notification_menu,.header .search_form{bottom:0} @@ -562,7 +562,7 @@ input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#F .page h2{font-size:24px;padding:20px} .page .navigation{z-index:2;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center;position:fixed;top:0;height:80px;left:152px;right:20px;background:#FFF;border-radius:3px 0 0} .more_menu,.more_menu .button:before{position:relative} -.more_menu .button,.page .navigation ul li{display:inline-block} +.more_menu .button,.more_menu a,.page .navigation ul li{display:inline-block} @media (max-width:480px){.page h2{font-size:18px;padding:10px} .page .navigation{height:44px;left:64px} } @@ -637,7 +637,7 @@ input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#F .page.settings .tab_content{display:none} .page.settings .tab_content.active{display:block} .page.settings .tabs{margin:0 20px 20px;list-style:none;font-size:24px} -.page.settings .tabs ul{list-style:none;font-size:initial} +.page.settings .tabs ul{list-style:none;font-size:14px} .page.settings .tabs li a{color:rgba(0,0,0,.5)} .page.settings .tabs li.active a{color:#000} .page.settings form.containers{margin:0 20px 0 0;-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto} diff --git a/couchpotato/static/style/main.scss b/couchpotato/static/style/main.scss index 4de10db..cf44c02 100644 --- a/couchpotato/static/style/main.scss +++ b/couchpotato/static/style/main.scss @@ -10,7 +10,7 @@ } body, html { - font-size: 14px; + font-size: $font_size; line-height: 1.5; font-family: OpenSans, "Helvetica Neue", Helvetica, Arial, Geneva, sans-serif; font-weight: 300; @@ -149,6 +149,8 @@ input, textarea, select { margin: 0; li { + display: block; + @include media-phablet { line-height: 0; } @@ -485,6 +487,10 @@ input, textarea, select { position: relative; line-height: 1em; + a { + display: inline-block; + } + .button { font-size: 24px; cursor: pointer; diff --git a/couchpotato/static/style/settings.scss b/couchpotato/static/style/settings.scss index f314a1c..fb93d14 100644 --- a/couchpotato/static/style/settings.scss +++ b/couchpotato/static/style/settings.scss @@ -44,7 +44,7 @@ ul { list-style: none; - font-size: initial; + font-size: $font_size; } li { From a93aac9150261a0b11c3dd2eb1e02718f19ae6f0 Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 10 Aug 2015 18:31:05 +0200 Subject: [PATCH 240/301] Limit suggestions --- couchpotato/core/media/movie/_base/static/movie.actions.js | 8 +++++--- couchpotato/core/media/movie/suggestion/main.py | 2 ++ couchpotato/static/scripts/combined.plugins.min.js | 8 +++++--- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/couchpotato/core/media/movie/_base/static/movie.actions.js b/couchpotato/core/media/movie/_base/static/movie.actions.js index c7a68dc..71bbfe6 100644 --- a/couchpotato/core/media/movie/_base/static/movie.actions.js +++ b/couchpotato/core/media/movie/_base/static/movie.actions.js @@ -584,10 +584,12 @@ var SuggestBase = new Class({ refresh: function(json){ var self = this; - self.movie.list.addMovies([json.movie], 1); + if(json && json.movie){ + self.movie.list.addMovies([json.movie], 1); - var last_added = self.movie.list.movies[self.movie.list.movies.length-1]; - $(last_added).inject(self.movie, 'before'); + var last_added = self.movie.list.movies[self.movie.list.movies.length-1]; + $(last_added).inject(self.movie, 'before'); + } self.movie.destroy(); } diff --git a/couchpotato/core/media/movie/suggestion/main.py b/couchpotato/core/media/movie/suggestion/main.py index 5cb68a3..baaf3fc 100755 --- a/couchpotato/core/media/movie/suggestion/main.py +++ b/couchpotato/core/media/movie/suggestion/main.py @@ -85,6 +85,8 @@ class Suggestion(Plugin): new_suggestions = self.updateSuggestionCache(ignore_imdb = imdb, limit = limit, ignored = ignored, seen = seen) + limit = limit if len(new_suggestions) > limit else len(new_suggestions) + # Only return new (last) item media = { 'status': 'suggested', diff --git a/couchpotato/static/scripts/combined.plugins.min.js b/couchpotato/static/scripts/combined.plugins.min.js index 71744f7..99e01d9 100644 --- a/couchpotato/static/scripts/combined.plugins.min.js +++ b/couchpotato/static/scripts/combined.plugins.min.js @@ -1427,9 +1427,11 @@ var SuggestBase = new Class({ }, refresh: function(json) { var self = this; - self.movie.list.addMovies([ json.movie ], 1); - var last_added = self.movie.list.movies[self.movie.list.movies.length - 1]; - $(last_added).inject(self.movie, "before"); + if (json && json.movie) { + self.movie.list.addMovies([ json.movie ], 1); + var last_added = self.movie.list.movies[self.movie.list.movies.length - 1]; + $(last_added).inject(self.movie, "before"); + } self.movie.destroy(); } }); From a4b062d8dc795ddeeddbab112fa3491bf054eb70 Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 10 Aug 2015 18:34:31 +0200 Subject: [PATCH 241/301] Don't return suggestions if there aren't any left --- couchpotato/core/media/movie/suggestion/main.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/couchpotato/core/media/movie/suggestion/main.py b/couchpotato/core/media/movie/suggestion/main.py index baaf3fc..e12b249 100755 --- a/couchpotato/core/media/movie/suggestion/main.py +++ b/couchpotato/core/media/movie/suggestion/main.py @@ -85,7 +85,10 @@ class Suggestion(Plugin): new_suggestions = self.updateSuggestionCache(ignore_imdb = imdb, limit = limit, ignored = ignored, seen = seen) - limit = limit if len(new_suggestions) > limit else len(new_suggestions) + if len(new_suggestions) <= limit: + return { + 'result': False + } # Only return new (last) item media = { From a6946df820858271d9c1c3092a28d01e17e7be0a Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 10 Aug 2015 18:48:47 +0200 Subject: [PATCH 242/301] Rename XBMC labels to Kodi --- couchpotato/core/notifications/xbmc.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/couchpotato/core/notifications/xbmc.py b/couchpotato/core/notifications/xbmc.py index 1eef709..1fbfdb8 100644 --- a/couchpotato/core/notifications/xbmc.py +++ b/couchpotato/core/notifications/xbmc.py @@ -83,7 +83,7 @@ class XBMC(Notification): # v6 (as of XBMC v12(Frodo)) is required to send notifications xbmc_rpc_version = str(result['result']['version']) - log.debug('XBMC JSON-RPC Version: %s ; Notifications by JSON-RPC only supported for v6 [as of XBMC v12(Frodo)]', xbmc_rpc_version) + log.debug('Kodi JSON-RPC Version: %s ; Notifications by JSON-RPC only supported for v6 [as of XBMC v12(Frodo)]', xbmc_rpc_version) # disable JSON use self.use_json_notifications[host] = False @@ -96,7 +96,7 @@ class XBMC(Notification): success = True break elif r.get('error'): - log.error('XBMC error; %s: %s (%s)', (r['id'], r['error']['message'], r['error']['code'])) + log.error('Kodi error; %s: %s (%s)', (r['id'], r['error']['message'], r['error']['code'])) break elif result.get('result') and type(result['result']['version']).__name__ == 'dict': @@ -106,7 +106,7 @@ class XBMC(Notification): xbmc_rpc_version += '.' + str(result['result']['version']['minor']) xbmc_rpc_version += '.' + str(result['result']['version']['patch']) - log.debug('XBMC JSON-RPC Version: %s', xbmc_rpc_version) + log.debug('Kodie JSON-RPC Version: %s', xbmc_rpc_version) # ok, XBMC version is supported self.use_json_notifications[host] = True @@ -119,12 +119,12 @@ class XBMC(Notification): success = True break elif r.get('error'): - log.error('XBMC error; %s: %s (%s)', (r['id'], r['error']['message'], r['error']['code'])) + log.error('Kodi error; %s: %s (%s)', (r['id'], r['error']['message'], r['error']['code'])) break # error getting version info (we do have contact with XBMC though) elif result.get('error'): - log.error('XBMC error; %s: %s (%s)', (result['id'], result['error']['message'], result['error']['code'])) + log.error('Kodi error; %s: %s (%s)', (result['id'], result['error']['message'], result['error']['code'])) log.debug('Use JSON notifications: %s ', self.use_json_notifications) @@ -173,10 +173,10 @@ class XBMC(Notification): return [{'result': 'Error'}] except (MaxRetryError, Timeout, ConnectionError): - log.info2('Couldn\'t send request to XBMC, assuming it\'s turned off') + log.info2('Couldn\'t send request to Kodi, assuming it\'s turned off') return [{'result': 'Error'}] except: - log.error('Failed sending non-JSON-type request to XBMC: %s', traceback.format_exc()) + log.error('Failed sending non-JSON-type request to Kodi: %s', traceback.format_exc()) return [{'result': 'Error'}] def request(self, host, do_requests): @@ -209,10 +209,10 @@ class XBMC(Notification): return response except (MaxRetryError, Timeout, ConnectionError): - log.info2('Couldn\'t send request to XBMC, assuming it\'s turned off') + log.info2('Couldn\'t send request to Kodi, assuming it\'s turned off') return [] except: - log.error('Failed sending request to XBMC: %s', traceback.format_exc()) + log.error('Failed sending request to Kodi: %s', traceback.format_exc()) return [] @@ -223,8 +223,8 @@ config = [{ 'tab': 'notifications', 'list': 'notification_providers', 'name': 'xbmc', - 'label': 'XBMC', - 'description': 'v11 (Eden), v12 (Frodo), v13 (Gotham)', + 'label': 'Kodi', + 'description': 'v14 (Helix), v15 (Isengard)', 'options': [ { 'name': 'enabled', @@ -249,7 +249,7 @@ config = [{ 'default': 0, 'type': 'bool', 'advanced': True, - 'description': 'Only update the first host when movie snatched, useful for synced XBMC', + 'description': 'Only update the first host when movie snatched, useful for synced Kodi', }, { 'name': 'remote_dir_scan', @@ -257,7 +257,7 @@ config = [{ 'default': 0, 'type': 'bool', 'advanced': True, - 'description': ('Only scan new movie folder at remote XBMC servers.', 'Useful if the XBMC path is different from the path CPS uses.'), + 'description': ('Only scan new movie folder at remote Kodi servers.', 'Useful if the Kodi path is different from the path CPS uses.'), }, { 'name': 'force_full_scan', @@ -265,7 +265,7 @@ config = [{ 'default': 0, 'type': 'bool', 'advanced': True, - 'description': ('Do a full scan instead of only the new movie.', 'Useful if the XBMC path is different from the path CPS uses.'), + 'description': ('Do a full scan instead of only the new movie.', 'Useful if the Kodi path is different from the path CPS uses.'), }, { 'name': 'on_snatch', From ec9ce4b172f62e21c516799ef2ef63afeef96d5b Mon Sep 17 00:00:00 2001 From: Ruud Date: Tue, 11 Aug 2015 16:44:37 +0200 Subject: [PATCH 243/301] Clean tmp folder on load --- Gruntfile.js | 16 +++++++++++++++- package.json | 1 + 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/Gruntfile.js b/Gruntfile.js index bb6f230..7861215 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -85,6 +85,11 @@ module.exports = function(grunt){ } }, + // Empties folders to start fresh + clean: { + server: '.tmp' + }, + // Add vendor prefixed styles autoprefixer: { options: { @@ -182,6 +187,15 @@ module.exports = function(grunt){ }); - grunt.registerTask('default', ['sass:server', 'autoprefixer', 'cssmin', 'uglify:vendor', 'uglify:base', 'uglify:plugins', 'concurrent']); + grunt.registerTask('default', [ + 'clean:server', + 'sass:server', + 'autoprefixer', + 'cssmin', + 'uglify:vendor', + 'uglify:base', + 'uglify:plugins', + 'concurrent' + ]); }; diff --git a/package.json b/package.json index 785f5a8..0a7bc02 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "grunt": "~0.4.5", "grunt-autoprefixer": "^3.0.3", "grunt-concurrent": "~2.0.1", + "grunt-contrib-clean": "^0.6.0", "grunt-contrib-cssmin": "~0.13.0", "grunt-contrib-jshint": "~0.11.2", "grunt-contrib-sass": "^0.9.2", From 6ef9e6101e7c47b668278066413cfadfc6955197 Mon Sep 17 00:00:00 2001 From: Ruud Date: Tue, 11 Aug 2015 17:11:40 +0200 Subject: [PATCH 244/301] Kinda styled the manual folder renamer --- .../core/media/movie/_base/static/wanted.js | 96 +++++---- couchpotato/static/scripts/combined.base.min.js | 2 +- couchpotato/static/scripts/combined.plugins.min.js | 11 +- couchpotato/static/scripts/page/settings.js | 2 +- couchpotato/static/style/combined.min.css | 110 +++++----- couchpotato/static/style/settings.scss | 223 ++++++++++----------- 6 files changed, 233 insertions(+), 211 deletions(-) diff --git a/couchpotato/core/media/movie/_base/static/wanted.js b/couchpotato/core/media/movie/_base/static/wanted.js index 1ff06ba..d3b32c8 100644 --- a/couchpotato/core/media/movie/_base/static/wanted.js +++ b/couchpotato/core/media/movie/_base/static/wanted.js @@ -20,13 +20,13 @@ var MoviesWanted = new Class({ } }); - self.scan_folder = new Element('a', { - 'title': 'Scan a folder and rename all movies in it', - 'text': 'Manual folder scan', - 'events':{ - 'click': self.scanFolder.bind(self) - } - }); + self.scan_folder = new Element('a', { + 'title': 'Scan a folder and rename all movies in it', + 'text': 'Manual folder scan', + 'events':{ + 'click': self.scanFolder.bind(self) + } + }); // Wanted movies self.list = new MovieList({ @@ -81,43 +81,55 @@ var MoviesWanted = new Class({ }, - scanFolder: function(e) { - (e).stop(); - - var self = this; - var options = { - 'name': 'Scan_folder' - }; - - if(!self.folder_browser){ - self.folder_browser = new Option.Directory("Scan", "folder", "", options); + scanFolder: function(e) { + (e).stop(); - self.folder_browser.save = function() { - var folder = self.folder_browser.getValue(); - Api.request('renamer.scan', { - 'data': { - 'base_folder': folder + var self = this; + var options = { + 'name': 'Scan_folder' + }; + + if(!self.folder_browser){ + self.folder_browser = new Option.Directory("Scan", "folder", "", options); + + self.folder_browser.save = function() { + var folder = self.folder_browser.getValue(); + Api.request('renamer.scan', { + 'data': { + 'base_folder': folder } }); - }; - - self.folder_browser.inject(self.content, 'top'); - self.folder_browser.fireEvent('injected'); - - // Hide the settings box - self.folder_browser.directory_inlay.hide(); - self.folder_browser.el.removeChild(self.folder_browser.el.firstChild); - - self.folder_browser.showBrowser(); - - // Make adjustments to the browser - self.folder_browser.browser.getElements('.clear.button').hide(); - self.folder_browser.save_button.text = "Select"; - self.folder_browser.browser.style.zIndex=1000; - } - else{ - self.folder_browser.showBrowser(); - } - } + }; + + self.folder_browser.inject(self.content, 'top'); + self.folder_browser.fireEvent('injected'); + + // Hide the settings box + self.folder_browser.directory_inlay.hide(); + self.folder_browser.el.removeChild(self.folder_browser.el.firstChild); + + self.folder_browser.showBrowser(); + + // Make adjustments to the browser + self.folder_browser.browser.getElements('.clear.button').hide(); + self.folder_browser.save_button.text = "Select"; + self.folder_browser.browser.setStyles({ + 'z-index': 1000, + 'right': 20, + 'top': 0, + 'margin': 0 + }); + + self.folder_browser.pointer.setStyles({ + 'right': 20 + }); + + } + else{ + self.folder_browser.showBrowser(); + } + + self.list.navigation_menu.hide(); + } }); diff --git a/couchpotato/static/scripts/combined.base.min.js b/couchpotato/static/scripts/combined.base.min.js index f95282b..e7bf473 100644 --- a/couchpotato/static/scripts/combined.base.min.js +++ b/couchpotato/static/scripts/combined.base.min.js @@ -1599,7 +1599,7 @@ Option.Directory = new Class({ var self = this; if (!self.browser || self.browser && !self.browser.isVisible()) self.caretAtEnd(); if (!self.browser) { - self.browser = new Element("div.directory_list").adopt(new Element("div.pointer"), new Element("div.wrapper").adopt(new Element("div.actions").adopt(self.back_button = new Element("a.back", { + self.browser = new Element("div.directory_list").adopt(self.pointer = new Element("div.pointer"), new Element("div.wrapper").adopt(new Element("div.actions").adopt(self.back_button = new Element("a.back", { html: "", events: { click: self.previousDirectory.bind(self) diff --git a/couchpotato/static/scripts/combined.plugins.min.js b/couchpotato/static/scripts/combined.plugins.min.js index 99e01d9..2ccba96 100644 --- a/couchpotato/static/scripts/combined.plugins.min.js +++ b/couchpotato/static/scripts/combined.plugins.min.js @@ -2319,10 +2319,19 @@ var MoviesWanted = new Class({ self.folder_browser.showBrowser(); self.folder_browser.browser.getElements(".clear.button").hide(); self.folder_browser.save_button.text = "Select"; - self.folder_browser.browser.style.zIndex = 1e3; + self.folder_browser.browser.setStyles({ + "z-index": 1e3, + right: 20, + top: 0, + margin: 0 + }); + self.folder_browser.pointer.setStyles({ + right: 20 + }); } else { self.folder_browser.showBrowser(); } + self.list.navigation_menu.hide(); } }); diff --git a/couchpotato/static/scripts/page/settings.js b/couchpotato/static/scripts/page/settings.js index 1ec17b6..ee96ea8 100644 --- a/couchpotato/static/scripts/page/settings.js +++ b/couchpotato/static/scripts/page/settings.js @@ -756,7 +756,7 @@ Option.Directory = new Class({ if(!self.browser){ self.browser = new Element('div.directory_list').adopt( - new Element('div.pointer'), + self.pointer = new Element('div.pointer'), new Element('div.wrapper').adopt( new Element('div.actions').adopt( self.back_button = new Element('a.back', { diff --git a/couchpotato/static/style/combined.min.css b/couchpotato/static/style/combined.min.css index e31e658..3a55b29 100644 --- a/couchpotato/static/style/combined.min.css +++ b/couchpotato/static/style/combined.min.css @@ -1,5 +1,5 @@ -.messages .message,.more_menu .wrapper,.page.settings .directory_list,.search_form .wrapper{box-shadow:0 0 15px 2px rgba(0,0,0,.15)} .movies>.description a:hover,.page.movie_details .releases .buttons a:hover{text-decoration:underline} +.directory_list,.messages .message,.more_menu .wrapper,.search_form .wrapper{box-shadow:0 0 15px 2px rgba(0,0,0,.15)} .search_form{display:inline-block;z-index:11;width:44px;position:relative} .search_form *{-webkit-transform:translateZ(0);transform:translateZ(0)} .search_form .icon-search{position:absolute;z-index:2;top:50%;left:0;height:100%;text-align:center;color:#FFF;font-size:20px;-webkit-transform:translateY(-50%);transform:translateY(-50%)} @@ -152,7 +152,7 @@ @media (max-width:480px){.page.movie_details .overlay{left:0;border-radius:0} .page.movie_details .overlay .close{width:44px} } -.page.movie_details .scroll_content{position:fixed;z-index:2;top:0;bottom:0;right:0;left:176px;background:#FFF;border-radius:3px 0 0 3px;overflow-y:auto;-webkit-transform:translateX(100%)translateZ(0);transform:translateX(100%)translateZ(0);transition:-webkit-transform 350ms cubic-bezier(.9,0,.1,1);transition:transform 350ms cubic-bezier(.9,0,.1,1)} +.page.movie_details .scroll_content{position:fixed;z-index:2;top:0;bottom:0;right:0;left:176px;background:#FFF;border-radius:3px 0 0 3px;overflow-y:auto;-webkit-transform:translateX(100%) translateZ(0);transform:translateX(100%) translateZ(0);transition:-webkit-transform 350ms cubic-bezier(.9,0,.1,1);transition:transform 350ms cubic-bezier(.9,0,.1,1)} .page.movie_details .scroll_content>.head{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row nowrap;-ms-flex-flow:row nowrap;flex-flow:row nowrap;padding:0 20px;position:relative;z-index:2} @media (max-width:480px){.page.movie_details .scroll_content{left:44px} .page.movie_details .scroll_content>.head{-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:0 10px;line-height:1em} @@ -186,7 +186,7 @@ .page.movie_details .scroll_content>.head .buttons{margin-left:auto} .page.movie_details .scroll_content .section{padding:10px} } -.page.movie_details .files span,.page.movie_details .releases .item span{text-overflow:ellipsis;overflow:hidden;white-space:nowrap;padding:6.67px 0} +.page.movie_details .files span,.page.movie_details .releases .item span{white-space:nowrap;padding:6.67px 0;overflow:hidden;text-overflow:ellipsis} .page.movie_details.show{pointer-events:auto} .page.movie_details.show .overlay{opacity:1;transition-delay:0s} .page.movie_details.show .overlay .close{opacity:1;transition-delay:300ms} @@ -245,7 +245,7 @@ .page.movie_details .releases .actions{min-width:80px;max-width:80px} .page.movie_details .trailer_container{background:#000;position:relative;padding-bottom:56.25%;height:0;overflow:hidden;max-width:100%;cursor:pointer} .alph_nav .menus .button,.alph_nav .menus .counter{line-height:80px;padding:0 10px} -.page.movie_details .trailer_container .background{opacity:.3;transition:all 300ms;-webkit-transform:scale(1.05)translateZ(0);transform:scale(1.05)translateZ(0);background:center no-repeat;background-size:cover;position:absolute;top:0;right:0;bottom:0;left:0;z-index:1} +.page.movie_details .trailer_container .background{opacity:.3;transition:all 300ms;-webkit-transform:scale(1.05) translateZ(0);transform:scale(1.05) translateZ(0);background:center no-repeat;background-size:cover;position:absolute;top:0;right:0;bottom:0;left:0;z-index:1} .page.movie_details .trailer_container .icon-play{opacity:.9;position:absolute;z-index:2;text-align:center;width:100%;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);transition:all 300ms;color:#FFF;font-size:110px} @media (max-width:1024px){.page.movie_details .trailer_container .icon-play{font-size:55px} } @@ -306,7 +306,6 @@ @media all and (max-width:600px){.charts .chart{width:100%} } .charts .chart .media_result .data{left:150px;background:#4e5969;border:none} -#category_ordering li:last-child,#profile_ordering li:last-child,.api_docs .api .params tr:last-child td,.api_docs .api .params tr:last-child th{border:0} .charts .chart .media_result .data .info{top:10px;left:15px;right:15px;bottom:10px;overflow:hidden} .charts .chart .media_result .data .info h2{white-space:normal;max-height:120px;font-size:18px;line-height:18px} .charts .chart .media_result .data .info .genres,.charts .chart .media_result .data .info .rating,.charts .chart .media_result .data .info .year{position:static;display:block;padding:0;opacity:.6} @@ -334,7 +333,6 @@ .toggle_menu a{display:block;width:50%;float:left;color:rgba(255,255,255,.6);border-bottom:1px solid rgba(255,255,255,.06667)} .toggle_menu a:hover{border-color:#047792;border-width:4px;color:#fff} .toggle_menu a.active{border-bottom:4px solid #04bce6;color:#fff} -#category_ordering li,#profile_ordering li,.add_new_profile{border-bottom:1px solid #eaeaea} .toggle_menu a:last-child{float:right} .toggle_menu h2{height:40px} @media all and (max-width:480px){.toggle_menu h2{font-size:16px;text-align:center;height:30px} @@ -348,7 +346,8 @@ .category .formHint{opacity:.1} .category:hover .formHint{opacity:1} #category_ordering ul{float:left;margin:0;width:275px;padding:0} -#category_ordering li{cursor:-webkit-grab;cursor:grab;padding:5px;list-style:none} +#category_ordering li{cursor:-webkit-grab;cursor:grab;border-bottom:1px solid #eaeaea;padding:5px;list-style:none} +#category_ordering li:last-child{border:0} #category_ordering li .check{margin:2px 10px 0 0;vertical-align:top} #category_ordering li>span{display:inline-block;height:20px;vertical-align:top;line-height:20px} #category_ordering li .handle{width:20px;float:right} @@ -376,7 +375,7 @@ .report_popup.report_popup .bug{width:80%;height:80%;max-height:800px;max-width:800px;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:column nowrap;-ms-flex-flow:column nowrap;flex-flow:column nowrap} .report_popup.report_popup .bug textarea{display:block;width:100%;background:#FFF;padding:20px;overflow:auto;color:#666;height:70%;font-size:12px} .do_report.do_report{z-index:10000;position:absolute;padding:10px;background:#ac0000;color:#FFF} -.add_new_profile{padding:20px;display:block;text-align:center;font-size:20px} +.add_new_profile{padding:20px;display:block;text-align:center;font-size:20px;border-bottom:1px solid #eaeaea} .profile{margin-bottom:20px} .profile .quality_label input{font-weight:700} .profile .ctrlHolder .types{-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto;min-width:360px} @@ -392,9 +391,11 @@ .profile .ctrlHolder.wait_for.wait_for input{min-width:0;width:40px;text-align:center;margin:0 2px} .profile .ctrlHolder.wait_for.wait_for .advanced{display:none;color:#ac0000} .show_advanced .profile .ctrlHolder.wait_for.wait_for .advanced{display:inline} +#profile_ordering li,.page.login{display:-webkit-flex;display:-ms-flexbox} #profile_ordering ul{list-style:none;margin:0;width:275px;padding:0} -#profile_ordering li{padding:5px;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center} +#profile_ordering li{border-bottom:1px solid #eaeaea;padding:5px;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center} #profile_ordering li:hover{background:#eaeaea} +#profile_ordering li:last-child{border:0} #profile_ordering li input[type=checkbox]{margin:2px 10px 0 0;vertical-align:top} #profile_ordering li>span{display:inline-block;height:20px;vertical-align:top;line-height:20px} #profile_ordering li>span.profile_label{-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto} @@ -429,6 +430,7 @@ .api_docs .api .params{background:#fafafa;width:100%} .api_docs .api .params h3{clear:both;float:left;width:100px} .api_docs .api .params td,.api_docs .api .params th{padding:3px 5px;border-bottom:1px solid #eee} +.api_docs .api .params tr:last-child td,.api_docs .api .params tr:last-child th{border:0} .api_docs .api .params .param{vertical-align:top} .api_docs .api .params .param th{text-align:left;width:100px} .api_docs .api .params .param .type{font-style:italic;margin-right:10px;width:100px;color:#666} @@ -446,7 +448,6 @@ .api_docs .database table .form,.api_docs .database table form{width:600px} .api_docs .database table textarea{font-size:12px;width:100%;height:200px} .api_docs .database table input[type=submit]{display:block} -.page.login,body{display:-webkit-flex;display:-ms-flexbox} .page.login{background:#FFF;display:flex;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;font-size:1.25em} .page.login h1{padding:0 0 10px;font-size:60px;font-family:Lobster;font-weight:400;color:#ac0000;text-align:center} .page.login form{padding:0;width:300px} @@ -455,7 +456,7 @@ .page.login input[type=password],.page.login input[type=text]{width:100%!important} .page.login .remember_me{font-size:15px;float:left;width:150px} .page.login .button{float:right;margin:0;transition:none} -@font-face{font-family:icons;src:url(../fonts/icons.eot?74719542);src:url(../fonts/icons.eot?74719542#iefix)format("embedded-opentype"),url(../fonts/icons.woff?747195412)format("woff"),url(../fonts/icons.ttf?74719542)format("truetype"),url(../fonts/icons.svg?74719542#icons)format("svg");font-weight:400;font-style:normal} +@font-face{font-family:icons;src:url(../fonts/icons.eot?74719542);src:url(../fonts/icons.eot?74719542#iefix) format("embedded-opentype"),url(../fonts/icons.woff?747195412) format("woff"),url(../fonts/icons.ttf?74719542) format("truetype"),url(../fonts/icons.svg?74719542#icons) format("svg");font-weight:400;font-style:normal} [class*=" icon-"]:before,[class^=icon-]:before{font-family:icons;font-style:normal;font-weight:400;speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale} .icon-left-arrow:before{content:'\e800'} .icon-settings:before{content:'\e801'} @@ -486,21 +487,21 @@ .icon-star:before{content:'\e81a'} .icon-star-empty:before{content:'\e81b'} .icon-star-half:before{content:'\e81c'} -@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Light-webfont.eot);src:url(../fonts/OpenSans-Light-webfont.eot?#iefix)format("embedded-opentype"),url(../fonts/OpenSans-Light-webfont.woff)format("woff"),url(../fonts/OpenSans-Light-webfont.ttf)format("truetype"),url(../fonts/OpenSans-Light-webfont.svg#OpenSansRegular)format("svg");font-weight:200;font-style:normal} -@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Regular-webfont.eot);src:url(../fonts/OpenSans-Regular-webfont.eot?#iefix)format("embedded-opentype"),url(../fonts/OpenSans-Regular-webfont.woff)format("woff"),url(../fonts/OpenSans-Regular-webfont.ttf)format("truetype"),url(../fonts/OpenSans-Regular-webfont.svg#OpenSansRegular)format("svg");font-weight:400;font-style:normal} -@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Italic-webfont.eot);src:url(../fonts/OpenSans-Italic-webfont.eot?#iefix)format("embedded-opentype"),url(../fonts/OpenSans-Italic-webfont.woff)format("woff"),url(../fonts/OpenSans-Italic-webfont.ttf)format("truetype"),url(../fonts/OpenSans-Italic-webfont.svg#OpenSansItalic)format("svg");font-weight:400;font-style:italic} -@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Bold-webfont.eot);src:url(../fonts/OpenSans-Bold-webfont.eot?#iefix)format("embedded-opentype"),url(../fonts/OpenSans-Bold-webfont.woff)format("woff"),url(../fonts/OpenSans-Bold-webfont.ttf)format("truetype"),url(../fonts/OpenSans-Bold-webfont.svg#OpenSansBold)format("svg");font-weight:700;font-style:normal} -@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-BoldItalic-webfont.eot);src:url(../fonts/OpenSans-BoldItalic-webfont.eot?#iefix)format("embedded-opentype"),url(../fonts/OpenSans-BoldItalic-webfont.woff)format("woff"),url(../fonts/OpenSans-BoldItalic-webfont.ttf)format("truetype"),url(../fonts/OpenSans-BoldItalic-webfont.svg#OpenSansBoldItalic)format("svg");font-weight:700;font-style:italic} -@font-face{font-family:Lobster;src:url(../fonts/Lobster-webfont.eot);src:url(../fonts/Lobster-webfont.eot?#iefix)format("embedded-opentype"),url(../fonts/Lobster-webfont.woff2)format("woff2"),url(../fonts/Lobster-webfont.woff)format("woff"),url(../fonts/Lobster-webfont.ttf)format("truetype"),url(../fonts/Lobster-webfont.svg#lobster_14regular)format("svg");font-weight:400;font-style:normal} +@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Light-webfont.eot);src:url(../fonts/OpenSans-Light-webfont.eot?#iefix) format("embedded-opentype"),url(../fonts/OpenSans-Light-webfont.woff) format("woff"),url(../fonts/OpenSans-Light-webfont.ttf) format("truetype"),url(../fonts/OpenSans-Light-webfont.svg#OpenSansRegular) format("svg");font-weight:200;font-style:normal} +@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Regular-webfont.eot);src:url(../fonts/OpenSans-Regular-webfont.eot?#iefix) format("embedded-opentype"),url(../fonts/OpenSans-Regular-webfont.woff) format("woff"),url(../fonts/OpenSans-Regular-webfont.ttf) format("truetype"),url(../fonts/OpenSans-Regular-webfont.svg#OpenSansRegular) format("svg");font-weight:400;font-style:normal} +@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Italic-webfont.eot);src:url(../fonts/OpenSans-Italic-webfont.eot?#iefix) format("embedded-opentype"),url(../fonts/OpenSans-Italic-webfont.woff) format("woff"),url(../fonts/OpenSans-Italic-webfont.ttf) format("truetype"),url(../fonts/OpenSans-Italic-webfont.svg#OpenSansItalic) format("svg");font-weight:400;font-style:italic} +@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Bold-webfont.eot);src:url(../fonts/OpenSans-Bold-webfont.eot?#iefix) format("embedded-opentype"),url(../fonts/OpenSans-Bold-webfont.woff) format("woff"),url(../fonts/OpenSans-Bold-webfont.ttf) format("truetype"),url(../fonts/OpenSans-Bold-webfont.svg#OpenSansBold) format("svg");font-weight:700;font-style:normal} +@font-face{font-family:OpenSans;src:url(../fonts/OpenSans-BoldItalic-webfont.eot);src:url(../fonts/OpenSans-BoldItalic-webfont.eot?#iefix) format("embedded-opentype"),url(../fonts/OpenSans-BoldItalic-webfont.woff) format("woff"),url(../fonts/OpenSans-BoldItalic-webfont.ttf) format("truetype"),url(../fonts/OpenSans-BoldItalic-webfont.svg#OpenSansBoldItalic) format("svg");font-weight:700;font-style:italic} +@font-face{font-family:Lobster;src:url(../fonts/Lobster-webfont.eot);src:url(../fonts/Lobster-webfont.eot?#iefix) format("embedded-opentype"),url(../fonts/Lobster-webfont.woff2) format("woff2"),url(../fonts/Lobster-webfont.woff) format("woff"),url(../fonts/Lobster-webfont.ttf) format("truetype"),url(../fonts/Lobster-webfont.svg#lobster_14regular) format("svg");font-weight:400;font-style:normal} *{margin:0;padding:0;box-sizing:border-box;text-rendering:optimizeSpeed;-webkit-backface-visibility:hidden;backface-visibility:hidden} body,html{font-size:14px;line-height:1.5;font-family:OpenSans,"Helvetica Neue",Helvetica,Arial,Geneva,sans-serif;font-weight:300;height:100%;margin:0;padding:0;background:#111;overflow:hidden} -body{display:flex;-webkit-flex-flow:row nowrap;-ms-flex-flow:row nowrap;flex-flow:row nowrap} +body{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row nowrap;-ms-flex-flow:row nowrap;flex-flow:row nowrap} a{position:relative;overflow:hidden;text-decoration:none;cursor:pointer;-webkit-tap-highlight-color:transparent} input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#FFF;border:1px solid #b7b7b7} .button{color:#ac0000;font-weight:300;padding:5px;cursor:pointer;border:1px solid #ac0000;border-radius:3px;margin:0 5px;transition:all 150ms} .button:hover{background:#ac0000;color:#FFF} -.ripple{position:absolute;height:10px;width:10px;border-radius:50%;background:#ac0000;-webkit-transform:translate(-50%,-50%)scale(1)rotateZ(360deg);transform:translate(-50%,-50%)scale(1)rotateZ(360deg);opacity:.2;transition:all 1.5s ease;transition-property:opacity,-webkit-transform;transition-property:opacity,transform} -.ripple.animate{-webkit-transform:translate(-50%,-50%)scale(100)rotateZ(360deg);transform:translate(-50%,-50%)scale(100)rotateZ(360deg);opacity:0} +.ripple{position:absolute;height:10px;width:10px;border-radius:50%;background:#ac0000;-webkit-transform:translate(-50%,-50%) scale(1) rotateZ(360deg);transform:translate(-50%,-50%) scale(1) rotateZ(360deg);opacity:.2;transition:all 1.5s ease;transition-property:opacity,-webkit-transform;transition-property:opacity,transform} +.ripple.animate{-webkit-transform:translate(-50%,-50%) scale(100) rotateZ(360deg);transform:translate(-50%,-50%) scale(100) rotateZ(360deg);opacity:0} .header{width:132px;min-width:132px;position:relative;z-index:100} @media (max-width:480px){.header{width:44px;min-width:44px;z-index:21} } @@ -561,8 +562,8 @@ input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#F .page h1,.page h2,.page h3,.page h4{font-weight:300} .page h2{font-size:24px;padding:20px} .page .navigation{z-index:2;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center;position:fixed;top:0;height:80px;left:152px;right:20px;background:#FFF;border-radius:3px 0 0} -.more_menu,.more_menu .button:before{position:relative} .more_menu .button,.more_menu a,.page .navigation ul li{display:inline-block} +.more_menu,.more_menu .button:before{position:relative} @media (max-width:480px){.page h2{font-size:18px;padding:10px} .page .navigation{height:44px;left:64px} } @@ -580,48 +581,49 @@ input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#F .more_menu{line-height:1em} .more_menu .button{font-size:24px;cursor:pointer} .more_menu .wrapper{display:none;position:absolute;right:0;background:#ac0000;z-index:5000;border-radius:3px 0 0 3px;-webkit-transform-origin:80% 0;transform-origin:80% 0} -.more_menu .wrapper:before{-webkit-transform:rotate(45deg)translateY(-60%);transform:rotate(45deg)translateY(-60%);content:'';display:block;position:absolute;background:#ac0000;height:10px;width:10px;left:-9px;bottom:11px;z-index:1;opacity:1;border-radius:3px} -.mask,.messages{right:0;bottom:0} +.more_menu .wrapper:before{-webkit-transform:rotate(45deg) translateY(-60%);transform:rotate(45deg) translateY(-60%);content:'';display:block;position:absolute;background:#ac0000;height:10px;width:10px;left:-9px;bottom:11px;z-index:1;opacity:1;border-radius:3px} .more_menu .wrapper ul{background:#FFF;position:relative;z-index:2;overflow:hidden;border-radius:3px 0 0 3px} .more_menu .wrapper ul li{display:block;line-height:1em;border-top:1px solid #eaeaea} .more_menu .wrapper ul li:first-child{border-top:0} .more_menu .wrapper ul li a{display:block;color:#000;padding:5px 10px;font-size:1em;line-height:22px} +.question,.table .item{display:-webkit-flex;display:-ms-flexbox} .more_menu .wrapper ul li:first-child a{padding-top:10px} .more_menu .wrapper ul li:last-child a{padding-bottom:10px} -.messages{position:fixed;width:320px;z-index:2000;overflow:hidden;font-size:14px;font-weight:700;padding:5px} +.messages{position:fixed;right:0;bottom:0;width:320px;z-index:2000;overflow:hidden;font-size:14px;font-weight:700;padding:5px} .messages .message{overflow:hidden;transition:all .6s cubic-bezier(.9,0,.1,1);width:100%;position:relative;max-height:0;font-size:1.1em;font-weight:400;-webkit-transform:scale(0);transform:scale(0);-webkit-transform-origin:100% 50%;transform-origin:100% 50%;background:#ac0000;margin-bottom:4px;border-radius:3px} +.mask,.messages .close{position:absolute;top:0;right:0} .messages .message .inner{padding:15px 30px 15px 20px;background:#FFF;margin-bottom:4px;border-radius:3px} .messages .message.sticky{background-color:#ac0000} .messages .message.show{max-height:100px;-webkit-transform:scale(1);transform:scale(1)} .messages .message.hide{max-height:0;padding:0 20px;margin:0;-webkit-transform:scale(0);transform:scale(0)} -.messages .close{position:absolute;padding:10px 8px;top:0;right:0;color:#FFF} -.question{position:fixed;z-index:20000;color:#FFF;padding:20px;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center} +.messages .close{padding:10px 8px;color:#FFF} +.question{position:fixed;z-index:20000;color:#FFF;padding:20px;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center} .question.show{opacity:1} .question .inner{width:100%;max-width:500px} .question h3{display:block;margin-bottom:20px;font-size:1.4em;font-weight:lighter} .question .hint{margin:-20px 0 20px} .question a{border-color:#FFF;color:#FFF;transition:none} .question a:hover{background:#FFF;color:#ac0000} -.mask{background:rgba(0,0,0,.8);z-index:1000;text-align:center;position:absolute;top:0;left:0;opacity:0;transition:opacity 500ms} +.mask{background:rgba(0,0,0,.8);z-index:1000;text-align:center;bottom:0;left:0;opacity:0;transition:opacity 500ms} .mask .message,.mask .spinner{position:absolute;top:50%;left:50%} .mask .message{color:#FFF;text-align:center;width:320px;margin:-49px 0 0 -160px;font-size:16px} .mask .message h1{font-size:1.5em} -.mask .spinner{width:22px;height:22px;display:block;background:#fff;margin-top:-11px;margin-left:-11px;outline:transparent solid 1px;-webkit-animation:rotating 2.5s cubic-bezier(.9,0,.1,1)infinite normal;animation:rotating 2.5s cubic-bezier(.9,0,.1,1)infinite normal;-webkit-transform:scale(0);transform:scale(0)} +.mask .spinner{width:22px;height:22px;display:block;background:#fff;margin-top:-11px;margin-left:-11px;outline:transparent solid 1px;-webkit-animation:rotating 2.5s cubic-bezier(.9,0,.1,1) infinite normal;animation:rotating 2.5s cubic-bezier(.9,0,.1,1) infinite normal;-webkit-transform:scale(0);transform:scale(0)} .mask.with_message .spinner{margin-top:-88px} .mask.show{pointer-events:auto;opacity:1} .mask.show .spinner{-webkit-transform:scale(1);transform:scale(1)} .mask.hide{opacity:0} .mask.hide .spinner{-webkit-transform:scale(0);transform:scale(0)} -@-webkit-keyframes rotating{0%{-webkit-transform:rotate(0)scale(1.6);transform:rotate(0)scale(1.6);border-radius:1px} -48%{-webkit-transform:rotate(360deg)scale(1);transform:rotate(360deg)scale(1);border-radius:50%} -100%{-webkit-transform:rotate(720deg)scale(1.6);transform:rotate(720deg)scale(1.6);border-radius:1px} +@-webkit-keyframes rotating{0%{-webkit-transform:rotate(0) scale(1.6);transform:rotate(0) scale(1.6);border-radius:1px} +48%{-webkit-transform:rotate(360deg) scale(1);transform:rotate(360deg) scale(1);border-radius:50%} +100%{-webkit-transform:rotate(720deg) scale(1.6);transform:rotate(720deg) scale(1.6);border-radius:1px} } -@keyframes rotating{0%{-webkit-transform:rotate(0)scale(1.6);transform:rotate(0)scale(1.6);border-radius:1px} -48%{-webkit-transform:rotate(360deg)scale(1);transform:rotate(360deg)scale(1);border-radius:50%} -100%{-webkit-transform:rotate(720deg)scale(1.6);transform:rotate(720deg)scale(1.6);border-radius:1px} +@keyframes rotating{0%{-webkit-transform:rotate(0) scale(1.6);transform:rotate(0) scale(1.6);border-radius:1px} +48%{-webkit-transform:rotate(360deg) scale(1);transform:rotate(360deg) scale(1);border-radius:50%} +100%{-webkit-transform:rotate(720deg) scale(1.6);transform:rotate(720deg) scale(1.6);border-radius:1px} } .table .head{font-weight:700} -.table .item{display:-webkit-flex;display:-ms-flexbox;display:flex;border-bottom:1px solid rgba(0,0,0,.2)} +.table .item{display:flex;border-bottom:1px solid rgba(0,0,0,.2)} .table .item:last-child{border-bottom:none} .table .item span{padding:1px 2px} .table .item span:first-child{padding-left:0} @@ -729,25 +731,25 @@ input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#F @media (max-width:480px){.page.settings .directory{width:100%} } .page.settings .directory input{width:100%} -.page.settings .directory_list{z-index:2;position:absolute;width:450px;margin:28px 0 20px;background:#ac0000;border-radius:3px 3px 0 0} -.page.settings .directory_list .pointer{border-right:6px solid transparent;border-left:6px solid transparent;border-bottom:6px solid #ac0000;display:block;position:absolute;width:0;margin:-6px 0 0 100px} -.page.settings .directory_list .wrapper{background:#FFF;border-radius:3px 3px 0 0;margin-top:5px} -.page.settings .directory_list ul{width:92%;height:300px;overflow:auto;margin:0 20px} -.page.settings .directory_list li{padding:4px 20px 4px 0;cursor:pointer;margin:0!important;border-top:1px solid rgba(255,255,255,.1);overflow:hidden;white-space:nowrap;text-overflow:ellipsis} -.page.settings .directory_list li.blur{opacity:.3} -.page.settings .directory_list li:last-child{border-bottom:1px solid rgba(255,255,255,.1)} -.page.settings .directory_list li:hover{color:#ac0000} -.page.settings .directory_list li.empty{background:0 0;height:100px;text-align:center;font-style:italic;border:none;line-height:100px;cursor:default;color:#BBB;text-shadow:none;font-size:12px} -.page.settings .directory_list .actions{clear:both;padding:20px;min-height:45px;position:relative;width:100%;text-align:right} -.page.settings .directory_list .actions label{float:right;width:auto;padding:0} -.page.settings .directory_list .actions label input{margin-left:10px} -.page.settings .directory_list .actions .back{font-weight:700;width:160px;display:inline-block;padding:0;line-height:120%;vertical-align:top;position:absolute;text-align:left;left:20px} -.page.settings .directory_list .actions:last-child{padding:20px} -.page.settings .directory_list .actions:last-child>span{padding:0 5px;text-shadow:none} -.page.settings .directory_list .actions:last-child>.clear{left:20px;position:absolute;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);margin:0} -.page.settings .directory_list .actions:last-child>.cancel{color:rgba(0,0,0,.4)} -.page.settings .directory_list .actions:last-child>.save{margin-right:0} .page.settings .choice .select_wrapper{margin-left:20px;width:120px;min-width:120px} @media (max-width:480px){.page.settings .choice .select_wrapper{margin:10px 0 0} } -.page.settings .choice .select_wrapper select{min-width:0!important} \ No newline at end of file +.page.settings .choice .select_wrapper select{min-width:0!important} +.directory_list{z-index:2;position:absolute;width:450px;margin:28px 0 20px;background:#ac0000;border-radius:3px 3px 0 0} +.directory_list .pointer{border-right:6px solid transparent;border-left:6px solid transparent;border-bottom:6px solid #ac0000;display:block;position:absolute;width:0;margin:-6px 0 0 100px} +.directory_list .wrapper{background:#FFF;border-radius:3px 3px 0 0;margin-top:5px} +.directory_list ul{width:92%;height:300px;overflow:auto;margin:0 20px} +.directory_list li{padding:4px 20px 4px 0;cursor:pointer;margin:0!important;border-top:1px solid rgba(255,255,255,.1);overflow:hidden;white-space:nowrap;text-overflow:ellipsis} +.directory_list li.blur{opacity:.3} +.directory_list li:last-child{border-bottom:1px solid rgba(255,255,255,.1)} +.directory_list li:hover{color:#ac0000} +.directory_list li.empty{background:0 0;height:100px;text-align:center;font-style:italic;border:none;line-height:100px;cursor:default;color:#BBB;text-shadow:none;font-size:12px} +.directory_list .actions{clear:both;padding:20px;min-height:45px;position:relative;width:100%;text-align:right} +.directory_list .actions label{float:right;width:auto;padding:0} +.directory_list .actions label input{margin-left:10px} +.directory_list .actions .back{font-weight:700;width:160px;display:inline-block;padding:0;line-height:120%;vertical-align:top;position:absolute;text-align:left;left:20px} +.directory_list .actions:last-child{padding:20px} +.directory_list .actions:last-child>span{padding:0 5px;text-shadow:none} +.directory_list .actions:last-child>.clear{left:20px;position:absolute;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);margin:0} +.directory_list .actions:last-child>.cancel{color:rgba(0,0,0,.4)} +.directory_list .actions:last-child>.save{margin-right:0} \ No newline at end of file diff --git a/couchpotato/static/style/settings.scss b/couchpotato/static/style/settings.scss index fb93d14..50680c9 100644 --- a/couchpotato/static/style/settings.scss +++ b/couchpotato/static/style/settings.scss @@ -537,145 +537,144 @@ } } + .choice { + .select_wrapper { + margin-left: $padding; + width: 120px; + min-width: 120px; + + @include media-phablet { + margin: $padding/2 0 0; + } - .directory_list { - z-index: 2; + select { + min-width: 0 !important; + } + } + } + +} + +.directory_list { + z-index: 2; + position: absolute; + width: 450px; + margin: 28px 0 20px 0; + background: $primary_color; + box-shadow: 0 0 15px 2px rgba(0,0,0,.15); + border-radius: $border_radius $border_radius 0 0; + + .pointer { + border-right: 6px solid transparent; + border-left: 6px solid transparent; + border-bottom: 6px solid $primary_color; + display: block; position: absolute; - width: 450px; - margin: 28px 0 20px 0; - background: $primary_color; - box-shadow: 0 0 15px 2px rgba(0,0,0,.15); + width: 0; + margin: -6px 0 0 100px; + } + + .wrapper { + background: $background_color; border-radius: $border_radius $border_radius 0 0; + margin-top: 5px; + } - .pointer { - border-right: 6px solid transparent; - border-left: 6px solid transparent; - border-bottom: 6px solid $primary_color; - display: block; - position: absolute; - width: 0; - margin: -6px 0 0 100px; + ul { + width: 92%; + height: 300px; + overflow: auto; + margin: 0 $padding; + } + + li { + padding: 4px $padding 4px 0; + cursor: pointer; + margin: 0 !important; + border-top: 1px solid rgba(255,255,255,0.1); + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + + &.blur { + opacity: .3; } - .wrapper { - background: $background_color; - border-radius: $border_radius $border_radius 0 0; - margin-top: 5px; + &:last-child { + border-bottom: 1px solid rgba(255,255,255,0.1); } - ul { - width: 92%; - height: 300px; - overflow: auto; - margin: 0 $padding; + &:hover { + color: $primary_color; } - li { - padding: 4px $padding 4px 0; - cursor: pointer; - margin: 0 !important; - border-top: 1px solid rgba(255,255,255,0.1); - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; + &.empty { + background: none; + height: 100px; + text-align: center; + font-style: italic; + border: none; + line-height: 100px; + cursor: default; + color: #BBB; + text-shadow: none; + font-size: 12px; + } + } - &.blur { - opacity: .3; - } + .actions { + clear: both; + padding: $padding; + min-height: 45px; + position: relative; + width: 100%; + text-align: right; - &:last-child { - border-bottom: 1px solid rgba(255,255,255,0.1); - } + label { + float: right; + width: auto; + padding: 0; - &:hover { - color: $primary_color; + input { + margin-left: $padding/2; } + } - &.empty { - background: none; - height: 100px; - text-align: center; - font-style: italic; - border: none; - line-height: 100px; - cursor: default; - color: #BBB; - text-shadow: none; - font-size: 12px; - } + .back { + font-weight: bold; + width: 160px; + display: inline-block; + padding: 0; + line-height: 120%; + vertical-align: top; + position: absolute; + text-align: left; + left: $padding; } - .actions { - clear: both; + &:last-child { padding: $padding; - min-height: 45px; - position: relative; - width: 100%; - text-align: right; - - label { - float: right; - width: auto; - padding: 0; - input { - margin-left: $padding/2; - } + > span { + padding: 0 5px; + text-shadow: none; } - .back { - font-weight: bold; - width: 160px; - display: inline-block; - padding: 0; - line-height: 120%; - vertical-align: top; - position: absolute; - text-align: left; + > .clear { left: $padding; + position: absolute; + top: 50%; + transform: translateY(-50%); + margin: 0; } - &:last-child { - padding: $padding; - - > span { - padding: 0 5px; - text-shadow: none; - } - - > .clear { - left: $padding; - position: absolute; - top: 50%; - transform: translateY(-50%); - margin: 0; - } - - > .cancel { - color: rgba(0,0,0,.4); - } - - > .save { - margin-right: 0; - } - } - } - } - - .choice { - .select_wrapper { - margin-left: $padding; - width: 120px; - min-width: 120px; - - @include media-phablet { - margin: $padding/2 0 0; + > .cancel { + color: rgba(0,0,0,.4); } - select { - min-width: 0 !important; + > .save { + margin-right: 0; } } } - } From 1e73af7d5d9361e3495958ee00f325e967032c15 Mon Sep 17 00:00:00 2001 From: Ruud Date: Tue, 11 Aug 2015 17:26:23 +0200 Subject: [PATCH 245/301] Remove Form replacements --- Gruntfile.js | 4 - .../core/media/movie/charts/static/charts.js | 6 +- .../core/notifications/core/static/notification.js | 18 +- couchpotato/static/scripts/combined.vendor.min.js | 639 --------------------- couchpotato/static/scripts/page/settings.js | 2 +- .../scripts/vendor/form_replacement/form_check.js | 126 ---- .../vendor/form_replacement/form_checkgroup.js | 51 -- .../vendor/form_replacement/form_dropdown.js | 325 ----------- .../scripts/vendor/form_replacement/form_radio.js | 34 -- .../vendor/form_replacement/form_radiogroup.js | 59 -- .../vendor/form_replacement/form_selectoption.js | 93 --- 11 files changed, 13 insertions(+), 1344 deletions(-) delete mode 100644 couchpotato/static/scripts/vendor/form_replacement/form_check.js delete mode 100644 couchpotato/static/scripts/vendor/form_replacement/form_checkgroup.js delete mode 100644 couchpotato/static/scripts/vendor/form_replacement/form_dropdown.js delete mode 100644 couchpotato/static/scripts/vendor/form_replacement/form_radio.js delete mode 100644 couchpotato/static/scripts/vendor/form_replacement/form_radiogroup.js delete mode 100644 couchpotato/static/scripts/vendor/form_replacement/form_selectoption.js diff --git a/Gruntfile.js b/Gruntfile.js index 7861215..f65bdc9 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -19,10 +19,6 @@ module.exports = function(grunt){ var vendor_scripts_files = [ 'couchpotato/static/scripts/vendor/mootools.js', 'couchpotato/static/scripts/vendor/mootools_more.js', - 'couchpotato/static/scripts/vendor/form_replacement/form_check.js', - 'couchpotato/static/scripts/vendor/form_replacement/form_radio.js', - 'couchpotato/static/scripts/vendor/form_replacement/form_dropdown.js', - 'couchpotato/static/scripts/vendor/form_replacement/form_selectoption.js', 'couchpotato/static/scripts/vendor/Array.stableSort.js', 'couchpotato/static/scripts/vendor/history.js', 'couchpotato/static/scripts/vendor/dynamics.js', diff --git a/couchpotato/core/media/movie/charts/static/charts.js b/couchpotato/core/media/movie/charts/static/charts.js index b011613..0481fb6 100644 --- a/couchpotato/core/media/movie/charts/static/charts.js +++ b/couchpotato/core/media/movie/charts/static/charts.js @@ -56,13 +56,13 @@ var Charts = new Class({ if(!json || json.count === 0){ self.el_no_charts_enabled.show(); - self.el_refresh_link.show(); - self.el_refreshing_text.hide(); + self.el_refresh_link.show(); + self.el_refreshing_text.hide(); } else { self.el_no_charts_enabled.hide(); - json.charts.sort(function(a, b) { + json.charts.sort(function(a, b) { return a.order - b.order; }); diff --git a/couchpotato/core/notifications/core/static/notification.js b/couchpotato/core/notifications/core/static/notification.js index cf2654e..57713c8 100644 --- a/couchpotato/core/notifications/core/static/notification.js +++ b/couchpotato/core/notifications/core/static/notification.js @@ -101,8 +101,8 @@ var NotificationBase = new Class({ } self.request = Api.request('notification.listener', { - 'data': {'init':true}, - 'onSuccess': function(json){ + 'data': {'init':true}, + 'onSuccess': function(json){ self.processData(json, true); } }).send(); @@ -128,15 +128,15 @@ var NotificationBase = new Class({ self.request.cancel(); self.request = Api.request('nonblock/notification.listener', { - 'onSuccess': function(json){ + 'onSuccess': function(json){ self.processData(json, false); }, - 'data': { - 'last_id': self.last_id - }, - 'onFailure': function(){ - self.startPoll.delay(2000, self); - } + 'data': { + 'last_id': self.last_id + }, + 'onFailure': function(){ + self.startPoll.delay(2000, self); + } }).send(); }, diff --git a/couchpotato/static/scripts/combined.vendor.min.js b/couchpotato/static/scripts/combined.vendor.min.js index 742c5e4..61ad0a5 100644 --- a/couchpotato/static/scripts/combined.vendor.min.js +++ b/couchpotato/static/scripts/combined.vendor.min.js @@ -6686,645 +6686,6 @@ Date.implement({ } }).alias("timeAgoInWords", "timeDiffInWords"); -if (typeof window.Form === "undefined") { - window.Form = {}; -} - -Form.Check = new Class({ - Implements: [ Events, Options ], - options: { - checked: false, - disabled: false - }, - bound: {}, - checked: false, - config: { - checkedClass: "checked", - disabledClass: "disabled", - elementClass: "check", - highlightedClass: "highlighted", - storage: "Form.Check::data" - }, - disabled: false, - element: null, - input: null, - label: null, - value: null, - initialize: function(input, options) { - this.setOptions(options); - this.bound = { - disable: this.disable.bind(this), - enable: this.enable.bind(this), - highlight: this.highlight.bind(this), - removeHighlight: this.removeHighlight.bind(this), - keyToggle: this.keyToggle.bind(this), - toggle: this.toggle.bind(this) - }; - var bound = this.bound; - input = this.input = $(input); - var id = input.get("id"); - this.label = document.getElement("label[for=" + id + "]"); - this.element = new Element("div", { - class: input.get("class") + " " + this.config.elementClass, - id: id ? id + "Check" : "", - events: { - click: bound.toggle, - mouseenter: bound.highlight, - mouseleave: bound.removeHighlight - } - }); - this.input.addEvents({ - keypress: bound.keyToggle, - keydown: bound.keyToggle, - keyup: bound.keyToggle - }); - if (this.label) { - this.label.addEvent("click", bound.toggle); - } - this.element.wraps(input); - this.value = input.get("value"); - if (this.input.checked) { - this.check(); - } else { - this.uncheck(); - } - if (this.input.disabled) { - this.disable(); - } else { - this.enable(); - } - input.store(this.config.storage, this).addEvents({ - blur: bound.removeHighlight, - focus: bound.highlight - }); - this.fireEvent("create", this); - }, - check: function() { - this.element.addClass(this.config.checkedClass); - this.input.set("checked", "checked").focus(); - this.checked = true; - this.fireEvent("check", this); - }, - disable: function() { - this.element.addClass(this.config.disabledClass); - this.input.set("disabled", "disabled"); - this.disabled = true; - this.fireEvent("disable", this); - }, - enable: function() { - this.element.removeClass(this.config.disabledClass); - this.input.erase("disabled"); - this.disabled = false; - this.fireEvent("enable", this); - }, - highlight: function() { - this.element.addClass(this.config.highlightedClass); - this.fireEvent("highlight", this); - }, - removeHighlight: function() { - this.element.removeClass(this.config.highlightedClass); - this.fireEvent("removeHighlight", this); - }, - keyToggle: function(e) { - var evt = e; - if (evt.key === "space") { - this.toggle(e); - } - }, - toggle: function(e) { - var evt; - if (this.disabled) { - return this; - } - if (e) { - evt = e.stopPropagation(); - if (evt.target.tagName.toLowerCase() !== "a") { - evt.stop(); - } - } - if (this.checked) { - this.uncheck(); - } else { - this.check(); - } - this.fireEvent("change", this); - this.input.fireEvent("change", this); - return this; - }, - uncheck: function() { - this.element.removeClass(this.config.checkedClass); - this.input.erase("checked"); - this.checked = false; - this.fireEvent("uncheck", this); - } -}); - -if (typeof window.Form === "undefined") { - window.Form = {}; -} - -Form.Radio = new Class({ - Extends: Form.Check, - config: { - elementClass: "radio", - storage: "Form.Radio::data" - }, - initialize: function(input, options) { - this.parent(input, options); - }, - toggle: function(e) { - if (this.element.hasClass("checked") || this.disabled) { - return; - } - var evt; - if (e) { - evt = e.stop(); - } - if (this.checked) { - this.uncheck(); - } else { - this.check(); - } - this.fireEvent(this.checked ? "onCheck" : "onUncheck", this); - this.fireEvent("onChange", this); - } -}); - -if (typeof window.Form === "undefined") { - window.Form = {}; -} - -Form.Dropdown = new Class({ - Implements: [ Events, Options ], - options: { - excludedValues: [], - initialValue: null, - mouseLeaveDelay: 350, - selectOptions: {}, - typeDelay: 500 - }, - bound: {}, - dropdownOptions: [], - element: null, - events: {}, - highlighted: null, - input: null, - open: true, - selected: null, - selection: null, - typed: { - lastKey: null, - value: null, - timer: null, - pressed: null, - shortlist: [], - startkey: null - }, - value: null, - initialize: function(select, options) { - this.setOptions(options); - select = $(select); - this.bound = { - collapse: this.collapse.bind(this), - expand: this.expand.bind(this), - focus: this.focus.bind(this), - highlightOption: this.highlightOption.bind(this), - keydown: this.keydown.bind(this), - keypress: this.keypress.bind(this), - mouseenterDropdown: this.mouseenterDropdown.bind(this), - mouseleaveDropdown: this.mouseleaveDropdown.bind(this), - mousemove: this.mousemove.bind(this), - removeHighlightOption: this.removeHighlightOption.bind(this), - select: this.select.bind(this), - toggle: this.toggle.bind(this) - }; - this.events = { - mouseenter: this.bound.mouseenterDropdown, - mouseleave: this.bound.mouseleaveDropdown - }; - this.value = this.options.initialValue; - this.initializeCreateElements(select); - var optionElements = select.getElements("option"); - this.updateOptions(optionElements); - this.element.replaces(select); - document.addEvent("click", this.bound.collapse); - var eventName = Browser.ie || Browser.webkit ? "keydown" : "keypress"; - var target = Browser.ie ? $(document.body) : window; - target.addEvent("keydown", this.bound.keydown).addEvent(eventName, this.bound.keypress); - }, - initializeCreateElements: function(select) { - var id = select.get("id"); - var dropdown = new Element("div", { - class: (select.get("class") + " select").trim(), - id: id && id !== "" ? id + "Dropdown" : "" - }); - var menu = new Element("div", { - class: "menu" - }); - var list = new Element("div", { - class: "list" - }); - var options = new Element("ul", { - class: "options" - }); - dropdown.adopt(menu.adopt(list.adopt(options))); - var dropdownSelection = new Element("div", { - class: "selection", - events: { - click: this.bound.toggle - } - }); - var dropdownBackground = new Element("div", { - class: "dropdownBackground" - }); - var selection = new Element("span", { - class: "selectionDisplay" - }); - var input = new Element("input", { - type: "text", - id: id, - name: select.get("name"), - events: { - focus: this.bound.focus - } - }); - dropdownSelection.adopt(dropdownBackground, selection, input); - dropdown.adopt(dropdownSelection); - this.element = dropdown; - this.selection = selection; - this.input = input; - return options; - }, - collapse: function(e) { - this.open = false; - this.element.removeClass("active").removeClass("dropdown-active"); - if (this.selected) { - this.selected.removeHighlight(); - } - this.element.removeEvents(this.events); - this.fireEvent("collapse", [ this, e ]); - }, - deselect: function(option) { - option.deselect(); - }, - destroy: function() { - this.element = null; - this.selection = null; - this.input = null; - }, - disable: function() { - this.collapse(); - this.input.set("disabled", "disabled").removeEvents({ - blur: this.bound.blur, - focus: this.bound.focus - }); - this.selection.getParent().removeEvent("click", this.bound.toggle); - this.fireEvent("disable", this); - }, - enable: function() { - this.input.erase("disabled").addEvents({ - blur: this.bound.blur, - focus: this.bound.focus - }); - this.selection.getParent().addEvent("click", this.bound.toggle); - this.fireEvent("enable", this); - }, - expand: function(e) { - clearTimeout(this.collapseInterval); - var evt = e ? e.stop() : null; - this.open = true; - this.input.focus(); - this.element.addClass("active").addClass("dropdown-active"); - if (this.selected) { - this.selected.highlight(); - } - this.element.addEvents(this.events); - this.fireEvent("expand", [ this, e ]); - }, - focus: function(e) { - this.expand(); - }, - foundMatch: function(e) { - var typed = this.typed; - var shortlist = typed.shortlist; - var value = typed.value; - var i = 0; - var optionsLength = shortlist.length; - var excludedValues = this.options.excludedValues; - var found = false; - if (!optionsLength) { - return; - } - var option; - do { - option = shortlist[i]; - if (option.text.toLowerCase().indexOf(value) === 0 && !excludedValues.contains(option.value)) { - found = true; - option.highlight(e); - typed.pressed = i + 1; - i = optionsLength; - } - i = i + 1; - } while (i < optionsLength); - return found; - }, - highlightOption: function(option) { - if (this.highlighted) { - this.highlighted.removeHighlight(); - } - this.highlighted = option; - }, - isOpen: function() { - return this.open; - }, - keydown: function(e) { - if (!this.open) { - return; - } - this.dropdownOptions.each(function(option) { - option.disable(); - }); - document.addEvent("mousemove", this.bound.mousemove); - }, - keypress: function(e) { - if (!this.open) { - return; - } - e.stop(); - var code = e.code, key = e.key; - var typed = this.typed; - var match, i, options, option, optionsLength, found, first, excludedValues, shortlist; - switch (code) { - case 38: - case 37: - if (typed.pressed > 0) { - typed.pressed = typed.pressed - 1; - } - if (!this.highlighted) { - this.dropdownOptions.getLast().highlight(e); - break; - } - match = this.highlighted.element.getPrevious(); - match = match ? match.retrieve("Form.SelectOption::data") : this.dropdownOptions.getLast(); - match.highlight(e); - break; - - case 40: - case 39: - if (typed.shortlist.length > 0) { - typed.pressed = typed.pressed + 1; - } - if (!this.highlighted) { - this.dropdownOptions[0].highlight(e); - break; - } - match = this.highlighted.element.getNext(); - match = match ? match.retrieve("Form.SelectOption::data") : this.dropdownOptions[0]; - match.highlight(e); - break; - - case 13: - e.stop(); - - case 9: - this.highlighted.select(); - break; - - case 27: - e.stop(); - this.toggle(); - break; - - case 32: - default: - if (!(code >= 48 && code <= 122 && (code <= 57 || code >= 65 && code <= 90 || code >= 97) || code === 32)) { - break; - } - if (evt.control || evt.alt || evt.meta) { - return; - } - key = code === 32 ? " " : key; - clearTimeout(typed.timer); - options = this.dropdownOptions; - optionsLength = options.length; - excludedValues = this.options.excludedValues; - if (typed.timer === null) { - typed.shortlist = []; - if (key === typed.lastKey || key === typed.startkey) { - typed.pressed = typed.pressed + 1; - typed.value = key; - } else { - typed = this.resetTyped(); - typed.value = key; - typed.startkey = key; - typed.pressed = 1; - } - typed.timer = this.resetTyped.delay(500, this); - } else { - if (key === typed.lastKey) { - typed.value = typed.value + key; - if (this.foundMatch(e)) { - typed.timer = this.resetTyped.delay(500, this); - break; - } else { - typed.shortlist = []; - typed.value = key; - typed.pressed = typed.pressed + 1; - typed.timer = null; - } - } else { - typed.timer = this.resetTyped.delay(500, this); - typed.value = typed.value + key; - typed.startkey = typed.value.substring(0, 1); - typed.lastKey = key; - this.foundMatch(e); - break; - } - } - typed.lastKey = key; - shortlist = typed.shortlist; - i = 0; - found = 0; - do { - option = options[i]; - if (option.text.toLowerCase().indexOf(key) === 0 && !excludedValues.contains(option.value)) { - if (found === 0) { - first = option; - } - found = found + 1; - if (found === typed.pressed) { - option.highlight(e); - } - shortlist.push(option); - } - i = i + 1; - } while (i < optionsLength); - if (typed.pressed > found) { - first.highlight(e); - typed.pressed = 1; - } - break; - } - }, - mouseenterDropdown: function() { - clearTimeout(this.collapseInterval); - }, - mouseleaveDropdown: function() { - this.collapseInterval = this.options.mouseLeaveDelay ? this.collapse.delay(this.options.mouseLeaveDelay, this) : null; - }, - mousemove: function() { - this.dropdownOptions.each(function(option) { - option.enable(); - }); - document.removeEvent("mousemove", this.bound.mousemove); - }, - removeHighlightOption: function(option) { - this.highlighted = null; - }, - reset: function() { - if (this.options.initialValue) { - this.dropdownOptions.each(function(o) { - if (o.value === this.options.initialValue) { - o.select(); - } - }, this); - } else { - this.dropdownOptions[0].select(); - } - }, - resetTyped: function() { - var typed = this.typed; - typed.value = null; - typed.timer = null; - return typed; - }, - select: function(option, e) { - this.dropdownOptions.each(this.deselect); - this.selection.set("html", option.element.get("html")); - var oldValue = this.value; - this.value = option.value; - this.input.set("value", option.value); - this.selected = option; - this.fireEvent("select", [ this, e ]); - if (oldValue && oldValue !== this.value) { - this.fireEvent("change", [ this, e ]); - } - this.collapse(e); - }, - toggle: function(e) { - if (this.open) { - this.collapse(e); - } else { - this.expand(e); - } - }, - updateOptions: function(optionElements) { - var optionsList = this.element.getElement("ul").empty(), dropdownOptions = this.dropdownOptions.empty(), selectOptions = this.options.selectOptions; - optionElements.each(function(opt) { - var option = new Form.SelectOption(opt, selectOptions); - option.addEvents({ - onHighlight: this.bound.highlightOption, - onRemoveHighlight: this.bound.removeHighlightOption, - onSelect: this.bound.select - }).owner = this; - if (option.value === this.options.initialValue || opt.get("selected")) { - this.select(option); - } - dropdownOptions.push(option); - optionsList.adopt(option.element); - }, this); - if (!this.selected && optionElements[0]) { - optionElements[0].retrieve("Form.SelectOption::data").select(); - } - } -}); - -if (typeof window.Form === "undefined") { - window.Form = {}; -} - -Form.SelectOption = new Class({ - Implements: [ Events, Options ], - options: { - optionTag: "li", - selected: false - }, - config: { - highlightedClass: "highlighted", - optionClass: "option", - selectedClass: "selected" - }, - element: null, - bound: {}, - option: null, - selected: false, - text: null, - value: null, - initialize: function(option, options) { - this.setOptions(options); - option = $(option); - this.option = option; - this.bound = { - deselect: this.deselect.bind(this), - highlight: this.highlight.bind(this), - removeHighlight: this.removeHighlight.bind(this), - select: this.select.bind(this) - }; - this.text = option.get("text"); - this.value = option.get("value"); - this.element = new Element(this.options.optionTag, { - class: (option.get("class") + " " + this.config.optionClass).trim(), - html: option.get("html"), - events: { - click: this.bound.select, - mouseenter: this.bound.highlight, - mouseleave: this.bound.removeHighlight - } - }); - this.element.store("Form.SelectOption::data", this); - option.store("Form.SelectOption::data", this); - }, - deselect: function(e) { - this.fireEvent("onDeselect", [ this, e ]); - this.element.removeClass(this.config.selectedClass).addEvent("click", this.bound.select); - this.selected = false; - }, - destroy: function() { - this.element = null; - this.bound = null; - this.option = null; - }, - disable: function() { - this.element.removeEvents({ - mouseenter: this.bound.highlight, - mouseleave: this.bound.removeHighlight - }); - this.fireEvent("onDisable", this); - }, - enable: function() { - this.element.addEvents({ - mouseenter: this.bound.highlight, - mouseleave: this.bound.removeHighlight - }); - this.fireEvent("onEnable", this); - }, - highlight: function(e) { - this.fireEvent("onHighlight", [ this, e ]); - this.element.addClass(this.config.highlightedClass); - return this; - }, - removeHighlight: function(e) { - this.fireEvent("onRemoveHighlight", [ this, e ]); - this.element.removeClass(this.config.highlightedClass); - return this; - }, - select: function(e) { - this.fireEvent("onSelect", [ this, e ]); - this.element.addClass(this.config.selectedClass).removeEvent("click", this.bound.select); - this.selected = true; - } -}); - (function() { var defaultSortFunction = function(a, b) { return a > b ? 1 : a < b ? -1 : 0; diff --git a/couchpotato/static/scripts/page/settings.js b/couchpotato/static/scripts/page/settings.js index ee96ea8..9620637 100644 --- a/couchpotato/static/scripts/page/settings.js +++ b/couchpotato/static/scripts/page/settings.js @@ -113,7 +113,7 @@ Page.Settings = new Class({ Cookie.write('advanced_toggle_checked', +self.advanced_toggle.checked, {'duration': 365}); }, - sortByOrder: function(a, b){ + sortByOrder: function(a, b){ return (a.order || 100) - (b.order || 100); }, diff --git a/couchpotato/static/scripts/vendor/form_replacement/form_check.js b/couchpotato/static/scripts/vendor/form_replacement/form_check.js deleted file mode 100644 index 4c240f6..0000000 --- a/couchpotato/static/scripts/vendor/form_replacement/form_check.js +++ /dev/null @@ -1,126 +0,0 @@ -/* ---- -name: Form.Check -description: Class to represent a checkbox -authors: Bryan J Swift (@bryanjswift) -license: MIT-style license. -requires: [Core/Class.Extras, Core/Element, Core/Element.Event] -provides: Form.Check -... -*/ -if (typeof window.Form === 'undefined') { window.Form = {}; } - -Form.Check = new Class({ - Implements: [Events, Options], - options: { - checked: false, - disabled: false - }, - bound: {}, - checked: false, - config: { - checkedClass: 'checked', - disabledClass: 'disabled', - elementClass: 'check', - highlightedClass: 'highlighted', - storage: 'Form.Check::data' - }, - disabled: false, - element: null, - input: null, - label: null, - value: null, - initialize: function(input, options) { - this.setOptions(options); - this.bound = { - disable: this.disable.bind(this), - enable: this.enable.bind(this), - highlight: this.highlight.bind(this), - removeHighlight: this.removeHighlight.bind(this), - keyToggle: this.keyToggle.bind(this), - toggle: this.toggle.bind(this) - }; - var bound = this.bound; - input = this.input = $(input); - var id = input.get('id'); - this.label = document.getElement('label[for=' + id + ']'); - this.element = new Element('div', { - 'class': input.get('class') + ' ' + this.config.elementClass, - id: id ? id + 'Check' : '', - events: { - click: bound.toggle, - mouseenter: bound.highlight, - mouseleave: bound.removeHighlight - } - }); - this.input.addEvents({ - keypress: bound.keyToggle, - keydown: bound.keyToggle, - keyup: bound.keyToggle - }); - if (this.label) { this.label.addEvent('click', bound.toggle); } - this.element.wraps(input); - this.value = input.get('value'); - if (this.input.checked) { this.check(); } else { this.uncheck(); } - if (this.input.disabled) { this.disable(); } else { this.enable(); } - input.store(this.config.storage, this).addEvents({ - blur: bound.removeHighlight, - focus: bound.highlight - }); - this.fireEvent('create', this); - }, - check: function() { - this.element.addClass(this.config.checkedClass); - this.input.set('checked', 'checked').focus(); - this.checked = true; - this.fireEvent('check', this); - }, - disable: function() { - this.element.addClass(this.config.disabledClass); - this.input.set('disabled', 'disabled'); - this.disabled = true; - this.fireEvent('disable', this); - }, - enable: function() { - this.element.removeClass(this.config.disabledClass); - this.input.erase('disabled'); - this.disabled = false; - this.fireEvent('enable', this); - }, - highlight: function() { - this.element.addClass(this.config.highlightedClass); - this.fireEvent('highlight', this); - }, - removeHighlight: function() { - this.element.removeClass(this.config.highlightedClass); - this.fireEvent('removeHighlight', this); - }, - keyToggle: function(e) { - var evt = (e); - if (evt.key === 'space') { this.toggle(e); } - }, - toggle: function(e) { - var evt; - if (this.disabled) { return this; } - if (e) { - evt = (e).stopPropagation(); - if (evt.target.tagName.toLowerCase() !== 'a') { - evt.stop(); - } - } - if (this.checked) { - this.uncheck(); - } else { - this.check(); - } - this.fireEvent('change', this); - this.input.fireEvent('change', this); - return this; - }, - uncheck: function() { - this.element.removeClass(this.config.checkedClass); - this.input.erase('checked'); - this.checked = false; - this.fireEvent('uncheck', this); - } -}); diff --git a/couchpotato/static/scripts/vendor/form_replacement/form_checkgroup.js b/couchpotato/static/scripts/vendor/form_replacement/form_checkgroup.js deleted file mode 100644 index 002fd6e..0000000 --- a/couchpotato/static/scripts/vendor/form_replacement/form_checkgroup.js +++ /dev/null @@ -1,51 +0,0 @@ -/* ---- -name: Form.CheckGroup -description: Class to represent a group of Form.Check wrapped checkboxes -authors: Bryan J Swift (@bryanjswift) -license: MIT-style license. -requires: [Core/Class.Extras, Core/Element, Core/Element.Event, Form-Replacement/Form.Check] -provides: Form.CheckGroup -... -*/ -if (typeof window.Form === 'undefined') { window.Form = {}; } - -Form.CheckGroup = new Class({ - Implements: [Events,Options], - options: { - checkOptions: {}, - initialValues: {} - }, - checks: [], - initialize: function(group,options) { - if (!Form.Check) { throw 'required Class Form.Check not found'; } - this.setOptions(options); - group = $(group); - if (!group) { return this; } - var checkboxes = group.getElements('input[type=checkbox]'); - checkboxes.each(this.addCheck,this); - }, - addCheck: function(checkbox) { - var initialValues = this.options.initialValues[checkbox.get('name')]; - var checkOptions = {}; - checkOptions.checked = initialValues ? initialValues.contains(checkbox.get('value')) : checkbox.get('checked'); - checkOptions.disabled = checkbox.get('disabled'); - checkbox.store('Form.CheckGroup::data',this); - var check = checkbox.retrieve('Form.Check::data') || new Form.Check(checkbox, Object.append(checkOptions,this.options.checkOptions)); - this.checks.push(check); - }, - checkAll: function() { - this.checks.each(function(check) { if (!check.checked) { check.toggle(); } }); - }, - disable: function() { - this.checks.each(function(check) { check.disable(); }); - this.fireEvent('disable',this); - }, - enable: function() { - this.checks.each(function(check) { check.enable(); }); - this.fireEvent('enable',this); - }, - uncheckAll: function() { - this.checks.each(function(check) { if (check.checked) { check.toggle(); } }); - } -}); diff --git a/couchpotato/static/scripts/vendor/form_replacement/form_dropdown.js b/couchpotato/static/scripts/vendor/form_replacement/form_dropdown.js deleted file mode 100644 index 86c2c3c..0000000 --- a/couchpotato/static/scripts/vendor/form_replacement/form_dropdown.js +++ /dev/null @@ -1,325 +0,0 @@ -/* ---- -name: Form.Dropdown -description: Class to represent a select input -authors: Bryan J Swift (@bryanjswift) -license: MIT-style license. -requires: [Core/Class.Extras, Core/Element, Core/Element.Event, Form-Replacement/Form.SelectOption] -provides: Form.Dropdown -... -*/ -if (typeof window.Form === 'undefined') { window.Form = {}; } - -Form.Dropdown = new Class({ - Implements: [Events,Options], - options: { - excludedValues: [], - initialValue: null, - mouseLeaveDelay: 350, - selectOptions: {}, - typeDelay: 500 - }, - bound: {}, - dropdownOptions: [], - element: null, - events: {}, - highlighted: null, - input: null, - open: true, - selected: null, - selection: null, - typed: { lastKey: null, value: null, timer: null, pressed: null, shortlist: [], startkey: null }, - value: null, - initialize: function(select,options) { - this.setOptions(options); - select = $(select); - this.bound = { - collapse: this.collapse.bind(this), - expand: this.expand.bind(this), - focus: this.focus.bind(this), - highlightOption: this.highlightOption.bind(this), - keydown: this.keydown.bind(this), - keypress: this.keypress.bind(this), - mouseenterDropdown: this.mouseenterDropdown.bind(this), - mouseleaveDropdown: this.mouseleaveDropdown.bind(this), - mousemove: this.mousemove.bind(this), - removeHighlightOption: this.removeHighlightOption.bind(this), - select: this.select.bind(this), - toggle: this.toggle.bind(this) - }; - this.events = { mouseenter: this.bound.mouseenterDropdown, mouseleave: this.bound.mouseleaveDropdown }; - this.value = this.options.initialValue; - this.initializeCreateElements(select); - var optionElements = select.getElements('option'); - this.updateOptions(optionElements); - this.element.replaces(select); - document.addEvent('click', this.bound.collapse); - var eventName = Browser.ie || Browser.webkit ? 'keydown' : 'keypress'; - var target = Browser.ie ? $(document.body) : window; - target.addEvent('keydown',this.bound.keydown).addEvent(eventName,this.bound.keypress); - }, - initializeCreateElements: function(select) { - var id = select.get('id'); - var dropdown = new Element('div', { - 'class': (select.get('class') + ' select').trim(), - 'id': (id && id !== '') ? id + 'Dropdown' : '' - }); - var menu = new Element('div', {'class': 'menu'}); - var list = new Element('div', {'class': 'list'}); - var options = new Element('ul', {'class': 'options'}); - dropdown.adopt(menu.adopt(list.adopt(options))); - var dropdownSelection = new Element('div', { - 'class': 'selection', - events: {click: this.bound.toggle} - }); - var dropdownBackground = new Element('div', { 'class': 'dropdownBackground' }); - var selection = new Element('span', { 'class': 'selectionDisplay' }); - var input = new Element('input', { - type:'text', - id: id, - name: select.get('name'), - events: { - focus: this.bound.focus - } - }); - dropdownSelection.adopt(dropdownBackground, selection, input); - dropdown.adopt(dropdownSelection); - this.element = dropdown; - this.selection = selection; - this.input = input; - return options; - }, - collapse: function(e) { - this.open = false; - this.element.removeClass('active').removeClass('dropdown-active'); - if (this.selected) { this.selected.removeHighlight(); } - this.element.removeEvents(this.events); - this.fireEvent('collapse', [this, e]); - }, - deselect: function(option) { - option.deselect(); - }, - destroy: function() { - this.element = null; - this.selection = null; - this.input = null; - }, - disable: function() { - this.collapse(); - this.input.set('disabled', 'disabled').removeEvents({blur:this.bound.blur, focus:this.bound.focus}); - this.selection.getParent().removeEvent('click', this.bound.toggle); - this.fireEvent('disable', this); - }, - enable: function() { - this.input.erase('disabled').addEvents({blur:this.bound.blur, focus:this.bound.focus}); - this.selection.getParent().addEvent('click', this.bound.toggle); - this.fireEvent('enable', this); - }, - expand: function(e) { - clearTimeout(this.collapseInterval); - var evt = e ? (e).stop() : null; - this.open = true; - this.input.focus(); - this.element.addClass('active').addClass('dropdown-active'); - if (this.selected) { this.selected.highlight(); } - this.element.addEvents(this.events); - this.fireEvent('expand', [this, e]); - }, - focus: function(e) { - this.expand(); - }, - foundMatch: function(e) { - var typed = this.typed; - var shortlist = typed.shortlist; - var value = typed.value; - var i = 0; - var optionsLength = shortlist.length; - var excludedValues = this.options.excludedValues; - var found = false; - if (!optionsLength) { return; } - var option; - do { - option = shortlist[i]; - if (option.text.toLowerCase().indexOf(value) === 0 && !excludedValues.contains(option.value)) { - found = true; - option.highlight(e); - typed.pressed = i + 1; - i = optionsLength; - } - i = i + 1; - } while(i < optionsLength); - return found; - }, - highlightOption: function(option) { - if (this.highlighted) { this.highlighted.removeHighlight(); } - this.highlighted = option; - }, - isOpen: function() { - return this.open; - }, - keydown: function(e) { - if (!this.open) { return; } - this.dropdownOptions.each(function(option) { option.disable(); }); - document.addEvent('mousemove', this.bound.mousemove); - }, - keypress: function(e) { - if (!this.open) { return; } - (e).stop(); - - var code = e.code, key = e.key; - - var typed = this.typed; - var match, i, options, option, optionsLength, found, first, excludedValues, shortlist; - switch(code) { - case 38: // up - case 37: // left - if (typed.pressed > 0) { typed.pressed = typed.pressed - 1; } - if (!this.highlighted) { this.dropdownOptions.getLast().highlight(e); break; } - match = this.highlighted.element.getPrevious(); - match = match ? match.retrieve('Form.SelectOption::data') : this.dropdownOptions.getLast(); - match.highlight(e); - break; - case 40: // down - case 39: // right - if (typed.shortlist.length > 0) { typed.pressed = typed.pressed + 1; } - if (!this.highlighted) { this.dropdownOptions[0].highlight(e); break; } - match = this.highlighted.element.getNext(); - match = match ? match.retrieve('Form.SelectOption::data') : this.dropdownOptions[0]; - match.highlight(e); - break; - case 13: // enter - e.stop(); - case 9: // tab - skips the stop event but selects the item - this.highlighted.select(); - break; - case 27: // esc - e.stop(); - this.toggle(); - break; - case 32: // space - default: // anything else - if (!(code >= 48 && code <= 122 && (code <= 57 || (code >= 65 && code <= 90) || code >=97) || code === 32)) { - break; - } - if (evt.control || evt.alt || evt.meta) { return; } - // alphanumeric or space - key = code === 32 ? ' ' : key; - clearTimeout(typed.timer); - options = this.dropdownOptions; - optionsLength = options.length; - excludedValues = this.options.excludedValues; - if (typed.timer === null) { // timer is expired - typed.shortlist = []; - if (key === typed.lastKey || key === typed.startkey) { // get next - typed.pressed = typed.pressed + 1; - typed.value = key; - } else { // get first - typed = this.resetTyped(); - typed.value = key; - typed.startkey = key; - typed.pressed = 1; - } - typed.timer = this.resetTyped.delay(500, this); - } else { - if (key === typed.lastKey) { // check for match, if no match get next - typed.value = typed.value + key; - if (this.foundMatch(e)) { // got a match so break - typed.timer = this.resetTyped.delay(500, this); - break; - } else { // no match fall through - typed.shortlist = []; - typed.value = key; - typed.pressed = typed.pressed + 1; - typed.timer = null; - } - } else { // reset timer, get first match, set pressed to found position - typed.timer = this.resetTyped.delay(500, this); - typed.value = typed.value + key; - typed.startkey = typed.value.substring(0, 1); - typed.lastKey = key; - this.foundMatch(e); - break; - } - } - typed.lastKey = key; - shortlist = typed.shortlist; - i = 0; - found = 0; - do { - option = options[i]; - if (option.text.toLowerCase().indexOf(key) === 0 && !excludedValues.contains(option.value)) { - if (found === 0) { first = option; } - found = found + 1; - if (found === typed.pressed) { option.highlight(e); } - shortlist.push(option); - } - i = i + 1; - } while(i < optionsLength); - if (typed.pressed > found) { - first.highlight(e); - typed.pressed = 1; - } - break; - } - }, - mouseenterDropdown: function() { - clearTimeout(this.collapseInterval); - }, - mouseleaveDropdown: function() { - this.collapseInterval = this.options.mouseLeaveDelay ? this.collapse.delay(this.options.mouseLeaveDelay,this) : null; - }, - mousemove: function() { - this.dropdownOptions.each(function(option) { option.enable(); }); - document.removeEvent('mousemove', this.bound.mousemove); - }, - removeHighlightOption: function(option) { - this.highlighted = null; - }, - reset: function() { - if (this.options.initialValue) { - this.dropdownOptions.each(function(o) { - if (o.value === this.options.initialValue) { o.select(); } - }, this); - } else { - this.dropdownOptions[0].select(); - } - }, - resetTyped: function() { - var typed = this.typed; - typed.value = null; - typed.timer = null; - return typed; - }, - select: function(option, e) { - this.dropdownOptions.each(this.deselect); - this.selection.set('html', option.element.get('html')); - var oldValue = this.value; - this.value = option.value; - this.input.set('value', option.value); - this.selected = option; - this.fireEvent('select', [this, e]); - if (oldValue && oldValue !== this.value) { this.fireEvent('change', [this, e]); } - this.collapse(e); - }, - toggle: function(e) { - if (this.open) { this.collapse(e); } - else { this.expand(e); } - }, - updateOptions: function(optionElements) { - var optionsList = this.element.getElement('ul').empty(), - dropdownOptions = this.dropdownOptions.empty(), - selectOptions = this.options.selectOptions; - optionElements.each(function(opt) { - var option = new Form.SelectOption(opt, selectOptions); - option.addEvents({ - 'onHighlight':this.bound.highlightOption, - 'onRemoveHighlight':this.bound.removeHighlightOption, - 'onSelect':this.bound.select - }).owner = this; - if (option.value === this.options.initialValue || opt.get('selected')) { this.select(option); } - dropdownOptions.push(option); - optionsList.adopt(option.element); - }, this); - if (!this.selected && optionElements[0]) { optionElements[0].retrieve('Form.SelectOption::data').select(); } - } -}); diff --git a/couchpotato/static/scripts/vendor/form_replacement/form_radio.js b/couchpotato/static/scripts/vendor/form_replacement/form_radio.js deleted file mode 100644 index 245aa4d..0000000 --- a/couchpotato/static/scripts/vendor/form_replacement/form_radio.js +++ /dev/null @@ -1,34 +0,0 @@ -/* ---- -name: Form.Radio -description: Class to represent a radio button -authors: Bryan J Swift (@bryanjswift) -license: MIT-style license. -requires: [Core/Class.Extras, Core/Element, Core/Element.Event, Form-Replacement/Form.Check] -provides: Form.Radio -... -*/ -if (typeof window.Form === 'undefined') { window.Form = {}; } - -Form.Radio = new Class({ - Extends: Form.Check, - config: { - elementClass: 'radio', - storage: 'Form.Radio::data' - }, - initialize: function(input,options) { - this.parent(input,options); - }, - toggle: function(e) { - if (this.element.hasClass('checked') || this.disabled) { return; } - var evt; - if (e) { evt = (e).stop(); } - if (this.checked) { - this.uncheck(); - } else { - this.check(); - } - this.fireEvent(this.checked ? 'onCheck' : 'onUncheck',this); - this.fireEvent('onChange',this); - } -}); \ No newline at end of file diff --git a/couchpotato/static/scripts/vendor/form_replacement/form_radiogroup.js b/couchpotato/static/scripts/vendor/form_replacement/form_radiogroup.js deleted file mode 100644 index f0000b5..0000000 --- a/couchpotato/static/scripts/vendor/form_replacement/form_radiogroup.js +++ /dev/null @@ -1,59 +0,0 @@ -/* ---- -name: Form.RadioGroup -description: Class to represent a group of Form.Radio buttons -authors: Bryan J Swift (@bryanjswift) -license: MIT-style license. -requires: [Core/Class.Extras, Core/Element, Core/Element.Event, Form-Replacement/Form.Radio] -provides: Form.RadioGroup -... -*/ -if (typeof window.Form === 'undefined') { window.Form = {}; } - -Form.RadioGroup = new Class({ - Implements: [Events,Options], - options: { - radioOptions: {}, - initialValues: {} - }, - bound: {}, - radios: [], - value: null, - initialize: function(group,options) { - if (!Form.Radio) { throw 'required Class Form.Radio not found'; } - this.setOptions(options); - this.bound = { select: this.select.bind(this) }; - group = $(group); - if (!group) { return this; } - var radios = group.getElements('input[type=radio]'); - radios.each(this.addCheck,this); - }, - addCheck: function(radio,i) { - var initialValues = this.options.initialValues[radio.get('name')]; - var radioOptions = {}; - radioOptions.checked = initialValues ? initialValues.contains(radio.get('value')) : radio.get('checked'); - radioOptions.disabled = radio.get('disabled'); - var check = (radio.retrieve('Form.Radio::data') - || new Form.Radio(radio,Object.append(radioOptions,this.options.radioOptions))); - check.addEvent('onCheck',this.bound.select); - if (check.checked) { i ? this.changed(check) : this.value = check.value; } - radio.store('Form.RadioGroup::data',this); - this.radios.push(check); - }, - changed: function(radio) { - this.value = radio.value; - this.fireEvent('onChange',this); - }, - disable: function() { - this.radios.each(function(radio) { radio.disable(); }); - }, - enable: function() { - this.radios.each(function(radio) { radio.enable(); }); - }, - select: function(checkedRadio) { - this.radios.each(function(radio) { - if (radio.checked && radio.value !== checkedRadio.value) { radio.uncheck(); } - }); - if (checkedRadio.value !== this.value) { this.changed(checkedRadio); } - } -}); diff --git a/couchpotato/static/scripts/vendor/form_replacement/form_selectoption.js b/couchpotato/static/scripts/vendor/form_replacement/form_selectoption.js deleted file mode 100644 index 2b81140..0000000 --- a/couchpotato/static/scripts/vendor/form_replacement/form_selectoption.js +++ /dev/null @@ -1,93 +0,0 @@ -/* ---- -name: Form.SelectOption -description: Class to represent an option for Form.Dropdown -authors: Bryan J Swift (@bryanjswift) -license: MIT-style license. -requires: [Core/Class.Extras, Core/Element, Core/Element.Event] -provides: Form.SelectOption -... -*/ -if (typeof window.Form === 'undefined') { window.Form = {}; } - -Form.SelectOption = new Class({ - Implements: [Events, Options], - options: { - optionTag: 'li', - selected: false - }, - config: { - highlightedClass: 'highlighted', - optionClass: 'option', - selectedClass: 'selected' - }, - element: null, - bound: {}, - option: null, - selected: false, - text: null, - value: null, - initialize: function(option, options) { - this.setOptions(options); - option = $(option); - this.option = option; - this.bound = { - deselect: this.deselect.bind(this), - highlight: this.highlight.bind(this), - removeHighlight: this.removeHighlight.bind(this), - select: this.select.bind(this) - }; - this.text = option.get('text'); - this.value = option.get('value'); - this.element = new Element(this.options.optionTag, { - 'class': (option.get('class') + ' ' + this.config.optionClass).trim(), - 'html': option.get('html'), - 'events': { - click: this.bound.select, - mouseenter: this.bound.highlight, - mouseleave: this.bound.removeHighlight - } - }); - this.element.store('Form.SelectOption::data', this); - option.store('Form.SelectOption::data', this); - }, - deselect: function(e) { - this.fireEvent('onDeselect', [this, e]); - this.element.removeClass(this.config.selectedClass).addEvent('click', this.bound.select); - this.selected = false; - }, - destroy: function() { - this.element = null; - this.bound = null; - this.option = null; - }, - disable: function() { - this.element.removeEvents({ - mouseenter: this.bound.highlight, - mouseleave: this.bound.removeHighlight - }); - this.fireEvent('onDisable', this); - }, - enable: function() { - this.element.addEvents({ - mouseenter: this.bound.highlight, - mouseleave: this.bound.removeHighlight - }); - this.fireEvent('onEnable', this); - }, - highlight: function(e) { - this.fireEvent('onHighlight', [this, e]); - this.element.addClass(this.config.highlightedClass); - return this; - }, - removeHighlight: function(e) { - this.fireEvent('onRemoveHighlight', [this, e]); - this.element.removeClass(this.config.highlightedClass); - return this; - }, - select: function(e) { - this.fireEvent('onSelect', [this, e]); - this.element.addClass(this.config.selectedClass).removeEvent('click', this.bound.select); - this.selected = true; - } -}); From 2e92034204d497da50af530971c49658411a8337 Mon Sep 17 00:00:00 2001 From: Ruud Date: Tue, 11 Aug 2015 23:37:51 +0200 Subject: [PATCH 246/301] Optimize movie inject --- couchpotato/core/media/movie/_base/static/list.js | 83 ++++---- couchpotato/core/media/movie/_base/static/movie.js | 164 ++++++++-------- couchpotato/static/scripts/block.js | 2 +- couchpotato/static/scripts/block/menu.js | 1 + couchpotato/static/scripts/combined.base.min.js | 3 +- couchpotato/static/scripts/combined.plugins.min.js | 214 ++++++++++----------- 6 files changed, 238 insertions(+), 229 deletions(-) diff --git a/couchpotato/core/media/movie/_base/static/list.js b/couchpotato/core/media/movie/_base/static/list.js index 2644304..c28baf5 100644 --- a/couchpotato/core/media/movie/_base/static/list.js +++ b/couchpotato/core/media/movie/_base/static/list.js @@ -4,7 +4,6 @@ var MovieList = new Class({ options: { api_call: 'media.list', - animated_in: false, navigation: true, limit: 50, load_more: true, @@ -39,7 +38,28 @@ var MovieList = new Class({ 'html': self.options.description, 'styles': {'display': 'none'} }) : null, - self.movie_list = new Element('div'), + self.movie_list = new Element('div', { + 'events': { + 'click:relay(.movie)': function(e, el){ + el.retrieve('klass').onClick(e); + }, + 'mouseenter:relay(.movie)': function(e, el){ + el.retrieve('klass').onMouseenter(e); + }, + 'mouseleave:relay(.movie)': function(e, el){ + el.retrieve('klass').onMouseleave(e); + }, + 'click:relay(.movie .action)': function(e){ + (e).stopPropagation(); + }, + 'change:relay(.movie input)': function(e, el){ + el = el.getParent(); + var klass = el.retrieve('klass'); + klass.fireEvent('select'); + klass.select(klass.select_checkbox.get('checked')); + } + } + }), self.load_more = self.options.load_more ? new Element('a.load_more', { 'events': { 'click': self.loadMore.bind(self) @@ -109,6 +129,7 @@ var MovieList = new Class({ addMovies: function(movies, total){ var self = this; + if(!self.created) self.create(); // do scrollspy @@ -117,9 +138,7 @@ var MovieList = new Class({ self.scrollspy.stop(); } - Object.each(movies, function(movie, nr){ - self.createMovie(movie, 'bottom', nr); - }); + self.createMovie(movies, 'bottom'); self.total_movies += total; self.setCounter(total); @@ -171,41 +190,35 @@ var MovieList = new Class({ createMovie: function(movie, inject_at, nr){ var self = this, - animate = self.options.animated_in && !App.mobile_screen && self.current_view == 'thumb' && nr !== undefined; - var m = new Movie(self, { - 'actions': self.options.actions, - 'view': self.current_view, - 'onSelect': self.calculateSelected.bind(self) - }, movie); - - var el = $(m); - - if(animate) { - dynamics.css(el, { - opacity: 0, - translateY: 150 - }); - } + movies = Array.isArray(movie) ? movie : [movie], + movie_els = []; + inject_at = inject_at || 'bottom'; - el.inject(self.movie_list, inject_at || 'bottom'); + movies.each(function(movie, nr){ - m.fireEvent('injected'); + var m = new Movie(self, { + 'actions': self.options.actions, + 'view': self.current_view, + 'onSelect': self.calculateSelected.bind(self) + }, movie); - self.movies.include(m); - self.movies_added[movie._id] = true; + var el = $(m); - if(animate){ - dynamics.animate(el, { - opacity: 1, - translateY: 0 - }, { - type: dynamics.spring, - frequency: 200, - friction: 300, - duration: 1200, - delay: 100 + (nr * 20) - }); + if(inject_at === 'bottom'){ + movie_els.push(el); + } + else { + el.inject(self.movie_list, inject_at); + } + + self.movies.include(m); + self.movies_added[movie._id] = true; + }); + + if(movie_els.length > 0){ + $(self.movie_list).adopt(movie_els); } + }, createNavigation: function(){ diff --git a/couchpotato/core/media/movie/_base/static/movie.js b/couchpotato/core/media/movie/_base/static/movie.js index 9c4a07e..9d0a893 100644 --- a/couchpotato/core/media/movie/_base/static/movie.js +++ b/couchpotato/core/media/movie/_base/static/movie.js @@ -1,6 +1,7 @@ var Movie = new Class({ Extends: BlockBase, + Implements: [Options, Events], actions: null, details: null, @@ -12,75 +13,10 @@ var Movie = new Class({ self.data = data; self.list = list; - var buttons = []; + self.buttons = []; - self.el = new Element('a.movie', { - 'events': { - 'click': function(e){ - if(e.target.get('tag') != 'input'){ - (e).preventDefault(); - self.openDetails(); - } - }, - 'mouseenter': function(){ - if(self.actions.length <= 0){ - self.options.actions.each(function(a){ - var action = new a(self), - button = action.getButton(); - if(button){ - self.actions_el.grab(button); - buttons.push(button); - } - - self.actions.push(action); - }); - } - - if(App.mobile_screen) return; - - if(list.current_view == 'thumb'){ - dynamics.css(self.el, { - scale: 1, - opacity: 1 - }); - - dynamics.animate(self.el, { - scale: 0.9 - }, { type: dynamics.bounce }); - - buttons.each(function(el, nr){ - - dynamics.css(el, { - opacity: 0, - translateY: 50 - }); - - dynamics.animate(el, { - opacity: 1, - translateY: 0 - }, { - type: dynamics.spring, - frequency: 200, - friction: 300, - duration: 800, - delay: 100 + (nr * 40) - }); - - }); - } - }, - 'mouseleave': function(){ - if(App.mobile_screen) return; - - if(list.current_view == 'thumb'){ - dynamics.animate(self.el, { - scale: 1 - }, { type: dynamics.spring }); - } - - } - } - }); + self.el = new Element('a.movie'); + self.el.store('klass', self); self.profile = Quality.getProfile(data.profile_id) || {}; self.category = CategoryList.getCategory(data.category_id) || {}; @@ -310,24 +246,11 @@ var Movie = new Class({ } self.el.adopt( - self.select_checkbox = new Element('input[type=checkbox]', { - 'events': { - 'change': function(){ - self.fireEvent('select'); - self.select(self.select_checkbox.get('checked')); - } - } - }), + self.select_checkbox = new Element('input[type=checkbox]'), self.thumbnail = thumbnail.grab( - self.actions_el = new Element('div.actions', { - 'events': { - 'click:relay(.action)': function(e){ - (e).stopPropagation(); - } - } - }) + self.actions_el = new Element('div.actions') ), - self.data_container = new Element('div.data.light').adopt( + self.data_container = new Element('div.data.light').grab( self.info_container = new Element('div.info').adopt( new Element('div.title').adopt( self.title = new Element('span', { @@ -370,6 +293,79 @@ var Movie = new Class({ }, + + onClick: function(e){ + var self = this; + + if(e.target.get('tag') != 'input'){ + (e).preventDefault(); + self.openDetails(); + } + }, + + onMouseenter: function(){ + var self = this; + + if(self.actions.length <= 0){ + self.options.actions.each(function(a){ + var action = new a(self), + button = action.getButton(); + if(button){ + self.actions_el.grab(button); + self.buttons.push(button); + } + + self.actions.push(action); + }); + } + + if(App.mobile_screen) return; + + if(self.list.current_view == 'thumb'){ + dynamics.css(self.el, { + scale: 1, + opacity: 1 + }); + + dynamics.animate(self.el, { + scale: 0.9 + }, { type: dynamics.bounce }); + + self.buttons.each(function(el, nr){ + + dynamics.css(el, { + opacity: 0, + translateY: 50 + }); + + dynamics.animate(el, { + opacity: 1, + translateY: 0 + }, { + type: dynamics.spring, + frequency: 200, + friction: 300, + duration: 800, + delay: 100 + (nr * 40) + }); + + }); + } + }, + + onMouseleave: function(){ + var self = this; + + if(App.mobile_screen) return; + + if(self.list.current_view == 'thumb'){ + dynamics.animate(self.el, { + scale: 1 + }, { type: dynamics.spring }); + } + + }, + updateReleases: function(){ var self = this; if(!self.data.releases || self.data.releases.length === 0) return; diff --git a/couchpotato/static/scripts/block.js b/couchpotato/static/scripts/block.js index dce3845..6f18bae 100644 --- a/couchpotato/static/scripts/block.js +++ b/couchpotato/static/scripts/block.js @@ -1,6 +1,6 @@ var BlockBase = new Class({ - Implements: [Options, Events], + Implements: [Options], options: {}, diff --git a/couchpotato/static/scripts/block/menu.js b/couchpotato/static/scripts/block/menu.js index 650e687..b800e5a 100644 --- a/couchpotato/static/scripts/block/menu.js +++ b/couchpotato/static/scripts/block/menu.js @@ -1,6 +1,7 @@ var BlockMenu = new Class({ Extends: BlockBase, + Implements: [Options, Events], options: { 'class': 'menu' diff --git a/couchpotato/static/scripts/combined.base.min.js b/couchpotato/static/scripts/combined.base.min.js index e7bf473..26bfc22 100644 --- a/couchpotato/static/scripts/combined.base.min.js +++ b/couchpotato/static/scripts/combined.base.min.js @@ -779,7 +779,7 @@ var PageBase = new Class({ var Page = {}; var BlockBase = new Class({ - Implements: [ Options, Events ], + Implements: [ Options ], options: {}, initialize: function(parent, options) { var self = this; @@ -866,6 +866,7 @@ var BlockFooter = new Class({ var BlockMenu = new Class({ Extends: BlockBase, + Implements: [ Options, Events ], options: { class: "menu" }, diff --git a/couchpotato/static/scripts/combined.plugins.min.js b/couchpotato/static/scripts/combined.plugins.min.js index 2ccba96..2673fed 100644 --- a/couchpotato/static/scripts/combined.plugins.min.js +++ b/couchpotato/static/scripts/combined.plugins.min.js @@ -423,7 +423,6 @@ var MovieList = new Class({ Implements: [ Events, Options ], options: { api_call: "media.list", - animated_in: false, navigation: true, limit: 50, load_more: true, @@ -455,7 +454,28 @@ var MovieList = new Class({ styles: { display: "none" } - }) : null, self.movie_list = new Element("div"), self.load_more = self.options.load_more ? new Element("a.load_more", { + }) : null, self.movie_list = new Element("div", { + events: { + "click:relay(.movie)": function(e, el) { + el.retrieve("klass").onClick(e); + }, + "mouseenter:relay(.movie)": function(e, el) { + el.retrieve("klass").onMouseenter(e); + }, + "mouseleave:relay(.movie)": function(e, el) { + el.retrieve("klass").onMouseleave(e); + }, + "click:relay(.movie .action)": function(e) { + e.stopPropagation(); + }, + "change:relay(.movie input)": function(e, el) { + el = el.getParent(); + var klass = el.retrieve("klass"); + klass.fireEvent("select"); + klass.select(klass.select_checkbox.get("checked")); + } + } + }), self.load_more = self.options.load_more ? new Element("a.load_more", { events: { click: self.loadMore.bind(self) } @@ -510,9 +530,7 @@ var MovieList = new Class({ self.load_more.hide(); self.scrollspy.stop(); } - Object.each(movies, function(movie, nr) { - self.createMovie(movie, "bottom", nr); - }); + self.createMovie(movies, "bottom"); self.total_movies += total; self.setCounter(total); self.calculateSelected(); @@ -549,34 +567,25 @@ var MovieList = new Class({ } }, createMovie: function(movie, inject_at, nr) { - var self = this, animate = self.options.animated_in && !App.mobile_screen && self.current_view == "thumb" && nr !== undefined; - var m = new Movie(self, { - actions: self.options.actions, - view: self.current_view, - onSelect: self.calculateSelected.bind(self) - }, movie); - var el = $(m); - if (animate) { - dynamics.css(el, { - opacity: 0, - translateY: 150 - }); - } - el.inject(self.movie_list, inject_at || "bottom"); - m.fireEvent("injected"); - self.movies.include(m); - self.movies_added[movie._id] = true; - if (animate) { - dynamics.animate(el, { - opacity: 1, - translateY: 0 - }, { - type: dynamics.spring, - frequency: 200, - friction: 300, - duration: 1200, - delay: 100 + nr * 20 - }); + var self = this, movies = Array.isArray(movie) ? movie : [ movie ], movie_els = []; + inject_at = inject_at || "bottom"; + movies.each(function(movie, nr) { + var m = new Movie(self, { + actions: self.options.actions, + view: self.current_view, + onSelect: self.calculateSelected.bind(self) + }, movie); + var el = $(m); + if (inject_at === "bottom") { + movie_els.push(el); + } else { + el.inject(self.movie_list, inject_at); + } + self.movies.include(m); + self.movies_added[movie._id] = true; + }); + if (movie_els.length > 0) { + $(self.movie_list).adopt(movie_els); } }, createNavigation: function() { @@ -1742,6 +1751,7 @@ MA.MarkAsDone = new Class({ var Movie = new Class({ Extends: BlockBase, + Implements: [ Options, Events ], actions: null, details: null, initialize: function(list, options, data) { @@ -1749,67 +1759,9 @@ var Movie = new Class({ self.actions = []; self.data = data; self.list = list; - var buttons = []; - self.el = new Element("a.movie", { - events: { - click: function(e) { - if (e.target.get("tag") != "input") { - e.preventDefault(); - self.openDetails(); - } - }, - mouseenter: function() { - if (self.actions.length <= 0) { - self.options.actions.each(function(a) { - var action = new a(self), button = action.getButton(); - if (button) { - self.actions_el.grab(button); - buttons.push(button); - } - self.actions.push(action); - }); - } - if (App.mobile_screen) return; - if (list.current_view == "thumb") { - dynamics.css(self.el, { - scale: 1, - opacity: 1 - }); - dynamics.animate(self.el, { - scale: .9 - }, { - type: dynamics.bounce - }); - buttons.each(function(el, nr) { - dynamics.css(el, { - opacity: 0, - translateY: 50 - }); - dynamics.animate(el, { - opacity: 1, - translateY: 0 - }, { - type: dynamics.spring, - frequency: 200, - friction: 300, - duration: 800, - delay: 100 + nr * 40 - }); - }); - } - }, - mouseleave: function() { - if (App.mobile_screen) return; - if (list.current_view == "thumb") { - dynamics.animate(self.el, { - scale: 1 - }, { - type: dynamics.spring - }); - } - } - } - }); + self.buttons = []; + self.el = new Element("a.movie"); + self.el.store("klass", self); self.profile = Quality.getProfile(data.profile_id) || {}; self.category = CategoryList.getCategory(data.category_id) || {}; self.parent(self, options); @@ -1962,20 +1914,7 @@ var Movie = new Class({ } }); } - self.el.adopt(self.select_checkbox = new Element("input[type=checkbox]", { - events: { - change: function() { - self.fireEvent("select"); - self.select(self.select_checkbox.get("checked")); - } - } - }), self.thumbnail = thumbnail.grab(self.actions_el = new Element("div.actions", { - events: { - "click:relay(.action)": function(e) { - e.stopPropagation(); - } - } - })), self.data_container = new Element("div.data.light").adopt(self.info_container = new Element("div.info").adopt(new Element("div.title").adopt(self.title = new Element("span", { + self.el.adopt(self.select_checkbox = new Element("input[type=checkbox]"), self.thumbnail = thumbnail.grab(self.actions_el = new Element("div.actions")), self.data_container = new Element("div.data.light").grab(self.info_container = new Element("div.info").adopt(new Element("div.title").adopt(self.title = new Element("span", { text: self.getTitle() || "n/a" }), self.year = new Element("div.year", { text: self.data.info.year || "n/a" @@ -1993,6 +1932,65 @@ var Movie = new Class({ }); self.updateReleases(); }, + onClick: function(e) { + var self = this; + if (e.target.get("tag") != "input") { + e.preventDefault(); + self.openDetails(); + } + }, + onMouseenter: function() { + var self = this; + if (self.actions.length <= 0) { + self.options.actions.each(function(a) { + var action = new a(self), button = action.getButton(); + if (button) { + self.actions_el.grab(button); + self.buttons.push(button); + } + self.actions.push(action); + }); + } + if (App.mobile_screen) return; + if (self.list.current_view == "thumb") { + dynamics.css(self.el, { + scale: 1, + opacity: 1 + }); + dynamics.animate(self.el, { + scale: .9 + }, { + type: dynamics.bounce + }); + self.buttons.each(function(el, nr) { + dynamics.css(el, { + opacity: 0, + translateY: 50 + }); + dynamics.animate(el, { + opacity: 1, + translateY: 0 + }, { + type: dynamics.spring, + frequency: 200, + friction: 300, + duration: 800, + delay: 100 + nr * 40 + }); + }); + } + }, + onMouseleave: function() { + var self = this; + if (App.mobile_screen) return; + if (self.list.current_view == "thumb") { + dynamics.animate(self.el, { + scale: 1 + }, { + type: dynamics.spring + }); + } + }, updateReleases: function() { var self = this; if (!self.data.releases || self.data.releases.length === 0) return; From 0601b70fb972f9085471d60f2eb4b28021473528 Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 12 Aug 2015 09:39:33 +0200 Subject: [PATCH 247/301] Don't catch error in develop mode --- couchpotato/templates/index.html | 35 +++++++++++++++++++---------------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/couchpotato/templates/index.html b/couchpotato/templates/index.html index c091dfb..17ccaac 100644 --- a/couchpotato/templates/index.html +++ b/couchpotato/templates/index.html @@ -50,21 +50,24 @@ $(document.body).set('data-api', window.location.protocol + '//' + window.location.host + Api.createUrl().replace('/default/', '/')); // Catch errors - window.onerror = function(message, file, line){ - - p(message, file, line); - - Api.request('logging.log', { - 'data': { - 'type': 'error', - 'message': Browser.name + ' ' + Browser.version + ': \n' + message, - 'page': window.location.href.replace(window.location.host, 'HOST'), - 'file': file.replace(window.location.host, 'HOST'), - 'line': line - } - }); - - return true; + var dev = {{ json_encode(Env.get('dev')) }}; + if(!dev){ + window.onerror = function(message, file, line){ + + p(message, file, line); + + Api.request('logging.log', { + 'data': { + 'type': 'error', + 'message': Browser.name + ' ' + Browser.version + ': \n' + message, + 'page': window.location.href.replace(window.location.host, 'HOST'), + 'file': file.replace(window.location.host, 'HOST'), + 'line': line + } + }); + + return true; + } } Quality.setup({ @@ -75,7 +78,7 @@ CategoryList.setup({{ json_encode(fireEvent('category.all', single = True)) }}); App.setup({ - 'dev': {{ json_encode(Env.get('dev')) }}, + 'dev': dev, 'base_url': {{ json_encode(Env.get('web_base')) }}, 'args': {{ json_encode(Env.get('args', unicode = True)) }}, 'options': {{ json_encode(('%s' % Env.get('options'))) }}, From 9864cb0d72e9a1e217b5ac5aae715c0708671e7a Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 12 Aug 2015 09:39:55 +0200 Subject: [PATCH 248/301] Hide poster so image isn't loaded --- couchpotato/core/media/movie/_base/static/movie.scss | 4 ++++ couchpotato/static/style/combined.min.css | 1 + 2 files changed, 5 insertions(+) diff --git a/couchpotato/core/media/movie/_base/static/movie.scss b/couchpotato/core/media/movie/_base/static/movie.scss index af1d018..cefc2c9 100644 --- a/couchpotato/core/media/movie/_base/static/movie.scss +++ b/couchpotato/core/media/movie/_base/static/movie.scss @@ -167,6 +167,10 @@ $mass_edit_height: 44px; top: 50%; transform: translateY(-50%); } + + .poster { + display: none; + } .data { padding: $padding/2 $padding; diff --git a/couchpotato/static/style/combined.min.css b/couchpotato/static/style/combined.min.css index 3a55b29..944f3d4 100644 --- a/couchpotato/static/style/combined.min.css +++ b/couchpotato/static/style/combined.min.css @@ -85,6 +85,7 @@ .list_list .movie:last-child{border-bottom:none} .list_list .movie:hover{background:rgba(0,0,0,.1)} .list_list .movie input[type=checkbox]{left:20px;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%)} +.list_list .movie .poster{display:none} .list_list .movie .data{padding:10px 20px} .list_list .movie .data .info{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row nowrap;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-align-items:center;-ms-flex-align:center;align-items:center} .list_list .movie .data .info .title{-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto} From 0f413b41f61216993ee93582e5b42a921e770f82 Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 12 Aug 2015 09:53:04 +0200 Subject: [PATCH 249/301] Empty manage --- couchpotato/core/media/movie/_base/static/manage.js | 9 +++------ couchpotato/core/media/movie/_base/static/movie.scss | 10 +++++++++- couchpotato/static/scripts/combined.plugins.min.js | 8 +++----- couchpotato/static/style/combined.min.css | 2 ++ 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/couchpotato/core/media/movie/_base/static/manage.js b/couchpotato/core/media/movie/_base/static/manage.js index 4b9f342..2455ac6 100644 --- a/couchpotato/core/media/movie/_base/static/manage.js +++ b/couchpotato/core/media/movie/_base/static/manage.js @@ -37,11 +37,8 @@ var MoviesManage = new Class({ 'menu': [self.refresh_button, self.refresh_quick], 'on_empty_element': new Element('div.empty_manage').adopt( new Element('div', { - 'text': 'Seems like you don\'t have anything in your library yet.' - }), - new Element('div', { - 'text': 'Add your existing movie folders in ' - }).adopt( + 'text': 'Seems like you don\'t have anything in your library yet. Add your existing movie folders in ' + }).grab( new Element('a', { 'text': 'Settings > Manage', 'href': App.createUrl('settings/manage') @@ -49,7 +46,7 @@ var MoviesManage = new Class({ ), new Element('div.after_manage', { 'text': 'When you\'ve done that, hit this button → ' - }).adopt( + }).grab( new Element('a.button.green', { 'text': 'Hit me, but not too hard', 'events':{ diff --git a/couchpotato/core/media/movie/_base/static/movie.scss b/couchpotato/core/media/movie/_base/static/movie.scss index cefc2c9..8ae3635 100644 --- a/couchpotato/core/media/movie/_base/static/movie.scss +++ b/couchpotato/core/media/movie/_base/static/movie.scss @@ -32,6 +32,14 @@ $mass_edit_height: 44px; font-size: 2em; display: block; } + + .empty_manage { + padding: $padding; + + .after_manage { + margin-top: $padding; + } + } } .movie { @@ -167,7 +175,7 @@ $mass_edit_height: 44px; top: 50%; transform: translateY(-50%); } - + .poster { display: none; } diff --git a/couchpotato/static/scripts/combined.plugins.min.js b/couchpotato/static/scripts/combined.plugins.min.js index 2673fed..d76b239 100644 --- a/couchpotato/static/scripts/combined.plugins.min.js +++ b/couchpotato/static/scripts/combined.plugins.min.js @@ -935,15 +935,13 @@ var MoviesManage = new Class({ actions: [ MA.IMDB, MA.Files, MA.Trailer, MA.Readd, MA.Delete ], menu: [ self.refresh_button, self.refresh_quick ], on_empty_element: new Element("div.empty_manage").adopt(new Element("div", { - text: "Seems like you don't have anything in your library yet." - }), new Element("div", { - text: "Add your existing movie folders in " - }).adopt(new Element("a", { + text: "Seems like you don't have anything in your library yet. Add your existing movie folders in " + }).grab(new Element("a", { text: "Settings > Manage", href: App.createUrl("settings/manage") })), new Element("div.after_manage", { text: "When you've done that, hit this button → " - }).adopt(new Element("a.button.green", { + }).grab(new Element("a.button.green", { text: "Hit me, but not too hard", events: { click: self.refresh.bind(self, true) diff --git a/couchpotato/static/style/combined.min.css b/couchpotato/static/style/combined.min.css index 944f3d4..bc7c67c 100644 --- a/couchpotato/static/style/combined.min.css +++ b/couchpotato/static/style/combined.min.css @@ -59,6 +59,8 @@ } .mass_editing .page.movies_manage,.mass_editing .page.movies_wanted{top:124px} .page.movies_manage .load_more,.page.movies_wanted .load_more{text-align:center;padding:20px;font-size:2em;display:block} +.page.movies_manage .empty_manage,.page.movies_wanted .empty_manage{padding:20px} +.page.movies_manage .empty_manage .after_manage,.page.movies_wanted .empty_manage .after_manage{margin-top:20px} .movie .ripple,.movie input[type=checkbox]{display:none} .with_navigation .movie input[type=checkbox]{display:inline-block;position:absolute;transition:opacity 200ms;opacity:0;z-index:2;cursor:pointer} .with_navigation .movie input[type=checkbox]:hover{opacity:1!important} From 6442eb7dca22568bcd2b137b4c6ff37fe746fba8 Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 12 Aug 2015 10:00:13 +0200 Subject: [PATCH 250/301] Adopt > grab --- .../core/_base/downloader/static/downloaders.js | 2 +- couchpotato/core/media/movie/_base/static/list.js | 2 +- couchpotato/core/media/movie/_base/static/search.js | 8 ++++---- .../core/notifications/core/static/notification.js | 2 +- couchpotato/core/plugins/quality/static/quality.js | 2 +- couchpotato/core/plugins/wizard/static/wizard.js | 6 +++--- couchpotato/static/scripts/combined.plugins.min.js | 20 ++++++++++---------- 7 files changed, 21 insertions(+), 21 deletions(-) diff --git a/couchpotato/core/_base/downloader/static/downloaders.js b/couchpotato/core/_base/downloader/static/downloaders.js index 81202fe..f963ca8 100644 --- a/couchpotato/core/_base/downloader/static/downloaders.js +++ b/couchpotato/core/_base/downloader/static/downloaders.js @@ -27,7 +27,7 @@ var DownloadersBase = new Class({ if(button_name.contains('Downloaders')) return; - new Element('.ctrlHolder.test_button').adopt( + new Element('.ctrlHolder.test_button').grab( new Element('a.button', { 'text': button_name, 'events': { diff --git a/couchpotato/core/media/movie/_base/static/list.js b/couchpotato/core/media/movie/_base/static/list.js index c28baf5..308c0a0 100644 --- a/couchpotato/core/media/movie/_base/static/list.js +++ b/couchpotato/core/media/movie/_base/static/list.js @@ -582,7 +582,7 @@ var MovieList = new Class({ var loader_timeout; if(self.movies.length === 0 && self.options.loader){ - self.loader_first = new Element('div.mask.loading.with_message').adopt( + self.loader_first = new Element('div.mask.loading.with_message').grab( new Element('div.message', {'text': self.options.title ? 'Loading \'' + self.options.title + '\'' : 'Loading...'}) ).inject(self.el, 'top'); createSpinner(self.loader_first); diff --git a/couchpotato/core/media/movie/_base/static/search.js b/couchpotato/core/media/movie/_base/static/search.js index 8bcc50f..734f464 100644 --- a/couchpotato/core/media/movie/_base/static/search.js +++ b/couchpotato/core/media/movie/_base/static/search.js @@ -38,8 +38,8 @@ var BlockSearchMovieItem = new Class({ 'width': null }) : null, self.options_el = new Element('div.options'), - self.data_container = new Element('div.data').adopt( - self.info_container = new Element('div.info').adopt( + self.data_container = new Element('div.data').grab( + self.info_container = new Element('div.info').grab( new Element('h2', { 'class': info.in_wanted && info.in_wanted.profile_id || in_library ? 'in_library_wanted' : '', 'title': self.getTitle() @@ -122,7 +122,7 @@ var BlockSearchMovieItem = new Class({ }, 'onComplete': function(json){ self.options_el.empty(); - self.options_el.adopt( + self.options_el.grab( new Element('div.message', { 'text': json.success ? 'Movie successfully added.' : 'Movie didn\'t add properly. Check logs' }) @@ -133,7 +133,7 @@ var BlockSearchMovieItem = new Class({ }, 'onFailure': function(){ self.options_el.empty(); - self.options_el.adopt( + self.options_el.grab( new Element('div.message', { 'text': 'Something went wrong, check the logs for more info.' }) diff --git a/couchpotato/core/notifications/core/static/notification.js b/couchpotato/core/notifications/core/static/notification.js index 57713c8..c9ac01b 100644 --- a/couchpotato/core/notifications/core/static/notification.js +++ b/couchpotato/core/notifications/core/static/notification.js @@ -221,7 +221,7 @@ var NotificationBase = new Class({ if(button_name.contains('Notifications')) return; - new Element('.ctrlHolder.test_button').adopt( + new Element('.ctrlHolder.test_button').grab( new Element('a.button', { 'text': button_name, 'events': { diff --git a/couchpotato/core/plugins/quality/static/quality.js b/couchpotato/core/plugins/quality/static/quality.js index 4025f95..67924b0 100644 --- a/couchpotato/core/plugins/quality/static/quality.js +++ b/couchpotato/core/plugins/quality/static/quality.js @@ -110,7 +110,7 @@ var QualityBase = new Class({ self.settings.createGroup({ 'label': 'Profile Defaults', 'description': '(Needs refresh \'' +(App.isMac() ? 'CMD+R' : 'F5')+ '\' after editing)' - }).adopt( + }).grab( new Element('.ctrlHolder#profile_ordering').adopt( new Element('label[text=Order]'), self.profiles_list = new Element('ul'), diff --git a/couchpotato/core/plugins/wizard/static/wizard.js b/couchpotato/core/plugins/wizard/static/wizard.js index ed2d59f..ec1101b 100644 --- a/couchpotato/core/plugins/wizard/static/wizard.js +++ b/couchpotato/core/plugins/wizard/static/wizard.js @@ -50,7 +50,7 @@ Page.Wizard = new Class({ 'description': 'Are you done? Did you fill in everything as much as possible?' + '
Be sure to check the settings to see what more CP can do!

' + '
After you\'ve used CP for a while, and you like it (which of course you will), consider supporting CP. Maybe even by writing some code.
Or by getting a subscription at Usenet Server or Newshosting.
', - 'content': new Element('div').adopt( + 'content': new Element('div').grab( new Element('a.button.green', { 'styles': { 'margin-top': 20 @@ -158,7 +158,7 @@ Page.Wizard = new Class({ self.el.getElement('.tab_'+inc).inject(group_container); }); - new Element('li.t_'+group).adopt( + new Element('li.t_'+group).grab( new Element('a', { 'href': App.createUrl('wizard/'+group), 'text': (self.headers[group].label || group).capitalize() @@ -179,7 +179,7 @@ Page.Wizard = new Class({ } } else { - new Element('li.t_'+group).adopt( + new Element('li.t_'+group).grab( new Element('a', { 'href': App.createUrl('wizard/'+group), 'text': (self.headers[group].label || group).capitalize() diff --git a/couchpotato/static/scripts/combined.plugins.min.js b/couchpotato/static/scripts/combined.plugins.min.js index d76b239..342d2bb 100644 --- a/couchpotato/static/scripts/combined.plugins.min.js +++ b/couchpotato/static/scripts/combined.plugins.min.js @@ -14,7 +14,7 @@ var DownloadersBase = new Class({ addTestButton: function(fieldset, plugin_name) { var self = this, button_name = self.testButtonName(fieldset); if (button_name.contains("Downloaders")) return; - new Element(".ctrlHolder.test_button").adopt(new Element("a.button", { + new Element(".ctrlHolder.test_button").grab(new Element("a.button", { text: button_name, events: { click: function() { @@ -837,7 +837,7 @@ var MovieList = new Class({ } var loader_timeout; if (self.movies.length === 0 && self.options.loader) { - self.loader_first = new Element("div.mask.loading.with_message").adopt(new Element("div.message", { + self.loader_first = new Element("div.mask.loading.with_message").grab(new Element("div.message", { text: self.options.title ? "Loading '" + self.options.title + "'" : "Loading..." })).inject(self.el, "top"); createSpinner(self.loader_first); @@ -2102,7 +2102,7 @@ var BlockSearchMovieItem = new Class({ src: info.images.poster[0], height: null, width: null - }) : null, self.options_el = new Element("div.options"), self.data_container = new Element("div.data").adopt(self.info_container = new Element("div.info").adopt(new Element("h2", { + }) : null, self.options_el = new Element("div.options"), self.data_container = new Element("div.data").grab(self.info_container = new Element("div.info").grab(new Element("h2", { class: info.in_wanted && info.in_wanted.profile_id || in_library ? "in_library_wanted" : "", title: self.getTitle() }).adopt(self.title = new Element("span.title", { @@ -2159,7 +2159,7 @@ var BlockSearchMovieItem = new Class({ }, onComplete: function(json) { self.options_el.empty(); - self.options_el.adopt(new Element("div.message", { + self.options_el.grab(new Element("div.message", { text: json.success ? "Movie successfully added." : "Movie didn't add properly. Check logs" })); self.mask.fade("out"); @@ -2167,7 +2167,7 @@ var BlockSearchMovieItem = new Class({ }, onFailure: function() { self.options_el.empty(); - self.options_el.adopt(new Element("div.message", { + self.options_el.grab(new Element("div.message", { text: "Something went wrong, check the logs for more info." })); self.mask.fade("out"); @@ -2616,7 +2616,7 @@ var NotificationBase = new Class({ addTestButton: function(fieldset, plugin_name) { var self = this, button_name = self.testButtonName(fieldset); if (button_name.contains("Notifications")) return; - new Element(".ctrlHolder.test_button").adopt(new Element("a.button", { + new Element(".ctrlHolder.test_button").grab(new Element("a.button", { text: button_name, events: { click: function() { @@ -3496,7 +3496,7 @@ var QualityBase = new Class({ self.settings.createGroup({ label: "Profile Defaults", description: "(Needs refresh '" + (App.isMac() ? "CMD+R" : "F5") + "' after editing)" - }).adopt(new Element(".ctrlHolder#profile_ordering").adopt(new Element("label[text=Order]"), self.profiles_list = new Element("ul"), new Element("p.formHint", { + }).grab(new Element(".ctrlHolder#profile_ordering").adopt(new Element("label[text=Order]"), self.profiles_list = new Element("ul"), new Element("p.formHint", { html: "Change the order the profiles are in the dropdown list. Uncheck to hide it completely.
First one will be default." }))).inject(self.content); Array.each(self.profiles, function(profile) { @@ -3729,7 +3729,7 @@ Page.Wizard = new Class({ finish: { title: "Finishing Up", description: "Are you done? Did you fill in everything as much as possible?" + "
Be sure to check the settings to see what more CP can do!

" + '
After you\'ve used CP for a while, and you like it (which of course you will), consider supporting CP. Maybe even by writing some code.
Or by getting a subscription at Usenet Server or Newshosting.
', - content: new Element("div").adopt(new Element("a.button.green", { + content: new Element("div").grab(new Element("a.button.green", { styles: { "margin-top": 20 }, @@ -3809,7 +3809,7 @@ Page.Wizard = new Class({ self.headers[group].include.each(function(inc) { self.el.getElement(".tab_" + inc).inject(group_container); }); - new Element("li.t_" + group).adopt(new Element("a", { + new Element("li.t_" + group).grab(new Element("a", { href: App.createUrl("wizard/" + group), text: (self.headers[group].label || group).capitalize() })).inject(tabs); @@ -3821,7 +3821,7 @@ Page.Wizard = new Class({ if (url_split.length > 3) a.set("href", a.get("href").replace(url_split[url_split.length - 3] + "/", "")); } } else { - new Element("li.t_" + group).adopt(new Element("a", { + new Element("li.t_" + group).grab(new Element("a", { href: App.createUrl("wizard/" + group), text: (self.headers[group].label || group).capitalize() })).inject(tabs); From 4c5d1273e204910564aebb1eaf0dabd2e1cfd404 Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 12 Aug 2015 10:18:29 +0200 Subject: [PATCH 251/301] Posters --- couchpotato/core/media/movie/charts/main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/couchpotato/core/media/movie/charts/main.py b/couchpotato/core/media/movie/charts/main.py index 6573eaa..515d8e3 100644 --- a/couchpotato/core/media/movie/charts/main.py +++ b/couchpotato/core/media/movie/charts/main.py @@ -44,8 +44,8 @@ class Charts(Plugin): pass # Cache poster - poster = media.get('images', {}).get('poster', []) - cached_poster = fireEvent('file.download', url = poster[0], single = True) if len(poster) > 0 else False + posters = media.get('images', {}).get('poster', []) + cached_poster = fireEvent('file.download', url = posters[0], single = True) if len(posters) > 0 else False files = {'image_poster': [cached_poster] } if cached_poster else {} medias.append({ From e1e3c49f9f516fb410172e65a9ced005670f7535 Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 12 Aug 2015 10:28:38 +0200 Subject: [PATCH 252/301] Try TheMovieDB poster first for suggestions and charts --- couchpotato/core/media/movie/charts/main.py | 3 +++ couchpotato/core/media/movie/suggestion/main.py | 7 +++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/couchpotato/core/media/movie/charts/main.py b/couchpotato/core/media/movie/charts/main.py index 515d8e3..d42c29a 100644 --- a/couchpotato/core/media/movie/charts/main.py +++ b/couchpotato/core/media/movie/charts/main.py @@ -45,6 +45,9 @@ class Charts(Plugin): # Cache poster posters = media.get('images', {}).get('poster', []) + poster = [x for x in posters if 'tmdb' in x] + posters = poster if len(poster) > 0 else posters + cached_poster = fireEvent('file.download', url = posters[0], single = True) if len(posters) > 0 else False files = {'image_poster': [cached_poster] } if cached_poster else {} diff --git a/couchpotato/core/media/movie/suggestion/main.py b/couchpotato/core/media/movie/suggestion/main.py index e12b249..2b4fd7b 100755 --- a/couchpotato/core/media/movie/suggestion/main.py +++ b/couchpotato/core/media/movie/suggestion/main.py @@ -49,8 +49,11 @@ class Suggestion(Plugin): for suggestion in suggestions[:int(limit)]: # Cache poster - poster = suggestion.get('images', {}).get('poster', []) - cached_poster = fireEvent('file.download', url = poster[0], single = True) if len(poster) > 0 else False + posters = suggestion.get('images', {}).get('poster', []) + poster = [x for x in posters if 'tmdb' in x] + posters = poster if len(poster) > 0 else posters + + cached_poster = fireEvent('file.download', url = posters[0], single = True) if len(posters) > 0 else False files = {'image_poster': [cached_poster] } if cached_poster else {} medias.append({ From 20cc5ab06819128bb2d238e13b1cba24a5c5dbe6 Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 12 Aug 2015 10:31:35 +0200 Subject: [PATCH 253/301] Don't use empty list as dict --- .../core/media/movie/providers/automation/popularmovies.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/couchpotato/core/media/movie/providers/automation/popularmovies.py b/couchpotato/core/media/movie/providers/automation/popularmovies.py index eb46ece..8b1726a 100644 --- a/couchpotato/core/media/movie/providers/automation/popularmovies.py +++ b/couchpotato/core/media/movie/providers/automation/popularmovies.py @@ -17,11 +17,12 @@ class PopularMovies(Automation): movies = [] retrieved_movies = self.getJsonData(self.url) - for movie in retrieved_movies.get('movies'): - imdb_id = movie.get('imdb_id') - info = fireEvent('movie.info', identifier = imdb_id, extended = False, merge = True) - if self.isMinimalMovie(info): - movies.append(imdb_id) + if retrieved_movies: + for movie in retrieved_movies.get('movies'): + imdb_id = movie.get('imdb_id') + info = fireEvent('movie.info', identifier = imdb_id, extended = False, merge = True) + if self.isMinimalMovie(info): + movies.append(imdb_id) return movies From acac9d4308f710d2f57ed6f304811ea64b585d2c Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 12 Aug 2015 15:26:44 +0200 Subject: [PATCH 254/301] Add warning if LXML isn't installed if running from source --- README.md | 3 +++ couchpotato/core/_base/_core.py | 7 +++++++ 2 files changed, 10 insertions(+) diff --git a/README.md b/README.md index 1ccf689..fca0793 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ OS X: * If you're on Leopard (10.5) install Python 2.6+: [Python 2.6.5](http://www.python.org/download/releases/2.6.5/) * Install [GIT](http://git-scm.com/) +* Install [LXML](http://lxml.de/installation.html) for better/faster website scraping * Open up `Terminal` * Go to your App folder `cd /Applications` * Run `git clone https://github.com/RuudBurger/CouchPotatoServer.git` @@ -33,6 +34,7 @@ Linux: * (Ubuntu / Debian) Install [GIT](http://git-scm.com/) with `apt-get install git-core` * (Fedora / CentOS) Install [GIT](http://git-scm.com/) with `yum install git` +* Install [LXML](http://lxml.de/installation.html) for better/faster website scraping * 'cd' to the folder of your choosing. * Run `git clone https://github.com/RuudBurger/CouchPotatoServer.git` * Then do `python CouchPotatoServer/CouchPotato.py` to start @@ -59,6 +61,7 @@ FreeBSD : * Install port [ftp/curl](http://www.freshports.org/ftp/bcurl), deselect 'Asynchronous DNS resolution via c-ares' when prompted as part of config `cd /usr/ports/ftp/fpc-libcurl; sudo make install clean` * Install port [textproc/docbook-xml-450](http://www.freshports.org/textproc/docbook-xml-450) with `cd /usr/ports/textproc/docbook-xml-450; sudo make install clean` * Install port [GIT](http://git-scm.com/) with `cd /usr/ports/devel/git; sudo make install clean` +* Install [LXML](http://lxml.de/installation.html) for better/faster website scraping * 'cd' to the folder of your choosing. * Run `git clone https://github.com/RuudBurger/CouchPotatoServer.git` * Then run `sudo python CouchPotatoServer/CouchPotato.py` to start for the first time diff --git a/couchpotato/core/_base/_core.py b/couchpotato/core/_base/_core.py index 81f5fa5..14281ef 100644 --- a/couchpotato/core/_base/_core.py +++ b/couchpotato/core/_base/_core.py @@ -73,6 +73,13 @@ class Core(Plugin): except: log.debug('Failed setting default ssl context: %s', traceback.format_exc()) + + # Check if lxml is available + try: + from lxml import etree + except: + log.error('LXML not available, please install for better/faster scraping support. `http://lxml.de/installation.html`') + def md5Password(self, value): return md5(value) if value else '' From 575d56afe3b3e048919fb2d5f2f6d1a6cfe6fe26 Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 12 Aug 2015 15:30:39 +0200 Subject: [PATCH 255/301] Kickass Proxy --- couchpotato/core/media/_base/providers/torrent/kickasstorrents.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/couchpotato/core/media/_base/providers/torrent/kickasstorrents.py b/couchpotato/core/media/_base/providers/torrent/kickasstorrents.py index f3cecdf..650d9c3 100644 --- a/couchpotato/core/media/_base/providers/torrent/kickasstorrents.py +++ b/couchpotato/core/media/_base/providers/torrent/kickasstorrents.py @@ -31,7 +31,8 @@ class Base(TorrentMagnetProvider): proxy_list = [ 'https://kat.cr', - 'http://katproxy.com', + 'https://kickass.unblocked.pw/', + 'https://katproxy.com', ] def _search(self, media, quality, results): From a3441f6ecdd1a48cea38fdcf630a48f8cd0d966f Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 12 Aug 2015 15:42:41 +0200 Subject: [PATCH 256/301] Use https base url if cert and key has been set --- couchpotato/core/_base/_core.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/couchpotato/core/_base/_core.py b/couchpotato/core/_base/_core.py index 14281ef..4726056 100644 --- a/couchpotato/core/_base/_core.py +++ b/couchpotato/core/_base/_core.py @@ -190,8 +190,9 @@ class Core(Plugin): if host == '0.0.0.0' or host == '': host = 'localhost' port = Env.setting('port') + ssl = Env.setting('ssl_cert') and Env.setting('ssl_key') - return '%s:%d%s' % (cleanHost(host).rstrip('/'), int(port), Env.get('web_base')) + return '%s:%d%s' % (cleanHost(host, ssl = ssl).rstrip('/'), int(port), Env.get('web_base')) def createApiUrl(self): return '%sapi/%s' % (self.createBaseUrl(), Env.setting('api_key')) From c66891f301c57e15baf37c238e97be343c7391f9 Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 12 Aug 2015 15:47:29 +0200 Subject: [PATCH 257/301] Update NZBClub rss --- couchpotato/core/media/_base/providers/nzb/nzbclub.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/core/media/_base/providers/nzb/nzbclub.py b/couchpotato/core/media/_base/providers/nzb/nzbclub.py index a266089..e585114 100644 --- a/couchpotato/core/media/_base/providers/nzb/nzbclub.py +++ b/couchpotato/core/media/_base/providers/nzb/nzbclub.py @@ -15,7 +15,7 @@ log = CPLog(__name__) class Base(NZBProvider, RSS): urls = { - 'search': 'https://www.nzbclub.com/nzbfeeds.aspx?%s', + 'search': 'https://www.nzbclub.com/nzbrss.aspx?%s', } http_time_between_calls = 4 # seconds From e60305491ddfee25259f06e66504da6506bf9555 Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 12 Aug 2015 16:02:50 +0200 Subject: [PATCH 258/301] Return empty list when no releases found fix #4273 --- couchpotato/core/plugins/release/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/core/plugins/release/main.py b/couchpotato/core/plugins/release/main.py index a241c34..ab3fffa 100644 --- a/couchpotato/core/plugins/release/main.py +++ b/couchpotato/core/plugins/release/main.py @@ -563,4 +563,4 @@ class Release(Plugin): if download_preference != 'both': releases = sorted(releases, key = lambda k: k.get('info', {}).get('protocol', '')[:3], reverse = (download_preference == 'torrent')) - return releases + return releases or [] From 656b130e8df959dfa1d9f01e5c5c97864cae2ee7 Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 12 Aug 2015 16:06:31 +0200 Subject: [PATCH 259/301] Skip if no media_id is set for release fix #5087 --- couchpotato/core/plugins/renamer.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/couchpotato/core/plugins/renamer.py b/couchpotato/core/plugins/renamer.py index 2874983..fd710d5 100755 --- a/couchpotato/core/plugins/renamer.py +++ b/couchpotato/core/plugins/renamer.py @@ -966,6 +966,7 @@ Remove it if you want it to be renamed (again, or at least let it try again) try: for rel in rels: + if not rel.get('media_id'): continue movie_dict = db.get('id', rel.get('media_id')) download_info = rel.get('download_info') @@ -1348,12 +1349,12 @@ config = [{ 'options': rename_options }, { - 'advanced': True, + 'advanced': True, 'name': 'replace_doubles', 'type': 'bool', 'label': 'Clean Name', 'description': ('Attempt to clean up double separaters due to missing data for fields.','Sometimes this eliminates wanted white space (see #2782).'), - 'default': True + 'default': True }, { 'name': 'unrar', From 057394f514df16ee5764a91765c43f5a1e56dd0c Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 12 Aug 2015 16:07:46 +0200 Subject: [PATCH 260/301] Don't loop over nonetype fix #5124 --- couchpotato/core/plugins/dashboard.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/couchpotato/core/plugins/dashboard.py b/couchpotato/core/plugins/dashboard.py index 0f23b5a..16dc418 100644 --- a/couchpotato/core/plugins/dashboard.py +++ b/couchpotato/core/plugins/dashboard.py @@ -64,7 +64,7 @@ class Dashboard(Plugin): except RecordDeleted: log.debug('Record already deleted: %s', media_id) continue - + except RecordNotFound: log.debug('Record not found: %s', media_id) continue @@ -96,7 +96,7 @@ class Dashboard(Plugin): if late: media['releases'] = fireEvent('release.for_media', media['_id'], single = True) - for release in media.get('releases'): + for release in media.get('releases', []): if release.get('status') in ['snatched', 'available', 'seeding', 'downloaded']: add = False break From 3483b97d6003f11a74a7f92ceff2c0be7a432bd6 Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 12 Aug 2015 23:33:17 +0200 Subject: [PATCH 261/301] Movie list buttons not visible --- .../core/media/movie/_base/static/movie.actions.js | 14 +++++++------- couchpotato/core/media/movie/_base/static/movie.js | 3 ++- couchpotato/core/media/movie/_base/static/movie.scss | 12 +++++++++--- couchpotato/static/scripts/combined.plugins.min.js | 16 ++++++++-------- couchpotato/static/style/combined.min.css | 3 ++- 5 files changed, 28 insertions(+), 20 deletions(-) diff --git a/couchpotato/core/media/movie/_base/static/movie.actions.js b/couchpotato/core/media/movie/_base/static/movie.actions.js index 71bbfe6..f729bb1 100644 --- a/couchpotato/core/media/movie/_base/static/movie.actions.js +++ b/couchpotato/core/media/movie/_base/static/movie.actions.js @@ -217,7 +217,7 @@ MA.Release = new Class({ new Element('a.icon-download', { 'events': { 'click': function(e){ - (e).preventDefault(); + (e).stopPropagation(); if(!this.hasClass('completed')) self.download(release); } @@ -227,7 +227,7 @@ MA.Release = new Class({ 'class': release.status == 'ignored' ? 'icon-redo' : 'icon-cancel', 'events': { 'click': function(e){ - (e).preventDefault(); + (e).stopPropagation(); self.ignore(release); this.toggleClass('icon-redo'); @@ -669,7 +669,7 @@ MA.SuggestSeen = new Class({ markAsSeen: function(e){ var self = this; - (e).preventDefault(); + (e).stopPropagation(); Api.request('suggestion.ignore', { 'data': { @@ -714,7 +714,7 @@ MA.SuggestIgnore = new Class({ markAsIgnored: function(e){ var self = this; - (e).preventDefault(); + (e).stopPropagation(); Api.request('suggestion.ignore', { 'data': { @@ -759,7 +759,7 @@ MA.ChartIgnore = new Class({ markAsHidden: function(e){ var self = this; - (e).preventDefault(); + (e).stopPropagation(); Api.request('charts.ignore', { 'data': { @@ -802,7 +802,7 @@ MA.Readd = new Class({ doReadd: function(e){ var self = this; - (e).preventDefault(); + (e).stopPropagation(); Api.request('movie.add', { 'data': { @@ -841,7 +841,7 @@ MA.Delete = new Class({ showConfirm: function(e){ var self = this; - (e).preventDefault(); + (e).stopPropagation(); self.question = new Question('Are you sure you want to delete ' + self.getTitle() + '?', '', [{ 'text': 'Yes, delete '+self.getTitle(), diff --git a/couchpotato/core/media/movie/_base/static/movie.js b/couchpotato/core/media/movie/_base/static/movie.js index 9d0a893..c7907d4 100644 --- a/couchpotato/core/media/movie/_base/static/movie.js +++ b/couchpotato/core/media/movie/_base/static/movie.js @@ -247,7 +247,8 @@ var Movie = new Class({ self.el.adopt( self.select_checkbox = new Element('input[type=checkbox]'), - self.thumbnail = thumbnail.grab( + new Element('div.poster_container').adopt( + self.thumbnail = thumbnail, self.actions_el = new Element('div.actions') ), self.data_container = new Element('div.data.light').grab( diff --git a/couchpotato/core/media/movie/_base/static/movie.scss b/couchpotato/core/media/movie/_base/static/movie.scss index 8ae3635..c714b9f 100644 --- a/couchpotato/core/media/movie/_base/static/movie.scss +++ b/couchpotato/core/media/movie/_base/static/movie.scss @@ -365,14 +365,20 @@ $mass_edit_height: 44px; left: $padding/2; } - .poster { - position: relative; + .poster_container { border-radius: $border_radius; + position: relative; + width: 100%; + padding-bottom: 150%; + } + + .poster { + position: absolute; background: $theme_off center no-repeat; background-size: cover; overflow: hidden; + height: 100%; width: 100%; - padding-bottom: 150%; } .data { diff --git a/couchpotato/static/scripts/combined.plugins.min.js b/couchpotato/static/scripts/combined.plugins.min.js index 342d2bb..518342f 100644 --- a/couchpotato/static/scripts/combined.plugins.min.js +++ b/couchpotato/static/scripts/combined.plugins.min.js @@ -1182,7 +1182,7 @@ MA.Release = new Class({ }) : new Element("a"), new Element("a.icon-download", { events: { click: function(e) { - e.preventDefault(); + e.stopPropagation(); if (!this.hasClass("completed")) self.download(release); } } @@ -1190,7 +1190,7 @@ MA.Release = new Class({ class: release.status == "ignored" ? "icon-redo" : "icon-cancel", events: { click: function(e) { - e.preventDefault(); + e.stopPropagation(); self.ignore(release); this.toggleClass("icon-redo"); this.toggleClass("icon-cancel"); @@ -1499,7 +1499,7 @@ MA.SuggestSeen = new Class({ }, markAsSeen: function(e) { var self = this; - e.preventDefault(); + e.stopPropagation(); Api.request("suggestion.ignore", { data: { imdb: self.getIMDB(), @@ -1535,7 +1535,7 @@ MA.SuggestIgnore = new Class({ }, markAsIgnored: function(e) { var self = this; - e.preventDefault(); + e.stopPropagation(); Api.request("suggestion.ignore", { data: { imdb: self.getIMDB() @@ -1570,7 +1570,7 @@ MA.ChartIgnore = new Class({ }, markAsHidden: function(e) { var self = this; - e.preventDefault(); + e.stopPropagation(); Api.request("charts.ignore", { data: { imdb: self.getIMDB() @@ -1601,7 +1601,7 @@ MA.Readd = new Class({ }, doReadd: function(e) { var self = this; - e.preventDefault(); + e.stopPropagation(); Api.request("movie.add", { data: { identifier: self.movie.getIdentifier(), @@ -1631,7 +1631,7 @@ MA.Delete = new Class({ }, showConfirm: function(e) { var self = this; - e.preventDefault(); + e.stopPropagation(); self.question = new Question("Are you sure you want to delete " + self.getTitle() + "?", "", [ { text: "Yes, delete " + self.getTitle(), class: "delete", @@ -1912,7 +1912,7 @@ var Movie = new Class({ } }); } - self.el.adopt(self.select_checkbox = new Element("input[type=checkbox]"), self.thumbnail = thumbnail.grab(self.actions_el = new Element("div.actions")), self.data_container = new Element("div.data.light").grab(self.info_container = new Element("div.info").adopt(new Element("div.title").adopt(self.title = new Element("span", { + self.el.adopt(self.select_checkbox = new Element("input[type=checkbox]"), new Element("div.poster_container").adopt(self.thumbnail = thumbnail, self.actions_el = new Element("div.actions")), self.data_container = new Element("div.data.light").grab(self.info_container = new Element("div.info").adopt(new Element("div.title").adopt(self.title = new Element("span", { text: self.getTitle() || "n/a" }), self.year = new Element("div.year", { text: self.data.info.year || "n/a" diff --git a/couchpotato/static/style/combined.min.css b/couchpotato/static/style/combined.min.css index bc7c67c..eac07a6 100644 --- a/couchpotato/static/style/combined.min.css +++ b/couchpotato/static/style/combined.min.css @@ -129,7 +129,8 @@ .thumb_list .movie{max-width:50%;border-width:0 4px} } .thumb_list .movie input[type=checkbox]{top:10px;left:10px} -.thumb_list .movie .poster{position:relative;border-radius:3px;background:center no-repeat #eaeaea;background-size:cover;overflow:hidden;width:100%;padding-bottom:150%} +.thumb_list .movie .poster_container{border-radius:3px;position:relative;width:100%;padding-bottom:150%} +.thumb_list .movie .poster{position:absolute;background:center no-repeat #eaeaea;background-size:cover;overflow:hidden;height:100%;width:100%} .thumb_list .movie .data{clear:both;font-size:.85em} .thumb_list .movie .data .info .title{display:-webkit-flex;display:-ms-flexbox;display:flex;padding:3px 0} .thumb_list .movie .data .info .title span{-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} From 9a71b0e687b8474dd7a4dc34277d7c634eec58ec Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 12 Aug 2015 23:41:39 +0200 Subject: [PATCH 262/301] stop propagation --- couchpotato/core/media/movie/_base/static/list.js | 4 ++++ couchpotato/core/media/movie/_base/static/movie.js | 2 +- couchpotato/static/scripts/combined.plugins.min.js | 6 +++++- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/couchpotato/core/media/movie/_base/static/list.js b/couchpotato/core/media/movie/_base/static/list.js index 308c0a0..e811cd3 100644 --- a/couchpotato/core/media/movie/_base/static/list.js +++ b/couchpotato/core/media/movie/_base/static/list.js @@ -41,18 +41,22 @@ var MovieList = new Class({ self.movie_list = new Element('div', { 'events': { 'click:relay(.movie)': function(e, el){ + (e).stopPropagation(); el.retrieve('klass').onClick(e); }, 'mouseenter:relay(.movie)': function(e, el){ + (e).stopPropagation(); el.retrieve('klass').onMouseenter(e); }, 'mouseleave:relay(.movie)': function(e, el){ + (e).stopPropagation(); el.retrieve('klass').onMouseleave(e); }, 'click:relay(.movie .action)': function(e){ (e).stopPropagation(); }, 'change:relay(.movie input)': function(e, el){ + (e).stopPropagation(); el = el.getParent(); var klass = el.retrieve('klass'); klass.fireEvent('select'); diff --git a/couchpotato/core/media/movie/_base/static/movie.js b/couchpotato/core/media/movie/_base/static/movie.js index c7907d4..06380bd 100644 --- a/couchpotato/core/media/movie/_base/static/movie.js +++ b/couchpotato/core/media/movie/_base/static/movie.js @@ -299,7 +299,7 @@ var Movie = new Class({ var self = this; if(e.target.get('tag') != 'input'){ - (e).preventDefault(); + (e).stopPropagation(); self.openDetails(); } }, diff --git a/couchpotato/static/scripts/combined.plugins.min.js b/couchpotato/static/scripts/combined.plugins.min.js index 518342f..cc1cdfe 100644 --- a/couchpotato/static/scripts/combined.plugins.min.js +++ b/couchpotato/static/scripts/combined.plugins.min.js @@ -457,18 +457,22 @@ var MovieList = new Class({ }) : null, self.movie_list = new Element("div", { events: { "click:relay(.movie)": function(e, el) { + e.stopPropagation(); el.retrieve("klass").onClick(e); }, "mouseenter:relay(.movie)": function(e, el) { + e.stopPropagation(); el.retrieve("klass").onMouseenter(e); }, "mouseleave:relay(.movie)": function(e, el) { + e.stopPropagation(); el.retrieve("klass").onMouseleave(e); }, "click:relay(.movie .action)": function(e) { e.stopPropagation(); }, "change:relay(.movie input)": function(e, el) { + e.stopPropagation(); el = el.getParent(); var klass = el.retrieve("klass"); klass.fireEvent("select"); @@ -1933,7 +1937,7 @@ var Movie = new Class({ onClick: function(e) { var self = this; if (e.target.get("tag") != "input") { - e.preventDefault(); + e.stopPropagation(); self.openDetails(); } }, From d540199727b18f02c75ee50ad36fda08b104c699 Mon Sep 17 00:00:00 2001 From: Ruud Date: Fri, 14 Aug 2015 16:33:22 +0200 Subject: [PATCH 263/301] Remove yify --- couchpotato/core/media/_base/providers/torrent/yify.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/couchpotato/core/media/_base/providers/torrent/yify.py b/couchpotato/core/media/_base/providers/torrent/yify.py index 53afb47..d5350a9 100644 --- a/couchpotato/core/media/_base/providers/torrent/yify.py +++ b/couchpotato/core/media/_base/providers/torrent/yify.py @@ -18,8 +18,6 @@ class Base(TorrentProvider): http_time_between_calls = 1 # seconds proxy_list = [ - 'https://yts.re', - 'https://yts.wf', 'https://yts.im', 'https://yts.to', 'https://yify.ml', From 7222aec65fd0d635378d3d2244b469fc84e3e9e0 Mon Sep 17 00:00:00 2001 From: Ruud Date: Tue, 18 Aug 2015 11:28:25 +0200 Subject: [PATCH 264/301] Overwrite force re-add parameter #5139 --- couchpotato/core/media/movie/_base/main.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/couchpotato/core/media/movie/_base/main.py b/couchpotato/core/media/movie/_base/main.py index 4e82fcb..3ded1f8 100755 --- a/couchpotato/core/media/movie/_base/main.py +++ b/couchpotato/core/media/movie/_base/main.py @@ -34,6 +34,7 @@ class MovieBase(MovieTypeBase): 'params': { 'identifier': {'desc': 'IMDB id of the movie your want to add.'}, 'profile_id': {'desc': 'ID of quality profile you want the add the movie in. If empty will use the default profile.'}, + 'force_readd': {'desc': 'Force re-add even if movie already in wanted or manage. Default: True'}, 'category_id': {'desc': 'ID of category you want the add the movie in. If empty will use no category.'}, 'title': {'desc': 'Movie title to use for searches. Has to be one of the titles returned by movie.search.'}, } @@ -78,6 +79,11 @@ class MovieBase(MovieTypeBase): if not info or (info and len(info.get('titles', [])) == 0): info = fireEvent('movie.info', merge = True, extended = False, identifier = params.get('identifier')) + # Allow force re-add overwrite from param + if 'force_readd' in params: + fra = params.get('force_readd') + force_readd = fra.lower() not in ['0', '-1'] if not isinstance(fra, bool) else fra + # Set default title default_title = toUnicode(info.get('title')) titles = info.get('titles', []) From 8ef106abe6c17a3dc0c7bd2b0c7d4bbdfb106a0a Mon Sep 17 00:00:00 2001 From: Ruud Date: Tue, 18 Aug 2015 12:25:04 +0200 Subject: [PATCH 265/301] Make dereferer editable fix #5152 --- couchpotato/core/_base/_core.py | 5 +++++ couchpotato/core/media/movie/_base/static/list.js | 4 ---- couchpotato/core/media/movie/_base/static/movie.js | 2 +- couchpotato/static/scripts/combined.base.min.js | 5 ++++- couchpotato/static/scripts/combined.plugins.min.js | 6 +----- couchpotato/static/scripts/couchpotato.js | 5 ++++- couchpotato/templates/index.html | 1 + 7 files changed, 16 insertions(+), 12 deletions(-) diff --git a/couchpotato/core/_base/_core.py b/couchpotato/core/_base/_core.py index 4726056..30925f4 100644 --- a/couchpotato/core/_base/_core.py +++ b/couchpotato/core/_base/_core.py @@ -284,6 +284,11 @@ config = [{ 'description': 'Let 3rd party app do stuff. Docs', }, { + 'name': 'dereferer', + 'default': 'http://www.dereferer.org/?', + 'description': 'Derefer links to external sites, keep empty for no dereferer. Example: http://www.dereferer.org/? or http://www.nullrefer.com/?.', + }, + { 'name': 'use_proxy', 'default': 0, 'type': 'bool', diff --git a/couchpotato/core/media/movie/_base/static/list.js b/couchpotato/core/media/movie/_base/static/list.js index e811cd3..6177da4 100644 --- a/couchpotato/core/media/movie/_base/static/list.js +++ b/couchpotato/core/media/movie/_base/static/list.js @@ -41,7 +41,6 @@ var MovieList = new Class({ self.movie_list = new Element('div', { 'events': { 'click:relay(.movie)': function(e, el){ - (e).stopPropagation(); el.retrieve('klass').onClick(e); }, 'mouseenter:relay(.movie)': function(e, el){ @@ -52,9 +51,6 @@ var MovieList = new Class({ (e).stopPropagation(); el.retrieve('klass').onMouseleave(e); }, - 'click:relay(.movie .action)': function(e){ - (e).stopPropagation(); - }, 'change:relay(.movie input)': function(e, el){ (e).stopPropagation(); el = el.getParent(); diff --git a/couchpotato/core/media/movie/_base/static/movie.js b/couchpotato/core/media/movie/_base/static/movie.js index 06380bd..a1a3b60 100644 --- a/couchpotato/core/media/movie/_base/static/movie.js +++ b/couchpotato/core/media/movie/_base/static/movie.js @@ -298,7 +298,7 @@ var Movie = new Class({ onClick: function(e){ var self = this; - if(e.target.get('tag') != 'input'){ + if(e.target.getParents('.actions').length == 0 && e.target != self.select_checkbox){ (e).stopPropagation(); self.openDetails(); } diff --git a/couchpotato/static/scripts/combined.base.min.js b/couchpotato/static/scripts/combined.base.min.js index 26bfc22..815d8d3 100644 --- a/couchpotato/static/scripts/combined.base.min.js +++ b/couchpotato/static/scripts/combined.base.min.js @@ -420,7 +420,10 @@ var CouchPotato = new Class({ openDerefered: function(e, el) { var self = this; e.stop(); - var url = "http://www.dereferer.org/?" + el.get("href"); + var url = el.get("href"); + if (self.getOption("dereferer")) { + url = self.getOption("dereferer") + el.get("href"); + } if (el.get("target") == "_blank" || e.meta && self.isMac() || e.control && !self.isMac()) window.open(url); else window.location = url; }, createUserscriptButtons: function() { diff --git a/couchpotato/static/scripts/combined.plugins.min.js b/couchpotato/static/scripts/combined.plugins.min.js index cc1cdfe..fa0bf8d 100644 --- a/couchpotato/static/scripts/combined.plugins.min.js +++ b/couchpotato/static/scripts/combined.plugins.min.js @@ -457,7 +457,6 @@ var MovieList = new Class({ }) : null, self.movie_list = new Element("div", { events: { "click:relay(.movie)": function(e, el) { - e.stopPropagation(); el.retrieve("klass").onClick(e); }, "mouseenter:relay(.movie)": function(e, el) { @@ -468,9 +467,6 @@ var MovieList = new Class({ e.stopPropagation(); el.retrieve("klass").onMouseleave(e); }, - "click:relay(.movie .action)": function(e) { - e.stopPropagation(); - }, "change:relay(.movie input)": function(e, el) { e.stopPropagation(); el = el.getParent(); @@ -1936,7 +1932,7 @@ var Movie = new Class({ }, onClick: function(e) { var self = this; - if (e.target.get("tag") != "input") { + if (e.target.getParents(".actions").length == 0 && e.target != self.select_checkbox) { e.stopPropagation(); self.openDetails(); } diff --git a/couchpotato/static/scripts/couchpotato.js b/couchpotato/static/scripts/couchpotato.js index 91327d0..76a7fd0 100644 --- a/couchpotato/static/scripts/couchpotato.js +++ b/couchpotato/static/scripts/couchpotato.js @@ -371,7 +371,10 @@ var self = this; (e).stop(); - var url = 'http://www.dereferer.org/?' + el.get('href'); + var url = el.get('href'); + if(self.getOption('dereferer')){ + url = self.getOption('dereferer') + el.get('href'); + } if(el.get('target') == '_blank' || (e.meta && self.isMac()) || (e.control && !self.isMac())) window.open(url); diff --git a/couchpotato/templates/index.html b/couchpotato/templates/index.html index 17ccaac..90bf959 100644 --- a/couchpotato/templates/index.html +++ b/couchpotato/templates/index.html @@ -80,6 +80,7 @@ App.setup({ 'dev': dev, 'base_url': {{ json_encode(Env.get('web_base')) }}, + 'dereferer': {{ json_encode(Env.setting('dereferer')) }}, 'args': {{ json_encode(Env.get('args', unicode = True)) }}, 'options': {{ json_encode(('%s' % Env.get('options'))) }}, 'app_dir': {{ json_encode(Env.get('app_dir', unicode = True)) }}, From 9afc538a6192fd20690e5db0da00ede55260d877 Mon Sep 17 00:00:00 2001 From: Ruud Date: Tue, 18 Aug 2015 13:45:28 +0200 Subject: [PATCH 266/301] Remove trailing seperator when setting directory in Transmission close #5154 --- couchpotato/core/downloaders/transmission.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/couchpotato/core/downloaders/transmission.py b/couchpotato/core/downloaders/transmission.py index 697f22a..78f8fd7 100644 --- a/couchpotato/core/downloaders/transmission.py +++ b/couchpotato/core/downloaders/transmission.py @@ -68,7 +68,7 @@ class Transmission(DownloaderBase): if self.conf('directory'): if os.path.isdir(self.conf('directory')): - params['download-dir'] = self.conf('directory') + params['download-dir'] = self.conf('directory').rstrip(os.path.sep) else: log.error('Download directory from Transmission settings: %s doesn\'t exist', self.conf('directory')) @@ -147,7 +147,7 @@ class Transmission(DownloaderBase): status = 'failed' elif torrent['status'] == 0 and torrent['percentDone'] == 1: status = 'completed' - elif torrent['status'] == 16 and torrent['percentDone'] == 1: + elif torrent['status'] == 16 and torrent['percentDone'] == 1: status = 'completed' elif torrent['status'] in [5, 6]: status = 'seeding' From 24ad0545a180c6d0ed15eae8d7bd330db685c0ce Mon Sep 17 00:00:00 2001 From: Ruud Date: Tue, 18 Aug 2015 15:32:40 +0200 Subject: [PATCH 267/301] Alternative docker file close #5161 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fca0793..84316a2 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Linux: * Open your browser and go to `http://localhost:5050/` Docker: -* You can use [razorgirl's Dockerfile](https://github.com/razorgirl/docker-couchpotato) to quickly build your own isolated app container. It's based on the Linux instructions above. For more info about Docker check out the [official website](https://www.docker.com). +* You can use [linuxserver.io](https://github.com/linuxserver/docker-couchpotato) or [razorgirl's](https://github.com/razorgirl/docker-couchpotato) to quickly build your own isolated app container. It's based on the Linux instructions above. For more info about Docker check out the [official website](https://www.docker.com). FreeBSD : From 4ba73871e41753122be6eb840275c8cc0361e735 Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 19 Aug 2015 13:56:06 +0200 Subject: [PATCH 268/301] SAB fail when no ids are returned --- couchpotato/core/downloaders/sabnzbd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/core/downloaders/sabnzbd.py b/couchpotato/core/downloaders/sabnzbd.py index 4859209..3f004e7 100644 --- a/couchpotato/core/downloaders/sabnzbd.py +++ b/couchpotato/core/downloaders/sabnzbd.py @@ -73,7 +73,7 @@ class Sabnzbd(DownloaderBase): return False log.debug('Result from SAB: %s', sab_data) - if sab_data.get('status') and not sab_data.get('error'): + if sab_data.get('status') and not sab_data.get('error') and len(sab_data.get('nzo_ids', []) > 0): log.info('NZB sent to SAB successfully.') if filedata: return self.downloadReturnId(sab_data.get('nzo_ids')[0]) From c1fa314a971169d7abf1b61266a9a83f87eb7e0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joel=20K=C3=A5berg?= Date: Tue, 11 Aug 2015 14:43:09 +0200 Subject: [PATCH 269/301] Merge pull request #5208 from gitter-badger/gitter-badge Add a Gitter chat badge to README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index d2ed348..fa59427 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ CouchPotato ===== +[![Join the chat at https://gitter.im/RuudBurger/CouchPotatoServer](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/RuudBurger/CouchPotatoServer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + CouchPotato (CP) is an automatic NZB and torrent downloader. You can keep a "movies I want"-list and it will search for NZBs/torrents of these movies every X hours. Once a movie is found, it will send it to SABnzbd or download the torrent to a specified directory. From 163b79a3040bf553dca4f7a9aafdf3018d9a7a30 Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 19 Aug 2015 14:31:00 +0200 Subject: [PATCH 270/301] Development description --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index fa59427..fe25ac0 100644 --- a/README.md +++ b/README.md @@ -68,3 +68,16 @@ FreeBSD: * If not default install, specify options with startup flags in `ee /etc/rc.conf` * Finally, `service couchpotato start` * Open your browser and go to: `http://server:5050/` + + +## Development + +For development you need a few tools that build and compress scss -> css and combine the javascript files. +[Node/NPM](https://nodejs.org/), [Grunt](http://gruntjs.com/installing-grunt), [Compass](http://compass-style.org/install/) + +After you've got these tools you can install the packages using `npm install`. Once this process has finished you can start CP using the command `grunt`. This will start all the needed tools and watches any files for changes. +You can now change css and javascript and it wil reload the page when needed. + +By default it will combine files used in the core folder. If you're adding a new .scss or .js file, you might need to add it and then restart the grunt process for it to combine it properly. + +Don't forget to enable development inside the CP settings. This disables some functions and also makes sure javascript rrors are pushed to console instead of the log. From 3c579659f3fa13465db14de15a7f62c48a386d37 Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 19 Aug 2015 15:16:59 +0200 Subject: [PATCH 271/301] Use env if available --- Gruntfile.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Gruntfile.js b/Gruntfile.js index f65bdc9..25ea797 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -8,6 +8,7 @@ module.exports = function(grunt){ // Configurable paths var config = { + python: grunt.file.exists('./_env/bin/python') ? './_env/bin/python' : 'python', tmp: '.tmp', base: 'couchpotato', css_dest: 'couchpotato/static/style/combined.min.css', @@ -140,7 +141,7 @@ module.exports = function(grunt){ shell: { runCouchPotato: { - command: 'python CouchPotato.py', + command: '<%= config.python %> CouchPotato.py', options: { stdout: true, stderr: true From 94a7b595a20415bff60f35a364b116745f5d403e Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 19 Aug 2015 15:43:24 +0200 Subject: [PATCH 272/301] Don't need build tools if you only add .py files --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index fe25ac0..6e5f612 100644 --- a/README.md +++ b/README.md @@ -72,8 +72,9 @@ FreeBSD: ## Development -For development you need a few tools that build and compress scss -> css and combine the javascript files. -[Node/NPM](https://nodejs.org/), [Grunt](http://gruntjs.com/installing-grunt), [Compass](http://compass-style.org/install/) +Be sure you're running the latest version of [Python 2.7](http://python.org/). + +If you're going to add styling or doing some javascript work you'll need a few tools that build and compress scss -> css and combine the javascript files. [Node/NPM](https://nodejs.org/), [Grunt](http://gruntjs.com/installing-grunt), [Compass](http://compass-style.org/install/) After you've got these tools you can install the packages using `npm install`. Once this process has finished you can start CP using the command `grunt`. This will start all the needed tools and watches any files for changes. You can now change css and javascript and it wil reload the page when needed. From 03b0407d7acaf98dc6b16da98e78d9412e04a30e Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 19 Aug 2015 15:44:36 +0200 Subject: [PATCH 273/301] Ignore env dir --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 873da1c..666da1a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,8 @@ *.pyc /data/ +/_env/ /_source/ .project .pydevproject node_modules -.tmp \ No newline at end of file +.tmp From 371184213ab532c276c42546e8e8b87a12b55ee3 Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 19 Aug 2015 15:44:50 +0200 Subject: [PATCH 274/301] TMDB https --- couchpotato/core/media/movie/providers/info/themoviedb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/core/media/movie/providers/info/themoviedb.py b/couchpotato/core/media/movie/providers/info/themoviedb.py index 0f4f42d..17110af 100644 --- a/couchpotato/core/media/movie/providers/info/themoviedb.py +++ b/couchpotato/core/media/movie/providers/info/themoviedb.py @@ -197,7 +197,7 @@ class TheMovieDb(MovieProvider): params = tryUrlencode(params) try: - url = 'http://api.themoviedb.org/3/%s?api_key=%s%s' % (call, self.conf('api_key'), '&%s' % params if params else '') + url = 'https://api.themoviedb.org/3/%s?api_key=%s%s' % (call, self.conf('api_key'), '&%s' % params if params else '') data = self.getJsonData(url, show_error = False) except: log.debug('Movie not found: %s, %s', (call, params)) From 7fa4bc56375877381a931cf441b9887c41c55c3b Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 19 Aug 2015 15:55:38 +0200 Subject: [PATCH 275/301] Requests update --- libs/requests/adapters.py | 7 ++++--- libs/requests/api.py | 4 ++-- libs/requests/sessions.py | 14 +++++++++----- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/libs/requests/adapters.py b/libs/requests/adapters.py index edc1af6..cdc5744 100644 --- a/libs/requests/adapters.py +++ b/libs/requests/adapters.py @@ -35,6 +35,7 @@ from .auth import _basic_auth_str DEFAULT_POOLBLOCK = False DEFAULT_POOLSIZE = 10 DEFAULT_RETRIES = 0 +DEFAULT_POOL_TIMEOUT = None class BaseAdapter(object): @@ -326,8 +327,8 @@ class HTTPAdapter(BaseAdapter): :param request: The :class:`PreparedRequest ` being sent. :param stream: (optional) Whether to stream the request content. :param timeout: (optional) How long to wait for the server to send - data before giving up, as a float, or a (`connect timeout, read - timeout `_) tuple. + data before giving up, as a float, or a :ref:`(connect timeout, + read timeout) ` tuple. :type timeout: float or tuple :param verify: (optional) Whether to verify SSL certificates. :param cert: (optional) Any user-provided SSL certificate to be trusted. @@ -375,7 +376,7 @@ class HTTPAdapter(BaseAdapter): if hasattr(conn, 'proxy_pool'): conn = conn.proxy_pool - low_conn = conn._get_conn(timeout=timeout) + low_conn = conn._get_conn(timeout=DEFAULT_POOL_TIMEOUT) try: low_conn.putrequest(request.method, diff --git a/libs/requests/api.py b/libs/requests/api.py index d40fa38..72a777b 100644 --- a/libs/requests/api.py +++ b/libs/requests/api.py @@ -27,8 +27,8 @@ def request(method, url, **kwargs): :param files: (optional) Dictionary of ``'name': file-like-objects`` (or ``{'name': ('filename', fileobj)}``) for multipart encoding upload. :param auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth. :param timeout: (optional) How long to wait for the server to send data - before giving up, as a float, or a (`connect timeout, read timeout - `_) tuple. + before giving up, as a float, or a :ref:`(connect timeout, read + timeout) ` tuple. :type timeout: float or tuple :param allow_redirects: (optional) Boolean. Set to True if POST/PUT/DELETE redirect following is allowed. :type allow_redirects: bool diff --git a/libs/requests/sessions.py b/libs/requests/sessions.py index 820919e..7c75460 100644 --- a/libs/requests/sessions.py +++ b/libs/requests/sessions.py @@ -63,12 +63,10 @@ def merge_setting(request_setting, session_setting, dict_class=OrderedDict): merged_setting.update(to_key_val_list(request_setting)) # Remove keys that are set to None. - for (k, v) in request_setting.items(): + for (k, v) in merged_setting.items(): if v is None: del merged_setting[k] - merged_setting = dict((k, v) for (k, v) in merged_setting.items() if v is not None) - return merged_setting @@ -275,6 +273,12 @@ class Session(SessionRedirectMixin): >>> s = requests.Session() >>> s.get('http://httpbin.org/get') 200 + + Or as a context manager:: + + >>> with requests.Session() as s: + >>> s.get('http://httpbin.org/get') + 200 """ __attrs__ = [ @@ -418,8 +422,8 @@ class Session(SessionRedirectMixin): :param auth: (optional) Auth tuple or callable to enable Basic/Digest/Custom HTTP Auth. :param timeout: (optional) How long to wait for the server to send - data before giving up, as a float, or a (`connect timeout, read - timeout `_) tuple. + data before giving up, as a float, or a :ref:`(connect timeout, + read timeout) ` tuple. :type timeout: float or tuple :param allow_redirects: (optional) Set to True by default. :type allow_redirects: bool From 8b0c39213a08eb0244bf65f04c86eae5abd57027 Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 19 Aug 2015 16:10:18 +0200 Subject: [PATCH 276/301] Dependencies warnings --- couchpotato/core/_base/_core.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/couchpotato/core/_base/_core.py b/couchpotato/core/_base/_core.py index 30925f4..eeaf96d 100644 --- a/couchpotato/core/_base/_core.py +++ b/couchpotato/core/_base/_core.py @@ -53,6 +53,7 @@ class Core(Plugin): addEvent('app.version', self.version) addEvent('app.load', self.checkDataDir) addEvent('app.load', self.cleanUpFolders) + addEvent('app.load.after', self.dependencies) addEvent('setting.save.core.password', self.md5Password) addEvent('setting.save.core.api_key', self.checkApikey) @@ -73,12 +74,14 @@ class Core(Plugin): except: log.debug('Failed setting default ssl context: %s', traceback.format_exc()) + def dependencies(self): # Check if lxml is available - try: - from lxml import etree - except: - log.error('LXML not available, please install for better/faster scraping support. `http://lxml.de/installation.html`') + try: from lxml import etree + except: log.error('LXML not available, please install for better/faster scraping support: `http://lxml.de/installation.html`') + + try: import OpenSSL + except: log.error('OpenSSL not available, please install for better requests validation: `https://pyopenssl.readthedocs.org/en/latest/install.html`') def md5Password(self, value): return md5(value) if value else '' From fe8f4619bac71cad2465bfebdbcc76cd7e3616c5 Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 19 Aug 2015 16:23:51 +0200 Subject: [PATCH 277/301] Log filtering not working --- couchpotato/core/plugins/log/static/log.scss | 12 ++++++------ couchpotato/static/style/combined.min.css | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/couchpotato/core/plugins/log/static/log.scss b/couchpotato/core/plugins/log/static/log.scss index 9a3dd35..3929ac0 100644 --- a/couchpotato/core/plugins/log/static/log.scss +++ b/couchpotato/core/plugins/log/static/log.scss @@ -91,12 +91,12 @@ - &[data-filter=INFO] .error, - &[data-filter=INFO] .debug, - &[data-filter=ERROR] .debug, - &[data-filter=ERROR] .info, - &[data-filter=DEBUG] .info, - &[data-filter=DEBUG] .error { + [data-filter=INFO] .error, + [data-filter=INFO] .debug, + [data-filter=ERROR] .debug, + [data-filter=ERROR] .info, + [data-filter=DEBUG] .info, + [data-filter=DEBUG] .error { display: none; } } diff --git a/couchpotato/static/style/combined.min.css b/couchpotato/static/style/combined.min.css index eac07a6..93e3afd 100644 --- a/couchpotato/static/style/combined.min.css +++ b/couchpotato/static/style/combined.min.css @@ -372,7 +372,7 @@ .page.log .container .message{float:right;width:86%;white-space:pre-wrap} .page.log .container .error{color:#FFA4A4} .page.log .container .debug span{opacity:.6} -.page.log[data-filter=DEBUG] .error,.page.log[data-filter=DEBUG] .info,.page.log[data-filter=ERROR] .debug,.page.log[data-filter=ERROR] .info,.page.log[data-filter=INFO] .debug,.page.log[data-filter=INFO] .error{display:none} +.page.log [data-filter=DEBUG] .error,.page.log [data-filter=DEBUG] .info,.page.log [data-filter=ERROR] .debug,.page.log [data-filter=ERROR] .info,.page.log [data-filter=INFO] .debug,.page.log [data-filter=INFO] .error{display:none} .report_popup.report_popup{position:fixed;left:0;right:0;bottom:0;top:0;z-index:99999;font-size:14px;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;opacity:1;color:#FFF;pointer-events:auto} .disable_hover .scroll_content>*,.mask,.ripple{pointer-events:none} .report_popup.report_popup .button{display:inline-block;margin:10px 0;padding:10px;color:#FFF} From 54224bd851f77133cede5cce01998775a59e6c11 Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 19 Aug 2015 21:09:58 +0200 Subject: [PATCH 278/301] Update DB lib --- libs/CodernityDB/__init__.py | 2 +- libs/CodernityDB/database.py | 2 +- libs/CodernityDB/database_safe_shared.py | 4 +--- libs/CodernityDB/debug_stuff.py | 2 +- libs/CodernityDB/sharded_hash.py | 2 +- libs/CodernityDB/tree_index.py | 12 ++++++------ 6 files changed, 11 insertions(+), 13 deletions(-) diff --git a/libs/CodernityDB/__init__.py b/libs/CodernityDB/__init__.py index 8399a60..c059538 100644 --- a/libs/CodernityDB/__init__.py +++ b/libs/CodernityDB/__init__.py @@ -16,5 +16,5 @@ # limitations under the License. -__version__ = '0.4.2' +__version__ = '0.5.0' __license__ = "Apache 2.0" diff --git a/libs/CodernityDB/database.py b/libs/CodernityDB/database.py index 064836f..7aa177a 100644 --- a/libs/CodernityDB/database.py +++ b/libs/CodernityDB/database.py @@ -339,7 +339,7 @@ class Database(object): self.__set_main_storage() self.__compat_things() for patch in getattr(ind_obj, 'patchers', ()): # index can patch db object - patch(self) + patch(self, ind_obj) return name def edit_index(self, index, reindex=False, ind_kwargs=None): diff --git a/libs/CodernityDB/database_safe_shared.py b/libs/CodernityDB/database_safe_shared.py index e31100f..72290e8 100644 --- a/libs/CodernityDB/database_safe_shared.py +++ b/libs/CodernityDB/database_safe_shared.py @@ -59,8 +59,7 @@ class SafeDatabase(Database): def __init__(self, path, *args, **kwargs): super(SafeDatabase, self).__init__(path, *args, **kwargs) - self.indexes_locks = defaultdict( - lambda: cdb_environment['rlock_obj']()) + self.indexes_locks = defaultdict(cdb_environment['rlock_obj']) self.close_open_lock = cdb_environment['rlock_obj']() self.main_lock = cdb_environment['rlock_obj']() self.id_revs = {} @@ -94,7 +93,6 @@ class SafeDatabase(Database): def initialize(self, *args, **kwargs): with self.close_open_lock: - self.close_open_lock.acquire() res = super(SafeDatabase, self).initialize(*args, **kwargs) for name in self.indexes_names.iterkeys(): self.indexes_locks[name] = cdb_environment['rlock_obj']() diff --git a/libs/CodernityDB/debug_stuff.py b/libs/CodernityDB/debug_stuff.py index 76cdedf..2dce695 100644 --- a/libs/CodernityDB/debug_stuff.py +++ b/libs/CodernityDB/debug_stuff.py @@ -92,7 +92,7 @@ class DebugTreeBasedIndex(TreeBasedIndex): + nr_of_elements * (self.key_size + self.pointer_size)) node = struct.unpack('<' + self.node_heading_format + self.pointer_format + nr_of_elements * ( - self.key_format + self.pointer_format), + self.key_format + self.pointer_format), data) print node print diff --git a/libs/CodernityDB/sharded_hash.py b/libs/CodernityDB/sharded_hash.py index 08a8c2f..3cf76ac 100644 --- a/libs/CodernityDB/sharded_hash.py +++ b/libs/CodernityDB/sharded_hash.py @@ -40,7 +40,7 @@ from CodernityDB.sharded_index import ShardedIndex self.patchers.append(self.wrap_insert_id_index) @staticmethod - def wrap_insert_id_index(db_obj, clean=False): + def wrap_insert_id_index(db_obj, ind_obj, clean=False): def _insert_id_index(_rev, data): """ Performs insert on **id** index. diff --git a/libs/CodernityDB/tree_index.py b/libs/CodernityDB/tree_index.py index b79805d..4257b44 100644 --- a/libs/CodernityDB/tree_index.py +++ b/libs/CodernityDB/tree_index.py @@ -1565,13 +1565,13 @@ class IU_TreeBasedIndex(Index): def update(self, doc_id, key, u_start=0, u_size=0, u_status='o'): containing_leaf_start, element_index, old_doc_id, old_key, old_start, old_size, old_status = self._find_key_to_update(key, doc_id) + if u_start: + old_start = u_start + if u_size: + old_size = u_size + if u_status: + old_status = u_status new_data = (old_doc_id, old_start, old_size, old_status) - if not u_start: - new_data[1] = u_start - if not u_size: - new_data[2] = u_size - if not u_status: - new_data[3] = u_status self._update_element(containing_leaf_start, element_index, new_data) self._find_key.delete(key) From c7491dfcbfad028b043764d10b51db42ae00cfc1 Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 19 Aug 2015 21:53:34 +0200 Subject: [PATCH 279/301] Delete corrupted release items --- couchpotato/core/plugins/release/main.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/couchpotato/core/plugins/release/main.py b/couchpotato/core/plugins/release/main.py index ab3fffa..41d8241 100644 --- a/couchpotato/core/plugins/release/main.py +++ b/couchpotato/core/plugins/release/main.py @@ -555,6 +555,8 @@ class Release(Plugin): releases.append(doc) except RecordDeleted: pass + except (ValueError, EOFError): + fireEvent('database.delete_corrupted', r.get('_id'), traceback_error = traceback.format_exc(0)) releases = sorted(releases, key = lambda k: k.get('info', {}).get('score', 0), reverse = True) From f8af8a9e909003ac1c0569e20584d3cfaddcccee Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 19 Aug 2015 21:54:31 +0200 Subject: [PATCH 280/301] Do a compact every 7 days --- couchpotato/core/database.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/core/database.py b/couchpotato/core/database.py index 66c4267..bed427e 100644 --- a/couchpotato/core/database.py +++ b/couchpotato/core/database.py @@ -272,7 +272,7 @@ class Database(object): prop_name = 'last_db_compact' last_check = int(Env.prop(prop_name, default = 0)) - if size > 26214400 and last_check < time.time()-604800: # 25MB / 7 days + if last_check < time.time()-604800: # 7 days self.compact() Env.prop(prop_name, value = int(time.time())) From 01be3f30245a957cda5a36e12c13bf3fbb2504a9 Mon Sep 17 00:00:00 2001 From: Ruud Date: Wed, 19 Aug 2015 23:38:17 +0200 Subject: [PATCH 281/301] Don't merge js and css files --- couchpotato/core/_base/clientscript.py | 113 ++--------------- couchpotato/core/_base/updater/main.py | 1 - couchpotato/core/plugins/base.py | 37 +----- couchpotato/templates/database.html | 2 +- couchpotato/templates/index.html | 9 +- couchpotato/templates/login.html | 4 +- libs/minify/__init__.py | 0 libs/minify/cssmin.py | 202 ------------------------------ libs/minify/jsmin.py | 218 --------------------------------- 9 files changed, 18 insertions(+), 568 deletions(-) delete mode 100644 libs/minify/__init__.py delete mode 100644 libs/minify/cssmin.py delete mode 100644 libs/minify/jsmin.py diff --git a/couchpotato/core/_base/clientscript.py b/couchpotato/core/_base/clientscript.py index 69f35f3..ab52003 100644 --- a/couchpotato/core/_base/clientscript.py +++ b/couchpotato/core/_base/clientscript.py @@ -1,13 +1,10 @@ import os -import re from couchpotato.core.event import addEvent -from couchpotato.core.helpers.encoding import ss from couchpotato.core.helpers.variable import tryInt from couchpotato.core.logger import CPLog from couchpotato.core.plugins.base import Plugin from couchpotato.environment import Env -from tornado.web import StaticFileHandler log = CPLog(__name__) @@ -17,7 +14,7 @@ autoload = 'ClientScript' class ClientScript(Plugin): - core_static = { + paths = { 'style': [ 'style/combined.min.css', ], @@ -28,91 +25,24 @@ class ClientScript(Plugin): ], } - watches = {} - - original_paths = {'style': {}, 'script': {}} - paths = {'style': {}, 'script': {}} - comment = { - 'style': '/*** %s:%d ***/\n', - 'script': '// %s:%d\n' - } - - html = { - 'style': '', - 'script': '', - } - def __init__(self): - addEvent('register_style', self.registerStyle) - addEvent('register_script', self.registerScript) - addEvent('clientscript.get_styles', self.getStyles) addEvent('clientscript.get_scripts', self.getScripts) - addEvent('app.load', self.compile) + self.makeRelative() - self.addCore() + def makeRelative(self): - def addCore(self): + for static_type in self.paths: - for static_type in self.core_static: - for rel_path in self.core_static.get(static_type): + updates_paths = [] + for rel_path in self.paths.get(static_type): file_path = os.path.join(Env.get('app_dir'), 'couchpotato', 'static', rel_path) - core_url = 'static/%s' % rel_path - - if static_type == 'script': - self.registerScript(core_url, file_path, position = 'front') - else: - self.registerStyle(core_url, file_path, position = 'front') - - def compile(self): - - # Create cache dir - cache = Env.get('cache_dir') - parent_dir = os.path.join(cache, 'minified') - self.makeDir(parent_dir) - - Env.get('app').add_handlers(".*$", [(Env.get('web_base') + 'minified/(.*)', StaticFileHandler, {'path': parent_dir})]) - - for file_type in ['style', 'script']: - ext = 'js' if file_type is 'script' else 'css' - positions = self.original_paths.get(file_type, {}) - for position in positions: - files = positions.get(position) - self._compile(file_type, files, position, position + '.' + ext) - - def _compile(self, file_type, paths, position, out): - - cache = Env.get('cache_dir') - out_name = out - minified_dir = os.path.join(cache, 'minified') + core_url = 'static/%s?%d' % (rel_path, tryInt(os.path.getmtime(file_path))) - data_combined = '' + updates_paths.append(core_url) - new_paths = [] - for x in paths: - file_path, url_path = x - - f = open(file_path, 'r').read() - - if not Env.get('dev'): - data = f - - data_combined += self.comment.get(file_type) % (ss(file_path), int(os.path.getmtime(file_path))) - data_combined += data + '\n\n' - else: - new_paths.append(x) - - # Combine all files together with some comments - if not Env.get('dev'): - - out_path = os.path.join(minified_dir, out_name) - self.createFile(out_path, data_combined.strip()) - - minified_url = 'minified/%s?%s' % (out_name, tryInt(os.path.getmtime(out))) - new_paths.append((out_path, {'url': minified_url})) - - self.paths[file_type][position] = new_paths + self.paths[static_type] = updates_paths def getStyles(self, *args, **kwargs): return self.get('style', *args, **kwargs) @@ -120,27 +50,8 @@ class ClientScript(Plugin): def getScripts(self, *args, **kwargs): return self.get('script', *args, **kwargs) - def get(self, type, location = 'head'): - if type in self.paths and location in self.paths[type]: - paths = self.paths[type][location] - return [x[1] for x in paths] + def get(self, type): + if type in self.paths: + return self.paths[type] return [] - - def registerStyle(self, api_path, file_path, position = 'head'): - self.register(api_path, file_path, 'style', position) - - def registerScript(self, api_path, file_path, position = 'head'): - self.register(api_path, file_path, 'script', position) - - def register(self, api_path, file_path, type, location): - - api_path = '%s?%s' % (api_path, tryInt(os.path.getmtime(file_path))) - - if not self.original_paths[type].get(location): - self.original_paths[type][location] = [] - self.original_paths[type][location].append((file_path, api_path)) - - if not self.paths[type].get(location): - self.paths[type][location] = [] - self.paths[type][location].append((file_path, api_path)) diff --git a/couchpotato/core/_base/updater/main.py b/couchpotato/core/_base/updater/main.py index 3393930..d739911 100644 --- a/couchpotato/core/_base/updater/main.py +++ b/couchpotato/core/_base/updater/main.py @@ -163,7 +163,6 @@ class BaseUpdater(Plugin): update_failed = False update_version = None last_check = 0 - auto_register_static = False def doUpdate(self): pass diff --git a/couchpotato/core/plugins/base.py b/couchpotato/core/plugins/base.py index e149381..b1db842 100644 --- a/couchpotato/core/plugins/base.py +++ b/couchpotato/core/plugins/base.py @@ -1,17 +1,14 @@ import threading from urllib import quote, getproxies from urlparse import urlparse -import glob -import inspect import os.path -import re import time import traceback from couchpotato.core.event import fireEvent, addEvent from couchpotato.core.helpers.encoding import ss, toSafeString, \ toUnicode, sp -from couchpotato.core.helpers.variable import getExt, md5, isLocalIP, scanForPassword, tryInt, getIdentifier, \ +from couchpotato.core.helpers.variable import md5, isLocalIP, scanForPassword, tryInt, getIdentifier, \ randomString from couchpotato.core.logger import CPLog from couchpotato.environment import Env @@ -19,8 +16,6 @@ import requests from requests.packages.urllib3 import Timeout from requests.packages.urllib3.exceptions import MaxRetryError from tornado import template -from tornado.web import StaticFileHandler - log = CPLog(__name__) @@ -32,7 +27,6 @@ class Plugin(object): plugin_path = None enabled_option = 'enabled' - auto_register_static = False _needs_shutdown = False _running = None @@ -57,9 +51,6 @@ class Plugin(object): addEvent('plugin.running', self.isRunning) self._running = [] - if self.auto_register_static: - self.registerStatic(inspect.getfile(self.__class__)) - # Setup database if self._database: addEvent('database.setup', self.databaseSetup) @@ -89,32 +80,6 @@ class Plugin(object): t = template.Template(open(os.path.join(os.path.dirname(parent_file), templ), 'r').read()) return t.generate(**params) - def registerStatic(self, plugin_file, add_to_head = True): - - # Register plugin path - self.plugin_path = os.path.dirname(plugin_file) - static_folder = toUnicode(os.path.join(self.plugin_path, 'static')) - - if not os.path.isdir(static_folder): - return - - # Get plugin_name from PluginName - s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', self.__class__.__name__) - class_name = re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower() - - # View path - path = 'static/plugin/%s/' % class_name - - # Add handler to Tornado - Env.get('app').add_handlers(".*$", [(Env.get('web_base') + path + '(.*)', StaticFileHandler, {'path': static_folder})]) - - # Register for HTML - if add_to_head: - for f in glob.glob(os.path.join(self.plugin_path, 'static', '*')): - ext = getExt(f) - if ext in ['js', 'css']: - fireEvent('register_%s' % ('script' if ext in 'js' else 'style'), path + os.path.basename(f), f) - def createFile(self, path, content, binary = False): path = sp(path) diff --git a/couchpotato/templates/database.html b/couchpotato/templates/database.html index e512538..3643f9c 100644 --- a/couchpotato/templates/database.html +++ b/couchpotato/templates/database.html @@ -4,7 +4,7 @@ - + {% end %} - {% for url in fireEvent('clientscript.get_scripts', location = 'head', single = True) %} - {% end %} - {% for url in fireEvent('clientscript.get_styles', location = 'head', single = True) %} - {% end %} - diff --git a/couchpotato/templates/login.html b/couchpotato/templates/login.html index 5d79dff..c9f4d1d 100644 --- a/couchpotato/templates/login.html +++ b/couchpotato/templates/login.html @@ -8,9 +8,9 @@ - {% for url in fireEvent('clientscript.get_styles', location = 'front', single = True) %} + {% for url in fireEvent('clientscript.get_styles', single = True) %} {% end %} - {% for url in fireEvent('clientscript.get_scripts', location = 'front', single = True) %}{% if 'combined.plugins' not in url %} + {% for url in fireEvent('clientscript.get_scripts', single = True) %}{% if 'combined.plugins' not in url %} {% end %}{% end %} diff --git a/libs/minify/__init__.py b/libs/minify/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/libs/minify/cssmin.py b/libs/minify/cssmin.py deleted file mode 100644 index 09beb19..0000000 --- a/libs/minify/cssmin.py +++ /dev/null @@ -1,202 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# `cssmin.py` - A Python port of the YUI CSS compressor. - - -from StringIO import StringIO # The pure-Python StringIO supports unicode. -import re - - -__version__ = '0.1.1' - - -def remove_comments(css): - """Remove all CSS comment blocks.""" - - iemac = False - preserve = False - comment_start = css.find("/*") - while comment_start >= 0: - # Preserve comments that look like `/*!...*/`. - # Slicing is used to make sure we don"t get an IndexError. - preserve = css[comment_start + 2:comment_start + 3] == "!" - - comment_end = css.find("*/", comment_start + 2) - if comment_end < 0: - if not preserve: - css = css[:comment_start] - break - elif comment_end >= (comment_start + 2): - if css[comment_end - 1] == "\\": - # This is an IE Mac-specific comment; leave this one and the - # following one alone. - comment_start = comment_end + 2 - iemac = True - elif iemac: - comment_start = comment_end + 2 - iemac = False - elif not preserve: - css = css[:comment_start] + css[comment_end + 2:] - else: - comment_start = comment_end + 2 - comment_start = css.find("/*", comment_start) - - return css - - -def remove_unnecessary_whitespace(css): - """Remove unnecessary whitespace characters.""" - - def pseudoclasscolon(css): - - """ - Prevents 'p :link' from becoming 'p:link'. - - Translates 'p :link' into 'p ___PSEUDOCLASSCOLON___link'; this is - translated back again later. - """ - - regex = re.compile(r"(^|\})(([^\{\:])+\:)+([^\{]*\{)") - match = regex.search(css) - while match: - css = ''.join([ - css[:match.start()], - match.group().replace(":", "___PSEUDOCLASSCOLON___"), - css[match.end():]]) - match = regex.search(css) - return css - - css = pseudoclasscolon(css) - # Remove spaces from before things. - css = re.sub(r"\s+([!{};:>+\(\)\],])", r"\1", css) - - # If there is a `@charset`, then only allow one, and move to the beginning. - css = re.sub(r"^(.*)(@charset \"[^\"]*\";)", r"\2\1", css) - css = re.sub(r"^(\s*@charset [^;]+;\s*)+", r"\1", css) - - # Put the space back in for a few cases, such as `@media screen` and - # `(-webkit-min-device-pixel-ratio:0)`. - css = re.sub(r"\band\(", "and (", css) - - # Put the colons back. - css = css.replace('___PSEUDOCLASSCOLON___', ':') - - # Remove spaces from after things. - css = re.sub(r"([!{}:;>+\(\[,])\s+", r"\1", css) - - return css - - -def remove_unnecessary_semicolons(css): - """Remove unnecessary semicolons.""" - - return re.sub(r";+\}", "}", css) - - -def remove_empty_rules(css): - """Remove empty rules.""" - - return re.sub(r"[^\}\{]+\{\}", "", css) - - -def normalize_rgb_colors_to_hex(css): - """Convert `rgb(51,102,153)` to `#336699`.""" - - regex = re.compile(r"rgb\s*\(\s*([0-9,\s]+)\s*\)") - match = regex.search(css) - while match: - colors = match.group(1).split(",") - hexcolor = '#%.2x%.2x%.2x' % tuple(map(int, colors)) - css = css.replace(match.group(), hexcolor) - match = regex.search(css) - return css - - -def condense_zero_units(css): - """Replace `0(px, em, %, etc)` with `0`.""" - - return re.sub(r"([\s:])(0)(px|em|%|in|cm|mm|pc|pt|ex)", r"\1\2", css) - - -def condense_multidimensional_zeros(css): - """Replace `:0 0 0 0;`, `:0 0 0;` etc. with `:0;`.""" - - css = css.replace(":0 0 0 0;", ":0;") - css = css.replace(":0 0 0;", ":0;") - css = css.replace(":0 0;", ":0;") - - # Revert `background-position:0;` to the valid `background-position:0 0;`. - css = css.replace("background-position:0;", "background-position:0 0;") - - return css - - -def condense_floating_points(css): - """Replace `0.6` with `.6` where possible.""" - - return re.sub(r"(:|\s)0+\.(\d+)", r"\1.\2", css) - - -def condense_hex_colors(css): - """Shorten colors from #AABBCC to #ABC where possible.""" - - regex = re.compile(r"([^\"'=\s])(\s*)#([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])([0-9a-fA-F])") - match = regex.search(css) - while match: - first = match.group(3) + match.group(5) + match.group(7) - second = match.group(4) + match.group(6) + match.group(8) - if first.lower() == second.lower(): - css = css.replace(match.group(), match.group(1) + match.group(2) + '#' + first) - match = regex.search(css, match.end() - 3) - else: - match = regex.search(css, match.end()) - return css - - -def condense_whitespace(css): - """Condense multiple adjacent whitespace characters into one.""" - - return re.sub(r"\s+", " ", css) - - -def condense_semicolons(css): - """Condense multiple adjacent semicolon characters into one.""" - - return re.sub(r";;+", ";", css) - - -def wrap_css_lines(css, line_length): - """Wrap the lines of the given CSS to an approximate length.""" - - lines = [] - line_start = 0 - for i, char in enumerate(css): - # It's safe to break after `}` characters. - if char == '}' and (i - line_start >= line_length): - lines.append(css[line_start:i + 1]) - line_start = i + 1 - - if line_start < len(css): - lines.append(css[line_start:]) - return '\n'.join(lines) - - -def cssmin(css, wrap = None): - css = remove_comments(css) - css = condense_whitespace(css) - # A pseudo class for the Box Model Hack - # (see http://tantek.com/CSS/Examples/boxmodelhack.html) - css = css.replace('"\\"}\\""', "___PSEUDOCLASSBMH___") - #css = remove_unnecessary_whitespace(css) - css = remove_unnecessary_semicolons(css) - css = condense_zero_units(css) - css = condense_multidimensional_zeros(css) - css = condense_floating_points(css) - css = normalize_rgb_colors_to_hex(css) - css = condense_hex_colors(css) - if wrap is not None: - css = wrap_css_lines(css, wrap) - css = css.replace("___PSEUDOCLASSBMH___", '"\\"}\\""') - css = condense_semicolons(css) - return css.strip() diff --git a/libs/minify/jsmin.py b/libs/minify/jsmin.py deleted file mode 100644 index a1b81f9..0000000 --- a/libs/minify/jsmin.py +++ /dev/null @@ -1,218 +0,0 @@ -#!/usr/bin/python - -# This code is original from jsmin by Douglas Crockford, it was translated to -# Python by Baruch Even. The original code had the following copyright and -# license. -# -# /* jsmin.c -# 2007-05-22 -# -# Copyright (c) 2002 Douglas Crockford (www.crockford.com) -# -# Permission is hereby granted, free of charge, to any person obtaining a copy of -# this software and associated documentation files (the "Software"), to deal in -# the Software without restriction, including without limitation the rights to -# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -# of the Software, and to permit persons to whom the Software is furnished to do -# so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in all -# copies or substantial portions of the Software. -# -# The Software shall be used for Good, not Evil. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -# SOFTWARE. -# */ - -from StringIO import StringIO - -def jsmin(js): - ins = StringIO(js) - outs = StringIO() - JavascriptMinify().minify(ins, outs) - str = outs.getvalue() - if len(str) > 0 and str[0] == '\n': - str = str[1:] - return str - -def isAlphanum(c): - """return true if the character is a letter, digit, underscore, - dollar sign, or non-ASCII character. - """ - return ((c >= 'a' and c <= 'z') or (c >= '0' and c <= '9') or - (c >= 'A' and c <= 'Z') or c == '_' or c == '$' or c == '\\' or (c is not None and ord(c) > 126)); - -class UnterminatedComment(Exception): - pass - -class UnterminatedStringLiteral(Exception): - pass - -class UnterminatedRegularExpression(Exception): - pass - -class JavascriptMinify(object): - - def _outA(self): - self.outstream.write(self.theA) - def _outB(self): - self.outstream.write(self.theB) - - def _get(self): - """return the next character from stdin. Watch out for lookahead. If - the character is a control character, translate it to a space or - linefeed. - """ - c = self.theLookahead - self.theLookahead = None - if c == None: - c = self.instream.read(1) - if c >= ' ' or c == '\n': - return c - if c == '': # EOF - return '\000' - if c == '\r': - return '\n' - return ' ' - - def _peek(self): - self.theLookahead = self._get() - return self.theLookahead - - def _next(self): - """get the next character, excluding comments. peek() is used to see - if a '/' is followed by a '/' or '*'. - """ - c = self._get() - if c == '/': - p = self._peek() - if p == '/': - c = self._get() - while c > '\n': - c = self._get() - return c - if p == '*': - c = self._get() - while 1: - c = self._get() - if c == '*': - if self._peek() == '/': - self._get() - return ' ' - if c == '\000': - raise UnterminatedComment() - - return c - - def _action(self, action): - """do something! What you do is determined by the argument: - 1 Output A. Copy B to A. Get the next B. - 2 Copy B to A. Get the next B. (Delete A). - 3 Get the next B. (Delete B). - action treats a string as a single character. Wow! - action recognizes a regular expression if it is preceded by ( or , or =. - """ - if action <= 1: - self._outA() - - if action <= 2: - self.theA = self.theB - if self.theA == "'" or self.theA == '"': - while 1: - self._outA() - self.theA = self._get() - if self.theA == self.theB: - break - if self.theA <= '\n': - raise UnterminatedStringLiteral() - if self.theA == '\\': - self._outA() - self.theA = self._get() - - - if action <= 3: - self.theB = self._next() - if self.theB == '/' and (self.theA == '(' or self.theA == ',' or - self.theA == '=' or self.theA == ':' or - self.theA == '[' or self.theA == '?' or - self.theA == '!' or self.theA == '&' or - self.theA == '|' or self.theA == ';' or - self.theA == '{' or self.theA == '}' or - self.theA == '\n'): - self._outA() - self._outB() - while 1: - self.theA = self._get() - if self.theA == '/': - break - elif self.theA == '\\': - self._outA() - self.theA = self._get() - elif self.theA <= '\n': - raise UnterminatedRegularExpression() - self._outA() - self.theB = self._next() - - - def _jsmin(self): - """Copy the input to the output, deleting the characters which are - insignificant to JavaScript. Comments will be removed. Tabs will be - replaced with spaces. Carriage returns will be replaced with linefeeds. - Most spaces and linefeeds will be removed. - """ - self.theA = '\n' - self._action(3) - - while self.theA != '\000': - if self.theA == ' ': - if isAlphanum(self.theB): - self._action(1) - else: - self._action(2) - elif self.theA == '\n': - if self.theB in ['{', '[', '(', '+', '-']: - self._action(1) - elif self.theB == ' ': - self._action(3) - else: - if isAlphanum(self.theB): - self._action(1) - else: - self._action(2) - else: - if self.theB == ' ': - if isAlphanum(self.theA): - self._action(1) - else: - self._action(3) - elif self.theB == '\n': - if self.theA in ['}', ']', ')', '+', '-', '"', '\'']: - self._action(1) - else: - if isAlphanum(self.theA): - self._action(1) - else: - self._action(3) - else: - self._action(1) - - def minify(self, instream, outstream): - self.instream = instream - self.outstream = outstream - self.theA = '\n' - self.theB = None - self.theLookahead = None - - self._jsmin() - self.instream.close() - -if __name__ == '__main__': - import sys - jsm = JavascriptMinify() - jsm.minify(sys.stdin, sys.stdout) From 8f9aafadc2c55073df74b07dc94f2c099a1ca499 Mon Sep 17 00:00:00 2001 From: Ruud Date: Thu, 20 Aug 2015 22:00:25 +0200 Subject: [PATCH 282/301] Sort images for poster --- couchpotato/core/media/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/couchpotato/core/media/__init__.py b/couchpotato/core/media/__init__.py index 0d98600..3642c10 100755 --- a/couchpotato/core/media/__init__.py +++ b/couchpotato/core/media/__init__.py @@ -88,8 +88,13 @@ class MediaBase(Plugin): if len(existing_files) == 0: del existing_files[file_type] + images = image_urls.get(image_type, []) + for y in ['SX300', 'tmdb']: + initially_try = [x for x in images if y in x] + images[:-1] = initially_try + # Loop over type - for image in image_urls.get(image_type, []): + for image in images: if not isinstance(image, (str, unicode)): continue From 595654bcc475416108ee9f3b749cb7a441a2fb08 Mon Sep 17 00:00:00 2001 From: Ruud Date: Fri, 21 Aug 2015 16:18:29 +0200 Subject: [PATCH 283/301] Layout tweaks --- couchpotato/core/media/movie/_base/static/movie.js | 42 +++--- .../core/media/movie/_base/static/movie.scss | 163 ++++++++++----------- couchpotato/static/scripts/combined.plugins.min.js | 10 +- couchpotato/static/style/combined.min.css | 67 ++++----- 4 files changed, 138 insertions(+), 144 deletions(-) diff --git a/couchpotato/core/media/movie/_base/static/movie.js b/couchpotato/core/media/movie/_base/static/movie.js index a1a3b60..9c8fad9 100644 --- a/couchpotato/core/media/movie/_base/static/movie.js +++ b/couchpotato/core/media/movie/_base/static/movie.js @@ -248,33 +248,31 @@ var Movie = new Class({ self.el.adopt( self.select_checkbox = new Element('input[type=checkbox]'), new Element('div.poster_container').adopt( - self.thumbnail = thumbnail, + thumbnail, self.actions_el = new Element('div.actions') ), - self.data_container = new Element('div.data.light').grab( - self.info_container = new Element('div.info').adopt( - new Element('div.title').adopt( - self.title = new Element('span', { - 'text': self.getTitle() || 'n/a' - }), - self.year = new Element('div.year', { - 'text': self.data.info.year || 'n/a' - }) - ), - self.eta = eta_date && (now+8035200 > eta) ? new Element('div.eta', { - 'text': eta_date, - 'title': 'ETA' - }) : null, - self.quality = new Element('div.quality'), - self.rating = rating ? new Element('div.rating[title='+rating[0]+']').adopt( - stars, - new Element('span.votes[text=('+rating.join(' / ')+')][title=Votes]') - ) : null - ) + new Element('div.info').adopt( + new Element('div.title').adopt( + new Element('span', { + 'text': self.getTitle() || 'n/a' + }), + new Element('div.year', { + 'text': self.data.info.year || 'n/a' + }) + ), + eta_date && (now+8035200 > eta) ? new Element('div.eta', { + 'text': eta_date, + 'title': 'ETA' + }) : null, + self.quality = new Element('div.quality'), + rating ? new Element('div.rating[title='+rating[0]+']').adopt( + stars, + new Element('span.votes[text=('+rating.join(' / ')+')][title=Votes]') + ) : null ) ); - if(!self.thumbnail) + if(!thumbnail) self.el.addClass('no_thumbnail'); // Add profile diff --git a/couchpotato/core/media/movie/_base/static/movie.scss b/couchpotato/core/media/movie/_base/static/movie.scss index c714b9f..074ffef 100644 --- a/couchpotato/core/media/movie/_base/static/movie.scss +++ b/couchpotato/core/media/movie/_base/static/movie.scss @@ -43,6 +43,7 @@ $mass_edit_height: 44px; } .movie { + .ripple { display: none; } @@ -75,8 +76,11 @@ $mass_edit_height: 44px; } .quality { + font-weight: 400; + span { - background: rgba(0,0,0,.2); + display: inline-block; + background: rgba(0,0,0,.25); border: 1px solid transparent; color: #FFF; border-radius: 1px; @@ -146,6 +150,7 @@ $mass_edit_height: 44px; } .actions { + transform: rotateY(360deg); @include media-phablet { pointer-events: none; @@ -180,77 +185,70 @@ $mass_edit_height: 44px; display: none; } - .data { + .info { padding: $padding/2 $padding; + display: flex; + flex-flow: row nowrap; + align-items: center; @include media-phablet { + display: block; padding: $padding/2; } - .info { - - display: flex; - flex-flow: row nowrap; - align-items: center; + .title { + flex: 1 auto; @include media-phablet { - display: block; + display: flex; + flex-flow: row nowrap; } - .title { - flex: 1 auto; + span { + transition: margin 200ms $cubic; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; @include media-phablet { - display: flex; - flex-flow: row nowrap; - } - - span { - transition: margin 200ms $cubic; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - - @include media-phablet { - width: 100%; - } - + width: 100%; } - .year { - display: inline-block; - margin: 0 10px; - opacity: .5; - } } - .eta { - font-size: .8em; + .year { + display: inline-block; + margin: 0 10px; opacity: .5; - margin-right: 4px; } + } - .quality { - clear: both; - overflow: hidden; + .eta { + font-size: .8em; + opacity: .5; + margin-right: 4px; + } - span { - float: left; - font-size: .7em; - margin: 2px 0 0 2px; + .quality { + clear: both; + overflow: hidden; - @include media-phablet { - margin: 2px 2px 0 0; - } + span { + float: left; + font-size: .7em; + margin: 2px 0 0 2px; + + @include media-phablet { + margin: 2px 2px 0 0; } } + } - .rating .vote { - display: inline-block; - min-width: 60px; - text-align: right; - } + .rating .vote { + display: inline-block; + min-width: 60px; + text-align: right; } } @@ -300,7 +298,7 @@ $mass_edit_height: 44px; &.with_navigation .movie { &:hover, &.checked { - .data .info .title span { + .info .title span { margin-left: $padding; } } @@ -336,22 +334,22 @@ $mass_edit_height: 44px; cursor: pointer; flex: 1 auto; width: 150px; - max-width: 14.285%; + max-width: 16.666666667%; border: 0 solid transparent; border-width: 0 $padding/2; @include media-desktop-plus { - max-width: 16.666666667%; + max-width: 20%; border-width: 0 $padding/2.5; } @include media-desktop { - max-width: 20%; + max-width: 33.333%; border-width: 0 $padding/3; } @include media-tablet { - max-width: 33.333%; + max-width: 50%; border-width: 0 $padding/4; } @@ -381,44 +379,41 @@ $mass_edit_height: 44px; width: 100%; } - .data { + .info { clear: both; font-size: .85em; - .info { - - .title { - display: flex; - padding: 3px 0; - - span { - flex: 1 auto; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - } + .title { + display: flex; + padding: 3px 0; - .year { - display: inline-block; - margin-left: 5px; - opacity: .5; - } + span { + flex: 1 auto; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } - .eta { + .year { + display: inline-block; + margin-left: 5px; opacity: .5; - float: right; - margin-left: 4px; } + } - .quality { - white-space: nowrap; - overflow: hidden; + .eta { + opacity: .5; + float: right; + margin-left: 4px; + } - span { - font-size: .8em; - margin-right: 2px; - } + .quality { + white-space: nowrap; + overflow: hidden; + + span { + font-size: .8em; + margin-right: 2px; } } } @@ -496,7 +491,7 @@ $mass_edit_height: 44px; background: rgba(0,0,0,.6); border-radius: $border_radius 0 0 $border_radius; opacity: 0; - transform: translateZ(0); + transform: rotateY(360deg); backface-visibility: hidden; transition: opacity 300ms ease 400ms; z-index: 1; @@ -543,7 +538,7 @@ $mass_edit_height: 44px; background: $background_color; border-radius: $border_radius 0 0 $border_radius; overflow-y: auto; - transform: translateX(100%) translateZ(0); + transform: translateX(100%) rotateY(360deg); transition: transform 350ms $cubic; @include media-phablet { @@ -770,7 +765,7 @@ $mass_edit_height: 44px; } } - .thumbnail, .data { + .thumbnail { display: none; } } @@ -946,7 +941,7 @@ $mass_edit_height: 44px; .background { opacity: .3; transition: all 300ms; - transform: scale(1.05) translateZ(0); + transform: scale(1.05) rotateY(360deg); background: no-repeat center; background-size: cover; position: absolute; diff --git a/couchpotato/static/scripts/combined.plugins.min.js b/couchpotato/static/scripts/combined.plugins.min.js index fa0bf8d..477b1bc 100644 --- a/couchpotato/static/scripts/combined.plugins.min.js +++ b/couchpotato/static/scripts/combined.plugins.min.js @@ -1912,15 +1912,15 @@ var Movie = new Class({ } }); } - self.el.adopt(self.select_checkbox = new Element("input[type=checkbox]"), new Element("div.poster_container").adopt(self.thumbnail = thumbnail, self.actions_el = new Element("div.actions")), self.data_container = new Element("div.data.light").grab(self.info_container = new Element("div.info").adopt(new Element("div.title").adopt(self.title = new Element("span", { + self.el.adopt(self.select_checkbox = new Element("input[type=checkbox]"), new Element("div.poster_container").adopt(thumbnail, self.actions_el = new Element("div.actions")), new Element("div.info").adopt(new Element("div.title").adopt(new Element("span", { text: self.getTitle() || "n/a" - }), self.year = new Element("div.year", { + }), new Element("div.year", { text: self.data.info.year || "n/a" - })), self.eta = eta_date && now + 8035200 > eta ? new Element("div.eta", { + })), eta_date && now + 8035200 > eta ? new Element("div.eta", { text: eta_date, title: "ETA" - }) : null, self.quality = new Element("div.quality"), self.rating = rating ? new Element("div.rating[title=" + rating[0] + "]").adopt(stars, new Element("span.votes[text=(" + rating.join(" / ") + ")][title=Votes]")) : null))); - if (!self.thumbnail) self.el.addClass("no_thumbnail"); + }) : null, self.quality = new Element("div.quality"), rating ? new Element("div.rating[title=" + rating[0] + "]").adopt(stars, new Element("span.votes[text=(" + rating.join(" / ") + ")][title=Votes]")) : null)); + if (!thumbnail) self.el.addClass("no_thumbnail"); if (self.profile.data) self.profile.getTypes().each(function(type) { var q = self.addQuality(type.get("quality"), type.get("3d")); if ((type.finish === true || type.get("finish")) && !q.hasClass("finish")) { diff --git a/couchpotato/static/style/combined.min.css b/couchpotato/static/style/combined.min.css index 93e3afd..884b228 100644 --- a/couchpotato/static/style/combined.min.css +++ b/couchpotato/static/style/combined.min.css @@ -66,7 +66,8 @@ .with_navigation .movie input[type=checkbox]:hover{opacity:1!important} .with_navigation .movie:hover input[type=checkbox]{opacity:.5} .with_navigation .movie.checked input[type=checkbox]{opacity:1} -.movie .quality span{background:rgba(0,0,0,.2);border:1px solid transparent;color:#FFF;border-radius:1px;padding:1px 3px} +.movie .quality{font-weight:400} +.movie .quality span{display:inline-block;background:rgba(0,0,0,.25);border:1px solid transparent;color:#FFF;border-radius:1px;padding:1px 3px} .movie .quality span.failed{background:#993619} .movie .quality span.available{color:#009902;border-color:#009902;background:#FFF} .movie .quality span.snatched{background:#568f99;color:#FFF} @@ -82,48 +83,48 @@ .movies>.loading{background:#FFF} .movies>.loading .message{color:#000} .movies>.loading .spinner{background-color:#000} +.movies .actions{-webkit-transform:rotateY(360deg);transform:rotateY(360deg)} .list_list{font-weight:300} .list_list .movie{display:block;border-bottom:1px solid #eaeaea;position:relative;cursor:pointer} .list_list .movie:last-child{border-bottom:none} .list_list .movie:hover{background:rgba(0,0,0,.1)} .list_list .movie input[type=checkbox]{left:20px;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%)} .list_list .movie .poster{display:none} -.list_list .movie .data{padding:10px 20px} -.list_list .movie .data .info{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row nowrap;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-align-items:center;-ms-flex-align:center;align-items:center} -.list_list .movie .data .info .title{-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto} -.list_list .movie .data .info .title span{transition:margin 200ms cubic-bezier(.9,0,.1,1);overflow:hidden;text-overflow:ellipsis;white-space:nowrap} +.list_list .movie .info,.thumb_list>div{display:-webkit-flex;display:-ms-flexbox} +.list_list .movie .info{padding:10px 20px;display:flex;-webkit-flex-flow:row nowrap;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-align-items:center;-ms-flex-align:center;align-items:center} +.list_list .movie .info .title{-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto} +.list_list .movie .info .title span{transition:margin 200ms cubic-bezier(.9,0,.1,1);overflow:hidden;text-overflow:ellipsis;white-space:nowrap} @media (max-width:480px){.movies .actions{pointer-events:none} -.list_list .movie .data{padding:10px} -.list_list .movie .data .info{display:block} -.list_list .movie .data .info .title{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row nowrap;-ms-flex-flow:row nowrap;flex-flow:row nowrap} -.list_list .movie .data .info .title span{width:100%} -} -.list_list .movie .data .info .title .year{display:inline-block;margin:0 10px;opacity:.5} -.list_list .movie .data .info .eta{font-size:.8em;opacity:.5;margin-right:4px} -.list_list .movie .data .info .quality{clear:both;overflow:hidden} -.list_list .movie .data .info .quality span{float:left;font-size:.7em;margin:2px 0 0 2px} -.list_list .movie .data .info .rating .vote{display:inline-block;min-width:60px;text-align:right} +.list_list .movie .info{display:block;padding:10px} +.list_list .movie .info .title{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row nowrap;-ms-flex-flow:row nowrap;flex-flow:row nowrap} +.list_list .movie .info .title span{width:100%} +} +.list_list .movie .info .title .year{display:inline-block;margin:0 10px;opacity:.5} +.list_list .movie .info .eta{font-size:.8em;opacity:.5;margin-right:4px} +.list_list .movie .info .quality{clear:both;overflow:hidden} +.list_list .movie .info .quality span{float:left;font-size:.7em;margin:2px 0 0 2px} +.list_list .movie .info .rating .vote{display:inline-block;min-width:60px;text-align:right} .list_list .movie .actions{position:absolute;right:10px;top:0;bottom:0;display:none;z-index:10} .list_list .movie .actions .action{display:inline-block} .list_list .movie .actions a{height:100%;display:block;background:#FFF;padding:10px;width:auto;float:right;color:#000} .list_list .movie .actions a .icon,.list_list .movie .actions a:before{display:none} .list_list .movie .actions a:hover{color:#ac0000} .list_list .movie:hover .actions{display:block} -@media (max-width:480px){.list_list .movie .data .info .quality span{margin:2px 2px 0 0} +@media (max-width:480px){.list_list .movie .info .quality span{margin:2px 2px 0 0} .list_list .movie:hover .actions{display:none} } -.list_list.with_navigation .movie.checked .data .info .title span,.list_list.with_navigation .movie:hover .data .info .title span{margin-left:20px} +.list_list.with_navigation .movie.checked .info .title span,.list_list.with_navigation .movie:hover .info .title span{margin-left:20px} .thumb_list>div:last-child{padding:0 12px} @media (max-width:480px){.thumb_list>div:last-child{padding:0 3.33px} } -.thumb_list>div{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row wrap;-ms-flex-flow:row wrap;flex-flow:row wrap;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch} -.thumb_list .movie{margin-bottom:20px;position:relative;cursor:pointer;-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto;width:150px;max-width:14.285%;border:0 solid transparent;border-width:0 10px} -@media (max-width:1382px){.thumb_list .movie{max-width:16.666666667%;border-width:0 8px} +.thumb_list>div{display:flex;-webkit-flex-flow:row wrap;-ms-flex-flow:row wrap;flex-flow:row wrap;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch} +.thumb_list .movie{margin-bottom:20px;position:relative;cursor:pointer;-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto;width:150px;max-width:16.666666667%;border:0 solid transparent;border-width:0 10px} +@media (max-width:1382px){.thumb_list .movie{max-width:20%;border-width:0 8px} } -@media (max-width:1024px){.thumb_list .movie{max-width:20%;border-width:0 6.67px} +@media (max-width:1024px){.thumb_list .movie{max-width:33.333%;border-width:0 6.67px} } @media (max-width:768px){.thumb_list{padding:0 5px} -.thumb_list .movie{max-width:33.333%;border-width:0 5px} +.thumb_list .movie{max-width:50%;border-width:0 5px} } @media (max-width:480px){.thumb_list{padding:0 3.33px} .thumb_list .movie{max-width:50%;border-width:0 4px} @@ -131,13 +132,13 @@ .thumb_list .movie input[type=checkbox]{top:10px;left:10px} .thumb_list .movie .poster_container{border-radius:3px;position:relative;width:100%;padding-bottom:150%} .thumb_list .movie .poster{position:absolute;background:center no-repeat #eaeaea;background-size:cover;overflow:hidden;height:100%;width:100%} -.thumb_list .movie .data{clear:both;font-size:.85em} -.thumb_list .movie .data .info .title{display:-webkit-flex;display:-ms-flexbox;display:flex;padding:3px 0} -.thumb_list .movie .data .info .title span{-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} -.thumb_list .movie .data .info .title .year{display:inline-block;margin-left:5px;opacity:.5} -.thumb_list .movie .data .info .eta{opacity:.5;float:right;margin-left:4px} -.thumb_list .movie .data .info .quality{white-space:nowrap;overflow:hidden} -.thumb_list .movie .data .info .quality span{font-size:.8em;margin-right:2px} +.thumb_list .movie .info{clear:both;font-size:.85em} +.thumb_list .movie .info .title{display:-webkit-flex;display:-ms-flexbox;display:flex;padding:3px 0} +.thumb_list .movie .info .title span{-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} +.thumb_list .movie .info .title .year{display:inline-block;margin-left:5px;opacity:.5} +.thumb_list .movie .info .eta{opacity:.5;float:right;margin-left:4px} +.thumb_list .movie .info .quality{white-space:nowrap;overflow:hidden} +.thumb_list .movie .info .quality span{font-size:.8em;margin-right:2px} .thumb_list .movie .actions{background-image:linear-gradient(25deg,rgba(172,0,0,0) 0,rgba(172,0,0,.9) 100%);transition:opacity 250ms;opacity:0;position:absolute;top:0;right:0;bottom:0;left:0;text-align:right} .thumb_list .movie .actions .action{position:relative;display:block;margin-bottom:1px;width:auto;margin-right:10px} .thumb_list .movie .actions .action:first-child{margin-top:10px} @@ -149,14 +150,14 @@ @media (max-width:480px){.thumb_list .movie:hover .actions{display:none} .page.movie_details{left:0} } -.page.movie_details .overlay{position:fixed;top:0;bottom:0;right:0;left:132px;background:rgba(0,0,0,.6);border-radius:3px 0 0 3px;opacity:0;-webkit-transform:translateZ(0);transform:translateZ(0);-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:opacity 300ms ease 400ms;z-index:1} +.page.movie_details .overlay{position:fixed;top:0;bottom:0;right:0;left:132px;background:rgba(0,0,0,.6);border-radius:3px 0 0 3px;opacity:0;-webkit-transform:rotateY(360deg);transform:rotateY(360deg);-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:opacity 300ms ease 400ms;z-index:1} .page.movie_details .overlay .ripple{background:#FFF} .page.movie_details .overlay .close{display:inline-block;text-align:center;font-size:60px;line-height:80px;color:#FFF;width:100%;height:100%;opacity:0;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:opacity 300ms ease 200ms} .page.movie_details .overlay .close:before{display:block;width:44px} @media (max-width:480px){.page.movie_details .overlay{left:0;border-radius:0} .page.movie_details .overlay .close{width:44px} } -.page.movie_details .scroll_content{position:fixed;z-index:2;top:0;bottom:0;right:0;left:176px;background:#FFF;border-radius:3px 0 0 3px;overflow-y:auto;-webkit-transform:translateX(100%) translateZ(0);transform:translateX(100%) translateZ(0);transition:-webkit-transform 350ms cubic-bezier(.9,0,.1,1);transition:transform 350ms cubic-bezier(.9,0,.1,1)} +.page.movie_details .scroll_content{position:fixed;z-index:2;top:0;bottom:0;right:0;left:176px;background:#FFF;border-radius:3px 0 0 3px;overflow-y:auto;-webkit-transform:translateX(100%) rotateY(360deg);transform:translateX(100%) rotateY(360deg);transition:-webkit-transform 350ms cubic-bezier(.9,0,.1,1);transition:transform 350ms cubic-bezier(.9,0,.1,1)} .page.movie_details .scroll_content>.head{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row nowrap;-ms-flex-flow:row nowrap;flex-flow:row nowrap;padding:0 20px;position:relative;z-index:2} @media (max-width:480px){.page.movie_details .scroll_content{left:44px} .page.movie_details .scroll_content>.head{-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:0 10px;line-height:1em} @@ -203,7 +204,7 @@ .page.movie_details .section_add .options>div .add{width:200px} .page.movie_details .section_add .options>div .add .button{background:#FFF;-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto;display:block;text-align:center;width:100%;margin:0} .page.movie_details .section_add .options>div .add .button:hover{background:#ac0000} -.page.movie_details .section_add .data,.page.movie_details .section_add .thumbnail{display:none} +.page.movie_details .section_add .thumbnail{display:none} .page.movie_details .files span{text-align:center} .page.movie_details .files .name{text-align:left;-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto} .page.movie_details .files .type{min-width:80px} @@ -249,7 +250,7 @@ .page.movie_details .releases .actions{min-width:80px;max-width:80px} .page.movie_details .trailer_container{background:#000;position:relative;padding-bottom:56.25%;height:0;overflow:hidden;max-width:100%;cursor:pointer} .alph_nav .menus .button,.alph_nav .menus .counter{line-height:80px;padding:0 10px} -.page.movie_details .trailer_container .background{opacity:.3;transition:all 300ms;-webkit-transform:scale(1.05) translateZ(0);transform:scale(1.05) translateZ(0);background:center no-repeat;background-size:cover;position:absolute;top:0;right:0;bottom:0;left:0;z-index:1} +.page.movie_details .trailer_container .background{opacity:.3;transition:all 300ms;-webkit-transform:scale(1.05) rotateY(360deg);transform:scale(1.05) rotateY(360deg);background:center no-repeat;background-size:cover;position:absolute;top:0;right:0;bottom:0;left:0;z-index:1} .page.movie_details .trailer_container .icon-play{opacity:.9;position:absolute;z-index:2;text-align:center;width:100%;top:50%;-webkit-transform:translateY(-50%);transform:translateY(-50%);transition:all 300ms;color:#FFF;font-size:110px} @media (max-width:1024px){.page.movie_details .trailer_container .icon-play{font-size:55px} } From 47bca3398f5ac6cca72dde91e4aa543c480c13b8 Mon Sep 17 00:00:00 2001 From: Ruud Date: Fri, 21 Aug 2015 17:21:54 +0200 Subject: [PATCH 284/301] Search on home --- .../core/media/_base/search/static/search.scss | 30 +++++++++++++++++++++- couchpotato/static/scripts/combined.base.min.js | 8 +++++- couchpotato/static/scripts/page/home.js | 10 ++++++++ couchpotato/static/style/combined.min.css | 17 +++++++----- 4 files changed, 57 insertions(+), 8 deletions(-) diff --git a/couchpotato/core/media/_base/search/static/search.scss b/couchpotato/core/media/_base/search/static/search.scss index a4c26f2..2c231f6 100644 --- a/couchpotato/core/media/_base/search/static/search.scss +++ b/couchpotato/core/media/_base/search/static/search.scss @@ -74,7 +74,8 @@ } &.focused, - &.shown { + &.shown, + .page.home & { border-color: #04bce6; .wrapper { @@ -305,4 +306,31 @@ } } + .page.home & { + height: 100px; + + .icon-search { + color: #000; + } + + .wrapper { + border-radius: 0; + box-shadow: 0; + bottom: auto; + top: 0; + + &:before { + display: none; + } + + .input { + border-radius: 0; + left: 0; + } + + } + + + } + } diff --git a/couchpotato/static/scripts/combined.base.min.js b/couchpotato/static/scripts/combined.base.min.js index 815d8d3..aba11dc 100644 --- a/couchpotato/static/scripts/combined.base.min.js +++ b/couchpotato/static/scripts/combined.base.min.js @@ -977,7 +977,13 @@ Page.Home = new Class({ return; } self.chain = new Chain(); - self.chain.chain(self.createAvailable.bind(self), self.createSoon.bind(self), self.createSuggestions.bind(self), self.createCharts.bind(self), self.createLate.bind(self)); + self.chain.chain(self.createBigsearch.bind(self), self.createAvailable.bind(self), self.createSoon.bind(self), self.createSuggestions.bind(self), self.createCharts.bind(self), self.createLate.bind(self)); + self.chain.callChain(); + }, + createBigsearch: function() { + var self = this; + var search = new BlockSearch(self, {}); + $(search).inject(self.content); self.chain.callChain(); }, createAvailable: function() { diff --git a/couchpotato/static/scripts/page/home.js b/couchpotato/static/scripts/page/home.js index b16b8c4..e605871 100644 --- a/couchpotato/static/scripts/page/home.js +++ b/couchpotato/static/scripts/page/home.js @@ -22,6 +22,7 @@ Page.Home = new Class({ self.chain = new Chain(); self.chain.chain( + self.createBigsearch.bind(self), self.createAvailable.bind(self), self.createSoon.bind(self), self.createSuggestions.bind(self), @@ -33,6 +34,15 @@ Page.Home = new Class({ }, + createBigsearch: function(){ + var self = this; + + var search = new BlockSearch(self, {}); + $(search).inject(self.content); + + self.chain.callChain(); + }, + createAvailable: function(){ var self = this; diff --git a/couchpotato/static/style/combined.min.css b/couchpotato/static/style/combined.min.css index 884b228..5e6460e 100644 --- a/couchpotato/static/style/combined.min.css +++ b/couchpotato/static/style/combined.min.css @@ -1,24 +1,23 @@ .movies>.description a:hover,.page.movie_details .releases .buttons a:hover{text-decoration:underline} -.directory_list,.messages .message,.more_menu .wrapper,.search_form .wrapper{box-shadow:0 0 15px 2px rgba(0,0,0,.15)} .search_form{display:inline-block;z-index:11;width:44px;position:relative} .search_form *{-webkit-transform:translateZ(0);transform:translateZ(0)} .search_form .icon-search{position:absolute;z-index:2;top:50%;left:0;height:100%;text-align:center;color:#FFF;font-size:20px;-webkit-transform:translateY(-50%);transform:translateY(-50%)} -.search_form .wrapper{position:absolute;left:44px;bottom:0;background:#ac0000;border-radius:3px 0 0 3px;display:none} +.search_form .wrapper{position:absolute;left:44px;bottom:0;background:#ac0000;border-radius:3px 0 0 3px;display:none;box-shadow:0 0 15px 2px rgba(0,0,0,.15)} .search_form .wrapper:before{-webkit-transform:rotate(45deg);transform:rotate(45deg);content:'';display:block;position:absolute;height:10px;width:10px;background:#ac0000;left:-6px;bottom:16px;z-index:1} .search_form .input{background:#FFF;border-radius:3px 0 0 3px;position:relative;left:4px;height:44px;overflow:hidden;width:100%} .search_form .input input{position:absolute;top:0;left:0;height:100%;width:100%;z-index:1} .search_form .input input::-ms-clear{width:0;height:0} .search_form.filled .input input{background:#eaeaea} -.search_form.focused,.search_form.shown{border-color:#04bce6} -.search_form.focused .wrapper,.search_form.shown .wrapper{display:block;width:380px;-webkit-transform-origin:0 90%;transform-origin:0 90%} -.search_form.focused .input input,.search_form.shown .input input{opacity:1} +.page.home .search_form,.search_form.focused,.search_form.shown{border-color:#04bce6} +.page.home .search_form .wrapper,.search_form.focused .wrapper,.search_form.shown .wrapper{display:block;width:380px;-webkit-transform-origin:0 90%;transform-origin:0 90%} +.page.home .search_form .input input,.search_form.focused .input input,.search_form.shown .input input{opacity:1} .search_form .results_container{min-height:50px;text-align:left;position:relative;left:4px;display:none;background:#FFF;border-radius:3px 0 0;overflow:hidden} .search_form .results_container .results{max-height:280px;overflow-x:hidden} .search_form .results_container .results .media_result{overflow:hidden;height:50px;position:relative} .search_form .results_container .results .media_result .options{position:absolute;height:100%;top:0;left:30px;right:0;background:rgba(0,0,0,.3);display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center} .search_form .results_container .results .media_result .options>.in_library_wanted{margin-top:-7px} .search_form .results_container .results .media_result .options>div{border:0;display:-webkit-flex;display:-ms-flexbox;display:flex;padding:10px;-webkit-align-items:stretch;-ms-flex-align:stretch;align-items:stretch;-webkit-justify-content:space-between;-ms-flex-pack:justify;justify-content:space-between} -@media (max-width:480px){.search_form.focused .wrapper,.search_form.shown .wrapper{width:260px} +@media (max-width:480px){.page.home .search_form .wrapper,.search_form.focused .wrapper,.search_form.shown .wrapper{width:260px} .search_form .results_container .results .media_result{font-size:12px} .search_form .results_container .results .media_result .options{left:0} .search_form .results_container .results .media_result .options>div{padding:3px} @@ -52,6 +51,12 @@ .search_form .results_container .results .media_result:last-child .data{border-bottom:0} .search_form.focused.filled .results_container,.search_form.shown.filled .results_container{display:block} .search_form.focused.filled .input,.search_form.shown.filled .input{border-radius:0 0 0 3px} +.page.home .search_form{height:100px} +.page.home .search_form .icon-search{color:#000} +.page.home .search_form .wrapper{border-radius:0;box-shadow:0;bottom:auto;top:0} +.directory_list,.messages .message,.more_menu .wrapper{box-shadow:0 0 15px 2px rgba(0,0,0,.15)} +.page.home .search_form .wrapper:before{display:none} +.page.home .search_form .wrapper .input{border-radius:0;left:0} .page.movies{bottom:auto;z-index:21;height:80px} .page.movies_manage,.page.movies_wanted{top:80px;padding:0;transition:top 300ms cubic-bezier(.9,0,.1,1)} @media (max-width:480px){.page.movies{height:44px} From 3e4e1a530e3342e53a847ee933b8d5a4e0f3a7c9 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 23 Aug 2015 09:32:17 +0200 Subject: [PATCH 285/301] Big search --- .../core/media/_base/search/static/search.js | 34 +++-- .../core/media/_base/search/static/search.scss | 169 ++++++++++++++++++++- .../core/media/movie/_base/static/movie.scss | 10 +- couchpotato/static/scripts/combined.base.min.js | 9 +- couchpotato/static/scripts/combined.plugins.min.js | 31 ++-- couchpotato/static/scripts/page/home.js | 11 +- couchpotato/static/style/_mixins.scss | 31 ++++ couchpotato/static/style/combined.min.css | 54 +++++-- 8 files changed, 301 insertions(+), 48 deletions(-) diff --git a/couchpotato/core/media/_base/search/static/search.js b/couchpotato/core/media/_base/search/static/search.js index 7c2bc24..6fabad4 100644 --- a/couchpotato/core/media/_base/search/static/search.js +++ b/couchpotato/core/media/_base/search/static/search.js @@ -2,6 +2,10 @@ var BlockSearch = new Class({ Extends: BlockBase, + options: { + 'animate': true + }, + cache: {}, create: function(){ @@ -72,20 +76,24 @@ var BlockSearch = new Class({ self.el.removeClass('filled'); // Animate in - dynamics.css(self.wrapper, { - opacity: 0, - scale: 0.1 - }); + if(self.options.animate){ - dynamics.animate(self.wrapper, { - opacity: 1, - scale: 1 - }, { - type: dynamics.spring, - frequency: 200, - friction: 270, - duration: 800 - }); + dynamics.css(self.wrapper, { + opacity: 0, + scale: 0.1 + }); + + dynamics.animate(self.wrapper, { + opacity: 1, + scale: 1 + }, { + type: dynamics.spring, + frequency: 200, + friction: 270, + duration: 800 + }); + + } } }, diff --git a/couchpotato/core/media/_base/search/static/search.scss b/couchpotato/core/media/_base/search/static/search.scss index 2c231f6..8ffd66f 100644 --- a/couchpotato/core/media/_base/search/static/search.scss +++ b/couchpotato/core/media/_base/search/static/search.scss @@ -66,11 +66,28 @@ width : 0; height: 0; } + + &:focus { + background: rgba($theme_off, .2); + + &::-webkit-input-placeholder { + color: $text_color; + opacity: .7; + } + &::-moz-placeholder { + color: $text_color; + opacity: .7; + } + &:-ms-input-placeholder { + color: $text_color; + opacity: .7; + } + } } } &.filled .input input { - background: $theme_off; + background: rgba($theme_off, .4); } &.focused, @@ -181,6 +198,10 @@ .add { width: 42px; flex: 1 auto; + + a { + color: #FFF; + } } .button { @@ -307,17 +328,63 @@ } .page.home & { - height: 100px; + $input_height: 66px; + $input_height_mobile: 44px; + + display: block; + padding: $padding; + width: 100%; + max-width: 500px; + margin: 0 auto; + height: $input_height + 2*$padding; + position: relative; + margin-top: $padding; + + @include media-phablet { + margin-top: $padding/2; + height: $input_height_mobile + $padding; + } .icon-search { + display: block; color: #000; + right: $padding; + top: $padding; + width: $input_height; + height: $input_height; + line-height: $input_height; + left: auto; + transform: none; + font-size: 2em; + opacity: .5; + pointer-events: none; + + @include media-phablet { + right: $padding/2; + width: $input_height_mobile; + height: $input_height_mobile; + line-height: $input_height_mobile; + right: $padding/2; + top: $padding/2; + font-size: 1.5em; + } } .wrapper { border-radius: 0; - box-shadow: 0; + box-shadow: none; bottom: auto; - top: 0; + top: $padding; + left: $padding; + right: $padding; + position: absolute; + width: auto; + + @include media-phablet { + right: $padding/2; + top: $padding/2; + left: $padding/2; + } &:before { display: none; @@ -326,6 +393,96 @@ .input { border-radius: 0; left: 0; + position: absolute; + top: 0; + height: $input_height; + + @include media-phablet { + height: $input_height_mobile; + } + + input { + box-shadow: 0; + font-size: 2em; + font-weight: 400; + + @include media-phablet { + font-size: 1em; + } + } + } + + .results_container { + min-height: $input_height; + position: absolute; + top: $input_height; + left: 0; + right: 0; + border: 1px solid #b7b7b7; + border-top: 0; + + @include media-phablet { + top: $input_height_mobile; + min-height: $input_height_mobile; + } + + + @include media-phablet-and-up { + .results { + max-height: 400px; + + .media_result { + height: $input_height; + + + @include media-phablet { + height: $input_height_mobile; + } + + .thumbnail { + width: 40px; + } + + .options { + left: 40px; + + .title { + margin-right: 5px; + width: 320px; + + @include media-phablet { + width: 140px; + margin-right: 2px; + } + } + } + + .data { + left: 40px; + } + } + } + } + + + @include media-phablet { + .results { + .media_result { + height: $input_height_mobile; + + .options { + + .title { + + width: 140px; + margin-right: 2px; + } + + } + + } + } + } } } @@ -334,3 +491,7 @@ } } + +.big_search { + background: $theme_off; +} diff --git a/couchpotato/core/media/movie/_base/static/movie.scss b/couchpotato/core/media/movie/_base/static/movie.scss index 074ffef..973003d 100644 --- a/couchpotato/core/media/movie/_base/static/movie.scss +++ b/couchpotato/core/media/movie/_base/static/movie.scss @@ -112,8 +112,16 @@ $mass_edit_height: 44px; position: relative; .no_movies { - text-align: center; + display: block; padding: $padding; + + @include media-phablet { + padding: $padding/2; + } + + a { + color: $primary_color; + } } > .description { diff --git a/couchpotato/static/scripts/combined.base.min.js b/couchpotato/static/scripts/combined.base.min.js index aba11dc..1e6fe34 100644 --- a/couchpotato/static/scripts/combined.base.min.js +++ b/couchpotato/static/scripts/combined.base.min.js @@ -977,13 +977,14 @@ Page.Home = new Class({ return; } self.chain = new Chain(); - self.chain.chain(self.createBigsearch.bind(self), self.createAvailable.bind(self), self.createSoon.bind(self), self.createSuggestions.bind(self), self.createCharts.bind(self), self.createLate.bind(self)); + self.chain.chain(self.createAvailable.bind(self), self.createBigsearch.bind(self), self.createSoon.bind(self), self.createSuggestions.bind(self), self.createCharts.bind(self), self.createLate.bind(self)); self.chain.callChain(); }, createBigsearch: function() { var self = this; - var search = new BlockSearch(self, {}); - $(search).inject(self.content); + new Element(".big_search").grab(new BlockSearch(self, { + animate: false + })).inject(self.content); self.chain.callChain(); }, createAvailable: function() { @@ -1003,7 +1004,7 @@ Page.Home = new Class({ events: { click: function(e) { e.preventDefault(); - $(document.body).getElement(".search_form .icon-search").click(); + $(document.body).getElement(".big_search input").focus(); } } })), diff --git a/couchpotato/static/scripts/combined.plugins.min.js b/couchpotato/static/scripts/combined.plugins.min.js index 477b1bc..9fbef4f 100644 --- a/couchpotato/static/scripts/combined.plugins.min.js +++ b/couchpotato/static/scripts/combined.plugins.min.js @@ -178,6 +178,9 @@ window.addEvent("domready", function() { var BlockSearch = new Class({ Extends: BlockBase, + options: { + animate: true + }, cache: {}, create: function() { var self = this; @@ -226,19 +229,21 @@ var BlockSearch = new Class({ self.media = {}; self.results.empty(); self.el.removeClass("filled"); - dynamics.css(self.wrapper, { - opacity: 0, - scale: .1 - }); - dynamics.animate(self.wrapper, { - opacity: 1, - scale: 1 - }, { - type: dynamics.spring, - frequency: 200, - friction: 270, - duration: 800 - }); + if (self.options.animate) { + dynamics.css(self.wrapper, { + opacity: 0, + scale: .1 + }); + dynamics.animate(self.wrapper, { + opacity: 1, + scale: 1 + }, { + type: dynamics.spring, + frequency: 200, + friction: 270, + duration: 800 + }); + } } }, hideResults: function(bool) { diff --git a/couchpotato/static/scripts/page/home.js b/couchpotato/static/scripts/page/home.js index e605871..46c31e9 100644 --- a/couchpotato/static/scripts/page/home.js +++ b/couchpotato/static/scripts/page/home.js @@ -22,8 +22,8 @@ Page.Home = new Class({ self.chain = new Chain(); self.chain.chain( - self.createBigsearch.bind(self), self.createAvailable.bind(self), + self.createBigsearch.bind(self), self.createSoon.bind(self), self.createSuggestions.bind(self), self.createCharts.bind(self), @@ -37,8 +37,11 @@ Page.Home = new Class({ createBigsearch: function(){ var self = this; - var search = new BlockSearch(self, {}); - $(search).inject(self.content); + new Element('.big_search').grab( + new BlockSearch(self, { + 'animate': false + }) + ).inject(self.content); self.chain.callChain(); }, @@ -61,7 +64,7 @@ Page.Home = new Class({ 'events': { 'click': function(e){ (e).preventDefault(); - $(document.body).getElement('.search_form .icon-search').click(); + $(document.body).getElement('.big_search input').focus(); } } }) diff --git a/couchpotato/static/style/_mixins.scss b/couchpotato/static/style/_mixins.scss index 25ac79b..26d03cb 100644 --- a/couchpotato/static/style/_mixins.scss +++ b/couchpotato/static/style/_mixins.scss @@ -49,3 +49,34 @@ $mq-desktop-plus: 1382px !default; @content; } } + + +@mixin media-phone-and-up { + @media (min-width : $mq-phone) { + @content; + } +} + +@mixin media-phablet-and-up { + @media (min-width : $mq-phablet) { + @content; + } +} + +@mixin media-tablet-and-up { + @media (min-width : $mq-tablet) { + @content; + } +} + +@mixin media-desktop-and-up { + @media (min-width : $mq-desktop) { + @content; + } +} + +@mixin media-desktop-plus-and-up { + @media (min-width : $mq-desktop-plus) { + @content; + } +} diff --git a/couchpotato/static/style/combined.min.css b/couchpotato/static/style/combined.min.css index 5e6460e..56d62c8 100644 --- a/couchpotato/static/style/combined.min.css +++ b/couchpotato/static/style/combined.min.css @@ -7,7 +7,11 @@ .search_form .input{background:#FFF;border-radius:3px 0 0 3px;position:relative;left:4px;height:44px;overflow:hidden;width:100%} .search_form .input input{position:absolute;top:0;left:0;height:100%;width:100%;z-index:1} .search_form .input input::-ms-clear{width:0;height:0} -.search_form.filled .input input{background:#eaeaea} +.search_form .input input:focus{background:rgba(234,234,234,.2)} +.search_form .input input:focus::-webkit-input-placeholder{color:#000;opacity:.7} +.search_form .input input:focus::-moz-placeholder{color:#000;opacity:.7} +.search_form .input input:focus:-ms-input-placeholder{color:#000;opacity:.7} +.search_form.filled .input input{background:rgba(234,234,234,.4)} .page.home .search_form,.search_form.focused,.search_form.shown{border-color:#04bce6} .page.home .search_form .wrapper,.search_form.focused .wrapper,.search_form.shown .wrapper{display:block;width:380px;-webkit-transform-origin:0 90%;transform-origin:0 90%} .page.home .search_form .input input,.search_form.focused .input input,.search_form.shown .input input{opacity:1} @@ -32,6 +36,7 @@ @media (max-width:480px){.search_form .results_container .results .media_result .options .category,.search_form .results_container .results .media_result .options .profile{margin-right:2px} } .search_form .results_container .results .media_result .options .add{width:42px;-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto} +.search_form .results_container .results .media_result .options .add a{color:#FFF} .search_form .results_container .results .media_result .options .button{display:block;background:#ac0000;text-align:center;margin:0} .search_form .results_container .results .media_result .options .message{font-size:20px;color:#fff} .search_form .results_container .results .media_result .thumbnail{width:30px;min-height:100%;display:block;margin:0;vertical-align:top} @@ -51,12 +56,40 @@ .search_form .results_container .results .media_result:last-child .data{border-bottom:0} .search_form.focused.filled .results_container,.search_form.shown.filled .results_container{display:block} .search_form.focused.filled .input,.search_form.shown.filled .input{border-radius:0 0 0 3px} -.page.home .search_form{height:100px} -.page.home .search_form .icon-search{color:#000} -.page.home .search_form .wrapper{border-radius:0;box-shadow:0;bottom:auto;top:0} -.directory_list,.messages .message,.more_menu .wrapper{box-shadow:0 0 15px 2px rgba(0,0,0,.15)} +.page.home .search_form{display:block;padding:20px;width:100%;max-width:500px;margin:20px auto 0;height:106px;position:relative} +@media (max-width:480px){.page.home .search_form{margin-top:10px;height:64px} +} +.page.home .search_form .icon-search{display:block;color:#000;right:20px;top:20px;width:66px;height:66px;line-height:66px;left:auto;-webkit-transform:none;transform:none;font-size:2em;opacity:.5;pointer-events:none} +@media (max-width:480px){.page.home .search_form .icon-search{width:44px;height:44px;line-height:44px;right:10px;top:10px;font-size:1.5em} +} +.page.home .search_form .wrapper{border-radius:0;box-shadow:none;bottom:auto;top:20px;left:20px;right:20px;position:absolute;width:auto} +@media (max-width:480px){.page.home .search_form .wrapper{right:10px;top:10px;left:10px} +} .page.home .search_form .wrapper:before{display:none} -.page.home .search_form .wrapper .input{border-radius:0;left:0} +.page.home .search_form .wrapper .input{border-radius:0;left:0;position:absolute;top:0;height:66px} +.page.home .search_form .wrapper .input input{box-shadow:0;font-size:2em;font-weight:400} +.directory_list,.messages .message,.more_menu .wrapper{box-shadow:0 0 15px 2px rgba(0,0,0,.15)} +.page.home .search_form .wrapper .results_container{min-height:66px;position:absolute;top:66px;left:0;right:0;border:1px solid #b7b7b7;border-top:0} +@media (max-width:480px){.page.home .search_form .wrapper .input{height:44px} +.page.home .search_form .wrapper .input input{font-size:1em} +.page.home .search_form .wrapper .results_container{top:44px;min-height:44px} +} +@media (min-width:480px){.page.home .search_form .wrapper .results_container .results{max-height:400px} +.page.home .search_form .wrapper .results_container .results .media_result{height:66px} +} +@media (min-width:480px){.page.home .search_form .wrapper .results_container .results .media_result .thumbnail{width:40px} +.page.home .search_form .wrapper .results_container .results .media_result .options{left:40px} +.page.home .search_form .wrapper .results_container .results .media_result .options .title{margin-right:5px;width:320px} +} +@media (min-width:480px) and (max-width:480px){.page.home .search_form .wrapper .results_container .results .media_result{height:44px} +.page.home .search_form .wrapper .results_container .results .media_result .options .title{width:140px;margin-right:2px} +} +@media (min-width:480px){.page.home .search_form .wrapper .results_container .results .media_result .data{left:40px} +} +@media (max-width:480px){.page.home .search_form .wrapper .results_container .results .media_result{height:44px} +.page.home .search_form .wrapper .results_container .results .media_result .options .title{width:140px;margin-right:2px} +} +.big_search{background:#eaeaea} .page.movies{bottom:auto;z-index:21;height:80px} .page.movies_manage,.page.movies_wanted{top:80px;padding:0;transition:top 300ms cubic-bezier(.9,0,.1,1)} @media (max-width:480px){.page.movies{height:44px} @@ -80,7 +113,8 @@ .movie .rating .votes{opacity:.7;margin-left:4px} .movie.status_suggested .quality{display:none} .movies{position:relative} -.movies .no_movies{text-align:center;padding:20px} +.movies .no_movies{display:block;padding:20px} +.movies .no_movies a{color:#ac0000} .movies>.description{position:absolute;top:0;right:20px;width:auto;line-height:80px} @media (max-width:768px){.movies>.description{display:none} } @@ -89,6 +123,9 @@ .movies>.loading .message{color:#000} .movies>.loading .spinner{background-color:#000} .movies .actions{-webkit-transform:rotateY(360deg);transform:rotateY(360deg)} +@media (max-width:480px){.movies .no_movies{padding:10px} +.movies .actions{pointer-events:none} +} .list_list{font-weight:300} .list_list .movie{display:block;border-bottom:1px solid #eaeaea;position:relative;cursor:pointer} .list_list .movie:last-child{border-bottom:none} @@ -99,8 +136,7 @@ .list_list .movie .info{padding:10px 20px;display:flex;-webkit-flex-flow:row nowrap;-ms-flex-flow:row nowrap;flex-flow:row nowrap;-webkit-align-items:center;-ms-flex-align:center;align-items:center} .list_list .movie .info .title{-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto} .list_list .movie .info .title span{transition:margin 200ms cubic-bezier(.9,0,.1,1);overflow:hidden;text-overflow:ellipsis;white-space:nowrap} -@media (max-width:480px){.movies .actions{pointer-events:none} -.list_list .movie .info{display:block;padding:10px} +@media (max-width:480px){.list_list .movie .info{display:block;padding:10px} .list_list .movie .info .title{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row nowrap;-ms-flex-flow:row nowrap;flex-flow:row nowrap} .list_list .movie .info .title span{width:100%} } From a23fadb553541788fe108d01c30ca3b6ae071078 Mon Sep 17 00:00:00 2001 From: Ruud Date: Sun, 23 Aug 2015 20:05:24 +0200 Subject: [PATCH 286/301] SABnzbd fails on id check fix #5256 --- couchpotato/core/downloaders/sabnzbd.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/couchpotato/core/downloaders/sabnzbd.py b/couchpotato/core/downloaders/sabnzbd.py index 3f004e7..c9bb7cc 100644 --- a/couchpotato/core/downloaders/sabnzbd.py +++ b/couchpotato/core/downloaders/sabnzbd.py @@ -73,10 +73,11 @@ class Sabnzbd(DownloaderBase): return False log.debug('Result from SAB: %s', sab_data) - if sab_data.get('status') and not sab_data.get('error') and len(sab_data.get('nzo_ids', []) > 0): + nzo_ids = sab_data.get('nzo_ids', []) + if sab_data.get('status') and not sab_data.get('error') and isinstance(nzo_ids, list) and len(nzo_ids) > 0: log.info('NZB sent to SAB successfully.') if filedata: - return self.downloadReturnId(sab_data.get('nzo_ids')[0]) + return self.downloadReturnId(nzo_ids[0]) else: return True else: From 843d77107aec3e8e723388ca7aadfad7fd86b7f1 Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 24 Aug 2015 20:26:41 +0200 Subject: [PATCH 287/301] Kickass failed searching --- couchpotato/core/media/_base/providers/torrent/kickasstorrents.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/couchpotato/core/media/_base/providers/torrent/kickasstorrents.py b/couchpotato/core/media/_base/providers/torrent/kickasstorrents.py index 650d9c3..c76bd32 100644 --- a/couchpotato/core/media/_base/providers/torrent/kickasstorrents.py +++ b/couchpotato/core/media/_base/providers/torrent/kickasstorrents.py @@ -67,7 +67,7 @@ class Base(TorrentMagnetProvider): link = td.find('div', {'class': 'torrentname'}).find_all('a')[2] new['id'] = temp.get('id')[-7:] new['name'] = link.text - new['url'] = td.find('a', 'imagnet')['href'] + new['url'] = td.find('a', {'href': re.compile('magnet:*')})['href'] new['detail_url'] = self.urls['detail'] % (self.getDomain(), link['href'][1:]) new['verified'] = True if td.find('a', 'iverify') else False new['score'] = 100 if new['verified'] else 0 From 5314fc7112c206f8c7aadaab910eeae5715705f4 Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 24 Aug 2015 21:35:21 +0200 Subject: [PATCH 288/301] Log cleanup --- couchpotato/core/plugins/log/main.py | 5 +- couchpotato/core/plugins/log/static/log.js | 116 +++++++++++---------- couchpotato/core/plugins/log/static/log.scss | 12 +-- couchpotato/static/scripts/combined.plugins.min.js | 105 ++++++++++--------- couchpotato/static/style/combined.min.css | 2 +- 5 files changed, 130 insertions(+), 110 deletions(-) diff --git a/couchpotato/core/plugins/log/main.py b/couchpotato/core/plugins/log/main.py index 003529b..19edc19 100644 --- a/couchpotato/core/plugins/log/main.py +++ b/couchpotato/core/plugins/log/main.py @@ -131,11 +131,12 @@ class Logging(Plugin): def toList(self, log_content = ''): - logs_raw = toUnicode(log_content).split('[0m\n') + logs_raw = re.split(r'\[0m\n', toUnicode(log_content)) logs = [] + re_split = r'\x1b' for log_line in logs_raw: - split = splitString(log_line, '\x1b') + split = re.split(re_split, log_line) if split: try: date, time, log_type = splitString(split[0], ' ') diff --git a/couchpotato/core/plugins/log/static/log.js b/couchpotato/core/plugins/log/static/log.js index 5cc2f50..ee471bc 100644 --- a/couchpotato/core/plugins/log/static/log.js +++ b/couchpotato/core/plugins/log/static/log.js @@ -7,6 +7,7 @@ Page.Log = new Class({ title: 'Show recent logs.', has_tab: false, + navigation: null, log_items: [], report_text: '### Steps to reproduce:\n'+ '1. ..\n'+ @@ -43,7 +44,15 @@ Page.Log = new Class({ } }).inject(self.content); - Api.request('logging.get', { + if(self.navigation){ + var nav = self.navigation.getElement('.nav'); + nav.getElements('.active').removeClass('active'); + + self.navigation.getElements('li')[nr+1].addClass('active'); + } + + if(self.request && self.request.running) self.request.cancel(); + self.request = Api.request('logging.get', { 'data': { 'nr': nr }, @@ -52,67 +61,68 @@ Page.Log = new Class({ self.log_items = self.createLogElements(json.log); self.log.adopt(self.log_items); self.log.removeClass('loading'); + self.scrollToBottom(); - var navigation = new Element('div.navigation').adopt( - new Element('h2[text=Logs]'), - new Element('div.hint', { - 'text': 'Select multiple lines & report an issue' - }) - ); + if(!self.navigation){ + self.navigation = new Element('div.navigation').adopt( + new Element('h2[text=Logs]'), + new Element('div.hint', { + 'text': 'Select multiple lines & report an issue' + }) + ); - var nav = new Element('ul.nav', { - 'events': { - 'click:relay(li.select)': function (e, el) { - self.getLogs(parseInt(el.get('text')) - 1); + var nav = new Element('ul.nav', { + 'events': { + 'click:relay(li.select)': function (e, el) { + self.getLogs(parseInt(el.get('text')) - 1); + } } + }).inject(self.navigation); + + // Type selection + new Element('li.filter').grab( + new Element('select', { + 'events': { + 'change': function () { + var type_filter = this.getSelected()[0].get('value'); + self.content.set('data-filter', type_filter); + self.scrollToBottom(); + } + } + }).adopt( + new Element('option', {'value': 'ALL', 'text': 'Show all logs'}), + new Element('option', {'value': 'INFO', 'text': 'Show only INFO'}), + new Element('option', {'value': 'DEBUG', 'text': 'Show only DEBUG'}), + new Element('option', {'value': 'ERROR', 'text': 'Show only ERROR'}) + ) + ).inject(nav); + + // Selections + for (var i = 0; i <= json.total; i++) { + new Element('li', { + 'text': i + 1, + 'class': 'select ' + (nr == i ? 'active' : '') + }).inject(nav); } - }).inject(navigation); - // Type selection - new Element('li.filter').grab( - new Element('select', { + // Clear button + new Element('li.clear', { + 'text': 'clear', 'events': { - 'change': function () { - var type_filter = this.getSelected()[0].get('value'); - self.content.set('data-filter', type_filter); - self.scrollToBottom(); + 'click': function () { + Api.request('logging.clear', { + 'onComplete': function () { + self.getLogs(0); + } + }); + } } - }).adopt( - new Element('option', {'value': 'ALL', 'text': 'Show all logs'}), - new Element('option', {'value': 'INFO', 'text': 'Show only INFO'}), - new Element('option', {'value': 'DEBUG', 'text': 'Show only DEBUG'}), - new Element('option', {'value': 'ERROR', 'text': 'Show only ERROR'}) - ) - ).inject(nav); - - // Selections - for (var i = 0; i <= json.total; i++) { - new Element('li', { - 'text': i + 1, - 'class': 'select ' + (nr == i ? 'active' : '') }).inject(nav); - } - - // Clear button - new Element('li.clear', { - 'text': 'clear', - 'events': { - 'click': function () { - Api.request('logging.clear', { - 'onComplete': function () { - self.getLogs(0); - } - }); - - } - } - }).inject(nav); - - // Add to page - navigation.inject(self.content, 'top'); - self.scrollToBottom(); + // Add to page + self.navigation.inject(self.content, 'top'); + } } }); @@ -142,7 +152,7 @@ Page.Log = new Class({ }, scrollToBottom: function () { - new Fx.Scroll(this.el, {'duration': 0}).toBottom(); + new Fx.Scroll(this.content, {'duration': 0}).toBottom(); }, showSelectionButton: function(e){ diff --git a/couchpotato/core/plugins/log/static/log.scss b/couchpotato/core/plugins/log/static/log.scss index 3929ac0..6226e02 100644 --- a/couchpotato/core/plugins/log/static/log.scss +++ b/couchpotato/core/plugins/log/static/log.scss @@ -34,17 +34,17 @@ margin-top: 3px; } - .loading { - text-align: center; - font-size: 20px; - padding: 50px; - } - .container { padding: $padding; overflow: hidden; line-height: 150%; + &.loading { + text-align: center; + font-size: 20px; + padding: 100px 50px; + } + select { vertical-align: top; } diff --git a/couchpotato/static/scripts/combined.plugins.min.js b/couchpotato/static/scripts/combined.plugins.min.js index 9fbef4f..d4cf027 100644 --- a/couchpotato/static/scripts/combined.plugins.min.js +++ b/couchpotato/static/scripts/combined.plugins.min.js @@ -2952,6 +2952,7 @@ Page.Log = new Class({ name: "log", title: "Show recent logs.", has_tab: false, + navigation: null, log_items: [], report_text: "### Steps to reproduce:\n" + "1. ..\n" + "2. ..\n" + "\n" + "### Information:\n" + "Movie(s) I have this with: ...\n" + "Quality of the movie being searched: ...\n" + "Providers I use: ...\n" + "Version of CouchPotato: {version}\n" + "Running on: ...\n" + "\n" + "### Logs:\n" + "```\n{issue}```", indexAction: function() { @@ -2969,7 +2970,13 @@ Page.Log = new Class({ } } }).inject(self.content); - Api.request("logging.get", { + if (self.navigation) { + var nav = self.navigation.getElement(".nav"); + nav.getElements(".active").removeClass("active"); + self.navigation.getElements("li")[nr + 1].addClass("active"); + } + if (self.request && self.request.running) self.request.cancel(); + self.request = Api.request("logging.get", { data: { nr: nr }, @@ -2978,57 +2985,59 @@ Page.Log = new Class({ self.log_items = self.createLogElements(json.log); self.log.adopt(self.log_items); self.log.removeClass("loading"); - var navigation = new Element("div.navigation").adopt(new Element("h2[text=Logs]"), new Element("div.hint", { - text: "Select multiple lines & report an issue" - })); - var nav = new Element("ul.nav", { - events: { - "click:relay(li.select)": function(e, el) { - self.getLogs(parseInt(el.get("text")) - 1); + self.scrollToBottom(); + if (!self.navigation) { + self.navigation = new Element("div.navigation").adopt(new Element("h2[text=Logs]"), new Element("div.hint", { + text: "Select multiple lines & report an issue" + })); + var nav = new Element("ul.nav", { + events: { + "click:relay(li.select)": function(e, el) { + self.getLogs(parseInt(el.get("text")) - 1); + } } - } - }).inject(navigation); - new Element("li.filter").grab(new Element("select", { - events: { - change: function() { - var type_filter = this.getSelected()[0].get("value"); - self.content.set("data-filter", type_filter); - self.scrollToBottom(); + }).inject(self.navigation); + new Element("li.filter").grab(new Element("select", { + events: { + change: function() { + var type_filter = this.getSelected()[0].get("value"); + self.content.set("data-filter", type_filter); + self.scrollToBottom(); + } } + }).adopt(new Element("option", { + value: "ALL", + text: "Show all logs" + }), new Element("option", { + value: "INFO", + text: "Show only INFO" + }), new Element("option", { + value: "DEBUG", + text: "Show only DEBUG" + }), new Element("option", { + value: "ERROR", + text: "Show only ERROR" + }))).inject(nav); + for (var i = 0; i <= json.total; i++) { + new Element("li", { + text: i + 1, + class: "select " + (nr == i ? "active" : "") + }).inject(nav); } - }).adopt(new Element("option", { - value: "ALL", - text: "Show all logs" - }), new Element("option", { - value: "INFO", - text: "Show only INFO" - }), new Element("option", { - value: "DEBUG", - text: "Show only DEBUG" - }), new Element("option", { - value: "ERROR", - text: "Show only ERROR" - }))).inject(nav); - for (var i = 0; i <= json.total; i++) { - new Element("li", { - text: i + 1, - class: "select " + (nr == i ? "active" : "") + new Element("li.clear", { + text: "clear", + events: { + click: function() { + Api.request("logging.clear", { + onComplete: function() { + self.getLogs(0); + } + }); + } + } }).inject(nav); + self.navigation.inject(self.content, "top"); } - new Element("li.clear", { - text: "clear", - events: { - click: function() { - Api.request("logging.clear", { - onComplete: function() { - self.getLogs(0); - } - }); - } - } - }).inject(nav); - navigation.inject(self.content, "top"); - self.scrollToBottom(); } }); }, @@ -3048,7 +3057,7 @@ Page.Log = new Class({ return elements; }, scrollToBottom: function() { - new Fx.Scroll(this.el, { + new Fx.Scroll(this.content, { duration: 0 }).toBottom(); }, diff --git a/couchpotato/static/style/combined.min.css b/couchpotato/static/style/combined.min.css index 56d62c8..0fb3f46 100644 --- a/couchpotato/static/style/combined.min.css +++ b/couchpotato/static/style/combined.min.css @@ -402,8 +402,8 @@ .page.log .nav li.clear,.page.log .nav li.select{cursor:pointer} .page.log .nav li.active{font-weight:700;cursor:default} .page.log .hint{font-style:italic;opacity:.5;margin-top:3px} -.page.log .loading{text-align:center;font-size:20px;padding:50px} .page.log .container{padding:20px;overflow:hidden;line-height:150%} +.page.log .container.loading{text-align:center;font-size:20px;padding:100px 50px} .page.log .container select{vertical-align:top} .page.log .container .time{clear:both;font-size:11px;border-top:1px solid rgba(255,255,255,.1);position:relative;overflow:hidden;padding:0 3px;font-family:Lucida Console,Monaco,Nimbus Mono L,monospace,serif} .page.log .container .time.highlight{background:#eaeaea} From 76cd7d7b079181c79db7dda453a364b7ac61d942 Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 24 Aug 2015 21:51:44 +0200 Subject: [PATCH 289/301] Nice movie scanning progress --- .../core/media/movie/_base/static/manage.js | 3 ++- .../core/media/movie/_base/static/movie.scss | 25 ++++++++++++++++++++++ couchpotato/static/scripts/combined.plugins.min.js | 2 +- couchpotato/static/style/combined.min.css | 5 +++++ 4 files changed, 33 insertions(+), 2 deletions(-) diff --git a/couchpotato/core/media/movie/_base/static/manage.js b/couchpotato/core/media/movie/_base/static/manage.js index 2455ac6..16d0a54 100644 --- a/couchpotato/core/media/movie/_base/static/manage.js +++ b/couchpotato/core/media/movie/_base/static/manage.js @@ -110,7 +110,8 @@ var MoviesManage = new Class({ return; if(!self.progress_container) - self.progress_container = new Element('div.progress').inject(self.list.navigation, 'after'); + self.progress_container = new Element('div.progress') + .inject(self.list, 'top'); self.progress_container.empty(); diff --git a/couchpotato/core/media/movie/_base/static/movie.scss b/couchpotato/core/media/movie/_base/static/movie.scss index 973003d..f10a5bc 100644 --- a/couchpotato/core/media/movie/_base/static/movie.scss +++ b/couchpotato/core/media/movie/_base/static/movie.scss @@ -164,6 +164,31 @@ $mass_edit_height: 44px; pointer-events: none; } } + + .progress { + + div { + width: 50%; + padding: $padding/4 $padding/2; + display: flex; + + @include media-tablet { + width: 100%; + } + + .folder { + flex: 1 auto; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + margin-right: $padding/2; + } + + .percentage { + font-weight: bold; + } + } + } } .list_list { diff --git a/couchpotato/static/scripts/combined.plugins.min.js b/couchpotato/static/scripts/combined.plugins.min.js index d4cf027..599976a 100644 --- a/couchpotato/static/scripts/combined.plugins.min.js +++ b/couchpotato/static/scripts/combined.plugins.min.js @@ -985,7 +985,7 @@ var MoviesManage = new Class({ } else { var progress = json.progress; if (!self.list.navigation) return; - if (!self.progress_container) self.progress_container = new Element("div.progress").inject(self.list.navigation, "after"); + if (!self.progress_container) self.progress_container = new Element("div.progress").inject(self.list, "top"); self.progress_container.empty(); var sorted_table = self.parseProgress(json.progress); sorted_table.each(function(folder) { diff --git a/couchpotato/static/style/combined.min.css b/couchpotato/static/style/combined.min.css index 0fb3f46..1145a88 100644 --- a/couchpotato/static/style/combined.min.css +++ b/couchpotato/static/style/combined.min.css @@ -126,6 +126,11 @@ @media (max-width:480px){.movies .no_movies{padding:10px} .movies .actions{pointer-events:none} } +.movies .progress div{width:50%;padding:5px 10px;display:-webkit-flex;display:-ms-flexbox;display:flex} +@media (max-width:768px){.movies .progress div{width:100%} +} +.movies .progress div .folder{-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;margin-right:10px} +.movies .progress div .percentage{font-weight:700} .list_list{font-weight:300} .list_list .movie{display:block;border-bottom:1px solid #eaeaea;position:relative;cursor:pointer} .list_list .movie:last-child{border-bottom:none} From 6cac91cfe80e3296bf3ba936fd98b82b5d608d00 Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 24 Aug 2015 21:54:51 +0200 Subject: [PATCH 290/301] Clearer overlay --- couchpotato/core/media/movie/_base/static/movie.scss | 4 ++-- couchpotato/static/style/combined.min.css | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/couchpotato/core/media/movie/_base/static/movie.scss b/couchpotato/core/media/movie/_base/static/movie.scss index f10a5bc..af93a99 100644 --- a/couchpotato/core/media/movie/_base/static/movie.scss +++ b/couchpotato/core/media/movie/_base/static/movie.scss @@ -452,8 +452,8 @@ $mass_edit_height: 44px; } .actions { - background-image: linear-gradient(25deg, rgba($primary_color,0) 0%, rgba($primary_color,.9) 100%); - transition: opacity 250ms; + background-image: linear-gradient(25deg, rgba($primary_color,0) 0%, rgba($primary_color,1) 80%); + transition: opacity 400ms; opacity: 0; position: absolute; top: 0; diff --git a/couchpotato/static/style/combined.min.css b/couchpotato/static/style/combined.min.css index 1145a88..f7414b3 100644 --- a/couchpotato/static/style/combined.min.css +++ b/couchpotato/static/style/combined.min.css @@ -185,7 +185,7 @@ .thumb_list .movie .info .eta{opacity:.5;float:right;margin-left:4px} .thumb_list .movie .info .quality{white-space:nowrap;overflow:hidden} .thumb_list .movie .info .quality span{font-size:.8em;margin-right:2px} -.thumb_list .movie .actions{background-image:linear-gradient(25deg,rgba(172,0,0,0) 0,rgba(172,0,0,.9) 100%);transition:opacity 250ms;opacity:0;position:absolute;top:0;right:0;bottom:0;left:0;text-align:right} +.thumb_list .movie .actions{background-image:linear-gradient(25deg,rgba(172,0,0,0) 0,#ac0000 80%);transition:opacity 400ms;opacity:0;position:absolute;top:0;right:0;bottom:0;left:0;text-align:right} .thumb_list .movie .actions .action{position:relative;display:block;margin-bottom:1px;width:auto;margin-right:10px} .thumb_list .movie .actions .action:first-child{margin-top:10px} .thumb_list .movie .actions .action a{transition:all 150ms cubic-bezier(.9,0,.1,1);display:inline-block;width:auto;padding:6.67px;color:#FFF;border-radius:2px} From c790b1e0d4dcfb554d35bd9da9bf6388ac9cda63 Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 24 Aug 2015 22:00:00 +0200 Subject: [PATCH 291/301] Don't show title on suggestion add --- couchpotato/core/media/movie/_base/static/movie.scss | 4 +++- couchpotato/static/style/combined.min.css | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/couchpotato/core/media/movie/_base/static/movie.scss b/couchpotato/core/media/movie/_base/static/movie.scss index af93a99..3e13c06 100644 --- a/couchpotato/core/media/movie/_base/static/movie.scss +++ b/couchpotato/core/media/movie/_base/static/movie.scss @@ -798,7 +798,9 @@ $mass_edit_height: 44px; } } - .thumbnail { + + .thumbnail, + .data { display: none; } } diff --git a/couchpotato/static/style/combined.min.css b/couchpotato/static/style/combined.min.css index f7414b3..108f4ba 100644 --- a/couchpotato/static/style/combined.min.css +++ b/couchpotato/static/style/combined.min.css @@ -250,7 +250,7 @@ .page.movie_details .section_add .options>div .add{width:200px} .page.movie_details .section_add .options>div .add .button{background:#FFF;-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto;display:block;text-align:center;width:100%;margin:0} .page.movie_details .section_add .options>div .add .button:hover{background:#ac0000} -.page.movie_details .section_add .thumbnail{display:none} +.page.movie_details .section_add .data,.page.movie_details .section_add .thumbnail{display:none} .page.movie_details .files span{text-align:center} .page.movie_details .files .name{text-align:left;-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto} .page.movie_details .files .type{min-width:80px} From 69fb89deb94b8e60792891177ac5e675dafaffc3 Mon Sep 17 00:00:00 2001 From: Ruud Date: Mon, 24 Aug 2015 22:23:33 +0200 Subject: [PATCH 292/301] Add menu button --- couchpotato/static/fonts/config.json | 6 ++++++ couchpotato/static/fonts/icons.eot | Bin 10660 -> 10952 bytes couchpotato/static/fonts/icons.svg | 1 + couchpotato/static/fonts/icons.ttf | Bin 10504 -> 10796 bytes couchpotato/static/fonts/icons.woff | Bin 6376 -> 6584 bytes couchpotato/static/scripts/combined.base.min.js | 7 ++++++- couchpotato/static/scripts/couchpotato.js | 8 ++++++++ couchpotato/static/style/_fonts.scss | 11 +++++----- couchpotato/static/style/combined.min.css | 17 +++++++++++----- couchpotato/static/style/main.scss | 26 ++++++++++++++++++++++++ 10 files changed, 65 insertions(+), 11 deletions(-) diff --git a/couchpotato/static/fonts/config.json b/couchpotato/static/fonts/config.json index 9cde88e..309d296 100644 --- a/couchpotato/static/fonts/config.json +++ b/couchpotato/static/fonts/config.json @@ -139,6 +139,12 @@ "src": "fontawesome" }, { + "uid": "026007bd17bfc67f3fe013199676f620", + "css": "donate", + "code": 59421, + "src": "fontawesome" + }, + { "uid": "94103e1b3f1e8cf514178ec5912b4469", "css": "dropdown", "code": 59409, diff --git a/couchpotato/static/fonts/icons.eot b/couchpotato/static/fonts/icons.eot index c10e56608570acba76b5b59bdbcec0f36e363940..a910d5ea33faff80e5abc8a7049965dde8698c2f 100644 GIT binary patch delta 635 zcmX9)O=}ZT6uoaId6~S7lj)F5CUG(y(lpgH9cw?5(gqW07UIW3YFkC9j7gfHO)}b0 z;s+F@f(wzB`~pz~cYz45T)8Qw3vuDD(5|dtS0e7JH}=6f_nvd_<-Ygc&TY@r?Ib`d zxkb~igAF#jm2a%e0PqvBP^{U`7c2Y`fD{myi)&32eLThl5?gjEwHKS4w@~K+d``t) zb2DuuEsmRMFNJg7j&J7wV1M^r_&bNSS9-P=cJyx^T58no LX1V)1%8UO14#|6_ delta 340 zcmX>Rx+Iuwi6#RBhvq~!GZqEaq{R~*^6MoT7#Q9FaaeM0V!;Jn$?Xgbj4nXzm0VV$ z02BuTSpguyl~$0R+c9B6B#*J}6bH)5asX+L^qk5x)ulPH3=B*Y7#Kw3 zGEx&$IMvVYVqg&e0F*b&019x~u>NOYkkA0~RWfo*D$)+e0htUEK0u-(CqFqc^l!jA zApZ!EZbKEvq9D6^T5sYy(Mfsuh>agwS+JipCX25#mT zK$Q#^4@Ut_1kw6mC!dfsXOx>HRWZ3n%9e9IP!y + \ No newline at end of file diff --git a/couchpotato/static/fonts/icons.ttf b/couchpotato/static/fonts/icons.ttf index 4731d8fe3ead63cc0b34c05b6390ee3e55cec23a..18c5f3a5fa36c7841c51141a8ac8b431a0e458c5 100644 GIT binary patch delta 649 zcmX9+OHUI~6h3#Rb345qr^94AotAc(lv3n1l=3KnhSu1H4~$U|G%j>(3yo43p&=Db zYK$fHK;|@L5n|$9n-*?W*edO+5e!18H0st~R z1Ric)To_Hy4V+cb`HomBRvq_)9exc!3W#&X?V5!?E^`8jt+=bz-DdL|>O6qoU3Iox z^l{!C#wf0?-CH@hd$bI|H39VPO4%v-p6`zVghi}RRVw9%!?G0fa2_d9sn+V1W6U62 zLVB>aQFN{xU;l>s5Ou!l)LnQlr2!%qYHQu8mS4%+zftFLlNavBR*fFje*#4D0wm{d zmfeFp{1bqIJ?y^^gfEgN2q1Dh+#7VBK06*Gu~v$lBC+-+d89Y~+~#`u1#p12rzsg3 z>eHw%3Pi7`RHNhF1lWI};3!6i7o=jlL6BFcR#Q6M9r)>&w_I zGAo-%Q0k+$M-gLk(l#awM2x6>F7yKdAqcuI3i=BBSeg-%uD`xfukf7@T7*7 zs4A&c@-f!sjp&v&7`jmFD&Y~Bc*~<|I^&uDl8>W7HlxTSm6Riz!U6*G%Z6bkQ}|Gw zUxGNz9)G?O$o>L9{}mnhNEQ$O$HBdpW@JcDYu>1H+-&Qek@M~OXa5cMv|0EIhqWKw V*-o&dpS-lRvF_B$Z6U@>e*uLud1n9s delta 355 zcmZ1z(h*e8z{tSBz{k+Qz|4@~AFOX=r|BsP6nO*0Vad6P1s8NBw=*y>x&X0Pa#@K2 zP#g$k1%L!sT0wel$Ak%yK)wtE19w4sVsQab94IHt0i-$7b1KtRm*&JWFfdJEU=WSV zNKH)PR6o0mfkFHOP~I#fBegz}%ZBwo1A~MHP(URkx1=KNFp$L{;R7Tpa`KZCL;nV> z1M-gm`F6R96$K0@d5jnsBw2ubg}lVv)Gd5v-+_D;pd~8{@{3CZJq-037^E)%9c)%m zlv;2%o4J*NL8Aw#zM6rNIhSz)11kduQx(%5ph(Zee};@QlMNUh8D%yXF*b=QfWl*O zlBz;Hzs*+$Zsr$2MGO}YM*)oo(fVH}>qwe2%1us@teE^j(w1{QP#6?~a+}qpo-+X@ T_+)J+`^a8nWZ0}J$IJr&nTS&{ diff --git a/couchpotato/static/fonts/icons.woff b/couchpotato/static/fonts/icons.woff index 98c64892a96264f35cd3a663154a6de92ecc4778..5f1194978ddc6dae78d21cc4c2ef8fef09e3e1e9 100644 GIT binary patch delta 4351 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 delta 4163 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 N`UN#yPk@t88EIOD?Hm9A diff --git a/couchpotato/static/scripts/combined.base.min.js b/couchpotato/static/scripts/combined.base.min.js index 1e6fe34..0dff6a1 100644 --- a/couchpotato/static/scripts/combined.base.min.js +++ b/couchpotato/static/scripts/combined.base.min.js @@ -229,7 +229,12 @@ var CouchPotato = new Class({ createLayout: function() { var self = this; self.block.header = new BlockBase(); - self.c.adopt($(self.block.header).addClass("header").adopt(self.block.navigation = new BlockHeader(self, {}), self.block.search = new BlockSearch(self, {}), self.block.more = new BlockMenu(self, { + self.c.adopt($(self.block.header).addClass("header").adopt(self.block.navigation = new BlockHeader(self, {}), self.block.search = new BlockSearch(self, {}), self.support = new Element("a.donate.icon-donate", { + href: "https://couchpota.to/support/", + target: "_blank" + }).grab(new Element("span", { + text: "Donate" + })), self.block.more = new BlockMenu(self, { button_class: "icon-settings" })), new Element("div.corner_background"), self.content = new Element("div.content").adopt(self.pages_container = new Element("div.pages"), self.block.footer = new BlockFooter(self, {}))); var setting_links = [ new Element("a", { diff --git a/couchpotato/static/scripts/couchpotato.js b/couchpotato/static/scripts/couchpotato.js index 76a7fd0..42a77c4 100644 --- a/couchpotato/static/scripts/couchpotato.js +++ b/couchpotato/static/scripts/couchpotato.js @@ -110,6 +110,14 @@ $(self.block.header).addClass('header').adopt( self.block.navigation = new BlockHeader(self, {}), self.block.search = new BlockSearch(self, {}), + self.support = new Element('a.donate.icon-donate', { + 'href': 'https://couchpota.to/support/', + 'target': '_blank' + }).grab( + new Element('span', { + 'text': 'Donate' + }) + ), self.block.more = new BlockMenu(self, {'button_class': 'icon-settings'}) ), new Element('div.corner_background'), diff --git a/couchpotato/static/style/_fonts.scss b/couchpotato/static/style/_fonts.scss index cf74a3e..4f91e35 100644 --- a/couchpotato/static/style/_fonts.scss +++ b/couchpotato/static/style/_fonts.scss @@ -1,11 +1,11 @@ /* Fonts */ @font-face { font-family: 'icons'; - src: url('../fonts/icons.eot?74719542'); - src: url('../fonts/icons.eot?74719542#iefix') format('embedded-opentype'), - url('../fonts/icons.woff?747195412') format('woff'), - url('../fonts/icons.ttf?74719542') format('truetype'), - url('../fonts/icons.svg?74719542#icons') format('svg'); + src: url('../fonts/icons.eot?3'); + src: url('../fonts/icons.eot?3#iefix') format('embedded-opentype'), + url('../fonts/icons.woff?3') format('woff'), + url('../fonts/icons.ttf?3') format('truetype'), + url('../fonts/icons.svg?3#icons') format('svg'); font-weight: normal; font-style: normal; } @@ -48,6 +48,7 @@ .icon-star:before { content: '\e81a'; } .icon-star-empty:before { content: '\e81b'; } .icon-star-half:before { content: '\e81c'; } +.icon-donate:before { content: '\e81d'; } @font-face { font-family: 'OpenSans'; diff --git a/couchpotato/static/style/combined.min.css b/couchpotato/static/style/combined.min.css index 108f4ba..4d64f48 100644 --- a/couchpotato/static/style/combined.min.css +++ b/couchpotato/static/style/combined.min.css @@ -507,7 +507,7 @@ .page.login input[type=password],.page.login input[type=text]{width:100%!important} .page.login .remember_me{font-size:15px;float:left;width:150px} .page.login .button{float:right;margin:0;transition:none} -@font-face{font-family:icons;src:url(../fonts/icons.eot?74719542);src:url(../fonts/icons.eot?74719542#iefix) format("embedded-opentype"),url(../fonts/icons.woff?747195412) format("woff"),url(../fonts/icons.ttf?74719542) format("truetype"),url(../fonts/icons.svg?74719542#icons) format("svg");font-weight:400;font-style:normal} +@font-face{font-family:icons;src:url(../fonts/icons.eot?3);src:url(../fonts/icons.eot?3#iefix) format("embedded-opentype"),url(../fonts/icons.woff?3) format("woff"),url(../fonts/icons.ttf?3) format("truetype"),url(../fonts/icons.svg?3#icons) format("svg");font-weight:400;font-style:normal} [class*=" icon-"]:before,[class^=icon-]:before{font-family:icons;font-style:normal;font-weight:400;speak:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale} .icon-left-arrow:before{content:'\e800'} .icon-settings:before{content:'\e801'} @@ -538,6 +538,7 @@ .icon-star:before{content:'\e81a'} .icon-star-empty:before{content:'\e81b'} .icon-star-half:before{content:'\e81c'} +.icon-donate:before{content:'\e81d'} @font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Light-webfont.eot);src:url(../fonts/OpenSans-Light-webfont.eot?#iefix) format("embedded-opentype"),url(../fonts/OpenSans-Light-webfont.woff) format("woff"),url(../fonts/OpenSans-Light-webfont.ttf) format("truetype"),url(../fonts/OpenSans-Light-webfont.svg#OpenSansRegular) format("svg");font-weight:200;font-style:normal} @font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Regular-webfont.eot);src:url(../fonts/OpenSans-Regular-webfont.eot?#iefix) format("embedded-opentype"),url(../fonts/OpenSans-Regular-webfont.woff) format("woff"),url(../fonts/OpenSans-Regular-webfont.ttf) format("truetype"),url(../fonts/OpenSans-Regular-webfont.svg#OpenSansRegular) format("svg");font-weight:400;font-style:normal} @font-face{font-family:OpenSans;src:url(../fonts/OpenSans-Italic-webfont.eot);src:url(../fonts/OpenSans-Italic-webfont.eot?#iefix) format("embedded-opentype"),url(../fonts/OpenSans-Italic-webfont.woff) format("woff"),url(../fonts/OpenSans-Italic-webfont.ttf) format("truetype"),url(../fonts/OpenSans-Italic-webfont.svg#OpenSansItalic) format("svg");font-weight:400;font-style:italic} @@ -569,21 +570,27 @@ input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#F .header .navigation ul li{display:block} .header .navigation ul li a{padding:10px 20px;display:block;position:relative} .header .navigation ul li a:before{position:absolute;width:100%;display:none;text-align:center;font-size:18px;text-indent:0} -.header .navigation ul li a.icon-home:before{font-size:24px} -.header .menu,.header .notification_menu,.header .search_form{position:absolute;z-index:21;bottom:6.67px;width:44px;height:44px} @media (max-width:480px){.header .navigation ul li{line-height:0} .header .navigation ul li a{line-height:24px;height:44px;padding:10px 0;text-align:center} .header .navigation ul li a span{display:none} .header .navigation ul li a:before{display:block} -.header .menu,.header .notification_menu,.header .search_form{bottom:0} } +.header .navigation ul li a.icon-home:before{font-size:24px} +.header .donate{position:absolute;bottom:54px;left:0;right:0;padding:10px 20px} +.header .donate:before{display:none;font-size:20px;text-align:center} +@media (max-width:480px){.header .donate{bottom:132px;padding:10px 0} +.header .donate span{display:none} +.header .donate:before{display:block} +} +.header .menu,.header .notification_menu,.header .search_form{position:absolute;z-index:21;bottom:6.67px;width:44px;height:44px} .header .menu .wrapper,.header .notification_menu .wrapper,.header .search_form .wrapper{min-width:170px;-webkit-transform-origin:0 90%;transform-origin:0 90%} .header .menu>a,.header .notification_menu>a,.header .search_form>a{display:inline-block;height:100%;width:100%;text-align:center;line-height:44px;font-size:20px} .header .notification_menu{left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)} .header .notification_menu .button:before{font-size:20px;top:-2px} .header .notification_menu .badge{position:absolute;color:#FFF;top:5px;right:0;background:#ac0000;border-radius:50%;width:18px;height:18px;line-height:16px;text-align:center;font-size:10px;font-weight:lighter} .header .notification_menu .wrapper{width:320px} -@media (max-width:480px){.header .notification_menu{bottom:44px} +@media (max-width:480px){.header .menu,.header .notification_menu,.header .search_form{bottom:0} +.header .notification_menu{bottom:44px} .header .notification_menu .wrapper{width:250px} } .header .notification_menu ul{min-height:60px;max-height:300px;overflow-y:auto!important} diff --git a/couchpotato/static/style/main.scss b/couchpotato/static/style/main.scss index cf44c02..2dce5a6 100644 --- a/couchpotato/static/style/main.scss +++ b/couchpotato/static/style/main.scss @@ -195,6 +195,32 @@ input, textarea, select { } + .donate { + position: absolute; + bottom: 44px + $padding/2; + left: 0; + right: 0; + padding: $padding/2 $padding; + + &:before { + display: none; + font-size: 20px; + text-align: center; + } + + @include media-phablet { + bottom: 44px * 3; + padding: $padding/2 0; + + span { + display: none; + } + + &:before { + display: block; + } + } + } .menu, .search_form, .notification_menu { position: absolute; From 1c20e97dd2d23defc0e91248289fced7348fd011 Mon Sep 17 00:00:00 2001 From: Ruud Date: Tue, 25 Aug 2015 10:27:16 +0200 Subject: [PATCH 293/301] IE/Edge fixes --- couchpotato/core/media/movie/_base/static/movie.scss | 3 ++- couchpotato/core/plugins/profile/static/profile.scss | 2 ++ couchpotato/static/scripts/combined.base.min.js | 3 +++ couchpotato/static/scripts/page.js | 4 ++++ couchpotato/static/style/combined.min.css | 11 ++++++----- couchpotato/static/style/settings.scss | 7 +++++++ 6 files changed, 24 insertions(+), 6 deletions(-) diff --git a/couchpotato/core/media/movie/_base/static/movie.scss b/couchpotato/core/media/movie/_base/static/movie.scss index 3e13c06..1ba67ae 100644 --- a/couchpotato/core/media/movie/_base/static/movie.scss +++ b/couchpotato/core/media/movie/_base/static/movie.scss @@ -401,6 +401,7 @@ $mass_edit_height: 44px; position: relative; width: 100%; padding-bottom: 150%; + overflow: hidden; } .poster { @@ -798,7 +799,7 @@ $mass_edit_height: 44px; } } - + .thumbnail, .data { display: none; diff --git a/couchpotato/core/plugins/profile/static/profile.scss b/couchpotato/core/plugins/profile/static/profile.scss index 6fd4577..ee9ae41 100644 --- a/couchpotato/core/plugins/profile/static/profile.scss +++ b/couchpotato/core/plugins/profile/static/profile.scss @@ -49,6 +49,7 @@ cursor: pointer; &.handle { + cursor: move; cursor: grab; } @@ -133,6 +134,7 @@ font-size: 20px; width: 20px; float: right; + cursor: move; cursor: grab; opacity: .5; text-align: center; diff --git a/couchpotato/static/scripts/combined.base.min.js b/couchpotato/static/scripts/combined.base.min.js index 0dff6a1..113bedb 100644 --- a/couchpotato/static/scripts/combined.base.min.js +++ b/couchpotato/static/scripts/combined.base.min.js @@ -722,6 +722,9 @@ var PageBase = new Class({ $(page["class"]).inject(App.getPageContainer()); }); }, + sortPageByOrder: function(a, b) { + return (a.order || 100) - (b.order || 100); + }, open: function(action, params) { var self = this; try { diff --git a/couchpotato/static/scripts/page.js b/couchpotato/static/scripts/page.js index 6644549..e59f941 100644 --- a/couchpotato/static/scripts/page.js +++ b/couchpotato/static/scripts/page.js @@ -93,6 +93,10 @@ var PageBase = new Class({ }, + sortPageByOrder: function(a, b){ + return (a.order || 100) - (b.order || 100); + }, + open: function(action, params){ var self = this; //p('Opening: ' +self.getName() + ', ' + action + ', ' + Object.toQueryString(params)); diff --git a/couchpotato/static/style/combined.min.css b/couchpotato/static/style/combined.min.css index 4d64f48..918fc52 100644 --- a/couchpotato/static/style/combined.min.css +++ b/couchpotato/static/style/combined.min.css @@ -176,7 +176,7 @@ .thumb_list .movie{max-width:50%;border-width:0 4px} } .thumb_list .movie input[type=checkbox]{top:10px;left:10px} -.thumb_list .movie .poster_container{border-radius:3px;position:relative;width:100%;padding-bottom:150%} +.thumb_list .movie .poster_container{border-radius:3px;position:relative;width:100%;padding-bottom:150%;overflow:hidden} .thumb_list .movie .poster{position:absolute;background:center no-repeat #eaeaea;background-size:cover;overflow:hidden;height:100%;width:100%} .thumb_list .movie .info{clear:both;font-size:.85em} .thumb_list .movie .info .title{display:-webkit-flex;display:-ms-flexbox;display:flex;padding:3px 0} @@ -435,7 +435,7 @@ .profile .ctrlHolder .types .type label span{font-size:.9em} .profile .ctrlHolder .types .type input[type=checkbox]{margin-right:3px} .profile .ctrlHolder .types .type .delete,.profile .ctrlHolder .types .type .handle{margin-left:5px;width:20px;font-size:20px;opacity:.1;text-align:center;cursor:pointer} -.profile .ctrlHolder .types .type .delete.handle,.profile .ctrlHolder .types .type .handle.handle{cursor:-webkit-grab;cursor:grab} +.profile .ctrlHolder .types .type .delete.handle,.profile .ctrlHolder .types .type .handle.handle{cursor:move;cursor:-webkit-grab;cursor:grab} .profile .ctrlHolder .types .type .delete:hover,.profile .ctrlHolder .types .type .handle:hover{opacity:1} .profile .ctrlHolder .types .type.is_empty .delete,.profile .ctrlHolder .types .type.is_empty .handle{display:none} .profile .ctrlHolder.wait_for.wait_for{display:block} @@ -450,7 +450,7 @@ #profile_ordering li input[type=checkbox]{margin:2px 10px 0 0;vertical-align:top} #profile_ordering li>span{display:inline-block;height:20px;vertical-align:top;line-height:20px} #profile_ordering li>span.profile_label{-webkit-flex:1 1 auto;-ms-flex:1 1 auto;flex:1 1 auto} -#profile_ordering li .handle{font-size:20px;width:20px;float:right;cursor:-webkit-grab;cursor:grab;opacity:.5;text-align:center} +#profile_ordering li .handle{font-size:20px;width:20px;float:right;cursor:move;cursor:-webkit-grab;cursor:grab;opacity:.5;text-align:center} #profile_ordering li .handle:hover{opacity:1} .group_sizes .item .label{min-width:150px} .group_sizes .item .max,.group_sizes .item .min{display:inline-block;width:70px!important;min-width:0!important;margin-right:10px;text-align:center} @@ -705,7 +705,7 @@ input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#F .page.settings fieldset h2{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:baseline;-ms-flex-align:baseline;align-items:baseline;padding:0 0 0 20px} .page.settings fieldset h2 .icon{margin-right:10px} .page.settings fieldset h2 .icon img{vertical-align:middle;position:relative;top:-1px} -.page.settings fieldset h2 .hint{margin-left:10px;font-size:1rem} +.page.settings fieldset h2 .hint{-webkit-flex:1;-ms-flex:1;flex:1;margin-left:10px;font-size:1rem} @media (max-width:480px){.page.settings fieldset h2{display:block} .page.settings fieldset h2 .hint{margin:0;display:block} } @@ -721,8 +721,9 @@ input,select,textarea{font-size:1em;font-weight:300;padding:6.67px;background:#F } .page.settings fieldset .ctrlHolder .select_wrapper{position:relative;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center} .page.settings fieldset .ctrlHolder .select_wrapper select{cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;width:100%;min-width:200px;border-radius:0} +.page.settings fieldset .ctrlHolder .select_wrapper select::-ms-expand{display:none} .page.settings fieldset .ctrlHolder .select_wrapper:before{vertical-align:top;pointer-events:none;position:absolute;top:0;line-height:2em;right:10px;height:100%} -.page.settings fieldset .ctrlHolder .formHint{opacity:.8;margin-left:20px} +.page.settings fieldset .ctrlHolder .formHint{-webkit-flex:1;-ms-flex:1;flex:1;opacity:.8;margin-left:20px} .page.settings fieldset .ctrlHolder .formHint a{font-weight:400;color:#ac0000;text-decoration:underline} @media (max-width:480px){.page.settings fieldset .ctrlHolder .select_wrapper{width:100%} .page.settings fieldset .ctrlHolder .formHint{min-width:100%;margin-left:0} diff --git a/couchpotato/static/style/settings.scss b/couchpotato/static/style/settings.scss index 50680c9..16c29e2 100644 --- a/couchpotato/static/style/settings.scss +++ b/couchpotato/static/style/settings.scss @@ -91,6 +91,7 @@ } .hint { + flex: 1; margin-left: $padding/2; font-size: 1rem; @@ -166,6 +167,10 @@ width: 100%; min-width: 200px; border-radius: 0; + + &::-ms-expand { + display: none; + } } &:before { @@ -180,6 +185,7 @@ } .formHint { + flex: 1; opacity: .8; margin-left: $padding; @@ -550,6 +556,7 @@ select { min-width: 0 !important; } + } } From cf4dd410e9523b128ed36d06ab59223e5400a9f4 Mon Sep 17 00:00:00 2001 From: Ruud Date: Tue, 25 Aug 2015 11:18:50 +0200 Subject: [PATCH 294/301] Log report fixes --- couchpotato/core/plugins/log/main.py | 2 +- couchpotato/core/plugins/log/static/log.js | 7 +------ couchpotato/core/plugins/log/static/log.scss | 17 +++++++++++------ couchpotato/static/scripts/combined.plugins.min.js | 7 +------ couchpotato/static/style/combined.min.css | 11 ++++++----- 5 files changed, 20 insertions(+), 24 deletions(-) diff --git a/couchpotato/core/plugins/log/main.py b/couchpotato/core/plugins/log/main.py index 19edc19..4bf7cf3 100644 --- a/couchpotato/core/plugins/log/main.py +++ b/couchpotato/core/plugins/log/main.py @@ -137,7 +137,7 @@ class Logging(Plugin): re_split = r'\x1b' for log_line in logs_raw: split = re.split(re_split, log_line) - if split: + if split and len(split) == 3: try: date, time, log_type = splitString(split[0], ' ') timestamp = '%s %s' % (date, time) diff --git a/couchpotato/core/plugins/log/static/log.js b/couchpotato/core/plugins/log/static/log.js index ee471bc..0d47914 100644 --- a/couchpotato/core/plugins/log/static/log.js +++ b/couchpotato/core/plugins/log/static/log.js @@ -257,12 +257,7 @@ Page.Log = new Class({ }) ), textarea = new Element('textarea', { - 'text': body, - 'events': { - 'click': function(){ - this.select(); - } - } + 'text': body }), new Element('a.button', { 'target': '_blank', diff --git a/couchpotato/core/plugins/log/static/log.scss b/couchpotato/core/plugins/log/static/log.scss index 6226e02..4be815b 100644 --- a/couchpotato/core/plugins/log/static/log.scss +++ b/couchpotato/core/plugins/log/static/log.scss @@ -51,12 +51,12 @@ .time { clear: both; - font-size: 11px; + font-size: .75em; border-top: 1px solid rgba(255, 255, 255, 0.1); - position: relative; overflow: hidden; padding: 0 3px; font-family: Lucida Console, Monaco, Nimbus Mono L, monospace, serif; + display: flex; &.highlight { background: $theme_off; @@ -66,6 +66,7 @@ padding: 5px 0 3px; display: inline-block; vertical-align: middle; + width: 90px; } ::selection { @@ -74,14 +75,14 @@ } } - .type { + .type.type { margin-left: 10px; + width: 40px; } .message { - float: right; - width: 86%; white-space: pre-wrap; + flex: 1 auto; } @@ -117,10 +118,10 @@ pointer-events: auto; .button { - display: inline-block; margin: 10px 0; padding: 10px; color: $background_color; + background: $primary_color; } .bug { @@ -132,6 +133,10 @@ display: flex; flex-flow: column nowrap; + > span { + margin: $padding/2 0 $padding 0; + } + textarea { display: block; width: 100%; diff --git a/couchpotato/static/scripts/combined.plugins.min.js b/couchpotato/static/scripts/combined.plugins.min.js index 599976a..958189e 100644 --- a/couchpotato/static/scripts/combined.plugins.min.js +++ b/couchpotato/static/scripts/combined.plugins.min.js @@ -3131,12 +3131,7 @@ Page.Log = new Class({ }), new Element("span", { html: " before posting, then copy the text below and FILL IN the dots." })), textarea = new Element("textarea", { - text: body, - events: { - click: function() { - this.select(); - } - } + text: body }), new Element("a.button", { target: "_blank", text: "Create a new issue on GitHub with the text above", diff --git a/couchpotato/static/style/combined.min.css b/couchpotato/static/style/combined.min.css index 918fc52..0d24d71 100644 --- a/couchpotato/static/style/combined.min.css +++ b/couchpotato/static/style/combined.min.css @@ -410,20 +410,21 @@ .page.log .container{padding:20px;overflow:hidden;line-height:150%} .page.log .container.loading{text-align:center;font-size:20px;padding:100px 50px} .page.log .container select{vertical-align:top} -.page.log .container .time{clear:both;font-size:11px;border-top:1px solid rgba(255,255,255,.1);position:relative;overflow:hidden;padding:0 3px;font-family:Lucida Console,Monaco,Nimbus Mono L,monospace,serif} +.page.log .container .time{clear:both;font-size:.75em;border-top:1px solid rgba(255,255,255,.1);overflow:hidden;padding:0 3px;font-family:Lucida Console,Monaco,Nimbus Mono L,monospace,serif;display:-webkit-flex;display:-ms-flexbox;display:flex} .page.log .container .time.highlight{background:#eaeaea} -.page.log .container .time span{padding:5px 0 3px;display:inline-block;vertical-align:middle} +.page.log .container .time span{padding:5px 0 3px;display:inline-block;vertical-align:middle;width:90px} .page.log .container .time ::-moz-selection{background-color:#000;color:#FFF} .page.log .container .time ::selection{background-color:#000;color:#FFF} -.page.log .container .type{margin-left:10px} -.page.log .container .message{float:right;width:86%;white-space:pre-wrap} +.page.log .container .type.type{margin-left:10px;width:40px} +.page.log .container .message{white-space:pre-wrap;-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto} .page.log .container .error{color:#FFA4A4} .page.log .container .debug span{opacity:.6} .page.log [data-filter=DEBUG] .error,.page.log [data-filter=DEBUG] .info,.page.log [data-filter=ERROR] .debug,.page.log [data-filter=ERROR] .info,.page.log [data-filter=INFO] .debug,.page.log [data-filter=INFO] .error{display:none} .report_popup.report_popup{position:fixed;left:0;right:0;bottom:0;top:0;z-index:99999;font-size:14px;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;opacity:1;color:#FFF;pointer-events:auto} .disable_hover .scroll_content>*,.mask,.ripple{pointer-events:none} -.report_popup.report_popup .button{display:inline-block;margin:10px 0;padding:10px;color:#FFF} +.report_popup.report_popup .button{margin:10px 0;padding:10px;color:#FFF;background:#ac0000} .report_popup.report_popup .bug{width:80%;height:80%;max-height:800px;max-width:800px;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:column nowrap;-ms-flex-flow:column nowrap;flex-flow:column nowrap} +.report_popup.report_popup .bug>span{margin:10px 0 20px} .report_popup.report_popup .bug textarea{display:block;width:100%;background:#FFF;padding:20px;overflow:auto;color:#666;height:70%;font-size:12px} .do_report.do_report{z-index:10000;position:absolute;padding:10px;background:#ac0000;color:#FFF} .add_new_profile{padding:20px;display:block;text-align:center;font-size:20px;border-bottom:1px solid #eaeaea} From 9dc47082d53b1c4397570e7961f24f7dfcea97b8 Mon Sep 17 00:00:00 2001 From: Ruud Date: Tue, 25 Aug 2015 11:56:52 +0200 Subject: [PATCH 295/301] Optimize search UI --- couchpotato/core/media/_base/search/static/search.js | 1 + couchpotato/core/media/_base/search/static/search.scss | 12 +++++++++--- couchpotato/static/scripts/combined.plugins.min.js | 1 + couchpotato/static/style/combined.min.css | 3 ++- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/couchpotato/core/media/_base/search/static/search.js b/couchpotato/core/media/_base/search/static/search.js index 6fabad4..7b9a536 100644 --- a/couchpotato/core/media/_base/search/static/search.js +++ b/couchpotato/core/media/_base/search/static/search.js @@ -44,6 +44,7 @@ var BlockSearch = new Class({ 'blur': function(){ focus_timer = (function(){ self.el.removeClass('focused'); + self.last_q = null; }).delay(100); } } diff --git a/couchpotato/core/media/_base/search/static/search.scss b/couchpotato/core/media/_base/search/static/search.scss index 8ffd66f..352fdf1 100644 --- a/couchpotato/core/media/_base/search/static/search.scss +++ b/couchpotato/core/media/_base/search/static/search.scss @@ -86,8 +86,15 @@ } } - &.filled .input input { - background: rgba($theme_off, .4); + &.filled { + &.focused .icon-search:before, + .page.home & .icon-search:before { + content: '\e80e'; + } + + .input input { + background: rgba($theme_off, .4); + } } &.focused, @@ -357,7 +364,6 @@ transform: none; font-size: 2em; opacity: .5; - pointer-events: none; @include media-phablet { right: $padding/2; diff --git a/couchpotato/static/scripts/combined.plugins.min.js b/couchpotato/static/scripts/combined.plugins.min.js index 958189e..a6b0571 100644 --- a/couchpotato/static/scripts/combined.plugins.min.js +++ b/couchpotato/static/scripts/combined.plugins.min.js @@ -209,6 +209,7 @@ var BlockSearch = new Class({ blur: function() { focus_timer = function() { self.el.removeClass("focused"); + self.last_q = null; }.delay(100); } } diff --git a/couchpotato/static/style/combined.min.css b/couchpotato/static/style/combined.min.css index 0d24d71..dcacccc 100644 --- a/couchpotato/static/style/combined.min.css +++ b/couchpotato/static/style/combined.min.css @@ -11,6 +11,7 @@ .search_form .input input:focus::-webkit-input-placeholder{color:#000;opacity:.7} .search_form .input input:focus::-moz-placeholder{color:#000;opacity:.7} .search_form .input input:focus:-ms-input-placeholder{color:#000;opacity:.7} +.page.home .search_form.filled .icon-search:before,.search_form.filled.focused .icon-search:before{content:'\e80e'} .search_form.filled .input input{background:rgba(234,234,234,.4)} .page.home .search_form,.search_form.focused,.search_form.shown{border-color:#04bce6} .page.home .search_form .wrapper,.search_form.focused .wrapper,.search_form.shown .wrapper{display:block;width:380px;-webkit-transform-origin:0 90%;transform-origin:0 90%} @@ -59,7 +60,7 @@ .page.home .search_form{display:block;padding:20px;width:100%;max-width:500px;margin:20px auto 0;height:106px;position:relative} @media (max-width:480px){.page.home .search_form{margin-top:10px;height:64px} } -.page.home .search_form .icon-search{display:block;color:#000;right:20px;top:20px;width:66px;height:66px;line-height:66px;left:auto;-webkit-transform:none;transform:none;font-size:2em;opacity:.5;pointer-events:none} +.page.home .search_form .icon-search{display:block;color:#000;right:20px;top:20px;width:66px;height:66px;line-height:66px;left:auto;-webkit-transform:none;transform:none;font-size:2em;opacity:.5} @media (max-width:480px){.page.home .search_form .icon-search{width:44px;height:44px;line-height:44px;right:10px;top:10px;font-size:1.5em} } .page.home .search_form .wrapper{border-radius:0;box-shadow:none;bottom:auto;top:20px;left:20px;right:20px;position:absolute;width:auto} From ec68e429014b49e4f4923dcbe8ccd588ebfe059d Mon Sep 17 00:00:00 2001 From: Ruud Date: Tue, 25 Aug 2015 12:02:38 +0200 Subject: [PATCH 296/301] Move suggestion class --- couchpotato/core/media/movie/suggestion.py | 141 +++++++++++++++++++++ .../core/media/movie/suggestion/__init__.py | 0 couchpotato/core/media/movie/suggestion/main.py | 141 --------------------- 3 files changed, 141 insertions(+), 141 deletions(-) create mode 100644 couchpotato/core/media/movie/suggestion.py delete mode 100644 couchpotato/core/media/movie/suggestion/__init__.py delete mode 100755 couchpotato/core/media/movie/suggestion/main.py diff --git a/couchpotato/core/media/movie/suggestion.py b/couchpotato/core/media/movie/suggestion.py new file mode 100644 index 0000000..2b4fd7b --- /dev/null +++ b/couchpotato/core/media/movie/suggestion.py @@ -0,0 +1,141 @@ +import time +from couchpotato.api import addApiView +from couchpotato.core.event import fireEvent, addEvent +from couchpotato.core.helpers.variable import splitString, removeDuplicate, getIdentifier, getTitle +from couchpotato.core.plugins.base import Plugin +from couchpotato.environment import Env + + +autoload = 'Suggestion' + + +class Suggestion(Plugin): + + def __init__(self): + + addApiView('suggestion.view', self.suggestView) + addApiView('suggestion.ignore', self.ignoreView) + + def test(): + time.sleep(1) + self.suggestView() + + addEvent('app.load', test) + + def suggestView(self, limit = 6, **kwargs): + + movies = splitString(kwargs.get('movies', '')) + ignored = splitString(kwargs.get('ignored', '')) + seen = splitString(kwargs.get('seen', '')) + + cached_suggestion = self.getCache('suggestion_cached') + if cached_suggestion: + suggestions = cached_suggestion + else: + + if not movies or len(movies) == 0: + active_movies = fireEvent('media.with_status', ['active', 'done'], types = 'movie', single = True) + movies = [getIdentifier(x) for x in active_movies] + + if not ignored or len(ignored) == 0: + ignored = splitString(Env.prop('suggest_ignore', default = '')) + if not seen or len(seen) == 0: + movies.extend(splitString(Env.prop('suggest_seen', default = ''))) + + suggestions = fireEvent('movie.suggest', movies = movies, ignore = ignored, single = True) + self.setCache('suggestion_cached', suggestions, timeout = 6048000) # Cache for 10 weeks + + medias = [] + for suggestion in suggestions[:int(limit)]: + + # Cache poster + posters = suggestion.get('images', {}).get('poster', []) + poster = [x for x in posters if 'tmdb' in x] + posters = poster if len(poster) > 0 else posters + + cached_poster = fireEvent('file.download', url = posters[0], single = True) if len(posters) > 0 else False + files = {'image_poster': [cached_poster] } if cached_poster else {} + + medias.append({ + 'status': 'suggested', + 'title': getTitle(suggestion), + 'type': 'movie', + 'info': suggestion, + 'files': files, + 'identifiers': { + 'imdb': suggestion.get('imdb') + } + }) + + return { + 'success': True, + 'movies': medias + } + + def ignoreView(self, imdb = None, limit = 6, remove_only = False, mark_seen = False, **kwargs): + + ignored = splitString(Env.prop('suggest_ignore', default = '')) + seen = splitString(Env.prop('suggest_seen', default = '')) + + new_suggestions = [] + if imdb: + if mark_seen: + seen.append(imdb) + Env.prop('suggest_seen', ','.join(set(seen))) + elif not remove_only: + ignored.append(imdb) + Env.prop('suggest_ignore', ','.join(set(ignored))) + + new_suggestions = self.updateSuggestionCache(ignore_imdb = imdb, limit = limit, ignored = ignored, seen = seen) + + if len(new_suggestions) <= limit: + return { + 'result': False + } + + # Only return new (last) item + media = { + 'status': 'suggested', + 'title': getTitle(new_suggestions[limit]), + 'type': 'movie', + 'info': new_suggestions[limit], + 'identifiers': { + 'imdb': new_suggestions[limit].get('imdb') + } + } + + return { + 'result': True, + 'movie': media + } + + def updateSuggestionCache(self, ignore_imdb = None, limit = 6, ignored = None, seen = None): + + # Combine with previous suggestion_cache + cached_suggestion = self.getCache('suggestion_cached') or [] + new_suggestions = [] + ignored = [] if not ignored else ignored + seen = [] if not seen else seen + + if ignore_imdb: + suggested_imdbs = [] + for cs in cached_suggestion: + if cs.get('imdb') != ignore_imdb and cs.get('imdb') not in suggested_imdbs: + suggested_imdbs.append(cs.get('imdb')) + new_suggestions.append(cs) + + # Get new results and add them + if len(new_suggestions) - 1 < limit: + active_movies = fireEvent('media.with_status', ['active', 'done'], single = True) + movies = [getIdentifier(x) for x in active_movies] + movies.extend(seen) + + ignored.extend([x.get('imdb') for x in cached_suggestion]) + suggestions = fireEvent('movie.suggest', movies = movies, ignore = removeDuplicate(ignored), single = True) + + if suggestions: + new_suggestions.extend(suggestions) + + self.setCache('suggestion_cached', new_suggestions, timeout = 3024000) + + return new_suggestions diff --git a/couchpotato/core/media/movie/suggestion/__init__.py b/couchpotato/core/media/movie/suggestion/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/couchpotato/core/media/movie/suggestion/main.py b/couchpotato/core/media/movie/suggestion/main.py deleted file mode 100755 index 2b4fd7b..0000000 --- a/couchpotato/core/media/movie/suggestion/main.py +++ /dev/null @@ -1,141 +0,0 @@ -import time -from couchpotato.api import addApiView -from couchpotato.core.event import fireEvent, addEvent -from couchpotato.core.helpers.variable import splitString, removeDuplicate, getIdentifier, getTitle -from couchpotato.core.plugins.base import Plugin -from couchpotato.environment import Env - - -autoload = 'Suggestion' - - -class Suggestion(Plugin): - - def __init__(self): - - addApiView('suggestion.view', self.suggestView) - addApiView('suggestion.ignore', self.ignoreView) - - def test(): - time.sleep(1) - self.suggestView() - - addEvent('app.load', test) - - def suggestView(self, limit = 6, **kwargs): - - movies = splitString(kwargs.get('movies', '')) - ignored = splitString(kwargs.get('ignored', '')) - seen = splitString(kwargs.get('seen', '')) - - cached_suggestion = self.getCache('suggestion_cached') - if cached_suggestion: - suggestions = cached_suggestion - else: - - if not movies or len(movies) == 0: - active_movies = fireEvent('media.with_status', ['active', 'done'], types = 'movie', single = True) - movies = [getIdentifier(x) for x in active_movies] - - if not ignored or len(ignored) == 0: - ignored = splitString(Env.prop('suggest_ignore', default = '')) - if not seen or len(seen) == 0: - movies.extend(splitString(Env.prop('suggest_seen', default = ''))) - - suggestions = fireEvent('movie.suggest', movies = movies, ignore = ignored, single = True) - self.setCache('suggestion_cached', suggestions, timeout = 6048000) # Cache for 10 weeks - - medias = [] - for suggestion in suggestions[:int(limit)]: - - # Cache poster - posters = suggestion.get('images', {}).get('poster', []) - poster = [x for x in posters if 'tmdb' in x] - posters = poster if len(poster) > 0 else posters - - cached_poster = fireEvent('file.download', url = posters[0], single = True) if len(posters) > 0 else False - files = {'image_poster': [cached_poster] } if cached_poster else {} - - medias.append({ - 'status': 'suggested', - 'title': getTitle(suggestion), - 'type': 'movie', - 'info': suggestion, - 'files': files, - 'identifiers': { - 'imdb': suggestion.get('imdb') - } - }) - - return { - 'success': True, - 'movies': medias - } - - def ignoreView(self, imdb = None, limit = 6, remove_only = False, mark_seen = False, **kwargs): - - ignored = splitString(Env.prop('suggest_ignore', default = '')) - seen = splitString(Env.prop('suggest_seen', default = '')) - - new_suggestions = [] - if imdb: - if mark_seen: - seen.append(imdb) - Env.prop('suggest_seen', ','.join(set(seen))) - elif not remove_only: - ignored.append(imdb) - Env.prop('suggest_ignore', ','.join(set(ignored))) - - new_suggestions = self.updateSuggestionCache(ignore_imdb = imdb, limit = limit, ignored = ignored, seen = seen) - - if len(new_suggestions) <= limit: - return { - 'result': False - } - - # Only return new (last) item - media = { - 'status': 'suggested', - 'title': getTitle(new_suggestions[limit]), - 'type': 'movie', - 'info': new_suggestions[limit], - 'identifiers': { - 'imdb': new_suggestions[limit].get('imdb') - } - } - - return { - 'result': True, - 'movie': media - } - - def updateSuggestionCache(self, ignore_imdb = None, limit = 6, ignored = None, seen = None): - - # Combine with previous suggestion_cache - cached_suggestion = self.getCache('suggestion_cached') or [] - new_suggestions = [] - ignored = [] if not ignored else ignored - seen = [] if not seen else seen - - if ignore_imdb: - suggested_imdbs = [] - for cs in cached_suggestion: - if cs.get('imdb') != ignore_imdb and cs.get('imdb') not in suggested_imdbs: - suggested_imdbs.append(cs.get('imdb')) - new_suggestions.append(cs) - - # Get new results and add them - if len(new_suggestions) - 1 < limit: - active_movies = fireEvent('media.with_status', ['active', 'done'], single = True) - movies = [getIdentifier(x) for x in active_movies] - movies.extend(seen) - - ignored.extend([x.get('imdb') for x in cached_suggestion]) - suggestions = fireEvent('movie.suggest', movies = movies, ignore = removeDuplicate(ignored), single = True) - - if suggestions: - new_suggestions.extend(suggestions) - - self.setCache('suggestion_cached', new_suggestions, timeout = 3024000) - - return new_suggestions From d7e88d32b74f840da0ced2d3afc58bc032b40692 Mon Sep 17 00:00:00 2001 From: Ruud Date: Tue, 25 Aug 2015 12:02:45 +0200 Subject: [PATCH 297/301] Remove old CSS --- couchpotato/static/style/main_old.css | 965 ---------------------------------- couchpotato/static/style/settings.css | 820 ----------------------------- 2 files changed, 1785 deletions(-) delete mode 100644 couchpotato/static/style/main_old.css delete mode 100644 couchpotato/static/style/settings.css diff --git a/couchpotato/static/style/main_old.css b/couchpotato/static/style/main_old.css deleted file mode 100644 index c7850b2..0000000 --- a/couchpotato/static/style/main_old.css +++ /dev/null @@ -1,965 +0,0 @@ -body, html { - color: #fff; - font-size: 12px; - line-height: 1.5; - font-family: OpenSans, "Helvetica Neue", Helvetica, Arial, Geneva, sans-serif; - height: 100%; - margin: 0; - padding: 0; - background: #4e5969; - -webkit-font-smoothing: subpixel-antialiased; - -moz-osx-font-smoothing: grayscale; -} - body { overflow-y: scroll; } - body.noscroll { overflow: hidden; } - - #clean { - background: transparent !important; - } - -* { - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - box-sizing: border-box; -} - -pre { - white-space: pre-wrap; - word-wrap: break-word; -} - -input, textarea { - font-size: 1em; - font-family: "Helvetica Neue", Helvetica, Arial, Geneva, sans-serif; -} -input:focus, textarea:focus { - outline: none; -} - -::-moz-placeholder { - color: rgba(255, 255, 255, 0.5); - font-style: italic; -} -input:-moz-placeholder { - color: rgba(255, 255, 255, 0.5); - font-style: italic; -} -::-webkit-input-placeholder { - color: rgba(255, 255, 255, 0.5); - font-style: italic; -} -:-ms-input-placeholder { - color: rgba(255, 255, 255, 0.5) !important; - font-style: italic; -} - -.tiny_scroll { - overflow: hidden; -} - - .tiny_scroll:hover { - overflow-y: auto; - } - - .tiny_scroll::-webkit-scrollbar { - width: 5px; - } - - .tiny_scroll::-webkit-scrollbar-track { - -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.4); - -webkit-border-radius: 5px; - border-radius: 5px; - } - - .tiny_scroll::-webkit-scrollbar-thumb { - -webkit-border-radius: 5px; - border-radius: 5px; - background: rgba(255,255,255,0.3); - } - -a img { - border:none; -} - -a { - text-decoration:none; - color: #ebfcbc; - outline: 0; - cursor: pointer; - font-weight: bold; -} -a:hover { color: #f3f3f3; } - -.page { - display: none; - width: 100%; - max-width: 980px; - margin: 0 auto; - line-height: 1.5em; - padding: 0 15px 20px; -} - .page.active { display: block; } - -.content { - clear:both; - padding: 65px 0 10px; - background: #4e5969; -} - - @media all and (max-width: 480px) { - .content { - padding-top: 40px; - } - } - -h2 { - font-size: 2.5em; - padding: 0; - margin: 20px 0 0 0; -} - -form { - padding:0; - margin:0; -} - -body > .spinner, .mask{ - background: rgba(0,0,0, 0.9); - z-index: 100; - text-align: center; -} - body > .mask { - position: fixed; - top: 0; - left: 0; - height: 100%; - width: 100%; - padding: 200px; - } - - @media all and (max-width: 480px) { - body > .mask { - padding: 20px; - } - } - -.button { - background: #5082bc; - padding: 5px 10px 6px; - color: #fff; - text-decoration: none; - font-weight: bold; - line-height: 1; - border-radius: 2px; - cursor: pointer; - border: none; - -webkit-appearance: none; -} - .button.red { background-color: #ff0000; } - .button.green { background-color: #2aa300; } - .button.orange { background-color: #ffa200; } - .button.yellow { background-color: #ffe400; } - -/*** Icons ***/ -.icon { - display: inline-block; - background: center no-repeat; -} -.icon.delete { background-image: url('../images/icon.delete.png'); } -.icon.download { background-image: url('../images/icon.download.png'); } -.icon.edit { background-image: url('../images/icon.edit.png'); } -.icon.completed { background-image: url('../images/icon.check.png'); } -.icon.folder { background-image: url('../images/icon.folder.gif'); } -.icon.imdb { background-image: url('../images/icon.imdb.png'); } -.icon.refresh { background-image: url('../images/icon.refresh.png'); } -.icon.readd { background-image: url('../images/icon.readd.png'); } -.icon.rating { background-image: url('../images/icon.rating.png'); } -.icon.files { background-image: url('../images/icon.files.png'); } -.icon.info { background-image: url('../images/icon.info.png'); } -.icon.trailer { background-image: url('../images/icon.trailer.png'); } -.icon.spinner { background-image: url('../images/icon.spinner.gif'); } -.icon.attention { background-image: url('../images/icon.attention.png'); } - -.icon2 { - display: inline-block; - background: center no-repeat; - font-family: 'Elusive-Icons'; - speak: none; - font-style: normal; - font-weight: normal; - font-variant: normal; - text-transform: none; - line-height: 1; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - font-size: 15px; - color: #FFF; -} - -.icon2.add:before { content: "\e05a"; color: #c2fac5; } -.icon2.cog:before { content: "\e109"; } -.icon2.eye-open:before { content: "\e09d"; } -.icon2.search:before { content: "\e03e"; } -.icon2.return-key:before { content: "\e111"; } -.icon2.close:before { content: "\e04e"; } -.icon2.trailer:before { content: "\e0e9"; } -.icon2.download:before { content: "\e0c3"; } -.icon2.edit:before { content: "\e068"; } -.icon2.refresh:before { content: "\e04f"; font-weight: bold; } -.icon2.delete:before { content: "\e04e"; } -.icon2.directory:before { content: "\e097"; } -.icon2.completed:before { content: "\e070"; } -.icon2.info:before { content: "\e089"; } -.icon2.attention:before { content: "\e009"; } -.icon2.readd:before { - display: inline-block; - content: "\e04b"; - transform: scale(-1, 1); -} -.icon2.imdb:before { - content: "IMDb"; - color: #444; - padding: 2px; - border-radius: 3px; - background-color: #fbec98; - font: bold 8px Arial; - position: relative; - top: -3px; -} -.icon2.menu:before { - content: "\e076\00a0 \e076\00a0 \e076\00a0"; - line-height: 6px; - transform: scaleX(2); - width: 20px; - font-size: 10px; - display: inline-block; - vertical-align: middle; - word-wrap: break-word; - text-align:center; - margin-left: 5px; -} - @media screen and (-webkit-min-device-pixel-ratio:0) { - .icon2.menu:before { - margin-top: -7px; - } - } - -/*** Navigation ***/ -.header { - height: 66px; - position: fixed; - margin: 0; - width: 100%; - z-index: 5; - background: #5c697b; - box-shadow: 0 0 10px rgba(0,0,0,.1); - transition: all .4s ease-in-out; -} - - @media all and (max-width: 480px) { - .header { - height: 44px; - } - } - -.header > div { - width: 100%; - max-width: 980px; - margin: 0 auto; - position: relative; - height: 100%; - padding: 0 15px; -} - - .header .navigation { - display: inline-block; - vertical-align: middle; - position: absolute; - height: 100%; - left: 0; - bottom: 0; - } - - .header .foldout { - width: 44px; - height: 100%; - text-align: center; - border-right: 1px solid rgba(255,255,255,.07); - display: none; - vertical-align: top; - line-height: 42px; - color: #FFF; - } - - .header .logo { - display: inline-block; - font-size: 3em; - padding: 4px 30px 0 15px; - height: 100%; - border-right: 1px solid rgba(255,255,255,.07); - color: #FFF; - font-weight: normal; - vertical-align: top; - font-family: Lobster, sans-serif; - } - - @media all and (max-width: 480px) { - .header .foldout { - display: inline-block; - } - - .header .logo { - padding-top: 7px; - border: 0; - font-size: 1.7em; - } - } - - @media all and (min-width: 481px) and (max-width: 640px) { - - .header .logo { - display: none; - } - - } - - .header .navigation ul { - display: inline-block; - margin: 0; - padding: 0; - height: 100%; - } - - .header .navigation li { - color: #fff; - display: inline-block; - font-size: 1.75em; - margin: 0; - text-align: center; - height: 100%; - border: 1px solid rgba(255,255,255,.07); - border-width: 0 0 0 1px; - } - .header .navigation li:first-child { - border: none; - } - - .header .navigation li a { - display: block; - padding: 15px; - position: relative; - height: 100%; - border: 1px solid transparent; - border-width: 0 0 4px 0; - font-weight: normal; - } - - .header .navigation li:hover a { border-color: #047792; } - .header .navigation li.active a { border-color: #04bce6; } - - .header .navigation li.disabled { color: #e5e5e5; } - .header .navigation li a { color: #fff; } - - .header .navigation .backtotop { - opacity: 0; - display: block; - width: 80px; - left: 50%; - position: fixed; - bottom: 0; - text-align: center; - margin: -10px 0 0 -40px; - background: #4e5969; - padding: 5px 0; - color: rgba(255,255,255,.4); - font-weight: normal; - } - .header:hover .navigation .backtotop { color: #fff; } - - @media all and (max-width: 480px) { - - body { - position: absolute; - width: 100%; - transition: all .5s cubic-bezier(0.9,0,0.1,1); - left: 0; - } - - .menu_shown body { - left: 240px; - } - - .header .navigation { - height: 100%; - } - - .menu_shown .header .navigation .overlay { - position: fixed; - right: 0; - top: 0; - bottom: 0; - left: 240px; - } - - .header .navigation ul { - width: 240px; - position: fixed; - left: -240px; - background: rgba(0,0,0,.5); - transition: all .5s cubic-bezier(0.9,0,0.1,1); - } - - .menu_shown .header .navigation ul { - left: 0; - } - - .header .navigation ul li { - display: block; - text-align: left; - border-width: 1px 0 0 0; - height: 44px; - } - .header .navigation ul li a { - border-width: 0 4px 0 0; - padding: 5px 20px; - } - - .header .navigation ul li.separator { - background-color: rgba(255,255,255, .07); - height: 5px; - } - } - - .header .more_menu { - position: absolute; - right: 15px; - height: 100%; - border-left: 1px solid rgba(255,255,255,.07); - } - - @media all and (max-width: 480px) { - .header .more_menu { - display: none; - } - } - - .header .more_menu .button { - height: 100%; - line-height: 66px; - text-align: center; - padding: 0; - } - - .header .more_menu .wrapper { - width: 200px; - margin-left: -106px; - margin-top: 22px; - } - - @media all and (max-width: 480px) { - .header .more_menu .button { - line-height: 44px; - } - - .header .more_menu .wrapper { - margin-top: 0; - } - } - - .header .more_menu .red { color: red; } - .header .more_menu .orange { color: orange; } - - .badge { - position: absolute; - width: 20px; - height: 20px; - text-align: center; - line-height: 20px; - margin: 0; - background-color: #1b79b8; - top: 0; - right: 0; - } - - .header .notification_menu { - right: 60px; - display: block; - } - - @media all and (max-width: 480px) { - .header .notification_menu { - right: 0; - } - } - - .header .notification_menu .wrapper { - width: 300px; - margin-left: -255px; - text-align: left; - } - - .header .notification_menu ul { - min-height: 60px; - max-height: 300px; - overflow: auto; - } - .header .notification_menu ul:empty:after { - content: 'No notifications (yet)'; - text-align: center; - width: 100%; - position: absolute; - padding: 18px 0; - font-size: 15px; - font-style: italic; - opacity: .4; - } - - .header .notification_menu li > span { - padding: 5px; - display: block; - border-bottom: 1px solid rgba(0,0,0,0.2); - word-wrap: break-word; - } - .header .notification_menu li > span { color: #777; } - .header .notification_menu li:last-child > span { border: 0; } - .header .notification_menu li .added { - display: block; - font-size: .85em; - color: #aaa; - } - - .header .notification_menu li .more { - text-align: center; - } - - .message.update { - text-align: center; - position: fixed; - padding: 10px; - background: #ff6134; - font-size: 15px; - bottom: 0; - left: 0; - width: 100%; - z-index: 19; - } - - .message.update a { - padding: 0 5px; - } - -/*** Global Styles ***/ -.check { - display: inline-block; - vertical-align: top; - margin-top: 4px; - height: 16px; - width: 16px; - cursor: pointer; - background: url('../images/sprite.png') no-repeat -200px; - border-radius: 3px; -} - .check.highlighted { background-color: #424c59; } - .check.checked { background-position: -2px 0; } - .check.indeterminate { background-position: -1px -119px; } - .check input { - display: none !important; - } - -.select { - cursor: pointer; - display: inline-block; - color: #fff; -} - - .select .selection { - display: inline-block; - padding: 0 30px 0 20px; - background: #5b9bd1 url('../images/sprite.png') no-repeat 94% -53px; - } - - .select .selection .selectionDisplay { - display: inline-block; - padding-right: 15px; - border-right: 1px solid rgba(0,0,0,0.2); - - box-shadow: 1px 0 0 rgba(255,255,255,0.15); - } - - .select .menu { - clear: both; - overflow: hidden; - font-weight: bold; - } - - .select .list { - display: none; - background: #282d34; - position: absolute; - margin: 25px 0 0 0; - box-shadow: 0 20px 20px -10px rgba(0,0,0,0.4); - z-index: 3; - } - .select.active .list { - display: block; - } - .select .list ul { - display: block; - width: 100% !important; - } - .select .list li { - padding: 0 33px 0 20px; - margin: 0 !important; - display: block; - border-top: 1px solid rgba(255,255,255,0.1); - white-space: nowrap; - } - .select .list li.highlighted { - background: rgba(255,255,255,0.1); - border-color: transparent; - } - - .select input { display: none; } - -.inlay { - color: #fff; - border: 0; - background-color: #282d34; - box-shadow: inset 0 1px 8px rgba(0,0,0,0.25); -} - - .inlay.light { - background-color: #47515f; - outline: none; - box-shadow: none; - } - - .inlay:focus { - background-color: #3a4350; - outline: none; - } - -.onlay, .inlay .selected, .inlay:not(.reversed) > li:hover, .inlay > li.active, .inlay.reversed > li { - border-radius:3px; - border: 1px solid #252930; - box-shadow: inset 0 1px 0 rgba(255,255,255,0.20); - background: rgb(55,62,74); - background-image: linear-gradient( - 0, - rgb(55,62,74) 0%, - rgb(73,83,98) 100% - ); -} -.onlay:active, .inlay.reversed > li:active { - color: #fff; - border: 1px solid transparent; - background-color: #282d34; - box-shadow: inset 0 1px 8px rgba(0,0,0,0.25); -} - -.question { - display: block; - width: 600px; - padding: 20px; - position:fixed; - z-index: 101; - text-align: center; -} - - .question h3 { - font-size: 25px; - padding: 0; - margin: 0 0 20px; - } - - .question .hint { - font-size: 14px; - color: #ccc; - } - - .question .answer { - font-size: 17px; - display: inline-block; - padding: 10px; - margin: 5px 1%; - cursor: pointer; - width: auto; - } - .question .answer:hover { - background: #000; - } - - .question .answer.delete { - background-color: #a82f12; - } - .question .answer.cancel { - margin-top: 20px; - background-color: #4c5766; - } - -.more_menu { - display: inline-block; - vertical-align: middle; - overflow: visible; -} - - .more_menu > a { - display: block; - height: 44px; - width: 44px; - transition: all 0.3s ease-in-out; - border-bottom: 4px solid transparent; - border-radius: 0; - } - - .more_menu .button:hover { - border-color: #047792; - } - - .more_menu.show .button { - border-color: #04bce6; - } - - .more_menu .wrapper { - display: none; - top: 0; - right: 0; - margin: 11px 0 0 0; - position: absolute; - z-index: 90; - width: 185px; - box-shadow: 0 20px 20px -10px rgba(0,0,0,0.55); - color: #444; - background: #fff; - } - - .more_menu.show .wrapper { - display: block; - top: 44px; - } - - .more_menu ul { - padding: 0; - margin: 0; - list-style: none; - } - - .more_menu .wrapper li { - width: 100%; - height: auto; - } - - .more_menu .wrapper li a { - display: block; - border-bottom: 1px solid rgba(255,255,255,0.2); - box-shadow: none; - font-weight: normal; - font-size: 1.2em; - letter-spacing: 1px; - padding: 2px 10px; - color: #444; - } - .more_menu .wrapper li:first-child a { padding-top: 5px; } - .more_menu .wrapper li:last-child a { padding-bottom: 5px; } - - .more_menu .wrapper li .separator { - border-bottom: 1px solid rgba(0,0,0,.1); - display: block; - height: 1px; - margin: 5px 0; - } - - .more_menu .wrapper li:last-child a { - border: none; - } - .more_menu .wrapper li a:hover { - background: rgba(0,0,0,0.05); - } - -.messages { - position: fixed; - right: 0; - bottom: 0; - width: 320px; - z-index: 20; - overflow: hidden; - font-size: 14px; - font-weight: bold; -} - @media all and (max-width: 480px) { - .messages { - width: 100%; - } - } - - .messages .message { - overflow: hidden; - transition: all .6s cubic-bezier(0.9,0,0.1,1); - background: #5b9bd1; - width: 100%; - position: relative; - margin: 1px 0 0; - max-height: 0; - padding: 0 30px 0 20px; - font-size: 1.1em; - font-weight: normal; - transform: scale(0); - } - .messages .message.sticky { - background-color: #c84040; - } - .messages .message.show { - max-height: 100px; - padding: 15px 30px 15px 20px; - transform: scale(1); - } - .messages .message.hide { - max-height: 0; - padding: 0 20px; - margin: 0; - transform: scale(0); - } - .messages .close { - position: absolute; - padding: 10px 8px; - top: 0; - right: 0; - color: #FFF; - } - -/*** Login ***/ -.page.login { - display: block; -} - - .login h1 { - padding: 0 0 10px; - font-size: 60px; - font-family: Lobster; - font-weight: normal; - } - - .login form { - padding: 0; - height: 300px; - width: 400px; - position: fixed; - left: 50%; - top: 50%; - margin: -200px 0 0 -200px; - } - @media all and (max-width: 480px) { - - .login form { - padding: 0; - height: 300px; - width: 90%; - position: absolute; - left: 5%; - top: 10px; - margin: 0; - } - - } - - .page.login .ctrlHolder { - padding: 0; - margin: 0 0 20px; - } - .page.login .ctrlHolder:hover { - background: none; - } - - .page.login input[type=text], - .page.login input[type=password] { - width: 100% !important; - font-size: 25px; - padding: 14px !important; - } - - .page.login .remember_me { - font-size: 15px; - float: left; - width: 150px; - padding: 20px 0; - } - - .page.login .remember_me .check { - margin: 5px 5px 0 0; - } - - .page.login .button { - font-size: 25px; - padding: 20px; - float: right; - } - -/* Fonts */ -@font-face { - font-family: 'Elusive-Icons'; - src:url('../fonts/Elusive-Icons.eot'); - src:url('../fonts/Elusive-Icons.eot?#iefix') format('embedded-opentype'), - url('../fonts/Elusive-Icons.woff') format('woff'), - url('../fonts/Elusive-Icons.ttf') format('truetype'), - url('../fonts/Elusive-Icons.svg#Elusive-Icons') format('svg'); - font-weight: normal; - font-style: normal; -} - -@font-face { - font-family: 'OpenSans'; - src: url('../fonts/OpenSans-Regular-webfont.eot'); - src: url('../fonts/OpenSans-Regular-webfont.eot?#iefix') format('embedded-opentype'), - url('../fonts/OpenSans-Regular-webfont.woff') format('woff'), - url('../fonts/OpenSans-Regular-webfont.ttf') format('truetype'), - url('../fonts/OpenSans-Regular-webfont.svg#OpenSansRegular') format('svg'); - font-weight: normal; - font-style: normal; - -} - -@font-face { - font-family: 'OpenSans'; - src: url('../fonts/OpenSans-Italic-webfont.eot'); - src: url('../fonts/OpenSans-Italic-webfont.eot?#iefix') format('embedded-opentype'), - url('../fonts/OpenSans-Italic-webfont.woff') format('woff'), - url('../fonts/OpenSans-Italic-webfont.ttf') format('truetype'), - url('../fonts/OpenSans-Italic-webfont.svg#OpenSansItalic') format('svg'); - font-weight: normal; - font-style: italic; - -} - -@font-face { - font-family: 'OpenSans'; - src: url('../fonts/OpenSans-Bold-webfont.eot'); - src: url('../fonts/OpenSans-Bold-webfont.eot?#iefix') format('embedded-opentype'), - url('../fonts/OpenSans-Bold-webfont.woff') format('woff'), - url('../fonts/OpenSans-Bold-webfont.ttf') format('truetype'), - url('../fonts/OpenSans-Bold-webfont.svg#OpenSansBold') format('svg'); - font-weight: bold; - font-style: normal; - -} - -@font-face { - font-family: 'OpenSans'; - src: url('../fonts/OpenSans-BoldItalic-webfont.eot'); - src: url('../fonts/OpenSans-BoldItalic-webfont.eot?#iefix') format('embedded-opentype'), - url('../fonts/OpenSans-BoldItalic-webfont.woff') format('woff'), - url('../fonts/OpenSans-BoldItalic-webfont.ttf') format('truetype'), - url('../fonts/OpenSans-BoldItalic-webfont.svg#OpenSansBoldItalic') format('svg'); - font-weight: bold; - font-style: italic; -} - -@font-face { - font-family: 'Lobster'; - src: url('../fonts/Lobster-webfont.eot'); - src: url('../fonts/Lobster-webfont.eot?#iefix') format('embedded-opentype'), - url('../fonts/Lobster-webfont.woff') format('woff'), - url('../fonts/Lobster-webfont.ttf') format('truetype'), - url('../fonts/Lobster-webfont.svg#lobster_1.4regular') format('svg'); - font-weight: normal; - font-style: normal; -} diff --git a/couchpotato/static/style/settings.css b/couchpotato/static/style/settings.css deleted file mode 100644 index 39013b2..0000000 --- a/couchpotato/static/style/settings.css +++ /dev/null @@ -1,820 +0,0 @@ -.page.settings { - min-width: 960px; -} - -.page.settings:after { - content: ""; - display: block; - clear: both; - visibility: hidden; - line-height: 0; - height: 0; -} - - .page.settings .tabs { - float: left; - width: 14.7%; - font-size: 17px; - text-align: right; - list-style: none; - padding: 35px 0; - margin: 0; - min-height: 470px; - background-image: linear-gradient( - 76deg, - rgba(0,0,0,0) 50%, - rgba(0,0,0,0.3) 100% - ); - } - .page.settings .tabs a { - display: block; - padding: 7px 15px; - font-weight: normal; - transition: all 0.3s ease-in-out; - color: rgba(255, 255, 255, 0.8); - text-shadow: none; - } - .page.settings .tabs a:hover, - .page.settings .tabs .active a { - background: rgb(78, 89, 105); - color: #fff; - } - .page.settings .tabs > li { - border-bottom: 1px solid rgb(78, 89, 105); - } - - .page.settings .tabs .subtabs { - list-style: none; - padding: 0; - margin: -5px 0 10px; - } - - .page.settings .tabs .subtabs a { - font-size: 13px; - padding: 0 15px; - font-weight: normal; - transition: all .3s ease-in-out; - color: rgba(255, 255, 255, 0.7); - } - - .page.settings .tabs .subtabs .active a { - color: #fff; - background: rgb(78, 89, 105); - } - - - .page.settings .containers { - width: 84%; - float: left; - padding: 40px 0 40px 2%; - min-height: 300px; - } - - .page .advanced { - display: none; - color: #edc07f; - } - .page.show_advanced .advanced { display: block; } - .page.show_advanced span.advanced, - .page.show_advanced input.advanced { display: inline; } - - .page.settings .tab_content { - display: none; - } - .page.settings .tab_content.active { display: block; } - - .page fieldset { - padding: 10px 0; - } - .page fieldset h2 { - font-weight: normal; - font-size: 25px; - padding: 0 9px 10px 30px; - margin: 0; - border-bottom: 1px solid #333; - box-shadow: 0 1px 0 rgba(255,255,255, 0.15); - } - - .page fieldset h2 .icon { - vertical-align: bottom; - position: absolute; - left: -25px; - top: 3px; - background: #FFF; - border-radius: 2.5px; - line-height: 0; - overflow: hidden; - } - - .page fieldset.enabler:hover h2 .icon { - display: none; - } - - .page fieldset h2 .hint { - font-size: 12px; - margin-left: 10px; - } - .page fieldset h2 .hint a { - margin: 0 !important; - padding: 0; - } - - .page fieldset.disabled .ctrlHolder { - display: none; - } - .page fieldset > .ctrlHolder:first-child { - display: block; - padding: 0; - position: relative; - margin: 0 0 -23px; - border: none; - width: 20px; - } - - .Scan_folder { padding: 0 !important; } - - .page .ctrlHolder { - line-height: 25px; - padding: 10px 10px 10px 30px; - font-size: 14px; - border: 0; - } - .page .ctrlHolder.save_success:not(:first-child) { - background: url('../images/icon.check.png') no-repeat 7px center; - } - .page .ctrlHolder:last-child { border: none; } - .page .ctrlHolder:hover { background-color: rgba(255,255,255,0.05); } - .page .ctrlHolder.focused { background-color: rgba(255,255,255,0.2); } - .page .ctrlHolder.focused:first-child, .page .ctrlHolder:first-child{ background-color: transparent; } - - .page .ctrlHolder .formHint { - width: 46%; - margin: -18px 0; - color: #fff !important; - display: inline-block; - vertical-align: middle; - padding: 0 0 0 2%; - line-height: 14px; - } - - .page .check { - margin-top: 6px; - } - - .page .check + .formHint { - float: none; - width: auto; - display: inline-block; - padding-left: 1% !important; - height: 24px; - vertical-align: middle; - line-height: 24px; - } - - .page .ctrlHolder label { - font-weight: bold; - width: 20%; - margin: 0; - padding: 6px 0 0; - } - - .page .xsmall { width: 25px !important; text-align: center; } - - .page .enabler { - display: block; - } - - .page .option_list { - margin-bottom: 20px; - } - - .page .option_list .check { - margin-top: 5px; - } - - .page .option_list .enabler { - padding: 0; - margin-left: 5px !important; - } - - .page .option_list .enabler:not(.disabled) { - margin: 0 0 0 30px; - } - - .page .option_list .enabler:not(.disabled) .ctrlHolder:first-child { - margin: 10px 0 -33px 0; - } - - .page .option_list h3 { - padding: 0; - margin: 10px 5px 0; - text-align: center; - font-weight: normal; - text-shadow: none; - text-transform: uppercase; - font-size: 12px; - background: rgba(255,255,255,0.03); - } - - .page .option_list .enabler.disabled { - display: inline-block; - padding: 4px 0 5px; - width: 24%; - vertical-align: top; - } - .page .option_list .enabler:not(.disabled) .icon { - display: none; - } - - .page .option_list .enabler.disabled h2 { - cursor: pointer; - border: none; - box-shadow: none; - padding: 0 10px 0 0; - font-size: 16px; - position: relative; - left: 25px; - margin-right: 25px; - } - - .page .option_list .enabler:not(.disabled) h2 { - font-size: 16px; - font-weight: bold; - border: none; - border-top: 1px solid rgba(255,255,255, 0.15); - box-shadow: 0 -1px 0 #333; - margin: 0; - padding: 10px 0 5px 25px; - } - .page .option_list .enabler:not(.disabled):first-child h2 { - border: none; - box-shadow: none; - } - - .page .option_list .enabler.disabled h2 .hint { - display: none; - } - .page .option_list .enabler h2 .hint { - font-weight: normal; - } - - .page input[type=text], .page input[type=password] { - padding: 5px 3px; - margin: 0; - width: 30%; - } - .page .input.xsmall { width: 5% } - .page .input.small { width: 10% } - .page .input.medium { width: 15% } - .page .input.large { width: 25% } - .page .input.xlarge { width: 30% } - - .page .advanced_toggle { - clear: both; - display: block; - text-align: right; - height: 20px; - margin: 0 0 -38px; - } - .page .advanced_toggle .check { - margin: 0; - } - .page .advanced_toggle span { padding: 0 5px; } - .page.show_advanced .advanced_toggle { - color: #edc07f; - } - - .page form .directory { - display: inline-block; - padding: 0 4% 0 4px; - font-size: 13px; - width: 30%; - overflow: hidden; - vertical-align: top; - position: relative; - } - .page form .directory:after { - content: "\e097"; - position: absolute; - right: 7px; - top: 2px; - font-family: 'Elusive-Icons'; - color: #f5e39c; - } - .page form .directory > input { - height: 25px; - display: inline-block; - float: right; - text-align: right; - white-space: nowrap; - cursor: pointer; - background: none; - border: 0; - color: #FFF; - width: 100%; - } - .page form .directory input:empty:before { - content: 'No folder selected'; - font-style: italic; - opacity: .3; - } - - .page .directory_list { - z-index: 2; - position: absolute; - width: 450px; - margin: 28px 0 20px 18.4%; - background: #5c697b; - box-shadow: 0 20px 40px -20px rgba(0,0,0,0.55); - } - - .page .directory_list .pointer { - border-right: 6px solid transparent; - border-left: 6px solid transparent; - border-bottom: 6px solid #5c697b; - display: block; - position: absolute; - width: 0; - margin: -6px 0 0 45%; - } - - .page .directory_list ul { - width: 92%; - height: 300px; - overflow: auto; - margin: 0 4%; - font-size: 16px; - } - - .page .directory_list li { - padding: 4px 30px 4px 10px; - cursor: pointer; - margin: 0 !important; - border-top: 1px solid rgba(255,255,255,0.1); - background: url('../images/right.arrow.png') no-repeat 98% center; - overflow: hidden; - white-space: nowrap; - text-overflow: ellipsis; - } - - .page .directory_list li.blur { - opacity: .3; - } - - .page .directory_list li:last-child { - border-bottom: 1px solid rgba(255,255,255,0.1); - } - - .page .directory_list li:hover { - background-color: #515c68; - } - - .page .directory_list li.empty { - background: none; - height: 100px; - text-align: center; - font-style: italic; - border: none; - line-height: 100px; - cursor: default; - color: #BBB; - text-shadow: none; - font-size: 12px; - } - - .page .directory_list .actions { - clear: both; - padding: 4% 4% 2%; - min-height: 45px; - position: relative; - width: 100%; - text-align: right; - } - - .page .directory_list .actions label { - float: right; - width: auto; - padding: 0; - } - .page .directory_list .actions .inlay { - margin: 0 0 0 7px; - } - - .page .directory_list .actions .back { - font-weight: bold; - width: 160px; - display: inline-block; - padding: 0; - line-height: 120%; - vertical-align: top; - position: absolute; - text-align: left; - left: 4%; - } - - .page .directory_list .actions:last-child { - float: right; - padding: 4%; - } - - .page .directory_list .actions:last-child > span { - padding: 0 5px; - text-shadow: none; - } - - .page .directory_list .actions:last-child > .clear { - left: 4%; - position: absolute; - background-color: #af3128; - } - - .page .directory_list .actions:last-child > .cancel { - font-weight: bold; - color: #ddd; - } - - .page .directory_list .actions:last-child > .save { - background: #9dc156; - } - - - .page .multi_directory.is_empty .delete { - visibility: hidden; - } - - .page .multi_directory .delete { - display: none; - } - .page .multi_directory:hover .delete { - display: inline-block; - width: 22px; - height: 24px; - vertical-align: top; - background-position: center; - margin-left: 5px; - } - - - .page .tag_input select { - width: 20%; - display: inline-block; - } - - .page .tag_input .selection { - border-radius: 0 10px 10px 0; - height: 26px; - } - - .page .tag_input > input { - display: none; - } - - .page .tag_input > ul { - list-style: none; - border-radius: 3px; - cursor: text; - width: 30%; - margin: 0 !important; - min-height: 27px; - line-height: 0; - display: inline-block; - } - .page .tag_input:hover > ul { - border-radius: 3px 0 0 3px; - } - .page .tag_input:hover .formHint { display: none; } - - .page .tag_input > ul > li { - display: inline-block; - min-height: 20px; - min-width: 2px; - font-size: 12px; - padding: 0; - margin: 4px 0 0 !important; - border-width: 0; - background: 0; - line-height: 20px; - } - .page .tag_input > ul > li:first-child { min-width: 4px; } - .page .tag_input li.choice { - cursor: -moz-grab; - cursor: -webkit-grab; - cursor: grab; - padding: 0; - border-radius: 2px; - } - .page .tag_input > ul:hover > li.choice { - background: linear-gradient( - 180deg, - rgba(255,255,255,0.3) 0%, - rgba(255,255,255,0.1) 100% - ); - } - .page .tag_input > ul > li.choice:hover, - .page .tag_input > ul > li.choice.selected { - background: linear-gradient( - 180deg, - #5b9bd1 0%, - #406db8 100% - ); - } - - .page .tag_input .select { - display: none; - } - .page .tag_input:hover .select { display: inline-block; } - - .page .tag_input li input { - background: 0; - border: 0; - color: #fff; - outline-width: 0; - padding: 0; - min-width: 2px; - } - .page .tag_input li:first-child input { - padding-left: 2px; - min-width: 0; - } - - .page .tag_input li:not(.choice) span { - white-space: pre; - position: absolute; - top: -9999px; - } - - .page .tag_input .delete { - display: none; - height: 10px; - width: 16px; - position: absolute; - margin: -9px 0 0 -16px; - border-radius: 30px 30px 0 0; - cursor: pointer; - background: url('../images/icon.delete.png') no-repeat center 2px, linear-gradient( - 180deg, - #5b9bd1 0%, - #5b9bd1 100% - ); - background-size: 65%; - } - .page .tag_input .choice:hover .delete, - .page .tag_input .choice.selected .delete { display: inline-block; } - .page .tag_input .choice .delete:hover { - height: 14px; - margin-top: -13px; - } - - .page .combined_table .head { - margin: 0 0 0 60px; - } - .page .disabled .head { display: none; } - .page .combined_table .head abbr { - display: inline-block; - font-weight: bold; - border-bottom: 1px dotted #fff; - line-height: 140%; - cursor: help; - } - .page .combined_table .head abbr:first-child { - display: none; - } - .page .combined_table .head abbr.host { margin-right: 120px; } - .page .combined_table input.host { width: 140px; } - .page .section_newznab .combined_table .head abbr.host { margin-right: 180px; } - .page .section_newznab .combined_table input.host { width: 200px; } - - .page .combined_table .head abbr.name { margin-right: 57px; } - .page .combined_table input.name { width: 120px; } - .page .combined_table .head abbr.api_key { margin-right: 75px; } - - .page .combined_table .head abbr.pass_key { margin-right: 71px; } - .page .combined_table input.pass_key { width: 113px; } - - .page .section_newznab .combined_table .head abbr.api_key { margin-right: 170px; } - .page .section_newznab .combined_table input.api_key { width: 203px; } - - .page .combined_table .head abbr.extra_score { - margin-right: 15px; - display: none; - } - .page .combined_table input.extra_score { - width: 75px; - display: none; - } - .page.show_advanced .combined_table .head .extra_score, - .page.show_advanced .combined_table .extra_score { - display: inline-block; - } - - .page .combined_table .head abbr.custom_tag { - margin-right: 15px; - display: none; - } - .page .combined_table input.custom_tag { - width: 140px; - display: none; - } - .page.show_advanced .combined_table .head .custom_tag, - .page.show_advanced .combined_table .custom_tag { - display: inline-block; - } - - - .page .combined_table .seed_ratio, - .page .combined_table .seed_time { - width: 70px; - text-align: center; - margin-left: 10px; - } - .page .combined_table .seed_time { - margin-right: 10px; - } - - .page .combined_table .ctrlHolder { - padding-top: 2px; - padding-bottom: 3px; - } - .page .combined_table .ctrlHolder.hide { display: none; } - - .page .combined_table .ctrlHolder > * { - margin: 0 10px 0 0; - } - .page .combined_table .ctrlHolder > .check { - margin-top: 6px; - } - - .page .combined_table .ctrlHolder .delete { - display: none; - width: 22px; - height: 22px; - line-height: 22px; - text-align: center; - vertical-align: middle; - color: #fe3d3d; - } - .page .combined_table .ctrlHolder:hover .delete { - display: inline-block; - } - - .page .combined_table .ctrlHolder.is_empty .delete, - .page.settings .combined_table .ctrlHolder.is_empty .check { - visibility: hidden; -} - - .page .tab_about .usenet { - padding: 20px 30px 0; - font-size: 1.5em; - line-height: 1.3em; - } - - .page .tab_about .usenet a { - padding: 0 5px; - } - - .page .tab_about .usenet ul { - float: left; - width: 50%; - margin: 10px 0; - padding: 0; - } - - .page .tab_about .usenet li { - background: url('../images/icon.check.png') no-repeat left center; - padding: 0 0 0 25px; - } - - .page .tab_about .donate { - float: left; - width: 42%; - text-align: center; - font-size: 17px; - padding: 0 0 0 4%; - margin: 20px 0 0; - border-left: 1px solid #333; - box-shadow: -1px 0 0 rgba(255,255,255, 0.15); - } - .page .tab_about .donate form { - padding: 10px 0 0; - } - - .page .tab_about .info { - padding: 20px 30px; - margin: 0; - overflow: hidden; - } - - .page .tab_about .info dt { - clear: both; - float: left; - width: 17%; - font-weight: bold; - } - - .page .tab_about .info dd { - float: right; - width: 80%; - padding: 0; - margin: 0; - font-style: italic; - } - .page .tab_about .info dd.version { cursor: pointer; } - - .page .tab_about .group_actions > div { - padding: 30px; - text-align: center; - } - - .page .tab_about .group_actions a { - margin: 0 10px; - font-size: 20px; - } - -.group_userscript { - background: 5px 75px no-repeat; - min-height: 460px; - font-size: 20px; - font-weight: normal; -} - - .settings .group_userscript { - background-position: center 120px; - background-size: auto 70%; - min-height: 360px; - } - - .group_userscript h2 .hint { - display: block; - margin: 0 !important; - } - - .group_userscript .userscript { - float: left; - margin: 14px 0 0 25px; - height: 36px; - line-height: 25px; - } - - .group_userscript .or { - float: left; - margin: 20px -10px 0 10px; - } - - .group_userscript .bookmarklet { - display: block; - float: left; - padding: 20px 15px 0 25px; - border-radius: 5px; - } - - .group_userscript .bookmarklet span { - margin-left: 10px; - display: inline-block; - } - -.active .group_imdb_automation:not(.disabled) { - background: url('../images/imdb_watchlist.png') no-repeat right 50px; - min-height: 210px; -} - - -.tooltip { - position: absolute; - right: 0; - width: 30px; - height: 30px; -} - - .tooltip > a { - opacity: .3; - font-size: 11px; - cursor: pointer; - } - - .tooltip:hover > a { - opacity: 1; - } - - .tooltip div { - background: #FFF; - color: #000; - padding: 10px; - width: 380px; - z-index: 200; - position: absolute; - transition: all .4s cubic-bezier(0.9,0,0.1,1); - margin-top: 40px; - right: 0; - opacity: 0; - visibility: hidden; - } - - .tooltip.shown div { - margin-top: 10px; - opacity: 1; - visibility: visible; - } - - .tooltip div a { - color: #5b9bd1; - } From 414f63b4b37413c9bb69221e9fe60d1dd7aa981f Mon Sep 17 00:00:00 2001 From: Ruud Date: Tue, 25 Aug 2015 12:35:44 +0200 Subject: [PATCH 298/301] Responsive movie details header --- .../core/media/movie/_base/static/movie.scss | 28 +++++++++++++++++----- couchpotato/static/style/combined.min.css | 25 ++++++++++--------- 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/couchpotato/core/media/movie/_base/static/movie.scss b/couchpotato/core/media/movie/_base/static/movie.scss index 1ba67ae..9abb86c 100644 --- a/couchpotato/core/media/movie/_base/static/movie.scss +++ b/couchpotato/core/media/movie/_base/static/movie.scss @@ -581,14 +581,13 @@ $mass_edit_height: 44px; > .head { display: flex; - flex-flow: row nowrap; + flex-flow: row wrap; padding: 0 $padding; position: relative; z-index: 2; @include media-phablet { - flex-wrap: wrap; - padding: 0 $padding/2; + padding: 0; line-height: 1em; } @@ -603,11 +602,22 @@ $mass_edit_height: 44px; @include media-phablet { min-width: 100%; line-height: $header_width_mobile; + margin-top: $padding; .more_menu { width: 100%; } } + + .more_menu .icon-dropdown { + padding-right: $padding; + + @include media-phablet { + &:before { + right: $padding/2; + } + } + } } .more_menu { @@ -624,17 +634,18 @@ $mass_edit_height: 44px; line-height: $header_height; @include media-phablet { - line-height: 1em; + line-height: $header_width_mobile/2; } } .icon-dropdown { position: relative; - padding: 0 $padding 0 $padding/2; + padding: 0 $padding/1.5 0 $padding/2; &:before { position: absolute; right: 0; + top: -2px; opacity: .2; } @@ -708,9 +719,10 @@ $mass_edit_height: 44px; .buttons { display: flex; + flex-wrap: wrap; @include media-phablet { - margin-left: auto; + margin: 0; } > a { @@ -719,6 +731,10 @@ $mass_edit_height: 44px; color: $primary_color; line-height: $header_height; + @include media-phablet { + line-height: $header_width_mobile/2; + } + &:hover { color: #000; } diff --git a/couchpotato/static/style/combined.min.css b/couchpotato/static/style/combined.min.css index dcacccc..c407afd 100644 --- a/couchpotato/static/style/combined.min.css +++ b/couchpotato/static/style/combined.min.css @@ -205,22 +205,24 @@ .page.movie_details .overlay .close{width:44px} } .page.movie_details .scroll_content{position:fixed;z-index:2;top:0;bottom:0;right:0;left:176px;background:#FFF;border-radius:3px 0 0 3px;overflow-y:auto;-webkit-transform:translateX(100%) rotateY(360deg);transform:translateX(100%) rotateY(360deg);transition:-webkit-transform 350ms cubic-bezier(.9,0,.1,1);transition:transform 350ms cubic-bezier(.9,0,.1,1)} -.page.movie_details .scroll_content>.head{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row nowrap;-ms-flex-flow:row nowrap;flex-flow:row nowrap;padding:0 20px;position:relative;z-index:2} -@media (max-width:480px){.page.movie_details .scroll_content{left:44px} -.page.movie_details .scroll_content>.head{-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap;padding:0 10px;line-height:1em} -} +.page.movie_details .scroll_content>.head{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-flow:row wrap;-ms-flex-flow:row wrap;flex-flow:row wrap;padding:0 20px;position:relative;z-index:2} .page.movie_details .scroll_content>.head h1{-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto;margin:0;font-size:24px;color:rgba(0,0,0,.5);font-weight:300;max-width:100%} -@media (max-width:480px){.page.movie_details .scroll_content>.head h1{min-width:100%;line-height:44px} +@media (max-width:480px){.page.movie_details .scroll_content{left:44px} +.page.movie_details .scroll_content>.head{padding:0;line-height:1em} +.page.movie_details .scroll_content>.head h1{min-width:100%;line-height:44px;margin-top:20px} .page.movie_details .scroll_content>.head h1 .more_menu{width:100%} } +.page.movie_details .scroll_content>.head h1 .more_menu .icon-dropdown{padding-right:20px} .page.movie_details .scroll_content>.head .more_menu{display:inline-block;vertical-align:top;max-width:100%;margin-bottom:0} .page.movie_details .scroll_content>.head .more_menu>a{line-height:80px} -.page.movie_details .scroll_content>.head .more_menu .icon-dropdown{position:relative;padding:0 20px 0 10px} -.page.movie_details .scroll_content>.head .more_menu .icon-dropdown:before{position:absolute;right:0;opacity:.2} +@media (max-width:480px){.page.movie_details .scroll_content>.head h1 .more_menu .icon-dropdown:before{right:10px} +.page.movie_details .scroll_content>.head .more_menu>a{line-height:22px} +} +.page.movie_details .scroll_content>.head .more_menu .icon-dropdown{position:relative;padding:0 13.33px 0 10px} +.page.movie_details .scroll_content>.head .more_menu .icon-dropdown:before{position:absolute;right:0;top:-2px;opacity:.2} .page.movie_details .scroll_content>.head .more_menu .icon-dropdown:hover:before{opacity:1} .page.movie_details .scroll_content>.head .more_menu .wrapper{top:70px;padding-top:4px;border-radius:3px 3px 0 0;font-size:14px} -@media (max-width:480px){.page.movie_details .scroll_content>.head .more_menu>a{line-height:1em} -.page.movie_details .scroll_content>.head .more_menu .wrapper{top:25px} +@media (max-width:480px){.page.movie_details .scroll_content>.head .more_menu .wrapper{top:25px} } .page.movie_details .scroll_content>.head .more_menu .wrapper:before{top:0;left:auto;right:22px} .page.movie_details .scroll_content>.head .more_menu .wrapper ul{border-radius:3px 3px 0 0;max-height:215px;overflow-y:auto} @@ -230,12 +232,13 @@ .page.movie_details .scroll_content>.head .more_menu.title>a{display:inline-block;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;width:100%} .page.movie_details .scroll_content>.head .more_menu.title .wrapper{-webkit-transform-origin:0 0;transform-origin:0 0;left:0;right:auto} .page.movie_details .scroll_content>.head .more_menu.title .wrapper:before{left:22px;right:auto} -.page.movie_details .scroll_content>.head .buttons{display:-webkit-flex;display:-ms-flexbox;display:flex} +.page.movie_details .scroll_content>.head .buttons{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap} .page.movie_details .scroll_content>.head .buttons>a{display:inline-block;padding:0 10px;color:#ac0000;line-height:80px} .page.movie_details .scroll_content>.head .buttons>a:hover{color:#000} .page.movie_details .scroll_content .section{padding:20px;border-top:1px solid rgba(0,0,0,.1)} @media (max-width:480px){.page.movie_details .scroll_content>.head .more_menu.title .wrapper{top:30px;max-width:240px} -.page.movie_details .scroll_content>.head .buttons{margin-left:auto} +.page.movie_details .scroll_content>.head .buttons{margin:0} +.page.movie_details .scroll_content>.head .buttons>a{line-height:22px} .page.movie_details .scroll_content .section{padding:10px} } .page.movie_details .files span,.page.movie_details .releases .item span{white-space:nowrap;padding:6.67px 0;overflow:hidden;text-overflow:ellipsis} From b3c7155d62515fdbd607239395dbbc8061abe1e8 Mon Sep 17 00:00:00 2001 From: Ruud Date: Tue, 25 Aug 2015 12:57:13 +0200 Subject: [PATCH 299/301] Userscript styling --- .../core/plugins/userscript/static/userscript.scss | 78 ++++++++++++++++------ couchpotato/static/style/combined.min.css | 22 +++--- 2 files changed, 69 insertions(+), 31 deletions(-) diff --git a/couchpotato/core/plugins/userscript/static/userscript.scss b/couchpotato/core/plugins/userscript/static/userscript.scss index d816101..720a69f 100644 --- a/couchpotato/core/plugins/userscript/static/userscript.scss +++ b/couchpotato/core/plugins/userscript/static/userscript.scss @@ -1,3 +1,5 @@ +@import "_mixins"; + .page.userscript { position: absolute; width: 100%; @@ -6,33 +8,65 @@ left: 0; right: 0; padding: 0; -} - .page.userscript .frame.loading { + .frame.loading { text-align: center; font-size: 20px; padding: 20px; } - - .page.userscript .media_result { + + .media_result { height: 140px; + display: flex; } - .page.userscript .media_result .thumbnail { - width: 90px; - } - .page.userscript .media_result .options { - left: 90px; - padding: 54px 15px; - } - - .page.userscript .media_result .year { - display: none; - } - - .page.userscript .media_result .options select[name="title"] { - width: 190px; - } - - .page.userscript .media_result .options select[name="profile"] { - width: 70px; + + .thumbnail { + width: 90px; + } + + .options { + left: 90px; + display: flex; + align-items: center; + padding: $padding/2; + + > div { + display: flex; + flex-wrap: wrap; + + div { + flex: 1 auto; + margin: 0; + padding: 0 $padding/4; + } + + .title { + min-width: 100%; + margin-bottom: $padding; + } + + .add { + text-align: right; + + a { + display: block; + text-align: center; + } + } + + select { + width: 100%; + } } + } + + .message { + font-size: 1.5em; + } + + .year, + .data { + display: none; + } + +} diff --git a/couchpotato/static/style/combined.min.css b/couchpotato/static/style/combined.min.css index c407afd..51daefe 100644 --- a/couchpotato/static/style/combined.min.css +++ b/couchpotato/static/style/combined.min.css @@ -447,9 +447,8 @@ .profile .ctrlHolder.wait_for.wait_for input{min-width:0;width:40px;text-align:center;margin:0 2px} .profile .ctrlHolder.wait_for.wait_for .advanced{display:none;color:#ac0000} .show_advanced .profile .ctrlHolder.wait_for.wait_for .advanced{display:inline} -#profile_ordering li,.page.login{display:-webkit-flex;display:-ms-flexbox} #profile_ordering ul{list-style:none;margin:0;width:275px;padding:0} -#profile_ordering li{border-bottom:1px solid #eaeaea;padding:5px;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center} +#profile_ordering li{border-bottom:1px solid #eaeaea;padding:5px;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center} #profile_ordering li:hover{background:#eaeaea} #profile_ordering li:last-child{border:0} #profile_ordering li input[type=checkbox]{margin:2px 10px 0 0;vertical-align:top} @@ -459,14 +458,19 @@ #profile_ordering li .handle:hover{opacity:1} .group_sizes .item .label{min-width:150px} .group_sizes .item .max,.group_sizes .item .min{display:inline-block;width:70px!important;min-width:0!important;margin-right:10px;text-align:center} -.page.userscript .media_result .year,.page.wizard .navigation.navigation{display:none} .page.userscript{position:absolute;width:100%;top:0;bottom:0;left:0;right:0;padding:0} .page.userscript .frame.loading{text-align:center;font-size:20px;padding:20px} -.page.userscript .media_result{height:140px} -.page.userscript .media_result .thumbnail{width:90px} -.page.userscript .media_result .options{left:90px;padding:54px 15px} -.page.userscript .media_result .options select[name=title]{width:190px} -.page.userscript .media_result .options select[name=profile]{width:70px} +.page.userscript .media_result{height:140px;display:-webkit-flex;display:-ms-flexbox;display:flex} +.page.userscript .thumbnail{width:90px} +.page.userscript .options{left:90px;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-align-items:center;-ms-flex-align:center;align-items:center;padding:10px} +.page.userscript .options>div{display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-flex-wrap:wrap;-ms-flex-wrap:wrap;flex-wrap:wrap} +.page.userscript .options>div div{-webkit-flex:1 auto;-ms-flex:1 auto;flex:1 auto;margin:0;padding:0 5px} +.page.userscript .options>div .title{min-width:100%;margin-bottom:20px} +.page.userscript .options>div .add{text-align:right} +.page.userscript .options>div .add a{display:block;text-align:center} +.page.userscript .options>div select{width:100%} +.page.userscript .message{font-size:1.5em} +.page.userscript .data,.page.userscript .year,.page.wizard .navigation.navigation{display:none} .page.wizard .tab_content.tab_content{display:block} .page.wizard .tab_content.tab_content fieldset .ctrlHolder,.page.wizard .tab_content.tab_content fieldset h2{padding:5px} .page.wizard h1{padding:10px 0;display:block;font-size:30px;margin:80px 5px 0;font-weight:300} @@ -504,7 +508,7 @@ .api_docs .database table .form,.api_docs .database table form{width:600px} .api_docs .database table textarea{font-size:12px;width:100%;height:200px} .api_docs .database table input[type=submit]{display:block} -.page.login{background:#FFF;display:flex;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;font-size:1.25em} +.page.login{background:#FFF;display:-webkit-flex;display:-ms-flexbox;display:flex;-webkit-justify-content:center;-ms-flex-pack:center;justify-content:center;-webkit-align-items:center;-ms-flex-align:center;align-items:center;font-size:1.25em} .page.login h1{padding:0 0 10px;font-size:60px;font-family:Lobster;font-weight:400;color:#ac0000;text-align:center} .page.login form{padding:0;width:300px} .page.login .ctrlHolder{padding:0;margin:0 0 20px} From d19a6cfffecd1c796e156d5edd9a1be28d02bd22 Mon Sep 17 00:00:00 2001 From: Ruud Date: Tue, 25 Aug 2015 13:04:25 +0200 Subject: [PATCH 300/301] Userscript popup styling --- couchpotato/core/plugins/userscript/template.js_tmpl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/couchpotato/core/plugins/userscript/template.js_tmpl b/couchpotato/core/plugins/userscript/template.js_tmpl index 5a32da3..25e1842 100644 --- a/couchpotato/core/plugins/userscript/template.js_tmpl +++ b/couchpotato/core/plugins/userscript/template.js_tmpl @@ -67,13 +67,13 @@ var addStyle = function(css) { // Styles addStyle('\ - #cp_popup { font-family: "Helvetica Neue", Helvetica, Arial, Geneva, sans-serif; -moz-border-radius: 6px 0px 0px 6px; -webkit-border-radius: 6px 0px 0px 6px; border-radius: 6px 0px 0px 6px; -moz-box-shadow: 0 0 20px rgba(0,0,0,0.5); -webkit-box-shadow: 0 0 20px rgba(0,0,0,0.5); box-shadow: 0 0 20px rgba(0,0,0,0.5); position:fixed; z-index:20000; bottom:0; right:0; font-size:15px; margin: 20px 0; display: block; background:#4E5969; } \ + #cp_popup { font-family: "Helvetica Neue", Helvetica, Arial, Geneva, sans-serif; -moz-border-radius: 6px 0px 0px 6px; -webkit-border-radius: 6px 0px 0px 6px; border-radius: 6px 0px 0px 6px; -moz-box-shadow: 0 0 20px rgba(0,0,0,0.5); -webkit-box-shadow: 0 0 20px rgba(0,0,0,0.5); box-shadow: 0 0 20px rgba(0,0,0,0.5); position:fixed; z-index:20000; bottom:0; right:0; font-size:15px; margin: 20px 0; display: block; background:#FFF; } \ #cp_popup.opened { width: 492px; } \ #cp_popup a#add_to { cursor:pointer; text-align:center; text-decoration:none; color: #000; display:block; padding:5px 0 5px 5px; } \ #cp_popup a#close_button { cursor:pointer; float: right; padding:120px 10px 10px; } \ #cp_popup a img { vertical-align: middle; } \ #cp_popup a:hover { color:#000; } \ - #cp_popup iframe{ background:#4E5969; margin:6px 0 2px 6px; height:140px; width:450px; overflow:hidden; border:none; } \ + #cp_popup iframe{ background:#FFF; margin:6px 0 2px 6px; height:140px; width:450px; overflow:hidden; border:none; } \ '); var cp_icon = 'data:image/png;base64,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'; From fbceb38d7df1e2a7ae5005c3397872acd084e8fe Mon Sep 17 00:00:00 2001 From: Ruud Date: Tue, 25 Aug 2015 14:29:06 +0200 Subject: [PATCH 301/301] Userscript fixes and tests --- .../media/movie/providers/userscript/filmweb.py | 20 ++++----- .../media/movie/providers/userscript/flickchart.py | 6 ++- .../media/movie/providers/userscript/moviemeter.py | 14 +++++++ .../movie/providers/userscript/rottentomatoes.py | 6 ++- .../media/movie/providers/userscript/sharethe.py | 8 ---- .../core/media/movie/providers/userscript/tmdb.py | 4 +- .../core/media/movie/providers/userscript/whiwa.py | 8 ---- couchpotato/core/plugins/userscript/main.py | 49 +++++++++++++++++++++- 8 files changed, 81 insertions(+), 34 deletions(-) delete mode 100644 couchpotato/core/media/movie/providers/userscript/sharethe.py delete mode 100644 couchpotato/core/media/movie/providers/userscript/whiwa.py diff --git a/couchpotato/core/media/movie/providers/userscript/filmweb.py b/couchpotato/core/media/movie/providers/userscript/filmweb.py index a4a51a0..1f7b371 100644 --- a/couchpotato/core/media/movie/providers/userscript/filmweb.py +++ b/couchpotato/core/media/movie/providers/userscript/filmweb.py @@ -1,14 +1,14 @@ -import re +from bs4 import BeautifulSoup +from couchpotato import fireEvent from couchpotato.core.media._base.providers.userscript.base import UserscriptBase - autoload = 'Filmweb' class Filmweb(UserscriptBase): - version = 2 + version = 3 includes = ['http://www.filmweb.pl/film/*'] @@ -21,14 +21,10 @@ class Filmweb(UserscriptBase): except: return - name = re.search("(?P[^<]+)", data) - - if name is None: - name = re.search("(?P[^<]+)", data) - - name = name.group('name').decode('string_escape') - - year = re.search("\((?P[^\)]+)\).*?", data) - year = year.group('year') + html = BeautifulSoup(data) + name = html.find('meta', {'name': 'title'})['content'][:-9].strip() + name_year = fireEvent('scanner.name_year', name, single = True) + name = name_year.get('name') + year = name_year.get('year') return self.search(name, year) diff --git a/couchpotato/core/media/movie/providers/userscript/flickchart.py b/couchpotato/core/media/movie/providers/userscript/flickchart.py index c54a8ca..8b48ad7 100644 --- a/couchpotato/core/media/movie/providers/userscript/flickchart.py +++ b/couchpotato/core/media/movie/providers/userscript/flickchart.py @@ -12,6 +12,8 @@ autoload = 'Flickchart' class Flickchart(UserscriptBase): + version = 2 + includes = ['http://www.flickchart.com/movie/*'] def getMovie(self, url): @@ -24,11 +26,11 @@ class Flickchart(UserscriptBase): try: start = data.find('') end = data.find('', start) - page_title = data[start + len(''):end].strip().split('-') + page_title = data[start + len('<title>'):end].strip().split('- Flick') year_name = fireEvent('scanner.name_year', page_title[0], single = True) - return self.search(**year_name) + return self.search(year_name.get('name'), year_name.get('year')) except: log.error('Failed parsing page for title and year: %s', traceback.format_exc()) diff --git a/couchpotato/core/media/movie/providers/userscript/moviemeter.py b/couchpotato/core/media/movie/providers/userscript/moviemeter.py index 4c9bb22..ee8d931 100644 --- a/couchpotato/core/media/movie/providers/userscript/moviemeter.py +++ b/couchpotato/core/media/movie/providers/userscript/moviemeter.py @@ -1,3 +1,4 @@ +from couchpotato.core.helpers.variable import getImdb from couchpotato.core.media._base.providers.userscript.base import UserscriptBase autoload = 'MovieMeter' @@ -6,3 +7,16 @@ autoload = 'MovieMeter' class MovieMeter(UserscriptBase): includes = ['http://*.moviemeter.nl/film/*', 'http://moviemeter.nl/film/*'] + + version = 2 + + def getMovie(self, url): + + cookie = {'Cookie': 'cok=1'} + + try: + data = self.urlopen(url, headers = cookie) + except: + return + + return self.getInfo(getImdb(data)) diff --git a/couchpotato/core/media/movie/providers/userscript/rottentomatoes.py b/couchpotato/core/media/movie/providers/userscript/rottentomatoes.py index c1ad4e3..e6ff262 100644 --- a/couchpotato/core/media/movie/providers/userscript/rottentomatoes.py +++ b/couchpotato/core/media/movie/providers/userscript/rottentomatoes.py @@ -16,7 +16,7 @@ class RottenTomatoes(UserscriptBase): includes = ['*://www.rottentomatoes.com/m/*'] excludes = ['*://www.rottentomatoes.com/m/*/*/'] - version = 3 + version = 4 def getMovie(self, url): @@ -27,7 +27,9 @@ class RottenTomatoes(UserscriptBase): try: title = re.findall("<title>(.*)", data) - name_year = fireEvent('scanner.name_year', title[0].split(' - Rotten')[0].decode('unicode_escape'), single = True) + title = title[0].split(' - Rotten')[0].replace(' ', ' ').decode('unicode_escape') + name_year = fireEvent('scanner.name_year', title, single = True) + name = name_year.get('name') year = name_year.get('year') diff --git a/couchpotato/core/media/movie/providers/userscript/sharethe.py b/couchpotato/core/media/movie/providers/userscript/sharethe.py deleted file mode 100644 index ef2f537..0000000 --- a/couchpotato/core/media/movie/providers/userscript/sharethe.py +++ /dev/null @@ -1,8 +0,0 @@ -from couchpotato.core.media._base.providers.userscript.base import UserscriptBase - -autoload = 'ShareThe' - - -class ShareThe(UserscriptBase): - - includes = ['http://*.sharethe.tv/movies/*', 'http://sharethe.tv/movies/*'] diff --git a/couchpotato/core/media/movie/providers/userscript/tmdb.py b/couchpotato/core/media/movie/providers/userscript/tmdb.py index f11dba2..fe7b139 100644 --- a/couchpotato/core/media/movie/providers/userscript/tmdb.py +++ b/couchpotato/core/media/movie/providers/userscript/tmdb.py @@ -9,7 +9,9 @@ autoload = 'TMDB' class TMDB(UserscriptBase): - includes = ['http://www.themoviedb.org/movie/*'] + version = 2 + + includes = ['*://www.themoviedb.org/movie/*'] def getMovie(self, url): match = re.search('(?P\d+)', url) diff --git a/couchpotato/core/media/movie/providers/userscript/whiwa.py b/couchpotato/core/media/movie/providers/userscript/whiwa.py deleted file mode 100644 index bd602d2..0000000 --- a/couchpotato/core/media/movie/providers/userscript/whiwa.py +++ /dev/null @@ -1,8 +0,0 @@ -from couchpotato.core.media._base.providers.userscript.base import UserscriptBase - -autoload = 'WHiWA' - - -class WHiWA(UserscriptBase): - - includes = ['http://whiwa.net/stats/movie/*'] diff --git a/couchpotato/core/plugins/userscript/main.py b/couchpotato/core/plugins/userscript/main.py index 4ca8ed3..0499526 100644 --- a/couchpotato/core/plugins/userscript/main.py +++ b/couchpotato/core/plugins/userscript/main.py @@ -1,4 +1,7 @@ import os +import traceback +import time +from base64 import b64encode, b64decode from couchpotato import index from couchpotato.api import addApiView @@ -15,7 +18,7 @@ log = CPLog(__name__) class Userscript(Plugin): - version = 5 + version = 8 def __init__(self): addApiView('userscript.get/(.*)/(.*)', self.getUserScript, static = True) @@ -26,6 +29,7 @@ class Userscript(Plugin): addApiView('userscript.bookmark', self.bookmark) addEvent('userscript.get_version', self.getVersion) + addEvent('app.test', self.doTest) def bookmark(self, host = None, **kwargs): @@ -91,3 +95,46 @@ class Userscript(Plugin): params['error'] = params['movie'] if params['movie'] else 'Failed getting movie info' return params + + def doTest(self): + time.sleep(1) + + tests = [ + 'aHR0cDovL3d3dy5hbGxvY2luZS5mci9maWxtL2ZpY2hlZmlsbV9nZW5fY2ZpbG09MjAxMTA1Lmh0bWw=', + 'aHR0cDovL3RyYWlsZXJzLmFwcGxlLmNvbS90cmFpbGVycy9wYXJhbW91bnQvbWlzc2lvbmltcG9zc2libGVyb2d1ZW5hdGlvbi8=', + 'aHR0cDovL3d3dy55b3V0aGVhdGVyLmNvbS92aWV3LnBocD9pZD0xMTI2Mjk5', + 'aHR0cDovL3RyYWt0LnR2L21vdmllcy9taXNzaW9uLWltcG9zc2libGUtcm9ndWUtbmF0aW9uLTIwMTU=', + 'aHR0cHM6Ly93d3cucmVkZGl0LmNvbS9yL0lqdXN0d2F0Y2hlZC9jb21tZW50cy8zZjk3bzYvaWp3X21pc3Npb25faW1wb3NzaWJsZV9yb2d1ZV9uYXRpb25fMjAxNS8=', + 'aHR0cDovL3d3dy5yb3R0ZW50b21hdG9lcy5jb20vbS9taXNzaW9uX2ltcG9zc2libGVfcm9ndWVfbmF0aW9uLw==', + 'aHR0cHM6Ly93d3cudGhlbW92aWVkYi5vcmcvbW92aWUvMTc3Njc3LW1pc3Npb24taW1wb3NzaWJsZS01', + 'aHR0cDovL3d3dy5jcml0aWNrZXIuY29tL2ZpbG0vTWlzc2lvbl9JbXBvc3NpYmxlX1JvZ3VlLw==', + 'aHR0cDovL2ZpbG1jZW50cnVtLm5sL2ZpbG1zLzE4MzIzL21pc3Npb24taW1wb3NzaWJsZS1yb2d1ZS1uYXRpb24v', + 'aHR0cDovL3d3dy5maWxtc3RhcnRzLmRlL2tyaXRpa2VuLzIwMTEwNS5odG1s', + 'aHR0cDovL3d3dy5maWxtd2ViLnBsL2ZpbG0vTWlzc2lvbiUzQStJbXBvc3NpYmxlKy0rUm9ndWUrTmF0aW9uLTIwMTUtNjU1MDQ4', + 'aHR0cDovL3d3dy5mbGlja2NoYXJ0LmNvbS9tb3ZpZS8zM0NFMzEyNUJB', + 'aHR0cDovL3d3dy5pbWRiLmNvbS90aXRsZS90dDIzODEyNDkv', + 'aHR0cDovL2xldHRlcmJveGQuY29tL2ZpbG0vbWlzc2lvbi1pbXBvc3NpYmxlLXJvZ3VlLW5hdGlvbi8=', + 'aHR0cDovL3d3dy5tb3ZpZW1ldGVyLm5sL2ZpbG0vMTA0MTcw', + 'aHR0cDovL21vdmllcy5pby9tLzMxL2Vu', + ] + + success = 0 + for x in tests: + x = b64decode(x) + try: + movie = self.getViaUrl(x) + movie = movie.get('movie', {}) or {} + imdb = movie.get('imdb') + + if imdb and b64encode(imdb) in ['dHQxMjI5MjM4', 'dHQyMzgxMjQ5']: + success += 1 + continue + except: + log.error('Failed userscript test "%s": %s', (x, traceback.format_exc())) + + log.error('Failed userscript test "%s"', x) + + if success == len(tests): + log.debug('All userscript tests successful') + else: + log.error('Failed userscript tests, %s out of %s', (success, len(tests)))