@parhelia/core 0.1.12868 → 0.1.12875
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 +22 -2
- package/dist/config/config.js.map +1 -1
- package/dist/config/types.d.ts +0 -1
- 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 +15 -15
- 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 +29 -13
- 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/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 +130 -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 +416 -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 +286 -126
- package/dist/editor/client/EditorShell.js.map +1 -1
- package/dist/editor/client/editContext.d.ts +37 -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 -7
- package/dist/editor/menubar/toolbar-sections/ManualBrowser.js +23 -198
- 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 +1525 -1507
- 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/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 +14 -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/BridgeDiagnostics.d.ts +1 -0
- package/dist/editor/sidebar/BridgeDiagnostics.js +41 -0
- package/dist/editor/sidebar/BridgeDiagnostics.js.map +1 -0
- 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,36 @@ 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);
|
|
167
314
|
const [scroll, setScroll] = useState(0);
|
|
168
315
|
const [showMiniMap, setShowMiniMap] = useState(false);
|
|
316
|
+
const [bridgeGeometryRevision, setBridgeGeometryRevision] = useState(0);
|
|
317
|
+
const [bridgeDomRevision, setBridgeDomRevision] = useState(0);
|
|
318
|
+
const bridgeGeometryRevisionRafRef = useRef(null);
|
|
319
|
+
const bridgeDomRevisionRafRef = useRef(null);
|
|
169
320
|
const fieldActionsOverlay = useRef(null);
|
|
321
|
+
const lastSentBridgeCaretRef = useRef(null);
|
|
170
322
|
const [contextMenuFieldButtons, setContextMenuFieldButtons] = useState([]);
|
|
171
323
|
const [contextMenuField, setContextMenuField] = useState();
|
|
172
324
|
const [contextMenuPosition, setContextMenuPosition] = useState();
|
|
173
325
|
const [preSelectedAction, setPreSelectedAction] = useState();
|
|
326
|
+
const bindIframeRef = useCallback((node) => {
|
|
327
|
+
iframeRef.current = node;
|
|
328
|
+
setIframeElement(node);
|
|
329
|
+
}, []);
|
|
174
330
|
// Clear preSelectedAction when overlay is closed
|
|
175
331
|
useEffect(() => {
|
|
176
332
|
if (editContext?.currentOverlay !==
|
|
@@ -179,7 +335,6 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
179
335
|
}
|
|
180
336
|
}, [editContext?.currentOverlay, contextMenuField?.fieldId]);
|
|
181
337
|
const zoom = pageViewContext.zoom;
|
|
182
|
-
const blockBlurEventRef = useRef(0);
|
|
183
338
|
const [currentItemDescriptor, setCurrentItemDescriptor] = useState(undefined);
|
|
184
339
|
// Field action handlers for the overlay
|
|
185
340
|
const handleActionClick = async (action, event) => {
|
|
@@ -195,1264 +350,1369 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
195
350
|
// Note: handleParameterizedActionFromContextMenu is now created inline in handleContextMenu
|
|
196
351
|
// to avoid React state timing issues with contextMenuPosition
|
|
197
352
|
const pageItemDescriptor = pageViewContext.pageItemDescriptor;
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
353
|
+
const shouldTrackMinimapScroll = useCallback(() => {
|
|
354
|
+
const editor = editContextRef.current;
|
|
355
|
+
return !!(showMiniMap &&
|
|
356
|
+
editor?.showMinimap &&
|
|
357
|
+
!editor?.isMobile &&
|
|
358
|
+
editor?.parheliaSettings?.showMinimap !== false);
|
|
359
|
+
}, [showMiniMap]);
|
|
360
|
+
const updateScrollPosition = useCallback((e) => {
|
|
361
|
+
if (!shouldTrackMinimapScroll())
|
|
204
362
|
return;
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
363
|
+
setScroll(e);
|
|
364
|
+
if (!compareView)
|
|
365
|
+
pageViewContextRef.current?.setScroll(e);
|
|
366
|
+
}, [compareView, shouldTrackMinimapScroll]);
|
|
367
|
+
const scrollHandler = useThrottledCallback(updateScrollPosition, 100);
|
|
368
|
+
const scrollHandlerRef = useRef(scrollHandler);
|
|
369
|
+
useEffect(() => {
|
|
370
|
+
scrollHandlerRef.current = scrollHandler;
|
|
371
|
+
}, [scrollHandler]);
|
|
372
|
+
const scheduleBridgeGeometryRevision = useCallback(() => {
|
|
373
|
+
if (bridgeGeometryRevisionRafRef.current != null)
|
|
208
374
|
return;
|
|
209
|
-
|
|
210
|
-
|
|
375
|
+
bridgeGeometryRevisionRafRef.current = window.requestAnimationFrame(() => {
|
|
376
|
+
bridgeGeometryRevisionRafRef.current = null;
|
|
377
|
+
setBridgeGeometryRevision((revision) => revision + 1);
|
|
378
|
+
});
|
|
379
|
+
}, []);
|
|
380
|
+
const scheduleBridgeDomRevision = useCallback(() => {
|
|
381
|
+
if (bridgeDomRevisionRafRef.current != null)
|
|
211
382
|
return;
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
setShowMiniMap(false);
|
|
383
|
+
bridgeDomRevisionRafRef.current = window.requestAnimationFrame(() => {
|
|
384
|
+
bridgeDomRevisionRafRef.current = null;
|
|
385
|
+
setBridgeDomRevision((revision) => revision + 1);
|
|
386
|
+
});
|
|
387
|
+
}, []);
|
|
388
|
+
useEffect(() => {
|
|
389
|
+
return () => {
|
|
390
|
+
if (bridgeGeometryRevisionRafRef.current != null) {
|
|
391
|
+
window.cancelAnimationFrame(bridgeGeometryRevisionRafRef.current);
|
|
222
392
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
if (contentHeight > upperThreshold && minimapEnabled) {
|
|
226
|
-
setShowMiniMap(true);
|
|
393
|
+
if (bridgeDomRevisionRafRef.current != null) {
|
|
394
|
+
window.cancelAnimationFrame(bridgeDomRevisionRafRef.current);
|
|
227
395
|
}
|
|
228
|
-
}
|
|
229
|
-
},
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
396
|
+
};
|
|
397
|
+
}, []);
|
|
398
|
+
const blockIframeBlurUntil = useCallback((_timestamp) => {
|
|
399
|
+
// Iframe blur is handled by the bridge inline-edit lifecycle now.
|
|
400
|
+
}, []);
|
|
401
|
+
const sendBeginInlineEdit = useCallback((payload) => {
|
|
402
|
+
return (bridgeClientRef.current?.sendCommand("beginInlineEdit", payload) ??
|
|
403
|
+
false);
|
|
404
|
+
}, []);
|
|
405
|
+
const sendApplyRichTextCommand = useCallback((payload) => {
|
|
406
|
+
return (bridgeClientRef.current?.sendCommand("applyRichTextCommand", payload) ??
|
|
407
|
+
false);
|
|
408
|
+
}, []);
|
|
409
|
+
const { beginInlineEdit: beginBridgeInlineEdit, clearInlineDedupe: clearBridgeInlineDedupe, endFieldFocus: endBridgeFieldFocus, handleFieldValueChanged: handleBridgeFieldValueChanged, handleInlineEditEnded: handleBridgeInlineEditEnded, } = useBridgeInlineEditing({
|
|
410
|
+
pageViewContextRef,
|
|
411
|
+
activeBridgeInlineEditRef,
|
|
412
|
+
blockIframeBlurUntil,
|
|
413
|
+
sendBeginInlineEdit,
|
|
234
414
|
});
|
|
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);
|
|
272
|
-
}
|
|
273
|
-
finally {
|
|
274
|
-
isApplying = false;
|
|
415
|
+
const beginTrackedBridgeInlineEdit = useCallback(async (interaction) => {
|
|
416
|
+
activeBridgeInlineEditRef.current = null;
|
|
417
|
+
const started = await beginBridgeInlineEdit(interaction);
|
|
418
|
+
if (started && interaction.elementKey && interaction.fieldId) {
|
|
419
|
+
const interactionItem = interaction.item?.id &&
|
|
420
|
+
interaction.item.language &&
|
|
421
|
+
typeof interaction.item.version === "number"
|
|
422
|
+
? {
|
|
423
|
+
id: interaction.item.id,
|
|
424
|
+
language: interaction.item.language,
|
|
425
|
+
version: interaction.item.version,
|
|
426
|
+
name: interaction.item.name,
|
|
427
|
+
displayName: interaction.item.displayName,
|
|
428
|
+
path: interaction.item.path,
|
|
429
|
+
database: interaction.item.database,
|
|
275
430
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
}
|
|
284
|
-
catch { }
|
|
285
|
-
editorCssGuardRef.current.observer = observer;
|
|
431
|
+
: undefined;
|
|
432
|
+
activeBridgeInlineEditRef.current = {
|
|
433
|
+
elementKey: interaction.elementKey,
|
|
434
|
+
fieldId: interaction.fieldId,
|
|
435
|
+
item: interactionItem,
|
|
436
|
+
modeAtStart: editContextRef.current?.mode ?? editContext.mode,
|
|
437
|
+
};
|
|
286
438
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
}, 0);
|
|
294
|
-
};
|
|
295
|
-
// Disconnect the CSS guard on unmount.
|
|
439
|
+
return started;
|
|
440
|
+
}, [beginBridgeInlineEdit, editContext.mode, editContextRef]);
|
|
441
|
+
const endTrackedBridgeFieldFocus = useCallback(() => {
|
|
442
|
+
activeBridgeInlineEditRef.current = null;
|
|
443
|
+
endBridgeFieldFocus();
|
|
444
|
+
}, [endBridgeFieldFocus]);
|
|
296
445
|
useEffect(() => {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
446
|
+
const handleBridgeInlineEditRelease = (event) => {
|
|
447
|
+
const detail = event
|
|
448
|
+
.detail;
|
|
449
|
+
const field = detail?.field;
|
|
450
|
+
const activeEdit = activeBridgeInlineEditRef.current;
|
|
451
|
+
if (!field || !activeEdit)
|
|
452
|
+
return;
|
|
453
|
+
if (!bridgeKeysMatch(activeEdit.fieldId, field.fieldId))
|
|
454
|
+
return;
|
|
455
|
+
if (activeEdit.item && !bridgeItemMatches(activeEdit.item, field.item)) {
|
|
456
|
+
return;
|
|
300
457
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
458
|
+
activeBridgeInlineEditRef.current = null;
|
|
459
|
+
bridgePatchSignatureRef.current.delete(`${activeEdit.elementKey}:${activeEdit.fieldId}`);
|
|
460
|
+
};
|
|
461
|
+
window.addEventListener(BRIDGE_INLINE_EDIT_RELEASE_EVENT, handleBridgeInlineEditRelease);
|
|
462
|
+
return () => {
|
|
463
|
+
window.removeEventListener(BRIDGE_INLINE_EDIT_RELEASE_EVENT, handleBridgeInlineEditRelease);
|
|
304
464
|
};
|
|
305
465
|
}, []);
|
|
306
|
-
// If the editor mode flips (edit/preview), re-apply CSS and disable inline editing in the iframe.
|
|
307
466
|
useEffect(() => {
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
467
|
+
return editContext.registerModeChangeParticipant({
|
|
468
|
+
beforeModeChange: () => {
|
|
469
|
+
editContext.operations.onFieldBlur?.();
|
|
470
|
+
void fieldsContextRef.current?.setFocusedField(undefined, false);
|
|
471
|
+
endBridgeFieldFocus();
|
|
472
|
+
},
|
|
473
|
+
clearInlineDedupe: () => {
|
|
474
|
+
clearBridgeInlineDedupe();
|
|
475
|
+
},
|
|
476
|
+
});
|
|
477
|
+
}, [
|
|
478
|
+
clearBridgeInlineDedupe,
|
|
479
|
+
editContext,
|
|
480
|
+
endBridgeFieldFocus,
|
|
481
|
+
fieldsContextRef,
|
|
482
|
+
]);
|
|
483
|
+
const getBridgePatchDisplayValue = useCallback(({ field, structureField, itemDescriptor, preferRepositoryValue, }) => {
|
|
484
|
+
const showSuggestions = editContext.mode === "suggestions" || editContext.showSuggestedEdits;
|
|
485
|
+
const repositoryValue = getBridgeFieldPatchValue(field);
|
|
486
|
+
const display = getSuggestionDisplayValue({
|
|
487
|
+
repositoryValue,
|
|
488
|
+
modifiedFields: preferRepositoryValue
|
|
489
|
+
? undefined
|
|
490
|
+
: fieldsContext?.modifiedFields,
|
|
491
|
+
suggestedEdits: editContext.suggestedEdits,
|
|
492
|
+
field: {
|
|
493
|
+
fieldId: field.id || structureField.fieldId,
|
|
494
|
+
itemId: itemDescriptor.id,
|
|
495
|
+
language: itemDescriptor.language,
|
|
496
|
+
version: itemDescriptor.version,
|
|
497
|
+
pageItemId: pageViewContext.pageItemDescriptor?.id,
|
|
498
|
+
pageItemVersion: pageViewContext.pageItemDescriptor?.version,
|
|
499
|
+
},
|
|
500
|
+
mode: showSuggestions ? "suggestions" : "baseline",
|
|
501
|
+
});
|
|
502
|
+
return {
|
|
503
|
+
value: showSuggestions ? display.mergedValue : display.baselineValue,
|
|
504
|
+
source: showSuggestions && display.hasSuggestions
|
|
505
|
+
? "suggestion-preview"
|
|
506
|
+
: "real",
|
|
507
|
+
};
|
|
508
|
+
}, [
|
|
509
|
+
editContext.mode,
|
|
510
|
+
editContext.showSuggestedEdits,
|
|
511
|
+
editContext.suggestedEdits,
|
|
512
|
+
fieldsContext?.modifiedFields,
|
|
513
|
+
pageViewContext.pageItemDescriptor,
|
|
514
|
+
]);
|
|
515
|
+
const buildBridgeFieldPatch = useCallback(({ field, structureField, itemDescriptor, preferRepositoryValue, }) => {
|
|
516
|
+
const display = getBridgePatchDisplayValue({
|
|
517
|
+
field,
|
|
518
|
+
structureField,
|
|
519
|
+
itemDescriptor,
|
|
520
|
+
preferRepositoryValue,
|
|
521
|
+
});
|
|
522
|
+
const activeInlineEdit = activeBridgeInlineEditRef.current;
|
|
523
|
+
const patch = buildBridgeFieldPatchPayload({
|
|
524
|
+
field,
|
|
525
|
+
structureField,
|
|
526
|
+
display,
|
|
527
|
+
activeInlineEdit,
|
|
528
|
+
});
|
|
529
|
+
return patch;
|
|
530
|
+
}, [getBridgePatchDisplayValue]);
|
|
531
|
+
const clearBridgePatchSignatures = useCallback(() => {
|
|
532
|
+
bridgePatchSignatureRef.current.clear();
|
|
533
|
+
}, []);
|
|
534
|
+
const sendBridgeFieldPatch = useCallback((bridge, patch, observedTextContent) => {
|
|
535
|
+
const cacheKey = `${patch.elementKey}:${patch.fieldId}`;
|
|
536
|
+
const signature = JSON.stringify({
|
|
537
|
+
value: patch.value,
|
|
538
|
+
isRichText: !!patch.isRichText,
|
|
539
|
+
source: patch.source ?? "",
|
|
540
|
+
observedTextContent: observedTextContent ?? "",
|
|
541
|
+
});
|
|
542
|
+
if (bridgePatchSignatureRef.current.get(cacheKey) === signature) {
|
|
543
|
+
return false;
|
|
315
544
|
}
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
545
|
+
bridgePatchSignatureRef.current.set(cacheKey, signature);
|
|
546
|
+
bridge.sendCommand("applyFieldPatch", patch);
|
|
547
|
+
return true;
|
|
548
|
+
}, []);
|
|
549
|
+
const clearBridgeRemoteCaretPosition = useCallback(() => {
|
|
550
|
+
const editor = editContextRef.current;
|
|
551
|
+
if (!editor || lastSentBridgeCaretRef.current === "clear")
|
|
552
|
+
return;
|
|
553
|
+
editor.sendSocketMessage({
|
|
554
|
+
type: "caret-position",
|
|
555
|
+
payload: { offset: null },
|
|
556
|
+
});
|
|
557
|
+
lastSentBridgeCaretRef.current = "clear";
|
|
558
|
+
}, [editContextRef]);
|
|
559
|
+
const sendBridgeRemoteCaretPosition = useCallback((selection) => {
|
|
560
|
+
const editor = editContextRef.current;
|
|
561
|
+
const activeField = selection.activeField;
|
|
562
|
+
const offset = selection.startOffset ?? selection.endOffset;
|
|
563
|
+
if (!editor ||
|
|
564
|
+
!activeField?.fieldId ||
|
|
565
|
+
!selection.collapsed ||
|
|
566
|
+
offset == null) {
|
|
567
|
+
clearBridgeRemoteCaretPosition();
|
|
321
568
|
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
569
|
}
|
|
354
|
-
|
|
355
|
-
|
|
570
|
+
const sourceItem = activeField.item ??
|
|
571
|
+
pageViewContextRef.current?.page?.item ??
|
|
572
|
+
pageViewContextRef.current?.pageItemDescriptor;
|
|
573
|
+
const version = typeof sourceItem?.version === "number" &&
|
|
574
|
+
Number.isFinite(sourceItem.version)
|
|
575
|
+
? sourceItem.version
|
|
576
|
+
: undefined;
|
|
577
|
+
if (!sourceItem?.id || !sourceItem.language || version === undefined) {
|
|
578
|
+
clearBridgeRemoteCaretPosition();
|
|
579
|
+
return;
|
|
356
580
|
}
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
581
|
+
const item = {
|
|
582
|
+
...sourceItem,
|
|
583
|
+
version,
|
|
584
|
+
};
|
|
585
|
+
const nextKey = `${activeField.fieldId}:${item.id}:${item.language}:${item.version}:${offset}`;
|
|
586
|
+
if (lastSentBridgeCaretRef.current === nextKey)
|
|
587
|
+
return;
|
|
588
|
+
editor.sendSocketMessage({
|
|
589
|
+
type: "caret-position",
|
|
590
|
+
payload: {
|
|
591
|
+
fieldId: activeField.fieldId,
|
|
592
|
+
item,
|
|
593
|
+
offset,
|
|
594
|
+
},
|
|
595
|
+
});
|
|
596
|
+
lastSentBridgeCaretRef.current = nextKey;
|
|
597
|
+
}, [clearBridgeRemoteCaretPosition, editContextRef, pageViewContextRef]);
|
|
598
|
+
const handleBridgeSelection = useCallback((selection, iframe) => {
|
|
599
|
+
const editor = editContextRef.current;
|
|
600
|
+
if (!editor)
|
|
601
|
+
return;
|
|
602
|
+
const isInlineAiUiFocused = () => {
|
|
603
|
+
const activeEl = document.activeElement;
|
|
604
|
+
return !!(activeEl?.closest(".agent-inline-dialog") ||
|
|
605
|
+
activeEl?.closest(".agent-inline-trigger") ||
|
|
606
|
+
activeEl?.closest('[role="dialog"]'));
|
|
607
|
+
};
|
|
608
|
+
if (selection.collapsed ||
|
|
609
|
+
!selection.text ||
|
|
610
|
+
!selection.activeField?.fieldId) {
|
|
611
|
+
sendBridgeRemoteCaretPosition(selection);
|
|
612
|
+
if (!isInlineAiUiFocused()) {
|
|
613
|
+
editor.setSelectedRange(undefined);
|
|
614
|
+
}
|
|
615
|
+
return;
|
|
362
616
|
}
|
|
363
|
-
|
|
364
|
-
|
|
617
|
+
clearBridgeRemoteCaretPosition();
|
|
618
|
+
const item = selection.activeField.item ??
|
|
619
|
+
pageViewContextRef.current?.page?.item ??
|
|
620
|
+
pageViewContextRef.current?.pageItemDescriptor;
|
|
621
|
+
if (!item?.id) {
|
|
622
|
+
editor.setSelectedRange(undefined);
|
|
623
|
+
return;
|
|
365
624
|
}
|
|
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;
|
|
625
|
+
const metadata = selection.metadata ?? {};
|
|
626
|
+
const iframeRect = iframe.getBoundingClientRect();
|
|
627
|
+
const selectionRect = selection.rect
|
|
628
|
+
? {
|
|
629
|
+
x: iframeRect.left + selection.rect.left,
|
|
630
|
+
y: iframeRect.top + selection.rect.top,
|
|
631
|
+
width: selection.rect.width,
|
|
632
|
+
height: selection.rect.height,
|
|
633
|
+
top: iframeRect.top + selection.rect.top,
|
|
634
|
+
right: iframeRect.left + selection.rect.right,
|
|
635
|
+
bottom: iframeRect.top + selection.rect.bottom,
|
|
636
|
+
left: iframeRect.left + selection.rect.left,
|
|
382
637
|
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
638
|
+
: undefined;
|
|
639
|
+
const nextRange = {
|
|
640
|
+
itemId: item.id,
|
|
641
|
+
fieldId: selection.activeField.fieldId,
|
|
642
|
+
elementKey: selection.activeField.elementKey,
|
|
643
|
+
isRichText: selection.activeField.isRichText,
|
|
644
|
+
language: item.language,
|
|
645
|
+
version: item.version,
|
|
646
|
+
startOffset: selection.startOffset ?? 0,
|
|
647
|
+
endOffset: selection.endOffset ?? selection.startOffset ?? 0,
|
|
648
|
+
text: selection.text,
|
|
649
|
+
contextBefore: typeof metadata.contextBefore === "string"
|
|
650
|
+
? metadata.contextBefore
|
|
651
|
+
: undefined,
|
|
652
|
+
contextAfter: typeof metadata.contextAfter === "string"
|
|
653
|
+
? metadata.contextAfter
|
|
654
|
+
: undefined,
|
|
655
|
+
clientRect: selectionRect,
|
|
656
|
+
};
|
|
657
|
+
editor.setSelectedRange(nextRange);
|
|
658
|
+
}, [clearBridgeRemoteCaretPosition, sendBridgeRemoteCaretPosition]);
|
|
659
|
+
const resolveBridgeFieldDescriptor = useCallback(async (interaction) => {
|
|
660
|
+
const pageView = pageViewContextRef.current;
|
|
661
|
+
const selectedRange = editContextRef.current?.selectedRange;
|
|
662
|
+
const selectedRangeElementKey = selectedRange?.elementKey;
|
|
663
|
+
const selectedRangeMatchesInteraction = !!((interaction.kind === "contextMenu" ||
|
|
664
|
+
interaction.kind === "keydown") &&
|
|
665
|
+
selectedRange?.text &&
|
|
666
|
+
selectedRange.fieldId &&
|
|
667
|
+
((selectedRangeElementKey &&
|
|
668
|
+
interaction.elementKey &&
|
|
669
|
+
selectedRangeElementKey === interaction.elementKey) ||
|
|
670
|
+
bridgeIdsMatch(selectedRange.fieldId, interaction.fieldId) ||
|
|
671
|
+
bridgeIdsMatch(selectedRange.itemId, interaction.item?.id)));
|
|
672
|
+
const fieldId = selectedRangeMatchesInteraction
|
|
673
|
+
? selectedRange?.fieldId
|
|
674
|
+
: interaction.fieldId;
|
|
675
|
+
if (!fieldId)
|
|
676
|
+
return undefined;
|
|
677
|
+
const targetElementKey = selectedRangeMatchesInteraction
|
|
678
|
+
? selectedRangeElementKey
|
|
679
|
+
: interaction.elementKey;
|
|
680
|
+
const structureFields = pageView?.bridgeStructure?.fields ?? [];
|
|
681
|
+
const structureField = structureFields.find((field) => bridgeIdsMatch(field.fieldId, fieldId)) ??
|
|
682
|
+
structureFields.find((field) => targetElementKey && field.elementKey === targetElementKey);
|
|
683
|
+
const fallbackItem = pageView?.page?.item ?? pageView?.pageItemDescriptor;
|
|
684
|
+
const componentId = resolveBridgeComponentId(interaction.componentId ?? structureField?.componentId, pageView?.page);
|
|
685
|
+
const component = componentId && pageView?.page
|
|
686
|
+
? getComponentById(componentId, pageView.page)
|
|
687
|
+
: undefined;
|
|
688
|
+
const candidateItems = [];
|
|
689
|
+
const selectedRangeItem = selectedRangeMatchesInteraction &&
|
|
690
|
+
selectedRange?.itemId &&
|
|
691
|
+
selectedRange.language &&
|
|
692
|
+
typeof selectedRange.version === "number"
|
|
693
|
+
? {
|
|
694
|
+
id: selectedRange.itemId,
|
|
695
|
+
language: selectedRange.language,
|
|
696
|
+
version: selectedRange.version,
|
|
697
|
+
database: fallbackItem?.database,
|
|
386
698
|
}
|
|
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);
|
|
699
|
+
: undefined;
|
|
700
|
+
const addCandidateItem = (source) => {
|
|
701
|
+
const itemId = source?.id ?? fallbackItem?.id;
|
|
702
|
+
const language = source?.language ?? fallbackItem?.language;
|
|
703
|
+
const versionValue = source?.version ?? fallbackItem?.version;
|
|
704
|
+
const version = typeof versionValue === "number" && Number.isFinite(versionValue)
|
|
705
|
+
? versionValue
|
|
706
|
+
: undefined;
|
|
707
|
+
if (!itemId || !language || version === undefined)
|
|
708
|
+
return;
|
|
709
|
+
if (candidateItems.some((item) => bridgeIdsMatch(item.id, itemId) &&
|
|
710
|
+
item.language.toLowerCase() === language.toLowerCase() &&
|
|
711
|
+
item.version === version)) {
|
|
712
|
+
return;
|
|
415
713
|
}
|
|
714
|
+
candidateItems.push({
|
|
715
|
+
id: itemId,
|
|
716
|
+
language,
|
|
717
|
+
version,
|
|
718
|
+
name: source?.name ?? fallbackItem?.name,
|
|
719
|
+
displayName: source?.displayName ?? fallbackItem?.displayName,
|
|
720
|
+
path: source?.path ?? fallbackItem?.path,
|
|
721
|
+
database: source?.database ?? fallbackItem?.database,
|
|
722
|
+
});
|
|
416
723
|
};
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
724
|
+
addCandidateItem(selectedRangeItem);
|
|
725
|
+
addCandidateItem(interaction.item);
|
|
726
|
+
addCandidateItem(structureField?.item);
|
|
727
|
+
addCandidateItem(component?.datasourceItem);
|
|
728
|
+
component?.items.forEach(addCandidateItem);
|
|
729
|
+
addCandidateItem(fallbackItem);
|
|
730
|
+
for (const item of candidateItems) {
|
|
731
|
+
const repositoryItem = await editContextRef.current?.itemsRepository.getItem(item);
|
|
732
|
+
const repositoryField = repositoryItem?.fields.find((field) => fieldIdentifierMatches(field, fieldId));
|
|
733
|
+
if (repositoryField) {
|
|
734
|
+
return {
|
|
735
|
+
fieldId: repositoryField.id,
|
|
736
|
+
item,
|
|
737
|
+
};
|
|
429
738
|
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
739
|
+
}
|
|
740
|
+
const fallbackDescriptorItem = candidateItems[0];
|
|
741
|
+
if (!fallbackDescriptorItem)
|
|
742
|
+
return undefined;
|
|
743
|
+
return {
|
|
744
|
+
fieldId,
|
|
745
|
+
item: fallbackDescriptorItem,
|
|
746
|
+
};
|
|
747
|
+
}, [editContextRef]);
|
|
748
|
+
const handleBridgeInteraction = useCallback(async (interaction, iframe) => {
|
|
749
|
+
if (interaction.kind === "wheel") {
|
|
750
|
+
if (!interaction.ctrlKey && !interaction.metaKey)
|
|
751
|
+
return;
|
|
752
|
+
if (!interaction.deltaY)
|
|
753
|
+
return;
|
|
754
|
+
const zoomContext = (compareView ? slotContext?.primaryPageViewContext : undefined) ??
|
|
755
|
+
pageViewContextRef.current;
|
|
756
|
+
const direction = interaction.deltaY < 0 ? 1 : -1;
|
|
757
|
+
zoomContext?.setZoom((value) => clampEditorZoom(value + direction * ZOOM_STEP));
|
|
758
|
+
return;
|
|
759
|
+
}
|
|
760
|
+
if (interaction.kind === "keydown") {
|
|
761
|
+
if (isBridgeInlineAiShortcut(interaction)) {
|
|
762
|
+
const editor = editContextRef.current;
|
|
763
|
+
const field = await resolveBridgeFieldDescriptor(interaction);
|
|
764
|
+
if (editor && field) {
|
|
765
|
+
await editor.setFocusedField(field, editor.mode !== "suggestions");
|
|
435
766
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
loadContent(renderUrl.toString(), initialLoad, expectedRevision, controller.signal);
|
|
767
|
+
document.dispatchEvent(new CustomEvent("inline-ai-open", {
|
|
768
|
+
bubbles: true,
|
|
769
|
+
cancelable: true,
|
|
770
|
+
}));
|
|
441
771
|
}
|
|
772
|
+
return;
|
|
442
773
|
}
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
let integrationRefreshFn;
|
|
446
|
-
try {
|
|
447
|
-
integrationRefreshFn = iframeRef.current?.contentWindow?.requestRefresh;
|
|
774
|
+
if (interaction.kind !== "click" && interaction.kind !== "contextMenu") {
|
|
775
|
+
return;
|
|
448
776
|
}
|
|
449
|
-
|
|
450
|
-
|
|
777
|
+
if (interaction.kind === "click" && interaction.button !== 0)
|
|
778
|
+
return;
|
|
779
|
+
if (interaction.kind === "contextMenu" && interaction.ctrlKey)
|
|
780
|
+
return;
|
|
781
|
+
const editor = editContextRef.current;
|
|
782
|
+
if (!editor)
|
|
783
|
+
return;
|
|
784
|
+
const selectFromBridgeInteraction = (ids) => {
|
|
785
|
+
if (!bridgeComponentSelectionsMatch(editor.selection, ids)) {
|
|
786
|
+
suppressNextSelectionScrollRef.current = true;
|
|
787
|
+
}
|
|
788
|
+
editor.select(ids);
|
|
789
|
+
};
|
|
790
|
+
let effectiveInteraction = interaction;
|
|
791
|
+
if (interaction.kind === "contextMenu" &&
|
|
792
|
+
!interaction.fieldId &&
|
|
793
|
+
interaction.clientX !== undefined &&
|
|
794
|
+
interaction.clientY !== undefined) {
|
|
795
|
+
const fieldTarget = findBridgeFieldTargetAtPoint(pageViewContextRef.current?.bridgeGeometry, interaction.clientX, interaction.clientY);
|
|
796
|
+
if (fieldTarget?.fieldId) {
|
|
797
|
+
effectiveInteraction = {
|
|
798
|
+
...interaction,
|
|
799
|
+
elementKey: fieldTarget.elementKey ?? interaction.elementKey,
|
|
800
|
+
fieldId: fieldTarget.fieldId,
|
|
801
|
+
};
|
|
802
|
+
}
|
|
451
803
|
}
|
|
452
|
-
|
|
453
|
-
|
|
804
|
+
const componentId = resolveBridgeComponentId(effectiveInteraction.componentId, pageViewContextRef.current?.page);
|
|
805
|
+
const isEditableFieldClick = !!(effectiveInteraction.kind === "click" &&
|
|
806
|
+
effectiveInteraction.fieldId &&
|
|
807
|
+
effectiveInteraction.elementKey &&
|
|
808
|
+
!effectiveInteraction.ctrlKey &&
|
|
809
|
+
!effectiveInteraction.shiftKey &&
|
|
810
|
+
!effectiveInteraction.metaKey);
|
|
811
|
+
const currentOverlay = editor.currentOverlay;
|
|
812
|
+
const isGeneratorOverlay = !!(currentOverlay &&
|
|
813
|
+
typeof currentOverlay === "string" &&
|
|
814
|
+
currentOverlay.endsWith("_generators"));
|
|
815
|
+
if (interaction.kind === "click" && currentOverlay === "context-menu") {
|
|
816
|
+
editor.setCurrentOverlay(undefined);
|
|
454
817
|
}
|
|
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);
|
|
818
|
+
if (currentOverlay &&
|
|
819
|
+
currentOverlay !== "context-menu" &&
|
|
820
|
+
!isGeneratorOverlay) {
|
|
821
|
+
editor.setCurrentOverlay(undefined);
|
|
474
822
|
}
|
|
475
|
-
|
|
476
|
-
|
|
823
|
+
if (!componentId) {
|
|
824
|
+
if (interaction.kind === "click")
|
|
825
|
+
selectFromBridgeInteraction([]);
|
|
826
|
+
if (isEditableFieldClick) {
|
|
827
|
+
await beginTrackedBridgeInlineEdit(effectiveInteraction);
|
|
828
|
+
}
|
|
829
|
+
else if (interaction.kind === "click") {
|
|
830
|
+
endTrackedBridgeFieldFocus();
|
|
831
|
+
}
|
|
832
|
+
return;
|
|
477
833
|
}
|
|
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);
|
|
834
|
+
const currentSelection = editor.selection || [];
|
|
835
|
+
if (interaction.kind === "contextMenu") {
|
|
836
|
+
if (pageViewContextRef.current) {
|
|
837
|
+
pageViewContextRef.current.bridgeInteraction = effectiveInteraction;
|
|
499
838
|
}
|
|
500
|
-
|
|
501
|
-
|
|
839
|
+
const selectedIds = currentSelection.includes(componentId)
|
|
840
|
+
? currentSelection
|
|
841
|
+
: [componentId];
|
|
842
|
+
if (!currentSelection.includes(componentId)) {
|
|
843
|
+
selectFromBridgeInteraction(selectedIds);
|
|
502
844
|
}
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
845
|
+
const page = pageViewContextRef.current?.page;
|
|
846
|
+
if (!page)
|
|
847
|
+
return;
|
|
848
|
+
const selectedComponents = selectedIds
|
|
849
|
+
.map((id) => getComponentById(id, page))
|
|
850
|
+
.filter((component) => !!component);
|
|
851
|
+
if (selectedComponents.length === 0)
|
|
852
|
+
return;
|
|
853
|
+
const iframeRect = iframe.getBoundingClientRect();
|
|
854
|
+
const clientX = iframeRect.x + (interaction.clientX ?? 0);
|
|
855
|
+
const clientY = iframeRect.y + (interaction.clientY ?? 0);
|
|
856
|
+
const menuPosition = { x: clientX, y: clientY };
|
|
857
|
+
const adjustedEvent = new MouseEvent("contextmenu", {
|
|
858
|
+
bubbles: true,
|
|
859
|
+
cancelable: true,
|
|
860
|
+
shiftKey: interaction.shiftKey,
|
|
861
|
+
altKey: interaction.altKey,
|
|
862
|
+
ctrlKey: interaction.ctrlKey,
|
|
863
|
+
metaKey: interaction.metaKey,
|
|
864
|
+
view: window,
|
|
865
|
+
clientX,
|
|
866
|
+
clientY,
|
|
867
|
+
button: 2,
|
|
868
|
+
});
|
|
869
|
+
setContextMenuPosition(menuPosition);
|
|
870
|
+
let loadingShown = false;
|
|
871
|
+
const loadingTimer = window.setTimeout(() => {
|
|
872
|
+
loadingShown = true;
|
|
873
|
+
editor.showContextMenu(adjustedEvent, [
|
|
874
|
+
{
|
|
875
|
+
id: "loading",
|
|
876
|
+
label: "Loading...",
|
|
877
|
+
disabled: true,
|
|
878
|
+
icon: _jsx(Spinner, { size: "sm", className: "mr-2" }),
|
|
879
|
+
},
|
|
880
|
+
]);
|
|
881
|
+
}, 100);
|
|
882
|
+
const field = await resolveBridgeFieldDescriptor(effectiveInteraction);
|
|
883
|
+
const fieldButtons = field ? await loadFieldButtons(field) : [];
|
|
884
|
+
const handleParameterizedActionWithPosition = (field, action) => {
|
|
885
|
+
setContextMenuField(field);
|
|
886
|
+
setContextMenuFieldButtons([action]);
|
|
887
|
+
setPreSelectedAction(action);
|
|
888
|
+
window.setTimeout(() => {
|
|
889
|
+
const tempElement = document.createElement("div");
|
|
890
|
+
tempElement.style.position = "fixed";
|
|
891
|
+
tempElement.style.left = menuPosition.x + "px";
|
|
892
|
+
tempElement.style.top = menuPosition.y + "px";
|
|
893
|
+
tempElement.style.width = "1px";
|
|
894
|
+
tempElement.style.height = "1px";
|
|
895
|
+
tempElement.style.visibility = "hidden";
|
|
896
|
+
tempElement.style.pointerEvents = "none";
|
|
897
|
+
document.body.appendChild(tempElement);
|
|
898
|
+
const positionedEvent = new MouseEvent("click", {
|
|
899
|
+
bubbles: true,
|
|
900
|
+
cancelable: true,
|
|
901
|
+
view: window,
|
|
902
|
+
clientX: menuPosition.x,
|
|
903
|
+
clientY: menuPosition.y,
|
|
904
|
+
});
|
|
905
|
+
Object.defineProperty(positionedEvent, "target", {
|
|
906
|
+
value: tempElement,
|
|
907
|
+
enumerable: true,
|
|
908
|
+
});
|
|
909
|
+
fieldActionsOverlay.current?.show(positionedEvent, action);
|
|
910
|
+
window.setTimeout(() => {
|
|
911
|
+
tempElement.remove();
|
|
912
|
+
}, 1000);
|
|
913
|
+
}, 100);
|
|
914
|
+
};
|
|
915
|
+
const items = await buildComponentContextMenuItems(selectedComponents, editor, field, fieldButtons, handleParameterizedActionWithPosition);
|
|
916
|
+
window.clearTimeout(loadingTimer);
|
|
917
|
+
if (loadingShown) {
|
|
918
|
+
editor.updateContextMenu(items);
|
|
524
919
|
}
|
|
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);
|
|
920
|
+
else {
|
|
921
|
+
editor.showContextMenu(adjustedEvent, items);
|
|
532
922
|
}
|
|
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
923
|
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();
|
|
924
|
+
}
|
|
925
|
+
if (interaction.shiftKey && currentSelection.length > 0) {
|
|
926
|
+
const orderedIds = getOrderedBridgeComponentIds(pageViewContextRef.current?.bridgeGeometry, pageViewContextRef.current?.page);
|
|
927
|
+
const anchorId = currentSelection[currentSelection.length - 1];
|
|
928
|
+
const anchorIndex = orderedIds.findIndex((id) => bridgeIdsMatch(id, anchorId));
|
|
929
|
+
const targetIndex = orderedIds.findIndex((id) => bridgeIdsMatch(id, componentId));
|
|
930
|
+
if (anchorIndex !== -1 && targetIndex !== -1) {
|
|
931
|
+
const start = Math.min(anchorIndex, targetIndex);
|
|
932
|
+
const end = Math.max(anchorIndex, targetIndex);
|
|
933
|
+
const range = orderedIds.slice(start, end + 1);
|
|
934
|
+
if (interaction.ctrlKey || interaction.metaKey) {
|
|
935
|
+
const nextSelection = [...currentSelection];
|
|
936
|
+
for (const id of range) {
|
|
937
|
+
if (!nextSelection.some((selectedId) => bridgeIdsMatch(selectedId, id))) {
|
|
938
|
+
nextSelection.push(id);
|
|
605
939
|
}
|
|
606
940
|
}
|
|
607
|
-
|
|
608
|
-
setShowSpinner(false);
|
|
941
|
+
selectFromBridgeInteraction(nextSelection);
|
|
609
942
|
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
injectSXAScripts(iframeRef.current);
|
|
613
|
-
}, 1000);
|
|
614
|
-
try {
|
|
615
|
-
requestPageModelBuild(doc);
|
|
943
|
+
else {
|
|
944
|
+
selectFromBridgeInteraction(range);
|
|
616
945
|
}
|
|
617
|
-
catch (buildErr) { }
|
|
618
946
|
}
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
if (err?.name === "AbortError") {
|
|
622
|
-
// Swallow aborts – a newer refresh superseded this one
|
|
623
|
-
return;
|
|
947
|
+
else {
|
|
948
|
+
selectFromBridgeInteraction([componentId]);
|
|
624
949
|
}
|
|
625
|
-
throw err;
|
|
626
|
-
}
|
|
627
|
-
};
|
|
628
|
-
useEffect(() => {
|
|
629
|
-
const iframeDoc = getAccessibleIframeDocument(iframeRef.current, "PageViewerFrame.tsx:scrollIntoView-doc");
|
|
630
|
-
if (!editContext.scrollIntoView || !iframeDoc?.documentElement)
|
|
631
|
-
return;
|
|
632
|
-
const component = getComponentById(editContext.scrollIntoView, pageViewContextRef.current.page);
|
|
633
|
-
if (!component)
|
|
634
|
-
return;
|
|
635
|
-
let rect;
|
|
636
|
-
try {
|
|
637
|
-
rect = findComponentRect(iframeRef.current, component, true);
|
|
638
|
-
}
|
|
639
|
-
catch {
|
|
640
|
-
rect = undefined;
|
|
641
|
-
}
|
|
642
|
-
if (!rect)
|
|
643
|
-
return;
|
|
644
|
-
if (!iframeRef.current)
|
|
645
|
-
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
|
-
}
|
|
652
|
-
catch {
|
|
653
|
-
scrollTop = 0;
|
|
654
|
-
}
|
|
655
|
-
const elementTop = rect.rect.y;
|
|
656
|
-
const isInViewport = elementTop >= scrollTop && elementTop <= scrollTop + iframeHeight;
|
|
657
|
-
// If already in viewport, no need to scroll
|
|
658
|
-
if (isInViewport) {
|
|
659
|
-
editContext.setScrollIntoView(undefined);
|
|
660
950
|
return;
|
|
661
951
|
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
top: scrollPosition,
|
|
666
|
-
behavior: "smooth",
|
|
667
|
-
});
|
|
668
|
-
}
|
|
669
|
-
catch { }
|
|
670
|
-
editContext.setScrollIntoView(undefined);
|
|
671
|
-
}, [editContext.scrollIntoView, pageViewContext.page]);
|
|
672
|
-
useEffect(() => {
|
|
673
|
-
const handleMessage = (message) => {
|
|
674
|
-
if (message.origin !== window.location.origin)
|
|
675
|
-
return;
|
|
676
|
-
if (message.data.type === "editor-exitFullscreen") {
|
|
677
|
-
pageViewContext.setFullscreen(false);
|
|
678
|
-
}
|
|
679
|
-
if (message.data.type === "editor-timings") {
|
|
680
|
-
editContext.setTimings(message.data.timings);
|
|
952
|
+
if (interaction.ctrlKey || interaction.metaKey) {
|
|
953
|
+
if (currentSelection.includes(componentId)) {
|
|
954
|
+
selectFromBridgeInteraction(currentSelection.filter((id) => id !== componentId));
|
|
681
955
|
}
|
|
682
|
-
|
|
683
|
-
|
|
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
|
-
if (!sel || sel.rangeCount === 0) {
|
|
697
|
-
if (!isInlineAiUiFocused()) {
|
|
698
|
-
editContextRef.current?.setSelectedRange(undefined);
|
|
956
|
+
else {
|
|
957
|
+
selectFromBridgeInteraction([...currentSelection, componentId]);
|
|
699
958
|
}
|
|
700
959
|
return;
|
|
701
960
|
}
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
return;
|
|
961
|
+
selectFromBridgeInteraction([componentId]);
|
|
962
|
+
if (isEditableFieldClick) {
|
|
963
|
+
await beginTrackedBridgeInlineEdit(effectiveInteraction);
|
|
706
964
|
}
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
if (!fieldElement) {
|
|
710
|
-
if (!isInlineAiUiFocused()) {
|
|
711
|
-
editContextRef.current?.setSelectedRange(undefined);
|
|
712
|
-
}
|
|
713
|
-
return;
|
|
965
|
+
else {
|
|
966
|
+
endTrackedBridgeFieldFocus();
|
|
714
967
|
}
|
|
715
|
-
|
|
716
|
-
|
|
968
|
+
}, [
|
|
969
|
+
beginTrackedBridgeInlineEdit,
|
|
970
|
+
endTrackedBridgeFieldFocus,
|
|
971
|
+
resolveBridgeFieldDescriptor,
|
|
972
|
+
]);
|
|
973
|
+
// Update the context whenever the iframe ref changes
|
|
974
|
+
useEffect(() => {
|
|
975
|
+
pageViewContext.setEditorIframe(iframeElement);
|
|
976
|
+
}, [iframeElement, pageViewContext.setEditorIframe]);
|
|
977
|
+
useEffect(() => {
|
|
978
|
+
const iframe = iframeElement;
|
|
979
|
+
if (!iframe)
|
|
980
|
+
return;
|
|
981
|
+
const allowedHostOrigins = [
|
|
982
|
+
getUrlOrigin(pageViewContext.editUrl),
|
|
983
|
+
getUrlOrigin(pageViewContext.previewUrl),
|
|
984
|
+
].filter((origin) => Boolean(origin));
|
|
985
|
+
if (allowedHostOrigins.length === 0) {
|
|
986
|
+
pageViewContext.setBridgeReady(false);
|
|
987
|
+
pageViewContext.setIframeSupportsRefresh(false);
|
|
717
988
|
return;
|
|
718
|
-
const range = sel.getRangeAt(0);
|
|
719
|
-
// Guard: if layout components are hidden, do not set focus for fields of layout components
|
|
720
|
-
if (editContextRef.current?.showLayoutComponents === false &&
|
|
721
|
-
pageViewContextRef.current?.page) {
|
|
722
|
-
const ownerId = fieldElement.getAttribute("data-itemid") || "";
|
|
723
|
-
const owner = getComponentById(ownerId, pageViewContextRef.current.page);
|
|
724
|
-
if (owner?.layoutId)
|
|
725
|
-
return;
|
|
726
989
|
}
|
|
727
|
-
//
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
//
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
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
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
item: { id: itemId, language, version },
|
|
990
|
+
// The canonical bridge is served from the editor's own origin. A stub
|
|
991
|
+
// bootloader on the host loads it from this URL; full-bridge hosts ignore
|
|
992
|
+
// it. Editor shells deploy under different base paths (e.g. dev-vite under
|
|
993
|
+
// `/parhelia/` in Sitecore), so each app advertises its own served location
|
|
994
|
+
// via `window.__PARHELIA_EDITOR_BRIDGE_URL__`. Fall back to origin-root for
|
|
995
|
+
// root-based deployments.
|
|
996
|
+
const editorBridgeUrl = window
|
|
997
|
+
.__PARHELIA_EDITOR_BRIDGE_URL__ ||
|
|
998
|
+
new URL("/editor-bridge/v1/parhelia-bridge.js", window.location.origin).toString();
|
|
999
|
+
let client;
|
|
1000
|
+
client = new BridgeClient({
|
|
1001
|
+
iframe,
|
|
1002
|
+
editorOrigin: window.location.origin,
|
|
1003
|
+
allowedHostOrigins,
|
|
1004
|
+
bridgeUrl: editorBridgeUrl,
|
|
1005
|
+
onReadyChange: (ready) => {
|
|
1006
|
+
pageViewContext.setBridgeReady(ready);
|
|
1007
|
+
if (!ready)
|
|
1008
|
+
pageViewContext.setIframeSupportsRefresh(false);
|
|
1009
|
+
// Only the primary editing frame drives the shared diagnostics; a
|
|
1010
|
+
// comparison frame's bridge state must not clobber it.
|
|
1011
|
+
if (!compareView) {
|
|
1012
|
+
editContextRef.current?.setBridgeDiagnostics(client.getDiagnostics());
|
|
1013
|
+
}
|
|
1014
|
+
},
|
|
1015
|
+
onError: (error) => {
|
|
1016
|
+
pageViewContext.setBridgeReady(false);
|
|
1017
|
+
pageViewContext.setIframeSupportsRefresh(false);
|
|
1018
|
+
console.error("[Parhelia bridge]", error.message);
|
|
1019
|
+
if (!compareView) {
|
|
1020
|
+
editContextRef.current?.setBridgeDiagnostics(client.getDiagnostics());
|
|
1021
|
+
editContextRef.current?.showErrorToast({
|
|
1022
|
+
summary: "Editing host bridge unavailable",
|
|
1023
|
+
details: error.message,
|
|
1024
|
+
});
|
|
1025
|
+
editContextRef.current?.openSidebar("bridge-diagnostics", {
|
|
1026
|
+
preserveOpenSidebars: true,
|
|
1027
|
+
});
|
|
1028
|
+
}
|
|
1029
|
+
},
|
|
1030
|
+
onEvent: (event, bridge) => {
|
|
1031
|
+
switch (event.name) {
|
|
1032
|
+
case "ready":
|
|
1033
|
+
clearBridgePatchSignatures();
|
|
1034
|
+
pageViewContext.setIframeSupportsRefresh(bridge.supportsCapability("refresh"));
|
|
1035
|
+
if (!compareView) {
|
|
1036
|
+
editContextRef.current?.setBridgeDiagnostics(bridge.getDiagnostics());
|
|
1037
|
+
}
|
|
1038
|
+
bridge.sendCommand("init", {
|
|
1039
|
+
editorOrigin: window.location.origin,
|
|
1040
|
+
sessionId: editContextRef.current?.sessionId,
|
|
1041
|
+
mode: editContextRef.current?.mode ?? "edit",
|
|
1042
|
+
selectedComponentIds: editContextRef.current?.selection ?? [],
|
|
1043
|
+
featureFlags: {
|
|
1044
|
+
crossOriginBridge: true,
|
|
783
1045
|
},
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
value: valueToSave,
|
|
1046
|
+
version: bridge.getDiagnostics().editorVersion,
|
|
1047
|
+
acknowledgedCapabilities: bridge.getAcknowledgedCapabilities(),
|
|
787
1048
|
});
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
1049
|
+
bridge.sendCommand("setZoom", {
|
|
1050
|
+
zoom: pageViewContextRef.current?.zoom ?? 1,
|
|
1051
|
+
});
|
|
1052
|
+
break;
|
|
1053
|
+
case "structureUpdated":
|
|
1054
|
+
pageViewContext.setBridgeStructure(event.payload.structure);
|
|
1055
|
+
break;
|
|
1056
|
+
case "pageSkeletonUpdated":
|
|
1057
|
+
pageViewContext.setPageSkeleton(event.payload.pageSkeleton);
|
|
1058
|
+
break;
|
|
1059
|
+
case "geometryUpdated":
|
|
1060
|
+
latestBridgeScrollRef.current = event.payload.geometry.scroll;
|
|
1061
|
+
pageViewContext.bridgeGeometry = event.payload.geometry;
|
|
1062
|
+
pageViewContext.setBridgeGeometry(event.payload.geometry);
|
|
1063
|
+
scheduleBridgeGeometryRevision();
|
|
1064
|
+
dispatchBridgeOverlayScroll(iframe, event.payload.geometry.scroll, getBridgeGeometryScrollScale(event.payload.geometry));
|
|
1065
|
+
dispatchBridgeOverlayGeometry(iframe, event.payload.geometry);
|
|
1066
|
+
if (shouldTrackMinimapScroll()) {
|
|
1067
|
+
scrollHandlerRef.current(event.payload.geometry.scroll.y);
|
|
1068
|
+
}
|
|
1069
|
+
break;
|
|
1070
|
+
case "domUpdated":
|
|
1071
|
+
pageViewContext.bridgeDom = event.payload;
|
|
1072
|
+
pageViewContext.setBridgeDom(event.payload);
|
|
1073
|
+
scheduleBridgeDomRevision();
|
|
1074
|
+
window.dispatchEvent(new CustomEvent(BRIDGE_DOM_UPDATED_EVENT));
|
|
1075
|
+
break;
|
|
1076
|
+
case "refreshStarted":
|
|
1077
|
+
clearBridgePatchSignatures();
|
|
1078
|
+
activeBridgeInlineEditRef.current = null;
|
|
1079
|
+
break;
|
|
1080
|
+
case "refreshCompleted":
|
|
1081
|
+
// Runtime refresh replaces host DOM without a new bridge ready event.
|
|
1082
|
+
// Field patches sent during the refresh window may have been cached
|
|
1083
|
+
// against the previous DOM generation, so force the refreshed host to
|
|
1084
|
+
// receive the current repository values again.
|
|
1085
|
+
clearBridgePatchSignatures();
|
|
1086
|
+
if (event.payload.structure) {
|
|
1087
|
+
pageViewContext.setBridgeStructure(event.payload.structure);
|
|
1088
|
+
}
|
|
1089
|
+
if (event.payload.geometry) {
|
|
1090
|
+
latestBridgeScrollRef.current = event.payload.geometry.scroll;
|
|
1091
|
+
pageViewContext.bridgeGeometry = event.payload.geometry;
|
|
1092
|
+
pageViewContext.setBridgeGeometry(event.payload.geometry);
|
|
1093
|
+
scheduleBridgeGeometryRevision();
|
|
1094
|
+
dispatchBridgeOverlayScroll(iframe, event.payload.geometry.scroll, getBridgeGeometryScrollScale(event.payload.geometry));
|
|
1095
|
+
dispatchBridgeOverlayGeometry(iframe, event.payload.geometry);
|
|
1096
|
+
if (shouldTrackMinimapScroll()) {
|
|
1097
|
+
scrollHandlerRef.current(event.payload.geometry.scroll.y);
|
|
1098
|
+
}
|
|
1099
|
+
}
|
|
1100
|
+
setShowSpinner(false);
|
|
1101
|
+
break;
|
|
1102
|
+
case "selectionChanged":
|
|
1103
|
+
pageViewContext.setBridgeSelection(event.payload.selection);
|
|
1104
|
+
handleBridgeSelection(event.payload.selection, iframe);
|
|
1105
|
+
break;
|
|
1106
|
+
case "fieldValueChanged":
|
|
1107
|
+
handleBridgeFieldValueChanged(event.payload);
|
|
1108
|
+
break;
|
|
1109
|
+
case "inlineEditEnded":
|
|
1110
|
+
handleBridgeInlineEditEnded(event.payload);
|
|
1111
|
+
activeBridgeInlineEditRef.current = null;
|
|
1112
|
+
break;
|
|
1113
|
+
case "interaction":
|
|
1114
|
+
pageViewContext.setBridgeInteraction(event.payload);
|
|
1115
|
+
void handleBridgeInteraction(event.payload, iframe);
|
|
1116
|
+
break;
|
|
1117
|
+
case "scrollChanged":
|
|
1118
|
+
latestBridgeScrollRef.current = event.payload.scroll;
|
|
1119
|
+
dispatchBridgeOverlayScroll(iframe, event.payload.scroll, getBridgeGeometryScrollScale(pageViewContextRef.current?.bridgeGeometry));
|
|
1120
|
+
if (shouldTrackMinimapScroll()) {
|
|
1121
|
+
scrollHandlerRef.current(event.payload.scroll.y);
|
|
1122
|
+
}
|
|
1123
|
+
break;
|
|
1124
|
+
case "renderError":
|
|
1125
|
+
console.warn("[Parhelia bridge] Host render error", event.payload);
|
|
1126
|
+
break;
|
|
792
1127
|
}
|
|
793
1128
|
},
|
|
794
1129
|
});
|
|
795
|
-
|
|
1130
|
+
bridgeClientRef.current = client;
|
|
1131
|
+
pageViewContext.setBridgeReady(false);
|
|
1132
|
+
client.connect();
|
|
1133
|
+
return () => {
|
|
1134
|
+
if (bridgeClientRef.current === client) {
|
|
1135
|
+
bridgeClientRef.current = null;
|
|
1136
|
+
}
|
|
1137
|
+
client.disconnect();
|
|
1138
|
+
clearBridgePatchSignatures();
|
|
1139
|
+
activeBridgeInlineEditRef.current = null;
|
|
1140
|
+
pageViewContext.setBridgeReady(false);
|
|
1141
|
+
pageViewContext.setIframeSupportsRefresh(false);
|
|
1142
|
+
};
|
|
1143
|
+
}, [
|
|
1144
|
+
iframeElement,
|
|
1145
|
+
compareView,
|
|
1146
|
+
handleBridgeFieldValueChanged,
|
|
1147
|
+
handleBridgeInlineEditEnded,
|
|
1148
|
+
handleBridgeInteraction,
|
|
1149
|
+
handleBridgeSelection,
|
|
1150
|
+
clearBridgePatchSignatures,
|
|
1151
|
+
pageViewContext.editUrl,
|
|
1152
|
+
pageViewContext.previewUrl,
|
|
1153
|
+
scheduleBridgeDomRevision,
|
|
1154
|
+
scheduleBridgeGeometryRevision,
|
|
1155
|
+
shouldTrackMinimapScroll,
|
|
1156
|
+
]);
|
|
796
1157
|
useEffect(() => {
|
|
797
|
-
const
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
let mutationObserver = null;
|
|
804
|
-
const handleIframeMouseDown = async (event) => {
|
|
805
|
-
const target = event.target;
|
|
806
|
-
const targetElement = target;
|
|
807
|
-
if (editContextRef.current?.isRefreshing && showSpinner)
|
|
1158
|
+
const repository = editContext.itemsRepository;
|
|
1159
|
+
let disposed = false;
|
|
1160
|
+
const unsubscribe = repository.subscribeItemsChanged((changes) => {
|
|
1161
|
+
const fieldChanges = changes.filter((change) => change.action === "update" &&
|
|
1162
|
+
(change.changes.fields?.length ?? 0) > 0);
|
|
1163
|
+
if (fieldChanges.length === 0)
|
|
808
1164
|
return;
|
|
809
|
-
|
|
810
|
-
const
|
|
811
|
-
|
|
812
|
-
const activeSlotId = editContextRef.current?.getActiveSlotId?.() ??
|
|
813
|
-
editContextRef.current?.activeSlotId;
|
|
814
|
-
if (slotId && activeSlotId !== slotId) {
|
|
815
|
-
editContextRef.current?.setActiveSlot(slotId);
|
|
816
|
-
}
|
|
817
|
-
// Skip selection changes on right-click (button 2) - let context menu handler deal with it
|
|
818
|
-
if (event.button === 2)
|
|
1165
|
+
const bridge = bridgeClientRef.current;
|
|
1166
|
+
const structure = pageViewContextRef.current?.bridgeStructure;
|
|
1167
|
+
if (!bridge || !structure?.fields.length)
|
|
819
1168
|
return;
|
|
820
|
-
const
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
? findNearestEditableComponentId(targetElement)
|
|
833
|
-
: undefined);
|
|
834
|
-
let componentId = rawComponentId;
|
|
835
|
-
if (!componentIdFromField && componentId && pageForSelection) {
|
|
836
|
-
componentId = resolveComponentIdForTarget(componentId, targetElement, pageForSelection);
|
|
837
|
-
}
|
|
838
|
-
// Layout components can still be selected even when showLayoutComponents is false
|
|
839
|
-
// They will be displayed in read-only mode
|
|
840
|
-
const currentOverlayName = editContextRef.current?.currentOverlay;
|
|
841
|
-
const isGeneratorOverlay = !!(currentOverlayName &&
|
|
842
|
-
typeof currentOverlayName === "string" &&
|
|
843
|
-
currentOverlayName.endsWith("_generators"));
|
|
844
|
-
// Don't close context-menu overlay on mousedown - let it handle its own closing
|
|
845
|
-
if (editContextRef.current?.currentOverlay &&
|
|
846
|
-
!isGeneratorOverlay &&
|
|
847
|
-
editContextRef.current.currentOverlay !== "context-menu")
|
|
848
|
-
editContextRef.current?.setCurrentOverlay(undefined);
|
|
849
|
-
if (componentId) {
|
|
850
|
-
const currentSelection = editContextRef.current?.selection || [];
|
|
851
|
-
if (event.shiftKey && currentSelection.length > 0) {
|
|
852
|
-
const page = pageViewContextRef.current?.page;
|
|
853
|
-
if (page) {
|
|
854
|
-
// Build ordered list of visible component ids by DOM order
|
|
855
|
-
const doc = getAccessibleIframeDocument(iframeRef.current, "PageViewerFrame.tsx:shift-select-doc");
|
|
856
|
-
const orderedIds = [];
|
|
857
|
-
if (doc) {
|
|
858
|
-
const all = Array.from(doc.querySelectorAll("[data-component-id]"));
|
|
859
|
-
for (const el of all) {
|
|
860
|
-
const id = el.getAttribute("data-component-id");
|
|
861
|
-
if (!id)
|
|
862
|
-
continue;
|
|
863
|
-
// Layout components are now selectable even when showLayoutComponents is false
|
|
864
|
-
orderedIds.push(id);
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
const anchorId = currentSelection[currentSelection.length - 1];
|
|
868
|
-
const aIdx = orderedIds.indexOf(anchorId);
|
|
869
|
-
const bIdx = orderedIds.indexOf(componentId);
|
|
870
|
-
if (aIdx !== -1 && bIdx !== -1) {
|
|
871
|
-
const start = Math.min(aIdx, bIdx);
|
|
872
|
-
const end = Math.max(aIdx, bIdx);
|
|
873
|
-
const range = orderedIds.slice(start, end + 1);
|
|
874
|
-
if (event.ctrlKey) {
|
|
875
|
-
const union = new Set([...currentSelection, ...range]);
|
|
876
|
-
editContextRef.current?.select(Array.from(union));
|
|
877
|
-
}
|
|
878
|
-
else {
|
|
879
|
-
editContextRef.current?.select(range);
|
|
880
|
-
}
|
|
881
|
-
}
|
|
882
|
-
else {
|
|
883
|
-
editContextRef.current?.select([componentId]);
|
|
884
|
-
}
|
|
1169
|
+
const processResolvedField = (change, changedFieldId, field) => {
|
|
1170
|
+
if (disposed)
|
|
1171
|
+
return;
|
|
1172
|
+
const patchedElementKeys = new Set();
|
|
1173
|
+
const patches = [];
|
|
1174
|
+
for (const structureField of structure.fields) {
|
|
1175
|
+
if (!structureField.elementKey)
|
|
1176
|
+
continue;
|
|
1177
|
+
if (patchedElementKeys.has(structureField.elementKey))
|
|
1178
|
+
continue;
|
|
1179
|
+
if (!bridgeDescriptorMatchesItem(structureField.item, change.item)) {
|
|
1180
|
+
continue;
|
|
885
1181
|
}
|
|
886
|
-
|
|
887
|
-
|
|
1182
|
+
if (!bridgeFieldMatchesChangedField(structureField.fieldId, changedFieldId, field)) {
|
|
1183
|
+
continue;
|
|
888
1184
|
}
|
|
1185
|
+
const patch = buildBridgeFieldPatch({
|
|
1186
|
+
field,
|
|
1187
|
+
structureField,
|
|
1188
|
+
itemDescriptor: change.item,
|
|
1189
|
+
preferRepositoryValue: change.source === "local-field-update",
|
|
1190
|
+
});
|
|
1191
|
+
if (!patch)
|
|
1192
|
+
continue;
|
|
1193
|
+
patchedElementKeys.add(structureField.elementKey);
|
|
1194
|
+
patches.push(patch);
|
|
889
1195
|
}
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
1196
|
+
for (const patch of patches) {
|
|
1197
|
+
if (disposed)
|
|
1198
|
+
return;
|
|
1199
|
+
const structureField = structure.fields.find((field) => field.elementKey === patch.elementKey &&
|
|
1200
|
+
field.fieldId === patch.fieldId);
|
|
1201
|
+
sendBridgeFieldPatch(bridge, patch, structureField?.textContent);
|
|
1202
|
+
}
|
|
1203
|
+
};
|
|
1204
|
+
const asyncFieldChanges = [];
|
|
1205
|
+
for (const change of fieldChanges) {
|
|
1206
|
+
const localFieldsById = new Map((change.changes.fieldValues ?? []).map((field) => [
|
|
1207
|
+
field.id.toLowerCase(),
|
|
1208
|
+
field,
|
|
1209
|
+
]));
|
|
1210
|
+
let needsAsyncLookup = false;
|
|
1211
|
+
for (const changedFieldId of change.changes.fields ?? []) {
|
|
1212
|
+
if (disposed)
|
|
1213
|
+
return;
|
|
1214
|
+
const localField = localFieldsById.get(changedFieldId.toLowerCase());
|
|
1215
|
+
if (!localField) {
|
|
1216
|
+
needsAsyncLookup = true;
|
|
1217
|
+
continue;
|
|
900
1218
|
}
|
|
1219
|
+
processResolvedField(change, changedFieldId, localField);
|
|
901
1220
|
}
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
editContextRef.current?.select([componentId]);
|
|
1221
|
+
if (needsAsyncLookup) {
|
|
1222
|
+
asyncFieldChanges.push(change);
|
|
905
1223
|
}
|
|
906
|
-
// if (mode !== "edit") {
|
|
907
|
-
//editContextRef.current?.setScrollIntoView(componentId);
|
|
908
|
-
//}
|
|
909
|
-
}
|
|
910
|
-
else {
|
|
911
|
-
editContextRef.current?.select([]);
|
|
912
1224
|
}
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
(
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
pageViewContextRef.current?.page) {
|
|
923
|
-
const owningItemId = fieldElement.getAttribute("data-itemid") || "";
|
|
924
|
-
const ownerComponent = getComponentById(owningItemId, pageViewContextRef.current.page);
|
|
925
|
-
if (ownerComponent?.layoutId) {
|
|
1225
|
+
if (asyncFieldChanges.length === 0)
|
|
1226
|
+
return;
|
|
1227
|
+
void (async () => {
|
|
1228
|
+
for (const change of asyncFieldChanges) {
|
|
1229
|
+
if (disposed)
|
|
1230
|
+
return;
|
|
1231
|
+
const item = await repository.getItem(change.item);
|
|
1232
|
+
for (const changedFieldId of change.changes.fields ?? []) {
|
|
1233
|
+
if (disposed)
|
|
926
1234
|
return;
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
1235
|
+
const field = change.changes.fieldValues?.find((candidate) => fieldIdentifierMatches(candidate, changedFieldId)) ??
|
|
1236
|
+
item?.fields.find((candidate) => fieldIdentifierMatches(candidate, changedFieldId)) ??
|
|
1237
|
+
(await repository.getField({
|
|
1238
|
+
fieldId: changedFieldId,
|
|
1239
|
+
item: change.item,
|
|
1240
|
+
}));
|
|
1241
|
+
if (!field)
|
|
1242
|
+
continue;
|
|
1243
|
+
processResolvedField(change, changedFieldId, field);
|
|
936
1244
|
}
|
|
937
1245
|
}
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
fieldsContextRef.current?.setInlineEditingFieldElement(undefined);
|
|
942
|
-
// Release field locks when unfocusing field
|
|
943
|
-
if (editContextRef.current?.unlockField) {
|
|
944
|
-
editContextRef.current.unlockField(undefined);
|
|
945
|
-
}
|
|
1246
|
+
})().catch((error) => {
|
|
1247
|
+
if (!disposed) {
|
|
1248
|
+
console.warn("[Parhelia bridge] Failed to patch host field", error);
|
|
946
1249
|
}
|
|
947
|
-
}
|
|
948
|
-
const clickEvent = new MouseEvent("click", {
|
|
949
|
-
view: window,
|
|
950
|
-
bubbles: true,
|
|
951
|
-
cancelable: true,
|
|
952
|
-
clientX: event.clientX,
|
|
953
|
-
clientY: event.clientY,
|
|
954
1250
|
});
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
const handleIframeScroll = () => {
|
|
960
|
-
const scrollTop = boundScrollContainer?.scrollTop || 0;
|
|
961
|
-
scrollHandler(scrollTop);
|
|
962
|
-
};
|
|
963
|
-
const handleIframeWindowScroll = () => {
|
|
964
|
-
const scrollTop = boundScrollContainer?.scrollTop || 0;
|
|
965
|
-
scrollHandler(scrollTop);
|
|
966
|
-
};
|
|
967
|
-
const handleIframeWheel = (event) => {
|
|
968
|
-
if (!event.ctrlKey && !event.metaKey)
|
|
969
|
-
return;
|
|
970
|
-
if (event.deltaY === 0)
|
|
971
|
-
return;
|
|
972
|
-
event.preventDefault();
|
|
973
|
-
event.stopPropagation();
|
|
974
|
-
const zoomContext = (compareView ? slotContext?.primaryPageViewContext : undefined) ??
|
|
975
|
-
pageViewContextRef.current;
|
|
976
|
-
const direction = event.deltaY < 0 ? 1 : -1;
|
|
977
|
-
zoomContext?.setZoom((value) => clampEditorZoom(value + direction * ZOOM_STEP));
|
|
1251
|
+
});
|
|
1252
|
+
return () => {
|
|
1253
|
+
disposed = true;
|
|
1254
|
+
unsubscribe();
|
|
978
1255
|
};
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
1256
|
+
}, [
|
|
1257
|
+
buildBridgeFieldPatch,
|
|
1258
|
+
editContext.itemsRepository,
|
|
1259
|
+
sendBridgeFieldPatch,
|
|
1260
|
+
]);
|
|
1261
|
+
useEffect(() => {
|
|
1262
|
+
const bridge = bridgeClientRef.current;
|
|
1263
|
+
const structure = pageViewContext.bridgeStructure;
|
|
1264
|
+
if (!pageViewContext.bridgeReady || !bridge || !structure?.fields.length) {
|
|
1265
|
+
return;
|
|
1266
|
+
}
|
|
1267
|
+
let disposed = false;
|
|
1268
|
+
void (async () => {
|
|
1269
|
+
const patchedElementKeys = new Set();
|
|
1270
|
+
for (const structureField of structure.fields) {
|
|
1271
|
+
if (disposed)
|
|
1272
|
+
return;
|
|
1273
|
+
if (!structureField.elementKey)
|
|
1274
|
+
continue;
|
|
1275
|
+
if (patchedElementKeys.has(structureField.elementKey))
|
|
1276
|
+
continue;
|
|
1277
|
+
const fallbackItem = pageViewContextRef.current?.pageItemDescriptor ??
|
|
1278
|
+
pageViewContextRef.current?.page?.item?.descriptor;
|
|
1279
|
+
const bridgeItem = structureField.item;
|
|
1280
|
+
const itemDescriptor = {
|
|
1281
|
+
id: bridgeItem?.id ?? fallbackItem?.id,
|
|
1282
|
+
language: bridgeItem?.language ?? fallbackItem?.language,
|
|
1283
|
+
version: typeof bridgeItem?.version === "number"
|
|
1284
|
+
? bridgeItem.version
|
|
1285
|
+
: fallbackItem?.version,
|
|
1286
|
+
};
|
|
1287
|
+
if (!itemDescriptor.id ||
|
|
1288
|
+
!itemDescriptor.language ||
|
|
1289
|
+
typeof itemDescriptor.version !== "number") {
|
|
1290
|
+
continue;
|
|
1291
|
+
}
|
|
1292
|
+
const loadedItem = await editContext.itemsRepository.getItem(itemDescriptor);
|
|
1293
|
+
if (disposed || !loadedItem)
|
|
1294
|
+
continue;
|
|
1295
|
+
const field = loadedItem.fields.find((candidate) => fieldIdentifierMatches(candidate, structureField.fieldId));
|
|
1296
|
+
if (!field)
|
|
1297
|
+
continue;
|
|
1298
|
+
const patch = buildBridgeFieldPatch({
|
|
1299
|
+
field,
|
|
1300
|
+
structureField,
|
|
1301
|
+
itemDescriptor: itemDescriptor,
|
|
989
1302
|
});
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
return;
|
|
1303
|
+
if (!patch)
|
|
1304
|
+
continue;
|
|
1305
|
+
patchedElementKeys.add(structureField.elementKey);
|
|
1306
|
+
sendBridgeFieldPatch(bridge, patch, structureField.textContent);
|
|
995
1307
|
}
|
|
996
|
-
|
|
997
|
-
if (
|
|
998
|
-
|
|
999
|
-
const fieldElement = target?.closest?.("[data-fieldid][data-itemid][data-language][data-version]");
|
|
1000
|
-
if (fieldElement?.isContentEditable) {
|
|
1001
|
-
setTimeout(() => {
|
|
1002
|
-
const fieldId = fieldElement.getAttribute("data-fieldid");
|
|
1003
|
-
const fieldName = fieldElement.getAttribute("data-fieldname");
|
|
1004
|
-
const itemId = fieldElement.getAttribute("data-itemid");
|
|
1005
|
-
const language = fieldElement.getAttribute("data-language");
|
|
1006
|
-
const versionText = fieldElement.getAttribute("data-version");
|
|
1007
|
-
const version = versionText ? parseInt(versionText, 10) : undefined;
|
|
1008
|
-
if (!fieldId || !itemId || !language || !version)
|
|
1009
|
-
return;
|
|
1010
|
-
const isRichText = fieldElement.getAttribute("data-is-richtext") === "true";
|
|
1011
|
-
const value = (isRichText ? fieldElement.innerHTML : fieldElement.innerText).replaceAll("\u200B", "");
|
|
1012
|
-
editContextRef.current?.operations.editField({
|
|
1013
|
-
field: {
|
|
1014
|
-
fieldId,
|
|
1015
|
-
fieldName: fieldName ?? undefined,
|
|
1016
|
-
item: { id: itemId, language, version },
|
|
1017
|
-
},
|
|
1018
|
-
forceMode: "suggestions",
|
|
1019
|
-
originatingSlotId: slotContext?.slotId,
|
|
1020
|
-
refresh: "none",
|
|
1021
|
-
value,
|
|
1022
|
-
});
|
|
1023
|
-
}, 0);
|
|
1024
|
-
}
|
|
1308
|
+
})().catch((error) => {
|
|
1309
|
+
if (!disposed) {
|
|
1310
|
+
console.warn("[Parhelia bridge] Failed to patch host suggestion display", error);
|
|
1025
1311
|
}
|
|
1312
|
+
});
|
|
1313
|
+
return () => {
|
|
1314
|
+
disposed = true;
|
|
1026
1315
|
};
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1316
|
+
}, [
|
|
1317
|
+
buildBridgeFieldPatch,
|
|
1318
|
+
editContext.itemsRepository,
|
|
1319
|
+
editContext.itemsRepository.revision,
|
|
1320
|
+
editContext.mode,
|
|
1321
|
+
editContext.showSuggestedEdits,
|
|
1322
|
+
editContext.suggestedEdits,
|
|
1323
|
+
fieldsContext?.modifiedFields,
|
|
1324
|
+
bridgeDomRevision,
|
|
1325
|
+
pageViewContext.bridgeReady,
|
|
1326
|
+
pageViewContext.bridgeStructure,
|
|
1327
|
+
sendBridgeFieldPatch,
|
|
1328
|
+
]);
|
|
1329
|
+
useEffect(() => {
|
|
1330
|
+
const requestBridgeGeometry = (payload) => {
|
|
1331
|
+
const bridge = bridgeClientRef.current;
|
|
1332
|
+
if (!bridge)
|
|
1333
|
+
return false;
|
|
1334
|
+
// The editing host only remembers the most recent queryGeometry payload.
|
|
1335
|
+
// Comments, suggestions, locks and version-diff each contribute text
|
|
1336
|
+
// ranges independently, so without merging they clobbered one another and
|
|
1337
|
+
// fought in an endless re-request loop (heavy highlight flicker). Keep the
|
|
1338
|
+
// latest ranges per `source` and always query with their union.
|
|
1339
|
+
if (payload && typeof payload.source === "string") {
|
|
1340
|
+
const { source, textRanges, ...rest } = payload;
|
|
1341
|
+
const sources = bridgeTextRangeSourcesRef.current;
|
|
1342
|
+
if (textRanges && textRanges.length > 0) {
|
|
1343
|
+
sources.set(source, textRanges);
|
|
1344
|
+
}
|
|
1345
|
+
else {
|
|
1346
|
+
sources.delete(source);
|
|
1347
|
+
}
|
|
1348
|
+
const mergedTextRanges = Array.from(sources.values()).flat();
|
|
1349
|
+
return (bridge.sendCommand("queryGeometry", {
|
|
1350
|
+
...rest,
|
|
1351
|
+
textRanges: mergedTextRanges,
|
|
1352
|
+
}) ?? false);
|
|
1054
1353
|
}
|
|
1354
|
+
return bridge.sendCommand("queryGeometry", payload) ?? false;
|
|
1055
1355
|
};
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
if (!fieldElement)
|
|
1060
|
-
return;
|
|
1061
|
-
const nextFocusedElement = event.relatedTarget;
|
|
1062
|
-
if (nextFocusedElement && fieldElement.contains(nextFocusedElement)) {
|
|
1063
|
-
return;
|
|
1064
|
-
}
|
|
1065
|
-
editContextRef.current?.operations.onFieldBlur?.();
|
|
1066
|
-
fieldsContextRef.current?.setInlineEditingFieldElement(undefined);
|
|
1067
|
-
fieldsContextRef.current?.setFocusedField(undefined, false);
|
|
1356
|
+
pageViewContext.requestBridgeGeometry = requestBridgeGeometry;
|
|
1357
|
+
const requestBridgeScrollBy = (payload) => {
|
|
1358
|
+
return bridgeClientRef.current?.sendCommand("scrollBy", payload) ?? false;
|
|
1068
1359
|
};
|
|
1069
|
-
const
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1360
|
+
const requestBridgeRichTextCommand = (payload) => {
|
|
1361
|
+
return (bridgeClientRef.current?.sendCommand("applyRichTextCommand", payload) ??
|
|
1362
|
+
false);
|
|
1363
|
+
};
|
|
1364
|
+
pageViewContext.requestBridgeScrollBy = requestBridgeScrollBy;
|
|
1365
|
+
pageViewContext.requestBridgeRichTextCommand = requestBridgeRichTextCommand;
|
|
1366
|
+
return () => {
|
|
1367
|
+
if (pageViewContext.requestBridgeGeometry === requestBridgeGeometry) {
|
|
1368
|
+
pageViewContext.requestBridgeGeometry = undefined;
|
|
1369
|
+
}
|
|
1370
|
+
if (pageViewContext.requestBridgeScrollBy === requestBridgeScrollBy) {
|
|
1371
|
+
pageViewContext.requestBridgeScrollBy = undefined;
|
|
1076
1372
|
}
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
boundDocument.removeEventListener("wheel", handleIframeWheel, true);
|
|
1081
|
-
boundDocument.removeEventListener("focusout", handleIframeFocusOut, true);
|
|
1082
|
-
boundDocumentElement.removeEventListener("blur", handleIframeBlur, true);
|
|
1083
|
-
boundScrollContainer?.removeEventListener("scroll", handleIframeScroll);
|
|
1084
|
-
try {
|
|
1085
|
-
iframe.contentWindow?.removeEventListener("scroll", handleIframeWindowScroll);
|
|
1373
|
+
if (pageViewContext.requestBridgeRichTextCommand ===
|
|
1374
|
+
requestBridgeRichTextCommand) {
|
|
1375
|
+
pageViewContext.requestBridgeRichTextCommand = undefined;
|
|
1086
1376
|
}
|
|
1087
|
-
catch { }
|
|
1088
|
-
mutationObserver?.disconnect();
|
|
1089
|
-
mutationObserver = null;
|
|
1090
|
-
boundScrollContainer = null;
|
|
1091
|
-
boundDocumentElement = null;
|
|
1092
|
-
boundDocument = null;
|
|
1093
1377
|
};
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
const iframeDocumentElement = iframeDocument?.documentElement;
|
|
1101
|
-
if (!iframeDocument || !iframeDocumentElement)
|
|
1102
|
-
return;
|
|
1103
|
-
// Skip if already bound to current root element.
|
|
1104
|
-
if (boundDocument === iframeDocument &&
|
|
1105
|
-
boundDocumentElement === iframeDocumentElement) {
|
|
1106
|
-
return;
|
|
1107
|
-
}
|
|
1108
|
-
detachListeners();
|
|
1109
|
-
boundDocument = iframeDocument;
|
|
1110
|
-
boundDocumentElement = iframeDocumentElement;
|
|
1111
|
-
iframeDocumentElement.addEventListener("mousedown", handleIframeMouseDown);
|
|
1112
|
-
iframeDocumentElement.addEventListener("click", handleIframeClick);
|
|
1113
|
-
try {
|
|
1114
|
-
iframe.contentWindow?.addEventListener("contextmenu", handleContextMenu, true);
|
|
1115
|
-
}
|
|
1116
|
-
catch { }
|
|
1117
|
-
boundScrollContainer =
|
|
1118
|
-
iframeDocument.scrollingElement || iframeDocument.body || null;
|
|
1119
|
-
boundScrollContainer?.addEventListener("scroll", handleIframeScroll);
|
|
1120
|
-
try {
|
|
1121
|
-
iframe.contentWindow?.addEventListener("scroll", handleIframeWindowScroll);
|
|
1378
|
+
}, [pageViewContext]);
|
|
1379
|
+
useEffect(() => {
|
|
1380
|
+
const requestBridgeCaptureDom = (payload) => {
|
|
1381
|
+
const bridge = bridgeClientRef.current;
|
|
1382
|
+
if (!bridge) {
|
|
1383
|
+
return Promise.reject(new Error("The Parhelia bridge is not connected to the host."));
|
|
1122
1384
|
}
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
iframeDocument.addEventListener("keydown", handleIframeKeyDown);
|
|
1126
|
-
iframeDocument.addEventListener("wheel", handleIframeWheel, {
|
|
1127
|
-
capture: true,
|
|
1128
|
-
passive: false,
|
|
1129
|
-
});
|
|
1130
|
-
iframeDocument.addEventListener("focusout", handleIframeFocusOut, true);
|
|
1131
|
-
iframeDocumentElement.addEventListener("blur", handleIframeBlur, true);
|
|
1132
|
-
mutationObserver = new MutationObserver((records) => {
|
|
1133
|
-
// Ignore all text field edits
|
|
1134
|
-
if (records.some((x) => !(x &&
|
|
1135
|
-
"getAttribute" in x.target &&
|
|
1136
|
-
x.target.getAttribute("data-fieldid") &&
|
|
1137
|
-
x.target.getAttribute("data-itemid")))) {
|
|
1138
|
-
requestPageModelBuild(iframeDocument);
|
|
1139
|
-
}
|
|
1385
|
+
return bridge.requestCommand("captureDom", payload, "captureDomResult", {
|
|
1386
|
+
requestId: payload.requestId,
|
|
1140
1387
|
});
|
|
1141
|
-
mutationObserver.observe(iframeDocument, {
|
|
1142
|
-
childList: true, // observe direct children changes
|
|
1143
|
-
subtree: true, // observe all descendants changes
|
|
1144
|
-
characterData: false, // observe text changes
|
|
1145
|
-
//attributes: true, // observe attribute changes (like style or class)
|
|
1146
|
-
});
|
|
1147
|
-
requestPageModelBuild(iframeDocument);
|
|
1148
1388
|
};
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1389
|
+
pageViewContext.requestBridgeCaptureDom = requestBridgeCaptureDom;
|
|
1390
|
+
return () => {
|
|
1391
|
+
if (pageViewContext.requestBridgeCaptureDom === requestBridgeCaptureDom) {
|
|
1392
|
+
pageViewContext.requestBridgeCaptureDom = undefined;
|
|
1393
|
+
}
|
|
1154
1394
|
};
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1395
|
+
}, [pageViewContext]);
|
|
1396
|
+
const updateMiniMapVisibility = useDebouncedCallback(() => {
|
|
1397
|
+
if (!iframeRef.current)
|
|
1398
|
+
return;
|
|
1399
|
+
const iframe = iframeRef.current;
|
|
1400
|
+
const bridgeGeometry = pageViewContextRef.current?.bridgeGeometry;
|
|
1401
|
+
const bridgeDom = pageViewContextRef.current?.bridgeDom;
|
|
1402
|
+
const contentHeight = bridgeDom?.scrollHeight ??
|
|
1403
|
+
getBridgeGeometryDocumentSize(bridgeGeometry)?.height;
|
|
1404
|
+
const clientHeight = iframe.clientHeight;
|
|
1405
|
+
if (!contentHeight || !clientHeight)
|
|
1406
|
+
return;
|
|
1407
|
+
const upperThreshold = clientHeight + 100; // show minimap if content exceeds this height
|
|
1408
|
+
const lowerThreshold = clientHeight; // hide minimap if content falls below this
|
|
1409
|
+
// Check if minimap is enabled in settings and user controls
|
|
1410
|
+
const minimapEnabled = editContext.parheliaSettings?.showMinimap !== false &&
|
|
1411
|
+
editContext.showMinimap;
|
|
1412
|
+
if (showMiniMap) {
|
|
1413
|
+
if (contentHeight <= lowerThreshold || !minimapEnabled) {
|
|
1414
|
+
setShowMiniMap(false);
|
|
1160
1415
|
}
|
|
1161
|
-
|
|
1162
|
-
|
|
1416
|
+
}
|
|
1417
|
+
else {
|
|
1418
|
+
if (contentHeight > upperThreshold && minimapEnabled) {
|
|
1419
|
+
setShowMiniMap(true);
|
|
1163
1420
|
}
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1421
|
+
}
|
|
1422
|
+
}, 100);
|
|
1423
|
+
useEffect(() => {
|
|
1424
|
+
updateMiniMapVisibility();
|
|
1425
|
+
}, [
|
|
1426
|
+
bridgeDomRevision,
|
|
1427
|
+
bridgeGeometryRevision,
|
|
1428
|
+
editContext.parheliaSettings?.showMinimap,
|
|
1429
|
+
editContext.showMinimap,
|
|
1430
|
+
iframeElement,
|
|
1431
|
+
showMiniMap,
|
|
1432
|
+
updateMiniMapVisibility,
|
|
1433
|
+
]);
|
|
1434
|
+
const [iframeSrc, setIframeSrc] = useState();
|
|
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
|
+
setIframeSrc(renderUrl.toString());
|
|
1498
|
+
}
|
|
1499
|
+
function runBridgeRefresh() {
|
|
1500
|
+
const bridge = bridgeClientRef.current;
|
|
1501
|
+
if (!bridge?.supportsCapability("refresh")) {
|
|
1502
|
+
runFallbackRefresh();
|
|
1503
|
+
return;
|
|
1176
1504
|
}
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
: undefined) ??
|
|
1188
|
-
(attributeContainer?.getAttribute("sc_item")
|
|
1189
|
-
? extractItemIdFromItemUri(attributeContainer.getAttribute("sc_item"))
|
|
1190
|
-
: undefined);
|
|
1505
|
+
console.log("Bridge - requesting iframe refresh");
|
|
1506
|
+
pageViewContext.setIframeSupportsRefresh(true);
|
|
1507
|
+
const accepted = bridge.sendCommand("refresh", {
|
|
1508
|
+
url: renderUrl.toString(),
|
|
1509
|
+
revision: editContext.revision,
|
|
1510
|
+
layoutKind: editContext.layoutMode,
|
|
1511
|
+
});
|
|
1512
|
+
if (!accepted) {
|
|
1513
|
+
pageViewContext.setIframeSupportsRefresh(false);
|
|
1514
|
+
runFallbackRefresh();
|
|
1191
1515
|
}
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
hrefUrl.searchParams.get("lang") ??
|
|
1197
|
-
hrefUrl.searchParams.get("language") ??
|
|
1198
|
-
anchor.getAttribute("data-language") ??
|
|
1199
|
-
anchor.getAttribute("sc_lang") ??
|
|
1200
|
-
attributeContainer?.getAttribute("data-language") ??
|
|
1201
|
-
attributeContainer?.getAttribute("sc_lang") ??
|
|
1202
|
-
editContextRef.current?.currentItemDescriptor?.language ??
|
|
1203
|
-
editContextRef.current?.item?.language;
|
|
1204
|
-
if (!language)
|
|
1205
|
-
return undefined;
|
|
1206
|
-
const version = parsePositiveInt(hrefUrl.searchParams.get("sc_version")) ??
|
|
1207
|
-
parsePositiveInt(hrefUrl.searchParams.get("version")) ??
|
|
1208
|
-
parsePositiveInt(anchor.getAttribute("data-version")) ??
|
|
1209
|
-
parsePositiveInt(attributeContainer?.getAttribute("data-version")) ??
|
|
1210
|
-
editContextRef.current?.currentItemDescriptor?.version ??
|
|
1211
|
-
editContextRef.current?.item?.version ??
|
|
1212
|
-
0;
|
|
1213
|
-
return {
|
|
1214
|
-
id: itemId,
|
|
1215
|
-
language,
|
|
1216
|
-
version,
|
|
1217
|
-
};
|
|
1218
|
-
};
|
|
1219
|
-
const handleIframeClick = async (event) => {
|
|
1220
|
-
const target = event.target;
|
|
1221
|
-
if (!target)
|
|
1222
|
-
return;
|
|
1223
|
-
const anchor = target.tagName.toLowerCase() === "a"
|
|
1224
|
-
? target
|
|
1225
|
-
: target.closest("a");
|
|
1226
|
-
if (!anchor)
|
|
1227
|
-
return;
|
|
1228
|
-
const href = anchor.getAttribute("href") || anchor.href;
|
|
1229
|
-
if (!href || href.startsWith("#") || href.startsWith("javascript:")) {
|
|
1230
|
-
return;
|
|
1516
|
+
}
|
|
1517
|
+
if (!shouldUseIframeSrcReload && !versionChanged) {
|
|
1518
|
+
if (bridgeClientRef.current?.supportsCapability("refresh")) {
|
|
1519
|
+
runBridgeRefresh();
|
|
1231
1520
|
}
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
catch {
|
|
1240
|
-
return;
|
|
1241
|
-
}
|
|
1242
|
-
const iframeOrigin = getAccessibleIframeLocationOrigin(iframe);
|
|
1243
|
-
const isInternalLink = hrefUrl.origin === window.location.origin ||
|
|
1244
|
-
(iframeOrigin ? hrefUrl.origin === iframeOrigin : false);
|
|
1245
|
-
if (!isInternalLink) {
|
|
1246
|
-
event.preventDefault();
|
|
1247
|
-
event.stopPropagation();
|
|
1248
|
-
window.open(hrefUrl.toString(), "_blank", "noopener,noreferrer");
|
|
1249
|
-
return;
|
|
1250
|
-
}
|
|
1251
|
-
if (isInternalLink) {
|
|
1252
|
-
const linkedItem = resolveLinkedItemDescriptor(anchor, target);
|
|
1253
|
-
if (linkedItem) {
|
|
1254
|
-
event.preventDefault();
|
|
1255
|
-
event.stopPropagation();
|
|
1256
|
-
await editContextRef.current?.loadItem(linkedItem, {
|
|
1257
|
-
openInNewSlot: event.altKey,
|
|
1258
|
-
});
|
|
1259
|
-
return;
|
|
1260
|
-
}
|
|
1261
|
-
}
|
|
1262
|
-
// If this link cannot be resolved to an item, allow browser behavior.
|
|
1263
|
-
return;
|
|
1521
|
+
else {
|
|
1522
|
+
const retryDelayMs = 150;
|
|
1523
|
+
const retryTimer = setTimeout(() => {
|
|
1524
|
+
runBridgeRefresh();
|
|
1525
|
+
}, retryDelayMs);
|
|
1526
|
+
setCurrentItemDescriptor(pageItemDescriptor);
|
|
1527
|
+
return () => clearTimeout(retryTimer);
|
|
1264
1528
|
}
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1529
|
+
}
|
|
1530
|
+
else {
|
|
1531
|
+
runFallbackRefresh();
|
|
1532
|
+
}
|
|
1533
|
+
setCurrentItemDescriptor(pageItemDescriptor);
|
|
1534
|
+
}, [
|
|
1535
|
+
pathname,
|
|
1536
|
+
editContext.revision,
|
|
1537
|
+
pageItemDescriptor,
|
|
1538
|
+
pageViewContext.editUrl,
|
|
1539
|
+
pageViewContext.previewUrl,
|
|
1540
|
+
pageViewContext.fullscreen,
|
|
1541
|
+
editContext.mode,
|
|
1542
|
+
editContext.previewDate,
|
|
1543
|
+
editContext.sessionId,
|
|
1544
|
+
editContext.layoutMode,
|
|
1545
|
+
]);
|
|
1546
|
+
useEffect(() => {
|
|
1547
|
+
if (fieldsContext?.focusedField) {
|
|
1548
|
+
if (editContext.selection.length > 0 &&
|
|
1549
|
+
fieldsContext.focusedField.item.id !== editContext.selection[0])
|
|
1273
1550
|
return;
|
|
1274
|
-
|
|
1551
|
+
const bounds = findBridgeFieldDocumentBounds(pageViewContextRef.current?.bridgeGeometry, fieldsContext.focusedField);
|
|
1552
|
+
const activePageViewContext = pageViewContextRef.current;
|
|
1553
|
+
if (bounds && activePageViewContext) {
|
|
1554
|
+
scrollBridgeBoundsIntoView(activePageViewContext, bounds, latestBridgeScrollRef.current);
|
|
1555
|
+
}
|
|
1556
|
+
}
|
|
1557
|
+
}, [fieldsContext?.focusedField]);
|
|
1558
|
+
useEffect(() => {
|
|
1559
|
+
const selectionKey = editContext.selection.join("|");
|
|
1560
|
+
const selectionChanged = selectionKey !== lastScrolledSelectionKeyRef.current;
|
|
1561
|
+
lastScrolledSelectionKeyRef.current = selectionKey;
|
|
1562
|
+
// The effect also re-runs when focusedField changes (e.g. a field blurs
|
|
1563
|
+
// after a click). Only the selection actually changing should drive an
|
|
1564
|
+
// auto-scroll; otherwise a blur re-render scrolls the just-clicked
|
|
1565
|
+
// component away.
|
|
1566
|
+
if (!selectionChanged)
|
|
1567
|
+
return;
|
|
1568
|
+
if (suppressNextSelectionScrollRef.current) {
|
|
1569
|
+
suppressNextSelectionScrollRef.current = false;
|
|
1570
|
+
return;
|
|
1571
|
+
}
|
|
1572
|
+
if (!fieldsContext?.focusedField && editContext.selection.length > 0) {
|
|
1573
|
+
const lastSelectedComponent = getComponentById(editContext.selection[editContext.selection.length - 1], pageViewContextRef.current.page);
|
|
1574
|
+
if (lastSelectedComponent) {
|
|
1575
|
+
editContext.setScrollIntoView(lastSelectedComponent.id);
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1578
|
+
}, [editContext.selection, fieldsContext?.focusedField]);
|
|
1579
|
+
useEffect(() => {
|
|
1580
|
+
if (!editContext.scrollIntoView)
|
|
1581
|
+
return;
|
|
1582
|
+
const activePageViewContext = pageViewContextRef.current;
|
|
1583
|
+
if (!activePageViewContext)
|
|
1584
|
+
return;
|
|
1585
|
+
const bounds = findBridgeComponentDocumentBounds(activePageViewContext.bridgeGeometry, editContext.scrollIntoView);
|
|
1586
|
+
if (bounds) {
|
|
1587
|
+
scrollBridgeBoundsIntoView(activePageViewContext, bounds, latestBridgeScrollRef.current);
|
|
1588
|
+
}
|
|
1589
|
+
editContext.setScrollIntoView(undefined);
|
|
1590
|
+
}, [editContext.scrollIntoView, pageViewContext.page]);
|
|
1591
|
+
useEffect(() => {
|
|
1592
|
+
const handleMessage = (message) => {
|
|
1593
|
+
if (message.origin !== window.location.origin)
|
|
1275
1594
|
return;
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
let componentId = findNearestEditableComponentId(target);
|
|
1279
|
-
const pageForContextMenu = pageViewContextRef.current?.page;
|
|
1280
|
-
if (componentId && pageForContextMenu) {
|
|
1281
|
-
componentId = resolveComponentIdForTarget(componentId, target, pageForContextMenu);
|
|
1595
|
+
if (message.data.type === "editor-exitFullscreen") {
|
|
1596
|
+
pageViewContext.setFullscreen(false);
|
|
1282
1597
|
}
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
if (componentId) {
|
|
1286
|
-
// Only change selection if right-clicking on a component that's not in the current selection
|
|
1287
|
-
if (!editContextRef.current?.selection.includes(componentId)) {
|
|
1288
|
-
// Right-clicking on a component not in the selection - select just that component
|
|
1289
|
-
editContextRef.current.select([componentId]);
|
|
1290
|
-
}
|
|
1291
|
-
// else: right-clicking on a component already in the selection - keep current selection
|
|
1598
|
+
if (message.data.type === "editor-timings") {
|
|
1599
|
+
editContext.setTimings(message.data.timings);
|
|
1292
1600
|
}
|
|
1293
|
-
const fieldElement = findParentWithAttribute(target, "data-fieldid");
|
|
1294
|
-
const selectedComponents = editContextRef.current?.selection
|
|
1295
|
-
.map((id) => getComponentById(id, pageViewContextRef.current.page))
|
|
1296
|
-
.filter((x) => x);
|
|
1297
|
-
// Context menu will now show for layout components even when showLayoutComponents is false
|
|
1298
|
-
// Commands will be appropriately filtered/disabled
|
|
1299
|
-
const iframeRect = iframe.getBoundingClientRect();
|
|
1300
|
-
const adjustedEvent = new MouseEvent("contextmenu", {
|
|
1301
|
-
bubbles: true,
|
|
1302
|
-
cancelable: true,
|
|
1303
|
-
shiftKey: event.shiftKey,
|
|
1304
|
-
altKey: event.altKey,
|
|
1305
|
-
ctrlKey: event.ctrlKey,
|
|
1306
|
-
view: window,
|
|
1307
|
-
clientX: event.clientX + iframeRect.x,
|
|
1308
|
-
clientY: event.clientY + iframeRect.y,
|
|
1309
|
-
});
|
|
1310
|
-
// Store the original context menu position for overlay positioning
|
|
1311
|
-
const menuPosition = {
|
|
1312
|
-
x: event.clientX + iframeRect.x,
|
|
1313
|
-
y: event.clientY + iframeRect.y,
|
|
1314
|
-
};
|
|
1315
|
-
setContextMenuPosition(menuPosition);
|
|
1316
|
-
// Only show a spinner if work takes longer than 100ms
|
|
1317
|
-
let loadingShown = false;
|
|
1318
|
-
const loadingTimer = setTimeout(() => {
|
|
1319
|
-
loadingShown = true;
|
|
1320
|
-
editContextRef.current?.showContextMenu(adjustedEvent, [
|
|
1321
|
-
{
|
|
1322
|
-
id: "loading",
|
|
1323
|
-
label: "Loading…",
|
|
1324
|
-
disabled: true,
|
|
1325
|
-
icon: _jsx(Spinner, { size: "sm", className: "mr-2" }),
|
|
1326
|
-
},
|
|
1327
|
-
]);
|
|
1328
|
-
}, 100);
|
|
1329
|
-
const field = fieldElement
|
|
1330
|
-
? getFieldDescriptorFromElement(fieldElement)
|
|
1331
|
-
: undefined;
|
|
1332
|
-
const fieldButtons = field ? await loadFieldButtons(field) : [];
|
|
1333
|
-
// Create a handler that has access to the current menu position
|
|
1334
|
-
const handleParameterizedActionWithPosition = (field, action, allFieldButtons, event) => {
|
|
1335
|
-
setContextMenuField(field);
|
|
1336
|
-
setContextMenuFieldButtons([action]);
|
|
1337
|
-
setPreSelectedAction(action);
|
|
1338
|
-
// Create a new MouseEvent with the captured position
|
|
1339
|
-
const syntheticEvent = new MouseEvent("click", {
|
|
1340
|
-
bubbles: true,
|
|
1341
|
-
cancelable: true,
|
|
1342
|
-
view: window,
|
|
1343
|
-
clientX: menuPosition.x,
|
|
1344
|
-
clientY: menuPosition.y,
|
|
1345
|
-
screenX: menuPosition.x,
|
|
1346
|
-
screenY: menuPosition.y,
|
|
1347
|
-
});
|
|
1348
|
-
// Add target property to the event for proper positioning
|
|
1349
|
-
Object.defineProperty(syntheticEvent, "target", {
|
|
1350
|
-
value: document.body,
|
|
1351
|
-
enumerable: true,
|
|
1352
|
-
});
|
|
1353
|
-
console.log("About to show overlay with position:", {
|
|
1354
|
-
menuPosition,
|
|
1355
|
-
syntheticEvent,
|
|
1356
|
-
preSelectedAction: action,
|
|
1357
|
-
});
|
|
1358
|
-
// Add a small delay to ensure context menu has closed
|
|
1359
|
-
setTimeout(() => {
|
|
1360
|
-
console.log("Showing overlay with position:", {
|
|
1361
|
-
menuPosition,
|
|
1362
|
-
syntheticEvent,
|
|
1363
|
-
preSelectedAction: action,
|
|
1364
|
-
});
|
|
1365
|
-
// Create a temporary element at the exact position for better positioning
|
|
1366
|
-
const tempElement = document.createElement("div");
|
|
1367
|
-
tempElement.style.position = "fixed";
|
|
1368
|
-
tempElement.style.left = menuPosition.x + "px";
|
|
1369
|
-
tempElement.style.top = menuPosition.y + "px";
|
|
1370
|
-
tempElement.style.width = "1px";
|
|
1371
|
-
tempElement.style.height = "1px";
|
|
1372
|
-
tempElement.style.visibility = "hidden";
|
|
1373
|
-
tempElement.style.pointerEvents = "none";
|
|
1374
|
-
document.body.appendChild(tempElement);
|
|
1375
|
-
// Create event targeting the positioned element
|
|
1376
|
-
const positionedEvent = new MouseEvent("click", {
|
|
1377
|
-
bubbles: true,
|
|
1378
|
-
cancelable: true,
|
|
1379
|
-
view: window,
|
|
1380
|
-
clientX: menuPosition.x,
|
|
1381
|
-
clientY: menuPosition.y,
|
|
1382
|
-
});
|
|
1383
|
-
Object.defineProperty(positionedEvent, "target", {
|
|
1384
|
-
value: tempElement,
|
|
1385
|
-
enumerable: true,
|
|
1386
|
-
});
|
|
1387
|
-
fieldActionsOverlay.current?.show(positionedEvent, action);
|
|
1388
|
-
// Clean up the temporary element after overlay is shown
|
|
1389
|
-
setTimeout(() => {
|
|
1390
|
-
document.body.removeChild(tempElement);
|
|
1391
|
-
}, 1000);
|
|
1392
|
-
}, 100);
|
|
1393
|
-
};
|
|
1394
|
-
const items = await buildComponentContextMenuItems(selectedComponents, editContextRef.current, field, fieldButtons, handleParameterizedActionWithPosition);
|
|
1395
|
-
clearTimeout(loadingTimer);
|
|
1396
|
-
if (loadingShown)
|
|
1397
|
-
editContextRef.current?.updateContextMenu(items);
|
|
1398
|
-
else
|
|
1399
|
-
editContextRef.current?.showContextMenu(adjustedEvent, items);
|
|
1400
1601
|
};
|
|
1602
|
+
window.addEventListener("message", handleMessage);
|
|
1603
|
+
return () => {
|
|
1604
|
+
window.removeEventListener("message", handleMessage);
|
|
1605
|
+
};
|
|
1606
|
+
}, []);
|
|
1607
|
+
useEffect(() => {
|
|
1608
|
+
const iframe = iframeRef.current;
|
|
1609
|
+
if (!iframe)
|
|
1610
|
+
return;
|
|
1401
1611
|
const handleLoad = () => {
|
|
1402
|
-
// Skip handling if this load does not correspond to the latest requested revision
|
|
1403
|
-
const expectedRevision = currentLoadRef.current?.revision ?? "";
|
|
1404
|
-
const currentRevision = editContextRef.current?.revision ?? "";
|
|
1405
|
-
if (expectedRevision && expectedRevision !== currentRevision) {
|
|
1406
|
-
console.log("Stale load skipped", {
|
|
1407
|
-
expectedRevision,
|
|
1408
|
-
currentRevision,
|
|
1409
|
-
});
|
|
1410
|
-
return;
|
|
1411
|
-
}
|
|
1412
1612
|
setShowSpinner(false);
|
|
1413
1613
|
applyIframeZoom(iframe, pageViewContextRef.current?.zoom ?? 1, "PageViewerFrame.tsx:handleLoad-zoom");
|
|
1414
|
-
attachListeners();
|
|
1415
1614
|
};
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1615
|
+
iframe.addEventListener("load", handleLoad);
|
|
1616
|
+
return () => {
|
|
1617
|
+
iframe.removeEventListener("load", handleLoad);
|
|
1618
|
+
};
|
|
1619
|
+
}, [iframeElement]);
|
|
1620
|
+
useEffect(() => {
|
|
1621
|
+
const iframe = iframeRef.current;
|
|
1622
|
+
if (!iframe || typeof ResizeObserver === "undefined")
|
|
1623
|
+
return;
|
|
1624
|
+
let lastWidth = iframe.clientWidth;
|
|
1625
|
+
let lastHeight = iframe.clientHeight;
|
|
1626
|
+
let geometryTimer = null;
|
|
1627
|
+
let trailingGeometryTimer = null;
|
|
1628
|
+
const requestGeometry = () => {
|
|
1629
|
+
geometryTimer = null;
|
|
1630
|
+
const nextWidth = iframe.clientWidth;
|
|
1631
|
+
const nextHeight = iframe.clientHeight;
|
|
1632
|
+
if (nextWidth === lastWidth && nextHeight === lastHeight)
|
|
1633
|
+
return;
|
|
1634
|
+
lastWidth = nextWidth;
|
|
1635
|
+
lastHeight = nextHeight;
|
|
1636
|
+
bridgeClientRef.current?.sendCommand("queryGeometry", {});
|
|
1637
|
+
};
|
|
1638
|
+
const scheduleGeometryRequest = () => {
|
|
1639
|
+
if (geometryTimer != null) {
|
|
1640
|
+
window.clearTimeout(geometryTimer);
|
|
1423
1641
|
}
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1642
|
+
if (trailingGeometryTimer != null) {
|
|
1643
|
+
window.clearTimeout(trailingGeometryTimer);
|
|
1644
|
+
}
|
|
1645
|
+
geometryTimer = window.setTimeout(requestGeometry, 50);
|
|
1646
|
+
trailingGeometryTimer = window.setTimeout(() => {
|
|
1647
|
+
trailingGeometryTimer = null;
|
|
1648
|
+
requestGeometry();
|
|
1649
|
+
}, ZOOM_TRANSITION_MS + 75);
|
|
1650
|
+
};
|
|
1651
|
+
const resizeObserver = new ResizeObserver(scheduleGeometryRequest);
|
|
1652
|
+
resizeObserver.observe(iframe);
|
|
1427
1653
|
return () => {
|
|
1428
|
-
|
|
1429
|
-
if (
|
|
1430
|
-
|
|
1654
|
+
resizeObserver.disconnect();
|
|
1655
|
+
if (geometryTimer != null) {
|
|
1656
|
+
window.clearTimeout(geometryTimer);
|
|
1657
|
+
}
|
|
1658
|
+
if (trailingGeometryTimer != null) {
|
|
1659
|
+
window.clearTimeout(trailingGeometryTimer);
|
|
1431
1660
|
}
|
|
1432
|
-
detachListeners();
|
|
1433
1661
|
};
|
|
1434
|
-
}, [
|
|
1662
|
+
}, [iframeElement]);
|
|
1435
1663
|
useEffect(() => {
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
}
|
|
1439
|
-
catch { }
|
|
1664
|
+
bridgeClientRef.current?.sendCommand("setSelection", {
|
|
1665
|
+
componentIds: editContext.selection,
|
|
1666
|
+
});
|
|
1440
1667
|
}, [editContext.selection]);
|
|
1441
|
-
|
|
1442
|
-
const
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1451
|
-
|
|
1668
|
+
useEffect(() => {
|
|
1669
|
+
const bridge = bridgeClientRef.current;
|
|
1670
|
+
bridge?.sendCommand("setPreviewMode", {
|
|
1671
|
+
enabled: editContext.mode === "preview",
|
|
1672
|
+
});
|
|
1673
|
+
bridge?.sendCommand("setEditorMode", {
|
|
1674
|
+
mode: editContext.mode,
|
|
1675
|
+
});
|
|
1676
|
+
}, [editContext.mode]);
|
|
1677
|
+
useEffect(() => {
|
|
1678
|
+
bridgeClientRef.current?.sendCommand("setLayoutKind", {
|
|
1679
|
+
layoutKind: editContext.layoutMode,
|
|
1680
|
+
});
|
|
1681
|
+
}, [editContext.layoutMode]);
|
|
1452
1682
|
useEffect(() => {
|
|
1453
1683
|
applyIframeZoom(iframeRef.current, zoom, "PageViewerFrame.tsx:zoom-doc");
|
|
1684
|
+
bridgeClientRef.current?.sendCommand("setZoom", { zoom });
|
|
1685
|
+
const geometryTimer = window.setTimeout(() => {
|
|
1686
|
+
bridgeClientRef.current?.sendCommand("queryGeometry", {});
|
|
1687
|
+
}, ZOOM_TRANSITION_MS + 75);
|
|
1688
|
+
return () => {
|
|
1689
|
+
window.clearTimeout(geometryTimer);
|
|
1690
|
+
};
|
|
1691
|
+
}, [zoom]);
|
|
1692
|
+
useEffect(() => {
|
|
1693
|
+
let geometryTimer = null;
|
|
1694
|
+
const sendViewportStateToBridge = (event) => {
|
|
1695
|
+
const detail = event.detail;
|
|
1696
|
+
const nextZoom = detail?.zoom ?? pageViewContextRef.current?.zoom ?? zoom;
|
|
1697
|
+
bridgeClientRef.current?.sendCommand("setZoom", {
|
|
1698
|
+
zoom: nextZoom,
|
|
1699
|
+
});
|
|
1700
|
+
if (geometryTimer != null) {
|
|
1701
|
+
window.clearTimeout(geometryTimer);
|
|
1702
|
+
}
|
|
1703
|
+
geometryTimer = window.setTimeout(() => {
|
|
1704
|
+
geometryTimer = null;
|
|
1705
|
+
bridgeClientRef.current?.sendCommand("queryGeometry", {});
|
|
1706
|
+
}, ZOOM_TRANSITION_MS + 75);
|
|
1707
|
+
};
|
|
1708
|
+
window.addEventListener(DEVICE_CHANGE_EVENT, sendViewportStateToBridge);
|
|
1709
|
+
return () => {
|
|
1710
|
+
if (geometryTimer != null) {
|
|
1711
|
+
window.clearTimeout(geometryTimer);
|
|
1712
|
+
}
|
|
1713
|
+
window.removeEventListener(DEVICE_CHANGE_EVENT, sendViewportStateToBridge);
|
|
1714
|
+
};
|
|
1454
1715
|
}, [zoom]);
|
|
1455
|
-
const scrollHandler = useThrottledCallback(updateScrollPosition, 100);
|
|
1456
1716
|
if (pageViewContext.page?.item && !pageViewContext.page?.item.hasLayout) {
|
|
1457
1717
|
return _jsx(NoLayout, {});
|
|
1458
1718
|
}
|
|
@@ -1468,265 +1728,23 @@ function PageViewerFrameContent({ compareView, pageViewContext, editContext, cla
|
|
|
1468
1728
|
"px";
|
|
1469
1729
|
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: {
|
|
1470
1730
|
width: deviceWidth,
|
|
1471
|
-
}, children: [_jsxs("div", { className: "relative h-full w-full overflow-hidden", children: [_jsx("iframe", { ref:
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1731
|
+
}, 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: () => {
|
|
1732
|
+
const sendZoomToBridge = () => {
|
|
1733
|
+
bridgeClientRef.current?.sendCommand("setZoom", {
|
|
1734
|
+
zoom: pageViewContextRef.current?.zoom ?? zoom,
|
|
1735
|
+
});
|
|
1736
|
+
};
|
|
1737
|
+
sendZoomToBridge();
|
|
1738
|
+
window.setTimeout(sendZoomToBridge, 100);
|
|
1739
|
+
window.setTimeout(sendZoomToBridge, 500);
|
|
1740
|
+
if (iframeSrc) {
|
|
1475
1741
|
setShowSpinner(false);
|
|
1476
|
-
ensureEditorCssGuard(doc, editContext.mode !== "preview");
|
|
1477
1742
|
applyIframeZoom(iframeRef.current, zoom, "PageViewerFrame.tsx:onLoad-zoom");
|
|
1478
|
-
setTimeout(() => {
|
|
1479
|
-
injectSXAScripts(iframeRef.current);
|
|
1480
|
-
}, 1000);
|
|
1481
|
-
requestPageModelBuild(doc);
|
|
1482
1743
|
}
|
|
1483
|
-
} }),
|
|
1744
|
+
} }), 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 &&
|
|
1484
1745
|
showMiniMap &&
|
|
1485
1746
|
editContext.showMinimap &&
|
|
1486
1747
|
!editContext.isMobile &&
|
|
1487
|
-
editContext.parheliaSettings?.showMinimap !== false && (_jsx(MiniMap, { compareView: compareView, scroll: scroll, mainViewIframeRef: iframeRef, pageViewContext: pageViewContext,
|
|
1488
|
-
? undefined
|
|
1489
|
-
: 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 })] }));
|
|
1490
|
-
}
|
|
1491
|
-
/**
|
|
1492
|
-
* In preview mode, disable editing in the iframe: set contentEditable=false on all
|
|
1493
|
-
* editable elements and blur the active element so the caret is removed and typing does nothing.
|
|
1494
|
-
*/
|
|
1495
|
-
function disableEditingInIframeDocument(doc) {
|
|
1496
|
-
if (!doc || !doc.body)
|
|
1497
|
-
return;
|
|
1498
|
-
const editable = doc.querySelectorAll("[contenteditable='true'], [contenteditable='']");
|
|
1499
|
-
editable.forEach((el) => {
|
|
1500
|
-
el.contentEditable = "false";
|
|
1501
|
-
});
|
|
1502
|
-
const active = doc.activeElement;
|
|
1503
|
-
if (active?.isContentEditable) {
|
|
1504
|
-
active.blur();
|
|
1505
|
-
}
|
|
1506
|
-
}
|
|
1507
|
-
function injectEditorCSS(iframeDocument, editMode) {
|
|
1508
|
-
if (!iframeDocument)
|
|
1509
|
-
return;
|
|
1510
|
-
const styleId = EDITOR_CSS_STYLE_ID;
|
|
1511
|
-
// Remove existing style element if present to avoid duplicates
|
|
1512
|
-
const existingStyle = iframeDocument.getElementById(styleId);
|
|
1513
|
-
if (existingStyle) {
|
|
1514
|
-
existingStyle.remove();
|
|
1515
|
-
}
|
|
1516
|
-
const style = iframeDocument.createElement("style");
|
|
1517
|
-
style.id = styleId;
|
|
1518
|
-
style.textContent = editMode
|
|
1519
|
-
? `[contentEditable]:empty:before {
|
|
1520
|
-
content: attr(placeholder);
|
|
1521
|
-
opacity: 0.6;
|
|
1522
|
-
}
|
|
1523
|
-
|
|
1524
|
-
.scChromeData, code {
|
|
1525
|
-
display: none;
|
|
1526
|
-
}
|
|
1527
|
-
|
|
1528
|
-
[contenteditable] {
|
|
1529
|
-
outline: 0px solid transparent;
|
|
1530
|
-
}
|
|
1531
|
-
|
|
1532
|
-
[data-fieldid][data-itemid][data-language][data-version][data-is-richtext="true"] {
|
|
1533
|
-
cursor: text;
|
|
1534
|
-
}
|
|
1535
|
-
`
|
|
1536
|
-
: `
|
|
1537
|
-
[contenteditable] {
|
|
1538
|
-
outline: 0px solid transparent;
|
|
1539
|
-
}
|
|
1540
|
-
.scChromeData, code {
|
|
1541
|
-
display: none;
|
|
1542
|
-
}
|
|
1543
|
-
`;
|
|
1544
|
-
if (iframeDocument && iframeDocument.head) {
|
|
1545
|
-
iframeDocument.head.appendChild(style);
|
|
1546
|
-
}
|
|
1547
|
-
if (!editMode) {
|
|
1548
|
-
disableEditingInIframeDocument(iframeDocument);
|
|
1549
|
-
}
|
|
1550
|
-
}
|
|
1551
|
-
function resolveComponentIdForTarget(rawId, target, page) {
|
|
1552
|
-
const direct = getComponentById(rawId, page);
|
|
1553
|
-
const candidates = getAllComponentInstances(rawId, page);
|
|
1554
|
-
if (candidates.length === 0) {
|
|
1555
|
-
return direct?.id || rawId;
|
|
1556
|
-
}
|
|
1557
|
-
const containing = candidates.filter((component) => isTargetInsideComponent(target, component));
|
|
1558
|
-
if (containing.length === 0) {
|
|
1559
|
-
return direct?.id || candidates[0].id;
|
|
1560
|
-
}
|
|
1561
|
-
// Prefer the deepest/most specific matching component for nested SXA markup.
|
|
1562
|
-
containing.sort((a, b) => getElementDepth(b.firstDOMElement || null) -
|
|
1563
|
-
getElementDepth(a.firstDOMElement || null));
|
|
1564
|
-
return containing[0].id;
|
|
1565
|
-
}
|
|
1566
|
-
function resolveComponentIdForFieldTarget(field, target, page) {
|
|
1567
|
-
const matches = findComponentsRenderingField(field, page);
|
|
1568
|
-
const containingMatches = matches.filter((component) => isTargetInsideComponent(target, component));
|
|
1569
|
-
const rankedMatches = (containingMatches.length > 0 ? containingMatches : matches).sort((a, b) => getElementDepth(b.firstDOMElement || null) -
|
|
1570
|
-
getElementDepth(a.firstDOMElement || null));
|
|
1571
|
-
return rankedMatches[0]?.id;
|
|
1572
|
-
}
|
|
1573
|
-
function findComponentsRenderingField(field, page) {
|
|
1574
|
-
if (!page?.rootComponent)
|
|
1575
|
-
return [];
|
|
1576
|
-
const matches = [];
|
|
1577
|
-
const visit = (component) => {
|
|
1578
|
-
if (componentRendersField(component, field)) {
|
|
1579
|
-
matches.push(component);
|
|
1580
|
-
}
|
|
1581
|
-
for (const placeholder of component.placeholders || []) {
|
|
1582
|
-
for (const child of placeholder.components || []) {
|
|
1583
|
-
visit(child);
|
|
1584
|
-
}
|
|
1585
|
-
}
|
|
1586
|
-
};
|
|
1587
|
-
visit(page.rootComponent);
|
|
1588
|
-
return matches;
|
|
1589
|
-
}
|
|
1590
|
-
function componentRendersField(component, field) {
|
|
1591
|
-
const renderedItems = [
|
|
1592
|
-
component.datasourceItem,
|
|
1593
|
-
...component.items.filter((item) => !component.datasourceItem ||
|
|
1594
|
-
!(item.id === component.datasourceItem.id &&
|
|
1595
|
-
item.language === component.datasourceItem.language &&
|
|
1596
|
-
item.version === component.datasourceItem.version)),
|
|
1597
|
-
].filter(Boolean);
|
|
1598
|
-
return renderedItems.some((item) => item.id === field.item.id &&
|
|
1599
|
-
item.language === field.item.language &&
|
|
1600
|
-
item.version === field.item.version &&
|
|
1601
|
-
item.renderedFieldIds.includes(field.fieldId));
|
|
1602
|
-
}
|
|
1603
|
-
function isTargetInsideComponent(target, component) {
|
|
1604
|
-
const start = component.firstDOMElement;
|
|
1605
|
-
const end = component.lastDOMElement || component.firstDOMElement;
|
|
1606
|
-
if (!start || !end)
|
|
1607
|
-
return false;
|
|
1608
|
-
if (start.contains(target) || end.contains(target))
|
|
1609
|
-
return true;
|
|
1610
|
-
const startPos = start.compareDocumentPosition(target);
|
|
1611
|
-
const endPos = end.compareDocumentPosition(target);
|
|
1612
|
-
const isAfterStart = !!(startPos & Node.DOCUMENT_POSITION_FOLLOWING) ||
|
|
1613
|
-
!!(startPos & Node.DOCUMENT_POSITION_CONTAINED_BY);
|
|
1614
|
-
const isBeforeEnd = !!(endPos & Node.DOCUMENT_POSITION_PRECEDING) ||
|
|
1615
|
-
!!(endPos & Node.DOCUMENT_POSITION_CONTAINED_BY);
|
|
1616
|
-
return isAfterStart && isBeforeEnd;
|
|
1617
|
-
}
|
|
1618
|
-
function getElementDepth(element) {
|
|
1619
|
-
let depth = 0;
|
|
1620
|
-
let current = element;
|
|
1621
|
-
while (current) {
|
|
1622
|
-
depth++;
|
|
1623
|
-
current = current.parentElement;
|
|
1624
|
-
}
|
|
1625
|
-
return depth;
|
|
1626
|
-
}
|
|
1627
|
-
function injectSXAScripts(iframe) {
|
|
1628
|
-
if (!iframe)
|
|
1629
|
-
return;
|
|
1630
|
-
const iframeDocument = getAccessibleIframeDocument(iframe, "PageViewerFrame.tsx:injectSXAScripts-doc");
|
|
1631
|
-
let iframeWindow;
|
|
1632
|
-
try {
|
|
1633
|
-
iframeWindow = iframe.contentWindow;
|
|
1634
|
-
}
|
|
1635
|
-
catch {
|
|
1636
|
-
iframeWindow = null;
|
|
1637
|
-
}
|
|
1638
|
-
if (!iframeDocument || !iframeWindow)
|
|
1639
|
-
return;
|
|
1640
|
-
iframeWindow.Sitecore = {
|
|
1641
|
-
PageModes: {
|
|
1642
|
-
HoverFrame: {
|
|
1643
|
-
extend: () => {
|
|
1644
|
-
// console.log("extend hoverframe");
|
|
1645
|
-
},
|
|
1646
|
-
},
|
|
1647
|
-
ChromeManager: {
|
|
1648
|
-
chromes: () => {
|
|
1649
|
-
// console.log("chromes");
|
|
1650
|
-
return [];
|
|
1651
|
-
},
|
|
1652
|
-
resetChromes: () => {
|
|
1653
|
-
// console.log("resetChromes");
|
|
1654
|
-
iframeWindow.XA.init();
|
|
1655
|
-
},
|
|
1656
|
-
chromesReseted: {
|
|
1657
|
-
observe: () => {
|
|
1658
|
-
// console.log("chromesReseted.observe");
|
|
1659
|
-
},
|
|
1660
|
-
},
|
|
1661
|
-
// Dummy selectionChanged with an _callbacks array
|
|
1662
|
-
selectionChanged: {
|
|
1663
|
-
_callbacks: [],
|
|
1664
|
-
// Optionally, you can add a method to trigger all callbacks if needed.
|
|
1665
|
-
trigger: function (...args) {
|
|
1666
|
-
this._callbacks.forEach((callback) => callback(...args));
|
|
1667
|
-
},
|
|
1668
|
-
},
|
|
1669
|
-
},
|
|
1670
|
-
ChromeControls: function () { },
|
|
1671
|
-
ChromeTypes: {
|
|
1672
|
-
PlaceholderSorting: function () { },
|
|
1673
|
-
Rendering: function () { },
|
|
1674
|
-
},
|
|
1675
|
-
},
|
|
1676
|
-
};
|
|
1677
|
-
// Option 1: Use main window's jQuery if available.
|
|
1678
|
-
if (iframeWindow.jQuery) {
|
|
1679
|
-
// Be cautious: if the iframe's document is different, it's better to load jQuery within the iframe.
|
|
1680
|
-
iframeWindow.$sc = iframeWindow.jQuery;
|
|
1681
|
-
}
|
|
1682
|
-
else {
|
|
1683
|
-
// Option 2: Dynamically load jQuery in the iframe.
|
|
1684
|
-
const jqueryScript = iframeDocument.createElement("script");
|
|
1685
|
-
jqueryScript.type = "text/javascript";
|
|
1686
|
-
jqueryScript.src = "https://code.jquery.com/jquery-3.6.0.min.js"; // Use the version you prefer.
|
|
1687
|
-
jqueryScript.integrity =
|
|
1688
|
-
"sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=";
|
|
1689
|
-
jqueryScript.crossOrigin = "anonymous";
|
|
1690
|
-
jqueryScript.onload = () => {
|
|
1691
|
-
// Once loaded, assign it to $sc using noConflict to avoid global collisions.
|
|
1692
|
-
iframeWindow.$sc = iframeWindow.jQuery.noConflict();
|
|
1693
|
-
// console.log("jQuery loaded in iframe and assigned to $sc");
|
|
1694
|
-
};
|
|
1695
|
-
(iframeDocument.head || iframeDocument.body).appendChild(jqueryScript);
|
|
1696
|
-
}
|
|
1697
|
-
// Add a dummy implementation for renderExpandCommand on the ChromeControls prototype.
|
|
1698
|
-
iframeWindow.Sitecore.PageModes.ChromeControls.prototype.renderExpandCommand =
|
|
1699
|
-
function () {
|
|
1700
|
-
console.log("renderExpandCommand called");
|
|
1701
|
-
// Return a dummy value if needed. For example, you might return a dummy HTML string.
|
|
1702
|
-
return "<div>Dummy Expand Command</div>";
|
|
1703
|
-
};
|
|
1704
|
-
iframeWindow.Sitecore.PageModes.ChromeTypes.PlaceholderSorting.prototype.insertSortingHandle =
|
|
1705
|
-
function () {
|
|
1706
|
-
console.log("insertSortingHandle called");
|
|
1707
|
-
};
|
|
1708
|
-
}
|
|
1709
|
-
/**
|
|
1710
|
-
* Calculates the global offset of a given target node and its local offset,
|
|
1711
|
-
* relative to the container's complete text content.
|
|
1712
|
-
*
|
|
1713
|
-
* @param container - The container element that holds the text nodes.
|
|
1714
|
-
* @param targetNode - The text node where the selection starts or ends.
|
|
1715
|
-
* @param localOffset - The offset within the target text node.
|
|
1716
|
-
* @returns The computed global offset.
|
|
1717
|
-
*/
|
|
1718
|
-
function getGlobalTextOffset(container, targetNode, localOffset) {
|
|
1719
|
-
let globalOffset = 0;
|
|
1720
|
-
const walker = document.createTreeWalker(container, NodeFilter.SHOW_TEXT, null);
|
|
1721
|
-
while (walker.nextNode()) {
|
|
1722
|
-
const currentNode = walker.currentNode;
|
|
1723
|
-
// If we've reached the target node, add the local offset and return.
|
|
1724
|
-
if (currentNode === targetNode) {
|
|
1725
|
-
return globalOffset + localOffset;
|
|
1726
|
-
}
|
|
1727
|
-
// Otherwise, add the length of this text node.
|
|
1728
|
-
globalOffset += (currentNode.textContent || "").length;
|
|
1729
|
-
}
|
|
1730
|
-
return globalOffset;
|
|
1748
|
+
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 })] }));
|
|
1731
1749
|
}
|
|
1732
1750
|
//# sourceMappingURL=PageViewerFrame.js.map
|