Merge pull request #261 from toasted-nutbread/search-button-reuse-tab
Reuse open search tab when clicking search button
This commit is contained in:
commit
d8f9c2e1d4
@ -91,9 +91,9 @@
|
|||||||
<input type="checkbox" id="enable-search">
|
<input type="checkbox" id="enable-search">
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<button type="button" title="Search (Alt + Insert)" class="btn btn-default btn-xs action-open-search"><span class="glyphicon glyphicon-search"></span></button>
|
<a title="Search (Alt + Insert) (Middle click to open in new tab)" class="btn btn-default btn-xs action-open-search"><span class="glyphicon glyphicon-search"></span></a>
|
||||||
<button type="button" title="Options" class="btn btn-default btn-xs action-open-options"><span class="glyphicon glyphicon-wrench"></span></button>
|
<a title="Options (Middle click to open in new tab)" class="btn btn-default btn-xs action-open-options"><span class="glyphicon glyphicon-wrench"></span></a>
|
||||||
<button type="button" title="Help" class="btn btn-default btn-xs action-open-help"><span class="glyphicon glyphicon-question-sign"></span></button>
|
<a title="Help" class="btn btn-default btn-xs action-open-help"><span class="glyphicon glyphicon-question-sign"></span></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="full">
|
<div id="full">
|
||||||
|
113
ext/bg/js/api.js
113
ext/bg/js/api.js
@ -144,24 +144,46 @@ async function apiTemplateRender(template, data, dynamic) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function apiCommandExec(command) {
|
async function apiCommandExec(command, params) {
|
||||||
const handlers = apiCommandExec.handlers;
|
const handlers = apiCommandExec.handlers;
|
||||||
if (handlers.hasOwnProperty(command)) {
|
if (handlers.hasOwnProperty(command)) {
|
||||||
const handler = handlers[command];
|
const handler = handlers[command];
|
||||||
handler();
|
handler(params);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
apiCommandExec.handlers = {
|
apiCommandExec.handlers = {
|
||||||
search: () => {
|
search: async (params) => {
|
||||||
chrome.tabs.create({url: chrome.extension.getURL('/bg/search.html')});
|
const url = chrome.extension.getURL('/bg/search.html');
|
||||||
|
if (!(params && params.newTab)) {
|
||||||
|
try {
|
||||||
|
const tab = await apiFindTab(1000, (url2) => (
|
||||||
|
url2 !== null &&
|
||||||
|
url2.startsWith(url) &&
|
||||||
|
(url2.length === url.length || url2[url.length] === '?' || url2[url.length] === '#')
|
||||||
|
));
|
||||||
|
if (tab !== null) {
|
||||||
|
await apiFocusTab(tab);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// NOP
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chrome.tabs.create({url});
|
||||||
},
|
},
|
||||||
|
|
||||||
help: () => {
|
help: () => {
|
||||||
chrome.tabs.create({url: 'https://foosoft.net/projects/yomichan/'});
|
chrome.tabs.create({url: 'https://foosoft.net/projects/yomichan/'});
|
||||||
},
|
},
|
||||||
|
|
||||||
options: () => {
|
options: (params) => {
|
||||||
|
if (!(params && params.newTab)) {
|
||||||
chrome.runtime.openOptionsPage();
|
chrome.runtime.openOptionsPage();
|
||||||
|
} else {
|
||||||
|
const manifest = chrome.runtime.getManifest();
|
||||||
|
const url = chrome.extension.getURL(manifest.options_ui.page);
|
||||||
|
chrome.tabs.create({url});
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
toggle: async () => {
|
toggle: async () => {
|
||||||
@ -298,3 +320,84 @@ async function apiGetBrowser() {
|
|||||||
return 'chrome';
|
return 'chrome';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function apiGetTabUrl(tab) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
chrome.tabs.sendMessage(tab.id, {action: 'getUrl'}, {frameId: 0}, (response) => {
|
||||||
|
let url = null;
|
||||||
|
if (!chrome.runtime.lastError) {
|
||||||
|
url = (response !== null && typeof response === 'object' && !Array.isArray(response) ? response.url : null);
|
||||||
|
if (url !== null && typeof url !== 'string') {
|
||||||
|
url = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resolve({tab, url});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function apiFindTab(timeout, checkUrl) {
|
||||||
|
// This function works around the need to have the "tabs" permission to access tab.url.
|
||||||
|
const tabs = await new Promise((resolve) => chrome.tabs.query({}, resolve));
|
||||||
|
let matchPromiseResolve = null;
|
||||||
|
const matchPromise = new Promise((resolve) => { matchPromiseResolve = resolve; });
|
||||||
|
|
||||||
|
const checkTabUrl = ({tab, url}) => {
|
||||||
|
if (checkUrl(url, tab)) {
|
||||||
|
matchPromiseResolve(tab);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const promises = [];
|
||||||
|
for (const tab of tabs) {
|
||||||
|
const promise = apiGetTabUrl(tab);
|
||||||
|
promise.then(checkTabUrl);
|
||||||
|
promises.push(promise);
|
||||||
|
}
|
||||||
|
|
||||||
|
const racePromises = [
|
||||||
|
matchPromise,
|
||||||
|
Promise.all(promises).then(() => null)
|
||||||
|
];
|
||||||
|
if (typeof timeout === 'number') {
|
||||||
|
racePromises.push(new Promise((resolve) => setTimeout(() => resolve(null), timeout)));
|
||||||
|
}
|
||||||
|
|
||||||
|
return await Promise.race(racePromises);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function apiFocusTab(tab) {
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
chrome.tabs.update(tab.id, {active: true}, () => {
|
||||||
|
const e = chrome.runtime.lastError;
|
||||||
|
if (e) { reject(e); }
|
||||||
|
else { resolve(); }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!(typeof chrome.windows === 'object' && chrome.windows !== null)) {
|
||||||
|
// Windows not supported (e.g. on Firefox mobile)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const tabWindow = await new Promise((resolve) => {
|
||||||
|
chrome.windows.get(tab.windowId, {}, (tabWindow) => {
|
||||||
|
const e = chrome.runtime.lastError;
|
||||||
|
if (e) { reject(e); }
|
||||||
|
else { resolve(tabWindow); }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
if (!tabWindow.focused) {
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
chrome.windows.update(tab.windowId, {focused: true}, () => {
|
||||||
|
const e = chrome.runtime.lastError;
|
||||||
|
if (e) { reject(e); }
|
||||||
|
else { resolve(); }
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Edge throws exception for no reason here.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -181,7 +181,7 @@ Backend.messageHandlers = {
|
|||||||
definitionsAddable: ({definitions, modes, optionsContext}) => apiDefinitionsAddable(definitions, modes, optionsContext),
|
definitionsAddable: ({definitions, modes, optionsContext}) => apiDefinitionsAddable(definitions, modes, optionsContext),
|
||||||
noteView: ({noteId}) => apiNoteView(noteId),
|
noteView: ({noteId}) => apiNoteView(noteId),
|
||||||
templateRender: ({template, data, dynamic}) => apiTemplateRender(template, data, dynamic),
|
templateRender: ({template, data, dynamic}) => apiTemplateRender(template, data, dynamic),
|
||||||
commandExec: ({command}) => apiCommandExec(command),
|
commandExec: ({command, params}) => apiCommandExec(command, params),
|
||||||
audioGetUrl: ({definition, source, optionsContext}) => apiAudioGetUrl(definition, source, optionsContext),
|
audioGetUrl: ({definition, source, optionsContext}) => apiAudioGetUrl(definition, source, optionsContext),
|
||||||
screenshotGet: ({options}, sender) => apiScreenshotGet(options, sender),
|
screenshotGet: ({options}, sender) => apiScreenshotGet(options, sender),
|
||||||
forward: ({action, params}, sender) => apiForward(action, params, sender),
|
forward: ({action, params}, sender) => apiForward(action, params, sender),
|
||||||
|
@ -25,6 +25,26 @@ function showExtensionInfo() {
|
|||||||
node.textContent = `${manifest.name} v${manifest.version}`;
|
node.textContent = `${manifest.name} v${manifest.version}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setupButtonEvents(selector, command, url) {
|
||||||
|
const node = $(selector);
|
||||||
|
node.on('click', (e) => {
|
||||||
|
if (e.button !== 0) { return; }
|
||||||
|
apiCommandExec(command, {newTab: e.ctrlKey});
|
||||||
|
e.preventDefault();
|
||||||
|
})
|
||||||
|
.on('auxclick', (e) => {
|
||||||
|
if (e.button !== 1) { return; }
|
||||||
|
apiCommandExec(command, {newTab: true});
|
||||||
|
e.preventDefault();
|
||||||
|
});
|
||||||
|
|
||||||
|
if (typeof url === 'string') {
|
||||||
|
node.attr('href', url);
|
||||||
|
node.attr('target', '_blank');
|
||||||
|
node.attr('rel', 'noopener');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$(document).ready(utilAsync(() => {
|
$(document).ready(utilAsync(() => {
|
||||||
showExtensionInfo();
|
showExtensionInfo();
|
||||||
|
|
||||||
@ -33,9 +53,11 @@ $(document).ready(utilAsync(() => {
|
|||||||
document.documentElement.dataset.mode = (browser === 'firefox-mobile' ? 'full' : 'mini');
|
document.documentElement.dataset.mode = (browser === 'firefox-mobile' ? 'full' : 'mini');
|
||||||
});
|
});
|
||||||
|
|
||||||
$('.action-open-search').click(() => apiCommandExec('search'));
|
const manifest = chrome.runtime.getManifest();
|
||||||
$('.action-open-options').click(() => apiCommandExec('options'));
|
|
||||||
$('.action-open-help').click(() => apiCommandExec('help'));
|
setupButtonEvents('.action-open-search', 'search', chrome.extension.getURL('/bg/search.html'));
|
||||||
|
setupButtonEvents('.action-open-options', 'options', chrome.extension.getURL(manifest.options_ui.page));
|
||||||
|
setupButtonEvents('.action-open-help', 'help');
|
||||||
|
|
||||||
const optionsContext = {
|
const optionsContext = {
|
||||||
depth: 0,
|
depth: 0,
|
||||||
|
@ -114,6 +114,17 @@ class DisplaySearch extends Display {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onRuntimeMessage({action, params}, sender, callback) {
|
||||||
|
const handlers = DisplaySearch.runtimeMessageHandlers;
|
||||||
|
if (handlers.hasOwnProperty(action)) {
|
||||||
|
const handler = handlers[action];
|
||||||
|
const result = handler(this, params);
|
||||||
|
callback(result);
|
||||||
|
} else {
|
||||||
|
return super.onRuntimeMessage({action, params}, sender, callback);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getOptionsContext() {
|
getOptionsContext() {
|
||||||
return this.optionsContext;
|
return this.optionsContext;
|
||||||
}
|
}
|
||||||
@ -188,4 +199,10 @@ class DisplaySearch extends Display {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DisplaySearch.runtimeMessageHandlers = {
|
||||||
|
getUrl: () => {
|
||||||
|
return {url: window.location.href};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
window.yomichan_search = DisplaySearch.create();
|
window.yomichan_search = DisplaySearch.create();
|
||||||
|
@ -430,9 +430,14 @@ async function onOptionsUpdate({source}) {
|
|||||||
await formWrite(options);
|
await formWrite(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
function onMessage({action, params}) {
|
function onMessage({action, params}, sender, callback) {
|
||||||
if (action === 'optionsUpdate') {
|
switch (action) {
|
||||||
|
case 'optionsUpdate':
|
||||||
onOptionsUpdate(params);
|
onOptionsUpdate(params);
|
||||||
|
break;
|
||||||
|
case 'getUrl':
|
||||||
|
callback({url: window.location.href});
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,8 +49,8 @@ function apiAudioGetUrl(definition, source, optionsContext) {
|
|||||||
return utilInvoke('audioGetUrl', {definition, source, optionsContext});
|
return utilInvoke('audioGetUrl', {definition, source, optionsContext});
|
||||||
}
|
}
|
||||||
|
|
||||||
function apiCommandExec(command) {
|
function apiCommandExec(command, params) {
|
||||||
return utilInvoke('commandExec', {command});
|
return utilInvoke('commandExec', {command, params});
|
||||||
}
|
}
|
||||||
|
|
||||||
function apiScreenshotGet(options) {
|
function apiScreenshotGet(options) {
|
||||||
|
@ -237,8 +237,8 @@ class Frontend {
|
|||||||
const handlers = Frontend.runtimeMessageHandlers;
|
const handlers = Frontend.runtimeMessageHandlers;
|
||||||
if (handlers.hasOwnProperty(action)) {
|
if (handlers.hasOwnProperty(action)) {
|
||||||
const handler = handlers[action];
|
const handler = handlers[action];
|
||||||
handler(this, params);
|
const result = handler(this, params);
|
||||||
callback();
|
callback(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -576,5 +576,9 @@ Frontend.runtimeMessageHandlers = {
|
|||||||
|
|
||||||
popupSetVisibleOverride: (self, {visible}) => {
|
popupSetVisibleOverride: (self, {visible}) => {
|
||||||
self.popup.setVisibleOverride(visible);
|
self.popup.setVisibleOverride(visible);
|
||||||
|
},
|
||||||
|
|
||||||
|
getUrl: () => {
|
||||||
|
return {url: window.location.href};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -175,8 +175,8 @@ class Display {
|
|||||||
const handlers = Display.runtimeMessageHandlers;
|
const handlers = Display.runtimeMessageHandlers;
|
||||||
if (handlers.hasOwnProperty(action)) {
|
if (handlers.hasOwnProperty(action)) {
|
||||||
const handler = handlers[action];
|
const handler = handlers[action];
|
||||||
handler(this, params);
|
const result = handler(this, params);
|
||||||
callback();
|
callback(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user