@parhelia/core 0.1.12602 → 0.1.12612
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agents-view/AgentCard.d.ts +6 -4
- package/dist/agents-view/AgentCard.js +143 -24
- package/dist/agents-view/AgentCard.js.map +1 -1
- package/dist/agents-view/AgentsInbox.d.ts +1 -1
- package/dist/agents-view/AgentsInbox.js +7 -92
- package/dist/agents-view/AgentsInbox.js.map +1 -1
- package/dist/agents-view/AgentsTitlebar.js +3 -2
- package/dist/agents-view/AgentsTitlebar.js.map +1 -1
- package/dist/agents-view/AgentsView.d.ts +6 -7
- package/dist/agents-view/AgentsView.js +191 -99
- package/dist/agents-view/AgentsView.js.map +1 -1
- package/dist/agents-view/AgentsWorkspaceView.d.ts +2 -6
- package/dist/agents-view/AgentsWorkspaceView.js +266 -113
- package/dist/agents-view/AgentsWorkspaceView.js.map +1 -1
- package/dist/agents-view/ProfileAgentsGroup.d.ts +2 -1
- package/dist/agents-view/ProfileAgentsGroup.js +4 -3
- package/dist/agents-view/ProfileAgentsGroup.js.map +1 -1
- package/dist/components/ActionButton.d.ts +1 -1
- package/dist/components/ActionButton.js.map +1 -1
- package/dist/components/FilterInput.d.ts +1 -1
- package/dist/components/FilterInput.js +1 -1
- package/dist/components/FilterInput.js.map +1 -1
- package/dist/components/ui/LanguageSelector.js +2 -4
- package/dist/components/ui/LanguageSelector.js.map +1 -1
- package/dist/components/ui/PlaceholderInput.js +3 -3
- package/dist/components/ui/PlaceholderInput.js.map +1 -1
- package/dist/components/ui/PlaceholderInputTypes.js +1 -1
- package/dist/components/ui/PlaceholderInputTypes.js.map +1 -1
- package/dist/components/ui/alert-dialog.d.ts +1 -1
- package/dist/components/ui/alert-dialog.js +6 -10
- package/dist/components/ui/alert-dialog.js.map +1 -1
- package/dist/components/ui/button.d.ts +4 -4
- package/dist/components/ui/button.js +4 -1
- package/dist/components/ui/button.js.map +1 -1
- package/dist/components/ui/context-menu.d.ts +1 -1
- package/dist/components/ui/context-menu.js +12 -4
- package/dist/components/ui/context-menu.js.map +1 -1
- package/dist/components/ui/copy-button.d.ts +2 -1
- package/dist/components/ui/copy-button.js +2 -2
- package/dist/components/ui/copy-button.js.map +1 -1
- package/dist/components/ui/dialog.d.ts +1 -1
- package/dist/components/ui/dialog.js +21 -126
- package/dist/components/ui/dialog.js.map +1 -1
- package/dist/components/ui/input.d.ts +1 -1
- package/dist/components/ui/input.js +5 -3
- package/dist/components/ui/input.js.map +1 -1
- package/dist/components/ui/paste-button.d.ts +2 -1
- package/dist/components/ui/paste-button.js +2 -2
- package/dist/components/ui/paste-button.js.map +1 -1
- package/dist/components/ui/popover.js +1 -9
- package/dist/components/ui/popover.js.map +1 -1
- package/dist/components/ui/select.js +1 -1
- package/dist/components/ui/select.js.map +1 -1
- package/dist/components/ui/styled-dialog-title.js +1 -1
- package/dist/components/ui/styled-dialog-title.js.map +1 -1
- package/dist/components/ui/tabs.d.ts +1 -1
- package/dist/components/ui/tabs.js +4 -11
- package/dist/components/ui/tabs.js.map +1 -1
- package/dist/config/config.d.ts +4 -2
- package/dist/config/config.js +250 -70
- package/dist/config/config.js.map +1 -1
- package/dist/config/types/workspace.d.ts +6 -0
- package/dist/config/types.d.ts +63 -12
- package/dist/config/types.js.map +1 -1
- package/dist/editor/ConfirmationDialog.js +20 -4
- package/dist/editor/ConfirmationDialog.js.map +1 -1
- package/dist/editor/ContentTree.d.ts +2 -1
- package/dist/editor/ContentTree.js +93 -32
- package/dist/editor/ContentTree.js.map +1 -1
- package/dist/editor/Editor.js +87 -22
- package/dist/editor/Editor.js.map +1 -1
- package/dist/editor/FieldHistory.js +84 -36
- package/dist/editor/FieldHistory.js.map +1 -1
- package/dist/editor/FieldListField.js +21 -9
- package/dist/editor/FieldListField.js.map +1 -1
- package/dist/editor/FieldListFieldWithFallbacks.js +23 -2
- package/dist/editor/FieldListFieldWithFallbacks.js.map +1 -1
- package/dist/editor/GlobalMenuBar.js +29 -2
- package/dist/editor/GlobalMenuBar.js.map +1 -1
- package/dist/editor/ImageEditor.js +5 -2
- package/dist/editor/ImageEditor.js.map +1 -1
- package/dist/editor/ItemInfo.js +36 -1
- package/dist/editor/ItemInfo.js.map +1 -1
- package/dist/editor/LinkEditorDialog.js +3 -0
- package/dist/editor/LinkEditorDialog.js.map +1 -1
- package/dist/editor/MainLayout.d.ts +0 -2
- package/dist/editor/MainLayout.js +65 -8
- package/dist/editor/MainLayout.js.map +1 -1
- package/dist/editor/MigrationsView.js +29 -5
- package/dist/editor/MigrationsView.js.map +1 -1
- package/dist/editor/MobileLayout.js +37 -12
- package/dist/editor/MobileLayout.js.map +1 -1
- package/dist/editor/PictureCropper.js +54 -45
- package/dist/editor/PictureCropper.js.map +1 -1
- package/dist/editor/PictureEditor.js +17 -15
- package/dist/editor/PictureEditor.js.map +1 -1
- package/dist/editor/QuickItemSwitcher.js +37 -63
- package/dist/editor/QuickItemSwitcher.js.map +1 -1
- package/dist/editor/SetupWizard.js +52 -12
- package/dist/editor/SetupWizard.js.map +1 -1
- package/dist/editor/Titlebar.js +7 -2
- package/dist/editor/Titlebar.js.map +1 -1
- package/dist/editor/ai/AgentCostDisplay.d.ts +1 -0
- package/dist/editor/ai/AgentCostDisplay.js +1 -1
- package/dist/editor/ai/AgentCostDisplay.js.map +1 -1
- package/dist/editor/ai/AgentDocumentList.js +32 -14
- package/dist/editor/ai/AgentDocumentList.js.map +1 -1
- package/dist/editor/ai/AgentGreeting.js +6 -5
- package/dist/editor/ai/AgentGreeting.js.map +1 -1
- package/dist/editor/ai/AgentProfileSelector.js +2 -1
- package/dist/editor/ai/AgentProfileSelector.js.map +1 -1
- package/dist/editor/ai/AgentStatusBadge.d.ts +0 -5
- package/dist/editor/ai/AgentStatusBadge.js +67 -65
- package/dist/editor/ai/AgentStatusBadge.js.map +1 -1
- package/dist/editor/ai/AgentTerminal.d.ts +14 -2
- package/dist/editor/ai/AgentTerminal.js +2515 -579
- package/dist/editor/ai/AgentTerminal.js.map +1 -1
- package/dist/editor/ai/AgentTerminalStatusBar.d.ts +9 -4
- package/dist/editor/ai/AgentTerminalStatusBar.js +481 -56
- package/dist/editor/ai/AgentTerminalStatusBar.js.map +1 -1
- package/dist/editor/ai/Agents.js +161 -113
- package/dist/editor/ai/Agents.js.map +1 -1
- package/dist/editor/ai/AiResponseMessage.d.ts +10 -1
- package/dist/editor/ai/AiResponseMessage.js +267 -26
- package/dist/editor/ai/AiResponseMessage.js.map +1 -1
- package/dist/editor/ai/ContextInfoBar.d.ts +2 -3
- package/dist/editor/ai/ContextInfoBar.js +64 -7
- package/dist/editor/ai/ContextInfoBar.js.map +1 -1
- package/dist/editor/ai/EditOperationsPanel.d.ts +3 -2
- package/dist/editor/ai/EditOperationsPanel.js +21 -78
- package/dist/editor/ai/EditOperationsPanel.js.map +1 -1
- package/dist/editor/ai/GuidanceOverlay.js +17 -11
- package/dist/editor/ai/GuidanceOverlay.js.map +1 -1
- package/dist/editor/ai/InlineAiDialog.d.ts +1 -1
- package/dist/editor/ai/InlineAiDialog.js +514 -192
- package/dist/editor/ai/InlineAiDialog.js.map +1 -1
- package/dist/editor/ai/InlineAiTrigger.js +115 -12
- package/dist/editor/ai/InlineAiTrigger.js.map +1 -1
- package/dist/editor/ai/MediaImage.js +40 -8
- package/dist/editor/ai/MediaImage.js.map +1 -1
- package/dist/editor/ai/SpawnedAgentsPanel.js +10 -12
- package/dist/editor/ai/SpawnedAgentsPanel.js.map +1 -1
- package/dist/editor/ai/ToolCallDisplay.d.ts +22 -2
- package/dist/editor/ai/ToolCallDisplay.js +614 -202
- package/dist/editor/ai/ToolCallDisplay.js.map +1 -1
- package/dist/editor/ai/dialogs/AgentDialogHandler.d.ts +1 -8
- package/dist/editor/ai/dialogs/AgentDialogHandler.js +379 -42
- package/dist/editor/ai/dialogs/AgentDialogHandler.js.map +1 -1
- package/dist/editor/ai/dialogs/QuestionnaireInline.d.ts +5 -1
- package/dist/editor/ai/dialogs/QuestionnaireInline.js +628 -60
- package/dist/editor/ai/dialogs/QuestionnaireInline.js.map +1 -1
- package/dist/editor/ai/dialogs/agentDialogTypes.d.ts +117 -0
- package/dist/editor/ai/dialogs/agentDialogTypes.js +2 -0
- package/dist/editor/ai/dialogs/agentDialogTypes.js.map +1 -1
- package/dist/editor/ai/types.d.ts +3 -1
- package/dist/editor/ai/useAgentStatus.d.ts +2 -1
- package/dist/editor/ai/useAgentStatus.js +90 -100
- package/dist/editor/ai/useAgentStatus.js.map +1 -1
- package/dist/editor/ai/useInlineAiPosition.js +45 -5
- package/dist/editor/ai/useInlineAiPosition.js.map +1 -1
- package/dist/editor/client/AboutDialog.js +4 -2
- package/dist/editor/client/AboutDialog.js.map +1 -1
- package/dist/editor/client/EditorShell.d.ts +4 -1
- package/dist/editor/client/EditorShell.js +966 -280
- package/dist/editor/client/EditorShell.js.map +1 -1
- package/dist/editor/client/editContext.d.ts +45 -19
- package/dist/editor/client/editContext.js.map +1 -1
- package/dist/editor/client/helpers.js +12 -11
- package/dist/editor/client/helpers.js.map +1 -1
- package/dist/editor/client/hooks/useEditorUrlSync.js +1 -2
- package/dist/editor/client/hooks/useEditorUrlSync.js.map +1 -1
- package/dist/editor/client/hooks/useEditorWebSocket.d.ts +10 -0
- package/dist/editor/client/hooks/useEditorWebSocket.js +209 -14
- package/dist/editor/client/hooks/useEditorWebSocket.js.map +1 -1
- package/dist/editor/client/hooks/useQuota.d.ts +8 -0
- package/dist/editor/client/hooks/useQuota.js.map +1 -1
- package/dist/editor/client/hooks/useSocketMessageHandler.js +73 -15
- package/dist/editor/client/hooks/useSocketMessageHandler.js.map +1 -1
- package/dist/editor/client/itemsRepository.js +10 -6
- package/dist/editor/client/itemsRepository.js.map +1 -1
- package/dist/editor/client/operations.d.ts +6 -3
- package/dist/editor/client/operations.js +208 -30
- package/dist/editor/client/operations.js.map +1 -1
- package/dist/editor/client/pageModelBuilder.js +4 -31
- package/dist/editor/client/pageModelBuilder.js.map +1 -1
- package/dist/editor/client/ui/DevModeIndicator.js +2 -2
- package/dist/editor/client/ui/DevModeIndicator.js.map +1 -1
- package/dist/editor/client/ui/EditorChrome.d.ts +0 -6
- package/dist/editor/client/ui/EditorChrome.js +55 -72
- package/dist/editor/client/ui/EditorChrome.js.map +1 -1
- package/dist/editor/client/ui/FullscreenControls.js +5 -3
- package/dist/editor/client/ui/FullscreenControls.js.map +1 -1
- package/dist/editor/commands/commands.d.ts +11 -1
- package/dist/editor/commands/commands.js +12 -1
- package/dist/editor/commands/commands.js.map +1 -1
- package/dist/editor/commands/componentCommands.js +109 -55
- package/dist/editor/commands/componentCommands.js.map +1 -1
- package/dist/editor/commands/customCommandConverter.d.ts +8 -1
- package/dist/editor/commands/customCommandConverter.js +35 -5
- package/dist/editor/commands/customCommandConverter.js.map +1 -1
- package/dist/editor/commands/handlers/agentHandler.js +2 -1
- package/dist/editor/commands/handlers/agentHandler.js.map +1 -1
- package/dist/editor/commands/itemCommands.d.ts +3 -0
- package/dist/editor/commands/itemCommands.js +93 -10
- package/dist/editor/commands/itemCommands.js.map +1 -1
- package/dist/editor/commands/undo.d.ts +9 -15
- package/dist/editor/commands/undo.js +24 -0
- package/dist/editor/commands/undo.js.map +1 -1
- package/dist/editor/context-menu/InsertMenu.js +83 -39
- package/dist/editor/context-menu/InsertMenu.js.map +1 -1
- package/dist/editor/field-types/MultiLineText.js +1 -1
- package/dist/editor/field-types/MultiLineText.js.map +1 -1
- package/dist/editor/field-types/RawEditor.js +1 -1
- package/dist/editor/field-types/RichTextEditor.js +13 -5
- package/dist/editor/field-types/RichTextEditor.js.map +1 -1
- package/dist/editor/field-types/RichTextEditorComponent.js +37 -3
- package/dist/editor/field-types/RichTextEditorComponent.js.map +1 -1
- package/dist/editor/field-types/SingleLineText.js +1 -1
- package/dist/editor/field-types/TreeListEditor.js +3 -2
- package/dist/editor/field-types/TreeListEditor.js.map +1 -1
- package/dist/editor/field-types/richtext/components/ReactSlate.css +23 -5
- package/dist/editor/field-types/richtext/components/ReactSlate.d.ts +2 -0
- package/dist/editor/field-types/richtext/components/ReactSlate.js +28 -4
- package/dist/editor/field-types/richtext/components/ReactSlate.js.map +1 -1
- package/dist/editor/field-types/richtext/components/ToolbarButton.js +4 -2
- package/dist/editor/field-types/richtext/components/ToolbarButton.js.map +1 -1
- package/dist/editor/field-types/richtext/contextMenuFactory.d.ts +13 -0
- package/dist/editor/field-types/richtext/contextMenuFactory.js +181 -24
- package/dist/editor/field-types/richtext/contextMenuFactory.js.map +1 -1
- package/dist/editor/field-types/richtext/types.d.ts +2 -0
- package/dist/editor/field-types/richtext/types.js.map +1 -1
- package/dist/editor/field-types/richtext/utils/plugins.js +4 -0
- package/dist/editor/field-types/richtext/utils/plugins.js.map +1 -1
- package/dist/editor/field-types/textContextMenuFactory.js +3 -2
- package/dist/editor/field-types/textContextMenuFactory.js.map +1 -1
- package/dist/editor/media-selector/AiImageSearchPrompt.js +4 -2
- package/dist/editor/media-selector/AiImageSearchPrompt.js.map +1 -1
- package/dist/editor/media-selector/MediaFolderBrowser.js +1 -1
- package/dist/editor/media-selector/MediaFolderBrowser.js.map +1 -1
- package/dist/editor/media-selector/MediaSelector.js +7 -1
- package/dist/editor/media-selector/MediaSelector.js.map +1 -1
- package/dist/editor/media-selector/TreeSelector.js +40 -35
- package/dist/editor/media-selector/TreeSelector.js.map +1 -1
- package/dist/editor/menubar/ActiveUsers.js +1 -1
- package/dist/editor/menubar/ActiveUsers.js.map +1 -1
- package/dist/editor/menubar/GenericToolbar.js +4 -2
- package/dist/editor/menubar/GenericToolbar.js.map +1 -1
- package/dist/editor/menubar/ItemLanguageVersion.js +2 -2
- package/dist/editor/menubar/ItemLanguageVersion.js.map +1 -1
- package/dist/editor/menubar/PageSelector.js +26 -147
- package/dist/editor/menubar/PageSelector.js.map +1 -1
- package/dist/editor/menubar/Separator.js +1 -1
- package/dist/editor/menubar/VersionSelector.js +2 -4
- package/dist/editor/menubar/VersionSelector.js.map +1 -1
- package/dist/editor/menubar/WorkflowButton.js +39 -12
- package/dist/editor/menubar/WorkflowButton.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/CustomCommandsToolbar.js +16 -38
- package/dist/editor/menubar/toolbar-sections/CustomCommandsToolbar.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/EditControls.js +3 -3
- package/dist/editor/menubar/toolbar-sections/EditControls.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/HelpButton.js +1 -0
- package/dist/editor/menubar/toolbar-sections/HelpButton.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/ManualBrowser.d.ts +6 -10
- package/dist/editor/menubar/toolbar-sections/ManualBrowser.js +597 -220
- package/dist/editor/menubar/toolbar-sections/ManualBrowser.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/UtilityControls.js +13 -2
- package/dist/editor/menubar/toolbar-sections/UtilityControls.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/ViewportControls.js +12 -2
- package/dist/editor/menubar/toolbar-sections/ViewportControls.js.map +1 -1
- package/dist/editor/page-editor-chrome/CommentHighlighting.js +42 -1
- package/dist/editor/page-editor-chrome/CommentHighlighting.js.map +1 -1
- package/dist/editor/page-editor-chrome/FrameMenu.js +1 -1
- package/dist/editor/page-editor-chrome/FrameMenu.js.map +1 -1
- package/dist/editor/page-editor-chrome/InlineEditor.js +97 -48
- package/dist/editor/page-editor-chrome/InlineEditor.js.map +1 -1
- package/dist/editor/page-editor-chrome/PlaceholderDropZone.js +38 -17
- package/dist/editor/page-editor-chrome/PlaceholderDropZone.js.map +1 -1
- package/dist/editor/page-editor-chrome/PlaceholderDropZones.js +17 -11
- package/dist/editor/page-editor-chrome/PlaceholderDropZones.js.map +1 -1
- package/dist/editor/page-editor-chrome/useInlineAICompletion.js +301 -301
- package/dist/editor/page-editor-chrome/useInlineAICompletion.js.map +1 -1
- package/dist/editor/page-viewer/DeviceToolbar.js +1 -1
- package/dist/editor/page-viewer/DeviceToolbar.js.map +1 -1
- package/dist/editor/page-viewer/EditorForm.js +69 -11
- package/dist/editor/page-viewer/EditorForm.js.map +1 -1
- package/dist/editor/page-viewer/EditorFormHintPopover.d.ts +7 -0
- package/dist/editor/page-viewer/EditorFormHintPopover.js +55 -0
- package/dist/editor/page-viewer/EditorFormHintPopover.js.map +1 -0
- package/dist/editor/page-viewer/MiniMap.d.ts +2 -4
- package/dist/editor/page-viewer/MiniMap.js +183 -33
- package/dist/editor/page-viewer/MiniMap.js.map +1 -1
- package/dist/editor/page-viewer/PageViewer.d.ts +3 -1
- package/dist/editor/page-viewer/PageViewer.js +67 -52
- package/dist/editor/page-viewer/PageViewer.js.map +1 -1
- package/dist/editor/page-viewer/PageViewerFrame.d.ts +2 -1
- package/dist/editor/page-viewer/PageViewerFrame.js +348 -115
- package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
- package/dist/editor/page-viewer/pageModelSkeletonBuilder.js +114 -49
- package/dist/editor/page-viewer/pageModelSkeletonBuilder.js.map +1 -1
- package/dist/editor/page-viewer/pageViewContext.d.ts +1 -0
- package/dist/editor/page-viewer/pageViewContext.js +51 -14
- package/dist/editor/page-viewer/pageViewContext.js.map +1 -1
- package/dist/editor/pageModel.d.ts +14 -1
- package/dist/editor/reviews/Comment.d.ts +2 -1
- package/dist/editor/reviews/Comment.js +92 -15
- package/dist/editor/reviews/Comment.js.map +1 -1
- package/dist/editor/reviews/CommentDisplayPopover.js +70 -5
- package/dist/editor/reviews/CommentDisplayPopover.js.map +1 -1
- package/dist/editor/reviews/CommentView.d.ts +3 -1
- package/dist/editor/reviews/CommentView.js +26 -6
- package/dist/editor/reviews/CommentView.js.map +1 -1
- package/dist/editor/reviews/Comments.js +140 -75
- package/dist/editor/reviews/Comments.js.map +1 -1
- package/dist/editor/reviews/CreateReviewDialog.js +281 -177
- package/dist/editor/reviews/CreateReviewDialog.js.map +1 -1
- package/dist/editor/reviews/DecisionsMatrix.js +96 -25
- package/dist/editor/reviews/DecisionsMatrix.js.map +1 -1
- package/dist/editor/reviews/DiffView.js +7 -14
- package/dist/editor/reviews/DiffView.js.map +1 -1
- package/dist/editor/reviews/EditReviewSettingsDialog.js +6 -4
- package/dist/editor/reviews/EditReviewSettingsDialog.js.map +1 -1
- package/dist/editor/reviews/MultiReviewManager.js +25 -3
- package/dist/editor/reviews/MultiReviewManager.js.map +1 -1
- package/dist/editor/reviews/PagesPanel.js +31 -15
- package/dist/editor/reviews/PagesPanel.js.map +1 -1
- package/dist/editor/reviews/PreviewInfo.js +1 -4
- package/dist/editor/reviews/PreviewInfo.js.map +1 -1
- package/dist/editor/reviews/ReviewCard.js +13 -7
- package/dist/editor/reviews/ReviewCard.js.map +1 -1
- package/dist/editor/reviews/ReviewDetail.js +3 -2
- package/dist/editor/reviews/ReviewDetail.js.map +1 -1
- package/dist/editor/reviews/ReviewsList.js +7 -3
- package/dist/editor/reviews/ReviewsList.js.map +1 -1
- package/dist/editor/reviews/SuggestedEdit.js +34 -3
- package/dist/editor/reviews/SuggestedEdit.js.map +1 -1
- package/dist/editor/reviews/SuggestionDisplayPopover.js +31 -5
- package/dist/editor/reviews/SuggestionDisplayPopover.js.map +1 -1
- package/dist/editor/reviews/commentAi.js +25 -6
- package/dist/editor/reviews/commentAi.js.map +1 -1
- package/dist/editor/reviews/reviewCommands.js +4 -1
- package/dist/editor/reviews/reviewCommands.js.map +1 -1
- package/dist/editor/reviews/useMultiReview.js +2 -2
- package/dist/editor/reviews/useMultiReview.js.map +1 -1
- package/dist/editor/reviews/useReviews.d.ts +4 -3
- package/dist/editor/reviews/useReviews.js +21 -32
- package/dist/editor/reviews/useReviews.js.map +1 -1
- package/dist/editor/services/agentService.d.ts +240 -5
- package/dist/editor/services/agentService.js +299 -39
- package/dist/editor/services/agentService.js.map +1 -1
- package/dist/editor/services/aiService.d.ts +57 -1
- package/dist/editor/services/aiService.js +79 -6
- package/dist/editor/services/aiService.js.map +1 -1
- package/dist/editor/services/contentService.d.ts +6 -3
- package/dist/editor/services/contentService.js +13 -12
- package/dist/editor/services/contentService.js.map +1 -1
- package/dist/editor/services/editService.d.ts +52 -1
- package/dist/editor/services/editService.js +121 -13
- package/dist/editor/services/editService.js.map +1 -1
- package/dist/editor/services/indexService.js +1 -1
- package/dist/editor/services/indexService.js.map +1 -1
- package/dist/editor/services/reviewsService.d.ts +3 -6
- package/dist/editor/services/reviewsService.js +2 -11
- package/dist/editor/services/reviewsService.js.map +1 -1
- package/dist/editor/services/serviceHelper.d.ts +2 -1
- package/dist/editor/services/serviceHelper.js +174 -22
- package/dist/editor/services/serviceHelper.js.map +1 -1
- package/dist/editor/services/systemService.d.ts +2 -1
- package/dist/editor/services/systemService.js +3 -0
- package/dist/editor/services/systemService.js.map +1 -1
- package/dist/editor/services-server/api.d.ts +1 -2
- package/dist/editor/services-server/api.js +11 -6
- package/dist/editor/services-server/api.js.map +1 -1
- package/dist/editor/settings/About.js +317 -3
- package/dist/editor/settings/About.js.map +1 -1
- package/dist/editor/settings/IndexOverview.js +3 -1
- package/dist/editor/settings/IndexOverview.js.map +1 -1
- package/dist/editor/settings/QuotaInfo.js +210 -4
- package/dist/editor/settings/QuotaInfo.js.map +1 -1
- package/dist/editor/settings/SettingsView.js +25 -23
- package/dist/editor/settings/SettingsView.js.map +1 -1
- package/dist/editor/settings/Status.js +7 -6
- package/dist/editor/settings/Status.js.map +1 -1
- package/dist/editor/settings/index/useIndexStatus.js +23 -22
- package/dist/editor/settings/index/useIndexStatus.js.map +1 -1
- package/dist/editor/settings/panels/AgentsPanel.d.ts +0 -4
- package/dist/editor/settings/panels/AgentsPanel.js +95 -121
- package/dist/editor/settings/panels/AgentsPanel.js.map +1 -1
- package/dist/editor/settings/panels/ModelsPanel.js +324 -108
- package/dist/editor/settings/panels/ModelsPanel.js.map +1 -1
- package/dist/editor/settings/panels/ProvidersPanel.d.ts +1 -1
- package/dist/editor/settings/panels/ProvidersPanel.js +86 -59
- package/dist/editor/settings/panels/ProvidersPanel.js.map +1 -1
- package/dist/editor/settings/panels/SearchConfigPanel.js +67 -6
- package/dist/editor/settings/panels/SearchConfigPanel.js.map +1 -1
- package/dist/editor/settings/panels/StatusPanel.js +7 -2
- package/dist/editor/settings/panels/StatusPanel.js.map +1 -1
- package/dist/editor/settings/panels/index.d.ts +3 -2
- package/dist/editor/settings/panels/index.js +3 -2
- package/dist/editor/settings/panels/index.js.map +1 -1
- package/dist/editor/settings/status/coreStatusChecks.js +124 -19
- package/dist/editor/settings/status/coreStatusChecks.js.map +1 -1
- package/dist/editor/settings/status/useStartupChecks.d.ts +3 -1
- package/dist/editor/settings/status/useStartupChecks.js +9 -5
- package/dist/editor/settings/status/useStartupChecks.js.map +1 -1
- package/dist/editor/setup-wizard/steps/CompleteStep.d.ts +2 -1
- package/dist/editor/setup-wizard/steps/CompleteStep.js +2 -1
- package/dist/editor/setup-wizard/steps/CompleteStep.js.map +1 -1
- package/dist/editor/sidebar/ComponentPalette.js +2 -1
- package/dist/editor/sidebar/ComponentPalette.js.map +1 -1
- package/dist/editor/sidebar/ComponentTree.d.ts +8 -1
- package/dist/editor/sidebar/ComponentTree.js +216 -69
- package/dist/editor/sidebar/ComponentTree.js.map +1 -1
- package/dist/editor/sidebar/EditHistory.js +22 -46
- package/dist/editor/sidebar/EditHistory.js.map +1 -1
- package/dist/editor/sidebar/Favorites.js +4 -8
- package/dist/editor/sidebar/Favorites.js.map +1 -1
- package/dist/editor/sidebar/MainContentTree.js +4 -3
- package/dist/editor/sidebar/MainContentTree.js.map +1 -1
- package/dist/editor/sidebar/OperationItem.js +21 -7
- package/dist/editor/sidebar/OperationItem.js.map +1 -1
- package/dist/editor/sidebar/SidebarPanel.d.ts +3 -1
- package/dist/editor/sidebar/SidebarPanel.js +44 -12
- package/dist/editor/sidebar/SidebarPanel.js.map +1 -1
- package/dist/editor/sidebar/SidebarStack.d.ts +2 -1
- package/dist/editor/sidebar/SidebarStack.js +4 -3
- package/dist/editor/sidebar/SidebarStack.js.map +1 -1
- package/dist/editor/sidebar/Validation.js +24 -12
- package/dist/editor/sidebar/Validation.js.map +1 -1
- package/dist/editor/sidebar/Workbox.js +53 -3
- package/dist/editor/sidebar/Workbox.js.map +1 -1
- package/dist/editor/sidebar/WorkspaceRail.d.ts +0 -1
- package/dist/editor/sidebar/WorkspaceRail.js +56 -167
- package/dist/editor/sidebar/WorkspaceRail.js.map +1 -1
- package/dist/editor/tree-indicators/GutterColumns.d.ts +3 -1
- package/dist/editor/tree-indicators/GutterColumns.js +26 -5
- package/dist/editor/tree-indicators/GutterColumns.js.map +1 -1
- package/dist/editor/tree-indicators/GutterContext.d.ts +4 -0
- package/dist/editor/tree-indicators/GutterContext.js +23 -0
- package/dist/editor/tree-indicators/GutterContext.js.map +1 -1
- package/dist/editor/tree-indicators/index.d.ts +0 -1
- package/dist/editor/tree-indicators/index.js +0 -1
- package/dist/editor/tree-indicators/index.js.map +1 -1
- package/dist/editor/tree-indicators/types.d.ts +12 -1
- package/dist/editor/ui/CopyMoveTargetSelectorDialog.js +1 -1
- package/dist/editor/ui/CopyMoveTargetSelectorDialog.js.map +1 -1
- package/dist/editor/ui/Icons.js +1 -1
- package/dist/editor/ui/Icons.js.map +1 -1
- package/dist/editor/ui/ItemNameDialogNew.d.ts +2 -0
- package/dist/editor/ui/ItemNameDialogNew.js +33 -17
- package/dist/editor/ui/ItemNameDialogNew.js.map +1 -1
- package/dist/editor/ui/ItemSearch.js +7 -11
- package/dist/editor/ui/ItemSearch.js.map +1 -1
- package/dist/editor/ui/SimpleIconButton.js +1 -1
- package/dist/editor/ui/SimpleIconButton.js.map +1 -1
- package/dist/editor/ui/SimpleTabs.d.ts +1 -0
- package/dist/editor/ui/SimpleTabs.js +45 -25
- package/dist/editor/ui/SimpleTabs.js.map +1 -1
- package/dist/editor/ui/Splitter.d.ts +1 -0
- package/dist/editor/ui/Splitter.js +102 -86
- package/dist/editor/ui/Splitter.js.map +1 -1
- package/dist/editor/ui/TemplateSelectorDialog.js +4 -4
- package/dist/editor/ui/TemplateSelectorDialog.js.map +1 -1
- package/dist/editor/ui/TreeListSelector.d.ts +6 -1
- package/dist/editor/ui/TreeListSelector.js +2 -2
- package/dist/editor/ui/TreeListSelector.js.map +1 -1
- package/dist/editor/utils/keyboardNavigation.d.ts +6 -20
- package/dist/editor/utils/keyboardNavigation.js +48 -140
- package/dist/editor/utils/keyboardNavigation.js.map +1 -1
- package/dist/editor/utils.js +19 -9
- package/dist/editor/utils.js.map +1 -1
- package/dist/editor/views/CompareView.d.ts +3 -1
- package/dist/editor/views/CompareView.js +7 -5
- package/dist/editor/views/CompareView.js.map +1 -1
- package/dist/editor/views/EditView.js +1 -1
- package/dist/editor/views/EditView.js.map +1 -1
- package/dist/editor/views/EditorSlot.js +27 -34
- package/dist/editor/views/EditorSlot.js.map +1 -1
- package/dist/editor/views/ItemEditor.js +7 -3
- package/dist/editor/views/ItemEditor.js.map +1 -1
- package/dist/editor/views/MediaFolderEditView.js +1 -1
- package/dist/editor/views/MediaFolderEditView.js.map +1 -1
- package/dist/editor/views/ParheliaView.js +5 -6
- package/dist/editor/views/ParheliaView.js.map +1 -1
- package/dist/editor/views/SingleEditView.d.ts +2 -1
- package/dist/editor/views/SingleEditView.js +10 -8
- package/dist/editor/views/SingleEditView.js.map +1 -1
- package/dist/editor/views/editorSlotContext.js +35 -6
- package/dist/editor/views/editorSlotContext.js.map +1 -1
- package/dist/index.d.ts +16 -2
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -1
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/dist/setup/services/setupWizardService.d.ts +48 -14
- package/dist/setup/services/setupWizardService.js +52 -17
- package/dist/setup/services/setupWizardService.js.map +1 -1
- package/dist/setup/wizard/steps/AddModelDialog.js +12 -3
- package/dist/setup/wizard/steps/AddModelDialog.js.map +1 -1
- package/dist/setup/wizard/steps/ImportModelDialog.js +46 -22
- package/dist/setup/wizard/steps/ImportModelDialog.js.map +1 -1
- package/dist/splash-screen/ModernSplashScreen.js +112 -32
- package/dist/splash-screen/ModernSplashScreen.js.map +1 -1
- package/dist/splash-screen/NewPage.js +33 -50
- package/dist/splash-screen/NewPage.js.map +1 -1
- package/dist/splash-screen/OpenPage.js +2 -6
- package/dist/splash-screen/OpenPage.js.map +1 -1
- package/dist/splash-screen/ParheliaAssistantChat.js +12 -29
- package/dist/splash-screen/ParheliaAssistantChat.js.map +1 -1
- package/dist/splash-screen/ParheliaLogo.js +87 -37
- package/dist/splash-screen/ParheliaLogo.js.map +1 -1
- package/dist/splash-screen/RecentPages.js +3 -3
- package/dist/splash-screen/RecentPages.js.map +1 -1
- package/dist/task-board/services/taskService.js +10 -3
- package/dist/task-board/services/taskService.js.map +1 -1
- package/dist/tour/Tour.d.ts +2 -1
- package/dist/tour/Tour.js +256 -75
- package/dist/tour/Tour.js.map +1 -1
- package/dist/tour/default-tour.js +222 -96
- package/dist/tour/default-tour.js.map +1 -1
- package/dist/types.d.ts +74 -29
- package/package.json +19 -15
- package/styles.css +39 -10
- package/dist/editor/ComponentInfo.d.ts +0 -4
- package/dist/editor/ComponentInfo.js +0 -41
- package/dist/editor/ComponentInfo.js.map +0 -1
- package/dist/editor/ai/HelpTerminal.d.ts +0 -5
- package/dist/editor/ai/HelpTerminal.js +0 -166
- package/dist/editor/ai/HelpTerminal.js.map +0 -1
- package/dist/editor/field-types/ReactQuill.d.ts +0 -125
- package/dist/editor/field-types/ReactQuill.js +0 -385
- package/dist/editor/field-types/ReactQuill.js.map +0 -1
- package/dist/editor/services-server/graphQL.d.ts +0 -29
- package/dist/editor/services-server/graphQL.js +0 -53
- package/dist/editor/services-server/graphQL.js.map +0 -1
- package/dist/editor/settings/AllAgentsPanel.d.ts +0 -5
- package/dist/editor/settings/AllAgentsPanel.js +0 -139
- package/dist/editor/settings/AllAgentsPanel.js.map +0 -1
- package/dist/editor/settings/LatestFeedback.d.ts +0 -1
- package/dist/editor/settings/LatestFeedback.js +0 -136
- package/dist/editor/settings/LatestFeedback.js.map +0 -1
- package/dist/editor/settings/Setup.d.ts +0 -1
- package/dist/editor/settings/Setup.js +0 -211
- package/dist/editor/settings/Setup.js.map +0 -1
- package/dist/editor/settings/panels/DatabasePanel.d.ts +0 -6
- package/dist/editor/settings/panels/DatabasePanel.js +0 -50
- package/dist/editor/settings/panels/DatabasePanel.js.map +0 -1
- package/dist/editor/settings/setup-steps/AiSetupStep/EmbeddingsModelSection.d.ts +0 -2
- package/dist/editor/settings/setup-steps/AiSetupStep/EmbeddingsModelSection.js +0 -195
- package/dist/editor/settings/setup-steps/AiSetupStep/EmbeddingsModelSection.js.map +0 -1
- package/dist/editor/settings/setup-steps/AiSetupStep/index.d.ts +0 -2
- package/dist/editor/settings/setup-steps/AiSetupStep/index.js +0 -21
- package/dist/editor/settings/setup-steps/AiSetupStep/index.js.map +0 -1
- package/dist/editor/settings/setup-steps/AiSetupStep/provider/ProviderSection.d.ts +0 -1
- package/dist/editor/settings/setup-steps/AiSetupStep/provider/ProviderSection.js +0 -233
- package/dist/editor/settings/setup-steps/AiSetupStep/provider/ProviderSection.js.map +0 -1
- package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersList.d.ts +0 -15
- package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersList.js +0 -14
- package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersList.js.map +0 -1
- package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersSection.d.ts +0 -1
- package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersSection.js +0 -94
- package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersSection.js.map +0 -1
- package/dist/editor/settings/setup-steps/AiSetupStep/types.d.ts +0 -1
- package/dist/editor/settings/setup-steps/AiSetupStep/types.js +0 -2
- package/dist/editor/settings/setup-steps/AiSetupStep/types.js.map +0 -1
- package/dist/editor/settings/setup-steps/AiSetupStep/utils.d.ts +0 -5
- package/dist/editor/settings/setup-steps/AiSetupStep/utils.js +0 -44
- package/dist/editor/settings/setup-steps/AiSetupStep/utils.js.map +0 -1
- package/dist/editor/settings/setup-steps/IndexSetupStep.d.ts +0 -2
- package/dist/editor/settings/setup-steps/IndexSetupStep.js +0 -36
- package/dist/editor/settings/setup-steps/IndexSetupStep.js.map +0 -1
- package/dist/editor/settings/setup-steps/SettingsSetupStep.d.ts +0 -2
- package/dist/editor/settings/setup-steps/SettingsSetupStep.js +0 -111
- package/dist/editor/settings/setup-steps/SettingsSetupStep.js.map +0 -1
- package/dist/editor/settings/setup-steps/SetupOverview.d.ts +0 -14
- package/dist/editor/settings/setup-steps/SetupOverview.js +0 -38
- package/dist/editor/settings/setup-steps/SetupOverview.js.map +0 -1
- package/dist/editor/sidebar/Debug.d.ts +0 -1
- package/dist/editor/sidebar/Debug.js +0 -70
- package/dist/editor/sidebar/Debug.js.map +0 -1
- package/dist/editor/sidebar/GraphQL.d.ts +0 -2
- package/dist/editor/sidebar/GraphQL.js +0 -234
- package/dist/editor/sidebar/GraphQL.js.map +0 -1
- package/dist/editor/sidebar/LeftToolbar.d.ts +0 -1
- package/dist/editor/sidebar/LeftToolbar.js +0 -12
- package/dist/editor/sidebar/LeftToolbar.js.map +0 -1
- package/dist/editor/sidebar/NavigationSidebar.d.ts +0 -4
- package/dist/editor/sidebar/NavigationSidebar.js +0 -254
- package/dist/editor/sidebar/NavigationSidebar.js.map +0 -1
- package/dist/editor/tree-indicators/GutterSelector.d.ts +0 -5
- package/dist/editor/tree-indicators/GutterSelector.js +0 -91
- package/dist/editor/tree-indicators/GutterSelector.js.map +0 -1
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx as _jsx,
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
3
|
import React, { useState, useEffect, useRef, useCallback, useSyncExternalStore, useMemo, startTransition, } from "react";
|
|
4
4
|
import { toast } from "sonner";
|
|
5
|
-
import { EditContextProvider, FieldsEditContextProvider, OperationsContextProvider, } from "./editContext";
|
|
5
|
+
import { EditContextProvider, FieldsEditContextProvider, OperationsContextProvider, useEditContext, } from "./editContext";
|
|
6
6
|
import { fieldModificationStore } from "./fieldModificationStore";
|
|
7
|
-
import { useRouter, useSearchParams, usePathname } from "
|
|
7
|
+
import { useRouter, useSearchParams, usePathname } from "./navigation";
|
|
8
8
|
import { findComponent, getComponentById } from "../componentTreeHelper";
|
|
9
9
|
import { getOperationsContext } from "./operations";
|
|
10
10
|
import { handleErrorResult } from "./helpers";
|
|
11
|
-
import { executeFieldAction as executeFieldServerAction, connectSocket, getEditHistory, getRunningOperations, releaseFieldLocks, validateItems, } from "../services/editService";
|
|
11
|
+
import { executeFieldAction as executeFieldServerAction, connectSocket, getEditHistory, getRunningOperations, reconnectSession, releaseFieldLocks, validateItems, } from "../services/editService";
|
|
12
12
|
import { useEditorWebSocket } from "./hooks/useEditorWebSocket";
|
|
13
|
+
import { createEditorSocketDiagnostics, } from "./socketDiagnostics";
|
|
14
|
+
import { localStorageService } from "../services/localStorageService";
|
|
13
15
|
import { useSocketMessageHandler } from "./hooks/useSocketMessageHandler";
|
|
14
16
|
import "react-json-view-lite/dist/index.css";
|
|
15
17
|
import { MediaSelector, } from "../media-selector/MediaSelector";
|
|
@@ -19,7 +21,9 @@ import ConfirmationDialog from "../ConfirmationDialog";
|
|
|
19
21
|
import { getItemDescriptor } from "../utils";
|
|
20
22
|
import { EditContextMenu } from "../ContextMenu";
|
|
21
23
|
import { InlineAiTrigger } from "../ai/InlineAiTrigger";
|
|
24
|
+
import { EditorFormHintPopover } from "../page-viewer/EditorFormHintPopover";
|
|
22
25
|
import { FieldEditorPopup } from "../FieldEditorPopup";
|
|
26
|
+
import { ConcurrentUserLimitDialog } from "../ConcurrentUserLimitDialog";
|
|
23
27
|
import { post } from "../services/serviceHelper";
|
|
24
28
|
import { PageViewerFrame } from "../page-viewer/PageViewerFrame";
|
|
25
29
|
import { useItemsRepository } from "./itemsRepository";
|
|
@@ -33,6 +37,7 @@ import { GuidanceOverlay } from "../ai/GuidanceOverlay";
|
|
|
33
37
|
import { AgentDialogHandler } from "../ai/dialogs";
|
|
34
38
|
import { usePageViewContext, } from "../page-viewer/pageViewContext";
|
|
35
39
|
import { QuickItemSwitcher } from "../QuickItemSwitcher";
|
|
40
|
+
import { useEditorSlotContext, } from "../views/editorSlotContext";
|
|
36
41
|
import { getComments, getAvailableCommentTags, } from "../services/reviewsService";
|
|
37
42
|
import { useReviews } from "../reviews/useReviews";
|
|
38
43
|
import uuid from "react-uuid";
|
|
@@ -49,7 +54,38 @@ import { useWorkbox } from "./hooks/useWorkbox";
|
|
|
49
54
|
import { useMediaSelector } from "./hooks/useMediaSelector";
|
|
50
55
|
import { useGlobalEditorKeyDown } from "./hooks/useGlobalEditorKeyDown";
|
|
51
56
|
import { useStartupChecks } from "../settings/status/index";
|
|
52
|
-
|
|
57
|
+
import { FeatureGate, LicenseFeatures, LicenseProvider, LicenseOverlay, } from "../../licensing";
|
|
58
|
+
// Sentinel written to the `sidebar` URL param when the user has explicitly closed
|
|
59
|
+
// every sidebar. Distinguishes "no preference yet" (param absent) from "user wants
|
|
60
|
+
// nothing open" (param present with this value) so reload can honor the user's intent.
|
|
61
|
+
const SIDEBAR_NONE_SENTINEL = "none";
|
|
62
|
+
const sidebarUrlValue = (ids) => ids.length ? ids.join(",") : SIDEBAR_NONE_SENTINEL;
|
|
63
|
+
function AgentsSlotContextBridge({ slot }) {
|
|
64
|
+
const editContext = useEditContext();
|
|
65
|
+
const slotContext = useEditorSlotContext({
|
|
66
|
+
slotId: slot.slotId,
|
|
67
|
+
itemDescriptor: slot.itemDescriptor,
|
|
68
|
+
refreshToken: slot.refreshToken,
|
|
69
|
+
});
|
|
70
|
+
if (!editContext || !slotContext)
|
|
71
|
+
return null;
|
|
72
|
+
useEffect(() => {
|
|
73
|
+
editContext.registerSlotContext(slot.slotId, slotContext);
|
|
74
|
+
return () => {
|
|
75
|
+
editContext.unregisterSlotContext(slot.slotId);
|
|
76
|
+
};
|
|
77
|
+
}, [
|
|
78
|
+
slot.slotId,
|
|
79
|
+
slotContext,
|
|
80
|
+
editContext.registerSlotContext,
|
|
81
|
+
editContext.unregisterSlotContext,
|
|
82
|
+
]);
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
function AgentsSlotContextBridgeHost({ slots }) {
|
|
86
|
+
return (_jsx(_Fragment, { children: slots.map((slot) => (_jsx(AgentsSlotContextBridge, { slot: slot }, `agents-slot-bridge-${slot.slotId}`))) }));
|
|
87
|
+
}
|
|
88
|
+
export function EditorShell({ configuration, className, item: loadItemDescriptor, sessionId, userInfo, userPreferences, initialLicenseStatus, initialLicenseStatusLoaded, parheliaSettings, setUserPreferences, children, }) {
|
|
53
89
|
const router = useRouter();
|
|
54
90
|
const pathname = usePathname();
|
|
55
91
|
const searchParams = useSearchParams();
|
|
@@ -97,6 +133,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
97
133
|
const [historyMode, setHistoryMode] = useState("global");
|
|
98
134
|
const [showOnlyMyChanges, setShowOnlyMyChanges] = useState(true);
|
|
99
135
|
const [filterByCurrentLanguage, setFilterByCurrentLanguage] = useState(true);
|
|
136
|
+
const [historySearchQuery, setHistorySearchQuery] = useState("");
|
|
100
137
|
const [recentEdits, setRecentEdits] = useState([]);
|
|
101
138
|
const addRecentEdit = useCallback((edit) => {
|
|
102
139
|
setRecentEdits((prevEditedFields) => {
|
|
@@ -124,14 +161,14 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
124
161
|
if (typeof window !== "undefined")
|
|
125
162
|
sessionStorage?.setItem("sessionId", sessionId);
|
|
126
163
|
// Workspace state
|
|
127
|
-
// Note: "reviews" is a sidebar, not a workspace. If
|
|
164
|
+
// Note: "reviews" is a sidebar, not a workspace. If workspace=reviews, we should
|
|
128
165
|
// set workspace to "editor" and open the reviews sidebar instead.
|
|
129
166
|
// Memoize searchParams reads to avoid triggering Router state updates during render
|
|
130
167
|
// (Next.js App Router uses startTransition internally for URL changes)
|
|
131
|
-
const rawWorkspace = useMemo(() => searchParams.get("workspace")
|
|
168
|
+
const rawWorkspace = useMemo(() => searchParams.get("workspace"), [searchParams]);
|
|
132
169
|
const isReviewsSidebarRequest = rawWorkspace === "reviews";
|
|
133
170
|
const [workspaceId, setWorkspaceId] = useState(
|
|
134
|
-
// If
|
|
171
|
+
// If workspace=reviews, use "editor" workspace (reviews is a sidebar, not a workspace)
|
|
135
172
|
isReviewsSidebarRequest
|
|
136
173
|
? "editor"
|
|
137
174
|
: (rawWorkspace ??
|
|
@@ -147,13 +184,17 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
147
184
|
const [openSidebars, setOpenSidebars] = useState(() => {
|
|
148
185
|
const sidebarParam = searchParams.get("sidebar");
|
|
149
186
|
let sidebars = [];
|
|
150
|
-
|
|
151
|
-
|
|
187
|
+
// Presence check (not truthiness) so the sentinel "none" is respected across reloads.
|
|
188
|
+
if (sidebarParam !== null) {
|
|
189
|
+
sidebars =
|
|
190
|
+
sidebarParam === SIDEBAR_NONE_SENTINEL
|
|
191
|
+
? []
|
|
192
|
+
: sidebarParam.split(",").filter(Boolean);
|
|
152
193
|
}
|
|
153
194
|
else {
|
|
154
195
|
sidebars = [...(configuration.editor.defaultOpenSidebars ?? [])];
|
|
155
196
|
}
|
|
156
|
-
// If
|
|
197
|
+
// If workspace=reviews was requested, ensure reviews sidebar is open
|
|
157
198
|
if (isReviewsSidebarRequest && !sidebars.includes("reviews")) {
|
|
158
199
|
sidebars.push("reviews");
|
|
159
200
|
}
|
|
@@ -161,17 +202,11 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
161
202
|
});
|
|
162
203
|
// Toolbar pinned sidebars - icons always visible in toolbar (defined early so callbacks can access)
|
|
163
204
|
const [pinnedSidebars, setPinnedSidebars] = useState(userInfo.preferences?.pinnedSidebars || []);
|
|
205
|
+
const pinnedSidebarsRef = useRef(pinnedSidebars);
|
|
164
206
|
// Locked sidebars - open panels that stay visible when selecting another sidebar
|
|
165
207
|
const [lockedSidebars, setLockedSidebars] = useState(() => {
|
|
166
|
-
if (typeof window === "undefined") {
|
|
167
|
-
return [];
|
|
168
|
-
}
|
|
169
208
|
try {
|
|
170
|
-
const
|
|
171
|
-
if (!stored) {
|
|
172
|
-
return [];
|
|
173
|
-
}
|
|
174
|
-
const parsed = JSON.parse(stored);
|
|
209
|
+
const parsed = localStorageService.getOrSetItem("editor.lockedSidebars", []);
|
|
175
210
|
if (!Array.isArray(parsed) ||
|
|
176
211
|
!parsed.every((id) => typeof id === "string")) {
|
|
177
212
|
return [];
|
|
@@ -187,6 +222,9 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
187
222
|
useEffect(() => {
|
|
188
223
|
lockedSidebarsRef.current = lockedSidebars;
|
|
189
224
|
}, [lockedSidebars]);
|
|
225
|
+
useEffect(() => {
|
|
226
|
+
pinnedSidebarsRef.current = pinnedSidebars;
|
|
227
|
+
}, [pinnedSidebars]);
|
|
190
228
|
// Filter locked sidebars to only include currently open sidebars
|
|
191
229
|
useEffect(() => {
|
|
192
230
|
const openSet = new Set(openSidebars);
|
|
@@ -248,15 +286,11 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
248
286
|
// Group open sidebars into vertical stacks (each stack shares one left column).
|
|
249
287
|
const [sidebarStacks, setSidebarStacks] = useState(() => {
|
|
250
288
|
const defaultStacks = openSidebars.map((id) => [id]);
|
|
251
|
-
if (typeof window === "undefined") {
|
|
252
|
-
return normalizeSidebarStacks(openSidebars, defaultStacks);
|
|
253
|
-
}
|
|
254
289
|
try {
|
|
255
|
-
const
|
|
256
|
-
if (!
|
|
290
|
+
const parsed = localStorageService.getItem("editor.sidebarStacks");
|
|
291
|
+
if (!parsed) {
|
|
257
292
|
return normalizeSidebarStacks(openSidebars, defaultStacks);
|
|
258
293
|
}
|
|
259
|
-
const parsed = JSON.parse(stored);
|
|
260
294
|
if (!Array.isArray(parsed) ||
|
|
261
295
|
!parsed.every((x) => Array.isArray(x) && x.every((id) => typeof id === "string"))) {
|
|
262
296
|
return normalizeSidebarStacks(openSidebars, defaultStacks);
|
|
@@ -278,28 +312,19 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
278
312
|
}, [openSidebars, normalizeSidebarStacks]);
|
|
279
313
|
// Persist stacks independently of the URL (user layout preference).
|
|
280
314
|
useEffect(() => {
|
|
281
|
-
if (typeof window === "undefined")
|
|
282
|
-
return;
|
|
283
315
|
try {
|
|
284
|
-
|
|
316
|
+
localStorageService.setItem("editor.sidebarStacks", sidebarStacks);
|
|
285
317
|
}
|
|
286
318
|
catch { }
|
|
287
319
|
}, [sidebarStacks]);
|
|
288
320
|
// Persist locked sidebars to localStorage.
|
|
289
321
|
useEffect(() => {
|
|
290
|
-
if (typeof window === "undefined")
|
|
291
|
-
return;
|
|
292
322
|
try {
|
|
293
|
-
|
|
323
|
+
localStorageService.setItem("editor.lockedSidebars", lockedSidebars);
|
|
294
324
|
}
|
|
295
325
|
catch { }
|
|
296
326
|
}, [lockedSidebars]);
|
|
297
|
-
// Legacy aliases for backwards compatibility
|
|
298
327
|
const viewName = workspaceId;
|
|
299
|
-
const setViewName = setWorkspaceId;
|
|
300
|
-
const viewNameRef = workspaceIdRef;
|
|
301
|
-
const previousViewName = previousWorkspaceId;
|
|
302
|
-
const setPreviousViewName = setPreviousWorkspaceId;
|
|
303
328
|
const [compareMode, setCompareModeState] = useState(false);
|
|
304
329
|
const [componentDesignerComponent, setComponentDesignerComponent] = useState();
|
|
305
330
|
const [componentDesignerRendering, setComponentDesignerRendering] = useState();
|
|
@@ -307,6 +332,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
307
332
|
const [ignoreBlur, setIgnoreBlur] = useState(false);
|
|
308
333
|
const [currentItemDescriptor, setCurrentItemDescriptor] = useState();
|
|
309
334
|
const currentItemDescriptorRef = useRef(undefined);
|
|
335
|
+
const latestGlobalLoadKeyRef = useRef(null);
|
|
310
336
|
const [editorSlots, setEditorSlots] = useState(() => {
|
|
311
337
|
// Always start with empty slots - don't persist the number of slots
|
|
312
338
|
return [];
|
|
@@ -326,6 +352,29 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
326
352
|
activeSlotIdRef.current = activeSlotId;
|
|
327
353
|
}, [activeSlotId]);
|
|
328
354
|
const [slotContexts, setSlotContexts] = useState(() => new Map());
|
|
355
|
+
const promptSessionReconnect = useCallback((reason) => {
|
|
356
|
+
const description = reason && reason !== "session-revoked"
|
|
357
|
+
? `${reason}. Click reconnect to continue in this browser.`
|
|
358
|
+
: "You were disconnected because this account is active in another browser.";
|
|
359
|
+
toast.error("Session disconnected", {
|
|
360
|
+
id: "session-revoked",
|
|
361
|
+
description,
|
|
362
|
+
action: {
|
|
363
|
+
label: "Reconnect",
|
|
364
|
+
onClick: async () => {
|
|
365
|
+
const result = await reconnectSession(sessionId);
|
|
366
|
+
if (result.type === "success") {
|
|
367
|
+
window.location.reload();
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
toast.error("Reconnect failed", {
|
|
371
|
+
description: "Could not claim this browser session. Try again.",
|
|
372
|
+
});
|
|
373
|
+
},
|
|
374
|
+
},
|
|
375
|
+
duration: Infinity,
|
|
376
|
+
});
|
|
377
|
+
}, [sessionId]);
|
|
329
378
|
// Track previous item ID to detect actual navigation between pages
|
|
330
379
|
const previousItemIdRef = useRef(undefined);
|
|
331
380
|
useEffect(() => {
|
|
@@ -354,10 +403,18 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
354
403
|
// Ref to track when we're handling a popstate event (browser back/forward)
|
|
355
404
|
// This prevents the URL sync effect from pushing new history entries during back navigation
|
|
356
405
|
const isHandlingPopStateRef = useRef(false);
|
|
357
|
-
//
|
|
358
|
-
//
|
|
359
|
-
|
|
360
|
-
|
|
406
|
+
// When switchWorkspace already pushed a URL entry, the follow-up URL sync
|
|
407
|
+
// effect should only *replace* that entry (not push a second one).
|
|
408
|
+
const switchWorkspacePushedRef = useRef(false);
|
|
409
|
+
// Ref to track the last known URL for the popstate handler.
|
|
410
|
+
// Uses pathname+search (not full href) so comparisons are consistent with the
|
|
411
|
+
// relative URLs produced by the URL sync effect and updateUrl.
|
|
412
|
+
const lastUrlRef = useRef(typeof window !== "undefined"
|
|
413
|
+
? `${window.location.pathname}${window.location.search}`
|
|
414
|
+
: "");
|
|
415
|
+
// The very first URL sync after initial load should replaceState (not pushState)
|
|
416
|
+
// so the initial bare URL isn't left as a dead-end history entry.
|
|
417
|
+
const isFirstUrlSyncRef = useRef(true);
|
|
361
418
|
const [inlineEditingFieldElement, setInlineEditingFieldElement] = useState();
|
|
362
419
|
const [lockedField, setLockedField] = useState();
|
|
363
420
|
const [itemLanguages, setItemLanguages] = useState([]);
|
|
@@ -370,26 +427,16 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
370
427
|
const [showSuggestedEditsDiff, setShowSuggestedEditsDiff] = useState(false);
|
|
371
428
|
const [availableCommentTags, setAvailableCommentTags] = useState([]);
|
|
372
429
|
const [showComments, setShowComments] = useState(() => {
|
|
373
|
-
|
|
374
|
-
? localStorage.getItem("editor.showComments")
|
|
375
|
-
: null;
|
|
376
|
-
return savedShowComments ? JSON.parse(savedShowComments) : true;
|
|
430
|
+
return localStorageService.getOrSetItem("editor.showComments", true);
|
|
377
431
|
});
|
|
378
432
|
useEffect(() => {
|
|
379
|
-
|
|
380
|
-
localStorage.setItem("editor.showComments", JSON.stringify(showComments));
|
|
381
|
-
}
|
|
433
|
+
localStorageService.setItem("editor.showComments", showComments);
|
|
382
434
|
}, [showComments]);
|
|
383
435
|
const [showResolvedComments, setShowResolvedComments] = useState(() => {
|
|
384
|
-
|
|
385
|
-
? localStorage.getItem("editor.showResolvedComments")
|
|
386
|
-
: null;
|
|
387
|
-
return saved ? JSON.parse(saved) : false;
|
|
436
|
+
return localStorageService.getOrSetItem("editor.showResolvedComments", false);
|
|
388
437
|
});
|
|
389
438
|
useEffect(() => {
|
|
390
|
-
|
|
391
|
-
localStorage.setItem("editor.showResolvedComments", JSON.stringify(showResolvedComments));
|
|
392
|
-
}
|
|
439
|
+
localStorageService.setItem("editor.showResolvedComments", showResolvedComments);
|
|
393
440
|
}, [showResolvedComments]);
|
|
394
441
|
const [selectedComment, setSelectedComment] = useState();
|
|
395
442
|
const [browseHistory, setBrowseHistory] = useState(() => {
|
|
@@ -408,13 +455,12 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
408
455
|
visitedAt: entry.visitedAt,
|
|
409
456
|
}));
|
|
410
457
|
});
|
|
411
|
-
// Navigation history for browser history (
|
|
458
|
+
// Navigation history for browser history (workspace + item combinations)
|
|
412
459
|
const [navigationHistory, setNavigationHistory] = useState(() => {
|
|
413
|
-
// Initialize from browse history with current
|
|
460
|
+
// Initialize from browse history with the current workspace
|
|
414
461
|
if (!userInfo.browseHistory)
|
|
415
462
|
return [];
|
|
416
463
|
const defaultWorkspaceId = searchParams.get("workspace") ??
|
|
417
|
-
searchParams.get("view") ??
|
|
418
464
|
configuration.editor.defaultWorkspace ??
|
|
419
465
|
configuration.editor.workspaces?.[0]?.id ??
|
|
420
466
|
"editor";
|
|
@@ -446,20 +492,47 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
446
492
|
const [statusMessage, setStatusMessage] = useState("");
|
|
447
493
|
const [focusFieldComponentId, setFocusFieldComponentId] = useState();
|
|
448
494
|
const [enableCompletions, setEnableCompletions] = useState(false);
|
|
449
|
-
const [
|
|
495
|
+
const [showComponentNavigatorDefault, setShowComponentNavigatorDefault] = useState(userPreferences.showComponentNavigator ?? false);
|
|
496
|
+
const [slotComponentNavigatorVisibility, setSlotComponentNavigatorVisibility] = useState({});
|
|
497
|
+
useEffect(() => {
|
|
498
|
+
setSlotComponentNavigatorVisibility((prev) => {
|
|
499
|
+
const next = {};
|
|
500
|
+
let changed = false;
|
|
501
|
+
for (const slot of editorSlots) {
|
|
502
|
+
if (Object.prototype.hasOwnProperty.call(prev, slot.slotId)) {
|
|
503
|
+
next[slot.slotId] = prev[slot.slotId];
|
|
504
|
+
}
|
|
505
|
+
else {
|
|
506
|
+
next[slot.slotId] = showComponentNavigatorDefault;
|
|
507
|
+
changed = true;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
if (Object.keys(prev).length !== editorSlots.length) {
|
|
511
|
+
changed = true;
|
|
512
|
+
}
|
|
513
|
+
return changed ? next : prev;
|
|
514
|
+
});
|
|
515
|
+
}, [editorSlots, showComponentNavigatorDefault]);
|
|
450
516
|
const [showAgentsPanel, setShowAgentsPanel] = useState(userPreferences.showAgentsPanel ?? false);
|
|
517
|
+
const [editorFormHidden, setEditorFormHiddenState] = useState(userPreferences.editorFormHidden ?? false);
|
|
518
|
+
const [editorFormHintSeen, setEditorFormHintSeenState] = useState(userPreferences.editorFormHintSeen ?? false);
|
|
519
|
+
const [editorFormHintVisible, setEditorFormHintVisible] = useState(false);
|
|
520
|
+
const editorFormToggleButtonRef = useRef(null);
|
|
451
521
|
const [showMinimap, setShowMinimap] = useState(userPreferences.showMinimap ?? true);
|
|
452
522
|
const [showHelpTerminal, setShowHelpTerminal] = useState(false);
|
|
453
523
|
const [helpTerminalInitialPrompt, setHelpTerminalInitialPrompt] = useState(undefined);
|
|
454
524
|
const [helpTerminalProfileName, setHelpTerminalProfileName] = useState(undefined);
|
|
455
525
|
const [helpTerminalActiveTab, setHelpTerminalActiveTab] = useState(undefined);
|
|
456
|
-
const [
|
|
526
|
+
const [selectedHelpSectionId, setSelectedHelpSectionId] = useState(null);
|
|
527
|
+
const [showAgentsWorkspaceEditor, setShowAgentsWorkspaceEditor] = useState(false);
|
|
528
|
+
const [selectedAgentsWorkspaceAgentId, setSelectedAgentsWorkspaceAgentId] = useState(null);
|
|
457
529
|
const [activeEditorTab, setActiveEditorTab] = useState(null);
|
|
458
530
|
const [showLayoutComponents, setShowLayoutComponents] = useState(userPreferences.showLayoutComponents ?? false);
|
|
459
531
|
const { quotaInfo, setQuotaInfo, isQuotaExceeded, getQuotaWarningMessage } = useQuota({
|
|
460
532
|
showError: ({ summary, details }) => showErrorToast({ summary, details }),
|
|
461
533
|
});
|
|
462
534
|
const [webSocketMessages, setWebSocketMessages] = useState([]);
|
|
535
|
+
const [socketDiagnostics, setSocketDiagnostics] = useState(() => createEditorSocketDiagnostics(sessionId));
|
|
463
536
|
const [favorites, setFavorites] = useState([]);
|
|
464
537
|
// Quick item switcher state
|
|
465
538
|
const [quickSwitcherVisible, setQuickSwitcherVisible] = useState(false);
|
|
@@ -477,13 +550,12 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
477
550
|
if (!startupChecks.hasBlockingIssues)
|
|
478
551
|
return;
|
|
479
552
|
// Don't redirect if already in settings workspace - let user navigate freely within settings
|
|
480
|
-
const currentWorkspace = searchParams.get("workspace")
|
|
553
|
+
const currentWorkspace = searchParams.get("workspace");
|
|
481
554
|
if (currentWorkspace === "settings")
|
|
482
555
|
return;
|
|
483
556
|
// Redirect to the status panel (where user can see all issues and navigate to fixes)
|
|
484
557
|
const url = new URL(window.location.href);
|
|
485
558
|
url.searchParams.set("workspace", "settings");
|
|
486
|
-
url.searchParams.delete("view"); // Remove legacy param
|
|
487
559
|
url.searchParams.set("ccpanel", "status");
|
|
488
560
|
router.push(url.toString(), { scroll: false });
|
|
489
561
|
}, [
|
|
@@ -501,6 +573,16 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
501
573
|
setMode(queryMode);
|
|
502
574
|
}
|
|
503
575
|
}, [searchParams, isInitialLoad]);
|
|
576
|
+
useEffect(() => {
|
|
577
|
+
if (!isInitialLoad)
|
|
578
|
+
return;
|
|
579
|
+
const helpParam = searchParams.get("help");
|
|
580
|
+
if (helpParam) {
|
|
581
|
+
setHelpTerminalActiveTab("manual");
|
|
582
|
+
setShowHelpTerminal(true);
|
|
583
|
+
setSelectedHelpSectionId(helpParam === "contents" || helpParam === "true" ? null : helpParam);
|
|
584
|
+
}
|
|
585
|
+
}, [searchParams, isInitialLoad]);
|
|
504
586
|
useEffect(() => {
|
|
505
587
|
if (mode === "suggestions") {
|
|
506
588
|
// Ensure we're in the editor workspace and open the feedback sidebar
|
|
@@ -547,12 +629,19 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
547
629
|
setSlotContexts((prev) => {
|
|
548
630
|
if (!prev.has(slotId))
|
|
549
631
|
return prev;
|
|
632
|
+
if (slotId === activeSlotIdRef.current) {
|
|
633
|
+
const activeCtx = prev.get(slotId);
|
|
634
|
+
if (activeCtx) {
|
|
635
|
+
lastActiveSlotContextRef.current = activeCtx;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
550
638
|
const next = new Map(prev);
|
|
551
639
|
next.delete(slotId);
|
|
552
640
|
return next;
|
|
553
641
|
});
|
|
554
642
|
}, []);
|
|
555
643
|
const slotContextsRef = useRef(slotContexts);
|
|
644
|
+
const lastActiveSlotContextRef = useRef(null);
|
|
556
645
|
useEffect(() => {
|
|
557
646
|
slotContextsRef.current = slotContexts;
|
|
558
647
|
}, [slotContexts]);
|
|
@@ -578,6 +667,12 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
578
667
|
const activeSlotContext = activeSlotId
|
|
579
668
|
? slotContexts.get(activeSlotId)
|
|
580
669
|
: undefined;
|
|
670
|
+
const isComponentNavigatorOpenForSlot = useCallback((slotId) => {
|
|
671
|
+
if (!slotId)
|
|
672
|
+
return showComponentNavigatorDefault;
|
|
673
|
+
return (slotComponentNavigatorVisibility[slotId] ?? showComponentNavigatorDefault);
|
|
674
|
+
}, [slotComponentNavigatorVisibility, showComponentNavigatorDefault]);
|
|
675
|
+
const showComponentNavigator = isComponentNavigatorOpenForSlot(activeSlotId);
|
|
581
676
|
// Sync global compareMode from the active slot's compareMode for URL syncing
|
|
582
677
|
useEffect(() => {
|
|
583
678
|
if (activeSlotContext && activeSlotContext.compareMode !== compareMode) {
|
|
@@ -625,6 +720,9 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
625
720
|
const isItemUsedInCurrentPage = useCallback((item) => pageItemsSetRef.current.has(makeItemKey(item)), [makeItemKey]);
|
|
626
721
|
const socketMessageListeners = useRef(new Set());
|
|
627
722
|
const [socketConnectionVersion, setSocketConnectionVersion] = useState(0);
|
|
723
|
+
useEffect(() => {
|
|
724
|
+
setSocketDiagnostics(createEditorSocketDiagnostics(sessionId));
|
|
725
|
+
}, [sessionId]);
|
|
628
726
|
const addSocketMessageListener = useCallback((callback) => {
|
|
629
727
|
socketMessageListeners.current.add(callback);
|
|
630
728
|
return () => socketMessageListeners.current.delete(callback);
|
|
@@ -670,7 +768,11 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
670
768
|
catch { }
|
|
671
769
|
};
|
|
672
770
|
}, [addSocketMessageListener]);
|
|
771
|
+
const shouldLoadReviews = openSidebars.includes("reviews") ||
|
|
772
|
+
workspaceId === "reviews" ||
|
|
773
|
+
workspaceId === "comments";
|
|
673
774
|
const reviews = useReviews({
|
|
775
|
+
enabled: shouldLoadReviews,
|
|
674
776
|
currentItemDescriptor,
|
|
675
777
|
addSocketMessageListener: addSocketMessageListener,
|
|
676
778
|
});
|
|
@@ -709,8 +811,13 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
709
811
|
console.error(`No workspace found for id: ${workspaceId}`);
|
|
710
812
|
return null;
|
|
711
813
|
}
|
|
712
|
-
|
|
713
|
-
|
|
814
|
+
const activeKeyboardCommands = useMemo(() => {
|
|
815
|
+
const activeCommandIds = new Set(currentWorkspace.keyboardCommandIds ?? []);
|
|
816
|
+
return (configuration.commands.keyboardCommands ?? []).filter((command) => activeCommandIds.has(command.id));
|
|
817
|
+
}, [
|
|
818
|
+
currentWorkspace.keyboardCommandIds,
|
|
819
|
+
configuration.commands.keyboardCommands,
|
|
820
|
+
]);
|
|
714
821
|
useEffect(() => {
|
|
715
822
|
if (currentWorkspace?.component) {
|
|
716
823
|
setCenterPanelView(currentWorkspace.component);
|
|
@@ -738,6 +845,45 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
738
845
|
const sendClientInfo = useCallback(() => {
|
|
739
846
|
debouncedSendClientInfo();
|
|
740
847
|
}, [debouncedSendClientInfo]);
|
|
848
|
+
const getCurrentHistoryState = useCallback(() => {
|
|
849
|
+
if (typeof window === "undefined") {
|
|
850
|
+
return null;
|
|
851
|
+
}
|
|
852
|
+
return window.history.state;
|
|
853
|
+
}, []);
|
|
854
|
+
useEffect(() => {
|
|
855
|
+
if (isInitialLoad) {
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
const itemid = searchParams.get("itemid");
|
|
859
|
+
const language = searchParams.get("lang") ?? searchParams.get("language");
|
|
860
|
+
const versionParam = searchParams.get("version");
|
|
861
|
+
const version = versionParam ? parseInt(versionParam, 10) : 0;
|
|
862
|
+
const itemId = cleanId(itemid ?? undefined);
|
|
863
|
+
if (!itemId || !language) {
|
|
864
|
+
return;
|
|
865
|
+
}
|
|
866
|
+
const currentDescriptor = currentItemDescriptorRef.current;
|
|
867
|
+
const matchesCurrentDescriptor = currentDescriptor?.id === itemId &&
|
|
868
|
+
currentDescriptor?.language === language &&
|
|
869
|
+
(!version || currentDescriptor?.version === version);
|
|
870
|
+
if (matchesCurrentDescriptor) {
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
isHandlingPopStateRef.current = true;
|
|
874
|
+
loadItemRef
|
|
875
|
+
.current({
|
|
876
|
+
id: itemId,
|
|
877
|
+
language,
|
|
878
|
+
version,
|
|
879
|
+
}, {
|
|
880
|
+
addToBrowseHistory: false,
|
|
881
|
+
skipViewChange: true,
|
|
882
|
+
})
|
|
883
|
+
.finally(() => {
|
|
884
|
+
isHandlingPopStateRef.current = false;
|
|
885
|
+
});
|
|
886
|
+
}, [isInitialLoad, searchParams]);
|
|
741
887
|
const startTour = useCallback(() => {
|
|
742
888
|
setIsTourActive(true);
|
|
743
889
|
// Persist tour state to URL so it survives navigation/remounts
|
|
@@ -745,32 +891,61 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
745
891
|
const params = new URLSearchParams(window.location.search);
|
|
746
892
|
params.set("tour", "active");
|
|
747
893
|
const newUrl = `${window.location.pathname}?${params.toString()}`;
|
|
748
|
-
window.history.replaceState(
|
|
894
|
+
window.history.replaceState(getCurrentHistoryState(), "", newUrl);
|
|
749
895
|
}, [setIsTourActive]);
|
|
750
|
-
const isMobile = useMediaQuery("(max-width:
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
896
|
+
const isMobile = useMediaQuery("(max-width: 767px)");
|
|
897
|
+
// On mobile, clear all locked sidebars and keep only the last-opened panel.
|
|
898
|
+
// Handles viewport resize: desktop -> mobile unlocks everything and trims to one panel.
|
|
899
|
+
useEffect(() => {
|
|
900
|
+
if (isMobile) {
|
|
901
|
+
setLockedSidebars([]);
|
|
902
|
+
const current = openSidebarsRef.current;
|
|
903
|
+
const lastSidebar = current[current.length - 1];
|
|
904
|
+
if (current.length > 1 && lastSidebar) {
|
|
905
|
+
const trimmed = [lastSidebar];
|
|
906
|
+
openSidebarsRef.current = trimmed;
|
|
907
|
+
setOpenSidebars(trimmed);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
}, [isMobile]);
|
|
911
|
+
const setComponentNavigatorOpenForSlot = useCallback((slotId, value) => {
|
|
912
|
+
const previousValue = isComponentNavigatorOpenForSlot(slotId);
|
|
913
|
+
const newValue = typeof value === "function" ? value(previousValue) : value;
|
|
914
|
+
if (slotId) {
|
|
915
|
+
setSlotComponentNavigatorVisibility((prev) => {
|
|
916
|
+
const currentValue = prev[slotId] ?? showComponentNavigatorDefault;
|
|
917
|
+
if (currentValue === newValue && prev[slotId] !== undefined) {
|
|
918
|
+
return prev;
|
|
919
|
+
}
|
|
920
|
+
return {
|
|
921
|
+
...prev,
|
|
922
|
+
[slotId]: newValue,
|
|
923
|
+
};
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
setShowComponentNavigatorDefault(newValue);
|
|
754
927
|
setUserPreferences({ showComponentNavigator: newValue });
|
|
755
|
-
// On mobile, close Agents Panel when opening Component Navigator
|
|
756
928
|
if (isMobile && newValue) {
|
|
757
929
|
setShowAgentsPanel(false);
|
|
758
930
|
setUserPreferences({ showAgentsPanel: false });
|
|
759
931
|
}
|
|
760
932
|
}, [
|
|
761
|
-
|
|
762
|
-
|
|
933
|
+
isComponentNavigatorOpenForSlot,
|
|
934
|
+
showComponentNavigatorDefault,
|
|
763
935
|
setUserPreferences,
|
|
764
936
|
isMobile,
|
|
765
937
|
setShowAgentsPanel,
|
|
766
938
|
]);
|
|
939
|
+
const handleSetShowComponentNavigator = useCallback((value) => {
|
|
940
|
+
setComponentNavigatorOpenForSlot(activeSlotIdRef.current, value);
|
|
941
|
+
}, [setComponentNavigatorOpenForSlot]);
|
|
767
942
|
const handleSetShowAgentsPanel = useCallback((value) => {
|
|
768
943
|
const newValue = typeof value === "function" ? value(showAgentsPanel) : value;
|
|
769
944
|
setShowAgentsPanel(newValue);
|
|
770
945
|
setUserPreferences({ showAgentsPanel: newValue });
|
|
771
946
|
// On mobile, close Component Navigator when opening Agents Panel
|
|
772
947
|
if (isMobile && newValue) {
|
|
773
|
-
|
|
948
|
+
setComponentNavigatorOpenForSlot(activeSlotIdRef.current, false);
|
|
774
949
|
setUserPreferences({ showComponentNavigator: false });
|
|
775
950
|
}
|
|
776
951
|
}, [
|
|
@@ -778,13 +953,65 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
778
953
|
setShowAgentsPanel,
|
|
779
954
|
setUserPreferences,
|
|
780
955
|
isMobile,
|
|
781
|
-
|
|
956
|
+
setComponentNavigatorOpenForSlot,
|
|
782
957
|
]);
|
|
958
|
+
// Mobile editor panel state (EditorForm shown in bottom panel on mobile)
|
|
959
|
+
const [mobileEditorPanelOpen, setMobileEditorPanelOpenRaw] = useState(false);
|
|
960
|
+
const [dismissedMobilePanelToken, setDismissedMobilePanelToken] = useState(null);
|
|
961
|
+
const previousActiveSlotIdRef = useRef(null);
|
|
962
|
+
const mobilePanelDismissToken = useMemo(() => {
|
|
963
|
+
const selectionKey = selection.join(",");
|
|
964
|
+
return [
|
|
965
|
+
activeSlotId || "no-slot",
|
|
966
|
+
selectionKey,
|
|
967
|
+
insertMode ? "insert" : "browse",
|
|
968
|
+
].join("|");
|
|
969
|
+
}, [activeSlotId, insertMode, selection]);
|
|
970
|
+
useEffect(() => {
|
|
971
|
+
const previousActiveSlotId = previousActiveSlotIdRef.current;
|
|
972
|
+
if (previousActiveSlotId !== activeSlotId) {
|
|
973
|
+
setDismissedMobilePanelToken(null);
|
|
974
|
+
}
|
|
975
|
+
previousActiveSlotIdRef.current = activeSlotId;
|
|
976
|
+
}, [activeSlotId]);
|
|
977
|
+
const handleSetMobileEditorPanelOpen = useCallback((open) => {
|
|
978
|
+
setMobileEditorPanelOpenRaw(open);
|
|
979
|
+
if (!open) {
|
|
980
|
+
setDismissedMobilePanelToken(mobilePanelDismissToken);
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
setDismissedMobilePanelToken(null);
|
|
984
|
+
if (open && isMobile) {
|
|
985
|
+
// Close all sidebars when opening the editor panel on mobile
|
|
986
|
+
openSidebarsRef.current = [];
|
|
987
|
+
setOpenSidebars([]);
|
|
988
|
+
}
|
|
989
|
+
}, [isMobile, mobilePanelDismissToken]);
|
|
783
990
|
const handleSetShowMinimap = useCallback((value) => {
|
|
784
991
|
const newValue = typeof value === "function" ? value(showMinimap) : value;
|
|
785
992
|
setShowMinimap(newValue);
|
|
786
993
|
setUserPreferences({ showMinimap: newValue });
|
|
787
994
|
}, [showMinimap, setUserPreferences]);
|
|
995
|
+
const handleSetEditorFormHidden = useCallback((value) => {
|
|
996
|
+
setEditorFormHiddenState(value);
|
|
997
|
+
setUserPreferences({ editorFormHidden: value });
|
|
998
|
+
}, [setUserPreferences]);
|
|
999
|
+
const markEditorFormHintSeen = useCallback(() => {
|
|
1000
|
+
setEditorFormHintSeenState(true);
|
|
1001
|
+
setUserPreferences({ editorFormHintSeen: true });
|
|
1002
|
+
}, [setUserPreferences]);
|
|
1003
|
+
const showEditorFormHint = useCallback(() => {
|
|
1004
|
+
setEditorFormHintVisible(true);
|
|
1005
|
+
}, []);
|
|
1006
|
+
const dismissEditorFormHint = useCallback(() => {
|
|
1007
|
+
setEditorFormHintVisible(false);
|
|
1008
|
+
setEditorFormHintSeenState((prev) => {
|
|
1009
|
+
if (prev)
|
|
1010
|
+
return prev;
|
|
1011
|
+
setUserPreferences({ editorFormHintSeen: true });
|
|
1012
|
+
return true;
|
|
1013
|
+
});
|
|
1014
|
+
}, [setUserPreferences]);
|
|
788
1015
|
const handleSetShowHelpTerminal = useCallback((value) => {
|
|
789
1016
|
const newValue = typeof value === "function" ? value(showHelpTerminal) : value;
|
|
790
1017
|
setShowHelpTerminal(newValue);
|
|
@@ -793,6 +1020,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
793
1020
|
setHelpTerminalInitialPrompt(undefined);
|
|
794
1021
|
setHelpTerminalProfileName(undefined);
|
|
795
1022
|
setHelpTerminalActiveTab(undefined);
|
|
1023
|
+
setSelectedHelpSectionId(null);
|
|
796
1024
|
}
|
|
797
1025
|
}, [showHelpTerminal]);
|
|
798
1026
|
const toggleHelpTerminal = useCallback((options) => {
|
|
@@ -835,13 +1063,27 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
835
1063
|
setOpenSidebars(newOrder);
|
|
836
1064
|
setSidebarStacks((prev) => normalizeSidebarStacks(newOrder, prev));
|
|
837
1065
|
}, []);
|
|
1066
|
+
const ensureSidebarPinned = useCallback((sidebarId) => {
|
|
1067
|
+
const currentPinnedSidebars = pinnedSidebarsRef.current;
|
|
1068
|
+
if (currentPinnedSidebars.includes(sidebarId)) {
|
|
1069
|
+
return;
|
|
1070
|
+
}
|
|
1071
|
+
const newPinnedSidebars = [...currentPinnedSidebars, sidebarId];
|
|
1072
|
+
pinnedSidebarsRef.current = newPinnedSidebars;
|
|
1073
|
+
setPinnedSidebars(newPinnedSidebars);
|
|
1074
|
+
setUserPreferences({ pinnedSidebars: newPinnedSidebars });
|
|
1075
|
+
}, [setUserPreferences]);
|
|
838
1076
|
// messageHandler is defined after loadItem/loadHistory declarations to avoid temporal dead zones
|
|
839
1077
|
const user = activeSessions.find((x) => x.sessionId === sessionId)?.user ||
|
|
840
1078
|
userInfo.user;
|
|
841
|
-
// Self-heal if our session disappears (e.g., after HMR)
|
|
1079
|
+
// Self-heal if our session disappears (e.g., after HMR). Skip when concurrent user limit
|
|
1080
|
+
// is showing so we don't spam recovery attempts when the connection was rejected.
|
|
842
1081
|
const missingSessionRecoveryTimerRef = useRef(null);
|
|
843
1082
|
const missingSessionRecoveryAttemptsRef = useRef(0);
|
|
1083
|
+
const concurrentUserLimitErrorRef = useRef(null);
|
|
844
1084
|
useEffect(() => {
|
|
1085
|
+
if (concurrentUserLimitErrorRef.current !== null)
|
|
1086
|
+
return;
|
|
845
1087
|
const hasMySession = activeSessions.some((s) => s.sessionId === sessionId);
|
|
846
1088
|
if (hasMySession) {
|
|
847
1089
|
// Reset recovery state when we see ourselves again
|
|
@@ -857,7 +1099,6 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
857
1099
|
return;
|
|
858
1100
|
const attempt = missingSessionRecoveryAttemptsRef.current + 1;
|
|
859
1101
|
const delay = Math.min(250 * Math.pow(2, attempt - 1), 2000);
|
|
860
|
-
console.warn(`⚠️ Current session not present in active sessions. Recovery attempt ${attempt} in ${delay}ms...`);
|
|
861
1102
|
missingSessionRecoveryTimerRef.current = setTimeout(() => {
|
|
862
1103
|
missingSessionRecoveryTimerRef.current = null;
|
|
863
1104
|
missingSessionRecoveryAttemptsRef.current = attempt;
|
|
@@ -866,6 +1107,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
866
1107
|
}
|
|
867
1108
|
else {
|
|
868
1109
|
// Force a reconnect to refresh presence after several failed nudges
|
|
1110
|
+
console.warn("Session presence did not recover after retries. Forcing reconnect.");
|
|
869
1111
|
try {
|
|
870
1112
|
globalThis.editorSocket?.close(4000, "recover-presence");
|
|
871
1113
|
}
|
|
@@ -885,22 +1127,56 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
885
1127
|
// Initialize lastUrlRef to current URL on mount
|
|
886
1128
|
lastUrlRef.current = window.location.href;
|
|
887
1129
|
const keepAliveUrl = "/parhelia/keepalive";
|
|
888
|
-
const
|
|
889
|
-
fetch(keepAliveUrl + "?ts=" + Date.now()
|
|
1130
|
+
const runSessionCheck = () => {
|
|
1131
|
+
fetch(keepAliveUrl + "?ts=" + Date.now(), {
|
|
1132
|
+
credentials: "include",
|
|
1133
|
+
headers: { "Cache-Control": "no-cache" },
|
|
1134
|
+
})
|
|
890
1135
|
.then((response) => {
|
|
891
|
-
if (response.
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
1136
|
+
if (response.headers.get("X-Parhelia-Session-Revoked") === "true") {
|
|
1137
|
+
window.dispatchEvent(new CustomEvent("parhelia:session-revoked", {
|
|
1138
|
+
detail: { reason: "session-revoked" },
|
|
1139
|
+
}));
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
// A redirected response (e.g. to /sitecore/login) or an explicit
|
|
1143
|
+
// 401/403 both mean the user is no longer authenticated. Let a
|
|
1144
|
+
// single event-driven toast handle the UI.
|
|
1145
|
+
const finalUrl = response.url || "";
|
|
1146
|
+
const redirectedToLogin = response.redirected &&
|
|
1147
|
+
(/\/sitecore\/login/i.test(finalUrl) ||
|
|
1148
|
+
/[?&]returnUrl=/i.test(finalUrl));
|
|
1149
|
+
if (redirectedToLogin ||
|
|
1150
|
+
response.status === 401 ||
|
|
1151
|
+
response.status === 403) {
|
|
1152
|
+
window.dispatchEvent(new CustomEvent("parhelia:session-expired", {
|
|
1153
|
+
detail: {
|
|
1154
|
+
reason: redirectedToLogin
|
|
1155
|
+
? "login-redirect"
|
|
1156
|
+
: `status-${response.status}`,
|
|
897
1157
|
},
|
|
898
|
-
|
|
899
|
-
});
|
|
1158
|
+
}));
|
|
900
1159
|
}
|
|
901
1160
|
})
|
|
902
1161
|
.catch((error) => console.error("Keep Alive error:", error));
|
|
903
|
-
}
|
|
1162
|
+
};
|
|
1163
|
+
const keepaliveIntervalMs = (() => {
|
|
1164
|
+
const param = new URLSearchParams(window.location.search).get("keepaliveIntervalMs");
|
|
1165
|
+
if (!param)
|
|
1166
|
+
return 5 * 60 * 1000;
|
|
1167
|
+
const ms = parseInt(param, 10);
|
|
1168
|
+
return Number.isFinite(ms) && ms >= 2000 && ms <= 60000
|
|
1169
|
+
? ms
|
|
1170
|
+
: 5 * 60 * 1000;
|
|
1171
|
+
})();
|
|
1172
|
+
const interval = setInterval(() => {
|
|
1173
|
+
runSessionCheck();
|
|
1174
|
+
}, keepaliveIntervalMs);
|
|
1175
|
+
const handleVisibilityChange = () => {
|
|
1176
|
+
if (document.visibilityState === "visible") {
|
|
1177
|
+
runSessionCheck();
|
|
1178
|
+
}
|
|
1179
|
+
};
|
|
904
1180
|
const handleMessage = (event) => {
|
|
905
1181
|
if (event.data.type === "componentsSelected") {
|
|
906
1182
|
setSelection(event.data.componentIds);
|
|
@@ -908,35 +1184,44 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
908
1184
|
};
|
|
909
1185
|
// Listen for browser navigation events (back/forward buttons)
|
|
910
1186
|
const handlePopState = (event) => {
|
|
911
|
-
const newUrl = window.location.
|
|
1187
|
+
const newUrl = `${window.location.pathname}${window.location.search}`;
|
|
912
1188
|
if (newUrl !== lastUrlRef.current) {
|
|
913
1189
|
lastUrlRef.current = newUrl;
|
|
914
1190
|
// Mark that we're handling a popstate to prevent URL sync from pushing to history
|
|
915
1191
|
isHandlingPopStateRef.current = true;
|
|
916
1192
|
// Sync URL parameters back to component state for browser navigation
|
|
917
1193
|
const urlParams = new URLSearchParams(window.location.search);
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
if (urlWorkspace && urlWorkspace !== viewNameRef.current) {
|
|
1194
|
+
const urlWorkspace = urlParams.get("workspace");
|
|
1195
|
+
if (urlWorkspace && urlWorkspace !== workspaceIdRef.current) {
|
|
921
1196
|
setWorkspaceId(urlWorkspace);
|
|
922
1197
|
}
|
|
923
|
-
// Handle sidebar changes
|
|
1198
|
+
// Handle sidebar changes — clear when absent so state matches the URL
|
|
924
1199
|
const sidebarParam = urlParams.get("sidebar");
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
1200
|
+
const newSidebars = sidebarParam
|
|
1201
|
+
? sidebarParam.split(",").filter(Boolean)
|
|
1202
|
+
: [];
|
|
1203
|
+
setOpenSidebars(newSidebars);
|
|
929
1204
|
// Handle wizard ID changes
|
|
930
1205
|
const wizardId = urlParams.get("wizardid");
|
|
931
1206
|
setCurrentWizardId(wizardId);
|
|
932
|
-
// Handle compare mode changes
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
1207
|
+
// Handle compare mode changes — always set to avoid stale-closure mismatch
|
|
1208
|
+
// (React skips re-render when the value is unchanged)
|
|
1209
|
+
setCompareMode(urlParams.get("compare") === "true");
|
|
1210
|
+
// Handle help panel changes
|
|
1211
|
+
const helpParam = urlParams.get("help");
|
|
1212
|
+
if (helpParam) {
|
|
1213
|
+
setHelpTerminalActiveTab("manual");
|
|
1214
|
+
setShowHelpTerminal(true);
|
|
1215
|
+
setSelectedHelpSectionId(helpParam === "contents" || helpParam === "true"
|
|
1216
|
+
? null
|
|
1217
|
+
: helpParam);
|
|
936
1218
|
}
|
|
937
|
-
|
|
1219
|
+
else {
|
|
1220
|
+
handleSetShowHelpTerminal(false);
|
|
1221
|
+
}
|
|
1222
|
+
// Handle mode changes — always set to avoid stale-closure mismatch
|
|
938
1223
|
const urlMode = urlParams.get("mode");
|
|
939
|
-
if (urlMode
|
|
1224
|
+
if (urlMode) {
|
|
940
1225
|
setMode(urlMode);
|
|
941
1226
|
}
|
|
942
1227
|
// Handle fullscreen changes (shell-level state)
|
|
@@ -989,9 +1274,11 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
989
1274
|
};
|
|
990
1275
|
window.addEventListener("message", handleMessage);
|
|
991
1276
|
window.addEventListener("popstate", handlePopState);
|
|
1277
|
+
window.addEventListener("visibilitychange", handleVisibilityChange);
|
|
992
1278
|
return () => {
|
|
993
1279
|
window.removeEventListener("message", handleMessage);
|
|
994
1280
|
window.removeEventListener("popstate", handlePopState);
|
|
1281
|
+
window.removeEventListener("visibilitychange", handleVisibilityChange);
|
|
995
1282
|
clearInterval(interval);
|
|
996
1283
|
};
|
|
997
1284
|
}, []);
|
|
@@ -1001,14 +1288,34 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
1001
1288
|
if (searchParams.get("noTour") !== null) {
|
|
1002
1289
|
return;
|
|
1003
1290
|
}
|
|
1291
|
+
// Don't start tour when there are setup errors (user is or will be on system status page)
|
|
1292
|
+
if (startupChecks.state === "complete" && startupChecks.hasBlockingIssues) {
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
// Don't start tour when already on settings system status page
|
|
1296
|
+
if (viewName === "settings" && searchParams.get("ccpanel") === "status") {
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
// Wait for startup checks so we know whether we'll redirect to status
|
|
1300
|
+
if (startupChecks.state !== "complete") {
|
|
1301
|
+
return;
|
|
1302
|
+
}
|
|
1004
1303
|
const tour = configuration.activeTour;
|
|
1005
1304
|
const key = tour === "default" ? "editor.tourShown" : "editor.tourShown." + tour;
|
|
1006
|
-
const tourShown =
|
|
1305
|
+
const tourShown = localStorageService.getString(key);
|
|
1007
1306
|
if (!tourShown) {
|
|
1008
1307
|
startTour();
|
|
1009
|
-
|
|
1308
|
+
localStorageService.setString(key, "true");
|
|
1010
1309
|
}
|
|
1011
|
-
}, [
|
|
1310
|
+
}, [
|
|
1311
|
+
user,
|
|
1312
|
+
startupChecks.state,
|
|
1313
|
+
startupChecks.hasBlockingIssues,
|
|
1314
|
+
viewName,
|
|
1315
|
+
searchParams,
|
|
1316
|
+
configuration.activeTour,
|
|
1317
|
+
startTour,
|
|
1318
|
+
]);
|
|
1012
1319
|
// WebSocket initialization is performed after messageHandler is defined
|
|
1013
1320
|
// Defer URL sync until loadItem is defined below
|
|
1014
1321
|
// Mark end of initial load phase
|
|
@@ -1140,7 +1447,8 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
1140
1447
|
const loadComments = useCallback(async () => {
|
|
1141
1448
|
if (!currentItemDescriptor)
|
|
1142
1449
|
return;
|
|
1143
|
-
const
|
|
1450
|
+
const reviewId = searchParams.get("reviewId");
|
|
1451
|
+
const result = await getComments(currentItemDescriptor.id, currentItemDescriptor.language, currentItemDescriptor.version, reviewId ?? undefined);
|
|
1144
1452
|
if (handleErrorResult(result, ui, state))
|
|
1145
1453
|
return;
|
|
1146
1454
|
setComments((x) => {
|
|
@@ -1153,7 +1461,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
1153
1461
|
allComments.sort((a, b) => a.position - b.position);
|
|
1154
1462
|
return allComments;
|
|
1155
1463
|
});
|
|
1156
|
-
}, [currentItemDescriptor]);
|
|
1464
|
+
}, [currentItemDescriptor, searchParams]);
|
|
1157
1465
|
// Assuming currentItemDescriptor, ui, state, handleErrorResult, and setSuggestedEdits
|
|
1158
1466
|
// are available in your component context.
|
|
1159
1467
|
const loadSuggestedEdits = useCallback(async () => {
|
|
@@ -1162,7 +1470,8 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
1162
1470
|
const result = await getSuggestedEdits(item.descriptor.id, item.descriptor.language, item.descriptor.version);
|
|
1163
1471
|
if (handleErrorResult(result, ui, state))
|
|
1164
1472
|
return;
|
|
1165
|
-
|
|
1473
|
+
const edits = result.data || [];
|
|
1474
|
+
setSuggestedEdits(edits);
|
|
1166
1475
|
}, [item]);
|
|
1167
1476
|
const loadFavorites = useCallback(async () => {
|
|
1168
1477
|
try {
|
|
@@ -1236,6 +1545,25 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
1236
1545
|
return idB.localeCompare(idA);
|
|
1237
1546
|
});
|
|
1238
1547
|
}, []);
|
|
1548
|
+
const normalizeEditHistoryPayload = useCallback((value) => {
|
|
1549
|
+
if (Array.isArray(value)) {
|
|
1550
|
+
return value;
|
|
1551
|
+
}
|
|
1552
|
+
if (value && typeof value === "object") {
|
|
1553
|
+
const payload = value;
|
|
1554
|
+
const candidates = [
|
|
1555
|
+
payload.operations,
|
|
1556
|
+
payload.history,
|
|
1557
|
+
payload.items,
|
|
1558
|
+
payload.data,
|
|
1559
|
+
];
|
|
1560
|
+
const firstArray = candidates.find((candidate) => Array.isArray(candidate));
|
|
1561
|
+
if (Array.isArray(firstArray)) {
|
|
1562
|
+
return firstArray;
|
|
1563
|
+
}
|
|
1564
|
+
}
|
|
1565
|
+
return [];
|
|
1566
|
+
}, []);
|
|
1239
1567
|
useEffect(() => {
|
|
1240
1568
|
// Read fresh page from the mutable slot context ref chain.
|
|
1241
1569
|
// The slot context uses a stable ref that is mutated in-place (see editorSlotContext.ts),
|
|
@@ -1258,7 +1586,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
1258
1586
|
if (handleErrorResult(result, ui, state))
|
|
1259
1587
|
return;
|
|
1260
1588
|
setEditHistory((prev) => {
|
|
1261
|
-
const next = result.data
|
|
1589
|
+
const next = normalizeEditHistoryPayload(result.data);
|
|
1262
1590
|
if (!prev.length)
|
|
1263
1591
|
return sortEditHistoryByDateDesc(next);
|
|
1264
1592
|
if (!next.length)
|
|
@@ -1317,6 +1645,8 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
1317
1645
|
const shouldFilterByLanguage = filterByLanguage !== undefined
|
|
1318
1646
|
? filterByLanguage
|
|
1319
1647
|
: filterByCurrentLanguage;
|
|
1648
|
+
const trimmedHistoryQuery = historySearchQuery.trim();
|
|
1649
|
+
const historyQuery = trimmedHistoryQuery.length > 0 ? trimmedHistoryQuery : undefined;
|
|
1320
1650
|
if (currentMode === "global") {
|
|
1321
1651
|
// Global mode: optionally filter by session and/or language
|
|
1322
1652
|
const currentLanguage = item?.descriptor?.language || currentItemDescriptor?.language;
|
|
@@ -1326,13 +1656,14 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
1326
1656
|
language: shouldFilterByLanguage && currentLanguage
|
|
1327
1657
|
? currentLanguage
|
|
1328
1658
|
: undefined,
|
|
1659
|
+
query: historyQuery,
|
|
1329
1660
|
});
|
|
1330
1661
|
if (handleErrorResult(result, ui, state)) {
|
|
1331
1662
|
console.error("[EditorShell] Failed to load history:", result);
|
|
1332
1663
|
return;
|
|
1333
1664
|
}
|
|
1334
1665
|
setEditHistory((prev) => {
|
|
1335
|
-
const next = result.data
|
|
1666
|
+
const next = normalizeEditHistoryPayload(result.data);
|
|
1336
1667
|
const scope = {
|
|
1337
1668
|
mode: currentMode,
|
|
1338
1669
|
filterBySession: shouldFilterBySession,
|
|
@@ -1343,8 +1674,12 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
1343
1674
|
};
|
|
1344
1675
|
if (!prev.length)
|
|
1345
1676
|
return sortEditHistoryByDateDesc(next.filter((op) => matchesHistoryScope(op, scope)));
|
|
1346
|
-
if (!next.length)
|
|
1677
|
+
if (!next.length) {
|
|
1678
|
+
// When searching, respect the empty result — don't fall back to previous items
|
|
1679
|
+
if (historyQuery)
|
|
1680
|
+
return [];
|
|
1347
1681
|
return sortEditHistoryByDateDesc(prev.filter((op) => matchesHistoryScope(op, scope)));
|
|
1682
|
+
}
|
|
1348
1683
|
const prevById = new Map(prev.map((x) => [x.id, x]));
|
|
1349
1684
|
const nextById = new Set(next.map((x) => x.id));
|
|
1350
1685
|
const merged = next
|
|
@@ -1380,9 +1715,12 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
1380
1715
|
return mergedOp;
|
|
1381
1716
|
});
|
|
1382
1717
|
// Preserve operations that arrived via WebSocket during the fetch window
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1718
|
+
// but not when filtering by search query — search results are authoritative
|
|
1719
|
+
if (!historyQuery) {
|
|
1720
|
+
for (const [id, priorOp] of prevById) {
|
|
1721
|
+
if (!nextById.has(id) && matchesHistoryScope(priorOp, scope)) {
|
|
1722
|
+
merged.push(priorOp);
|
|
1723
|
+
}
|
|
1386
1724
|
}
|
|
1387
1725
|
}
|
|
1388
1726
|
return sortEditHistoryByDateDesc(merged);
|
|
@@ -1405,12 +1743,13 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
1405
1743
|
const result = await getEditHistory({
|
|
1406
1744
|
item: itemFilter,
|
|
1407
1745
|
sessionId: shouldFilterBySession ? sessionId : undefined,
|
|
1746
|
+
query: historyQuery,
|
|
1408
1747
|
});
|
|
1409
1748
|
if (handleErrorResult(result, ui, state)) {
|
|
1410
1749
|
console.error("[EditorShell] Failed to load item history:", result);
|
|
1411
1750
|
return;
|
|
1412
1751
|
}
|
|
1413
|
-
let operations = result.data
|
|
1752
|
+
let operations = normalizeEditHistoryPayload(result.data);
|
|
1414
1753
|
// Client-side version filtering for current-version mode
|
|
1415
1754
|
if (currentMode === "current-version") {
|
|
1416
1755
|
// Defensive filter: only include operations for the current version
|
|
@@ -1433,8 +1772,11 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
1433
1772
|
};
|
|
1434
1773
|
if (!prev.length)
|
|
1435
1774
|
return sortEditHistoryByDateDesc(operations.filter((op) => matchesHistoryScope(op, scope)));
|
|
1436
|
-
if (!operations.length)
|
|
1775
|
+
if (!operations.length) {
|
|
1776
|
+
if (historyQuery)
|
|
1777
|
+
return [];
|
|
1437
1778
|
return sortEditHistoryByDateDesc(prev.filter((op) => matchesHistoryScope(op, scope)));
|
|
1779
|
+
}
|
|
1438
1780
|
const prevById = new Map(prev.map((x) => [x.id, x]));
|
|
1439
1781
|
const nextById = new Set(operations.map((x) => x.id));
|
|
1440
1782
|
const merged = operations
|
|
@@ -1470,9 +1812,12 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
1470
1812
|
return mergedOp;
|
|
1471
1813
|
});
|
|
1472
1814
|
// Preserve operations that arrived via WebSocket during the fetch window
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1815
|
+
// but not when filtering by search query — search results are authoritative
|
|
1816
|
+
if (!historyQuery) {
|
|
1817
|
+
for (const [id, priorOp] of prevById) {
|
|
1818
|
+
if (!nextById.has(id) && matchesHistoryScope(priorOp, scope)) {
|
|
1819
|
+
merged.push(priorOp);
|
|
1820
|
+
}
|
|
1476
1821
|
}
|
|
1477
1822
|
}
|
|
1478
1823
|
return sortEditHistoryByDateDesc(merged);
|
|
@@ -1483,9 +1828,11 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
1483
1828
|
historyMode,
|
|
1484
1829
|
showOnlyMyChanges,
|
|
1485
1830
|
filterByCurrentLanguage,
|
|
1831
|
+
historySearchQuery,
|
|
1486
1832
|
item,
|
|
1487
1833
|
currentItemDescriptor,
|
|
1488
1834
|
matchesHistoryScope,
|
|
1835
|
+
normalizeEditHistoryPayload,
|
|
1489
1836
|
sortEditHistoryByDateDesc,
|
|
1490
1837
|
]);
|
|
1491
1838
|
// Debounced history refresh to avoid hammering `/parhelia/editHistory` on rapid UI changes.
|
|
@@ -1551,13 +1898,14 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
1551
1898
|
}
|
|
1552
1899
|
}
|
|
1553
1900
|
else if (historyMode !== "global" && currentItemDescriptor) {
|
|
1901
|
+
// Always load immediately for page-centric/current-version/timeline so we don't show
|
|
1902
|
+
// an empty history list for the debounce window (e.g. 600ms). The effect only runs on
|
|
1903
|
+
// mode or item id/lang/version change, not on every keystroke, so this avoids flaky
|
|
1904
|
+
// undo/redo tests and improves UX when switching to page-centric.
|
|
1554
1905
|
if (!historyInitialLoadDoneRef.current) {
|
|
1555
1906
|
historyInitialLoadDoneRef.current = true;
|
|
1556
|
-
refreshHistoryRef.current(historyMode);
|
|
1557
|
-
}
|
|
1558
|
-
else {
|
|
1559
|
-
debouncedRefreshHistoryRef.current(historyMode);
|
|
1560
1907
|
}
|
|
1908
|
+
refreshHistoryRef.current(historyMode);
|
|
1561
1909
|
}
|
|
1562
1910
|
else if (historyMode !== "global" && !currentItemDescriptor) {
|
|
1563
1911
|
// Clear history if no item loaded in page-centric modes
|
|
@@ -1570,6 +1918,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
1570
1918
|
historyMode,
|
|
1571
1919
|
showOnlyMyChanges,
|
|
1572
1920
|
filterByCurrentLanguage,
|
|
1921
|
+
historySearchQuery,
|
|
1573
1922
|
currentItemDescriptor?.id,
|
|
1574
1923
|
currentItemDescriptor?.language,
|
|
1575
1924
|
currentItemDescriptor?.version,
|
|
@@ -1634,6 +1983,9 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
1634
1983
|
if (isInitialLoad)
|
|
1635
1984
|
return;
|
|
1636
1985
|
const current = new URLSearchParams(window.location.search);
|
|
1986
|
+
const urlWorkspace = current.get("workspace");
|
|
1987
|
+
const urlView = current.get("view");
|
|
1988
|
+
const isWorkspaceTransitioning = !!urlWorkspace && urlWorkspace !== viewName;
|
|
1637
1989
|
// Sync item-related parameters only when an item is selected
|
|
1638
1990
|
if (currentItemDescriptor) {
|
|
1639
1991
|
if (current.get("itemid") !== currentItemDescriptor.id) {
|
|
@@ -1664,20 +2016,21 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
1664
2016
|
// If reviewId or urlItemId exists, preserve itemid/lang/version from URL
|
|
1665
2017
|
}
|
|
1666
2018
|
// Always sync workspace-related parameters regardless of item selection
|
|
1667
|
-
if (current.get("workspace") !== viewName) {
|
|
2019
|
+
if (!isWorkspaceTransitioning && current.get("workspace") !== viewName) {
|
|
1668
2020
|
current.set("workspace", viewName);
|
|
1669
2021
|
}
|
|
1670
|
-
|
|
1671
|
-
//
|
|
2022
|
+
// Sync sidebar state. Always write a value (sentinel when empty) so that an
|
|
2023
|
+
// explicit "user closed everything" state survives a reload.
|
|
1672
2024
|
const currentSidebars = current.get("sidebar") ?? "";
|
|
1673
|
-
const newSidebars = openSidebars
|
|
2025
|
+
const newSidebars = sidebarUrlValue(openSidebars);
|
|
1674
2026
|
if (currentSidebars !== newSidebars) {
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
2027
|
+
current.set("sidebar", newSidebars);
|
|
2028
|
+
}
|
|
2029
|
+
if (showHelpTerminal) {
|
|
2030
|
+
current.set("help", selectedHelpSectionId ?? "contents");
|
|
2031
|
+
}
|
|
2032
|
+
else {
|
|
2033
|
+
current.delete("help");
|
|
1681
2034
|
}
|
|
1682
2035
|
if (!compareMode) {
|
|
1683
2036
|
current.delete("compare");
|
|
@@ -1703,15 +2056,15 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
1703
2056
|
else {
|
|
1704
2057
|
current.delete("wizardid");
|
|
1705
2058
|
}
|
|
1706
|
-
//
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
}
|
|
1713
|
-
else {
|
|
2059
|
+
// Preserve settings-specific parameters while transitioning into Settings too.
|
|
2060
|
+
// Some callers update the URL first and switch the workspace state a moment later.
|
|
2061
|
+
const isSettingsNavigation = viewName === "settings" ||
|
|
2062
|
+
urlWorkspace === "settings" ||
|
|
2063
|
+
urlView === "settings";
|
|
2064
|
+
if (!isSettingsNavigation) {
|
|
1714
2065
|
current.delete("ccpanel");
|
|
2066
|
+
current.delete("providerId");
|
|
2067
|
+
current.delete("modelId");
|
|
1715
2068
|
}
|
|
1716
2069
|
// Preserve reviewId parameter if it exists (for review links)
|
|
1717
2070
|
// This ensures review links don't lose the reviewId when URL is synced
|
|
@@ -1724,17 +2077,42 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
1724
2077
|
const browserPathname = typeof window !== "undefined" ? window.location.pathname : pathname;
|
|
1725
2078
|
const newUrl = `${browserPathname}?${current.toString()}`;
|
|
1726
2079
|
const oldUrl = `${browserPathname}${window.location.search}`;
|
|
2080
|
+
const isRestoringLastSyncedUrl = newUrl === lastUrlRef.current;
|
|
1727
2081
|
// Skip pushing to history if we're handling a popstate event (browser back/forward)
|
|
1728
2082
|
// This prevents the URL sync from overwriting the history during back navigation
|
|
1729
2083
|
if (isHandlingPopStateRef.current) {
|
|
1730
2084
|
return;
|
|
1731
2085
|
}
|
|
1732
2086
|
if (newUrl !== oldUrl) {
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
2087
|
+
if (isFirstUrlSyncRef.current ||
|
|
2088
|
+
switchWorkspacePushedRef.current ||
|
|
2089
|
+
isRestoringLastSyncedUrl) {
|
|
2090
|
+
window.history.replaceState(getCurrentHistoryState(), "", newUrl);
|
|
2091
|
+
isFirstUrlSyncRef.current = false;
|
|
2092
|
+
switchWorkspacePushedRef.current = false;
|
|
2093
|
+
}
|
|
2094
|
+
else {
|
|
2095
|
+
window.history.pushState(getCurrentHistoryState(), "", newUrl);
|
|
2096
|
+
}
|
|
1736
2097
|
lastUrlRef.current = newUrl;
|
|
1737
2098
|
}
|
|
2099
|
+
else {
|
|
2100
|
+
// Even when the first sync is a no-op (URL already matches state), consume
|
|
2101
|
+
// the first-sync flag. Otherwise the *next* real change (e.g. user selecting
|
|
2102
|
+
// a different item) would hit the replaceState branch and overwrite the
|
|
2103
|
+
// landing history entry — eating the "back" target and making browser back
|
|
2104
|
+
// skip past the initial page.
|
|
2105
|
+
if (isFirstUrlSyncRef.current) {
|
|
2106
|
+
isFirstUrlSyncRef.current = false;
|
|
2107
|
+
lastUrlRef.current = newUrl;
|
|
2108
|
+
}
|
|
2109
|
+
if (switchWorkspacePushedRef.current) {
|
|
2110
|
+
// A workspace change may already have pushed the exact target URL. If we leave
|
|
2111
|
+
// this flag set, the next unrelated item navigation will incorrectly replace
|
|
2112
|
+
// history instead of pushing a new entry, which breaks browser back/forward.
|
|
2113
|
+
switchWorkspacePushedRef.current = false;
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
1738
2116
|
}, [
|
|
1739
2117
|
currentItemDescriptor,
|
|
1740
2118
|
viewName,
|
|
@@ -1746,6 +2124,8 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
1746
2124
|
fullscreen,
|
|
1747
2125
|
currentWizardId,
|
|
1748
2126
|
openSidebars,
|
|
2127
|
+
showHelpTerminal,
|
|
2128
|
+
selectedHelpSectionId,
|
|
1749
2129
|
]);
|
|
1750
2130
|
useEffect(() => {
|
|
1751
2131
|
async function load() {
|
|
@@ -1842,12 +2222,9 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
1842
2222
|
"en",
|
|
1843
2223
|
version: 0,
|
|
1844
2224
|
};
|
|
1845
|
-
const loadedItem = await itemsRepository.getItem(itemToLoad);
|
|
1846
|
-
if (!loadedItem) {
|
|
1847
|
-
return undefined;
|
|
1848
|
-
}
|
|
1849
2225
|
// ensure this is object has no additional properties
|
|
1850
2226
|
itemToLoad = getItemDescriptor(itemToLoad);
|
|
2227
|
+
const requestedItemKey = makeItemKey(itemToLoad);
|
|
1851
2228
|
// Check if item is already open in any slot - if so, reuse that slot instead of creating a new one
|
|
1852
2229
|
const existingSlotForItem = editorSlots.find((s) => s.itemDescriptor.id === itemToLoad.id &&
|
|
1853
2230
|
s.itemDescriptor.language === itemToLoad.language &&
|
|
@@ -1858,6 +2235,17 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
1858
2235
|
!options?.openInNewSlot &&
|
|
1859
2236
|
!options?.targetSlotId) {
|
|
1860
2237
|
const isExistingSlotActive = existingSlotForItem.slotId === activeSlotIdRef.current;
|
|
2238
|
+
if (isExistingSlotActive) {
|
|
2239
|
+
latestGlobalLoadKeyRef.current = requestedItemKey;
|
|
2240
|
+
}
|
|
2241
|
+
const loadedItem = await itemsRepository.getItem(itemToLoad);
|
|
2242
|
+
if (!loadedItem) {
|
|
2243
|
+
return undefined;
|
|
2244
|
+
}
|
|
2245
|
+
if (isExistingSlotActive &&
|
|
2246
|
+
latestGlobalLoadKeyRef.current !== requestedItemKey) {
|
|
2247
|
+
return loadedItem;
|
|
2248
|
+
}
|
|
1861
2249
|
// If forceRefresh is true, update the slot with a new refreshToken to trigger a reload
|
|
1862
2250
|
if (options?.forceRefresh) {
|
|
1863
2251
|
setEditorSlots((prev) => {
|
|
@@ -1928,12 +2316,30 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
1928
2316
|
return next;
|
|
1929
2317
|
});
|
|
1930
2318
|
// Decide whether this load should update the global "current item" state.
|
|
1931
|
-
// - If we're loading into an inactive slot
|
|
1932
|
-
// - If the slot is active
|
|
2319
|
+
// - If we're loading into an inactive slot we won't normally touch, do NOT update URL/tree/controls.
|
|
2320
|
+
// - If the slot is active, becoming active as the first slot, or is a brand-new
|
|
2321
|
+
// slot the caller explicitly opened, do update.
|
|
1933
2322
|
const isFirstSlot = editorSlots.length === 0;
|
|
1934
|
-
const
|
|
1935
|
-
|
|
1936
|
-
|
|
2323
|
+
const isExplicitNewSlot = options?.openInNewSlot === true && !options?.targetSlotId;
|
|
2324
|
+
const shouldUpdateGlobalCurrentItem = isFirstSlot ||
|
|
2325
|
+
isExplicitNewSlot ||
|
|
2326
|
+
targetSlotId === activeSlotIdRef.current;
|
|
2327
|
+
if (shouldUpdateGlobalCurrentItem) {
|
|
2328
|
+
latestGlobalLoadKeyRef.current = requestedItemKey;
|
|
2329
|
+
}
|
|
2330
|
+
const loadedItem = await itemsRepository.getItem(itemToLoad);
|
|
2331
|
+
if (!loadedItem) {
|
|
2332
|
+
return undefined;
|
|
2333
|
+
}
|
|
2334
|
+
if (shouldUpdateGlobalCurrentItem &&
|
|
2335
|
+
latestGlobalLoadKeyRef.current !== requestedItemKey) {
|
|
2336
|
+
return loadedItem;
|
|
2337
|
+
}
|
|
2338
|
+
// Activate the new slot when this is either the initial first slot, or the
|
|
2339
|
+
// caller explicitly opened the item in a new slot — focus follows the action so
|
|
2340
|
+
// the user immediately sees what they just opened. Beyond that, slot focus is
|
|
2341
|
+
// driven by mouse hover/click.
|
|
2342
|
+
if (isFirstSlot || isExplicitNewSlot) {
|
|
1937
2343
|
activeSlotIdRef.current = targetSlotId;
|
|
1938
2344
|
setActiveSlotId(targetSlotId);
|
|
1939
2345
|
}
|
|
@@ -2039,9 +2445,10 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
2039
2445
|
useEffect(() => {
|
|
2040
2446
|
if (fullscreen &&
|
|
2041
2447
|
!searchParams.get("fullscreen") &&
|
|
2042
|
-
!configuration.forceFullscreen
|
|
2448
|
+
!configuration.forceFullscreen &&
|
|
2449
|
+
!isMobile)
|
|
2043
2450
|
setShowFullscreenHint(true);
|
|
2044
|
-
}, [fullscreen, configuration.forceFullscreen, searchParams]);
|
|
2451
|
+
}, [fullscreen, configuration.forceFullscreen, searchParams, isMobile]);
|
|
2045
2452
|
const state = {
|
|
2046
2453
|
page,
|
|
2047
2454
|
configuration,
|
|
@@ -2140,20 +2547,35 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
2140
2547
|
? existingOp.progress
|
|
2141
2548
|
: op.progress;
|
|
2142
2549
|
// IMPORTANT: Once canUndo becomes true, never downgrade it to false,
|
|
2143
|
-
// UNLESS this update indicates the operation was undone.
|
|
2144
|
-
//
|
|
2145
|
-
//
|
|
2146
|
-
|
|
2550
|
+
// UNLESS this update indicates the operation was undone.
|
|
2551
|
+
// Runtime logs show long-running undo completion can arrive as:
|
|
2552
|
+
// - existing: canUndo=true, executionStatus=executing
|
|
2553
|
+
// - incoming: canUndo=false, executionStatus=completed
|
|
2554
|
+
// without explicit undone/canRedo flags yet.
|
|
2555
|
+
const isExplicitUndoUpdate = op.undone === true || op.canRedo === true;
|
|
2556
|
+
const isInferredUndoCompletion = existingOp.executionStatus === "executing" &&
|
|
2557
|
+
op.executionStatus === "completed" &&
|
|
2558
|
+
existingOp.canUndo === true &&
|
|
2559
|
+
op.canUndo === false;
|
|
2560
|
+
const isUndoUpdate = isExplicitUndoUpdate || isInferredUndoCompletion;
|
|
2147
2561
|
const mergedCanUndo = isUndoUpdate
|
|
2148
2562
|
? false
|
|
2149
2563
|
: existingOp.canUndo === true
|
|
2150
2564
|
? true
|
|
2151
2565
|
: op.canUndo;
|
|
2566
|
+
const mergedCanRedo = isUndoUpdate
|
|
2567
|
+
? true
|
|
2568
|
+
: op.canRedo ?? existingOp.canRedo;
|
|
2569
|
+
const mergedUndone = isUndoUpdate
|
|
2570
|
+
? true
|
|
2571
|
+
: op.undone ?? existingOp.undone;
|
|
2152
2572
|
const mergedOp = {
|
|
2153
2573
|
...existingOp,
|
|
2154
2574
|
...op,
|
|
2155
2575
|
progress: mergedProgress,
|
|
2156
2576
|
canUndo: mergedCanUndo,
|
|
2577
|
+
canRedo: mergedCanRedo,
|
|
2578
|
+
undone: mergedUndone,
|
|
2157
2579
|
};
|
|
2158
2580
|
// Ensure undone operations always have canRedo: true.
|
|
2159
2581
|
if (mergedOp.undone && !mergedOp.canRedo) {
|
|
@@ -2207,10 +2629,6 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
2207
2629
|
});
|
|
2208
2630
|
// Ref for markOperationComplete callback (needed because operationsContext is created later)
|
|
2209
2631
|
const markOperationCompleteRef = useRef(null);
|
|
2210
|
-
// When the websocket is reconnecting, we can briefly lose the ability to send messages.
|
|
2211
|
-
// Agent dialog responses (e.g. questionnaire cancel/submit) must not be dropped, otherwise
|
|
2212
|
-
// the backend tool call can remain pending and tests/users will hang.
|
|
2213
|
-
const pendingAgentDialogResponsesRef = useRef([]);
|
|
2214
2632
|
// WebSocket message handler and connection
|
|
2215
2633
|
const messageHandler = useSocketMessageHandler({
|
|
2216
2634
|
sessionId,
|
|
@@ -2236,28 +2654,31 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
2236
2654
|
markOperationCompleteRef.current?.(operationId);
|
|
2237
2655
|
},
|
|
2238
2656
|
});
|
|
2657
|
+
// Concurrent user limit error state
|
|
2658
|
+
const [concurrentUserLimitError, setConcurrentUserLimitError] = useState(null);
|
|
2659
|
+
concurrentUserLimitErrorRef.current = concurrentUserLimitError;
|
|
2660
|
+
const handleRetryConnection = useCallback(() => {
|
|
2661
|
+
setConcurrentUserLimitError(null);
|
|
2662
|
+
// Force reconnection by triggering a new connection attempt
|
|
2663
|
+
// The useEditorWebSocket hook will check availability again
|
|
2664
|
+
const socket = globalThis.editorSocket;
|
|
2665
|
+
if (socket) {
|
|
2666
|
+
socket.close();
|
|
2667
|
+
delete globalThis.editorSocket;
|
|
2668
|
+
}
|
|
2669
|
+
// The hook will automatically attempt to reconnect
|
|
2670
|
+
}, []);
|
|
2239
2671
|
const { socketRef: socketInstanceRef } = useEditorWebSocket({
|
|
2240
2672
|
sessionId,
|
|
2241
2673
|
onMessage: messageHandler,
|
|
2242
2674
|
onOpen: async () => {
|
|
2675
|
+
// Clear concurrent user limit error on successful connection
|
|
2676
|
+
setConcurrentUserLimitError(null);
|
|
2677
|
+
// Startup WebSocket probe may have failed with a blocking error while seats were full;
|
|
2678
|
+
// re-run status checks quietly so "1 error" does not stick after a successful connect.
|
|
2679
|
+
void startupChecks.recheckQuiet();
|
|
2243
2680
|
// Increment socket connection version to trigger re-subscriptions
|
|
2244
2681
|
setSocketConnectionVersion((v) => v + 1);
|
|
2245
|
-
// Flush any queued agent dialog responses now that the socket is open.
|
|
2246
|
-
// Keep this early so pending tool calls unblock ASAP.
|
|
2247
|
-
try {
|
|
2248
|
-
if (socketInstanceRef.current &&
|
|
2249
|
-
socketInstanceRef.current.readyState === WebSocket.OPEN &&
|
|
2250
|
-
pendingAgentDialogResponsesRef.current.length > 0) {
|
|
2251
|
-
// FIFO flush
|
|
2252
|
-
while (pendingAgentDialogResponsesRef.current.length > 0) {
|
|
2253
|
-
const queued = pendingAgentDialogResponsesRef.current.shift();
|
|
2254
|
-
socketInstanceRef.current.send(JSON.stringify(queued));
|
|
2255
|
-
}
|
|
2256
|
-
}
|
|
2257
|
-
}
|
|
2258
|
-
catch (e) {
|
|
2259
|
-
console.error("Failed to flush queued agent dialog responses:", e);
|
|
2260
|
-
}
|
|
2261
2682
|
// Fetch any running operations on (re)connect for auto-resume
|
|
2262
2683
|
// This ensures the UI shows operations that are still executing
|
|
2263
2684
|
try {
|
|
@@ -2273,24 +2694,62 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
2273
2694
|
}
|
|
2274
2695
|
},
|
|
2275
2696
|
onError: (error) => console.error("WebSocket error:", error),
|
|
2697
|
+
onConcurrentUserLimit: (error) => {
|
|
2698
|
+
setConcurrentUserLimitError(error);
|
|
2699
|
+
},
|
|
2700
|
+
onSessionRevoked: () => promptSessionReconnect("session-revoked"),
|
|
2276
2701
|
connectSocket,
|
|
2277
2702
|
requestQuota,
|
|
2278
2703
|
sendClientInfo,
|
|
2704
|
+
setSocketDiagnostics,
|
|
2279
2705
|
});
|
|
2706
|
+
useEffect(() => {
|
|
2707
|
+
const hasMySession = activeSessions.some((s) => s.sessionId === sessionId);
|
|
2708
|
+
if (hasMySession &&
|
|
2709
|
+
socketInstanceRef.current?.readyState === WebSocket.OPEN) {
|
|
2710
|
+
toast.dismiss("session-revoked");
|
|
2711
|
+
}
|
|
2712
|
+
}, [activeSessions, sessionId, socketConnectionVersion, socketInstanceRef]);
|
|
2713
|
+
useEffect(() => {
|
|
2714
|
+
const handleRevoked = (event) => {
|
|
2715
|
+
const customEvent = event;
|
|
2716
|
+
promptSessionReconnect(customEvent?.detail?.reason);
|
|
2717
|
+
};
|
|
2718
|
+
window.addEventListener("parhelia:session-revoked", handleRevoked);
|
|
2719
|
+
return () => {
|
|
2720
|
+
window.removeEventListener("parhelia:session-revoked", handleRevoked);
|
|
2721
|
+
};
|
|
2722
|
+
}, [promptSessionReconnect]);
|
|
2723
|
+
// Show a single "session expired" toast with a Login action when any
|
|
2724
|
+
// service call (or the keepalive check) detects that the user is no
|
|
2725
|
+
// longer authenticated. Using a stable toast id coalesces the toast so a
|
|
2726
|
+
// burst of failing calls does not spam the UI with duplicate toasts.
|
|
2727
|
+
useEffect(() => {
|
|
2728
|
+
if (typeof window === "undefined")
|
|
2729
|
+
return;
|
|
2730
|
+
const handleExpired = () => {
|
|
2731
|
+
toast.error("Your session has expired", {
|
|
2732
|
+
id: "session-expired",
|
|
2733
|
+
description: "Please login again to continue editing.",
|
|
2734
|
+
action: {
|
|
2735
|
+
label: "Login",
|
|
2736
|
+
onClick: () => {
|
|
2737
|
+
window.location.href = "/sitecore/login";
|
|
2738
|
+
},
|
|
2739
|
+
},
|
|
2740
|
+
duration: Infinity,
|
|
2741
|
+
});
|
|
2742
|
+
};
|
|
2743
|
+
window.addEventListener("parhelia:session-expired", handleExpired);
|
|
2744
|
+
return () => {
|
|
2745
|
+
window.removeEventListener("parhelia:session-expired", handleExpired);
|
|
2746
|
+
};
|
|
2747
|
+
}, []);
|
|
2280
2748
|
const sendSocketMessage = useCallback((message) => {
|
|
2281
2749
|
if (socketInstanceRef.current &&
|
|
2282
2750
|
socketInstanceRef.current.readyState === WebSocket.OPEN) {
|
|
2283
2751
|
socketInstanceRef.current.send(JSON.stringify(message));
|
|
2284
2752
|
}
|
|
2285
|
-
else if (message.type === "agent-dialog-response") {
|
|
2286
|
-
// Queue dialog responses to avoid losing them during reconnects.
|
|
2287
|
-
pendingAgentDialogResponsesRef.current.push(message);
|
|
2288
|
-
// Prevent unbounded growth in pathological scenarios.
|
|
2289
|
-
if (pendingAgentDialogResponsesRef.current.length > 50) {
|
|
2290
|
-
pendingAgentDialogResponsesRef.current =
|
|
2291
|
-
pendingAgentDialogResponsesRef.current.slice(-50);
|
|
2292
|
-
}
|
|
2293
|
-
}
|
|
2294
2753
|
}, [socketInstanceRef]);
|
|
2295
2754
|
// URL update helper - defined early so it can be used by workspace/sidebar functions
|
|
2296
2755
|
const updateUrl = useCallback((params) => {
|
|
@@ -2309,7 +2768,8 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
2309
2768
|
? `${browserPathname}?${queryString}`
|
|
2310
2769
|
: browserPathname;
|
|
2311
2770
|
if (typeof window !== "undefined") {
|
|
2312
|
-
window.history.pushState(
|
|
2771
|
+
window.history.pushState(getCurrentHistoryState(), "", newUrl);
|
|
2772
|
+
lastUrlRef.current = newUrl;
|
|
2313
2773
|
}
|
|
2314
2774
|
else {
|
|
2315
2775
|
router.push(newUrl, { scroll: false });
|
|
@@ -2334,8 +2794,13 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
2334
2794
|
if (!options?.skipNavigationHistory) {
|
|
2335
2795
|
addNavigationEntry(targetWorkspaceId, item);
|
|
2336
2796
|
}
|
|
2337
|
-
//
|
|
2338
|
-
|
|
2797
|
+
// Mark that we're pushing from switchWorkspace so the URL sync effect
|
|
2798
|
+
// will replaceState instead of pushing a second history entry.
|
|
2799
|
+
switchWorkspacePushedRef.current = true;
|
|
2800
|
+
updateUrl({
|
|
2801
|
+
workspace: targetWorkspaceId,
|
|
2802
|
+
...options?.urlParams,
|
|
2803
|
+
});
|
|
2339
2804
|
if (typeof document.startViewTransition === "function") {
|
|
2340
2805
|
document.startViewTransition(() => {
|
|
2341
2806
|
flushSync(() => {
|
|
@@ -2355,14 +2820,6 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
2355
2820
|
updateUrl,
|
|
2356
2821
|
handleSetShowAgentsPanel,
|
|
2357
2822
|
]);
|
|
2358
|
-
// Legacy alias for backwards compatibility
|
|
2359
|
-
const switchView = useCallback((viewName, options) => {
|
|
2360
|
-
// Handle ccpanel for settings workspace
|
|
2361
|
-
if (options?.ccpanel) {
|
|
2362
|
-
updateUrl({ ccpanel: options.ccpanel, workspace: viewName });
|
|
2363
|
-
}
|
|
2364
|
-
switchWorkspace(viewName, options);
|
|
2365
|
-
}, [switchWorkspace, updateUrl]);
|
|
2366
2823
|
// Helper: get all sidebar IDs that should be preserved (locked sidebars + their stack mates)
|
|
2367
2824
|
const getPreservedSidebarIds = useCallback(() => {
|
|
2368
2825
|
const lockedSet = new Set(lockedSidebarsRef.current);
|
|
@@ -2400,8 +2857,11 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
2400
2857
|
setOpenSidebars(newSidebars);
|
|
2401
2858
|
setLockedSidebars((locked) => locked.filter((id) => id !== sidebarId));
|
|
2402
2859
|
setSidebarStacks((prevStacks) => normalizeSidebarStacks(newSidebars, prevStacks));
|
|
2860
|
+
if (sidebarId === "agents-panel") {
|
|
2861
|
+
setUserPreferences({ showAgentsPanel: false });
|
|
2862
|
+
}
|
|
2403
2863
|
startTransition(() => {
|
|
2404
|
-
updateUrl({ sidebar: newSidebars
|
|
2864
|
+
updateUrl({ sidebar: sidebarUrlValue(newSidebars) });
|
|
2405
2865
|
});
|
|
2406
2866
|
return;
|
|
2407
2867
|
}
|
|
@@ -2414,34 +2874,69 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
2414
2874
|
];
|
|
2415
2875
|
openSidebarsRef.current = newSidebars;
|
|
2416
2876
|
setOpenSidebars(newSidebars);
|
|
2877
|
+
ensureSidebarPinned(sidebarId);
|
|
2417
2878
|
setSidebarStacks((prevStacks) => normalizeSidebarStacks(newSidebars, prevStacks));
|
|
2879
|
+
if (sidebarId === "agents-panel") {
|
|
2880
|
+
setUserPreferences({ showAgentsPanel: true });
|
|
2881
|
+
}
|
|
2882
|
+
// On mobile, close the editor form panel when opening a sidebar
|
|
2883
|
+
if (isMobile) {
|
|
2884
|
+
setMobileEditorPanelOpenRaw(false);
|
|
2885
|
+
}
|
|
2418
2886
|
startTransition(() => {
|
|
2419
|
-
updateUrl({ sidebar: newSidebars
|
|
2887
|
+
updateUrl({ sidebar: sidebarUrlValue(newSidebars) });
|
|
2420
2888
|
});
|
|
2421
|
-
}, [
|
|
2889
|
+
}, [
|
|
2890
|
+
updateUrl,
|
|
2891
|
+
getPreservedSidebarIds,
|
|
2892
|
+
normalizeSidebarStacks,
|
|
2893
|
+
ensureSidebarPinned,
|
|
2894
|
+
setUserPreferences,
|
|
2895
|
+
isMobile,
|
|
2896
|
+
]);
|
|
2422
2897
|
// Ensure a sidebar is open (without toggling it closed if already open)
|
|
2423
|
-
const openSidebar = useCallback((sidebarId) => {
|
|
2898
|
+
const openSidebar = useCallback((sidebarId, options) => {
|
|
2424
2899
|
const currentOpenSidebars = openSidebarsRef.current;
|
|
2425
2900
|
if (currentOpenSidebars.includes(sidebarId)) {
|
|
2426
2901
|
// Already open, nothing to do
|
|
2427
2902
|
return;
|
|
2428
2903
|
}
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2904
|
+
const preservedSet = options?.preserveOpenSidebars
|
|
2905
|
+
? undefined
|
|
2906
|
+
: getPreservedSidebarIds();
|
|
2907
|
+
const newSidebars = options?.preserveOpenSidebars
|
|
2908
|
+
? [...currentOpenSidebars, sidebarId]
|
|
2909
|
+
: [
|
|
2910
|
+
...currentOpenSidebars.filter((id) => preservedSet?.has(id)),
|
|
2911
|
+
sidebarId,
|
|
2912
|
+
];
|
|
2435
2913
|
openSidebarsRef.current = newSidebars;
|
|
2436
2914
|
setOpenSidebars(newSidebars);
|
|
2915
|
+
ensureSidebarPinned(sidebarId);
|
|
2437
2916
|
setSidebarStacks((prevStacks) => normalizeSidebarStacks(newSidebars, prevStacks));
|
|
2917
|
+
if (sidebarId === "agents-panel") {
|
|
2918
|
+
setUserPreferences({ showAgentsPanel: true });
|
|
2919
|
+
}
|
|
2920
|
+
// On mobile, close the editor form panel when opening a sidebar
|
|
2921
|
+
if (isMobile) {
|
|
2922
|
+
setMobileEditorPanelOpenRaw(false);
|
|
2923
|
+
}
|
|
2438
2924
|
startTransition(() => {
|
|
2439
|
-
updateUrl({ sidebar: newSidebars
|
|
2925
|
+
updateUrl({ sidebar: sidebarUrlValue(newSidebars) });
|
|
2440
2926
|
});
|
|
2441
|
-
}, [
|
|
2927
|
+
}, [
|
|
2928
|
+
updateUrl,
|
|
2929
|
+
getPreservedSidebarIds,
|
|
2930
|
+
normalizeSidebarStacks,
|
|
2931
|
+
ensureSidebarPinned,
|
|
2932
|
+
setUserPreferences,
|
|
2933
|
+
isMobile,
|
|
2934
|
+
]);
|
|
2442
2935
|
// Toggle lock state for a sidebar stack (keeps it visible when selecting another)
|
|
2443
2936
|
// When toggling any sidebar, we toggle the entire stack it belongs to
|
|
2444
2937
|
const toggleSidebarLock = useCallback((sidebarId) => {
|
|
2938
|
+
if (isMobile)
|
|
2939
|
+
return;
|
|
2445
2940
|
const currentStacks = sidebarStacksRef.current;
|
|
2446
2941
|
const stackContainingSidebar = currentStacks.find((stack) => stack.includes(sidebarId));
|
|
2447
2942
|
const sidebarIdsToToggle = stackContainingSidebar || [sidebarId];
|
|
@@ -2459,7 +2954,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
2459
2954
|
];
|
|
2460
2955
|
}
|
|
2461
2956
|
});
|
|
2462
|
-
}, []);
|
|
2957
|
+
}, [isMobile]);
|
|
2463
2958
|
const stackSidebar = useCallback((sidebarId, targetSidebarId, position = "after") => {
|
|
2464
2959
|
if (!sidebarId || !targetSidebarId || sidebarId === targetSidebarId)
|
|
2465
2960
|
return;
|
|
@@ -2491,6 +2986,38 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
2491
2986
|
return normalizeSidebarStacks(openIds, next);
|
|
2492
2987
|
});
|
|
2493
2988
|
}, [normalizeSidebarStacks]);
|
|
2989
|
+
const moveSidebarToColumn = useCallback((sidebarId, targetSidebarId, position = "after") => {
|
|
2990
|
+
if (!sidebarId || !targetSidebarId || sidebarId === targetSidebarId) {
|
|
2991
|
+
return;
|
|
2992
|
+
}
|
|
2993
|
+
const currentOpen = openSidebarsRef.current;
|
|
2994
|
+
const baseOpen = currentOpen.filter((id) => id !== sidebarId);
|
|
2995
|
+
const targetIndex = baseOpen.indexOf(targetSidebarId);
|
|
2996
|
+
if (targetIndex === -1) {
|
|
2997
|
+
return;
|
|
2998
|
+
}
|
|
2999
|
+
const insertIndex = position === "before" ? targetIndex : targetIndex + 1;
|
|
3000
|
+
const nextOpen = [...baseOpen];
|
|
3001
|
+
nextOpen.splice(insertIndex, 0, sidebarId);
|
|
3002
|
+
openSidebarsRef.current = nextOpen;
|
|
3003
|
+
setOpenSidebars(nextOpen);
|
|
3004
|
+
startTransition(() => {
|
|
3005
|
+
updateUrl({ sidebar: sidebarUrlValue(nextOpen) });
|
|
3006
|
+
});
|
|
3007
|
+
setSidebarStacks((prev) => {
|
|
3008
|
+
const normalized = normalizeSidebarStacks(nextOpen, prev);
|
|
3009
|
+
const next = normalized
|
|
3010
|
+
.map((stack) => stack.filter((id) => id !== sidebarId))
|
|
3011
|
+
.filter((stack) => stack.length > 0);
|
|
3012
|
+
const targetStackIndex = next.findIndex((stack) => stack.includes(targetSidebarId));
|
|
3013
|
+
if (targetStackIndex === -1) {
|
|
3014
|
+
next.push([sidebarId]);
|
|
3015
|
+
return normalizeSidebarStacks(nextOpen, next);
|
|
3016
|
+
}
|
|
3017
|
+
next.splice(position === "before" ? targetStackIndex : targetStackIndex + 1, 0, [sidebarId]);
|
|
3018
|
+
return normalizeSidebarStacks(nextOpen, next);
|
|
3019
|
+
});
|
|
3020
|
+
}, [normalizeSidebarStacks, updateUrl]);
|
|
2494
3021
|
const unstackSidebar = useCallback((sidebarId) => {
|
|
2495
3022
|
setSidebarStacks((prev) => {
|
|
2496
3023
|
const openIds = openSidebarsRef.current;
|
|
@@ -2536,8 +3063,34 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
2536
3063
|
// Get resolved sidebar (with panels materialized)
|
|
2537
3064
|
// Note: This is defined as a function that will be called later when editContext is available
|
|
2538
3065
|
const sidebars = configuration.editor.sidebars ?? [];
|
|
3066
|
+
const taskboardSidebarIds = new Set([
|
|
3067
|
+
"taskboard-project-list",
|
|
3068
|
+
"taskboard-my-tasks",
|
|
3069
|
+
]);
|
|
3070
|
+
const getSidebarsForWorkspace = useCallback((targetWorkspaceId) => {
|
|
3071
|
+
const isTaskboardWorkspace = targetWorkspaceId === "taskboard";
|
|
3072
|
+
const workspaceAllowedSidebarIds = userInfo.workspaces?.find((w) => w.id === targetWorkspaceId)
|
|
3073
|
+
?.sidebars ?? [];
|
|
3074
|
+
return sidebars.filter((s) => {
|
|
3075
|
+
const isTaskboardSidebar = taskboardSidebarIds.has(s.id);
|
|
3076
|
+
if (isTaskboardWorkspace && !isTaskboardSidebar)
|
|
3077
|
+
return false;
|
|
3078
|
+
if (!isTaskboardWorkspace && isTaskboardSidebar)
|
|
3079
|
+
return false;
|
|
3080
|
+
// Always show agents-panel regardless of workspace settings.
|
|
3081
|
+
if (s.id === "agents-panel") {
|
|
3082
|
+
return true;
|
|
3083
|
+
}
|
|
3084
|
+
// If no workspace settings or no sidebars defined for current workspace, show all.
|
|
3085
|
+
if (workspaceAllowedSidebarIds.length === 0) {
|
|
3086
|
+
return true;
|
|
3087
|
+
}
|
|
3088
|
+
// Only show sidebars that are in the allowed list for the current workspace.
|
|
3089
|
+
return workspaceAllowedSidebarIds.includes(s.id);
|
|
3090
|
+
});
|
|
3091
|
+
}, [sidebars, userInfo.workspaces]);
|
|
2539
3092
|
const getResolvedSidebar = useCallback((sidebarId) => {
|
|
2540
|
-
const sidebar =
|
|
3093
|
+
const sidebar = getSidebarsForWorkspace(workspaceId).find((s) => s.id === sidebarId);
|
|
2541
3094
|
if (!sidebar)
|
|
2542
3095
|
return undefined;
|
|
2543
3096
|
// Resolve panel factories using editContextRef to avoid circular dependency
|
|
@@ -2551,7 +3104,44 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
2551
3104
|
...sidebar,
|
|
2552
3105
|
panels: resolvedPanels,
|
|
2553
3106
|
};
|
|
2554
|
-
}, [
|
|
3107
|
+
}, [getSidebarsForWorkspace, workspaceId]);
|
|
3108
|
+
// Track the last workspaceId this effect saw so we only auto-apply
|
|
3109
|
+
// `currentWorkspace.defaultSidebars` on an actual workspace change. On initial mount
|
|
3110
|
+
// the user's explicit empty state (sidebar=none in URL) must not be overwritten.
|
|
3111
|
+
const prevWorkspaceIdForDefaultsRef = useRef(null);
|
|
3112
|
+
useEffect(() => {
|
|
3113
|
+
if (!currentWorkspace.supportsSidebars) {
|
|
3114
|
+
return;
|
|
3115
|
+
}
|
|
3116
|
+
const prev = prevWorkspaceIdForDefaultsRef.current;
|
|
3117
|
+
prevWorkspaceIdForDefaultsRef.current = workspaceId;
|
|
3118
|
+
const isWorkspaceChange = prev !== null && prev !== workspaceId;
|
|
3119
|
+
const allowedIds = new Set(getSidebarsForWorkspace(workspaceId).map((sidebar) => sidebar.id));
|
|
3120
|
+
const currentOpen = openSidebarsRef.current;
|
|
3121
|
+
let nextOpen = currentOpen.filter((id) => allowedIds.has(id));
|
|
3122
|
+
if (isWorkspaceChange &&
|
|
3123
|
+
nextOpen.length === 0 &&
|
|
3124
|
+
currentWorkspace.defaultSidebars?.length) {
|
|
3125
|
+
nextOpen = currentWorkspace.defaultSidebars.filter((id) => allowedIds.has(id));
|
|
3126
|
+
}
|
|
3127
|
+
const unchanged = nextOpen.length === currentOpen.length &&
|
|
3128
|
+
nextOpen.every((id, index) => id === currentOpen[index]);
|
|
3129
|
+
if (unchanged) {
|
|
3130
|
+
return;
|
|
3131
|
+
}
|
|
3132
|
+
openSidebarsRef.current = nextOpen;
|
|
3133
|
+
setOpenSidebars(nextOpen);
|
|
3134
|
+
setSidebarStacks((prev) => normalizeSidebarStacks(nextOpen, prev));
|
|
3135
|
+
startTransition(() => {
|
|
3136
|
+
updateUrl({ sidebar: sidebarUrlValue(nextOpen) });
|
|
3137
|
+
});
|
|
3138
|
+
}, [
|
|
3139
|
+
currentWorkspace,
|
|
3140
|
+
getSidebarsForWorkspace,
|
|
3141
|
+
normalizeSidebarStacks,
|
|
3142
|
+
updateUrl,
|
|
3143
|
+
workspaceId,
|
|
3144
|
+
]);
|
|
2555
3145
|
// Listen for switch-workspace and open-sidebar commands from agents via websocket
|
|
2556
3146
|
useEffect(() => {
|
|
2557
3147
|
const handleAgentMessage = (message) => {
|
|
@@ -2765,9 +3355,45 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
2765
3355
|
}
|
|
2766
3356
|
return true;
|
|
2767
3357
|
}, [operations, ignoreBlur, sessionId]);
|
|
3358
|
+
const quickSwitcherEntries = useMemo(() => {
|
|
3359
|
+
const entries = [];
|
|
3360
|
+
const seen = new Set();
|
|
3361
|
+
const pushEntry = (entry) => {
|
|
3362
|
+
const key = entry.item
|
|
3363
|
+
? `${entry.workspaceId}:${entry.item.id}:${entry.item.language}:${entry.item.version}`
|
|
3364
|
+
: `${entry.workspaceId}:no-item`;
|
|
3365
|
+
if (seen.has(key))
|
|
3366
|
+
return;
|
|
3367
|
+
seen.add(key);
|
|
3368
|
+
entries.push(entry);
|
|
3369
|
+
};
|
|
3370
|
+
for (const entry of navigationHistory) {
|
|
3371
|
+
pushEntry(entry);
|
|
3372
|
+
}
|
|
3373
|
+
for (const entry of browseHistory) {
|
|
3374
|
+
if (!entry.id || !entry.language)
|
|
3375
|
+
continue;
|
|
3376
|
+
pushEntry({
|
|
3377
|
+
workspaceId,
|
|
3378
|
+
item: {
|
|
3379
|
+
id: entry.id,
|
|
3380
|
+
language: entry.language,
|
|
3381
|
+
version: entry.version ?? 0,
|
|
3382
|
+
},
|
|
3383
|
+
timestamp: entry.visitedAt
|
|
3384
|
+
? new Date(entry.visitedAt).getTime()
|
|
3385
|
+
: Date.now(),
|
|
3386
|
+
displayName: entry.name || workspaceId,
|
|
3387
|
+
itemName: entry.name,
|
|
3388
|
+
itemPath: entry.path,
|
|
3389
|
+
itemIcon: entry.icon,
|
|
3390
|
+
});
|
|
3391
|
+
}
|
|
3392
|
+
return entries.slice(0, 25);
|
|
3393
|
+
}, [navigationHistory, browseHistory, workspaceId]);
|
|
2768
3394
|
// Quick switcher handlers
|
|
2769
3395
|
const showQuickSwitcher = useCallback((show) => {
|
|
2770
|
-
if (show &&
|
|
3396
|
+
if (show && quickSwitcherEntries.length > 1) {
|
|
2771
3397
|
setQuickSwitcherVisible(true);
|
|
2772
3398
|
// Start with index 1 (second entry - previous entry) for quick switching
|
|
2773
3399
|
setQuickSwitcherSelectedIndex(1);
|
|
@@ -2775,11 +3401,11 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
2775
3401
|
else {
|
|
2776
3402
|
setQuickSwitcherVisible(false);
|
|
2777
3403
|
}
|
|
2778
|
-
}, [
|
|
3404
|
+
}, [quickSwitcherEntries]);
|
|
2779
3405
|
const cycleQuickSwitcher = useCallback((direction) => {
|
|
2780
3406
|
if (!quickSwitcherVisible)
|
|
2781
3407
|
return;
|
|
2782
|
-
const maxItems = Math.min(5,
|
|
3408
|
+
const maxItems = Math.min(5, quickSwitcherEntries.length);
|
|
2783
3409
|
setQuickSwitcherSelectedIndex((current) => {
|
|
2784
3410
|
let newIndex = current;
|
|
2785
3411
|
// Determine grid layout (responsive columns)
|
|
@@ -2824,9 +3450,9 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
2824
3450
|
}
|
|
2825
3451
|
return newIndex;
|
|
2826
3452
|
});
|
|
2827
|
-
}, [quickSwitcherVisible,
|
|
3453
|
+
}, [quickSwitcherVisible, quickSwitcherEntries]);
|
|
2828
3454
|
const handleQuickSwitcherSelect = useCallback((index) => {
|
|
2829
|
-
const selectedEntry =
|
|
3455
|
+
const selectedEntry = quickSwitcherEntries[index];
|
|
2830
3456
|
if (selectedEntry) {
|
|
2831
3457
|
// Determine target workspace: entries with items should go to "editor" workspace
|
|
2832
3458
|
// (fixes issue where browse history entries initialized with wrong workspaceId)
|
|
@@ -2878,23 +3504,46 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
2878
3504
|
}
|
|
2879
3505
|
setQuickSwitcherVisible(false);
|
|
2880
3506
|
}, [
|
|
2881
|
-
|
|
3507
|
+
quickSwitcherEntries,
|
|
2882
3508
|
loadItem,
|
|
2883
3509
|
switchWorkspace,
|
|
2884
3510
|
workspaceId,
|
|
2885
3511
|
item,
|
|
2886
3512
|
setNavigationHistory,
|
|
2887
3513
|
]);
|
|
3514
|
+
useEffect(() => {
|
|
3515
|
+
if (typeof window === "undefined" ||
|
|
3516
|
+
process.env.PARHELIA_DEV_MODE !== "true") {
|
|
3517
|
+
return;
|
|
3518
|
+
}
|
|
3519
|
+
window.__parheliaQuickSwitcherTestApi = {
|
|
3520
|
+
open: () => showQuickSwitcher(true),
|
|
3521
|
+
cycle: (direction = "next") => cycleQuickSwitcher(direction),
|
|
3522
|
+
closeWithoutSelection: () => setQuickSwitcherVisible(false),
|
|
3523
|
+
selectCurrent: () => handleQuickSwitcherSelect(quickSwitcherSelectedIndex),
|
|
3524
|
+
getState: () => ({
|
|
3525
|
+
visible: quickSwitcherVisible,
|
|
3526
|
+
selectedIndex: quickSwitcherSelectedIndex,
|
|
3527
|
+
entryCount: quickSwitcherEntries.length,
|
|
3528
|
+
entries: quickSwitcherEntries.map((entry) => ({
|
|
3529
|
+
workspaceId: entry.workspaceId,
|
|
3530
|
+
itemId: entry.item?.id,
|
|
3531
|
+
displayName: entry.displayName,
|
|
3532
|
+
})),
|
|
3533
|
+
}),
|
|
3534
|
+
};
|
|
3535
|
+
return () => {
|
|
3536
|
+
delete window.__parheliaQuickSwitcherTestApi;
|
|
3537
|
+
};
|
|
3538
|
+
}, [
|
|
3539
|
+
showQuickSwitcher,
|
|
3540
|
+
cycleQuickSwitcher,
|
|
3541
|
+
handleQuickSwitcherSelect,
|
|
3542
|
+
quickSwitcherSelectedIndex,
|
|
3543
|
+
]);
|
|
2888
3544
|
const { handleKeyDown } = useKeyboardNavigation({
|
|
2889
3545
|
editContextRef,
|
|
2890
|
-
|
|
2891
|
-
pageViewContext: activePageViewContext,
|
|
2892
|
-
configuration,
|
|
2893
|
-
item,
|
|
2894
|
-
browseHistory,
|
|
2895
|
-
loadItem,
|
|
2896
|
-
showInfoToast,
|
|
2897
|
-
showErrorToast,
|
|
3546
|
+
keyboardCommands: activeKeyboardCommands,
|
|
2898
3547
|
executeCommand,
|
|
2899
3548
|
showQuickSwitcher,
|
|
2900
3549
|
cycleQuickSwitcher,
|
|
@@ -3014,10 +3663,19 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
3014
3663
|
}
|
|
3015
3664
|
return null;
|
|
3016
3665
|
};
|
|
3666
|
+
let modifierWasPressedOnMouseDown = false;
|
|
3667
|
+
const handleMouseDown = (event) => {
|
|
3668
|
+
modifierWasPressedOnMouseDown = event.ctrlKey || event.metaKey;
|
|
3669
|
+
};
|
|
3017
3670
|
const handleCtrlClick = async (event) => {
|
|
3018
|
-
// Only proceed if Ctrl
|
|
3671
|
+
// Only proceed if Ctrl/Cmd was already pressed when the mouse interaction started.
|
|
3672
|
+
// This avoids accidental navigation when users press Ctrl after selecting text to copy.
|
|
3673
|
+
if (!modifierWasPressedOnMouseDown)
|
|
3674
|
+
return;
|
|
3675
|
+
// Also require the modifier to still be held for the final click event.
|
|
3019
3676
|
if (!event.ctrlKey && !event.metaKey)
|
|
3020
3677
|
return;
|
|
3678
|
+
modifierWasPressedOnMouseDown = false;
|
|
3021
3679
|
const target = event.target;
|
|
3022
3680
|
const text = getTextFromElement(target);
|
|
3023
3681
|
if (text && isGuid(text)) {
|
|
@@ -3032,8 +3690,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
3032
3690
|
skipViewChange: true,
|
|
3033
3691
|
});
|
|
3034
3692
|
if (item) {
|
|
3035
|
-
|
|
3036
|
-
switchView("editor", {
|
|
3693
|
+
switchWorkspace("editor", {
|
|
3037
3694
|
skipNavigationHistory: true,
|
|
3038
3695
|
});
|
|
3039
3696
|
showInfoToast({
|
|
@@ -3059,12 +3716,14 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
3059
3716
|
}
|
|
3060
3717
|
};
|
|
3061
3718
|
if (typeof document !== "undefined") {
|
|
3719
|
+
document.addEventListener("mousedown", handleMouseDown, true);
|
|
3062
3720
|
document.addEventListener("click", handleCtrlClick, true);
|
|
3063
3721
|
return () => {
|
|
3722
|
+
document.removeEventListener("mousedown", handleMouseDown, true);
|
|
3064
3723
|
document.removeEventListener("click", handleCtrlClick, true);
|
|
3065
3724
|
};
|
|
3066
3725
|
}
|
|
3067
|
-
}, [loadItem,
|
|
3726
|
+
}, [loadItem, switchWorkspace, showInfoToast, showErrorToast]);
|
|
3068
3727
|
useEffect(() => {
|
|
3069
3728
|
const handleGlobalBlur = () => {
|
|
3070
3729
|
operations.onFieldBlur?.();
|
|
@@ -3102,21 +3761,6 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
3102
3761
|
// Otherwise, only show workspaces that are in the settings
|
|
3103
3762
|
return workspaceIdsFromSettings.includes(w.id) && !w.visible;
|
|
3104
3763
|
});
|
|
3105
|
-
// Get sidebars allowed for the current workspace from settings
|
|
3106
|
-
const currentWorkspaceSettings = userInfo.workspaces?.find((w) => w.id === workspaceId);
|
|
3107
|
-
const allowedSidebarIds = currentWorkspaceSettings?.sidebars ?? [];
|
|
3108
|
-
// Legacy: Calculate visible views for backwards compatibility
|
|
3109
|
-
const allViews = (configuration.editor.views ?? [])
|
|
3110
|
-
.filter((x) => {
|
|
3111
|
-
return !x.visible && !x.hidden;
|
|
3112
|
-
})
|
|
3113
|
-
.filter((x) => !userInfo.views ||
|
|
3114
|
-
userInfo.views.map((view) => view.name).includes(x.name));
|
|
3115
|
-
const pinnedViews = userInfo.preferences?.pinnedViews ||
|
|
3116
|
-
configuration.editor.defaultPinnedViews ||
|
|
3117
|
-
[];
|
|
3118
|
-
// Legacy visibleViews for backwards compatibility
|
|
3119
|
-
const visibleViews = allViews.filter((view) => view.name === viewName || pinnedViews.includes(view.name));
|
|
3120
3764
|
// Handle initial mode setup from URL (only on initial load)
|
|
3121
3765
|
useEffect(() => {
|
|
3122
3766
|
if (!isInitialLoad)
|
|
@@ -3161,7 +3805,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
3161
3805
|
// This is especially important when called from the tour, where the current workspace
|
|
3162
3806
|
// might be the editor and URL-only navigation would get "corrected" back.
|
|
3163
3807
|
try {
|
|
3164
|
-
|
|
3808
|
+
switchWorkspace("home", {
|
|
3165
3809
|
skipConfirmation: true,
|
|
3166
3810
|
skipNavigationHistory: true,
|
|
3167
3811
|
});
|
|
@@ -3208,7 +3852,6 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
3208
3852
|
const current = new URLSearchParams(searchParams.toString());
|
|
3209
3853
|
current.delete("version");
|
|
3210
3854
|
current.delete("itemid");
|
|
3211
|
-
current.delete("view"); // Remove legacy param
|
|
3212
3855
|
current.delete("workspace"); // Clear workspace
|
|
3213
3856
|
current.set("create", "1");
|
|
3214
3857
|
const newUrl = `${pathname}?${current.toString()}`;
|
|
@@ -3488,6 +4131,8 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
3488
4131
|
setShowOnlyMyChanges,
|
|
3489
4132
|
filterByCurrentLanguage,
|
|
3490
4133
|
setFilterByCurrentLanguage,
|
|
4134
|
+
historySearchQuery,
|
|
4135
|
+
setHistorySearchQuery,
|
|
3491
4136
|
refreshHistory,
|
|
3492
4137
|
isRefreshing,
|
|
3493
4138
|
activeSessions,
|
|
@@ -3525,19 +4170,8 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
3525
4170
|
workspaceId,
|
|
3526
4171
|
previousWorkspaceId,
|
|
3527
4172
|
switchWorkspace,
|
|
3528
|
-
// Sidebar state
|
|
3529
|
-
availableSidebars: (
|
|
3530
|
-
// Always show agents-panel regardless of workspace settings
|
|
3531
|
-
if (s.id === "agents-panel") {
|
|
3532
|
-
return true;
|
|
3533
|
-
}
|
|
3534
|
-
// If no workspace settings or no sidebars defined for current workspace, show all
|
|
3535
|
-
if (!allowedSidebarIds || allowedSidebarIds.length === 0) {
|
|
3536
|
-
return true;
|
|
3537
|
-
}
|
|
3538
|
-
// Only show sidebars that are in the allowed list for the current workspace
|
|
3539
|
-
return allowedSidebarIds.includes(s.id);
|
|
3540
|
-
}),
|
|
4173
|
+
// Sidebar state
|
|
4174
|
+
availableSidebars: getSidebarsForWorkspace(workspaceId),
|
|
3541
4175
|
openSidebars,
|
|
3542
4176
|
pinnedSidebars,
|
|
3543
4177
|
lockedSidebars,
|
|
@@ -3547,17 +4181,12 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
3547
4181
|
toggleSidebarPin,
|
|
3548
4182
|
toggleSidebarLock,
|
|
3549
4183
|
stackSidebar,
|
|
4184
|
+
moveSidebarToColumn,
|
|
3550
4185
|
unstackSidebar,
|
|
3551
4186
|
reorderSidebarInStack,
|
|
3552
4187
|
reorderPinnedSidebars,
|
|
3553
4188
|
reorderOpenSidebars,
|
|
3554
4189
|
getResolvedSidebar,
|
|
3555
|
-
// Legacy compatibility (deprecated)
|
|
3556
|
-
viewName,
|
|
3557
|
-
previousViewName,
|
|
3558
|
-
switchView,
|
|
3559
|
-
view: currentView,
|
|
3560
|
-
visibleViews,
|
|
3561
4190
|
compareMode,
|
|
3562
4191
|
setCompareMode,
|
|
3563
4192
|
fullscreen,
|
|
@@ -3597,6 +4226,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
3597
4226
|
addSocketMessageListener,
|
|
3598
4227
|
sendSocketMessage,
|
|
3599
4228
|
socketConnectionVersion,
|
|
4229
|
+
socketDiagnostics,
|
|
3600
4230
|
currentItemDescriptor,
|
|
3601
4231
|
editorSlots,
|
|
3602
4232
|
activeSlotId,
|
|
@@ -3609,6 +4239,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
3609
4239
|
setActiveSlot,
|
|
3610
4240
|
revision,
|
|
3611
4241
|
notifyPageModelReady,
|
|
4242
|
+
pageModelReadyToken,
|
|
3612
4243
|
selectedComment,
|
|
3613
4244
|
setSelectedComment,
|
|
3614
4245
|
comments,
|
|
@@ -3687,8 +4318,18 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
3687
4318
|
setEnableCompletions,
|
|
3688
4319
|
showComponentNavigator,
|
|
3689
4320
|
setShowComponentNavigator: handleSetShowComponentNavigator,
|
|
4321
|
+
isComponentNavigatorOpenForSlot,
|
|
4322
|
+
setComponentNavigatorOpenForSlot,
|
|
3690
4323
|
showAgentsPanel,
|
|
3691
4324
|
setShowAgentsPanel: handleSetShowAgentsPanel,
|
|
4325
|
+
editorFormHidden,
|
|
4326
|
+
setEditorFormHidden: handleSetEditorFormHidden,
|
|
4327
|
+
editorFormHintSeen,
|
|
4328
|
+
markEditorFormHintSeen,
|
|
4329
|
+
editorFormHintVisible,
|
|
4330
|
+
showEditorFormHint,
|
|
4331
|
+
dismissEditorFormHint,
|
|
4332
|
+
editorFormToggleButtonRef,
|
|
3692
4333
|
showMinimap,
|
|
3693
4334
|
setShowMinimap: handleSetShowMinimap,
|
|
3694
4335
|
showHelpTerminal,
|
|
@@ -3698,8 +4339,12 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
3698
4339
|
helpTerminalProfileName,
|
|
3699
4340
|
helpTerminalActiveTab,
|
|
3700
4341
|
setHelpTerminalActiveTab,
|
|
4342
|
+
selectedHelpSectionId,
|
|
4343
|
+
setSelectedHelpSectionId,
|
|
3701
4344
|
showAgentsWorkspaceEditor,
|
|
3702
4345
|
setShowAgentsWorkspaceEditor: handleSetShowAgentsWorkspaceEditor,
|
|
4346
|
+
selectedAgentsWorkspaceAgentId,
|
|
4347
|
+
setSelectedAgentsWorkspaceAgentId,
|
|
3703
4348
|
activeEditorTab,
|
|
3704
4349
|
setActiveEditorTab,
|
|
3705
4350
|
showLayoutComponents,
|
|
@@ -3708,6 +4353,8 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
3708
4353
|
isQuotaExceeded: isQuotaExceeded(),
|
|
3709
4354
|
getQuotaWarningMessage,
|
|
3710
4355
|
isMobile,
|
|
4356
|
+
mobileEditorPanelOpen,
|
|
4357
|
+
setMobileEditorPanelOpen: handleSetMobileEditorPanelOpen,
|
|
3711
4358
|
openDialog,
|
|
3712
4359
|
webSocketMessages,
|
|
3713
4360
|
clearWebSocketMessages: () => setWebSocketMessages([]),
|
|
@@ -3746,7 +4393,6 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
3746
4393
|
configuration,
|
|
3747
4394
|
updateUrl,
|
|
3748
4395
|
workspaceId,
|
|
3749
|
-
switchView,
|
|
3750
4396
|
pathname,
|
|
3751
4397
|
router,
|
|
3752
4398
|
item,
|
|
@@ -3776,7 +4422,6 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
3776
4422
|
currentWorkspace,
|
|
3777
4423
|
previousWorkspaceId,
|
|
3778
4424
|
switchWorkspace,
|
|
3779
|
-
allowedSidebarIds,
|
|
3780
4425
|
openSidebars,
|
|
3781
4426
|
pinnedSidebars,
|
|
3782
4427
|
lockedSidebars,
|
|
@@ -3788,9 +4433,6 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
3788
4433
|
reorderOpenSidebars,
|
|
3789
4434
|
getResolvedSidebar,
|
|
3790
4435
|
viewName,
|
|
3791
|
-
previousViewName,
|
|
3792
|
-
currentView,
|
|
3793
|
-
visibleViews,
|
|
3794
4436
|
compareMode,
|
|
3795
4437
|
// Important: in multi-slot mode the active PageViewContext can change
|
|
3796
4438
|
// without the base `pageViewContext` identity changing (e.g. switching slots).
|
|
@@ -3815,6 +4457,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
3815
4457
|
currentItemDescriptor,
|
|
3816
4458
|
revision,
|
|
3817
4459
|
notifyPageModelReady,
|
|
4460
|
+
pageModelReadyToken,
|
|
3818
4461
|
selectedComment,
|
|
3819
4462
|
comments,
|
|
3820
4463
|
availableCommentTags,
|
|
@@ -3833,6 +4476,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
3833
4476
|
quickSwitcherSelectedIndex,
|
|
3834
4477
|
handleQuickSwitcherSelect,
|
|
3835
4478
|
webSocketMessages,
|
|
4479
|
+
socketDiagnostics,
|
|
3836
4480
|
factoriesRef,
|
|
3837
4481
|
user,
|
|
3838
4482
|
statusMessage,
|
|
@@ -3841,10 +4485,22 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
3841
4485
|
isQuotaExceeded,
|
|
3842
4486
|
getQuotaWarningMessage,
|
|
3843
4487
|
isMobile,
|
|
4488
|
+
mobileEditorPanelOpen,
|
|
4489
|
+
handleSetMobileEditorPanelOpen,
|
|
3844
4490
|
showComponentNavigator,
|
|
4491
|
+
isComponentNavigatorOpenForSlot,
|
|
4492
|
+
setComponentNavigatorOpenForSlot,
|
|
3845
4493
|
handleSetShowComponentNavigator,
|
|
3846
4494
|
showAgentsPanel,
|
|
3847
4495
|
handleSetShowAgentsPanel,
|
|
4496
|
+
editorFormHidden,
|
|
4497
|
+
handleSetEditorFormHidden,
|
|
4498
|
+
editorFormHintSeen,
|
|
4499
|
+
markEditorFormHintSeen,
|
|
4500
|
+
editorFormHintVisible,
|
|
4501
|
+
showEditorFormHint,
|
|
4502
|
+
dismissEditorFormHint,
|
|
4503
|
+
editorFormToggleButtonRef,
|
|
3848
4504
|
showMinimap,
|
|
3849
4505
|
handleSetShowMinimap,
|
|
3850
4506
|
showHelpTerminal,
|
|
@@ -3853,7 +4509,9 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
3853
4509
|
helpTerminalProfileName,
|
|
3854
4510
|
helpTerminalActiveTab,
|
|
3855
4511
|
setHelpTerminalActiveTab,
|
|
4512
|
+
selectedHelpSectionId,
|
|
3856
4513
|
showAgentsWorkspaceEditor,
|
|
4514
|
+
selectedAgentsWorkspaceAgentId,
|
|
3857
4515
|
activeEditorTab,
|
|
3858
4516
|
showLayoutComponents,
|
|
3859
4517
|
openDialog,
|
|
@@ -4098,18 +4756,40 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
4098
4756
|
// prevDependencies.current = currentDependencies;
|
|
4099
4757
|
// editContextRef.current = editContext;
|
|
4100
4758
|
// }, [editContext]);
|
|
4759
|
+
// Auto-open the mobile editor panel for new selection/intent changes, but
|
|
4760
|
+
// keep a manual close sticky for the exact same context.
|
|
4761
|
+
useEffect(() => {
|
|
4762
|
+
if (!isMobile || workspaceId !== "editor")
|
|
4763
|
+
return;
|
|
4764
|
+
if (activeEditorTab) {
|
|
4765
|
+
handleSetMobileEditorPanelOpen(true);
|
|
4766
|
+
return;
|
|
4767
|
+
}
|
|
4768
|
+
if (!(selection.length > 0 || insertMode))
|
|
4769
|
+
return;
|
|
4770
|
+
if (dismissedMobilePanelToken === mobilePanelDismissToken)
|
|
4771
|
+
return;
|
|
4772
|
+
handleSetMobileEditorPanelOpen(true);
|
|
4773
|
+
}, [
|
|
4774
|
+
activeEditorTab,
|
|
4775
|
+
dismissedMobilePanelToken,
|
|
4776
|
+
handleSetMobileEditorPanelOpen,
|
|
4777
|
+
insertMode,
|
|
4778
|
+
isMobile,
|
|
4779
|
+
mobilePanelDismissToken,
|
|
4780
|
+
selection,
|
|
4781
|
+
workspaceId,
|
|
4782
|
+
]);
|
|
4101
4783
|
useEffect(() => {
|
|
4102
4784
|
fieldsEditContext.clearModifiedFields();
|
|
4103
4785
|
}, [currentItemDescriptor]);
|
|
4104
|
-
if (!
|
|
4786
|
+
if (!currentWorkspace)
|
|
4105
4787
|
return null;
|
|
4106
|
-
const editorUi = fullscreen ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "fixed inset-0 flex", children: [_jsx(PageViewerFrame, { compareView: compareMode, pageViewContext: activePageViewContext }), _jsx(FullscreenControls, { device: activePageViewContext.device, setDevice: (d) => activePageViewContext.setDevice(d), canExit: !configuration.forceFullscreen, onExit: () => setFullscreen(false), firstMobileDeviceName: configuration.devices[0]?.name })] }), showFullscreenHint && !configuration.forceFullscreen && (_jsx("div", { className: "fixed inset-0", onMouseMoveCapture: () => {
|
|
4788
|
+
const editorUi = fullscreen ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "fixed inset-0 flex", children: [_jsx(PageViewerFrame, { compareView: compareMode, pageViewContext: activePageViewContext }), _jsx(FullscreenControls, { device: activePageViewContext.device, setDevice: (d) => activePageViewContext.setDevice(d), canExit: !configuration.forceFullscreen, onExit: () => setFullscreen(false), firstMobileDeviceName: configuration.devices[0]?.name })] }), showFullscreenHint && !configuration.forceFullscreen && !isMobile && (_jsx("div", { className: "fixed inset-0 z-10000", onMouseMoveCapture: () => {
|
|
4107
4789
|
setTimeout(() => {
|
|
4108
4790
|
setShowFullscreenHint(false);
|
|
4109
4791
|
}, 600);
|
|
4110
|
-
}, "data-testid": "fullscreen-hint-overlay", children: _jsxs("div", { className: "fixed top-6 left-1/2 -translate-x-1/2 transform rounded-full bg-black/60 px-6 py-2.5 text-sm font-medium text-white shadow-2xl backdrop-blur-md transition-all duration-500", children: ["Press", " ", _jsx("kbd", { className: "mx-1 rounded bg-white/20 px-1.5 py-0.5 text-xs font-semibold", children: "Ctrl + F11" }), " ", "to exit fullscreen"] }) }))] })) : (_jsxs(_Fragment, { children: [_jsx(EditorChrome, { className: className, currentWorkspace: currentWorkspace, centerPanelView: centerPanelView, editContext: editContext,
|
|
4111
|
-
// Legacy props for backwards compatibility
|
|
4112
|
-
currentView: currentView, viewName: viewName }), isTourActive && (_jsx(Tour, { tourStopCallback: () => {
|
|
4792
|
+
}, "data-testid": "fullscreen-hint-overlay", children: _jsxs("div", { className: "fixed top-6 left-1/2 -translate-x-1/2 transform rounded-full bg-black/60 px-6 py-2.5 text-sm font-medium text-white shadow-2xl backdrop-blur-md transition-all duration-500", children: ["Press", " ", _jsx("kbd", { className: "mx-1 rounded bg-white/20 px-1.5 py-0.5 text-xs font-semibold", children: "Ctrl + F11" }), " ", "to exit fullscreen"] }) }))] })) : (_jsxs(_Fragment, { children: [_jsx(EditorChrome, { className: className, currentWorkspace: currentWorkspace, centerPanelView: centerPanelView, editContext: editContext, showAgentsPanel: showAgentsPanel, handleSetShowAgentsPanel: handleSetShowAgentsPanel, workspaceId: workspaceId }), isTourActive && (_jsx(Tour, { tourStopCallback: () => {
|
|
4113
4793
|
setIsTourActive(false);
|
|
4114
4794
|
// Remove tour state from URL
|
|
4115
4795
|
// Use history.replaceState instead of router.replace to avoid triggering React navigation
|
|
@@ -4119,8 +4799,14 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
|
|
|
4119
4799
|
const newUrl = queryString
|
|
4120
4800
|
? `${window.location.pathname}?${queryString}`
|
|
4121
4801
|
: window.location.pathname;
|
|
4122
|
-
window.history.replaceState(
|
|
4123
|
-
}, configuration: configuration, restoredFromUrl: tourRestoredRef.current })), _jsx(GuidanceOverlay, {}), _jsx(
|
|
4124
|
-
return (_jsx("div", { className: `editor h-full w-full`, children: _jsx(OperationsContextProvider, { value: operationsContext.context, children: _jsx(FieldsEditContextProvider, { value: fieldsEditContext, children: _jsxs(EditContextProvider, { value: editContext, children: [_jsx(DevModeIndicator, {}), startupChecks.state === "loading" && (_jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-white/70 backdrop-blur-[1px]", children: _jsx("div", { className: "flex items-center gap-3 rounded-md border border-gray-200 bg-white px-4 py-3 text-gray-700 shadow-sm", children: _jsx(Spinner, { size: "xl" }) }) })), editContext.isRefreshing && (_jsx("div", { className: "pointer-events-none fixed right-0 bottom-0 flex h-24 w-24 items-center justify-center text-gray-600 opacity-50 select-none", children: _jsx(Spinner, {}) })),
|
|
4802
|
+
window.history.replaceState(getCurrentHistoryState(), "", newUrl);
|
|
4803
|
+
}, configuration: configuration, restoredFromUrl: tourRestoredRef.current })), _jsx(FeatureGate, { feature: LicenseFeatures.AI, children: _jsx(GuidanceOverlay, {}) }), _jsx(FeatureGate, { feature: LicenseFeatures.AI, children: _jsx(AgentDialogHandler, {}) })] }));
|
|
4804
|
+
return (_jsx(LicenseProvider, { initialLicenseStatus: initialLicenseStatus, initialStatusLoaded: initialLicenseStatusLoaded, children: _jsx("div", { className: `editor h-full w-full`, children: _jsx(OperationsContextProvider, { value: operationsContext.context, children: _jsx(FieldsEditContextProvider, { value: fieldsEditContext, children: _jsxs(EditContextProvider, { value: editContext, children: [_jsx(DevModeIndicator, {}), startupChecks.state === "loading" && (_jsx("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-white/70 backdrop-blur-[1px]", children: _jsx("div", { className: "flex items-center gap-3 rounded-md border border-gray-200 bg-white px-4 py-3 text-gray-700 shadow-sm", children: _jsx(Spinner, { size: "xl" }) }) })), editContext.isRefreshing && (_jsx("div", { className: "pointer-events-none fixed right-0 bottom-0 flex h-24 w-24 items-center justify-center text-gray-600 opacity-50 select-none", children: _jsx(Spinner, {}) })), (currentWorkspace.id === "agents" ||
|
|
4805
|
+
currentWorkspace.id === "taskboard") &&
|
|
4806
|
+
showAgentsWorkspaceEditor && (_jsx(AgentsSlotContextBridgeHost, { slots: editorSlots })), startupChecks.state !== "loading" && (children || editorUi), startupChecks.state !== "loading" && dialog, _jsx(Toaster, { position: "top-center" }), " ", _jsx(ConfirmationDialog, { ref: confirmationDialogRef }), _jsx(ConcurrentUserLimitDialog, { open: concurrentUserLimitError !== null, onOpenChange: (open) => {
|
|
4807
|
+
if (!open) {
|
|
4808
|
+
setConcurrentUserLimitError(null);
|
|
4809
|
+
}
|
|
4810
|
+
}, sessionId: sessionId, currentUsers: concurrentUserLimitError?.currentUsers ?? 0, maxUsers: concurrentUserLimitError?.maxUsers ?? 0, message: concurrentUserLimitError?.message ?? "", onRetry: handleRetryConnection, isAdministrator: userInfo.user.isAdministrator === true }), _jsx(QuickItemSwitcher, { visible: quickSwitcherVisible, entries: quickSwitcherEntries.slice(0, 5), selectedIndex: quickSwitcherSelectedIndex, onSelect: handleQuickSwitcherSelect, onClose: () => setQuickSwitcherVisible(false) }), _jsx(EditContextMenu, { ref: contextMenuRef }), _jsx(EditorFormHintPopover, {}), _jsx(FeatureGate, { feature: LicenseFeatures.AI, children: _jsx(InlineAiTrigger, {}) }), media.mediaSelectorVisible && (_jsx(MediaSelector, { language: editContext.currentItemDescriptor.language, visible: media.mediaSelectorVisible, onHide: media.handleHide, onMediaSelected: media.onMediaSelect, selectedIdPath: media.selectedMediaIdPath, mode: media.mediaSelectorMode, initialSearchTerm: media.initialSearchTerm })), _jsx(FieldEditorPopup, { ref: fieldEditorPopupRef }), _jsx(LicenseOverlay, {})] }) }) }) }) }));
|
|
4125
4811
|
}
|
|
4126
4812
|
//# sourceMappingURL=EditorShell.js.map
|