aboutsummaryrefslogtreecommitdiff
path: root/ext/bg/js/search.js
diff options
context:
space:
mode:
Diffstat (limited to 'ext/bg/js/search.js')
-rw-r--r--ext/bg/js/search.js144
1 files changed, 87 insertions, 57 deletions
diff --git a/ext/bg/js/search.js b/ext/bg/js/search.js
index fe48773f..a4103ef2 100644
--- a/ext/bg/js/search.js
+++ b/ext/bg/js/search.js
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2016-2017 Alex Yatskov <alex@foosoft.net>
+ * Copyright (C) 2016-2020 Alex Yatskov <alex@foosoft.net>
* Author: Alex Yatskov <alex@foosoft.net>
*
* This program is free software: you can redistribute it and/or modify
@@ -13,16 +13,9 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
-
-let IS_FIREFOX = null;
-(async () => {
- const {browser} = await apiGetEnvironmentInfo();
- IS_FIREFOX = ['firefox', 'firefox-mobile'].includes(browser);
-})();
-
class DisplaySearch extends Display {
constructor() {
super(document.querySelector('#spinner'), document.querySelector('#content'));
@@ -43,8 +36,12 @@ class DisplaySearch extends Display {
this.introVisible = true;
this.introAnimationTimer = null;
- this.clipboardMonitorIntervalId = null;
- this.clipboardPrevText = null;
+ this.isFirefox = false;
+
+ this.clipboardMonitorTimerId = null;
+ this.clipboardMonitorTimerToken = null;
+ this.clipboardInterval = 250;
+ this.clipboardPreviousText = null;
}
static create() {
@@ -56,6 +53,7 @@ class DisplaySearch extends Display {
async prepare() {
try {
await this.initialize();
+ this.isFirefox = await DisplaySearch._isFirefox();
if (this.search !== null) {
this.search.addEventListener('click', (e) => this.onSearch(e), false);
@@ -207,10 +205,14 @@ class DisplaySearch extends Display {
async onSearchQueryUpdated(query, animate) {
try {
const details = {};
- const match = /[*\uff0a]+$/.exec(query);
+ const match = /^([*\uff0a]*)([\w\W]*?)([*\uff0a]*)$/.exec(query);
if (match !== null) {
- details.wildcard = true;
- query = query.substring(0, query.length - match[0].length);
+ if (match[1]) {
+ details.wildcard = 'prefix';
+ } else if (match[3]) {
+ details.wildcard = 'suffix';
+ }
+ query = match[2];
}
const valid = (query.length > 0);
@@ -224,63 +226,81 @@ class DisplaySearch extends Display {
sentence: {text: query, offset: 0},
url: window.location.href
});
- this.setTitleText(query);
} else {
this.container.textContent = '';
}
+ this.setTitleText(query);
window.parent.postMessage('popupClose', '*');
} catch (e) {
this.onError(e);
}
}
- onRuntimeMessage({action, params}, sender, callback) {
- const handlers = DisplaySearch.runtimeMessageHandlers;
- if (hasOwn(handlers, action)) {
- const handler = handlers[action];
- const result = handler(this, params);
- callback(result);
- } else {
- return super.onRuntimeMessage({action, params}, sender, callback);
- }
- }
-
initClipboardMonitor() {
// ignore copy from search page
window.addEventListener('copy', () => {
- this.clipboardPrevText = document.getSelection().toString().trim();
+ this.clipboardPreviousText = document.getSelection().toString().trim();
});
}
startClipboardMonitor() {
- this.clipboardMonitorIntervalId = setInterval(async () => {
- let curText = null;
- // TODO get rid of this and figure out why apiClipboardGet doesn't work on Firefox
- if (IS_FIREFOX) {
- curText = (await navigator.clipboard.readText()).trim();
- } else if (IS_FIREFOX === false) {
- curText = (await apiClipboardGet()).trim();
- }
- if (curText && (curText !== this.clipboardPrevText) && jpIsJapaneseText(curText)) {
- if (this.isWanakanaEnabled()) {
- this.setQuery(window.wanakana.toKana(curText));
- } else {
- this.setQuery(curText);
+ // The token below is used as a unique identifier to ensure that a new clipboard monitor
+ // hasn't been started during the await call. The check below the await this.getClipboardText()
+ // call will exit early if the reference has changed.
+ const token = {};
+ const intervalCallback = async () => {
+ this.clipboardMonitorTimerId = null;
+
+ let text = await this.getClipboardText();
+ if (this.clipboardMonitorTimerToken !== token) { return; }
+
+ if (
+ typeof text === 'string' &&
+ (text = text.trim()).length > 0 &&
+ text !== this.clipboardPreviousText
+ ) {
+ this.clipboardPreviousText = text;
+ if (jpIsJapaneseText(text)) {
+ this.setQuery(this.isWanakanaEnabled() ? window.wanakana.toKana(text) : text);
+ window.history.pushState(null, '', `${window.location.pathname}?query=${encodeURIComponent(text)}`);
+ this.onSearchQueryUpdated(this.query.value, true);
}
+ }
- const queryString = curText.length > 0 ? `?query=${encodeURIComponent(curText)}` : '';
- window.history.pushState(null, '', `${window.location.pathname}${queryString}`);
- this.onSearchQueryUpdated(this.query.value, true);
+ this.clipboardMonitorTimerId = setTimeout(intervalCallback, this.clipboardInterval);
+ };
- this.clipboardPrevText = curText;
- }
- }, 100);
+ this.clipboardMonitorTimerToken = token;
+
+ intervalCallback();
}
stopClipboardMonitor() {
- if (this.clipboardMonitorIntervalId) {
- clearInterval(this.clipboardMonitorIntervalId);
- this.clipboardMonitorIntervalId = null;
+ this.clipboardMonitorTimerToken = null;
+ if (this.clipboardMonitorTimerId !== null) {
+ clearTimeout(this.clipboardMonitorTimerId);
+ this.clipboardMonitorTimerId = null;
+ }
+ }
+
+ async getClipboardText() {
+ /*
+ Notes:
+ apiClipboardGet doesn't work on Firefox because document.execCommand('paste')
+ results in an empty string on the web extension background page.
+ This may be a bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1603985
+ Therefore, navigator.clipboard.readText() is used on Firefox.
+
+ navigator.clipboard.readText() can't be used in Chrome for two reasons:
+ * Requires page to be focused, else it rejects with an exception.
+ * When the page is focused, Chrome will request clipboard permission, despite already
+ being an extension with clipboard permissions. It effectively asks for the
+ non-extension permission for clipboard access.
+ */
+ try {
+ return this.isFirefox ? await navigator.clipboard.readText() : await apiClipboardGet();
+ } catch (e) {
+ return null;
}
}
@@ -360,22 +380,32 @@ class DisplaySearch extends Display {
setTitleText(text) {
// Chrome limits title to 1024 characters
if (text.length > 1000) {
- text = text.slice(0, 1000) + '...';
+ text = text.substring(0, 1000) + '...';
+ }
+
+ if (text.length === 0) {
+ document.title = 'Yomichan Search';
+ } else {
+ document.title = `${text} - Yomichan Search`;
}
- document.title = `${text} - Yomichan Search`;
}
static getSearchQueryFromLocation(url) {
const match = /^[^?#]*\?(?:[^&#]*&)?query=([^&#]*)/.exec(url);
return match !== null ? decodeURIComponent(match[1]) : null;
}
-}
-DisplaySearch.runtimeMessageHandlers = {
- getUrl: () => {
- return {url: window.location.href};
+ static async _isFirefox() {
+ const {browser} = await apiGetEnvironmentInfo();
+ switch (browser) {
+ case 'firefox':
+ case 'firefox-mobile':
+ return true;
+ default:
+ return false;
+ }
}
-};
+}
DisplaySearch.onKeyDownIgnoreKeys = {
'ANY_MOD': [
@@ -392,4 +422,4 @@ DisplaySearch.onKeyDownIgnoreKeys = {
'Shift': []
};
-window.yomichan_search = DisplaySearch.create();
+DisplaySearch.instance = DisplaySearch.create();