Jelajahi Sumber

Merge pull request #1086 from simon04/prefers-color-scheme

Add support for prefers-color-scheme media query
Jed Fox 5 tahun lalu
induk
melakukan
364be3bf11

+ 22 - 6
assets/javascripts/app/settings.coffee

@@ -7,7 +7,8 @@ class app.Settings
     'arrowScroll'
     'analyticsConsent'
     'docs'
-    'dark'
+    'dark' # legacy
+    'theme'
     'layout'
     'size'
     'tips'
@@ -31,10 +32,16 @@ class app.Settings
     manualUpdate: false
     schema: 1
     analyticsConsent: false
+    theme: 'auto'
 
   constructor: ->
     @store = new CookiesStore
     @cache = {}
+    @autoSupported = window.matchMedia('(prefers-color-scheme)').media != 'not all'
+    if @autoSupported
+      @darkModeQuery = window.matchMedia('(prefers-color-scheme: dark)')
+      @darkModeQuery.addListener => @setTheme(@get('theme'))
+
 
   get: (key) ->
     return @cache[key] if @cache.hasOwnProperty(key)
@@ -43,7 +50,7 @@ class app.Settings
   set: (key, value) ->
     @store.set(key, value)
     delete @cache[key]
-    @toggleDark(value) if key == 'dark'
+    @setTheme(value) if key == 'theme'
     return
 
   del: (key) ->
@@ -114,15 +121,24 @@ class app.Settings
     return
 
   initLayout: ->
-    @toggleDark(@get('dark') is 1)
+    if @get('dark') is 1
+      @set('theme', 'dark')
+      @del 'dark'
+    @setTheme(@get('theme'))
     @toggleLayout(layout, @hasLayout(layout)) for layout in @LAYOUTS
     @initSidebarWidth()
     return
 
-  toggleDark: (enable) ->
+  setTheme: (theme) ->
+    if theme is 'auto'
+      theme = if @darkModeQuery.matches then 'dark' else 'default'
     classList = document.documentElement.classList
-    classList.toggle('_theme-default', !enable)
-    classList.toggle('_theme-dark', enable)
+    classList.remove('_theme-default', '_theme-dark')
+    classList.add('_theme-' + theme)
+    @updateColorMeta()
+    return
+
+  updateColorMeta: ->
     color = getComputedStyle(document.documentElement).getPropertyValue('--headerBackground').trim()
     $('meta[name=theme-color]').setAttribute('content', color)
     return

+ 19 - 3
assets/javascripts/templates/pages/settings_tmpl.coffee

@@ -1,13 +1,29 @@
+themeOption = ({ label, value }, settings) -> """
+  <label class="_settings-label _theme-label">
+    <input type="radio" name="theme" value="#{value}"#{if settings.theme == value then ' checked' else ''}>
+    #{label}
+  </label>
+"""
+
 app.templates.settingsPage = (settings) -> """
   <h1 class="_lined-heading">Preferences</h1>
 
+  <div class="_settings-fieldset">
+    <h2 class="_settings-legend">Theme:</h2>
+    <div class="_settings-inputs">
+      #{if settings.autoSupported
+          themeOption label: "Automatic <small>Matches system setting</small>", value: "auto", settings
+        else
+          ""}
+      #{themeOption label: "Light", value: "default", settings}
+      #{themeOption label: "Dark", value: "dark", settings}
+    </div>
+  </div>
+
   <div class="_settings-fieldset">
     <h2 class="_settings-legend">General:</h2>
 
     <div class="_settings-inputs">
-      <label class="_settings-label">
-        <input type="checkbox" form="settings" name="dark" value="1"#{if settings.dark then ' checked' else ''}>Enable dark theme
-      </label>
       <label class="_settings-label _setting-max-width">
         <input type="checkbox" form="settings" name="layout" value="_max-width"#{if settings['_max-width'] then ' checked' else ''}>Enable fixed-width layout
       </label>

+ 6 - 5
assets/javascripts/views/content/settings_page.coffee

@@ -11,19 +11,20 @@ class app.views.SettingsPage extends app.View
 
   currentSettings: ->
     settings = {}
-    settings.dark = app.settings.get('dark')
+    settings.theme = app.settings.get('theme')
     settings.smoothScroll = !app.settings.get('fastScroll')
     settings.arrowScroll = app.settings.get('arrowScroll')
     settings.autoInstall = app.settings.get('autoInstall')
     settings.analyticsConsent = app.settings.get('analyticsConsent')
+    settings.autoSupported = app.settings.autoSupported
     settings[layout] = app.settings.hasLayout(layout) for layout in app.settings.LAYOUTS
     settings
 
   getTitle: ->
     'Preferences'
 
-  toggleDark: (enable) ->
-    app.settings.set('dark', !!enable)
+  setTheme: (value) ->
+    app.settings.set('theme', value)
     return
 
   toggleLayout: (layout, enable) ->
@@ -74,8 +75,8 @@ class app.views.SettingsPage extends app.View
   onChange: (event) =>
     input = event.target
     switch input.name
-      when 'dark'
-        @toggleDark input.checked
+      when 'theme'
+        @setTheme input.value
       when 'layout'
         @toggleLayout input.value, input.checked
       when 'smoothScroll'

+ 8 - 2
assets/stylesheets/components/_settings.scss

@@ -48,15 +48,21 @@
 }
 
 ._settings-label {
-  margin: 0 0 .375rem;
+  &:not(._theme-label) {
+    margin: 0 0 .375rem;
+  }
 
   > small {
     display: block;
     color: var(--textColorLight);
     margin-left: 1.75rem;
   }
+  &._theme-label > small {
+    display: inline-block;
+    margin-left: 0.75rem;
+  }
 
-  input[type=checkbox] {
+  input[type=checkbox], input[type=radio] {
     vertical-align: top;
     margin: .25rem .375rem;
   }

+ 7 - 0
assets/stylesheets/global/_base.scss

@@ -10,6 +10,13 @@ html {
   @import 'global/print';
 }
 
+html._theme-default {
+  color-scheme: light only;
+}
+html._theme-dark {
+  color-scheme: dark only;
+}
+
 body {
   height: 100%;
   margin: 0;