diff --git a/js/dom.js b/js/dom.js
index 86ebf7c6..fb82ba8c 100644
--- a/js/dom.js
+++ b/js/dom.js
@@ -337,10 +337,11 @@ async function showSpinner(parent) {
}
function toggleDataset(el, prop, state) {
+ const wasEnabled = el.dataset[prop] != null; // avoids mutating DOM unnecessarily
if (state) {
- el.dataset[prop] = '';
+ if (!wasEnabled) el.dataset[prop] = '';
} else {
- delete el.dataset[prop];
+ if (wasEnabled) delete el.dataset[prop];
}
}
diff --git a/popup.html b/popup.html
index 31f99be9..f8ef4983 100644
--- a/popup.html
+++ b/popup.html
@@ -104,9 +104,9 @@
diff --git a/popup/search.css b/popup/search.css
index dba75ef3..0ad3863a 100644
--- a/popup/search.css
+++ b/popup/search.css
@@ -102,7 +102,7 @@ body.search-results-shown {
position: relative;
}
-.search-result[data-installed] .search-result-status {
+.search-result .search-result-status {
top: 0;
left: 0;
right: 0;
@@ -114,9 +114,16 @@ body.search-results-shown {
transition: background-color .5s;
pointer-events: none;
}
-
+.search-result[data-no-image] .search-result-status {
+ line-height: 140px;
+}
+.search-result[data-no-image]:not([data-installed]) .search-result-status {
+ background-color: rgba(128, 128, 128, .1);
+ color: currentColor;
+}
.search-result-screenshot:hover ~ .search-result-status {
background-color: hsla(180, 100%, 27%, 1);
+ color: #fff;
}
.search-result-actions {
@@ -131,16 +138,26 @@ body.search-results-shown {
opacity: 0;
transition: opacity .5s;
}
-
.search-result:not([data-installed]):hover .search-result-actions {
opacity: 1;
}
-
.search-result-actions button {
box-shadow: 2px 2px 20px #000;
white-space: nowrap;
margin: 3px;
}
+.search-result[data-no-image] .search-result-actions button {
+ box-shadow: none;
+}
+.search-result-install,
+.search-result-uninstall,
+.search-result:not([data-installed]) .search-result-customize,
+.search-result:not([data-customizable]) .search-result-customize {
+ display: none;
+}
+.search-result.not-matching .search-result-customize {
+ opacity: .5;
+}
.search-result-meta {
background-color: hsla(0, 0%, 93%, 0.75);
diff --git a/popup/search.js b/popup/search.js
index bdbeb636..d7a560d6 100644
--- a/popup/search.js
+++ b/popup/search.js
@@ -1,4 +1,4 @@
-/* global $ $$ $create $remove showSpinner */// dom.js
+/* global $ $$ $create $remove showSpinner toggleDataset */// dom.js
/* global $entry tabURL */// popup.js
/* global API */// msg.js
/* global Events */
@@ -10,7 +10,8 @@
(() => {
require(['/popup/search.css']);
- const RESULT_ID_PREFIX = 'search-result-';
+ const RESULT_ID_PREFIX = t.template.searchResult.className + '-';
+ const RESULT_SEL = '.' + t.template.searchResult.className;
const INDEX_URL = URLS.usoArchiveRaw[0] + 'search-index.json';
const USW_INDEX_URL = URLS.usw + 'api/index/uso-format';
const USW_ICON = $create('img', {
@@ -39,6 +40,11 @@
* @prop {string} an - authorName
* @prop {string} sn - screenshotName
* @prop {boolean} sa - screenshotArchived
+ * --------------------- Stylus' internally added extras
+ * @prop {boolean} isUsw
+ * @prop {boolean} installed
+ * @prop {number} installedStyleId
+ * @prop {number} pingbackTimer
*/
/** @type IndexEntry[] */
let results;
@@ -61,9 +67,14 @@
onload: () => (imgType = '.webp'),
});
- const $class = sel => (sel instanceof Node ? sel : $(sel)).classList;
- const show = sel => $class(sel).remove('hidden');
- const hide = sel => $class(sel).add('hidden');
+ /** @returns {{result: IndexEntry, entry: HTMLElement}} */
+ const $resultEntry = el => {
+ const entry = el.closest(RESULT_SEL);
+ return {entry, result: entry && entry._result};
+ };
+ const $classList = sel => (sel instanceof Node ? sel : $(sel)).classList;
+ const show = sel => $classList(sel).remove('hidden');
+ const hide = sel => $classList(sel).add('hidden');
Object.assign(Events, {
/**
@@ -215,7 +226,7 @@
// keep rendered elements with ids in the range of interest
while (
plantAt < PAGE_LENGTH &&
- slot && slot.id === 'search-result-' + (results[start] || {}).i
+ slot && slot.id === RESULT_ID_PREFIX + (results[start] || {}).i
) {
slot = slot.nextElementSibling;
plantAt++;
@@ -292,19 +303,22 @@
t.breakWord(name.length < 300 ? name : name.slice(0, 300) + '...');
// screenshot
const elShot = $('.search-result-screenshot', entry);
+ let shotSrc;
if (isUsw) {
- elShot.src = !/^https?:/i.test(shot) ? BLANK_PIXEL :
- imgType !== '.jpg' ? shot.replace(/\.jpg$/, imgType) :
- shot;
+ shotSrc = /^https?:/i.test(shot) && shot.replace(/\.jpg$/, imgType);
} else {
- const auto = URLS.uso + `auto_style_screenshots/${id}${USO_AUTO_PIC_SUFFIX}`;
- Object.assign(elShot, {
- src: shot && !shot.endsWith(USO_AUTO_PIC_SUFFIX)
- ? `${shotArchived ? URLS.usoArchiveRaw[0] : URLS.uso + 'style_'}screenshots/${shot}`
- : auto,
- _src: auto,
- onerror: fixScreenshot,
- });
+ elShot._src = URLS.uso + `auto_style_screenshots/${id}${USO_AUTO_PIC_SUFFIX}`;
+ shotSrc = shot && !shot.endsWith(USO_AUTO_PIC_SUFFIX)
+ ? `${shotArchived ? URLS.usoArchiveRaw[0] : URLS.uso + 'style_'}screenshots/${shot}`
+ : elShot._src;
+ }
+ if (shotSrc) {
+ elShot._entry = entry;
+ elShot.src = shotSrc;
+ elShot.onerror = fixScreenshot;
+ } else {
+ elShot.src = BLANK_PIXEL;
+ entry.dataset.noImage = '';
}
// author
Object.assign($('[data-type="author"] a', entry), {
@@ -350,8 +364,10 @@
this.src = _src;
delete this._src;
} else {
- this.src = BLANK_PIXEL;
this.onerror = null;
+ this.src = BLANK_PIXEL;
+ this._entry.dataset.noImage = '';
+ renderActionButtons(this._entry);
}
}
@@ -366,14 +382,10 @@
result.installedStyleId = installedId;
}
const isInstalled = result.installed;
- if (isInstalled && !('installed' in entry.dataset)) {
- entry.dataset.installed = '';
- $('.search-result-status', entry).textContent = t('clickToUninstall');
- } else if (!isInstalled && 'installed' in entry.dataset) {
- delete entry.dataset.installed;
- $('.search-result-status', entry).textContent = '';
- hide('.search-result-customize', entry);
- }
+ const status = $('.search-result-status', entry).textContent =
+ isInstalled ? t('clickToUninstall') :
+ entry.dataset.noImage != null ? t('installButton') :
+ '';
const notMatching = installedId > 0 && !$entry(installedId);
if (notMatching !== entry.classList.contains('not-matching')) {
entry.classList.toggle('not-matching');
@@ -385,10 +397,15 @@
}
Object.assign($('.search-result-screenshot', entry), {
onclick: isInstalled ? uninstall : install,
- title: isInstalled ? '' : t('installButton'),
+ title: status ? '' : t('installButton'),
});
$('.search-result-uninstall', entry).onclick = uninstall;
$('.search-result-install', entry).onclick = install;
+ Object.assign($('.search-result-customize', entry), {
+ onclick: configure,
+ disabled: notMatching,
+ });
+ toggleDataset(entry, 'installed', isInstalled);
}
function renderFullInfo(entry, style) {
@@ -402,17 +419,16 @@
textContent: description,
title: description,
});
- // config button
- if (vars) {
- const btn = $('.search-result-customize', entry);
- btn.onclick = () => $('.configure', $entry(style)).click();
- show(btn);
- }
+ toggleDataset(entry, 'customizable', vars);
+ }
+
+ function configure() {
+ const styleEntry = $entry($resultEntry(this).result.installedStyleId);
+ Events.configure.call(this, {target: styleEntry});
}
async function install() {
- const entry = this.closest('.search-result');
- const result = /** @type IndexEntry */ entry._result;
+ const {entry, result} = $resultEntry(this);
const {i: id, isUsw} = result;
const installButton = $('.search-result-install', entry);
@@ -443,9 +459,9 @@
}
function uninstall() {
- const entry = this.closest('.search-result');
+ const {entry, result} = $resultEntry(this);
saveScrollPosition(entry);
- API.styles.delete(entry._result.installedStyleId);
+ API.styles.delete(result.installedStyleId);
}
function saveScrollPosition(entry) {