diff options
Diffstat (limited to 'ext/fg/js')
| -rw-r--r-- | ext/fg/js/float.js | 107 | ||||
| -rw-r--r-- | ext/fg/js/frontend.js | 80 | ||||
| -rw-r--r-- | ext/fg/js/popup-proxy-host.js | 6 | ||||
| -rw-r--r-- | ext/fg/js/popup-proxy.js | 4 | ||||
| -rw-r--r-- | ext/fg/js/popup.js | 41 | ||||
| -rw-r--r-- | ext/fg/js/source.js | 190 | 
6 files changed, 240 insertions, 188 deletions
| diff --git a/ext/fg/js/float.js b/ext/fg/js/float.js index fd7986b8..88842eef 100644 --- a/ext/fg/js/float.js +++ b/ext/fg/js/float.js @@ -19,7 +19,7 @@  class DisplayFloat extends Display {      constructor() { -        super($('#spinner'), $('#definitions')); +        super(document.querySelector('#spinner'), document.querySelector('#definitions'));          this.autoPlayAudioTimer = null;          this.styleNode = null; @@ -30,7 +30,7 @@ class DisplayFloat extends Display {          this.dependencies = Object.assign({}, this.dependencies, {docRangeFromPoint, docSentenceExtract}); -        $(window).on('message', utilAsync(this.onMessage.bind(this))); +        window.addEventListener('message', (e) => this.onMessage(e), false);      }      onError(error) { @@ -42,8 +42,16 @@ class DisplayFloat extends Display {      }      onOrphaned() { -        $('#definitions').hide(); -        $('#error-orphaned').show(); +        const definitions = document.querySelector('#definitions'); +        const errorOrphaned = document.querySelector('#error-orphaned'); + +        if (definitions !== null) { +            definitions.style.setProperty('display', 'none', 'important'); +        } + +        if (errorOrphaned !== null) { +            errorOrphaned.style.setProperty('display', 'block', 'important'); +        }      }      onSearchClear() { @@ -55,60 +63,25 @@ class DisplayFloat extends Display {      }      onMessage(e) { -        const handlers = { -            termsShow: ({definitions, options, context}) => { -                this.termsShow(definitions, options, context); -            }, - -            kanjiShow: ({definitions, options, context}) => { -                this.kanjiShow(definitions, options, context); -            }, - -            clearAutoPlayTimer: () => { -                this.clearAutoPlayTimer(); -            }, - -            orphaned: () => { -                this.onOrphaned(); -            }, - -            setOptions: (options) => { -                const css = options.general.customPopupCss; -                if (css) { -                    this.setStyle(css); -                } -            }, - -            popupNestedInitialize: ({id, depth, parentFrameId, url}) => { -                this.optionsContext.depth = depth; -                this.optionsContext.url = url; -                popupNestedInitialize(id, depth, parentFrameId, url); -            } -        }; - -        const {action, params} = e.originalEvent.data; -        const handler = handlers[action]; -        if (handler) { -            handler(params); +        const {action, params} = e.data; +        const handlers = DisplayFloat.messageHandlers; +        if (handlers.hasOwnProperty(action)) { +            const handler = handlers[action]; +            handler(this, params);          }      }      onKeyDown(e) { -        const handlers = { -            67: /* c */ () => { -                if (e.ctrlKey && !window.getSelection().toString()) { -                    this.onSelectionCopy(); -                    return true; -                } +        const key = Display.getKeyFromEvent(e); +        const handlers = DisplayFloat.onKeyDownHandlers; +        if (handlers.hasOwnProperty(key)) { +            const handler = handlers[key]; +            if (handler(this, e)) { +                e.preventDefault(); +                return;              } -        }; - -        const handler = handlers[e.keyCode]; -        if (handler && handler()) { -            e.preventDefault(); -        } else { -            super.onKeyDown(e);          } +        super.onKeyDown(e);      }      autoPlayAudio() { @@ -123,6 +96,18 @@ class DisplayFloat extends Display {          }      } +    initialize(options, popupInfo, url) { +        const css = options.general.customPopupCss; +        if (css) { +            this.setStyle(css); +        } + +        const {id, depth, parentFrameId} = popupInfo; +        this.optionsContext.depth = depth; +        this.optionsContext.url = url; +        popupNestedInitialize(id, depth, parentFrameId, url); +    } +      setStyle(css) {          const parent = document.head; @@ -138,4 +123,22 @@ class DisplayFloat extends Display {      }  } +DisplayFloat.onKeyDownHandlers = { +    'C': (self, e) => { +        if (e.ctrlKey && !window.getSelection().toString()) { +            self.onSelectionCopy(); +            return true; +        } +        return false; +    } +}; + +DisplayFloat.messageHandlers = { +    termsShow: (self, {definitions, options, context}) => self.termsShow(definitions, options, context), +    kanjiShow: (self, {definitions, options, context}) => self.kanjiShow(definitions, options, context), +    clearAutoPlayTimer: (self) => self.clearAutoPlayTimer(), +    orphaned: (self) => self.onOrphaned(), +    initialize: (self, {options, popupInfo, url}) => self.initialize(options, popupInfo, url) +}; +  window.yomichan_display = new DisplayFloat(); diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js index 167e82c0..58dc0e4a 100644 --- a/ext/fg/js/frontend.js +++ b/ext/fg/js/frontend.js @@ -55,7 +55,7 @@ class Frontend {          try {              this.options = await apiOptionsGet(this.getOptionsContext()); -            window.addEventListener('message', this.onFrameMessage.bind(this)); +            window.addEventListener('message', this.onWindowMessage.bind(this));              window.addEventListener('mousedown', this.onMouseDown.bind(this));              window.addEventListener('mousemove', this.onMouseMove.bind(this));              window.addEventListener('mouseover', this.onMouseOver.bind(this)); @@ -71,7 +71,7 @@ class Frontend {                  window.addEventListener('contextmenu', this.onContextMenu.bind(this));              } -            chrome.runtime.onMessage.addListener(this.onBgMessage.bind(this)); +            chrome.runtime.onMessage.addListener(this.onRuntimeMessage.bind(this));          } catch (e) {              this.onError(e);          } @@ -128,32 +128,24 @@ class Frontend {          }          this.popupTimerClear(); -        this.searchClear(); +        this.searchClear(true);      }      onMouseOut(e) {          this.popupTimerClear();      } -    onFrameMessage(e) { -        const handlers = { -            popupClose: () => { -                this.searchClear(); -            }, - -            selectionCopy: () => { -                document.execCommand('copy'); -            } -        }; - -        const handler = handlers[e.data]; -        if (handler) { -            handler(); +    onWindowMessage(e) { +        const action = e.data; +        const handlers = Frontend.windowMessageHandlers; +        if (handlers.hasOwnProperty(action)) { +            const handler = handlers[action]; +            handler(this);          }      }      onResize() { -        this.searchClear(); +        this.searchClear(true);      }      onClick(e) { @@ -240,20 +232,11 @@ class Frontend {          this.contextMenuChecking = false;      } -    onBgMessage({action, params}, sender, callback) { -        const handlers = { -            optionsUpdate: () => { -                this.updateOptions(); -            }, - -            popupSetVisible: ({visible}) => { -                this.popup.setVisible(visible); -            } -        }; - -        const handler = handlers[action]; -        if (handler) { -            handler(params); +    onRuntimeMessage({action, params}, sender, callback) { +        const handlers = Frontend.runtimeMessageHandlers; +        if (handlers.hasOwnProperty(action)) { +            const handler = handlers[action]; +            handler(this, params);              callback();          }      } @@ -265,7 +248,7 @@ class Frontend {      async updateOptions() {          this.options = await apiOptionsGet(this.getOptionsContext());          if (!this.options.enable) { -            this.searchClear(); +            this.searchClear(false);          }      } @@ -320,7 +303,7 @@ class Frontend {                  textSource.cleanup();              }              if (hideResults && this.options.scanning.autoHideResults) { -                this.searchClear(); +                this.searchClear(true);              }              this.pendingLookup = false; @@ -333,7 +316,7 @@ class Frontend {          const searchText = textSource.text();          if (searchText.length === 0) { -            return; +            return false;          }          const {definitions, length} = await apiTermsFind(searchText, this.getOptionsContext()); @@ -366,7 +349,7 @@ class Frontend {          const searchText = textSource.text();          if (searchText.length === 0) { -            return; +            return false;          }          const definitions = await apiKanjiFind(searchText, this.getOptionsContext()); @@ -392,8 +375,8 @@ class Frontend {          return true;      } -    searchClear() { -        this.popup.hide(); +    searchClear(changeFocus) { +        this.popup.hide(changeFocus);          this.popup.clearAutoPlayTimer();          if (this.options.scanning.selectText && this.textSourceLast) { @@ -529,4 +512,25 @@ class Frontend {      }  } +Frontend.windowMessageHandlers = { +    popupClose: (self) => { +        self.searchClear(true); +    }, + +    selectionCopy: () => { +        document.execCommand('copy'); +    } +}; + +Frontend.runtimeMessageHandlers = { +    optionsUpdate: (self) => { +        self.updateOptions(); +    }, + +    popupSetVisible: (self, {visible}) => { +        self.popup.setVisible(visible); +    } +}; + +  window.yomichan_frontend = Frontend.create(); diff --git a/ext/fg/js/popup-proxy-host.js b/ext/fg/js/popup-proxy-host.js index 396f7556..cb9741be 100644 --- a/ext/fg/js/popup-proxy-host.js +++ b/ext/fg/js/popup-proxy-host.js @@ -40,7 +40,7 @@ class PopupProxyHost {              createNestedPopup: ({parentId}) => this.createNestedPopup(parentId),              show: ({id, elementRect, options}) => this.show(id, elementRect, options),              showOrphaned: ({id, elementRect, options}) => this.show(id, elementRect, options), -            hide: ({id}) => this.hide(id), +            hide: ({id, changeFocus}) => this.hide(id, changeFocus),              setVisible: ({id, visible}) => this.setVisible(id, visible),              containsPoint: ({id, x, y}) => this.containsPoint(id, x, y),              termsShow: ({id, elementRect, writingMode, definitions, options, context}) => this.termsShow(id, elementRect, writingMode, definitions, options, context), @@ -98,9 +98,9 @@ class PopupProxyHost {          return await popup.showOrphaned(elementRect, options);      } -    async hide(id) { +    async hide(id, changeFocus) {          const popup = this.getPopup(id); -        return popup.hide(); +        return popup.hide(changeFocus);      }      async setVisible(id, visible) { diff --git a/ext/fg/js/popup-proxy.js b/ext/fg/js/popup-proxy.js index 235e1730..072cebc9 100644 --- a/ext/fg/js/popup-proxy.js +++ b/ext/fg/js/popup-proxy.js @@ -58,11 +58,11 @@ class PopupProxy {          return await this.invokeHostApi('showOrphaned', {id, elementRect, options});      } -    async hide() { +    async hide(changeFocus) {          if (this.id === null) {              return;          } -        return await this.invokeHostApi('hide', {id: this.id}); +        return await this.invokeHostApi('hide', {id: this.id, changeFocus});      }      async setVisible(visible) { diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js index 08965084..9dff6f28 100644 --- a/ext/fg/js/popup.js +++ b/ext/fg/js/popup.js @@ -56,17 +56,19 @@ class Popup {          return new Promise((resolve) => {              const parentFrameId = (typeof this.frameId === 'number' ? this.frameId : null);              this.container.addEventListener('load', () => { -                this.invokeApi('popupNestedInitialize', { -                    id: this.id, -                    depth: this.depth, -                    parentFrameId, +                this.invokeApi('initialize', { +                    options: { +                        general: { +                            customPopupCss: options.general.customPopupCss +                        } +                    }, +                    popupInfo: { +                        id: this.id, +                        depth: this.depth, +                        parentFrameId +                    },                      url: this.url                  }); -                this.invokeApi('setOptions', { -                    general: { -                        customPopupCss: options.general.customPopupCss -                    } -                });                  resolve();              });              this.observeFullscreen(); @@ -105,7 +107,7 @@ class Popup {          container.style.height = `${height}px`;          container.style.visibility = 'visible'; -        this.hideChildren(); +        this.hideChildren(true);      }      static getPositionForHorizontalText(elementRect, width, height, maxWidth, maxHeight, optionsGeneral) { @@ -206,16 +208,21 @@ class Popup {          this.invokeApi('orphaned');      } -    hide() { -        this.hideChildren(); +    hide(changeFocus) { +        if (this.isContainerHidden()) { +            changeFocus = false; +        } +        this.hideChildren(changeFocus);          this.hideContainer(); -        this.focusParent(); +        if (changeFocus) { +            this.focusParent(); +        }      } -    hideChildren() { -        // recursively hides all children -        if (this.child && !this.child.isContainerHidden()) { -            this.child.hide(); +    hideChildren(changeFocus) { +        // Recursively hides all children. +        if (this.child !== null && !this.child.isContainerHidden()) { +            this.child.hide(changeFocus);          }      } diff --git a/ext/fg/js/source.js b/ext/fg/js/source.js index 4642de50..ee4f58e2 100644 --- a/ext/fg/js/source.js +++ b/ext/fg/js/source.js @@ -83,120 +83,142 @@ class TextSourceRange {      }      static shouldEnter(node) { -        if (node.nodeType !== 1) { -            return false; -        } - -        const skip = ['RT', 'SCRIPT', 'STYLE']; -        if (skip.includes(node.nodeName.toUpperCase())) { -            return false; +        switch (node.nodeName.toUpperCase()) { +            case 'RT': +            case 'SCRIPT': +            case 'STYLE': +                return false;          }          const style = window.getComputedStyle(node); -        const hidden = +        return !(              style.visibility === 'hidden' ||              style.display === 'none' || -            parseFloat(style.fontSize) === 0; +            parseFloat(style.fontSize) === 0); +    } -        return !hidden; +    static getRubyElement(node) { +        node = TextSourceRange.getParentElement(node); +        if (node !== null && node.nodeName.toUpperCase() === "RT") { +            node = node.parentNode; +            return (node !== null && node.nodeName.toUpperCase() === "RUBY") ? node : null; +        } +        return null;      }      static seekForward(node, offset, length) {          const state = {node, offset, remainder: length, content: ''}; -        if (!TextSourceRange.seekForwardHelper(node, state)) { -            return state; +        const TEXT_NODE = Node.TEXT_NODE; +        const ELEMENT_NODE = Node.ELEMENT_NODE; +        let resetOffset = false; + +        const ruby = TextSourceRange.getRubyElement(node); +        if (ruby !== null) { +            node = ruby; +            resetOffset = true;          } -        for (let current = node; current !== null; current = current.parentElement) { -            for (let sibling = current.nextSibling; sibling !== null; sibling = sibling.nextSibling) { -                if (!TextSourceRange.seekForwardHelper(sibling, state)) { -                    return state; +        while (node !== null) { +            let visitChildren = true; +            const nodeType = node.nodeType; + +            if (nodeType === TEXT_NODE) { +                state.node = node; +                if (TextSourceRange.seekForwardTextNode(state, resetOffset)) { +                    break;                  } +                resetOffset = true; +            } else if (nodeType === ELEMENT_NODE) { +                visitChildren = TextSourceRange.shouldEnter(node);              } + +            node = TextSourceRange.getNextNode(node, visitChildren);          }          return state;      } -    static seekForwardHelper(node, state) { -        if (node.nodeType === 3 && node.parentElement && TextSourceRange.shouldEnter(node.parentElement)) { -            const offset = state.node === node ? state.offset : 0; - -            let consumed = 0; -            let stripped = 0; -            while (state.remainder - consumed > 0) { -                const currentChar = node.nodeValue[offset + consumed + stripped]; -                if (!currentChar) { -                    break; -                } else if (currentChar.match(IGNORE_TEXT_PATTERN)) { -                    stripped++; -                } else { -                    consumed++; -                    state.content += currentChar; -                } -            } - -            state.node = node; -            state.offset = offset + consumed + stripped; -            state.remainder -= consumed; -        } else if (TextSourceRange.shouldEnter(node)) { -            for (let i = 0; i < node.childNodes.length; ++i) { -                if (!TextSourceRange.seekForwardHelper(node.childNodes[i], state)) { +    static seekForwardTextNode(state, resetOffset) { +        const nodeValue = state.node.nodeValue; +        const nodeValueLength = nodeValue.length; +        let content = state.content; +        let offset = resetOffset ? 0 : state.offset; +        let remainder = state.remainder; +        let result = false; + +        for (; offset < nodeValueLength; ++offset) { +            const c = nodeValue[offset]; +            if (!IGNORE_TEXT_PATTERN.test(c)) { +                content += c; +                if (--remainder <= 0) { +                    result = true; +                    ++offset;                      break;                  }              }          } -        return state.remainder > 0; +        state.offset = offset; +        state.content = content; +        state.remainder = remainder; +        return result;      }      static seekBackward(node, offset, length) {          const state = {node, offset, remainder: length, content: ''}; -        if (!TextSourceRange.seekBackwardHelper(node, state)) { -            return state; +        const TEXT_NODE = Node.TEXT_NODE; +        const ELEMENT_NODE = Node.ELEMENT_NODE; +        let resetOffset = false; + +        const ruby = TextSourceRange.getRubyElement(node); +        if (ruby !== null) { +            node = ruby; +            resetOffset = true;          } -        for (let current = node; current !== null; current = current.parentElement) { -            for (let sibling = current.previousSibling; sibling !== null; sibling = sibling.previousSibling) { -                if (!TextSourceRange.seekBackwardHelper(sibling, state)) { -                    return state; +        while (node !== null) { +            let visitChildren = true; +            const nodeType = node.nodeType; + +            if (nodeType === TEXT_NODE) { +                state.node = node; +                if (TextSourceRange.seekBackwardTextNode(state, resetOffset)) { +                    break;                  } +                resetOffset = true; +            } else if (nodeType === ELEMENT_NODE) { +                visitChildren = TextSourceRange.shouldEnter(node);              } + +            node = TextSourceRange.getPreviousNode(node, visitChildren);          }          return state;      } -    static seekBackwardHelper(node, state) { -        if (node.nodeType === 3 && node.parentElement && TextSourceRange.shouldEnter(node.parentElement)) { -            const offset = state.node === node ? state.offset : node.length; - -            let consumed = 0; -            let stripped = 0; -            while (state.remainder - consumed > 0) { -                const currentChar = node.nodeValue[offset - 1 - consumed - stripped]; // negative indices are undefined in JS -                if (!currentChar) { -                    break; -                } else if (currentChar.match(IGNORE_TEXT_PATTERN)) { -                    stripped++; -                } else { -                    consumed++; -                    state.content = currentChar + state.content; -                } -            } - -            state.node = node; -            state.offset = offset - consumed - stripped; -            state.remainder -= consumed; -        } else if (TextSourceRange.shouldEnter(node)) { -            for (let i = node.childNodes.length - 1; i >= 0; --i) { -                if (!TextSourceRange.seekBackwardHelper(node.childNodes[i], state)) { +    static seekBackwardTextNode(state, resetOffset) { +        const nodeValue = state.node.nodeValue; +        let content = state.content; +        let offset = resetOffset ? nodeValue.length : state.offset; +        let remainder = state.remainder; +        let result = false; + +        for (; offset > 0; --offset) { +            const c = nodeValue[offset - 1]; +            if (!IGNORE_TEXT_PATTERN.test(c)) { +                content = c + content; +                if (--remainder <= 0) { +                    result = true; +                    --offset;                      break;                  }              }          } -        return state.remainder > 0; +        state.offset = offset; +        state.content = content; +        state.remainder = remainder; +        return result;      }      static getParentElement(node) { @@ -219,22 +241,38 @@ class TextSourceRange {      static getNodesInRange(range) {          const end = range.endContainer;          const nodes = []; -        for (let node = range.startContainer; node !== null; node = TextSourceRange.getNextNode(node)) { +        for (let node = range.startContainer; node !== null; node = TextSourceRange.getNextNode(node, true)) {              nodes.push(node);              if (node === end) { break; }          }          return nodes;      } -    static getNextNode(node) { -        let next = node.firstChild; +    static getNextNode(node, visitChildren) { +        let next = visitChildren ? node.firstChild : null;          if (next === null) {              while (true) {                  next = node.nextSibling;                  if (next !== null) { break; }                  next = node.parentNode; -                if (node === null) { break; } +                if (next === null) { break; } + +                node = next; +            } +        } +        return next; +    } + +    static getPreviousNode(node, visitChildren) { +        let next = visitChildren ? node.lastChild : null; +        if (next === null) { +            while (true) { +                next = node.previousSibling; +                if (next !== null) { break; } + +                next = node.parentNode; +                if (next === null) { break; }                  node = next;              } |