@parhelia/core 0.1.12882 → 0.1.12883
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/AgentsSidebar.js +1 -1
- package/dist/agents-view/AgentsSidebar.js.map +1 -1
- package/dist/agents-view/AgentsTitlebar.d.ts +1 -1
- package/dist/agents-view/AgentsTitlebar.js +3 -6
- package/dist/agents-view/AgentsTitlebar.js.map +1 -1
- package/dist/agents-view/AgentsView.d.ts +2 -2
- package/dist/agents-view/AgentsView.js +2 -2
- package/dist/agents-view/AgentsView.js.map +1 -1
- package/dist/agents-view/AgentsWorkspaceView.js +1 -12
- package/dist/agents-view/AgentsWorkspaceView.js.map +1 -1
- package/dist/agents-view/CreateAgentView.d.ts +1 -1
- package/dist/agents-view/CreateAgentView.js +1 -1
- package/dist/agents-view/DateAgentsGroup.js +12 -1
- package/dist/agents-view/DateAgentsGroup.js.map +1 -1
- package/dist/agents-view/ProfileAgentsGroup.js +16 -4
- package/dist/agents-view/ProfileAgentsGroup.js.map +1 -1
- package/dist/components/ui/card.d.ts +3 -1
- package/dist/components/ui/card.js +2 -2
- package/dist/components/ui/card.js.map +1 -1
- package/dist/components/ui/checkbox.js +1 -1
- package/dist/components/ui/checkbox.js.map +1 -1
- package/dist/components/ui/context-menu.d.ts +2 -1
- package/dist/components/ui/context-menu.js +6 -3
- package/dist/components/ui/context-menu.js.map +1 -1
- package/dist/components/ui/input.js +2 -2
- package/dist/components/ui/input.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/textarea.js +2 -2
- package/dist/components/ui/textarea.js.map +1 -1
- package/dist/config/config.js +91 -12
- package/dist/config/config.js.map +1 -1
- package/dist/editor/ContextMenu.d.ts +1 -0
- package/dist/editor/ContextMenu.js +4 -4
- package/dist/editor/ContextMenu.js.map +1 -1
- package/dist/editor/FieldActionsOverlay.d.ts +0 -1
- package/dist/editor/FieldActionsOverlay.js +1 -45
- package/dist/editor/FieldActionsOverlay.js.map +1 -1
- package/dist/editor/FieldHistory.d.ts +2 -1
- package/dist/editor/FieldHistory.js +13 -12
- package/dist/editor/FieldHistory.js.map +1 -1
- package/dist/editor/FieldListField.d.ts +1 -1
- package/dist/editor/FieldListField.js +24 -36
- package/dist/editor/FieldListField.js.map +1 -1
- package/dist/editor/ImageEditor.d.ts +6 -1
- package/dist/editor/ImageEditor.js +19 -3
- package/dist/editor/ImageEditor.js.map +1 -1
- package/dist/editor/LinkEditorDialog.d.ts +9 -2
- package/dist/editor/LinkEditorDialog.js +174 -70
- package/dist/editor/LinkEditorDialog.js.map +1 -1
- package/dist/editor/MainLayout.js +49 -6
- package/dist/editor/MainLayout.js.map +1 -1
- package/dist/editor/MobileLayout.js +33 -1
- package/dist/editor/MobileLayout.js.map +1 -1
- package/dist/editor/PictureCropper.js +45 -28
- package/dist/editor/PictureCropper.js.map +1 -1
- package/dist/editor/PictureEditor.d.ts +2 -1
- package/dist/editor/PictureEditor.js +5 -14
- package/dist/editor/PictureEditor.js.map +1 -1
- package/dist/editor/ai/AgentProfileSelector.js +7 -7
- package/dist/editor/ai/AgentProfileSelector.js.map +1 -1
- package/dist/editor/ai/Agents.js +20 -6
- package/dist/editor/ai/Agents.js.map +1 -1
- package/dist/editor/ai/GuidanceOverlay.js +1 -11
- package/dist/editor/ai/GuidanceOverlay.js.map +1 -1
- package/dist/editor/ai/InlineAiDialog.d.ts +1 -0
- package/dist/editor/ai/InlineAiDialog.js +254 -202
- package/dist/editor/ai/InlineAiDialog.js.map +1 -1
- package/dist/editor/ai/InlineAiTextEditTooltip.d.ts +8 -0
- package/dist/editor/ai/InlineAiTextEditTooltip.js +10 -0
- package/dist/editor/ai/InlineAiTextEditTooltip.js.map +1 -0
- package/dist/editor/ai/InlineAiTrigger.js +158 -31
- package/dist/editor/ai/InlineAiTrigger.js.map +1 -1
- package/dist/editor/ai/dialogs/capturePageDom.js +66 -36
- package/dist/editor/ai/dialogs/capturePageDom.js.map +1 -1
- package/dist/editor/ai/dialogs/capturePageScreenshot.js +281 -162
- package/dist/editor/ai/dialogs/capturePageScreenshot.js.map +1 -1
- package/dist/editor/ai/inlineAiTextEditLabels.d.ts +2 -0
- package/dist/editor/ai/inlineAiTextEditLabels.js +8 -0
- package/dist/editor/ai/inlineAiTextEditLabels.js.map +1 -0
- package/dist/editor/ai/prepareInlineAiTextSelection.d.ts +5 -0
- package/dist/editor/ai/prepareInlineAiTextSelection.js +86 -0
- package/dist/editor/ai/prepareInlineAiTextSelection.js.map +1 -0
- package/dist/editor/ai/terminal/agentSessionState.d.ts +3 -0
- package/dist/editor/ai/terminal/agentSessionState.js +3 -1
- package/dist/editor/ai/terminal/agentSessionState.js.map +1 -1
- package/dist/editor/ai/terminal/agentStartRequest.d.ts +2 -1
- package/dist/editor/ai/terminal/agentStartRequest.js +2 -1
- package/dist/editor/ai/terminal/agentStartRequest.js.map +1 -1
- package/dist/editor/ai/terminal/components/AgentCostDisplay.js +1 -1
- package/dist/editor/ai/terminal/components/AgentCostDisplay.js.map +1 -1
- package/dist/editor/ai/terminal/components/AgentDocumentList.d.ts +7 -0
- package/dist/editor/ai/terminal/components/AgentDocumentList.js +55 -13
- package/dist/editor/ai/terminal/components/AgentDocumentList.js.map +1 -1
- package/dist/editor/ai/terminal/components/AgentEditHistoryButton.d.ts +5 -0
- package/dist/editor/ai/terminal/components/AgentEditHistoryButton.js +12 -0
- package/dist/editor/ai/terminal/components/AgentEditHistoryButton.js.map +1 -0
- package/dist/editor/ai/terminal/components/AgentFullPromptControls.d.ts +3 -1
- package/dist/editor/ai/terminal/components/AgentFullPromptControls.js +22 -14
- package/dist/editor/ai/terminal/components/AgentFullPromptControls.js.map +1 -1
- package/dist/editor/ai/terminal/components/AgentModeSelector.js +4 -4
- package/dist/editor/ai/terminal/components/AgentModeSelector.js.map +1 -1
- package/dist/editor/ai/terminal/components/AgentPromptActionButtons.js +4 -4
- package/dist/editor/ai/terminal/components/AgentPromptActionButtons.js.map +1 -1
- package/dist/editor/ai/terminal/components/AgentPromptComposer.js +1 -1
- package/dist/editor/ai/terminal/components/AgentPromptComposer.js.map +1 -1
- package/dist/editor/ai/terminal/components/AgentPromptInputArea.d.ts +2 -1
- package/dist/editor/ai/terminal/components/AgentPromptInputArea.js +8 -11
- package/dist/editor/ai/terminal/components/AgentPromptInputArea.js.map +1 -1
- package/dist/editor/ai/terminal/components/AgentPromptTrayPopovers.d.ts +1 -4
- package/dist/editor/ai/terminal/components/AgentPromptTrayPopovers.js +31 -14
- package/dist/editor/ai/terminal/components/AgentPromptTrayPopovers.js.map +1 -1
- package/dist/editor/ai/terminal/components/AgentSettingsPopover.js +1 -1
- package/dist/editor/ai/terminal/components/AgentSettingsPopover.js.map +1 -1
- package/dist/editor/ai/terminal/components/AgentTerminalFullLayout.d.ts +2 -1
- package/dist/editor/ai/terminal/components/AgentTerminalFullLayout.js +2 -4
- package/dist/editor/ai/terminal/components/AgentTerminalFullLayout.js.map +1 -1
- package/dist/editor/ai/terminal/components/AgentTerminalMessageGroups.js +1 -1
- package/dist/editor/ai/terminal/components/AgentTerminalMessageGroups.js.map +1 -1
- package/dist/editor/ai/terminal/components/AgentTerminalView.js +13 -2
- package/dist/editor/ai/terminal/components/AgentTerminalView.js.map +1 -1
- package/dist/editor/ai/terminal/components/AiResponseMessage.js +11 -9
- package/dist/editor/ai/terminal/components/AiResponseMessage.js.map +1 -1
- package/dist/editor/ai/terminal/components/ContextInfoBar.js +22 -2
- package/dist/editor/ai/terminal/components/ContextInfoBar.js.map +1 -1
- package/dist/editor/ai/terminal/components/QueuedPromptsPanel.js +37 -26
- package/dist/editor/ai/terminal/components/QueuedPromptsPanel.js.map +1 -1
- package/dist/editor/ai/terminal/components/ToolCallDisplay.js +3 -1
- package/dist/editor/ai/terminal/components/ToolCallDisplay.js.map +1 -1
- package/dist/editor/ai/terminal/components/UserMessage.d.ts +2 -1
- package/dist/editor/ai/terminal/components/UserMessage.js +144 -8
- package/dist/editor/ai/terminal/components/UserMessage.js.map +1 -1
- package/dist/editor/ai/terminal/useAgentPromptComposerHandlers.js +1 -1
- package/dist/editor/ai/terminal/useAgentPromptComposerHandlers.js.map +1 -1
- package/dist/editor/ai/terminal/useAgentSessionSync.d.ts +1 -0
- package/dist/editor/ai/terminal/useAgentSubmitHandlers.d.ts +3 -1
- package/dist/editor/ai/terminal/useAgentSubmitHandlers.js +9 -3
- package/dist/editor/ai/terminal/useAgentSubmitHandlers.js.map +1 -1
- package/dist/editor/ai/terminal/useAgentTerminalController.js +7 -0
- package/dist/editor/ai/terminal/useAgentTerminalController.js.map +1 -1
- package/dist/editor/ai/terminal/useAgentTerminalUiState.js +1 -1
- package/dist/editor/ai/terminal/useAgentTerminalUiState.js.map +1 -1
- package/dist/editor/ai/terminal/useAgentUserMessageSocketHandler.js +3 -1
- package/dist/editor/ai/terminal/useAgentUserMessageSocketHandler.js.map +1 -1
- package/dist/editor/ai/useActiveAgentConversation.d.ts +3 -0
- package/dist/editor/ai/useActiveAgentConversation.js +32 -0
- package/dist/editor/ai/useActiveAgentConversation.js.map +1 -0
- package/dist/editor/ai/useInlineAiPosition.d.ts +10 -2
- package/dist/editor/ai/useInlineAiPosition.js +32 -71
- package/dist/editor/ai/useInlineAiPosition.js.map +1 -1
- package/dist/editor/ai-image-editor/AiImageResultOverlay.js +30 -62
- package/dist/editor/ai-image-editor/AiImageResultOverlay.js.map +1 -1
- package/dist/editor/bridge/BridgeClient.d.ts +80 -0
- package/dist/editor/bridge/BridgeClient.js +417 -0
- package/dist/editor/bridge/BridgeClient.js.map +1 -0
- package/dist/editor/client/EditorShell.d.ts +5 -1
- package/dist/editor/client/EditorShell.js +295 -127
- package/dist/editor/client/EditorShell.js.map +1 -1
- package/dist/editor/client/editContext.d.ts +58 -5
- package/dist/editor/client/editContext.js.map +1 -1
- package/dist/editor/client/fieldModificationStore.d.ts +1 -0
- package/dist/editor/client/fieldModificationStore.js +7 -2
- package/dist/editor/client/fieldModificationStore.js.map +1 -1
- package/dist/editor/client/hooks/useSocketMessageHandler.js +14 -17
- package/dist/editor/client/hooks/useSocketMessageHandler.js.map +1 -1
- package/dist/editor/client/itemsRepository.d.ts +2 -0
- package/dist/editor/client/itemsRepository.js +18 -9
- package/dist/editor/client/itemsRepository.js.map +1 -1
- package/dist/editor/client/operations.d.ts +1 -1
- package/dist/editor/client/operations.js +67 -21
- package/dist/editor/client/operations.js.map +1 -1
- package/dist/editor/client/pageModelBuilder.js +24 -7
- package/dist/editor/client/pageModelBuilder.js.map +1 -1
- package/dist/editor/client/ui/EditorChrome.js +1 -1
- package/dist/editor/client/ui/EditorChrome.js.map +1 -1
- package/dist/editor/commands/componentCommands.d.ts +3 -1
- package/dist/editor/commands/componentCommands.js +8 -3
- package/dist/editor/commands/componentCommands.js.map +1 -1
- package/dist/editor/field-types/DateFieldEditor.js +1 -1
- package/dist/editor/field-types/DateFieldEditor.js.map +1 -1
- package/dist/editor/field-types/DateTimeFieldEditor.js +1 -1
- package/dist/editor/field-types/DateTimeFieldEditor.js.map +1 -1
- package/dist/editor/field-types/DropLinkEditor.js +1 -1
- package/dist/editor/field-types/DropLinkEditor.js.map +1 -1
- package/dist/editor/field-types/DropListEditor.js +1 -1
- package/dist/editor/field-types/DropListEditor.js.map +1 -1
- package/dist/editor/field-types/ImageFieldEditor.js +1 -1
- package/dist/editor/field-types/ImageFieldEditor.js.map +1 -1
- package/dist/editor/field-types/InternalLinkFieldEditor.js +1 -1
- package/dist/editor/field-types/InternalLinkFieldEditor.js.map +1 -1
- package/dist/editor/field-types/LinkFieldEditor.js +15 -3
- package/dist/editor/field-types/LinkFieldEditor.js.map +1 -1
- package/dist/editor/field-types/MultiLineText.js +11 -4
- package/dist/editor/field-types/MultiLineText.js.map +1 -1
- package/dist/editor/field-types/NameValueListEditor.js +1 -1
- package/dist/editor/field-types/NameValueListEditor.js.map +1 -1
- package/dist/editor/field-types/PictureFieldEditor.js +2 -2
- package/dist/editor/field-types/PictureFieldEditor.js.map +1 -1
- package/dist/editor/field-types/RawEditor.js +9 -2
- package/dist/editor/field-types/RawEditor.js.map +1 -1
- package/dist/editor/field-types/RichTextEditorComponent.js +170 -77
- package/dist/editor/field-types/RichTextEditorComponent.js.map +1 -1
- package/dist/editor/field-types/SingleLineText.js +10 -3
- package/dist/editor/field-types/SingleLineText.js.map +1 -1
- package/dist/editor/field-types/TreeListEditor.js +1 -1
- package/dist/editor/field-types/TreeListEditor.js.map +1 -1
- package/dist/editor/field-types/richtext/bridgeRichTextProfile.d.ts +21 -0
- package/dist/editor/field-types/richtext/bridgeRichTextProfile.js +96 -0
- package/dist/editor/field-types/richtext/bridgeRichTextProfile.js.map +1 -0
- package/dist/editor/field-types/richtext/components/ReactSlate.css +44 -6
- package/dist/editor/field-types/richtext/components/ReactSlate.js +191 -36
- package/dist/editor/field-types/richtext/components/ReactSlate.js.map +1 -1
- package/dist/editor/field-types/richtext/components/SimpleRichTextEditor.css +5 -2
- package/dist/editor/field-types/richtext/components/SimpleToolbar.js +5 -4
- package/dist/editor/field-types/richtext/components/SimpleToolbar.js.map +1 -1
- package/dist/editor/field-types/richtext/contextMenuFactory.d.ts +2 -15
- package/dist/editor/field-types/richtext/contextMenuFactory.js +4 -435
- package/dist/editor/field-types/richtext/contextMenuFactory.js.map +1 -1
- package/dist/editor/field-types/richtext/richTextToolbarIcons.d.ts +7 -0
- package/dist/editor/field-types/richtext/richTextToolbarIcons.js +49 -0
- package/dist/editor/field-types/richtext/richTextToolbarIcons.js.map +1 -0
- 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/conversion.js +23 -2
- package/dist/editor/field-types/richtext/utils/conversion.js.map +1 -1
- package/dist/editor/field-types/useFormFieldCaretPresence.d.ts +13 -0
- package/dist/editor/field-types/useFormFieldCaretPresence.js +92 -0
- package/dist/editor/field-types/useFormFieldCaretPresence.js.map +1 -0
- package/dist/editor/fieldTypes.d.ts +2 -0
- package/dist/editor/media-selector/TreeSelector.js +15 -15
- package/dist/editor/media-selector/TreeSelector.js.map +1 -1
- package/dist/editor/menubar/PageSelector.js +8 -2
- package/dist/editor/menubar/PageSelector.js.map +1 -1
- package/dist/editor/menubar/VersionPreviewCard.js +4 -249
- package/dist/editor/menubar/VersionPreviewCard.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/EditControls.js +2 -2
- package/dist/editor/menubar/toolbar-sections/EditControls.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/ManualBrowser.js +310 -187
- package/dist/editor/menubar/toolbar-sections/ManualBrowser.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/UtilityControls.js +3 -1
- package/dist/editor/menubar/toolbar-sections/UtilityControls.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/ViewportControls.js +1 -1
- package/dist/editor/page-editor-chrome/BridgeInlineFormatOverlay.d.ts +8 -0
- package/dist/editor/page-editor-chrome/BridgeInlineFormatOverlay.js +407 -0
- package/dist/editor/page-editor-chrome/BridgeInlineFormatOverlay.js.map +1 -0
- package/dist/editor/page-editor-chrome/CommentHighlightings.d.ts +5 -2
- package/dist/editor/page-editor-chrome/CommentHighlightings.js +340 -215
- package/dist/editor/page-editor-chrome/CommentHighlightings.js.map +1 -1
- package/dist/editor/page-editor-chrome/FeedbackHighlightBadge.d.ts +5 -1
- package/dist/editor/page-editor-chrome/FeedbackHighlightBadge.js +11 -4
- package/dist/editor/page-editor-chrome/FeedbackHighlightBadge.js.map +1 -1
- package/dist/editor/page-editor-chrome/FieldActionIndicator.js +21 -13
- package/dist/editor/page-editor-chrome/FieldActionIndicator.js.map +1 -1
- package/dist/editor/page-editor-chrome/FieldEditedIndicator.js +23 -29
- package/dist/editor/page-editor-chrome/FieldEditedIndicator.js.map +1 -1
- package/dist/editor/page-editor-chrome/FrameMenu.js +110 -19
- package/dist/editor/page-editor-chrome/FrameMenu.js.map +1 -1
- package/dist/editor/page-editor-chrome/LockedFieldIndicator.d.ts +3 -2
- package/dist/editor/page-editor-chrome/LockedFieldIndicator.js +148 -45
- package/dist/editor/page-editor-chrome/LockedFieldIndicator.js.map +1 -1
- package/dist/editor/page-editor-chrome/PageEditorChrome.d.ts +2 -0
- package/dist/editor/page-editor-chrome/PageEditorChrome.js +25 -21
- package/dist/editor/page-editor-chrome/PageEditorChrome.js.map +1 -1
- package/dist/editor/page-editor-chrome/PictureEditorOverlay.js +163 -128
- package/dist/editor/page-editor-chrome/PictureEditorOverlay.js.map +1 -1
- package/dist/editor/page-editor-chrome/PlaceholderDropZone.d.ts +1 -1
- package/dist/editor/page-editor-chrome/PlaceholderDropZone.js +6 -3
- package/dist/editor/page-editor-chrome/PlaceholderDropZone.js.map +1 -1
- package/dist/editor/page-editor-chrome/PlaceholderDropZones.d.ts +1 -2
- package/dist/editor/page-editor-chrome/PlaceholderDropZones.js +83 -146
- package/dist/editor/page-editor-chrome/PlaceholderDropZones.js.map +1 -1
- package/dist/editor/page-editor-chrome/SuggestionHighlightings.d.ts +5 -2
- package/dist/editor/page-editor-chrome/SuggestionHighlightings.js +144 -63
- package/dist/editor/page-editor-chrome/SuggestionHighlightings.js.map +1 -1
- package/dist/editor/page-editor-chrome/VersionDiffHighlightings.d.ts +1 -2
- package/dist/editor/page-editor-chrome/VersionDiffHighlightings.js +101 -30
- package/dist/editor/page-editor-chrome/VersionDiffHighlightings.js.map +1 -1
- package/dist/editor/page-editor-chrome/bridgeInlineFormatToolbarLayout.d.ts +24 -0
- package/dist/editor/page-editor-chrome/bridgeInlineFormatToolbarLayout.js +89 -0
- package/dist/editor/page-editor-chrome/bridgeInlineFormatToolbarLayout.js.map +1 -0
- package/dist/editor/page-editor-chrome/overlay/IframeOverlayProvider.d.ts +10 -1
- package/dist/editor/page-editor-chrome/overlay/IframeOverlayProvider.js +105 -122
- package/dist/editor/page-editor-chrome/overlay/IframeOverlayProvider.js.map +1 -1
- package/dist/editor/page-editor-chrome/overlay/geometry.d.ts +11 -4
- package/dist/editor/page-editor-chrome/overlay/geometry.js +139 -36
- package/dist/editor/page-editor-chrome/overlay/geometry.js.map +1 -1
- package/dist/editor/page-editor-chrome/useBridgeInlineEditing.d.ts +26 -0
- package/dist/editor/page-editor-chrome/useBridgeInlineEditing.js +228 -0
- package/dist/editor/page-editor-chrome/useBridgeInlineEditing.js.map +1 -0
- package/dist/editor/page-viewer/EditorForm.js +17 -1
- package/dist/editor/page-viewer/EditorForm.js.map +1 -1
- package/dist/editor/page-viewer/MiniMap.d.ts +2 -2
- package/dist/editor/page-viewer/MiniMap.js +176 -364
- package/dist/editor/page-viewer/MiniMap.js.map +1 -1
- package/dist/editor/page-viewer/PageViewer.js +63 -17
- package/dist/editor/page-viewer/PageViewer.js.map +1 -1
- package/dist/editor/page-viewer/PageViewerFrame.d.ts +0 -5
- package/dist/editor/page-viewer/PageViewerFrame.js +1685 -1512
- package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
- package/dist/editor/page-viewer/bridgeFieldPatch.d.ts +20 -0
- package/dist/editor/page-viewer/bridgeFieldPatch.js +33 -0
- package/dist/editor/page-viewer/bridgeFieldPatch.js.map +1 -0
- package/dist/editor/page-viewer/pageViewContext.d.ts +32 -0
- package/dist/editor/page-viewer/pageViewContext.js +37 -6
- package/dist/editor/page-viewer/pageViewContext.js.map +1 -1
- package/dist/editor/reviews/Comment.d.ts +2 -1
- package/dist/editor/reviews/Comment.js +10 -5
- package/dist/editor/reviews/Comment.js.map +1 -1
- package/dist/editor/reviews/CommentDisplayPopover.js +2 -1
- package/dist/editor/reviews/CommentDisplayPopover.js.map +1 -1
- package/dist/editor/reviews/CommentEditor.d.ts +1 -0
- package/dist/editor/reviews/CommentEditor.js +3 -2
- package/dist/editor/reviews/CommentEditor.js.map +1 -1
- package/dist/editor/reviews/CommentPopover.js +69 -10
- package/dist/editor/reviews/CommentPopover.js.map +1 -1
- package/dist/editor/reviews/CommentView.js +24 -4
- package/dist/editor/reviews/CommentView.js.map +1 -1
- package/dist/editor/reviews/Comments.d.ts +0 -2
- package/dist/editor/reviews/Comments.js +31 -31
- package/dist/editor/reviews/Comments.js.map +1 -1
- package/dist/editor/reviews/FeedbackCard.d.ts +4 -2
- package/dist/editor/reviews/FeedbackCard.js +8 -10
- package/dist/editor/reviews/FeedbackCard.js.map +1 -1
- package/dist/editor/reviews/SuggestedEdit.js +4 -6
- package/dist/editor/reviews/SuggestedEdit.js.map +1 -1
- package/dist/editor/reviews/SuggestionCommentThread.js +3 -3
- package/dist/editor/reviews/SuggestionCommentThread.js.map +1 -1
- package/dist/editor/reviews/SuggestionDisplayPopover.js +3 -2
- package/dist/editor/reviews/SuggestionDisplayPopover.js.map +1 -1
- package/dist/editor/reviews/commentAi.js +96 -27
- package/dist/editor/reviews/commentAi.js.map +1 -1
- package/dist/editor/reviews/commentTransientSelection.d.ts +23 -0
- package/dist/editor/reviews/commentTransientSelection.js +7 -0
- package/dist/editor/reviews/commentTransientSelection.js.map +1 -0
- package/dist/editor/reviews/feedbackOrdering.d.ts +5 -0
- package/dist/editor/reviews/feedbackOrdering.js +27 -0
- package/dist/editor/reviews/feedbackOrdering.js.map +1 -0
- package/dist/editor/reviews/feedbackSelection.js +32 -4
- package/dist/editor/reviews/feedbackSelection.js.map +1 -1
- package/dist/editor/reviews/suggestedEditState.d.ts +12 -0
- package/dist/editor/reviews/suggestedEditState.js +90 -0
- package/dist/editor/reviews/suggestedEditState.js.map +1 -0
- package/dist/editor/reviews/suggestionDisplayValue.d.ts +43 -0
- package/dist/editor/reviews/suggestionDisplayValue.js +93 -0
- package/dist/editor/reviews/suggestionDisplayValue.js.map +1 -0
- package/dist/editor/services/agentService.d.ts +15 -0
- package/dist/editor/services/agentService.js +11 -1
- package/dist/editor/services/agentService.js.map +1 -1
- package/dist/editor/services/reviewsService.d.ts +2 -2
- package/dist/editor/services/reviewsService.js.map +1 -1
- package/dist/editor/settings/SettingsView.js +2 -2
- package/dist/editor/settings/SettingsView.js.map +1 -1
- package/dist/editor/settings/panels/ProjectTemplatesPanel.js +1 -1
- package/dist/editor/settings/panels/ProjectTemplatesPanel.js.map +1 -1
- package/dist/editor/settings/panels/ProvidersPanel.js +2 -3
- package/dist/editor/settings/panels/ProvidersPanel.js.map +1 -1
- package/dist/editor/sidebar/MorePanelsButton.js +1 -1
- package/dist/editor/sidebar/MorePanelsButton.js.map +1 -1
- package/dist/editor/sidebar/Validation.js +4 -1
- package/dist/editor/sidebar/Validation.js.map +1 -1
- package/dist/editor/sidebar/Workbox.js +1 -1
- package/dist/editor/sidebar/Workbox.js.map +1 -1
- package/dist/editor/template-wizard/TemplateStructureInlineEditor.js +1 -1
- package/dist/editor/template-wizard/TemplateStructureInlineEditor.js.map +1 -1
- package/dist/editor/ui/IconSelectorDialog.js +1 -1
- package/dist/editor/ui/IconSelectorDialog.js.map +1 -1
- package/dist/editor/ui/SimpleIconButton.d.ts +2 -2
- package/dist/editor/ui/SimpleIconButton.js +7 -1
- package/dist/editor/ui/SimpleIconButton.js.map +1 -1
- package/dist/editor/ui/Splitter.d.ts +1 -0
- package/dist/editor/ui/Splitter.js +12 -2
- package/dist/editor/ui/Splitter.js.map +1 -1
- package/dist/editor/ui/animationSettle.d.ts +32 -0
- package/dist/editor/ui/animationSettle.js +85 -0
- package/dist/editor/ui/animationSettle.js.map +1 -0
- package/dist/editor/utils/expandSelectionAtCaret.d.ts +15 -0
- package/dist/editor/utils/expandSelectionAtCaret.js +183 -0
- package/dist/editor/utils/expandSelectionAtCaret.js.map +1 -0
- package/dist/editor/utils.d.ts +1 -17
- package/dist/editor/utils.js +0 -143
- package/dist/editor/utils.js.map +1 -1
- package/dist/editor/version-diff/versionDiffTargets.d.ts +3 -8
- package/dist/editor/version-diff/versionDiffTargets.js +37 -94
- package/dist/editor/version-diff/versionDiffTargets.js.map +1 -1
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/dist/splash-screen/ModernSplashScreen.js +11 -3
- package/dist/splash-screen/ModernSplashScreen.js.map +1 -1
- package/dist/splash-screen/NewPage.js +7 -5
- package/dist/splash-screen/NewPage.js.map +1 -1
- package/dist/splash-screen/OpenPage.js +5 -3
- package/dist/splash-screen/OpenPage.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/components/TaskDetailPanel.js +2 -1
- package/dist/task-board/components/TaskDetailPanel.js.map +1 -1
- package/dist/task-board/views/DependencyGraphView.d.ts +42 -1
- package/dist/task-board/views/DependencyGraphView.js +94 -0
- package/dist/task-board/views/DependencyGraphView.js.map +1 -1
- package/dist/types.d.ts +1 -0
- package/package.json +2 -1
- package/styles.css +96 -0
- package/dist/editor/page-editor-chrome/InlineEditor.d.ts +0 -7
- package/dist/editor/page-editor-chrome/InlineEditor.js +0 -1719
- package/dist/editor/page-editor-chrome/InlineEditor.js.map +0 -1
- package/dist/editor/page-editor-chrome/overlay/iframeAccess.d.ts +0 -2
- package/dist/editor/page-editor-chrome/overlay/iframeAccess.js +0 -21
- package/dist/editor/page-editor-chrome/overlay/iframeAccess.js.map +0 -1
- package/dist/editor/page-editor-chrome/useInlineAICompletion.d.ts +0 -7
- package/dist/editor/page-editor-chrome/useInlineAICompletion.js +0 -758
- package/dist/editor/page-editor-chrome/useInlineAICompletion.js.map +0 -1
- package/dist/editor/page-viewer/pageModelSkeletonBuilder.d.ts +0 -3
- package/dist/editor/page-viewer/pageModelSkeletonBuilder.js +0 -796
- package/dist/editor/page-viewer/pageModelSkeletonBuilder.js.map +0 -1
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useEffect, useRef, useState } from "react";
|
|
3
|
-
import { MiniMap } from "./MiniMap";
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
+
import { BRIDGE_DOM_UPDATED_EVENT, MiniMap } from "./MiniMap";
|
|
4
4
|
import { useEditContext, useEditContextRef, useFieldsEditContext, useFieldsEditContextRef, } from "../client/editContext";
|
|
5
5
|
import { useSlotContext } from "../views/editorSlotContext";
|
|
6
6
|
import { useDebouncedCallback, useThrottledCallback } from "use-debounce";
|
|
7
7
|
import { PageEditorChrome } from "../page-editor-chrome/PageEditorChrome";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import
|
|
8
|
+
import { useBridgeInlineEditing, } from "../page-editor-chrome/useBridgeInlineEditing";
|
|
9
|
+
import { getInlineAiAnchorFromBridgeToolbar, getVisibleBridgeToolbarRect, waitForVisibleBridgeToolbarRect, } from "../page-editor-chrome/bridgeInlineFormatToolbarLayout";
|
|
10
|
+
import { IFRAME_OVERLAY_BRIDGE_GEOMETRY_EVENT, IFRAME_OVERLAY_BRIDGE_SCROLL_EVENT, IframeOverlayProvider, } from "../page-editor-chrome/overlay/IframeOverlayProvider";
|
|
11
|
+
import { DEVICE_CHANGE_EVENT, useViewportChangeSignal, } from "./pageViewContext";
|
|
11
12
|
import uuid from "react-uuid";
|
|
12
13
|
import { cn } from "../../lib/utils";
|
|
13
|
-
import {
|
|
14
|
-
import { extractDOMSelectionContext } from "../utils/selectionContext";
|
|
14
|
+
import { normalizeMarkerId } from "../utils";
|
|
15
15
|
import { cleanId } from "../utils/id-helper";
|
|
16
|
-
import {
|
|
16
|
+
import { getComponentById } from "../componentTreeHelper";
|
|
17
17
|
import { buildComponentContextMenuItems } from "../ContextMenu";
|
|
18
18
|
import { loadFieldButtons } from "../services/editService";
|
|
19
19
|
import { usePathname } from "../client/navigation";
|
|
@@ -22,125 +22,311 @@ import { FieldActionsOverlay, } from "../FieldActionsOverlay";
|
|
|
22
22
|
import { NoLayout } from "../page-editor-chrome/NoLayout";
|
|
23
23
|
import { Spinner } from "../ui/Spinner";
|
|
24
24
|
import { DeviceToolbar } from "./DeviceToolbar";
|
|
25
|
-
import { buildPageModelSkeleton } from "./pageModelSkeletonBuilder";
|
|
26
25
|
import { toSitecoreDate } from "../utils/sitecoreDate";
|
|
27
|
-
|
|
26
|
+
import { BridgeClient } from "../bridge/BridgeClient";
|
|
27
|
+
import { getSuggestionDisplayValue } from "../reviews/suggestionDisplayValue";
|
|
28
|
+
import { buildBridgeFieldPatchPayload, getBridgeFieldPatchValue, } from "./bridgeFieldPatch";
|
|
28
29
|
const ZOOM_MIN = 0.25;
|
|
29
30
|
const ZOOM_MAX = 2;
|
|
30
31
|
const ZOOM_STEP = 0.25;
|
|
31
32
|
const ZOOM_TRANSITION_MS = 300;
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
const BRIDGE_INLINE_EDIT_RELEASE_EVENT = "parhelia:bridge-inline-edit-release";
|
|
34
|
+
const INLINE_AI_CLOSE_EVENT = "inline-ai-close";
|
|
35
|
+
function dispatchBridgeOverlayScroll(iframe, scroll, scrollScale = 1) {
|
|
36
|
+
iframe?.dispatchEvent(new CustomEvent(IFRAME_OVERLAY_BRIDGE_SCROLL_EVENT, {
|
|
37
|
+
detail: {
|
|
38
|
+
scrollLeft: scroll.x * scrollScale,
|
|
39
|
+
scrollTop: scroll.y * scrollScale,
|
|
40
|
+
},
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
function getBridgeGeometryScrollScale(geometry) {
|
|
44
|
+
return typeof geometry?.scrollScale === "number" &&
|
|
45
|
+
Number.isFinite(geometry.scrollScale) &&
|
|
46
|
+
geometry.scrollScale > 0
|
|
47
|
+
? geometry.scrollScale
|
|
48
|
+
: 1;
|
|
49
|
+
}
|
|
50
|
+
function dispatchBridgeOverlayGeometry(iframe, geometry) {
|
|
51
|
+
const documentSize = getBridgeGeometryDocumentSize(geometry);
|
|
52
|
+
const scrollScale = getBridgeGeometryScrollScale(geometry);
|
|
53
|
+
const detail = {
|
|
54
|
+
scrollLeft: (geometry?.scroll.x ?? 0) * scrollScale,
|
|
55
|
+
scrollTop: (geometry?.scroll.y ?? 0) * scrollScale,
|
|
56
|
+
viewportWidth: geometry?.viewport.width,
|
|
57
|
+
viewportHeight: geometry?.viewport.height,
|
|
58
|
+
scrollWidth: documentSize?.width,
|
|
59
|
+
scrollHeight: documentSize?.height,
|
|
60
|
+
};
|
|
61
|
+
iframe?.dispatchEvent(new CustomEvent(IFRAME_OVERLAY_BRIDGE_GEOMETRY_EVENT, { detail }));
|
|
62
|
+
}
|
|
63
|
+
function getBridgeGeometryDocumentSize(geometry) {
|
|
64
|
+
if (!geometry)
|
|
65
|
+
return undefined;
|
|
66
|
+
const rectScale = geometry.rectScale ?? 1;
|
|
67
|
+
const scrollScale = getBridgeGeometryScrollScale(geometry);
|
|
68
|
+
return {
|
|
69
|
+
width: Math.max(geometry.viewport.width, ...geometry.targets.map((target) => target.rect.right * rectScale + geometry.scroll.x * scrollScale)),
|
|
70
|
+
height: Math.max(geometry.viewport.height, ...geometry.targets.map((target) => target.rect.bottom * rectScale + geometry.scroll.y * scrollScale)),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function bridgeKeysMatch(left, right) {
|
|
74
|
+
const normalizedLeft = normalizeMarkerId(left);
|
|
75
|
+
const normalizedRight = normalizeMarkerId(right);
|
|
76
|
+
return (!!normalizedLeft && !!normalizedRight && normalizedLeft === normalizedRight);
|
|
77
|
+
}
|
|
78
|
+
function bridgeItemMatches(item, descriptor) {
|
|
79
|
+
if (!item)
|
|
80
|
+
return false;
|
|
81
|
+
if (!bridgeKeysMatch(item.id, descriptor.id))
|
|
82
|
+
return false;
|
|
83
|
+
if (item.language &&
|
|
84
|
+
descriptor.language &&
|
|
85
|
+
item.language !== descriptor.language) {
|
|
86
|
+
return false;
|
|
38
87
|
}
|
|
39
|
-
|
|
40
|
-
|
|
88
|
+
if (item.version !== undefined &&
|
|
89
|
+
descriptor.version !== undefined &&
|
|
90
|
+
item.version !== descriptor.version) {
|
|
91
|
+
return false;
|
|
41
92
|
}
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
function bridgeTargetToDocumentBounds(geometry, target) {
|
|
96
|
+
const rectScale = geometry.rectScale ?? 1;
|
|
97
|
+
const scrollScale = getBridgeGeometryScrollScale(geometry);
|
|
98
|
+
return {
|
|
99
|
+
x: target.rect.left * rectScale + geometry.scroll.x * scrollScale,
|
|
100
|
+
y: target.rect.top * rectScale + geometry.scroll.y * scrollScale,
|
|
101
|
+
width: target.rect.width * rectScale,
|
|
102
|
+
height: target.rect.height * rectScale,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function findBridgeFieldDocumentBounds(geometry, descriptor) {
|
|
106
|
+
if (!geometry)
|
|
107
|
+
return undefined;
|
|
108
|
+
const target = geometry.targets.find((candidate) => {
|
|
109
|
+
if (candidate.kind !== "field")
|
|
110
|
+
return false;
|
|
111
|
+
return (bridgeKeysMatch(candidate.fieldId, descriptor.fieldId) &&
|
|
112
|
+
bridgeItemMatches(candidate.item, descriptor.item));
|
|
113
|
+
});
|
|
114
|
+
return target?.rect
|
|
115
|
+
? bridgeTargetToDocumentBounds(geometry, target)
|
|
116
|
+
: undefined;
|
|
117
|
+
}
|
|
118
|
+
function findBridgeComponentDocumentBounds(geometry, componentId) {
|
|
119
|
+
if (!geometry)
|
|
120
|
+
return undefined;
|
|
121
|
+
const target = geometry.targets.find((candidate) => {
|
|
122
|
+
if (candidate.kind !== "component")
|
|
123
|
+
return false;
|
|
124
|
+
return (bridgeKeysMatch(candidate.componentId, componentId) ||
|
|
125
|
+
bridgeKeysMatch(candidate.key, componentId));
|
|
126
|
+
});
|
|
127
|
+
return target?.rect
|
|
128
|
+
? bridgeTargetToDocumentBounds(geometry, target)
|
|
129
|
+
: undefined;
|
|
130
|
+
}
|
|
131
|
+
function scrollBridgeBoundsIntoView(pageViewContext, bounds, currentScroll) {
|
|
132
|
+
const geometry = pageViewContext.bridgeGeometry;
|
|
133
|
+
if (!geometry)
|
|
134
|
+
return false;
|
|
135
|
+
const scrollScale = getBridgeGeometryScrollScale(geometry);
|
|
136
|
+
const currentScrollY = (currentScroll?.y ?? geometry.scroll.y) * scrollScale;
|
|
137
|
+
const viewportHeight = geometry.viewport.height;
|
|
138
|
+
const isInViewport = bounds.y + bounds.height > currentScrollY &&
|
|
139
|
+
bounds.y < currentScrollY + viewportHeight;
|
|
140
|
+
if (isInViewport)
|
|
141
|
+
return false;
|
|
142
|
+
const targetScrollY = bounds.y - viewportHeight / 2 + Math.max(bounds.height, 1) / 2;
|
|
143
|
+
return (pageViewContext.requestBridgeScrollBy?.({
|
|
144
|
+
y: targetScrollY - currentScrollY,
|
|
145
|
+
behavior: "smooth",
|
|
146
|
+
}) ?? false);
|
|
42
147
|
}
|
|
43
|
-
function
|
|
148
|
+
function getIframeWindowScroll(iframe) {
|
|
44
149
|
try {
|
|
45
|
-
|
|
150
|
+
const win = iframe?.contentWindow;
|
|
151
|
+
const doc = win?.document;
|
|
152
|
+
if (!win || !doc)
|
|
153
|
+
return undefined;
|
|
154
|
+
const scrollingElement = doc.scrollingElement;
|
|
155
|
+
return {
|
|
156
|
+
x: Math.max(win.scrollX || 0, scrollingElement?.scrollLeft || 0, doc.documentElement?.scrollLeft || 0, doc.body?.scrollLeft || 0),
|
|
157
|
+
y: Math.max(win.scrollY || 0, scrollingElement?.scrollTop || 0, doc.documentElement?.scrollTop || 0, doc.body?.scrollTop || 0),
|
|
158
|
+
};
|
|
46
159
|
}
|
|
47
160
|
catch {
|
|
48
161
|
return undefined;
|
|
49
162
|
}
|
|
50
163
|
}
|
|
51
|
-
function
|
|
164
|
+
function restoreIframeWindowScroll(iframe, targetScroll) {
|
|
165
|
+
if (!targetScroll)
|
|
166
|
+
return false;
|
|
52
167
|
try {
|
|
53
|
-
|
|
168
|
+
const win = iframe?.contentWindow;
|
|
169
|
+
if (!win)
|
|
170
|
+
return false;
|
|
171
|
+
let attempts = 0;
|
|
172
|
+
const apply = () => {
|
|
173
|
+
try {
|
|
174
|
+
attempts += 1;
|
|
175
|
+
win.scrollTo(targetScroll.x, targetScroll.y);
|
|
176
|
+
const settled = Math.abs((win.scrollX || 0) - targetScroll.x) <= 1 &&
|
|
177
|
+
Math.abs((win.scrollY || 0) - targetScroll.y) <= 1;
|
|
178
|
+
if (!settled && attempts < 8) {
|
|
179
|
+
window.requestAnimationFrame(apply);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
// Cross-origin preview frames expose a WindowProxy, but reading named
|
|
184
|
+
// properties such as scrollTo/scrollX can throw. In that case the
|
|
185
|
+
// bridge-side scroll restoration is responsible for preserving scroll.
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
window.requestAnimationFrame(apply);
|
|
189
|
+
return true;
|
|
54
190
|
}
|
|
55
191
|
catch {
|
|
56
|
-
return
|
|
192
|
+
return false;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function applyIframeZoom(_iframe, _zoom, _location) {
|
|
196
|
+
// Zoom is applied inside the page host through the bridge setZoom command.
|
|
197
|
+
}
|
|
198
|
+
function clampEditorZoom(value) {
|
|
199
|
+
return Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, value));
|
|
200
|
+
}
|
|
201
|
+
function isLoopbackHost(hostname) {
|
|
202
|
+
return hostname === "localhost" || hostname === "127.0.0.1";
|
|
203
|
+
}
|
|
204
|
+
function alignLoopbackHostToEditor(url) {
|
|
205
|
+
if (isLoopbackHost(window.location.hostname) &&
|
|
206
|
+
isLoopbackHost(url.hostname)) {
|
|
207
|
+
// Sitecore auth cookies are host-scoped, so localhost and 127.0.0.1 are
|
|
208
|
+
// different origins even when they point at the same machine.
|
|
209
|
+
url.hostname = window.location.hostname;
|
|
57
210
|
}
|
|
58
211
|
}
|
|
59
|
-
function
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return;
|
|
63
|
-
const ownerWindow = documentElement.ownerDocument.defaultView ?? window;
|
|
64
|
-
let reduceMotion = false;
|
|
212
|
+
function getUrlOrigin(url) {
|
|
213
|
+
if (!url)
|
|
214
|
+
return undefined;
|
|
65
215
|
try {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
216
|
+
const parsedUrl = new URL(url, window.location.href);
|
|
217
|
+
alignLoopbackHostToEditor(parsedUrl);
|
|
218
|
+
return parsedUrl.origin;
|
|
69
219
|
}
|
|
70
|
-
catch {
|
|
71
|
-
|
|
72
|
-
if (animationFrame !== undefined) {
|
|
73
|
-
ownerWindow.cancelAnimationFrame(animationFrame);
|
|
74
|
-
zoomAnimationFrames.delete(documentElement);
|
|
220
|
+
catch {
|
|
221
|
+
return undefined;
|
|
75
222
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
223
|
+
}
|
|
224
|
+
function resolveBridgeComponentId(rawId, page) {
|
|
225
|
+
if (!rawId)
|
|
226
|
+
return undefined;
|
|
227
|
+
if (!page?.rootComponent)
|
|
228
|
+
return rawId;
|
|
229
|
+
const normalizedId = cleanId(rawId);
|
|
230
|
+
if (!normalizedId)
|
|
231
|
+
return undefined;
|
|
232
|
+
let exactMatch;
|
|
233
|
+
let datasourceMatch;
|
|
234
|
+
const visit = (component) => {
|
|
235
|
+
if (!exactMatch && cleanId(component.id) === normalizedId) {
|
|
236
|
+
exactMatch = component;
|
|
84
237
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
documentElement.style.removeProperty("will-change");
|
|
94
|
-
return;
|
|
238
|
+
if (!datasourceMatch &&
|
|
239
|
+
cleanId(component.datasourceItem?.id) === normalizedId) {
|
|
240
|
+
datasourceMatch = component;
|
|
241
|
+
}
|
|
242
|
+
for (const placeholder of component.placeholders || []) {
|
|
243
|
+
for (const child of placeholder.components || []) {
|
|
244
|
+
visit(child);
|
|
245
|
+
}
|
|
95
246
|
}
|
|
96
|
-
documentElement.style.zoom = String(zoom);
|
|
97
|
-
documentElement.style.removeProperty("transform");
|
|
98
|
-
documentElement.style.removeProperty("transform-origin");
|
|
99
|
-
documentElement.style.overflow = "auto";
|
|
100
|
-
documentElement.style.removeProperty("will-change");
|
|
101
247
|
};
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
248
|
+
visit(page.rootComponent);
|
|
249
|
+
return exactMatch?.id ?? datasourceMatch?.id ?? rawId;
|
|
250
|
+
}
|
|
251
|
+
function getOrderedBridgeComponentIds(geometry, page) {
|
|
252
|
+
const orderedIds = [];
|
|
253
|
+
for (const target of geometry?.targets ?? []) {
|
|
254
|
+
if (target.kind !== "component")
|
|
255
|
+
continue;
|
|
256
|
+
const componentId = resolveBridgeComponentId(target.componentId, page);
|
|
257
|
+
if (!componentId)
|
|
258
|
+
continue;
|
|
259
|
+
if (orderedIds.some((id) => bridgeIdsMatch(id, componentId)))
|
|
260
|
+
continue;
|
|
261
|
+
orderedIds.push(componentId);
|
|
262
|
+
}
|
|
263
|
+
return orderedIds;
|
|
264
|
+
}
|
|
265
|
+
function bridgeIdsMatch(left, right) {
|
|
266
|
+
const normalizedLeft = normalizeMarkerId(left);
|
|
267
|
+
const normalizedRight = normalizeMarkerId(right);
|
|
268
|
+
return (!!normalizedLeft && !!normalizedRight && normalizedLeft === normalizedRight);
|
|
269
|
+
}
|
|
270
|
+
function bridgeComponentSelectionsMatch(left, right) {
|
|
271
|
+
if ((left?.length ?? 0) !== (right?.length ?? 0))
|
|
272
|
+
return false;
|
|
273
|
+
return (left ?? []).every((leftId, index) => bridgeIdsMatch(leftId, right?.[index]));
|
|
274
|
+
}
|
|
275
|
+
function bridgeDescriptorMatchesItem(bridgeItem, item) {
|
|
276
|
+
if (!bridgeItem?.id)
|
|
277
|
+
return true;
|
|
278
|
+
if (!bridgeIdsMatch(bridgeItem.id, item.id))
|
|
279
|
+
return false;
|
|
280
|
+
if (bridgeItem.language &&
|
|
281
|
+
item.language &&
|
|
282
|
+
bridgeItem.language.toLowerCase() !== item.language.toLowerCase()) {
|
|
283
|
+
return false;
|
|
105
284
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
285
|
+
if (typeof bridgeItem.version === "number" &&
|
|
286
|
+
Number.isFinite(bridgeItem.version) &&
|
|
287
|
+
typeof item.version === "number" &&
|
|
288
|
+
Number.isFinite(item.version) &&
|
|
289
|
+
bridgeItem.version !== item.version) {
|
|
290
|
+
return false;
|
|
111
291
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
292
|
+
return true;
|
|
293
|
+
}
|
|
294
|
+
function fieldIdentifierMatches(field, fieldId) {
|
|
295
|
+
return (bridgeIdsMatch(field.id, fieldId) ||
|
|
296
|
+
bridgeIdsMatch(field.name, fieldId) ||
|
|
297
|
+
bridgeIdsMatch(field.displayName, fieldId));
|
|
298
|
+
}
|
|
299
|
+
function bridgeFieldMatchesChangedField(bridgeFieldId, changedFieldId, field) {
|
|
300
|
+
return (bridgeIdsMatch(bridgeFieldId, changedFieldId) ||
|
|
301
|
+
bridgeIdsMatch(bridgeFieldId, field.id) ||
|
|
302
|
+
bridgeIdsMatch(bridgeFieldId, field.name) ||
|
|
303
|
+
bridgeIdsMatch(bridgeFieldId, field.displayName));
|
|
304
|
+
}
|
|
305
|
+
function findBridgeFieldTargetAtPoint(geometry, clientX, clientY) {
|
|
306
|
+
if (!geometry || typeof clientX !== "number" || typeof clientY !== "number") {
|
|
307
|
+
return undefined;
|
|
116
308
|
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
documentElement.style.removeProperty("transform-origin");
|
|
130
|
-
if (targetZoom !== 1) {
|
|
131
|
-
documentElement.style.overflow = "auto";
|
|
132
|
-
}
|
|
133
|
-
if (progress < 1) {
|
|
134
|
-
zoomAnimationFrames.set(documentElement, ownerWindow.requestAnimationFrame(step));
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
zoomAnimationFrames.delete(documentElement);
|
|
138
|
-
applyFinalZoom();
|
|
139
|
-
};
|
|
140
|
-
zoomAnimationFrames.set(documentElement, ownerWindow.requestAnimationFrame(step));
|
|
309
|
+
return geometry.targets
|
|
310
|
+
.filter((target) => {
|
|
311
|
+
if (target.kind !== "field")
|
|
312
|
+
return false;
|
|
313
|
+
const rect = target.rect;
|
|
314
|
+
return (clientX >= rect.left &&
|
|
315
|
+
clientX <= rect.right &&
|
|
316
|
+
clientY >= rect.top &&
|
|
317
|
+
clientY <= rect.bottom);
|
|
318
|
+
})
|
|
319
|
+
.sort((left, right) => left.rect.width * left.rect.height -
|
|
320
|
+
right.rect.width * right.rect.height)[0];
|
|
141
321
|
}
|
|
142
|
-
function
|
|
143
|
-
|
|
322
|
+
function isBridgeInlineAiShortcut(interaction) {
|
|
323
|
+
const keyboardInteraction = interaction;
|
|
324
|
+
return !!((keyboardInteraction.ctrlKey || keyboardInteraction.metaKey) &&
|
|
325
|
+
(keyboardInteraction.key === "." ||
|
|
326
|
+
keyboardInteraction.key === "Period" ||
|
|
327
|
+
keyboardInteraction.key === "Decimal" ||
|
|
328
|
+
keyboardInteraction.code === "Period" ||
|
|
329
|
+
keyboardInteraction.code === "NumpadDecimal"));
|
|
144
330
|
}
|
|
145
331
|
export function PageViewerFrame(props) {
|
|
146
332
|
const editContext = useEditContext();
|
|
@@ -160,17 +346,39 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
160
346
|
const editContextRef = useEditContextRef();
|
|
161
347
|
const fieldsContextRef = useFieldsEditContextRef();
|
|
162
348
|
const iframeRef = useRef(null);
|
|
163
|
-
const
|
|
164
|
-
const
|
|
165
|
-
const
|
|
349
|
+
const [iframeElement, setIframeElement] = useState(null);
|
|
350
|
+
const bridgeClientRef = useRef(null);
|
|
351
|
+
const bridgePatchSignatureRef = useRef(new Map());
|
|
352
|
+
const bridgeTextRangeSourcesRef = useRef(new Map());
|
|
353
|
+
const activeBridgeInlineEditRef = useRef(null);
|
|
354
|
+
const latestBridgeScrollRef = useRef(undefined);
|
|
355
|
+
const pendingRefreshScrollRef = useRef(undefined);
|
|
356
|
+
const suppressNextSelectionScrollRef = useRef(false);
|
|
357
|
+
// Tracks the selection we last evaluated for auto-scroll. The selection
|
|
358
|
+
// effect also depends on focusedField, so it re-runs when a field blurs
|
|
359
|
+
// (focusedField -> undefined) even though the selection itself did not
|
|
360
|
+
// change. Without this guard that blur re-render would scroll the just
|
|
361
|
+
// clicked component into view after the suppress flag was already consumed.
|
|
362
|
+
const lastScrolledSelectionKeyRef = useRef("");
|
|
166
363
|
const [showSpinner, setShowSpinner] = useState(false);
|
|
364
|
+
const [iframeSrc, setIframeSrc] = useState();
|
|
365
|
+
const [loadedIframeSrc, setLoadedIframeSrc] = useState();
|
|
167
366
|
const [scroll, setScroll] = useState(0);
|
|
168
367
|
const [showMiniMap, setShowMiniMap] = useState(false);
|
|
368
|
+
const [bridgeGeometryRevision, setBridgeGeometryRevision] = useState(0);
|
|
369
|
+
const [bridgeDomRevision, setBridgeDomRevision] = useState(0);
|
|
370
|
+
const bridgeGeometryRevisionRafRef = useRef(null);
|
|
371
|
+
const bridgeDomRevisionRafRef = useRef(null);
|
|
169
372
|
const fieldActionsOverlay = useRef(null);
|
|
373
|
+
const lastSentBridgeCaretRef = useRef(null);
|
|
170
374
|
const [contextMenuFieldButtons, setContextMenuFieldButtons] = useState([]);
|
|
171
375
|
const [contextMenuField, setContextMenuField] = useState();
|
|
172
376
|
const [contextMenuPosition, setContextMenuPosition] = useState();
|
|
173
377
|
const [preSelectedAction, setPreSelectedAction] = useState();
|
|
378
|
+
const bindIframeRef = useCallback((node) => {
|
|
379
|
+
iframeRef.current = node;
|
|
380
|
+
setIframeElement(node);
|
|
381
|
+
}, []);
|
|
174
382
|
// Clear preSelectedAction when overlay is closed
|
|
175
383
|
useEffect(() => {
|
|
176
384
|
if (editContext?.currentOverlay !==
|
|
@@ -179,7 +387,6 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
179
387
|
}
|
|
180
388
|
}, [editContext?.currentOverlay, contextMenuField?.fieldId]);
|
|
181
389
|
const zoom = pageViewContext.zoom;
|
|
182
|
-
const blockBlurEventRef = useRef(0);
|
|
183
390
|
const [currentItemDescriptor, setCurrentItemDescriptor] = useState(undefined);
|
|
184
391
|
// Field action handlers for the overlay
|
|
185
392
|
const handleActionClick = async (action, event) => {
|
|
@@ -195,1179 +402,607 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
195
402
|
// Note: handleParameterizedActionFromContextMenu is now created inline in handleContextMenu
|
|
196
403
|
// to avoid React state timing issues with contextMenuPosition
|
|
197
404
|
const pageItemDescriptor = pageViewContext.pageItemDescriptor;
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
405
|
+
const shouldTrackMinimapScroll = useCallback(() => {
|
|
406
|
+
const editor = editContextRef.current;
|
|
407
|
+
return !!(showMiniMap &&
|
|
408
|
+
editor?.showMinimap &&
|
|
409
|
+
!editor?.isMobile &&
|
|
410
|
+
editor?.parheliaSettings?.showMinimap !== false);
|
|
411
|
+
}, [showMiniMap]);
|
|
412
|
+
const updateScrollPosition = useCallback((e) => {
|
|
413
|
+
if (!shouldTrackMinimapScroll())
|
|
204
414
|
return;
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
415
|
+
setScroll(e);
|
|
416
|
+
if (!compareView)
|
|
417
|
+
pageViewContextRef.current?.setScroll(e);
|
|
418
|
+
}, [compareView, shouldTrackMinimapScroll]);
|
|
419
|
+
const scrollHandler = useThrottledCallback(updateScrollPosition, 100);
|
|
420
|
+
const scrollHandlerRef = useRef(scrollHandler);
|
|
421
|
+
useEffect(() => {
|
|
422
|
+
scrollHandlerRef.current = scrollHandler;
|
|
423
|
+
}, [scrollHandler]);
|
|
424
|
+
const scheduleBridgeGeometryRevision = useCallback(() => {
|
|
425
|
+
if (bridgeGeometryRevisionRafRef.current != null)
|
|
208
426
|
return;
|
|
209
|
-
|
|
210
|
-
|
|
427
|
+
bridgeGeometryRevisionRafRef.current = window.requestAnimationFrame(() => {
|
|
428
|
+
bridgeGeometryRevisionRafRef.current = null;
|
|
429
|
+
setBridgeGeometryRevision((revision) => revision + 1);
|
|
430
|
+
});
|
|
431
|
+
}, []);
|
|
432
|
+
const scheduleBridgeDomRevision = useCallback(() => {
|
|
433
|
+
if (bridgeDomRevisionRafRef.current != null)
|
|
211
434
|
return;
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
setShowMiniMap(false);
|
|
435
|
+
bridgeDomRevisionRafRef.current = window.requestAnimationFrame(() => {
|
|
436
|
+
bridgeDomRevisionRafRef.current = null;
|
|
437
|
+
setBridgeDomRevision((revision) => revision + 1);
|
|
438
|
+
});
|
|
439
|
+
}, []);
|
|
440
|
+
useEffect(() => {
|
|
441
|
+
return () => {
|
|
442
|
+
if (bridgeGeometryRevisionRafRef.current != null) {
|
|
443
|
+
window.cancelAnimationFrame(bridgeGeometryRevisionRafRef.current);
|
|
222
444
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
if (contentHeight > upperThreshold && minimapEnabled) {
|
|
226
|
-
setShowMiniMap(true);
|
|
445
|
+
if (bridgeDomRevisionRafRef.current != null) {
|
|
446
|
+
window.cancelAnimationFrame(bridgeDomRevisionRafRef.current);
|
|
227
447
|
}
|
|
228
|
-
}
|
|
229
|
-
},
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
448
|
+
};
|
|
449
|
+
}, []);
|
|
450
|
+
const blockIframeBlurUntil = useCallback((_timestamp) => {
|
|
451
|
+
// Iframe blur is handled by the bridge inline-edit lifecycle now.
|
|
452
|
+
}, []);
|
|
453
|
+
const sendBeginInlineEdit = useCallback((payload) => {
|
|
454
|
+
return (bridgeClientRef.current?.sendCommand("beginInlineEdit", payload) ??
|
|
455
|
+
false);
|
|
456
|
+
}, []);
|
|
457
|
+
const sendApplyRichTextCommand = useCallback((payload) => {
|
|
458
|
+
return (bridgeClientRef.current?.sendCommand("applyRichTextCommand", payload) ??
|
|
459
|
+
false);
|
|
460
|
+
}, []);
|
|
461
|
+
const { beginInlineEdit: beginBridgeInlineEdit, clearInlineDedupe: clearBridgeInlineDedupe, endFieldFocus: endBridgeFieldFocus, handleFieldValueChanged: handleBridgeFieldValueChanged, handleInlineEditEnded: handleBridgeInlineEditEnded, } = useBridgeInlineEditing({
|
|
462
|
+
pageViewContextRef,
|
|
463
|
+
activeBridgeInlineEditRef,
|
|
464
|
+
blockIframeBlurUntil,
|
|
465
|
+
sendBeginInlineEdit,
|
|
234
466
|
});
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
editorCssGuardRef.current.observer?.disconnect();
|
|
251
|
-
}
|
|
252
|
-
catch { }
|
|
253
|
-
editorCssGuardRef.current.doc = doc;
|
|
254
|
-
editorCssGuardRef.current.observer = null;
|
|
255
|
-
// Guard against frameworks (Next/Head managers) removing/replacing our injected <style>.
|
|
256
|
-
let isApplying = false;
|
|
257
|
-
const observer = new MutationObserver(() => {
|
|
258
|
-
const currentDoc = editorCssGuardRef.current.doc;
|
|
259
|
-
if (!currentDoc || currentDoc !== doc)
|
|
260
|
-
return;
|
|
261
|
-
if (isApplying)
|
|
262
|
-
return;
|
|
263
|
-
const head = currentDoc.head;
|
|
264
|
-
if (!head)
|
|
265
|
-
return;
|
|
266
|
-
const style = head.querySelector(`#${EDITOR_CSS_STYLE_ID}`);
|
|
267
|
-
if (style)
|
|
268
|
-
return;
|
|
269
|
-
try {
|
|
270
|
-
isApplying = true;
|
|
271
|
-
injectEditorCSS(currentDoc, editorCssGuardRef.current.editMode);
|
|
467
|
+
const beginTrackedBridgeInlineEdit = useCallback(async (interaction) => {
|
|
468
|
+
activeBridgeInlineEditRef.current = null;
|
|
469
|
+
const started = await beginBridgeInlineEdit(interaction);
|
|
470
|
+
if (started && interaction.elementKey && interaction.fieldId) {
|
|
471
|
+
const interactionItem = interaction.item?.id &&
|
|
472
|
+
interaction.item.language &&
|
|
473
|
+
typeof interaction.item.version === "number"
|
|
474
|
+
? {
|
|
475
|
+
id: interaction.item.id,
|
|
476
|
+
language: interaction.item.language,
|
|
477
|
+
version: interaction.item.version,
|
|
478
|
+
name: interaction.item.name,
|
|
479
|
+
displayName: interaction.item.displayName,
|
|
480
|
+
path: interaction.item.path,
|
|
481
|
+
database: interaction.item.database,
|
|
272
482
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
observer.observe(doc.head, { childList: true });
|
|
281
|
-
if (doc.documentElement)
|
|
282
|
-
observer.observe(doc.documentElement, { childList: true });
|
|
283
|
-
}
|
|
284
|
-
catch { }
|
|
285
|
-
editorCssGuardRef.current.observer = observer;
|
|
483
|
+
: undefined;
|
|
484
|
+
activeBridgeInlineEditRef.current = {
|
|
485
|
+
elementKey: interaction.elementKey,
|
|
486
|
+
fieldId: interaction.fieldId,
|
|
487
|
+
item: interactionItem,
|
|
488
|
+
modeAtStart: editContextRef.current?.mode ?? editContext.mode,
|
|
489
|
+
};
|
|
286
490
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
}, 0);
|
|
294
|
-
};
|
|
295
|
-
// Disconnect the CSS guard on unmount.
|
|
491
|
+
return started;
|
|
492
|
+
}, [beginBridgeInlineEdit, editContext.mode, editContextRef]);
|
|
493
|
+
const endTrackedBridgeFieldFocus = useCallback(() => {
|
|
494
|
+
activeBridgeInlineEditRef.current = null;
|
|
495
|
+
endBridgeFieldFocus();
|
|
496
|
+
}, [endBridgeFieldFocus]);
|
|
296
497
|
useEffect(() => {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
498
|
+
const handleBridgeInlineEditRelease = (event) => {
|
|
499
|
+
const detail = event
|
|
500
|
+
.detail;
|
|
501
|
+
const field = detail?.field;
|
|
502
|
+
const activeEdit = activeBridgeInlineEditRef.current;
|
|
503
|
+
if (!field || !activeEdit)
|
|
504
|
+
return;
|
|
505
|
+
if (!bridgeKeysMatch(activeEdit.fieldId, field.fieldId))
|
|
506
|
+
return;
|
|
507
|
+
if (activeEdit.item && !bridgeItemMatches(activeEdit.item, field.item)) {
|
|
508
|
+
return;
|
|
300
509
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
510
|
+
activeBridgeInlineEditRef.current = null;
|
|
511
|
+
bridgePatchSignatureRef.current.delete(`${activeEdit.elementKey}:${activeEdit.fieldId}`);
|
|
512
|
+
};
|
|
513
|
+
window.addEventListener(BRIDGE_INLINE_EDIT_RELEASE_EVENT, handleBridgeInlineEditRelease);
|
|
514
|
+
return () => {
|
|
515
|
+
window.removeEventListener(BRIDGE_INLINE_EDIT_RELEASE_EVENT, handleBridgeInlineEditRelease);
|
|
304
516
|
};
|
|
305
517
|
}, []);
|
|
306
|
-
// If the editor mode flips (edit/preview), re-apply CSS and disable inline editing in the iframe.
|
|
307
518
|
useEffect(() => {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
519
|
+
return editContext.registerModeChangeParticipant({
|
|
520
|
+
beforeModeChange: () => {
|
|
521
|
+
editContext.operations.onFieldBlur?.();
|
|
522
|
+
void fieldsContextRef.current?.setFocusedField(undefined, false);
|
|
523
|
+
endBridgeFieldFocus();
|
|
524
|
+
},
|
|
525
|
+
clearInlineDedupe: () => {
|
|
526
|
+
clearBridgeInlineDedupe();
|
|
527
|
+
},
|
|
528
|
+
});
|
|
529
|
+
}, [
|
|
530
|
+
clearBridgeInlineDedupe,
|
|
531
|
+
editContext,
|
|
532
|
+
endBridgeFieldFocus,
|
|
533
|
+
fieldsContextRef,
|
|
534
|
+
]);
|
|
535
|
+
const getBridgePatchDisplayValue = useCallback(({ field, structureField, itemDescriptor, preferRepositoryValue, }) => {
|
|
536
|
+
const showSuggestions = editContext.mode === "suggestions" || editContext.showSuggestedEdits;
|
|
537
|
+
const repositoryValue = getBridgeFieldPatchValue(field);
|
|
538
|
+
const display = getSuggestionDisplayValue({
|
|
539
|
+
repositoryValue,
|
|
540
|
+
modifiedFields: preferRepositoryValue
|
|
541
|
+
? undefined
|
|
542
|
+
: fieldsContext?.modifiedFields,
|
|
543
|
+
suggestedEdits: editContext.suggestedEdits,
|
|
544
|
+
field: {
|
|
545
|
+
fieldId: field.id || structureField.fieldId,
|
|
546
|
+
itemId: itemDescriptor.id,
|
|
547
|
+
language: itemDescriptor.language,
|
|
548
|
+
version: itemDescriptor.version,
|
|
549
|
+
pageItemId: pageViewContext.pageItemDescriptor?.id,
|
|
550
|
+
pageItemVersion: pageViewContext.pageItemDescriptor?.version,
|
|
551
|
+
},
|
|
552
|
+
mode: showSuggestions ? "suggestions" : "baseline",
|
|
553
|
+
});
|
|
554
|
+
return {
|
|
555
|
+
value: showSuggestions ? display.mergedValue : display.baselineValue,
|
|
556
|
+
source: showSuggestions && display.hasSuggestions
|
|
557
|
+
? "suggestion-preview"
|
|
558
|
+
: "real",
|
|
559
|
+
};
|
|
560
|
+
}, [
|
|
561
|
+
editContext.mode,
|
|
562
|
+
editContext.showSuggestedEdits,
|
|
563
|
+
editContext.suggestedEdits,
|
|
564
|
+
fieldsContext?.modifiedFields,
|
|
565
|
+
pageViewContext.pageItemDescriptor,
|
|
566
|
+
]);
|
|
567
|
+
const buildBridgeFieldPatch = useCallback(({ field, structureField, itemDescriptor, preferRepositoryValue, }) => {
|
|
568
|
+
const display = getBridgePatchDisplayValue({
|
|
569
|
+
field,
|
|
570
|
+
structureField,
|
|
571
|
+
itemDescriptor,
|
|
572
|
+
preferRepositoryValue,
|
|
573
|
+
});
|
|
574
|
+
const activeInlineEdit = activeBridgeInlineEditRef.current;
|
|
575
|
+
const patch = buildBridgeFieldPatchPayload({
|
|
576
|
+
field,
|
|
577
|
+
structureField,
|
|
578
|
+
display,
|
|
579
|
+
activeInlineEdit,
|
|
580
|
+
});
|
|
581
|
+
return patch;
|
|
582
|
+
}, [getBridgePatchDisplayValue]);
|
|
583
|
+
const clearBridgePatchSignatures = useCallback(() => {
|
|
584
|
+
bridgePatchSignatureRef.current.clear();
|
|
585
|
+
}, []);
|
|
586
|
+
const sendBridgeFieldPatch = useCallback((bridge, patch, observedTextContent) => {
|
|
587
|
+
const cacheKey = `${patch.elementKey}:${patch.fieldId}`;
|
|
588
|
+
const signature = JSON.stringify({
|
|
589
|
+
value: patch.value,
|
|
590
|
+
isRichText: !!patch.isRichText,
|
|
591
|
+
source: patch.source ?? "",
|
|
592
|
+
observedTextContent: observedTextContent ?? "",
|
|
593
|
+
});
|
|
594
|
+
if (bridgePatchSignatureRef.current.get(cacheKey) === signature) {
|
|
595
|
+
return false;
|
|
315
596
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
597
|
+
bridgePatchSignatureRef.current.set(cacheKey, signature);
|
|
598
|
+
bridge.sendCommand("applyFieldPatch", patch);
|
|
599
|
+
return true;
|
|
600
|
+
}, []);
|
|
601
|
+
const clearBridgeRemoteCaretPosition = useCallback(() => {
|
|
602
|
+
const editor = editContextRef.current;
|
|
603
|
+
if (!editor || lastSentBridgeCaretRef.current === "clear")
|
|
604
|
+
return;
|
|
605
|
+
editor.sendSocketMessage({
|
|
606
|
+
type: "caret-position",
|
|
607
|
+
payload: { offset: null },
|
|
608
|
+
});
|
|
609
|
+
lastSentBridgeCaretRef.current = "clear";
|
|
610
|
+
}, [editContextRef]);
|
|
611
|
+
const sendBridgeRemoteCaretPosition = useCallback((selection) => {
|
|
612
|
+
const editor = editContextRef.current;
|
|
613
|
+
const activeField = selection.activeField;
|
|
614
|
+
const offset = selection.startOffset ?? selection.endOffset;
|
|
615
|
+
if (!editor ||
|
|
616
|
+
!activeField?.fieldId ||
|
|
617
|
+
!selection.collapsed ||
|
|
618
|
+
offset == null) {
|
|
619
|
+
clearBridgeRemoteCaretPosition();
|
|
321
620
|
return;
|
|
322
|
-
const urlPath = editContext.mode === "preview"
|
|
323
|
-
? pageViewContext.previewUrl
|
|
324
|
-
: pageViewContext.editUrl;
|
|
325
|
-
const prevMode = prevModeRef.current;
|
|
326
|
-
prevModeRef.current = editContext.mode;
|
|
327
|
-
const modeOnlyChange = prevMode !== editContext.mode &&
|
|
328
|
-
prevMode !== "preview" &&
|
|
329
|
-
editContext.mode !== "preview";
|
|
330
|
-
let savedScrollY = 0;
|
|
331
|
-
try {
|
|
332
|
-
savedScrollY = iframeRef.current?.contentWindow?.scrollY ?? 0;
|
|
333
|
-
}
|
|
334
|
-
catch {
|
|
335
|
-
savedScrollY = 0;
|
|
336
|
-
}
|
|
337
|
-
const renderUrl = new URL(urlPath, window.location.origin);
|
|
338
|
-
const editRev = uuid();
|
|
339
|
-
renderUrl.searchParams.set("edit_rev", editRev);
|
|
340
|
-
if (editContext.mode !== "preview" && editContext.sessionId) {
|
|
341
|
-
renderUrl.searchParams.set("parhelia_session", editContext.sessionId);
|
|
342
|
-
}
|
|
343
|
-
if (editContext.mode === "preview" && editContext.previewDate) {
|
|
344
|
-
renderUrl.searchParams.delete("sc_version");
|
|
345
|
-
renderUrl.searchParams.set("sc_date", toSitecoreDate(editContext.previewDate));
|
|
346
621
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
622
|
+
const sourceItem = activeField.item ??
|
|
623
|
+
pageViewContextRef.current?.page?.item ??
|
|
624
|
+
pageViewContextRef.current?.pageItemDescriptor;
|
|
625
|
+
const version = typeof sourceItem?.version === "number" &&
|
|
626
|
+
Number.isFinite(sourceItem.version)
|
|
627
|
+
? sourceItem.version
|
|
628
|
+
: undefined;
|
|
629
|
+
if (!sourceItem?.id || !sourceItem.language || version === undefined) {
|
|
630
|
+
clearBridgeRemoteCaretPosition();
|
|
631
|
+
return;
|
|
353
632
|
}
|
|
354
|
-
|
|
355
|
-
|
|
633
|
+
const item = {
|
|
634
|
+
...sourceItem,
|
|
635
|
+
version,
|
|
636
|
+
};
|
|
637
|
+
const nextKey = `${activeField.fieldId}:${item.id}:${item.language}:${item.version}:${offset}`;
|
|
638
|
+
if (lastSentBridgeCaretRef.current === nextKey)
|
|
639
|
+
return;
|
|
640
|
+
editor.sendSocketMessage({
|
|
641
|
+
type: "caret-position",
|
|
642
|
+
payload: {
|
|
643
|
+
fieldId: activeField.fieldId,
|
|
644
|
+
item,
|
|
645
|
+
offset,
|
|
646
|
+
},
|
|
647
|
+
});
|
|
648
|
+
lastSentBridgeCaretRef.current = nextKey;
|
|
649
|
+
}, [clearBridgeRemoteCaretPosition, editContextRef, pageViewContextRef]);
|
|
650
|
+
const handleBridgeSelection = useCallback((selection, iframe) => {
|
|
651
|
+
const editor = editContextRef.current;
|
|
652
|
+
if (!editor)
|
|
653
|
+
return;
|
|
654
|
+
const isInlineAiUiFocused = () => {
|
|
655
|
+
const activeEl = document.activeElement;
|
|
656
|
+
return !!(activeEl?.closest(".agent-inline-dialog") ||
|
|
657
|
+
activeEl?.closest(".agent-inline-trigger") ||
|
|
658
|
+
activeEl?.closest('[role="dialog"]'));
|
|
659
|
+
};
|
|
660
|
+
const isInlineAiUiPresent = () => !!document.querySelector(".agent-inline-dialog, .agent-inline-trigger");
|
|
661
|
+
const inlineAiUiFocused = isInlineAiUiFocused();
|
|
662
|
+
const inlineAiUiPresent = isInlineAiUiPresent();
|
|
663
|
+
if (selection.collapsed && (inlineAiUiFocused || inlineAiUiPresent)) {
|
|
664
|
+
return;
|
|
356
665
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
666
|
+
if (selection.collapsed ||
|
|
667
|
+
!selection.text ||
|
|
668
|
+
!selection.activeField?.fieldId) {
|
|
669
|
+
sendBridgeRemoteCaretPosition(selection);
|
|
670
|
+
if (!inlineAiUiFocused && !inlineAiUiPresent) {
|
|
671
|
+
editor.setSelectedRange(undefined);
|
|
672
|
+
}
|
|
673
|
+
return;
|
|
362
674
|
}
|
|
363
|
-
|
|
364
|
-
|
|
675
|
+
clearBridgeRemoteCaretPosition();
|
|
676
|
+
const item = selection.activeField.item ??
|
|
677
|
+
pageViewContextRef.current?.page?.item ??
|
|
678
|
+
pageViewContextRef.current?.pageItemDescriptor;
|
|
679
|
+
if (!item?.id) {
|
|
680
|
+
editor.setSelectedRange(undefined);
|
|
681
|
+
return;
|
|
365
682
|
}
|
|
366
|
-
const
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
const refreshAccepted = refreshFn(renderUrl.toString());
|
|
379
|
-
if (refreshAccepted === false) {
|
|
380
|
-
runFallbackRefresh();
|
|
381
|
-
return;
|
|
683
|
+
const metadata = selection.metadata ?? {};
|
|
684
|
+
const iframeRect = iframe.getBoundingClientRect();
|
|
685
|
+
const selectionRect = selection.rect
|
|
686
|
+
? {
|
|
687
|
+
x: iframeRect.left + selection.rect.left,
|
|
688
|
+
y: iframeRect.top + selection.rect.top,
|
|
689
|
+
width: selection.rect.width,
|
|
690
|
+
height: selection.rect.height,
|
|
691
|
+
top: iframeRect.top + selection.rect.top,
|
|
692
|
+
right: iframeRect.left + selection.rect.right,
|
|
693
|
+
bottom: iframeRect.top + selection.rect.bottom,
|
|
694
|
+
left: iframeRect.left + selection.rect.left,
|
|
382
695
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
696
|
+
: undefined;
|
|
697
|
+
const nextRange = {
|
|
698
|
+
itemId: item.id,
|
|
699
|
+
fieldId: selection.activeField.fieldId,
|
|
700
|
+
elementKey: selection.activeField.elementKey,
|
|
701
|
+
isRichText: selection.activeField.isRichText,
|
|
702
|
+
language: item.language,
|
|
703
|
+
version: item.version,
|
|
704
|
+
startOffset: selection.startOffset ?? 0,
|
|
705
|
+
endOffset: selection.endOffset ?? selection.startOffset ?? 0,
|
|
706
|
+
text: selection.text,
|
|
707
|
+
contextBefore: typeof metadata.contextBefore === "string"
|
|
708
|
+
? metadata.contextBefore
|
|
709
|
+
: undefined,
|
|
710
|
+
contextAfter: typeof metadata.contextAfter === "string"
|
|
711
|
+
? metadata.contextAfter
|
|
712
|
+
: undefined,
|
|
713
|
+
formatting: selection.formatting,
|
|
714
|
+
clientRect: selectionRect,
|
|
715
|
+
};
|
|
716
|
+
editor.setSelectedRange(nextRange);
|
|
717
|
+
}, [clearBridgeRemoteCaretPosition, sendBridgeRemoteCaretPosition]);
|
|
718
|
+
const resolveBridgeFieldDescriptor = useCallback(async (interaction) => {
|
|
719
|
+
const pageView = pageViewContextRef.current;
|
|
720
|
+
const selectedRange = editContextRef.current?.selectedRange;
|
|
721
|
+
const selectedRangeElementKey = selectedRange?.elementKey;
|
|
722
|
+
const selectedRangeMatchesInteraction = !!((interaction.kind === "contextMenu" ||
|
|
723
|
+
interaction.kind === "keydown") &&
|
|
724
|
+
selectedRange?.text &&
|
|
725
|
+
selectedRange.fieldId &&
|
|
726
|
+
((selectedRangeElementKey &&
|
|
727
|
+
interaction.elementKey &&
|
|
728
|
+
selectedRangeElementKey === interaction.elementKey) ||
|
|
729
|
+
bridgeIdsMatch(selectedRange.fieldId, interaction.fieldId) ||
|
|
730
|
+
bridgeIdsMatch(selectedRange.itemId, interaction.item?.id)));
|
|
731
|
+
const fieldId = selectedRangeMatchesInteraction
|
|
732
|
+
? selectedRange?.fieldId
|
|
733
|
+
: interaction.fieldId;
|
|
734
|
+
if (!fieldId)
|
|
735
|
+
return undefined;
|
|
736
|
+
const targetElementKey = selectedRangeMatchesInteraction
|
|
737
|
+
? selectedRangeElementKey
|
|
738
|
+
: interaction.elementKey;
|
|
739
|
+
const structureFields = pageView?.bridgeStructure?.fields ?? [];
|
|
740
|
+
const structureField = structureFields.find((field) => bridgeIdsMatch(field.fieldId, fieldId)) ??
|
|
741
|
+
structureFields.find((field) => targetElementKey && field.elementKey === targetElementKey);
|
|
742
|
+
const fallbackItem = pageView?.page?.item ?? pageView?.pageItemDescriptor;
|
|
743
|
+
const componentId = resolveBridgeComponentId(interaction.componentId ?? structureField?.componentId, pageView?.page);
|
|
744
|
+
const component = componentId && pageView?.page
|
|
745
|
+
? getComponentById(componentId, pageView.page)
|
|
746
|
+
: undefined;
|
|
747
|
+
const candidateItems = [];
|
|
748
|
+
const selectedRangeItem = selectedRangeMatchesInteraction &&
|
|
749
|
+
selectedRange?.itemId &&
|
|
750
|
+
selectedRange.language &&
|
|
751
|
+
typeof selectedRange.version === "number"
|
|
752
|
+
? {
|
|
753
|
+
id: selectedRange.itemId,
|
|
754
|
+
language: selectedRange.language,
|
|
755
|
+
version: selectedRange.version,
|
|
756
|
+
database: fallbackItem?.database,
|
|
386
757
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
if (modeOnlyChange && savedScrollY > 0) {
|
|
402
|
-
const restoreScroll = (delay) => {
|
|
403
|
-
setTimeout(() => {
|
|
404
|
-
const win = iframeRef.current?.contentWindow;
|
|
405
|
-
if (win && win.scrollY === 0) {
|
|
406
|
-
win.scrollTo(0, savedScrollY);
|
|
407
|
-
}
|
|
408
|
-
}, delay);
|
|
409
|
-
};
|
|
410
|
-
restoreScroll(50);
|
|
411
|
-
restoreScroll(200);
|
|
412
|
-
restoreScroll(500);
|
|
413
|
-
restoreScroll(1000);
|
|
414
|
-
restoreScroll(2000);
|
|
758
|
+
: undefined;
|
|
759
|
+
const addCandidateItem = (source) => {
|
|
760
|
+
const itemId = source?.id ?? fallbackItem?.id;
|
|
761
|
+
const language = source?.language ?? fallbackItem?.language;
|
|
762
|
+
const versionValue = source?.version ?? fallbackItem?.version;
|
|
763
|
+
const version = typeof versionValue === "number" && Number.isFinite(versionValue)
|
|
764
|
+
? versionValue
|
|
765
|
+
: undefined;
|
|
766
|
+
if (!itemId || !language || version === undefined)
|
|
767
|
+
return;
|
|
768
|
+
if (candidateItems.some((item) => bridgeIdsMatch(item.id, itemId) &&
|
|
769
|
+
item.language.toLowerCase() === language.toLowerCase() &&
|
|
770
|
+
item.version === version)) {
|
|
771
|
+
return;
|
|
415
772
|
}
|
|
773
|
+
candidateItems.push({
|
|
774
|
+
id: itemId,
|
|
775
|
+
language,
|
|
776
|
+
version,
|
|
777
|
+
name: source?.name ?? fallbackItem?.name,
|
|
778
|
+
displayName: source?.displayName ?? fallbackItem?.displayName,
|
|
779
|
+
path: source?.path ?? fallbackItem?.path,
|
|
780
|
+
database: source?.database ?? fallbackItem?.database,
|
|
781
|
+
});
|
|
416
782
|
};
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
783
|
+
addCandidateItem(selectedRangeItem);
|
|
784
|
+
addCandidateItem(interaction.item);
|
|
785
|
+
addCandidateItem(structureField?.item);
|
|
786
|
+
addCandidateItem(component?.datasourceItem);
|
|
787
|
+
component?.items.forEach(addCandidateItem);
|
|
788
|
+
addCandidateItem(fallbackItem);
|
|
789
|
+
for (const item of candidateItems) {
|
|
790
|
+
const repositoryItem = await editContextRef.current?.itemsRepository.getItem(item);
|
|
791
|
+
const repositoryField = repositoryItem?.fields.find((field) => fieldIdentifierMatches(field, fieldId));
|
|
792
|
+
if (repositoryField) {
|
|
793
|
+
return {
|
|
794
|
+
fieldId: repositoryField.id,
|
|
795
|
+
item,
|
|
796
|
+
};
|
|
429
797
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
798
|
+
}
|
|
799
|
+
const fallbackDescriptorItem = candidateItems[0];
|
|
800
|
+
if (!fallbackDescriptorItem)
|
|
801
|
+
return undefined;
|
|
802
|
+
return {
|
|
803
|
+
fieldId,
|
|
804
|
+
item: fallbackDescriptorItem,
|
|
805
|
+
};
|
|
806
|
+
}, [editContextRef]);
|
|
807
|
+
const handleBridgeInteraction = useCallback(async (interaction, iframe) => {
|
|
808
|
+
if (interaction.kind === "wheel") {
|
|
809
|
+
if (!interaction.ctrlKey && !interaction.metaKey)
|
|
810
|
+
return;
|
|
811
|
+
if (!interaction.deltaY)
|
|
812
|
+
return;
|
|
813
|
+
const zoomContext = (compareView ? slotContext?.primaryPageViewContext : undefined) ??
|
|
814
|
+
pageViewContextRef.current;
|
|
815
|
+
const direction = interaction.deltaY < 0 ? 1 : -1;
|
|
816
|
+
zoomContext?.setZoom((value) => clampEditorZoom(value + direction * ZOOM_STEP));
|
|
817
|
+
return;
|
|
818
|
+
}
|
|
819
|
+
if (interaction.kind === "keydown") {
|
|
820
|
+
if (isBridgeInlineAiShortcut(interaction)) {
|
|
821
|
+
const editor = editContextRef.current;
|
|
822
|
+
const field = await resolveBridgeFieldDescriptor(interaction);
|
|
823
|
+
if (editor && field) {
|
|
824
|
+
await editor.setFocusedField(field, editor.mode !== "suggestions");
|
|
435
825
|
}
|
|
436
|
-
|
|
437
|
-
const
|
|
438
|
-
const
|
|
439
|
-
|
|
440
|
-
|
|
826
|
+
const iframeRect = iframe.getBoundingClientRect();
|
|
827
|
+
const bridgeSelection = pageViewContextRef.current?.bridgeSelection;
|
|
828
|
+
const fallbackCaretRect = !interaction.selectionRect &&
|
|
829
|
+
!interaction.caretRect &&
|
|
830
|
+
bridgeSelection?.collapsed &&
|
|
831
|
+
bridgeSelection.rect &&
|
|
832
|
+
bridgeSelection.activeField?.fieldId &&
|
|
833
|
+
bridgeIdsMatch(bridgeSelection.activeField.fieldId, interaction.fieldId)
|
|
834
|
+
? bridgeSelection.rect
|
|
835
|
+
: undefined;
|
|
836
|
+
const resolvedSelectionRect = interaction.selectionRect ??
|
|
837
|
+
interaction.caretRect ??
|
|
838
|
+
fallbackCaretRect;
|
|
839
|
+
const toolbarRect = await waitForVisibleBridgeToolbarRect();
|
|
840
|
+
const selectedRange = editContextRef.current?.selectedRange ?? editor?.selectedRange;
|
|
841
|
+
const toolbarAnchor = getInlineAiAnchorFromBridgeToolbar(selectedRange, toolbarRect ?? getVisibleBridgeToolbarRect());
|
|
842
|
+
const caretAnchor = toolbarAnchor
|
|
843
|
+
? toolbarAnchor
|
|
844
|
+
: selectedRange?.text && selectedRange.clientRect
|
|
845
|
+
? {
|
|
846
|
+
x: selectedRange.clientRect.left +
|
|
847
|
+
selectedRange.clientRect.width / 2,
|
|
848
|
+
y: selectedRange.clientRect.bottom,
|
|
849
|
+
origin: "caret",
|
|
850
|
+
}
|
|
851
|
+
: resolvedSelectionRect
|
|
852
|
+
? {
|
|
853
|
+
x: iframeRect.left +
|
|
854
|
+
resolvedSelectionRect.left +
|
|
855
|
+
(resolvedSelectionRect.width ?? 0) / 2,
|
|
856
|
+
y: iframeRect.top + resolvedSelectionRect.bottom,
|
|
857
|
+
origin: "caret",
|
|
858
|
+
}
|
|
859
|
+
: undefined;
|
|
860
|
+
document.dispatchEvent(new CustomEvent("inline-ai-open", {
|
|
861
|
+
bubbles: true,
|
|
862
|
+
cancelable: true,
|
|
863
|
+
detail: caretAnchor ? { anchor: caretAnchor } : undefined,
|
|
864
|
+
}));
|
|
441
865
|
}
|
|
866
|
+
return;
|
|
442
867
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
let integrationRefreshFn;
|
|
446
|
-
try {
|
|
447
|
-
integrationRefreshFn = iframeRef.current?.contentWindow?.requestRefresh;
|
|
868
|
+
if (interaction.kind !== "click" && interaction.kind !== "contextMenu") {
|
|
869
|
+
return;
|
|
448
870
|
}
|
|
449
|
-
|
|
450
|
-
integrationRefreshFn = undefined;
|
|
451
|
-
}
|
|
452
|
-
if (!versionChanged && integrationRefreshFn) {
|
|
453
|
-
runIntegrationRefresh(integrationRefreshFn, "Integration - requesting refresh");
|
|
454
|
-
}
|
|
455
|
-
else if (!versionChanged && !shouldUseIframeSrcReload) {
|
|
456
|
-
const retryDelayMs = 150;
|
|
457
|
-
const retryTimer = setTimeout(() => {
|
|
458
|
-
let retryIntegrationRefreshFn;
|
|
459
|
-
try {
|
|
460
|
-
retryIntegrationRefreshFn =
|
|
461
|
-
iframeRef.current?.contentWindow?.requestRefresh;
|
|
462
|
-
}
|
|
463
|
-
catch {
|
|
464
|
-
retryIntegrationRefreshFn = undefined;
|
|
465
|
-
}
|
|
466
|
-
if (retryIntegrationRefreshFn) {
|
|
467
|
-
runIntegrationRefresh(retryIntegrationRefreshFn, "Integration became available after brief wait - requesting refresh");
|
|
468
|
-
return;
|
|
469
|
-
}
|
|
470
|
-
runFallbackRefresh();
|
|
471
|
-
}, retryDelayMs);
|
|
472
|
-
setCurrentItemDescriptor(pageItemDescriptor);
|
|
473
|
-
return () => clearTimeout(retryTimer);
|
|
474
|
-
}
|
|
475
|
-
else {
|
|
476
|
-
runFallbackRefresh();
|
|
477
|
-
}
|
|
478
|
-
setCurrentItemDescriptor(pageItemDescriptor);
|
|
479
|
-
}, [
|
|
480
|
-
pathname,
|
|
481
|
-
editContext.revision,
|
|
482
|
-
pageItemDescriptor,
|
|
483
|
-
pageViewContext.editUrl,
|
|
484
|
-
pageViewContext.previewUrl,
|
|
485
|
-
pageViewContext.fullscreen,
|
|
486
|
-
editContext.mode,
|
|
487
|
-
editContext.previewDate,
|
|
488
|
-
editContext.sessionId,
|
|
489
|
-
editContext.layoutMode,
|
|
490
|
-
]);
|
|
491
|
-
useEffect(() => {
|
|
492
|
-
if (fieldsContext?.focusedField) {
|
|
493
|
-
if (editContext.selection.length > 0 &&
|
|
494
|
-
fieldsContext.focusedField.item.id !== editContext.selection[0])
|
|
495
|
-
return;
|
|
496
|
-
let fieldElement;
|
|
497
|
-
try {
|
|
498
|
-
fieldElement = findFieldElement(iframeRef.current, fieldsContext.focusedField);
|
|
499
|
-
}
|
|
500
|
-
catch {
|
|
501
|
-
fieldElement = null;
|
|
502
|
-
}
|
|
503
|
-
if (fieldElement) {
|
|
504
|
-
const rect = getAbsolutePosition(fieldElement, iframeRef.current);
|
|
505
|
-
let scrollTop = 0;
|
|
506
|
-
try {
|
|
507
|
-
scrollTop = iframeRef.current?.contentWindow?.scrollY || 0;
|
|
508
|
-
}
|
|
509
|
-
catch {
|
|
510
|
-
scrollTop = 0;
|
|
511
|
-
}
|
|
512
|
-
const iframeHeight = iframeRef.current?.getBoundingClientRect().height;
|
|
513
|
-
const isInViewport = rect.y + rect.height > scrollTop &&
|
|
514
|
-
rect.y < scrollTop + (iframeHeight || 0);
|
|
515
|
-
if (!isInViewport) {
|
|
516
|
-
try {
|
|
517
|
-
iframeRef.current?.contentWindow?.scrollTo({
|
|
518
|
-
top: rect.y,
|
|
519
|
-
behavior: "smooth",
|
|
520
|
-
});
|
|
521
|
-
}
|
|
522
|
-
catch { }
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
}
|
|
526
|
-
}, [fieldsContext?.focusedField]);
|
|
527
|
-
useEffect(() => {
|
|
528
|
-
if (!fieldsContext?.focusedField && editContext.selection.length > 0) {
|
|
529
|
-
const lastSelectedComponent = getComponentById(editContext.selection[editContext.selection.length - 1], pageViewContextRef.current.page);
|
|
530
|
-
if (lastSelectedComponent) {
|
|
531
|
-
editContext.setScrollIntoView(lastSelectedComponent.id);
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
}, [editContext.selection, fieldsContext?.focusedField]);
|
|
535
|
-
const loadContent = async (href, initialLoad, expectedRevision, signal) => {
|
|
536
|
-
console.log("Loading content:", href);
|
|
537
|
-
const start = performance.now();
|
|
538
|
-
if (!href)
|
|
539
|
-
return;
|
|
540
|
-
try {
|
|
541
|
-
const content = await fetch(href, { signal });
|
|
542
|
-
// Detect redirect to login page (session expired)
|
|
543
|
-
if (content.redirected) {
|
|
544
|
-
const redirectUrl = content.url.toLowerCase();
|
|
545
|
-
if (redirectUrl.includes("/identity/login") ||
|
|
546
|
-
redirectUrl.includes("/sitecore/login") ||
|
|
547
|
-
redirectUrl.includes("returnurl=")) {
|
|
548
|
-
console.warn("Session expired - redirected to login page:", content.url);
|
|
549
|
-
setShowSpinner(false);
|
|
550
|
-
// Redirect the main window to the login page
|
|
551
|
-
window.location.href = content.url;
|
|
552
|
-
return;
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
const text = await content.text();
|
|
556
|
-
console.log("Content loaded in " + (performance.now() - start) + " ms");
|
|
557
|
-
// Skip applying if this response is stale
|
|
558
|
-
if (expectedRevision !== (editContextRef.current?.revision ?? "")) {
|
|
559
|
-
console.log("Stale refresh skipped", {
|
|
560
|
-
expectedRevision,
|
|
561
|
-
current: editContextRef.current?.revision,
|
|
562
|
-
});
|
|
563
|
-
return;
|
|
564
|
-
}
|
|
565
|
-
console.log("Content loaded in " + (performance.now() - start) + " ms");
|
|
566
|
-
const doc = getAccessibleIframeDocument(iframeRef.current, "PageViewerFrame.tsx:loadContent-doc");
|
|
567
|
-
if (doc) {
|
|
568
|
-
if (initialLoad) {
|
|
569
|
-
// Guard again just before applying
|
|
570
|
-
if (expectedRevision !== (editContextRef.current?.revision ?? "")) {
|
|
571
|
-
console.log("Stale initial load skipped", {
|
|
572
|
-
expectedRevision,
|
|
573
|
-
current: editContextRef.current?.revision,
|
|
574
|
-
});
|
|
575
|
-
return;
|
|
576
|
-
}
|
|
577
|
-
doc.open();
|
|
578
|
-
doc.write(text);
|
|
579
|
-
doc.close();
|
|
580
|
-
}
|
|
581
|
-
else {
|
|
582
|
-
const parser = new DOMParser();
|
|
583
|
-
const newDoc = parser.parseFromString(text, "text/html");
|
|
584
|
-
// Guard before morphing DOM to avoid applying stale content
|
|
585
|
-
if (expectedRevision !== (editContextRef.current?.revision ?? "")) {
|
|
586
|
-
console.log("Stale morphdom skipped", {
|
|
587
|
-
expectedRevision,
|
|
588
|
-
current: editContextRef.current?.revision,
|
|
589
|
-
});
|
|
590
|
-
return;
|
|
591
|
-
}
|
|
592
|
-
const morphTarget = doc.body && newDoc.body ? "body" : "documentElement";
|
|
593
|
-
if (morphTarget === "body") {
|
|
594
|
-
morphdom(doc.body, newDoc.body);
|
|
595
|
-
}
|
|
596
|
-
else {
|
|
597
|
-
morphdom(doc.documentElement, newDoc.documentElement);
|
|
598
|
-
}
|
|
599
|
-
rebindIframeInteractionsRef.current?.();
|
|
600
|
-
try {
|
|
601
|
-
const xa = iframeRef.current.contentWindow.XA;
|
|
602
|
-
if (xa) {
|
|
603
|
-
console.log("init XA");
|
|
604
|
-
xa.init();
|
|
605
|
-
}
|
|
606
|
-
}
|
|
607
|
-
catch { }
|
|
608
|
-
setShowSpinner(false);
|
|
609
|
-
}
|
|
610
|
-
ensureEditorCssGuard(doc, editContext.mode !== "preview");
|
|
611
|
-
setTimeout(() => {
|
|
612
|
-
injectSXAScripts(iframeRef.current);
|
|
613
|
-
}, 1000);
|
|
614
|
-
try {
|
|
615
|
-
requestPageModelBuild(doc);
|
|
616
|
-
}
|
|
617
|
-
catch (buildErr) { }
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
catch (err) {
|
|
621
|
-
if (err?.name === "AbortError") {
|
|
622
|
-
// Swallow aborts – a newer refresh superseded this one
|
|
623
|
-
return;
|
|
624
|
-
}
|
|
625
|
-
throw err;
|
|
626
|
-
}
|
|
627
|
-
};
|
|
628
|
-
useEffect(() => {
|
|
629
|
-
const iframeDoc = getAccessibleIframeDocument(iframeRef.current, "PageViewerFrame.tsx:scrollIntoView-doc");
|
|
630
|
-
if (!editContext.scrollIntoView || !iframeDoc?.documentElement)
|
|
631
|
-
return;
|
|
632
|
-
const component = getComponentById(editContext.scrollIntoView, pageViewContextRef.current.page);
|
|
633
|
-
if (!component)
|
|
634
|
-
return;
|
|
635
|
-
let rect;
|
|
636
|
-
try {
|
|
637
|
-
rect = findComponentRect(iframeRef.current, component, true);
|
|
638
|
-
}
|
|
639
|
-
catch {
|
|
640
|
-
rect = undefined;
|
|
641
|
-
}
|
|
642
|
-
if (!rect)
|
|
871
|
+
if (interaction.kind === "click" && interaction.button !== 0)
|
|
643
872
|
return;
|
|
644
|
-
if (
|
|
873
|
+
if (interaction.kind === "contextMenu" && interaction.ctrlKey)
|
|
645
874
|
return;
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
let scrollTop = 0;
|
|
649
|
-
try {
|
|
650
|
-
scrollTop = iframeRef.current?.contentWindow?.scrollY || 0;
|
|
651
|
-
}
|
|
652
|
-
catch {
|
|
653
|
-
scrollTop = 0;
|
|
654
|
-
}
|
|
655
|
-
const elementTop = rect.rect.y;
|
|
656
|
-
const isInViewport = elementTop >= scrollTop && elementTop <= scrollTop + iframeHeight;
|
|
657
|
-
// If already in viewport, no need to scroll
|
|
658
|
-
if (isInViewport) {
|
|
659
|
-
editContext.setScrollIntoView(undefined);
|
|
875
|
+
const editor = editContextRef.current;
|
|
876
|
+
if (!editor)
|
|
660
877
|
return;
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
iframeRef.current?.contentWindow?.scrollTo({
|
|
665
|
-
top: scrollPosition,
|
|
666
|
-
behavior: "smooth",
|
|
667
|
-
});
|
|
668
|
-
}
|
|
669
|
-
catch { }
|
|
670
|
-
editContext.setScrollIntoView(undefined);
|
|
671
|
-
}, [editContext.scrollIntoView, pageViewContext.page]);
|
|
672
|
-
useEffect(() => {
|
|
673
|
-
const handleMessage = (message) => {
|
|
674
|
-
if (message.origin !== window.location.origin)
|
|
675
|
-
return;
|
|
676
|
-
if (message.data.type === "editor-exitFullscreen") {
|
|
677
|
-
pageViewContext.setFullscreen(false);
|
|
678
|
-
}
|
|
679
|
-
if (message.data.type === "editor-timings") {
|
|
680
|
-
editContext.setTimings(message.data.timings);
|
|
878
|
+
const selectFromBridgeInteraction = (ids) => {
|
|
879
|
+
if (!bridgeComponentSelectionsMatch(editor.selection, ids)) {
|
|
880
|
+
suppressNextSelectionScrollRef.current = true;
|
|
681
881
|
}
|
|
882
|
+
editor.select(ids);
|
|
682
883
|
};
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
};
|
|
696
|
-
// Whether the inline AI UI is currently mounted (regardless of focus). When the
|
|
697
|
-
// AI dialog is open, the selection may transiently collapse (e.g. a click falling
|
|
698
|
-
// through into the iframe right after opening from the context menu). We must not
|
|
699
|
-
// wipe the selectedRange in that case, otherwise the dialog closes immediately.
|
|
700
|
-
const isInlineAiUiPresent = () => !!document.querySelector(".agent-inline-dialog, .agent-inline-trigger");
|
|
701
|
-
if (!sel || sel.rangeCount === 0) {
|
|
702
|
-
if (!isInlineAiUiFocused() && !isInlineAiUiPresent()) {
|
|
703
|
-
editContextRef.current?.setSelectedRange(undefined);
|
|
704
|
-
}
|
|
705
|
-
return;
|
|
706
|
-
}
|
|
707
|
-
// Preserve the last non-collapsed selection while the inline AI UI is open.
|
|
708
|
-
// This avoids losing context when users click the trigger or dialog.
|
|
709
|
-
if (sel.isCollapsed && (isInlineAiUiFocused() || isInlineAiUiPresent())) {
|
|
710
|
-
return;
|
|
711
|
-
}
|
|
712
|
-
// Find the field element containing the selection/caret
|
|
713
|
-
const fieldElement = findClosestFieldElement(sel.anchorNode);
|
|
714
|
-
if (!fieldElement) {
|
|
715
|
-
if (!isInlineAiUiFocused() && !isInlineAiUiPresent()) {
|
|
716
|
-
editContextRef.current?.setSelectedRange(undefined);
|
|
884
|
+
let effectiveInteraction = interaction;
|
|
885
|
+
if (interaction.kind === "contextMenu" &&
|
|
886
|
+
!interaction.fieldId &&
|
|
887
|
+
interaction.clientX !== undefined &&
|
|
888
|
+
interaction.clientY !== undefined) {
|
|
889
|
+
const fieldTarget = findBridgeFieldTargetAtPoint(pageViewContextRef.current?.bridgeGeometry, interaction.clientX, interaction.clientY);
|
|
890
|
+
if (fieldTarget?.fieldId) {
|
|
891
|
+
effectiveInteraction = {
|
|
892
|
+
...interaction,
|
|
893
|
+
elementKey: fieldTarget.elementKey ?? interaction.elementKey,
|
|
894
|
+
fieldId: fieldTarget.fieldId,
|
|
895
|
+
};
|
|
717
896
|
}
|
|
718
|
-
return;
|
|
719
|
-
}
|
|
720
|
-
const fieldId = fieldElement.getAttribute("data-fieldid");
|
|
721
|
-
if (!fieldId)
|
|
722
|
-
return;
|
|
723
|
-
const range = sel.getRangeAt(0);
|
|
724
|
-
// Guard: if layout components are hidden, do not set focus for fields of layout components
|
|
725
|
-
if (editContextRef.current?.showLayoutComponents === false &&
|
|
726
|
-
pageViewContextRef.current?.page) {
|
|
727
|
-
const ownerId = fieldElement.getAttribute("data-itemid") || "";
|
|
728
|
-
const owner = getComponentById(ownerId, pageViewContextRef.current.page);
|
|
729
|
-
if (owner?.layoutId)
|
|
730
|
-
return;
|
|
731
897
|
}
|
|
732
|
-
|
|
733
|
-
const
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
const
|
|
740
|
-
const
|
|
741
|
-
|
|
742
|
-
const versionStr = fieldElement.getAttribute("data-version");
|
|
743
|
-
const version = versionStr ? parseInt(versionStr, 10) : 0;
|
|
744
|
-
editContextRef.current?.setSelectedRange({
|
|
745
|
-
itemId,
|
|
746
|
-
fieldId: fieldId,
|
|
747
|
-
language,
|
|
748
|
-
version,
|
|
749
|
-
startOffset: globalStartOffset,
|
|
750
|
-
endOffset: globalEndOffset,
|
|
751
|
-
text: selectedText,
|
|
752
|
-
contextBefore,
|
|
753
|
-
contextAfter,
|
|
754
|
-
// Create a callback that uses DOM Range API to replace text in contenteditable
|
|
755
|
-
replaceText: (newText) => {
|
|
756
|
-
try {
|
|
757
|
-
// Delete the selected content (no-op for collapsed ranges)
|
|
758
|
-
rangeClone.deleteContents();
|
|
759
|
-
// Insert the new text - use the correct document from the range containers
|
|
760
|
-
const doc = rangeClone.startContainer.ownerDocument || document;
|
|
761
|
-
const textNode = doc.createTextNode(newText);
|
|
762
|
-
rangeClone.insertNode(textNode);
|
|
763
|
-
// Collapse the range to the end of the inserted text
|
|
764
|
-
rangeClone.setStartAfter(textNode);
|
|
765
|
-
rangeClone.collapse(true);
|
|
766
|
-
// Update the selection
|
|
767
|
-
const iframeSel = getAccessibleIframeDocument(iframeRef.current, "PageViewerFrame.tsx:replaceText-doc")?.getSelection();
|
|
768
|
-
if (iframeSel) {
|
|
769
|
-
iframeSel.removeAllRanges();
|
|
770
|
-
iframeSel.addRange(rangeClone);
|
|
771
|
-
}
|
|
772
|
-
// Explicitly save the field value since the MutationObserver may not be active
|
|
773
|
-
// when text is selected without entering inline edit mode
|
|
774
|
-
const isRichText = fieldElement?.getAttribute("data-is-richtext") === "true";
|
|
775
|
-
const valueToSave = isRichText
|
|
776
|
-
? fieldElement?.innerHTML
|
|
777
|
-
: fieldElement?.innerText;
|
|
778
|
-
if (fieldId &&
|
|
779
|
-
itemId &&
|
|
780
|
-
language &&
|
|
781
|
-
version &&
|
|
782
|
-
editContextRef.current) {
|
|
783
|
-
editContextRef.current.operations.editField({
|
|
784
|
-
field: {
|
|
785
|
-
fieldId,
|
|
786
|
-
fieldName: fieldElement?.getAttribute("data-fieldname") || undefined,
|
|
787
|
-
item: { id: itemId, language, version },
|
|
788
|
-
},
|
|
789
|
-
originatingSlotId: slotContext?.slotId,
|
|
790
|
-
refresh: "none",
|
|
791
|
-
value: valueToSave,
|
|
792
|
-
});
|
|
793
|
-
}
|
|
794
|
-
}
|
|
795
|
-
catch (error) {
|
|
796
|
-
console.error("[PageViewerFrame] Failed to replace text:", error);
|
|
797
|
-
}
|
|
798
|
-
},
|
|
799
|
-
});
|
|
800
|
-
}, 300);
|
|
801
|
-
useEffect(() => {
|
|
802
|
-
const iframe = iframeRef.current;
|
|
803
|
-
if (!iframe)
|
|
804
|
-
return;
|
|
805
|
-
let boundDocument = null;
|
|
806
|
-
let boundDocumentElement = null;
|
|
807
|
-
let boundScrollContainer = null;
|
|
808
|
-
let mutationObserver = null;
|
|
809
|
-
const handleIframeMouseDown = async (event) => {
|
|
810
|
-
const target = event.target;
|
|
811
|
-
const targetElement = target;
|
|
812
|
-
if (editContextRef.current?.isRefreshing && showSpinner)
|
|
813
|
-
return;
|
|
814
|
-
// Activate the editor slot this iframe belongs to
|
|
815
|
-
const slotElement = iframe?.closest("[data-editor-slot]");
|
|
816
|
-
const slotId = slotElement?.getAttribute("data-slotid");
|
|
817
|
-
const activeSlotId = editContextRef.current?.getActiveSlotId?.() ??
|
|
818
|
-
editContextRef.current?.activeSlotId;
|
|
819
|
-
if (slotId && activeSlotId !== slotId) {
|
|
820
|
-
editContextRef.current?.setActiveSlot(slotId);
|
|
821
|
-
}
|
|
822
|
-
// Skip selection changes on right-click (button 2) - let context menu handler deal with it
|
|
823
|
-
if (event.button === 2)
|
|
824
|
-
return;
|
|
825
|
-
const fieldElement = targetElement
|
|
826
|
-
? findParentWithAttribute(targetElement, "data-fieldid")
|
|
827
|
-
: null;
|
|
828
|
-
const pageForSelection = pageViewContextRef.current?.page;
|
|
829
|
-
const fieldDescriptor = fieldElement?.hasAttribute("data-itemid")
|
|
830
|
-
? getFieldDescriptorFromElement(fieldElement)
|
|
831
|
-
: undefined;
|
|
832
|
-
const componentIdFromField = fieldDescriptor && targetElement && pageForSelection
|
|
833
|
-
? resolveComponentIdForFieldTarget(fieldDescriptor, targetElement, pageForSelection)
|
|
834
|
-
: undefined;
|
|
835
|
-
const rawComponentId = componentIdFromField ??
|
|
836
|
-
(targetElement
|
|
837
|
-
? findNearestEditableComponentId(targetElement)
|
|
838
|
-
: undefined);
|
|
839
|
-
let componentId = rawComponentId;
|
|
840
|
-
if (!componentIdFromField && componentId && pageForSelection) {
|
|
841
|
-
componentId = resolveComponentIdForTarget(componentId, targetElement, pageForSelection);
|
|
842
|
-
}
|
|
843
|
-
// Layout components can still be selected even when showLayoutComponents is false
|
|
844
|
-
// They will be displayed in read-only mode
|
|
845
|
-
const currentOverlayName = editContextRef.current?.currentOverlay;
|
|
846
|
-
const isGeneratorOverlay = !!(currentOverlayName &&
|
|
847
|
-
typeof currentOverlayName === "string" &&
|
|
848
|
-
currentOverlayName.endsWith("_generators"));
|
|
849
|
-
// Don't close context-menu overlay on mousedown - let it handle its own closing
|
|
850
|
-
if (editContextRef.current?.currentOverlay &&
|
|
851
|
-
!isGeneratorOverlay &&
|
|
852
|
-
editContextRef.current.currentOverlay !== "context-menu")
|
|
853
|
-
editContextRef.current?.setCurrentOverlay(undefined);
|
|
854
|
-
if (componentId) {
|
|
855
|
-
const currentSelection = editContextRef.current?.selection || [];
|
|
856
|
-
if (event.shiftKey && currentSelection.length > 0) {
|
|
857
|
-
const page = pageViewContextRef.current?.page;
|
|
858
|
-
if (page) {
|
|
859
|
-
// Build ordered list of visible component ids by DOM order
|
|
860
|
-
const doc = getAccessibleIframeDocument(iframeRef.current, "PageViewerFrame.tsx:shift-select-doc");
|
|
861
|
-
const orderedIds = [];
|
|
862
|
-
if (doc) {
|
|
863
|
-
const all = Array.from(doc.querySelectorAll("[data-component-id]"));
|
|
864
|
-
for (const el of all) {
|
|
865
|
-
const id = el.getAttribute("data-component-id");
|
|
866
|
-
if (!id)
|
|
867
|
-
continue;
|
|
868
|
-
// Layout components are now selectable even when showLayoutComponents is false
|
|
869
|
-
orderedIds.push(id);
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
const anchorId = currentSelection[currentSelection.length - 1];
|
|
873
|
-
const aIdx = orderedIds.indexOf(anchorId);
|
|
874
|
-
const bIdx = orderedIds.indexOf(componentId);
|
|
875
|
-
if (aIdx !== -1 && bIdx !== -1) {
|
|
876
|
-
const start = Math.min(aIdx, bIdx);
|
|
877
|
-
const end = Math.max(aIdx, bIdx);
|
|
878
|
-
const range = orderedIds.slice(start, end + 1);
|
|
879
|
-
if (event.ctrlKey) {
|
|
880
|
-
const union = new Set([...currentSelection, ...range]);
|
|
881
|
-
editContextRef.current?.select(Array.from(union));
|
|
882
|
-
}
|
|
883
|
-
else {
|
|
884
|
-
editContextRef.current?.select(range);
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
else {
|
|
888
|
-
editContextRef.current?.select([componentId]);
|
|
889
|
-
}
|
|
890
|
-
}
|
|
891
|
-
else {
|
|
892
|
-
editContextRef.current?.select([componentId]);
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
else if (event.ctrlKey) {
|
|
896
|
-
// Toggle selection: add if not selected, remove if already selected
|
|
897
|
-
const idx = currentSelection.indexOf(componentId);
|
|
898
|
-
if (idx === -1) {
|
|
899
|
-
editContextRef.current?.select([...currentSelection, componentId]);
|
|
900
|
-
}
|
|
901
|
-
else {
|
|
902
|
-
const newSelection = [...currentSelection];
|
|
903
|
-
newSelection.splice(idx, 1);
|
|
904
|
-
editContextRef.current?.select(newSelection);
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
else {
|
|
908
|
-
// Regular click: always select just this component
|
|
909
|
-
editContextRef.current?.select([componentId]);
|
|
910
|
-
}
|
|
911
|
-
// if (mode !== "edit") {
|
|
912
|
-
//editContextRef.current?.setScrollIntoView(componentId);
|
|
913
|
-
//}
|
|
914
|
-
}
|
|
915
|
-
else {
|
|
916
|
-
editContextRef.current?.select([]);
|
|
917
|
-
}
|
|
918
|
-
// Skip field focusing when using modifier keys for multi-selection
|
|
919
|
-
if (!event.ctrlKey &&
|
|
920
|
-
!event.shiftKey &&
|
|
921
|
-
((editContextRef.current?.mode === "edit" &&
|
|
922
|
-
pageViewContextRef.current?.page?.item.canWriteItem) ||
|
|
923
|
-
editContextRef.current?.mode === "suggestions")) {
|
|
924
|
-
if (fieldElement?.hasAttribute("data-itemid")) {
|
|
925
|
-
// Guard: if layout components are hidden, do not allow editing fields of layout components
|
|
926
|
-
if (editContextRef.current?.showLayoutComponents === false &&
|
|
927
|
-
pageViewContextRef.current?.page) {
|
|
928
|
-
const owningItemId = fieldElement.getAttribute("data-itemid") || "";
|
|
929
|
-
const ownerComponent = getComponentById(owningItemId, pageViewContextRef.current.page);
|
|
930
|
-
if (ownerComponent?.layoutId) {
|
|
931
|
-
return;
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
blockBlurEventRef.current = Date.now() + 500;
|
|
935
|
-
const shouldRequestLock = editContextRef.current?.mode !== "suggestions";
|
|
936
|
-
const hasLock = (await fieldsContextRef.current?.setFocusedField(fieldDescriptor, shouldRequestLock)) ?? false;
|
|
937
|
-
if (hasLock &&
|
|
938
|
-
fieldsContextRef.current?.inlineEditingFieldElement !== fieldElement) {
|
|
939
|
-
fieldsContextRef.current?.setInlineEditingFieldElement(fieldElement);
|
|
940
|
-
// Don't prevent default - we want the browser's natural cursor positioning
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
else {
|
|
944
|
-
// Clicked inside iframe but not on a field: clear focused field
|
|
945
|
-
fieldsContextRef.current?.setFocusedField(undefined, false);
|
|
946
|
-
fieldsContextRef.current?.setInlineEditingFieldElement(undefined);
|
|
947
|
-
// Release field locks when unfocusing field
|
|
948
|
-
if (editContextRef.current?.unlockField) {
|
|
949
|
-
editContextRef.current.unlockField(undefined);
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
}
|
|
953
|
-
const clickEvent = new MouseEvent("click", {
|
|
954
|
-
view: window,
|
|
955
|
-
bubbles: true,
|
|
956
|
-
cancelable: true,
|
|
957
|
-
clientX: event.clientX,
|
|
958
|
-
clientY: event.clientY,
|
|
959
|
-
});
|
|
960
|
-
if (!isGeneratorOverlay) {
|
|
961
|
-
document.dispatchEvent(clickEvent);
|
|
962
|
-
}
|
|
963
|
-
};
|
|
964
|
-
const handleIframeScroll = () => {
|
|
965
|
-
const scrollTop = boundScrollContainer?.scrollTop || 0;
|
|
966
|
-
scrollHandler(scrollTop);
|
|
967
|
-
};
|
|
968
|
-
const handleIframeWindowScroll = () => {
|
|
969
|
-
const scrollTop = boundScrollContainer?.scrollTop || 0;
|
|
970
|
-
scrollHandler(scrollTop);
|
|
971
|
-
};
|
|
972
|
-
const handleIframeWheel = (event) => {
|
|
973
|
-
if (!event.ctrlKey && !event.metaKey)
|
|
974
|
-
return;
|
|
975
|
-
if (event.deltaY === 0)
|
|
976
|
-
return;
|
|
977
|
-
event.preventDefault();
|
|
978
|
-
event.stopPropagation();
|
|
979
|
-
const zoomContext = (compareView ? slotContext?.primaryPageViewContext : undefined) ??
|
|
980
|
-
pageViewContextRef.current;
|
|
981
|
-
const direction = event.deltaY < 0 ? 1 : -1;
|
|
982
|
-
zoomContext?.setZoom((value) => clampEditorZoom(value + direction * ZOOM_STEP));
|
|
983
|
-
};
|
|
984
|
-
const handleIframeKeyDown = (event) => {
|
|
985
|
-
// Handle Ctrl+. keyboard shortcut to open AI text editor
|
|
986
|
-
// Forward this to the parent window since InlineAiTrigger listens there
|
|
987
|
-
const isCtrlPeriod = (event.ctrlKey || event.metaKey) && event.key === ".";
|
|
988
|
-
if (isCtrlPeriod) {
|
|
989
|
-
// Dispatch the inline-ai-open event on the parent window's document
|
|
990
|
-
// so InlineAiTrigger can catch it
|
|
991
|
-
const parentEvent = new CustomEvent("inline-ai-open", {
|
|
992
|
-
bubbles: true,
|
|
993
|
-
cancelable: true,
|
|
994
|
-
});
|
|
995
|
-
window.parent.document.dispatchEvent(parentEvent);
|
|
996
|
-
// Also prevent default to avoid any browser behavior
|
|
997
|
-
event.preventDefault();
|
|
998
|
-
event.stopPropagation();
|
|
999
|
-
return;
|
|
1000
|
-
}
|
|
1001
|
-
editContextRef.current?.handleKeyDown(event);
|
|
1002
|
-
if (editContextRef.current?.mode === "suggestions") {
|
|
1003
|
-
const target = event.target;
|
|
1004
|
-
const fieldElement = target?.closest?.("[data-fieldid][data-itemid][data-language][data-version]");
|
|
1005
|
-
if (fieldElement?.isContentEditable) {
|
|
1006
|
-
setTimeout(() => {
|
|
1007
|
-
const fieldId = fieldElement.getAttribute("data-fieldid");
|
|
1008
|
-
const fieldName = fieldElement.getAttribute("data-fieldname");
|
|
1009
|
-
const itemId = fieldElement.getAttribute("data-itemid");
|
|
1010
|
-
const language = fieldElement.getAttribute("data-language");
|
|
1011
|
-
const versionText = fieldElement.getAttribute("data-version");
|
|
1012
|
-
const version = versionText ? parseInt(versionText, 10) : undefined;
|
|
1013
|
-
if (!fieldId || !itemId || !language || !version)
|
|
1014
|
-
return;
|
|
1015
|
-
const isRichText = fieldElement.getAttribute("data-is-richtext") === "true";
|
|
1016
|
-
const value = (isRichText ? fieldElement.innerHTML : fieldElement.innerText).replaceAll("\u200B", "");
|
|
1017
|
-
editContextRef.current?.operations.editField({
|
|
1018
|
-
field: {
|
|
1019
|
-
fieldId,
|
|
1020
|
-
fieldName: fieldName ?? undefined,
|
|
1021
|
-
item: { id: itemId, language, version },
|
|
1022
|
-
},
|
|
1023
|
-
forceMode: "suggestions",
|
|
1024
|
-
originatingSlotId: slotContext?.slotId,
|
|
1025
|
-
refresh: "none",
|
|
1026
|
-
value,
|
|
1027
|
-
});
|
|
1028
|
-
}, 0);
|
|
1029
|
-
}
|
|
1030
|
-
}
|
|
1031
|
-
};
|
|
1032
|
-
const handleIframeBlur = () => {
|
|
1033
|
-
// Block blur event if it was triggered by clicking on a field
|
|
1034
|
-
if (blockBlurEventRef.current < Date.now()) {
|
|
1035
|
-
// Only clear if the focus didn't move to the parent document (e.g. AI dialog)
|
|
1036
|
-
// OR if we still have a selection range.
|
|
1037
|
-
// We use a small timeout because document.activeElement might not be updated yet
|
|
1038
|
-
setTimeout(() => {
|
|
1039
|
-
const activeEl = document.activeElement;
|
|
1040
|
-
const isDialog = activeEl?.closest(".agent-inline-dialog") ||
|
|
1041
|
-
activeEl?.closest('[role="dialog"]');
|
|
1042
|
-
const isTrigger = activeEl?.closest(".agent-inline-trigger");
|
|
1043
|
-
// If focus moved to dialog/trigger (e.g. inline AI), don't force a blur boundary yet.
|
|
1044
|
-
if (!isDialog && !isTrigger) {
|
|
1045
|
-
// Always mark a field blur operation boundary so consecutive edits do not
|
|
1046
|
-
// collapse into a single undo step when a selection range is still present.
|
|
1047
|
-
editContextRef.current?.operations.onFieldBlur?.();
|
|
1048
|
-
// Leaving the iframe ends inline editing even if we keep the
|
|
1049
|
-
// selection metadata. Suggestions mode relies on this blur
|
|
1050
|
-
// boundary so pending suggestions can be re-applied after focus
|
|
1051
|
-
// moves to the surrounding editor UI.
|
|
1052
|
-
fieldsContextRef.current?.setInlineEditingFieldElement(undefined);
|
|
1053
|
-
fieldsContextRef.current?.setFocusedField(undefined, false);
|
|
1054
|
-
}
|
|
1055
|
-
}, 100);
|
|
1056
|
-
}
|
|
1057
|
-
else {
|
|
1058
|
-
blockBlurEventRef.current = 0;
|
|
1059
|
-
}
|
|
1060
|
-
};
|
|
1061
|
-
const handleIframeFocusOut = (event) => {
|
|
1062
|
-
const target = event.target;
|
|
1063
|
-
const fieldElement = target?.closest?.("[data-fieldid][data-itemid],[data-fieldname]");
|
|
1064
|
-
if (!fieldElement)
|
|
1065
|
-
return;
|
|
1066
|
-
const nextFocusedElement = event.relatedTarget;
|
|
1067
|
-
if (nextFocusedElement && fieldElement.contains(nextFocusedElement)) {
|
|
1068
|
-
return;
|
|
1069
|
-
}
|
|
1070
|
-
editContextRef.current?.operations.onFieldBlur?.();
|
|
1071
|
-
fieldsContextRef.current?.setInlineEditingFieldElement(undefined);
|
|
1072
|
-
fieldsContextRef.current?.setFocusedField(undefined, false);
|
|
1073
|
-
};
|
|
1074
|
-
const detachListeners = () => {
|
|
1075
|
-
if (!iframe || !boundDocument || !boundDocumentElement)
|
|
898
|
+
const componentId = resolveBridgeComponentId(effectiveInteraction.componentId, pageViewContextRef.current?.page);
|
|
899
|
+
const isEditableFieldClick = !!(effectiveInteraction.kind === "click" &&
|
|
900
|
+
effectiveInteraction.fieldId &&
|
|
901
|
+
effectiveInteraction.elementKey &&
|
|
902
|
+
!effectiveInteraction.ctrlKey &&
|
|
903
|
+
!effectiveInteraction.shiftKey &&
|
|
904
|
+
!effectiveInteraction.metaKey);
|
|
905
|
+
const isInlineAiUiPresent = () => !!document.querySelector(".agent-inline-dialog, .agent-inline-trigger");
|
|
906
|
+
const closeInlineAiIfOpen = (clearSelection = true) => {
|
|
907
|
+
if (!isInlineAiUiPresent())
|
|
1076
908
|
return;
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
try {
|
|
1080
|
-
iframe.contentWindow?.removeEventListener("contextmenu", handleContextMenu, true);
|
|
1081
|
-
}
|
|
1082
|
-
catch { }
|
|
1083
|
-
boundDocument.removeEventListener("selectionchange", selecionChangeHandler);
|
|
1084
|
-
boundDocument.removeEventListener("keydown", handleIframeKeyDown);
|
|
1085
|
-
boundDocument.removeEventListener("wheel", handleIframeWheel, true);
|
|
1086
|
-
boundDocument.removeEventListener("focusout", handleIframeFocusOut, true);
|
|
1087
|
-
boundDocumentElement.removeEventListener("blur", handleIframeBlur, true);
|
|
1088
|
-
boundScrollContainer?.removeEventListener("scroll", handleIframeScroll);
|
|
1089
|
-
try {
|
|
1090
|
-
iframe.contentWindow?.removeEventListener("scroll", handleIframeWindowScroll);
|
|
909
|
+
if (clearSelection) {
|
|
910
|
+
editor.setSelectedRange(undefined);
|
|
1091
911
|
}
|
|
1092
|
-
|
|
1093
|
-
mutationObserver?.disconnect();
|
|
1094
|
-
mutationObserver = null;
|
|
1095
|
-
boundScrollContainer = null;
|
|
1096
|
-
boundDocumentElement = null;
|
|
1097
|
-
boundDocument = null;
|
|
912
|
+
document.dispatchEvent(new CustomEvent(INLINE_AI_CLOSE_EVENT));
|
|
1098
913
|
};
|
|
1099
|
-
const
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
return;
|
|
1108
|
-
// Skip if already bound to current root element.
|
|
1109
|
-
if (boundDocument === iframeDocument &&
|
|
1110
|
-
boundDocumentElement === iframeDocumentElement) {
|
|
914
|
+
const clearSelectedRangeForEditableClick = () => {
|
|
915
|
+
const selectedRange = editor.selectedRange;
|
|
916
|
+
const clickMatchesActiveTextSelection = !!(selectedRange?.text?.trim() &&
|
|
917
|
+
selectedRange.elementKey &&
|
|
918
|
+
effectiveInteraction.elementKey &&
|
|
919
|
+
selectedRange.elementKey === effectiveInteraction.elementKey);
|
|
920
|
+
if (clickMatchesActiveTextSelection) {
|
|
921
|
+
closeInlineAiIfOpen(false);
|
|
1111
922
|
return;
|
|
1112
923
|
}
|
|
1113
|
-
|
|
1114
|
-
boundDocument = iframeDocument;
|
|
1115
|
-
boundDocumentElement = iframeDocumentElement;
|
|
1116
|
-
iframeDocumentElement.addEventListener("mousedown", handleIframeMouseDown);
|
|
1117
|
-
iframeDocumentElement.addEventListener("click", handleIframeClick);
|
|
1118
|
-
try {
|
|
1119
|
-
iframe.contentWindow?.addEventListener("contextmenu", handleContextMenu, true);
|
|
1120
|
-
}
|
|
1121
|
-
catch { }
|
|
1122
|
-
boundScrollContainer =
|
|
1123
|
-
iframeDocument.scrollingElement || iframeDocument.body || null;
|
|
1124
|
-
boundScrollContainer?.addEventListener("scroll", handleIframeScroll);
|
|
1125
|
-
try {
|
|
1126
|
-
iframe.contentWindow?.addEventListener("scroll", handleIframeWindowScroll);
|
|
1127
|
-
}
|
|
1128
|
-
catch { }
|
|
1129
|
-
iframeDocument.addEventListener("selectionchange", selecionChangeHandler);
|
|
1130
|
-
iframeDocument.addEventListener("keydown", handleIframeKeyDown);
|
|
1131
|
-
iframeDocument.addEventListener("wheel", handleIframeWheel, {
|
|
1132
|
-
capture: true,
|
|
1133
|
-
passive: false,
|
|
1134
|
-
});
|
|
1135
|
-
iframeDocument.addEventListener("focusout", handleIframeFocusOut, true);
|
|
1136
|
-
iframeDocumentElement.addEventListener("blur", handleIframeBlur, true);
|
|
1137
|
-
mutationObserver = new MutationObserver((records) => {
|
|
1138
|
-
// Ignore all text field edits
|
|
1139
|
-
if (records.some((x) => !(x &&
|
|
1140
|
-
"getAttribute" in x.target &&
|
|
1141
|
-
x.target.getAttribute("data-fieldid") &&
|
|
1142
|
-
x.target.getAttribute("data-itemid")))) {
|
|
1143
|
-
requestPageModelBuild(iframeDocument);
|
|
1144
|
-
}
|
|
1145
|
-
});
|
|
1146
|
-
mutationObserver.observe(iframeDocument, {
|
|
1147
|
-
childList: true, // observe direct children changes
|
|
1148
|
-
subtree: true, // observe all descendants changes
|
|
1149
|
-
characterData: false, // observe text changes
|
|
1150
|
-
//attributes: true, // observe attribute changes (like style or class)
|
|
1151
|
-
});
|
|
1152
|
-
requestPageModelBuild(iframeDocument);
|
|
924
|
+
closeInlineAiIfOpen(true);
|
|
1153
925
|
};
|
|
1154
|
-
const
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
if (href.includes("~/link.aspx?_id=") ||
|
|
1173
|
-
href.includes("link.aspx?_id=")) {
|
|
1174
|
-
const idFromUrl = hrefUrl.searchParams.get("_id");
|
|
1175
|
-
if (idFromUrl) {
|
|
1176
|
-
// Convert compact GUID (AD973E51E8454BD2B333859375FBBA24) to standard format with dashes
|
|
1177
|
-
itemIdRaw = idFromUrl
|
|
1178
|
-
.replace(/^(\w{8})(\w{4})(\w{4})(\w{4})(\w{12})$/i, "$1-$2-$3-$4-$5")
|
|
1179
|
-
.toLowerCase();
|
|
1180
|
-
}
|
|
926
|
+
const currentOverlay = editor.currentOverlay;
|
|
927
|
+
const isGeneratorOverlay = !!(currentOverlay &&
|
|
928
|
+
typeof currentOverlay === "string" &&
|
|
929
|
+
currentOverlay.endsWith("_generators"));
|
|
930
|
+
if (interaction.kind === "click" && currentOverlay === "context-menu") {
|
|
931
|
+
editor.setCurrentOverlay(undefined);
|
|
932
|
+
}
|
|
933
|
+
if (currentOverlay &&
|
|
934
|
+
currentOverlay !== "context-menu" &&
|
|
935
|
+
!isGeneratorOverlay) {
|
|
936
|
+
editor.setCurrentOverlay(undefined);
|
|
937
|
+
}
|
|
938
|
+
if (!componentId) {
|
|
939
|
+
if (interaction.kind === "click")
|
|
940
|
+
selectFromBridgeInteraction([]);
|
|
941
|
+
if (isEditableFieldClick) {
|
|
942
|
+
clearSelectedRangeForEditableClick();
|
|
943
|
+
await beginTrackedBridgeInlineEdit(effectiveInteraction);
|
|
1181
944
|
}
|
|
1182
|
-
if (
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
hrefUrl.searchParams.get("itemid") ??
|
|
1186
|
-
anchor.getAttribute("data-itemid") ??
|
|
1187
|
-
anchor.getAttribute("sc_itemid") ??
|
|
1188
|
-
attributeContainer?.getAttribute("data-itemid") ??
|
|
1189
|
-
attributeContainer?.getAttribute("sc_itemid") ??
|
|
1190
|
-
(anchor.getAttribute("sc_item")
|
|
1191
|
-
? extractItemIdFromItemUri(anchor.getAttribute("sc_item"))
|
|
1192
|
-
: undefined) ??
|
|
1193
|
-
(attributeContainer?.getAttribute("sc_item")
|
|
1194
|
-
? extractItemIdFromItemUri(attributeContainer.getAttribute("sc_item"))
|
|
1195
|
-
: undefined);
|
|
945
|
+
else if (interaction.kind === "click") {
|
|
946
|
+
closeInlineAiIfOpen();
|
|
947
|
+
endTrackedBridgeFieldFocus();
|
|
1196
948
|
}
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
anchor.getAttribute("data-language") ??
|
|
1204
|
-
anchor.getAttribute("sc_lang") ??
|
|
1205
|
-
attributeContainer?.getAttribute("data-language") ??
|
|
1206
|
-
attributeContainer?.getAttribute("sc_lang") ??
|
|
1207
|
-
editContextRef.current?.currentItemDescriptor?.language ??
|
|
1208
|
-
editContextRef.current?.item?.language;
|
|
1209
|
-
if (!language)
|
|
1210
|
-
return undefined;
|
|
1211
|
-
const version = parsePositiveInt(hrefUrl.searchParams.get("sc_version")) ??
|
|
1212
|
-
parsePositiveInt(hrefUrl.searchParams.get("version")) ??
|
|
1213
|
-
parsePositiveInt(anchor.getAttribute("data-version")) ??
|
|
1214
|
-
parsePositiveInt(attributeContainer?.getAttribute("data-version")) ??
|
|
1215
|
-
editContextRef.current?.currentItemDescriptor?.version ??
|
|
1216
|
-
editContextRef.current?.item?.version ??
|
|
1217
|
-
0;
|
|
1218
|
-
return {
|
|
1219
|
-
id: itemId,
|
|
1220
|
-
language,
|
|
1221
|
-
version,
|
|
1222
|
-
};
|
|
1223
|
-
};
|
|
1224
|
-
const handleIframeClick = async (event) => {
|
|
1225
|
-
const target = event.target;
|
|
1226
|
-
if (!target)
|
|
1227
|
-
return;
|
|
1228
|
-
const anchor = target.tagName.toLowerCase() === "a"
|
|
1229
|
-
? target
|
|
1230
|
-
: target.closest("a");
|
|
1231
|
-
if (!anchor)
|
|
1232
|
-
return;
|
|
1233
|
-
const href = anchor.getAttribute("href") || anchor.href;
|
|
1234
|
-
if (!href || href.startsWith("#") || href.startsWith("javascript:")) {
|
|
1235
|
-
return;
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
const currentSelection = editor.selection || [];
|
|
952
|
+
if (interaction.kind === "contextMenu") {
|
|
953
|
+
if (pageViewContextRef.current) {
|
|
954
|
+
pageViewContextRef.current.bridgeInteraction = effectiveInteraction;
|
|
1236
955
|
}
|
|
1237
|
-
const
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
hrefUrl = new URL(anchor.href, getAccessibleIframeLocationHref(iframe) || window.location.href);
|
|
1243
|
-
}
|
|
1244
|
-
catch {
|
|
1245
|
-
return;
|
|
1246
|
-
}
|
|
1247
|
-
const iframeOrigin = getAccessibleIframeLocationOrigin(iframe);
|
|
1248
|
-
const isInternalLink = hrefUrl.origin === window.location.origin ||
|
|
1249
|
-
(iframeOrigin ? hrefUrl.origin === iframeOrigin : false);
|
|
1250
|
-
if (!isInternalLink) {
|
|
1251
|
-
event.preventDefault();
|
|
1252
|
-
event.stopPropagation();
|
|
1253
|
-
window.open(hrefUrl.toString(), "_blank", "noopener,noreferrer");
|
|
1254
|
-
return;
|
|
1255
|
-
}
|
|
1256
|
-
if (isInternalLink) {
|
|
1257
|
-
const linkedItem = resolveLinkedItemDescriptor(anchor, target);
|
|
1258
|
-
if (linkedItem) {
|
|
1259
|
-
event.preventDefault();
|
|
1260
|
-
event.stopPropagation();
|
|
1261
|
-
await editContextRef.current?.loadItem(linkedItem, {
|
|
1262
|
-
openInNewSlot: event.altKey,
|
|
1263
|
-
});
|
|
1264
|
-
return;
|
|
1265
|
-
}
|
|
1266
|
-
}
|
|
1267
|
-
// If this link cannot be resolved to an item, allow browser behavior.
|
|
1268
|
-
return;
|
|
956
|
+
const selectedIds = currentSelection.includes(componentId)
|
|
957
|
+
? currentSelection
|
|
958
|
+
: [componentId];
|
|
959
|
+
if (!currentSelection.includes(componentId)) {
|
|
960
|
+
selectFromBridgeInteraction(selectedIds);
|
|
1269
961
|
}
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
};
|
|
1273
|
-
const handleContextMenu = async (event) => {
|
|
1274
|
-
if (editContextRef.current?.isRefreshing && showSpinner)
|
|
1275
|
-
return;
|
|
1276
|
-
const target = event.target;
|
|
1277
|
-
if (!target)
|
|
962
|
+
const page = pageViewContextRef.current?.page;
|
|
963
|
+
if (!page)
|
|
1278
964
|
return;
|
|
1279
|
-
|
|
965
|
+
const selectedComponents = selectedIds
|
|
966
|
+
.map((id) => getComponentById(id, page))
|
|
967
|
+
.filter((component) => !!component);
|
|
968
|
+
if (selectedComponents.length === 0)
|
|
1280
969
|
return;
|
|
1281
|
-
event.preventDefault();
|
|
1282
|
-
event.stopPropagation();
|
|
1283
|
-
let componentId = findNearestEditableComponentId(target);
|
|
1284
|
-
const pageForContextMenu = pageViewContextRef.current?.page;
|
|
1285
|
-
if (componentId && pageForContextMenu) {
|
|
1286
|
-
componentId = resolveComponentIdForTarget(componentId, target, pageForContextMenu);
|
|
1287
|
-
}
|
|
1288
|
-
// Layout components can now be right-clicked even when showLayoutComponents is false
|
|
1289
|
-
// Context menu will show limited/read-only actions
|
|
1290
|
-
if (componentId) {
|
|
1291
|
-
// Only change selection if right-clicking on a component that's not in the current selection
|
|
1292
|
-
if (!editContextRef.current?.selection.includes(componentId)) {
|
|
1293
|
-
// Right-clicking on a component not in the selection - select just that component
|
|
1294
|
-
editContextRef.current.select([componentId]);
|
|
1295
|
-
}
|
|
1296
|
-
// else: right-clicking on a component already in the selection - keep current selection
|
|
1297
|
-
}
|
|
1298
|
-
const fieldElement = findParentWithAttribute(target, "data-fieldid");
|
|
1299
|
-
const selectedComponents = editContextRef.current?.selection
|
|
1300
|
-
.map((id) => getComponentById(id, pageViewContextRef.current.page))
|
|
1301
|
-
.filter((x) => x);
|
|
1302
|
-
// Context menu will now show for layout components even when showLayoutComponents is false
|
|
1303
|
-
// Commands will be appropriately filtered/disabled
|
|
1304
970
|
const iframeRect = iframe.getBoundingClientRect();
|
|
971
|
+
const clientX = iframeRect.x + (interaction.clientX ?? 0);
|
|
972
|
+
const clientY = iframeRect.y + (interaction.clientY ?? 0);
|
|
973
|
+
const menuPosition = { x: clientX, y: clientY };
|
|
1305
974
|
const adjustedEvent = new MouseEvent("contextmenu", {
|
|
1306
975
|
bubbles: true,
|
|
1307
976
|
cancelable: true,
|
|
1308
|
-
shiftKey:
|
|
1309
|
-
altKey:
|
|
1310
|
-
ctrlKey:
|
|
977
|
+
shiftKey: interaction.shiftKey,
|
|
978
|
+
altKey: interaction.altKey,
|
|
979
|
+
ctrlKey: interaction.ctrlKey,
|
|
980
|
+
metaKey: interaction.metaKey,
|
|
1311
981
|
view: window,
|
|
1312
|
-
clientX
|
|
1313
|
-
clientY
|
|
982
|
+
clientX,
|
|
983
|
+
clientY,
|
|
984
|
+
button: 2,
|
|
1314
985
|
});
|
|
1315
|
-
// Store the original context menu position for overlay positioning
|
|
1316
|
-
const menuPosition = {
|
|
1317
|
-
x: event.clientX + iframeRect.x,
|
|
1318
|
-
y: event.clientY + iframeRect.y,
|
|
1319
|
-
};
|
|
1320
986
|
setContextMenuPosition(menuPosition);
|
|
1321
|
-
// Only show a spinner if work takes longer than 100ms
|
|
1322
987
|
let loadingShown = false;
|
|
1323
|
-
const loadingTimer = setTimeout(() => {
|
|
988
|
+
const loadingTimer = window.setTimeout(() => {
|
|
1324
989
|
loadingShown = true;
|
|
1325
|
-
|
|
990
|
+
editor.showContextMenu(adjustedEvent, [
|
|
1326
991
|
{
|
|
1327
992
|
id: "loading",
|
|
1328
|
-
label: "Loading
|
|
993
|
+
label: "Loading...",
|
|
1329
994
|
disabled: true,
|
|
1330
995
|
icon: _jsx(Spinner, { size: "sm", className: "mr-2" }),
|
|
1331
996
|
},
|
|
1332
997
|
]);
|
|
1333
998
|
}, 100);
|
|
1334
|
-
const field =
|
|
1335
|
-
? getFieldDescriptorFromElement(fieldElement)
|
|
1336
|
-
: undefined;
|
|
999
|
+
const field = await resolveBridgeFieldDescriptor(effectiveInteraction);
|
|
1337
1000
|
const fieldButtons = field ? await loadFieldButtons(field) : [];
|
|
1338
|
-
|
|
1339
|
-
const handleParameterizedActionWithPosition = (field, action, allFieldButtons, event) => {
|
|
1001
|
+
const handleParameterizedActionWithPosition = (field, action) => {
|
|
1340
1002
|
setContextMenuField(field);
|
|
1341
1003
|
setContextMenuFieldButtons([action]);
|
|
1342
1004
|
setPreSelectedAction(action);
|
|
1343
|
-
|
|
1344
|
-
const syntheticEvent = new MouseEvent("click", {
|
|
1345
|
-
bubbles: true,
|
|
1346
|
-
cancelable: true,
|
|
1347
|
-
view: window,
|
|
1348
|
-
clientX: menuPosition.x,
|
|
1349
|
-
clientY: menuPosition.y,
|
|
1350
|
-
screenX: menuPosition.x,
|
|
1351
|
-
screenY: menuPosition.y,
|
|
1352
|
-
});
|
|
1353
|
-
// Add target property to the event for proper positioning
|
|
1354
|
-
Object.defineProperty(syntheticEvent, "target", {
|
|
1355
|
-
value: document.body,
|
|
1356
|
-
enumerable: true,
|
|
1357
|
-
});
|
|
1358
|
-
console.log("About to show overlay with position:", {
|
|
1359
|
-
menuPosition,
|
|
1360
|
-
syntheticEvent,
|
|
1361
|
-
preSelectedAction: action,
|
|
1362
|
-
});
|
|
1363
|
-
// Add a small delay to ensure context menu has closed
|
|
1364
|
-
setTimeout(() => {
|
|
1365
|
-
console.log("Showing overlay with position:", {
|
|
1366
|
-
menuPosition,
|
|
1367
|
-
syntheticEvent,
|
|
1368
|
-
preSelectedAction: action,
|
|
1369
|
-
});
|
|
1370
|
-
// Create a temporary element at the exact position for better positioning
|
|
1005
|
+
window.setTimeout(() => {
|
|
1371
1006
|
const tempElement = document.createElement("div");
|
|
1372
1007
|
tempElement.style.position = "fixed";
|
|
1373
1008
|
tempElement.style.left = menuPosition.x + "px";
|
|
@@ -1377,7 +1012,6 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
1377
1012
|
tempElement.style.visibility = "hidden";
|
|
1378
1013
|
tempElement.style.pointerEvents = "none";
|
|
1379
1014
|
document.body.appendChild(tempElement);
|
|
1380
|
-
// Create event targeting the positioned element
|
|
1381
1015
|
const positionedEvent = new MouseEvent("click", {
|
|
1382
1016
|
bubbles: true,
|
|
1383
1017
|
cancelable: true,
|
|
@@ -1390,74 +1024,840 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
1390
1024
|
enumerable: true,
|
|
1391
1025
|
});
|
|
1392
1026
|
fieldActionsOverlay.current?.show(positionedEvent, action);
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
document.body.removeChild(tempElement);
|
|
1027
|
+
window.setTimeout(() => {
|
|
1028
|
+
tempElement.remove();
|
|
1396
1029
|
}, 1000);
|
|
1397
1030
|
}, 100);
|
|
1398
1031
|
};
|
|
1399
|
-
const items = await buildComponentContextMenuItems(selectedComponents,
|
|
1400
|
-
clearTimeout(loadingTimer);
|
|
1401
|
-
if (loadingShown)
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1032
|
+
const items = await buildComponentContextMenuItems(selectedComponents, editor, field, fieldButtons, handleParameterizedActionWithPosition);
|
|
1033
|
+
window.clearTimeout(loadingTimer);
|
|
1034
|
+
if (loadingShown) {
|
|
1035
|
+
editor.updateContextMenu(items);
|
|
1036
|
+
}
|
|
1037
|
+
else {
|
|
1038
|
+
editor.showContextMenu(adjustedEvent, items);
|
|
1039
|
+
}
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
if (interaction.shiftKey && currentSelection.length > 0) {
|
|
1043
|
+
const orderedIds = getOrderedBridgeComponentIds(pageViewContextRef.current?.bridgeGeometry, pageViewContextRef.current?.page);
|
|
1044
|
+
const anchorId = currentSelection[currentSelection.length - 1];
|
|
1045
|
+
const anchorIndex = orderedIds.findIndex((id) => bridgeIdsMatch(id, anchorId));
|
|
1046
|
+
const targetIndex = orderedIds.findIndex((id) => bridgeIdsMatch(id, componentId));
|
|
1047
|
+
if (anchorIndex !== -1 && targetIndex !== -1) {
|
|
1048
|
+
const start = Math.min(anchorIndex, targetIndex);
|
|
1049
|
+
const end = Math.max(anchorIndex, targetIndex);
|
|
1050
|
+
const range = orderedIds.slice(start, end + 1);
|
|
1051
|
+
if (interaction.ctrlKey || interaction.metaKey) {
|
|
1052
|
+
const nextSelection = [...currentSelection];
|
|
1053
|
+
for (const id of range) {
|
|
1054
|
+
if (!nextSelection.some((selectedId) => bridgeIdsMatch(selectedId, id))) {
|
|
1055
|
+
nextSelection.push(id);
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
selectFromBridgeInteraction(nextSelection);
|
|
1059
|
+
}
|
|
1060
|
+
else {
|
|
1061
|
+
selectFromBridgeInteraction(range);
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
else {
|
|
1065
|
+
selectFromBridgeInteraction([componentId]);
|
|
1066
|
+
}
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1069
|
+
if (interaction.ctrlKey || interaction.metaKey) {
|
|
1070
|
+
if (currentSelection.includes(componentId)) {
|
|
1071
|
+
selectFromBridgeInteraction(currentSelection.filter((id) => id !== componentId));
|
|
1072
|
+
}
|
|
1073
|
+
else {
|
|
1074
|
+
selectFromBridgeInteraction([...currentSelection, componentId]);
|
|
1075
|
+
}
|
|
1076
|
+
return;
|
|
1077
|
+
}
|
|
1078
|
+
selectFromBridgeInteraction([componentId]);
|
|
1079
|
+
if (isEditableFieldClick) {
|
|
1080
|
+
clearSelectedRangeForEditableClick();
|
|
1081
|
+
await beginTrackedBridgeInlineEdit(effectiveInteraction);
|
|
1082
|
+
}
|
|
1083
|
+
else {
|
|
1084
|
+
closeInlineAiIfOpen();
|
|
1085
|
+
endTrackedBridgeFieldFocus();
|
|
1086
|
+
}
|
|
1087
|
+
}, [
|
|
1088
|
+
beginTrackedBridgeInlineEdit,
|
|
1089
|
+
endTrackedBridgeFieldFocus,
|
|
1090
|
+
resolveBridgeFieldDescriptor,
|
|
1091
|
+
]);
|
|
1092
|
+
// Update the context whenever the iframe ref changes
|
|
1093
|
+
useEffect(() => {
|
|
1094
|
+
pageViewContext.setEditorIframe(iframeElement);
|
|
1095
|
+
}, [iframeElement, pageViewContext.setEditorIframe]);
|
|
1096
|
+
useEffect(() => {
|
|
1097
|
+
const iframe = iframeElement;
|
|
1098
|
+
if (!iframe)
|
|
1099
|
+
return;
|
|
1100
|
+
const currentIframeSrc = iframe.src;
|
|
1101
|
+
if (!iframeSrc ||
|
|
1102
|
+
!loadedIframeSrc ||
|
|
1103
|
+
loadedIframeSrc !== currentIframeSrc) {
|
|
1104
|
+
pageViewContext.setBridgeReady(false);
|
|
1105
|
+
pageViewContext.setIframeSupportsRefresh(false);
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
const allowedHostOrigins = [
|
|
1109
|
+
getUrlOrigin(pageViewContext.editUrl),
|
|
1110
|
+
getUrlOrigin(pageViewContext.previewUrl),
|
|
1111
|
+
].filter((origin) => Boolean(origin));
|
|
1112
|
+
if (allowedHostOrigins.length === 0) {
|
|
1113
|
+
pageViewContext.setBridgeReady(false);
|
|
1114
|
+
pageViewContext.setIframeSupportsRefresh(false);
|
|
1115
|
+
return;
|
|
1116
|
+
}
|
|
1117
|
+
// The canonical bridge is served from the editor's own origin. A stub
|
|
1118
|
+
// bootloader on the host loads it from this URL; full-bridge hosts ignore
|
|
1119
|
+
// it. Editor shells deploy under different base paths (e.g. dev-vite under
|
|
1120
|
+
// `/parhelia/` in Sitecore), so each app advertises its own served location
|
|
1121
|
+
// via `window.__PARHELIA_EDITOR_BRIDGE_URL__`. If that global is missing,
|
|
1122
|
+
// infer the known `/parhelia` mount from the current editor URL.
|
|
1123
|
+
const configuredEditorBridgeUrl = window.__PARHELIA_EDITOR_BRIDGE_URL__;
|
|
1124
|
+
const editorBasePath = window.location.pathname.startsWith("/parhelia")
|
|
1125
|
+
? "/parhelia"
|
|
1126
|
+
: "";
|
|
1127
|
+
const editorBridgeUrl = configuredEditorBridgeUrl ||
|
|
1128
|
+
new URL(`${editorBasePath}/editor-bridge/v1/parhelia-bridge.js`, window.location.origin).toString();
|
|
1129
|
+
let client;
|
|
1130
|
+
client = new BridgeClient({
|
|
1131
|
+
iframe,
|
|
1132
|
+
editorOrigin: window.location.origin,
|
|
1133
|
+
allowedHostOrigins,
|
|
1134
|
+
bridgeUrl: editorBridgeUrl,
|
|
1135
|
+
onReadyChange: (ready) => {
|
|
1136
|
+
pageViewContext.setBridgeReady(ready);
|
|
1137
|
+
if (!ready)
|
|
1138
|
+
pageViewContext.setIframeSupportsRefresh(false);
|
|
1139
|
+
},
|
|
1140
|
+
onError: (error) => {
|
|
1141
|
+
pageViewContext.setBridgeReady(false);
|
|
1142
|
+
pageViewContext.setIframeSupportsRefresh(false);
|
|
1143
|
+
console.error("[Parhelia bridge]", error.message);
|
|
1144
|
+
if (!compareView) {
|
|
1145
|
+
editContextRef.current?.showErrorToast({
|
|
1146
|
+
summary: "Editing host bridge unavailable",
|
|
1147
|
+
details: error.message,
|
|
1148
|
+
});
|
|
1149
|
+
}
|
|
1150
|
+
},
|
|
1151
|
+
onEvent: (event, bridge) => {
|
|
1152
|
+
switch (event.name) {
|
|
1153
|
+
case "ready":
|
|
1154
|
+
clearBridgePatchSignatures();
|
|
1155
|
+
pageViewContext.setIframeSupportsRefresh(bridge.supportsCapability("refresh"));
|
|
1156
|
+
bridge.sendCommand("init", {
|
|
1157
|
+
editorOrigin: window.location.origin,
|
|
1158
|
+
sessionId: editContextRef.current?.sessionId,
|
|
1159
|
+
mode: editContextRef.current?.mode ?? "edit",
|
|
1160
|
+
selectedComponentIds: editContextRef.current?.selection ?? [],
|
|
1161
|
+
featureFlags: {
|
|
1162
|
+
crossOriginBridge: true,
|
|
1163
|
+
},
|
|
1164
|
+
version: bridge.getDiagnostics().editorVersion,
|
|
1165
|
+
acknowledgedCapabilities: bridge.getAcknowledgedCapabilities(),
|
|
1166
|
+
});
|
|
1167
|
+
bridge.sendCommand("setZoom", {
|
|
1168
|
+
zoom: pageViewContextRef.current?.zoom ?? 1,
|
|
1169
|
+
});
|
|
1170
|
+
break;
|
|
1171
|
+
case "structureUpdated":
|
|
1172
|
+
pageViewContext.setBridgeStructure(event.payload.structure);
|
|
1173
|
+
break;
|
|
1174
|
+
case "pageSkeletonUpdated":
|
|
1175
|
+
pageViewContext.setPageSkeleton(event.payload.pageSkeleton);
|
|
1176
|
+
break;
|
|
1177
|
+
case "geometryUpdated":
|
|
1178
|
+
latestBridgeScrollRef.current = event.payload.geometry.scroll;
|
|
1179
|
+
pageViewContext.bridgeGeometry = event.payload.geometry;
|
|
1180
|
+
pageViewContext.setBridgeGeometry(event.payload.geometry);
|
|
1181
|
+
scheduleBridgeGeometryRevision();
|
|
1182
|
+
dispatchBridgeOverlayScroll(iframe, event.payload.geometry.scroll, getBridgeGeometryScrollScale(event.payload.geometry));
|
|
1183
|
+
dispatchBridgeOverlayGeometry(iframe, event.payload.geometry);
|
|
1184
|
+
if (shouldTrackMinimapScroll()) {
|
|
1185
|
+
scrollHandlerRef.current(event.payload.geometry.scroll.y);
|
|
1186
|
+
}
|
|
1187
|
+
break;
|
|
1188
|
+
case "domUpdated":
|
|
1189
|
+
pageViewContext.bridgeDom = event.payload;
|
|
1190
|
+
pageViewContext.setBridgeDom(event.payload);
|
|
1191
|
+
scheduleBridgeDomRevision();
|
|
1192
|
+
window.dispatchEvent(new CustomEvent(BRIDGE_DOM_UPDATED_EVENT));
|
|
1193
|
+
break;
|
|
1194
|
+
case "refreshStarted":
|
|
1195
|
+
clearBridgePatchSignatures();
|
|
1196
|
+
activeBridgeInlineEditRef.current = null;
|
|
1197
|
+
break;
|
|
1198
|
+
case "refreshCompleted":
|
|
1199
|
+
// Runtime refresh replaces host DOM without a new bridge ready event.
|
|
1200
|
+
// Field patches sent during the refresh window may have been cached
|
|
1201
|
+
// against the previous DOM generation, so force the refreshed host to
|
|
1202
|
+
// receive the current repository values again.
|
|
1203
|
+
clearBridgePatchSignatures();
|
|
1204
|
+
if (event.payload.structure) {
|
|
1205
|
+
pageViewContext.setBridgeStructure(event.payload.structure);
|
|
1206
|
+
}
|
|
1207
|
+
if (event.payload.geometry) {
|
|
1208
|
+
latestBridgeScrollRef.current = event.payload.geometry.scroll;
|
|
1209
|
+
pageViewContext.bridgeGeometry = event.payload.geometry;
|
|
1210
|
+
pageViewContext.setBridgeGeometry(event.payload.geometry);
|
|
1211
|
+
scheduleBridgeGeometryRevision();
|
|
1212
|
+
dispatchBridgeOverlayScroll(iframe, event.payload.geometry.scroll, getBridgeGeometryScrollScale(event.payload.geometry));
|
|
1213
|
+
dispatchBridgeOverlayGeometry(iframe, event.payload.geometry);
|
|
1214
|
+
if (shouldTrackMinimapScroll()) {
|
|
1215
|
+
scrollHandlerRef.current(event.payload.geometry.scroll.y);
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
if (pendingRefreshScrollRef.current) {
|
|
1219
|
+
const targetScroll = pendingRefreshScrollRef.current;
|
|
1220
|
+
const observedScroll = event.payload.geometry?.scroll ?? {
|
|
1221
|
+
x: 0,
|
|
1222
|
+
y: 0,
|
|
1223
|
+
};
|
|
1224
|
+
const deltaX = targetScroll.x - observedScroll.x;
|
|
1225
|
+
const deltaY = targetScroll.y - observedScroll.y;
|
|
1226
|
+
pendingRefreshScrollRef.current = undefined;
|
|
1227
|
+
if (Math.abs(deltaX) > 1 || Math.abs(deltaY) > 1) {
|
|
1228
|
+
restoreIframeWindowScroll(iframe, targetScroll);
|
|
1229
|
+
bridgeClientRef.current?.sendCommand("scrollBy", {
|
|
1230
|
+
x: deltaX,
|
|
1231
|
+
y: deltaY,
|
|
1232
|
+
behavior: "instant",
|
|
1233
|
+
});
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
setShowSpinner(false);
|
|
1237
|
+
break;
|
|
1238
|
+
case "selectionChanged":
|
|
1239
|
+
pageViewContext.setBridgeSelection(event.payload.selection);
|
|
1240
|
+
handleBridgeSelection(event.payload.selection, iframe);
|
|
1241
|
+
break;
|
|
1242
|
+
case "fieldValueChanged":
|
|
1243
|
+
handleBridgeFieldValueChanged(event.payload);
|
|
1244
|
+
break;
|
|
1245
|
+
case "inlineEditEnded":
|
|
1246
|
+
handleBridgeInlineEditEnded(event.payload);
|
|
1247
|
+
activeBridgeInlineEditRef.current = null;
|
|
1248
|
+
break;
|
|
1249
|
+
case "interaction":
|
|
1250
|
+
pageViewContext.setBridgeInteraction(event.payload);
|
|
1251
|
+
void handleBridgeInteraction(event.payload, iframe);
|
|
1252
|
+
break;
|
|
1253
|
+
case "scrollChanged":
|
|
1254
|
+
latestBridgeScrollRef.current = event.payload.scroll;
|
|
1255
|
+
dispatchBridgeOverlayScroll(iframe, event.payload.scroll, getBridgeGeometryScrollScale(pageViewContextRef.current?.bridgeGeometry));
|
|
1256
|
+
if (shouldTrackMinimapScroll()) {
|
|
1257
|
+
scrollHandlerRef.current(event.payload.scroll.y);
|
|
1258
|
+
}
|
|
1259
|
+
break;
|
|
1260
|
+
case "renderError":
|
|
1261
|
+
console.warn("[Parhelia bridge] Host render error", event.payload);
|
|
1262
|
+
break;
|
|
1263
|
+
}
|
|
1264
|
+
},
|
|
1265
|
+
});
|
|
1266
|
+
bridgeClientRef.current = client;
|
|
1267
|
+
pageViewContext.setBridgeReady(false);
|
|
1268
|
+
client.connect();
|
|
1269
|
+
return () => {
|
|
1270
|
+
if (bridgeClientRef.current === client) {
|
|
1271
|
+
bridgeClientRef.current = null;
|
|
1272
|
+
}
|
|
1273
|
+
client.disconnect();
|
|
1274
|
+
clearBridgePatchSignatures();
|
|
1275
|
+
activeBridgeInlineEditRef.current = null;
|
|
1276
|
+
pageViewContext.setBridgeReady(false);
|
|
1277
|
+
pageViewContext.setIframeSupportsRefresh(false);
|
|
1278
|
+
};
|
|
1279
|
+
}, [
|
|
1280
|
+
iframeElement,
|
|
1281
|
+
iframeSrc,
|
|
1282
|
+
loadedIframeSrc,
|
|
1283
|
+
compareView,
|
|
1284
|
+
handleBridgeFieldValueChanged,
|
|
1285
|
+
handleBridgeInlineEditEnded,
|
|
1286
|
+
handleBridgeInteraction,
|
|
1287
|
+
handleBridgeSelection,
|
|
1288
|
+
clearBridgePatchSignatures,
|
|
1289
|
+
pageViewContext.editUrl,
|
|
1290
|
+
pageViewContext.previewUrl,
|
|
1291
|
+
scheduleBridgeDomRevision,
|
|
1292
|
+
scheduleBridgeGeometryRevision,
|
|
1293
|
+
shouldTrackMinimapScroll,
|
|
1294
|
+
]);
|
|
1295
|
+
useEffect(() => {
|
|
1296
|
+
const repository = editContext.itemsRepository;
|
|
1297
|
+
let disposed = false;
|
|
1298
|
+
const unsubscribe = repository.subscribeItemsChanged((changes) => {
|
|
1299
|
+
const fieldChanges = changes.filter((change) => change.action === "update" &&
|
|
1300
|
+
(change.changes.fields?.length ?? 0) > 0);
|
|
1301
|
+
if (fieldChanges.length === 0)
|
|
1302
|
+
return;
|
|
1303
|
+
const bridge = bridgeClientRef.current;
|
|
1304
|
+
const structure = pageViewContextRef.current?.bridgeStructure;
|
|
1305
|
+
if (!bridge || !structure?.fields.length)
|
|
1306
|
+
return;
|
|
1307
|
+
const processResolvedField = (change, changedFieldId, field) => {
|
|
1308
|
+
if (disposed)
|
|
1309
|
+
return;
|
|
1310
|
+
const patchedElementKeys = new Set();
|
|
1311
|
+
const patches = [];
|
|
1312
|
+
for (const structureField of structure.fields) {
|
|
1313
|
+
if (!structureField.elementKey)
|
|
1314
|
+
continue;
|
|
1315
|
+
if (patchedElementKeys.has(structureField.elementKey))
|
|
1316
|
+
continue;
|
|
1317
|
+
if (!bridgeDescriptorMatchesItem(structureField.item, change.item)) {
|
|
1318
|
+
continue;
|
|
1319
|
+
}
|
|
1320
|
+
if (!bridgeFieldMatchesChangedField(structureField.fieldId, changedFieldId, field)) {
|
|
1321
|
+
continue;
|
|
1322
|
+
}
|
|
1323
|
+
const patch = buildBridgeFieldPatch({
|
|
1324
|
+
field,
|
|
1325
|
+
structureField,
|
|
1326
|
+
itemDescriptor: change.item,
|
|
1327
|
+
preferRepositoryValue: change.source === "local-field-update",
|
|
1328
|
+
});
|
|
1329
|
+
if (!patch)
|
|
1330
|
+
continue;
|
|
1331
|
+
patchedElementKeys.add(structureField.elementKey);
|
|
1332
|
+
patches.push(patch);
|
|
1333
|
+
}
|
|
1334
|
+
for (const patch of patches) {
|
|
1335
|
+
if (disposed)
|
|
1336
|
+
return;
|
|
1337
|
+
const structureField = structure.fields.find((field) => field.elementKey === patch.elementKey &&
|
|
1338
|
+
field.fieldId === patch.fieldId);
|
|
1339
|
+
sendBridgeFieldPatch(bridge, patch, structureField?.textContent);
|
|
1340
|
+
}
|
|
1341
|
+
};
|
|
1342
|
+
const asyncFieldChanges = [];
|
|
1343
|
+
for (const change of fieldChanges) {
|
|
1344
|
+
const localFieldsById = new Map((change.changes.fieldValues ?? []).map((field) => [
|
|
1345
|
+
field.id.toLowerCase(),
|
|
1346
|
+
field,
|
|
1347
|
+
]));
|
|
1348
|
+
let needsAsyncLookup = false;
|
|
1349
|
+
for (const changedFieldId of change.changes.fields ?? []) {
|
|
1350
|
+
if (disposed)
|
|
1351
|
+
return;
|
|
1352
|
+
const localField = localFieldsById.get(changedFieldId.toLowerCase());
|
|
1353
|
+
if (!localField) {
|
|
1354
|
+
needsAsyncLookup = true;
|
|
1355
|
+
continue;
|
|
1356
|
+
}
|
|
1357
|
+
processResolvedField(change, changedFieldId, localField);
|
|
1358
|
+
}
|
|
1359
|
+
if (needsAsyncLookup) {
|
|
1360
|
+
asyncFieldChanges.push(change);
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
if (asyncFieldChanges.length === 0)
|
|
1364
|
+
return;
|
|
1365
|
+
void (async () => {
|
|
1366
|
+
for (const change of asyncFieldChanges) {
|
|
1367
|
+
if (disposed)
|
|
1368
|
+
return;
|
|
1369
|
+
const item = await repository.getItem(change.item);
|
|
1370
|
+
for (const changedFieldId of change.changes.fields ?? []) {
|
|
1371
|
+
if (disposed)
|
|
1372
|
+
return;
|
|
1373
|
+
const field = change.changes.fieldValues?.find((candidate) => fieldIdentifierMatches(candidate, changedFieldId)) ??
|
|
1374
|
+
item?.fields.find((candidate) => fieldIdentifierMatches(candidate, changedFieldId)) ??
|
|
1375
|
+
(await repository.getField({
|
|
1376
|
+
fieldId: changedFieldId,
|
|
1377
|
+
item: change.item,
|
|
1378
|
+
}));
|
|
1379
|
+
if (!field)
|
|
1380
|
+
continue;
|
|
1381
|
+
processResolvedField(change, changedFieldId, field);
|
|
1382
|
+
}
|
|
1383
|
+
}
|
|
1384
|
+
})().catch((error) => {
|
|
1385
|
+
if (!disposed) {
|
|
1386
|
+
console.warn("[Parhelia bridge] Failed to patch host field", error);
|
|
1387
|
+
}
|
|
1388
|
+
});
|
|
1389
|
+
});
|
|
1390
|
+
return () => {
|
|
1391
|
+
disposed = true;
|
|
1392
|
+
unsubscribe();
|
|
1393
|
+
};
|
|
1394
|
+
}, [
|
|
1395
|
+
buildBridgeFieldPatch,
|
|
1396
|
+
editContext.itemsRepository,
|
|
1397
|
+
sendBridgeFieldPatch,
|
|
1398
|
+
]);
|
|
1399
|
+
useEffect(() => {
|
|
1400
|
+
const bridge = bridgeClientRef.current;
|
|
1401
|
+
const structure = pageViewContext.bridgeStructure;
|
|
1402
|
+
if (!pageViewContext.bridgeReady || !bridge || !structure?.fields.length) {
|
|
1403
|
+
return;
|
|
1404
|
+
}
|
|
1405
|
+
let disposed = false;
|
|
1406
|
+
void (async () => {
|
|
1407
|
+
const patchedElementKeys = new Set();
|
|
1408
|
+
for (const structureField of structure.fields) {
|
|
1409
|
+
if (disposed)
|
|
1410
|
+
return;
|
|
1411
|
+
if (!structureField.elementKey)
|
|
1412
|
+
continue;
|
|
1413
|
+
if (patchedElementKeys.has(structureField.elementKey))
|
|
1414
|
+
continue;
|
|
1415
|
+
const fallbackItem = pageViewContextRef.current?.pageItemDescriptor ??
|
|
1416
|
+
pageViewContextRef.current?.page?.item?.descriptor;
|
|
1417
|
+
const bridgeItem = structureField.item;
|
|
1418
|
+
const itemDescriptor = {
|
|
1419
|
+
id: bridgeItem?.id ?? fallbackItem?.id,
|
|
1420
|
+
language: bridgeItem?.language ?? fallbackItem?.language,
|
|
1421
|
+
version: typeof bridgeItem?.version === "number"
|
|
1422
|
+
? bridgeItem.version
|
|
1423
|
+
: fallbackItem?.version,
|
|
1424
|
+
};
|
|
1425
|
+
if (!itemDescriptor.id ||
|
|
1426
|
+
!itemDescriptor.language ||
|
|
1427
|
+
typeof itemDescriptor.version !== "number") {
|
|
1428
|
+
continue;
|
|
1429
|
+
}
|
|
1430
|
+
const loadedItem = await editContext.itemsRepository.getItem(itemDescriptor);
|
|
1431
|
+
if (disposed || !loadedItem)
|
|
1432
|
+
continue;
|
|
1433
|
+
const field = loadedItem.fields.find((candidate) => fieldIdentifierMatches(candidate, structureField.fieldId));
|
|
1434
|
+
if (!field)
|
|
1435
|
+
continue;
|
|
1436
|
+
const patch = buildBridgeFieldPatch({
|
|
1437
|
+
field,
|
|
1438
|
+
structureField,
|
|
1439
|
+
itemDescriptor: itemDescriptor,
|
|
1414
1440
|
});
|
|
1441
|
+
if (!patch)
|
|
1442
|
+
continue;
|
|
1443
|
+
patchedElementKeys.add(structureField.elementKey);
|
|
1444
|
+
sendBridgeFieldPatch(bridge, patch, structureField.textContent);
|
|
1445
|
+
}
|
|
1446
|
+
})().catch((error) => {
|
|
1447
|
+
if (!disposed) {
|
|
1448
|
+
console.warn("[Parhelia bridge] Failed to patch host suggestion display", error);
|
|
1449
|
+
}
|
|
1450
|
+
});
|
|
1451
|
+
return () => {
|
|
1452
|
+
disposed = true;
|
|
1453
|
+
};
|
|
1454
|
+
}, [
|
|
1455
|
+
buildBridgeFieldPatch,
|
|
1456
|
+
editContext.itemsRepository,
|
|
1457
|
+
editContext.itemsRepository.revision,
|
|
1458
|
+
editContext.mode,
|
|
1459
|
+
editContext.showSuggestedEdits,
|
|
1460
|
+
editContext.suggestedEdits,
|
|
1461
|
+
fieldsContext?.modifiedFields,
|
|
1462
|
+
bridgeDomRevision,
|
|
1463
|
+
pageViewContext.bridgeReady,
|
|
1464
|
+
pageViewContext.bridgeStructure,
|
|
1465
|
+
sendBridgeFieldPatch,
|
|
1466
|
+
]);
|
|
1467
|
+
useEffect(() => {
|
|
1468
|
+
const requestBridgeGeometry = (payload) => {
|
|
1469
|
+
const bridge = bridgeClientRef.current;
|
|
1470
|
+
if (!bridge)
|
|
1471
|
+
return false;
|
|
1472
|
+
// The editing host only remembers the most recent queryGeometry payload.
|
|
1473
|
+
// Comments, suggestions, locks and version-diff each contribute text
|
|
1474
|
+
// ranges independently, so without merging they clobbered one another and
|
|
1475
|
+
// fought in an endless re-request loop (heavy highlight flicker). Keep the
|
|
1476
|
+
// latest ranges per `source` and always query with their union.
|
|
1477
|
+
if (payload && typeof payload.source === "string") {
|
|
1478
|
+
const { source, textRanges, ...rest } = payload;
|
|
1479
|
+
const sources = bridgeTextRangeSourcesRef.current;
|
|
1480
|
+
if (textRanges && textRanges.length > 0) {
|
|
1481
|
+
sources.set(source, textRanges);
|
|
1482
|
+
}
|
|
1483
|
+
else {
|
|
1484
|
+
sources.delete(source);
|
|
1485
|
+
}
|
|
1486
|
+
const mergedTextRanges = Array.from(sources.values()).flat();
|
|
1487
|
+
return (bridge.sendCommand("queryGeometry", {
|
|
1488
|
+
...rest,
|
|
1489
|
+
textRanges: mergedTextRanges,
|
|
1490
|
+
}) ?? false);
|
|
1491
|
+
}
|
|
1492
|
+
return bridge.sendCommand("queryGeometry", payload) ?? false;
|
|
1493
|
+
};
|
|
1494
|
+
pageViewContext.requestBridgeGeometry = requestBridgeGeometry;
|
|
1495
|
+
const requestBridgeScrollBy = (payload) => {
|
|
1496
|
+
return bridgeClientRef.current?.sendCommand("scrollBy", payload) ?? false;
|
|
1497
|
+
};
|
|
1498
|
+
const requestBridgeRichTextCommand = (payload) => {
|
|
1499
|
+
return (bridgeClientRef.current?.sendCommand("applyRichTextCommand", payload) ??
|
|
1500
|
+
false);
|
|
1501
|
+
};
|
|
1502
|
+
pageViewContext.requestBridgeScrollBy = requestBridgeScrollBy;
|
|
1503
|
+
pageViewContext.requestBridgeRichTextCommand = requestBridgeRichTextCommand;
|
|
1504
|
+
return () => {
|
|
1505
|
+
if (pageViewContext.requestBridgeGeometry === requestBridgeGeometry) {
|
|
1506
|
+
pageViewContext.requestBridgeGeometry = undefined;
|
|
1507
|
+
}
|
|
1508
|
+
if (pageViewContext.requestBridgeScrollBy === requestBridgeScrollBy) {
|
|
1509
|
+
pageViewContext.requestBridgeScrollBy = undefined;
|
|
1510
|
+
}
|
|
1511
|
+
if (pageViewContext.requestBridgeRichTextCommand ===
|
|
1512
|
+
requestBridgeRichTextCommand) {
|
|
1513
|
+
pageViewContext.requestBridgeRichTextCommand = undefined;
|
|
1514
|
+
}
|
|
1515
|
+
};
|
|
1516
|
+
}, [pageViewContext]);
|
|
1517
|
+
useEffect(() => {
|
|
1518
|
+
const requestBridgeCaptureDom = (payload) => {
|
|
1519
|
+
const bridge = bridgeClientRef.current;
|
|
1520
|
+
if (!bridge) {
|
|
1521
|
+
return Promise.reject(new Error("The Parhelia bridge is not connected to the host."));
|
|
1522
|
+
}
|
|
1523
|
+
return bridge.requestCommand("captureDom", payload, "captureDomResult", {
|
|
1524
|
+
requestId: payload.requestId,
|
|
1525
|
+
});
|
|
1526
|
+
};
|
|
1527
|
+
pageViewContext.requestBridgeCaptureDom = requestBridgeCaptureDom;
|
|
1528
|
+
return () => {
|
|
1529
|
+
if (pageViewContext.requestBridgeCaptureDom === requestBridgeCaptureDom) {
|
|
1530
|
+
pageViewContext.requestBridgeCaptureDom = undefined;
|
|
1531
|
+
}
|
|
1532
|
+
};
|
|
1533
|
+
}, [pageViewContext]);
|
|
1534
|
+
const updateMiniMapVisibility = useDebouncedCallback(() => {
|
|
1535
|
+
if (!iframeRef.current)
|
|
1536
|
+
return;
|
|
1537
|
+
const iframe = iframeRef.current;
|
|
1538
|
+
const bridgeGeometry = pageViewContextRef.current?.bridgeGeometry;
|
|
1539
|
+
const bridgeDom = pageViewContextRef.current?.bridgeDom;
|
|
1540
|
+
const contentHeight = bridgeDom?.scrollHeight ??
|
|
1541
|
+
getBridgeGeometryDocumentSize(bridgeGeometry)?.height;
|
|
1542
|
+
const clientHeight = iframe.clientHeight;
|
|
1543
|
+
if (!contentHeight || !clientHeight)
|
|
1544
|
+
return;
|
|
1545
|
+
const upperThreshold = clientHeight + 100; // show minimap if content exceeds this height
|
|
1546
|
+
const lowerThreshold = clientHeight; // hide minimap if content falls below this
|
|
1547
|
+
// Check if minimap is enabled in settings and user controls
|
|
1548
|
+
const minimapEnabled = editContext.parheliaSettings?.showMinimap !== false &&
|
|
1549
|
+
editContext.showMinimap;
|
|
1550
|
+
if (showMiniMap) {
|
|
1551
|
+
if (contentHeight <= lowerThreshold || !minimapEnabled) {
|
|
1552
|
+
setShowMiniMap(false);
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
else {
|
|
1556
|
+
if (contentHeight > upperThreshold && minimapEnabled) {
|
|
1557
|
+
setShowMiniMap(true);
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
}, 100);
|
|
1561
|
+
useEffect(() => {
|
|
1562
|
+
updateMiniMapVisibility();
|
|
1563
|
+
}, [
|
|
1564
|
+
bridgeDomRevision,
|
|
1565
|
+
bridgeGeometryRevision,
|
|
1566
|
+
editContext.parheliaSettings?.showMinimap,
|
|
1567
|
+
editContext.showMinimap,
|
|
1568
|
+
iframeElement,
|
|
1569
|
+
showMiniMap,
|
|
1570
|
+
updateMiniMapVisibility,
|
|
1571
|
+
]);
|
|
1572
|
+
// If the editor mode flips into preview, clear any active text selection state.
|
|
1573
|
+
useEffect(() => {
|
|
1574
|
+
const isPreview = editContext.mode === "preview";
|
|
1575
|
+
if (isPreview) {
|
|
1576
|
+
editContextRef.current?.setSelectedRange(undefined);
|
|
1577
|
+
}
|
|
1578
|
+
}, [editContext.mode]);
|
|
1579
|
+
useEffect(() => {
|
|
1580
|
+
if (!pageItemDescriptor ||
|
|
1581
|
+
!pageViewContext.editUrl ||
|
|
1582
|
+
!pageViewContext.previewUrl)
|
|
1583
|
+
return;
|
|
1584
|
+
const urlPath = editContext.mode === "preview"
|
|
1585
|
+
? pageViewContext.previewUrl
|
|
1586
|
+
: pageViewContext.editUrl;
|
|
1587
|
+
const renderUrl = new URL(urlPath, window.location.origin);
|
|
1588
|
+
alignLoopbackHostToEditor(renderUrl);
|
|
1589
|
+
const editRev = uuid();
|
|
1590
|
+
renderUrl.searchParams.set("edit_rev", editRev);
|
|
1591
|
+
if (editContext.mode !== "preview" && editContext.sessionId) {
|
|
1592
|
+
renderUrl.searchParams.set("parhelia_session", editContext.sessionId);
|
|
1593
|
+
}
|
|
1594
|
+
if (editContext.mode === "preview" && editContext.previewDate) {
|
|
1595
|
+
renderUrl.searchParams.delete("sc_version");
|
|
1596
|
+
renderUrl.searchParams.set("sc_date", toSitecoreDate(editContext.previewDate));
|
|
1597
|
+
}
|
|
1598
|
+
// Layout-mode marker. Only set when editing shared layout, paired with the
|
|
1599
|
+
// `parhelia` editor marker so ParheliaSetLayoutRenderings ignores any unrelated
|
|
1600
|
+
// requests that happen to carry parhelia_layout.
|
|
1601
|
+
if (editContext.mode !== "preview" && editContext.layoutMode === "shared") {
|
|
1602
|
+
renderUrl.searchParams.set("parhelia", "1");
|
|
1603
|
+
renderUrl.searchParams.set("parhelia_layout", "shared");
|
|
1604
|
+
}
|
|
1605
|
+
else {
|
|
1606
|
+
renderUrl.searchParams.delete("parhelia_layout");
|
|
1607
|
+
}
|
|
1608
|
+
// Detect if the version in the URL changed - this requires a full reload, not just requestRefresh
|
|
1609
|
+
// because Next.js router.replace may not properly refetch server data for version changes.
|
|
1610
|
+
const currentIframeUrl = iframeRef.current?.src;
|
|
1611
|
+
const currentIframeOrigin = getUrlOrigin(currentIframeUrl);
|
|
1612
|
+
const renderUrlIsCrossOrigin = renderUrl.origin !== window.location.origin;
|
|
1613
|
+
const iframeOriginChanged = !!(currentIframeOrigin && currentIframeOrigin !== renderUrl.origin);
|
|
1614
|
+
const currentVersion = currentIframeUrl
|
|
1615
|
+
? new URL(currentIframeUrl).searchParams.get("sc_version")
|
|
1616
|
+
: null;
|
|
1617
|
+
const newVersion = renderUrl.searchParams.get("sc_version");
|
|
1618
|
+
const versionChanged = (currentVersion !== null || newVersion !== null) &&
|
|
1619
|
+
currentVersion !== newVersion;
|
|
1620
|
+
const initialLoad = currentItemDescriptor?.id !== pageItemDescriptor.id ||
|
|
1621
|
+
currentItemDescriptor?.language !== pageItemDescriptor.language ||
|
|
1622
|
+
currentItemDescriptor?.version !== pageItemDescriptor.version;
|
|
1623
|
+
const shouldUseIframeSrcReload = initialLoad || pageViewContext.fullscreen || iframeOriginChanged;
|
|
1624
|
+
function runFallbackRefresh() {
|
|
1625
|
+
pageViewContext.setIframeSupportsRefresh(false);
|
|
1626
|
+
pendingRefreshScrollRef.current ??=
|
|
1627
|
+
getIframeWindowScroll(iframeRef.current) ??
|
|
1628
|
+
latestBridgeScrollRef.current;
|
|
1629
|
+
setShowSpinner(true);
|
|
1630
|
+
console.log(initialLoad
|
|
1631
|
+
? "Initial load - setting iframe src"
|
|
1632
|
+
: renderUrlIsCrossOrigin
|
|
1633
|
+
? "Cross-origin load - setting iframe src"
|
|
1634
|
+
: iframeOriginChanged
|
|
1635
|
+
? "Iframe origin changed - setting iframe src"
|
|
1636
|
+
: "Reloading iframe src");
|
|
1637
|
+
setLoadedIframeSrc(undefined);
|
|
1638
|
+
setIframeSrc(renderUrl.toString());
|
|
1639
|
+
}
|
|
1640
|
+
function runBridgeRefresh() {
|
|
1641
|
+
pendingRefreshScrollRef.current =
|
|
1642
|
+
getIframeWindowScroll(iframeRef.current) ??
|
|
1643
|
+
latestBridgeScrollRef.current;
|
|
1644
|
+
const bridge = bridgeClientRef.current;
|
|
1645
|
+
if (!bridge?.supportsCapability("refresh")) {
|
|
1646
|
+
runFallbackRefresh();
|
|
1647
|
+
return;
|
|
1648
|
+
}
|
|
1649
|
+
console.log("Bridge - requesting iframe refresh");
|
|
1650
|
+
pageViewContext.setIframeSupportsRefresh(true);
|
|
1651
|
+
const accepted = bridge.sendCommand("refresh", {
|
|
1652
|
+
url: renderUrl.toString(),
|
|
1653
|
+
revision: editContext.revision,
|
|
1654
|
+
layoutKind: editContext.layoutMode,
|
|
1655
|
+
});
|
|
1656
|
+
if (!accepted) {
|
|
1657
|
+
pendingRefreshScrollRef.current = undefined;
|
|
1658
|
+
pageViewContext.setIframeSupportsRefresh(false);
|
|
1659
|
+
runFallbackRefresh();
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
if (!shouldUseIframeSrcReload && !versionChanged) {
|
|
1663
|
+
if (bridgeClientRef.current?.supportsCapability("refresh")) {
|
|
1664
|
+
runBridgeRefresh();
|
|
1665
|
+
}
|
|
1666
|
+
else {
|
|
1667
|
+
const retryDelayMs = 150;
|
|
1668
|
+
const retryTimer = setTimeout(() => {
|
|
1669
|
+
runBridgeRefresh();
|
|
1670
|
+
}, retryDelayMs);
|
|
1671
|
+
setCurrentItemDescriptor(pageItemDescriptor);
|
|
1672
|
+
return () => clearTimeout(retryTimer);
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
else {
|
|
1676
|
+
runFallbackRefresh();
|
|
1677
|
+
}
|
|
1678
|
+
setCurrentItemDescriptor(pageItemDescriptor);
|
|
1679
|
+
}, [
|
|
1680
|
+
pathname,
|
|
1681
|
+
editContext.revision,
|
|
1682
|
+
pageItemDescriptor,
|
|
1683
|
+
pageViewContext.editUrl,
|
|
1684
|
+
pageViewContext.previewUrl,
|
|
1685
|
+
pageViewContext.fullscreen,
|
|
1686
|
+
editContext.mode,
|
|
1687
|
+
editContext.previewDate,
|
|
1688
|
+
editContext.sessionId,
|
|
1689
|
+
editContext.layoutMode,
|
|
1690
|
+
]);
|
|
1691
|
+
useEffect(() => {
|
|
1692
|
+
if (fieldsContext?.focusedField) {
|
|
1693
|
+
if (editContext.selection.length > 0 &&
|
|
1694
|
+
fieldsContext.focusedField.item.id !== editContext.selection[0])
|
|
1695
|
+
return;
|
|
1696
|
+
const bounds = findBridgeFieldDocumentBounds(pageViewContextRef.current?.bridgeGeometry, fieldsContext.focusedField);
|
|
1697
|
+
const activePageViewContext = pageViewContextRef.current;
|
|
1698
|
+
if (bounds && activePageViewContext) {
|
|
1699
|
+
scrollBridgeBoundsIntoView(activePageViewContext, bounds, latestBridgeScrollRef.current);
|
|
1700
|
+
}
|
|
1701
|
+
}
|
|
1702
|
+
}, [fieldsContext?.focusedField]);
|
|
1703
|
+
useEffect(() => {
|
|
1704
|
+
const selectionKey = editContext.selection.join("|");
|
|
1705
|
+
const selectionChanged = selectionKey !== lastScrolledSelectionKeyRef.current;
|
|
1706
|
+
lastScrolledSelectionKeyRef.current = selectionKey;
|
|
1707
|
+
// The effect also re-runs when focusedField changes (e.g. a field blurs
|
|
1708
|
+
// after a click). Only the selection actually changing should drive an
|
|
1709
|
+
// auto-scroll; otherwise a blur re-render scrolls the just-clicked
|
|
1710
|
+
// component away.
|
|
1711
|
+
if (!selectionChanged)
|
|
1712
|
+
return;
|
|
1713
|
+
if (suppressNextSelectionScrollRef.current) {
|
|
1714
|
+
suppressNextSelectionScrollRef.current = false;
|
|
1715
|
+
return;
|
|
1716
|
+
}
|
|
1717
|
+
if (!fieldsContext?.focusedField && editContext.selection.length > 0) {
|
|
1718
|
+
const lastSelectedComponent = getComponentById(editContext.selection[editContext.selection.length - 1], pageViewContextRef.current.page);
|
|
1719
|
+
if (lastSelectedComponent) {
|
|
1720
|
+
editContext.setScrollIntoView(lastSelectedComponent.id);
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
}, [editContext.selection, fieldsContext?.focusedField]);
|
|
1724
|
+
useEffect(() => {
|
|
1725
|
+
if (!editContext.scrollIntoView)
|
|
1726
|
+
return;
|
|
1727
|
+
const activePageViewContext = pageViewContextRef.current;
|
|
1728
|
+
if (!activePageViewContext)
|
|
1729
|
+
return;
|
|
1730
|
+
const bounds = findBridgeComponentDocumentBounds(activePageViewContext.bridgeGeometry, editContext.scrollIntoView);
|
|
1731
|
+
if (bounds) {
|
|
1732
|
+
scrollBridgeBoundsIntoView(activePageViewContext, bounds, latestBridgeScrollRef.current);
|
|
1733
|
+
}
|
|
1734
|
+
editContext.setScrollIntoView(undefined);
|
|
1735
|
+
}, [editContext.scrollIntoView, pageViewContext.page]);
|
|
1736
|
+
useEffect(() => {
|
|
1737
|
+
const handleMessage = (message) => {
|
|
1738
|
+
if (message.origin !== window.location.origin)
|
|
1415
1739
|
return;
|
|
1740
|
+
if (message.data.type === "editor-exitFullscreen") {
|
|
1741
|
+
pageViewContext.setFullscreen(false);
|
|
1416
1742
|
}
|
|
1743
|
+
if (message.data.type === "editor-timings") {
|
|
1744
|
+
editContext.setTimings(message.data.timings);
|
|
1745
|
+
}
|
|
1746
|
+
};
|
|
1747
|
+
window.addEventListener("message", handleMessage);
|
|
1748
|
+
return () => {
|
|
1749
|
+
window.removeEventListener("message", handleMessage);
|
|
1750
|
+
};
|
|
1751
|
+
}, []);
|
|
1752
|
+
useEffect(() => {
|
|
1753
|
+
const iframe = iframeRef.current;
|
|
1754
|
+
if (!iframe)
|
|
1755
|
+
return;
|
|
1756
|
+
const handleLoad = () => {
|
|
1417
1757
|
setShowSpinner(false);
|
|
1418
1758
|
applyIframeZoom(iframe, pageViewContextRef.current?.zoom ?? 1, "PageViewerFrame.tsx:handleLoad-zoom");
|
|
1419
|
-
attachListeners();
|
|
1420
1759
|
};
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1760
|
+
iframe.addEventListener("load", handleLoad);
|
|
1761
|
+
return () => {
|
|
1762
|
+
iframe.removeEventListener("load", handleLoad);
|
|
1763
|
+
};
|
|
1764
|
+
}, [iframeElement]);
|
|
1765
|
+
useEffect(() => {
|
|
1766
|
+
const iframe = iframeRef.current;
|
|
1767
|
+
if (!iframe || typeof ResizeObserver === "undefined")
|
|
1768
|
+
return;
|
|
1769
|
+
let lastWidth = iframe.clientWidth;
|
|
1770
|
+
let lastHeight = iframe.clientHeight;
|
|
1771
|
+
let geometryTimer = null;
|
|
1772
|
+
let trailingGeometryTimer = null;
|
|
1773
|
+
const requestGeometry = () => {
|
|
1774
|
+
geometryTimer = null;
|
|
1775
|
+
const nextWidth = iframe.clientWidth;
|
|
1776
|
+
const nextHeight = iframe.clientHeight;
|
|
1777
|
+
if (nextWidth === lastWidth && nextHeight === lastHeight)
|
|
1778
|
+
return;
|
|
1779
|
+
lastWidth = nextWidth;
|
|
1780
|
+
lastHeight = nextHeight;
|
|
1781
|
+
bridgeClientRef.current?.sendCommand("queryGeometry", {});
|
|
1782
|
+
};
|
|
1783
|
+
const scheduleGeometryRequest = () => {
|
|
1784
|
+
if (geometryTimer != null) {
|
|
1785
|
+
window.clearTimeout(geometryTimer);
|
|
1428
1786
|
}
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1787
|
+
if (trailingGeometryTimer != null) {
|
|
1788
|
+
window.clearTimeout(trailingGeometryTimer);
|
|
1789
|
+
}
|
|
1790
|
+
geometryTimer = window.setTimeout(requestGeometry, 50);
|
|
1791
|
+
trailingGeometryTimer = window.setTimeout(() => {
|
|
1792
|
+
trailingGeometryTimer = null;
|
|
1793
|
+
requestGeometry();
|
|
1794
|
+
}, ZOOM_TRANSITION_MS + 75);
|
|
1795
|
+
};
|
|
1796
|
+
const resizeObserver = new ResizeObserver(scheduleGeometryRequest);
|
|
1797
|
+
resizeObserver.observe(iframe);
|
|
1432
1798
|
return () => {
|
|
1433
|
-
|
|
1434
|
-
if (
|
|
1435
|
-
|
|
1799
|
+
resizeObserver.disconnect();
|
|
1800
|
+
if (geometryTimer != null) {
|
|
1801
|
+
window.clearTimeout(geometryTimer);
|
|
1802
|
+
}
|
|
1803
|
+
if (trailingGeometryTimer != null) {
|
|
1804
|
+
window.clearTimeout(trailingGeometryTimer);
|
|
1436
1805
|
}
|
|
1437
|
-
detachListeners();
|
|
1438
1806
|
};
|
|
1439
|
-
}, [
|
|
1807
|
+
}, [iframeElement]);
|
|
1440
1808
|
useEffect(() => {
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
}
|
|
1444
|
-
catch { }
|
|
1809
|
+
bridgeClientRef.current?.sendCommand("setSelection", {
|
|
1810
|
+
componentIds: editContext.selection,
|
|
1811
|
+
});
|
|
1445
1812
|
}, [editContext.selection]);
|
|
1446
|
-
|
|
1447
|
-
const
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1813
|
+
useEffect(() => {
|
|
1814
|
+
const bridge = bridgeClientRef.current;
|
|
1815
|
+
bridge?.sendCommand("setPreviewMode", {
|
|
1816
|
+
enabled: editContext.mode === "preview",
|
|
1817
|
+
});
|
|
1818
|
+
bridge?.sendCommand("setEditorMode", {
|
|
1819
|
+
mode: editContext.mode,
|
|
1820
|
+
});
|
|
1821
|
+
}, [editContext.mode]);
|
|
1822
|
+
useEffect(() => {
|
|
1823
|
+
bridgeClientRef.current?.sendCommand("setLayoutKind", {
|
|
1824
|
+
layoutKind: editContext.layoutMode,
|
|
1825
|
+
});
|
|
1826
|
+
}, [editContext.layoutMode]);
|
|
1457
1827
|
useEffect(() => {
|
|
1458
1828
|
applyIframeZoom(iframeRef.current, zoom, "PageViewerFrame.tsx:zoom-doc");
|
|
1829
|
+
bridgeClientRef.current?.sendCommand("setZoom", { zoom });
|
|
1830
|
+
const geometryTimer = window.setTimeout(() => {
|
|
1831
|
+
bridgeClientRef.current?.sendCommand("queryGeometry", {});
|
|
1832
|
+
}, ZOOM_TRANSITION_MS + 75);
|
|
1833
|
+
return () => {
|
|
1834
|
+
window.clearTimeout(geometryTimer);
|
|
1835
|
+
};
|
|
1836
|
+
}, [zoom]);
|
|
1837
|
+
useEffect(() => {
|
|
1838
|
+
let geometryTimer = null;
|
|
1839
|
+
const sendViewportStateToBridge = (event) => {
|
|
1840
|
+
const detail = event.detail;
|
|
1841
|
+
const nextZoom = detail?.zoom ?? pageViewContextRef.current?.zoom ?? zoom;
|
|
1842
|
+
bridgeClientRef.current?.sendCommand("setZoom", {
|
|
1843
|
+
zoom: nextZoom,
|
|
1844
|
+
});
|
|
1845
|
+
if (geometryTimer != null) {
|
|
1846
|
+
window.clearTimeout(geometryTimer);
|
|
1847
|
+
}
|
|
1848
|
+
geometryTimer = window.setTimeout(() => {
|
|
1849
|
+
geometryTimer = null;
|
|
1850
|
+
bridgeClientRef.current?.sendCommand("queryGeometry", {});
|
|
1851
|
+
}, ZOOM_TRANSITION_MS + 75);
|
|
1852
|
+
};
|
|
1853
|
+
window.addEventListener(DEVICE_CHANGE_EVENT, sendViewportStateToBridge);
|
|
1854
|
+
return () => {
|
|
1855
|
+
if (geometryTimer != null) {
|
|
1856
|
+
window.clearTimeout(geometryTimer);
|
|
1857
|
+
}
|
|
1858
|
+
window.removeEventListener(DEVICE_CHANGE_EVENT, sendViewportStateToBridge);
|
|
1859
|
+
};
|
|
1459
1860
|
}, [zoom]);
|
|
1460
|
-
const scrollHandler = useThrottledCallback(updateScrollPosition, 100);
|
|
1461
1861
|
if (pageViewContext.page?.item && !pageViewContext.page?.item.hasLayout) {
|
|
1462
1862
|
return _jsx(NoLayout, {});
|
|
1463
1863
|
}
|
|
@@ -1473,265 +1873,38 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
1473
1873
|
"px";
|
|
1474
1874
|
return (_jsxs("div", { className: cn("relative flex h-full w-full flex-col items-center select-none", className, editContext.showAgentsPanel && !editContext.currentWizardId && "pr-0"), children: [!pageViewContext.fullscreen && (_jsx(EditorWarnings, { item: pageViewContext.page?.item })), slotCloseButton && (_jsx("div", { className: "absolute top-3 right-3 z-50", children: slotCloseButton })), pageViewContext.device !== "desktop" && (_jsx(DeviceToolbar, { pageViewContext: pageViewContext, configuration: editContext.configuration })), _jsxs("div", { className: "relative flex flex-1 transition-[width] duration-300 ease-in-out select-none motion-reduce:transition-none", "data-testid": "page-viewer-viewport", style: {
|
|
1475
1875
|
width: deviceWidth,
|
|
1476
|
-
}, children: [_jsxs("div", { className: "relative h-full w-full overflow-hidden", children: [_jsx("iframe", { ref:
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1876
|
+
}, children: [_jsxs("div", { className: "relative h-full w-full overflow-hidden", children: [_jsx("iframe", { ref: bindIframeRef, className: "page-iframe h-full w-full bg-white transition-[height] duration-300 ease-in-out motion-reduce:transition-none", style: { height: deviceHeight }, src: iframeSrc, "data-testid": "pageEditoriframe", onLoad: () => {
|
|
1877
|
+
const loadedSrc = iframeRef.current?.src;
|
|
1878
|
+
if (loadedSrc) {
|
|
1879
|
+
setLoadedIframeSrc(loadedSrc);
|
|
1880
|
+
}
|
|
1881
|
+
const sendZoomToBridge = () => {
|
|
1882
|
+
bridgeClientRef.current?.sendCommand("setZoom", {
|
|
1883
|
+
zoom: pageViewContextRef.current?.zoom ?? zoom,
|
|
1884
|
+
});
|
|
1885
|
+
};
|
|
1886
|
+
sendZoomToBridge();
|
|
1887
|
+
window.setTimeout(sendZoomToBridge, 100);
|
|
1888
|
+
window.setTimeout(sendZoomToBridge, 500);
|
|
1889
|
+
if (iframeSrc) {
|
|
1480
1890
|
setShowSpinner(false);
|
|
1481
|
-
ensureEditorCssGuard(doc, editContext.mode !== "preview");
|
|
1482
1891
|
applyIframeZoom(iframeRef.current, zoom, "PageViewerFrame.tsx:onLoad-zoom");
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1892
|
+
if (pendingRefreshScrollRef.current) {
|
|
1893
|
+
const targetScroll = pendingRefreshScrollRef.current;
|
|
1894
|
+
pendingRefreshScrollRef.current = undefined;
|
|
1895
|
+
restoreIframeWindowScroll(iframeRef.current, targetScroll);
|
|
1896
|
+
const observedScroll = getIframeWindowScroll(iframeRef.current) ?? { x: 0, y: 0 };
|
|
1897
|
+
bridgeClientRef.current?.sendCommand("scrollBy", {
|
|
1898
|
+
x: targetScroll.x - observedScroll.x,
|
|
1899
|
+
y: targetScroll.y - observedScroll.y,
|
|
1900
|
+
behavior: "instant",
|
|
1901
|
+
});
|
|
1902
|
+
}
|
|
1487
1903
|
}
|
|
1488
|
-
} }),
|
|
1904
|
+
} }), iframeElement && (_jsx(IframeOverlayProvider, { iframe: iframeElement, mode: "scrolling", scrollScale: 1, visualScale: 1, invalidationKey: `${zoom}:${bridgeGeometryRevision}`, deferredInvalidationMs: ZOOM_TRANSITION_MS + 50, children: _jsx(PageEditorChrome, { iframe: iframeElement, compareView: compareView, pageViewContext: pageViewContext, onBridgeRichTextCommand: sendApplyRichTextCommand }) })), pageViewContext.deviceHeight && pageViewContext.device && (_jsx("div", { className: "bg-neutral-grey-5 relative z-40 h-full w-full" }))] }), !pageViewContext.fullscreen &&
|
|
1489
1905
|
showMiniMap &&
|
|
1490
1906
|
editContext.showMinimap &&
|
|
1491
1907
|
!editContext.isMobile &&
|
|
1492
|
-
editContext.parheliaSettings?.showMinimap !== false && (_jsx(MiniMap, { compareView: compareView, scroll: scroll, mainViewIframeRef: iframeRef, pageViewContext: pageViewContext,
|
|
1493
|
-
? undefined
|
|
1494
|
-
: pageViewContext.deviceHeight })), showSpinner && (_jsxs(_Fragment, { children: [_jsx("div", { className: "bg-neutral-grey-5/50 absolute top-0 left-0 h-full w-full" }), _jsx("div", { className: "absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 transform", children: _jsx(Spinner, {}) })] }))] }), _jsx(FieldActionsOverlay, { ref: fieldActionsOverlay, generatorButtons: contextMenuFieldButtons, onActionClick: handleActionClick, onParameterizedActionExecute: handleParameterizedActionExecute, currentOverlay: editContext?.currentOverlay, fieldId: contextMenuField?.fieldId || "", setCurrentOverlay: editContext?.setCurrentOverlay || (() => { }), preSelectedAction: preSelectedAction, iframe: iframeRef.current })] }));
|
|
1495
|
-
}
|
|
1496
|
-
/**
|
|
1497
|
-
* In preview mode, disable editing in the iframe: set contentEditable=false on all
|
|
1498
|
-
* editable elements and blur the active element so the caret is removed and typing does nothing.
|
|
1499
|
-
*/
|
|
1500
|
-
function disableEditingInIframeDocument(doc) {
|
|
1501
|
-
if (!doc || !doc.body)
|
|
1502
|
-
return;
|
|
1503
|
-
const editable = doc.querySelectorAll("[contenteditable='true'], [contenteditable='']");
|
|
1504
|
-
editable.forEach((el) => {
|
|
1505
|
-
el.contentEditable = "false";
|
|
1506
|
-
});
|
|
1507
|
-
const active = doc.activeElement;
|
|
1508
|
-
if (active?.isContentEditable) {
|
|
1509
|
-
active.blur();
|
|
1510
|
-
}
|
|
1511
|
-
}
|
|
1512
|
-
function injectEditorCSS(iframeDocument, editMode) {
|
|
1513
|
-
if (!iframeDocument)
|
|
1514
|
-
return;
|
|
1515
|
-
const styleId = EDITOR_CSS_STYLE_ID;
|
|
1516
|
-
// Remove existing style element if present to avoid duplicates
|
|
1517
|
-
const existingStyle = iframeDocument.getElementById(styleId);
|
|
1518
|
-
if (existingStyle) {
|
|
1519
|
-
existingStyle.remove();
|
|
1520
|
-
}
|
|
1521
|
-
const style = iframeDocument.createElement("style");
|
|
1522
|
-
style.id = styleId;
|
|
1523
|
-
style.textContent = editMode
|
|
1524
|
-
? `[contentEditable]:empty:before {
|
|
1525
|
-
content: attr(placeholder);
|
|
1526
|
-
opacity: 0.6;
|
|
1527
|
-
}
|
|
1528
|
-
|
|
1529
|
-
.scChromeData, code {
|
|
1530
|
-
display: none;
|
|
1531
|
-
}
|
|
1532
|
-
|
|
1533
|
-
[contenteditable] {
|
|
1534
|
-
outline: 0px solid transparent;
|
|
1535
|
-
}
|
|
1536
|
-
|
|
1537
|
-
[data-fieldid][data-itemid][data-language][data-version][data-is-richtext="true"] {
|
|
1538
|
-
cursor: text;
|
|
1539
|
-
}
|
|
1540
|
-
`
|
|
1541
|
-
: `
|
|
1542
|
-
[contenteditable] {
|
|
1543
|
-
outline: 0px solid transparent;
|
|
1544
|
-
}
|
|
1545
|
-
.scChromeData, code {
|
|
1546
|
-
display: none;
|
|
1547
|
-
}
|
|
1548
|
-
`;
|
|
1549
|
-
if (iframeDocument && iframeDocument.head) {
|
|
1550
|
-
iframeDocument.head.appendChild(style);
|
|
1551
|
-
}
|
|
1552
|
-
if (!editMode) {
|
|
1553
|
-
disableEditingInIframeDocument(iframeDocument);
|
|
1554
|
-
}
|
|
1555
|
-
}
|
|
1556
|
-
function resolveComponentIdForTarget(rawId, target, page) {
|
|
1557
|
-
const direct = getComponentById(rawId, page);
|
|
1558
|
-
const candidates = getAllComponentInstances(rawId, page);
|
|
1559
|
-
if (candidates.length === 0) {
|
|
1560
|
-
return direct?.id || rawId;
|
|
1561
|
-
}
|
|
1562
|
-
const containing = candidates.filter((component) => isTargetInsideComponent(target, component));
|
|
1563
|
-
if (containing.length === 0) {
|
|
1564
|
-
return direct?.id || candidates[0].id;
|
|
1565
|
-
}
|
|
1566
|
-
// Prefer the deepest/most specific matching component for nested SXA markup.
|
|
1567
|
-
containing.sort((a, b) => getElementDepth(b.firstDOMElement || null) -
|
|
1568
|
-
getElementDepth(a.firstDOMElement || null));
|
|
1569
|
-
return containing[0].id;
|
|
1570
|
-
}
|
|
1571
|
-
function resolveComponentIdForFieldTarget(field, target, page) {
|
|
1572
|
-
const matches = findComponentsRenderingField(field, page);
|
|
1573
|
-
const containingMatches = matches.filter((component) => isTargetInsideComponent(target, component));
|
|
1574
|
-
const rankedMatches = (containingMatches.length > 0 ? containingMatches : matches).sort((a, b) => getElementDepth(b.firstDOMElement || null) -
|
|
1575
|
-
getElementDepth(a.firstDOMElement || null));
|
|
1576
|
-
return rankedMatches[0]?.id;
|
|
1577
|
-
}
|
|
1578
|
-
function findComponentsRenderingField(field, page) {
|
|
1579
|
-
if (!page?.rootComponent)
|
|
1580
|
-
return [];
|
|
1581
|
-
const matches = [];
|
|
1582
|
-
const visit = (component) => {
|
|
1583
|
-
if (componentRendersField(component, field)) {
|
|
1584
|
-
matches.push(component);
|
|
1585
|
-
}
|
|
1586
|
-
for (const placeholder of component.placeholders || []) {
|
|
1587
|
-
for (const child of placeholder.components || []) {
|
|
1588
|
-
visit(child);
|
|
1589
|
-
}
|
|
1590
|
-
}
|
|
1591
|
-
};
|
|
1592
|
-
visit(page.rootComponent);
|
|
1593
|
-
return matches;
|
|
1594
|
-
}
|
|
1595
|
-
function componentRendersField(component, field) {
|
|
1596
|
-
const renderedItems = [
|
|
1597
|
-
component.datasourceItem,
|
|
1598
|
-
...component.items.filter((item) => !component.datasourceItem ||
|
|
1599
|
-
!(item.id === component.datasourceItem.id &&
|
|
1600
|
-
item.language === component.datasourceItem.language &&
|
|
1601
|
-
item.version === component.datasourceItem.version)),
|
|
1602
|
-
].filter(Boolean);
|
|
1603
|
-
return renderedItems.some((item) => item.id === field.item.id &&
|
|
1604
|
-
item.language === field.item.language &&
|
|
1605
|
-
item.version === field.item.version &&
|
|
1606
|
-
item.renderedFieldIds.includes(field.fieldId));
|
|
1607
|
-
}
|
|
1608
|
-
function isTargetInsideComponent(target, component) {
|
|
1609
|
-
const start = component.firstDOMElement;
|
|
1610
|
-
const end = component.lastDOMElement || component.firstDOMElement;
|
|
1611
|
-
if (!start || !end)
|
|
1612
|
-
return false;
|
|
1613
|
-
if (start.contains(target) || end.contains(target))
|
|
1614
|
-
return true;
|
|
1615
|
-
const startPos = start.compareDocumentPosition(target);
|
|
1616
|
-
const endPos = end.compareDocumentPosition(target);
|
|
1617
|
-
const isAfterStart = !!(startPos & Node.DOCUMENT_POSITION_FOLLOWING) ||
|
|
1618
|
-
!!(startPos & Node.DOCUMENT_POSITION_CONTAINED_BY);
|
|
1619
|
-
const isBeforeEnd = !!(endPos & Node.DOCUMENT_POSITION_PRECEDING) ||
|
|
1620
|
-
!!(endPos & Node.DOCUMENT_POSITION_CONTAINED_BY);
|
|
1621
|
-
return isAfterStart && isBeforeEnd;
|
|
1622
|
-
}
|
|
1623
|
-
function getElementDepth(element) {
|
|
1624
|
-
let depth = 0;
|
|
1625
|
-
let current = element;
|
|
1626
|
-
while (current) {
|
|
1627
|
-
depth++;
|
|
1628
|
-
current = current.parentElement;
|
|
1629
|
-
}
|
|
1630
|
-
return depth;
|
|
1631
|
-
}
|
|
1632
|
-
function injectSXAScripts(iframe) {
|
|
1633
|
-
if (!iframe)
|
|
1634
|
-
return;
|
|
1635
|
-
const iframeDocument = getAccessibleIframeDocument(iframe, "PageViewerFrame.tsx:injectSXAScripts-doc");
|
|
1636
|
-
let iframeWindow;
|
|
1637
|
-
try {
|
|
1638
|
-
iframeWindow = iframe.contentWindow;
|
|
1639
|
-
}
|
|
1640
|
-
catch {
|
|
1641
|
-
iframeWindow = null;
|
|
1642
|
-
}
|
|
1643
|
-
if (!iframeDocument || !iframeWindow)
|
|
1644
|
-
return;
|
|
1645
|
-
iframeWindow.Sitecore = {
|
|
1646
|
-
PageModes: {
|
|
1647
|
-
HoverFrame: {
|
|
1648
|
-
extend: () => {
|
|
1649
|
-
// console.log("extend hoverframe");
|
|
1650
|
-
},
|
|
1651
|
-
},
|
|
1652
|
-
ChromeManager: {
|
|
1653
|
-
chromes: () => {
|
|
1654
|
-
// console.log("chromes");
|
|
1655
|
-
return [];
|
|
1656
|
-
},
|
|
1657
|
-
resetChromes: () => {
|
|
1658
|
-
// console.log("resetChromes");
|
|
1659
|
-
iframeWindow.XA.init();
|
|
1660
|
-
},
|
|
1661
|
-
chromesReseted: {
|
|
1662
|
-
observe: () => {
|
|
1663
|
-
// console.log("chromesReseted.observe");
|
|
1664
|
-
},
|
|
1665
|
-
},
|
|
1666
|
-
// Dummy selectionChanged with an _callbacks array
|
|
1667
|
-
selectionChanged: {
|
|
1668
|
-
_callbacks: [],
|
|
1669
|
-
// Optionally, you can add a method to trigger all callbacks if needed.
|
|
1670
|
-
trigger: function (...args) {
|
|
1671
|
-
this._callbacks.forEach((callback) => callback(...args));
|
|
1672
|
-
},
|
|
1673
|
-
},
|
|
1674
|
-
},
|
|
1675
|
-
ChromeControls: function () { },
|
|
1676
|
-
ChromeTypes: {
|
|
1677
|
-
PlaceholderSorting: function () { },
|
|
1678
|
-
Rendering: function () { },
|
|
1679
|
-
},
|
|
1680
|
-
},
|
|
1681
|
-
};
|
|
1682
|
-
// Option 1: Use main window's jQuery if available.
|
|
1683
|
-
if (iframeWindow.jQuery) {
|
|
1684
|
-
// Be cautious: if the iframe's document is different, it's better to load jQuery within the iframe.
|
|
1685
|
-
iframeWindow.$sc = iframeWindow.jQuery;
|
|
1686
|
-
}
|
|
1687
|
-
else {
|
|
1688
|
-
// Option 2: Dynamically load jQuery in the iframe.
|
|
1689
|
-
const jqueryScript = iframeDocument.createElement("script");
|
|
1690
|
-
jqueryScript.type = "text/javascript";
|
|
1691
|
-
jqueryScript.src = "https://code.jquery.com/jquery-3.6.0.min.js"; // Use the version you prefer.
|
|
1692
|
-
jqueryScript.integrity =
|
|
1693
|
-
"sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=";
|
|
1694
|
-
jqueryScript.crossOrigin = "anonymous";
|
|
1695
|
-
jqueryScript.onload = () => {
|
|
1696
|
-
// Once loaded, assign it to $sc using noConflict to avoid global collisions.
|
|
1697
|
-
iframeWindow.$sc = iframeWindow.jQuery.noConflict();
|
|
1698
|
-
// console.log("jQuery loaded in iframe and assigned to $sc");
|
|
1699
|
-
};
|
|
1700
|
-
(iframeDocument.head || iframeDocument.body).appendChild(jqueryScript);
|
|
1701
|
-
}
|
|
1702
|
-
// Add a dummy implementation for renderExpandCommand on the ChromeControls prototype.
|
|
1703
|
-
iframeWindow.Sitecore.PageModes.ChromeControls.prototype.renderExpandCommand =
|
|
1704
|
-
function () {
|
|
1705
|
-
console.log("renderExpandCommand called");
|
|
1706
|
-
// Return a dummy value if needed. For example, you might return a dummy HTML string.
|
|
1707
|
-
return "<div>Dummy Expand Command</div>";
|
|
1708
|
-
};
|
|
1709
|
-
iframeWindow.Sitecore.PageModes.ChromeTypes.PlaceholderSorting.prototype.insertSortingHandle =
|
|
1710
|
-
function () {
|
|
1711
|
-
console.log("insertSortingHandle called");
|
|
1712
|
-
};
|
|
1713
|
-
}
|
|
1714
|
-
/**
|
|
1715
|
-
* Calculates the global offset of a given target node and its local offset,
|
|
1716
|
-
* relative to the container's complete text content.
|
|
1717
|
-
*
|
|
1718
|
-
* @param container - The container element that holds the text nodes.
|
|
1719
|
-
* @param targetNode - The text node where the selection starts or ends.
|
|
1720
|
-
* @param localOffset - The offset within the target text node.
|
|
1721
|
-
* @returns The computed global offset.
|
|
1722
|
-
*/
|
|
1723
|
-
function getGlobalTextOffset(container, targetNode, localOffset) {
|
|
1724
|
-
let globalOffset = 0;
|
|
1725
|
-
const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, null);
|
|
1726
|
-
while (walker.nextNode()) {
|
|
1727
|
-
const currentNode = walker.currentNode;
|
|
1728
|
-
// If we've reached the target node, add the local offset and return.
|
|
1729
|
-
if (currentNode === targetNode) {
|
|
1730
|
-
return globalOffset + localOffset;
|
|
1731
|
-
}
|
|
1732
|
-
// Otherwise, add the length of this text node.
|
|
1733
|
-
globalOffset += (currentNode.textContent || "").length;
|
|
1734
|
-
}
|
|
1735
|
-
return globalOffset;
|
|
1908
|
+
editContext.parheliaSettings?.showMinimap !== false && (_jsx(MiniMap, { compareView: compareView, scroll: scroll, mainViewIframeRef: iframeRef, pageViewContext: pageViewContext })), showSpinner && (_jsxs(_Fragment, { children: [_jsx("div", { className: "bg-neutral-grey-5/50 absolute top-0 left-0 h-full w-full" }), _jsx("div", { className: "absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 transform", children: _jsx(Spinner, {}) })] }))] }), _jsx(FieldActionsOverlay, { ref: fieldActionsOverlay, generatorButtons: contextMenuFieldButtons, onActionClick: handleActionClick, onParameterizedActionExecute: handleParameterizedActionExecute, currentOverlay: editContext?.currentOverlay, fieldId: contextMenuField?.fieldId || "", setCurrentOverlay: editContext?.setCurrentOverlay || (() => { }), preSelectedAction: preSelectedAction })] }));
|
|
1736
1909
|
}
|
|
1737
1910
|
//# sourceMappingURL=PageViewerFrame.js.map
|