11import { useState , useEffect , useCallback , useRef } from 'react' ;
22import { useTranslation } from 'react-i18next' ;
3- import { X , Save , Loader2 , AlertTriangle , Wand2 , Plus } from 'lucide-react' ;
3+ import { X , Save , Loader2 , AlertTriangle , Wand2 , Plus , Search } from 'lucide-react' ;
44import { readSpaceConfig , saveSpaceConfig } from '@/lib/api/spaces' ;
55import { type Monaco } from '@monaco-editor/react' ;
66import type { editor } from 'monaco-editor' ;
@@ -171,6 +171,20 @@ export function ConfigEditorModal({
171171 }
172172 } , [ content , t ] ) ;
173173
174+ /**
175+ * Whether Monaco's find widget is currently visible in the editor DOM.
176+ */
177+ const isFindWidgetOpen = useCallback ( ( ) : boolean => {
178+ return ! ! editorRef . current ?. getDomNode ( ) ?. querySelector ( '.find-widget.visible' ) ;
179+ } , [ ] ) ;
180+
181+ /**
182+ * Open Monaco's built-in find widget for the JSON editor.
183+ */
184+ const handleSearch = useCallback ( ( ) => {
185+ editorRef . current ?. getAction ( 'actions.find' ) ?. run ( ) ;
186+ } , [ ] ) ;
187+
174188 const handleInsertCustomServer = useCallback ( ( ) => {
175189 try {
176190 const parsed = JSON . parse ( content || '{"mcpServers":{}}' ) as SpaceConfigJson ;
@@ -260,24 +274,25 @@ export function ConfigEditorModal({
260274 // Keyboard shortcuts
261275 useEffect ( ( ) => {
262276 const handleKeyDown = ( e : KeyboardEvent ) => {
263- // Ctrl+Shift+F to format
264- if ( e . ctrlKey && e . shiftKey && e . key === 'F' ) {
277+ const mod = e . ctrlKey || e . metaKey ;
278+
279+ if ( mod && e . shiftKey && e . key === 'F' ) {
265280 e . preventDefault ( ) ;
266281 handleFormat ( ) ;
267282 }
268- // Ctrl+S to save
269- if ( e . ctrlKey && e . key === 's' ) {
283+
284+ if ( mod && e . key === 's' ) {
270285 e . preventDefault ( ) ;
271286 handleSave ( ) ;
272287 }
273- // Escape to close
274- if ( e . key === 'Escape' ) {
288+
289+ if ( e . key === 'Escape' && ! isFindWidgetOpen ( ) ) {
275290 onClose ( ) ;
276291 }
277292 } ;
278293 window . addEventListener ( 'keydown' , handleKeyDown ) ;
279294 return ( ) => window . removeEventListener ( 'keydown' , handleKeyDown ) ;
280- } , [ handleFormat , handleSave , onClose ] ) ;
295+ } , [ handleFormat , handleSave , isFindWidgetOpen , onClose ] ) ;
281296
282297 return (
283298 < >
@@ -286,11 +301,11 @@ export function ConfigEditorModal({
286301 onClose = { ( id ) => toasts . find ( ( toast ) => toast . id === id ) ?. onClose ( id ) }
287302 />
288303 < div
289- className = "fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4 backdrop-blur-sm"
304+ className = "fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-6 backdrop-blur-sm"
290305 data-testid = "config-editor-modal-overlay"
291306 >
292307 < div
293- className = "flex h-[80vh ] w-full max-w-4xl flex-col rounded-xl border border-[rgb(var(--border))] bg-[rgb(var(--surface))] shadow-2xl"
308+ className = "flex h-[95vh ] w-[95vw] flex-col rounded-xl border border-[rgb(var(--border))] bg-[rgb(var(--surface))] shadow-2xl"
294309 data-testid = "config-editor-modal"
295310 >
296311 { /* Header */ }
@@ -341,6 +356,16 @@ export function ConfigEditorModal({
341356 { t ( 'configEditorModal.format' ) }
342357 </ button >
343358
359+ < button
360+ onClick = { handleSearch }
361+ disabled = { isLoading || editorLoadFailed }
362+ className = "flex items-center gap-2 rounded-lg px-3 py-1.5 text-sm font-medium text-[rgb(var(--muted))] transition-colors hover:bg-[rgb(var(--surface-hover))] hover:text-[rgb(var(--foreground))] disabled:opacity-50"
363+ title = { t ( 'configEditorModal.searchTitle' ) }
364+ >
365+ < Search className = "h-4 w-4" />
366+ { t ( 'configEditorModal.search' ) }
367+ </ button >
368+
344369 < button
345370 onClick = { handleInsertCustomServer }
346371 disabled = { isLoading || ! isValidJson }
0 commit comments