From 1088c17503cd6f52019a094ac19f68b0e12ba007 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Tue, 6 Jul 2021 22:00:18 -0400 Subject: [PATCH] Add support for injecting dictionary media into Anki cards (#1805) --- ext/js/background/backend.js | 76 +++++++++++++++++++++++++++----- ext/js/comm/api.js | 4 +- ext/js/data/anki-note-builder.js | 26 ++++++++--- 3 files changed, 88 insertions(+), 18 deletions(-) diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js index 8ad6c19f..7f9fe7f8 100644 --- a/ext/js/background/backend.js +++ b/ext/js/background/backend.js @@ -490,14 +490,15 @@ class Backend { return results; } - async _onApiInjectAnkiNoteMedia({timestamp, definitionDetails, audioDetails, screenshotDetails, clipboardDetails}) { + async _onApiInjectAnkiNoteMedia({timestamp, definitionDetails, audioDetails, screenshotDetails, clipboardDetails, dictionaryMediaDetails}) { return await this._injectAnkNoteMedia( this._anki, timestamp, definitionDetails, audioDetails, screenshotDetails, - clipboardDetails + clipboardDetails, + dictionaryMediaDetails ); } @@ -1682,7 +1683,7 @@ class Backend { } } - async _injectAnkNoteMedia(ankiConnect, timestamp, definitionDetails, audioDetails, screenshotDetails, clipboardDetails) { + async _injectAnkNoteMedia(ankiConnect, timestamp, definitionDetails, audioDetails, screenshotDetails, clipboardDetails, dictionaryMediaDetails) { let screenshotFileName = null; let clipboardImageFileName = null; let clipboardText = null; @@ -1721,14 +1722,25 @@ class Backend { errors.push(serializeError(e)); } + let dictionaryMedia; + try { + let errors2; + ({results: dictionaryMedia, errors: errors2} = await this._injectAnkiNoteDictionaryMedia(ankiConnect, timestamp, definitionDetails, dictionaryMediaDetails)); + for (const error of errors2) { + errors.push(serializeError(error)); + } + } catch (e) { + dictionaryMedia = []; + errors.push(serializeError(e)); + } + return { - result: { - screenshotFileName, - clipboardImageFileName, - clipboardText, - audioFileName - }, - errors + screenshotFileName, + clipboardImageFileName, + clipboardText, + audioFileName, + dictionaryMedia, + errors: errors }; } @@ -1801,6 +1813,50 @@ class Backend { return fileName; } + async _injectAnkiNoteDictionaryMedia(ankiConnect, timestamp, definitionDetails, dictionaryMediaDetails) { + const targets = []; + const detailsList = []; + const detailsMap = new Map(); + for (const {dictionary, path} of dictionaryMediaDetails) { + const target = {dictionary, path}; + const details = {dictionary, path, media: null}; + const key = JSON.stringify(target); + targets.push(target); + detailsList.push(details); + detailsMap.set(key, details); + } + const mediaList = await this._dictionaryDatabase.getMedia(targets); + + for (const media of mediaList) { + const {dictionary, path} = media; + const key = JSON.stringify({dictionary, path}); + const details = detailsMap.get(key); + if (typeof details === 'undefined' || details.media !== null) { continue; } + details.media = media; + } + + const errors = []; + const results = []; + for (let i = 0, ii = detailsList.length; i < ii; ++i) { + const {dictionary, path, media} = detailsList[i]; + let fileName = null; + if (media !== null) { + const {content, mediaType} = media; + const extension = MediaUtil.getFileExtensionFromImageMediaType(mediaType); + fileName = this._generateAnkiNoteMediaFileName(`yomichan_dictionary_media_${i + 1}`, extension, timestamp, definitionDetails); + try { + await ankiConnect.storeMediaFile(fileName, content); + } catch (e) { + errors.push(e); + fileName = null; + } + } + results.push({dictionary, path, fileName}); + } + + return {results, errors}; + } + _generateAnkiNoteMediaFileName(prefix, extension, timestamp, definitionDetails) { let fileName = prefix; diff --git a/ext/js/comm/api.js b/ext/js/comm/api.js index 7e77180e..7c89f0c5 100644 --- a/ext/js/comm/api.js +++ b/ext/js/comm/api.js @@ -56,8 +56,8 @@ class API { return this._invoke('getAnkiNoteInfo', {notes, fetchAdditionalInfo}); } - injectAnkiNoteMedia(timestamp, definitionDetails, audioDetails, screenshotDetails, clipboardDetails) { - return this._invoke('injectAnkiNoteMedia', {timestamp, definitionDetails, audioDetails, screenshotDetails, clipboardDetails}); + injectAnkiNoteMedia(timestamp, definitionDetails, audioDetails, screenshotDetails, clipboardDetails, dictionaryMediaDetails) { + return this._invoke('injectAnkiNoteMedia', {timestamp, definitionDetails, audioDetails, screenshotDetails, clipboardDetails, dictionaryMediaDetails}); } noteView(noteId) { diff --git a/ext/js/data/anki-note-builder.js b/ext/js/data/anki-note-builder.js index c69d6741..65740254 100644 --- a/ext/js/data/anki-note-builder.js +++ b/ext/js/data/anki-note-builder.js @@ -283,7 +283,7 @@ class AnkiNoteBuilder { let injectScreenshot = false; let injectClipboardImage = false; let injectClipboardText = false; - const injectDictionaryMedia = []; + const dictionaryMediaDetails = []; for (const requirement of requirements) { const {type} = requirement; switch (type) { @@ -291,7 +291,12 @@ class AnkiNoteBuilder { case 'screenshot': injectScreenshot = true; break; case 'clipboardImage': injectClipboardImage = true; break; case 'clipboardText': injectClipboardText = true; break; - case 'dictionaryMedia': injectDictionaryMedia.push(requirement); break; + case 'dictionaryMedia': + { + const {dictionary, path} = requirement; + dictionaryMediaDetails.push({dictionary, path}); + } + break; } } @@ -318,17 +323,26 @@ class AnkiNoteBuilder { } // Inject media - // TODO : injectDictionaryMedia - const {result: {audioFileName, screenshotFileName, clipboardImageFileName, clipboardText}, errors} = await yomichan.api.injectAnkiNoteMedia( + const {audioFileName, screenshotFileName, clipboardImageFileName, clipboardText, dictionaryMedia: dictionaryMediaArray, errors} = await yomichan.api.injectAnkiNoteMedia( timestamp, dictionaryEntryDetails, audioDetails, screenshotDetails, - clipboardDetails + clipboardDetails, + dictionaryMediaDetails ); // Format results - const dictionaryMedia = {}; // TODO + const dictionaryMedia = {}; + for (const {dictionary, path, fileName} of dictionaryMediaArray) { + if (fileName === null) { continue; } + const dictionaryMedia2 = ( + Object.prototype.hasOwnProperty.call(dictionaryMedia, dictionary) ? + (dictionaryMedia[dictionary]) : + (dictionaryMedia[dictionary] = {}) + ); + dictionaryMedia2[path] = {fileName}; + } const media = { audio: (typeof audioFileName === 'string' ? {fileName: audioFileName} : null), screenshot: (typeof screenshotFileName === 'string' ? {fileName: screenshotFileName} : null),