simov пре 7 година
родитељ
комит
4b3ff62d26
7 измењених фајлова са 289 додато и 30 уклоњено
  1. 1 1
      background/inject.js
  2. 9 1
      background/messages.js
  3. 10 0
      background/storage.js
  4. 2 2
      content/content.js
  5. 172 0
      content/options.js
  6. 18 5
      content/popup.js
  7. 77 21
      css/options.css

+ 1 - 1
background/inject.js

@@ -4,7 +4,7 @@ md.inject = ({storage: {state}}) => (id) => {
   chrome.tabs.executeScript(id, {
     code: `
       document.querySelector('pre').style.visibility = 'hidden'
-      var theme = '${state.theme}'
+      var theme = ${JSON.stringify(state.theme)}
       var raw = ${state.raw}
       var content = ${JSON.stringify(state.content)}
       var compiler = '${state.compiler}'

+ 9 - 1
background/messages.js

@@ -25,7 +25,8 @@ md.messages = ({storage: {defaults, state, set}, compilers, mathjax, webrequest}
       sendResponse(Object.assign({}, state, {
         options: state[state.compiler],
         description: compilers[state.compiler].description,
-        compilers: Object.keys(compilers)
+        compilers: Object.keys(compilers),
+        themes: state.themes,
       }))
     }
     else if (req.message === 'popup.theme') {
@@ -80,6 +81,7 @@ md.messages = ({storage: {defaults, state, set}, compilers, mathjax, webrequest}
         origins: state.origins,
         header: state.header,
         match: state.match,
+        themes: state.themes,
       })
     }
     else if (req.message === 'options.header') {
@@ -109,6 +111,12 @@ md.messages = ({storage: {defaults, state, set}, compilers, mathjax, webrequest}
       webrequest()
       sendResponse()
     }
+
+    // themes
+    else if (req.message === 'themes') {
+      set({themes: req.themes})
+      sendResponse()
+    }
   }
 
   function notifyContent (req, res) {

+ 10 - 0
background/storage.js

@@ -58,6 +58,7 @@ md.storage.defaults = (compilers) => {
         encoding: '',
       }
     },
+    themes: [],
   }
 
   Object.keys(compilers).forEach((compiler) => {
@@ -83,4 +84,13 @@ md.storage.migrations = (state) => {
   if (typeof state.origins['file://'] === 'object') {
     state.origins['file://'].csp = false
   }
+  if (typeof state.theme === 'string') {
+    state.theme = {
+      name: state.theme,
+      url: chrome.runtime.getURL(`/themes/${state.theme}.css`)
+    }
+  }
+  if (state.themes === undefined) {
+    state.themes = []
+  }
 }

+ 2 - 2
content/content.js

@@ -75,12 +75,12 @@ function mount () {
         if (state.theme) {
           dom.push(m('link#_theme', {
             rel: 'stylesheet', type: 'text/css',
-            href: chrome.runtime.getURL('/themes/' + state.theme + '.css')
+            href: state.theme.url,
           }))
         }
         if (state.html) {
           dom.push(m('#_html', {oncreate: oncreate.html,
-            class: /github(-dark)?/.test(state.theme) ? 'markdown-body' : 'markdown-theme'},
+            class: /github(-dark)?/.test(state.theme.name) ? 'markdown-body' : 'markdown-theme'},
             m.trust(state.html)
           ))
           if (state.content.toc && state.toc) {

+ 172 - 0
content/options.js

@@ -4,11 +4,13 @@ var defaults = {
   origins: {},
   header: false,
   match: '',
+  themes: [],
   // UI
   scheme: 'https',
   host: '',
   timeout: null,
   file: true,
+  theme: {},
   // static
   schemes: ['https', 'http', '*'],
   encodings: {
@@ -149,6 +151,75 @@ var events = {
       })
     },
   },
+
+  themes: {
+    name: (e) => {
+      state.theme.name = e.target.value
+    },
+
+    url: (e) => {
+      state.theme.url = e.target.value
+    },
+
+    add: () => {
+      if (!state.theme.name || !state.theme.url) {
+        return
+      }
+      var all = chrome.runtime.getManifest().web_accessible_resources
+        .filter((file) => file.indexOf('/themes/') === 0)
+        .map((file) => file.replace(/\/themes\/(.*)\.css/, '$1'))
+        .concat(state.themes.map(({name}) => name))
+      if (all.includes(state.theme.name)) {
+        return
+      }
+      state.themes.push({
+        name: state.theme.name,
+        url: state.theme.url,
+      })
+      chrome.runtime.sendMessage({
+        message: 'themes',
+        themes: state.themes.map(({name, url}) => ({name, url}))
+      })
+      state.theme.name = ''
+      state.theme.url = ''
+      m.redraw()
+    },
+
+    update: {
+      name: (theme) => (e) => {
+        theme.name = e.target.value
+        clearTimeout(state.timeout)
+        state.timeout = setTimeout(() => {
+          chrome.runtime.sendMessage({
+            message: 'themes',
+            themes: state.themes.map(({name, url}) => ({name, url}))
+          })
+          m.redraw()
+        }, 750)
+      },
+
+      url: (theme) => (e) => {
+        theme.url = e.target.value
+        clearTimeout(state.timeout)
+        state.timeout = setTimeout(() => {
+          chrome.runtime.sendMessage({
+            message: 'themes',
+            themes: state.themes.map(({name, url}) => ({name, url}))
+          })
+        }, 750)
+      }
+    },
+
+    remove: (theme) => () => {
+      var index = state.themes.findIndex(({name}) => name === theme.name)
+      state.themes.splice(index, 1)
+      chrome.runtime.sendMessage({
+        message: 'themes',
+        themes: state.themes.map(({name, url}) => ({name, url}))
+      })
+      m.redraw()
+    },
+  }
 }
 
 var oncreate = {
@@ -374,6 +445,107 @@ m.mount(document.querySelector('main'), {
         )
       )
     ),
+
+    // custom themes
+    m('.bs-callout m-themes',
+
+      // add theme
+      m('.m-add-theme',
+        m('h4.mdc-typography--headline5', 'Custom Themes'),
+        // name
+        m('.mdc-text-field m-textfield m-name', {
+          oncreate: oncreate.textfield,
+          },
+          m('input.mdc-text-field__input', {
+            type: 'text',
+            value: state.theme.name,
+            onchange: events.themes.name,
+            placeholder: 'Name'
+          }),
+          m('.mdc-line-ripple')
+        ),
+        // url
+        m('.mdc-text-field m-textfield m-url', {
+          oncreate: oncreate.textfield,
+          },
+          m('input.mdc-text-field__input', {
+            type: 'text',
+            value: state.theme.url,
+            onchange: events.themes.url,
+            placeholder: 'URL - file:///home.. | http://localhost..'
+          }),
+          m('.mdc-line-ripple')
+        ),
+        m('button.mdc-button mdc-button--raised m-button', {
+          oncreate: oncreate.ripple,
+          onclick: events.themes.add
+          },
+          'Add'
+        ),
+      ),
+
+      // themes list
+      (state.themes.length || null) &&
+      m('ul.m-list', state.themes.map((theme) =>
+        m('li.mdc-elevation--z2', {
+          class: theme.expanded ? 'm-expanded' : null,
+          },
+          m('.m-summary', {
+            onclick: (e) => theme.expanded = !theme.expanded
+            },
+            m('.m-origin', theme.name),
+            m('i.material-icons', {
+              class: theme.expanded ? 'icon-arrow-up' : 'icon-arrow-down'
+            })
+          ),
+          m('.m-content',
+            // name
+            m('.m-option m-theme',
+              m('.m-name', m('span', 'Name')),
+              m('.m-control',
+                m('.mdc-text-field m-textfield', {
+                  oncreate: oncreate.textfield
+                  },
+                  m('input.mdc-text-field__input', {
+                    type: 'text',
+                    onkeyup: events.themes.update.name(theme),
+                    value: theme.name,
+                  }),
+                  m('.mdc-line-ripple')
+                )
+              )
+            ),
+            // url
+            m('.m-option m-theme',
+              m('.m-name', m('span', 'URL')),
+              m('.m-control',
+                m('.mdc-text-field m-textfield', {
+                  oncreate: oncreate.textfield
+                  },
+                  m('input.mdc-text-field__input', {
+                    type: 'text',
+                    onkeyup: events.themes.update.url(theme),
+                    value: theme.url,
+                  }),
+                  m('.mdc-line-ripple')
+                )
+              )
+            ),
+            // update/remove
+            m('.m-footer',
+              m('span',
+                m('button.mdc-button mdc-button--raised m-button', {
+                  oncreate: oncreate.ripple,
+                  onclick: events.themes.remove(theme)
+                  },
+                  'Remove'
+                )
+              )
+            )
+          )
+        )
+      ))
+    )
   ]
 })
 

+ 18 - 5
content/popup.js

@@ -5,6 +5,7 @@ var state = {
   content: {},
   theme: '',
   themes: [],
+  custom: [],
   raw: false,
   tab: '',
   tabs: ['theme', 'compiler', 'content'],
@@ -12,6 +13,7 @@ var state = {
   description: {
     compiler: {},
     content: {
+      autoreload: 'Auto reload on file change',
       emoji: 'Convert emoji :shortnames: into EmojiOne images',
       scroll: 'Remember scroll position',
       toc: 'Generate Table of Contents',
@@ -56,7 +58,16 @@ var events = {
   },
 
   theme: (e) => {
-    state.theme = state.themes[e.target.selectedIndex]
+    var name = state.themes[e.target.selectedIndex]
+
+    var defaults = chrome.runtime.getManifest().web_accessible_resources
+      .filter((file) => file.indexOf('/themes/') === 0)
+      .map((file) => file.replace(/\/themes\/(.*)\.css/, '$1'))
+
+    state.theme = defaults.includes(name)
+      ? {name, url: chrome.runtime.getURL(`/themes/${name}.css`)}
+      : state.custom.find((theme) => theme.name === name)
+
     chrome.runtime.sendMessage({
       message: 'popup.theme',
       theme: state.theme
@@ -92,9 +103,11 @@ var init = (res) => {
   state.content = res.content
   state.theme = res.theme
 
-  state.themes = chrome.runtime.getManifest().web_accessible_resources
-    .filter((file) => file.indexOf('/themes/') === 0)
-    .map((file) => file.replace(/\/themes\/(.*)\.css/, '$1'))
+  state.custom = res.themes
+  state.themes = res.themes.map(({name}) => name).concat(
+    chrome.runtime.getManifest().web_accessible_resources
+      .filter((file) => file.indexOf('/themes/') === 0)
+      .map((file) => file.replace(/\/themes\/(.*)\.css/, '$1')))
 
   state.raw = res.raw
   state.tab = localStorage.getItem('tab') || 'theme'
@@ -168,7 +181,7 @@ m.mount(document.querySelector('body'), {
             onchange: events.theme
             },
             state.themes.map((theme) =>
-              m('option', {selected: state.theme === theme}, theme)
+              m('option', {selected: state.theme.name === theme}, theme)
             )
           )
         ),

+ 77 - 21
css/options.css

@@ -24,10 +24,24 @@ main {
   margin: 0 auto;
 }
 
+/*font family*/
+body,
+body #wrapper,
+body #wrapper header.mdc-toolbar .mdc-toolbar__title,
+body #wrapper header.mdc-toolbar nav.mdc-tab-bar a,
+body label,
+body input,
+body select,
+body button,
+body h4 {
+  font-family: 'Monospace', monospace !important;
+}
+
 
 /*content*/
-main { padding: 90px 0 30px 0; }
-#options { letter-spacing: 0.2px; }
+main {
+  padding: 90px 0 30px 0;
+}
 
 
 /*header*/
@@ -97,6 +111,10 @@ footer .icon-hidden {
   padding: 20px;
   margin: 0 0 30px 0;
 }
+.bs-callout h4 {
+  line-height: 36px;
+  margin: 0 !important;
+}
 
 
 /*button*/
@@ -139,12 +157,10 @@ footer .icon-hidden {
 
 /*select*/
 .m-select {
-  font-family: 'Roboto', 'Helvetica', 'Arial', sans-serif;
   font-size: 14px;
   line-height: 17px;
   color: #000;
   text-transform: uppercase;
-  letter-spacing: 0.2px;
   background-color: #ececec;
   border: none;
   border-radius: 2px;
@@ -162,14 +178,13 @@ footer .icon-hidden {
 }
 
 
-/*add-origin*/
+/*add origin*/
+
 .m-add-origin {}
 .m-add-origin:after { content: ''; display: block; clear: both; }
 .m-add-origin h4 {
-  line-height: 36px;
   float: left;
   padding: 0 100px 0 0;
-  margin: 0 !important;
 }
 .m-add-origin .m-select {
   width: 110px;
@@ -194,6 +209,31 @@ footer .icon-hidden {
 }
 
 
+/*add theme*/
+.m-add-theme:after { content: ''; display: block; clear: both; }
+.m-add-theme h4 {
+  float: left;
+  padding: 0 80px 0 0;
+}
+.m-add-theme .m-textfield {
+  height: auto !important;
+  padding: 0 !important;
+  margin: 0 10px !important;
+}
+.m-add-theme .m-textfield input {
+  padding-top: 3px;
+}
+.m-add-theme .m-name {
+  width: 270px;
+}
+.m-add-theme .m-url {
+  width: 500px;
+}
+.m-add-theme button {
+  float: right;
+}
+
+
 /*global*/
 .m-global {
   margin: 20px 0 0 0;
@@ -208,6 +248,7 @@ footer .icon-hidden {
 
 
 /*list*/
+
 .m-list {
   list-style: none;
   padding: 0;
@@ -234,6 +275,8 @@ footer .icon-hidden {
   margin: 20px 0;
 }
 
+/*list summary*/
+
 .m-list .m-summary {
   display: block;
   height: 36px;
@@ -244,7 +287,6 @@ footer .icon-hidden {
 .m-list .m-summary:after { content: ''; display: block; clear: both; }
 .m-list .m-summary .m-origin {
   float: left;
-  font-family: monospace;
   font-size: 16px;
   line-height: 28px;
 }
@@ -266,8 +308,9 @@ footer .icon-hidden {
   right: 17px;
 }
 
+/*list content*/
+
 .m-list .m-content {
-  /*TEST*/
   display: none;
   background: #fff;
   padding: 0 20px 12px 20px;
@@ -276,11 +319,12 @@ footer .icon-hidden {
 .m-list .m-expanded .m-summary {
   background: #ececec;
 }
-.m-list .m-expanded .m-origin {}
 .m-list .m-expanded .m-content {
   display: block;
 }
 
+/*list option*/
+
 .m-list .m-content .m-option {
   min-height: 50px;
 }
@@ -296,38 +340,50 @@ footer .icon-hidden {
   font-size: 12px;
   line-height: 15px;
   text-transform: uppercase;
-  letter-spacing: .2px;
 }
 .m-list .m-content .m-option .m-control {
   float: left;
   width: 90%;
 }
+.m-list .m-content .m-option .m-control .m-textfield {
+  height: 43px;
+  margin: 0 0 7px 10px;
+}
+.m-list .m-content .m-option .m-control .m-textfield input {
+  border-bottom: 1px solid #ececec !important;
+  padding-top: 3px;
+}
+
+
+/*list options - origin*/
 
-.m-list .m-content .m-option.m-match {}
 .m-list .m-content .m-option.m-match .m-control {
   width: 80%;
   margin: 0 0 0 15px;
 }
 .m-list .m-content .m-option.m-match .m-textfield {
   width: 100%;
-  height: 43px;
-  margin: 0 0 7px 10px;
 }
-.m-list .m-content .m-option.m-match input {
-  border-bottom: 1px solid #ececec !important;
-  padding-top: 3px;
-}
-
-.m-list .m-content .m-option.m-csp {}
 .m-list .m-content .m-option.m-csp label {
   margin: 12px 0 0 10px;
 }
-.m-list .m-content .m-option.m-encoding {}
 .m-list .m-content .m-option.m-encoding select {
   width: 200px;
   margin: 6px 0 0 25px;
 }
 
+/*list options - themes*/
+
+.m-list .m-content .m-option.m-theme .m-control {
+  width: 80%;
+  margin: 0 0 0 15px;
+}
+.m-list .m-content .m-option.m-theme .m-textfield {
+  width: 80%;
+}
+
+/*list footer*/
+
 .m-list .m-footer {
   text-align: right;
   position: absolute;