diff --git a/js/usercss.js b/js/usercss.js
index 551fcd39..7e2f41ad 100644
--- a/js/usercss.js
+++ b/js/usercss.js
@@ -46,6 +46,49 @@ var usercss = (function () {
}
};
+ const colorParser = (function () {
+ const el = document.createElement('div');
+ // https://bugs.webkit.org/show_bug.cgi?id=14563
+ document.head.appendChild(el);
+
+ function _parse(color) {
+ const [r, g, b, a = 1] = color.match(/[.\d]+/g).map(Number);
+ return {r, g, b, a};
+ }
+
+ function parse(color) {
+ el.style.color = color;
+ if (el.style.color === '') {
+ throw new Error(`"${color}" is not a valid color`);
+ }
+ color = getComputedStyle(el).color;
+ el.style.color = '';
+ return _parse(color);
+ }
+
+ function format({r, g, b, a = 1}) {
+ return `rgba(${r}, ${g}, ${b}, ${a})`;
+ }
+
+ function pad(s) {
+ if (s.padStart) {
+ // chrome 57+
+ return s.padStart(2, '0');
+ }
+ return `00${s}`.slice(-2);
+ }
+
+ function formatHex({r, g, b, a = null}) {
+ const values = [r, g, b];
+ if (a !== null) {
+ values.push(Math.floor(a * 255));
+ }
+ return '#' + values.map(n => pad(n.toString(16))).join('');
+ }
+
+ return {parse, format, formatHex};
+ })();
+
function getMetaSource(source) {
const commentRe = /\/\*[\s\S]*?\*\//g;
const metaRe = /==userstyle==[\s\S]*?==\/userstyle==/i;
@@ -219,5 +262,5 @@ var usercss = (function () {
// FIXME: validate variable formats
}
- return {buildMeta, buildCode};
+ return {buildMeta, buildCode, colorParser};
})();
diff --git a/manage.html b/manage.html
index 9f7752ca..308c954f 100644
--- a/manage.html
+++ b/manage.html
@@ -139,6 +139,8 @@
+
+
diff --git a/manage/config-dialog.js b/manage/config-dialog.js
new file mode 100644
index 00000000..6c7d9905
--- /dev/null
+++ b/manage/config-dialog.js
@@ -0,0 +1,121 @@
+/* global usercss messageBox */
+
+'use strict';
+
+function configDialog(style) {
+ const {colorParser} = usercss;
+ const form = buildConfigForm();
+
+ return messageBox({
+ title: `Configure ${style.name}`,
+ className: 'config-dialog',
+ contents: form.el,
+ buttons: [
+ t('confirmSave'),
+ {
+ textContent: t('confirmDefault'),
+ onclick: form.useDefault
+ },
+ t('confirmCancel')
+ ]
+ }).then(result => {
+ if (result.button !== 0 && !result.enter) {
+ return;
+ }
+ return form.getVars();
+ });
+
+ function buildConfigForm() {
+ const labels = [];
+ const vars = deepCopy(style.vars);
+ for (const key of Object.keys(vars)) {
+ const va = vars[key];
+ let appendChild;
+ if (va.type === 'color') {
+ va.inputColor = $element({tag: 'input', type: 'color'});
+ // FIXME: i18n
+ va.inputAlpha = $element({tag: 'input', type: 'range', min: 0, max: 1, title: 'Opacity', step: 'any'});
+ va.inputColor.onchange = va.inputAlpha.oninput = () => {
+ va.dirty = true;
+ const color = colorParser.parse(va.inputColor.value);
+ color.a = Number(va.inputAlpha.value);
+ va.value = colorParser.format(color);
+ va.inputColor.style.opacity = color.a;
+ };
+ appendChild = [va.label, va.inputColor, va.inputAlpha];
+ } else if (va.type === 'checkbox') {
+ va.input = $element({tag: 'input', type: 'checkbox'});
+ va.input.onchange = () => {
+ va.dirty = true;
+ va.value = String(Number(va.input.checked));
+ };
+ appendChild = [va.input, $element({tag: 'span', appendChild: va.label})];
+ } else if (va.type === 'select') {
+ va.input = $element({
+ tag: 'select',
+ appendChild: Object.keys(va.select).map(key => $element({
+ tag: 'option', value: key, appendChild: va.select[key]
+ }))
+ });
+ va.input.onchange = () => {
+ va.dirty = true;
+ va.value = va.input.value;
+ };
+ appendChild = [va.label, va.input];
+ } else {
+ va.input = $element({tag: 'input', type: 'text'});
+ va.input.oninput = () => {
+ va.dirty = true;
+ va.value = va.input.value;
+ };
+ appendChild = [va.label, va.input];
+ }
+ labels.push($element({
+ tag: 'label',
+ className: `config-${va.type}`,
+ appendChild
+ }));
+ }
+ drawValues();
+
+ function drawValues() {
+ for (const key of Object.keys(vars)) {
+ const va = vars[key];
+ const value = va.value === null || va.value === undefined ?
+ va.default : va.value;
+
+ if (va.type === 'color') {
+ const color = colorParser.parse(value);
+ va.inputAlpha.value = color.a;
+ va.inputColor.style.opacity = color.a;
+ delete color.a;
+ va.inputColor.value = colorParser.formatHex(color);
+ } else if (va.type === 'checkbox') {
+ va.input.checked = Number(value);
+ } else {
+ va.input.value = value;
+ }
+ }
+ }
+
+ function useDefault() {
+ for (const key of Object.keys(vars)) {
+ const va = vars[key];
+ va.dirty = va.value !== null && va.value !== undefined &&
+ va.value !== va.default;
+ va.value = null;
+ }
+ drawValues();
+ }
+
+ function getVars() {
+ return vars;
+ }
+
+ return {
+ el: labels,
+ useDefault,
+ getVars
+ };
+ }
+}
diff --git a/manage/manage.js b/manage/manage.js
index 9524406e..4e8d447a 100644
--- a/manage/manage.js
+++ b/manage/manage.js
@@ -2,6 +2,7 @@
/* global filtersSelector, filterAndAppend */
/* global checkUpdate, handleUpdateInstalled */
/* global objectDiff */
+/* global configDialog */
'use strict';
let installed;
@@ -282,128 +283,20 @@ Object.assign(handleEvent, {
},
config(event, {styleMeta: style}) {
- const form = buildConfigForm();
-
- messageBox({
- title: `Configure ${style.name}`,
- className: 'config-dialog',
- contents: form.el,
- buttons: [
- t('confirmSave'),
- {
- textContent: t('confirmDefault'),
- onclick: form.useDefault
- },
- t('confirmCancel')
- ]
- }).then(result => {
- if (result.button !== 0 && !result.enter) {
+ configDialog(style).then(vars => {
+ if (!vars) {
+ return;
+ }
+ const keys = Object.keys(vars).filter(k => vars[k].dirty);
+ if (!keys.length) {
return;
}
style.reason = 'config';
- const vars = form.getVars();
- let dirty = false;
- for (const key of Object.keys(vars)) {
- if (vars[key].dirty) {
- dirty = true;
- style.vars[key].value = vars[key].value;
- }
- }
- if (!dirty) {
- return;
+ for (const key of keys) {
+ style.vars[key].value = vars[key].value;
}
saveStyleSafe(style);
});
-
- function buildConfigForm() {
- const labels = [];
- const vars = deepCopy(style.vars);
- for (const key of Object.keys(vars)) {
- const va = vars[key];
- let appendChild;
- if (va.type === 'color') {
- va.inputColor = $element({tag: 'input', type: 'color'});
- // FIXME: i18n
- va.inputAlpha = $element({tag: 'input', type: 'range', min: 0, max: 255, title: 'Opacity'});
- va.inputColor.onchange = va.inputAlpha.oninput = () => {
- va.dirty = true;
- va.value = va.inputColor.value + Number(va.inputAlpha.value).toString(16);
- va.inputColor.style.opacity = va.inputAlpha.value / 255;
- };
- appendChild = [va.label, va.inputColor, va.inputAlpha];
- } else if (va.type === 'checkbox') {
- va.input = $element({tag: 'input', type: 'checkbox'});
- va.input.onchange = () => {
- va.dirty = true;
- va.value = String(Number(va.input.checked));
- };
- appendChild = [va.input, $element({tag: 'span', appendChild: va.label})];
- } else if (va.type === 'select') {
- va.input = $element({
- tag: 'select',
- appendChild: Object.keys(va.select).map(key => $element({
- tag: 'option', value: key, appendChild: va.select[key]
- }))
- });
- va.input.onchange = () => {
- va.dirty = true;
- va.value = va.input.value;
- };
- appendChild = [va.label, va.input];
- } else {
- va.input = $element({tag: 'input', type: 'text'});
- va.input.oninput = () => {
- va.dirty = true;
- va.value = va.input.value;
- };
- appendChild = [va.label, va.input];
- }
- labels.push($element({
- tag: 'label',
- className: `config-${va.type}`,
- appendChild
- }));
- }
- drawValues();
-
- function drawValues() {
- for (const key of Object.keys(vars)) {
- const va = vars[key];
- const value = va.value === null || va.value === undefined ?
- va.default : va.value;
-
- if (va.type === 'color') {
- va.inputColor.value = value.slice(0, -2);
- va.inputAlpha.value = parseInt(value.slice(-2), 16);
- va.inputColor.style.opacity = va.inputAlpha.value / 255;
- } else if (va.type === 'checkbox') {
- va.input.checked = Number(value);
- } else {
- va.input.value = value;
- }
- }
- }
-
- function useDefault() {
- for (const key of Object.keys(vars)) {
- const va = vars[key];
- va.dirty = va.value !== null && va.value !== undefined &&
- va.value !== va.default;
- va.value = null;
- }
- drawValues();
- }
-
- function getVars() {
- return vars;
- }
-
- return {
- el: labels,
- useDefault,
- getVars
- };
- }
},
entryClicked(event) {