Add: toggle dark/night mode styles automatically (#736)
* Add: color-scheme.js * Add: handle color scheme * Add: styleManager.setMeta * Add: make setupLivePrefs work with radio * Change: drop setupRadioButtons * Add: UI for schemeSwitcher * Add: prefer-scheme select in installation page * Fix: add alarm listener * Add: display excluded reason in popup * Fix: rely on data-value-type instead of input name * Fix: oldValue and newValue should have the same type * Change: detect media change in content script * Fix: duplicate capitalize * Fix: minor * Update web-ext * Fix: valueAsNumber doesn't work for all inputs * Fix: disable colorscheme selection after install * Fix: API error
This commit is contained in:
parent
19ebeedf6a
commit
6c13db1468
|
@ -596,6 +596,18 @@
|
||||||
"message": "Update style",
|
"message": "Update style",
|
||||||
"description": "Label for update button"
|
"description": "Label for update button"
|
||||||
},
|
},
|
||||||
|
"installPreferSchemeLabel": {
|
||||||
|
"message": "The style should be applied:"
|
||||||
|
},
|
||||||
|
"installPreferSchemeNone": {
|
||||||
|
"message": "Always"
|
||||||
|
},
|
||||||
|
"installPreferSchemeDark": {
|
||||||
|
"message": "In Dark Mode"
|
||||||
|
},
|
||||||
|
"installPreferSchemeLight": {
|
||||||
|
"message": "In Light Mode"
|
||||||
|
},
|
||||||
"installUpdate": {
|
"installUpdate": {
|
||||||
"message": "Install update",
|
"message": "Install update",
|
||||||
"description": "Label for the button to install an update for a single style"
|
"description": "Label for the button to install an update for a single style"
|
||||||
|
@ -1053,6 +1065,18 @@
|
||||||
"optionsAdvancedNewStyleAsUsercss": {
|
"optionsAdvancedNewStyleAsUsercss": {
|
||||||
"message": "Write new style as usercss"
|
"message": "Write new style as usercss"
|
||||||
},
|
},
|
||||||
|
"optionsAdvancedAutoSwitchScheme": {
|
||||||
|
"message": "Toggle Light/Dark Mode styles automatically"
|
||||||
|
},
|
||||||
|
"optionsAdvancedAutoSwitchSchemeNever": {
|
||||||
|
"message": "Never"
|
||||||
|
},
|
||||||
|
"optionsAdvancedAutoSwitchSchemeBySystem": {
|
||||||
|
"message": "By system preference"
|
||||||
|
},
|
||||||
|
"optionsAdvancedAutoSwitchSchemeByTime": {
|
||||||
|
"message": "By night time:"
|
||||||
|
},
|
||||||
"optionsAdvancedPatchCsp": {
|
"optionsAdvancedPatchCsp": {
|
||||||
"message": "Patch <code>CSP</code> to allow style assets"
|
"message": "Patch <code>CSP</code> to allow style assets"
|
||||||
},
|
},
|
||||||
|
@ -1533,6 +1557,12 @@
|
||||||
"message": "Style was not applied due to its incorrect usage of 'regexp()'",
|
"message": "Style was not applied due to its incorrect usage of 'regexp()'",
|
||||||
"description": "Tooltip in the popup for styles that were not applied at all"
|
"description": "Tooltip in the popup for styles that were not applied at all"
|
||||||
},
|
},
|
||||||
|
"styleNotAppliedSchemeDark": {
|
||||||
|
"message": "This style is only applied in Dark Mode"
|
||||||
|
},
|
||||||
|
"styleNotAppliedSchemeLight": {
|
||||||
|
"message": "This style is only applied in Light Mode"
|
||||||
|
},
|
||||||
"styleRegexpInvalidExplanation": {
|
"styleRegexpInvalidExplanation": {
|
||||||
"message": "Some 'regexp()' rules that could not be compiled at all."
|
"message": "Some 'regexp()' rules that could not be compiled at all."
|
||||||
},
|
},
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
findExistingTab
|
findExistingTab
|
||||||
openURL
|
openURL
|
||||||
*/ // toolbox.js
|
*/ // toolbox.js
|
||||||
|
/* global colorScheme */ // color-scheme.js
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
//#region API
|
//#region API
|
||||||
|
@ -41,6 +42,7 @@ addAPI(/** @namespace API */ {
|
||||||
updater: updateMan,
|
updater: updateMan,
|
||||||
usercss: usercssMan,
|
usercss: usercssMan,
|
||||||
usw: uswApi,
|
usw: uswApi,
|
||||||
|
colorScheme,
|
||||||
/** @type {BackgroundWorker} */
|
/** @type {BackgroundWorker} */
|
||||||
worker: createWorker({url: '/background/background-worker'}),
|
worker: createWorker({url: '/background/background-worker'}),
|
||||||
|
|
||||||
|
|
100
background/color-scheme.js
Normal file
100
background/color-scheme.js
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
/* global prefs */
|
||||||
|
/* exported colorScheme */
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const colorScheme = (() => {
|
||||||
|
let systemPreferDark = false;
|
||||||
|
let timePreferDark = false;
|
||||||
|
const changeListeners = new Set();
|
||||||
|
|
||||||
|
const checkTime = ['schemeSwitcher.nightStart', 'schemeSwitcher.nightEnd'];
|
||||||
|
prefs.subscribe(checkTime, (key, value) => {
|
||||||
|
updateTimePreferDark();
|
||||||
|
createAlarm(key, value);
|
||||||
|
});
|
||||||
|
checkTime.forEach(key => createAlarm(key, prefs.get(key)));
|
||||||
|
|
||||||
|
prefs.subscribe(['schemeSwitcher.enabled'], emitChange);
|
||||||
|
|
||||||
|
chrome.alarms.onAlarm.addListener(info => {
|
||||||
|
if (checkTime.includes(info.name)) {
|
||||||
|
updateTimePreferDark();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
updateSystemPreferDark();
|
||||||
|
updateTimePreferDark();
|
||||||
|
|
||||||
|
return {shouldIncludeStyle, onChange, updateSystemPreferDark};
|
||||||
|
|
||||||
|
function createAlarm(key, value) {
|
||||||
|
const date = new Date();
|
||||||
|
applyDate(date, value);
|
||||||
|
if (date.getTime() < Date.now()) {
|
||||||
|
date.setDate(date.getDate() + 1);
|
||||||
|
}
|
||||||
|
chrome.alarms.create(key, {
|
||||||
|
when: date.getTime(),
|
||||||
|
periodInMinutes: 24 * 60,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function shouldIncludeStyle(style) {
|
||||||
|
const isDark = style.preferScheme === 'dark';
|
||||||
|
const isLight = style.preferScheme === 'light';
|
||||||
|
if (!isDark && !isLight) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const switcherState = prefs.get('schemeSwitcher.enabled');
|
||||||
|
if (switcherState === 'never') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (switcherState === 'system') {
|
||||||
|
return systemPreferDark && isDark ||
|
||||||
|
!systemPreferDark && isLight;
|
||||||
|
}
|
||||||
|
return timePreferDark && isDark ||
|
||||||
|
!timePreferDark && isLight;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSystemPreferDark() {
|
||||||
|
const oldValue = systemPreferDark;
|
||||||
|
systemPreferDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
||||||
|
if (systemPreferDark !== oldValue) {
|
||||||
|
emitChange();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateTimePreferDark() {
|
||||||
|
const oldValue = timePreferDark;
|
||||||
|
const date = new Date();
|
||||||
|
const now = date.getTime();
|
||||||
|
applyDate(date, prefs.get('schemeSwitcher.nightStart'));
|
||||||
|
const start = date.getTime();
|
||||||
|
applyDate(date, prefs.get('schemeSwitcher.nightEnd'));
|
||||||
|
const end = date.getTime();
|
||||||
|
timePreferDark = start > end ?
|
||||||
|
now >= start || now < end :
|
||||||
|
now >= start && now < end;
|
||||||
|
if (timePreferDark !== oldValue) {
|
||||||
|
emitChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyDate(date, time) {
|
||||||
|
const [h, m] = time.split(':').map(Number);
|
||||||
|
date.setHours(h, m, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onChange(listener) {
|
||||||
|
changeListeners.add(listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
function emitChange() {
|
||||||
|
for (const listener of changeListeners) {
|
||||||
|
listener();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})();
|
|
@ -6,6 +6,7 @@
|
||||||
/* global prefs */
|
/* global prefs */
|
||||||
/* global tabMan */
|
/* global tabMan */
|
||||||
/* global usercssMan */
|
/* global usercssMan */
|
||||||
|
/* global colorScheme */
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -61,6 +62,14 @@ const styleMan = (() => {
|
||||||
let ready = init();
|
let ready = init();
|
||||||
|
|
||||||
chrome.runtime.onConnect.addListener(handleLivePreview);
|
chrome.runtime.onConnect.addListener(handleLivePreview);
|
||||||
|
// function handleColorScheme() {
|
||||||
|
colorScheme.onChange(() => {
|
||||||
|
for (const {style: data} of dataMap.values()) {
|
||||||
|
if (data.preferScheme === 'dark' || data.preferScheme === 'light') {
|
||||||
|
broadcastStyleUpdated(data, 'colorScheme', undefined, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
//#endregion
|
//#endregion
|
||||||
//#region Exports
|
//#region Exports
|
||||||
|
@ -183,6 +192,7 @@ const styleMan = (() => {
|
||||||
const query = createMatchQuery(url);
|
const query = createMatchQuery(url);
|
||||||
for (const style of styles) {
|
for (const style of styles) {
|
||||||
let excluded = false;
|
let excluded = false;
|
||||||
|
let excludedScheme = false;
|
||||||
let sloppy = false;
|
let sloppy = false;
|
||||||
let sectionMatched = false;
|
let sectionMatched = false;
|
||||||
const match = urlMatchStyle(query, style);
|
const match = urlMatchStyle(query, style);
|
||||||
|
@ -193,6 +203,9 @@ const styleMan = (() => {
|
||||||
if (match === 'excluded') {
|
if (match === 'excluded') {
|
||||||
excluded = true;
|
excluded = true;
|
||||||
}
|
}
|
||||||
|
if (match === 'excludedScheme') {
|
||||||
|
excludedScheme = true;
|
||||||
|
}
|
||||||
for (const section of style.sections) {
|
for (const section of style.sections) {
|
||||||
if (styleSectionGlobal(section) && styleCodeEmpty(section.code)) {
|
if (styleSectionGlobal(section) && styleCodeEmpty(section.code)) {
|
||||||
continue;
|
continue;
|
||||||
|
@ -207,7 +220,7 @@ const styleMan = (() => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (sectionMatched) {
|
if (sectionMatched) {
|
||||||
result.push(/** @namespace StylesByUrlResult */ {style, excluded, sloppy});
|
result.push(/** @namespace StylesByUrlResult */ {style, excluded, sloppy, excludedScheme});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
@ -546,6 +559,9 @@ const styleMan = (() => {
|
||||||
if (!style.enabled) {
|
if (!style.enabled) {
|
||||||
return 'disabled';
|
return 'disabled';
|
||||||
}
|
}
|
||||||
|
if (!colorScheme.shouldIncludeStyle(style)) {
|
||||||
|
return 'excludedScheme';
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -68,6 +68,13 @@
|
||||||
window.addEventListener(orphanEventId, orphanCheck, true);
|
window.addEventListener(orphanEventId, orphanCheck, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// detect media change in content script
|
||||||
|
// FIXME: move this to background page when following bugs are fixed:
|
||||||
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1561546
|
||||||
|
// https://bugs.chromium.org/p/chromium/issues/detail?id=968651
|
||||||
|
const media = window.matchMedia('(prefers-color-scheme: dark)');
|
||||||
|
media.addListener(() => API.colorScheme.updateSystemPreferDark().catch(console.error));
|
||||||
|
|
||||||
function onInjectorUpdate() {
|
function onInjectorUpdate() {
|
||||||
if (!isOrphaned) {
|
if (!isOrphaned) {
|
||||||
updateCount();
|
updateCount();
|
||||||
|
|
|
@ -50,6 +50,14 @@
|
||||||
<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="liveReloadLabel"></span>
|
<span i18n-text="liveReloadLabel"></span>
|
||||||
</label>
|
</label>
|
||||||
|
<label class="set-prefer-scheme">
|
||||||
|
<span i18n-text="installPreferSchemeLabel"></span>
|
||||||
|
<select>
|
||||||
|
<option value="none" i18n-text="installPreferSchemeNone"></option>
|
||||||
|
<option value="dark" i18n-text="installPreferSchemeDark"></option>
|
||||||
|
<option value="light" i18n-text="installPreferSchemeLight"></option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
<p hidden class="installed-actions">
|
<p hidden class="installed-actions">
|
||||||
<a href="manage.html" tabindex="0"><button i18n-text="openManage"></button></a>
|
<a href="manage.html" tabindex="0"><button i18n-text="openManage"></button></a>
|
||||||
<a href="edit.html?id=" tabindex="0"><button i18n-text="editStyleLabel"></button></a>
|
<a href="edit.html?id=" tabindex="0"><button i18n-text="editStyleLabel"></button></a>
|
||||||
|
|
|
@ -242,7 +242,6 @@ h2.installed.active ~ .configure-usercss:hover svg {
|
||||||
.set-update-url {
|
.set-update-url {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
.set-update-url p {
|
.set-update-url p {
|
||||||
word-break: break-all;
|
word-break: break-all;
|
||||||
opacity: .5;
|
opacity: .5;
|
||||||
|
@ -250,6 +249,10 @@ h2.installed.active ~ .configure-usercss:hover svg {
|
||||||
margin: .25em 0 .25em;
|
margin: .25em 0 .25em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
label.set-prefer-scheme:not(.unavailable) {
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.external {
|
.external {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
@ -299,6 +302,7 @@ li {
|
||||||
}
|
}
|
||||||
|
|
||||||
label {
|
label {
|
||||||
|
/* FIXME: why do we want to give all labels a padding? */
|
||||||
padding-left: 16px;
|
padding-left: 16px;
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,6 +135,13 @@ setTimeout(() => !cm && showSpinner($('#header')), 200);
|
||||||
$('.set-update-url p').textContent = updateUrl.href.length < 300 ? updateUrl.href :
|
$('.set-update-url p').textContent = updateUrl.href.length < 300 ? updateUrl.href :
|
||||||
updateUrl.href.slice(0, 300) + '...';
|
updateUrl.href.slice(0, 300) + '...';
|
||||||
|
|
||||||
|
// set prefer scheme
|
||||||
|
const preferScheme = $('.set-prefer-scheme select');
|
||||||
|
preferScheme.onchange = () => {
|
||||||
|
style.preferScheme = preferScheme.value;
|
||||||
|
};
|
||||||
|
preferScheme.onchange();
|
||||||
|
|
||||||
if (URLS.isLocalhost(initialUrl)) {
|
if (URLS.isLocalhost(initialUrl)) {
|
||||||
$('.live-reload input').onchange = liveReload.onToggled;
|
$('.live-reload input').onchange = liveReload.onToggled;
|
||||||
} else {
|
} else {
|
||||||
|
@ -167,6 +174,9 @@ function updateMeta(style, dup = installedDup) {
|
||||||
$('.meta-name').textContent = data.name;
|
$('.meta-name').textContent = data.name;
|
||||||
$('.meta-version').textContent = data.version;
|
$('.meta-version').textContent = data.version;
|
||||||
$('.meta-description').textContent = data.description;
|
$('.meta-description').textContent = data.description;
|
||||||
|
$('.set-prefer-scheme select').value =
|
||||||
|
style.preferScheme === 'dark' ? 'dark' :
|
||||||
|
style.preferScheme === 'light' ? 'light' : 'none';
|
||||||
|
|
||||||
if (data.author) {
|
if (data.author) {
|
||||||
$('.meta-author').parentNode.style.display = '';
|
$('.meta-author').parentNode.style.display = '';
|
||||||
|
@ -305,6 +315,7 @@ function install(style) {
|
||||||
$('.set-update-url input[type=checkbox]').disabled = true;
|
$('.set-update-url input[type=checkbox]').disabled = true;
|
||||||
$('.set-update-url').title = style.updateUrl ?
|
$('.set-update-url').title = style.updateUrl ?
|
||||||
t('installUpdateFrom', style.updateUrl) : '';
|
t('installUpdateFrom', style.updateUrl) : '';
|
||||||
|
$('.set-prefer-scheme select').disabled = true;
|
||||||
enablePostActions();
|
enablePostActions();
|
||||||
updateMeta(style);
|
updateMeta(style);
|
||||||
}
|
}
|
||||||
|
|
59
js/dom.js
59
js/dom.js
|
@ -291,39 +291,64 @@ function scrollElementIntoView(element, {invalidMarginRatio = 0} = {}) {
|
||||||
* Accepts an array of pref names (values are fetched via prefs.get)
|
* Accepts an array of pref names (values are fetched via prefs.get)
|
||||||
* and establishes a two-way connection between the document elements and the actual prefs
|
* and establishes a two-way connection between the document elements and the actual prefs
|
||||||
*/
|
*/
|
||||||
function setupLivePrefs(ids = prefs.knownKeys.filter(id => $('#' + id))) {
|
function setupLivePrefs(ids = prefs.knownKeys.filter(id => $(`#${CSS.escape(id)}, [name=${CSS.escape(id)}]`))) {
|
||||||
let forceUpdate = true;
|
let forceUpdate = true;
|
||||||
prefs.subscribe(ids, updateElement, {runNow: true});
|
prefs.subscribe(ids, updateElement, {runNow: true});
|
||||||
forceUpdate = false;
|
forceUpdate = false;
|
||||||
ids.forEach(id => $('#' + id).on('change', onChange));
|
|
||||||
|
for (const id of ids) {
|
||||||
|
const elements = $$(`#${CSS.escape(id)}, [name=${CSS.escape(id)}]`);
|
||||||
|
for (const element of elements) {
|
||||||
|
element.addEventListener('change', onChange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function onChange() {
|
function onChange() {
|
||||||
prefs.set(this.id, this[getPropName(this)]);
|
if (!this.checkValidity()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (this.type === 'radio' && !this.checked) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
prefs.set(this.id || this.name, getValue(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
function getPropName(el) {
|
function getValue(el) {
|
||||||
return el.type === 'checkbox' ? 'checked'
|
const type = el.dataset.valueType || el.type;
|
||||||
: el.type === 'number' ? 'valueAsNumber' :
|
return type === 'checkbox' ? el.checked :
|
||||||
'value';
|
// https://stackoverflow.com/questions/18062069/why-does-valueasnumber-return-nan-as-a-value
|
||||||
|
// valueAsNumber is not applicable for input[text/radio] or select
|
||||||
|
type === 'number' ? Number(el.value) :
|
||||||
|
el.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isSame(el, propName, value) {
|
function isSame(el, oldValue, value) {
|
||||||
return el[propName] === value ||
|
return oldValue === value ||
|
||||||
typeof value === 'boolean' &&
|
typeof value === 'boolean' &&
|
||||||
el.tagName === 'SELECT' &&
|
el.tagName === 'SELECT' &&
|
||||||
el[propName] === `${value}`;
|
oldValue === `${value}` ||
|
||||||
|
el.type === 'radio' && (oldValue === value) === el.checked;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateElement(id, value) {
|
function updateElement(id, value) {
|
||||||
const el = $('#' + id);
|
const els = $$(`#${CSS.escape(id)}, [name=${CSS.escape(id)}]`);
|
||||||
if (el) {
|
if (!els.length) {
|
||||||
const prop = getPropName(el);
|
// FIXME: why do we unsub all ids when a single id is missing from the page
|
||||||
if (!isSame(el, prop, value) || forceUpdate) {
|
prefs.unsubscribe(ids, updateElement);
|
||||||
el[prop] = value;
|
return;
|
||||||
|
}
|
||||||
|
for (const el of els) {
|
||||||
|
const oldValue = getValue(el);
|
||||||
|
if (!isSame(el, oldValue, value) || forceUpdate) {
|
||||||
|
if (el.type === 'radio') {
|
||||||
|
el.checked = value === oldValue;
|
||||||
|
} else if (el.type === 'checkbox') {
|
||||||
|
el.checked = value;
|
||||||
|
} else {
|
||||||
|
el.value = value;
|
||||||
|
}
|
||||||
el.dispatchEvent(new Event('change', {bubbles: true}));
|
el.dispatchEvent(new Event('change', {bubbles: true}));
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
prefs.unsubscribe(ids, updateElement);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,6 +30,10 @@
|
||||||
// checkbox in style config dialog
|
// checkbox in style config dialog
|
||||||
'config.autosave': true,
|
'config.autosave': true,
|
||||||
|
|
||||||
|
'schemeSwitcher.enabled': 'never',
|
||||||
|
'schemeSwitcher.nightStart': '18:00',
|
||||||
|
'schemeSwitcher.nightEnd': '06:00',
|
||||||
|
|
||||||
'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
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
"background/common.js",
|
"background/common.js",
|
||||||
|
|
||||||
"background/db.js",
|
"background/db.js",
|
||||||
|
"background/color-scheme.js",
|
||||||
"background/icon-manager.js",
|
"background/icon-manager.js",
|
||||||
"background/navigation-manager.js",
|
"background/navigation-manager.js",
|
||||||
"background/style-search-db.js",
|
"background/style-search-db.js",
|
||||||
|
|
24
options.html
24
options.html
|
@ -52,7 +52,7 @@
|
||||||
<label>
|
<label>
|
||||||
<span i18n-text="optionsIconDark"></span>
|
<span i18n-text="optionsIconDark"></span>
|
||||||
<div class="iconset">
|
<div class="iconset">
|
||||||
<input type="radio" name="iconset">
|
<input type="radio" name="iconset" value="0" data-value-type="number">
|
||||||
<img src="/images/icon/16.png">
|
<img src="/images/icon/16.png">
|
||||||
<img src="/images/icon/16w.png">
|
<img src="/images/icon/16w.png">
|
||||||
<img src="/images/icon/16x.png">
|
<img src="/images/icon/16x.png">
|
||||||
|
@ -61,7 +61,7 @@
|
||||||
<label>
|
<label>
|
||||||
<span i18n-text="optionsIconLight"></span>
|
<span i18n-text="optionsIconLight"></span>
|
||||||
<div class="iconset">
|
<div class="iconset">
|
||||||
<input type="radio" name="iconset">
|
<input type="radio" name="iconset" value="1" data-value-type="number">
|
||||||
<img src="/images/icon/light/16.png">
|
<img src="/images/icon/light/16.png">
|
||||||
<img src="/images/icon/light/16w.png">
|
<img src="/images/icon/light/16w.png">
|
||||||
<img src="/images/icon/light/16x.png">
|
<img src="/images/icon/light/16x.png">
|
||||||
|
@ -272,6 +272,26 @@
|
||||||
<span></span>
|
<span></span>
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
|
<div class="radio-group">
|
||||||
|
<span i18n-text="optionsAdvancedAutoSwitchScheme" class="radio-group-label"></span>
|
||||||
|
<label class="radio-group-item">
|
||||||
|
<input type="radio" value="never" name="schemeSwitcher.enabled" class="radio">
|
||||||
|
<span i18n-text="optionsAdvancedAutoSwitchSchemeNever"></span>
|
||||||
|
</label>
|
||||||
|
<label class="radio-group-item">
|
||||||
|
<input type="radio" value="system" name="schemeSwitcher.enabled" class="radio">
|
||||||
|
<span i18n-text="optionsAdvancedAutoSwitchSchemeBySystem"></span>
|
||||||
|
</label>
|
||||||
|
<label class="radio-group-item">
|
||||||
|
<input type="radio" value="time" name="schemeSwitcher.enabled" class="radio">
|
||||||
|
<span>
|
||||||
|
<span i18n-text="optionsAdvancedAutoSwitchSchemeByTime"></span>
|
||||||
|
<input type="text" pattern="\d{2}:\d{2}" required id="schemeSwitcher.nightStart" class="input-sm">
|
||||||
|
~
|
||||||
|
<input type="text" pattern="\d{2}:\d{2}" required id="schemeSwitcher.nightEnd" class="input-sm">
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -192,7 +192,8 @@ input[type=number] {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type=number]:invalid {
|
input[type=number]:invalid,
|
||||||
|
input[type=text]:invalid {
|
||||||
background-color: rgba(255, 0, 0, 0.1);
|
background-color: rgba(255, 0, 0, 0.1);
|
||||||
color: darkred;
|
color: darkred;
|
||||||
}
|
}
|
||||||
|
@ -376,6 +377,40 @@ html:not(.firefox):not(.opera) #updates {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* radio group */
|
||||||
|
.radio-group-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
min-height: 1.5em;
|
||||||
|
}
|
||||||
|
.radio-group-item > input {
|
||||||
|
margin: 0 8px 0 0;
|
||||||
|
flex-grow: 0;
|
||||||
|
}
|
||||||
|
.radio-group-label {
|
||||||
|
display: block;
|
||||||
|
margin: 0 0 .3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-sm {
|
||||||
|
width: 3em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* pixel perfect radio */
|
||||||
|
input[type="radio"].radio::after {
|
||||||
|
position: absolute;
|
||||||
|
top: -1px;
|
||||||
|
right: -1px;
|
||||||
|
bottom: -1px;
|
||||||
|
left: -1px;
|
||||||
|
height: auto;
|
||||||
|
width: auto;
|
||||||
|
transform: scale(0);
|
||||||
|
}
|
||||||
|
input[type="radio"].radio:checked::after {
|
||||||
|
transform: scale(.65);
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes fadeinout {
|
@keyframes fadeinout {
|
||||||
0% { opacity: 0 }
|
0% { opacity: 0 }
|
||||||
10% { opacity: 1 }
|
10% { opacity: 1 }
|
||||||
|
|
|
@ -23,7 +23,6 @@
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
setupLivePrefs();
|
setupLivePrefs();
|
||||||
setupRadioButtons();
|
|
||||||
$$('input[min], input[max]').forEach(enforceInputRange);
|
$$('input[min], input[max]').forEach(enforceInputRange);
|
||||||
|
|
||||||
if (CHROME_POPUP_BORDER_BUG) {
|
if (CHROME_POPUP_BORDER_BUG) {
|
||||||
|
@ -196,29 +195,6 @@ function checkUpdates() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupRadioButtons() {
|
|
||||||
const sets = {};
|
|
||||||
const onChange = function () {
|
|
||||||
const newValue = sets[this.name].indexOf(this);
|
|
||||||
if (newValue >= 0 && prefs.get(this.name) !== newValue) {
|
|
||||||
prefs.set(this.name, newValue);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
// group all radio-inputs by name="prefName" attribute
|
|
||||||
for (const el of $$('input[type="radio"][name]')) {
|
|
||||||
(sets[el.name] = sets[el.name] || []).push(el);
|
|
||||||
el.on('change', onChange);
|
|
||||||
}
|
|
||||||
// select the input corresponding to the actual pref value
|
|
||||||
for (const name in sets) {
|
|
||||||
sets[name][prefs.get(name)].checked = true;
|
|
||||||
}
|
|
||||||
// listen to pref changes and update the values
|
|
||||||
prefs.subscribe(Object.keys(sets), (key, value) => {
|
|
||||||
sets[key][value].checked = true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function customizeHotkeys() {
|
function customizeHotkeys() {
|
||||||
// command name -> i18n id
|
// command name -> i18n id
|
||||||
const hotkeys = new Map([
|
const hotkeys = new Map([
|
||||||
|
|
8349
package-lock.json
generated
8349
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
|
@ -27,7 +27,7 @@
|
||||||
"make-fetch-happen": "^8.0.7",
|
"make-fetch-happen": "^8.0.7",
|
||||||
"sync-version": "^1.0.1",
|
"sync-version": "^1.0.1",
|
||||||
"tiny-glob": "^0.2.6",
|
"tiny-glob": "^0.2.6",
|
||||||
"web-ext": "^5.5.0"
|
"web-ext": "^6.5.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "eslint \"**/*.js\" --cache",
|
"lint": "eslint \"**/*.js\" --cache",
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
CHROME_POPUP_BORDER_BUG
|
CHROME_POPUP_BORDER_BUG
|
||||||
FIREFOX
|
FIREFOX
|
||||||
URLS
|
URLS
|
||||||
|
capitalize
|
||||||
getActiveTab
|
getActiveTab
|
||||||
isEmptyObj
|
isEmptyObj
|
||||||
*/// toolbox.js
|
*/// toolbox.js
|
||||||
|
@ -372,13 +373,14 @@ function createStyleElement(style) {
|
||||||
const styleName = $('.style-name', entry);
|
const styleName = $('.style-name', entry);
|
||||||
styleName.lastChild.textContent = style.customName || style.name;
|
styleName.lastChild.textContent = style.customName || style.name;
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
styleName.title = entry.styleMeta.sloppy ?
|
styleName.title =
|
||||||
t('styleNotAppliedRegexpProblemTooltip') :
|
entry.styleMeta.sloppy ? t('styleNotAppliedRegexpProblemTooltip') :
|
||||||
styleName.scrollWidth > styleName.clientWidth + 1 ?
|
entry.styleMeta.excludedScheme ? t(`styleNotAppliedScheme${capitalize(entry.styleMeta.preferScheme)}`) :
|
||||||
styleName.textContent : '';
|
styleName.scrollWidth > styleName.clientWidth + 1 ? styleName.textContent :
|
||||||
|
'';
|
||||||
});
|
});
|
||||||
|
|
||||||
entry.classList.toggle('not-applied', style.excluded || style.sloppy);
|
entry.classList.toggle('not-applied', style.excluded || style.sloppy || style.excludedScheme);
|
||||||
entry.classList.toggle('regexp-partial', style.sloppy);
|
entry.classList.toggle('regexp-partial', style.sloppy);
|
||||||
|
|
||||||
$('.exclude-by-domain-checkbox', entry).checked = Events.isStyleExcluded(style, 'domain');
|
$('.exclude-by-domain-checkbox', entry).checked = Events.isStyleExcluded(style, 'domain');
|
||||||
|
|
Loading…
Reference in New Issue
Block a user