aboutsummaryrefslogtreecommitdiff
path: root/ext/js/settings/anki-templates-controller.js
diff options
context:
space:
mode:
Diffstat (limited to 'ext/js/settings/anki-templates-controller.js')
-rw-r--r--ext/js/settings/anki-templates-controller.js228
1 files changed, 228 insertions, 0 deletions
diff --git a/ext/js/settings/anki-templates-controller.js b/ext/js/settings/anki-templates-controller.js
new file mode 100644
index 00000000..31bd1e92
--- /dev/null
+++ b/ext/js/settings/anki-templates-controller.js
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2019-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/>.
+ */
+
+/* global
+ * AnkiNoteBuilder
+ * api
+ */
+
+class AnkiTemplatesController {
+ constructor(settingsController, modalController, ankiController) {
+ this._settingsController = settingsController;
+ this._modalController = modalController;
+ this._ankiController = ankiController;
+ this._cachedDefinitionValue = null;
+ this._cachedDefinitionText = null;
+ this._defaultFieldTemplates = null;
+ this._fieldTemplatesTextarea = null;
+ this._compileResultInfo = null;
+ this._renderFieldInput = null;
+ this._renderResult = null;
+ this._fieldTemplateResetModal = null;
+ this._ankiNoteBuilder = new AnkiNoteBuilder(true);
+ }
+
+ async prepare() {
+ this._defaultFieldTemplates = await api.getDefaultAnkiFieldTemplates();
+
+ this._fieldTemplatesTextarea = document.querySelector('#anki-card-templates-textarea');
+ this._compileResultInfo = document.querySelector('#anki-card-templates-compile-result');
+ this._renderFieldInput = document.querySelector('#anki-card-templates-test-field-input');
+ this._renderTextInput = document.querySelector('#anki-card-templates-test-text-input');
+ this._renderResult = document.querySelector('#anki-card-templates-render-result');
+ const menuButton = document.querySelector('#anki-card-templates-test-field-menu-button');
+ const testRenderButton = document.querySelector('#anki-card-templates-test-render-button');
+ const resetButton = document.querySelector('#anki-card-templates-reset-button');
+ const resetConfirmButton = document.querySelector('#anki-card-templates-reset-button-confirm');
+ const fieldList = document.querySelector('#anki-card-templates-field-list');
+ this._fieldTemplateResetModal = this._modalController.getModal('anki-card-templates-reset');
+
+ const markers = new Set([
+ ...this._ankiController.getFieldMarkers('terms'),
+ ...this._ankiController.getFieldMarkers('kanji')
+ ]);
+
+ if (fieldList !== null) {
+ const fragment = this._ankiController.getFieldMarkersHtml(markers);
+ fieldList.appendChild(fragment);
+ for (const node of fieldList.querySelectorAll('.marker-link')) {
+ node.addEventListener('click', this._onMarkerClicked.bind(this), false);
+ }
+ }
+
+ this._fieldTemplatesTextarea.addEventListener('change', this._onChanged.bind(this), false);
+ testRenderButton.addEventListener('click', this._onRender.bind(this), false);
+ resetButton.addEventListener('click', this._onReset.bind(this), false);
+ resetConfirmButton.addEventListener('click', this._onResetConfirm.bind(this), false);
+ if (menuButton !== null) {
+ menuButton.addEventListener('menuClose', this._onFieldMenuClose.bind(this), false);
+ }
+
+ this._settingsController.on('optionsChanged', this._onOptionsChanged.bind(this));
+
+ const options = await this._settingsController.getOptions();
+ this._onOptionsChanged({options});
+ }
+
+ // Private
+
+ _onOptionsChanged({options}) {
+ let templates = options.anki.fieldTemplates;
+ if (typeof templates !== 'string') { templates = this._defaultFieldTemplates; }
+ this._fieldTemplatesTextarea.value = templates;
+
+ this._onValidateCompile();
+ }
+
+ _onReset(e) {
+ e.preventDefault();
+ this._fieldTemplateResetModal.setVisible(true);
+ }
+
+ _onResetConfirm(e) {
+ e.preventDefault();
+
+ this._fieldTemplateResetModal.setVisible(false);
+
+ const value = this._defaultFieldTemplates;
+
+ this._fieldTemplatesTextarea.value = value;
+ this._fieldTemplatesTextarea.dispatchEvent(new Event('change'));
+ }
+
+ async _onChanged(e) {
+ // Get value
+ let templates = e.currentTarget.value;
+ if (templates === this._defaultFieldTemplates) {
+ // Default
+ templates = null;
+ }
+
+ // Overwrite
+ await this._settingsController.setProfileSetting('anki.fieldTemplates', templates);
+
+ // Compile
+ this._onValidateCompile();
+ }
+
+ _onValidateCompile() {
+ this._validate(this._compileResultInfo, '{expression}', 'term-kanji', false, true);
+ }
+
+ _onMarkerClicked(e) {
+ e.preventDefault();
+ this._renderFieldInput.value = `{${e.target.textContent}}`;
+ }
+
+ _onRender(e) {
+ e.preventDefault();
+
+ const field = this._renderFieldInput.value;
+ const infoNode = this._renderResult;
+ infoNode.hidden = true;
+ this._cachedDefinitionText = null;
+ this._validate(infoNode, field, 'term-kanji', true, false);
+ }
+
+ _onFieldMenuClose({currentTarget: button, detail: {action, item}}) {
+ switch (action) {
+ case 'setFieldMarker':
+ this._setFieldMarker(button, item.dataset.marker);
+ break;
+ }
+ }
+
+ _setFieldMarker(element, marker) {
+ const input = this._renderFieldInput;
+ input.value = `{${marker}}`;
+ input.dispatchEvent(new Event('change'));
+ }
+
+ async _getDefinition(text, optionsContext) {
+ if (this._cachedDefinitionText !== text) {
+ const {definitions} = await api.termsFind(text, {}, optionsContext);
+ if (definitions.length === 0) { return null; }
+
+ this._cachedDefinitionValue = definitions[0];
+ this._cachedDefinitionText = text;
+ }
+ return this._cachedDefinitionValue;
+ }
+
+ async _validate(infoNode, field, mode, showSuccessResult, invalidateInput) {
+ const text = this._renderTextInput.value || '';
+ const errors = [];
+ let result = `No definition found for ${text}`;
+ try {
+ const optionsContext = this._settingsController.getOptionsContext();
+ const definition = await this._getDefinition(text, optionsContext);
+ if (definition !== null) {
+ const options = await this._settingsController.getOptions();
+ const context = {
+ url: window.location.href,
+ sentence: {text: definition.rawSource, offset: 0},
+ documentTitle: document.title
+ };
+ let templates = options.anki.fieldTemplates;
+ if (typeof templates !== 'string') { templates = this._defaultFieldTemplates; }
+ const {general: {resultOutputMode, glossaryLayoutMode, compactTags}} = options;
+ const note = await this._ankiNoteBuilder.createNote({
+ definition,
+ mode,
+ context,
+ templates,
+ deckName: '',
+ modelName: '',
+ fields: [
+ ['field', field]
+ ],
+ resultOutputMode,
+ glossaryLayoutMode,
+ compactTags,
+ errors
+ });
+ result = note.fields.field;
+ }
+ } catch (e) {
+ errors.push(e);
+ }
+
+ const errorToMessageString = (e) => {
+ if (isObject(e)) {
+ let v = e.data;
+ if (isObject(v)) {
+ v = v.error;
+ if (isObject(v)) {
+ e = v;
+ }
+ }
+
+ v = e.message;
+ if (typeof v === 'string') { return v; }
+ }
+ return `${e}`;
+ };
+
+ const hasError = errors.length > 0;
+ infoNode.hidden = !(showSuccessResult || hasError);
+ infoNode.textContent = hasError ? errors.map(errorToMessageString).join('\n') : (showSuccessResult ? result : '');
+ infoNode.classList.toggle('text-danger', hasError);
+ if (invalidateInput) {
+ this._fieldTemplatesTextarea.dataset.invalid = `${hasError}`;
+ }
+ }
+}