@@ -98,6 +98,7 @@ import { usePanelEditorStore } from '@/stores/panel'
9898import { useUndoRedoStore } from '@/stores/undo-redo'
9999import { useVariablesModalStore } from '@/stores/variables/modal'
100100import { useWorkflowDiffStore } from '@/stores/workflow-diff/store'
101+ import { useWorkflowSearchReplaceStore } from '@/stores/workflow-search-replace/store'
101102import { useWorkflowRegistry } from '@/stores/workflows/registry/store'
102103import { getUniqueBlockName , prepareBlockState } from '@/stores/workflows/utils'
103104import { useWorkflowStore } from '@/stores/workflows/workflow/store'
@@ -965,6 +966,56 @@ const WorkflowContent = React.memo(
965966 copyBlocks ( blockIds )
966967 } , [ contextMenuBlocks , copyBlocks ] )
967968
969+ const notifyProtectedBlockRemoval = useCallback (
970+ ( protectedIds : string [ ] , allProtected : boolean ) => {
971+ if ( protectedIds . length === 0 ) return false
972+
973+ if ( allProtected ) {
974+ addNotification ( {
975+ level : 'info' ,
976+ message : 'Cannot delete locked blocks or blocks inside locked containers' ,
977+ workflowId : activeWorkflowId || undefined ,
978+ } )
979+ return true
980+ }
981+
982+ addNotification ( {
983+ level : 'info' ,
984+ message : `Skipped ${ protectedIds . length } protected block(s)` ,
985+ workflowId : activeWorkflowId || undefined ,
986+ } )
987+ return false
988+ } ,
989+ [ activeWorkflowId , addNotification ]
990+ )
991+
992+ const removeBlocksWithProtection = useCallback (
993+ ( blockIds : string [ ] ) => {
994+ const { deletableIds, protectedIds, allProtected } = filterProtectedBlocks ( blockIds , blocks )
995+ if ( notifyProtectedBlockRemoval ( protectedIds , allProtected ) ) return [ ]
996+
997+ if ( deletableIds . length > 0 ) {
998+ collaborativeBatchRemoveBlocks ( deletableIds )
999+ }
1000+
1001+ return deletableIds
1002+ } ,
1003+ [ blocks , collaborativeBatchRemoveBlocks , notifyProtectedBlockRemoval ]
1004+ )
1005+
1006+ const cutBlocksWithProtection = useCallback (
1007+ ( blockIds : string [ ] ) => {
1008+ const { deletableIds, protectedIds, allProtected } = filterProtectedBlocks ( blockIds , blocks )
1009+ if ( notifyProtectedBlockRemoval ( protectedIds , allProtected ) ) return
1010+
1011+ if ( deletableIds . length > 0 ) {
1012+ copyBlocks ( deletableIds )
1013+ collaborativeBatchRemoveBlocks ( deletableIds )
1014+ }
1015+ } ,
1016+ [ blocks , collaborativeBatchRemoveBlocks , copyBlocks , notifyProtectedBlockRemoval ]
1017+ )
1018+
9681019 /**
9691020 * Executes a paste operation with validation and selection handling.
9701021 * Consolidates shared logic for context paste, duplicate, and keyboard paste.
@@ -1165,35 +1216,13 @@ const WorkflowContent = React.memo(
11651216 executePasteOperation ( 'duplicate' , DEFAULT_PASTE_OFFSET )
11661217 } , [ contextMenuBlocks , copyBlocks , executePasteOperation ] )
11671218
1168- const handleContextDelete = useCallback ( ( ) => {
1169- const blockIds = contextMenuBlocks . map ( ( b ) => b . id )
1170- const { deletableIds , protectedIds , allProtected } = filterProtectedBlocks ( blockIds , blocks )
1219+ const handleContextCut = useCallback ( ( ) => {
1220+ cutBlocksWithProtection ( contextMenuBlocks . map ( ( b ) => b . id ) )
1221+ } , [ contextMenuBlocks , cutBlocksWithProtection ] )
11711222
1172- if ( protectedIds . length > 0 ) {
1173- if ( allProtected ) {
1174- addNotification ( {
1175- level : 'info' ,
1176- message : 'Cannot delete locked blocks or blocks inside locked containers' ,
1177- workflowId : activeWorkflowId || undefined ,
1178- } )
1179- return
1180- }
1181- addNotification ( {
1182- level : 'info' ,
1183- message : `Skipped ${ protectedIds . length } protected block(s)` ,
1184- workflowId : activeWorkflowId || undefined ,
1185- } )
1186- }
1187- if ( deletableIds . length > 0 ) {
1188- collaborativeBatchRemoveBlocks ( deletableIds )
1189- }
1190- } , [
1191- contextMenuBlocks ,
1192- collaborativeBatchRemoveBlocks ,
1193- addNotification ,
1194- activeWorkflowId ,
1195- blocks ,
1196- ] )
1223+ const handleContextDelete = useCallback ( ( ) => {
1224+ removeBlocksWithProtection ( contextMenuBlocks . map ( ( b ) => b . id ) )
1225+ } , [ contextMenuBlocks , removeBlocksWithProtection ] )
11971226
11981227 const handleContextToggleEnabled = useCallback ( ( ) => {
11991228 const blockIds = contextMenuBlocks . map ( ( block ) => block . id )
@@ -1416,6 +1445,10 @@ const WorkflowContent = React.memo(
14161445 router . push ( `/workspace/${ workspaceId } /logs?workflowIds=${ workflowIdParam } ` )
14171446 } , [ router , workspaceId , workflowIdParam ] )
14181447
1448+ const handleContextOpenSearchReplace = useCallback ( ( ) => {
1449+ useWorkflowSearchReplaceStore . getState ( ) . open ( )
1450+ } , [ ] )
1451+
14191452 const handleContextToggleVariables = useCallback ( ( ) => {
14201453 const { isOpen, setIsOpen } = useVariablesModalStore . getState ( )
14211454 setIsOpen ( ! isOpen )
@@ -1467,6 +1500,19 @@ const WorkflowContent = React.memo(
14671500 copyBlocks ( [ currentBlockId ] )
14681501 }
14691502 }
1503+ } else if ( ( event . ctrlKey || event . metaKey ) && event . key === 'x' ) {
1504+ const selection = window . getSelection ( )
1505+ const hasTextSelection = selection && selection . toString ( ) . length > 0
1506+
1507+ if ( hasTextSelection || ! effectivePermissions . canEdit ) {
1508+ return
1509+ }
1510+
1511+ const selectedNodes = getNodes ( ) . filter ( ( node ) => node . selected )
1512+ if ( selectedNodes . length > 0 ) {
1513+ event . preventDefault ( )
1514+ cutBlocksWithProtection ( selectedNodes . map ( ( node ) => node . id ) )
1515+ }
14701516 } else if ( ( event . ctrlKey || event . metaKey ) && event . key === 'v' ) {
14711517 if ( effectivePermissions . canEdit && hasClipboard ( ) ) {
14721518 event . preventDefault ( )
@@ -1487,6 +1533,7 @@ const WorkflowContent = React.memo(
14871533 redo ,
14881534 getNodes ,
14891535 copyBlocks ,
1536+ cutBlocksWithProtection ,
14901537 hasClipboard ,
14911538 effectivePermissions . canEdit ,
14921539 clipboard ,
@@ -4172,6 +4219,7 @@ const WorkflowContent = React.memo(
41724219 onClose = { closeContextMenu }
41734220 selectedBlocks = { contextMenuBlocks }
41744221 onCopy = { handleContextCopy }
4222+ onCut = { handleContextCut }
41754223 onPaste = { handleContextPaste }
41764224 onDuplicate = { handleContextDuplicate }
41774225 onDelete = { handleContextDelete }
@@ -4214,6 +4262,7 @@ const WorkflowContent = React.memo(
42144262 onAutoLayout = { handleAutoLayout }
42154263 onFitToView = { ( ) => fitViewToBounds ( { padding : 0.1 , duration : 300 } ) }
42164264 onOpenLogs = { handleContextOpenLogs }
4265+ onOpenSearchReplace = { handleContextOpenSearchReplace }
42174266 onToggleVariables = { handleContextToggleVariables }
42184267 onToggleChat = { handleContextToggleChat }
42194268 isVariablesOpen = { isVariablesOpen }
0 commit comments