diff --git a/_locales/en/messages.json b/_locales/en/messages.json
index 2552b60b..5cab08ba 100644
--- a/_locales/en/messages.json
+++ b/_locales/en/messages.json
@@ -625,6 +625,18 @@
"message": "Get styles",
"description": "Help link text on the manage page e.g. https://userstyles.org"
},
+ "linkGetStylesInfo": {
+ "message": "This archive site was created by a userstyle community member to back up the slow and unresponsive userstyles.org. The archive updates its contents approximately once a day.",
+ "description": "Info shown when clicking the (i) icon of the uso-archive link in the manager"
+ },
+ "linkGetShareStyles": {
+ "message": "Get and share styles",
+ "description": "Link text for https://userstyles.world/ on the manage page"
+ },
+ "linkGetShareStylesInfo": {
+ "message": "The new community-driven userstyles.world site is created by userstyle authors in order to replace userstyles.org, which has been so slow and unresponsive for the past year that many authors stopped updating their styles.",
+ "description": "Info shown when clicking the (i) icon of the userstyles.world link in the manager"
+ },
"linkStylusWiki": {
"message": "Wiki",
"description": "Wiki link text on the manage page e.g. https://github.com/openstyles/stylus/wiki"
@@ -633,10 +645,6 @@
"message": "Translate",
"description": "Transifex link text on the manage page"
},
- "linkUSW": {
- "message": "Upload and discover styles on userstyles.world",
- "description": "Link text for https://userstyles.world/ on the manage page"
- },
"linterCSSLintIncompatible": {
"message": "CSSLint doesn't support $preprocessorname$ preprocessor",
"placeholders": {
diff --git a/install-usercss.html b/install-usercss.html
index 9ed75523..98cd0eec 100644
--- a/install-usercss.html
+++ b/install-usercss.html
@@ -35,6 +35,9 @@
+
+
+
@@ -74,6 +77,12 @@
+
+
+
+
+
+
diff --git a/install-usercss/install-usercss.css b/install-usercss/install-usercss.css
index 55c2ad90..efc088e3 100644
--- a/install-usercss/install-usercss.css
+++ b/install-usercss/install-usercss.css
@@ -305,6 +305,20 @@ label {
animation: none;
}
+.configure-usercss .svg-icon.config {
+ width: 20px;
+ height: 20px;
+ margin-top: -3px;
+}
+#message-box.config-dialog {
+ width: 0;
+ height: 0;
+ background: none;
+}
+#message-box.config-dialog > div {
+ box-shadow: 5px 5px 50px rgba(0, 0, 0, 0.75); /* copied from message-box.css + darkened the color */
+}
+
/************ reponsive layouts ************/
@media (max-width: 850px) {
@@ -417,6 +431,10 @@ label {
flex-shrink: 0;
margin-right: 1em;
}
+ #message-box.config-dialog > div {
+ top: auto;
+ bottom: 3rem;
+ }
}
/* Retina-specific stuff here */
diff --git a/install-usercss/install-usercss.js b/install-usercss/install-usercss.js
index 74ad9388..6645e882 100644
--- a/install-usercss/install-usercss.js
+++ b/install-usercss/install-usercss.js
@@ -1,6 +1,6 @@
-/* global $ $create $createLink $$remove */
+/* global $ $create $createLink $$remove showSpinner */// dom.js
/* global API */// msg.js
-/* global closeCurrentTab */// toolbox.js
+/* global closeCurrentTab deepEqual */// toolbox.js
/* global messageBox */
/* global prefs */
/* global preinit */
@@ -13,20 +13,16 @@ let installed;
let installedDup;
let liveReload;
let tabId;
+let vars;
// "History back" in Firefox (for now) restores the old DOM including the messagebox,
// which stays after installing since we don't want to wait for the fadeout animation before resolving.
document.on('visibilitychange', () => {
- if (messageBox.element) messageBox.element.remove();
+ $$remove('#message-box:not(.config-dialog)');
if (installed) liveReload.onToggled();
});
-setTimeout(() => {
- if (!cm) {
- $('#header').appendChild($create('.lds-spinner',
- new Array(12).fill($create('div')).map(e => e.cloneNode())));
- }
-}, 200);
+setTimeout(() => !cm && showSpinner($('#header')), 200);
/*
* Preinit starts to download as early as possible,
@@ -187,11 +183,29 @@ function updateMeta(style, dup = installedDup) {
$('.external-link').appendChild(externalLink);
}
+ Object.assign($('.configure-usercss'), {
+ hidden: !data.vars,
+ onclick: openConfigDialog,
+ });
+ if (!data.vars) {
+ $$remove('#message-box.config-dialog');
+ } else if (!deepEqual(data.vars, vars)) {
+ vars = data.vars;
+ // Use the user-customized vars from the installed style
+ for (const [dk, dv] of Object.entries(dup && dupData.vars || {})) {
+ const v = vars[dk];
+ if (v && v.type === dv.type) {
+ v.value = dv.value;
+ }
+ }
+ openConfigDialog();
+ }
+
$('#header').dataset.arrivedFast = performance.now() < 500;
$('#header').classList.add('meta-init');
$('#header').classList.remove('meta-init-error');
- setTimeout(() => $$remove('.lds-spinner'), 1000);
+ setTimeout(() => $$remove('.lds-spinner'), 1000);
showError('');
requestAnimationFrame(adjustCodeHeight);
@@ -233,6 +247,11 @@ function updateMeta(style, dup = installedDup) {
)),
]));
}
+
+ async function openConfigDialog() {
+ await require(['/js/dlg/config-dialog']); /* global configDialog */
+ configDialog(style);
+ }
}
function showError(err) {
diff --git a/js/dlg/config-dialog.css b/js/dlg/config-dialog.css
index e6fe339c..7464e30c 100644
--- a/js/dlg/config-dialog.css
+++ b/js/dlg/config-dialog.css
@@ -2,12 +2,12 @@
--onoffswitch-width: 60px;
}
-#stylus-manage .config-dialog #message-box-contents {
- padding: 2em 16px;
+.config-dialog {
+ --pad: 16px;
}
-#stylus-popup .config-dialog #message-box-contents {
- padding: 8px 16px;
+.config-dialog #message-box-contents {
+ padding: var(--pad);
}
#stylus-popup .config-dialog > div {
@@ -33,7 +33,7 @@
display: flex;
padding: .75em 0;
align-items: center;
- margin-right: -16px; /* for .config-reset-icon */
+ margin-right: calc(-1 * var(--pad)); /* for .config-reset-icon */
}
.config-body .select-resizer {
@@ -120,14 +120,14 @@
}
.config-reset-icon {
- height: 16px;
+ height: var(--pad);
}
.config-reset-icon .svg-icon {
cursor: pointer;
fill: #aaa;
- width: 16px;
- height: 16px;
+ width: var(--pad);
+ height: var(--pad);
padding: 0 1px;
box-sizing: border-box;
flex-shrink: 0;
@@ -139,7 +139,7 @@
#config-autosave-wrapper {
position: relative;
- padding: 0 0 0 16px;
+ padding: 0 0 0 var(--pad);
display: inline-flex;
}
diff --git a/js/dlg/config-dialog.js b/js/dlg/config-dialog.js
index c1054299..9e539301 100644
--- a/js/dlg/config-dialog.js
+++ b/js/dlg/config-dialog.js
@@ -28,8 +28,9 @@ async function configDialog(style) {
let varsInitial = getInitialValues(varsHash);
const elements = [];
- const colorpicker = window.colorpicker();
- const isPopup = location.href.includes('popup.html');
+ const isInstaller = location.pathname.startsWith('/install-usercss.html');
+ const isPopup = location.pathname.startsWith('/popup.html');
+ const colorpicker = ((window.CodeMirror || {}).prototype || window).colorpicker();
const buttons = {};
buildConfigForm();
@@ -122,7 +123,7 @@ async function configDialog(style) {
buttons.close.textContent = t(someDirty ? 'confirmCancel' : 'confirmClose');
}
- async function save({anyChangeIsDirty = false} = {}, bgStyle) {
+ async function save({anyChangeIsDirty = false} = {}) {
for (let delay = 1; saving && delay < 1000; delay *= 2) {
await new Promise(resolve => setTimeout(resolve, delay));
}
@@ -132,15 +133,13 @@ async function configDialog(style) {
if (!vars.some(va => va.dirty || anyChangeIsDirty && va.value !== va.savedValue)) {
return;
}
- if (!bgStyle) {
- bgStyle = await API.styles.get(style.id).catch(() => ({}));
- }
+ const bgStyle = !isInstaller && await API.styles.get(style.id).catch(() => ({}));
style = style.sections ? Object.assign({}, style) : style;
style.enabled = true;
style.sourceCode = null;
style.sections = null;
const styleVars = style.usercssData.vars;
- const bgVars = (bgStyle.usercssData || {}).vars || {};
+ const bgVars = isInstaller ? styleVars : (bgStyle.usercssData || {}).vars || {};
const invalid = [];
let numValid = 0;
for (const va of vars) {
@@ -184,7 +183,7 @@ async function configDialog(style) {
}
saving = true;
try {
- const newVars = await API.usercss.configVars(style.id, style.usercssData.vars);
+ const newVars = isInstaller ? styleVars : await API.usercss.configVars(style.id, styleVars);
varsInitial = getInitialValues(newVars);
vars.forEach(va => onchange({target: va.input, justSaved: true}));
renderValues();
diff --git a/js/dlg/message-box.js b/js/dlg/message-box.js
index 3c348882..423c39a3 100644
--- a/js/dlg/message-box.js
+++ b/js/dlg/message-box.js
@@ -40,10 +40,9 @@ messageBox.show = async ({
}) => {
await require(['/js/dlg/message-box.css']);
if (!messageBox.listeners) initOwnListeners();
- bindGlobalListeners();
createElement();
+ bindGlobalListeners();
document.body.appendChild(messageBox.element);
- bindElementLiseners();
messageBox._originalFocus = document.activeElement;
// focus the first focusable child but skip the first external link which is usually `feedback`
@@ -54,6 +53,9 @@ messageBox.show = async ({
if (focusAccessibility.lastFocusedViaClick && document.activeElement) {
document.activeElement.dataset.focusedViaClick = '';
}
+ if (document.activeElement === messageBox._originalFocus) {
+ document.body.focus();
+ }
if (typeof onshow === 'function') {
onshow(messageBox.element);
@@ -132,15 +134,9 @@ messageBox.show = async ({
listening = false;
},
mouseMove(event) {
- const x = clamp(event.clientX, 30, innerWidth - 30) - clickX;
- const y = clamp(event.clientY, 30, innerHeight - 30) - clickY;
-
- offsetX = x;
- offsetY = y;
-
- $('#message-box > div').style.transform =
- `translateX(${x}px)
- translateY(${y}px)`;
+ offsetX = clamp(event.clientX, 30, innerWidth - 30) - clickX;
+ offsetY = clamp(event.clientY, 30, innerHeight - 30) - clickY;
+ messageBox.element.firstChild.style.transform = `translate(${offsetX}px,${offsetY}px)`;
},
};
}
@@ -164,7 +160,7 @@ messageBox.show = async ({
messageBox.element =
$create({id, className}, [
$create([
- $create(`#${id}-title`, title),
+ $create(`#${id}-title`, {onmousedown: messageBox.listeners.mouseDown}, title),
$create(`#${id}-close-icon`, {onclick: messageBox.listeners.closeIcon},
$create('SVG:svg.svg-icon', {viewBox: '0 0 20 20'},
$create('SVG:path', {d: 'M11.69,10l4.55,4.55-1.69,1.69L10,11.69,' +
@@ -191,16 +187,11 @@ messageBox.show = async ({
window.on('keydown', messageBox.listeners.key, true);
}
- function bindElementLiseners() {
- $('#message-box-title').on('mousedown', messageBox.listeners.mouseDown, {passive: true});
- }
-
function unbindGlobalListeners() {
window.off('keydown', messageBox.listeners.key, true);
window.off('scroll', messageBox.listeners.scroll);
window.off('mouseup', messageBox.listeners.mouseUp);
window.off('mousemove', messageBox.listeners.mouseMove);
- $('#message-box-title').off('mousedown', messageBox.listeners.mouseDown);
}
function removeSelf() {
diff --git a/js/dom.js b/js/dom.js
index a2893146..86ebf7c6 100644
--- a/js/dom.js
+++ b/js/dom.js
@@ -1,5 +1,6 @@
/* global FIREFOX debounce */// toolbox.js
/* global prefs */
+/* global t */// localization.js
'use strict';
/* exported
@@ -259,7 +260,7 @@ function moveFocus(rootElement, step) {
const activeIndex = step ? Math.max(step < 0 ? 0 : -1, elements.indexOf(activeEl)) : -1;
const num = elements.length;
if (!step) step = 1;
- for (let i = 1; i < num; i++) {
+ for (let i = 1; i <= num; i++) {
const el = elements[(activeIndex + i * step + num) % num];
if (!el.disabled && el.tabIndex >= 0) {
el.focus();
@@ -443,6 +444,7 @@ async function waitForSheet({
document.documentElement.setAttribute('lang', chrome.i18n.getUILanguage());
document.on('keypress', clickDummyLinkOnEnter);
document.on('wheel', changeFocusedInputOnWheel, {capture: true, passive: false});
+ document.on('click', showTooltipNote);
Promise.resolve().then(async () => {
if (!chrome.app) addFaviconFF();
@@ -451,6 +453,7 @@ async function waitForSheet({
});
onDOMready().then(() => {
+ splitLongTooltips();
debounce(addTooltipsToEllipsized, 500);
window.on('resize', () => debounce(addTooltipsToEllipsized, 100));
});
@@ -546,6 +549,34 @@ async function waitForSheet({
}
}
}
+
+ function showTooltipNote(event) {
+ const el = event.target.closest('[data-cmd=note]');
+ if (el) {
+ event.preventDefault();
+ window.messageBoxProxy.show({
+ className: 'note center-dialog',
+ contents: el.dataset.title || el.title,
+ buttons: [t('confirmClose')],
+ });
+ }
+ }
+
+ function splitLongTooltips() {
+ for (const el of $$('[title]')) {
+ el.dataset.title = el.title;
+ el.title = el.title.replace(/<\/?\w+>/g, ''); // strip html tags
+ if (el.title.length < 50) {
+ continue;
+ }
+ const newTitle = el.title
+ .split('\n')
+ .map(s => s.replace(/([^.][.。?!]|.{50,60},)\s+/g, '$1\n'))
+ .map(s => s.replace(/(.{50,80}(?=.{40,}))\s+/g, '$1\n'))
+ .join('\n');
+ if (newTitle !== el.title) el.title = newTitle;
+ }
+ }
})();
//#endregion
diff --git a/manage.html b/manage.html
index f6f9ea5c..8e119947 100644
--- a/manage.html
+++ b/manage.html
@@ -329,13 +329,21 @@
-
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
@@ -354,7 +362,6 @@
-
diff --git a/manage/filters.js b/manage/filters.js
index ad53d320..f5fe5f77 100644
--- a/manage/filters.js
+++ b/manage/filters.js
@@ -36,7 +36,7 @@ function initFilters() {
$('#search-help').onclick = event => {
event.preventDefault();
messageBoxProxy.show({
- className: 'help-text',
+ className: 'help-text center-dialog',
title: t('search'),
contents:
$create('ul',
diff --git a/manage/manage.css b/manage/manage.css
index 6f1042a3..98f28d71 100644
--- a/manage/manage.css
+++ b/manage/manage.css
@@ -432,33 +432,17 @@ a:hover {
#manage-text {
display: flex;
flex-wrap: wrap;
- align-items: baseline;
padding-top: .35rem;
}
-
+#manage-text > * {
+ display: flex;
+ align-items: center;
+}
#manage-text > :not(:last-child):after {
content: "|";
margin: 0 .5em;
}
-#link-usw {
- display: flex;
- align-items: center;
- margin-top: .5em;
-}
-
-#link-usw img {
- max-width: 2.5em;
- max-height: 2.5em;
- margin-right: .75em;
- filter: grayscale(1);
- transition: filter .5s;
-}
-
-#link-usw:hover img {
- filter: none;
-}
-
.newUI .entry .svg-icon.checked,
.newUI .entry:hover .svg-icon.checked {
fill: #000;
diff --git a/manage/sorter.js b/manage/sorter.js
index 9a181e47..97963e54 100644
--- a/manage/sorter.js
+++ b/manage/sorter.js
@@ -189,7 +189,7 @@ const sorter = (() => {
async function showHelp(event) {
event.preventDefault();
messageBoxProxy.show({
- className: 'help-text',
+ className: 'help-text center-dialog',
title: t('sortStylesHelpTitle'),
contents:
$create('div',
diff --git a/options/options.js b/options/options.js
index 107c02fc..3c15538f 100644
--- a/options/options.js
+++ b/options/options.js
@@ -25,7 +25,6 @@
setupLivePrefs();
setupRadioButtons();
$$('input[min], input[max]').forEach(enforceInputRange);
-setTimeout(splitLongTooltips);
if (CHROME_POPUP_BORDER_BUG) {
const borderOption = $('.chrome-no-popup-border');
@@ -87,15 +86,6 @@ document.onclick = e => {
.filter(input => prefs.knownKeys.includes(input.id))
.forEach(input => prefs.reset(input.id));
break;
-
- case 'note': {
- e.preventDefault();
- messageBoxProxy.show({
- className: 'note',
- contents: target.dataset.title,
- buttons: [t('confirmClose')],
- });
- }
}
};
@@ -229,22 +219,6 @@ function setupRadioButtons() {
});
}
-function splitLongTooltips() {
- for (const el of $$('[title]')) {
- el.dataset.title = el.title;
- el.title = el.title.replace(/<\/?\w+>/g, ''); // strip html tags
- if (el.title.length < 50) {
- continue;
- }
- const newTitle = el.title
- .split('\n')
- .map(s => s.replace(/([^.][.。?!]|.{50,60},)\s+/g, '$1\n'))
- .map(s => s.replace(/(.{50,80}(?=.{40,}))\s+/g, '$1\n'))
- .join('\n');
- if (newTitle !== el.title) el.title = newTitle;
- }
-}
-
function customizeHotkeys() {
// command name -> i18n id
const hotkeys = new Map([
diff --git a/popup/search.css b/popup/search.css
index bd67d940..dba75ef3 100644
--- a/popup/search.css
+++ b/popup/search.css
@@ -76,8 +76,9 @@ body.search-results-shown {
}
.search-result-title {
+ display: flex;
+ align-items: center;
margin-bottom: .5em;
- display: block;
color: #555;
overflow-wrap: break-word;
}
@@ -196,7 +197,8 @@ body.search-results-shown {
color: darkred;
}
-.search-result-meta [data-type="rating"][data-class="none"] dd {
+/* Keeping an empty rule so customizers can easily tweak it */
+search-result-meta [data-type="rating"][data-class="none"] dd {
}
.search-result-meta [data-type="weekly"],
@@ -228,6 +230,28 @@ body.search-results-shown {
cursor: help;
}
+[data-error],
+[data-error]:hover {
+ border: calc(var(--pad) / 2) solid red;
+ border-radius: var(--pad);
+ padding: calc(var(--pad) / 2);
+ background: hsl(0, 90%, 85%);
+}
+[data-error]::after {
+ content: attr(data-error);
+ display: block;
+ color: hsl(0, 100%, 8%);
+ font-weight: bold;
+ padding-top: var(--pad);
+ hyphens: auto;
+}
+[data-error] .search-result-description {
+ display: none;
+}
+[data-error] .search-result-meta {
+ background: hsla(0, 100%, 90%, .80);
+}
+
.search-results-nav {
flex-direction: row;
text-align: center;
diff --git a/popup/search.js b/popup/search.js
index 7c424ff2..5cee8367 100644
--- a/popup/search.js
+++ b/popup/search.js
@@ -54,6 +54,13 @@
let totalPages = 1;
let ready;
+ let imgType = '.jpg';
+ // detect WebP support
+ $create('img', {
+ src: 'data:image/webp;base64,UklGRh4AAABXRUJQVlA4TBEAAAAvAAAAAAfQ//73v/+BiOh/AAA=',
+ 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');
@@ -301,7 +308,9 @@
// screenshot
const elShot = $('.search-result-screenshot', entry);
if (isUsw) {
- elShot.src = /^https?:/i.test(shotName) ? shotName : BLANK_PIXEL;
+ elShot.src = !/^https?:/i.test(shotName) ? BLANK_PIXEL :
+ imgType !== '.jpg' ? shotName.replace(/\.jpg$/, imgType) :
+ shotName;
} else {
const auto = URLS.uso + `auto_style_screenshots/${id}${USO_AUTO_PIC_SUFFIX}`;
Object.assign(elShot, {
@@ -432,6 +441,7 @@
saveScrollPosition(entry);
installButton.disabled = true;
entry.style.setProperty('pointer-events', 'none', 'important');
+ delete entry.dataset.error;
if (!isUsw) {
// FIXME: move this to background page and create an API like installUSOStyle
result.pingbackTimer = setTimeout(download, PINGBACK_DELAY,
@@ -445,7 +455,8 @@
const style = await API.usercss.install({sourceCode, updateUrl});
renderFullInfo(entry, style);
} catch (reason) {
- error(`Error while downloading usoID:${id}\nReason: ${reason}`);
+ entry.dataset.error = `${t('genericError')}: ${reason}`;
+ entry.scrollIntoView({behavior: 'smooth', block: 'nearest'});
}
$remove('.lds-spinner', entry);
installButton.disabled = false;