Merge branch 'dev-usercss-meta' into dev-exclusions
This commit is contained in:
commit
bd4a453f45
|
@ -738,6 +738,194 @@
|
|||
"message": "Show active style count",
|
||||
"description": "Label (must be very short) for the checkbox in the toolbar button context menu controlling toolbar badge text."
|
||||
},
|
||||
"meta_invalidCheckboxDefault": {
|
||||
"message": "Invalid @var checkbox: value must be 0 or 1",
|
||||
"description": "Error displayed when the value of @var checkbox is invalid"
|
||||
},
|
||||
"meta_invalidColor": {
|
||||
"message": "Invalid @var color: $color$ is not a color",
|
||||
"description": "Error displayed when the value of @var color is invalid",
|
||||
"placeholders": {
|
||||
"color": {
|
||||
"content": "$1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"meta_invalidRange": {
|
||||
"message": "Invalid @var $type$: value must be a number or an array",
|
||||
"description": "Error displayed when the value of @var range or @var number is invalid",
|
||||
"placeholders": {
|
||||
"type": {
|
||||
"content": "$1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"meta_invalidRangeMultipleUnits": {
|
||||
"message": "Invalid @var $type$: multiple units are defined",
|
||||
"description": "Error displayed when the value of @var range or @var number is invalid",
|
||||
"placeholders": {
|
||||
"type": {
|
||||
"content": "$1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"meta_invalidRangeTooManyValues": {
|
||||
"message": "Invalid @var $type$: the array contains too many items",
|
||||
"description": "Error displayed when the value of @var range or @var number is invalid",
|
||||
"placeholders": {
|
||||
"type": {
|
||||
"content": "$1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"meta_invalidRangeValue": {
|
||||
"message": "Invalid @var $type$: items in the array must be number, string, or null",
|
||||
"description": "Error displayed when the value of @var range or @var number is invalid",
|
||||
"placeholders": {
|
||||
"type": {
|
||||
"content": "$1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"meta_invalidRangeDefault": {
|
||||
"message": "Invalid @var $type$: default value is null",
|
||||
"description": "Error displayed when the value of @var range or @var number is invalid",
|
||||
"placeholders": {
|
||||
"type": {
|
||||
"content": "$1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"meta_invalidRangeMin": {
|
||||
"message": "Invalid @var $type$: default value is lower than the minimum",
|
||||
"description": "Error displayed when the value of @var range or @var number is invalid",
|
||||
"placeholders": {
|
||||
"type": {
|
||||
"content": "$1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"meta_invalidRangeMax": {
|
||||
"message": "Invalid @var $type$: default value is larger than the maximum",
|
||||
"description": "Error displayed when the value of @var range or @var number is invalid",
|
||||
"placeholders": {
|
||||
"type": {
|
||||
"content": "$1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"meta_invalidRangeStep": {
|
||||
"message": "Invalid @var $type$: default value is not a mutiple of the step",
|
||||
"description": "Error displayed when the value of @var range or @var number is invalid",
|
||||
"placeholders": {
|
||||
"type": {
|
||||
"content": "$1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"meta_invalidSelectEmptyOptions": {
|
||||
"message": "Invalid @var select: options list is empty",
|
||||
"description": "Error displayed when the value of @var select is invalid"
|
||||
},
|
||||
"meta_invalidSelectMultipleDefaults": {
|
||||
"message": "Invalid @var select: multiple default options are defined",
|
||||
"description": "Error displayed when the value of @var select is invalid"
|
||||
},
|
||||
"meta_invalidSelectValueMismatch": {
|
||||
"message": "Invalid @var select: value doesn't exist in the option list",
|
||||
"description": "Error displayed when the value of @var select is invalid"
|
||||
},
|
||||
"meta_invalidURLProtocol": {
|
||||
"message": "Invalid URL protocol. Only http and https are allowed: $protocol$",
|
||||
"description": "Error displayed when the protocol of the URL is invalid",
|
||||
"placeholders": {
|
||||
"protocol": {
|
||||
"content": "$1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"meta_invalidVersion": {
|
||||
"message": "Invalid version number. The value doesn't match SemVer pattern: $version$",
|
||||
"description": "Error displayed when @version is invalid",
|
||||
"placeholders": {
|
||||
"version": {
|
||||
"content": "$1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"meta_invalidNumber": {
|
||||
"message": "Expect a number",
|
||||
"description": "Error displayed when the value is expected to be a number"
|
||||
},
|
||||
"meta_invalidString": {
|
||||
"message": "Expect a quoted string",
|
||||
"description": "Error displayed when the value is expected to be a quoted string"
|
||||
},
|
||||
"meta_invalidWord": {
|
||||
"message": "Expect a word",
|
||||
"description": "Error displayed when the value is expected to be a word"
|
||||
},
|
||||
"meta_missingChar": {
|
||||
"message": "Expect characters: $chars$",
|
||||
"description": "Error displayed when the value is expected to be some characters",
|
||||
"placeholders": {
|
||||
"chars": {
|
||||
"content": "$1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"meta_missingEOT": {
|
||||
"message": "Expect EOT data",
|
||||
"description": "Error displayed when the value is expected to be an EOT list"
|
||||
},
|
||||
"meta_missingMandatory": {
|
||||
"message": "Missing mandatory metadata: $keys$",
|
||||
"description": "Error displayed when mandatory keys are missing",
|
||||
"placeholders": {
|
||||
"keys": {
|
||||
"content": "$1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"meta_unknownJSONLiteral": {
|
||||
"message": "Invalid JSON: $literal$ is not a valid JSON literal",
|
||||
"description": "Error displayed when JSON value is invalid",
|
||||
"placeholders": {
|
||||
"literal": {
|
||||
"content": "$1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"meta_unknownMeta": {
|
||||
"message": "Unknown metadata: $key$",
|
||||
"description": "Error displayed when unknown metadata is parsed",
|
||||
"placeholders": {
|
||||
"key": {
|
||||
"content": "$1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"meta_unknownVarType": {
|
||||
"message": "Unknown @$varkey$ type: $vartype$",
|
||||
"description": "Error displayed when unknown variable type is parsed",
|
||||
"placeholders": {
|
||||
"varkey": {
|
||||
"content": "$1"
|
||||
},
|
||||
"vartype": {
|
||||
"content": "$2"
|
||||
}
|
||||
}
|
||||
},
|
||||
"meta_unknownPreprocessor": {
|
||||
"message": "Unknown @preprocessor: $preprocessor$",
|
||||
"description": "Error displayed when unknown @preprocessor is parsed",
|
||||
"placeholders": {
|
||||
"preprocessor": {
|
||||
"content": "$1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"noStylesForSite": {
|
||||
"message": "No styles installed for this site.",
|
||||
"description": "Text displayed when no styles are installed for the current site"
|
||||
|
@ -1087,50 +1275,6 @@
|
|||
},
|
||||
"description": "Confirmation when re-installing a style"
|
||||
},
|
||||
"styleMetaErrorCheckbox": {
|
||||
"message": "Invalid @var checkbox: value must be 0 or 1",
|
||||
"description": "Error displayed when the value of @var checkbox is invalid"
|
||||
},
|
||||
"styleMetaErrorColor": {
|
||||
"message": "$color$ is not a valid color",
|
||||
"placeholders": {
|
||||
"color": {
|
||||
"content": "$1"
|
||||
}
|
||||
},
|
||||
"description": "Error displayed when the value of @var color is invalid"
|
||||
},
|
||||
"styleMetaErrorRangeOrNumber": {
|
||||
"message": "Invalid @var $type$: value must be an array containing at least one number at index zero",
|
||||
"description": "Error displayed when the value of @var number or @var range is invalid",
|
||||
"placeholders": {
|
||||
"type": {
|
||||
"content": "$1"
|
||||
}
|
||||
}
|
||||
},
|
||||
"styleMetaErrorPreprocessor": {
|
||||
"message": "Unsupported @preprocessor: $preprocessor$",
|
||||
"placeholders": {
|
||||
"preprocessor": {
|
||||
"content": "$1"
|
||||
}
|
||||
},
|
||||
"description": "Error displayed when the value of @preprocessor is not supported"
|
||||
},
|
||||
"styleMetaErrorSelectValueMismatch": {
|
||||
"message": "Invalid @select: value doesn't exist in the list",
|
||||
"description": "Error displayed when the value of @select is invalid"
|
||||
},
|
||||
"styleMissingMeta": {
|
||||
"message": "Missing metadata @$key$",
|
||||
"placeholders": {
|
||||
"key": {
|
||||
"content": "$1"
|
||||
}
|
||||
},
|
||||
"description": "Error displayed when a mandatory metadata is missing"
|
||||
},
|
||||
"styleMissingName": {
|
||||
"message": "Enter a name",
|
||||
"description": "Error displayed when user saves without providing a name"
|
||||
|
|
167
background/background-worker.js
Normal file
167
background/background-worker.js
Normal file
|
@ -0,0 +1,167 @@
|
|||
/* global workerUtil importScripts parseMozFormat metaParser styleCodeEmpty colorConverter */
|
||||
'use strict';
|
||||
|
||||
importScripts('/js/worker-util.js');
|
||||
const {loadScript, createAPI} = workerUtil;
|
||||
|
||||
createAPI({
|
||||
parseMozFormat(arg) {
|
||||
loadScript('/vendor-overwrites/csslint/parserlib.js', '/js/moz-parser.js');
|
||||
return parseMozFormat(arg);
|
||||
},
|
||||
compileUsercss,
|
||||
parseUsercssMeta(text, indexOffset = 0) {
|
||||
loadScript(
|
||||
'/vendor/usercss-meta/usercss-meta.min.js',
|
||||
'/vendor-overwrites/colorpicker/colorconverter.js',
|
||||
'/js/meta-parser.js'
|
||||
);
|
||||
return metaParser.parse(text, indexOffset);
|
||||
},
|
||||
nullifyInvalidVars(vars) {
|
||||
loadScript(
|
||||
'/vendor/usercss-meta/usercss-meta.min.js',
|
||||
'/vendor-overwrites/colorpicker/colorconverter.js',
|
||||
'/js/meta-parser.js'
|
||||
);
|
||||
return metaParser.nullifyInvalidVars(vars);
|
||||
}
|
||||
});
|
||||
|
||||
function compileUsercss(preprocessor, code, vars) {
|
||||
loadScript('/vendor-overwrites/csslint/parserlib.js', '/js/moz-parser.js');
|
||||
const builder = getUsercssCompiler(preprocessor);
|
||||
vars = simpleVars(vars);
|
||||
return Promise.resolve(builder.preprocess ? builder.preprocess(code, vars) : code)
|
||||
.then(code => parseMozFormat({code}))
|
||||
.then(({sections, errors}) => {
|
||||
if (builder.postprocess) {
|
||||
builder.postprocess(sections, vars);
|
||||
}
|
||||
return {sections, errors};
|
||||
});
|
||||
|
||||
function simpleVars(vars) {
|
||||
if (!vars) {
|
||||
return {};
|
||||
}
|
||||
// simplify vars by merging `va.default` to `va.value`, so BUILDER don't
|
||||
// need to test each va's default value.
|
||||
return Object.keys(vars).reduce((output, key) => {
|
||||
const va = vars[key];
|
||||
output[key] = Object.assign({}, va, {
|
||||
value: va.value === null || va.value === undefined ?
|
||||
getVarValue(va, 'default') : getVarValue(va, 'value')
|
||||
});
|
||||
return output;
|
||||
}, {});
|
||||
}
|
||||
|
||||
function getVarValue(va, prop) {
|
||||
if (va.type === 'select' || va.type === 'dropdown' || va.type === 'image') {
|
||||
// TODO: handle customized image
|
||||
return va.options.find(o => o.name === va[prop]).value;
|
||||
}
|
||||
if ((va.type === 'number' || va.type === 'range') && va.units) {
|
||||
return va[prop] + va.units;
|
||||
}
|
||||
return va[prop];
|
||||
}
|
||||
}
|
||||
|
||||
function getUsercssCompiler(preprocessor) {
|
||||
const BUILDER = {
|
||||
default: {
|
||||
postprocess(sections, vars) {
|
||||
loadScript('/js/sections-util.js');
|
||||
let varDef = Object.keys(vars).map(k => ` --${k}: ${vars[k].value};\n`).join('');
|
||||
if (!varDef) return;
|
||||
varDef = ':root {\n' + varDef + '}\n';
|
||||
for (const section of sections) {
|
||||
if (!styleCodeEmpty(section.code)) {
|
||||
section.code = varDef + section.code;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
stylus: {
|
||||
preprocess(source, vars) {
|
||||
loadScript('/vendor/stylus-lang-bundle/stylus.min.js');
|
||||
return new Promise((resolve, reject) => {
|
||||
const varDef = Object.keys(vars).map(key => `${key} = ${vars[key].value};\n`).join('');
|
||||
if (!Error.captureStackTrace) Error.captureStackTrace = () => {};
|
||||
self.stylus(varDef + source).render((err, output) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(output);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
less: {
|
||||
preprocess(source, vars) {
|
||||
if (!self.less) {
|
||||
self.less = {
|
||||
logLevel: 0,
|
||||
useFileCache: false,
|
||||
};
|
||||
}
|
||||
loadScript('/vendor/less/less.min.js');
|
||||
const varDefs = Object.keys(vars).map(key => `@${key}:${vars[key].value};\n`).join('');
|
||||
return self.less.render(varDefs + source)
|
||||
.then(({css}) => css);
|
||||
}
|
||||
},
|
||||
uso: {
|
||||
preprocess(source, vars) {
|
||||
loadScript('/vendor-overwrites/colorpicker/colorconverter.js');
|
||||
const pool = new Map();
|
||||
return Promise.resolve(doReplace(source));
|
||||
|
||||
function getValue(name, rgb) {
|
||||
if (!vars.hasOwnProperty(name)) {
|
||||
if (name.endsWith('-rgb')) {
|
||||
return getValue(name.slice(0, -4), true);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (rgb) {
|
||||
if (vars[name].type === 'color') {
|
||||
const color = colorConverter.parse(vars[name].value);
|
||||
if (!color) return null;
|
||||
const {r, g, b} = color;
|
||||
return `${r}, ${g}, ${b}`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (vars[name].type === 'dropdown' || vars[name].type === 'select') {
|
||||
// prevent infinite recursion
|
||||
pool.set(name, '');
|
||||
return doReplace(vars[name].value);
|
||||
}
|
||||
return vars[name].value;
|
||||
}
|
||||
|
||||
function doReplace(text) {
|
||||
return text.replace(/\/\*\[\[([\w-]+)\]\]\*\//g, (match, name) => {
|
||||
if (!pool.has(name)) {
|
||||
const value = getValue(name);
|
||||
pool.set(name, value === null ? match : value);
|
||||
}
|
||||
return pool.get(name);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (preprocessor) {
|
||||
if (!BUILDER[preprocessor]) {
|
||||
throw new Error('unknwon preprocessor');
|
||||
}
|
||||
return BUILDER[preprocessor];
|
||||
}
|
||||
return BUILDER.default;
|
||||
}
|
|
@ -1,9 +1,13 @@
|
|||
/* global detectSloppyRegexps download prefs openURL FIREFOX CHROME VIVALDI
|
||||
openEditor debounce URLS ignoreChromeError queryTabs getTab
|
||||
usercss styleManager db msg navigatorUtil iconUtil
|
||||
*/
|
||||
usercss styleManager db msg navigatorUtil iconUtil */
|
||||
'use strict';
|
||||
|
||||
// eslint-disable-next-line no-var
|
||||
var backgroundWorker = workerUtil.createWorker({
|
||||
url: '/background/background-worker.js'
|
||||
});
|
||||
|
||||
window.API_METHODS = Object.assign(window.API_METHODS || {}, {
|
||||
getSectionsByUrl: styleManager.getSectionsByUrl,
|
||||
getSectionsById: styleManager.getSectionsById,
|
||||
|
@ -26,7 +30,7 @@ window.API_METHODS = Object.assign(window.API_METHODS || {}, {
|
|||
return download(msg.url, msg);
|
||||
},
|
||||
parseCss({code}) {
|
||||
return usercss.invokeWorker({action: 'parse', code});
|
||||
return backgroundWorker.parseMozFormat({code});
|
||||
},
|
||||
getPrefs: prefs.getAll,
|
||||
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
/* global importScripts parserlib parseMozFormat */
|
||||
'use strict';
|
||||
|
||||
importScripts('/vendor-overwrites/csslint/parserlib.js', '/js/moz-parser.js');
|
||||
parserlib.css.Tokens[parserlib.css.Tokens.COMMENT].hide = false;
|
||||
|
||||
self.onmessage = ({data}) => {
|
||||
self.postMessage(parseMozFormat(data));
|
||||
};
|
|
@ -153,8 +153,10 @@
|
|||
|
||||
function maybeUpdateUsercss() {
|
||||
// TODO: when sourceCode is > 100kB use http range request(s) for version check
|
||||
return download(style.updateUrl).then(text => {
|
||||
const json = usercss.buildMeta(text);
|
||||
return download(style.updateUrl)
|
||||
.then(text =>
|
||||
usercss.buildMeta(text)
|
||||
.then(json => {
|
||||
const {usercssData: {version}} = style;
|
||||
const {usercssData: {version: newVersion}} = json;
|
||||
switch (Math.sign(semverCompare(version, newVersion))) {
|
||||
|
@ -170,7 +172,8 @@
|
|||
return Promise.reject(STATES.ERROR_VERSION);
|
||||
}
|
||||
return usercss.buildCode(json);
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function maybeSave(json = {}) {
|
||||
|
|
|
@ -42,14 +42,13 @@
|
|||
if (style.usercssData) {
|
||||
return Promise.resolve(style);
|
||||
}
|
||||
try {
|
||||
const {sourceCode} = style;
|
||||
|
||||
// allow sourceCode to be normalized
|
||||
const {sourceCode} = style;
|
||||
delete style.sourceCode;
|
||||
return Promise.resolve(Object.assign(usercss.buildMeta(sourceCode), style));
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
|
||||
return usercss.buildMeta(sourceCode)
|
||||
.then(newStyle => Object.assign(newStyle, style));
|
||||
}
|
||||
|
||||
function assignVars(style) {
|
||||
|
@ -62,7 +61,8 @@
|
|||
style.id = dup.id;
|
||||
if (style.reason !== 'config') {
|
||||
// preserve style.vars during update
|
||||
usercss.assignVars(style, dup);
|
||||
return usercss.assignVars(style, dup)
|
||||
.then(() => style);
|
||||
}
|
||||
}
|
||||
return style;
|
||||
|
@ -95,7 +95,8 @@
|
|||
function doBuild(style) {
|
||||
if (vars) {
|
||||
const oldStyle = {usercssData: {vars}};
|
||||
usercss.assignVars(style, oldStyle);
|
||||
return usercss.assignVars(style, oldStyle)
|
||||
.then(() => usercss.buildCode(style));
|
||||
}
|
||||
return usercss.buildCode(style);
|
||||
}
|
||||
|
|
|
@ -70,6 +70,7 @@
|
|||
<script src="js/script-loader.js"></script>
|
||||
<script src="js/storage-util.js"></script>
|
||||
<script src="js/msg.js"></script>
|
||||
<script src="js/worker-util.js"></script>
|
||||
|
||||
<script src="content/apply.js"></script>
|
||||
|
||||
|
@ -81,8 +82,6 @@
|
|||
<link href="edit/codemirror-default.css" rel="stylesheet">
|
||||
<script src="edit/codemirror-default.js"></script>
|
||||
|
||||
<script src="edit/editor-worker.js"></script>
|
||||
|
||||
<script src="edit/util.js"></script>
|
||||
<script src="edit/regexp-tester.js"></script>
|
||||
<script src="edit/live-preview.js"></script>
|
||||
|
|
|
@ -351,8 +351,9 @@ CodeMirror.hint && (() => {
|
|||
}
|
||||
|
||||
// USO vars in usercss mode editor
|
||||
const list = Object.keys(editor.getStyle().usercssData.vars)
|
||||
.filter(name => name.startsWith(leftPart));
|
||||
const vars = editor.getStyle().usercssData.vars;
|
||||
const list = vars ?
|
||||
Object.keys(vars).filter(name => name.startsWith(leftPart)) : [];
|
||||
return {
|
||||
list,
|
||||
from: {line, ch: prev},
|
||||
|
|
|
@ -2,11 +2,14 @@
|
|||
createSourceEditor queryTabs sessionStorageHash getOwnTab FIREFOX API tryCatch
|
||||
closeCurrentTab messageBox debounce
|
||||
beautify
|
||||
moveFocus msg createSectionsEditor rerouteHotkeys
|
||||
*/
|
||||
moveFocus msg createSectionsEditor rerouteHotkeys */
|
||||
/* exported showCodeMirrorPopup */
|
||||
'use strict';
|
||||
|
||||
const editorWorker = workerUtil.createWorker({
|
||||
url: '/edit/editor-worker.js'
|
||||
});
|
||||
|
||||
let saveSizeOnClose;
|
||||
|
||||
// direct & reverse mapping of @-moz-document keywords and internal property names
|
||||
|
|
|
@ -1,118 +0,0 @@
|
|||
/* global importScripts parseMozFormat parserlib CSSLint require */
|
||||
'use strict';
|
||||
|
||||
createAPI({
|
||||
csslint: (code, config) => {
|
||||
loadParserLib();
|
||||
loadScript(['/vendor-overwrites/csslint/csslint.js']);
|
||||
return CSSLint.verify(code, config).messages
|
||||
.map(m => Object.assign(m, {rule: {id: m.rule.id}}));
|
||||
},
|
||||
stylelint: (code, config) => {
|
||||
loadScript(['/vendor/stylelint-bundle/stylelint-bundle.min.js']);
|
||||
return require('stylelint').lint({code, config});
|
||||
},
|
||||
parseMozFormat: data => {
|
||||
loadParserLib();
|
||||
loadScript(['/js/moz-parser.js']);
|
||||
return parseMozFormat(data);
|
||||
},
|
||||
getStylelintRules,
|
||||
getCsslintRules
|
||||
});
|
||||
|
||||
function getCsslintRules() {
|
||||
loadScript(['/vendor-overwrites/csslint/csslint.js']);
|
||||
return CSSLint.getRules().map(rule => {
|
||||
const output = {};
|
||||
for (const [key, value] of Object.entries(rule)) {
|
||||
if (typeof value !== 'function') {
|
||||
output[key] = value;
|
||||
}
|
||||
}
|
||||
return output;
|
||||
});
|
||||
}
|
||||
|
||||
function getStylelintRules() {
|
||||
loadScript(['/vendor/stylelint-bundle/stylelint-bundle.min.js']);
|
||||
const stylelint = require('stylelint');
|
||||
const options = {};
|
||||
const rxPossible = /\bpossible:("(?:[^"]*?)"|\[(?:[^\]]*?)\]|\{(?:[^}]*?)\})/g;
|
||||
const rxString = /"([-\w\s]{3,}?)"/g;
|
||||
for (const id of Object.keys(stylelint.rules)) {
|
||||
const ruleCode = String(stylelint.rules[id]);
|
||||
const sets = [];
|
||||
let m, mStr;
|
||||
while ((m = rxPossible.exec(ruleCode))) {
|
||||
const possible = m[1];
|
||||
const set = [];
|
||||
while ((mStr = rxString.exec(possible))) {
|
||||
const s = mStr[1];
|
||||
if (s.includes(' ')) {
|
||||
set.push(...s.split(/\s+/));
|
||||
} else {
|
||||
set.push(s);
|
||||
}
|
||||
}
|
||||
if (possible.includes('ignoreAtRules')) {
|
||||
set.push('ignoreAtRules');
|
||||
}
|
||||
if (possible.includes('ignoreShorthands')) {
|
||||
set.push('ignoreShorthands');
|
||||
}
|
||||
if (set.length) {
|
||||
sets.push(set);
|
||||
}
|
||||
}
|
||||
if (sets.length) {
|
||||
options[id] = sets;
|
||||
}
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
function loadParserLib() {
|
||||
if (typeof parserlib !== 'undefined') {
|
||||
return;
|
||||
}
|
||||
importScripts('/vendor-overwrites/csslint/parserlib.js');
|
||||
parserlib.css.Tokens[parserlib.css.Tokens.COMMENT].hide = false;
|
||||
}
|
||||
|
||||
const loadedUrls = new Set();
|
||||
function loadScript(urls) {
|
||||
urls = urls.filter(u => !loadedUrls.has(u));
|
||||
importScripts(...urls);
|
||||
urls.forEach(u => loadedUrls.add(u));
|
||||
}
|
||||
|
||||
function createAPI(methods) {
|
||||
self.onmessage = e => {
|
||||
const message = e.data;
|
||||
Promise.resolve()
|
||||
.then(() => methods[message.action](...message.args))
|
||||
.then(result => ({
|
||||
id: message.id,
|
||||
error: false,
|
||||
data: result
|
||||
}))
|
||||
.catch(err => ({
|
||||
id: message.id,
|
||||
error: true,
|
||||
data: cloneError(err)
|
||||
}))
|
||||
.then(data => self.postMessage(data));
|
||||
};
|
||||
}
|
||||
|
||||
function cloneError(err) {
|
||||
return Object.assign({
|
||||
name: err.name,
|
||||
stack: err.stack,
|
||||
message: err.message,
|
||||
lineNumber: err.lineNumber,
|
||||
columnNumber: err.columnNumber,
|
||||
fileName: err.fileName
|
||||
}, err);
|
||||
}
|
|
@ -1,40 +1,89 @@
|
|||
/* global importScripts workerUtil CSSLint require metaParser */
|
||||
/* exported editorWorker */
|
||||
'use strict';
|
||||
|
||||
// eslint-disable-next-line no-var
|
||||
var editorWorker = (() => {
|
||||
let worker;
|
||||
return new Proxy({}, {
|
||||
get: (target, prop) =>
|
||||
(...args) => {
|
||||
if (!worker) {
|
||||
worker = createWorker();
|
||||
}
|
||||
return worker.invoke(prop, args);
|
||||
}
|
||||
importScripts('/js/worker-util.js');
|
||||
const {createAPI, loadScript} = workerUtil;
|
||||
|
||||
createAPI({
|
||||
csslint: (code, config) => {
|
||||
loadScript('/vendor-overwrites/csslint/parserlib.js', '/vendor-overwrites/csslint/csslint.js');
|
||||
return CSSLint.verify(code, config).messages
|
||||
.map(m => Object.assign(m, {rule: {id: m.rule.id}}));
|
||||
},
|
||||
stylelint: (code, config) => {
|
||||
loadScript('/vendor/stylelint-bundle/stylelint-bundle.min.js');
|
||||
return require('stylelint').lint({code, config});
|
||||
},
|
||||
metalint: code => {
|
||||
loadScript(
|
||||
'/vendor/usercss-meta/usercss-meta.min.js',
|
||||
'/vendor-overwrites/colorpicker/colorconverter.js',
|
||||
'/js/meta-parser.js'
|
||||
);
|
||||
const result = metaParser.lint(code);
|
||||
// extract needed info
|
||||
result.errors = result.errors.map(err =>
|
||||
({
|
||||
code: err.code,
|
||||
args: err.args,
|
||||
message: err.message,
|
||||
index: err.index
|
||||
})
|
||||
);
|
||||
return result;
|
||||
},
|
||||
getStylelintRules,
|
||||
getCsslintRules
|
||||
});
|
||||
|
||||
function createWorker() {
|
||||
let id = 0;
|
||||
const pendingResponse = new Map();
|
||||
const worker = new Worker('/edit/editor-worker-body.js');
|
||||
worker.onmessage = e => {
|
||||
const message = e.data;
|
||||
pendingResponse.get(message.id)[message.error ? 'reject' : 'resolve'](message.data);
|
||||
pendingResponse.delete(message.id);
|
||||
};
|
||||
return {invoke};
|
||||
function getCsslintRules() {
|
||||
loadScript('/vendor-overwrites/csslint/csslint.js');
|
||||
return CSSLint.getRules().map(rule => {
|
||||
const output = {};
|
||||
for (const [key, value] of Object.entries(rule)) {
|
||||
if (typeof value !== 'function') {
|
||||
output[key] = value;
|
||||
}
|
||||
}
|
||||
return output;
|
||||
});
|
||||
}
|
||||
|
||||
function invoke(action, args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
pendingResponse.set(id, {resolve, reject});
|
||||
worker.postMessage({
|
||||
id,
|
||||
action,
|
||||
args
|
||||
});
|
||||
id++;
|
||||
});
|
||||
function getStylelintRules() {
|
||||
loadScript('/vendor/stylelint-bundle/stylelint-bundle.min.js');
|
||||
const stylelint = require('stylelint');
|
||||
const options = {};
|
||||
const rxPossible = /\bpossible:("(?:[^"]*?)"|\[(?:[^\]]*?)\]|\{(?:[^}]*?)\})/g;
|
||||
const rxString = /"([-\w\s]{3,}?)"/g;
|
||||
for (const id of Object.keys(stylelint.rules)) {
|
||||
const ruleCode = String(stylelint.rules[id]);
|
||||
const sets = [];
|
||||
let m, mStr;
|
||||
while ((m = rxPossible.exec(ruleCode))) {
|
||||
const possible = m[1];
|
||||
const set = [];
|
||||
while ((mStr = rxString.exec(possible))) {
|
||||
const s = mStr[1];
|
||||
if (s.includes(' ')) {
|
||||
set.push(...s.split(/\s+/));
|
||||
} else {
|
||||
set.push(s);
|
||||
}
|
||||
}
|
||||
})();
|
||||
if (possible.includes('ignoreAtRules')) {
|
||||
set.push('ignoreAtRules');
|
||||
}
|
||||
if (possible.includes('ignoreShorthands')) {
|
||||
set.push('ignoreShorthands');
|
||||
}
|
||||
if (set.length) {
|
||||
sets.push(set);
|
||||
}
|
||||
}
|
||||
if (sets.length) {
|
||||
options[id] = sets;
|
||||
}
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/* global linter API */
|
||||
/* global linter API editorWorker */
|
||||
/* exported createMetaCompiler */
|
||||
'use strict';
|
||||
|
||||
|
@ -19,25 +19,23 @@ function createMetaCompiler(cm) {
|
|||
if (match[0] === meta && match.index === metaIndex) {
|
||||
return cache;
|
||||
}
|
||||
return API.parseUsercss({sourceCode: match[0], metaOnly: true})
|
||||
.then(result => result.usercssData)
|
||||
.then(result => {
|
||||
return editorWorker.metalint(match[0])
|
||||
.then(({metadata, errors}) => {
|
||||
if (errors.every(err => err.code === 'unknownMeta')) {
|
||||
for (const cb of updateListeners) {
|
||||
cb(result);
|
||||
cb(metadata);
|
||||
}
|
||||
meta = match[0];
|
||||
metaIndex = match.index;
|
||||
cache = [];
|
||||
return cache;
|
||||
}, err => {
|
||||
meta = match[0];
|
||||
metaIndex = match.index;
|
||||
cache = [{
|
||||
}
|
||||
cache = errors.map(err =>
|
||||
({
|
||||
from: cm.posFromIndex((err.index || 0) + match.index),
|
||||
to: cm.posFromIndex((err.index || 0) + match.index),
|
||||
message: err.message,
|
||||
severity: 'error'
|
||||
}];
|
||||
message: err.code && chrome.i18n.getMessage(`meta_${err.code}`, err.args) || err.message,
|
||||
severity: err.code === 'unknownMeta' ? 'warning' : 'error'
|
||||
})
|
||||
);
|
||||
meta = match[0];
|
||||
metaIndex = match.index;
|
||||
return cache;
|
||||
});
|
||||
});
|
||||
|
|
|
@ -432,7 +432,7 @@ function createSectionsEditor(style) {
|
|||
|
||||
function doImport({replaceOldStyle = false}) {
|
||||
lockPageUI(true);
|
||||
editorWorker.parseMozFormat({code: popup.codebox.getValue().trim()})
|
||||
API.parseCss({code: popup.codebox.getValue().trim()})
|
||||
.then(({sections, errors}) => {
|
||||
// shouldn't happen but just in case
|
||||
if (!sections.length || errors.length) {
|
||||
|
|
|
@ -249,7 +249,7 @@ function createSourceEditor(style) {
|
|||
.then(replaceStyle)
|
||||
.catch(err => {
|
||||
if (err.handled) return;
|
||||
if (err.message === t('styleMissingMeta', 'name')) {
|
||||
if (err.code === 'missingMandatory' && err.args.includes('name')) {
|
||||
messageBox.confirm(t('usercssReplaceTemplateConfirmation')).then(ok => ok &&
|
||||
chromeSync.setLZValue('usercssTemplate', code)
|
||||
.then(() => chromeSync.getLZValue('usercssTemplate'))
|
||||
|
@ -258,7 +258,7 @@ function createSourceEditor(style) {
|
|||
}
|
||||
const contents = Array.isArray(err) ?
|
||||
$create('pre', err.join('\n')) :
|
||||
[String(err)];
|
||||
[err.message || String(err)];
|
||||
if (Number.isInteger(err.index)) {
|
||||
const pos = cm.posFromIndex(err.index);
|
||||
contents[0] += ` (line ${pos.line + 1} col ${pos.ch + 1})`;
|
||||
|
|
|
@ -241,7 +241,7 @@
|
|||
const contents = Array.isArray(err) ?
|
||||
[$create('pre', err.join('\n'))] :
|
||||
[err && err.message && $create('pre', err.message) || err || 'Unknown error'];
|
||||
if (Number.isInteger(err.index)) {
|
||||
if (Number.isInteger(err.index) && typeof contents[0] === 'string') {
|
||||
const pos = cm.posFromIndex(err.index);
|
||||
contents[0] = `${pos.line + 1}:${pos.ch + 1} ` + contents[0];
|
||||
contents.push($create('pre', drawLinePointer(pos)));
|
||||
|
|
78
js/meta-parser.js
Normal file
78
js/meta-parser.js
Normal file
|
@ -0,0 +1,78 @@
|
|||
/* global usercssMeta colorConverter */
|
||||
'use strict';
|
||||
|
||||
// eslint-disable-next-line no-var
|
||||
var metaParser = (() => {
|
||||
const {createParser, ParseError} = usercssMeta;
|
||||
const PREPROCESSORS = new Set(['default', 'uso', 'stylus', 'less']);
|
||||
const options = {
|
||||
validateKey: {
|
||||
preprocessor: state => {
|
||||
if (!PREPROCESSORS.has(state.value)) {
|
||||
throw new ParseError({
|
||||
code: 'unknownPreprocessor',
|
||||
args: [state.value],
|
||||
index: state.valueIndex
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
validateVar: {
|
||||
select: state => {
|
||||
if (state.varResult.options.every(o => o.name !== state.value)) {
|
||||
throw new ParseError({
|
||||
code: 'invalidSelectValueMismatch',
|
||||
index: state.valueIndex
|
||||
});
|
||||
}
|
||||
},
|
||||
color: state => {
|
||||
const color = colorConverter.parse(state.value);
|
||||
if (!color) {
|
||||
throw new ParseError({
|
||||
code: 'invalidColor',
|
||||
args: [state.value],
|
||||
index: state.valueIndex
|
||||
});
|
||||
}
|
||||
state.value = colorConverter.format(color, 'rgb');
|
||||
}
|
||||
}
|
||||
};
|
||||
const parser = createParser(options);
|
||||
const looseParser = createParser(Object.assign({}, options, {allowErrors: true, unknownKey: 'throw'}));
|
||||
return {
|
||||
parse,
|
||||
lint,
|
||||
nullifyInvalidVars
|
||||
};
|
||||
|
||||
function parse(text, indexOffset) {
|
||||
try {
|
||||
return parser.parse(text);
|
||||
} catch (err) {
|
||||
if (typeof err.index === 'number') {
|
||||
err.index += indexOffset;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
function lint(text) {
|
||||
return looseParser.parse(text);
|
||||
}
|
||||
|
||||
function nullifyInvalidVars(vars) {
|
||||
for (const va of Object.values(vars)) {
|
||||
if (va.value === null) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
parser.validateVar(va);
|
||||
} catch (err) {
|
||||
va.value = null;
|
||||
}
|
||||
}
|
||||
return vars;
|
||||
}
|
||||
})();
|
|
@ -1,6 +1,29 @@
|
|||
/* exported styleSectionsEqual */
|
||||
'use strict';
|
||||
|
||||
const RX_NAMESPACE = /\s*(@namespace\s+(?:\S+\s+)?url\(http:\/\/.*?\);)\s*/g;
|
||||
const RX_CHARSET = /\s*@charset\s+(['"]).*?\1\s*;\s*/g;
|
||||
const RX_CSS_COMMENTS = /\/\*[\s\S]*?(?:\*\/|$)/g;
|
||||
|
||||
function styleCodeEmpty(code) {
|
||||
// Collect the global section if it's not empty, not comment-only, not namespace-only.
|
||||
const cmtOpen = code && code.indexOf('/*');
|
||||
if (cmtOpen >= 0) {
|
||||
const cmtCloseLast = code.lastIndexOf('*/');
|
||||
if (cmtCloseLast < 0) {
|
||||
code = code.substr(0, cmtOpen);
|
||||
} else {
|
||||
code = code.substr(0, cmtOpen) +
|
||||
code.substring(cmtOpen, cmtCloseLast + 2).replace(RX_CSS_COMMENTS, '') +
|
||||
code.substr(cmtCloseLast + 2);
|
||||
}
|
||||
}
|
||||
if (!code || !code.trim()) return true;
|
||||
if (code.includes('@namespace')) code = code.replace(RX_NAMESPACE, '').trim();
|
||||
if (code.includes('@charset')) code = code.replace(RX_CHARSET, '').trim();
|
||||
return !code;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Style} a - first style object
|
||||
* @param {Style} b - second style object
|
645
js/usercss.js
645
js/usercss.js
|
@ -1,514 +1,56 @@
|
|||
/* global loadScript semverCompare colorConverter styleCodeEmpty */
|
||||
/* global loadScript semverCompare colorConverter styleCodeEmpty backgroundWorker */
|
||||
/* exported usercss */
|
||||
'use strict';
|
||||
|
||||
const usercss = (() => {
|
||||
// true = global
|
||||
// false or 0 = private
|
||||
// <string> = global key name
|
||||
// <function> = (style, newValue)
|
||||
const KNOWN_META = new Map([
|
||||
['author', true],
|
||||
['advanced', 0],
|
||||
['description', true],
|
||||
['homepageURL', 'url'],
|
||||
['icon', 0],
|
||||
['license', 0],
|
||||
['name', true],
|
||||
['namespace', 0],
|
||||
//['noframes', 0],
|
||||
['preprocessor', 0],
|
||||
['supportURL', 0],
|
||||
['updateURL', (style, newValue) => {
|
||||
// always preserve locally installed style's updateUrl
|
||||
if (!/^file:/.test(style.updateUrl)) {
|
||||
style.updateUrl = newValue;
|
||||
}
|
||||
}],
|
||||
['var', 0],
|
||||
['version', 0],
|
||||
]);
|
||||
const MANDATORY_META = ['name', 'namespace', 'version'];
|
||||
const META_VARS = ['text', 'color', 'checkbox', 'select', 'dropdown', 'image', 'number', 'range'];
|
||||
const META_URLS = [...KNOWN_META.keys()].filter(k => k.endsWith('URL'));
|
||||
|
||||
const BUILDER = {
|
||||
default: {
|
||||
postprocess(sections, vars) {
|
||||
let varDef = Object.keys(vars).map(k => ` --${k}: ${vars[k].value};\n`).join('');
|
||||
if (!varDef) return;
|
||||
varDef = ':root {\n' + varDef + '}\n';
|
||||
for (const section of sections) {
|
||||
if (!styleCodeEmpty(section.code)) {
|
||||
section.code = varDef + section.code;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
stylus: {
|
||||
preprocess(source, vars) {
|
||||
return loadScript('/vendor/stylus-lang-bundle/stylus.min.js').then(() => (
|
||||
new Promise((resolve, reject) => {
|
||||
const varDef = Object.keys(vars).map(key => `${key} = ${vars[key].value};\n`).join('');
|
||||
if (!Error.captureStackTrace) Error.captureStackTrace = () => {};
|
||||
window.stylus(varDef + source).render((err, output) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(output);
|
||||
}
|
||||
});
|
||||
})
|
||||
));
|
||||
}
|
||||
},
|
||||
less: {
|
||||
preprocess(source, vars) {
|
||||
window.less = window.less || {
|
||||
logLevel: 0,
|
||||
useFileCache: false,
|
||||
const GLOBAL_METAS = {
|
||||
author: undefined,
|
||||
description: undefined,
|
||||
homepageURL: 'url',
|
||||
// updateURL: 'updateUrl',
|
||||
name: undefined,
|
||||
};
|
||||
const varDefs = Object.keys(vars).map(key => `@${key}:${vars[key].value};\n`).join('');
|
||||
return loadScript('/vendor/less/less.min.js')
|
||||
.then(() => window.less.render(varDefs + source))
|
||||
.then(({css}) => css);
|
||||
}
|
||||
},
|
||||
uso: {
|
||||
preprocess(source, vars) {
|
||||
const pool = new Map();
|
||||
return Promise.resolve(doReplace(source));
|
||||
|
||||
function getValue(name, rgb) {
|
||||
if (!vars.hasOwnProperty(name)) {
|
||||
if (name.endsWith('-rgb')) {
|
||||
return getValue(name.slice(0, -4), true);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (rgb) {
|
||||
if (vars[name].type === 'color') {
|
||||
const color = colorConverter.parse(vars[name].value);
|
||||
if (!color) return null;
|
||||
const {r, g, b} = color;
|
||||
return `${r}, ${g}, ${b}`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
if (vars[name].type === 'dropdown' || vars[name].type === 'select') {
|
||||
// prevent infinite recursion
|
||||
pool.set(name, '');
|
||||
return doReplace(vars[name].value);
|
||||
}
|
||||
return vars[name].value;
|
||||
}
|
||||
|
||||
function doReplace(text) {
|
||||
return text.replace(/\/\*\[\[([\w-]+)\]\]\*\//g, (match, name) => {
|
||||
if (!pool.has(name)) {
|
||||
const value = getValue(name);
|
||||
pool.set(name, value === null ? match : value);
|
||||
}
|
||||
return pool.get(name);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const RX_NUMBER = /-?\d+(\.\d+)?\s*/y;
|
||||
const RX_WHITESPACE = /\s*/y;
|
||||
const RX_WORD = /([\w-]+)\s*/y;
|
||||
const RX_STRING_BACKTICK = /(`(?:\\`|[\s\S])*?`)\s*/y;
|
||||
const RX_STRING_QUOTED = /((['"])(?:\\\2|[^\n])*?\2|\w+)\s*/y;
|
||||
|
||||
const worker = {};
|
||||
|
||||
function getMetaSource(source) {
|
||||
const commentRe = /\/\*[\s\S]*?\*\//g;
|
||||
const metaRe = /==userstyle==[\s\S]*?==\/userstyle==/i;
|
||||
|
||||
let m;
|
||||
// iterate through each comment
|
||||
while ((m = commentRe.exec(source))) {
|
||||
const commentSource = source.slice(m.index, m.index + m[0].length);
|
||||
const n = commentSource.match(metaRe);
|
||||
if (n) {
|
||||
return {
|
||||
index: m.index + n.index,
|
||||
text: n[0]
|
||||
};
|
||||
}
|
||||
}
|
||||
return {text: '', index: 0};
|
||||
}
|
||||
|
||||
function parseWord(state, error = 'invalid word') {
|
||||
RX_WORD.lastIndex = state.re.lastIndex;
|
||||
const match = RX_WORD.exec(state.text);
|
||||
if (!match) {
|
||||
throw new Error((state.errorPrefix || '') + error);
|
||||
}
|
||||
state.value = match[1];
|
||||
state.re.lastIndex += match[0].length;
|
||||
}
|
||||
|
||||
function parseVar(state) {
|
||||
const result = {
|
||||
type: null,
|
||||
label: null,
|
||||
name: null,
|
||||
value: null,
|
||||
default: null,
|
||||
options: null
|
||||
};
|
||||
|
||||
parseWord(state, 'missing type');
|
||||
result.type = state.type = state.value;
|
||||
|
||||
if (!META_VARS.includes(state.type)) {
|
||||
throw new Error(`unknown type: ${state.type}`);
|
||||
}
|
||||
|
||||
parseWord(state, 'missing name');
|
||||
result.name = state.value;
|
||||
|
||||
parseString(state);
|
||||
result.label = state.value;
|
||||
|
||||
const {re, type, text} = state;
|
||||
|
||||
switch (type === 'image' && state.key === 'var' ? '@image@var' : type) {
|
||||
case 'checkbox': {
|
||||
const match = text.slice(re.lastIndex).match(/([01])\s+/);
|
||||
if (!match) {
|
||||
throw new Error('value must be 0 or 1');
|
||||
}
|
||||
re.lastIndex += match[0].length;
|
||||
result.default = match[1];
|
||||
break;
|
||||
}
|
||||
|
||||
case 'select':
|
||||
case '@image@var': {
|
||||
state.errorPrefix = 'Invalid JSON: ';
|
||||
parseJSONValue(state);
|
||||
state.errorPrefix = '';
|
||||
const extractDefaultOption = (key, value) => {
|
||||
if (key.endsWith('*')) {
|
||||
const option = createOption(key.slice(0, -1), value);
|
||||
result.default = option.name;
|
||||
return option;
|
||||
}
|
||||
return createOption(key, value);
|
||||
};
|
||||
if (Array.isArray(state.value)) {
|
||||
result.options = state.value.map(k => extractDefaultOption(k));
|
||||
} else {
|
||||
result.options = Object.keys(state.value).map(k => extractDefaultOption(k, state.value[k]));
|
||||
}
|
||||
if (result.default === null) {
|
||||
result.default = (result.options[0] || {}).name || '';
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'number':
|
||||
case 'range': {
|
||||
state.errorPrefix = 'Invalid JSON: ';
|
||||
parseJSONValue(state);
|
||||
state.errorPrefix = '';
|
||||
// [default, start, end, step, units] (start, end, step & units are optional)
|
||||
if (Array.isArray(state.value) && state.value.length) {
|
||||
// label may be placed anywhere
|
||||
result.units = (state.value.find(i => typeof i === 'string') || '').replace(/[\d.+-]/g, '');
|
||||
const range = state.value.filter(i => typeof i === 'number' || i === null);
|
||||
result.default = range[0];
|
||||
result.min = range[1];
|
||||
result.max = range[2];
|
||||
result.step = range[3] === 0 ? 1 : range[3];
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'dropdown':
|
||||
case 'image': {
|
||||
if (text[re.lastIndex] !== '{') {
|
||||
throw new Error('no open {');
|
||||
}
|
||||
result.options = [];
|
||||
re.lastIndex++;
|
||||
while (text[re.lastIndex] !== '}') {
|
||||
const option = {};
|
||||
|
||||
parseStringUnquoted(state);
|
||||
option.name = state.value;
|
||||
|
||||
parseString(state);
|
||||
option.label = state.value;
|
||||
|
||||
if (type === 'dropdown') {
|
||||
parseEOT(state);
|
||||
} else {
|
||||
parseString(state);
|
||||
}
|
||||
option.value = state.value;
|
||||
|
||||
result.options.push(option);
|
||||
}
|
||||
re.lastIndex++;
|
||||
eatWhitespace(state);
|
||||
result.default = result.options[0].name;
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
// text, color
|
||||
parseStringToEnd(state);
|
||||
result.default = state.value;
|
||||
}
|
||||
}
|
||||
state.usercssData.vars[result.name] = result;
|
||||
validateVar(result);
|
||||
}
|
||||
|
||||
function createOption(label, value) {
|
||||
let name;
|
||||
const match = label.match(/^(\w+):(.*)/);
|
||||
if (match) {
|
||||
([, name, label] = match);
|
||||
}
|
||||
if (!name) {
|
||||
name = label;
|
||||
}
|
||||
if (!value) {
|
||||
value = name;
|
||||
}
|
||||
return {name, label, value};
|
||||
}
|
||||
|
||||
function parseEOT(state) {
|
||||
const re = /<<<EOT([\s\S]+?)EOT;/y;
|
||||
re.lastIndex = state.re.lastIndex;
|
||||
const match = state.text.match(re);
|
||||
if (!match) {
|
||||
throw new Error('missing EOT');
|
||||
}
|
||||
state.re.lastIndex += match[0].length;
|
||||
state.value = match[1].trim().replace(/\*\\\//g, '*/');
|
||||
eatWhitespace(state);
|
||||
}
|
||||
|
||||
function parseStringUnquoted(state) {
|
||||
const pos = state.re.lastIndex;
|
||||
const nextQuoteOrEOL = posOrEnd(state.text, '"', pos);
|
||||
state.re.lastIndex = nextQuoteOrEOL;
|
||||
state.value = state.text.slice(pos, nextQuoteOrEOL).trim().replace(/\s+/g, '-');
|
||||
}
|
||||
|
||||
function parseString(state) {
|
||||
const pos = state.re.lastIndex;
|
||||
const rx = state.text[pos] === '`' ? RX_STRING_BACKTICK : RX_STRING_QUOTED;
|
||||
rx.lastIndex = pos;
|
||||
const match = rx.exec(state.text);
|
||||
if (!match) {
|
||||
throw new Error((state.errorPrefix || '') + 'Quoted string expected');
|
||||
}
|
||||
state.re.lastIndex += match[0].length;
|
||||
state.value = unquote(match[1]);
|
||||
}
|
||||
|
||||
function parseJSONValue(state) {
|
||||
const JSON_PRIME = {
|
||||
__proto__: null,
|
||||
'null': null,
|
||||
'true': true,
|
||||
'false': false
|
||||
};
|
||||
const {text, re, errorPrefix} = state;
|
||||
if (text[re.lastIndex] === '{') {
|
||||
// object
|
||||
const obj = {};
|
||||
re.lastIndex++;
|
||||
eatWhitespace(state);
|
||||
while (text[re.lastIndex] !== '}') {
|
||||
parseString(state);
|
||||
const key = state.value;
|
||||
if (text[re.lastIndex] !== ':') {
|
||||
throw new Error(`${errorPrefix}missing ':'`);
|
||||
}
|
||||
re.lastIndex++;
|
||||
eatWhitespace(state);
|
||||
parseJSONValue(state);
|
||||
obj[key] = state.value;
|
||||
if (text[re.lastIndex] === ',') {
|
||||
re.lastIndex++;
|
||||
eatWhitespace(state);
|
||||
} else if (text[re.lastIndex] !== '}') {
|
||||
throw new Error(`${errorPrefix}missing ',' or '}'`);
|
||||
}
|
||||
}
|
||||
re.lastIndex++;
|
||||
eatWhitespace(state);
|
||||
state.value = obj;
|
||||
} else if (text[re.lastIndex] === '[') {
|
||||
// array
|
||||
const arr = [];
|
||||
re.lastIndex++;
|
||||
eatWhitespace(state);
|
||||
while (text[re.lastIndex] !== ']') {
|
||||
parseJSONValue(state);
|
||||
arr.push(state.value);
|
||||
if (text[re.lastIndex] === ',') {
|
||||
re.lastIndex++;
|
||||
eatWhitespace(state);
|
||||
} else if (text[re.lastIndex] !== ']') {
|
||||
throw new Error(`${errorPrefix}missing ',' or ']'`);
|
||||
}
|
||||
}
|
||||
re.lastIndex++;
|
||||
eatWhitespace(state);
|
||||
state.value = arr;
|
||||
} else if (text[re.lastIndex] === '"' || text[re.lastIndex] === '`') {
|
||||
// string
|
||||
parseString(state);
|
||||
} else if (/\d/.test(text[re.lastIndex])) {
|
||||
// number
|
||||
parseNumber(state);
|
||||
} else {
|
||||
parseWord(state);
|
||||
if (!(state.value in JSON_PRIME)) {
|
||||
throw new Error(`${errorPrefix}unknown literal '${state.value}'`);
|
||||
}
|
||||
state.value = JSON_PRIME[state.value];
|
||||
}
|
||||
}
|
||||
|
||||
function parseNumber(state) {
|
||||
RX_NUMBER.lastIndex = state.re.lastIndex;
|
||||
const match = RX_NUMBER.exec(state.text);
|
||||
if (!match) {
|
||||
throw new Error((state.errorPrefix || '') + 'invalid number');
|
||||
}
|
||||
state.value = Number(match[0].trim());
|
||||
state.re.lastIndex += match[0].length;
|
||||
}
|
||||
|
||||
function eatWhitespace(state) {
|
||||
RX_WHITESPACE.lastIndex = state.re.lastIndex;
|
||||
state.re.lastIndex += RX_WHITESPACE.exec(state.text)[0].length;
|
||||
}
|
||||
|
||||
function parseStringToEnd(state) {
|
||||
const EOL = posOrEnd(state.text, '\n', state.re.lastIndex);
|
||||
const match = state.text.slice(state.re.lastIndex, EOL);
|
||||
state.value = unquote(match.trim());
|
||||
state.re.lastIndex += match.length;
|
||||
}
|
||||
|
||||
function unquote(s) {
|
||||
const q = s[0];
|
||||
if (q === s[s.length - 1] && (q === '"' || q === "'" || q === '`')) {
|
||||
// http://www.json.org/
|
||||
return s.slice(1, -1).replace(
|
||||
new RegExp(`\\\\([${q}\\\\/bfnrt]|u[0-9a-fA-F]{4})`, 'g'),
|
||||
s => {
|
||||
if (s[1] === q) {
|
||||
return q;
|
||||
}
|
||||
return JSON.parse(`"${s}"`);
|
||||
}
|
||||
);
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
function posOrEnd(haystack, needle, start) {
|
||||
const pos = haystack.indexOf(needle, start);
|
||||
return pos < 0 ? haystack.length : pos;
|
||||
}
|
||||
const RX_META = /\/\*\s*==userstyle==[\s\S]*?==\/userstyle==\s*\*\//i;
|
||||
const ERR_ARGS_IS_LIST = new Set(['missingMandatory', 'missingChar']);
|
||||
return {buildMeta, buildCode, assignVars};
|
||||
|
||||
function buildMeta(sourceCode) {
|
||||
sourceCode = sourceCode.replace(/\r\n?/g, '\n');
|
||||
|
||||
const usercssData = {
|
||||
vars: {}
|
||||
};
|
||||
|
||||
const style = {
|
||||
reason: 'install',
|
||||
enabled: true,
|
||||
sourceCode,
|
||||
sections: [],
|
||||
usercssData
|
||||
sections: []
|
||||
};
|
||||
|
||||
const {text, index: metaIndex} = getMetaSource(sourceCode);
|
||||
const re = /@(\w+)[ \t\xA0]*/mg;
|
||||
const state = {style, re, text, usercssData};
|
||||
|
||||
function doParse() {
|
||||
let match;
|
||||
while ((match = re.exec(text))) {
|
||||
const key = state.key = match[1];
|
||||
const route = KNOWN_META.get(key);
|
||||
if (route === undefined) {
|
||||
continue;
|
||||
}
|
||||
if (key === 'var' || key === 'advanced') {
|
||||
if (key === 'advanced') {
|
||||
state.maybeUSO = true;
|
||||
}
|
||||
parseVar(state);
|
||||
} else {
|
||||
parseStringToEnd(state);
|
||||
usercssData[key] = state.value;
|
||||
}
|
||||
let value = state.value;
|
||||
if (key === 'version') {
|
||||
value = usercssData[key] = normalizeVersion(value);
|
||||
validateVersion(value);
|
||||
}
|
||||
if (META_URLS.includes(key)) {
|
||||
validateUrl(key, value);
|
||||
}
|
||||
switch (typeof route) {
|
||||
case 'function':
|
||||
route(style, value);
|
||||
break;
|
||||
case 'string':
|
||||
style[route] = value;
|
||||
break;
|
||||
default:
|
||||
if (route) {
|
||||
style[key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
const match = sourceCode.match(RX_META);
|
||||
if (!match) {
|
||||
throw new Error('can not find metadata');
|
||||
}
|
||||
|
||||
try {
|
||||
doParse();
|
||||
} catch (e) {
|
||||
// the source code string offset
|
||||
e.index = metaIndex + state.re.lastIndex;
|
||||
throw e;
|
||||
return backgroundWorker.parseUsercssMeta(match[0], match.index)
|
||||
.catch(err => {
|
||||
if (err.code) {
|
||||
const args = ERR_ARGS_IS_LIST.has(err.code) ? drawList(err.args) : err.args;
|
||||
const message = chrome.i18n.getMessage(`meta_${err.code}`, args);
|
||||
if (message) {
|
||||
err.message = message;
|
||||
}
|
||||
|
||||
if (state.maybeUSO && !usercssData.preprocessor) {
|
||||
usercssData.preprocessor = 'uso';
|
||||
}
|
||||
|
||||
validateStyle(style);
|
||||
throw err;
|
||||
})
|
||||
.then(({metadata}) => {
|
||||
style.usercssData = metadata;
|
||||
for (const [key, value = key] of Object.entries(GLOBAL_METAS)) {
|
||||
style[value] = metadata[key];
|
||||
}
|
||||
return style;
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeVersion(version) {
|
||||
// https://docs.npmjs.com/misc/semver#versions
|
||||
if (version[0] === 'v' || version[0] === '=') {
|
||||
return version.slice(1);
|
||||
}
|
||||
return version;
|
||||
function drawList(items) {
|
||||
return items.map(i => i.length === 1 ? JSON.stringify(i) : i).join(', ');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -518,136 +60,37 @@ const usercss = (() => {
|
|||
* when allowErrors is falsy or {style, errors} object when allowErrors is truthy
|
||||
*/
|
||||
function buildCode(style, allowErrors) {
|
||||
const {usercssData: {preprocessor, vars}, sourceCode} = style;
|
||||
let builder;
|
||||
if (preprocessor) {
|
||||
if (!BUILDER[preprocessor]) {
|
||||
return Promise.reject(chrome.i18n.getMessage('styleMetaErrorPreprocessor', preprocessor));
|
||||
}
|
||||
builder = BUILDER[preprocessor];
|
||||
} else {
|
||||
builder = BUILDER.default;
|
||||
}
|
||||
|
||||
const sVars = simpleVars(vars);
|
||||
|
||||
return (
|
||||
Promise.resolve(
|
||||
builder.preprocess && builder.preprocess(sourceCode, sVars) ||
|
||||
sourceCode)
|
||||
.then(mozStyle => invokeWorker({
|
||||
action: 'parse',
|
||||
styleId: style.id,
|
||||
code: mozStyle,
|
||||
}))
|
||||
const match = style.sourceCode.match(RX_META);
|
||||
return backgroundWorker.compileUsercss(
|
||||
style.usercssData.preprocessor,
|
||||
style.sourceCode.slice(0, match.index) + style.sourceCode.slice(match.index + match[0].length),
|
||||
style.usercssData.vars
|
||||
)
|
||||
.then(({sections, errors}) => {
|
||||
if (!errors.length) errors = false;
|
||||
if (!sections.length || errors && !allowErrors) {
|
||||
return Promise.reject(errors);
|
||||
throw errors;
|
||||
}
|
||||
style.sections = sections;
|
||||
if (builder.postprocess) builder.postprocess(style.sections, sVars);
|
||||
return allowErrors ? {style, errors} : style;
|
||||
}));
|
||||
}
|
||||
|
||||
function simpleVars(vars) {
|
||||
// simplify vars by merging `va.default` to `va.value`, so BUILDER don't
|
||||
// need to test each va's default value.
|
||||
return Object.keys(vars).reduce((output, key) => {
|
||||
const va = vars[key];
|
||||
output[key] = Object.assign({}, va, {
|
||||
value: va.value === null || va.value === undefined ?
|
||||
getVarValue(va, 'default') : getVarValue(va, 'value')
|
||||
});
|
||||
return output;
|
||||
}, {});
|
||||
}
|
||||
|
||||
function getVarValue(va, prop) {
|
||||
if (va.type === 'select' || va.type === 'dropdown' || va.type === 'image') {
|
||||
// TODO: handle customized image
|
||||
return va.options.find(o => o.name === va[prop]).value;
|
||||
}
|
||||
if ((va.type === 'number' || va.type === 'range') && va.units) {
|
||||
return va[prop] + va.units;
|
||||
}
|
||||
return va[prop];
|
||||
}
|
||||
|
||||
function validateStyle({usercssData: data}) {
|
||||
for (const prop of MANDATORY_META) {
|
||||
if (!data[prop]) {
|
||||
throw new Error(chrome.i18n.getMessage('styleMissingMeta', prop));
|
||||
}
|
||||
}
|
||||
validateVersion(data.version);
|
||||
META_URLS.forEach(k => validateUrl(k, data[k]));
|
||||
Object.keys(data.vars).forEach(k => validateVar(data.vars[k]));
|
||||
}
|
||||
|
||||
function validateVersion(version) {
|
||||
semverCompare(version, '0.0.0');
|
||||
}
|
||||
|
||||
function validateUrl(key, url) {
|
||||
if (!url) {
|
||||
return;
|
||||
}
|
||||
url = new URL(url);
|
||||
if (!/^https?:/.test(url.protocol)) {
|
||||
throw new Error(`${url.protocol} is not a valid protocol in ${key}`);
|
||||
}
|
||||
}
|
||||
|
||||
function validateVar(va, value = 'default') {
|
||||
if (va.type === 'select' || va.type === 'dropdown') {
|
||||
if (va.options.every(o => o.name !== va[value])) {
|
||||
throw new Error(chrome.i18n.getMessage('styleMetaErrorSelectValueMismatch'));
|
||||
}
|
||||
} else if (va.type === 'checkbox' && !/^[01]$/.test(va[value])) {
|
||||
throw new Error(chrome.i18n.getMessage('styleMetaErrorCheckbox'));
|
||||
} else if (va.type === 'color') {
|
||||
va[value] = colorConverter.format(colorConverter.parse(va[value]), 'rgb');
|
||||
} else if ((va.type === 'number' || va.type === 'range') && typeof va[value] !== 'number') {
|
||||
throw new Error(chrome.i18n.getMessage('styleMetaErrorRangeOrNumber', va.type));
|
||||
}
|
||||
}
|
||||
|
||||
function assignVars(style, oldStyle) {
|
||||
const {usercssData: {vars}} = style;
|
||||
const {usercssData: {vars: oldVars}} = oldStyle;
|
||||
if (!vars || !oldVars) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
// The type of var might be changed during the update. Set value to null if the value is invalid.
|
||||
for (const key of Object.keys(vars)) {
|
||||
if (oldVars[key] && oldVars[key].value) {
|
||||
vars[key].value = oldVars[key].value;
|
||||
try {
|
||||
validateVar(vars[key], 'value');
|
||||
} catch (e) {
|
||||
vars[key].value = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function invokeWorker(message) {
|
||||
if (!worker.queue) {
|
||||
worker.instance = new Worker('/background/parserlib-loader.js');
|
||||
worker.queue = [];
|
||||
worker.instance.onmessage = ({data}) => {
|
||||
worker.queue.shift().resolve(data.__ERROR__ ? Promise.reject(data.__ERROR__) : data);
|
||||
if (worker.queue.length) {
|
||||
worker.instance.postMessage(worker.queue[0].message);
|
||||
}
|
||||
};
|
||||
}
|
||||
return new Promise(resolve => {
|
||||
worker.queue.push({message, resolve});
|
||||
if (worker.queue.length === 1) {
|
||||
worker.instance.postMessage(message);
|
||||
}
|
||||
return backgroundWorker.nullifyInvalidVars(vars)
|
||||
.then(vars => {
|
||||
style.usercssData.vars = vars;
|
||||
});
|
||||
}
|
||||
|
||||
return {buildMeta, buildCode, assignVars, invokeWorker};
|
||||
})();
|
||||
|
|
98
js/worker-util.js
Normal file
98
js/worker-util.js
Normal file
|
@ -0,0 +1,98 @@
|
|||
/* global importScripts */
|
||||
'use strict';
|
||||
|
||||
// eslint-disable-next-line no-var
|
||||
var workerUtil = (() => {
|
||||
const loadedScripts = new Set();
|
||||
return {createWorker, createAPI, loadScript, cloneError};
|
||||
|
||||
function createWorker({url, lifeTime = 30}) {
|
||||
let worker;
|
||||
let id;
|
||||
let timer;
|
||||
const pendingResponse = new Map();
|
||||
|
||||
return new Proxy({}, {
|
||||
get: (target, prop) =>
|
||||
(...args) => {
|
||||
if (!worker) {
|
||||
init();
|
||||
}
|
||||
return invoke(prop, args);
|
||||
}
|
||||
});
|
||||
|
||||
function init() {
|
||||
id = 0;
|
||||
worker = new Worker(url);
|
||||
worker.onmessage = onMessage;
|
||||
}
|
||||
|
||||
function uninit() {
|
||||
worker.onmessage = null;
|
||||
worker.terminate();
|
||||
worker = null;
|
||||
}
|
||||
|
||||
function onMessage(e) {
|
||||
const message = e.data;
|
||||
pendingResponse.get(message.id)[message.error ? 'reject' : 'resolve'](message.data);
|
||||
pendingResponse.delete(message.id);
|
||||
if (!pendingResponse.size && lifeTime >= 0) {
|
||||
timer = setTimeout(uninit, lifeTime * 1000);
|
||||
}
|
||||
}
|
||||
|
||||
function invoke(action, args) {
|
||||
return new Promise((resolve, reject) => {
|
||||
pendingResponse.set(id, {resolve, reject});
|
||||
clearTimeout(timer);
|
||||
worker.postMessage({
|
||||
id,
|
||||
action,
|
||||
args
|
||||
});
|
||||
id++;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function createAPI(methods) {
|
||||
self.onmessage = e => {
|
||||
const message = e.data;
|
||||
Promise.resolve()
|
||||
.then(() => methods[message.action](...message.args))
|
||||
.then(result => ({
|
||||
id: message.id,
|
||||
error: false,
|
||||
data: result
|
||||
}))
|
||||
.catch(err => ({
|
||||
id: message.id,
|
||||
error: true,
|
||||
data: cloneError(err)
|
||||
}))
|
||||
.then(data => self.postMessage(data));
|
||||
};
|
||||
}
|
||||
|
||||
function cloneError(err) {
|
||||
return Object.assign({
|
||||
name: err.name,
|
||||
stack: err.stack,
|
||||
message: err.message,
|
||||
lineNumber: err.lineNumber,
|
||||
columnNumber: err.columnNumber,
|
||||
fileName: err.fileName
|
||||
}, err);
|
||||
}
|
||||
|
||||
function loadScript(...scripts) {
|
||||
const urls = scripts.filter(u => !loadedScripts.has(u));
|
||||
if (!urls.length) {
|
||||
return;
|
||||
}
|
||||
importScripts(...urls);
|
||||
urls.forEach(u => loadedScripts.add(u));
|
||||
}
|
||||
})();
|
|
@ -172,7 +172,7 @@
|
|||
<script src="manage/import-export.js" async></script>
|
||||
<script src="manage/incremental-search.js" async></script>
|
||||
<script src="msgbox/msgbox.js" async></script>
|
||||
<script src="js/sections-equal.js" async></script>
|
||||
<script src="js/sections-util.js" async></script>
|
||||
<script src="js/storage-util.js" async></script>
|
||||
</head>
|
||||
|
||||
|
|
|
@ -184,7 +184,7 @@ function configDialog(style) {
|
|||
.catch(errors => {
|
||||
const el = $('.config-error', messageBox.element) ||
|
||||
$('#message-box-buttons').insertAdjacentElement('afterbegin', $create('.config-error'));
|
||||
el.textContent = el.title = Array.isArray(errors) ? errors.join('\n') : errors;
|
||||
el.textContent = el.title = Array.isArray(errors) ? errors.join('\n') : errors.message || String(errors);
|
||||
})
|
||||
.then(() => {
|
||||
saving = false;
|
||||
|
|
|
@ -27,7 +27,8 @@
|
|||
"js/messaging.js",
|
||||
"js/msg.js",
|
||||
"js/storage-util.js",
|
||||
"js/sections-equal.js",
|
||||
"js/sections-util.js",
|
||||
"js/worker-util.js",
|
||||
"background/storage.js",
|
||||
"js/prefs.js",
|
||||
"js/script-loader.js",
|
||||
|
@ -43,8 +44,7 @@
|
|||
"background/search-db.js",
|
||||
"background/update.js",
|
||||
"background/openusercss-api.js",
|
||||
"vendor/semver-bundle/semver.js",
|
||||
"vendor-overwrites/colorpicker/colorconverter.js"
|
||||
"vendor/semver-bundle/semver.js"
|
||||
]
|
||||
},
|
||||
"commands": {
|
||||
|
|
|
@ -18,7 +18,8 @@
|
|||
"stylelint-bundle": "^8.0.0",
|
||||
"stylus-lang-bundle": "^0.54.5",
|
||||
"updates": "^4.2.1",
|
||||
"web-ext": "^2.9.1"
|
||||
"web-ext": "^2.9.1",
|
||||
"usercss-meta": "^0.8.1"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint **/*.js --cache || exit 0",
|
||||
|
|
|
@ -28,6 +28,9 @@ const files = {
|
|||
],
|
||||
'stylus-lang-bundle': [
|
||||
'stylus.min.js'
|
||||
],
|
||||
'usercss-meta': [
|
||||
'dist/usercss-meta.min.js → usercss-meta.min.js'
|
||||
]
|
||||
};
|
||||
|
||||
|
@ -35,7 +38,7 @@ async function updateReadme(lib) {
|
|||
const pkg = await fs.readJson(`${root}/node_modules/${lib}/package.json`);
|
||||
const file = `${root}/vendor/${lib}/README.md`;
|
||||
const txt = await fs.readFile(file, 'utf8');
|
||||
return fs.writeFile(file, txt.replace(/\bv[\d.]+[-\w]*\b/g, `v${pkg.version}`));
|
||||
return fs.writeFile(file, txt.replace(/\b([v@])[\d.]+[-\w]*\b/g, `$1${pkg.version}`));
|
||||
}
|
||||
|
||||
function isFolder(fileOrFolder) {
|
||||
|
|
|
@ -5505,3 +5505,5 @@ self.parserlib = (() => {
|
|||
|
||||
//endregion
|
||||
})();
|
||||
|
||||
self.parserlib.css.Tokens[self.parserlib.css.Tokens.COMMENT].hide = false;
|
||||
|
|
4
vendor/README.md
vendored
4
vendor/README.md
vendored
|
@ -9,7 +9,8 @@ Using this repo, run `npm install`... the latest versions of:
|
|||
* `less` (https://github.com/less/less.js) is installed.
|
||||
* `lz-string-unsafe` (https://github.com/openstyles/lz-string-unsafe) is installed.
|
||||
* `semver-bundle` (https://github.com/openstyles/semver-bundle) is installed.
|
||||
* `stylus-lang` (https://github.com/openstyles/stylus-lang-bundle) is installed.<br><br>
|
||||
* `stylus-lang` (https://github.com/openstyles/stylus-lang-bundle) is installed.
|
||||
* `usercss-meta` (https://github.com/StylishThemes/parse-usercss) is installed.
|
||||
* The necessary build tools are installed; see `devDependencies` in the `package.json`.
|
||||
|
||||
## Running the build script
|
||||
|
@ -24,6 +25,7 @@ The following changes are made:
|
|||
* `lz-string-unsafe`: The compressed `lz-string-unsafe.min.js` file is copied directly into `vendor/lz-string-unsafe`.
|
||||
* `semver-bundle`: The `dist/semver.js` file is copied directly into `vendor/semver`.
|
||||
* `stylus-lang-bundle`: The `stylus.min.js` file is copied directly into `vendor/stylus-lang-bundle`.
|
||||
* `usercss-meta`: The `dist/usercss-meta.min.js` file is copied directly into `vendor/usercss-meta`.
|
||||
|
||||
## Creating the ZIP
|
||||
|
||||
|
|
22
vendor/usercss-meta/LICENCE
vendored
Normal file
22
vendor/usercss-meta/LICENCE
vendored
Normal file
|
@ -0,0 +1,22 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Original code: Copyright (c) Stylus team (github.com/openstyles/stylus)
|
||||
Modified code: Copyright (c) StylishThemes (github.com/StylishThemes/parse-usercss)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
5
vendor/usercss-meta/README.md
vendored
Normal file
5
vendor/usercss-meta/README.md
vendored
Normal file
|
@ -0,0 +1,5 @@
|
|||
## usercss-meta v0.8.1
|
||||
|
||||
usercss-meta installed via npm - source repo:
|
||||
|
||||
https://unpkg.com/usercss-meta@0.8.1/dist/usercss-meta.min.js
|
2
vendor/usercss-meta/usercss-meta.min.js
vendored
Normal file
2
vendor/usercss-meta/usercss-meta.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user