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'; +} |