diff options
Diffstat (limited to 'ext')
-rw-r--r-- | ext/data/templates/anki-field-templates-upgrade-v34.handlebars | 59 | ||||
-rw-r--r-- | ext/data/templates/default-anki-field-templates.handlebars | 20 | ||||
-rw-r--r-- | ext/js/data/anki-template-util.js | 51 | ||||
-rw-r--r-- | ext/js/data/anki-util.js | 4 | ||||
-rw-r--r-- | ext/js/data/options-util.js | 12 | ||||
-rw-r--r-- | ext/js/display/display-anki.js | 11 | ||||
-rw-r--r-- | ext/js/pages/settings/anki-controller.js | 28 | ||||
-rw-r--r-- | ext/js/pages/settings/anki-templates-controller.js | 15 | ||||
-rw-r--r-- | ext/settings.html | 19 |
9 files changed, 202 insertions, 17 deletions
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"~}} + <div style="text-align: left;"> + {{~#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)~}} + <ol>{{~#each definition.definitions~}}<li>{{~> glossary-single . brief=../brief noDictionaryTag=../noDictionaryTag ~}}</li>{{~/each~}}</ol> + {{~else~}} + {{~#each definition.definitions~}}{{~> glossary-single . brief=../brief noDictionaryTag=../noDictionaryTag ~}}{{~/each~}} + {{~/if~}} + {{~else if (op "===" definition.type "kanji")~}} + {{~#if (op ">" definition.glossary.length 1)~}} + <ol>{{#each definition.glossary}}<li>{{.}}</li>{{/each}}</ol> + {{~else~}} + {{~#each definition.glossary~}}{{.}}{{~/each~}} + {{~/if~}} + {{~/if~}} + {{~/scope~}} + </div> +{{~/inline~}} +{{=======}} +{{~#*inline "glossary"~}} + <div style="text-align: left;"> + {{~#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)~}} + <ol> + {{~#each definition.definitions~}} + {{~#unless (op "&&" ../selectedDictionary (op "!=" ../selectedDictionary dictionary))~}} + <li> + {{~> glossary-single . brief=../brief noDictionaryTag=../noDictionaryTag ~}} + </li> + {{~/unless~}} + {{~/each~}} + </ol> + {{~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)~}} + <ol>{{#each definition.glossary}}<li>{{.}}</li>{{/each}}</ol> + {{~else~}} + {{~#each definition.glossary~}}{{.}}{{~/each~}} + {{~/if~}} + {{~/if~}} + {{~/scope~}} + </div> +{{~/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 @@ <div style="text-align: left;"> {{~#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)~}} - <ol>{{~#each definition.definitions~}}<li>{{~> glossary-single . brief=../brief noDictionaryTag=../noDictionaryTag ~}}</li>{{~/each~}}</ol> + <ol> + {{~#each definition.definitions~}} + {{~#unless (op "&&" ../selectedDictionary (op "!=" ../selectedDictionary dictionary))~}} + <li> + {{~> glossary-single . brief=../brief noDictionaryTag=../noDictionaryTag ~}} + </li> + {{~/unless~}} + {{~/each~}} + </ol> {{~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) { @@ -1268,6 +1269,15 @@ export class OptionsUtil { } /** + * - 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<chrome.tabs.Tab>} */ 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<string>} */ async _getAnkiFieldTemplates(options) { + const staticTemplates = await this._getStaticAnkiFieldTemplates(options); + const dynamicTemplates = getDynamicTemplates(options); + return staticTemplates + dynamicTemplates; + } + + /** + * @param {import('settings').ProfileOptions} options + * @returns {Promise<string>} + */ + 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 @@ -3010,6 +3010,25 @@ <td>Kana reading for the term, or empty for terms where the expression is the reading.</td> </tr> <tr> + <td><code class="anki-field-marker">{single-glossary-DICT-NAME}</code></td> + <td> + Same as <code class="anki-field-marker">{glossary}</code>, but with entries from only a single dictionary. + The dictionary name will likely be modified, use the options from the ▼ dropdown. + </td> + </tr> + <tr> + <td><code class="anki-field-marker">{single-glossary-DICT-NAME-brief}</code></td> + <td> + See <code class="anki-field-marker">{single-glossary-DICT-NAME}</code> and <code class="anki-field-marker">{glossary-brief}</code>. + </td> + </tr> + <tr> + <td><code class="anki-field-marker">{single-glossary-DICT-NAME-no-dictionary}</code></td> + <td> + See <code class="anki-field-marker">{single-glossary-DICT-NAME}</code> and <code class="anki-field-marker">{glossary-no-dictionary}</code>. + </td> + </tr> + <tr> <td><code class="anki-field-marker">{tags}</code></td> <td>Grammar and usage tags providing information about the term.</td> </tr> |