From 2d191bfdbd955a363e7afdc79c7a2b4b11a2e9b7 Mon Sep 17 00:00:00 2001 From: StefanVukovic99 Date: Sun, 5 May 2024 02:30:09 +0200 Subject: add single dictionary handlebars (#814) * add single dictionary handlebars * fix dicts with kanji in title * sort * rename to single-glossary-XYZ * add brief and no dict variants * add docs, only terms no kanji * allow testing single dict handlebars * remove empty comment --- docs/anki-integration.md | 84 +++++++++++----------- .../anki-field-templates-upgrade-v34.handlebars | 59 +++++++++++++++ .../default-anki-field-templates.handlebars | 20 +++++- ext/js/data/anki-template-util.js | 51 +++++++++++++ ext/js/data/anki-util.js | 4 +- ext/js/data/options-util.js | 12 +++- ext/js/display/display-anki.js | 11 +++ ext/js/pages/settings/anki-controller.js | 28 +++++--- ext/js/pages/settings/anki-templates-controller.js | 15 +++- ext/settings.html | 19 +++++ test/options-util.test.js | 2 +- 11 files changed, 247 insertions(+), 58 deletions(-) create mode 100644 ext/data/templates/anki-field-templates-upgrade-v34.handlebars diff --git a/docs/anki-integration.md b/docs/anki-integration.md index cae3d96e..547a73c8 100644 --- a/docs/anki-integration.md +++ b/docs/anki-integration.md @@ -18,49 +18,53 @@ Flashcard fields can be configured with the following steps: 3. Select the type of template to configure by clicking on either the _Terms_ or _Kanji_ tabs. 4. Select the Anki deck and model to use for new creating new flashcards of this type. 5. Fill the model fields with markers corresponding to the information you wish to include (several can be used at - once). Advanced users can also configure the actual [Handlebars](https://handlebarsjs.com/) templates used to create - the flashcard contents (this is strictly optional). + once). +6. _(optional, advanced)_ Users can also configure the actual [Handlebars](https://handlebarsjs.com/) templates used to create + the flashcard contents. #### Markers for Term Cards - | Marker | Description | - | --------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | - | `{audio}` | Audio of the term's pronunciation from one of the audio sources (if available). | - | `{clipboard-image}` | An image which is stored in the system clipboard, if present. | - | `{clipboard-text}` | Text which is stored in the system clipboard, if present. | - | `{cloze-body}` | Raw, inflected term as it appeared before being reduced to dictionary form by Yomitan. | - | `{cloze-body-kana}` | Kana reading for `{cloze-body}`. | - | `{cloze-prefix}` | Fragment of the containing `{sentence}` starting at the beginning of `{sentence}` until the beginning of `{cloze-body}`. | - | `{cloze-suffix}` | Fragment of the containing `{sentence}` starting at the end of `{cloze-body}` until the end of `{sentence}`. | - | `{conjugation}` | Conjugation path from the raw inflected term to the source term. | - | `{dictionary}` | Name of the dictionary from which the card is being created (unavailable in _grouped_ mode). | - | `{document-title}` | Title of the web page that the term appeared in. | - | `{expression}` | Term expressed as kanji (will be displayed in kana if kanji is not available). | - | `{frequencies}` | Frequency information for the term. | - | `{frequency-harmonic-rank}` | The harmonic mean of frequency data for the current term. Defaults to rank 9999999 when frequency data is not found, indicating extremely low rank-based term usage. | - | `{frequency-harmonic-occurrence}` | The harmonic mean of frequency data for the current term. Defaults to 0 occurrences when frequency data is not found, the lowest possible occurrence-based term usage. | - | `{frequency-average-rank}` | The average of frequency data for the current term. Defaults to rank 9999999 when frequency data is not found, indicating extremely low rank-based term usage. | - | `{frequency-average-occurrence}` | The average of frequency data for the current term. Defaults to 0 occurrences when frequency data is not found, the lowest possible occurrence-based term usage. | - | `{furigana}` | Term expressed as kanji with furigana displayed above it (e.g. 日本語にほんご). | - | `{furigana-plain}` | Term expressed as kanji with furigana displayed next to it in brackets (e.g. 日本語[にほんご]). | - | `{glossary}` | List of definitions for the term (output format depends on whether running in _grouped_ mode). | - | `{glossary-brief}` | List of definitions for the term in a more compact format. | - | `{glossary-no-dictionary}` | List of definitions for the term, except the dictionary tag is omitted. | - | `{part-of-speech}` | Part of speech information for the term. | - | `{phonetic-transcriptions}` | List of phonetic transcriptions for the term. | - | `{pitch-accents}` | List of pitch accent downstep notations for the term. | - | `{pitch-accent-graphs}` | List of pitch accent graphs for the term. | - | `{pitch-accent-graphs-jj}` | List of pitch accent graphs for the term (styled after Jidoujisho). | - | `{pitch-accent-positions}` | List of accent downstep positions for the term as a number. | - | `{pitch-accent-categories}` | List of pitch accent categories for the term (e.g. heiban, kifuku, atamadaka, odaka, nakadaka). | - | `{reading}` | Kana reading for the term (empty for terms where the expression is the reading). | - | `{screenshot}` | Screenshot of the web page taken at the time the term was added. | - | `{search-query}` | The full search query shown on the search page. | - | `{selection-text}` | The selected text on the search page or popup. | - | `{sentence}` | Sentence, quote, or phrase that the term appears in from the source content. | - | `{sentence-furigana}` | Sentence, quote, or phrase that the term appears in from the source content, with furigana added. | - | `{tags}` | Grammar and usage tags providing information about the term (unavailable in _grouped_ mode). | - | `{url}` | Address of the web page in which the term appeared in. | + | Marker | Description | + | ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | + | `{audio}` | Audio of the term's pronunciation from one of the audio sources (if available). | + | `{clipboard-image}` | An image which is stored in the system clipboard, if present. | + | `{clipboard-text}` | Text which is stored in the system clipboard, if present. | + | `{cloze-body}` | Raw, inflected term as it appeared before being reduced to dictionary form by Yomitan. | + | `{cloze-body-kana}` | Kana reading for `{cloze-body}`. | + | `{cloze-prefix}` | Fragment of the containing `{sentence}` starting at the beginning of `{sentence}` until the beginning of `{cloze-body}`. | + | `{cloze-suffix}` | Fragment of the containing `{sentence}` starting at the end of `{cloze-body}` until the end of `{sentence}`. | + | `{conjugation}` | Conjugation path from the raw inflected term to the source term. | + | `{dictionary}` | Name of the dictionary from which the card is being created (unavailable in _grouped_ mode). | + | `{document-title}` | Title of the web page that the term appeared in. | + | `{expression}` | Term expressed as kanji (will be displayed in kana if kanji is not available). | + | `{frequencies}` | Frequency information for the term. | + | `{frequency-harmonic-rank}` | The harmonic mean of frequency data for the current term. Defaults to rank 9999999 when frequency data is not found, indicating extremely low rank-based term usage. | + | `{frequency-harmonic-occurrence}` | The harmonic mean of frequency data for the current term. Defaults to 0 occurrences when frequency data is not found, the lowest possible occurrence-based term usage. | + | `{frequency-average-rank}` | The average of frequency data for the current term. Defaults to rank 9999999 when frequency data is not found, indicating extremely low rank-based term usage. | + | `{frequency-average-occurrence}` | The average of frequency data for the current term. Defaults to 0 occurrences when frequency data is not found, the lowest possible occurrence-based term usage. | + | `{furigana}` | Term expressed as kanji with furigana displayed above it (e.g. 日本語にほんご). | + | `{furigana-plain}` | Term expressed as kanji with furigana displayed next to it in brackets (e.g. 日本語[にほんご]). | + | `{glossary}` | List of definitions for the term (output format depends on whether running in _grouped_ mode). | + | `{glossary-brief}` | List of definitions for the term in a more compact format. | + | `{glossary-no-dictionary}` | List of definitions for the term, except the dictionary tag is omitted. | + | `{part-of-speech}` | Part of speech information for the term. | + | `{phonetic-transcriptions}` | List of phonetic transcriptions for the term. | + | `{pitch-accents}` | List of pitch accent downstep notations for the term. | + | `{pitch-accent-graphs}` | List of pitch accent graphs for the term. | + | `{pitch-accent-graphs-jj}` | List of pitch accent graphs for the term (styled after Jidoujisho). | + | `{pitch-accent-positions}` | List of accent downstep positions for the term as a number. | + | `{pitch-accent-categories}` | List of pitch accent categories for the term (e.g. heiban, kifuku, atamadaka, odaka, nakadaka). | + | `{reading}` | Kana reading for the term (empty for terms where the expression is the reading). | + | `{screenshot}` | Screenshot of the web page taken at the time the term was added. | + | `{search-query}` | The full search query shown on the search page. | + | `{selection-text}` | The selected text on the search page or popup. | + | `{sentence}` | Sentence, quote, or phrase that the term appears in from the source content. | + | `{sentence-furigana}` | Sentence, quote, or phrase that the term appears in from the source content, with furigana added. | + | `{single-glossary-DICT-NAME}` | Same as `{glossary}`, but with entries from only a single dictionary. The dictionary name will likely be modified, use the options from the ▼ dropdown. | + | `{single-glossary-DICT-NAME-brief}` | See `{single-glossary-DICT-NAME}` and `{glossary-brief}`. | + | `{single-glossary-DICT-NAME-no-dictionary}` | See `{single-glossary-DICT-NAME}` and `{glossary-no-dictionary}`. | + | `{tags}` | Grammar and usage tags providing information about the term (unavailable in _grouped_ mode). | + | `{url}` | Address of the web page in which the term appeared in. | #### Markers for Kanji Cards diff --git a/ext/data/templates/anki-field-templates-upgrade-v34.handlebars b/ext/data/templates/anki-field-templates-upgrade-v34.handlebars new file mode 100644 index 00000000..5538df85 --- /dev/null +++ b/ext/data/templates/anki-field-templates-upgrade-v34.handlebars @@ -0,0 +1,59 @@ +{{<<<<<<<}} +{{~#*inline "glossary"~}} +
+ {{~#scope~}} + {{~#if (op "===" definition.type "term")~}} + {{~> glossary-single definition brief=brief noDictionaryTag=noDictionaryTag ~}} + {{~else if (op "||" (op "===" definition.type "termGrouped") (op "===" definition.type "termMerged"))~}} + {{~#if (op ">" definition.definitions.length 1)~}} +
    {{~#each definition.definitions~}}
  1. {{~> glossary-single . brief=../brief noDictionaryTag=../noDictionaryTag ~}}
  2. {{~/each~}}
+ {{~else~}} + {{~#each definition.definitions~}}{{~> glossary-single . brief=../brief noDictionaryTag=../noDictionaryTag ~}}{{~/each~}} + {{~/if~}} + {{~else if (op "===" definition.type "kanji")~}} + {{~#if (op ">" definition.glossary.length 1)~}} +
    {{#each definition.glossary}}
  1. {{.}}
  2. {{/each}}
+ {{~else~}} + {{~#each definition.glossary~}}{{.}}{{~/each~}} + {{~/if~}} + {{~/if~}} + {{~/scope~}} +
+{{~/inline~}} +{{=======}} +{{~#*inline "glossary"~}} +
+ {{~#scope~}} + {{~#if (op "===" definition.type "term")~}} + {{~#unless (op "&&" selectedDictionary (op "!=" selectedDictionary definition.dictionary))~}} + {{~> glossary-single definition brief=brief noDictionaryTag=noDictionaryTag ~}} + {{~/unless~}} + {{~else if (op "||" (op "===" definition.type "termGrouped") (op "===" definition.type "termMerged"))~}} + {{~#if (op ">" definition.definitions.length 1)~}} +
    + {{~#each definition.definitions~}} + {{~#unless (op "&&" ../selectedDictionary (op "!=" ../selectedDictionary dictionary))~}} +
  1. + {{~> glossary-single . brief=../brief noDictionaryTag=../noDictionaryTag ~}} +
  2. + {{~/unless~}} + {{~/each~}} +
+ {{~else~}} + {{~#each definition.definitions~}} + {{~#unless (op "&&" ../selectedDictionary (op "!=" ../selectedDictionary dictionary))~}} + {{~> glossary-single . brief=../brief noDictionaryTag=../noDictionaryTag ~}} + {{~/unless~}} + {{~/each~}} + {{~/if~}} + {{~else if (op "===" definition.type "kanji")~}} + {{~#if (op ">" definition.glossary.length 1)~}} +
    {{#each definition.glossary}}
  1. {{.}}
  2. {{/each}}
+ {{~else~}} + {{~#each definition.glossary~}}{{.}}{{~/each~}} + {{~/if~}} + {{~/if~}} + {{~/scope~}} +
+{{~/inline~}} +{{>>>>>>>}} \ No newline at end of file diff --git a/ext/data/templates/default-anki-field-templates.handlebars b/ext/data/templates/default-anki-field-templates.handlebars index 3ca7ba18..3f20f8ee 100644 --- a/ext/data/templates/default-anki-field-templates.handlebars +++ b/ext/data/templates/default-anki-field-templates.handlebars @@ -101,12 +101,26 @@
{{~#scope~}} {{~#if (op "===" definition.type "term")~}} - {{~> glossary-single definition brief=brief noDictionaryTag=noDictionaryTag ~}} + {{~#unless (op "&&" selectedDictionary (op "!=" selectedDictionary definition.dictionary))~}} + {{~> glossary-single definition brief=brief noDictionaryTag=noDictionaryTag ~}} + {{~/unless~}} {{~else if (op "||" (op "===" definition.type "termGrouped") (op "===" definition.type "termMerged"))~}} {{~#if (op ">" definition.definitions.length 1)~}} -
    {{~#each definition.definitions~}}
  1. {{~> glossary-single . brief=../brief noDictionaryTag=../noDictionaryTag ~}}
  2. {{~/each~}}
+
    + {{~#each definition.definitions~}} + {{~#unless (op "&&" ../selectedDictionary (op "!=" ../selectedDictionary dictionary))~}} +
  1. + {{~> glossary-single . brief=../brief noDictionaryTag=../noDictionaryTag ~}} +
  2. + {{~/unless~}} + {{~/each~}} +
{{~else~}} - {{~#each definition.definitions~}}{{~> glossary-single . brief=../brief noDictionaryTag=../noDictionaryTag ~}}{{~/each~}} + {{~#each definition.definitions~}} + {{~#unless (op "&&" ../selectedDictionary (op "!=" ../selectedDictionary dictionary))~}} + {{~> glossary-single . brief=../brief noDictionaryTag=../noDictionaryTag ~}} + {{~/unless~}} + {{~/each~}} {{~/if~}} {{~else if (op "===" definition.type "kanji")~}} {{~#if (op ">" definition.glossary.length 1)~}} diff --git a/ext/js/data/anki-template-util.js b/ext/js/data/anki-template-util.js index 380d87f9..b9c5ba84 100644 --- a/ext/js/data/anki-template-util.js +++ b/ext/js/data/anki-template-util.js @@ -93,3 +93,54 @@ export function getStandardFieldMarkers(type) { throw new Error(`Unsupported type: ${type}`); } } + +/** + * @param {import('settings').ProfileOptions} options + * @returns {string} + */ +export function getDynamicTemplates(options) { + let dynamicTemplates = '\n'; + for (const dictionary of options.dictionaries) { + if (!dictionary.enabled) { continue; } + dynamicTemplates += ` +{{#*inline "single-glossary-${getKebabCase(dictionary.name)}"}} + {{~> glossary selectedDictionary='${dictionary.name}'}} +{{/inline}} + +{{#*inline "single-glossary-${getKebabCase(dictionary.name)}-no-dictionary"}} + {{~> glossary selectedDictionary='${dictionary.name}' noDictionaryTag=true}} +{{/inline}} + +{{#*inline "single-glossary-${getKebabCase(dictionary.name)}-brief"}} + {{~> glossary selectedDictionary='${dictionary.name}' brief=true}} +{{/inline}} +`; + } + return dynamicTemplates; +} + +/** + * @param {import('settings').DictionariesOptions} dictionaries + * @returns {string[]} The list of field markers. + */ +export function getDynamicFieldMarkers(dictionaries) { + const markers = []; + for (const dictionary of dictionaries) { + if (!dictionary.enabled) { continue; } + markers.push(`single-glossary-${getKebabCase(dictionary.name)}`); + } + return markers; +} + +/** + * @param {string} str + * @returns {string} + */ +function getKebabCase(str) { + return str + .replace(/[\s_\u3000]/g, '-') + .replace(/[^\p{L}\p{N}-]/gu, '') + .replace(/--+/g, '-') + .replace(/^-|-$/g, '') + .toLowerCase(); +} diff --git a/ext/js/data/anki-util.js b/ext/js/data/anki-util.js index a063980f..c076c482 100644 --- a/ext/js/data/anki-util.js +++ b/ext/js/data/anki-util.js @@ -19,7 +19,7 @@ import {isObjectNotArray} from '../core/object-utilities.js'; /** @type {RegExp} @readonly */ -const markerPattern = /\{([\w-]+)\}/g; +const markerPattern = /\{([\p{Letter}\p{Number}_-]+)\}/gu; /** * Gets the root deck name of a full deck name. If the deck is a root deck, @@ -65,7 +65,7 @@ export function getFieldMarkers(string) { * @returns {RegExp} A new `RegExp` instance. */ export function cloneFieldMarkerPattern(global) { - return new RegExp(markerPattern.source, global ? 'g' : ''); + return new RegExp(markerPattern.source, global ? 'gu' : 'u'); } /** diff --git a/ext/js/data/options-util.js b/ext/js/data/options-util.js index d6c667aa..b6fb6686 100644 --- a/ext/js/data/options-util.js +++ b/ext/js/data/options-util.js @@ -539,7 +539,8 @@ export class OptionsUtil { this._updateVersion30, this._updateVersion31, this._updateVersion32, - this._updateVersion33 + this._updateVersion33, + this._updateVersion34 ]; /* eslint-enable @typescript-eslint/unbound-method */ if (typeof targetVersion === 'number' && targetVersion < result.length) { @@ -1267,6 +1268,15 @@ export class OptionsUtil { await this._applyAnkiFieldTemplatesPatch(options, '/data/templates/anki-field-templates-upgrade-v33.handlebars'); } + /** + * - Added dynamic handlebars for single dictionaries. + * @type {import('options-util').UpdateFunction} + */ + async _updateVersion34(options) { + await this._applyAnkiFieldTemplatesPatch(options, '/data/templates/anki-field-templates-upgrade-v34.handlebars'); + } + + /** * @param {string} url * @returns {Promise} diff --git a/ext/js/display/display-anki.js b/ext/js/display/display-anki.js index 23f7157f..6a6ec215 100644 --- a/ext/js/display/display-anki.js +++ b/ext/js/display/display-anki.js @@ -21,6 +21,7 @@ import {log} from '../core/log.js'; import {toError} from '../core/to-error.js'; import {deferPromise} from '../core/utilities.js'; import {AnkiNoteBuilder} from '../data/anki-note-builder.js'; +import {getDynamicTemplates} from '../data/anki-template-util.js'; import {invalidNoteId, isNoteDataValid} from '../data/anki-util.js'; import {PopupMenu} from '../dom/popup-menu.js'; import {querySelectorNotNull} from '../dom/query-selector.js'; @@ -659,6 +660,16 @@ export class DisplayAnki { * @returns {Promise} */ async _getAnkiFieldTemplates(options) { + const staticTemplates = await this._getStaticAnkiFieldTemplates(options); + const dynamicTemplates = getDynamicTemplates(options); + return staticTemplates + dynamicTemplates; + } + + /** + * @param {import('settings').ProfileOptions} options + * @returns {Promise} + */ + async _getStaticAnkiFieldTemplates(options) { let templates = options.anki.fieldTemplates; if (typeof templates === 'string') { return templates; } diff --git a/ext/js/pages/settings/anki-controller.js b/ext/js/pages/settings/anki-controller.js index d166d3f0..9640ed62 100644 --- a/ext/js/pages/settings/anki-controller.js +++ b/ext/js/pages/settings/anki-controller.js @@ -21,7 +21,7 @@ import {EventListenerCollection} from '../../core/event-listener-collection.js'; import {ExtensionError} from '../../core/extension-error.js'; import {log} from '../../core/log.js'; import {toError} from '../../core/to-error.js'; -import {getStandardFieldMarkers} from '../../data/anki-template-util.js'; +import {getDynamicFieldMarkers, getStandardFieldMarkers} from '../../data/anki-template-util.js'; import {stringContainsAnyFieldMarker} from '../../data/anki-util.js'; import {getRequiredPermissionsForAnkiFieldValue, hasPermissions, setPermissionsGranted} from '../../data/permissions-util.js'; import {querySelectorNotNull} from '../../dom/query-selector.js'; @@ -85,7 +85,6 @@ export class AnkiController { /** @type {HTMLElement} */ const ankiErrorLog = querySelectorNotNull(document, '#anki-error-log'); - this._setupFieldMenus(); this._ankiErrorMessageDetailsToggle.addEventListener('click', this._onAnkiErrorMessageDetailsToggleClick.bind(this), false); if (this._ankiEnableCheckbox !== null) { @@ -110,14 +109,14 @@ export class AnkiController { ankiApiKeyInput.addEventListener('focus', this._onApiKeyInputFocus.bind(this)); ankiApiKeyInput.addEventListener('blur', this._onApiKeyInputBlur.bind(this)); + await this._updateOptions(); + this._settingsController.on('optionsChanged', this._onOptionsChanged.bind(this)); + const onAnkiSettingChanged = () => { void this._updateOptions(); }; const nodes = [ankiApiKeyInput, ...document.querySelectorAll('[data-setting="anki.enable"]')]; for (const node of nodes) { node.addEventListener('settingChanged', onAnkiSettingChanged); } - - await this._updateOptions(); - this._settingsController.on('optionsChanged', this._onOptionsChanged.bind(this)); } /** @@ -161,7 +160,7 @@ export class AnkiController { /** * @param {import('settings-controller').EventArgument<'optionsChanged'>} details */ - _onOptionsChanged({options: {anki}}) { + _onOptionsChanged({options: {anki, dictionaries}}) { /** @type {?string} */ let apiKey = anki.apiKey; if (apiKey === '') { apiKey = null; } @@ -171,6 +170,8 @@ export class AnkiController { this._selectorObserver.disconnect(); this._selectorObserver.observe(document.documentElement, true); + + this._setupFieldMenus(dictionaries); } /** */ @@ -279,8 +280,10 @@ export class AnkiController { return cardController.isStale(); } - /** */ - _setupFieldMenus() { + /** + * @param {import('settings').DictionariesOptions} dictionaries + */ + _setupFieldMenus(dictionaries) { /** @type {[types: import('dictionary').DictionaryEntryType[], templateName: string][]} */ const fieldMenuTargets = [ [['term'], 'anki-card-terms-field-menu'], @@ -301,11 +304,18 @@ export class AnkiController { return; } + while (container.firstChild) { + container.removeChild(container.firstChild); + } + let markers = []; for (const type of types) { markers.push(...getStandardFieldMarkers(type)); } - markers = [...new Set(markers)]; + if (types.includes('term')) { + markers.push(...getDynamicFieldMarkers(dictionaries)); + } + markers = [...new Set(markers.sort())]; const fragment = document.createDocumentFragment(); for (const marker of markers) { diff --git a/ext/js/pages/settings/anki-templates-controller.js b/ext/js/pages/settings/anki-templates-controller.js index a0a6bd6c..dd96f69c 100644 --- a/ext/js/pages/settings/anki-templates-controller.js +++ b/ext/js/pages/settings/anki-templates-controller.js @@ -19,6 +19,7 @@ import {ExtensionError} from '../../core/extension-error.js'; import {toError} from '../../core/to-error.js'; import {AnkiNoteBuilder} from '../../data/anki-note-builder.js'; +import {getDynamicTemplates} from '../../data/anki-template-util.js'; import {querySelectorNotNull} from '../../dom/query-selector.js'; import {TemplateRendererProxy} from '../../templates/template-renderer-proxy.js'; @@ -244,8 +245,7 @@ export class AnkiTemplatesController { query: sentenceText, fullQuery: sentenceText }; - let template = options.anki.fieldTemplates; - if (typeof template !== 'string') { template = this._defaultFieldTemplates; } + const template = this._getAnkiTemplate(options); const {general: {resultOutputMode, glossaryLayoutMode, compactTags}} = options; const {note, errors} = await this._ankiNoteBuilder.createNote(/** @type {import('anki-note-builder').CreateNoteDetails} */ ({ dictionaryEntry, @@ -293,4 +293,15 @@ export class AnkiTemplatesController { /** @type {HTMLTextAreaElement} */ (this._fieldTemplatesTextarea).dataset.invalid = `${hasError}`; } } + + /** + * @param {import('settings').ProfileOptions} options + * @returns {string} + */ + _getAnkiTemplate(options) { + let staticTemplates = options.anki.fieldTemplates; + if (typeof staticTemplates !== 'string') { staticTemplates = this._defaultFieldTemplates; } + const dynamicTemplates = getDynamicTemplates(options); + return staticTemplates + '\n' + dynamicTemplates; + } } diff --git a/ext/settings.html b/ext/settings.html index 68113d21..3e28436b 100644 --- a/ext/settings.html +++ b/ext/settings.html @@ -3009,6 +3009,25 @@ {reading} Kana reading for the term, or empty for terms where the expression is the reading. + + {single-glossary-DICT-NAME} + + Same as {glossary}, but with entries from only a single dictionary. + The dictionary name will likely be modified, use the options from the ▼ dropdown. + + + + {single-glossary-DICT-NAME-brief} + + See {single-glossary-DICT-NAME} and {glossary-brief}. + + + + {single-glossary-DICT-NAME-no-dictionary} + + See {single-glossary-DICT-NAME} and {glossary-no-dictionary}. + + {tags} Grammar and usage tags providing information about the term. diff --git a/test/options-util.test.js b/test/options-util.test.js index f44dbb4f..8f4dc80e 100644 --- a/test/options-util.test.js +++ b/test/options-util.test.js @@ -605,7 +605,7 @@ function createOptionsUpdatedTestData1() { } ], profileCurrent: 0, - version: 33, + version: 34, global: { database: { prefixWildcardsSupported: false -- cgit v1.2.3