Skip to content

Commit

Permalink
add single dictionary handlebars
Browse files Browse the repository at this point in the history
* 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

<rikaitan.link>MmQxOTFiZmRiZDk1NWEzNjNlN2FmZGM3OWM3YTJiNGIxMWEyZTliNwo=</rikaitan.link>
  • Loading branch information
StefanVukovic99 authored and tatsumoto-ren committed May 6, 2024
1 parent f7eb4ee commit 7291f99
Show file tree
Hide file tree
Showing 11 changed files with 246 additions and 56 deletions.
81 changes: 43 additions & 38 deletions docs/anki-integration.md

Large diffs are not rendered by default.

59 changes: 59 additions & 0 deletions ext/data/templates/anki-field-templates-upgrade-v34.handlebars
Original file line number Diff line number Diff line change
@@ -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~}}
{{>>>>>>>}}
20 changes: 17 additions & 3 deletions ext/data/templates/default-anki-field-templates.handlebars
Original file line number Diff line number Diff line change
Expand Up @@ -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)~}}
Expand Down
51 changes: 51 additions & 0 deletions ext/js/data/anki-template-util.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
4 changes: 2 additions & 2 deletions ext/js/data/anki-util.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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');
}

/**
Expand Down
12 changes: 11 additions & 1 deletion ext/js/data/options-util.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -1254,6 +1255,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<chrome.tabs.Tab>}
Expand Down
11 changes: 11 additions & 0 deletions ext/js/display/display-anki.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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; }

Expand Down
28 changes: 19 additions & 9 deletions ext/js/pages/settings/anki-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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) {
Expand All @@ -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));
}

/**
Expand Down Expand Up @@ -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; }
Expand All @@ -171,6 +170,8 @@ export class AnkiController {

this._selectorObserver.disconnect();
this._selectorObserver.observe(document.documentElement, true);

this._setupFieldMenus(dictionaries);
}

/** */
Expand Down Expand Up @@ -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'],
Expand All @@ -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) {
Expand Down
15 changes: 13 additions & 2 deletions ext/js/pages/settings/anki-templates-controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
}
}
19 changes: 19 additions & 0 deletions ext/settings.html
Original file line number Diff line number Diff line change
Expand Up @@ -3011,6 +3011,25 @@ <h1>Rikaitan Settings</h1>
<td><code class="anki-field-marker">{reading}</code></td>
<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>
Expand Down
2 changes: 1 addition & 1 deletion test/options-util.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -605,7 +605,7 @@ function createOptionsUpdatedTestData1() {
}
],
profileCurrent: 0,
version: 33,
version: 34,
global: {
database: {
prefixWildcardsSupported: false
Expand Down

0 comments on commit 7291f99

Please sign in to comment.