| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187 |
- 'use strict';
- let builderChain = Promise.resolve();
- const BUILDERS = Object.assign(Object.create(null), {
- default: {
- post(sections, vars) {
- require(['/js/sections-util']); /* global styleCodeEmpty */
- 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)) {
- spliceCssAfterGlobals(section, varDef, styleCodeEmpty.lastIndex);
- }
- }
- },
- },
- stylus: {
- pre(source, vars) {
- require(['/vendor/stylus-lang-bundle/stylus-renderer.min']); /* global StylusRenderer */
- return new Promise((resolve, reject) => {
- const varDef = Object.keys(vars).map(key => `${key} = ${vars[key].value};\n`).join('');
- new StylusRenderer(varDef + source)
- .render((err, output) => err ? reject(err) : resolve(output));
- });
- },
- },
- less: {
- async pre(source, vars) {
- if (!self.less) {
- self.less = {
- logLevel: 0,
- useFileCache: false,
- };
- }
- require(['/vendor/less-bundle/less.min']); /* global less */
- const varDefs = Object.keys(vars).map(key => `@${key}:${vars[key].value};\n`).join('');
- const res = await less.render(varDefs + source, {
- math: 'parens-division',
- });
- return res.css;
- },
- },
- uso: {
- pre(source, vars) {
- require(['/js/color/color-converter']); /* global colorConverter */
- const pool = Object.create(null);
- return doReplace(source);
- function doReplace(text) {
- return text.replace(/(\/\*\[\[([\w-]+)]]\*\/)([0-9a-f]{2}(?=\W))?/gi, (_, cmt, name, alpha) => {
- const key = alpha ? name + '[A]' : name;
- let val = pool[key];
- if (val === undefined) {
- val = pool[key] = getValue(name, null, alpha);
- }
- return (val != null ? val : cmt) + (alpha || '');
- });
- }
- function getValue(name, isUsoRgb, alpha) {
- const v = vars[name];
- if (!v) {
- return name.endsWith('-rgb')
- ? getValue(name.slice(0, -4), true)
- : null;
- }
- let {value} = v;
- switch (v.type) {
- case 'color':
- value = colorConverter.parse(value) || null;
- if (value) {
- /* #rrggbb - inline alpha is present; an opaque hsl/a; #rrggbb originally
- * rgba(r, g, b, a) - transparency <1 is present (Chrome pre-66 compatibility)
- * rgb(r, g, b) - if color is rgb/a with a=1, note: r/g/b will be rounded
- * r, g, b - if the var has `-rgb` suffix per USO specification
- * TODO: when minimum_chrome_version >= 66 try to keep `value` intact */
- if (alpha) delete value.a;
- const isRgb = isUsoRgb || value.type === 'rgb' || value.a != null && value.a !== 1;
- const usoMode = isUsoRgb || !isRgb;
- value = colorConverter.format(value, isRgb ? 'rgb' : 'hex', undefined, usoMode);
- }
- return value;
- case 'dropdown':
- case 'select':
- pool[name] = ''; // prevent infinite recursion
- return doReplace(value);
- }
- return value;
- }
- },
- },
- });
- /* exported compileUsercss */
- /**
- * @param {string} preprocessor
- * @param {string} code
- * @param {Object} [vars] - WARNING: each var's `value` will be overwritten
- (not a problem currently as this code runs in a worker so `vars` is just a copy)
- * @returns {Promise<{sections, errors}>}
- */
- async function compileUsercss(preprocessor, code, vars) {
- let builder = BUILDERS[preprocessor];
- if (!builder) {
- builder = BUILDERS.default;
- if (preprocessor != null) console.warn(`Unknown preprocessor "${preprocessor}"`);
- }
- if (vars) {
- simplifyUsercssVars(vars);
- } else {
- vars = {};
- }
- const log = [];
- if (builder.pre) {
- // another compileUsercss may(?) become active while this one is awaited so let's chain
- builderChain = builderChain.catch(() => {}).then(async () => {
- const logFn = console.log;
- console.log = (...args) => log.push(args);
- code = await builder.pre(code, vars);
- console.log = logFn;
- });
- await builderChain;
- }
- require(['/js/moz-parser']); /* global extractSections */
- const res = extractSections({code});
- if (builder.post) {
- builder.post(res.sections, vars);
- }
- if (log.length) {
- res.log = log;
- }
- return res;
- }
- /**
- * Adds units and sets `null` values to their defaults
- * WARNING: the old value is overwritten
- */
- function simplifyUsercssVars(vars) {
- for (const va of Object.values(vars)) {
- let value = va.value != null ? va.value : va.default;
- switch (va.type) {
- case 'select':
- case 'dropdown':
- case 'image':
- // TODO: handle customized image
- for (const opt of va.options) {
- if (opt.name === value) {
- value = opt.value;
- break;
- }
- }
- break;
- case 'number':
- case 'range':
- value += va.units || '';
- break;
- }
- va.value = value;
- }
- }
- function spliceCssAfterGlobals(section, newText, after) {
- const {code} = section;
- const RX_IMPORT = /@import\s/gi;
- RX_IMPORT.lastIndex = after;
- if (RX_IMPORT.test(code)) {
- require(['/js/csslint/parserlib']); /* global parserlib */
- const parser = new parserlib.css.Parser();
- parser._tokenStream = new parserlib.css.TokenStream(code);
- parser._sheetGlobals();
- const {col, line, offset} = parser._tokenStream._token;
- // normalizing newlines in non-usercss to match line:col from parserlib
- if ((code.indexOf('\r') + 1 || 1e99) - 1 < offset) {
- after = col + code.split('\n', line).reduce((len, s) => len + s.length + 1, 0);
- } else {
- after = offset + 1;
- }
- }
- section.code = (after ? code.slice(0, after) + '\n' : '') + newText + code.slice(after);
- }
|