aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortoasted-nutbread <toasted-nutbread@users.noreply.github.com>2021-07-18 13:43:11 -0400
committerGitHub <noreply@github.com>2021-07-18 13:43:11 -0400
commit637d4a2087b9e93ccd47d689411887b6c40c3992 (patch)
treef893a08502b79f8cc03b186394665dd721377bc4
parent10a9da4d31b4837d8c48e3cd1c36a7b760691d74 (diff)
Pronunciation template helper (#1840)
* Rename field * Set up pronunication components * Fix documentation * Rename function * Update test dependencies * Fix constructor * Log errors * Add pronunciation helper * Add styleApplier argument to _getHtml/_normalizeHtml * Use getAttribute for 'class' to support namespaced elements (e.g. svg) * Update format name * Add optional tag * Update docs
-rw-r--r--docs/templates.md37
-rw-r--r--ext/js/display/display-generator.js2
-rw-r--r--ext/js/display/sandbox/pronunciation-generator.js2
-rw-r--r--ext/js/dom/sandbox/css-style-applier.js2
-rw-r--r--ext/js/templates/sandbox/anki-template-renderer.js57
-rw-r--r--ext/template-renderer.html1
-rw-r--r--test/test-anki-note-builder.js4
7 files changed, 91 insertions, 14 deletions
diff --git a/docs/templates.md b/docs/templates.md
index 8ee3bb0c..f1777fde 100644
--- a/docs/templates.md
+++ b/docs/templates.md
@@ -3,7 +3,7 @@
## Helpers
Yomichan supports several custom Handlebars helpers for rendering templates.
-The source code for these templates can be found [here](../ext/js/templates/sandbox/template-renderer.js).
+The source code for these templates can be found [here](../ext/js/templates/sandbox/anki-template-renderer.js).
### `dumpObject`
@@ -689,7 +689,7 @@ These functions are used together in order to request media and other types of o
The type of media to check for.
* _`args`_ <br>
Additional arguments for the media. The arguments depend on the media type.
- * _`escape`_ <br>
+ * _`escape`_ _(optional)_ <br>
Whether or not the resulting text should be HTML-escaped. If omitted, defaults to `true`.
**Available media types and arguments**
@@ -742,6 +742,39 @@ These functions are used together in order to request media and other types of o
</details>
+### `pronunciation`
+
+Converts pronunciation information into a formatted HTML content string. The display layout is the
+same as the system used for generating popup and search page dictionary entries.
+
+<details>
+ <summary>Syntax:</summary>
+
+ <code>{{#pronunciation <i>format=string</i> <i>reading=string</i> <i>downstepPosition=integer</i> <i>[nasalPositions=array]</i> <i>[devoicePositions=array]</i>}}{{/pronunciation}}</code><br>
+
+ * _`format`_ <br>
+ The format of the HTML to generate. This can be any of the following values:
+ * `'text'`
+ * `'graph'`
+ * `'position'`
+ * _`reading`_ <br>
+ The kana reading of the term.
+ * _`downstepPosition`_ <br>
+ The mora position of the downstep in the reading.
+ * _`nasalPositions`_ _(optional)_ <br>
+ An array of indices of mora that have a nasal pronunciation.
+ * _`devoicePositions`_ _(optional)_ <br>
+ An array of indices of mora that are devoiced.
+</details>
+<details>
+ <summary>Example:</summary>
+
+ ```handlebars
+ {{~#pronunciation format='text' reading='よむ' downstepPosition=1~}}{{~/pronunciation~}}
+ ```
+</details>
+
+
## Legacy Helpers
Yomichan has historically used Handlebars templates to generate the HTML used on the search page and results popup.
diff --git a/ext/js/display/display-generator.js b/ext/js/display/display-generator.js
index 2421b88a..11a0a9d3 100644
--- a/ext/js/display/display-generator.js
+++ b/ext/js/display/display-generator.js
@@ -477,7 +477,7 @@ class DisplayGenerator {
this._createPitchAccentDisambiguations(n, exclusiveTerms, exclusiveReadings);
n = node.querySelector('.pronunciation-downstep-notation-container');
- n.appendChild(this._pronunciationGenerator.createPronunciationDownstepNotation(position));
+ n.appendChild(this._pronunciationGenerator.createPronunciationDownstepPosition(position));
n = node.querySelector('.pronunciation-text-container');
n.lang = 'ja';
diff --git a/ext/js/display/sandbox/pronunciation-generator.js b/ext/js/display/sandbox/pronunciation-generator.js
index bab36add..3739e716 100644
--- a/ext/js/display/sandbox/pronunciation-generator.js
+++ b/ext/js/display/sandbox/pronunciation-generator.js
@@ -144,7 +144,7 @@ class PronunciationGenerator {
return svg;
}
- createPronunciationDownstepNotation(downstepPosition) {
+ createPronunciationDownstepPosition(downstepPosition) {
downstepPosition = `${downstepPosition}`;
const n1 = document.createElement('span');
diff --git a/ext/js/dom/sandbox/css-style-applier.js b/ext/js/dom/sandbox/css-style-applier.js
index c617fead..84b8450b 100644
--- a/ext/js/dom/sandbox/css-style-applier.js
+++ b/ext/js/dom/sandbox/css-style-applier.js
@@ -54,7 +54,7 @@ class CssStyleApplier {
applyClassStyles(elements) {
const elementStyles = [];
for (const element of elements) {
- const {className} = element;
+ const className = element.getAttribute('class');
if (className.length === 0) { continue; }
let cssTextNew = '';
for (const {selectorText, styles} of this._getRulesForClass(className)) {
diff --git a/ext/js/templates/sandbox/anki-template-renderer.js b/ext/js/templates/sandbox/anki-template-renderer.js
index 907ab0fa..43092ec2 100644
--- a/ext/js/templates/sandbox/anki-template-renderer.js
+++ b/ext/js/templates/sandbox/anki-template-renderer.js
@@ -21,6 +21,7 @@
* DictionaryDataUtil
* Handlebars
* JapaneseUtil
+ * PronunciationGenerator
* StructuredContentGenerator
* TemplateRenderer
* TemplateRendererMediaProvider
@@ -35,11 +36,13 @@ class AnkiTemplateRenderer {
* Creates a new instance of the class.
*/
constructor() {
- this._cssStyleApplier = new CssStyleApplier('/data/structured-content-style.json');
+ this._structuredContentStyleApplier = new CssStyleApplier('/data/structured-content-style.json');
+ this._pronunciationStyleApplier = new CssStyleApplier('/data/pronunciation-style.json');
this._japaneseUtil = new JapaneseUtil(null);
this._templateRenderer = new TemplateRenderer();
this._ankiNoteDataCreator = new AnkiNoteDataCreator(this._japaneseUtil);
this._mediaProvider = new TemplateRendererMediaProvider();
+ this._pronunciationGenerator = new PronunciationGenerator(this._japaneseUtil);
this._stateStack = null;
this._requirements = null;
this._cleanupCallbacks = null;
@@ -83,7 +86,8 @@ class AnkiTemplateRenderer {
['pitchCategories', this._pitchCategories.bind(this)],
['formatGlossary', this._formatGlossary.bind(this)],
['hasMedia', this._hasMedia.bind(this)],
- ['getMedia', this._getMedia.bind(this)]
+ ['getMedia', this._getMedia.bind(this)],
+ ['pronunciation', this._pronunciation.bind(this)]
]);
this._templateRenderer.registerDataType('ankiNote', {
modifier: ({marker, commonData}) => this._ankiNoteDataCreator.create(marker, commonData),
@@ -93,7 +97,10 @@ class AnkiTemplateRenderer {
this._onRenderSetup.bind(this),
this._onRenderCleanup.bind(this)
);
- await this._cssStyleApplier.prepare();
+ await Promise.all([
+ this._structuredContentStyleApplier.prepare(),
+ this._pronunciationStyleApplier.prepare()
+ ]);
}
// Private
@@ -453,16 +460,16 @@ class AnkiTemplateRenderer {
return element;
}
- _getHtml(node) {
+ _getHtml(node, styleApplier) {
const container = this._getTemporaryElement();
container.appendChild(node);
- this._normalizeHtml(container);
+ this._normalizeHtml(container, styleApplier);
const result = container.innerHTML;
container.textContent = '';
return result;
}
- _normalizeHtml(root) {
+ _normalizeHtml(root, styleApplier) {
const {ELEMENT_NODE, TEXT_NODE} = Node;
const treeWalker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT);
const elements = [];
@@ -479,7 +486,7 @@ class AnkiTemplateRenderer {
break;
}
}
- this._cssStyleApplier.applyClassStyles(elements);
+ styleApplier.applyClassStyles(elements);
for (const element of elements) {
const {dataset} = element;
for (const key of Object.keys(dataset)) {
@@ -532,13 +539,13 @@ class AnkiTemplateRenderer {
_formatGlossaryImage(content, dictionary, data) {
const structuredContentGenerator = this._createStructuredContentGenerator(data);
const node = structuredContentGenerator.createDefinitionImage(content, dictionary);
- return this._getHtml(node);
+ return this._getHtml(node, this._structuredContentStyleApplier);
}
_formatStructuredContent(content, dictionary, data) {
const structuredContentGenerator = this._createStructuredContentGenerator(data);
const node = structuredContentGenerator.createStructuredContent(content.content, dictionary);
- return node !== null ? this._getHtml(node) : '';
+ return node !== null ? this._getHtml(node, this._structuredContentStyleApplier) : '';
}
_hasMedia(context, ...args) {
@@ -552,4 +559,36 @@ class AnkiTemplateRenderer {
const options = args[ii];
return this._mediaProvider.getMedia(options.data.root, args.slice(0, ii), options.hash);
}
+
+ _pronunciation(context, ...args) {
+ const ii = args.length - 1;
+ const options = args[ii];
+ let {format, reading, downstepPosition, nasalPositions, devoicePositions} = options.hash;
+
+ if (typeof reading !== 'string' || reading.length === 0) { return ''; }
+ if (typeof downstepPosition !== 'number') { return ''; }
+ if (!Array.isArray(nasalPositions)) { nasalPositions = []; }
+ if (!Array.isArray(devoicePositions)) { devoicePositions = []; }
+ const morae = this._japaneseUtil.getKanaMorae(reading);
+
+ switch (format) {
+ case 'text':
+ return this._getHtml(
+ this._pronunciationGenerator.createPronunciationText(morae, downstepPosition, nasalPositions, devoicePositions),
+ this._pronunciationStyleApplier
+ );
+ case 'graph':
+ return this._getHtml(
+ this._pronunciationGenerator.createPronunciationGraph(morae, downstepPosition),
+ this._pronunciationStyleApplier
+ );
+ case 'position':
+ return this._getHtml(
+ this._pronunciationGenerator.createPronunciationDownstepPosition(downstepPosition),
+ this._pronunciationStyleApplier
+ );
+ default:
+ return '';
+ }
+ }
}
diff --git a/ext/template-renderer.html b/ext/template-renderer.html
index 2db61d05..19595ec4 100644
--- a/ext/template-renderer.html
+++ b/ext/template-renderer.html
@@ -18,6 +18,7 @@
<script src="/lib/handlebars.min.js"></script>
<script src="/js/data/sandbox/anki-note-data-creator.js"></script>
+<script src="/js/display/sandbox/pronunciation-generator.js"></script>
<script src="/js/display/sandbox/structured-content-generator.js"></script>
<script src="/js/dom/sandbox/css-style-applier.js"></script>
<script src="/js/language/sandbox/dictionary-data-util.js"></script>
diff --git a/test/test-anki-note-builder.js b/test/test-anki-note-builder.js
index b8002c4b..45087134 100644
--- a/test/test-anki-note-builder.js
+++ b/test/test-anki-note-builder.js
@@ -44,6 +44,7 @@ async function createVM() {
'js/data/anki-note-builder.js',
'js/data/anki-util.js',
'js/dom/sandbox/css-style-applier.js',
+ 'js/display/sandbox/pronunciation-generator.js',
'js/display/sandbox/structured-content-generator.js',
'js/templates/sandbox/anki-template-renderer.js',
'js/templates/sandbox/template-renderer.js',
@@ -234,6 +235,9 @@ async function getRenderResults(dictionaryEntries, type, mode, template, AnkiNot
compactTags: false
});
if (!write) {
+ for (const error of errors) {
+ console.error(error);
+ }
assert.strictEqual(errors.length, 0);
}
results.push(noteFields);