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>
This commit is contained in:
parent
23d86c53a7
commit
6650a37194
|
@ -452,6 +452,9 @@
|
||||||
"message": "Clone",
|
"message": "Clone",
|
||||||
"description": "Used in various places for an action that clones something"
|
"description": "Used in various places for an action that clones something"
|
||||||
},
|
},
|
||||||
|
"genericDescription": {
|
||||||
|
"message": "Description"
|
||||||
|
},
|
||||||
"genericDisabledLabel": {
|
"genericDisabledLabel": {
|
||||||
"message": "Disabled",
|
"message": "Disabled",
|
||||||
"description": "Used in various lists/options to indicate that something is disabled"
|
"description": "Used in various lists/options to indicate that something is disabled"
|
||||||
|
@ -1242,6 +1245,28 @@
|
||||||
"message": "Temporarily applies the changes without saving.\nSave the style to make the changes permanent.",
|
"message": "Temporarily applies the changes without saving.\nSave the style to make the changes permanent.",
|
||||||
"description": "Tooltip for the checkbox in style editor to enable live preview while editing."
|
"description": "Tooltip for the checkbox in style editor to enable live preview while editing."
|
||||||
},
|
},
|
||||||
|
"publish": {
|
||||||
|
"message": "Publish",
|
||||||
|
"description": "Header for the section to link the style with userStyles.world"
|
||||||
|
},
|
||||||
|
"publishPush": {
|
||||||
|
"message": "Push update",
|
||||||
|
"description": "The 'Publish style' button's new name when a connection is established"
|
||||||
|
},
|
||||||
|
"publishReconnect": {
|
||||||
|
"message": "Try disconnecting then publish again"
|
||||||
|
},
|
||||||
|
"publishRetry": {
|
||||||
|
"message": "Stylus is still trying to publish this style, but you can retry if you see no authentication activity or popups. Retry now?"
|
||||||
|
},
|
||||||
|
"publishStyle": {
|
||||||
|
"message": "Publish style",
|
||||||
|
"description": "Publish the current style to userstyles.world"
|
||||||
|
},
|
||||||
|
"publishUsw": {
|
||||||
|
"message": "Using <userstyles.world>",
|
||||||
|
"description": "Name of the link to https://userstyles.world in the editor"
|
||||||
|
},
|
||||||
"readingStyles": {
|
"readingStyles": {
|
||||||
"message": "Reading styles..."
|
"message": "Reading styles..."
|
||||||
},
|
},
|
||||||
|
@ -1365,18 +1390,6 @@
|
||||||
"message": "Sections",
|
"message": "Sections",
|
||||||
"description": "Header for the table of contents block listing style section names in the left panel of the classic editor"
|
"description": "Header for the table of contents block listing style section names in the left panel of the classic editor"
|
||||||
},
|
},
|
||||||
"integration": {
|
|
||||||
"message": "UserStyles.world integration",
|
|
||||||
"description": "Header for the section to link the style with userStyles.world"
|
|
||||||
},
|
|
||||||
"uploadStyle": {
|
|
||||||
"message": "Publish style",
|
|
||||||
"description": "Publish the current style to userstyles.world"
|
|
||||||
},
|
|
||||||
"revokeLink": {
|
|
||||||
"message": "Revoke link",
|
|
||||||
"description": "Revoke current link of style with userstyles.world"
|
|
||||||
},
|
|
||||||
"shortcuts": {
|
"shortcuts": {
|
||||||
"message": "Shortcuts",
|
"message": "Shortcuts",
|
||||||
"description": "Go to shortcut configuration"
|
"description": "Go to shortcut configuration"
|
||||||
|
@ -1493,6 +1506,9 @@
|
||||||
"message": "Mozilla Format",
|
"message": "Mozilla Format",
|
||||||
"description": "Heading for the section with buttons to import/export Mozilla format of the style"
|
"description": "Heading for the section with buttons to import/export Mozilla format of the style"
|
||||||
},
|
},
|
||||||
|
"styleName": {
|
||||||
|
"message": "Style name"
|
||||||
|
},
|
||||||
"styleNotAppliedRegexpProblemTooltip": {
|
"styleNotAppliedRegexpProblemTooltip": {
|
||||||
"message": "Style was not applied due to its incorrect usage of 'regexp()'",
|
"message": "Style was not applied due to its incorrect usage of 'regexp()'",
|
||||||
"description": "Tooltip in the popup for styles that were not applied at all"
|
"description": "Tooltip in the popup for styles that were not applied at all"
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
/* global syncMan */
|
/* global syncMan */
|
||||||
/* global updateMan */
|
/* global updateMan */
|
||||||
/* global usercssMan */
|
/* global usercssMan */
|
||||||
|
/* global uswApi */
|
||||||
/* global
|
/* global
|
||||||
FIREFOX
|
FIREFOX
|
||||||
URLS
|
URLS
|
||||||
|
@ -20,10 +21,26 @@
|
||||||
|
|
||||||
addAPI(/** @namespace API */ {
|
addAPI(/** @namespace API */ {
|
||||||
|
|
||||||
|
/** Temporary storage for data needed elsewhere e.g. in a content script */
|
||||||
|
data: ((data = {}) => ({
|
||||||
|
del: key => delete data[key],
|
||||||
|
get: key => data[key],
|
||||||
|
has: key => key in data,
|
||||||
|
pop: key => {
|
||||||
|
const val = data[key];
|
||||||
|
delete data[key];
|
||||||
|
return val;
|
||||||
|
},
|
||||||
|
set: (key, val) => {
|
||||||
|
data[key] = val;
|
||||||
|
},
|
||||||
|
}))(),
|
||||||
|
|
||||||
styles: styleMan,
|
styles: styleMan,
|
||||||
sync: syncMan,
|
sync: syncMan,
|
||||||
updater: updateMan,
|
updater: updateMan,
|
||||||
usercss: usercssMan,
|
usercss: usercssMan,
|
||||||
|
usw: uswApi,
|
||||||
/** @type {BackgroundWorker} */
|
/** @type {BackgroundWorker} */
|
||||||
worker: createWorker({url: '/background/background-worker'}),
|
worker: createWorker({url: '/background/background-worker'}),
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,6 @@
|
||||||
/* global prefs */
|
/* global prefs */
|
||||||
/* global tabMan */
|
/* global tabMan */
|
||||||
/* global usercssMan */
|
/* global usercssMan */
|
||||||
/* global tokenMan */
|
|
||||||
/* global retrieveStyleInformation uploadStyle */// usw-api.js
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -63,7 +61,6 @@ const styleMan = (() => {
|
||||||
let ready = init();
|
let ready = init();
|
||||||
|
|
||||||
chrome.runtime.onConnect.addListener(handleLivePreview);
|
chrome.runtime.onConnect.addListener(handleLivePreview);
|
||||||
chrome.runtime.onConnect.addListener(handlePublishingUSW);
|
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
//#region Exports
|
//#region Exports
|
||||||
|
@ -74,16 +71,21 @@ const styleMan = (() => {
|
||||||
async delete(id, reason) {
|
async delete(id, reason) {
|
||||||
if (ready.then) await ready;
|
if (ready.then) await ready;
|
||||||
const data = id2data(id);
|
const data = id2data(id);
|
||||||
|
const {style, appliesTo} = data;
|
||||||
await db.exec('delete', id);
|
await db.exec('delete', id);
|
||||||
if (reason !== 'sync') {
|
if (reason !== 'sync') {
|
||||||
API.sync.delete(data.style._id, Date.now());
|
API.sync.delete(style._id, Date.now());
|
||||||
}
|
}
|
||||||
for (const url of data.appliesTo) {
|
for (const url of appliesTo) {
|
||||||
const cache = cachedStyleForUrl.get(url);
|
const cache = cachedStyleForUrl.get(url);
|
||||||
if (cache) delete cache.sections[id];
|
if (cache) delete cache.sections[id];
|
||||||
}
|
}
|
||||||
dataMap.delete(id);
|
dataMap.delete(id);
|
||||||
uuidIndex.delete(data.style._id);
|
uuidIndex.delete(style._id);
|
||||||
|
if (style._usw && style._usw.token) {
|
||||||
|
// Must be called after the style is deleted from dataMap
|
||||||
|
API.usw.revoke(id);
|
||||||
|
}
|
||||||
await msg.broadcast({
|
await msg.broadcast({
|
||||||
method: 'styleDeleted',
|
method: 'styleDeleted',
|
||||||
style: {id},
|
style: {id},
|
||||||
|
@ -107,7 +109,7 @@ const styleMan = (() => {
|
||||||
if (ready.then) await ready;
|
if (ready.then) await ready;
|
||||||
style = mergeWithMapped(style);
|
style = mergeWithMapped(style);
|
||||||
style.updateDate = Date.now();
|
style.updateDate = Date.now();
|
||||||
return handleSave(await saveStyle(style), {reason: 'editSave'});
|
return saveStyle(style, {reason: 'editSave'});
|
||||||
},
|
},
|
||||||
|
|
||||||
/** @returns {Promise<?StyleObj>} */
|
/** @returns {Promise<?StyleObj>} */
|
||||||
|
@ -240,7 +242,7 @@ const styleMan = (() => {
|
||||||
if (url) style.url = style.installationUrl = url;
|
if (url) style.url = style.installationUrl = url;
|
||||||
style.originalDigest = await calcStyleDigest(style);
|
style.originalDigest = await calcStyleDigest(style);
|
||||||
// FIXME: update updateDate? what about usercss config?
|
// FIXME: update updateDate? what about usercss config?
|
||||||
return handleSave(await saveStyle(style), {reason});
|
return saveStyle(style, {reason});
|
||||||
},
|
},
|
||||||
|
|
||||||
/** @returns {Promise<?StyleObj>} */
|
/** @returns {Promise<?StyleObj>} */
|
||||||
|
@ -268,11 +270,13 @@ const styleMan = (() => {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
save: saveStyle,
|
||||||
|
|
||||||
/** @returns {Promise<number>} style id */
|
/** @returns {Promise<number>} style id */
|
||||||
async toggle(id, enabled) {
|
async toggle(id, enabled) {
|
||||||
if (ready.then) await ready;
|
if (ready.then) await ready;
|
||||||
const style = Object.assign({}, id2style(id), {enabled});
|
const style = Object.assign({}, id2style(id), {enabled});
|
||||||
handleSave(await saveStyle(style), {reason: 'toggle', codeIsUpdated: false});
|
await saveStyle(style, {reason: 'toggle', codeIsUpdated: false});
|
||||||
return id;
|
return id;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -356,65 +360,6 @@ const styleMan = (() => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePublishingUSW(port) {
|
|
||||||
if (port.name !== 'link-style-usw') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
port.onMessage.addListener(async incData => {
|
|
||||||
const {data: style, reason} = incData;
|
|
||||||
if (!style.id) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
switch (reason) {
|
|
||||||
case 'revoke':
|
|
||||||
await tokenMan.revokeToken('userstylesworld', style.id);
|
|
||||||
style._usw = {};
|
|
||||||
handleSave(await saveStyle(style), {reason: 'success-revoke', codeIsUpdated: true});
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'publish': {
|
|
||||||
if (!style._usw || !style._usw.token) {
|
|
||||||
for (const {style: someStyle} of dataMap.values()) {
|
|
||||||
if (someStyle._id === style._id) {
|
|
||||||
someStyle.tmpSourceCode = style.sourceCode;
|
|
||||||
let metadata = {};
|
|
||||||
try {
|
|
||||||
const {metadata: tmpMetadata} = await API.worker.parseUsercssMeta(style.sourceCode);
|
|
||||||
metadata = tmpMetadata;
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
}
|
|
||||||
someStyle.metadata = metadata;
|
|
||||||
} else {
|
|
||||||
delete someStyle.tmpSourceCode;
|
|
||||||
delete someStyle.metadata;
|
|
||||||
}
|
|
||||||
handleSave(await saveStyle(someStyle), {broadcast: false});
|
|
||||||
}
|
|
||||||
style._usw = {
|
|
||||||
token: await tokenMan.getToken('userstylesworld', true, style.id),
|
|
||||||
};
|
|
||||||
|
|
||||||
delete style.tmpSourceCode;
|
|
||||||
delete style.metadata;
|
|
||||||
for (const [k, v] of Object.entries(await retrieveStyleInformation(style._usw.token))) {
|
|
||||||
style._usw[k] = v;
|
|
||||||
}
|
|
||||||
handleSave(await saveStyle(style), {reason: 'success-publishing', codeIsUpdated: true});
|
|
||||||
}
|
|
||||||
|
|
||||||
const returnResult = await uploadStyle(style);
|
|
||||||
// USw prefix errors with `Error:`.
|
|
||||||
if (returnResult.startsWith('Error:')) {
|
|
||||||
style._usw.publishingError = returnResult;
|
|
||||||
handleSave(await saveStyle(style), {reason: 'publishing-failed', codeIsUpdated: true});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async function addIncludeExclude(type, id, rule) {
|
async function addIncludeExclude(type, id, rule) {
|
||||||
if (ready.then) await ready;
|
if (ready.then) await ready;
|
||||||
const style = Object.assign({}, id2style(id));
|
const style = Object.assign({}, id2style(id));
|
||||||
|
@ -423,7 +368,7 @@ const styleMan = (() => {
|
||||||
throw new Error('The rule already exists');
|
throw new Error('The rule already exists');
|
||||||
}
|
}
|
||||||
style[type] = list.concat([rule]);
|
style[type] = list.concat([rule]);
|
||||||
return handleSave(await saveStyle(style), {reason: 'styleSettings'});
|
return saveStyle(style, {reason: 'styleSettings'});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function removeIncludeExclude(type, id, rule) {
|
async function removeIncludeExclude(type, id, rule) {
|
||||||
|
@ -434,7 +379,7 @@ const styleMan = (() => {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
style[type] = list.filter(r => r !== rule);
|
style[type] = list.filter(r => r !== rule);
|
||||||
return handleSave(await saveStyle(style), {reason: 'styleSettings'});
|
return saveStyle(style, {reason: 'styleSettings'});
|
||||||
}
|
}
|
||||||
|
|
||||||
function broadcastStyleUpdated(style, reason, method = 'styleUpdated', codeIsUpdated = true) {
|
function broadcastStyleUpdated(style, reason, method = 'styleUpdated', codeIsUpdated = true) {
|
||||||
|
@ -490,14 +435,14 @@ const styleMan = (() => {
|
||||||
style.id = newId;
|
style.id = newId;
|
||||||
}
|
}
|
||||||
uuidIndex.set(style._id, style.id);
|
uuidIndex.set(style._id, style.id);
|
||||||
API.sync.put(style._id, style._rev, style._usw);
|
API.sync.put(style._id, style._rev);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveStyle(style) {
|
async function saveStyle(style, handlingOptions) {
|
||||||
beforeSave(style);
|
beforeSave(style);
|
||||||
const newId = await db.exec('put', style);
|
const newId = await db.exec('put', style);
|
||||||
afterSave(style, newId);
|
afterSave(style, newId);
|
||||||
return style;
|
return handleSave(style, handlingOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSave(style, {reason, codeIsUpdated, broadcast = true}) {
|
function handleSave(style, {reason, codeIsUpdated, broadcast = true}) {
|
||||||
|
@ -528,9 +473,7 @@ const styleMan = (() => {
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
const styles = await db.exec('getAll') || [];
|
const styles = await db.exec('getAll') || [];
|
||||||
const updated = styles.filter(style =>
|
const updated = styles.filter(fixOldStyleProps);
|
||||||
addMissingProps(style) +
|
|
||||||
addCustomName(style));
|
|
||||||
if (updated.length) {
|
if (updated.length) {
|
||||||
await db.exec('putMany', updated);
|
await db.exec('putMany', updated);
|
||||||
}
|
}
|
||||||
|
@ -543,7 +486,7 @@ const styleMan = (() => {
|
||||||
bgReady._resolveStyles();
|
bgReady._resolveStyles();
|
||||||
}
|
}
|
||||||
|
|
||||||
function addMissingProps(style) {
|
function fixOldStyleProps(style) {
|
||||||
let res = 0;
|
let res = 0;
|
||||||
for (const key in MISSING_PROPS) {
|
for (const key in MISSING_PROPS) {
|
||||||
if (!style[key]) {
|
if (!style[key]) {
|
||||||
|
@ -551,20 +494,15 @@ const styleMan = (() => {
|
||||||
res = 1;
|
res = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return res;
|
/* Upgrade the old way of customizing local names */
|
||||||
}
|
|
||||||
|
|
||||||
/** Upgrades the old way of customizing local names */
|
|
||||||
function addCustomName(style) {
|
|
||||||
let res = 0;
|
|
||||||
const {originalName} = style;
|
const {originalName} = style;
|
||||||
if (originalName) {
|
if (originalName) {
|
||||||
res = 1;
|
|
||||||
if (originalName !== style.name) {
|
if (originalName !== style.name) {
|
||||||
style.customName = style.name;
|
style.customName = style.name;
|
||||||
style.name = originalName;
|
style.name = originalName;
|
||||||
}
|
}
|
||||||
delete style.originalName;
|
delete style.originalName;
|
||||||
|
res = 1;
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,11 +64,12 @@ const tokenMan = (() => {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
||||||
buildKeys(name, styleId) {
|
buildKeys(name, hooks) {
|
||||||
|
const prefix = `secure/token/${hooks ? hooks.keyName(name) : name}/`;
|
||||||
const k = {
|
const k = {
|
||||||
TOKEN: `secure/token/${name}/${styleId ? `${styleId}/` : ''}token`,
|
TOKEN: `${prefix}token`,
|
||||||
EXPIRE: `secure/token/${name}/${styleId ? `${styleId}/` : ''}expire`,
|
EXPIRE: `${prefix}expire`,
|
||||||
REFRESH: `secure/token/${name}/${styleId ? `${styleId}/` : ''}refresh`,
|
REFRESH: `${prefix}refresh`,
|
||||||
};
|
};
|
||||||
k.LIST = Object.values(k);
|
k.LIST = Object.values(k);
|
||||||
return k;
|
return k;
|
||||||
|
@ -78,8 +79,8 @@ const tokenMan = (() => {
|
||||||
return AUTH[name].clientId;
|
return AUTH[name].clientId;
|
||||||
},
|
},
|
||||||
|
|
||||||
async getToken(name, interactive, styleId) {
|
async getToken(name, interactive, hooks) {
|
||||||
const k = tokenMan.buildKeys(name, styleId);
|
const k = tokenMan.buildKeys(name, hooks);
|
||||||
const obj = await chromeLocal.get(k.LIST);
|
const obj = await chromeLocal.get(k.LIST);
|
||||||
if (obj[k.TOKEN]) {
|
if (obj[k.TOKEN]) {
|
||||||
if (!obj[k.EXPIRE] || Date.now() < obj[k.EXPIRE]) {
|
if (!obj[k.EXPIRE] || Date.now() < obj[k.EXPIRE]) {
|
||||||
|
@ -92,13 +93,12 @@ const tokenMan = (() => {
|
||||||
if (!interactive) {
|
if (!interactive) {
|
||||||
throw new Error(`Invalid token: ${name}`);
|
throw new Error(`Invalid token: ${name}`);
|
||||||
}
|
}
|
||||||
const accessToken = authUser(name, k, interactive, styleId ? {vendor_data: styleId} : {});
|
return authUser(k, name, interactive, hooks);
|
||||||
return accessToken;
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async revokeToken(name, styleId) {
|
async revokeToken(name, hooks) {
|
||||||
const provider = AUTH[name];
|
const provider = AUTH[name];
|
||||||
const k = tokenMan.buildKeys(name, styleId);
|
const k = tokenMan.buildKeys(name, hooks);
|
||||||
if (provider.revoke) {
|
if (provider.revoke) {
|
||||||
try {
|
try {
|
||||||
const token = await chromeLocal.getValue(k.TOKEN);
|
const token = await chromeLocal.getValue(k.TOKEN);
|
||||||
|
@ -133,17 +133,17 @@ const tokenMan = (() => {
|
||||||
return handleTokenResult(result, k);
|
return handleTokenResult(result, k);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function authUser(name, k, interactive = false, extraQuery = {}) {
|
async function authUser(keys, name, interactive = false, hooks = null) {
|
||||||
await require(['/vendor/webext-launch-web-auth-flow/webext-launch-web-auth-flow.min']);
|
await require(['/vendor/webext-launch-web-auth-flow/webext-launch-web-auth-flow.min']);
|
||||||
/* global webextLaunchWebAuthFlow */
|
/* global webextLaunchWebAuthFlow */
|
||||||
const provider = AUTH[name];
|
const provider = AUTH[name];
|
||||||
const state = Math.random().toFixed(8).slice(2);
|
const state = Math.random().toFixed(8).slice(2);
|
||||||
const query = Object.assign(extraQuery, {
|
const query = {
|
||||||
response_type: provider.flow,
|
response_type: provider.flow,
|
||||||
client_id: provider.clientId,
|
client_id: provider.clientId,
|
||||||
redirect_uri: provider.redirect_uri || chrome.identity.getRedirectURL(),
|
redirect_uri: provider.redirect_uri || chrome.identity.getRedirectURL(),
|
||||||
state,
|
state,
|
||||||
});
|
};
|
||||||
if (provider.scopes) {
|
if (provider.scopes) {
|
||||||
query.scope = provider.scopes.join(' ');
|
query.scope = provider.scopes.join(' ');
|
||||||
}
|
}
|
||||||
|
@ -153,17 +153,25 @@ const tokenMan = (() => {
|
||||||
if (alwaysUseTab == null) {
|
if (alwaysUseTab == null) {
|
||||||
alwaysUseTab = await detectVivaldiWebRequestBug();
|
alwaysUseTab = await detectVivaldiWebRequestBug();
|
||||||
}
|
}
|
||||||
|
if (hooks) hooks.query(query);
|
||||||
const url = `${provider.authURL}?${new URLSearchParams(query)}`;
|
const url = `${provider.authURL}?${new URLSearchParams(query)}`;
|
||||||
|
const width = Math.min(screen.availWidth - 100, 800);
|
||||||
|
const height = Math.min(screen.availHeight - 100, 800);
|
||||||
|
const wnd = await browser.windows.getLastFocused();
|
||||||
const finalUrl = await webextLaunchWebAuthFlow({
|
const finalUrl = await webextLaunchWebAuthFlow({
|
||||||
url,
|
url,
|
||||||
alwaysUseTab,
|
alwaysUseTab,
|
||||||
interactive,
|
interactive,
|
||||||
redirect_uri: query.redirect_uri,
|
redirect_uri: query.redirect_uri,
|
||||||
windowOptions: {
|
windowOptions: Object.assign({
|
||||||
state: 'normal',
|
state: 'normal',
|
||||||
width: Math.min(screen.width - 100, 800),
|
width,
|
||||||
height: Math.min(screen.height - 100, 800),
|
height,
|
||||||
},
|
}, wnd.state !== 'minimized' && {
|
||||||
|
// Center the popup to the current window
|
||||||
|
top: Math.ceil(wnd.top + (wnd.height - width) / 2),
|
||||||
|
left: Math.ceil(wnd.left + (wnd.width - width) / 2),
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
const params = new URLSearchParams(
|
const params = new URLSearchParams(
|
||||||
provider.flow === 'token' ?
|
provider.flow === 'token' ?
|
||||||
|
@ -194,7 +202,7 @@ const tokenMan = (() => {
|
||||||
}
|
}
|
||||||
result = await postQuery(provider.tokenURL, body);
|
result = await postQuery(provider.tokenURL, body);
|
||||||
}
|
}
|
||||||
return handleTokenResult(result, k);
|
return handleTokenResult(result, keys);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleTokenResult(result, k) {
|
async function handleTokenResult(result, k) {
|
||||||
|
|
|
@ -1,29 +1,119 @@
|
||||||
|
/* global API msg */// msg.js
|
||||||
/* global URLS */ // toolbox.js
|
/* global URLS */ // toolbox.js
|
||||||
|
/* global tokenMan */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/* exported retrieveStyleInformation */
|
const uswApi = (() => {
|
||||||
async function retrieveStyleInformation(token) {
|
|
||||||
return (await (await fetch(`${URLS.usw}api/style`, {
|
|
||||||
method: 'GET',
|
|
||||||
headers: new Headers({
|
|
||||||
'Authorization': `Bearer ${token}`,
|
|
||||||
}),
|
|
||||||
credentials: 'omit',
|
|
||||||
})).json()).data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* exported uploadStyle */
|
//#region Internals
|
||||||
async function uploadStyle(style) {
|
|
||||||
return (await (await fetch(`${URLS.usw}api/style/${style._usw.id}`, {
|
class TokenHooks {
|
||||||
method: 'POST',
|
constructor(id) {
|
||||||
headers: new Headers({
|
this.id = id;
|
||||||
'Authorization': `Bearer ${style._usw.token}`,
|
}
|
||||||
'Content-Type': 'application/json',
|
keyName(name) {
|
||||||
}),
|
return `${name}/${this.id}`;
|
||||||
body: JSON.stringify({
|
}
|
||||||
code: style.sourceCode,
|
query(query) {
|
||||||
}),
|
return Object.assign(query, {vendor_data: this.id});
|
||||||
credentials: 'omit',
|
}
|
||||||
})).json()).data;
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
})();
|
||||||
|
|
||||||
|
/* 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);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,9 +19,8 @@
|
||||||
|
|
||||||
if (location.pathname === '/api/oauth/style/new') {
|
if (location.pathname === '/api/oauth/style/new') {
|
||||||
const styleId = Number(new URLSearchParams(location.search).get('vendor_data'));
|
const styleId = Number(new URLSearchParams(location.search).get('vendor_data'));
|
||||||
API.styles.get(styleId).then(style => {
|
API.data.pop('usw' + styleId).then(data => {
|
||||||
style.sourceCode = style.tmpSourceCode;
|
sendPostMessage({type: 'usw-fill-new-style', data});
|
||||||
sendPostMessage({type: 'usw-fill-new-style', data: style});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
22
edit.html
22
edit.html
|
@ -242,7 +242,7 @@
|
||||||
|
|
||||||
<body id="stylus-edit">
|
<body id="stylus-edit">
|
||||||
<div id="header">
|
<div id="header">
|
||||||
<h1 id="heading"> </h1> <!-- nbsp allocates the actual height which prevents page shift -->
|
<h1 id="heading" i18n-data-edit="editStyleHeading" i18n-data-add="addStyleTitle"></h1>
|
||||||
<section id="basic-info">
|
<section id="basic-info">
|
||||||
<div id="basic-info-name">
|
<div id="basic-info-name">
|
||||||
<input id="name" class="style-contributor" spellcheck="false">
|
<input id="name" class="style-contributor" spellcheck="false">
|
||||||
|
@ -262,7 +262,7 @@
|
||||||
<input type="checkbox" id="enabled" class="style-contributor">
|
<input type="checkbox" id="enabled" class="style-contributor">
|
||||||
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
|
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
|
||||||
</label>
|
</label>
|
||||||
<label id="preview-label" i18n-text="previewLabel" i18n-title="previewTooltip" class="hidden">
|
<label id="preview-label" i18n-text="previewLabel" i18n-title="previewTooltip">
|
||||||
<input type="checkbox" id="editor.livePreview">
|
<input type="checkbox" id="editor.livePreview">
|
||||||
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
|
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
|
||||||
</label>
|
</label>
|
||||||
|
@ -392,11 +392,21 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
<details id="integration" data-pref="editor.integration.expanded" class="ignore-pref-if-compact">
|
<details id="publish" data-pref="editor.publish.expanded" class="ignore-pref-if-compact">
|
||||||
<summary><h2 i18n-text="integration"></h2></summary>
|
<summary><h2 i18n-text="publish"></h2></summary>
|
||||||
<div>
|
<div>
|
||||||
<button id="publish-style" i18n-text="uploadStyle"></button>
|
<a id="usw-url" href="https://userstyles.world" target="_blank"> </a>
|
||||||
<button id="revoke-link" i18n-text="revokeLink"></button>
|
<div id="usw-link-info">
|
||||||
|
<dl><dt i18n-text="styleName"></dt><dd data-usw="name"></dd></dl>
|
||||||
|
<dl><dt i18n-text="genericDescription"></dt><dd data-usw="description"></dd></dl>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<button id="usw-publish-style"
|
||||||
|
i18n-data-publish="publishStyle"
|
||||||
|
i18n-data-push="publishPush"></button>
|
||||||
|
<button id="usw-disconnect" i18n-text="optionsSyncDisconnect"></button>
|
||||||
|
<span id="usw-progress"></span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
<details id="sections-list" data-pref="editor.toc.expanded" class="ignore-pref-if-compact">
|
<details id="sections-list" data-pref="editor.toc.expanded" class="ignore-pref-if-compact">
|
||||||
|
|
12
edit/base.js
12
edit/base.js
|
@ -21,6 +21,7 @@
|
||||||
* @namespace Editor
|
* @namespace Editor
|
||||||
*/
|
*/
|
||||||
const editor = {
|
const editor = {
|
||||||
|
style: null,
|
||||||
dirty: DirtyReporter(),
|
dirty: DirtyReporter(),
|
||||||
isUsercss: false,
|
isUsercss: false,
|
||||||
isWindowed: false,
|
isWindowed: false,
|
||||||
|
@ -34,6 +35,10 @@ const editor = {
|
||||||
previewDelay: 200, // Chrome devtools uses 200
|
previewDelay: 200, // Chrome devtools uses 200
|
||||||
scrollInfo: null,
|
scrollInfo: null,
|
||||||
|
|
||||||
|
onStyleUpdated() {
|
||||||
|
document.documentElement.classList.toggle('is-new-style', !editor.style.id);
|
||||||
|
},
|
||||||
|
|
||||||
updateTitle(isDirty = editor.dirty.isDirty()) {
|
updateTitle(isDirty = editor.dirty.isDirty()) {
|
||||||
const {customName, name} = editor.style;
|
const {customName, name} = editor.style;
|
||||||
document.title = `${
|
document.title = `${
|
||||||
|
@ -84,6 +89,7 @@ const baseInit = (() => {
|
||||||
// switching the mode here to show the correct page ASAP, usually before DOMContentLoaded
|
// switching the mode here to show the correct page ASAP, usually before DOMContentLoaded
|
||||||
editor.isUsercss = Boolean(style.usercssData || !style.id && prefs.get('newStyleAsUsercss'));
|
editor.isUsercss = Boolean(style.usercssData || !style.id && prefs.get('newStyleAsUsercss'));
|
||||||
editor.style = style;
|
editor.style = style;
|
||||||
|
editor.onStyleUpdated();
|
||||||
editor.updateTitle(false);
|
editor.updateTitle(false);
|
||||||
document.documentElement.classList.toggle('usercss', editor.isUsercss);
|
document.documentElement.classList.toggle('usercss', editor.isUsercss);
|
||||||
sessionStore.justEditedStyleId = style.id || '';
|
sessionStore.justEditedStyleId = style.id || '';
|
||||||
|
@ -132,8 +138,7 @@ baseInit.domReady.then(() => {
|
||||||
document.body.classList.remove('compact-layout', 'fixed-header');
|
document.body.classList.remove('compact-layout', 'fixed-header');
|
||||||
window.off('scroll', fixedHeader);
|
window.off('scroll', fixedHeader);
|
||||||
}
|
}
|
||||||
for (const type of ['options', 'toc', 'lint']) {
|
for (const el of $$('details[data-pref]')) {
|
||||||
const el = $(`details[data-pref="editor.${type}.expanded"]`);
|
|
||||||
el.open = compact ? false : prefs.get(el.dataset.pref);
|
el.open = compact ? false : prefs.get(el.dataset.pref);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -161,9 +166,6 @@ baseInit.ready.then(() => {
|
||||||
initThemeElement();
|
initThemeElement();
|
||||||
setupLivePrefs();
|
setupLivePrefs();
|
||||||
|
|
||||||
$('#heading').textContent = t(editor.style.id ? 'editStyleHeading' : 'addStyleTitle');
|
|
||||||
$('#preview-label').classList.toggle('hidden', !editor.style.id);
|
|
||||||
|
|
||||||
require(Object.values(editor.lazyKeymaps), () => {
|
require(Object.values(editor.lazyKeymaps), () => {
|
||||||
initKeymapElement();
|
initKeymapElement();
|
||||||
prefs.subscribe('editor.keyMap', showHotkeyInTooltip, {runNow: true});
|
prefs.subscribe('editor.keyMap', showHotkeyInTooltip, {runNow: true});
|
||||||
|
|
139
edit/edit.css
139
edit/edit.css
|
@ -7,6 +7,14 @@ body {
|
||||||
font: 12px arial,sans-serif;
|
font: 12px arial,sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #000;
|
||||||
|
transition: color .5s;
|
||||||
|
}
|
||||||
|
a:hover {
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
|
||||||
#global-progress {
|
#global-progress {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
height: 4px;
|
height: 4px;
|
||||||
|
@ -24,10 +32,17 @@ body {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html.is-new-style #preview-label,
|
||||||
|
html.is-new-style #publish,
|
||||||
.hidden {
|
.hidden {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
html.is-new-style #heading::after {
|
||||||
|
content: attr(data-add);
|
||||||
|
}
|
||||||
|
html:not(.is-new-style) #heading::after {
|
||||||
|
content: attr(data-edit);
|
||||||
|
}
|
||||||
|
|
||||||
/************ embedded popup for simple-window editor ************/
|
/************ embedded popup for simple-window editor ************/
|
||||||
#popup-iframe {
|
#popup-iframe {
|
||||||
|
@ -215,7 +230,9 @@ input:invalid {
|
||||||
margin-left: -13px;
|
margin-left: -13px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
#header summary + * {
|
||||||
|
padding: .5rem 0;
|
||||||
|
}
|
||||||
#header summary h2 {
|
#header summary h2 {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
border-bottom: 1px dotted transparent;
|
border-bottom: 1px dotted transparent;
|
||||||
|
@ -225,9 +242,6 @@ input:invalid {
|
||||||
padding-left: 13px; /* clicking directly on details-marker doesn't set pref so we cover it with h2 */
|
padding-left: 13px; /* clicking directly on details-marker doesn't set pref so we cover it with h2 */
|
||||||
}
|
}
|
||||||
|
|
||||||
#options-wrapper {
|
|
||||||
padding: .5rem 0;
|
|
||||||
}
|
|
||||||
#header summary:hover h2 {
|
#header summary:hover h2 {
|
||||||
border-color: #bbb;
|
border-color: #bbb;
|
||||||
}
|
}
|
||||||
|
@ -244,6 +258,7 @@ input:invalid {
|
||||||
|
|
||||||
#header details {
|
#header details {
|
||||||
margin-top: .5rem;
|
margin-top: .5rem;
|
||||||
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#actions > * {
|
#actions > * {
|
||||||
|
@ -276,6 +291,81 @@ input:invalid {
|
||||||
#lint:not([open]) h2 {
|
#lint:not([open]) h2 {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#publish > div > * {
|
||||||
|
margin-top: .75em;
|
||||||
|
}
|
||||||
|
#publish a:visited {
|
||||||
|
margin-top: .75em;
|
||||||
|
}
|
||||||
|
#publish[data-connected] summary::marker,
|
||||||
|
#publish[data-connected] h2 {
|
||||||
|
color: hsl(180, 100%, 20%);
|
||||||
|
}
|
||||||
|
#publish:not([data-connected]) #usw-link-info,
|
||||||
|
#publish:not([data-connected]) #usw-disconnect {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
#publish[data-connected] #usw-publish-style::after {
|
||||||
|
content: attr(data-push);
|
||||||
|
}
|
||||||
|
#publish:not([data-connected]) #usw-publish-style::after {
|
||||||
|
content: attr(data-publish);
|
||||||
|
}
|
||||||
|
#usw-link-info dl {
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
#usw-link-info dt {
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
#usw-link-info dt::after {
|
||||||
|
content: ":"
|
||||||
|
}
|
||||||
|
#usw-link-info dt,
|
||||||
|
#usw-link-info dd {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
#usw-link-info dd {
|
||||||
|
margin-left: .5em;
|
||||||
|
}
|
||||||
|
#usw-link-info dd[data-usw="name"] {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
#usw-progress {
|
||||||
|
position: relative;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
#usw-progress .success,
|
||||||
|
#usw-progress .unchanged {
|
||||||
|
font-size: 150%;
|
||||||
|
font-weight: bold;
|
||||||
|
position: absolute;
|
||||||
|
margin-left: .25em;
|
||||||
|
}
|
||||||
|
#usw-progress .success {
|
||||||
|
margin-top: -.25em;
|
||||||
|
}
|
||||||
|
#usw-progress .success::after {
|
||||||
|
content: '\2713'; /* checkmark */
|
||||||
|
}
|
||||||
|
#usw-progress .unchanged::after {
|
||||||
|
content: '=';
|
||||||
|
}
|
||||||
|
#usw-progress .error {
|
||||||
|
display: block;
|
||||||
|
margin-top: .5em;
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
#usw-progress .error + div {
|
||||||
|
font-size: smaller;
|
||||||
|
}
|
||||||
|
#usw-progress .lds-spinner {
|
||||||
|
transform: scale(0.125);
|
||||||
|
transform-origin: 0 10px;
|
||||||
|
}
|
||||||
/* options */
|
/* options */
|
||||||
#options [type="number"] {
|
#options [type="number"] {
|
||||||
width: 3.5em;
|
width: 3.5em;
|
||||||
|
@ -739,7 +829,6 @@ body:not(.find-open) [data-match-highlight-count="1"] .CodeMirror-selection-high
|
||||||
#lint {
|
#lint {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
margin: .5rem -1rem 0;
|
margin: .5rem -1rem 0;
|
||||||
min-height: 30px;
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -758,7 +847,7 @@ body:not(.find-open) [data-match-highlight-count="1"] .CodeMirror-selection-high
|
||||||
text-indent: -2px;
|
text-indent: -2px;
|
||||||
}
|
}
|
||||||
#lint > .lint-scroll-container {
|
#lint > .lint-scroll-container {
|
||||||
margin: 34px 10px 0;
|
margin: 1rem 10px 0;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
|
@ -954,7 +1043,7 @@ body.linter-disabled .hidden-unless-compact {
|
||||||
position: inherit;
|
position: inherit;
|
||||||
border-right: none;
|
border-right: none;
|
||||||
border-bottom: 1px dashed #AAA;
|
border-bottom: 1px dashed #AAA;
|
||||||
padding: 0;
|
padding: .5rem 1rem .5rem .5rem;
|
||||||
}
|
}
|
||||||
.fixed-header {
|
.fixed-header {
|
||||||
padding-top: var(--fixed-padding);
|
padding-top: var(--fixed-padding);
|
||||||
|
@ -972,24 +1061,30 @@ body.linter-disabled .hidden-unless-compact {
|
||||||
.fixed-header #options {
|
.fixed-header #options {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
#header summary + *,
|
||||||
|
#lint > .lint-scroll-container {
|
||||||
|
margin-left: 1rem;
|
||||||
|
padding: .25rem 0 .5rem;
|
||||||
|
}
|
||||||
#actions {
|
#actions {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
padding: 0 1rem;
|
|
||||||
margin: 0;
|
margin: 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
#header input[type="checkbox"] {
|
#header input[type="checkbox"] {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
#header details {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
#heading,
|
#heading,
|
||||||
h2 {
|
h2 {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
#basic-info {
|
#basic-info {
|
||||||
padding: .5rem 1rem;
|
margin-bottom: .5rem;
|
||||||
margin: 0;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
@ -1006,22 +1101,17 @@ body.linter-disabled .hidden-unless-compact {
|
||||||
#options-wrapper {
|
#options-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
padding: .5rem 1rem 0;
|
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
#toc {
|
|
||||||
padding: .5rem 1rem;
|
|
||||||
}
|
|
||||||
#details-wrapper {
|
#details-wrapper {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
padding-bottom: .25rem;
|
|
||||||
}
|
}
|
||||||
#options {
|
#options[open] {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
#sections-list[open] {
|
#sections-list[open] {
|
||||||
height: 102px;
|
max-height: 102px;
|
||||||
}
|
}
|
||||||
#sections-list[open] #toc {
|
#sections-list[open] #toc {
|
||||||
max-height: 60px;
|
max-height: 60px;
|
||||||
|
@ -1029,13 +1119,16 @@ body.linter-disabled .hidden-unless-compact {
|
||||||
}
|
}
|
||||||
#sections-list,
|
#sections-list,
|
||||||
#lint {
|
#lint {
|
||||||
width: 50%;
|
max-width: 50%;
|
||||||
}
|
}
|
||||||
.options-column {
|
.options-column {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
padding-right: .5rem;
|
padding-right: .5rem;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
.options-column > .usercss-only {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
#options-wrapper .options-column:nth-child(2) {
|
#options-wrapper .options-column:nth-child(2) {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
@ -1054,8 +1147,9 @@ body.linter-disabled .hidden-unless-compact {
|
||||||
margin-left: 0;
|
margin-left: 0;
|
||||||
padding-left: 4px;
|
padding-left: 4px;
|
||||||
}
|
}
|
||||||
#options h2 {
|
#header summary h2 {
|
||||||
margin: 0 0 .5em;
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
}
|
}
|
||||||
.option label {
|
.option label {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
@ -1069,7 +1163,8 @@ body.linter-disabled .hidden-unless-compact {
|
||||||
top: 0.2rem;
|
top: 0.2rem;
|
||||||
}
|
}
|
||||||
#lint > .lint-scroll-container {
|
#lint > .lint-scroll-container {
|
||||||
margin: 26px 1rem 0;
|
padding-top: 0;
|
||||||
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
#lint {
|
#lint {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
42
edit/edit.js
42
edit/edit.js
|
@ -11,7 +11,6 @@
|
||||||
/* global linterMan */
|
/* global linterMan */
|
||||||
/* global prefs */
|
/* global prefs */
|
||||||
/* global t */// localization.js
|
/* global t */// localization.js
|
||||||
/* global updateUI revokeLinking publishStyle */// usw-integration.js
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
//#region init
|
//#region init
|
||||||
|
@ -19,7 +18,6 @@
|
||||||
baseInit.ready.then(async () => {
|
baseInit.ready.then(async () => {
|
||||||
await waitForSheet();
|
await waitForSheet();
|
||||||
(editor.isUsercss ? SourceEditor : SectionsEditor)();
|
(editor.isUsercss ? SourceEditor : SectionsEditor)();
|
||||||
updateUI();
|
|
||||||
await editor.ready;
|
await editor.ready;
|
||||||
editor.ready = true;
|
editor.ready = true;
|
||||||
editor.dirty.onChange(editor.updateDirty);
|
editor.dirty.onChange(editor.updateDirty);
|
||||||
|
@ -48,33 +46,29 @@ baseInit.ready.then(async () => {
|
||||||
require(['/edit/linter-dialogs'], () => linterMan.showLintConfig());
|
require(['/edit/linter-dialogs'], () => linterMan.showLintConfig());
|
||||||
$('#lint-help').onclick = () =>
|
$('#lint-help').onclick = () =>
|
||||||
require(['/edit/linter-dialogs'], () => linterMan.showLintHelp());
|
require(['/edit/linter-dialogs'], () => linterMan.showLintHelp());
|
||||||
$('#revoke-link').onclick = () => revokeLinking();
|
|
||||||
$('#publish-style').onclick = () => publishStyle();
|
|
||||||
require([
|
require([
|
||||||
'/edit/autocomplete',
|
'/edit/autocomplete',
|
||||||
'/edit/global-search',
|
'/edit/global-search',
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
//#region events
|
||||||
|
|
||||||
|
const IGNORE_UPDATE_REASONS = [
|
||||||
|
'editPreview',
|
||||||
|
'editPreviewEnd',
|
||||||
|
'editSave',
|
||||||
|
'config',
|
||||||
|
];
|
||||||
|
|
||||||
msg.onExtension(request => {
|
msg.onExtension(request => {
|
||||||
const {style} = request;
|
const {style} = request;
|
||||||
switch (request.method) {
|
switch (request.method) {
|
||||||
case 'styleUpdated':
|
case 'styleUpdated':
|
||||||
if (editor.style.id === style.id) {
|
if (editor.style.id === style.id && !IGNORE_UPDATE_REASONS.includes(request.reason)) {
|
||||||
if (!['editPreview', 'editPreviewEnd', 'editSave', 'config'].includes(request.reason)) {
|
Promise.resolve(request.codeIsUpdated === false ? style : API.styles.get(style.id))
|
||||||
Promise.resolve(request.codeIsUpdated === false ? style : API.styles.get(style.id))
|
.then(newStyle => editor.replaceStyle(newStyle, request.codeIsUpdated));
|
||||||
.then(newStyle => {
|
|
||||||
editor.replaceStyle(newStyle, request.codeIsUpdated);
|
|
||||||
|
|
||||||
if (['success-publishing', 'success-revoke'].includes(request.reason)) {
|
|
||||||
updateUI(newStyle);
|
|
||||||
}
|
|
||||||
if (request.reason === 'publishing-failed') {
|
|
||||||
messageBoxProxy.alert(newStyle._usw.publishingError, 'pre',
|
|
||||||
'UserStyles.world: ' + t('genericError'));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'styleDeleted':
|
case 'styleDeleted':
|
||||||
|
@ -262,15 +256,11 @@ editor.livePreview = (() => {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Function} [fn] - preprocessor
|
* @param {Function} [fn] - preprocessor
|
||||||
* @param {boolean} [show]
|
|
||||||
*/
|
*/
|
||||||
init(fn, show) {
|
init(fn) {
|
||||||
preprocess = fn;
|
preprocess = fn;
|
||||||
if (show != null) toggle(show);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
toggle,
|
|
||||||
|
|
||||||
update(newData) {
|
update(newData) {
|
||||||
data = newData;
|
data = newData;
|
||||||
if (!port) {
|
if (!port) {
|
||||||
|
@ -290,10 +280,6 @@ editor.livePreview = (() => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggle(state) {
|
|
||||||
$('#preview-label').classList.toggle('hidden', !state);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function updatePreviewer(data) {
|
async function updatePreviewer(data) {
|
||||||
const errorContainer = $('#preview-errors');
|
const errorContainer = $('#preview-errors');
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/* global $ $$ $create $remove focusAccessibility */// dom.js
|
/* global $ $$ $create $remove focusAccessibility toggleDataset */// dom.js
|
||||||
/* global CodeMirror */
|
/* global CodeMirror */
|
||||||
/* global chromeLocal */// storage-util.js
|
/* global chromeLocal */// storage-util.js
|
||||||
/* global colorMimicry */
|
/* global colorMimicry */
|
||||||
|
@ -876,15 +876,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function toggleDataset(el, prop, state) {
|
|
||||||
if (state) {
|
|
||||||
el.dataset[prop] = '';
|
|
||||||
} else {
|
|
||||||
delete el.dataset[prop];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function saveWindowScrollPos() {
|
function saveWindowScrollPos() {
|
||||||
state.scrollX = window.scrollX;
|
state.scrollX = window.scrollX;
|
||||||
state.scrollY = window.scrollY;
|
state.scrollY = window.scrollY;
|
||||||
|
|
|
@ -25,7 +25,7 @@ function SectionsEditor() {
|
||||||
|
|
||||||
updateHeader();
|
updateHeader();
|
||||||
rerouteHotkeys.toggle(true); // enabled initially because we don't always focus a CodeMirror
|
rerouteHotkeys.toggle(true); // enabled initially because we don't always focus a CodeMirror
|
||||||
editor.livePreview.init(null, style.id);
|
editor.livePreview.init();
|
||||||
container.classList.add('section-editor');
|
container.classList.add('section-editor');
|
||||||
$('#to-mozilla').on('click', showMozillaFormat);
|
$('#to-mozilla').on('click', showMozillaFormat);
|
||||||
$('#to-mozilla-help').on('click', showToMozillaHelp);
|
$('#to-mozilla-help').on('click', showToMozillaHelp);
|
||||||
|
@ -54,6 +54,11 @@ function SectionsEditor() {
|
||||||
return `${t('sectionCode')} ${index + 1}`;
|
return `${t('sectionCode')} ${index + 1}`;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getValue(asObject) {
|
||||||
|
const st = getModel();
|
||||||
|
return asObject ? st : MozDocMapper.styleToCss(st);
|
||||||
|
},
|
||||||
|
|
||||||
getSearchableInputs(cm) {
|
getSearchableInputs(cm) {
|
||||||
const sec = sections.find(s => s.cm === cm);
|
const sec = sections.find(s => s.cm === cm);
|
||||||
return sec ? sec.appliesTo.map(a => a.valueEl).filter(Boolean) : [];
|
return sec ? sec.appliesTo.map(a => a.valueEl).filter(Boolean) : [];
|
||||||
|
@ -86,14 +91,13 @@ function SectionsEditor() {
|
||||||
await initSections(newStyle.sections, {replace: true});
|
await initSections(newStyle.sections, {replace: true});
|
||||||
}
|
}
|
||||||
Object.assign(style, newStyle);
|
Object.assign(style, newStyle);
|
||||||
|
editor.onStyleUpdated();
|
||||||
updateHeader();
|
updateHeader();
|
||||||
dirty.clear();
|
dirty.clear();
|
||||||
// Go from new style URL to edit style URL
|
// Go from new style URL to edit style URL
|
||||||
if (location.href.indexOf('id=') === -1 && style.id) {
|
if (style.id && !/[&?]id=/.test(location.search)) {
|
||||||
history.replaceState({}, document.title, 'edit.html?id=' + style.id);
|
history.replaceState({}, document.title, `${location.pathname}?id=${style.id}`);
|
||||||
$('#heading').textContent = t('editStyleHeading');
|
|
||||||
}
|
}
|
||||||
editor.livePreview.toggle(Boolean(style.id));
|
|
||||||
updateLivePreview();
|
updateLivePreview();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -323,7 +327,7 @@ function SectionsEditor() {
|
||||||
|
|
||||||
function showMozillaFormat() {
|
function showMozillaFormat() {
|
||||||
const popup = showCodeMirrorPopup(t('styleToMozillaFormatTitle'), '', {readOnly: true});
|
const popup = showCodeMirrorPopup(t('styleToMozillaFormatTitle'), '', {readOnly: true});
|
||||||
popup.codebox.setValue(MozDocMapper.styleToCss(getModel()));
|
popup.codebox.setValue(editor.getValue());
|
||||||
popup.codebox.execCommand('selectAll');
|
popup.codebox.execCommand('selectAll');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -425,7 +429,7 @@ function SectionsEditor() {
|
||||||
editor.updateToc();
|
editor.updateToc();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @returns {Style} */
|
/** @returns {StyleObj} */
|
||||||
function getModel() {
|
function getModel() {
|
||||||
return Object.assign({}, style, {
|
return Object.assign({}, style, {
|
||||||
sections: sections.filter(s => !s.removed).map(s => s.getModel()),
|
sections: sections.filter(s => !s.removed).map(s => s.getModel()),
|
||||||
|
|
|
@ -30,7 +30,7 @@ function SourceEditor() {
|
||||||
const cm = cmFactory.create($('.single-editor'));
|
const cm = cmFactory.create($('.single-editor'));
|
||||||
const sectionFinder = MozSectionFinder(cm);
|
const sectionFinder = MozSectionFinder(cm);
|
||||||
const sectionWidget = MozSectionWidget(cm, sectionFinder);
|
const sectionWidget = MozSectionWidget(cm, sectionFinder);
|
||||||
editor.livePreview.init(preprocess, style.id);
|
editor.livePreview.init(preprocess);
|
||||||
createMetaCompiler(meta => {
|
createMetaCompiler(meta => {
|
||||||
style.usercssData = meta;
|
style.usercssData = meta;
|
||||||
style.name = meta.name;
|
style.name = meta.name;
|
||||||
|
@ -48,6 +48,7 @@ function SourceEditor() {
|
||||||
closestVisible: () => cm,
|
closestVisible: () => cm,
|
||||||
getEditors: () => [cm],
|
getEditors: () => [cm],
|
||||||
getEditorTitle: () => '',
|
getEditorTitle: () => '',
|
||||||
|
getValue: () => cm.getValue(),
|
||||||
getSearchableInputs: () => [],
|
getSearchableInputs: () => [],
|
||||||
prevEditor: nextPrevSection.bind(null, -1),
|
prevEditor: nextPrevSection.bind(null, -1),
|
||||||
nextEditor: nextPrevSection.bind(null, 1),
|
nextEditor: nextPrevSection.bind(null, 1),
|
||||||
|
@ -241,9 +242,8 @@ function SourceEditor() {
|
||||||
}
|
}
|
||||||
sessionStore.justEditedStyleId = newStyle.id;
|
sessionStore.justEditedStyleId = newStyle.id;
|
||||||
Object.assign(style, newStyle);
|
Object.assign(style, newStyle);
|
||||||
$('#preview-label').classList.remove('hidden');
|
editor.onStyleUpdated();
|
||||||
updateMeta();
|
updateMeta();
|
||||||
editor.livePreview.toggle(Boolean(style.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 editor */
|
||||||
|
/* global t */// localization.js
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
let uswPort;
|
(() => {
|
||||||
|
//#region Main
|
||||||
|
|
||||||
function connectToPort() {
|
const ERROR_TITLE = 'UserStyles.world ' + t('genericError');
|
||||||
if (!uswPort) {
|
const PROGRESS = '#usw-progress';
|
||||||
uswPort = chrome.runtime.connect({name: 'link-style-usw'});
|
let spinnerTimer = 0;
|
||||||
uswPort.onDisconnect.addListener(err => {
|
let prevCode = '';
|
||||||
throw err;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
msg.onExtension(request => {
|
||||||
|
if (request.method === 'uswData' &&
|
||||||
|
request.style.id === editor.style.id) {
|
||||||
|
Object.assign(editor.style, request.style);
|
||||||
|
updateUI();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/* exported revokeLinking */
|
baseInit.ready.then(() => {
|
||||||
function revokeLinking() {
|
updateUI();
|
||||||
connectToPort();
|
$('#usw-publish-style').onclick = disableWhileActive(publishStyle);
|
||||||
|
$('#usw-disconnect').onclick = disableWhileActive(disconnect);
|
||||||
|
});
|
||||||
|
|
||||||
uswPort.postMessage({reason: 'revoke', data: editor.style});
|
async function publishStyle() {
|
||||||
}
|
const {id} = editor.style;
|
||||||
|
if (await API.data.has('usw' + id) &&
|
||||||
/* exported publishStyle */
|
!await messageBoxProxy.confirm(t('publishRetry'), 'danger', ERROR_TITLE)) {
|
||||||
function publishStyle() {
|
return;
|
||||||
connectToPort();
|
}
|
||||||
const data = Object.assign(editor.style, {sourceCode: editor.getEditors()[0].getValue()});
|
const code = editor.getValue();
|
||||||
uswPort.postMessage({reason: 'publish', data});
|
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);
|
||||||
/* exported updateUI */
|
$(PROGRESS).append(...failed && [
|
||||||
function updateUI(useStyle) {
|
$create('div.error', {title}, res),
|
||||||
const style = useStyle || editor.style;
|
$create('div', t('publishReconnect')),
|
||||||
if (style._usw && style._usw.token) {
|
] || [
|
||||||
$('#revoke-link').style = '';
|
$create(`span.${isDiff ? 'success' : 'unchanged'}`, {title}),
|
||||||
|
|
||||||
const linkInformation = $create('div', {id: 'link-info'}, [
|
|
||||||
$create('p', `Style name: ${style._usw.name}`),
|
|
||||||
$create('p', `Description: ${style._usw.description}`),
|
|
||||||
]);
|
]);
|
||||||
$remove('#link-info');
|
if (!failed) prevCode = code;
|
||||||
$('#integration').insertBefore(linkInformation, $('#integration').firstChild);
|
|
||||||
} else {
|
|
||||||
$('#revoke-link').style = 'display: none;';
|
|
||||||
$remove('#link-info');
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
async function disconnect() {
|
||||||
|
await API.usw.revoke(editor.style.id);
|
||||||
|
prevCode = null; // to allow the next publishStyle to upload 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}` : ''}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
//#region Utility
|
||||||
|
|
||||||
|
function disableWhileActive(fn) {
|
||||||
|
/** @this {Element} */
|
||||||
|
return async function () {
|
||||||
|
this.disabled = true;
|
||||||
|
timerOn();
|
||||||
|
await fn().catch(console.error);
|
||||||
|
timerOff();
|
||||||
|
this.disabled = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function timerOn() {
|
||||||
|
if (!spinnerTimer) {
|
||||||
|
$(PROGRESS).textContent = '';
|
||||||
|
spinnerTimer = setTimeout(showSpinner, 250, PROGRESS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function timerOff() {
|
||||||
|
$remove(`${PROGRESS} .lds-spinner`);
|
||||||
|
clearTimeout(spinnerTimer);
|
||||||
|
spinnerTimer = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//#endregion
|
||||||
|
})();
|
||||||
|
|
|
@ -236,6 +236,11 @@ select[disabled] + .select-arrow {
|
||||||
fill: hsl(0, 0%, 50%);
|
fill: hsl(0, 0%, 50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
summary {
|
||||||
|
-moz-user-select: none;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* global stuff we use everywhere */
|
/* global stuff we use everywhere */
|
||||||
.hidden {
|
.hidden {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
<script src="content/style-injector.js"></script>
|
<script src="content/style-injector.js"></script>
|
||||||
<script src="content/apply.js"></script>
|
<script src="content/apply.js"></script>
|
||||||
|
|
||||||
|
<link href="spinner.css" rel="stylesheet">
|
||||||
<link href="install-usercss/install-usercss.css" rel="stylesheet">
|
<link href="install-usercss/install-usercss.css" rel="stylesheet">
|
||||||
</head>
|
</head>
|
||||||
<body id="stylus-install-usercss">
|
<body id="stylus-install-usercss">
|
||||||
|
|
|
@ -297,93 +297,12 @@ label {
|
||||||
padding-left: 16px;
|
padding-left: 16px;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
/* spinner: https://github.com/loadingio/css-spinner */
|
|
||||||
|
|
||||||
@keyframes lds-spinner {
|
|
||||||
0% {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.lds-spinner {
|
.lds-spinner {
|
||||||
position: absolute;
|
|
||||||
width: 200px;
|
|
||||||
height: 200px;
|
|
||||||
top: 50px;
|
top: 50px;
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
margin: auto;
|
|
||||||
opacity: .2;
|
opacity: .2;
|
||||||
transition: opacity .5s;
|
transition: opacity .5s;
|
||||||
}
|
animation: none;
|
||||||
.lds-spinner div {
|
|
||||||
left: 94px;
|
|
||||||
top: 23px;
|
|
||||||
position: absolute;
|
|
||||||
animation: lds-spinner linear 1s infinite;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
@keyframes load3 {
|
|
||||||
0% {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
100% {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/************ reponsive layouts ************/
|
/************ reponsive layouts ************/
|
||||||
|
|
|
@ -22,7 +22,7 @@ document.on('visibilitychange', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (!installed) {
|
if (!cm) {
|
||||||
$('#header').appendChild($create('.lds-spinner',
|
$('#header').appendChild($create('.lds-spinner',
|
||||||
new Array(12).fill($create('div')).map(e => e.cloneNode())));
|
new Array(12).fill($create('div')).map(e => e.cloneNode())));
|
||||||
}
|
}
|
||||||
|
|
18
js/dom.js
18
js/dom.js
|
@ -13,6 +13,8 @@
|
||||||
moveFocus
|
moveFocus
|
||||||
scrollElementIntoView
|
scrollElementIntoView
|
||||||
setupLivePrefs
|
setupLivePrefs
|
||||||
|
showSpinner
|
||||||
|
toggleDataset
|
||||||
waitForSheet
|
waitForSheet
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -325,6 +327,22 @@ function setupLivePrefs(ids = prefs.knownKeys.filter(id => $('#' + id))) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @param {string|Node} parent - selector or DOM node */
|
||||||
|
async function showSpinner(parent) {
|
||||||
|
await require(['/spinner.css']);
|
||||||
|
parent = parent instanceof Node ? parent : $(parent);
|
||||||
|
parent.appendChild($create('.lds-spinner',
|
||||||
|
new Array(12).fill($create('div')).map(e => e.cloneNode())));
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleDataset(el, prop, state) {
|
||||||
|
if (state) {
|
||||||
|
el.dataset[prop] = '';
|
||||||
|
} else {
|
||||||
|
delete el.dataset[prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} selector - beware of $ quirks with `#dotted.id` that won't work with $$
|
* @param {string} selector - beware of $ quirks with `#dotted.id` that won't work with $$
|
||||||
* @param {Object} [opt]
|
* @param {Object} [opt]
|
||||||
|
|
|
@ -61,7 +61,7 @@
|
||||||
'editor.toc.expanded': true, // UI element state: expanded/collapsed
|
'editor.toc.expanded': true, // UI element state: expanded/collapsed
|
||||||
'editor.options.expanded': true, // UI element state: expanded/collapsed
|
'editor.options.expanded': true, // UI element state: expanded/collapsed
|
||||||
'editor.lint.expanded': true, // UI element state: expanded/collapsed
|
'editor.lint.expanded': true, // UI element state: expanded/collapsed
|
||||||
'editor.integration.expanded': true, // UI element state expanded/collapsed
|
'editor.publish.expanded': true, // UI element state expanded/collapsed
|
||||||
'editor.lineWrapping': true, // word wrap
|
'editor.lineWrapping': true, // word wrap
|
||||||
'editor.smartIndent': true, // 'smart' indent
|
'editor.smartIndent': true, // 'smart' indent
|
||||||
'editor.indentWithTabs': false, // smart indent with tabs
|
'editor.indentWithTabs': false, // smart indent with tabs
|
||||||
|
|
|
@ -44,10 +44,10 @@
|
||||||
"background/sync-manager.js",
|
"background/sync-manager.js",
|
||||||
"background/tab-manager.js",
|
"background/tab-manager.js",
|
||||||
"background/token-manager.js",
|
"background/token-manager.js",
|
||||||
"background/usw-api.js",
|
|
||||||
"background/update-manager.js",
|
"background/update-manager.js",
|
||||||
"background/usercss-install-helper.js",
|
"background/usercss-install-helper.js",
|
||||||
"background/usercss-manager.js",
|
"background/usercss-manager.js",
|
||||||
|
"background/usw-api.js",
|
||||||
|
|
||||||
"background/style-manager.js",
|
"background/style-manager.js",
|
||||||
"background/background.js"
|
"background/background.js"
|
||||||
|
|
|
@ -282,102 +282,3 @@ body.search-results-shown {
|
||||||
margin-right: .5em;
|
margin-right: .5em;
|
||||||
flex: 1 1 0;
|
flex: 1 1 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 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;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/* global $ $$ $create $remove */// dom.js
|
/* global $ $$ $create $remove showSpinner */// dom.js
|
||||||
/* global $entry tabURL */// popup.js
|
/* global $entry tabURL */// popup.js
|
||||||
/* global API */// msg.js
|
/* global API */// msg.js
|
||||||
/* global Events */
|
/* global Events */
|
||||||
|
@ -153,13 +153,6 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function showSpinner(parent) {
|
|
||||||
parent = parent instanceof Node ? parent : $(parent);
|
|
||||||
parent.appendChild($create('.lds-spinner',
|
|
||||||
new Array(12).fill($create('div')).map(e => e.cloneNode())));
|
|
||||||
}
|
|
||||||
|
|
||||||
function next() {
|
function next() {
|
||||||
displayedPage = Math.min(totalPages, displayedPage + 1);
|
displayedPage = Math.min(totalPages, displayedPage + 1);
|
||||||
scrollToFirstResult = true;
|
scrollToFirstResult = true;
|
||||||
|
|
98
spinner.css
Normal file
98
spinner.css
Normal file
|
@ -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
Block a user