diff options
Diffstat (limited to 'ext/fg/js')
| -rw-r--r-- | ext/fg/js/driver.js | 75 | ||||
| -rw-r--r-- | ext/fg/js/frame.js | 96 | ||||
| -rw-r--r-- | ext/fg/js/popup.js | 14 | ||||
| -rw-r--r-- | ext/fg/js/util.js | 94 | 
4 files changed, 197 insertions, 82 deletions
| diff --git a/ext/fg/js/driver.js b/ext/fg/js/driver.js index 2e818acf..9d972abf 100644 --- a/ext/fg/js/driver.js +++ b/ext/fg/js/driver.js @@ -24,21 +24,16 @@ class Driver {          this.lastMousePos = null;          this.lastTextSource = null;          this.pendingLookup = false; -        this.enabled = false;          this.options = null; -        chrome.runtime.onMessage.addListener(this.onBgMessage.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('resize', e => this.searchClear()); -          getOptions().then(options => {              this.options = options; -            return isEnabled(); -        }).then(enabled => { -            this.enabled = enabled; -        }); +            window.addEventListener('mouseover', this.onMouseOver.bind(this)); +            window.addEventListener('mousedown', this.onMouseDown.bind(this)); +            window.addEventListener('mousemove', this.onMouseMove.bind(this)); +            window.addEventListener('resize', e => this.searchClear()); +            chrome.runtime.onMessage.addListener(this.onBgMessage.bind(this)); +        }).catch(this.handleError.bind(this));      }      popupTimerSet(callback) { @@ -63,7 +58,7 @@ class Driver {          this.lastMousePos = {x: e.clientX, y: e.clientY};          this.popupTimerClear(); -        if (!this.enabled) { +        if (!this.options.general.enable) {              return;          } @@ -75,7 +70,7 @@ class Driver {              return;          } -        const searcher = () => this.searchAt(this.lastMousePos, false); +        const searcher = () => this.searchAt(this.lastMousePos);          if (!this.popup.isVisible() || e.shiftKey || e.which === 2 /* mmb */) {              searcher();          } else { @@ -98,17 +93,13 @@ class Driver {          callback();      } -    searchAt(point, hideNotFound) { +    searchAt(point) {          if (this.pendingLookup) {              return;          } -        const textSource = textSourceFromPoint(point); +        const textSource = textSourceFromPoint(point, this.options.scanning.imposter);          if (textSource === null || !textSource.containsPoint(point)) { -            if (hideNotFound) { -                this.searchClear(); -            } -              return;          } @@ -119,14 +110,10 @@ class Driver {          this.pendingLookup = true;          this.searchTerms(textSource).then(found => {              if (!found) { -                this.searchKanji(textSource).then(found => { -                    if (!found && hideNotFound) { -                        this.searchClear(); -                    } -                }); +                return this.searchKanji(textSource);              }          }).catch(error => { -            window.alert('Error: ' + error); +            this.handleError(error, textSource);          }).then(() => {              this.pendingLookup = false;          }); @@ -143,13 +130,11 @@ class Driver {                  textSource.setEndOffset(length);                  const sentence = extractSentence(textSource, this.options.anki.sentenceExt); -                definitions.forEach(definition => { -                    definition.url = window.location.href; -                    definition.sentence = sentence; -                }); +                const url = window.location.href;                  this.popup.showNextTo(textSource.getRect()); -                this.popup.showTermDefs(definitions, this.options); +                this.popup.showTermDefs(definitions, this.options, {sentence, url}); +                  this.lastTextSource = textSource;                  if (this.options.scanning.selectText) {                      textSource.select(); @@ -157,9 +142,6 @@ class Driver {                  return true;              } -        }).catch(error => { -            window.alert('Error: ' + error); -            return false;          });      } @@ -170,10 +152,12 @@ class Driver {              if (definitions.length === 0) {                  return false;              } else { -                definitions.forEach(definition => definition.url = window.location.href); +                const sentence = extractSentence(textSource, this.options.anki.sentenceExt); +                const url = window.location.href;                  this.popup.showNextTo(textSource.getRect()); -                this.popup.showKanjiDefs(definitions, this.options); +                this.popup.showKanjiDefs(definitions, this.options, {sentence, url}); +                  this.lastTextSource = textSource;                  if (this.options.scanning.selectText) {                      textSource.select(); @@ -181,13 +165,11 @@ class Driver {                  return true;              } -        }).catch(error => { -            window.alert('Error: ' + error); -            return false;          });      }      searchClear() { +        destroyImposters();          this.popup.hide();          if (this.options.scanning.selectText && this.lastTextSource !== null) { @@ -197,14 +179,19 @@ class Driver {          this.lastTextSource = null;      } -    api_setOptions(options) { -        this.options = options; +    handleError(error, textSource) { +        if (window.orphaned) { +            if (textSource) { +                this.popup.showNextTo(textSource.getRect()); +                this.popup.showOrphaned(); +            } +        } else { +            showError(error); +        }      } -    api_setEnabled(enabled) { -        if (!(this.enabled = enabled)) { -            this.searchClear(); -        } +    api_setOptions(options) { +        this.options = options;      }  } diff --git a/ext/fg/js/frame.js b/ext/fg/js/frame.js index 36356f02..1028f0f6 100644 --- a/ext/fg/js/frame.js +++ b/ext/fg/js/frame.js @@ -30,26 +30,32 @@ class Frame {          });      } -    api_showTermDefs({definitions, options}) { +    api_showTermDefs({definitions, options, context}) {          const sequence = ++this.sequence; -        const context = { +        const params = {              definitions,              grouped: options.general.groupResults,              addable: options.ankiMethod !== 'disabled',              playback: options.general.audioPlayback          }; +        definitions.forEach(definition => { +            definition.sentence = context.sentence; +            definition.url = context.url; +        }); +          this.definitions = definitions;          this.showSpinner(false);          window.scrollTo(0, 0); -        renderText(context, 'terms.html').then(content => { -            $('.content').html(content); +        renderText(params, 'terms.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})); +                const character = $(e.target).text(); +                findKanji(character).then(definitions => this.api_showKanjiDefs({definitions, options, context}));              });              $('.action-play-audio').click(e => { @@ -59,28 +65,42 @@ class Frame {              });              this.updateAddNoteButtons(['term_kanji', 'term_kana'], sequence); +        }).catch(error => { +            this.handleError(error);          });      } -    api_showKanjiDefs({definitions, options}) { +    api_showKanjiDefs({definitions, options, context}) {          const sequence = ++this.sequence; -        const context = { +        const params = {              definitions,              addable: options.ankiMethod !== 'disabled'          }; +        definitions.forEach(definition => { +            definition.sentence = context.sentence; +            definition.url = context.url; +        }); +          this.definitions = definitions;          this.showSpinner(false);          window.scrollTo(0, 0); -        renderText(context, 'kanji.html').then(content => { -            $('.content').html(content); +        renderText(params, 'kanji.html').then(content => { +            $('#content').html(content);              $('.action-add-note').click(this.onAddNote.bind(this));              this.updateAddNoteButtons(['kanji'], sequence); +        }).catch(error => { +            this.handleError(error);          });      } +    api_showOrphaned() { +        $('#content').hide(); +        $('#orphan').show(); +    } +      findAddNoteButton(index, mode) {          return $(`.action-add-note[data-index="${index}"][data-mode="${mode}"]`);      } @@ -93,15 +113,24 @@ class Frame {          const index = link.data('index');          const mode = link.data('mode'); -        addDefinition(this.definitions[index], mode).then(success => { +        const definition = this.definitions[index]; +        if (mode !== 'kanji') { +            const url = buildAudioUrl(definition); +            const filename = buildAudioFilename(definition); +            if (url && filename) { +                definition.audio = {url, filename}; +            } +        } + +        addDefinition(definition, mode).then(success => {              if (success) {                  const button = this.findAddNoteButton(index, mode);                  button.addClass('disabled');              } else { -                window.alert('Note could not be added'); +                showError('note could not be added');              }          }).catch(error => { -            window.alert('Error: ' + error); +            this.handleError(error);          }).then(() => {              this.showSpinner(false);          }); @@ -118,7 +147,7 @@ class Frame {              }              states.forEach((state, index) => { -                for (let mode in state) { +                for (const mode in state) {                      const button = this.findAddNoteButton(index, mode);                      if (state[mode]) {                          button.removeClass('disabled'); @@ -129,6 +158,8 @@ class Frame {                      button.removeClass('pending');                  }              }); +        }).catch(error => { +            this.handleError(error);          });      } @@ -142,18 +173,41 @@ class Frame {      }      playAudio(definition) { -        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.audioCache) { +            const audio = this.audioCache[key]; +            if (audio !== null) { +                audio.pause(); +            }          } -        for (let key in this.audioCache) { -            this.audioCache[key].pause(); +        const url = buildAudioUrl(definition); +        if (!url) { +            return; +        } + +        let audio = this.audioCache[url]; +        if (audio) { +            audio.currentTime = 0; +            audio.play(); +        } else { +            audio = new Audio(url); +            audio.onloadeddata = () => { +                if (audio.duration === 5.694694) { +                    audio = new Audio('mp3/button.mp3'); +                } + +                this.audioCache[url] = audio; +                audio.play(); +            };          } +    } -        const audio = this.audioCache[url] || new Audio(url); -        audio.currentTime = 0; -        audio.play(); +    handleError(error) { +        if (window.orphaned) { +            this.api_showOrphaned(); +        } else { +            showError(error); +        }      }  } diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js index d47ab4ae..751c6acc 100644 --- a/ext/fg/js/popup.js +++ b/ext/fg/js/popup.js @@ -68,15 +68,19 @@ class Popup {          return this.container.style.visibility !== 'hidden';      } -    showTermDefs(definitions, options) { -        this.invokeApi('showTermDefs', {definitions, options}); +    showTermDefs(definitions, options, context) { +        this.invokeApi('showTermDefs', {definitions, options, context});      } -    showKanjiDefs(definitions, options) { -        this.invokeApi('showKanjiDefs', {definitions, options}); +    showKanjiDefs(definitions, options, context) { +        this.invokeApi('showKanjiDefs', {definitions, options, context});      } -    invokeApi(action, params) { +    showOrphaned() { +        this.invokeApi('showOrphaned'); +    } + +    invokeApi(action, params={}) {          this.container.contentWindow.postMessage({action, params}, '*');      }  } diff --git a/ext/fg/js/util.js b/ext/fg/js/util.js index ef45d08c..99da6381 100644 --- a/ext/fg/js/util.js +++ b/ext/fg/js/util.js @@ -19,18 +19,23 @@  function invokeBgApi(action, params) {      return new Promise((resolve, reject) => { -        chrome.runtime.sendMessage({action, params}, ({result, error}) => { -            if (error) { -                reject(error); -            } else { -                resolve(result); -            } -        }); +        try { +            chrome.runtime.sendMessage({action, params}, ({result, error}) => { +                if (error) { +                    reject(error); +                } else { +                    resolve(result); +                } +            }); +        } catch (e) { +            window.orphaned = true; +            reject(e.message); +        }      });  } -function isEnabled() { -    return invokeBgApi('getEnabled', {}); +function showError(error) { +    window.alert(`Error: ${error}`);  }  function getOptions() { @@ -61,12 +66,36 @@ function addDefinition(definition, mode) {      return invokeBgApi('addDefinition', {definition, mode});  } -function textSourceFromPoint(point) { +function createImposter(element) { +    const imposter = document.createElement('div'); +    const elementRect = element.getBoundingClientRect(); + +    imposter.className = 'yomichan-imposter'; +    imposter.innerText = element.value; +    imposter.style.cssText = window.getComputedStyle(element).cssText; +    imposter.style.position = 'absolute'; +    imposter.style.top = elementRect.top + 'px'; +    imposter.style.left = elementRect.left + 'px'; +    imposter.style.zIndex = 2147483646; +    document.body.appendChild(imposter); + +    imposter.scrollTop = element.scrollTop; +    imposter.scrollLeft = element.scrollLeft; +} + +function destroyImposters() { +    for (const element of document.getElementsByClassName('yomichan-imposter')) { +        element.parentNode.removeChild(element); +    } +} + +function textSourceFromPoint(point, imposter) {      const element = document.elementFromPoint(point.x, point.y);      if (element !== null) { -        const names = ['IMG', 'INPUT', 'BUTTON', 'TEXTAREA']; -        if (names.includes(element.nodeName)) { +        if (element.nodeName === 'IMG' || element.nodeName === 'BUTTON') {              return new TextSourceElement(element); +        } else if (imposter && (element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA')) { +            createImposter(element);          }      } @@ -75,6 +104,7 @@ function textSourceFromPoint(point) {          return new TextSourceRange(range);      } +    destroyImposters();      return null;  } @@ -132,3 +162,43 @@ function extractSentence(source, extent) {      return content.substring(startPos, endPos).trim();  } + +function buildAudioUrl(definition) { +    let kana = definition.reading; +    let kanji = definition.expression; + +    if (!kana && !kanji) { +        return null; +    } + +    if (!kana && wanakana.isHiragana(kanji)) { +        kana = kanji; +        kanji = null; +    } + +    const params = []; +    if (kanji) { +        params.push(`kanji=${encodeURIComponent(kanji)}`); +    } +    if (kana) { +        params.push(`kana=${encodeURIComponent(kana)}`); +    } + +    return `https://assets.languagepod101.com/dictionary/japanese/audiomp3.php?${params.join('&')}`; +} + +function buildAudioFilename(definition) { +    if (!definition.reading && !definition.expression) { +        return null; +    } + +    let filename = 'yomichan'; +    if (definition.reading) { +        filename += `_${definition.reading}`; +    } +    if (definition.expression) { +        filename += `_${definition.expression}`; +    } + +    return filename += '.mp3'; +} |