manage: add new UI option
* align svg icons to pixel grid * globalize enforceInputRange() and use it in manage * 1 installed.onclick instead of 1000+ local ones
This commit is contained in:
parent
efd3e9ef6c
commit
e3b461a9e8
|
@ -57,7 +57,10 @@ globals:
|
|||
isCheckbox: false
|
||||
runTryCatch: false
|
||||
tryRegExp: false
|
||||
tryJSONparse: false
|
||||
debounce: false
|
||||
setupLivePrefs: false
|
||||
enforceInputRange: false
|
||||
getCodeMirrorThemes: false
|
||||
styleSectionsEqual: false
|
||||
|
||||
|
|
|
@ -282,6 +282,18 @@
|
|||
"message": "Only edited styles",
|
||||
"description": "Checkbox to show only locally edited styles"
|
||||
},
|
||||
"manageNewUI": {
|
||||
"message": "New manage UI layout",
|
||||
"description": "Label for the checkbox that toggles the new UI on manage page"
|
||||
},
|
||||
"manageFavicons": {
|
||||
"message": "Favicons in applies-to column",
|
||||
"description": "Label for the checkbox that toggles applies-to favicons in the new UI on manage page"
|
||||
},
|
||||
"manageMaxTargets": {
|
||||
"message": "Number of applies-to items",
|
||||
"description": "Label for the numeric input box to limit max number of applies-to targets in the new UI on manage page"
|
||||
},
|
||||
"manageText": {
|
||||
"message": "<a href='https://userstyles.org'>Get styles on userstyles.org</a> | <a href='http://add0n.com/stylus.html'>Get help</a>",
|
||||
"description": "Help text on the manage page"
|
||||
|
@ -482,6 +494,10 @@
|
|||
"message": "(Stylus can't affect this page.)",
|
||||
"description": "Note in the toolbar pop-up when on a URL Stylus can't affect"
|
||||
},
|
||||
"toggleStyle": {
|
||||
"message": "Toggle style",
|
||||
"description": "Label for the checkbox to enable/disable a style"
|
||||
},
|
||||
"undo": {
|
||||
"message": "Undo",
|
||||
"description": "Button label"
|
||||
|
|
198
manage.css
198
manage.css
|
@ -47,10 +47,9 @@ a:hover {
|
|||
.svg-icon {
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
margin-top: -4px;
|
||||
transition: fill .5s;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
fill: #000;
|
||||
}
|
||||
|
||||
|
@ -58,11 +57,21 @@ a:hover {
|
|||
fill: #666;
|
||||
}
|
||||
|
||||
.svg-icon.delete {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.homepage {
|
||||
margin-left: 0.1em;
|
||||
margin-right: 0.1em;
|
||||
}
|
||||
|
||||
.homepage .svg-icon {
|
||||
margin-top: -4px;
|
||||
margin-left: .5ex;
|
||||
}
|
||||
|
||||
.style-name {
|
||||
margin-top: .25em;
|
||||
word-break: break-word;
|
||||
|
@ -101,7 +110,7 @@ a:hover {
|
|||
margin-right: .25rem;
|
||||
}
|
||||
|
||||
.applies-to > :first-child {
|
||||
.applies-to label {
|
||||
margin-right: .5ex;
|
||||
}
|
||||
|
||||
|
@ -109,8 +118,9 @@ a:hover {
|
|||
background-color: rgba(128, 128, 128, .15);
|
||||
}
|
||||
|
||||
.applies-to-extra {
|
||||
.applies-to-extra:not([open]) {
|
||||
display: inline;
|
||||
margin-left: 1ex;
|
||||
}
|
||||
|
||||
summary {
|
||||
|
@ -142,6 +152,175 @@ summary {
|
|||
display: none;
|
||||
}
|
||||
|
||||
/* compact layout */
|
||||
|
||||
.newUI {
|
||||
display: table;
|
||||
margin: .75rem 0 .75rem 0;
|
||||
}
|
||||
|
||||
.newUI .disabled {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.newUI .disabled .style-name,
|
||||
.newUI .disabled .applies-to {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.newUI .entry {
|
||||
display: table-row;
|
||||
}
|
||||
|
||||
.newUI .entry:nth-child(2n) {
|
||||
background-color: rgba(128, 128, 128, 0.05);
|
||||
}
|
||||
|
||||
.newUI .entry > * {
|
||||
padding: .9rem 0 1rem;
|
||||
margin: 0;
|
||||
display: table-cell;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.newUI .checker {
|
||||
position: relative;
|
||||
top: 1px;
|
||||
margin-right: 1ex;
|
||||
}
|
||||
|
||||
.newUI .style-name {
|
||||
font-size: 14px;
|
||||
font-family: sans-serif;
|
||||
text-indent: -2em;
|
||||
padding-left: 3em;
|
||||
padding-right: 30px;
|
||||
}
|
||||
|
||||
.newUI .homepage .svg-icon {
|
||||
position: absolute;
|
||||
margin-top: 0;
|
||||
margin-left: -28px;
|
||||
}
|
||||
|
||||
.newUI .actions {
|
||||
width: 60px;
|
||||
height: 20px;
|
||||
white-space: nowrap;
|
||||
font-size: 0;
|
||||
}
|
||||
|
||||
.newUI .actions > * {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.newUI .actions .svg-icon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.newUI .updater-icons > * {
|
||||
transition: opacity 1s;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.newUI .checking-update .check-update {
|
||||
opacity: 0;
|
||||
display: inline;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.newUI .can-update .update,
|
||||
.newUI .no-update:not(.update-problem) .up-to-date,
|
||||
.newUI .no-update.update-problem .check-update,
|
||||
.newUI .update-done .updated {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.update-problem .check-update svg {
|
||||
fill: darkred;
|
||||
}
|
||||
|
||||
.newUI .applies-to {
|
||||
padding-top: .25rem;
|
||||
padding-bottom: .25rem;
|
||||
}
|
||||
|
||||
.newUI .targets {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.newUI .applies-to.expanded .targets {
|
||||
max-height: 100vh;
|
||||
}
|
||||
|
||||
.newUI .target {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: calc(100vw - 280px - 60px - 25vw - 3rem);
|
||||
box-sizing: border-box;
|
||||
padding-right: 1rem;
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.newUI .applies-to .expander {
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
font-size: 3ex;
|
||||
line-height: .5ex;
|
||||
vertical-align: super;
|
||||
letter-spacing: .1ex;
|
||||
}
|
||||
|
||||
.newUI.has-favicons .target {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.newUI .target:hover {
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
.newUI .target img {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
vertical-align: sub;
|
||||
margin-left: -20px;
|
||||
margin-right: 4px;
|
||||
transition: opacity .5s, filter .5s;
|
||||
/* unprefixed since Chrome 53 */
|
||||
-webkit-filter: grayscale(1);
|
||||
filter: grayscale(1);
|
||||
opacity: .25;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.newUI.has-favicons .target img[src] {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.newUI .entry:hover .target img {
|
||||
opacity: 1;
|
||||
/* unprefixed since Chrome 53 */
|
||||
-webkit-filter: grayscale(0);
|
||||
filter: grayscale(0);
|
||||
}
|
||||
|
||||
#newUIoptions label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
#newUIoptions input[type="number"] {
|
||||
width: 3em;
|
||||
margin-right: .5em;
|
||||
}
|
||||
|
||||
input[id^="manage.newUI"] {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
/* Default, no update buttons */
|
||||
.update,
|
||||
.check-update {
|
||||
|
@ -285,6 +464,15 @@ fieldset {
|
|||
}
|
||||
}
|
||||
|
||||
@keyframes fadein-25pct {
|
||||
from {
|
||||
opacity: 0;
|
||||
}
|
||||
to {
|
||||
opacity: .25;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 675px) {
|
||||
#header {
|
||||
height: auto;
|
||||
|
|
67
manage.html
67
manage.html
|
@ -6,10 +6,12 @@
|
|||
<link rel="stylesheet" href="manage.css">
|
||||
<link rel="stylesheet" href="msgbox/msgbox.css">
|
||||
|
||||
<style id="style-overrides"></style>
|
||||
|
||||
<template data-id="style">
|
||||
<div class="entry">
|
||||
<h2 class="style-name"><a class="style-name-link" href="edit.html?id="></a></h2>
|
||||
<p class="applies-to"><span></span></p>
|
||||
<p class="applies-to"><label i18n-text="appliesDisplay"></label></p>
|
||||
<p class="actions">
|
||||
<a class="style-edit-link" href="edit.html?id=">
|
||||
<button i18n-text="editStyleLabel"></button>
|
||||
|
@ -24,6 +26,26 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<template data-id="styleCompact">
|
||||
<div class="entry">
|
||||
<h2 class="style-name">
|
||||
<input class="checker" type="checkbox" i18n-title="toggleStyle">
|
||||
<a class="style-name-link" href="edit.html?id="></a>
|
||||
</h2>
|
||||
<p class="actions">
|
||||
<span i18n-title="deleteStyleLabel"><svg class="svg-icon delete"><use xlink:href="#svg-icon-remove"/></svg></span>
|
||||
<span class="updater-icons">
|
||||
<span class="check-update" i18n-title="checkForUpdate"><svg class="svg-icon"><use xlink:href="#svg-icon-check"/></svg></span>
|
||||
<span class="update" i18n-title="installUpdate"><svg class="svg-icon"><use xlink:href="#svg-icon-update"/></svg></span>
|
||||
<span class="up-to-date" i18n-title="updateCheckSucceededNoUpdate"><svg class="svg-icon"><use xlink:href="#svg-icon-uptodate"/></svg></span>
|
||||
<span class="updated" i18n-title="updateCompleted"><svg class="svg-icon"><use xlink:href="#svg-icon-uptodate"/></svg></span>
|
||||
<span class="update-note"></span>
|
||||
</span>
|
||||
</p>
|
||||
<div class="applies-to"><div class="targets"></div></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template data-id="styleHomepage">
|
||||
<a target="_blank" class="homepage"><svg class="svg-icon"><use xlink:href="#svg-icon-external-link"/></svg></a>
|
||||
</template>
|
||||
|
@ -46,6 +68,10 @@
|
|||
</details>
|
||||
</template>
|
||||
|
||||
<template data-id="expandAppliesTo">
|
||||
<span class="expander">...</span>
|
||||
</template>
|
||||
|
||||
<script src="health.js"></script>
|
||||
<script src="storage.js"></script>
|
||||
<script src="messaging.js"></script>
|
||||
|
@ -85,6 +111,11 @@
|
|||
</p>
|
||||
<div id="options">
|
||||
<h2 id="options-heading" i18n-text="optionsHeading"></h2>
|
||||
<label><input id="manage.newUI" type="checkbox"><span i18n-text="manageNewUI"></span></label>
|
||||
<div id="newUIoptions" class="hidden">
|
||||
<label><input id="manage.newUI.favicons" type="checkbox"><span i18n-text="manageFavicons"></span></label>
|
||||
<label><input id="manage.newUI.targets" type="number" min="1" max="99"><span i18n-text="manageMaxTargets"></span></label>
|
||||
</div>
|
||||
<p>
|
||||
<button id="manage-options-button" i18n-text="openOptionsManage"></button>
|
||||
<button id="manage-shortcuts-button" i18n-text="openOptionsShortcuts"></button>
|
||||
|
@ -106,13 +137,39 @@
|
|||
</div>
|
||||
<div id="installed"></div>
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="display: none">
|
||||
<symbol id="svg-icon-external-link" height="16" width="16" viewBox="0 0 8 8">
|
||||
<path d="M0 0v8h8v-2h-1v1h-6v-6h1v-1h-2zm4 0l1.5 1.5-2.5 2.5 1 1 2.5-2.5 1.5 1.5v-4h-4z"></path>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" style="display: none !important;">
|
||||
|
||||
<symbol id="svg-icon-external-link" viewBox="0 0 20 20">
|
||||
<polygon shape-rendering="crispEdges" points="3,3 3,17 17,17 17,13 15,13 15,15 5,15 5,5 7,5 7,3 "/>
|
||||
<polygon points="10,3 12.5,5.5 8,10 10,12 14.5,7.5 17,10 17,3 "/>
|
||||
</symbol>
|
||||
<symbol id="svg-icon-close" height="16" width="12" viewBox="0 0 12 16">
|
||||
|
||||
<symbol id="svg-icon-close" viewBox="0 0 12 16">
|
||||
<path fill-rule="evenodd" d="M7.48 8l3.75 3.75-1.48 1.48L6 9.48l-3.75 3.75-1.48-1.48L4.52 8 .77 4.25l1.48-1.48L6 6.52l3.75-3.75 1.48 1.48z"></path>
|
||||
</symbol>
|
||||
|
||||
<symbol id="svg-icon-remove" viewBox="0 0 14 16">
|
||||
<path shape-rendering="crispEdges" fill-rule="evenodd" d="M11 2H9c0-.55-.45-1-1-1H5c-.55 0-1 .45-1 1H2c-.55 0-1 .45-1 1v1c0 .55.45 1 1 1v9c0 .55.45 1 1 1h7c.55 0 1-.45 1-1V5c.55 0 1-.45 1-1V3c0-.55-.45-1-1-1zm-1 12H3V5h1v8h1V5h1v8h1V5h1v8h1V5h1v9zm1-10H2V3h9v1z">
|
||||
</path>
|
||||
</symbol>
|
||||
|
||||
<symbol id="svg-icon-check" viewBox="0 0 20 20">
|
||||
<path d="M15,11c-0.5,2.3-2.5,4.1-5,4.1c-2.4,0-4.5-1.7-5-4.1H3.3c0.5,3.3,3.3,5.8,6.7,5.8s6.2-2.5,6.7-5.8H15z"/>
|
||||
<path d="M10,3.2C6.6,3.2,3.8,5.7,3.3,9H5c0.5-2.3,2.5-4.1,5-4.1c2.4,0,4.5,1.7,5,4.1h1.7C16.2,5.7,13.4,3.2,10,3.2z"/>
|
||||
<polygon shape-rendering="crispEdges" points="17,3 17,9 11,9 "/>
|
||||
<polygon shape-rendering="crispEdges" points="9.1,11 3,11 3,17.1 "/>
|
||||
<polygon points="19.9,18.6 15.1,13.8 13.8,15.1 18.6,19.9 "/>
|
||||
</symbol>
|
||||
|
||||
<symbol id="svg-icon-update" viewBox="0 0 20 20">
|
||||
<polygon points="16,8 12,8 12,3 8,3 8,8 4,8 10,14 "/>
|
||||
<rect shape-rendering="crispEdges" x="4" y="15" width="12" height="2"/>
|
||||
</symbol>
|
||||
|
||||
<symbol id="svg-icon-uptodate" viewBox="0 0 20 20">
|
||||
<polygon shape-rendering="crispEdges" points="4,9 8,13 18,3 19,4 8,15 3,10 "/>
|
||||
</symbol>
|
||||
|
||||
</svg>
|
||||
|
||||
<script src="manage.js"></script>
|
||||
|
|
262
manage.js
262
manage.js
|
@ -2,11 +2,19 @@
|
|||
'use strict';
|
||||
|
||||
const installed = $('#installed');
|
||||
const TARGET_LABEL = t('appliesDisplay', '').trim();
|
||||
const newUI = {
|
||||
enabled: prefs.get('manage.newUI'),
|
||||
favicons: prefs.get('manage.newUI.favicons'),
|
||||
targets: prefs.get('manage.newUI.targets'),
|
||||
};
|
||||
|
||||
const TARGET_TYPES = ['domains', 'urls', 'urlPrefixes', 'regexps'];
|
||||
const TARGET_LIMIT = 10;
|
||||
const GET_FAVICON_URL = 'https://www.google.com/s2/favicons?domain=';
|
||||
const OWN_ICON = chrome.app.getDetails().icons['16'];
|
||||
|
||||
const handleEvent = {};
|
||||
|
||||
|
||||
getStylesSafe()
|
||||
.then(showStyles)
|
||||
.then(initGlobalEvents);
|
||||
|
@ -26,6 +34,7 @@ chrome.runtime.onMessage.addListener(msg => {
|
|||
|
||||
|
||||
function initGlobalEvents() {
|
||||
installed.onclick = handleEvent.entryClicked;
|
||||
$('#check-all-updates').onclick = checkUpdateAll;
|
||||
$('#apply-all-updates').onclick = applyUpdateAll;
|
||||
$('#search').oninput = searchStyles;
|
||||
|
@ -50,19 +59,28 @@ function initGlobalEvents() {
|
|||
}
|
||||
});
|
||||
|
||||
for (const [className, checkbox] of [
|
||||
['enabled-only', $('#manage.onlyEnabled')],
|
||||
['edited-only', $('#manage.onlyEdited')],
|
||||
]) {
|
||||
// will be triggered by setupLivePrefs immediately
|
||||
checkbox.onchange = () => installed.classList.toggle(className, checkbox.checked);
|
||||
}
|
||||
|
||||
enforceInputRange($('#manage.newUI.favicons'));
|
||||
|
||||
setupLivePrefs([
|
||||
'manage.onlyEnabled',
|
||||
'manage.onlyEdited',
|
||||
'manage.newUI',
|
||||
'manage.newUI.favicons',
|
||||
'manage.newUI.targets',
|
||||
]);
|
||||
|
||||
[
|
||||
['enabled-only', $('#manage.onlyEnabled')],
|
||||
['edited-only', $('#manage.onlyEdited')],
|
||||
]
|
||||
.forEach(([className, checkbox]) => {
|
||||
checkbox.onchange = () => installed.classList.toggle(className, checkbox.checked);
|
||||
checkbox.onchange();
|
||||
});
|
||||
$$('[id^="manage.newUI"]')
|
||||
.forEach(el => (el.oninput = (el.onchange = switchUI)));
|
||||
|
||||
switchUI({styleOnly: true});
|
||||
}
|
||||
|
||||
|
||||
|
@ -98,7 +116,7 @@ function showStyles(styles = []) {
|
|||
|
||||
|
||||
function createStyleElement({style, name}) {
|
||||
const entry = template.style.cloneNode(true);
|
||||
const entry = template[`style${newUI.enabled ? 'Compact' : ''}`].cloneNode(true);
|
||||
entry.classList.add(style.enabled ? 'enabled' : 'disabled');
|
||||
entry.setAttribute('style-id', style.id);
|
||||
entry.id = 'style-' + style.id;
|
||||
|
@ -118,69 +136,111 @@ function createStyleElement({style, name}) {
|
|||
const styleNameEditLink = $('a', styleName);
|
||||
styleNameEditLink.appendChild(document.createTextNode(style.name));
|
||||
styleNameEditLink.href = styleNameEditLink.getAttribute('href') + style.id;
|
||||
styleNameEditLink.onclick = handleEvent.edit;
|
||||
if (style.url) {
|
||||
const homepage = template.styleHomepage.cloneNode(true);
|
||||
homepage.href = style.url;
|
||||
homepage.onclick = handleEvent.external;
|
||||
styleName.appendChild(document.createTextNode(' '));
|
||||
styleName.appendChild(homepage);
|
||||
const homepage = Object.assign(template.styleHomepage.cloneNode(true), {
|
||||
href: style.url,
|
||||
title: style.url,
|
||||
});
|
||||
if (newUI.enabled) {
|
||||
const actions = $('.actions', entry);
|
||||
actions.insertBefore(homepage, actions.firstChild);
|
||||
} else {
|
||||
styleName.appendChild(homepage);
|
||||
}
|
||||
}
|
||||
|
||||
const targets = new Map(TARGET_TYPES.map(t => [t, new Set()]));
|
||||
const appliesTo = $('.applies-to', entry);
|
||||
const decorations = {
|
||||
urlPrefixesAfter: '*',
|
||||
regexpsBefore: '/',
|
||||
regexpsAfter: '/',
|
||||
};
|
||||
for (const [name, target] of targets.entries()) {
|
||||
const showFavicons = newUI.enabled && newUI.favicons;
|
||||
const maxTargets = newUI.enabled ? Number.MAX_VALUE : 10;
|
||||
const displayed = new Set();
|
||||
let container = newUI.enabled ? $('.targets', appliesTo) : appliesTo;
|
||||
let numTargets = 0;
|
||||
for (const type of TARGET_TYPES) {
|
||||
for (const section of style.sections) {
|
||||
for (const targetValue of section[name] || []) {
|
||||
target.add(
|
||||
(decorations[name + 'Before'] || '') +
|
||||
targetValue.trim() +
|
||||
(decorations[name + 'After'] || ''));
|
||||
for (const targetValue of section[type] || []) {
|
||||
if (displayed.has(targetValue)) {
|
||||
continue;
|
||||
}
|
||||
displayed.add(targetValue);
|
||||
if (numTargets++ == maxTargets) {
|
||||
container = appliesTo.appendChild(template.extraAppliesTo.cloneNode(true));
|
||||
} else if (numTargets > 1 && !newUI.enabled) {
|
||||
container.appendChild(template.appliesToSeparator.cloneNode(true));
|
||||
}
|
||||
const element = template.appliesToTarget.cloneNode(true);
|
||||
if (showFavicons) {
|
||||
let favicon = '';
|
||||
if (type == 'domains') {
|
||||
favicon = GET_FAVICON_URL + targetValue;
|
||||
} else if (targetValue.startsWith('chrome-extension:')) {
|
||||
favicon = OWN_ICON;
|
||||
} else if (type != 'regexps') {
|
||||
favicon = targetValue.match(/^.*?:\/\/([^/]+)/);
|
||||
favicon = favicon ? GET_FAVICON_URL + favicon[1] : '';
|
||||
}
|
||||
if (favicon) {
|
||||
element.appendChild(document.createElement('img')).dataset.src = favicon;
|
||||
debounce(handleEvent.loadFavicons);
|
||||
}
|
||||
}
|
||||
element.appendChild(
|
||||
document.createTextNode(
|
||||
(decorations[type + 'Before'] || '') +
|
||||
targetValue +
|
||||
(decorations[type + 'After'] || '')));
|
||||
container.appendChild(element);
|
||||
}
|
||||
}
|
||||
}
|
||||
const appliesTo = $('.applies-to', entry);
|
||||
appliesTo.firstElementChild.textContent = TARGET_LABEL;
|
||||
const targetsList = Array.prototype.concat.apply([],
|
||||
[...targets.values()].map(set => [...set.values()]));
|
||||
if (!targetsList.length) {
|
||||
if (!numTargets) {
|
||||
appliesTo.appendChild(template.appliesToEverything.cloneNode(true));
|
||||
entry.classList.add('global');
|
||||
} else {
|
||||
let index = 0;
|
||||
let container = appliesTo;
|
||||
for (const target of targetsList) {
|
||||
if (index > 0) {
|
||||
container.appendChild(template.appliesToSeparator.cloneNode(true));
|
||||
}
|
||||
if (++index == TARGET_LIMIT) {
|
||||
container = appliesTo.appendChild(template.extraAppliesTo.cloneNode(true));
|
||||
}
|
||||
const item = template.appliesToTarget.cloneNode(true);
|
||||
item.textContent = target;
|
||||
container.appendChild(item);
|
||||
}
|
||||
}
|
||||
|
||||
const editLink = $('.style-edit-link', entry);
|
||||
editLink.href = editLink.getAttribute('href') + style.id;
|
||||
editLink.onclick = handleEvent.edit;
|
||||
if (newUI.enabled) {
|
||||
$('.checker', entry).checked = style.enabled;
|
||||
if (numTargets > newUI.targets) {
|
||||
appliesTo.appendChild(template.expandAppliesTo.cloneNode(true));
|
||||
}
|
||||
} else {
|
||||
const editLink = $('.style-edit-link', entry);
|
||||
editLink.href = editLink.getAttribute('href') + style.id;
|
||||
}
|
||||
|
||||
$('.enable', entry).onclick = handleEvent.toggle;
|
||||
$('.disable', entry).onclick = handleEvent.toggle;
|
||||
$('.check-update', entry).onclick = handleEvent.check;
|
||||
$('.update', entry).onclick = handleEvent.update;
|
||||
$('.delete', entry).onclick = handleEvent.delete;
|
||||
return entry;
|
||||
}
|
||||
|
||||
|
||||
Object.assign(handleEvent, {
|
||||
|
||||
ENTRY_ROUTES: {
|
||||
'.checker, .enable, .disable': 'toggle',
|
||||
'.style-name-link': 'edit',
|
||||
'.homepage': 'external',
|
||||
'.check-update': 'check',
|
||||
'.update': 'update',
|
||||
'.delete': 'delete',
|
||||
'.applies-to .expander': 'expandTargets',
|
||||
},
|
||||
|
||||
entryClicked(event) {
|
||||
const target = event.target;
|
||||
const entry = target.closest('.entry');
|
||||
for (const selector in handleEvent.ENTRY_ROUTES) {
|
||||
for (let el = target; el && el != entry; el = el.parentElement) {
|
||||
if (el.matches(selector)) {
|
||||
const handler = handleEvent.ENTRY_ROUTES[selector];
|
||||
return handleEvent[handler].call(el, event, entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
edit(event) {
|
||||
if (event.altKey) {
|
||||
return;
|
||||
|
@ -210,30 +270,28 @@ Object.assign(handleEvent, {
|
|||
}
|
||||
},
|
||||
|
||||
toggle(event) {
|
||||
enableStyle(getClickedStyleId(event), this.matches('.enable'))
|
||||
toggle(event, entry) {
|
||||
enableStyle(entry.styleId, this.matches('.enable') || this.checked)
|
||||
.then(handleUpdate);
|
||||
},
|
||||
|
||||
check(event) {
|
||||
checkUpdate(getClickedStyleElement(event));
|
||||
check(event, entry) {
|
||||
checkUpdate(entry);
|
||||
},
|
||||
|
||||
update(event) {
|
||||
const styleElement = getClickedStyleElement(event);
|
||||
update(event, entry) {
|
||||
// update everything but name
|
||||
saveStyle(Object.assign(styleElement.updatedCode, {
|
||||
id: styleElement.styleId,
|
||||
saveStyle(Object.assign(entry.updatedCode, {
|
||||
id: entry.styleId,
|
||||
name: null,
|
||||
reason: 'update',
|
||||
}));
|
||||
},
|
||||
|
||||
delete(event) {
|
||||
const styleElement = getClickedStyleElement(event);
|
||||
const id = styleElement.styleId;
|
||||
delete(event, entry) {
|
||||
const id = entry.styleId;
|
||||
const {name} = cachedStyles.byId.get(id) || {};
|
||||
animateElement(styleElement, {className: 'highlight'});
|
||||
animateElement(entry, {className: 'highlight'});
|
||||
messageBox({
|
||||
title: t('deleteStyleConfirm'),
|
||||
contents: name,
|
||||
|
@ -251,10 +309,23 @@ Object.assign(handleEvent, {
|
|||
openURL({url: event.target.closest('a').href});
|
||||
event.preventDefault();
|
||||
},
|
||||
|
||||
expandTargets() {
|
||||
this.closest('.applies-to').classList.toggle('expanded');
|
||||
},
|
||||
|
||||
loadFavicons(container = installed) {
|
||||
for (const img of container.getElementsByTagName('img')) {
|
||||
if (img.dataset.src) {
|
||||
img.src = img.dataset.src;
|
||||
delete img.dataset.src;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function handleUpdate(style, {reason} = {}) {
|
||||
function handleUpdate(style, {reason, quiet} = {}) {
|
||||
const element = createStyleElement({style});
|
||||
const oldElement = $('#style-' + style.id, installed);
|
||||
if (oldElement) {
|
||||
|
@ -269,8 +340,10 @@ function handleUpdate(style, {reason} = {}) {
|
|||
}
|
||||
}
|
||||
installed.insertBefore(element, findNextElement(style));
|
||||
animateElement(element, {className: 'highlight'});
|
||||
scrollElementIntoView(element);
|
||||
if (!quiet) {
|
||||
animateElement(element, {className: 'highlight'});
|
||||
scrollElementIntoView(element);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
@ -282,6 +355,38 @@ function handleDelete(id) {
|
|||
}
|
||||
|
||||
|
||||
function switchUI({styleOnly} = {}) {
|
||||
const enabled = $('#manage.newUI').checked;
|
||||
const favicons = $('#manage.newUI.favicons').checked;
|
||||
const targets = Number($('#manage.newUI.targets').value);
|
||||
|
||||
const stateToggled = newUI.enabled != enabled;
|
||||
const targetsChanged = enabled && targets != newUI.targets;
|
||||
const faviconsChanged = enabled && favicons != newUI.favicons;
|
||||
const missingFavicons = enabled && favicons && !$('.applies-to img');
|
||||
|
||||
if (!styleOnly && !stateToggled && !targetsChanged && !faviconsChanged) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object.assign(newUI, {enabled, favicons, targets});
|
||||
|
||||
installed.classList.toggle('newUI', enabled);
|
||||
installed.classList.toggle('has-favicons', favicons);
|
||||
$('#newUIoptions').classList.toggle('hidden', !enabled);
|
||||
$('#style-overrides').textContent = `
|
||||
.newUI .targets {
|
||||
max-height: ${newUI.targets * 18}px;
|
||||
}
|
||||
`;
|
||||
|
||||
if (!styleOnly && (stateToggled || missingFavicons)) {
|
||||
installed.innerHTML = '';
|
||||
getStylesSafe().then(showStyles);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function applyUpdateAll() {
|
||||
const btnApply = $('#apply-all-updates');
|
||||
btnApply.disabled = true;
|
||||
|
@ -310,8 +415,11 @@ function checkUpdateAll() {
|
|||
Promise.all($$('[style-update-url]').map(checkUpdate))
|
||||
.then(updatables => {
|
||||
btnCheck.disabled = false;
|
||||
if (updatables.includes(true)) {
|
||||
const numUpdatable = updatables.filter(u => u).length;
|
||||
if (numUpdatable) {
|
||||
btnApply.classList.remove('hidden');
|
||||
btnApply.originalLabel = btnApply.originalLabel || btnApply.textContent;
|
||||
btnApply.textContent = btnApply.originalLabel + ` (${numUpdatable})`;
|
||||
} else {
|
||||
noUpdates.classList.remove('hidden');
|
||||
setTimeout(() => {
|
||||
|
@ -329,7 +437,8 @@ function checkUpdateAll() {
|
|||
|
||||
function checkUpdate(element) {
|
||||
$('.update-note', element).innerHTML = t('checkingForUpdate');
|
||||
element.classList.remove('checking-update', 'no-update', 'can-update');
|
||||
$('.check-update', element).title = '';
|
||||
element.classList.remove('checking-update', 'no-update', 'can-update', 'update-problem');
|
||||
element.classList.add('checking-update');
|
||||
return new Updater(element).run(); // eslint-disable-line no-use-before-define
|
||||
}
|
||||
|
@ -337,12 +446,13 @@ function checkUpdate(element) {
|
|||
|
||||
class Updater {
|
||||
constructor(element) {
|
||||
const style = cachedStyles.byId.get(element.styleId);
|
||||
Object.assign(this, {
|
||||
element,
|
||||
id: element.styleId,
|
||||
url: element.getAttribute('style-update-url'),
|
||||
md5Url: element.getAttribute('style-md5-url'),
|
||||
md5: element.getAttribute('style-original-md5'),
|
||||
id: style.id,
|
||||
url: style.updateUrl,
|
||||
md5Url: style.md5Url,
|
||||
md5: style.originalMd5,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -357,7 +467,7 @@ class Updater {
|
|||
md5 => (md5.length == 32
|
||||
? this.decideOnMd5(md5 != this.md5)
|
||||
: this.onFailure(-1)),
|
||||
this.onFailure);
|
||||
status => this.onFailure(status));
|
||||
}
|
||||
|
||||
decideOnMd5(md5changed) {
|
||||
|
@ -370,7 +480,7 @@ class Updater {
|
|||
checkFullCode({forceUpdate = false} = {}) {
|
||||
return Updater.download(this.url).then(
|
||||
text => this.handleJson(forceUpdate, JSON.parse(text)),
|
||||
this.onFailure);
|
||||
status => this.onFailure(status));
|
||||
}
|
||||
|
||||
handleJson(forceUpdate, json) {
|
||||
|
@ -400,7 +510,11 @@ class Updater {
|
|||
$('.update-note', this.element).innerHTML = '';
|
||||
} else {
|
||||
this.element.classList.add('no-update');
|
||||
this.element.classList.toggle('update-problem', Boolean(message));
|
||||
$('.update-note', this.element).innerHTML = message || t('updateCheckSucceededNoUpdate');
|
||||
if (newUI.enabled) {
|
||||
$('.check-update', this.element).title = message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -86,7 +86,12 @@
|
|||
cursor: pointer;
|
||||
position: absolute;
|
||||
right: 3px;
|
||||
top: 7px;
|
||||
top: 4px;
|
||||
}
|
||||
|
||||
#message-box-close-icon svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
#message-box-contents {
|
||||
|
|
|
@ -10,20 +10,7 @@ setupLivePrefs([
|
|||
'popupWidth',
|
||||
'updateInterval',
|
||||
]);
|
||||
enforceValueRange('popupWidth');
|
||||
|
||||
function enforceValueRange(id) {
|
||||
const element = document.getElementById(id);
|
||||
const min = Number(element.min);
|
||||
const max = Number(element.max);
|
||||
let value = Number(element.value);
|
||||
if (value < min || value > max) {
|
||||
value = Math.max(min, Math.min(max, value));
|
||||
element.value = value;
|
||||
}
|
||||
element.onchange = element.onchange || (() => enforceValueRange(id));
|
||||
return value | 0;
|
||||
}
|
||||
enforceInputRange($('#popupWidth'));
|
||||
|
||||
// overwrite the default URL if browser is Opera
|
||||
$('[data-cmd="open-keyboard"]').href = configureCommands.url;
|
||||
|
|
22
storage.js
22
storage.js
|
@ -547,14 +547,14 @@ function tryJSONparse(jsonString) {
|
|||
}
|
||||
|
||||
|
||||
function debounce(fn, ...args) {
|
||||
function debounce(fn, delay, ...args) {
|
||||
const timers = debounce.timers = debounce.timers || new Map();
|
||||
debounce.run = debounce.run || ((fn, ...args) => {
|
||||
timers.delete(fn);
|
||||
fn(...args);
|
||||
});
|
||||
clearTimeout(timers.get(fn));
|
||||
timers.set(fn, setTimeout(debounce.run, 0, fn, ...args));
|
||||
timers.set(fn, setTimeout(debounce.run, delay, fn, ...args));
|
||||
}
|
||||
|
||||
|
||||
|
@ -572,6 +572,9 @@ prefs = prefs || new function Prefs() {
|
|||
|
||||
'manage.onlyEnabled': false, // display only enabled styles
|
||||
'manage.onlyEdited': false, // display only styles created locally
|
||||
'manage.newUI': true, // use the new compact layout
|
||||
'manage.newUI.favicons': true, // show favicons for the sites in applies-to
|
||||
'manage.newUI.targets': 3, // max number of applies-to targets visible: 0 = none
|
||||
|
||||
'editor.options': {}, // CodeMirror.defaults.*
|
||||
'editor.lineWrapping': true, // word wrap
|
||||
|
@ -755,6 +758,21 @@ function setupLivePrefs(IDs) {
|
|||
}
|
||||
|
||||
|
||||
function enforceInputRange(element) {
|
||||
const min = Number(element.min);
|
||||
const max = Number(element.max);
|
||||
const onChange = () => {
|
||||
const value = Number(element.value);
|
||||
if (value < min || value > max) {
|
||||
element.value = Math.max(min, Math.min(max, value));
|
||||
}
|
||||
};
|
||||
onChange();
|
||||
element.addEventListener('change', onChange);
|
||||
element.addEventListener('input', onChange);
|
||||
}
|
||||
|
||||
|
||||
function getCodeMirrorThemes(callback) {
|
||||
chrome.runtime.getPackageDirectoryEntry(function(rootDir) {
|
||||
rootDir.getDirectory('codemirror/theme', {create: false}, function(themeDir) {
|
||||
|
|
Loading…
Reference in New Issue
Block a user