tidy up USW-related UI and code (#1285)
* shortened the title to "Publish" and fixed the compact mode * made collapsed <details> share the same line in compact mode * made hard-coded strings localizable * IIFE modules instead of generically named globals * unified and sorted the names of localized messages * adjusted spacing of header items * center auth popup to current window Co-authored-by: Gusted <williamzijl7@hotmail.com>pull/1294/head
parent
23d86c53a7
commit
6650a37194
@ -1,29 +1,119 @@
|
||||
/* global API msg */// msg.js
|
||||
/* global URLS */ // toolbox.js
|
||||
|
||||
/* global tokenMan */
|
||||
'use strict';
|
||||
|
||||
/* exported retrieveStyleInformation */
|
||||
async function retrieveStyleInformation(token) {
|
||||
return (await (await fetch(`${URLS.usw}api/style`, {
|
||||
method: 'GET',
|
||||
headers: new Headers({
|
||||
'Authorization': `Bearer ${token}`,
|
||||
}),
|
||||
credentials: 'omit',
|
||||
})).json()).data;
|
||||
}
|
||||
const uswApi = (() => {
|
||||
|
||||
//#region Internals
|
||||
|
||||
class TokenHooks {
|
||||
constructor(id) {
|
||||
this.id = id;
|
||||
}
|
||||
keyName(name) {
|
||||
return `${name}/${this.id}`;
|
||||
}
|
||||
query(query) {
|
||||
return Object.assign(query, {vendor_data: this.id});
|
||||
}
|
||||
}
|
||||
|
||||
function fakeUsercssHeader(style) {
|
||||
const {name, _usw: u = {}} = style;
|
||||
const meta = Object.entries({
|
||||
'@name': u.name || name || '?',
|
||||
'@version': // Same as USO-archive version: YYYYMMDD.hh.mm
|
||||
new Date().toISOString().replace(/^(\d+)-(\d+)-(\d+)T(\d+):(\d+).+/, '$1$2$3.$4.$5'),
|
||||
'@namespace': u.namespace !== '?' && u.namespace ||
|
||||
u.username && `userstyles.world/user/${u.username}` ||
|
||||
'?',
|
||||
'@description': u.description,
|
||||
'@author': u.username,
|
||||
'@license': u.license,
|
||||
});
|
||||
const maxKeyLen = meta.reduce((res, [k]) => Math.max(res, k.length), 0);
|
||||
return [
|
||||
'/* ==UserStyle==',
|
||||
...meta.map(([k, v]) => `${k}${' '.repeat(maxKeyLen - k.length + 2)}${v || ''}`),
|
||||
'==/UserStyle== */',
|
||||
].join('\n') + '\n\n';
|
||||
}
|
||||
|
||||
async function linkStyle(style, sourceCode) {
|
||||
const {id} = style;
|
||||
const metadata = await API.worker.parseUsercssMeta(sourceCode).catch(console.warn) || {};
|
||||
const uswData = Object.assign({}, style, {metadata, sourceCode});
|
||||
API.data.set('usw' + id, uswData);
|
||||
const token = await tokenMan.getToken('userstylesworld', true, new TokenHooks(id));
|
||||
const info = await uswFetch('style', token);
|
||||
const data = style._usw = Object.assign({token}, info);
|
||||
style.url = style.url || data.homepage || `${URLS.usw}style/${data.id}`;
|
||||
await uswSave(style);
|
||||
return data;
|
||||
}
|
||||
|
||||
async function uswFetch(path, token, opts) {
|
||||
opts = Object.assign({credentials: 'omit'}, opts);
|
||||
opts.headers = Object.assign({Authorization: `Bearer ${token}`}, opts.headers);
|
||||
return (await (await fetch(`${URLS.usw}api/${path}`, opts)).json()).data;
|
||||
}
|
||||
|
||||
/** Uses a custom method when broadcasting and avoids needlessly sending the entire style */
|
||||
async function uswSave(style) {
|
||||
const {id, _usw} = style;
|
||||
await API.styles.save(style, {broadcast: false});
|
||||
msg.broadcastExtension({method: 'uswData', style: {id, _usw}});
|
||||
}
|
||||
|
||||
//#endregion
|
||||
//#region Exports
|
||||
|
||||
return {
|
||||
/**
|
||||
* @param {number} id
|
||||
* @param {string} sourceCode
|
||||
* @return {Promise<string>}
|
||||
*/
|
||||
async publish(id, sourceCode) {
|
||||
const style = await API.styles.get(id);
|
||||
const data = (style._usw || {}).token
|
||||
? style._usw
|
||||
: await linkStyle(style, sourceCode);
|
||||
const header = style.usercssData ? '' : fakeUsercssHeader(style);
|
||||
return uswFetch(`style/${data.id}`, data.token, {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({code: header + sourceCode}),
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* @param {number} id
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async revoke(id) {
|
||||
await tokenMan.revokeToken('userstylesworld', new TokenHooks(id));
|
||||
const style = await API.styles.get(id);
|
||||
if (style) {
|
||||
style._usw = {};
|
||||
await uswSave(style);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
//#endregion
|
||||
})();
|
||||
|
||||
/* exported uploadStyle */
|
||||
async function uploadStyle(style) {
|
||||
return (await (await fetch(`${URLS.usw}api/style/${style._usw.id}`, {
|
||||
method: 'POST',
|
||||
headers: new Headers({
|
||||
'Authorization': `Bearer ${style._usw.token}`,
|
||||
'Content-Type': 'application/json',
|
||||
}),
|
||||
body: JSON.stringify({
|
||||
code: style.sourceCode,
|
||||
}),
|
||||
credentials: 'omit',
|
||||
})).json()).data;
|
||||
/* Doing this outside so we don't break IDE's recognition of the exported methods in IIFE */
|
||||
for (const [k, fn] of Object.entries(uswApi)) {
|
||||
uswApi[k] = async (id, ...args) => {
|
||||
API.data.set('usw' + id, true);
|
||||
try {
|
||||
/* Awaiting inside `try` so that `finally` runs when done */
|
||||
return await fn(id, ...args);
|
||||
} finally {
|
||||
API.data.del('usw' + id);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -1,49 +1,97 @@
|
||||
/* global $ $create $remove */// dom.js
|
||||
/* global $ $create $remove messageBoxProxy showSpinner toggleDataset */// dom.js
|
||||
/* global API msg */// msg.js
|
||||
/* global URLS */// toolbox.js
|
||||
/* global baseInit */
|
||||
/* global editor */
|
||||
|
||||
/* global t */// localization.js
|
||||
'use strict';
|
||||
|
||||
let uswPort;
|
||||
(() => {
|
||||
//#region Main
|
||||
|
||||
function connectToPort() {
|
||||
if (!uswPort) {
|
||||
uswPort = chrome.runtime.connect({name: 'link-style-usw'});
|
||||
uswPort.onDisconnect.addListener(err => {
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
}
|
||||
const ERROR_TITLE = 'UserStyles.world ' + t('genericError');
|
||||
const PROGRESS = '#usw-progress';
|
||||
let spinnerTimer = 0;
|
||||
let prevCode = '';
|
||||
|
||||
msg.onExtension(request => {
|
||||
if (request.method === 'uswData' &&
|
||||
request.style.id === editor.style.id) {
|
||||
Object.assign(editor.style, request.style);
|
||||
updateUI();
|
||||
}
|
||||
});
|
||||
|
||||
baseInit.ready.then(() => {
|
||||
updateUI();
|
||||
$('#usw-publish-style').onclick = disableWhileActive(publishStyle);
|
||||
$('#usw-disconnect').onclick = disableWhileActive(disconnect);
|
||||
});
|
||||
|
||||
async function publishStyle() {
|
||||
const {id} = editor.style;
|
||||
if (await API.data.has('usw' + id) &&
|
||||
!await messageBoxProxy.confirm(t('publishRetry'), 'danger', ERROR_TITLE)) {
|
||||
return;
|
||||
}
|
||||
const code = editor.getValue();
|
||||
const isDiff = code !== prevCode;
|
||||
const res = isDiff ? await API.usw.publish(id, code) : t('importReportUnchanged');
|
||||
const title = `${new Date().toLocaleString()}\n${res}`;
|
||||
const failed = /^Error:/.test(res);
|
||||
$(PROGRESS).append(...failed && [
|
||||
$create('div.error', {title}, res),
|
||||
$create('div', t('publishReconnect')),
|
||||
] || [
|
||||
$create(`span.${isDiff ? 'success' : 'unchanged'}`, {title}),
|
||||
]);
|
||||
if (!failed) prevCode = code;
|
||||
}
|
||||
|
||||
/* exported revokeLinking */
|
||||
function revokeLinking() {
|
||||
connectToPort();
|
||||
async function disconnect() {
|
||||
await API.usw.revoke(editor.style.id);
|
||||
prevCode = null; // to allow the next publishStyle to upload style
|
||||
}
|
||||
|
||||
uswPort.postMessage({reason: 'revoke', data: editor.style});
|
||||
}
|
||||
function updateUI(style = editor.style) {
|
||||
const usw = style._usw || {};
|
||||
const section = $('#publish');
|
||||
toggleDataset(section, 'connected', usw.token);
|
||||
for (const type of ['name', 'description']) {
|
||||
const el = $(`dd[data-usw="${type}"]`, section);
|
||||
el.textContent = el.title = usw[type] || '';
|
||||
}
|
||||
const elUrl = $('#usw-url');
|
||||
elUrl.href = `${URLS.usw}${usw.id ? `style/${usw.id}` : ''}`;
|
||||
elUrl.textContent = t('publishUsw').replace(/<(.+)>/, `$1${usw.id ? `#${usw.id}` : ''}`);
|
||||
}
|
||||
|
||||
/* exported publishStyle */
|
||||
function publishStyle() {
|
||||
connectToPort();
|
||||
const data = Object.assign(editor.style, {sourceCode: editor.getEditors()[0].getValue()});
|
||||
uswPort.postMessage({reason: 'publish', data});
|
||||
}
|
||||
//#endregion
|
||||
//#region Utility
|
||||
|
||||
function disableWhileActive(fn) {
|
||||
/** @this {Element} */
|
||||
return async function () {
|
||||
this.disabled = true;
|
||||
timerOn();
|
||||
await fn().catch(console.error);
|
||||
timerOff();
|
||||
this.disabled = false;
|
||||
};
|
||||
}
|
||||
|
||||
/* exported updateUI */
|
||||
function updateUI(useStyle) {
|
||||
const style = useStyle || editor.style;
|
||||
if (style._usw && style._usw.token) {
|
||||
$('#revoke-link').style = '';
|
||||
function timerOn() {
|
||||
if (!spinnerTimer) {
|
||||
$(PROGRESS).textContent = '';
|
||||
spinnerTimer = setTimeout(showSpinner, 250, PROGRESS);
|
||||
}
|
||||
}
|
||||
|
||||
const linkInformation = $create('div', {id: 'link-info'}, [
|
||||
$create('p', `Style name: ${style._usw.name}`),
|
||||
$create('p', `Description: ${style._usw.description}`),
|
||||
]);
|
||||
$remove('#link-info');
|
||||
$('#integration').insertBefore(linkInformation, $('#integration').firstChild);
|
||||
} else {
|
||||
$('#revoke-link').style = 'display: none;';
|
||||
$remove('#link-info');
|
||||
function timerOff() {
|
||||
$remove(`${PROGRESS} .lds-spinner`);
|
||||
clearTimeout(spinnerTimer);
|
||||
spinnerTimer = 0;
|
||||
}
|
||||
}
|
||||
|
||||
//#endregion
|
||||
})();
|
||||
|
@ -0,0 +1,98 @@
|
||||
/* spinner: https://github.com/loadingio/css-spinner */
|
||||
.lds-spinner {
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
width: 200px; /* don't change! use "transform: scale(.75)" */
|
||||
height: 200px; /* don't change! use "transform: scale(.75)" */
|
||||
margin: auto;
|
||||
animation: lds-spinner 1s reverse;
|
||||
animation-fill-mode: both;
|
||||
}
|
||||
|
||||
@keyframes lds-spinner {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.lds-spinner div {
|
||||
left: 94px;
|
||||
top: 23px;
|
||||
position: absolute;
|
||||
animation: lds-spinner linear 1s infinite;
|
||||
animation-direction: reverse;
|
||||
background: currentColor;
|
||||
width: 12px;
|
||||
height: 34px;
|
||||
border-radius: 20%;
|
||||
transform-origin: 6px 77px;
|
||||
}
|
||||
|
||||
.lds-spinner div:nth-child(1) {
|
||||
transform: rotate(0deg);
|
||||
animation-delay: -0.916666666666667s;
|
||||
}
|
||||
|
||||
.lds-spinner div:nth-child(2) {
|
||||
transform: rotate(30deg);
|
||||
animation-delay: -0.833333333333333s;
|
||||
}
|
||||
|
||||
.lds-spinner div:nth-child(3) {
|
||||
transform: rotate(60deg);
|
||||
animation-delay: -0.75s;
|
||||
}
|
||||
|
||||
.lds-spinner div:nth-child(4) {
|
||||
transform: rotate(90deg);
|
||||
animation-delay: -0.666666666666667s;
|
||||
}
|
||||
|
||||
.lds-spinner div:nth-child(5) {
|
||||
transform: rotate(120deg);
|
||||
animation-delay: -0.583333333333333s;
|
||||
}
|
||||
|
||||
.lds-spinner div:nth-child(6) {
|
||||
transform: rotate(150deg);
|
||||
animation-delay: -0.5s;
|
||||
}
|
||||
|
||||
.lds-spinner div:nth-child(7) {
|
||||
transform: rotate(180deg);
|
||||
animation-delay: -0.416666666666667s;
|
||||
}
|
||||
|
||||
.lds-spinner div:nth-child(8) {
|
||||
transform: rotate(210deg);
|
||||
animation-delay: -0.333333333333333s;
|
||||
}
|
||||
|
||||
.lds-spinner div:nth-child(9) {
|
||||
transform: rotate(240deg);
|
||||
animation-delay: -0.25s;
|
||||
}
|
||||
|
||||
.lds-spinner div:nth-child(10) {
|
||||
transform: rotate(270deg);
|
||||
animation-delay: -0.166666666666667s;
|
||||
}
|
||||
|
||||
.lds-spinner div:nth-child(11) {
|
||||
transform: rotate(300deg);
|
||||
animation-delay: -0.083333333333333s;
|
||||
}
|
||||
|
||||
.lds-spinner div:nth-child(12) {
|
||||
transform: rotate(330deg);
|
||||
animation-delay: 0s;
|
||||
}
|
Loading…
Reference in new issue