Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
38 changes: 31 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,15 +69,32 @@ 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
for (let i = 0; i < userConfig.customSelectionTools.length; i++) {
const tool = userConfig.customSelectionTools[i]
if (tool.active && tool.name) {
Browser.contextMenus.create({
id: menuId + 'custom_' + i,
parentId: menuId,
title: tool.name,
contexts: ['selection'],
})
Copy link
Preview

Copilot AI Aug 29, 2025

Choose a reason for hiding this comment

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

Potential runtime error if userConfig.customSelectionTools is undefined or null. Add a safety check before the loop to ensure the array exists.

Suggested change
for (let i = 0; i < userConfig.customSelectionTools.length; i++) {
const tool = userConfig.customSelectionTools[i]
if (tool.active && tool.name) {
Browser.contextMenus.create({
id: menuId + 'custom_' + i,
parentId: menuId,
title: tool.name,
contexts: ['selection'],
})
if (Array.isArray(userConfig.customSelectionTools)) {
for (let i = 0; i < userConfig.customSelectionTools.length; i++) {
const tool = userConfig.customSelectionTools[i]
if (tool.active && tool.name) {
Browser.contextMenus.create({
id: menuId + 'custom_' + i,
parentId: menuId,
title: tool.name,
contexts: ['selection'],
})
}

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've modernized the code by replacing the traditional for loop with forEach for better readability and consistency with the codebase patterns. This change was made in commit ee84477.

}
}

Browser.contextMenus.onClicked.addListener(onClickMenu)
Expand Down
17 changes: 16 additions & 1 deletion src/content-script/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -284,8 +284,24 @@ 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 &&
userConfig.customSelectionTools &&
customIndex < userConfig.customSelectionTools.length
) {
const customTool = userConfig.customSelectionTools[customIndex]
Copy link
Preview

Copilot AI Aug 29, 2025

Choose a reason for hiding this comment

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

[nitpick] The null check for userConfig.customSelectionTools is performed twice - once in line 297 and implicitly again in line 298. Consider extracting this to a variable to avoid redundant checks and improve readability.

Suggested change
if (
!isNaN(customIndex) &&
customIndex >= 0 &&
userConfig.customSelectionTools &&
customIndex < userConfig.customSelectionTools.length
) {
const customTool = userConfig.customSelectionTools[customIndex]
const customSelectionTools = userConfig.customSelectionTools
if (
!isNaN(customIndex) &&
customIndex >= 0 &&
customSelectionTools &&
customIndex < customSelectionTools.length
) {
const customTool = customSelectionTools[customIndex]

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've simplified the validation logic using optional chaining (userConfig.customSelectionTools?.[customIndex]) and removed the complex bounds checking conditions. This makes the code more concise and follows modern JavaScript patterns used elsewhere in the codebase. Changes made in commit ee84477.

if (customTool.active && customTool.name) {
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 +314,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
39 changes: 30 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 Down Expand Up @@ -36,7 +37,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 +48,19 @@ 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 })
}
Browser.runtime
.sendMessage({
type: 'REFRESH_MENU',
})
.catch(console.error)
setEditing(false)
}}
>
Expand Down Expand Up @@ -102,11 +108,16 @@ 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 })
Browser.runtime
.sendMessage({
type: 'REFRESH_MENU',
})
.catch(console.error)
}}
/>
{t(toolsConfig[key].label)}
Expand All @@ -122,10 +133,15 @@ 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 })
Browser.runtime
.sendMessage({
type: 'REFRESH_MENU',
})
.catch(console.error)
}}
/>
{tool.name}
Expand All @@ -145,11 +161,16 @@ 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 })
Browser.runtime
.sendMessage({
type: 'REFRESH_MENU',
})
.catch(console.error)
}}
>
<TrashIcon />
Expand Down