Persistent display mode (#714)
* Simplify calls using chrome.tabs.sendMessage and getMessageResponseResult * Rename message handlers * Move onMessage handler into Display * Assign search popup mode which persists across refreshes * Update clipboard monitor on the search page * Remove mode param
This commit is contained in:
parent
04d47bf8a9
commit
8ee717cdf7
@ -899,24 +899,8 @@ class Backend {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
if (tab !== null) {
|
if (tab !== null) {
|
||||||
const isValidTab = await new Promise((resolve) => {
|
const url = await this._getTabUrl(tabId);
|
||||||
chrome.tabs.sendMessage(
|
const isValidTab = (url !== null && url.startsWith(baseUrl));
|
||||||
tabId,
|
|
||||||
{action: 'getUrl', params: {}},
|
|
||||||
{frameId: 0},
|
|
||||||
(response) => {
|
|
||||||
let result = false;
|
|
||||||
try {
|
|
||||||
const {url} = yomichan.getMessageResponseResult(response);
|
|
||||||
result = url.startsWith(baseUrl);
|
|
||||||
} catch (e) {
|
|
||||||
// NOP
|
|
||||||
}
|
|
||||||
resolve(result);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
});
|
|
||||||
// windowId
|
|
||||||
if (isValidTab) {
|
if (isValidTab) {
|
||||||
return {tab, created: false};
|
return {tab, created: false};
|
||||||
}
|
}
|
||||||
@ -935,7 +919,7 @@ class Backend {
|
|||||||
const popupWindow = await new Promise((resolve, reject) => {
|
const popupWindow = await new Promise((resolve, reject) => {
|
||||||
chrome.windows.create(
|
chrome.windows.create(
|
||||||
{
|
{
|
||||||
url: `${baseUrl}?mode=popup`,
|
url: baseUrl,
|
||||||
width: popupWidth,
|
width: popupWidth,
|
||||||
height: popupHeight,
|
height: popupHeight,
|
||||||
type: 'popup'
|
type: 'popup'
|
||||||
@ -959,23 +943,22 @@ class Backend {
|
|||||||
const tab = tabs[0];
|
const tab = tabs[0];
|
||||||
await this._waitUntilTabFrameIsReady(tab.id, 0, 2000);
|
await this._waitUntilTabFrameIsReady(tab.id, 0, 2000);
|
||||||
|
|
||||||
|
await this._sendMessageTab(
|
||||||
|
tab.id,
|
||||||
|
{action: 'setMode', params: {mode: 'popup'}},
|
||||||
|
{frameId: 0}
|
||||||
|
);
|
||||||
|
|
||||||
this._searchPopupTabId = tab.id;
|
this._searchPopupTabId = tab.id;
|
||||||
return {tab, created: true};
|
return {tab, created: true};
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateSearchQuery(tabId, text, animate) {
|
_updateSearchQuery(tabId, text, animate) {
|
||||||
return new Promise((resolve, reject) => {
|
return this._sendMessageTab(
|
||||||
const callback = (response) => {
|
tabId,
|
||||||
try {
|
{action: 'updateSearchQuery', params: {text, animate}},
|
||||||
resolve(yomichan.getMessageResponseResult(response));
|
{frameId: 0}
|
||||||
} catch (error) {
|
);
|
||||||
reject(error);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const message = {action: 'updateSearchQuery', params: {text, animate}};
|
|
||||||
chrome.tabs.sendMessage(tabId, message, callback);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_sendMessageAllTabs(action, params={}) {
|
_sendMessageAllTabs(action, params={}) {
|
||||||
@ -1381,18 +1364,20 @@ class Backend {
|
|||||||
return typeof templates === 'string' ? templates : this._defaultAnkiFieldTemplates;
|
return typeof templates === 'string' ? templates : this._defaultAnkiFieldTemplates;
|
||||||
}
|
}
|
||||||
|
|
||||||
_getTabUrl(tab) {
|
async _getTabUrl(tabId) {
|
||||||
return new Promise((resolve) => {
|
|
||||||
chrome.tabs.sendMessage(tab.id, {action: 'getUrl'}, {frameId: 0}, (response) => {
|
|
||||||
let url = null;
|
|
||||||
try {
|
try {
|
||||||
({url} = yomichan.getMessageResponseResult(response));
|
const {url} = await this._sendMessageTab(
|
||||||
} catch (error) {
|
tabId,
|
||||||
|
{action: 'getUrl', params: {}},
|
||||||
|
{frameId: 0}
|
||||||
|
);
|
||||||
|
if (typeof url === 'string') {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
// NOP
|
// NOP
|
||||||
}
|
}
|
||||||
resolve({tab, url});
|
return null;
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async _findTab(timeout, checkUrl) {
|
async _findTab(timeout, checkUrl) {
|
||||||
@ -1538,4 +1523,18 @@ class Backend {
|
|||||||
}
|
}
|
||||||
return await (json ? response.json() : response.text());
|
return await (json ? response.json() : response.text());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_sendMessageTab(...args) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const callback = (response) => {
|
||||||
|
try {
|
||||||
|
resolve(yomichan.getMessageResponseResult(response));
|
||||||
|
} catch (error) {
|
||||||
|
reject(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
chrome.tabs.sendMessage(...args, callback);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,7 @@ class DisplaySearch extends Display {
|
|||||||
this._clipboardMonitor = new ClipboardMonitor({
|
this._clipboardMonitor = new ClipboardMonitor({
|
||||||
getClipboard: api.clipboardGet.bind(api)
|
getClipboard: api.clipboardGet.bind(api)
|
||||||
});
|
});
|
||||||
|
this._clipboardMonitorEnabled = false;
|
||||||
this._onKeyDownIgnoreKeys = new Map([
|
this._onKeyDownIgnoreKeys = new Map([
|
||||||
['ANY_MOD', new Set([
|
['ANY_MOD', new Set([
|
||||||
'Tab', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'PageDown', 'PageUp', 'Home', 'End',
|
'Tab', 'ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'PageDown', 'PageUp', 'Home', 'End',
|
||||||
@ -51,9 +52,6 @@ class DisplaySearch extends Display {
|
|||||||
['AltGraph', new Set()],
|
['AltGraph', new Set()],
|
||||||
['Shift', new Set()]
|
['Shift', new Set()]
|
||||||
]);
|
]);
|
||||||
this._runtimeMessageHandlers = new Map([
|
|
||||||
['updateSearchQuery', {async: false, handler: this._onExternalSearchUpdate.bind(this)}]
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async prepare() {
|
async prepare() {
|
||||||
@ -62,18 +60,16 @@ class DisplaySearch extends Display {
|
|||||||
yomichan.on('optionsUpdated', () => this.updateOptions());
|
yomichan.on('optionsUpdated', () => this.updateOptions());
|
||||||
|
|
||||||
this.on('contentUpdating', this._onContentUpdating.bind(this));
|
this.on('contentUpdating', this._onContentUpdating.bind(this));
|
||||||
|
this.on('modeChange', this._onModeChange.bind(this));
|
||||||
|
|
||||||
|
this.registerMessageHandlers([
|
||||||
|
['updateSearchQuery', {async: false, handler: this._onExternalSearchUpdate.bind(this)}]
|
||||||
|
]);
|
||||||
|
|
||||||
this.queryParserVisible = true;
|
this.queryParserVisible = true;
|
||||||
this.setHistorySettings({useBrowserHistory: true});
|
this.setHistorySettings({useBrowserHistory: true});
|
||||||
|
|
||||||
const options = this.getOptions();
|
const options = this.getOptions();
|
||||||
|
|
||||||
const urlSearchParams = new URLSearchParams(location.search);
|
|
||||||
let mode = urlSearchParams.get('mode');
|
|
||||||
if (mode === null) { mode = ''; }
|
|
||||||
|
|
||||||
document.documentElement.dataset.searchMode = mode;
|
|
||||||
|
|
||||||
if (options.general.enableWanakana === true) {
|
if (options.general.enableWanakana === true) {
|
||||||
this._wanakanaEnable.checked = true;
|
this._wanakanaEnable.checked = true;
|
||||||
wanakana.bind(this._query);
|
wanakana.bind(this._query);
|
||||||
@ -81,25 +77,15 @@ class DisplaySearch extends Display {
|
|||||||
this._wanakanaEnable.checked = false;
|
this._wanakanaEnable.checked = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode !== 'popup') {
|
|
||||||
if (options.general.enableClipboardMonitor === true) {
|
|
||||||
this._clipboardMonitorEnable.checked = true;
|
|
||||||
this._clipboardMonitor.start();
|
|
||||||
} else {
|
|
||||||
this._clipboardMonitorEnable.checked = false;
|
|
||||||
}
|
|
||||||
this._clipboardMonitorEnable.addEventListener('change', this._onClipboardMonitorEnableChange.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
chrome.runtime.onMessage.addListener(this._onRuntimeMessage.bind(this));
|
|
||||||
|
|
||||||
this._search.addEventListener('click', this._onSearch.bind(this), false);
|
this._search.addEventListener('click', this._onSearch.bind(this), false);
|
||||||
this._query.addEventListener('input', this._onSearchInput.bind(this), false);
|
this._query.addEventListener('input', this._onSearchInput.bind(this), false);
|
||||||
this._wanakanaEnable.addEventListener('change', this._onWanakanaEnableChange.bind(this));
|
this._wanakanaEnable.addEventListener('change', this._onWanakanaEnableChange.bind(this));
|
||||||
window.addEventListener('copy', this._onCopy.bind(this));
|
window.addEventListener('copy', this._onCopy.bind(this));
|
||||||
this._clipboardMonitor.on('change', this._onExternalSearchUpdate.bind(this));
|
this._clipboardMonitor.on('change', this._onExternalSearchUpdate.bind(this));
|
||||||
|
this._clipboardMonitorEnable.addEventListener('change', this._onClipboardMonitorEnableChange.bind(this));
|
||||||
|
|
||||||
this._updateSearchButton();
|
this._updateSearchButton();
|
||||||
|
this._onModeChange();
|
||||||
|
|
||||||
await this._prepareNestedPopups();
|
await this._prepareNestedPopups();
|
||||||
|
|
||||||
@ -209,12 +195,6 @@ class DisplaySearch extends Display {
|
|||||||
this._onSearchQueryUpdated(query, true);
|
this._onSearchQueryUpdated(query, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onRuntimeMessage({action, params}, sender, callback) {
|
|
||||||
const messageHandler = this._runtimeMessageHandlers.get(action);
|
|
||||||
if (typeof messageHandler === 'undefined') { return false; }
|
|
||||||
return yomichan.invokeMessageHandler(messageHandler, params, callback, sender);
|
|
||||||
}
|
|
||||||
|
|
||||||
_onCopy() {
|
_onCopy() {
|
||||||
// ignore copy from search page
|
// ignore copy from search page
|
||||||
this._clipboardMonitor.setPreviousText(window.getSelection().toString().trim());
|
this._clipboardMonitor.setPreviousText(window.getSelection().toString().trim());
|
||||||
@ -261,34 +241,15 @@ class DisplaySearch extends Display {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_onClipboardMonitorEnableChange(e) {
|
_onClipboardMonitorEnableChange(e) {
|
||||||
if (e.target.checked) {
|
const enabled = e.target.checked;
|
||||||
chrome.permissions.request(
|
this._setClipboardMonitorEnabled(enabled);
|
||||||
{permissions: ['clipboardRead']},
|
|
||||||
(granted) => {
|
|
||||||
if (granted) {
|
|
||||||
this._clipboardMonitor.start();
|
|
||||||
api.modifySettings([{
|
|
||||||
action: 'set',
|
|
||||||
path: 'general.enableClipboardMonitor',
|
|
||||||
value: true,
|
|
||||||
scope: 'profile',
|
|
||||||
optionsContext: this.getOptionsContext()
|
|
||||||
}], 'search');
|
|
||||||
} else {
|
|
||||||
e.target.checked = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
this._clipboardMonitor.stop();
|
|
||||||
api.modifySettings([{
|
|
||||||
action: 'set',
|
|
||||||
path: 'general.enableClipboardMonitor',
|
|
||||||
value: false,
|
|
||||||
scope: 'profile',
|
|
||||||
optionsContext: this.getOptionsContext()
|
|
||||||
}], 'search');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_onModeChange() {
|
||||||
|
let mode = this.mode;
|
||||||
|
if (mode === null) { mode = ''; }
|
||||||
|
document.documentElement.dataset.searchMode = mode;
|
||||||
|
this._updateClipboardMonitorEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
_isWanakanaEnabled() {
|
_isWanakanaEnabled() {
|
||||||
@ -381,4 +342,48 @@ class DisplaySearch extends Display {
|
|||||||
|
|
||||||
await onOptionsUpdated();
|
await onOptionsUpdated();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async _setClipboardMonitorEnabled(value) {
|
||||||
|
let modify = true;
|
||||||
|
if (value) {
|
||||||
|
value = await this._requestPermissions(['clipboardRead']);
|
||||||
|
modify = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._clipboardMonitorEnabled = value;
|
||||||
|
this._updateClipboardMonitorEnabled();
|
||||||
|
|
||||||
|
if (!modify) { return; }
|
||||||
|
|
||||||
|
await api.modifySettings([{
|
||||||
|
action: 'set',
|
||||||
|
path: 'general.enableClipboardMonitor',
|
||||||
|
value,
|
||||||
|
scope: 'profile',
|
||||||
|
optionsContext: this.getOptionsContext()
|
||||||
|
}], 'search');
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateClipboardMonitorEnabled() {
|
||||||
|
const mode = this.mode;
|
||||||
|
const enabled = this._clipboardMonitorEnabled;
|
||||||
|
this._clipboardMonitorEnable.checked = enabled;
|
||||||
|
if (enabled && mode !== 'popup') {
|
||||||
|
this._clipboardMonitor.start();
|
||||||
|
} else {
|
||||||
|
this._clipboardMonitor.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_requestPermissions(permissions) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
chrome.permissions.request(
|
||||||
|
{permissions},
|
||||||
|
(granted) => {
|
||||||
|
const e = chrome.runtime.lastError;
|
||||||
|
resolve(!e && granted);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ class DisplayFloat extends Display {
|
|||||||
async prepare() {
|
async prepare() {
|
||||||
await super.prepare();
|
await super.prepare();
|
||||||
|
|
||||||
this.registerMessageHandlers([
|
this.registerDirectMessageHandlers([
|
||||||
['configure', {async: true, handler: this._onMessageConfigure.bind(this)}],
|
['configure', {async: true, handler: this._onMessageConfigure.bind(this)}],
|
||||||
['setContentScale', {async: false, handler: this._onMessageSetContentScale.bind(this)}]
|
['setContentScale', {async: false, handler: this._onMessageSetContentScale.bind(this)}]
|
||||||
]);
|
]);
|
||||||
|
@ -65,6 +65,7 @@ class Display extends EventDispatcher {
|
|||||||
this._hotkeys = new Map();
|
this._hotkeys = new Map();
|
||||||
this._actions = new Map();
|
this._actions = new Map();
|
||||||
this._messageHandlers = new Map();
|
this._messageHandlers = new Map();
|
||||||
|
this._directMessageHandlers = new Map();
|
||||||
this._history = new DisplayHistory({clearable: true, useBrowserHistory: false});
|
this._history = new DisplayHistory({clearable: true, useBrowserHistory: false});
|
||||||
this._historyChangeIgnore = false;
|
this._historyChangeIgnore = false;
|
||||||
this._historyHasChanged = false;
|
this._historyHasChanged = false;
|
||||||
@ -80,6 +81,7 @@ class Display extends EventDispatcher {
|
|||||||
getOptionsContext: this.getOptionsContext.bind(this),
|
getOptionsContext: this.getOptionsContext.bind(this),
|
||||||
setSpinnerVisible: this.setSpinnerVisible.bind(this)
|
setSpinnerVisible: this.setSpinnerVisible.bind(this)
|
||||||
});
|
});
|
||||||
|
this._mode = null;
|
||||||
|
|
||||||
this.registerActions([
|
this.registerActions([
|
||||||
['close', () => { this.onEscape(); }],
|
['close', () => { this.onEscape(); }],
|
||||||
@ -114,6 +116,9 @@ class Display extends EventDispatcher {
|
|||||||
{key: 'V', modifiers: ['alt'], action: 'viewNote'}
|
{key: 'V', modifiers: ['alt'], action: 'viewNote'}
|
||||||
]);
|
]);
|
||||||
this.registerMessageHandlers([
|
this.registerMessageHandlers([
|
||||||
|
['setMode', {async: false, handler: this._onMessageSetMode.bind(this)}]
|
||||||
|
]);
|
||||||
|
this.registerDirectMessageHandlers([
|
||||||
['setOptionsContext', {async: false, handler: this._onMessageSetOptionsContext.bind(this)}],
|
['setOptionsContext', {async: false, handler: this._onMessageSetOptionsContext.bind(this)}],
|
||||||
['setContent', {async: false, handler: this._onMessageSetContent.bind(this)}],
|
['setContent', {async: false, handler: this._onMessageSetContent.bind(this)}],
|
||||||
['clearAutoPlayTimer', {async: false, handler: this._onMessageClearAutoPlayTimer.bind(this)}],
|
['clearAutoPlayTimer', {async: false, handler: this._onMessageClearAutoPlayTimer.bind(this)}],
|
||||||
@ -138,7 +143,12 @@ class Display extends EventDispatcher {
|
|||||||
this._updateQueryParserVisibility();
|
this._updateQueryParserVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get mode() {
|
||||||
|
return this._mode;
|
||||||
|
}
|
||||||
|
|
||||||
async prepare() {
|
async prepare() {
|
||||||
|
this._updateMode();
|
||||||
this._setInteractive(true);
|
this._setInteractive(true);
|
||||||
await this._displayGenerator.prepare();
|
await this._displayGenerator.prepare();
|
||||||
await this._queryParser.prepare();
|
await this._queryParser.prepare();
|
||||||
@ -146,8 +156,9 @@ class Display extends EventDispatcher {
|
|||||||
this._history.on('stateChanged', this._onStateChanged.bind(this));
|
this._history.on('stateChanged', this._onStateChanged.bind(this));
|
||||||
this._queryParser.on('searched', this._onQueryParserSearch.bind(this));
|
this._queryParser.on('searched', this._onQueryParserSearch.bind(this));
|
||||||
yomichan.on('extensionUnloaded', this._onExtensionUnloaded.bind(this));
|
yomichan.on('extensionUnloaded', this._onExtensionUnloaded.bind(this));
|
||||||
|
chrome.runtime.onMessage.addListener(this._onMessage.bind(this));
|
||||||
api.crossFrame.registerHandlers([
|
api.crossFrame.registerHandlers([
|
||||||
['popupMessage', {async: 'dynamic', handler: this._onMessage.bind(this)}]
|
['popupMessage', {async: 'dynamic', handler: this._onDirectMessage.bind(this)}]
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,6 +324,12 @@ class Display extends EventDispatcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registerDirectMessageHandlers(handlers) {
|
||||||
|
for (const [name, handlerInfo] of handlers) {
|
||||||
|
this._directMessageHandlers.set(name, handlerInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async setupNestedPopups(frontendInitializationData) {
|
async setupNestedPopups(frontendInitializationData) {
|
||||||
await dynamicLoader.loadScripts([
|
await dynamicLoader.loadScripts([
|
||||||
'/mixed/js/text-scanner.js',
|
'/mixed/js/text-scanner.js',
|
||||||
@ -343,10 +360,16 @@ class Display extends EventDispatcher {
|
|||||||
|
|
||||||
// Message handlers
|
// Message handlers
|
||||||
|
|
||||||
_onMessage(data) {
|
_onMessage({action, params}, sender, callback) {
|
||||||
|
const messageHandler = this._messageHandlers.get(action);
|
||||||
|
if (typeof messageHandler === 'undefined') { return false; }
|
||||||
|
return yomichan.invokeMessageHandler(messageHandler, params, callback, sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
_onDirectMessage(data) {
|
||||||
data = this.authenticateMessageData(data);
|
data = this.authenticateMessageData(data);
|
||||||
const {action, params} = data;
|
const {action, params} = data;
|
||||||
const handlerInfo = this._messageHandlers.get(action);
|
const handlerInfo = this._directMessageHandlers.get(action);
|
||||||
if (typeof handlerInfo === 'undefined') {
|
if (typeof handlerInfo === 'undefined') {
|
||||||
throw new Error(`Invalid action: ${action}`);
|
throw new Error(`Invalid action: ${action}`);
|
||||||
}
|
}
|
||||||
@ -356,6 +379,10 @@ class Display extends EventDispatcher {
|
|||||||
return {async, result};
|
return {async, result};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_onMessageSetMode({mode}) {
|
||||||
|
this._setMode(mode, true);
|
||||||
|
}
|
||||||
|
|
||||||
_onMessageSetOptionsContext({optionsContext}) {
|
_onMessageSetOptionsContext({optionsContext}) {
|
||||||
this.setOptionsContext(optionsContext);
|
this.setOptionsContext(optionsContext);
|
||||||
}
|
}
|
||||||
@ -1253,4 +1280,22 @@ class Display extends EventDispatcher {
|
|||||||
_closePopups() {
|
_closePopups() {
|
||||||
yomichan.trigger('closePopups');
|
yomichan.trigger('closePopups');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_updateMode() {
|
||||||
|
const mode = sessionStorage.getItem('mode');
|
||||||
|
this._setMode(mode, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
_setMode(mode, save) {
|
||||||
|
if (mode === this._mode) { return; }
|
||||||
|
if (save) {
|
||||||
|
if (mode === null) {
|
||||||
|
sessionStorage.removeItem('mode');
|
||||||
|
} else {
|
||||||
|
sessionStorage.setItem('mode', mode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._mode = mode;
|
||||||
|
this.trigger('modeChange', {mode});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user