Let's be better than we are

Should fix some padding issues, optimize theme editting, and a
bit of other stuff that's hard to remember.
This commit is contained in:
Zixaphir 2013-06-21 00:11:10 -07:00
parent 6ddfc0d5a6
commit 27c6f07913
11 changed files with 161 additions and 1302 deletions

View File

@ -1,5 +1,5 @@
/*
* appchan x - Version 2.1.3 - 2013-06-20
* appchan x - Version 2.1.3 - 2013-06-21
*
* Licensed under the MIT license.
* https://github.com/zixaphir/appchan-x/blob/master/LICENSE

View File

@ -18,7 +18,7 @@
// ==/UserScript==
/*
* appchan x - Version 2.1.3 - 2013-06-20
* appchan x - Version 2.1.3 - 2013-06-21
*
* Licensed under the MIT license.
* https://github.com/zixaphir/appchan-x/blob/master/LICENSE
@ -11122,8 +11122,7 @@
val = evt.target.result;
el.previousSibling.value = val;
editMascot.image = val;
return Style.addStyle();
return editMascot.image = val;
};
return reader.readAsDataURL(file);
},
@ -11185,7 +11184,6 @@
Conf['editMode'] = false;
editMascot = {};
$.rm($.id('mascotConf'));
Style.addStyle();
return Settings.open("Mascots");
},
importMascot: function(evt) {
@ -11364,7 +11362,8 @@
$.asap((function() {
return d.body;
}), this.asapInit);
return $.ready(this.readyInit);
$.on(window, "resize", Style.padding);
return $.on(doc, '4chanXInitFinished', this.readyInit);
},
asapInit: function() {
var cat, hyphenated, name, setting, title, _ref;
@ -11427,23 +11426,15 @@
}
},
readyInit: function() {
if (!$.id('navtopright')) {
return;
}
return setTimeout(function() {
var exLink;
var exLink;
Style.padding.nav = Header.bar;
Style.padding.pages = $(".pagelist", d.body);
Style.padding();
$.on(window, "resize", Style.padding);
Style.iconPositions();
if (exLink = $("#navtopright .exlinksOptionsLink", d.body)) {
return $.on(exLink, "click", function() {
return setTimeout(Rice.nodes, 100);
});
}
}, 500);
Style.padding();
Style.iconPositions();
if (exLink = $("#navtopright .exlinksOptionsLink", d.body)) {
return $.on(exLink, "click", function() {
return setTimeout(Rice.nodes, 100);
});
}
},
setup: function() {
var theme;
@ -11503,10 +11494,7 @@
delete Style.headCount;
return delete Style.cleanup;
},
addStyle: function(theme) {
if (!theme) {
theme = Themes[Conf['theme']] || Themes['Yotsuba B'];
}
addStyle: function() {
Style.dynamicCSS.textContent = Style.dynamic();
Style.iconPositions();
return Style.padding();
@ -11618,9 +11606,11 @@
padding: function() {
var css;
css = "body {\npadding-bottom: 1px;\n}\n.fourchan-ss-navigation.fixed.top:not(.autohide) body::before {\ntop: " + Style.padding.nav.offsetHeight + "px;\n}\n.fourchan-ss-navigation.fixed.bottom:not(.autohide) body::before {\nbottom: " + Style.padding.nav.offsetHeight + "px;\n}\n.fourchan-ss-navigation.top:not(.autohide) body {\npadding-top: " + (Style.padding.nav.offsetHeight + 1) + "px;\n}\n.fourchan-ss-navigation.bottom:not(.autohide) body {\npadding-bottom: " + (Style.padding.nav.offsetHeight + 1) + "px;\"\n}";
Style.padding.nav = Header.bar;
Style.padding.pages = $('.pagelist', d.body);
css = "body {\npadding-bottom: 1px;\n}\n.fourchan-ss-navigation.fixed.top:not(.autohide) body::before {\ntop: " + Style.padding.nav.offsetHeight + "px;\n}\n.fourchan-ss-navigation.fixed.bottom:not(.autohide) body::before {\nbottom: " + Style.padding.nav.offsetHeight + "px;\n}\n.top:not(.autohide) body {\npadding-top: " + (Style.padding.nav.offsetHeight + 1) + "px;\n}\n.bottom:not(.autohide) body {\npadding-bottom: " + (Style.padding.nav.offsetHeight + 1) + "px;\"\n}";
if (Style.padding.pages) {
css += ".fourchan-ss-navigation.index.pagination-sticky-top body::before,\n.fourchan-ss-navigation.index.pagination-top body::before {\n top: " + Style.padding.pages.offsetHeight + "px;\n}\n.fourchan-ss-navigation.index.pagination-sticky-bottom body::before,\n.fourchan-ss-navigation.index.pagination-bottom body::before {\n bottom: " + Style.padding.pages.offsetHeight + "px;\n}\n.fourchan-ss-navigation.index.pagination-sticky-top body,\n.fourchan-ss-navigation.index.pagination-top body {\n padding-top: " + (Style.padding.pages.offsetHeight + 1) + "px;\n}\n.fourchan-ss-navigation.index.pagination-sticky-bottom body,\n.fourchan-ss-navigation.index.pagination-bottom body {\n padding-bottom: " + (Style.padding.pages.offsetHeight + 1) + "px;\n}";
css += ".fourchan-ss-navigation.index.pagination-sticky-top body::before,\n.fourchan-ss-navigation.index.pagination-top body::before {\n top: " + Style.padding.pages.offsetHeight + "px;\n}\n.fourchan-ss-navigation.index.pagination-sticky-bottom body::before,\n.fourchan-ss-navigation.index.pagination-bottom body::before {\n bottom: " + Style.padding.pages.offsetHeight + "px;\n}\n.index.pagination-sticky-top body,\n.index.pagination-top body {\n padding-top: " + (Style.padding.pages.offsetHeight + 1) + "px;\n}\n.index.pagination-sticky-bottom body,\n.index.pagination-bottom body {\n padding-bottom: " + (Style.padding.pages.offsetHeight + 1) + "px;\n}";
}
return Style.paddingSheet.textContent = css;
},
@ -11750,44 +11740,9 @@
JSColor.bind(colorInput);
$.after(input, colorInput);
}
$.on(input, 'blur', function() {
var depth, i, len, toggle1, toggle2;
depth = 0;
toggle1 = false;
toggle2 = false;
len = this.value.length;
if (len < 1000) {
i = 0;
while (i < len) {
switch (this.value[i++]) {
case '(':
++depth;
break;
case ')':
--depth;
break;
case '"':
toggle1 = !toggle1;
break;
case "'":
toggle2 = !toggle2;
}
}
}
if ((depth !== 0) || toggle1 || toggle2) {
return alert("Syntax error on " + this.name + ".");
}
if (this.className === "colorfield") {
this.nextSibling.value = "#" + (Style.colorToHex(this.value) || 'aaaaaa');
this.nextSibling.color.importColor();
}
editTheme[this.name] = this.value;
return Style.addStyle(editTheme);
});
$.on(input, 'blur', ThemeTools.apply);
$.add(themeContent, div);
}
Style.addStyle(editTheme);
if (!editTheme["Custom CSS"]) {
editTheme["Custom CSS"] = "";
}
@ -11807,6 +11762,41 @@
$.add(d.body, ThemeTools.dialog);
return Style.themeCSS.textContent = Style.theme(editTheme);
},
apply: function() {
var depth, i, len, toggle1, toggle2;
depth = 0;
toggle1 = false;
toggle2 = false;
len = this.value.length;
if (len < 1000) {
i = 0;
while (i < len) {
switch (this.value[i++]) {
case '(':
++depth;
break;
case ')':
--depth;
break;
case '"':
toggle1 = !toggle1;
break;
case "'":
toggle2 = !toggle2;
}
}
}
if ((depth !== 0) || toggle1 || toggle2) {
return alert("Syntax error on " + this.name + ".");
}
if (this.className === "colorfield") {
this.nextSibling.value = '#' + (Style.colorToHex(this.value) || 'aaaaaa');
this.nextSibling.color.importColor();
}
editTheme[this.name] = this.value;
return Style.themeCSS.textContent = Style.theme(editTheme);
},
uploadImage: function(evt, el) {
var file, reader;
@ -11815,7 +11805,7 @@
reader.onload = function(evt) {
var val;
val = 'url("' + evt.target.result + '")';
val = "url(\"" + evt.target.result + "\")";
el.previousSibling.value = val;
editTheme["Background Image"] = val;
return Style.themeCSS.textContent = Style.theme(editTheme);
@ -11962,14 +11952,14 @@
userThemes = _arg.userThemes;
userThemes[name] = Themes[name];
$.set('userThemes', userThemes);
$.set("theme", Conf['theme'] = name);
$.set('theme', Conf['theme'] = name);
return alert("Theme \"" + name + "\" saved.");
});
},
close: function() {
Conf['editMode'] = false;
Style.themeCSS.textContent = Style.theme(Themes[Conf['theme']]);
$.rm($.id('themeConf'));
Style.addStyle();
return Settings.open('Themes');
}
};

View File

@ -1,6 +1,6 @@
// Generated by CoffeeScript
/*
* appchan x - Version 2.1.3 - 2013-06-20
* appchan x - Version 2.1.3 - 2013-06-21
*
* Licensed under the MIT license.
* https://github.com/zixaphir/appchan-x/blob/master/LICENSE
@ -11110,8 +11110,7 @@
val = evt.target.result;
el.previousSibling.value = val;
editMascot.image = val;
return Style.addStyle();
return editMascot.image = val;
};
return reader.readAsDataURL(file);
},
@ -11173,7 +11172,6 @@
Conf['editMode'] = false;
editMascot = {};
$.rm($.id('mascotConf'));
Style.addStyle();
return Settings.open("Mascots");
},
importMascot: function(evt) {
@ -11352,7 +11350,8 @@
$.asap((function() {
return d.body;
}), this.asapInit);
return $.ready(this.readyInit);
$.on(window, "resize", Style.padding);
return $.on(doc, '4chanXInitFinished', this.readyInit);
},
asapInit: function() {
var cat, hyphenated, name, setting, title, _ref;
@ -11416,23 +11415,15 @@
}
},
readyInit: function() {
if (!$.id('navtopright')) {
return;
}
return setTimeout(function() {
var exLink;
var exLink;
Style.padding.nav = Header.bar;
Style.padding.pages = $(".pagelist", d.body);
Style.padding();
$.on(window, "resize", Style.padding);
Style.iconPositions();
if (exLink = $("#navtopright .exlinksOptionsLink", d.body)) {
return $.on(exLink, "click", function() {
return setTimeout(Rice.nodes, 100);
});
}
}, 500);
Style.padding();
Style.iconPositions();
if (exLink = $("#navtopright .exlinksOptionsLink", d.body)) {
return $.on(exLink, "click", function() {
return setTimeout(Rice.nodes, 100);
});
}
},
setup: function() {
var theme;
@ -11492,10 +11483,7 @@
delete Style.headCount;
return delete Style.cleanup;
},
addStyle: function(theme) {
if (!theme) {
theme = Themes[Conf['theme']] || Themes['Yotsuba B'];
}
addStyle: function() {
Style.dynamicCSS.textContent = Style.dynamic();
Style.iconPositions();
return Style.padding();
@ -11607,9 +11595,11 @@
padding: function() {
var css;
css = "body {\npadding-bottom: 1px;\n}\n.fourchan-ss-navigation.fixed.top:not(.autohide) body::before {\ntop: " + Style.padding.nav.offsetHeight + "px;\n}\n.fourchan-ss-navigation.fixed.bottom:not(.autohide) body::before {\nbottom: " + Style.padding.nav.offsetHeight + "px;\n}\n.fourchan-ss-navigation.top:not(.autohide) body {\npadding-top: " + (Style.padding.nav.offsetHeight + 1) + "px;\n}\n.fourchan-ss-navigation.bottom:not(.autohide) body {\npadding-bottom: " + (Style.padding.nav.offsetHeight + 1) + "px;\"\n}";
Style.padding.nav = Header.bar;
Style.padding.pages = $('.pagelist', d.body);
css = "body {\npadding-bottom: 1px;\n}\n.fourchan-ss-navigation.fixed.top:not(.autohide) body::before {\ntop: " + Style.padding.nav.offsetHeight + "px;\n}\n.fourchan-ss-navigation.fixed.bottom:not(.autohide) body::before {\nbottom: " + Style.padding.nav.offsetHeight + "px;\n}\n.top:not(.autohide) body {\npadding-top: " + (Style.padding.nav.offsetHeight + 1) + "px;\n}\n.bottom:not(.autohide) body {\npadding-bottom: " + (Style.padding.nav.offsetHeight + 1) + "px;\"\n}";
if (Style.padding.pages) {
css += ".fourchan-ss-navigation.index.pagination-sticky-top body::before,\n.fourchan-ss-navigation.index.pagination-top body::before {\n top: " + Style.padding.pages.offsetHeight + "px;\n}\n.fourchan-ss-navigation.index.pagination-sticky-bottom body::before,\n.fourchan-ss-navigation.index.pagination-bottom body::before {\n bottom: " + Style.padding.pages.offsetHeight + "px;\n}\n.fourchan-ss-navigation.index.pagination-sticky-top body,\n.fourchan-ss-navigation.index.pagination-top body {\n padding-top: " + (Style.padding.pages.offsetHeight + 1) + "px;\n}\n.fourchan-ss-navigation.index.pagination-sticky-bottom body,\n.fourchan-ss-navigation.index.pagination-bottom body {\n padding-bottom: " + (Style.padding.pages.offsetHeight + 1) + "px;\n}";
css += ".fourchan-ss-navigation.index.pagination-sticky-top body::before,\n.fourchan-ss-navigation.index.pagination-top body::before {\n top: " + Style.padding.pages.offsetHeight + "px;\n}\n.fourchan-ss-navigation.index.pagination-sticky-bottom body::before,\n.fourchan-ss-navigation.index.pagination-bottom body::before {\n bottom: " + Style.padding.pages.offsetHeight + "px;\n}\n.index.pagination-sticky-top body,\n.index.pagination-top body {\n padding-top: " + (Style.padding.pages.offsetHeight + 1) + "px;\n}\n.index.pagination-sticky-bottom body,\n.index.pagination-bottom body {\n padding-bottom: " + (Style.padding.pages.offsetHeight + 1) + "px;\n}";
}
return Style.paddingSheet.textContent = css;
},
@ -11739,44 +11729,9 @@
JSColor.bind(colorInput);
$.after(input, colorInput);
}
$.on(input, 'blur', function() {
var depth, i, len, toggle1, toggle2;
depth = 0;
toggle1 = false;
toggle2 = false;
len = this.value.length;
if (len < 1000) {
i = 0;
while (i < len) {
switch (this.value[i++]) {
case '(':
++depth;
break;
case ')':
--depth;
break;
case '"':
toggle1 = !toggle1;
break;
case "'":
toggle2 = !toggle2;
}
}
}
if ((depth !== 0) || toggle1 || toggle2) {
return alert("Syntax error on " + this.name + ".");
}
if (this.className === "colorfield") {
this.nextSibling.value = "#" + (Style.colorToHex(this.value) || 'aaaaaa');
this.nextSibling.color.importColor();
}
editTheme[this.name] = this.value;
return Style.addStyle(editTheme);
});
$.on(input, 'blur', ThemeTools.apply);
$.add(themeContent, div);
}
Style.addStyle(editTheme);
if (!editTheme["Custom CSS"]) {
editTheme["Custom CSS"] = "";
}
@ -11796,6 +11751,41 @@
$.add(d.body, ThemeTools.dialog);
return Style.themeCSS.textContent = Style.theme(editTheme);
},
apply: function() {
var depth, i, len, toggle1, toggle2;
depth = 0;
toggle1 = false;
toggle2 = false;
len = this.value.length;
if (len < 1000) {
i = 0;
while (i < len) {
switch (this.value[i++]) {
case '(':
++depth;
break;
case ')':
--depth;
break;
case '"':
toggle1 = !toggle1;
break;
case "'":
toggle2 = !toggle2;
}
}
}
if ((depth !== 0) || toggle1 || toggle2) {
return alert("Syntax error on " + this.name + ".");
}
if (this.className === "colorfield") {
this.nextSibling.value = '#' + (Style.colorToHex(this.value) || 'aaaaaa');
this.nextSibling.color.importColor();
}
editTheme[this.name] = this.value;
return Style.themeCSS.textContent = Style.theme(editTheme);
},
uploadImage: function(evt, el) {
var file, reader;
@ -11804,7 +11794,7 @@
reader.onload = function(evt) {
var val;
val = 'url("' + evt.target.result + '")';
val = "url(\"" + evt.target.result + "\")";
el.previousSibling.value = val;
editTheme["Background Image"] = val;
return Style.themeCSS.textContent = Style.theme(editTheme);
@ -11951,14 +11941,14 @@
userThemes = _arg.userThemes;
userThemes[name] = Themes[name];
$.set('userThemes', userThemes);
$.set("theme", Conf['theme'] = name);
$.set('theme', Conf['theme'] = name);
return alert("Theme \"" + name + "\" saved.");
});
},
close: function() {
Conf['editMode'] = false;
Style.themeCSS.textContent = Style.theme(Themes[Conf['theme']]);
$.rm($.id('themeConf'));
Style.addStyle();
return Settings.open('Themes');
}
};

View File

@ -7,9 +7,9 @@ body {
.fourchan-ss-navigation.fixed.bottom:not(.autohide) body::before {
bottom: #{Style.padding.nav.offsetHeight}px;
}
.fourchan-ss-navigation.top:not(.autohide) body {
.top:not(.autohide) body {
padding-top: #{Style.padding.nav.offsetHeight + 1}px;
}
.fourchan-ss-navigation.bottom:not(.autohide) body {
.bottom:not(.autohide) body {
padding-bottom: #{Style.padding.nav.offsetHeight + 1}px;"
}

View File

@ -6,11 +6,11 @@
.fourchan-ss-navigation.index.pagination-bottom body::before {
bottom: #{Style.padding.pages.offsetHeight}px;
}
.fourchan-ss-navigation.index.pagination-sticky-top body,
.fourchan-ss-navigation.index.pagination-top body {
.index.pagination-sticky-top body,
.index.pagination-top body {
padding-top: #{Style.padding.pages.offsetHeight + 1}px;
}
.fourchan-ss-navigation.index.pagination-sticky-bottom body,
.fourchan-ss-navigation.index.pagination-bottom body {
.index.pagination-sticky-bottom body,
.index.pagination-bottom body {
padding-bottom: #{Style.padding.pages.offsetHeight + 1}px;
}

File diff suppressed because it is too large Load Diff

View File

@ -396,51 +396,6 @@ $.set = do ->
$.extend items, key
set()
<% } else if (type === 'userjs') { %>
do ->
# http://www.opera.com/docs/userjs/specs/#scriptstorage
# http://www.opera.com/docs/userjs/using/#securepages
# The scriptStorage object is available only during
# the main User JavaScript thread, being therefore
# accessible only in the main body of the user script.
# To access the storage object later, keep a reference
# to the object.
{scriptStorage} = opera
$.delete = (keys) ->
unless keys instanceof Array
keys = [keys]
for key in keys
key = g.NAMESPACE + key
localStorage.removeItem key
delete scriptStorage[key]
return
$.get = (key, val, cb) ->
if typeof cb is 'function'
items = $.item key, val
else
items = key
cb = val
$.queueTask ->
for key of items
if val = scriptStorage[g.NAMESPACE + key]
items[key] = JSON.parse val
cb items
$.set = do ->
set = (key, val) ->
key = g.NAMESPACE + key
val = JSON.stringify val
if key of $.syncing
# for `storage` events
localStorage.setItem key, val
scriptStorage[key] = val
(keys, val) ->
if typeof keys is 'string'
set keys, val
return
for key, val of keys
set key, val
return
return
<% } else { %>
# http://wiki.greasespot.net/Main_Page

View File

@ -33,21 +33,6 @@ QuotePreview =
cb: QuotePreview.mouseout
asapTest: -> qp.firstElementChild
<% if (type === 'userjs') { %>
# XXX Opera workaround for "no mouseout fired" bug.
# Remove it once Opera uses Blink.
root = @
workaround = (e) ->
if @ is root
e.stopPropagation()
return
$.event 'mouseout', null, root
$.off d, 'mousemove', workaround
$.off root, 'mousemove', workaround
$.on d, 'mousemove', workaround
$.on root, 'mousemove', workaround
<% } %>
return unless origin = g.posts["#{boardID}.#{postID}"]
if Conf['Quote Highlighting']

View File

@ -238,7 +238,6 @@ MascotTools =
el.previousSibling.value = val
editMascot.image = val
Style.addStyle()
reader.readAsDataURL file
@ -289,7 +288,6 @@ MascotTools =
Conf['editMode'] = false
editMascot = {}
$.rm $.id 'mascotConf'
Style.addStyle()
Settings.open "Mascots"
importMascot: (evt) ->

View File

@ -2,14 +2,13 @@ Style =
init: ->
@setup()
$.asap (-> d.body), @asapInit
$.ready @readyInit
$.on window, "resize", Style.padding
$.on doc, '4chanXInitFinished', @readyInit
asapInit: ->
<% if (type === 'crx') { %>
$.addClass doc, 'webkit'
$.addClass doc, 'blink'
<% } else if (type === 'userjs') { %>
$.addClass doc, 'presto'
<% } else { %>
$.addClass doc, 'gecko'
<% } %>
@ -57,19 +56,11 @@ Style =
$.add next, nextA
readyInit: ->
return unless $.id 'navtopright'
# Give ExLinks and 4sight a little time to append their dialog links
setTimeout ->
Style.padding.nav = Header.bar
Style.padding.pages = $(".pagelist", d.body)
Style.padding()
$.on window, "resize", Style.padding
Style.iconPositions()
if exLink = $ "#navtopright .exlinksOptionsLink", d.body
$.on exLink, "click", ->
setTimeout Rice.nodes, 100
, 500
Style.padding()
Style.iconPositions()
if exLink = $ "#navtopright .exlinksOptionsLink", d.body
$.on exLink, "click", ->
setTimeout Rice.nodes, 100
setup: ->
theme = Themes[Conf['theme']] or Themes['Yotsuba B']
@ -120,10 +111,7 @@ Style =
delete Style.headCount
delete Style.cleanup
addStyle: (theme) ->
unless theme
theme = Themes[Conf['theme']] or Themes['Yotsuba B']
addStyle: ->
Style.dynamicCSS.textContent = Style.dynamic()
Style.iconPositions()
Style.padding()
@ -294,6 +282,8 @@ Style =
Style.icons.textContent = css
padding: ->
Style.padding.nav = Header.bar
Style.padding.pages = $ '.pagelist', d.body
css = """<%= grunt.file.read('src/General/css/padding.nav.css') %>"""
if Style.padding.pages

View File

@ -146,35 +146,10 @@ ThemeTools =
$.after input, colorInput
$.on input, 'blur', ->
depth = 0
toggle1 = false
toggle2 = false
len = @value.length
if len < 1000
i = 0
while i < len
switch @value[i++]
when '(' then ++depth
when ')' then --depth
when '"' then toggle1 = !toggle1
when "'" then toggle2 = !toggle2
if (depth isnt 0) or toggle1 or toggle2
return alert "Syntax error on #{@name}."
if @className is "colorfield"
@nextSibling.value = "##{Style.colorToHex(@value) or 'aaaaaa'}"
@nextSibling.color.importColor()
editTheme[@name] = @value
Style.addStyle(editTheme)
$.on input, 'blur', ThemeTools.apply
$.add themeContent, div
Style.addStyle(editTheme)
unless editTheme["Custom CSS"]
editTheme["Custom CSS"] = ""
@ -196,12 +171,37 @@ ThemeTools =
$.add d.body, ThemeTools.dialog
Style.themeCSS.textContent = Style.theme editTheme
apply: ->
depth = 0
toggle1 = false
toggle2 = false
len = @value.length
if len < 1000
i = 0
while i < len
switch @value[i++]
when '(' then ++depth
when ')' then --depth
when '"' then toggle1 = !toggle1
when "'" then toggle2 = !toggle2
if (depth isnt 0) or toggle1 or toggle2
return alert "Syntax error on #{@name}."
if @className is "colorfield"
@nextSibling.value = '#' + (Style.colorToHex(@value) or 'aaaaaa')
@nextSibling.color.importColor()
editTheme[@name] = @value
Style.themeCSS.textContent = Style.theme editTheme
uploadImage: (evt, el) ->
file = evt.target.files[0]
reader = new FileReader()
reader.onload = (evt) ->
val = 'url("' + evt.target.result + '")'
val = """url("#{evt.target.result}")"""
el.previousSibling.value = val
editTheme["Background Image"] = val
@ -336,16 +336,17 @@ ThemeTools =
else
return
Themes[name] = JSON.parse(JSON.stringify(theme))
Themes[name] = JSON.parse JSON.stringify theme
delete Themes[name]["Theme"]
$.get "userThemes", {}, ({userThemes}) ->
userThemes[name] = Themes[name]
$.set 'userThemes', userThemes
$.set "theme", Conf['theme'] = name
alert "Theme \"#{name}\" saved."
$.set 'theme', Conf['theme'] = name
alert """Theme "#{name}" saved."""
close: ->
Conf['editMode'] = false
Style.themeCSS.textContent = Style.theme Themes[Conf['theme']]
$.rm $.id 'themeConf'
Style.addStyle()
Settings.open 'Themes'