@parhelia/core 0.1.12882 → 0.1.12884

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 (414) hide show
  1. package/dist/agents-view/AgentsSidebar.js +1 -1
  2. package/dist/agents-view/AgentsSidebar.js.map +1 -1
  3. package/dist/agents-view/AgentsTitlebar.d.ts +1 -1
  4. package/dist/agents-view/AgentsTitlebar.js +3 -6
  5. package/dist/agents-view/AgentsTitlebar.js.map +1 -1
  6. package/dist/agents-view/AgentsView.d.ts +2 -2
  7. package/dist/agents-view/AgentsView.js +2 -2
  8. package/dist/agents-view/AgentsView.js.map +1 -1
  9. package/dist/agents-view/AgentsWorkspaceView.js +1 -12
  10. package/dist/agents-view/AgentsWorkspaceView.js.map +1 -1
  11. package/dist/agents-view/CreateAgentView.d.ts +1 -1
  12. package/dist/agents-view/CreateAgentView.js +1 -1
  13. package/dist/agents-view/DateAgentsGroup.js +12 -1
  14. package/dist/agents-view/DateAgentsGroup.js.map +1 -1
  15. package/dist/agents-view/ProfileAgentsGroup.js +16 -4
  16. package/dist/agents-view/ProfileAgentsGroup.js.map +1 -1
  17. package/dist/components/ui/card.d.ts +3 -1
  18. package/dist/components/ui/card.js +2 -2
  19. package/dist/components/ui/card.js.map +1 -1
  20. package/dist/components/ui/checkbox.js +1 -1
  21. package/dist/components/ui/checkbox.js.map +1 -1
  22. package/dist/components/ui/context-menu.d.ts +2 -1
  23. package/dist/components/ui/context-menu.js +6 -3
  24. package/dist/components/ui/context-menu.js.map +1 -1
  25. package/dist/components/ui/input.js +2 -2
  26. package/dist/components/ui/input.js.map +1 -1
  27. package/dist/components/ui/select.js +1 -1
  28. package/dist/components/ui/select.js.map +1 -1
  29. package/dist/components/ui/textarea.js +2 -2
  30. package/dist/components/ui/textarea.js.map +1 -1
  31. package/dist/config/config.js +107 -12
  32. package/dist/config/config.js.map +1 -1
  33. package/dist/editor/ContextMenu.d.ts +1 -0
  34. package/dist/editor/ContextMenu.js +4 -4
  35. package/dist/editor/ContextMenu.js.map +1 -1
  36. package/dist/editor/FieldActionsOverlay.d.ts +0 -1
  37. package/dist/editor/FieldActionsOverlay.js +1 -45
  38. package/dist/editor/FieldActionsOverlay.js.map +1 -1
  39. package/dist/editor/FieldHistory.d.ts +2 -1
  40. package/dist/editor/FieldHistory.js +13 -12
  41. package/dist/editor/FieldHistory.js.map +1 -1
  42. package/dist/editor/FieldListField.d.ts +1 -1
  43. package/dist/editor/FieldListField.js +24 -36
  44. package/dist/editor/FieldListField.js.map +1 -1
  45. package/dist/editor/ImageEditor.d.ts +6 -1
  46. package/dist/editor/ImageEditor.js +19 -3
  47. package/dist/editor/ImageEditor.js.map +1 -1
  48. package/dist/editor/LinkEditorDialog.d.ts +9 -2
  49. package/dist/editor/LinkEditorDialog.js +174 -70
  50. package/dist/editor/LinkEditorDialog.js.map +1 -1
  51. package/dist/editor/MainLayout.js +49 -6
  52. package/dist/editor/MainLayout.js.map +1 -1
  53. package/dist/editor/MobileLayout.js +33 -1
  54. package/dist/editor/MobileLayout.js.map +1 -1
  55. package/dist/editor/PictureCropper.js +45 -28
  56. package/dist/editor/PictureCropper.js.map +1 -1
  57. package/dist/editor/PictureEditor.d.ts +2 -1
  58. package/dist/editor/PictureEditor.js +5 -14
  59. package/dist/editor/PictureEditor.js.map +1 -1
  60. package/dist/editor/ai/AgentProfileSelector.js +7 -7
  61. package/dist/editor/ai/AgentProfileSelector.js.map +1 -1
  62. package/dist/editor/ai/Agents.js +20 -6
  63. package/dist/editor/ai/Agents.js.map +1 -1
  64. package/dist/editor/ai/GuidanceOverlay.js +1 -11
  65. package/dist/editor/ai/GuidanceOverlay.js.map +1 -1
  66. package/dist/editor/ai/InlineAiDialog.d.ts +1 -0
  67. package/dist/editor/ai/InlineAiDialog.js +254 -202
  68. package/dist/editor/ai/InlineAiDialog.js.map +1 -1
  69. package/dist/editor/ai/InlineAiTextEditTooltip.d.ts +8 -0
  70. package/dist/editor/ai/InlineAiTextEditTooltip.js +10 -0
  71. package/dist/editor/ai/InlineAiTextEditTooltip.js.map +1 -0
  72. package/dist/editor/ai/InlineAiTrigger.js +158 -31
  73. package/dist/editor/ai/InlineAiTrigger.js.map +1 -1
  74. package/dist/editor/ai/dialogs/capturePageDom.js +66 -36
  75. package/dist/editor/ai/dialogs/capturePageDom.js.map +1 -1
  76. package/dist/editor/ai/dialogs/capturePageScreenshot.js +281 -162
  77. package/dist/editor/ai/dialogs/capturePageScreenshot.js.map +1 -1
  78. package/dist/editor/ai/inlineAiTextEditLabels.d.ts +2 -0
  79. package/dist/editor/ai/inlineAiTextEditLabels.js +8 -0
  80. package/dist/editor/ai/inlineAiTextEditLabels.js.map +1 -0
  81. package/dist/editor/ai/prepareInlineAiTextSelection.d.ts +5 -0
  82. package/dist/editor/ai/prepareInlineAiTextSelection.js +86 -0
  83. package/dist/editor/ai/prepareInlineAiTextSelection.js.map +1 -0
  84. package/dist/editor/ai/terminal/agentSessionState.d.ts +3 -0
  85. package/dist/editor/ai/terminal/agentSessionState.js +3 -1
  86. package/dist/editor/ai/terminal/agentSessionState.js.map +1 -1
  87. package/dist/editor/ai/terminal/agentStartRequest.d.ts +2 -1
  88. package/dist/editor/ai/terminal/agentStartRequest.js +2 -1
  89. package/dist/editor/ai/terminal/agentStartRequest.js.map +1 -1
  90. package/dist/editor/ai/terminal/components/AgentCostDisplay.js +1 -1
  91. package/dist/editor/ai/terminal/components/AgentCostDisplay.js.map +1 -1
  92. package/dist/editor/ai/terminal/components/AgentDocumentList.d.ts +7 -0
  93. package/dist/editor/ai/terminal/components/AgentDocumentList.js +55 -13
  94. package/dist/editor/ai/terminal/components/AgentDocumentList.js.map +1 -1
  95. package/dist/editor/ai/terminal/components/AgentEditHistoryButton.d.ts +5 -0
  96. package/dist/editor/ai/terminal/components/AgentEditHistoryButton.js +12 -0
  97. package/dist/editor/ai/terminal/components/AgentEditHistoryButton.js.map +1 -0
  98. package/dist/editor/ai/terminal/components/AgentFullPromptControls.d.ts +3 -1
  99. package/dist/editor/ai/terminal/components/AgentFullPromptControls.js +22 -14
  100. package/dist/editor/ai/terminal/components/AgentFullPromptControls.js.map +1 -1
  101. package/dist/editor/ai/terminal/components/AgentModeSelector.js +4 -4
  102. package/dist/editor/ai/terminal/components/AgentModeSelector.js.map +1 -1
  103. package/dist/editor/ai/terminal/components/AgentPromptActionButtons.js +4 -4
  104. package/dist/editor/ai/terminal/components/AgentPromptActionButtons.js.map +1 -1
  105. package/dist/editor/ai/terminal/components/AgentPromptComposer.js +1 -1
  106. package/dist/editor/ai/terminal/components/AgentPromptComposer.js.map +1 -1
  107. package/dist/editor/ai/terminal/components/AgentPromptInputArea.d.ts +2 -1
  108. package/dist/editor/ai/terminal/components/AgentPromptInputArea.js +8 -11
  109. package/dist/editor/ai/terminal/components/AgentPromptInputArea.js.map +1 -1
  110. package/dist/editor/ai/terminal/components/AgentPromptTrayPopovers.d.ts +1 -4
  111. package/dist/editor/ai/terminal/components/AgentPromptTrayPopovers.js +31 -14
  112. package/dist/editor/ai/terminal/components/AgentPromptTrayPopovers.js.map +1 -1
  113. package/dist/editor/ai/terminal/components/AgentSettingsPopover.js +1 -1
  114. package/dist/editor/ai/terminal/components/AgentSettingsPopover.js.map +1 -1
  115. package/dist/editor/ai/terminal/components/AgentTerminalFullLayout.d.ts +2 -1
  116. package/dist/editor/ai/terminal/components/AgentTerminalFullLayout.js +2 -4
  117. package/dist/editor/ai/terminal/components/AgentTerminalFullLayout.js.map +1 -1
  118. package/dist/editor/ai/terminal/components/AgentTerminalMessageGroups.js +1 -1
  119. package/dist/editor/ai/terminal/components/AgentTerminalMessageGroups.js.map +1 -1
  120. package/dist/editor/ai/terminal/components/AgentTerminalView.js +13 -2
  121. package/dist/editor/ai/terminal/components/AgentTerminalView.js.map +1 -1
  122. package/dist/editor/ai/terminal/components/AiResponseMessage.js +11 -9
  123. package/dist/editor/ai/terminal/components/AiResponseMessage.js.map +1 -1
  124. package/dist/editor/ai/terminal/components/ContextInfoBar.js +22 -2
  125. package/dist/editor/ai/terminal/components/ContextInfoBar.js.map +1 -1
  126. package/dist/editor/ai/terminal/components/QueuedPromptsPanel.js +37 -26
  127. package/dist/editor/ai/terminal/components/QueuedPromptsPanel.js.map +1 -1
  128. package/dist/editor/ai/terminal/components/ToolCallDisplay.js +3 -1
  129. package/dist/editor/ai/terminal/components/ToolCallDisplay.js.map +1 -1
  130. package/dist/editor/ai/terminal/components/UserMessage.d.ts +2 -1
  131. package/dist/editor/ai/terminal/components/UserMessage.js +144 -8
  132. package/dist/editor/ai/terminal/components/UserMessage.js.map +1 -1
  133. package/dist/editor/ai/terminal/useAgentPromptComposerHandlers.js +1 -1
  134. package/dist/editor/ai/terminal/useAgentPromptComposerHandlers.js.map +1 -1
  135. package/dist/editor/ai/terminal/useAgentSessionSync.d.ts +1 -0
  136. package/dist/editor/ai/terminal/useAgentSubmitHandlers.d.ts +3 -1
  137. package/dist/editor/ai/terminal/useAgentSubmitHandlers.js +9 -3
  138. package/dist/editor/ai/terminal/useAgentSubmitHandlers.js.map +1 -1
  139. package/dist/editor/ai/terminal/useAgentTerminalController.js +7 -0
  140. package/dist/editor/ai/terminal/useAgentTerminalController.js.map +1 -1
  141. package/dist/editor/ai/terminal/useAgentTerminalUiState.js +1 -1
  142. package/dist/editor/ai/terminal/useAgentTerminalUiState.js.map +1 -1
  143. package/dist/editor/ai/terminal/useAgentUserMessageSocketHandler.js +3 -1
  144. package/dist/editor/ai/terminal/useAgentUserMessageSocketHandler.js.map +1 -1
  145. package/dist/editor/ai/useActiveAgentConversation.d.ts +3 -0
  146. package/dist/editor/ai/useActiveAgentConversation.js +32 -0
  147. package/dist/editor/ai/useActiveAgentConversation.js.map +1 -0
  148. package/dist/editor/ai/useInlineAiPosition.d.ts +10 -2
  149. package/dist/editor/ai/useInlineAiPosition.js +32 -71
  150. package/dist/editor/ai/useInlineAiPosition.js.map +1 -1
  151. package/dist/editor/ai-image-editor/AiImageResultOverlay.js +30 -62
  152. package/dist/editor/ai-image-editor/AiImageResultOverlay.js.map +1 -1
  153. package/dist/editor/bridge/BridgeClient.d.ts +80 -0
  154. package/dist/editor/bridge/BridgeClient.js +417 -0
  155. package/dist/editor/bridge/BridgeClient.js.map +1 -0
  156. package/dist/editor/client/EditorShell.d.ts +5 -1
  157. package/dist/editor/client/EditorShell.js +295 -127
  158. package/dist/editor/client/EditorShell.js.map +1 -1
  159. package/dist/editor/client/editContext.d.ts +58 -5
  160. package/dist/editor/client/editContext.js.map +1 -1
  161. package/dist/editor/client/fieldModificationStore.d.ts +1 -0
  162. package/dist/editor/client/fieldModificationStore.js +7 -2
  163. package/dist/editor/client/fieldModificationStore.js.map +1 -1
  164. package/dist/editor/client/hooks/useSocketMessageHandler.js +14 -17
  165. package/dist/editor/client/hooks/useSocketMessageHandler.js.map +1 -1
  166. package/dist/editor/client/itemsRepository.d.ts +2 -0
  167. package/dist/editor/client/itemsRepository.js +18 -9
  168. package/dist/editor/client/itemsRepository.js.map +1 -1
  169. package/dist/editor/client/operations.d.ts +1 -1
  170. package/dist/editor/client/operations.js +67 -21
  171. package/dist/editor/client/operations.js.map +1 -1
  172. package/dist/editor/client/pageModelBuilder.js +24 -7
  173. package/dist/editor/client/pageModelBuilder.js.map +1 -1
  174. package/dist/editor/client/ui/EditorChrome.js +1 -1
  175. package/dist/editor/client/ui/EditorChrome.js.map +1 -1
  176. package/dist/editor/commands/componentCommands.d.ts +3 -1
  177. package/dist/editor/commands/componentCommands.js +8 -3
  178. package/dist/editor/commands/componentCommands.js.map +1 -1
  179. package/dist/editor/field-types/DateFieldEditor.js +1 -1
  180. package/dist/editor/field-types/DateFieldEditor.js.map +1 -1
  181. package/dist/editor/field-types/DateTimeFieldEditor.js +1 -1
  182. package/dist/editor/field-types/DateTimeFieldEditor.js.map +1 -1
  183. package/dist/editor/field-types/DropLinkEditor.js +1 -1
  184. package/dist/editor/field-types/DropLinkEditor.js.map +1 -1
  185. package/dist/editor/field-types/DropListEditor.js +1 -1
  186. package/dist/editor/field-types/DropListEditor.js.map +1 -1
  187. package/dist/editor/field-types/ImageFieldEditor.js +1 -1
  188. package/dist/editor/field-types/ImageFieldEditor.js.map +1 -1
  189. package/dist/editor/field-types/InternalLinkFieldEditor.js +1 -1
  190. package/dist/editor/field-types/InternalLinkFieldEditor.js.map +1 -1
  191. package/dist/editor/field-types/LinkFieldEditor.js +15 -3
  192. package/dist/editor/field-types/LinkFieldEditor.js.map +1 -1
  193. package/dist/editor/field-types/MultiLineText.js +11 -4
  194. package/dist/editor/field-types/MultiLineText.js.map +1 -1
  195. package/dist/editor/field-types/NameValueListEditor.js +1 -1
  196. package/dist/editor/field-types/NameValueListEditor.js.map +1 -1
  197. package/dist/editor/field-types/PictureFieldEditor.js +2 -2
  198. package/dist/editor/field-types/PictureFieldEditor.js.map +1 -1
  199. package/dist/editor/field-types/RawEditor.js +9 -2
  200. package/dist/editor/field-types/RawEditor.js.map +1 -1
  201. package/dist/editor/field-types/RichTextEditorComponent.js +170 -77
  202. package/dist/editor/field-types/RichTextEditorComponent.js.map +1 -1
  203. package/dist/editor/field-types/SingleLineText.js +10 -3
  204. package/dist/editor/field-types/SingleLineText.js.map +1 -1
  205. package/dist/editor/field-types/TreeListEditor.js +1 -1
  206. package/dist/editor/field-types/TreeListEditor.js.map +1 -1
  207. package/dist/editor/field-types/richtext/bridgeRichTextProfile.d.ts +21 -0
  208. package/dist/editor/field-types/richtext/bridgeRichTextProfile.js +96 -0
  209. package/dist/editor/field-types/richtext/bridgeRichTextProfile.js.map +1 -0
  210. package/dist/editor/field-types/richtext/components/ReactSlate.css +44 -6
  211. package/dist/editor/field-types/richtext/components/ReactSlate.js +191 -36
  212. package/dist/editor/field-types/richtext/components/ReactSlate.js.map +1 -1
  213. package/dist/editor/field-types/richtext/components/SimpleRichTextEditor.css +5 -2
  214. package/dist/editor/field-types/richtext/components/SimpleToolbar.js +5 -4
  215. package/dist/editor/field-types/richtext/components/SimpleToolbar.js.map +1 -1
  216. package/dist/editor/field-types/richtext/contextMenuFactory.d.ts +2 -15
  217. package/dist/editor/field-types/richtext/contextMenuFactory.js +4 -435
  218. package/dist/editor/field-types/richtext/contextMenuFactory.js.map +1 -1
  219. package/dist/editor/field-types/richtext/richTextToolbarIcons.d.ts +7 -0
  220. package/dist/editor/field-types/richtext/richTextToolbarIcons.js +49 -0
  221. package/dist/editor/field-types/richtext/richTextToolbarIcons.js.map +1 -0
  222. package/dist/editor/field-types/richtext/types.d.ts +2 -0
  223. package/dist/editor/field-types/richtext/types.js.map +1 -1
  224. package/dist/editor/field-types/richtext/utils/conversion.js +23 -2
  225. package/dist/editor/field-types/richtext/utils/conversion.js.map +1 -1
  226. package/dist/editor/field-types/useFormFieldCaretPresence.d.ts +13 -0
  227. package/dist/editor/field-types/useFormFieldCaretPresence.js +92 -0
  228. package/dist/editor/field-types/useFormFieldCaretPresence.js.map +1 -0
  229. package/dist/editor/fieldTypes.d.ts +2 -0
  230. package/dist/editor/media-selector/TreeSelector.js +15 -15
  231. package/dist/editor/media-selector/TreeSelector.js.map +1 -1
  232. package/dist/editor/menubar/PageSelector.js +8 -2
  233. package/dist/editor/menubar/PageSelector.js.map +1 -1
  234. package/dist/editor/menubar/VersionPreviewCard.js +4 -249
  235. package/dist/editor/menubar/VersionPreviewCard.js.map +1 -1
  236. package/dist/editor/menubar/toolbar-sections/EditControls.js +2 -2
  237. package/dist/editor/menubar/toolbar-sections/EditControls.js.map +1 -1
  238. package/dist/editor/menubar/toolbar-sections/ManualBrowser.js +338 -187
  239. package/dist/editor/menubar/toolbar-sections/ManualBrowser.js.map +1 -1
  240. package/dist/editor/menubar/toolbar-sections/UtilityControls.js +3 -1
  241. package/dist/editor/menubar/toolbar-sections/UtilityControls.js.map +1 -1
  242. package/dist/editor/menubar/toolbar-sections/ViewportControls.js +1 -1
  243. package/dist/editor/page-editor-chrome/BridgeInlineFormatOverlay.d.ts +8 -0
  244. package/dist/editor/page-editor-chrome/BridgeInlineFormatOverlay.js +407 -0
  245. package/dist/editor/page-editor-chrome/BridgeInlineFormatOverlay.js.map +1 -0
  246. package/dist/editor/page-editor-chrome/CommentHighlightings.d.ts +5 -2
  247. package/dist/editor/page-editor-chrome/CommentHighlightings.js +340 -215
  248. package/dist/editor/page-editor-chrome/CommentHighlightings.js.map +1 -1
  249. package/dist/editor/page-editor-chrome/FeedbackHighlightBadge.d.ts +5 -1
  250. package/dist/editor/page-editor-chrome/FeedbackHighlightBadge.js +11 -4
  251. package/dist/editor/page-editor-chrome/FeedbackHighlightBadge.js.map +1 -1
  252. package/dist/editor/page-editor-chrome/FieldActionIndicator.js +21 -13
  253. package/dist/editor/page-editor-chrome/FieldActionIndicator.js.map +1 -1
  254. package/dist/editor/page-editor-chrome/FieldEditedIndicator.js +23 -29
  255. package/dist/editor/page-editor-chrome/FieldEditedIndicator.js.map +1 -1
  256. package/dist/editor/page-editor-chrome/FrameMenu.js +110 -19
  257. package/dist/editor/page-editor-chrome/FrameMenu.js.map +1 -1
  258. package/dist/editor/page-editor-chrome/LockedFieldIndicator.d.ts +3 -2
  259. package/dist/editor/page-editor-chrome/LockedFieldIndicator.js +148 -45
  260. package/dist/editor/page-editor-chrome/LockedFieldIndicator.js.map +1 -1
  261. package/dist/editor/page-editor-chrome/PageEditorChrome.d.ts +2 -0
  262. package/dist/editor/page-editor-chrome/PageEditorChrome.js +25 -21
  263. package/dist/editor/page-editor-chrome/PageEditorChrome.js.map +1 -1
  264. package/dist/editor/page-editor-chrome/PictureEditorOverlay.js +163 -128
  265. package/dist/editor/page-editor-chrome/PictureEditorOverlay.js.map +1 -1
  266. package/dist/editor/page-editor-chrome/PlaceholderDropZone.d.ts +1 -1
  267. package/dist/editor/page-editor-chrome/PlaceholderDropZone.js +6 -3
  268. package/dist/editor/page-editor-chrome/PlaceholderDropZone.js.map +1 -1
  269. package/dist/editor/page-editor-chrome/PlaceholderDropZones.d.ts +1 -2
  270. package/dist/editor/page-editor-chrome/PlaceholderDropZones.js +83 -146
  271. package/dist/editor/page-editor-chrome/PlaceholderDropZones.js.map +1 -1
  272. package/dist/editor/page-editor-chrome/SuggestionHighlightings.d.ts +5 -2
  273. package/dist/editor/page-editor-chrome/SuggestionHighlightings.js +144 -63
  274. package/dist/editor/page-editor-chrome/SuggestionHighlightings.js.map +1 -1
  275. package/dist/editor/page-editor-chrome/VersionDiffHighlightings.d.ts +1 -2
  276. package/dist/editor/page-editor-chrome/VersionDiffHighlightings.js +101 -30
  277. package/dist/editor/page-editor-chrome/VersionDiffHighlightings.js.map +1 -1
  278. package/dist/editor/page-editor-chrome/bridgeInlineFormatToolbarLayout.d.ts +24 -0
  279. package/dist/editor/page-editor-chrome/bridgeInlineFormatToolbarLayout.js +89 -0
  280. package/dist/editor/page-editor-chrome/bridgeInlineFormatToolbarLayout.js.map +1 -0
  281. package/dist/editor/page-editor-chrome/overlay/IframeOverlayProvider.d.ts +10 -1
  282. package/dist/editor/page-editor-chrome/overlay/IframeOverlayProvider.js +105 -122
  283. package/dist/editor/page-editor-chrome/overlay/IframeOverlayProvider.js.map +1 -1
  284. package/dist/editor/page-editor-chrome/overlay/geometry.d.ts +11 -4
  285. package/dist/editor/page-editor-chrome/overlay/geometry.js +139 -36
  286. package/dist/editor/page-editor-chrome/overlay/geometry.js.map +1 -1
  287. package/dist/editor/page-editor-chrome/useBridgeInlineEditing.d.ts +26 -0
  288. package/dist/editor/page-editor-chrome/useBridgeInlineEditing.js +228 -0
  289. package/dist/editor/page-editor-chrome/useBridgeInlineEditing.js.map +1 -0
  290. package/dist/editor/page-viewer/EditorForm.js +17 -1
  291. package/dist/editor/page-viewer/EditorForm.js.map +1 -1
  292. package/dist/editor/page-viewer/MiniMap.d.ts +2 -2
  293. package/dist/editor/page-viewer/MiniMap.js +176 -364
  294. package/dist/editor/page-viewer/MiniMap.js.map +1 -1
  295. package/dist/editor/page-viewer/PageViewer.js +63 -17
  296. package/dist/editor/page-viewer/PageViewer.js.map +1 -1
  297. package/dist/editor/page-viewer/PageViewerFrame.d.ts +0 -5
  298. package/dist/editor/page-viewer/PageViewerFrame.js +1685 -1512
  299. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  300. package/dist/editor/page-viewer/bridgeFieldPatch.d.ts +20 -0
  301. package/dist/editor/page-viewer/bridgeFieldPatch.js +33 -0
  302. package/dist/editor/page-viewer/bridgeFieldPatch.js.map +1 -0
  303. package/dist/editor/page-viewer/pageViewContext.d.ts +32 -0
  304. package/dist/editor/page-viewer/pageViewContext.js +37 -6
  305. package/dist/editor/page-viewer/pageViewContext.js.map +1 -1
  306. package/dist/editor/reviews/Comment.d.ts +2 -1
  307. package/dist/editor/reviews/Comment.js +10 -5
  308. package/dist/editor/reviews/Comment.js.map +1 -1
  309. package/dist/editor/reviews/CommentDisplayPopover.js +2 -1
  310. package/dist/editor/reviews/CommentDisplayPopover.js.map +1 -1
  311. package/dist/editor/reviews/CommentEditor.d.ts +1 -0
  312. package/dist/editor/reviews/CommentEditor.js +3 -2
  313. package/dist/editor/reviews/CommentEditor.js.map +1 -1
  314. package/dist/editor/reviews/CommentPopover.js +69 -10
  315. package/dist/editor/reviews/CommentPopover.js.map +1 -1
  316. package/dist/editor/reviews/CommentView.js +24 -4
  317. package/dist/editor/reviews/CommentView.js.map +1 -1
  318. package/dist/editor/reviews/Comments.d.ts +0 -2
  319. package/dist/editor/reviews/Comments.js +31 -31
  320. package/dist/editor/reviews/Comments.js.map +1 -1
  321. package/dist/editor/reviews/FeedbackCard.d.ts +4 -2
  322. package/dist/editor/reviews/FeedbackCard.js +8 -10
  323. package/dist/editor/reviews/FeedbackCard.js.map +1 -1
  324. package/dist/editor/reviews/SuggestedEdit.js +4 -6
  325. package/dist/editor/reviews/SuggestedEdit.js.map +1 -1
  326. package/dist/editor/reviews/SuggestionCommentThread.js +3 -3
  327. package/dist/editor/reviews/SuggestionCommentThread.js.map +1 -1
  328. package/dist/editor/reviews/SuggestionDisplayPopover.js +3 -2
  329. package/dist/editor/reviews/SuggestionDisplayPopover.js.map +1 -1
  330. package/dist/editor/reviews/commentAi.js +96 -27
  331. package/dist/editor/reviews/commentAi.js.map +1 -1
  332. package/dist/editor/reviews/commentTransientSelection.d.ts +23 -0
  333. package/dist/editor/reviews/commentTransientSelection.js +7 -0
  334. package/dist/editor/reviews/commentTransientSelection.js.map +1 -0
  335. package/dist/editor/reviews/feedbackOrdering.d.ts +5 -0
  336. package/dist/editor/reviews/feedbackOrdering.js +27 -0
  337. package/dist/editor/reviews/feedbackOrdering.js.map +1 -0
  338. package/dist/editor/reviews/feedbackSelection.js +32 -4
  339. package/dist/editor/reviews/feedbackSelection.js.map +1 -1
  340. package/dist/editor/reviews/suggestedEditState.d.ts +12 -0
  341. package/dist/editor/reviews/suggestedEditState.js +90 -0
  342. package/dist/editor/reviews/suggestedEditState.js.map +1 -0
  343. package/dist/editor/reviews/suggestionDisplayValue.d.ts +43 -0
  344. package/dist/editor/reviews/suggestionDisplayValue.js +93 -0
  345. package/dist/editor/reviews/suggestionDisplayValue.js.map +1 -0
  346. package/dist/editor/services/agentService.d.ts +15 -0
  347. package/dist/editor/services/agentService.js +11 -1
  348. package/dist/editor/services/agentService.js.map +1 -1
  349. package/dist/editor/services/reviewsService.d.ts +2 -2
  350. package/dist/editor/services/reviewsService.js.map +1 -1
  351. package/dist/editor/settings/SettingsView.js +2 -2
  352. package/dist/editor/settings/SettingsView.js.map +1 -1
  353. package/dist/editor/settings/panels/ProjectTemplatesPanel.js +1 -1
  354. package/dist/editor/settings/panels/ProjectTemplatesPanel.js.map +1 -1
  355. package/dist/editor/settings/panels/ProvidersPanel.js +2 -3
  356. package/dist/editor/settings/panels/ProvidersPanel.js.map +1 -1
  357. package/dist/editor/sidebar/MorePanelsButton.js +1 -1
  358. package/dist/editor/sidebar/MorePanelsButton.js.map +1 -1
  359. package/dist/editor/sidebar/Validation.js +4 -1
  360. package/dist/editor/sidebar/Validation.js.map +1 -1
  361. package/dist/editor/sidebar/Workbox.js +1 -1
  362. package/dist/editor/sidebar/Workbox.js.map +1 -1
  363. package/dist/editor/template-wizard/TemplateStructureInlineEditor.js +1 -1
  364. package/dist/editor/template-wizard/TemplateStructureInlineEditor.js.map +1 -1
  365. package/dist/editor/ui/IconSelectorDialog.js +1 -1
  366. package/dist/editor/ui/IconSelectorDialog.js.map +1 -1
  367. package/dist/editor/ui/SimpleIconButton.d.ts +2 -2
  368. package/dist/editor/ui/SimpleIconButton.js +7 -1
  369. package/dist/editor/ui/SimpleIconButton.js.map +1 -1
  370. package/dist/editor/ui/Splitter.d.ts +1 -0
  371. package/dist/editor/ui/Splitter.js +12 -2
  372. package/dist/editor/ui/Splitter.js.map +1 -1
  373. package/dist/editor/ui/animationSettle.d.ts +32 -0
  374. package/dist/editor/ui/animationSettle.js +85 -0
  375. package/dist/editor/ui/animationSettle.js.map +1 -0
  376. package/dist/editor/utils/expandSelectionAtCaret.d.ts +15 -0
  377. package/dist/editor/utils/expandSelectionAtCaret.js +183 -0
  378. package/dist/editor/utils/expandSelectionAtCaret.js.map +1 -0
  379. package/dist/editor/utils.d.ts +1 -17
  380. package/dist/editor/utils.js +0 -143
  381. package/dist/editor/utils.js.map +1 -1
  382. package/dist/editor/version-diff/versionDiffTargets.d.ts +3 -8
  383. package/dist/editor/version-diff/versionDiffTargets.js +37 -94
  384. package/dist/editor/version-diff/versionDiffTargets.js.map +1 -1
  385. package/dist/revision.d.ts +2 -2
  386. package/dist/revision.js +2 -2
  387. package/dist/splash-screen/ModernSplashScreen.js +11 -3
  388. package/dist/splash-screen/ModernSplashScreen.js.map +1 -1
  389. package/dist/splash-screen/NewPage.js +7 -5
  390. package/dist/splash-screen/NewPage.js.map +1 -1
  391. package/dist/splash-screen/OpenPage.js +5 -3
  392. package/dist/splash-screen/OpenPage.js.map +1 -1
  393. package/dist/splash-screen/RecentPages.js +3 -3
  394. package/dist/splash-screen/RecentPages.js.map +1 -1
  395. package/dist/task-board/components/TaskDetailPanel.js +2 -1
  396. package/dist/task-board/components/TaskDetailPanel.js.map +1 -1
  397. package/dist/task-board/views/DependencyGraphView.d.ts +42 -1
  398. package/dist/task-board/views/DependencyGraphView.js +94 -0
  399. package/dist/task-board/views/DependencyGraphView.js.map +1 -1
  400. package/dist/types.d.ts +1 -0
  401. package/package.json +2 -1
  402. package/styles.css +96 -0
  403. package/dist/editor/page-editor-chrome/InlineEditor.d.ts +0 -7
  404. package/dist/editor/page-editor-chrome/InlineEditor.js +0 -1719
  405. package/dist/editor/page-editor-chrome/InlineEditor.js.map +0 -1
  406. package/dist/editor/page-editor-chrome/overlay/iframeAccess.d.ts +0 -2
  407. package/dist/editor/page-editor-chrome/overlay/iframeAccess.js +0 -21
  408. package/dist/editor/page-editor-chrome/overlay/iframeAccess.js.map +0 -1
  409. package/dist/editor/page-editor-chrome/useInlineAICompletion.d.ts +0 -7
  410. package/dist/editor/page-editor-chrome/useInlineAICompletion.js +0 -758
  411. package/dist/editor/page-editor-chrome/useInlineAICompletion.js.map +0 -1
  412. package/dist/editor/page-viewer/pageModelSkeletonBuilder.d.ts +0 -3
  413. package/dist/editor/page-viewer/pageModelSkeletonBuilder.js +0 -796
  414. package/dist/editor/page-viewer/pageModelSkeletonBuilder.js.map +0 -1
@@ -1,1719 +0,0 @@
1
- import { jsx as _jsx } from "react/jsx-runtime";
2
- import { useEffect, useRef, useState } from "react";
3
- import { createPortal } from "react-dom";
4
- import { useEditContext, useFieldsEditContext, useEditContextRef, } from "../client/editContext";
5
- import { findFieldElement, getFieldDescriptorFromElement, hasFieldLock, getSessionWithFieldLock, } from "../utils";
6
- import { applyPatch, diffWords } from "diff";
7
- import { createPatch } from "diff";
8
- // import { useDebouncedCallback } from "use-debounce";
9
- // import { executePrompt } from "../services/aiService";
10
- import { useInlineAiCompletion } from "./useInlineAICompletion";
11
- import { getComponentById } from "../componentTreeHelper";
12
- import { useSlotContext } from "../views/editorSlotContext";
13
- const INLINE_FORMAT_QUERY_COMMANDS_BY_MARK = {
14
- bold: "bold",
15
- italic: "italic",
16
- underline: "underline",
17
- strikethrough: "strikeThrough",
18
- subscript: "subscript",
19
- superscript: "superscript",
20
- };
21
- function getInlineFormatMarkId(menuItemId) {
22
- return menuItemId
23
- .replace(/^slate-format-mark-/, "")
24
- .replace(/^format-mark-/, "");
25
- }
26
- function getInlineFieldKey(fieldElement) {
27
- const fieldId = fieldElement.getAttribute("data-fieldid") || "";
28
- const itemId = fieldElement.getAttribute("data-itemid") || "";
29
- const language = fieldElement.getAttribute("data-language") || "";
30
- const version = fieldElement.getAttribute("data-version") || "";
31
- return `${itemId}:${language}:${version}:${fieldId}`;
32
- }
33
- function getSelectedTextAnchorRect(range, root) {
34
- const ownerDocument = root.ownerDocument;
35
- const nodeFilter = ownerDocument.defaultView?.NodeFilter.SHOW_TEXT ?? NodeFilter.SHOW_TEXT;
36
- const walker = ownerDocument.createTreeWalker(root, nodeFilter);
37
- let currentNode = walker.nextNode();
38
- while (currentNode) {
39
- if (range.intersectsNode(currentNode)) {
40
- const textNode = currentNode;
41
- const start = textNode === range.startContainer ? range.startOffset : 0;
42
- const end = textNode === range.endContainer ? range.endOffset : textNode.length;
43
- if (end > start) {
44
- const probeRange = ownerDocument.createRange();
45
- probeRange.setStart(textNode, start);
46
- probeRange.setEnd(textNode, end);
47
- const rect = Array.from(probeRange.getClientRects()).find((candidate) => candidate.width > 0 && candidate.height > 0);
48
- probeRange.detach();
49
- if (rect)
50
- return rect;
51
- }
52
- }
53
- currentNode = walker.nextNode();
54
- }
55
- return null;
56
- }
57
- function getFirstVisibleSelectionLineRect(range, viewportWidth, viewportHeight) {
58
- const visibleRects = Array.from(range.getClientRects())
59
- .filter((rect) => rect.width > 0 &&
60
- rect.height > 0 &&
61
- rect.bottom >= 0 &&
62
- rect.top <= viewportHeight &&
63
- rect.right >= 0 &&
64
- rect.left <= viewportWidth)
65
- .sort((a, b) => a.top - b.top || a.left - b.left);
66
- const firstRect = visibleRects[0];
67
- if (!firstRect)
68
- return null;
69
- const rowTolerance = Math.max(2, firstRect.height / 2);
70
- const firstLineRects = visibleRects.filter((rect) => Math.abs(rect.top - firstRect.top) <= rowTolerance);
71
- const left = Math.min(...firstLineRects.map((rect) => rect.left));
72
- const top = Math.min(...firstLineRects.map((rect) => rect.top));
73
- const right = Math.max(...firstLineRects.map((rect) => rect.right));
74
- const bottom = Math.max(...firstLineRects.map((rect) => rect.bottom));
75
- return DOMRect.fromRect({
76
- x: left,
77
- y: top,
78
- width: right - left,
79
- height: bottom - top,
80
- });
81
- }
82
- function hasUsableRect(rect) {
83
- return (!!rect &&
84
- Number.isFinite(rect.left) &&
85
- Number.isFinite(rect.top) &&
86
- rect.width > 0 &&
87
- rect.height > 0);
88
- }
89
- function getInlineFormatAnchorRect(range, fieldElement, viewportWidth, viewportHeight) {
90
- const candidates = [
91
- getFirstVisibleSelectionLineRect(range, viewportWidth, viewportHeight),
92
- getSelectedTextAnchorRect(range, fieldElement),
93
- range.getBoundingClientRect(),
94
- fieldElement.getBoundingClientRect(),
95
- ].filter(hasUsableRect);
96
- return (candidates.find((rect) => rect.bottom >= 0 &&
97
- rect.top <= viewportHeight &&
98
- rect.right >= 0 &&
99
- rect.left <= viewportWidth) ??
100
- candidates[0] ??
101
- null);
102
- }
103
- function getTextNodeAtTextOffset(root, offset, affinity = "backward") {
104
- const ownerDocument = root.ownerDocument || document;
105
- const nodeFilter = ownerDocument.defaultView?.NodeFilter.SHOW_TEXT ?? NodeFilter.SHOW_TEXT;
106
- const walker = ownerDocument.createTreeWalker(root, nodeFilter);
107
- let currentNode;
108
- let boundaryNode = null;
109
- let remainingOffset = offset;
110
- while ((currentNode = walker.nextNode())) {
111
- const nodeLength = currentNode.textContent?.length || 0;
112
- if (remainingOffset < nodeLength) {
113
- return { node: currentNode, offset: remainingOffset };
114
- }
115
- if (remainingOffset === nodeLength) {
116
- if (affinity === "backward") {
117
- return { node: currentNode, offset: remainingOffset };
118
- }
119
- boundaryNode = currentNode;
120
- remainingOffset = 0;
121
- continue;
122
- }
123
- remainingOffset -= nodeLength;
124
- }
125
- return boundaryNode
126
- ? { node: boundaryNode, offset: boundaryNode.textContent?.length || 0 }
127
- : null;
128
- }
129
- function createRangeFromTextOffsets(fieldElement, startOffset, endOffset) {
130
- const startData = getTextNodeAtTextOffset(fieldElement, startOffset, "forward");
131
- const endData = getTextNodeAtTextOffset(fieldElement, endOffset, "backward");
132
- if (!startData || !endData)
133
- return null;
134
- try {
135
- const range = fieldElement.ownerDocument.createRange();
136
- range.setStart(startData.node, startData.offset);
137
- range.setEnd(endData.node, endData.offset);
138
- return range;
139
- }
140
- catch {
141
- return null;
142
- }
143
- }
144
- function getAccessibleIframeDocument(iframe) {
145
- if (!iframe)
146
- return null;
147
- try {
148
- return iframe.contentDocument || iframe.contentWindow?.document || null;
149
- }
150
- catch {
151
- return null;
152
- }
153
- }
154
- function getAccessibleIframeSelection(iframe) {
155
- if (!iframe)
156
- return null;
157
- try {
158
- return iframe.contentWindow?.getSelection() ?? null;
159
- }
160
- catch {
161
- return null;
162
- }
163
- }
164
- export function InlineEditor(props) {
165
- const context = useEditContext();
166
- if (!context)
167
- return null;
168
- return _jsx(InlineEditorContent, { ...props, context: context });
169
- }
170
- function InlineEditorContent({ pageViewContext, compareView, context, }) {
171
- const contextRef = useEditContextRef();
172
- const modifiedFieldsContext = useFieldsEditContext();
173
- const slotContext = useSlotContext();
174
- const [cursorSpanId] = useState(() => `cursor-indicator-${Math.random().toString(36).substring(2, 9)}`);
175
- const isUpdatingRef = useRef(false);
176
- const mutationObserverRef = useRef(null);
177
- const selectionChangeListenerRef = useRef(null);
178
- const lastQueuedInlineValueRef = useRef(new Map());
179
- const lastInlineUserEditAtRef = useRef(0);
180
- const [inlineFormatOverlay, setInlineFormatOverlay] = useState({
181
- visible: false,
182
- left: 0,
183
- top: 0,
184
- activeCommandIds: [],
185
- });
186
- const inlineFormatOverlayRef = useRef(null);
187
- const inlineFormatSelectionRef = useRef(null);
188
- const inlineFormatCommandsCacheRef = useRef(new Map());
189
- const inlineFormatCommandsLoadingRef = useRef(new Set());
190
- const inlineFormatPointerDownRef = useRef(false);
191
- const caretUpdateFrameRef = useRef(null);
192
- const lastSentCaretRef = useRef(null);
193
- const refreshCompletion = useInlineAiCompletion({
194
- pageViewContext,
195
- cursorSpanId,
196
- isUpdatingRef,
197
- });
198
- const isTextFieldType = (fieldType) => {
199
- const fieldTypeLower = fieldType.toLowerCase();
200
- return (fieldTypeLower === "single-line text" ||
201
- fieldTypeLower === "multi-line text" ||
202
- fieldTypeLower === "rich text");
203
- };
204
- const hideInlineFormatOverlay = () => {
205
- inlineFormatSelectionRef.current = null;
206
- setInlineFormatOverlay((current) => current.visible
207
- ? { visible: false, left: 0, top: 0, activeCommandIds: [] }
208
- : current);
209
- };
210
- const getSelectedInlineRichTextField = (range) => {
211
- const candidate = range.commonAncestorContainer instanceof Element
212
- ? range.commonAncestorContainer
213
- : range.commonAncestorContainer.parentElement;
214
- const fieldElement = candidate?.closest?.('[data-is-richtext="true"][data-fieldid][data-itemid][data-language][data-version]');
215
- if (!fieldElement ||
216
- !fieldElement.contains(range.startContainer) ||
217
- !fieldElement.contains(range.endContainer)) {
218
- return null;
219
- }
220
- return fieldElement;
221
- };
222
- const getInlineRichTextFieldForSelectedRange = (iframeDocument, selectedRange) => {
223
- if (!selectedRange?.fieldId || !selectedRange.itemId)
224
- return null;
225
- const fields = Array.from(iframeDocument.querySelectorAll('[data-is-richtext="true"][data-fieldid][data-itemid][data-language][data-version]'));
226
- return (fields.find((fieldElement) => {
227
- const version = fieldElement.getAttribute("data-version");
228
- return (fieldElement.getAttribute("data-fieldid") === selectedRange.fieldId &&
229
- fieldElement.getAttribute("data-itemid") === selectedRange.itemId &&
230
- (!selectedRange.language ||
231
- fieldElement.getAttribute("data-language") ===
232
- selectedRange.language) &&
233
- (selectedRange.version == null ||
234
- version === String(selectedRange.version)));
235
- }) ?? null);
236
- };
237
- const loadInlineFormatCommands = async (fieldElement) => {
238
- const key = getInlineFieldKey(fieldElement);
239
- if (inlineFormatCommandsCacheRef.current.has(key) ||
240
- inlineFormatCommandsLoadingRef.current.has(key)) {
241
- return;
242
- }
243
- inlineFormatCommandsLoadingRef.current.add(key);
244
- try {
245
- const currentContext = contextRef.current;
246
- if (!currentContext)
247
- return;
248
- const field = await currentContext.itemsRepository.getField(getFieldDescriptorFromElement(fieldElement));
249
- const contextMenuFactory = field
250
- ? currentContext.configuration.fieldTypes[field.type]
251
- ?.contextMenuFactory
252
- : undefined;
253
- if (!field || !contextMenuFactory) {
254
- inlineFormatCommandsCacheRef.current.set(key, []);
255
- return;
256
- }
257
- const menuItems = await contextMenuFactory(field, currentContext);
258
- const formatSubmenu = menuItems.find((item) => item.items?.length &&
259
- (item.id === "format-submenu" ||
260
- item.id === "slate-format-submenu" ||
261
- item.label === "Format"));
262
- const commands = formatSubmenu?.items
263
- ?.filter((item) => !item.separator && !item.disabled && !!item.command)
264
- .map((item) => ({
265
- id: item.id,
266
- label: item.label ?? "",
267
- icon: item.icon,
268
- command: item.command,
269
- queryCommand: INLINE_FORMAT_QUERY_COMMANDS_BY_MARK[getInlineFormatMarkId(item.id)],
270
- })) ?? [];
271
- inlineFormatCommandsCacheRef.current.set(key, commands);
272
- }
273
- catch (error) {
274
- console.error("[InlineEditor] Failed to load format overlay:", error);
275
- inlineFormatCommandsCacheRef.current.set(key, []);
276
- }
277
- finally {
278
- inlineFormatCommandsLoadingRef.current.delete(key);
279
- window.requestAnimationFrame(updateInlineFormatOverlay);
280
- }
281
- };
282
- const getActiveInlineFormatCommandIds = (iframeDocument, range, commands) => {
283
- const selection = iframeDocument.getSelection();
284
- if (!selection)
285
- return [];
286
- const queryActiveCommands = () => commands
287
- .filter((command) => {
288
- if (!command.queryCommand)
289
- return false;
290
- try {
291
- return iframeDocument.queryCommandState(command.queryCommand);
292
- }
293
- catch {
294
- return false;
295
- }
296
- })
297
- .map((command) => command.id);
298
- const currentRange = selection.rangeCount > 0 ? selection.getRangeAt(0) : null;
299
- if (!currentRange ||
300
- currentRange.startContainer !== range.startContainer ||
301
- currentRange.startOffset !== range.startOffset ||
302
- currentRange.endContainer !== range.endContainer ||
303
- currentRange.endOffset !== range.endOffset) {
304
- return [];
305
- }
306
- return queryActiveCommands();
307
- };
308
- const updateInlineFormatOverlay = () => {
309
- if (inlineFormatPointerDownRef.current) {
310
- hideInlineFormatOverlay();
311
- return;
312
- }
313
- if (compareView ||
314
- contextRef.current?.readonly ||
315
- (contextRef.current?.mode !== "edit" &&
316
- contextRef.current?.mode !== "suggestions")) {
317
- hideInlineFormatOverlay();
318
- return;
319
- }
320
- const iframe = pageViewContext.editorIframe;
321
- const iframeDocument = getAccessibleIframeDocument(iframe);
322
- if (!iframe || !iframeDocument) {
323
- hideInlineFormatOverlay();
324
- return;
325
- }
326
- const selection = getAccessibleIframeSelection(iframe);
327
- const selectedRange = contextRef.current?.selectedRange;
328
- if (selection?.rangeCount && selection.isCollapsed) {
329
- hideInlineFormatOverlay();
330
- return;
331
- }
332
- const liveRange = selection && !selection.isCollapsed && selection.rangeCount > 0
333
- ? selection.getRangeAt(0)
334
- : null;
335
- const liveRangeText = liveRange?.toString().trim();
336
- if (!liveRangeText && !selectedRange?.text?.trim()) {
337
- hideInlineFormatOverlay();
338
- return;
339
- }
340
- const liveRangeFieldElement = liveRange
341
- ? getSelectedInlineRichTextField(liveRange)
342
- : null;
343
- const fieldElement = liveRangeFieldElement ??
344
- getInlineRichTextFieldForSelectedRange(iframeDocument, selectedRange);
345
- if (!fieldElement || !fieldElement.isContentEditable) {
346
- hideInlineFormatOverlay();
347
- return;
348
- }
349
- const range = (liveRangeFieldElement ? liveRange : null) ??
350
- createRangeFromTextOffsets(fieldElement, selectedRange?.startOffset ?? 0, selectedRange?.endOffset ?? 0);
351
- if (!range) {
352
- hideInlineFormatOverlay();
353
- return;
354
- }
355
- const commandKey = getInlineFieldKey(fieldElement);
356
- const inlineFormatCommands = inlineFormatCommandsCacheRef.current.get(commandKey);
357
- if (!inlineFormatCommands) {
358
- void loadInlineFormatCommands(fieldElement);
359
- hideInlineFormatOverlay();
360
- return;
361
- }
362
- if (inlineFormatCommands.length === 0) {
363
- hideInlineFormatOverlay();
364
- return;
365
- }
366
- const iframeViewportWidth = iframe.contentWindow?.innerWidth ?? iframe.clientWidth;
367
- const iframeViewportHeight = iframe.contentWindow?.innerHeight ?? iframe.clientHeight;
368
- const anchorRect = getInlineFormatAnchorRect(range, fieldElement, iframeViewportWidth, iframeViewportHeight) ?? fieldElement.getBoundingClientRect();
369
- const iframeRect = iframe.getBoundingClientRect();
370
- const overlayWidth = inlineFormatOverlayRef.current?.offsetWidth ?? 180;
371
- const overlayHeight = inlineFormatOverlayRef.current?.offsetHeight ?? 34;
372
- const viewportPadding = 8;
373
- const selectionGap = 8;
374
- const anchorCenter = iframeRect.left +
375
- anchorRect.left +
376
- Math.min(anchorRect.width, overlayWidth) / 2;
377
- let top = iframeRect.top + anchorRect.top - overlayHeight - selectionGap;
378
- if (top < viewportPadding) {
379
- top = iframeRect.top + anchorRect.bottom + selectionGap + 36;
380
- }
381
- top = Math.max(viewportPadding, Math.min(top, window.innerHeight - overlayHeight - viewportPadding));
382
- const left = Math.max(viewportPadding, Math.min(anchorCenter - overlayWidth / 2, window.innerWidth - overlayWidth - viewportPadding));
383
- inlineFormatSelectionRef.current = {
384
- range: range.cloneRange(),
385
- fieldElement,
386
- };
387
- setInlineFormatOverlay({
388
- visible: true,
389
- left,
390
- top,
391
- activeCommandIds: getActiveInlineFormatCommandIds(iframeDocument, range, inlineFormatCommands),
392
- });
393
- };
394
- const applyInlineFormatCommand = async (command, event) => {
395
- event.preventDefault();
396
- event.stopPropagation();
397
- const selectionContext = inlineFormatSelectionRef.current;
398
- if (!selectionContext?.range || !command.command)
399
- return;
400
- const iframeWindow = pageViewContext.editorIframe?.contentWindow;
401
- const iframeDocument = selectionContext.range.startContainer.ownerDocument;
402
- if (!iframeDocument)
403
- return;
404
- const selection = iframeDocument.getSelection();
405
- if (!selection)
406
- return;
407
- iframeWindow?.focus();
408
- selectionContext.fieldElement.focus({ preventScroll: true });
409
- selection.removeAllRanges();
410
- selection.addRange(selectionContext.range);
411
- await command.command(event);
412
- if (selection.rangeCount > 0) {
413
- inlineFormatSelectionRef.current = {
414
- range: selection.getRangeAt(0).cloneRange(),
415
- fieldElement: selectionContext.fieldElement,
416
- };
417
- }
418
- window.requestAnimationFrame(updateInlineFormatOverlay);
419
- };
420
- // Stable identity check: compare by field key (not by DOM node reference).
421
- // This avoids stomping the active field if the iframe DOM is replaced/morphed.
422
- const getFieldKeyFromElement = (el) => {
423
- const fieldId = el.getAttribute("data-fieldid") || "";
424
- const itemId = el.getAttribute("data-itemid") || "";
425
- const language = el.getAttribute("data-language") || "";
426
- const versionRaw = el.getAttribute("data-version");
427
- const version = versionRaw ? parseInt(versionRaw, 10) : NaN;
428
- return { fieldId, itemId, language, version };
429
- };
430
- const isSameFieldKey = (el, key) => {
431
- if (!key)
432
- return false;
433
- const k = getFieldKeyFromElement(el);
434
- return (k.fieldId === key.fieldId &&
435
- k.itemId === key.itemId &&
436
- k.language === key.language &&
437
- k.version === key.version);
438
- };
439
- // Only skip DOM updates for fields being edited inline in the iframe.
440
- // Do NOT skip updates for sidebar-focused fields - we want those to update the preview.
441
- const activeFieldKey = modifiedFieldsContext?.inlineEditingFieldElement
442
- ? getFieldKeyFromElement(modifiedFieldsContext.inlineEditingFieldElement)
443
- : context.selectedRange
444
- ? {
445
- fieldId: context.selectedRange.fieldId,
446
- itemId: context.selectedRange.itemId,
447
- language: context.selectedRange.language,
448
- version: context.selectedRange.version,
449
- }
450
- : undefined;
451
- const isElementActiveInIframe = (doc, element) => {
452
- if (!doc || !element)
453
- return false;
454
- const activeElement = doc.activeElement;
455
- return (activeElement === element ||
456
- (activeElement ? element.contains(activeElement) : false));
457
- };
458
- const isSelectionInsideElement = (element) => {
459
- if (!element)
460
- return false;
461
- const iframe = pageViewContext.editorIframe;
462
- const iframeDocument = getAccessibleIframeDocument(iframe);
463
- const iframeHasFocus = iframe?.ownerDocument.activeElement === iframe ||
464
- iframeDocument?.hasFocus() === true;
465
- if (!iframeHasFocus)
466
- return false;
467
- const selection = getAccessibleIframeSelection(pageViewContext.editorIframe);
468
- return !!(selection?.anchorNode &&
469
- selection?.focusNode &&
470
- element.contains(selection.anchorNode) &&
471
- element.contains(selection.focusNode));
472
- };
473
- const isElementActiveOrSelectedInIframe = (doc, element) => isElementActiveInIframe(doc, element) || isSelectionInsideElement(element);
474
- const clearRemoteCaretPosition = () => {
475
- if (caretUpdateFrameRef.current != null) {
476
- window.cancelAnimationFrame(caretUpdateFrameRef.current);
477
- caretUpdateFrameRef.current = null;
478
- }
479
- if (lastSentCaretRef.current === "clear")
480
- return;
481
- context.sendSocketMessage({
482
- type: "caret-position",
483
- payload: { offset: null },
484
- });
485
- lastSentCaretRef.current = "clear";
486
- };
487
- const scheduleCaretPositionUpdate = (element, fieldDescriptor) => {
488
- if (caretUpdateFrameRef.current != null)
489
- return;
490
- caretUpdateFrameRef.current = window.requestAnimationFrame(() => {
491
- caretUpdateFrameRef.current = null;
492
- const iframeDocument = getAccessibleIframeDocument(pageViewContext.editorIframe);
493
- if (!isElementActiveOrSelectedInIframe(iframeDocument, element)) {
494
- clearRemoteCaretPosition();
495
- return;
496
- }
497
- const selection = getAccessibleIframeSelection(pageViewContext.editorIframe);
498
- if (!selection || selection.rangeCount === 0 || !selection.isCollapsed) {
499
- clearRemoteCaretPosition();
500
- return;
501
- }
502
- const caret = saveCaretPosition(element);
503
- if (!caret || caret.startOffset !== caret.endOffset) {
504
- clearRemoteCaretPosition();
505
- return;
506
- }
507
- const value = sanitizeEditableValue(element, element.getAttribute("data-is-richtext") === "true");
508
- const nextKey = `${fieldDescriptor.fieldId}:${fieldDescriptor.item.id}:${fieldDescriptor.item.language}:${fieldDescriptor.item.version}:${caret.startOffset}:${value}`;
509
- if (lastSentCaretRef.current === nextKey)
510
- return;
511
- context.sendSocketMessage({
512
- type: "caret-position",
513
- payload: {
514
- fieldId: fieldDescriptor.fieldId,
515
- item: fieldDescriptor.item,
516
- offset: caret.startOffset,
517
- value,
518
- },
519
- });
520
- lastSentCaretRef.current = nextKey;
521
- });
522
- };
523
- const isInlineFieldBeingEdited = (doc, element) => {
524
- if (!doc || !element)
525
- return false;
526
- const iframe = pageViewContext.editorIframe;
527
- const iframeHasFocus = iframe?.ownerDocument.activeElement === iframe || doc.hasFocus() === true;
528
- return ((iframeHasFocus && isElementActiveInIframe(doc, element)) ||
529
- isSelectionInsideElement(element));
530
- };
531
- const focusInlineElement = (element) => {
532
- const iframeWindow = pageViewContext.editorIframe?.contentWindow;
533
- const selection = getAccessibleIframeSelection(pageViewContext.editorIframe);
534
- iframeWindow?.focus();
535
- element.focus({ preventScroll: true });
536
- if (!selection || isSelectionInsideElement(element))
537
- return;
538
- const range = element.ownerDocument.createRange();
539
- range.selectNodeContents(element);
540
- range.collapse(false);
541
- selection.removeAllRanges();
542
- selection.addRange(range);
543
- };
544
- function hasRemoteCaretForField({ fieldId, itemId, language, version, }) {
545
- if (!fieldId || !itemId || !language || version == null)
546
- return false;
547
- return Object.values(contextRef.current?.remoteCarets ?? context.remoteCarets).some((caret) => caret.offset != null &&
548
- caret.fieldId === fieldId &&
549
- caret.item?.id === itemId &&
550
- caret.item?.language === language &&
551
- caret.item?.version === version);
552
- }
553
- // Helper: set innerHTML when content changes
554
- function setInnerHTMLWithStackTrace(element, nextHTML, meta) {
555
- const prevHTML = element.innerHTML ?? "";
556
- if (prevHTML === nextHTML)
557
- return false;
558
- if (meta.source !== "remote-caret:value" &&
559
- hasRemoteCaretForField({
560
- fieldId: meta.fieldId ?? element.getAttribute("data-fieldid"),
561
- itemId: meta.itemId ?? element.getAttribute("data-itemid"),
562
- language: meta.language ?? element.getAttribute("data-language"),
563
- version: meta.version ??
564
- (element.getAttribute("data-version")
565
- ? parseInt(element.getAttribute("data-version"), 10)
566
- : null),
567
- })) {
568
- return false;
569
- }
570
- if (contextRef.current?.mode === "suggestions") {
571
- const iframeDocument = getAccessibleIframeDocument(pageViewContext.editorIframe);
572
- const iframeHasFocus = pageViewContext.editorIframe?.ownerDocument.activeElement ===
573
- pageViewContext.editorIframe || iframeDocument?.hasFocus() === true;
574
- const recentlyEdited = Date.now() - lastInlineUserEditAtRef.current < 1500;
575
- const isFocusedEditable = iframeHasFocus &&
576
- (isElementActiveInIframe(iframeDocument, element) ||
577
- isSelectionInsideElement(element));
578
- const willSkipRewrite = isFocusedEditable || (element.isContentEditable && recentlyEdited);
579
- if (willSkipRewrite) {
580
- return false;
581
- }
582
- }
583
- isUpdatingRef.current = true;
584
- element.innerHTML = nextHTML;
585
- setTimeout(() => {
586
- isUpdatingRef.current = false;
587
- }, 0);
588
- // try {
589
- // const resolvedFieldId =
590
- // meta.fieldId ?? element.getAttribute("data-fieldid");
591
- // const resolvedItemId = meta.itemId ?? element.getAttribute("data-itemid");
592
- // const resolvedLanguage =
593
- // meta.language ?? element.getAttribute("data-language");
594
- // const resolvedVersion =
595
- // meta.version ??
596
- // (element.getAttribute("data-version")
597
- // ? parseInt(element.getAttribute("data-version") as string, 10)
598
- // : null);
599
- // // Log concise info and stack trace
600
- // // Limit previews to keep console tidy
601
- // const preview = (s: string) =>
602
- // s.length > 200 ? s.slice(0, 200) + "…" : s;
603
- // console.groupCollapsed(
604
- // `[InlineEditor] DOM field update (${meta.source}) ${nextHTML} `,
605
- // );
606
- // const modifiedFieldValue = modifiedFieldsContext?.modifiedFields.find(
607
- // (x) =>
608
- // x.fieldId === resolvedFieldId &&
609
- // x.item.id === resolvedItemId &&
610
- // x.item.language === resolvedLanguage &&
611
- // x.item.version === resolvedVersion,
612
- // )?.value;
613
- // console.log({
614
- // fieldId: resolvedFieldId,
615
- // itemId: resolvedItemId,
616
- // language: resolvedLanguage,
617
- // version: resolvedVersion,
618
- // prevLength: prevHTML.length,
619
- // nextLength: nextHTML.length,
620
- // prevPreview: preview(prevHTML),
621
- // nextPreview: preview(nextHTML),
622
- // modifiedFieldsContext: modifiedFieldsContext?.modifiedFields,
623
- // repository: contextRef.current?.itemsRepository,
624
- // modifiedFieldValue,
625
- // });
626
- // console.trace();
627
- // console.groupEnd();
628
- // } catch {
629
- // // no-op logging failure
630
- // }
631
- return true;
632
- }
633
- function setEditableElementValue(element, nextValue, isRichText, meta) {
634
- if (isRichText) {
635
- return setInnerHTMLWithStackTrace(element, nextValue, meta);
636
- }
637
- if ((element.innerText ?? "") === nextValue)
638
- return false;
639
- isUpdatingRef.current = true;
640
- element.innerText = nextValue;
641
- setTimeout(() => {
642
- isUpdatingRef.current = false;
643
- }, 0);
644
- return true;
645
- }
646
- function isCurrentInlineEditSource(sourceElement, key) {
647
- if (!sourceElement.isConnected)
648
- return false;
649
- const iframeDocument = getAccessibleIframeDocument(pageViewContext.editorIframe);
650
- if (iframeDocument && sourceElement.ownerDocument !== iframeDocument) {
651
- return false;
652
- }
653
- const activePage = pageViewContext.pageItemDescriptor;
654
- if (activePage?.language && activePage.language !== key.language) {
655
- return false;
656
- }
657
- const activeElement = modifiedFieldsContext?.inlineEditingFieldElement;
658
- if (activeElement && isSameFieldKey(sourceElement, key))
659
- return true;
660
- if (key.fromUserInput &&
661
- contextRef.current?.mode === "suggestions" &&
662
- sourceElement instanceof HTMLElement &&
663
- sourceElement.isContentEditable &&
664
- isSameFieldKey(sourceElement, key)) {
665
- return true;
666
- }
667
- if (contextRef.current?.mode === "suggestions" &&
668
- sourceElement instanceof HTMLElement &&
669
- sourceElement.isContentEditable &&
670
- isSameFieldKey(sourceElement, key) &&
671
- isInlineFieldBeingEdited(iframeDocument, sourceElement)) {
672
- return true;
673
- }
674
- if (!activeElement || !isSameFieldKey(sourceElement, key))
675
- return false;
676
- return true;
677
- }
678
- function queueInlineFieldValue({ value, fieldId, fieldName, itemId, language, version, sourceElement, fromUserInput = false, source = "unknown", }) {
679
- const isCurrentSource = isCurrentInlineEditSource(sourceElement, {
680
- fieldId,
681
- itemId,
682
- language,
683
- version,
684
- fromUserInput,
685
- });
686
- if (!isCurrentSource) {
687
- return;
688
- }
689
- const fieldValueKey = `${itemId}:${language}:${version}:${fieldId}`;
690
- if (lastQueuedInlineValueRef.current.get(fieldValueKey) === value)
691
- return;
692
- lastQueuedInlineValueRef.current.set(fieldValueKey, value);
693
- lastInlineUserEditAtRef.current = Date.now();
694
- contextRef.current?.operations.editField({
695
- field: {
696
- fieldId,
697
- fieldName: fieldName ?? undefined,
698
- item: {
699
- id: itemId,
700
- language,
701
- version,
702
- },
703
- },
704
- forceMode: contextRef.current?.mode === "suggestions" ||
705
- context.mode === "suggestions"
706
- ? "suggestions"
707
- : undefined,
708
- originatingSlotId: slotContext?.slotId,
709
- refresh: "none",
710
- value,
711
- });
712
- }
713
- const sanitizeEditableValue = (element, isRichText) => {
714
- let currentValue = isRichText ? element.innerHTML : element.innerText;
715
- if (isRichText) {
716
- const clone = element.cloneNode(true);
717
- const cursorElem = clone.querySelector(`#${cursorSpanId}`);
718
- if (cursorElem)
719
- cursorElem.parentNode?.removeChild(cursorElem);
720
- const ownerDoc = clone.ownerDocument || document;
721
- const walker = ownerDoc.createTreeWalker(clone, NodeFilter.SHOW_TEXT);
722
- const toClean = [];
723
- while (walker.nextNode()) {
724
- const tn = walker.currentNode;
725
- if (tn.nodeValue && tn.nodeValue.includes("\u200B")) {
726
- toClean.push(tn);
727
- }
728
- }
729
- toClean.forEach((tn) => (tn.nodeValue = tn.nodeValue?.replaceAll("\u200B", "") || ""));
730
- currentValue = clone.innerHTML;
731
- }
732
- else {
733
- currentValue = currentValue.replaceAll("\u200B", "");
734
- }
735
- return currentValue;
736
- };
737
- useEffect(() => {
738
- const editorIframe = pageViewContext.editorIframe;
739
- if (compareView || !editorIframe)
740
- return;
741
- context.activeSessions.forEach((session) => {
742
- if (session.sessionId === context.sessionId || !session.fieldLock)
743
- return;
744
- const remoteCaret = context.remoteCarets[session.sessionId];
745
- if (remoteCaret?.value == null)
746
- return;
747
- const caretMatchesLock = remoteCaret.fieldId === session.fieldLock.fieldId &&
748
- remoteCaret.item?.id === session.fieldLock.item.id &&
749
- remoteCaret.item?.language === session.fieldLock.item.language &&
750
- remoteCaret.item?.version === session.fieldLock.item.version;
751
- if (!caretMatchesLock)
752
- return;
753
- const element = findFieldElement(editorIframe, session.fieldLock);
754
- if (!element || element === modifiedFieldsContext?.inlineEditingFieldElement)
755
- return;
756
- setEditableElementValue(element, remoteCaret.value, element.getAttribute("data-is-richtext") === "true", {
757
- source: "remote-caret:value",
758
- fieldId: session.fieldLock.fieldId,
759
- itemId: session.fieldLock.item.id,
760
- language: session.fieldLock.item.language,
761
- version: session.fieldLock.item.version,
762
- });
763
- });
764
- }, [
765
- compareView,
766
- context.activeSessions,
767
- context.remoteCarets,
768
- context.sessionId,
769
- modifiedFieldsContext?.inlineEditingFieldElement,
770
- pageViewContext.editorIframe,
771
- ]);
772
- useEffect(() => {
773
- if (compareView || context.mode === "preview")
774
- return;
775
- const queueElementValue = (element, fromUserInput = false) => {
776
- if (contextRef.current?.mode !== "suggestions" &&
777
- context.mode !== "suggestions") {
778
- return;
779
- }
780
- if (isUpdatingRef.current)
781
- return;
782
- if (!element?.isContentEditable)
783
- return;
784
- const fieldId = element.getAttribute("data-fieldid");
785
- const fieldName = element.getAttribute("data-fieldname");
786
- const itemId = element.getAttribute("data-itemid");
787
- const language = element.getAttribute("data-language");
788
- const versionText = element.getAttribute("data-version");
789
- const version = versionText ? parseInt(versionText) : undefined;
790
- if (!fieldId || !itemId || !language || !version)
791
- return;
792
- queueInlineFieldValue({
793
- value: sanitizeEditableValue(element, element.getAttribute("data-is-richtext") === "true"),
794
- fieldId,
795
- fieldName,
796
- itemId,
797
- language,
798
- version,
799
- sourceElement: element,
800
- fromUserInput,
801
- });
802
- };
803
- const getFieldElement = (target) => {
804
- const element = target instanceof Element ? target : target?.parentElement;
805
- return element?.closest?.("[data-fieldid][data-itemid][data-language][data-version]");
806
- };
807
- const inputHandler = (event) => {
808
- queueElementValue(getFieldElement(event.target), true);
809
- };
810
- let detachDocumentInput = null;
811
- const attachToCurrentIframeDocument = () => {
812
- detachDocumentInput?.();
813
- const iframeDocument = getAccessibleIframeDocument(pageViewContext.editorIframe);
814
- if (!iframeDocument) {
815
- detachDocumentInput = null;
816
- return;
817
- }
818
- iframeDocument.addEventListener("input", inputHandler, true);
819
- const observer = new MutationObserver((mutations) => {
820
- const queuedElements = new Set();
821
- for (const mutation of mutations) {
822
- const fieldElement = getFieldElement(mutation.target);
823
- if (fieldElement)
824
- queuedElements.add(fieldElement);
825
- mutation.addedNodes.forEach((node) => {
826
- const addedFieldElement = getFieldElement(node);
827
- if (addedFieldElement)
828
- queuedElements.add(addedFieldElement);
829
- });
830
- }
831
- queuedElements.forEach((element) => queueElementValue(element));
832
- });
833
- observer.observe(iframeDocument.body || iframeDocument.documentElement, {
834
- childList: true,
835
- characterData: true,
836
- subtree: true,
837
- });
838
- const pollFocusedField = () => {
839
- if (contextRef.current?.mode !== "suggestions" &&
840
- context.mode !== "suggestions") {
841
- return;
842
- }
843
- const activeField = iframeDocument.activeElement?.closest?.("[data-fieldid][data-itemid][data-language][data-version]");
844
- if (activeField) {
845
- queueElementValue(activeField, true);
846
- return;
847
- }
848
- const selection = iframeDocument.defaultView?.getSelection();
849
- const selectionElement = selection?.anchorNode instanceof Element
850
- ? selection.anchorNode
851
- : selection?.anchorNode?.parentElement;
852
- const selectedField = selectionElement?.closest?.("[data-fieldid][data-itemid][data-language][data-version]");
853
- if (selectedField) {
854
- queueElementValue(selectedField, true);
855
- return;
856
- }
857
- iframeDocument
858
- .querySelectorAll('[contenteditable="true"][data-fieldid][data-itemid][data-language][data-version]')
859
- .forEach((fieldElement) => queueElementValue(fieldElement, true));
860
- };
861
- const pollIntervalId = iframeDocument.defaultView?.setInterval(pollFocusedField, 250);
862
- detachDocumentInput = () => {
863
- iframeDocument.removeEventListener("input", inputHandler, true);
864
- observer.disconnect();
865
- if (pollIntervalId !== undefined) {
866
- iframeDocument.defaultView?.clearInterval(pollIntervalId);
867
- }
868
- };
869
- };
870
- attachToCurrentIframeDocument();
871
- pageViewContext.editorIframe?.addEventListener("load", attachToCurrentIframeDocument);
872
- return () => {
873
- pageViewContext.editorIframe?.removeEventListener("load", attachToCurrentIframeDocument);
874
- detachDocumentInput?.();
875
- };
876
- }, [compareView, context.mode, pageViewContext.editorIframe]);
877
- useEffect(() => {
878
- if (!context || compareView || context.mode === "preview")
879
- return;
880
- const iframeDocument = getAccessibleIframeDocument(pageViewContext.editorIframe);
881
- let element = modifiedFieldsContext?.inlineEditingFieldElement;
882
- if (!element && context.mode === "suggestions" && iframeDocument) {
883
- const iframeHasFocus = pageViewContext.editorIframe?.ownerDocument.activeElement ===
884
- pageViewContext.editorIframe || iframeDocument.hasFocus() === true;
885
- if (iframeHasFocus) {
886
- const activeFieldElement = iframeDocument.activeElement?.closest?.("[data-fieldid][data-itemid][data-language][data-version]") ?? null;
887
- const selection = getAccessibleIframeSelection(pageViewContext.editorIframe);
888
- const selectionElement = selection?.anchorNode instanceof Element
889
- ? selection.anchorNode
890
- : selection?.anchorNode?.parentElement;
891
- const selectedFieldElement = selectionElement?.closest?.("[data-fieldid][data-itemid][data-language][data-version]") ?? null;
892
- const recoveredElement = (activeFieldElement ??
893
- selectedFieldElement);
894
- if (recoveredElement?.isConnected) {
895
- element = recoveredElement;
896
- modifiedFieldsContext?.setInlineEditingFieldElement(recoveredElement);
897
- }
898
- }
899
- }
900
- const editableElements = iframeDocument?.querySelectorAll('[contenteditable="true"]');
901
- if (element) {
902
- editableElements?.forEach((editableElement) => {
903
- if (editableElement !== element) {
904
- editableElement.setAttribute("contenteditable", "false");
905
- }
906
- });
907
- }
908
- if (element) {
909
- lastQueuedInlineValueRef.current.clear();
910
- // Clean up any existing cursor indicators
911
- const iframeDocument = getAccessibleIframeDocument(pageViewContext.editorIframe);
912
- const existingCursors = iframeDocument?.querySelectorAll('[data-cursor-indicator="true"]');
913
- existingCursors?.forEach((el) => el.parentNode?.removeChild(el));
914
- const fieldId = element.getAttribute("data-fieldid");
915
- const fieldName = element.getAttribute("data-fieldname");
916
- const itemId = element.getAttribute("data-itemid");
917
- const language = element.getAttribute("data-language");
918
- const versionText = element.getAttribute("data-version");
919
- const version = versionText ? parseInt(versionText) : undefined;
920
- const isRichText = element.getAttribute("data-is-richtext") === "true";
921
- if (!fieldId || !itemId || !language || !version)
922
- return;
923
- // Guard: if layout components are hidden, do not allow editing fields of layout components
924
- if (contextRef.current?.showLayoutComponents === false &&
925
- contextRef.current?.page) {
926
- const owner = getComponentById(itemId, contextRef.current.page);
927
- if (owner?.layoutId)
928
- return;
929
- }
930
- const fieldDescriptor = getFieldDescriptorFromElement(element);
931
- // Check if another session has this field locked
932
- const fieldLockedBySession = getSessionWithFieldLock(fieldDescriptor, context);
933
- const isLockedByAnotherUser = fieldLockedBySession &&
934
- fieldLockedBySession.sessionId !== context.sessionId;
935
- // If another user has the lock, don't allow editing
936
- if (isLockedByAnotherUser)
937
- return;
938
- const hasFielLock = context.mode === "suggestions" ||
939
- hasFieldLock(fieldDescriptor, context);
940
- if (!hasFielLock)
941
- return;
942
- element.setAttribute("contenteditable", "true");
943
- const iframeWindow = pageViewContext.editorIframe?.contentWindow;
944
- const sanitizeCurrentValue = () => sanitizeEditableValue(element, isRichText);
945
- const inputHandler = () => {
946
- const currentValue = sanitizeCurrentValue();
947
- queueInlineFieldValue({
948
- value: currentValue,
949
- fieldId,
950
- fieldName,
951
- itemId,
952
- language,
953
- version,
954
- sourceElement: element,
955
- fromUserInput: true,
956
- source: "input",
957
- });
958
- scheduleCaretPositionUpdate(element, fieldDescriptor);
959
- };
960
- element.addEventListener("input", inputHandler);
961
- // Setup a more robust mutation observer
962
- mutationObserverRef.current = new MutationObserver((mutations) => {
963
- // If we're already processing an update, don't trigger another one
964
- if (isUpdatingRef.current)
965
- return;
966
- // Check if mutations only affect our cursor span
967
- const hasRealChanges = mutations.some((mutation) => {
968
- // Skip our cursor span
969
- if (mutation.target.nodeType === Node.ELEMENT_NODE &&
970
- mutation.target.id === cursorSpanId) {
971
- console.log("skipping cursor span");
972
- return false;
973
- }
974
- // Skip mutations where target is our span
975
- if (mutation.target.nodeType === Node.ELEMENT_NODE &&
976
- mutation.target.getAttribute &&
977
- mutation.target.getAttribute("data-cursor-indicator") === "true") {
978
- console.log("skipping cursor span 2");
979
- return false;
980
- }
981
- // Skip if all added/removed nodes are just our cursor span
982
- if (mutation.type === "childList") {
983
- const nonCursorChanges = Array.from(mutation.addedNodes)
984
- .concat(Array.from(mutation.removedNodes))
985
- .filter((node) => {
986
- return (node.nodeType !== Node.ELEMENT_NODE ||
987
- !node.id ||
988
- node.id !== cursorSpanId);
989
- });
990
- return nonCursorChanges.length > 0;
991
- }
992
- return true;
993
- });
994
- if (hasRealChanges) {
995
- // Capture and sanitize the value at mutation time
996
- const capturedValue = sanitizeCurrentValue();
997
- queueInlineFieldValue({
998
- value: capturedValue,
999
- fieldId,
1000
- fieldName,
1001
- itemId,
1002
- language,
1003
- version,
1004
- sourceElement: element,
1005
- source: "mutation",
1006
- });
1007
- }
1008
- // Always update cursor position after any mutation,
1009
- // but delay to let the mutation processing complete
1010
- // if (!isUpdatingRef.current) {
1011
- // setTimeout(updateCursorSpan, 0);
1012
- // // updateCompletion();
1013
- // }
1014
- });
1015
- const focusIfNeeded = () => {
1016
- // Only focus if no other important element has focus (like the AI dialog)
1017
- const activeEl = document.activeElement;
1018
- const isInDialog = activeEl?.closest(".agent-inline-dialog") ||
1019
- activeEl?.closest('[role="dialog"]');
1020
- const iframeDocument = getAccessibleIframeDocument(pageViewContext.editorIframe);
1021
- const alreadyFocused = isElementActiveInIframe(iframeDocument, element);
1022
- if (!isInDialog && !alreadyFocused) {
1023
- focusInlineElement(element);
1024
- }
1025
- };
1026
- focusIfNeeded();
1027
- setTimeout(focusIfNeeded, 50);
1028
- scheduleCaretPositionUpdate(element, fieldDescriptor);
1029
- // Initial cursor placement
1030
- // setTimeout(updateCursorSpan, 10);
1031
- // Add listener for selection changes
1032
- if (iframeWindow) {
1033
- const selectionChangeHandler = () => {
1034
- scheduleCaretPositionUpdate(element, fieldDescriptor);
1035
- };
1036
- const keyOrMouseHandler = () => {
1037
- scheduleCaretPositionUpdate(element, fieldDescriptor);
1038
- };
1039
- const blurHandler = () => {
1040
- clearRemoteCaretPosition();
1041
- };
1042
- iframeWindow.document.addEventListener("selectionchange", selectionChangeHandler);
1043
- iframeWindow.document.addEventListener("keyup", keyOrMouseHandler);
1044
- iframeWindow.document.addEventListener("mouseup", keyOrMouseHandler);
1045
- element.addEventListener("blur", blurHandler);
1046
- selectionChangeListenerRef.current = () => {
1047
- iframeWindow.document.removeEventListener("selectionchange", selectionChangeHandler);
1048
- iframeWindow.document.removeEventListener("keyup", keyOrMouseHandler);
1049
- iframeWindow.document.removeEventListener("mouseup", keyOrMouseHandler);
1050
- element.removeEventListener("blur", blurHandler);
1051
- };
1052
- mutationObserverRef.current.observe(element, {
1053
- childList: true, // observe direct children changes
1054
- subtree: true, // observe all descendants changes
1055
- characterData: true, // observe text changes
1056
- });
1057
- // Cleanup
1058
- return () => {
1059
- element.removeEventListener("input", inputHandler);
1060
- if (mutationObserverRef.current) {
1061
- mutationObserverRef.current.disconnect();
1062
- mutationObserverRef.current = null;
1063
- }
1064
- if (selectionChangeListenerRef.current) {
1065
- selectionChangeListenerRef.current();
1066
- selectionChangeListenerRef.current = null;
1067
- }
1068
- // iframeWindow.document.removeEventListener("keydown", keyHandler);
1069
- // Clean up any cursor indicators on unmount
1070
- const cursorElement = iframeDocument?.getElementById(cursorSpanId);
1071
- if (cursorElement) {
1072
- cursorElement.parentNode?.removeChild(cursorElement);
1073
- }
1074
- clearRemoteCaretPosition();
1075
- };
1076
- }
1077
- }
1078
- async function updateFocusedFieldContent() {
1079
- const element = modifiedFieldsContext?.inlineEditingFieldElement;
1080
- if (!element)
1081
- return;
1082
- const iframeDocument = getAccessibleIframeDocument(pageViewContext.editorIframe);
1083
- if (isElementActiveOrSelectedInIframe(iframeDocument, element))
1084
- return;
1085
- if (Date.now() - lastInlineUserEditAtRef.current < 1000)
1086
- return;
1087
- const savedPosition = saveCaretPosition(element);
1088
- let contentWasUpdated = false;
1089
- const fieldId = element.getAttribute("data-fieldid");
1090
- const itemId = element.getAttribute("data-itemid");
1091
- const language = element.getAttribute("data-language");
1092
- const versionStr = element.getAttribute("data-version");
1093
- if (!fieldId || !itemId || !language || !versionStr)
1094
- return;
1095
- const version = parseInt(versionStr, 10);
1096
- const descriptor = { id: itemId, language, version };
1097
- // Retrieve the current field value from the repository.
1098
- const loadedItem = await contextRef.current?.itemsRepository.getItem(descriptor);
1099
- if (!loadedItem)
1100
- return;
1101
- // Get the baseline value from the repository.
1102
- const repositoryField = loadedItem.fields.find((f) => f.id === fieldId);
1103
- let baseValue = repositoryField ? repositoryField.rawValue || "" : "";
1104
- // Override with the modified field value if it exists.
1105
- const modField = modifiedFieldsContext?.modifiedFields.find((mod) => mod.fieldId === fieldId &&
1106
- mod.item.id === itemId &&
1107
- mod.item.language === language &&
1108
- mod.item.version === version);
1109
- if (modField) {
1110
- baseValue = modField.value || "";
1111
- }
1112
- // If suggestions mode is active, merge all suggestions for this field.
1113
- if (contextRef.current?.mode === "suggestions") {
1114
- const pageVer = pageViewContext.pageItemDescriptor?.version;
1115
- const fieldSuggestions = (contextRef.current?.suggestedEdits ?? []).filter((s) => s.fieldId === fieldId &&
1116
- s.itemId === itemId &&
1117
- s.mainItemLanguage === language &&
1118
- (pageVer != null
1119
- ? s.mainItemVersion === pageVer || s.mainItemVersion === 0
1120
- : s.mainItemVersion === version));
1121
- // Sort suggestions in chronological order (oldest first).
1122
- fieldSuggestions.sort((a, b) => new Date(a.created).getTime() - new Date(b.created).getTime());
1123
- let mergedValue = baseValue;
1124
- for (const suggestion of fieldSuggestions) {
1125
- const patch = createPatch("field", suggestion.oldValue, suggestion.newValue);
1126
- const patchedCandidate = applyPatch(mergedValue, patch);
1127
- if (patchedCandidate !== false &&
1128
- typeof patchedCandidate === "string") {
1129
- mergedValue = patchedCandidate;
1130
- }
1131
- else {
1132
- mergedValue = suggestion.newValue;
1133
- }
1134
- }
1135
- // Update focused field element with the merged plain text value.
1136
- setInnerHTMLWithStackTrace(element, mergedValue, {
1137
- source: "updateFocusedFieldContent:mergedValue",
1138
- fieldId,
1139
- itemId,
1140
- language,
1141
- version,
1142
- });
1143
- contentWasUpdated = true;
1144
- }
1145
- else {
1146
- if (element.innerHTML !== baseValue) {
1147
- // If suggestions mode is off, update with the base value.
1148
- setInnerHTMLWithStackTrace(element, baseValue, {
1149
- source: "updateFocusedFieldContent:baseValue",
1150
- fieldId,
1151
- itemId,
1152
- language,
1153
- version,
1154
- });
1155
- contentWasUpdated = true;
1156
- }
1157
- }
1158
- if (contentWasUpdated) {
1159
- setTimeout(() => {
1160
- if (savedPosition)
1161
- restoreCaretPosition(element, savedPosition);
1162
- }, 0);
1163
- }
1164
- }
1165
- // Avoid rewriting the focused editable DOM while the user is typing in normal edit mode.
1166
- // This prevents lost/reordered characters on slow machines.
1167
- if (contextRef.current?.mode === "suggestions" ||
1168
- contextRef.current?.showSuggestedEdits) {
1169
- setTimeout(() => {
1170
- updateFocusedFieldContent();
1171
- }, 50);
1172
- }
1173
- return () => {
1174
- if (mutationObserverRef.current) {
1175
- mutationObserverRef.current.disconnect();
1176
- mutationObserverRef.current = null;
1177
- }
1178
- if (selectionChangeListenerRef.current) {
1179
- selectionChangeListenerRef.current();
1180
- selectionChangeListenerRef.current = null;
1181
- }
1182
- // Clean up any cursor indicators on unmount
1183
- const iframeDocument = getAccessibleIframeDocument(pageViewContext.editorIframe);
1184
- const cursorElement = iframeDocument?.getElementById(cursorSpanId);
1185
- if (cursorElement) {
1186
- cursorElement.parentNode?.removeChild(cursorElement);
1187
- }
1188
- clearRemoteCaretPosition();
1189
- };
1190
- }, [context.mode, modifiedFieldsContext?.inlineEditingFieldElement]);
1191
- function saveCaretPosition(editableElement) {
1192
- const selection = getAccessibleIframeSelection(pageViewContext.editorIframe);
1193
- if (!selection ||
1194
- selection.rangeCount === 0 ||
1195
- !editableElement.contains(selection.anchorNode)) {
1196
- return null;
1197
- }
1198
- // Get the current range
1199
- const range = selection.getRangeAt(0);
1200
- const getGlobalOffset = (node, offset) => {
1201
- let absoluteOffset = 0;
1202
- function walk(current) {
1203
- if (current === node) {
1204
- absoluteOffset += offset;
1205
- return true;
1206
- }
1207
- if (current.nodeType === Node.TEXT_NODE) {
1208
- absoluteOffset += current.length;
1209
- }
1210
- else {
1211
- for (let i = 0; i < current.childNodes.length; i++) {
1212
- if (walk(current.childNodes[i]))
1213
- return true;
1214
- }
1215
- }
1216
- return false;
1217
- }
1218
- walk(editableElement);
1219
- return absoluteOffset;
1220
- };
1221
- return {
1222
- startOffset: getGlobalOffset(range.startContainer, range.startOffset),
1223
- endOffset: getGlobalOffset(range.endContainer, range.endOffset),
1224
- };
1225
- }
1226
- function restoreCaretPosition(element, position) {
1227
- const offsets = typeof position === "number"
1228
- ? { startOffset: position, endOffset: position }
1229
- : position;
1230
- if (!offsets)
1231
- return;
1232
- const selection = getAccessibleIframeSelection(pageViewContext.editorIframe);
1233
- if (!selection)
1234
- return;
1235
- selection.removeAllRanges();
1236
- function findNodeAndOffset(targetPos) {
1237
- let currentPos = 0;
1238
- function walk(node) {
1239
- if (node.nodeType === Node.TEXT_NODE) {
1240
- const length = node.length;
1241
- if (currentPos + length >= targetPos) {
1242
- return { node, offset: targetPos - currentPos };
1243
- }
1244
- currentPos += length;
1245
- }
1246
- else {
1247
- for (let i = 0; i < node.childNodes.length; i++) {
1248
- const res = walk(node.childNodes[i]);
1249
- if (res)
1250
- return res;
1251
- }
1252
- }
1253
- return null;
1254
- }
1255
- return walk(element);
1256
- }
1257
- const start = findNodeAndOffset(offsets.startOffset);
1258
- const end = findNodeAndOffset(offsets.endOffset);
1259
- if (start && end) {
1260
- const range = new Range();
1261
- range.setStart(start.node, start.offset);
1262
- range.setEnd(end.node, end.offset);
1263
- selection.addRange(range);
1264
- }
1265
- else if (element.firstChild) {
1266
- // Fallback to end of content
1267
- const range = new Range();
1268
- range.selectNodeContents(element);
1269
- range.collapse(false);
1270
- selection.addRange(range);
1271
- }
1272
- }
1273
- useEffect(() => {
1274
- const updateFieldsWithSuggestions = async () => {
1275
- const iframeWindow = pageViewContext.editorIframe?.contentWindow;
1276
- if (!iframeWindow)
1277
- return;
1278
- const doc = iframeWindow.document;
1279
- // First, process modified fields directly (from updateFields functionality)
1280
- // IMPORTANT: Only update fields that belong to this page's language/version context.
1281
- // There can be multiple InlineEditor instances (main + comparison), and each should only
1282
- // update its own iframe's fields to avoid cross-contamination.
1283
- const currentPageLang = pageViewContext.pageItemDescriptor?.language;
1284
- const currentPageVer = pageViewContext.pageItemDescriptor?.version;
1285
- for (const modField of modifiedFieldsContext?.modifiedFields ?? []) {
1286
- // Skip fields that don't belong to this page's context
1287
- if (currentPageLang && modField.item.language !== currentPageLang)
1288
- continue;
1289
- if (currentPageVer && modField.item.version !== currentPageVer)
1290
- continue;
1291
- // In SXA markup, the same field can appear multiple times for a component.
1292
- // Update all matching nodes and only skip the exact node currently edited inline.
1293
- const allMatchingElements = doc.querySelectorAll(`[data-fieldid="${modField.fieldId}"][data-itemid="${modField.item.id}"]`);
1294
- const inlineEditingElement = modifiedFieldsContext?.inlineEditingFieldElement ?? null;
1295
- const isInlineEditActive = inlineEditingElement &&
1296
- isElementActiveOrSelectedInIframe(doc, inlineEditingElement);
1297
- const targetElements = Array.from(allMatchingElements).filter((candidate) => !isInlineEditActive || candidate !== inlineEditingElement);
1298
- if (targetElements.length > 0) {
1299
- const realField = await context.itemsRepository.getField({
1300
- fieldId: modField.fieldId,
1301
- item: modField.item,
1302
- });
1303
- const fieldType = realField?.type;
1304
- if (fieldType && isTextFieldType(fieldType)) {
1305
- const next = modField.value ? modField.value : "";
1306
- let wroteAtLeastOne = false;
1307
- for (const targetElement of targetElements) {
1308
- const currentHTML = targetElement.innerHTML;
1309
- if (currentHTML === next)
1310
- continue;
1311
- wroteAtLeastOne = true;
1312
- // Suppress MutationObserver to prevent feedback loop where server-driven
1313
- // updates get re-sent to the backend as new operations
1314
- isUpdatingRef.current = true;
1315
- setInnerHTMLWithStackTrace(targetElement, next, {
1316
- source: "updateFieldsWithSuggestions:modifiedField",
1317
- fieldId: modField.fieldId,
1318
- itemId: modField.item.id,
1319
- language: modField.item.language,
1320
- version: modField.item.version,
1321
- });
1322
- }
1323
- if (wroteAtLeastOne) {
1324
- // Reset after macrotask to ensure MutationObserver callbacks (which are microtasks) are suppressed
1325
- setTimeout(() => {
1326
- isUpdatingRef.current = false;
1327
- }, 0);
1328
- }
1329
- }
1330
- }
1331
- }
1332
- if (context.mode !== "suggestions" && !context.showSuggestedEdits) {
1333
- return;
1334
- }
1335
- // Now proceed with the original updateFieldsWithSuggestions logic
1336
- const allFieldElements = doc.querySelectorAll("[data-fieldid][data-itemid][data-language][data-version]");
1337
- const fieldItems = await context.itemsRepository.getItems(Array.from(allFieldElements)
1338
- .map((element) => {
1339
- const fieldId = element.getAttribute("data-fieldid");
1340
- return fieldId
1341
- ? {
1342
- id: fieldId,
1343
- language: "en",
1344
- version: 0,
1345
- }
1346
- : null;
1347
- })
1348
- .filter((item) => item !== null));
1349
- const validFields = fieldItems.filter((item) => {
1350
- const typeField = item.fields.find((f) => f.name === "Type");
1351
- if (!typeField)
1352
- return false;
1353
- const typeValue = typeField.value;
1354
- return typeValue && isTextFieldType(typeValue);
1355
- });
1356
- const validFieldElements = Array.from(allFieldElements).filter((element) => validFields.some((item) => element.getAttribute("data-fieldid") === item.id));
1357
- validFields.forEach(async (item, index) => {
1358
- const fieldElement = validFieldElements[index];
1359
- if (!fieldElement)
1360
- return;
1361
- // Do not update if this field is currently being edited inline.
1362
- // In suggestions mode we show suggested text in all fields, but must NOT overwrite
1363
- // the focused field while the user is actively typing - that resets the cursor.
1364
- // Treat stale iframe activeElement state as inactive once focus has left
1365
- // the iframe, otherwise persisted suggestions can overwrite active typing.
1366
- const fieldId = fieldElement.getAttribute("data-fieldid");
1367
- const itemId = fieldElement.getAttribute("data-itemid");
1368
- const language = fieldElement.getAttribute("data-language");
1369
- const versionStr = fieldElement.getAttribute("data-version");
1370
- if (!fieldId || !itemId || !language || !versionStr)
1371
- return;
1372
- const version = parseInt(versionStr, 10);
1373
- const isModifiedNow = modifiedFieldsContext?.modifiedFields?.some((m) => m.fieldId === fieldId &&
1374
- m.item.id === itemId &&
1375
- m.item.language === language &&
1376
- m.item.version === version);
1377
- const iframeDoc = getAccessibleIframeDocument(pageViewContext.editorIframe);
1378
- const isFieldBeingEdited = isInlineFieldBeingEdited(iframeDoc, fieldElement);
1379
- if (isFieldBeingEdited) {
1380
- return;
1381
- }
1382
- if (isSameFieldKey(fieldElement, activeFieldKey)) {
1383
- if (context.mode !== "suggestions") {
1384
- return;
1385
- }
1386
- }
1387
- // Skip if this field currently has local modifications to avoid stomping fresh edits.
1388
- // In suggestions mode, don't skip after the edit has been persisted - we need to
1389
- // re-apply the merged suggestion even if the iframe still reports the field as active.
1390
- if (isModifiedNow && context.mode !== "suggestions") {
1391
- return;
1392
- }
1393
- // Build an item descriptor.
1394
- const descriptor = { id: itemId, language, version };
1395
- // Fetch the current item from the repository.
1396
- const loadedItem = await context.itemsRepository.getItem(descriptor);
1397
- if (!loadedItem)
1398
- return;
1399
- // Get the baseline from repository.
1400
- const repositoryField = loadedItem.fields.find((f) => f.id === fieldId);
1401
- let originalValue = repositoryField
1402
- ? repositoryField.rawValue || ""
1403
- : "";
1404
- // If the field is modified locally, use that value.
1405
- const modField = modifiedFieldsContext?.modifiedFields.find((mod) => mod.fieldId === fieldId &&
1406
- mod.item.id === itemId &&
1407
- mod.item.language === language &&
1408
- mod.item.version === version);
1409
- if (modField) {
1410
- originalValue = modField.value || "";
1411
- }
1412
- // If showSuggestedEdits is false, update with the base value.
1413
- if (!context.showSuggestedEdits && context.mode !== "suggestions") {
1414
- if (fieldElement.innerHTML !== originalValue) {
1415
- setInnerHTMLWithStackTrace(fieldElement, originalValue, {
1416
- source: "updateFieldsWithSuggestions:baselineOnly",
1417
- fieldId,
1418
- itemId,
1419
- language,
1420
- version,
1421
- });
1422
- }
1423
- return;
1424
- }
1425
- // Otherwise, gather all suggestions for this field.
1426
- const pageId = pageViewContext.pageItemDescriptor?.id;
1427
- const pageVer = pageViewContext.pageItemDescriptor?.version;
1428
- const fieldSuggestions = context.suggestedEdits.filter((s) => s.status === "pending" &&
1429
- s.fieldId === fieldId &&
1430
- s.itemId === itemId &&
1431
- s.mainItemLanguage === language &&
1432
- s.mainItemId === pageId &&
1433
- (pageVer != null
1434
- ? s.mainItemVersion === pageVer || s.mainItemVersion === 0
1435
- : s.mainItemVersion === version));
1436
- if (!fieldSuggestions.length)
1437
- return;
1438
- // Sort suggestions in chronological order (oldest first).
1439
- fieldSuggestions.sort((a, b) => new Date(a.created).getTime() - new Date(b.created).getTime());
1440
- // Apply suggestions sequentially to generate the merged value.
1441
- let mergedValue = originalValue;
1442
- let couldApplyPatch = true;
1443
- for (const suggestion of fieldSuggestions) {
1444
- // Compute a patch from the suggestion's baseline to its intended new value.
1445
- const patch = createPatch("field", suggestion.oldValue, suggestion.newValue);
1446
- const patchedCandidate = applyPatch(mergedValue, patch);
1447
- if (patchedCandidate !== false &&
1448
- typeof patchedCandidate === "string") {
1449
- mergedValue = patchedCandidate;
1450
- }
1451
- else {
1452
- couldApplyPatch = false;
1453
- }
1454
- }
1455
- if (!couldApplyPatch)
1456
- mergedValue = fieldSuggestions[0]?.newValue || originalValue;
1457
- // If showSuggestedEditsDiff is false, show only the merged text
1458
- if (!context.showSuggestedEditsDiff) {
1459
- if (fieldElement.innerHTML !== mergedValue) {
1460
- setInnerHTMLWithStackTrace(fieldElement, mergedValue, {
1461
- source: "updateFieldsWithSuggestions:mergedNoDiff",
1462
- fieldId,
1463
- itemId,
1464
- language,
1465
- version,
1466
- });
1467
- }
1468
- return;
1469
- }
1470
- // Compute a word-based diff between originalValue and mergedValue.
1471
- const diffParts = diffWords(originalValue, mergedValue);
1472
- // Build HTML markup from the diff:
1473
- let diffHTML = "";
1474
- diffParts.forEach((part) => {
1475
- let style = "";
1476
- if (part.added) {
1477
- style = "color: green;";
1478
- }
1479
- else if (part.removed) {
1480
- style = "color: red; text-decoration: line-through;";
1481
- }
1482
- else {
1483
- style = "color: gray;";
1484
- }
1485
- // Escape any HTML in part.value if needed.
1486
- diffHTML += `<span style="${style}">${part.value}</span>`;
1487
- });
1488
- // Update the element's innerHTML with the diff markup.
1489
- if (fieldElement.innerHTML !== diffHTML) {
1490
- setInnerHTMLWithStackTrace(fieldElement, diffHTML, {
1491
- source: "updateFieldsWithSuggestions:diffMarkup",
1492
- fieldId,
1493
- itemId,
1494
- language,
1495
- version,
1496
- });
1497
- }
1498
- });
1499
- };
1500
- updateFieldsWithSuggestions();
1501
- }, [
1502
- context.mode,
1503
- modifiedFieldsContext?.modifiedFields,
1504
- context?.itemsRepository.revision,
1505
- modifiedFieldsContext?.inlineEditingFieldElement,
1506
- context?.showSuggestedEdits,
1507
- context?.suggestedEdits,
1508
- pageViewContext.pageItemDescriptor,
1509
- pageViewContext.page,
1510
- context.showSuggestedEditsDiff,
1511
- context.showSuggestedEdits,
1512
- context.remoteCarets,
1513
- ]);
1514
- useEffect(() => {
1515
- if (!context || compareView)
1516
- return;
1517
- const iframeDoc = getAccessibleIframeDocument(pageViewContext.editorIframe);
1518
- if (!iframeDoc)
1519
- return;
1520
- // Get current page language and version to filter elements - use currentItemDescriptor as fallback during transitions
1521
- const resetEffectPageLanguage = context.page?.item?.language ?? context.currentItemDescriptor?.language;
1522
- const resetEffectPageVersion = context.page?.item?.version ?? context.currentItemDescriptor?.version;
1523
- // Query all field elements
1524
- const allFieldEls = Array.from(iframeDoc.querySelectorAll("[data-fieldid][data-itemid][data-language][data-version]"));
1525
- allFieldEls.forEach(async (el) => {
1526
- // don't stomp on the one that's live‑editing (only in non-preview modes)
1527
- if (context.mode !== "preview" && isSameFieldKey(el, activeFieldKey))
1528
- return;
1529
- if (context.mode !== "preview" &&
1530
- isElementActiveOrSelectedInIframe(iframeDoc, el)) {
1531
- return;
1532
- }
1533
- const fieldId = el.getAttribute("data-fieldid");
1534
- const itemId = el.getAttribute("data-itemid");
1535
- const language = el.getAttribute("data-language");
1536
- const version = parseInt(el.getAttribute("data-version"), 10);
1537
- // CRITICAL FIX: Skip elements from other languages or versions during transitions
1538
- // to prevent cross-language/version contamination during rapid switching
1539
- if (resetEffectPageLanguage && language !== resetEffectPageLanguage) {
1540
- return;
1541
- }
1542
- if (resetEffectPageVersion && version !== resetEffectPageVersion) {
1543
- return;
1544
- }
1545
- // In suggestions mode (but not preview), only reset fields that don't have any suggested edits
1546
- if (context.mode === "suggestions" ||
1547
- (context.mode !== "preview" && context.showSuggestedEdits)) {
1548
- const hasActiveSuggestions = context.suggestedEdits.some((edit) => edit.status === "pending" &&
1549
- edit.fieldId === fieldId &&
1550
- edit.itemId === itemId &&
1551
- edit.mainItemLanguage === language &&
1552
- (resetEffectPageVersion != null
1553
- ? edit.mainItemVersion === resetEffectPageVersion ||
1554
- edit.mainItemVersion === 0
1555
- : edit.mainItemVersion === version));
1556
- // If field has active suggestions, don't reset it (let the other hook handle it)
1557
- if (hasActiveSuggestions)
1558
- return;
1559
- }
1560
- const realField = await context.itemsRepository.getItem({
1561
- id: fieldId,
1562
- language: "en",
1563
- version: 0,
1564
- });
1565
- const fieldType = realField?.fields.find((f) => f.name === "Type")?.value;
1566
- if (!fieldType || !isTextFieldType(fieldType)) {
1567
- return;
1568
- }
1569
- // lookup baseline
1570
- const loaded = await context.itemsRepository.getItem({
1571
- id: itemId,
1572
- language,
1573
- version,
1574
- });
1575
- const repoF = loaded?.fields.find((f) => f.id === fieldId);
1576
- let value = repoF?.rawValue ?? "";
1577
- // override with any local edit (only in non-preview modes)
1578
- // CRITICAL FIX: Only apply modified fields that match the current page language and version
1579
- // to prevent cross-language/version contamination during rapid switching
1580
- if (context.mode !== "preview" &&
1581
- resetEffectPageLanguage &&
1582
- language === resetEffectPageLanguage &&
1583
- resetEffectPageVersion &&
1584
- version === resetEffectPageVersion) {
1585
- const mod = modifiedFieldsContext?.modifiedFields.find((m) => m.fieldId === fieldId &&
1586
- m.item.id === itemId &&
1587
- m.item.language === language &&
1588
- m.item.version === version);
1589
- if (mod)
1590
- value = mod.value;
1591
- }
1592
- // write it in
1593
- if (el.innerHTML !== value) {
1594
- setInnerHTMLWithStackTrace(el, value, {
1595
- source: "resetAllFieldsToBaselineOrLocal",
1596
- fieldId,
1597
- itemId,
1598
- language,
1599
- version,
1600
- });
1601
- }
1602
- });
1603
- }, [
1604
- context.mode,
1605
- context.showSuggestedEdits,
1606
- context.suggestedEdits,
1607
- context.remoteCarets,
1608
- ]);
1609
- useEffect(() => {
1610
- if (compareView || context.mode === "preview") {
1611
- hideInlineFormatOverlay();
1612
- return;
1613
- }
1614
- const iframe = pageViewContext.editorIframe;
1615
- if (!iframe) {
1616
- hideInlineFormatOverlay();
1617
- return;
1618
- }
1619
- let detachDocumentListeners = null;
1620
- const attachToCurrentIframeDocument = () => {
1621
- detachDocumentListeners?.();
1622
- const iframeDocument = getAccessibleIframeDocument(iframe);
1623
- if (!iframeDocument) {
1624
- detachDocumentListeners = null;
1625
- hideInlineFormatOverlay();
1626
- return;
1627
- }
1628
- let animationFrame = 0;
1629
- const scheduleUpdate = () => {
1630
- window.cancelAnimationFrame(animationFrame);
1631
- animationFrame = window.requestAnimationFrame(updateInlineFormatOverlay);
1632
- };
1633
- const handlePointerDown = () => {
1634
- inlineFormatPointerDownRef.current = true;
1635
- hideInlineFormatOverlay();
1636
- };
1637
- const handlePointerUp = () => {
1638
- inlineFormatPointerDownRef.current = false;
1639
- scheduleUpdate();
1640
- };
1641
- iframeDocument.addEventListener("selectionchange", scheduleUpdate);
1642
- iframeDocument.addEventListener("pointerdown", handlePointerDown);
1643
- iframeDocument.addEventListener("pointerup", handlePointerUp);
1644
- iframeDocument.addEventListener("mouseup", scheduleUpdate);
1645
- iframeDocument.addEventListener("keyup", scheduleUpdate);
1646
- iframe.contentWindow?.addEventListener("scroll", scheduleUpdate, true);
1647
- window.addEventListener("scroll", scheduleUpdate, true);
1648
- window.addEventListener("resize", scheduleUpdate);
1649
- scheduleUpdate();
1650
- detachDocumentListeners = () => {
1651
- window.cancelAnimationFrame(animationFrame);
1652
- iframeDocument.removeEventListener("selectionchange", scheduleUpdate);
1653
- iframeDocument.removeEventListener("pointerdown", handlePointerDown);
1654
- iframeDocument.removeEventListener("pointerup", handlePointerUp);
1655
- iframeDocument.removeEventListener("mouseup", scheduleUpdate);
1656
- iframeDocument.removeEventListener("keyup", scheduleUpdate);
1657
- iframe.contentWindow?.removeEventListener("scroll", scheduleUpdate, true);
1658
- window.removeEventListener("scroll", scheduleUpdate, true);
1659
- window.removeEventListener("resize", scheduleUpdate);
1660
- };
1661
- };
1662
- attachToCurrentIframeDocument();
1663
- iframe.addEventListener("load", attachToCurrentIframeDocument);
1664
- return () => {
1665
- iframe.removeEventListener("load", attachToCurrentIframeDocument);
1666
- detachDocumentListeners?.();
1667
- };
1668
- }, [compareView, context.mode, pageViewContext.editorIframe]);
1669
- useEffect(() => {
1670
- const animationFrame = window.requestAnimationFrame(updateInlineFormatOverlay);
1671
- return () => window.cancelAnimationFrame(animationFrame);
1672
- }, [
1673
- context.selectedRange?.fieldId,
1674
- context.selectedRange?.itemId,
1675
- context.selectedRange?.language,
1676
- context.selectedRange?.version,
1677
- context.selectedRange?.startOffset,
1678
- context.selectedRange?.endOffset,
1679
- context.selectedRange?.text,
1680
- ]);
1681
- useEffect(() => {
1682
- if (!inlineFormatOverlay.visible)
1683
- return;
1684
- const handlePointerDown = (event) => {
1685
- const target = event.target;
1686
- if (target &&
1687
- (inlineFormatOverlayRef.current?.contains(target) ||
1688
- target === pageViewContext.editorIframe)) {
1689
- return;
1690
- }
1691
- hideInlineFormatOverlay();
1692
- };
1693
- window.addEventListener("pointerdown", handlePointerDown, true);
1694
- return () => {
1695
- window.removeEventListener("pointerdown", handlePointerDown, true);
1696
- };
1697
- }, [inlineFormatOverlay.visible, pageViewContext.editorIframe]);
1698
- const visibleInlineFormatCommands = inlineFormatSelectionRef.current
1699
- ? (inlineFormatCommandsCacheRef.current.get(getInlineFieldKey(inlineFormatSelectionRef.current.fieldElement)) ?? [])
1700
- : [];
1701
- if (!inlineFormatOverlay.visible ||
1702
- visibleInlineFormatCommands.length === 0) {
1703
- return null;
1704
- }
1705
- const overlay = (_jsx("div", { ref: inlineFormatOverlayRef, className: "border-border-default bg-neutral-grey-5 fixed z-[1000] flex h-8 items-center gap-0.5 rounded-md border px-0.5 py-px shadow-lg", style: {
1706
- left: inlineFormatOverlay.left,
1707
- top: inlineFormatOverlay.top,
1708
- }, onMouseDown: (event) => {
1709
- event.preventDefault();
1710
- event.stopPropagation();
1711
- }, children: visibleInlineFormatCommands.map((item) => {
1712
- const isActive = inlineFormatOverlay.activeCommandIds.includes(item.id);
1713
- return (_jsx("button", { type: "button", className: `inline-flex h-7 w-7 items-center justify-center rounded-sm p-0 text-[13px] leading-none font-semibold focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-blue-600 ${isActive
1714
- ? "text-neutral-grey-100 bg-white"
1715
- : "bg-neutral-grey-5 text-neutral-grey-100 hover:bg-neutral-grey-10"}`, title: item.label, "aria-label": item.label, "aria-pressed": isActive, onMouseDown: (event) => void applyInlineFormatCommand(item, event), children: _jsx("span", { "aria-hidden": "true", className: "inline-flex translate-y-px items-center justify-center leading-none", children: item.icon ?? item.label }) }, item.id));
1716
- }) }));
1717
- return createPortal(overlay, document.body);
1718
- }
1719
- //# sourceMappingURL=InlineEditor.js.map