diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js index e968e8cf..e9ec2b85 100644 --- a/ext/bg/js/search.js +++ b/ext/bg/js/search.js @@ -33,10 +33,10 @@ class DisplaySearch extends Display { this._isPrepared = false; - this.optionsContext = { + this.setOptionsContext({ depth: 0, url: window.location.href - }; + }); this.queryParser = new QueryParser({ getOptionsContext: this.getOptionsContext.bind(this), @@ -80,12 +80,13 @@ class DisplaySearch extends Display { await this.updateOptions(); yomichan.on('optionsUpdated', () => this.updateOptions()); await this.queryParser.prepare(); + const options = this.getOptions(); const {queryParams: {query='', mode=''}} = parseUrl(window.location.href); document.documentElement.dataset.searchMode = mode; - if (this.options.general.enableWanakana === true) { + if (options.general.enableWanakana === true) { this.wanakanaEnable.checked = true; wanakana.bind(this.query); } else { @@ -96,7 +97,7 @@ class DisplaySearch extends Display { this.onSearchQueryUpdated(this.query.value, false); if (mode !== 'popup') { - if (this.options.general.enableClipboardMonitor === true) { + if (options.general.enableClipboardMonitor === true) { this.clipboardMonitorEnable.checked = true; this.clipboardMonitor.start(); } else { @@ -121,10 +122,6 @@ class DisplaySearch extends Display { this._isPrepared = true; } - onError(error) { - yomichan.logError(error); - } - onEscape() { if (this.query === null) { return; @@ -241,7 +238,7 @@ class DisplaySearch extends Display { url: window.location.href }}); } else { - this.container.textContent = ''; + this.clearContent(); } this.setTitleText(query); window.parent.postMessage('popupClose', '*'); @@ -299,7 +296,8 @@ class DisplaySearch extends Display { async updateOptions() { await super.updateOptions(); - this.queryParser.setOptions(this.options); + const options = this.getOptions(); + this.queryParser.setOptions(options); if (!this._isPrepared) { return; } const query = this.query.value; if (query) { diff --git a/ext/fg/js/float.js b/ext/fg/js/float.js index d7beb675..d86f16c3 100644 --- a/ext/fg/js/float.js +++ b/ext/fg/js/float.js @@ -31,7 +31,6 @@ class DisplayFloat extends Display { this._secret = yomichan.generateId(16); this._token = null; - this._orphaned = false; this._nestedPopupsPrepared = false; this._onKeyDownHandlers = new Map([ @@ -59,24 +58,11 @@ class DisplayFloat extends Display { async prepare() { await super.prepare(); - yomichan.on('orphaned', this.onOrphaned.bind(this)); window.addEventListener('message', this.onMessage.bind(this), false); api.broadcastTab('popupPrepared', {secret: this._secret}); } - onError(error) { - if (this._orphaned) { - this.setContent('orphaned'); - } else { - yomichan.logError(error); - } - } - - onOrphaned() { - this._orphaned = true; - } - onEscape() { window.parent.postMessage('popupClose', '*'); } @@ -126,7 +112,7 @@ class DisplayFloat extends Display { } async setOptionsContext(optionsContext) { - this.optionsContext = optionsContext; + super.setOptionsContext(optionsContext); await this.updateOptions(); } @@ -181,7 +167,7 @@ class DisplayFloat extends Display { } async _configure({messageId, frameId, popupId, optionsContext, childrenSupported, scale}) { - this.optionsContext = optionsContext; + this.setOptionsContext(optionsContext); await this.updateOptions(); @@ -208,7 +194,7 @@ class DisplayFloat extends Display { let complete = false; const onOptionsUpdated = async () => { - const optionsContext = this.optionsContext; + const optionsContext = this.getOptionsContext(); const options = await api.optionsGet(optionsContext); const maxPopupDepthExceeded = !(typeof depth === 'number' && depth < options.scanning.popupNestingMaxDepth); if (maxPopupDepthExceeded || complete) { return; } diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index e7439796..c8c574f4 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -29,16 +29,16 @@ class Display { constructor(spinner, container) { - this.spinner = spinner; - this.container = container; - this.definitions = []; - this.optionsContext = null; - this.options = null; - this.context = null; - this.index = 0; - this.audioPlaying = null; - this.audioFallback = null; - this.audioSystem = new AudioSystem({ + this._spinner = spinner; + this._container = container; + this._definitions = []; + this._optionsContext = null; + this._options = null; + this._context = null; + this._index = 0; + this._audioPlaying = null; + this._audioFallback = null; + this._audioSystem = new AudioSystem({ audioUriBuilder: { getUri: async (definition, source, details) => { return await api.audioGetUri(definition, source, details); @@ -46,18 +46,19 @@ class Display { }, useCache: true }); - this.styleNode = null; + this._styleNode = null; + this._orphaned = false; - this.eventListeners = new EventListenerCollection(); - this.persistentEventListeners = new EventListenerCollection(); - this.interactive = false; - this.eventListenersActive = false; - this.clickScanPrevent = false; - this.setContentToken = null; + this._eventListeners = new EventListenerCollection(); + this._persistentEventListeners = new EventListenerCollection(); + this._interactive = false; + this._eventListenersActive = false; + this._clickScanPrevent = false; + this._setContentToken = null; - this.mediaLoader = new MediaLoader(); - this.displayGenerator = new DisplayGenerator({mediaLoader: this.mediaLoader}); - this.windowScroll = new WindowScroll(); + this._mediaLoader = new MediaLoader(); + this._displayGenerator = new DisplayGenerator({mediaLoader: this._mediaLoader}); + this._windowScroll = new WindowScroll(); this._onKeyDownHandlers = new Map([ ['Escape', () => { @@ -66,89 +67,89 @@ class Display { }], ['PageUp', (e) => { if (e.altKey) { - this.entryScrollIntoView(this.index - 3, null, true); + this._entryScrollIntoView(this._index - 3, null, true); return true; } return false; }], ['PageDown', (e) => { if (e.altKey) { - this.entryScrollIntoView(this.index + 3, null, true); + this._entryScrollIntoView(this._index + 3, null, true); return true; } return false; }], ['End', (e) => { if (e.altKey) { - this.entryScrollIntoView(this.definitions.length - 1, null, true); + this._entryScrollIntoView(this._definitions.length - 1, null, true); return true; } return false; }], ['Home', (e) => { if (e.altKey) { - this.entryScrollIntoView(0, null, true); + this._entryScrollIntoView(0, null, true); return true; } return false; }], ['ArrowUp', (e) => { if (e.altKey) { - this.entryScrollIntoView(this.index - 1, null, true); + this._entryScrollIntoView(this._index - 1, null, true); return true; } return false; }], ['ArrowDown', (e) => { if (e.altKey) { - this.entryScrollIntoView(this.index + 1, null, true); + this._entryScrollIntoView(this._index + 1, null, true); return true; } return false; }], ['B', (e) => { if (e.altKey) { - this.sourceTermView(); + this._sourceTermView(); return true; } return false; }], ['F', (e) => { if (e.altKey) { - this.nextTermView(); + this._nextTermView(); return true; } return false; }], ['E', (e) => { if (e.altKey) { - this.noteTryAdd('term-kanji'); + this._noteTryAdd('term-kanji'); return true; } return false; }], ['K', (e) => { if (e.altKey) { - this.noteTryAdd('kanji'); + this._noteTryAdd('kanji'); return true; } return false; }], ['R', (e) => { if (e.altKey) { - this.noteTryAdd('term-kana'); + this._noteTryAdd('term-kana'); return true; } return false; }], ['P', (e) => { if (e.altKey) { - const index = this.index; - if (index < 0 || index >= this.definitions.length) { return; } + const index = this._index; + if (index < 0 || index >= this._definitions.length) { return; } - const entry = this.getEntry(index); + const entry = this._getEntry(index); if (entry !== null && entry.dataset.type === 'term') { - this.audioPlay(this.definitions[index], this.firstExpressionIndex, index); + this._audioPlay(this._definitions[index], this._getFirstExpressionIndex(), index); } return true; } @@ -156,52 +157,60 @@ class Display { }], ['V', (e) => { if (e.altKey) { - this.noteTryView(); + this._noteTryView(); return true; } return false; }] ]); - - this.setInteractive(true); } async prepare() { + this._setInteractive(true); await yomichan.ready(); - await this.displayGenerator.prepare(); + await this._displayGenerator.prepare(); + yomichan.on('orphaned', this._onOrphaned.bind(this)); } - onError(_error) { - throw new Error('Override me'); + _onOrphaned() { + this._orphaned = true; + } + + onError(error) { + if (this._orphaned) { + this.setContent('orphaned'); + } else { + yomichan.logError(error); + } } onEscape() { throw new Error('Override me'); } - onSourceTermView(e) { + _onSourceTermView(e) { e.preventDefault(); - this.sourceTermView(); + this._sourceTermView(); } - onNextTermView(e) { + _onNextTermView(e) { e.preventDefault(); - this.nextTermView(); + this._nextTermView(); } - async onKanjiLookup(e) { + async _onKanjiLookup(e) { try { e.preventDefault(); - if (!this.context) { return; } + if (!this._context) { return; } const link = e.target; - this.context.update({ - index: this.entryIndexFind(link), - scroll: this.windowScroll.y + this._context.update({ + index: this._entryIndexFind(link), + scroll: this._windowScroll.y }); const context = { - sentence: this.context.get('sentence'), - url: this.context.get('url') + sentence: this._context.get('sentence'), + url: this._context.get('url') }; const definitions = await api.kanjiFind(link.textContent, this.getOptionsContext()); @@ -211,53 +220,53 @@ class Display { } } - onGlossaryMouseDown(e) { + _onGlossaryMouseDown(e) { if (DOM.isMouseButtonPressed(e, 'primary')) { - this.clickScanPrevent = false; + this._clickScanPrevent = false; } } - onGlossaryMouseMove() { - this.clickScanPrevent = true; + _onGlossaryMouseMove() { + this._clickScanPrevent = true; } - onGlossaryMouseUp(e) { - if (!this.clickScanPrevent && DOM.isMouseButtonPressed(e, 'primary')) { - this.onTermLookup(e); + _onGlossaryMouseUp(e) { + if (!this._clickScanPrevent && DOM.isMouseButtonPressed(e, 'primary')) { + this._onTermLookup(e); } } - async onTermLookup(e, {disableScroll, selectText, disableHistory}={}) { + async _onTermLookup(e, {disableScroll, selectText, disableHistory}={}) { try { - if (!this.context) { return; } + if (!this._context) { return; } - const termLookupResults = await this.termLookup(e); + const termLookupResults = await this._termLookup(e); if (!termLookupResults) { return; } const {textSource, definitions} = termLookupResults; const scannedElement = e.target; - const sentenceExtent = this.options.anki.sentenceExt; - const layoutAwareScan = this.options.scanning.layoutAwareScan; + const sentenceExtent = this._options.anki.sentenceExt; + const layoutAwareScan = this._options.scanning.layoutAwareScan; const sentence = docSentenceExtract(textSource, sentenceExtent, layoutAwareScan); - this.context.update({ - index: this.entryIndexFind(scannedElement), - scroll: this.windowScroll.y + this._context.update({ + index: this._entryIndexFind(scannedElement), + scroll: this._windowScroll.y }); const context = { disableScroll, disableHistory, sentence, - url: this.context.get('url') + url: this._context.get('url') }; if (disableHistory) { Object.assign(context, { - previous: this.context.previous, - next: this.context.next + previous: this._context.previous, + next: this._context.next }); } else { Object.assign(context, { - previous: this.context + previous: this._context }); } @@ -271,11 +280,11 @@ class Display { } } - async termLookup(e) { + async _termLookup(e) { try { e.preventDefault(); - const {length: scanLength, deepDomScan: deepScan, layoutAwareScan} = this.options.scanning; + const {length: scanLength, deepDomScan: deepScan, layoutAwareScan} = this._options.scanning; const textSource = docRangeFromPoint(e.clientX, e.clientY, deepScan); if (textSource === null) { return false; @@ -301,32 +310,32 @@ class Display { } } - onAudioPlay(e) { + _onAudioPlay(e) { e.preventDefault(); const link = e.currentTarget; const entry = link.closest('.entry'); - const index = this.entryIndexFind(entry); - if (index < 0 || index >= this.definitions.length) { return; } + 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[index], + const expressionIndex = this._indexOf(entry.querySelectorAll('.term-expression .action-play-audio'), link); + this._audioPlay( + this._definitions[index], // expressionIndex is used in audioPlay to detect result output mode - Math.max(expressionIndex, this.options.general.resultOutputMode === 'merge' ? 0 : -1), + Math.max(expressionIndex, this._options.general.resultOutputMode === 'merge' ? 0 : -1), index ); } - onNoteAdd(e) { + _onNoteAdd(e) { e.preventDefault(); const link = e.currentTarget; - const index = this.entryIndexFind(link); - if (index < 0 || index >= this.definitions.length) { return; } + const index = this._entryIndexFind(link); + if (index < 0 || index >= this._definitions.length) { return; } - this.noteAdd(this.definitions[index], link.dataset.mode); + this._noteAdd(this._definitions[index], link.dataset.mode); } - onNoteView(e) { + _onNoteView(e) { e.preventDefault(); const link = e.currentTarget; api.noteView(link.dataset.noteId); @@ -344,43 +353,51 @@ class Display { return false; } - onWheel(e) { + _onWheel(e) { if (e.altKey) { if (e.deltaY !== 0) { - this.entryScrollIntoView(this.index + (e.deltaY > 0 ? 1 : -1), null, true); + this._entryScrollIntoView(this._index + (e.deltaY > 0 ? 1 : -1), null, true); e.preventDefault(); } } else if (e.shiftKey) { - this.onHistoryWheel(e); + this._onHistoryWheel(e); } } - onHistoryWheel(e) { + _onHistoryWheel(e) { if (e.altKey) { return; } const delta = -e.deltaX || e.deltaY; if (delta > 0) { - this.sourceTermView(); + this._sourceTermView(); e.preventDefault(); e.stopPropagation(); } else if (delta < 0) { - this.nextTermView(); + this._nextTermView(); e.preventDefault(); e.stopPropagation(); } } + getOptions() { + return this._options; + } + getOptionsContext() { - return this.optionsContext; + return this._optionsContext; + } + + setOptionsContext(optionsContext) { + this._optionsContext = optionsContext; } async updateOptions() { - this.options = await api.optionsGet(this.getOptionsContext()); - this.updateDocumentOptions(this.options); - this.updateTheme(this.options.general.popupTheme); - this.setCustomCss(this.options.general.customPopupCss); + this._options = await api.optionsGet(this.getOptionsContext()); + this._updateDocumentOptions(this._options); + this._updateTheme(this._options.general.popupTheme); + this.setCustomCss(this._options.general.customPopupCss); } - updateDocumentOptions(options) { + _updateDocumentOptions(options) { const data = document.documentElement.dataset; data.ankiEnabled = `${options.anki.enable}`; data.audioEnabled = `${options.audio.enabled}`; @@ -392,227 +409,227 @@ class Display { data.debug = `${options.general.debugInfo}`; } - updateTheme(themeName) { + _updateTheme(themeName) { document.documentElement.dataset.yomichanTheme = themeName; } setCustomCss(css) { - if (this.styleNode === null) { + if (this._styleNode === null) { if (css.length === 0) { return; } - this.styleNode = document.createElement('style'); + this._styleNode = document.createElement('style'); } - this.styleNode.textContent = css; + this._styleNode.textContent = css; const parent = document.head; - if (this.styleNode.parentNode !== parent) { - parent.appendChild(this.styleNode); + if (this._styleNode.parentNode !== parent) { + parent.appendChild(this._styleNode); } } - setInteractive(interactive) { + _setInteractive(interactive) { interactive = !!interactive; - if (this.interactive === interactive) { return; } - this.interactive = interactive; + if (this._interactive === interactive) { return; } + this._interactive = interactive; if (interactive) { 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}); + 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)); + this._persistentEventListeners.addEventListener(actionPrevious, 'click', this._onSourceTermView.bind(this)); } if (actionNext !== null) { - this.persistentEventListeners.addEventListener(actionNext, 'click', this.onNextTermView.bind(this)); + this._persistentEventListeners.addEventListener(actionNext, 'click', this._onNextTermView.bind(this)); } // temporarily disabled // if (navigationHeader !== null) { // this.persistentEventListeners.addEventListener(navigationHeader, 'wheel', this.onHistoryWheel.bind(this), {passive: false}); // } } else { - this.persistentEventListeners.removeAllEventListeners(); + this._persistentEventListeners.removeAllEventListeners(); } - this.setEventListenersActive(this.eventListenersActive); + this._setEventListenersActive(this._eventListenersActive); } - setEventListenersActive(active) { - active = !!active && this.interactive; - if (this.eventListenersActive === active) { return; } - this.eventListenersActive = active; + _setEventListenersActive(active) { + active = !!active && this._interactive; + if (this._eventListenersActive === active) { return; } + this._eventListenersActive = active; if (active) { - 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.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)); + 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.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 { - this.eventListeners.removeAllEventListeners(); + this._eventListeners.removeAllEventListeners(); } } addMultipleEventListeners(selector, type, listener, options) { - for (const node of this.container.querySelectorAll(selector)) { - this.eventListeners.addEventListener(node, type, listener, options); + for (const node of this._container.querySelectorAll(selector)) { + this._eventListeners.addEventListener(node, type, listener, options); } } async setContent(type, details) { const token = {}; // Unique identifier token - this.setContentToken = token; + this._setContentToken = token; try { - this.mediaLoader.unloadAll(); + this._mediaLoader.unloadAll(); switch (type) { case 'terms': - await this.setContentTerms(details.definitions, details.context, token); + await this._setContentTerms(details.definitions, details.context, token); break; case 'kanji': - await this.setContentKanji(details.definitions, details.context, token); + await this._setContentKanji(details.definitions, details.context, token); break; case 'orphaned': - this.setContentOrphaned(); + this._setContentOrphaned(); break; } } catch (e) { this.onError(e); } finally { - if (this.setContentToken === token) { - this.setContentToken = null; + if (this._setContentToken === token) { + this._setContentToken = null; } } } - async setContentTerms(definitions, context, token) { + async _setContentTerms(definitions, context, token) { if (!context) { throw new Error('Context expected'); } - this.setEventListenersActive(false); + this._setEventListenersActive(false); if (context.focus !== false) { window.focus(); } - this.definitions = definitions; + this._definitions = definitions; if (context.disableHistory) { delete context.disableHistory; - this.context = new DisplayContext('terms', definitions, context); + this._context = new DisplayContext('terms', definitions, context); } else { - this.context = DisplayContext.push(this.context, 'terms', definitions, context); + this._context = DisplayContext.push(this._context, 'terms', definitions, context); } for (const definition of definitions) { - definition.cloze = Display.clozeBuild(context.sentence, definition.source); + definition.cloze = this._clozeBuild(context.sentence, definition.source); definition.url = context.url; } - this.updateNavigation(this.context.previous, this.context.next); - this.setNoContentVisible(definitions.length === 0); + this._updateNavigation(this._context.previous, this._context.next); + this._setNoContentVisible(definitions.length === 0); - const container = this.container; + const container = this._container; container.textContent = ''; for (let i = 0, ii = definitions.length; i < ii; ++i) { if (i > 0) { await promiseTimeout(1); - if (this.setContentToken !== token) { return; } + if (this._setContentToken !== token) { return; } } - const entry = this.displayGenerator.createTermEntry(definitions[i]); + const entry = this._displayGenerator.createTermEntry(definitions[i]); container.appendChild(entry); } const {index, scroll, disableScroll} = context; if (!disableScroll) { - this.entryScrollIntoView(index || 0, scroll); + this._entryScrollIntoView(index || 0, scroll); } else { delete context.disableScroll; - this.entrySetCurrent(index || 0); + this._entrySetCurrent(index || 0); } - if (this.options.audio.enabled && this.options.audio.autoPlay) { + if (this._options.audio.enabled && this._options.audio.autoPlay) { this.autoPlayAudio(); } - this.setEventListenersActive(true); + this._setEventListenersActive(true); - const states = await this.getDefinitionsAddable(definitions, ['term-kanji', 'term-kana']); - if (this.setContentToken !== token) { return; } + const states = await this._getDefinitionsAddable(definitions, ['term-kanji', 'term-kana']); + if (this._setContentToken !== token) { return; } - this.updateAdderButtons(states); + this._updateAdderButtons(states); } - async setContentKanji(definitions, context, token) { + async _setContentKanji(definitions, context, token) { if (!context) { throw new Error('Context expected'); } - this.setEventListenersActive(false); + this._setEventListenersActive(false); if (context.focus !== false) { window.focus(); } - this.definitions = definitions; + this._definitions = definitions; if (context.disableHistory) { delete context.disableHistory; - this.context = new DisplayContext('kanji', definitions, context); + this._context = new DisplayContext('kanji', definitions, context); } else { - this.context = DisplayContext.push(this.context, 'kanji', definitions, context); + this._context = DisplayContext.push(this._context, 'kanji', definitions, context); } for (const definition of definitions) { - definition.cloze = Display.clozeBuild(context.sentence, definition.character); + definition.cloze = this._clozeBuild(context.sentence, definition.character); definition.url = context.url; } - this.updateNavigation(this.context.previous, this.context.next); - this.setNoContentVisible(definitions.length === 0); + this._updateNavigation(this._context.previous, this._context.next); + this._setNoContentVisible(definitions.length === 0); - const container = this.container; + const container = this._container; container.textContent = ''; for (let i = 0, ii = definitions.length; i < ii; ++i) { if (i > 0) { await promiseTimeout(1); - if (this.setContentToken !== token) { return; } + if (this._setContentToken !== token) { return; } } - const entry = this.displayGenerator.createKanjiEntry(definitions[i]); + const entry = this._displayGenerator.createKanjiEntry(definitions[i]); container.appendChild(entry); } const {index, scroll} = context; - this.entryScrollIntoView(index || 0, scroll); + this._entryScrollIntoView(index || 0, scroll); - this.setEventListenersActive(true); + this._setEventListenersActive(true); - const states = await this.getDefinitionsAddable(definitions, ['kanji']); - if (this.setContentToken !== token) { return; } + const states = await this._getDefinitionsAddable(definitions, ['kanji']); + if (this._setContentToken !== token) { return; } - this.updateAdderButtons(states); + this._updateAdderButtons(states); } - setContentOrphaned() { + _setContentOrphaned() { const errorOrphaned = document.querySelector('#error-orphaned'); - if (this.container !== null) { - this.container.hidden = true; + if (this._container !== null) { + this._container.hidden = true; } if (errorOrphaned !== null) { errorOrphaned.hidden = false; } - this.updateNavigation(null, null); - this.setNoContentVisible(false); + this._updateNavigation(null, null); + this._setNoContentVisible(false); } - setNoContentVisible(visible) { + _setNoContentVisible(visible) { const noResults = document.querySelector('#no-results'); if (noResults !== null) { @@ -620,7 +637,11 @@ class Display { } } - updateNavigation(previous, next) { + clearContent() { + this._container.textContent = ''; + } + + _updateNavigation(previous, next) { const navigation = document.querySelector('#navigation-header'); if (navigation !== null) { navigation.hidden = !(previous || next); @@ -630,16 +651,16 @@ class Display { } autoPlayAudio() { - if (this.definitions.length === 0) { return; } + if (this._definitions.length === 0) { return; } - this.audioPlay(this.definitions[0], this.firstExpressionIndex, 0); + this._audioPlay(this._definitions[0], this._getFirstExpressionIndex(), 0); } - updateAdderButtons(states) { + _updateAdderButtons(states) { for (let i = 0; i < states.length; ++i) { let noteId = null; for (const [mode, info] of Object.entries(states[i])) { - const button = this.adderButtonFind(i, mode); + const button = this._adderButtonFind(i, mode); if (button === null) { continue; } @@ -651,39 +672,39 @@ class Display { button.classList.remove('pending'); } if (noteId !== null) { - this.viewerButtonShow(i, noteId); + this._viewerButtonShow(i, noteId); } } } - entrySetCurrent(index) { - index = Math.min(index, this.definitions.length - 1); + _entrySetCurrent(index) { + index = Math.min(index, this._definitions.length - 1); index = Math.max(index, 0); - const entryPre = this.getEntry(this.index); + const entryPre = this._getEntry(this._index); if (entryPre !== null) { entryPre.classList.remove('entry-current'); } - const entry = this.getEntry(index); + const entry = this._getEntry(index); if (entry !== null) { entry.classList.add('entry-current'); } - this.index = index; + this._index = index; return entry; } - entryScrollIntoView(index, scroll, smooth) { - this.windowScroll.stop(); + _entryScrollIntoView(index, scroll, smooth) { + this._windowScroll.stop(); - const entry = this.entrySetCurrent(index); + const entry = this._entrySetCurrent(index); let target; if (scroll !== null) { target = scroll; } else { - target = this.index === 0 || entry === null ? 0 : Display.getElementTop(entry); + target = this._index === 0 || entry === null ? 0 : this._getElementTop(entry); const header = document.querySelector('#navigation-header'); if (header !== null) { @@ -692,19 +713,19 @@ class Display { } if (smooth) { - this.windowScroll.animate(this.windowScroll.x, target, 200); + this._windowScroll.animate(this._windowScroll.x, target, 200); } else { - this.windowScroll.toY(target); + this._windowScroll.toY(target); } } - sourceTermView() { - if (!this.context || !this.context.previous) { return; } - this.context.update({ - index: this.index, - scroll: this.windowScroll.y + _sourceTermView() { + if (!this._context || !this._context.previous) { return; } + this._context.update({ + index: this._index, + scroll: this._windowScroll.y }); - const previousContext = this.context.previous; + const previousContext = this._context.previous; previousContext.set('disableHistory', true); const details = { definitions: previousContext.definitions, @@ -713,13 +734,13 @@ class Display { this.setContent(previousContext.type, details); } - nextTermView() { - if (!this.context || !this.context.next) { return; } - this.context.update({ - index: this.index, - scroll: this.windowScroll.y + _nextTermView() { + if (!this._context || !this._context.next) { return; } + this._context.update({ + index: this._index, + scroll: this._windowScroll.y }); - const nextContext = this.context.next; + const nextContext = this._context.next; nextContext.set('disableHistory', true); const details = { definitions: nextContext.definitions, @@ -728,30 +749,30 @@ class Display { this.setContent(nextContext.type, details); } - noteTryAdd(mode) { - const index = this.index; - if (index < 0 || index >= this.definitions.length) { return; } + _noteTryAdd(mode) { + const index = this._index; + if (index < 0 || index >= this._definitions.length) { return; } - const button = this.adderButtonFind(index, mode); + const button = this._adderButtonFind(index, mode); if (button !== null && !button.classList.contains('disabled')) { - this.noteAdd(this.definitions[index], mode); + this._noteAdd(this._definitions[index], mode); } } - noteTryView() { - const button = this.viewerButtonFind(this.index); + _noteTryView() { + const button = this._viewerButtonFind(this._index); if (button !== null && !button.classList.contains('disabled')) { api.noteView(button.dataset.noteId); } } - async noteAdd(definition, mode) { + async _noteAdd(definition, mode) { try { this.setSpinnerVisible(true); const details = {}; - if (this.noteUsesScreenshot(mode)) { - const screenshot = await this.getScreenshot(); + if (this._noteUsesScreenshot(mode)) { + const screenshot = await this._getScreenshot(); if (screenshot) { details.screenshot = screenshot; } @@ -760,12 +781,12 @@ class Display { const context = await this._getNoteContext(); const noteId = await api.definitionAdd(definition, mode, context, details, this.getOptionsContext()); if (noteId) { - const index = this.definitions.indexOf(definition); - const adderButton = this.adderButtonFind(index, mode); + const index = this._definitions.indexOf(definition); + const adderButton = this._adderButtonFind(index, mode); if (adderButton !== null) { adderButton.classList.add('disabled'); } - this.viewerButtonShow(index, noteId); + this._viewerButtonShow(index, noteId); } else { throw new Error('Note could not be added'); } @@ -776,7 +797,7 @@ class Display { } } - async audioPlay(definition, expressionIndex, entryIndex) { + async _audioPlay(definition, expressionIndex, entryIndex) { try { this.setSpinnerVisible(true); @@ -786,19 +807,19 @@ class Display { let audio, info; try { - const {sources, textToSpeechVoice, customSourceUrl} = this.options.audio; + const {sources, textToSpeechVoice, customSourceUrl} = this._options.audio; let index; - ({audio, index} = await this.audioSystem.getDefinitionAudio(expression, sources, {textToSpeechVoice, customSourceUrl})); + ({audio, index} = await this._audioSystem.getDefinitionAudio(expression, sources, {textToSpeechVoice, customSourceUrl})); info = `From source ${1 + index}: ${sources[index]}`; } catch (e) { - if (this.audioFallback === null) { - this.audioFallback = new Audio('/mixed/mp3/button.mp3'); + if (this._audioFallback === null) { + this._audioFallback = new Audio('/mixed/mp3/button.mp3'); } - audio = this.audioFallback; + audio = this._audioFallback; info = 'Could not find audio'; } - const button = this.audioButtonFindImage(entryIndex, expressionIndex); + const button = this._audioButtonFindImage(entryIndex, expressionIndex); if (button !== null) { let titleDefault = button.dataset.titleDefault; if (!titleDefault) { @@ -810,8 +831,8 @@ class Display { this._stopPlayingAudio(); - const volume = Math.max(0.0, Math.min(1.0, this.options.audio.volume / 100.0)); - this.audioPlaying = audio; + const volume = Math.max(0.0, Math.min(1.0, this._options.audio.volume / 100.0)); + this._audioPlaying = audio; audio.currentTime = 0; audio.volume = Number.isFinite(volume) ? volume : 1.0; const playPromise = audio.play(); @@ -830,14 +851,14 @@ class Display { } _stopPlayingAudio() { - if (this.audioPlaying !== null) { - this.audioPlaying.pause(); - this.audioPlaying = null; + if (this._audioPlaying !== null) { + this._audioPlaying.pause(); + this._audioPlaying = null; } } - noteUsesScreenshot(mode) { - const optionsAnki = this.options.anki; + _noteUsesScreenshot(mode) { + const optionsAnki = this._options.anki; const fields = (mode === 'kanji' ? optionsAnki.kanji : optionsAnki.terms).fields; for (const fieldValue of Object.values(fields)) { if (fieldValue.includes('{screenshot}')) { @@ -847,41 +868,41 @@ class Display { return false; } - async getScreenshot() { + async _getScreenshot() { try { - await this.setPopupVisibleOverride(false); + await this._setPopupVisibleOverride(false); await promiseTimeout(1); // Wait for popup to be hidden. - const {format, quality} = this.options.anki.screenshot; + const {format, quality} = this._options.anki.screenshot; const dataUrl = await api.screenshotGet({format, quality}); if (!dataUrl || dataUrl.error) { return; } return {dataUrl, format}; } finally { - await this.setPopupVisibleOverride(null); + await this._setPopupVisibleOverride(null); } } - get firstExpressionIndex() { - return this.options.general.resultOutputMode === 'merge' ? 0 : -1; + _getFirstExpressionIndex() { + return this._options.general.resultOutputMode === 'merge' ? 0 : -1; } - setPopupVisibleOverride(visible) { + _setPopupVisibleOverride(visible) { return api.broadcastTab('popupSetVisibleOverride', {visible}); } setSpinnerVisible(visible) { - if (this.spinner !== null) { - this.spinner.hidden = !visible; + if (this._spinner !== null) { + this._spinner.hidden = !visible; } } - getEntry(index) { - const entries = this.container.querySelectorAll('.entry'); + _getEntry(index) { + const entries = this._container.querySelectorAll('.entry'); return index >= 0 && index < entries.length ? entries[index] : null; } - static clozeBuild({text, offset}, source) { + _clozeBuild({text, offset}, source) { return { sentence: text.trim(), prefix: text.substring(0, offset).trim(), @@ -890,23 +911,23 @@ class Display { }; } - entryIndexFind(element) { + _entryIndexFind(element) { const entry = element.closest('.entry'); - return entry !== null ? Display.indexOf(this.container.querySelectorAll('.entry'), entry) : -1; + return entry !== null ? this._indexOf(this._container.querySelectorAll('.entry'), entry) : -1; } - adderButtonFind(index, mode) { - const entry = this.getEntry(index); + _adderButtonFind(index, mode) { + const entry = this._getEntry(index); return entry !== null ? entry.querySelector(`.action-add-note[data-mode="${mode}"]`) : null; } - viewerButtonFind(index) { - const entry = this.getEntry(index); + _viewerButtonFind(index) { + const entry = this._getEntry(index); return entry !== null ? entry.querySelector('.action-view-note') : null; } - viewerButtonShow(index, noteId) { - const viewerButton = this.viewerButtonFind(index); + _viewerButtonShow(index, noteId) { + const viewerButton = this._viewerButtonFind(index); if (viewerButton === null) { return; } @@ -914,8 +935,8 @@ class Display { viewerButton.dataset.noteId = noteId; } - audioButtonFindImage(index, expressionIndex) { - const entry = this.getEntry(index); + _audioButtonFindImage(index, expressionIndex) { + const entry = this._getEntry(index); if (entry === null) { return null; } const container = ( @@ -926,7 +947,7 @@ class Display { return container !== null ? container.querySelector('.action-play-audio>img') : null; } - async getDefinitionsAddable(definitions, modes) { + async _getDefinitionsAddable(definitions, modes) { try { const context = await this._getNoteContext(); return await api.definitionsAddable(definitions, modes, context, this.getOptionsContext()); @@ -939,7 +960,7 @@ class Display { return document.title; } - static indexOf(nodeList, node) { + _indexOf(nodeList, node) { for (let i = 0, ii = nodeList.length; i < ii; ++i) { if (nodeList[i] === node) { return i; @@ -948,7 +969,7 @@ class Display { return -1; } - static getElementTop(element) { + _getElementTop(element) { const elementRect = element.getBoundingClientRect(); const documentRect = document.documentElement.getBoundingClientRect(); return elementRect.top - documentRect.top;