aboutsummaryrefslogtreecommitdiff
path: root/ext/bg/js
diff options
context:
space:
mode:
authortoasted-nutbread <toasted-nutbread@users.noreply.github.com>2020-03-07 21:52:47 -0500
committerGitHub <noreply@github.com>2020-03-07 21:52:47 -0500
commita0d8caffb4f83e0e7219318fa08839ee93e653b8 (patch)
tree3c242c084c10793d1396d7caf7d8e7bce2c07f29 /ext/bg/js
parentd022d61b1a66614e1837585afcb53a25253b643a (diff)
parent426c1534e7d740fa2c30488a64ad4fa6a382deed (diff)
Merge pull request #400 from toasted-nutbread/template-render-refactor
Template render refactor
Diffstat (limited to 'ext/bg/js')
-rw-r--r--ext/bg/js/anki-note-builder.js100
-rw-r--r--ext/bg/js/api.js47
-rw-r--r--ext/bg/js/backend.js15
-rw-r--r--ext/bg/js/dictionary.js88
-rw-r--r--ext/bg/js/settings/anki-templates.js8
5 files changed, 115 insertions, 143 deletions
diff --git a/ext/bg/js/anki-note-builder.js b/ext/bg/js/anki-note-builder.js
new file mode 100644
index 00000000..d0ff8205
--- /dev/null
+++ b/ext/bg/js/anki-note-builder.js
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2020 Alex Yatskov <alex@foosoft.net>
+ * Author: Alex Yatskov <alex@foosoft.net>
+ *
+ * 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 AnkiNoteBuilder {
+ constructor({renderTemplate}) {
+ this._renderTemplate = renderTemplate;
+ }
+
+ async createNote(definition, mode, options, templates) {
+ const isKanji = (mode === 'kanji');
+ const tags = options.anki.tags;
+ const modeOptions = isKanji ? options.anki.kanji : options.anki.terms;
+ const modeOptionsFieldEntries = Object.entries(modeOptions.fields);
+
+ const note = {
+ fields: {},
+ tags,
+ deckName: modeOptions.deck,
+ modelName: modeOptions.model
+ };
+
+ for (const [fieldName, fieldValue] of modeOptionsFieldEntries) {
+ note.fields[fieldName] = await this.formatField(fieldValue, definition, mode, options, templates, null);
+ }
+
+ if (!isKanji && definition.audio) {
+ const audioFields = [];
+
+ for (const [fieldName, fieldValue] of modeOptionsFieldEntries) {
+ if (fieldValue.includes('{audio}')) {
+ audioFields.push(fieldName);
+ }
+ }
+
+ if (audioFields.length > 0) {
+ note.audio = {
+ url: definition.audio.url,
+ filename: definition.audio.filename,
+ skipHash: '7e2c2f954ef6051373ba916f000168dc', // hash of audio data that should be skipped
+ fields: audioFields
+ };
+ }
+ }
+
+ return note;
+ }
+
+ async formatField(field, definition, mode, options, templates, errors=null) {
+ const data = {
+ marker: null,
+ definition,
+ group: options.general.resultOutputMode === 'group',
+ merge: options.general.resultOutputMode === 'merge',
+ modeTermKanji: mode === 'term-kanji',
+ modeTermKana: mode === 'term-kana',
+ modeKanji: mode === 'kanji',
+ compactGlossaries: options.general.compactGlossaries
+ };
+ const pattern = /\{([\w-]+)\}/g;
+ return await AnkiNoteBuilder.stringReplaceAsync(field, pattern, async (g0, marker) => {
+ data.marker = marker;
+ try {
+ return await this._renderTemplate(templates, data);
+ } catch (e) {
+ if (errors) { errors.push(e); }
+ return `{${marker}-render-error}`;
+ }
+ });
+ }
+
+ static stringReplaceAsync(str, regex, replacer) {
+ let match;
+ let index = 0;
+ const parts = [];
+ while ((match = regex.exec(str)) !== null) {
+ parts.push(str.substring(index, match.index), replacer(...match, match.index, str));
+ index = regex.lastIndex;
+ }
+ if (parts.length === 0) {
+ return Promise.resolve(str);
+ }
+ parts.push(str.substring(index));
+ return Promise.all(parts).then((v) => v.join(''));
+ }
+}
diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js
deleted file mode 100644
index 4e5d81db..00000000
--- a/ext/bg/js/api.js
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Copyright (C) 2019-2020 Alex Yatskov <alex@foosoft.net>
- * Author: Alex Yatskov <alex@foosoft.net>
- *
- * 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/>.
- */
-
-
-function apiTemplateRender(template, data) {
- return _apiInvoke('templateRender', {data, template});
-}
-
-function _apiInvoke(action, params={}) {
- const data = {action, params};
- return new Promise((resolve, reject) => {
- try {
- const callback = (response) => {
- if (response !== null && typeof response === 'object') {
- if (typeof response.error !== 'undefined') {
- reject(jsonToError(response.error));
- } else {
- resolve(response.result);
- }
- } else {
- const message = response === null ? 'Unexpected null response' : `Unexpected response of type ${typeof response}`;
- reject(new Error(`${message} (${JSON.stringify(data)})`));
- }
- };
- const backend = window.yomichanBackend;
- backend.onMessage({action, params}, null, callback);
- } catch (e) {
- reject(e);
- yomichan.triggerOrphaned(e);
- }
- });
-}
diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js
index 60a87916..6e5235ed 100644
--- a/ext/bg/js/backend.js
+++ b/ext/bg/js/backend.js
@@ -20,10 +20,10 @@
conditionsTestValue, profileConditionsDescriptor
handlebarsRenderDynamic
requestText, requestJson, optionsLoad
-dictConfigured, dictTermsSort, dictEnabledSet, dictNoteFormat
+dictConfigured, dictTermsSort, dictEnabledSet
audioGetUrl, audioInject
jpConvertReading, jpDistributeFuriganaInflected, jpKatakanaToHiragana
-AudioSystem, Translator, AnkiConnect, AnkiNull, Mecab, BackendApiForwarder, JsonSchema, ClipboardMonitor*/
+AnkiNoteBuilder, AudioSystem, Translator, AnkiConnect, AnkiNull, Mecab, BackendApiForwarder, JsonSchema, ClipboardMonitor*/
class Backend {
constructor() {
@@ -31,6 +31,7 @@ class Backend {
this.anki = new AnkiNull();
this.mecab = new Mecab();
this.clipboardMonitor = new ClipboardMonitor({getClipboard: this._onApiClipboardGet.bind(this)});
+ this.ankiNoteBuilder = new AnkiNoteBuilder({renderTemplate: this._renderTemplate.bind(this)});
this.options = null;
this.optionsSchema = null;
this.defaultAnkiFieldTemplates = null;
@@ -450,7 +451,7 @@ class Backend {
);
}
- const note = await dictNoteFormat(definition, mode, options, templates);
+ const note = await this.ankiNoteBuilder.createNote(definition, mode, options, templates);
return this.anki.addNote(note);
}
@@ -463,7 +464,7 @@ class Backend {
const notes = [];
for (const definition of definitions) {
for (const mode of modes) {
- const note = await dictNoteFormat(definition, mode, options, templates);
+ const note = await this.ankiNoteBuilder.createNote(definition, mode, options, templates);
notes.push(note);
}
}
@@ -506,7 +507,7 @@ class Backend {
}
async _onApiTemplateRender({template, data}) {
- return handlebarsRenderDynamic(template, data);
+ return this._renderTemplate(template, data);
}
async _onApiCommandExec({command, params}) {
@@ -810,6 +811,10 @@ class Backend {
definition.screenshotFileName = filename;
}
+ async _renderTemplate(template, data) {
+ return handlebarsRenderDynamic(template, data);
+ }
+
static _getTabUrl(tab) {
return new Promise((resolve) => {
chrome.tabs.sendMessage(tab.id, {action: 'getUrl'}, {frameId: 0}, (response) => {
diff --git a/ext/bg/js/dictionary.js b/ext/bg/js/dictionary.js
index ffeac80a..3dd1d0c1 100644
--- a/ext/bg/js/dictionary.js
+++ b/ext/bg/js/dictionary.js
@@ -16,8 +16,6 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-/*global apiTemplateRender*/
-
function dictEnabledSet(options) {
const enabledDictionaryMap = new Map();
for (const [title, {enabled, priority, allowSecondarySearches}] of Object.entries(options.dictionaries)) {
@@ -333,89 +331,3 @@ function dictTagsSort(tags) {
function dictFieldSplit(field) {
return field.length === 0 ? [] : field.split(' ');
}
-
-async function dictFieldFormat(field, definition, mode, options, templates, exceptions) {
- const data = {
- marker: null,
- definition,
- group: options.general.resultOutputMode === 'group',
- merge: options.general.resultOutputMode === 'merge',
- modeTermKanji: mode === 'term-kanji',
- modeTermKana: mode === 'term-kana',
- modeKanji: mode === 'kanji',
- compactGlossaries: options.general.compactGlossaries
- };
- const markers = dictFieldFormat.markers;
- const pattern = /\{([\w-]+)\}/g;
- return await stringReplaceAsync(field, pattern, async (g0, marker) => {
- if (!markers.has(marker)) {
- return g0;
- }
- data.marker = marker;
- try {
- return await apiTemplateRender(templates, data);
- } catch (e) {
- if (exceptions) { exceptions.push(e); }
- return `{${marker}-render-error}`;
- }
- });
-}
-dictFieldFormat.markers = new Set([
- 'audio',
- 'character',
- 'cloze-body',
- 'cloze-prefix',
- 'cloze-suffix',
- 'dictionary',
- 'expression',
- 'furigana',
- 'furigana-plain',
- 'glossary',
- 'glossary-brief',
- 'kunyomi',
- 'onyomi',
- 'reading',
- 'screenshot',
- 'sentence',
- 'tags',
- 'url'
-]);
-
-async function dictNoteFormat(definition, mode, options, templates) {
- const isKanji = (mode === 'kanji');
- const tags = options.anki.tags;
- const modeOptions = isKanji ? options.anki.kanji : options.anki.terms;
- const modeOptionsFieldEntries = Object.entries(modeOptions.fields);
-
- const note = {
- fields: {},
- tags,
- deckName: modeOptions.deck,
- modelName: modeOptions.model
- };
-
- for (const [fieldName, fieldValue] of modeOptionsFieldEntries) {
- note.fields[fieldName] = await dictFieldFormat(fieldValue, definition, mode, options, templates);
- }
-
- if (!isKanji && definition.audio) {
- const audioFields = [];
-
- for (const [fieldName, fieldValue] of modeOptionsFieldEntries) {
- if (fieldValue.includes('{audio}')) {
- audioFields.push(fieldName);
- }
- }
-
- if (audioFields.length > 0) {
- note.audio = {
- url: definition.audio.url,
- filename: definition.audio.filename,
- skipHash: '7e2c2f954ef6051373ba916f000168dc',
- fields: audioFields
- };
- }
- }
-
- return note;
-}
diff --git a/ext/bg/js/settings/anki-templates.js b/ext/bg/js/settings/anki-templates.js
index 244ec42e..b1665048 100644
--- a/ext/bg/js/settings/anki-templates.js
+++ b/ext/bg/js/settings/anki-templates.js
@@ -17,8 +17,9 @@
*/
/*global getOptionsContext, getOptionsMutable, settingsSaveOptions
-ankiGetFieldMarkers, ankiGetFieldMarkersHtml, dictFieldFormat
-apiOptionsGet, apiTermsFind, apiGetDefaultAnkiFieldTemplates*/
+ankiGetFieldMarkers, ankiGetFieldMarkersHtml
+apiOptionsGet, apiTermsFind, apiGetDefaultAnkiFieldTemplates, apiTemplateRender
+AnkiNoteBuilder*/
function onAnkiFieldTemplatesReset(e) {
e.preventDefault();
@@ -92,7 +93,8 @@ async function ankiTemplatesValidate(infoNode, field, mode, showSuccessResult, i
const options = await apiOptionsGet(optionsContext);
let templates = options.anki.fieldTemplates;
if (typeof templates !== 'string') { templates = await apiGetDefaultAnkiFieldTemplates(); }
- result = await dictFieldFormat(field, definition, mode, options, templates, exceptions);
+ const ankiNoteBuilder = new AnkiNoteBuilder({renderTemplate: apiTemplateRender});
+ result = await ankiNoteBuilder.formatField(field, definition, mode, options, templates, exceptions);
}
} catch (e) {
exceptions.push(e);