From f2184fb36fcacd6e581b87c75c3732fa0636455e Mon Sep 17 00:00:00 2001
From: himself65 <himself65@outlook.com>
Date: Mon, 16 Dec 2019 22:53:46 +0800
Subject: [PATCH] feat: support dark theme

---
 components/Header.vue                | 14 ++---
 components/MobileHeader.vue          |  4 +-
 components/Pagination.vue            |  6 +--
 components/ThemeToggle.vue           | 44 +++++++++++++++
 components/Toc.vue                   |  8 +--
 components/util.js                   | 21 ++++++++
 global-components/BaseListLayout.vue | 81 ++++++++++++++++++----------
 global-components/BlogTag.vue        |  4 +-
 global-components/BlogTags.vue       |  2 +-
 global-components/NavLink.vue        |  4 +-
 layouts/FrontmatterKey.vue           | 10 ++++
 layouts/GlobalLayout.vue             | 24 ++++++++-
 layouts/Layout.vue                   | 10 ++++
 layouts/Post.vue                     | 22 +++++---
 package.json                         |  3 +-
 styles/code.styl                     |  8 +--
 styles/index.styl                    | 20 +++----
 yarn.lock                            |  5 ++
 18 files changed, 219 insertions(+), 71 deletions(-)
 create mode 100644 components/ThemeToggle.vue

diff --git a/components/Header.vue b/components/Header.vue
index 19b36c3..1ef4e79 100644
--- a/components/Header.vue
+++ b/components/Header.vue
@@ -26,12 +26,11 @@
   import SearchBox from '@SearchBox'
 
   export default {
-    components: { SearchBox },
+    components: { SearchBox }
   }
 </script>
 
 <style lang="stylus">
-  @import '~@app/style/config'
   #header
     z-index 12
     position fixed
@@ -39,7 +38,7 @@
     width 100vw
     box-sizing border-box
     // background lighten(#3eaf7c, 90%)
-    background-color #FFF
+    background-color var(--background)
     padding 20px 32px 20px
     margin auto
     box-shadow: 0 5px 20px rgba(0, 0, 0, 0.03), 0 6px 6px rgba(0, 0, 0, 0.05)
@@ -64,7 +63,7 @@
       /*flex 0 0 200px*/
       // color #3eaf7c
       // color lighten(#3eaf7c, 10%)
-      color #000
+      color var(--text)
       font-size 30px
       margin 0
       letter-spacing 2px
@@ -72,7 +71,7 @@
       text-transform uppercase
 
       a
-        color #000
+        color var(--text)
         font-weight bold
         font-family PT Serif, Serif
         text-decoration none
@@ -93,6 +92,7 @@
           margin-left 20px
 
           a
+            color var(--text)
             font-family PT Serif, Serif
             font-size 20px
             // color lighten(#3eaf7c, 30%)
@@ -102,8 +102,10 @@
       .search-box
         font-family PT Serif, Serif
         margin-left 20px
+        background-color transparent
 
         input
+          background-color transparent
           border-radius 5px
           transition all .5s
           border: 1px solid #cecece
@@ -118,7 +120,7 @@
           right 0
 
           a
-            color #000
+            color var(--text)
             text-decoration none
 
             &.focused
diff --git a/components/MobileHeader.vue b/components/MobileHeader.vue
index 1189d9c..58324a2 100644
--- a/components/MobileHeader.vue
+++ b/components/MobileHeader.vue
@@ -50,7 +50,7 @@
     top 0
     width 100vw
     box-sizing border-box
-    background-color #fff
+    background-color var(--background)
     margin auto
     box-shadow 0 5px 20px rgba(0,0,0,0.03), 0 6px 6px rgba(0,0,0,0.05)
     transition all 1s cubic-bezier(0.25, 0.8, 0.25, 1)
@@ -84,7 +84,7 @@
     max-height 0
     overflow hidden
     transition 0.3s ease
-    background-color #fff
+    background-color var(--background)
 
   .mobile-menu-wrapper.open
     max-height 450px
diff --git a/components/Pagination.vue b/components/Pagination.vue
index 28936ad..3ccb61e 100644
--- a/components/Pagination.vue
+++ b/components/Pagination.vue
@@ -32,21 +32,21 @@
   .pagination
     a
       margin-right: 20px
-      color: #000
+      color: var(--text)
       height: 38px
       line-height: 38px
       transition: all .3s ease
       position: relative
       overflow: hidden
       display: inline-block
-      background #FFF
+      background var(--background)
       padding: 0 15px
       text-decoration: none
       border 1px solid #000
       border-radius 5px
       transition all .5s
       &:hover
-        color #FFF
+        color var(--text)
         border 1px solid $accentColor
         background-color $accentColor
 </style>
diff --git a/components/ThemeToggle.vue b/components/ThemeToggle.vue
new file mode 100644
index 0000000..7bfc2e8
--- /dev/null
+++ b/components/ThemeToggle.vue
@@ -0,0 +1,44 @@
+<template>
+  <ToggleButton
+    :labels="{ checked: 'Dark', unchecked: 'Light' }"
+    :value="this.defaultValue"
+    :width="55"
+    @change="onChange"
+    color="#8a278c"
+  />
+</template>
+
+<script>
+  import {ToggleButton} from 'vue-js-toggle-button'
+  import {darkThemeKey, darkTheme} from './util'
+
+  export default {
+    components: {ToggleButton},
+    data() {
+      return {
+        defaultValue: false
+      }
+    },
+    created() {
+      this.defaultValue = window.localStorage.getItem(darkThemeKey) === 'true' || false
+    },
+    methods: {
+      onChange({value}) {
+        window.localStorage.setItem(darkThemeKey, value)
+        this.setTheme(value)
+      },
+
+      setTheme(isDark) {
+        const style = window.document.body.style
+        Object.keys(darkTheme).forEach(key => {
+          isDark && style.setProperty(`--${key}`, darkTheme[key])
+          !isDark && style.removeProperty(`--${key}`)
+        })
+      }
+    }
+  }
+</script>
+
+<style scoped>
+
+</style>
diff --git a/components/Toc.vue b/components/Toc.vue
index a980767..f879e16 100644
--- a/components/Toc.vue
+++ b/components/Toc.vue
@@ -157,7 +157,7 @@
 
       a
         display: block;
-        color: #2c3e50;
+        color: var(--text--link);
         width: 100%;
         box-sizing: border-box;
         font-size: 12px;
@@ -169,14 +169,14 @@
         white-space: nowrap;
 
       &.active
-        border-left-color: $accentColor;
+        border-left-color: var(--text--link--lighten);
 
         a
-          color: $accentColor;
+          color: var(--text--link--lighten);
 
       &:hover
         a
-          color: $accentColor;
+          color: var(--text--link--lighten);
 
     for i in range(3, 6)
       .vuepress-toc-h{i} a
diff --git a/components/util.js b/components/util.js
index 613a489..e78ee70 100644
--- a/components/util.js
+++ b/components/util.js
@@ -2,6 +2,18 @@ export const hashRE = /#.*$/
 export const extRE = /\.(md|html)$/
 export const endingSlashRE = /\/$/
 export const outboundRE = /^(https?:|mailto:|tel:)/
+export const darkThemeKey = 'ui-dark-theme'
+export const darkTheme = {
+  'background': '#282c35',
+  'code--background': '#373c49',
+  'text': '#fff',
+  'text--code': '#fff',
+  'text--mask': 'rgba(255, 255, 255, 0.84)',
+  'text--mask2': 'rgba(255, 255, 255, 0.54)',
+  'text--link': '#ffa7c4',
+  'text--link--lighten': '#d23669',
+  'title': '#8a278c'
+}
 
 export function normalize (path) {
   return decodeURI(path)
@@ -21,6 +33,15 @@ export function isTel (path) {
   return /^tel:/.test(path)
 }
 
+export function initTheme() {
+  const isDark = window.localStorage.getItem(darkThemeKey) === 'true' || false
+  const style = window.document.body.style
+  Object.keys(darkTheme).forEach(key => {
+    isDark && style.setProperty(`--${key}`, darkTheme[key])
+    !isDark && style.removeProperty(`--${key}`)
+  })
+}
+
 export function ensureExt (path) {
   if (isExternal(path)) {
     return path
diff --git a/global-components/BaseListLayout.vue b/global-components/BaseListLayout.vue
index 6af0b7a..cda7ccb 100644
--- a/global-components/BaseListLayout.vue
+++ b/global-components/BaseListLayout.vue
@@ -1,11 +1,16 @@
 <template>
   <div id="base-list-layout">
     <div class="ui-posts">
+      <div class="ui-functional">
+        <span class="ui-site-title">{{ $site.title }}</span>
+        <component v-if="themeToggle" :is="themeToggle"/>
+      </div>
+
       <div class="ui-post" v-for="page in pages">
         <div class="ui-post-title">
           <NavLink :link="page.path">{{ page.title }}</NavLink>
         </div>
-        
+
         <div class="ui-post-summary">
           {{ page.frontmatter.summary || page.summary }}
           <!-- <Content :page-key="page.key" slot-key="intro"/>-->
@@ -22,37 +27,45 @@
         </div>
       </div>
     </div>
-    
+
     <component v-if="$pagination.length > 1 && paginationComponent" :is="paginationComponent"></component>
   </div>
 </template>
 
 <script>
   /* global THEME_BLOG_PAGINATION_COMPONENT */
-  
+
   import Vue from 'vue'
-  import { NavigationIcon, ClockIcon } from 'vue-feather-icons'
-  import { Pagination, SimplePagination } from '@vuepress/plugin-blog/lib/client/components'
-  
+  import {NavigationIcon, ClockIcon} from 'vue-feather-icons'
+  import {Pagination, SimplePagination} from '@vuepress/plugin-blog/lib/client/components'
+
   export default {
-    components: { NavigationIcon, ClockIcon },
+    components: {NavigationIcon, ClockIcon},
 
     data() {
       return {
-        paginationComponent: null
+        paginationComponent: null,
+        themeToggle: null
       }
     },
-    
+
     created() {
       this.paginationComponent = this.getPaginationComponent()
     },
-    
+
+    beforeMount() {
+      import('@theme/components/ThemeToggle.vue')
+        .then(module => {
+          this.themeToggle = module.default
+        })
+    },
+
     computed: {
       pages() {
         return this.$pagination.pages
       },
     },
-    
+
     methods: {
       getPaginationComponent() {
         const n = THEME_BLOG_PAGINATION_COMPONENT
@@ -74,64 +87,78 @@
   }
 </script>
 
-<style lang="stylus">
+<style lang="stylus" scoped>
   .common-layout
     .content-wrapper
       padding-bottom 80px
-  
+
+  .ui-functional
+    display flex
+    flex-direction row
+    align-items center
+    justify-content space-between
+    width 100%
+    margin-bottom 2rem
+
+    .ui-site-title
+      color var(--title)
+      font-size 25px
+      font-weight 900
+
+
   .ui-post
     padding-bottom 25px
     margin-bottom 25px
     border-bottom 1px solid #f1f1f1
-    
+
     &:last-child
       border-bottom 0px
       margin-bottom 0px
-    
+
     p
       margin 0
-  
+
   .ui-post-title
     font-family PT Serif, Serif
     font-size 28px
     border-bottom 0
-    
+
     a
       cursor pointer
-      color #000
+      color var(--text)
       transition all .2s
       text-decoration none
-      
+
       &:hover
         text-decoration underline
-  
+
   .ui-post-summary
     font-size 14px
     margin-bottom 15px
-    color rgba(0, 0, 0, 0.54)
+    color var(--text--mask)
     font-weight 200
-  
+
   .ui-post-author
     display flex
     align-items center
     font-size 12px
     line-height 12px
-    color rgba(0, 0, 0, 0.84)
+    color var(--text--mask2)
     margin-bottom 3px
     font-weight 400
-    
+
     svg
       margin-right 5px
       width 14px
       height 14px
-  
+
   .ui-post-date
     display flex
     align-items center
     font-size 12px
-    color rgba(0, 0, 0, 0.54)
+    color var(--text--mask)
     font-weight 200
-    
+
     svg
       margin-right 5px
       width 14px
diff --git a/global-components/BlogTag.vue b/global-components/BlogTag.vue
index b656621..844bcc3 100644
--- a/global-components/BlogTag.vue
+++ b/global-components/BlogTag.vue
@@ -25,8 +25,8 @@
     text-align left
     box-sizing border-box
     transition: background-color .3s
-    color #000
-    border 1px solid #000
+    color var(--text)
+    border 1px solid var(--text)
     text-decoration none
     transition all .5s
 
diff --git a/global-components/BlogTags.vue b/global-components/BlogTags.vue
index 4ecb9fb..b2db956 100644
--- a/global-components/BlogTags.vue
+++ b/global-components/BlogTags.vue
@@ -17,7 +17,7 @@
 <style lang="stylus">
   .blog-tags
     width 66%
-    
+
   @media screen and (max-width: 1000px)
     .blog-tags
       width 90%
diff --git a/global-components/NavLink.vue b/global-components/NavLink.vue
index f853eca..1b348dc 100644
--- a/global-components/NavLink.vue
+++ b/global-components/NavLink.vue
@@ -51,9 +51,9 @@ export default {
 
 <style lang="stylus">
 .nav-link
-  color #000
+  color var(--text)
 
 .nav-link
   &:hover, &.router-link-active
-    color $accentColor
+    color var(--text--link)
 </style>
diff --git a/layouts/FrontmatterKey.vue b/layouts/FrontmatterKey.vue
index 071037a..b5e2a92 100644
--- a/layouts/FrontmatterKey.vue
+++ b/layouts/FrontmatterKey.vue
@@ -3,3 +3,13 @@
     <BlogTags :tags="$frontmatterKey.list"/>
   </div>
 </template>
+
+<script>
+  import {initTheme} from '../components/util'
+
+  export default {
+    mounted() {
+      initTheme()
+    }
+  }
+</script>
diff --git a/layouts/GlobalLayout.vue b/layouts/GlobalLayout.vue
index 9dd285a..1bb831b 100644
--- a/layouts/GlobalLayout.vue
+++ b/layouts/GlobalLayout.vue
@@ -14,6 +14,7 @@
   import Header from '@theme/components/Header.vue'
   import MobileHeader from '@theme/components/MobileHeader.vue'
   import Footer from '@theme/components/Footer.vue'
+  import {initTheme} from '../components/util'
 
   export default {
     components: {
@@ -30,14 +31,35 @@
     },
 
     mounted() {
-      this.$router.afterEach(()=>{
+      initTheme()
+      this.$router.afterEach(() => {
         this.isMobileHeaderOpen = false
       })
     }
   }
 </script>
 
+
+<style lang="css">
+  body {
+    --background: #fff;
+    --code--background: #000;
+    --text: #000;
+    --text--code: #fff;
+    --text--mask: rgba(0, 0, 0, 0.84);
+    --text--mask2: rgba(0, 0, 0, 0.54);
+    --text--link: #d05dd2;
+    --text--link--lighten: #8a278c;
+    --title: #2c3e50;
+  }
+</style>
+
 <style lang="stylus">
+  @import "../styles/config.styl"
+
+  #vuperess-theme-blog__global-layout
+    background-color var(--background)
+
   .content-wrapper
     padding 160px 15px 80px 15px
     min-height calc(100vh - 80px - 60px - 160px)
diff --git a/layouts/Layout.vue b/layouts/Layout.vue
index 106e6df..d7120cb 100644
--- a/layouts/Layout.vue
+++ b/layouts/Layout.vue
@@ -5,4 +5,14 @@
   </div>
 </template>
 
+<script>
+  import {initTheme} from '../components/util'
+
+  export default {
+    mounted() {
+      initTheme()
+    }
+  }
+</script>
+
 <style src="prismjs/themes/prism-okaidia.css"></style>
diff --git a/layouts/Post.vue b/layouts/Post.vue
index b70c986..1f9c440 100644
--- a/layouts/Post.vue
+++ b/layouts/Post.vue
@@ -11,30 +11,36 @@
 
 <script>
   import Toc from '@theme/components/Toc.vue'
-  import { Comment } from '@vuepress/plugin-blog/lib/client/components'
-  
+  import {Comment} from '@vuepress/plugin-blog/lib/client/components'
+  import {initTheme} from '../components/util'
+
   export default {
     components: {
       Toc,
-      Comment,
+      Comment
     },
+
+    mounted() {
+      initTheme()
+    }
   }
 </script>
 
 <style lang="stylus">
-@require '../styles/wrapper.styl';
+  @require '../styles/wrapper.styl';
 
   .vuepress-blog-theme-content
     @extend $wrapper
     font-size 16px
     letter-spacing 0px
     font-family PT Serif, Serif
-    color #2c3e50
+    color var(--text)
+    background-color var(--background)
     position relative
 
-    @media(min-width: $MQNarrow)
-      box-shadow: 0 10px 20px rgba(0,0,0,0.05), 0 6px 6px rgba(0,0,0,0.07)
-  
+    @media (min-width: $MQNarrow)
+      box-shadow: 0 10px 20px rgba(0, 0, 0, 0.05), 0 6px 6px rgba(0, 0, 0, 0.07)
+
 </style>
 
 <style src="prismjs/themes/prism-okaidia.css"></style>
diff --git a/package.json b/package.json
index 84d5411..0774cc3 100644
--- a/package.json
+++ b/package.json
@@ -33,7 +33,8 @@
     "@vuepress/plugin-pwa": "1.0.0",
     "@vuepress/plugin-search": "1.0.0",
     "remove-markdown": "^0.3.0",
-    "vue-feather-icons": "^4.21.0"
+    "vue-feather-icons": "^4.21.0",
+    "vue-js-toggle-button": "^1.3.3"
   },
   "devDependencies": {
     "conventional-changelog-cli": "^2.0.1",
diff --git a/styles/code.styl b/styles/code.styl
index 8d3c1d1..5854ede 100644
--- a/styles/code.styl
+++ b/styles/code.styl
@@ -1,6 +1,6 @@
 {$contentClass}
   code
-    color lighten($textColor, 20%)
+    color var(--text--link)
     padding 0.1rem 0.4rem
     margin 0.1rem
     font-size 0.85em
@@ -21,14 +21,14 @@
     border-radius 6px
     overflow auto
     code
-      color #fff
+      color var(--text--code)
       padding 0
       background-color transparent
       border-radius 0
 
 div[class*="language-"]
   position relative
-  background-color $codeBgColor
+  background-color var(--code--background)
   border-radius 6px
   .highlight-lines
     user-select none
@@ -95,7 +95,7 @@ div[class*="language-"]
       height 100%
       border-radius 6px 0 0 6px
       border-right 1px solid rgba(0, 0, 0, 66%)
-      background-color $codeBgColor
+      background-color var(--code--background)
 
 
 for lang in $codeLang
diff --git a/styles/index.styl b/styles/index.styl
index 5fa0348..48686da 100644
--- a/styles/index.styl
+++ b/styles/index.styl
@@ -10,15 +10,15 @@
 html, body
   padding 0
   margin 0
-  background-color #fff
+  background-color var(--background)
 
 body
   font-family -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif
   -webkit-font-smoothing antialiased
   -moz-osx-font-smoothing grayscale
   font-size 16px
-  color $textColor
-  background-color #FFF
+  color var(--title)
+  background-color var(--background)
 
 .page
   padding-left $sidebarWidth
@@ -30,7 +30,7 @@ body
   left 0
   right 0
   height $navbarHeight
-  background-color #fff
+  background-color var(--background)
   box-sizing border-box
   border-bottom 1px solid $borderColor
 
@@ -45,7 +45,7 @@ body
 
 .sidebar
   font-size 16px
-  background-color #fff
+  background-color var(--background)
   width $sidebarWidth
   position fixed
   z-index 10
@@ -81,14 +81,14 @@ body
 
 a
   font-weight 500
-  color $accentColor
+  color var(--text--link)
   text-decoration underline
   &:hover
-    color darken($accentColor, 20%)
+    color var(--text--link--lighten)
 
 p a code
   font-weight 400
-  color $accentColor
+  color var(--text--link--lighten)
 
 kbd
   background #eee
@@ -99,8 +99,8 @@ kbd
 
 blockquote
   font-size .9rem
-  color darken($accentColor, 50%)
-  border-left: 3px solid $accentColor;
+  color var(--text--link--lighten)
+  border-left: 3px solid var(--text--link--lighten);
   margin 0.5rem 0
   padding .25rem 0 .25rem 1rem
 
diff --git a/yarn.lock b/yarn.lock
index 6866b81..2a90416 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -8204,6 +8204,11 @@ vue-i18n@^8.11.2:
   resolved "https://registry.yarnpkg.com/vue-i18n/-/vue-i18n-8.15.0.tgz#9b11ef8e7a124f67cdf788c8c90a81f3606240ed"
   integrity sha512-juJ/avAP39bOMycC+qQDLJ8U9z9LtLF/9PsRoJLBSfsYZo9bqYntyyX5QPicwlb1emJKjgxhZ3YofHiQcXBu0Q==
 
+vue-js-toggle-button@^1.3.3:
+  version "1.3.3"
+  resolved "https://registry.yarnpkg.com/vue-js-toggle-button/-/vue-js-toggle-button-1.3.3.tgz#d603089039e41d45e607355ad2e0478c6a52aceb"
+  integrity sha512-0b920oztgK+1SqlYF26MPiT28hAieL5aAQE7u21XEym5ryfzD4EMer4hLkgDC/1sWsCHb22GvV+t1Kb4AI6QFw==
+
 vue-loader@^15.7.1:
   version "15.7.1"
   resolved "https://registry.yarnpkg.com/vue-loader/-/vue-loader-15.7.1.tgz#6ccacd4122aa80f69baaac08ff295a62e3aefcfd"