@alpaca-editor/core 1.0.3942 → 1.0.3943

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.
Files changed (308) hide show
  1. package/.prettierrc +3 -0
  2. package/build.css +3 -0
  3. package/components.json +21 -0
  4. package/dist/editor/ContentTree.d.ts +2 -1
  5. package/dist/editor/ContentTree.js +23 -21
  6. package/dist/editor/ContentTree.js.map +1 -1
  7. package/dist/editor/FieldActionsOverlay.js +0 -2
  8. package/dist/editor/FieldActionsOverlay.js.map +1 -1
  9. package/dist/editor/ScrollingContentTree.js +1 -1
  10. package/dist/editor/ScrollingContentTree.js.map +1 -1
  11. package/dist/editor/Titlebar.js +1 -1
  12. package/dist/editor/Titlebar.js.map +1 -1
  13. package/dist/editor/ai/GhostWriter.js +24 -3
  14. package/dist/editor/ai/GhostWriter.js.map +1 -1
  15. package/dist/editor/client/EditorClient.js +7 -7
  16. package/dist/editor/client/EditorClient.js.map +1 -1
  17. package/dist/editor/field-types/InternalLinkFieldEditor.js +60 -10
  18. package/dist/editor/field-types/InternalLinkFieldEditor.js.map +1 -1
  19. package/dist/editor/media-selector/MediaFolderBrowser.js +48 -1
  20. package/dist/editor/media-selector/MediaFolderBrowser.js.map +1 -1
  21. package/dist/editor/menubar/PageSelector.js +116 -65
  22. package/dist/editor/menubar/PageSelector.js.map +1 -1
  23. package/dist/editor/page-viewer/EditorForm.js +5 -2
  24. package/dist/editor/page-viewer/EditorForm.js.map +1 -1
  25. package/dist/editor/ui/ItemSearch.js +14 -8
  26. package/dist/editor/ui/ItemSearch.js.map +1 -1
  27. package/dist/editor/ui/PerfectTree.d.ts +4 -2
  28. package/dist/editor/ui/PerfectTree.js +78 -4
  29. package/dist/editor/ui/PerfectTree.js.map +1 -1
  30. package/dist/editor/ui/Splitter.js +1 -1
  31. package/dist/revision.d.ts +2 -2
  32. package/dist/revision.js +2 -2
  33. package/dist/styles.css +8 -2
  34. package/eslint.config.mjs +4 -0
  35. package/images/bg-shape-black.webp +0 -0
  36. package/images/wizard-bg.png +0 -0
  37. package/images/wizard-tour.png +0 -0
  38. package/images/wizard.png +0 -0
  39. package/package.json +2 -8
  40. package/src/client-components/api.ts +6 -0
  41. package/src/client-components/index.ts +19 -0
  42. package/src/components/ActionButton.tsx +50 -0
  43. package/src/components/Error.tsx +57 -0
  44. package/src/components/ui/CardConnector.tsx +56 -0
  45. package/src/components/ui/button.tsx +62 -0
  46. package/src/components/ui/card.tsx +372 -0
  47. package/src/components/ui/context-menu.tsx +250 -0
  48. package/src/config/config.tsx +917 -0
  49. package/src/config/types.ts +286 -0
  50. package/src/editor/ComponentInfo.tsx +90 -0
  51. package/src/editor/ConfirmationDialog.tsx +103 -0
  52. package/src/editor/ContentTree.tsx +733 -0
  53. package/src/editor/ContextMenu.tsx +230 -0
  54. package/src/editor/Editor.tsx +90 -0
  55. package/src/editor/EditorWarning.tsx +34 -0
  56. package/src/editor/EditorWarnings.tsx +33 -0
  57. package/src/editor/FieldActionsOverlay.tsx +296 -0
  58. package/src/editor/FieldEditorPopup.tsx +65 -0
  59. package/src/editor/FieldHistory.tsx +75 -0
  60. package/src/editor/FieldList.tsx +190 -0
  61. package/src/editor/FieldListField.tsx +391 -0
  62. package/src/editor/FieldListFieldWithFallbacks.tsx +217 -0
  63. package/src/editor/FloatingToolbar.tsx +163 -0
  64. package/src/editor/ImageEditor.tsx +128 -0
  65. package/src/editor/ItemInfo.tsx +90 -0
  66. package/src/editor/LinkEditorDialog.tsx +196 -0
  67. package/src/editor/MainLayout.tsx +95 -0
  68. package/src/editor/MobileLayout.tsx +68 -0
  69. package/src/editor/NewEditorClient.tsx +11 -0
  70. package/src/editor/PictureCropper.tsx +568 -0
  71. package/src/editor/PictureEditor.tsx +301 -0
  72. package/src/editor/PictureEditorDialog.tsx +381 -0
  73. package/src/editor/PublishDialog.ignore +74 -0
  74. package/src/editor/ScrollingContentTree.tsx +68 -0
  75. package/src/editor/Terminal.tsx +227 -0
  76. package/src/editor/Titlebar.tsx +104 -0
  77. package/src/editor/ai/AiPopup.tsx +59 -0
  78. package/src/editor/ai/AiResponseMessage.tsx +106 -0
  79. package/src/editor/ai/AiTerminal.tsx +503 -0
  80. package/src/editor/ai/AiToolCall.tsx +61 -0
  81. package/src/editor/ai/EditorAiTerminal.tsx +20 -0
  82. package/src/editor/ai/GhostWriter.tsx +480 -0
  83. package/src/editor/ai/aiPageModel.ts +108 -0
  84. package/src/editor/ai/editorAiContext.ts +18 -0
  85. package/src/editor/client/AboutDialog.tsx +44 -0
  86. package/src/editor/client/EditorClient.tsx +2241 -0
  87. package/src/editor/client/GenericDialog.tsx +50 -0
  88. package/src/editor/client/editContext.ts +416 -0
  89. package/src/editor/client/helpers.ts +44 -0
  90. package/src/editor/client/itemsRepository.ts +574 -0
  91. package/src/editor/client/operations.ts +768 -0
  92. package/src/editor/client/pageModelBuilder.ts +219 -0
  93. package/src/editor/commands/commands.ts +22 -0
  94. package/src/editor/commands/componentCommands.tsx +431 -0
  95. package/src/editor/commands/createVersionCommand.ts +33 -0
  96. package/src/editor/commands/deleteVersionCommand.ts +71 -0
  97. package/src/editor/commands/itemCommands.tsx +351 -0
  98. package/src/editor/commands/localizeItem/LocalizeItemDialog.tsx +201 -0
  99. package/src/editor/commands/localizeItem/LocalizeItemUtils.ts +27 -0
  100. package/src/editor/commands/undo.ts +39 -0
  101. package/src/editor/component-designer/ComponentDesigner.tsx +70 -0
  102. package/src/editor/component-designer/ComponentDesignerAiTerminal.tsx +11 -0
  103. package/src/editor/component-designer/ComponentDesignerMenu.tsx +91 -0
  104. package/src/editor/component-designer/ComponentEditor.tsx +97 -0
  105. package/src/editor/component-designer/ComponentRenderingCodeEditor.tsx +31 -0
  106. package/src/editor/component-designer/ComponentRenderingEditor.tsx +104 -0
  107. package/src/editor/component-designer/ComponentsDropdown.tsx +39 -0
  108. package/src/editor/component-designer/PlaceholdersEditor.tsx +179 -0
  109. package/src/editor/component-designer/RenderingsDropdown.tsx +36 -0
  110. package/src/editor/component-designer/TemplateEditor.tsx +236 -0
  111. package/src/editor/component-designer/aiContext.ts +23 -0
  112. package/src/editor/componentTreeHelper.tsx +116 -0
  113. package/src/editor/context-menu/CopyMoveMenu.tsx +103 -0
  114. package/src/editor/context-menu/InsertMenu.tsx +347 -0
  115. package/src/editor/control-center/About.tsx +342 -0
  116. package/src/editor/control-center/ControlCenterMenu.tsx +76 -0
  117. package/src/editor/control-center/IndexOverview.tsx +50 -0
  118. package/src/editor/control-center/IndexSettings.tsx +266 -0
  119. package/src/editor/control-center/Info.tsx +104 -0
  120. package/src/editor/control-center/QuotaInfo.tsx +301 -0
  121. package/src/editor/control-center/Status.tsx +113 -0
  122. package/src/editor/control-center/WebSocketMessages.tsx +155 -0
  123. package/src/editor/editor-warnings/ItemLocked.tsx +63 -0
  124. package/src/editor/editor-warnings/NoLanguageWriteAccess.tsx +22 -0
  125. package/src/editor/editor-warnings/NoWorkflowWriteAccess.tsx +23 -0
  126. package/src/editor/editor-warnings/NoWriteAccess.tsx +16 -0
  127. package/src/editor/editor-warnings/ValidationErrors.tsx +54 -0
  128. package/src/editor/field-types/AttachmentEditor.tsx +9 -0
  129. package/src/editor/field-types/CheckboxEditor.tsx +47 -0
  130. package/src/editor/field-types/DropLinkEditor.tsx +80 -0
  131. package/src/editor/field-types/DropListEditor.tsx +84 -0
  132. package/src/editor/field-types/ImageFieldEditor.tsx +65 -0
  133. package/src/editor/field-types/InternalLinkFieldEditor.tsx +188 -0
  134. package/src/editor/field-types/LinkFieldEditor.tsx +85 -0
  135. package/src/editor/field-types/MultiLineText.tsx +82 -0
  136. package/src/editor/field-types/PictureFieldEditor.tsx +121 -0
  137. package/src/editor/field-types/RawEditor.tsx +53 -0
  138. package/src/editor/field-types/ReactQuill.tsx +580 -0
  139. package/src/editor/field-types/RichTextEditor.tsx +22 -0
  140. package/src/editor/field-types/RichTextEditorComponent.tsx +127 -0
  141. package/src/editor/field-types/SingleLineText.tsx +174 -0
  142. package/src/editor/field-types/TreeListEditor.tsx +261 -0
  143. package/src/editor/fieldTypes.ts +140 -0
  144. package/src/editor/media-selector/AiImageSearch.tsx +185 -0
  145. package/src/editor/media-selector/AiImageSearchPrompt.tsx +94 -0
  146. package/src/editor/media-selector/MediaFolderBrowser.tsx +321 -0
  147. package/src/editor/media-selector/MediaSelector.tsx +42 -0
  148. package/src/editor/media-selector/Preview.tsx +14 -0
  149. package/src/editor/media-selector/Thumbnails.tsx +48 -0
  150. package/src/editor/media-selector/TreeSelector.tsx +292 -0
  151. package/src/editor/media-selector/UploadZone.tsx +137 -0
  152. package/src/editor/media-selector/index.ts +8 -0
  153. package/src/editor/menubar/ActionsMenu.tsx +94 -0
  154. package/src/editor/menubar/ActiveUsers.tsx +17 -0
  155. package/src/editor/menubar/ApproveAndPublish.tsx +18 -0
  156. package/src/editor/menubar/BrowseHistory.tsx +28 -0
  157. package/src/editor/menubar/ItemLanguageVersion.tsx +76 -0
  158. package/src/editor/menubar/LanguageSelector.tsx +226 -0
  159. package/src/editor/menubar/Menu.tsx +83 -0
  160. package/src/editor/menubar/NavButtons.tsx +74 -0
  161. package/src/editor/menubar/PageSelector.tsx +278 -0
  162. package/src/editor/menubar/PageViewerControls.tsx +120 -0
  163. package/src/editor/menubar/PreviewSecondaryControls.tsx +18 -0
  164. package/src/editor/menubar/SecondaryControls.tsx +45 -0
  165. package/src/editor/menubar/Separator.tsx +12 -0
  166. package/src/editor/menubar/SiteInfo.tsx +53 -0
  167. package/src/editor/menubar/User.tsx +27 -0
  168. package/src/editor/menubar/VersionSelector.tsx +142 -0
  169. package/src/editor/page-editor-chrome/CommentHighlighting.tsx +307 -0
  170. package/src/editor/page-editor-chrome/CommentHighlightings.tsx +35 -0
  171. package/src/editor/page-editor-chrome/FieldActionIndicator.tsx +59 -0
  172. package/src/editor/page-editor-chrome/FieldActionIndicators.tsx +23 -0
  173. package/src/editor/page-editor-chrome/FieldEditedIndicator.tsx +64 -0
  174. package/src/editor/page-editor-chrome/FieldEditedIndicators.tsx +35 -0
  175. package/src/editor/page-editor-chrome/FrameMenu.tsx +338 -0
  176. package/src/editor/page-editor-chrome/FrameMenus.tsx +48 -0
  177. package/src/editor/page-editor-chrome/InlineEditor.tsx +765 -0
  178. package/src/editor/page-editor-chrome/LockedFieldIndicator.tsx +61 -0
  179. package/src/editor/page-editor-chrome/NoLayout.tsx +36 -0
  180. package/src/editor/page-editor-chrome/PageEditorChrome.tsx +122 -0
  181. package/src/editor/page-editor-chrome/PictureEditorOverlay.tsx +161 -0
  182. package/src/editor/page-editor-chrome/PlaceholderDropZone.tsx +169 -0
  183. package/src/editor/page-editor-chrome/PlaceholderDropZones.tsx +315 -0
  184. package/src/editor/page-editor-chrome/SuggestionHighlighting.tsx +300 -0
  185. package/src/editor/page-editor-chrome/SuggestionHighlightings.tsx +40 -0
  186. package/src/editor/page-editor-chrome/useInlineAICompletion.tsx +828 -0
  187. package/src/editor/page-viewer/DeviceToolbar.tsx +70 -0
  188. package/src/editor/page-viewer/EditorForm.tsx +262 -0
  189. package/src/editor/page-viewer/MiniMap.tsx +362 -0
  190. package/src/editor/page-viewer/PageViewer.tsx +169 -0
  191. package/src/editor/page-viewer/PageViewerFrame.tsx +1022 -0
  192. package/src/editor/page-viewer/pageModelSkeletonBuilder.ts +412 -0
  193. package/src/editor/page-viewer/pageViewContext.ts +186 -0
  194. package/src/editor/pageModel.ts +220 -0
  195. package/src/editor/picture-shared.tsx +53 -0
  196. package/src/editor/reviews/Comment.tsx +308 -0
  197. package/src/editor/reviews/Comments.tsx +125 -0
  198. package/src/editor/reviews/DiffView.tsx +109 -0
  199. package/src/editor/reviews/PreviewInfo.tsx +35 -0
  200. package/src/editor/reviews/Reviews.tsx +280 -0
  201. package/src/editor/reviews/SuggestedEdit.tsx +316 -0
  202. package/src/editor/reviews/reviewCommands.tsx +47 -0
  203. package/src/editor/reviews/useReviews.tsx +70 -0
  204. package/src/editor/services/aiService.ts +173 -0
  205. package/src/editor/services/componentDesignerService.ts +151 -0
  206. package/src/editor/services/contentService.ts +180 -0
  207. package/src/editor/services/editService.ts +488 -0
  208. package/src/editor/services/indexService.ts +24 -0
  209. package/src/editor/services/reviewsService.ts +53 -0
  210. package/src/editor/services/serviceHelper.ts +95 -0
  211. package/src/editor/services/suggestedEditsService.ts +39 -0
  212. package/src/editor/services/systemService.ts +5 -0
  213. package/src/editor/services/translationService.ts +21 -0
  214. package/src/editor/services-server/api.ts +150 -0
  215. package/src/editor/services-server/graphQL.ts +106 -0
  216. package/src/editor/sidebar/ComponentPalette.tsx +161 -0
  217. package/src/editor/sidebar/ComponentTree.tsx +549 -0
  218. package/src/editor/sidebar/Debug.tsx +111 -0
  219. package/src/editor/sidebar/DictionaryEditor.tsx +261 -0
  220. package/src/editor/sidebar/EditHistory.tsx +134 -0
  221. package/src/editor/sidebar/GraphQL.tsx +164 -0
  222. package/src/editor/sidebar/Insert.tsx +35 -0
  223. package/src/editor/sidebar/MainContentTree.tsx +102 -0
  224. package/src/editor/sidebar/Performance.tsx +53 -0
  225. package/src/editor/sidebar/Sessions.tsx +35 -0
  226. package/src/editor/sidebar/Sidebar.tsx +20 -0
  227. package/src/editor/sidebar/SidebarView.tsx +152 -0
  228. package/src/editor/sidebar/Translations.tsx +295 -0
  229. package/src/editor/sidebar/Validation.tsx +102 -0
  230. package/src/editor/sidebar/ViewSelector.tsx +60 -0
  231. package/src/editor/sidebar/Workbox.tsx +209 -0
  232. package/src/editor/ui/CenteredMessage.tsx +7 -0
  233. package/src/editor/ui/CopyMoveTargetSelectorDialog.tsx +81 -0
  234. package/src/editor/ui/CopyToClipboardButton.tsx +24 -0
  235. package/src/editor/ui/DialogButtons.tsx +11 -0
  236. package/src/editor/ui/Icons.tsx +709 -0
  237. package/src/editor/ui/ItemList.tsx +76 -0
  238. package/src/editor/ui/ItemNameDialogNew.tsx +118 -0
  239. package/src/editor/ui/ItemSearch.tsx +159 -0
  240. package/src/editor/ui/PerfectTree.tsx +676 -0
  241. package/src/editor/ui/Section.tsx +35 -0
  242. package/src/editor/ui/SimpleIconButton.tsx +54 -0
  243. package/src/editor/ui/SimpleMenu.tsx +40 -0
  244. package/src/editor/ui/SimpleTable.tsx +60 -0
  245. package/src/editor/ui/SimpleTabs.tsx +60 -0
  246. package/src/editor/ui/SimpleToolbar.tsx +7 -0
  247. package/src/editor/ui/Spinner.tsx +9 -0
  248. package/src/editor/ui/Splitter.tsx +420 -0
  249. package/src/editor/ui/StackedPanels.tsx +134 -0
  250. package/src/editor/ui/Toolbar.tsx +7 -0
  251. package/src/editor/utils/id-helper.ts +3 -0
  252. package/src/editor/utils/insertOptions.ts +69 -0
  253. package/src/editor/utils/itemutils.ts +29 -0
  254. package/src/editor/utils/useMemoDebug.ts +28 -0
  255. package/src/editor/utils.ts +486 -0
  256. package/src/editor/views/CompareView.tsx +245 -0
  257. package/src/editor/views/EditView.tsx +27 -0
  258. package/src/editor/views/ItemEditor.tsx +58 -0
  259. package/src/editor/views/MediaFolderEditView.tsx +66 -0
  260. package/src/editor/views/SingleEditView.tsx +57 -0
  261. package/src/fonts/Geist-Black.woff2 +0 -0
  262. package/src/fonts/Geist-Bold.woff2 +0 -0
  263. package/src/fonts/Geist-ExtraBold.woff2 +0 -0
  264. package/src/fonts/Geist-ExtraLight.woff2 +0 -0
  265. package/src/fonts/Geist-Light.woff2 +0 -0
  266. package/src/fonts/Geist-Medium.woff2 +0 -0
  267. package/src/fonts/Geist-Regular.woff2 +0 -0
  268. package/src/fonts/Geist-SemiBold.woff2 +0 -0
  269. package/src/fonts/Geist-Thin.woff2 +0 -0
  270. package/src/fonts/Geist[wght].woff2 +0 -0
  271. package/src/fonts/index.ts +10 -0
  272. package/src/index.ts +23 -0
  273. package/src/lib/safelist.tsx +16 -0
  274. package/src/lib/utils.ts +6 -0
  275. package/src/page-wizard/PageWizard.tsx +139 -0
  276. package/src/page-wizard/WizardBox.tsx +4 -0
  277. package/src/page-wizard/WizardBoxConnector.tsx +56 -0
  278. package/src/page-wizard/WizardSteps.tsx +458 -0
  279. package/src/page-wizard/service.ts +35 -0
  280. package/src/page-wizard/startPageWizardCommand.ts +26 -0
  281. package/src/page-wizard/steps/BuildPageStep.tsx +259 -0
  282. package/src/page-wizard/steps/CollectStep.tsx +296 -0
  283. package/src/page-wizard/steps/ComponentTypesSelector.tsx +454 -0
  284. package/src/page-wizard/steps/Components.tsx +193 -0
  285. package/src/page-wizard/steps/ContentStep.tsx +890 -0
  286. package/src/page-wizard/steps/EditButton.tsx +34 -0
  287. package/src/page-wizard/steps/FieldEditor.tsx +102 -0
  288. package/src/page-wizard/steps/Generate.tsx +60 -0
  289. package/src/page-wizard/steps/ImagesStep.tsx +382 -0
  290. package/src/page-wizard/steps/LayoutStep.tsx +227 -0
  291. package/src/page-wizard/steps/MetaDataStep.tsx +173 -0
  292. package/src/page-wizard/steps/SelectStep.tsx +281 -0
  293. package/src/page-wizard/steps/schema.ts +180 -0
  294. package/src/page-wizard/steps/usePageCreator.ts +325 -0
  295. package/src/page-wizard/usePageWizard.ts +79 -0
  296. package/src/revision.ts +2 -0
  297. package/src/splash-screen/NewPage.tsx +294 -0
  298. package/src/splash-screen/OpenPage.tsx +113 -0
  299. package/src/splash-screen/RecentPages.tsx +123 -0
  300. package/src/splash-screen/SectionHeadline.tsx +21 -0
  301. package/src/splash-screen/SplashScreen.tsx +195 -0
  302. package/src/tour/Tour.tsx +566 -0
  303. package/src/tour/default-tour.tsx +301 -0
  304. package/src/tour/preview-tour.tsx +128 -0
  305. package/src/types.ts +335 -0
  306. package/styles.css +765 -0
  307. package/tsconfig.build.json +31 -0
  308. package/tsconfig.json +14 -0
@@ -0,0 +1,1022 @@
1
+ import { useEffect, useRef, useState } from "react";
2
+ import { MiniMap } from "./MiniMap";
3
+ import { useEditContext, useEditContextRef } from "../client/editContext";
4
+ import { useDebouncedCallback, useThrottledCallback } from "use-debounce";
5
+ import { PageEditorChrome } from "../page-editor-chrome/PageEditorChrome";
6
+ import { PageViewContext } from "./pageViewContext";
7
+ import morphdom from "morphdom";
8
+ import uuid from "react-uuid";
9
+
10
+ import {
11
+ findComponentRect,
12
+ findFieldElement,
13
+ findNearestComponentId,
14
+ findParentComponentId,
15
+ findParentWithAttribute,
16
+ getAbsolutePosition,
17
+ getFieldDescriptorFromElement,
18
+ findClosestFieldElement,
19
+ } from "../utils";
20
+
21
+ import { getComponentById } from "../componentTreeHelper";
22
+ import { showComponentContextMenu } from "../ContextMenu";
23
+ import { loadFieldButtons } from "../services/editService";
24
+ import { usePathname } from "next/navigation";
25
+ import { EditorWarnings } from "../EditorWarnings";
26
+ import {
27
+ FieldActionsOverlay,
28
+ FieldActionsOverlayRef,
29
+ } from "../FieldActionsOverlay";
30
+ import { FieldButton, FieldDescriptor } from "../pageModel";
31
+
32
+ import { Component, ItemDescriptor } from "../pageModel";
33
+ import { NoLayout } from "../page-editor-chrome/NoLayout";
34
+ import { Spinner } from "../ui/Spinner";
35
+ import { DeviceToolbar } from "./DeviceToolbar";
36
+ import { buildPageModelSkeleton } from "./pageModelSkeletonBuilder";
37
+ declare global {
38
+ interface Window {
39
+ requestRefresh?: (newUri?: string) => boolean;
40
+ }
41
+ }
42
+
43
+ export function PageViewerFrame({
44
+ compareView,
45
+ pageViewContext,
46
+ }: {
47
+ compareView: boolean;
48
+ pageViewContext?: PageViewContext;
49
+ }) {
50
+ const editContext = useEditContext();
51
+
52
+ const pathname = usePathname();
53
+
54
+ const pageViewContextRef = useRef<PageViewContext | undefined>(undefined);
55
+
56
+ if (!editContext || !pageViewContext) return null;
57
+
58
+ useEffect(() => {
59
+ pageViewContextRef.current = pageViewContext;
60
+ }, [pageViewContext]);
61
+
62
+ const editContextRef = useEditContextRef();
63
+
64
+ const iframeRef = pageViewContext.editorIframeRef;
65
+
66
+ const [showSpinner, setShowSpinner] = useState(false);
67
+ const [scroll, setScroll] = useState(0);
68
+ const [showMiniMap, setShowMiniMap] = useState(false);
69
+ const fieldActionsOverlay = useRef<FieldActionsOverlayRef>(null);
70
+ const [contextMenuFieldButtons, setContextMenuFieldButtons] = useState<
71
+ FieldButton[]
72
+ >([]);
73
+ const [contextMenuField, setContextMenuField] = useState<
74
+ FieldDescriptor | undefined
75
+ >();
76
+ const [contextMenuPosition, setContextMenuPosition] = useState<
77
+ { x: number; y: number } | undefined
78
+ >();
79
+ const [preSelectedAction, setPreSelectedAction] = useState<
80
+ FieldButton | undefined
81
+ >();
82
+
83
+ // Clear preSelectedAction when overlay is closed
84
+ useEffect(() => {
85
+ if (
86
+ editContext?.currentOverlay !==
87
+ `${contextMenuField?.fieldId || ""}_generators`
88
+ ) {
89
+ setPreSelectedAction(undefined);
90
+ }
91
+ }, [editContext?.currentOverlay, contextMenuField?.fieldId]);
92
+
93
+ const zoom = pageViewContext.zoom;
94
+ const blockBlurEventRef = useRef(0);
95
+ const [currentItemDescriptor, setCurrentItemDescriptor] = useState<
96
+ ItemDescriptor | undefined
97
+ >(undefined);
98
+
99
+ // Field action handlers for the overlay
100
+ const handleActionClick = async (action: FieldButton): Promise<void> => {
101
+ if (contextMenuField) {
102
+ editContext?.triggerFieldAction(contextMenuField, action);
103
+ }
104
+ };
105
+
106
+ const handleParameterizedActionExecute = async (
107
+ action: FieldButton,
108
+ parameters: Record<string, string>,
109
+ ): Promise<void> => {
110
+ if (contextMenuField) {
111
+ editContext?.triggerFieldAction(contextMenuField, action, parameters);
112
+ }
113
+ };
114
+
115
+ // Note: handleParameterizedActionFromContextMenu is now created inline in handleContextMenu
116
+ // to avoid React state timing issues with contextMenuPosition
117
+
118
+ const pageItemDescriptor = pageViewContext.pageItemDescriptor;
119
+
120
+ useEffect(() => {
121
+ //Workaround for iframeref updates not propagating to usePageViewContext
122
+ pageViewContext.setWorkaround((x) => !x);
123
+ }, [iframeRef.current]);
124
+
125
+ const updateMiniMapVisibility = useDebouncedCallback(() => {
126
+ if (!iframeRef.current) return;
127
+ const iframe = iframeRef.current;
128
+ const doc = iframe.contentDocument;
129
+ if (!doc?.documentElement) return;
130
+ const scrollContainer = doc.scrollingElement || doc.body;
131
+ if (!scrollContainer) return;
132
+
133
+ const contentHeight = scrollContainer.scrollHeight;
134
+ const clientHeight = iframe.clientHeight;
135
+
136
+ const upperThreshold = clientHeight + 100; // show minimap if content exceeds this height
137
+ const lowerThreshold = clientHeight; // hide minimap if content falls below this
138
+
139
+ if (showMiniMap) {
140
+ if (contentHeight <= lowerThreshold) {
141
+ setShowMiniMap(false);
142
+ }
143
+ } else {
144
+ if (contentHeight > upperThreshold) {
145
+ setShowMiniMap(true);
146
+ }
147
+ }
148
+ }, 100);
149
+
150
+ updateMiniMapVisibility();
151
+
152
+ const buildPageModelThrottled = useThrottledCallback(
153
+ buildPageModelSkeleton,
154
+ 500,
155
+ {
156
+ leading: false,
157
+ trailing: true,
158
+ },
159
+ );
160
+
161
+ const [iframeSrc, setIframeSrc] = useState<string>();
162
+
163
+ useEffect(() => {
164
+ const isHeadless = pageViewContext.isHeadless;
165
+ if (!pageItemDescriptor || isHeadless === undefined) return;
166
+
167
+ let renderUrl: URL;
168
+ if (!isHeadless) {
169
+ // Sitecore MVC rendering
170
+ renderUrl = new URL(window.location.href);
171
+ renderUrl.pathname = "/";
172
+
173
+ renderUrl.searchParams.set("sc_itemid", pageItemDescriptor.id);
174
+ renderUrl.searchParams.set("sc_lang", pageItemDescriptor.language);
175
+ renderUrl.searchParams.set(
176
+ "sc_version",
177
+ pageItemDescriptor.version.toString(),
178
+ );
179
+ if (editContext.mode === "preview") {
180
+ renderUrl.searchParams.set("alpaca_preview", "1");
181
+ } else {
182
+ renderUrl.searchParams.set("alpaca_editor", "1");
183
+ }
184
+ renderUrl.searchParams.set("sc_mode", "edit");
185
+ renderUrl.searchParams.set("alpaca_editor", "1");
186
+ renderUrl.searchParams.set("sc_database", "master");
187
+ //renderUrl.searchParams.set(
188
+ // "sc_site",
189
+ // editContext.site?.name || "website"
190
+ // );
191
+ } else {
192
+ // Headless rendering
193
+ console.log("Headless rendering", pageItemDescriptor);
194
+ renderUrl = new URL(window.location.href);
195
+ if (editContext.configuration.services.renderService.path)
196
+ renderUrl.pathname =
197
+ editContext.configuration.services.renderService.path;
198
+
199
+ renderUrl.searchParams.set("itemid", pageItemDescriptor.id);
200
+ renderUrl.searchParams.set("lang", pageItemDescriptor.language);
201
+ renderUrl.searchParams.set(
202
+ "version",
203
+ pageItemDescriptor.version.toString(),
204
+ );
205
+
206
+ renderUrl.searchParams.set(
207
+ "site",
208
+ editContext.pageView.site?.name || "website",
209
+ );
210
+ renderUrl.searchParams.set(
211
+ "mode",
212
+ editContext.mode === "preview" ? "preview" : "edit",
213
+ );
214
+ }
215
+
216
+ renderUrl.searchParams.set("edit_rev", editContext.revision ?? uuid());
217
+
218
+ if (iframeRef.current?.contentWindow?.requestRefresh) {
219
+ console.log("Integration - requesting refresh");
220
+ iframeRef.current?.contentWindow.requestRefresh(renderUrl.toString());
221
+ } else {
222
+ setShowSpinner(true);
223
+ console.log("No integration - reloading frame");
224
+ if (isHeadless) {
225
+ console.log("Setting iframe src", renderUrl.toString());
226
+ setIframeSrc(renderUrl.toString());
227
+ } else {
228
+ const initialLoad =
229
+ currentItemDescriptor?.id !== pageItemDescriptor.id ||
230
+ currentItemDescriptor?.language !== pageItemDescriptor.language ||
231
+ currentItemDescriptor?.version !== pageItemDescriptor.version;
232
+
233
+ loadContent(renderUrl.toString(), initialLoad);
234
+ }
235
+ }
236
+
237
+ setCurrentItemDescriptor(pageItemDescriptor);
238
+ }, [
239
+ pathname,
240
+ editContext.revision,
241
+ pageItemDescriptor,
242
+ pageViewContext.isHeadless,
243
+ editContext.mode,
244
+ ]);
245
+
246
+ useEffect(() => {
247
+ if (editContext.focusedField) {
248
+ if (
249
+ editContext.selection.length > 0 &&
250
+ editContext.focusedField.item.id !== editContext.selection[0]
251
+ )
252
+ return;
253
+
254
+ const fieldElement = findFieldElement(
255
+ iframeRef.current!,
256
+ editContext.focusedField,
257
+ );
258
+ if (fieldElement) {
259
+ const rect = getAbsolutePosition(
260
+ fieldElement as HTMLElement,
261
+ iframeRef.current!,
262
+ );
263
+
264
+ const scrollTop = iframeRef.current?.contentWindow?.scrollY || 0;
265
+ const iframeHeight = iframeRef.current?.getBoundingClientRect().height;
266
+
267
+ const isInViewport =
268
+ rect.y >= scrollTop && rect.y <= scrollTop + (iframeHeight || 0);
269
+
270
+ if (!isInViewport) {
271
+ iframeRef.current?.contentWindow?.scrollTo({
272
+ top: rect.y,
273
+ behavior: "smooth",
274
+ });
275
+ }
276
+ }
277
+ }
278
+ }, [editContext.focusedField]);
279
+
280
+ useEffect(() => {
281
+ if (!editContext.focusedField && editContext.selection.length > 0) {
282
+ const lastSelectedComponent = getComponentById(
283
+ editContext.selection[editContext.selection.length - 1]!,
284
+ pageViewContextRef.current!.page!,
285
+ );
286
+ if (lastSelectedComponent) {
287
+ editContext.setScrollIntoView(lastSelectedComponent.id);
288
+ }
289
+ }
290
+ }, [editContext.selection]);
291
+
292
+ const loadContent = async (href: string, initialLoad: boolean) => {
293
+ console.log("Loading content:", href);
294
+ const start = performance.now();
295
+ if (!href) return;
296
+ const content = await fetch(href);
297
+ const text = await content.text();
298
+ console.log("Content loaded in " + (performance.now() - start) + " ms");
299
+ const doc = iframeRef.current?.contentDocument;
300
+
301
+ if (doc) {
302
+ if (initialLoad) {
303
+ doc.open();
304
+ doc.write(text);
305
+ doc.close();
306
+ } else {
307
+ const parser = new DOMParser();
308
+ const newDoc = parser.parseFromString(text, "text/html");
309
+ morphdom(doc.documentElement, newDoc.documentElement);
310
+ if ((iframeRef.current!.contentWindow as any).XA) {
311
+ console.log("init XA");
312
+ (iframeRef.current!.contentWindow as any).XA.init();
313
+ }
314
+ setShowSpinner(false);
315
+ }
316
+
317
+ injectEditorCSS(doc, editContext.mode !== "preview");
318
+
319
+ setTimeout(() => {
320
+ injectSXAScripts(iframeRef.current!);
321
+ }, 1000);
322
+
323
+ buildPageModelThrottled(doc, editContextRef, pageViewContextRef);
324
+ }
325
+ };
326
+
327
+ useEffect(() => {
328
+ if (
329
+ !editContext.scrollIntoView ||
330
+ !iframeRef.current?.contentDocument?.documentElement
331
+ )
332
+ return;
333
+
334
+ const component = getComponentById(
335
+ editContext.scrollIntoView,
336
+ pageViewContextRef.current!.page!,
337
+ );
338
+
339
+ if (!component) return;
340
+
341
+ const rect = findComponentRect(iframeRef.current!, component, true);
342
+
343
+ if (!rect) return;
344
+
345
+ // Check if element is already in viewport
346
+ const iframeHeight = iframeRef.current.getBoundingClientRect().height;
347
+
348
+ const scrollTop = iframeRef.current?.contentWindow?.scrollY || 0;
349
+ const elementTop = rect.rect.y;
350
+
351
+ const isInViewport =
352
+ elementTop >= scrollTop && elementTop <= scrollTop + iframeHeight;
353
+
354
+ // If already in viewport, no need to scroll
355
+ if (isInViewport) {
356
+ editContext.setScrollIntoView(undefined);
357
+ return;
358
+ }
359
+
360
+ const scrollPosition =
361
+ rect.rect.y - iframeHeight / 2 + rect.rect.height / 2;
362
+
363
+ iframeRef.current?.contentWindow?.scrollTo({
364
+ top: scrollPosition,
365
+ behavior: "smooth",
366
+ });
367
+
368
+ editContext.setScrollIntoView(undefined);
369
+ }, [editContext.scrollIntoView, pageViewContext.page]);
370
+
371
+ useEffect(() => {
372
+ const handleMessage = (message: MessageEvent) => {
373
+ if (message.data.type === "editor-exitFullscreen") {
374
+ editContext.pageView.setFullscreen(false);
375
+ }
376
+ if (message.data.type === "editor-timings") {
377
+ editContext.setTimings(message.data.timings);
378
+ }
379
+ };
380
+ window.addEventListener("message", handleMessage);
381
+
382
+ return () => {
383
+ window.removeEventListener("message", handleMessage);
384
+ };
385
+ }, []);
386
+
387
+ const selecionChangeHandler = useThrottledCallback(() => {
388
+ const sel = iframeRef.current?.contentDocument?.getSelection();
389
+
390
+ if (sel && sel.rangeCount > 0) {
391
+ // Find the field element containing the selection
392
+ const fieldElement = findClosestFieldElement(sel.anchorNode);
393
+
394
+ if (!fieldElement) return;
395
+
396
+ const fieldId = fieldElement.getAttribute("data-fieldid");
397
+ if (!fieldId) return;
398
+
399
+ const range = sel.getRangeAt(0);
400
+
401
+ editContextRef.current?.setFocusedField(
402
+ {
403
+ fieldId: fieldId,
404
+ item: {
405
+ id: fieldElement.getAttribute("data-itemid") || "",
406
+ language: fieldElement.getAttribute("data-language") || "",
407
+ version: parseInt(fieldElement.getAttribute("data-version") || "0"),
408
+ },
409
+ },
410
+ editContext.mode === "edit",
411
+ );
412
+
413
+ // Compute the global offsets relative to the field element.
414
+ const globalStartOffset = getGlobalTextOffset(
415
+ fieldElement,
416
+ range.startContainer,
417
+ range.startOffset,
418
+ );
419
+ const globalEndOffset = getGlobalTextOffset(
420
+ fieldElement,
421
+ range.endContainer,
422
+ range.endOffset,
423
+ );
424
+
425
+ editContextRef.current?.setSelectedRange({
426
+ itemId: fieldElement.getAttribute("data-itemid") || "",
427
+ fieldId: fieldId,
428
+ startOffset: globalStartOffset,
429
+ endOffset: globalEndOffset,
430
+ text: range.toString(),
431
+ });
432
+ }
433
+ }, 100);
434
+
435
+ useEffect(() => {
436
+ const iframe = iframeRef.current;
437
+ if (!iframe) return;
438
+
439
+ const handleIframeMouseDown = async (event: any) => {
440
+ const target = event.target;
441
+
442
+ if (editContextRef.current?.isRefreshing) return;
443
+
444
+ const componentId = findParentComponentId(target);
445
+ if (editContextRef.current?.currentOverlay)
446
+ editContextRef.current?.setCurrentOverlay(undefined);
447
+
448
+ if (componentId) {
449
+ if (event.ctrlKey) {
450
+ const currentSelection = editContextRef.current?.selection || [];
451
+ if (currentSelection.indexOf(componentId) === -1)
452
+ editContextRef.current?.select([...currentSelection, componentId]);
453
+ } else {
454
+ if (editContextRef.current?.selection.indexOf(componentId) === -1) {
455
+ editContextRef.current?.select([componentId]);
456
+ }
457
+ }
458
+
459
+ // if (mode !== "edit") {
460
+ //editContextRef.current?.setScrollIntoView(componentId);
461
+ //}
462
+ } else editContextRef.current?.select([]);
463
+
464
+ if (
465
+ (editContext.mode === "edit" &&
466
+ pageViewContextRef.current?.page?.item.canWriteItem) ||
467
+ editContext.mode === "suggestions"
468
+ ) {
469
+ const fieldElement = findParentWithAttribute(target, "data-fieldid");
470
+ if (fieldElement?.hasAttribute("data-itemid")) {
471
+ blockBlurEventRef.current = Date.now() + 500;
472
+
473
+ const hasLock =
474
+ editContext.mode === "suggestions" ||
475
+ (await editContextRef.current?.setFocusedField(
476
+ getFieldDescriptorFromElement(fieldElement),
477
+ true,
478
+ ));
479
+
480
+ if (
481
+ hasLock &&
482
+ editContextRef.current?.inlineEditingFieldElement !== fieldElement
483
+ ) {
484
+ editContextRef.current?.setInlineEditingFieldElement(fieldElement);
485
+ // Don't prevent default - we want the browser's natural cursor positioning
486
+ }
487
+ }
488
+ }
489
+ //Forward events so primereact overlays can close
490
+ const clickEvent = new MouseEvent("click", {
491
+ view: window,
492
+ bubbles: true,
493
+ cancelable: true,
494
+ clientX: event.clientX,
495
+ clientY: event.clientY,
496
+ });
497
+
498
+ document.dispatchEvent(clickEvent);
499
+ };
500
+
501
+ const handleIframeClick = async (event: any) => {
502
+ const target = event.target;
503
+ // Check if the click target is a link (anchor tag)
504
+ const anchor =
505
+ target.tagName.toLowerCase() === "a" ? target : target.closest("a");
506
+ if (anchor) {
507
+ const href = (anchor as HTMLAnchorElement).href;
508
+
509
+ // Block only navigation links, allow anchor links and javascript links
510
+ if (href && !href.startsWith("#") && !href.startsWith("javascript:")) {
511
+ event.preventDefault();
512
+ }
513
+ }
514
+ };
515
+
516
+ const handleContextMenu = async (event: MouseEvent) => {
517
+ if (editContextRef.current?.isRefreshing) return;
518
+ const target = event.target;
519
+ if (!target) return;
520
+
521
+ if (event.ctrlKey) return;
522
+
523
+ event.preventDefault();
524
+ event.stopPropagation();
525
+
526
+ const componentId = findNearestComponentId(target as HTMLElement);
527
+
528
+ if (componentId) {
529
+ if (!editContextRef.current?.selection.includes(componentId)) {
530
+ editContextRef.current!.select([componentId]);
531
+ }
532
+ }
533
+
534
+ const fieldElement = findParentWithAttribute(
535
+ target as HTMLElement,
536
+ "data-fieldid",
537
+ );
538
+
539
+ const selectedComponents = editContextRef.current?.selection
540
+ .map((id) => getComponentById(id, pageViewContextRef.current!.page!))
541
+ .filter((x) => x) as Component[];
542
+
543
+ const iframeRect = iframe.getBoundingClientRect();
544
+ const adjustedEvent = new MouseEvent("contextmenu", {
545
+ bubbles: true,
546
+ cancelable: true,
547
+ shiftKey: event.shiftKey,
548
+ altKey: event.altKey,
549
+ ctrlKey: event.ctrlKey,
550
+ view: window,
551
+ clientX: event.clientX + iframeRect.x,
552
+ clientY: event.clientY + iframeRect.y,
553
+ });
554
+
555
+ // Store the original context menu position for overlay positioning
556
+ const menuPosition = {
557
+ x: event.clientX + iframeRect.x,
558
+ y: event.clientY + iframeRect.y,
559
+ };
560
+ setContextMenuPosition(menuPosition);
561
+
562
+ console.log("Original event:", event);
563
+ console.log("Adjusted event:", adjustedEvent);
564
+ console.log("Menu position:", menuPosition);
565
+
566
+ const field = fieldElement
567
+ ? getFieldDescriptorFromElement(fieldElement)
568
+ : undefined;
569
+
570
+ const fieldButtons = field ? await loadFieldButtons(field) : [];
571
+
572
+ // Create a handler that has access to the current menu position
573
+ const handleParameterizedActionWithPosition = (
574
+ field: FieldDescriptor,
575
+ action: FieldButton,
576
+ allFieldButtons: FieldButton[],
577
+ event: any,
578
+ ) => {
579
+ setContextMenuField(field);
580
+ setContextMenuFieldButtons([action]);
581
+ setPreSelectedAction(action);
582
+
583
+ // Create a new MouseEvent with the captured position
584
+ const syntheticEvent = new MouseEvent("click", {
585
+ bubbles: true,
586
+ cancelable: true,
587
+ view: window,
588
+ clientX: menuPosition.x,
589
+ clientY: menuPosition.y,
590
+ screenX: menuPosition.x,
591
+ screenY: menuPosition.y,
592
+ });
593
+
594
+ // Add target property to the event for proper positioning
595
+ Object.defineProperty(syntheticEvent, "target", {
596
+ value: document.body,
597
+ enumerable: true,
598
+ });
599
+ console.log("About to show overlay with position:", {
600
+ menuPosition,
601
+ syntheticEvent,
602
+ preSelectedAction: action,
603
+ });
604
+
605
+ // Add a small delay to ensure context menu has closed
606
+ setTimeout(() => {
607
+ console.log("Showing overlay with position:", {
608
+ menuPosition,
609
+ syntheticEvent,
610
+ preSelectedAction: action,
611
+ });
612
+
613
+ // Create a temporary element at the exact position for better positioning
614
+ const tempElement = document.createElement("div");
615
+ tempElement.style.position = "fixed";
616
+ tempElement.style.left = menuPosition.x + "px";
617
+ tempElement.style.top = menuPosition.y + "px";
618
+ tempElement.style.width = "1px";
619
+ tempElement.style.height = "1px";
620
+ tempElement.style.visibility = "hidden";
621
+ tempElement.style.pointerEvents = "none";
622
+ document.body.appendChild(tempElement);
623
+
624
+ // Create event targeting the positioned element
625
+ const positionedEvent = new MouseEvent("click", {
626
+ bubbles: true,
627
+ cancelable: true,
628
+ view: window,
629
+ clientX: menuPosition.x,
630
+ clientY: menuPosition.y,
631
+ });
632
+ Object.defineProperty(positionedEvent, "target", {
633
+ value: tempElement,
634
+ enumerable: true,
635
+ });
636
+
637
+ fieldActionsOverlay.current?.show(positionedEvent, action);
638
+
639
+ // Clean up the temporary element after overlay is shown
640
+ setTimeout(() => {
641
+ document.body.removeChild(tempElement);
642
+ }, 1000);
643
+ }, 100);
644
+ };
645
+
646
+ const showMenu = await showComponentContextMenu(
647
+ selectedComponents,
648
+ editContextRef.current!,
649
+ field,
650
+ fieldButtons,
651
+ handleParameterizedActionWithPosition,
652
+ );
653
+ if (showMenu) showMenu(adjustedEvent);
654
+ };
655
+
656
+ const handleLoad = () => {
657
+ setShowSpinner(false);
658
+ const iframeDocument =
659
+ iframe!.contentDocument || iframe!.contentWindow?.document;
660
+ if (iframeDocument) {
661
+ iframeDocument.documentElement.addEventListener(
662
+ "mousedown",
663
+ handleIframeMouseDown,
664
+ );
665
+ iframeDocument.documentElement.addEventListener(
666
+ "click",
667
+ handleIframeClick,
668
+ );
669
+ iframe.contentWindow?.addEventListener(
670
+ "contextmenu",
671
+ handleContextMenu,
672
+ true,
673
+ );
674
+
675
+ const scrollContainer =
676
+ iframe.contentWindow?.document.scrollingElement ||
677
+ iframe.contentWindow?.document.body;
678
+ scrollContainer?.addEventListener("scroll", () => {
679
+ const scrollTop = scrollContainer.scrollTop || 0;
680
+ scrollHandler(scrollTop);
681
+ });
682
+ iframe.contentWindow?.addEventListener("scroll", () => {
683
+ const scrollTop = scrollContainer?.scrollTop || 0;
684
+ scrollHandler(scrollTop);
685
+ });
686
+
687
+ iframeDocument.addEventListener(
688
+ "selectionchange",
689
+ selecionChangeHandler,
690
+ );
691
+
692
+ iframeDocument.addEventListener("keydown", (event: KeyboardEvent) => {
693
+ editContextRef.current?.handleKeyDown(event);
694
+ });
695
+
696
+ iframeDocument.documentElement?.addEventListener(
697
+ "blur",
698
+ () => {
699
+ // Block blur event if it was triggered by clicking on a field
700
+ if (blockBlurEventRef.current < Date.now()) {
701
+ //editContext.setFocusedField(undefined, false);
702
+ editContext.setInlineEditingFieldElement(undefined);
703
+ } else {
704
+ blockBlurEventRef.current = 0;
705
+ }
706
+ },
707
+ true,
708
+ );
709
+
710
+ const observer = new MutationObserver((records) => {
711
+ // Ignore all text field edits
712
+ if (
713
+ records.some(
714
+ (x) =>
715
+ !(
716
+ x &&
717
+ "getAttribute" in x.target &&
718
+ (x.target as Element).getAttribute("data-fieldid") &&
719
+ (x.target as Element).getAttribute("data-itemid")
720
+ ),
721
+ )
722
+ ) {
723
+ buildPageModelThrottled(
724
+ iframeDocument,
725
+ editContextRef,
726
+ pageViewContextRef,
727
+ );
728
+ }
729
+ });
730
+
731
+ observer.observe(iframeDocument!, {
732
+ childList: true, // observe direct children changes
733
+ subtree: true, // observe all descendants changes
734
+ characterData: false, // observe text changes
735
+
736
+ //attributes: true, // observe attribute changes (like style or class)
737
+ });
738
+
739
+ buildPageModelThrottled(
740
+ iframeDocument,
741
+ editContextRef,
742
+ pageViewContextRef,
743
+ );
744
+ }
745
+ };
746
+
747
+ if (iframe) {
748
+ iframe.addEventListener("load", handleLoad);
749
+ }
750
+
751
+ // Cleanup function
752
+ return () => {
753
+ if (iframe) {
754
+ iframe.removeEventListener("load", handleLoad);
755
+
756
+ const iframeDocument =
757
+ iframe.contentDocument || iframe.contentWindow?.document;
758
+ if (iframeDocument) {
759
+ iframeDocument.documentElement.removeEventListener(
760
+ "click",
761
+ handleIframeMouseDown,
762
+ );
763
+ iframeDocument.documentElement.removeEventListener(
764
+ "contextmenu",
765
+ handleContextMenu,
766
+ );
767
+ }
768
+ }
769
+ };
770
+ }, [iframeRef.current]);
771
+
772
+ useEffect(() => {
773
+ iframeRef.current?.contentWindow?.postMessage(
774
+ { type: "componentsSelected", componentIds: editContext.selection },
775
+ "*",
776
+ );
777
+ }, [editContext.selection]);
778
+
779
+ const updateScrollPosition = (e: number) => {
780
+ setScroll(e);
781
+ if (!compareView) pageViewContextRef.current?.setScroll(e);
782
+ };
783
+
784
+ useEffect(() => {
785
+ if (!iframeRef.current) return;
786
+ const body = iframeRef.current.contentWindow?.document.documentElement;
787
+ if (!body) return;
788
+ body.style.transform = `scale(${zoom})`;
789
+ body.style.transformOrigin = "top center";
790
+ body.style.overflow = "auto";
791
+ //body.style.height = `${body.scrollHeight / zoom}px`;
792
+ }, [zoom]);
793
+
794
+ const scrollHandler = useThrottledCallback(updateScrollPosition, 100);
795
+
796
+ if (pageViewContext.page?.item && !pageViewContext.page?.item.hasLayout) {
797
+ return <NoLayout />;
798
+ }
799
+
800
+ const deviceHeight =
801
+ pageViewContext.device === "desktop" || !pageViewContext.deviceHeight
802
+ ? "100%"
803
+ : pageViewContext.deviceHeight || 640;
804
+
805
+ return (
806
+ <div className="relative flex h-full w-full flex-col items-center bg-slate-100 select-none">
807
+ {!editContext.pageView.fullscreen && (
808
+ <EditorWarnings item={pageViewContext.page?.item} />
809
+ )}
810
+ {pageViewContext.device !== "desktop" && (
811
+ <DeviceToolbar
812
+ pageViewContext={pageViewContext}
813
+ configuration={editContext.configuration}
814
+ />
815
+ )}
816
+ <div
817
+ className="relative flex flex-1 select-none"
818
+ style={{
819
+ width:
820
+ pageViewContext.device === "desktop" ||
821
+ pageViewContext.device === "Responsive"
822
+ ? "100%"
823
+ : (pageViewContext.deviceWidth || 640) +
824
+ editContext.configuration.outline.width +
825
+ "px",
826
+ }}
827
+ >
828
+ <div className="relative h-full w-full">
829
+ <iframe
830
+ ref={iframeRef}
831
+ className="page-iframe h-full w-full bg-white"
832
+ style={{ height: deviceHeight }}
833
+ src={iframeSrc}
834
+ data-testid="pageEditoriframe"
835
+ />
836
+
837
+ {iframeRef.current && (
838
+ <PageEditorChrome
839
+ iframe={iframeRef.current}
840
+ compareView={compareView}
841
+ pageViewContext={pageViewContext}
842
+ />
843
+ )}
844
+ {pageViewContext.deviceHeight && pageViewContext.device && (
845
+ <div className="relative z-40 h-full w-full bg-slate-100"></div>
846
+ )}
847
+ </div>
848
+ {!editContext.pageView.fullscreen && showMiniMap && (
849
+ <MiniMap
850
+ compareView={compareView}
851
+ scroll={scroll}
852
+ mainViewIframeRef={iframeRef}
853
+ pageViewContext={pageViewContext}
854
+ deviceHeight={
855
+ pageViewContext.device === "Desktop"
856
+ ? undefined
857
+ : pageViewContext.deviceHeight
858
+ }
859
+ />
860
+ )}
861
+ {showSpinner && (
862
+ <>
863
+ <div className="absolute top-0 left-0 h-full w-full bg-slate-100/50"></div>
864
+ <div className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 transform">
865
+ <Spinner />
866
+ </div>
867
+ </>
868
+ )}
869
+ </div>
870
+ <FieldActionsOverlay
871
+ ref={fieldActionsOverlay}
872
+ generatorButtons={contextMenuFieldButtons}
873
+ onActionClick={handleActionClick}
874
+ onParameterizedActionExecute={handleParameterizedActionExecute}
875
+ currentOverlay={editContext?.currentOverlay}
876
+ fieldId={contextMenuField?.fieldId || ""}
877
+ setCurrentOverlay={editContext?.setCurrentOverlay || (() => {})}
878
+ preSelectedAction={preSelectedAction}
879
+ />
880
+ </div>
881
+ );
882
+ }
883
+
884
+ function injectEditorCSS(iframeDocument: Document, editMode: boolean) {
885
+ if (!iframeDocument) return;
886
+ const style = iframeDocument.createElement("style");
887
+
888
+ style.textContent = editMode
889
+ ? `[contentEditable]:empty:before {
890
+ content: attr(placeholder);
891
+ opacity: 0.6;
892
+ }
893
+
894
+ .scChromeData, code {
895
+ display: none;
896
+ }
897
+
898
+ [contenteditable] {
899
+ outline: 0px solid transparent;
900
+ }
901
+ `
902
+ : `
903
+ [contenteditable] {
904
+ outline: 0px solid transparent;
905
+ }
906
+ .scChromeData, code {
907
+ display: none;
908
+ }
909
+ `;
910
+
911
+ if (iframeDocument && iframeDocument.head) {
912
+ iframeDocument.head.appendChild(style);
913
+ }
914
+ }
915
+
916
+ function injectSXAScripts(iframe: HTMLIFrameElement): void {
917
+ if (!iframe) return;
918
+ const iframeDocument = iframe.contentDocument;
919
+ const iframeWindow = iframe.contentWindow as any;
920
+ if (!iframeDocument || !iframeWindow) return;
921
+
922
+ iframeWindow.Sitecore = {
923
+ PageModes: {
924
+ HoverFrame: {
925
+ extend: () => {
926
+ // console.log("extend hoverframe");
927
+ },
928
+ },
929
+ ChromeManager: {
930
+ chromes: () => {
931
+ // console.log("chromes");
932
+ return [];
933
+ },
934
+ resetChromes: () => {
935
+ // console.log("resetChromes");
936
+ iframeWindow.XA.init();
937
+ },
938
+ chromesReseted: {
939
+ observe: () => {
940
+ // console.log("chromesReseted.observe");
941
+ },
942
+ },
943
+ // Dummy selectionChanged with an _callbacks array
944
+ selectionChanged: {
945
+ _callbacks: [] as any[],
946
+ // Optionally, you can add a method to trigger all callbacks if needed.
947
+ trigger: function (...args: any[]): void {
948
+ this._callbacks.forEach((callback: Function) => callback(...args));
949
+ },
950
+ },
951
+ },
952
+
953
+ ChromeControls: function () {},
954
+ ChromeTypes: {
955
+ PlaceholderSorting: function () {},
956
+ Rendering: function () {},
957
+ },
958
+ },
959
+ };
960
+
961
+ // Option 1: Use main window's jQuery if available.
962
+ if (iframeWindow.jQuery) {
963
+ // Be cautious: if the iframe's document is different, it's better to load jQuery within the iframe.
964
+ iframeWindow.$sc = iframeWindow.jQuery;
965
+ } else {
966
+ // Option 2: Dynamically load jQuery in the iframe.
967
+ const jqueryScript = iframeDocument.createElement("script");
968
+ jqueryScript.type = "text/javascript";
969
+ jqueryScript.src = "https://code.jquery.com/jquery-3.6.0.min.js"; // Use the version you prefer.
970
+ jqueryScript.onload = () => {
971
+ // Once loaded, assign it to $sc using noConflict to avoid global collisions.
972
+ iframeWindow.$sc = iframeWindow.jQuery.noConflict();
973
+ // console.log("jQuery loaded in iframe and assigned to $sc");
974
+ };
975
+ (iframeDocument.head || iframeDocument.body).appendChild(jqueryScript);
976
+ }
977
+
978
+ // Add a dummy implementation for renderExpandCommand on the ChromeControls prototype.
979
+ iframeWindow.Sitecore.PageModes.ChromeControls.prototype.renderExpandCommand =
980
+ function () {
981
+ console.log("renderExpandCommand called");
982
+ // Return a dummy value if needed. For example, you might return a dummy HTML string.
983
+ return "<div>Dummy Expand Command</div>";
984
+ };
985
+
986
+ iframeWindow.Sitecore.PageModes.ChromeTypes.PlaceholderSorting.prototype.insertSortingHandle =
987
+ function () {
988
+ console.log("insertSortingHandle called");
989
+ };
990
+ }
991
+
992
+ /**
993
+ * Calculates the global offset of a given target node and its local offset,
994
+ * relative to the container's complete text content.
995
+ *
996
+ * @param container - The container element that holds the text nodes.
997
+ * @param targetNode - The text node where the selection starts or ends.
998
+ * @param localOffset - The offset within the target text node.
999
+ * @returns The computed global offset.
1000
+ */
1001
+ function getGlobalTextOffset(
1002
+ container: Node,
1003
+ targetNode: Node,
1004
+ localOffset: number,
1005
+ ): number {
1006
+ let globalOffset = 0;
1007
+ const walker = document.createTreeWalker(
1008
+ container,
1009
+ NodeFilter.SHOW_TEXT,
1010
+ null,
1011
+ );
1012
+ while (walker.nextNode()) {
1013
+ const currentNode = walker.currentNode;
1014
+ // If we've reached the target node, add the local offset and return.
1015
+ if (currentNode === targetNode) {
1016
+ return globalOffset + localOffset;
1017
+ }
1018
+ // Otherwise, add the length of this text node.
1019
+ globalOffset += (currentNode.textContent || "").length;
1020
+ }
1021
+ return globalOffset;
1022
+ }