diff --git a/README.md b/README.md
index 6231179c..b913fe25 100644
--- a/README.md
+++ b/README.md
@@ -163,6 +163,10 @@ Flashcard fields can be configured with the following steps:
`{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.
+ `{pitch-accents}` | List of pitch accent downstep notations for the term.
+ `{pitch-accent-graphs}` | List of pitch accent graphs for the term.
+ `{pitch-accent-positions}` | List of accent downstep positions for the term as a number.
`{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.
`{sentence}` | Sentence, quote, or phrase that the term appears in from the source content.
diff --git a/ext/bg/background.html b/ext/bg/background.html
index a30b55a5..73dbc251 100644
--- a/ext/bg/background.html
+++ b/ext/bg/background.html
@@ -46,6 +46,7 @@
+
diff --git a/ext/bg/data/anki-field-templates-upgrade-v2.handlebars b/ext/bg/data/anki-field-templates-upgrade-v2.handlebars
new file mode 100644
index 00000000..c018094e
--- /dev/null
+++ b/ext/bg/data/anki-field-templates-upgrade-v2.handlebars
@@ -0,0 +1,109 @@
+{{! Pitch Accents }}
+{{#*inline "pitch-accent-item-downstep-notation"}}
+ {{~#scope~}}
+
+ {{~#set "style1a"~}}display:inline-block;position:relative;{{~/set~}}
+ {{~#set "style1b"~}}padding-right:0.1em;margin-right:0.1em;{{~/set~}}
+ {{~#set "style2a"~}}display:block;user-select:none;pointer-events:none;position:absolute;top:0.1em;left:0;right:0;height:0;border-top:0.1em solid;{{~/set~}}
+ {{~#set "style2b"~}}right:-0.1em;height:0.4em;border-right:0.1em solid;{{~/set~}}
+ {{~#each (getKanaMorae reading)~}}
+ {{~#set "style1"}}{{#get "style1a"}}{{/get}}{{/set~}}
+ {{~#set "style2"}}{{/set~}}
+ {{~#if (isMoraPitchHigh @index ../position)}}
+ {{~#set "style2"}}{{#get "style2a"}}{{/get}}{{/set~}}
+ {{~#if (op "!" (isMoraPitchHigh (op "+" @index 1) ../position))~}}
+ {{~#set "style1" (op "+" (get "style1") (get "style1b"))}}{{/set~}}
+ {{~#set "style2" (op "+" (get "style2") (get "style2b"))}}{{/set~}}
+ {{~/if~}}
+ {{~/if~}}
+ {{{.}}}
+ {{~/each~}}
+
+ {{~/scope~}}
+{{/inline}}
+
+{{#*inline "pitch-accent-item-graph-position-x"}}{{#op "+" 25 (op "*" index 50)}}{{/op}}{{/inline}}
+{{#*inline "pitch-accent-item-graph-position-y"}}{{#op "+" 25 (op "?:" (isMoraPitchHigh index position) 0 50)}}{{/op}}{{/inline}}
+{{#*inline "pitch-accent-item-graph-position"}}{{> pitch-accent-item-graph-position-x index=index position=position}} {{> pitch-accent-item-graph-position-y index=index position=position}}{{/inline}}
+{{#*inline "pitch-accent-item-graph"}}
+ {{~#scope~}}
+ {{~#set "morae" (getKanaMorae reading)}}{{/set~}}
+ {{~#set "morae-count" (property (get "morae") "length")}}{{/set~}}
+
+
+
+
+
+
+ pitch-accent-item-graph-position index=@index position=../position~}}
+ {{~#set "cmd" "L"}}{{/set~}}
+ {{~/each~}}
+ ">
+ pitch-accent-item-graph-position index=(get "morae-count") position=position}}">
+ {{#each (get "morae")}}
+
+ {{/each}}
+
+
+ {{~/scope~}}
+{{/inline}}
+
+{{#*inline "pitch-accent-item-position"~}}
+ [{{position}}]
+{{~/inline}}
+
+{{#*inline "pitch-accent-item"}}
+ {{~#if (op "==" format "downstep-notation")~}}
+ {{~> pitch-accent-item-downstep-notation~}}
+ {{~else if (op "==" format "graph")~}}
+ {{~> pitch-accent-item-graph~}}
+ {{~else if (op "==" format "position")~}}
+ {{~> pitch-accent-item-position~}}
+ {{~/if~}}
+{{/inline}}
+
+{{#*inline "pitch-accent-item-disambiguation"}}
+ {{~#scope~}}
+ {{~#set "exclusive" (spread exclusiveExpressions exclusiveReadings)}}{{/set~}}
+ {{~#if (op ">" (property (get "exclusive") "length") 0)~}}
+ {{~#set "separator" ""~}}{{/set~}}
+ ({{#each (get "exclusive")~}}
+ {{~#get "separator"}}{{/get~}}{{{.}}}
+ {{~/each}} only)
+ {{~/if~}}
+ {{~/scope~}}
+{{/inline}}
+
+{{#*inline "pitch-accent-list"}}
+ {{~#if (op ">" pitchCount 0)~}}
+ {{~#if (op ">" pitchCount 1)~}}
{{~/if~}}
+ {{~#each pitches~}}
+ {{~#each pitches~}}
+ {{~#if (op ">" ../../pitchCount 1)~}}{{~/if~}}
+ {{~> pitch-accent-item-disambiguation~}}
+ {{~> pitch-accent-item format=../../format~}}
+ {{~#if (op ">" ../../pitchCount 1)~}} {{~/if~}}
+ {{~/each~}}
+ {{~/each~}}
+ {{~#if (op ">" pitchCount 1)~}} {{~/if~}}
+ {{~else~}}
+ No pitch accent data
+ {{~/if~}}
+{{/inline}}
+
+{{#*inline "pitch-accents"}}
+ {{~> pitch-accent-list format='downstep-notation'~}}
+{{/inline}}
+
+{{#*inline "pitch-accent-graphs"}}
+ {{~> pitch-accent-list format='graph'~}}
+{{/inline}}
+
+{{#*inline "pitch-accent-positions"}}
+ {{~> pitch-accent-list format='position'~}}
+{{/inline}}
+{{! End Pitch Accents }}
diff --git a/ext/bg/data/default-anki-field-templates.handlebars b/ext/bg/data/default-anki-field-templates.handlebars
index 42deae23..b348042c 100644
--- a/ext/bg/data/default-anki-field-templates.handlebars
+++ b/ext/bg/data/default-anki-field-templates.handlebars
@@ -166,4 +166,114 @@
{{~context.document.title~}}
{{/inline}}
+{{! Pitch Accents }}
+{{#*inline "pitch-accent-item-downstep-notation"}}
+ {{~#scope~}}
+
+ {{~#set "style1a"~}}display:inline-block;position:relative;{{~/set~}}
+ {{~#set "style1b"~}}padding-right:0.1em;margin-right:0.1em;{{~/set~}}
+ {{~#set "style2a"~}}display:block;user-select:none;pointer-events:none;position:absolute;top:0.1em;left:0;right:0;height:0;border-top:0.1em solid;{{~/set~}}
+ {{~#set "style2b"~}}right:-0.1em;height:0.4em;border-right:0.1em solid;{{~/set~}}
+ {{~#each (getKanaMorae reading)~}}
+ {{~#set "style1"}}{{#get "style1a"}}{{/get}}{{/set~}}
+ {{~#set "style2"}}{{/set~}}
+ {{~#if (isMoraPitchHigh @index ../position)}}
+ {{~#set "style2"}}{{#get "style2a"}}{{/get}}{{/set~}}
+ {{~#if (op "!" (isMoraPitchHigh (op "+" @index 1) ../position))~}}
+ {{~#set "style1" (op "+" (get "style1") (get "style1b"))}}{{/set~}}
+ {{~#set "style2" (op "+" (get "style2") (get "style2b"))}}{{/set~}}
+ {{~/if~}}
+ {{~/if~}}
+ {{{.}}}
+ {{~/each~}}
+
+ {{~/scope~}}
+{{/inline}}
+
+{{#*inline "pitch-accent-item-graph-position-x"}}{{#op "+" 25 (op "*" index 50)}}{{/op}}{{/inline}}
+{{#*inline "pitch-accent-item-graph-position-y"}}{{#op "+" 25 (op "?:" (isMoraPitchHigh index position) 0 50)}}{{/op}}{{/inline}}
+{{#*inline "pitch-accent-item-graph-position"}}{{> pitch-accent-item-graph-position-x index=index position=position}} {{> pitch-accent-item-graph-position-y index=index position=position}}{{/inline}}
+{{#*inline "pitch-accent-item-graph"}}
+ {{~#scope~}}
+ {{~#set "morae" (getKanaMorae reading)}}{{/set~}}
+ {{~#set "morae-count" (property (get "morae") "length")}}{{/set~}}
+
+
+
+
+
+
+ pitch-accent-item-graph-position index=@index position=../position~}}
+ {{~#set "cmd" "L"}}{{/set~}}
+ {{~/each~}}
+ ">
+ pitch-accent-item-graph-position index=(get "morae-count") position=position}}">
+ {{#each (get "morae")}}
+
+ {{/each}}
+
+
+ {{~/scope~}}
+{{/inline}}
+
+{{#*inline "pitch-accent-item-position"~}}
+ [{{position}}]
+{{~/inline}}
+
+{{#*inline "pitch-accent-item"}}
+ {{~#if (op "==" format "downstep-notation")~}}
+ {{~> pitch-accent-item-downstep-notation~}}
+ {{~else if (op "==" format "graph")~}}
+ {{~> pitch-accent-item-graph~}}
+ {{~else if (op "==" format "position")~}}
+ {{~> pitch-accent-item-position~}}
+ {{~/if~}}
+{{/inline}}
+
+{{#*inline "pitch-accent-item-disambiguation"}}
+ {{~#scope~}}
+ {{~#set "exclusive" (spread exclusiveExpressions exclusiveReadings)}}{{/set~}}
+ {{~#if (op ">" (property (get "exclusive") "length") 0)~}}
+ {{~#set "separator" ""~}}{{/set~}}
+ ({{#each (get "exclusive")~}}
+ {{~#get "separator"}}{{/get~}}{{{.}}}
+ {{~/each}} only)
+ {{~/if~}}
+ {{~/scope~}}
+{{/inline}}
+
+{{#*inline "pitch-accent-list"}}
+ {{~#if (op ">" pitchCount 0)~}}
+ {{~#if (op ">" pitchCount 1)~}}{{~/if~}}
+ {{~#each pitches~}}
+ {{~#each pitches~}}
+ {{~#if (op ">" ../../pitchCount 1)~}}{{~/if~}}
+ {{~> pitch-accent-item-disambiguation~}}
+ {{~> pitch-accent-item format=../../format~}}
+ {{~#if (op ">" ../../pitchCount 1)~}} {{~/if~}}
+ {{~/each~}}
+ {{~/each~}}
+ {{~#if (op ">" pitchCount 1)~}} {{~/if~}}
+ {{~else~}}
+ No pitch accent data
+ {{~/if~}}
+{{/inline}}
+
+{{#*inline "pitch-accents"}}
+ {{~> pitch-accent-list format='downstep-notation'~}}
+{{/inline}}
+
+{{#*inline "pitch-accent-graphs"}}
+ {{~> pitch-accent-list format='graph'~}}
+{{/inline}}
+
+{{#*inline "pitch-accent-positions"}}
+ {{~> pitch-accent-list format='position'~}}
+{{/inline}}
+{{! End Pitch Accents }}
+
{{~> (lookup . "marker") ~}}
diff --git a/ext/bg/js/anki-note-builder.js b/ext/bg/js/anki-note-builder.js
index 7fe8962a..2405543e 100644
--- a/ext/bg/js/anki-note-builder.js
+++ b/ext/bg/js/anki-note-builder.js
@@ -15,6 +15,10 @@
* along with this program. If not, see .
*/
+/* global
+ * DictionaryDataUtil
+ */
+
class AnkiNoteBuilder {
constructor({anki, audioSystem, renderTemplate}) {
this._anki = anki;
@@ -39,9 +43,10 @@ class AnkiNoteBuilder {
}
};
+ const data = this.createNoteData(definition, mode, context, options);
const formattedFieldValuePromises = [];
for (const [, fieldValue] of modeOptionsFieldEntries) {
- const formattedFieldValuePromise = this.formatField(fieldValue, definition, mode, context, options, templates, null);
+ const formattedFieldValuePromise = this.formatField(fieldValue, data, templates, null);
formattedFieldValuePromises.push(formattedFieldValuePromise);
}
@@ -55,10 +60,14 @@ class AnkiNoteBuilder {
return note;
}
- async formatField(field, definition, mode, context, options, templates, errors=null) {
- const data = {
+ createNoteData(definition, mode, context, options) {
+ const pitches = DictionaryDataUtil.getPitchAccentInfos(definition);
+ const pitchCount = pitches.reduce((i, v) => i + v.pitches.length, 0);
+ return {
marker: null,
definition,
+ pitches,
+ pitchCount,
group: options.general.resultOutputMode === 'group',
merge: options.general.resultOutputMode === 'merge',
modeTermKanji: mode === 'term-kanji',
@@ -67,6 +76,9 @@ class AnkiNoteBuilder {
compactGlossaries: options.general.compactGlossaries,
context
};
+ }
+
+ async formatField(field, data, templates, errors=null) {
const pattern = /\{([\w-]+)\}/g;
return await AnkiNoteBuilder.stringReplaceAsync(field, pattern, async (g0, marker) => {
data.marker = marker;
diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js
index ffea96f8..0d83f428 100644
--- a/ext/bg/js/options.js
+++ b/ext/bg/js/options.js
@@ -380,31 +380,83 @@ class OptionsUtil {
return [
{
async: false,
- update: (options) => {
- // Version 1 changes:
- // Added options.global.database.prefixWildcardsSupported = false
- options.global = {
- database: {
- prefixWildcardsSupported: false
- }
- };
- return options;
- }
+ update: this._updateVersion1.bind(this)
},
{
async: false,
- update: (options) => {
- // Version 2 changes:
- // Legacy profile update process moved into this upgrade function.
- for (const profile of options.profiles) {
- if (!Array.isArray(profile.conditionGroups)) {
- profile.conditionGroups = [];
- }
- profile.options = this._legacyProfileUpdateUpdateVersion(profile.options);
- }
- return options;
- }
+ update: this._updateVersion2.bind(this)
+ },
+ {
+ async: true,
+ update: this._updateVersion3.bind(this)
}
];
}
+
+ static _updateVersion1(options) {
+ // Version 1 changes:
+ // Added options.global.database.prefixWildcardsSupported = false.
+ options.global = {
+ database: {
+ prefixWildcardsSupported: false
+ }
+ };
+ return options;
+ }
+
+ static _updateVersion2(options) {
+ // Version 2 changes:
+ // Legacy profile update process moved into this upgrade function.
+ for (const profile of options.profiles) {
+ if (!Array.isArray(profile.conditionGroups)) {
+ profile.conditionGroups = [];
+ }
+ profile.options = this._legacyProfileUpdateUpdateVersion(profile.options);
+ }
+ return options;
+ }
+
+ static async _updateVersion3(options) {
+ // Version 3 changes:
+ // Pitch accent Anki field templates added.
+ let addition = null;
+ for (const {options: profileOptions} of options.profiles) {
+ const fieldTemplates = profileOptions.anki.fieldTemplates;
+ if (fieldTemplates !== null) {
+ if (addition === null) {
+ addition = await this._updateVersion3GetAnkiFieldTemplates();
+ }
+ profileOptions.anki.fieldTemplates = this._addFieldTemplatesBeforeEnd(fieldTemplates, addition);
+ }
+ }
+ return options;
+ }
+
+ static async _updateVersion3GetAnkiFieldTemplates() {
+ const url = chrome.runtime.getURL('/bg/data/anki-field-templates-upgrade-v2.handlebars');
+ const response = await fetch(url, {
+ method: 'GET',
+ mode: 'no-cors',
+ cache: 'default',
+ credentials: 'omit',
+ redirect: 'follow',
+ referrerPolicy: 'no-referrer'
+ });
+ return await response.text();
+ }
+
+ static async _addFieldTemplatesBeforeEnd(fieldTemplates, addition) {
+ const pattern = /[ \t]*\{\{~?>\s*\(\s*lookup\s*\.\s*"marker"\s*\)\s*~?\}\}/;
+ const newline = '\n';
+ let replaced = false;
+ fieldTemplates = fieldTemplates.replace(pattern, (g0) => {
+ replaced = true;
+ return `${addition}${newline}${g0}`;
+ });
+ if (!replaced) {
+ fieldTemplates += newline;
+ fieldTemplates += addition;
+ }
+ return fieldTemplates;
+ }
}
diff --git a/ext/bg/js/settings/anki-templates.js b/ext/bg/js/settings/anki-templates.js
index 88d4fe04..4e004308 100644
--- a/ext/bg/js/settings/anki-templates.js
+++ b/ext/bg/js/settings/anki-templates.js
@@ -144,7 +144,8 @@ class AnkiTemplatesController {
let templates = options.anki.fieldTemplates;
if (typeof templates !== 'string') { templates = this._defaultFieldTemplates; }
const ankiNoteBuilder = new AnkiNoteBuilder({renderTemplate: api.templateRender.bind(api)});
- result = await ankiNoteBuilder.formatField(field, definition, mode, context, options, templates, exceptions);
+ const data = ankiNoteBuilder.createNoteData(definition, mode, context, options);
+ result = await ankiNoteBuilder.formatField(field, data, templates, exceptions);
}
} catch (e) {
exceptions.push(e);
diff --git a/ext/bg/js/settings/anki.js b/ext/bg/js/settings/anki.js
index 51dabba4..ac4c5455 100644
--- a/ext/bg/js/settings/anki.js
+++ b/ext/bg/js/settings/anki.js
@@ -54,6 +54,9 @@ class AnkiController {
'furigana-plain',
'glossary',
'glossary-brief',
+ 'pitch-accents',
+ 'pitch-accent-graphs',
+ 'pitch-accent-positions',
'reading',
'screenshot',
'sentence',
@@ -63,6 +66,9 @@ class AnkiController {
case 'kanji':
return [
'character',
+ 'cloze-body',
+ 'cloze-prefix',
+ 'cloze-suffix',
'dictionary',
'document-title',
'glossary',
diff --git a/ext/bg/js/template-renderer.js b/ext/bg/js/template-renderer.js
index ef05cbd8..59af74c8 100644
--- a/ext/bg/js/template-renderer.js
+++ b/ext/bg/js/template-renderer.js
@@ -82,7 +82,10 @@ class TemplateRenderer {
['get', this._get.bind(this)],
['set', this._set.bind(this)],
['scope', this._scope.bind(this)],
- ['isMoraPitchHigh', this._isMoraPitchHigh.bind(this)]
+ ['property', this._property.bind(this)],
+ ['noop', this._noop.bind(this)],
+ ['isMoraPitchHigh', this._isMoraPitchHigh.bind(this)],
+ ['getKanaMorae', this._getKanaMorae.bind(this)]
];
for (const [name, helper] of helpers) {
@@ -316,21 +319,20 @@ class TemplateRenderer {
_set(context, ...args) {
switch (args.length) {
case 2:
- {
- const [key, options] = args;
- const value = options.fn(context);
- this._stateStack[this._stateStack.length - 1].set(key, value);
- return value;
- }
+ {
+ const [key, options] = args;
+ const value = options.fn(context);
+ this._stateStack[this._stateStack.length - 1].set(key, value);
+ }
+ break;
case 3:
- {
- const [key, value] = args;
- this._stateStack[this._stateStack.length - 1].set(key, value);
- return value;
- }
- default:
- return void 0;
+ {
+ const [key, value] = args;
+ this._stateStack[this._stateStack.length - 1].set(key, value);
+ }
+ break;
}
+ return '';
}
_scope(context, options) {
@@ -344,7 +346,30 @@ class TemplateRenderer {
}
}
- _isMoraPitchHigh(context, position, index) {
+ _property(context, ...args) {
+ const ii = args.length - 1;
+ if (ii <= 0) { return void 0; }
+
+ try {
+ let value = args[0];
+ for (let i = 1; i < ii; ++i) {
+ value = value[args[i]];
+ }
+ return value;
+ } catch (e) {
+ return void 0;
+ }
+ }
+
+ _noop(context, options) {
+ return options.fn(context);
+ }
+
+ _isMoraPitchHigh(context, index, position) {
return jp.isMoraPitchHigh(index, position);
}
+
+ _getKanaMorae(context, text) {
+ return jp.getKanaMorae(`${text}`);
+ }
}
diff --git a/ext/bg/settings.html b/ext/bg/settings.html
index 260c1b46..e29b1f45 100644
--- a/ext/bg/settings.html
+++ b/ext/bg/settings.html
@@ -1162,6 +1162,7 @@
+