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); }