stylus/js/usercss.js
eight dece4b57f3 Add: install styles from *.user.css file
Fix: handle dup name+namespace

Fix: eslint eqeqeq

Fix: trim @name's spaces

Add: check update for userstyle

Add: build CSS variable

Fix: only check dup when id is not provided

Refactor: userStyle2json -> userstyle.json

Add: style for input

Add: config dialog

Fix: preserve config during update

Fix: onchange doesn't fire on keyboard enter event

Fix: remove empty file

Add: validator. Metas must stay in the same line

Add: warn the user if installation failed

Fix: add some delay before starting installation

Add: open the editor after first installation

Fix: add openEditor to globals

Fix: i18n

Add: preprocessor. Move userstyle.build to background page.

Fix: remove unused global

Fix: preserved unknown prop in saveStyleSource() like saveStyle()

Add: edit userstyle source

Fix: load preprocessor dynamically

Fix: load content script dynamically

Fix: buildCode is async function

Fix: drop Object.entries

Fix: style.sections is undefined

Fix: don't hide the name input but disable it

Fix: query the style before installation

Revert: changes to editor, editor.html

Refactor: use term `usercss` instead of `userstyle`

Fix: don't show homepage action for usercss

Refactor: move script-loader to js/

Refactor: pull out mozParser

Fix: code style

Fix: we don't need to build meta anymore

Fix: use saveUsercss instead of saveStyle to get responsed error

Fix: last is undefined, load script error

Fix: switch to moz-format

Fix: drop injectContentScript. Move usercss check into install-user-css

Fix: response -> respond

Fix: globals -> global

Fix: queryUsercss -> filterUsercss

Fix: add processUsercss function

Fix: only open editor for usercss

Fix: remove findupUsercss fixme

Fix: globals -> global

Fix: globals -> global

Fix: global pollution

Revert: update.js

Refactor: checkStyle

Add: support usercss

Fix: no need to getURL in background page

Fix: merget semver.js into usercss.js

Fix: drop all_urls in match pattern

Fix: drop respondWithError

Move stylus -> stylus-lang

Add stylus-lang/readme

Fix: use include_globs

Fix: global pollution
2017-08-30 17:29:41 +08:00

203 lines
4.8 KiB
JavaScript

/* global loadScript mozParser */
'use strict';
// eslint-disable-next-line no-var
var usercss = (function () {
function semverTest(a, b) {
a = a.split('.').map(Number);
b = b.split('.').map(Number);
for (let i = 0; i < a.length; i++) {
if (!(i in b)) {
return 1;
}
if (a[i] < b[i]) {
return -1;
}
if (a[i] > b[i]) {
return 1;
}
}
if (a.length < b.length) {
return -1;
}
return 0;
}
function guessType(value) {
if (/^url\(.+\)$/i.test(value)) {
return 'image';
}
if (/^#[0-9a-f]{3,8}$/i.test(value)) {
return 'color';
}
if (/^hsla?\(.+\)$/i.test(value)) {
return 'color';
}
if (/^rgba?\(.+\)$/i.test(value)) {
return 'color';
}
// should we use a color-name table to guess type?
return 'text';
}
const BUILDER = {
default: {
postprocess(sections, vars) {
let varDef = ':root {\n';
for (const key of Object.keys(vars)) {
varDef += ` --${key}: ${vars[key].value};\n`;
}
varDef += '}\n';
for (const section of sections) {
section.code = varDef + section.code;
}
}
},
stylus: {
preprocess(source, vars) {
return loadScript('vendor/stylus-lang/stylus.min.js').then(() => (
new Promise((resolve, reject) => {
let varDef = '';
for (const key of Object.keys(vars)) {
varDef += `${key} = ${vars[key].value};\n`;
}
// eslint-disable-next-line no-undef
stylus(varDef + source).render((err, output) => {
if (err) {
reject(err);
} else {
resolve(output);
}
});
})
));
}
}
};
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 n[0];
}
}
}
function buildMeta(source) {
const style = _buildMeta(source);
validate(style);
return style;
}
function _buildMeta(source) {
const style = {
name: null,
usercss: true,
version: null,
source: source,
enabled: true,
sections: [],
vars: {},
preprocessor: null
};
const metaSource = getMetaSource(source);
const match = (re, callback) => {
let m;
if (!re.global) {
if ((m = metaSource.match(re))) {
if (m.length === 1) {
callback(m[0]);
} else {
callback(...m.slice(1));
}
}
} else {
const result = [];
while ((m = re.exec(metaSource))) {
if (m.length <= 2) {
result.push(m[m.length - 1]);
} else {
result.push(m.slice(1));
}
}
if (result.length) {
callback(result);
}
}
};
// FIXME: finish all metas
match(/@name[^\S\r\n]+(.+?)[^\S\r\n]*$/m, m => (style.name = m));
match(/@namespace[^\S\r\n]+(\S+)/, m => (style.namespace = m));
match(/@preprocessor[^\S\r\n]+(\S+)/, m => (style.preprocessor = m));
match(/@version[^\S\r\n]+(\S+)/, m => (style.version = m));
match(
/@var[^\S\r\n]+(\S+)[^\S\r\n]+(?:(['"])((?:\\\2|.)*?)\2|(\S+))[^\S\r\n]+(.+?)[^\S\r\n]*$/gm,
ms => ms.forEach(([key,, label1, label2, value]) => (
style.vars[key] = {
type: guessType(value),
label: label1 || label2,
value: value
}
))
);
return style;
}
function buildCode(style) {
let builder;
if (style.preprocessor && style.preprocessor in BUILDER) {
builder = BUILDER[style.preprocessor];
} else {
builder = BUILDER.default;
}
return Promise.resolve().then(() => {
// preprocess
if (builder.preprocess) {
return builder.preprocess(style.source, style.vars);
}
return style.source;
}).then(mozStyle =>
// moz-parser
loadScript('/js/moz-parser.js').then(() =>
mozParser.parse(mozStyle).then(sections => {
style.sections = sections;
})
)
).then(() => {
// postprocess
if (builder.postprocess) {
return builder.postprocess(style.sections, style.vars);
}
}).then(() => style);
}
function validate(style) {
// mandatory fields
for (const prop of ['name', 'namespace', 'version']) {
if (!style[prop]) {
throw new Error(chrome.i18n.getMessage('styleMissingMeta', prop));
}
}
}
return {buildMeta, buildCode, semverTest};
})();