summaryrefslogtreecommitdiff
path: root/ext/js/data
diff options
context:
space:
mode:
authortoasted-nutbread <toasted-nutbread@users.noreply.github.com>2021-07-09 17:48:27 -0400
committerGitHub <noreply@github.com>2021-07-09 17:48:27 -0400
commit8c68fa4d9435b562ffe23df92a2b7b620a0ed78e (patch)
tree8c622fe11063b3f9694033f10e47b2ac05badccc /ext/js/data
parent0d167095479822adf1ed8918e3d1a349b3a53377 (diff)
Anki text furigana parsing and {sentence-furigana} marker (#1814)
* Add support for textFurigana media * Add readingMode parameter * Implement readingMode * Add {sentence-furigana} marker * Fallback to sentence if furigana isn't available * Update test data
Diffstat (limited to 'ext/js/data')
-rw-r--r--ext/js/data/anki-note-builder.js66
-rw-r--r--ext/js/data/options-util.js1
2 files changed, 66 insertions, 1 deletions
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;
}