'
}).inject(self.message_container, 'top');
setTimeout(function(){
- new_message.addClass('show')
+ new_message.addClass('show');
}, 10);
var hide_message = function(){
@@ -211,8 +210,8 @@ var NotificationBase = new Class({
var setting_page = App.getPage('Settings');
setting_page.addEvent('create', function(){
- Object.each(setting_page.tabs.notifications.groups, self.addTestButton.bind(self))
- })
+ Object.each(setting_page.tabs.notifications.groups, self.addTestButton.bind(self));
+ });
},
@@ -222,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': {
@@ -235,20 +234,21 @@ var NotificationBase = new Class({
button.set('text', button_name);
+ var message;
if(json.success){
- var message = new Element('span.success', {
+ message = new Element('span.success', {
'text': 'Notification successful'
- }).inject(button, 'after')
+ }).inject(button, 'after');
}
else {
- var message = new Element('span.failed', {
+ message = new Element('span.failed', {
'text': 'Notification failed. Check logs for details.'
- }).inject(button, 'after')
+ }).inject(button, 'after');
}
(function(){
message.destroy();
- }).delay(3000)
+ }).delay(3000);
}
});
}
@@ -258,7 +258,7 @@ var NotificationBase = new Class({
},
testButtonName: function(fieldset){
- var name = String(fieldset.getElement('h2').innerHTML).substring(0,String(fieldset.getElement('h2').innerHTML).indexOf("
- 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/core/plugins/category/static/category.js b/couchpotato/core/plugins/category/static/category.js
index 2d79ff1..5962609 100644
--- a/couchpotato/core/plugins/category/static/category.js
+++ b/couchpotato/core/plugins/category/static/category.js
@@ -52,7 +52,7 @@ var CategoryListBase = new Class({
});
- })
+ });
},
@@ -71,7 +71,7 @@ var CategoryListBase = new Class({
'events': {
'click': function(){
var category = self.createCategory();
- $(category).inject(self.category_container)
+ $(category).inject(self.category_container);
}
}
})
@@ -79,15 +79,15 @@ var CategoryListBase = new Class({
// Add categories, that aren't part of the core (for editing)
Array.each(self.categories, function(category){
- $(category).inject(self.category_container)
+ $(category).inject(self.category_container);
});
},
getCategory: function(id){
return this.categories.filter(function(category){
- return category.data._id == id
- }).pick()
+ return category.data._id == id;
+ }).pick();
},
getAll: function(){
@@ -97,7 +97,7 @@ var CategoryListBase = new Class({
createCategory: function(data){
var self = this;
- var data = data || {'id': randomString()};
+ data = data || {'id': randomString()};
var category = new Category(data);
self.categories.include(category);
@@ -125,7 +125,7 @@ var CategoryListBase = new Class({
new Element('span.category_label', {
'text': category.data.label
}),
- new Element('span.handle')
+ new Element('span.handle.icon-handle')
).inject(category_list);
});
@@ -192,7 +192,7 @@ var Category = new Class({
}),
new Element('.category_label.ctrlHolder').adopt(
new Element('label', {'text':'Name'}),
- new Element('input.inlay', {
+ new Element('input', {
'type':'text',
'value': data.label,
'placeholder': 'Example: Kids, Horror or His'
@@ -201,7 +201,7 @@ var Category = new Class({
),
new Element('.category_preferred.ctrlHolder').adopt(
new Element('label', {'text':'Preferred'}),
- new Element('input.inlay', {
+ new Element('input', {
'type':'text',
'value': data.preferred,
'placeholder': 'Blu-ray, DTS'
@@ -209,7 +209,7 @@ var Category = new Class({
),
new Element('.category_required.ctrlHolder').adopt(
new Element('label', {'text':'Required'}),
- new Element('input.inlay', {
+ new Element('input', {
'type':'text',
'value': data.required,
'placeholder': 'Example: DTS, AC3 & English'
@@ -217,7 +217,7 @@ var Category = new Class({
),
new Element('.category_ignored.ctrlHolder').adopt(
new Element('label', {'text':'Ignored'}),
- new Element('input.inlay', {
+ new Element('input', {
'type':'text',
'value': data.ignored,
'placeholder': 'Example: dubbed, swesub, french'
@@ -225,7 +225,7 @@ var Category = new Class({
)
);
- self.makeSortable()
+ self.makeSortable();
},
@@ -248,7 +248,7 @@ var Category = new Class({
}
});
- }).delay(delay || 0, self)
+ }).delay(delay || 0, self);
},
@@ -262,13 +262,13 @@ var Category = new Class({
'preferred' : self.el.getElement('.category_preferred input').get('value'),
'ignored' : self.el.getElement('.category_ignored input').get('value'),
'destination': self.data.destination
- }
+ };
},
del: function(){
var self = this;
- if(self.data.label == undefined){
+ if(self.data.label === undefined){
self.el.destroy();
return;
}
@@ -318,11 +318,11 @@ var Category = new Class({
},
get: function(attr){
- return this.data[attr]
+ return this.data[attr];
},
toElement: function(){
- return this.el
+ return this.el;
}
});
diff --git a/couchpotato/core/plugins/category/static/category.css b/couchpotato/core/plugins/category/static/category.scss
similarity index 72%
rename from couchpotato/core/plugins/category/static/category.css
rename to couchpotato/core/plugins/category/static/category.scss
index 3218a79..24ba16e 100644
--- a/couchpotato/core/plugins/category/static/category.css
+++ b/couchpotato/core/plugins/category/static/category.scss
@@ -1,13 +1,14 @@
+@import "_mixins";
+
.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);
+ margin-bottom: 20px;
position: relative;
}
@@ -28,8 +29,6 @@
}
.category .formHint {
- width: 250px !important;
- margin: 0 !important;
opacity: 0.1;
}
.category:hover .formHint {
@@ -48,11 +47,10 @@
}
#category_ordering li {
- cursor: -webkit-grab;
- cursor: -moz-grab;
cursor: grab;
- border-bottom: 1px solid rgba(255,255,255,0.2);
- padding: 0 5px;
+ border-bottom: 1px solid $theme_off;
+ padding: 5px;
+ list-style: none;
}
#category_ordering li:last-child { border: 0; }
@@ -69,14 +67,9 @@
}
#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/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
diff --git a/couchpotato/core/plugins/log/main.py b/couchpotato/core/plugins/log/main.py
index 003529b..4bf7cf3 100644
--- a/couchpotato/core/plugins/log/main.py
+++ b/couchpotato/core/plugins/log/main.py
@@ -131,12 +131,13 @@ 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')
- if split:
+ split = re.split(re_split, log_line)
+ 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.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.js b/couchpotato/core/plugins/log/static/log.js
index 71a65d0..0d47914 100644
--- a/couchpotato/core/plugins/log/static/log.js
+++ b/couchpotato/core/plugins/log/static/log.js
@@ -7,21 +7,21 @@ Page.Log = new Class({
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}```',
+ 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 () {
var self = this;
@@ -34,6 +34,7 @@ Running on: ...\n\
var self = this;
if (self.log) self.log.destroy();
+
self.log = new Element('div.container.loading', {
'text': 'loading...',
'events': {
@@ -41,9 +42,17 @@ Running on: ...\n\
self.showSelectionButton.delay(100, self, e);
}
}
- }).inject(self.el);
+ }).inject(self.content);
+
+ if(self.navigation){
+ var nav = self.navigation.getElement('.nav');
+ nav.getElements('.active').removeClass('active');
+
+ self.navigation.getElements('li')[nr+1].addClass('active');
+ }
- Api.request('logging.get', {
+ if(self.request && self.request.running) self.request.cancel();
+ self.request = Api.request('logging.get', {
'data': {
'nr': nr
},
@@ -52,65 +61,68 @@ Running on: ...\n\
self.log_items = self.createLogElements(json.log);
self.log.adopt(self.log_items);
self.log.removeClass('loading');
+ self.scrollToBottom();
- var nav = new Element('ul.nav', {
- 'events': {
- 'click:relay(li.select)': function (e, el) {
- self.getLogs(parseInt(el.get('text')) - 1);
- }
- }
- });
+ 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'
+ })
+ );
- // Type selection
- new Element('li.filter').grab(
- new Element('select', {
+ var nav = new Element('ul.nav', {
'events': {
- 'change': function () {
- var type_filter = this.getSelected()[0].get('value');
- self.el.set('data-filter', type_filter);
- self.scrollToBottom();
+ 'click:relay(li.select)': function (e, el) {
+ self.getLogs(parseInt(el.get('text')) - 1);
}
}
- }).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(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(nav);
- // Hint
- new Element('li.hint', {
- 'text': 'Select multiple lines & report an issue'
- }).inject(nav);
+ // Clear button
+ new Element('li.clear', {
+ 'text': 'clear',
+ 'events': {
+ 'click': function () {
+ Api.request('logging.clear', {
+ 'onComplete': function () {
+ self.getLogs(0);
+ }
+ });
- // Add to page
- nav.inject(self.log, 'top');
+ }
+ }
+ }).inject(nav);
- self.scrollToBottom();
+ // Add to page
+ self.navigation.inject(self.content, 'top');
+ }
}
});
@@ -133,14 +145,14 @@ Running on: ...\n\
new Element('span.message', {
'text': log.message
})
- ))
+ ));
});
return elements;
},
scrollToBottom: function () {
- new Fx.Scroll(window, {'duration': 0}).toBottom();
+ new Fx.Scroll(this.content, {'duration': 0}).toBottom();
},
showSelectionButton: function(e){
@@ -213,7 +225,7 @@ Running on: ...\n\
.replace('{version}', version ? version.version.repr : '...'),
textarea;
- var overlay = new Element('div.report', {
+ var overlay = new Element('div.mask.report_popup', {
'method': 'post',
'events': {
'click': function(e){
@@ -245,12 +257,7 @@ Running on: ...\n\
})
),
textarea = new Element('textarea', {
- 'text': body,
- 'events': {
- 'click': function(){
- this.select();
- }
- }
+ 'text': body
}),
new Element('a.button', {
'target': '_blank',
@@ -270,7 +277,7 @@ Running on: ...\n\
)
);
- overlay.inject(self.log);
+ overlay.inject(document.body);
},
getSelected: function(){
diff --git a/couchpotato/core/plugins/log/static/log.scss b/couchpotato/core/plugins/log/static/log.scss
new file mode 100644
index 0000000..4be815b
--- /dev/null
+++ b/couchpotato/core/plugins/log/static/log.scss
@@ -0,0 +1,159 @@
+@import "_mixins";
+
+.page.log {
+
+ .nav {
+ text-align: right;
+ padding: 0;
+ margin: 0;
+
+ li {
+ display: inline-block;
+ padding: 5px 10px;
+ margin: 0;
+
+ &.select, &.clear {
+ cursor: pointer;
+ }
+
+ &:hover:not(.active):not(.filter) {
+ background: rgba(255, 255, 255, 0.1);
+ }
+
+ &.active {
+ font-weight: bold;
+ cursor: default;
+ background: rgba(255,255,255,.1);
+ }
+ }
+ }
+
+ .hint {
+ font-style: italic;
+ opacity: .5;
+ margin-top: 3px;
+ }
+
+ .container {
+ padding: $padding;
+ overflow: hidden;
+ line-height: 150%;
+
+ &.loading {
+ text-align: center;
+ font-size: 20px;
+ padding: 100px 50px;
+ }
+
+ select {
+ vertical-align: top;
+ }
+
+ .time {
+ clear: both;
+ font-size: .75em;
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
+ overflow: hidden;
+ padding: 0 3px;
+ font-family: Lucida Console, Monaco, Nimbus Mono L, monospace, serif;
+ display: flex;
+
+ &.highlight {
+ background: $theme_off;
+ }
+
+ span {
+ padding: 5px 0 3px;
+ display: inline-block;
+ vertical-align: middle;
+ width: 90px;
+ }
+
+ ::selection {
+ background-color: #000;
+ color: #FFF;
+ }
+ }
+
+ .type.type {
+ margin-left: 10px;
+ width: 40px;
+ }
+
+ .message {
+ white-space: pre-wrap;
+ flex: 1 auto;
+ }
+
+
+ .error { color: #FFA4A4; }
+ .debug span { opacity: .6; }
+ }
+
+
+
+ [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;
+ }
+}
+
+.report_popup.report_popup {
+ position: fixed;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ top: 0;
+ z-index: 99999;
+ font-size: 14px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ opacity: 1;
+ color: #FFF;
+ pointer-events: auto;
+
+ .button {
+ margin: 10px 0;
+ padding: 10px;
+ color: $background_color;
+ background: $primary_color;
+ }
+
+ .bug {
+ width: 80%;
+ height: 80%;
+ max-height: 800px;
+ max-width: 800px;
+
+ display: flex;
+ flex-flow: column nowrap;
+
+ > span {
+ margin: $padding/2 0 $padding 0;
+ }
+
+ 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: $primary_color;
+ color: #FFF;
+}
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.js b/couchpotato/core/plugins/profile/static/profile.js
index 35ad81b..626c8ce 100644
--- a/couchpotato/core/plugins/profile/static/profile.js
+++ b/couchpotato/core/plugins/profile/static/profile.js
@@ -31,7 +31,7 @@ var Profile = new Class({
}),
new Element('.quality_label.ctrlHolder').adopt(
new Element('label', {'text':'Name'}),
- new Element('input.inlay', {
+ new Element('input', {
'type':'text',
'value': data.label,
'placeholder': 'Profile name'
@@ -47,7 +47,7 @@ var Profile = new Class({
new Element('div.wait_for.ctrlHolder').adopt(
// "Wait the entered number of days for a checked quality, before downloading a lower quality release."
new Element('span', {'text':'Wait'}),
- new Element('input.inlay.wait_for_input.xsmall', {
+ new Element('input.wait_for_input.xsmall', {
'type':'text',
'value': data.wait_for && data.wait_for.length > 0 ? data.wait_for[0] : 0
}),
@@ -55,7 +55,7 @@ var Profile = new Class({
new Element('span.advanced', {'text':'and keep searching'}),
// "After a checked quality is found and downloaded, continue searching for even better quality releases for the entered number of days."
- new Element('input.inlay.xsmall.stop_after_input.advanced', {
+ new Element('input.xsmall.stop_after_input.advanced', {
'type':'text',
'value': data.stop_after && data.stop_after.length > 0 ? data.stop_after[0] : 0
}),
@@ -63,7 +63,7 @@ var Profile = new Class({
// Minimum score of
new Element('span.advanced', {'html':' Releases need a minimum score of'}),
- new Element('input.advanced.inlay.xsmall.minimum_score_input', {
+ new Element('input.advanced.xsmall.minimum_score_input', {
'size': 4,
'type':'text',
'value': data.minimum_score || 1
@@ -81,7 +81,7 @@ var Profile = new Class({
'quality': quality,
'finish': data.finish[nr] || false,
'3d': data['3d'] ? data['3d'][nr] || false : false
- })
+ });
});
}
@@ -123,7 +123,7 @@ var Profile = new Class({
}
});
- }).delay(delay, self)
+ }).delay(delay, self);
},
@@ -148,7 +148,7 @@ var Profile = new Class({
});
});
- return data
+ return data;
},
addType: function(data){
@@ -177,7 +177,7 @@ var Profile = new Class({
var self = this;
return self.types.filter(function(type){
- return type.get('quality')
+ return type.get('quality');
});
},
@@ -231,15 +231,15 @@ var Profile = new Class({
},
get: function(attr){
- return this.data[attr]
+ return this.data[attr];
},
isCore: function(){
- return this.data.core
+ return this.data.core;
},
toElement: function(){
- return this.el
+ return this.el;
}
});
@@ -270,47 +270,42 @@ Profile.Type = new Class({
var data = self.data;
self.el = new Element('li.type').adopt(
- new Element('span.quality_type').grab(
+ new Element('span.quality_type.select_wrapper.icon-dropdown').grab(
self.fillQualities()
),
self.finish_container = new Element('label.finish').adopt(
- new Element('span.finish').grab(
- self.finish = new Element('input.inlay.finish[type=checkbox]', {
- 'checked': data.finish !== undefined ? data.finish : 1,
- 'events': {
- 'change': function(){
- if(self.el == self.el.getParent().getElement(':first-child')){
- self.finish_class.check();
- alert('Top quality always finishes the search');
- return;
- }
-
- self.fireEvent('change');
+ self.finish = new Element('input.finish[type=checkbox]', {
+ 'checked': data.finish !== undefined ? data.finish : 1,
+ 'events': {
+ 'change': function(){
+ if(self.el == self.el.getParent().getElement(':first-child')){
+ alert('Top quality always finishes the search');
+ return;
}
+
+ self.fireEvent('change');
}
- })
- ),
+ }
+ }),
new Element('span.check_label[text=finish]')
),
self['3d_container'] = new Element('label.threed').adopt(
- new Element('span.3d').grab(
- self['3d'] = new Element('input.inlay.3d[type=checkbox]', {
- 'checked': data['3d'] !== undefined ? data['3d'] : 0,
- 'events': {
- 'change': function(){
- self.fireEvent('change');
- }
+ self['3d'] = new Element('input.3d[type=checkbox]', {
+ 'checked': data['3d'] !== undefined ? data['3d'] : 0,
+ 'events': {
+ 'change': function(){
+ self.fireEvent('change');
}
- })
- ),
+ }
+ }),
new Element('span.check_label[text=3D]')
),
- new Element('span.delete.icon2', {
+ new Element('span.delete.icon-cancel', {
'events': {
'click': self.del.bind(self)
}
}),
- new Element('span.handle')
+ new Element('span.handle.icon-handle')
);
self.el[self.data.quality ? 'removeClass' : 'addClass']('is_empty');
@@ -318,9 +313,6 @@ Profile.Type = new Class({
if(self.data.quality && Quality.getQuality(self.data.quality).allow_3d)
self.el.addClass('allow_3d');
- self.finish_class = new Form.Check(self.finish);
- self['3d_class'] = new Form.Check(self['3d']);
-
},
fillQualities: function(){
@@ -342,7 +334,7 @@ Profile.Type = new Class({
'text': q.label,
'value': q.identifier,
'data-allow_3d': q.allow_3d
- }).inject(self.qualities)
+ }).inject(self.qualities);
});
self.qualities.set('value', self.data.quality);
@@ -358,7 +350,7 @@ Profile.Type = new Class({
'quality': self.qualities.get('value'),
'finish': +self.finish.checked,
'3d': +self['3d'].checked
- }
+ };
},
get: function(key){
diff --git a/couchpotato/core/plugins/profile/static/profile.scss b/couchpotato/core/plugins/profile/static/profile.scss
new file mode 100644
index 0000000..ee9ae41
--- /dev/null
+++ b/couchpotato/core/plugins/profile/static/profile.scss
@@ -0,0 +1,150 @@
+@import "_mixins";
+
+.add_new_profile {
+ padding: 20px;
+ display: block;
+ text-align: center;
+ font-size: 20px;
+ border-bottom: 1px solid $theme_off;
+}
+
+.profile {
+ margin-bottom: 20px;
+
+ .quality_label input {
+ font-weight: bold;
+ }
+
+ .ctrlHolder {
+
+ .types {
+ flex: 1 1 auto;
+ min-width: 360px;
+
+ .type {
+ display: flex;
+ flex-row: row nowrap;
+ align-items: center;
+ padding: 2px 0;
+
+ label {
+ min-width: 0;
+ margin-left: $padding/2;
+
+ span {
+ font-size: .9em;
+ }
+ }
+
+ input[type=checkbox] {
+ margin-right: 3px;
+ }
+
+ .delete, .handle {
+ margin-left: $padding/4;
+ width: 20px;
+ font-size: 20px;
+ opacity: .1;
+ text-align: center;
+ cursor: pointer;
+
+ &.handle {
+ cursor: move;
+ cursor: grab;
+ }
+
+ &:hover {
+ opacity: 1;
+ }
+ }
+
+ &.is_empty {
+ .delete, .handle {
+ display: none;
+ }
+ }
+ }
+
+ }
+
+ &.wait_for.wait_for {
+ display: block;
+
+ input {
+ min-width: 0;
+ width: 40px;
+ text-align: center;
+ margin: 0 2px;
+ }
+
+ .advanced {
+ display: none;
+ color: $primary_color;
+
+ .show_advanced & {
+ display: inline;
+ }
+ }
+
+ }
+
+ .formHint {
+ }
+
+ }
+}
+
+#profile_ordering {
+ ul {
+ list-style: none;
+ margin: 0;
+ width: 275px;
+ padding: 0;
+ }
+
+ li {
+ border-bottom: 1px solid $theme_off;
+ padding: 5px;
+ display: flex;
+ align-items: center;
+
+ &:hover {
+ background: $theme_off;
+ }
+
+ &:last-child { border: 0; }
+
+ input[type=checkbox] {
+ margin: 2px 10px 0 0;
+ vertical-align: top;
+ }
+
+ > span {
+ display: inline-block;
+ height: 20px;
+ vertical-align: top;
+ line-height: 20px;
+
+ &.profile_label {
+ flex: 1 1 auto;
+ }
+ }
+
+ .handle {
+ font-size: 20px;
+ width: 20px;
+ float: right;
+ cursor: move;
+ cursor: grab;
+ opacity: .5;
+ text-align: center;
+
+ &:hover {
+ opacity: 1;
+ }
+ }
+ }
+
+ .formHint {
+ }
+}
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.js b/couchpotato/core/plugins/quality/static/quality.js
index d233b1c..67924b0 100644
--- a/couchpotato/core/plugins/quality/static/quality.js
+++ b/couchpotato/core/plugins/quality/static/quality.js
@@ -12,20 +12,20 @@ var QualityBase = new Class({
self.profiles = [];
Array.each(data.profiles, self.createProfilesClass.bind(self));
- App.addEvent('loadSettings', self.addSettings.bind(self))
+ App.addEvent('loadSettings', self.addSettings.bind(self));
},
getProfile: function(id){
return this.profiles.filter(function(profile){
- return profile.data._id == id
- }).pick()
+ return profile.data._id == id;
+ }).pick();
},
// Hide items when getting profiles
getActiveProfiles: function(){
return Array.filter(this.profiles, function(profile){
- return !profile.data.hide
+ return !profile.data.hide;
});
},
@@ -37,7 +37,7 @@ var QualityBase = new Class({
}
catch(e){}
- return {}
+ return {};
},
addSettings: function(){
@@ -58,7 +58,7 @@ var QualityBase = new Class({
self.createProfileOrdering();
self.createSizes();
- })
+ });
},
@@ -68,7 +68,7 @@ var QualityBase = new Class({
createProfiles: function(){
var self = this;
- var non_core_profiles = Array.filter(self.profiles, function(profile){ return !profile.isCore() });
+ var non_core_profiles = Array.filter(self.profiles, function(profile){ return !profile.isCore(); });
var count = non_core_profiles.length;
self.settings.createGroup({
@@ -81,7 +81,7 @@ var QualityBase = new Class({
'events': {
'click': function(){
var profile = self.createProfilesClass();
- $(profile).inject(self.profile_container)
+ $(profile).inject(self.profile_container);
}
}
})
@@ -89,7 +89,7 @@ var QualityBase = new Class({
// Add profiles, that aren't part of the core (for editing)
Array.each(non_core_profiles, function(profile){
- $(profile).inject(self.profile_container)
+ $(profile).inject(self.profile_container);
});
},
@@ -97,7 +97,7 @@ var QualityBase = new Class({
createProfilesClass: function(data){
var self = this;
- var data = data || {'id': randomString()};
+ data = data || {'id': randomString()};
var profile = new Profile(data);
self.profiles.include(profile);
@@ -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'),
@@ -123,7 +123,7 @@ var QualityBase = new Class({
Array.each(self.profiles, function(profile){
var check;
new Element('li', {'data-id': profile.data._id}).adopt(
- check = new Element('input.inlay[type=checkbox]', {
+ check = new Element('input[type=checkbox]', {
'checked': !profile.data.hide,
'events': {
'change': self.saveProfileOrdering.bind(self)
@@ -132,11 +132,8 @@ var QualityBase = new Class({
new Element('span.profile_label', {
'text': profile.data.label
}),
- new Element('span.handle')
+ new Element('span.handle.icon-handle')
).inject(self.profiles_list);
-
- new Form.Check(check);
-
});
// Sortable
@@ -190,7 +187,6 @@ var QualityBase = new Class({
'name': 'sizes'
}).inject(self.content);
-
new Element('div.item.head.ctrlHolder').adopt(
new Element('span.label', {'text': 'Quality'}),
new Element('span.min', {'text': 'Min'}),
@@ -200,23 +196,23 @@ var QualityBase = new Class({
Array.each(self.qualities, function(quality){
new Element('div.ctrlHolder.item').adopt(
new Element('span.label', {'text': quality.label}),
- new Element('input.min.inlay[type=text]', {
+ new Element('input.min[type=text]', {
'value': quality.size_min,
'events': {
'keyup': function(e){
- self.changeSize(quality.identifier, 'size_min', e.target.get('value'))
+ self.changeSize(quality.identifier, 'size_min', e.target.get('value'));
}
}
}),
- new Element('input.max.inlay[type=text]', {
+ new Element('input.max[type=text]', {
'value': quality.size_max,
'events': {
'keyup': function(e){
- self.changeSize(quality.identifier, 'size_max', e.target.get('value'))
+ self.changeSize(quality.identifier, 'size_max', e.target.get('value'));
}
}
})
- ).inject(group)
+ ).inject(group);
});
},
@@ -235,7 +231,7 @@ var QualityBase = new Class({
'value': value
}
});
- }).delay(300)
+ }).delay(300);
}
diff --git a/couchpotato/core/plugins/quality/static/quality.scss b/couchpotato/core/plugins/quality/static/quality.scss
new file mode 100644
index 0000000..c2aa9f9
--- /dev/null
+++ b/couchpotato/core/plugins/quality/static/quality.scss
@@ -0,0 +1,19 @@
+@import "_mixins";
+
+.group_sizes {
+
+ .item {
+ .label {
+ min-width: 150px;
+ }
+
+ .min, .max {
+ display: inline-block;
+ width: 70px !important;
+ min-width: 0 !important;
+ margin-right: $padding/2;
+ text-align: center;
+ }
+ }
+
+}
diff --git a/couchpotato/core/plugins/release/main.py b/couchpotato/core/plugins/release/main.py
index a241c34..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)
@@ -563,4 +565,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 []
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',
diff --git a/couchpotato/core/plugins/userscript/static/userscript.js b/couchpotato/core/plugins/userscript/static/userscript.js
index d8caeb3..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...'
})
@@ -35,7 +35,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();
}
@@ -54,7 +54,7 @@ var UserscriptSettingTab = new Class({
initialize: function(){
var self = this;
- App.addEvent('loadSettings', self.addSettings.bind(self))
+ App.addEvent('loadSettings', self.addSettings.bind(self));
},
@@ -80,25 +80,28 @@ var UserscriptSettingTab = new Class({
new Element('span.bookmarklet').adopt(
new Element('a.button.green', {
'text': '+CouchPotato',
+ /* jshint ignore:start */
'href': "javascript:void((function(){var e=document.createElement('script');e.setAttribute('type','text/javascript');e.setAttribute('charset','UTF-8');e.setAttribute('src','" +
host_url + Api.createUrl('userscript.bookmark') +
"?host="+ encodeURI(host_url + Api.createUrl('userscript.get')+randomString()+'/') +
"&r='+Math.random()*99999999);document.body.appendChild(e)})());",
+ /* jshint ignore:end */
'target': '',
'events': {
'click': function(e){
(e).stop();
- alert('Drag it to your bookmark ;)')
+ alert('Drag it to your bookmark ;)');
}
}
}),
new Element('span', {
'text': '⇽ Drag this to your bookmarks'
})
- )
- ).setStyles({
- 'background-image': "url('https://couchpota.to/media/images/userscript.gif')"
- });
+ ),
+ new Element('img', {
+ 'src': 'https://couchpota.to/media/images/userscript.gif'
+ })
+ );
});
diff --git a/couchpotato/core/plugins/userscript/static/userscript.css b/couchpotato/core/plugins/userscript/static/userscript.scss
similarity index 100%
rename from couchpotato/core/plugins/userscript/static/userscript.css
rename to couchpotato/core/plugins/userscript/static/userscript.scss
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.js b/couchpotato/core/plugins/wizard/static/wizard.js
index f215dbf..ec1101b 100644
--- a/couchpotato/core/plugins/wizard/static/wizard.js
+++ b/couchpotato/core/plugins/wizard/static/wizard.js
@@ -1,245 +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){
-
- if(self.headers[group]){
- var 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.
"
+ }).inject(self.message_container, "top");
+ setTimeout(function() {
+ new_message.addClass("show");
+ }, 10);
+ var hide_message = function() {
+ new_message.addClass("hide");
+ setTimeout(function() {
+ new_message.destroy();
+ }, 1e3);
+ };
+ if (sticky) new_message.grab(new Element("a.close.icon2", {
+ events: {
+ click: function() {
+ self.markAsRead([ data._id ]);
+ hide_message();
+ }
+ }
+ })); else setTimeout(hide_message, 4e3);
+ },
+ addTestButtons: function() {
+ var self = this;
+ var setting_page = App.getPage("Settings");
+ setting_page.addEvent("create", function() {
+ Object.each(setting_page.tabs.notifications.groups, self.addTestButton.bind(self));
+ });
+ },
+ addTestButton: function(fieldset, plugin_name) {
+ var self = this, button_name = self.testButtonName(fieldset);
+ if (button_name.contains("Notifications")) return;
+ new Element(".ctrlHolder.test_button").grab(new Element("a.button", {
+ text: button_name,
+ events: {
+ click: function() {
+ var button = fieldset.getElement(".test_button .button");
+ button.set("text", "Sending notification");
+ Api.request("notify." + plugin_name + ".test", {
+ onComplete: function(json) {
+ button.set("text", button_name);
+ var message;
+ if (json.success) {
+ message = new Element("span.success", {
+ text: "Notification successful"
+ }).inject(button, "after");
+ } else {
+ message = new Element("span.failed", {
+ text: "Notification failed. Check logs for details."
+ }).inject(button, "after");
+ }
+ (function() {
+ message.destroy();
+ }).delay(3e3);
+ }
+ });
+ }
+ }
+ })).inject(fieldset);
+ },
+ testButtonName: function(fieldset) {
+ var name = fieldset.getElement("h2 .group_label").get("text");
+ return "Test " + name;
+ }
+});
+
+window.Notification = new NotificationBase();
+
+var TwitterNotification = new Class({
+ initialize: function() {
+ var self = this;
+ App.addEvent("loadSettings", self.addRegisterButton.bind(self));
+ },
+ addRegisterButton: function() {
+ var self = this;
+ var setting_page = App.getPage("Settings");
+ setting_page.addEvent("create", function() {
+ var fieldset = setting_page.tabs.notifications.groups.twitter, l = window.location;
+ var twitter_set = 0;
+ fieldset.getElements("input[type=text]").each(function(el) {
+ twitter_set += +(el.get("value") !== "");
+ });
+ new Element(".ctrlHolder").adopt(twitter_set > 0 ? [ self.unregister = new Element("a.button.red", {
+ text: 'Unregister "' + fieldset.getElement("input[name*=screen_name]").get("value") + '"',
+ events: {
+ click: function() {
+ fieldset.getElements("input[type=text]").set("value", "").fireEvent("change");
+ self.unregister.destroy();
+ self.unregister_or.destroy();
+ }
+ }
+ }), self.unregister_or = new Element("span[text=or]") ] : null, new Element("a.button", {
+ text: twitter_set > 0 ? "Register a different account" : "Register your Twitter account",
+ events: {
+ click: function() {
+ Api.request("notify.twitter.auth_url", {
+ data: {
+ host: l.protocol + "//" + l.hostname + (l.port ? ":" + l.port : "")
+ },
+ onComplete: function(json) {
+ window.location = json.url;
+ }
+ });
+ }
+ }
+ })).inject(fieldset.getElement(".test_button"), "before");
+ });
+ }
+});
+
+window.addEvent("domready", function() {
+ new TwitterNotification();
+});
+
+var CategoryListBase = new Class({
+ initialize: function() {
+ var self = this;
+ App.addEvent("loadSettings", self.addSettings.bind(self));
+ },
+ setup: function(categories) {
+ var self = this;
+ self.categories = [];
+ Array.each(categories, self.createCategory.bind(self));
+ },
+ addSettings: function() {
+ var self = this;
+ self.settings = App.getPage("Settings");
+ self.settings.addEvent("create", function() {
+ var tab = self.settings.createSubTab("category", {
+ label: "Categories",
+ name: "category",
+ subtab_label: "Category & filtering"
+ }, self.settings.tabs.searcher, "searcher");
+ self.tab = tab.tab;
+ self.content = tab.content;
+ self.createList();
+ self.createOrdering();
+ });
+ self.settings.addEvent("create", function() {
+ var renamer_group = self.settings.tabs.renamer.groups.renamer;
+ self.categories.each(function(category) {
+ var input = new Option.Directory("section_name", "option.name", category.get("destination"), {
+ name: category.get("label")
+ });
+ input.inject(renamer_group.getElement(".renamer_to"));
+ input.fireEvent("injected");
+ input.save = function() {
+ category.data.destination = input.getValue();
+ category.save();
+ };
+ });
+ });
+ },
+ createList: function() {
+ var self = this;
+ var count = self.categories.length;
+ self.settings.createGroup({
+ label: "Categories",
+ description: "Create categories, each one extending global filters. (Needs refresh '" + (App.isMac() ? "CMD+R" : "F5") + "' after editing)"
+ }).inject(self.content).adopt(self.category_container = new Element("div.container"), new Element("a.add_new_category", {
+ text: count > 0 ? "Create another category" : "Click here to create a category.",
+ events: {
+ click: function() {
+ var category = self.createCategory();
+ $(category).inject(self.category_container);
+ }
+ }
+ }));
+ Array.each(self.categories, function(category) {
+ $(category).inject(self.category_container);
+ });
+ },
+ getCategory: function(id) {
+ return this.categories.filter(function(category) {
+ return category.data._id == id;
+ }).pick();
+ },
+ getAll: function() {
+ return this.categories;
+ },
+ createCategory: function(data) {
+ var self = this;
+ data = data || {
+ id: randomString()
+ };
+ var category = new Category(data);
+ self.categories.include(category);
+ return category;
+ },
+ createOrdering: function() {
+ var self = this;
+ var category_list;
+ self.settings.createGroup({
+ label: "Category ordering"
+ }).adopt(new Element(".ctrlHolder#category_ordering").adopt(new Element("label[text=Order]"), category_list = new Element("ul"), new Element("p.formHint", {
+ html: "Change the order the categories are in the dropdown list."
+ }))).inject(self.content);
+ Array.each(self.categories, function(category) {
+ new Element("li", {
+ "data-id": category.data._id
+ }).adopt(new Element("span.category_label", {
+ text: category.data.label
+ }), new Element("span.handle.icon-handle")).inject(category_list);
+ });
+ self.category_sortable = new Sortables(category_list, {
+ revert: true,
+ handle: "",
+ opacity: .5,
+ onComplete: self.saveOrdering.bind(self)
+ });
+ },
+ saveOrdering: function() {
+ var self = this;
+ var ids = [];
+ self.category_sortable.list.getElements("li").each(function(el) {
+ ids.include(el.get("data-id"));
+ });
+ Api.request("category.save_order", {
+ data: {
+ ids: ids
+ }
+ });
+ }
+});
+
+window.CategoryList = new CategoryListBase();
+
+var Category = new Class({
+ data: {},
+ initialize: function(data) {
+ var self = this;
+ self.data = data;
+ self.create();
+ self.el.addEvents({
+ "change:relay(select)": self.save.bind(self, 0),
+ "keyup:relay(input[type=text])": self.save.bind(self, [ 300 ])
+ });
+ },
+ create: function() {
+ var self = this;
+ var data = self.data;
+ self.el = new Element("div.category").adopt(self.delete_button = new Element("span.delete.icon2", {
+ events: {
+ click: self.del.bind(self)
+ }
+ }), new Element(".category_label.ctrlHolder").adopt(new Element("label", {
+ text: "Name"
+ }), new Element("input", {
+ type: "text",
+ value: data.label,
+ placeholder: "Example: Kids, Horror or His"
+ }), new Element("p.formHint", {
+ text: "See global filters for explanation."
+ })), new Element(".category_preferred.ctrlHolder").adopt(new Element("label", {
+ text: "Preferred"
+ }), new Element("input", {
+ type: "text",
+ value: data.preferred,
+ placeholder: "Blu-ray, DTS"
+ })), new Element(".category_required.ctrlHolder").adopt(new Element("label", {
+ text: "Required"
+ }), new Element("input", {
+ type: "text",
+ value: data.required,
+ placeholder: "Example: DTS, AC3 & English"
+ })), new Element(".category_ignored.ctrlHolder").adopt(new Element("label", {
+ text: "Ignored"
+ }), new Element("input", {
+ type: "text",
+ value: data.ignored,
+ placeholder: "Example: dubbed, swesub, french"
+ })));
+ self.makeSortable();
+ },
+ save: function(delay) {
+ var self = this;
+ if (self.save_timer) clearTimeout(self.save_timer);
+ self.save_timer = function() {
+ Api.request("category.save", {
+ data: self.getData(),
+ useSpinner: true,
+ spinnerOptions: {
+ target: self.el
+ },
+ onComplete: function(json) {
+ if (json.success) {
+ self.data = json.category;
+ }
+ }
+ });
+ }.delay(delay || 0, self);
+ },
+ getData: function() {
+ var self = this;
+ return {
+ id: self.data._id,
+ label: self.el.getElement(".category_label input").get("value"),
+ required: self.el.getElement(".category_required input").get("value"),
+ preferred: self.el.getElement(".category_preferred input").get("value"),
+ ignored: self.el.getElement(".category_ignored input").get("value"),
+ destination: self.data.destination
+ };
+ },
+ del: function() {
+ var self = this;
+ if (self.data.label === undefined) {
+ self.el.destroy();
+ return;
+ }
+ var label = self.el.getElement(".category_label input").get("value");
+ var qObj = new Question('Are you sure you want to delete "' + label + '"?', "", [ {
+ text: 'Delete "' + label + '"',
+ class: "delete",
+ events: {
+ click: function(e) {
+ e.preventDefault();
+ Api.request("category.delete", {
+ data: {
+ id: self.data._id
+ },
+ useSpinner: true,
+ spinnerOptions: {
+ target: self.el
+ },
+ onComplete: function(json) {
+ if (json.success) {
+ qObj.close();
+ self.el.destroy();
+ } else {
+ alert(json.message);
+ }
+ }
+ });
+ }
+ }
+ }, {
+ text: "Cancel",
+ cancel: true
+ } ]);
+ },
+ makeSortable: function() {
+ var self = this;
+ self.sortable = new Sortables(self.category_container, {
+ revert: true,
+ handle: ".handle",
+ opacity: .5,
+ onComplete: self.save.bind(self, 300)
+ });
+ },
+ get: function(attr) {
+ return this.data[attr];
+ },
+ toElement: function() {
+ return this.el;
+ }
+});
+
+Page.Log = new Class({
+ Extends: PageBase,
+ order: 60,
+ 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() {
+ var self = this;
+ self.getLogs(0);
+ },
+ getLogs: function(nr) {
+ var self = this;
+ if (self.log) self.log.destroy();
+ self.log = new Element("div.container.loading", {
+ text: "loading...",
+ events: {
+ "mouseup:relay(.time)": function(e) {
+ self.showSelectionButton.delay(100, self, e);
+ }
+ }
+ }).inject(self.content);
+ 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
+ },
+ onComplete: function(json) {
+ self.log.set("text", "");
+ self.log_items = self.createLogElements(json.log);
+ self.log.adopt(self.log_items);
+ self.log.removeClass("loading");
+ 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(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);
+ }
+ 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");
+ }
+ }
+ });
+ },
+ createLogElements: function(logs) {
+ var elements = [];
+ logs.each(function(log) {
+ elements.include(new Element("div", {
+ class: "time " + log.type.toLowerCase()
+ }).adopt(new Element("span", {
+ text: log.time
+ }), new Element("span.type", {
+ text: log.type
+ }), new Element("span.message", {
+ text: log.message
+ })));
+ });
+ return elements;
+ },
+ scrollToBottom: function() {
+ new Fx.Scroll(this.content, {
+ duration: 0
+ }).toBottom();
+ },
+ showSelectionButton: function(e) {
+ var self = this, selection = self.getSelected(), start_node = selection.anchorNode, parent_start = start_node.parentNode.getParent(".time"), end_node = selection.focusNode.parentNode.getParent(".time"), text = "";
+ var remove_button = function() {
+ self.log.getElements(".highlight").removeClass("highlight");
+ if (self.do_report) self.do_report.destroy();
+ document.body.removeEvent("click", remove_button);
+ };
+ remove_button();
+ if (parent_start) start_node = parent_start;
+ var index = {
+ start: self.log_items.indexOf(start_node),
+ end: self.log_items.indexOf(end_node)
+ };
+ if (index.start > index.end) {
+ index = {
+ start: index.end,
+ end: index.start
+ };
+ }
+ var nodes = self.log_items.slice(index.start, index.end + 1);
+ nodes.each(function(node, nr) {
+ node.addClass("highlight");
+ node.getElements("span").each(function(span) {
+ text += self.spaceFill(span.get("text") + " ", 6);
+ });
+ text += "\n";
+ });
+ self.do_report = new Element("a.do_report.button", {
+ text: "Report issue",
+ styles: {
+ top: e.page.y,
+ left: e.page.x
+ },
+ events: {
+ click: function(e) {
+ e.stop();
+ self.showReport(text);
+ }
+ }
+ }).inject(document.body);
+ setTimeout(function() {
+ document.body.addEvent("click", remove_button);
+ }, 0);
+ },
+ showReport: function(text) {
+ var self = this, version = Updater.getInfo(), body = self.report_text.replace("{issue}", text).replace("{version}", version ? version.version.repr : "..."), textarea;
+ var overlay = new Element("div.mask.report_popup", {
+ method: "post",
+ events: {
+ click: function(e) {
+ overlay.destroy();
+ }
+ }
+ }).grab(new Element("div.bug", {
+ events: {
+ click: function(e) {
+ e.stopPropagation();
+ }
+ }
+ }).adopt(new Element("h1", {
+ text: "Report a bug"
+ }), new Element("span").adopt(new Element("span", {
+ text: "Read "
+ }), new Element("a.button", {
+ target: "_blank",
+ text: "the contributing guide",
+ href: "https://github.com/RuudBurger/CouchPotatoServer/blob/develop/contributing.md"
+ }), new Element("span", {
+ html: " before posting, then copy the text below and FILL IN the dots."
+ })), textarea = new Element("textarea", {
+ text: body
+ }), new Element("a.button", {
+ target: "_blank",
+ text: "Create a new issue on GitHub with the text above",
+ href: "https://github.com/RuudBurger/CouchPotatoServer/issues/new",
+ events: {
+ click: function(e) {
+ e.stop();
+ var body = textarea.get("value"), bdy = "?body=" + (body.length < 2e3 ? encodeURIComponent(body) : "Paste the text here"), win = window.open(e.target.get("href") + bdy, "_blank");
+ win.focus();
+ }
+ }
+ })));
+ overlay.inject(document.body);
+ },
+ getSelected: function() {
+ if (window.getSelection) return window.getSelection(); else if (document.getSelection) return document.getSelection(); else {
+ var selection = document.selection && document.selection.createRange();
+ if (selection.text) return selection.text;
+ }
+ return false;
+ },
+ spaceFill: function(number, width) {
+ if (number.toString().length >= width) return number;
+ return (new Array(width).join(" ") + number.toString()).substr(-width);
+ }
+});
+
+var Profile = new Class({
+ data: {},
+ types: [],
+ initialize: function(data) {
+ var self = this;
+ self.data = data;
+ self.types = [];
+ self.create();
+ self.el.addEvents({
+ "change:relay(select, input[type=checkbox])": self.save.bind(self, 0),
+ "keyup:relay(input[type=text])": self.save.bind(self, [ 300 ])
+ });
+ },
+ create: function() {
+ var self = this;
+ var data = self.data;
+ self.el = new Element("div.profile").adopt(self.delete_button = new Element("span.delete.icon2", {
+ events: {
+ click: self.del.bind(self)
+ }
+ }), new Element(".quality_label.ctrlHolder").adopt(new Element("label", {
+ text: "Name"
+ }), new Element("input", {
+ type: "text",
+ value: data.label,
+ placeholder: "Profile name"
+ })), new Element("div.qualities.ctrlHolder").adopt(new Element("label", {
+ text: "Search for"
+ }), self.type_container = new Element("ol.types"), new Element("div.formHint", {
+ html: "Search these qualities (2 minimum), from top to bottom. Use the checkbox, to stop searching after it found this quality."
+ })), new Element("div.wait_for.ctrlHolder").adopt(new Element("span", {
+ text: "Wait"
+ }), new Element("input.wait_for_input.xsmall", {
+ type: "text",
+ value: data.wait_for && data.wait_for.length > 0 ? data.wait_for[0] : 0
+ }), new Element("span", {
+ text: "day(s) for a better quality "
+ }), new Element("span.advanced", {
+ text: "and keep searching"
+ }), new Element("input.xsmall.stop_after_input.advanced", {
+ type: "text",
+ value: data.stop_after && data.stop_after.length > 0 ? data.stop_after[0] : 0
+ }), new Element("span.advanced", {
+ text: "day(s) for a better (checked) quality."
+ }), new Element("span.advanced", {
+ html: " Releases need a minimum score of"
+ }), new Element("input.advanced.xsmall.minimum_score_input", {
+ size: 4,
+ type: "text",
+ value: data.minimum_score || 1
+ })));
+ self.makeSortable();
+ if (data.qualities) {
+ data.types = [];
+ data.qualities.each(function(quality, nr) {
+ data.types.include({
+ quality: quality,
+ finish: data.finish[nr] || false,
+ "3d": data["3d"] ? data["3d"][nr] || false : false
+ });
+ });
+ }
+ if (data.types) data.types.each(self.addType.bind(self)); else self.delete_button.hide();
+ self.addType();
+ },
+ save: function(delay) {
+ var self = this;
+ if (self.save_timer) clearTimeout(self.save_timer);
+ self.save_timer = function() {
+ self.addType();
+ var data = self.getData();
+ if (data.types.length < 2) return; else self.delete_button.show();
+ Api.request("profile.save", {
+ data: self.getData(),
+ useSpinner: true,
+ spinnerOptions: {
+ target: self.el
+ },
+ onComplete: function(json) {
+ if (json.success) {
+ self.data = json.profile;
+ self.type_container.getElement("li:first-child input.finish[type=checkbox]").set("checked", true).getParent().addClass("checked");
+ }
+ }
+ });
+ }.delay(delay, self);
+ },
+ getData: function() {
+ var self = this;
+ var data = {
+ id: self.data._id,
+ label: self.el.getElement(".quality_label input").get("value"),
+ wait_for: self.el.getElement(".wait_for_input").get("value"),
+ stop_after: self.el.getElement(".stop_after_input").get("value"),
+ minimum_score: self.el.getElement(".minimum_score_input").get("value"),
+ types: []
+ };
+ Array.each(self.type_container.getElements(".type"), function(type) {
+ if (!type.hasClass("deleted") && type.getElement("select").get("value") != -1) data.types.include({
+ quality: type.getElement("select").get("value"),
+ finish: +type.getElement("input.finish[type=checkbox]").checked,
+ "3d": +type.getElement("input.3d[type=checkbox]").checked
+ });
+ });
+ return data;
+ },
+ addType: function(data) {
+ var self = this;
+ var has_empty = false;
+ self.types.each(function(type) {
+ if ($(type).hasClass("is_empty")) has_empty = true;
+ });
+ if (has_empty) return;
+ var t = new Profile.Type(data, {
+ onChange: self.save.bind(self, 0)
+ });
+ $(t).inject(self.type_container);
+ self.sortable.addItems($(t));
+ self.types.include(t);
+ },
+ getTypes: function() {
+ var self = this;
+ return self.types.filter(function(type) {
+ return type.get("quality");
+ });
+ },
+ del: function() {
+ var self = this;
+ var label = self.el.getElement(".quality_label input").get("value");
+ var qObj = new Question('Are you sure you want to delete "' + label + '"?', "Items using this profile, will be set to the default quality.", [ {
+ text: 'Delete "' + label + '"',
+ class: "delete",
+ events: {
+ click: function(e) {
+ e.preventDefault();
+ Api.request("profile.delete", {
+ data: {
+ id: self.data._id
+ },
+ useSpinner: true,
+ spinnerOptions: {
+ target: self.el
+ },
+ onComplete: function(json) {
+ if (json.success) {
+ qObj.close();
+ self.el.destroy();
+ } else {
+ alert(json.message);
+ }
+ }
+ });
+ }
+ }
+ }, {
+ text: "Cancel",
+ cancel: true
+ } ]);
+ },
+ makeSortable: function() {
+ var self = this;
+ self.sortable = new Sortables(self.type_container, {
+ revert: true,
+ handle: ".handle",
+ opacity: .5,
+ onComplete: self.save.bind(self, 300)
+ });
+ },
+ get: function(attr) {
+ return this.data[attr];
+ },
+ isCore: function() {
+ return this.data.core;
+ },
+ toElement: function() {
+ return this.el;
+ }
+});
+
+Profile.Type = new Class({
+ Implements: [ Events, Options ],
+ deleted: false,
+ initialize: function(data, options) {
+ var self = this;
+ self.setOptions(options);
+ self.data = data || {};
+ self.create();
+ self.addEvent("change", function() {
+ self.el[self.qualities.get("value") == "-1" ? "addClass" : "removeClass"]("is_empty");
+ self.el[Quality.getQuality(self.qualities.get("value")).allow_3d ? "addClass" : "removeClass"]("allow_3d");
+ self.deleted = self.qualities.get("value") == "-1";
+ });
+ },
+ create: function() {
+ var self = this;
+ var data = self.data;
+ self.el = new Element("li.type").adopt(new Element("span.quality_type.select_wrapper.icon-dropdown").grab(self.fillQualities()), self.finish_container = new Element("label.finish").adopt(self.finish = new Element("input.finish[type=checkbox]", {
+ checked: data.finish !== undefined ? data.finish : 1,
+ events: {
+ change: function() {
+ if (self.el == self.el.getParent().getElement(":first-child")) {
+ alert("Top quality always finishes the search");
+ return;
+ }
+ self.fireEvent("change");
+ }
+ }
+ }), new Element("span.check_label[text=finish]")), self["3d_container"] = new Element("label.threed").adopt(self["3d"] = new Element("input.3d[type=checkbox]", {
+ checked: data["3d"] !== undefined ? data["3d"] : 0,
+ events: {
+ change: function() {
+ self.fireEvent("change");
+ }
+ }
+ }), new Element("span.check_label[text=3D]")), new Element("span.delete.icon-cancel", {
+ events: {
+ click: self.del.bind(self)
+ }
+ }), new Element("span.handle.icon-handle"));
+ self.el[self.data.quality ? "removeClass" : "addClass"]("is_empty");
+ if (self.data.quality && Quality.getQuality(self.data.quality).allow_3d) self.el.addClass("allow_3d");
+ },
+ fillQualities: function() {
+ var self = this;
+ self.qualities = new Element("select", {
+ events: {
+ change: self.fireEvent.bind(self, "change")
+ }
+ }).grab(new Element("option", {
+ text: "+ Add another quality",
+ value: -1
+ }));
+ Object.each(Quality.qualities, function(q) {
+ new Element("option", {
+ text: q.label,
+ value: q.identifier,
+ "data-allow_3d": q.allow_3d
+ }).inject(self.qualities);
+ });
+ self.qualities.set("value", self.data.quality);
+ return self.qualities;
+ },
+ getData: function() {
+ var self = this;
+ return {
+ quality: self.qualities.get("value"),
+ finish: +self.finish.checked,
+ "3d": +self["3d"].checked
+ };
+ },
+ get: function(key) {
+ return this.data[key];
+ },
+ del: function() {
+ var self = this;
+ self.el.addClass("deleted");
+ self.el.hide();
+ self.deleted = true;
+ self.fireEvent("change");
+ },
+ toElement: function() {
+ return this.el;
+ }
+});
+
+var QualityBase = new Class({
+ tab: "",
+ content: "",
+ setup: function(data) {
+ var self = this;
+ self.qualities = data.qualities;
+ self.profiles_list = null;
+ self.profiles = [];
+ Array.each(data.profiles, self.createProfilesClass.bind(self));
+ App.addEvent("loadSettings", self.addSettings.bind(self));
+ },
+ getProfile: function(id) {
+ return this.profiles.filter(function(profile) {
+ return profile.data._id == id;
+ }).pick();
+ },
+ getActiveProfiles: function() {
+ return Array.filter(this.profiles, function(profile) {
+ return !profile.data.hide;
+ });
+ },
+ getQuality: function(identifier) {
+ try {
+ return this.qualities.filter(function(q) {
+ return q.identifier == identifier;
+ }).pick();
+ } catch (e) {}
+ return {};
+ },
+ addSettings: function() {
+ var self = this;
+ self.settings = App.getPage("Settings");
+ self.settings.addEvent("create", function() {
+ var tab = self.settings.createSubTab("profile", {
+ label: "Quality",
+ name: "profile",
+ subtab_label: "Qualities"
+ }, self.settings.tabs.searcher, "searcher");
+ self.tab = tab.tab;
+ self.content = tab.content;
+ self.createProfiles();
+ self.createProfileOrdering();
+ self.createSizes();
+ });
+ },
+ createProfiles: function() {
+ var self = this;
+ var non_core_profiles = Array.filter(self.profiles, function(profile) {
+ return !profile.isCore();
+ });
+ var count = non_core_profiles.length;
+ self.settings.createGroup({
+ label: "Quality Profiles",
+ description: "Create your own profiles with multiple qualities."
+ }).inject(self.content).adopt(self.profile_container = new Element("div.container"), new Element("a.add_new_profile", {
+ text: count > 0 ? "Create another quality profile" : "Click here to create a quality profile.",
+ events: {
+ click: function() {
+ var profile = self.createProfilesClass();
+ $(profile).inject(self.profile_container);
+ }
+ }
+ }));
+ Array.each(non_core_profiles, function(profile) {
+ $(profile).inject(self.profile_container);
+ });
+ },
+ createProfilesClass: function(data) {
+ var self = this;
+ data = data || {
+ id: randomString()
+ };
+ var profile = new Profile(data);
+ self.profiles.include(profile);
+ return profile;
+ },
+ createProfileOrdering: function() {
+ var self = this;
+ self.settings.createGroup({
+ label: "Profile Defaults",
+ description: "(Needs refresh '" + (App.isMac() ? "CMD+R" : "F5") + "' after editing)"
+ }).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) {
+ var check;
+ new Element("li", {
+ "data-id": profile.data._id
+ }).adopt(check = new Element("input[type=checkbox]", {
+ checked: !profile.data.hide,
+ events: {
+ change: self.saveProfileOrdering.bind(self)
+ }
+ }), new Element("span.profile_label", {
+ text: profile.data.label
+ }), new Element("span.handle.icon-handle")).inject(self.profiles_list);
+ });
+ var sorted_changed = false;
+ self.profile_sortable = new Sortables(self.profiles_list, {
+ revert: true,
+ handle: ".handle",
+ opacity: .5,
+ onSort: function() {
+ sorted_changed = true;
+ },
+ onComplete: function() {
+ if (sorted_changed) {
+ self.saveProfileOrdering();
+ sorted_changed = false;
+ }
+ }
+ });
+ },
+ saveProfileOrdering: function() {
+ var self = this, ids = [], hidden = [];
+ self.profiles_list.getElements("li").each(function(el, nr) {
+ ids.include(el.get("data-id"));
+ hidden[nr] = +!el.getElement("input[type=checkbox]").get("checked");
+ });
+ Api.request("profile.save_order", {
+ data: {
+ ids: ids,
+ hidden: hidden
+ }
+ });
+ },
+ createSizes: function() {
+ var self = this;
+ var group = self.settings.createGroup({
+ label: "Sizes",
+ description: "Edit the minimal and maximum sizes (in MB) for each quality.",
+ advanced: true,
+ name: "sizes"
+ }).inject(self.content);
+ new Element("div.item.head.ctrlHolder").adopt(new Element("span.label", {
+ text: "Quality"
+ }), new Element("span.min", {
+ text: "Min"
+ }), new Element("span.max", {
+ text: "Max"
+ })).inject(group);
+ Array.each(self.qualities, function(quality) {
+ new Element("div.ctrlHolder.item").adopt(new Element("span.label", {
+ text: quality.label
+ }), new Element("input.min[type=text]", {
+ value: quality.size_min,
+ events: {
+ keyup: function(e) {
+ self.changeSize(quality.identifier, "size_min", e.target.get("value"));
+ }
+ }
+ }), new Element("input.max[type=text]", {
+ value: quality.size_max,
+ events: {
+ keyup: function(e) {
+ self.changeSize(quality.identifier, "size_max", e.target.get("value"));
+ }
+ }
+ })).inject(group);
+ });
+ },
+ size_timer: {},
+ changeSize: function(identifier, type, value) {
+ var self = this;
+ if (self.size_timer[identifier + type]) clearTimeout(self.size_timer[identifier + type]);
+ self.size_timer[identifier + type] = function() {
+ Api.request("quality.size.save", {
+ data: {
+ identifier: identifier,
+ value_type: type,
+ value: value
+ }
+ });
+ }.delay(300);
+ }
+});
+
+window.Quality = new QualityBase();
+
+Page.Userscript = new Class({
+ Extends: PageBase,
+ order: 80,
+ name: "userscript",
+ has_tab: false,
+ options: {
+ onOpened: function() {
+ App.fireEvent("unload");
+ App.getBlock("header").hide();
+ }
+ },
+ indexAction: function() {
+ var self = this;
+ self.content.grab(self.frame = new Element("div.frame.loading", {
+ text: "Loading..."
+ }));
+ var url = window.location.href.split("url=")[1];
+ Api.request("userscript.add_via_url", {
+ data: {
+ url: url
+ },
+ onComplete: function(json) {
+ self.frame.empty();
+ self.frame.removeClass("loading");
+ if (json.error) self.frame.set("html", json.error); else {
+ var item = new BlockSearchMovieItem(json.movie);
+ self.frame.adopt(item);
+ item.showOptions();
+ }
+ }
+ });
+ }
+});
+
+var UserscriptSettingTab = new Class({
+ tab: "",
+ content: "",
+ initialize: function() {
+ var self = this;
+ App.addEvent("loadSettings", self.addSettings.bind(self));
+ },
+ addSettings: function() {
+ var self = this;
+ self.settings = App.getPage("Settings");
+ self.settings.addEvent("create", function() {
+ var host_url = window.location.protocol + "//" + window.location.host;
+ self.settings.createGroup({
+ name: "userscript",
+ label: "Install the browser extension or bookmarklet",
+ description: "Easily add movies via imdb.com, appletrailers and more"
+ }).inject(self.settings.tabs.automation.content, "top").adopt(new Element("a.userscript.button", {
+ text: "Install extension",
+ href: "https://couchpota.to/extension/",
+ target: "_blank"
+ }), new Element("span.or[text=or]"), new Element("span.bookmarklet").adopt(new Element("a.button.green", {
+ text: "+CouchPotato",
+ href: "javascript:void((function(){var e=document.createElement('script');e.setAttribute('type','text/javascript');e.setAttribute('charset','UTF-8');e.setAttribute('src','" + host_url + Api.createUrl("userscript.bookmark") + "?host=" + encodeURI(host_url + Api.createUrl("userscript.get") + randomString() + "/") + "&r='+Math.random()*99999999);document.body.appendChild(e)})());",
+ target: "",
+ events: {
+ click: function(e) {
+ e.stop();
+ alert("Drag it to your bookmark ;)");
+ }
+ }
+ }), new Element("span", {
+ text: "⇽ Drag this to your bookmarks"
+ })), new Element("img", {
+ src: "https://couchpota.to/media/images/userscript.gif"
+ }));
+ });
+ }
+});
+
+window.addEvent("domready", function() {
+ new UserscriptSettingTab();
+});
+
+window.addEvent("load", function() {
+ var your_version = $(document.body).get("data-userscript_version"), latest_version = App.getOption("userscript_version") || "", key = "cp_version_check", checked_already = Cookie.read(key);
+ if (your_version && your_version < latest_version && checked_already < latest_version) {
+ if (confirm("Update to the latest Userscript?\nYour version: " + your_version + ", new version: " + latest_version)) {
+ document.location = Api.createUrl("userscript.get") + randomString() + "/couchpotato.user.js";
+ }
+ Cookie.write(key, latest_version, {
+ duration: 100
+ });
+ }
+});
+
+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").grab(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);
+ 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).grab(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);
+ 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).grab(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();
+ });
+ self.el.getElement(".advanced_toggle").destroy();
+ self.el.getElement(".section_nzb").hide();
+ }
+});
\ No newline at end of file
diff --git a/couchpotato/static/scripts/combined.vendor.min.js b/couchpotato/static/scripts/combined.vendor.min.js
new file mode 100644
index 0000000..61ad0a5
--- /dev/null
+++ b/couchpotato/static/scripts/combined.vendor.min.js
@@ -0,0 +1,9028 @@
+(function() {
+ this.MooTools = {
+ version: "1.5.1",
+ build: "0542c135fdeb7feed7d9917e01447a408f22c876"
+ };
+ var typeOf = this.typeOf = function(item) {
+ if (item == null) return "null";
+ if (item.$family != null) return item.$family();
+ if (item.nodeName) {
+ if (item.nodeType == 1) return "element";
+ if (item.nodeType == 3) return /\S/.test(item.nodeValue) ? "textnode" : "whitespace";
+ } else if (typeof item.length == "number") {
+ if ("callee" in item) return "arguments";
+ if ("item" in item) return "collection";
+ }
+ return typeof item;
+ };
+ var instanceOf = this.instanceOf = function(item, object) {
+ if (item == null) return false;
+ var constructor = item.$constructor || item.constructor;
+ while (constructor) {
+ if (constructor === object) return true;
+ constructor = constructor.parent;
+ }
+ if (!item.hasOwnProperty) return false;
+ return item instanceof object;
+ };
+ var Function = this.Function;
+ var enumerables = true;
+ for (var i in {
+ toString: 1
+ }) enumerables = null;
+ if (enumerables) enumerables = [ "hasOwnProperty", "valueOf", "isPrototypeOf", "propertyIsEnumerable", "toLocaleString", "toString", "constructor" ];
+ Function.prototype.overloadSetter = function(usePlural) {
+ var self = this;
+ return function(a, b) {
+ if (a == null) return this;
+ if (usePlural || typeof a != "string") {
+ for (var k in a) self.call(this, k, a[k]);
+ if (enumerables) for (var i = enumerables.length; i--; ) {
+ k = enumerables[i];
+ if (a.hasOwnProperty(k)) self.call(this, k, a[k]);
+ }
+ } else {
+ self.call(this, a, b);
+ }
+ return this;
+ };
+ };
+ Function.prototype.overloadGetter = function(usePlural) {
+ var self = this;
+ return function(a) {
+ var args, result;
+ if (typeof a != "string") args = a; else if (arguments.length > 1) args = arguments; else if (usePlural) args = [ a ];
+ if (args) {
+ result = {};
+ for (var i = 0; i < args.length; i++) result[args[i]] = self.call(this, args[i]);
+ } else {
+ result = self.call(this, a);
+ }
+ return result;
+ };
+ };
+ Function.prototype.extend = function(key, value) {
+ this[key] = value;
+ }.overloadSetter();
+ Function.prototype.implement = function(key, value) {
+ this.prototype[key] = value;
+ }.overloadSetter();
+ var slice = Array.prototype.slice;
+ Function.from = function(item) {
+ return typeOf(item) == "function" ? item : function() {
+ return item;
+ };
+ };
+ Array.from = function(item) {
+ if (item == null) return [];
+ return Type.isEnumerable(item) && typeof item != "string" ? typeOf(item) == "array" ? item : slice.call(item) : [ item ];
+ };
+ Number.from = function(item) {
+ var number = parseFloat(item);
+ return isFinite(number) ? number : null;
+ };
+ String.from = function(item) {
+ return item + "";
+ };
+ Function.implement({
+ hide: function() {
+ this.$hidden = true;
+ return this;
+ },
+ protect: function() {
+ this.$protected = true;
+ return this;
+ }
+ });
+ var Type = this.Type = function(name, object) {
+ if (name) {
+ var lower = name.toLowerCase();
+ var typeCheck = function(item) {
+ return typeOf(item) == lower;
+ };
+ Type["is" + name] = typeCheck;
+ if (object != null) {
+ object.prototype.$family = function() {
+ return lower;
+ }.hide();
+ }
+ }
+ if (object == null) return null;
+ object.extend(this);
+ object.$constructor = Type;
+ object.prototype.$constructor = object;
+ return object;
+ };
+ var toString = Object.prototype.toString;
+ Type.isEnumerable = function(item) {
+ return item != null && typeof item.length == "number" && toString.call(item) != "[object Function]";
+ };
+ var hooks = {};
+ var hooksOf = function(object) {
+ var type = typeOf(object.prototype);
+ return hooks[type] || (hooks[type] = []);
+ };
+ var implement = function(name, method) {
+ if (method && method.$hidden) return;
+ var hooks = hooksOf(this);
+ for (var i = 0; i < hooks.length; i++) {
+ var hook = hooks[i];
+ if (typeOf(hook) == "type") implement.call(hook, name, method); else hook.call(this, name, method);
+ }
+ var previous = this.prototype[name];
+ if (previous == null || !previous.$protected) this.prototype[name] = method;
+ if (this[name] == null && typeOf(method) == "function") extend.call(this, name, function(item) {
+ return method.apply(item, slice.call(arguments, 1));
+ });
+ };
+ var extend = function(name, method) {
+ if (method && method.$hidden) return;
+ var previous = this[name];
+ if (previous == null || !previous.$protected) this[name] = method;
+ };
+ Type.implement({
+ implement: implement.overloadSetter(),
+ extend: extend.overloadSetter(),
+ alias: function(name, existing) {
+ implement.call(this, name, this.prototype[existing]);
+ }.overloadSetter(),
+ mirror: function(hook) {
+ hooksOf(this).push(hook);
+ return this;
+ }
+ });
+ new Type("Type", Type);
+ var force = function(name, object, methods) {
+ var isType = object != Object, prototype = object.prototype;
+ if (isType) object = new Type(name, object);
+ for (var i = 0, l = methods.length; i < l; i++) {
+ var key = methods[i], generic = object[key], proto = prototype[key];
+ if (generic) generic.protect();
+ if (isType && proto) object.implement(key, proto.protect());
+ }
+ if (isType) {
+ var methodsEnumerable = prototype.propertyIsEnumerable(methods[0]);
+ object.forEachMethod = function(fn) {
+ if (!methodsEnumerable) for (var i = 0, l = methods.length; i < l; i++) {
+ fn.call(prototype, prototype[methods[i]], methods[i]);
+ }
+ for (var key in prototype) fn.call(prototype, prototype[key], key);
+ };
+ }
+ return force;
+ };
+ force("String", String, [ "charAt", "charCodeAt", "concat", "contains", "indexOf", "lastIndexOf", "match", "quote", "replace", "search", "slice", "split", "substr", "substring", "trim", "toLowerCase", "toUpperCase" ])("Array", Array, [ "pop", "push", "reverse", "shift", "sort", "splice", "unshift", "concat", "join", "slice", "indexOf", "lastIndexOf", "filter", "forEach", "every", "map", "some", "reduce", "reduceRight" ])("Number", Number, [ "toExponential", "toFixed", "toLocaleString", "toPrecision" ])("Function", Function, [ "apply", "call", "bind" ])("RegExp", RegExp, [ "exec", "test" ])("Object", Object, [ "create", "defineProperty", "defineProperties", "keys", "getPrototypeOf", "getOwnPropertyDescriptor", "getOwnPropertyNames", "preventExtensions", "isExtensible", "seal", "isSealed", "freeze", "isFrozen" ])("Date", Date, [ "now" ]);
+ Object.extend = extend.overloadSetter();
+ Date.extend("now", function() {
+ return +new Date();
+ });
+ new Type("Boolean", Boolean);
+ Number.prototype.$family = function() {
+ return isFinite(this) ? "number" : "null";
+ }.hide();
+ Number.extend("random", function(min, max) {
+ return Math.floor(Math.random() * (max - min + 1) + min);
+ });
+ var hasOwnProperty = Object.prototype.hasOwnProperty;
+ Object.extend("forEach", function(object, fn, bind) {
+ for (var key in object) {
+ if (hasOwnProperty.call(object, key)) fn.call(bind, object[key], key, object);
+ }
+ });
+ Object.each = Object.forEach;
+ Array.implement({
+ forEach: function(fn, bind) {
+ for (var i = 0, l = this.length; i < l; i++) {
+ if (i in this) fn.call(bind, this[i], i, this);
+ }
+ },
+ each: function(fn, bind) {
+ Array.forEach(this, fn, bind);
+ return this;
+ }
+ });
+ var cloneOf = function(item) {
+ switch (typeOf(item)) {
+ case "array":
+ return item.clone();
+
+ case "object":
+ return Object.clone(item);
+
+ default:
+ return item;
+ }
+ };
+ Array.implement("clone", function() {
+ var i = this.length, clone = new Array(i);
+ while (i--) clone[i] = cloneOf(this[i]);
+ return clone;
+ });
+ var mergeOne = function(source, key, current) {
+ switch (typeOf(current)) {
+ case "object":
+ if (typeOf(source[key]) == "object") Object.merge(source[key], current); else source[key] = Object.clone(current);
+ break;
+
+ case "array":
+ source[key] = current.clone();
+ break;
+
+ default:
+ source[key] = current;
+ }
+ return source;
+ };
+ Object.extend({
+ merge: function(source, k, v) {
+ if (typeOf(k) == "string") return mergeOne(source, k, v);
+ for (var i = 1, l = arguments.length; i < l; i++) {
+ var object = arguments[i];
+ for (var key in object) mergeOne(source, key, object[key]);
+ }
+ return source;
+ },
+ clone: function(object) {
+ var clone = {};
+ for (var key in object) clone[key] = cloneOf(object[key]);
+ return clone;
+ },
+ append: function(original) {
+ for (var i = 1, l = arguments.length; i < l; i++) {
+ var extended = arguments[i] || {};
+ for (var key in extended) original[key] = extended[key];
+ }
+ return original;
+ }
+ });
+ [ "Object", "WhiteSpace", "TextNode", "Collection", "Arguments" ].each(function(name) {
+ new Type(name);
+ });
+ var UID = Date.now();
+ String.extend("uniqueID", function() {
+ return (UID++).toString(36);
+ });
+})();
+
+Array.implement({
+ every: function(fn, bind) {
+ for (var i = 0, l = this.length >>> 0; i < l; i++) {
+ if (i in this && !fn.call(bind, this[i], i, this)) return false;
+ }
+ return true;
+ },
+ filter: function(fn, bind) {
+ var results = [];
+ for (var value, i = 0, l = this.length >>> 0; i < l; i++) if (i in this) {
+ value = this[i];
+ if (fn.call(bind, value, i, this)) results.push(value);
+ }
+ return results;
+ },
+ indexOf: function(item, from) {
+ var length = this.length >>> 0;
+ for (var i = from < 0 ? Math.max(0, length + from) : from || 0; i < length; i++) {
+ if (this[i] === item) return i;
+ }
+ return -1;
+ },
+ map: function(fn, bind) {
+ var length = this.length >>> 0, results = Array(length);
+ for (var i = 0; i < length; i++) {
+ if (i in this) results[i] = fn.call(bind, this[i], i, this);
+ }
+ return results;
+ },
+ some: function(fn, bind) {
+ for (var i = 0, l = this.length >>> 0; i < l; i++) {
+ if (i in this && fn.call(bind, this[i], i, this)) return true;
+ }
+ return false;
+ },
+ clean: function() {
+ return this.filter(function(item) {
+ return item != null;
+ });
+ },
+ invoke: function(methodName) {
+ var args = Array.slice(arguments, 1);
+ return this.map(function(item) {
+ return item[methodName].apply(item, args);
+ });
+ },
+ associate: function(keys) {
+ var obj = {}, length = Math.min(this.length, keys.length);
+ for (var i = 0; i < length; i++) obj[keys[i]] = this[i];
+ return obj;
+ },
+ link: function(object) {
+ var result = {};
+ for (var i = 0, l = this.length; i < l; i++) {
+ for (var key in object) {
+ if (object[key](this[i])) {
+ result[key] = this[i];
+ delete object[key];
+ break;
+ }
+ }
+ }
+ return result;
+ },
+ contains: function(item, from) {
+ return this.indexOf(item, from) != -1;
+ },
+ append: function(array) {
+ this.push.apply(this, array);
+ return this;
+ },
+ getLast: function() {
+ return this.length ? this[this.length - 1] : null;
+ },
+ getRandom: function() {
+ return this.length ? this[Number.random(0, this.length - 1)] : null;
+ },
+ include: function(item) {
+ if (!this.contains(item)) this.push(item);
+ return this;
+ },
+ combine: function(array) {
+ for (var i = 0, l = array.length; i < l; i++) this.include(array[i]);
+ return this;
+ },
+ erase: function(item) {
+ for (var i = this.length; i--; ) {
+ if (this[i] === item) this.splice(i, 1);
+ }
+ return this;
+ },
+ empty: function() {
+ this.length = 0;
+ return this;
+ },
+ flatten: function() {
+ var array = [];
+ for (var i = 0, l = this.length; i < l; i++) {
+ var type = typeOf(this[i]);
+ if (type == "null") continue;
+ array = array.concat(type == "array" || type == "collection" || type == "arguments" || instanceOf(this[i], Array) ? Array.flatten(this[i]) : this[i]);
+ }
+ return array;
+ },
+ pick: function() {
+ for (var i = 0, l = this.length; i < l; i++) {
+ if (this[i] != null) return this[i];
+ }
+ return null;
+ },
+ hexToRgb: function(array) {
+ if (this.length != 3) return null;
+ var rgb = this.map(function(value) {
+ if (value.length == 1) value += value;
+ return parseInt(value, 16);
+ });
+ return array ? rgb : "rgb(" + rgb + ")";
+ },
+ rgbToHex: function(array) {
+ if (this.length < 3) return null;
+ if (this.length == 4 && this[3] == 0 && !array) return "transparent";
+ var hex = [];
+ for (var i = 0; i < 3; i++) {
+ var bit = (this[i] - 0).toString(16);
+ hex.push(bit.length == 1 ? "0" + bit : bit);
+ }
+ return array ? hex : "#" + hex.join("");
+ }
+});
+
+String.implement({
+ contains: function(string, index) {
+ return (index ? String(this).slice(index) : String(this)).indexOf(string) > -1;
+ },
+ test: function(regex, params) {
+ return (typeOf(regex) == "regexp" ? regex : new RegExp("" + regex, params)).test(this);
+ },
+ trim: function() {
+ return String(this).replace(/^\s+|\s+$/g, "");
+ },
+ clean: function() {
+ return String(this).replace(/\s+/g, " ").trim();
+ },
+ camelCase: function() {
+ return String(this).replace(/-\D/g, function(match) {
+ return match.charAt(1).toUpperCase();
+ });
+ },
+ hyphenate: function() {
+ return String(this).replace(/[A-Z]/g, function(match) {
+ return "-" + match.charAt(0).toLowerCase();
+ });
+ },
+ capitalize: function() {
+ return String(this).replace(/\b[a-z]/g, function(match) {
+ return match.toUpperCase();
+ });
+ },
+ escapeRegExp: function() {
+ return String(this).replace(/([-.*+?^${}()|[\]\/\\])/g, "\\$1");
+ },
+ toInt: function(base) {
+ return parseInt(this, base || 10);
+ },
+ toFloat: function() {
+ return parseFloat(this);
+ },
+ hexToRgb: function(array) {
+ var hex = String(this).match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
+ return hex ? hex.slice(1).hexToRgb(array) : null;
+ },
+ rgbToHex: function(array) {
+ var rgb = String(this).match(/\d{1,3}/g);
+ return rgb ? rgb.rgbToHex(array) : null;
+ },
+ substitute: function(object, regexp) {
+ return String(this).replace(regexp || /\\?\{([^{}]+)\}/g, function(match, name) {
+ if (match.charAt(0) == "\\") return match.slice(1);
+ return object[name] != null ? object[name] : "";
+ });
+ }
+});
+
+Function.extend({
+ attempt: function() {
+ for (var i = 0, l = arguments.length; i < l; i++) {
+ try {
+ return arguments[i]();
+ } catch (e) {}
+ }
+ return null;
+ }
+});
+
+Function.implement({
+ attempt: function(args, bind) {
+ try {
+ return this.apply(bind, Array.from(args));
+ } catch (e) {}
+ return null;
+ },
+ bind: function(that) {
+ var self = this, args = arguments.length > 1 ? Array.slice(arguments, 1) : null, F = function() {};
+ var bound = function() {
+ var context = that, length = arguments.length;
+ if (this instanceof bound) {
+ F.prototype = self.prototype;
+ context = new F();
+ }
+ var result = !args && !length ? self.call(context) : self.apply(context, args && length ? args.concat(Array.slice(arguments)) : args || arguments);
+ return context == that ? result : context;
+ };
+ return bound;
+ },
+ pass: function(args, bind) {
+ var self = this;
+ if (args != null) args = Array.from(args);
+ return function() {
+ return self.apply(bind, args || arguments);
+ };
+ },
+ delay: function(delay, bind, args) {
+ return setTimeout(this.pass(args == null ? [] : args, bind), delay);
+ },
+ periodical: function(periodical, bind, args) {
+ return setInterval(this.pass(args == null ? [] : args, bind), periodical);
+ }
+});
+
+Number.implement({
+ limit: function(min, max) {
+ return Math.min(max, Math.max(min, this));
+ },
+ round: function(precision) {
+ precision = Math.pow(10, precision || 0).toFixed(precision < 0 ? -precision : 0);
+ return Math.round(this * precision) / precision;
+ },
+ times: function(fn, bind) {
+ for (var i = 0; i < this; i++) fn.call(bind, i, this);
+ },
+ toFloat: function() {
+ return parseFloat(this);
+ },
+ toInt: function(base) {
+ return parseInt(this, base || 10);
+ }
+});
+
+Number.alias("each", "times");
+
+(function(math) {
+ var methods = {};
+ math.each(function(name) {
+ if (!Number[name]) methods[name] = function() {
+ return Math[name].apply(null, [ this ].concat(Array.from(arguments)));
+ };
+ });
+ Number.implement(methods);
+})([ "abs", "acos", "asin", "atan", "atan2", "ceil", "cos", "exp", "floor", "log", "max", "min", "pow", "sin", "sqrt", "tan" ]);
+
+(function() {
+ var Class = this.Class = new Type("Class", function(params) {
+ if (instanceOf(params, Function)) params = {
+ initialize: params
+ };
+ var newClass = function() {
+ reset(this);
+ if (newClass.$prototyping) return this;
+ this.$caller = null;
+ var value = this.initialize ? this.initialize.apply(this, arguments) : this;
+ this.$caller = this.caller = null;
+ return value;
+ }.extend(this).implement(params);
+ newClass.$constructor = Class;
+ newClass.prototype.$constructor = newClass;
+ newClass.prototype.parent = parent;
+ return newClass;
+ });
+ var parent = function() {
+ if (!this.$caller) throw new Error('The method "parent" cannot be called.');
+ var name = this.$caller.$name, parent = this.$caller.$owner.parent, previous = parent ? parent.prototype[name] : null;
+ if (!previous) throw new Error('The method "' + name + '" has no parent.');
+ return previous.apply(this, arguments);
+ };
+ var reset = function(object) {
+ for (var key in object) {
+ var value = object[key];
+ switch (typeOf(value)) {
+ case "object":
+ var F = function() {};
+ F.prototype = value;
+ object[key] = reset(new F());
+ break;
+
+ case "array":
+ object[key] = value.clone();
+ break;
+ }
+ }
+ return object;
+ };
+ var wrap = function(self, key, method) {
+ if (method.$origin) method = method.$origin;
+ var wrapper = function() {
+ if (method.$protected && this.$caller == null) throw new Error('The method "' + key + '" cannot be called.');
+ var caller = this.caller, current = this.$caller;
+ this.caller = current;
+ this.$caller = wrapper;
+ var result = method.apply(this, arguments);
+ this.$caller = current;
+ this.caller = caller;
+ return result;
+ }.extend({
+ $owner: self,
+ $origin: method,
+ $name: key
+ });
+ return wrapper;
+ };
+ var implement = function(key, value, retain) {
+ if (Class.Mutators.hasOwnProperty(key)) {
+ value = Class.Mutators[key].call(this, value);
+ if (value == null) return this;
+ }
+ if (typeOf(value) == "function") {
+ if (value.$hidden) return this;
+ this.prototype[key] = retain ? value : wrap(this, key, value);
+ } else {
+ Object.merge(this.prototype, key, value);
+ }
+ return this;
+ };
+ var getInstance = function(klass) {
+ klass.$prototyping = true;
+ var proto = new klass();
+ delete klass.$prototyping;
+ return proto;
+ };
+ Class.implement("implement", implement.overloadSetter());
+ Class.Mutators = {
+ Extends: function(parent) {
+ this.parent = parent;
+ this.prototype = getInstance(parent);
+ },
+ Implements: function(items) {
+ Array.from(items).each(function(item) {
+ var instance = new item();
+ for (var key in instance) implement.call(this, key, instance[key], true);
+ }, this);
+ }
+ };
+})();
+
+(function() {
+ this.Chain = new Class({
+ $chain: [],
+ chain: function() {
+ this.$chain.append(Array.flatten(arguments));
+ return this;
+ },
+ callChain: function() {
+ return this.$chain.length ? this.$chain.shift().apply(this, arguments) : false;
+ },
+ clearChain: function() {
+ this.$chain.empty();
+ return this;
+ }
+ });
+ var removeOn = function(string) {
+ return string.replace(/^on([A-Z])/, function(full, first) {
+ return first.toLowerCase();
+ });
+ };
+ this.Events = new Class({
+ $events: {},
+ addEvent: function(type, fn, internal) {
+ type = removeOn(type);
+ this.$events[type] = (this.$events[type] || []).include(fn);
+ if (internal) fn.internal = true;
+ return this;
+ },
+ addEvents: function(events) {
+ for (var type in events) this.addEvent(type, events[type]);
+ return this;
+ },
+ fireEvent: function(type, args, delay) {
+ type = removeOn(type);
+ var events = this.$events[type];
+ if (!events) return this;
+ args = Array.from(args);
+ events.each(function(fn) {
+ if (delay) fn.delay(delay, this, args); else fn.apply(this, args);
+ }, this);
+ return this;
+ },
+ removeEvent: function(type, fn) {
+ type = removeOn(type);
+ var events = this.$events[type];
+ if (events && !fn.internal) {
+ var index = events.indexOf(fn);
+ if (index != -1) delete events[index];
+ }
+ return this;
+ },
+ removeEvents: function(events) {
+ var type;
+ if (typeOf(events) == "object") {
+ for (type in events) this.removeEvent(type, events[type]);
+ return this;
+ }
+ if (events) events = removeOn(events);
+ for (type in this.$events) {
+ if (events && events != type) continue;
+ var fns = this.$events[type];
+ for (var i = fns.length; i--; ) if (i in fns) {
+ this.removeEvent(type, fns[i]);
+ }
+ }
+ return this;
+ }
+ });
+ this.Options = new Class({
+ setOptions: function() {
+ var options = this.options = Object.merge.apply(null, [ {}, this.options ].append(arguments));
+ if (this.addEvent) for (var option in options) {
+ if (typeOf(options[option]) != "function" || !/^on[A-Z]/.test(option)) continue;
+ this.addEvent(option, options[option]);
+ delete options[option];
+ }
+ return this;
+ }
+ });
+})();
+
+(function() {
+ var document = this.document;
+ var window = document.window = this;
+ var parse = function(ua, platform) {
+ ua = ua.toLowerCase();
+ platform = platform ? platform.toLowerCase() : "";
+ var UA = ua.match(/(opera|ie|firefox|chrome|trident|crios|version)[\s\/:]([\w\d\.]+)?.*?(safari|(?:rv[\s\/:]|version[\s\/:])([\w\d\.]+)|$)/) || [ null, "unknown", 0 ];
+ if (UA[1] == "trident") {
+ UA[1] = "ie";
+ if (UA[4]) UA[2] = UA[4];
+ } else if (UA[1] == "crios") {
+ UA[1] = "chrome";
+ }
+ platform = ua.match(/ip(?:ad|od|hone)/) ? "ios" : (ua.match(/(?:webos|android)/) || platform.match(/mac|win|linux/) || [ "other" ])[0];
+ if (platform == "win") platform = "windows";
+ return {
+ extend: Function.prototype.extend,
+ name: UA[1] == "version" ? UA[3] : UA[1],
+ version: parseFloat(UA[1] == "opera" && UA[4] ? UA[4] : UA[2]),
+ platform: platform
+ };
+ };
+ var Browser = this.Browser = parse(navigator.userAgent, navigator.platform);
+ if (Browser.name == "ie") {
+ Browser.version = document.documentMode;
+ }
+ Browser.extend({
+ Features: {
+ xpath: !!document.evaluate,
+ air: !!window.runtime,
+ query: !!document.querySelector,
+ json: !!window.JSON
+ },
+ parseUA: parse
+ });
+ Browser.Request = function() {
+ var XMLHTTP = function() {
+ return new XMLHttpRequest();
+ };
+ var MSXML2 = function() {
+ return new ActiveXObject("MSXML2.XMLHTTP");
+ };
+ var MSXML = function() {
+ return new ActiveXObject("Microsoft.XMLHTTP");
+ };
+ return Function.attempt(function() {
+ XMLHTTP();
+ return XMLHTTP;
+ }, function() {
+ MSXML2();
+ return MSXML2;
+ }, function() {
+ MSXML();
+ return MSXML;
+ });
+ }();
+ Browser.Features.xhr = !!Browser.Request;
+ Browser.exec = function(text) {
+ if (!text) return text;
+ if (window.execScript) {
+ window.execScript(text);
+ } else {
+ var script = document.createElement("script");
+ script.setAttribute("type", "text/javascript");
+ script.text = text;
+ document.head.appendChild(script);
+ document.head.removeChild(script);
+ }
+ return text;
+ };
+ String.implement("stripScripts", function(exec) {
+ var scripts = "";
+ var text = this.replace(/
+
{% end %}
- {% for url in fireEvent('clientscript.get_scripts', as_html = True, location = 'head', single = True) %}
- {% end %}
- {% for url in fireEvent('clientscript.get_styles', as_html = True, location = 'head', single = True) %}
- {% end %}
-
-
+ {% if Env.get('dev') %}
+
+ {% end %}
{% end %}
+ {% for url in fireEvent('clientscript.get_styles', single = True) %}
+ {% end %}
+ {% for url in fireEvent('clientscript.get_scripts', single = True) %}{% if 'combined.plugins' not in url %}
+ {% end %}{% end %}
-
+ {% if Env.get('dev') %}
+
+ {% end %}
CouchPotato
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)
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)
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
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..0a7bc02
--- /dev/null
+++ b/package.json
@@ -0,0 +1,26 @@
+{
+ "name": "couchpotato_develop",
+ "repository": {
+ "type": "git",
+ "url": ""
+ },
+ "scripts": {
+ "start": "grunt"
+ },
+ "dependencies": {},
+ "devDependencies": {
+ "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",
+ "grunt-contrib-uglify": "~0.9.1",
+ "grunt-contrib-watch": "~0.6.1",
+ "grunt-shell-spawn": "^0.3.8",
+ "jit-grunt": "^0.9.1",
+ "jshint-stylish": "^2.0.1",
+ "time-grunt": "^1.2.1"
+ }
+}