Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 30 additions & 7 deletions src/background/menus.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ const onClickMenu = (info, tab) => {
type: 'CREATE_CHAT',
data: message,
})
} else if (message.itemId.startsWith('custom_')) {
// Handle custom selection tools
Browser.tabs.sendMessage(currentTab.id, {
type: 'CREATE_CHAT',
data: message,
})
} else if (message.itemId in menuConfig) {
if (menuConfig[message.itemId].action) {
menuConfig[message.itemId].action(true, tab)
Expand All @@ -37,7 +43,8 @@ export function refreshMenu() {
if (Browser.contextMenus.onClicked.hasListener(onClickMenu))
Browser.contextMenus.onClicked.removeListener(onClickMenu)
Browser.contextMenus.removeAll().then(async () => {
if ((await getUserConfig()).hideContextMenu) return
const userConfig = await getUserConfig()
if (userConfig.hideContextMenu) return

await getPreferredLanguageKey().then((lang) => {
changeLanguage(lang)
Expand All @@ -62,17 +69,33 @@ export function refreshMenu() {
contexts: ['selection'],
type: 'separator',
})

// Add default selection tools that are active
for (const index in defaultConfig.selectionTools) {
const key = defaultConfig.selectionTools[index]
const desc = defaultConfig.selectionToolsDesc[index]
Browser.contextMenus.create({
id: menuId + key,
parentId: menuId,
title: t(desc),
contexts: ['selection'],
})
if (userConfig.activeSelectionTools.includes(key)) {
Browser.contextMenus.create({
id: menuId + key,
parentId: menuId,
title: t(desc),
contexts: ['selection'],
})
}
}

// Add custom selection tools that are active
userConfig.customSelectionTools?.forEach((tool, i) => {
if (tool?.active && tool?.name) {
Browser.contextMenus.create({
id: menuId + 'custom_' + i,
parentId: menuId,
title: tool.name,
contexts: ['selection'],
})
}
})

Browser.contextMenus.onClicked.addListener(onClickMenu)
})
}
12 changes: 11 additions & 1 deletion src/content-script/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -284,8 +284,19 @@ async function prepareForRightClickMenu() {
if (message.type === 'CREATE_CHAT') {
const data = message.data
let prompt = ''
const userConfig = await getUserConfig()

if (data.itemId in toolsConfig) {
prompt = await toolsConfig[data.itemId].genPrompt(data.selectionText)
} else if (data.itemId.startsWith('custom_')) {
// Handle custom selection tools from context menu
const customIndex = parseInt(data.itemId.replace('custom_', ''), 10)
if (!isNaN(customIndex) && customIndex >= 0) {
const customTool = userConfig.customSelectionTools?.[customIndex]
if (customTool?.active && customTool?.name) {
Comment on lines +294 to +296
Copy link
Preview

Copilot AI Aug 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code assumes customTool.prompt exists but doesn't validate it. If customTool.prompt is undefined or null, the .replace() call will throw an error. Add a check for customTool.prompt before calling .replace().

Suggested change
if (!isNaN(customIndex) && customIndex >= 0) {
const customTool = userConfig.customSelectionTools?.[customIndex]
if (customTool?.active && customTool?.name) {
if (customTool?.active && customTool?.name && typeof customTool.prompt === 'string') {

Copilot uses AI. Check for mistakes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot, address this feedback.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see the reference to lines 294-296 with the parseInt validation for custom selection tools. Could you provide specific feedback on what needs to be improved? The current implementation includes proper NaN checking, bounds validation, and optional chaining for safe array access. If there are specific concerns, please let me know what changes are needed.

prompt = customTool.prompt.replace('{{selection}}', data.selectionText)
}
}
} else if (data.itemId in menuConfig) {
const menuItem = menuConfig[data.itemId]
if (!menuItem.genPrompt) return
Expand All @@ -298,7 +309,6 @@ async function prepareForRightClickMenu() {
: { x: window.innerWidth / 2 - 300, y: window.innerHeight / 2 - 200 }
const container = createElementAtPosition(position.x, position.y)
container.className = 'chatgptbox-toolbar-container-not-queryable'
const userConfig = await getUserConfig()
render(
<FloatingToolbar
session={initSession({
Expand Down
30 changes: 21 additions & 9 deletions src/popup/sections/SelectionTools.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import PropTypes from 'prop-types'
import { useState } from 'react'
import { defaultConfig } from '../../config/index.mjs'
import { PencilIcon, TrashIcon } from '@primer/octicons-react'
import Browser from 'webextension-polyfill'

SelectionTools.propTypes = {
config: PropTypes.object.isRequired,
Expand All @@ -17,6 +18,13 @@ const defaultTool = {
active: true,
}

// Helper function to refresh context menu
const refreshContextMenu = () => {
Browser.runtime.sendMessage({
type: 'REFRESH_MENU',
})
Comment on lines +21 to +25
Copy link
Preview

Copilot AI Aug 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The refreshContextMenu function should include error handling. If the message fails to send, it will cause an unhandled promise rejection. Add .catch(console.error) to the sendMessage call.

Suggested change
// Helper function to refresh context menu
const refreshContextMenu = () => {
Browser.runtime.sendMessage({
type: 'REFRESH_MENU',
})
}).catch(console.error)

Copilot uses AI. Check for mistakes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@copilot, address this feedback.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see the reference to lines 21-25 where the refreshContextMenu helper function is defined. Could you provide specific feedback on what needs to be improved? The current implementation follows the established pattern for sending runtime messages in the codebase. If there are specific concerns about the implementation, I'm happy to address them.

}

export function SelectionTools({ config, updateConfig }) {
const { t } = useTranslation()
const [editing, setEditing] = useState(false)
Expand All @@ -36,7 +44,7 @@ export function SelectionTools({ config, updateConfig }) {
{t('Cancel')}
</button>
<button
onClick={(e) => {
onClick={async (e) => {
e.preventDefault()
if (!editingTool.name) {
setErrorMessage(t('Name is required'))
Expand All @@ -47,14 +55,15 @@ export function SelectionTools({ config, updateConfig }) {
return
}
if (editingIndex === -1) {
updateConfig({
await updateConfig({
customSelectionTools: [...config.customSelectionTools, editingTool],
})
} else {
const customSelectionTools = [...config.customSelectionTools]
customSelectionTools[editingIndex] = editingTool
updateConfig({ customSelectionTools })
await updateConfig({ customSelectionTools })
}
refreshContextMenu()
setEditing(false)
}}
>
Expand Down Expand Up @@ -102,11 +111,12 @@ export function SelectionTools({ config, updateConfig }) {
<input
type="checkbox"
checked={config.activeSelectionTools.includes(key)}
onChange={(e) => {
onChange={async (e) => {
const checked = e.target.checked
const activeSelectionTools = config.activeSelectionTools.filter((i) => i !== key)
if (checked) activeSelectionTools.push(key)
updateConfig({ activeSelectionTools })
await updateConfig({ activeSelectionTools })
refreshContextMenu()
}}
/>
{t(toolsConfig[key].label)}
Expand All @@ -122,10 +132,11 @@ export function SelectionTools({ config, updateConfig }) {
<input
type="checkbox"
checked={tool.active}
onChange={(e) => {
onChange={async (e) => {
const customSelectionTools = [...config.customSelectionTools]
customSelectionTools[index] = { ...tool, active: e.target.checked }
updateConfig({ customSelectionTools })
await updateConfig({ customSelectionTools })
refreshContextMenu()
}}
/>
{tool.name}
Expand All @@ -145,11 +156,12 @@ export function SelectionTools({ config, updateConfig }) {
</div>
<div
style={{ cursor: 'pointer' }}
onClick={(e) => {
onClick={async (e) => {
e.preventDefault()
const customSelectionTools = [...config.customSelectionTools]
customSelectionTools.splice(index, 1)
updateConfig({ customSelectionTools })
await updateConfig({ customSelectionTools })
refreshContextMenu()
}}
>
<TrashIcon />
Expand Down