add year selector in popup search (#1411)
This commit is contained in:
parent
9d3fa1c5f9
commit
5379f62c90
|
@ -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": {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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%;
|
||||||
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
121
popup/search.js
121
popup/search.js
|
@ -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;
|
|
||||||
}
|
|
||||||
})();
|
})();
|
||||||
|
|
Loading…
Reference in New Issue
Block a user