add a terse invocation syntax for $element and rename it to $create

This commit is contained in:
tophf 2017-12-04 00:12:09 +03:00
parent 6e142a7444
commit c0c60fb7a2
18 changed files with 275 additions and 366 deletions

View File

@ -55,7 +55,8 @@ globals:
animateElement: false
$: false
$$: false
$element: false
$create: false
$createLink: false
# prefs.js
prefs: false
setupLivePrefs: false
@ -236,7 +237,7 @@ rules:
one-var: [0]
operator-assignment: [2, always]
operator-linebreak: [2, after, overrides: {"?": ignore, ":": ignore, "&&": ignore, "||": ignore}]
padded-blocks: [2, never]
padded-blocks: [0]
prefer-numeric-literals: [2]
prefer-rest-params: [0]
prefer-const: [1, {destructuring: any, ignoreReadBeforeAssign: true}]

View File

@ -25,53 +25,25 @@ function createAppliesToLineWidget(cm) {
TPL = {
container:
$element({className: 'applies-to', appendChild: [
$element({tag: 'label', appendChild: t('appliesLabel')}),
$element({tag: 'ul', className: 'applies-to-list'}),
]}),
listItem: $element({
tag: 'li',
className: 'applies-to-item',
appendChild: [
$element({
tag: 'select',
className: 'applies-type',
appendChild: [
[t('appliesUrlOption'), 'url'],
[t('appliesUrlPrefixOption'), 'url-prefix'],
[t('appliesDomainOption'), 'domain'],
[t('appliesRegexpOption'), 'regexp']
].map(([textContent, value]) => $element({
tag: 'option',
value,
textContent,
})),
}),
$element({
tag: 'input',
className: 'applies-value',
}),
$element({
tag: 'button',
className: 'test-regexp',
textContent: t('styleRegexpTestButton'),
}),
$element({
tag: 'button',
className: 'remove-applies-to',
textContent: t('appliesRemove'),
}),
$element({
tag: 'button',
className: 'add-applies-to',
textContent: t('appliesAdd'),
})
]}),
appliesToEverything: $element({
tag: 'li',
className: 'applies-to-everything',
textContent: t('appliesToEverything'),
}),
$create('div.applies-to', [
$create('label', t('appliesLabel')),
$create('ul.applies-to-list'),
]),
listItem:
$create('li.applies-to-item', [
$create('select.applies-type', [
$create('option', {value: 'url'}, t('appliesUrlOption')),
$create('option', {value: 'url-prefix'}, t('appliesUrlPrefixOption')),
$create('option', {value: 'domain'}, t('appliesDomainOption')),
$create('option', {value: 'regexp'}, t('appliesRegexpOption')),
]),
$create('input.applies-value'),
$create('button.test-regexp', t('styleRegexpTestButton')),
$create('button.remove-applies-to', t('appliesRemove')),
$create('button.add-applies-to', t('appliesAdd')),
]),
appliesToEverything:
$create('li.applies-to-everything', t('appliesToEverything')),
};
CLICK_ROUTE = {
@ -164,7 +136,7 @@ function createAppliesToLineWidget(cm) {
}
};
styleVariables = $element({tag: 'style'});
styleVariables = $create('style');
fromLine = 0;
toLine = cm.doc.size;

View File

@ -115,10 +115,8 @@ onDOMready().then(() => {
if (option.type === 'checkbox') {
option = (option.labels || [])[0] || option.nextElementSibling || option;
}
progress = document.body.appendChild($element({
className: 'set-option-progress',
targetElement: option,
}));
progress = document.body.appendChild(
$create('.set-option-progress', {targetElement: option}));
}
}
if (progress) {
@ -222,12 +220,7 @@ onDOMready().then(() => {
break;
}
// avoid flicker: wait for the second stylesheet to load, then apply the theme
document.head.appendChild($element({
tag: 'link',
id: 'cm-theme2',
rel: 'stylesheet',
href: url
}));
document.head.appendChild($create('link#cm-theme2', {rel: 'stylesheet', href: url}));
setTimeout(() => {
CodeMirror.setOption(option, value);
themeLink.remove();
@ -287,7 +280,7 @@ onDOMready().then(() => {
function optionsFromArray(parent, options) {
const fragment = document.createDocumentFragment();
for (const opt of options) {
fragment.appendChild($element({tag: 'option', textContent: opt}));
fragment.appendChild($create('option', opt));
}
parent.appendChild(fragment);
}

View File

@ -69,8 +69,7 @@ var initColorpicker = () => {
}
function configureColorpicker() {
const input = $element({
tag: 'input',
const input = $create('input', {
type: 'search',
spellcheck: false,
value: prefs.get('editor.colorpicker.hotkey'),

View File

@ -457,22 +457,20 @@ function toMozillaFormat() {
function fromMozillaFormat() {
const popup = showCodeMirrorPopup(t('styleFromMozillaFormatPrompt'),
$element({appendChild: [
$element({
tag: 'button',
$create([
$create('button', {
name: 'import-append',
textContent: t('importAppendLabel'),
title: 'Ctrl-Enter:\n' + t('importAppendTooltip'),
onclick: doImport,
}),
$element({
tag: 'button',
$create('button', {
name: 'import-replace',
textContent: t('importReplaceLabel'),
title: 'Ctrl-Shift-Enter:\n' + t('importReplaceTooltip'),
onclick: () => doImport({replaceOldStyle: true}),
}),
]}));
]));
const contents = $('.contents', popup);
contents.insertBefore(popup.codebox.display.wrapper, contents.firstElementChild);
popup.codebox.focus();
@ -522,10 +520,8 @@ function fromMozillaFormat() {
}
function showError(errors) {
showHelp(t('styleFromMozillaFormatError'), $element({
tag: 'pre',
textContent: Array.isArray(errors) ? errors.join('\n') : errors,
}));
showHelp(t('styleFromMozillaFormatError'),
$create('pre', Array.isArray(errors) ? errors.join('\n') : errors));
}
}
@ -617,7 +613,7 @@ function showCodeMirrorPopup(title, html, options) {
function setGlobalProgress(done, total) {
const progressElement = $('#global-progress') ||
total && document.body.appendChild($element({id: 'global-progress'}));
total && document.body.appendChild($create('#global-progress'));
if (total) {
const progress = (done / Math.max(done, total) * 100).toFixed(1);
progressElement.style.borderLeftWidth = progress + 'vw';

View File

@ -1,7 +1,6 @@
/* global CodeMirror messageBox */
/* global editors makeSectionVisible showCodeMirrorPopup showHelp */
/* global loadScript require CSSLint stylelint */
/* global makeLink */
'use strict';
onDOMready().then(loadLinterAssets);
@ -231,9 +230,7 @@ function updateLinter({immediately, linter = linterConfig.getName()} = {}) {
cm.options.gutters = guttersOption;
const el = $('.' + GUTTERS_CLASS, cm.display.gutters);
if (linter && !el) {
cm.display.gutters.appendChild($element({
className: 'CodeMirror-gutter ' + GUTTERS_CLASS
}));
cm.display.gutters.appendChild($create('.CodeMirror-gutter ' + GUTTERS_CLASS));
} else if (!linter && el) {
el.remove();
}
@ -281,9 +278,8 @@ function updateLintReportInternal(scope, {postponeNewIssues} = {}) {
const newMarkers = lintState.stylusMarkers = new Map();
const oldText = (lintState.body || {}).textContentCached || '';
const activeLine = cm.getCursor().line;
const body = !(lintState.marked || {}).length ? {} : $element({
tag: 'tbody',
appendChild: lintState.marked.map(mark => {
const body = !(lintState.marked || {}).length ? {} :
$create('tbody', lintState.marked.map(mark => {
const info = mark.__annotation;
const {line, ch} = info.from;
const isActiveLine = line === activeLine;
@ -294,27 +290,15 @@ function updateLintReportInternal(scope, {postponeNewIssues} = {}) {
oldMarkers.delete(pos);
}
newMarkers.set(pos, message);
return $element({
tag: 'tr',
className: info.severity,
appendChild: [
$element({
tag: 'td',
attributes: {role: 'severity'},
dataset: {rule: info.rule},
appendChild: $element({
className: 'CodeMirror-lint-marker-' + info.severity,
textContent: info.severity,
}),
}),
$element({tag: 'td', attributes: {role: 'line'}, textContent: line + 1}),
$element({tag: 'td', attributes: {role: 'sep'}, textContent: ':'}),
$element({tag: 'td', attributes: {role: 'col'}, textContent: ch + 1}),
$element({tag: 'td', attributes: {role: 'message'}, textContent: message, title}),
],
});
})
});
return $create(`tr.${info.severity}`, [
$create('td', {attributes: {role: 'severity'}, dataset: {rule: info.rule}},
$create('.CodeMirror-lint-marker-' + info.severity, info.severity)),
$create('td', {attributes: {role: 'line'}}, line + 1),
$create('td', {attributes: {role: 'sep'}}, ':'),
$create('td', {attributes: {role: 'col'}}, ch + 1),
$create('td', {attributes: {role: 'message'}, title}, message),
]);
}));
body.textContentCached = body.textContent || '';
lintState.body = body.textContentCached && body;
result.changed |= oldText !== body.textContentCached;
@ -340,14 +324,10 @@ function renderLintReport(someBlockChanged) {
if (!body) {
return;
}
const newBlock = $element({
tag: 'table',
appendChild: [
$element({tag: 'caption', textContent: label + ' ' + (index + 1)}),
body,
],
cm,
});
const newBlock = $create('table', {cm}, [
$create('caption', label + ' ' + (index + 1)),
body,
]);
newContent.appendChild(newBlock);
issueCount += newBlock.rows.length;
@ -388,35 +368,29 @@ function showLintHelp() {
: 'https://github.com/CSSLint/csslint/issues/535';
let headerLink, template;
if (linter === 'csslint') {
headerLink = makeLink('https://github.com/CSSLint/csslint/wiki/Rules-by-ID', 'CSSLint');
headerLink = $createLink('https://github.com/CSSLint/csslint/wiki/Rules-by-ID', 'CSSLint');
template = ruleID => {
const rule = linterConfig.allRuleIds.csslint.find(rule => rule.id === ruleID);
return rule &&
$element({tag: 'li', appendChild: [
$element({tag: 'b', appendChild: makeLink(rule.url || baseUrl, rule.name)}),
$element({tag: 'br'}),
$create('li', [
$create('b', $createLink(rule.url || baseUrl, rule.name)),
$create('br'),
rule.desc,
]});
]);
};
} else {
headerLink = makeLink(baseUrl, 'stylelint');
headerLink = $createLink(baseUrl, 'stylelint');
template = rule =>
$element({
tag: 'li',
appendChild: makeLink(baseUrl + rule, rule),
});
$create('li',
$createLink(baseUrl + rule, rule));
}
const header = t('linterIssuesHelp', '\x01').split('\x01');
const activeRules = new Set($$('#lint td[role="severity"]').map(el => el.dataset.rule));
return showHelp(t('linterIssues'),
$element({appendChild: [
$create([
header[0], headerLink, header[1],
$element({
tag: 'ul',
className: 'rules',
appendChild: [...activeRules.values()].map(template),
}),
]})
$create('ul.rules', [...activeRules.values()].map(template)),
])
);
}
@ -465,41 +439,21 @@ function setupLinterPopup(config) {
});
function makeFooter() {
const makeButton = (className, onclick, text, options = {}) =>
$element(Object.assign(options, {
className,
onclick,
tag: 'button',
type: 'button',
textContent: t(text),
}));
return $element({
appendChild: [
$element({
tag: 'p',
appendChild: [
t('linterRulesLink') + ' ',
$element({
tag: 'a',
target: '_blank',
href: linter === 'stylelint'
? 'https://stylelint.io/user-guide/rules/'
: 'https://github.com/CSSLint/csslint/wiki/Rules-by-ID',
textContent: linterTitle
}),
linter === 'csslint' ? ' ' + t('linterCSSLintSettings') : ''
]
}),
makeButton('save', save, 'styleSaveLabel', {title: 'Ctrl-Enter'}),
makeButton('cancel', cancel, 'confirmClose'),
makeButton('reset', reset, 'genericResetLabel', {title: t('linterResetMessage')}),
$element({
tag: 'span',
className: 'saved-message',
textContent: t('genericSavedMessage')
})
]
});
return $create('div', [
$create('p', [
t('linterRulesLink') + ' ',
$createLink(
linter === 'stylelint'
? 'https://stylelint.io/user-guide/rules/'
: 'https://github.com/CSSLint/csslint/wiki/Rules-by-ID',
linterTitle),
linter === 'csslint' ? ' ' + t('linterCSSLintSettings') : '',
]),
$create('button.save', {onclick: save, title: 'Ctrl-Enter'}, t('styleSaveLabel')),
$create('button.cancel', {onclick: cancel}, t('confirmClose')),
$create('button.reset', {onclick: reset, title: t('linterResetMessage')}, t('genericResetLabel')),
$create('span.saved-message', t('genericSavedMessage')),
]);
}
function save(event) {
@ -516,9 +470,7 @@ function setupLinterPopup(config) {
if (invalid.length) {
showLinterErrorMessage(linter, [
t('linterInvalidConfigError'),
$element({tag: 'ul', appendChild: invalid.map(name =>
$element({tag: 'li', textContent: name})),
}),
$create('ul', invalid.map(name => $create('li', name))),
], popup);
return;
}

View File

@ -34,7 +34,7 @@ var regExpTester = (() => {
if (!isInit) {
init();
}
showHelp('', $element({className: 'regexp-report'}));
showHelp('', $create('.regexp-report'));
} else if (!state && isShown()) {
if (isInit) {
uninit();
@ -108,19 +108,19 @@ var regExpTester = (() => {
const faviconUrl = url.startsWith(URLS.ownOrigin)
? OWN_ICON
: GET_FAVICON_URL + new URL(url).hostname;
const icon = $element({tag: 'img', src: faviconUrl});
const icon = $create('img', {src: faviconUrl});
if (match.text.length === url.length) {
full.push($element({appendChild: [
full.push($create('div', [
icon,
url,
]}));
]));
} else {
partial.push($element({appendChild: [
partial.push($create('div', [
icon,
url.substr(0, match.pos),
$element({tag: 'mark', textContent: match.text}),
$create('mark', match.text),
url.substr(match.pos + match.text.length),
]}));
]));
}
}
if (full.length) {
@ -131,33 +131,28 @@ var regExpTester = (() => {
}
}
// render stats
const report = $element({className: 'regexp-report'});
const br = $element({tag: 'br'});
const report = $create('.regexp-report');
const br = $create('br');
for (const type in stats) {
// top level groups: full, partial, none, invalid
const {label, data} = stats[type];
if (!data.length) {
continue;
}
const block = report.appendChild($element({
tag: 'details',
open: true,
dataset: {type},
appendChild: $element({tag: 'summary', appendChild: label}),
}));
const block = report.appendChild(
$create('details', {open: true, dataset: {type}}, [
$create('summary', label),
]));
// 2nd level: regexp text
for (const {text, urls} of data) {
if (urls) {
// type is partial or full
block.appendChild($element({
tag: 'details',
open: true,
appendChild: [
$element({tag: 'summary', textContent: text}),
block.appendChild(
$create('details', {open: true}, [
$create('summary', text),
// 3rd level: tab urls
...urls,
],
}));
]));
} else {
// type is none or invalid
block.appendChild(document.createTextNode(text));
@ -165,12 +160,11 @@ var regExpTester = (() => {
}
}
}
report.appendChild($element({
tag: 'p',
className: 'regexp-report-note',
appendChild: t('styleRegexpTestNote').split(/(\\+)/)
.map(s => s.startsWith('\\') ? $element({tag: 'code', textContent: s}) : s),
}));
report.appendChild(
$create('p.regexp-report-note',
t('styleRegexpTestNote')
.split(/(\\+)/)
.map(s => (s.startsWith('\\') ? $create('code', s) : s))));
showHelp(t('styleRegexpTestTitle'), report);
report.onclick = event => {

View File

@ -64,7 +64,7 @@ function showKeyMapHelp() {
if (index > offset) {
cell.appendChild(document.createTextNode(text.substring(offset, index)));
}
cell.appendChild($element({tag: 'mark', textContent: match}));
cell.appendChild($create('mark', match));
offset = index + match.length;
});
if (offset + 1 !== text.length) {

View File

@ -1,7 +1,7 @@
/* global CodeMirror dirtyReporter initLint */
/* global showToggleStyleHelp goBackToManage updateLintReportIfEnabled */
/* global editors linterConfig updateLinter regExpTester mozParser */
/* global makeLink createAppliesToLineWidget messageBox */
/* global createAppliesToLineWidget messageBox */
'use strict';
function createSourceEditor(style) {
@ -12,9 +12,7 @@ function createSourceEditor(style) {
$('#name').disabled = true;
$('#mozilla-format-container').remove();
$('#sections').textContent = '';
$('#sections').appendChild(
$element({className: 'single-editor'})
);
$('#sections').appendChild($create('.single-editor'));
const dirty = dirtyReporter();
dirty.onChange(() => {
@ -236,15 +234,12 @@ function createSourceEditor(style) {
return;
}
const contents = Array.isArray(err) ?
$element({tag: 'pre', textContent: err.join('\n')}) :
$create('pre', err.join('\n')) :
[String(err)];
if (Number.isInteger(err.index)) {
const pos = cm.posFromIndex(err.index);
contents[0] += ` (line ${pos.line + 1} col ${pos.ch + 1})`;
contents.push($element({
tag: 'pre',
textContent: drawLinePointer(pos)
}));
contents.push($create('pre', drawLinePointer(pos)));
}
messageBox.alert(contents);
});

View File

@ -1,4 +1,4 @@
/* global CodeMirror semverCompare makeLink closeCurrentTab */
/* global CodeMirror semverCompare closeCurrentTab */
/* global messageBox download chromeLocal */
'use strict';
@ -44,11 +44,8 @@
setTimeout(() => {
if (!installed) {
const div = $element({});
$('.header').appendChild($element({
className: 'lds-spinner',
appendChild: new Array(12).fill(div).map(e => e.cloneNode()),
}));
$('.header').appendChild($create('.lds-spinner',
new Array(12).fill($create('div')).map(e => e.cloneNode())));
}
}, 200);
@ -101,8 +98,7 @@
$('.applies-to').textContent = '';
getAppliesTo(style).forEach(pattern =>
$('.applies-to').appendChild($element({tag: 'li', textContent: pattern}))
);
$('.applies-to').appendChild($create('li', pattern)));
$('.external-link').textContent = '';
const externalLink = makeExternalLink();
@ -125,46 +121,35 @@
const [, name, email, url] = match;
const frag = document.createDocumentFragment();
if (email) {
frag.appendChild(makeLink(`mailto:${email}`, name));
frag.appendChild($createLink(`mailto:${email}`, name));
} else {
frag.appendChild($element({
tag: 'span',
textContent: name
}));
frag.appendChild($create('span', name));
}
if (url) {
frag.appendChild(makeLink(
url,
$element({
tag: 'svg#svg',
viewBox: '0 0 20 20',
class: 'svg-icon',
appendChild: $element({
tag: 'svg#path',
frag.appendChild($createLink(url,
$create('SVG:svg.svg-icon', {viewBox: '0 0 20 20'},
$create('SVG:path', {
d: 'M4,4h5v2H6v8h8v-3h2v5H4V4z M11,3h6v6l-2-2l-4,4L9,9l4-4L11,3z'
})
})
}))
));
}
return frag;
}
function makeExternalLink() {
const urls = [];
if (data.homepageURL) {
urls.push([data.homepageURL, t('externalHomepage')]);
}
if (data.supportURL) {
urls.push([data.supportURL, t('externalSupport')]);
}
if (urls.length) {
return $element({appendChild: [
$element({tag: 'h3', textContent: t('externalLink')}),
$element({tag: 'ul', appendChild: urls.map(args =>
$element({tag: 'li', appendChild: makeLink(...args)})
)})
]});
}
const urls = [
data.homepageURL && [data.homepageURL, t('externalHomepage')],
data.supportURL && [data.supportURL, t('externalSupport')],
];
return (data.homepageURL || data.supportURL) && (
$create('div', [
$create('h3', t('externalLink')),
$create('ul', urls.map(args => args &&
$create('li',
$createLink(...args)
)
))
]));
}
function installButtonClass() {
@ -230,26 +215,23 @@
function buildWarning(err) {
const contents = Array.isArray(err) ?
$element({tag: 'pre', textContent: err.join('\n')}) :
$create('pre', err.join('\n')) :
[err && err.message || err || 'Unknown error'];
if (Number.isInteger(err.index)) {
const pos = cm.posFromIndex(err.index);
contents[0] = `${pos.line + 1}:${pos.ch + 1} ` + contents[0];
contents.push($element({
tag: 'pre',
textContent: drawLinePointer(pos)
}));
contents.push($create('pre', drawLinePointer(pos)));
setTimeout(() => {
cm.scrollIntoView({line: pos.line + 1, ch: pos.ch}, window.innerHeight / 4);
cm.setCursor(pos.line, pos.ch + 1);
cm.focus();
});
}
return $element({className: 'warning', appendChild: [
return $create('.warning', [
t('parseUsercssError'),
'\n',
...contents,
]});
]);
}
function drawLinePointer(pos) {
@ -281,7 +263,7 @@
// update UI
if (versionTest < 0) {
$('.actions').parentNode.insertBefore(
$element({className: 'warning', textContent: t('versionInvalidOlder')}),
$create('.warning', t('versionInvalidOlder')),
$('.actions')
);
}

View File

@ -73,8 +73,7 @@ if (!chrome.app && chrome.windows) {
}
const iconset = ['', 'light/'][prefs.get('iconset')] || '';
for (const size of [38, 32, 19, 16]) {
document.head.appendChild($element({
tag: 'link',
document.head.appendChild($create('link', {
rel: 'icon',
href: `/images/icon/${iconset}${size}.png`,
sizes: size + 'x' + size,
@ -167,35 +166,88 @@ function $$(selector, base = document) {
}
function $element(opt) {
// tag: string, default 'div', may include namespace like 'ns#tag'
// appendChild: element/string or an array of elements/strings
// dataset: object
// any DOM property: assigned as is
const [ns, tag] = opt.tag && opt.tag.includes('#')
? opt.tag.split('#')
: [null, opt.tag];
function $create(selector = 'div', properties, children) {
/*
$create('tag#id.class.class', ?[children])
$create('tag#id.class.class', ?textContentOrChildNode)
$create('tag#id.class.class', {properties}, ?[children])
$create('tag#id.class.class', {properties}, ?textContentOrChildNode)
tag is 'div' by default, #id and .class are optional
$create([children])
$create({propertiesAndOptions})
$create({propertiesAndOptions}, ?[children])
tag: string, default 'div'
appendChild: element/string or an array of elements/strings
dataset: object
any DOM property: assigned as is
tag may include namespace like 'ns:tag'
*/
let ns, tag, opt;
if (typeof selector === 'string') {
if (Array.isArray(properties) ||
properties instanceof Node ||
typeof properties !== 'object') {
opt = {};
children = properties;
} else {
opt = properties || {};
}
const idStart = (selector.indexOf('#') + 1 || selector.length + 1) - 1;
const classStart = (selector.indexOf('.') + 1 || selector.length + 1) - 1;
const id = selector.slice(idStart + 1, classStart);
if (id) {
opt.id = id;
}
const cls = selector.slice(classStart + 1);
if (cls) {
opt[selector.includes(':') ? 'class' : 'className'] =
cls.includes('.') ? cls.replace(/\./g, ' ') : cls;
}
tag = selector.slice(0, Math.min(idStart, classStart));
} else if (Array.isArray(selector)) {
tag = 'div';
opt = {};
children = selector;
} else {
opt = selector;
tag = opt.tag;
delete opt.tag;
children = opt.appendChild || properties;
delete opt.appendChild;
}
if (tag && tag.includes(':')) {
([ns, tag] = tag.split(':'));
}
const element = ns
? document.createElementNS(ns === 'SVG' || ns === 'svg' ? 'http://www.w3.org/2000/svg' : ns, tag)
: document.createElement(tag || 'div');
const children = Array.isArray(opt.appendChild) ? opt.appendChild : [opt.appendChild];
for (const child of children) {
for (const child of Array.isArray(children) ? children : [children]) {
if (child) {
element.appendChild(child instanceof Node ? child : document.createTextNode(child));
}
}
delete opt.appendChild;
delete opt.tag;
if (opt.dataset) {
Object.assign(element.dataset, opt.dataset);
delete opt.dataset;
}
if (opt.attributes) {
for (const attr in opt.attributes) {
element.setAttribute(attr, opt.attributes[attr]);
}
delete opt.attributes;
}
if (ns) {
for (const attr in opt) {
element.setAttributeNS(null, attr, opt[attr]);
@ -203,11 +255,12 @@ function $element(opt) {
} else {
Object.assign(element, opt);
}
return element;
}
function makeLink(href = '', content) {
function $createLink(href = '', content) {
const opt = {
tag: 'a',
target: '_blank',
@ -217,9 +270,9 @@ function makeLink(href = '', content) {
Object.assign(opt, href);
} else {
opt.href = href;
opt.appendChild = content;
}
return $element(opt);
opt.appendChild = opt.appendChild || content;
return $create(opt);
}

View File

@ -1,8 +1,9 @@
/* global messageBox makeLink */
/* global messageBox */
'use strict';
function configDialog(style) {
const varsHash = deepCopy(style.usercssData.vars) || {};
const data = style.usercssData;
const varsHash = deepCopy(data.vars) || {};
const varNames = Object.keys(varsHash);
const vars = varNames.map(name => varsHash[name]);
const elements = [];
@ -12,21 +13,12 @@ function configDialog(style) {
renderValues();
return messageBox({
title: `${style.name} v${style.usercssData.version}`,
title: `${style.name} v${data.version}`,
className: 'config-dialog',
contents: [
$element({
className: 'config-heading',
appendChild: style.usercssData.supportURL && makeLink({
className: 'external-support',
href: style.usercssData.supportURL,
textContent: t('externalFeedback')
})
}),
$element({
className: 'config-body',
appendChild: elements
})
$create('.config-heading', data.supportURL &&
$createLink({className: '.external-support', href: data.supportURL}, t('externalFeedback'))),
$create('.config-body', elements)
],
buttons: [
t('confirmSave'),
@ -71,19 +63,17 @@ function configDialog(style) {
continue;
}
invalid.push(['*' + va.name, ': ', ...error].map(e =>
e[0] === '*' && $element({tag: 'b', textContent: e.slice(1)}) || e));
e[0] === '*' && $create('b', e.slice(1)) || e));
if (bgva) {
styleVars[va.name].value = deepCopy(bgva);
}
}
if (invalid.length) {
messageBox.alert([
$element({textContent: t('usercssConfigIncomplete'), style: 'max-width: 34em'}),
$element({
tag: 'ol',
style: 'text-align: left',
appendChild: invalid.map(msg => $element({tag: 'li', appendChild: msg})),
}),
$create('div', {style: 'max-width: 34em'}, t('usercssConfigIncomplete')),
$create('ol', {style: 'text-align: left'},
invalid.map(msg =>
$create({tag: 'li', appendChild: msg}))),
]);
}
return numValid && BG.usercssHelper.save(style);
@ -91,30 +81,28 @@ function configDialog(style) {
function buildConfigForm() {
for (const va of vars) {
let appendChild;
let children;
switch (va.type) {
case 'color':
appendChild = [$element({
className: 'cm-colorview',
appendChild: va.inputColor = $element({
va,
className: 'color-swatch',
onclick: showColorpicker,
})
})];
va.inputColor = $create('.color-swatch', {va, onclick: showColorpicker});
children = [
$create('.cm-colorview', [
va.inputColor,
]),
];
break;
case 'checkbox':
va.input = $element({tag: 'input', type: 'checkbox', className: 'slider'});
va.input = $create('input.slider', {type: 'checkbox'});
va.input.onchange = () => {
va.dirty = true;
va.value = String(Number(va.input.checked));
};
appendChild = [
$element({tag: 'span', className: 'onoffswitch', appendChild: [
children = [
$create('span.onoffswitch', [
va.input,
$element({tag: 'span'})
]})
$create('span'),
])
];
break;
@ -122,36 +110,30 @@ function configDialog(style) {
case 'dropdown':
case 'image':
// TODO: a image picker input?
va.input = $element({
tag: 'select',
appendChild: va.options.map(o => $element({
tag: 'option', value: o.name, textContent: o.label
}))
});
va.input = $create('select',
va.options.map(o =>
$create('option', {value: o.name}, o.label)));
va.input.onchange = () => {
va.dirty = true;
va.value = va.input.value;
};
appendChild = [va.input];
children = [va.input];
break;
default:
va.input = $element({tag: 'input', type: 'text'});
va.input = $create('input', {type: 'text'});
va.input.oninput = () => {
va.dirty = true;
va.value = va.input.value;
};
appendChild = [va.input];
children = [va.input];
break;
}
elements.push($element({
tag: 'label',
className: `config-${va.type}`,
appendChild: [
$element({tag: 'span', appendChild: va.label}),
...appendChild,
],
}));
elements.push(
$create(`label.config-${va.type}`, [
$create('span', va.label),
...children,
]));
}
}

View File

@ -175,18 +175,16 @@ function importFromString(jsonString) {
.map(kind => {
const {ids, names, legend} = stats[kind];
const listItemsWithId = (name, i) =>
$element({dataset: {id: ids[i]}, textContent: name});
$create('div', {dataset: {id: ids[i]}}, name);
const listItems = name =>
$element({textContent: name});
$create('div', name);
const block =
$element({tag: 'details', dataset: {id: kind}, appendChild: [
$element({tag: 'summary', appendChild:
$element({tag: 'b', textContent: names.length + ' ' + t(legend)})
}),
$element({tag: 'small', appendChild:
names.map(ids ? listItemsWithId : listItems)
}),
]});
$create('details', {dataset: {id: kind}}, [
$create('summary',
$create('b', names.length + ' ' + t(legend))),
$create('small',
names.map(ids ? listItemsWithId : listItems)),
]);
return block;
});
scrollTo(0, 0);
@ -308,8 +306,7 @@ $('#file-all-styles').onclick = () => {
const text = JSON.stringify(styles, null, '\t');
const blob = new Blob([text], {type: 'application/json'});
const objectURL = URL.createObjectURL(blob);
let link = $element({
tag:'a',
let link = $create('a', {
href: objectURL,
type: 'application/json',
download: generateFileName(),
@ -319,8 +316,7 @@ $('#file-all-styles').onclick = () => {
link.dispatchEvent(new MouseEvent('click'));
setTimeout(() => URL.revokeObjectURL(objectURL));
} else {
const iframe = document.body.appendChild($element({
tag: 'iframe',
const iframe = document.body.appendChild($create('iframe', {
style: 'width: 0; height: 0; position: fixed; opacity: 0;'.replace(/;/g, '!important;'),
}));
doTimeout()

View File

@ -5,8 +5,7 @@ onDOMready().then(() => {
let prevText, focusedLink, focusedEntry;
let prevTime = performance.now();
let focusedName = '';
const input = $element({
tag: 'textarea',
const input = $create('textarea', {
spellcheck: false,
oninput: incrementalSearch,
});

View File

@ -84,14 +84,14 @@ function initGlobalEvents() {
switchUI({styleOnly: true});
// translate CSS manually
document.head.appendChild($element({tag: 'style', textContent: `
document.head.appendChild($create('style', `
.disabled h2::after {
content: "${t('genericDisabledLabel')}";
}
#update-all-no-updates[data-skipped-edited="true"]:after {
content: " ${t('updateAllCheckSucceededSomeEdited')}";
}
`}));
`));
}

View File

@ -174,10 +174,7 @@ function showUpdateHistory() {
BG.chromeLocal.getValue('updateLog').then((lines = []) => {
messageBox({
title: t('updateCheckHistory'),
contents: $element({
className: 'update-history-log',
textContent: lines.join('\n'),
}),
contents: $create('.update-history-log', lines.join('\n')),
buttons: [t('confirmOK')],
onshow: () => ($('#message-box-contents').scrollTop = 1e9),
});

View File

@ -58,27 +58,25 @@ function messageBox({
removeSelf();
}
const id = 'message-box';
messageBox.element = $element({id, className, appendChild: [
$element({appendChild: [
$element({id: `${id}-title`, textContent: title}),
$element({id: `${id}-close-icon`, appendChild:
$element({tag: 'SVG#svg', class: 'svg-icon', viewBox: '0 0 20 20', appendChild:
$element({tag: 'SVG#path', d: 'M11.69,10l4.55,4.55-1.69,1.69L10,11.69,' +
'5.45,16.23,3.77,14.55,8.31,10,3.77,5.45,5.45,3.77,10,8.31l4.55-4.55,1.69,1.69Z',
})
}),
onclick: messageBox.listeners.closeIcon}),
$element({id: `${id}-contents`, appendChild: tHTML(contents)}),
$element({id: `${id}-buttons`, appendChild:
buttons.map((content, buttonIndex) => content && $element({
tag: 'button',
buttonIndex,
textContent: content.textContent || content,
onclick: content.onclick || messageBox.listeners.button,
}))
}),
]}),
]});
messageBox.element =
$create({id, className}, [
$create([
$create(`#${id}-title`, title),
$create(`#${id}-close-icon`, {onclick: messageBox.listeners.closeIcon},
$create('SVG:svg.svg-icon', {viewBox: '0 0 20 20'},
$create('SVG:path', {d: 'M11.69,10l4.55,4.55-1.69,1.69L10,11.69,' +
'5.45,16.23,3.77,14.55,8.31,10,3.77,5.45,5.45,3.77,10,8.31l4.55-4.55,1.69,1.69Z',
}))),
$create(`#${id}-contents`, tHTML(contents)),
$create(`#${id}-buttons`,
buttons.map((content, buttonIndex) => content &&
$create('button', {
buttonIndex,
textContent: content.textContent || content,
onclick: content.onclick || messageBox.listeners.button,
}))),
]),
]);
}
function bindGlobalListeners() {

View File

@ -142,13 +142,13 @@ window.addEventListener('showStyles:done', function _() {
line
.split(/(<.*?>)/)
.map(s => (!s.startsWith('<') ? s :
$element({tag: 'mark', textContent: s.slice(1, -1)})));
$create('mark', s.slice(1, -1))));
const linesToElements = text =>
text
.trim()
.split('\n')
.map((line, i, array) =>
$element(i < array.length - 1 ? {
$create(i < array.length - 1 ? {
tag: 'p',
appendChild: keysToElements(line),
} : {
@ -159,9 +159,9 @@ window.addEventListener('showStyles:done', function _() {
}));
[
linesToElements(t('popupHotkeysInfo')),
$element({tag: 'button', textContent: t('confirmOK')}),
$create('button', t('confirmOK')),
].forEach(child => {
container.appendChild($element({appendChild: child}));
container.appendChild($create('div', child));
});
}
}