diff --git a/background/style-manager.js b/background/style-manager.js index d7d766cf..c862ddec 100644 --- a/background/style-manager.js +++ b/background/style-manager.js @@ -199,6 +199,18 @@ const styleMan = (() => { getOrder: () => orderWrap.value, + /** @returns {Promise}>} */ + async getRemoteInfo(id) { + if (ready.then) await ready; + if (id) return calcRemoteId(id2style(id)); + const res = {}; + for (const {style} of dataMap.values()) { + const [rid, vars] = calcRemoteId(style); + if (rid) res[rid] = [style.id, vars]; + } + return res; + }, + /** @returns {Promise} */ async getSectionsByUrl(url, id, isInitialApply) { if (ready.then) await ready; @@ -366,6 +378,17 @@ const styleMan = (() => { return id2style(uuidIndex.get(uuid)); } + function calcRemoteId({md5Url, updateUrl, usercssData: ucd} = {}) { + let id; + id = (id = /\d+/.test(md5Url) || URLS.extractUsoArchiveId(updateUrl)) && `uso-${id}` + || (id = URLS.extractUSwId(updateUrl)) && `usw-${id}` + || ''; + return id && [ + id, + ucd && !isEmptyObj(ucd.vars), + ]; + } + /** @returns {StyleObj} */ function createNewStyle() { return /** @namespace StyleObj */ { diff --git a/popup/search.css b/popup/search.css index 53fd7115..c0f363c2 100644 --- a/popup/search.css +++ b/popup/search.css @@ -134,6 +134,10 @@ width: 100%; } +.search-result[data-installed] { + box-shadow: 1px 1px 10px darkcyan; + border-color: darkcyan; +} .search-result:not([data-installed]) .search-result-actions { opacity: 0; transition: opacity .5s; diff --git a/popup/search.js b/popup/search.js index 6c5827d3..d490f3ae 100644 --- a/popup/search.js +++ b/popup/search.js @@ -2,7 +2,7 @@ /* global $entry tabURL */// popup.js /* global API */// msg.js /* global Events */ -/* global FIREFOX URLS debounce download stringAsRegExp tryRegExp tryURL */// toolbox.js +/* global FIREFOX URLS debounce download isEmptyObj stringAsRegExp tryRegExp tryURL */// toolbox.js /* global prefs */ /* global t */// localization.js 'use strict'; @@ -27,7 +27,7 @@ /** * @typedef IndexEntry * @prop {'uso' | 'uso-android'} f - format - * @prop {Number} i - id + * @prop {Number} i - id, later replaced with string like `uso-123` * @prop {string} n - name * @prop {string} c - category * @prop {Number} u - updatedTime @@ -39,8 +39,8 @@ * @prop {string} sn - screenshotName * @prop {boolean} sa - screenshotArchived * - * @prop {boolean} _installed - * @prop {number} _installedStyleId + * @prop {number} _styleId - installed style id + * @prop {boolean} _styleVars - installed style has vars * @prop {number} _year */ /** @type IndexEntry[] */ @@ -73,6 +73,7 @@ const entry = el.closest(RESULT_SEL); return {entry, result: entry && entry._result}; }; + const rid2id = rid => rid.split('-')[1]; Events.searchInline = () => { calcCategory(); ready = start(); @@ -158,18 +159,22 @@ window.on('styleDeleted', ({detail: {style: {id}}}) => { restoreScrollPosition(); - const result = results.find(r => r._installedStyleId === id); - if (result) { - API.uso.pingback(result.i, false); - renderActionButtons(result.i, -1); + const r = results.find(r => r._styleId === id); + if (r) { + if (r.f) API.uso.pingback(rid2id(r.i), false); + delete r._styleId; + renderActionButtons(r.i); } }); window.on('styleAdded', async ({detail: {style}}) => { restoreScrollPosition(); - const id = calcId(style) || calcId(await API.styles.get(style.id)); - if (id && results.find(r => r.i === id)) { - renderActionButtons(id, style.id); + const ri = await API.styles.getRemoteInfo(style.id); + const r = ri && results.find(r => ri[0] === r.i); + if (r) { + r._styleId = style.id; + r._styleVars = ri[1]; + renderActionButtons(ri[0]); } }); @@ -209,9 +214,10 @@ results = await search({retry}); } if (results.length) { - const installedStyles = await API.styles.getAll(); - const allSupportedIds = new Set(installedStyles.map(calcId)); - results = results.filter(r => !allSupportedIds.has(r.i)); + const info = await API.styles.getRemoteInfo(); + for (const r of results) { + [r._styleId, r._styleVars] = info[r.i] || []; + } } if (!keepYears) resultsAllYears = results; renderYears(); @@ -330,7 +336,7 @@ function createSearchResultNode(result) { const entry = t.template.searchResult.cloneNode(true); const { - i: id, + i: rid, n: name, r: rating, u: updateTime, @@ -342,7 +348,8 @@ sn: shot, f: fmt, } = entry._result = result; - entry.id = RESULT_ID_PREFIX + id; + const id = rid2id(rid); + entry.id = RESULT_ID_PREFIX + rid; // title Object.assign($('.search-result-title', entry), { onclick: Events.openURLandHide, @@ -421,22 +428,19 @@ } } - function renderActionButtons(entry, installedId) { - if (Number(entry)) { + function renderActionButtons(entry) { + if (typeof entry !== 'object') { entry = $('#' + RESULT_ID_PREFIX + entry); } if (!entry) return; const result = entry._result; - if (typeof installedId === 'number') { - result._installed = installedId > 0; - result._installedStyleId = installedId; - } - const isInstalled = result._installed; + const installedId = result._styleId; + const isInstalled = installedId > 0; // must be boolean for comparisons below const status = $('.search-result-status', entry).textContent = isInstalled ? t('clickToUninstall') : entry.dataset.noImage != null ? t('installButton') : ''; - const notMatching = installedId > 0 && !$entry(installedId); + const notMatching = isInstalled && !$entry(installedId); if (notMatching !== entry.classList.contains('not-matching')) { entry.classList.toggle('not-matching'); if (notMatching) { @@ -456,6 +460,7 @@ disabled: notMatching, }); toggleDataset(entry, 'installed', isInstalled); + toggleDataset(entry, 'customizable', result._styleVars); } function renderFullInfo(entry, style) { @@ -469,17 +474,20 @@ textContent: description, title: description, }); + vars = !isEmptyObj(vars); + entry._result._styleVars = vars; toggleDataset(entry, 'customizable', vars); } function configure() { - const styleEntry = $entry($resultEntry(this).result._installedStyleId); + const styleEntry = $entry($resultEntry(this).result._styleId); Events.configure.call(this, {target: styleEntry}); } async function install() { const {entry, result} = $resultEntry(this); - const {i: id, f: fmt} = result; + const {f: fmt} = result; + const id = rid2id(result.i); const installButton = $('.search-result-install', entry); showSpinner(entry); @@ -507,7 +515,7 @@ function uninstall() { const {entry, result} = $resultEntry(this); saveScrollPosition(entry); - API.styles.delete(result._installedStyleId); + API.styles.delete(result._styleId); } function saveScrollPosition(entry) { @@ -553,10 +561,11 @@ async function fetchIndex() { const timer = setTimeout(showSpinner, BUSY_DELAY, dom.list); const jobs = [ - [INDEX_URL, json => json.filter(entry => entry.f === 'uso')], - [USW_INDEX_URL, json => json.data], - ].map(async ([url, transform]) => { + [INDEX_URL, 'uso', json => json.filter(v => v.f === 'uso')], + [USW_INDEX_URL, 'usw', json => json.data], + ].map(async ([url, prefix, transform]) => { const res = transform(await download(url, {responseType: 'json'})); + for (const v of res) v.i = `${prefix}-${v.i}`; index = index ? index.concat(res) : res; if (index !== res) ready = ready.then(start); }); @@ -607,17 +616,4 @@ : b[order] - a[order] ) || b.t - a.t; } - - function calcUsoId({md5Url: m, updateUrl}) { - return Number(m && m.match(/\d+|$/)[0]) || - URLS.extractUsoArchiveId(updateUrl); - } - - function calcUswId({updateUrl}) { - return URLS.extractUSwId(updateUrl) || 0; - } - - function calcId(style) { - return calcUsoId(style) || calcUswId(style); - } })();