PatchCSP + tweaks/fixes/features (#1107)
* add Patch CSP option * show style version, size, and update age in manager * add scope selector to style search in manager * keep scroll position and selections in tab's session * directly install usercss from raw github links * ditch localStorage, use on-demand SessionStore proxy * simplify localization * allow <code> tag in i18n-html * keep nodes in HTML templates * API.getAllStyles is actually faster with code untouched * fix fitToContent when applies-to is taller than window * dedupe linter.enableForEditor calls * prioritize visible CMs in refreshOnViewListener * don't scroll to last style on editing a new one * delay colorview for invisible CMs * eslint comma-dangle error + autofix files * styleViaXhr: also toggle for disableAll pref * styleViaXhr: allow cookies for sandbox CSP * simplify notes in options * simplify getStylesViaXhr * oldUI fixups: * remove separator before 1st applies-to * center name bubbles * fix updateToc focus on a newly added section * fix fitToContent when cloning section * remove CSS `contain` as it makes no difference * replace overrides with declarative CSS + code cosmetics * simplify adjustWidth and make it work in FF
This commit is contained in:
parent
7fa4d10fd6
commit
420733b93a
|
@ -19,7 +19,7 @@ rules:
|
||||||
brace-style: [2, 1tbs, {allowSingleLine: false}]
|
brace-style: [2, 1tbs, {allowSingleLine: false}]
|
||||||
camelcase: [2, {properties: never}]
|
camelcase: [2, {properties: never}]
|
||||||
class-methods-use-this: [2]
|
class-methods-use-this: [2]
|
||||||
comma-dangle: [0]
|
comma-dangle: [2, {arrays: always-multiline, objects: always-multiline}]
|
||||||
comma-spacing: [2, {before: false, after: true}]
|
comma-spacing: [2, {before: false, after: true}]
|
||||||
comma-style: [2, last]
|
comma-style: [2, last]
|
||||||
complexity: [0]
|
complexity: [0]
|
||||||
|
|
|
@ -260,6 +260,42 @@
|
||||||
"message": "Stop using customized name, switch to the style's own name",
|
"message": "Stop using customized name, switch to the style's own name",
|
||||||
"description": "Tooltip of 'x' button shown in editor when changing the name input of a) styles updated from a URL i.e. not locally created, b) UserCSS styles"
|
"description": "Tooltip of 'x' button shown in editor when changing the name input of a) styles updated from a URL i.e. not locally created, b) UserCSS styles"
|
||||||
},
|
},
|
||||||
|
"dateAbbrDay": {
|
||||||
|
"message": "$value$d",
|
||||||
|
"description": "Day suffix in a short relative date, for example: 8d",
|
||||||
|
"placeholders": {
|
||||||
|
"value": {
|
||||||
|
"content": "$1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dateAbbrHour": {
|
||||||
|
"message": "$value$h",
|
||||||
|
"description": "Hour suffix in a short relative date, for example: 8h",
|
||||||
|
"placeholders": {
|
||||||
|
"value": {
|
||||||
|
"content": "$1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dateAbbrMonth": {
|
||||||
|
"message": "$value$m",
|
||||||
|
"description": "Month suffix in a short relative date, for example: 8m",
|
||||||
|
"placeholders": {
|
||||||
|
"value": {
|
||||||
|
"content": "$1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dateAbbrYear": {
|
||||||
|
"message": "$value$y",
|
||||||
|
"description": "Year suffix in a short relative date, for example: 8y",
|
||||||
|
"placeholders": {
|
||||||
|
"value": {
|
||||||
|
"content": "$1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"dateInstalled": {
|
"dateInstalled": {
|
||||||
"message": "Date installed",
|
"message": "Date installed",
|
||||||
"description": "Option text for the user to sort the style by install date"
|
"description": "Option text for the user to sort the style by install date"
|
||||||
|
@ -976,6 +1012,12 @@
|
||||||
"optionsAdvancedNewStyleAsUsercss": {
|
"optionsAdvancedNewStyleAsUsercss": {
|
||||||
"message": "Write new style as usercss"
|
"message": "Write new style as usercss"
|
||||||
},
|
},
|
||||||
|
"optionsAdvancedPatchCsp": {
|
||||||
|
"message": "Patch <code>CSP</code> to allow style assets"
|
||||||
|
},
|
||||||
|
"optionsAdvancedPatchCspNote": {
|
||||||
|
"message": "Enable this if styles contain images or fonts which fail to load on sites with a strict <code>CSP</code> (<code>Content-Security-Policy</code>).\n\nEnabling this setting will relax <code>CSP</code> restrictions, allowing essential style content to load. This option is only intended for advanced users who understand the potential security implications, and accept responsibility for monitoring the content which they're allowing. Read about CSS-based attacks for more information.\n\nAlso be aware, this particular setting is not guaranteed to take effect if another installed extension modifies the network response first."
|
||||||
|
},
|
||||||
"optionsAdvancedStyleViaXhr": {
|
"optionsAdvancedStyleViaXhr": {
|
||||||
"message": "Instant inject mode"
|
"message": "Instant inject mode"
|
||||||
},
|
},
|
||||||
|
@ -1243,14 +1285,30 @@
|
||||||
"message": "Weekly installs",
|
"message": "Weekly installs",
|
||||||
"description": "Text for label that shows the number of times a search result was installed during last week"
|
"description": "Text for label that shows the number of times a search result was installed during last week"
|
||||||
},
|
},
|
||||||
"searchStyles": {
|
"searchStylesAll": {
|
||||||
"message": "Search contents",
|
"message": "All",
|
||||||
"description": "Label for the search filter textbox on the Manage styles page"
|
"description": "Option for `find styles` scope selector in the manager."
|
||||||
|
},
|
||||||
|
"searchStylesCode": {
|
||||||
|
"message": "CSS code",
|
||||||
|
"description": "Option for `find styles` scope selector in the manager."
|
||||||
},
|
},
|
||||||
"searchStylesHelp": {
|
"searchStylesHelp": {
|
||||||
"message": "</> key focuses the search field.\nPlain text: search within the name, code, homepage URL and sites it is applied to. Words with less than 3 letters are ignored.\nStyles matching a full URL: prefix the search with <url:>, e.g. <url:https://github.com/openstyles/stylus>\nRegular expressions: include slashes and flags, e.g. </body.*?\\ba\\b/simguy>\nExact words: wrap the query in double quotes, e.g. <\".header ~ div\">",
|
"message": "</> or <Ctrl-F> key focuses the search field.\nDefault mode is plain text search for all space-separated terms in any order.\nExact words: wrap the query in double quotes, e.g. <\".header ~ div\">\nRegular expressions: include slashes and flags, e.g. </body.*?\\ba\\b/i>\n\"By URL\" in scope selector: finds styles that apply to a fully specified URL e.g. https://www.example.org/\n\"Metadata\" in scope selector: searches in names, \"applies to\" specifiers, installation URL, update URL, and the entire metadata block for usercss styles.",
|
||||||
"description": "Text in the minihelp displayed when clicking (i) icon to the right of the search input field on the Manage styles page"
|
"description": "Text in the minihelp displayed when clicking (i) icon to the right of the search input field on the Manage styles page"
|
||||||
},
|
},
|
||||||
|
"searchStylesMatchUrl": {
|
||||||
|
"message": "By URL",
|
||||||
|
"description": "Option for `find styles` scope selector in the manager. See searchMatchUrlHint for more info."
|
||||||
|
},
|
||||||
|
"searchStylesMeta": {
|
||||||
|
"message": "Metadata",
|
||||||
|
"description": "Option for `find styles` scope selector in the manager."
|
||||||
|
},
|
||||||
|
"searchStylesName": {
|
||||||
|
"message": "Name",
|
||||||
|
"description": "Option for `find styles` scope selector in the manager."
|
||||||
|
},
|
||||||
"sectionAdd": {
|
"sectionAdd": {
|
||||||
"message": "Add another section",
|
"message": "Add another section",
|
||||||
"description": "Label for the button to add a section"
|
"description": "Label for the button to add a section"
|
||||||
|
|
|
@ -25,7 +25,7 @@ workerUtil.createAPI({
|
||||||
'/js/meta-parser.js'
|
'/js/meta-parser.js'
|
||||||
);
|
);
|
||||||
return metaParser.nullifyInvalidVars(vars);
|
return metaParser.nullifyInvalidVars(vars);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
function compileUsercss(preprocessor, code, vars) {
|
function compileUsercss(preprocessor, code, vars) {
|
||||||
|
@ -55,7 +55,7 @@ function compileUsercss(preprocessor, code, vars) {
|
||||||
const va = vars[key];
|
const va = vars[key];
|
||||||
output[key] = Object.assign({}, va, {
|
output[key] = Object.assign({}, va, {
|
||||||
value: va.value === null || va.value === undefined ?
|
value: va.value === null || va.value === undefined ?
|
||||||
getVarValue(va, 'default') : getVarValue(va, 'value')
|
getVarValue(va, 'default') : getVarValue(va, 'value'),
|
||||||
});
|
});
|
||||||
return output;
|
return output;
|
||||||
}, {});
|
}, {});
|
||||||
|
@ -86,7 +86,7 @@ function getUsercssCompiler(preprocessor) {
|
||||||
section.code = varDef + section.code;
|
section.code = varDef + section.code;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
stylus: {
|
stylus: {
|
||||||
preprocess(source, vars) {
|
preprocess(source, vars) {
|
||||||
|
@ -96,7 +96,7 @@ function getUsercssCompiler(preprocessor) {
|
||||||
new self.StylusRenderer(varDef + source)
|
new self.StylusRenderer(varDef + source)
|
||||||
.render((err, output) => err ? reject(err) : resolve(output));
|
.render((err, output) => err ? reject(err) : resolve(output));
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
less: {
|
less: {
|
||||||
preprocess(source, vars) {
|
preprocess(source, vars) {
|
||||||
|
@ -110,7 +110,7 @@ function getUsercssCompiler(preprocessor) {
|
||||||
const varDefs = Object.keys(vars).map(key => `@${key}:${vars[key].value};\n`).join('');
|
const varDefs = Object.keys(vars).map(key => `@${key}:${vars[key].value};\n`).join('');
|
||||||
return self.less.render(varDefs + source)
|
return self.less.render(varDefs + source)
|
||||||
.then(({css}) => css);
|
.then(({css}) => css);
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
uso: {
|
uso: {
|
||||||
preprocess(source, vars) {
|
preprocess(source, vars) {
|
||||||
|
@ -162,8 +162,8 @@ function getUsercssCompiler(preprocessor) {
|
||||||
return pool.get(name);
|
return pool.get(name);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (preprocessor) {
|
if (preprocessor) {
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
// eslint-disable-next-line no-var
|
// eslint-disable-next-line no-var
|
||||||
var backgroundWorker = workerUtil.createWorker({
|
var backgroundWorker = workerUtil.createWorker({
|
||||||
url: '/background/background-worker.js'
|
url: '/background/background-worker.js',
|
||||||
});
|
});
|
||||||
|
|
||||||
// eslint-disable-next-line no-var
|
// eslint-disable-next-line no-var
|
||||||
|
@ -99,7 +99,7 @@ window.API_METHODS = Object.assign(window.API_METHODS || {}, {
|
||||||
getSyncStatus: sync.getStatus,
|
getSyncStatus: sync.getStatus,
|
||||||
syncLogin: sync.login,
|
syncLogin: sync.login,
|
||||||
|
|
||||||
openManage
|
openManage,
|
||||||
});
|
});
|
||||||
|
|
||||||
// *************************************************************************
|
// *************************************************************************
|
||||||
|
@ -119,7 +119,7 @@ if (FIREFOX) {
|
||||||
navigatorUtil.onDOMContentLoaded(webNavIframeHelperFF, {
|
navigatorUtil.onDOMContentLoaded(webNavIframeHelperFF, {
|
||||||
url: [
|
url: [
|
||||||
{urlEquals: 'about:blank'},
|
{urlEquals: 'about:blank'},
|
||||||
]
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,24 +135,13 @@ if (chrome.commands) {
|
||||||
|
|
||||||
// *************************************************************************
|
// *************************************************************************
|
||||||
chrome.runtime.onInstalled.addListener(({reason, previousVersion}) => {
|
chrome.runtime.onInstalled.addListener(({reason, previousVersion}) => {
|
||||||
// save install type: "admin", "development", "normal", "sideload" or "other"
|
|
||||||
// "normal" = addon installed from webstore
|
|
||||||
chrome.management.getSelf(info => {
|
|
||||||
localStorage.installType = info.installType;
|
|
||||||
if (reason === 'install' && info.installType === 'development' && chrome.contextMenus) {
|
|
||||||
createContextMenus(['reload']);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (reason !== 'update') return;
|
if (reason !== 'update') return;
|
||||||
// translations may change
|
|
||||||
localStorage.L10N = JSON.stringify({
|
|
||||||
browserUIlanguage: chrome.i18n.getUILanguage(),
|
|
||||||
});
|
|
||||||
// themes may change
|
|
||||||
delete localStorage.codeMirrorThemes;
|
|
||||||
// inline search cache for USO is not needed anymore, TODO: remove this by the middle of 2021
|
|
||||||
if (semverCompare(previousVersion, '1.5.13') <= 0) {
|
if (semverCompare(previousVersion, '1.5.13') <= 0) {
|
||||||
|
// Removing unused stuff
|
||||||
|
// TODO: delete this entire block by the middle of 2021
|
||||||
|
try {
|
||||||
|
localStorage.clear();
|
||||||
|
} catch (e) {}
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
const del = Object.keys(await chromeLocal.get())
|
const del = Object.keys(await chromeLocal.get())
|
||||||
.filter(key => key.startsWith('usoSearchCache'));
|
.filter(key => key.startsWith('usoSearchCache'));
|
||||||
|
@ -181,7 +170,7 @@ contextMenus = {
|
||||||
click: browserCommands.openOptions,
|
click: browserCommands.openOptions,
|
||||||
},
|
},
|
||||||
'reload': {
|
'reload': {
|
||||||
presentIf: () => localStorage.installType === 'development',
|
presentIf: async () => (await browser.management.getSelf()).installType === 'development',
|
||||||
title: 'reload',
|
title: 'reload',
|
||||||
click: browserCommands.reload,
|
click: browserCommands.reload,
|
||||||
},
|
},
|
||||||
|
@ -195,13 +184,13 @@ contextMenus = {
|
||||||
msg.sendTab(tab.id, {method: 'editDeleteText'}, undefined, 'extension')
|
msg.sendTab(tab.id, {method: 'editDeleteText'}, undefined, 'extension')
|
||||||
.catch(msg.ignoreError);
|
.catch(msg.ignoreError);
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function createContextMenus(ids) {
|
async function createContextMenus(ids) {
|
||||||
for (const id of ids) {
|
for (const id of ids) {
|
||||||
let item = contextMenus[id];
|
let item = contextMenus[id];
|
||||||
if (item.presentIf && !item.presentIf()) {
|
if (item.presentIf && !await item.presentIf()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
item = Object.assign({id}, item);
|
item = Object.assign({id}, item);
|
||||||
|
@ -320,33 +309,29 @@ function openEditor(params) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function openManage({options = false, search} = {}) {
|
async function openManage({options = false, search, searchMode} = {}) {
|
||||||
let url = chrome.runtime.getURL('manage.html');
|
let url = chrome.runtime.getURL('manage.html');
|
||||||
if (search) {
|
if (search) {
|
||||||
url += `?search=${encodeURIComponent(search)}`;
|
url += `?search=${encodeURIComponent(search)}&searchMode=${searchMode}`;
|
||||||
}
|
}
|
||||||
if (options) {
|
if (options) {
|
||||||
url += '#stylus-options';
|
url += '#stylus-options';
|
||||||
}
|
}
|
||||||
return findExistingTab({
|
let tab = await findExistingTab({
|
||||||
url,
|
url,
|
||||||
currentWindow: null,
|
currentWindow: null,
|
||||||
ignoreHash: true,
|
ignoreHash: true,
|
||||||
ignoreSearch: true
|
ignoreSearch: true,
|
||||||
})
|
});
|
||||||
.then(tab => {
|
if (tab) {
|
||||||
if (tab) {
|
await activateTab(tab);
|
||||||
return Promise.all([
|
if (url !== (tab.pendingUrl || tab.url)) {
|
||||||
activateTab(tab),
|
await msg.sendTab(tab.id, {method: 'pushState', url}).catch(console.error);
|
||||||
(tab.pendingUrl || tab.url) !== url && msg.sendTab(tab.id, {method: 'pushState', url})
|
}
|
||||||
.catch(console.error)
|
return tab;
|
||||||
]);
|
}
|
||||||
}
|
tab = await getActiveTab();
|
||||||
return getActiveTab().then(tab => {
|
return isTabReplaceable(tab, url)
|
||||||
if (isTabReplaceable(tab, url)) {
|
? activateTab(tab, {url})
|
||||||
return activateTab(tab, {url});
|
: browser.tabs.create({url});
|
||||||
}
|
|
||||||
return browser.tabs.create({url});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,7 +29,7 @@ const contentScripts = (() => {
|
||||||
url: [
|
url: [
|
||||||
{hostEquals: 'greasyfork.org', urlMatches},
|
{hostEquals: 'greasyfork.org', urlMatches},
|
||||||
{hostEquals: 'sleazyfork.org', urlMatches},
|
{hostEquals: 'sleazyfork.org', urlMatches},
|
||||||
]
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
return {injectToTab, injectToAllTabs};
|
return {injectToTab, injectToAllTabs};
|
||||||
|
@ -57,7 +57,7 @@ const contentScripts = (() => {
|
||||||
const options = {
|
const options = {
|
||||||
runAt: script.run_at,
|
runAt: script.run_at,
|
||||||
allFrames: script.all_frames,
|
allFrames: script.all_frames,
|
||||||
matchAboutBlank: script.match_about_blank
|
matchAboutBlank: script.match_about_blank,
|
||||||
};
|
};
|
||||||
if (frameId !== null) {
|
if (frameId !== null) {
|
||||||
options.allFrames = false;
|
options.allFrames = false;
|
||||||
|
@ -80,7 +80,7 @@ const contentScripts = (() => {
|
||||||
} else {
|
} else {
|
||||||
injectToTab({
|
injectToTab({
|
||||||
url: tab.pendingUrl || tab.url,
|
url: tab.pendingUrl || tab.url,
|
||||||
tabId: tab.id
|
tabId: tab.id,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,7 +32,7 @@ function createChromeStorageDB() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return output;
|
return output;
|
||||||
})
|
}),
|
||||||
};
|
};
|
||||||
|
|
||||||
return {exec};
|
return {exec};
|
||||||
|
|
|
@ -24,14 +24,11 @@ const db = (() => {
|
||||||
async function tryUsingIndexedDB() {
|
async function tryUsingIndexedDB() {
|
||||||
// we use chrome.storage.local fallback if IndexedDB doesn't save data,
|
// we use chrome.storage.local fallback if IndexedDB doesn't save data,
|
||||||
// which, once detected on the first run, is remembered in chrome.storage.local
|
// which, once detected on the first run, is remembered in chrome.storage.local
|
||||||
// for reliablility and in localStorage for fast synchronous access
|
// note that accessing indexedDB may throw, https://github.com/openstyles/stylus/issues/615
|
||||||
// (FF may block localStorage depending on its privacy options)
|
|
||||||
// note that it may throw when accessing the variable
|
|
||||||
// https://github.com/openstyles/stylus/issues/615
|
|
||||||
if (typeof indexedDB === 'undefined') {
|
if (typeof indexedDB === 'undefined') {
|
||||||
throw new Error('indexedDB is undefined');
|
throw new Error('indexedDB is undefined');
|
||||||
}
|
}
|
||||||
switch (await getFallback()) {
|
switch (await chromeLocal.getValue(FALLBACK)) {
|
||||||
case true: throw null;
|
case true: throw null;
|
||||||
case false: break;
|
case false: break;
|
||||||
default: await testDB();
|
default: await testDB();
|
||||||
|
@ -39,12 +36,6 @@ const db = (() => {
|
||||||
return useIndexedDB();
|
return useIndexedDB();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getFallback() {
|
|
||||||
return localStorage[FALLBACK] === 'true' ? true :
|
|
||||||
localStorage[FALLBACK] === 'false' ? false :
|
|
||||||
chromeLocal.getValue(FALLBACK);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function testDB() {
|
async function testDB() {
|
||||||
let e = await dbExecIndexedDB('getAllKeys', IDBKeyRange.lowerBound(1), 1);
|
let e = await dbExecIndexedDB('getAllKeys', IDBKeyRange.lowerBound(1), 1);
|
||||||
// throws if result is null
|
// throws if result is null
|
||||||
|
@ -62,13 +53,11 @@ const db = (() => {
|
||||||
chromeLocal.setValue(FALLBACK + 'Reason', workerUtil.cloneError(err));
|
chromeLocal.setValue(FALLBACK + 'Reason', workerUtil.cloneError(err));
|
||||||
console.warn('Failed to access indexedDB. Switched to storage API.', err);
|
console.warn('Failed to access indexedDB. Switched to storage API.', err);
|
||||||
}
|
}
|
||||||
localStorage[FALLBACK] = 'true';
|
|
||||||
return createChromeStorageDB().exec;
|
return createChromeStorageDB().exec;
|
||||||
}
|
}
|
||||||
|
|
||||||
function useIndexedDB() {
|
function useIndexedDB() {
|
||||||
chromeLocal.setValue(FALLBACK, false);
|
chromeLocal.setValue(FALLBACK, false);
|
||||||
localStorage[FALLBACK] = 'false';
|
|
||||||
return dbExecIndexedDB;
|
return dbExecIndexedDB;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@ const iconManager = (() => {
|
||||||
], () => debounce(refreshIconBadgeColor));
|
], () => debounce(refreshIconBadgeColor));
|
||||||
|
|
||||||
prefs.subscribe([
|
prefs.subscribe([
|
||||||
'show-badge'
|
'show-badge',
|
||||||
], () => debounce(refreshAllIconsBadgeText));
|
], () => debounce(refreshAllIconsBadgeText));
|
||||||
|
|
||||||
prefs.subscribe([
|
prefs.subscribe([
|
||||||
|
@ -79,7 +79,7 @@ const iconManager = (() => {
|
||||||
tabManager.set(tabId, 'icon', newIcon);
|
tabManager.set(tabId, 'icon', newIcon);
|
||||||
iconUtil.setIcon({
|
iconUtil.setIcon({
|
||||||
path: getIconPath(newIcon),
|
path: getIconPath(newIcon),
|
||||||
tabId
|
tabId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,14 +103,14 @@ const iconManager = (() => {
|
||||||
|
|
||||||
function refreshGlobalIcon() {
|
function refreshGlobalIcon() {
|
||||||
iconUtil.setIcon({
|
iconUtil.setIcon({
|
||||||
path: getIconPath(getIconName())
|
path: getIconPath(getIconName()),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function refreshIconBadgeColor() {
|
function refreshIconBadgeColor() {
|
||||||
const color = prefs.get(prefs.get('disableAll') ? 'badgeDisabled' : 'badgeNormal');
|
const color = prefs.get(prefs.get('disableAll') ? 'badgeDisabled' : 'badgeNormal');
|
||||||
iconUtil.setBadgeBackgroundColor({
|
iconUtil.setBadgeBackgroundColor({
|
||||||
color
|
color,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ const iconUtil = (() => {
|
||||||
Cache imageData for paths
|
Cache imageData for paths
|
||||||
*/
|
*/
|
||||||
setIcon,
|
setIcon,
|
||||||
setBadgeText
|
setBadgeText,
|
||||||
});
|
});
|
||||||
|
|
||||||
function loadImage(url) {
|
function loadImage(url) {
|
||||||
|
@ -85,7 +85,7 @@ const iconUtil = (() => {
|
||||||
return target[prop];
|
return target[prop];
|
||||||
}
|
}
|
||||||
return chrome.browserAction[prop].bind(chrome.browserAction);
|
return chrome.browserAction[prop].bind(chrome.browserAction);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
const navigatorUtil = (() => {
|
const navigatorUtil = (() => {
|
||||||
const handler = {
|
const handler = {
|
||||||
urlChange: null
|
urlChange: null,
|
||||||
};
|
};
|
||||||
return extendNative({onUrlChange});
|
return extendNative({onUrlChange});
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ const navigatorUtil = (() => {
|
||||||
return target[prop];
|
return target[prop];
|
||||||
}
|
}
|
||||||
return chrome.webNavigation[prop].addListener.bind(chrome.webNavigation[prop]);
|
return chrome.webNavigation[prop].addListener.bind(chrome.webNavigation[prop]);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -31,11 +31,11 @@
|
||||||
return fetch(api, {
|
return fetch(api, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: new Headers({
|
headers: new Headers({
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json',
|
||||||
}),
|
}),
|
||||||
body: query({
|
body: query({
|
||||||
id
|
id,
|
||||||
})
|
}),
|
||||||
})
|
})
|
||||||
.then(res => res.json());
|
.then(res => res.json());
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,90 +1,97 @@
|
||||||
/* global API_METHODS styleManager tryRegExp debounce */
|
/* global
|
||||||
|
API_METHODS
|
||||||
|
debounce
|
||||||
|
stringAsRegExp
|
||||||
|
styleManager
|
||||||
|
tryRegExp
|
||||||
|
usercss
|
||||||
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
(() => {
|
(() => {
|
||||||
// toLocaleLowerCase cache, autocleared after 1 minute
|
// toLocaleLowerCase cache, autocleared after 1 minute
|
||||||
const cache = new Map();
|
const cache = new Map();
|
||||||
// top-level style properties to be searched
|
const METAKEYS = ['customName', 'name', 'url', 'installationUrl', 'updateUrl'];
|
||||||
const PARTS = {
|
|
||||||
name: searchText,
|
const extractMeta = style =>
|
||||||
url: searchText,
|
style.usercssData
|
||||||
sourceCode: searchText,
|
? (style.sourceCode.match(usercss.RX_META) || [''])[0]
|
||||||
sections: searchSections,
|
: null;
|
||||||
};
|
|
||||||
|
const stripMeta = style =>
|
||||||
|
style.usercssData
|
||||||
|
? style.sourceCode.replace(usercss.RX_META, '')
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const MODES = Object.assign(Object.create(null), {
|
||||||
|
code: (style, test) =>
|
||||||
|
style.usercssData
|
||||||
|
? test(stripMeta(style))
|
||||||
|
: searchSections(style, test, 'code'),
|
||||||
|
|
||||||
|
meta: (style, test, part) =>
|
||||||
|
METAKEYS.some(key => test(style[key])) ||
|
||||||
|
test(part === 'all' ? style.sourceCode : extractMeta(style)) ||
|
||||||
|
searchSections(style, test, 'funcs'),
|
||||||
|
|
||||||
|
name: (style, test) =>
|
||||||
|
test(style.customName) ||
|
||||||
|
test(style.name),
|
||||||
|
|
||||||
|
all: (style, test) =>
|
||||||
|
MODES.meta(style, test, 'all') ||
|
||||||
|
!style.usercssData && MODES.code(style, test),
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param params
|
* @param params
|
||||||
* @param {string} params.query - 1. url:someurl 2. text (may contain quoted parts like "qUot Ed")
|
* @param {string} params.query - 1. url:someurl 2. text (may contain quoted parts like "qUot Ed")
|
||||||
|
* @param {'name'|'meta'|'code'|'all'|'url'} [params.mode=all]
|
||||||
* @param {number[]} [params.ids] - if not specified, all styles are searched
|
* @param {number[]} [params.ids] - if not specified, all styles are searched
|
||||||
* @returns {number[]} - array of matched styles ids
|
* @returns {number[]} - array of matched styles ids
|
||||||
*/
|
*/
|
||||||
API_METHODS.searchDB = ({query, ids}) => {
|
API_METHODS.searchDB = async ({query, mode = 'all', ids}) => {
|
||||||
let rx, words, icase, matchUrl;
|
let res = [];
|
||||||
query = query.trim();
|
if (mode === 'url' && query) {
|
||||||
|
res = (await styleManager.getStylesByUrl(query)).map(r => r.data.id);
|
||||||
if (/^url:/i.test(query)) {
|
} else if (mode in MODES) {
|
||||||
matchUrl = query.slice(query.indexOf(':') + 1).trim();
|
const modeHandler = MODES[mode];
|
||||||
if (matchUrl) {
|
const m = /^\/(.+?)\/([gimsuy]*)$/.exec(query);
|
||||||
return styleManager.getStylesByUrl(matchUrl)
|
const rx = m && tryRegExp(m[1], m[2]);
|
||||||
.then(results => results.map(r => r.data.id));
|
const test = rx ? rx.test.bind(rx) : makeTester(query);
|
||||||
}
|
res = (await styleManager.getAllStyles())
|
||||||
}
|
.filter(style =>
|
||||||
if (query.startsWith('/') && /^\/(.+?)\/([gimsuy]*)$/.test(query)) {
|
(!ids || ids.includes(style.id)) &&
|
||||||
rx = tryRegExp(RegExp.$1, RegExp.$2);
|
(!query || modeHandler(style, test)))
|
||||||
}
|
.map(style => style.id);
|
||||||
if (!rx) {
|
|
||||||
words = query
|
|
||||||
.split(/(".*?")|\s+/)
|
|
||||||
.filter(Boolean)
|
|
||||||
.map(w => w.startsWith('"') && w.endsWith('"')
|
|
||||||
? w.slice(1, -1)
|
|
||||||
: w)
|
|
||||||
.filter(w => w.length > 1);
|
|
||||||
words = words.length ? words : [query];
|
|
||||||
icase = words.some(w => w === lower(w));
|
|
||||||
}
|
|
||||||
|
|
||||||
return styleManager.getAllStyles().then(styles => {
|
|
||||||
if (ids) {
|
|
||||||
const idSet = new Set(ids);
|
|
||||||
styles = styles.filter(s => idSet.has(s.id));
|
|
||||||
}
|
|
||||||
const results = [];
|
|
||||||
for (const style of styles) {
|
|
||||||
const id = style.id;
|
|
||||||
if (!query || words && !words.length) {
|
|
||||||
results.push(id);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (const part in PARTS) {
|
|
||||||
const text = part === 'name' ? style.customName || style.name : style[part];
|
|
||||||
if (text && PARTS[part](text, rx, words, icase)) {
|
|
||||||
results.push(id);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (cache.size) debounce(clearCache, 60e3);
|
if (cache.size) debounce(clearCache, 60e3);
|
||||||
return results;
|
}
|
||||||
});
|
return res;
|
||||||
};
|
};
|
||||||
|
|
||||||
function searchText(text, rx, words, icase) {
|
function makeTester(query) {
|
||||||
if (rx) return rx.test(text);
|
const flags = `u${lower(query) === query ? 'i' : ''}`;
|
||||||
for (let pass = 1; pass <= (icase ? 2 : 1); pass++) {
|
const words = query
|
||||||
if (words.every(w => text.includes(w))) return true;
|
.split(/(".*?")|\s+/)
|
||||||
text = lower(text);
|
.filter(Boolean)
|
||||||
}
|
.map(w => w.startsWith('"') && w.endsWith('"')
|
||||||
|
? w.slice(1, -1)
|
||||||
|
: w)
|
||||||
|
.filter(w => w.length > 1);
|
||||||
|
const rxs = (words.length ? words : [query])
|
||||||
|
.map(w => stringAsRegExp(w, flags));
|
||||||
|
return text => rxs.every(rx => rx.test(text));
|
||||||
}
|
}
|
||||||
|
|
||||||
function searchSections(sections, rx, words, icase) {
|
function searchSections({sections}, test, part) {
|
||||||
|
const inCode = part === 'code' || part === 'all';
|
||||||
|
const inFuncs = part === 'funcs' || part === 'all';
|
||||||
for (const section of sections) {
|
for (const section of sections) {
|
||||||
for (const prop in section) {
|
for (const prop in section) {
|
||||||
const value = section[prop];
|
const value = section[prop];
|
||||||
if (typeof value === 'string') {
|
if (inCode && prop === 'code' && test(value) ||
|
||||||
if (searchText(value, rx, words, icase)) return true;
|
inFuncs && Array.isArray(value) && value.some(str => test(str))) {
|
||||||
} else if (Array.isArray(value)) {
|
return true;
|
||||||
if (value.some(str => searchText(str, rx, words, icase))) return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -92,9 +99,7 @@
|
||||||
|
|
||||||
function lower(text) {
|
function lower(text) {
|
||||||
let result = cache.get(text);
|
let result = cache.get(text);
|
||||||
if (result) return result;
|
if (!result) cache.set(text, result = text.toLocaleLowerCase());
|
||||||
result = text.toLocaleLowerCase();
|
|
||||||
cache.set(text, result);
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,8 @@ script would try to fetch the new code.
|
||||||
The live preview feature relies on `runtime.connect` and `port.onDisconnect`
|
The live preview feature relies on `runtime.connect` and `port.onDisconnect`
|
||||||
to cleanup the temporary code. See /edit/live-preview.js.
|
to cleanup the temporary code. See /edit/live-preview.js.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/** @type {styleManager} */
|
||||||
const styleManager = (() => {
|
const styleManager = (() => {
|
||||||
const preparing = prepare();
|
const preparing = prepare();
|
||||||
|
|
||||||
|
@ -38,7 +40,7 @@ const styleManager = (() => {
|
||||||
style.appliesTo.delete(url);
|
style.appliesTo.delete(url);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const BAD_MATCHER = {test: () => false};
|
const BAD_MATCHER = {test: () => false};
|
||||||
|
@ -58,16 +60,16 @@ const styleManager = (() => {
|
||||||
protocol: '',
|
protocol: '',
|
||||||
search: '',
|
search: '',
|
||||||
searchParams: new URLSearchParams(),
|
searchParams: new URLSearchParams(),
|
||||||
username: ''
|
username: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
const DELETE_IF_NULL = ['id', 'customName'];
|
const DELETE_IF_NULL = ['id', 'customName'];
|
||||||
|
|
||||||
handleLivePreviewConnections();
|
handleLivePreviewConnections();
|
||||||
|
|
||||||
return Object.assign({
|
return Object.assign(/** @namespace styleManager */{
|
||||||
compareRevision
|
compareRevision,
|
||||||
}, ensurePrepared({
|
}, ensurePrepared(/** @namespace styleManager */{
|
||||||
get,
|
get,
|
||||||
getByUUID,
|
getByUUID,
|
||||||
getSectionsByUrl,
|
getSectionsByUrl,
|
||||||
|
@ -86,7 +88,7 @@ const styleManager = (() => {
|
||||||
addExclusion,
|
addExclusion,
|
||||||
removeExclusion,
|
removeExclusion,
|
||||||
addInclusion,
|
addInclusion,
|
||||||
removeInclusion
|
removeInclusion,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
function handleLivePreviewConnections() {
|
function handleLivePreviewConnections() {
|
||||||
|
@ -135,9 +137,8 @@ const styleManager = (() => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAllStyles(noCode = false) {
|
function getAllStyles() {
|
||||||
const datas = [...styles.values()].map(s => s.data);
|
return [...styles.values()].map(s => s.data);
|
||||||
return noCode ? datas.map(getStyleWithNoCode) : datas;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function compareRevision(rev1, rev2) {
|
function compareRevision(rev1, rev2) {
|
||||||
|
@ -316,7 +317,7 @@ const styleManager = (() => {
|
||||||
uuidIndex.delete(style.data._id);
|
uuidIndex.delete(style.data._id);
|
||||||
return msg.broadcast({
|
return msg.broadcast({
|
||||||
method: 'styleDeleted',
|
method: 'styleDeleted',
|
||||||
style: {id}
|
style: {id},
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.then(() => id);
|
.then(() => id);
|
||||||
|
@ -347,7 +348,7 @@ const styleManager = (() => {
|
||||||
md5Url: null,
|
md5Url: null,
|
||||||
url: null,
|
url: null,
|
||||||
originalMd5: null,
|
originalMd5: null,
|
||||||
installDate: Date.now()
|
installDate: Date.now(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -368,7 +369,7 @@ const styleManager = (() => {
|
||||||
updated.add(url);
|
updated.add(url);
|
||||||
cache.sections[data.id] = {
|
cache.sections[data.id] = {
|
||||||
id: data.id,
|
id: data.id,
|
||||||
code
|
code,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -378,10 +379,10 @@ const styleManager = (() => {
|
||||||
style: {
|
style: {
|
||||||
id: data.id,
|
id: data.id,
|
||||||
md5Url: data.md5Url,
|
md5Url: data.md5Url,
|
||||||
enabled: data.enabled
|
enabled: data.enabled,
|
||||||
},
|
},
|
||||||
reason,
|
reason,
|
||||||
codeIsUpdated
|
codeIsUpdated,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -424,7 +425,7 @@ const styleManager = (() => {
|
||||||
if (!style) {
|
if (!style) {
|
||||||
styles.set(data.id, {
|
styles.set(data.id, {
|
||||||
appliesTo: new Set(),
|
appliesTo: new Set(),
|
||||||
data
|
data,
|
||||||
});
|
});
|
||||||
method = 'styleAdded';
|
method = 'styleAdded';
|
||||||
} else {
|
} else {
|
||||||
|
@ -469,11 +470,7 @@ const styleManager = (() => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (sectionMatched) {
|
if (sectionMatched) {
|
||||||
result.push({
|
result.push({data, excluded, sloppy});
|
||||||
data: getStyleWithNoCode(data),
|
|
||||||
excluded,
|
|
||||||
sloppy
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
@ -484,7 +481,7 @@ const styleManager = (() => {
|
||||||
if (!cache) {
|
if (!cache) {
|
||||||
cache = {
|
cache = {
|
||||||
sections: {},
|
sections: {},
|
||||||
maybeMatch: new Set()
|
maybeMatch: new Set(),
|
||||||
};
|
};
|
||||||
buildCache(styles.values());
|
buildCache(styles.values());
|
||||||
cachedStyleForUrl.set(url, cache);
|
cachedStyleForUrl.set(url, cache);
|
||||||
|
@ -510,7 +507,7 @@ const styleManager = (() => {
|
||||||
if (code) {
|
if (code) {
|
||||||
cache.sections[data.id] = {
|
cache.sections[data.id] = {
|
||||||
id: data.id,
|
id: data.id,
|
||||||
code
|
code,
|
||||||
};
|
};
|
||||||
appliesTo.add(url);
|
appliesTo.add(url);
|
||||||
}
|
}
|
||||||
|
@ -535,7 +532,7 @@ const styleManager = (() => {
|
||||||
const ADD_MISSING_PROPS = {
|
const ADD_MISSING_PROPS = {
|
||||||
name: style => `ID: ${style.id}`,
|
name: style => `ID: ${style.id}`,
|
||||||
_id: () => uuidv4(),
|
_id: () => uuidv4(),
|
||||||
_rev: () => Date.now()
|
_rev: () => Date.now(),
|
||||||
};
|
};
|
||||||
|
|
||||||
return db.exec('getAll')
|
return db.exec('getAll')
|
||||||
|
@ -559,7 +556,7 @@ const styleManager = (() => {
|
||||||
fixUsoMd5Issue(style);
|
fixUsoMd5Issue(style);
|
||||||
styles.set(style.id, {
|
styles.set(style.id, {
|
||||||
appliesTo: new Set(),
|
appliesTo: new Set(),
|
||||||
data: style
|
data: style,
|
||||||
});
|
});
|
||||||
uuidIndex.set(style._id, style.id);
|
uuidIndex.set(style._id, style.id);
|
||||||
}
|
}
|
||||||
|
@ -705,7 +702,7 @@ const styleManager = (() => {
|
||||||
domain = u.hostname;
|
domain = u.hostname;
|
||||||
}
|
}
|
||||||
return domain;
|
return domain;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
140
background/style-via-webrequest.js
Normal file
140
background/style-via-webrequest.js
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
/* global API CHROME prefs */
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-unused-expressions
|
||||||
|
CHROME && (async () => {
|
||||||
|
const idCSP = 'patchCsp';
|
||||||
|
const idOFF = 'disableAll';
|
||||||
|
const idXHR = 'styleViaXhr';
|
||||||
|
const rxHOST = /^('none'|(https?:\/\/)?[^']+?[^:'])$/; // strips CSP sources covered by *
|
||||||
|
const blobUrlPrefix = 'blob:' + chrome.runtime.getURL('/');
|
||||||
|
const stylesToPass = {};
|
||||||
|
const enabled = {};
|
||||||
|
|
||||||
|
await prefs.initializing;
|
||||||
|
prefs.subscribe([idXHR, idOFF, idCSP], toggle, {now: true});
|
||||||
|
|
||||||
|
function toggle() {
|
||||||
|
const csp = prefs.get(idCSP) && !prefs.get(idOFF);
|
||||||
|
const xhr = prefs.get(idXHR) && !prefs.get(idOFF) && Boolean(chrome.declarativeContent);
|
||||||
|
if (xhr === enabled.xhr && csp === enabled.csp) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Need to unregister first so that the optional EXTRA_HEADERS is properly registered
|
||||||
|
chrome.webRequest.onBeforeRequest.removeListener(prepareStyles);
|
||||||
|
chrome.webRequest.onHeadersReceived.removeListener(modifyHeaders);
|
||||||
|
if (xhr || csp) {
|
||||||
|
const reqFilter = {
|
||||||
|
urls: ['<all_urls>'],
|
||||||
|
types: ['main_frame', 'sub_frame'],
|
||||||
|
};
|
||||||
|
chrome.webRequest.onBeforeRequest.addListener(prepareStyles, reqFilter);
|
||||||
|
chrome.webRequest.onHeadersReceived.addListener(modifyHeaders, reqFilter, [
|
||||||
|
'blocking',
|
||||||
|
'responseHeaders',
|
||||||
|
xhr && chrome.webRequest.OnHeadersReceivedOptions.EXTRA_HEADERS,
|
||||||
|
].filter(Boolean));
|
||||||
|
}
|
||||||
|
if (enabled.xhr !== xhr) {
|
||||||
|
enabled.xhr = xhr;
|
||||||
|
toggleEarlyInjection();
|
||||||
|
}
|
||||||
|
enabled.csp = csp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Runs content scripts earlier than document_start */
|
||||||
|
function toggleEarlyInjection() {
|
||||||
|
const api = chrome.declarativeContent;
|
||||||
|
if (!api) return;
|
||||||
|
api.onPageChanged.removeRules([idXHR], async () => {
|
||||||
|
if (enabled.xhr) {
|
||||||
|
api.onPageChanged.addRules([{
|
||||||
|
id: idXHR,
|
||||||
|
conditions: [
|
||||||
|
new api.PageStateMatcher({
|
||||||
|
pageUrl: {urlContains: '://'},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
actions: [
|
||||||
|
new api.RequestContentScript({
|
||||||
|
js: chrome.runtime.getManifest().content_scripts[0].js,
|
||||||
|
allFrames: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {chrome.webRequest.WebRequestBodyDetails} req */
|
||||||
|
function prepareStyles(req) {
|
||||||
|
API.getSectionsByUrl(req.url).then(sections => {
|
||||||
|
if (Object.keys(sections).length) {
|
||||||
|
stylesToPass[req.requestId] = !enabled.xhr ? true :
|
||||||
|
URL.createObjectURL(new Blob([JSON.stringify(sections)])).slice(blobUrlPrefix.length);
|
||||||
|
setTimeout(cleanUp, 600e3, req.requestId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {chrome.webRequest.WebResponseHeadersDetails} req */
|
||||||
|
function modifyHeaders(req) {
|
||||||
|
const {responseHeaders} = req;
|
||||||
|
const id = stylesToPass[req.requestId];
|
||||||
|
if (!id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (enabled.xhr) {
|
||||||
|
responseHeaders.push({
|
||||||
|
name: 'Set-Cookie',
|
||||||
|
value: `${chrome.runtime.id}=${id}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const csp = enabled.csp &&
|
||||||
|
responseHeaders.find(h => h.name.toLowerCase() === 'content-security-policy');
|
||||||
|
if (csp) {
|
||||||
|
patchCsp(csp);
|
||||||
|
}
|
||||||
|
if (enabled.xhr || csp) {
|
||||||
|
return {responseHeaders};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @param {chrome.webRequest.HttpHeader} csp */
|
||||||
|
function patchCsp(csp) {
|
||||||
|
const src = {};
|
||||||
|
for (let p of csp.value.split(';')) {
|
||||||
|
p = p.trim().split(/\s+/);
|
||||||
|
src[p[0]] = p.slice(1);
|
||||||
|
}
|
||||||
|
// Allow style assets
|
||||||
|
patchCspSrc(src, 'img-src', 'data:', '*');
|
||||||
|
patchCspSrc(src, 'font-src', 'data:', '*');
|
||||||
|
// Allow our DOM styles
|
||||||
|
patchCspSrc(src, 'style-src', '\'unsafe-inline\'');
|
||||||
|
// Allow our XHR cookies in CSP sandbox (known case: raw github urls)
|
||||||
|
if (src.sandbox && !src.sandbox.includes('allow-same-origin')) {
|
||||||
|
src.sandbox.push('allow-same-origin');
|
||||||
|
}
|
||||||
|
csp.value = Object.entries(src).map(([k, v]) =>
|
||||||
|
`${k}${v.length ? ' ' : ''}${v.join(' ')}`).join('; ');
|
||||||
|
}
|
||||||
|
|
||||||
|
function patchCspSrc(src, name, ...values) {
|
||||||
|
let def = src['default-src'];
|
||||||
|
let list = src[name];
|
||||||
|
if (def || list) {
|
||||||
|
if (!def) def = [];
|
||||||
|
if (!list) list = [...def];
|
||||||
|
if (values.includes('*')) list = src[name] = list.filter(v => !rxHOST.test(v));
|
||||||
|
list.push(...values.filter(v => !list.includes(v) && !def.includes(v)));
|
||||||
|
if (!list.length) delete src[name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanUp(key) {
|
||||||
|
const blobId = stylesToPass[key];
|
||||||
|
delete stylesToPass[key];
|
||||||
|
if (blobId) URL.revokeObjectURL(blobUrlPrefix + blobId);
|
||||||
|
}
|
||||||
|
})();
|
|
@ -1,85 +0,0 @@
|
||||||
/* global API CHROME prefs */
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// eslint-disable-next-line no-unused-expressions
|
|
||||||
CHROME && (async () => {
|
|
||||||
const prefId = 'styleViaXhr';
|
|
||||||
const blobUrlPrefix = 'blob:' + chrome.runtime.getURL('/');
|
|
||||||
const stylesToPass = {};
|
|
||||||
|
|
||||||
await prefs.initializing;
|
|
||||||
toggle(prefId, prefs.get(prefId));
|
|
||||||
prefs.subscribe([prefId], toggle);
|
|
||||||
|
|
||||||
function toggle(key, value) {
|
|
||||||
if (!chrome.declarativeContent) { // not yet granted in options page
|
|
||||||
value = false;
|
|
||||||
}
|
|
||||||
if (value) {
|
|
||||||
const reqFilter = {
|
|
||||||
urls: ['<all_urls>'],
|
|
||||||
types: ['main_frame', 'sub_frame'],
|
|
||||||
};
|
|
||||||
chrome.webRequest.onBeforeRequest.addListener(prepareStyles, reqFilter);
|
|
||||||
chrome.webRequest.onHeadersReceived.addListener(passStyles, reqFilter, [
|
|
||||||
'blocking',
|
|
||||||
'responseHeaders',
|
|
||||||
chrome.webRequest.OnHeadersReceivedOptions.EXTRA_HEADERS,
|
|
||||||
].filter(Boolean));
|
|
||||||
} else {
|
|
||||||
chrome.webRequest.onBeforeRequest.removeListener(prepareStyles);
|
|
||||||
chrome.webRequest.onHeadersReceived.removeListener(passStyles);
|
|
||||||
}
|
|
||||||
if (!chrome.declarativeContent) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
chrome.declarativeContent.onPageChanged.removeRules([prefId], async () => {
|
|
||||||
if (!value) return;
|
|
||||||
chrome.declarativeContent.onPageChanged.addRules([{
|
|
||||||
id: prefId,
|
|
||||||
conditions: [
|
|
||||||
new chrome.declarativeContent.PageStateMatcher({
|
|
||||||
pageUrl: {urlContains: ':'},
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
actions: [
|
|
||||||
new chrome.declarativeContent.RequestContentScript({
|
|
||||||
allFrames: true,
|
|
||||||
// This runs earlier than document_start
|
|
||||||
js: chrome.runtime.getManifest().content_scripts[0].js,
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
}]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {chrome.webRequest.WebRequestBodyDetails} req */
|
|
||||||
function prepareStyles(req) {
|
|
||||||
API.getSectionsByUrl(req.url).then(sections => {
|
|
||||||
const str = JSON.stringify(sections);
|
|
||||||
if (str !== '{}') {
|
|
||||||
stylesToPass[req.requestId] = URL.createObjectURL(new Blob([str])).slice(blobUrlPrefix.length);
|
|
||||||
setTimeout(cleanUp, 600e3, req.requestId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** @param {chrome.webRequest.WebResponseHeadersDetails} req */
|
|
||||||
function passStyles(req) {
|
|
||||||
const blobId = stylesToPass[req.requestId];
|
|
||||||
if (blobId) {
|
|
||||||
const {responseHeaders} = req;
|
|
||||||
responseHeaders.push({
|
|
||||||
name: 'Set-Cookie',
|
|
||||||
value: `${chrome.runtime.id}=${prefs.get('disableAll') ? 1 : 0}${blobId}`,
|
|
||||||
});
|
|
||||||
return {responseHeaders};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function cleanUp(key) {
|
|
||||||
const blobId = stylesToPass[key];
|
|
||||||
delete stylesToPass[key];
|
|
||||||
if (blobId) URL.revokeObjectURL(blobUrlPrefix + blobId);
|
|
||||||
}
|
|
||||||
})();
|
|
|
@ -13,7 +13,7 @@ const sync = (() => {
|
||||||
progress: null,
|
progress: null,
|
||||||
currentDriveName: null,
|
currentDriveName: null,
|
||||||
errorMessage: null,
|
errorMessage: null,
|
||||||
login: false
|
login: false,
|
||||||
};
|
};
|
||||||
let currentDrive;
|
let currentDrive;
|
||||||
const ctrl = dbToCloud.dbToCloud({
|
const ctrl = dbToCloud.dbToCloud({
|
||||||
|
@ -43,7 +43,7 @@ const sync = (() => {
|
||||||
setState(drive, state) {
|
setState(drive, state) {
|
||||||
const key = `sync/state/${drive.name}`;
|
const key = `sync/state/${drive.name}`;
|
||||||
return chromeLocal.setValue(key, state);
|
return chromeLocal.setValue(key, state);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const initializing = prefs.initializing.then(() => {
|
const initializing = prefs.initializing.then(() => {
|
||||||
|
@ -58,7 +58,7 @@ const sync = (() => {
|
||||||
});
|
});
|
||||||
|
|
||||||
return Object.assign({
|
return Object.assign({
|
||||||
getStatus: () => status
|
getStatus: () => status,
|
||||||
}, ensurePrepared({
|
}, ensurePrepared({
|
||||||
start,
|
start,
|
||||||
stop,
|
stop,
|
||||||
|
@ -73,7 +73,7 @@ const sync = (() => {
|
||||||
return ctrl.delete(...args);
|
return ctrl.delete(...args);
|
||||||
},
|
},
|
||||||
syncNow,
|
syncNow,
|
||||||
login
|
login,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
function ensurePrepared(obj) {
|
function ensurePrepared(obj) {
|
||||||
|
@ -99,7 +99,7 @@ const sync = (() => {
|
||||||
function schedule(delay = SYNC_DELAY) {
|
function schedule(delay = SYNC_DELAY) {
|
||||||
chrome.alarms.create('syncNow', {
|
chrome.alarms.create('syncNow', {
|
||||||
delayInMinutes: delay,
|
delayInMinutes: delay,
|
||||||
periodInMinutes: SYNC_INTERVAL
|
periodInMinutes: SYNC_INTERVAL,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -206,7 +206,7 @@ const sync = (() => {
|
||||||
function getDrive(name) {
|
function getDrive(name) {
|
||||||
if (name === 'dropbox' || name === 'google' || name === 'onedrive') {
|
if (name === 'dropbox' || name === 'google' || name === 'onedrive') {
|
||||||
return dbToCloud.drive[name]({
|
return dbToCloud.drive[name]({
|
||||||
getAccessToken: () => tokenManager.getToken(name)
|
getAccessToken: () => tokenManager.getToken(name),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
throw new Error(`unknown cloud name: ${name}`);
|
throw new Error(`unknown cloud name: ${name}`);
|
||||||
|
|
|
@ -13,9 +13,9 @@ const tokenManager = (() => {
|
||||||
fetch('https://api.dropboxapi.com/2/auth/token/revoke', {
|
fetch('https://api.dropboxapi.com/2/auth/token/revoke', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Authorization': `Bearer ${token}`
|
'Authorization': `Bearer ${token}`,
|
||||||
}
|
},
|
||||||
})
|
}),
|
||||||
},
|
},
|
||||||
google: {
|
google: {
|
||||||
flow: 'code',
|
flow: 'code',
|
||||||
|
@ -27,14 +27,14 @@ const tokenManager = (() => {
|
||||||
// tokens for multiple machines.
|
// tokens for multiple machines.
|
||||||
// https://stackoverflow.com/q/18519185
|
// https://stackoverflow.com/q/18519185
|
||||||
access_type: 'offline',
|
access_type: 'offline',
|
||||||
prompt: 'consent'
|
prompt: 'consent',
|
||||||
},
|
},
|
||||||
tokenURL: 'https://oauth2.googleapis.com/token',
|
tokenURL: 'https://oauth2.googleapis.com/token',
|
||||||
scopes: ['https://www.googleapis.com/auth/drive.appdata'],
|
scopes: ['https://www.googleapis.com/auth/drive.appdata'],
|
||||||
revoke: token => {
|
revoke: token => {
|
||||||
const params = {token};
|
const params = {token};
|
||||||
return postQuery(`https://accounts.google.com/o/oauth2/revoke?${new URLSearchParams(params)}`);
|
return postQuery(`https://accounts.google.com/o/oauth2/revoke?${new URLSearchParams(params)}`);
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
onedrive: {
|
onedrive: {
|
||||||
flow: 'code',
|
flow: 'code',
|
||||||
|
@ -45,8 +45,8 @@ const tokenManager = (() => {
|
||||||
redirect_uri: FIREFOX ?
|
redirect_uri: FIREFOX ?
|
||||||
'https://clngdbkpkpeebahjckkjfobafhncgmne.chromiumapp.org/' :
|
'https://clngdbkpkpeebahjckkjfobafhncgmne.chromiumapp.org/' :
|
||||||
'https://' + location.hostname + '.chromiumapp.org/',
|
'https://' + location.hostname + '.chromiumapp.org/',
|
||||||
scopes: ['Files.ReadWrite.AppFolder', 'offline_access']
|
scopes: ['Files.ReadWrite.AppFolder', 'offline_access'],
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const NETWORK_LATENCY = 30; // seconds
|
const NETWORK_LATENCY = 30; // seconds
|
||||||
|
|
||||||
|
@ -114,7 +114,7 @@ const tokenManager = (() => {
|
||||||
client_id: provider.clientId,
|
client_id: provider.clientId,
|
||||||
refresh_token: obj[k.REFRESH],
|
refresh_token: obj[k.REFRESH],
|
||||||
grant_type: 'refresh_token',
|
grant_type: 'refresh_token',
|
||||||
scope: provider.scopes.join(' ')
|
scope: provider.scopes.join(' '),
|
||||||
};
|
};
|
||||||
if (provider.clientSecret) {
|
if (provider.clientSecret) {
|
||||||
body.client_secret = provider.clientSecret;
|
body.client_secret = provider.clientSecret;
|
||||||
|
@ -136,7 +136,7 @@ const tokenManager = (() => {
|
||||||
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(' ');
|
||||||
|
@ -148,7 +148,7 @@ const tokenManager = (() => {
|
||||||
return webextLaunchWebAuthFlow({
|
return webextLaunchWebAuthFlow({
|
||||||
url,
|
url,
|
||||||
interactive,
|
interactive,
|
||||||
redirect_uri: query.redirect_uri
|
redirect_uri: query.redirect_uri,
|
||||||
})
|
})
|
||||||
.then(url => {
|
.then(url => {
|
||||||
const params = new URLSearchParams(
|
const params = new URLSearchParams(
|
||||||
|
@ -171,7 +171,7 @@ const tokenManager = (() => {
|
||||||
code,
|
code,
|
||||||
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,
|
||||||
};
|
};
|
||||||
if (provider.clientSecret) {
|
if (provider.clientSecret) {
|
||||||
body.client_secret = provider.clientSecret;
|
body.client_secret = provider.clientSecret;
|
||||||
|
@ -185,7 +185,7 @@ const tokenManager = (() => {
|
||||||
return chromeLocal.set({
|
return chromeLocal.set({
|
||||||
[k.TOKEN]: result.access_token,
|
[k.TOKEN]: result.access_token,
|
||||||
[k.EXPIRE]: result.expires_in ? Date.now() + (Number(result.expires_in) - NETWORK_LATENCY) * 1000 : undefined,
|
[k.EXPIRE]: result.expires_in ? Date.now() + (Number(result.expires_in) - NETWORK_LATENCY) * 1000 : undefined,
|
||||||
[k.REFRESH]: result.refresh_token
|
[k.REFRESH]: result.refresh_token,
|
||||||
})
|
})
|
||||||
.then(() => result.access_token);
|
.then(() => result.access_token);
|
||||||
}
|
}
|
||||||
|
@ -194,7 +194,7 @@ const tokenManager = (() => {
|
||||||
const options = {
|
const options = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded'
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
},
|
},
|
||||||
body: body ? new URLSearchParams(body) : null,
|
body: body ? new URLSearchParams(body) : null,
|
||||||
};
|
};
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
const ALARM_NAME = 'scheduledUpdate';
|
const ALARM_NAME = 'scheduledUpdate';
|
||||||
const MIN_INTERVAL_MS = 60e3;
|
const MIN_INTERVAL_MS = 60e3;
|
||||||
|
|
||||||
let lastUpdateTime = parseInt(localStorage.lastUpdateTime) || Date.now();
|
let lastUpdateTime;
|
||||||
let checkingAll = false;
|
let checkingAll = false;
|
||||||
let logQueue = [];
|
let logQueue = [];
|
||||||
let logLastWriteTime = 0;
|
let logLastWriteTime = 0;
|
||||||
|
@ -46,9 +46,11 @@
|
||||||
API_METHODS.updateCheck = checkStyle;
|
API_METHODS.updateCheck = checkStyle;
|
||||||
API_METHODS.getUpdaterStates = () => STATES;
|
API_METHODS.getUpdaterStates = () => STATES;
|
||||||
|
|
||||||
prefs.subscribe(['updateInterval'], schedule);
|
chromeLocal.getValue('lastUpdateTime').then(val => {
|
||||||
schedule();
|
lastUpdateTime = val || Date.now();
|
||||||
chrome.alarms.onAlarm.addListener(onAlarm);
|
prefs.subscribe('updateInterval', schedule, {now: true});
|
||||||
|
chrome.alarms.onAlarm.addListener(onAlarm);
|
||||||
|
});
|
||||||
|
|
||||||
return {checkAllStyles, checkStyle, STATES};
|
return {checkAllStyles, checkStyle, STATES};
|
||||||
|
|
||||||
|
@ -255,7 +257,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function resetInterval() {
|
function resetInterval() {
|
||||||
localStorage.lastUpdateTime = lastUpdateTime = Date.now();
|
chromeLocal.setValue('lastUpdateTime', lastUpdateTime = Date.now());
|
||||||
schedule();
|
schedule();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,7 @@ const usercssHelper = (() => {
|
||||||
find(styleId ? {id: styleId} : style) : Promise.resolve();
|
find(styleId ? {id: styleId} : style) : Promise.resolve();
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
metaOnly ? style : doBuild(style, findDup),
|
metaOnly ? style : doBuild(style, findDup),
|
||||||
findDup
|
findDup,
|
||||||
]);
|
]);
|
||||||
})
|
})
|
||||||
.then(([style, dup]) => ({style, dup}));
|
.then(([style, dup]) => ({style, dup}));
|
||||||
|
|
|
@ -1,4 +1,10 @@
|
||||||
/* global API_METHODS openURL download URLS tabManager */
|
/* global
|
||||||
|
API_METHODS
|
||||||
|
download
|
||||||
|
openURL
|
||||||
|
tabManager
|
||||||
|
URLS
|
||||||
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
(() => {
|
(() => {
|
||||||
|
@ -27,16 +33,39 @@
|
||||||
return code;
|
return code;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// `glob`: pathname match pattern for webRequest
|
||||||
|
// `rx`: pathname regex to verify the URL really looks like a raw usercss
|
||||||
|
const maybeDistro = {
|
||||||
|
// https://github.com/StylishThemes/GitHub-Dark/raw/master/github-dark.user.css
|
||||||
|
'github.com': {
|
||||||
|
glob: '/*/raw/*',
|
||||||
|
rx: /^\/[^/]+\/[^/]+\/raw\/[^/]+\/[^/]+?\.user\.(css|styl)$/,
|
||||||
|
},
|
||||||
|
// https://raw.githubusercontent.com/StylishThemes/GitHub-Dark/master/github-dark.user.css
|
||||||
|
'raw.githubusercontent.com': {
|
||||||
|
glob: '/*',
|
||||||
|
rx: /^(\/[^/]+?){4}\.user\.(css|styl)$/,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
// Faster installation on known distribution sites to avoid flicker of css text
|
// Faster installation on known distribution sites to avoid flicker of css text
|
||||||
chrome.webRequest.onBeforeSendHeaders.addListener(({tabId, url}) => {
|
chrome.webRequest.onBeforeSendHeaders.addListener(({tabId, url}) => {
|
||||||
openInstallerPage(tabId, url, {});
|
const u = new URL(url);
|
||||||
// Silently suppressing navigation like it never happened
|
const m = maybeDistro[u.hostname];
|
||||||
return {redirectUrl: 'javascript:void 0'}; // eslint-disable-line no-script-url
|
if (!m || m.rx.test(u.pathname)) {
|
||||||
|
openInstallerPage(tabId, url, {});
|
||||||
|
// Silently suppress navigation.
|
||||||
|
// Don't redirect to the install URL as it'll flash the text!
|
||||||
|
return {redirectUrl: 'javascript:void 0'}; // eslint-disable-line no-script-url
|
||||||
|
}
|
||||||
}, {
|
}, {
|
||||||
urls: [
|
urls: [
|
||||||
URLS.usoArchiveRaw + 'usercss/*.user.css',
|
URLS.usoArchiveRaw + 'usercss/*.user.css',
|
||||||
'*://greasyfork.org/scripts/*/code/*.user.css',
|
'*://greasyfork.org/scripts/*/code/*.user.css',
|
||||||
'*://sleazyfork.org/scripts/*/code/*.user.css',
|
'*://sleazyfork.org/scripts/*/code/*.user.css',
|
||||||
|
...[].concat(
|
||||||
|
...Object.entries(maybeDistro)
|
||||||
|
.map(([host, {glob}]) => makeUsercssGlobs(host, glob))),
|
||||||
],
|
],
|
||||||
types: ['main_frame'],
|
types: ['main_frame'],
|
||||||
}, ['blocking']);
|
}, ['blocking']);
|
||||||
|
@ -46,7 +75,7 @@
|
||||||
const h = responseHeaders.find(h => h.name.toLowerCase() === 'content-type');
|
const h = responseHeaders.find(h => h.name.toLowerCase() === 'content-type');
|
||||||
tabManager.set(tabId, isContentTypeText.name, h && isContentTypeText(h.value) || undefined);
|
tabManager.set(tabId, isContentTypeText.name, h && isContentTypeText(h.value) || undefined);
|
||||||
}, {
|
}, {
|
||||||
urls: '%css,%css?*,%styl,%styl?*'.replace(/%/g, '*://*/*.user.').split(','),
|
urls: makeUsercssGlobs('*', '/*'),
|
||||||
types: ['main_frame'],
|
types: ['main_frame'],
|
||||||
}, ['responseHeaders']);
|
}, ['responseHeaders']);
|
||||||
|
|
||||||
|
@ -57,7 +86,7 @@
|
||||||
!oldUrl.startsWith(URLS.installUsercss)) {
|
!oldUrl.startsWith(URLS.installUsercss)) {
|
||||||
const inTab = url.startsWith('file:') && Boolean(fileLoader);
|
const inTab = url.startsWith('file:') && Boolean(fileLoader);
|
||||||
const code = await (inTab ? fileLoader : urlLoader)(tabId, url);
|
const code = await (inTab ? fileLoader : urlLoader)(tabId, url);
|
||||||
if (/==userstyle==/i.test(code)) {
|
if (/==userstyle==/i.test(code) && !/^\s*</.test(code)) {
|
||||||
openInstallerPage(tabId, url, {code, inTab});
|
openInstallerPage(tabId, url, {code, inTab});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,4 +109,8 @@
|
||||||
chrome.tabs.update(tabId, {url: newUrl});
|
chrome.tabs.update(tabId, {url: newUrl});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function makeUsercssGlobs(host, path) {
|
||||||
|
return '%css,%css?*,%styl,%styl?*'.replace(/%/g, `*://${host}${path}.user.`).split(',');
|
||||||
|
}
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -59,8 +59,7 @@ self.INJECTED !== 1 && (() => {
|
||||||
if (STYLE_VIA_API) {
|
if (STYLE_VIA_API) {
|
||||||
await API.styleViaAPI({method: 'styleApply'});
|
await API.styleViaAPI({method: 'styleApply'});
|
||||||
} else {
|
} else {
|
||||||
const blobId = chrome.app && getXhrBlobId();
|
const styles = chrome.app && !chrome.tabs && getStylesViaXhr() ||
|
||||||
const styles = blobId && getStylesViaXhr(blobId) ||
|
|
||||||
await API.getSectionsByUrl(getMatchUrl(), null, true);
|
await API.getSectionsByUrl(getMatchUrl(), null, true);
|
||||||
if (styles.disableAll) {
|
if (styles.disableAll) {
|
||||||
delete styles.disableAll;
|
delete styles.disableAll;
|
||||||
|
@ -70,27 +69,16 @@ self.INJECTED !== 1 && (() => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function getXhrBlobId() {
|
function getStylesViaXhr() {
|
||||||
try {
|
try {
|
||||||
const {cookie} = document; // may throw in sandboxed frames
|
const blobId = document.cookie.split(chrome.runtime.id + '=')[1].split(';')[0];
|
||||||
return new RegExp(`(^|\\s|;)${chrome.runtime.id}=\\s*([-\\w]+)\\s*(;|$)`).exec(cookie)[2];
|
const url = 'blob:' + chrome.runtime.getURL(blobId);
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getStylesViaXhr(data) {
|
|
||||||
try {
|
|
||||||
const disableAll = data[0] === '1';
|
|
||||||
const url = 'blob:' + chrome.runtime.getURL(data.slice(1));
|
|
||||||
document.cookie = `${chrome.runtime.id}=1; max-age=0`; // remove our cookie
|
document.cookie = `${chrome.runtime.id}=1; max-age=0`; // remove our cookie
|
||||||
let res;
|
const xhr = new XMLHttpRequest();
|
||||||
if (!disableAll) { // when disabled, will get the styles asynchronously, no rush
|
xhr.open('GET', url, false); // synchronous
|
||||||
const xhr = new XMLHttpRequest();
|
xhr.send();
|
||||||
xhr.open('GET', url, false); // synchronous
|
|
||||||
xhr.send();
|
|
||||||
res = JSON.parse(xhr.response);
|
|
||||||
}
|
|
||||||
URL.revokeObjectURL(url);
|
URL.revokeObjectURL(url);
|
||||||
return res;
|
return JSON.parse(xhr.response);
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
const manifest = chrome.runtime.getManifest();
|
const manifest = chrome.runtime.getManifest();
|
||||||
const allowedOrigins = [
|
const allowedOrigins = [
|
||||||
'https://openusercss.org',
|
'https://openusercss.org',
|
||||||
'https://openusercss.com'
|
'https://openusercss.com',
|
||||||
];
|
];
|
||||||
|
|
||||||
const sendPostMessage = message => {
|
const sendPostMessage = message => {
|
||||||
|
@ -17,7 +17,7 @@
|
||||||
const askHandshake = () => {
|
const askHandshake = () => {
|
||||||
// Tell the page that we exist and that it should send the handshake
|
// Tell the page that we exist and that it should send the handshake
|
||||||
sendPostMessage({
|
sendPostMessage({
|
||||||
type: 'ouc-begin-handshake'
|
type: 'ouc-begin-handshake',
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@
|
||||||
const sendInstalledCallback = styleData => {
|
const sendInstalledCallback = styleData => {
|
||||||
sendPostMessage({
|
sendPostMessage({
|
||||||
type: 'ouc-is-installed-response',
|
type: 'ouc-is-installed-response',
|
||||||
style: styleData
|
style: styleData,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -36,14 +36,14 @@
|
||||||
) {
|
) {
|
||||||
API.findUsercss({
|
API.findUsercss({
|
||||||
name: event.data.name,
|
name: event.data.name,
|
||||||
namespace: event.data.namespace
|
namespace: event.data.namespace,
|
||||||
}).then(style => {
|
}).then(style => {
|
||||||
const data = {event};
|
const data = {event};
|
||||||
const callbackObject = {
|
const callbackObject = {
|
||||||
installed: Boolean(style),
|
installed: Boolean(style),
|
||||||
enabled: style.enabled,
|
enabled: style.enabled,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
namespace: data.namespace
|
namespace: data.namespace,
|
||||||
};
|
};
|
||||||
|
|
||||||
sendInstalledCallback(callbackObject);
|
sendInstalledCallback(callbackObject);
|
||||||
|
@ -71,7 +71,7 @@
|
||||||
'update-auto',
|
'update-auto',
|
||||||
'export-json-backups',
|
'export-json-backups',
|
||||||
'import-json-backups',
|
'import-json-backups',
|
||||||
'manage-local'
|
'manage-local',
|
||||||
];
|
];
|
||||||
const reportedFeatures = [];
|
const reportedFeatures = [];
|
||||||
|
|
||||||
|
@ -96,8 +96,8 @@
|
||||||
key: event.data.key,
|
key: event.data.key,
|
||||||
extension: {
|
extension: {
|
||||||
name: manifest.name,
|
name: manifest.name,
|
||||||
capabilities: reportedFeatures
|
capabilities: reportedFeatures,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -120,7 +120,7 @@
|
||||||
// we were able to install the theme and it may display a success message
|
// we were able to install the theme and it may display a success message
|
||||||
sendPostMessage({
|
sendPostMessage({
|
||||||
type: 'ouc-install-callback',
|
type: 'ouc-install-callback',
|
||||||
key: data.key
|
key: data.key,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -135,7 +135,7 @@
|
||||||
}).then(style => {
|
}).then(style => {
|
||||||
sendInstallCallback({
|
sendInstallCallback({
|
||||||
enabled: style.enabled,
|
enabled: style.enabled,
|
||||||
key: event.data.key
|
key: event.data.key,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,7 +85,7 @@
|
||||||
const observer = new MutationObserver(check);
|
const observer = new MutationObserver(check);
|
||||||
observer.observe(document.documentElement, {
|
observer.observe(document.documentElement, {
|
||||||
childList: true,
|
childList: true,
|
||||||
subtree: true
|
subtree: true,
|
||||||
});
|
});
|
||||||
check();
|
check();
|
||||||
|
|
||||||
|
@ -105,7 +105,7 @@
|
||||||
? 'styleCanBeUpdatedChrome'
|
? 'styleCanBeUpdatedChrome'
|
||||||
: 'styleAlreadyInstalledChrome',
|
: 'styleAlreadyInstalledChrome',
|
||||||
detail: {
|
detail: {
|
||||||
updateUrl: installedStyle.updateUrl
|
updateUrl: installedStyle.updateUrl,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -155,7 +155,7 @@
|
||||||
function doInstall() {
|
function doInstall() {
|
||||||
let oldStyle;
|
let oldStyle;
|
||||||
return API.findStyle({
|
return API.findStyle({
|
||||||
md5Url: getMeta('stylish-md5-url') || location.href
|
md5Url: getMeta('stylish-md5-url') || location.href,
|
||||||
}, true)
|
}, true)
|
||||||
.then(_oldStyle => {
|
.then(_oldStyle => {
|
||||||
oldStyle = _oldStyle;
|
oldStyle = _oldStyle;
|
||||||
|
|
|
@ -162,7 +162,7 @@ function beautify(scope, ui = true) {
|
||||||
$create('SVG:path', {
|
$create('SVG:path', {
|
||||||
'fill-rule': 'evenodd',
|
'fill-rule': 'evenodd',
|
||||||
'd': 'M1408 704q0 26-19 45l-448 448q-19 19-45 ' +
|
'd': 'M1408 704q0 26-19 45l-448 448q-19 19-45 ' +
|
||||||
'19t-45-19l-448-448q-19-19-19-45t19-45 45-19h896q26 0 45 19t19 45z'
|
'19t-45-19l-448-448q-19-19-19-45t19-45 45-19h896q26 0 45 19t19 45z',
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
]),
|
]),
|
||||||
|
@ -176,7 +176,7 @@ function beautify(scope, ui = true) {
|
||||||
$create('input', {
|
$create('input', {
|
||||||
type: 'checkbox',
|
type: 'checkbox',
|
||||||
dataset: {option: optionName},
|
dataset: {option: optionName},
|
||||||
checked: options[optionName] !== false
|
checked: options[optionName] !== false,
|
||||||
}),
|
}),
|
||||||
$create('SVG:svg.svg-icon.checked',
|
$create('SVG:svg.svg-icon.checked',
|
||||||
$create('SVG:use', {'xlink:href': '#svg-icon-checked'})),
|
$create('SVG:use', {'xlink:href': '#svg-icon-checked'})),
|
||||||
|
|
|
@ -1,4 +1,10 @@
|
||||||
/* global CodeMirror prefs editor $ template */
|
/* global
|
||||||
|
$
|
||||||
|
CodeMirror
|
||||||
|
editor
|
||||||
|
prefs
|
||||||
|
t
|
||||||
|
*/
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
@ -82,7 +88,7 @@
|
||||||
[
|
[
|
||||||
{from: 'Ctrl-', to: ['Alt-', 'Ctrl-Alt-']},
|
{from: 'Ctrl-', to: ['Alt-', 'Ctrl-Alt-']},
|
||||||
// Note: modifier order in CodeMirror is S-C-A
|
// Note: modifier order in CodeMirror is S-C-A
|
||||||
{from: 'Shift-Ctrl-', to: ['Ctrl-Alt-', 'Shift-Ctrl-Alt-']}
|
{from: 'Shift-Ctrl-', to: ['Ctrl-Alt-', 'Shift-Ctrl-Alt-']},
|
||||||
].forEach(remap => {
|
].forEach(remap => {
|
||||||
const oldKey = remap.from + char;
|
const oldKey = remap.from + char;
|
||||||
Object.keys(CodeMirror.keyMap).forEach(keyMapName => {
|
Object.keys(CodeMirror.keyMap).forEach(keyMapName => {
|
||||||
|
@ -134,7 +140,7 @@
|
||||||
let filled;
|
let filled;
|
||||||
this.eachLine(({text}) => (filled = text && /\S/.test(text)));
|
this.eachLine(({text}) => (filled = text && /\S/.test(text)));
|
||||||
return !filled;
|
return !filled;
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// editor commands
|
// editor commands
|
||||||
|
@ -183,7 +189,7 @@
|
||||||
// setTimeout(() => {
|
// setTimeout(() => {
|
||||||
// $('.CodeMirror-dialog', section).focus();
|
// $('.CodeMirror-dialog', section).focus();
|
||||||
// });
|
// });
|
||||||
cm.openDialog(template.jumpToLine.cloneNode(true), str => {
|
cm.openDialog(t.template.jumpToLine.cloneNode(true), str => {
|
||||||
const m = str.match(/^\s*(\d+)(?:\s*:\s*(\d+))?\s*$/);
|
const m = str.match(/^\s*(\d+)(?:\s*:\s*(\d+))?\s*$/);
|
||||||
if (m) {
|
if (m) {
|
||||||
cm.setCursor(m[1] - 1, m[2] ? m[2] - 1 : cur.ch);
|
cm.setCursor(m[1] - 1, m[2] ? m[2] - 1 : cur.ch);
|
||||||
|
|
|
@ -33,13 +33,13 @@ const cmFactory = (() => {
|
||||||
cm.setOption('highlightSelectionMatches', {
|
cm.setOption('highlightSelectionMatches', {
|
||||||
showToken: /[#.\-\w]/,
|
showToken: /[#.\-\w]/,
|
||||||
annotateScrollbar: true,
|
annotateScrollbar: true,
|
||||||
onUpdate: updateMatchHighlightCount
|
onUpdate: updateMatchHighlightCount,
|
||||||
});
|
});
|
||||||
} else if (value === 'selection') {
|
} else if (value === 'selection') {
|
||||||
cm.setOption('highlightSelectionMatches', {
|
cm.setOption('highlightSelectionMatches', {
|
||||||
showToken: false,
|
showToken: false,
|
||||||
annotateScrollbar: true,
|
annotateScrollbar: true,
|
||||||
onUpdate: updateMatchHighlightCount
|
onUpdate: updateMatchHighlightCount,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
cm.setOption('highlightSelectionMatches', null);
|
cm.setOption('highlightSelectionMatches', null);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/* exported CODEMIRROR_THEMES */
|
/* Do not edit. This file is auto-generated by build-vendor.js */
|
||||||
// this file is generated by update-codemirror-themes.js
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
/* exported CODEMIRROR_THEMES */
|
||||||
const CODEMIRROR_THEMES = [
|
const CODEMIRROR_THEMES = [
|
||||||
'3024-day',
|
'3024-day',
|
||||||
'3024-night',
|
'3024-night',
|
||||||
|
@ -65,5 +65,5 @@ const CODEMIRROR_THEMES = [
|
||||||
'xq-light',
|
'xq-light',
|
||||||
'yeti',
|
'yeti',
|
||||||
'yonce',
|
'yonce',
|
||||||
'zenburn'
|
'zenburn',
|
||||||
];
|
];
|
||||||
|
|
70
edit/edit.js
70
edit/edit.js
|
@ -22,11 +22,10 @@
|
||||||
prefs
|
prefs
|
||||||
rerouteHotkeys
|
rerouteHotkeys
|
||||||
SectionsEditor
|
SectionsEditor
|
||||||
sessionStorageHash
|
sessionStore
|
||||||
setupLivePrefs
|
setupLivePrefs
|
||||||
SourceEditor
|
SourceEditor
|
||||||
t
|
t
|
||||||
tHTML
|
|
||||||
tryCatch
|
tryCatch
|
||||||
tryJSONparse
|
tryJSONparse
|
||||||
*/
|
*/
|
||||||
|
@ -56,13 +55,23 @@ lazyInit();
|
||||||
.then(initTheme),
|
.then(initTheme),
|
||||||
onDOMready(),
|
onDOMready(),
|
||||||
]);
|
]);
|
||||||
|
const scrollInfo = style.id && tryJSONparse(sessionStore['editorScrollInfo' + style.id]);
|
||||||
/** @namespace EditorBase */
|
/** @namespace EditorBase */
|
||||||
Object.assign(editor, {
|
Object.assign(editor, {
|
||||||
style,
|
style,
|
||||||
dirty,
|
dirty,
|
||||||
|
scrollInfo,
|
||||||
updateName,
|
updateName,
|
||||||
updateToc,
|
updateToc,
|
||||||
toggleStyle,
|
toggleStyle,
|
||||||
|
applyScrollInfo(cm, si = ((scrollInfo || {}).cms || [])[0]) {
|
||||||
|
if (si && si.sel) {
|
||||||
|
cm.operation(() => {
|
||||||
|
cm.setSelections(...si.sel, {scroll: false});
|
||||||
|
cm.scrollIntoView(cm.getCursor(), si.parentHeight / 2);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
prefs.subscribe('editor.linter', updateLinter);
|
prefs.subscribe('editor.linter', updateLinter);
|
||||||
prefs.subscribe('editor.keyMap', showHotkeyInTooltip);
|
prefs.subscribe('editor.keyMap', showHotkeyInTooltip);
|
||||||
|
@ -78,17 +87,21 @@ lazyInit();
|
||||||
|
|
||||||
$('#heading').textContent = t(style.id ? 'editStyleHeading' : 'addStyleTitle');
|
$('#heading').textContent = t(style.id ? 'editStyleHeading' : 'addStyleTitle');
|
||||||
$('#preview-label').classList.toggle('hidden', !style.id);
|
$('#preview-label').classList.toggle('hidden', !style.id);
|
||||||
|
|
||||||
const toc = [];
|
const toc = [];
|
||||||
const elToc = $('#toc');
|
const elToc = $('#toc');
|
||||||
elToc.onclick = e => editor.jumpToEditor([...elToc.children].indexOf(e.target));
|
elToc.onclick = e => editor.jumpToEditor([...elToc.children].indexOf(e.target));
|
||||||
|
if (editor.isUsercss) {
|
||||||
(editor.isUsercss ? SourceEditor : SectionsEditor)();
|
SourceEditor();
|
||||||
|
} else {
|
||||||
|
SectionsEditor();
|
||||||
|
}
|
||||||
prefs.subscribe('editor.toc.expanded', (k, val) => val && editor.updateToc(), {now: true});
|
prefs.subscribe('editor.toc.expanded', (k, val) => val && editor.updateToc(), {now: true});
|
||||||
dirty.onChange(updateDirty);
|
dirty.onChange(updateDirty);
|
||||||
await editor.ready;
|
|
||||||
|
|
||||||
|
await editor.ready;
|
||||||
|
editor.ready = true;
|
||||||
|
|
||||||
|
setTimeout(() => editor.getEditors().forEach(linter.enableForEditor));
|
||||||
// enabling after init to prevent flash of validation failure on an empty name
|
// enabling after init to prevent flash of validation failure on an empty name
|
||||||
$('#name').required = !editor.isUsercss;
|
$('#name').required = !editor.isUsercss;
|
||||||
$('#save-button').onclick = editor.save;
|
$('#save-button').onclick = editor.save;
|
||||||
|
@ -100,7 +113,7 @@ lazyInit();
|
||||||
// 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'));
|
||||||
document.documentElement.classList.toggle('usercss', editor.isUsercss);
|
document.documentElement.classList.toggle('usercss', editor.isUsercss);
|
||||||
sessionStorage.justEditedStyleId = style.id || '';
|
sessionStore.justEditedStyleId = style.id || '';
|
||||||
// no such style so let's clear the invalid URL parameters
|
// no such style so let's clear the invalid URL parameters
|
||||||
if (!style.id) history.replaceState({}, '', location.pathname);
|
if (!style.id) history.replaceState({}, '', location.pathname);
|
||||||
updateTitle(false);
|
updateTitle(false);
|
||||||
|
@ -290,16 +303,9 @@ lazyInit();
|
||||||
function updateToc(added = editor.sections) {
|
function updateToc(added = editor.sections) {
|
||||||
const {sections} = editor;
|
const {sections} = editor;
|
||||||
const first = sections.indexOf(added[0]);
|
const first = sections.indexOf(added[0]);
|
||||||
let el = elToc.children[first];
|
const elFirst = elToc.children[first];
|
||||||
if (added.focus) {
|
if (first >= 0 && (!added.focus || !elFirst)) {
|
||||||
const cls = 'current';
|
for (let el = elFirst, i = first; i < sections.length; i++) {
|
||||||
const old = $('.' + cls, elToc);
|
|
||||||
if (old && old !== el) old.classList.remove(cls);
|
|
||||||
el.classList.add(cls);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (first >= 0) {
|
|
||||||
for (let i = first; i < sections.length; i++) {
|
|
||||||
const entry = sections[i].tocEntry;
|
const entry = sections[i].tocEntry;
|
||||||
if (!deepEqual(entry, toc[i])) {
|
if (!deepEqual(entry, toc[i])) {
|
||||||
if (!el) el = elToc.appendChild($create('li', {tabIndex: 0}));
|
if (!el) el = elToc.appendChild($create('li', {tabIndex: 0}));
|
||||||
|
@ -318,6 +324,13 @@ lazyInit();
|
||||||
elToc.lastElementChild.remove();
|
elToc.lastElementChild.remove();
|
||||||
toc.length--;
|
toc.length--;
|
||||||
}
|
}
|
||||||
|
if (added.focus) {
|
||||||
|
const cls = 'current';
|
||||||
|
const old = $('.' + cls, elToc);
|
||||||
|
const el = elFirst || elToc.children[first];
|
||||||
|
if (old && old !== el) old.classList.remove(cls);
|
||||||
|
el.classList.add(cls);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
@ -335,7 +348,7 @@ function lazyInit() {
|
||||||
async function patchHistoryBack(tab) {
|
async function patchHistoryBack(tab) {
|
||||||
ownTabId = tab.id;
|
ownTabId = tab.id;
|
||||||
// use browser history back when 'back to manage' is clicked
|
// use browser history back when 'back to manage' is clicked
|
||||||
if (sessionStorageHash('manageStylesHistory').value[ownTabId] === location.href) {
|
if (sessionStore['manageStylesHistory' + ownTabId] === location.href) {
|
||||||
await onDOMready();
|
await onDOMready();
|
||||||
$('#cancel-button').onclick = event => {
|
$('#cancel-button').onclick = event => {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
@ -346,8 +359,8 @@ function lazyInit() {
|
||||||
}
|
}
|
||||||
/** resize on 'undo close' */
|
/** resize on 'undo close' */
|
||||||
function restoreWindowSize() {
|
function restoreWindowSize() {
|
||||||
const pos = tryJSONparse(sessionStorage.windowPos);
|
const pos = tryJSONparse(sessionStore.windowPos);
|
||||||
delete sessionStorage.windowPos;
|
delete sessionStore.windowPos;
|
||||||
if (pos && pos.left != null && chrome.windows) {
|
if (pos && pos.left != null && chrome.windows) {
|
||||||
chrome.windows.update(chrome.windows.WINDOW_ID_CURRENT, pos);
|
chrome.windows.update(chrome.windows.WINDOW_ID_CURRENT, pos);
|
||||||
}
|
}
|
||||||
|
@ -408,7 +421,16 @@ function onRuntimeMessage(request) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function beforeUnload(e) {
|
function beforeUnload(e) {
|
||||||
sessionStorage.windowPos = JSON.stringify(canSaveWindowPos() && prefs.get('windowPosition'));
|
sessionStore.windowPos = JSON.stringify(canSaveWindowPos() && prefs.get('windowPosition'));
|
||||||
|
sessionStore['editorScrollInfo' + editor.style.id] = JSON.stringify({
|
||||||
|
scrollY: window.scrollY,
|
||||||
|
cms: editor.getEditors().map(cm => /** @namespace EditorScrollInfo */({
|
||||||
|
focus: cm.hasFocus(),
|
||||||
|
height: cm.display.wrapper.style.height.replace('100vh', ''),
|
||||||
|
parentHeight: cm.display.wrapper.parentElement.offsetHeight,
|
||||||
|
sel: cm.isClean() && [cm.doc.sel.ranges, cm.doc.sel.primIndex],
|
||||||
|
})),
|
||||||
|
});
|
||||||
const activeElement = document.activeElement;
|
const activeElement = document.activeElement;
|
||||||
if (activeElement) {
|
if (activeElement) {
|
||||||
// blurring triggers 'change' or 'input' event if needed
|
// blurring triggers 'change' or 'input' event if needed
|
||||||
|
@ -429,7 +451,7 @@ function showHelp(title = '', body) {
|
||||||
const contents = $('.contents', div);
|
const contents = $('.contents', div);
|
||||||
contents.textContent = '';
|
contents.textContent = '';
|
||||||
if (body) {
|
if (body) {
|
||||||
contents.appendChild(typeof body === 'string' ? tHTML(body) : body);
|
contents.appendChild(typeof body === 'string' ? t.HTML(body) : body);
|
||||||
}
|
}
|
||||||
|
|
||||||
$('.title', div).textContent = title;
|
$('.title', div).textContent = title;
|
||||||
|
@ -492,7 +514,7 @@ function showCodeMirrorPopup(title, html, options) {
|
||||||
matchBrackets: true,
|
matchBrackets: true,
|
||||||
styleActiveLine: true,
|
styleActiveLine: true,
|
||||||
theme: prefs.get('editor.theme'),
|
theme: prefs.get('editor.theme'),
|
||||||
keyMap: prefs.get('editor.keyMap')
|
keyMap: prefs.get('editor.keyMap'),
|
||||||
}, options));
|
}, options));
|
||||||
cm.focus();
|
cm.focus();
|
||||||
rerouteHotkeys(false);
|
rerouteHotkeys(false);
|
||||||
|
|
|
@ -29,13 +29,13 @@ workerUtil.createAPI({
|
||||||
code: err.code,
|
code: err.code,
|
||||||
args: err.args,
|
args: err.args,
|
||||||
message: err.message,
|
message: err.message,
|
||||||
index: err.index
|
index: err.index,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
getStylelintRules,
|
getStylelintRules,
|
||||||
getCsslintRules
|
getCsslintRules,
|
||||||
});
|
});
|
||||||
|
|
||||||
function getCsslintRules() {
|
function getCsslintRules() {
|
||||||
|
|
|
@ -1,5 +1,18 @@
|
||||||
/* global CodeMirror focusAccessibility colorMimicry editor chromeLocal
|
/* global
|
||||||
onDOMready $ $$ $create t debounce tryRegExp stringAsRegExp template */
|
$
|
||||||
|
$$
|
||||||
|
$create
|
||||||
|
chromeLocal
|
||||||
|
CodeMirror
|
||||||
|
colorMimicry
|
||||||
|
debounce
|
||||||
|
editor
|
||||||
|
focusAccessibility
|
||||||
|
onDOMready
|
||||||
|
stringAsRegExp
|
||||||
|
t
|
||||||
|
tryRegExp
|
||||||
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
onDOMready().then(() => {
|
onDOMready().then(() => {
|
||||||
|
@ -100,7 +113,7 @@ onDOMready().then(() => {
|
||||||
state.lastFind = '';
|
state.lastFind = '';
|
||||||
toggleDataset(this, 'enabled', !state.icase);
|
toggleDataset(this, 'enabled', !state.icase);
|
||||||
doSearch({canAdvance: false});
|
doSearch({canAdvance: false});
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -136,7 +149,7 @@ onDOMready().then(() => {
|
||||||
trimUndoHistory();
|
trimUndoHistory();
|
||||||
enableUndoButton(state.undoHistory.length);
|
enableUndoButton(state.undoHistory.length);
|
||||||
if (state.find) doSearch({canAdvance: false});
|
if (state.find) doSearch({canAdvance: false});
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const DIALOG_PROPS = {
|
const DIALOG_PROPS = {
|
||||||
|
@ -152,7 +165,7 @@ onDOMready().then(() => {
|
||||||
state.replace = this.value;
|
state.replace = this.value;
|
||||||
adjustTextareaSize(this);
|
adjustTextareaSize(this);
|
||||||
debounce(writeStorage, STORAGE_UPDATE_DELAY);
|
debounce(writeStorage, STORAGE_UPDATE_DELAY);
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -169,7 +182,7 @@ onDOMready().then(() => {
|
||||||
replace(cm) {
|
replace(cm) {
|
||||||
state.reverse = false;
|
state.reverse = false;
|
||||||
focusDialog('replace', cm);
|
focusDialog('replace', cm);
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
COMMANDS.replaceAll = COMMANDS.replace;
|
COMMANDS.replaceAll = COMMANDS.replace;
|
||||||
|
|
||||||
|
@ -563,14 +576,14 @@ onDOMready().then(() => {
|
||||||
state.originalFocus = document.activeElement;
|
state.originalFocus = document.activeElement;
|
||||||
state.firstRun = true;
|
state.firstRun = true;
|
||||||
|
|
||||||
const dialog = state.dialog = template.searchReplaceDialog.cloneNode(true);
|
const dialog = state.dialog = t.template.searchReplaceDialog.cloneNode(true);
|
||||||
Object.assign(dialog, DIALOG_PROPS.dialog);
|
Object.assign(dialog, DIALOG_PROPS.dialog);
|
||||||
dialog.addEventListener('focusout', EVENTS.onfocusout);
|
dialog.addEventListener('focusout', EVENTS.onfocusout);
|
||||||
dialog.dataset.type = type;
|
dialog.dataset.type = type;
|
||||||
dialog.style.pointerEvents = 'auto';
|
dialog.style.pointerEvents = 'auto';
|
||||||
|
|
||||||
const content = $('[data-type="content"]', dialog);
|
const content = $('[data-type="content"]', dialog);
|
||||||
content.parentNode.replaceChild(template[type].cloneNode(true), content);
|
content.parentNode.replaceChild(t.template[type].cloneNode(true), content);
|
||||||
|
|
||||||
createInput(0, 'input', state.find);
|
createInput(0, 'input', state.find);
|
||||||
createInput(1, 'input2', state.replace);
|
createInput(1, 'input2', state.replace);
|
||||||
|
@ -633,7 +646,7 @@ onDOMready().then(() => {
|
||||||
input.value = value;
|
input.value = value;
|
||||||
Object.assign(input, DIALOG_PROPS[name]);
|
Object.assign(input, DIALOG_PROPS[name]);
|
||||||
|
|
||||||
input.parentElement.appendChild(template.clearSearch.cloneNode(true));
|
input.parentElement.appendChild(t.template.clearSearch.cloneNode(true));
|
||||||
$('[data-action]', input.parentElement)._input = input;
|
$('[data-action]', input.parentElement)._input = input;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,7 +61,7 @@
|
||||||
loadScript([
|
loadScript([
|
||||||
'/vendor/codemirror/mode/javascript/javascript.js',
|
'/vendor/codemirror/mode/javascript/javascript.js',
|
||||||
'/vendor/codemirror/addon/lint/json-lint.js',
|
'/vendor/codemirror/addon/lint/json-lint.js',
|
||||||
'/vendor/jsonlint/jsonlint.js'
|
'/vendor/jsonlint/jsonlint.js',
|
||||||
]).then(() => {
|
]).then(() => {
|
||||||
cm.setOption('mode', 'application/json');
|
cm.setOption('mode', 'application/json');
|
||||||
cm.setOption('lint', true);
|
cm.setOption('lint', true);
|
||||||
|
|
|
@ -12,13 +12,13 @@ const LINTER_DEFAULTS = (() => {
|
||||||
rules: {
|
rules: {
|
||||||
'at-rule-no-unknown': [true, {
|
'at-rule-no-unknown': [true, {
|
||||||
'ignoreAtRules': ['extend', 'extends', 'css', 'block'],
|
'ignoreAtRules': ['extend', 'extends', 'css', 'block'],
|
||||||
'severity': 'warning'
|
'severity': 'warning',
|
||||||
}],
|
}],
|
||||||
'block-no-empty': [true, SEVERITY],
|
'block-no-empty': [true, SEVERITY],
|
||||||
'color-no-invalid-hex': [true, SEVERITY],
|
'color-no-invalid-hex': [true, SEVERITY],
|
||||||
'declaration-block-no-duplicate-properties': [true, {
|
'declaration-block-no-duplicate-properties': [true, {
|
||||||
'ignore': ['consecutive-duplicates-with-different-values'],
|
'ignore': ['consecutive-duplicates-with-different-values'],
|
||||||
'severity': 'warning'
|
'severity': 'warning',
|
||||||
}],
|
}],
|
||||||
'declaration-block-no-shorthand-property-overrides': [true, SEVERITY],
|
'declaration-block-no-shorthand-property-overrides': [true, SEVERITY],
|
||||||
'font-family-no-duplicate-names': [true, SEVERITY],
|
'font-family-no-duplicate-names': [true, SEVERITY],
|
||||||
|
@ -172,7 +172,7 @@ const LINTER_DEFAULTS = (() => {
|
||||||
'value-list-comma-space-before': 'never',
|
'value-list-comma-space-before': 'never',
|
||||||
'value-list-max-empty-lines': 0
|
'value-list-max-empty-lines': 0
|
||||||
*/
|
*/
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const CSSLINT = {
|
const CSSLINT = {
|
||||||
// Default warnings
|
// Default warnings
|
||||||
|
@ -216,7 +216,7 @@ const LINTER_DEFAULTS = (() => {
|
||||||
'universal-selector': 0,
|
'universal-selector': 0,
|
||||||
'unqualified-attributes': 0,
|
'unqualified-attributes': 0,
|
||||||
'vendor-prefix': 0,
|
'vendor-prefix': 0,
|
||||||
'zero-units': 0
|
'zero-units': 0,
|
||||||
};
|
};
|
||||||
return {STYLELINT, CSSLINT, SEVERITY};
|
return {STYLELINT, CSSLINT, SEVERITY};
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
storageName: chromeSync.LZ_KEY.csslint,
|
storageName: chromeSync.LZ_KEY.csslint,
|
||||||
lint: csslint,
|
lint: csslint,
|
||||||
validMode: mode => mode === 'css',
|
validMode: mode => mode === 'css',
|
||||||
getConfig: config => Object.assign({}, LINTER_DEFAULTS.CSSLINT, config)
|
getConfig: config => Object.assign({}, LINTER_DEFAULTS.CSSLINT, config),
|
||||||
},
|
},
|
||||||
stylelint: {
|
stylelint: {
|
||||||
storageName: chromeSync.LZ_KEY.stylelint,
|
storageName: chromeSync.LZ_KEY.stylelint,
|
||||||
|
@ -15,9 +15,9 @@
|
||||||
validMode: () => true,
|
validMode: () => true,
|
||||||
getConfig: config => ({
|
getConfig: config => ({
|
||||||
syntax: 'sugarss',
|
syntax: 'sugarss',
|
||||||
rules: Object.assign({}, LINTER_DEFAULTS.STYLELINT.rules, config && config.rules)
|
rules: Object.assign({}, LINTER_DEFAULTS.STYLELINT.rules, config && config.rules),
|
||||||
})
|
}),
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
async function stylelint(text, config, mode) {
|
async function stylelint(text, config, mode) {
|
||||||
|
|
|
@ -33,7 +33,7 @@ function createMetaCompiler(cm, onUpdated) {
|
||||||
to: cm.posFromIndex((err.index || 0) + match.index),
|
to: cm.posFromIndex((err.index || 0) + match.index),
|
||||||
message: err.code && chrome.i18n.getMessage(`meta_${err.code}`, err.args) || err.message,
|
message: err.code && chrome.i18n.getMessage(`meta_${err.code}`, err.args) || err.message,
|
||||||
severity: err.code === 'unknownMeta' ? 'warning' : 'error',
|
severity: err.code === 'unknownMeta' ? 'warning' : 'error',
|
||||||
rule: err.code
|
rule: err.code,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
meta = match[0];
|
meta = match[0];
|
||||||
|
|
|
@ -77,7 +77,7 @@ Object.assign(linter, (() => {
|
||||||
element: table,
|
element: table,
|
||||||
trs,
|
trs,
|
||||||
updateAnnotations,
|
updateAnnotations,
|
||||||
updateCaption
|
updateCaption,
|
||||||
};
|
};
|
||||||
|
|
||||||
function updateCaption() {
|
function updateCaption() {
|
||||||
|
@ -124,18 +124,18 @@ Object.assign(linter, (() => {
|
||||||
const message = $create('td', {attributes: {role: 'message'}});
|
const message = $create('td', {attributes: {role: 'message'}});
|
||||||
|
|
||||||
const trElement = $create('tr', {
|
const trElement = $create('tr', {
|
||||||
onclick: () => gotoLintIssue(cm, anno)
|
onclick: () => gotoLintIssue(cm, anno),
|
||||||
}, [
|
}, [
|
||||||
severity,
|
severity,
|
||||||
line,
|
line,
|
||||||
$create('td', {attributes: {role: 'sep'}}, ':'),
|
$create('td', {attributes: {role: 'sep'}}, ':'),
|
||||||
col,
|
col,
|
||||||
message
|
message,
|
||||||
]);
|
]);
|
||||||
return {
|
return {
|
||||||
element: trElement,
|
element: trElement,
|
||||||
update,
|
update,
|
||||||
getAnnotation: () => anno
|
getAnnotation: () => anno,
|
||||||
};
|
};
|
||||||
|
|
||||||
function update(_anno) {
|
function update(_anno) {
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
|
|
||||||
/* exported editorWorker */
|
/* exported editorWorker */
|
||||||
const editorWorker = workerUtil.createWorker({
|
const editorWorker = workerUtil.createWorker({
|
||||||
url: '/edit/editor-worker.js'
|
url: '/edit/editor-worker.js',
|
||||||
});
|
});
|
||||||
|
|
||||||
/* exported linter */
|
/* exported linter */
|
||||||
|
@ -19,7 +19,7 @@ const linter = (() => {
|
||||||
enableForEditor,
|
enableForEditor,
|
||||||
disableForEditor,
|
disableForEditor,
|
||||||
onLintingUpdated,
|
onLintingUpdated,
|
||||||
onUnhook
|
onUnhook,
|
||||||
};
|
};
|
||||||
|
|
||||||
function onUnhook(cb) {
|
function onUnhook(cb) {
|
||||||
|
|
|
@ -40,7 +40,7 @@ function createLivePreview(preprocess, shouldShow) {
|
||||||
|
|
||||||
function createPreviewer() {
|
function createPreviewer() {
|
||||||
const port = chrome.runtime.connect({
|
const port = chrome.runtime.connect({
|
||||||
name: 'livePreview'
|
name: 'livePreview',
|
||||||
});
|
});
|
||||||
port.onDisconnect.addListener(err => {
|
port.onDisconnect.addListener(err => {
|
||||||
throw err;
|
throw err;
|
||||||
|
|
|
@ -75,7 +75,7 @@ function MozSectionFinder(cm) {
|
||||||
/** @param {MozSection} [section] */
|
/** @param {MozSection} [section] */
|
||||||
updatePositions(section) {
|
updatePositions(section) {
|
||||||
(section ? [section] : getState().sections).forEach(setPositionFromMark);
|
(section ? [section] : getState().sections).forEach(setPositionFromMark);
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
return MozSectionFinder;
|
return MozSectionFinder;
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
prefs
|
prefs
|
||||||
regExpTester
|
regExpTester
|
||||||
t
|
t
|
||||||
template
|
|
||||||
tryCatch
|
tryCatch
|
||||||
*/
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
@ -55,7 +54,7 @@ function MozSectionWidget(
|
||||||
$create('ul' + C_LIST),
|
$create('ul' + C_LIST),
|
||||||
]),
|
]),
|
||||||
listItem:
|
listItem:
|
||||||
template.appliesTo.cloneNode(true),
|
t.template.appliesTo.cloneNode(true),
|
||||||
appliesToEverything:
|
appliesToEverything:
|
||||||
$create('li.applies-to-everything', t('appliesToEverything')),
|
$create('li.applies-to-everything', t('appliesToEverything')),
|
||||||
};
|
};
|
||||||
|
@ -74,7 +73,7 @@ function MozSectionWidget(
|
||||||
if (funcs.length < 2) {
|
if (funcs.length < 2) {
|
||||||
messageBox({
|
messageBox({
|
||||||
contents: t('appliesRemoveError'),
|
contents: t('appliesRemoveError'),
|
||||||
buttons: [t('confirmClose')]
|
buttons: [t('confirmClose')],
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -125,7 +124,7 @@ function MozSectionWidget(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
actualStyle = $create('style');
|
actualStyle = $create('style');
|
||||||
|
|
|
@ -1,4 +1,12 @@
|
||||||
/* global showHelp $ $create tryRegExp URLS t template openURL */
|
/* global
|
||||||
|
$
|
||||||
|
$create
|
||||||
|
openURL
|
||||||
|
showHelp
|
||||||
|
t
|
||||||
|
tryRegExp
|
||||||
|
URLS
|
||||||
|
*/
|
||||||
/* exported regExpTester */
|
/* exported regExpTester */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
@ -86,7 +94,7 @@ const regExpTester = (() => {
|
||||||
full: {data: [], label: t('styleRegexpTestFull')},
|
full: {data: [], label: t('styleRegexpTestFull')},
|
||||||
partial: {data: [], label: [
|
partial: {data: [], label: [
|
||||||
t('styleRegexpTestPartial'),
|
t('styleRegexpTestPartial'),
|
||||||
template.regexpTestPartial.cloneNode(true),
|
t.template.regexpTestPartial.cloneNode(true),
|
||||||
]},
|
]},
|
||||||
none: {data: [], label: t('styleRegexpTestNone')},
|
none: {data: [], label: t('styleRegexpTestNone')},
|
||||||
invalid: {data: [], label: t('styleRegexpTestInvalid')},
|
invalid: {data: [], label: t('styleRegexpTestInvalid')},
|
||||||
|
|
|
@ -9,7 +9,6 @@
|
||||||
prefs
|
prefs
|
||||||
regExpTester
|
regExpTester
|
||||||
t
|
t
|
||||||
template
|
|
||||||
trimCommentLabel
|
trimCommentLabel
|
||||||
tryRegExp
|
tryRegExp
|
||||||
*/
|
*/
|
||||||
|
@ -17,20 +16,28 @@
|
||||||
|
|
||||||
/* exported createSection */
|
/* exported createSection */
|
||||||
|
|
||||||
/** @returns {EditorSection} */
|
/**
|
||||||
function createSection(originalSection, genId) {
|
* @param {StyleSection} originalSection
|
||||||
|
* @param {function():number} genId
|
||||||
|
* @param {EditorScrollInfo} [si]
|
||||||
|
* @returns {EditorSection}
|
||||||
|
*/
|
||||||
|
function createSection(originalSection, genId, si) {
|
||||||
const {dirty} = editor;
|
const {dirty} = editor;
|
||||||
const sectionId = genId();
|
const sectionId = genId();
|
||||||
const el = template.section.cloneNode(true);
|
const el = t.template.section.cloneNode(true);
|
||||||
const elLabel = $('.code-label', el);
|
const elLabel = $('.code-label', el);
|
||||||
const cm = cmFactory.create(wrapper => {
|
const cm = cmFactory.create(wrapper => {
|
||||||
// making it tall during initial load so IntersectionObserver sees only one adjacent CM
|
// making it tall during initial load so IntersectionObserver sees only one adjacent CM
|
||||||
wrapper.style.height = '100vh';
|
if (editor.ready !== true) {
|
||||||
|
wrapper.style.height = si ? si.height : '100vh';
|
||||||
|
}
|
||||||
elLabel.after(wrapper);
|
elLabel.after(wrapper);
|
||||||
}, {
|
}, {
|
||||||
value: originalSection.code,
|
value: originalSection.code,
|
||||||
});
|
});
|
||||||
el.CodeMirror = cm; // used by getAssociatedEditor
|
el.CodeMirror = cm; // used by getAssociatedEditor
|
||||||
|
editor.applyScrollInfo(cm, si);
|
||||||
|
|
||||||
const changeListeners = new Set();
|
const changeListeners = new Set();
|
||||||
|
|
||||||
|
@ -259,8 +266,8 @@ function createSection(originalSection, genId) {
|
||||||
function createApply({type = 'url', value, all = false}) {
|
function createApply({type = 'url', value, all = false}) {
|
||||||
const applyId = genId();
|
const applyId = genId();
|
||||||
const dirtyPrefix = `section.${sectionId}.apply.${applyId}`;
|
const dirtyPrefix = `section.${sectionId}.apply.${applyId}`;
|
||||||
const el = all ? template.appliesToEverything.cloneNode(true) :
|
const el = all ? t.template.appliesToEverything.cloneNode(true) :
|
||||||
template.appliesTo.cloneNode(true);
|
t.template.appliesTo.cloneNode(true);
|
||||||
|
|
||||||
const selectEl = !all && $('.applies-type', el);
|
const selectEl = !all && $('.applies-type', el);
|
||||||
if (selectEl) {
|
if (selectEl) {
|
||||||
|
@ -353,7 +360,7 @@ function createSection(originalSection, genId) {
|
||||||
function createResizeGrip(cm) {
|
function createResizeGrip(cm) {
|
||||||
const wrapper = cm.display.wrapper;
|
const wrapper = cm.display.wrapper;
|
||||||
wrapper.classList.add('resize-grip-enabled');
|
wrapper.classList.add('resize-grip-enabled');
|
||||||
const resizeGrip = template.resizeGrip.cloneNode(true);
|
const resizeGrip = t.template.resizeGrip.cloneNode(true);
|
||||||
wrapper.appendChild(resizeGrip);
|
wrapper.appendChild(resizeGrip);
|
||||||
let lastClickTime = 0;
|
let lastClickTime = 0;
|
||||||
let initHeight;
|
let initHeight;
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
messageBox
|
messageBox
|
||||||
prefs
|
prefs
|
||||||
sectionsToMozFormat
|
sectionsToMozFormat
|
||||||
|
sessionStore
|
||||||
showCodeMirrorPopup
|
showCodeMirrorPopup
|
||||||
showHelp
|
showHelp
|
||||||
t
|
t
|
||||||
|
@ -117,7 +118,7 @@ function SectionsEditor() {
|
||||||
}
|
}
|
||||||
newStyle = await API.editSave(newStyle);
|
newStyle = await API.editSave(newStyle);
|
||||||
destroyRemovedSections();
|
destroyRemovedSections();
|
||||||
sessionStorage.justEditedStyleId = newStyle.id;
|
sessionStore.justEditedStyleId = newStyle.id;
|
||||||
editor.replaceStyle(newStyle, false);
|
editor.replaceStyle(newStyle, false);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -141,7 +142,7 @@ function SectionsEditor() {
|
||||||
|
|
||||||
/** @param {EditorSection} section */
|
/** @param {EditorSection} section */
|
||||||
function fitToContent(section) {
|
function fitToContent(section) {
|
||||||
const {el, cm, cm: {display: {wrapper, sizer}}} = section;
|
const {cm, cm: {display: {wrapper, sizer}}} = section;
|
||||||
if (cm.display.renderedView) {
|
if (cm.display.renderedView) {
|
||||||
resize();
|
resize();
|
||||||
} else {
|
} else {
|
||||||
|
@ -154,12 +155,13 @@ function SectionsEditor() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (headerOffset == null) {
|
if (headerOffset == null) {
|
||||||
headerOffset = el.getBoundingClientRect().top;
|
headerOffset = container.getBoundingClientRect().top;
|
||||||
}
|
}
|
||||||
contentHeight += 9; // border & resize grip
|
contentHeight += 9; // border & resize grip
|
||||||
cm.off('update', resize);
|
cm.off('update', resize);
|
||||||
const cmHeight = wrapper.offsetHeight;
|
const cmHeight = wrapper.offsetHeight;
|
||||||
const maxHeight = (window.innerHeight - headerOffset) - (section.el.offsetHeight - cmHeight);
|
const appliesToHeight = Math.min(section.el.offsetHeight - cmHeight, window.innerHeight / 2);
|
||||||
|
const maxHeight = (window.innerHeight - headerOffset) - appliesToHeight;
|
||||||
const fit = Math.min(contentHeight, maxHeight);
|
const fit = Math.min(contentHeight, maxHeight);
|
||||||
if (Math.abs(fit - cmHeight) > 1) {
|
if (Math.abs(fit - cmHeight) > 1) {
|
||||||
cm.setSize(null, fit);
|
cm.setSize(null, fit);
|
||||||
|
@ -434,7 +436,7 @@ function SectionsEditor() {
|
||||||
/** @returns {Style} */
|
/** @returns {Style} */
|
||||||
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()),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -484,7 +486,7 @@ function SectionsEditor() {
|
||||||
livePreview.update(getModel());
|
livePreview.update(getModel());
|
||||||
}
|
}
|
||||||
|
|
||||||
function initSections(originalSections, {
|
function initSections(src, {
|
||||||
focusOn = 0,
|
focusOn = 0,
|
||||||
replace = false,
|
replace = false,
|
||||||
pristine = false,
|
pristine = false,
|
||||||
|
@ -495,27 +497,35 @@ function SectionsEditor() {
|
||||||
container.textContent = '';
|
container.textContent = '';
|
||||||
}
|
}
|
||||||
let done;
|
let done;
|
||||||
const total = originalSections.length;
|
let index = 0;
|
||||||
originalSections = originalSections.slice();
|
let y = 0;
|
||||||
|
const total = src.length;
|
||||||
|
let si = editor.scrollInfo;
|
||||||
|
if (si && si.cms && si.cms.length === src.length) {
|
||||||
|
si.scrollY2 = si.scrollY + window.innerHeight;
|
||||||
|
container.style.height = si.scrollY2 + 'px';
|
||||||
|
scrollTo(0, si.scrollY);
|
||||||
|
} else {
|
||||||
|
si = null;
|
||||||
|
}
|
||||||
return new Promise(resolve => {
|
return new Promise(resolve => {
|
||||||
done = resolve;
|
done = resolve;
|
||||||
chunk(true);
|
chunk(!si);
|
||||||
});
|
});
|
||||||
function chunk(forceRefresh) {
|
function chunk(forceRefresh) {
|
||||||
const t0 = performance.now();
|
const t0 = performance.now();
|
||||||
while (originalSections.length && performance.now() - t0 < 100) {
|
while (index < total && performance.now() - t0 < 100) {
|
||||||
insertSectionAfter(originalSections.shift(), undefined, forceRefresh);
|
if (si) forceRefresh = y < si.scrollY2 && (y += si.cms[index].parentHeight) > si.scrollY;
|
||||||
|
insertSectionAfter(src[index], undefined, forceRefresh, si && si.cms[index]);
|
||||||
if (pristine) dirty.clear();
|
if (pristine) dirty.clear();
|
||||||
if (focusOn !== false && sections[focusOn]) {
|
if (index === focusOn && !si) sections[index].cm.focus();
|
||||||
sections[focusOn].cm.focus();
|
index++;
|
||||||
focusOn = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
setGlobalProgress(total - originalSections.length, total);
|
setGlobalProgress(index, total);
|
||||||
if (!originalSections.length) {
|
if (index === total) {
|
||||||
setGlobalProgress();
|
setGlobalProgress();
|
||||||
requestAnimationFrame(fitToAvailableSpace);
|
if (!si) requestAnimationFrame(fitToAvailableSpace);
|
||||||
sections.forEach(({cm}) => setTimeout(linter.enableForEditor, 0, cm));
|
container.style.removeProperty('height');
|
||||||
done();
|
done();
|
||||||
} else {
|
} else {
|
||||||
setTimeout(chunk);
|
setTimeout(chunk);
|
||||||
|
@ -564,24 +574,26 @@ function SectionsEditor() {
|
||||||
* @param {StyleSection} [init]
|
* @param {StyleSection} [init]
|
||||||
* @param {EditorSection} [base]
|
* @param {EditorSection} [base]
|
||||||
* @param {boolean} [forceRefresh]
|
* @param {boolean} [forceRefresh]
|
||||||
|
* @param {EditorScrollInfo} [si]
|
||||||
*/
|
*/
|
||||||
function insertSectionAfter(init, base, forceRefresh) {
|
function insertSectionAfter(init, base, forceRefresh, si) {
|
||||||
if (!init) {
|
if (!init) {
|
||||||
init = {code: '', urlPrefixes: ['http://example.com']};
|
init = {code: '', urlPrefixes: ['http://example.com']};
|
||||||
}
|
}
|
||||||
const section = createSection(init, genId);
|
const section = createSection(init, genId, si);
|
||||||
const {cm} = section;
|
const {cm} = section;
|
||||||
sections.splice(base ? sections.indexOf(base) + 1 : sections.length, 0, section);
|
const index = base ? sections.indexOf(base) + 1 : sections.length;
|
||||||
|
sections.splice(index, 0, section);
|
||||||
container.insertBefore(section.el, base ? base.el.nextSibling : null);
|
container.insertBefore(section.el, base ? base.el.nextSibling : null);
|
||||||
refreshOnView(cm, forceRefresh);
|
refreshOnView(cm, base || forceRefresh);
|
||||||
registerEvents(section);
|
registerEvents(section);
|
||||||
if (!base || init.code) {
|
if ((!si || !si.height) && (!base || init.code)) {
|
||||||
// Fit a) during startup or b) when the clone button is clicked on a section with some code
|
// Fit a) during startup or b) when the clone button is clicked on a section with some code
|
||||||
fitToContent(section);
|
fitToContent(section);
|
||||||
}
|
}
|
||||||
if (base) {
|
if (base) {
|
||||||
cm.focus();
|
cm.focus();
|
||||||
setTimeout(editor.scrollToEditor, 0, cm);
|
editor.scrollToEditor(cm);
|
||||||
linter.enableForEditor(cm);
|
linter.enableForEditor(cm);
|
||||||
}
|
}
|
||||||
updateSectionOrder();
|
updateSectionOrder();
|
||||||
|
@ -646,11 +658,18 @@ function SectionsEditor() {
|
||||||
xo.observe(cm.display.wrapper);
|
xo.observe(cm.display.wrapper);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @param {IntersectionObserverEntry[]} entries */
|
||||||
function refreshOnViewListener(entries) {
|
function refreshOnViewListener(entries) {
|
||||||
for (const {isIntersecting, target} of entries) {
|
for (const e of entries) {
|
||||||
if (isIntersecting) {
|
const r = e.isIntersecting && e.intersectionRect;
|
||||||
target.CodeMirror.refresh();
|
if (r) {
|
||||||
xo.unobserve(target);
|
xo.unobserve(e.target);
|
||||||
|
const cm = e.target.CodeMirror;
|
||||||
|
if (r.bottom > 0 && r.top < window.innerHeight) {
|
||||||
|
cm.refresh();
|
||||||
|
} else {
|
||||||
|
setTimeout(() => cm.refresh());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,14 @@
|
||||||
/* global CodeMirror showHelp onDOMready $ $$ $create template t
|
/* global
|
||||||
prefs stringAsRegExp */
|
$
|
||||||
|
$$
|
||||||
|
$create
|
||||||
|
CodeMirror
|
||||||
|
onDOMready
|
||||||
|
prefs
|
||||||
|
showHelp
|
||||||
|
stringAsRegExp
|
||||||
|
t
|
||||||
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
onDOMready().then(() => {
|
onDOMready().then(() => {
|
||||||
|
@ -11,7 +20,7 @@ function showKeyMapHelp() {
|
||||||
const keyMapSorted = Object.keys(keyMap)
|
const keyMapSorted = Object.keys(keyMap)
|
||||||
.map(key => ({key, cmd: keyMap[key]}))
|
.map(key => ({key, cmd: keyMap[key]}))
|
||||||
.sort((a, b) => (a.cmd < b.cmd || (a.cmd === b.cmd && a.key < b.key) ? -1 : 1));
|
.sort((a, b) => (a.cmd < b.cmd || (a.cmd === b.cmd && a.key < b.key) ? -1 : 1));
|
||||||
const table = template.keymapHelp.cloneNode(true);
|
const table = t.template.keymapHelp.cloneNode(true);
|
||||||
const tBody = table.tBodies[0];
|
const tBody = table.tBodies[0];
|
||||||
const row = tBody.rows[0];
|
const row = tBody.rows[0];
|
||||||
const cellA = row.children[0];
|
const cellA = row.children[0];
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
MozSectionWidget
|
MozSectionWidget
|
||||||
prefs
|
prefs
|
||||||
sectionsToMozFormat
|
sectionsToMozFormat
|
||||||
|
sessionStore
|
||||||
t
|
t
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -74,6 +75,7 @@ function SourceEditor() {
|
||||||
'editor.appliesToLineWidget': (k, val) => sectionWidget.toggle(val),
|
'editor.appliesToLineWidget': (k, val) => sectionWidget.toggle(val),
|
||||||
'editor.toc.expanded': (k, val) => sectionFinder.onOff(editor.updateToc, val),
|
'editor.toc.expanded': (k, val) => sectionFinder.onOff(editor.updateToc, val),
|
||||||
}, {now: true});
|
}, {now: true});
|
||||||
|
editor.applyScrollInfo(cm);
|
||||||
cm.clearHistory();
|
cm.clearHistory();
|
||||||
cm.markClean();
|
cm.markClean();
|
||||||
savedGeneration = cm.changeGeneration();
|
savedGeneration = cm.changeGeneration();
|
||||||
|
@ -89,7 +91,6 @@ function SourceEditor() {
|
||||||
linter.run();
|
linter.run();
|
||||||
updateLinterSwitch();
|
updateLinterSwitch();
|
||||||
});
|
});
|
||||||
debounce(linter.enableForEditor, 0, cm);
|
|
||||||
if (!$.isTextInput(document.activeElement)) {
|
if (!$.isTextInput(document.activeElement)) {
|
||||||
cm.focus();
|
cm.focus();
|
||||||
}
|
}
|
||||||
|
@ -98,7 +99,7 @@ function SourceEditor() {
|
||||||
return API.buildUsercss({
|
return API.buildUsercss({
|
||||||
styleId: style.id,
|
styleId: style.id,
|
||||||
sourceCode: style.sourceCode,
|
sourceCode: style.sourceCode,
|
||||||
assignVars: true
|
assignVars: true,
|
||||||
})
|
})
|
||||||
.then(({style: newStyle}) => {
|
.then(({style: newStyle}) => {
|
||||||
delete newStyle.enabled;
|
delete newStyle.enabled;
|
||||||
|
@ -217,7 +218,7 @@ function SourceEditor() {
|
||||||
if (style.id !== newStyle.id) {
|
if (style.id !== newStyle.id) {
|
||||||
history.replaceState({}, '', `?id=${newStyle.id}`);
|
history.replaceState({}, '', `?id=${newStyle.id}`);
|
||||||
}
|
}
|
||||||
sessionStorage.justEditedStyleId = newStyle.id;
|
sessionStore.justEditedStyleId = newStyle.id;
|
||||||
Object.assign(style, newStyle);
|
Object.assign(style, newStyle);
|
||||||
$('#preview-label').classList.remove('hidden');
|
$('#preview-label').classList.remove('hidden');
|
||||||
updateMeta();
|
updateMeta();
|
||||||
|
|
|
@ -214,6 +214,6 @@ function createHotkeyInput(prefId, onDone = () => {}) {
|
||||||
},
|
},
|
||||||
onpaste(event) {
|
onpaste(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,6 +142,7 @@ select {
|
||||||
transition: color .5s;
|
transition: color .5s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.select-wrapper,
|
||||||
.select-resizer {
|
.select-resizer {
|
||||||
display: inline-flex!important;
|
display: inline-flex!important;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
if (theme !== 'default') {
|
if (theme !== 'default') {
|
||||||
document.head.appendChild($create('link', {
|
document.head.appendChild($create('link', {
|
||||||
rel: 'stylesheet',
|
rel: 'stylesheet',
|
||||||
href: `vendor/codemirror/theme/${theme}.css`
|
href: `vendor/codemirror/theme/${theme}.css`,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
window.addEventListener('resize', adjustCodeHeight);
|
window.addEventListener('resize', adjustCodeHeight);
|
||||||
|
@ -111,7 +111,7 @@
|
||||||
frag.appendChild($createLink(url,
|
frag.appendChild($createLink(url,
|
||||||
$create('SVG:svg.svg-icon', {viewBox: '0 0 20 20'},
|
$create('SVG:svg.svg-icon', {viewBox: '0 0 20 20'},
|
||||||
$create('SVG:path', {
|
$create('SVG:path', {
|
||||||
d: 'M4,4h5v2H6v8h8v-3h2v5H4V4z M11,3h6v6l-2-2l-4,4L9,9l4-4L11,3z'
|
d: 'M4,4h5v2H6v8h8v-3h2v5H4V4z M11,3h6v6l-2-2l-4,4L9,9l4-4L11,3z',
|
||||||
}))
|
}))
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
@ -130,7 +130,7 @@
|
||||||
$create('li',
|
$create('li',
|
||||||
$createLink(...args)
|
$createLink(...args)
|
||||||
)
|
)
|
||||||
))
|
)),
|
||||||
]));
|
]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,7 @@ function createCache({size = 1000, onDeleted} = {}) {
|
||||||
},
|
},
|
||||||
get size() {
|
get size() {
|
||||||
return map.size;
|
return map.size;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function get(id) {
|
function get(id) {
|
||||||
|
|
|
@ -296,7 +296,7 @@ function $createLink(href = '', content) {
|
||||||
const opt = {
|
const opt = {
|
||||||
tag: 'a',
|
tag: 'a',
|
||||||
target: '_blank',
|
target: '_blank',
|
||||||
rel: 'noopener'
|
rel: 'noopener',
|
||||||
};
|
};
|
||||||
if (typeof href === 'object') {
|
if (typeof href === 'object') {
|
||||||
Object.assign(opt, href);
|
Object.assign(opt, href);
|
||||||
|
|
|
@ -1,149 +1,16 @@
|
||||||
/* global tryCatch */
|
|
||||||
/* exported tHTML formatDate */
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const template = {};
|
|
||||||
tDocLoader();
|
|
||||||
|
|
||||||
|
|
||||||
function t(key, params) {
|
function t(key, params) {
|
||||||
const cache = !params && t.cache[key];
|
const s = chrome.i18n.getMessage(key, params);
|
||||||
const s = cache || chrome.i18n.getMessage(key, params);
|
if (!s) throw `Missing string "${key}"`;
|
||||||
if (s === '') {
|
|
||||||
throw `Missing string "${key}"`;
|
|
||||||
}
|
|
||||||
if (!params && !cache) {
|
|
||||||
t.cache[key] = s;
|
|
||||||
}
|
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Object.assign(t, {
|
||||||
function tHTML(html, tag) {
|
template: {},
|
||||||
// body is a text node without HTML tags
|
DOMParser: new DOMParser(),
|
||||||
if (typeof html === 'string' && !tag && /<\w+/.test(html) === false) {
|
ALLOWED_TAGS: 'a,b,code,i,sub,sup,wbr'.split(','),
|
||||||
return document.createTextNode(html);
|
RX_WORD_BREAK: new RegExp([
|
||||||
}
|
|
||||||
if (typeof html === 'string') {
|
|
||||||
// spaces are removed; use for an explicit space
|
|
||||||
html = html.replace(/>\s+</g, '><').trim();
|
|
||||||
if (tag) {
|
|
||||||
html = `<${tag}>${html}</${tag}>`;
|
|
||||||
}
|
|
||||||
const body = t.DOMParser.parseFromString(html, 'text/html').body;
|
|
||||||
if (html.includes('i18n-')) {
|
|
||||||
tNodeList(body.getElementsByTagName('*'));
|
|
||||||
}
|
|
||||||
// the html string may contain more than one top-level node
|
|
||||||
if (!body.childNodes[1]) {
|
|
||||||
return body.firstChild;
|
|
||||||
}
|
|
||||||
const fragment = document.createDocumentFragment();
|
|
||||||
while (body.firstChild) {
|
|
||||||
fragment.appendChild(body.firstChild);
|
|
||||||
}
|
|
||||||
return fragment;
|
|
||||||
}
|
|
||||||
return html;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function tNodeList(nodes) {
|
|
||||||
const PREFIX = 'i18n-';
|
|
||||||
|
|
||||||
for (let n = nodes.length; --n >= 0;) {
|
|
||||||
const node = nodes[n];
|
|
||||||
if (node.nodeType !== Node.ELEMENT_NODE) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (node.localName === 'template') {
|
|
||||||
createTemplate(node);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
for (let a = node.attributes.length; --a >= 0;) {
|
|
||||||
const attr = node.attributes[a];
|
|
||||||
const name = attr.nodeName;
|
|
||||||
if (!name.startsWith(PREFIX)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const type = name.substr(PREFIX.length);
|
|
||||||
const value = t(attr.value);
|
|
||||||
let toInsert, before;
|
|
||||||
switch (type) {
|
|
||||||
case 'word-break':
|
|
||||||
// we already know that: hasWordBreak
|
|
||||||
break;
|
|
||||||
case 'text':
|
|
||||||
before = node.firstChild;
|
|
||||||
// fallthrough to text-append
|
|
||||||
case 'text-append':
|
|
||||||
toInsert = createText(value);
|
|
||||||
break;
|
|
||||||
case 'html': {
|
|
||||||
toInsert = createHtml(value);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
node.setAttribute(type, value);
|
|
||||||
}
|
|
||||||
tDocLoader.pause();
|
|
||||||
if (toInsert) {
|
|
||||||
node.insertBefore(toInsert, before || null);
|
|
||||||
}
|
|
||||||
node.removeAttribute(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createTemplate(node) {
|
|
||||||
const elements = node.content.querySelectorAll('*');
|
|
||||||
tNodeList(elements);
|
|
||||||
template[node.dataset.id] = elements[0];
|
|
||||||
// compress inter-tag whitespace to reduce number of DOM nodes by 25%
|
|
||||||
const walker = document.createTreeWalker(elements[0], NodeFilter.SHOW_TEXT);
|
|
||||||
const toRemove = [];
|
|
||||||
while (walker.nextNode()) {
|
|
||||||
const textNode = walker.currentNode;
|
|
||||||
if (!textNode.nodeValue.trim()) {
|
|
||||||
toRemove.push(textNode);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tDocLoader.pause();
|
|
||||||
toRemove.forEach(el => el.remove());
|
|
||||||
}
|
|
||||||
|
|
||||||
function createText(str) {
|
|
||||||
return document.createTextNode(tWordBreak(str));
|
|
||||||
}
|
|
||||||
|
|
||||||
function createHtml(value) {
|
|
||||||
// <a href=foo>bar</a> are the only recognizable HTML elements
|
|
||||||
const rx = /(?:<a\s([^>]*)>([^<]*)<\/a>)?([^<]*)/gi;
|
|
||||||
const bin = document.createDocumentFragment();
|
|
||||||
for (let m; (m = rx.exec(value)) && m[0];) {
|
|
||||||
const [, linkParams, linkText, nextText] = m;
|
|
||||||
if (linkText) {
|
|
||||||
const href = /\bhref\s*=\s*(\S+)/.exec(linkParams);
|
|
||||||
const a = bin.appendChild(document.createElement('a'));
|
|
||||||
a.href = href && href[1].replace(/^(["'])(.*)\1$/, '$2') || '';
|
|
||||||
a.appendChild(createText(linkText));
|
|
||||||
}
|
|
||||||
if (nextText) {
|
|
||||||
bin.appendChild(createText(nextText));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return bin;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function tDocLoader() {
|
|
||||||
t.DOMParser = new DOMParser();
|
|
||||||
t.cache = (() => {
|
|
||||||
try {
|
|
||||||
return JSON.parse(localStorage.L10N);
|
|
||||||
} catch (e) {}
|
|
||||||
})() || {};
|
|
||||||
t.RX_WORD_BREAK = new RegExp([
|
|
||||||
'(',
|
'(',
|
||||||
/[\d\w\u007B-\uFFFF]{10}/,
|
/[\d\w\u007B-\uFFFF]{10}/,
|
||||||
'|',
|
'|',
|
||||||
|
@ -152,73 +19,171 @@ function tDocLoader() {
|
||||||
/((?!\s)\W){10}/,
|
/((?!\s)\W){10}/,
|
||||||
')',
|
')',
|
||||||
/(?!\b|\s|$)/,
|
/(?!\b|\s|$)/,
|
||||||
].map(rx => rx.source || rx).join(''), 'g');
|
].map(rx => rx.source || rx).join(''), 'g'),
|
||||||
|
|
||||||
// reset L10N cache on UI language change
|
HTML(html) {
|
||||||
const UIlang = chrome.i18n.getUILanguage();
|
return typeof html !== 'string'
|
||||||
if (t.cache.browserUIlanguage !== UIlang) {
|
? html
|
||||||
t.cache = {browserUIlanguage: UIlang};
|
: /<\w+/.test(html) // check for html tags
|
||||||
localStorage.L10N = JSON.stringify(t.cache);
|
? t.createHtml(html.replace(/>\n\s*</g, '><').trim())
|
||||||
}
|
: document.createTextNode(html);
|
||||||
const cacheLength = Object.keys(t.cache).length;
|
},
|
||||||
|
|
||||||
Object.assign(tDocLoader, {
|
NodeList(nodes) {
|
||||||
observer: new MutationObserver(process),
|
const PREFIX = 'i18n-';
|
||||||
start() {
|
for (let n = nodes.length; --n >= 0;) {
|
||||||
if (!tDocLoader.observing) {
|
const node = nodes[n];
|
||||||
tDocLoader.observing = true;
|
if (node.nodeType !== Node.ELEMENT_NODE) {
|
||||||
tDocLoader.observer.observe(document, {subtree: true, childList: true});
|
continue;
|
||||||
}
|
}
|
||||||
},
|
if (node.localName === 'template') {
|
||||||
stop() {
|
t.createTemplate(node);
|
||||||
tDocLoader.pause();
|
continue;
|
||||||
document.removeEventListener('DOMContentLoaded', onLoad);
|
}
|
||||||
},
|
for (let a = node.attributes.length; --a >= 0;) {
|
||||||
pause() {
|
const attr = node.attributes[a];
|
||||||
if (tDocLoader.observing) {
|
const name = attr.nodeName;
|
||||||
tDocLoader.observing = false;
|
if (!name.startsWith(PREFIX)) {
|
||||||
tDocLoader.observer.disconnect();
|
continue;
|
||||||
|
}
|
||||||
|
const type = name.substr(PREFIX.length);
|
||||||
|
const value = t(attr.value);
|
||||||
|
let toInsert, before;
|
||||||
|
switch (type) {
|
||||||
|
case 'word-break':
|
||||||
|
// we already know that: hasWordBreak
|
||||||
|
break;
|
||||||
|
case 'text':
|
||||||
|
before = node.firstChild;
|
||||||
|
// fallthrough to text-append
|
||||||
|
case 'text-append':
|
||||||
|
toInsert = t.createText(value);
|
||||||
|
break;
|
||||||
|
case 'html': {
|
||||||
|
toInsert = t.createHtml(value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
node.setAttribute(type, value);
|
||||||
|
}
|
||||||
|
t.stopObserver();
|
||||||
|
if (toInsert) {
|
||||||
|
node.insertBefore(toInsert, before || null);
|
||||||
|
}
|
||||||
|
node.removeAttribute(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Adds soft hyphens every 10 characters to ensure the long words break before breaking the layout */
|
||||||
|
breakWord(text) {
|
||||||
|
return text.length <= 10 ? text :
|
||||||
|
text.replace(t.RX_WORD_BREAK, '$&\u00AD');
|
||||||
|
},
|
||||||
|
|
||||||
|
createTemplate(node) {
|
||||||
|
const elements = node.content.querySelectorAll('*');
|
||||||
|
t.NodeList(elements);
|
||||||
|
t.template[node.dataset.id] = elements[0];
|
||||||
|
// compress inter-tag whitespace to reduce number of DOM nodes by 25%
|
||||||
|
const walker = document.createTreeWalker(elements[0], NodeFilter.SHOW_TEXT);
|
||||||
|
const toRemove = [];
|
||||||
|
while (walker.nextNode()) {
|
||||||
|
const textNode = walker.currentNode;
|
||||||
|
if (!/[\xA0\S]/.test(textNode.nodeValue)) { // allow \xA0 to keep
|
||||||
|
toRemove.push(textNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.stopObserver();
|
||||||
|
toRemove.forEach(el => el.remove());
|
||||||
|
},
|
||||||
|
|
||||||
|
createText(str) {
|
||||||
|
return document.createTextNode(t.breakWord(str));
|
||||||
|
},
|
||||||
|
|
||||||
|
createHtml(str, trusted) {
|
||||||
|
const root = t.DOMParser.parseFromString(str, 'text/html').body;
|
||||||
|
if (!trusted) {
|
||||||
|
t.sanitizeHtml(root);
|
||||||
|
} else if (str.includes('i18n-')) {
|
||||||
|
t.NodeList(root.getElementsByTagName('*'));
|
||||||
|
}
|
||||||
|
const bin = document.createDocumentFragment();
|
||||||
|
while (root.firstChild) {
|
||||||
|
bin.appendChild(root.firstChild);
|
||||||
|
}
|
||||||
|
return bin;
|
||||||
|
},
|
||||||
|
|
||||||
|
sanitizeHtml(root) {
|
||||||
|
const toRemove = [];
|
||||||
|
const walker = document.createTreeWalker(root);
|
||||||
|
for (let n; (n = walker.nextNode());) {
|
||||||
|
if (n.nodeType === Node.TEXT_NODE) {
|
||||||
|
n.nodeValue = t.breakWord(n.nodeValue);
|
||||||
|
} else if (t.ALLOWED_TAGS.includes(n.localName)) {
|
||||||
|
for (const attr of n.attributes) {
|
||||||
|
if (n.localName !== 'a' || attr.localName !== 'href' || !/^https?:/.test(n.href)) {
|
||||||
|
n.removeAttribute(attr.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
toRemove.push(n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const n of toRemove) {
|
||||||
|
const parent = n.parentNode;
|
||||||
|
if (parent) parent.removeChild(n); // not using .remove() as there may be a non-element
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
formatDate(date) {
|
||||||
|
if (!date) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const newDate = new Date(Number(date) || date);
|
||||||
|
const string = newDate.toLocaleDateString([chrome.i18n.getUILanguage(), 'en'], {
|
||||||
|
day: '2-digit',
|
||||||
|
month: 'short',
|
||||||
|
year: newDate.getYear() === new Date().getYear() ? undefined : '2-digit',
|
||||||
|
});
|
||||||
|
return string === 'Invalid Date' ? '' : string;
|
||||||
|
} catch (e) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
(() => {
|
||||||
|
const observer = new MutationObserver(process);
|
||||||
|
let observing = false;
|
||||||
|
Object.assign(t, {
|
||||||
|
stopObserver() {
|
||||||
|
if (observing) {
|
||||||
|
observing = false;
|
||||||
|
observer.disconnect();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
process(observer.takeRecords());
|
||||||
|
t.stopObserver();
|
||||||
|
}, {once: true});
|
||||||
|
|
||||||
tNodeList(document.getElementsByTagName('*'));
|
t.NodeList(document.getElementsByTagName('*'));
|
||||||
tDocLoader.start();
|
start();
|
||||||
document.addEventListener('DOMContentLoaded', onLoad);
|
|
||||||
|
|
||||||
function process(mutations) {
|
function process(mutations) {
|
||||||
for (const mutation of mutations) {
|
mutations.forEach(m => t.NodeList(m.addedNodes));
|
||||||
tNodeList(mutation.addedNodes);
|
start();
|
||||||
}
|
|
||||||
tDocLoader.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onLoad() {
|
function start() {
|
||||||
document.removeEventListener('DOMContentLoaded', onLoad);
|
if (!observing) {
|
||||||
process(tDocLoader.observer.takeRecords());
|
observing = true;
|
||||||
tDocLoader.stop();
|
observer.observe(document, {subtree: true, childList: true});
|
||||||
if (cacheLength !== Object.keys(t.cache).length) {
|
|
||||||
localStorage.L10N = JSON.stringify(t.cache);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})();
|
||||||
|
|
||||||
|
|
||||||
function tWordBreak(text) {
|
|
||||||
// adds soft hyphens every 10 characters to ensure the long words break before breaking the layout
|
|
||||||
return text.length <= 10 ? text :
|
|
||||||
text.replace(t.RX_WORD_BREAK, '$&\u00AD');
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function formatDate(date) {
|
|
||||||
return !date ? '' : tryCatch(() => {
|
|
||||||
const newDate = new Date(Number(date) || date);
|
|
||||||
const string = newDate.toLocaleDateString([t.cache.browserUIlanguage, 'en'], {
|
|
||||||
day: '2-digit',
|
|
||||||
month: 'short',
|
|
||||||
year: newDate.getYear() === new Date().getYear() ? undefined : '2-digit',
|
|
||||||
});
|
|
||||||
return string === 'Invalid Date' ? '' : string;
|
|
||||||
}) || '';
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,20 @@
|
||||||
/* exported getTab getActiveTab onTabReady stringAsRegExp openURL ignoreChromeError
|
/* exported
|
||||||
getStyleWithNoCode tryRegExp sessionStorageHash download deepEqual
|
capitalize
|
||||||
closeCurrentTab capitalize CHROME_HAS_BORDER_BUG */
|
CHROME_HAS_BORDER_BUG
|
||||||
|
closeCurrentTab
|
||||||
|
deepEqual
|
||||||
|
download
|
||||||
|
getActiveTab
|
||||||
|
getStyleWithNoCode
|
||||||
|
getTab
|
||||||
|
ignoreChromeError
|
||||||
|
onTabReady
|
||||||
|
openURL
|
||||||
|
sessionStore
|
||||||
|
stringAsRegExp
|
||||||
|
tryCatch
|
||||||
|
tryRegExp
|
||||||
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const CHROME = Boolean(chrome.app) && parseInt(navigator.userAgent.match(/Chrom\w+\/(\d+)|$/)[1]);
|
const CHROME = Boolean(chrome.app) && parseInt(navigator.userAgent.match(/Chrom\w+\/(\d+)|$/)[1]);
|
||||||
|
@ -112,7 +126,7 @@ function urlToMatchPattern(url, ignoreSearch) {
|
||||||
if (ignoreSearch) {
|
if (ignoreSearch) {
|
||||||
return [
|
return [
|
||||||
`${url.protocol}//${url.hostname}/${url.pathname}`,
|
`${url.protocol}//${url.hostname}/${url.pathname}`,
|
||||||
`${url.protocol}//${url.hostname}/${url.pathname}?*`
|
`${url.protocol}//${url.hostname}/${url.pathname}?*`,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
// FIXME: is %2f allowed in pathname and search?
|
// FIXME: is %2f allowed in pathname and search?
|
||||||
|
@ -206,7 +220,7 @@ function activateTab(tab, {url, index, openerTabId} = {}) {
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
browser.tabs.update(tab.id, options),
|
browser.tabs.update(tab.id, options),
|
||||||
browser.windows && browser.windows.update(tab.windowId, {focused: true}),
|
browser.windows && browser.windows.update(tab.windowId, {focused: true}),
|
||||||
index != null && browser.tabs.move(tab.id, {index})
|
index != null && browser.tabs.move(tab.id, {index}),
|
||||||
])
|
])
|
||||||
.then(() => tab);
|
.then(() => tab);
|
||||||
}
|
}
|
||||||
|
@ -316,24 +330,28 @@ function deepEqual(a, b, ignoredKeys) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* A simple polyfill in case DOM storage is disabled in the browser */
|
||||||
function sessionStorageHash(name) {
|
const sessionStore = new Proxy({}, {
|
||||||
return {
|
get(target, name) {
|
||||||
name,
|
try {
|
||||||
value: tryCatch(JSON.parse, sessionStorage[name]) || {},
|
return sessionStorage[name];
|
||||||
set(k, v) {
|
} catch (e) {
|
||||||
this.value[k] = v;
|
Object.defineProperty(window, 'sessionStorage', {value: target});
|
||||||
this.updateStorage();
|
|
||||||
},
|
|
||||||
unset(k) {
|
|
||||||
delete this.value[k];
|
|
||||||
this.updateStorage();
|
|
||||||
},
|
|
||||||
updateStorage() {
|
|
||||||
sessionStorage[this.name] = JSON.stringify(this.value);
|
|
||||||
}
|
}
|
||||||
};
|
},
|
||||||
}
|
set(target, name, value, proxy) {
|
||||||
|
try {
|
||||||
|
sessionStorage[name] = `${value}`;
|
||||||
|
} catch (e) {
|
||||||
|
proxy[name]; // eslint-disable-line no-unused-expressions
|
||||||
|
target[name] = `${value}`;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
deleteProperty(target, name) {
|
||||||
|
return delete target[name];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {String} url
|
* @param {String} url
|
||||||
|
|
|
@ -12,17 +12,17 @@ const metaParser = (() => {
|
||||||
throw new ParseError({
|
throw new ParseError({
|
||||||
code: 'unknownPreprocessor',
|
code: 'unknownPreprocessor',
|
||||||
args: [state.value],
|
args: [state.value],
|
||||||
index: state.valueIndex
|
index: state.valueIndex,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
},
|
||||||
validateVar: {
|
validateVar: {
|
||||||
select: state => {
|
select: state => {
|
||||||
if (state.varResult.options.every(o => o.name !== state.value)) {
|
if (state.varResult.options.every(o => o.name !== state.value)) {
|
||||||
throw new ParseError({
|
throw new ParseError({
|
||||||
code: 'invalidSelectValueMismatch',
|
code: 'invalidSelectValueMismatch',
|
||||||
index: state.valueIndex
|
index: state.valueIndex,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -32,19 +32,19 @@ const metaParser = (() => {
|
||||||
throw new ParseError({
|
throw new ParseError({
|
||||||
code: 'invalidColor',
|
code: 'invalidColor',
|
||||||
args: [state.value],
|
args: [state.value],
|
||||||
index: state.valueIndex
|
index: state.valueIndex,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
state.value = colorConverter.format(color, 'rgb');
|
state.value = colorConverter.format(color, 'rgb');
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
const parser = createParser(options);
|
const parser = createParser(options);
|
||||||
const looseParser = createParser(Object.assign({}, options, {allowErrors: true, unknownKey: 'throw'}));
|
const looseParser = createParser(Object.assign({}, options, {allowErrors: true, unknownKey: 'throw'}));
|
||||||
return {
|
return {
|
||||||
parse,
|
parse,
|
||||||
lint,
|
lint,
|
||||||
nullifyInvalidVars
|
nullifyInvalidVars,
|
||||||
};
|
};
|
||||||
|
|
||||||
function parse(text, indexOffset) {
|
function parse(text, indexOffset) {
|
||||||
|
|
|
@ -66,15 +66,6 @@ self.INJECTED !== 1 && (() => {
|
||||||
|
|
||||||
//#region for our extension pages
|
//#region for our extension pages
|
||||||
|
|
||||||
for (const storage of ['localStorage', 'sessionStorage']) {
|
|
||||||
try {
|
|
||||||
window[storage]._access_check = 1;
|
|
||||||
delete window[storage]._access_check;
|
|
||||||
} catch (err) {
|
|
||||||
Object.defineProperty(window, storage, {value: {}});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(new URLSearchParams({foo: 1})).get('foo')) {
|
if (!(new URLSearchParams({foo: 1})).get('foo')) {
|
||||||
// TODO: remove when minimum_chrome_version >= 61
|
// TODO: remove when minimum_chrome_version >= 61
|
||||||
window.URLSearchParams = class extends URLSearchParams {
|
window.URLSearchParams = class extends URLSearchParams {
|
||||||
|
|
|
@ -15,6 +15,7 @@ window.INJECTED !== 1 && (() => {
|
||||||
'exposeIframes': false, // Add 'stylus-iframe' attribute to HTML element in all iframes
|
'exposeIframes': false, // Add 'stylus-iframe' attribute to HTML element in all iframes
|
||||||
'newStyleAsUsercss': false, // create new style in usercss format
|
'newStyleAsUsercss': false, // create new style in usercss format
|
||||||
'styleViaXhr': false, // early style injection to avoid FOUC
|
'styleViaXhr': false, // early style injection to avoid FOUC
|
||||||
|
'patchCsp': false, // add data: and popular image hosting sites to strict CSP
|
||||||
|
|
||||||
// checkbox in style config dialog
|
// checkbox in style config dialog
|
||||||
'config.autosave': true,
|
'config.autosave': true,
|
||||||
|
|
|
@ -12,7 +12,12 @@ const usercss = (() => {
|
||||||
};
|
};
|
||||||
const RX_META = /\/\*!?\s*==userstyle==[\s\S]*?==\/userstyle==\s*\*\//i;
|
const RX_META = /\/\*!?\s*==userstyle==[\s\S]*?==\/userstyle==\s*\*\//i;
|
||||||
const ERR_ARGS_IS_LIST = new Set(['missingMandatory', 'missingChar']);
|
const ERR_ARGS_IS_LIST = new Set(['missingMandatory', 'missingChar']);
|
||||||
return {buildMeta, buildCode, assignVars};
|
return {
|
||||||
|
RX_META,
|
||||||
|
buildMeta,
|
||||||
|
buildCode,
|
||||||
|
assignVars,
|
||||||
|
};
|
||||||
|
|
||||||
function buildMeta(sourceCode) {
|
function buildMeta(sourceCode) {
|
||||||
sourceCode = sourceCode.replace(/\r\n?/g, '\n');
|
sourceCode = sourceCode.replace(/\r\n?/g, '\n');
|
||||||
|
@ -20,7 +25,7 @@ const usercss = (() => {
|
||||||
const style = {
|
const style = {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
sourceCode,
|
sourceCode,
|
||||||
sections: []
|
sections: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
const match = sourceCode.match(RX_META);
|
const match = sourceCode.match(RX_META);
|
||||||
|
|
|
@ -67,7 +67,7 @@ const workerUtil = {
|
||||||
message: err.message,
|
message: err.message,
|
||||||
lineNumber: err.lineNumber,
|
lineNumber: err.lineNumber,
|
||||||
columnNumber: err.columnNumber,
|
columnNumber: err.columnNumber,
|
||||||
fileName: err.fileName
|
fileName: err.fileName,
|
||||||
}, err);
|
}, err);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
35
manage.html
35
manage.html
|
@ -11,8 +11,6 @@
|
||||||
<link rel="stylesheet" href="options/onoffswitch.css">
|
<link rel="stylesheet" href="options/onoffswitch.css">
|
||||||
<link rel="stylesheet" href="vendor-overwrites/colorpicker/colorpicker.css">
|
<link rel="stylesheet" href="vendor-overwrites/colorpicker/colorpicker.css">
|
||||||
|
|
||||||
<style id="style-overrides"></style>
|
|
||||||
|
|
||||||
<style id="firefox-transitions-bug-suppressor">
|
<style id="firefox-transitions-bug-suppressor">
|
||||||
/* restrict to FF */
|
/* restrict to FF */
|
||||||
@supports (-moz-appearance:none) {
|
@supports (-moz-appearance:none) {
|
||||||
|
@ -32,7 +30,10 @@
|
||||||
<template data-id="style">
|
<template data-id="style">
|
||||||
<div class="entry">
|
<div class="entry">
|
||||||
<h2 class="style-name">
|
<h2 class="style-name">
|
||||||
<a class="style-name-link"></a>
|
<a class="style-name-link">
|
||||||
|
|
||||||
|
<span class="style-info" data-type="version"></span>
|
||||||
|
</a>
|
||||||
<a target="_blank" class="homepage"></a>
|
<a target="_blank" class="homepage"></a>
|
||||||
</h2>
|
</h2>
|
||||||
<p class="applies-to">
|
<p class="applies-to">
|
||||||
|
@ -54,14 +55,17 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template data-id="styleCompact">
|
<template data-id="styleNewUI">
|
||||||
<div class="entry">
|
<div class="entry">
|
||||||
<h2 class="style-name">
|
<h2 class="style-name">
|
||||||
<div class="checkmate">
|
<div class="checkmate">
|
||||||
<input class="checker" type="checkbox" i18n-title="toggleStyle">
|
<input class="checker" type="checkbox" i18n-title="toggleStyle">
|
||||||
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
|
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
|
||||||
</div>
|
</div>
|
||||||
<a class="style-name-link"></a>
|
<a class="style-name-link">
|
||||||
|
|
||||||
|
<span class="style-info" data-type="version"></span>
|
||||||
|
</a>
|
||||||
</h2>
|
</h2>
|
||||||
<p class="actions">
|
<p class="actions">
|
||||||
<a target="_blank" class="homepage" tabindex="0"></a>
|
<a target="_blank" class="homepage" tabindex="0"></a>
|
||||||
|
@ -72,6 +76,7 @@
|
||||||
</svg>
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
</p>
|
</p>
|
||||||
|
<p class="style-info" data-type="age"></p>
|
||||||
<div class="applies-to">
|
<div class="applies-to">
|
||||||
<div class="targets"></div>
|
<div class="targets"></div>
|
||||||
<a href="#" class="expander" tabindex="0">...</a>
|
<a href="#" class="expander" tabindex="0">...</a>
|
||||||
|
@ -260,9 +265,19 @@
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<div id="search-wrapper">
|
<div id="search-wrapper">
|
||||||
<input id="search" type="search" i18n-placeholder="searchStyles" spellcheck="false"
|
<input id="search" type="search" i18n-placeholder="search" spellcheck="false"
|
||||||
data-filter=":not(.not-matching)"
|
data-filter=":not(.not-matching)"
|
||||||
data-filter-hide=".not-matching">
|
data-filter-hide=".not-matching">
|
||||||
|
<div class="select-wrapper">
|
||||||
|
<select id="searchMode">
|
||||||
|
<option i18n-text="searchStylesName" value="name"></option>
|
||||||
|
<option i18n-text="searchStylesMeta" value="meta" selected></option>
|
||||||
|
<option i18n-text="searchStylesCode" value="code"></option>
|
||||||
|
<option i18n-text="searchStylesMatchUrl" value="url"></option>
|
||||||
|
<option i18n-text="searchStylesAll" value="all"></option>
|
||||||
|
</select>
|
||||||
|
<svg class="svg-icon select-arrow"><use xlink:href="#svg-icon-select-arrow"/></svg>
|
||||||
|
</div>
|
||||||
<a href="#" id="search-help" tabindex="0">
|
<a href="#" id="search-help" tabindex="0">
|
||||||
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
|
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
|
||||||
</a>
|
</a>
|
||||||
|
|
|
@ -1,5 +1,15 @@
|
||||||
/* global messageBox deepCopy $create $createLink $ t tWordBreak
|
/* global
|
||||||
prefs setupLivePrefs debounce API */
|
$
|
||||||
|
$create
|
||||||
|
$createLink
|
||||||
|
API
|
||||||
|
debounce
|
||||||
|
deepCopy
|
||||||
|
messageBox
|
||||||
|
prefs
|
||||||
|
setupLivePrefs
|
||||||
|
t
|
||||||
|
*/
|
||||||
/* exported configDialog */
|
/* exported configDialog */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
@ -28,7 +38,7 @@ function configDialog(style) {
|
||||||
contents: [
|
contents: [
|
||||||
$create('.config-heading', data.supportURL &&
|
$create('.config-heading', data.supportURL &&
|
||||||
$createLink({className: '.external-support', href: data.supportURL}, t('externalFeedback'))),
|
$createLink({className: '.external-support', href: data.supportURL}, t('externalFeedback'))),
|
||||||
$create('.config-body', elements)
|
$create('.config-body', elements),
|
||||||
],
|
],
|
||||||
buttons: [{
|
buttons: [{
|
||||||
textContent: t('confirmSave'),
|
textContent: t('confirmSave'),
|
||||||
|
@ -210,8 +220,8 @@ function configDialog(style) {
|
||||||
$create('SVG:polygon', {
|
$create('SVG:polygon', {
|
||||||
points: '16.2,5.5 14.5,3.8 10,8.3 5.5,3.8 3.8,5.5 8.3,10 3.8,14.5 ' +
|
points: '16.2,5.5 14.5,3.8 10,8.3 5.5,3.8 3.8,5.5 8.3,10 3.8,14.5 ' +
|
||||||
'5.5,16.2 10,11.7 14.5,16.2 16.2,14.5 11.7,10',
|
'5.5,16.2 10,11.7 14.5,16.2 16.2,14.5 11.7,10',
|
||||||
})
|
}),
|
||||||
])
|
]),
|
||||||
]);
|
]);
|
||||||
for (const va of vars) {
|
for (const va of vars) {
|
||||||
let children;
|
let children;
|
||||||
|
@ -222,7 +232,7 @@ function configDialog(style) {
|
||||||
va.input = $create('a.color-swatch', {
|
va.input = $create('a.color-swatch', {
|
||||||
va,
|
va,
|
||||||
href: '#',
|
href: '#',
|
||||||
onclick: showColorpicker
|
onclick: showColorpicker,
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
];
|
];
|
||||||
|
@ -268,7 +278,7 @@ function configDialog(style) {
|
||||||
onblur: va.type === 'number' ? updateVarOnBlur : null,
|
onblur: va.type === 'number' ? updateVarOnBlur : null,
|
||||||
onchange: updateVarOnChange,
|
onchange: updateVarOnChange,
|
||||||
oninput: updateVarOnInput,
|
oninput: updateVarOnInput,
|
||||||
required: true
|
required: true,
|
||||||
};
|
};
|
||||||
if (typeof va.min === 'number') {
|
if (typeof va.min === 'number') {
|
||||||
options.min = va.min;
|
options.min = va.min;
|
||||||
|
@ -281,7 +291,7 @@ function configDialog(style) {
|
||||||
}
|
}
|
||||||
children = [
|
children = [
|
||||||
va.type === 'range' && $create('span.current-value'),
|
va.type === 'range' && $create('span.current-value'),
|
||||||
va.input = $create('input.config-value', options)
|
va.input = $create('input.config-value', options),
|
||||||
];
|
];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -305,7 +315,7 @@ function configDialog(style) {
|
||||||
|
|
||||||
elements.push(
|
elements.push(
|
||||||
$create(`label.config-${va.type}`, [
|
$create(`label.config-${va.type}`, [
|
||||||
$create('span.config-name', tWordBreak(va.label)),
|
$create('span.config-name', t.breakWord(va.label)),
|
||||||
...children,
|
...children,
|
||||||
resetter,
|
resetter,
|
||||||
]));
|
]));
|
||||||
|
|
|
@ -11,8 +11,9 @@ const filtersSelector = {
|
||||||
|
|
||||||
let initialized = false;
|
let initialized = false;
|
||||||
|
|
||||||
router.watch({search: ['search']}, ([search]) => {
|
router.watch({search: ['search', 'searchMode']}, ([search, mode]) => {
|
||||||
$('#search').value = search || '';
|
$('#search').value = search || '';
|
||||||
|
if (mode) $('#searchMode').value = mode;
|
||||||
if (!initialized) {
|
if (!initialized) {
|
||||||
initFilters();
|
initFilters();
|
||||||
initialized = true;
|
initialized = true;
|
||||||
|
@ -22,30 +23,29 @@ router.watch({search: ['search']}, ([search]) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
HTMLSelectElement.prototype.adjustWidth = function () {
|
HTMLSelectElement.prototype.adjustWidth = function () {
|
||||||
const option0 = this.selectedOptions[0];
|
const sel = this.selectedOptions[0];
|
||||||
if (!option0) return;
|
if (!sel) return;
|
||||||
const parent = this.parentNode;
|
const wOld = parseFloat(this.style.width);
|
||||||
const singleSelect = this.cloneNode(false);
|
const opts = [...this];
|
||||||
singleSelect.style.width = '';
|
opts.forEach(opt => opt !== sel && opt.remove());
|
||||||
singleSelect.appendChild(option0.cloneNode(true));
|
this.style.width = '';
|
||||||
parent.replaceChild(singleSelect, this);
|
requestAnimationFrame(() => {
|
||||||
const w = singleSelect.offsetWidth;
|
const w = this.offsetWidth;
|
||||||
if (w && this.style.width !== w + 'px') {
|
if (w && wOld !== w) this.style.width = w + 'px';
|
||||||
this.style.width = w + 'px';
|
this.append(...opts);
|
||||||
}
|
});
|
||||||
parent.replaceChild(this, singleSelect);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function initFilters() {
|
function initFilters() {
|
||||||
$('#search').oninput = e => {
|
$('#search').oninput = $('#searchMode').oninput = function (e) {
|
||||||
router.updateSearch('search', e.target.value);
|
router.updateSearch(this.id, e.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
$('#search-help').onclick = event => {
|
$('#search-help').onclick = event => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
messageBox({
|
messageBox({
|
||||||
className: 'help-text',
|
className: 'help-text',
|
||||||
title: t('searchStyles'),
|
title: t('search'),
|
||||||
contents:
|
contents:
|
||||||
$create('ul',
|
$create('ul',
|
||||||
t('searchStylesHelp').split('\n').map(line =>
|
t('searchStylesHelp').split('\n').map(line =>
|
||||||
|
@ -133,7 +133,7 @@ function initFilters() {
|
||||||
prefs.subscribe(['manage.filters.expanded'], () => {
|
prefs.subscribe(['manage.filters.expanded'], () => {
|
||||||
const el = $('#filters');
|
const el = $('#filters');
|
||||||
if (el.open) {
|
if (el.open) {
|
||||||
$$('select', el).forEach(select => select.adjustWidth());
|
$$('.filter-selection select', el).forEach(select => select.adjustWidth());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -141,7 +141,7 @@ function initFilters() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function filterOnChange({target: el, forceRefilter}) {
|
function filterOnChange({target: el, forceRefilter, alreadySearched}) {
|
||||||
const getValue = el => (el.type === 'checkbox' ? el.checked : el.value.trim());
|
const getValue = el => (el.type === 'checkbox' ? el.checked : el.value.trim());
|
||||||
if (!forceRefilter) {
|
if (!forceRefilter) {
|
||||||
const value = getValue(el);
|
const value = getValue(el);
|
||||||
|
@ -157,14 +157,14 @@ function filterOnChange({target: el, forceRefilter}) {
|
||||||
el.dataset[hide ? 'filterHide' : 'filter']
|
el.dataset[hide ? 'filterHide' : 'filter']
|
||||||
.split(/,\s*/)
|
.split(/,\s*/)
|
||||||
.map(s => (hide ? '.entry:not(.hidden)' : '') + s)
|
.map(s => (hide ? '.entry:not(.hidden)' : '') + s)
|
||||||
.join(','))
|
.join(',')),
|
||||||
].join(hide ? ',' : '');
|
].join(hide ? ',' : '');
|
||||||
Object.assign(filtersSelector, {
|
Object.assign(filtersSelector, {
|
||||||
hide: buildFilter(true),
|
hide: buildFilter(true),
|
||||||
unhide: buildFilter(false),
|
unhide: buildFilter(false),
|
||||||
});
|
});
|
||||||
if (installed) {
|
if (installed) {
|
||||||
reapplyFilter().then(sorter.updateStripes);
|
reapplyFilter(installed, alreadySearched).then(sorter.updateStripes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -278,10 +278,12 @@ function showFiltersStats() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function searchStyles({immediately, container} = {}) {
|
async function searchStyles({immediately, container} = {}) {
|
||||||
const el = $('#search');
|
const el = $('#search');
|
||||||
|
const elMode = $('#searchMode');
|
||||||
const query = el.value.trim();
|
const query = el.value.trim();
|
||||||
if (query === el.lastValue && !immediately && !container) {
|
const mode = elMode.value;
|
||||||
|
if (query === el.lastValue && mode === elMode.lastValue && !immediately && !container) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!immediately) {
|
if (!immediately) {
|
||||||
|
@ -289,24 +291,24 @@ function searchStyles({immediately, container} = {}) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
el.lastValue = query;
|
el.lastValue = query;
|
||||||
|
elMode.lastValue = mode;
|
||||||
|
|
||||||
const entries = container && container.children || container || installed.children;
|
const all = installed.children;
|
||||||
return API.searchDB({
|
const entries = container && container.children || container || all;
|
||||||
query,
|
const idsToSearch = entries !== all && [...entries].map(el => el.styleId);
|
||||||
ids: [...entries].map(el => el.styleId),
|
const ids = entries[0]
|
||||||
}).then(ids => {
|
? await API.searchDB({query, mode, ids: idsToSearch})
|
||||||
ids = new Set(ids);
|
: [];
|
||||||
let needsRefilter = false;
|
let needsRefilter = false;
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
const isMatching = ids.has(entry.styleId);
|
const isMatching = ids.includes(entry.styleId);
|
||||||
if (entry.classList.contains('not-matching') !== !isMatching) {
|
if (entry.classList.contains('not-matching') !== !isMatching) {
|
||||||
entry.classList.toggle('not-matching', !isMatching);
|
entry.classList.toggle('not-matching', !isMatching);
|
||||||
needsRefilter = true;
|
needsRefilter = true;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (needsRefilter && !container) {
|
}
|
||||||
filterOnChange({forceRefilter: true});
|
if (needsRefilter && !container) {
|
||||||
}
|
filterOnChange({forceRefilter: true, alreadySearched: true});
|
||||||
return container;
|
}
|
||||||
});
|
return container;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,8 +18,8 @@ body {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.all-styles-hidden-by-filters:before,
|
body.all-styles-hidden-by-filters::before,
|
||||||
body.all-styles-hidden-by-filters:after {
|
body.all-styles-hidden-by-filters::after {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: calc(3rem + var(--header-width));
|
left: calc(3rem + var(--header-width));
|
||||||
color: hsla(180, 40%, 45%, .3);
|
color: hsla(180, 40%, 45%, .3);
|
||||||
|
@ -32,7 +32,8 @@ body.all-styles-hidden-by-filters:before {
|
||||||
top: 3.5rem;
|
top: 3.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
body.all-styles-hidden-by-filters:after {
|
body.all-styles-hidden-by-filters::after {
|
||||||
|
content: var(--filteredStylesAllHidden);
|
||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 3rem;
|
top: 3rem;
|
||||||
|
@ -177,6 +178,20 @@ a:hover {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.style-info {
|
||||||
|
text-align: right;
|
||||||
|
padding: 0 .25em;
|
||||||
|
font-weight: normal;
|
||||||
|
color: #999;
|
||||||
|
}
|
||||||
|
.style-info[data-type=version] {
|
||||||
|
color: #666;
|
||||||
|
padding-left: .5em;
|
||||||
|
}
|
||||||
|
.newUI .style-info[data-type=version][data-value="1.0.0"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.applies-to {
|
.applies-to {
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
@ -233,7 +248,11 @@ a:hover {
|
||||||
vertical-align: text-top;
|
vertical-align: text-top;
|
||||||
}
|
}
|
||||||
|
|
||||||
.disabled h2::after, .entry.usercss .style-name-link::after {
|
.disabled h2::after {
|
||||||
|
content: var(--genericDisabledLabel);
|
||||||
|
}
|
||||||
|
.disabled h2::after,
|
||||||
|
.entry.usercss .style-name-link::after {
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
text-transform: lowercase;
|
text-transform: lowercase;
|
||||||
|
@ -243,6 +262,10 @@ a:hover {
|
||||||
margin-left: 1ex;
|
margin-left: 1ex;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
top: -2px;
|
||||||
|
}
|
||||||
|
.newUI .disabled h2::after,
|
||||||
|
.newUI .entry.usercss .style-name-link::after {
|
||||||
top: 2px;
|
top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -353,6 +376,10 @@ a:hover {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.newUI .entry > .style-info {
|
||||||
|
padding-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
.newUI .entry .actions {
|
.newUI .entry .actions {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
@ -614,6 +641,7 @@ a:hover {
|
||||||
|
|
||||||
.newUI .targets {
|
.newUI .targets {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
max-height: calc(var(--num-targets) * 18px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.newUI .applies-to.expanded .targets {
|
.newUI .applies-to.expanded .targets {
|
||||||
|
@ -625,7 +653,7 @@ a:hover {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
max-width: calc(100vw - var(--header-width) - var(--actions-width) - var(--name-padding-left) - 25vw - var(--name-padding-right));
|
max-width: calc(75vw - var(--header-width) - var(--actions-width) - var(--name-padding-left) - var(--name-padding-right) - 6rem);
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
padding-right: 1rem;
|
padding-right: 1rem;
|
||||||
line-height: 18px;
|
line-height: 18px;
|
||||||
|
@ -659,13 +687,16 @@ a:hover {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
margin: -1px 4px 0 -20px;
|
margin: -1px 4px 0 -20px;
|
||||||
transition: opacity .5s, filter .5s;
|
transition: opacity .5s, filter .5s;
|
||||||
filter: grayscale(1);
|
|
||||||
/* workaround for the buggy CSS filter: images in the hidden overflow are shown on Mac */
|
/* workaround for the buggy CSS filter: images in the hidden overflow are shown on Mac */
|
||||||
backface-visibility: hidden;
|
backface-visibility: hidden;
|
||||||
opacity: .25;
|
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.newUI .favicons-grayed .target img {
|
||||||
|
filter: grayscale(1);
|
||||||
|
opacity: .25;
|
||||||
|
}
|
||||||
|
|
||||||
.newUI .has-favicons .target {
|
.newUI .has-favicons .target {
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
}
|
}
|
||||||
|
@ -745,6 +776,7 @@ a:hover {
|
||||||
}
|
}
|
||||||
|
|
||||||
#update-all-no-updates[data-skipped-edited="true"]::after {
|
#update-all-no-updates[data-skipped-edited="true"]::after {
|
||||||
|
content: " " var(--updateAllCheckSucceededSomeEdited);
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
@ -787,10 +819,6 @@ a:hover {
|
||||||
background-color: hsla(0, 0%, 50%, .4);
|
background-color: hsla(0, 0%, 50%, .4);
|
||||||
}
|
}
|
||||||
|
|
||||||
#filters {
|
|
||||||
border: 1px solid transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.active #filters-stats {
|
.active #filters-stats {
|
||||||
background-color: darkcyan;
|
background-color: darkcyan;
|
||||||
border-color: darkcyan;
|
border-color: darkcyan;
|
||||||
|
@ -836,10 +864,11 @@ a:hover {
|
||||||
#search-wrapper, #sort-wrapper {
|
#search-wrapper, #sort-wrapper {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
flex-wrap: wrap;
|
|
||||||
margin-bottom: .5rem;
|
margin-bottom: .5rem;
|
||||||
}
|
}
|
||||||
|
#searchMode {
|
||||||
|
margin-left: -1px;
|
||||||
|
}
|
||||||
#search-wrapper {
|
#search-wrapper {
|
||||||
margin-top: .35rem;
|
margin-top: .35rem;
|
||||||
}
|
}
|
||||||
|
@ -848,18 +877,14 @@ a:hover {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
position: relative;
|
position: relative;
|
||||||
max-width: calc(100% - 30px);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#manage\.newUI\.sort {
|
#manage\.newUI\.sort {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#search {
|
|
||||||
max-width: calc(100% - 30px);
|
|
||||||
}
|
|
||||||
|
|
||||||
#search, #manage\.newUI\.sort {
|
#search, #manage\.newUI\.sort {
|
||||||
|
min-width: 4em; /* reduces the big default width */
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
height: 20px;
|
height: 20px;
|
||||||
|
@ -1022,6 +1047,12 @@ a:hover {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1000px) {
|
||||||
|
.newUI .entry > .style-info {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 850px) {
|
@media (max-width: 850px) {
|
||||||
body {
|
body {
|
||||||
display: table;
|
display: table;
|
||||||
|
@ -1164,10 +1195,6 @@ a:hover {
|
||||||
padding-left: 0;
|
padding-left: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#reset-filters {
|
|
||||||
margin-top: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#filters summary h2 {
|
#filters summary h2 {
|
||||||
margin-left: -2px;
|
margin-left: -2px;
|
||||||
}
|
}
|
||||||
|
|
277
manage/manage.js
277
manage/manage.js
|
@ -1,20 +1,39 @@
|
||||||
/*
|
/* global
|
||||||
global messageBox getStyleWithNoCode
|
$
|
||||||
filterAndAppend showFiltersStats
|
$$
|
||||||
checkUpdate handleUpdateInstalled
|
$create
|
||||||
objectDiff
|
animateElement
|
||||||
|
API
|
||||||
|
checkUpdate
|
||||||
|
CHROME
|
||||||
configDialog
|
configDialog
|
||||||
sorter msg prefs API $ $$ $create template setupLivePrefs
|
debounce
|
||||||
t tWordBreak formatDate
|
filterAndAppend
|
||||||
getOwnTab getActiveTab openURL animateElement sessionStorageHash debounce
|
getOwnTab
|
||||||
scrollElementIntoView CHROME VIVALDI router
|
getStyleWithNoCode
|
||||||
|
handleUpdateInstalled
|
||||||
|
messageBox
|
||||||
|
msg
|
||||||
|
objectDiff
|
||||||
|
openURL
|
||||||
|
prefs
|
||||||
|
router
|
||||||
|
scrollElementIntoView
|
||||||
|
sessionStore
|
||||||
|
setupLivePrefs
|
||||||
|
showFiltersStats
|
||||||
|
sorter
|
||||||
|
t
|
||||||
|
VIVALDI
|
||||||
*/
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
/** @type {HTMLElement} */
|
||||||
let installed;
|
let installed;
|
||||||
|
|
||||||
const ENTRY_ID_PREFIX_RAW = 'style-';
|
const ENTRY_ID_PREFIX_RAW = 'style-';
|
||||||
const ENTRY_ID_PREFIX = '#' + ENTRY_ID_PREFIX_RAW;
|
const ENTRY_ID_PREFIX = '#' + ENTRY_ID_PREFIX_RAW;
|
||||||
|
const REVEAL_DATES_FOR = 'h2.style-name, [data-type=age]';
|
||||||
|
|
||||||
const BULK_THROTTLE_MS = 100;
|
const BULK_THROTTLE_MS = 100;
|
||||||
const bulkChangeQueue = [];
|
const bulkChangeQueue = [];
|
||||||
|
@ -43,24 +62,31 @@ newUI.renderClass();
|
||||||
const TARGET_TYPES = ['domains', 'urls', 'urlPrefixes', 'regexps'];
|
const TARGET_TYPES = ['domains', 'urls', 'urlPrefixes', 'regexps'];
|
||||||
const GET_FAVICON_URL = 'https://www.google.com/s2/favicons?domain=';
|
const GET_FAVICON_URL = 'https://www.google.com/s2/favicons?domain=';
|
||||||
const OWN_ICON = chrome.runtime.getManifest().icons['16'];
|
const OWN_ICON = chrome.runtime.getManifest().icons['16'];
|
||||||
|
const AGES = [
|
||||||
|
[24, 'h', t('dateAbbrHour', '\x01')],
|
||||||
|
[30, 'd', t('dateAbbrDay', '\x01')],
|
||||||
|
[12, 'm', t('dateAbbrMonth', '\x01')],
|
||||||
|
[Infinity, 'y', t('dateAbbrYear', '\x01')],
|
||||||
|
];
|
||||||
|
|
||||||
const handleEvent = {};
|
const handleEvent = {};
|
||||||
|
|
||||||
Promise.all([
|
(async () => {
|
||||||
API.getAllStyles(true),
|
const query = router.getSearch('search');
|
||||||
// FIXME: integrate this into filter.js
|
const [styles, ids, el] = await Promise.all([
|
||||||
router.getSearch('search') && API.searchDB({query: router.getSearch('search')}),
|
API.getAllStyles(),
|
||||||
waitForSelector('#installed'), // needed to avoid flicker due to an extra frame and layout shift
|
query && API.searchDB({query, mode: router.getSearch('searchMode')}),
|
||||||
prefs.initializing
|
waitForSelector('#installed'), // needed to avoid flicker due to an extra frame and layout shift
|
||||||
]).then(([styles, ids, el]) => {
|
prefs.initializing,
|
||||||
|
]);
|
||||||
installed = el;
|
installed = el;
|
||||||
installed.onclick = handleEvent.entryClicked;
|
installed.onclick = handleEvent.entryClicked;
|
||||||
$('#manage-options-button').onclick = () => router.updateHash('#stylus-options');
|
$('#manage-options-button').onclick = () => router.updateHash('#stylus-options');
|
||||||
$('#sync-styles').onclick = () => router.updateHash('#stylus-options');
|
$('#sync-styles').onclick = () => router.updateHash('#stylus-options');
|
||||||
$$('#header a[href^="http"]').forEach(a => (a.onclick = handleEvent.external));
|
$$('#header a[href^="http"]').forEach(a => (a.onclick = handleEvent.external));
|
||||||
// show date installed & last update on hover
|
// show date installed & last update on hover
|
||||||
installed.addEventListener('mouseover', handleEvent.lazyAddEntryTitle);
|
installed.addEventListener('mouseover', handleEvent.lazyAddEntryTitle, {passive: true});
|
||||||
installed.addEventListener('mouseout', handleEvent.lazyAddEntryTitle);
|
installed.addEventListener('mouseout', handleEvent.lazyAddEntryTitle, {passive: true});
|
||||||
document.addEventListener('visibilitychange', onVisibilityChange);
|
document.addEventListener('visibilitychange', onVisibilityChange);
|
||||||
// N.B. triggers existing onchange listeners
|
// N.B. triggers existing onchange listeners
|
||||||
setupLivePrefs();
|
setupLivePrefs();
|
||||||
|
@ -68,19 +94,15 @@ Promise.all([
|
||||||
prefs.subscribe(newUI.ids.map(newUI.prefKeyForId), () => switchUI());
|
prefs.subscribe(newUI.ids.map(newUI.prefKeyForId), () => switchUI());
|
||||||
switchUI({styleOnly: true});
|
switchUI({styleOnly: true});
|
||||||
// translate CSS manually
|
// translate CSS manually
|
||||||
document.head.appendChild($create('style', `
|
document.styleSheets[0].insertRule(
|
||||||
.disabled h2::after {
|
`:root {${[
|
||||||
content: "${t('genericDisabledLabel')}";
|
'genericDisabledLabel',
|
||||||
}
|
'updateAllCheckSucceededSomeEdited',
|
||||||
#update-all-no-updates[data-skipped-edited="true"]::after {
|
'filteredStylesAllHidden',
|
||||||
content: " ${t('updateAllCheckSucceededSomeEdited')}";
|
].map(id => `--${id}:"${CSS.escape(t(id))}";`).join('')
|
||||||
}
|
}}`);
|
||||||
body.all-styles-hidden-by-filters::after {
|
|
||||||
content: "${t('filteredStylesAllHidden')}";
|
|
||||||
}
|
|
||||||
`));
|
|
||||||
if (!VIVALDI) {
|
if (!VIVALDI) {
|
||||||
$$('#header select').forEach(el => el.adjustWidth());
|
$$('.filter-selection select').forEach(el => el.adjustWidth());
|
||||||
}
|
}
|
||||||
if (CHROME >= 80 && CHROME <= 88) {
|
if (CHROME >= 80 && CHROME <= 88) {
|
||||||
// Wrong checkboxes are randomly checked after going back in history, https://crbug.com/1138598
|
// Wrong checkboxes are randomly checked after going back in history, https://crbug.com/1138598
|
||||||
|
@ -89,9 +111,11 @@ Promise.all([
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
showStyles(styles, ids);
|
showStyles(styles, ids);
|
||||||
});
|
})();
|
||||||
|
|
||||||
msg.onExtension(onRuntimeMessage);
|
msg.onExtension(onRuntimeMessage);
|
||||||
|
router.watch({hash: '#stylus-options'}, state => (state ? embedOptions : unembedOptions)());
|
||||||
|
window.addEventListener('closeOptions', () => router.updateHash(''));
|
||||||
|
|
||||||
function onRuntimeMessage(msg) {
|
function onRuntimeMessage(msg) {
|
||||||
switch (msg.method) {
|
switch (msg.method) {
|
||||||
|
@ -129,7 +153,7 @@ function showStyles(styles = [], matchUrlIds) {
|
||||||
let firstRun = true;
|
let firstRun = true;
|
||||||
installed.dataset.total = styles.length;
|
installed.dataset.total = styles.length;
|
||||||
const scrollY = (history.state || {}).scrollY;
|
const scrollY = (history.state || {}).scrollY;
|
||||||
const shouldRenderAll = scrollY > window.innerHeight || sessionStorage.justEditedStyleId;
|
const shouldRenderAll = scrollY > window.innerHeight || sessionStore.justEditedStyleId;
|
||||||
const renderBin = document.createDocumentFragment();
|
const renderBin = document.createDocumentFragment();
|
||||||
if (scrollY) {
|
if (scrollY) {
|
||||||
renderStyles();
|
renderStyles();
|
||||||
|
@ -155,7 +179,7 @@ function showStyles(styles = [], matchUrlIds) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setTimeout(getFaviconImgSrc);
|
setTimeout(getFaviconImgSrc);
|
||||||
if (sessionStorage.justEditedStyleId) {
|
if (sessionStore.justEditedStyleId) {
|
||||||
highlightEditedStyle();
|
highlightEditedStyle();
|
||||||
} else if ('scrollY' in (history.state || {})) {
|
} else if ('scrollY' in (history.state || {})) {
|
||||||
setTimeout(window.scrollTo, 0, 0, history.state.scrollY);
|
setTimeout(window.scrollTo, 0, 0, history.state.scrollY);
|
||||||
|
@ -167,7 +191,7 @@ function showStyles(styles = [], matchUrlIds) {
|
||||||
function createStyleElement({style, name: nameLC}) {
|
function createStyleElement({style, name: nameLC}) {
|
||||||
// query the sub-elements just once, then reuse the references
|
// query the sub-elements just once, then reuse the references
|
||||||
if ((createStyleElement.parts || {}).newUI !== newUI.enabled) {
|
if ((createStyleElement.parts || {}).newUI !== newUI.enabled) {
|
||||||
const entry = template[`style${newUI.enabled ? 'Compact' : ''}`];
|
const entry = t.template[newUI.enabled ? 'styleNewUI' : 'style'];
|
||||||
createStyleElement.parts = {
|
createStyleElement.parts = {
|
||||||
newUI: newUI.enabled,
|
newUI: newUI.enabled,
|
||||||
entry,
|
entry,
|
||||||
|
@ -177,7 +201,9 @@ function createStyleElement({style, name: nameLC}) {
|
||||||
editLink: $('.style-edit-link', entry) || {},
|
editLink: $('.style-edit-link', entry) || {},
|
||||||
editHrefBase: 'edit.html?id=',
|
editHrefBase: 'edit.html?id=',
|
||||||
homepage: $('.homepage', entry),
|
homepage: $('.homepage', entry),
|
||||||
homepageIcon: template[`homepageIcon${newUI.enabled ? 'Small' : 'Big'}`],
|
homepageIcon: t.template[`homepageIcon${newUI.enabled ? 'Small' : 'Big'}`],
|
||||||
|
infoAge: $('[data-type=age]', entry),
|
||||||
|
infoVer: $('[data-type=version]', entry),
|
||||||
appliesTo: $('.applies-to', entry),
|
appliesTo: $('.applies-to', entry),
|
||||||
targets: $('.targets', entry),
|
targets: $('.targets', entry),
|
||||||
expander: $('.expander', entry),
|
expander: $('.expander', entry),
|
||||||
|
@ -192,13 +218,18 @@ function createStyleElement({style, name: nameLC}) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const parts = createStyleElement.parts;
|
const parts = createStyleElement.parts;
|
||||||
const configurable = style.usercssData && style.usercssData.vars && Object.keys(style.usercssData.vars).length > 0;
|
const ud = style.usercssData;
|
||||||
|
const configurable = ud && ud.vars && Object.keys(ud.vars).length > 0;
|
||||||
const name = style.customName || style.name;
|
const name = style.customName || style.name;
|
||||||
parts.checker.checked = style.enabled;
|
parts.checker.checked = style.enabled;
|
||||||
parts.nameLink.textContent = tWordBreak(name);
|
parts.nameLink.firstChild.textContent = t.breakWord(name);
|
||||||
parts.nameLink.href = parts.editLink.href = parts.editHrefBase + style.id;
|
parts.nameLink.href = parts.editLink.href = parts.editHrefBase + style.id;
|
||||||
parts.homepage.href = parts.homepage.title = style.url || '';
|
parts.homepage.href = parts.homepage.title = style.url || '';
|
||||||
if (!newUI.enabled) {
|
parts.infoVer.textContent = ud ? ud.version : '';
|
||||||
|
parts.infoVer.dataset.value = ud ? ud.version : '';
|
||||||
|
if (newUI.enabled) {
|
||||||
|
createAgeText(parts.infoAge, style);
|
||||||
|
} else {
|
||||||
parts.oldConfigure.classList.toggle('hidden', !configurable);
|
parts.oldConfigure.classList.toggle('hidden', !configurable);
|
||||||
parts.oldCheckUpdate.classList.toggle('hidden', !style.updateUrl);
|
parts.oldCheckUpdate.classList.toggle('hidden', !style.updateUrl);
|
||||||
parts.oldUpdate.classList.toggle('hidden', !style.updateUrl);
|
parts.oldUpdate.classList.toggle('hidden', !style.updateUrl);
|
||||||
|
@ -217,16 +248,16 @@ function createStyleElement({style, name: nameLC}) {
|
||||||
entry.className = parts.entryClassBase + ' ' +
|
entry.className = parts.entryClassBase + ' ' +
|
||||||
(style.enabled ? 'enabled' : 'disabled') +
|
(style.enabled ? 'enabled' : 'disabled') +
|
||||||
(style.updateUrl ? ' updatable' : '') +
|
(style.updateUrl ? ' updatable' : '') +
|
||||||
(style.usercssData ? ' usercss' : '');
|
(ud ? ' usercss' : '');
|
||||||
|
|
||||||
if (style.url) {
|
if (style.url) {
|
||||||
$('.homepage', entry).appendChild(parts.homepageIcon.cloneNode(true));
|
$('.homepage', entry).appendChild(parts.homepageIcon.cloneNode(true));
|
||||||
}
|
}
|
||||||
if (style.updateUrl && newUI.enabled) {
|
if (style.updateUrl && newUI.enabled) {
|
||||||
$('.actions', entry).appendChild(template.updaterIcons.cloneNode(true));
|
$('.actions', entry).appendChild(t.template.updaterIcons.cloneNode(true));
|
||||||
}
|
}
|
||||||
if (configurable && newUI.enabled) {
|
if (configurable && newUI.enabled) {
|
||||||
$('.actions', entry).appendChild(template.configureIcon.cloneNode(true));
|
$('.actions', entry).appendChild(t.template.configureIcon.cloneNode(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
createStyleTargetsElement({entry, style});
|
createStyleTargetsElement({entry, style});
|
||||||
|
@ -267,12 +298,12 @@ function createStyleTargetsElement({entry, expanded, style = entry.styleMeta}) {
|
||||||
el = next;
|
el = next;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const element = template.appliesToTarget.cloneNode(true);
|
const element = t.template.appliesToTarget.cloneNode(true);
|
||||||
if (!newUI.enabled) {
|
if (!newUI.enabled) {
|
||||||
if (numTargets === maxTargets) {
|
if (numTargets === maxTargets) {
|
||||||
container = container.appendChild(template.extraAppliesTo.cloneNode(true));
|
container = container.appendChild(t.template.extraAppliesTo.cloneNode(true));
|
||||||
} else if (numTargets > 0) {
|
} else if (numTargets > 1) {
|
||||||
container.appendChild(template.appliesToSeparator.cloneNode(true));
|
container.appendChild(t.template.appliesToSeparator.cloneNode(true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
element.dataset.type = type;
|
element.dataset.type = type;
|
||||||
|
@ -291,13 +322,37 @@ function createStyleTargetsElement({entry, expanded, style = entry.styleMeta}) {
|
||||||
if (entryTargets.firstElementChild) {
|
if (entryTargets.firstElementChild) {
|
||||||
entryTargets.textContent = '';
|
entryTargets.textContent = '';
|
||||||
}
|
}
|
||||||
entryTargets.appendChild(template.appliesToEverything.cloneNode(true));
|
entryTargets.appendChild(t.template.appliesToEverything.cloneNode(true));
|
||||||
}
|
}
|
||||||
entry.classList.toggle('global', !numTargets);
|
entry.classList.toggle('global', !numTargets);
|
||||||
entry._allTargetsRendered = allTargetsRendered;
|
entry._allTargetsRendered = allTargetsRendered;
|
||||||
entry._numTargets = numTargets;
|
entry._numTargets = numTargets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function createAgeText(el, style) {
|
||||||
|
let val = style.updateDate || style.installDate;
|
||||||
|
if (val) {
|
||||||
|
val = (Date.now() - val) / 3600e3; // age in hours
|
||||||
|
for (const [max, unit, text] of AGES) {
|
||||||
|
const rounded = Math.round(val);
|
||||||
|
if (rounded < max) {
|
||||||
|
el.textContent = text.replace('\x01', rounded);
|
||||||
|
el.dataset.value = padLeft(Math.round(rounded), 2) + unit;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
val /= max;
|
||||||
|
}
|
||||||
|
} else if (el.firstChild) {
|
||||||
|
el.textContent = '';
|
||||||
|
delete el.dataset.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Adding spaces so CSS can detect "bigness" of a value via amount of spaces at the beginning */
|
||||||
|
function padLeft(val, width) {
|
||||||
|
val = `${val}`;
|
||||||
|
return ' '.repeat(Math.max(0, width - val.length)) + val;
|
||||||
|
}
|
||||||
|
|
||||||
function getFaviconImgSrc(container = installed) {
|
function getFaviconImgSrc(container = installed) {
|
||||||
if (!newUI.enabled || !newUI.favicons) return;
|
if (!newUI.enabled || !newUI.favicons) return;
|
||||||
|
@ -350,7 +405,7 @@ Object.assign(handleEvent, {
|
||||||
'.update': 'update',
|
'.update': 'update',
|
||||||
'.delete': 'delete',
|
'.delete': 'delete',
|
||||||
'.applies-to .expander': 'expandTargets',
|
'.applies-to .expander': 'expandTargets',
|
||||||
'.configure-usercss': 'config'
|
'.configure-usercss': 'config',
|
||||||
},
|
},
|
||||||
|
|
||||||
entryClicked(event) {
|
entryClicked(event) {
|
||||||
|
@ -366,42 +421,29 @@ Object.assign(handleEvent, {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
name(event) {
|
name(event, entry) {
|
||||||
if (newUI.enabled) handleEvent.edit(event);
|
if (newUI.enabled) handleEvent.edit(event, entry);
|
||||||
},
|
},
|
||||||
|
|
||||||
edit(event) {
|
async edit(event, entry) {
|
||||||
if (event.altKey) {
|
if (event.altKey) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
const left = event.button === 0;
|
const key = `${event.shiftKey ? 's' : ''}${event.ctrlKey ? 'c' : ''}${'LMR'[event.button]}`;
|
||||||
const middle = event.button === 1;
|
|
||||||
const shift = event.shiftKey;
|
|
||||||
const ctrl = event.ctrlKey;
|
|
||||||
const openWindow = left && shift && !ctrl;
|
|
||||||
const openBackgroundTab = (middle && !shift) || (left && ctrl && !shift);
|
|
||||||
const openForegroundTab = (middle && shift) || (left && ctrl && shift);
|
|
||||||
const entry = event.target.closest('.entry');
|
|
||||||
const url = $('[href]', entry).href;
|
const url = $('[href]', entry).href;
|
||||||
if (openWindow || openBackgroundTab || openForegroundTab) {
|
const ownTab = await getOwnTab();
|
||||||
if (chrome.windows && openWindow) {
|
if (key === 'L') {
|
||||||
API.openEditor({id: entry.styleId});
|
sessionStore['manageStylesHistory' + ownTab.id] = url;
|
||||||
} else {
|
location.href = url;
|
||||||
getOwnTab().then(({index}) => {
|
} else if (chrome.windows && key === 'sL') {
|
||||||
openURL({
|
API.openEditor({id: entry.styleId});
|
||||||
url,
|
|
||||||
index: index + 1,
|
|
||||||
active: openForegroundTab
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
onVisibilityChange();
|
openURL({
|
||||||
getActiveTab().then(tab => {
|
url,
|
||||||
sessionStorageHash('manageStylesHistory').set(tab.id, url);
|
index: ownTab.index + 1,
|
||||||
location.href = url;
|
active: key === 'sM' || key === 'scL',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -501,9 +543,9 @@ Object.assign(handleEvent, {
|
||||||
},
|
},
|
||||||
|
|
||||||
lazyAddEntryTitle({type, target}) {
|
lazyAddEntryTitle({type, target}) {
|
||||||
const cell = target.closest('h2.style-name');
|
const cell = target.closest(REVEAL_DATES_FOR);
|
||||||
if (cell) {
|
if (cell) {
|
||||||
const link = $('.style-name-link', cell);
|
const link = $('.style-name-link', cell) || cell;
|
||||||
if (type === 'mouseover' && !link.title) {
|
if (type === 'mouseover' && !link.title) {
|
||||||
debounce(handleEvent.addEntryTitle, 50, link);
|
debounce(handleEvent.addEntryTitle, 50, link);
|
||||||
} else {
|
} else {
|
||||||
|
@ -518,8 +560,8 @@ Object.assign(handleEvent, {
|
||||||
{prop: 'installDate', name: 'dateInstalled'},
|
{prop: 'installDate', name: 'dateInstalled'},
|
||||||
{prop: 'updateDate', name: 'dateUpdated'},
|
{prop: 'updateDate', name: 'dateUpdated'},
|
||||||
].map(({prop, name}) =>
|
].map(({prop, name}) =>
|
||||||
t(name) + ': ' + (formatDate(entry.styleMeta[prop]) || '—')).join('\n');
|
t(name) + ': ' + (t.formatDate(entry.styleMeta[prop]) || '—')).join('\n');
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@ -537,7 +579,7 @@ function handleBulkChange() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleUpdateForId(id, opts) {
|
function handleUpdateForId(id, opts) {
|
||||||
return API.getStyle(id, true).then(style => {
|
return API.getStyle(id).then(style => {
|
||||||
handleUpdate(style, opts);
|
handleUpdate(style, opts);
|
||||||
bulkChangeQueue.time = performance.now();
|
bulkChangeQueue.time = performance.now();
|
||||||
});
|
});
|
||||||
|
@ -620,43 +662,9 @@ function switchUI({styleOnly} = {}) {
|
||||||
|
|
||||||
Object.assign(newUI, current);
|
Object.assign(newUI, current);
|
||||||
newUI.renderClass();
|
newUI.renderClass();
|
||||||
installed.classList.toggle('has-favicons', newUI.favicons);
|
installed.classList.toggle('has-favicons', newUI.enabled && newUI.favicons);
|
||||||
$('#style-overrides').textContent = `
|
installed.classList.toggle('favicons-grayed', newUI.enabled && newUI.faviconsGray);
|
||||||
.newUI .targets {
|
if (changed.targets) installed.style.setProperty('--num-targets', newUI.targets);
|
||||||
max-height: ${newUI.targets * 18}px;
|
|
||||||
}
|
|
||||||
` + (newUI.faviconsGray ? `
|
|
||||||
.newUI .target img {
|
|
||||||
filter: grayscale(1);
|
|
||||||
opacity: .25;
|
|
||||||
}
|
|
||||||
` : `
|
|
||||||
.newUI .target img {
|
|
||||||
filter: none;
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
`) + (CHROME >= 58 ? `
|
|
||||||
.newUI .entry {
|
|
||||||
contain: strict;
|
|
||||||
}
|
|
||||||
.newUI .entry > * {
|
|
||||||
contain: content;
|
|
||||||
}
|
|
||||||
.newUI .entry .actions {
|
|
||||||
contain: none;
|
|
||||||
}
|
|
||||||
.newUI .target {
|
|
||||||
contain: layout style;
|
|
||||||
}
|
|
||||||
.newUI .target img {
|
|
||||||
contain: layout style size;
|
|
||||||
}
|
|
||||||
.newUI .entry.can-update,
|
|
||||||
.newUI .entry.update-problem,
|
|
||||||
.newUI .entry.update-done {
|
|
||||||
contain: none;
|
|
||||||
}
|
|
||||||
` : '');
|
|
||||||
|
|
||||||
if (styleOnly) {
|
if (styleOnly) {
|
||||||
return;
|
return;
|
||||||
|
@ -666,7 +674,7 @@ function switchUI({styleOnly} = {}) {
|
||||||
let iconsMissing = iconsEnabled && !$('.applies-to img');
|
let iconsMissing = iconsEnabled && !$('.applies-to img');
|
||||||
if (changed.enabled || (iconsMissing && !createStyleElement.parts)) {
|
if (changed.enabled || (iconsMissing && !createStyleElement.parts)) {
|
||||||
installed.textContent = '';
|
installed.textContent = '';
|
||||||
API.getAllStyles(true).then(showStyles);
|
API.getAllStyles().then(showStyles);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (changed.targets) {
|
if (changed.targets) {
|
||||||
|
@ -691,10 +699,10 @@ function onVisibilityChange() {
|
||||||
// the catch here is that DOM may be outdated so we'll at least refresh the just edited style
|
// the catch here is that DOM may be outdated so we'll at least refresh the just edited style
|
||||||
// assuming other changes aren't important enough to justify making a complicated DOM sync
|
// assuming other changes aren't important enough to justify making a complicated DOM sync
|
||||||
case 'visible': {
|
case 'visible': {
|
||||||
const id = sessionStorage.justEditedStyleId;
|
const id = sessionStore.justEditedStyleId;
|
||||||
if (id) {
|
if (id) {
|
||||||
handleUpdateForId(Number(id), {method: 'styleUpdated'});
|
handleUpdateForId(Number(id), {method: 'styleUpdated'});
|
||||||
delete sessionStorage.justEditedStyleId;
|
delete sessionStore.justEditedStyleId;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -707,9 +715,9 @@ function onVisibilityChange() {
|
||||||
|
|
||||||
|
|
||||||
function highlightEditedStyle() {
|
function highlightEditedStyle() {
|
||||||
if (!sessionStorage.justEditedStyleId) return;
|
if (!sessionStore.justEditedStyleId) return;
|
||||||
const entry = $(ENTRY_ID_PREFIX + sessionStorage.justEditedStyleId);
|
const entry = $(ENTRY_ID_PREFIX + sessionStore.justEditedStyleId);
|
||||||
delete sessionStorage.justEditedStyleId;
|
delete sessionStore.justEditedStyleId;
|
||||||
if (entry) {
|
if (entry) {
|
||||||
animateElement(entry);
|
animateElement(entry);
|
||||||
requestAnimationFrame(() => scrollElementIntoView(entry));
|
requestAnimationFrame(() => scrollElementIntoView(entry));
|
||||||
|
@ -732,13 +740,11 @@ function waitForSelector(selector) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function embedOptions() {
|
function embedOptions() {
|
||||||
let options = $('#stylus-embedded-options');
|
const options = $('#stylus-embedded-options') ||
|
||||||
if (!options) {
|
document.documentElement.appendChild($create('iframe', {
|
||||||
options = document.createElement('iframe');
|
id: 'stylus-embedded-options',
|
||||||
options.id = 'stylus-embedded-options';
|
src: '/options.html',
|
||||||
options.src = '/options.html';
|
}));
|
||||||
document.documentElement.appendChild(options);
|
|
||||||
}
|
|
||||||
options.focus();
|
options.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -746,20 +752,7 @@ async function unembedOptions() {
|
||||||
const options = $('#stylus-embedded-options');
|
const options = $('#stylus-embedded-options');
|
||||||
if (options) {
|
if (options) {
|
||||||
options.contentWindow.document.body.classList.add('scaleout');
|
options.contentWindow.document.body.classList.add('scaleout');
|
||||||
options.classList.add('fadeout');
|
|
||||||
await animateElement(options, 'fadeout');
|
await animateElement(options, 'fadeout');
|
||||||
options.remove();
|
options.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
router.watch({hash: '#stylus-options'}, state => {
|
|
||||||
if (state) {
|
|
||||||
embedOptions();
|
|
||||||
} else {
|
|
||||||
unembedOptions();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
window.addEventListener('closeOptions', () => {
|
|
||||||
router.updateHash('');
|
|
||||||
});
|
|
||||||
|
|
|
@ -13,28 +13,28 @@ const sorter = (() => {
|
||||||
title: {
|
title: {
|
||||||
text: t('genericTitle'),
|
text: t('genericTitle'),
|
||||||
parse: ({name}) => name,
|
parse: ({name}) => name,
|
||||||
sorter: sorterType.alpha
|
sorter: sorterType.alpha,
|
||||||
},
|
},
|
||||||
usercss: {
|
usercss: {
|
||||||
text: 'Usercss',
|
text: 'Usercss',
|
||||||
parse: ({style}) => style.usercssData ? 0 : 1,
|
parse: ({style}) => style.usercssData ? 0 : 1,
|
||||||
sorter: sorterType.number
|
sorter: sorterType.number,
|
||||||
},
|
},
|
||||||
disabled: {
|
disabled: {
|
||||||
text: '', // added as either "enabled" or "disabled" by the addOptions function
|
text: '', // added as either "enabled" or "disabled" by the addOptions function
|
||||||
parse: ({style}) => style.enabled ? 1 : 0,
|
parse: ({style}) => style.enabled ? 1 : 0,
|
||||||
sorter: sorterType.number
|
sorter: sorterType.number,
|
||||||
},
|
},
|
||||||
dateInstalled: {
|
dateInstalled: {
|
||||||
text: t('dateInstalled'),
|
text: t('dateInstalled'),
|
||||||
parse: ({style}) => style.installDate,
|
parse: ({style}) => style.installDate,
|
||||||
sorter: sorterType.number
|
sorter: sorterType.number,
|
||||||
},
|
},
|
||||||
dateUpdated: {
|
dateUpdated: {
|
||||||
text: t('dateUpdated'),
|
text: t('dateUpdated'),
|
||||||
parse: ({style}) => style.updateDate || style.installDate,
|
parse: ({style}) => style.updateDate || style.installDate,
|
||||||
sorter: sorterType.number
|
sorter: sorterType.number,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Adding (assumed) most commonly used ('title,asc' should always be first)
|
// Adding (assumed) most commonly used ('title,asc' should always be first)
|
||||||
|
@ -56,7 +56,7 @@ const sorter = (() => {
|
||||||
'usercss,asc, title,desc',
|
'usercss,asc, title,desc',
|
||||||
'usercss,desc, title,desc',
|
'usercss,desc, title,desc',
|
||||||
'disabled,desc, title,desc',
|
'disabled,desc, title,desc',
|
||||||
'disabled,desc, usercss,asc, title,desc'
|
'disabled,desc, usercss,asc, title,desc',
|
||||||
];
|
];
|
||||||
|
|
||||||
const splitRegex = /\s*,\s*/;
|
const splitRegex = /\s*,\s*/;
|
||||||
|
@ -76,7 +76,7 @@ const sorter = (() => {
|
||||||
dateNew: ` (${t('sortDateNewestFirst')})`,
|
dateNew: ` (${t('sortDateNewestFirst')})`,
|
||||||
dateOld: ` (${t('sortDateOldestFirst')})`,
|
dateOld: ` (${t('sortDateOldestFirst')})`,
|
||||||
groupAsc: t('sortLabelTitleAsc'),
|
groupAsc: t('sortLabelTitleAsc'),
|
||||||
groupDesc: t('sortLabelTitleDesc')
|
groupDesc: t('sortLabelTitleDesc'),
|
||||||
};
|
};
|
||||||
const optgroupRegex = /\{\w+\}/;
|
const optgroupRegex = /\{\w+\}/;
|
||||||
selectOptions.forEach(sort => {
|
selectOptions.forEach(sort => {
|
||||||
|
@ -132,7 +132,7 @@ const sorter = (() => {
|
||||||
entry,
|
entry,
|
||||||
name: entry.styleNameLowerCase,
|
name: entry.styleNameLowerCase,
|
||||||
style: entry.styleMeta,
|
style: entry.styleMeta,
|
||||||
}))
|
})),
|
||||||
});
|
});
|
||||||
if (current.some((entry, index) => entry !== sorted[index].entry)) {
|
if (current.some((entry, index) => entry !== sorted[index].entry)) {
|
||||||
const renderBin = document.createDocumentFragment();
|
const renderBin = document.createDocumentFragment();
|
||||||
|
|
|
@ -55,7 +55,7 @@
|
||||||
"background/usercss-helper.js",
|
"background/usercss-helper.js",
|
||||||
"background/usercss-install-helper.js",
|
"background/usercss-install-helper.js",
|
||||||
"background/style-via-api.js",
|
"background/style-via-api.js",
|
||||||
"background/style-via-xhr.js",
|
"background/style-via-webrequest.js",
|
||||||
"background/search-db.js",
|
"background/search-db.js",
|
||||||
"background/update.js",
|
"background/update.js",
|
||||||
"background/openusercss-api.js"
|
"background/openusercss-api.js"
|
||||||
|
|
|
@ -1,11 +1,18 @@
|
||||||
/* global focusAccessibility moveFocus $ $create t tHTML animateElement */
|
/* global
|
||||||
|
$
|
||||||
|
$create
|
||||||
|
animateElement
|
||||||
|
focusAccessibility
|
||||||
|
moveFocus
|
||||||
|
t
|
||||||
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Object} params
|
* @param {Object} params
|
||||||
* @param {String} params.title
|
* @param {String} params.title
|
||||||
* @param {String|Node|Object|Array<String|Node|Object>} params.contents
|
* @param {String|Node|Object|Array<String|Node|Object>} params.contents
|
||||||
* a string gets parsed via tHTML,
|
* a string gets parsed via t.HTML,
|
||||||
* a non-string is passed as is to $create()
|
* a non-string is passed as is to $create()
|
||||||
* @param {String} [params.className]
|
* @param {String} [params.className]
|
||||||
* CSS class name of the message box element
|
* CSS class name of the message box element
|
||||||
|
@ -87,7 +94,7 @@ function messageBox({
|
||||||
},
|
},
|
||||||
scroll() {
|
scroll() {
|
||||||
scrollTo(blockScroll.x, blockScroll.y);
|
scrollTo(blockScroll.x, blockScroll.y);
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,7 +123,7 @@ function messageBox({
|
||||||
$create('SVG:path', {d: 'M11.69,10l4.55,4.55-1.69,1.69L10,11.69,' +
|
$create('SVG:path', {d: 'M11.69,10l4.55,4.55-1.69,1.69L10,11.69,' +
|
||||||
'5.45,16.23,3.77,14.55,8.31,10,3.77,5.45,5.45,3.77,10,8.31l4.55-4.55,1.69,1.69Z',
|
'5.45,16.23,3.77,14.55,8.31,10,3.77,5.45,5.45,3.77,10,8.31l4.55-4.55,1.69,1.69Z',
|
||||||
}))),
|
}))),
|
||||||
$create(`#${id}-contents`, tHTML(contents)),
|
$create(`#${id}-contents`, t.HTML(contents)),
|
||||||
$create(`#${id}-buttons`,
|
$create(`#${id}-buttons`,
|
||||||
buttons.map((content, buttonIndex) => content &&
|
buttons.map((content, buttonIndex) => content &&
|
||||||
$create('button', Object.assign({
|
$create('button', Object.assign({
|
||||||
|
@ -160,7 +167,7 @@ messageBox.alert = (contents, className, title) =>
|
||||||
title,
|
title,
|
||||||
contents,
|
contents,
|
||||||
className: `center ${className || ''}`,
|
className: `center ${className || ''}`,
|
||||||
buttons: [t('confirmClose')]
|
buttons: [t('confirmClose')],
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -174,5 +181,5 @@ messageBox.confirm = (contents, className, title) =>
|
||||||
title,
|
title,
|
||||||
contents,
|
contents,
|
||||||
className: `center ${className || ''}`,
|
className: `center ${className || ''}`,
|
||||||
buttons: [t('confirmYes'), t('confirmNo')]
|
buttons: [t('confirmYes'), t('confirmNo')],
|
||||||
}).then(result => result.button === 0 || result.enter);
|
}).then(result => result.button === 0 || result.enter);
|
||||||
|
|
56
options.html
56
options.html
|
@ -52,7 +52,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<h1 i18n-text="optionsCustomizeIcon"></h1>
|
<h1 i18n-text="optionsCustomizeIcon"></h1>
|
||||||
<div class="items">
|
<div class="items">
|
||||||
|
@ -76,7 +76,7 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<h1 i18n-text="optionsCustomizeBadge"></h1>
|
<h1 i18n-text="optionsCustomizeBadge"></h1>
|
||||||
<div class="items">
|
<div class="items">
|
||||||
|
@ -97,7 +97,7 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<h1 i18n-text="optionsCustomizePopup"></h1>
|
<h1 i18n-text="optionsCustomizePopup"></h1>
|
||||||
<div class="items">
|
<div class="items">
|
||||||
|
@ -143,7 +143,7 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="block">
|
<div class="block">
|
||||||
<h1 i18n-text="openManage"></h1>
|
<h1 i18n-text="openManage"></h1>
|
||||||
<div class="items">
|
<div class="items">
|
||||||
|
@ -156,11 +156,8 @@
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<span i18n-text="manageFavicons">
|
<span i18n-text="manageFavicons">
|
||||||
<a data-cmd="note"
|
<a i18n-title="manageFaviconsHelp"
|
||||||
i18n-title="manageFaviconsHelp"
|
data-cmd="note" href="#" class="svg-inline-wrapper">
|
||||||
href="#"
|
|
||||||
class="svg-inline-wrapper"
|
|
||||||
tabindex="0">
|
|
||||||
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
|
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
|
@ -182,17 +179,14 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="block" id="updates">
|
<div class="block" id="updates">
|
||||||
<h1 i18n-text="optionsCustomizeUpdate"></h1>
|
<h1 i18n-text="optionsCustomizeUpdate"></h1>
|
||||||
<div class="items">
|
<div class="items">
|
||||||
<label>
|
<label>
|
||||||
<span i18n-text="optionsUpdateInterval">
|
<span i18n-text="optionsUpdateInterval">
|
||||||
<a data-cmd="note"
|
<a i18n-title="optionsUpdateImportNote"
|
||||||
i18n-title="optionsUpdateImportNote"
|
data-cmd="note" href="#" class="svg-inline-wrapper">
|
||||||
href="#"
|
|
||||||
class="svg-inline-wrapper"
|
|
||||||
tabindex="0">
|
|
||||||
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
|
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
|
@ -200,7 +194,7 @@
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="block sync-options">
|
<div class="block sync-options">
|
||||||
<h1 i18n-text="optionsCustomizeSync"></h1>
|
<h1 i18n-text="optionsCustomizeSync"></h1>
|
||||||
<div class="items">
|
<div class="items">
|
||||||
|
@ -224,7 +218,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="block" id="advanced">
|
<div class="block" id="advanced">
|
||||||
<div class="collapsible-resizer">
|
<div class="collapsible-resizer">
|
||||||
<h1 i18n-text="optionsAdvanced">
|
<h1 i18n-text="optionsAdvanced">
|
||||||
|
@ -243,11 +237,8 @@
|
||||||
<div class="items">
|
<div class="items">
|
||||||
<label class="chromium-only">
|
<label class="chromium-only">
|
||||||
<span i18n-text="optionsAdvancedStyleViaXhr">
|
<span i18n-text="optionsAdvancedStyleViaXhr">
|
||||||
<a data-cmd="note"
|
<a i18n-title="optionsAdvancedStyleViaXhrNote"
|
||||||
i18n-title="optionsAdvancedStyleViaXhrNote"
|
data-cmd="note" href="#" class="svg-inline-wrapper">
|
||||||
href="#"
|
|
||||||
class="svg-inline-wrapper"
|
|
||||||
tabindex="0">
|
|
||||||
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
|
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
|
@ -256,14 +247,23 @@
|
||||||
<span></span>
|
<span></span>
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
|
<label>
|
||||||
|
<span>
|
||||||
|
<span i18n-html="optionsAdvancedPatchCsp"></span>
|
||||||
|
<a i18n-title="optionsAdvancedPatchCspNote"
|
||||||
|
data-cmd="note" href="#" class="svg-inline-wrapper">
|
||||||
|
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
|
||||||
|
</a>
|
||||||
|
</span>
|
||||||
|
<span class="onoffswitch">
|
||||||
|
<input type="checkbox" id="patchCsp" class="slider">
|
||||||
|
<span></span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
<label>
|
<label>
|
||||||
<span i18n-text="optionsAdvancedExposeIframes">
|
<span i18n-text="optionsAdvancedExposeIframes">
|
||||||
<a data-cmd="note"
|
<a i18n-title="optionsAdvancedExposeIframesNote"
|
||||||
i18n-data-title="optionsAdvancedExposeIframesNote"
|
data-cmd="note" href="#" class="svg-inline-wrapper">
|
||||||
i18n-title="optionsAdvancedExposeIframesNote"
|
|
||||||
href="#"
|
|
||||||
class="svg-inline-wrapper"
|
|
||||||
tabindex="0">
|
|
||||||
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
|
<svg class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
|
||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
|
|
|
@ -238,6 +238,7 @@ function setupRadioButtons() {
|
||||||
function splitLongTooltips() {
|
function splitLongTooltips() {
|
||||||
for (const el of $$('[title]')) {
|
for (const el of $$('[title]')) {
|
||||||
el.dataset.title = el.title;
|
el.dataset.title = el.title;
|
||||||
|
el.title = el.title.replace(/<\/?\w+>/g, ''); // strip html tags
|
||||||
if (el.title.length < 50) {
|
if (el.title.length < 50) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,23 @@
|
||||||
/* global configDialog hotkeys msg
|
/* global
|
||||||
getActiveTab CHROME FIREFOX URLS API onDOMready $ $$ prefs
|
$
|
||||||
setupLivePrefs template t $create animateElement
|
$$
|
||||||
tryJSONparse CHROME_HAS_BORDER_BUG */
|
$create
|
||||||
|
animateElement
|
||||||
|
API
|
||||||
|
CHROME
|
||||||
|
CHROME_HAS_BORDER_BUG
|
||||||
|
configDialog
|
||||||
|
FIREFOX
|
||||||
|
getActiveTab
|
||||||
|
hotkeys
|
||||||
|
msg
|
||||||
|
onDOMready
|
||||||
|
prefs
|
||||||
|
setupLivePrefs
|
||||||
|
t
|
||||||
|
tryJSONparse
|
||||||
|
URLS
|
||||||
|
*/
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
@ -121,7 +137,6 @@ function initPopup(frames) {
|
||||||
|
|
||||||
Object.assign($('#popup-manage-button'), {
|
Object.assign($('#popup-manage-button'), {
|
||||||
onclick: handleEvent.openManager,
|
onclick: handleEvent.openManager,
|
||||||
onmouseup: handleEvent.openManager,
|
|
||||||
oncontextmenu: handleEvent.openManager,
|
oncontextmenu: handleEvent.openManager,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -165,7 +180,7 @@ function initPopup(frames) {
|
||||||
setTimeout(ping, 100, tab, --retryCountdown);
|
setTimeout(ping, 100, tab, --retryCountdown);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const info = template.unreachableInfo;
|
const info = t.template.unreachableInfo;
|
||||||
if (!FIREFOX) {
|
if (!FIREFOX) {
|
||||||
// Chrome "Allow access to file URLs" in chrome://extensions message
|
// Chrome "Allow access to file URLs" in chrome://extensions message
|
||||||
info.appendChild($create('p', t('unreachableFileHint')));
|
info.appendChild($create('p', t('unreachableFileHint')));
|
||||||
|
@ -204,7 +219,7 @@ function createWriterElement(frame) {
|
||||||
const targets = $create('span');
|
const targets = $create('span');
|
||||||
|
|
||||||
// For this URL
|
// For this URL
|
||||||
const urlLink = template.writeStyle.cloneNode(true);
|
const urlLink = t.template.writeStyle.cloneNode(true);
|
||||||
const isAboutBlank = url === ABOUT_BLANK;
|
const isAboutBlank = url === ABOUT_BLANK;
|
||||||
Object.assign(urlLink, {
|
Object.assign(urlLink, {
|
||||||
href: 'edit.html?url-prefix=' + encodeURIComponent(url),
|
href: 'edit.html?url-prefix=' + encodeURIComponent(url),
|
||||||
|
@ -233,7 +248,7 @@ function createWriterElement(frame) {
|
||||||
if (domains.length > 1 && numParts === 1) {
|
if (domains.length > 1 && numParts === 1) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const domainLink = template.writeStyle.cloneNode(true);
|
const domainLink = t.template.writeStyle.cloneNode(true);
|
||||||
Object.assign(domainLink, {
|
Object.assign(domainLink, {
|
||||||
href: 'edit.html?domain=' + encodeURIComponent(domain),
|
href: 'edit.html?domain=' + encodeURIComponent(domain),
|
||||||
textContent: numParts > 2 ? domain.split('.')[0] : domain,
|
textContent: numParts > 2 ? domain.split('.')[0] : domain,
|
||||||
|
@ -322,7 +337,7 @@ function showStyles(frameResults) {
|
||||||
if (entries.size) {
|
if (entries.size) {
|
||||||
resortEntries([...entries.values()]);
|
resortEntries([...entries.values()]);
|
||||||
} else {
|
} else {
|
||||||
installed.appendChild(template.noStyles);
|
installed.appendChild(t.template.noStyles);
|
||||||
}
|
}
|
||||||
window.dispatchEvent(new Event('showStyles:done'));
|
window.dispatchEvent(new Event('showStyles:done'));
|
||||||
}
|
}
|
||||||
|
@ -337,14 +352,14 @@ function resortEntries(entries) {
|
||||||
function createStyleElement(style) {
|
function createStyleElement(style) {
|
||||||
let entry = $.entry(style);
|
let entry = $.entry(style);
|
||||||
if (!entry) {
|
if (!entry) {
|
||||||
entry = template.style.cloneNode(true);
|
entry = t.template.style.cloneNode(true);
|
||||||
entry.setAttribute('style-id', style.id);
|
entry.setAttribute('style-id', style.id);
|
||||||
Object.assign(entry, {
|
Object.assign(entry, {
|
||||||
id: ENTRY_ID_PREFIX_RAW + style.id,
|
id: ENTRY_ID_PREFIX_RAW + style.id,
|
||||||
styleId: style.id,
|
styleId: style.id,
|
||||||
styleIsUsercss: Boolean(style.usercssData),
|
styleIsUsercss: Boolean(style.usercssData),
|
||||||
onmousedown: handleEvent.maybeEdit,
|
onmousedown: handleEvent.maybeEdit,
|
||||||
styleMeta: style
|
styleMeta: style,
|
||||||
});
|
});
|
||||||
const checkbox = $('.checker', entry);
|
const checkbox = $('.checker', entry);
|
||||||
Object.assign(checkbox, {
|
Object.assign(checkbox, {
|
||||||
|
@ -384,7 +399,7 @@ function createStyleElement(style) {
|
||||||
|
|
||||||
$('.delete', entry).onclick = handleEvent.delete;
|
$('.delete', entry).onclick = handleEvent.delete;
|
||||||
|
|
||||||
const indicator = template.regexpProblemIndicator.cloneNode(true);
|
const indicator = t.template.regexpProblemIndicator.cloneNode(true);
|
||||||
indicator.appendChild(document.createTextNode('!'));
|
indicator.appendChild(document.createTextNode('!'));
|
||||||
indicator.onclick = handleEvent.indicator;
|
indicator.onclick = handleEvent.indicator;
|
||||||
$('.main-controls', entry).appendChild(indicator);
|
$('.main-controls', entry).appendChild(indicator);
|
||||||
|
@ -587,7 +602,7 @@ Object.assign(handleEvent, {
|
||||||
|
|
||||||
indicator(event) {
|
indicator(event) {
|
||||||
const entry = handleEvent.getClickedStyleElement(event);
|
const entry = handleEvent.getClickedStyleElement(event);
|
||||||
const info = template.regexpProblemExplanation.cloneNode(true);
|
const info = t.template.regexpProblemExplanation.cloneNode(true);
|
||||||
$.remove('#' + info.id);
|
$.remove('#' + info.id);
|
||||||
$$('a', info).forEach(el => (el.onclick = handleEvent.openURLandHide));
|
$$('a', info).forEach(el => (el.onclick = handleEvent.openURLandHide));
|
||||||
$$('button', info).forEach(el => (el.onclick = handleEvent.closeExplanation));
|
$$('button', info).forEach(el => (el.onclick = handleEvent.closeExplanation));
|
||||||
|
@ -638,17 +653,10 @@ Object.assign(handleEvent, {
|
||||||
},
|
},
|
||||||
|
|
||||||
openManager(event) {
|
openManager(event) {
|
||||||
if (event.button === 2 && !tabURL) return;
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (!this.eventHandled) {
|
const isSearch = tabURL && (event.shiftKey || event.button === 2);
|
||||||
// FIXME: this only works if popup is closed
|
API.openManage(isSearch ? {search: tabURL, searchMode: 'url'} : {});
|
||||||
this.eventHandled = true;
|
window.close();
|
||||||
API.openManage({
|
|
||||||
search: tabURL && (event.shiftKey || event.button === 2) ?
|
|
||||||
`url:${tabURL}` : null
|
|
||||||
});
|
|
||||||
window.close();
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
copyContent(event) {
|
copyContent(event) {
|
||||||
|
@ -684,7 +692,7 @@ function handleDelete(id) {
|
||||||
const el = $.entry(id);
|
const el = $.entry(id);
|
||||||
if (el) {
|
if (el) {
|
||||||
el.remove();
|
el.remove();
|
||||||
if (!$('.entry')) installed.appendChild(template.noStyles);
|
if (!$('.entry')) installed.appendChild(t.template.noStyles);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -714,9 +722,9 @@ async function getStyleDataMerged(url, id) {
|
||||||
function blockPopup(isBlocked = true) {
|
function blockPopup(isBlocked = true) {
|
||||||
document.body.classList.toggle('blocked', isBlocked);
|
document.body.classList.toggle('blocked', isBlocked);
|
||||||
if (isBlocked) {
|
if (isBlocked) {
|
||||||
document.body.prepend(template.unavailableInfo);
|
document.body.prepend(t.template.unavailableInfo);
|
||||||
} else {
|
} else {
|
||||||
template.unavailableInfo.remove();
|
t.template.unavailableInfo.remove();
|
||||||
template.noStyles.remove();
|
t.template.noStyles.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,18 @@
|
||||||
/* global URLS tabURL handleEvent $ $$ prefs template FIREFOX debounce
|
/* global
|
||||||
$create t API tWordBreak formatDate tryCatch download */
|
$
|
||||||
|
$$
|
||||||
|
$create
|
||||||
|
API
|
||||||
|
debounce
|
||||||
|
download
|
||||||
|
FIREFOX
|
||||||
|
handleEvent
|
||||||
|
prefs
|
||||||
|
t
|
||||||
|
tabURL
|
||||||
|
tryCatch
|
||||||
|
URLS
|
||||||
|
*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
window.addEventListener('showStyles:done', () => {
|
window.addEventListener('showStyles:done', () => {
|
||||||
|
@ -103,7 +116,7 @@ window.addEventListener('showStyles:done', () => {
|
||||||
const navOnClick = {prev, next};
|
const navOnClick = {prev, next};
|
||||||
for (const place of ['top', 'bottom']) {
|
for (const place of ['top', 'bottom']) {
|
||||||
const nav = $(`.search-results-nav[data-type="${place}"]`);
|
const nav = $(`.search-results-nav[data-type="${place}"]`);
|
||||||
nav.appendChild(template.searchNav.cloneNode(true));
|
nav.appendChild(t.template.searchNav.cloneNode(true));
|
||||||
dom.nav[place] = nav;
|
dom.nav[place] = nav;
|
||||||
for (const child of $$('[data-type]', nav)) {
|
for (const child of $$('[data-type]', nav)) {
|
||||||
const type = child.dataset.type;
|
const type = child.dataset.type;
|
||||||
|
@ -181,7 +194,7 @@ window.addEventListener('showStyles:done', () => {
|
||||||
results = await search({retry});
|
results = await search({retry});
|
||||||
}
|
}
|
||||||
if (results.length) {
|
if (results.length) {
|
||||||
const installedStyles = await API.getAllStyles(true);
|
const installedStyles = await API.getAllStyles();
|
||||||
const allUsoIds = new Set(installedStyles.map(calcUsoId));
|
const allUsoIds = new Set(installedStyles.map(calcUsoId));
|
||||||
results = results.filter(r => !allUsoIds.has(r.i));
|
results = results.filter(r => !allUsoIds.has(r.i));
|
||||||
}
|
}
|
||||||
|
@ -257,7 +270,7 @@ window.addEventListener('showStyles:done', () => {
|
||||||
* @returns {Node}
|
* @returns {Node}
|
||||||
*/
|
*/
|
||||||
function createSearchResultNode(result) {
|
function createSearchResultNode(result) {
|
||||||
const entry = template.searchResult.cloneNode(true);
|
const entry = t.template.searchResult.cloneNode(true);
|
||||||
const {
|
const {
|
||||||
i: id,
|
i: id,
|
||||||
n: name,
|
n: name,
|
||||||
|
@ -273,10 +286,10 @@ window.addEventListener('showStyles:done', () => {
|
||||||
// title
|
// title
|
||||||
Object.assign($('.search-result-title', entry), {
|
Object.assign($('.search-result-title', entry), {
|
||||||
onclick: handleEvent.openURLandHide,
|
onclick: handleEvent.openURLandHide,
|
||||||
href: URLS.usoArchive + `?category=${category}&style=${id}`
|
href: URLS.usoArchive + `?category=${category}&style=${id}`,
|
||||||
});
|
});
|
||||||
$('.search-result-title span', entry).textContent =
|
$('.search-result-title span', entry).textContent =
|
||||||
tWordBreak(name.length < 300 ? name : name.slice(0, 300) + '...');
|
t.breakWord(name.length < 300 ? name : name.slice(0, 300) + '...');
|
||||||
// screenshot
|
// screenshot
|
||||||
const auto = URLS.uso + `auto_style_screenshots/${id}${USO_AUTO_PIC_SUFFIX}`;
|
const auto = URLS.uso + `auto_style_screenshots/${id}${USO_AUTO_PIC_SUFFIX}`;
|
||||||
Object.assign($('.search-result-screenshot', entry), {
|
Object.assign($('.search-result-screenshot', entry), {
|
||||||
|
@ -303,7 +316,7 @@ window.addEventListener('showStyles:done', () => {
|
||||||
// time
|
// time
|
||||||
Object.assign($('[data-type="updated"] time', entry), {
|
Object.assign($('[data-type="updated"] time', entry), {
|
||||||
dateTime: updateTime * 1000,
|
dateTime: updateTime * 1000,
|
||||||
textContent: formatDate(updateTime * 1000)
|
textContent: t.formatDate(updateTime * 1000),
|
||||||
});
|
});
|
||||||
// totals
|
// totals
|
||||||
$('[data-type="weekly"] dd', entry).textContent = formatNumber(weeklyInstalls);
|
$('[data-type="weekly"] dd', entry).textContent = formatNumber(weeklyInstalls);
|
||||||
|
|
|
@ -32,37 +32,37 @@ const files = {
|
||||||
'mode/css',
|
'mode/css',
|
||||||
'mode/javascript',
|
'mode/javascript',
|
||||||
'mode/stylus',
|
'mode/stylus',
|
||||||
'theme/*'
|
'theme/*',
|
||||||
],
|
],
|
||||||
'jsonlint': [
|
'jsonlint': [
|
||||||
'lib/jsonlint.js → jsonlint.js',
|
'lib/jsonlint.js → jsonlint.js',
|
||||||
'README.md → LICENSE'
|
'README.md → LICENSE',
|
||||||
],
|
],
|
||||||
'less-bundle': [
|
'less-bundle': [
|
||||||
'dist/less.min.js → less.min.js'
|
'dist/less.min.js → less.min.js',
|
||||||
],
|
],
|
||||||
'lz-string-unsafe': [
|
'lz-string-unsafe': [
|
||||||
'lz-string-unsafe.min.js'
|
'lz-string-unsafe.min.js',
|
||||||
],
|
],
|
||||||
'semver-bundle': [
|
'semver-bundle': [
|
||||||
'dist/semver.js → semver.js'
|
'dist/semver.js → semver.js',
|
||||||
],
|
],
|
||||||
'stylelint-bundle': [
|
'stylelint-bundle': [
|
||||||
'stylelint-bundle.min.js',
|
'stylelint-bundle.min.js',
|
||||||
'https://github.com/stylelint/stylelint/raw/{VERSION}/LICENSE → LICENSE'
|
'https://github.com/stylelint/stylelint/raw/{VERSION}/LICENSE → LICENSE',
|
||||||
],
|
],
|
||||||
'stylus-lang-bundle': [
|
'stylus-lang-bundle': [
|
||||||
'dist/stylus-renderer.min.js → stylus-renderer.min.js'
|
'dist/stylus-renderer.min.js → stylus-renderer.min.js',
|
||||||
],
|
],
|
||||||
'usercss-meta': [
|
'usercss-meta': [
|
||||||
'dist/usercss-meta.min.js → usercss-meta.min.js'
|
'dist/usercss-meta.min.js → usercss-meta.min.js',
|
||||||
],
|
],
|
||||||
'db-to-cloud': [
|
'db-to-cloud': [
|
||||||
'dist/db-to-cloud.min.js → db-to-cloud.min.js'
|
'dist/db-to-cloud.min.js → db-to-cloud.min.js',
|
||||||
],
|
],
|
||||||
'webext-launch-web-auth-flow': [
|
'webext-launch-web-auth-flow': [
|
||||||
'dist/webext-launch-web-auth-flow.min.js → webext-launch-web-auth-flow.min.js'
|
'dist/webext-launch-web-auth-flow.min.js → webext-launch-web-auth-flow.min.js',
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
main().catch(console.error);
|
main().catch(console.error);
|
||||||
|
@ -87,12 +87,15 @@ async function generateThemeList() {
|
||||||
.map(name => name.replace('.css', ''))
|
.map(name => name.replace('.css', ''))
|
||||||
.sort();
|
.sort();
|
||||||
return endent`
|
return endent`
|
||||||
/* exported CODEMIRROR_THEMES */
|
/* Do not edit. This file is auto-generated by build-vendor.js */
|
||||||
// this file is generated by update-codemirror-themes.js
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const CODEMIRROR_THEMES = ${JSON.stringify(themes, null, 2)};
|
/* exported CODEMIRROR_THEMES */
|
||||||
`.replace(/"/g, "'") + '\n';
|
const CODEMIRROR_THEMES = [
|
||||||
|
${
|
||||||
|
themes.map(t => ` '${t.replace(/'/g, '\\$&')}',\n`).join('')
|
||||||
|
}];
|
||||||
|
` + '\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
async function copyLicense(pkg) {
|
async function copyLicense(pkg) {
|
||||||
|
|
|
@ -16,7 +16,7 @@ function createZip({isFirefox} = {}) {
|
||||||
'package-lock.json',
|
'package-lock.json',
|
||||||
'yarn.lock',
|
'yarn.lock',
|
||||||
'*.zip',
|
'*.zip',
|
||||||
'*.map'
|
'*.map',
|
||||||
];
|
];
|
||||||
|
|
||||||
const file = fs.createWriteStream(fileName);
|
const file = fs.createWriteStream(fileName);
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
{hex: '#00ffff', start: .50},
|
{hex: '#00ffff', start: .50},
|
||||||
{hex: '#0000ff', start: .67},
|
{hex: '#0000ff', start: .67},
|
||||||
{hex: '#ff00ff', start: .83},
|
{hex: '#ff00ff', start: .83},
|
||||||
{hex: '#ff0000', start: 1}
|
{hex: '#ff0000', start: 1},
|
||||||
];
|
];
|
||||||
const MIN_HEIGHT = 220;
|
const MIN_HEIGHT = 220;
|
||||||
const MARGIN = 8;
|
const MARGIN = 8;
|
||||||
|
@ -119,7 +119,7 @@
|
||||||
$inputGroups.hex = $(['input-group', 'hex'], [
|
$inputGroups.hex = $(['input-group', 'hex'], [
|
||||||
$(['input-field', 'hex'], [
|
$(['input-field', 'hex'], [
|
||||||
$hexCode = $('input', {tag: 'input', type: 'text', spellcheck: false,
|
$hexCode = $('input', {tag: 'input', type: 'text', spellcheck: false,
|
||||||
pattern: /^\s*#([a-fA-F\d]{3}([a-fA-F\d]([a-fA-F\d]{2}([a-fA-F\d]{2})?)?)?)\s*$/.source
|
pattern: /^\s*#([a-fA-F\d]{3}([a-fA-F\d]([a-fA-F\d]{2}([a-fA-F\d]{2})?)?)?)\s*$/.source,
|
||||||
}),
|
}),
|
||||||
$('title', [
|
$('title', [
|
||||||
$hexLettercase.true = $('title-action', {onclick: onHexLettercaseClicked}, 'HEX'),
|
$hexLettercase.true = $('title-action', {onclick: onHexLettercaseClicked}, 'HEX'),
|
||||||
|
@ -186,7 +186,7 @@
|
||||||
Object.defineProperty($inputs.hsl, 'color', {get: inputsToHSL});
|
Object.defineProperty($inputs.hsl, 'color', {get: inputsToHSL});
|
||||||
Object.defineProperty($inputs, 'color', {get: () => $inputs[currentFormat].color});
|
Object.defineProperty($inputs, 'color', {get: () => $inputs[currentFormat].color});
|
||||||
Object.defineProperty($inputs, 'colorString', {
|
Object.defineProperty($inputs, 'colorString', {
|
||||||
get: () => currentFormat && colorConverter.format($inputs[currentFormat].color)
|
get: () => currentFormat && colorConverter.format($inputs[currentFormat].color),
|
||||||
});
|
});
|
||||||
|
|
||||||
HUE_COLORS.forEach(color => Object.assign(color, colorConverter.parse(color.hex)));
|
HUE_COLORS.forEach(color => Object.assign(color, colorConverter.parse(color.hex)));
|
||||||
|
|
|
@ -73,6 +73,11 @@
|
||||||
const height = cm.display.lastWrapHeight;
|
const height = cm.display.lastWrapHeight;
|
||||||
if (!height || !textHeight) return;
|
if (!height || !textHeight) return;
|
||||||
maxRenderChunkSize = Math.max(20, Math.ceil(height / textHeight));
|
maxRenderChunkSize = Math.max(20, Math.ceil(height / textHeight));
|
||||||
|
const state = cm.state.colorpicker;
|
||||||
|
if (state.colorizeOnUpdate) {
|
||||||
|
state.colorizeOnUpdate = false;
|
||||||
|
colorizeAll(state);
|
||||||
|
}
|
||||||
cm.off('update', CM_EVENTS.update);
|
cm.off('update', CM_EVENTS.update);
|
||||||
},
|
},
|
||||||
mousedown(cm, event) {
|
mousedown(cm, event) {
|
||||||
|
@ -164,12 +169,14 @@
|
||||||
|
|
||||||
function colorizeAll(state) {
|
function colorizeAll(state) {
|
||||||
const {cm} = state;
|
const {cm} = state;
|
||||||
|
const {viewFrom, viewTo} = cm.display;
|
||||||
|
if (!viewTo) {
|
||||||
|
state.colorizeOnUpdate = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
const {curOp} = cm;
|
const {curOp} = cm;
|
||||||
if (!curOp) cm.startOperation();
|
if (!curOp) cm.startOperation();
|
||||||
|
|
||||||
const viewFrom = cm.display.viewFrom;
|
|
||||||
const viewTo = (cm.display.viewTo || maxRenderChunkSize - 1) + 1;
|
|
||||||
|
|
||||||
state.line = viewFrom;
|
state.line = viewFrom;
|
||||||
state.inComment = null;
|
state.inComment = null;
|
||||||
state.now = performance.now();
|
state.now = performance.now();
|
||||||
|
|
|
@ -2057,7 +2057,7 @@ self.parserlib = (() => {
|
||||||
return m.toString(p);
|
return m.toString(p);
|
||||||
}).join(required === false ? ' || ' : ' && ');
|
}).join(required === false ? ' || ' : ' && ');
|
||||||
return prec > p ? `[ ${s} ]` : s;
|
return prec > p ? `[ ${s} ]` : s;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
Matcher.parseGrammar = (() => {
|
Matcher.parseGrammar = (() => {
|
||||||
|
@ -5300,7 +5300,7 @@ self.parserlib = (() => {
|
||||||
_readDeclarations({
|
_readDeclarations({
|
||||||
checkStart = true,
|
checkStart = true,
|
||||||
readMargins = false,
|
readMargins = false,
|
||||||
stopAfterBrace = false
|
stopAfterBrace = false,
|
||||||
} = {}) {
|
} = {}) {
|
||||||
const stream = this._tokenStream;
|
const stream = this._tokenStream;
|
||||||
if (checkStart) stream.mustMatch(Tokens.LBRACE);
|
if (checkStart) stream.mustMatch(Tokens.LBRACE);
|
||||||
|
|
Loading…
Reference in New Issue
Block a user