extract popup search + show error + CtrlF

This commit is contained in:
tophf 2022-04-05 13:07:24 +03:00
parent 3907116328
commit a74ae5ac4f
5 changed files with 139 additions and 119 deletions

View File

@ -1,4 +1,4 @@
/* global $$ waitForSelector */// dom.js /* global $$ $ waitForSelector */// dom.js
/* global download */// toolbox.js /* global download */// toolbox.js
'use strict'; 'use strict';
@ -117,13 +117,22 @@ Object.assign(t, {
return t.toFragment(root); return t.toFragment(root);
}, },
fetchTemplate: async (url, name) => t.template[name] || fetchTemplate: async (url, name) => {
t.createTemplate({ let res = t.template[name];
content: t.toFragment(t.parse(await download(url))), if (!res) {
dataset: {id: name}, res = t.parse(await download(url), '*');
}), if (!$$('template', res).map(t.createTemplate).length) {
t.createTemplate({
content: t.toFragment($('body', res)),
dataset: {id: name},
});
}
res = t.template[name];
}
return res;
},
parse: str => new DOMParser().parseFromString(str, 'text/html').body, parse: (str, pick = 'body') => $(pick, new DOMParser().parseFromString(str, 'text/html')),
sanitizeHtml(root) { sanitizeHtml(root) {
const toRemove = []; const toRemove = [];

View File

@ -98,65 +98,6 @@
</div> </div>
</template> </template>
<template data-id="searchResult">
<div class="search-result">
<a class="search-result-title"><span></span></a>
<div class="search-result-info">
<img class="search-result-screenshot" i18n="title:installButton">
<div class="search-result-status"></div>
<div class="search-result-actions">
<button class="search-result-install" i18n="installButton"></button>
<button class="search-result-uninstall" i18n="deleteStyleLabel"></button>
<button class="search-result-customize" i18n="configureStyle"></button>
</div>
<dl class="search-result-meta">
<div data-type="author">
<dt i18n="author"></dt>
<dd><a target="_blank" i18n="title:author"></a></dd>
</div>
<div data-type="rating">
<dt i18n="searchResultRating"></dt>
<dd i18n="title:searchResultRating"></dd>
</div>
<div data-type="updated">
<dt i18n="searchResultUpdated"></dt>
<dd i18n="title:searchResultUpdated"><time></time></dd>
</div>
<div data-type="weekly">
<dt i18n="searchResultWeeklyCount"></dt>
<dd i18n="title:searchResultWeeklyCount"></dd>
</div>
<div data-type="total">
<dt i18n="searchResultInstallCount"></dt>
<dd i18n="title:searchResultInstallCount"></dd>
</div>
</dl>
<div class="search-result-description"></div>
</div>
</div>
</template>
<template data-id="searchNav">
<div>
<button data-type="prev" i18n="title:paginationPrevious" disabled></button>
<label>
<span data-type="page" i18n="title:paginationCurrent">-</span>
/
<span data-type="total" i18n="title:paginationEstimated">-</span>
</label>
<button data-type="next" i18n="title:paginationNext" disabled></button>
</div>
</template>
<template data-id="emptySearchResult">
<div class="search-result-empty"></div>
</template>
<template data-id="searchResultNotMatching">
<p class="not-matching-explainer"
i18n="searchResultNotMatching, title:searchResultNotMatchingNote"></p>
</template>
<script src="js/polyfill.js"></script> <script src="js/polyfill.js"></script>
<script src="js/msg.js"></script> <script src="js/msg.js"></script>
<script src="js/toolbox.js"></script> <script src="js/toolbox.js"></script>
@ -214,7 +155,7 @@
><button class="if-not-blocked split-btn-pedal" i18n="menu-site:popupManageSiteStyles"></button> ><button class="if-not-blocked split-btn-pedal" i18n="menu-site:popupManageSiteStyles"></button>
</div> </div>
<div class="split-btn if-not-blocked" id="find-split"> <div class="split-btn if-not-blocked" id="find-split">
<button id="find-styles-btn" i18n="findStyles"></button <button id="find-styles-btn" i18n="findStyles" title="Ctrl-F"></button
><button class="split-btn-pedal" ><button class="split-btn-pedal"
menu-usoa="UserStyles Archive" menu-usoa="UserStyles Archive"
menu-usw="UserStyles World" menu-usw="UserStyles World"
@ -231,33 +172,6 @@
</a> </a>
</div> </div>
<div id="search-results-error" class="hidden"></div>
<div id="search-results" class="hidden">
<div class="search-results-nav" data-type="top"></div>
<div id="search-params">
<input id="search-query" type="search" i18n="placeholder:search, title:searchStyleQueryHint">
<div class="select-resizer">
<select id="search-order" i18n="title:sortStylesHelpTitle">
<option value="n" i18n="genericTitle">
<option value="u" i18n="searchResultUpdated">
<option value="t" i18n="searchResultInstallCount">
<option value="w" i18n="searchResultWeeklyCount">
<option value="r" i18n="searchResultRating">
</select>
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
</div>
<label>
<span class="checkbox-container">
<input id="search-globals" type="checkbox" checked>
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</span>
<span i18n="searchGlobalStyles"></span>
</label>
</div>
<div id="search-results-list"></div>
<div class="search-results-nav" data-type="bottom"></div>
</div>
<!-- Here we can use the above elements before DOMContentLoaded --> <!-- Here we can use the above elements before DOMContentLoaded -->
<script src="popup/events.js"></script> <script src="popup/events.js"></script>
<script src="popup/popup.js"></script> <script src="popup/popup.js"></script>

View File

@ -1,4 +1,4 @@
/* global $ $$ $create setupLivePrefs */// dom.js /* global $ $$ $create getEventKeyName setupLivePrefs */// dom.js
/* global ABOUT_BLANK getStyleDataMerged preinit */// preinit.js /* global ABOUT_BLANK getStyleDataMerged preinit */// preinit.js
/* global API msg */// msg.js /* global API msg */// msg.js
/* global Events */ /* global Events */
@ -86,13 +86,30 @@ async function initPopup(frames) {
setupLivePrefs(); setupLivePrefs();
const elFind = $('#find-styles-btn'); const elFind = $('#find-styles-btn');
elFind.onclick = async e => { const elFindDeps = async () => {
const inline = e.type === 'click'; if (!t.template.searchUI) {
if (inline) elFind.disabled = true; document.body.append(await t.fetchTemplate('/popup/search.html', 'searchUI'));
await require(['/popup/search']); }
if (!inline) Events.searchSite(e); await require([
'/popup/search.css',
'/popup/search',
]);
}; };
elFind.on('split-btn', elFind.onclick); elFind.on('click', async () => {
elFind.disabled = true;
await elFindDeps();
Events.searchInline();
});
elFind.on('split-btn', async e => {
await elFindDeps();
Events.searchSite(e);
});
window.on('keydown', e => {
if (getEventKeyName(e) === 'Ctrl-F') {
e.preventDefault();
elFind.click();
}
});
Object.assign($('#popup-manage-button'), { Object.assign($('#popup-manage-button'), {
onclick: Events.openManager, onclick: Events.openManager,

84
popup/search.html Normal file
View File

@ -0,0 +1,84 @@
<template data-id="searchUI">
<div id="search-results-error" hidden></div>
<div id="search-results" hidden>
<div class="search-results-nav" data-type="top"></div>
<div id="search-params">
<input id="search-query" type="search" i18n="placeholder:search, title:searchStyleQueryHint">
<div class="select-resizer">
<select id="search-order" i18n="title:sortStylesHelpTitle">
<option value="n" i18n="genericTitle">
<option value="u" i18n="searchResultUpdated">
<option value="t" i18n="searchResultInstallCount">
<option value="w" i18n="searchResultWeeklyCount">
<option value="r" i18n="searchResultRating">
</select>
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
</div>
<label class="checkbox-wrapper" i18n="searchGlobalStyles">
<input id="search-globals" type="checkbox" checked>
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label>
</div>
<div id="search-results-list"></div>
<div class="search-results-nav" data-type="bottom"></div>
</div>
</template>
<template data-id="searchNav">
<div>
<button data-type="prev" i18n="title:paginationPrevious" disabled></button>
<label>
<span data-type="page" i18n="title:paginationCurrent">-</span>
/
<span data-type="total" i18n="title:paginationEstimated">-</span>
</label>
<button data-type="next" i18n="title:paginationNext" disabled></button>
</div>
</template>
<template data-id="searchResult">
<div class="search-result">
<a class="search-result-title"><span></span></a>
<div class="search-result-info">
<img class="search-result-screenshot" i18n="title:installButton">
<div class="search-result-status"></div>
<div class="search-result-actions">
<button class="search-result-install" i18n="installButton"></button>
<button class="search-result-uninstall" i18n="deleteStyleLabel"></button>
<button class="search-result-customize" i18n="configureStyle"></button>
</div>
<dl class="search-result-meta">
<div data-type="author">
<dt i18n="author"></dt>
<dd><a target="_blank" i18n="title:author"></a></dd>
</div>
<div data-type="rating">
<dt i18n="searchResultRating"></dt>
<dd i18n="title:searchResultRating"></dd>
</div>
<div data-type="updated">
<dt i18n="searchResultUpdated"></dt>
<dd i18n="title:searchResultUpdated"><time></time></dd>
</div>
<div data-type="weekly">
<dt i18n="searchResultWeeklyCount"></dt>
<dd i18n="title:searchResultWeeklyCount"></dd>
</div>
<div data-type="total">
<dt i18n="searchResultInstallCount"></dt>
<dd i18n="title:searchResultInstallCount"></dd>
</div>
</dl>
<div class="search-result-description"></div>
</div>
</div>
</template>
<template data-id="emptySearchResult">
<div class="search-result-empty"></div>
</template>
<template data-id="searchResultNotMatching">
<p class="not-matching-explainer"
i18n="searchResultNotMatching, title:searchResultNotMatchingNote"></p>
</template>

View File

@ -8,8 +8,6 @@
'use strict'; 'use strict';
(() => { (() => {
require(['/popup/search.css']);
const RESULT_ID_PREFIX = t.template.searchResult.className + '-'; const RESULT_ID_PREFIX = t.template.searchResult.className + '-';
const RESULT_SEL = '.' + t.template.searchResult.className; const RESULT_SEL = '.' + t.template.searchResult.className;
const INDEX_URL = URLS.usoArchiveRaw[0] + 'search-index.json'; const INDEX_URL = URLS.usoArchiveRaw[0] + 'search-index.json';
@ -71,10 +69,10 @@
const entry = el.closest(RESULT_SEL); const entry = el.closest(RESULT_SEL);
return {entry, result: entry && entry._result}; return {entry, result: entry && entry._result};
}; };
const $classList = sel => (sel instanceof Node ? sel : $(sel)).classList; Events.searchInline = () => {
const show = sel => $classList(sel).remove('hidden'); calcCategory();
const hide = sel => $classList(sel).add('hidden'); ready = start();
};
Events.searchSite = event => { Events.searchSite = event => {
// use a less specific category if the inline search wasn't used yet // use a less specific category if the inline search wasn't used yet
if (!category) calcCategory({retry: 1}); if (!category) calcCategory({retry: 1});
@ -94,7 +92,6 @@
`/scripts/by-site/${tryURL(tabURL).hostname.replace(/^www\./, '')}?language=css${add('&q=', q)}`; `/scripts/by-site/${tryURL(tabURL).hostname.replace(/^www\./, '')}?language=css${add('&q=', q)}`;
Events.openURLandHide.call({href}, event); Events.openURLandHide.call({href}, event);
}; };
$('#search-globals').onchange = function () { $('#search-globals').onchange = function () {
searchGlobals = this.checked; searchGlobals = this.checked;
ready = ready.then(start); ready = ready.then(start);
@ -165,9 +162,6 @@
} }
}); });
calcCategory();
ready = start();
function next() { function next() {
displayedPage = Math.min(totalPages, displayedPage + 1); displayedPage = Math.min(totalPages, displayedPage + 1);
scrollToFirstResult = true; scrollToFirstResult = true;
@ -182,15 +176,15 @@
function error(reason) { function error(reason) {
dom.error.textContent = reason; dom.error.textContent = reason;
show(dom.error); dom.error.hidden = false;
hide(dom.list); dom.list.hidden = true;
if (dom.error.getBoundingClientRect().bottom < 0) { if (dom.error.getBoundingClientRect().bottom < 0) {
dom.error.scrollIntoView({behavior: 'smooth', block: 'start'}); dom.error.scrollIntoView({behavior: 'smooth', block: 'start'});
} }
} }
async function start() { async function start() {
resetUI.timer = setTimeout(resetUI, 500); resetUI.timer = resetUI.timer || setTimeout(resetUI, 500);
try { try {
results = []; results = [];
for (let retry = 0; !results.length && retry <= 2; retry++) { for (let retry = 0; !results.length && retry <= 2; retry++) {
@ -202,22 +196,24 @@
results = results.filter(r => !allSupportedIds.has(r.i)); results = results.filter(r => !allSupportedIds.has(r.i));
} }
render(); render();
(results.length ? show : hide)(dom.list); dom.list.hidden = !results.length;
if (!results.length && !$('#search-query').value) { if (!results.length && !$('#search-query').value) {
error(t('searchResultNoneFound')); error(t('searchResultNoneFound'));
} else {
resetUI();
} }
} catch (reason) { } catch (reason) {
error(reason); error(reason);
} }
clearTimeout(resetUI.timer); clearTimeout(resetUI.timer);
resetUI(); resetUI.timer = 0;
} }
function resetUI() { function resetUI() {
document.body.classList.add('search-results-shown'); document.body.classList.add('search-results-shown');
show(dom.container); dom.container.hidden = false;
show(dom.list); dom.list.hidden = false;
hide(dom.error); dom.error.hidden = true;
} }
function render() { function render() {