aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ext/bg/js/api.js37
-rw-r--r--ext/bg/js/backend.js1
-rw-r--r--ext/bg/js/search-query-parser.js97
-rw-r--r--ext/fg/js/api.js4
-rw-r--r--ext/mixed/css/display.css4
-rw-r--r--ext/mixed/js/display.js78
6 files changed, 188 insertions, 33 deletions
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();