diff --git a/ext/bg/background.html b/ext/bg/background.html
index f2f70d4d..8db017f1 100644
--- a/ext/bg/background.html
+++ b/ext/bg/background.html
@@ -22,6 +22,7 @@
+
diff --git a/ext/bg/js/anki-note-builder.js b/ext/bg/js/anki-note-builder.js
new file mode 100644
index 00000000..f7555280
--- /dev/null
+++ b/ext/bg/js/anki-note-builder.js
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2020 Alex Yatskov
+ * Author: Alex Yatskov
+ *
+ * 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 .
+ */
+
+/*global apiTemplateRender*/
+
+class AnkiNoteBuilder {
+ constructor() {
+ this._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 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 markers = this._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 (errors) { errors.push(e); }
+ return `{${marker}-render-error}`;
+ }
+ });
+ }
+}
diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js
index 60a87916..929281da 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();
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);
}
}
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 .
*/
-/*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..32a990f9 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,
+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();
+ result = await ankiNoteBuilder.formatField(field, definition, mode, options, templates, exceptions);
}
} catch (e) {
exceptions.push(e);
diff --git a/ext/bg/settings.html b/ext/bg/settings.html
index e9fc6be5..0db76d71 100644
--- a/ext/bg/settings.html
+++ b/ext/bg/settings.html
@@ -1090,6 +1090,7 @@
+