summaryrefslogtreecommitdiff
path: root/ext
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
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')
-rw-r--r--ext/data/templates/anki-field-templates-upgrade-v13.handlebars34
-rw-r--r--ext/data/templates/default-anki-field-templates.handlebars14
-rw-r--r--ext/js/data/anki-note-builder.js126
-rw-r--r--ext/js/data/anki-note-data-creator.js40
-rw-r--r--ext/js/display/display-anki.js95
-rw-r--r--ext/js/templates/template-renderer-media-provider.js116
-rw-r--r--ext/js/templates/template-renderer.js42
-rw-r--r--ext/template-renderer.html1
8 files changed, 320 insertions, 148 deletions
diff --git a/ext/data/templates/anki-field-templates-upgrade-v13.handlebars b/ext/data/templates/anki-field-templates-upgrade-v13.handlebars
index 04cc855a..ebf9069e 100644
--- a/ext/data/templates/anki-field-templates-upgrade-v13.handlebars
+++ b/ext/data/templates/anki-field-templates-upgrade-v13.handlebars
@@ -15,3 +15,37 @@
{{=======}}
{{#each glossary}}<li>{{#formatGlossary ../dictionary}}{{{.}}}{{/formatGlossary}}</li>{{/each}}
{{>>>>>>>}}
+
+{{<<<<<<<}}
+ {{~#if definition.audioFileName~}}
+ [sound:{{definition.audioFileName}}]
+ {{~/if~}}
+{{=======}}
+ {{~#if (hasMedia "audio")~}}
+ [sound:{{#getMedia "audio" format="fileName"}}{{/getMedia}}]
+ {{~/if~}}
+{{>>>>>>>}}
+
+{{<<<<<<<}}
+ <img src="{{definition.screenshotFileName}}" />
+{{=======}}
+ {{~#if (hasMedia "screenshot")~}}
+ <img src="{{#getMedia "screenshot" format="fileName"}}{{/getMedia}}" />
+ {{~/if~}}
+{{>>>>>>>}}
+
+{{<<<<<<<}}
+ {{~#if definition.clipboardImageFileName~}}
+ <img src="{{definition.clipboardImageFileName}}" />
+ {{~/if~}}
+{{=======}}
+ {{~#if (hasMedia "clipboardImage")~}}
+ <img src="{{#getMedia "clipboardImage" format="fileName"}}{{/getMedia}}" />
+ {{~/if~}}
+{{>>>>>>>}}
+
+{{<<<<<<<}}
+{{~#if definition.clipboardText~}}{{definition.clipboardText}}{{~/if~}}
+{{=======}}
+{{~#if (hasMedia "clipboardText")}}{{#getMedia "clipboardText" format="text"}}{{/getMedia}}{{/if~}}
+{{>>>>>>>}}
diff --git a/ext/data/templates/default-anki-field-templates.handlebars b/ext/data/templates/default-anki-field-templates.handlebars
index 67547732..62da796d 100644
--- a/ext/data/templates/default-anki-field-templates.handlebars
+++ b/ext/data/templates/default-anki-field-templates.handlebars
@@ -31,8 +31,8 @@
{{/inline}}
{{#*inline "audio"}}
- {{~#if definition.audioFileName~}}
- [sound:{{definition.audioFileName}}]
+ {{~#if (hasMedia "audio")~}}
+ [sound:{{#getMedia "audio" format="fileName"}}{{/getMedia}}]
{{~/if~}}
{{/inline}}
@@ -173,7 +173,9 @@
{{/inline}}
{{#*inline "screenshot"}}
- <img src="{{definition.screenshotFileName}}" />
+ {{~#if (hasMedia "screenshot")~}}
+ <img src="{{#getMedia "screenshot" format="fileName"}}{{/getMedia}}" />
+ {{~/if~}}
{{/inline}}
{{#*inline "document-title"}}
@@ -291,13 +293,13 @@
{{! End Pitch Accents }}
{{#*inline "clipboard-image"}}
- {{~#if definition.clipboardImageFileName~}}
- <img src="{{definition.clipboardImageFileName}}" />
+ {{~#if (hasMedia "clipboardImage")~}}
+ <img src="{{#getMedia "clipboardImage" format="fileName"}}{{/getMedia}}" />
{{~/if~}}
{{/inline}}
{{#*inline "clipboard-text"}}
- {{~#if definition.clipboardText~}}{{definition.clipboardText}}{{~/if~}}
+ {{~#if (hasMedia "clipboardText")}}{{#getMedia "clipboardText" format="text"}}{{/getMedia}}{{/if~}}
{{/inline}}
{{#*inline "conjugation"}}
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); }
diff --git a/ext/js/display/display-anki.js b/ext/js/display/display-anki.js
index b9fbb2bc..f5456ebb 100644
--- a/ext/js/display/display-anki.js
+++ b/ext/js/display/display-anki.js
@@ -100,7 +100,6 @@ class DisplayAnki {
resultOutputMode: this.resultOutputMode,
glossaryLayoutMode: this._glossaryLayoutMode,
compactTags: this._compactTags,
- injectedMedia: null,
marker: 'test'
});
} catch (e) {
@@ -119,7 +118,7 @@ class DisplayAnki {
let errors;
let requirements;
try {
- ({note: note, errors, requirements} = await this._createNote(dictionaryEntry, mode, false, []));
+ ({note: note, errors, requirements} = await this._createNote(dictionaryEntry, mode, []));
} catch (e) {
errors = [e];
}
@@ -237,33 +236,6 @@ class DisplayAnki {
};
}
- _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};
- }
-
async _updateDictionaryEntryDetails() {
const {dictionaryEntries} = this._display;
const token = {};
@@ -390,7 +362,7 @@ class DisplayAnki {
const progressIndicatorVisible = this._display.progressIndicatorVisible;
const overrideToken = progressIndicatorVisible.setOverride(true);
try {
- const {note, errors, requirements: outputRequirements} = await this._createNote(dictionaryEntry, mode, true, requirements);
+ const {note, errors, requirements: outputRequirements} = await this._createNote(dictionaryEntry, mode, requirements);
allErrors.push(...errors);
if (outputRequirements.length > 0) {
@@ -494,7 +466,7 @@ class DisplayAnki {
const modes = this._dictionaryEntryTypeModeMap.get(type);
if (typeof modes === 'undefined') { continue; }
for (const mode of modes) {
- const notePromise = this._createNote(dictionaryEntry, mode, false, []);
+ const notePromise = this._createNote(dictionaryEntry, mode, []);
notePromises.push(notePromise);
noteTargets.push({index: i, mode});
}
@@ -544,25 +516,18 @@ class DisplayAnki {
return results;
}
- async _createNote(dictionaryEntry, mode, injectMedia, _requirements) {
+ async _createNote(dictionaryEntry, mode, requirements) {
const context = this._noteContext;
const modeOptions = this._modeOptions.get(mode);
if (typeof modeOptions === 'undefined') { throw new Error(`Unsupported note type: ${mode}`); }
const template = this._ankiFieldTemplates;
const {deck: deckName, model: modelName} = modeOptions;
const fields = Object.entries(modeOptions.fields);
+ const contentOrigin = this._display.getContentOrigin();
+ const details = this._ankiNoteBuilder.getDictionaryEntryDetailsForNote(dictionaryEntry);
+ const audioDetails = (details.type === 'term' ? this._display.getAnkiNoteMediaAudioDetails(details.term, details.reading) : null);
- const errors = [];
- let injectedMedia = null;
- if (injectMedia) {
- let errors2;
- ({result: injectedMedia, errors: errors2} = await this._injectAnkiNoteMedia(dictionaryEntry, fields));
- for (const error of errors2) {
- errors.push(deserializeError(error));
- }
- }
-
- const {note, errors: createNoteErrors, requirements: outputRequirements} = await this._ankiNoteBuilder.createNote({
+ const {note, errors, requirements: outputRequirements} = await this._ankiNoteBuilder.createNote({
dictionaryEntry,
mode,
context,
@@ -576,45 +541,19 @@ class DisplayAnki {
resultOutputMode: this.resultOutputMode,
glossaryLayoutMode: this._glossaryLayoutMode,
compactTags: this._compactTags,
- injectedMedia,
- errors
+ mediaOptions: {
+ audio: audioDetails,
+ screenshot: {
+ format: this._screenshotFormat,
+ quality: this._screenshotQuality,
+ contentOrigin
+ }
+ },
+ requirements
});
- errors.push(...createNoteErrors);
return {note, errors, requirements: outputRequirements};
}
- async _injectAnkiNoteMedia(dictionaryEntry, fields) {
- const timestamp = Date.now();
-
- const dictionaryEntryDetails = this._getDictionaryEntryDetailsForNote(dictionaryEntry);
-
- const audioDetails = (
- dictionaryEntryDetails.type !== 'kanji' && AnkiUtil.fieldsObjectContainsMarker(fields, 'audio') ?
- this._display.getAnkiNoteMediaAudioDetails(dictionaryEntryDetails.term, dictionaryEntryDetails.reading) :
- null
- );
-
- const {tabId, frameId} = this._display.getContentOrigin();
- const screenshotDetails = (
- AnkiUtil.fieldsObjectContainsMarker(fields, 'screenshot') && typeof tabId === 'number' ?
- {tabId, frameId, format: this._screenshotFormat, quality: this._screenshotQuality} :
- null
- );
-
- const clipboardDetails = {
- image: AnkiUtil.fieldsObjectContainsMarker(fields, 'clipboard-image'),
- text: AnkiUtil.fieldsObjectContainsMarker(fields, 'clipboard-text')
- };
-
- return await yomichan.api.injectAnkiNoteMedia(
- timestamp,
- dictionaryEntryDetails,
- audioDetails,
- screenshotDetails,
- clipboardDetails
- );
- }
-
_getModes(isTerms) {
return isTerms ? ['term-kanji', 'term-kana'] : ['kanji'];
}
diff --git a/ext/js/templates/template-renderer-media-provider.js b/ext/js/templates/template-renderer-media-provider.js
new file mode 100644
index 00000000..db4a6d18
--- /dev/null
+++ b/ext/js/templates/template-renderer-media-provider.js
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2021 Yomichan Authors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+class TemplateRendererMediaProvider {
+ constructor() {
+ this._requirements = null;
+ }
+
+ get requirements() {
+ return this._requirements;
+ }
+
+ set requirements(value) {
+ this._requirements = value;
+ }
+
+ hasMedia(root, args, namedArgs) {
+ const {media} = root;
+ const data = this._getMediaData(media, args, namedArgs);
+ return (data !== null);
+ }
+
+ getMedia(root, args, namedArgs) {
+ const {media} = root;
+ const data = this._getMediaData(media, args, namedArgs);
+ if (data !== null) {
+ const {format} = namedArgs;
+ const result = this._getFormattedValue(data, format);
+ if (typeof result === 'string') { return result; }
+ }
+ const defaultValue = namedArgs.default;
+ return typeof defaultValue !== 'undefined' ? defaultValue : '';
+ }
+
+ // Private
+
+ _addRequirement(value) {
+ if (this._requirements === null) { return; }
+ this._requirements.push(value);
+ }
+
+ _getFormattedValue(data, format) {
+ switch (format) {
+ case 'fileName':
+ {
+ const {fileName} = data;
+ if (typeof fileName === 'string') { return fileName; }
+ }
+ break;
+ case 'text':
+ {
+ const {text} = data;
+ if (typeof text === 'string') { return text; }
+ }
+ break;
+ }
+ return null;
+ }
+
+ _getMediaData(media, args, namedArgs) {
+ const type = args[0];
+ switch (type) {
+ case 'audio': return this._getSimpleMediaData(media, 'audio');
+ case 'screenshot': return this._getSimpleMediaData(media, 'screenshot');
+ case 'clipboardImage': return this._getSimpleMediaData(media, 'clipboardImage');
+ case 'clipboardText': return this._getSimpleMediaData(media, 'clipboardText');
+ case 'dictionaryMedia': return this._getDictionaryMedia(media, args[1], namedArgs);
+ default: return null;
+ }
+ }
+
+ _getSimpleMediaData(media, type) {
+ const result = media[type];
+ if (typeof result === 'object' && result !== null) { return result; }
+ this._addRequirement({type});
+ return null;
+ }
+
+ _getDictionaryMedia(media, path, namedArgs) {
+ const {dictionaryMedia} = media;
+ const {dictionary} = namedArgs;
+ if (
+ typeof dictionaryMedia !== 'undefined' &&
+ typeof dictionary === 'string' &&
+ Object.prototype.hasOwnProperty.call(dictionaryMedia, dictionary)
+ ) {
+ const dictionaryMedia2 = dictionaryMedia[dictionary];
+ if (Object.prototype.hasOwnProperty.call(dictionaryMedia2, path)) {
+ const result = dictionaryMedia2[path];
+ if (typeof result === 'object' && result !== null) {
+ return result;
+ }
+ }
+ }
+ this._addRequirement({
+ type: 'dictionaryMedia',
+ dictionary,
+ path
+ });
+ return null;
+ }
+}
diff --git a/ext/js/templates/template-renderer.js b/ext/js/templates/template-renderer.js
index f9fbdeb5..02471c97 100644
--- a/ext/js/templates/template-renderer.js
+++ b/ext/js/templates/template-renderer.js
@@ -19,12 +19,14 @@
* DictionaryDataUtil
* Handlebars
* StructuredContentGenerator
+ * TemplateRendererMediaProvider
*/
class TemplateRenderer {
constructor(japaneseUtil, cssStyleApplier) {
this._japaneseUtil = japaneseUtil;
this._cssStyleApplier = cssStyleApplier;
+ this._mediaProvider = new TemplateRendererMediaProvider();
this._cache = new Map();
this._cacheMaxSize = 5;
this._helpersRegistered = false;
@@ -94,6 +96,7 @@ class TemplateRenderer {
try {
this._stateStack = [new Map()];
this._requirements = requirements;
+ this._mediaProvider.requirements = requirements;
this._cleanupCallbacks = cleanupCallbacks;
const result = instance(data).trim();
return {result, requirements};
@@ -101,6 +104,7 @@ class TemplateRenderer {
for (const callback of cleanupCallbacks) { callback(); }
this._stateStack = null;
this._requirements = null;
+ this._mediaProvider.requirements = null;
this._cleanupCallbacks = null;
}
}
@@ -162,7 +166,9 @@ class TemplateRenderer {
['join', this._join.bind(this)],
['concat', this._concat.bind(this)],
['pitchCategories', this._pitchCategories.bind(this)],
- ['formatGlossary', this._formatGlossary.bind(this)]
+ ['formatGlossary', this._formatGlossary.bind(this)],
+ ['hasMedia', this._hasMedia.bind(this)],
+ ['getMedia', this._getMedia.bind(this)]
];
for (const [name, helper] of helpers) {
@@ -563,33 +569,13 @@ class TemplateRenderer {
parentNode.replaceChild(fragment, textNode);
}
- _getDictionaryMedia(data, dictionary, path) {
- const {media} = data;
- if (typeof media === 'object' && media !== null && Object.prototype.hasOwnProperty.call(media, 'dictionaryMedia')) {
- const {dictionaryMedia} = media;
- if (typeof dictionaryMedia === 'object' && dictionaryMedia !== null && Object.prototype.hasOwnProperty.call(dictionaryMedia, dictionary)) {
- const dictionaryMedia2 = dictionaryMedia[dictionary];
- if (Object.prototype.hasOwnProperty.call(dictionaryMedia2, path)) {
- return dictionaryMedia2[path];
- }
- }
- }
- return null;
- }
-
_createStructuredContentGenerator(data) {
const mediaLoader = {
loadMedia: async (path, dictionary, onLoad, onUnload) => {
- const imageUrl = this._getDictionaryMedia(data, dictionary, path);
+ const imageUrl = this._mediaProvider.getMedia(data, ['dictionaryMedia', path], {dictionary, format: 'fileName', default: null});
if (imageUrl !== null) {
onLoad(imageUrl);
this._cleanupCallbacks.push(() => onUnload(true));
- } else {
- this._requirements.push({
- type: 'dictionaryMedia',
- dictionary,
- path
- });
}
}
};
@@ -619,4 +605,16 @@ class TemplateRenderer {
const node = structuredContentGenerator.createStructuredContent(content.content, dictionary);
return node !== null ? this._getHtml(node) : '';
}
+
+ _hasMedia(context, ...args) {
+ const ii = args.length - 1;
+ const options = args[ii];
+ return this._mediaProvider.hasMedia(options.data.root, args.slice(0, ii), options.hash);
+ }
+
+ _getMedia(context, ...args) {
+ const ii = args.length - 1;
+ const options = args[ii];
+ return this._mediaProvider.getMedia(options.data.root, args.slice(0, ii), options.hash);
+ }
}
diff --git a/ext/template-renderer.html b/ext/template-renderer.html
index f01b5b9a..a3812b37 100644
--- a/ext/template-renderer.html
+++ b/ext/template-renderer.html
@@ -24,6 +24,7 @@
<script src="/js/language/japanese-util.js"></script>
<script src="/js/templates/template-renderer.js"></script>
<script src="/js/templates/template-renderer-frame-api.js"></script>
+<script src="/js/templates/template-renderer-media-provider.js"></script>
<script src="/js/templates/template-renderer-frame-main.js"></script>