From 637d4a2087b9e93ccd47d689411887b6c40c3992 Mon Sep 17 00:00:00 2001
From: toasted-nutbread <toasted-nutbread@users.noreply.github.com>
Date: Sun, 18 Jul 2021 13:43:11 -0400
Subject: 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
---
 ext/js/display/display-generator.js                |  2 +-
 ext/js/display/sandbox/pronunciation-generator.js  |  2 +-
 ext/js/dom/sandbox/css-style-applier.js            |  2 +-
 ext/js/templates/sandbox/anki-template-renderer.js | 57 ++++++++++++++++++----
 4 files changed, 51 insertions(+), 12 deletions(-)

(limited to 'ext/js')

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 '';
+        }
+    }
 }
-- 
cgit v1.2.3