diff --git a/_locales/en/messages.json b/_locales/en/messages.json
index f7cb74fa..d735ab85 100644
--- a/_locales/en/messages.json
+++ b/_locales/en/messages.json
@@ -630,6 +630,10 @@
"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/background/style-manager.js b/background/style-manager.js
index ed074854..bdbf3748 100644
--- a/background/style-manager.js
+++ b/background/style-manager.js
@@ -223,7 +223,8 @@ const styleMan = (() => {
style = mergeWithMapped(style);
const url = !style.url && style.updateUrl && (
URLS.extractUsoArchiveInstallUrl(style.updateUrl) ||
- URLS.extractGreasyForkInstallUrl(style.updateUrl)
+ URLS.extractGreasyForkInstallUrl(style.updateUrl) ||
+ URLS.extractUSwInstallUrl(style.updateUrl)
);
if (url) style.url = style.installationUrl = url;
style.originalDigest = await calcStyleDigest(style);
diff --git a/background/usercss-install-helper.js b/background/usercss-install-helper.js
index 6656ac7b..20e75536 100644
--- a/background/usercss-install-helper.js
+++ b/background/usercss-install-helper.js
@@ -36,6 +36,7 @@ bgReady.all.then(() => {
chrome.webRequest.onBeforeSendHeaders.addListener(maybeInstallFromDistro, {
urls: [
URLS.usoArchiveRaw + 'usercss/*.user.css',
+ URLS.usw + 'api/style/*.user.css',
'*://greasyfork.org/scripts/*/code/*.user.css',
'*://sleazyfork.org/scripts/*/code/*.user.css',
...[].concat(
diff --git a/js/prefs.js b/js/prefs.js
index 0d1879dc..3d7b93fa 100644
--- a/js/prefs.js
+++ b/js/prefs.js
@@ -37,6 +37,8 @@
'popup.autoResort': false, // auto resort styles after toggling
'popup.borders': false, // add white borders on the sides
'popup.findStylesInline': true, // use the inline style search
+ /** @type {'n' | 'u' | 't' | 'w' | 'r'} see IndexEntry */
+ 'popup.findSort': 'u', // the inline style search sort order
'manage.onlyEnabled': false, // display only enabled styles
'manage.onlyLocal': false, // display only styles created locally
diff --git a/js/toolbox.js b/js/toolbox.js
index 57d1ad9a..589a35e9 100644
--- a/js/toolbox.js
+++ b/js/toolbox.js
@@ -78,6 +78,9 @@ const URLS = {
usoArchive: 'https://33kk.github.io/uso-archive/',
usoArchiveRaw: 'https://raw.githubusercontent.com/33kk/uso-archive/flomaster/data/',
+
+ usw: 'https://userstyles.world/',
+
extractUsoArchiveId: url =>
url &&
url.startsWith(URLS.usoArchiveRaw) &&
@@ -91,6 +94,16 @@ const URLS = {
extractGreasyForkInstallUrl: url =>
/^(https:\/\/(?:greasy|sleazy)fork\.org\/scripts\/\d+)[^/]*\/code\/[^/]*\.user\.css$|$/.exec(url)[1],
+ extractUSwId: url =>
+ url &&
+ url.startsWith(URLS.usw) &&
+ Number(url.match(/\/(\d+)\.user\.css|$/)[1]),
+ extractUSwInstallUrl: url => {
+ const id = URLS.extractUSwId(url);
+ return id ? `${URLS.usw}style/${id}` : '';
+ },
+ makeUswCodeUrl: id => `${URLS.usw}api/style/${id}.user.css`,
+
supported: url => (
url.startsWith('http') ||
url.startsWith('ftp') ||
diff --git a/manage.html b/manage.html
index f22b5e47..f6f9ea5c 100644
--- a/manage.html
+++ b/manage.html
@@ -333,6 +333,9 @@
+
+
+
diff --git a/manage/manage.css b/manage/manage.css
index 88fd8083..8e66ccd3 100644
--- a/manage/manage.css
+++ b/manage/manage.css
@@ -440,6 +440,24 @@ a:hover {
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/popup/search.css b/popup/search.css
index 127738b4..4887fadd 100644
--- a/popup/search.css
+++ b/popup/search.css
@@ -82,6 +82,12 @@ body.search-results-shown {
overflow-wrap: break-word;
}
+.search-result-title img {
+ width: 20px;
+ height: 20px;
+ margin: -6px 4px -6px 0;
+}
+
.search-result-title span {
font-size: 12px;
font-weight: 600;
diff --git a/popup/search.js b/popup/search.js
index cac23f0d..a08ea171 100644
--- a/popup/search.js
+++ b/popup/search.js
@@ -12,6 +12,11 @@
const RESULT_ID_PREFIX = 'search-result-';
const INDEX_URL = URLS.usoArchiveRaw + 'search-index.json';
+ const USW_INDEX_URL = URLS.usw + 'api/index/uso-format';
+ const USW_ICON = $create('img', {
+ src: `${URLS.usw}favicon.ico`,
+ title: URLS.usw,
+ });
const STYLUS_CATEGORY = 'chrome-extension';
const PAGE_LENGTH = 10;
// update USO style install counter if the style isn't uninstalled immediately
@@ -43,8 +48,7 @@
let searchGlobals = $('#search-globals').checked;
/** @type string[] */
let query = [];
- /** @type 'n' | 'u' | 't' | 'w' | 'r' */
- let order = 't';
+ let order = prefs.get('popup.findSort');
let scrollToFirstResult = true;
let displayedPage = 1;
let totalPages = 1;
@@ -98,6 +102,7 @@
$('#search-order').value = order;
$('#search-order').onchange = function () {
order = this.value;
+ prefs.set('popup.findSort', order);
results.sort(comparator);
render();
};
@@ -141,10 +146,9 @@
window.on('styleAdded', async ({detail: {style}}) => {
restoreScrollPosition();
- const usoId = calcUsoId(style) ||
- calcUsoId(await API.styles.get(style.id));
- if (usoId && results.find(r => r.i === usoId)) {
- renderActionButtons(usoId, style.id);
+ const id = calcId(style) || calcId(await API.styles.get(style.id));
+ if (id && results.find(r => r.i === id)) {
+ renderActionButtons(id, style.id);
}
});
}
@@ -181,15 +185,15 @@
show(dom.container);
show(dom.list);
hide(dom.error);
- results = [];
try {
+ results = [];
for (let retry = 0; !results.length && retry <= 2; retry++) {
results = await search({retry});
}
if (results.length) {
const installedStyles = await API.styles.getAll();
- const allUsoIds = new Set(installedStyles.map(calcUsoId));
- results = results.filter(r => !allUsoIds.has(r.i));
+ const allSupportedIds = new Set(installedStyles.map(calcId));
+ results = results.filter(r => !allSupportedIds.has(r.i));
}
render();
(results.length ? show : hide)(dom.list);
@@ -274,29 +278,39 @@
an: author,
sa: shotArchived,
sn: shotName,
+ isUsw,
} = entry._result = result;
entry.id = RESULT_ID_PREFIX + id;
// title
Object.assign($('.search-result-title', entry), {
onclick: Events.openURLandHide,
- href: URLS.usoArchive + `?category=${category}&style=${id}`,
+ href: isUsw ? `${URLS.usw}style/${id}` :
+ `${URLS.usoArchive}?category=${category}&style=${id}`,
});
+ if (isUsw) $('.search-result-title', entry).prepend(USW_ICON.cloneNode(true));
$('.search-result-title span', entry).textContent =
t.breakWord(name.length < 300 ? name : name.slice(0, 300) + '...');
// screenshot
- const auto = URLS.uso + `auto_style_screenshots/${id}${USO_AUTO_PIC_SUFFIX}`;
- Object.assign($('.search-result-screenshot', entry), {
- src: shotName && !shotName.endsWith(USO_AUTO_PIC_SUFFIX)
- ? `${shotArchived ? URLS.usoArchiveRaw : URLS.uso + 'style_'}screenshots/${shotName}`
- : auto,
- _src: auto,
- onerror: fixScreenshot,
- });
+ const elShot = $('.search-result-screenshot', entry);
+ if (isUsw) {
+ elShot.src = /^https?:/i.test(shotName) ? shotName : BLANK_PIXEL;
+ } else {
+ const auto = URLS.uso + `auto_style_screenshots/${id}${USO_AUTO_PIC_SUFFIX}`;
+ Object.assign(elShot, {
+ src: shotName && !shotName.endsWith(USO_AUTO_PIC_SUFFIX)
+ ? `${shotArchived ? URLS.usoArchiveRaw : URLS.uso + 'style_'}screenshots/${shotName}`
+ : auto,
+ _src: auto,
+ onerror: fixScreenshot,
+ });
+ }
// author
+ const eAuthor = encodeURIComponent(author);
Object.assign($('[data-type="author"] a', entry), {
textContent: author,
title: author,
- href: URLS.usoArchive + '?author=' + encodeURIComponent(author).replace(/%20/g, '+'),
+ href: isUsw ? `${URLS.usw}user/${eAuthor}` :
+ `${URLS.usoArchive}?author=${eAuthor.replace(/%20/g, '+')}`,
onclick: Events.openURLandHide,
});
// rating
@@ -398,18 +412,21 @@
async function install() {
const entry = this.closest('.search-result');
const result = /** @type IndexEntry */ entry._result;
- const {i: id} = result;
+ const {i: id, isUsw} = result;
const installButton = $('.search-result-install', entry);
showSpinner(entry);
saveScrollPosition(entry);
installButton.disabled = true;
entry.style.setProperty('pointer-events', 'none', 'important');
- // FIXME: move this to background page and create an API like installUSOStyle
- result.pingbackTimer = setTimeout(download, PINGBACK_DELAY,
- `${URLS.uso}styles/install/${id}?source=stylish-ch`);
+ if (!isUsw) {
+ // FIXME: move this to background page and create an API like installUSOStyle
+ result.pingbackTimer = setTimeout(download, PINGBACK_DELAY,
+ `${URLS.uso}styles/install/${id}?source=stylish-ch`);
+ }
+
+ const updateUrl = isUsw ? URLS.makeUswCodeUrl(id) : URLS.makeUsoArchiveCodeUrl(id);
- const updateUrl = URLS.makeUsoArchiveCodeUrl(id);
try {
const sourceCode = await download(updateUrl);
const style = await API.usercss.install({sourceCode, updateUrl});
@@ -469,8 +486,18 @@
async function fetchIndex() {
const timer = setTimeout(showSpinner, BUSY_DELAY, dom.list);
- index = (await download(INDEX_URL, {responseType: 'json'}))
- .filter(res => res.f === 'uso');
+ index = [];
+ await Promise.all([
+ download(INDEX_URL, {responseType: 'json'}).then(res => {
+ index = index.concat(res.filter(res => res.f === 'uso'));
+ }).catch(() => {}),
+ download(USW_INDEX_URL, {responseType: 'json'}).then(res => {
+ for (const style of res.data) {
+ style.isUsw = true;
+ index.push(style);
+ }
+ }).catch(() => {}),
+ ]);
clearTimeout(timer);
$remove(':scope > .lds-spinner', dom.list);
return index;
@@ -521,6 +548,14 @@
URLS.extractUsoArchiveId(updateUrl);
}
+ function calcUswId({updateUrl}) {
+ return URLS.extractUSwId(updateUrl) || 0;
+ }
+
+ function calcId(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();