@parhelia/core 0.1.12880 → 0.1.12881
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/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/context-menu.js +2 -2
- package/dist/config/config.js +6 -5
- package/dist/config/config.js.map +1 -1
- package/dist/config/types.d.ts +0 -7
- package/dist/config/types.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/FieldListField.d.ts +1 -1
- package/dist/editor/FieldListField.js +20 -18
- 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/PictureEditor.d.ts +2 -1
- package/dist/editor/PictureEditor.js +5 -14
- package/dist/editor/PictureEditor.js.map +1 -1
- package/dist/editor/ai/Agents.js +2 -2
- 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.js +22 -11
- package/dist/editor/ai/InlineAiDialog.js.map +1 -1
- package/dist/editor/ai/InlineAiTrigger.js +17 -57
- 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/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 +16 -14
- 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/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/useInlineAiPosition.d.ts +1 -1
- package/dist/editor/ai/useInlineAiPosition.js +22 -52
- 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 +282 -126
- package/dist/editor/client/EditorShell.js.map +1 -1
- package/dist/editor/client/editContext.d.ts +33 -5
- package/dist/editor/client/editContext.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 +15 -8
- package/dist/editor/client/itemsRepository.js.map +1 -1
- package/dist/editor/client/operations.d.ts +1 -1
- package/dist/editor/client/operations.js +41 -17
- 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/field-types/MultiLineText.js +10 -3
- package/dist/editor/field-types/MultiLineText.js.map +1 -1
- package/dist/editor/field-types/RawEditor.js +8 -1
- package/dist/editor/field-types/RawEditor.js.map +1 -1
- package/dist/editor/field-types/RichTextEditorComponent.js +156 -45
- 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/richtext/components/ReactSlate.js +8 -2
- package/dist/editor/field-types/richtext/components/ReactSlate.js.map +1 -1
- package/dist/editor/field-types/richtext/contextMenuFactory.d.ts +1 -2
- package/dist/editor/field-types/richtext/contextMenuFactory.js +100 -303
- package/dist/editor/field-types/richtext/contextMenuFactory.js.map +1 -1
- package/dist/editor/field-types/richtext/types.d.ts +2 -0
- package/dist/editor/field-types/richtext/types.js.map +1 -1
- package/dist/editor/field-types/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/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.d.ts +0 -10
- package/dist/editor/menubar/toolbar-sections/ManualBrowser.js +63 -462
- package/dist/editor/menubar/toolbar-sections/ManualBrowser.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/ViewportControls.js +1 -1
- package/dist/editor/page-editor-chrome/BridgeInlineFormatOverlay.d.ts +6 -0
- package/dist/editor/page-editor-chrome/BridgeInlineFormatOverlay.js +123 -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/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 +222 -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 +40 -13
- 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 +1529 -1511
- 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 +9 -4
- package/dist/editor/reviews/Comment.js.map +1 -1
- package/dist/editor/reviews/CommentEditor.js +1 -1
- package/dist/editor/reviews/CommentEditor.js.map +1 -1
- package/dist/editor/reviews/CommentPopover.js +68 -9
- 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 +29 -30
- package/dist/editor/reviews/Comments.js.map +1 -1
- package/dist/editor/reviews/FeedbackCard.d.ts +4 -2
- package/dist/editor/reviews/FeedbackCard.js +5 -5
- 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/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/Workbox.js +1 -1
- package/dist/editor/sidebar/Workbox.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 +1 -1
- package/dist/editor/ui/SimpleIconButton.js.map +1 -1
- 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/DialogWrappers.js +2 -2
- package/dist/splash-screen/DialogWrappers.js.map +1 -1
- 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/package.json +2 -1
- package/styles.css +49 -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,18 @@
|
|
|
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 { IFRAME_OVERLAY_BRIDGE_GEOMETRY_EVENT, IFRAME_OVERLAY_BRIDGE_SCROLL_EVENT, IframeOverlayProvider, } from "../page-editor-chrome/overlay/IframeOverlayProvider";
|
|
10
|
+
import { DEVICE_CHANGE_EVENT, useViewportChangeSignal, } from "./pageViewContext";
|
|
11
11
|
import uuid from "react-uuid";
|
|
12
12
|
import { cn } from "../../lib/utils";
|
|
13
|
-
import {
|
|
14
|
-
import { extractDOMSelectionContext } from "../utils/selectionContext";
|
|
13
|
+
import { normalizeMarkerId } from "../utils";
|
|
15
14
|
import { cleanId } from "../utils/id-helper";
|
|
16
|
-
import {
|
|
15
|
+
import { getComponentById } from "../componentTreeHelper";
|
|
17
16
|
import { buildComponentContextMenuItems } from "../ContextMenu";
|
|
18
17
|
import { loadFieldButtons } from "../services/editService";
|
|
19
18
|
import { usePathname } from "../client/navigation";
|
|
@@ -22,125 +21,263 @@ import { FieldActionsOverlay, } from "../FieldActionsOverlay";
|
|
|
22
21
|
import { NoLayout } from "../page-editor-chrome/NoLayout";
|
|
23
22
|
import { Spinner } from "../ui/Spinner";
|
|
24
23
|
import { DeviceToolbar } from "./DeviceToolbar";
|
|
25
|
-
import { buildPageModelSkeleton } from "./pageModelSkeletonBuilder";
|
|
26
24
|
import { toSitecoreDate } from "../utils/sitecoreDate";
|
|
27
|
-
|
|
25
|
+
import { BridgeClient } from "../bridge/BridgeClient";
|
|
26
|
+
import { getSuggestionDisplayValue } from "../reviews/suggestionDisplayValue";
|
|
27
|
+
import { buildBridgeFieldPatchPayload, getBridgeFieldPatchValue, } from "./bridgeFieldPatch";
|
|
28
28
|
const ZOOM_MIN = 0.25;
|
|
29
29
|
const ZOOM_MAX = 2;
|
|
30
30
|
const ZOOM_STEP = 0.25;
|
|
31
31
|
const ZOOM_TRANSITION_MS = 300;
|
|
32
|
-
const
|
|
33
|
-
function
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
32
|
+
const BRIDGE_INLINE_EDIT_RELEASE_EVENT = "parhelia:bridge-inline-edit-release";
|
|
33
|
+
function dispatchBridgeOverlayScroll(iframe, scroll, scrollScale = 1) {
|
|
34
|
+
iframe?.dispatchEvent(new CustomEvent(IFRAME_OVERLAY_BRIDGE_SCROLL_EVENT, {
|
|
35
|
+
detail: {
|
|
36
|
+
scrollLeft: scroll.x * scrollScale,
|
|
37
|
+
scrollTop: scroll.y * scrollScale,
|
|
38
|
+
},
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
function getBridgeGeometryScrollScale(geometry) {
|
|
42
|
+
return typeof geometry?.scrollScale === "number" &&
|
|
43
|
+
Number.isFinite(geometry.scrollScale) &&
|
|
44
|
+
geometry.scrollScale > 0
|
|
45
|
+
? geometry.scrollScale
|
|
46
|
+
: 1;
|
|
47
|
+
}
|
|
48
|
+
function dispatchBridgeOverlayGeometry(iframe, geometry) {
|
|
49
|
+
const documentSize = getBridgeGeometryDocumentSize(geometry);
|
|
50
|
+
const scrollScale = getBridgeGeometryScrollScale(geometry);
|
|
51
|
+
const detail = {
|
|
52
|
+
scrollLeft: (geometry?.scroll.x ?? 0) * scrollScale,
|
|
53
|
+
scrollTop: (geometry?.scroll.y ?? 0) * scrollScale,
|
|
54
|
+
viewportWidth: geometry?.viewport.width,
|
|
55
|
+
viewportHeight: geometry?.viewport.height,
|
|
56
|
+
scrollWidth: documentSize?.width,
|
|
57
|
+
scrollHeight: documentSize?.height,
|
|
58
|
+
};
|
|
59
|
+
iframe?.dispatchEvent(new CustomEvent(IFRAME_OVERLAY_BRIDGE_GEOMETRY_EVENT, { detail }));
|
|
60
|
+
}
|
|
61
|
+
function getBridgeGeometryDocumentSize(geometry) {
|
|
62
|
+
if (!geometry)
|
|
63
|
+
return undefined;
|
|
64
|
+
const rectScale = geometry.rectScale ?? 1;
|
|
65
|
+
const scrollScale = getBridgeGeometryScrollScale(geometry);
|
|
66
|
+
return {
|
|
67
|
+
width: Math.max(geometry.viewport.width, ...geometry.targets.map((target) => target.rect.right * rectScale + geometry.scroll.x * scrollScale)),
|
|
68
|
+
height: Math.max(geometry.viewport.height, ...geometry.targets.map((target) => target.rect.bottom * rectScale + geometry.scroll.y * scrollScale)),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function bridgeKeysMatch(left, right) {
|
|
72
|
+
const normalizedLeft = normalizeMarkerId(left);
|
|
73
|
+
const normalizedRight = normalizeMarkerId(right);
|
|
74
|
+
return (!!normalizedLeft && !!normalizedRight && normalizedLeft === normalizedRight);
|
|
75
|
+
}
|
|
76
|
+
function bridgeItemMatches(item, descriptor) {
|
|
77
|
+
if (!item)
|
|
78
|
+
return false;
|
|
79
|
+
if (!bridgeKeysMatch(item.id, descriptor.id))
|
|
80
|
+
return false;
|
|
81
|
+
if (item.language &&
|
|
82
|
+
descriptor.language &&
|
|
83
|
+
item.language !== descriptor.language) {
|
|
84
|
+
return false;
|
|
38
85
|
}
|
|
39
|
-
|
|
40
|
-
|
|
86
|
+
if (item.version !== undefined &&
|
|
87
|
+
descriptor.version !== undefined &&
|
|
88
|
+
item.version !== descriptor.version) {
|
|
89
|
+
return false;
|
|
41
90
|
}
|
|
91
|
+
return true;
|
|
42
92
|
}
|
|
43
|
-
function
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
93
|
+
function bridgeTargetToDocumentBounds(geometry, target) {
|
|
94
|
+
const rectScale = geometry.rectScale ?? 1;
|
|
95
|
+
const scrollScale = getBridgeGeometryScrollScale(geometry);
|
|
96
|
+
return {
|
|
97
|
+
x: target.rect.left * rectScale + geometry.scroll.x * scrollScale,
|
|
98
|
+
y: target.rect.top * rectScale + geometry.scroll.y * scrollScale,
|
|
99
|
+
width: target.rect.width * rectScale,
|
|
100
|
+
height: target.rect.height * rectScale,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function findBridgeFieldDocumentBounds(geometry, descriptor) {
|
|
104
|
+
if (!geometry)
|
|
105
|
+
return undefined;
|
|
106
|
+
const target = geometry.targets.find((candidate) => {
|
|
107
|
+
if (candidate.kind !== "field")
|
|
108
|
+
return false;
|
|
109
|
+
return (bridgeKeysMatch(candidate.fieldId, descriptor.fieldId) &&
|
|
110
|
+
bridgeItemMatches(candidate.item, descriptor.item));
|
|
111
|
+
});
|
|
112
|
+
return target?.rect
|
|
113
|
+
? bridgeTargetToDocumentBounds(geometry, target)
|
|
114
|
+
: undefined;
|
|
115
|
+
}
|
|
116
|
+
function findBridgeComponentDocumentBounds(geometry, componentId) {
|
|
117
|
+
if (!geometry)
|
|
48
118
|
return undefined;
|
|
119
|
+
const target = geometry.targets.find((candidate) => {
|
|
120
|
+
if (candidate.kind !== "component")
|
|
121
|
+
return false;
|
|
122
|
+
return (bridgeKeysMatch(candidate.componentId, componentId) ||
|
|
123
|
+
bridgeKeysMatch(candidate.key, componentId));
|
|
124
|
+
});
|
|
125
|
+
return target?.rect
|
|
126
|
+
? bridgeTargetToDocumentBounds(geometry, target)
|
|
127
|
+
: undefined;
|
|
128
|
+
}
|
|
129
|
+
function scrollBridgeBoundsIntoView(pageViewContext, bounds, currentScroll) {
|
|
130
|
+
const geometry = pageViewContext.bridgeGeometry;
|
|
131
|
+
if (!geometry)
|
|
132
|
+
return false;
|
|
133
|
+
const scrollScale = getBridgeGeometryScrollScale(geometry);
|
|
134
|
+
const currentScrollY = (currentScroll?.y ?? geometry.scroll.y) * scrollScale;
|
|
135
|
+
const viewportHeight = geometry.viewport.height;
|
|
136
|
+
const isInViewport = bounds.y + bounds.height > currentScrollY &&
|
|
137
|
+
bounds.y < currentScrollY + viewportHeight;
|
|
138
|
+
if (isInViewport)
|
|
139
|
+
return false;
|
|
140
|
+
const targetScrollY = bounds.y - viewportHeight / 2 + Math.max(bounds.height, 1) / 2;
|
|
141
|
+
return (pageViewContext.requestBridgeScrollBy?.({
|
|
142
|
+
y: targetScrollY - currentScrollY,
|
|
143
|
+
behavior: "smooth",
|
|
144
|
+
}) ?? false);
|
|
145
|
+
}
|
|
146
|
+
function applyIframeZoom(_iframe, _zoom, _location) {
|
|
147
|
+
// Zoom is applied inside the page host through the bridge setZoom command.
|
|
148
|
+
}
|
|
149
|
+
function clampEditorZoom(value) {
|
|
150
|
+
return Math.min(ZOOM_MAX, Math.max(ZOOM_MIN, value));
|
|
151
|
+
}
|
|
152
|
+
function isLoopbackHost(hostname) {
|
|
153
|
+
return hostname === "localhost" || hostname === "127.0.0.1";
|
|
154
|
+
}
|
|
155
|
+
function alignLoopbackHostToEditor(url) {
|
|
156
|
+
if (isLoopbackHost(window.location.hostname) &&
|
|
157
|
+
isLoopbackHost(url.hostname)) {
|
|
158
|
+
// Sitecore auth cookies are host-scoped, so localhost and 127.0.0.1 are
|
|
159
|
+
// different origins even when they point at the same machine.
|
|
160
|
+
url.hostname = window.location.hostname;
|
|
49
161
|
}
|
|
50
162
|
}
|
|
51
|
-
function
|
|
163
|
+
function getUrlOrigin(url) {
|
|
164
|
+
if (!url)
|
|
165
|
+
return undefined;
|
|
52
166
|
try {
|
|
53
|
-
|
|
167
|
+
const parsedUrl = new URL(url, window.location.href);
|
|
168
|
+
alignLoopbackHostToEditor(parsedUrl);
|
|
169
|
+
return parsedUrl.origin;
|
|
54
170
|
}
|
|
55
171
|
catch {
|
|
56
172
|
return undefined;
|
|
57
173
|
}
|
|
58
174
|
}
|
|
59
|
-
function
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
if (animationFrame !== undefined) {
|
|
73
|
-
ownerWindow.cancelAnimationFrame(animationFrame);
|
|
74
|
-
zoomAnimationFrames.delete(documentElement);
|
|
75
|
-
}
|
|
76
|
-
documentElement.style.removeProperty("transition");
|
|
77
|
-
const getCurrentZoom = () => {
|
|
78
|
-
const inlineZoom = Number.parseFloat(documentElement.style.zoom);
|
|
79
|
-
if (Number.isFinite(inlineZoom) && inlineZoom > 0)
|
|
80
|
-
return inlineZoom;
|
|
81
|
-
const computedZoom = Number.parseFloat(ownerWindow.getComputedStyle(documentElement).zoom);
|
|
82
|
-
if (Number.isFinite(computedZoom) && computedZoom > 0) {
|
|
83
|
-
return computedZoom;
|
|
175
|
+
function resolveBridgeComponentId(rawId, page) {
|
|
176
|
+
if (!rawId)
|
|
177
|
+
return undefined;
|
|
178
|
+
if (!page?.rootComponent)
|
|
179
|
+
return rawId;
|
|
180
|
+
const normalizedId = cleanId(rawId);
|
|
181
|
+
if (!normalizedId)
|
|
182
|
+
return undefined;
|
|
183
|
+
let exactMatch;
|
|
184
|
+
let datasourceMatch;
|
|
185
|
+
const visit = (component) => {
|
|
186
|
+
if (!exactMatch && cleanId(component.id) === normalizedId) {
|
|
187
|
+
exactMatch = component;
|
|
84
188
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
documentElement.style.removeProperty("will-change");
|
|
94
|
-
return;
|
|
189
|
+
if (!datasourceMatch &&
|
|
190
|
+
cleanId(component.datasourceItem?.id) === normalizedId) {
|
|
191
|
+
datasourceMatch = component;
|
|
192
|
+
}
|
|
193
|
+
for (const placeholder of component.placeholders || []) {
|
|
194
|
+
for (const child of placeholder.components || []) {
|
|
195
|
+
visit(child);
|
|
196
|
+
}
|
|
95
197
|
}
|
|
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
198
|
};
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
199
|
+
visit(page.rootComponent);
|
|
200
|
+
return exactMatch?.id ?? datasourceMatch?.id ?? rawId;
|
|
201
|
+
}
|
|
202
|
+
function getOrderedBridgeComponentIds(geometry, page) {
|
|
203
|
+
const orderedIds = [];
|
|
204
|
+
for (const target of geometry?.targets ?? []) {
|
|
205
|
+
if (target.kind !== "component")
|
|
206
|
+
continue;
|
|
207
|
+
const componentId = resolveBridgeComponentId(target.componentId, page);
|
|
208
|
+
if (!componentId)
|
|
209
|
+
continue;
|
|
210
|
+
if (orderedIds.some((id) => bridgeIdsMatch(id, componentId)))
|
|
211
|
+
continue;
|
|
212
|
+
orderedIds.push(componentId);
|
|
105
213
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
214
|
+
return orderedIds;
|
|
215
|
+
}
|
|
216
|
+
function bridgeIdsMatch(left, right) {
|
|
217
|
+
const normalizedLeft = normalizeMarkerId(left);
|
|
218
|
+
const normalizedRight = normalizeMarkerId(right);
|
|
219
|
+
return (!!normalizedLeft && !!normalizedRight && normalizedLeft === normalizedRight);
|
|
220
|
+
}
|
|
221
|
+
function bridgeComponentSelectionsMatch(left, right) {
|
|
222
|
+
if ((left?.length ?? 0) !== (right?.length ?? 0))
|
|
223
|
+
return false;
|
|
224
|
+
return (left ?? []).every((leftId, index) => bridgeIdsMatch(leftId, right?.[index]));
|
|
225
|
+
}
|
|
226
|
+
function bridgeDescriptorMatchesItem(bridgeItem, item) {
|
|
227
|
+
if (!bridgeItem?.id)
|
|
228
|
+
return true;
|
|
229
|
+
if (!bridgeIdsMatch(bridgeItem.id, item.id))
|
|
230
|
+
return false;
|
|
231
|
+
if (bridgeItem.language &&
|
|
232
|
+
item.language &&
|
|
233
|
+
bridgeItem.language.toLowerCase() !== item.language.toLowerCase()) {
|
|
234
|
+
return false;
|
|
111
235
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
236
|
+
if (typeof bridgeItem.version === "number" &&
|
|
237
|
+
Number.isFinite(bridgeItem.version) &&
|
|
238
|
+
typeof item.version === "number" &&
|
|
239
|
+
Number.isFinite(item.version) &&
|
|
240
|
+
bridgeItem.version !== item.version) {
|
|
241
|
+
return false;
|
|
116
242
|
}
|
|
117
|
-
|
|
118
|
-
documentElement.style.removeProperty("transform-origin");
|
|
119
|
-
const startedAt = ownerWindow.performance.now();
|
|
120
|
-
const easeInOut = (value) => value < 0.5
|
|
121
|
-
? 4 * value * value * value
|
|
122
|
-
: 1 - Math.pow(-2 * value + 2, 3) / 2;
|
|
123
|
-
const step = (now) => {
|
|
124
|
-
const progress = Math.min(1, (now - startedAt) / ZOOM_TRANSITION_MS);
|
|
125
|
-
const easedProgress = easeInOut(progress);
|
|
126
|
-
const currentZoom = startZoom + (targetZoom - startZoom) * easedProgress;
|
|
127
|
-
documentElement.style.zoom = String(currentZoom);
|
|
128
|
-
documentElement.style.removeProperty("transform");
|
|
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));
|
|
243
|
+
return true;
|
|
141
244
|
}
|
|
142
|
-
function
|
|
143
|
-
return
|
|
245
|
+
function fieldIdentifierMatches(field, fieldId) {
|
|
246
|
+
return (bridgeIdsMatch(field.id, fieldId) ||
|
|
247
|
+
bridgeIdsMatch(field.name, fieldId) ||
|
|
248
|
+
bridgeIdsMatch(field.displayName, fieldId));
|
|
249
|
+
}
|
|
250
|
+
function bridgeFieldMatchesChangedField(bridgeFieldId, changedFieldId, field) {
|
|
251
|
+
return (bridgeIdsMatch(bridgeFieldId, changedFieldId) ||
|
|
252
|
+
bridgeIdsMatch(bridgeFieldId, field.id) ||
|
|
253
|
+
bridgeIdsMatch(bridgeFieldId, field.name) ||
|
|
254
|
+
bridgeIdsMatch(bridgeFieldId, field.displayName));
|
|
255
|
+
}
|
|
256
|
+
function findBridgeFieldTargetAtPoint(geometry, clientX, clientY) {
|
|
257
|
+
if (!geometry || typeof clientX !== "number" || typeof clientY !== "number") {
|
|
258
|
+
return undefined;
|
|
259
|
+
}
|
|
260
|
+
return geometry.targets
|
|
261
|
+
.filter((target) => {
|
|
262
|
+
if (target.kind !== "field")
|
|
263
|
+
return false;
|
|
264
|
+
const rect = target.rect;
|
|
265
|
+
return (clientX >= rect.left &&
|
|
266
|
+
clientX <= rect.right &&
|
|
267
|
+
clientY >= rect.top &&
|
|
268
|
+
clientY <= rect.bottom);
|
|
269
|
+
})
|
|
270
|
+
.sort((left, right) => left.rect.width * left.rect.height -
|
|
271
|
+
right.rect.width * right.rect.height)[0];
|
|
272
|
+
}
|
|
273
|
+
function isBridgeInlineAiShortcut(interaction) {
|
|
274
|
+
const keyboardInteraction = interaction;
|
|
275
|
+
return !!((keyboardInteraction.ctrlKey || keyboardInteraction.metaKey) &&
|
|
276
|
+
(keyboardInteraction.key === "." ||
|
|
277
|
+
keyboardInteraction.key === "Period" ||
|
|
278
|
+
keyboardInteraction.key === "Decimal" ||
|
|
279
|
+
keyboardInteraction.code === "Period" ||
|
|
280
|
+
keyboardInteraction.code === "NumpadDecimal"));
|
|
144
281
|
}
|
|
145
282
|
export function PageViewerFrame(props) {
|
|
146
283
|
const editContext = useEditContext();
|
|
@@ -160,17 +297,38 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
160
297
|
const editContextRef = useEditContextRef();
|
|
161
298
|
const fieldsContextRef = useFieldsEditContextRef();
|
|
162
299
|
const iframeRef = useRef(null);
|
|
163
|
-
const
|
|
164
|
-
const
|
|
165
|
-
const
|
|
300
|
+
const [iframeElement, setIframeElement] = useState(null);
|
|
301
|
+
const bridgeClientRef = useRef(null);
|
|
302
|
+
const bridgePatchSignatureRef = useRef(new Map());
|
|
303
|
+
const bridgeTextRangeSourcesRef = useRef(new Map());
|
|
304
|
+
const activeBridgeInlineEditRef = useRef(null);
|
|
305
|
+
const latestBridgeScrollRef = useRef(undefined);
|
|
306
|
+
const suppressNextSelectionScrollRef = useRef(false);
|
|
307
|
+
// Tracks the selection we last evaluated for auto-scroll. The selection
|
|
308
|
+
// effect also depends on focusedField, so it re-runs when a field blurs
|
|
309
|
+
// (focusedField -> undefined) even though the selection itself did not
|
|
310
|
+
// change. Without this guard that blur re-render would scroll the just
|
|
311
|
+
// clicked component into view after the suppress flag was already consumed.
|
|
312
|
+
const lastScrolledSelectionKeyRef = useRef("");
|
|
166
313
|
const [showSpinner, setShowSpinner] = useState(false);
|
|
314
|
+
const [iframeSrc, setIframeSrc] = useState();
|
|
315
|
+
const [loadedIframeSrc, setLoadedIframeSrc] = useState();
|
|
167
316
|
const [scroll, setScroll] = useState(0);
|
|
168
317
|
const [showMiniMap, setShowMiniMap] = useState(false);
|
|
318
|
+
const [bridgeGeometryRevision, setBridgeGeometryRevision] = useState(0);
|
|
319
|
+
const [bridgeDomRevision, setBridgeDomRevision] = useState(0);
|
|
320
|
+
const bridgeGeometryRevisionRafRef = useRef(null);
|
|
321
|
+
const bridgeDomRevisionRafRef = useRef(null);
|
|
169
322
|
const fieldActionsOverlay = useRef(null);
|
|
323
|
+
const lastSentBridgeCaretRef = useRef(null);
|
|
170
324
|
const [contextMenuFieldButtons, setContextMenuFieldButtons] = useState([]);
|
|
171
325
|
const [contextMenuField, setContextMenuField] = useState();
|
|
172
326
|
const [contextMenuPosition, setContextMenuPosition] = useState();
|
|
173
327
|
const [preSelectedAction, setPreSelectedAction] = useState();
|
|
328
|
+
const bindIframeRef = useCallback((node) => {
|
|
329
|
+
iframeRef.current = node;
|
|
330
|
+
setIframeElement(node);
|
|
331
|
+
}, []);
|
|
174
332
|
// Clear preSelectedAction when overlay is closed
|
|
175
333
|
useEffect(() => {
|
|
176
334
|
if (editContext?.currentOverlay !==
|
|
@@ -179,7 +337,6 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
179
337
|
}
|
|
180
338
|
}, [editContext?.currentOverlay, contextMenuField?.fieldId]);
|
|
181
339
|
const zoom = pageViewContext.zoom;
|
|
182
|
-
const blockBlurEventRef = useRef(0);
|
|
183
340
|
const [currentItemDescriptor, setCurrentItemDescriptor] = useState(undefined);
|
|
184
341
|
// Field action handlers for the overlay
|
|
185
342
|
const handleActionClick = async (action, event) => {
|
|
@@ -195,1269 +352,1368 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
195
352
|
// Note: handleParameterizedActionFromContextMenu is now created inline in handleContextMenu
|
|
196
353
|
// to avoid React state timing issues with contextMenuPosition
|
|
197
354
|
const pageItemDescriptor = pageViewContext.pageItemDescriptor;
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
355
|
+
const shouldTrackMinimapScroll = useCallback(() => {
|
|
356
|
+
const editor = editContextRef.current;
|
|
357
|
+
return !!(showMiniMap &&
|
|
358
|
+
editor?.showMinimap &&
|
|
359
|
+
!editor?.isMobile &&
|
|
360
|
+
editor?.parheliaSettings?.showMinimap !== false);
|
|
361
|
+
}, [showMiniMap]);
|
|
362
|
+
const updateScrollPosition = useCallback((e) => {
|
|
363
|
+
if (!shouldTrackMinimapScroll())
|
|
204
364
|
return;
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
365
|
+
setScroll(e);
|
|
366
|
+
if (!compareView)
|
|
367
|
+
pageViewContextRef.current?.setScroll(e);
|
|
368
|
+
}, [compareView, shouldTrackMinimapScroll]);
|
|
369
|
+
const scrollHandler = useThrottledCallback(updateScrollPosition, 100);
|
|
370
|
+
const scrollHandlerRef = useRef(scrollHandler);
|
|
371
|
+
useEffect(() => {
|
|
372
|
+
scrollHandlerRef.current = scrollHandler;
|
|
373
|
+
}, [scrollHandler]);
|
|
374
|
+
const scheduleBridgeGeometryRevision = useCallback(() => {
|
|
375
|
+
if (bridgeGeometryRevisionRafRef.current != null)
|
|
208
376
|
return;
|
|
209
|
-
|
|
210
|
-
|
|
377
|
+
bridgeGeometryRevisionRafRef.current = window.requestAnimationFrame(() => {
|
|
378
|
+
bridgeGeometryRevisionRafRef.current = null;
|
|
379
|
+
setBridgeGeometryRevision((revision) => revision + 1);
|
|
380
|
+
});
|
|
381
|
+
}, []);
|
|
382
|
+
const scheduleBridgeDomRevision = useCallback(() => {
|
|
383
|
+
if (bridgeDomRevisionRafRef.current != null)
|
|
211
384
|
return;
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
setShowMiniMap(false);
|
|
385
|
+
bridgeDomRevisionRafRef.current = window.requestAnimationFrame(() => {
|
|
386
|
+
bridgeDomRevisionRafRef.current = null;
|
|
387
|
+
setBridgeDomRevision((revision) => revision + 1);
|
|
388
|
+
});
|
|
389
|
+
}, []);
|
|
390
|
+
useEffect(() => {
|
|
391
|
+
return () => {
|
|
392
|
+
if (bridgeGeometryRevisionRafRef.current != null) {
|
|
393
|
+
window.cancelAnimationFrame(bridgeGeometryRevisionRafRef.current);
|
|
222
394
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
if (contentHeight > upperThreshold && minimapEnabled) {
|
|
226
|
-
setShowMiniMap(true);
|
|
395
|
+
if (bridgeDomRevisionRafRef.current != null) {
|
|
396
|
+
window.cancelAnimationFrame(bridgeDomRevisionRafRef.current);
|
|
227
397
|
}
|
|
228
|
-
}
|
|
229
|
-
},
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
398
|
+
};
|
|
399
|
+
}, []);
|
|
400
|
+
const blockIframeBlurUntil = useCallback((_timestamp) => {
|
|
401
|
+
// Iframe blur is handled by the bridge inline-edit lifecycle now.
|
|
402
|
+
}, []);
|
|
403
|
+
const sendBeginInlineEdit = useCallback((payload) => {
|
|
404
|
+
return (bridgeClientRef.current?.sendCommand("beginInlineEdit", payload) ??
|
|
405
|
+
false);
|
|
406
|
+
}, []);
|
|
407
|
+
const sendApplyRichTextCommand = useCallback((payload) => {
|
|
408
|
+
return (bridgeClientRef.current?.sendCommand("applyRichTextCommand", payload) ??
|
|
409
|
+
false);
|
|
410
|
+
}, []);
|
|
411
|
+
const { beginInlineEdit: beginBridgeInlineEdit, clearInlineDedupe: clearBridgeInlineDedupe, endFieldFocus: endBridgeFieldFocus, handleFieldValueChanged: handleBridgeFieldValueChanged, handleInlineEditEnded: handleBridgeInlineEditEnded, } = useBridgeInlineEditing({
|
|
412
|
+
pageViewContextRef,
|
|
413
|
+
activeBridgeInlineEditRef,
|
|
414
|
+
blockIframeBlurUntil,
|
|
415
|
+
sendBeginInlineEdit,
|
|
234
416
|
});
|
|
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);
|
|
417
|
+
const beginTrackedBridgeInlineEdit = useCallback(async (interaction) => {
|
|
418
|
+
activeBridgeInlineEditRef.current = null;
|
|
419
|
+
const started = await beginBridgeInlineEdit(interaction);
|
|
420
|
+
if (started && interaction.elementKey && interaction.fieldId) {
|
|
421
|
+
const interactionItem = interaction.item?.id &&
|
|
422
|
+
interaction.item.language &&
|
|
423
|
+
typeof interaction.item.version === "number"
|
|
424
|
+
? {
|
|
425
|
+
id: interaction.item.id,
|
|
426
|
+
language: interaction.item.language,
|
|
427
|
+
version: interaction.item.version,
|
|
428
|
+
name: interaction.item.name,
|
|
429
|
+
displayName: interaction.item.displayName,
|
|
430
|
+
path: interaction.item.path,
|
|
431
|
+
database: interaction.item.database,
|
|
272
432
|
}
|
|
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;
|
|
433
|
+
: undefined;
|
|
434
|
+
activeBridgeInlineEditRef.current = {
|
|
435
|
+
elementKey: interaction.elementKey,
|
|
436
|
+
fieldId: interaction.fieldId,
|
|
437
|
+
item: interactionItem,
|
|
438
|
+
modeAtStart: editContextRef.current?.mode ?? editContext.mode,
|
|
439
|
+
};
|
|
286
440
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
}, 0);
|
|
294
|
-
};
|
|
295
|
-
// Disconnect the CSS guard on unmount.
|
|
441
|
+
return started;
|
|
442
|
+
}, [beginBridgeInlineEdit, editContext.mode, editContextRef]);
|
|
443
|
+
const endTrackedBridgeFieldFocus = useCallback(() => {
|
|
444
|
+
activeBridgeInlineEditRef.current = null;
|
|
445
|
+
endBridgeFieldFocus();
|
|
446
|
+
}, [endBridgeFieldFocus]);
|
|
296
447
|
useEffect(() => {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
448
|
+
const handleBridgeInlineEditRelease = (event) => {
|
|
449
|
+
const detail = event
|
|
450
|
+
.detail;
|
|
451
|
+
const field = detail?.field;
|
|
452
|
+
const activeEdit = activeBridgeInlineEditRef.current;
|
|
453
|
+
if (!field || !activeEdit)
|
|
454
|
+
return;
|
|
455
|
+
if (!bridgeKeysMatch(activeEdit.fieldId, field.fieldId))
|
|
456
|
+
return;
|
|
457
|
+
if (activeEdit.item && !bridgeItemMatches(activeEdit.item, field.item)) {
|
|
458
|
+
return;
|
|
300
459
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
460
|
+
activeBridgeInlineEditRef.current = null;
|
|
461
|
+
bridgePatchSignatureRef.current.delete(`${activeEdit.elementKey}:${activeEdit.fieldId}`);
|
|
462
|
+
};
|
|
463
|
+
window.addEventListener(BRIDGE_INLINE_EDIT_RELEASE_EVENT, handleBridgeInlineEditRelease);
|
|
464
|
+
return () => {
|
|
465
|
+
window.removeEventListener(BRIDGE_INLINE_EDIT_RELEASE_EVENT, handleBridgeInlineEditRelease);
|
|
304
466
|
};
|
|
305
467
|
}, []);
|
|
306
|
-
// If the editor mode flips (edit/preview), re-apply CSS and disable inline editing in the iframe.
|
|
307
468
|
useEffect(() => {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
469
|
+
return editContext.registerModeChangeParticipant({
|
|
470
|
+
beforeModeChange: () => {
|
|
471
|
+
editContext.operations.onFieldBlur?.();
|
|
472
|
+
void fieldsContextRef.current?.setFocusedField(undefined, false);
|
|
473
|
+
endBridgeFieldFocus();
|
|
474
|
+
},
|
|
475
|
+
clearInlineDedupe: () => {
|
|
476
|
+
clearBridgeInlineDedupe();
|
|
477
|
+
},
|
|
478
|
+
});
|
|
479
|
+
}, [
|
|
480
|
+
clearBridgeInlineDedupe,
|
|
481
|
+
editContext,
|
|
482
|
+
endBridgeFieldFocus,
|
|
483
|
+
fieldsContextRef,
|
|
484
|
+
]);
|
|
485
|
+
const getBridgePatchDisplayValue = useCallback(({ field, structureField, itemDescriptor, preferRepositoryValue, }) => {
|
|
486
|
+
const showSuggestions = editContext.mode === "suggestions" || editContext.showSuggestedEdits;
|
|
487
|
+
const repositoryValue = getBridgeFieldPatchValue(field);
|
|
488
|
+
const display = getSuggestionDisplayValue({
|
|
489
|
+
repositoryValue,
|
|
490
|
+
modifiedFields: preferRepositoryValue
|
|
491
|
+
? undefined
|
|
492
|
+
: fieldsContext?.modifiedFields,
|
|
493
|
+
suggestedEdits: editContext.suggestedEdits,
|
|
494
|
+
field: {
|
|
495
|
+
fieldId: field.id || structureField.fieldId,
|
|
496
|
+
itemId: itemDescriptor.id,
|
|
497
|
+
language: itemDescriptor.language,
|
|
498
|
+
version: itemDescriptor.version,
|
|
499
|
+
pageItemId: pageViewContext.pageItemDescriptor?.id,
|
|
500
|
+
pageItemVersion: pageViewContext.pageItemDescriptor?.version,
|
|
501
|
+
},
|
|
502
|
+
mode: showSuggestions ? "suggestions" : "baseline",
|
|
503
|
+
});
|
|
504
|
+
return {
|
|
505
|
+
value: showSuggestions ? display.mergedValue : display.baselineValue,
|
|
506
|
+
source: showSuggestions && display.hasSuggestions
|
|
507
|
+
? "suggestion-preview"
|
|
508
|
+
: "real",
|
|
509
|
+
};
|
|
510
|
+
}, [
|
|
511
|
+
editContext.mode,
|
|
512
|
+
editContext.showSuggestedEdits,
|
|
513
|
+
editContext.suggestedEdits,
|
|
514
|
+
fieldsContext?.modifiedFields,
|
|
515
|
+
pageViewContext.pageItemDescriptor,
|
|
516
|
+
]);
|
|
517
|
+
const buildBridgeFieldPatch = useCallback(({ field, structureField, itemDescriptor, preferRepositoryValue, }) => {
|
|
518
|
+
const display = getBridgePatchDisplayValue({
|
|
519
|
+
field,
|
|
520
|
+
structureField,
|
|
521
|
+
itemDescriptor,
|
|
522
|
+
preferRepositoryValue,
|
|
523
|
+
});
|
|
524
|
+
const activeInlineEdit = activeBridgeInlineEditRef.current;
|
|
525
|
+
const patch = buildBridgeFieldPatchPayload({
|
|
526
|
+
field,
|
|
527
|
+
structureField,
|
|
528
|
+
display,
|
|
529
|
+
activeInlineEdit,
|
|
530
|
+
});
|
|
531
|
+
return patch;
|
|
532
|
+
}, [getBridgePatchDisplayValue]);
|
|
533
|
+
const clearBridgePatchSignatures = useCallback(() => {
|
|
534
|
+
bridgePatchSignatureRef.current.clear();
|
|
535
|
+
}, []);
|
|
536
|
+
const sendBridgeFieldPatch = useCallback((bridge, patch, observedTextContent) => {
|
|
537
|
+
const cacheKey = `${patch.elementKey}:${patch.fieldId}`;
|
|
538
|
+
const signature = JSON.stringify({
|
|
539
|
+
value: patch.value,
|
|
540
|
+
isRichText: !!patch.isRichText,
|
|
541
|
+
source: patch.source ?? "",
|
|
542
|
+
observedTextContent: observedTextContent ?? "",
|
|
543
|
+
});
|
|
544
|
+
if (bridgePatchSignatureRef.current.get(cacheKey) === signature) {
|
|
545
|
+
return false;
|
|
315
546
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
547
|
+
bridgePatchSignatureRef.current.set(cacheKey, signature);
|
|
548
|
+
bridge.sendCommand("applyFieldPatch", patch);
|
|
549
|
+
return true;
|
|
550
|
+
}, []);
|
|
551
|
+
const clearBridgeRemoteCaretPosition = useCallback(() => {
|
|
552
|
+
const editor = editContextRef.current;
|
|
553
|
+
if (!editor || lastSentBridgeCaretRef.current === "clear")
|
|
554
|
+
return;
|
|
555
|
+
editor.sendSocketMessage({
|
|
556
|
+
type: "caret-position",
|
|
557
|
+
payload: { offset: null },
|
|
558
|
+
});
|
|
559
|
+
lastSentBridgeCaretRef.current = "clear";
|
|
560
|
+
}, [editContextRef]);
|
|
561
|
+
const sendBridgeRemoteCaretPosition = useCallback((selection) => {
|
|
562
|
+
const editor = editContextRef.current;
|
|
563
|
+
const activeField = selection.activeField;
|
|
564
|
+
const offset = selection.startOffset ?? selection.endOffset;
|
|
565
|
+
if (!editor ||
|
|
566
|
+
!activeField?.fieldId ||
|
|
567
|
+
!selection.collapsed ||
|
|
568
|
+
offset == null) {
|
|
569
|
+
clearBridgeRemoteCaretPosition();
|
|
321
570
|
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
|
-
}
|
|
347
|
-
// Layout-mode marker. Only set when editing shared layout, paired with the
|
|
348
|
-
// `parhelia` editor marker so ParheliaSetLayoutRenderings ignores any unrelated
|
|
349
|
-
// requests that happen to carry parhelia_layout.
|
|
350
|
-
if (editContext.mode !== "preview" && editContext.layoutMode === "shared") {
|
|
351
|
-
renderUrl.searchParams.set("parhelia", "1");
|
|
352
|
-
renderUrl.searchParams.set("parhelia_layout", "shared");
|
|
353
571
|
}
|
|
354
|
-
|
|
355
|
-
|
|
572
|
+
const sourceItem = activeField.item ??
|
|
573
|
+
pageViewContextRef.current?.page?.item ??
|
|
574
|
+
pageViewContextRef.current?.pageItemDescriptor;
|
|
575
|
+
const version = typeof sourceItem?.version === "number" &&
|
|
576
|
+
Number.isFinite(sourceItem.version)
|
|
577
|
+
? sourceItem.version
|
|
578
|
+
: undefined;
|
|
579
|
+
if (!sourceItem?.id || !sourceItem.language || version === undefined) {
|
|
580
|
+
clearBridgeRemoteCaretPosition();
|
|
581
|
+
return;
|
|
356
582
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
583
|
+
const item = {
|
|
584
|
+
...sourceItem,
|
|
585
|
+
version,
|
|
586
|
+
};
|
|
587
|
+
const nextKey = `${activeField.fieldId}:${item.id}:${item.language}:${item.version}:${offset}`;
|
|
588
|
+
if (lastSentBridgeCaretRef.current === nextKey)
|
|
589
|
+
return;
|
|
590
|
+
editor.sendSocketMessage({
|
|
591
|
+
type: "caret-position",
|
|
592
|
+
payload: {
|
|
593
|
+
fieldId: activeField.fieldId,
|
|
594
|
+
item,
|
|
595
|
+
offset,
|
|
596
|
+
},
|
|
597
|
+
});
|
|
598
|
+
lastSentBridgeCaretRef.current = nextKey;
|
|
599
|
+
}, [clearBridgeRemoteCaretPosition, editContextRef, pageViewContextRef]);
|
|
600
|
+
const handleBridgeSelection = useCallback((selection, iframe) => {
|
|
601
|
+
const editor = editContextRef.current;
|
|
602
|
+
if (!editor)
|
|
603
|
+
return;
|
|
604
|
+
const isInlineAiUiFocused = () => {
|
|
605
|
+
const activeEl = document.activeElement;
|
|
606
|
+
return !!(activeEl?.closest(".agent-inline-dialog") ||
|
|
607
|
+
activeEl?.closest(".agent-inline-trigger") ||
|
|
608
|
+
activeEl?.closest('[role="dialog"]'));
|
|
609
|
+
};
|
|
610
|
+
if (selection.collapsed ||
|
|
611
|
+
!selection.text ||
|
|
612
|
+
!selection.activeField?.fieldId) {
|
|
613
|
+
sendBridgeRemoteCaretPosition(selection);
|
|
614
|
+
if (!isInlineAiUiFocused()) {
|
|
615
|
+
editor.setSelectedRange(undefined);
|
|
616
|
+
}
|
|
617
|
+
return;
|
|
362
618
|
}
|
|
363
|
-
|
|
364
|
-
|
|
619
|
+
clearBridgeRemoteCaretPosition();
|
|
620
|
+
const item = selection.activeField.item ??
|
|
621
|
+
pageViewContextRef.current?.page?.item ??
|
|
622
|
+
pageViewContextRef.current?.pageItemDescriptor;
|
|
623
|
+
if (!item?.id) {
|
|
624
|
+
editor.setSelectedRange(undefined);
|
|
625
|
+
return;
|
|
365
626
|
}
|
|
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;
|
|
627
|
+
const metadata = selection.metadata ?? {};
|
|
628
|
+
const iframeRect = iframe.getBoundingClientRect();
|
|
629
|
+
const selectionRect = selection.rect
|
|
630
|
+
? {
|
|
631
|
+
x: iframeRect.left + selection.rect.left,
|
|
632
|
+
y: iframeRect.top + selection.rect.top,
|
|
633
|
+
width: selection.rect.width,
|
|
634
|
+
height: selection.rect.height,
|
|
635
|
+
top: iframeRect.top + selection.rect.top,
|
|
636
|
+
right: iframeRect.left + selection.rect.right,
|
|
637
|
+
bottom: iframeRect.top + selection.rect.bottom,
|
|
638
|
+
left: iframeRect.left + selection.rect.left,
|
|
382
639
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
640
|
+
: undefined;
|
|
641
|
+
const nextRange = {
|
|
642
|
+
itemId: item.id,
|
|
643
|
+
fieldId: selection.activeField.fieldId,
|
|
644
|
+
elementKey: selection.activeField.elementKey,
|
|
645
|
+
isRichText: selection.activeField.isRichText,
|
|
646
|
+
language: item.language,
|
|
647
|
+
version: item.version,
|
|
648
|
+
startOffset: selection.startOffset ?? 0,
|
|
649
|
+
endOffset: selection.endOffset ?? selection.startOffset ?? 0,
|
|
650
|
+
text: selection.text,
|
|
651
|
+
contextBefore: typeof metadata.contextBefore === "string"
|
|
652
|
+
? metadata.contextBefore
|
|
653
|
+
: undefined,
|
|
654
|
+
contextAfter: typeof metadata.contextAfter === "string"
|
|
655
|
+
? metadata.contextAfter
|
|
656
|
+
: undefined,
|
|
657
|
+
clientRect: selectionRect,
|
|
658
|
+
};
|
|
659
|
+
editor.setSelectedRange(nextRange);
|
|
660
|
+
}, [clearBridgeRemoteCaretPosition, sendBridgeRemoteCaretPosition]);
|
|
661
|
+
const resolveBridgeFieldDescriptor = useCallback(async (interaction) => {
|
|
662
|
+
const pageView = pageViewContextRef.current;
|
|
663
|
+
const selectedRange = editContextRef.current?.selectedRange;
|
|
664
|
+
const selectedRangeElementKey = selectedRange?.elementKey;
|
|
665
|
+
const selectedRangeMatchesInteraction = !!((interaction.kind === "contextMenu" ||
|
|
666
|
+
interaction.kind === "keydown") &&
|
|
667
|
+
selectedRange?.text &&
|
|
668
|
+
selectedRange.fieldId &&
|
|
669
|
+
((selectedRangeElementKey &&
|
|
670
|
+
interaction.elementKey &&
|
|
671
|
+
selectedRangeElementKey === interaction.elementKey) ||
|
|
672
|
+
bridgeIdsMatch(selectedRange.fieldId, interaction.fieldId) ||
|
|
673
|
+
bridgeIdsMatch(selectedRange.itemId, interaction.item?.id)));
|
|
674
|
+
const fieldId = selectedRangeMatchesInteraction
|
|
675
|
+
? selectedRange?.fieldId
|
|
676
|
+
: interaction.fieldId;
|
|
677
|
+
if (!fieldId)
|
|
678
|
+
return undefined;
|
|
679
|
+
const targetElementKey = selectedRangeMatchesInteraction
|
|
680
|
+
? selectedRangeElementKey
|
|
681
|
+
: interaction.elementKey;
|
|
682
|
+
const structureFields = pageView?.bridgeStructure?.fields ?? [];
|
|
683
|
+
const structureField = structureFields.find((field) => bridgeIdsMatch(field.fieldId, fieldId)) ??
|
|
684
|
+
structureFields.find((field) => targetElementKey && field.elementKey === targetElementKey);
|
|
685
|
+
const fallbackItem = pageView?.page?.item ?? pageView?.pageItemDescriptor;
|
|
686
|
+
const componentId = resolveBridgeComponentId(interaction.componentId ?? structureField?.componentId, pageView?.page);
|
|
687
|
+
const component = componentId && pageView?.page
|
|
688
|
+
? getComponentById(componentId, pageView.page)
|
|
689
|
+
: undefined;
|
|
690
|
+
const candidateItems = [];
|
|
691
|
+
const selectedRangeItem = selectedRangeMatchesInteraction &&
|
|
692
|
+
selectedRange?.itemId &&
|
|
693
|
+
selectedRange.language &&
|
|
694
|
+
typeof selectedRange.version === "number"
|
|
695
|
+
? {
|
|
696
|
+
id: selectedRange.itemId,
|
|
697
|
+
language: selectedRange.language,
|
|
698
|
+
version: selectedRange.version,
|
|
699
|
+
database: fallbackItem?.database,
|
|
386
700
|
}
|
|
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);
|
|
701
|
+
: undefined;
|
|
702
|
+
const addCandidateItem = (source) => {
|
|
703
|
+
const itemId = source?.id ?? fallbackItem?.id;
|
|
704
|
+
const language = source?.language ?? fallbackItem?.language;
|
|
705
|
+
const versionValue = source?.version ?? fallbackItem?.version;
|
|
706
|
+
const version = typeof versionValue === "number" && Number.isFinite(versionValue)
|
|
707
|
+
? versionValue
|
|
708
|
+
: undefined;
|
|
709
|
+
if (!itemId || !language || version === undefined)
|
|
710
|
+
return;
|
|
711
|
+
if (candidateItems.some((item) => bridgeIdsMatch(item.id, itemId) &&
|
|
712
|
+
item.language.toLowerCase() === language.toLowerCase() &&
|
|
713
|
+
item.version === version)) {
|
|
714
|
+
return;
|
|
415
715
|
}
|
|
716
|
+
candidateItems.push({
|
|
717
|
+
id: itemId,
|
|
718
|
+
language,
|
|
719
|
+
version,
|
|
720
|
+
name: source?.name ?? fallbackItem?.name,
|
|
721
|
+
displayName: source?.displayName ?? fallbackItem?.displayName,
|
|
722
|
+
path: source?.path ?? fallbackItem?.path,
|
|
723
|
+
database: source?.database ?? fallbackItem?.database,
|
|
724
|
+
});
|
|
416
725
|
};
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
726
|
+
addCandidateItem(selectedRangeItem);
|
|
727
|
+
addCandidateItem(interaction.item);
|
|
728
|
+
addCandidateItem(structureField?.item);
|
|
729
|
+
addCandidateItem(component?.datasourceItem);
|
|
730
|
+
component?.items.forEach(addCandidateItem);
|
|
731
|
+
addCandidateItem(fallbackItem);
|
|
732
|
+
for (const item of candidateItems) {
|
|
733
|
+
const repositoryItem = await editContextRef.current?.itemsRepository.getItem(item);
|
|
734
|
+
const repositoryField = repositoryItem?.fields.find((field) => fieldIdentifierMatches(field, fieldId));
|
|
735
|
+
if (repositoryField) {
|
|
736
|
+
return {
|
|
737
|
+
fieldId: repositoryField.id,
|
|
738
|
+
item,
|
|
739
|
+
};
|
|
429
740
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
741
|
+
}
|
|
742
|
+
const fallbackDescriptorItem = candidateItems[0];
|
|
743
|
+
if (!fallbackDescriptorItem)
|
|
744
|
+
return undefined;
|
|
745
|
+
return {
|
|
746
|
+
fieldId,
|
|
747
|
+
item: fallbackDescriptorItem,
|
|
748
|
+
};
|
|
749
|
+
}, [editContextRef]);
|
|
750
|
+
const handleBridgeInteraction = useCallback(async (interaction, iframe) => {
|
|
751
|
+
if (interaction.kind === "wheel") {
|
|
752
|
+
if (!interaction.ctrlKey && !interaction.metaKey)
|
|
753
|
+
return;
|
|
754
|
+
if (!interaction.deltaY)
|
|
755
|
+
return;
|
|
756
|
+
const zoomContext = (compareView ? slotContext?.primaryPageViewContext : undefined) ??
|
|
757
|
+
pageViewContextRef.current;
|
|
758
|
+
const direction = interaction.deltaY < 0 ? 1 : -1;
|
|
759
|
+
zoomContext?.setZoom((value) => clampEditorZoom(value + direction * ZOOM_STEP));
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
if (interaction.kind === "keydown") {
|
|
763
|
+
if (isBridgeInlineAiShortcut(interaction)) {
|
|
764
|
+
const editor = editContextRef.current;
|
|
765
|
+
const field = await resolveBridgeFieldDescriptor(interaction);
|
|
766
|
+
if (editor && field) {
|
|
767
|
+
await editor.setFocusedField(field, editor.mode !== "suggestions");
|
|
435
768
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
loadContent(renderUrl.toString(), initialLoad, expectedRevision, controller.signal);
|
|
769
|
+
document.dispatchEvent(new CustomEvent("inline-ai-open", {
|
|
770
|
+
bubbles: true,
|
|
771
|
+
cancelable: true,
|
|
772
|
+
}));
|
|
441
773
|
}
|
|
774
|
+
return;
|
|
442
775
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
let integrationRefreshFn;
|
|
446
|
-
try {
|
|
447
|
-
integrationRefreshFn = iframeRef.current?.contentWindow?.requestRefresh;
|
|
776
|
+
if (interaction.kind !== "click" && interaction.kind !== "contextMenu") {
|
|
777
|
+
return;
|
|
448
778
|
}
|
|
449
|
-
|
|
450
|
-
|
|
779
|
+
if (interaction.kind === "click" && interaction.button !== 0)
|
|
780
|
+
return;
|
|
781
|
+
if (interaction.kind === "contextMenu" && interaction.ctrlKey)
|
|
782
|
+
return;
|
|
783
|
+
const editor = editContextRef.current;
|
|
784
|
+
if (!editor)
|
|
785
|
+
return;
|
|
786
|
+
const selectFromBridgeInteraction = (ids) => {
|
|
787
|
+
if (!bridgeComponentSelectionsMatch(editor.selection, ids)) {
|
|
788
|
+
suppressNextSelectionScrollRef.current = true;
|
|
789
|
+
}
|
|
790
|
+
editor.select(ids);
|
|
791
|
+
};
|
|
792
|
+
let effectiveInteraction = interaction;
|
|
793
|
+
if (interaction.kind === "contextMenu" &&
|
|
794
|
+
!interaction.fieldId &&
|
|
795
|
+
interaction.clientX !== undefined &&
|
|
796
|
+
interaction.clientY !== undefined) {
|
|
797
|
+
const fieldTarget = findBridgeFieldTargetAtPoint(pageViewContextRef.current?.bridgeGeometry, interaction.clientX, interaction.clientY);
|
|
798
|
+
if (fieldTarget?.fieldId) {
|
|
799
|
+
effectiveInteraction = {
|
|
800
|
+
...interaction,
|
|
801
|
+
elementKey: fieldTarget.elementKey ?? interaction.elementKey,
|
|
802
|
+
fieldId: fieldTarget.fieldId,
|
|
803
|
+
};
|
|
804
|
+
}
|
|
451
805
|
}
|
|
452
|
-
|
|
453
|
-
|
|
806
|
+
const componentId = resolveBridgeComponentId(effectiveInteraction.componentId, pageViewContextRef.current?.page);
|
|
807
|
+
const isEditableFieldClick = !!(effectiveInteraction.kind === "click" &&
|
|
808
|
+
effectiveInteraction.fieldId &&
|
|
809
|
+
effectiveInteraction.elementKey &&
|
|
810
|
+
!effectiveInteraction.ctrlKey &&
|
|
811
|
+
!effectiveInteraction.shiftKey &&
|
|
812
|
+
!effectiveInteraction.metaKey);
|
|
813
|
+
const currentOverlay = editor.currentOverlay;
|
|
814
|
+
const isGeneratorOverlay = !!(currentOverlay &&
|
|
815
|
+
typeof currentOverlay === "string" &&
|
|
816
|
+
currentOverlay.endsWith("_generators"));
|
|
817
|
+
if (interaction.kind === "click" && currentOverlay === "context-menu") {
|
|
818
|
+
editor.setCurrentOverlay(undefined);
|
|
454
819
|
}
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
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);
|
|
820
|
+
if (currentOverlay &&
|
|
821
|
+
currentOverlay !== "context-menu" &&
|
|
822
|
+
!isGeneratorOverlay) {
|
|
823
|
+
editor.setCurrentOverlay(undefined);
|
|
474
824
|
}
|
|
475
|
-
|
|
476
|
-
|
|
825
|
+
if (!componentId) {
|
|
826
|
+
if (interaction.kind === "click")
|
|
827
|
+
selectFromBridgeInteraction([]);
|
|
828
|
+
if (isEditableFieldClick) {
|
|
829
|
+
await beginTrackedBridgeInlineEdit(effectiveInteraction);
|
|
830
|
+
}
|
|
831
|
+
else if (interaction.kind === "click") {
|
|
832
|
+
endTrackedBridgeFieldFocus();
|
|
833
|
+
}
|
|
834
|
+
return;
|
|
477
835
|
}
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
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);
|
|
836
|
+
const currentSelection = editor.selection || [];
|
|
837
|
+
if (interaction.kind === "contextMenu") {
|
|
838
|
+
if (pageViewContextRef.current) {
|
|
839
|
+
pageViewContextRef.current.bridgeInteraction = effectiveInteraction;
|
|
499
840
|
}
|
|
500
|
-
|
|
501
|
-
|
|
841
|
+
const selectedIds = currentSelection.includes(componentId)
|
|
842
|
+
? currentSelection
|
|
843
|
+
: [componentId];
|
|
844
|
+
if (!currentSelection.includes(componentId)) {
|
|
845
|
+
selectFromBridgeInteraction(selectedIds);
|
|
502
846
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
847
|
+
const page = pageViewContextRef.current?.page;
|
|
848
|
+
if (!page)
|
|
849
|
+
return;
|
|
850
|
+
const selectedComponents = selectedIds
|
|
851
|
+
.map((id) => getComponentById(id, page))
|
|
852
|
+
.filter((component) => !!component);
|
|
853
|
+
if (selectedComponents.length === 0)
|
|
854
|
+
return;
|
|
855
|
+
const iframeRect = iframe.getBoundingClientRect();
|
|
856
|
+
const clientX = iframeRect.x + (interaction.clientX ?? 0);
|
|
857
|
+
const clientY = iframeRect.y + (interaction.clientY ?? 0);
|
|
858
|
+
const menuPosition = { x: clientX, y: clientY };
|
|
859
|
+
const adjustedEvent = new MouseEvent("contextmenu", {
|
|
860
|
+
bubbles: true,
|
|
861
|
+
cancelable: true,
|
|
862
|
+
shiftKey: interaction.shiftKey,
|
|
863
|
+
altKey: interaction.altKey,
|
|
864
|
+
ctrlKey: interaction.ctrlKey,
|
|
865
|
+
metaKey: interaction.metaKey,
|
|
866
|
+
view: window,
|
|
867
|
+
clientX,
|
|
868
|
+
clientY,
|
|
869
|
+
button: 2,
|
|
870
|
+
});
|
|
871
|
+
setContextMenuPosition(menuPosition);
|
|
872
|
+
let loadingShown = false;
|
|
873
|
+
const loadingTimer = window.setTimeout(() => {
|
|
874
|
+
loadingShown = true;
|
|
875
|
+
editor.showContextMenu(adjustedEvent, [
|
|
876
|
+
{
|
|
877
|
+
id: "loading",
|
|
878
|
+
label: "Loading...",
|
|
879
|
+
disabled: true,
|
|
880
|
+
icon: _jsx(Spinner, { size: "sm", className: "mr-2" }),
|
|
881
|
+
},
|
|
882
|
+
]);
|
|
883
|
+
}, 100);
|
|
884
|
+
const field = await resolveBridgeFieldDescriptor(effectiveInteraction);
|
|
885
|
+
const fieldButtons = field ? await loadFieldButtons(field) : [];
|
|
886
|
+
const handleParameterizedActionWithPosition = (field, action) => {
|
|
887
|
+
setContextMenuField(field);
|
|
888
|
+
setContextMenuFieldButtons([action]);
|
|
889
|
+
setPreSelectedAction(action);
|
|
890
|
+
window.setTimeout(() => {
|
|
891
|
+
const tempElement = document.createElement("div");
|
|
892
|
+
tempElement.style.position = "fixed";
|
|
893
|
+
tempElement.style.left = menuPosition.x + "px";
|
|
894
|
+
tempElement.style.top = menuPosition.y + "px";
|
|
895
|
+
tempElement.style.width = "1px";
|
|
896
|
+
tempElement.style.height = "1px";
|
|
897
|
+
tempElement.style.visibility = "hidden";
|
|
898
|
+
tempElement.style.pointerEvents = "none";
|
|
899
|
+
document.body.appendChild(tempElement);
|
|
900
|
+
const positionedEvent = new MouseEvent("click", {
|
|
901
|
+
bubbles: true,
|
|
902
|
+
cancelable: true,
|
|
903
|
+
view: window,
|
|
904
|
+
clientX: menuPosition.x,
|
|
905
|
+
clientY: menuPosition.y,
|
|
906
|
+
});
|
|
907
|
+
Object.defineProperty(positionedEvent, "target", {
|
|
908
|
+
value: tempElement,
|
|
909
|
+
enumerable: true,
|
|
910
|
+
});
|
|
911
|
+
fieldActionsOverlay.current?.show(positionedEvent, action);
|
|
912
|
+
window.setTimeout(() => {
|
|
913
|
+
tempElement.remove();
|
|
914
|
+
}, 1000);
|
|
915
|
+
}, 100);
|
|
916
|
+
};
|
|
917
|
+
const items = await buildComponentContextMenuItems(selectedComponents, editor, field, fieldButtons, handleParameterizedActionWithPosition);
|
|
918
|
+
window.clearTimeout(loadingTimer);
|
|
919
|
+
if (loadingShown) {
|
|
920
|
+
editor.updateContextMenu(items);
|
|
524
921
|
}
|
|
525
|
-
|
|
526
|
-
|
|
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);
|
|
922
|
+
else {
|
|
923
|
+
editor.showContextMenu(adjustedEvent, items);
|
|
532
924
|
}
|
|
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
925
|
return;
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
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();
|
|
926
|
+
}
|
|
927
|
+
if (interaction.shiftKey && currentSelection.length > 0) {
|
|
928
|
+
const orderedIds = getOrderedBridgeComponentIds(pageViewContextRef.current?.bridgeGeometry, pageViewContextRef.current?.page);
|
|
929
|
+
const anchorId = currentSelection[currentSelection.length - 1];
|
|
930
|
+
const anchorIndex = orderedIds.findIndex((id) => bridgeIdsMatch(id, anchorId));
|
|
931
|
+
const targetIndex = orderedIds.findIndex((id) => bridgeIdsMatch(id, componentId));
|
|
932
|
+
if (anchorIndex !== -1 && targetIndex !== -1) {
|
|
933
|
+
const start = Math.min(anchorIndex, targetIndex);
|
|
934
|
+
const end = Math.max(anchorIndex, targetIndex);
|
|
935
|
+
const range = orderedIds.slice(start, end + 1);
|
|
936
|
+
if (interaction.ctrlKey || interaction.metaKey) {
|
|
937
|
+
const nextSelection = [...currentSelection];
|
|
938
|
+
for (const id of range) {
|
|
939
|
+
if (!nextSelection.some((selectedId) => bridgeIdsMatch(selectedId, id))) {
|
|
940
|
+
nextSelection.push(id);
|
|
605
941
|
}
|
|
606
942
|
}
|
|
607
|
-
|
|
608
|
-
setShowSpinner(false);
|
|
943
|
+
selectFromBridgeInteraction(nextSelection);
|
|
609
944
|
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
injectSXAScripts(iframeRef.current);
|
|
613
|
-
}, 1000);
|
|
614
|
-
try {
|
|
615
|
-
requestPageModelBuild(doc);
|
|
945
|
+
else {
|
|
946
|
+
selectFromBridgeInteraction(range);
|
|
616
947
|
}
|
|
617
|
-
catch (buildErr) { }
|
|
618
948
|
}
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
if (err?.name === "AbortError") {
|
|
622
|
-
// Swallow aborts – a newer refresh superseded this one
|
|
623
|
-
return;
|
|
949
|
+
else {
|
|
950
|
+
selectFromBridgeInteraction([componentId]);
|
|
624
951
|
}
|
|
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
952
|
return;
|
|
635
|
-
let rect;
|
|
636
|
-
try {
|
|
637
|
-
rect = findComponentRect(iframeRef.current, component, true);
|
|
638
953
|
}
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
954
|
+
if (interaction.ctrlKey || interaction.metaKey) {
|
|
955
|
+
if (currentSelection.includes(componentId)) {
|
|
956
|
+
selectFromBridgeInteraction(currentSelection.filter((id) => id !== componentId));
|
|
957
|
+
}
|
|
958
|
+
else {
|
|
959
|
+
selectFromBridgeInteraction([...currentSelection, componentId]);
|
|
960
|
+
}
|
|
645
961
|
return;
|
|
646
|
-
// Check if element is already in viewport
|
|
647
|
-
const iframeHeight = iframeRef.current.getBoundingClientRect().height;
|
|
648
|
-
let scrollTop = 0;
|
|
649
|
-
try {
|
|
650
|
-
scrollTop = iframeRef.current?.contentWindow?.scrollY || 0;
|
|
651
962
|
}
|
|
652
|
-
|
|
653
|
-
|
|
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);
|
|
660
|
-
return;
|
|
963
|
+
selectFromBridgeInteraction([componentId]);
|
|
964
|
+
if (isEditableFieldClick) {
|
|
965
|
+
await beginTrackedBridgeInlineEdit(effectiveInteraction);
|
|
661
966
|
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
iframeRef.current?.contentWindow?.scrollTo({
|
|
665
|
-
top: scrollPosition,
|
|
666
|
-
behavior: "smooth",
|
|
667
|
-
});
|
|
967
|
+
else {
|
|
968
|
+
endTrackedBridgeFieldFocus();
|
|
668
969
|
}
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
970
|
+
}, [
|
|
971
|
+
beginTrackedBridgeInlineEdit,
|
|
972
|
+
endTrackedBridgeFieldFocus,
|
|
973
|
+
resolveBridgeFieldDescriptor,
|
|
974
|
+
]);
|
|
975
|
+
// Update the context whenever the iframe ref changes
|
|
672
976
|
useEffect(() => {
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
}
|
|
679
|
-
if (message.data.type === "editor-timings") {
|
|
680
|
-
editContext.setTimings(message.data.timings);
|
|
681
|
-
}
|
|
682
|
-
};
|
|
683
|
-
window.addEventListener("message", handleMessage);
|
|
684
|
-
return () => {
|
|
685
|
-
window.removeEventListener("message", handleMessage);
|
|
686
|
-
};
|
|
687
|
-
}, []);
|
|
688
|
-
const selecionChangeHandler = useDebouncedCallback(() => {
|
|
689
|
-
const sel = getAccessibleIframeDocument(iframeRef.current, "PageViewerFrame.tsx:selectionchange-doc")?.getSelection();
|
|
690
|
-
const isInlineAiUiFocused = () => {
|
|
691
|
-
const activeEl = document.activeElement;
|
|
692
|
-
return !!(activeEl?.closest(".agent-inline-dialog") ||
|
|
693
|
-
activeEl?.closest(".agent-inline-trigger") ||
|
|
694
|
-
activeEl?.closest('[role="dialog"]'));
|
|
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())) {
|
|
977
|
+
pageViewContext.setEditorIframe(iframeElement);
|
|
978
|
+
}, [iframeElement, pageViewContext.setEditorIframe]);
|
|
979
|
+
useEffect(() => {
|
|
980
|
+
const iframe = iframeElement;
|
|
981
|
+
if (!iframe)
|
|
710
982
|
return;
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
if (!isInlineAiUiFocused() && !isInlineAiUiPresent()) {
|
|
716
|
-
editContextRef.current?.setSelectedRange(undefined);
|
|
717
|
-
}
|
|
983
|
+
const currentIframeSrc = iframe.src;
|
|
984
|
+
if (!iframeSrc || !loadedIframeSrc || loadedIframeSrc !== currentIframeSrc) {
|
|
985
|
+
pageViewContext.setBridgeReady(false);
|
|
986
|
+
pageViewContext.setIframeSupportsRefresh(false);
|
|
718
987
|
return;
|
|
719
988
|
}
|
|
720
|
-
const
|
|
721
|
-
|
|
989
|
+
const allowedHostOrigins = [
|
|
990
|
+
getUrlOrigin(pageViewContext.editUrl),
|
|
991
|
+
getUrlOrigin(pageViewContext.previewUrl),
|
|
992
|
+
].filter((origin) => Boolean(origin));
|
|
993
|
+
if (allowedHostOrigins.length === 0) {
|
|
994
|
+
pageViewContext.setBridgeReady(false);
|
|
995
|
+
pageViewContext.setIframeSupportsRefresh(false);
|
|
722
996
|
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
997
|
}
|
|
732
|
-
//
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
//
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
const
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
const
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
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 },
|
|
998
|
+
// The canonical bridge is served from the editor's own origin. A stub
|
|
999
|
+
// bootloader on the host loads it from this URL; full-bridge hosts ignore
|
|
1000
|
+
// it. Editor shells deploy under different base paths (e.g. dev-vite under
|
|
1001
|
+
// `/parhelia/` in Sitecore), so each app advertises its own served location
|
|
1002
|
+
// via `window.__PARHELIA_EDITOR_BRIDGE_URL__`. If that global is missing,
|
|
1003
|
+
// infer the known `/parhelia` mount from the current editor URL.
|
|
1004
|
+
const configuredEditorBridgeUrl = window.__PARHELIA_EDITOR_BRIDGE_URL__;
|
|
1005
|
+
const editorBasePath = window.location.pathname.startsWith("/parhelia")
|
|
1006
|
+
? "/parhelia"
|
|
1007
|
+
: "";
|
|
1008
|
+
const editorBridgeUrl = configuredEditorBridgeUrl ||
|
|
1009
|
+
new URL(`${editorBasePath}/editor-bridge/v1/parhelia-bridge.js`, window.location.origin).toString();
|
|
1010
|
+
let client;
|
|
1011
|
+
client = new BridgeClient({
|
|
1012
|
+
iframe,
|
|
1013
|
+
editorOrigin: window.location.origin,
|
|
1014
|
+
allowedHostOrigins,
|
|
1015
|
+
bridgeUrl: editorBridgeUrl,
|
|
1016
|
+
onReadyChange: (ready) => {
|
|
1017
|
+
pageViewContext.setBridgeReady(ready);
|
|
1018
|
+
if (!ready)
|
|
1019
|
+
pageViewContext.setIframeSupportsRefresh(false);
|
|
1020
|
+
},
|
|
1021
|
+
onError: (error) => {
|
|
1022
|
+
pageViewContext.setBridgeReady(false);
|
|
1023
|
+
pageViewContext.setIframeSupportsRefresh(false);
|
|
1024
|
+
console.error("[Parhelia bridge]", error.message);
|
|
1025
|
+
if (!compareView) {
|
|
1026
|
+
editContextRef.current?.showErrorToast({
|
|
1027
|
+
summary: "Editing host bridge unavailable",
|
|
1028
|
+
details: error.message,
|
|
1029
|
+
});
|
|
1030
|
+
}
|
|
1031
|
+
},
|
|
1032
|
+
onEvent: (event, bridge) => {
|
|
1033
|
+
switch (event.name) {
|
|
1034
|
+
case "ready":
|
|
1035
|
+
clearBridgePatchSignatures();
|
|
1036
|
+
pageViewContext.setIframeSupportsRefresh(bridge.supportsCapability("refresh"));
|
|
1037
|
+
bridge.sendCommand("init", {
|
|
1038
|
+
editorOrigin: window.location.origin,
|
|
1039
|
+
sessionId: editContextRef.current?.sessionId,
|
|
1040
|
+
mode: editContextRef.current?.mode ?? "edit",
|
|
1041
|
+
selectedComponentIds: editContextRef.current?.selection ?? [],
|
|
1042
|
+
featureFlags: {
|
|
1043
|
+
crossOriginBridge: true,
|
|
788
1044
|
},
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
value: valueToSave,
|
|
1045
|
+
version: bridge.getDiagnostics().editorVersion,
|
|
1046
|
+
acknowledgedCapabilities: bridge.getAcknowledgedCapabilities(),
|
|
792
1047
|
});
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
1048
|
+
bridge.sendCommand("setZoom", {
|
|
1049
|
+
zoom: pageViewContextRef.current?.zoom ?? 1,
|
|
1050
|
+
});
|
|
1051
|
+
break;
|
|
1052
|
+
case "structureUpdated":
|
|
1053
|
+
pageViewContext.setBridgeStructure(event.payload.structure);
|
|
1054
|
+
break;
|
|
1055
|
+
case "pageSkeletonUpdated":
|
|
1056
|
+
pageViewContext.setPageSkeleton(event.payload.pageSkeleton);
|
|
1057
|
+
break;
|
|
1058
|
+
case "geometryUpdated":
|
|
1059
|
+
latestBridgeScrollRef.current = event.payload.geometry.scroll;
|
|
1060
|
+
pageViewContext.bridgeGeometry = event.payload.geometry;
|
|
1061
|
+
pageViewContext.setBridgeGeometry(event.payload.geometry);
|
|
1062
|
+
scheduleBridgeGeometryRevision();
|
|
1063
|
+
dispatchBridgeOverlayScroll(iframe, event.payload.geometry.scroll, getBridgeGeometryScrollScale(event.payload.geometry));
|
|
1064
|
+
dispatchBridgeOverlayGeometry(iframe, event.payload.geometry);
|
|
1065
|
+
if (shouldTrackMinimapScroll()) {
|
|
1066
|
+
scrollHandlerRef.current(event.payload.geometry.scroll.y);
|
|
1067
|
+
}
|
|
1068
|
+
break;
|
|
1069
|
+
case "domUpdated":
|
|
1070
|
+
pageViewContext.bridgeDom = event.payload;
|
|
1071
|
+
pageViewContext.setBridgeDom(event.payload);
|
|
1072
|
+
scheduleBridgeDomRevision();
|
|
1073
|
+
window.dispatchEvent(new CustomEvent(BRIDGE_DOM_UPDATED_EVENT));
|
|
1074
|
+
break;
|
|
1075
|
+
case "refreshStarted":
|
|
1076
|
+
clearBridgePatchSignatures();
|
|
1077
|
+
activeBridgeInlineEditRef.current = null;
|
|
1078
|
+
break;
|
|
1079
|
+
case "refreshCompleted":
|
|
1080
|
+
// Runtime refresh replaces host DOM without a new bridge ready event.
|
|
1081
|
+
// Field patches sent during the refresh window may have been cached
|
|
1082
|
+
// against the previous DOM generation, so force the refreshed host to
|
|
1083
|
+
// receive the current repository values again.
|
|
1084
|
+
clearBridgePatchSignatures();
|
|
1085
|
+
if (event.payload.structure) {
|
|
1086
|
+
pageViewContext.setBridgeStructure(event.payload.structure);
|
|
1087
|
+
}
|
|
1088
|
+
if (event.payload.geometry) {
|
|
1089
|
+
latestBridgeScrollRef.current = event.payload.geometry.scroll;
|
|
1090
|
+
pageViewContext.bridgeGeometry = event.payload.geometry;
|
|
1091
|
+
pageViewContext.setBridgeGeometry(event.payload.geometry);
|
|
1092
|
+
scheduleBridgeGeometryRevision();
|
|
1093
|
+
dispatchBridgeOverlayScroll(iframe, event.payload.geometry.scroll, getBridgeGeometryScrollScale(event.payload.geometry));
|
|
1094
|
+
dispatchBridgeOverlayGeometry(iframe, event.payload.geometry);
|
|
1095
|
+
if (shouldTrackMinimapScroll()) {
|
|
1096
|
+
scrollHandlerRef.current(event.payload.geometry.scroll.y);
|
|
1097
|
+
}
|
|
1098
|
+
}
|
|
1099
|
+
setShowSpinner(false);
|
|
1100
|
+
break;
|
|
1101
|
+
case "selectionChanged":
|
|
1102
|
+
pageViewContext.setBridgeSelection(event.payload.selection);
|
|
1103
|
+
handleBridgeSelection(event.payload.selection, iframe);
|
|
1104
|
+
break;
|
|
1105
|
+
case "fieldValueChanged":
|
|
1106
|
+
handleBridgeFieldValueChanged(event.payload);
|
|
1107
|
+
break;
|
|
1108
|
+
case "inlineEditEnded":
|
|
1109
|
+
handleBridgeInlineEditEnded(event.payload);
|
|
1110
|
+
activeBridgeInlineEditRef.current = null;
|
|
1111
|
+
break;
|
|
1112
|
+
case "interaction":
|
|
1113
|
+
pageViewContext.setBridgeInteraction(event.payload);
|
|
1114
|
+
void handleBridgeInteraction(event.payload, iframe);
|
|
1115
|
+
break;
|
|
1116
|
+
case "scrollChanged":
|
|
1117
|
+
latestBridgeScrollRef.current = event.payload.scroll;
|
|
1118
|
+
dispatchBridgeOverlayScroll(iframe, event.payload.scroll, getBridgeGeometryScrollScale(pageViewContextRef.current?.bridgeGeometry));
|
|
1119
|
+
if (shouldTrackMinimapScroll()) {
|
|
1120
|
+
scrollHandlerRef.current(event.payload.scroll.y);
|
|
1121
|
+
}
|
|
1122
|
+
break;
|
|
1123
|
+
case "renderError":
|
|
1124
|
+
console.warn("[Parhelia bridge] Host render error", event.payload);
|
|
1125
|
+
break;
|
|
797
1126
|
}
|
|
798
1127
|
},
|
|
799
1128
|
});
|
|
800
|
-
|
|
1129
|
+
bridgeClientRef.current = client;
|
|
1130
|
+
pageViewContext.setBridgeReady(false);
|
|
1131
|
+
client.connect();
|
|
1132
|
+
return () => {
|
|
1133
|
+
if (bridgeClientRef.current === client) {
|
|
1134
|
+
bridgeClientRef.current = null;
|
|
1135
|
+
}
|
|
1136
|
+
client.disconnect();
|
|
1137
|
+
clearBridgePatchSignatures();
|
|
1138
|
+
activeBridgeInlineEditRef.current = null;
|
|
1139
|
+
pageViewContext.setBridgeReady(false);
|
|
1140
|
+
pageViewContext.setIframeSupportsRefresh(false);
|
|
1141
|
+
};
|
|
1142
|
+
}, [
|
|
1143
|
+
iframeElement,
|
|
1144
|
+
iframeSrc,
|
|
1145
|
+
loadedIframeSrc,
|
|
1146
|
+
compareView,
|
|
1147
|
+
handleBridgeFieldValueChanged,
|
|
1148
|
+
handleBridgeInlineEditEnded,
|
|
1149
|
+
handleBridgeInteraction,
|
|
1150
|
+
handleBridgeSelection,
|
|
1151
|
+
clearBridgePatchSignatures,
|
|
1152
|
+
pageViewContext.editUrl,
|
|
1153
|
+
pageViewContext.previewUrl,
|
|
1154
|
+
scheduleBridgeDomRevision,
|
|
1155
|
+
scheduleBridgeGeometryRevision,
|
|
1156
|
+
shouldTrackMinimapScroll,
|
|
1157
|
+
]);
|
|
801
1158
|
useEffect(() => {
|
|
802
|
-
const
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
let mutationObserver = null;
|
|
809
|
-
const handleIframeMouseDown = async (event) => {
|
|
810
|
-
const target = event.target;
|
|
811
|
-
const targetElement = target;
|
|
812
|
-
if (editContextRef.current?.isRefreshing && showSpinner)
|
|
1159
|
+
const repository = editContext.itemsRepository;
|
|
1160
|
+
let disposed = false;
|
|
1161
|
+
const unsubscribe = repository.subscribeItemsChanged((changes) => {
|
|
1162
|
+
const fieldChanges = changes.filter((change) => change.action === "update" &&
|
|
1163
|
+
(change.changes.fields?.length ?? 0) > 0);
|
|
1164
|
+
if (fieldChanges.length === 0)
|
|
813
1165
|
return;
|
|
814
|
-
|
|
815
|
-
const
|
|
816
|
-
|
|
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)
|
|
1166
|
+
const bridge = bridgeClientRef.current;
|
|
1167
|
+
const structure = pageViewContextRef.current?.bridgeStructure;
|
|
1168
|
+
if (!bridge || !structure?.fields.length)
|
|
824
1169
|
return;
|
|
825
|
-
const
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
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
|
-
}
|
|
1170
|
+
const processResolvedField = (change, changedFieldId, field) => {
|
|
1171
|
+
if (disposed)
|
|
1172
|
+
return;
|
|
1173
|
+
const patchedElementKeys = new Set();
|
|
1174
|
+
const patches = [];
|
|
1175
|
+
for (const structureField of structure.fields) {
|
|
1176
|
+
if (!structureField.elementKey)
|
|
1177
|
+
continue;
|
|
1178
|
+
if (patchedElementKeys.has(structureField.elementKey))
|
|
1179
|
+
continue;
|
|
1180
|
+
if (!bridgeDescriptorMatchesItem(structureField.item, change.item)) {
|
|
1181
|
+
continue;
|
|
890
1182
|
}
|
|
891
|
-
|
|
892
|
-
|
|
1183
|
+
if (!bridgeFieldMatchesChangedField(structureField.fieldId, changedFieldId, field)) {
|
|
1184
|
+
continue;
|
|
893
1185
|
}
|
|
1186
|
+
const patch = buildBridgeFieldPatch({
|
|
1187
|
+
field,
|
|
1188
|
+
structureField,
|
|
1189
|
+
itemDescriptor: change.item,
|
|
1190
|
+
preferRepositoryValue: change.source === "local-field-update",
|
|
1191
|
+
});
|
|
1192
|
+
if (!patch)
|
|
1193
|
+
continue;
|
|
1194
|
+
patchedElementKeys.add(structureField.elementKey);
|
|
1195
|
+
patches.push(patch);
|
|
894
1196
|
}
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
1197
|
+
for (const patch of patches) {
|
|
1198
|
+
if (disposed)
|
|
1199
|
+
return;
|
|
1200
|
+
const structureField = structure.fields.find((field) => field.elementKey === patch.elementKey &&
|
|
1201
|
+
field.fieldId === patch.fieldId);
|
|
1202
|
+
sendBridgeFieldPatch(bridge, patch, structureField?.textContent);
|
|
1203
|
+
}
|
|
1204
|
+
};
|
|
1205
|
+
const asyncFieldChanges = [];
|
|
1206
|
+
for (const change of fieldChanges) {
|
|
1207
|
+
const localFieldsById = new Map((change.changes.fieldValues ?? []).map((field) => [
|
|
1208
|
+
field.id.toLowerCase(),
|
|
1209
|
+
field,
|
|
1210
|
+
]));
|
|
1211
|
+
let needsAsyncLookup = false;
|
|
1212
|
+
for (const changedFieldId of change.changes.fields ?? []) {
|
|
1213
|
+
if (disposed)
|
|
1214
|
+
return;
|
|
1215
|
+
const localField = localFieldsById.get(changedFieldId.toLowerCase());
|
|
1216
|
+
if (!localField) {
|
|
1217
|
+
needsAsyncLookup = true;
|
|
1218
|
+
continue;
|
|
905
1219
|
}
|
|
1220
|
+
processResolvedField(change, changedFieldId, localField);
|
|
906
1221
|
}
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
editContextRef.current?.select([componentId]);
|
|
1222
|
+
if (needsAsyncLookup) {
|
|
1223
|
+
asyncFieldChanges.push(change);
|
|
910
1224
|
}
|
|
911
|
-
// if (mode !== "edit") {
|
|
912
|
-
//editContextRef.current?.setScrollIntoView(componentId);
|
|
913
|
-
//}
|
|
914
|
-
}
|
|
915
|
-
else {
|
|
916
|
-
editContextRef.current?.select([]);
|
|
917
1225
|
}
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
(
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
pageViewContextRef.current?.page) {
|
|
928
|
-
const owningItemId = fieldElement.getAttribute("data-itemid") || "";
|
|
929
|
-
const ownerComponent = getComponentById(owningItemId, pageViewContextRef.current.page);
|
|
930
|
-
if (ownerComponent?.layoutId) {
|
|
1226
|
+
if (asyncFieldChanges.length === 0)
|
|
1227
|
+
return;
|
|
1228
|
+
void (async () => {
|
|
1229
|
+
for (const change of asyncFieldChanges) {
|
|
1230
|
+
if (disposed)
|
|
1231
|
+
return;
|
|
1232
|
+
const item = await repository.getItem(change.item);
|
|
1233
|
+
for (const changedFieldId of change.changes.fields ?? []) {
|
|
1234
|
+
if (disposed)
|
|
931
1235
|
return;
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
1236
|
+
const field = change.changes.fieldValues?.find((candidate) => fieldIdentifierMatches(candidate, changedFieldId)) ??
|
|
1237
|
+
item?.fields.find((candidate) => fieldIdentifierMatches(candidate, changedFieldId)) ??
|
|
1238
|
+
(await repository.getField({
|
|
1239
|
+
fieldId: changedFieldId,
|
|
1240
|
+
item: change.item,
|
|
1241
|
+
}));
|
|
1242
|
+
if (!field)
|
|
1243
|
+
continue;
|
|
1244
|
+
processResolvedField(change, changedFieldId, field);
|
|
941
1245
|
}
|
|
942
1246
|
}
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
fieldsContextRef.current?.setInlineEditingFieldElement(undefined);
|
|
947
|
-
// Release field locks when unfocusing field
|
|
948
|
-
if (editContextRef.current?.unlockField) {
|
|
949
|
-
editContextRef.current.unlockField(undefined);
|
|
950
|
-
}
|
|
1247
|
+
})().catch((error) => {
|
|
1248
|
+
if (!disposed) {
|
|
1249
|
+
console.warn("[Parhelia bridge] Failed to patch host field", error);
|
|
951
1250
|
}
|
|
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
1251
|
});
|
|
960
|
-
|
|
961
|
-
|
|
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));
|
|
1252
|
+
});
|
|
1253
|
+
return () => {
|
|
1254
|
+
disposed = true;
|
|
1255
|
+
unsubscribe();
|
|
983
1256
|
};
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1257
|
+
}, [
|
|
1258
|
+
buildBridgeFieldPatch,
|
|
1259
|
+
editContext.itemsRepository,
|
|
1260
|
+
sendBridgeFieldPatch,
|
|
1261
|
+
]);
|
|
1262
|
+
useEffect(() => {
|
|
1263
|
+
const bridge = bridgeClientRef.current;
|
|
1264
|
+
const structure = pageViewContext.bridgeStructure;
|
|
1265
|
+
if (!pageViewContext.bridgeReady || !bridge || !structure?.fields.length) {
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
let disposed = false;
|
|
1269
|
+
void (async () => {
|
|
1270
|
+
const patchedElementKeys = new Set();
|
|
1271
|
+
for (const structureField of structure.fields) {
|
|
1272
|
+
if (disposed)
|
|
1273
|
+
return;
|
|
1274
|
+
if (!structureField.elementKey)
|
|
1275
|
+
continue;
|
|
1276
|
+
if (patchedElementKeys.has(structureField.elementKey))
|
|
1277
|
+
continue;
|
|
1278
|
+
const fallbackItem = pageViewContextRef.current?.pageItemDescriptor ??
|
|
1279
|
+
pageViewContextRef.current?.page?.item?.descriptor;
|
|
1280
|
+
const bridgeItem = structureField.item;
|
|
1281
|
+
const itemDescriptor = {
|
|
1282
|
+
id: bridgeItem?.id ?? fallbackItem?.id,
|
|
1283
|
+
language: bridgeItem?.language ?? fallbackItem?.language,
|
|
1284
|
+
version: typeof bridgeItem?.version === "number"
|
|
1285
|
+
? bridgeItem.version
|
|
1286
|
+
: fallbackItem?.version,
|
|
1287
|
+
};
|
|
1288
|
+
if (!itemDescriptor.id ||
|
|
1289
|
+
!itemDescriptor.language ||
|
|
1290
|
+
typeof itemDescriptor.version !== "number") {
|
|
1291
|
+
continue;
|
|
1292
|
+
}
|
|
1293
|
+
const loadedItem = await editContext.itemsRepository.getItem(itemDescriptor);
|
|
1294
|
+
if (disposed || !loadedItem)
|
|
1295
|
+
continue;
|
|
1296
|
+
const field = loadedItem.fields.find((candidate) => fieldIdentifierMatches(candidate, structureField.fieldId));
|
|
1297
|
+
if (!field)
|
|
1298
|
+
continue;
|
|
1299
|
+
const patch = buildBridgeFieldPatch({
|
|
1300
|
+
field,
|
|
1301
|
+
structureField,
|
|
1302
|
+
itemDescriptor: itemDescriptor,
|
|
994
1303
|
});
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
return;
|
|
1304
|
+
if (!patch)
|
|
1305
|
+
continue;
|
|
1306
|
+
patchedElementKeys.add(structureField.elementKey);
|
|
1307
|
+
sendBridgeFieldPatch(bridge, patch, structureField.textContent);
|
|
1000
1308
|
}
|
|
1001
|
-
|
|
1002
|
-
if (
|
|
1003
|
-
|
|
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
|
-
}
|
|
1309
|
+
})().catch((error) => {
|
|
1310
|
+
if (!disposed) {
|
|
1311
|
+
console.warn("[Parhelia bridge] Failed to patch host suggestion display", error);
|
|
1030
1312
|
}
|
|
1313
|
+
});
|
|
1314
|
+
return () => {
|
|
1315
|
+
disposed = true;
|
|
1031
1316
|
};
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1317
|
+
}, [
|
|
1318
|
+
buildBridgeFieldPatch,
|
|
1319
|
+
editContext.itemsRepository,
|
|
1320
|
+
editContext.itemsRepository.revision,
|
|
1321
|
+
editContext.mode,
|
|
1322
|
+
editContext.showSuggestedEdits,
|
|
1323
|
+
editContext.suggestedEdits,
|
|
1324
|
+
fieldsContext?.modifiedFields,
|
|
1325
|
+
bridgeDomRevision,
|
|
1326
|
+
pageViewContext.bridgeReady,
|
|
1327
|
+
pageViewContext.bridgeStructure,
|
|
1328
|
+
sendBridgeFieldPatch,
|
|
1329
|
+
]);
|
|
1330
|
+
useEffect(() => {
|
|
1331
|
+
const requestBridgeGeometry = (payload) => {
|
|
1332
|
+
const bridge = bridgeClientRef.current;
|
|
1333
|
+
if (!bridge)
|
|
1334
|
+
return false;
|
|
1335
|
+
// The editing host only remembers the most recent queryGeometry payload.
|
|
1336
|
+
// Comments, suggestions, locks and version-diff each contribute text
|
|
1337
|
+
// ranges independently, so without merging they clobbered one another and
|
|
1338
|
+
// fought in an endless re-request loop (heavy highlight flicker). Keep the
|
|
1339
|
+
// latest ranges per `source` and always query with their union.
|
|
1340
|
+
if (payload && typeof payload.source === "string") {
|
|
1341
|
+
const { source, textRanges, ...rest } = payload;
|
|
1342
|
+
const sources = bridgeTextRangeSourcesRef.current;
|
|
1343
|
+
if (textRanges && textRanges.length > 0) {
|
|
1344
|
+
sources.set(source, textRanges);
|
|
1345
|
+
}
|
|
1346
|
+
else {
|
|
1347
|
+
sources.delete(source);
|
|
1348
|
+
}
|
|
1349
|
+
const mergedTextRanges = Array.from(sources.values()).flat();
|
|
1350
|
+
return (bridge.sendCommand("queryGeometry", {
|
|
1351
|
+
...rest,
|
|
1352
|
+
textRanges: mergedTextRanges,
|
|
1353
|
+
}) ?? false);
|
|
1059
1354
|
}
|
|
1355
|
+
return bridge.sendCommand("queryGeometry", payload) ?? false;
|
|
1060
1356
|
};
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
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);
|
|
1357
|
+
pageViewContext.requestBridgeGeometry = requestBridgeGeometry;
|
|
1358
|
+
const requestBridgeScrollBy = (payload) => {
|
|
1359
|
+
return bridgeClientRef.current?.sendCommand("scrollBy", payload) ?? false;
|
|
1073
1360
|
};
|
|
1074
|
-
const
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
boundDocumentElement.removeEventListener("mousedown", handleIframeMouseDown);
|
|
1078
|
-
boundDocumentElement.removeEventListener("click", handleIframeClick);
|
|
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);
|
|
1091
|
-
}
|
|
1092
|
-
catch { }
|
|
1093
|
-
mutationObserver?.disconnect();
|
|
1094
|
-
mutationObserver = null;
|
|
1095
|
-
boundScrollContainer = null;
|
|
1096
|
-
boundDocumentElement = null;
|
|
1097
|
-
boundDocument = null;
|
|
1361
|
+
const requestBridgeRichTextCommand = (payload) => {
|
|
1362
|
+
return (bridgeClientRef.current?.sendCommand("applyRichTextCommand", payload) ??
|
|
1363
|
+
false);
|
|
1098
1364
|
};
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
const iframeDocument = getAccessibleIframeDocument(iframe, "PageViewerFrame.tsx:attachListeners-document-access");
|
|
1105
|
-
const iframeDocumentElement = iframeDocument?.documentElement;
|
|
1106
|
-
if (!iframeDocument || !iframeDocumentElement)
|
|
1107
|
-
return;
|
|
1108
|
-
// Skip if already bound to current root element.
|
|
1109
|
-
if (boundDocument === iframeDocument &&
|
|
1110
|
-
boundDocumentElement === iframeDocumentElement) {
|
|
1111
|
-
return;
|
|
1365
|
+
pageViewContext.requestBridgeScrollBy = requestBridgeScrollBy;
|
|
1366
|
+
pageViewContext.requestBridgeRichTextCommand = requestBridgeRichTextCommand;
|
|
1367
|
+
return () => {
|
|
1368
|
+
if (pageViewContext.requestBridgeGeometry === requestBridgeGeometry) {
|
|
1369
|
+
pageViewContext.requestBridgeGeometry = undefined;
|
|
1112
1370
|
}
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
boundDocumentElement = iframeDocumentElement;
|
|
1116
|
-
iframeDocumentElement.addEventListener("mousedown", handleIframeMouseDown);
|
|
1117
|
-
iframeDocumentElement.addEventListener("click", handleIframeClick);
|
|
1118
|
-
try {
|
|
1119
|
-
iframe.contentWindow?.addEventListener("contextmenu", handleContextMenu, true);
|
|
1371
|
+
if (pageViewContext.requestBridgeScrollBy === requestBridgeScrollBy) {
|
|
1372
|
+
pageViewContext.requestBridgeScrollBy = undefined;
|
|
1120
1373
|
}
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
boundScrollContainer?.addEventListener("scroll", handleIframeScroll);
|
|
1125
|
-
try {
|
|
1126
|
-
iframe.contentWindow?.addEventListener("scroll", handleIframeWindowScroll);
|
|
1374
|
+
if (pageViewContext.requestBridgeRichTextCommand ===
|
|
1375
|
+
requestBridgeRichTextCommand) {
|
|
1376
|
+
pageViewContext.requestBridgeRichTextCommand = undefined;
|
|
1127
1377
|
}
|
|
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);
|
|
1153
|
-
};
|
|
1154
|
-
const parsePositiveInt = (value) => {
|
|
1155
|
-
if (!value)
|
|
1156
|
-
return undefined;
|
|
1157
|
-
const parsed = parseInt(value, 10);
|
|
1158
|
-
return Number.isFinite(parsed) ? parsed : undefined;
|
|
1159
1378
|
};
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1379
|
+
}, [pageViewContext]);
|
|
1380
|
+
useEffect(() => {
|
|
1381
|
+
const requestBridgeCaptureDom = (payload) => {
|
|
1382
|
+
const bridge = bridgeClientRef.current;
|
|
1383
|
+
if (!bridge) {
|
|
1384
|
+
return Promise.reject(new Error("The Parhelia bridge is not connected to the host."));
|
|
1165
1385
|
}
|
|
1166
|
-
|
|
1167
|
-
|
|
1386
|
+
return bridge.requestCommand("captureDom", payload, "captureDomResult", {
|
|
1387
|
+
requestId: payload.requestId,
|
|
1388
|
+
});
|
|
1389
|
+
};
|
|
1390
|
+
pageViewContext.requestBridgeCaptureDom = requestBridgeCaptureDom;
|
|
1391
|
+
return () => {
|
|
1392
|
+
if (pageViewContext.requestBridgeCaptureDom === requestBridgeCaptureDom) {
|
|
1393
|
+
pageViewContext.requestBridgeCaptureDom = undefined;
|
|
1168
1394
|
}
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1395
|
+
};
|
|
1396
|
+
}, [pageViewContext]);
|
|
1397
|
+
const updateMiniMapVisibility = useDebouncedCallback(() => {
|
|
1398
|
+
if (!iframeRef.current)
|
|
1399
|
+
return;
|
|
1400
|
+
const iframe = iframeRef.current;
|
|
1401
|
+
const bridgeGeometry = pageViewContextRef.current?.bridgeGeometry;
|
|
1402
|
+
const bridgeDom = pageViewContextRef.current?.bridgeDom;
|
|
1403
|
+
const contentHeight = bridgeDom?.scrollHeight ??
|
|
1404
|
+
getBridgeGeometryDocumentSize(bridgeGeometry)?.height;
|
|
1405
|
+
const clientHeight = iframe.clientHeight;
|
|
1406
|
+
if (!contentHeight || !clientHeight)
|
|
1407
|
+
return;
|
|
1408
|
+
const upperThreshold = clientHeight + 100; // show minimap if content exceeds this height
|
|
1409
|
+
const lowerThreshold = clientHeight; // hide minimap if content falls below this
|
|
1410
|
+
// Check if minimap is enabled in settings and user controls
|
|
1411
|
+
const minimapEnabled = editContext.parheliaSettings?.showMinimap !== false &&
|
|
1412
|
+
editContext.showMinimap;
|
|
1413
|
+
if (showMiniMap) {
|
|
1414
|
+
if (contentHeight <= lowerThreshold || !minimapEnabled) {
|
|
1415
|
+
setShowMiniMap(false);
|
|
1181
1416
|
}
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
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);
|
|
1417
|
+
}
|
|
1418
|
+
else {
|
|
1419
|
+
if (contentHeight > upperThreshold && minimapEnabled) {
|
|
1420
|
+
setShowMiniMap(true);
|
|
1196
1421
|
}
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1422
|
+
}
|
|
1423
|
+
}, 100);
|
|
1424
|
+
useEffect(() => {
|
|
1425
|
+
updateMiniMapVisibility();
|
|
1426
|
+
}, [
|
|
1427
|
+
bridgeDomRevision,
|
|
1428
|
+
bridgeGeometryRevision,
|
|
1429
|
+
editContext.parheliaSettings?.showMinimap,
|
|
1430
|
+
editContext.showMinimap,
|
|
1431
|
+
iframeElement,
|
|
1432
|
+
showMiniMap,
|
|
1433
|
+
updateMiniMapVisibility,
|
|
1434
|
+
]);
|
|
1435
|
+
// If the editor mode flips into preview, clear any active text selection state.
|
|
1436
|
+
useEffect(() => {
|
|
1437
|
+
const isPreview = editContext.mode === "preview";
|
|
1438
|
+
if (isPreview) {
|
|
1439
|
+
editContextRef.current?.setSelectedRange(undefined);
|
|
1440
|
+
}
|
|
1441
|
+
}, [editContext.mode]);
|
|
1442
|
+
useEffect(() => {
|
|
1443
|
+
if (!pageItemDescriptor ||
|
|
1444
|
+
!pageViewContext.editUrl ||
|
|
1445
|
+
!pageViewContext.previewUrl)
|
|
1446
|
+
return;
|
|
1447
|
+
const urlPath = editContext.mode === "preview"
|
|
1448
|
+
? pageViewContext.previewUrl
|
|
1449
|
+
: pageViewContext.editUrl;
|
|
1450
|
+
const renderUrl = new URL(urlPath, window.location.origin);
|
|
1451
|
+
alignLoopbackHostToEditor(renderUrl);
|
|
1452
|
+
const editRev = uuid();
|
|
1453
|
+
renderUrl.searchParams.set("edit_rev", editRev);
|
|
1454
|
+
if (editContext.mode !== "preview" && editContext.sessionId) {
|
|
1455
|
+
renderUrl.searchParams.set("parhelia_session", editContext.sessionId);
|
|
1456
|
+
}
|
|
1457
|
+
if (editContext.mode === "preview" && editContext.previewDate) {
|
|
1458
|
+
renderUrl.searchParams.delete("sc_version");
|
|
1459
|
+
renderUrl.searchParams.set("sc_date", toSitecoreDate(editContext.previewDate));
|
|
1460
|
+
}
|
|
1461
|
+
// Layout-mode marker. Only set when editing shared layout, paired with the
|
|
1462
|
+
// `parhelia` editor marker so ParheliaSetLayoutRenderings ignores any unrelated
|
|
1463
|
+
// requests that happen to carry parhelia_layout.
|
|
1464
|
+
if (editContext.mode !== "preview" && editContext.layoutMode === "shared") {
|
|
1465
|
+
renderUrl.searchParams.set("parhelia", "1");
|
|
1466
|
+
renderUrl.searchParams.set("parhelia_layout", "shared");
|
|
1467
|
+
}
|
|
1468
|
+
else {
|
|
1469
|
+
renderUrl.searchParams.delete("parhelia_layout");
|
|
1470
|
+
}
|
|
1471
|
+
// Detect if the version in the URL changed - this requires a full reload, not just requestRefresh
|
|
1472
|
+
// because Next.js router.replace may not properly refetch server data for version changes.
|
|
1473
|
+
const currentIframeUrl = iframeRef.current?.src;
|
|
1474
|
+
const currentIframeOrigin = getUrlOrigin(currentIframeUrl);
|
|
1475
|
+
const renderUrlIsCrossOrigin = renderUrl.origin !== window.location.origin;
|
|
1476
|
+
const iframeOriginChanged = !!(currentIframeOrigin && currentIframeOrigin !== renderUrl.origin);
|
|
1477
|
+
const currentVersion = currentIframeUrl
|
|
1478
|
+
? new URL(currentIframeUrl).searchParams.get("sc_version")
|
|
1479
|
+
: null;
|
|
1480
|
+
const newVersion = renderUrl.searchParams.get("sc_version");
|
|
1481
|
+
const versionChanged = (currentVersion !== null || newVersion !== null) &&
|
|
1482
|
+
currentVersion !== newVersion;
|
|
1483
|
+
const initialLoad = currentItemDescriptor?.id !== pageItemDescriptor.id ||
|
|
1484
|
+
currentItemDescriptor?.language !== pageItemDescriptor.language ||
|
|
1485
|
+
currentItemDescriptor?.version !== pageItemDescriptor.version;
|
|
1486
|
+
const shouldUseIframeSrcReload = initialLoad || pageViewContext.fullscreen || iframeOriginChanged;
|
|
1487
|
+
function runFallbackRefresh() {
|
|
1488
|
+
pageViewContext.setIframeSupportsRefresh(false);
|
|
1489
|
+
setShowSpinner(true);
|
|
1490
|
+
console.log(initialLoad
|
|
1491
|
+
? "Initial load - setting iframe src"
|
|
1492
|
+
: renderUrlIsCrossOrigin
|
|
1493
|
+
? "Cross-origin load - setting iframe src"
|
|
1494
|
+
: iframeOriginChanged
|
|
1495
|
+
? "Iframe origin changed - setting iframe src"
|
|
1496
|
+
: "Reloading iframe src");
|
|
1497
|
+
setLoadedIframeSrc(undefined);
|
|
1498
|
+
setIframeSrc(renderUrl.toString());
|
|
1499
|
+
}
|
|
1500
|
+
function runBridgeRefresh() {
|
|
1501
|
+
const bridge = bridgeClientRef.current;
|
|
1502
|
+
if (!bridge?.supportsCapability("refresh")) {
|
|
1503
|
+
runFallbackRefresh();
|
|
1235
1504
|
return;
|
|
1236
1505
|
}
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
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;
|
|
1506
|
+
console.log("Bridge - requesting iframe refresh");
|
|
1507
|
+
pageViewContext.setIframeSupportsRefresh(true);
|
|
1508
|
+
const accepted = bridge.sendCommand("refresh", {
|
|
1509
|
+
url: renderUrl.toString(),
|
|
1510
|
+
revision: editContext.revision,
|
|
1511
|
+
layoutKind: editContext.layoutMode,
|
|
1512
|
+
});
|
|
1513
|
+
if (!accepted) {
|
|
1514
|
+
pageViewContext.setIframeSupportsRefresh(false);
|
|
1515
|
+
runFallbackRefresh();
|
|
1269
1516
|
}
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1517
|
+
}
|
|
1518
|
+
if (!shouldUseIframeSrcReload && !versionChanged) {
|
|
1519
|
+
if (bridgeClientRef.current?.supportsCapability("refresh")) {
|
|
1520
|
+
runBridgeRefresh();
|
|
1521
|
+
}
|
|
1522
|
+
else {
|
|
1523
|
+
const retryDelayMs = 150;
|
|
1524
|
+
const retryTimer = setTimeout(() => {
|
|
1525
|
+
runBridgeRefresh();
|
|
1526
|
+
}, retryDelayMs);
|
|
1527
|
+
setCurrentItemDescriptor(pageItemDescriptor);
|
|
1528
|
+
return () => clearTimeout(retryTimer);
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
else {
|
|
1532
|
+
runFallbackRefresh();
|
|
1533
|
+
}
|
|
1534
|
+
setCurrentItemDescriptor(pageItemDescriptor);
|
|
1535
|
+
}, [
|
|
1536
|
+
pathname,
|
|
1537
|
+
editContext.revision,
|
|
1538
|
+
pageItemDescriptor,
|
|
1539
|
+
pageViewContext.editUrl,
|
|
1540
|
+
pageViewContext.previewUrl,
|
|
1541
|
+
pageViewContext.fullscreen,
|
|
1542
|
+
editContext.mode,
|
|
1543
|
+
editContext.previewDate,
|
|
1544
|
+
editContext.sessionId,
|
|
1545
|
+
editContext.layoutMode,
|
|
1546
|
+
]);
|
|
1547
|
+
useEffect(() => {
|
|
1548
|
+
if (fieldsContext?.focusedField) {
|
|
1549
|
+
if (editContext.selection.length > 0 &&
|
|
1550
|
+
fieldsContext.focusedField.item.id !== editContext.selection[0])
|
|
1278
1551
|
return;
|
|
1279
|
-
|
|
1552
|
+
const bounds = findBridgeFieldDocumentBounds(pageViewContextRef.current?.bridgeGeometry, fieldsContext.focusedField);
|
|
1553
|
+
const activePageViewContext = pageViewContextRef.current;
|
|
1554
|
+
if (bounds && activePageViewContext) {
|
|
1555
|
+
scrollBridgeBoundsIntoView(activePageViewContext, bounds, latestBridgeScrollRef.current);
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
}, [fieldsContext?.focusedField]);
|
|
1559
|
+
useEffect(() => {
|
|
1560
|
+
const selectionKey = editContext.selection.join("|");
|
|
1561
|
+
const selectionChanged = selectionKey !== lastScrolledSelectionKeyRef.current;
|
|
1562
|
+
lastScrolledSelectionKeyRef.current = selectionKey;
|
|
1563
|
+
// The effect also re-runs when focusedField changes (e.g. a field blurs
|
|
1564
|
+
// after a click). Only the selection actually changing should drive an
|
|
1565
|
+
// auto-scroll; otherwise a blur re-render scrolls the just-clicked
|
|
1566
|
+
// component away.
|
|
1567
|
+
if (!selectionChanged)
|
|
1568
|
+
return;
|
|
1569
|
+
if (suppressNextSelectionScrollRef.current) {
|
|
1570
|
+
suppressNextSelectionScrollRef.current = false;
|
|
1571
|
+
return;
|
|
1572
|
+
}
|
|
1573
|
+
if (!fieldsContext?.focusedField && editContext.selection.length > 0) {
|
|
1574
|
+
const lastSelectedComponent = getComponentById(editContext.selection[editContext.selection.length - 1], pageViewContextRef.current.page);
|
|
1575
|
+
if (lastSelectedComponent) {
|
|
1576
|
+
editContext.setScrollIntoView(lastSelectedComponent.id);
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
}, [editContext.selection, fieldsContext?.focusedField]);
|
|
1580
|
+
useEffect(() => {
|
|
1581
|
+
if (!editContext.scrollIntoView)
|
|
1582
|
+
return;
|
|
1583
|
+
const activePageViewContext = pageViewContextRef.current;
|
|
1584
|
+
if (!activePageViewContext)
|
|
1585
|
+
return;
|
|
1586
|
+
const bounds = findBridgeComponentDocumentBounds(activePageViewContext.bridgeGeometry, editContext.scrollIntoView);
|
|
1587
|
+
if (bounds) {
|
|
1588
|
+
scrollBridgeBoundsIntoView(activePageViewContext, bounds, latestBridgeScrollRef.current);
|
|
1589
|
+
}
|
|
1590
|
+
editContext.setScrollIntoView(undefined);
|
|
1591
|
+
}, [editContext.scrollIntoView, pageViewContext.page]);
|
|
1592
|
+
useEffect(() => {
|
|
1593
|
+
const handleMessage = (message) => {
|
|
1594
|
+
if (message.origin !== window.location.origin)
|
|
1280
1595
|
return;
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
let componentId = findNearestEditableComponentId(target);
|
|
1284
|
-
const pageForContextMenu = pageViewContextRef.current?.page;
|
|
1285
|
-
if (componentId && pageForContextMenu) {
|
|
1286
|
-
componentId = resolveComponentIdForTarget(componentId, target, pageForContextMenu);
|
|
1596
|
+
if (message.data.type === "editor-exitFullscreen") {
|
|
1597
|
+
pageViewContext.setFullscreen(false);
|
|
1287
1598
|
}
|
|
1288
|
-
|
|
1289
|
-
|
|
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
|
|
1599
|
+
if (message.data.type === "editor-timings") {
|
|
1600
|
+
editContext.setTimings(message.data.timings);
|
|
1297
1601
|
}
|
|
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
|
-
const iframeRect = iframe.getBoundingClientRect();
|
|
1305
|
-
const adjustedEvent = new MouseEvent("contextmenu", {
|
|
1306
|
-
bubbles: true,
|
|
1307
|
-
cancelable: true,
|
|
1308
|
-
shiftKey: event.shiftKey,
|
|
1309
|
-
altKey: event.altKey,
|
|
1310
|
-
ctrlKey: event.ctrlKey,
|
|
1311
|
-
view: window,
|
|
1312
|
-
clientX: event.clientX + iframeRect.x,
|
|
1313
|
-
clientY: event.clientY + iframeRect.y,
|
|
1314
|
-
});
|
|
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
|
-
setContextMenuPosition(menuPosition);
|
|
1321
|
-
// Only show a spinner if work takes longer than 100ms
|
|
1322
|
-
let loadingShown = false;
|
|
1323
|
-
const loadingTimer = setTimeout(() => {
|
|
1324
|
-
loadingShown = true;
|
|
1325
|
-
editContextRef.current?.showContextMenu(adjustedEvent, [
|
|
1326
|
-
{
|
|
1327
|
-
id: "loading",
|
|
1328
|
-
label: "Loading…",
|
|
1329
|
-
disabled: true,
|
|
1330
|
-
icon: _jsx(Spinner, { size: "sm", className: "mr-2" }),
|
|
1331
|
-
},
|
|
1332
|
-
]);
|
|
1333
|
-
}, 100);
|
|
1334
|
-
const field = fieldElement
|
|
1335
|
-
? getFieldDescriptorFromElement(fieldElement)
|
|
1336
|
-
: undefined;
|
|
1337
|
-
const fieldButtons = field ? await loadFieldButtons(field) : [];
|
|
1338
|
-
// Create a handler that has access to the current menu position
|
|
1339
|
-
const handleParameterizedActionWithPosition = (field, action, allFieldButtons, event) => {
|
|
1340
|
-
setContextMenuField(field);
|
|
1341
|
-
setContextMenuFieldButtons([action]);
|
|
1342
|
-
setPreSelectedAction(action);
|
|
1343
|
-
// Create a new MouseEvent with the captured position
|
|
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
|
|
1371
|
-
const tempElement = document.createElement("div");
|
|
1372
|
-
tempElement.style.position = "fixed";
|
|
1373
|
-
tempElement.style.left = menuPosition.x + "px";
|
|
1374
|
-
tempElement.style.top = menuPosition.y + "px";
|
|
1375
|
-
tempElement.style.width = "1px";
|
|
1376
|
-
tempElement.style.height = "1px";
|
|
1377
|
-
tempElement.style.visibility = "hidden";
|
|
1378
|
-
tempElement.style.pointerEvents = "none";
|
|
1379
|
-
document.body.appendChild(tempElement);
|
|
1380
|
-
// Create event targeting the positioned element
|
|
1381
|
-
const positionedEvent = new MouseEvent("click", {
|
|
1382
|
-
bubbles: true,
|
|
1383
|
-
cancelable: true,
|
|
1384
|
-
view: window,
|
|
1385
|
-
clientX: menuPosition.x,
|
|
1386
|
-
clientY: menuPosition.y,
|
|
1387
|
-
});
|
|
1388
|
-
Object.defineProperty(positionedEvent, "target", {
|
|
1389
|
-
value: tempElement,
|
|
1390
|
-
enumerable: true,
|
|
1391
|
-
});
|
|
1392
|
-
fieldActionsOverlay.current?.show(positionedEvent, action);
|
|
1393
|
-
// Clean up the temporary element after overlay is shown
|
|
1394
|
-
setTimeout(() => {
|
|
1395
|
-
document.body.removeChild(tempElement);
|
|
1396
|
-
}, 1000);
|
|
1397
|
-
}, 100);
|
|
1398
|
-
};
|
|
1399
|
-
const items = await buildComponentContextMenuItems(selectedComponents, editContextRef.current, field, fieldButtons, handleParameterizedActionWithPosition);
|
|
1400
|
-
clearTimeout(loadingTimer);
|
|
1401
|
-
if (loadingShown)
|
|
1402
|
-
editContextRef.current?.updateContextMenu(items);
|
|
1403
|
-
else
|
|
1404
|
-
editContextRef.current?.showContextMenu(adjustedEvent, items);
|
|
1405
1602
|
};
|
|
1603
|
+
window.addEventListener("message", handleMessage);
|
|
1604
|
+
return () => {
|
|
1605
|
+
window.removeEventListener("message", handleMessage);
|
|
1606
|
+
};
|
|
1607
|
+
}, []);
|
|
1608
|
+
useEffect(() => {
|
|
1609
|
+
const iframe = iframeRef.current;
|
|
1610
|
+
if (!iframe)
|
|
1611
|
+
return;
|
|
1406
1612
|
const handleLoad = () => {
|
|
1407
|
-
// Skip handling if this load does not correspond to the latest requested revision
|
|
1408
|
-
const expectedRevision = currentLoadRef.current?.revision ?? "";
|
|
1409
|
-
const currentRevision = editContextRef.current?.revision ?? "";
|
|
1410
|
-
if (expectedRevision && expectedRevision !== currentRevision) {
|
|
1411
|
-
console.log("Stale load skipped", {
|
|
1412
|
-
expectedRevision,
|
|
1413
|
-
currentRevision,
|
|
1414
|
-
});
|
|
1415
|
-
return;
|
|
1416
|
-
}
|
|
1417
1613
|
setShowSpinner(false);
|
|
1418
1614
|
applyIframeZoom(iframe, pageViewContextRef.current?.zoom ?? 1, "PageViewerFrame.tsx:handleLoad-zoom");
|
|
1419
|
-
attachListeners();
|
|
1420
1615
|
};
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1616
|
+
iframe.addEventListener("load", handleLoad);
|
|
1617
|
+
return () => {
|
|
1618
|
+
iframe.removeEventListener("load", handleLoad);
|
|
1619
|
+
};
|
|
1620
|
+
}, [iframeElement]);
|
|
1621
|
+
useEffect(() => {
|
|
1622
|
+
const iframe = iframeRef.current;
|
|
1623
|
+
if (!iframe || typeof ResizeObserver === "undefined")
|
|
1624
|
+
return;
|
|
1625
|
+
let lastWidth = iframe.clientWidth;
|
|
1626
|
+
let lastHeight = iframe.clientHeight;
|
|
1627
|
+
let geometryTimer = null;
|
|
1628
|
+
let trailingGeometryTimer = null;
|
|
1629
|
+
const requestGeometry = () => {
|
|
1630
|
+
geometryTimer = null;
|
|
1631
|
+
const nextWidth = iframe.clientWidth;
|
|
1632
|
+
const nextHeight = iframe.clientHeight;
|
|
1633
|
+
if (nextWidth === lastWidth && nextHeight === lastHeight)
|
|
1634
|
+
return;
|
|
1635
|
+
lastWidth = nextWidth;
|
|
1636
|
+
lastHeight = nextHeight;
|
|
1637
|
+
bridgeClientRef.current?.sendCommand("queryGeometry", {});
|
|
1638
|
+
};
|
|
1639
|
+
const scheduleGeometryRequest = () => {
|
|
1640
|
+
if (geometryTimer != null) {
|
|
1641
|
+
window.clearTimeout(geometryTimer);
|
|
1428
1642
|
}
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1643
|
+
if (trailingGeometryTimer != null) {
|
|
1644
|
+
window.clearTimeout(trailingGeometryTimer);
|
|
1645
|
+
}
|
|
1646
|
+
geometryTimer = window.setTimeout(requestGeometry, 50);
|
|
1647
|
+
trailingGeometryTimer = window.setTimeout(() => {
|
|
1648
|
+
trailingGeometryTimer = null;
|
|
1649
|
+
requestGeometry();
|
|
1650
|
+
}, ZOOM_TRANSITION_MS + 75);
|
|
1651
|
+
};
|
|
1652
|
+
const resizeObserver = new ResizeObserver(scheduleGeometryRequest);
|
|
1653
|
+
resizeObserver.observe(iframe);
|
|
1432
1654
|
return () => {
|
|
1433
|
-
|
|
1434
|
-
if (
|
|
1435
|
-
|
|
1655
|
+
resizeObserver.disconnect();
|
|
1656
|
+
if (geometryTimer != null) {
|
|
1657
|
+
window.clearTimeout(geometryTimer);
|
|
1658
|
+
}
|
|
1659
|
+
if (trailingGeometryTimer != null) {
|
|
1660
|
+
window.clearTimeout(trailingGeometryTimer);
|
|
1436
1661
|
}
|
|
1437
|
-
detachListeners();
|
|
1438
1662
|
};
|
|
1439
|
-
}, [
|
|
1663
|
+
}, [iframeElement]);
|
|
1440
1664
|
useEffect(() => {
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
}
|
|
1444
|
-
catch { }
|
|
1665
|
+
bridgeClientRef.current?.sendCommand("setSelection", {
|
|
1666
|
+
componentIds: editContext.selection,
|
|
1667
|
+
});
|
|
1445
1668
|
}, [editContext.selection]);
|
|
1446
|
-
|
|
1447
|
-
const
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1669
|
+
useEffect(() => {
|
|
1670
|
+
const bridge = bridgeClientRef.current;
|
|
1671
|
+
bridge?.sendCommand("setPreviewMode", {
|
|
1672
|
+
enabled: editContext.mode === "preview",
|
|
1673
|
+
});
|
|
1674
|
+
bridge?.sendCommand("setEditorMode", {
|
|
1675
|
+
mode: editContext.mode,
|
|
1676
|
+
});
|
|
1677
|
+
}, [editContext.mode]);
|
|
1678
|
+
useEffect(() => {
|
|
1679
|
+
bridgeClientRef.current?.sendCommand("setLayoutKind", {
|
|
1680
|
+
layoutKind: editContext.layoutMode,
|
|
1681
|
+
});
|
|
1682
|
+
}, [editContext.layoutMode]);
|
|
1457
1683
|
useEffect(() => {
|
|
1458
1684
|
applyIframeZoom(iframeRef.current, zoom, "PageViewerFrame.tsx:zoom-doc");
|
|
1685
|
+
bridgeClientRef.current?.sendCommand("setZoom", { zoom });
|
|
1686
|
+
const geometryTimer = window.setTimeout(() => {
|
|
1687
|
+
bridgeClientRef.current?.sendCommand("queryGeometry", {});
|
|
1688
|
+
}, ZOOM_TRANSITION_MS + 75);
|
|
1689
|
+
return () => {
|
|
1690
|
+
window.clearTimeout(geometryTimer);
|
|
1691
|
+
};
|
|
1692
|
+
}, [zoom]);
|
|
1693
|
+
useEffect(() => {
|
|
1694
|
+
let geometryTimer = null;
|
|
1695
|
+
const sendViewportStateToBridge = (event) => {
|
|
1696
|
+
const detail = event.detail;
|
|
1697
|
+
const nextZoom = detail?.zoom ?? pageViewContextRef.current?.zoom ?? zoom;
|
|
1698
|
+
bridgeClientRef.current?.sendCommand("setZoom", {
|
|
1699
|
+
zoom: nextZoom,
|
|
1700
|
+
});
|
|
1701
|
+
if (geometryTimer != null) {
|
|
1702
|
+
window.clearTimeout(geometryTimer);
|
|
1703
|
+
}
|
|
1704
|
+
geometryTimer = window.setTimeout(() => {
|
|
1705
|
+
geometryTimer = null;
|
|
1706
|
+
bridgeClientRef.current?.sendCommand("queryGeometry", {});
|
|
1707
|
+
}, ZOOM_TRANSITION_MS + 75);
|
|
1708
|
+
};
|
|
1709
|
+
window.addEventListener(DEVICE_CHANGE_EVENT, sendViewportStateToBridge);
|
|
1710
|
+
return () => {
|
|
1711
|
+
if (geometryTimer != null) {
|
|
1712
|
+
window.clearTimeout(geometryTimer);
|
|
1713
|
+
}
|
|
1714
|
+
window.removeEventListener(DEVICE_CHANGE_EVENT, sendViewportStateToBridge);
|
|
1715
|
+
};
|
|
1459
1716
|
}, [zoom]);
|
|
1460
|
-
const scrollHandler = useThrottledCallback(updateScrollPosition, 100);
|
|
1461
1717
|
if (pageViewContext.page?.item && !pageViewContext.page?.item.hasLayout) {
|
|
1462
1718
|
return _jsx(NoLayout, {});
|
|
1463
1719
|
}
|
|
@@ -1473,265 +1729,27 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
1473
1729
|
"px";
|
|
1474
1730
|
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
1731
|
width: deviceWidth,
|
|
1476
|
-
}, children: [_jsxs("div", { className: "relative h-full w-full overflow-hidden", children: [_jsx("iframe", { ref:
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1732
|
+
}, 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: () => {
|
|
1733
|
+
const loadedSrc = iframeRef.current?.src;
|
|
1734
|
+
if (loadedSrc) {
|
|
1735
|
+
setLoadedIframeSrc(loadedSrc);
|
|
1736
|
+
}
|
|
1737
|
+
const sendZoomToBridge = () => {
|
|
1738
|
+
bridgeClientRef.current?.sendCommand("setZoom", {
|
|
1739
|
+
zoom: pageViewContextRef.current?.zoom ?? zoom,
|
|
1740
|
+
});
|
|
1741
|
+
};
|
|
1742
|
+
sendZoomToBridge();
|
|
1743
|
+
window.setTimeout(sendZoomToBridge, 100);
|
|
1744
|
+
window.setTimeout(sendZoomToBridge, 500);
|
|
1745
|
+
if (iframeSrc) {
|
|
1480
1746
|
setShowSpinner(false);
|
|
1481
|
-
ensureEditorCssGuard(doc, editContext.mode !== "preview");
|
|
1482
1747
|
applyIframeZoom(iframeRef.current, zoom, "PageViewerFrame.tsx:onLoad-zoom");
|
|
1483
|
-
setTimeout(() => {
|
|
1484
|
-
injectSXAScripts(iframeRef.current);
|
|
1485
|
-
}, 1000);
|
|
1486
|
-
requestPageModelBuild(doc);
|
|
1487
1748
|
}
|
|
1488
|
-
} }),
|
|
1749
|
+
} }), 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
1750
|
showMiniMap &&
|
|
1490
1751
|
editContext.showMinimap &&
|
|
1491
1752
|
!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;
|
|
1753
|
+
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
1754
|
}
|
|
1737
1755
|
//# sourceMappingURL=PageViewerFrame.js.map
|