Linking Styles to USW (#1256)
* Prototype Just able to log the token for the requested style. * Store USw Token * Fix linting * Add revoke capabilities * Add upload capabilities + UI? * Add credentials for production server * Patch up several things * Send styleInfo We will be adding the feature to add style based of the currentStyle, see paring commit31813da300
* Fix clientSecret * Pass styleInfo trough usw's hook Related commit on USW:461ddb03c7
* Adjusted behavior Applied suggestions from Narco. * Wait for `usw-ready`before sending style * don't use `window.` * Ensure correct style is pre-filled * Send over metadata Related USW commit:7d8c4c1248
* Title Case => Title case * _linking => _isUswLinked
This commit is contained in:
parent
ada46e8277
commit
fe45781545
|
@ -1365,6 +1365,18 @@
|
||||||
"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"
|
||||||
|
|
|
@ -6,6 +6,8 @@
|
||||||
/* global prefs */
|
/* global prefs */
|
||||||
/* global tabMan */
|
/* global tabMan */
|
||||||
/* global usercssMan */
|
/* global usercssMan */
|
||||||
|
/* global tokenMan */
|
||||||
|
/* global retrieveStyleInformation uploadStyle */// usw-api.js
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -54,12 +56,14 @@ const styleMan = (() => {
|
||||||
name: style => `ID: ${style.id}`,
|
name: style => `ID: ${style.id}`,
|
||||||
_id: () => uuidv4(),
|
_id: () => uuidv4(),
|
||||||
_rev: () => Date.now(),
|
_rev: () => Date.now(),
|
||||||
|
_usw: () => ({}),
|
||||||
};
|
};
|
||||||
const DELETE_IF_NULL = ['id', 'customName', 'md5Url', 'originalMd5'];
|
const DELETE_IF_NULL = ['id', 'customName', 'md5Url', 'originalMd5'];
|
||||||
/** @type {Promise|boolean} will be `true` to avoid wasting a microtask tick on each `await` */
|
/** @type {Promise|boolean} will be `true` to avoid wasting a microtask tick on each `await` */
|
||||||
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
|
||||||
|
@ -352,6 +356,56 @@ 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), 'success-revoke', true);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'publish':
|
||||||
|
if (!style._usw || !style._usw.token) {
|
||||||
|
// Ensures just the style does have the _isUswLinked property as `true`.
|
||||||
|
for (const {style: someStyle} of dataMap.values()) {
|
||||||
|
if (someStyle._id === style._id) {
|
||||||
|
someStyle._isUswLinked = true;
|
||||||
|
someStyle.sourceCode = style.sourceCode;
|
||||||
|
const {metadata} = await API.worker.parseUsercssMeta(style.sourceCode);
|
||||||
|
someStyle.metadata = metadata;
|
||||||
|
} else {
|
||||||
|
delete someStyle._isUswLinked;
|
||||||
|
delete someStyle.sourceCode;
|
||||||
|
delete someStyle.metadata;
|
||||||
|
}
|
||||||
|
handleSave(await saveStyle(someStyle), null, null, false);
|
||||||
|
}
|
||||||
|
style._usw = {
|
||||||
|
token: await tokenMan.getToken('userstylesworld', true, style),
|
||||||
|
};
|
||||||
|
|
||||||
|
delete style._isUswLinked;
|
||||||
|
delete style.sourceCode;
|
||||||
|
delete style.metadata;
|
||||||
|
for (const [k, v] of Object.entries(await retrieveStyleInformation(style._usw.token))) {
|
||||||
|
style._usw[k] = v;
|
||||||
|
}
|
||||||
|
handleSave(await saveStyle(style), 'success-publishing', true);
|
||||||
|
}
|
||||||
|
uploadStyle(style);
|
||||||
|
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));
|
||||||
|
@ -427,7 +481,7 @@ 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);
|
API.sync.put(style._id, style._rev, style._usw);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveStyle(style) {
|
async function saveStyle(style) {
|
||||||
|
@ -437,7 +491,7 @@ const styleMan = (() => {
|
||||||
return style;
|
return style;
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSave(style, reason, codeIsUpdated) {
|
function handleSave(style, reason, codeIsUpdated, broadcast = true) {
|
||||||
const data = id2data(style.id);
|
const data = id2data(style.id);
|
||||||
const method = data ? 'styleUpdated' : 'styleAdded';
|
const method = data ? 'styleUpdated' : 'styleAdded';
|
||||||
if (!data) {
|
if (!data) {
|
||||||
|
@ -445,7 +499,7 @@ const styleMan = (() => {
|
||||||
} else {
|
} else {
|
||||||
data.style = style;
|
data.style = style;
|
||||||
}
|
}
|
||||||
broadcastStyleUpdated(style, reason, method, codeIsUpdated);
|
if (broadcast) broadcastStyleUpdated(style, reason, method, codeIsUpdated);
|
||||||
return style;
|
return style;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
/* global FIREFOX getActiveTab waitForTabUrl */// toolbox.js
|
/* global FIREFOX getActiveTab waitForTabUrl URLS */// toolbox.js
|
||||||
/* global chromeLocal */// storage-util.js
|
/* global chromeLocal */// storage-util.js
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
@ -48,6 +48,15 @@ const tokenMan = (() => {
|
||||||
'https://' + location.hostname + '.chromiumapp.org/',
|
'https://' + location.hostname + '.chromiumapp.org/',
|
||||||
scopes: ['Files.ReadWrite.AppFolder', 'offline_access'],
|
scopes: ['Files.ReadWrite.AppFolder', 'offline_access'],
|
||||||
},
|
},
|
||||||
|
userstylesworld: {
|
||||||
|
flow: 'code',
|
||||||
|
clientId: 'zeDmKhJIfJqULtcrGMsWaxRtWHEimKgS',
|
||||||
|
clientSecret: 'wqHsvTuThQmXmDiVvOpZxPwSIbyycNFImpAOTxjaIRqDbsXcTOqrymMJKsOMuibFaij' +
|
||||||
|
'ZZAkVYTDbLkQuYFKqgpMsMlFlgwQOYHvHFbgxQHDTwwdOroYhOwFuekCwXUlk',
|
||||||
|
authURL: URLS.usw + 'api/oauth/authorize_style',
|
||||||
|
tokenURL: URLS.usw + 'api/oauth/access_token',
|
||||||
|
redirect_uri: 'https://gusted.xyz/callback_helper/',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
const NETWORK_LATENCY = 30; // seconds
|
const NETWORK_LATENCY = 30; // seconds
|
||||||
|
|
||||||
|
@ -55,11 +64,11 @@ const tokenMan = (() => {
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
||||||
buildKeys(name) {
|
buildKeys(name, styleId) {
|
||||||
const k = {
|
const k = {
|
||||||
TOKEN: `secure/token/${name}/token`,
|
TOKEN: `secure/token/${name}/${styleId ? `${styleId}/` : ''}token`,
|
||||||
EXPIRE: `secure/token/${name}/expire`,
|
EXPIRE: `secure/token/${name}/${styleId ? `${styleId}/` : ''}expire`,
|
||||||
REFRESH: `secure/token/${name}/refresh`,
|
REFRESH: `secure/token/${name}/${styleId ? `${styleId}/` : ''}refresh`,
|
||||||
};
|
};
|
||||||
k.LIST = Object.values(k);
|
k.LIST = Object.values(k);
|
||||||
return k;
|
return k;
|
||||||
|
@ -69,8 +78,8 @@ const tokenMan = (() => {
|
||||||
return AUTH[name].clientId;
|
return AUTH[name].clientId;
|
||||||
},
|
},
|
||||||
|
|
||||||
async getToken(name, interactive) {
|
async getToken(name, interactive, style) {
|
||||||
const k = tokenMan.buildKeys(name);
|
const k = tokenMan.buildKeys(name, style.id);
|
||||||
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]) {
|
||||||
|
@ -83,12 +92,13 @@ const tokenMan = (() => {
|
||||||
if (!interactive) {
|
if (!interactive) {
|
||||||
throw new Error(`Invalid token: ${name}`);
|
throw new Error(`Invalid token: ${name}`);
|
||||||
}
|
}
|
||||||
return authUser(name, k, interactive);
|
const accessToken = authUser(name, k, interactive);
|
||||||
|
return accessToken;
|
||||||
},
|
},
|
||||||
|
|
||||||
async revokeToken(name) {
|
async revokeToken(name, styleId) {
|
||||||
const provider = AUTH[name];
|
const provider = AUTH[name];
|
||||||
const k = tokenMan.buildKeys(name);
|
const k = tokenMan.buildKeys(name, styleId);
|
||||||
if (provider.revoke) {
|
if (provider.revoke) {
|
||||||
try {
|
try {
|
||||||
const token = await chromeLocal.getValue(k.TOKEN);
|
const token = await chromeLocal.getValue(k.TOKEN);
|
||||||
|
@ -177,6 +187,7 @@ const tokenMan = (() => {
|
||||||
grant_type: 'authorization_code',
|
grant_type: 'authorization_code',
|
||||||
client_id: provider.clientId,
|
client_id: provider.clientId,
|
||||||
redirect_uri: query.redirect_uri,
|
redirect_uri: query.redirect_uri,
|
||||||
|
state,
|
||||||
};
|
};
|
||||||
if (provider.clientSecret) {
|
if (provider.clientSecret) {
|
||||||
body.client_secret = provider.clientSecret;
|
body.client_secret = provider.clientSecret;
|
||||||
|
|
29
background/usw-api.js
Normal file
29
background/usw-api.js
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
/* global URLS */ // toolbox.js
|
||||||
|
|
||||||
|
'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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
/* global API */// msg.js
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
(() => {
|
(() => {
|
||||||
|
@ -15,6 +16,12 @@
|
||||||
&& allowedOrigin === event.origin
|
&& allowedOrigin === event.origin
|
||||||
) {
|
) {
|
||||||
sendPostMessage({type: 'usw-remove-stylus-button'});
|
sendPostMessage({type: 'usw-remove-stylus-button'});
|
||||||
|
|
||||||
|
if (location.pathname === '/api/oauth/authorize_style/new') {
|
||||||
|
API.styles.find({_isUswLinked: true}).then(style => {
|
||||||
|
sendPostMessage({type: 'usw-fill-new-style', data: style});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -59,6 +59,7 @@
|
||||||
<script src="edit/source-editor.js"></script>
|
<script src="edit/source-editor.js"></script>
|
||||||
<script src="edit/sections-editor-section.js"></script>
|
<script src="edit/sections-editor-section.js"></script>
|
||||||
<script src="edit/sections-editor.js"></script>
|
<script src="edit/sections-editor.js"></script>
|
||||||
|
<script src="edit/usw-integration.js"></script>
|
||||||
<script src="edit/edit.js"></script>
|
<script src="edit/edit.js"></script>
|
||||||
|
|
||||||
<template data-id="appliesTo">
|
<template data-id="appliesTo">
|
||||||
|
@ -391,6 +392,13 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
|
<details id="integration" data-pref="editor.toc.expanded" class="ignore-pref-if-compact">
|
||||||
|
<summary><h2 i18n-text="integration"></h2></summary>
|
||||||
|
<div>
|
||||||
|
<button id="publish-style" i18n-text="uploadStyle"></button>
|
||||||
|
<button id="revoke-link" i18n-text="revokeLink"></button>
|
||||||
|
</div>
|
||||||
|
</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">
|
||||||
<summary><h2 i18n-text="sections"></h2></summary>
|
<summary><h2 i18n-text="sections"></h2></summary>
|
||||||
<ol id="toc"></ol>
|
<ol id="toc"></ol>
|
||||||
|
|
17
edit/edit.js
17
edit/edit.js
|
@ -11,6 +11,7 @@
|
||||||
/* 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
|
||||||
|
@ -18,6 +19,7 @@
|
||||||
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);
|
||||||
|
@ -42,6 +44,8 @@ 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',
|
||||||
|
@ -52,10 +56,17 @@ 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) {
|
||||||
!['editPreview', 'editPreviewEnd', 'editSave', 'config'].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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case 'styleDeleted':
|
case 'styleDeleted':
|
||||||
|
|
49
edit/usw-integration.js
Normal file
49
edit/usw-integration.js
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
/* global $ $create $remove */// dom.js
|
||||||
|
/* global editor */
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
let uswPort;
|
||||||
|
|
||||||
|
function connectToPort() {
|
||||||
|
if (!uswPort) {
|
||||||
|
uswPort = chrome.runtime.connect({name: 'link-style-usw'});
|
||||||
|
uswPort.onDisconnect.addListener(err => {
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* exported revokeLinking */
|
||||||
|
function revokeLinking() {
|
||||||
|
connectToPort();
|
||||||
|
|
||||||
|
uswPort.postMessage({reason: 'revoke', data: editor.style});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* exported publishStyle */
|
||||||
|
function publishStyle() {
|
||||||
|
connectToPort();
|
||||||
|
const data = Object.assign(editor.style, {sourceCode: editor.getEditors()[0].getValue()});
|
||||||
|
uswPort.postMessage({reason: 'publish', data});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* exported updateUI */
|
||||||
|
function updateUI(useStyle) {
|
||||||
|
const style = useStyle || editor.style;
|
||||||
|
if (style._usw && style._usw.token) {
|
||||||
|
$('#revoke-link').style = '';
|
||||||
|
|
||||||
|
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');
|
||||||
|
}
|
||||||
|
}
|
|
@ -44,6 +44,7 @@
|
||||||
"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",
|
||||||
|
|
Loading…
Reference in New Issue
Block a user