Add regexp tester to the editor
This commit is contained in:
parent
486d4258d3
commit
c356fb9be5
|
@ -362,6 +362,30 @@
|
||||||
"message": "Regexp is invalid.",
|
"message": "Regexp is invalid.",
|
||||||
"description": "Validation message for a bad regexp in a style"
|
"description": "Validation message for a bad regexp in a style"
|
||||||
},
|
},
|
||||||
|
"styleRegexpTestButton": {
|
||||||
|
"message": "RegExp test",
|
||||||
|
"description": "RegExp test button label in the editor shown when applies-to list has a regexp value"
|
||||||
|
},
|
||||||
|
"styleRegexpTestTitle": {
|
||||||
|
"message": "List of matching opened tabs (click on URL to focus its tab)",
|
||||||
|
"description": "RegExp test report: title of the report"
|
||||||
|
},
|
||||||
|
"styleRegexpTestFull": {
|
||||||
|
"message": "Matching tabs",
|
||||||
|
"description": "RegExp test report: label for the fully matching expressions"
|
||||||
|
},
|
||||||
|
"styleRegexpTestPartial": {
|
||||||
|
"message": "Not matching fully, hence skipped",
|
||||||
|
"description": "RegExp test report: label for the partially matching expressions"
|
||||||
|
},
|
||||||
|
"styleRegexpTestNone": {
|
||||||
|
"message": "No matching tabs",
|
||||||
|
"description": "RegExp test report: label for expressions that didn't match any tabs"
|
||||||
|
},
|
||||||
|
"styleRegexpTestInvalid": {
|
||||||
|
"message": "Invalid regexps skipped",
|
||||||
|
"description": "RegExp test report: label for the invalid expressions"
|
||||||
|
},
|
||||||
"styleBeautify": {
|
"styleBeautify": {
|
||||||
"message": "Beautify",
|
"message": "Beautify",
|
||||||
"description": "Label for the CSS-beautifier button on the edit style page"
|
"description": "Label for the CSS-beautifier button on the edit style page"
|
||||||
|
|
66
edit.html
66
edit.html
|
@ -207,6 +207,14 @@
|
||||||
background-color: none;
|
background-color: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@keyframes fadein {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
.resize-grip {
|
.resize-grip {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -261,6 +269,61 @@
|
||||||
.applies-to img {
|
.applies-to img {
|
||||||
vertical-align: bottom;
|
vertical-align: bottom;
|
||||||
}
|
}
|
||||||
|
.test-regexp {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.has-regexp .test-regexp {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.regexp-report summary, .regexp-report div {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.regexp-report mark {
|
||||||
|
background-color: rgba(255, 255, 0, .5);
|
||||||
|
}
|
||||||
|
.regexp-report details {
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
.regexp-report details:not(:last-child) {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
.regexp-report summary {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-left: -1rem;
|
||||||
|
margin-bottom: .5rem;
|
||||||
|
outline: none;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
.regexp-report details[data-type="full"] {
|
||||||
|
color: darkgreen;
|
||||||
|
}
|
||||||
|
.regexp-report details[data-type="partial"] {
|
||||||
|
color: darkgray;
|
||||||
|
}
|
||||||
|
.regexp-report details[data-type="invalid"] {
|
||||||
|
color: maroon;
|
||||||
|
}
|
||||||
|
.regexp-report details details {
|
||||||
|
margin-left: 2rem;
|
||||||
|
margin-top: .5rem;
|
||||||
|
}
|
||||||
|
.regexp-report .svg-icon {
|
||||||
|
position: absolute;
|
||||||
|
margin-top: -1px;
|
||||||
|
}
|
||||||
|
.regexp-report details div:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
text-decoration-skip: ink;
|
||||||
|
}
|
||||||
|
.regexp-report details div img {
|
||||||
|
width: 16px;
|
||||||
|
max-height: 16px;
|
||||||
|
position: absolute;
|
||||||
|
margin-left: -20px;
|
||||||
|
margin-top: -1px;
|
||||||
|
animation: fadein 1s cubic-bezier(.03, .67, .08, .94);
|
||||||
|
animation-fill-mode: both;
|
||||||
|
}
|
||||||
/************ help popup ************/
|
/************ help popup ************/
|
||||||
#help-popup {
|
#help-popup {
|
||||||
top: 3rem;
|
top: 3rem;
|
||||||
|
@ -285,7 +348,7 @@
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
background-color: rgba(0,0,0,0.05);
|
background-color: rgba(0,0,0,0.05);
|
||||||
margin: -0.5rem -0.5rem 0.5rem;
|
margin: -0.5rem -0.5rem 0.5rem;
|
||||||
padding: 0.5rem;
|
padding: .5rem 32px .5rem .5rem;
|
||||||
}
|
}
|
||||||
#help-popup .contents {
|
#help-popup .contents {
|
||||||
max-height: calc(100vh - 8rem);
|
max-height: calc(100vh - 8rem);
|
||||||
|
@ -537,6 +600,7 @@
|
||||||
<button class="remove-section" i18n-text="sectionRemove"></button>
|
<button class="remove-section" i18n-text="sectionRemove"></button>
|
||||||
<button class="add-section" i18n-text="sectionAdd"></button>
|
<button class="add-section" i18n-text="sectionAdd"></button>
|
||||||
<button class="beautify-section" i18n-text="styleBeautify"></button>
|
<button class="beautify-section" i18n-text="styleBeautify"></button>
|
||||||
|
<button class="test-regexp" i18n-text="styleRegexpTestButton"></button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template data-id="find">
|
<template data-id="find">
|
||||||
|
|
118
edit.js
118
edit.js
|
@ -500,6 +500,23 @@ function addSection(event, section) {
|
||||||
appliesTo.addEventListener("change", onChange);
|
appliesTo.addEventListener("change", onChange);
|
||||||
appliesTo.addEventListener("input", onChange);
|
appliesTo.addEventListener("input", onChange);
|
||||||
|
|
||||||
|
toggleTestRegExpVisibility();
|
||||||
|
appliesTo.addEventListener('change', toggleTestRegExpVisibility);
|
||||||
|
div.querySelector('.test-regexp').onclick = showRegExpTester;
|
||||||
|
function toggleTestRegExpVisibility() {
|
||||||
|
const show = [...appliesTo.children].some(item =>
|
||||||
|
!item.matches('.applies-to-everything') &&
|
||||||
|
item.querySelector('.applies-type').value == 'regexp' &&
|
||||||
|
item.querySelector('.applies-value').value.trim());
|
||||||
|
div.classList.toggle('has-regexp', show);
|
||||||
|
appliesTo.oninput = appliesTo.oninput || show && (event => {
|
||||||
|
if (event.target.matches('.applies-value')
|
||||||
|
&& event.target.parentElement.querySelector('.applies-type').value == 'regexp') {
|
||||||
|
showRegExpTester(null, div);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
var sections = document.getElementById("sections");
|
var sections = document.getElementById("sections");
|
||||||
if (event) {
|
if (event) {
|
||||||
var clickedSection = getSectionForChild(event.target);
|
var clickedSection = getSectionForChild(event.target);
|
||||||
|
@ -1605,6 +1622,107 @@ function showLintHelp() {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showRegExpTester(event, section = getSectionForChild(this)) {
|
||||||
|
const GET_FAVICON_URL = 'https://www.google.com/s2/favicons?domain=';
|
||||||
|
const OWN_ICON = chrome.app.getDetails().icons['16'];
|
||||||
|
const RX_SUPPORTED_URLS = new RegExp(`^(file|https?|ftps?):|^${OWN_ORIGIN}`);
|
||||||
|
const cachedRegexps = showRegExpTester.cachedRegexps =
|
||||||
|
showRegExpTester.cachedRegexps || new Map();
|
||||||
|
const regexps = [...section.querySelector('.applies-to-list').children]
|
||||||
|
.map(item =>
|
||||||
|
!item.matches('.applies-to-everything') &&
|
||||||
|
item.querySelector('.applies-type').value == 'regexp' &&
|
||||||
|
item.querySelector('.applies-value').value.trim())
|
||||||
|
.filter(item => item)
|
||||||
|
.map(text => {
|
||||||
|
const rxData = Object.assign({text}, cachedRegexps.get(text));
|
||||||
|
if (!rxData.urls) {
|
||||||
|
cachedRegexps.set(text, Object.assign(rxData, {
|
||||||
|
rx: tryRegExp(text),
|
||||||
|
urls: new Map(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return rxData;
|
||||||
|
});
|
||||||
|
chrome.tabs.query({}, tabs => {
|
||||||
|
const supported = tabs.map(tab => tab.url)
|
||||||
|
.filter(url => RX_SUPPORTED_URLS.test(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) || (url.match(rx) || [])[0];
|
||||||
|
if (match) {
|
||||||
|
urlsNow.set(url, match);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rxData.urls = urlsNow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const moreInfoLink = '<a target="_blank" ' +
|
||||||
|
'href="https://github.com/stylish-userstyles/' +
|
||||||
|
'stylish/wiki/Applying-styles-to-specific-sites' +
|
||||||
|
'#advanced-matching-with-regular-expressions">' +
|
||||||
|
'<img src="/images/help.png"></a>';
|
||||||
|
const stats = {
|
||||||
|
full: {data: [], label: t('styleRegexpTestFull')},
|
||||||
|
partial: {data: [], label: t('styleRegexpTestPartial') + moreInfoLink},
|
||||||
|
none: {data: [], label: t('styleRegexpTestNone')},
|
||||||
|
invalid: {data: [], label: t('styleRegexpTestInvalid')},
|
||||||
|
};
|
||||||
|
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(OWN_ORIGIN)
|
||||||
|
? OWN_ICON
|
||||||
|
: GET_FAVICON_URL + new URL(url).hostname;
|
||||||
|
const icon = `<img src="${faviconUrl}">`;
|
||||||
|
if (match.length == url.length) {
|
||||||
|
full.push(`<div>${icon + url}</div>`);
|
||||||
|
} else {
|
||||||
|
partial.push(`<div>${icon}<mark>${match}</mark>` +
|
||||||
|
url.substr(match.length) + '</div>');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (full.length) {
|
||||||
|
stats.full.data.push({text, urls: full});
|
||||||
|
}
|
||||||
|
if (partial.length) {
|
||||||
|
stats.partial.data.push({text, urls: partial});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
showHelp(t('styleRegexpTestTitle'),
|
||||||
|
'<div class="regexp-report">' +
|
||||||
|
Object.keys(stats).map(type => (!stats[type].data.length ? '' :
|
||||||
|
`<details open data-type="${type}">
|
||||||
|
<summary>${stats[type].label}</summary>` +
|
||||||
|
stats[type].data.map(({text, urls}) => (!urls ? text :
|
||||||
|
`<details open><summary>${text}</summary>${urls.join('')}</details>`
|
||||||
|
)).join('<br>') +
|
||||||
|
'</details>'
|
||||||
|
)).join('') +
|
||||||
|
'</div>');
|
||||||
|
document.querySelector('.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, text) {
|
function showHelp(title, text) {
|
||||||
var div = document.getElementById("help-popup");
|
var div = document.getElementById("help-popup");
|
||||||
div.classList.remove("big");
|
div.classList.remove("big");
|
||||||
|
|
Loading…
Reference in New Issue
Block a user