diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js index 2ab01af3..967bded7 100644 --- a/ext/bg/js/api.js +++ b/ext/bg/js/api.js @@ -109,25 +109,30 @@ async function apiTextParseMecab(text, optionsContext) { const options = await apiOptionsGet(optionsContext); const mecab = utilBackend().mecab; - const results = []; - for (const parsedLine of await mecab.parseText(text)) { - for (const {expression, reading, source} of parsedLine) { - const term = []; - if (expression && reading) { - for (const {text, furigana} of jpDistributeFuriganaInflected( - expression, - jpKatakanaToHiragana(reading), - source - )) { - // can't use 'furigana' in templates - term.push({text, reading: furigana}); + const results = {}; + const rawResults = await mecab.parseText(text); + for (const mecabName in rawResults) { + const result = []; + for (const parsedLine of rawResults[mecabName]) { + for (const {expression, reading, source} of parsedLine) { + const term = []; + if (expression && reading) { + for (const {text, furigana} of jpDistributeFuriganaInflected( + expression, + jpKatakanaToHiragana(reading), + source + )) { + // can't use 'furigana' in templates + term.push({text, reading: furigana}); + } + } else { + term.push({text: source}); } - } else { - term.push({text: source}); + result.push(term); } - results.push(term); + result.push([{text: '\n'}]); } - results.push([{text: '\n'}]); + results[mecabName] = result; } return results; } diff --git a/ext/bg/js/mecab.js b/ext/bg/js/mecab.js index 14f68393..fba9b2eb 100644 --- a/ext/bg/js/mecab.js +++ b/ext/bg/js/mecab.js @@ -60,4 +60,4 @@ class Mecab { } } -Mecab.timeout = 1000; +Mecab.timeout = 5000; diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js index be1ccfbb..f1bafaf9 100644 --- a/ext/bg/js/options.js +++ b/ext/bg/js/options.js @@ -311,6 +311,11 @@ function profileOptionsCreateDefaults() { dictionaries: {}, + parsing: { + enableScanningParser: true, + enableMecabParser: false + }, + anki: { enable: false, server: 'http://127.0.0.1:8765', diff --git a/ext/bg/js/search-query-parser.js b/ext/bg/js/search-query-parser.js index 1cf00425..81eb18c3 100644 --- a/ext/bg/js/search-query-parser.js +++ b/ext/bg/js/search-query-parser.js @@ -86,22 +86,32 @@ class QueryParser { this.search.setSpinnerVisible(true); await this.setPreview(text); - // const results = await apiTextParse(text, this.search.getOptionsContext()); - const results = await apiTextParseMecab(text, this.search.getOptionsContext()); + const results = {}; + if (this.search.options.parsing.enableScanningParser) { + results['scan'] = await apiTextParse(text, this.search.getOptionsContext()); + } + if (this.search.options.parsing.enableMecabParser) { + let mecabResults = await apiTextParseMecab(text, this.search.getOptionsContext()); + for (const mecabDictName in mecabResults) { + results[`mecab-${mecabDictName}`] = mecabResults[mecabDictName]; + } + } - const content = await apiTemplateRender('query-parser.html', { - terms: results.map((term) => { - return term.filter(part => part.text.trim()).map((part) => { - return { - text: Array.from(part.text), - reading: part.reading, - raw: !part.reading || !part.reading.trim(), - }; - }); - }) - }); + const contents = await Promise.all(Object.values(results).map(async result => { + return await apiTemplateRender('query-parser.html', { + terms: result.map((term) => { + return term.filter(part => part.text.trim()).map((part) => { + return { + text: Array.from(part.text), + reading: part.reading, + raw: !part.reading || !part.reading.trim(), + }; + }); + }) + }); + })); - this.queryParser.innerHTML = content; + this.queryParser.innerHTML = contents.join('
'); this.queryParser.querySelectorAll('.query-parser-char').forEach((charElement) => { this.activateScanning(charElement); diff --git a/ext/bg/js/settings.js b/ext/bg/js/settings.js index e562c54e..f4fe032a 100644 --- a/ext/bg/js/settings.js +++ b/ext/bg/js/settings.js @@ -64,6 +64,9 @@ async function formRead(options) { options.scanning.modifier = $('#scan-modifier-key').val(); options.scanning.popupNestingMaxDepth = parseInt($('#popup-nesting-max-depth').val(), 10); + options.parsing.enableScanningParser = $('#parsing-scan-enable').prop('checked'); + options.parsing.enableMecabParser = $('#parsing-mecab-enable').prop('checked'); + const optionsAnkiEnableOld = options.anki.enable; options.anki.enable = $('#anki-enable').prop('checked'); options.anki.tags = utilBackgroundIsolate($('#card-tags').val().split(/[,; ]+/)); @@ -126,6 +129,9 @@ async function formWrite(options) { $('#scan-modifier-key').val(options.scanning.modifier); $('#popup-nesting-max-depth').val(options.scanning.popupNestingMaxDepth); + $('#parsing-scan-enable').prop('checked', options.parsing.enableScanningParser); + $('#parsing-mecab-enable').prop('checked', options.parsing.enableMecabParser); + $('#anki-enable').prop('checked', options.anki.enable); $('#card-tags').val(options.anki.tags.join(' ')); $('#sentence-detection-extent').val(options.anki.sentenceExt); diff --git a/ext/bg/settings.html b/ext/bg/settings.html index bdcc11d3..8505567b 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -587,6 +587,35 @@ +
+

Text Parsing Options

+ +

+ Yomichan can attempt to parse entire sentences or longer text blocks on the search page, + adding furigana above words and a small space between words. +

+ +

+ Two types of parsers are supported. The first one, enabled by default, works using the built-in + scanning functionality by automatically advancing in the sentence after a matching word. +

+ +

+ The second type is an external program called MeCab + that uses its own dictionaries and a special parsing algorithm. To get it working, you must first + install it and a native messaging component + that acts as a bridge between the program and Yomichan. +

+ +
+ +
+ +
+ +
+
+