From f63e8e4be0e7bdb1a2e45e349bf667ea7ca4adab Mon Sep 17 00:00:00 2001 From: siikamiika Date: Tue, 29 Oct 2019 23:49:36 +0200 Subject: add simple query parser --- ext/mixed/css/display.css | 9 +++++++++ 1 file changed, 9 insertions(+) (limited to 'ext/mixed/css/display.css') diff --git a/ext/mixed/css/display.css b/ext/mixed/css/display.css index 7ee6f5ac..5e5213ff 100644 --- a/ext/mixed/css/display.css +++ b/ext/mixed/css/display.css @@ -88,6 +88,15 @@ ol, ul { user-select: none; } +#query-parser { + margin-top: 10px; + font-size: 24px; +} + +.highlight { + background-color: lightblue; +} + /* * Entries -- cgit v1.2.3 From c35a05cd62d43ff435c022a353de55510b020277 Mon Sep 17 00:00:00 2001 From: siikamiika Date: Wed, 30 Oct 2019 03:58:24 +0200 Subject: add kana to text --- ext/bg/js/api.js | 37 +++++++++++++++ ext/bg/js/backend.js | 1 + ext/bg/js/search-query-parser.js | 97 ++++++++++++++++++++++++++++++++++++---- ext/fg/js/api.js | 4 ++ ext/mixed/css/display.css | 4 ++ ext/mixed/js/display.js | 78 +++++++++++++++++++++----------- 6 files changed, 188 insertions(+), 33 deletions(-) (limited to 'ext/mixed/css/display.css') diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js index df73aa2a..064903ca 100644 --- a/ext/bg/js/api.js +++ b/ext/bg/js/api.js @@ -79,6 +79,43 @@ async function apiTermsFind(text, details, optionsContext) { return {length, definitions}; } +async function apiTextParse(text, optionsContext) { + const options = await apiOptionsGet(optionsContext); + const translator = utilBackend().translator; + + const results = []; + while (text) { + let [definitions, length] = await translator.findTerms(text, {}, options); + if (definitions.length > 0) { + definitions = dictTermsSort(definitions); + const {expression, source, reading} = definitions[0]; + + let stemLength = source.length; + while (source[stemLength - 1] !== expression[stemLength - 1]) { + --stemLength; + } + const offset = source.length - stemLength; + + for (const result of jpDistributeFurigana( + source.slice(0, offset === 0 ? source.length : source.length - offset), + reading.slice(0, offset === 0 ? reading.length : source.length + (reading.length - expression.length) - offset) + )) { + results.push(result); + } + + if (stemLength !== source.length) { + results.push({text: source.slice(stemLength)}); + } + + text = text.slice(source.length); + } else { + results.push({text: text[0]}); + text = text.slice(1); + } + } + return results; +} + async function apiKanjiFind(text, optionsContext) { const options = await apiOptionsGet(optionsContext); const definitions = await utilBackend().translator.findKanji(text, options); diff --git a/ext/bg/js/backend.js b/ext/bg/js/backend.js index efad153a..d0e404f2 100644 --- a/ext/bg/js/backend.js +++ b/ext/bg/js/backend.js @@ -180,6 +180,7 @@ Backend.messageHandlers = { optionsSet: ({changedOptions, optionsContext, source}) => apiOptionsSet(changedOptions, optionsContext, source), kanjiFind: ({text, optionsContext}) => apiKanjiFind(text, optionsContext), termsFind: ({text, details, optionsContext}) => apiTermsFind(text, details, optionsContext), + textParse: ({text, optionsContext}) => apiTextParse(text, optionsContext), definitionAdd: ({definition, mode, context, optionsContext}) => apiDefinitionAdd(definition, mode, context, optionsContext), definitionsAddable: ({definitions, modes, optionsContext}) => apiDefinitionsAddable(definitions, modes, optionsContext), noteView: ({noteId}) => apiNoteView(noteId), diff --git a/ext/bg/js/search-query-parser.js b/ext/bg/js/search-query-parser.js index debe53b4..c3a3900b 100644 --- a/ext/bg/js/search-query-parser.js +++ b/ext/bg/js/search-query-parser.js @@ -23,22 +23,103 @@ class QueryParser { this.queryParser = document.querySelector('#query-parser'); - // TODO also enable for mouseover scanning - this.queryParser.addEventListener('click', (e) => this.onTermLookup(e)); + this.queryParser.addEventListener('click', (e) => this.onClick(e)); + this.queryParser.addEventListener('mousemove', (e) => this.onMouseMove(e)); } onError(error) { logError(error, false); } - async onTermLookup(e) { - const {textSource} = await this.search.onTermLookup(e, {isQueryParser: true}); - if (textSource) { - textSource.select(); + onClick(e) { + this.onTermLookup(e, {disableScroll: true, selectText: true}); + } + + async onMouseMove(e) { + if ( + (e.buttons & 0x1) !== 0x0 // Left mouse button + ) { + return; + } + + const scanningOptions = this.search.options.scanning; + const scanningModifier = scanningOptions.modifier; + if (!( + QueryParser.isScanningModifierPressed(scanningModifier, e) || + (scanningOptions.middleMouse && (e.buttons & 0x4) !== 0x0) // Middle mouse button + )) { + return; } + + await this.onTermLookup(e, {disableScroll: true, selectText: true, disableHistory: true}) + } + + onTermLookup(e, params) { + this.search.onTermLookup(e, params); } - setText(text) { - this.queryParser.innerText = text; + async setText(text) { + this.search.setSpinnerVisible(true); + + const results = await apiTextParse(text, this.search.getOptionsContext()); + + const tempContainer = document.createElement('div'); + for (const {text, furigana} of results) { + if (furigana) { + const rubyElement = document.createElement('ruby'); + const furiganaElement = document.createElement('rt'); + furiganaElement.innerText = furigana; + rubyElement.appendChild(document.createTextNode(text)); + rubyElement.appendChild(furiganaElement); + tempContainer.appendChild(rubyElement); + } else { + tempContainer.appendChild(document.createTextNode(text)); + } + } + this.queryParser.innerHTML = ''; + this.queryParser.appendChild(tempContainer); + + this.search.setSpinnerVisible(false); + } + + async parseText(text) { + const results = []; + while (text) { + const {definitions, length} = await apiTermsFind(text, {}, this.search.getOptionsContext()); + if (length) { + results.push(definitions); + text = text.slice(length); + } else { + results.push(text[0]); + text = text.slice(1); + } + } + return results; + } + + popupTimerSet(callback) { + const delay = this.options.scanning.delay; + if (delay > 0) { + this.popupTimer = window.setTimeout(callback, delay); + } else { + Promise.resolve().then(callback); + } + } + + popupTimerClear() { + if (this.popupTimer !== null) { + window.clearTimeout(this.popupTimer); + this.popupTimer = null; + } + } + + static isScanningModifierPressed(scanningModifier, mouseEvent) { + switch (scanningModifier) { + case 'alt': return mouseEvent.altKey; + case 'ctrl': return mouseEvent.ctrlKey; + case 'shift': return mouseEvent.shiftKey; + case 'none': return true; + default: return false; + } } } diff --git a/ext/fg/js/api.js b/ext/fg/js/api.js index 945ba076..cc1e0e90 100644 --- a/ext/fg/js/api.js +++ b/ext/fg/js/api.js @@ -29,6 +29,10 @@ function apiTermsFind(text, details, optionsContext) { return utilInvoke('termsFind', {text, details, optionsContext}); } +function apiTextParse(text, optionsContext) { + return utilInvoke('textParse', {text, optionsContext}); +} + function apiKanjiFind(text, optionsContext) { return utilInvoke('kanjiFind', {text, optionsContext}); } diff --git a/ext/mixed/css/display.css b/ext/mixed/css/display.css index 5e5213ff..81109fc6 100644 --- a/ext/mixed/css/display.css +++ b/ext/mixed/css/display.css @@ -73,6 +73,10 @@ ol, ul { } +html:root[data-yomichan-page=search] body { + min-height: 101vh; /* always show scroll bar to avoid scanning problems */ +} + /* * Search page */ diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index 07a851f5..82385bf9 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -98,17 +98,62 @@ class Display { } } - async onTermLookup(e) { + async onTermLookup(e, {disableScroll, selectText, disableHistory}) { + const termLookupResults = await this.termLookup(e); + if (!termLookupResults) { + return false; + } + + try { + const {textSource, definitions} = termLookupResults; + + const scannedElement = e.target; + const sentence = docSentenceExtract(textSource, this.options.anki.sentenceExt); + + if (!disableScroll) { + this.windowScroll.toY(0); + } + let context; + if (disableHistory) { + const {url, source} = this.context || {}; + context = {sentence, url, source, disableScroll}; + } else { + context = { + disableScroll, + source: { + definitions: this.definitions, + index: this.entryIndexFind(scannedElement), + scroll: this.windowScroll.y + } + }; + + if (this.context) { + context.sentence = sentence; + context.url = this.context.url; + context.source.source = this.context.source; + } + } + + this.setContentTerms(definitions, context); + + if (selectText) { + textSource.select(); + } + } catch (error) { + this.onError(error); + } + } + + async termLookup(e) { try { e.preventDefault(); - const clickedElement = e.target; const textSource = docRangeFromPoint(e.clientX, e.clientY, this.options); if (textSource === null) { return false; } - let definitions, length, sentence; + let definitions, length; try { textSource.setEndOffset(this.options.scanning.length); @@ -118,30 +163,11 @@ class Display { } textSource.setEndOffset(length); - - sentence = docSentenceExtract(textSource, this.options.anki.sentenceExt); } finally { textSource.cleanup(); } - this.windowScroll.toY(0); - const context = { - source: { - definitions: this.definitions, - index: this.entryIndexFind(clickedElement), - scroll: this.windowScroll.y - } - }; - - if (this.context) { - context.sentence = sentence; - context.url = this.context.url; - context.source.source = this.context.source; - } - - this.setContentTerms(definitions, context); - - return {textSource}; + return {textSource, definitions}; } catch (error) { this.onError(error); } @@ -338,8 +364,10 @@ class Display { const content = await apiTemplateRender('terms.html', params); this.container.innerHTML = content; - const {index, scroll} = context || {}; - this.entryScrollIntoView(index || 0, scroll); + const {index, scroll, disableScroll} = context || {}; + if (!disableScroll) { + this.entryScrollIntoView(index || 0, scroll); + } if (options.audio.enabled && options.audio.autoPlay) { this.autoPlayAudio(); -- cgit v1.2.3 From 530b95895bb8a807cbaea6a324323c49152c16ab Mon Sep 17 00:00:00 2001 From: siikamiika Date: Wed, 30 Oct 2019 12:06:25 +0200 Subject: remove unused css --- ext/mixed/css/display.css | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'ext/mixed/css/display.css') diff --git a/ext/mixed/css/display.css b/ext/mixed/css/display.css index 81109fc6..65b8b466 100644 --- a/ext/mixed/css/display.css +++ b/ext/mixed/css/display.css @@ -73,10 +73,6 @@ ol, ul { } -html:root[data-yomichan-page=search] body { - min-height: 101vh; /* always show scroll bar to avoid scanning problems */ -} - /* * Search page */ @@ -97,8 +93,13 @@ html:root[data-yomichan-page=search] body { font-size: 24px; } -.highlight { - background-color: lightblue; +html:root[data-yomichan-page=search] body { + min-height: 101vh; /* always show scroll bar to avoid scanning problems */ +} + +#query-parser { + margin-top: 10px; + font-size: 24px; } -- cgit v1.2.3 From 3881457e4ed3f9c7833ac21a5e7fc44c2ba00b0f Mon Sep 17 00:00:00 2001 From: siikamiika Date: Thu, 31 Oct 2019 23:56:44 +0200 Subject: use handlebars templates for query parser --- ext/bg/js/api.js | 12 +++++----- ext/bg/js/search-query-parser.js | 40 ++++++++++++++------------------- ext/bg/js/templates.js | 48 ++++++++++++++++++++++++++++++++++++++++ ext/mixed/css/display.css | 9 ++++---- tmpl/query-parser.html | 23 +++++++++++++++++++ 5 files changed, 99 insertions(+), 33 deletions(-) create mode 100644 tmpl/query-parser.html (limited to 'ext/mixed/css/display.css') diff --git a/ext/bg/js/api.js b/ext/bg/js/api.js index dbbe7368..7c9a72a7 100644 --- a/ext/bg/js/api.js +++ b/ext/bg/js/api.js @@ -85,6 +85,7 @@ async function apiTextParse(text, optionsContext) { const results = []; while (text) { + const term = []; let [definitions, sourceLength] = await translator.findTerms(text, {}, options); if (definitions.length > 0) { definitions = dictTermsSort(definitions); @@ -98,22 +99,23 @@ async function apiTextParse(text, optionsContext) { } const offset = source.length - stemLength; - for (const result of jpDistributeFurigana( + for (const {text, furigana} of jpDistributeFurigana( source.slice(0, offset === 0 ? source.length : source.length - offset), - reading.slice(0, offset === 0 ? reading.length : source.length + (reading.length - expression.length) - offset) + reading.slice(0, offset === 0 ? reading.length : reading.length - expression.length + stemLength) )) { - results.push(result); + term.push({text, reading: furigana || ''}); } if (stemLength !== source.length) { - results.push({text: source.slice(stemLength)}); + term.push({text: source.slice(stemLength)}); } text = text.slice(source.length); } else { - results.push({text: text[0]}); + term.push({text: text[0]}); text = text.slice(1); } + results.push(term); } return results; } diff --git a/ext/bg/js/search-query-parser.js b/ext/bg/js/search-query-parser.js index 9bea6508..8a7db69a 100644 --- a/ext/bg/js/search-query-parser.js +++ b/ext/bg/js/search-query-parser.js @@ -59,39 +59,33 @@ class QueryParser { } async setText(text) { - this.queryParser.innerHTML = ''; this.search.setSpinnerVisible(true); + const previewTerms = []; let previewText = text; while (previewText) { const tempText = previewText.slice(0, 2); + previewTerms.push([{text: tempText}]); previewText = previewText.slice(2); - - const tempRuby = document.createElement('ruby'); - const tempFurigana = document.createElement('rt'); - tempRuby.appendChild(document.createTextNode(tempText)); - tempRuby.appendChild(tempFurigana); - this.queryParser.appendChild(tempRuby); } + this.queryParser.innerHTML = await apiTemplateRender('query-parser.html', { + terms: previewTerms, + preview: true + }); + const results = await apiTextParse(text, this.search.getOptionsContext()); - const textContainer = document.createElement('div'); - for (const {text, furigana} of results) { - const rubyElement = document.createElement('ruby'); - const furiganaElement = document.createElement('rt'); - if (furigana) { - furiganaElement.innerText = furigana; - rubyElement.appendChild(document.createTextNode(text)); - rubyElement.appendChild(furiganaElement); - } else { - rubyElement.appendChild(document.createTextNode(text)); - rubyElement.appendChild(furiganaElement); - } - textContainer.appendChild(rubyElement); - } - this.queryParser.innerHTML = ''; - this.queryParser.appendChild(textContainer); + const content = await apiTemplateRender('query-parser.html', { + terms: results.map((term) => { + return term.map((part) => { + part.raw = !part.text.trim() && (!part.reading || !part.reading.trim()); + return part; + }); + }) + }); + + this.queryParser.innerHTML = content; this.search.setSpinnerVisible(false); } diff --git a/ext/bg/js/templates.js b/ext/bg/js/templates.js index 823b9e6f..cc233d49 100644 --- a/ext/bg/js/templates.js +++ b/ext/bg/js/templates.js @@ -162,6 +162,54 @@ templates['kanji.html'] = template({"1":function(container,depth0,helpers,partia return fn; } +,"useDecorators":true,"usePartial":true,"useData":true,"useDepths":true}); +templates['query-parser.html'] = template({"1":function(container,depth0,helpers,partials,data) { + var stack1, alias1=depth0 != null ? depth0 : (container.nullContext || {}); + + return ((stack1 = helpers["if"].call(alias1,(depth0 != null ? depth0.preview : depth0),{"name":"if","hash":{},"fn":container.program(2, data, 0),"inverse":container.program(4, data, 0),"data":data})) != null ? stack1 : "") + + ((stack1 = helpers.each.call(alias1,depth0,{"name":"each","hash":{},"fn":container.program(6, data, 0),"inverse":container.noop,"data":data})) != null ? stack1 : "") + + ""; +},"2":function(container,depth0,helpers,partials,data) { + return ""; +},"4":function(container,depth0,helpers,partials,data) { + return ""; +},"6":function(container,depth0,helpers,partials,data) { + var stack1; + + return ((stack1 = container.invokePartial(partials.part,depth0,{"name":"part","data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : ""); +},"8":function(container,depth0,helpers,partials,data) { + var stack1; + + return ((stack1 = helpers["if"].call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.raw : depth0),{"name":"if","hash":{},"fn":container.program(9, data, 0),"inverse":container.program(11, data, 0),"data":data})) != null ? stack1 : ""); +},"9":function(container,depth0,helpers,partials,data) { + var helper; + + return container.escapeExpression(((helper = (helper = helpers.text || (depth0 != null ? depth0.text : depth0)) != null ? helper : helpers.helperMissing),(typeof helper === "function" ? helper.call(depth0 != null ? depth0 : (container.nullContext || {}),{"name":"text","hash":{},"data":data}) : helper))); +},"11":function(container,depth0,helpers,partials,data) { + var helper, alias1=depth0 != null ? depth0 : (container.nullContext || {}), alias2=helpers.helperMissing, alias3="function", alias4=container.escapeExpression; + + return "" + + alias4(((helper = (helper = helpers.text || (depth0 != null ? depth0.text : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"text","hash":{},"data":data}) : helper))) + + "" + + alias4(((helper = (helper = helpers.reading || (depth0 != null ? depth0.reading : depth0)) != null ? helper : alias2),(typeof helper === alias3 ? helper.call(alias1,{"name":"reading","hash":{},"data":data}) : helper))) + + ""; +},"13":function(container,depth0,helpers,partials,data,blockParams,depths) { + var stack1; + + return ((stack1 = container.invokePartial(partials.term,depth0,{"name":"term","hash":{"preview":(depths[1] != null ? depths[1].preview : depths[1])},"data":data,"helpers":helpers,"partials":partials,"decorators":container.decorators})) != null ? stack1 : ""); +},"compiler":[7,">= 4.0.0"],"main":function(container,depth0,helpers,partials,data,blockParams,depths) { + var stack1; + + return ((stack1 = helpers.each.call(depth0 != null ? depth0 : (container.nullContext || {}),(depth0 != null ? depth0.terms : depth0),{"name":"each","hash":{},"fn":container.program(13, data, 0, blockParams, depths),"inverse":container.noop,"data":data})) != null ? stack1 : ""); +},"main_d": function(fn, props, container, depth0, data, blockParams, depths) { + + var decorators = container.decorators; + + fn = decorators.inline(fn,props,container,{"name":"inline","hash":{},"fn":container.program(1, data, 0, blockParams, depths),"inverse":container.noop,"args":["term"],"data":data}) || fn; + fn = decorators.inline(fn,props,container,{"name":"inline","hash":{},"fn":container.program(8, data, 0, blockParams, depths),"inverse":container.noop,"args":["part"],"data":data}) || fn; + return fn; + } + ,"useDecorators":true,"usePartial":true,"useData":true,"useDepths":true}); templates['terms.html'] = template({"1":function(container,depth0,helpers,partials,data) { var stack1, helper, options, alias1=depth0 != null ? depth0 : (container.nullContext || {}), buffer = diff --git a/ext/mixed/css/display.css b/ext/mixed/css/display.css index 65b8b466..d24aa58c 100644 --- a/ext/mixed/css/display.css +++ b/ext/mixed/css/display.css @@ -93,13 +93,12 @@ ol, ul { font-size: 24px; } -html:root[data-yomichan-page=search] body { - min-height: 101vh; /* always show scroll bar to avoid scanning problems */ +.query-parser-term { + margin-right: 5px; } -#query-parser { - margin-top: 10px; - font-size: 24px; +html:root[data-yomichan-page=search] body { + min-height: 101vh; /* always show scroll bar to avoid scanning problems */ } diff --git a/tmpl/query-parser.html b/tmpl/query-parser.html new file mode 100644 index 00000000..818650e6 --- /dev/null +++ b/tmpl/query-parser.html @@ -0,0 +1,23 @@ +{{~#*inline "term"~}} +{{~#if preview~}} + +{{~else~}} + +{{~/if~}} +{{~#each this~}} +{{> part }} +{{~/each~}} + +{{~/inline~}} + +{{~#*inline "part"~}} +{{~#if raw~}} +{{text}} +{{~else~}} +{{text}}{{reading}} +{{~/if~}} +{{~/inline~}} + +{{~#each terms~}} +{{> term preview=../preview }} +{{~/each~}} -- cgit v1.2.3 From 0d6e0edc31d4ceac58b886e238b77f0143a5c3ec Mon Sep 17 00:00:00 2001 From: siikamiika Date: Wed, 13 Nov 2019 12:05:39 +0200 Subject: remove height hack and use overflow-y: scroll --- ext/mixed/css/display.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'ext/mixed/css/display.css') diff --git a/ext/mixed/css/display.css b/ext/mixed/css/display.css index d24aa58c..ba2fadb7 100644 --- a/ext/mixed/css/display.css +++ b/ext/mixed/css/display.css @@ -98,7 +98,7 @@ ol, ul { } html:root[data-yomichan-page=search] body { - min-height: 101vh; /* always show scroll bar to avoid scanning problems */ + overflow-y: scroll; /* always show scroll bar to avoid scanning problems */ } -- cgit v1.2.3