Use Anki classes directly in Display (#804)

* Add _getTemplates function

* Add template renderer to display pages

* Add AnkiNoteBuilder to Display

* Update AnkiTemplatesController to directly use TemplateRenderer

* Remove old APIs
This commit is contained in:
toasted-nutbread 2020-09-10 18:03:46 -04:00 committed by GitHub
parent 9ce682272c
commit a531618c48
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 136 additions and 123 deletions

View File

@ -24,7 +24,6 @@
<script src="/mixed/js/japanese.js"></script> <script src="/mixed/js/japanese.js"></script>
<script src="/bg/js/anki.js"></script> <script src="/bg/js/anki.js"></script>
<script src="/bg/js/anki-note-builder.js"></script>
<script src="/bg/js/backend.js"></script> <script src="/bg/js/backend.js"></script>
<script src="/bg/js/mecab.js"></script> <script src="/bg/js/mecab.js"></script>
<script src="/bg/js/audio-uri-builder.js"></script> <script src="/bg/js/audio-uri-builder.js"></script>
@ -36,7 +35,6 @@
<script src="/bg/js/options.js"></script> <script src="/bg/js/options.js"></script>
<script src="/bg/js/profile-conditions.js"></script> <script src="/bg/js/profile-conditions.js"></script>
<script src="/bg/js/request-builder.js"></script> <script src="/bg/js/request-builder.js"></script>
<script src="/bg/js/template-renderer.js"></script>
<script src="/bg/js/simple-dom-parser.js"></script> <script src="/bg/js/simple-dom-parser.js"></script>
<script src="/bg/js/text-source-map.js"></script> <script src="/bg/js/text-source-map.js"></script>
<script src="/bg/js/translator.js"></script> <script src="/bg/js/translator.js"></script>

View File

@ -17,7 +17,6 @@
/* global /* global
* AnkiConnect * AnkiConnect
* AnkiNoteBuilder
* AudioSystem * AudioSystem
* AudioUriBuilder * AudioUriBuilder
* ClipboardMonitor * ClipboardMonitor
@ -29,7 +28,6 @@
* OptionsUtil * OptionsUtil
* ProfileConditions * ProfileConditions
* RequestBuilder * RequestBuilder
* TemplateRenderer
* Translator * Translator
* jp * jp
*/ */
@ -57,10 +55,6 @@ class Backend {
requestBuilder: this._requestBuilder, requestBuilder: this._requestBuilder,
useCache: false useCache: false
}); });
this._ankiNoteBuilder = new AnkiNoteBuilder({
renderTemplate: this._renderTemplate.bind(this)
});
this._templateRenderer = new TemplateRenderer();
this._clipboardPasteTarget = null; this._clipboardPasteTarget = null;
this._clipboardPasteTargetInitialized = false; this._clipboardPasteTargetInitialized = false;
@ -94,10 +88,7 @@ class Backend {
['addAnkiNote', {async: true, contentScript: true, handler: this._onApiAddAnkiNote.bind(this)}], ['addAnkiNote', {async: true, contentScript: true, handler: this._onApiAddAnkiNote.bind(this)}],
['getAnkiNoteInfo', {async: true, contentScript: true, handler: this._onApiGetAnkiNoteInfo.bind(this)}], ['getAnkiNoteInfo', {async: true, contentScript: true, handler: this._onApiGetAnkiNoteInfo.bind(this)}],
['injectAnkiNoteMedia', {async: true, contentScript: true, handler: this._onApiInjectAnkiNoteMedia.bind(this)}], ['injectAnkiNoteMedia', {async: true, contentScript: true, handler: this._onApiInjectAnkiNoteMedia.bind(this)}],
['definitionAdd', {async: true, contentScript: true, handler: this._onApiDefinitionAdd.bind(this)}],
['definitionsAddable', {async: true, contentScript: true, handler: this._onApiDefinitionsAddable.bind(this)}],
['noteView', {async: true, contentScript: true, handler: this._onApiNoteView.bind(this)}], ['noteView', {async: true, contentScript: true, handler: this._onApiNoteView.bind(this)}],
['templateRender', {async: true, contentScript: true, handler: this._onApiTemplateRender.bind(this)}],
['commandExec', {async: false, contentScript: true, handler: this._onApiCommandExec.bind(this)}], ['commandExec', {async: false, contentScript: true, handler: this._onApiCommandExec.bind(this)}],
['audioGetUri', {async: true, contentScript: true, handler: this._onApiAudioGetUri.bind(this)}], ['audioGetUri', {async: true, contentScript: true, handler: this._onApiAudioGetUri.bind(this)}],
['screenshotGet', {async: true, contentScript: true, handler: this._onApiScreenshotGet.bind(this)}], ['screenshotGet', {async: true, contentScript: true, handler: this._onApiScreenshotGet.bind(this)}],
@ -473,7 +464,11 @@ class Backend {
return results; return results;
} }
async _onApiInjectAnkiNoteMedia({expression, reading, timestamp, audioDetails, screenshotDetails, clipboardImage}) { async _onApiInjectAnkiNoteMedia({expression, reading, timestamp, audioDetails, screenshotDetails, clipboardImage}, sender) {
if (isObject(screenshotDetails)) {
const {id: tabId, windowId} = (sender && sender.tab ? sender.tab : {});
screenshotDetails = Object.assign({}, screenshotDetails, {tabId, windowId});
}
return await this._injectAnkNoteMedia( return await this._injectAnkNoteMedia(
this._anki, this._anki,
expression, expression,
@ -485,45 +480,10 @@ class Backend {
); );
} }
async _onApiDefinitionAdd({definition, mode, context, ownerFrameId, optionsContext}, sender) {
const options = this.getOptions(optionsContext);
const templates = this._getTemplates(options);
const {id: tabId, windowId} = (sender && sender.tab ? sender.tab : {});
const note = await this._createNote(definition, mode, context, options, templates, true, {windowId, tabId, ownerFrameId});
return await this._onApiAddAnkiNote({note});
}
async _onApiDefinitionsAddable({definitions, modes, context, optionsContext}) {
const options = this.getOptions(optionsContext);
const templates = this._getTemplates(options);
const modeCount = modes.length;
const {duplicateScope} = options.anki;
const notePromises = [];
for (const definition of definitions) {
for (const mode of modes) {
const notePromise = this._createNote(definition, mode, context, options, templates, false, null);
notePromises.push(notePromise);
}
}
const notes = await Promise.all(notePromises);
const infos = await this._onApiGetAnkiNoteInfo({notes, duplicateScope});
const results = [];
for (let i = 0, ii = infos.length; i < ii; i += modeCount) {
results.push(infos.slice(i, i + modeCount));
}
return results;
}
async _onApiNoteView({noteId}) { async _onApiNoteView({noteId}) {
return await this._anki.guiBrowseNote(noteId); return await this._anki.guiBrowseNote(noteId);
} }
async _onApiTemplateRender({template, data, marker}) {
return this._renderTemplate(template, data, marker);
}
_onApiCommandExec({command, params}) { _onApiCommandExec({command, params}) {
return this._runCommand(command, params); return this._runCommand(command, params);
} }
@ -1381,10 +1341,6 @@ class Backend {
return false; return false;
} }
async _renderTemplate(template, data, marker) {
return await this._templateRenderer.render(template, data, marker);
}
_getTemplates(options) { _getTemplates(options) {
const templates = options.anki.fieldTemplates; const templates = options.anki.fieldTemplates;
return typeof templates === 'string' ? templates : this._defaultAnkiFieldTemplates; return typeof templates === 'string' ? templates : this._defaultAnkiFieldTemplates;
@ -1595,52 +1551,6 @@ class Backend {
}); });
} }
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 : {});
if (injectMedia) {
const fields = modeOptions.fields;
const timestamp = Date.now();
const definitionExpressions = definition.expressions;
const {expression, reading} = Array.isArray(definitionExpressions) ? definitionExpressions[0] : definition;
const audioDetails = (mode !== 'kanji' && this._ankiNoteBuilder.containsMarker(fields, 'audio') ? {sources, customSourceUrl} : null);
const screenshotDetails = (this._ankiNoteBuilder.containsMarker(fields, 'screenshot') ? {windowId, tabId, ownerFrameId, format, quality} : null);
const clipboardImage = (this._ankiNoteBuilder.containsMarker(fields, 'clipboard-image'));
const {screenshotFileName, clipboardImageFileName, audioFileName} = await this._onApiInjectAnkiNoteMedia({
expression,
reading,
timestamp,
audioDetails,
screenshotDetails,
clipboardImage
});
if (screenshotFileName !== null) { definition.screenshotFileName = screenshotFileName; }
if (clipboardImageFileName !== null) { definition.clipboardImageFileName = clipboardImageFileName; }
if (audioFileName !== null) { definition.audioFileName = audioFileName; }
}
return await this._ankiNoteBuilder.createNote({
definition,
mode,
context,
templates,
tags,
duplicateScope,
resultOutputMode,
compactGlossaries,
modeOptions,
audioDetails: {sources, customSourceUrl},
screenshotDetails: {windowId, tabId, ownerFrameId, format, quality},
clipboardImage: true
});
}
async _getScreenshot(windowId, tabId, ownerFrameId, format, quality) { async _getScreenshot(windowId, tabId, ownerFrameId, format, quality) {
if (typeof windowId !== 'number') { if (typeof windowId !== 'number') {
throw new Error('Invalid window ID'); throw new Error('Invalid window ID');

View File

@ -17,6 +17,7 @@
/* global /* global
* AnkiNoteBuilder * AnkiNoteBuilder
* TemplateRenderer
* api * api
*/ */
@ -27,6 +28,7 @@ class AnkiTemplatesController {
this._cachedDefinitionValue = null; this._cachedDefinitionValue = null;
this._cachedDefinitionText = null; this._cachedDefinitionText = null;
this._defaultFieldTemplates = null; this._defaultFieldTemplates = null;
this._templateRenderer = new TemplateRenderer();
} }
async prepare() { async prepare() {
@ -144,7 +146,7 @@ class AnkiTemplatesController {
let templates = options.anki.fieldTemplates; let templates = options.anki.fieldTemplates;
if (typeof templates !== 'string') { templates = this._defaultFieldTemplates; } if (typeof templates !== 'string') { templates = this._defaultFieldTemplates; }
const ankiNoteBuilder = new AnkiNoteBuilder({ const ankiNoteBuilder = new AnkiNoteBuilder({
renderTemplate: api.templateRender.bind(api) renderTemplate: this._renderTemplate.bind(this)
}); });
const {general: {resultOutputMode, compactGlossaries}} = options; const {general: {resultOutputMode, compactGlossaries}} = options;
const note = await ankiNoteBuilder.createNote({ const note = await ankiNoteBuilder.createNote({
@ -176,4 +178,8 @@ class AnkiTemplatesController {
input.classList.toggle('is-invalid', hasException); input.classList.toggle('is-invalid', hasException);
} }
} }
async _renderTemplate(template, data, marker) {
return await this._templateRenderer.render(template, data, marker);
}
} }

View File

@ -91,6 +91,9 @@
<script src="/mixed/js/template-handler.js"></script> <script src="/mixed/js/template-handler.js"></script>
<script src="/mixed/js/text-to-speech-audio.js"></script> <script src="/mixed/js/text-to-speech-audio.js"></script>
<script src="/bg/js/anki-note-builder.js"></script>
<script src="/bg/js/template-renderer.js"></script>
<script src="/bg/js/query-parser-generator.js"></script> <script src="/bg/js/query-parser-generator.js"></script>
<script src="/bg/js/query-parser.js"></script> <script src="/bg/js/query-parser.js"></script>
<script src="/bg/js/clipboard-monitor.js"></script> <script src="/bg/js/clipboard-monitor.js"></script>

View File

@ -1183,6 +1183,7 @@
<script src="/bg/js/dictionary-importer.js"></script> <script src="/bg/js/dictionary-importer.js"></script>
<script src="/bg/js/json-schema.js"></script> <script src="/bg/js/json-schema.js"></script>
<script src="/bg/js/media-utility.js"></script> <script src="/bg/js/media-utility.js"></script>
<script src="/bg/js/template-renderer.js"></script>
<script src="/bg/js/settings/keyboard-mouse-input-field.js"></script> <script src="/bg/js/settings/keyboard-mouse-input-field.js"></script>
<script src="/bg/js/settings/profile-conditions-ui.js"></script> <script src="/bg/js/settings/profile-conditions-ui.js"></script>

View File

@ -44,6 +44,8 @@
</div> </div>
</div> </div>
<script src="/mixed/lib/handlebars.min.js"></script>
<script src="/mixed/js/core.js"></script> <script src="/mixed/js/core.js"></script>
<script src="/mixed/js/yomichan.js"></script> <script src="/mixed/js/yomichan.js"></script>
<script src="/mixed/js/comm.js"></script> <script src="/mixed/js/comm.js"></script>
@ -66,6 +68,9 @@
<script src="/mixed/js/template-handler.js"></script> <script src="/mixed/js/template-handler.js"></script>
<script src="/mixed/js/text-to-speech-audio.js"></script> <script src="/mixed/js/text-to-speech-audio.js"></script>
<script src="/bg/js/anki-note-builder.js"></script>
<script src="/bg/js/template-renderer.js"></script>
<script src="/bg/js/query-parser-generator.js"></script> <script src="/bg/js/query-parser-generator.js"></script>
<script src="/bg/js/query-parser.js"></script> <script src="/bg/js/query-parser.js"></script>
<script src="/fg/js/float.js"></script> <script src="/fg/js/float.js"></script>

View File

@ -89,22 +89,10 @@ const api = (() => {
return this._invoke('injectAnkiNoteMedia', {expression, reading, timestamp, audioDetails, screenshotDetails, clipboardImage}); return this._invoke('injectAnkiNoteMedia', {expression, reading, timestamp, audioDetails, screenshotDetails, clipboardImage});
} }
definitionAdd(definition, mode, context, ownerFrameId, optionsContext) {
return this._invoke('definitionAdd', {definition, mode, context, ownerFrameId, optionsContext});
}
definitionsAddable(definitions, modes, context, optionsContext) {
return this._invoke('definitionsAddable', {definitions, modes, context, optionsContext});
}
noteView(noteId) { noteView(noteId) {
return this._invoke('noteView', {noteId}); return this._invoke('noteView', {noteId});
} }
templateRender(template, data, marker) {
return this._invoke('templateRender', {data, template, marker});
}
audioGetUri(source, expression, reading, details) { audioGetUri(source, expression, reading, details) {
return this._invoke('audioGetUri', {source, expression, reading, details}); return this._invoke('audioGetUri', {source, expression, reading, details});
} }

View File

@ -16,6 +16,7 @@
*/ */
/* global /* global
* AnkiNoteBuilder
* AudioSystem * AudioSystem
* DisplayGenerator * DisplayGenerator
* DisplayHistory * DisplayHistory
@ -24,6 +25,7 @@
* MediaLoader * MediaLoader
* PopupFactory * PopupFactory
* QueryParser * QueryParser
* TemplateRenderer
* WindowScroll * WindowScroll
* api * api
* dynamicLoader * dynamicLoader
@ -83,6 +85,12 @@ class Display extends EventDispatcher {
}); });
this._mode = null; this._mode = null;
this._ownerFrameId = null; this._ownerFrameId = null;
this._defaultAnkiFieldTemplates = null;
this._defaultAnkiFieldTemplatesPromise = null;
this._templateRenderer = new TemplateRenderer();
this._ankiNoteBuilder = new AnkiNoteBuilder({
renderTemplate: this._renderTemplate.bind(this)
});
this.registerActions([ this.registerActions([
['close', () => { this.onEscape(); }], ['close', () => { this.onEscape(); }],
@ -891,7 +899,8 @@ class Display extends EventDispatcher {
const modes = isTerms ? ['term-kanji', 'term-kana'] : ['kanji']; const modes = isTerms ? ['term-kanji', 'term-kana'] : ['kanji'];
let states; let states;
try { try {
states = await this._getDefinitionsAddable(definitions, modes); const noteContext = await this._getNoteContext();
states = await this._areDefinitionsAddable(definitions, modes, noteContext);
} catch (e) { } catch (e) {
return; return;
} }
@ -1054,10 +1063,8 @@ class Display extends EventDispatcher {
try { try {
this.setSpinnerVisible(true); this.setSpinnerVisible(true);
const ownerFrameId = this._ownerFrameId;
const optionsContext = this.getOptionsContext();
const noteContext = await this._getNoteContext(); const noteContext = await this._getNoteContext();
const noteId = await api.definitionAdd(definition, mode, noteContext, ownerFrameId, optionsContext); const noteId = await this._addDefinition(definition, mode, noteContext);
if (noteId) { if (noteId) {
const index = this._definitions.indexOf(definition); const index = this._definitions.indexOf(definition);
const adderButton = this._adderButtonFind(index, mode); const adderButton = this._adderButtonFind(index, mode);
@ -1196,15 +1203,6 @@ class Display extends EventDispatcher {
return container !== null ? container.querySelector('.action-play-audio>img') : null; return container !== null ? container.querySelector('.action-play-audio>img') : null;
} }
async _getDefinitionsAddable(definitions, modes) {
try {
const noteContext = await this._getNoteContext();
return await api.definitionsAddable(definitions, modes, noteContext, this.getOptionsContext());
} catch (e) {
return [];
}
}
_indexOf(nodeList, node) { _indexOf(nodeList, node) {
for (let i = 0, ii = nodeList.length; i < ii; ++i) { for (let i = 0, ii = nodeList.length; i < ii; ++i) {
if (nodeList[i] === node) { if (nodeList[i] === node) {
@ -1315,4 +1313,108 @@ class Display extends EventDispatcher {
this._mode = mode; this._mode = mode;
this.trigger('modeChange', {mode}); this.trigger('modeChange', {mode});
} }
async _getTemplates(options) {
let templates = options.anki.fieldTemplates;
if (typeof templates === 'string') { return templates; }
templates = this._defaultAnkiFieldTemplates;
if (typeof templates === 'string') { return templates; }
return await this._getDefaultTemplatesPromise();
}
_getDefaultTemplatesPromise() {
if (this._defaultAnkiFieldTemplatesPromise === null) {
this._defaultAnkiFieldTemplatesPromise = this._getDefaultTemplates();
this._defaultAnkiFieldTemplatesPromise.then(
() => { this._defaultAnkiFieldTemplatesPromise = null; },
() => {} // NOP
);
}
return this._defaultAnkiFieldTemplatesPromise;
}
async _getDefaultTemplates() {
const value = await api.getDefaultAnkiFieldTemplates();
this._defaultAnkiFieldTemplates = value;
return value;
}
async _renderTemplate(template, data, marker) {
return await this._templateRenderer.render(template, data, marker);
}
async _addDefinition(definition, mode, context) {
const options = this._options;
const templates = await this._getTemplates(options);
const note = await this._createNote(definition, mode, context, options, templates, true);
return await api.addAnkiNote(note);
}
async _areDefinitionsAddable(definitions, modes, context) {
const options = this._options;
const templates = await this._getTemplates(options);
const modeCount = modes.length;
const {duplicateScope} = options.anki;
const notePromises = [];
for (const definition of definitions) {
for (const mode of modes) {
const notePromise = this._createNote(definition, mode, context, options, templates, false);
notePromises.push(notePromise);
}
}
const notes = await Promise.all(notePromises);
const infos = await api.getAnkiNoteInfo(notes, duplicateScope);
const results = [];
for (let i = 0, ii = infos.length; i < ii; i += modeCount) {
results.push(infos.slice(i, i + modeCount));
}
return results;
}
async _createNote(definition, mode, context, options, templates, injectMedia) {
const {
general: {resultOutputMode, compactGlossaries},
anki: {tags, duplicateScope, kanji, terms, screenshot: {format, quality}},
audio: {sources, customSourceUrl}
} = options;
const modeOptions = (mode === 'kanji') ? kanji : terms;
if (injectMedia) {
const timestamp = Date.now();
const ownerFrameId = this._ownerFrameId;
const {fields} = modeOptions;
const definitionExpressions = definition.expressions;
const {expression, reading} = Array.isArray(definitionExpressions) ? definitionExpressions[0] : definition;
const audioDetails = (mode !== 'kanji' && this._ankiNoteBuilder.containsMarker(fields, 'audio') ? {sources, customSourceUrl} : null);
const screenshotDetails = (this._ankiNoteBuilder.containsMarker(fields, 'screenshot') ? {ownerFrameId, format, quality} : null);
const clipboardImage = (this._ankiNoteBuilder.containsMarker(fields, 'clipboard-image'));
const {screenshotFileName, clipboardImageFileName, audioFileName} = await api.injectAnkiNoteMedia(
expression,
reading,
timestamp,
audioDetails,
screenshotDetails,
clipboardImage
);
if (screenshotFileName !== null) { definition.screenshotFileName = screenshotFileName; }
if (clipboardImageFileName !== null) { definition.clipboardImageFileName = clipboardImageFileName; }
if (audioFileName !== null) { definition.audioFileName = audioFileName; }
}
return await this._ankiNoteBuilder.createNote({
definition,
mode,
context,
templates,
tags,
duplicateScope,
resultOutputMode,
compactGlossaries,
modeOptions
});
}
} }