diff options
Diffstat (limited to 'ext/mixed/js')
-rw-r--r-- | ext/mixed/js/api.js | 6 | ||||
-rw-r--r-- | ext/mixed/js/audio.js | 9 | ||||
-rw-r--r-- | ext/mixed/js/core.js | 45 | ||||
-rw-r--r-- | ext/mixed/js/display-generator.js | 26 | ||||
-rw-r--r-- | ext/mixed/js/display.js | 131 | ||||
-rw-r--r-- | ext/mixed/js/text-scanner.js | 24 |
6 files changed, 121 insertions, 120 deletions
diff --git a/ext/mixed/js/api.js b/ext/mixed/js/api.js index 0b1e7e4f..86bdc73c 100644 --- a/ext/mixed/js/api.js +++ b/ext/mixed/js/api.js @@ -58,15 +58,15 @@ function apiDefinitionAdd(definition, mode, context, optionsContext) { } function apiDefinitionsAddable(definitions, modes, optionsContext) { - return _apiInvoke('definitionsAddable', {definitions, modes, optionsContext}).catch(() => null); + return _apiInvoke('definitionsAddable', {definitions, modes, optionsContext}); } function apiNoteView(noteId) { return _apiInvoke('noteView', {noteId}); } -function apiTemplateRender(template, data, dynamic) { - return _apiInvoke('templateRender', {data, template, dynamic}); +function apiTemplateRender(template, data) { + return _apiInvoke('templateRender', {data, template}); } function apiAudioGetUrl(definition, source, optionsContext) { diff --git a/ext/mixed/js/audio.js b/ext/mixed/js/audio.js index 76a3e7da..47db5c75 100644 --- a/ext/mixed/js/audio.js +++ b/ext/mixed/js/audio.js @@ -114,8 +114,11 @@ function audioGetFromUrl(url, willDownload) { async function audioGetFromSources(expression, sources, optionsContext, willDownload, cache=null) { const key = `${expression.expression}:${expression.reading}`; - if (cache !== null && hasOwn(cache, expression)) { - return cache[key]; + if (cache !== null) { + const cacheValue = cache.get(expression); + if (typeof cacheValue !== 'undefined') { + return cacheValue; + } } for (let i = 0, ii = sources.length; i < ii; ++i) { @@ -133,7 +136,7 @@ async function audioGetFromSources(expression, sources, optionsContext, willDown } const result = {audio, url, source}; if (cache !== null) { - cache[key] = result; + cache.set(key, result); } return result; } catch (e) { diff --git a/ext/mixed/js/core.js b/ext/mixed/js/core.js index ca9e98e5..330a30fb 100644 --- a/ext/mixed/js/core.js +++ b/ext/mixed/js/core.js @@ -113,11 +113,7 @@ function toIterable(value) { if (value !== null && typeof value === 'object') { const length = value.length; if (typeof length === 'number' && Number.isFinite(length)) { - const array = []; - for (let i = 0; i < length; ++i) { - array.push(value[i]); - } - return array; + return Array.from(value); } } @@ -240,6 +236,29 @@ class EventDispatcher { } } +class EventListenerCollection { + constructor() { + this._eventListeners = []; + } + + get size() { + return this._eventListeners.length; + } + + addEventListener(node, type, listener, options) { + node.addEventListener(type, listener, options); + this._eventListeners.push([node, type, listener, options]); + } + + removeAllEventListeners() { + if (this._eventListeners.length === 0) { return; } + for (const [node, type, listener, options] of this._eventListeners) { + node.removeEventListener(type, listener, options); + } + this._eventListeners = []; + } +} + /* * Default message handlers @@ -252,7 +271,7 @@ const yomichan = (() => { this._messageHandlers = new Map([ ['getUrl', this._onMessageGetUrl.bind(this)], - ['optionsUpdate', this._onMessageOptionsUpdate.bind(this)], + ['optionsUpdated', this._onMessageOptionsUpdated.bind(this)], ['zoomChanged', this._onMessageZoomChanged.bind(this)] ]); @@ -261,6 +280,16 @@ const yomichan = (() => { // Public + generateId(length) { + const array = new Uint8Array(length); + window.crypto.getRandomValues(array); + let id = ''; + for (const value of array) { + id += value.toString(16).padStart(2, '0'); + } + return id; + } + triggerOrphaned(error) { this.trigger('orphaned', {error}); } @@ -280,8 +309,8 @@ const yomichan = (() => { return {url: window.location.href}; } - _onMessageOptionsUpdate({source}) { - this.trigger('optionsUpdate', {source}); + _onMessageOptionsUpdated({source}) { + this.trigger('optionsUpdated', {source}); } _onMessageZoomChanged({oldZoomFactor, newZoomFactor}) { diff --git a/ext/mixed/js/display-generator.js b/ext/mixed/js/display-generator.js index 3617e546..46f3d17e 100644 --- a/ext/mixed/js/display-generator.js +++ b/ext/mixed/js/display-generator.js @@ -20,9 +20,6 @@ class DisplayGenerator { constructor() { - this._isInitialized = false; - this._initializationPromise = null; - this._termEntryTemplate = null; this._termExpressionTemplate = null; this._termDefinitionItemTemplate = null; @@ -41,18 +38,10 @@ class DisplayGenerator { this._tagFrequencyTemplate = null; } - isInitialized() { - return this._isInitialized; - } - - initialize() { - if (this._isInitialized) { - return Promise.resolve(); - } - if (this._initializationPromise === null) { - this._initializationPromise = this._initializeInternal(); - } - return this._initializationPromise; + async prepare() { + const html = await apiGetDisplayTemplatesHtml(); + const doc = new DOMParser().parseFromString(html, 'text/html'); + this._setTemplates(doc); } createTermEntry(details) { @@ -304,13 +293,6 @@ class DisplayGenerator { return node; } - async _initializeInternal() { - const html = await apiGetDisplayTemplatesHtml(); - const doc = new DOMParser().parseFromString(html, 'text/html'); - this._setTemplates(doc); - this._isInitialized = true; - } - _setTemplates(doc) { this._termEntryTemplate = doc.querySelector('#term-entry-template'); this._termExpressionTemplate = doc.querySelector('#term-expression-template'); diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index b18e275d..8113260c 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -32,11 +32,11 @@ class Display { this.index = 0; this.audioPlaying = null; this.audioFallback = null; - this.audioCache = {}; + this.audioCache = new Map(); this.styleNode = null; - this.eventListeners = []; - this.persistentEventListeners = []; + this.eventListeners = new EventListenerCollection(); + this.persistentEventListeners = new EventListenerCollection(); this.interactive = false; this.eventListenersActive = false; this.clickScanPrevent = false; @@ -48,6 +48,13 @@ class Display { this.setInteractive(true); } + async prepare(options=null) { + const displayGeneratorPromise = this.displayGenerator.prepare(); + const updateOptionsPromise = this.updateOptions(options); + await Promise.all([displayGeneratorPromise, updateOptionsPromise]); + yomichan.on('optionsUpdated', () => this.updateOptions(null)); + } + onError(_error) { throw new Error('Override me'); } @@ -179,13 +186,15 @@ class Display { e.preventDefault(); const link = e.currentTarget; const entry = link.closest('.entry'); - const definitionIndex = this.entryIndexFind(entry); + const index = this.entryIndexFind(entry); + if (index < 0 || index >= this.definitions.length) { return; } + const expressionIndex = Display.indexOf(entry.querySelectorAll('.term-expression .action-play-audio'), link); this.audioPlay( - this.definitions[definitionIndex], + this.definitions[index], // expressionIndex is used in audioPlay to detect result output mode Math.max(expressionIndex, this.options.general.resultOutputMode === 'merge' ? 0 : -1), - definitionIndex + index ); } @@ -193,6 +202,8 @@ class Display { e.preventDefault(); const link = e.currentTarget; const index = this.entryIndexFind(link); + if (index < 0 || index >= this.definitions.length) { return; } + this.noteAdd(this.definitions[index], link.dataset.mode); } @@ -243,15 +254,6 @@ class Display { throw new Error('Override me'); } - isInitialized() { - return this.options !== null; - } - - async initialize(options=null) { - await this.updateOptions(options); - yomichan.on('optionsUpdate', () => this.updateOptions(null)); - } - async updateOptions(options) { this.options = options ? options : await apiOptionsGet(this.getOptionsContext()); this.updateDocumentOptions(this.options); @@ -299,13 +301,23 @@ class Display { this.interactive = interactive; if (interactive) { - Display.addEventListener(this.persistentEventListeners, document, 'keydown', this.onKeyDown.bind(this), false); - Display.addEventListener(this.persistentEventListeners, document, 'wheel', this.onWheel.bind(this), {passive: false}); - Display.addEventListener(this.persistentEventListeners, document.querySelector('.action-previous'), 'click', this.onSourceTermView.bind(this)); - Display.addEventListener(this.persistentEventListeners, document.querySelector('.action-next'), 'click', this.onNextTermView.bind(this)); - Display.addEventListener(this.persistentEventListeners, document.querySelector('.navigation-header'), 'wheel', this.onHistoryWheel.bind(this), {passive: false}); + const actionPrevious = document.querySelector('.action-previous'); + const actionNext = document.querySelector('.action-next'); + const navigationHeader = document.querySelector('.navigation-header'); + + this.persistentEventListeners.addEventListener(document, 'keydown', this.onKeyDown.bind(this), false); + this.persistentEventListeners.addEventListener(document, 'wheel', this.onWheel.bind(this), {passive: false}); + if (actionPrevious !== null) { + this.persistentEventListeners.addEventListener(actionPrevious, 'click', this.onSourceTermView.bind(this)); + } + if (actionNext !== null) { + this.persistentEventListeners.addEventListener(actionNext, 'click', this.onNextTermView.bind(this)); + } + if (navigationHeader !== null) { + this.persistentEventListeners.addEventListener(navigationHeader, 'wheel', this.onHistoryWheel.bind(this), {passive: false}); + } } else { - Display.clearEventListeners(this.persistentEventListeners); + this.persistentEventListeners.removeAllEventListeners(); } this.setEventListenersActive(this.eventListenersActive); } @@ -316,23 +328,23 @@ class Display { this.eventListenersActive = active; if (active) { - this.addEventListeners('.action-add-note', 'click', this.onNoteAdd.bind(this)); - this.addEventListeners('.action-view-note', 'click', this.onNoteView.bind(this)); - this.addEventListeners('.action-play-audio', 'click', this.onAudioPlay.bind(this)); - this.addEventListeners('.kanji-link', 'click', this.onKanjiLookup.bind(this)); + this.addMultipleEventListeners('.action-add-note', 'click', this.onNoteAdd.bind(this)); + this.addMultipleEventListeners('.action-view-note', 'click', this.onNoteView.bind(this)); + this.addMultipleEventListeners('.action-play-audio', 'click', this.onAudioPlay.bind(this)); + this.addMultipleEventListeners('.kanji-link', 'click', this.onKanjiLookup.bind(this)); if (this.options.scanning.enablePopupSearch) { - this.addEventListeners('.term-glossary-item, .tag', 'mouseup', this.onGlossaryMouseUp.bind(this)); - this.addEventListeners('.term-glossary-item, .tag', 'mousedown', this.onGlossaryMouseDown.bind(this)); - this.addEventListeners('.term-glossary-item, .tag', 'mousemove', this.onGlossaryMouseMove.bind(this)); + this.addMultipleEventListeners('.term-glossary-item, .tag', 'mouseup', this.onGlossaryMouseUp.bind(this)); + this.addMultipleEventListeners('.term-glossary-item, .tag', 'mousedown', this.onGlossaryMouseDown.bind(this)); + this.addMultipleEventListeners('.term-glossary-item, .tag', 'mousemove', this.onGlossaryMouseMove.bind(this)); } } else { - Display.clearEventListeners(this.eventListeners); + this.eventListeners.removeAllEventListeners(); } } - addEventListeners(selector, type, listener, options) { + addMultipleEventListeners(selector, type, listener, options) { for (const node of this.container.querySelectorAll(selector)) { - Display.addEventListener(this.eventListeners, node, type, listener, options); + this.eventListeners.addEventListener(node, type, listener, options); } } @@ -362,7 +374,6 @@ class Display { async setContentTerms(definitions, context, token) { if (!context) { throw new Error('Context expected'); } - if (!this.isInitialized()) { return; } this.setEventListenersActive(false); @@ -370,11 +381,6 @@ class Display { window.focus(); } - if (!this.displayGenerator.isInitialized()) { - await this.displayGenerator.initialize(); - if (this.setContentToken !== token) { return; } - } - this.definitions = definitions; if (context.disableHistory) { delete context.disableHistory; @@ -418,7 +424,7 @@ class Display { this.setEventListenersActive(true); - const states = await apiDefinitionsAddable(definitions, ['term-kanji', 'term-kana'], this.getOptionsContext()); + const states = await this.getDefinitionsAddable(definitions, ['term-kanji', 'term-kana']); if (this.setContentToken !== token) { return; } this.updateAdderButtons(states); @@ -426,7 +432,6 @@ class Display { async setContentKanji(definitions, context, token) { if (!context) { throw new Error('Context expected'); } - if (!this.isInitialized()) { return; } this.setEventListenersActive(false); @@ -434,11 +439,6 @@ class Display { window.focus(); } - if (!this.displayGenerator.isInitialized()) { - await this.displayGenerator.initialize(); - if (this.setContentToken !== token) { return; } - } - this.definitions = definitions; if (context.disableHistory) { delete context.disableHistory; @@ -460,7 +460,7 @@ class Display { for (let i = 0, ii = definitions.length; i < ii; ++i) { if (i > 0) { - await promiseTimeout(0); + await promiseTimeout(1); if (this.setContentToken !== token) { return; } } @@ -473,7 +473,7 @@ class Display { this.setEventListenersActive(true); - const states = await apiDefinitionsAddable(definitions, ['kanji'], this.getOptionsContext()); + const states = await this.getDefinitionsAddable(definitions, ['kanji']); if (this.setContentToken !== token) { return; } this.updateAdderButtons(states); @@ -512,6 +512,8 @@ class Display { } autoPlayAudio() { + if (this.definitions.length === 0) { return; } + this.audioPlay(this.definitions[0], this.firstExpressionIndex, 0); } @@ -611,9 +613,12 @@ class Display { } noteTryAdd(mode) { - const button = this.adderButtonFind(this.index, mode); + const index = this.index; + if (index < 0 || index >= this.definitions.length) { return; } + + const button = this.adderButtonFind(index, mode); if (button !== null && !button.classList.contains('disabled')) { - this.noteAdd(this.definitions[this.index], mode); + this.noteAdd(this.definitions[index], mode); } } @@ -712,7 +717,7 @@ class Display { async getScreenshot() { try { await this.setPopupVisibleOverride(false); - await Display.delay(1); // Wait for popup to be hidden. + await promiseTimeout(1); // Wait for popup to be hidden. const {format, quality} = this.options.anki.screenshot; const dataUrl = await apiScreenshotGet({format, quality}); @@ -781,8 +786,12 @@ class Display { return entry !== null ? entry.querySelector('.action-play-audio>img') : null; } - static delay(time) { - return new Promise((resolve) => setTimeout(resolve, time)); + async getDefinitionsAddable(definitions, modes) { + try { + return await apiDefinitionsAddable(definitions, modes, this.getOptionsContext()); + } catch (e) { + return []; + } } static indexOf(nodeList, node) { @@ -794,19 +803,6 @@ class Display { return -1; } - static addEventListener(eventListeners, object, type, listener, options) { - if (object === null) { return; } - object.addEventListener(type, listener, options); - eventListeners.push([object, type, listener, options]); - } - - static clearEventListeners(eventListeners) { - for (const [object, type, listener, options] of eventListeners) { - object.removeEventListener(type, listener, options); - } - eventListeners.length = 0; - } - static getElementTop(element) { const elementRect = element.getBoundingClientRect(); const documentRect = document.documentElement.getBoundingClientRect(); @@ -915,9 +911,12 @@ Display._onKeyDownHandlers = new Map([ ['P', (self, e) => { if (e.altKey) { - const entry = self.getEntry(self.index); + const index = self.index; + if (index < 0 || index >= self.definitions.length) { return; } + + const entry = self.getEntry(index); if (entry !== null && entry.dataset.type === 'term') { - self.audioPlay(self.definitions[self.index], self.firstExpressionIndex, self.index); + self.audioPlay(self.definitions[index], self.firstExpressionIndex, index); } return true; } diff --git a/ext/mixed/js/text-scanner.js b/ext/mixed/js/text-scanner.js index e6da1e5e..aa10bbaf 100644 --- a/ext/mixed/js/text-scanner.js +++ b/ext/mixed/js/text-scanner.js @@ -31,7 +31,7 @@ class TextScanner { this.options = null; this.enabled = false; - this.eventListeners = []; + this.eventListeners = new EventListenerCollection(); this.primaryTouchIdentifier = null; this.preventNextContextMenu = false; @@ -229,7 +229,7 @@ class TextScanner { } } else { if (this.enabled) { - this.clearEventListeners(); + this.eventListeners.removeAllEventListeners(); this.enabled = false; } this.onSearchClear(false); @@ -237,13 +237,13 @@ class TextScanner { } hookEvents() { - let eventListeners = this.getMouseEventListeners(); + let eventListenerInfos = this.getMouseEventListeners(); if (this.options.scanning.touchInputEnabled) { - eventListeners = eventListeners.concat(this.getTouchEventListeners()); + eventListenerInfos = eventListenerInfos.concat(this.getTouchEventListeners()); } - for (const [node, type, listener, options] of eventListeners) { - this.addEventListener(node, type, listener, options); + for (const [node, type, listener, options] of eventListenerInfos) { + this.eventListeners.addEventListener(node, type, listener, options); } } @@ -268,18 +268,6 @@ class TextScanner { ]; } - 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 = []; - } - setOptions(options) { this.options = options; this.setEnabled(this.options.general.enable); |