Browse Source

Add custom theme

simov 1 year ago
parent
commit
5fb5673c83

+ 1 - 0
background/inject.js

@@ -9,6 +9,7 @@ md.inject = ({storage: {state}}) => (id) => {
       themes: state.themes,
       content: state.content,
       compiler: state.compiler,
+      custom: state.custom,
       icon: state.settings.icon,
     }],
     func: (_args) => {

+ 10 - 0
background/messages.js

@@ -149,6 +149,16 @@ md.messages = ({storage: {defaults, state, set}, compilers, mathjax, xhr, webreq
       set({settings: req.settings})
       sendResponse()
     }
+    else if (req.message === 'custom.get') {
+      sendResponse(state.custom)
+    }
+    else if (req.message === 'custom.set') {
+      set({custom: req.custom}).then(sendResponse).catch((err) => {
+        if (/QUOTA_BYTES_PER_ITEM quota exceeded/.test(err.message)) {
+          sendResponse({error: 'Minified theme exceeded 8KB in size!'})
+        }
+      })
+    }
 
     return true
   }

+ 13 - 2
background/storage.js

@@ -8,8 +8,8 @@ md.storage = ({compilers}) => {
 
   var state = {}
 
-  function set (options) {
-    chrome.storage.sync.set(options)
+  async function set (options) {
+    await chrome.storage.sync.set(options)
     Object.assign(state, options)
   }
 
@@ -64,6 +64,10 @@ md.storage.defaults = (compilers) => {
     settings: {
       icon: 'default',
       theme: 'light',
+    },
+    custom: {
+      theme: '',
+      color: 'auto',
     }
   }
 
@@ -182,4 +186,11 @@ md.storage.migrations = (state) => {
     })
     delete state.marked.sanitize
   }
+  // v5.2 -> v5.3
+  if (state.custom === undefined) {
+    state.custom = {
+      theme: '',
+      color: 'auto'
+    }
+  }
 }

+ 1 - 0
build/README.md

@@ -29,6 +29,7 @@ sh build/package.sh
 | mermaid             | 10.4.0
 | mithril             | 1.1.7
 | prismjs             | 1.29.0
+| csso                | 5.0.5
 | **markdown-it**
 | markdown-it            | 13.0.1
 | markdown-it-abbr       | 1.0.4

+ 13 - 0
build/csso/build.sh

@@ -0,0 +1,13 @@
+#!/bin/bash
+
+# set current working directory to directory of the shell script
+cd "$(dirname "$0")"
+
+# before
+npm ci 2> /dev/null || npm i
+
+# copy
+cp node_modules/csso/dist/csso.js ../../vendor/csso.min.js
+
+# after
+rm -rf node_modules/

+ 12 - 0
build/csso/package.json

@@ -0,0 +1,12 @@
+{
+  "name": "markdown-viewer",
+  "version": "0.0.0",
+  "description": "Markdown Viewer / Browser Extension",
+  "private": true,
+  "dependencies": {
+    "csso": "5.0.5"
+  },
+  "engines": {
+    "node": ">=18.0.0"
+  }
+}

+ 1 - 0
build/package.sh

@@ -23,6 +23,7 @@ mkdir -p ../vendor
 
 # build deps
 sh bootstrap/build.sh
+sh csso/build.sh
 sh markdown-it/build.sh
 sh marked/build.sh
 sh mathjax/build.sh

+ 7 - 0
content/index.js

@@ -7,6 +7,7 @@ var state = {
   themes: args.themes,
   content: args.content,
   compiler: args.compiler,
+  custom: args.custom,
   icon: args.icon,
   html: '',
   markdown: '',
@@ -149,6 +150,8 @@ function mount () {
       var dom = []
 
       if (state.html) {
+        state._themes.custom = state.custom.color
+
         var color =
           state._themes[state.theme] === 'dark' ||
           (state._themes[state.theme] === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches)
@@ -193,6 +196,10 @@ function mount () {
           dom.push(m('#_toc.tex2jax-ignore', m.trust(state.toc)))
           state.raw ? $('body').classList.remove('_toc-left') : $('body').classList.add('_toc-left')
         }
+
+        if (state.theme === 'custom') {
+          dom.push(m('style', {type: 'text/css'}, state.custom.theme))
+        }
       }
 
       return dom

+ 139 - 0
options/custom.js

@@ -0,0 +1,139 @@
+
+var Custom = () => {
+  var defaults = {
+    theme: '',
+    color: 'auto',
+    _colors: ['auto', 'light', 'dark'],
+    error: '',
+  }
+
+  var state = Object.assign({}, defaults)
+
+  chrome.runtime.sendMessage({message: 'custom.get'}, (res) => {
+    Object.assign(state, res)
+    m.redraw()
+  })
+
+  var events = {
+    file: (e) => {
+      document.querySelector('input[type=file]').click()
+    },
+    theme: (e) => {
+      var file = e.target.files[0]
+      if (file) {
+        var minified
+        var reader = new FileReader()
+        reader.readAsText(file, 'UTF-8')
+        reader.onload = (e) => {
+          minified = csso.minify(e.target.result).css
+          chrome.runtime.sendMessage({
+            message: 'custom.set',
+            custom: {
+              theme: minified,
+              color: state.color,
+            },
+          }, (res) => {
+            if (res?.error) {
+              state.error = res.error
+            }
+            else {
+              state.theme = minified
+              state.error = ''
+            }
+            m.redraw()
+          })
+        }
+      }
+    },
+    remove: (e) => {
+      state.theme = ''
+      state.error = ''
+      chrome.runtime.sendMessage({
+        message: 'custom.set',
+        custom: {
+          theme: state.theme,
+          color: state.color,
+        },
+      })
+      m.redraw()
+    },
+    color: (e) => {
+      state.color = state._colors[e.target.selectedIndex]
+      chrome.runtime.sendMessage({
+        message: 'custom.set',
+        custom: {
+          theme: state.theme,
+          color: state.color,
+        },
+      })
+    }
+  }
+
+  var oncreate = {
+    ripple: (vnode) => {
+      mdc.ripple.MDCRipple.attachTo(vnode.dom)
+    }
+  }
+
+  var onupdate = {}
+
+  var render = () => [
+    m('.bs-callout m-custom',
+      state.error &&
+      m('.row',
+        m('.col-12',
+          m('span.m-label.m-error', state.error)
+        )
+      ),
+      m('.row',
+        m('.col-xxl-6.col-xl-6.col-lg-6.col-md-6.col-sm-12',
+          m('span.m-label',
+            'Custom Theme'
+          )
+        ),
+        m('.col-xxl-6.col-xl-6.col-lg-6.col-md-6.col-sm-12',
+          m('input', {
+            type: 'file',
+            accept: '.css',
+            onchange: events.theme,
+            oncancel: events.theme,
+          }),
+          m('button.mdc-button mdc-button--raised m-button', {
+            oncreate: oncreate.ripple,
+            onclick: events.file
+            },
+            !state.theme ? 'Add' : 'Update'
+          ),
+          state.theme &&
+          m('button.mdc-button mdc-button--raised m-button', {
+            oncreate: oncreate.ripple,
+            onclick: events.remove
+            },
+            'Remove'
+          ),
+        ),
+      ),
+      state.theme &&
+      m('.row',
+        m('.col-xxl-6.col-xl-6.col-lg-6.col-md-6.col-sm-12',
+          m('span.m-label',
+            'Color Scheme'
+          )
+        ),
+        m('.col-xxl-6.col-xl-6.col-lg-6.col-md-6.col-sm-12',
+          m('select.mdc-elevation--z2 m-select', {
+            onchange: events.color
+            },
+            state._colors.map((color) =>
+              m('option', {
+                selected: state.color === color,
+              }, color)
+            )
+          )
+        ),
+      ),
+    )
+  ]
+
+  return {state, render}
+}

+ 19 - 0
options/index.css

@@ -269,6 +269,25 @@ h4 {
   height: 324px;
 }
 
+/*---------------------------------------------------------------------------*/
+/*custom*/
+
+.m-custom input[type=file] {
+  display: none;
+}
+.m-custom .m-button {
+  letter-spacing: 0;
+  padding: 0 11px;
+  margin-bottom: 20px;
+}
+.m-custom .m-button:first-of-type {
+  margin-right: 10px;
+}
+.m-custom .m-error {
+  color: #dc3545;
+  margin-bottom: 10px;
+}
+
 /*---------------------------------------------------------------------------*/
 /*footer*/
 

+ 2 - 0
options/index.html

@@ -9,6 +9,7 @@
   <link href="/options/index.css" rel="stylesheet" type="text/css" media="all" />
   <script src="/vendor/mdc.min.js" type="text/javascript" charset="utf-8"></script>
   <script src="/vendor/mithril.min.js" type="text/javascript" charset="utf-8"></script>
+  <script src="/vendor/csso.min.js" type="text/javascript" charset="utf-8"></script>
 </head>
 <body>
   <div id="wrapper">
@@ -38,6 +39,7 @@
 </body>
 <script src="/options/origins.js" type="text/javascript" charset="utf-8"></script>
 <script src="/options/settings.js" type="text/javascript" charset="utf-8"></script>
+<script src="/options/custom.js" type="text/javascript" charset="utf-8"></script>
 <script src="/popup/index.js" type="text/javascript" charset="utf-8"></script>
 <script src="/options/index.js" type="text/javascript" charset="utf-8"></script>
 </html>

+ 3 - 0
popup/index.js

@@ -346,6 +346,8 @@ var Popup = () => {
           ),
           settings.render()
         ),
+        state.theme === 'custom' &&
+        custom.render()
       ),
 
       m('.col-xxl-4.col-xl-4.col-lg-6.col-md-6.col-sm-12',
@@ -418,4 +420,5 @@ if (document.querySelector('.is-popup')) {
 }
 else {
   var settings = Settings()
+  var custom = Custom()
 }