diff options
| -rw-r--r-- | ext/bg/css/settings.css | 9 | ||||
| -rw-r--r-- | ext/bg/js/options.js | 2 | ||||
| -rw-r--r-- | ext/bg/js/search.js | 4 | ||||
| -rw-r--r-- | ext/bg/js/settings-popup-preview.js | 161 | ||||
| -rw-r--r-- | ext/bg/js/settings.js | 48 | ||||
| -rw-r--r-- | ext/bg/search.html | 4 | ||||
| -rw-r--r-- | ext/bg/settings-popup-preview.html | 125 | ||||
| -rw-r--r-- | ext/bg/settings.html | 33 | ||||
| -rw-r--r-- | ext/fg/css/client.css | 8 | ||||
| -rw-r--r-- | ext/fg/float.html | 12 | ||||
| -rw-r--r-- | ext/fg/js/float.js | 21 | ||||
| -rw-r--r-- | ext/fg/js/frontend.js | 8 | ||||
| -rw-r--r-- | ext/fg/js/popup-proxy-host.js | 6 | ||||
| -rw-r--r-- | ext/fg/js/popup-proxy.js | 5 | ||||
| -rw-r--r-- | ext/fg/js/popup.js | 46 | ||||
| -rw-r--r-- | ext/mixed/css/display-dark.css | 50 | ||||
| -rw-r--r-- | ext/mixed/css/display-default.css | 50 | ||||
| -rw-r--r-- | ext/mixed/css/display.css | 96 | ||||
| -rw-r--r-- | ext/mixed/js/display.js | 27 | 
19 files changed, 627 insertions, 88 deletions
| diff --git a/ext/bg/css/settings.css b/ext/bg/css/settings.css index 100478aa..09d60b26 100644 --- a/ext/bg/css/settings.css +++ b/ext/bg/css/settings.css @@ -148,6 +148,15 @@ input[type=checkbox]#storage-persist-button-checkbox {      padding: 0;  } +#settings-popup-preview-frame { +    background-color: transparent; +    border: none; +    margin: 0; +    padding: 0; +    width: 100%; +    height: 320px; +} +  [data-show-for-browser] {      display: none;  } diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js index 1021e18d..cadc4443 100644 --- a/ext/bg/js/options.js +++ b/ext/bg/js/options.js @@ -276,6 +276,8 @@ function profileOptionsCreateDefaults() {              compactTags: false,              compactGlossaries: false,              mainDictionary: '', +            popupTheme: 'default', +            popupOuterTheme: 'default',              customPopupCss: ''          }, diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index 1d780d70..68afe47e 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -118,6 +118,10 @@ class DisplaySearch extends Display {          return this.optionsContext;      } +    setCustomCss() { +        // No custom CSS +    } +      setIntroVisible(visible, animate) {          if (this.introVisible === visible) {              return; diff --git a/ext/bg/js/settings-popup-preview.js b/ext/bg/js/settings-popup-preview.js new file mode 100644 index 00000000..53a5f1d0 --- /dev/null +++ b/ext/bg/js/settings-popup-preview.js @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2019  Alex Yatskov <alex@foosoft.net> + * Author: Alex Yatskov <alex@foosoft.net> + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ + + +class SettingsPopupPreview { +    constructor() { +        this.frontend = null; +        this.apiOptionsGetOld = apiOptionsGet; +        this.popupShown = false; +        this.themeChangeTimeout = null; +    } + +    static create() { +        const instance = new SettingsPopupPreview(); +        instance.prepare(); +        return instance; +    } + +    async prepare() { +        // Setup events +        window.addEventListener('resize', (e) => this.onWindowResize(e), false); +        window.addEventListener('message', (e) => this.onMessage(e), false); + +        const themeDarkCheckbox = document.querySelector('#theme-dark-checkbox'); +        if (themeDarkCheckbox !== null) { +            themeDarkCheckbox.addEventListener('change', () => this.onThemeDarkCheckboxChanged(themeDarkCheckbox), false); +        } + +        // Overwrite API functions +        window.apiOptionsGet = (...args) => this.apiOptionsGet(...args); + +        // Overwrite frontend +        this.frontend = Frontend.create(); +        window.yomichan_frontend = this.frontend; + +        this.frontend.setEnabled = function () {}; +        this.frontend.searchClear = function () {}; + +        this.frontend.popup.childrenSupported = false; +        this.frontend.popup.interactive = false; + +        await this.frontend.isPrepared(); + +        // Update search +        this.updateSearch(); +    } + +    async apiOptionsGet(...args) { +        const options = await this.apiOptionsGetOld(...args); +        options.general.enable = true; +        options.general.debugInfo = false; +        options.general.popupWidth = 400; +        options.general.popupHeight = 250; +        options.general.popupHorizontalOffset = 0; +        options.general.popupVerticalOffset = 10; +        options.general.popupHorizontalOffset2 = 10; +        options.general.popupVerticalOffset2 = 0; +        options.general.popupHorizontalTextPosition = 'below'; +        options.general.popupVerticalTextPosition = 'before'; +        options.scanning.selectText = false; +        return options; +    } + +    onWindowResize() { +        if (this.frontend === null) { return; } +        const textSource = this.frontend.textSourceLast; +        if (textSource === null) { return; } + +        const elementRect = textSource.getRect(); +        const writingMode = textSource.getWritingMode(); +        const options = this.frontend.options; +        this.frontend.popup.show(elementRect, writingMode, options); +    } + +    onMessage(e) { +        const {action, params} = e.data; +        const handlers = SettingsPopupPreview.messageHandlers; +        if (handlers.hasOwnProperty(action)) { +            const handler = handlers[action]; +            handler(this, params); +        } +    } + +    onThemeDarkCheckboxChanged(node) { +        document.documentElement.classList.toggle('dark', node.checked); +        if (this.themeChangeTimeout !== null) { +            clearTimeout(this.themeChangeTimeout); +        } +        this.themeChangeTimeout = setTimeout(() => { +            this.themeChangeTimeout = null; +            this.frontend.popup.updateTheme(); +        }, 300); +    } + +    setText(text) { +        const exampleText = document.querySelector('#example-text'); +        if (exampleText === null) { return; } + +        exampleText.textContent = text; +        this.updateSearch(); +    } + +    setInfoVisible(visible) { +        const node = document.querySelector('.placeholder-info'); +        if (node === null) { return; } + +        node.classList.toggle('placeholder-info-visible', visible); +    } + +    setCustomCss(css) { +        if (this.frontend === null) { return; } +        this.frontend.popup.setCustomCss(css); +    } + +    async updateSearch() { +        const exampleText = document.querySelector('#example-text'); +        if (exampleText === null) { return; } + +        const textNode = exampleText.firstChild; +        if (textNode === null) { return; } + +        const range = document.createRange(); +        range.selectNode(textNode); +        const source = new TextSourceRange(range, range.toString(), null); + +        this.frontend.textSourceLast = null; +        await this.frontend.searchSource(source, 'script'); +        await this.frontend.lastShowPromise; + +        if (this.frontend.popup.isVisible()) { +            this.popupShown = true; +        } + +        this.setInfoVisible(!this.popupShown); +    } +} + +SettingsPopupPreview.messageHandlers = { +    setText: (self, {text}) => self.setText(text), +    setCustomCss: (self, {css}) => self.setCustomCss(css) +}; + +SettingsPopupPreview.instance = SettingsPopupPreview.create(); + + + diff --git a/ext/bg/js/settings.js b/ext/bg/js/settings.js index bd15f5d0..900b89bb 100644 --- a/ext/bg/js/settings.js +++ b/ext/bg/js/settings.js @@ -39,6 +39,8 @@ async function formRead(options) {      options.general.popupVerticalOffset = parseInt($('#popup-vertical-offset').val(), 10);      options.general.popupHorizontalOffset2 = parseInt($('#popup-horizontal-offset2').val(), 0);      options.general.popupVerticalOffset2 = parseInt($('#popup-vertical-offset2').val(), 10); +    options.general.popupTheme = $('#popup-theme').val(); +    options.general.popupOuterTheme = $('#popup-outer-theme').val();      options.general.customPopupCss = $('#custom-popup-css').val();      options.audio.enabled = $('#audio-playback-enabled').prop('checked'); @@ -107,6 +109,8 @@ async function formWrite(options) {      $('#popup-vertical-offset').val(options.general.popupVerticalOffset);      $('#popup-horizontal-offset2').val(options.general.popupHorizontalOffset2);      $('#popup-vertical-offset2').val(options.general.popupVerticalOffset2); +    $('#popup-theme').val(options.general.popupTheme); +    $('#popup-outer-theme').val(options.general.popupOuterTheme);      $('#custom-popup-css').val(options.general.customPopupCss);      $('#audio-playback-enabled').prop('checked', options.audio.enabled); @@ -248,6 +252,7 @@ async function onReady() {      showExtensionInformation();      formSetupEventListeners(); +    appearanceInitialize();      await audioSettingsInitialize();      await profileOptionsSetup(); @@ -260,6 +265,49 @@ $(document).ready(utilAsync(onReady));  /* + * Appearance + */ + +function appearanceInitialize() { +    let previewVisible = false; +    $('#settings-popup-preview-button').on('click', () => { +        if (previewVisible) { return; } +        showAppearancePreview(); +        previewVisible = true; +    }); +} + +function showAppearancePreview() { +    const container = $('#settings-popup-preview-container'); +    const buttonContainer = $('#settings-popup-preview-button-container'); +    const settings = $('#settings-popup-preview-settings'); +    const text = $('#settings-popup-preview-text'); +    const customCss = $('#custom-popup-css'); + +    const frame = document.createElement('iframe'); +    frame.src = '/bg/settings-popup-preview.html'; +    frame.id = 'settings-popup-preview-frame'; + +    window.wanakana.bind(text[0]); + +    text.on('input', () => { +        const action = 'setText'; +        const params = {text: text.val()}; +        frame.contentWindow.postMessage({action, params}, '*'); +    }); +    customCss.on('input', () => { +        const action = 'setCustomCss'; +        const params = {css: customCss.val()}; +        frame.contentWindow.postMessage({action, params}, '*'); +    }); + +    container.append(frame); +    buttonContainer.remove(); +    settings.css('display', ''); +} + + +/*   * Audio   */ diff --git a/ext/bg/search.html b/ext/bg/search.html index 3284ed43..6930830a 100644 --- a/ext/bg/search.html +++ b/ext/bg/search.html @@ -1,5 +1,5 @@  <!DOCTYPE html> -<html lang="en"> +<html lang="en" class="yomichan-search">      <head>          <meta charset="UTF-8">          <meta name="viewport" content="width=device-width,initial-scale=1" /> @@ -7,6 +7,8 @@          <link rel="stylesheet" type="text/css" href="/mixed/lib/bootstrap/css/bootstrap.min.css">          <link rel="stylesheet" type="text/css" href="/mixed/lib/bootstrap/css/bootstrap-theme.min.css">          <link rel="stylesheet" type="text/css" href="/mixed/css/display.css"> +        <link rel="stylesheet" type="text/css" href="/mixed/css/display-default.css" data-yomichan-theme-name="default"> +        <link rel="stylesheet alternate" type="text/css" href="/mixed/css/display-dark.css" data-yomichan-theme-name="dark">      </head>      <body>          <div class="container"> diff --git a/ext/bg/settings-popup-preview.html b/ext/bg/settings-popup-preview.html new file mode 100644 index 00000000..3d426f7a --- /dev/null +++ b/ext/bg/settings-popup-preview.html @@ -0,0 +1,125 @@ +<!DOCTYPE html> +<html lang="en"> +    <head> +        <meta charset="UTF-8"> +        <meta name="viewport" content="width=device-width,initial-scale=1" /> +        <title>Yomichan Popup Preview</title> +        <link rel="stylesheet" type="text/css" href="/fg/css/client.css"> +        <style> +            html { +                transition: background-color 0.25s linear 0s, color 0.25s linear 0s; +                color: #333333; +            } +            html.dark { +                background-color: #1e1e1e; +                color: #d4d4d4; +            } +            html, body { +                margin: 0; +                padding: 0; +                border: 0; +                overflow: hidden; +                width: 100%; +                height: 100%; +                font-family: "Helvetica Neue", Helvetica, Arial ,sans-serif; +                font-size: 14px; +            } +            iframe#yomichan-float { +                resize: none; +            } +            .vertical-align-outer { +                width: 100%; +                height: 100%; +                white-space: nowrap; +            } +            .vertical-align-outer::before { +                content: ""; +                display: inline-block; +                vertical-align: middle; +                width: 0; +                height: 100%; +            } +            .vertical-align-inner { +                display: inline-block; +                vertical-align: middle; +                white-space: normal; +                width: 100%; +            } +            .horizontal-size { +                max-width: 400px; +                padding: 15px; +                margin: 0 auto; +            } +            .example-text-container { +                font-size: 24px; +                line-height: 1.25em; +                height: 1.25em; +            } +            .popup-placeholder { +                height: 250px; +                padding-top: 10px; +                border: 1px solid rgba(0, 0, 0, 0); +            } +            .placeholder-info { +                visibility: hidden; +                opacity: 0; +                transition: opacity 0.5s linear 0s, visibility 0s linear 0.5s; +            } +            .placeholder-info.placeholder-info-visible { +                visibility: visible; +                opacity: 1; +                transition: opacity 0.5s linear 0s, visibility 0s linear 0s; +            } + +            .options { +                float: right; +                font-size: 14px; +                line-height: 30px; +            } +            .theme-button { +                display: inline-block; +                margin-left: 0.5em; +                text-decoration: none; +                cursor: pointer; +                white-space: nowrap; +                line-height: 0; +            } +            .theme-button>input { +                vertical-align: middle; +                margin: 0 0.25em 0 0; +                padding: 0; +            } +            .theme-button>span { +                vertical-align: middle; +            } +            .theme-button:hover>span { +                text-decoration: underline; +            } +        </style> +    </head> +    <body> +        <div class="vertical-align-outer"><div class="vertical-align-inner"><div class="horizontal-size"> +            <div class="example-text-container"> +                <div class="options"><label class="theme-button"><input type="checkbox" id="theme-dark-checkbox" /><span>dark</span></label></div> +                <span id="example-text">読め</span> +            </div> +            <div class="popup-placeholder"> +                <div class="vertical-align-outer"><div class="vertical-align-inner placeholder-info"> +                    This page uses the dictionaries you have installed in order to show a preview. +                    If you see this message, make sure you have a dictionary installed. +                </div></div> +            </div> +        </div></div></div> + +        <script src="/mixed/js/extension.js"></script> +        <script src="/fg/js/api.js"></script> +        <script src="/fg/js/document.js"></script> +        <script src="/fg/js/frontend-api-receiver.js"></script> +        <script src="/fg/js/popup.js"></script> +        <script src="/fg/js/source.js"></script> +        <script src="/fg/js/util.js"></script> +        <script src="/fg/js/popup-proxy-host.js"></script> +        <script src="/fg/js/frontend.js"></script> +        <script src="/bg/js/settings-popup-preview.js"></script> +    </body> +</html> diff --git a/ext/bg/settings.html b/ext/bg/settings.html index 76955b2c..08e56a09 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -231,10 +231,42 @@                      </div>                  </div> +                <div class="form-group"> +                    <div class="row"> +                        <div class="col-xs-6"> +                            <label for="popup-theme">Popup theme</label> +                            <select class="form-control" id="popup-theme"> +                                <option value="default">Light</option> +                                <option value="dark">Dark</option> +                            </select> +                        </div> +                        <div class="col-xs-6"> +                            <label for="popup-outer-theme">Popup shadow theme</label> +                            <select class="form-control" id="popup-outer-theme"> +                                <option value="auto">Auto-detect</option> +                                <option value="default">Light</option> +                                <option value="dark">Dark</option> +                            </select> +                        </div> +                    </div> +                </div> +                  <div class="form-group options-advanced">                      <label for="custom-popup-css">Custom popup CSS</label>                      <div><textarea autocomplete="off" spellcheck="false" wrap="soft" id="custom-popup-css" class="form-control"></textarea></div>                  </div> + +                <div class="form-group ignore-form-changes" style="display: none;" id="settings-popup-preview-settings"> +                    <label for="settings-popup-preview-text">Popup preview text</label> +                    <input type="text" id="settings-popup-preview-text" class="form-control" value="読め"> +                </div> + +                <div class="form-group ignore-form-changes"> +                    <div id="settings-popup-preview-button-container"> +                        <button class="btn btn-default" id="settings-popup-preview-button">Show popup preview</button> +                    </div> +                    <div id="settings-popup-preview-container"></div> +                </div>              </div>              <div> @@ -603,6 +635,7 @@          <script src="/mixed/lib/jquery.min.js"></script>          <script src="/mixed/lib/bootstrap/js/bootstrap.min.js"></script>          <script src="/mixed/lib/handlebars.min.js"></script> +        <script src="/mixed/lib/wanakana.min.js"></script>          <script src="/mixed/js/extension.js"></script> diff --git a/ext/fg/css/client.css b/ext/fg/css/client.css index a2b06d0f..84098653 100644 --- a/ext/fg/css/client.css +++ b/ext/fg/css/client.css @@ -21,7 +21,7 @@ iframe#yomichan-float {      all: initial;      background-color: #fff;      border: 1px solid #999; -    box-shadow: 0 0 10px rgba(0, 0, 0, .5); +    box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);      position: fixed;      resize: both;      visibility: hidden; @@ -29,6 +29,12 @@ iframe#yomichan-float {      box-sizing: border-box;  } +iframe#yomichan-float[data-yomichan-theme=dark] { +    background-color: #1e1e1e; +    border: 1px solid #666; +    box-shadow: 0 0 10px rgba(255, 255, 255, 0.5); +} +  iframe#yomichan-float.yomichan-float-full-width {      border-left: none;      border-right: none; diff --git a/ext/fg/float.html b/ext/fg/float.html index fe1aee8f..2504f448 100644 --- a/ext/fg/float.html +++ b/ext/fg/float.html @@ -1,18 +1,12 @@  <!DOCTYPE html> -<html lang="en"> +<html lang="en" class="yomichan-float">      <head>          <meta charset="UTF-8">          <meta name="viewport" content="width=device-width,initial-scale=1" />          <title></title> -        <link rel="stylesheet" href="/mixed/lib/bootstrap/css/bootstrap.min.css"> -        <link rel="stylesheet" href="/mixed/lib/bootstrap/css/bootstrap-theme.min.css">          <link rel="stylesheet" href="/mixed/css/display.css"> -        <style type="text/css"> -            .entry, .note { -                padding-left: 10px; -                padding-right: 10px; -            } -        </style> +        <link rel="stylesheet" type="text/css" href="/mixed/css/display-default.css" data-yomichan-theme-name="default"> +        <link rel="stylesheet alternate" type="text/css" href="/mixed/css/display-dark.css" data-yomichan-theme-name="dark">      </head>      <body>          <div id="spinner"> diff --git a/ext/fg/js/float.js b/ext/fg/js/float.js index 5164cd8f..4b3cd848 100644 --- a/ext/fg/js/float.js +++ b/ext/fg/js/float.js @@ -21,7 +21,6 @@ class DisplayFloat extends Display {      constructor() {          super(document.querySelector('#spinner'), document.querySelector('#definitions'));          this.autoPlayAudioTimer = null; -        this.styleNode = null;          this.optionsContext = {              depth: 0, @@ -101,11 +100,6 @@ class DisplayFloat extends Display {      async initialize(options, popupInfo, url, childrenSupported) {          await super.initialize(options); -        const css = options.general.customPopupCss; -        if (css) { -            this.setStyle(css); -        } -          const {id, depth, parentFrameId} = popupInfo;          this.optionsContext.depth = depth;          this.optionsContext.url = url; @@ -114,20 +108,6 @@ class DisplayFloat extends Display {              popupNestedInitialize(id, depth, parentFrameId, url);          }      } - -    setStyle(css) { -        const parent = document.head; - -        if (this.styleNode === null) { -            this.styleNode = document.createElement('style'); -        } - -        this.styleNode.textContent = css; - -        if (this.styleNode.parentNode !== parent) { -            parent.appendChild(this.styleNode); -        } -    }  }  DisplayFloat.onKeyDownHandlers = { @@ -145,6 +125,7 @@ DisplayFloat.messageHandlers = {      kanjiShow: (self, {definitions, context}) => self.kanjiShow(definitions, context),      clearAutoPlayTimer: (self) => self.clearAutoPlayTimer(),      orphaned: (self) => self.onOrphaned(), +    setCustomCss: (self, {css}) => self.setCustomCss(css),      initialize: (self, {options, popupInfo, url, childrenSupported}) => self.initialize(options, popupInfo, url, childrenSupported)  }; diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js index 52a23889..3ddeae78 100644 --- a/ext/fg/js/frontend.js +++ b/ext/fg/js/frontend.js @@ -44,6 +44,8 @@ class Frontend {          this.isPreparedPromiseResolve = null;          this.isPreparedPromise = new Promise((resolve) => { this.isPreparedPromiseResolve = resolve; }); + +        this.lastShowPromise = Promise.resolve();      }      static create() { @@ -331,7 +333,7 @@ class Frontend {          } catch (e) {              if (window.yomichan_orphaned) {                  if (textSource && this.options.scanning.modifier !== 'none') { -                    this.popup.showOrphaned( +                    this.lastShowPromise = this.popup.showOrphaned(                          textSource.getRect(),                          textSource.getWritingMode()                      ); @@ -369,7 +371,7 @@ class Frontend {          const sentence = docSentenceExtract(textSource, this.options.anki.sentenceExt);          const url = window.location.href; -        this.popup.termsShow( +        this.lastShowPromise = this.popup.termsShow(              textSource.getRect(),              textSource.getWritingMode(),              definitions, @@ -399,7 +401,7 @@ class Frontend {          const sentence = docSentenceExtract(textSource, this.options.anki.sentenceExt);          const url = window.location.href; -        this.popup.kanjiShow( +        this.lastShowPromise = this.popup.kanjiShow(              textSource.getRect(),              textSource.getWritingMode(),              definitions, diff --git a/ext/fg/js/popup-proxy-host.js b/ext/fg/js/popup-proxy-host.js index 74a5153a..bb323f64 100644 --- a/ext/fg/js/popup-proxy-host.js +++ b/ext/fg/js/popup-proxy-host.js @@ -45,6 +45,7 @@ class PopupProxyHost {              containsPoint: ({id, x, y}) => this.containsPoint(id, x, y),              termsShow: ({id, elementRect, writingMode, definitions, context}) => this.termsShow(id, elementRect, writingMode, definitions, context),              kanjiShow: ({id, elementRect, writingMode, definitions, context}) => this.kanjiShow(id, elementRect, writingMode, definitions, context), +            setCustomCss: ({id, css}) => this.setCustomCss(id, css),              clearAutoPlayTimer: ({id}) => this.clearAutoPlayTimer(id)          });      } @@ -126,6 +127,11 @@ class PopupProxyHost {          return await popup.kanjiShow(elementRect, writingMode, definitions, context);      } +    async setCustomCss(id, css) { +        const popup = this.getPopup(id); +        return popup.setCustomCss(css); +    } +      async clearAutoPlayTimer(id) {          const popup = this.getPopup(id);          return popup.clearAutoPlayTimer(); diff --git a/ext/fg/js/popup-proxy.js b/ext/fg/js/popup-proxy.js index e8d6bc98..6ea94b6a 100644 --- a/ext/fg/js/popup-proxy.js +++ b/ext/fg/js/popup-proxy.js @@ -88,6 +88,11 @@ class PopupProxy {          return await this.invokeHostApi('kanjiShow', {id, elementRect, writingMode, definitions, context});      } +    async setCustomCss(css) { +        const id = await this.getPopupId(); +        return await this.invokeHostApi('setCustomCss', {id, css}); +    } +      async clearAutoPlayTimer() {          if (this.id === null) {              return; diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js index f36bb436..ef4cdb67 100644 --- a/ext/fg/js/popup.js +++ b/ext/fg/js/popup.js @@ -85,6 +85,7 @@ class Popup {      async setOptions(options) {          this.options = options; +        this.updateTheme();      }      async show(elementRect, writingMode) { @@ -269,6 +270,47 @@ class Popup {          }      } +    updateTheme() { +        this.container.dataset.yomichanTheme = this.getTheme(this.options.general.popupOuterTheme); +    } + +    getTheme(themeName) { +        if (themeName === 'auto') { +            const color = [255, 255, 255]; +            Popup.addColor(color, Popup.getColorInfo(window.getComputedStyle(document.documentElement).backgroundColor)); +            Popup.addColor(color, Popup.getColorInfo(window.getComputedStyle(document.body).backgroundColor)); +            const dark = (color[0] < 128 && color[1] < 128 && color[2] < 128); +            themeName = dark ? 'dark' : 'default'; +        } + +        return themeName; +    } + +    static addColor(target, color) { +        if (color === null) { return; } + +        const a = color[3]; +        if (a <= 0.0) { return; } + +        const aInv = 1.0 - a; +        for (let i = 0; i < 3; ++i) { +            target[i] = target[i] * aInv + color[i] * a; +        } +    } + +    static getColorInfo(cssColor) { +        const m = /^\s*rgba?\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([\d\.]+)\s*)?\)\s*$/.exec(cssColor); +        if (m === null) { return null; } + +        const m4 = m[4]; +        return [ +            Number.parseInt(m[1], 10), +            Number.parseInt(m[2], 10), +            Number.parseInt(m[3], 10), +            m4 ? Math.max(0.0, Math.min(1.0, Number.parseFloat(m4))) : 1.0 +        ]; +    } +      async containsPoint(x, y) {          for (let popup = this; popup !== null && popup.isVisible(); popup = popup.child) {              const rect = popup.container.getBoundingClientRect(); @@ -291,6 +333,10 @@ class Popup {          this.invokeApi('kanjiShow', {definitions, context});      } +    async setCustomCss(css) { +        this.invokeApi('setCustomCss', {css}); +    } +      clearAutoPlayTimer() {          if (this.isInjected) {              this.invokeApi('clearAutoPlayTimer'); diff --git a/ext/mixed/css/display-dark.css b/ext/mixed/css/display-dark.css new file mode 100644 index 00000000..34a0ccd1 --- /dev/null +++ b/ext/mixed/css/display-dark.css @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2019  Alex Yatskov <alex@foosoft.net> + * Author: Alex Yatskov <alex@foosoft.net> + * + * This program is free software: you can redistribute it and/or modify + * it under the entrys of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ + + +body { background-color: #1e1e1e; color: #d4d4d4; } + +hr { border-top-color: #2f2f2f; } + +.tag-default      { background-color: #69696e; } +.tag-name         { background-color: #489148; } +.tag-expression   { background-color: #b07f39; } +.tag-popular      { background-color: #025caa; } +.tag-frequent     { background-color: #4490a7; } +.tag-archaism     { background-color: #b04340; } +.tag-dictionary   { background-color: #9057ad; } +.tag-frequency    { background-color: #489148; } +.tag-partOfSpeech { background-color: #565656; } + +.reasons       { color: #888888; } +.glossary li   { color: #888888; } +.glossary-item { color: #d4d4d4; } +.label         { color: #e1e1e1; } + +.expression .kanji-link { +    border-bottom-color: #888888; +    color: #CCCCCC; +} + +.expression-popular, .expression-popular .kanji-link { +    color: #0275d8; +} + +.expression-rare, .expression-rare .kanji-link { +    color: #666666; +} diff --git a/ext/mixed/css/display-default.css b/ext/mixed/css/display-default.css new file mode 100644 index 00000000..176c5387 --- /dev/null +++ b/ext/mixed/css/display-default.css @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2019  Alex Yatskov <alex@foosoft.net> + * Author: Alex Yatskov <alex@foosoft.net> + * + * This program is free software: you can redistribute it and/or modify + * it under the entrys of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program.  If not, see <http://www.gnu.org/licenses/>. + */ + + +body { background-color: #ffffff; color: #333333; } + +hr { border-top-color: #eeeeee; } + +.tag-default      { background-color: #8a8a91; } +.tag-name         { background-color: #5cb85c; } +.tag-expression   { background-color: #f0ad4e; } +.tag-popular      { background-color: #0275d8; } +.tag-frequent     { background-color: #5bc0de; } +.tag-archaism     { background-color: #d9534f; } +.tag-dictionary   { background-color: #aa66cc; } +.tag-frequency    { background-color: #5cb85c; } +.tag-partOfSpeech { background-color: #565656; } + +.reasons       { color: #777777; } +.glossary li   { color: #777777; } +.glossary-item { color: #000000; } +.label         { color: #ffffff; } + +.expression .kanji-link { +    border-bottom-color: #777777; +    color: #333333; +} + +.expression-popular, .expression-popular .kanji-link { +    color: #0275d8; +} + +.expression-rare, .expression-rare .kanji-link { +    color: #999999; +} diff --git a/ext/mixed/css/display.css b/ext/mixed/css/display.css index 8a4cf4a7..7ebad090 100644 --- a/ext/mixed/css/display.css +++ b/ext/mixed/css/display.css @@ -30,9 +30,31 @@   * General   */ +html.yomichan-float:not([data-yomichan-theme]), +html.yomichan-float:not([data-yomichan-theme]) body { +    background-color: transparent; +} + +body { +    font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; +    font-size: 14px; +    line-height: 1.42857143; +    margin: 0; +    border: 0; +    padding: 0; +} +  hr {      padding: 0px;      margin: 0px; +    border: 0; +    border-top-width: 1px; +    border-top-style: solid; +} + +ol, ul { +    margin-top: 0; +    margin-bottom: 10px;  }  #spinner { @@ -60,40 +82,10 @@ hr {      padding-bottom: 10px;  } -.tag-default { -    background-color: #8a8a91; -} - -.tag-name { -    background-color: #5cb85c; -} - -.tag-expression { -    background-color: #f0ad4e; -} - -.tag-popular { -    background-color: #0275d8; -} - -.tag-frequent { -    background-color: #5bc0de; -} - -.tag-archaism { -    background-color: #d9534f; -} - -.tag-dictionary { -    background-color: #aa66cc; -} - -.tag-frequency { -    background-color: #5cb85c; -} - -.tag-partOfSpeech { -    background-color: #565656; +html:root.yomichan-float .entry, +html:root.yomichan-float .note { +    padding-left: 10px; +    padding-right: 10px;  }  .actions .disabled { @@ -103,6 +95,7 @@ hr {  .actions .disabled img {      -webkit-filter: grayscale(100%); +    filter: grayscale(100%);      opacity: 0.25;  } @@ -111,7 +104,7 @@ hr {  }  .actions { -    display: inline-block; +    display: block;      float: right;  } @@ -127,19 +120,11 @@ hr {  }  .expression .kanji-link { -    border-bottom: 1px #777 dashed; -    color: #333; +    border-bottom-width: 1px; +    border-bottom-style: dashed;      text-decoration: none;  } -.expression-popular, .expression-popular .kanji-link { -    color: #0275d8; -} - -.expression-rare, .expression-rare .kanji-link { -    color: #999; -} -  .expression .peek-wrapper {      font-size: 14px;      white-space: nowrap; @@ -173,7 +158,6 @@ hr {  }  .reasons { -    color: #777;      display: inline-block;  } @@ -199,14 +183,6 @@ hr {      content: " | ";  } -.glossary li { -    color: #777; -} - -.glossary-item { -    color: #000; -} -  div.glossary-item.compact-glossary {      display: inline;  } @@ -234,3 +210,15 @@ div.glossary-item.compact-glossary {  .entry:not(.entry-current) .current {      display: none;  } + +.label { +    display: inline; +    padding: 0.2em 0.6em 0.3em; +    font-size: 75%; +    font-weight: 700; +    line-height: 1; +    text-align: center; +    white-space: nowrap; +    vertical-align: baseline; +    border-radius: 0.25em; +} diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index cd9f41bd..51a3dc22 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -29,6 +29,7 @@ class Display {          this.audioPlaying = null;          this.audioFallback = null;          this.audioCache = {}; +        this.styleNode = null;          this.eventListeners = [];          this.persistentEventListeners = []; @@ -194,6 +195,32 @@ class Display {      async updateOptions(options) {          this.options = options ? options : await apiOptionsGet(this.getOptionsContext()); +        this.updateTheme(this.options.general.popupTheme); +        this.setCustomCss(this.options.general.customPopupCss); +    } + +    updateTheme(themeName) { +        document.documentElement.dataset.yomichanTheme = themeName; + +        const stylesheets = document.querySelectorAll('link[data-yomichan-theme-name]'); +        for (const stylesheet of stylesheets) { +            const match = (stylesheet.dataset.yomichanThemeName === themeName); +            stylesheet.rel = (match ? 'stylesheet' : 'stylesheet alternate'); +        } +    } + +    setCustomCss(css) { +        if (this.styleNode === null) { +            if (css.length === 0) { return; } +            this.styleNode = document.createElement('style'); +        } + +        this.styleNode.textContent = css; + +        const parent = document.head; +        if (this.styleNode.parentNode !== parent) { +            parent.appendChild(this.styleNode); +        }      }      setInteractive(interactive) { |