editor import: warn about invalid -moz-document functions

* fixes #118
* alleviates #116 by switching showRegExpTester() from innerHTML to DOM
This commit is contained in:
tophf 2017-07-19 15:09:29 +03:00
parent e48e1ab874
commit b50c19a802
2 changed files with 82 additions and 29 deletions

View File

@ -1557,7 +1557,7 @@ function fromMozillaFormat() {
const parser = new parserlib.css.Parser(); const parser = new parserlib.css.Parser();
const lines = mozStyle.split('\n'); const lines = mozStyle.split('\n');
const sectionStack = [{code: '', start: {line: 1, col: 1}}]; const sectionStack = [{code: '', start: {line: 1, col: 1}}];
let errors = ''; const errors = [];
// let oldSectionCount = editors.length; // let oldSectionCount = editors.length;
let firstAddedCM; let firstAddedCM;
@ -1575,12 +1575,16 @@ function fromMozillaFormat() {
doAddSection(sectionStack.last); doAddSection(sectionStack.last);
sectionStack.last.code = ''; sectionStack.last.code = '';
} }
e.functions.forEach(f => { for (const f of e.functions) {
const m = f.match(/^(url|url-prefix|domain|regexp)\((['"]?)(.+?)\2?\)$/); const m = f && f.match(/^([\w-]*)\((['"]?)(.+?)\2?\)$/);
if (!m || !/^(url|url-prefix|domain|regexp)$/.test(m[1])) {
errors.push(`${e.line}:${e.col + 1} invalid function "${m ? m[1] : f || ''}"`);
continue;
}
const aType = CssToProperty[m[1]]; const aType = CssToProperty[m[1]];
const aValue = aType !== 'regexps' ? m[3] : m[3].replace(/\\\\/g, '\\'); const aValue = aType !== 'regexps' ? m[3] : m[3].replace(/\\\\/g, '\\');
(section[aType] = section[aType] || []).push(aValue); (section[aType] = section[aType] || []).push(aValue);
}); }
sectionStack.push(section); sectionStack.push(section);
}); });
@ -1607,12 +1611,16 @@ function fromMozillaFormat() {
firstAddedCM.focus(); firstAddedCM.focus();
if (errors) { if (errors) {
showHelp(t('issues'), errors); showHelp(t('issues'), $element({
tag: 'pre',
textContent: errors.join('\n'),
}));
} }
}); });
parser.addListener('error', e => { parser.addListener('error', e => {
errors += e.line + ':' + e.col + ' ' + e.message.replace(/ at line \d.+$/, '') + '<br>'; errors.push(e.line + ':' + e.col + ' ' +
e.message.replace(/ at line \d.+$/, ''));
}); });
parser.parse(mozStyle); parser.parse(mozStyle);
@ -1834,13 +1842,16 @@ function showRegExpTester(event, section = getSectionForChild(this)) {
rxData.urls = urlsNow; rxData.urls = urlsNow;
} }
} }
const moreInfoLink = template.regexpTestPartial.outerHTML;
const stats = { const stats = {
full: {data: [], label: t('styleRegexpTestFull')}, full: {data: [], label: t('styleRegexpTestFull')},
partial: {data: [], label: t('styleRegexpTestPartial') + moreInfoLink}, partial: {data: [], label: [
t('styleRegexpTestPartial'),
template.regexpTestPartial.cloneNode(true),
]},
none: {data: [], label: t('styleRegexpTestNone')}, none: {data: [], label: t('styleRegexpTestNone')},
invalid: {data: [], label: t('styleRegexpTestInvalid')}, invalid: {data: [], label: t('styleRegexpTestInvalid')},
}; };
// collect stats
for (const {text, rx, urls} of regexps) { for (const {text, rx, urls} of regexps) {
if (!rx) { if (!rx) {
stats.invalid.data.push({text}); stats.invalid.data.push({text});
@ -1856,12 +1867,18 @@ function showRegExpTester(event, section = getSectionForChild(this)) {
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 = `<img src="${faviconUrl}">`; const icon = $element({tag: 'img', src: faviconUrl});
if (match.length === url.length) { if (match.length === url.length) {
full.push(`<div>${icon + url}</div>`); full.push($element({appendChild: [
icon,
url,
]}));
} else { } else {
partial.push(`<div>${icon}<mark>${match}</mark>` + partial.push($element({appendChild: [
url.substr(match.length) + '</div>'); icon,
$element({tag: 'mark', textContent: match}),
url.substr(match.length),
]}));
} }
} }
if (full.length) { if (full.length) {
@ -1871,17 +1888,42 @@ function showRegExpTester(event, section = getSectionForChild(this)) {
stats.partial.data.push({text, urls: partial}); stats.partial.data.push({text, urls: partial});
} }
} }
showHelp(t('styleRegexpTestTitle'), // render stats
'<div class="regexp-report">' + const report = $element({className: 'regexp-report'});
Object.keys(stats).map(type => (!stats[type].data.length ? '' : const br = $element({tag: 'br'});
`<details open data-type="${type}"> for (const type in stats) {
<summary>${stats[type].label}</summary>` + // top level groups: full, partial, none, invalid
stats[type].data.map(({text, urls}) => (!urls ? text : const {label, data} = stats[type];
`<details open><summary>${text}</summary>${urls.join('')}</details>` if (!data.length) {
)).join('<br>') + continue;
'</details>' }
)).join('') + // 2nd level: regexp text
'</div>'); const summary = $element({tag: 'summary', appendChild: label});
const block = [summary];
for (const {text, urls} of data) {
if (!urls) {
block.push(text, br.cloneNode());
continue;
}
block.push($element({
tag: 'details',
open: true,
appendChild: [
$element({tag: 'summary', textContent: text}),
// 3rd level: tab urls
...urls,
],
}));
}
report.appendChild($element({
tag: 'details',
open: true,
dataset: {type},
appendChild: block,
}));
}
showHelp(t('styleRegexpTestTitle'), report);
document.querySelector('.regexp-report').onclick = event => { document.querySelector('.regexp-report').onclick = event => {
const target = event.target.closest('a, .regexp-report div'); const target = event.target.closest('a, .regexp-report div');
if (target) { if (target) {
@ -1893,10 +1935,17 @@ function showRegExpTester(event, section = getSectionForChild(this)) {
} }
function showHelp(title, text) { function showHelp(title, text) {
const div = document.getElementById('help-popup'); const div = $('#help-popup');
div.classList.remove('big'); div.classList.remove('big');
div.querySelector('.contents').innerHTML = text;
div.querySelector('.title').innerHTML = title; const contents = $('.contents', div);
if (text instanceof HTMLElement) {
contents.textContent = '';
contents.appendChild(text);
} else {
contents.innerHTML = text;
}
$('.title', div).textContent = title;
if (getComputedStyle(div).display === 'none') { if (getComputedStyle(div).display === 'none') {
document.addEventListener('keydown', closeHelp); document.addEventListener('keydown', closeHelp);

View File

@ -106,7 +106,7 @@ function $$(selector, base = document) {
function $element(opt) { function $element(opt) {
// tag: string, default 'div', may include namespace like 'ns#tag' // tag: string, default 'div', may include namespace like 'ns#tag'
// appendChild: element or an array of elements // appendChild: element/string or an array of elements/strings
// dataset: object // dataset: object
// any DOM property: assigned as is // any DOM property: assigned as is
const [ns, tag] = opt.tag && opt.tag.includes('#') const [ns, tag] = opt.tag && opt.tag.includes('#')
@ -115,8 +115,12 @@ function $element(opt) {
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');
(opt.appendChild instanceof Array ? opt.appendChild : [opt.appendChild]) const children = opt.appendChild instanceof Array ? opt.appendChild : [opt.appendChild];
.forEach(child => child && element.appendChild(child)); for (const child of children) {
if (child) {
element.appendChild(child instanceof Node ? child : document.createTextNode(child));
}
}
delete opt.appendChild; delete opt.appendChild;
delete opt.tag; delete opt.tag;
if (opt.dataset) { if (opt.dataset) {