feat: add advanced context actions and optional translation
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
import { getActions, getContext } from '../lib/actions.js';
|
||||
|
||||
const ROOT = 'ask-chatgpt';
|
||||
const SIMPLE = 'ask-chatgpt-selection';
|
||||
const PREFIX = 'ask-chatgpt:';
|
||||
const CONTEXTS = ['selection', 'link', 'page'];
|
||||
const MENU_KEYS = ['contextMenuMode', 'showContextMenu', 'showTranslate', 'translationMode', 'translationLanguage'];
|
||||
|
||||
export function initializeMenus({ browser, load, onAction }) {
|
||||
let queue = Promise.resolve();
|
||||
let mode = 'off';
|
||||
let children = [];
|
||||
let menuInstance = 0;
|
||||
const enqueue = task => {
|
||||
queue = queue.catch(() => {}).then(task);
|
||||
return queue;
|
||||
};
|
||||
const create = properties => new Promise((resolve, reject) => {
|
||||
browser.menus.create(properties, () => {
|
||||
const error = browser.runtime.lastError;
|
||||
if (error) reject(new Error(error.message)); else resolve();
|
||||
});
|
||||
});
|
||||
function rebuild() {
|
||||
return enqueue(async () => {
|
||||
const settings = await load();
|
||||
await browser.menus.removeAll();
|
||||
children = [];
|
||||
mode = settings.contextMenuMode;
|
||||
if (mode === 'off') return;
|
||||
if (mode === 'simple') {
|
||||
await create({ id: SIMPLE, title: 'Ask ChatGPT', contexts: ['selection'] });
|
||||
return;
|
||||
}
|
||||
await create({ id: ROOT, title: 'Ask ChatGPT', contexts: CONTEXTS });
|
||||
for (const kind of ['selection', 'link', 'page']) {
|
||||
for (const action of getActions({ kind }, settings)) {
|
||||
const id = PREFIX + action.id;
|
||||
await create({ id, parentId: ROOT, title: action.title, contexts: CONTEXTS, visible: false });
|
||||
children.push({ id, kind });
|
||||
if (action.id === 'ask') {
|
||||
const separator = PREFIX + 'separator';
|
||||
await create({ id: separator, parentId: ROOT, type: 'separator', contexts: CONTEXTS, visible: false });
|
||||
children.push({ id: separator, kind });
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
browser.storage.onChanged.addListener((changes, area) => {
|
||||
if (area === 'sync' && MENU_KEYS.some(key => Object.hasOwn(changes, key))) return rebuild().catch(console.error);
|
||||
});
|
||||
browser.menus.onHidden.addListener(() => { menuInstance++; });
|
||||
browser.menus.onShown.addListener(info => {
|
||||
const instance = ++menuInstance;
|
||||
// Context labels are available without host access; selectionText/pageUrl
|
||||
// are deliberately NOT required until the user invokes an item.
|
||||
const contexts = info.contexts ?? [];
|
||||
const kind = contexts.includes('selection') ? 'selection'
|
||||
: contexts.includes('link') ? 'link'
|
||||
: contexts.includes('page') && !info.editable && !contexts.some(c => ['image', 'video', 'audio'].includes(c)) ? 'page' : null;
|
||||
return enqueue(async () => {
|
||||
if (instance !== menuInstance || mode !== 'advanced') return;
|
||||
await Promise.all([
|
||||
browser.menus.update(ROOT, { visible: kind !== null }),
|
||||
...children.map(child => browser.menus.update(child.id, { visible: child.kind === kind })),
|
||||
]);
|
||||
if (instance === menuInstance) await browser.menus.refresh();
|
||||
}).catch(console.error);
|
||||
});
|
||||
browser.menus.onClicked.addListener(async info => {
|
||||
try {
|
||||
const settings = await load();
|
||||
const context = getContext(info);
|
||||
if (!context || settings.contextMenuMode === 'off') return;
|
||||
let actionId;
|
||||
if (settings.contextMenuMode === 'simple') {
|
||||
if (info.menuItemId !== SIMPLE || context.kind !== 'selection') return;
|
||||
actionId = 'ask';
|
||||
} else {
|
||||
const allowed = getActions(context, settings);
|
||||
actionId = allowed.find(action => PREFIX + action.id === info.menuItemId)?.id;
|
||||
if (!actionId) return;
|
||||
}
|
||||
await onAction({ actionId, source: context.source, settings });
|
||||
} catch { console.error('Could not run context menu action.'); }
|
||||
});
|
||||
return rebuild();
|
||||
}
|
||||
Reference in New Issue
Block a user