diff options
Diffstat (limited to 'ext')
| -rw-r--r-- | ext/data/templates/anki-field-templates-upgrade-v13.handlebars | 10 | ||||
| -rw-r--r-- | ext/data/templates/default-anki-field-templates.handlebars | 10 | ||||
| -rw-r--r-- | ext/js/data/anki-note-builder.js | 66 | ||||
| -rw-r--r-- | ext/js/data/options-util.js | 1 | ||||
| -rw-r--r-- | ext/js/display/display-anki.js | 14 | ||||
| -rw-r--r-- | ext/js/display/display.js | 2 | ||||
| -rw-r--r-- | ext/js/pages/settings/anki-controller.js | 2 | ||||
| -rw-r--r-- | ext/js/pages/settings/anki-templates-controller.js | 3 | ||||
| -rw-r--r-- | ext/js/templates/template-renderer-media-provider.js | 34 | ||||
| -rw-r--r-- | ext/settings.html | 4 | 
10 files changed, 125 insertions, 21 deletions
| diff --git a/ext/data/templates/anki-field-templates-upgrade-v13.handlebars b/ext/data/templates/anki-field-templates-upgrade-v13.handlebars index 78820b95..25007030 100644 --- a/ext/data/templates/anki-field-templates-upgrade-v13.handlebars +++ b/ext/data/templates/anki-field-templates-upgrade-v13.handlebars @@ -2,6 +2,16 @@      {{~#if (hasMedia "selectionText")}}{{#getMedia "selectionText" format="text"}}{{/getMedia}}{{/if~}}  {{/inline}} +{{#*inline "sentence-furigana"}} +    {{~#if definition.cloze~}} +        {{~#if (hasMedia "textFurigana" definition.cloze.sentence)~}} +            {{#getMedia "textFurigana" definition.cloze.sentence format="html"}}{{/getMedia}} +        {{~else~}} +            {{definition.cloze.sentence}} +        {{~/if~}} +    {{~/if~}} +{{/inline}} +  {{<<<<<<<}}  {{#each glossary}}{{#multiLine}}{{.}}{{/multiLine}}{{/each}}  {{=======}} diff --git a/ext/data/templates/default-anki-field-templates.handlebars b/ext/data/templates/default-anki-field-templates.handlebars index c9ee2833..cdbec87d 100644 --- a/ext/data/templates/default-anki-field-templates.handlebars +++ b/ext/data/templates/default-anki-field-templates.handlebars @@ -382,4 +382,14 @@      {{~#if (hasMedia "selectionText")}}{{#getMedia "selectionText" format="text"}}{{/getMedia}}{{/if~}}  {{/inline}} +{{#*inline "sentence-furigana"}} +    {{~#if definition.cloze~}} +        {{~#if (hasMedia "textFurigana" definition.cloze.sentence)~}} +            {{#getMedia "textFurigana" definition.cloze.sentence format="html"}}{{/getMedia}} +        {{~else~}} +            {{definition.cloze.sentence}} +        {{~/if~}} +    {{~/if~}} +{{/inline}} +  {{~> (lookup . "marker") ~}} diff --git a/ext/js/data/anki-note-builder.js b/ext/js/data/anki-note-builder.js index 23dd648b..02aa7969 100644 --- a/ext/js/data/anki-note-builder.js +++ b/ext/js/data/anki-note-builder.js @@ -21,7 +21,8 @@   */  class AnkiNoteBuilder { -    constructor() { +    constructor({japaneseUtil}) { +        this._japaneseUtil = japaneseUtil;          this._markerPattern = AnkiUtil.cloneFieldMarkerPattern(true);          this._templateRenderer = new TemplateRendererProxy();          this._batchedRequests = []; @@ -284,6 +285,7 @@ class AnkiNoteBuilder {          let injectClipboardImage = false;          let injectClipboardText = false;          let injectSelectionText = false; +        const textFuriganaDetails = [];          const dictionaryMediaDetails = [];          for (const requirement of requirements) {              const {type} = requirement; @@ -293,6 +295,12 @@ class AnkiNoteBuilder {                  case 'clipboardImage': injectClipboardImage = true; break;                  case 'clipboardText': injectClipboardText = true; break;                  case 'selectionText': injectSelectionText = true; break; +                case 'textFurigana': +                    { +                        const {text, readingMode} = requirement; +                        textFuriganaDetails.push({text, readingMode}); +                    } +                    break;                  case 'dictionaryMedia':                      {                          const {dictionary, path} = requirement; @@ -323,6 +331,14 @@ class AnkiNoteBuilder {                  }              }          } +        let textFuriganaPromise = null; +        if (textFuriganaDetails.length > 0) { +            const textParsingOptions = mediaOptions.textParsing; +            if (typeof textParsingOptions === 'object' && textParsingOptions !== null) { +                const {optionsContext, scanLength} = textParsingOptions; +                textFuriganaPromise = this._getTextFurigana(textFuriganaDetails, optionsContext, scanLength); +            } +        }          // Inject media          const selectionText = injectSelectionText ? this._getSelectionText() : null; @@ -335,6 +351,7 @@ class AnkiNoteBuilder {              dictionaryMediaDetails          );          const {audioFileName, screenshotFileName, clipboardImageFileName, clipboardText, dictionaryMedia: dictionaryMediaArray, errors} = injectedMedia; +        const textFurigana = textFuriganaPromise !== null ? await textFuriganaPromise : [];          // Format results          const dictionaryMedia = {}; @@ -353,6 +370,7 @@ class AnkiNoteBuilder {              clipboardImage: (typeof clipboardImageFileName === 'string' ? {fileName: clipboardImageFileName} : null),              clipboardText: (typeof clipboardText === 'string' ? {text: clipboardText} : null),              selectionText: (typeof selectionText === 'string' ? {text: selectionText} : null), +            textFurigana,              dictionaryMedia          };          return {media, errors}; @@ -361,4 +379,50 @@ class AnkiNoteBuilder {      _getSelectionText() {          return document.getSelection().toString();      } + +    async _getTextFurigana(entries, optionsContext, scanLength) { +        const results = []; +        for (const {text, readingMode} of entries) { +            const parseResults = await yomichan.api.parseText(text, optionsContext, scanLength, true, false); +            let data = null; +            for (const {source, content} of parseResults) { +                if (source !== 'scanning-parser') { continue; } +                data = content; +                break; +            } +            if (data !== null) { +                const html = this._createFuriganaHtml(data, readingMode); +                results.push({text, readingMode, details: {html}}); +            } +        } +        return results; +    } + +    _createFuriganaHtml(data, readingMode) { +        let result = ''; +        for (const term of data) { +            result += '<span class="term">'; +            for (const {text, reading} of term) { +                if (reading.length > 0) { +                    const reading2 = this._convertReading(reading, readingMode); +                    result += `<ruby>${text}<rt>${reading2}</rt></ruby>`; +                } else { +                    result += text; +                } +            } +            result += '</span>'; +        } +        return result; +    } + +    _convertReading(reading, readingMode) { +        switch (readingMode) { +            case 'hiragana': +                return this._japaneseUtil.convertKatakanaToHiragana(reading); +            case 'katakana': +                return this._japaneseUtil.convertHiraganaToKatakana(reading); +            default: +                return reading; +        } +    }  } diff --git a/ext/js/data/options-util.js b/ext/js/data/options-util.js index 3d36fc2e..36630e2f 100644 --- a/ext/js/data/options-util.js +++ b/ext/js/data/options-util.js @@ -855,6 +855,7 @@ class OptionsUtil {          //  Handlebars templates updated to use formatGlossary.          //  Handlebars templates updated to use new media format.          //  Added {selection-text} field marker. +        //  Added {sentence-furigana} field marker.          await this._applyAnkiFieldTemplatesPatch(options, '/data/templates/anki-field-templates-upgrade-v13.handlebars');          return options;      } diff --git a/ext/js/display/display-anki.js b/ext/js/display/display-anki.js index 114a9d13..235149ad 100644 --- a/ext/js/display/display-anki.js +++ b/ext/js/display/display-anki.js @@ -22,11 +22,11 @@   */  class DisplayAnki { -    constructor(display) { +    constructor(display, japaneseUtil) {          this._display = display;          this._ankiFieldTemplates = null;          this._ankiFieldTemplatesDefault = null; -        this._ankiNoteBuilder = new AnkiNoteBuilder(); +        this._ankiNoteBuilder = new AnkiNoteBuilder({japaneseUtil});          this._ankiNoteNotification = null;          this._ankiNoteNotificationEventListeners = null;          this._ankiTagNotification = null; @@ -44,6 +44,7 @@ class DisplayAnki {          this._duplicateScope = 'collection';          this._screenshotFormat = 'png';          this._screenshotQuality = 100; +        this._scanLength = 10;          this._noteTags = [];          this._modeOptions = new Map();          this._dictionaryEntryTypeModeMap = new Map([ @@ -141,7 +142,8 @@ class DisplayAnki {      _onOptionsUpdated({options}) {          const {              general: {resultOutputMode, glossaryLayoutMode, compactTags}, -            anki: {tags, duplicateScope, suspendNewCards, checkForDuplicates, displayTags, kanji, terms, screenshot: {format, quality}} +            anki: {tags, duplicateScope, suspendNewCards, checkForDuplicates, displayTags, kanji, terms, screenshot: {format, quality}}, +            scanning: {length: scanLength}          } = options;          this._checkForDuplicates = checkForDuplicates; @@ -153,6 +155,7 @@ class DisplayAnki {          this._duplicateScope = duplicateScope;          this._screenshotFormat = format;          this._screenshotQuality = quality; +        this._scanLength = scanLength;          this._noteTags = [...tags];          this._modeOptions.clear();          this._modeOptions.set('kanji', kanji); @@ -526,6 +529,7 @@ class DisplayAnki {          const contentOrigin = this._display.getContentOrigin();          const details = this._ankiNoteBuilder.getDictionaryEntryDetailsForNote(dictionaryEntry);          const audioDetails = (details.type === 'term' ? this._display.getAnkiNoteMediaAudioDetails(details.term, details.reading) : null); +        const optionsContext = this._display.getOptionsContext();          const {note, errors, requirements: outputRequirements} = await this._ankiNoteBuilder.createNote({              dictionaryEntry, @@ -547,6 +551,10 @@ class DisplayAnki {                      format: this._screenshotFormat,                      quality: this._screenshotQuality,                      contentOrigin +                }, +                textParsing: { +                    optionsContext, +                    scanLength: this._scanLength                  }              },              requirements diff --git a/ext/js/display/display.js b/ext/js/display/display.js index d79cc7e2..12486a72 100644 --- a/ext/js/display/display.js +++ b/ext/js/display/display.js @@ -110,7 +110,7 @@ class Display extends EventDispatcher {          this._queryPostProcessor = null;          this._optionToggleHotkeyHandler = new OptionToggleHotkeyHandler(this);          this._elementOverflowController = new ElementOverflowController(); -        this._displayAnki = new DisplayAnki(this); +        this._displayAnki = new DisplayAnki(this, japaneseUtil);          this._hotkeyHandler.registerActions([              ['close',             () => { this._onHotkeyClose(); }], diff --git a/ext/js/pages/settings/anki-controller.js b/ext/js/pages/settings/anki-controller.js index b98de1b6..c0f7f626 100644 --- a/ext/js/pages/settings/anki-controller.js +++ b/ext/js/pages/settings/anki-controller.js @@ -104,6 +104,7 @@ class AnkiController {                      'search-query',                      'selection-text',                      'sentence', +                    'sentence-furigana',                      'tags',                      'url'                  ]; @@ -123,6 +124,7 @@ class AnkiController {                      'screenshot',                      'search-query',                      'selection-text', +                    'sentence-furigana',                      'sentence',                      'stroke-count',                      'tags', diff --git a/ext/js/pages/settings/anki-templates-controller.js b/ext/js/pages/settings/anki-templates-controller.js index aa565bad..ad2790ca 100644 --- a/ext/js/pages/settings/anki-templates-controller.js +++ b/ext/js/pages/settings/anki-templates-controller.js @@ -17,6 +17,7 @@  /* global   * AnkiNoteBuilder + * JapaneseUtil   */  class AnkiTemplatesController { @@ -32,7 +33,7 @@ class AnkiTemplatesController {          this._renderFieldInput = null;          this._renderResult = null;          this._fieldTemplateResetModal = null; -        this._ankiNoteBuilder = new AnkiNoteBuilder(); +        this._ankiNoteBuilder = new AnkiNoteBuilder({japaneseUtil: new JapaneseUtil(null)});      }      async prepare() { diff --git a/ext/js/templates/template-renderer-media-provider.js b/ext/js/templates/template-renderer-media-provider.js index 4fd40c67..604b5331 100644 --- a/ext/js/templates/template-renderer-media-provider.js +++ b/ext/js/templates/template-renderer-media-provider.js @@ -54,21 +54,7 @@ class TemplateRendererMediaProvider {      }      _getFormattedValue(data, format) { -        switch (format) { -            case 'fileName': -                { -                    const {fileName} = data; -                    if (typeof fileName === 'string') { return fileName; } -                } -                break; -            case 'text': -                { -                    const {text} = data; -                    if (typeof text === 'string') { return text; } -                } -                break; -        } -        return null; +        return Object.prototype.hasOwnProperty.call(data, format) ? data[format] : null;      }      _getMediaData(media, args, namedArgs) { @@ -79,6 +65,7 @@ class TemplateRendererMediaProvider {              case 'clipboardImage': return this._getSimpleMediaData(media, 'clipboardImage');              case 'clipboardText': return this._getSimpleMediaData(media, 'clipboardText');              case 'selectionText': return this._getSimpleMediaData(media, 'selectionText'); +            case 'textFurigana': return this._getTextFurigana(media, args[1], namedArgs);              case 'dictionaryMedia': return this._getDictionaryMedia(media, args[1], namedArgs);              default: return null;          } @@ -114,4 +101,21 @@ class TemplateRendererMediaProvider {          });          return null;      } + +    _getTextFurigana(media, text, namedArgs) { +        const {readingMode=null} = namedArgs; +        const {textFurigana} = media; +        if (Array.isArray(textFurigana)) { +            for (const entry of textFurigana) { +                if (entry.text !== text || entry.readingMode !== readingMode) { continue; } +                return entry.details; +            } +        } +        this._addRequirement({ +            type: 'textFurigana', +            text, +            readingMode +        }); +        return null; +    }  } diff --git a/ext/settings.html b/ext/settings.html index a8c8149b..0144ddd3 100644 --- a/ext/settings.html +++ b/ext/settings.html @@ -2868,6 +2868,10 @@                      <td>Sentence, quote, or phrase that the term or kanji appears in from the source content.</td>                  </tr>                  <tr> +                    <td><code class="anki-field-marker">{sentence-furigana}</code></td> +                    <td>Sentence, quote, or phrase that the term or kanji appears in from the source content, with furigana added.</td> +                </tr> +                <tr>                      <td><code class="anki-field-marker">{url}</code></td>                      <td>Address of the web page in which the term or kanji appeared in.</td>                  </tr> |