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.
154 lines
4.6 KiB
JavaScript
154 lines
4.6 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) {
|
|
/* `alpha` is present or `value` is opaque: #rrggbb
|
|
* `isUsoRgb`: r, g, b -- transparency is usually handled via a separate var
|
|
* `value` is transparent: rgba(r, g, b, a) -- for pre-66 Chrome
|
|
* TODO: when minimum_chrome_version >= 66 try to keep `value` intact */
|
|
if (alpha) delete value.a;
|
|
const isRgb = isUsoRgb || value.type === 'rgb' || value.a > 0 && 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;
|
|
}
|
|
}
|