diff --git a/background/update-manager.js b/background/update-manager.js
index 1c975281..0796e4af 100644
--- a/background/update-manager.js
+++ b/background/update-manager.js
@@ -63,7 +63,7 @@ const updateMan = (() => {
checkingAll = true;
const port = observe && chrome.runtime.connect({name: 'updater'});
const styles = (await API.styles.getAll())
- .filter(style => style.updateUrl);
+ .filter(style => style.updateUrl && style.updatable !== false);
if (port) port.postMessage({count: styles.length});
log('');
log(`${save ? 'Scheduled' : 'Manual'} update check for ${styles.length} styles`);
diff --git a/edit/settings.css b/edit/settings.css
index 32397434..d4da2290 100644
--- a/edit/settings.css
+++ b/edit/settings.css
@@ -23,10 +23,13 @@
.style-settings input[type=radio] {
margin-left: -.5em; /* compensate for label's 16px margin in edit.css */
}
-.style-settings input[type=text],
-.style-settings input[type=number],
-.style-settings select,
-.style-settings textarea {
+.style-settings input:disabled ~ label {
+ opacity: .5;
+}
+.style-settings .rel {
+ position: relative;
+}
+.style-settings .w100 {
display: block;
width: 100%;
margin-top: .25em;
diff --git a/edit/settings.html b/edit/settings.html
index 054beece..725a1ba8 100644
--- a/edit/settings.html
+++ b/edit/settings.html
@@ -1,8 +1,11 @@
diff --git a/edit/settings.js b/edit/settings.js
index e1264f11..855bdb0a 100644
--- a/edit/settings.js
+++ b/edit/settings.js
@@ -1,26 +1,32 @@
-/* global $ $$ moveFocus setupLivePrefs */// dom.js
+/* global $ moveFocus setupLivePrefs */// dom.js
/* global API */// msg.js
/* global editor */
/* global helpPopup */// util.js
/* global t */// localization.js
-/* global debounce */// toolbox.js
+/* global debounce tryURL */// toolbox.js
/* exported StyleSettings */
'use strict';
async function StyleSettings() {
const AUTOSAVE_DELAY = 500; // same as config-dialog.js
const SS_ID = 'styleSettings';
+ const PASS = val => val;
await t.fetchTemplate('/edit/settings.html', SS_ID);
const {style} = editor;
const ui = t.template[SS_ID].cloneNode(true);
const elAuto = $('#config\\.autosave', ui);
const elSave = $('#ss-save', ui);
+ const elUpd = $('#ss-updatable', ui);
const pendingSetters = new Map();
const updaters = [
- initInput('#ss-update-url', () => style.updateUrl || '',
- val => API.styles.config(style.id, 'updateUrl', val)),
- initRadio('ss-scheme', () => style.preferScheme || 'none',
- val => API.styles.config(style.id, 'preferScheme', val)),
+ initCheckbox(elUpd, 'updatable', tryURL(style.updateUrl).href),
+ initInput('#ss-update-url', 'updateUrl', '', {
+ validate(el) {
+ elUpd.disabled = !el.value || !el.validity.valid;
+ return el.validity.valid;
+ },
+ }),
+ initRadio('ss-scheme', 'preferScheme', 'none'),
initArea('inclusions'),
initArea('exclusions'),
];
@@ -43,51 +49,67 @@ async function StyleSettings() {
}
function initArea(type) {
- const selector = `#ss-${type}`;
- const el = $(selector, ui);
- el.oninput = () => {
- const val = el.value;
- el.rows = val.match(/^/gm).length + !val.endsWith('\n');
- };
- return initInput(selector,
- () => {
- const list = style[type] || [];
- const text = list.join('\n');
- el.rows = (list.length || 1) + 1;
- return text;
+ return initInput(`#ss-${type}`, type, [], {
+ get: textToList,
+ set: list => list.join('\n'),
+ validate(el) {
+ const val = el.value;
+ el.rows = val.match(/^/gm).length + !val.endsWith('\n');
},
- val => API.styles.config(style.id, type, textToList(val))
- );
+ });
}
- function initInput(selector, getter, setter) {
- const el = $(selector, ui);
- el.oninput = () => autosave(el, setter);
+ function initCheckbox(el, key, defVal) {
+ return initInput(el, key, Boolean(defVal), {dom: 'checked'});
+ }
+
+ function initInput(el, key, defVal, {
+ dom = 'value', // DOM property name
+ get = PASS, // transformer function(val) after getting DOM value
+ set = PASS, // transformer function(val) before setting DOM value
+ validate = PASS, // function(el) - return `false` to prevent saving
+ } = {}) {
+ if (typeof el === 'string') {
+ el = $(el, ui);
+ }
+ el.oninput = () => {
+ if (validate(el) !== false) {
+ autosave(el, {dom, get, key});
+ }
+ };
return () => {
- const val = getter();
+ let val = style[key];
+ val = set(val != null ? val : defVal);
// Skipping if unchanged to preserve the Undo history of the input
- if (el.value !== val) el.value = val;
+ if (el[dom] !== val) el[dom] = val;
+ validate(el);
};
}
- function initRadio(name, getter, setter) {
- for (const el of $$(`[name="${name}"]`, ui)) {
- el.onchange = () => {
- if (el.checked) autosave(el, setter);
- };
- }
+ function initRadio(name, key, defVal) {
+ $(`#${name}`, ui).oninput = e => {
+ if (e.target.checked) {
+ autosave(e.target, {key});
+ }
+ };
return () => {
- $(`[name="${name}"][value="${getter()}"]`, ui).checked = true;
+ const val = style[key] || defVal;
+ const el = $(`[name="${name}"][value="${val}"]`, ui);
+ el.checked = true;
};
}
function save() {
- pendingSetters.forEach((fn, el) => fn(el.value));
+ pendingSetters.forEach(saveValue);
pendingSetters.clear();
helpPopup.div.classList.remove('dirty');
elSave.disabled = true;
}
+ function saveValue({dom = 'value', get = PASS, key}, el) {
+ return API.styles.config(style.id, key, get(el[dom]));
+ }
+
function textToList(text) {
return text.split(/\n/).map(s => s.trim()).filter(Boolean);
}