Change style structure

This commit is contained in:
eight 2017-09-16 09:24:50 +08:00
parent dc988a413e
commit a0495f466f
8 changed files with 156 additions and 118 deletions

View File

@ -370,7 +370,7 @@ function filterStylesInternal({
// Parse the source and find the duplication
// {id: int, style: object, source: string, checkDup: boolean}
// {id: int, style: object, sourceCode: string, checkDup: boolean}
function filterUsercss(req) {
let style;
let pendingBuild;
@ -381,8 +381,8 @@ function filterUsercss(req) {
function buildMeta() {
return new Promise(resolve => {
if (req.source) {
style = usercss.buildMeta(req.source);
if (req.sourceCode) {
style = usercss.buildMeta(req.sourceCode);
} else {
style = req.style;
}
@ -426,8 +426,8 @@ function saveUsercss(style) {
function buildMeta() {
return new Promise(resolve => {
if (!style.name || !style.namespace) {
resolve(Object.assign(usercss.buildMeta(style.source), style));
if (!style.usercssData) {
resolve(Object.assign(usercss.buildMeta(style.sourceCode), style));
return;
}
resolve(style);
@ -449,26 +449,32 @@ function saveStyle(style) {
let existed;
let codeIsUpdated;
if (style.usercss) {
return processUsercss(style).then(decide);
}
const maybeProcess = style.usercssData ? processUsercss() : Promise.resolve();
if (reason === 'update' || reason === 'update-digest') {
return calcStyleDigest(style).then(digest => {
style.originalDigest = digest;
return decide();
});
}
if (reason === 'import') {
style.originalDigest = style.originalDigest || style.styleDigest; // TODO: remove in the future
delete style.styleDigest; // TODO: remove in the future
if (typeof style.originalDigest !== 'string' || style.originalDigest.length !== 40) {
delete style.originalDigest;
return maybeProcess
.then(maybeUpdate)
.then(maybeImportFix)
.then(decide);
function maybeUpdate() {
if (reason === 'update' || reason === 'update-digest') {
return calcStyleDigest(style).then(digest => {
style.originalDigest = digest;
});
}
}
return decide();
function processUsercss(style) {
function maybeImportFix() {
if (reason === 'import') {
style.originalDigest = style.originalDigest || style.styleDigest; // TODO: remove in the future
delete style.styleDigest; // TODO: remove in the future
if (typeof style.originalDigest !== 'string' || style.originalDigest.length !== 40) {
delete style.originalDigest;
}
}
}
function processUsercss() {
return findDupUsercss(style)
.then(dup => {
if (!dup) {
@ -477,11 +483,10 @@ function saveStyle(style) {
if (!id) {
id = dup.id;
}
if (reason === 'config') {
return;
if (reason !== 'config') {
// preserve style.vars during update
usercss.assignVars(style, dup);
}
// preserve style.vars during update
usercss.assignVars(style, dup);
})
.then(() => usercss.buildCode(style));
}
@ -538,7 +543,7 @@ function saveStyle(style) {
style, codeIsUpdated, reason,
});
}
if (style.usercss && !existed && reason === 'install') {
if (style.usercssData && !existed && reason === 'update') {
// open the editor for usercss with the first install?
openEditor(style.id);
}
@ -563,7 +568,13 @@ function findDupUsercss(style) {
return getStyles({id: style.id}).then(s => s[0]);
}
return getStyles().then(styles =>
styles.find(s => s.name === style.name && s.namespace === style.namespace)
styles.find(target => {
if (!target.usercssData) {
return false;
}
return target.usercssData.name === style.usercssData.name &&
target.usercssData.namespace === style.usercssData.namespace;
})
);
}
@ -815,7 +826,8 @@ function normalizeStyleSections({sections}) {
function calcStyleDigest(style) {
const jsonString = JSON.stringify(normalizeStyleSections(style));
const jsonString = style.usercssData ?
style.sourceCode : JSON.stringify(normalizeStyleSections(style));
const text = new TextEncoder('utf-8').encode(jsonString);
return crypto.subtle.digest('SHA-1', text).then(hex);

View File

@ -64,7 +64,7 @@ var updater = {
'ignoreDigest' option is set on the second manual individual update check on the manage page.
*/
const maybeUpdate = style.usercss ? maybeUpdateUsercss : maybeUpdateUSO;
const maybeUpdate = style.usercssData ? maybeUpdateUsercss : maybeUpdateUSO;
return (ignoreDigest ? Promise.resolve() : calcStyleDigest(style))
.then(checkIfEdited)
.then(maybeUpdate)
@ -83,9 +83,7 @@ var updater = {
if (ignoreDigest) {
return;
}
if (style.usercss && style.edited) {
return Promise.reject(updater.EDITED);
} else if (style.originalDigest && style.originalDigest !== digest) {
if (style.originalDigest && style.originalDigest !== digest) {
return Promise.reject(updater.EDITED);
}
}
@ -106,12 +104,14 @@ var updater = {
function maybeUpdateUsercss() {
return download(style.updateUrl).then(text => {
const json = usercss.buildMeta(text);
const {usercssData: {version}} = style;
const {usercssData: {version: newVersion}} = json;
// re-install is invalid in a soft upgrade
if (semverCompare(style.version, json.version) === 0 && !ignoreDigest) {
if (semverCompare(version, newVersion) === 0 && !ignoreDigest) {
return Promise.reject(updater.SAME_VERSION);
}
// downgrade is always invalid
if (semverCompare(style.version, json.version) > 0) {
if (semverCompare(version, newVersion) > 0) {
return Promise.reject(updater.ERROR_VERSION);
}
return json;
@ -121,7 +121,7 @@ var updater = {
function maybeSave(json) {
json.id = style.id;
// no need to compare section code for usercss, they are built dynamically
if (!json.usercss) {
if (!json.usercssData) {
if (!styleJSONseemsValid(json)) {
return Promise.reject(updater.ERROR_JSON);
}

View File

@ -7,7 +7,7 @@ let pendingResource;
function install(style) {
const request = Object.assign(style, {
method: 'saveUsercss',
reason: 'install',
reason: 'update',
updateUrl: location.href
});
return runtimeSend(request)
@ -55,7 +55,9 @@ function getAppliesTo(style) {
function initInstallPage({style, dup}, sourceLoader) {
return pendingResource.then(() => {
const versionTest = dup && semverCompare(style.version, dup.version);
const data = style.usercssData;
const dupData = dup && dup.usercssData;
const versionTest = dup && semverCompare(data.version, dupData.version);
document.body.textContent = '';
document.body.appendChild(buildPage());
@ -65,13 +67,15 @@ function initInstallPage({style, dup}, sourceLoader) {
$('.actions')
);
}
$('.code').textContent = style.source;
$('.code').textContent = style.sourceCode;
$('button.install').onclick = () => {
if (dup) {
if (confirm(chrome.i18n.getMessage('styleInstallOverwrite', [style.name, dup.version, style.version]))) {
if (confirm(chrome.i18n.getMessage('styleInstallOverwrite', [
data.name, dupData.version, data.version
]))) {
install(style);
}
} else if (confirm(chrome.i18n.getMessage('styleInstall', [style.name]))) {
} else if (confirm(chrome.i18n.getMessage('styleInstall', [data.name]))) {
install(style);
}
};
@ -87,19 +91,19 @@ function initInstallPage({style, dup}, sourceLoader) {
$element({tag: 'button', className: 'install', textContent: installButtonLabel()})
]}),
$element({tag: 'h1', appendChild: [
style.name,
$element({tag: 'small', className: 'meta-version', textContent: style.version})
data.name,
$element({tag: 'small', className: 'meta-version', textContent: data.version})
]}),
$element({tag: 'p', textContent: style.description}),
style.author && $element({tag: 'h3', textContent: t('author')}),
style.author,
style.license && $element({tag: 'h3', textContent: t('license')}),
style.license,
$element({tag: 'p', textContent: data.description}),
data.author && $element({tag: 'h3', textContent: t('author')}),
data.author,
data.license && $element({tag: 'h3', textContent: t('license')}),
data.license,
$element({tag: 'h3', textContent: t('appliesLabel')}),
$element({tag: 'ul', appendChild: getAppliesTo(style).map(
pattern => $element({tag: 'li', textContent: pattern})
)}),
externalLink(style),
externalLink(),
]}),
$element({className: 'main', appendChild: [
$element({className: 'code'})
@ -107,13 +111,13 @@ function initInstallPage({style, dup}, sourceLoader) {
]});
}
function externalLink(style) {
function externalLink() {
const urls = [];
if (style.url) {
urls.push([style.url, t('externalHomepage')]);
if (data.homepageURL) {
urls.push([data.homepageURL, t('externalHomepage')]);
}
if (style.supportURL) {
urls.push([style.supportURL, t('externalSupport')]);
if (data.supportURL) {
urls.push([data.supportURL, t('externalSupport')]);
}
if (urls.length) {
return $element({appendChild: [
@ -139,7 +143,7 @@ function initLiveReload(sourceLoader) {
return runtimeSend({
method: 'saveUsercss',
id: installed.id,
source: source
sourceCode: source
}).then(() => {
$$('.main .warning').forEach(e => e.remove());
}).catch(err => {
@ -267,7 +271,7 @@ function initUsercssInstall() {
.then(() =>
runtimeSend({
method: 'filterUsercss',
source: sourceLoader.source(),
sourceCode: sourceLoader.source(),
checkDup: true
})
)

View File

@ -1352,7 +1352,7 @@ function setStyleMeta(style) {
function initWithStyle(request) {
if (!editor) {
if (!request.style.usercss) {
if (!request.style.usercssData) {
initWithSectionStyle(request);
} else {
editor = createSourceEditor(request.style);

View File

@ -26,7 +26,7 @@ function createSourceEditor(style) {
);
// draw CodeMirror
$('#sections textarea').value = style.source;
$('#sections textarea').value = style.sourceCode;
const cm = CodeMirror.fromTextArea($('#sections textarea'));
// too many functions depend on this global
editors.push(cm);
@ -378,8 +378,8 @@ function createSourceEditor(style) {
// source
cm.on('change', () => {
const value = cm.getValue();
dirty.modify('source', style.source, value);
style.source = value;
dirty.modify('source', style.sourceCode, value);
style.sourceCode = value;
updateLintReportIfEnabled(cm);
});
@ -402,10 +402,11 @@ function createSourceEditor(style) {
$('#name').value = style.name;
$('#enabled').checked = style.enabled;
$('#url').href = style.url;
cm.setOption('mode', MODE[style.preprocessor] || 'css');
CodeMirror.autoLoadMode(cm, style.preprocessor || 'css');
const {usercssData: {preprocessor}} = style;
cm.setOption('mode', MODE[preprocessor] || 'css');
CodeMirror.autoLoadMode(cm, MODE[preprocessor] || 'css');
// beautify only works with regular CSS
$('#beautify').disabled = Boolean(style.preprocessor);
$('#beautify').disabled = MODE[preprocessor] && MODE[preprocessor] !== 'css';
updateTitle();
}
@ -417,9 +418,9 @@ function createSourceEditor(style) {
function replaceStyle(newStyle) {
style = deepCopy(newStyle);
updateMetas();
if (style.source !== cm.getValue()) {
if (style.sourceCode !== cm.getValue()) {
const cursor = cm.getCursor();
cm.setValue(style.source);
cm.setValue(style.sourceCode);
cm.setCursor(cursor);
}
dirty.clear();
@ -448,8 +449,7 @@ function createSourceEditor(style) {
reason: 'editSave',
id: style.id,
enabled: style.enabled,
edited: dirty.has('source'),
source: style.source
sourceCode: style.sourceCode
};
return onBackgroundReady().then(() => BG.saveUsercss(req))
.then(result => {

View File

@ -4,10 +4,23 @@
// eslint-disable-next-line no-var
var usercss = (function () {
const METAS = [
'author', 'advanced', 'description', 'homepageURL', 'icon', 'license', 'name',
'namespace', 'noframes', 'preprocessor', 'supportURL', 'var', 'version'
];
// true for global, false for private
const METAS = {
__proto__: null,
author: true,
advanced: false,
description: true,
homepageURL: false,
// icon: false,
license: false,
name: true,
namespace: false,
// noframes: false,
preprocessor: false,
supportURL: false,
'var': false,
version: false
};
const META_VARS = ['text', 'color', 'checkbox', 'select', 'dropdown', 'image'];
@ -222,7 +235,7 @@ var usercss = (function () {
parseStringToEnd(state);
result.default = state.value;
}
state.style.vars[result.name] = result;
state.usercssData.vars[result.name] = result;
}
function parseEOT(state) {
@ -319,72 +332,71 @@ var usercss = (function () {
return s;
}
function _buildMeta(source) {
const style = {
name: null,
usercss: true,
version: null,
source: source,
edited: false,
enabled: true,
sections: [],
vars: {},
preprocessor: null,
noframes: false
function _buildMeta(sourceCode) {
const usercssData = {
vars: {}
};
const text = getMetaSource(source);
const style = {
enabled: true,
sourceCode,
sections: [],
usercssData
};
const text = getMetaSource(sourceCode);
const re = /@(\w+)\s+/mg;
const state = {style, re, text};
const state = {style, re, text, usercssData};
let match;
while ((match = re.exec(text))) {
state.key = match[1];
if (!METAS.includes(state.key)) {
if (!(state.key in METAS)) {
continue;
}
if (state.key === 'noframes') {
style.noframes = true;
} else if (state.key === 'var' || state.key === 'advanced') {
if (state.key === 'var' || state.key === 'advanced') {
if (state.key === 'advanced') {
state.maybeUSO = true;
}
parseVar(state);
} else {
parseStringToEnd(state);
if (state.key === 'homepageURL') {
style.url = state.value;
} else {
style[state.key] = state.value;
}
usercssData[state.key] = state.value;
}
if (METAS[state.key]) {
style[state.key] = usercssData[state.key];
}
}
if (state.maybeUSO && !style.preprocessor) {
style.preprocessor = 'uso';
if (state.maybeUSO && !usercssData.preprocessor) {
usercssData.preprocessor = 'uso';
}
if (usercssData.homepageURL) {
style.url = usercssData.homepageURL;
}
return style;
}
function buildCode(style) {
const {usercssData: {preprocessor, vars}, sourceCode} = style;
let builder;
if (style.preprocessor) {
if (!BUILDER.hasOwnProperty(style.preprocessor)) {
return Promise.reject(new Error(`Unsupported preprocessor: ${style.preprocessor}`));
if (preprocessor) {
if (!BUILDER[preprocessor]) {
return Promise.reject(new Error(`Unsupported preprocessor: ${preprocessor}`));
}
builder = BUILDER[style.preprocessor];
builder = BUILDER[preprocessor];
} else {
builder = BUILDER.default;
}
const vars = simpleVars(style.vars);
const sVars = simpleVars(vars);
return Promise.resolve().then(() => {
// preprocess
if (builder.preprocess) {
return builder.preprocess(style.source, vars);
return builder.preprocess(sourceCode, sVars);
}
return style.source;
return sourceCode;
}).then(mozStyle =>
// moz-parser
loadScript('/js/moz-parser.js').then(() =>
@ -395,7 +407,7 @@ var usercss = (function () {
).then(() => {
// postprocess
if (builder.postprocess) {
return builder.postprocess(style.sections, vars);
return builder.postprocess(style.sections, sVars);
}
}).then(() => style);
}
@ -414,22 +426,23 @@ var usercss = (function () {
}
function validate(style) {
const {usercssData: data} = style;
// mandatory fields
for (const prop of ['name', 'namespace', 'version']) {
if (!style[prop]) {
if (!data[prop]) {
throw new Error(chrome.i18n.getMessage('styleMissingMeta', prop));
}
}
// validate version
semverCompare(style.version, '0.0.0');
semverCompare(data.version, '0.0.0');
// validate URLs
validUrl(style.url);
validUrl(style.supportURL);
validUrl(data.homepageURL);
validUrl(data.supportURL);
// validate vars
for (const key of Object.keys(style.vars)) {
validVar(style.vars[key]);
for (const key of Object.keys(data.vars)) {
validVar(data.vars[key]);
}
}
@ -456,14 +469,16 @@ var usercss = (function () {
}
function assignVars(style, old) {
const {usercssData: {vars}} = style;
const {usercssData: {vars: oldVars}} = old;
// 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(style.vars)) {
if (old.vars[key] && old.vars[key].value) {
style.vars[key].value = old.vars[key].value;
for (const key of Object.keys(vars)) {
if (oldVars[key] && oldVars[key].value) {
vars[key].value = oldVars[key].value;
try {
validVar(style.vars[key], 'value');
validVar(vars[key], 'value');
} catch (e) {
style.vars[key].value = null;
vars[key].value = null;
}
}
}

View File

@ -27,7 +27,7 @@ function configDialog(style) {
function buildConfigForm() {
const labels = [];
const vars = deepCopy(style.vars);
const vars = deepCopy(style.usercssData.vars);
for (const key of Object.keys(vars)) {
const va = vars[key];
let appendChild;

View File

@ -193,7 +193,7 @@ function createStyleElement({style, name}) {
if (style.updateUrl && newUI.enabled) {
$('.actions', entry).appendChild(template.updaterIcons.cloneNode(true));
}
if (style.vars && Object.keys(style.vars).length && newUI.enabled) {
if (shouldShowConfig() && newUI.enabled) {
$('.actions', entry).appendChild(template.configureIcon.cloneNode(true));
}
@ -202,6 +202,13 @@ function createStyleElement({style, name}) {
createStyleTargetsElement({entry, style, postponeFavicons: name});
return entry;
function shouldShowConfig() {
if (!style.usercssData) {
return false;
}
return Object.keys(style.usercssData.vars).length > 0;
}
}
@ -293,7 +300,7 @@ Object.assign(handleEvent, {
}
style.reason = 'config';
for (const key of keys) {
style.vars[key].value = vars[key].value;
style.usercssData.vars[key].value = vars[key].value;
}
saveStyleSafe(style);
});