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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -73,8 +73,7 @@ if (!chrome.app && chrome.windows) {
} }
const iconset = ['', 'light/'][prefs.get('iconset')] || ''; const iconset = ['', 'light/'][prefs.get('iconset')] || '';
for (const size of [38, 32, 19, 16]) { for (const size of [38, 32, 19, 16]) {
document.head.appendChild($element({ document.head.appendChild($create('link', {
tag: 'link',
rel: 'icon', rel: 'icon',
href: `/images/icon/${iconset}${size}.png`, href: `/images/icon/${iconset}${size}.png`,
sizes: size + 'x' + size, sizes: size + 'x' + size,
@ -167,35 +166,88 @@ function $$(selector, base = document) {
} }
function $element(opt) { function $create(selector = 'div', properties, children) {
// tag: string, default 'div', may include namespace like 'ns#tag' /*
// appendChild: element/string or an array of elements/strings $create('tag#id.class.class', ?[children])
// dataset: object $create('tag#id.class.class', ?textContentOrChildNode)
// any DOM property: assigned as is $create('tag#id.class.class', {properties}, ?[children])
const [ns, tag] = opt.tag && opt.tag.includes('#') $create('tag#id.class.class', {properties}, ?textContentOrChildNode)
? opt.tag.split('#') tag is 'div' by default, #id and .class are optional
: [null, opt.tag];
$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 const element = ns
? document.createElementNS(ns === 'SVG' || ns === 'svg' ? 'http://www.w3.org/2000/svg' : ns, tag) ? document.createElementNS(ns === 'SVG' || ns === 'svg' ? 'http://www.w3.org/2000/svg' : ns, tag)
: document.createElement(tag || 'div'); : 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) { if (child) {
element.appendChild(child instanceof Node ? child : document.createTextNode(child)); element.appendChild(child instanceof Node ? child : document.createTextNode(child));
} }
} }
delete opt.appendChild;
delete opt.tag;
if (opt.dataset) { if (opt.dataset) {
Object.assign(element.dataset, opt.dataset); Object.assign(element.dataset, opt.dataset);
delete opt.dataset; delete opt.dataset;
} }
if (opt.attributes) { if (opt.attributes) {
for (const attr in opt.attributes) { for (const attr in opt.attributes) {
element.setAttribute(attr, opt.attributes[attr]); element.setAttribute(attr, opt.attributes[attr]);
} }
delete opt.attributes; delete opt.attributes;
} }
if (ns) { if (ns) {
for (const attr in opt) { for (const attr in opt) {
element.setAttributeNS(null, attr, opt[attr]); element.setAttributeNS(null, attr, opt[attr]);
@ -203,11 +255,12 @@ function $element(opt) {
} else { } else {
Object.assign(element, opt); Object.assign(element, opt);
} }
return element; return element;
} }
function makeLink(href = '', content) { function $createLink(href = '', content) {
const opt = { const opt = {
tag: 'a', tag: 'a',
target: '_blank', target: '_blank',
@ -217,9 +270,9 @@ function makeLink(href = '', content) {
Object.assign(opt, href); Object.assign(opt, href);
} else { } else {
opt.href = href; 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'; 'use strict';
function configDialog(style) { function configDialog(style) {
const varsHash = deepCopy(style.usercssData.vars) || {}; const data = style.usercssData;
const varsHash = deepCopy(data.vars) || {};
const varNames = Object.keys(varsHash); const varNames = Object.keys(varsHash);
const vars = varNames.map(name => varsHash[name]); const vars = varNames.map(name => varsHash[name]);
const elements = []; const elements = [];
@ -12,21 +13,12 @@ function configDialog(style) {
renderValues(); renderValues();
return messageBox({ return messageBox({
title: `${style.name} v${style.usercssData.version}`, title: `${style.name} v${data.version}`,
className: 'config-dialog', className: 'config-dialog',
contents: [ contents: [
$element({ $create('.config-heading', data.supportURL &&
className: 'config-heading', $createLink({className: '.external-support', href: data.supportURL}, t('externalFeedback'))),
appendChild: style.usercssData.supportURL && makeLink({ $create('.config-body', elements)
className: 'external-support',
href: style.usercssData.supportURL,
textContent: t('externalFeedback')
})
}),
$element({
className: 'config-body',
appendChild: elements
})
], ],
buttons: [ buttons: [
t('confirmSave'), t('confirmSave'),
@ -71,19 +63,17 @@ function configDialog(style) {
continue; continue;
} }
invalid.push(['*' + va.name, ': ', ...error].map(e => 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) { if (bgva) {
styleVars[va.name].value = deepCopy(bgva); styleVars[va.name].value = deepCopy(bgva);
} }
} }
if (invalid.length) { if (invalid.length) {
messageBox.alert([ messageBox.alert([
$element({textContent: t('usercssConfigIncomplete'), style: 'max-width: 34em'}), $create('div', {style: 'max-width: 34em'}, t('usercssConfigIncomplete')),
$element({ $create('ol', {style: 'text-align: left'},
tag: 'ol', invalid.map(msg =>
style: 'text-align: left', $create({tag: 'li', appendChild: msg}))),
appendChild: invalid.map(msg => $element({tag: 'li', appendChild: msg})),
}),
]); ]);
} }
return numValid && BG.usercssHelper.save(style); return numValid && BG.usercssHelper.save(style);
@ -91,30 +81,28 @@ function configDialog(style) {
function buildConfigForm() { function buildConfigForm() {
for (const va of vars) { for (const va of vars) {
let appendChild; let children;
switch (va.type) { switch (va.type) {
case 'color': case 'color':
appendChild = [$element({ va.inputColor = $create('.color-swatch', {va, onclick: showColorpicker});
className: 'cm-colorview', children = [
appendChild: va.inputColor = $element({ $create('.cm-colorview', [
va, va.inputColor,
className: 'color-swatch', ]),
onclick: showColorpicker, ];
})
})];
break; break;
case 'checkbox': case 'checkbox':
va.input = $element({tag: 'input', type: 'checkbox', className: 'slider'}); va.input = $create('input.slider', {type: 'checkbox'});
va.input.onchange = () => { va.input.onchange = () => {
va.dirty = true; va.dirty = true;
va.value = String(Number(va.input.checked)); va.value = String(Number(va.input.checked));
}; };
appendChild = [ children = [
$element({tag: 'span', className: 'onoffswitch', appendChild: [ $create('span.onoffswitch', [
va.input, va.input,
$element({tag: 'span'}) $create('span'),
]}) ])
]; ];
break; break;
@ -122,36 +110,30 @@ function configDialog(style) {
case 'dropdown': case 'dropdown':
case 'image': case 'image':
// TODO: a image picker input? // TODO: a image picker input?
va.input = $element({ va.input = $create('select',
tag: 'select', va.options.map(o =>
appendChild: va.options.map(o => $element({ $create('option', {value: o.name}, o.label)));
tag: 'option', value: o.name, textContent: o.label
}))
});
va.input.onchange = () => { va.input.onchange = () => {
va.dirty = true; va.dirty = true;
va.value = va.input.value; va.value = va.input.value;
}; };
appendChild = [va.input]; children = [va.input];
break; break;
default: default:
va.input = $element({tag: 'input', type: 'text'}); va.input = $create('input', {type: 'text'});
va.input.oninput = () => { va.input.oninput = () => {
va.dirty = true; va.dirty = true;
va.value = va.input.value; va.value = va.input.value;
}; };
appendChild = [va.input]; children = [va.input];
break; break;
} }
elements.push($element({ elements.push(
tag: 'label', $create(`label.config-${va.type}`, [
className: `config-${va.type}`, $create('span', va.label),
appendChild: [ ...children,
$element({tag: 'span', appendChild: va.label}), ]));
...appendChild,
],
}));
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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