aboutsummaryrefslogtreecommitdiff
path: root/ext/js/data
diff options
context:
space:
mode:
authortoasted-nutbread <toasted-nutbread@users.noreply.github.com>2021-07-06 19:43:53 -0400
committerGitHub <noreply@github.com>2021-07-06 19:43:53 -0400
commite88d63fc6d251bc298eb721fee1cbb9f5f4b752e (patch)
treef24b38bd421da53f84ab6b47ddff3c6492d44087 /ext/js/data
parente15513208584764526e2348ca7796ea665925086 (diff)
Template renderer media updates (#1802)
* Add TemplateRendererMediaProvider to abstract media-related functionality * Update representation of injected media * Update templates * Update upgrade file * Update tests * Update test data * Force media to be an object * Update test data
Diffstat (limited to 'ext/js/data')
-rw-r--r--ext/js/data/anki-note-builder.js126
-rw-r--r--ext/js/data/anki-note-data-creator.js40
2 files changed, 124 insertions, 42 deletions
diff --git a/ext/js/data/anki-note-builder.js b/ext/js/data/anki-note-builder.js
index 6077eec1..c69d6741 100644
--- a/ext/js/data/anki-note-builder.js
+++ b/ext/js/data/anki-note-builder.js
@@ -37,12 +37,13 @@ class AnkiNoteBuilder {
modelName,
fields,
tags=[],
- injectedMedia=null,
+ requirements=[],
checkForDuplicates=true,
duplicateScope='collection',
resultOutputMode='split',
glossaryLayoutMode='default',
- compactTags=false
+ compactTags=false,
+ mediaOptions=null
}) {
let duplicateScopeDeckName = null;
let duplicateScopeCheckChildren = false;
@@ -52,7 +53,19 @@ class AnkiNoteBuilder {
duplicateScopeCheckChildren = true;
}
- const commonData = this._createData(dictionaryEntry, mode, context, resultOutputMode, glossaryLayoutMode, compactTags, injectedMedia);
+ const allErrors = [];
+ let media;
+ if (requirements.length > 0 && mediaOptions !== null) {
+ let errors;
+ ({media, errors} = await this._injectMedia(dictionaryEntry, requirements, mediaOptions));
+ for (const error of errors) {
+ allErrors.push(deserializeError(error));
+ }
+ } else {
+ media = {};
+ }
+
+ const commonData = this._createData(dictionaryEntry, mode, context, resultOutputMode, glossaryLayoutMode, compactTags, media);
const formattedFieldValuePromises = [];
for (const [, fieldValue] of fields) {
const formattedFieldValuePromise = this._formatField(fieldValue, commonData, template);
@@ -60,15 +73,14 @@ class AnkiNoteBuilder {
}
const formattedFieldValues = await Promise.all(formattedFieldValuePromises);
- const errors = [];
const uniqueRequirements = new Map();
const noteFields = {};
for (let i = 0, ii = fields.length; i < ii; ++i) {
const fieldName = fields[i][0];
- const {value, errors: fieldErrors, requirements} = formattedFieldValues[i];
+ const {value, errors: fieldErrors, requirements: fieldRequirements} = formattedFieldValues[i];
noteFields[fieldName] = value;
- errors.push(...fieldErrors);
- for (const requirement of requirements) {
+ allErrors.push(...fieldErrors);
+ for (const requirement of fieldRequirements) {
const key = JSON.stringify(requirement);
if (uniqueRequirements.has(key)) { continue; }
uniqueRequirements.set(key, requirement);
@@ -89,7 +101,7 @@ class AnkiNoteBuilder {
}
}
};
- return {note, errors, requirements: [...uniqueRequirements.values()]};
+ return {note, errors: allErrors, requirements: [...uniqueRequirements.values()]};
}
async getRenderingData({
@@ -99,16 +111,42 @@ class AnkiNoteBuilder {
resultOutputMode='split',
glossaryLayoutMode='default',
compactTags=false,
- injectedMedia=null,
marker=null
}) {
- const commonData = this._createData(dictionaryEntry, mode, context, resultOutputMode, glossaryLayoutMode, compactTags, injectedMedia);
+ const commonData = this._createData(dictionaryEntry, mode, context, resultOutputMode, glossaryLayoutMode, compactTags, {});
return await this._templateRenderer.getModifiedData({marker, commonData}, 'ankiNote');
}
+ getDictionaryEntryDetailsForNote(dictionaryEntry) {
+ const {type} = dictionaryEntry;
+ if (type === 'kanji') {
+ const {character} = dictionaryEntry;
+ return {type, character};
+ }
+
+ const {headwords} = dictionaryEntry;
+ let bestIndex = -1;
+ for (let i = 0, ii = headwords.length; i < ii; ++i) {
+ const {term, reading, sources} = headwords[i];
+ for (const {deinflectedText} of sources) {
+ if (term === deinflectedText) {
+ bestIndex = i;
+ i = ii;
+ break;
+ } else if (reading === deinflectedText && bestIndex < 0) {
+ bestIndex = i;
+ break;
+ }
+ }
+ }
+
+ const {term, reading} = headwords[Math.max(0, bestIndex)];
+ return {type, term, reading};
+ }
+
// Private
- _createData(dictionaryEntry, mode, context, resultOutputMode, glossaryLayoutMode, compactTags, injectedMedia) {
+ _createData(dictionaryEntry, mode, context, resultOutputMode, glossaryLayoutMode, compactTags, media) {
return {
dictionaryEntry,
mode,
@@ -116,7 +154,7 @@ class AnkiNoteBuilder {
resultOutputMode,
glossaryLayoutMode,
compactTags,
- injectedMedia
+ media
};
}
@@ -236,4 +274,68 @@ class AnkiNoteBuilder {
}
}
}
+
+ async _injectMedia(dictionaryEntry, requirements, mediaOptions) {
+ const timestamp = Date.now();
+
+ // Parse requirements
+ let injectAudio = false;
+ let injectScreenshot = false;
+ let injectClipboardImage = false;
+ let injectClipboardText = false;
+ const injectDictionaryMedia = [];
+ for (const requirement of requirements) {
+ const {type} = requirement;
+ switch (type) {
+ case 'audio': injectAudio = true; break;
+ case 'screenshot': injectScreenshot = true; break;
+ case 'clipboardImage': injectClipboardImage = true; break;
+ case 'clipboardText': injectClipboardText = true; break;
+ case 'dictionaryMedia': injectDictionaryMedia.push(requirement); break;
+ }
+ }
+
+ // Generate request data
+ const dictionaryEntryDetails = this.getDictionaryEntryDetailsForNote(dictionaryEntry);
+ let audioDetails = null;
+ let screenshotDetails = null;
+ const clipboardDetails = {image: injectClipboardImage, text: injectClipboardText};
+ if (injectAudio && dictionaryEntryDetails.type !== 'kanji') {
+ const audioOptions = mediaOptions.audio;
+ if (typeof audioOptions === 'object' && audioOptions !== null) {
+ const {sources, preferredAudioIndex} = audioOptions;
+ audioDetails = {sources, preferredAudioIndex};
+ }
+ }
+ if (injectScreenshot) {
+ const screenshotOptions = mediaOptions.screenshot;
+ if (typeof screenshotOptions === 'object' && screenshotOptions !== null) {
+ const {format, quality, contentOrigin: {tabId, frameId}} = screenshotOptions;
+ if (typeof tabId === 'number' && typeof frameId === 'number') {
+ screenshotDetails = {tabId, frameId, format, quality};
+ }
+ }
+ }
+
+ // Inject media
+ // TODO : injectDictionaryMedia
+ const {result: {audioFileName, screenshotFileName, clipboardImageFileName, clipboardText}, errors} = await yomichan.api.injectAnkiNoteMedia(
+ timestamp,
+ dictionaryEntryDetails,
+ audioDetails,
+ screenshotDetails,
+ clipboardDetails
+ );
+
+ // Format results
+ const dictionaryMedia = {}; // TODO
+ const media = {
+ audio: (typeof audioFileName === 'string' ? {fileName: audioFileName} : null),
+ screenshot: (typeof screenshotFileName === 'string' ? {fileName: screenshotFileName} : null),
+ clipboardImage: (typeof clipboardImageFileName === 'string' ? {fileName: clipboardImageFileName} : null),
+ clipboardText: (typeof clipboardText === 'string' ? {text: clipboardText} : null),
+ dictionaryMedia
+ };
+ return {media, errors};
+ }
}
diff --git a/ext/js/data/anki-note-data-creator.js b/ext/js/data/anki-note-data-creator.js
index 6a6bfd36..3622e837 100644
--- a/ext/js/data/anki-note-data-creator.js
+++ b/ext/js/data/anki-note-data-creator.js
@@ -44,15 +44,16 @@ class AnkiNoteDataCreator {
glossaryLayoutMode,
compactTags,
context,
- injectedMedia=null
+ media
}) {
const self = this;
- const definition = this.createCachedValue(this._getDefinition.bind(this, dictionaryEntry, injectedMedia, context, resultOutputMode));
+ const definition = this.createCachedValue(this._getDefinition.bind(this, dictionaryEntry, context, resultOutputMode));
const uniqueExpressions = this.createCachedValue(this._getUniqueExpressions.bind(this, dictionaryEntry));
const uniqueReadings = this.createCachedValue(this._getUniqueReadings.bind(this, dictionaryEntry));
const context2 = this.createCachedValue(this._getPublicContext.bind(this, context));
const pitches = this.createCachedValue(this._getPitches.bind(this, dictionaryEntry));
const pitchCount = this.createCachedValue(this._getPitchCount.bind(this, pitches));
+ if (typeof media !== 'object' || media === null || Array.isArray(media)) { media = {}; }
const result = {
marker,
get definition() { return self.getCachedValue(definition); },
@@ -68,7 +69,8 @@ class AnkiNoteDataCreator {
get uniqueReadings() { return self.getCachedValue(uniqueReadings); },
get pitches() { return self.getCachedValue(pitches); },
get pitchCount() { return self.getCachedValue(pitchCount); },
- get context() { return self.getCachedValue(context2); }
+ get context() { return self.getCachedValue(context2); },
+ media
};
Object.defineProperty(result, 'dictionaryEntry', {
configurable: false,
@@ -178,29 +180,22 @@ class AnkiNoteDataCreator {
return pitches.reduce((i, v) => i + v.pitches.length, 0);
}
- _getDefinition(dictionaryEntry, injectedMedia, context, resultOutputMode) {
+ _getDefinition(dictionaryEntry, context, resultOutputMode) {
switch (dictionaryEntry.type) {
case 'term':
- return this._getTermDefinition(dictionaryEntry, injectedMedia, context, resultOutputMode);
+ return this._getTermDefinition(dictionaryEntry, context, resultOutputMode);
case 'kanji':
- return this._getKanjiDefinition(dictionaryEntry, injectedMedia, context);
+ return this._getKanjiDefinition(dictionaryEntry, context);
default:
return {};
}
}
- _getKanjiDefinition(dictionaryEntry, injectedMedia, context) {
+ _getKanjiDefinition(dictionaryEntry, context) {
const self = this;
const {character, dictionary, onyomi, kunyomi, definitions} = dictionaryEntry;
- const {
- screenshotFileName=null,
- clipboardImageFileName=null,
- clipboardText=null,
- audioFileName=null
- } = this._asObject(injectedMedia);
-
let {url} = this._asObject(context);
if (typeof url !== 'string') { url = ''; }
@@ -219,10 +214,6 @@ class AnkiNoteDataCreator {
get tags() { return self.getCachedValue(tags); },
get stats() { return self.getCachedValue(stats); },
get frequencies() { return self.getCachedValue(frequencies); },
- screenshotFileName,
- clipboardImageFileName,
- clipboardText,
- audioFileName,
url,
get cloze() { return self.getCachedValue(cloze); }
};
@@ -265,7 +256,7 @@ class AnkiNoteDataCreator {
return results;
}
- _getTermDefinition(dictionaryEntry, injectedMedia, context, resultOutputMode) {
+ _getTermDefinition(dictionaryEntry, context, resultOutputMode) {
const self = this;
let type = 'term';
@@ -276,13 +267,6 @@ class AnkiNoteDataCreator {
const {inflections, score, dictionaryIndex, dictionaryPriority, sourceTermExactMatchCount, definitions} = dictionaryEntry;
- const {
- screenshotFileName=null,
- clipboardImageFileName=null,
- clipboardText=null,
- audioFileName=null
- } = this._asObject(injectedMedia);
-
let {url} = this._asObject(context);
if (typeof url !== 'string') { url = ''; }
@@ -331,10 +315,6 @@ class AnkiNoteDataCreator {
get frequencies() { return self.getCachedValue(frequencies); },
get pitches() { return self.getCachedValue(pitches); },
sourceTermExactMatchCount,
- screenshotFileName,
- clipboardImageFileName,
- clipboardText,
- audioFileName,
url,
get cloze() { return self.getCachedValue(cloze); },
get furiganaSegments() { return self.getCachedValue(furiganaSegments); }