diff --git a/_locales/en/messages.json b/_locales/en/messages.json index e1b3416c..57bf1b02 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -623,18 +623,6 @@ "message": "Update style", "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": { "message": "Install update", "description": "Label for the button to install an update for a single style" @@ -1098,11 +1086,8 @@ "optionsAdvancedNewStyleAsUsercss": { "message": "Write new style as usercss" }, - "optionsAdvancedAutoSwitchScheme": { - "message": "Toggle Light/Dark Mode styles automatically" - }, "optionsAdvancedAutoSwitchSchemeNever": { - "message": "Never" + "message": "Disabled. The dark/light setting in styles is ignored." }, "optionsAdvancedAutoSwitchSchemeBySystem": { "message": "By system preference" @@ -1153,6 +1138,9 @@ "message": "Options", "description": "Heading for options section on manage page." }, + "optionsIconAuto": { + "message": "Match the Dark/Light mode" + }, "optionsIconDark": { "message": "Dark browser themes" }, @@ -1323,6 +1311,21 @@ "message": "Styles before commands", "description": "Label for the checkbox controlling section order in the popup." }, + "preferScheme": { + "message": "Dark/Light mode preference" + }, + "preferSchemeAlways": { + "message": "Currently ignored (the style always applies) because the global Dark/Light mode is disabled" + }, + "preferSchemeDark": { + "message": "Dark" + }, + "preferSchemeLight": { + "message": "Light" + }, + "preferSchemeNone": { + "message": "None (always applied)" + }, "prefShowBadge": { "message": "Number of styles active for the current site", "description": "Label for the checkbox controlling toolbar badge text." diff --git a/background/color-scheme.js b/background/color-scheme.js index 66e2e4c6..1c389623 100644 --- a/background/color-scheme.js +++ b/background/color-scheme.js @@ -4,33 +4,59 @@ 'use strict'; const colorScheme = (() => { - let systemPreferDark = false; - let timePreferDark = false; const changeListeners = new Set(); + const kSTATE = 'schemeSwitcher.enabled'; + const kSTART = 'schemeSwitcher.nightStart'; + const kEND = 'schemeSwitcher.nightEnd'; + const SCHEMES = ['dark', 'light']; + const isDark = { + never: null, + dark: true, + light: false, + system: false, + time: false, + }; + let isDarkNow = false; - const checkTime = ['schemeSwitcher.nightStart', 'schemeSwitcher.nightEnd']; - prefs.subscribe(checkTime, (key, value) => { + prefs.subscribe(kSTATE, () => update()); + prefs.subscribe([kSTART, kEND], (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)) { + }, {runNow: true}); + chrome.alarms.onAlarm.addListener(({name}) => { + if (name === kSTART || name === kEND) { updateTimePreferDark(); } }); - updateSystemPreferDark(); - updateTimePreferDark(); + return { + SCHEMES, + onChange(listener) { + changeListeners.add(listener); + }, + /** @param {StyleObj | 'darkUI'} val - the string is used by the built-in dark themer */ + shouldIncludeStyle(val) { + return val === 'darkUI' + ? isDarkNow + : prefs.get(kSTATE) === 'never' || + !SCHEMES.includes(val = val.preferScheme) || + isDarkNow === (val === 'dark'); + }, + updateSystemPreferDark(val) { + update('system', val); + return true; + }, + }; - return {shouldIncludeStyle, onChange, updateSystemPreferDark}; + function calcTime(key) { + const [h, m] = prefs.get(key).split(':'); + return (h * 3600 + m * 60) * 1000; + } function createAlarm(key, value) { const date = new Date(); - applyDate(date, value); + const [h, m] = value.split(':'); + date.setHours(h, m, 0, 0); if (date.getTime() < Date.now()) { date.setDate(date.getDate() + 1); } @@ -40,61 +66,27 @@ const colorScheme = (() => { }); } - 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 ? + const now = Date.now() - new Date().setHours(0, 0, 0, 0); + const start = calcTime(kSTART); + const end = calcTime(kEND); + const val = start > end ? now >= start || now < end : now >= start && now < end; - if (timePreferDark !== oldValue) { - emitChange(); + update('time', val); + } + + function update(type, val) { + if (type) { + if (isDark[type] === val) return; + isDark[type] = val; } - } - - 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(); + val = isDark[prefs.get(kSTATE)]; + if (isDarkNow !== val) { + isDarkNow = val; + for (const listener of changeListeners) { + listener(isDarkNow); + } } } })(); diff --git a/background/icon-manager.js b/background/icon-manager.js index 9b8d1f2d..97490d43 100644 --- a/background/icon-manager.js +++ b/background/icon-manager.js @@ -1,5 +1,6 @@ /* global API */// msg.js /* global addAPI bgReady */// common.js +/* global colorScheme */ /* global prefs */ /* global tabMan */ /* global CHROME FIREFOX UA debounce ignoreChromeError */// toolbox.js @@ -13,7 +14,7 @@ const iconMan = (() => { const badgeOvr = {color: '', text: ''}; // https://github.com/openstyles/stylus/issues/1287 Fenix can't use custom ImageData const FIREFOX_ANDROID = FIREFOX && UA.mobile; - + let isDark; // https://github.com/openstyles/stylus/issues/335 let hasCanvas = FIREFOX_ANDROID ? false : loadImage(`/images/icon/${ICON_SIZES[0]}.png`) .then(({data}) => (hasCanvas = data.some(b => b !== 255))); @@ -37,13 +38,17 @@ const iconMan = (() => { chrome.webNavigation.onCommitted.addListener(({tabId, frameId}) => { if (!frameId) tabMan.set(tabId, 'styleIds', undefined); }); - chrome.runtime.onConnect.addListener(port => { if (port.name === 'iframe') { port.onDisconnect.addListener(onPortDisconnected); } }); - + colorScheme.onChange(val => { + isDark = val; + if (prefs.get('iconset') === -1) { + debounce(refreshAllIcons); + } + }); bgReady.all.then(() => { prefs.subscribe([ 'disableAll', @@ -95,9 +100,10 @@ const iconMan = (() => { } function getIconName(hasStyles = false) { - const iconset = prefs.get('iconset') === 1 ? 'light/' : ''; + const i = prefs.get('iconset'); + const prefix = i === 0 || i === -1 && isDark ? '' : 'light/'; const postfix = prefs.get('disableAll') ? 'x' : !hasStyles ? 'w' : ''; - return `${iconset}$SIZE$${postfix}`; + return `${prefix}$SIZE$${postfix}`; } function refreshIcon(tabId, force = false) { diff --git a/background/style-manager.js b/background/style-manager.js index fed12aee..a4094c40 100644 --- a/background/style-manager.js +++ b/background/style-manager.js @@ -84,11 +84,11 @@ const styleMan = (() => { handleDraft(port); } }); - // function handleColorScheme() { - colorScheme.onChange(() => { - for (const {style: data} of dataMap.values()) { - if (data.preferScheme === 'dark' || data.preferScheme === 'light') { - broadcastStyleUpdated(data, 'colorScheme', undefined, false); + colorScheme.onChange(value => { + msg.broadcastExtension({method: 'colorScheme', value}); + for (const {style} of dataMap.values()) { + if (colorScheme.SCHEMES.includes(style.preferScheme)) { + broadcastStyleUpdated(style, 'colorScheme'); } } }); @@ -320,7 +320,8 @@ const styleMan = (() => { async config(id, prop, value) { if (ready.then) await ready; const style = Object.assign({}, id2style(id)); - style[prop] = value; + const {preview = {}} = dataMap.get(id); + style[prop] = preview[prop] = value; return saveStyle(style, {reason: 'config'}); }, }; diff --git a/content/apply.js b/content/apply.js index 5d490ff4..ce2e86a7 100644 --- a/content/apply.js +++ b/content/apply.js @@ -52,6 +52,12 @@ xo.observe(el); }; + // FIXME: move this to background page when following bugs are fixed: + // https://bugzil.la/1587723, https://crbug.com/968651 + const mqDark = matchMedia('(prefers-color-scheme: dark)'); + mqDark.onchange = e => API.colorScheme.updateSystemPreferDark(e.matches); + mqDark.onchange(mqDark); + // Declare all vars before init() or it'll throw due to "temporal dead zone" of const/let const ready = init(); @@ -70,13 +76,6 @@ 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() { if (!isOrphaned) { updateCount(); @@ -265,6 +264,7 @@ // In Chrome content script is orphaned on an extension update/reload // so we need to detach event listeners window.removeEventListener(orphanEventId, orphanCheck, true); + mqDark.onchange = null; isOrphaned = true; setTimeout(styleInjector.clear, 1000); // avoiding FOUC tryCatch(msg.off, applyOnMessage); diff --git a/edit.html b/edit.html index 8dd7cf5e..1b08f9eb 100644 --- a/edit.html +++ b/edit.html @@ -5,6 +5,7 @@ + @@ -239,6 +240,7 @@ +
diff --git a/edit/codemirror-default.css b/edit/codemirror-default.css index 73347813..0a8ef287 100644 --- a/edit/codemirror-default.css +++ b/edit/codemirror-default.css @@ -4,13 +4,23 @@ z-index: 999; } .CodeMirror-hint:hover { - color: white; + color: var(--bg); background: #08f; } .CodeMirror { - border: solid #CCC 1px; + border: solid var(--c80) 1px; transition: box-shadow .1s; } +.CodeMirror { + color: inherit; + background-color: inherit; + border: solid var(--c80) 1px; + transition: box-shadow .1s; +} +.CodeMirror-gutters { + background-color: var(--c95); + border-color: var(--c85); +} #stylus#stylus .CodeMirror { /* Using a specificity hack to override userstyles */ /* Not using the ring-color hack as it became ugly in new Chrome */ @@ -26,7 +36,7 @@ width: 5em; } .CodeMirror-search-hint { - color: #888; + color: var(--c50); } .CodeMirror-activeline .applies-to:before { background-color: hsla(214, 100%, 90%, 0.15); @@ -74,3 +84,58 @@ .gutter-bookmark { background: linear-gradient(0deg, hsla(180, 100%, 30%, .75) 2px, hsla(180, 100%, 30%, .2) 2px); } + +@media screen and (prefers-color-scheme: dark), dark { + .CodeMirror-dialog { + background-color: #333; + } + .CodeMirror-dialog-top { + border-color: #555; + } + .CodeMirror-activeline-background { + background: hsl(180, 21%, 18%); + } + .CodeMirror-selected, + .CodeMirror-focused .CodeMirror-selected, + .CodeMirror-line::selection, + .CodeMirror-line > span::selection, + .CodeMirror-line > span > span::selection { + background: #444; + } + .CodeMirror-line::-moz-selection, + .CodeMirror-line > span::-moz-selection, + .CodeMirror-line > span > span::-moz-selection { + /* TODO: remove this when strict_min_version >= 62 */ + background: #444; + } + .cm-s-default div.CodeMirror-cursor { + border-left: 1px solid #fff; + } + /* Using Chromium's dark devtools colors */ + .cm-s-default .cm-atom, + .cm-s-default .cm-number { color: #a1f7b5 } + .cm-s-default .cm-attribute { color: #6194c6 } + .cm-s-default .cm-bracket { color: #997 } + .cm-s-default .cm-builtin, + .cm-s-default .cm-link { color: #9fb4d6 } + .cm-s-default .cm-comment { color: #747474 } + .cm-s-default .cm-qualifier { color: #ffa34f } + .cm-s-default .cm-def, + .cm-s-default .cm-header, + .cm-s-default .cm-tag, + .cm-s-default .cm-type { color: #5db0d7 } + .cm-s-default .cm-hr { color: #999 } + .cm-s-default .cm-keyword { color: #9a7fd5 } + .cm-s-default .cm-meta { color: #ddfb55 } + .cm-s-default .cm-operator { color: #d2c057 } + .cm-s-default .cm-string { color: #f28b54 } + .cm-s-default .cm-variable { color: #d9d9d9 } + .cm-s-default .cm-variable-2 { color: #72b9ff } + .cm-s-default .cm-variable-3 { color: #9bbbdc } + + @keyframes highlight { + from { + background-color: #888; + } + } +} diff --git a/edit/edit.css b/edit/edit.css index b8acdf96..8ee54035 100644 --- a/edit/edit.css +++ b/edit/edit.css @@ -5,12 +5,11 @@ } body { - margin: 0; height: 100vh; } a { - color: #000; + color: var(--fg); transition: color .5s; } a:hover { @@ -55,8 +54,8 @@ html:not(.is-new-style) #heading::after { top: 0; z-index: 1001; border: none; - background: #fff; - box-shadow: 0 0 30px #000; + background: var(--bg); + box-shadow: 0 0 30px var(--fg); } #popup-iframe:not([data-loaded]) { opacity: 0; @@ -97,7 +96,7 @@ label { position: fixed; top: 0; padding-top: var(--pad); - box-shadow: 0 0 3rem -1.2rem black; + box-shadow: 0 0 3rem -1.2rem #000; box-sizing: border-box; z-index: 10; display: flex; @@ -173,7 +172,7 @@ label { #preview-errors { background-color: red; - color: white; + color: var(--bg); padding: 0 6px; border-radius: 9px; margin-left: -.5em; @@ -206,12 +205,12 @@ label { .svg-icon:hover, .svg-icon.info, .svg-icon.settings { - fill: #666; + fill: var(--c40); } .svg-icon, .svg-icon.info:hover, .svg-icon.settings:hover { - fill: #000; + fill: var(--fg); } #options span .svg-icon { margin-top: -3px; /* inline info and config icons */ @@ -256,7 +255,7 @@ input:invalid { text-overflow: ellipsis; } #header summary:hover h2 { - border-color: #bbb; + border-color: var(--c70); } #header summary svg { margin-top: -3px; @@ -385,7 +384,7 @@ input:invalid { padding: 1rem; } .sectioned .section:not(:first-child) { - border-top: 2px solid hsl(0, 0%, 80%); + border-top: 2px solid var(--c80); } .add-section:after { content: attr(short-text); @@ -649,14 +648,14 @@ body:not(.find-open) [data-match-highlight-count="1"] .CodeMirror-selection-high .add-applies-to .svg-icon, .remove-applies-to .svg-icon { pointer-events: none; - fill: hsl(0, 0%, 60%); + fill: var(--c60); height: 12px; width: 12px; } .add-applies-to:hover .svg-icon, .remove-applies-to:hover .svg-icon { pointer-events: none; - fill: hsl(0, 0%, 0%); + fill: var(--fg); } .test-regexp { display: none; @@ -688,7 +687,7 @@ body:not(.find-open) [data-match-highlight-count="1"] .CodeMirror-selection-high color: darkgreen; } .regexp-report details[data-type="partial"] { - color: darkgray; + color: var(--c65); } .regexp-report details[data-type="invalid"] { color: maroon; @@ -721,7 +720,7 @@ body:not(.find-open) [data-match-highlight-count="1"] .CodeMirror-selection-high margin-right: .5em; } .regexp-report-note { - color: #999; + color: var(--c60); position: absolute; bottom: 0; hyphens: auto; @@ -736,7 +735,7 @@ body:not(.find-open) [data-match-highlight-count="1"] .CodeMirror-selection-high max-width: 50vw; position: fixed; display: none; - background-color: white; + background-color: var(--bg); box-shadow: 3px 3px 30px rgba(0, 0, 0, 0.5); padding: var(--pad-y) var(--pad-x) 0; z-index: 99; @@ -755,7 +754,7 @@ body:not(.find-open) [data-match-highlight-count="1"] .CodeMirror-selection-high } #help-popup .title { font-weight: bold; - background-color: rgba(0,0,0,0.05); + background-color: rgba(128, 128, 128, .15); margin: calc(-1 * var(--pad-y)) calc(-1 * var(--pad-x)) 0; padding: var(--pad-y2) var(--pad-x); } @@ -980,12 +979,12 @@ body:not(.find-open) [data-match-highlight-count="1"] .CodeMirror-selection-high } #footer a { - color: #333; + color: var(--c20); transition: color .5s; } #footer a:hover { - color: #666; + color: var(--c40); } /************ line widget *************/ @@ -1041,14 +1040,14 @@ body:not(.find-open) [data-match-highlight-count="1"] .CodeMirror-selection-high height: unset; width: 100%; overflow: visible; - background: #fff; + background: var(--bg); border-right: none; - border-bottom: 1px dashed #AAA; + border-bottom: 1px dashed var(--c65); padding: var(--pad05) var(--pad05) 0; } #header.sticky { flex-direction: row; - box-shadow: 0 0 3rem -.75rem black; + box-shadow: 0 0 3rem -.75rem; } #header.sticky #basic-info, #header.sticky #mozilla-format-buttons, @@ -1106,7 +1105,7 @@ body:not(.find-open) [data-match-highlight-count="1"] .CodeMirror-selection-high position: absolute; overflow: hidden auto; max-height: var(--max-height, 10vh); - background: #fff; + background: var(--bg); box-shadow: 0 6px 20px rgba(0, 0, 0, .3); padding: var(--pad); margin-top: var(--pad05); diff --git a/edit/edit.js b/edit/edit.js index 910ea6d1..ca514d92 100644 --- a/edit/edit.js +++ b/edit/edit.js @@ -140,7 +140,6 @@ async function handleExternalUpdate({style, reason}) { delete style.name; delete style.enabled; Object.assign(editor.style, style); - editor.updateLivePreview(); } else { await editor.replaceStyle(style); } diff --git a/edit/global-search.js b/edit/global-search.js index e4ebbaa8..359add49 100644 --- a/edit/global-search.js +++ b/edit/global-search.js @@ -607,10 +607,10 @@ } #search-replace-dialog[data-type="replace"] button:hover svg, #search-replace-dialog svg:hover { - fill: inherit; + fill: var(--cmin); } #search-replace-dialog [data-action="case"]:hover { - color: inherit; + color: var(--cmin); } #search-replace-dialog [data-action="clear"] { background-color: ${colors.input.bg.replace(/[^,]+$/, '') + '.75)'}; diff --git a/edit/moz-section-widget.js b/edit/moz-section-widget.js index 674d03b1..84ffb0a2 100644 --- a/edit/moz-section-widget.js +++ b/edit/moz-section-widget.js @@ -158,6 +158,7 @@ function MozSectionWidget(cm, finder = MozSectionFinder(cm)) { } if (msg.style || msg.styles || msg.prefs && 'disableAll' in msg.prefs || + msg.method === 'colorScheme' || msg.method === 'styleDeleted') { requestAnimationFrame(updateWidgetStyle); } diff --git a/edit/settings.css b/edit/settings.css index 3bf71cce..1cfd7789 100644 --- a/edit/settings.css +++ b/edit/settings.css @@ -21,7 +21,7 @@ margin-bottom: 0; } .style-settings input[type=radio] { - margin-left: -.5em; /* compensate for label's 16px margin in edit.css */ + margin: 0 .3em 0 0; } .style-settings input:disabled ~ label { opacity: .5; @@ -44,3 +44,12 @@ .style-settings textarea:not(:placeholder-shown) { min-width: 50vw; } +.ss-radio { + display: inline-flex; + align-items: center; + line-height: 2; + padding: 0 .8em 0 0; +} +a[data-cmd=note] { + vertical-align: text-bottom; +} diff --git a/edit/settings.html b/edit/settings.html index 725a1ba8..a83a958f 100644 --- a/edit/settings.html +++ b/edit/settings.html @@ -7,14 +7,16 @@