diff --git a/_locales/en/messages.json b/_locales/en/messages.json index e1b3416c..b018fb0c 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" @@ -1323,6 +1311,27 @@ "message": "Styles before commands", "description": "Label for the checkbox controlling section order in the popup." }, + "preferScheme": { + "message": "Dark/Light mode preference:" + }, + "preferSchemeDark": { + "message": "Dark" + }, + "preferSchemeDarkOnly": { + "message": "Dark (strict)" + }, + "preferSchemeLight": { + "message": "Light" + }, + "preferSchemeLightOnly": { + "message": "Light (strict)" + }, + "preferSchemeNone": { + "message": "None (always applied)" + }, + "preferSchemeTip": { + "message": "The style will be applied in a matching Dark/Light global mode. For strict options, the mode being active is mandatory, so the style doesn't apply when the mode is 'Never'. For non-strict options, the style also applies when the user doesn't care i.e. the global mode is 'Never'." + }, "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..300322c2 100644 --- a/background/color-scheme.js +++ b/background/color-scheme.js @@ -4,33 +4,45 @@ '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', 'dark!', 'light!']; // ! = only if schemeSwitcher is enabled + const isDark = {never: null, system: false, time: false}; + let isDarkNow = false; - const checkTime = ['schemeSwitcher.nightStart', 'schemeSwitcher.nightEnd']; - prefs.subscribe(checkTime, (key, value) => { + prefs.subscribe(kSTATE, () => emitChange()); + 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 {shouldIncludeStyle, onChange, updateSystemPreferDark}; + return { + SCHEMES, + onChange(listener) { + changeListeners.add(listener); + }, + shouldIncludeStyle({preferScheme: val}) { + return !SCHEMES.includes(val) || + !val.endsWith('!') && prefs.get(kSTATE) === 'never' || + val.startsWith('dark') === isDarkNow; + }, + updateSystemPreferDark(val) { + emitChange('system', val); + return true; + }, + }; 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 +52,29 @@ 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(); + emitChange('time', val); + } + + function calcTime(key) { + const [h, m] = prefs.get(key).split(':'); + return (h * 3600 + m * 60) * 1000; + } + + function emitChange(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() { + isDarkNow = isDark[prefs.get(kSTATE)]; for (const listener of changeListeners) { - listener(); + listener(isDarkNow); } } })(); 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..5dc1da24 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(); diff --git a/edit.html b/edit.html index 8dd7cf5e..c30e84ba 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..0fdf0791 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,55 @@ .gutter-bookmark { background: linear-gradient(0deg, hsla(180, 100%, 30%, .75) 2px, hsla(180, 100%, 30%, .2) 2px); } + +@media not screen, 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; + } + /* 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: #05a } + .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..cd8e4080 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; @@ -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/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..83495186 100644 --- a/edit/settings.html +++ b/edit/settings.html @@ -7,16 +7,26 @@
-
-