This commit is contained in:
derv82 2017-12-06 22:21:59 -08:00
commit 0c4f4844b6
26 changed files with 364 additions and 210 deletions

View File

@ -183,6 +183,13 @@
"message": "Open color picker", "message": "Open color picker",
"description": "Tooltip for the colored squares shown before CSS colors in the style editor." "description": "Tooltip for the colored squares shown before CSS colors in the style editor."
}, },
"configOnChange": {
"message": "on change",
"description": "VERY SHORT label for the checkbox in style config dialog after the save button - when enabled the changes in the dialog are saved and applied automatically without the need to press the Save button"
},
"configOnChangeTooltip": {
"message": "Autosave and apply changes automatically"
},
"dysfunctional": { "dysfunctional": {
"message": "Stylus cannot function in private windows because Firefox disallows direct connection to the internal background page context of the extension.", "message": "Stylus cannot function in private windows because Firefox disallows direct connection to the internal background page context of the extension.",
"description": "Displayed in Firefox when its settings make Stylus dysfunctional" "description": "Displayed in Firefox when its settings make Stylus dysfunctional"

View File

@ -78,8 +78,8 @@ var usercssHelper = (() => {
); );
} }
function openInstallPage(tab, {url = tab.url, direct} = {}) { function openInstallPage(tab, {url = tab.url, direct, downloaded} = {}) {
if (direct) { if (direct && !downloaded) {
prefetchCodeForInstallation(tab.id, url); prefetchCodeForInstallation(tab.id, url);
} }
return wrapReject(openURL({ return wrapReject(openURL({

View File

@ -312,7 +312,7 @@
</summary> </summary>
<div></div> <div></div>
</details> </details>
<div id="footer"> <div id="footer" class="hidden">
<a href="https://github.com/openstyles/stylus/wiki/Usercss" <a href="https://github.com/openstyles/stylus/wiki/Usercss"
i18n-text="externalUsercssDocument" i18n-text="externalUsercssDocument"
target="_blank"></a> target="_blank"></a>

View File

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

View File

@ -126,6 +126,9 @@ h2 .svg-icon, label .svg-icon {
.svg-icon.settings:hover { .svg-icon.settings:hover {
fill: #000; fill: #000;
} }
#options span .svg-icon {
margin-top: -3px; /* inline info and config icons */
}
input:invalid { input:invalid {
background-color: rgba(255, 0, 0, 0.1); background-color: rgba(255, 0, 0, 0.1);
color: darkred; color: darkred;
@ -137,11 +140,6 @@ input:invalid {
#enabled-label { #enabled-label {
vertical-align: middle; vertical-align: middle;
} }
/* actions */
#actions > * {
margin-right: 0.5rem;
margin-bottom: 0.5rem;
}
/* collapsibles */ /* collapsibles */
#header summary { #header summary {
align-items: center; align-items: center;
@ -166,6 +164,11 @@ input:invalid {
#header summary svg { #header summary svg {
margin-top: -3px; margin-top: -3px;
} }
#actions {
margin-bottom: .5rem;
}
#options:not([open]) + #lint h2 { #options:not([open]) + #lint h2 {
margin-top: 0; margin-top: 0;
} }
@ -189,7 +192,8 @@ input:invalid {
opacity: .15; opacity: .15;
} }
/* footer */ /* footer */
#footer { .usercss #footer {
display: block;
margin-top: 1em; margin-top: 1em;
margin-bottom: .5em; margin-bottom: .5em;
} }
@ -504,7 +508,8 @@ html:not(.usercss) .applies-to li:last-child .add-applies-to {
/************ lint ************/ /************ lint ************/
#lint { #lint {
overflow-y: auto; overflow-y: auto;
overflow-x: hidden;} overflow-x: hidden;
}
#lint > summary { #lint > summary {
/* workaround for overflow:auto to show the toggle triangle */ /* workaround for overflow:auto to show the toggle triangle */
position: absolute; position: absolute;
@ -661,7 +666,7 @@ html:not(.usercss) .usercss-only,
} }
/************ reponsive layouts ************/ /************ reponsive layouts ************/
@media(max-width:737px) { @media(max-width: 850px) {
#header { #header {
width: auto; width: auto;
height: auto; height: auto;
@ -670,9 +675,11 @@ html:not(.usercss) .usercss-only,
border-bottom: 1px dashed #AAA; border-bottom: 1px dashed #AAA;
min-height: var(--header-narrow-min-height); min-height: var(--header-narrow-min-height);
max-height: 50vh; max-height: 50vh;
flex-wrap: wrap;
flex-direction: row;
} }
#header section:not(:last-child) { #header section:not(:last-child) {
margin-bottom: 0.4rem; margin-bottom: .5rem;
} }
#header input[type="checkbox"] { #header input[type="checkbox"] {
vertical-align: middle; vertical-align: middle;
@ -684,6 +691,8 @@ html:not(.usercss) .usercss-only,
#basic-info { #basic-info {
display: flex; display: flex;
align-items: baseline; align-items: baseline;
flex: 1;
margin-right: 2em;
} }
#basic-info > * { #basic-info > * {
flex: auto; flex: auto;
@ -700,15 +709,35 @@ html:not(.usercss) .usercss-only,
flex-grow: 99; flex-grow: 99;
} }
#actions { #actions {
margin-top: 1rem; margin-top: 0;
white-space: nowrap;
} }
#actions > * { #actions > * {
display: inline-block; display: inline-block;
} }
#options { #options {
-webkit-column-count: 2; -webkit-column-count: 3;
-moz-column-count: 2; -moz-column-count: 3;
column-count: 2; column-count: 3;
width: 100%;
}
#options:not([open]),
#lint:not([open]) {
column-count: 1;
overflow: initial;
}
#options:not([open]) + #lint:not([open]) {
margin-top: -1em;
}
#lint {
overflow: initial;
}
#lint summary {
position: static;
margin-bottom: 0;
}
#options summary {
margin-top: -.25em;
} }
#options h2 { #options h2 {
margin: 0 0 .5em; margin: 0 0 .5em;
@ -726,10 +755,10 @@ html:not(.usercss) .usercss-only,
top: 0.2rem; top: 0.2rem;
} }
#options:not([open]) ~ #lint { #options:not([open]) ~ #lint {
margin-top: -1ex; margin-top: -1em;
} }
#lint > div { #lint > div {
max-height: 20vh; margin-top: 0;
} }
#lint table { #lint table {
width: 100%; width: 100%;
@ -737,6 +766,9 @@ html:not(.usercss) .usercss-only,
#lint td[role="message"] { #lint td[role="message"] {
max-width: none; max-width: none;
} }
#lint:not([open]) + #footer {
margin: .25em 0 -1em .25em;
}
#sections { #sections {
padding-left: 0; padding-left: 0;
} }
@ -746,9 +778,6 @@ html:not(.usercss) .usercss-only,
#sections > *:not(h2) { #sections > *:not(h2) {
padding-left: 0.4rem; padding-left: 0.4rem;
} }
.applies-type {
width: 30%;
}
.usercss .CodeMirror-scroll { .usercss .CodeMirror-scroll {
max-height: calc(100vh - var(--header-narrow-min-height)); max-height: calc(100vh - var(--header-narrow-min-height));
} }
@ -760,13 +789,29 @@ html:not(.usercss) .usercss-only,
left: 3rem; left: 3rem;
} }
} }
@media(max-width:500px) {
@media (max-width: 720px) {
#options {
-webkit-column-count: 2;
-moz-column-count: 2;
column-count: 2;
}
}
@media (max-width: 450px) {
#options { #options {
-webkit-column-count: 1; -webkit-column-count: 1;
-moz-column-count: 1; -moz-column-count: 1;
column-count: 1; column-count: 1;
} }
#options #tabSize-label { #actions {
position: static; flex-wrap: wrap;
white-space: normal;
}
}
@supports (-moz-appearance: none) {
#header button {
padding: 0 3px 2px;
} }
} }

View File

@ -445,12 +445,11 @@ function setupLinterPopup(config) {
function makeFooter() { function makeFooter() {
return $create('div', [ return $create('div', [
$create('p', [ $create('p', [
t('linterRulesLink') + ' ',
$createLink( $createLink(
linter === 'stylelint' linter === 'stylelint'
? 'https://stylelint.io/user-guide/rules/' ? 'https://stylelint.io/user-guide/rules/'
: 'https://github.com/CSSLint/csslint/wiki/Rules-by-ID', : 'https://github.com/CSSLint/csslint/wiki/Rules-by-ID',
linterTitle), t('linterRulesLink')),
linter === 'csslint' ? ' ' + t('linterCSSLintSettings') : '', linter === 'csslint' ? ' ' + t('linterCSSLintSettings') : '',
]), ]),
$create('button.save', {onclick: save, title: 'Ctrl-Enter'}, t('styleSaveLabel')), $create('button.save', {onclick: save, title: 'Ctrl-Enter'}, t('styleSaveLabel')),

View File

@ -129,6 +129,8 @@ function createSourceEditor(style) {
style.enabled = value; style.enabled = value;
}; };
$('#header').addEventListener('wheel', headerOnScroll, {passive: true});
cm.on('changes', () => { cm.on('changes', () => {
dirty.modify('sourceGeneration', savedGeneration, cm.changeGeneration()); dirty.modify('sourceGeneration', savedGeneration, cm.changeGeneration());
updateLintReportIfEnabled(cm); updateLintReportIfEnabled(cm);
@ -333,6 +335,20 @@ function createSourceEditor(style) {
} }
} }
function headerOnScroll({deltaY, deltaMode, shiftKey}) {
if (deltaY < 0 && this.scrollTop ||
deltaY > 0 && this.scrollTop + this.clientHeight < this.scrollHeight) {
return;
}
cm.display.scroller.scrollTop +=
// WheelEvent.DOM_DELTA_LINE
deltaMode === 1 ? deltaY * cm.display.cachedTextHeight :
// WheelEvent.DOM_DELTA_PAGE
deltaMode === 2 || shiftKey ? Math.sign(deltaY) * cm.display.scroller.clientHeight :
// WheelEvent.DOM_DELTA_PIXEL
deltaY;
}
return { return {
replaceStyle, replaceStyle,
save, save,

View File

@ -102,12 +102,7 @@ select {
-moz-appearance: checkbox !important; -moz-appearance: checkbox !important;
} }
.moz-appearance-bug button { ::-moz-focus-inner {
padding-left: .75ex;
padding-right: .75ex;
}
::-moz-focus-inner {
border: 0; border: 0;
} }

View File

@ -64,6 +64,7 @@
<input type="checkbox"> <input 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>
<span></span> <span></span>
<p></p>
</label> </label>
<label class="live-reload"> <label class="live-reload">
<input type="checkbox"> <input type="checkbox">

View File

@ -193,6 +193,17 @@ h2.installed.active {
min-width: 0; min-width: 0;
} }
.set-update-url {
flex-wrap: wrap;
}
.set-update-url p {
word-break: break-all;
opacity: .5;
width: 100%;
margin: .25em 0 .25em;
}
.external { .external {
text-align: center; text-align: center;
} }

View File

@ -283,24 +283,28 @@
}; };
// set updateUrl // set updateUrl
const setUpdate = $('.set-update-url input[type=checkbox]'); const checker = $('.set-update-url input[type=checkbox]');
const updateUrl = new URL(params.get('updateUrl')); // prefer the installation URL unless drag'n'dropped on the manage page
const installationUrl = (params.get('updateUrl') || '').replace(/^blob.+/, '');
const updateUrl = new URL(installationUrl || style.updateUrl || 'foo:bar');
$('.set-update-url > span').textContent = t('installUpdateFromLabel'); $('.set-update-url > span').textContent = t('installUpdateFromLabel');
if (dup && dup.updateUrl === updateUrl.href) { if (dup && dup.updateUrl === updateUrl.href) {
setUpdate.checked = true; checker.checked = true;
// there is no way to "unset" updateUrl, you can only overwrite it. // there is no way to "unset" updateUrl, you can only overwrite it.
setUpdate.disabled = true; checker.disabled = true;
} else if (updateUrl.protocol === 'foo:') {
// drag'n'dropped on the manage page and the style doesn't have @updateURL
checker.disabled = true;
} else if (updateUrl.protocol !== 'file:') { } else if (updateUrl.protocol !== 'file:') {
setUpdate.checked = true; checker.checked = true;
style.updateUrl = updateUrl.href; style.updateUrl = updateUrl.href;
} }
setUpdate.onchange = e => { checker.onchange = () => {
if (e.target.checked) { style.updateUrl = checker.checked ? updateUrl.href : null;
style.updateUrl = updateUrl.href;
} else {
delete style.updateUrl;
}
}; };
checker.onchange();
$('.set-update-url p').textContent = updateUrl.href.length < 300 ? updateUrl.href :
updateUrl.href.slice(0, 300) + '...';
if (!port) { if (!port) {
return; return;

View File

@ -61,8 +61,13 @@ if (BG && !BG.getStyles && BG !== window) {
BG = null; BG = null;
} }
if (!BG || BG !== window) { if (!BG || BG !== window) {
document.documentElement.classList.toggle('firefox', FIREFOX); if (FIREFOX) {
document.documentElement.classList.toggle('opera', OPERA); document.documentElement.classList.add('firefox');
} else if (OPERA) {
document.documentElement.classList.add('opera');
} else if (chrome.app && navigator.userAgent.includes('Vivaldi')) {
document.documentElement.classList.add('vivaldi');
}
// TODO: remove once our manifest's minimum_chrome_version is 50+ // TODO: remove once our manifest's minimum_chrome_version is 50+
// Chrome 49 doesn't report own extension pages in webNavigation apparently // Chrome 49 doesn't report own extension pages in webNavigation apparently
if (CHROME && CHROME < 2661) { if (CHROME && CHROME < 2661) {

View File

@ -11,6 +11,9 @@ var prefs = new function Prefs() {
'exposeIframes': false, // Add 'stylus-iframe' attribute to HTML element in all iframes 'exposeIframes': false, // Add 'stylus-iframe' attribute to HTML element in all iframes
'newStyleAsUsercss': false, // create new style in usercss format 'newStyleAsUsercss': false, // create new style in usercss format
// checkbox in style config dialog
'config.autosave': true,
'popup.breadcrumbs': true, // display 'New style' links as URL breadcrumbs 'popup.breadcrumbs': true, // display 'New style' links as URL breadcrumbs
'popup.breadcrumbs.usePath': false, // use URL path for 'this URL' 'popup.breadcrumbs.usePath': false, // use URL path for 'this URL'
'popup.enabledFirst': true, // display enabled styles before disabled styles 'popup.enabledFirst': true, // display enabled styles before disabled styles

View File

@ -3,25 +3,34 @@
// eslint-disable-next-line no-var // eslint-disable-next-line no-var
var usercss = (() => { var usercss = (() => {
// true for global, false for private // true = global
const METAS = { // false or 0 = private
__proto__: null, // <string> = global key name
author: true, // <function> = (style, newValue)
advanced: false, const KNOWN_META = new Map([
description: true, ['author', true],
homepageURL: false, ['advanced', 0],
// icon: false, ['description', true],
license: false, ['homepageURL', 'url'],
name: true, ['icon', 0],
namespace: false, ['license', 0],
// noframes: false, ['name', true],
preprocessor: false, ['namespace', 0],
supportURL: false, //['noframes', 0],
'var': false, ['preprocessor', 0],
version: false ['supportURL', 0],
}; ['updateURL', (style, newValue) => {
// always preserve locally installed style's updateUrl
if (!/^file:/.test(style.updateUrl)) {
style.updateUrl = newValue;
}
}],
['var', 0],
['version', 0],
]);
const MANDATORY_META = ['name', 'namespace', 'version'];
const META_VARS = ['text', 'color', 'checkbox', 'select', 'dropdown', 'image']; const META_VARS = ['text', 'color', 'checkbox', 'select', 'dropdown', 'image'];
const META_URLS = [...KNOWN_META.keys()].filter(k => k.endsWith('URL'));
const BUILDER = { const BUILDER = {
default: { default: {
@ -221,7 +230,7 @@ var usercss = (() => {
} }
} }
state.usercssData.vars[result.name] = result; state.usercssData.vars[result.name] = result;
validVar(result); validateVar(result);
} }
function createOption(label, value) { function createOption(label, value) {
@ -407,28 +416,39 @@ var usercss = (() => {
function doParse() { function doParse() {
let match; let match;
while ((match = re.exec(text))) { while ((match = re.exec(text))) {
state.key = match[1]; const key = state.key = match[1];
if (!(state.key in METAS)) { const route = KNOWN_META.get(key);
if (route === undefined) {
continue; continue;
} }
if (state.key === 'var' || state.key === 'advanced') { if (key === 'var' || key === 'advanced') {
if (state.key === 'advanced') { if (key === 'advanced') {
state.maybeUSO = true; state.maybeUSO = true;
} }
parseVar(state); parseVar(state);
} else { } else {
parseStringToEnd(state); parseStringToEnd(state);
usercssData[state.key] = state.value; usercssData[key] = state.value;
} }
if (state.key === 'version') { let value = state.value;
usercssData[state.key] = normalizeVersion(usercssData[state.key]); if (key === 'version') {
validVersion(usercssData[state.key]); value = usercssData[key] = normalizeVersion(value);
validateVersion(value);
} }
if (METAS[state.key]) { if (META_URLS.includes(key)) {
style[state.key] = usercssData[state.key]; validateUrl(key, value);
} }
if (state.key === 'homepageURL' || state.key === 'supportURL') { switch (typeof route) {
validUrl(usercssData[state.key]); case 'function':
route(style, value);
break;
case 'string':
style[route] = value;
break;
default:
if (route) {
style[key] = value;
}
} }
} }
} }
@ -446,12 +466,8 @@ var usercss = (() => {
if (state.maybeUSO && !usercssData.preprocessor) { if (state.maybeUSO && !usercssData.preprocessor) {
usercssData.preprocessor = 'uso'; usercssData.preprocessor = 'uso';
} }
if (usercssData.homepageURL) {
style.url = usercssData.homepageURL;
}
validate(style);
validateStyle(style);
return style; return style;
} }
@ -505,42 +521,32 @@ var usercss = (() => {
return va[prop]; return va[prop];
} }
function validate(style) { function validateStyle({usercssData: data}) {
const {usercssData: data} = style; for (const prop of MANDATORY_META) {
// mandatory fields
for (const prop of ['name', 'namespace', 'version']) {
if (!data[prop]) { if (!data[prop]) {
throw new Error(chrome.i18n.getMessage('styleMissingMeta', prop)); throw new Error(chrome.i18n.getMessage('styleMissingMeta', prop));
} }
} }
// validate version validateVersion(data.version);
validVersion(data.version); META_URLS.forEach(k => validateUrl(k, data[k]));
Object.keys(data.vars).forEach(k => validateVar(data.vars[k]));
// validate URLs
validUrl(data.homepageURL);
validUrl(data.supportURL);
// validate vars
for (const key of Object.keys(data.vars)) {
validVar(data.vars[key]);
}
} }
function validVersion(version) { function validateVersion(version) {
semverCompare(version, '0.0.0'); semverCompare(version, '0.0.0');
} }
function validUrl(url) { function validateUrl(key, url) {
if (!url) { if (!url) {
return; return;
} }
url = new URL(url); url = new URL(url);
if (url.protocol !== 'http:' && url.protocol !== 'https:') { if (!/^https?:/.test(url.protocol)) {
throw new Error(`${url.protocol} is not a valid protocol`); throw new Error(`${url.protocol} is not a valid protocol in ${key}`);
} }
} }
function validVar(va, value = 'default') { function validateVar(va, value = 'default') {
if (va.type === 'select' || va.type === 'dropdown') { if (va.type === 'select' || va.type === 'dropdown') {
if (va.options.every(o => o.name !== va[value])) { if (va.options.every(o => o.name !== va[value])) {
throw new Error(chrome.i18n.getMessage('styleMetaErrorSelectValueMismatch')); throw new Error(chrome.i18n.getMessage('styleMetaErrorSelectValueMismatch'));
@ -560,7 +566,7 @@ var usercss = (() => {
if (oldVars[key] && oldVars[key].value) { if (oldVars[key] && oldVars[key].value) {
vars[key].value = oldVars[key].value; vars[key].value = oldVars[key].value;
try { try {
validVar(vars[key], 'value'); validateVar(vars[key], 'value');
} catch (e) { } catch (e) {
vars[key].value = null; vars[key].value = null;
} }
@ -573,7 +579,7 @@ var usercss = (() => {
worker.instance = new Worker('/vendor-overwrites/csslint/csslint-worker.js'); worker.instance = new Worker('/vendor-overwrites/csslint/csslint-worker.js');
worker.queue = []; worker.queue = [];
worker.instance.onmessage = ({data}) => { worker.instance.onmessage = ({data}) => {
worker.queue.shift().resolve(data); worker.queue.shift().resolve(data.__ERROR__ ? Promise.reject(data.__ERROR__) : data);
if (worker.queue.length) { if (worker.queue.length) {
worker.instance.postMessage(worker.queue[0].message); worker.instance.postMessage(worker.queue[0].message);
} }

View File

@ -93,9 +93,7 @@
<template data-id="configureIcon"> <template data-id="configureIcon">
<span class="configure-usercss" i18n-title="configureStyle"> <span class="configure-usercss" i18n-title="configureStyle">
<svg class="svg-icon configure" viewBox="0 0 16 16"> <svg class="svg-icon config"><use xlink:href="#svg-icon-config"></use></svg>
<path d="M8,0C7.6,0,7.3,0,6.9,0.1v2.2C6.1,2.5,5.4,2.8,4.8,3.2L3.2,1.6c-0.6,0.4-1.1,1-1.6,1.6l1.6,1.6C2.8,5.4,2.5,6.1,2.3,6.9H0.1C0,7.3,0,7.6,0,8c0,0.4,0,0.7,0.1,1.1h2.2c0.1,0.8,0.4,1.5,0.9,2.1l-1.6,1.6c0.4,0.6,1,1.1,1.6,1.6l1.6-1.6c0.6,0.4,1.4,0.7,2.1,0.9v2.2C7.3,16,7.6,16,8,16c0.4,0,0.7,0,1.1-0.1v-2.2c0.8-0.1,1.5-0.4,2.1-0.9l1.6,1.6c0.6-0.4,1.1-1,1.6-1.6l-1.6-1.6c0.4-0.6,0.7-1.4,0.9-2.1h2.2C16,8.7,16,8.4,16,8c0-0.4,0-0.7-0.1-1.1h-2.2c-0.1-0.8-0.4-1.5-0.9-2.1l1.6-1.6c-0.4-0.6-1-1.1-1.6-1.6l-1.6,1.6c-0.6-0.4-1.4-0.7-2.1-0.9V0.1C8.7,0,8.4,0,8,0z M8,4.3c2.1,0,3.7,1.7,3.7,3.7c0,0,0,0,0,0c0,2.1-1.7,3.7-3.7,3.7c0,0,0,0,0,0c-2.1,0-3.7-1.7-3.7-3.7c0,0,0,0,0,0C4.3,5.9,5.9,4.3,8,4.3C8,4.3,8,4.3,8,4.3z"></path>
</svg>
</span> </span>
</template> </template>
@ -229,7 +227,7 @@
</div> </div>
</div> </div>
<label id="onlyUpdates" class="hidden"> <label id="only-updates" class="hidden">
<input type="checkbox" <input type="checkbox"
data-filter=".can-update, .update-problem, .update-done" data-filter=".can-update, .update-problem, .update-done"
data-filter-hide=":not(.updatable):not(.update-done), .no-update:not(.update-problem)"> data-filter-hide=":not(.updatable):not(.update-done), .no-update:not(.update-problem)">
@ -356,6 +354,10 @@
<title i18n-text="helpAlt"></title> <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> <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> </symbol>
<symbol id="svg-icon-config" viewBox="0 0 14 14">
<path d="M6.2,0C5.8,0,5.4,0.4,5.4,0.8v0.7C5,1.7,4.6,1.8,4.3,2L3.8,1.5C3.6,1.4,3.4,1.3,3.2,1.3S2.7,1.4,2.6,1.5L1.5,2.6c-0.3,0.3-0.3,0.9,0,1.2L2,4.3C1.8,4.6,1.7,5,1.5,5.4H0.8C0.4,5.4,0,5.8,0,6.2v1.5c0,0.5,0.4,0.8,0.8,0.8h0.7C1.7,9,1.8,9.4,2,9.7l-0.5,0.5c-0.3,0.3-0.3,0.8,0,1.2l1.1,1.1c0.3,0.3,0.9,0.3,1.2,0L4.3,12c0.4,0.2,0.8,0.4,1.2,0.5v0.7c0,0.5,0.4,0.8,0.8,0.8h1.5c0.5,0,0.8-0.4,0.8-0.8v-0.7C9,12.3,9.4,12.2,9.7,12l0.5,0.5c0.3,0.3,0.9,0.3,1.2,0l1.1-1.1c0.3-0.3,0.3-0.8,0-1.2L12,9.7c0.2-0.4,0.4-0.8,0.5-1.2h0.7c0.5,0,0.8-0.4,0.8-0.8V6.2c0-0.5-0.4-0.8-0.8-0.8h-0.7C12.3,5,12.2,4.6,12,4.3l0.5-0.5c0.3-0.3,0.3-0.9,0-1.2l-1.1-1.1c-0.2-0.2-0.4-0.2-0.6-0.2s-0.4,0.1-0.6,0.2L9.7,2C9.4,1.8,9,1.7,8.6,1.5V0.8C8.6,0.4,8.2,0,7.8,0L6.2,0z M6.8,0.8h0.4c0.2,0,0.4,0.2,0.4,0.4v1.2c0.8,0.1,1.6,0.4,2.3,0.9l0.8-0.8c0.2-0.2,0.4-0.2,0.6,0l0.3,0.3c0.2,0.2,0.2,0.4,0,0.6l-0.8,0.8c0.5,0.7,0.8,1.4,0.9,2.3h1.2c0.2,0,0.4,0.2,0.4,0.4v0.4c0,0.2-0.2,0.4-0.4,0.4h-1.2c-0.1,0.8-0.4,1.6-0.9,2.3l0.8,0.8c0.2,0.2,0.2,0.4,0,0.6l-0.3,0.3c-0.2,0.2-0.4,0.2-0.6,0l-0.8-0.8c-0.7,0.5-1.4,0.8-2.3,0.9v1.2c0,0.2-0.2,0.4-0.4,0.4H6.8c-0.2,0-0.4-0.2-0.4-0.4v-1.2c-0.8-0.1-1.6-0.4-2.3-0.9l-0.8,0.8c-0.2,0.2-0.4,0.2-0.6,0l-0.3-0.3c-0.2-0.2-0.2-0.4,0-0.6l0.8-0.8C2.8,9.2,2.5,8.4,2.4,7.6H1.2C1,7.6,0.8,7.4,0.8,7.2V6.8c0-0.2,0.2-0.4,0.4-0.4h1.2c0.1-0.8,0.4-1.6,0.9-2.3L2.5,3.3c-0.2-0.2-0.2-0.4,0-0.6l0.3-0.3c0.2-0.2,0.4-0.2,0.6,0l0.8,0.8c0.7-0.5,1.4-0.8,2.3-0.9V1.2C6.4,1,6.6,0.8,6.8,0.8L6.8,0.8z M7,3.6C5.1,3.6,3.6,5.1,3.6,7c0,0,0,0,0,0c0,1.9,1.5,3.4,3.4,3.4c1.9,0,3.4-1.5,3.4-3.4C10.4,5.1,8.9,3.6,7,3.6C7,3.6,7,3.6,7,3.6z M7,4.8c1.2,0,2.2,1,2.2,2.2c0,1.2-1,2.2-2.2,2.2c-1.2,0-2.2-1-2.2-2.2C4.8,5.8,5.8,4.8,7,4.8z"/>
</symbol>
</svg> </svg>
</body> </body>

View File

@ -1,11 +1,23 @@
/* config dialog */ #stylus-popup #message-box-contents {
.config-dialog .config-heading { padding: .25rem .75rem;
}
#stylus-popup .config-body label {
padding: .5em 0;
}
#stylus-popup .config-body label > :first-child {
max-width: 140px;
min-width: 140px;
}
.config-heading {
float: right; float: right;
margin: -1.25rem 0 0 0; margin: -1.25rem 0 0 0;
font-size: 0.9em; font-size: 0.9em;
} }
.config-dialog label { .config-body label {
display: flex; display: flex;
padding: .75em 0; padding: .75em 0;
align-items: center; align-items: center;
@ -15,32 +27,27 @@
position: static; position: static;
} }
.config-dialog label:first-child { .config-body label:first-child {
padding-top: 0; padding-top: 0;
} }
.config-dialog label:last-child { .config-body label:last-child {
padding-bottom: 0; padding-bottom: 0;
} }
.config-dialog label:not(:first-child) { .config-body label:not(:first-child) {
border-top: 1px dotted #ccc; border-top: 1px dotted #ccc;
} }
.config-dialog label > :first-child { .config-body label > :first-child {
margin-right: 8px; margin-right: 8px;
flex-grow: 1; flex-grow: 1;
} }
.config-dialog label:not([disabled]) > :first-child { .config-body label:not([disabled]) > :first-child {
cursor: default; 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 { .config-dialog .dirty:after {
content: "*"; content: "*";
position: absolute; position: absolute;
@ -51,18 +58,19 @@
font-style: italic; font-style: italic;
} }
.config-dialog input, .config-body input,
.config-dialog select, .config-body select,
.config-dialog .onoffswitch { .config-body .onoffswitch {
width: var(--onoffswitch-width); width: var(--onoffswitch-width);
margin: 0; margin: 0;
height: 2em; height: 22px;
box-sizing: border-box; box-sizing: border-box;
vertical-align: middle; vertical-align: middle;
} }
.config-dialog .select-resizer, .config-body input[type="text"],
.config-dialog select { .config-body .select-resizer,
.config-body select {
width: auto; width: auto;
min-width: var(--onoffswitch-width); min-width: var(--onoffswitch-width);
max-width: 124px; max-width: 124px;
@ -70,24 +78,30 @@
position: relative; position: relative;
} }
.config-dialog .onoffswitch { .config-body .onoffswitch {
height: auto; height: auto;
margin: calc((2em - 12px) / 2) 0; margin: calc((2em - 12px) / 2) 0;
} }
.config-dialog input[type="text"] { .config-body input[type="text"] {
padding-left: 0.25em; padding-left: 0.25em;
} }
.config-dialog label > :last-child { .config-body label > :last-child {
box-sizing: border-box; box-sizing: border-box;
flex-shrink: 0; flex-shrink: 0;
} }
.config-dialog label > :last-child:not(.onoffswitch):not(.select-resizer) > :not(:last-child) { .config-body label > :last-child:not(.onoffswitch):not(.select-resizer) > :not(:last-child) {
margin-right: 4px; margin-right: 4px;
} }
#config-autosave-wrapper {
position: relative;
padding: 0 0 0 16px;
display: inline-flex;
}
.cm-colorview::before, .cm-colorview::before,
.color-swatch { .color-swatch {
width: var(--onoffswitch-width) !important; width: var(--onoffswitch-width) !important;

View File

@ -30,11 +30,7 @@ function configDialog(style) {
{textContent: t('confirmClose'), dataset: {cmd: 'close'}}, {textContent: t('confirmClose'), dataset: {cmd: 'close'}},
], ],
onshow, onshow,
}).then(() => { }).then(onhide);
document.body.style.minWidth = '';
document.body.style.minHeight = '';
colorpicker.hide();
});
function getInitialValues(source) { function getInitialValues(source) {
const data = {}; const data = {};
@ -46,10 +42,22 @@ function configDialog(style) {
} }
function onshow(box) { function onshow(box) {
$('button', box).insertAdjacentElement('afterend',
$create('label#config-autosave-wrapper', {
title: t('configOnChangeTooltip'),
}, [
$create('input', {id: 'config.autosave', type: 'checkbox'}),
$create('SVG:svg.svg-icon.checked',
$create('SVG:use', {'xlink:href': '#svg-icon-checked'})),
t('configOnChange'),
]));
setupLivePrefs(['config.autosave']);
if (isPopup) { if (isPopup) {
adjustSizeForPopup(box); adjustSizeForPopup(box);
box.style.animationDuration = '0s'; box.style.animationDuration = '0s';
} }
box.addEventListener('change', onchange); box.addEventListener('change', onchange);
buttons.save = $('[data-cmd="save"]', box); buttons.save = $('[data-cmd="save"]', box);
buttons.default = $('[data-cmd="default"]', box); buttons.default = $('[data-cmd="default"]', box);
@ -57,13 +65,23 @@ function configDialog(style) {
updateButtons(); updateButtons();
} }
function onhide() {
document.body.style.minWidth = '';
document.body.style.minHeight = '';
colorpicker.hide();
}
function onchange({target}) { function onchange({target}) {
// invoked after element's own onchange so 'va' contains the updated value // invoked after element's own onchange so 'va' contains the updated value
const va = target.va; const va = target.va;
if (va) { if (va) {
va.dirty = varsInitial[va.name] !== (isDefault(va) ? va.default : va.value); va.dirty = varsInitial[va.name] !== (isDefault(va) ? va.default : va.value);
target.closest('label').classList.toggle('dirty', va.dirty); if (prefs.get('config.autosave')) {
updateButtons(); debounce(save);
} else {
target.closest('label').classList.toggle('dirty', va.dirty);
updateButtons();
}
} }
} }
@ -113,6 +131,7 @@ function configDialog(style) {
} }
} }
if (invalid.length) { if (invalid.length) {
onhide();
messageBox.alert([ messageBox.alert([
$create('div', {style: 'max-width: 34em'}, t('usercssConfigIncomplete')), $create('div', {style: 'max-width: 34em'}, t('usercssConfigIncomplete')),
$create('ol', {style: 'text-align: left'}, $create('ol', {style: 'text-align: left'},
@ -120,11 +139,16 @@ function configDialog(style) {
$create({tag: 'li', appendChild: msg}))), $create({tag: 'li', appendChild: msg}))),
]); ]);
} }
return numValid && BG.usercssHelper.save(style).then(saved => { if (!numValid) {
varsInitial = getInitialValues(deepCopy(saved.usercssData.vars)); return;
vars.forEach(va => onchange({target: va.input})); }
updateButtons(); return BG.usercssHelper.save(style)
}); .then(saved => {
varsInitial = getInitialValues(deepCopy(saved.usercssData.vars));
vars.forEach(va => onchange({target: va.input}));
updateButtons();
})
.catch(errors => onhide() + messageBox.alert(Array.isArray(errors) ? errors.join('\n') : errors));
} }
function useDefault() { function useDefault() {
@ -256,10 +280,10 @@ function configDialog(style) {
} }
function adjustSizeForPopup(box) { function adjustSizeForPopup(box) {
box.style = 'white-space: nowrap !important'; const contents = box.firstElementChild;
box.firstElementChild.style = 'max-width: none; max-height: none;'.replace(/;/g, '!important;'); contents.style = 'max-width: none; max-height: none;'.replace(/;/g, '!important;');
const {offsetWidth, offsetHeight} = box.firstElementChild; let {offsetWidth: width, offsetHeight: height} = contents;
box.style = box.firstElementChild.style = ''; contents.style = '';
const colorpicker = document.body.appendChild( const colorpicker = document.body.appendChild(
$create('.colorpicker-popup', {style: 'display: none!important'})); $create('.colorpicker-popup', {style: 'display: none!important'}));
@ -267,8 +291,8 @@ function configDialog(style) {
const MIN_HEIGHT = 250; const MIN_HEIGHT = 250;
colorpicker.remove(); colorpicker.remove();
const width = Math.max(Math.min(offsetWidth / 0.9 + 2, 800), MIN_WIDTH); width = Math.max(Math.min(width / 0.9 + 2, 800), MIN_WIDTH);
const height = Math.max(Math.min(offsetHeight / 0.9 + 2, 600), MIN_HEIGHT); height = Math.max(Math.min(height / 0.9 + 2, 600), MIN_HEIGHT);
document.body.style.setProperty('min-width', width + 'px', 'important'); document.body.style.setProperty('min-width', width + 'px', 'important');
document.body.style.setProperty('min-height', height + 'px', 'important'); document.body.style.setProperty('min-height', height + 'px', 'important');
} }

View File

@ -392,8 +392,8 @@ Object.assign(document.body, {
this.ondragend(); this.ondragend();
if (event.dataTransfer.files.length) { if (event.dataTransfer.files.length) {
event.preventDefault(); event.preventDefault();
if ($('#onlyUpdates input').checked) { if ($('#only-updates input').checked) {
$('#onlyUpdates input').click(); $('#only-updates input').click();
} }
importFromFile({file: event.dataTransfer.files[0]}); importFromFile({file: event.dataTransfer.files[0]});
} }

View File

@ -75,9 +75,9 @@ onDOMready().then(() => {
event.target.matches('[type="text"], [type="search"]')) { event.target.matches('[type="text"], [type="search"]')) {
return; return;
} }
const k = event.which; const {which: k, key} = event;
// focus search field on "/" key // focus search field on "/" key
if (k === 191 && !event.shiftKey) { if (key === '/' || !key && k === 191 && !event.shiftKey) {
event.preventDefault(); event.preventDefault();
$('#search').focus(); $('#search').focus();
return; return;

View File

@ -139,7 +139,7 @@ select {
margin-left: .5ex; margin-left: .5ex;
} }
.svg-icon.configure { .svg-icon.config {
width: 16px; width: 16px;
height: 16px; height: 16px;
} }
@ -392,6 +392,13 @@ select {
background-color: hsla(0, 0%, 50%, .2); background-color: hsla(0, 0%, 50%, .2);
} }
#only-updates {
position: relative;
left: -5px;
margin-top: 2px;
margin-bottom: 2px;
}
.checkmate { .checkmate {
position: relative; position: relative;
height: 12px; height: 12px;

View File

@ -108,7 +108,7 @@ function reportUpdateState(state, style, details) {
entry.classList.add('can-update'); entry.classList.add('can-update');
entry.updatedCode = style; entry.updatedCode = style;
$('.update-note', entry).textContent = ''; $('.update-note', entry).textContent = '';
$('#onlyUpdates').classList.remove('hidden'); $('#only-updates').classList.remove('hidden');
break; break;
case BG.updater.SKIPPED: { case BG.updater.SKIPPED: {
if (entry.classList.contains('can-update')) { if (entry.classList.contains('can-update')) {
@ -151,12 +151,12 @@ function reportUpdateState(state, style, details) {
function renderUpdatesOnlyFilter({show, check} = {}) { function renderUpdatesOnlyFilter({show, check} = {}) {
const numUpdatable = $$('.can-update').length; const numUpdatable = $$('.can-update').length;
const mightUpdate = numUpdatable > 0 || $('.update-problem'); const mightUpdate = numUpdatable > 0 || $('.update-problem');
const checkbox = $('#onlyUpdates input'); const checkbox = $('#only-updates input');
show = show !== undefined ? show : mightUpdate; show = show !== undefined ? show : mightUpdate;
check = check !== undefined ? show && check : checkbox.checked && mightUpdate; check = check !== undefined ? show && check : checkbox.checked && mightUpdate;
$('#onlyUpdates').classList.toggle('hidden', !show); $('#only-updates').classList.toggle('hidden', !show);
checkbox.checked = check; checkbox.checked = check && show;
checkbox.dispatchEvent(new Event('change')); checkbox.dispatchEvent(new Event('change'));
const btnApply = $('#apply-all-updates'); const btnApply = $('#apply-all-updates');

View File

@ -33,9 +33,7 @@
<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"> <a href="#" class="configure" i18n-title="configureStyle">
<svg class="svg-icon config" viewBox="0 0 14 16"> <svg class="svg-icon config"><use xlink:href="#svg-icon-config"></use></svg>
<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>
<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">
@ -221,6 +219,18 @@
<symbol id="svg-icon-checked" viewBox="0 0 1000 1000"> <symbol id="svg-icon-checked" viewBox="0 0 1000 1000">
<path fill-rule="evenodd" d="M983.2,184.3L853,69.8c-4-3.5-9.3-5.3-14.5-5c-5.3,0.4-10.3,2.8-13.8,6.8L352.3,609.2L184.4,386.9c-3.2-4.2-8-7-13.2-7.8c-5.3-0.8-10.6,0.6-14.9,3.9L18,487.5c-8.8,6.7-10.6,19.3-3.9,28.1L325,927.2c3.6,4.8,9.3,7.7,15.3,8c0.2,0,0.5,0,0.7,0c5.8,0,11.3-2.5,15.1-6.8L985,212.6C992.3,204.3,991.5,191.6,983.2,184.3z"/> <path fill-rule="evenodd" d="M983.2,184.3L853,69.8c-4-3.5-9.3-5.3-14.5-5c-5.3,0.4-10.3,2.8-13.8,6.8L352.3,609.2L184.4,386.9c-3.2-4.2-8-7-13.2-7.8c-5.3-0.8-10.6,0.6-14.9,3.9L18,487.5c-8.8,6.7-10.6,19.3-3.9,28.1L325,927.2c3.6,4.8,9.3,7.7,15.3,8c0.2,0,0.5,0,0.7,0c5.8,0,11.3-2.5,15.1-6.8L985,212.6C992.3,204.3,991.5,191.6,983.2,184.3z"/>
</symbol> </symbol>
<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"/>
</symbol>
<symbol id="svg-icon-config" viewBox="0 0 14 14">
<path d="M6.2,0C5.8,0,5.4,0.4,5.4,0.8v0.7C5,1.7,4.6,1.8,4.3,2L3.8,1.5C3.6,1.4,3.4,1.3,3.2,1.3S2.7,1.4,2.6,1.5L1.5,2.6c-0.3,0.3-0.3,0.9,0,1.2L2,4.3C1.8,4.6,1.7,5,1.5,5.4H0.8C0.4,5.4,0,5.8,0,6.2v1.5c0,0.5,0.4,0.8,0.8,0.8h0.7C1.7,9,1.8,9.4,2,9.7l-0.5,0.5c-0.3,0.3-0.3,0.8,0,1.2l1.1,1.1c0.3,0.3,0.9,0.3,1.2,0L4.3,12c0.4,0.2,0.8,0.4,1.2,0.5v0.7c0,0.5,0.4,0.8,0.8,0.8h1.5c0.5,0,0.8-0.4,0.8-0.8v-0.7C9,12.3,9.4,12.2,9.7,12l0.5,0.5c0.3,0.3,0.9,0.3,1.2,0l1.1-1.1c0.3-0.3,0.3-0.8,0-1.2L12,9.7c0.2-0.4,0.4-0.8,0.5-1.2h0.7c0.5,0,0.8-0.4,0.8-0.8V6.2c0-0.5-0.4-0.8-0.8-0.8h-0.7C12.3,5,12.2,4.6,12,4.3l0.5-0.5c0.3-0.3,0.3-0.9,0-1.2l-1.1-1.1c-0.2-0.2-0.4-0.2-0.6-0.2s-0.4,0.1-0.6,0.2L9.7,2C9.4,1.8,9,1.7,8.6,1.5V0.8C8.6,0.4,8.2,0,7.8,0L6.2,0z M6.8,0.8h0.4c0.2,0,0.4,0.2,0.4,0.4v1.2c0.8,0.1,1.6,0.4,2.3,0.9l0.8-0.8c0.2-0.2,0.4-0.2,0.6,0l0.3,0.3c0.2,0.2,0.2,0.4,0,0.6l-0.8,0.8c0.5,0.7,0.8,1.4,0.9,2.3h1.2c0.2,0,0.4,0.2,0.4,0.4v0.4c0,0.2-0.2,0.4-0.4,0.4h-1.2c-0.1,0.8-0.4,1.6-0.9,2.3l0.8,0.8c0.2,0.2,0.2,0.4,0,0.6l-0.3,0.3c-0.2,0.2-0.4,0.2-0.6,0l-0.8-0.8c-0.7,0.5-1.4,0.8-2.3,0.9v1.2c0,0.2-0.2,0.4-0.4,0.4H6.8c-0.2,0-0.4-0.2-0.4-0.4v-1.2c-0.8-0.1-1.6-0.4-2.3-0.9l-0.8,0.8c-0.2,0.2-0.4,0.2-0.6,0l-0.3-0.3c-0.2-0.2-0.2-0.4,0-0.6l0.8-0.8C2.8,9.2,2.5,8.4,2.4,7.6H1.2C1,7.6,0.8,7.4,0.8,7.2V6.8c0-0.2,0.2-0.4,0.4-0.4h1.2c0.1-0.8,0.4-1.6,0.9-2.3L2.5,3.3c-0.2-0.2-0.2-0.4,0-0.6l0.3-0.3c0.2-0.2,0.4-0.2,0.6,0l0.8,0.8c0.7-0.5,1.4-0.8,2.3-0.9V1.2C6.4,1,6.6,0.8,6.8,0.8L6.8,0.8z M7,3.6C5.1,3.6,3.6,5.1,3.6,7c0,0,0,0,0,0c0,1.9,1.5,3.4,3.4,3.4c1.9,0,3.4-1.5,3.4-3.4C10.4,5.1,8.9,3.6,7,3.6C7,3.6,7,3.6,7,3.6z M7,4.8c1.2,0,2.2,1,2.2,2.2c0,1.2-1,2.2-2.2,2.2c-1.2,0-2.2-1-2.2-2.2C4.8,5.8,5.8,4.8,7,4.8z"/>
</symbol>
<symbol id="svg-icon-config-uso" viewBox="0 0 14 14">
<path d="M2,3h4v2H4v6h6V9h2v4H2V3z M8,1h6v6l-2.2-2.2l-4,4L6.2,7.2l4-4L8,1z"/>
</symbol>
</svg> </svg>
</body> </body>

View File

@ -34,23 +34,24 @@ var hotkeys = (() => {
return; return;
} }
let entry; let entry;
const k = event.which; const {which: k, key} = event;
if (k >= 48 && k <= 57 || k >= 96 && k <= 105) { if (key ? key >= '0' && key <= '9' : k >= 48 && k <= 57 || k >= 96 && k <= 105) {
// 0-9, numpad 0-9 // 0-9, numpad 0-9
entry = installed.children[k === 48 || k === 96 ? 9 : k - (k > 96 ? 97 : 49)]; const i = key === '0' ? 9 : key ? Number(key) - 1 : k === 48 || k === 96 ? 9 : k - (k > 96 ? 97 : 49);
} else if (k >= 65 && k <= 90) { entry = installed.children[i];
// a-z } else if (key ? key === '`' || key === '*' && !event.shiftKey : k === 192 || k === 106) {
const letter = new RegExp('^\\x' + k.toString(16), 'i');
entry = [...installed.children].find(entry => letter.test(entry.textContent));
} else if (k === 192 || k === 106) {
// backtick ` and numpad * // backtick ` and numpad *
invertTogglables(); invertTogglables();
} else if (k === 109) { } else if (key ? key === '-' : k === 109) {
// numpad - // numpad -
toggleState(installed.children, 'enabled', false); toggleState(installed.children, 'enabled', false);
} else if (k === 107) { } else if (key ? key === '+' : k === 107) {
// numpad + // numpad +
toggleState(installed.children, 'disabled', true); toggleState(installed.children, 'disabled', true);
} else if (key ? key.length === 1 : k >= 65 && k <= 90) {
// any single character
const letter = new RegExp(key ? '^' + key : '^\\x' + k.toString(16), 'i');
entry = [...installed.children].find(entry => letter.test(entry.textContent));
} }
if (!entry) { if (!entry) {
return; return;

View File

@ -274,6 +274,7 @@ function createStyleElement({
if (!style.usercssData && style.updateUrl && style.updateUrl.includes('?') && style.url) { if (!style.usercssData && style.updateUrl && style.updateUrl.includes('?') && style.url) {
config.href = style.url; config.href = style.url;
config.target = '_blank'; config.target = '_blank';
$('use', config).attributes['xlink:href'].nodeValue = '#svg-icon-config-uso';
} else if (!style.usercssData || !Object.keys(style.usercssData.vars || {}).length) { } else if (!style.usercssData || !Object.keys(style.usercssData.vars || {}).length) {
config.style.display = 'none'; config.style.display = 'none';
} }

View File

@ -1,18 +0,0 @@
1. Until https://github.com/CSSLint/parser-lib/issues/229 is fixed, manually replace:
while (lt !== Tokens.COMMA && lt !== Tokens.S && lt !== Tokens.RPAREN) {
in "_function: function()" with
while (lt !== Tokens.COMMA && lt !== Tokens.S && lt !== Tokens.RPAREN && lt !== Tokens.EOF) {
2. Apply our hacks unless supported natively
(use git history for the file as this warning may be obsolete):
* 449a27cc Add CSSLint position sticky rule
* d49e44dd CSS variables
* 2e86c958 fire startdocument on {
* bc63ecca support "i" in attribute selector
* 2468784e fix crashing on unclosed calc() at eof
* 3287b79f Support :any(), :-webkit-any(), :-moz-any()
* 4684016a Support @supports inside @-moz-document

View File

@ -2627,9 +2627,12 @@ Parser.prototype = function() {
if (value === null) { if (value === null) {
break; break;
} else {
values.push(value);
} }
const last = values[values.length - 1];
if (last && last.line === value.line && last.col === value.col && last.text === value.text) {
break;
}
values.push(value);
} while (true); } while (true);
} }
@ -2731,6 +2734,13 @@ Parser.prototype = function() {
} }
} }
if (value === null) {
const usoVar = this._isUsoVar();
if (usoVar) {
([line, col, value] = usoVar);
}
}
/*if (value === null) { /*if (value === null) {
return null; return null;
//throw new Error("Expected identifier at line " + tokenStream.token().startLine + ", character " + tokenStream.token().startCol + "."); //throw new Error("Expected identifier at line " + tokenStream.token().startLine + ", character " + tokenStream.token().startCol + ".");
@ -3173,6 +3183,21 @@ Parser.prototype = function() {
}, },
_isUsoVar() {
const tokenStream = this._tokenStream;
for (let i = tokenStream._ltIndex - 1; i >= 0; i--) {
const {type, value, startLine, startCol} = tokenStream._lt[i];
if (type === tokenStream._tokenData.S) {
// NOP
} else if (type === tokenStream._tokenData.COMMENT &&
value[2] === '[' && value[3] === '[' && value.endsWith(']]*/')) {
return [startLine, startCol, value];
} else {
return false;
}
}
},
/** /**
* Throws an error when an unexpected token is found. * Throws an error when an unexpected token is found.
* @param {Object} token The token that was found. * @param {Object} token The token that was found.
@ -3181,17 +3206,6 @@ 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);
}, },
@ -7186,7 +7200,8 @@ TokenStreamBase.prototype = {
if (!this.match(tokenTypes)) { if (!this.match(tokenTypes)) {
token = this.LT(1); token = this.LT(1);
throw new SyntaxError("Expected " + this._tokenData[tokenTypes[0]].name + const info = this._tokenData[tokenTypes[0]];
throw new SyntaxError("Expected " + (info.text || info.name) +
" at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol); " at line " + token.startLine + ", col " + token.startCol + ".", token.startLine, token.startCol);
} }
}, },
@ -10978,6 +10993,8 @@ CSSLint.addFormatter({
} }
}); });
parserlib.css.Tokens[parserlib.css.Tokens.COMMENT].hide = false;
self.onmessage = ({data: {action = 'run', code, config}}) => { self.onmessage = ({data: {action = 'run', code, config}}) => {
switch (action) { switch (action) {
@ -10991,19 +11008,19 @@ self.onmessage = ({data: {action = 'run', code, config}}) => {
self.postMessage(CSSLint.getRules().map(rule => JSON.parse(JSON.stringify(rule)))); self.postMessage(CSSLint.getRules().map(rule => JSON.parse(JSON.stringify(rule))));
return; return;
case 'run': case 'run': {
self.postMessage(CSSLint.verify(code, config).messages.map(m => { const results = CSSLint.verify(code, config).messages
// the functions are non-tranferable and we need only an id .filter(m => !m.message.includes('/*[[') && !m.message.includes(']]*/'))
m.rule = {id: m.rule.id}; .map(m => Object.assign(m, {rule: {id: m.rule.id}}));
return m; self.postMessage(results);
}));
return; return;
}
case 'parse': case 'parse':
if (!self.mozParser) { if (!self.mozParser) {
self.importScripts('/js/moz-parser.js'); self.importScripts('/js/moz-parser.js');
} }
mozParser.parse(code) mozParser.parse(code)
.then(sections => self.postMessage(sections)); .then(sections => self.postMessage(sections))
.catch(info => self.postMessage({__ERROR__: info}));
} }
}; };