65ac351699
USO has always produced 6-digit #rrggbb so some styles rely on it and use /*[[color]]*/11 notation to specify the transparency. Now we will try to preserve the opacity customized by the user via colorpicker unless the style specifies it inline.
155 lines
4.7 KiB
JavaScript
155 lines
4.7 KiB
JavaScript
'use strict';
|
|
|
|
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)) {
|
|
section.code = varDef + section.code;
|
|
}
|
|
}
|
|
},
|
|
},
|
|
|
|
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 = {};
|
|
}
|
|
if (builder.pre) {
|
|
code = await builder.pre(code, vars);
|
|
}
|
|
require(['/js/moz-parser']); /* global extractSections */
|
|
const res = extractSections({code});
|
|
if (builder.post) {
|
|
builder.post(res.sections, vars);
|
|
}
|
|
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;
|
|
}
|
|
}
|