From acb7ad32f39c40b879400c9daa4bc8cd25585ba7 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 9 Sep 2020 16:57:35 -0400 Subject: Anki media injection move (#793) * Update AnkiNoteBuilder to not store a reference to an AniConnect instance * Use more consistent details format * Organize options assignment * Move media injection * Inject images before injecting audio * Make functions private * Make static functions private --- ext/bg/js/anki-note-builder.js | 64 ++++++++++++++++++++++++++++-------------- ext/bg/js/backend.js | 49 +++++++++++--------------------- 2 files changed, 59 insertions(+), 54 deletions(-) (limited to 'ext/bg/js') diff --git a/ext/bg/js/anki-note-builder.js b/ext/bg/js/anki-note-builder.js index c5f87cb8..b46bf3ba 100644 --- a/ext/bg/js/anki-note-builder.js +++ b/ext/bg/js/anki-note-builder.js @@ -20,8 +20,7 @@ */ class AnkiNoteBuilder { - constructor({anki, audioSystem, renderTemplate, getClipboardImage=null, getScreenshot=null}) { - this._anki = anki; + constructor({audioSystem, renderTemplate, getClipboardImage=null, getScreenshot=null}) { this._audioSystem = audioSystem; this._renderTemplate = renderTemplate; this._getClipboardImage = getClipboardImage; @@ -29,6 +28,7 @@ class AnkiNoteBuilder { } async createNote({ + anki=null, definition, mode, context, @@ -38,8 +38,15 @@ class AnkiNoteBuilder { resultOutputMode='split', compactGlossaries=false, modeOptions: {fields, deck, model}, + audioDetails=null, + screenshotDetails=null, + clipboardImage=false, errors=null }) { + if (anki !== null) { + await this._injectMedia(anki, definition, fields, mode, audioDetails, screenshotDetails, clipboardImage); + } + const fieldEntries = Object.entries(fields); const noteFields = {}; const note = { @@ -50,10 +57,10 @@ class AnkiNoteBuilder { options: {duplicateScope} }; - const data = this.createNoteData(definition, mode, context, resultOutputMode, compactGlossaries); + const data = this._createNoteData(definition, mode, context, resultOutputMode, compactGlossaries); const formattedFieldValuePromises = []; for (const [, fieldValue] of fieldEntries) { - const formattedFieldValuePromise = this.formatField(fieldValue, data, templates, errors); + const formattedFieldValuePromise = this._formatField(fieldValue, data, templates, errors); formattedFieldValuePromises.push(formattedFieldValuePromise); } @@ -67,7 +74,9 @@ class AnkiNoteBuilder { return note; } - createNoteData(definition, mode, context, resultOutputMode, compactGlossaries) { + // Private + + _createNoteData(definition, mode, context, resultOutputMode, compactGlossaries) { const pitches = DictionaryDataUtil.getPitchAccentInfos(definition); const pitchCount = pitches.reduce((i, v) => i + v.pitches.length, 0); return { @@ -85,9 +94,9 @@ class AnkiNoteBuilder { }; } - async formatField(field, data, templates, errors=null) { + async _formatField(field, data, templates, errors=null) { const pattern = /\{([\w-]+)\}/g; - return await AnkiNoteBuilder.stringReplaceAsync(field, pattern, async (g0, marker) => { + return await this._stringReplaceAsync(field, pattern, async (g0, marker) => { try { return await this._renderTemplate(templates, data, marker); } catch (e) { @@ -97,16 +106,29 @@ class AnkiNoteBuilder { }); } - async injectAudio(definition, fields, sources, customSourceUrl) { + async _injectMedia(anki, definition, fields, mode, audioDetails, screenshotDetails, clipboardImage) { + if (screenshotDetails !== null) { + await this._injectScreenshot(anki, definition, fields, screenshotDetails); + } + if (clipboardImage) { + await this._injectClipboardImage(anki, definition, fields); + } + if (mode !== 'kanji' && audioDetails !== null) { + await this._injectAudio(anki, definition, fields, audioDetails); + } + } + + async _injectAudio(anki, definition, fields, details) { if (!this._containsMarker(fields, 'audio')) { return; } try { + const {sources, customSourceUrl} = details; const expressions = definition.expressions; const audioSourceDefinition = Array.isArray(expressions) ? expressions[0] : definition; let fileName = this._createInjectedAudioFileName(audioSourceDefinition); if (fileName === null) { return; } - fileName = AnkiNoteBuilder.replaceInvalidFileNameCharacters(fileName); + fileName = this._replaceInvalidFileNameCharacters(fileName); const {audio} = await this._audioSystem.getDefinitionAudio( audioSourceDefinition, @@ -119,8 +141,8 @@ class AnkiNoteBuilder { } ); - const data = AnkiNoteBuilder.arrayBufferToBase64(audio); - await this._anki.storeMediaFile(fileName, data); + const data = this._arrayBufferToBase64(audio); + await anki.storeMediaFile(fileName, data); definition.audioFileName = fileName; } catch (e) { @@ -128,14 +150,14 @@ class AnkiNoteBuilder { } } - async injectScreenshot(definition, fields, screenshot) { + async _injectScreenshot(anki, definition, fields, details) { if (!this._containsMarker(fields, 'screenshot')) { return; } const reading = definition.reading; const now = new Date(Date.now()); try { - const {windowId, tabId, ownerFrameId, format, quality} = screenshot; + const {windowId, tabId, ownerFrameId, format, quality} = details; const dataUrl = await this._getScreenshot(windowId, tabId, ownerFrameId, format, quality); const {mediaType, data} = this._getDataUrlInfo(dataUrl); @@ -143,9 +165,9 @@ class AnkiNoteBuilder { if (extension === null) { return; } let fileName = `yomichan_browser_screenshot_${reading}_${this._dateToString(now)}.${extension}`; - fileName = AnkiNoteBuilder.replaceInvalidFileNameCharacters(fileName); + fileName = this._replaceInvalidFileNameCharacters(fileName); - await this._anki.storeMediaFile(fileName, data); + await anki.storeMediaFile(fileName, data); definition.screenshotFileName = fileName; } catch (e) { @@ -153,7 +175,7 @@ class AnkiNoteBuilder { } } - async injectClipboardImage(definition, fields) { + async _injectClipboardImage(anki, definition, fields) { if (!this._containsMarker(fields, 'clipboard-image')) { return; } const reading = definition.reading; @@ -168,9 +190,9 @@ class AnkiNoteBuilder { if (extension === null) { return; } let fileName = `yomichan_clipboard_image_${reading}_${this._dateToString(now)}.${extension}`; - fileName = AnkiNoteBuilder.replaceInvalidFileNameCharacters(fileName); + fileName = this._replaceInvalidFileNameCharacters(fileName); - await this._anki.storeMediaFile(fileName, data); + await anki.storeMediaFile(fileName, data); definition.clipboardImageFileName = fileName; } catch (e) { @@ -233,16 +255,16 @@ class AnkiNoteBuilder { } } - static replaceInvalidFileNameCharacters(fileName) { + _replaceInvalidFileNameCharacters(fileName) { // eslint-disable-next-line no-control-regex return fileName.replace(/[<>:"/\\|?*\x00-\x1F]/g, '-'); } - static arrayBufferToBase64(arrayBuffer) { + _arrayBufferToBase64(arrayBuffer) { return btoa(String.fromCharCode(...new Uint8Array(arrayBuffer))); } - static stringReplaceAsync(str, regex, replacer) { + _stringReplaceAsync(str, regex, replacer) { let match; let index = 0; const parts = []; diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index 47e68072..df979c6f 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -58,7 +58,6 @@ class Backend { useCache: false }); this._ankiNoteBuilder = new AnkiNoteBuilder({ - anki: this._anki, audioSystem: this._audioSystem, renderTemplate: this._renderTemplate.bind(this), getClipboardImage: this._onApiClipboardImageGet.bind(this), @@ -446,33 +445,8 @@ class Backend { async _onApiDefinitionAdd({definition, mode, context, ownerFrameId, optionsContext}, sender) { const options = this.getOptions(optionsContext); const templates = this._getTemplates(options); - const fields = ( - mode === 'kanji' ? - options.anki.kanji.fields : - options.anki.terms.fields - ); - - if (mode !== 'kanji') { - const {customSourceUrl} = options.audio; - await this._ankiNoteBuilder.injectAudio( - definition, - fields, - options.audio.sources, - customSourceUrl - ); - } - - await this._ankiNoteBuilder.injectClipboardImage(definition, fields); - const {id: tabId, windowId} = (sender && sender.tab ? sender.tab : {}); - const {format, quality} = options.anki.screenshot; - await this._ankiNoteBuilder.injectScreenshot( - definition, - fields, - {windowId, tabId, ownerFrameId, format, quality} - ); - - const note = await this._createNote(definition, mode, context, options, templates); + const note = await this._createNote(definition, mode, context, options, templates, true, {windowId, tabId, ownerFrameId}); return this._anki.addNote(note); } @@ -485,7 +459,7 @@ class Backend { const notePromises = []; for (const definition of definitions) { for (const mode of modes) { - const notePromise = this._createNote(definition, mode, context, options, templates); + const notePromise = this._createNote(definition, mode, context, options, templates, false, null); notePromises.push(notePromise); } } @@ -1611,11 +1585,17 @@ class Backend { }); } - async _createNote(definition, mode, context, options, templates) { - const {general: {resultOutputMode, compactGlossaries}, anki: ankiOptions} = options; - const {tags, duplicateScope} = ankiOptions; - const modeOptions = (mode === 'kanji') ? ankiOptions.kanji : ankiOptions.terms; + async _createNote(definition, mode, context, options, templates, injectMedia, screenshotTarget) { + const { + general: {resultOutputMode, compactGlossaries}, + anki: {tags, duplicateScope, kanji, terms, screenshot: {format, quality}}, + audio: {sources, customSourceUrl} + } = options; + const modeOptions = (mode === 'kanji') ? kanji : terms; + const {windowId, tabId, ownerFrameId} = (isObject(screenshotTarget) ? screenshotTarget : {}); + return await this._ankiNoteBuilder.createNote({ + anki: injectMedia ? this._anki : null, definition, mode, context, @@ -1624,7 +1604,10 @@ class Backend { duplicateScope, resultOutputMode, compactGlossaries, - modeOptions + modeOptions, + audioDetails: {sources, customSourceUrl}, + screenshotDetails: {windowId, tabId, ownerFrameId, format, quality}, + clipboardImage: true }); } -- cgit v1.2.3