From c967b7a9f13ab7c3e0fe47e83fe5f05debf3aa73 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Thu, 3 Oct 2019 20:22:12 -0400 Subject: Remove event listeners when scanning is disabled --- ext/fg/js/frontend.js | 73 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 51 insertions(+), 22 deletions(-) diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js index 58dc0e4a..78f94cd9 100644 --- a/ext/fg/js/frontend.js +++ b/ext/fg/js/frontend.js @@ -38,6 +38,9 @@ class Frontend { this.mouseDownPrevent = false; this.clickPrevent = false; this.scrollPrevent = false; + + this.enabled = false; + this.eventListeners = []; } static create() { @@ -53,23 +56,7 @@ class Frontend { async prepare() { try { - this.options = await apiOptionsGet(this.getOptionsContext()); - - 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)); - window.addEventListener('mouseout', this.onMouseOut.bind(this)); - window.addEventListener('resize', this.onResize.bind(this)); - - if (this.options.scanning.touchInputEnabled) { - window.addEventListener('click', this.onClick.bind(this)); - window.addEventListener('touchstart', this.onTouchStart.bind(this)); - window.addEventListener('touchend', this.onTouchEnd.bind(this)); - window.addEventListener('touchcancel', this.onTouchCancel.bind(this)); - window.addEventListener('touchmove', this.onTouchMove.bind(this), {passive: false}); - window.addEventListener('contextmenu', this.onContextMenu.bind(this)); - } + await this.updateOptions(); chrome.runtime.onMessage.addListener(this.onRuntimeMessage.bind(this)); } catch (e) { @@ -88,7 +75,6 @@ class Frontend { if ( this.pendingLookup || - !this.options.general.enable || (e.buttons & 0x1) !== 0x0 // Left mouse button ) { return; @@ -245,13 +231,56 @@ class Frontend { console.log(error); } - async updateOptions() { - this.options = await apiOptionsGet(this.getOptionsContext()); - if (!this.options.enable) { + setEnabled(enabled) { + if (enabled) { + if (!this.enabled) { + this.hookEvents(); + this.enabled = true; + } + } else { + if (this.enabled) { + this.clearEventListeners(); + this.enabled = false; + } this.searchClear(false); } } + hookEvents() { + this.addEventListener(window, 'message', this.onWindowMessage.bind(this)); + this.addEventListener(window, 'mousedown', this.onMouseDown.bind(this)); + this.addEventListener(window, 'mousemove', this.onMouseMove.bind(this)); + this.addEventListener(window, 'mouseover', this.onMouseOver.bind(this)); + this.addEventListener(window, 'mouseout', this.onMouseOut.bind(this)); + this.addEventListener(window, 'resize', this.onResize.bind(this)); + + if (this.options.scanning.touchInputEnabled) { + this.addEventListener(window, 'click', this.onClick.bind(this)); + this.addEventListener(window, 'touchstart', this.onTouchStart.bind(this)); + this.addEventListener(window, 'touchend', this.onTouchEnd.bind(this)); + this.addEventListener(window, 'touchcancel', this.onTouchCancel.bind(this)); + this.addEventListener(window, 'touchmove', this.onTouchMove.bind(this), {passive: false}); + this.addEventListener(window, 'contextmenu', this.onContextMenu.bind(this)); + } + } + + addEventListener(node, type, listener, options) { + node.addEventListener(type, listener, options); + this.eventListeners.push([node, type, listener, options]); + } + + clearEventListeners() { + for (const [node, type, listener, options] of this.eventListeners) { + node.removeEventListener(type, listener, options); + } + this.eventListeners = []; + } + + async updateOptions() { + this.options = await apiOptionsGet(this.getOptionsContext()); + this.setEnabled(this.options.general.enable); + } + popupTimerSet(callback) { const delay = this.options.scanning.delay; if (delay > 0) { @@ -452,7 +481,7 @@ class Frontend { searchFromTouch(x, y, cause) { this.popupTimerClear(); - if (!this.options.general.enable || this.pendingLookup) { + if (this.pendingLookup) { return; } -- cgit v1.2.3 From 30263c3db84714a01e516c1f56225b423f5c8612 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 5 Oct 2019 17:19:27 -0400 Subject: Improve progressive/perfect deinflection rules --- ext/bg/lang/deinflect.json | 163 +++++++++++++++++++++++++-------------------- 1 file changed, 90 insertions(+), 73 deletions(-) diff --git a/ext/bg/lang/deinflect.json b/ext/bg/lang/deinflect.json index c7977c88..682093e1 100644 --- a/ext/bg/lang/deinflect.json +++ b/ext/bg/lang/deinflect.json @@ -1186,7 +1186,7 @@ "kanaIn": "て", "kanaOut": "る", "rulesIn": [ - "iru" + "iru" ], "rulesOut": [ "v1", @@ -1197,7 +1197,7 @@ "kanaIn": "いて", "kanaOut": "く", "rulesIn": [ - "iru" + "iru" ], "rulesOut": [ "v5" @@ -1207,7 +1207,7 @@ "kanaIn": "いで", "kanaOut": "ぐ", "rulesIn": [ - "iru" + "iru" ], "rulesOut": [ "v5" @@ -1217,7 +1217,7 @@ "kanaIn": "きて", "kanaOut": "くる", "rulesIn": [ - "iru" + "iru" ], "rulesOut": [ "vk" @@ -1227,7 +1227,7 @@ "kanaIn": "くて", "kanaOut": "い", "rulesIn": [ - "iru" + "iru" ], "rulesOut": [ "adj-i" @@ -1237,7 +1237,7 @@ "kanaIn": "して", "kanaOut": "す", "rulesIn": [ - "iru" + "iru" ], "rulesOut": [ "v5" @@ -1247,7 +1247,7 @@ "kanaIn": "して", "kanaOut": "する", "rulesIn": [ - "iru" + "iru" ], "rulesOut": [ "vs" @@ -1257,7 +1257,7 @@ "kanaIn": "って", "kanaOut": "う", "rulesIn": [ - "iru" + "iru" ], "rulesOut": [ "v5" @@ -1267,7 +1267,7 @@ "kanaIn": "って", "kanaOut": "つ", "rulesIn": [ - "iru" + "iru" ], "rulesOut": [ "v5" @@ -1277,7 +1277,7 @@ "kanaIn": "って", "kanaOut": "る", "rulesIn": [ - "iru" + "iru" ], "rulesOut": [ "v5" @@ -1287,7 +1287,7 @@ "kanaIn": "んで", "kanaOut": "ぬ", "rulesIn": [ - "iru" + "iru" ], "rulesOut": [ "v5" @@ -1297,7 +1297,7 @@ "kanaIn": "んで", "kanaOut": "ぶ", "rulesIn": [ - "iru" + "iru" ], "rulesOut": [ "v5" @@ -1307,7 +1307,7 @@ "kanaIn": "んで", "kanaOut": "む", "rulesIn": [ - "iru" + "iru" ], "rulesOut": [ "v5" @@ -1317,7 +1317,7 @@ "kanaIn": "のたもうて", "kanaOut": "のたまう", "rulesIn": [ - "iru" + "iru" ], "rulesOut": [ "v5" @@ -1327,7 +1327,7 @@ "kanaIn": "いって", "kanaOut": "いく", "rulesIn": [ - "iru" + "iru" ], "rulesOut": [ "v5" @@ -1337,7 +1337,7 @@ "kanaIn": "おうて", "kanaOut": "おう", "rulesIn": [ - "iru" + "iru" ], "rulesOut": [ "v5" @@ -1347,7 +1347,7 @@ "kanaIn": "こうて", "kanaOut": "こう", "rulesIn": [ - "iru" + "iru" ], "rulesOut": [ "v5" @@ -1357,7 +1357,7 @@ "kanaIn": "そうて", "kanaOut": "そう", "rulesIn": [ - "iru" + "iru" ], "rulesOut": [ "v5" @@ -1367,7 +1367,7 @@ "kanaIn": "とうて", "kanaOut": "とう", "rulesIn": [ - "iru" + "iru" ], "rulesOut": [ "v5" @@ -1377,7 +1377,7 @@ "kanaIn": "行って", "kanaOut": "行く", "rulesIn": [ - "iru" + "iru" ], "rulesOut": [ "v5" @@ -1387,7 +1387,7 @@ "kanaIn": "逝って", "kanaOut": "逝く", "rulesIn": [ - "iru" + "iru" ], "rulesOut": [ "v5" @@ -1397,7 +1397,7 @@ "kanaIn": "往って", "kanaOut": "往く", "rulesIn": [ - "iru" + "iru" ], "rulesOut": [ "v5" @@ -1407,7 +1407,7 @@ "kanaIn": "請うて", "kanaOut": "請う", "rulesIn": [ - "iru" + "iru" ], "rulesOut": [ "v5" @@ -1417,7 +1417,7 @@ "kanaIn": "乞うて", "kanaOut": "乞う", "rulesIn": [ - "iru" + "iru" ], "rulesOut": [ "v5" @@ -1427,7 +1427,7 @@ "kanaIn": "恋うて", "kanaOut": "恋う", "rulesIn": [ - "iru" + "iru" ], "rulesOut": [ "v5" @@ -1437,7 +1437,7 @@ "kanaIn": "問うて", "kanaOut": "問う", "rulesIn": [ - "iru" + "iru" ], "rulesOut": [ "v5" @@ -1447,7 +1447,7 @@ "kanaIn": "負うて", "kanaOut": "負う", "rulesIn": [ - "iru" + "iru" ], "rulesOut": [ "v5" @@ -1457,7 +1457,7 @@ "kanaIn": "沿うて", "kanaOut": "沿う", "rulesIn": [ - "iru" + "iru" ], "rulesOut": [ "v5" @@ -1467,7 +1467,7 @@ "kanaIn": "添うて", "kanaOut": "添う", "rulesIn": [ - "iru" + "iru" ], "rulesOut": [ "v5" @@ -1477,7 +1477,7 @@ "kanaIn": "副うて", "kanaOut": "副う", "rulesIn": [ - "iru" + "iru" ], "rulesOut": [ "v5" @@ -1487,21 +1487,11 @@ "kanaIn": "厭うて", "kanaOut": "厭う", "rulesIn": [ - "iru" + "iru" ], "rulesOut": [ "v5" ] - }, - { - "kanaIn": "で", - "kanaOut": "", - "rulesIn": [ - "iru" - ], - "rulesOut": [ - "neg-de" - ] } ], "-zu": [ @@ -2233,8 +2223,7 @@ "kanaIn": "ない", "kanaOut": "る", "rulesIn": [ - "adj-i", - "neg-de" + "adj-i" ], "rulesOut": [ "v1", @@ -2245,8 +2234,7 @@ "kanaIn": "かない", "kanaOut": "く", "rulesIn": [ - "adj-i", - "neg-de" + "adj-i" ], "rulesOut": [ "v5" @@ -2256,8 +2244,7 @@ "kanaIn": "がない", "kanaOut": "ぐ", "rulesIn": [ - "adj-i", - "neg-de" + "adj-i" ], "rulesOut": [ "v5" @@ -2267,8 +2254,7 @@ "kanaIn": "くない", "kanaOut": "い", "rulesIn": [ - "adj-i", - "neg-de" + "adj-i" ], "rulesOut": [ "adj-i" @@ -2278,8 +2264,7 @@ "kanaIn": "こない", "kanaOut": "くる", "rulesIn": [ - "adj-i", - "neg-de" + "adj-i" ], "rulesOut": [ "vk" @@ -2289,8 +2274,7 @@ "kanaIn": "さない", "kanaOut": "す", "rulesIn": [ - "adj-i", - "neg-de" + "adj-i" ], "rulesOut": [ "v5" @@ -2300,8 +2284,7 @@ "kanaIn": "しない", "kanaOut": "する", "rulesIn": [ - "adj-i", - "neg-de" + "adj-i" ], "rulesOut": [ "vs" @@ -2311,8 +2294,7 @@ "kanaIn": "たない", "kanaOut": "つ", "rulesIn": [ - "adj-i", - "neg-de" + "adj-i" ], "rulesOut": [ "v5" @@ -2322,8 +2304,7 @@ "kanaIn": "なない", "kanaOut": "ぬ", "rulesIn": [ - "adj-i", - "neg-de" + "adj-i" ], "rulesOut": [ "v5" @@ -2333,8 +2314,7 @@ "kanaIn": "ばない", "kanaOut": "ぶ", "rulesIn": [ - "adj-i", - "neg-de" + "adj-i" ], "rulesOut": [ "v5" @@ -2344,8 +2324,7 @@ "kanaIn": "まない", "kanaOut": "む", "rulesIn": [ - "adj-i", - "neg-de" + "adj-i" ], "rulesOut": [ "v5" @@ -2355,8 +2334,7 @@ "kanaIn": "らない", "kanaOut": "る", "rulesIn": [ - "adj-i", - "neg-de" + "adj-i" ], "rulesOut": [ "v5" @@ -2366,8 +2344,7 @@ "kanaIn": "わない", "kanaOut": "う", "rulesIn": [ - "adj-i", - "neg-de" + "adj-i" ], "rulesOut": [ "v5" @@ -3681,8 +3658,8 @@ ], "progressive or perfect": [ { - "kanaIn": "いる", - "kanaOut": "", + "kanaIn": "ている", + "kanaOut": "て", "rulesIn": [ "v1" ], @@ -3691,8 +3668,8 @@ ] }, { - "kanaIn": "る", - "kanaOut": "", + "kanaIn": "ておる", + "kanaOut": "て", "rulesIn": [ "v1" ], @@ -3701,14 +3678,54 @@ ] }, { - "kanaIn": "おる", - "kanaOut": "", + "kanaIn": "てる", + "kanaOut": "て", + "rulesIn": [ + "v1" + ], + "rulesOut": [ + "iru" + ] + }, + { + "kanaIn": "でいる", + "kanaOut": "で", + "rulesIn": [ + "v1" + ], + "rulesOut": [ + "iru" + ] + }, + { + "kanaIn": "でおる", + "kanaOut": "で", + "rulesIn": [ + "v1" + ], + "rulesOut": [ + "iru" + ] + }, + { + "kanaIn": "とる", + "kanaOut": "て", "rulesIn": [ "v1" ], "rulesOut": [ "iru" ] + }, + { + "kanaIn": "ないでいる", + "kanaOut": "ない", + "rulesIn": [ + "v1" + ], + "rulesOut": [ + "adj-i" + ] } ] } -- cgit v1.2.3 From 50a47348a7a040d1bcaf0a12a38cca049dc207f7 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 5 Oct 2019 16:24:42 -0400 Subject: Optimize internal data structure used by the Deinflector class --- ext/bg/js/deinflector.js | 73 +++++++++++++++++++++++++++++++----------------- ext/bg/js/translator.js | 17 ++--------- 2 files changed, 51 insertions(+), 39 deletions(-) diff --git a/ext/bg/js/deinflector.js b/ext/bg/js/deinflector.js index ad77895c..ce4b2961 100644 --- a/ext/bg/js/deinflector.js +++ b/ext/bg/js/deinflector.js @@ -19,51 +19,74 @@ class Deinflector { constructor(reasons) { - this.reasons = reasons; + this.reasons = Deinflector.normalizeReasons(reasons); } deinflect(source) { const results = [{ source, term: source, - rules: [], + rules: 0, definitions: [], reasons: [] }]; for (let i = 0; i < results.length; ++i) { - const entry = results[i]; - - for (const reason in this.reasons) { - for (const variant of this.reasons[reason]) { - let accept = entry.rules.length === 0; - if (!accept) { - for (const rule of entry.rules) { - if (variant.rulesIn.includes(rule)) { - accept = true; - break; - } - } - } - - if (!accept || !entry.term.endsWith(variant.kanaIn)) { - continue; - } - - const term = entry.term.slice(0, -variant.kanaIn.length) + variant.kanaOut; - if (term.length === 0) { + const {rules, term, reasons} = results[i]; + for (const [reason, variants] of this.reasons) { + for (const [kanaIn, kanaOut, rulesIn, rulesOut] of variants) { + if ( + (rules !== 0 && (rules & rulesIn) === 0) || + !term.endsWith(kanaIn) || + (term.length - kanaIn.length + kanaOut.length) <= 0 + ) { continue; } results.push({ source, - term, - rules: variant.rulesOut, + term: term.slice(0, -kanaIn.length) + kanaOut, + rules: rulesOut, definitions: [], - reasons: [reason, ...entry.reasons] + reasons: [reason, ...reasons] }); } } } return results; } + + static normalizeReasons(reasons) { + const normalizedReasons = []; + for (const reason in reasons) { + const variants = []; + for (const {kanaIn, kanaOut, rulesIn, rulesOut} of reasons[reason]) { + variants.push([ + kanaIn, + kanaOut, + Deinflector.rulesToRuleFlags(rulesIn), + Deinflector.rulesToRuleFlags(rulesOut) + ]); + } + normalizedReasons.push([reason, variants]); + } + return normalizedReasons; + } + + static rulesToRuleFlags(rules) { + const ruleTypes = Deinflector.ruleTypes; + let value = 0; + for (const rule of rules) { + value |= ruleTypes[rule]; + } + return value; + } } + +Deinflector.ruleTypes = { + 'v1': 0b0000001, // Verb ichidan + 'v5': 0b0000010, // Verb godan + 'vs': 0b0000100, // Verb suru + 'vk': 0b0001000, // Verb kuru + 'adj-i': 0b0010000, // Adjective i + 'iru': 0b0100000, // Intermediate -iru endings for progressive or perfect tense +}; diff --git a/ext/bg/js/translator.js b/ext/bg/js/translator.js index 65d746ea..601ee30c 100644 --- a/ext/bg/js/translator.js +++ b/ext/bg/js/translator.js @@ -238,8 +238,10 @@ class Translator { const definitions = await this.database.findTermsBulk(uniqueDeinflectionTerms, titles); for (const definition of definitions) { + const definitionRules = Deinflector.rulesToRuleFlags(definition.rules); for (const deinflection of uniqueDeinflectionArrays[definition.index]) { - if (Translator.definitionContainsAnyRule(definition, deinflection.rules)) { + const deinflectionRules = deinflection.rules; + if (deinflectionRules === 0 || (definitionRules & deinflectionRules) !== 0) { deinflection.definitions.push(definition); } } @@ -248,19 +250,6 @@ class Translator { return deinflections.filter(e => e.definitions.length > 0); } - static definitionContainsAnyRule(definition, rules) { - if (rules.length === 0) { - return true; - } - const definitionRules = definition.rules; - for (const rule of rules) { - if (definitionRules.includes(rule)) { - return true; - } - } - return false; - } - getDeinflections(text) { const deinflections = []; -- cgit v1.2.3 From 2255fadf52bff17d183f48e0bc72a078568481f2 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 5 Oct 2019 21:38:13 -0400 Subject: Rename Popup.setVisible to setVisibleOverride --- ext/fg/js/frontend.js | 4 ++-- ext/fg/js/popup-proxy-host.js | 6 +++--- ext/fg/js/popup-proxy.js | 4 ++-- ext/fg/js/popup.js | 2 +- ext/mixed/js/display.js | 8 ++++---- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js index 58dc0e4a..3fa70357 100644 --- a/ext/fg/js/frontend.js +++ b/ext/fg/js/frontend.js @@ -527,8 +527,8 @@ Frontend.runtimeMessageHandlers = { self.updateOptions(); }, - popupSetVisible: (self, {visible}) => { - self.popup.setVisible(visible); + popupSetVisibleOverride: (self, {visible}) => { + self.popup.setVisibleOverride(visible); } }; diff --git a/ext/fg/js/popup-proxy-host.js b/ext/fg/js/popup-proxy-host.js index cb9741be..7fad71c1 100644 --- a/ext/fg/js/popup-proxy-host.js +++ b/ext/fg/js/popup-proxy-host.js @@ -41,7 +41,7 @@ class PopupProxyHost { show: ({id, elementRect, options}) => this.show(id, elementRect, options), showOrphaned: ({id, elementRect, options}) => this.show(id, elementRect, options), hide: ({id, changeFocus}) => this.hide(id, changeFocus), - setVisible: ({id, visible}) => this.setVisible(id, visible), + setVisibleOverride: ({id, visible}) => this.setVisibleOverride(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), kanjiShow: ({id, elementRect, writingMode, definitions, options, context}) => this.kanjiShow(id, elementRect, writingMode, definitions, options, context), @@ -103,9 +103,9 @@ class PopupProxyHost { return popup.hide(changeFocus); } - async setVisible(id, visible) { + async setVisibleOverride(id, visible) { const popup = this.getPopup(id); - return popup.setVisible(visible); + return popup.setVisibleOverride(visible); } async containsPoint(id, x, y) { diff --git a/ext/fg/js/popup-proxy.js b/ext/fg/js/popup-proxy.js index 072cebc9..99b28549 100644 --- a/ext/fg/js/popup-proxy.js +++ b/ext/fg/js/popup-proxy.js @@ -65,9 +65,9 @@ class PopupProxy { return await this.invokeHostApi('hide', {id: this.id, changeFocus}); } - async setVisible(visible) { + async setVisibleOverride(visible) { const id = await this.getPopupId(); - return await this.invokeHostApi('setVisible', {id, visible}); + return await this.invokeHostApi('setVisibleOverride', {id, visible}); } async containsPoint(x, y) { diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js index 9dff6f28..5509c98c 100644 --- a/ext/fg/js/popup.js +++ b/ext/fg/js/popup.js @@ -238,7 +238,7 @@ class Popup { return this.isInjected && this.container.style.visibility !== 'hidden'; } - setVisible(visible) { + setVisibleOverride(visible) { if (visible) { this.container.style.setProperty('display', ''); } else { diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index dc64dbea..b70fa4b8 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -445,7 +445,7 @@ class Display { async getScreenshot() { try { - await this.setPopupVisible(false); + await this.setPopupVisibleOverride(false); await Display.delay(1); // Wait for popup to be hidden. const {format, quality} = this.options.anki.screenshot; @@ -454,7 +454,7 @@ class Display { return {dataUrl, format}; } finally { - await this.setPopupVisible(true); + await this.setPopupVisibleOverride(true); } } @@ -462,8 +462,8 @@ class Display { return this.options.general.resultOutputMode === 'merge' ? 0 : -1; } - setPopupVisible(visible) { - return apiForward('popupSetVisible', {visible}); + setPopupVisibleOverride(visible) { + return apiForward('popupSetVisibleOverride', {visible}); } setSpinnerVisible(visible) { -- cgit v1.2.3 From cd6d4e7ee14b3bfde9efb76eb65d7ee91c740c77 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 5 Oct 2019 21:57:13 -0400 Subject: Update how popup visibility works --- ext/fg/js/popup.js | 49 ++++++++++++++++++++++++------------------------- ext/mixed/js/display.js | 2 +- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/ext/fg/js/popup.js b/ext/fg/js/popup.js index 5509c98c..9ca91afa 100644 --- a/ext/fg/js/popup.js +++ b/ext/fg/js/popup.js @@ -34,6 +34,9 @@ class Popup { this.container.style.height = '0px'; this.injectPromise = null; this.isInjected = false; + this.visible = false; + this.visibleOverride = null; + this.updateVisibility(); } inject(options) { @@ -105,9 +108,11 @@ class Popup { container.style.top = `${y}px`; container.style.width = `${width}px`; container.style.height = `${height}px`; - container.style.visibility = 'visible'; - this.hideChildren(true); + this.setVisible(true); + if (this.child !== null) { + this.child.hide(true); + } } static getPositionForHorizontalText(elementRect, width, height, maxWidth, maxHeight, optionsGeneral) { @@ -209,41 +214,35 @@ class Popup { } hide(changeFocus) { - if (this.isContainerHidden()) { - changeFocus = false; + if (!this.isVisible()) { + return; + } + + this.setVisible(false); + if (this.child !== null) { + this.child.hide(false); } - this.hideChildren(changeFocus); - this.hideContainer(); if (changeFocus) { this.focusParent(); } } - hideChildren(changeFocus) { - // Recursively hides all children. - if (this.child !== null && !this.child.isContainerHidden()) { - this.child.hide(changeFocus); - } - } - - hideContainer() { - this.container.style.visibility = 'hidden'; + isVisible() { + return this.isInjected && (this.visibleOverride !== null ? this.visibleOverride : this.visible); } - isContainerHidden() { - return (this.container.style.visibility === 'hidden'); + setVisible(visible) { + this.visible = visible; + this.updateVisibility(); } - isVisible() { - return this.isInjected && this.container.style.visibility !== 'hidden'; + setVisibleOverride(visible) { + this.visibleOverride = visible; + this.updateVisibility(); } - setVisibleOverride(visible) { - if (visible) { - this.container.style.setProperty('display', ''); - } else { - this.container.style.setProperty('display', 'none', 'important'); - } + updateVisibility() { + this.container.style.setProperty('visibility', this.isVisible() ? 'visible' : 'hidden', 'important'); } focusParent() { diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index b70fa4b8..575011fd 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -454,7 +454,7 @@ class Display { return {dataUrl, format}; } finally { - await this.setPopupVisibleOverride(true); + await this.setPopupVisibleOverride(null); } } -- cgit v1.2.3 From 113cc725c1764e5eb814a22ace23a7e7150d016f Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sat, 5 Oct 2019 22:11:05 -0400 Subject: Make the window.onresize handler not change focus --- ext/fg/js/frontend.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js index 3fa70357..4ad78aa7 100644 --- a/ext/fg/js/frontend.js +++ b/ext/fg/js/frontend.js @@ -145,7 +145,7 @@ class Frontend { } onResize() { - this.searchClear(true); + this.searchClear(false); } onClick(e) { -- cgit v1.2.3 From c3926c67ad83e13d278d58085eda7c84f5003505 Mon Sep 17 00:00:00 2001 From: Alex Yatskov Date: Sun, 6 Oct 2019 09:07:22 -0700 Subject: version bump --- ext/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/manifest.json b/ext/manifest.json index 34b575cd..b52c1b25 100644 --- a/ext/manifest.json +++ b/ext/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 2, "name": "Yomichan", - "version": "1.8.5", + "version": "1.8.7", "description": "Japanese dictionary with Anki integration (testing)", "icons": {"16": "mixed/img/icon16.png", "48": "mixed/img/icon48.png", "128": "mixed/img/icon128.png"}, -- cgit v1.2.3 From 69b9cac8af448e9769c5d3f50fac785b46800b79 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 6 Oct 2019 20:50:59 -0400 Subject: Create SVG icons --- ext/mixed/img/add-term-kana.svg | 23 + ext/mixed/img/add-term-kanji.svg | 24 ++ ext/mixed/img/entry-current.svg | 16 + ext/mixed/img/play-audio.svg | 27 ++ ext/mixed/img/source-term.svg | 31 ++ ext/mixed/img/view-note.svg | 22 + resources/icons.svg | 892 +++++++++++++++++++++++++++++++++++++++ 7 files changed, 1035 insertions(+) create mode 100644 ext/mixed/img/add-term-kana.svg create mode 100644 ext/mixed/img/add-term-kanji.svg create mode 100644 ext/mixed/img/entry-current.svg create mode 100644 ext/mixed/img/play-audio.svg create mode 100644 ext/mixed/img/source-term.svg create mode 100644 ext/mixed/img/view-note.svg create mode 100644 resources/icons.svg diff --git a/ext/mixed/img/add-term-kana.svg b/ext/mixed/img/add-term-kana.svg new file mode 100644 index 00000000..bb964476 --- /dev/null +++ b/ext/mixed/img/add-term-kana.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ext/mixed/img/add-term-kanji.svg b/ext/mixed/img/add-term-kanji.svg new file mode 100644 index 00000000..3737eaec --- /dev/null +++ b/ext/mixed/img/add-term-kanji.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ext/mixed/img/entry-current.svg b/ext/mixed/img/entry-current.svg new file mode 100644 index 00000000..abf3f76d --- /dev/null +++ b/ext/mixed/img/entry-current.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/ext/mixed/img/play-audio.svg b/ext/mixed/img/play-audio.svg new file mode 100644 index 00000000..1d5e2d9c --- /dev/null +++ b/ext/mixed/img/play-audio.svg @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ext/mixed/img/source-term.svg b/ext/mixed/img/source-term.svg new file mode 100644 index 00000000..a70938f2 --- /dev/null +++ b/ext/mixed/img/source-term.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ext/mixed/img/view-note.svg b/ext/mixed/img/view-note.svg new file mode 100644 index 00000000..3e6f1dce --- /dev/null +++ b/ext/mixed/img/view-note.svg @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/icons.svg b/resources/icons.svg new file mode 100644 index 00000000..4bc46c02 --- /dev/null +++ b/resources/icons.svg @@ -0,0 +1,892 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -- cgit v1.2.3 From 7c826fdd0bffe2df49bdb3d783e496af41908f13 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 6 Oct 2019 21:05:27 -0400 Subject: Switch from using PNGs to SVGs --- ext/bg/guide.html | 2 +- ext/bg/js/templates.js | 16 ++++++++-------- tmpl/kanji.html | 8 ++++---- tmpl/terms.html | 14 +++++++------- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/ext/bg/guide.html b/ext/bg/guide.html index 7ec1d8d9..2a602f1f 100644 --- a/ext/bg/guide.html +++ b/ext/bg/guide.html @@ -23,7 +23,7 @@
  • Click on the monkey wrench icon in the middle to open the options page.
  • Import the dictionaries you wish to use for term and Kanji searches.
  • Hold down Shift key or the middle mouse button as you move your mouse over text to display definitions.
  • -
  • Click on the icon to hear the term pronounced by a native speaker.
  • +
  • Click on the icon to hear the term pronounced by a native speaker.
  • Click on individual Kanji in the term definition results to view additional information about those characters.
  • diff --git a/ext/bg/js/templates.js b/ext/bg/js/templates.js index e12d1bf3..c61f5d7f 100644 --- a/ext/bg/js/templates.js +++ b/ext/bg/js/templates.js @@ -61,7 +61,7 @@ templates['kanji.html'] = template({"1":function(container,depth0,helpers,partia return "
    \n
    \n" + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.addable : depth0),{"name":"if","hash":{},"fn":container.program(11, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.source : depth0),{"name":"if","hash":{},"fn":container.program(13, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") - + " \n
    \n\n
    " + + " \n
    \n\n
    " + container.escapeExpression(((helper = (helper = helpers.character || (depth0 != null ? depth0.character : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(alias1,{"name":"character","hash":{},"data":data}) : helper))) + "
    \n\n" + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.frequencies : depth0),{"name":"if","hash":{},"fn":container.program(15, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") @@ -85,9 +85,9 @@ templates['kanji.html'] = template({"1":function(container,depth0,helpers,partia + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.debug : depth0),{"name":"if","hash":{},"fn":container.program(31, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + "
    \n"; },"11":function(container,depth0,helpers,partials,data) { - return " \n \n"; + return " \n \n"; },"13":function(container,depth0,helpers,partials,data) { - return " \n"; + return " \n"; },"15":function(container,depth0,helpers,partials,data) { var stack1; @@ -290,7 +290,7 @@ templates['terms.html'] = template({"1":function(container,depth0,helpers,partia + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.addable : depth0),{"name":"if","hash":{},"fn":container.program(23, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "") + ((stack1 = helpers.unless.call(alias1,(depth0 != null ? depth0.merged : depth0),{"name":"unless","hash":{},"fn":container.program(25, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "") + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.source : depth0),{"name":"if","hash":{},"fn":container.program(28, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "") - + " \n \n\n" + + " \n \n\n" + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.merged : depth0),{"name":"if","hash":{},"fn":container.program(30, data, 0, blockParams, depths),"inverse":container.program(45, data, 0, blockParams, depths),"data":data})) != null ? stack1 : "") + "\n" + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.reasons : depth0),{"name":"if","hash":{},"fn":container.program(49, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "") @@ -302,15 +302,15 @@ templates['terms.html'] = template({"1":function(container,depth0,helpers,partia + ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.debug : depth0),{"name":"if","hash":{},"fn":container.program(65, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : "") + "\n"; },"23":function(container,depth0,helpers,partials,data) { - return " \n \n \n"; + return " \n \n \n"; },"25":function(container,depth0,helpers,partials,data) { var stack1; return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.playback : depth0),{"name":"if","hash":{},"fn":container.program(26, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : ""); },"26":function(container,depth0,helpers,partials,data) { - return " \n"; + return " \n"; },"28":function(container,depth0,helpers,partials,data) { - return " \n"; + return " \n"; },"30":function(container,depth0,helpers,partials,data,blockParams,depths) { var stack1; @@ -342,7 +342,7 @@ templates['terms.html'] = template({"1":function(container,depth0,helpers,partia return ((stack1 = container.lambda(depth0, depth0)) != null ? stack1 : ""); },"35":function(container,depth0,helpers,partials,data) { - return ""; + return ""; },"37":function(container,depth0,helpers,partials,data) { var stack1; diff --git a/tmpl/kanji.html b/tmpl/kanji.html index a4bd95ee..0888d141 100644 --- a/tmpl/kanji.html +++ b/tmpl/kanji.html @@ -17,13 +17,13 @@ No data found
    {{#if addable}} - - + + {{/if}} {{#if source}} - + {{/if}} - +
    {{character}}
    diff --git a/tmpl/terms.html b/tmpl/terms.html index b9e5e0ad..0b967bb5 100644 --- a/tmpl/terms.html +++ b/tmpl/terms.html @@ -30,26 +30,26 @@
    {{#if addable}} - - - + + + {{/if}} {{#unless merged}} {{#if playback}} - + {{/if}} {{/unless}} {{#if source}} - + {{/if}} - +
    {{#if merged}} {{~#each expressions~}}
    {{#kanjiLinks}}{{#furigana}}{{{.}}}{{/furigana}}{{/kanjiLinks}}
    {{~#if ../playback~}} - + {{~/if~}} {{~#if termTags~}}
    -- cgit v1.2.3 From 205c7d88d071d2c9328ff103ca90edfecf793919 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 6 Oct 2019 21:07:57 -0400 Subject: Remove PNG icons --- ext/mixed/img/add-kanji.png | Bin 733 -> 0 bytes ext/mixed/img/add-term-kana.png | Bin 286 -> 0 bytes ext/mixed/img/add-term-kanji.png | Bin 733 -> 0 bytes ext/mixed/img/entry-current.png | Bin 743 -> 0 bytes ext/mixed/img/play-audio.png | Bin 610 -> 0 bytes ext/mixed/img/source-term.png | Bin 680 -> 0 bytes ext/mixed/img/view-note.png | Bin 622 -> 0 bytes 7 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 ext/mixed/img/add-kanji.png delete mode 100644 ext/mixed/img/add-term-kana.png delete mode 100644 ext/mixed/img/add-term-kanji.png delete mode 100644 ext/mixed/img/entry-current.png delete mode 100644 ext/mixed/img/play-audio.png delete mode 100644 ext/mixed/img/source-term.png delete mode 100644 ext/mixed/img/view-note.png diff --git a/ext/mixed/img/add-kanji.png b/ext/mixed/img/add-kanji.png deleted file mode 100644 index 6332fefe..00000000 Binary files a/ext/mixed/img/add-kanji.png and /dev/null differ diff --git a/ext/mixed/img/add-term-kana.png b/ext/mixed/img/add-term-kana.png deleted file mode 100644 index 41ff8335..00000000 Binary files a/ext/mixed/img/add-term-kana.png and /dev/null differ diff --git a/ext/mixed/img/add-term-kanji.png b/ext/mixed/img/add-term-kanji.png deleted file mode 100644 index 6332fefe..00000000 Binary files a/ext/mixed/img/add-term-kanji.png and /dev/null differ diff --git a/ext/mixed/img/entry-current.png b/ext/mixed/img/entry-current.png deleted file mode 100644 index bab7cc9b..00000000 Binary files a/ext/mixed/img/entry-current.png and /dev/null differ diff --git a/ext/mixed/img/play-audio.png b/ext/mixed/img/play-audio.png deleted file mode 100644 index 6056d234..00000000 Binary files a/ext/mixed/img/play-audio.png and /dev/null differ diff --git a/ext/mixed/img/source-term.png b/ext/mixed/img/source-term.png deleted file mode 100644 index 2e53c698..00000000 Binary files a/ext/mixed/img/source-term.png and /dev/null differ diff --git a/ext/mixed/img/view-note.png b/ext/mixed/img/view-note.png deleted file mode 100644 index 7d863f94..00000000 Binary files a/ext/mixed/img/view-note.png and /dev/null differ -- cgit v1.2.3 From 176f6a248b5f2b4487d760b73221c0fbc90ab4cf Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 7 Oct 2019 22:28:12 -0400 Subject: Fix findTermMetaBulk trying to use undefined row.id --- ext/bg/js/database.js | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/ext/bg/js/database.js b/ext/bg/js/database.js index e8214c3c..c812dee8 100644 --- a/ext/bg/js/database.js +++ b/ext/bg/js/database.js @@ -80,7 +80,12 @@ class Database { const visited = {}; const results = []; const createResult = Database.createTerm; - const filter = (row) => titles.includes(row.dictionary); + const processRow = (row, index) => { + if (titles.includes(row.dictionary) && !visited.hasOwnProperty(row.id)) { + visited[row.id] = true; + results.push(createResult(row, index)); + } + }; const db = this.db.backendDB(); const dbTransaction = db.transaction(['terms'], 'readonly'); @@ -91,8 +96,8 @@ class Database { for (let i = 0; i < terms.length; ++i) { const only = IDBKeyRange.only(terms[i]); promises.push( - Database.getAll(dbIndex1, only, i, visited, filter, createResult, results), - Database.getAll(dbIndex2, only, i, visited, filter, createResult, results) + Database.getAll(dbIndex1, only, i, processRow), + Database.getAll(dbIndex2, only, i, processRow) ); } @@ -152,10 +157,13 @@ class Database { async findTermMetaBulk(terms, titles) { const promises = []; - const visited = {}; const results = []; const createResult = Database.createTermMeta; - const filter = (row) => titles.includes(row.dictionary); + const processRow = (row, index) => { + if (titles.includes(row.dictionary)) { + results.push(createResult(row, index)); + } + }; const db = this.db.backendDB(); const dbTransaction = db.transaction(['termMeta'], 'readonly'); @@ -164,7 +172,7 @@ class Database { for (let i = 0; i < terms.length; ++i) { const only = IDBKeyRange.only(terms[i]); - promises.push(Database.getAll(dbIndex, only, i, visited, filter, createResult, results)); + promises.push(Database.getAll(dbIndex, only, i, processRow)); } await Promise.all(promises); @@ -537,39 +545,32 @@ class Database { }; } - static getAll(dbIndex, query, index, visited, filter, createResult, results) { + static getAll(dbIndex, query, context, processRow) { const fn = typeof dbIndex.getAll === 'function' ? Database.getAllFast : Database.getAllUsingCursor; - return fn(dbIndex, query, index, visited, filter, createResult, results); + return fn(dbIndex, query, context, processRow); } - static getAllFast(dbIndex, query, index, visited, filter, createResult, results) { + static getAllFast(dbIndex, query, context, processRow) { return new Promise((resolve, reject) => { const request = dbIndex.getAll(query); request.onerror = (e) => reject(e); request.onsuccess = (e) => { for (const row of e.target.result) { - if (filter(row, index) && !visited.hasOwnProperty(row.id)) { - visited[row.id] = true; - results.push(createResult(row, index)); - } + processRow(row, context); } resolve(); }; }); } - static getAllUsingCursor(dbIndex, query, index, visited, filter, createResult, results) { + static getAllUsingCursor(dbIndex, query, context, processRow) { return new Promise((resolve, reject) => { const request = dbIndex.openCursor(query, 'next'); request.onerror = (e) => reject(e); request.onsuccess = (e) => { const cursor = e.target.result; if (cursor) { - const row = cursor.value; - if (filter(row, index) && !visited.hasOwnProperty(row.id)) { - visited[row.id] = true; - results.push(createResult(row, index)); - } + processRow(cursor.value, context); cursor.continue(); } else { resolve(); -- cgit v1.2.3 From 9d488e1916f60ca03ccbff511f810753176d9023 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Tue, 8 Oct 2019 19:32:44 -0400 Subject: Validate that key is a string On Chrome, when clicking an autocomplete dropdown menu option, a keydown event is generated which is not of type KeyboardEvent. --- ext/mixed/js/display.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index 575011fd..228e554a 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -539,7 +539,7 @@ class Display { static getKeyFromEvent(event) { const key = event.key; - return key.length === 1 ? key.toUpperCase() : key; + return (typeof key === 'string' ? (key.length === 1 ? key.toUpperCase() : key) : ''); } } -- cgit v1.2.3 From 1074c33f2074984fe5b21654e944d787408c0a93 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Tue, 8 Oct 2019 19:49:08 -0400 Subject: Add support for query parameter in URL on search page --- ext/bg/js/search.js | 99 +++++++++++++++++++++++++++++++++++++++++++++-------- ext/bg/search.html | 2 +- 2 files changed, 86 insertions(+), 15 deletions(-) diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index 13ed1e08..52adab16 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -29,7 +29,8 @@ class DisplaySearch extends Display { this.search = document.querySelector('#search'); this.query = document.querySelector('#query'); this.intro = document.querySelector('#intro'); - this.introHidden = false; + this.introVisible = true; + this.introAnimationTimer = null; this.dependencies = Object.assign({}, this.dependencies, {docRangeFromPoint, docSentenceExtract}); @@ -38,8 +39,17 @@ class DisplaySearch extends Display { } if (this.query !== null) { this.query.addEventListener('input', () => this.onSearchInput(), false); + + const query = DisplaySearch.getSearchQueryFromLocation(window.location.href); + if (query !== null) { + this.query.value = window.wanakana.toKana(query); + this.onSearchQueryUpdated(query, false); + } + window.wanakana.bind(this.query); } + + this.updateSearchButton(); } onError(error) { @@ -56,41 +66,102 @@ class DisplaySearch extends Display { } onSearchInput() { - this.search.disabled = (this.query === null || this.query.value.length === 0); + this.updateSearchButton(); } - async onSearch(e) { + onSearch(e) { if (this.query === null) { return; } + e.preventDefault(); + + const query = this.query.value; + const queryString = query.length > 0 ? `?query=${encodeURIComponent(query)}` : ''; + window.history.replaceState(null, '', `${window.location.pathname}${queryString}`); + this.onSearchQueryUpdated(query, true); + } + + async onSearchQueryUpdated(query, animate) { try { - e.preventDefault(); - this.hideIntro(); - const {length, definitions} = await apiTermsFind(this.query.value, this.optionsContext); - super.termsShow(definitions, await apiOptionsGet(this.optionsContext)); + const valid = (query.length > 0); + this.setIntroVisible(!valid, animate); + this.updateSearchButton(); + if (valid) { + const {definitions} = await apiTermsFind(query, this.optionsContext); + this.termsShow(definitions, await apiOptionsGet(this.optionsContext)); + } else { + this.container.textContent = ''; + } } catch (e) { this.onError(e); } } - hideIntro() { - if (this.introHidden) { + setIntroVisible(visible, animate) { + if (this.introVisible === visible) { return; } - this.introHidden = true; + this.introVisible = visible; if (this.intro === null) { return; } - const size = this.intro.getBoundingClientRect(); - this.intro.style.height = `${size.height}px`; - this.intro.style.transition = 'height 0.4s ease-in-out 0s'; - window.getComputedStyle(this.intro).getPropertyValue('height'); // Commits height so next line can start animation + if (this.introAnimationTimer !== null) { + clearTimeout(this.introAnimationTimer); + this.introAnimationTimer = null; + } + + if (visible) { + this.showIntro(animate); + } else { + this.hideIntro(animate); + } + } + + showIntro(animate) { + if (animate) { + const duration = 0.4; + this.intro.style.transition = ''; + this.intro.style.height = ''; + const size = this.intro.getBoundingClientRect(); + this.intro.style.height = `0px`; + this.intro.style.transition = `height ${duration}s ease-in-out 0s`; + window.getComputedStyle(this.intro).getPropertyValue('height'); // Commits height so next line can start animation + this.intro.style.height = `${size.height}px`; + this.introAnimationTimer = setTimeout(() => { + this.intro.style.height = ''; + this.introAnimationTimer = null; + }, duration * 1000); + } else { + this.intro.style.transition = ''; + this.intro.style.height = ''; + } + } + + hideIntro(animate) { + if (animate) { + const duration = 0.4; + const size = this.intro.getBoundingClientRect(); + this.intro.style.height = `${size.height}px`; + this.intro.style.transition = `height ${duration}s ease-in-out 0s`; + window.getComputedStyle(this.intro).getPropertyValue('height'); // Commits height so next line can start animation + } else { + this.intro.style.transition = ''; + } this.intro.style.height = '0'; } + + updateSearchButton() { + this.search.disabled = this.introVisible && (this.query === null || this.query.value.length === 0); + } + + static getSearchQueryFromLocation(url) { + let match = /^[^\?#]*\?(?:[^&#]*&)?query=([^&#]*)/.exec(url); + return match !== null ? decodeURIComponent(match[1]) : null; + } } window.yomichan_search = new DisplaySearch(); diff --git a/ext/bg/search.html b/ext/bg/search.html index 668b2436..141dea5d 100644 --- a/ext/bg/search.html +++ b/ext/bg/search.html @@ -20,7 +20,7 @@
    - +
    -- cgit v1.2.3 From bf5d301685db6c31ea64771dab59610586bc1619 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Tue, 8 Oct 2019 19:54:02 -0400 Subject: Only use foreground api.js/util.js --- ext/bg/js/search-frontend.js | 2 -- ext/bg/search.html | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/ext/bg/js/search-frontend.js b/ext/bg/js/search-frontend.js index faec29ef..0c1a61ea 100644 --- a/ext/bg/js/search-frontend.js +++ b/ext/bg/js/search-frontend.js @@ -26,10 +26,8 @@ async function searchFrontendSetup() { if (!options.scanning.enableOnSearchPage) { return; } const scriptSrcs = [ - '/fg/js/api.js', '/fg/js/frontend-api-receiver.js', '/fg/js/popup.js', - '/fg/js/util.js', '/fg/js/popup-proxy-host.js', '/fg/js/frontend.js' ]; diff --git a/ext/bg/search.html b/ext/bg/search.html index 141dea5d..e71824d3 100644 --- a/ext/bg/search.html +++ b/ext/bg/search.html @@ -36,14 +36,14 @@ - - + + -- cgit v1.2.3 From 88de4271843197d37243a9ac360236f9dfb414e1 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 7 Oct 2019 20:46:02 -0400 Subject: Throw Error instead of string --- ext/bg/js/anki.js | 2 +- ext/bg/js/backend.js | 2 +- ext/bg/js/database.js | 60 +++++++++++++++++-------------------------- ext/fg/js/popup-proxy-host.js | 2 +- ext/mixed/js/display.js | 6 ++--- ext/mixed/js/extension.js | 2 +- 6 files changed, 30 insertions(+), 44 deletions(-) diff --git a/ext/bg/js/anki.js b/ext/bg/js/anki.js index bd4e46cd..5c89aad8 100644 --- a/ext/bg/js/anki.js +++ b/ext/bg/js/anki.js @@ -67,7 +67,7 @@ class AnkiConnect { if (this.remoteVersion < this.localVersion) { this.remoteVersion = await this.ankiInvoke('version'); if (this.remoteVersion < this.localVersion) { - throw 'Extension and plugin versions incompatible'; + throw new Error('Extension and plugin versions incompatible'); } } } diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 3c5ad885..fb77b71e 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -75,7 +75,7 @@ class Backend { const promise = handler(params, sender); promise .then(result => callback({result})) - .catch(error => callback({error: typeof error.toString === 'function' ? error.toString() : error})); + .catch(error => callback(errorToJson(error))); } return true; diff --git a/ext/bg/js/database.js b/ext/bg/js/database.js index c812dee8..771a71c9 100644 --- a/ext/bg/js/database.js +++ b/ext/bg/js/database.js @@ -25,7 +25,7 @@ class Database { async prepare() { if (this.db) { - throw 'Database already initialized'; + throw new Error('Database already initialized'); } this.db = new Dexie('dict'); @@ -48,9 +48,7 @@ class Database { } async purge() { - if (!this.db) { - throw 'Database not initialized'; - } + this.validate(); this.db.close(); await this.db.delete(); @@ -61,9 +59,7 @@ class Database { } async findTerms(term, titles) { - if (!this.db) { - throw 'Database not initialized'; - } + this.validate(); const results = []; await this.db.terms.where('expression').equals(term).or('reading').equals(term).each(row => { @@ -107,9 +103,7 @@ class Database { } async findTermsExact(term, reading, titles) { - if (!this.db) { - throw 'Database not initialized'; - } + this.validate(); const results = []; await this.db.terms.where('expression').equals(term).each(row => { @@ -122,9 +116,7 @@ class Database { } async findTermsBySequence(sequence, mainDictionary) { - if (!this.db) { - throw 'Database not initialized'; - } + this.validate(); const results = []; await this.db.terms.where('sequence').equals(sequence).each(row => { @@ -137,9 +129,7 @@ class Database { } async findTermMeta(term, titles) { - if (!this.db) { - throw 'Database not initialized'; - } + this.validate(); const results = []; await this.db.termMeta.where('expression').equals(term).each(row => { @@ -181,9 +171,7 @@ class Database { } async findKanji(kanji, titles) { - if (!this.db) { - throw 'Database not initialized'; - } + this.validate(); const results = []; await this.db.kanji.where('character').equals(kanji).each(row => { @@ -204,9 +192,7 @@ class Database { } async findKanjiMeta(kanji, titles) { - if (!this.db) { - throw 'Database not initialized'; - } + this.validate(); const results = []; await this.db.kanjiMeta.where('character').equals(kanji).each(row => { @@ -232,9 +218,7 @@ class Database { } async findTagForTitle(name, title) { - if (!this.db) { - throw 'Database not initialized'; - } + this.validate(); const cache = (this.tagCache.hasOwnProperty(title) ? this.tagCache[title] : (this.tagCache[title] = {})); @@ -251,17 +235,13 @@ class Database { } async summarize() { - if (this.db) { - return this.db.dictionaries.toArray(); - } else { - throw 'Database not initialized'; - } + this.validate(); + + return this.db.dictionaries.toArray(); } async importDictionary(archive, progressCallback, exceptions) { - if (!this.db) { - throw 'Database not initialized'; - } + this.validate(); const maxTransactionLength = 1000; const bulkAdd = async (table, items, total, current) => { @@ -301,12 +281,12 @@ class Database { const indexDataLoaded = async summary => { if (summary.version > 3) { - throw 'Unsupported dictionary version'; + throw new Error('Unsupported dictionary version'); } const count = await this.db.dictionaries.where('title').equals(summary.title).count(); if (count > 0) { - throw 'Dictionary is already imported'; + throw new Error('Dictionary is already imported'); } await this.db.dictionaries.add(summary); @@ -432,6 +412,12 @@ class Database { ); } + validate() { + if (this.db === null) { + throw new Error('Database not initialized'); + } + } + static async importDictionaryZip( archive, indexDataLoaded, @@ -445,12 +431,12 @@ class Database { const indexFile = zip.files['index.json']; if (!indexFile) { - throw 'No dictionary index found in archive'; + throw new Error('No dictionary index found in archive'); } const index = JSON.parse(await indexFile.async('string')); if (!index.title || !index.revision) { - throw 'Unrecognized dictionary format'; + throw new Error('Unrecognized dictionary format'); } const summary = { diff --git a/ext/fg/js/popup-proxy-host.js b/ext/fg/js/popup-proxy-host.js index 7fad71c1..f933639c 100644 --- a/ext/fg/js/popup-proxy-host.js +++ b/ext/fg/js/popup-proxy-host.js @@ -69,7 +69,7 @@ class PopupProxyHost { getPopup(id) { if (!this.popups.hasOwnProperty(id)) { - throw 'Invalid popup ID'; + throw new Error('Invalid popup ID'); } return this.popups[id]; diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index 228e554a..9beb2c52 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -39,11 +39,11 @@ class Display { } onError(error) { - throw 'Override me'; + throw new Error('Override me'); } onSearchClear() { - throw 'Override me'; + throw new Error('Override me'); } onSourceTermView(e) { @@ -386,7 +386,7 @@ class Display { viewerButton.dataset.noteId = noteId; } } else { - throw 'Note could note be added'; + throw new Error('Note could not be added'); } } catch (e) { this.onError(e); diff --git a/ext/mixed/js/extension.js b/ext/mixed/js/extension.js index 5c803132..1a87003d 100644 --- a/ext/mixed/js/extension.js +++ b/ext/mixed/js/extension.js @@ -34,7 +34,7 @@ function toIterable(value) { } } - throw 'Could not convert to iterable'; + throw new Error('Could not convert to iterable'); } function extensionHasChrome() { -- cgit v1.2.3 From 6a6e200ef947b92576351e39fc30b6653a576d70 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 7 Oct 2019 21:04:58 -0400 Subject: Update rejections to use Error --- ext/bg/js/audio.js | 4 ++-- ext/bg/js/options.js | 4 ++-- ext/bg/js/request.js | 4 ++-- ext/fg/js/frontend-api-receiver.js | 5 ++--- ext/fg/js/frontend-api-sender.js | 8 ++++---- ext/fg/js/popup-proxy.js | 2 +- ext/fg/js/util.js | 8 ++++---- ext/mixed/js/extension.js | 15 +++++++++++++++ 8 files changed, 32 insertions(+), 18 deletions(-) diff --git a/ext/bg/js/audio.js b/ext/bg/js/audio.js index 2e5db7cc..44da51d1 100644 --- a/ext/bg/js/audio.js +++ b/ext/bg/js/audio.js @@ -57,7 +57,7 @@ async function audioBuildUrl(definition, mode, cache={}) { const xhr = new XMLHttpRequest(); xhr.open('POST', 'https://www.japanesepod101.com/learningcenter/reference/dictionary_post'); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); - xhr.addEventListener('error', () => reject('Failed to scrape audio data')); + xhr.addEventListener('error', () => reject(new Error('Failed to scrape audio data'))); xhr.addEventListener('load', () => { cache[definition.expression] = xhr.responseText; resolve(xhr.responseText); @@ -87,7 +87,7 @@ async function audioBuildUrl(definition, mode, cache={}) { } else { const xhr = new XMLHttpRequest(); xhr.open('GET', `https://jisho.org/search/${definition.expression}`); - xhr.addEventListener('error', () => reject('Failed to scrape audio data')); + xhr.addEventListener('error', () => reject(new Error('Failed to scrape audio data'))); xhr.addEventListener('load', () => { cache[definition.expression] = xhr.responseText; resolve(xhr.responseText); diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js index 2c9de1ec..a2ab8877 100644 --- a/ext/bg/js/options.js +++ b/ext/bg/js/options.js @@ -402,7 +402,7 @@ function optionsLoad() { chrome.storage.local.get(['options'], store => { const error = chrome.runtime.lastError; if (error) { - reject(error); + reject(new Error(error)); } else { resolve(store.options); } @@ -431,7 +431,7 @@ function optionsSave(options) { chrome.storage.local.set({options: JSON.stringify(options)}, () => { const error = chrome.runtime.lastError; if (error) { - reject(error); + reject(new Error(error)); } else { resolve(); } diff --git a/ext/bg/js/request.js b/ext/bg/js/request.js index e4359863..3afc1506 100644 --- a/ext/bg/js/request.js +++ b/ext/bg/js/request.js @@ -22,7 +22,7 @@ function requestJson(url, action, params) { const xhr = new XMLHttpRequest(); xhr.overrideMimeType('application/json'); xhr.addEventListener('load', () => resolve(xhr.responseText)); - xhr.addEventListener('error', () => reject('Failed to connect')); + xhr.addEventListener('error', () => reject(new Error('Failed to connect'))); xhr.open(action, url); if (params) { xhr.send(JSON.stringify(params)); @@ -34,7 +34,7 @@ function requestJson(url, action, params) { return JSON.parse(responseText); } catch (e) { - return Promise.reject('Invalid response'); + return Promise.reject(new Error('Invalid response')); } }); } diff --git a/ext/fg/js/frontend-api-receiver.js b/ext/fg/js/frontend-api-receiver.js index 687e5c3c..fbfb3ab0 100644 --- a/ext/fg/js/frontend-api-receiver.js +++ b/ext/fg/js/frontend-api-receiver.js @@ -46,9 +46,8 @@ class FrontendApiReceiver { result => { this.sendResult(port, id, senderId, {result}); }, - e => { - const error = typeof e.toString === 'function' ? e.toString() : e; - this.sendResult(port, id, senderId, {error}); + error => { + this.sendResult(port, id, senderId, {error: errorToJson(error)}); }); } diff --git a/ext/fg/js/frontend-api-sender.js b/ext/fg/js/frontend-api-sender.js index 2e037e62..c6eeaeb2 100644 --- a/ext/fg/js/frontend-api-sender.js +++ b/ext/fg/js/frontend-api-sender.js @@ -31,7 +31,7 @@ class FrontendApiSender { invoke(action, params, target) { if (this.disconnected) { - return Promise.reject('Disconnected'); + return Promise.reject(new Error('Disconnected')); } if (this.port === null) { @@ -110,8 +110,8 @@ class FrontendApiSender { clearTimeout(info.timer); info.timer = null; - if (typeof data.error === 'string') { - info.reject(data.error); + if (typeof data.error !== 'undefined') { + info.reject(jsonToError(data.error)); } else { info.resolve(data.result); } @@ -122,7 +122,7 @@ class FrontendApiSender { const info = this.callbacks[id]; delete this.callbacks[id]; info.timer = null; - info.reject(reason); + info.reject(new Error(reason)); } static generateId(length) { diff --git a/ext/fg/js/popup-proxy.js b/ext/fg/js/popup-proxy.js index 99b28549..efbd28b2 100644 --- a/ext/fg/js/popup-proxy.js +++ b/ext/fg/js/popup-proxy.js @@ -98,7 +98,7 @@ class PopupProxy { invokeHostApi(action, params={}) { if (typeof this.parentFrameId !== 'number') { - return Promise.reject('Invalid frame'); + return Promise.reject(new Error('Invalid frame')); } return this.apiSender.invoke(action, params, `popup-proxy-host#${this.parentFrameId}`); } diff --git a/ext/fg/js/util.js b/ext/fg/js/util.js index dc99274e..9a7968a7 100644 --- a/ext/fg/js/util.js +++ b/ext/fg/js/util.js @@ -30,19 +30,19 @@ function utilInvoke(action, params={}) { chrome.runtime.sendMessage(data, (response) => { utilCheckLastError(chrome.runtime.lastError); if (response !== null && typeof response === 'object') { - if (response.error) { - reject(response.error); + if (typeof response.error !== 'undefined') { + reject(jsonToError(response.error)); } else { resolve(response.result); } } else { const message = response === null ? 'Unexpected null response' : `Unexpected response of type ${typeof response}`; - reject(`${message} (${JSON.stringify(data)})`); + reject(new Error(`${message} (${JSON.stringify(data)})`)); } }); } catch (e) { window.yomichan_orphaned = true; - reject(e.message); + reject(e); } }); } diff --git a/ext/mixed/js/extension.js b/ext/mixed/js/extension.js index 1a87003d..5e925124 100644 --- a/ext/mixed/js/extension.js +++ b/ext/mixed/js/extension.js @@ -53,6 +53,21 @@ function extensionHasBrowser() { } } +function errorToJson(error) { + return { + name: error.name, + message: error.message, + stack: error.stack + }; +} + +function jsonToError(jsonError) { + const error = new Error(jsonError.message); + error.name = jsonError.name; + error.stack = jsonError.stack; + return error; +} + const EXTENSION_IS_BROWSER_EDGE = ( extensionHasBrowser() && (!extensionHasChrome() || (typeof chrome.runtime === 'undefined' && typeof browser.runtime !== 'undefined')) -- cgit v1.2.3 From c5d6b9452d1bfa51a5fef9a19082d115ef180a9b Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Tue, 8 Oct 2019 21:33:10 -0400 Subject: Create utility function for logging errors --- ext/bg/js/search.js | 2 +- ext/fg/js/float.js | 2 +- ext/fg/js/frontend.js | 2 +- ext/mixed/js/extension.js | 18 ++++++++++++++++++ 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index 52adab16..ead9ba6f 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -53,7 +53,7 @@ class DisplaySearch extends Display { } onError(error) { - window.alert(`Error: ${error.toString ? error.toString() : error}`); + logError(error, true); } onSearchClear() { diff --git a/ext/fg/js/float.js b/ext/fg/js/float.js index 88842eef..8fdb6925 100644 --- a/ext/fg/js/float.js +++ b/ext/fg/js/float.js @@ -37,7 +37,7 @@ class DisplayFloat extends Display { if (window.yomichan_orphaned) { this.onOrphaned(); } else { - window.alert(`Error: ${error.toString ? error.toString() : error}`); + logError(error, true); } } diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js index 39433afb..88cb93a9 100644 --- a/ext/fg/js/frontend.js +++ b/ext/fg/js/frontend.js @@ -228,7 +228,7 @@ class Frontend { } onError(error) { - console.log(error); + logError(error, false); } setEnabled(enabled) { diff --git a/ext/mixed/js/extension.js b/ext/mixed/js/extension.js index 5e925124..861e52a5 100644 --- a/ext/mixed/js/extension.js +++ b/ext/mixed/js/extension.js @@ -68,6 +68,24 @@ function jsonToError(jsonError) { return error; } +function logError(error, alert) { + const manifest = chrome.runtime.getManifest(); + let errorMessage = `${manifest.name} v${manifest.version} has encountered an error.\n`; + errorMessage += `Originating URL: ${window.location.href}\n`; + + const errorString = `${error.toString ? error.toString() : error}`; + const stack = `${error.stack}`.trimRight(); + errorMessage += (!stack.startsWith(errorString) ? `${errorString}\n${stack}` : `${stack}`); + + errorMessage += '\n\nIssues can be reported at https://github.com/FooSoft/yomichan/issues'; + + console.error(errorMessage); + + if (alert) { + window.alert(`${errorString}\n\nCheck the developer console for more details.`); + } +} + const EXTENSION_IS_BROWSER_EDGE = ( extensionHasBrowser() && (!extensionHasChrome() || (typeof chrome.runtime === 'undefined' && typeof browser.runtime !== 'undefined')) -- cgit v1.2.3 From 97f5b7139fcb69c68560d025f99418b0f697940c Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 9 Oct 2019 20:27:05 -0400 Subject: Add findNoteIds to AnkiConnect --- ext/bg/js/anki.js | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/ext/bg/js/anki.js b/ext/bg/js/anki.js index 5c89aad8..9f851f13 100644 --- a/ext/bg/js/anki.js +++ b/ext/bg/js/anki.js @@ -72,9 +72,34 @@ class AnkiConnect { } } + async findNoteIds(notes) { + await this.checkVersion(); + const actions = notes.map(note => ({ + action: 'findNotes', + params: { + query: `deck:"${AnkiConnect.escapeQuery(note.deckName)}" ${AnkiConnect.fieldsToQuery(note.fields)}` + } + })); + return await this.ankiInvoke('multi', {actions}); + } + ankiInvoke(action, params) { return requestJson(this.server, 'POST', {action, params, version: this.localVersion}); } + + static escapeQuery(text) { + return text.replace(/"/g, ''); + } + + static fieldsToQuery(fields) { + const fieldNames = Object.keys(fields); + if (fieldNames.length === 0) { + return ''; + } + + const key = fieldNames[0]; + return `${key.toLowerCase()}:"${AnkiConnect.escapeQuery(fields[key])}"`; + } } @@ -106,4 +131,8 @@ class AnkiNull { async guiBrowse(query) { return []; } + + async findNoteIds(notes) { + return []; + } } -- cgit v1.2.3 From 7ce54864f3fbaaeade2ebc20d80bcb084d9fa426 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 9 Oct 2019 20:31:09 -0400 Subject: Show the viewer button for anki notes which already exist --- ext/bg/js/api.js | 22 ++++++++++++++++++++-- ext/mixed/js/display.js | 25 +++++++++++++++++++------ 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js index 222e7ffe..ed7171b5 100644 --- a/ext/bg/js/api.js +++ b/ext/bg/js/api.js @@ -97,15 +97,33 @@ async function apiDefinitionsAddable(definitions, modes, optionsContext) { } } - const results = await utilBackend().anki.canAddNotes(notes); + const cannotAdd = []; + const anki = utilBackend().anki; + const results = await anki.canAddNotes(notes); for (let resultBase = 0; resultBase < results.length; resultBase += modes.length) { const state = {}; for (let modeOffset = 0; modeOffset < modes.length; ++modeOffset) { - state[modes[modeOffset]] = results[resultBase + modeOffset]; + const index = resultBase + modeOffset; + const result = results[index]; + const info = {canAdd: result}; + state[modes[modeOffset]] = info; + if (!result) { + cannotAdd.push([notes[index], info]); + } } states.push(state); } + + if (cannotAdd.length > 0) { + const noteIdsArray = await anki.findNoteIds(cannotAdd.map(e => e[0])); + for (let i = 0, ii = Math.min(cannotAdd.length, noteIdsArray.length); i < ii; ++i) { + const noteIds = noteIdsArray[i]; + if (noteIds.length > 0) { + cannotAdd[i][1].noteId = noteIds[0]; + } + } + } } catch (e) { // NOP } diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index 9beb2c52..17370654 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -286,15 +286,23 @@ class Display { for (let i = 0; i < states.length; ++i) { const state = states[i]; + let noteId = null; for (const mode in state) { const button = this.adderButtonFind(i, mode); if (button === null) { continue; } - button.classList.toggle('disabled', !state[mode]); + const info = state[mode]; + if (!info.canAdd && noteId === null && info.noteId) { + noteId = info.noteId; + } + button.classList.toggle('disabled', !info.canAdd); button.classList.remove('pending'); } + if (noteId !== null) { + this.viewerButtonShow(i, noteId); + } } } catch (e) { this.onError(e); @@ -380,11 +388,7 @@ class Display { if (adderButton !== null) { adderButton.classList.add('disabled'); } - const viewerButton = this.viewerButtonFind(index); - if (viewerButton !== null) { - viewerButton.classList.remove('pending', 'disabled'); - viewerButton.dataset.noteId = noteId; - } + this.viewerButtonShow(index, noteId); } else { throw new Error('Note could not be added'); } @@ -504,6 +508,15 @@ class Display { return entry !== null ? entry.querySelector('.action-view-note') : null; } + viewerButtonShow(index, noteId) { + const viewerButton = this.viewerButtonFind(index); + if (viewerButton === null) { + return; + } + viewerButton.classList.remove('pending', 'disabled'); + viewerButton.dataset.noteId = noteId; + } + static delay(time) { return new Promise((resolve) => setTimeout(resolve, time)); } -- cgit v1.2.3 From 60a80418d7e2a695ddff538a03c263ab89935d5e Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 9 Oct 2019 21:54:58 -0400 Subject: Update how audio URLs are constructed --- ext/bg/js/audio.js | 133 +++++++++++++++++++++++++---------------------------- 1 file changed, 62 insertions(+), 71 deletions(-) diff --git a/ext/bg/js/audio.js b/ext/bg/js/audio.js index 44da51d1..de4e80f3 100644 --- a/ext/bg/js/audio.js +++ b/ext/bg/js/audio.js @@ -17,8 +17,8 @@ */ -async function audioBuildUrl(definition, mode, cache={}) { - if (mode === 'jpod101') { +const audioUrlBuilders = { + 'jpod101': async (definition) => { let kana = definition.reading; let kanji = definition.expression; @@ -35,84 +35,75 @@ async function audioBuildUrl(definition, mode, cache={}) { params.push(`kana=${encodeURIComponent(kana)}`); } - const url = `https://assets.languagepod101.com/dictionary/japanese/audiomp3.php?${params.join('&')}`; - return Promise.resolve(url); - } else if (mode === 'jpod101-alternate') { - return new Promise((resolve, reject) => { - const response = cache[definition.expression]; - if (response) { - resolve(response); - } else { - const data = { - post: 'dictionary_reference', - match_type: 'exact', - search_query: definition.expression - }; - - const params = []; - for (const key in data) { - params.push(`${encodeURIComponent(key)}=${encodeURIComponent(data[key])}`); - } - - const xhr = new XMLHttpRequest(); - xhr.open('POST', 'https://www.japanesepod101.com/learningcenter/reference/dictionary_post'); - xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); - xhr.addEventListener('error', () => reject(new Error('Failed to scrape audio data'))); - xhr.addEventListener('load', () => { - cache[definition.expression] = xhr.responseText; - resolve(xhr.responseText); - }); - - xhr.send(params.join('&')); - } - }).then(response => { - const dom = new DOMParser().parseFromString(response, 'text/html'); - for (const row of dom.getElementsByClassName('dc-result-row')) { - try { - const url = row.querySelector('audio>source[src]').getAttribute('src'); - const reading = row.getElementsByClassName('dc-vocab_kana').item(0).innerText; - if (url && reading && (!definition.reading || definition.reading === reading)) { - return audioUrlNormalize(url, 'https://www.japanesepod101.com', '/learningcenter/reference/'); - } - } catch (e) { - // NOP - } - } + return `https://assets.languagepod101.com/dictionary/japanese/audiomp3.php?${params.join('&')}`; + }, + 'jpod101-alternate': async (definition) => { + const response = await new Promise((resolve, reject) => { + const xhr = new XMLHttpRequest(); + xhr.open('POST', 'https://www.japanesepod101.com/learningcenter/reference/dictionary_post'); + xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); + xhr.addEventListener('error', () => reject(new Error('Failed to scrape audio data'))); + xhr.addEventListener('load', () => resolve(xhr.responseText)); + xhr.send(`post=dictionary_reference&match_type=exact&search_query=${encodeURIComponent(definition.expression)}`); }); - } else if (mode === 'jisho') { - return new Promise((resolve, reject) => { - const response = cache[definition.expression]; - if (response) { - resolve(response); - } else { - const xhr = new XMLHttpRequest(); - xhr.open('GET', `https://jisho.org/search/${definition.expression}`); - xhr.addEventListener('error', () => reject(new Error('Failed to scrape audio data'))); - xhr.addEventListener('load', () => { - cache[definition.expression] = xhr.responseText; - resolve(xhr.responseText); - }); - - xhr.send(); - } - }).then(response => { + + const dom = new DOMParser().parseFromString(response, 'text/html'); + for (const row of dom.getElementsByClassName('dc-result-row')) { try { - const dom = new DOMParser().parseFromString(response, 'text/html'); - const audio = dom.getElementById(`audio_${definition.expression}:${definition.reading}`); - if (audio) { - const url = audio.getElementsByTagName('source').item(0).getAttribute('src'); - if (url) { - return audioUrlNormalize(url, 'https://jisho.org', '/search/'); - } + const url = row.querySelector('audio>source[src]').getAttribute('src'); + const reading = row.getElementsByClassName('dc-vocab_kana').item(0).innerText; + if (url && reading && (!definition.reading || definition.reading === reading)) { + return audioUrlNormalize(url, 'https://www.japanesepod101.com', '/learningcenter/reference/'); } } catch (e) { // NOP } + } + + throw new Error('Failed to find audio URL'); + }, + 'jisho': async (definition) => { + const response = await new Promise((resolve, reject) => { + const xhr = new XMLHttpRequest(); + xhr.open('GET', `https://jisho.org/search/${definition.expression}`); + xhr.addEventListener('error', () => reject(new Error('Failed to scrape audio data'))); + xhr.addEventListener('load', () => resolve(xhr.responseText)); + xhr.send(); }); + + const dom = new DOMParser().parseFromString(response, 'text/html'); + try { + const audio = dom.getElementById(`audio_${definition.expression}:${definition.reading}`); + if (audio !== null) { + const url = audio.getElementsByTagName('source').item(0).getAttribute('src'); + if (url) { + return audioUrlNormalize(url, 'https://jisho.org', '/search/'); + } + } + } catch (e) { + // NOP + } + + throw new Error('Failed to find audio URL'); } - else { - return Promise.resolve(); +}; + +async function audioBuildUrl(definition, mode, cache={}) { + const cacheKey = `${mode}:${definition.expression}`; + if (cache.hasOwnProperty(cacheKey)) { + return Promise.resolve(cache[cacheKey]); + } + + if (audioUrlBuilders.hasOwnProperty(mode)) { + const handler = audioUrlBuilders[mode]; + return handler(definition).then( + (url) => { + cache[cacheKey] = url; + return url; + }, + () => null); } + return null; } function audioUrlNormalize(url, baseUrl, basePath) { -- cgit v1.2.3 From 8be0ddeb26ec5a7bf8d3044852754c4625a27230 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 9 Oct 2019 22:02:25 -0400 Subject: Create region for audio options --- ext/bg/settings.html | 42 +++++++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/ext/bg/settings.html b/ext/bg/settings.html index 0bc5e14c..bfbb7c7d 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -140,10 +140,6 @@
    -
    - -
    -
    @@ -161,16 +157,6 @@
    -
    - - -
    -
    -
    - - -
    -
    @@ -256,6 +237,29 @@
    +
    +

    Audio Options

    + +
    + +
    + +
    + + +
    + +
    + + +
    +
    +

    Scanning Options

    -- cgit v1.2.3 From 22b218d17238cc003938ef46403596c2183a9c1b Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 9 Oct 2019 22:03:56 -0400 Subject: Pass optionsContext to audioBuildUrl handlers --- ext/bg/js/api.js | 7 ++++--- ext/bg/js/audio.js | 8 ++++---- ext/bg/js/backend.js | 2 +- ext/fg/js/api.js | 4 ++-- ext/mixed/js/display.js | 2 +- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js index ed7171b5..d12104bf 100644 --- a/ext/bg/js/api.js +++ b/ext/bg/js/api.js @@ -68,7 +68,8 @@ async function apiDefinitionAdd(definition, mode, context, optionsContext) { await audioInject( definition, options.anki.terms.fields, - options.general.audioSource + options.general.audioSource, + optionsContext ); } @@ -174,8 +175,8 @@ apiCommandExec.handlers = { } }; -async function apiAudioGetUrl(definition, source) { - return audioBuildUrl(definition, source); +async function apiAudioGetUrl(definition, source, optionsContext) { + return audioBuildUrl(definition, source, optionsContext); } async function apiInjectScreenshot(definition, fields, screenshot) { diff --git a/ext/bg/js/audio.js b/ext/bg/js/audio.js index de4e80f3..36ce03cf 100644 --- a/ext/bg/js/audio.js +++ b/ext/bg/js/audio.js @@ -88,7 +88,7 @@ const audioUrlBuilders = { } }; -async function audioBuildUrl(definition, mode, cache={}) { +async function audioBuildUrl(definition, mode, optionsContext, cache={}) { const cacheKey = `${mode}:${definition.expression}`; if (cache.hasOwnProperty(cacheKey)) { return Promise.resolve(cache[cacheKey]); @@ -96,7 +96,7 @@ async function audioBuildUrl(definition, mode, cache={}) { if (audioUrlBuilders.hasOwnProperty(mode)) { const handler = audioUrlBuilders[mode]; - return handler(definition).then( + return handler(definition, optionsContext).then( (url) => { cache[cacheKey] = url; return url; @@ -138,7 +138,7 @@ function audioBuildFilename(definition) { } } -async function audioInject(definition, fields, mode) { +async function audioInject(definition, fields, mode, optionsContext) { let usesAudio = false; for (const name in fields) { if (fields[name].includes('{audio}')) { @@ -157,7 +157,7 @@ async function audioInject(definition, fields, mode) { audioSourceDefinition = definition.expressions[0]; } - const url = await audioBuildUrl(audioSourceDefinition, mode); + const url = await audioBuildUrl(audioSourceDefinition, mode, optionsContext); const filename = audioBuildFilename(audioSourceDefinition); if (url && filename) { diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index fb77b71e..453f4282 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -181,7 +181,7 @@ Backend.messageHandlers = { noteView: ({noteId}) => apiNoteView(noteId), templateRender: ({template, data, dynamic}) => apiTemplateRender(template, data, dynamic), commandExec: ({command}) => apiCommandExec(command), - audioGetUrl: ({definition, source}) => apiAudioGetUrl(definition, source), + audioGetUrl: ({definition, source, optionsContext}) => apiAudioGetUrl(definition, source, optionsContext), screenshotGet: ({options}, sender) => apiScreenshotGet(options, sender), forward: ({action, params}, sender) => apiForward(action, params, sender), frameInformationGet: (params, sender) => apiFrameInformationGet(sender), diff --git a/ext/fg/js/api.js b/ext/fg/js/api.js index d0ac649a..a553e514 100644 --- a/ext/fg/js/api.js +++ b/ext/fg/js/api.js @@ -45,8 +45,8 @@ function apiTemplateRender(template, data, dynamic) { return utilInvoke('templateRender', {data, template, dynamic}); } -function apiAudioGetUrl(definition, source) { - return utilInvoke('audioGetUrl', {definition, source}); +function apiAudioGetUrl(definition, source, optionsContext) { + return utilInvoke('audioGetUrl', {definition, source, optionsContext}); } function apiCommandExec(command) { diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index 17370654..78e6d8e3 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -404,7 +404,7 @@ class Display { this.setSpinnerVisible(true); const expression = expressionIndex === -1 ? definition : definition.expressions[expressionIndex]; - let url = await apiAudioGetUrl(expression, this.options.general.audioSource); + let url = await apiAudioGetUrl(expression, this.options.general.audioSource, this.optionsContext); if (!url) { url = '/mixed/mp3/button.mp3'; } -- cgit v1.2.3 From 8ae1da427756a9a1e057b3518c4069ac7d5b4b3a Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 9 Oct 2019 22:33:35 -0400 Subject: Update audio options format --- ext/bg/js/api.js | 2 +- ext/bg/js/audio.js | 4 ++-- ext/bg/js/options.js | 23 ++++++++++++++++++++--- ext/bg/js/settings.js | 14 ++++++++------ ext/bg/settings.html | 4 ++++ ext/mixed/js/display.js | 11 ++++++----- 6 files changed, 41 insertions(+), 17 deletions(-) diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js index d12104bf..f768e6f9 100644 --- a/ext/bg/js/api.js +++ b/ext/bg/js/api.js @@ -68,7 +68,7 @@ async function apiDefinitionAdd(definition, mode, context, optionsContext) { await audioInject( definition, options.anki.terms.fields, - options.general.audioSource, + options.audio.sources, optionsContext ); } diff --git a/ext/bg/js/audio.js b/ext/bg/js/audio.js index 36ce03cf..26896027 100644 --- a/ext/bg/js/audio.js +++ b/ext/bg/js/audio.js @@ -138,7 +138,7 @@ function audioBuildFilename(definition) { } } -async function audioInject(definition, fields, mode, optionsContext) { +async function audioInject(definition, fields, sources, optionsContext) { let usesAudio = false; for (const name in fields) { if (fields[name].includes('{audio}')) { @@ -157,7 +157,7 @@ async function audioInject(definition, fields, mode, optionsContext) { audioSourceDefinition = definition.expressions[0]; } - const url = await audioBuildUrl(audioSourceDefinition, mode, optionsContext); + const url = await audioBuildUrl(audioSourceDefinition, sources[0], optionsContext); const filename = audioBuildFilename(audioSourceDefinition); if (url && filename) { diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js index a2ab8877..d0aa6fd3 100644 --- a/ext/bg/js/options.js +++ b/ext/bg/js/options.js @@ -74,6 +74,18 @@ const profileOptionsVersionUpdates = [ if (utilStringHashCode(options.anki.fieldTemplates) === -250091611) { options.anki.fieldTemplates = profileOptionsGetDefaultFieldTemplates(); } + }, + (options) => { + const oldAudioSource = options.general.audioSource; + const disabled = oldAudioSource === 'disabled'; + options.audio.enabled = !disabled; + options.audio.volume = options.general.audioVolume; + options.audio.autoPlay = options.general.autoPlayAudio; + options.audio.sources = [disabled ? 'jpod101' : oldAudioSource]; + + delete options.general.audioSource; + delete options.general.audioVolume; + delete options.general.autoPlayAudio; } ]; @@ -247,9 +259,6 @@ function profileOptionsCreateDefaults() { return { general: { enable: true, - audioSource: 'jpod101', - audioVolume: 100, - autoPlayAudio: false, resultOutputMode: 'group', debugInfo: false, maxResults: 32, @@ -270,6 +279,14 @@ function profileOptionsCreateDefaults() { customPopupCss: '' }, + audio: { + enabled: true, + sources: ['jpod101', 'jpod101-alternate', 'jisho', 'custom'], + volume: 100, + autoPlay: false, + customSourceUrl: '' + }, + scanning: { middleMouse: true, touchInputEnabled: true, diff --git a/ext/bg/js/settings.js b/ext/bg/js/settings.js index 9838ea02..46521f1f 100644 --- a/ext/bg/js/settings.js +++ b/ext/bg/js/settings.js @@ -26,10 +26,7 @@ async function formRead(options) { options.general.showGuide = $('#show-usage-guide').prop('checked'); options.general.compactTags = $('#compact-tags').prop('checked'); options.general.compactGlossaries = $('#compact-glossaries').prop('checked'); - options.general.autoPlayAudio = $('#auto-play-audio').prop('checked'); options.general.resultOutputMode = $('#result-output-mode').val(); - options.general.audioSource = $('#audio-playback-source').val(); - options.general.audioVolume = parseFloat($('#audio-playback-volume').val()); options.general.debugInfo = $('#show-debug-info').prop('checked'); options.general.showAdvanced = $('#show-advanced-options').prop('checked'); options.general.maxResults = parseInt($('#max-displayed-results').val(), 10); @@ -44,6 +41,10 @@ async function formRead(options) { options.general.popupVerticalOffset2 = parseInt($('#popup-vertical-offset2').val(), 10); options.general.customPopupCss = $('#custom-popup-css').val(); + options.audio.enabled = $('#audio-playback-enabled').prop('checked'); + options.audio.autoPlay = $('#auto-play-audio').prop('checked'); + options.audio.volume = parseFloat($('#audio-playback-volume').val()); + options.scanning.middleMouse = $('#middle-mouse-button-scan').prop('checked'); options.scanning.touchInputEnabled = $('#touch-input-enabled').prop('checked'); options.scanning.selectText = $('#select-matched-text').prop('checked'); @@ -92,10 +93,7 @@ async function formWrite(options) { $('#show-usage-guide').prop('checked', options.general.showGuide); $('#compact-tags').prop('checked', options.general.compactTags); $('#compact-glossaries').prop('checked', options.general.compactGlossaries); - $('#auto-play-audio').prop('checked', options.general.autoPlayAudio); $('#result-output-mode').val(options.general.resultOutputMode); - $('#audio-playback-source').val(options.general.audioSource); - $('#audio-playback-volume').val(options.general.audioVolume); $('#show-debug-info').prop('checked', options.general.debugInfo); $('#show-advanced-options').prop('checked', options.general.showAdvanced); $('#max-displayed-results').val(options.general.maxResults); @@ -110,6 +108,10 @@ async function formWrite(options) { $('#popup-vertical-offset2').val(options.general.popupVerticalOffset2); $('#custom-popup-css').val(options.general.customPopupCss); + $('#audio-playback-enabled').prop('checked', options.audio.enabled); + $('#auto-play-audio').prop('checked', options.audio.autoPlay); + $('#audio-playback-volume').val(options.audio.volume); + $('#middle-mouse-button-scan').prop('checked', options.scanning.middleMouse); $('#touch-input-enabled').prop('checked', options.scanning.touchInputEnabled); $('#select-matched-text').prop('checked', options.scanning.selectText); diff --git a/ext/bg/settings.html b/ext/bg/settings.html index bfbb7c7d..f10e55b7 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -240,6 +240,10 @@

    Audio Options

    +
    + +
    +
    diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index 78e6d8e3..c1224084 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -189,7 +189,7 @@ class Display { addable: options.anki.enable, grouped: options.general.resultOutputMode === 'group', merged: options.general.resultOutputMode === 'merge', - playback: options.general.audioSource !== 'disabled', + playback: options.audio.enabled, compactGlossaries: options.general.compactGlossaries, debug: options.general.debugInfo }; @@ -209,7 +209,7 @@ class Display { const {index, scroll} = context || {}; this.entryScrollIntoView(index || 0, scroll); - if (this.options.general.autoPlayAudio && this.options.general.audioSource !== 'disabled') { + if (this.options.audio.enabled && this.options.audio.autoPlay) { this.autoPlayAudio(); } @@ -404,7 +404,7 @@ class Display { this.setSpinnerVisible(true); const expression = expressionIndex === -1 ? definition : definition.expressions[expressionIndex]; - let url = await apiAudioGetUrl(expression, this.options.general.audioSource, this.optionsContext); + let url = await apiAudioGetUrl(expression, this.options.audio.sources[0], this.optionsContext); if (!url) { url = '/mixed/mp3/button.mp3'; } @@ -413,10 +413,11 @@ class Display { this.audioCache[key].pause(); } + const volume = this.options.audio.volume / 100.0; let audio = this.audioCache[url]; if (audio) { audio.currentTime = 0; - audio.volume = this.options.general.audioVolume / 100.0; + audio.volume = volume; audio.play(); } else { audio = new Audio(url); @@ -426,7 +427,7 @@ class Display { } this.audioCache[url] = audio; - audio.volume = this.options.general.audioVolume / 100.0; + audio.volume = volume; audio.play(); }; } -- cgit v1.2.3 From 1d516b3b24eb6b89aa3a345341b6c1c35e24dfed Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Thu, 10 Oct 2019 19:58:06 -0400 Subject: Implement audio fallbacks --- ext/bg/background.html | 1 + ext/bg/js/audio.js | 12 +++++----- ext/bg/search.html | 2 +- ext/fg/float.html | 1 + ext/mixed/js/audio.js | 60 +++++++++++++++++++++++++++++++++++++++++++++++++ ext/mixed/js/display.js | 39 +++++++++++++------------------- 6 files changed, 86 insertions(+), 29 deletions(-) create mode 100644 ext/mixed/js/audio.js diff --git a/ext/bg/background.html b/ext/bg/background.html index 3b37db87..194d4a45 100644 --- a/ext/bg/background.html +++ b/ext/bg/background.html @@ -27,6 +27,7 @@ + diff --git a/ext/bg/js/audio.js b/ext/bg/js/audio.js index 26896027..0bf836df 100644 --- a/ext/bg/js/audio.js +++ b/ext/bg/js/audio.js @@ -136,6 +136,7 @@ function audioBuildFilename(definition) { return filename += '.mp3'; } + return null; } async function audioInject(definition, fields, sources, optionsContext) { @@ -157,11 +158,12 @@ async function audioInject(definition, fields, sources, optionsContext) { audioSourceDefinition = definition.expressions[0]; } - const url = await audioBuildUrl(audioSourceDefinition, sources[0], optionsContext); - const filename = audioBuildFilename(audioSourceDefinition); - - if (url && filename) { - definition.audio = {url, filename}; + const {url} = await audioGetFromSources(audioSourceDefinition, sources, optionsContext, false); + if (url !== null) { + const filename = audioBuildFilename(audioSourceDefinition); + if (filename !== null) { + definition.audio = {url, filename}; + } } return true; diff --git a/ext/bg/search.html b/ext/bg/search.html index e71824d3..3284ed43 100644 --- a/ext/bg/search.html +++ b/ext/bg/search.html @@ -36,7 +36,6 @@ - @@ -44,6 +43,7 @@ + diff --git a/ext/fg/float.html b/ext/fg/float.html index 52c7faa3..fe1aee8f 100644 --- a/ext/fg/float.html +++ b/ext/fg/float.html @@ -39,6 +39,7 @@ + diff --git a/ext/mixed/js/audio.js b/ext/mixed/js/audio.js new file mode 100644 index 00000000..b905140c --- /dev/null +++ b/ext/mixed/js/audio.js @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2019 Alex Yatskov + * Author: Alex Yatskov + * + * 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 . + */ + + +function audioGetFromUrl(url) { + return new Promise((resolve, reject) => { + const audio = new Audio(url); + audio.addEventListener('loadeddata', () => { + if (audio.duration === 5.694694 || audio.duration === 5.720718) { + // Hardcoded values for invalid audio + reject(new Error('Could not retrieve audio')); + } else { + resolve(audio); + } + }); + audio.addEventListener('error', () => reject(audio.error)); + }); +} + +async function audioGetFromSources(expression, sources, optionsContext, createAudioObject, cache=null) { + const key = `${expression.expression}:${expression.reading}`; + if (cache !== null && cache.hasOwnProperty(expression)) { + return cache[key]; + } + + for (let i = 0, ii = sources.length; i < ii; ++i) { + const source = sources[i]; + const url = await apiAudioGetUrl(expression, source, optionsContext); + if (url === null) { + continue; + } + + try { + const audio = createAudioObject ? await audioGetFromUrl(url) : null; + const result = {audio, url, source}; + if (cache !== null) { + cache[key] = result; + } + return result; + } catch (e) { + // NOP + } + } + return {audio: null, source: null}; +} diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index c1224084..8d4e1e68 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -26,6 +26,8 @@ class Display { this.context = null; this.sequence = 0; this.index = 0; + this.audioPlaying = null; + this.audioFallback = null; this.audioCache = {}; this.optionsContext = {}; this.eventListeners = []; @@ -404,33 +406,24 @@ class Display { this.setSpinnerVisible(true); const expression = expressionIndex === -1 ? definition : definition.expressions[expressionIndex]; - let url = await apiAudioGetUrl(expression, this.options.audio.sources[0], this.optionsContext); - if (!url) { - url = '/mixed/mp3/button.mp3'; - } - for (const key in this.audioCache) { - this.audioCache[key].pause(); + if (this.audioPlaying !== null) { + this.audioPlaying.pause(); + this.audioPlaying = null; } - const volume = this.options.audio.volume / 100.0; - let audio = this.audioCache[url]; - if (audio) { - audio.currentTime = 0; - audio.volume = volume; - audio.play(); - } else { - audio = new Audio(url); - audio.onloadeddata = () => { - if (audio.duration === 5.694694 || audio.duration === 5.720718) { - audio = new Audio('/mixed/mp3/button.mp3'); - } - - this.audioCache[url] = audio; - audio.volume = volume; - audio.play(); - }; + let {audio} = await audioGetFromSources(expression, this.options.audio.sources, this.optionsContext, true, this.audioCache); + if (audio === null) { + if (this.audioFallback === null) { + this.audioFallback = new Audio('/mixed/mp3/button.mp3'); + } + audio = this.audioFallback; } + + this.audioPlaying = audio; + audio.currentTime = 0; + audio.volume = this.options.audio.volume / 100.0; + audio.play(); } catch (e) { this.onError(e); } finally { -- cgit v1.2.3 From 50252ec627eb26515b5ad22a68e615726589aff1 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Thu, 10 Oct 2019 20:13:36 -0400 Subject: Update title with info about what the audio source was --- ext/mixed/js/display.js | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index 8d4e1e68..22181301 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -135,7 +135,7 @@ class Display { const entry = link.closest('.entry'); const definitionIndex = this.entryIndexFind(entry); const expressionIndex = Display.indexOf(entry.querySelectorAll('.expression .action-play-audio'), link); - this.audioPlay(this.definitions[definitionIndex], expressionIndex); + this.audioPlay(this.definitions[definitionIndex], expressionIndex, definitionIndex); } onNoteAdd(e) { @@ -276,7 +276,7 @@ class Display { } autoPlayAudio() { - this.audioPlay(this.definitions[0], this.firstExpressionIndex); + this.audioPlay(this.definitions[0], this.firstExpressionIndex, 0); } async adderButtonUpdate(modes, sequence) { @@ -401,7 +401,7 @@ class Display { } } - async audioPlay(definition, expressionIndex) { + async audioPlay(definition, expressionIndex, entryIndex) { try { this.setSpinnerVisible(true); @@ -412,12 +412,27 @@ class Display { this.audioPlaying = null; } - let {audio} = await audioGetFromSources(expression, this.options.audio.sources, this.optionsContext, true, this.audioCache); + const sources = this.options.audio.sources; + let {audio, source} = await audioGetFromSources(expression, sources, this.optionsContext, true, this.audioCache); + let info; if (audio === null) { if (this.audioFallback === null) { this.audioFallback = new Audio('/mixed/mp3/button.mp3'); } audio = this.audioFallback; + info = 'Could not find audio'; + } else { + info = `From source ${1 + sources.indexOf(source)}: ${source}`; + } + + const button = this.audioButtonFindImage(entryIndex); + if (button !== null) { + let titleDefault = button.dataset.titleDefault; + if (!titleDefault) { + titleDefault = button.title || ""; + button.dataset.titleDefault = titleDefault; + } + button.title = `${titleDefault}\n${info}`; } this.audioPlaying = audio; @@ -511,6 +526,11 @@ class Display { viewerButton.dataset.noteId = noteId; } + audioButtonFindImage(index) { + const entry = this.getEntry(index); + return entry !== null ? entry.querySelector('.action-play-audio>img') : null; + } + static delay(time) { return new Promise((resolve) => setTimeout(resolve, time)); } @@ -640,7 +660,7 @@ Display.onKeyDownHandlers = { if (e.altKey) { const entry = self.getEntry(self.index); if (entry !== null && entry.dataset.type === 'term') { - self.audioPlay(self.definitions[self.index], self.firstExpressionIndex); + self.audioPlay(self.definitions[self.index], self.firstExpressionIndex, self.index); } return true; } -- cgit v1.2.3 From 27c8430915af0a11a5f0c3216053cf3c5f090c50 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Thu, 10 Oct 2019 20:17:01 -0400 Subject: Implement custom audio source --- ext/bg/js/audio.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ext/bg/js/audio.js b/ext/bg/js/audio.js index 0bf836df..9e0ae67c 100644 --- a/ext/bg/js/audio.js +++ b/ext/bg/js/audio.js @@ -85,6 +85,11 @@ const audioUrlBuilders = { } throw new Error('Failed to find audio URL'); + }, + 'custom': async (definition, optionsContext) => { + const options = await apiOptionsGet(optionsContext); + const customSourceUrl = options.audio.customSourceUrl; + return customSourceUrl.replace(/\{([^\}]*)\}/g, (m0, m1) => (definition.hasOwnProperty(m1) ? `${definition[m1]}` : m0)); } }; -- cgit v1.2.3 From 1dc8bf77ca129555f0e56ef0f890105c14ab94ff Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Thu, 10 Oct 2019 20:26:31 -0400 Subject: Add input setting for custom audio source --- ext/bg/js/settings.js | 2 ++ ext/bg/settings.html | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/ext/bg/js/settings.js b/ext/bg/js/settings.js index 46521f1f..89ba046d 100644 --- a/ext/bg/js/settings.js +++ b/ext/bg/js/settings.js @@ -44,6 +44,7 @@ async function formRead(options) { options.audio.enabled = $('#audio-playback-enabled').prop('checked'); options.audio.autoPlay = $('#auto-play-audio').prop('checked'); options.audio.volume = parseFloat($('#audio-playback-volume').val()); + options.audio.customSourceUrl = $('#audio-custom-source').val(); options.scanning.middleMouse = $('#middle-mouse-button-scan').prop('checked'); options.scanning.touchInputEnabled = $('#touch-input-enabled').prop('checked'); @@ -111,6 +112,7 @@ async function formWrite(options) { $('#audio-playback-enabled').prop('checked', options.audio.enabled); $('#auto-play-audio').prop('checked', options.audio.autoPlay); $('#audio-playback-volume').val(options.audio.volume); + $('#audio-custom-source').val(options.audio.customSourceUrl); $('#middle-mouse-button-scan').prop('checked', options.scanning.middleMouse); $('#touch-input-enabled').prop('checked', options.scanning.touchInputEnabled); diff --git a/ext/bg/settings.html b/ext/bg/settings.html index f10e55b7..168dd040 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -253,6 +253,11 @@
    +
    + + +
    +
    -
    - - +
    + +
    +
    + +
    + +
    @@ -587,6 +596,7 @@ + -- cgit v1.2.3 From 55b2c1d8f51c658b0457ae8329fb1b0e52f5f799 Mon Sep 17 00:00:00 2001 From: Alex Yatskov Date: Thu, 10 Oct 2019 19:49:05 -0700 Subject: version bump --- ext/manifest.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/manifest.json b/ext/manifest.json index b52c1b25..927861bd 100644 --- a/ext/manifest.json +++ b/ext/manifest.json @@ -1,9 +1,9 @@ { "manifest_version": 2, "name": "Yomichan", - "version": "1.8.7", + "version": "1.8.8", - "description": "Japanese dictionary with Anki integration (testing)", + "description": "Japanese dictionary with Anki integration", "icons": {"16": "mixed/img/icon16.png", "48": "mixed/img/icon48.png", "128": "mixed/img/icon128.png"}, "browser_action": { "default_icon": {"19": "mixed/img/icon19.png", "38": "mixed/img/icon38.png"}, -- cgit v1.2.3