@parhelia/core 0.4.12784 → 0.4.12800

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 (158) hide show
  1. package/dist/agents-view/AgentCard.js +1 -1
  2. package/dist/agents-view/AgentCard.js.map +1 -1
  3. package/dist/agents-view/AgentsView.js +4 -2
  4. package/dist/agents-view/AgentsView.js.map +1 -1
  5. package/dist/agents-view/DateAgentsGroup.js +1 -1
  6. package/dist/agents-view/DateAgentsGroup.js.map +1 -1
  7. package/dist/config/config.js +38 -7
  8. package/dist/config/config.js.map +1 -1
  9. package/dist/config/types/sidebar.d.ts +8 -0
  10. package/dist/config/types/workspace.d.ts +8 -0
  11. package/dist/config/types.d.ts +2 -0
  12. package/dist/config/types.js.map +1 -1
  13. package/dist/editor/FieldListField.js +8 -4
  14. package/dist/editor/FieldListField.js.map +1 -1
  15. package/dist/editor/GlobalMenuBar.js +8 -6
  16. package/dist/editor/GlobalMenuBar.js.map +1 -1
  17. package/dist/editor/LinkEditorDialog.d.ts +2 -0
  18. package/dist/editor/LinkEditorDialog.js +6 -1
  19. package/dist/editor/LinkEditorDialog.js.map +1 -1
  20. package/dist/editor/ai/Agents.js +19 -8
  21. package/dist/editor/ai/Agents.js.map +1 -1
  22. package/dist/editor/ai/terminal/components/AgentCostDisplay.d.ts +2 -3
  23. package/dist/editor/ai/terminal/components/AgentCostDisplay.js +4 -5
  24. package/dist/editor/ai/terminal/components/AgentCostDisplay.js.map +1 -1
  25. package/dist/editor/ai/terminal/components/AgentGreeting.js +4 -1
  26. package/dist/editor/ai/terminal/components/AgentGreeting.js.map +1 -1
  27. package/dist/editor/ai/terminal/components/AgentProfileSwitcher.js +1 -1
  28. package/dist/editor/ai/terminal/components/AgentProfileSwitcher.js.map +1 -1
  29. package/dist/editor/ai/terminal/components/AgentPromptActionButtons.js +1 -1
  30. package/dist/editor/ai/terminal/components/AgentPromptActionButtons.js.map +1 -1
  31. package/dist/editor/ai/terminal/components/AgentPromptInputArea.js +1 -1
  32. package/dist/editor/ai/terminal/components/AgentPromptInputArea.js.map +1 -1
  33. package/dist/editor/ai/terminal/components/AgentTerminalFullUpperContent.js +11 -4
  34. package/dist/editor/ai/terminal/components/AgentTerminalFullUpperContent.js.map +1 -1
  35. package/dist/editor/ai/terminal/components/AgentTerminalStatusBar.js +3 -3
  36. package/dist/editor/ai/terminal/components/AgentTerminalStatusBar.js.map +1 -1
  37. package/dist/editor/ai/terminal/components/AgentThinkingIndicator.d.ts +14 -1
  38. package/dist/editor/ai/terminal/components/AgentThinkingIndicator.js +10 -3
  39. package/dist/editor/ai/terminal/components/AgentThinkingIndicator.js.map +1 -1
  40. package/dist/editor/ai/terminal/components/AiResponseMessage.js +28 -20
  41. package/dist/editor/ai/terminal/components/AiResponseMessage.js.map +1 -1
  42. package/dist/editor/ai/terminal/components/ApprovalCard.js +67 -4
  43. package/dist/editor/ai/terminal/components/ApprovalCard.js.map +1 -1
  44. package/dist/editor/ai/terminal/components/InitialThinkingSplash.js +2 -2
  45. package/dist/editor/ai/terminal/components/InitialThinkingSplash.js.map +1 -1
  46. package/dist/editor/ai/terminal/components/ToolCallDisplay.d.ts +3 -1
  47. package/dist/editor/ai/terminal/components/ToolCallDisplay.js +5 -3
  48. package/dist/editor/ai/terminal/components/ToolCallDisplay.js.map +1 -1
  49. package/dist/editor/ai/terminal/components/agentThinkingStyles.d.ts +21 -0
  50. package/dist/editor/ai/terminal/components/agentThinkingStyles.js +21 -0
  51. package/dist/editor/ai/terminal/components/agentThinkingStyles.js.map +1 -1
  52. package/dist/editor/ai/terminal/components/approvalActions.d.ts +1 -0
  53. package/dist/editor/ai/terminal/components/approvalActions.js +5 -2
  54. package/dist/editor/ai/terminal/components/approvalActions.js.map +1 -1
  55. package/dist/editor/ai/terminal/components/useApprovalActions.d.ts +4 -4
  56. package/dist/editor/ai/terminal/components/useApprovalActions.js +5 -2
  57. package/dist/editor/ai/terminal/components/useApprovalActions.js.map +1 -1
  58. package/dist/editor/ai/terminal/useAgentSubmitHandlers.d.ts +1 -0
  59. package/dist/editor/ai/terminal/useAgentSubmitHandlers.js +6 -1
  60. package/dist/editor/ai/terminal/useAgentSubmitHandlers.js.map +1 -1
  61. package/dist/editor/ai/terminal/useAgentTerminalController.js +1 -0
  62. package/dist/editor/ai/terminal/useAgentTerminalController.js.map +1 -1
  63. package/dist/editor/client/EditorShell.js +706 -692
  64. package/dist/editor/client/EditorShell.js.map +1 -1
  65. package/dist/editor/client/ui/DevModeIndicator.js +3 -3
  66. package/dist/editor/client/ui/DevModeIndicator.js.map +1 -1
  67. package/dist/editor/commands/componentCommands.js +1 -2
  68. package/dist/editor/commands/componentCommands.js.map +1 -1
  69. package/dist/editor/commands/mediaCommands.d.ts +5 -0
  70. package/dist/editor/commands/mediaCommands.js +59 -0
  71. package/dist/editor/commands/mediaCommands.js.map +1 -0
  72. package/dist/editor/field-types/FieldHistoryBoundary.d.ts +7 -0
  73. package/dist/editor/field-types/FieldHistoryBoundary.js +19 -0
  74. package/dist/editor/field-types/FieldHistoryBoundary.js.map +1 -0
  75. package/dist/editor/field-types/LinkFieldEditor.js +15 -4
  76. package/dist/editor/field-types/LinkFieldEditor.js.map +1 -1
  77. package/dist/editor/field-types/MultiLineText.js +6 -1
  78. package/dist/editor/field-types/MultiLineText.js.map +1 -1
  79. package/dist/editor/field-types/RichTextEditor.js +5 -5
  80. package/dist/editor/field-types/RichTextEditor.js.map +1 -1
  81. package/dist/editor/field-types/SingleLineText.js +6 -1
  82. package/dist/editor/field-types/SingleLineText.js.map +1 -1
  83. package/dist/editor/field-types/TreeListEditor.js +3 -3
  84. package/dist/editor/field-types/TreeListEditor.js.map +1 -1
  85. package/dist/editor/fieldTypes.d.ts +2 -0
  86. package/dist/editor/itemContextMenuGroups.d.ts +16 -0
  87. package/dist/editor/itemContextMenuGroups.js +48 -0
  88. package/dist/editor/itemContextMenuGroups.js.map +1 -0
  89. package/dist/editor/media-selector/UploadMediaFileDialog.d.ts +5 -0
  90. package/dist/editor/media-selector/UploadMediaFileDialog.js +14 -0
  91. package/dist/editor/media-selector/UploadMediaFileDialog.js.map +1 -0
  92. package/dist/editor/page-editor-chrome/PageEditorChrome.js +4 -0
  93. package/dist/editor/page-editor-chrome/PageEditorChrome.js.map +1 -1
  94. package/dist/editor/reviews/CreateReviewDialog.js +21 -4
  95. package/dist/editor/reviews/CreateReviewDialog.js.map +1 -1
  96. package/dist/editor/reviews/CreateReviewReviewersStep.d.ts +6 -1
  97. package/dist/editor/reviews/CreateReviewReviewersStep.js +29 -27
  98. package/dist/editor/reviews/CreateReviewReviewersStep.js.map +1 -1
  99. package/dist/editor/reviews/CreateReviewSuccessStep.d.ts +8 -1
  100. package/dist/editor/reviews/CreateReviewSuccessStep.js +4 -3
  101. package/dist/editor/reviews/CreateReviewSuccessStep.js.map +1 -1
  102. package/dist/editor/reviews/ReviewersPanel.js +4 -1
  103. package/dist/editor/reviews/ReviewersPanel.js.map +1 -1
  104. package/dist/editor/services/aiService.d.ts +2 -0
  105. package/dist/editor/services/aiService.js.map +1 -1
  106. package/dist/editor/services/reviewsService.d.ts +2 -2
  107. package/dist/editor/services/reviewsService.js +3 -1
  108. package/dist/editor/services/reviewsService.js.map +1 -1
  109. package/dist/editor/settings/About.js +4 -3
  110. package/dist/editor/settings/About.js.map +1 -1
  111. package/dist/editor/settings/SettingsView.js +1 -1
  112. package/dist/editor/settings/SettingsView.js.map +1 -1
  113. package/dist/editor/settings/panels/AgentProfileConfigPanel.js +1 -0
  114. package/dist/editor/settings/panels/AgentProfileConfigPanel.js.map +1 -1
  115. package/dist/editor/settings/panels/JavaScriptToolConfigPanel.js +11 -0
  116. package/dist/editor/settings/panels/JavaScriptToolConfigPanel.js.map +1 -1
  117. package/dist/editor/settings/panels/PublicSiteEntryDialog.js +1 -1
  118. package/dist/editor/settings/panels/PublicSiteEntryDialog.js.map +1 -1
  119. package/dist/editor/sidebar/ComponentPalette.js +1 -1
  120. package/dist/editor/sidebar/ComponentPalette.js.map +1 -1
  121. package/dist/editor/sidebar/OperationDetailsContent.d.ts +14 -0
  122. package/dist/editor/sidebar/OperationDetailsContent.js +521 -0
  123. package/dist/editor/sidebar/OperationDetailsContent.js.map +1 -0
  124. package/dist/editor/sidebar/OperationItem.js +9 -77
  125. package/dist/editor/sidebar/OperationItem.js.map +1 -1
  126. package/dist/editor/ui/PerfectTree.js +4 -1
  127. package/dist/editor/ui/PerfectTree.js.map +1 -1
  128. package/dist/editor/ui/PublishDialog.d.ts +4 -2
  129. package/dist/editor/ui/PublishDialog.js +30 -13
  130. package/dist/editor/ui/PublishDialog.js.map +1 -1
  131. package/dist/editor/ui/publishReadiness.d.ts +21 -1
  132. package/dist/editor/ui/publishReadiness.js +40 -0
  133. package/dist/editor/ui/publishReadiness.js.map +1 -1
  134. package/dist/editor/utils/keyboardNavigation.js +20 -0
  135. package/dist/editor/utils/keyboardNavigation.js.map +1 -1
  136. package/dist/editor/utils/linkDisplay.d.ts +12 -0
  137. package/dist/editor/utils/linkDisplay.js +23 -0
  138. package/dist/editor/utils/linkDisplay.js.map +1 -0
  139. package/dist/editor/views/ItemEditor.js +30 -5
  140. package/dist/editor/views/ItemEditor.js.map +1 -1
  141. package/dist/revision.d.ts +2 -2
  142. package/dist/revision.js +2 -2
  143. package/dist/task-board/components/GoalDataView.js +120 -27
  144. package/dist/task-board/components/GoalDataView.js.map +1 -1
  145. package/dist/tour/Tour.js +3 -2
  146. package/dist/tour/Tour.js.map +1 -1
  147. package/dist/tour/TourInputBoundary.d.ts +6 -0
  148. package/dist/tour/TourInputBoundary.js +82 -0
  149. package/dist/tour/TourInputBoundary.js.map +1 -0
  150. package/dist/tour/default-tour.js +4 -4
  151. package/dist/tour/default-tour.js.map +1 -1
  152. package/dist/tour/preview-tour.js +1 -1
  153. package/dist/tour/preview-tour.js.map +1 -1
  154. package/dist/tour/sandbox-tour.js +2 -1
  155. package/dist/tour/sandbox-tour.js.map +1 -1
  156. package/dist/types.d.ts +18 -0
  157. package/package.json +1 -1
  158. package/styles.css +6 -1
@@ -4855,733 +4855,747 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
4855
4855
  };
4856
4856
  }
4857
4857
  const factoriesRef = globalThis.__editorContextFactoriesRef;
4858
- const editContext = useMemo(() => ({
4859
- operations: operationsContext.ops,
4860
- itemsRepository,
4861
- configuration,
4862
- updateUrl,
4863
- openSplashScreen: () => {
4864
- const currentParams = new URLSearchParams(window.location.search);
4865
- const isAlreadyOnHome = workspaceId === "home" &&
4866
- (!currentParams.get("workspace") ||
4867
- currentParams.get("workspace") === "home");
4868
- if (isAlreadyOnHome) {
4869
- // Already on home workspace, no need to navigate - this prevents reloading the tour
4870
- return;
4871
- }
4872
- // Ensure we actually land on the home workspace (not just change the URL).
4873
- // This is especially important when called from the tour, where the current workspace
4874
- // might be the editor and URL-only navigation would get "corrected" back.
4875
- try {
4876
- switchWorkspace("home", {
4877
- skipConfirmation: true,
4878
- skipNavigationHistory: true,
4879
- });
4880
- }
4881
- catch { }
4882
- const preservedParams = new URLSearchParams();
4883
- if (currentParams.get("fullscreen") === "true") {
4884
- preservedParams.set("fullscreen", "true");
4885
- }
4886
- // Preserve tour state so the tour survives navigation
4887
- const currentTour = currentParams.get("tour");
4888
- if (currentTour) {
4889
- preservedParams.set("tour", currentTour);
4890
- }
4891
- const queryString = preservedParams.toString();
4892
- // Use the current Next.js pathname so this works correctly with or without
4893
- // a configured basePath (e.g. basePath="/parhelia" in the static export).
4894
- // Avoid hard-coding "/parhelia" here because Next will automatically
4895
- // prepend the basePath when navigating.
4896
- const basePath = pathname || "/";
4897
- const newUrl = queryString ? `${basePath}?${queryString}` : basePath;
4898
- router.push(newUrl, { scroll: false });
4899
- },
4900
- item: item || page?.item,
4901
- itemLanguages,
4902
- itemVersions,
4903
- isLoadingVersions,
4904
- loadItemVersions,
4905
- sessionId: sessionId,
4906
- readonly: isReadOnly,
4907
- getSelection: () => selectionRef.current,
4908
- select: (ids) => {
4909
- // Re-selecting the same components is a no-op: without this bail a
4910
- // fresh array identity re-renders every selection consumer.
4911
- const current = selectionRef.current;
4912
- if (ids.length === current.length &&
4913
- ids.every((id, index) => id === current[index])) {
4914
- perfTraceStep("shell:setSelection-bailed (unchanged)", { ids });
4915
- return;
4916
- }
4917
- perfTraceStep("shell:setSelection-state-update", { ids });
4918
- setSelection(ids);
4919
- },
4920
- selectWithoutAutoScroll,
4921
- consumeNextSelectionAutoScrollSuppression,
4922
- consumeNextFocusedFieldAutoScrollSuppression,
4923
- contentTreeSelection,
4924
- setContentTreeSelection,
4925
- selectedForInsertion,
4926
- setSelectedForInsertion,
4927
- dragObject,
4928
- getDragObject: () => dragObjectRef.current,
4929
- clipboard,
4930
- setClipboard,
4931
- workboxItems,
4932
- isWorkboxLoading,
4933
- refreshWorkbox,
4934
- requestRefresh,
4935
- refreshCompletedFlag,
4936
- openCreatePageDialog: () => {
4937
- const current = new URLSearchParams(searchParams.toString());
4938
- current.delete("version");
4939
- current.delete("itemid");
4940
- current.delete("workspace"); // Clear workspace
4941
- current.set("create", "1");
4942
- const newUrl = `${pathname}?${current.toString()}`;
4943
- const oldUrl = `${pathname}?${searchParams.toString()}`;
4944
- router.push(newUrl, { scroll: false });
4945
- },
4946
- selectMedia: media.selectMedia,
4947
- showToast: (message) => {
4948
- // Ensure only strings are passed to toast to prevent accidental JSON display
4949
- if (typeof message === "string") {
4950
- toast(message);
4951
- }
4952
- else {
4953
- console.warn("showToast called with non-string value:", message);
4954
- toast("An operation occurred");
4955
- }
4956
- },
4957
- scrollIntoView,
4958
- setScrollIntoView,
4959
- focusedField,
4960
- setFocusedField: setFocusedFieldWithLock,
4961
- getComponentCommands: async (components, providerContext) => {
4962
- return await getComponentCommands(components, editContext, providerContext);
4963
- },
4964
- dragStart: (nextDragObject) => {
4965
- // A drag supersedes any unresolved scroll request (e.g. a comment
4966
- // target still waiting for geometry). Left pending, it would replay
4967
- // against the post-drop refresh and yank the canvas to a stale spot.
4968
- setScrollIntoView(undefined);
4969
- dragObjectRef.current = nextDragObject;
4970
- setDragObject(nextDragObject);
4971
- },
4972
- dragEnd: () => {
4973
- dragObjectRef.current = undefined;
4974
- setDragObject(undefined);
4975
- },
4976
- droppedInPlaceholder: async (placeholderKey, index, spotPositionElement, spotPositionAnchor, insertOption, spotPosition, color) => {
4977
- const droppedDragObject = dragObjectRef.current;
4978
- if ((!droppedDragObject && !insertOption) || !page) {
4979
- return { committed: false };
4980
- }
4981
- dragObjectRef.current = undefined;
4982
- setDragObject(undefined);
4983
- const placeholderKeyComponents = placeholderKey.split("_");
4984
- const parentId = placeholderKeyComponents.length === 1
4985
- ? undefined
4986
- : placeholderKeyComponents[1];
4987
- // Selection is left alone at drop. The server response's focus id
4988
- // selects the moved/added component (executeEditOperation); selecting
4989
- // the drop target's parent here made the single-selection frame
4990
- // FLIP-animate component -> parent -> component around every drop.
4991
- let op;
4992
- // A placeholder is rendered from this page model, so its insertion
4993
- // must target the same item. The shell-level descriptor can briefly
4994
- // lag while slots or a newly created page settle; using it here could
4995
- // either discard a valid click or write the component to another page.
4996
- const dropTargetDescriptor = page.item.descriptor;
4997
- let insertionSequence;
4998
- if ((spotPosition || spotPositionElement) && spotPositionAnchor) {
4999
- insertionSequence = ++insertingSequenceRef.current;
5000
- setInserting({
5001
- position: spotPosition,
5002
- positionElement: spotPositionElement,
5003
- positionAnchor: spotPositionAnchor,
5004
- color,
5005
- });
5006
- }
5007
- let operationPromise;
5008
- if (insertOption) {
5009
- // Clear any selected component type for insertion once the component
5010
- // has been dropped into a placeholder. This ensures that insert mode
5011
- // can exit cleanly and the editor header updates from "Add Component"
5012
- // to the actual component name after the operation completes.
5013
- setSelectedForInsertion("");
5014
- operationPromise = operations.addComponent(insertOption.typeId, placeholderKey, index, dropTargetDescriptor, { showIndicator: false });
5015
- }
5016
- else if (droppedDragObject?.type == "template") {
5017
- operationPromise = operations.addComponent(droppedDragObject.typeId, placeholderKey, index, dropTargetDescriptor, { showIndicator: false });
5018
- }
5019
- else if (droppedDragObject?.type == "component" ||
5020
- (droppedDragObject?.type == "items" && droppedDragObject.items)) {
5021
- const parentComponent = parentId && page ? getComponentById(parentId, page) : null;
5022
- if (droppedDragObject.type == "items" && droppedDragObject.items) {
5023
- op = {
5024
- type: "link-component",
5025
- mainItem: page.item.descriptor,
5026
- parent: parentId && {
5027
- id: parentId,
5028
- language: page.item.descriptor.language,
5029
- version: page.item.descriptor.version,
5030
- name: parentComponent?.name,
5031
- },
5032
- placeholderKey,
5033
- placeholderIndex: index,
5034
- date: new Date().toISOString(),
5035
- id: uuid(),
5036
- linkedComponentItem: droppedDragObject.items[0],
5037
- title: "Link component",
5038
- };
4858
+ /**
4859
+ * Workspaces shown in the rail: not hidden, allowed by the server's workspace list (when provided) and,
4860
+ * for workspaces with a dynamic `visible` callback, visible for the given (complete) context.
4861
+ */
4862
+ const filterVisibleWorkspaces = (context) => (context.configuration.editor.workspaces ?? [])
4863
+ .filter((w) => {
4864
+ if (w.hidden)
4865
+ return false;
4866
+ if (w.visible && !w.visible(context))
4867
+ return false;
4868
+ // Filter by workspace settings from Sitecore (if provided)
4869
+ const serverWorkspaces = context.userInfo?.workspaces;
4870
+ if (serverWorkspaces && serverWorkspaces.length > 0) {
4871
+ return serverWorkspaces.some((ws) => ws.id === w.id);
4872
+ }
4873
+ return true;
4874
+ })
4875
+ .sort((a, b) => (a.sortOrder ?? 100) - (b.sortOrder ?? 100));
4876
+ const editContext = useMemo(() => {
4877
+ // Typed as the context (for contextual parameter types) plus the runtime-only extras the literal carries.
4878
+ const editContextValue = {
4879
+ operations: operationsContext.ops,
4880
+ itemsRepository,
4881
+ configuration,
4882
+ updateUrl,
4883
+ openSplashScreen: () => {
4884
+ const currentParams = new URLSearchParams(window.location.search);
4885
+ const isAlreadyOnHome = workspaceId === "home" &&
4886
+ (!currentParams.get("workspace") ||
4887
+ currentParams.get("workspace") === "home");
4888
+ if (isAlreadyOnHome) {
4889
+ // Already on home workspace, no need to navigate - this prevents reloading the tour
4890
+ return;
4891
+ }
4892
+ // Ensure we actually land on the home workspace (not just change the URL).
4893
+ // This is especially important when called from the tour, where the current workspace
4894
+ // might be the editor and URL-only navigation would get "corrected" back.
4895
+ try {
4896
+ switchWorkspace("home", {
4897
+ skipConfirmation: true,
4898
+ skipNavigationHistory: true,
4899
+ });
4900
+ }
4901
+ catch { }
4902
+ const preservedParams = new URLSearchParams();
4903
+ if (currentParams.get("fullscreen") === "true") {
4904
+ preservedParams.set("fullscreen", "true");
4905
+ }
4906
+ // Preserve tour state so the tour survives navigation
4907
+ const currentTour = currentParams.get("tour");
4908
+ if (currentTour) {
4909
+ preservedParams.set("tour", currentTour);
4910
+ }
4911
+ const queryString = preservedParams.toString();
4912
+ // Use the current Next.js pathname so this works correctly with or without
4913
+ // a configured basePath (e.g. basePath="/parhelia" in the static export).
4914
+ // Avoid hard-coding "/parhelia" here because Next will automatically
4915
+ // prepend the basePath when navigating.
4916
+ const basePath = pathname || "/";
4917
+ const newUrl = queryString ? `${basePath}?${queryString}` : basePath;
4918
+ router.push(newUrl, { scroll: false });
4919
+ },
4920
+ item: item || page?.item,
4921
+ itemLanguages,
4922
+ itemVersions,
4923
+ isLoadingVersions,
4924
+ loadItemVersions,
4925
+ sessionId: sessionId,
4926
+ readonly: isReadOnly,
4927
+ getSelection: () => selectionRef.current,
4928
+ select: (ids) => {
4929
+ // Re-selecting the same components is a no-op: without this bail a
4930
+ // fresh array identity re-renders every selection consumer.
4931
+ const current = selectionRef.current;
4932
+ if (ids.length === current.length &&
4933
+ ids.every((id, index) => id === current[index])) {
4934
+ perfTraceStep("shell:setSelection-bailed (unchanged)", { ids });
4935
+ return;
4936
+ }
4937
+ perfTraceStep("shell:setSelection-state-update", { ids });
4938
+ setSelection(ids);
4939
+ },
4940
+ selectWithoutAutoScroll,
4941
+ consumeNextSelectionAutoScrollSuppression,
4942
+ consumeNextFocusedFieldAutoScrollSuppression,
4943
+ contentTreeSelection,
4944
+ setContentTreeSelection,
4945
+ selectedForInsertion,
4946
+ setSelectedForInsertion,
4947
+ dragObject,
4948
+ getDragObject: () => dragObjectRef.current,
4949
+ clipboard,
4950
+ setClipboard,
4951
+ workboxItems,
4952
+ isWorkboxLoading,
4953
+ refreshWorkbox,
4954
+ requestRefresh,
4955
+ refreshCompletedFlag,
4956
+ openCreatePageDialog: () => {
4957
+ const current = new URLSearchParams(searchParams.toString());
4958
+ current.delete("version");
4959
+ current.delete("itemid");
4960
+ current.delete("workspace"); // Clear workspace
4961
+ current.set("create", "1");
4962
+ const newUrl = `${pathname}?${current.toString()}`;
4963
+ const oldUrl = `${pathname}?${searchParams.toString()}`;
4964
+ router.push(newUrl, { scroll: false });
4965
+ },
4966
+ selectMedia: media.selectMedia,
4967
+ showToast: (message) => {
4968
+ // Ensure only strings are passed to toast to prevent accidental JSON display
4969
+ if (typeof message === "string") {
4970
+ toast(message);
5039
4971
  }
5040
4972
  else {
5041
- const componentIds = droppedDragObject.components?.map((c) => c.id) ?? [];
5042
- if (componentIds.length > 0) {
4973
+ console.warn("showToast called with non-string value:", message);
4974
+ toast("An operation occurred");
4975
+ }
4976
+ },
4977
+ scrollIntoView,
4978
+ setScrollIntoView,
4979
+ focusedField,
4980
+ setFocusedField: setFocusedFieldWithLock,
4981
+ getComponentCommands: async (components, providerContext) => {
4982
+ return await getComponentCommands(components, editContext, providerContext);
4983
+ },
4984
+ dragStart: (nextDragObject) => {
4985
+ // A drag supersedes any unresolved scroll request (e.g. a comment
4986
+ // target still waiting for geometry). Left pending, it would replay
4987
+ // against the post-drop refresh and yank the canvas to a stale spot.
4988
+ setScrollIntoView(undefined);
4989
+ dragObjectRef.current = nextDragObject;
4990
+ setDragObject(nextDragObject);
4991
+ },
4992
+ dragEnd: () => {
4993
+ dragObjectRef.current = undefined;
4994
+ setDragObject(undefined);
4995
+ },
4996
+ droppedInPlaceholder: async (placeholderKey, index, spotPositionElement, spotPositionAnchor, insertOption, spotPosition, color) => {
4997
+ const droppedDragObject = dragObjectRef.current;
4998
+ if ((!droppedDragObject && !insertOption) || !page) {
4999
+ return { committed: false };
5000
+ }
5001
+ dragObjectRef.current = undefined;
5002
+ setDragObject(undefined);
5003
+ const placeholderKeyComponents = placeholderKey.split("_");
5004
+ const parentId = placeholderKeyComponents.length === 1
5005
+ ? undefined
5006
+ : placeholderKeyComponents[1];
5007
+ // Selection is left alone at drop. The server response's focus id
5008
+ // selects the moved/added component (executeEditOperation); selecting
5009
+ // the drop target's parent here made the single-selection frame
5010
+ // FLIP-animate component -> parent -> component around every drop.
5011
+ let op;
5012
+ // A placeholder is rendered from this page model, so its insertion
5013
+ // must target the same item. The shell-level descriptor can briefly
5014
+ // lag while slots or a newly created page settle; using it here could
5015
+ // either discard a valid click or write the component to another page.
5016
+ const dropTargetDescriptor = page.item.descriptor;
5017
+ let insertionSequence;
5018
+ if ((spotPosition || spotPositionElement) && spotPositionAnchor) {
5019
+ insertionSequence = ++insertingSequenceRef.current;
5020
+ setInserting({
5021
+ position: spotPosition,
5022
+ positionElement: spotPositionElement,
5023
+ positionAnchor: spotPositionAnchor,
5024
+ color,
5025
+ });
5026
+ }
5027
+ let operationPromise;
5028
+ if (insertOption) {
5029
+ // Clear any selected component type for insertion once the component
5030
+ // has been dropped into a placeholder. This ensures that insert mode
5031
+ // can exit cleanly and the editor header updates from "Add Component"
5032
+ // to the actual component name after the operation completes.
5033
+ setSelectedForInsertion("");
5034
+ operationPromise = operations.addComponent(insertOption.typeId, placeholderKey, index, dropTargetDescriptor, { showIndicator: false });
5035
+ }
5036
+ else if (droppedDragObject?.type == "template") {
5037
+ operationPromise = operations.addComponent(droppedDragObject.typeId, placeholderKey, index, dropTargetDescriptor, { showIndicator: false });
5038
+ }
5039
+ else if (droppedDragObject?.type == "component" ||
5040
+ (droppedDragObject?.type == "items" && droppedDragObject.items)) {
5041
+ const parentComponent = parentId && page ? getComponentById(parentId, page) : null;
5042
+ if (droppedDragObject.type == "items" && droppedDragObject.items) {
5043
5043
  op = {
5044
- type: "move-component",
5044
+ type: "link-component",
5045
5045
  mainItem: page.item.descriptor,
5046
5046
  parent: parentId && {
5047
5047
  id: parentId,
5048
5048
  language: page.item.descriptor.language,
5049
5049
  version: page.item.descriptor.version,
5050
+ name: parentComponent?.name,
5050
5051
  },
5051
5052
  placeholderKey,
5052
5053
  placeholderIndex: index,
5053
- componentIds,
5054
5054
  date: new Date().toISOString(),
5055
5055
  id: uuid(),
5056
- title: "Move component",
5056
+ linkedComponentItem: droppedDragObject.items[0],
5057
+ title: "Link component",
5057
5058
  };
5058
5059
  }
5060
+ else {
5061
+ const componentIds = droppedDragObject.components?.map((c) => c.id) ?? [];
5062
+ if (componentIds.length > 0) {
5063
+ op = {
5064
+ type: "move-component",
5065
+ mainItem: page.item.descriptor,
5066
+ parent: parentId && {
5067
+ id: parentId,
5068
+ language: page.item.descriptor.language,
5069
+ version: page.item.descriptor.version,
5070
+ },
5071
+ placeholderKey,
5072
+ placeholderIndex: index,
5073
+ componentIds,
5074
+ date: new Date().toISOString(),
5075
+ id: uuid(),
5076
+ title: "Move component",
5077
+ };
5078
+ }
5079
+ }
5059
5080
  }
5060
- }
5061
- if (op)
5062
- operationPromise = operations.executeEditOperation(op);
5063
- if (!operationPromise) {
5064
- if (insertionSequence !== undefined &&
5065
- insertingSequenceRef.current === insertionSequence) {
5066
- setInserting(undefined);
5067
- }
5068
- return { committed: false };
5069
- }
5070
- let operationFailed = false;
5071
- try {
5072
- // executeOp resolves (instead of throwing) with a failed status when
5073
- // the server rejects the operation — no refresh follows then.
5074
- const result = (await operationPromise);
5075
- operationFailed = result?.executionStatus === "failed";
5076
- }
5077
- catch (err) {
5078
- operationFailed = true;
5079
- console.error("Failed to drop component in placeholder", err);
5080
- }
5081
- finally {
5082
- if (insertionSequence !== undefined &&
5083
- insertingSequenceRef.current === insertionSequence) {
5084
- if (operationFailed) {
5081
+ if (op)
5082
+ operationPromise = operations.executeEditOperation(op);
5083
+ if (!operationPromise) {
5084
+ if (insertionSequence !== undefined &&
5085
+ insertingSequenceRef.current === insertionSequence) {
5085
5086
  setInserting(undefined);
5086
5087
  }
5087
- else {
5088
- // The server accepted the drop; the canvas has not repainted
5089
- // yet. Hold the indicator until the refresh lands so it stops
5090
- // when the component actually appears, not seconds earlier.
5091
- holdInsertingUntilCanvasRefresh(insertionSequence);
5092
- }
5088
+ return { committed: false };
5093
5089
  }
5094
- }
5095
- return { committed: !operationFailed };
5096
- },
5097
- page,
5098
- triggerFieldAction: async (fieldDescriptor, actionButton, parameters, event) => {
5099
- const field = await itemsRepository.getField(fieldDescriptor);
5100
- if (!field)
5101
- return;
5102
- const op = {
5103
- field: fieldDescriptor,
5104
- // actionButton,
5105
- label: actionButton.label,
5106
- state: "running",
5107
- };
5108
- const fieldItem = fieldDescriptor.item;
5109
- if ("clientAction" in actionButton) {
5110
- await actionButton.clientAction({
5111
- field,
5112
- editContext,
5113
- event,
5114
- //dialogContext,
5115
- });
5116
- }
5117
- if (actionButton.action) {
5118
- setActiveFieldActions((prevFieldActions) => {
5119
- const isSameField = (x) => x.field.fieldId == fieldDescriptor.fieldId &&
5120
- x.field.item.id == fieldItem.id &&
5121
- x.field.item.language == fieldItem.language &&
5122
- x.field.item.version == fieldItem.version;
5123
- const existing = prevFieldActions.find(isSameField);
5124
- // Preserve any existing state/message (e.g., an early websocket error)
5125
- // to avoid overwriting it with a stale "running" state
5126
- const mergedOp = existing
5127
- ? {
5128
- ...op,
5129
- state: existing.state,
5130
- message: existing.message,
5131
- label: existing.label ?? op.label,
5132
- }
5133
- : op;
5134
- return [
5135
- ...prevFieldActions.filter((x) => !isSameField(x)),
5136
- mergedOp,
5137
- ];
5138
- });
5090
+ let operationFailed = false;
5139
5091
  try {
5140
- await executeFieldServerAction(fieldDescriptor.item, fieldDescriptor.fieldId, item.descriptor, actionButton.action, editContext.sessionId, selectedRange?.text || "", parameters || {}, (data) => {
5141
- // Update progress message if available
5142
- if (data?.responseText) {
5143
- op.message = data.responseText;
5144
- setActiveFieldActions((prevFieldActions) => [
5145
- ...prevFieldActions,
5146
- ]);
5092
+ // executeOp resolves (instead of throwing) with a failed status when
5093
+ // the server rejects the operation — no refresh follows then.
5094
+ const result = (await operationPromise);
5095
+ operationFailed = result?.executionStatus === "failed";
5096
+ }
5097
+ catch (err) {
5098
+ operationFailed = true;
5099
+ console.error("Failed to drop component in placeholder", err);
5100
+ }
5101
+ finally {
5102
+ if (insertionSequence !== undefined &&
5103
+ insertingSequenceRef.current === insertionSequence) {
5104
+ if (operationFailed) {
5105
+ setInserting(undefined);
5147
5106
  }
5148
- // Handle error state from the server response
5149
- if (data?.state === "Error" || data?.state === "failed") {
5150
- op.state = "error";
5151
- op.message = data.errorMessage || "Field action failed";
5152
- setActiveFieldActions((prevFieldActions) => [
5153
- ...prevFieldActions,
5154
- ]);
5107
+ else {
5108
+ // The server accepted the drop; the canvas has not repainted
5109
+ // yet. Hold the indicator until the refresh lands so it stops
5110
+ // when the component actually appears, not seconds earlier.
5111
+ holdInsertingUntilCanvasRefresh(insertionSequence);
5155
5112
  }
5113
+ }
5114
+ }
5115
+ return { committed: !operationFailed };
5116
+ },
5117
+ page,
5118
+ triggerFieldAction: async (fieldDescriptor, actionButton, parameters, event) => {
5119
+ const field = await itemsRepository.getField(fieldDescriptor);
5120
+ if (!field)
5121
+ return;
5122
+ const op = {
5123
+ field: fieldDescriptor,
5124
+ // actionButton,
5125
+ label: actionButton.label,
5126
+ state: "running",
5127
+ };
5128
+ const fieldItem = fieldDescriptor.item;
5129
+ if ("clientAction" in actionButton) {
5130
+ await actionButton.clientAction({
5131
+ field,
5132
+ editContext,
5133
+ event,
5134
+ //dialogContext,
5156
5135
  });
5157
- itemsRepository.refreshItems([fieldDescriptor.item]);
5158
- // Only mark as success if no websocket error has been reported
5159
- if (op.state === "running") {
5160
- op.state = "success";
5161
- // Auto-dismiss successful actions after a brief delay
5162
- setTimeout(() => {
5163
- setActiveFieldActions((prevFieldActions) => prevFieldActions.filter((x) => !(x.field.fieldId === fieldDescriptor.fieldId &&
5164
- x.field.item.id === fieldDescriptor.item.id &&
5165
- x.field.item.language ===
5166
- fieldDescriptor.item.language &&
5167
- x.field.item.version === fieldDescriptor.item.version &&
5168
- x.state === "success")));
5169
- }, 500);
5136
+ }
5137
+ if (actionButton.action) {
5138
+ setActiveFieldActions((prevFieldActions) => {
5139
+ const isSameField = (x) => x.field.fieldId == fieldDescriptor.fieldId &&
5140
+ x.field.item.id == fieldItem.id &&
5141
+ x.field.item.language == fieldItem.language &&
5142
+ x.field.item.version == fieldItem.version;
5143
+ const existing = prevFieldActions.find(isSameField);
5144
+ // Preserve any existing state/message (e.g., an early websocket error)
5145
+ // to avoid overwriting it with a stale "running" state
5146
+ const mergedOp = existing
5147
+ ? {
5148
+ ...op,
5149
+ state: existing.state,
5150
+ message: existing.message,
5151
+ label: existing.label ?? op.label,
5152
+ }
5153
+ : op;
5154
+ return [
5155
+ ...prevFieldActions.filter((x) => !isSameField(x)),
5156
+ mergedOp,
5157
+ ];
5158
+ });
5159
+ try {
5160
+ await executeFieldServerAction(fieldDescriptor.item, fieldDescriptor.fieldId, item.descriptor, actionButton.action, editContext.sessionId, selectedRange?.text || "", parameters || {}, (data) => {
5161
+ // Update progress message if available
5162
+ if (data?.responseText) {
5163
+ op.message = data.responseText;
5164
+ setActiveFieldActions((prevFieldActions) => [
5165
+ ...prevFieldActions,
5166
+ ]);
5167
+ }
5168
+ // Handle error state from the server response
5169
+ if (data?.state === "Error" || data?.state === "failed") {
5170
+ op.state = "error";
5171
+ op.message = data.errorMessage || "Field action failed";
5172
+ setActiveFieldActions((prevFieldActions) => [
5173
+ ...prevFieldActions,
5174
+ ]);
5175
+ }
5176
+ });
5177
+ itemsRepository.refreshItems([fieldDescriptor.item]);
5178
+ // Only mark as success if no websocket error has been reported
5179
+ if (op.state === "running") {
5180
+ op.state = "success";
5181
+ // Auto-dismiss successful actions after a brief delay
5182
+ setTimeout(() => {
5183
+ setActiveFieldActions((prevFieldActions) => prevFieldActions.filter((x) => !(x.field.fieldId === fieldDescriptor.fieldId &&
5184
+ x.field.item.id === fieldDescriptor.item.id &&
5185
+ x.field.item.language ===
5186
+ fieldDescriptor.item.language &&
5187
+ x.field.item.version === fieldDescriptor.item.version &&
5188
+ x.state === "success")));
5189
+ }, 500);
5190
+ }
5191
+ else if (op.state === "error") {
5192
+ // Auto-dismiss error after a longer delay (matching the catch block)
5193
+ setTimeout(() => {
5194
+ setActiveFieldActions((prevFieldActions) => prevFieldActions.filter((x) => !(x.field.fieldId === fieldDescriptor.fieldId &&
5195
+ x.field.item.id === fieldDescriptor.item.id &&
5196
+ x.field.item.language ===
5197
+ fieldDescriptor.item.language &&
5198
+ x.field.item.version === fieldDescriptor.item.version)));
5199
+ }, 3000);
5200
+ }
5201
+ requestRefresh("immediate");
5170
5202
  }
5171
- else if (op.state === "error") {
5172
- // Auto-dismiss error after a longer delay (matching the catch block)
5203
+ catch (error) {
5204
+ console.error("Field action failed", error);
5205
+ // Mark as failed and show error message
5206
+ op.state = "error";
5207
+ op.message =
5208
+ error instanceof Error ? error.message : "Field action failed";
5209
+ // Update the field action state to show error
5210
+ setActiveFieldActions((prevFieldActions) => {
5211
+ const isSameField = (x) => x.field.fieldId == fieldDescriptor.fieldId &&
5212
+ x.field.item.id == fieldItem.id &&
5213
+ x.field.item.language == fieldItem.language &&
5214
+ x.field.item.version == fieldItem.version;
5215
+ return [...prevFieldActions.filter((x) => !isSameField(x)), op];
5216
+ });
5217
+ // Auto-dismiss error after a longer delay
5173
5218
  setTimeout(() => {
5174
5219
  setActiveFieldActions((prevFieldActions) => prevFieldActions.filter((x) => !(x.field.fieldId === fieldDescriptor.fieldId &&
5175
5220
  x.field.item.id === fieldDescriptor.item.id &&
5176
- x.field.item.language ===
5177
- fieldDescriptor.item.language &&
5221
+ x.field.item.language === fieldDescriptor.item.language &&
5178
5222
  x.field.item.version === fieldDescriptor.item.version)));
5179
5223
  }, 3000);
5180
5224
  }
5181
- requestRefresh("immediate");
5182
5225
  }
5183
- catch (error) {
5184
- console.error("Field action failed", error);
5185
- // Mark as failed and show error message
5186
- op.state = "error";
5187
- op.message =
5188
- error instanceof Error ? error.message : "Field action failed";
5189
- // Update the field action state to show error
5190
- setActiveFieldActions((prevFieldActions) => {
5191
- const isSameField = (x) => x.field.fieldId == fieldDescriptor.fieldId &&
5192
- x.field.item.id == fieldItem.id &&
5193
- x.field.item.language == fieldItem.language &&
5194
- x.field.item.version == fieldItem.version;
5195
- return [...prevFieldActions.filter((x) => !isSameField(x)), op];
5196
- });
5197
- // Auto-dismiss error after a longer delay
5198
- setTimeout(() => {
5199
- setActiveFieldActions((prevFieldActions) => prevFieldActions.filter((x) => !(x.field.fieldId === fieldDescriptor.fieldId &&
5200
- x.field.item.id === fieldDescriptor.item.id &&
5201
- x.field.item.language === fieldDescriptor.item.language &&
5202
- x.field.item.version === fieldDescriptor.item.version)));
5203
- }, 3000);
5226
+ },
5227
+ dismissFieldAction: (fieldDescriptor) => {
5228
+ setActiveFieldActions((prevFieldActions) => prevFieldActions.filter((x) => !(x.field.fieldId === fieldDescriptor.fieldId &&
5229
+ x.field.item.id === fieldDescriptor.item.id &&
5230
+ x.field.item.language === fieldDescriptor.item.language &&
5231
+ x.field.item.version === fieldDescriptor.item.version)));
5232
+ },
5233
+ addFieldAction: (action) => {
5234
+ setActiveFieldActions((prev) => {
5235
+ const isSameField = (x) => x.field.fieldId === action.field.fieldId &&
5236
+ x.field.item.id === action.field.item.id &&
5237
+ x.field.item.language === action.field.item.language &&
5238
+ x.field.item.version === action.field.item.version;
5239
+ return [...prev.filter((x) => !isSameField(x)), action];
5240
+ });
5241
+ },
5242
+ updateFieldAction: (fieldDescriptor, updates) => {
5243
+ setActiveFieldActions((prev) => prev.map((x) => x.field.fieldId === fieldDescriptor.fieldId &&
5244
+ x.field.item.id === fieldDescriptor.item.id &&
5245
+ x.field.item.language === fieldDescriptor.item.language &&
5246
+ x.field.item.version === fieldDescriptor.item.version
5247
+ ? { ...x, ...updates }
5248
+ : x));
5249
+ },
5250
+ activeFieldActions,
5251
+ showContextMenu: (event, items, options) => {
5252
+ contextMenuRef.current?.show(event, items);
5253
+ setContextMenuSource(options?.source);
5254
+ setCurrentOverlay("context-menu");
5255
+ },
5256
+ updateContextMenu: (items) => {
5257
+ contextMenuRef.current?.update(items);
5258
+ },
5259
+ showFieldEditorPopup: (fields, sections, ev) => {
5260
+ setCurrentOverlay("fields");
5261
+ fieldEditorPopupRef.current?.show(fields, sections, ev);
5262
+ },
5263
+ inserting,
5264
+ removingComponentIds,
5265
+ validating,
5266
+ validationResult,
5267
+ loadItem,
5268
+ editHistory: editHistory,
5269
+ historyMode,
5270
+ setHistoryMode,
5271
+ showOnlyMyChanges,
5272
+ setShowOnlyMyChanges,
5273
+ filterByCurrentLanguage,
5274
+ setFilterByCurrentLanguage,
5275
+ historySearchQuery,
5276
+ setHistorySearchQuery,
5277
+ refreshHistory,
5278
+ isRefreshing,
5279
+ isHistoryLoading,
5280
+ canLoadMoreHistory,
5281
+ isHistoryLoadingMore,
5282
+ loadMoreHistory,
5283
+ activeSessions,
5284
+ remoteCarets,
5285
+ unlockField: async () => {
5286
+ setLockedField(undefined); // Clear client-side lock state
5287
+ await releaseFieldLocks(sessionId);
5288
+ },
5289
+ isCommandDisabled: ({ command, data, }) => {
5290
+ const props = {
5291
+ editContext,
5292
+ pathname,
5293
+ router,
5294
+ searchParams,
5295
+ data,
5296
+ openDialog,
5297
+ };
5298
+ return command.disabled(props);
5299
+ },
5300
+ executeCommand,
5301
+ // Workspace state
5302
+ workspace: currentWorkspace,
5303
+ // Filled in below: a workspace's dynamic `visible` callback receives the finished context object.
5304
+ // Evaluating it inside this literal would read `editContext` before its initialization.
5305
+ visibleWorkspaces: [],
5306
+ workspaceId,
5307
+ previousWorkspaceId,
5308
+ switchWorkspace,
5309
+ // Sidebar state
5310
+ availableSidebars: getSidebarsForWorkspace(workspaceId),
5311
+ openSidebars,
5312
+ pinnedSidebars,
5313
+ lockedSidebars,
5314
+ sidebarStacks,
5315
+ toggleSidebar,
5316
+ openSidebar,
5317
+ toggleSidebarPin,
5318
+ toggleSidebarLock,
5319
+ stackSidebar,
5320
+ moveSidebarToColumn,
5321
+ unstackSidebar,
5322
+ reorderSidebarInStack,
5323
+ reorderPinnedSidebars,
5324
+ reorderOpenSidebars,
5325
+ getResolvedSidebar,
5326
+ compareMode,
5327
+ setCompareMode,
5328
+ fullscreen,
5329
+ setFullscreen,
5330
+ componentDesignerComponent,
5331
+ setComponentDesignerComponent,
5332
+ componentDesignerRendering,
5333
+ setComponentDesignerRendering,
5334
+ insertMode,
5335
+ setInsertMode,
5336
+ currentOverlay,
5337
+ contextMenuSource,
5338
+ setCurrentOverlay,
5339
+ lockedField,
5340
+ timings,
5341
+ setTimings,
5342
+ selectedRange,
5343
+ setSelectedRange,
5344
+ replaceSelectedText,
5345
+ insertTextAtCursor,
5346
+ confirm: (props) => {
5347
+ confirmationDialogRef.current?.confirm(props);
5348
+ },
5349
+ showMessageDialog: (props) => {
5350
+ confirmationDialogRef.current?.confirm({
5351
+ header: props.header,
5352
+ message: props.message,
5353
+ accept: () => { },
5354
+ acceptLabel: "Ok",
5355
+ });
5356
+ },
5357
+ showInfoToast,
5358
+ showErrorToast,
5359
+ browseHistory,
5360
+ setCenterPanelView,
5361
+ handleKeyDown,
5362
+ startTour,
5363
+ addSocketMessageListener,
5364
+ sendSocketMessage,
5365
+ socketConnectionVersion,
5366
+ socketDiagnostics,
5367
+ currentItemDescriptor,
5368
+ editorSlots,
5369
+ activeSlotId,
5370
+ slotContexts,
5371
+ registerSlotContext,
5372
+ unregisterSlotContext,
5373
+ getActiveSlotContext,
5374
+ getActiveSlotId,
5375
+ getFieldModificationStoreForSlot,
5376
+ closeEditorSlot,
5377
+ setActiveSlot,
5378
+ revision,
5379
+ fullReloadToken,
5380
+ notifyPageModelReady,
5381
+ pageModelReadyToken,
5382
+ selectedComment,
5383
+ setSelectedComment,
5384
+ selectedSuggestedEdit,
5385
+ setSelectedSuggestedEdit,
5386
+ comments,
5387
+ commentsLoaded,
5388
+ availableCommentTags,
5389
+ loadComments,
5390
+ loadSuggestedEdits,
5391
+ setComments,
5392
+ showComments,
5393
+ setShowComments,
5394
+ showResolvedComments,
5395
+ setShowResolvedComments,
5396
+ addComment: async (options) => {
5397
+ const descriptor = item?.descriptor;
5398
+ if (!descriptor)
5399
+ return;
5400
+ // Only keep a single empty draft comment at a time. Clicking
5401
+ // "add comment" again while an unsaved draft already exists should
5402
+ // focus that draft instead of stacking up more empty inputs.
5403
+ const existingDraft = comments.find((c) => c.isNew);
5404
+ if (existingDraft) {
5405
+ await activateCommentsModeForWriting("comment-add");
5406
+ setSelectedComment(existingDraft);
5407
+ setSelectedSuggestedEdit(undefined);
5408
+ return;
5204
5409
  }
5205
- }
5206
- },
5207
- dismissFieldAction: (fieldDescriptor) => {
5208
- setActiveFieldActions((prevFieldActions) => prevFieldActions.filter((x) => !(x.field.fieldId === fieldDescriptor.fieldId &&
5209
- x.field.item.id === fieldDescriptor.item.id &&
5210
- x.field.item.language === fieldDescriptor.item.language &&
5211
- x.field.item.version === fieldDescriptor.item.version)));
5212
- },
5213
- addFieldAction: (action) => {
5214
- setActiveFieldActions((prev) => {
5215
- const isSameField = (x) => x.field.fieldId === action.field.fieldId &&
5216
- x.field.item.id === action.field.item.id &&
5217
- x.field.item.language === action.field.item.language &&
5218
- x.field.item.version === action.field.item.version;
5219
- return [...prev.filter((x) => !isSameField(x)), action];
5220
- });
5221
- },
5222
- updateFieldAction: (fieldDescriptor, updates) => {
5223
- setActiveFieldActions((prev) => prev.map((x) => x.field.fieldId === fieldDescriptor.fieldId &&
5224
- x.field.item.id === fieldDescriptor.item.id &&
5225
- x.field.item.language === fieldDescriptor.item.language &&
5226
- x.field.item.version === fieldDescriptor.item.version
5227
- ? { ...x, ...updates }
5228
- : x));
5229
- },
5230
- activeFieldActions,
5231
- showContextMenu: (event, items, options) => {
5232
- contextMenuRef.current?.show(event, items);
5233
- setContextMenuSource(options?.source);
5234
- setCurrentOverlay("context-menu");
5235
- },
5236
- updateContextMenu: (items) => {
5237
- contextMenuRef.current?.update(items);
5238
- },
5239
- showFieldEditorPopup: (fields, sections, ev) => {
5240
- setCurrentOverlay("fields");
5241
- fieldEditorPopupRef.current?.show(fields, sections, ev);
5242
- },
5243
- inserting,
5244
- removingComponentIds,
5245
- validating,
5246
- validationResult,
5247
- loadItem,
5248
- editHistory: editHistory,
5249
- historyMode,
5250
- setHistoryMode,
5251
- showOnlyMyChanges,
5252
- setShowOnlyMyChanges,
5253
- filterByCurrentLanguage,
5254
- setFilterByCurrentLanguage,
5255
- historySearchQuery,
5256
- setHistorySearchQuery,
5257
- refreshHistory,
5258
- isRefreshing,
5259
- isHistoryLoading,
5260
- canLoadMoreHistory,
5261
- isHistoryLoadingMore,
5262
- loadMoreHistory,
5263
- activeSessions,
5264
- remoteCarets,
5265
- unlockField: async () => {
5266
- setLockedField(undefined); // Clear client-side lock state
5267
- await releaseFieldLocks(sessionId);
5268
- },
5269
- isCommandDisabled: ({ command, data, }) => {
5270
- const props = {
5271
- editContext,
5272
- pathname,
5273
- router,
5274
- searchParams,
5275
- data,
5276
- openDialog,
5277
- };
5278
- return command.disabled(props);
5279
- },
5280
- executeCommand,
5281
- // Workspace state
5282
- workspace: currentWorkspace,
5283
- visibleWorkspaces: (configuration.editor.workspaces ?? [])
5284
- .filter((w) => {
5285
- if (w.hidden)
5286
- return false;
5287
- if (w.visible && !w.visible(editContext))
5288
- return false;
5289
- // Filter by workspace settings from Sitecore (if provided)
5290
- if (userInfo.workspaces && userInfo.workspaces.length > 0) {
5291
- return userInfo.workspaces.some((ws) => ws.id === w.id);
5292
- }
5293
- return true;
5294
- })
5295
- .sort((a, b) => (a.sortOrder ?? 100) - (b.sortOrder ?? 100)),
5296
- workspaceId,
5297
- previousWorkspaceId,
5298
- switchWorkspace,
5299
- // Sidebar state
5300
- availableSidebars: getSidebarsForWorkspace(workspaceId),
5301
- openSidebars,
5302
- pinnedSidebars,
5303
- lockedSidebars,
5304
- sidebarStacks,
5305
- toggleSidebar,
5306
- openSidebar,
5307
- toggleSidebarPin,
5308
- toggleSidebarLock,
5309
- stackSidebar,
5310
- moveSidebarToColumn,
5311
- unstackSidebar,
5312
- reorderSidebarInStack,
5313
- reorderPinnedSidebars,
5314
- reorderOpenSidebars,
5315
- getResolvedSidebar,
5316
- compareMode,
5317
- setCompareMode,
5318
- fullscreen,
5319
- setFullscreen,
5320
- componentDesignerComponent,
5321
- setComponentDesignerComponent,
5322
- componentDesignerRendering,
5323
- setComponentDesignerRendering,
5324
- insertMode,
5325
- setInsertMode,
5326
- currentOverlay,
5327
- contextMenuSource,
5328
- setCurrentOverlay,
5329
- lockedField,
5330
- timings,
5331
- setTimings,
5332
- selectedRange,
5333
- setSelectedRange,
5334
- replaceSelectedText,
5335
- insertTextAtCursor,
5336
- confirm: (props) => {
5337
- confirmationDialogRef.current?.confirm(props);
5338
- },
5339
- showMessageDialog: (props) => {
5340
- confirmationDialogRef.current?.confirm({
5341
- header: props.header,
5342
- message: props.message,
5343
- accept: () => { },
5344
- acceptLabel: "Ok",
5345
- });
5346
- },
5347
- showInfoToast,
5348
- showErrorToast,
5349
- browseHistory,
5350
- setCenterPanelView,
5351
- handleKeyDown,
5352
- startTour,
5353
- addSocketMessageListener,
5354
- sendSocketMessage,
5355
- socketConnectionVersion,
5356
- socketDiagnostics,
5357
- currentItemDescriptor,
5358
- editorSlots,
5359
- activeSlotId,
5360
- slotContexts,
5361
- registerSlotContext,
5362
- unregisterSlotContext,
5363
- getActiveSlotContext,
5364
- getActiveSlotId,
5365
- getFieldModificationStoreForSlot,
5366
- closeEditorSlot,
5367
- setActiveSlot,
5368
- revision,
5369
- fullReloadToken,
5370
- notifyPageModelReady,
5371
- pageModelReadyToken,
5372
- selectedComment,
5373
- setSelectedComment,
5374
- selectedSuggestedEdit,
5375
- setSelectedSuggestedEdit,
5376
- comments,
5377
- commentsLoaded,
5378
- availableCommentTags,
5379
- loadComments,
5380
- loadSuggestedEdits,
5381
- setComments,
5382
- showComments,
5383
- setShowComments,
5384
- showResolvedComments,
5385
- setShowResolvedComments,
5386
- addComment: async (options) => {
5387
- const descriptor = item?.descriptor;
5388
- if (!descriptor)
5389
- return;
5390
- // Only keep a single empty draft comment at a time. Clicking
5391
- // "add comment" again while an unsaved draft already exists should
5392
- // focus that draft instead of stacking up more empty inputs.
5393
- const existingDraft = comments.find((c) => c.isNew);
5394
- if (existingDraft) {
5410
+ // Snapshot selection context up-front so switching views doesn't change what we comment on.
5411
+ const hintedFieldSnapshot = options?.field;
5412
+ const focusedFieldSnapshot = focusedFieldRef.current;
5413
+ const selectedRangeCandidate = isUsableTextSelectionRange(selectedRange)
5414
+ ? selectedRange
5415
+ : lastTextSelectionRangeRef.current;
5416
+ const selectedRangeSnapshot = !hintedFieldSnapshot ||
5417
+ selectionRangeMatchesField(selectedRangeCandidate, hintedFieldSnapshot)
5418
+ ? selectedRangeCandidate
5419
+ : undefined;
5420
+ const selectedRangeFieldSnapshot = isUsableTextSelectionRange(selectedRangeSnapshot)
5421
+ ? {
5422
+ fieldId: selectedRangeSnapshot.fieldId,
5423
+ item: {
5424
+ id: selectedRangeSnapshot.itemId,
5425
+ language: selectedRangeSnapshot.language || descriptor.language,
5426
+ version: typeof selectedRangeSnapshot.version === "number"
5427
+ ? selectedRangeSnapshot.version
5428
+ : descriptor.version,
5429
+ },
5430
+ }
5431
+ : undefined;
5432
+ const commentFieldSnapshot = selectedRangeFieldSnapshot ??
5433
+ hintedFieldSnapshot ??
5434
+ focusedFieldSnapshot;
5435
+ const itemId = commentFieldSnapshot?.item.id ||
5436
+ (selection.length > 0 ? selection[0] : undefined) ||
5437
+ descriptor.id;
5438
+ if (!itemId)
5439
+ return;
5440
+ // Ensure the promoted comments surface is active when adding a comment.
5395
5441
  await activateCommentsModeForWriting("comment-add");
5396
- setSelectedComment(existingDraft);
5397
- setSelectedSuggestedEdit(undefined);
5398
- return;
5399
- }
5400
- // Snapshot selection context up-front so switching views doesn't change what we comment on.
5401
- const hintedFieldSnapshot = options?.field;
5402
- const focusedFieldSnapshot = focusedFieldRef.current;
5403
- const selectedRangeCandidate = isUsableTextSelectionRange(selectedRange)
5404
- ? selectedRange
5405
- : lastTextSelectionRangeRef.current;
5406
- const selectedRangeSnapshot = !hintedFieldSnapshot ||
5407
- selectionRangeMatchesField(selectedRangeCandidate, hintedFieldSnapshot)
5408
- ? selectedRangeCandidate
5409
- : undefined;
5410
- const selectedRangeFieldSnapshot = isUsableTextSelectionRange(selectedRangeSnapshot)
5411
- ? {
5412
- fieldId: selectedRangeSnapshot.fieldId,
5413
- item: {
5414
- id: selectedRangeSnapshot.itemId,
5415
- language: selectedRangeSnapshot.language || descriptor.language,
5416
- version: typeof selectedRangeSnapshot.version === "number"
5417
- ? selectedRangeSnapshot.version
5418
- : descriptor.version,
5419
- },
5420
- }
5421
- : undefined;
5422
- const commentFieldSnapshot = selectedRangeFieldSnapshot ??
5423
- hintedFieldSnapshot ??
5424
- focusedFieldSnapshot;
5425
- const itemId = commentFieldSnapshot?.item.id ||
5426
- (selection.length > 0 ? selection[0] : undefined) ||
5427
- descriptor.id;
5428
- if (!itemId)
5429
- return;
5430
- // Ensure the promoted comments surface is active when adding a comment.
5431
- await activateCommentsModeForWriting("comment-add");
5432
- const language = commentFieldSnapshot?.item.language || descriptor.language;
5433
- const version = typeof commentFieldSnapshot?.item.version === "number"
5434
- ? commentFieldSnapshot.item.version
5435
- : descriptor.version;
5436
- const getFieldName = async () => {
5437
- if (!commentFieldSnapshot)
5438
- return "";
5439
- const field = await itemsRepository.getField(commentFieldSnapshot);
5440
- return field?.name;
5441
- };
5442
- const getItemName = async () => {
5443
- const item = await itemsRepository.getItem({
5444
- id: itemId,
5442
+ const language = commentFieldSnapshot?.item.language || descriptor.language;
5443
+ const version = typeof commentFieldSnapshot?.item.version === "number"
5444
+ ? commentFieldSnapshot.item.version
5445
+ : descriptor.version;
5446
+ const getFieldName = async () => {
5447
+ if (!commentFieldSnapshot)
5448
+ return "";
5449
+ const field = await itemsRepository.getField(commentFieldSnapshot);
5450
+ return field?.name;
5451
+ };
5452
+ const getItemName = async () => {
5453
+ const item = await itemsRepository.getItem({
5454
+ id: itemId,
5455
+ language,
5456
+ version,
5457
+ });
5458
+ return item?.name;
5459
+ };
5460
+ const newComment = {
5461
+ id: uuid(),
5462
+ isNew: true,
5463
+ itemId,
5464
+ itemName: await getItemName(),
5465
+ fieldId: commentFieldSnapshot?.fieldId,
5466
+ fieldName: await getFieldName(),
5467
+ mainItemId: descriptor.id,
5445
5468
  language,
5446
5469
  version,
5447
- });
5448
- return item?.name;
5449
- };
5450
- const newComment = {
5451
- id: uuid(),
5452
- isNew: true,
5453
- itemId,
5454
- itemName: await getItemName(),
5455
- fieldId: commentFieldSnapshot?.fieldId,
5456
- fieldName: await getFieldName(),
5457
- mainItemId: descriptor.id,
5458
- language,
5459
- version,
5460
- position: 0,
5461
- rangeStart: isUsableTextSelectionRange(selectedRangeSnapshot)
5462
- ? selectedRangeSnapshot.startOffset
5463
- : 0,
5464
- rangeEnd: isUsableTextSelectionRange(selectedRangeSnapshot)
5465
- ? selectedRangeSnapshot.endOffset
5466
- : 0,
5467
- author: user?.name,
5468
- authorDisplayName: user?.fullName,
5469
- created: new Date().toISOString(),
5470
- };
5471
- if (selectedRangeSnapshot?.clientRect) {
5472
- newComment[COMMENT_TRANSIENT_SELECTION_RECT] =
5473
- selectedRangeSnapshot.clientRect;
5474
- }
5475
- if (selectedRangeSnapshot?.elementKey) {
5476
- newComment[COMMENT_TRANSIENT_ELEMENT_KEY] =
5477
- selectedRangeSnapshot.elementKey;
5478
- }
5479
- setComments([newComment, ...comments]);
5480
- setSelectedComment(newComment);
5481
- setSelectedSuggestedEdit(undefined);
5482
- lastTextSelectionRangeRef.current = undefined;
5483
- },
5484
- mode,
5485
- changeMode,
5486
- setMode,
5487
- registerModeChangeParticipant,
5488
- modeTransitionRevision,
5489
- previewDate,
5490
- setPreviewDate,
5491
- layoutMode,
5492
- setLayoutMode,
5493
- user,
5494
- reviews,
5495
- statusMessage,
5496
- setStatusMessage,
5497
- suggestedEdits,
5498
- setSuggestedEdits,
5499
- getCurrentSuggestedEdits,
5500
- suggestionVariantSelection,
5501
- setSuggestionVariantSelection,
5502
- showSuggestedEdits,
5503
- setShowSuggestedEdits,
5504
- showSuggestedEditsDiff,
5505
- setShowSuggestedEditsDiff,
5506
- showVersionDiffHighlighting,
5507
- setShowVersionDiffHighlighting,
5508
- enableCompletions,
5509
- setEnableCompletions,
5510
- showComponentNavigator,
5511
- setShowComponentNavigator: handleSetShowComponentNavigator,
5512
- isComponentNavigatorOpenForSlot,
5513
- setComponentNavigatorOpenForSlot,
5514
- showAgentsPanel,
5515
- setShowAgentsPanel: handleSetShowAgentsPanel,
5516
- waitForSurfaceSettled: settleRegistry.waitForSurfaceSettled,
5517
- notifySurfaceSettled: settleRegistry.notifySettled,
5518
- isEditorFormHiddenForSlot,
5519
- setEditorFormHiddenForSlot,
5520
- editorFormHintSeen,
5521
- markEditorFormHintSeen,
5522
- editorFormHintVisible,
5523
- showEditorFormHint,
5524
- dismissEditorFormHint,
5525
- editorFormToggleButtonRef,
5526
- showMinimap: effectiveShowMinimap,
5527
- setShowMinimap: handleSetShowMinimap,
5528
- showHelpTerminal,
5529
- setShowHelpTerminal: handleSetShowHelpTerminal,
5530
- toggleHelpTerminal,
5531
- helpTerminalInitialPrompt,
5532
- helpTerminalProfileName,
5533
- helpTerminalActiveTab,
5534
- setHelpTerminalActiveTab,
5535
- selectedHelpSectionId,
5536
- setSelectedHelpSectionId,
5537
- showAgentsWorkspaceEditor,
5538
- setShowAgentsWorkspaceEditor: handleSetShowAgentsWorkspaceEditor,
5539
- selectedAgentsWorkspaceAgentId,
5540
- setSelectedAgentsWorkspaceAgentId,
5541
- activeEditorTab,
5542
- setActiveEditorTab,
5543
- showLayoutComponents,
5544
- setShowLayoutComponents: handleSetShowLayoutComponents,
5545
- quotaInfo,
5546
- isQuotaExceeded: isQuotaExceeded(),
5547
- getQuotaWarningMessage,
5548
- isMobile,
5549
- mobileEditorPanelOpen,
5550
- setMobileEditorPanelOpen: handleSetMobileEditorPanelOpen,
5551
- openDialog,
5552
- webSocketMessages,
5553
- clearWebSocketMessages: () => setWebSocketMessages([]),
5554
- userInfo: userInfo,
5555
- parheliaSettings: effectiveParheliaSettings,
5556
- setUserPreferences,
5557
- currentWizardId,
5558
- setCurrentWizardId,
5559
- favorites,
5560
- loadFavorites,
5561
- refreshFavorites,
5562
- isQuickSwitcherVisible: quickSwitcherVisible,
5563
- // Context factory registry methods
5564
- registerContextFactory: (name, factory) => {
5565
- try {
5566
- factoriesRef.map.set(name, factory);
5567
- }
5568
- catch { }
5569
- },
5570
- unregisterContextFactory: (name) => {
5571
- try {
5572
- factoriesRef.map.delete(name);
5573
- }
5574
- catch { }
5575
- },
5576
- getContextFactory: (name) => {
5577
- try {
5578
- return factoriesRef.map.get(name);
5579
- }
5580
- catch {
5581
- return undefined;
5582
- }
5583
- },
5584
- }), [
5470
+ position: 0,
5471
+ rangeStart: isUsableTextSelectionRange(selectedRangeSnapshot)
5472
+ ? selectedRangeSnapshot.startOffset
5473
+ : 0,
5474
+ rangeEnd: isUsableTextSelectionRange(selectedRangeSnapshot)
5475
+ ? selectedRangeSnapshot.endOffset
5476
+ : 0,
5477
+ author: user?.name,
5478
+ authorDisplayName: user?.fullName,
5479
+ created: new Date().toISOString(),
5480
+ };
5481
+ if (selectedRangeSnapshot?.clientRect) {
5482
+ newComment[COMMENT_TRANSIENT_SELECTION_RECT] =
5483
+ selectedRangeSnapshot.clientRect;
5484
+ }
5485
+ if (selectedRangeSnapshot?.elementKey) {
5486
+ newComment[COMMENT_TRANSIENT_ELEMENT_KEY] =
5487
+ selectedRangeSnapshot.elementKey;
5488
+ }
5489
+ setComments([newComment, ...comments]);
5490
+ setSelectedComment(newComment);
5491
+ setSelectedSuggestedEdit(undefined);
5492
+ lastTextSelectionRangeRef.current = undefined;
5493
+ },
5494
+ mode,
5495
+ changeMode,
5496
+ setMode,
5497
+ registerModeChangeParticipant,
5498
+ modeTransitionRevision,
5499
+ previewDate,
5500
+ setPreviewDate,
5501
+ layoutMode,
5502
+ setLayoutMode,
5503
+ user,
5504
+ reviews,
5505
+ statusMessage,
5506
+ setStatusMessage,
5507
+ suggestedEdits,
5508
+ setSuggestedEdits,
5509
+ getCurrentSuggestedEdits,
5510
+ suggestionVariantSelection,
5511
+ setSuggestionVariantSelection,
5512
+ showSuggestedEdits,
5513
+ setShowSuggestedEdits,
5514
+ showSuggestedEditsDiff,
5515
+ setShowSuggestedEditsDiff,
5516
+ showVersionDiffHighlighting,
5517
+ setShowVersionDiffHighlighting,
5518
+ enableCompletions,
5519
+ setEnableCompletions,
5520
+ showComponentNavigator,
5521
+ setShowComponentNavigator: handleSetShowComponentNavigator,
5522
+ isComponentNavigatorOpenForSlot,
5523
+ setComponentNavigatorOpenForSlot,
5524
+ showAgentsPanel,
5525
+ setShowAgentsPanel: handleSetShowAgentsPanel,
5526
+ waitForSurfaceSettled: settleRegistry.waitForSurfaceSettled,
5527
+ notifySurfaceSettled: settleRegistry.notifySettled,
5528
+ isEditorFormHiddenForSlot,
5529
+ setEditorFormHiddenForSlot,
5530
+ editorFormHintSeen,
5531
+ markEditorFormHintSeen,
5532
+ editorFormHintVisible,
5533
+ showEditorFormHint,
5534
+ dismissEditorFormHint,
5535
+ editorFormToggleButtonRef,
5536
+ showMinimap: effectiveShowMinimap,
5537
+ setShowMinimap: handleSetShowMinimap,
5538
+ showHelpTerminal,
5539
+ setShowHelpTerminal: handleSetShowHelpTerminal,
5540
+ toggleHelpTerminal,
5541
+ helpTerminalInitialPrompt,
5542
+ helpTerminalProfileName,
5543
+ helpTerminalActiveTab,
5544
+ setHelpTerminalActiveTab,
5545
+ selectedHelpSectionId,
5546
+ setSelectedHelpSectionId,
5547
+ showAgentsWorkspaceEditor,
5548
+ setShowAgentsWorkspaceEditor: handleSetShowAgentsWorkspaceEditor,
5549
+ selectedAgentsWorkspaceAgentId,
5550
+ setSelectedAgentsWorkspaceAgentId,
5551
+ activeEditorTab,
5552
+ setActiveEditorTab,
5553
+ showLayoutComponents,
5554
+ setShowLayoutComponents: handleSetShowLayoutComponents,
5555
+ quotaInfo,
5556
+ isQuotaExceeded: isQuotaExceeded(),
5557
+ getQuotaWarningMessage,
5558
+ isMobile,
5559
+ mobileEditorPanelOpen,
5560
+ setMobileEditorPanelOpen: handleSetMobileEditorPanelOpen,
5561
+ openDialog,
5562
+ webSocketMessages,
5563
+ clearWebSocketMessages: () => setWebSocketMessages([]),
5564
+ userInfo: userInfo,
5565
+ parheliaSettings: effectiveParheliaSettings,
5566
+ setUserPreferences,
5567
+ currentWizardId,
5568
+ setCurrentWizardId,
5569
+ favorites,
5570
+ loadFavorites,
5571
+ refreshFavorites,
5572
+ isQuickSwitcherVisible: quickSwitcherVisible,
5573
+ // Context factory registry methods
5574
+ registerContextFactory: (name, factory) => {
5575
+ try {
5576
+ factoriesRef.map.set(name, factory);
5577
+ }
5578
+ catch { }
5579
+ },
5580
+ unregisterContextFactory: (name) => {
5581
+ try {
5582
+ factoriesRef.map.delete(name);
5583
+ }
5584
+ catch { }
5585
+ },
5586
+ getContextFactory: (name) => {
5587
+ try {
5588
+ return factoriesRef.map.get(name);
5589
+ }
5590
+ catch {
5591
+ return undefined;
5592
+ }
5593
+ },
5594
+ };
5595
+ editContextValue.visibleWorkspaces =
5596
+ filterVisibleWorkspaces(editContextValue);
5597
+ return editContextValue;
5598
+ }, [
5585
5599
  operationsContext.ops,
5586
5600
  itemsRepository,
5587
5601
  configuration,