diff options
Diffstat (limited to 'ext/fg/js')
| -rw-r--r-- | ext/fg/js/driver.js | 228 | ||||
| -rw-r--r-- | ext/fg/js/frame.js | 168 | ||||
| -rw-r--r-- | ext/fg/js/popup.js | 63 | ||||
| -rw-r--r-- | ext/fg/js/util.js | 22 | 
4 files changed, 216 insertions, 265 deletions
| diff --git a/ext/fg/js/driver.js b/ext/fg/js/driver.js index 9aab7950..ef7db481 100644 --- a/ext/fg/js/driver.js +++ b/ext/fg/js/driver.js @@ -21,27 +21,22 @@ class Driver {      constructor() {          this.popup = new Popup();          this.popupTimer = null; -        this.audio = {};          this.lastMousePos = null;          this.lastTextSource = null;          this.pendingLookup = false;          this.enabled = false;          this.options = null; -        this.definitions = null; -        this.sequence = 0; -        this.fgRoot = chrome.extension.getURL('fg');          chrome.runtime.onMessage.addListener(this.onBgMessage.bind(this)); -        window.addEventListener('message', this.onFrameMessage.bind(this));          window.addEventListener('mouseover', this.onMouseOver.bind(this));          window.addEventListener('mousedown', this.onMouseDown.bind(this));          window.addEventListener('mousemove', this.onMouseMove.bind(this));          window.addEventListener('keydown', this.onKeyDown.bind(this)); -        window.addEventListener('resize', e => this.hidePopup()); +        window.addEventListener('resize', e => this.searchClear());          getOptions().then(opts => {              this.options = opts; -            return getEnabled(); +            return isEnabled();          }).then(enabled => {              this.enabled = enabled;          }); @@ -65,7 +60,7 @@ class Driver {          if (this.enabled && this.lastMousePos !== null && e.keyCode === 16 /* shift */) {              this.searchAt(this.lastMousePos, true);          } else if (e.keyCode === 27 /* esc */) { -            this.hidePopup(); +            this.searchClear();          }      } @@ -92,7 +87,7 @@ class Driver {          }          const searcher = () => this.searchAt(this.lastMousePos, false); -        if (!this.popup.visible() || e.shiftKey || e.which === 2 /* mmb */) { +        if (!this.popup.isVisible() || e.shiftKey || e.which === 2 /* mmb */) {              searcher();          } else {              this.popupTimerSet(searcher); @@ -102,7 +97,7 @@ class Driver {      onMouseDown(e) {          this.lastMousePos = {x: e.clientX, y: e.clientY};          this.popupTimerClear(); -        this.hidePopup(); +        this.searchClear();      }      onBgMessage({action, params}, sender, callback) { @@ -114,20 +109,46 @@ class Driver {          callback();      } -    onFrameMessage(e) { -        const {action, params} = e.data, method = this['api_' + action]; -        if (typeof(method) === 'function') { -            method.call(this, params); +    searchAt(point, hideNotFound) { +        if (this.pendingLookup) { +            return; +        } + +        const textSource = textSourceFromPoint(point); +        if (textSource === null || !textSource.containsPoint(point)) { +            if (hideNotFound) { +                this.searchClear(); +            } + +            return; +        } + +        if (this.lastTextSource !== null && this.lastTextSource.equals(textSource)) { +            return;          } + +        this.pendingLookup = true; +        this.searchTerms(textSource).then(found => { +            if (!found) { +                this.searchKanji(textSource).then(found => { +                    if (!found && hideNotFound) { +                        this.searchClear(); +                    } +                }); +            } +        }).catch(error => { +            window.alert('Error: ' + error); +        }).then(() => { +            this.pendingLookup = false; +        });      }      searchTerms(textSource) {          textSource.setEndOffset(this.options.scanLength); -        this.pendingLookup = true; -        return findTerm(textSource.text()).then(({definitions, length}) => { +        const findFunc = this.options.groupTermResults ? findTermGrouped : findTerm; +        return findFunc(textSource.text()).then(({definitions, length}) => {              if (definitions.length === 0) { -                this.pendingLookup = false;                  return false;              } else {                  textSource.setEndOffset(length); @@ -138,119 +159,46 @@ class Driver {                      definition.sentence = sentence;                  }); -                const sequence = ++this.sequence; -                const context = { -                    definitions, -                    sequence, -                    addable: this.options.ankiMethod !== 'disabled', -                    root: this.fgRoot, -                    options: this.options -                }; - -                return renderText(context, 'term-list.html').then(content => { -                    this.definitions = definitions; -                    this.pendingLookup = false; -                    this.showPopup(textSource, content); -                    return canAddDefinitions(definitions, ['term_kanji', 'term_kana']); -                }).then(states => { -                    if (states !== null) { -                        states.forEach((state, index) => this.popup.invokeApi( -                            'setActionState', -                            {index, state, sequence} -                        )); -                    } +                this.popup.showNextTo(textSource.getRect()); +                this.popup.showTermDefs(definitions, this.options); +                this.lastTextSource = textSource; +                if (this.options.selectMatchedText) { +                    textSource.select(); +                } -                    return true; -                }); +                return true;              }          }).catch(error => { -            alert('Error: ' + error); +            window.alert('Error: ' + error); +            return false;          });      }      searchKanji(textSource) {          textSource.setEndOffset(1); -        this.pendingLookup = true;          return findKanji(textSource.text()).then(definitions => {              if (definitions.length === 0) { -                this.pendingLookup = false;                  return false;              } else {                  definitions.forEach(definition => definition.url = window.location.href); -                const sequence = ++this.sequence; -                const context = { -                    definitions, -                    sequence, -                    addable: this.options.ankiMethod !== 'disabled', -                    root: this.fgRoot, -                    options: this.options -                }; - -                return renderText(context, 'kanji-list.html').then(content => { -                    this.definitions = definitions; -                    this.pendingLookup = false; -                    this.showPopup(textSource, content); -                    return canAddDefinitions(definitions, ['kanji']); -                }).then(states => { -                    if (states !== null) { -                        states.forEach((state, index) => this.popup.invokeApi( -                            'setActionState', -                            {index, state, sequence} -                        )); -                    } - -                    return true; -                }); -            } -        }).catch(error => { -            alert('Error: ' + error); -        }); -    } - -    searchAt(point, hideNotFound) { -        if (this.pendingLookup) { -            return; -        } - -        const textSource = textSourceFromPoint(point); -        if (textSource === null || !textSource.containsPoint(point)) { -            if (hideNotFound) { -                this.hidePopup(); -            } - -            return; -        } - -        if (this.lastTextSource !== null && this.lastTextSource.equals(textSource)) { -            return true; -        } +                this.popup.showNextTo(textSource.getRect()); +                this.popup.showKanjiDefs(definitions, this.options); +                this.lastTextSource = textSource; +                if (this.options.selectMatchedText) { +                    textSource.select(); +                } -        this.searchTerms(textSource).then(found => { -            if (!found) { -                this.searchKanji(textSource).then(found => { -                    if (!found && hideNotFound) { -                        this.hidePopup(); -                    } -                }); +                return true;              }          }).catch(error => { -            alert('Error: ' + error); +            window.alert('Error: ' + error); +            return false;          });      } -    showPopup(textSource, content) { -        this.popup.showNextTo(textSource.getRect(), content); - -        if (this.options.selectMatchedText) { -            textSource.select(); -        } - -        this.lastTextSource = textSource; -    } - -    hidePopup() { +    searchClear() {          this.popup.hide();          if (this.options.selectMatchedText && this.lastTextSource !== null) { @@ -258,7 +206,6 @@ class Driver {          }          this.lastTextSource = null; -        this.definitions = null;      }      api_setOptions(opts) { @@ -267,68 +214,9 @@ class Driver {      api_setEnabled(enabled) {          if (!(this.enabled = enabled)) { -            this.hidePopup(); +            this.searchClear();          }      } - -    api_addNote({index, mode}) { -        const state = {[mode]: false}; -        addDefinition(this.definitions[index], mode).then(success => { -            if (success) { -                this.popup.invokeApi('setActionState', {index, state, sequence: this.sequence}); -            } else { -                alert('Note could not be added'); -            } -        }).catch(error => { -            alert('Error: ' + error); -        }); -    } - -    api_playAudio(index) { -        const definition = this.definitions[index]; - -        let url = `https://assets.languagepod101.com/dictionary/japanese/audiomp3.php?kanji=${encodeURIComponent(definition.expression)}`; -        if (definition.reading) { -            url += `&kana=${encodeURIComponent(definition.reading)}`; -        } - -        for (const key in this.audio) { -            this.audio[key].pause(); -        } - -        const audio = this.audio[url] || new Audio(url); -        audio.currentTime = 0; -        audio.play(); - -        this.audio[url] = audio; -    } - -    api_displayKanji(kanji) { -        findKanji(kanji).then(definitions => { -            definitions.forEach(definition => definition.url = window.location.href); - -            const sequence = ++this.sequence; -            const context = { -                definitions, -                sequence, -                addable: this.options.ankiMethod !== 'disabled', -                root: this.fgRoot, -                options: this.options -            }; - -            return renderText(context, 'kanji-list.html').then(content => { -                this.definitions = definitions; -                this.popup.setContent(content, definitions); -                return canAddDefinitions(definitions, ['kanji']); -            }).then(states => { -                if (states !== null) { -                    states.forEach((state, index) => this.popup.invokeApi('setActionState', {index, state, sequence})); -                } -            }); -        }).catch(error => { -            alert('Error: ' + error); -        }); -    }  }  window.driver = new Driver(); diff --git a/ext/fg/js/frame.js b/ext/fg/js/frame.js index 8a99a405..4295dbb3 100644 --- a/ext/fg/js/frame.js +++ b/ext/fg/js/frame.js @@ -16,65 +16,145 @@   * along with this program.  If not, see <http://www.gnu.org/licenses/>.   */ +class Frame { +    constructor() { +        this.definitions = []; +        this.audioCache = {}; +        this.sequence = 0; -function invokeApi(action, params, target) { -    target.postMessage({action, params}, '*'); -} +        $(window).on('message', e => { +            const {action, params} = e.originalEvent.data, method = this['api_' + action]; +            if (typeof(method) === 'function') { +                method.call(this, params); +            } +        }); +    } + +    api_showTermDefs({definitions, options}) { +        const sequence = ++this.sequence; +        const context = { +            definitions, +            grouped: options.groupTermResults, +            addable: options.ankiMethod !== 'disabled', +            playback: options.enableAudioPlayback +        }; + +        this.definitions = definitions; +        this.showSpinner(false); +        window.scrollTo(0, 0); + +        renderText(context, 'term-list.html').then(content => { +            $('.content').html(content); +            $('.action-add-note').click(this.onAddNote.bind(this)); + +            $('.kanji-link').click(e => { +                e.preventDefault(); +                findKanji($(e.target).text()).then(kdefs => this.api_showKanjiDefs({options, definitions: kdefs})); +            }); + +            $('.action-play-audio').click(e => { +                e.preventDefault(); +                const index = $(e.currentTarget).data('index'); +                this.playAudio(this.definitions[index]); +            }); -function registerKanjiLinks() { -    for (const link of Array.from(document.getElementsByClassName('kanji-link'))) { -        link.addEventListener('click', e => { -            e.preventDefault(); -            invokeApi('displayKanji', e.target.innerHTML, window.parent); +            this.updateAddNoteButtons(['term_kanji', 'term_kana'], sequence);          });      } -} -function registerAddNoteLinks() { -    for (const link of Array.from(document.getElementsByClassName('action-add-note'))) { -        link.addEventListener('click', e => { -            e.preventDefault(); -            const ds = e.currentTarget.dataset; -            invokeApi('addNote', {index: ds.index, mode: ds.mode}, window.parent); +    api_showKanjiDefs({definitions, options}) { +        const sequence = ++this.sequence; +        const context = { +            definitions, +            addable: options.ankiMethod !== 'disabled' +        }; + +        this.definitions = definitions; +        this.showSpinner(false); +        window.scrollTo(0, 0); + +        renderText(context, 'kanji-list.html').then(content => { +            $('.content').html(content); +            $('.action-add-note').click(this.onAddNote.bind(this)); + +            this.updateAddNoteButtons(['kanji'], sequence);          });      } -} -function registerAudioLinks() { -    for (const link of Array.from(document.getElementsByClassName('action-play-audio'))) { -        link.addEventListener('click', e => { -            e.preventDefault(); -            const ds = e.currentTarget.dataset; -            invokeApi('playAudio', ds.index, window.parent); +    findAddNoteButton(index, mode) { +        return $(`.action-add-note[data-index="${index}"][data-mode="${mode}"]`); +    } + +    onAddNote(e) { +        e.preventDefault(); +        this.showSpinner(true); + +        const link = $(e.currentTarget); +        const index = link.data('index'); +        const mode = link.data('mode'); + +        addDefinition(this.definitions[index], mode).then(success => { +            if (success) { +                const button = this.findAddNoteButton(index, mode); +                button.addClass('disabled'); +            } else { +                window.alert('Note could not be added'); +            } +        }).catch(error => { +            window.alert('Error: ' + error); +        }).then(() => { +            this.showSpinner(false);          });      } -} -function api_setActionState({index, state, sequence}) { -    for (const mode in state) { -        const matches = document.querySelectorAll(`.action-bar[data-sequence="${sequence}"] .action-add-note[data-index="${index}"][data-mode="${mode}"]`); -        if (matches.length === 0) { -            return; -        } +    updateAddNoteButtons(modes, sequence) { +        canAddDefinitions(this.definitions, modes).then(states => { +            if (states === null) { +                return; +            } -        const classes = matches[0].classList; -        if (state[mode]) { -            classes.remove('disabled'); +            if (sequence !== this.sequence) { +                return; +            } + +            states.forEach((state, index) => { +                for (const mode in state) { +                    const button = this.findAddNoteButton(index, mode); +                    if (state[mode]) { +                        button.removeClass('disabled'); +                    } else { +                        button.addClass('disabled'); +                    } + +                    button.removeClass('pending'); +                } +            }); +        }); +    } + +    showSpinner(show) { +        const spinner = $('.spinner'); +        if (show) { +            spinner.show();          } else { -            classes.add('disabled'); +            spinner.hide();          }      } -} -document.addEventListener('DOMContentLoaded', () => { -    registerKanjiLinks(); -    registerAddNoteLinks(); -    registerAudioLinks(); -}); +    playAudio(definition) { +        let url = `https://assets.languagepod101.com/dictionary/japanese/audiomp3.php?kanji=${encodeURIComponent(definition.expression)}`; +        if (definition.reading) { +            url += `&kana=${encodeURIComponent(definition.reading)}`; +        } -window.addEventListener('message', e => { -    const {action, params} = e.data, method = window['api_' + action]; -    if (typeof(method) === 'function') { -        method(params); +        for (const key in this.audioCache) { +            this.audioCache[key].pause(); +        } + +        const audio = this.audioCache[url] || new Audio(url); +        audio.currentTime = 0; +        audio.play();      } -}); +} + +window.frame = new Frame(); diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js index 8e71fefa..d47ab4ae 100644 --- a/ext/fg/js/popup.js +++ b/ext/fg/js/popup.js @@ -19,25 +19,26 @@  class Popup {      constructor() { -        this.container = null;          this.offset = 10; -    } -    show(rect, content) { -        this.inject(); +        this.container = document.createElement('iframe'); +        this.container.id = 'yomichan-popup'; +        this.container.addEventListener('mousedown', e => e.stopPropagation()); +        this.container.addEventListener('scroll', e => e.stopPropagation()); +        this.container.setAttribute('src', chrome.extension.getURL('fg/frame.html')); + +        document.body.appendChild(this.container); +    } +    showAt(rect) {          this.container.style.left = rect.x + 'px';          this.container.style.top = rect.y + 'px';          this.container.style.height = rect.height + 'px';          this.container.style.width = rect.width + 'px';          this.container.style.visibility = 'visible'; - -        this.setContent(content);      } -    showNextTo(elementRect, content) { -        this.inject(); - +    showNextTo(elementRect) {          const containerStyle = window.getComputedStyle(this.container);          const containerHeight = parseInt(containerStyle.height);          const containerWidth = parseInt(containerStyle.width); @@ -56,48 +57,26 @@ class Popup {              y = elementRect.top - height - this.offset;          } -        this.show({x, y, width, height}, content); -    } - -    visible() { -        return this.container !== null && this.container.style.visibility !== 'hidden'; +        this.showAt({x, y, width, height});      }      hide() { -        if (this.container !== null) { -            this.container.style.visibility = 'hidden'; -        } +        this.container.style.visibility = 'hidden';      } -    setContent(content) { -        if (this.container === null) { -            return; -        } - -        this.container.contentWindow.scrollTo(0, 0); - -        const doc = this.container.contentDocument; -        doc.open(); -        doc.write(content); -        doc.close(); +    isVisible() { +        return this.container.style.visibility !== 'hidden';      } -    invokeApi(action, params) { -        if (this.container !== null) { -            this.container.contentWindow.postMessage({action, params}, '*'); -        } +    showTermDefs(definitions, options) { +        this.invokeApi('showTermDefs', {definitions, options});      } -    inject() { -        if (this.container !== null) { -            return; -        } - -        this.container = document.createElement('iframe'); -        this.container.id = 'yomichan-popup'; -        this.container.addEventListener('mousedown', e => e.stopPropagation()); -        this.container.addEventListener('scroll', e => e.stopPropagation()); +    showKanjiDefs(definitions, options) { +        this.invokeApi('showKanjiDefs', {definitions, options}); +    } -        document.body.appendChild(this.container); +    invokeApi(action, params) { +        this.container.contentWindow.postMessage({action, params}, '*');      }  } diff --git a/ext/fg/js/util.js b/ext/fg/js/util.js index 96007b74..cedfb887 100644 --- a/ext/fg/js/util.js +++ b/ext/fg/js/util.js @@ -17,7 +17,7 @@   */ -function invokeApiBg(action, params) { +function invokeBgApi(action, params) {      return new Promise((resolve, reject) => {          chrome.runtime.sendMessage({action, params}, ({result, error}) => {              if (error) { @@ -29,32 +29,36 @@ function invokeApiBg(action, params) {      });  } -function getEnabled() { -    return invokeApiBg('getEnabled', {}); +function isEnabled() { +    return invokeBgApi('getEnabled', {});  }  function getOptions() { -    return invokeApiBg('getOptions', {}); +    return invokeBgApi('getOptions', {});  }  function findTerm(text) { -    return invokeApiBg('findTerm', {text}); +    return invokeBgApi('findTerm', {text}); +} + +function findTermGrouped(text) { +    return invokeBgApi('findTermGrouped', {text});  }  function findKanji(text) { -    return invokeApiBg('findKanji', {text}); +    return invokeBgApi('findKanji', {text});  }  function renderText(data, template) { -    return invokeApiBg('renderText', {data, template}); +    return invokeBgApi('renderText', {data, template});  }  function canAddDefinitions(definitions, modes) { -    return invokeApiBg('canAddDefinitions', {definitions, modes}).catch(() => null); +    return invokeBgApi('canAddDefinitions', {definitions, modes}).catch(() => null);  }  function addDefinition(definition, mode) { -    return invokeApiBg('addDefinition', {definition, mode}); +    return invokeBgApi('addDefinition', {definition, mode});  }  function textSourceFromPoint(point) { |