Refactor anki note building (#1223)

* Move TemplateRendererProxy creation into AnkiNoteBuilder

* Simplify _stringReplaceAsync

* Organize note generation

* Rename API

* Make the template rendering function more generic
This commit is contained in:
toasted-nutbread 2021-01-10 19:28:50 -05:00 committed by GitHub
parent 25080ac82e
commit 4ed9493645
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 64 additions and 57 deletions

View File

@ -17,12 +17,13 @@
/* global /* global
* DictionaryDataUtil * DictionaryDataUtil
* TemplateRendererProxy
*/ */
class AnkiNoteBuilder { class AnkiNoteBuilder {
constructor({renderTemplate}) { constructor(enabled) {
this._renderTemplate = renderTemplate;
this._markerPattern = /\{([\w-]+)\}/g; this._markerPattern = /\{([\w-]+)\}/g;
this._templateRenderer = enabled ? new TemplateRendererProxy() : null;
} }
async createNote({ async createNote({
@ -49,8 +50,22 @@ class AnkiNoteBuilder {
duplicateScopeCheckChildren = true; duplicateScopeCheckChildren = true;
} }
const data = this._createNoteData(definition, mode, context, resultOutputMode, glossaryLayoutMode, compactTags);
const formattedFieldValuePromises = [];
for (const [, fieldValue] of fields) {
const formattedFieldValuePromise = this._formatField(fieldValue, data, templates, errors);
formattedFieldValuePromises.push(formattedFieldValuePromise);
}
const formattedFieldValues = await Promise.all(formattedFieldValuePromises);
const noteFields = {}; const noteFields = {};
const note = { for (let i = 0, ii = fields.length; i < ii; ++i) {
const fieldName = fields[i][0];
const formattedFieldValue = formattedFieldValues[i];
noteFields[fieldName] = formattedFieldValue;
}
return {
fields: noteFields, fields: noteFields,
tags, tags,
deckName, deckName,
@ -64,22 +79,6 @@ class AnkiNoteBuilder {
} }
} }
}; };
const data = this._createNoteData(definition, mode, context, resultOutputMode, glossaryLayoutMode, compactTags);
const formattedFieldValuePromises = [];
for (const [, fieldValue] of fields) {
const formattedFieldValuePromise = this._formatField(fieldValue, data, templates, errors);
formattedFieldValuePromises.push(formattedFieldValuePromise);
}
const formattedFieldValues = await Promise.all(formattedFieldValuePromises);
for (let i = 0, ii = fields.length; i < ii; ++i) {
const fieldName = fields[i][0];
const formattedFieldValue = formattedFieldValues[i];
noteFields[fieldName] = formattedFieldValue;
}
return note;
} }
containsMarker(fields, marker) { containsMarker(fields, marker) {
@ -146,7 +145,7 @@ class AnkiNoteBuilder {
}); });
} }
_stringReplaceAsync(str, regex, replacer) { async _stringReplaceAsync(str, regex, replacer) {
let match; let match;
let index = 0; let index = 0;
const parts = []; const parts = [];
@ -155,9 +154,13 @@ class AnkiNoteBuilder {
index = regex.lastIndex; index = regex.lastIndex;
} }
if (parts.length === 0) { if (parts.length === 0) {
return Promise.resolve(str); return str;
} }
parts.push(str.substring(index)); parts.push(str.substring(index));
return Promise.all(parts).then((v) => v.join('')); return (await Promise.all(parts)).join('');
}
async _renderTemplate(template, data, marker) {
return await this._templateRenderer.render(template, {data, marker}, 'ankiNote');
} }
} }

View File

@ -26,9 +26,7 @@ class AnkiController {
constructor(settingsController) { constructor(settingsController) {
this._settingsController = settingsController; this._settingsController = settingsController;
this._ankiConnect = new AnkiConnect(); this._ankiConnect = new AnkiConnect();
this._ankiNoteBuilder = new AnkiNoteBuilder({ this._ankiNoteBuilder = new AnkiNoteBuilder(false);
renderTemplate: null
});
this._selectorObserver = new SelectorObserver({ this._selectorObserver = new SelectorObserver({
selector: '.anki-card', selector: '.anki-card',
ignoreSelector: null, ignoreSelector: null,

View File

@ -17,7 +17,6 @@
/* global /* global
* AnkiNoteBuilder * AnkiNoteBuilder
* TemplateRendererProxy
* api * api
*/ */
@ -34,7 +33,7 @@ class AnkiTemplatesController {
this._renderFieldInput = null; this._renderFieldInput = null;
this._renderResult = null; this._renderResult = null;
this._fieldTemplateResetModal = null; this._fieldTemplateResetModal = null;
this._templateRenderer = new TemplateRendererProxy(); this._ankiNoteBuilder = new AnkiNoteBuilder(true);
} }
async prepare() { async prepare() {
@ -180,11 +179,8 @@ 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({
renderTemplate: this._renderTemplate.bind(this)
});
const {general: {resultOutputMode, glossaryLayoutMode, compactTags}} = options; const {general: {resultOutputMode, glossaryLayoutMode, compactTags}} = options;
const note = await ankiNoteBuilder.createNote({ const note = await this._ankiNoteBuilder.createNote({
definition, definition,
mode, mode,
context, context,
@ -213,8 +209,4 @@ class AnkiTemplatesController {
this._fieldTemplatesTextarea.dataset.invalid = `${hasException}`; this._fieldTemplatesTextarea.dataset.invalid = `${hasException}`;
} }
} }
async _renderTemplate(template, data, marker) {
return await this._templateRenderer.render(template, data, marker);
}
} }

View File

@ -19,7 +19,7 @@ class TemplateRendererFrameApi {
constructor(templateRenderer) { constructor(templateRenderer) {
this._templateRenderer = templateRenderer; this._templateRenderer = templateRenderer;
this._windowMessageHandlers = new Map([ this._windowMessageHandlers = new Map([
['renderHandlebarsTemplate', {async: true, handler: this._onRenderHandlebarsTemplate.bind(this)}] ['render', {async: true, handler: this._onRender.bind(this)}]
]); ]);
} }
@ -27,6 +27,8 @@ class TemplateRendererFrameApi {
window.addEventListener('message', this._onWindowMessage.bind(this), false); window.addEventListener('message', this._onWindowMessage.bind(this), false);
} }
// Private
_onWindowMessage(e) { _onWindowMessage(e) {
const {source, data: {action, params, id}} = e; const {source, data: {action, params, id}} = e;
const messageHandler = this._windowMessageHandlers.get(action); const messageHandler = this._windowMessageHandlers.get(action);
@ -51,8 +53,8 @@ class TemplateRendererFrameApi {
source.postMessage({action: `${action}.response`, params: response, id}, '*'); source.postMessage({action: `${action}.response`, params: response, id}, '*');
} }
async _onRenderHandlebarsTemplate({template, data, marker}) { async _onRender({template, data, type}) {
return await this._templateRenderer.render(template, data, marker); return await this._templateRenderer.render(template, data, type);
} }
_errorToJson(error) { _errorToJson(error) {

View File

@ -24,6 +24,12 @@
(() => { (() => {
const japaneseUtil = new JapaneseUtil(null); const japaneseUtil = new JapaneseUtil(null);
const templateRenderer = new TemplateRenderer(japaneseUtil); const templateRenderer = new TemplateRenderer(japaneseUtil);
templateRenderer.registerDataType('ankiNote', {
modifier: ({data, marker}) => {
data.marker = marker;
return data;
}
});
const api = new TemplateRendererFrameApi(templateRenderer); const api = new TemplateRendererFrameApi(templateRenderer);
api.prepare(); api.prepare();
})(); })();

View File

@ -25,9 +25,9 @@ class TemplateRendererProxy {
this._invocations = new Set(); this._invocations = new Set();
} }
async render(template, data, marker) { async render(template, data, type) {
await this._prepareFrame(); await this._prepareFrame();
return await this._invoke('renderHandlebarsTemplate', {template, data, marker}); return await this._invoke('render', {template, data, type});
} }
// Private // Private

View File

@ -26,9 +26,14 @@ class TemplateRenderer {
this._cacheMaxSize = 5; this._cacheMaxSize = 5;
this._helpersRegistered = false; this._helpersRegistered = false;
this._stateStack = null; this._stateStack = null;
this._dataTypes = new Map();
} }
async render(template, data, marker) { registerDataType(name, {modifier=null, modifierPost=null}) {
this._dataTypes.set(name, {modifier, modifierPost});
}
async render(template, data, type) {
if (!this._helpersRegistered) { if (!this._helpersRegistered) {
this._registerHelpers(); this._registerHelpers();
this._helpersRegistered = true; this._helpersRegistered = true;
@ -42,18 +47,27 @@ class TemplateRenderer {
cache.set(template, instance); cache.set(template, instance);
} }
const markerPre = data.marker; let modifier = null;
const markerPreHas = Object.prototype.hasOwnProperty.call(data, 'marker'); let modifierPost = null;
if (typeof type === 'string') {
const typeInfo = this._dataTypes.get(type);
if (typeof typeInfo !== 'undefined') {
({modifier, modifierPost} = typeInfo);
}
}
try { try {
if (typeof modifier === 'function') {
data = modifier(data);
}
this._stateStack = [new Map()]; this._stateStack = [new Map()];
data.marker = marker;
return instance(data).trim(); return instance(data).trim();
} finally { } finally {
this._stateStack = null; this._stateStack = null;
if (markerPreHas) {
data.marker = markerPre; if (typeof modifierPost === 'function') {
} else { modifierPost(data);
delete data.marker;
} }
} }
} }

View File

@ -27,7 +27,6 @@
* MediaLoader * MediaLoader
* PopupFactory * PopupFactory
* QueryParser * QueryParser
* TemplateRendererProxy
* TextScanner * TextScanner
* WindowScroll * WindowScroll
* api * api
@ -89,10 +88,7 @@ class Display extends EventDispatcher {
this._mode = null; this._mode = null;
this._defaultAnkiFieldTemplates = null; this._defaultAnkiFieldTemplates = null;
this._defaultAnkiFieldTemplatesPromise = null; this._defaultAnkiFieldTemplatesPromise = null;
this._templateRenderer = new TemplateRendererProxy(); this._ankiNoteBuilder = new AnkiNoteBuilder(true);
this._ankiNoteBuilder = new AnkiNoteBuilder({
renderTemplate: this._renderTemplate.bind(this)
});
this._updateAdderButtonsPromise = Promise.resolve(); this._updateAdderButtonsPromise = Promise.resolve();
this._contentScrollElement = document.querySelector('#content-scroll'); this._contentScrollElement = document.querySelector('#content-scroll');
this._contentScrollBodyElement = document.querySelector('#content-body'); this._contentScrollBodyElement = document.querySelector('#content-body');
@ -1493,10 +1489,6 @@ class Display extends EventDispatcher {
return value; return value;
} }
async _renderTemplate(template, data, marker) {
return await this._templateRenderer.render(template, data, marker);
}
async _addDefinition(definition, mode, context) { async _addDefinition(definition, mode, context) {
const options = this._options; const options = this._options;
const templates = await this._getTemplates(options); const templates = await this._getTemplates(options);