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
This commit is contained in:
parent
c0a6849f98
commit
acb7ad32f3
@ -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 = [];
|
||||
|
@ -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
|
||||
});
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user