add year selector in popup search (#1411)

This commit is contained in:
tophf 2022-08-27 21:57:14 +03:00 committed by GitHub
parent 9d3fa1c5f9
commit 5379f62c90
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 113 additions and 53 deletions

View File

@ -1434,7 +1434,7 @@
"description": "Text for label that shows the number of times a search result was installed during last week" "description": "Text for label that shows the number of times a search result was installed during last week"
}, },
"searchStyleQueryHint": { "searchStyleQueryHint": {
"message": "Search style names case-insensitively:\nsome words - all words in any order\n\"some phrase\" - this exact phrase without quotes\n2020 - a year like this also shows styles updated in 2020", "message": "Search style names (case-sensitively if an uppercase letter is used):\nsome words - all these words in any order\n\"some phrase\" - this exact phrase without quotes\n/foo.*bar/i - regular expression without spaces (use \\s instead)",
"description": "Tooltip shown for the text input in the popup's inline style finder" "description": "Tooltip shown for the text input in the popup's inline style finder"
}, },
"searchStylesAll": { "searchStylesAll": {

View File

@ -624,11 +624,11 @@ a:hover .svg-icon {
fill: transparent; fill: transparent;
stroke: currentColor; stroke: currentColor;
} }
html:not(.styles-last) #popup-options .split-btn-menu { html:not(.styles-last):not(.search-results-shown) #popup-options .split-btn-menu {
bottom: 0; bottom: 0;
transform: translateY(-20px); /* global button style: 13(font) * 1.2(line) + 4(pad) + 2(border) */ transform: translateY(-20px); /* global button style: 13(font) * 1.2(line) + 4(pad) + 2(border) */
} }
html:not(.styles-last) .split-btn-pedal::after { html:not(.styles-last):not(.search-results-shown) .split-btn-pedal::after {
border-top: var(--side) solid transparent; border-top: var(--side) solid transparent;
border-bottom: calc(var(--side) * 1.3) solid currentColor; border-bottom: calc(var(--side) * 1.3) solid currentColor;
vertical-align: top; vertical-align: top;

View File

@ -2,7 +2,7 @@
This file is loaded dynamically when the inline search is invoked. This file is loaded dynamically when the inline search is invoked.
So don't put main popup's stuff here. */ So don't put main popup's stuff here. */
body.search-results-shown { .search-results-shown body {
overflow-y: auto; overflow-y: auto;
overflow-x: hidden; overflow-x: hidden;
} }
@ -288,35 +288,39 @@ search-result-meta [data-type="rating"][data-class="none"] dd {
flex-direction: row; flex-direction: row;
text-align: center; text-align: center;
word-break: keep-all; word-break: keep-all;
line-height: 24px;
font-size: 16px;
} }
.search-results-nav[data-type="top"] { .search-results-nav[data-type="top"] {
padding-top: 1em; padding-top: 1em;
margin-bottom: 1em; margin-bottom: 1em;
} }
.search-results-nav[data-type="bottom"] { .search-results-nav[data-type="bottom"] {
margin-top: -1em; margin-top: -1em;
margin-bottom: 1em; margin-bottom: 1em;
} }
.search-results-nav label {
vertical-align: middle;
-moz-user-select: none;
user-select: none;
}
.search-results-nav [data-type="page"] {
font-weight: bold;
}
#search-results .search-results-nav button { #search-results .search-results-nav button {
background: none; background: none;
border: none; border: none;
padding: 0 1rem; padding: 0 .5rem;
margin: 0 .5rem; margin: 0 .5rem;
font-size: 150%; font-size: 18px;
line-height: 24px;
vertical-align: middle; vertical-align: middle;
cursor: pointer; cursor: pointer;
} }
#search-results .search-results-nav button:disabled { #search-results .search-results-nav button:disabled {
cursor: auto; cursor: auto;
opacity: .25; opacity: .25;
pointer-events: none; pointer-events: none;
} }
#search-results .search-results-nav button:not(:disabled):hover { #search-results .search-results-nav button:not(:disabled):hover {
text-shadow: 0 1px 4px rgba(0, 0, 0, .5); text-shadow: 0 1px 4px rgba(0, 0, 0, .5);
} }
@ -338,3 +342,8 @@ search-result-meta [data-type="rating"][data-class="none"] dd {
margin-right: .5em; margin-right: .5em;
flex: 1 1 0; flex: 1 1 0;
} }
#search-years {
text-align: center;
width: 100%;
}

View File

@ -3,6 +3,17 @@
<div id="search-results" hidden> <div id="search-results" hidden>
<div class="search-results-nav" data-type="top"></div> <div class="search-results-nav" data-type="top"></div>
<div id="search-params"> <div id="search-params">
<div id="search-years">
<div class="select-resizer">
<select></select>
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
</div>
-
<div class="select-resizer">
<select></select>
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
</div>
</div>
<input id="search-query" type="search" i18n="placeholder:search, title:searchStyleQueryHint"> <input id="search-query" type="search" i18n="placeholder:search, title:searchStyleQueryHint">
<div class="select-resizer"> <div class="select-resizer">
<select id="search-order" i18n="title:sortStylesHelpTitle"> <select id="search-order" i18n="title:sortStylesHelpTitle">
@ -31,6 +42,7 @@
<span data-type="page" i18n="title:paginationCurrent">-</span> <span data-type="page" i18n="title:paginationCurrent">-</span>
/ /
<span data-type="total" i18n="title:paginationEstimated">-</span> <span data-type="total" i18n="title:paginationEstimated">-</span>
(<span data-type="num"></span>)
</label> </label>
<button data-type="next" i18n="title:paginationNext" disabled></button> <button data-type="next" i18n="title:paginationNext" disabled></button>
</div> </div>

View File

@ -2,7 +2,7 @@
/* global $entry tabURL */// popup.js /* global $entry tabURL */// popup.js
/* global API */// msg.js /* global API */// msg.js
/* global Events */ /* global Events */
/* global FIREFOX URLS debounce download tryURL */// toolbox.js /* global FIREFOX URLS debounce download stringAsRegExp tryRegExp tryURL */// toolbox.js
/* global prefs */ /* global prefs */
/* global t */// localization.js /* global t */// localization.js
'use strict'; 'use strict';
@ -17,7 +17,7 @@
title: URLS.usw, title: URLS.usw,
}); });
const STYLUS_CATEGORY = 'chrome-extension'; const STYLUS_CATEGORY = 'chrome-extension';
const PAGE_LENGTH = 10; const PAGE_LENGTH = 100;
// update USO style install counter if the style isn't uninstalled immediately // update USO style install counter if the style isn't uninstalled immediately
const PINGBACK_DELAY = 5e3; const PINGBACK_DELAY = 5e3;
const BUSY_DELAY = .5e3; const BUSY_DELAY = .5e3;
@ -38,17 +38,20 @@
* @prop {string} an - authorName * @prop {string} an - authorName
* @prop {string} sn - screenshotName * @prop {string} sn - screenshotName
* @prop {boolean} sa - screenshotArchived * @prop {boolean} sa - screenshotArchived
* --------------------- Stylus' internally added extras *
* @prop {boolean} installed * @prop {boolean} _installed
* @prop {number} installedStyleId * @prop {number} _installedStyleId
* @prop {number} _year
*/ */
/** @type IndexEntry[] */ /** @type IndexEntry[] */
let results; let results, resultsAllYears;
/** @type IndexEntry[] */ /** @type IndexEntry[] */
let index; let index;
let category = ''; let category = '';
/** @type RegExp */
let rxCategory;
let searchGlobals = $('#search-globals').checked; let searchGlobals = $('#search-globals').checked;
/** @type string[] */ /** @type {RegExp[]} */
let query = []; let query = [];
let order = prefs.get('popup.findSort'); let order = prefs.get('popup.findSort');
let scrollToFirstResult = true; let scrollToFirstResult = true;
@ -97,17 +100,24 @@
}; };
$('#search-query').oninput = function () { $('#search-query').oninput = function () {
query = []; query = [];
const text = this.value.trim().toLocaleLowerCase(); const text = this.value.trim();
const thisYear = new Date().getFullYear(); for (let re = /(")(.+?)"|((\/)?(\S+?)(\/\w*)?)(?=\s|$)/g, m; (m = re.exec(text));) {
for (let re = /"(.+?)"|(\S+)/g, m; (m = re.exec(text));) { const [
const n = Number(m[2]); all,
query.push(n >= 2000 && n <= thisYear ? n : m[1] || m[2]); q, qt,
t, rx1 = '', rx, rx2 = '',
] = m;
query.push(rx1 && rx2 && tryRegExp(rx, rx2.slice(1)) ||
stringAsRegExp(q ? qt : t, all === all.toLocaleLowerCase() ? 'i' : ''));
} }
if (category === STYLUS_CATEGORY && !query.includes('stylus')) { if (category === STYLUS_CATEGORY) {
query.push('stylus'); query.push(/\bStylus\b/);
} }
ready = ready.then(start); ready = ready.then(start);
}; };
$('#search-years').onchange = () => {
ready = ready.then(() => start({keepYears: true}));
};
$('#search-order').value = order; $('#search-order').value = order;
$('#search-order').onchange = function () { $('#search-order').onchange = function () {
order = this.value; order = this.value;
@ -146,7 +156,7 @@
window.on('styleDeleted', ({detail: {style: {id}}}) => { window.on('styleDeleted', ({detail: {style: {id}}}) => {
restoreScrollPosition(); restoreScrollPosition();
const result = results.find(r => r.installedStyleId === id); const result = results.find(r => r._installedStyleId === id);
if (result) { if (result) {
API.uso.pingback(result.i, false); API.uso.pingback(result.i, false);
renderActionButtons(result.i, -1); renderActionButtons(result.i, -1);
@ -178,11 +188,11 @@
dom.error.hidden = false; dom.error.hidden = false;
dom.list.hidden = true; 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(true);
} }
} }
async function start() { async function start({keepYears} = {}) {
resetUI.timer = resetUI.timer || setTimeout(resetUI, 500); resetUI.timer = resetUI.timer || setTimeout(resetUI, 500);
try { try {
results = []; results = [];
@ -194,6 +204,8 @@
const allSupportedIds = new Set(installedStyles.map(calcId)); const allSupportedIds = new Set(installedStyles.map(calcId));
results = results.filter(r => !allSupportedIds.has(r.i)); results = results.filter(r => !allSupportedIds.has(r.i));
} }
if (!keepYears) resultsAllYears = results;
renderYears();
render(); render();
dom.list.hidden = !results.length; dom.list.hidden = !results.length;
if (!results.length && !$('#search-query').value) { if (!results.length && !$('#search-query').value) {
@ -201,6 +213,7 @@
} else { } else {
resetUI(); resetUI();
} }
resetUI();
} catch (reason) { } catch (reason) {
error(reason); error(reason);
} }
@ -209,12 +222,44 @@
} }
function resetUI() { function resetUI() {
document.body.classList.add('search-results-shown'); $.rootCL.add('search-results-shown');
dom.container.hidden = false; dom.container.hidden = false;
dom.list.hidden = false; dom.list.hidden = false;
dom.error.hidden = true; dom.error.hidden = true;
} }
function renderYears() {
const SCALE = 1000;
const BASE = new Date(0).getFullYear(); // 1970
const DAYS = 365.2425;
const DAY = 24 * 3600e3;
const YEAR = DAYS * DAY / SCALE;
const SAFETY = 1 / DAYS; // 1 day safety margin: recheck Jan 1 and Dec 31
const years = [];
for (const r of resultsAllYears) {
let y = r._year;
if (!y) {
y = r.u / YEAR + BASE;
r._year = y = Math.abs(y % 1 - 1) <= SAFETY
? new Date(r.u * SCALE).getFullYear()
: y | 0;
}
years[y] = (years[y] || 0) + 1;
}
const texts = years.reduceRight((res, num, y) => res.push(`${y} (${num})`) && res, []);
const selects = $$('#search-years select');
selects.forEach((sel, selNum) => {
if (texts.length !== sel.length || texts.some((v, i) => v !== sel[i].text)) {
const {value} = sel;
sel.textContent = '';
sel.append(...texts.map(t => $create('option', {value: t.split(' ')[0]}, t)));
sel.value = value in years ? value : (sel[`${selNum ? 'first' : 'last'}Child`] || {}).value;
}
});
const [y1, y2] = selects.map(el => Number(el.value)).sort();
results = y1 ? resultsAllYears.filter(r => (r = r._year) >= y1 && r <= y2) : resultsAllYears;
}
function render() { function render() {
totalPages = Math.ceil(results.length / PAGE_LENGTH); totalPages = Math.ceil(results.length / PAGE_LENGTH);
displayedPage = Math.min(displayedPage, totalPages) || 1; displayedPage = Math.min(displayedPage, totalPages) || 1;
@ -262,13 +307,14 @@
nav._next.disabled = displayedPage >= totalPages; nav._next.disabled = displayedPage >= totalPages;
nav._page.textContent = displayedPage; nav._page.textContent = displayedPage;
nav._total.textContent = totalPages; nav._total.textContent = totalPages;
nav._num.textContent = results.length;
} }
} }
function doScrollToFirstResult() { function doScrollToFirstResult() {
if (dom.container.scrollHeight > window.innerHeight * 2) { if (dom.container.scrollHeight > window.innerHeight * 2) {
scrollToFirstResult = false; scrollToFirstResult = false;
dom.container.scrollIntoView({behavior: 'smooth', block: 'start'}); dom.container.scrollIntoView(true);
} }
} }
@ -377,10 +423,10 @@
if (!entry) return; if (!entry) return;
const result = entry._result; const result = entry._result;
if (typeof installedId === 'number') { if (typeof installedId === 'number') {
result.installed = installedId > 0; result._installed = installedId > 0;
result.installedStyleId = installedId; result._installedStyleId = installedId;
} }
const isInstalled = result.installed; const isInstalled = result._installed;
const status = $('.search-result-status', entry).textContent = const status = $('.search-result-status', entry).textContent =
isInstalled ? t('clickToUninstall') : isInstalled ? t('clickToUninstall') :
entry.dataset.noImage != null ? t('installButton') : entry.dataset.noImage != null ? t('installButton') :
@ -422,7 +468,7 @@
} }
function configure() { function configure() {
const styleEntry = $entry($resultEntry(this).result.installedStyleId); const styleEntry = $entry($resultEntry(this).result._installedStyleId);
Events.configure.call(this, {target: styleEntry}); Events.configure.call(this, {target: styleEntry});
} }
@ -456,7 +502,7 @@
function uninstall() { function uninstall() {
const {entry, result} = $resultEntry(this); const {entry, result} = $resultEntry(this);
saveScrollPosition(entry); saveScrollPosition(entry);
API.styles.delete(result.installedStyleId); API.styles.delete(result._installedStyleId);
} }
function saveScrollPosition(entry) { function saveScrollPosition(entry) {
@ -495,6 +541,7 @@
); );
category = (keepThird && `${third}.` || '') + main + (keepTld || keepThird ? `.${tld}` : ''); category = (keepThird && `${third}.` || '') + main + (keepTld || keepThird ? `.${tld}` : '');
} }
rxCategory = new RegExp(`\\b${stringAsRegExp(category, '', true)}\\b`, 'i');
return category !== old; return category !== old;
} }
@ -519,31 +566,29 @@
} }
async function search({retry} = {}) { async function search({retry} = {}) {
return retry && !calcCategory({retry}) return retry && !query.length && !calcCategory({retry})
? [] ? []
: (index || await fetchIndex()).filter(isResultMatching).sort(comparator); : (index || await fetchIndex()).filter(isResultMatching).sort(comparator);
} }
function isResultMatching(res) { function isResultMatching(res) {
// We're trying to call calcHaystack only when needed, not on all 100K items
const {c} = res; const {c} = res;
return ( return (
c === category || c === category ||
(category === STYLUS_CATEGORY (category === STYLUS_CATEGORY
? c === 'stylus' // USW ? c === 'stylus' // USW
: c === 'global' && searchGlobals && : c === 'global' && searchGlobals &&
(query.length || calcHaystack(res)._nLC.includes(category)) (query.length || rxCategory.test(res.n))
) )
) && ( ) && query.every(isInHaystack, res);
!query.length || // to skip calling calcHaystack
query.every(isInHaystack, calcHaystack(res))
);
} }
/** @this {IndexEntry} haystack */ /**
function isInHaystack(needle) { * @this {IndexEntry} haystack
return this._year === needle && this.c !== 'global' || * @param {RegExp} q
this._nLC.includes(needle); */
function isInHaystack(q) {
return q.test(this.n);
} }
/** /**
@ -570,10 +615,4 @@
function calcId(style) { function calcId(style) {
return calcUsoId(style) || calcUswId(style); return calcUsoId(style) || calcUswId(style);
} }
function calcHaystack(res) {
if (!res._nLC) res._nLC = res.n.toLocaleLowerCase();
if (!res._year) res._year = new Date(res.u * 1000).getFullYear();
return res;
}
})(); })();