diff options
| -rw-r--r-- | docs/templates.md | 37 | ||||
| -rw-r--r-- | ext/js/display/display-generator.js | 2 | ||||
| -rw-r--r-- | ext/js/display/sandbox/pronunciation-generator.js | 2 | ||||
| -rw-r--r-- | ext/js/dom/sandbox/css-style-applier.js | 2 | ||||
| -rw-r--r-- | ext/js/templates/sandbox/anki-template-renderer.js | 57 | ||||
| -rw-r--r-- | ext/template-renderer.html | 1 | ||||
| -rw-r--r-- | test/test-anki-note-builder.js | 4 | 
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); |