This commit is contained in:
derv82 2017-12-06 01:29:49 -08:00
commit 0366c8508f
27 changed files with 822 additions and 390 deletions

View File

@ -739,9 +739,9 @@
"message": "Search contents", "message": "Search contents",
"description": "Label for the search filter textbox on the Manage styles page" "description": "Label for the search filter textbox on the Manage styles page"
}, },
"searchStylesTooltip": { "searchStylesHelp": {
"message": "To show styles for a URL, prefix it with 'url:'\nFor example, url:https://github.com/openstyles/stylus", "message": "</> key focuses the search field.\nPlain text: search within the name, code, homepage URL and sites it is applied to. Words with less than 3 letters are ignored.\nMatching URL: prefix the search with <url:>, e.g. <url:https://github.com/openstyles/stylus>\nRegular expressions: include slashes and flags, e.g. </body.*?\\ba\\b/simguy>\nExact words: wrap the query in double quotes, e.g. <\".header ~ div\">",
"description": "Label for the search filter textbox on the Manage styles page" "description": "Text in the minihelp displayed when clicking (i) icon to the right of the search input field on the Manage styles page"
}, },
"sectionAdd": { "sectionAdd": {
"message": "Add another section", "message": "Add another section",

View File

@ -74,7 +74,7 @@
<link rel="stylesheet" href="/edit/codemirror-default.css"> <link rel="stylesheet" href="/edit/codemirror-default.css">
<template data-id="appliesTo"> <template data-id="appliesTo">
<li> <li class="applies-to-item">
<div class="select-resizer"> <div class="select-resizer">
<select name="applies-type" class="applies-type style-contributor"> <select name="applies-type" class="applies-type style-contributor">
<option value="url" i18n-text="appliesUrlOption"></option> <option value="url" i18n-text="appliesUrlOption"></option>
@ -322,7 +322,7 @@
<h2><span id="sections-heading" i18n-text="styleSectionsTitle"></span><svg id="sections-help" class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg></h2> <h2><span id="sections-heading" i18n-text="styleSectionsTitle"></span><svg id="sections-help" class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg></h2>
</section> </section>
<div id="help-popup"> <div id="help-popup">
<div class="title"></div><svg id="sections-help" class="svg-icon dismiss"><use xlink:href="#svg-icon-close"/></svg></svg> <div class="title"></div><svg id="sections-help" class="svg-icon dismiss"><use xlink:href="#svg-icon-close"/></svg>
<div class="contents"></div> <div class="contents"></div>
</div> </div>

View File

@ -5,7 +5,7 @@ function createAppliesToLineWidget(cm) {
const THROTTLE_DELAY = 400; const THROTTLE_DELAY = 400;
let TPL, EVENTS, CLICK_ROUTE; let TPL, EVENTS, CLICK_ROUTE;
let widgets = []; let widgets = [];
let fromLine, toLine, styleVariables; let fromLine, toLine, actualStyle;
let initialized = false; let initialized = false;
return {toggle}; return {toggle};
@ -29,28 +29,16 @@ function createAppliesToLineWidget(cm) {
$create('label', t('appliesLabel')), $create('label', t('appliesLabel')),
$create('ul.applies-to-list'), $create('ul.applies-to-list'),
]), ]),
listItem: listItem: template.appliesTo,
$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', {spellcheck: false}),
$create('button.test-regexp', t('styleRegexpTestButton')),
$create('button.remove-applies-to', t('appliesRemove')),
$create('button.add-applies-to', t('appliesAdd')),
]),
appliesToEverything: appliesToEverything:
$create('li.applies-to-everything', t('appliesToEverything')), $create('li.applies-to-everything', t('appliesToEverything')),
}; };
$('button', TPL.listItem).insertAdjacentElement('beforebegin',
$create('button.test-regexp', t('styleRegexpTestButton')));
CLICK_ROUTE = { CLICK_ROUTE = {
'.test-regexp': (item, apply) => { '.test-regexp': showRegExpTester,
regExpTester.toggle();
regExpTester.update([apply.value.text]);
},
'.remove-applies-to': (item, apply) => { '.remove-applies-to': (item, apply) => {
const applies = item.closest('.applies-to').__applies; const applies = item.closest('.applies-to').__applies;
@ -108,20 +96,17 @@ function createAppliesToLineWidget(cm) {
if (typeElement) { if (typeElement) {
const item = target.closest('.applies-to-item'); const item = target.closest('.applies-to-item');
const apply = item.__apply; const apply = item.__apply;
changeItem(apply, 'type', typeElement.value); changeItem(item, apply, 'type', typeElement.value);
item.dataset.type = apply.type.text; item.dataset.type = apply.type.text;
} else {
return EVENTS.oninput.apply(this, arguments);
} }
}, },
oninput({target}) { oninput({target}) {
if (target.matches('.applies-value')) { if (target.matches('.applies-value')) {
const apply = target.closest('.applies-to-item').__apply; const item = target.closest('.applies-to-item');
debounce(changeItem, THROTTLE_DELAY, apply, 'value', target.value); const apply = item.__apply;
} changeItem(item, apply, 'value', target.value);
},
onfocus({target}) {
if (target.matches('.test-regexp')) {
const apply = target.closest('.applies-to-item').__apply;
updateRegexpTest(apply);
} }
}, },
onclick({target}) { onclick({target}) {
@ -136,15 +121,13 @@ function createAppliesToLineWidget(cm) {
} }
}; };
styleVariables = $create('style'); actualStyle = $create('style');
fromLine = 0; fromLine = 0;
toLine = cm.doc.size; toLine = cm.doc.size;
cm.on('change', onChange); cm.on('change', onChange);
cm.on('optionChange', onOptionChange); cm.on('optionChange', onOptionChange);
// is it possible to avoid flickering?
window.addEventListener('load', updateWidgetStyle);
chrome.runtime.onMessage.addListener(onRuntimeMessage); chrome.runtime.onMessage.addListener(onRuntimeMessage);
updateWidgetStyle(); updateWidgetStyle();
@ -158,9 +141,8 @@ function createAppliesToLineWidget(cm) {
widgets.length = 0; widgets.length = 0;
cm.off('change', onChange); cm.off('change', onChange);
cm.off('optionChange', onOptionChange); cm.off('optionChange', onOptionChange);
window.removeEventListener('load', updateWidgetStyle);
chrome.runtime.onMessage.removeListener(onRuntimeMessage); chrome.runtime.onMessage.removeListener(onRuntimeMessage);
styleVariables.remove(); actualStyle.remove();
} }
function onChange(cm, event) { function onChange(cm, event) {
@ -229,24 +211,112 @@ function createAppliesToLineWidget(cm) {
} }
function updateWidgetStyle() { function updateWidgetStyle() {
const gutterStyle = getComputedStyle(cm.getGutterElement()); const MIN_LUMA = .05;
const borderStyle = gutterStyle.borderRightWidth !== '0px' ? const MIN_LUMA_DIFF = .4;
`${gutterStyle.borderRightWidth} ${gutterStyle.borderRightStyle} ${gutterStyle.borderRightColor}` : const color = {
`1px solid ${gutterStyle.color}`; wrapper: getRealColors(cm.display.wrapper),
const id = Date.now(); gutter: getRealColors(cm.display.gutters, {
styleVariables.textContent = ` bg: 'backgroundColor',
.single-editor { border: 'borderRightColor',
--at-background-color-${id}: ${gutterStyle.backgroundColor}; }),
--at-border-top-${id}: ${borderStyle}; line: getRealColors('.CodeMirror-linenumber'),
--at-border-bottom-${id}: ${borderStyle}; comment: getRealColors('span.cm-comment'),
} };
const hasBorder =
color.gutter.style.borderRightWidth !== '0px' &&
!/transparent|\b0\)/g.test(color.gutter.style.borderRightColor);
const diff = {
wrapper: Math.abs(color.gutter.bgLuma - color.wrapper.foreLuma),
border: hasBorder ? Math.abs(color.gutter.bgLuma - color.gutter.borderLuma) : 0,
line: Math.abs(color.gutter.bgLuma - color.line.foreLuma),
};
const preferLine = diff.line > diff.wrapper || diff.line > MIN_LUMA_DIFF;
const fore = preferLine ? color.line.fore : color.wrapper.fore;
const border = fore.replace(/[\d.]+(?=\))/, MIN_LUMA_DIFF / 2);
const borderStyleForced = `1px ${hasBorder ? color.gutter.style.borderRightStyle : 'solid'} ${border}`;
actualStyle.textContent = `
.applies-to { .applies-to {
background-color: var(--at-background-color-${id}); background-color: ${color.gutter.bg};
border-top: var(--at-border-top-${id}); border-top: ${borderStyleForced};
border-bottom: var(--at-border-bottom-${id}); border-bottom: ${borderStyleForced};
}
.applies-to label {
color: ${fore};
}
.applies-to input,
.applies-to select {
background-color: rgba(255, 255, 255, ${
Math.max(MIN_LUMA, Math.pow(Math.max(0, color.gutter.bgLuma - MIN_LUMA * 2), 2)).toFixed(2)
});
border: ${borderStyleForced};
transition: none;
color: ${fore};
}
.applies-to .svg-icon.select-arrow {
fill: ${fore};
transition: none;
} }
`; `;
document.documentElement.appendChild(styleVariables); document.documentElement.appendChild(actualStyle);
function getRealColors(el, targets = {}) {
targets.fore = 'color';
const colors = {};
const done = {};
let numDone = 0;
let numTotal = 0;
for (const k in targets) {
colors[k] = {r: 255, g: 255, b: 255, a: 1};
numTotal++;
}
const isDummy = typeof el === 'string';
el = isDummy ? cm.display.lineDiv.appendChild($create(el, {style: 'display: none'})) : el;
for (let current = el; current; current = current && current.parentElement) {
const style = getComputedStyle(current);
for (const k in targets) {
if (!done[k]) {
done[k] = blend(colors[k], style[targets[k]]);
numDone += done[k] ? 1 : 0;
if (numDone === numTotal) {
current = null;
break;
}
}
}
colors.style = colors.style || style;
}
if (isDummy) {
el.remove();
}
for (const k in targets) {
const {r, g, b, a} = colors[k];
colors[k] = `rgba(${r}, ${g}, ${b}, ${a})`;
// https://www.w3.org/TR/AERT#color-contrast
colors[k + 'Luma'] = (r * .299 + g * .587 + b * .114) / 256;
}
return colors;
}
function blend(base, color) {
const [r, g, b, a = 255] = (color.match(/\d+/g) || []).map(Number);
if (a === 255) {
base.r = r;
base.g = g;
base.b = b;
base.a = 1;
} else if (a) {
const mixedA = 1 - (1 - a / 255) * (1 - base.a);
const q1 = a / 255 / mixedA;
const q2 = base.a * (1 - mixedA) / mixedA;
base.r = Math.round(r * q1 + base.r * q2);
base.g = Math.round(g * q1 + base.g * q2);
base.b = Math.round(b * q1 + base.b * q2);
base.a = mixedA;
}
return Math.abs(base.a - 1) < 1e-3;
}
} }
function doUpdate() { function doUpdate() {
@ -406,14 +476,14 @@ function createAppliesToLineWidget(cm) {
return el; return el;
} }
function changeItem(apply, part, newText) { function changeItem(itemElement, apply, part, newText) {
if (!apply) { if (!apply) {
return; return;
} }
part = apply[part]; part = apply[part];
const range = part.mark.find(); const range = part.mark.find();
part.mark.clear(); part.mark.clear();
newText = newText.replace(/\\/g, '\\\\'); newText = unescapeDoubleslash(newText).replace(/\\/g, '\\\\');
cm.replaceRange(newText, range.from, range.to, 'appliesTo'); cm.replaceRange(newText, range.from, range.to, 'appliesTo');
part.mark = cm.markText( part.mark = cm.markText(
range.from, range.from,
@ -432,13 +502,8 @@ function createAppliesToLineWidget(cm) {
); );
} }
updateRegexpTest(apply); if (apply.type.text === 'regexp' && apply.value.text.trim()) {
} showRegExpTester(itemElement);
function updateRegexpTest(apply) {
if (apply.type.text === 'regexp') {
const rx = apply.value.text.trim();
regExpTester.update(rx ? [rx] : {});
} }
} }
@ -450,7 +515,6 @@ function createAppliesToLineWidget(cm) {
const valueStart = typeEnd + 1 + Number(isQuoted); const valueStart = typeEnd + 1 + Number(isQuoted);
const valueEnd = valueStart + valueText.length; const valueEnd = valueStart + valueText.length;
const end = valueEnd + Number(isQuoted) + 1; const end = valueEnd + Number(isQuoted) + 1;
const hasSingleEscapes = /([^\\]|^)\\([^\\]|$)/.test(valueText);
return { return {
start, start,
type: { type: {
@ -459,7 +523,7 @@ function createAppliesToLineWidget(cm) {
end: typeEnd, end: typeEnd,
}, },
value: { value: {
text: hasSingleEscapes ? valueText : valueText.replace(/\\\\/g, '\\'), text: unescapeDoubleslash(valueText),
start: valueStart, start: valueStart,
end: valueEnd, end: valueEnd,
}, },
@ -505,4 +569,17 @@ function createAppliesToLineWidget(cm) {
const first = s.charAt(0); const first = s.charAt(0);
return (first === '"' || first === "'") && s.endsWith(first) ? s.slice(1, -1) : s; return (first === '"' || first === "'") && s.endsWith(first) ? s.slice(1, -1) : s;
} }
function unescapeDoubleslash(s) {
const hasSingleEscapes = /([^\\]|^)\\([^\\]|$)/.test(s);
return hasSingleEscapes ? s : s.replace(/\\\\/g, '\\');
}
function showRegExpTester(item) {
regExpTester.toggle(true);
regExpTester.update(
item.closest('.applies-to').__applies
.filter(a => a.type.text === 'regexp')
.map(a => unescapeDoubleslash(a.value.text)));
}
} }

View File

@ -56,7 +56,3 @@
position: absolute; position: absolute;
pointer-events: none; pointer-events: none;
} }
.CodeMirror-activeline .applies-to ul {
z-index: 2;
}

View File

@ -193,7 +193,8 @@ CodeMirror.hint && (() => {
string[pos - 3] === ']' && string[pos - 3] === ']' &&
string[pos - 4] === ']') { string[pos - 4] === ']') {
const vars = typeof editor !== 'undefined' && (editor.getStyle().usercssData || {}).vars; const vars = typeof editor !== 'undefined' && (editor.getStyle().usercssData || {}).vars;
if (vars && Object.hasOwnProperty.call(vars, string.slice(start + 4, pos - 4))) { const name = vars && string.slice(start + 4, pos - 4);
if (vars && Object.hasOwnProperty.call(vars, name.endsWith('-rgb') ? name.slice(0, -4) : name)) {
token[0] = USO_VALID_VAR; token[0] = USO_VALID_VAR;
} else { } else {
token[0] = USO_INVALID_VAR; token[0] = USO_INVALID_VAR;

View File

@ -331,11 +331,11 @@ body[data-match-highlight="selection"] .CodeMirror-selection-highlight-scrollbar
min-height: 1.4rem; min-height: 1.4rem;
margin-left: 0.35rem; margin-left: 0.35rem;
} }
.applies-to li .add-applies-to { html:not(.usercss) .applies-to li .add-applies-to {
visibility: hidden; visibility: hidden;
text-align: left; text-align: left;
} }
.applies-to li:last-child .add-applies-to { html:not(.usercss) .applies-to li:last-child .add-applies-to {
visibility: visible visibility: visible
} }
.applies-to li .add-applies-to:first-child { .applies-to li .add-applies-to:first-child {
@ -404,6 +404,12 @@ body[data-match-highlight="selection"] .CodeMirror-selection-highlight-scrollbar
margin-left: -20px; margin-left: -20px;
margin-top: -1px; margin-top: -1px;
} }
.regexp-report-note {
color: #999;
position: absolute;
margin: 0 0.5rem 0 0;
hyphens: auto;
}
/************ help popup ************/ /************ help popup ************/
#help-popup { #help-popup {
top: 3rem; top: 3rem;
@ -646,8 +652,8 @@ html:not(.usercss) .usercss-only,
margin-top: 0.35rem; margin-top: 0.35rem;
} }
.CodeMirror-linewidget .applies-to li:not([data-type="regexp"]) .applies-to-regexp-test { .CodeMirror-linewidget .applies-to li[data-type="regexp"] .test-regexp {
display: none; display: inline;
} }
.CodeMirror-linewidget li.applies-to-everything { .CodeMirror-linewidget li.applies-to-everything {

View File

@ -160,12 +160,14 @@ var regExpTester = (() => {
} }
} }
} }
report.appendChild( showHelp(t('styleRegexpTestTitle'), report);
$create('p.regexp-report-note',
const note = $create('p.regexp-report-note',
t('styleRegexpTestNote') t('styleRegexpTestNote')
.split(/(\\+)/) .split(/(\\+)/)
.map(s => (s.startsWith('\\') ? $create('code', s) : s)))); .map(s => (s.startsWith('\\') ? $create('code', s) : s)));
showHelp(t('styleRegexpTestTitle'), report); report.appendChild(note);
report.style.paddingBottom = note.offsetHeight + 'px';
report.onclick = event => { report.onclick = event => {
const target = event.target.closest('a, .regexp-report div'); const target = event.target.closest('a, .regexp-report div');

View File

@ -42,6 +42,19 @@ input[type="checkbox"]:not(.slider):checked + .svg-icon.checked {
pointer-events: none; pointer-events: none;
} }
input[type="checkbox"]:not(.slider):disabled {
background-color: transparent;
border-color: hsl(0, 0%, 50%);
}
input[type="checkbox"]:not(.slider):disabled + .svg-icon.checked {
fill: hsl(0, 0%, 50%);
}
input[type="checkbox"]:not(.slider):disabled + .svg-icon.checked + span {
color: hsl(0, 0%, 50%);
}
label { label {
transition: color .1s; transition: color .1s;
} }
@ -50,6 +63,7 @@ select {
-moz-appearance: none; -moz-appearance: none;
-webkit-appearance: none; -webkit-appearance: none;
height: 22px; height: 22px;
color: currentColor;
background-color: transparent; background-color: transparent;
border: 1px solid hsl(0, 0%, 66%); border: 1px solid hsl(0, 0%, 66%);
padding: 0 20px 0 6px; padding: 0 20px 0 6px;
@ -72,7 +86,7 @@ select {
display: inline-flex; display: inline-flex;
height: 14px; height: 14px;
width: 14px; width: 14px;
fill: #000; fill: currentColor;
position: absolute; position: absolute;
top: 4px; top: 4px;
right: 4px; right: 4px;
@ -92,4 +106,12 @@ select {
padding-left: .75ex; padding-left: .75ex;
padding-right: .75ex; padding-right: .75ex;
} }
::-moz-focus-inner {
border: 0;
}
svg {
transform: scale(1); /* de-blur */
}
} }

View File

@ -276,7 +276,7 @@
data.version, data.version,
])) ]))
).then(ok => ok && ).then(ok => ok &&
BG.usercssHelper.save(BG.deepCopy(Object.assign(style, {reason: 'update'}))) BG.usercssHelper.save(BG.deepCopy(Object.assign(style, dup && {reason: 'update'})))
.then(r => install(deepCopy(r))) .then(r => install(deepCopy(r)))
.catch(err => messageBox.alert(t('styleInstallFailed', err))) .catch(err => messageBox.alert(t('styleInstallFailed', err)))
); );

View File

@ -286,9 +286,9 @@ function tryCatch(func, ...args) {
} }
function tryRegExp(regexp) { function tryRegExp(regexp, flags) {
try { try {
return new RegExp(regexp); return new RegExp(regexp, flags);
} catch (e) {} } catch (e) {}
} }

View File

@ -24,6 +24,8 @@ var prefs = new function Prefs() {
'manage.onlyLocal.invert': false, // display only externally installed styles 'manage.onlyLocal.invert': false, // display only externally installed styles
'manage.onlyUsercss.invert': false, // display only non-usercss (standard) styles 'manage.onlyUsercss.invert': false, // display only non-usercss (standard) styles
// UI element state: expanded/collapsed // UI element state: expanded/collapsed
'manage.backup.expanded': true,
'manage.filters.expanded': true,
'manage.options.expanded': true, 'manage.options.expanded': true,
// the new compact layout doesn't look good on Android yet // the new compact layout doesn't look good on Android yet
'manage.newUI': !navigator.appVersion.includes('Android'), 'manage.newUI': !navigator.appVersion.includes('Android'),

View File

@ -393,6 +393,7 @@ var usercss = (() => {
}; };
const style = { const style = {
reason: 'install',
enabled: true, enabled: true,
sourceCode, sourceCode,
sections: [], sections: [],

View File

@ -6,6 +6,7 @@
<title i18n-text="manageTitle"></title> <title i18n-text="manageTitle"></title>
<link rel="stylesheet" href="global.css"> <link rel="stylesheet" href="global.css">
<link rel="stylesheet" href="manage/manage.css"> <link rel="stylesheet" href="manage/manage.css">
<link rel="stylesheet" href="manage/config-dialog.css">
<link rel="stylesheet" href="msgbox/msgbox.css"> <link rel="stylesheet" href="msgbox/msgbox.css">
<link rel="stylesheet" href="options/onoffswitch.css"> <link rel="stylesheet" href="options/onoffswitch.css">
<link rel="stylesheet" href="vendor-overwrites/colorpicker/colorpicker.css"> <link rel="stylesheet" href="vendor-overwrites/colorpicker/colorpicker.css">
@ -164,11 +165,15 @@
<div id="header"> <div id="header">
<h1 id="manage-heading" i18n-text="manageHeading"></h1> <h1 id="manage-heading" i18n-text="manageHeading"></h1>
<fieldset> <details id="filters" data-pref="manage.filters.expanded">
<summary>
<legend id="filters"> <h2 i18n-text="manageFilters">: <span id="filters-stats"></span></h2>
<span i18n-text="manageFilters"></span><span id="filters-stats"></span> <svg id="reset-filters" class="svg-icon" viewBox="0 0 20 20">
</legend> <title i18n-text="genericResetLabel"></title>
<polygon points="16.2,5.5 14.5,3.8 10,8.3 5.5,3.8 3.8,5.5 8.3,10 3.8,14.5
5.5,16.2 10,11.7 14.5,16.2 16.2,14.5 11.7,10 "/>
</svg>
</summary>
<div class="filter-selection"> <div class="filter-selection">
<label> <label>
@ -232,12 +237,14 @@
<span i18n-text="manageOnlyUpdates"></span> <span i18n-text="manageOnlyUpdates"></span>
</label> </label>
<div id="search-wrapper">
<input id="search" type="search" i18n-placeholder="searchStyles" spellcheck="false" <input id="search" type="search" i18n-placeholder="searchStyles" spellcheck="false"
i18n-title="searchStylesTooltip"
data-filter=":not(.not-matching)" data-filter=":not(.not-matching)"
data-filter-hide=".not-matching"> data-filter-hide=".not-matching">
<svg id="search-help" class="svg-icon info"><use xlink:href="#svg-icon-help"/></svg>
</div>
</fieldset> </details>
<p class="nowrap"> <p class="nowrap">
<button id="check-all-updates" i18n-text="checkAllUpdates"><span id="update-progress"></span></button> <button id="check-all-updates" i18n-text="checkAllUpdates"><span id="update-progress"></span></button>
@ -254,11 +261,11 @@
<button id="check-all-updates-force" class="hidden" i18n-text="checkAllUpdatesForce"></button> <button id="check-all-updates-force" class="hidden" i18n-text="checkAllUpdatesForce"></button>
</p> </p>
<p> <p id="add-style-wrapper">
<a href="edit.html"> <a href="edit.html">
<button id="add-style-label" i18n-text="addStyleLabel"></button> <button id="add-style-label" i18n-text="addStyleLabel"></button>
</a> </a>
<label id="newStyleAsUsercss-wrapper" class="nobreak"> <label id="add-style-as-usercss-wrapper">
<input type="checkbox" id="newStyleAsUsercss"> <input type="checkbox" id="newStyleAsUsercss">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg> <svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
<span i18n-text="manageNewStyleAsUsercss" i18n-title="optionsAdvancedNewStyleAsUsercss"></span> <span i18n-text="manageNewStyleAsUsercss" i18n-title="optionsAdvancedNewStyleAsUsercss"></span>
@ -287,10 +294,11 @@
<label for="manage.newUI.favicons" i18n-text="manageFavicons"> <label for="manage.newUI.favicons" i18n-text="manageFavicons">
<input id="manage.newUI.favicons" type="checkbox"> <input id="manage.newUI.favicons" type="checkbox">
<svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg> <svg class="svg-icon checked"><use xlink:href="#svg-icon-checked"/></svg>
</label> <svg class="svg-icon select-arrow" data-toggle-on-click="#faviconsHelp">
<svg class="svg-icon info" viewBox="0 0 14 16" i18n-alt="helpAlt" data-toggle-on-click="#faviconsHelp"> <title i18n-text="optionsSubheading"></title>
<path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path> <use xlink:href="#svg-icon-select-arrow"/>
</svg> </svg>
</label>
<div id="faviconsHelp" class="hidden" i18n-text="manageFaviconsHelp"> <div id="faviconsHelp" class="hidden" i18n-text="manageFaviconsHelp">
<div> <div>
<label for="manage.newUI.faviconsGray" i18n-text="manageFaviconsGray"> <label for="manage.newUI.faviconsGray" i18n-text="manageFaviconsGray">
@ -316,14 +324,14 @@
</details> </details>
<div id="backup"> <details id="backup" data-pref="manage.backup.expanded">
<h2 id="backup-title" i18n-text="backupButtons"></h2> <summary><h2 id="backup-title" i18n-text="backupButtons"></h2></summary>
<span id="backup-message" i18n-text="backupMessage"></span> <span id="backup-message" i18n-text="backupMessage"></span>
<p> <p>
<button id="file-all-styles" i18n-text="bckpInstStyles"></button> <button id="file-all-styles" i18n-text="bckpInstStyles"></button>
<button id="unfile-all-styles" i18n-text="retrieveBckp"></button> <button id="unfile-all-styles" i18n-text="retrieveBckp"></button>
</p> </p>
</div> </details>
<p id="manage-text" i18n-html="manageText"></p> <p id="manage-text" i18n-html="manageText"></p>
@ -343,6 +351,11 @@
<symbol id="svg-icon-select-arrow" viewBox="0 0 1792 1792"> <symbol id="svg-icon-select-arrow" viewBox="0 0 1792 1792">
<path fill-rule="evenodd" d="M1408 704q0 26-19 45l-448 448q-19 19-45 19t-45-19l-448-448q-19-19-19-45t19-45 45-19h896q26 0 45 19t19 45z"/> <path fill-rule="evenodd" d="M1408 704q0 26-19 45l-448 448q-19 19-45 19t-45-19l-448-448q-19-19-19-45t19-45 45-19h896q26 0 45 19t19 45z"/>
</symbol> </symbol>
<symbol id="svg-icon-help" viewBox="0 0 14 16">
<title i18n-text="helpAlt"></title>
<path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path>
</symbol>
</svg> </svg>
</body> </body>

112
manage/config-dialog.css Normal file
View File

@ -0,0 +1,112 @@
/* config dialog */
.config-dialog .config-heading {
float: right;
margin: -1.25rem 0 0 0;
font-size: 0.9em;
}
.config-dialog label {
display: flex;
padding: .75em 0;
align-items: center;
}
.config-dialog .select-resizer {
position: static;
}
.config-dialog label:first-child {
padding-top: 0;
}
.config-dialog label:last-child {
padding-bottom: 0;
}
.config-dialog label:not(:first-child) {
border-top: 1px dotted #ccc;
}
.config-dialog label > :first-child {
margin-right: 8px;
flex-grow: 1;
}
.config-dialog label:not([disabled]) > :first-child {
cursor: default;
}
.config-dialog label:not([disabled]):hover > :first-child {
text-shadow: 0 0 0.01px rgba(0, 0, 0, .25);
cursor: pointer;
}
.config-dialog .dirty:after {
content: "*";
position: absolute;
left: 6px;
}
.config-dialog .dirty {
font-style: italic;
}
.config-dialog input,
.config-dialog select,
.config-dialog .onoffswitch {
width: var(--onoffswitch-width);
margin: 0;
height: 2em;
box-sizing: border-box;
vertical-align: middle;
}
.config-dialog .select-resizer,
.config-dialog select {
width: auto;
min-width: var(--onoffswitch-width);
max-width: 124px;
left: auto;
position: relative;
}
.config-dialog .onoffswitch {
height: auto;
margin: calc((2em - 12px) / 2) 0;
}
.config-dialog input[type="text"] {
padding-left: 0.25em;
}
.config-dialog label > :last-child {
box-sizing: border-box;
flex-shrink: 0;
}
.config-dialog label > :last-child:not(.onoffswitch):not(.select-resizer) > :not(:last-child) {
margin-right: 4px;
}
.cm-colorview::before,
.color-swatch {
width: var(--onoffswitch-width) !important;
height: 20px !important;
}
.cm-colorview::before {
margin: 1px !important;
}
.color-swatch {
position: absolute;
border: 1px solid gray;
margin-top: -22px;
cursor: pointer;
}
.colorpicker-popup {
z-index: 2147483647 !important;
border: none !important;
box-shadow: 3px 3px 50px rgba(0,0,0,.5) !important;
}

View File

@ -6,35 +6,79 @@ function configDialog(style) {
const varsHash = deepCopy(data.vars) || {}; 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]);
let varsInitial = getInitialValues(varsHash);
const elements = []; const elements = [];
const colorpicker = window.colorpicker(); const colorpicker = window.colorpicker();
const isPopup = location.href.includes('popup.html');
const buttons = {};
buildConfigForm(); buildConfigForm();
renderValues(); renderValues();
return messageBox({ return messageBox({
title: `${style.name} v${data.version}`, title: `${style.name} v${data.version}`,
className: 'config-dialog', className: 'config-dialog' + (isPopup ? ' stylus-popup' : ''),
contents: [ contents: [
$create('.config-heading', data.supportURL && $create('.config-heading', data.supportURL &&
$createLink({className: '.external-support', href: data.supportURL}, t('externalFeedback'))), $createLink({className: '.external-support', href: data.supportURL}, t('externalFeedback'))),
$create('.config-body', elements) $create('.config-body', elements)
], ],
buttons: [ buttons: [
t('confirmSave'), {textContent: t('confirmSave'), dataset: {cmd: 'save'}, disabled: true, onclick: save},
{ {textContent: t('confirmDefault'), dataset: {cmd: 'default'}, onclick: useDefault},
textContent: t('confirmDefault'), {textContent: t('confirmClose'), dataset: {cmd: 'close'}},
onclick: useDefault ],
}, onshow,
t('confirmCancel') }).then(() => {
] document.body.style.minWidth = '';
}).then(({button, esc}) => { document.body.style.minHeight = '';
if (button !== 1) {
colorpicker.hide(); colorpicker.hide();
});
function getInitialValues(source) {
const data = {};
for (const name of varNames) {
const va = source[name];
data[name] = isDefault(va) ? va.default : va.value;
} }
if (button > 0 || esc || !vars.length || !vars.some(va => va.dirty)) { return data;
}
function onshow(box) {
if (isPopup) {
adjustSizeForPopup(box);
box.style.animationDuration = '0s';
}
box.addEventListener('change', onchange);
buttons.save = $('[data-cmd="save"]', box);
buttons.default = $('[data-cmd="default"]', box);
buttons.close = $('[data-cmd="close"]', box);
updateButtons();
}
function onchange({target}) {
// invoked after element's own onchange so 'va' contains the updated value
const va = target.va;
if (va) {
va.dirty = varsInitial[va.name] !== (isDefault(va) ? va.default : va.value);
target.closest('label').classList.toggle('dirty', va.dirty);
updateButtons();
}
}
function updateButtons() {
const someDirty = vars.some(va => va.dirty);
buttons.save.disabled = !someDirty;
buttons.default.disabled = vars.every(isDefault);
buttons.close.textContent = t(someDirty ? 'confirmCancel' : 'confirmClose');
}
function save() {
if (!vars.length || !vars.some(va => va.dirty)) {
return; return;
} }
style.enabled = true;
style.reason = 'config'; style.reason = 'config';
const styleVars = style.usercssData.vars; const styleVars = style.usercssData.vars;
const bgStyle = BG.cachedStyles.byId.get(style.id); const bgStyle = BG.cachedStyles.byId.get(style.id);
@ -52,7 +96,7 @@ function configDialog(style) {
error = ['type ', '*' + va.type, ' != ', '*' + bgva.type]; error = ['type ', '*' + va.type, ' != ', '*' + bgva.type];
} else } else
if ((va.type === 'select' || va.type === 'dropdown') && if ((va.type === 'select' || va.type === 'dropdown') &&
va.value !== null && va.value !== undefined && !isDefault(va) &&
bgva.options.every(o => o.name !== va.value)) { bgva.options.every(o => o.name !== va.value)) {
error = `'${va.value}' not in the updated '${va.type}' list`; error = `'${va.value}' not in the updated '${va.type}' list`;
} else if (!va.dirty) { } else if (!va.dirty) {
@ -76,33 +120,52 @@ function configDialog(style) {
$create({tag: 'li', appendChild: msg}))), $create({tag: 'li', appendChild: msg}))),
]); ]);
} }
return numValid && BG.usercssHelper.save(style); return numValid && BG.usercssHelper.save(style).then(saved => {
varsInitial = getInitialValues(deepCopy(saved.usercssData.vars));
vars.forEach(va => onchange({target: va.input}));
updateButtons();
}); });
}
function useDefault() {
for (const va of vars) {
va.value = null;
onchange({target: va.input});
}
renderValues();
}
function isDefault(va) {
return va.value === null || va.value === undefined || va.value === va.default;
}
function buildConfigForm() { function buildConfigForm() {
for (const va of vars) { for (const va of vars) {
let children; let children;
switch (va.type) { switch (va.type) {
case 'color': case 'color':
va.inputColor = $create('.color-swatch', {va, onclick: showColorpicker});
children = [ children = [
$create('.cm-colorview', [ $create('.cm-colorview', [
va.inputColor, va.input = $create('.color-swatch', {
va,
onclick: showColorpicker
}),
]), ]),
]; ];
break; break;
case 'checkbox': case 'checkbox':
va.input = $create('input.slider', {type: 'checkbox'});
va.input.onchange = () => {
va.dirty = true;
va.value = String(Number(va.input.checked));
};
children = [ children = [
$create('span.onoffswitch', [ $create('span.onoffswitch', [
va.input, va.input = $create('input.slider', {
va,
type: 'checkbox',
onchange() {
va.value = va.input.checked ? '1' : '0';
},
}),
$create('span'), $create('span'),
]) ]),
]; ];
break; break;
@ -110,26 +173,33 @@ function configDialog(style) {
case 'dropdown': case 'dropdown':
case 'image': case 'image':
// TODO: a image picker input? // TODO: a image picker input?
va.input = $create('.select-resizer', [ children = [
$create('select', va.options.map(o => $create('.select-resizer', [
va.input = $create('select', {
va,
onchange() {
va.value = this.value;
}
},
va.options.map(o =>
$create('option', {value: o.name}, o.label))), $create('option', {value: o.name}, o.label))),
$create('SVG:svg.svg-icon.select-arrow', $create('SVG:svg.svg-icon.select-arrow',
$create('SVG:use', {'xlink:href': '#svg-icon-select-arrow'})), $create('SVG:use', {'xlink:href': '#svg-icon-select-arrow'})),
]); ]),
va.input.onchange = () => { ];
va.dirty = true;
va.value = va.input.value;
};
children = [va.input];
break; break;
default: default:
va.input = $create('input', {type: 'text'}); children = [
va.input.oninput = () => { va.input = $create('input', {
va.dirty = true; va,
va.value = va.input.value; type: 'text',
}; oninput() {
children = [va.input]; va.value = this.value;
this.dispatchEvent(new Event('change', {bubbles: true}));
},
}),
];
break; break;
} }
elements.push( elements.push(
@ -142,10 +212,9 @@ function configDialog(style) {
function renderValues() { function renderValues() {
for (const va of vars) { for (const va of vars) {
const useDefault = va.value === null || va.value === undefined; const value = isDefault(va) ? va.default : va.value;
const value = useDefault ? va.default : va.value;
if (va.type === 'color') { if (va.type === 'color') {
va.inputColor.style.backgroundColor = value; va.input.style.backgroundColor = value;
if (colorpicker.options.va === va) { if (colorpicker.options.va === va) {
colorpicker.setColor(value); colorpicker.setColor(value);
} }
@ -157,15 +226,6 @@ function configDialog(style) {
} }
} }
function useDefault() {
for (const va of vars) {
const hasValue = va.value !== null && va.value !== undefined;
va.dirty = hasValue && va.value !== va.default;
va.value = null;
}
renderValues();
}
function showColorpicker() { function showColorpicker() {
window.removeEventListener('keydown', messageBox.listeners.key, true); window.removeEventListener('keydown', messageBox.listeners.key, true);
const box = $('#message-box-contents'); const box = $('#message-box-contents');
@ -182,9 +242,9 @@ function configDialog(style) {
function onColorChanged(newColor) { function onColorChanged(newColor) {
if (newColor) { if (newColor) {
this.va.dirty = true;
this.va.value = newColor; this.va.value = newColor;
this.va.inputColor.style.backgroundColor = newColor; this.va.input.style.backgroundColor = newColor;
this.va.input.dispatchEvent(new Event('change', {bubbles: true}));
} }
debounce(restoreEscInDialog); debounce(restoreEscInDialog);
} }
@ -194,4 +254,22 @@ function configDialog(style) {
window.addEventListener('keydown', messageBox.listeners.key, true); window.addEventListener('keydown', messageBox.listeners.key, true);
} }
} }
function adjustSizeForPopup(box) {
box.style = 'white-space: nowrap !important';
box.firstElementChild.style = 'max-width: none; max-height: none;'.replace(/;/g, '!important;');
const {offsetWidth, offsetHeight} = box.firstElementChild;
box.style = box.firstElementChild.style = '';
const colorpicker = document.body.appendChild(
$create('.colorpicker-popup', {style: 'display: none!important'}));
const MIN_WIDTH = parseFloat(getComputedStyle(colorpicker).width) || 350;
const MIN_HEIGHT = 250;
colorpicker.remove();
const width = Math.max(Math.min(offsetWidth / 0.9 + 2, 800), MIN_WIDTH);
const height = Math.max(Math.min(offsetHeight / 0.9 + 2, 600), MIN_HEIGHT);
document.body.style.setProperty('min-width', width + 'px', 'important');
document.body.style.setProperty('min-height', height + 'px', 'important');
}
} }

View File

@ -1,4 +1,4 @@
/* global installed */ /* global installed messageBox */
'use strict'; 'use strict';
const filtersSelector = { const filtersSelector = {
@ -31,6 +31,25 @@ onDOMready().then(onBackgroundReady).then(() => {
if (urlFilterParam) { if (urlFilterParam) {
$('#search').value = 'url:' + urlFilterParam; $('#search').value = 'url:' + urlFilterParam;
} }
$('#search-help').onclick = () => {
messageBox({
className: 'help-text',
title: t('searchStyles'),
contents:
$create('ul',
t('searchStylesHelp').split('\n').map(line =>
$create('li', line.split(/(<.*?>)/).map((s, i, words) => {
if (s.startsWith('<')) {
const num = words.length;
const className = i === num - 2 && !words[num - 1] ? '.last' : '';
return $create('mark' + className, s.slice(1, -1));
} else {
return s;
}
})))),
buttons: [t('confirmOK')],
});
};
$$('select[id$=".invert"]').forEach(el => { $$('select[id$=".invert"]').forEach(el => {
const slave = $('#' + el.id.replace('.invert', '')); const slave = $('#' + el.id.replace('.invert', ''));
@ -76,6 +95,36 @@ onDOMready().then(onBackgroundReady).then(() => {
} }
}); });
$('#reset-filters').onclick = event => {
event.preventDefault();
if (!filtersSelector.hide) {
return;
}
for (const el of $$('#filters [data-filter]')) {
let value;
if (el.type === 'checkbox' && el.checked) {
value = el.checked = false;
} else if (el.value) {
value = el.value = '';
}
if (value !== undefined) {
el.lastValue = value;
if (el.id in prefs.readOnlyValues) {
prefs.set(el.id, false);
}
}
}
filterOnChange({forceRefilter: true});
};
// Adjust width after selects are visible
prefs.subscribe(['manage.filters.expanded'], () => {
const el = $('#filters');
if (el.open) {
$$('select', el).forEach(select => select.adjustWidth());
}
});
filterOnChange({forceRefilter: true}); filterOnChange({forceRefilter: true});
}); });
@ -303,7 +352,7 @@ function showFiltersStats() {
debounce(showFiltersStats, 100); debounce(showFiltersStats, 100);
return; return;
} }
$('#filters').classList.toggle('active', filtersSelector.hide !== ''); $('#filters summary').classList.toggle('active', filtersSelector.hide !== '');
const numTotal = BG.cachedStyles.list.length; const numTotal = BG.cachedStyles.list.length;
const numHidden = installed.getElementsByClassName('entry hidden').length; const numHidden = installed.getElementsByClassName('entry hidden').length;
const numShown = Math.min(numTotal - numHidden, installed.children.length); const numShown = Math.min(numTotal - numHidden, installed.children.length);
@ -320,10 +369,11 @@ function showFiltersStats() {
function searchStyles({immediately, container}) { function searchStyles({immediately, container}) {
const searchElement = $('#search'); const searchElement = $('#search');
const urlMode = /^\s*url:/i.test(searchElement.value); const value = searchElement.value;
const urlMode = /^\s*url:/i.test(value);
const query = urlMode const query = urlMode
? searchElement.value.replace(/^\s*url:/i, '').trim() ? value.replace(/^\s*url:/i, '').trim()
: searchElement.value.toLocaleLowerCase(); : value.toLocaleLowerCase();
const queryPrev = searchElement.lastValue || ''; const queryPrev = searchElement.lastValue || '';
if (query === queryPrev && !immediately && !container) { if (query === queryPrev && !immediately && !container) {
return; return;
@ -334,6 +384,12 @@ function searchStyles({immediately, container}) {
} }
searchElement.lastValue = query; searchElement.lastValue = query;
const trimmed = query.trim();
const rx = trimmed.startsWith('/') && trimmed.indexOf('/', 1) > 0 &&
tryRegExp(...(value.match(/^\s*\/(.*?)\/([gimsuy]*)\s*$/) || []).slice(1));
const words = rx ? null :
trimmed.startsWith('"') && trimmed.endsWith('"') ? [value.trim().slice(1, -1)] :
query.split(/\s+/).filter(s => s.length > 2);
const searchInVisible = !urlMode && queryPrev && query.includes(queryPrev); const searchInVisible = !urlMode && queryPrev && query.includes(queryPrev);
const entries = container && container.children || container || const entries = container && container.children || container ||
(searchInVisible ? $$('.entry:not(.hidden)') : installed.children); (searchInVisible ? $$('.entry:not(.hidden)') : installed.children);
@ -349,6 +405,7 @@ function searchStyles({immediately, container}) {
urlMode || urlMode ||
isMatchingText(style.name) || isMatchingText(style.name) ||
style.url && isMatchingText(style.url) || style.url && isMatchingText(style.url) ||
style.sourceCode && isMatchingText(style.sourceCode) ||
isMatchingStyle(style))); isMatchingStyle(style)));
} }
if (entry.classList.contains('not-matching') !== !isMatching) { if (entry.classList.contains('not-matching') !== !isMatching) {
@ -384,6 +441,17 @@ function searchStyles({immediately, container}) {
} }
function isMatchingText(text) { function isMatchingText(text) {
return text.toLocaleLowerCase().indexOf(query) >= 0; if (rx) {
return rx.test(text);
}
for (let pass = 1; pass <= 2; pass++) {
for (const word of words) {
if (text.includes(word)) {
return true;
}
}
text = text.toLocaleLowerCase();
}
return false;
} }
} }

View File

@ -34,7 +34,17 @@ function importFromFile({fileTypeFilter, file} = {}) {
const fReader = new FileReader(); const fReader = new FileReader();
fReader.onloadend = event => { fReader.onloadend = event => {
fileInput.remove(); fileInput.remove();
importFromString(event.target.result).then(numStyles => { const text = event.target.result;
const maybeUsercss = !/^[\s\r\n]*\[/.test(text) &&
(text.includes('==UserStyle==') || /==UserStyle==/i.test(text));
(!maybeUsercss ?
importFromString(text) :
getOwnTab().then(tab => {
tab.url = URL.createObjectURL(new Blob([text], {type: 'text/css'}));
return BG.usercssHelper.openInstallPage(tab, {direct: true})
.then(() => URL.revokeObjectURL(tab.url));
})
).then(numStyles => {
document.body.style.cursor = ''; document.body.style.cursor = '';
resolve(numStyles); resolve(numStyles);
}); });

View File

@ -79,6 +79,7 @@ select {
#header h1 { #header h1 {
margin-top: 0; margin-top: 0;
margin-bottom: .3em;
} }
#header a[href^="edit"] { #header a[href^="edit"] {
@ -90,25 +91,16 @@ select {
margin-bottom: .25em; margin-bottom: .25em;
} }
#newStyleAsUsercss-wrapper:not(:hover) input:not(:checked) ~ a svg { #add-style-as-usercss-wrapper {
display: inline-flex;
}
#add-style-as-usercss-wrapper:not(:hover) input:not(:checked) ~ a svg {
fill: #aaa; fill: #aaa;
} }
#usercss-wiki svg { #usercss-wiki svg {
margin-top: -2px; margin-top: -4px;
}
.nobreak {
white-space: nowrap;
}
label.nobreak input {
vertical-align: sub;
margin: 0;
}
.firefox .chromium-only {
display: none;
} }
#installed { #installed {
@ -264,9 +256,24 @@ label.nobreak input {
} }
/* collapsibles */ /* collapsibles */
#header details:not(#filters),
#add-style-wrapper {
padding-bottom: .7em;
}
#add-style-wrapper,
#options :last-child,
#backup :last-child {
margin-bottom: 0;
}
#header details:not([open]),
#header details:not([open]) h2 { #header details:not([open]) h2 {
margin-bottom: -.25em; padding-bottom: 0;
}
#header details[open] summary {
padding-bottom: .5em;
} }
#header summary { #header summary {
@ -274,7 +281,6 @@ label.nobreak input {
margin-left: -13px; margin-left: -13px;
cursor: pointer; cursor: pointer;
outline: none; outline: none;
margin-bottom: 8px;
} }
#header summary h2 { #header summary h2 {
@ -326,6 +332,7 @@ label.nobreak input {
.filter-selection { .filter-selection {
position: relative; position: relative;
left: -9px;
} }
#header label { #header label {
@ -346,10 +353,6 @@ label.nobreak input {
margin-top: -2px; margin-top: -2px;
} }
.firefox #header .filter-selection label .checkmate {
margin: 0;
}
.newUI #newUIoptions > label { .newUI #newUIoptions > label {
padding-left: 0; padding-left: 0;
} }
@ -361,10 +364,7 @@ label.nobreak input {
max-width: 100%; max-width: 100%;
padding-left: 4px; padding-left: 4px;
padding-right: 14px; padding-right: 14px;
} margin-left: 4px;
.firefox .filter-selection select {
padding-left: 0;
} }
.filter-selection .select-arrow { .filter-selection .select-arrow {
@ -382,17 +382,13 @@ label.nobreak input {
left: 16px; left: 16px;
} }
.firefox .select-resizer { #filters label,
left: 16px; #filters .filter-selection {
}
fieldset > label,
fieldset > .filter-selection {
transition: background-color .25s; transition: background-color .25s;
} }
fieldset > label:hover, #filters label:hover,
fieldset > .filter-selection:hover { #filters .filter-selection:hover {
background-color: hsla(0, 0%, 50%, .2); background-color: hsla(0, 0%, 50%, .2);
} }
@ -646,6 +642,7 @@ fieldset > .filter-selection:hover {
align-items: center; align-items: center;
margin-bottom: auto; margin-bottom: auto;
flex-wrap: wrap; flex-wrap: wrap;
position: relative;
} }
#newUIoptions input[type="number"] { #newUIoptions input[type="number"] {
@ -653,6 +650,18 @@ fieldset > .filter-selection:hover {
margin-right: .5em; margin-right: .5em;
} }
#newUIoptions [data-toggle-on-click] {
transform: rotate(-90deg);
cursor: pointer;
right: -16px;
top: 0;
pointer-events: auto;
}
#newUIoptions [data-toggle-on-click][open] {
transform: none;
}
input[id^="manage.newUI"] { input[id^="manage.newUI"] {
margin-left: 0; margin-left: 0;
} }
@ -751,26 +760,14 @@ input[id^="manage.newUI"] {
display: none !important; display: none !important;
} }
fieldset { #filters label {
border-width: 1px;
border-radius: 6px;
margin: 1em 0;
min-width: 0;
max-width: 250px;
}
fieldset > input,
fieldset > label {
display: flex; display: flex;
align-items: center; align-items: center;
}
#header fieldset > label {
padding-left: 20px; padding-left: 20px;
} }
#header fieldset > label input[type="checkbox"]:not(.slider), #filters label input[type="checkbox"]:not(.slider),
#header fieldset > label input[type="checkbox"]:not(.slider):checked + .svg-icon.checked{ #filters label input[type="checkbox"]:not(.slider):checked + .svg-icon.checked{
left: 4px; left: 4px;
} }
@ -778,28 +775,82 @@ fieldset > label {
border: 1px solid transparent; border: 1px solid transparent;
} }
#filters.active { #filters summary h2 {
margin-left: -4px;
}
.active #filters-stats {
background-color: darkcyan; background-color: darkcyan;
border-color: darkcyan; border-color: darkcyan;
color: white; color: white;
font-size: 0.7rem;
font-weight: normal;
padding: 2px 5px;
position: relative;
top: -2px;
} }
#filters:not(.active) #filters-stats { #reset-filters {
position: absolute;
margin-top: 2px;
fill: hsla(180, 50%, 27%, .5);
width: 24px; /* widen the click area a bit */
height: 20px;
padding: 2px;
box-sizing: border-box;
}
#reset-filters:hover {
fill: hsla(180, 50%, 27%, 1);
}
#filters summary:not(.active) #reset-filters,
#filters summary:not(.active) #filters-stats {
display: none; display: none;
} }
#filters-stats::before { #search-wrapper {
content: ": "; display: flex;
align-items: center;
flex-wrap: wrap;
} }
#search { #search {
width: calc(100% - 4px); flex-grow: 1;
margin: 0.25rem 4px 0; margin: 0.25rem 0 0;
border-radius: 0.25rem;
padding-left: 0.25rem; padding-left: 0.25rem;
border-width: 1px; border-width: 1px;
} }
#search-wrapper .info {
margin: 4px -5px 0 8px;
}
#message-box.help-text > div {
max-width: 26rem;
}
.help-text li:not(:last-child) {
margin-bottom: 1em;
}
.help-text mark {
background-color: rgba(128, 128, 128, .15);
color: currentColor;
padding: 2px 6px;
font-weight: bold;
font-family: monospace;
border: 1px solid rgba(128, 128, 128, .25);
display: inline-block;
margin: 2px;
}
.help-text mark.last {
display: block;
width: -moz-min-content;
width: min-content;
white-space: nowrap;
}
#import ul { #import ul {
margin-left: 0; margin-left: 0;
padding-left: 0; padding-left: 0;
@ -860,109 +911,6 @@ fieldset > label {
text-overflow: ellipsis; text-overflow: ellipsis;
} }
/* config dialog */
.config-dialog .config-heading {
float: right;
margin: -1.25rem 0 0 0;
font-size: 0.9em;
}
.config-dialog label {
display: flex;
padding: .75em 0;
align-items: center;
}
.config-dialog .select-resizer {
position: static;
}
.config-dialog label:first-child {
padding-top: 0;
}
.config-dialog label:last-child {
padding-bottom: 0;
}
.config-dialog label:not(:first-child) {
border-top: 1px dotted #ccc;
}
.config-dialog label > :first-child {
margin-right: 8px;
flex-grow: 1;
}
.config-dialog label:not([disabled]) > :first-child {
cursor: default;
}
.config-dialog label:not([disabled]):hover > :first-child {
text-shadow: 0 0 0.01px rgba(0, 0, 0, .25);
cursor: pointer;
}
.config-dialog input,
.config-dialog select,
.config-dialog .onoffswitch {
width: var(--onoffswitch-width);
margin: 0;
height: 2em;
box-sizing: border-box;
vertical-align: middle;
}
.config-dialog .select-resizer,
.config-dialog select {
width: auto;
min-width: var(--onoffswitch-width);
max-width: 124px;
left: auto;
position: relative;
}
.config-dialog .onoffswitch {
height: auto;
margin: calc((2em - 12px) / 2) 0;
}
.config-dialog input[type="text"] {
padding-left: 0.25em;
}
.config-dialog label > :last-child {
box-sizing: border-box;
flex-shrink: 0;
}
.config-dialog label > :last-child:not(.onoffswitch):not(.select-resizer) > :not(:last-child) {
margin-right: 4px;
}
.cm-colorview::before,
.color-swatch {
width: var(--onoffswitch-width) !important;
height: 20px !important;
}
.cm-colorview::before {
margin: 1px !important;
}
.color-swatch {
position: absolute;
border: 1px solid gray;
margin-top: -22px;
cursor: pointer;
}
.colorpicker-popup {
z-index: 2147483647 !important;
border: none !important;
box-shadow: 3px 3px 50px rgba(0,0,0,.5) !important;
}
@keyframes fadein { @keyframes fadein {
from { from {
opacity: 0; opacity: 0;
@ -998,7 +946,6 @@ fieldset > label {
} }
#header p, #header p,
#header fieldset div,
#backup { #backup {
display: inline-block; display: inline-block;
} }
@ -1011,8 +958,7 @@ fieldset > label {
margin-right: 1em; margin-right: 1em;
} }
#backup p, #backup p {
#header fieldset {
margin: 0; margin: 0;
} }
@ -1053,12 +999,6 @@ fieldset > label {
padding-left: 0; padding-left: 0;
} }
fieldset {
max-width: none;
margin-top: 0;
margin-right: 2em;
}
#header h1, #header h1,
#header h2, #header h2,
#header h3, #header h3,
@ -1088,21 +1028,10 @@ fieldset > label {
.newUI #header > *:not(h1), .newUI #header > *:not(h1),
.newUI #newUIoptions, .newUI #newUIoptions,
#newUIoptions > *, #newUIoptions > * {
fieldset label {
display: inline; display: inline;
} }
fieldset {
border: none;
margin: 0;
padding: 0 0 1em;
}
fieldset legend {
display: none;
}
#header label { #header label {
white-space: nowrap; white-space: nowrap;
} }
@ -1111,3 +1040,29 @@ fieldset > label {
word-break: break-all; word-break: break-all;
} }
} }
@supports (-moz-appearance: none) {
.chromium-only {
display: none;
}
#header .filter-selection label .checkmate {
margin: 0;
}
.filter-selection select {
padding-left: 0;
}
.select-resizer {
left: 16px;
}
#reset-filters {
margin-top: 4px;
}
#filters summary h2 {
margin-left: -2px;
}
}

View File

@ -66,7 +66,15 @@ function initGlobalEvents() {
$$('[data-toggle-on-click]').forEach(el => { $$('[data-toggle-on-click]').forEach(el => {
// dataset on SVG doesn't work in Chrome 49-??, works in 57+ // dataset on SVG doesn't work in Chrome 49-??, works in 57+
const target = $(el.getAttribute('data-toggle-on-click')); const target = $(el.getAttribute('data-toggle-on-click'));
el.onclick = () => target.classList.toggle('hidden'); el.onclick = event => {
event.preventDefault();
target.classList.toggle('hidden');
if (target.classList.contains('hidden')) {
el.removeAttribute('open');
} else {
el.setAttribute('open', '');
}
};
}); });
// triggered automatically by setupLivePrefs() below // triggered automatically by setupLivePrefs() below

View File

@ -132,6 +132,18 @@
margin: 0 .375rem; margin: 0 .375rem;
} }
/* popup */
#message-box.stylus-popup {
margin: 0;
align-items: center;
justify-content: center;
}
#message-box.stylus-popup > div {
max-width: 90vw;
top: auto;
right: auto;
}
@keyframes fadein { @keyframes fadein {
from { from {
opacity: 0; opacity: 0;

View File

@ -70,11 +70,11 @@ function messageBox({
$create(`#${id}-contents`, tHTML(contents)), $create(`#${id}-contents`, tHTML(contents)),
$create(`#${id}-buttons`, $create(`#${id}-buttons`,
buttons.map((content, buttonIndex) => content && buttons.map((content, buttonIndex) => content &&
$create('button', { $create('button', Object.assign({
buttonIndex, buttonIndex,
textContent: content.textContent || content, textContent: typeof content === 'object' ? '' : content,
onclick: content.onclick || messageBox.listeners.button, onclick: messageBox.listeners.button,
}))), }, typeof content === 'object' && content)))),
]), ]),
]); ]);
} }

View File

@ -4,8 +4,6 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="global.css"> <link rel="stylesheet" href="global.css">
<link rel="stylesheet" href="popup/popup.css">
<link rel="stylesheet" href="popup/search-results.css">
<style id="firefox-transitions-bug-suppressor"> <style id="firefox-transitions-bug-suppressor">
/* restrict to FF */ /* restrict to FF */
@ -34,6 +32,11 @@
<div class="actions"> <div class="actions">
<a href="#" class="enable" i18n-text="enableStyleLabel"></a> <a href="#" class="enable" i18n-text="enableStyleLabel"></a>
<a href="#" class="disable" i18n-text="disableStyleLabel"></a> <a href="#" class="disable" i18n-text="disableStyleLabel"></a>
<a href="#" class="configure" i18n-title="configureStyle">
<svg class="svg-icon config" viewBox="0 0 14 16">
<path d="m6.2578 1.1191c-0.4526 0-0.81641 0.36381-0.81641 0.81641v0.69531a5.5932 5.5932 0 0 0-1.1328 0.47266l-0.49414-0.49414c-0.16002-0.16002-0.36907-0.24023-0.57812-0.24023-0.20905 0-0.41811 0.080216-0.57812 0.24023l-1.0488 1.0488c-0.32004 0.32004-0.32004 0.83621 0 1.1562l0.49023 0.49023a5.5932 5.5932 0 0 0-0.4668 1.1348h-0.69726c-0.4526 0-0.81641 0.36576-0.81641 0.81836v1.4844c0 0.4526 0.36381 0.81641 0.81641 0.81641h0.69726a5.5932 5.5932 0 0 0 0.4707 1.1328l-0.49414 0.49414c-0.32004 0.32004-0.32004 0.83426 0 1.1543l1.0488 1.0508c0.32004 0.32003 0.83621 0.32003 1.1562 0l0.49023-0.49024a5.5932 5.5932 0 0 0 1.1367 0.4668v0.69727c0 0.4526 0.36381 0.81641 0.81641 0.81641h1.4844c0.4526 0 0.81641-0.36381 0.81641-0.81641v-0.69727a5.5932 5.5932 0 0 0 1.1328-0.4707l0.49414 0.49414c0.32004 0.32003 0.83622 0.32003 1.1562 0l1.0488-1.0508c0.32004-0.32004 0.32004-0.83426 0-1.1543l-0.49023-0.49023a5.5932 5.5932 0 0 0 0.4668-1.1367h0.69726c0.4526 0 0.81641-0.36381 0.81641-0.81641v-1.4844c0-0.4526-0.36381-0.81836-0.81641-0.81836h-0.69726a5.5932 5.5932 0 0 0-0.4707-1.1309l0.49414-0.49414c0.32004-0.32004 0.32004-0.83621 0-1.1562l-1.0488-1.0488c-0.16002-0.16002-0.36907-0.24023-0.57812-0.24023s-0.41811 0.080216-0.57812 0.24023l-0.49023 0.49023a5.5932 5.5932 0 0 0-1.1367-0.4668v-0.69727c0-0.4526-0.36381-0.81641-0.81641-0.81641zm0.56836 0.7793h0.34766c0.22291 0 0.40234 0.17943 0.40234 0.40234v1.1621a4.5763 4.5763 0 0 1 2.2227 0.92383l0.82422-0.82422c0.15762-0.15762 0.41074-0.15762 0.56836 0l0.24609 0.24609c0.15762 0.15762 0.15762 0.41074 0 0.56836l-0.82226 0.82227a4.5763 4.5763 0 0 1 0.91797 2.2246h1.166c0.22291 0 0.40234 0.17943 0.40234 0.40234v0.34766c0 0.22291-0.17943 0.40234-0.40234 0.40234h-1.1641a4.5763 4.5763 0 0 1-0.92188 2.2227l0.82422 0.82422c0.15762 0.15762 0.15762 0.41074 0 0.56836l-0.24609 0.24609c-0.15762 0.15763-0.41074 0.15763-0.56836 0l-0.82227-0.82226a4.5763 4.5763 0 0 1-2.2246 0.91797v1.166c0 0.22291-0.17943 0.40234-0.40234 0.40234h-0.34766c-0.22291 0-0.40234-0.17943-0.40234-0.40234v-1.1641a4.5763 4.5763 0 0 1-2.2227-0.92188l-0.82422 0.82422c-0.15762 0.15763-0.41074 0.15763-0.56836 0l-0.24609-0.24609c-0.15762-0.15762-0.15762-0.41074 0-0.56836l0.82226-0.82226a4.5763 4.5763 0 0 1-0.91797-2.2246h-1.166c-0.22291 0-0.40234-0.17943-0.40234-0.40234v-0.34766c0-0.22291 0.17943-0.40234 0.40234-0.40234h1.1641a4.5763 4.5763 0 0 1 0.92188-2.2227l-0.82422-0.82422c-0.15762-0.15762-0.15762-0.41074 0-0.56836l0.24609-0.24609c0.15762-0.15762 0.41074-0.15762 0.56836 0l0.82227 0.82227a4.5763 4.5763 0 0 1 2.2246-0.91797v-1.166c0-0.22291 0.17943-0.40234 0.40234-0.40234zm0.17383 2.7109a3.3898 3.3898 0 0 0-3.3906 3.3906 3.3898 3.3898 0 0 0 3.3906 3.3887 3.3898 3.3898 0 0 0 3.3906-3.3887 3.3898 3.3898 0 0 0-3.3906-3.3906zm0 1.1875a2.2034 2.2034 0 0 1 2.2031 2.2031 2.2034 2.2034 0 0 1-2.2031 2.2031 2.2034 2.2034 0 0 1-2.2031-2.2031 2.2034 2.2034 0 0 1 2.2031-2.2031z"/>
</svg>
</a>
<a class="style-edit-link" href="edit.html?id=" i18n-title="editStyleLabel"> <a class="style-edit-link" href="edit.html?id=" i18n-title="editStyleLabel">
<svg class="svg-icon edit" viewBox="0 0 14 16"> <svg class="svg-icon edit" viewBox="0 0 14 16">
<path fill-rule="evenodd" d="M0 12v3h3l8-8-3-3-8 8zm3 2H1v-2h1v1h1v1zm10.3-9.3L12 6 9 3l1.3-1.3a.996.996 0 0 1 1.41 0l1.59 1.59c.39.39.39 1.02 0 1.41z"/> <path fill-rule="evenodd" d="M0 12v3h3l8-8-3-3-8 8zm3 2H1v-2h1v1h1v1zm10.3-9.3L12 6 9 3l1.3-1.3a.996.996 0 0 1 1.41 0l1.59 1.59c.39.39.39 1.02 0 1.41z"/>
@ -117,11 +120,24 @@
</div> </div>
</template> </template>
<link rel="stylesheet" href="vendor-overwrites/colorpicker/colorpicker.css">
<script src="vendor-overwrites/colorpicker/colorpicker.js"></script>
<link rel="stylesheet" href="msgbox/msgbox.css">
<script src="msgbox/msgbox.js"></script>
<link rel="stylesheet" href="options/onoffswitch.css">
<link rel="stylesheet" href="manage/config-dialog.css">
<script src="manage/config-dialog.js"></script>
<script src="js/dom.js"></script> <script src="js/dom.js"></script>
<script src="js/messaging.js"></script> <script src="js/messaging.js"></script>
<script src="js/localization.js"></script> <script src="js/localization.js"></script>
<script src="js/prefs.js"></script> <script src="js/prefs.js"></script>
<script src="content/apply.js"></script> <script src="content/apply.js"></script>
<link rel="stylesheet" href="popup/popup.css">
<link rel="stylesheet" href="popup/search-results.css">
<script src="popup/popup.js"></script> <script src="popup/popup.js"></script>
<script src="popup/search-results.js"></script> <script src="popup/search-results.js"></script>
<script src="popup/hotkeys.js"></script> <script src="popup/hotkeys.js"></script>

View File

@ -1,18 +1,36 @@
/* global applyOnMessage installed */ /* global applyOnMessage installed */
'use strict'; 'use strict';
// eslint-disable-next-line no-var
var hotkeys = (() => {
let togglablesShown;
let togglables;
let enabled = false;
let ready = false;
window.addEventListener('showStyles:done', function _() { window.addEventListener('showStyles:done', function _() {
window.removeEventListener('showStyles:done', _); window.removeEventListener('showStyles:done', _);
togglablesShown = true;
let togglablesShown = true; togglables = getTogglables();
let togglables = getTogglables(); ready = true;
setState(true);
window.addEventListener('keydown', onKeyDown);
initHotkeyInfo(); initHotkeyInfo();
return; });
return {setState};
function setState(newState = !enabled) {
if (!ready) {
throw new Error('hotkeys no ready');
}
if (newState !== enabled) {
window[`${newState ? 'add' : 'remove'}EventListener`]('keydown', onKeyDown);
enabled = newState;
}
}
function onKeyDown(event) { function onKeyDown(event) {
if (event.ctrlKey || event.altKey || event.metaKey) { if (event.ctrlKey || event.altKey || event.metaKey || !enabled) {
return; return;
} }
let entry; let entry;
@ -165,4 +183,4 @@ window.addEventListener('showStyles:done', function _() {
}); });
} }
} }
}); })();

View File

@ -1,3 +1,12 @@
:root {
--header-width: 280px;
--checkbox-width: 24px;
--name-padding-left: 40px;
--name-padding-right: 40px;
--actions-width: 75px;
--onoffswitch-width: 60px;
}
html, body { html, body {
height: min-content; height: min-content;
} }
@ -14,7 +23,7 @@ body {
color: #000; color: #000;
} }
body > div:not(#installed) { body > div:not(#installed):not(#message-box):not(.colorpicker-popup) {
margin-left: 9px; margin-left: 9px;
margin-right: 9px; margin-right: 9px;
} }
@ -216,14 +225,15 @@ html[style] .entry:nth-child(10):before {
content: "0"; content: "0";
} }
.entry .style-edit-link { .entry .actions {
margin-right: 2px; margin-left: -1px;
margin-right: -1px;
} }
.entry .style-edit-link, .entry .actions > * {
.entry .delete {
display: inline-block; display: inline-block;
padding: 0 1px 0; padding: 0 1px;
margin: 0 1px;
} }
.not-applied .checker, .not-applied .checker,
@ -331,8 +341,8 @@ body.blocked .actions > .main-controls {
/* Never shown, but can be enabled with a style */ /* Never shown, but can be enabled with a style */
.enable, .entry .actions > .enable,
.disable { .entry .actions > .disable {
display: none; display: none;
} }

View File

@ -1,3 +1,5 @@
/* global configDialog hotkeys */
'use strict'; 'use strict';
let installed; let installed;
@ -237,6 +239,7 @@ function createStyleElement({
Object.assign(entry, { Object.assign(entry, {
id: ENTRY_ID_PREFIX_RAW + style.id, id: ENTRY_ID_PREFIX_RAW + style.id,
styleId: style.id, styleId: style.id,
styleIsUsercss: Boolean(style.usercssData),
className: entry.className + ' ' + (style.enabled ? 'enabled' : 'disabled'), className: entry.className + ' ' + (style.enabled ? 'enabled' : 'disabled'),
onmousedown: handleEvent.maybeEdit, onmousedown: handleEvent.maybeEdit,
}); });
@ -267,9 +270,18 @@ function createStyleElement({
} }
}); });
const config = $('.configure', entry);
if (!style.usercssData && style.updateUrl && style.updateUrl.includes('?') && style.url) {
config.href = style.url;
config.target = '_blank';
} else if (!style.usercssData || !Object.keys(style.usercssData.vars || {}).length) {
config.style.display = 'none';
}
$('.enable', entry).onclick = handleEvent.toggle; $('.enable', entry).onclick = handleEvent.toggle;
$('.disable', entry).onclick = handleEvent.toggle; $('.disable', entry).onclick = handleEvent.toggle;
$('.delete', entry).onclick = handleEvent.delete; $('.delete', entry).onclick = handleEvent.delete;
$('.configure', entry).onclick = handleEvent.configure;
invokeOrPostpone(!postponeDetect, detectSloppyRegexps, {entry, style}); invokeOrPostpone(!postponeDetect, detectSloppyRegexps, {entry, style});
@ -339,6 +351,18 @@ Object.assign(handleEvent, {
} }
}, },
configure(event) {
const {styleId, styleIsUsercss} = handleEvent.getClickedStyleElement(event);
if (styleIsUsercss) {
getStylesSafe({id: styleId}).then(([style]) => {
hotkeys.setState(false);
configDialog(style).then(() => {
hotkeys.setState(true);
});
});
}
},
indicator(event) { indicator(event) {
const entry = handleEvent.getClickedStyleElement(event); const entry = handleEvent.getClickedStyleElement(event);
const info = template.regexpProblemExplanation.cloneNode(true); const info = template.regexpProblemExplanation.cloneNode(true);

View File

@ -218,8 +218,11 @@
setFromHexLettercaseElement(); setFromHexLettercaseElement();
} }
function hide() { function hide({notify = true} = {}) {
if (shown) { if (shown) {
if (notify) {
colorpickerCallback('');
}
unregisterEvents(); unregisterEvents();
focusNoScroll(prevFocusedElement); focusNoScroll(prevFocusedElement);
$root.remove(); $root.remove();
@ -550,8 +553,11 @@
} }
function onMouseUp(event) { function onMouseUp(event) {
if (releaseMouse(event, ['saturation', 'hue', 'opacity']) && releaseMouse(event, ['saturation', 'hue', 'opacity']);
!event.target.closest('.codemirror-colorview, .colorpicker-popup, .CodeMirror')) { }
function onMouseDown(event) {
if (event.button === 0 && !event.target.closest('.colorpicker-popup')) {
hide(); hide();
} }
} }
@ -592,7 +598,7 @@
colorpickerCallback(e.which === 27 ? '' : undefined); colorpickerCallback(e.which === 27 ? '' : undefined);
e.preventDefault(); e.preventDefault();
e.stopPropagation(); e.stopPropagation();
hide(); hide({notify: false});
break; break;
} }
} }
@ -660,6 +666,7 @@
function registerEvents() { function registerEvents() {
window.addEventListener('keydown', onKeyDown, true); window.addEventListener('keydown', onKeyDown, true);
window.addEventListener('mousedown', onMouseDown, true);
window.addEventListener('close-colorpicker-popup', onCloseRequest, true); window.addEventListener('close-colorpicker-popup', onCloseRequest, true);
$root.addEventListener('mouseleave', snooze); $root.addEventListener('mouseleave', snooze);
$root.addEventListener('mouseenter', stopSnoozing); $root.addEventListener('mouseenter', stopSnoozing);
@ -681,6 +688,7 @@
function unregisterEvents() { function unregisterEvents() {
window.removeEventListener('keydown', onKeyDown, true); window.removeEventListener('keydown', onKeyDown, true);
window.removeEventListener('mousedown', onMouseDown, true);
window.removeEventListener('close-colorpicker-popup', hide, true); window.removeEventListener('close-colorpicker-popup', hide, true);
$root.removeEventListener('mouseleave', snooze); $root.removeEventListener('mouseleave', snooze);
$root.removeEventListener('mouseenter', stopSnoozing); $root.removeEventListener('mouseenter', stopSnoozing);
@ -940,6 +948,9 @@
realColor.g = Math.round(g * q1 + realColor.g * q2); realColor.g = Math.round(g * q1 + realColor.g * q2);
realColor.b = Math.round(b * q1 + realColor.b * q2); realColor.b = Math.round(b * q1 + realColor.b * q2);
realColor.a = mixedA; realColor.a = mixedA;
if (Math.abs(realColor.a - 1) < 1e-3) {
break;
}
} }
// https://www.w3.org/TR/AERT#color-contrast // https://www.w3.org/TR/AERT#color-contrast
const {r, g, b} = realColor; const {r, g, b} = realColor;

View File

@ -3181,6 +3181,17 @@ Parser.prototype = function() {
* @private * @private
*/ */
_unexpectedToken: function(token) { _unexpectedToken: function(token) {
for (let i = tokenStream._ltIndex - 1; i >= 0; i--) {
const {type, value} = tokenStream._lt[i];
if (type === tokenStream._tokenData.S) {
// NOP
} else if (type === tokenStream._tokenData.COMMENT &&
value[2] === '[' && value[3] === '[' && value.endsWith(']]*/')) {
return;
} else {
break;
}
}
throw new SyntaxError("Unexpected token '" + token.value + "' at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol); throw new SyntaxError("Unexpected token '" + token.value + "' at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol);
}, },
@ -7148,7 +7159,7 @@ TokenStreamBase.prototype = {
if (tokenTypes.includes(tt)) { if (tokenTypes.includes(tt)) {
return true; return true;
} }
} while (tt === 4 && this.LA(0) !== 0); } while (tt === this._tokenData.COMMENT && this.LA(0) !== 0);
//no match found, put the token back //no match found, put the token back
this.unget(); this.unget();
@ -10967,25 +10978,6 @@ CSSLint.addFormatter({
} }
}); });
if (!CSSLint.suppressUsoVarError) {
CSSLint.suppressUsoVarError = true;
parserlib.css.Tokens[parserlib.css.Tokens.COMMENT].hide = false;
const isUsoVar = ({value}) => value.startsWith('/*[[') && value.endsWith(']]*/');
CSSLint.addRule({
id: 'uso-vars',
init(parser, reporter) {
parser.addListener('error', function ({message, line, col}) {
if (!isUsoVar(this._tokenStream._token)) {
const {_lt, _ltIndex: i} = this._tokenStream;
if (i < 2 || !_lt.slice(0, i - 1).reverse().some(isUsoVar)) {
reporter.error(message, line, col);
}
}
});
},
});
}
self.onmessage = ({data: {action = 'run', code, config}}) => { self.onmessage = ({data: {action = 'run', code, config}}) => {
switch (action) { switch (action) {
@ -11000,8 +10992,6 @@ self.onmessage = ({data: {action = 'run', code, config}}) => {
return; return;
case 'run': case 'run':
Object.defineProperty(config, 'errors', {get: () => 0, set: () => 0});
config['uso-vars'] = 1;
self.postMessage(CSSLint.verify(code, config).messages.map(m => { self.postMessage(CSSLint.verify(code, config).messages.map(m => {
// the functions are non-tranferable and we need only an id // the functions are non-tranferable and we need only an id
m.rule = {id: m.rule.id}; m.rule = {id: m.rule.id};