From cdb17e44fae26fce7cc133c548cf06fc166b646b Mon Sep 17 00:00:00 2001 From: Roo Code Date: Thu, 24 Jul 2025 16:10:14 +0000 Subject: [PATCH] feat: add export option to slash command menu - Added Export as a new ContextMenuOptionType - Modified getContextMenuOptions to include Export option at the top when "/" is typed - Updated ContextMenu component to render Export option with appropriate icon - Modified ChatTextArea to handle Export selection and trigger exportMode message - Export option triggers the same action as the export button in mode settings --- .../src/components/chat/ChatTextArea.tsx | 10 +++++++++- .../src/components/chat/ContextMenu.tsx | 16 +++++++++++++++ webview-ui/src/utils/context-mentions.ts | 20 +++++++++++++++++-- 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/webview-ui/src/components/chat/ChatTextArea.tsx b/webview-ui/src/components/chat/ChatTextArea.tsx index 6c541353eb..93340f5102 100644 --- a/webview-ui/src/components/chat/ChatTextArea.tsx +++ b/webview-ui/src/components/chat/ChatTextArea.tsx @@ -264,6 +264,14 @@ const ChatTextArea = forwardRef( return } + if (type === ContextMenuOptionType.Export) { + // Handle export action for current mode + setInputValue("") + setShowContextMenu(false) + vscode.postMessage({ type: "exportMode", slug: mode }) + return + } + if (type === ContextMenuOptionType.Mode && value) { // Handle mode selection. setMode(value) @@ -325,7 +333,7 @@ const ChatTextArea = forwardRef( } }, // eslint-disable-next-line react-hooks/exhaustive-deps - [setInputValue, cursorPosition], + [setInputValue, cursorPosition, mode], ) const handleKeyDown = useCallback( diff --git a/webview-ui/src/components/chat/ContextMenu.tsx b/webview-ui/src/components/chat/ContextMenu.tsx index 1672c35ee3..5df57b88b8 100644 --- a/webview-ui/src/components/chat/ContextMenu.tsx +++ b/webview-ui/src/components/chat/ContextMenu.tsx @@ -69,6 +69,7 @@ const ContextMenu: React.FC = ({ const renderOptionContent = (option: ContextMenuQueryItem) => { switch (option.type) { case ContextMenuOptionType.Mode: + case ContextMenuOptionType.Export: return (
{option.label} @@ -163,6 +164,8 @@ const ContextMenu: React.FC = ({ switch (option.type) { case ContextMenuOptionType.Mode: return "symbol-misc" + case ContextMenuOptionType.Export: + return "export" case ContextMenuOptionType.OpenedFile: return "window" case ContextMenuOptionType.File: @@ -263,7 +266,20 @@ const ContextMenu: React.FC = ({ }} /> )} + {(option.type === ContextMenuOptionType.Mode || + option.type === ContextMenuOptionType.Export) && ( + + )} {option.type !== ContextMenuOptionType.Mode && + option.type !== ContextMenuOptionType.Export && option.type !== ContextMenuOptionType.File && option.type !== ContextMenuOptionType.Folder && option.type !== ContextMenuOptionType.OpenedFile && diff --git a/webview-ui/src/utils/context-mentions.ts b/webview-ui/src/utils/context-mentions.ts index 889dca9dbe..89b846b2bd 100644 --- a/webview-ui/src/utils/context-mentions.ts +++ b/webview-ui/src/utils/context-mentions.ts @@ -105,6 +105,7 @@ export enum ContextMenuOptionType { Git = "git", NoResults = "noResults", Mode = "mode", // Add mode type + Export = "export", // Add export type } export interface ContextMenuQueryItem { @@ -126,7 +127,18 @@ export function getContextMenuOptions( // Handle slash commands for modes if (query.startsWith("/") && inputValue.startsWith("/")) { const modeQuery = query.slice(1) - if (!modes?.length) return [{ type: ContextMenuOptionType.NoResults }] + + // Always include Export option at the top + const exportOption: ContextMenuQueryItem = { + type: ContextMenuOptionType.Export, + label: "Export current mode", + description: "Export the current mode configuration", + } + + if (!modes?.length) { + // If no modes, just show export option + return [exportOption] + } // Create searchable strings array for fzf const searchableItems = modes.map((mode) => ({ @@ -154,7 +166,11 @@ export function getContextMenuOptions( description: getModeDescription(mode), })) - return matchingModes.length > 0 ? matchingModes : [{ type: ContextMenuOptionType.NoResults }] + // Filter export option based on search query + const exportMatches = modeQuery === "" || "export".toLowerCase().includes(modeQuery.toLowerCase()) + const filteredOptions = exportMatches ? [exportOption, ...matchingModes] : matchingModes + + return filteredOptions.length > 0 ? filteredOptions : [{ type: ContextMenuOptionType.NoResults }] } const workingChanges: ContextMenuQueryItem = {