diff --git a/edit.html b/edit.html
index 09c3a602..88c3ded6 100644
--- a/edit.html
+++ b/edit.html
@@ -23,6 +23,7 @@
+
diff --git a/edit/edit.js b/edit/edit.js
index 130f7dd0..a6417575 100644
--- a/edit/edit.js
+++ b/edit/edit.js
@@ -3,7 +3,7 @@
/* global css_beautify */
/* global CSSLint initLint linterConfig updateLintReport renderLintReport updateLinter */
/* global mozParser createSourceEditor */
-/* global loadScript closeCurrentTab */
+/* global loadScript closeCurrentTab regExpTester */
'use strict';
@@ -568,20 +568,30 @@ function addSection(event, section) {
toggleTestRegExpVisibility();
appliesTo.addEventListener('change', toggleTestRegExpVisibility);
- $('.test-regexp', div).onclick = showRegExpTester;
+ $('.test-regexp', div).onclick = () => {
+ regExpTester.toggle();
+ regExpTester.update(getRegExps());
+ };
+
+ function getRegExps() {
+ return [...appliesTo.children]
+ .map(item =>
+ !item.matches('.applies-to-everything') &&
+ $('.applies-type', item).value === 'regexp' &&
+ $('.applies-value', item).value.trim()
+ )
+ .filter(item => item);
+ }
+
function toggleTestRegExpVisibility() {
- const show = [...appliesTo.children].some(item =>
- !item.matches('.applies-to-everything') &&
- $('.applies-type', item).value === 'regexp' &&
- $('.applies-value', item).value.trim()
- );
+ const show = getRegExps().length > 0;
div.classList.toggle('has-regexp', show);
appliesTo.oninput = appliesTo.oninput || show && (event => {
if (
event.target.matches('.applies-value') &&
$('.applies-type', event.target.parentElement).value === 'regexp'
) {
- showRegExpTester(null, div);
+ regExpTester.update(getRegExps());
}
});
}
@@ -1771,151 +1781,6 @@ function showKeyMapHelp() {
}
}
-function showRegExpTester(event, section = getSectionForChild(this)) {
- const GET_FAVICON_URL = 'https://www.google.com/s2/favicons?domain=';
- const OWN_ICON = chrome.runtime.getManifest().icons['16'];
- const cachedRegexps = showRegExpTester.cachedRegexps =
- showRegExpTester.cachedRegexps || new Map();
- const regexps = [...$('.applies-to-list', section).children]
- .map(item =>
- !item.matches('.applies-to-everything') &&
- $('.applies-type', item).value === 'regexp' &&
- $('.applies-value', item).value.trim()
- )
- .filter(item => item)
- .map(text => {
- const rxData = Object.assign({text}, cachedRegexps.get(text));
- if (!rxData.urls) {
- cachedRegexps.set(text, Object.assign(rxData, {
- // imitate buggy Stylish-for-chrome, see detectSloppyRegexps()
- rx: tryRegExp('^' + text + '$'),
- urls: new Map(),
- }));
- }
- return rxData;
- });
- chrome.tabs.onUpdated.addListener(function _(tabId, info) {
- if ($('.regexp-report')) {
- if (info.url) {
- showRegExpTester(event, section);
- }
- } else {
- chrome.tabs.onUpdated.removeListener(_);
- }
- });
- const getMatchInfo = m => m && {text: m[0], pos: m.index};
-
- queryTabs().then(tabs => {
- const supported = tabs.map(tab => tab.url)
- .filter(url => URLS.supported(url));
- const unique = [...new Set(supported).values()];
- for (const rxData of regexps) {
- const {rx, urls} = rxData;
- if (rx) {
- const urlsNow = new Map();
- for (const url of unique) {
- const match = urls.get(url) || getMatchInfo(url.match(rx));
- if (match) {
- urlsNow.set(url, match);
- }
- }
- rxData.urls = urlsNow;
- }
- }
- const stats = {
- full: {data: [], label: t('styleRegexpTestFull')},
- partial: {data: [], label: [
- t('styleRegexpTestPartial'),
- template.regexpTestPartial.cloneNode(true),
- ]},
- none: {data: [], label: t('styleRegexpTestNone')},
- invalid: {data: [], label: t('styleRegexpTestInvalid')},
- };
- // collect stats
- for (const {text, rx, urls} of regexps) {
- if (!rx) {
- stats.invalid.data.push({text});
- continue;
- }
- if (!urls.size) {
- stats.none.data.push({text});
- continue;
- }
- const full = [];
- const partial = [];
- for (const [url, match] of urls.entries()) {
- const faviconUrl = url.startsWith(URLS.ownOrigin)
- ? OWN_ICON
- : GET_FAVICON_URL + new URL(url).hostname;
- const icon = $element({tag: 'img', src: faviconUrl});
- if (match.text.length === url.length) {
- full.push($element({appendChild: [
- icon,
- url,
- ]}));
- } else {
- partial.push($element({appendChild: [
- icon,
- url.substr(0, match.pos),
- $element({tag: 'mark', textContent: match.text}),
- url.substr(match.pos + match.text.length),
- ]}));
- }
- }
- if (full.length) {
- stats.full.data.push({text, urls: full});
- }
- if (partial.length) {
- stats.partial.data.push({text, urls: partial});
- }
- }
- // render stats
- const report = $element({className: 'regexp-report'});
- const br = $element({tag: '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}),
- }));
- // 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}),
- // 3rd level: tab urls
- ...urls,
- ],
- }));
- } else {
- // type is none or invalid
- block.appendChild(document.createTextNode(text));
- block.appendChild(br.cloneNode());
- }
- }
- }
- showHelp(t('styleRegexpTestTitle'), report);
-
- $('.regexp-report').onclick = event => {
- const target = event.target.closest('a, .regexp-report div');
- if (target) {
- openURL({url: target.href || target.textContent});
- event.preventDefault();
- }
- };
- });
-}
-
function showHelp(title, body) {
const div = $('#help-popup');
div.classList.remove('big');
diff --git a/edit/regexp-tester.js b/edit/regexp-tester.js
new file mode 100644
index 00000000..ee89d3a3
--- /dev/null
+++ b/edit/regexp-tester.js
@@ -0,0 +1,162 @@
+/* global showHelp */
+
+'use strict';
+
+// eslint-disable-next-line no-var
+var regExpTester = (function () {
+ const GET_FAVICON_URL = 'https://www.google.com/s2/favicons?domain=';
+ const OWN_ICON = chrome.runtime.getManifest().icons['16'];
+ const cachedRegexps = new Map();
+ let currentRegexps = [];
+
+ chrome.tabs.onUpdated.addListener(function _(tabId, info) {
+ if (info.url) {
+ update();
+ }
+ });
+
+ function isShowed() {
+ return $('.regexp-report');
+ }
+
+ function toggle(state = !isShowed()) {
+ if (state && !isShowed()) {
+ showHelp('', $element({className: 'regexp-report'}));
+ } else if (!state && isShowed()) {
+ // TODO: need a closeHelp function
+ $('#help-popup .dismiss').onclick();
+ }
+ }
+
+ function update(newRegexps) {
+ if (!isShowed()) {
+ return;
+ }
+ if (newRegexps) {
+ currentRegexps = newRegexps;
+ }
+ const regexps = currentRegexps.map(text => {
+ const rxData = Object.assign({text}, cachedRegexps.get(text));
+ if (!rxData.urls) {
+ cachedRegexps.set(text, Object.assign(rxData, {
+ // imitate buggy Stylish-for-chrome, see detectSloppyRegexps()
+ rx: tryRegExp('^' + text + '$'),
+ urls: new Map(),
+ }));
+ }
+ return rxData;
+ });
+ const getMatchInfo = m => m && {text: m[0], pos: m.index};
+ queryTabs().then(tabs => {
+ const supported = tabs.map(tab => tab.url)
+ .filter(url => URLS.supported(url));
+ const unique = [...new Set(supported).values()];
+ for (const rxData of regexps) {
+ const {rx, urls} = rxData;
+ if (rx) {
+ const urlsNow = new Map();
+ for (const url of unique) {
+ const match = urls.get(url) || getMatchInfo(url.match(rx));
+ if (match) {
+ urlsNow.set(url, match);
+ }
+ }
+ rxData.urls = urlsNow;
+ }
+ }
+ const stats = {
+ full: {data: [], label: t('styleRegexpTestFull')},
+ partial: {data: [], label: [
+ t('styleRegexpTestPartial'),
+ template.regexpTestPartial.cloneNode(true),
+ ]},
+ none: {data: [], label: t('styleRegexpTestNone')},
+ invalid: {data: [], label: t('styleRegexpTestInvalid')},
+ };
+ // collect stats
+ for (const {text, rx, urls} of regexps) {
+ if (!rx) {
+ stats.invalid.data.push({text});
+ continue;
+ }
+ if (!urls.size) {
+ stats.none.data.push({text});
+ continue;
+ }
+ const full = [];
+ const partial = [];
+ for (const [url, match] of urls.entries()) {
+ const faviconUrl = url.startsWith(URLS.ownOrigin)
+ ? OWN_ICON
+ : GET_FAVICON_URL + new URL(url).hostname;
+ const icon = $element({tag: 'img', src: faviconUrl});
+ if (match.text.length === url.length) {
+ full.push($element({appendChild: [
+ icon,
+ url,
+ ]}));
+ } else {
+ partial.push($element({appendChild: [
+ icon,
+ url.substr(0, match.pos),
+ $element({tag: 'mark', textContent: match.text}),
+ url.substr(match.pos + match.text.length),
+ ]}));
+ }
+ }
+ if (full.length) {
+ stats.full.data.push({text, urls: full});
+ }
+ if (partial.length) {
+ stats.partial.data.push({text, urls: partial});
+ }
+ }
+ // render stats
+ const report = $element({className: 'regexp-report'});
+ const br = $element({tag: '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}),
+ }));
+ // 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}),
+ // 3rd level: tab urls
+ ...urls,
+ ],
+ }));
+ } else {
+ // type is none or invalid
+ block.appendChild(document.createTextNode(text));
+ block.appendChild(br.cloneNode());
+ }
+ }
+ }
+ showHelp(t('styleRegexpTestTitle'), report);
+
+ $('.regexp-report').onclick = event => {
+ const target = event.target.closest('a, .regexp-report div');
+ if (target) {
+ openURL({url: target.href || target.textContent});
+ event.preventDefault();
+ }
+ };
+ });
+ }
+
+ return {toggle, update};
+})();