diff --git a/background/search-db.js b/background/search-db.js index d774297d..1404f216 100644 --- a/background/search-db.js +++ b/background/search-db.js @@ -7,6 +7,7 @@ define(require => { tryRegExp, } = require('/js/toolbox'); const {API} = require('/js/msg'); + const usercss = require('./usercss-api-helper'); // toLocaleLowerCase cache, autocleared after 1 minute const cache = new Map(); @@ -103,13 +104,13 @@ define(require => { function extractMeta(style) { return style.usercssData - ? (style.sourceCode.match(API.usercss.rxMETA) || [''])[0] + ? (style.sourceCode.match(usercss.rxMETA) || [''])[0] : null; } function stripMeta(style) { return style.usercssData - ? style.sourceCode.replace(API.usercss.rxMETA, '') + ? style.sourceCode.replace(usercss.rxMETA, '') : null; } diff --git a/js/prefs.js b/js/prefs.js index 4db5ad45..754835b7 100644 --- a/js/prefs.js +++ b/js/prefs.js @@ -141,10 +141,6 @@ define(require => { } }); - /** - * @type Prefs - * @namespace Prefs - */ const prefs = { STORAGE_KEY, diff --git a/js/toolbox.js b/js/toolbox.js index 2e6e9d40..6a739c9e 100644 --- a/js/toolbox.js +++ b/js/toolbox.js @@ -13,7 +13,8 @@ define(require => { const debounceTimers = new Map(); let URLS, deepCopy, deepEqual, deepMerge; - const toolbox = { + /** @type {Toolbox} */ + const toolbox = /** @namespace Toolbox */ { CHROME, FIREFOX, diff --git a/manage.html b/manage.html index 0f73d00b..260e97a2 100644 --- a/manage.html +++ b/manage.html @@ -217,7 +217,7 @@
- @@ -235,7 +235,7 @@
- @@ -253,7 +253,7 @@
- @@ -276,7 +276,7 @@ data-filter=":not(.not-matching)" data-filter-hide=".not-matching">
- diff --git a/manage/events.js b/manage/events.js index caaa8dc9..58836a00 100644 --- a/manage/events.js +++ b/manage/events.js @@ -35,17 +35,6 @@ define(require => { const Events = { - ENTRY_ROUTES: { - 'input, .enable, .disable': 'toggle', - '.style-name': 'name', - '.homepage': 'external', - '.check-update': 'check', - '.update': 'update', - '.delete': 'delete', - '.applies-to .expander': 'expandTargets', - '.configure-usercss': 'config', - }, - addEntryTitle(link) { const style = link.closest('.entry').styleMeta; const ucd = style.usercssData; @@ -130,8 +119,7 @@ define(require => { for (const selector in Events.ENTRY_ROUTES) { for (let el = target; el && el !== entry; el = el.parentElement) { if (el.matches(selector)) { - const handler = Events.ENTRY_ROUTES[selector]; - return Events[handler].call(el, event, entry); + return Events.ENTRY_ROUTES[selector].call(el, event, entry); } } } @@ -164,6 +152,17 @@ define(require => { }, }; + Events.ENTRY_ROUTES = { + 'input, .enable, .disable': Events.toggle, + '.style-name': Events.name, + '.homepage': Events.external, + '.check-update': Events.check, + '.update': Events.update, + '.delete': Events.delete, + '.applies-to .expander': Events.expandTargets, + '.configure-usercss': Events.config, + }; + async function handleUpdateForId(id, opts) { handleUpdate(await API.styles.get(id), opts); bulkChangeQueue.time = performance.now(); diff --git a/manage/filters.js b/manage/filters.js index ca3ef47e..2eee941b 100644 --- a/manage/filters.js +++ b/manage/filters.js @@ -62,20 +62,6 @@ define(require => { } }); - HTMLSelectElement.prototype.adjustWidth = function () { - const sel = this.selectedOptions[0]; - if (!sel) return; - const wOld = parseFloat(this.style.width); - const opts = [...this]; - opts.forEach(opt => opt !== sel && opt.remove()); - this.style.width = ''; - requestAnimationFrame(() => { - const w = this.offsetWidth; - if (w && wOld !== w) this.style.width = w + 'px'; - this.append(...opts); - }); - }; - function initFilters() { $('#search').oninput = $('#searchMode').oninput = function (e) { router.updateSearch(this.id, e.target.value); @@ -112,10 +98,6 @@ define(require => { slaveData.filter = filter; slaveData.filterHide = valueMap.get(!value); debounce(filterOnChange, 0, event); - // avoid triggering MutationObserver during page load - if (document.readyState === 'complete') { - el.adjustWidth(); - } }; el.onchange({target: el}); }); @@ -150,14 +132,6 @@ define(require => { router.updateSearch('search', ''); }; - // Adjust width after selects are visible - prefs.subscribe('manage.filters.expanded', () => { - const el = $('#filters'); - if (el.open) { - $$('.filter-selection select', el).forEach(select => select.adjustWidth()); - } - }); - filterOnChange({forceRefilter: true}); } diff --git a/manage/manage.js b/manage/manage.js index f575b501..8734fee2 100644 --- a/manage/manage.js +++ b/manage/manage.js @@ -1,6 +1,6 @@ 'use strict'; -define(require => { +define(async require => { const {API, msg} = require('/js/msg'); const { CHROME, @@ -21,7 +21,8 @@ define(require => { const { BULK_THROTTLE_MS, bulkChangeQueue, - waitingForContainer, + containerPromise, + fitSelectBoxInOpenDetails, showStyles, switchUI, } = require('./render'); @@ -32,54 +33,56 @@ define(require => { handleVisibilityChange, } = require('./events'); - (async () => { - const query = router.getSearch('search'); - const [styles, ids, container] = await Promise.all([ - API.styles.getAll(), - query && API.searchDB({query, mode: router.getSearch('searchMode')}), - waitingForContainer, - prefs.initializing, - ]); - container.onclick = Events.entryClicked; - $('#manage-options-button').onclick = () => router.updateHash('#stylus-options'); - $('#sync-styles').onclick = () => router.updateHash('#stylus-options'); - $$('#header a[href^="http"]').forEach(a => (a.onclick = Events.external)); - // show date installed & last update on hover - container.on('mouseover', Events.lazyAddEntryTitle, {passive: true}); - container.on('mouseout', Events.lazyAddEntryTitle, {passive: true}); - document.on('visibilitychange', handleVisibilityChange); - // N.B. triggers existing onchange listeners - setupLivePrefs(); - prefs.subscribe(newUI.ids.map(newUI.prefKeyForId), () => switchUI()); - switchUI({styleOnly: true}); - // translate CSS manually - document.styleSheets[0].insertRule( - `:root {${[ - 'genericDisabledLabel', - 'updateAllCheckSucceededSomeEdited', - 'filteredStylesAllHidden', - ].map(id => `--${id}:"${CSS.escape(t(id))}";`).join('') - }}`); - if (!VIVALDI) { - $$('.filter-selection select').forEach(el => el.adjustWidth()); - } - if (CHROME >= 80 && CHROME <= 88) { - // Wrong checkboxes are randomly checked after going back in history, https://crbug.com/1138598 - window.on('pagehide', () => { - $$('input[type=checkbox]').forEach((el, i) => (el.name = `bug${i}`)); - }); - } - showStyles(styles, ids); - require([ - './import-export', - './incremental-search', - ]); - })(); - msg.onExtension(onRuntimeMessage); - router.watch({hash: '#stylus-options'}, state => (state ? embedOptions : unembedOptions)()); + router.watch({hash: '#stylus-options'}, toggleEmbeddedOptions); window.on('closeOptions', () => router.updateHash('')); + const query = router.getSearch('search'); + const [styles, ids, container] = await Promise.all([ + API.styles.getAll(), + query && API.searchDB({query, mode: router.getSearch('searchMode')}), + containerPromise, + prefs.initializing, + ]); + + container.on('click', Events.entryClicked); + container.on('mouseover', Events.lazyAddEntryTitle, {passive: true}); + container.on('mouseout', Events.lazyAddEntryTitle, {passive: true}); + $('#manage-options-button').onclick = () => router.updateHash('#stylus-options'); + $('#sync-styles').onclick = () => router.updateHash('#stylus-options'); + $$('#header a[href^="http"]').forEach(a => (a.onclick = Events.external)); + document.on('visibilitychange', handleVisibilityChange); + + setupLivePrefs(); + prefs.subscribe(newUI.ids.map(newUI.prefKeyForId), () => switchUI()); + switchUI({styleOnly: true}); + + // translate CSS manually + document.styleSheets[0].insertRule( + `:root {${[ + 'genericDisabledLabel', + 'updateAllCheckSucceededSomeEdited', + 'filteredStylesAllHidden', + ].map(id => `--${id}:"${CSS.escape(t(id))}";`).join('') + }}`); + + if (!VIVALDI) { + fitSelectBoxInOpenDetails($('#filters')); + } + if (CHROME >= 80 && CHROME <= 88) { + // Wrong checkboxes are randomly checked after going back in history, https://crbug.com/1138598 + window.on('pagehide', () => { + $$('input[type=checkbox]').forEach((el, i) => (el.name = `bug${i}`)); + }); + } + + showStyles(styles, ids); + + require([ + './import-export', + './incremental-search', + ]); + function onRuntimeMessage(msg) { switch (msg.method) { case 'styleUpdated': @@ -101,21 +104,18 @@ define(require => { setTimeout(sorter.updateStripes, 0, {onlyWhenColumnsChanged: true}); } - function embedOptions() { - const options = $('#stylus-embedded-options') || - document.documentElement.appendChild($create('iframe', { + async function toggleEmbeddedOptions(state) { + const el = $('#stylus-embedded-options') || + state && document.documentElement.appendChild($create('iframe', { id: 'stylus-embedded-options', src: '/options.html', })); - options.focus(); - } - - async function unembedOptions() { - const options = $('#stylus-embedded-options'); - if (options) { - options.contentWindow.document.body.classList.add('scaleout'); - await animateElement(options, 'fadeout'); - options.remove(); + if (state) { + el.focus(); + } else if (el) { + el.contentDocument.body.classList.add('scaleout'); + await animateElement(el, 'fadeout'); + el.remove(); } } }); diff --git a/manage/render.js b/manage/render.js index 5a34fdf7..4a11c85b 100644 --- a/manage/render.js +++ b/manage/render.js @@ -36,7 +36,7 @@ define(require => { BULK_THROTTLE_MS: 100, bulkChangeQueue: [], // needed to avoid flicker due to an extra frame and layout shift - waitingForContainer: waitForSelector('#installed').then(el => (installed = el)), + containerPromise: waitForSelector('#installed').then(el => (installed = el)), $entry(styleOrId, root = installed) { return $(`#${ENTRY_ID_PREFIX_RAW}${styleOrId.id || styleOrId}`, root); @@ -184,6 +184,26 @@ define(require => { entry._numTargets = numTargets; }, + /** + * @param {HTMLDetailsElement} el + * @param {string} targetSel + */ + fitSelectBoxInOpenDetails(el, targetSel = 'select.fit-width') { + const run = () => { + if (el.open) { + fitSelectBox(...$$(targetSel, el)); + } + }; + el.on('change', ({target}) => { + if (el.open && target.matches(targetSel)) { + fitSelectBox(target); + } + }); + new MutationObserver(run) + .observe(el, {attributeFilter: ['open'], attributes: true}); + run(); + }, + getFaviconImgSrc(container = installed) { if (!newUI.enabled || !newUI.favicons) return; const regexpRemoveNegativeLookAhead = /(\?!([^)]+\))|\(\?![\w(]+[^)]+[\w|)]+)/g; @@ -355,6 +375,26 @@ define(require => { } } + function fitSelectBox(...elems) { + const data = []; + for (const el of elems) { + const sel = el.selectedOptions[0]; + if (!sel) return; + const oldWidth = parseFloat(el.style.width); + const elOpts = [...el]; + data.push({el, elOpts, oldWidth}); + elOpts.forEach(opt => opt !== sel && opt.remove()); + el.style.width = ''; + } + requestAnimationFrame(() => { + for (const {el, elOpts, oldWidth} of data) { + const w = el.offsetWidth; + if (w && oldWidth !== w) el.style.width = w + 'px'; + el.append(...elOpts); + } + }); + } + function highlightEditedStyle() { if (!sessionStore.justEditedStyleId) return; const entry = render.$entry(sessionStore.justEditedStyleId); @@ -375,6 +415,7 @@ define(require => { const x = Math.max(0, left); const y = Math.max(0, top); const first = document.elementFromPoint(x, y); + if (!first) return requestAnimationFrame(loadFavicons.bind(null, ...arguments)); const lastOffset = first.offsetTop + window.innerHeight; const numTargets = newUI.targets; let entry = first && first.closest('.entry') || installed.children[0];