aboutsummaryrefslogtreecommitdiff
path: root/ext/bg/js/settings/anki.js
diff options
context:
space:
mode:
authorAlex Yatskov <alex@foosoft.net>2020-06-27 19:04:19 -0700
committerAlex Yatskov <alex@foosoft.net>2020-06-27 19:04:19 -0700
commit88af95d20bfdbeb59d44bf0f0d46e772a329f839 (patch)
treed1dfa7268f274fed32061221c0f030e3647f9ae2 /ext/bg/js/settings/anki.js
parent19197a9a5d6a1f54a179d894577dfac513b97401 (diff)
parent0a6c08d0f53090a4ad48663bc5846ddae5723d52 (diff)
Merge branch 'master' into testing
Diffstat (limited to 'ext/bg/js/settings/anki.js')
-rw-r--r--ext/bg/js/settings/anki.js454
1 files changed, 229 insertions, 225 deletions
diff --git a/ext/bg/js/settings/anki.js b/ext/bg/js/settings/anki.js
index ff1277ed..51dabba4 100644
--- a/ext/bg/js/settings/anki.js
+++ b/ext/bg/js/settings/anki.js
@@ -16,278 +16,282 @@
*/
/* global
- * apiGetAnkiDeckNames
- * apiGetAnkiModelFieldNames
- * apiGetAnkiModelNames
- * getOptionsContext
- * getOptionsMutable
- * onFormOptionsChanged
- * settingsSaveOptions
- * utilBackgroundIsolate
+ * api
*/
-// Private
-
-let _ankiDataPopulated = false;
-
-
-function _ankiSpinnerShow(show) {
- const spinner = $('#anki-spinner');
- if (show) {
- spinner.show();
- } else {
- spinner.hide();
+class AnkiController {
+ constructor(settingsController) {
+ this._settingsController = settingsController;
}
-}
-function _ankiSetError(error) {
- const node = document.querySelector('#anki-error');
- const node2 = document.querySelector('#anki-invalid-response-error');
- if (error) {
- const errorString = `${error}`;
- if (node !== null) {
- node.hidden = false;
- node.textContent = errorString;
- _ankiSetErrorData(node, error);
+ async prepare() {
+ for (const element of document.querySelectorAll('#anki-fields-container input,#anki-fields-container select')) {
+ element.addEventListener('change', this._onFieldsChanged.bind(this), false);
}
- if (node2 !== null) {
- node2.hidden = (errorString.indexOf('Invalid response') < 0);
- }
- } else {
- if (node !== null) {
- node.hidden = true;
- node.textContent = '';
+ for (const element of document.querySelectorAll('#anki-terms-model,#anki-kanji-model')) {
+ element.addEventListener('change', this._onModelChanged.bind(this), false);
}
- if (node2 !== null) {
- node2.hidden = true;
- }
- }
-}
+ this._settingsController.on('optionsChanged', this._onOptionsChanged.bind(this));
-function _ankiSetErrorData(node, error) {
- const data = error.data;
- let message = '';
- if (typeof data !== 'undefined') {
- message += `${JSON.stringify(data, null, 4)}\n\n`;
+ const options = await this._settingsController.getOptions();
+ this._onOptionsChanged({options});
}
- message += `${error.stack}`.trimRight();
- const button = document.createElement('a');
- button.className = 'error-data-show-button';
+ getFieldMarkers(type) {
+ switch (type) {
+ case 'terms':
+ return [
+ 'audio',
+ 'cloze-body',
+ 'cloze-prefix',
+ 'cloze-suffix',
+ 'dictionary',
+ 'document-title',
+ 'expression',
+ 'furigana',
+ 'furigana-plain',
+ 'glossary',
+ 'glossary-brief',
+ 'reading',
+ 'screenshot',
+ 'sentence',
+ 'tags',
+ 'url'
+ ];
+ case 'kanji':
+ return [
+ 'character',
+ 'dictionary',
+ 'document-title',
+ 'glossary',
+ 'kunyomi',
+ 'onyomi',
+ 'screenshot',
+ 'sentence',
+ 'tags',
+ 'url'
+ ];
+ default:
+ return [];
+ }
+ }
- const content = document.createElement('div');
- content.className = 'error-data-container';
- content.textContent = message;
- content.hidden = true;
+ getFieldMarkersHtml(markers) {
+ const template = document.querySelector('#anki-field-marker-template').content;
+ const fragment = document.createDocumentFragment();
+ for (const marker of markers) {
+ const markerNode = document.importNode(template, true).firstChild;
+ markerNode.querySelector('.marker-link').textContent = marker;
+ fragment.appendChild(markerNode);
+ }
+ return fragment;
+ }
- button.addEventListener('click', () => content.hidden = !content.hidden, false);
+ // Private
- node.appendChild(button);
- node.appendChild(content);
-}
+ async _onOptionsChanged({options}) {
+ if (!options.anki.enable) {
+ return;
+ }
-function _ankiSetDropdownOptions(dropdown, optionValues) {
- const fragment = document.createDocumentFragment();
- for (const optionValue of optionValues) {
- const option = document.createElement('option');
- option.value = optionValue;
- option.textContent = optionValue;
- fragment.appendChild(option);
+ await this._deckAndModelPopulate(options);
+ await Promise.all([
+ this._populateFields('terms', options.anki.terms.fields),
+ this._populateFields('kanji', options.anki.kanji.fields)
+ ]);
}
- dropdown.textContent = '';
- dropdown.appendChild(fragment);
-}
-async function _ankiDeckAndModelPopulate(options) {
- const termsDeck = {value: options.anki.terms.deck, selector: '#anki-terms-deck'};
- const kanjiDeck = {value: options.anki.kanji.deck, selector: '#anki-kanji-deck'};
- const termsModel = {value: options.anki.terms.model, selector: '#anki-terms-model'};
- const kanjiModel = {value: options.anki.kanji.model, selector: '#anki-kanji-model'};
- try {
- _ankiSpinnerShow(true);
- const [deckNames, modelNames] = await Promise.all([apiGetAnkiDeckNames(), apiGetAnkiModelNames()]);
- deckNames.sort();
- modelNames.sort();
- termsDeck.values = deckNames;
- kanjiDeck.values = deckNames;
- termsModel.values = modelNames;
- kanjiModel.values = modelNames;
- _ankiSetError(null);
- } catch (error) {
- _ankiSetError(error);
- } finally {
- _ankiSpinnerShow(false);
+ _fieldsToDict(elements) {
+ const result = {};
+ for (const element of elements) {
+ result[element.dataset.field] = element.value;
+ }
+ return result;
}
- for (const {value, values, selector} of [termsDeck, kanjiDeck, termsModel, kanjiModel]) {
- const node = document.querySelector(selector);
- _ankiSetDropdownOptions(node, Array.isArray(values) ? values : [value]);
- node.value = value;
+ _spinnerShow(show) {
+ const spinner = document.querySelector('#anki-spinner');
+ spinner.hidden = !show;
}
-}
-function _ankiCreateFieldTemplate(name, value, markers) {
- const template = document.querySelector('#anki-field-template').content;
- const content = document.importNode(template, true).firstChild;
+ _setError(error) {
+ const node = document.querySelector('#anki-error');
+ const node2 = document.querySelector('#anki-invalid-response-error');
+ if (error) {
+ const errorString = `${error}`;
+ if (node !== null) {
+ node.hidden = false;
+ node.textContent = errorString;
+ this._setErrorData(node, error);
+ }
+
+ if (node2 !== null) {
+ node2.hidden = (errorString.indexOf('Invalid response') < 0);
+ }
+ } else {
+ if (node !== null) {
+ node.hidden = true;
+ node.textContent = '';
+ }
+
+ if (node2 !== null) {
+ node2.hidden = true;
+ }
+ }
+ }
- content.querySelector('.anki-field-name').textContent = name;
+ _setErrorData(node, error) {
+ const data = error.data;
+ let message = '';
+ if (typeof data !== 'undefined') {
+ message += `${JSON.stringify(data, null, 4)}\n\n`;
+ }
+ message += `${error.stack}`.trimRight();
- const field = content.querySelector('.anki-field-value');
- field.dataset.field = name;
- field.value = value;
+ const button = document.createElement('a');
+ button.className = 'error-data-show-button';
- content.querySelector('.anki-field-marker-list').appendChild(ankiGetFieldMarkersHtml(markers));
+ const content = document.createElement('div');
+ content.className = 'error-data-container';
+ content.textContent = message;
+ content.hidden = true;
- return content;
-}
+ button.addEventListener('click', () => content.hidden = !content.hidden, false);
-async function _ankiFieldsPopulate(tabId, options) {
- const tab = document.querySelector(`.tab-pane[data-anki-card-type=${tabId}]`);
- const container = tab.querySelector('tbody');
- const markers = ankiGetFieldMarkers(tabId);
-
- const fragment = document.createDocumentFragment();
- const fields = options.anki[tabId].fields;
- for (const name of Object.keys(fields)) {
- const value = fields[name];
- const html = _ankiCreateFieldTemplate(name, value, markers);
- fragment.appendChild(html);
+ node.appendChild(button);
+ node.appendChild(content);
}
- container.textContent = '';
- container.appendChild(fragment);
-
- for (const node of container.querySelectorAll('.anki-field-value')) {
- node.addEventListener('change', onFormOptionsChanged, false);
- }
- for (const node of container.querySelectorAll('.marker-link')) {
- node.addEventListener('click', _onAnkiMarkerClicked, false);
+ _setDropdownOptions(dropdown, optionValues) {
+ const fragment = document.createDocumentFragment();
+ for (const optionValue of optionValues) {
+ const option = document.createElement('option');
+ option.value = optionValue;
+ option.textContent = optionValue;
+ fragment.appendChild(option);
+ }
+ dropdown.textContent = '';
+ dropdown.appendChild(fragment);
}
-}
-function _onAnkiMarkerClicked(e) {
- e.preventDefault();
- const link = e.currentTarget;
- const input = $(link).closest('.input-group').find('.anki-field-value')[0];
- input.value = `{${link.textContent}}`;
- input.dispatchEvent(new Event('change'));
-}
+ async _deckAndModelPopulate(options) {
+ const termsDeck = {value: options.anki.terms.deck, selector: '#anki-terms-deck'};
+ const kanjiDeck = {value: options.anki.kanji.deck, selector: '#anki-kanji-deck'};
+ const termsModel = {value: options.anki.terms.model, selector: '#anki-terms-model'};
+ const kanjiModel = {value: options.anki.kanji.model, selector: '#anki-kanji-model'};
+ try {
+ this._spinnerShow(true);
+ const [deckNames, modelNames] = await Promise.all([api.getAnkiDeckNames(), api.getAnkiModelNames()]);
+ deckNames.sort();
+ modelNames.sort();
+ termsDeck.values = deckNames;
+ kanjiDeck.values = deckNames;
+ termsModel.values = modelNames;
+ kanjiModel.values = modelNames;
+ this._setError(null);
+ } catch (error) {
+ this._setError(error);
+ } finally {
+ this._spinnerShow(false);
+ }
-async function _onAnkiModelChanged(e) {
- const node = e.currentTarget;
- let fieldNames;
- try {
- const modelName = node.value;
- fieldNames = await apiGetAnkiModelFieldNames(modelName);
- _ankiSetError(null);
- } catch (error) {
- _ankiSetError(error);
- return;
- } finally {
- _ankiSpinnerShow(false);
+ for (const {value, values, selector} of [termsDeck, kanjiDeck, termsModel, kanjiModel]) {
+ const node = document.querySelector(selector);
+ this._setDropdownOptions(node, Array.isArray(values) ? values : [value]);
+ node.value = value;
+ }
}
- const tabId = node.dataset.ankiCardType;
- if (tabId !== 'terms' && tabId !== 'kanji') { return; }
-
- const fields = {};
- for (const name of fieldNames) {
- fields[name] = '';
- }
+ _createFieldTemplate(name, value, markers) {
+ const template = document.querySelector('#anki-field-template').content;
+ const content = document.importNode(template, true).firstChild;
- const optionsContext = getOptionsContext();
- const options = await getOptionsMutable(optionsContext);
- options.anki[tabId].fields = utilBackgroundIsolate(fields);
- await settingsSaveOptions();
+ content.querySelector('.anki-field-name').textContent = name;
- await _ankiFieldsPopulate(tabId, options);
-}
+ const field = content.querySelector('.anki-field-value');
+ field.dataset.field = name;
+ field.value = value;
+ content.querySelector('.anki-field-marker-list').appendChild(this.getFieldMarkersHtml(markers));
-// Public
+ return content;
+ }
-function ankiErrorShown() {
- const node = document.querySelector('#anki-error');
- return node && !node.hidden;
-}
+ async _populateFields(tabId, fields) {
+ const tab = document.querySelector(`.tab-pane[data-anki-card-type=${tabId}]`);
+ const container = tab.querySelector('tbody');
+ const markers = this.getFieldMarkers(tabId);
-function ankiFieldsToDict(elements) {
- const result = {};
- for (const element of elements) {
- result[element.dataset.field] = element.value;
- }
- return result;
-}
+ const fragment = document.createDocumentFragment();
+ for (const [name, value] of Object.entries(fields)) {
+ const html = this._createFieldTemplate(name, value, markers);
+ fragment.appendChild(html);
+ }
+ container.textContent = '';
+ container.appendChild(fragment);
-function ankiGetFieldMarkersHtml(markers) {
- const template = document.querySelector('#anki-field-marker-template').content;
- const fragment = document.createDocumentFragment();
- for (const marker of markers) {
- const markerNode = document.importNode(template, true).firstChild;
- markerNode.querySelector('.marker-link').textContent = marker;
- fragment.appendChild(markerNode);
+ for (const node of container.querySelectorAll('.anki-field-value')) {
+ node.addEventListener('change', this._onFieldsChanged.bind(this), false);
+ }
+ for (const node of container.querySelectorAll('.marker-link')) {
+ node.addEventListener('click', this._onMarkerClicked.bind(this), false);
+ }
}
- return fragment;
-}
-function ankiGetFieldMarkers(type) {
- switch (type) {
- case 'terms':
- return [
- 'audio',
- 'cloze-body',
- 'cloze-prefix',
- 'cloze-suffix',
- 'dictionary',
- 'document-title',
- 'expression',
- 'furigana',
- 'furigana-plain',
- 'glossary',
- 'glossary-brief',
- 'reading',
- 'screenshot',
- 'sentence',
- 'tags',
- 'url'
- ];
- case 'kanji':
- return [
- 'character',
- 'dictionary',
- 'document-title',
- 'glossary',
- 'kunyomi',
- 'onyomi',
- 'screenshot',
- 'sentence',
- 'tags',
- 'url'
- ];
- default:
- return [];
+ _onMarkerClicked(e) {
+ e.preventDefault();
+ const link = e.currentTarget;
+ const input = link.closest('.input-group').querySelector('.anki-field-value');
+ input.value = `{${link.textContent}}`;
+ input.dispatchEvent(new Event('change'));
}
-}
+ async _onModelChanged(e) {
+ const node = e.currentTarget;
+ let fieldNames;
+ try {
+ const modelName = node.value;
+ fieldNames = await api.getAnkiModelFieldNames(modelName);
+ this._setError(null);
+ } catch (error) {
+ this._setError(error);
+ return;
+ } finally {
+ this._spinnerShow(false);
+ }
-function ankiInitialize() {
- for (const node of document.querySelectorAll('#anki-terms-model,#anki-kanji-model')) {
- node.addEventListener('change', _onAnkiModelChanged, false);
- }
-}
+ const tabId = node.dataset.ankiCardType;
+ if (tabId !== 'terms' && tabId !== 'kanji') { return; }
-async function onAnkiOptionsChanged(options) {
- if (!options.anki.enable) {
- _ankiDataPopulated = false;
- return;
- }
+ const fields = {};
+ for (const name of fieldNames) {
+ fields[name] = '';
+ }
- if (_ankiDataPopulated) { return; }
+ await this._settingsController.setProfileSetting(`anki["${tabId}"].fields`, fields);
+ await this._populateFields(tabId, fields);
+ }
- await _ankiDeckAndModelPopulate(options);
- _ankiDataPopulated = true;
- await Promise.all([_ankiFieldsPopulate('terms', options), _ankiFieldsPopulate('kanji', options)]);
+ async _onFieldsChanged() {
+ const termsDeck = document.querySelector('#anki-terms-deck').value;
+ const termsModel = document.querySelector('#anki-terms-model').value;
+ const termsFields = this._fieldsToDict(document.querySelectorAll('#terms .anki-field-value'));
+ const kanjiDeck = document.querySelector('#anki-kanji-deck').value;
+ const kanjiModel = document.querySelector('#anki-kanji-model').value;
+ const kanjiFields = this._fieldsToDict(document.querySelectorAll('#kanji .anki-field-value'));
+
+ const targets = [
+ {action: 'set', path: 'anki.terms.deck', value: termsDeck},
+ {action: 'set', path: 'anki.terms.model', value: termsModel},
+ {action: 'set', path: 'anki.terms.fields', value: termsFields},
+ {action: 'set', path: 'anki.kanji.deck', value: kanjiDeck},
+ {action: 'set', path: 'anki.kanji.model', value: kanjiModel},
+ {action: 'set', path: 'anki.kanji.fields', value: kanjiFields}
+ ];
+
+ await this._settingsController.modifyProfileSettings(targets);
+ }
}