From f683f2e8411ef0519430c3dbd1d64489080bb32f Mon Sep 17 00:00:00 2001 From: golem Date: Sat, 1 Aug 2026 22:21:06 -0600 Subject: [PATCH] feat: add context menu and keyboard shortcut entry points --- background/background.js | 99 ++++++++++++++++++++++++++++++++++++++++ manifest.json | 27 ++++++++++- 2 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 background/background.js diff --git a/background/background.js b/background/background.js new file mode 100644 index 0000000..e1c85ea --- /dev/null +++ b/background/background.js @@ -0,0 +1,99 @@ +import { load } from "../lib/settings.js"; +import { buildChatGptUrl } from "../lib/build-url.js"; + +const MENU_ID = "ask-chatgpt-selection"; +const COMMAND_ID = "ask-chatgpt"; + +// Reads the user's selection for the keyboard-shortcut path. The context menu +// does not need this — it receives info.selectionText directly. +// +// A commands invocation confers activeTab (ext-commands.js grants it before +// firing onCommand), so executeScript works with no host permission. +// +// Failure has three measured shapes, all handled here: a thrown +// "Missing host permission for the tab", a thrown variant naming frames, and a +// SILENT resolution to [null] on parent-process about: pages. Never index into +// a result without a null check. +async function readSelection() { + let tabs; + try { + tabs = await browser.tabs.query({ active: true, currentWindow: true }); + } catch { + return ""; + } + + // Only tab.id may be read. Reading tab.url or tab.title would require the + // "tabs" permission, which does add an install-prompt warning line. + const tabId = tabs?.[0]?.id; + if (typeof tabId !== "number") { + return ""; + } + + let results; + try { + results = await browser.tabs.executeScript(tabId, { + code: "window.getSelection().toString()", + allFrames: true, + matchAboutBlank: true, + }); + } catch { + return ""; + } + + if (!Array.isArray(results)) { + return ""; + } + for (const result of results) { + if (typeof result === "string" && result.trim() !== "") { + return result; + } + } + return ""; +} + +async function openChatGpt(query) { + const settings = await load(); + const url = buildChatGptUrl(settings, query); + + if (settings.openIn === "new-window") { + try { + await browser.windows.create({ url }); + return; + } catch { + // Fall through to a tab rather than doing nothing. + } + } + + await browser.tabs.create({ url, active: settings.openIn !== "background-tab" }); +} + +// removeAll() first, so a background event page waking up and re-running this +// module cannot throw on a duplicate menu id. +async function registerMenu() { + await browser.menus.removeAll(); + browser.menus.create({ + id: MENU_ID, + title: 'Ask ChatGPT about "%s"', + contexts: ["selection"], + }); +} + +browser.menus.onClicked.addListener((info) => { + if (info.menuItemId !== MENU_ID) { + return; + } + openChatGpt(info.selectionText ?? ""); +}); + +// browser.commands does not exist on Firefox for Android. This extension is +// desktop-only, but guard rather than throwing at startup. +if (browser.commands) { + browser.commands.onCommand.addListener(async (command) => { + if (command !== COMMAND_ID) { + return; + } + openChatGpt(await readSelection()); + }); +} + +registerMenu(); diff --git a/manifest.json b/manifest.json index a7d06a9..167a9b6 100644 --- a/manifest.json +++ b/manifest.json @@ -4,7 +4,7 @@ "manifest_version": 2, "author": "Mathew Guest ", "homepage_url": "https://zavage-software.com/portfolio/search-with-ChatGPT", - "version": "1.0", + "version": "1.1", "icons": { "16": "icons/powered-by-openai-icon-16x16.png", "32": "icons/powered-by-openai-icon-32x32.png", @@ -22,6 +22,31 @@ } } }, + "permissions": [ + "storage", + "menus", + "activeTab" + ], + "background": { + "scripts": [ + "background/background.js" + ], + "type": "module", + "persistent": false + }, + "options_ui": { + "page": "options/options.html", + "open_in_tab": false, + "browser_style": false + }, + "commands": { + "ask-chatgpt": { + "suggested_key": { + "default": "Alt+Shift+G" + }, + "description": "Ask ChatGPT about the selected text" + } + }, "chrome_settings_overrides": { "search_provider": { "name": "ChatGPT",