@alpaca-editor/core 1.0.3941 → 1.0.3943

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (308) hide show
  1. package/.prettierrc +3 -0
  2. package/build.css +3 -0
  3. package/components.json +21 -0
  4. package/dist/editor/ContentTree.d.ts +2 -1
  5. package/dist/editor/ContentTree.js +23 -21
  6. package/dist/editor/ContentTree.js.map +1 -1
  7. package/dist/editor/FieldActionsOverlay.js +0 -2
  8. package/dist/editor/FieldActionsOverlay.js.map +1 -1
  9. package/dist/editor/ScrollingContentTree.js +1 -1
  10. package/dist/editor/ScrollingContentTree.js.map +1 -1
  11. package/dist/editor/Titlebar.js +1 -1
  12. package/dist/editor/Titlebar.js.map +1 -1
  13. package/dist/editor/ai/GhostWriter.js +24 -3
  14. package/dist/editor/ai/GhostWriter.js.map +1 -1
  15. package/dist/editor/client/EditorClient.js +7 -7
  16. package/dist/editor/client/EditorClient.js.map +1 -1
  17. package/dist/editor/field-types/InternalLinkFieldEditor.js +60 -10
  18. package/dist/editor/field-types/InternalLinkFieldEditor.js.map +1 -1
  19. package/dist/editor/media-selector/MediaFolderBrowser.js +48 -1
  20. package/dist/editor/media-selector/MediaFolderBrowser.js.map +1 -1
  21. package/dist/editor/menubar/PageSelector.js +116 -65
  22. package/dist/editor/menubar/PageSelector.js.map +1 -1
  23. package/dist/editor/page-viewer/EditorForm.js +5 -2
  24. package/dist/editor/page-viewer/EditorForm.js.map +1 -1
  25. package/dist/editor/ui/ItemSearch.js +14 -8
  26. package/dist/editor/ui/ItemSearch.js.map +1 -1
  27. package/dist/editor/ui/PerfectTree.d.ts +4 -2
  28. package/dist/editor/ui/PerfectTree.js +78 -4
  29. package/dist/editor/ui/PerfectTree.js.map +1 -1
  30. package/dist/editor/ui/Splitter.js +1 -1
  31. package/dist/revision.d.ts +2 -2
  32. package/dist/revision.js +2 -2
  33. package/dist/styles.css +8 -2
  34. package/eslint.config.mjs +4 -0
  35. package/images/bg-shape-black.webp +0 -0
  36. package/images/wizard-bg.png +0 -0
  37. package/images/wizard-tour.png +0 -0
  38. package/images/wizard.png +0 -0
  39. package/package.json +2 -8
  40. package/src/client-components/api.ts +6 -0
  41. package/src/client-components/index.ts +19 -0
  42. package/src/components/ActionButton.tsx +50 -0
  43. package/src/components/Error.tsx +57 -0
  44. package/src/components/ui/CardConnector.tsx +56 -0
  45. package/src/components/ui/button.tsx +62 -0
  46. package/src/components/ui/card.tsx +372 -0
  47. package/src/components/ui/context-menu.tsx +250 -0
  48. package/src/config/config.tsx +917 -0
  49. package/src/config/types.ts +286 -0
  50. package/src/editor/ComponentInfo.tsx +90 -0
  51. package/src/editor/ConfirmationDialog.tsx +103 -0
  52. package/src/editor/ContentTree.tsx +733 -0
  53. package/src/editor/ContextMenu.tsx +230 -0
  54. package/src/editor/Editor.tsx +90 -0
  55. package/src/editor/EditorWarning.tsx +34 -0
  56. package/src/editor/EditorWarnings.tsx +33 -0
  57. package/src/editor/FieldActionsOverlay.tsx +296 -0
  58. package/src/editor/FieldEditorPopup.tsx +65 -0
  59. package/src/editor/FieldHistory.tsx +75 -0
  60. package/src/editor/FieldList.tsx +190 -0
  61. package/src/editor/FieldListField.tsx +391 -0
  62. package/src/editor/FieldListFieldWithFallbacks.tsx +217 -0
  63. package/src/editor/FloatingToolbar.tsx +163 -0
  64. package/src/editor/ImageEditor.tsx +128 -0
  65. package/src/editor/ItemInfo.tsx +90 -0
  66. package/src/editor/LinkEditorDialog.tsx +196 -0
  67. package/src/editor/MainLayout.tsx +95 -0
  68. package/src/editor/MobileLayout.tsx +68 -0
  69. package/src/editor/NewEditorClient.tsx +11 -0
  70. package/src/editor/PictureCropper.tsx +568 -0
  71. package/src/editor/PictureEditor.tsx +301 -0
  72. package/src/editor/PictureEditorDialog.tsx +381 -0
  73. package/src/editor/PublishDialog.ignore +74 -0
  74. package/src/editor/ScrollingContentTree.tsx +68 -0
  75. package/src/editor/Terminal.tsx +227 -0
  76. package/src/editor/Titlebar.tsx +104 -0
  77. package/src/editor/ai/AiPopup.tsx +59 -0
  78. package/src/editor/ai/AiResponseMessage.tsx +106 -0
  79. package/src/editor/ai/AiTerminal.tsx +503 -0
  80. package/src/editor/ai/AiToolCall.tsx +61 -0
  81. package/src/editor/ai/EditorAiTerminal.tsx +20 -0
  82. package/src/editor/ai/GhostWriter.tsx +480 -0
  83. package/src/editor/ai/aiPageModel.ts +108 -0
  84. package/src/editor/ai/editorAiContext.ts +18 -0
  85. package/src/editor/client/AboutDialog.tsx +44 -0
  86. package/src/editor/client/EditorClient.tsx +2241 -0
  87. package/src/editor/client/GenericDialog.tsx +50 -0
  88. package/src/editor/client/editContext.ts +416 -0
  89. package/src/editor/client/helpers.ts +44 -0
  90. package/src/editor/client/itemsRepository.ts +574 -0
  91. package/src/editor/client/operations.ts +768 -0
  92. package/src/editor/client/pageModelBuilder.ts +219 -0
  93. package/src/editor/commands/commands.ts +22 -0
  94. package/src/editor/commands/componentCommands.tsx +431 -0
  95. package/src/editor/commands/createVersionCommand.ts +33 -0
  96. package/src/editor/commands/deleteVersionCommand.ts +71 -0
  97. package/src/editor/commands/itemCommands.tsx +351 -0
  98. package/src/editor/commands/localizeItem/LocalizeItemDialog.tsx +201 -0
  99. package/src/editor/commands/localizeItem/LocalizeItemUtils.ts +27 -0
  100. package/src/editor/commands/undo.ts +39 -0
  101. package/src/editor/component-designer/ComponentDesigner.tsx +70 -0
  102. package/src/editor/component-designer/ComponentDesignerAiTerminal.tsx +11 -0
  103. package/src/editor/component-designer/ComponentDesignerMenu.tsx +91 -0
  104. package/src/editor/component-designer/ComponentEditor.tsx +97 -0
  105. package/src/editor/component-designer/ComponentRenderingCodeEditor.tsx +31 -0
  106. package/src/editor/component-designer/ComponentRenderingEditor.tsx +104 -0
  107. package/src/editor/component-designer/ComponentsDropdown.tsx +39 -0
  108. package/src/editor/component-designer/PlaceholdersEditor.tsx +179 -0
  109. package/src/editor/component-designer/RenderingsDropdown.tsx +36 -0
  110. package/src/editor/component-designer/TemplateEditor.tsx +236 -0
  111. package/src/editor/component-designer/aiContext.ts +23 -0
  112. package/src/editor/componentTreeHelper.tsx +116 -0
  113. package/src/editor/context-menu/CopyMoveMenu.tsx +103 -0
  114. package/src/editor/context-menu/InsertMenu.tsx +347 -0
  115. package/src/editor/control-center/About.tsx +342 -0
  116. package/src/editor/control-center/ControlCenterMenu.tsx +76 -0
  117. package/src/editor/control-center/IndexOverview.tsx +50 -0
  118. package/src/editor/control-center/IndexSettings.tsx +266 -0
  119. package/src/editor/control-center/Info.tsx +104 -0
  120. package/src/editor/control-center/QuotaInfo.tsx +301 -0
  121. package/src/editor/control-center/Status.tsx +113 -0
  122. package/src/editor/control-center/WebSocketMessages.tsx +155 -0
  123. package/src/editor/editor-warnings/ItemLocked.tsx +63 -0
  124. package/src/editor/editor-warnings/NoLanguageWriteAccess.tsx +22 -0
  125. package/src/editor/editor-warnings/NoWorkflowWriteAccess.tsx +23 -0
  126. package/src/editor/editor-warnings/NoWriteAccess.tsx +16 -0
  127. package/src/editor/editor-warnings/ValidationErrors.tsx +54 -0
  128. package/src/editor/field-types/AttachmentEditor.tsx +9 -0
  129. package/src/editor/field-types/CheckboxEditor.tsx +47 -0
  130. package/src/editor/field-types/DropLinkEditor.tsx +80 -0
  131. package/src/editor/field-types/DropListEditor.tsx +84 -0
  132. package/src/editor/field-types/ImageFieldEditor.tsx +65 -0
  133. package/src/editor/field-types/InternalLinkFieldEditor.tsx +188 -0
  134. package/src/editor/field-types/LinkFieldEditor.tsx +85 -0
  135. package/src/editor/field-types/MultiLineText.tsx +82 -0
  136. package/src/editor/field-types/PictureFieldEditor.tsx +121 -0
  137. package/src/editor/field-types/RawEditor.tsx +53 -0
  138. package/src/editor/field-types/ReactQuill.tsx +580 -0
  139. package/src/editor/field-types/RichTextEditor.tsx +22 -0
  140. package/src/editor/field-types/RichTextEditorComponent.tsx +127 -0
  141. package/src/editor/field-types/SingleLineText.tsx +174 -0
  142. package/src/editor/field-types/TreeListEditor.tsx +261 -0
  143. package/src/editor/fieldTypes.ts +140 -0
  144. package/src/editor/media-selector/AiImageSearch.tsx +185 -0
  145. package/src/editor/media-selector/AiImageSearchPrompt.tsx +94 -0
  146. package/src/editor/media-selector/MediaFolderBrowser.tsx +321 -0
  147. package/src/editor/media-selector/MediaSelector.tsx +42 -0
  148. package/src/editor/media-selector/Preview.tsx +14 -0
  149. package/src/editor/media-selector/Thumbnails.tsx +48 -0
  150. package/src/editor/media-selector/TreeSelector.tsx +292 -0
  151. package/src/editor/media-selector/UploadZone.tsx +137 -0
  152. package/src/editor/media-selector/index.ts +8 -0
  153. package/src/editor/menubar/ActionsMenu.tsx +94 -0
  154. package/src/editor/menubar/ActiveUsers.tsx +17 -0
  155. package/src/editor/menubar/ApproveAndPublish.tsx +18 -0
  156. package/src/editor/menubar/BrowseHistory.tsx +28 -0
  157. package/src/editor/menubar/ItemLanguageVersion.tsx +76 -0
  158. package/src/editor/menubar/LanguageSelector.tsx +226 -0
  159. package/src/editor/menubar/Menu.tsx +83 -0
  160. package/src/editor/menubar/NavButtons.tsx +74 -0
  161. package/src/editor/menubar/PageSelector.tsx +278 -0
  162. package/src/editor/menubar/PageViewerControls.tsx +120 -0
  163. package/src/editor/menubar/PreviewSecondaryControls.tsx +18 -0
  164. package/src/editor/menubar/SecondaryControls.tsx +45 -0
  165. package/src/editor/menubar/Separator.tsx +12 -0
  166. package/src/editor/menubar/SiteInfo.tsx +53 -0
  167. package/src/editor/menubar/User.tsx +27 -0
  168. package/src/editor/menubar/VersionSelector.tsx +142 -0
  169. package/src/editor/page-editor-chrome/CommentHighlighting.tsx +307 -0
  170. package/src/editor/page-editor-chrome/CommentHighlightings.tsx +35 -0
  171. package/src/editor/page-editor-chrome/FieldActionIndicator.tsx +59 -0
  172. package/src/editor/page-editor-chrome/FieldActionIndicators.tsx +23 -0
  173. package/src/editor/page-editor-chrome/FieldEditedIndicator.tsx +64 -0
  174. package/src/editor/page-editor-chrome/FieldEditedIndicators.tsx +35 -0
  175. package/src/editor/page-editor-chrome/FrameMenu.tsx +338 -0
  176. package/src/editor/page-editor-chrome/FrameMenus.tsx +48 -0
  177. package/src/editor/page-editor-chrome/InlineEditor.tsx +765 -0
  178. package/src/editor/page-editor-chrome/LockedFieldIndicator.tsx +61 -0
  179. package/src/editor/page-editor-chrome/NoLayout.tsx +36 -0
  180. package/src/editor/page-editor-chrome/PageEditorChrome.tsx +122 -0
  181. package/src/editor/page-editor-chrome/PictureEditorOverlay.tsx +161 -0
  182. package/src/editor/page-editor-chrome/PlaceholderDropZone.tsx +169 -0
  183. package/src/editor/page-editor-chrome/PlaceholderDropZones.tsx +315 -0
  184. package/src/editor/page-editor-chrome/SuggestionHighlighting.tsx +300 -0
  185. package/src/editor/page-editor-chrome/SuggestionHighlightings.tsx +40 -0
  186. package/src/editor/page-editor-chrome/useInlineAICompletion.tsx +828 -0
  187. package/src/editor/page-viewer/DeviceToolbar.tsx +70 -0
  188. package/src/editor/page-viewer/EditorForm.tsx +262 -0
  189. package/src/editor/page-viewer/MiniMap.tsx +362 -0
  190. package/src/editor/page-viewer/PageViewer.tsx +169 -0
  191. package/src/editor/page-viewer/PageViewerFrame.tsx +1022 -0
  192. package/src/editor/page-viewer/pageModelSkeletonBuilder.ts +412 -0
  193. package/src/editor/page-viewer/pageViewContext.ts +186 -0
  194. package/src/editor/pageModel.ts +220 -0
  195. package/src/editor/picture-shared.tsx +53 -0
  196. package/src/editor/reviews/Comment.tsx +308 -0
  197. package/src/editor/reviews/Comments.tsx +125 -0
  198. package/src/editor/reviews/DiffView.tsx +109 -0
  199. package/src/editor/reviews/PreviewInfo.tsx +35 -0
  200. package/src/editor/reviews/Reviews.tsx +280 -0
  201. package/src/editor/reviews/SuggestedEdit.tsx +316 -0
  202. package/src/editor/reviews/reviewCommands.tsx +47 -0
  203. package/src/editor/reviews/useReviews.tsx +70 -0
  204. package/src/editor/services/aiService.ts +173 -0
  205. package/src/editor/services/componentDesignerService.ts +151 -0
  206. package/src/editor/services/contentService.ts +180 -0
  207. package/src/editor/services/editService.ts +488 -0
  208. package/src/editor/services/indexService.ts +24 -0
  209. package/src/editor/services/reviewsService.ts +53 -0
  210. package/src/editor/services/serviceHelper.ts +95 -0
  211. package/src/editor/services/suggestedEditsService.ts +39 -0
  212. package/src/editor/services/systemService.ts +5 -0
  213. package/src/editor/services/translationService.ts +21 -0
  214. package/src/editor/services-server/api.ts +150 -0
  215. package/src/editor/services-server/graphQL.ts +106 -0
  216. package/src/editor/sidebar/ComponentPalette.tsx +161 -0
  217. package/src/editor/sidebar/ComponentTree.tsx +549 -0
  218. package/src/editor/sidebar/Debug.tsx +111 -0
  219. package/src/editor/sidebar/DictionaryEditor.tsx +261 -0
  220. package/src/editor/sidebar/EditHistory.tsx +134 -0
  221. package/src/editor/sidebar/GraphQL.tsx +164 -0
  222. package/src/editor/sidebar/Insert.tsx +35 -0
  223. package/src/editor/sidebar/MainContentTree.tsx +102 -0
  224. package/src/editor/sidebar/Performance.tsx +53 -0
  225. package/src/editor/sidebar/Sessions.tsx +35 -0
  226. package/src/editor/sidebar/Sidebar.tsx +20 -0
  227. package/src/editor/sidebar/SidebarView.tsx +152 -0
  228. package/src/editor/sidebar/Translations.tsx +295 -0
  229. package/src/editor/sidebar/Validation.tsx +102 -0
  230. package/src/editor/sidebar/ViewSelector.tsx +60 -0
  231. package/src/editor/sidebar/Workbox.tsx +209 -0
  232. package/src/editor/ui/CenteredMessage.tsx +7 -0
  233. package/src/editor/ui/CopyMoveTargetSelectorDialog.tsx +81 -0
  234. package/src/editor/ui/CopyToClipboardButton.tsx +24 -0
  235. package/src/editor/ui/DialogButtons.tsx +11 -0
  236. package/src/editor/ui/Icons.tsx +709 -0
  237. package/src/editor/ui/ItemList.tsx +76 -0
  238. package/src/editor/ui/ItemNameDialogNew.tsx +118 -0
  239. package/src/editor/ui/ItemSearch.tsx +159 -0
  240. package/src/editor/ui/PerfectTree.tsx +676 -0
  241. package/src/editor/ui/Section.tsx +35 -0
  242. package/src/editor/ui/SimpleIconButton.tsx +54 -0
  243. package/src/editor/ui/SimpleMenu.tsx +40 -0
  244. package/src/editor/ui/SimpleTable.tsx +60 -0
  245. package/src/editor/ui/SimpleTabs.tsx +60 -0
  246. package/src/editor/ui/SimpleToolbar.tsx +7 -0
  247. package/src/editor/ui/Spinner.tsx +9 -0
  248. package/src/editor/ui/Splitter.tsx +420 -0
  249. package/src/editor/ui/StackedPanels.tsx +134 -0
  250. package/src/editor/ui/Toolbar.tsx +7 -0
  251. package/src/editor/utils/id-helper.ts +3 -0
  252. package/src/editor/utils/insertOptions.ts +69 -0
  253. package/src/editor/utils/itemutils.ts +29 -0
  254. package/src/editor/utils/useMemoDebug.ts +28 -0
  255. package/src/editor/utils.ts +486 -0
  256. package/src/editor/views/CompareView.tsx +245 -0
  257. package/src/editor/views/EditView.tsx +27 -0
  258. package/src/editor/views/ItemEditor.tsx +58 -0
  259. package/src/editor/views/MediaFolderEditView.tsx +66 -0
  260. package/src/editor/views/SingleEditView.tsx +57 -0
  261. package/src/fonts/Geist-Black.woff2 +0 -0
  262. package/src/fonts/Geist-Bold.woff2 +0 -0
  263. package/src/fonts/Geist-ExtraBold.woff2 +0 -0
  264. package/src/fonts/Geist-ExtraLight.woff2 +0 -0
  265. package/src/fonts/Geist-Light.woff2 +0 -0
  266. package/src/fonts/Geist-Medium.woff2 +0 -0
  267. package/src/fonts/Geist-Regular.woff2 +0 -0
  268. package/src/fonts/Geist-SemiBold.woff2 +0 -0
  269. package/src/fonts/Geist-Thin.woff2 +0 -0
  270. package/src/fonts/Geist[wght].woff2 +0 -0
  271. package/src/fonts/index.ts +10 -0
  272. package/src/index.ts +23 -0
  273. package/src/lib/safelist.tsx +16 -0
  274. package/src/lib/utils.ts +6 -0
  275. package/src/page-wizard/PageWizard.tsx +139 -0
  276. package/src/page-wizard/WizardBox.tsx +4 -0
  277. package/src/page-wizard/WizardBoxConnector.tsx +56 -0
  278. package/src/page-wizard/WizardSteps.tsx +458 -0
  279. package/src/page-wizard/service.ts +35 -0
  280. package/src/page-wizard/startPageWizardCommand.ts +26 -0
  281. package/src/page-wizard/steps/BuildPageStep.tsx +259 -0
  282. package/src/page-wizard/steps/CollectStep.tsx +296 -0
  283. package/src/page-wizard/steps/ComponentTypesSelector.tsx +454 -0
  284. package/src/page-wizard/steps/Components.tsx +193 -0
  285. package/src/page-wizard/steps/ContentStep.tsx +890 -0
  286. package/src/page-wizard/steps/EditButton.tsx +34 -0
  287. package/src/page-wizard/steps/FieldEditor.tsx +102 -0
  288. package/src/page-wizard/steps/Generate.tsx +60 -0
  289. package/src/page-wizard/steps/ImagesStep.tsx +382 -0
  290. package/src/page-wizard/steps/LayoutStep.tsx +227 -0
  291. package/src/page-wizard/steps/MetaDataStep.tsx +173 -0
  292. package/src/page-wizard/steps/SelectStep.tsx +281 -0
  293. package/src/page-wizard/steps/schema.ts +180 -0
  294. package/src/page-wizard/steps/usePageCreator.ts +325 -0
  295. package/src/page-wizard/usePageWizard.ts +79 -0
  296. package/src/revision.ts +2 -0
  297. package/src/splash-screen/NewPage.tsx +294 -0
  298. package/src/splash-screen/OpenPage.tsx +113 -0
  299. package/src/splash-screen/RecentPages.tsx +123 -0
  300. package/src/splash-screen/SectionHeadline.tsx +21 -0
  301. package/src/splash-screen/SplashScreen.tsx +195 -0
  302. package/src/tour/Tour.tsx +566 -0
  303. package/src/tour/default-tour.tsx +301 -0
  304. package/src/tour/preview-tour.tsx +128 -0
  305. package/src/types.ts +335 -0
  306. package/styles.css +765 -0
  307. package/tsconfig.build.json +31 -0
  308. package/tsconfig.json +14 -0
@@ -0,0 +1,768 @@
1
+ import uuid from "react-uuid";
2
+ import {
3
+ AddComponentOperation,
4
+ DuplicateComponentsOperation,
5
+ EditFieldOperation,
6
+ EditOperation,
7
+ ExecuteEditOptions,
8
+ RenameItemOperation,
9
+ SuggestedEdit,
10
+ User,
11
+ } from "../../types";
12
+ import {
13
+ FieldDescriptor,
14
+ FullItem,
15
+ ItemDescriptor,
16
+ Page,
17
+ Field,
18
+ } from "../pageModel";
19
+ import {
20
+ executeEditOperation,
21
+ executeRedo,
22
+ executeUndo,
23
+ executeWorkflowCommand,
24
+ executeDeleteItems,
25
+ ExecutionResult,
26
+ lockField,
27
+ lockItems,
28
+ unlockItems,
29
+ executeCreateItem,
30
+ createVersion as doCreateVersion,
31
+ executeMoveItems,
32
+ executeCopyItems,
33
+ executeDuplicateItem,
34
+ } from "../services/editService";
35
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
36
+ import { EditorConfiguration } from "../../config/types";
37
+ import { ConfirmationDialogHandle } from "../ConfirmationDialog";
38
+ import { handleErrorResult } from "./helpers";
39
+ import { ItemsRepository } from "./itemsRepository";
40
+ import { useDebouncedCallback } from "use-debounce";
41
+ import { createOrUpdateSuggestedEdit } from "../services/suggestedEditsService";
42
+ import { decode } from "html-entities";
43
+ import { EditorMode } from "./editContext";
44
+
45
+ export function getOperationsContext(
46
+ state: {
47
+ page?: Page;
48
+ contentEditorItem?: FullItem;
49
+ sessionId: string;
50
+ user?: User;
51
+ setSelection: (ids: string[]) => void;
52
+ requestRefresh: (refresh: "immediate" | "waitForQuietPeriod") => void;
53
+ configuration: EditorConfiguration;
54
+ setLockedField: (field: FieldDescriptor) => void;
55
+ lockedField?: FieldDescriptor;
56
+ loadItem: (item: ItemDescriptor) => void;
57
+ itemsRepository: ItemsRepository;
58
+ editHistory: EditOperation[];
59
+ refreshHistory: (item: ItemDescriptor) => Promise<void>;
60
+ suggestedEdits: SuggestedEdit[];
61
+ setSuggestedEdits: (suggestedEdits: SuggestedEdit[]) => void;
62
+ mode: EditorMode;
63
+ setFocusFieldComponentId: (componentId: string) => void;
64
+ },
65
+ ui: {
66
+ showErrorToast: (error: { summary?: string; details?: string }) => void;
67
+ confirmationDialogRef: React.RefObject<ConfirmationDialogHandle | null>;
68
+ onOperationExecuted: (op: EditOperation) => void;
69
+ },
70
+ ) {
71
+ const [editOperationExecuted, setEditOperationExecuted] =
72
+ useState<EditOperation>();
73
+ const [executingEditOperations, setExecutingEditOperations] = useState<
74
+ EditOperation[]
75
+ >([]);
76
+
77
+ const lastOp = useRef<EditOperation | undefined>(undefined);
78
+
79
+ const undoing = useRef<boolean>(false);
80
+
81
+ const stateRef = useRef(state);
82
+
83
+ useEffect(() => {
84
+ stateRef.current = state;
85
+ }, [state]);
86
+
87
+ const executeOp = async (
88
+ op: EditOperation,
89
+ options: ExecuteEditOptions = {
90
+ refresh: "immediate",
91
+ },
92
+ ): Promise<EditOperation> => {
93
+ console.log("Executing op", op);
94
+ setExecutingEditOperations((ops) => (ops ? [...ops, op] : [op]));
95
+ const result = await executeEditOperation(op, state.sessionId);
96
+ // console.log("Result", result);
97
+ handleResult(result, options);
98
+ await state.refreshHistory(op.mainItem);
99
+ //state.addToEditHistory(result.data as EditOperation);
100
+ setEditOperationExecuted(result.data as EditOperation);
101
+ setExecutingEditOperations((ops) =>
102
+ ops ? ops.filter((x: EditOperation) => x.id !== op.id) : [],
103
+ );
104
+ return result.data as EditOperation;
105
+ };
106
+
107
+ const duplicateComponents = useCallback(
108
+ ({ componentIds }: { componentIds: string[] }) => {
109
+ const op = {
110
+ type: "duplicate-components",
111
+ mainItem: state.page?.item.descriptor,
112
+ sourceComponentIds: componentIds,
113
+ date: new Date().toISOString(),
114
+ id: uuid(),
115
+ description: "Duplicate components",
116
+ } as DuplicateComponentsOperation;
117
+
118
+ executeOp(op);
119
+ },
120
+ [state.page],
121
+ );
122
+
123
+ const addComponent = async (
124
+ componentTypeId: string,
125
+ placeholderKey: string,
126
+ index: number,
127
+ pageItem: ItemDescriptor,
128
+ ) => {
129
+ // if (!page) {
130
+ // console.log("Add component: No page found");
131
+ // return;
132
+ // }
133
+
134
+ const op: AddComponentOperation = {
135
+ type: "add-component",
136
+ mainItem: pageItem,
137
+ // parent: parentId
138
+ // ? {
139
+ // id: parentId,
140
+ // language: page.item.language,
141
+ // version: page.item.version,
142
+ // }
143
+ // : undefined,
144
+ placeholderKey,
145
+ placeholderIndex: index,
146
+ componentTypeId,
147
+ date: new Date().toISOString(),
148
+ id: uuid(),
149
+ description: "Add component",
150
+ };
151
+ const result = await executeOp(op);
152
+ return result as AddComponentOperation;
153
+ };
154
+
155
+ const handleResult = (
156
+ result: ExecutionResult<unknown>,
157
+ options: ExecuteEditOptions,
158
+ ) => {
159
+ if (handleErrorResult(result as ExecutionResult<unknown>, ui, state))
160
+ return;
161
+
162
+ const executedOps = Array.isArray(result.data)
163
+ ? result.data
164
+ : [result.data];
165
+
166
+ for (const executedOp of executedOps) {
167
+ if (
168
+ executedOp &&
169
+ (executedOp.type === "move-component" ||
170
+ executedOp.type === "add-component" ||
171
+ executedOp.type === "link-component" ||
172
+ executedOp.type === "duplicate-components")
173
+ ) {
174
+ if (executedOp.focus) {
175
+ state.setSelection([executedOp.focus]);
176
+ state.setFocusFieldComponentId(executedOp.focus);
177
+ }
178
+ }
179
+
180
+ ui.onOperationExecuted(executedOp);
181
+ }
182
+
183
+ if (options.refresh !== "none") {
184
+ state.requestRefresh(
185
+ options.refresh === "immediate" ? "immediate" : "waitForQuietPeriod",
186
+ );
187
+ }
188
+ };
189
+
190
+ const ensureLock = useCallback(
191
+ async (field: FieldDescriptor): Promise<boolean> => {
192
+ if (!field || !field.item) return false;
193
+ if (
194
+ state.lockedField?.fieldId === field.fieldId &&
195
+ state.lockedField?.item.id === field.item.id &&
196
+ state.lockedField?.item.language === field.item.language &&
197
+ state.lockedField?.item.version === field.item.version
198
+ )
199
+ return true;
200
+ const result = await lockField(
201
+ field.item,
202
+ field.fieldId,
203
+ state.sessionId,
204
+ );
205
+ if (result.type == "error") {
206
+ console.log("error locking field", result);
207
+ }
208
+ //handleErrorResult(result, ui, state);
209
+ if (result.type == "success" && result.data.success) {
210
+ state.setLockedField(field);
211
+ return true;
212
+ }
213
+ return false;
214
+ },
215
+ [state, ui],
216
+ );
217
+
218
+ const lockItemsAndRefresh = useCallback(
219
+ async (items: ItemDescriptor[]) => {
220
+ const item = items[0];
221
+ if (!item) return;
222
+ await lockItems(items);
223
+ if (item.id == state.contentEditorItem?.id) {
224
+ await state.loadItem(item);
225
+ } else state.requestRefresh("immediate");
226
+ },
227
+ [state],
228
+ );
229
+
230
+ const unlockItemsAndRefresh = useCallback(
231
+ async (items: ItemDescriptor[]) => {
232
+ const item = items[0];
233
+ if (!item) return;
234
+ await unlockItems(items);
235
+ if (item.id == state.contentEditorItem?.id) {
236
+ await state.loadItem(item);
237
+ } else state.requestRefresh("immediate");
238
+ },
239
+ [state],
240
+ );
241
+
242
+ const executeWorkflowCommandAndRefresh = useCallback(
243
+ async (item: ItemDescriptor, commandId: string) => {
244
+ const result = await executeWorkflowCommand(item, commandId, "");
245
+ if (result.type === "success") {
246
+ if (!result.data.succeeded) {
247
+ const errorMessage = {
248
+ summary: "Workflow command failed",
249
+ details: result.data.message,
250
+ };
251
+ ui.showErrorToast(errorMessage);
252
+ }
253
+ state.requestRefresh("immediate");
254
+ }
255
+ },
256
+ [state],
257
+ );
258
+
259
+ const renameItem = useCallback(
260
+ async (item: ItemDescriptor, newName: string) => {
261
+ await executeOp({
262
+ type: "rename-item",
263
+ itemId: item.id,
264
+ mainItem: state.page?.item.descriptor || item,
265
+ date: new Date().toISOString(),
266
+ id: uuid(),
267
+ item,
268
+ newName,
269
+ description: "Rename item",
270
+ } as RenameItemOperation);
271
+ state.requestRefresh("immediate");
272
+ state.itemsRepository.refreshItems([item]);
273
+ },
274
+ [state.itemsRepository, executeOp],
275
+ );
276
+
277
+ const lastEditField = useRef<FieldDescriptor | undefined>(undefined);
278
+
279
+ const editField = useCallback(
280
+ async ({
281
+ field,
282
+ value,
283
+ rawValue,
284
+ refresh = "immediate",
285
+ }: {
286
+ field: FieldDescriptor;
287
+ value?: string;
288
+ rawValue?: string | null;
289
+ refresh?: "none" | "immediate" | "delayed" | "waitForQuietPeriod";
290
+ }): Promise<void> => {
291
+ if (state.mode === "suggestions") {
292
+ const op = await getOrMergeSuggestedEditOp(field, rawValue, value, {
293
+ page: state.page,
294
+ user: state.user,
295
+ itemsRepository: state.itemsRepository,
296
+ suggestedEdits: state.suggestedEdits,
297
+ setSuggestedEdits: state.setSuggestedEdits,
298
+ });
299
+ if (op) {
300
+ await createOrUpdateSuggestedEdit(op);
301
+ }
302
+ return;
303
+ }
304
+
305
+ state.itemsRepository.updateFieldValue(
306
+ field,
307
+ state.user ?? { name: "unknown", ai: false },
308
+ true,
309
+ value,
310
+ rawValue,
311
+ );
312
+
313
+ if (refresh === "immediate") {
314
+ return editFieldImmediate({ field, value, rawValue, refresh });
315
+ }
316
+ if (
317
+ lastEditField.current?.fieldId !== field.fieldId ||
318
+ lastEditField.current?.item?.id !== field.item?.id ||
319
+ lastEditField.current?.item?.language !== field.item?.language ||
320
+ lastEditField.current?.item?.version !== field.item?.version
321
+ ) {
322
+ executeEditFieldDebounced.flush();
323
+ }
324
+ lastEditField.current = field;
325
+ return executeEditFieldDebounced({ field, value, rawValue, refresh });
326
+ },
327
+ [
328
+ state.itemsRepository,
329
+ getOrMergeSuggestedEditOp,
330
+ state.suggestedEdits,
331
+ state.page,
332
+ state.user,
333
+ state.mode,
334
+ ],
335
+ );
336
+
337
+ const editFieldImmediate = useCallback(
338
+ async ({
339
+ field,
340
+ value,
341
+ rawValue,
342
+ refresh = "immediate",
343
+ }: {
344
+ field: FieldDescriptor;
345
+ value?: string;
346
+ rawValue?: string | null;
347
+ refresh?: "none" | "immediate" | "delayed" | "waitForQuietPeriod";
348
+ }): Promise<void> => {
349
+ // state.itemsRepository.updateFieldValue(
350
+ // field,
351
+ // state.user ?? { name: "unknown", ai: false },
352
+ // true,
353
+ // value,
354
+ // rawValue,
355
+ // );
356
+
357
+ const val = rawValue !== undefined ? rawValue : value;
358
+ if (val === undefined) return;
359
+
360
+ const op = await getEditOP(field, rawValue, value);
361
+
362
+ if (op) {
363
+ await executeOp(op, { refresh });
364
+ await state.itemsRepository.refreshItems([field.item]);
365
+ state.itemsRepository.onFieldSaved(field, val);
366
+ }
367
+ },
368
+ [state.itemsRepository, executeOp],
369
+ );
370
+
371
+ const executeEditFieldDebounced = useDebouncedCallback(
372
+ async ({
373
+ field,
374
+ value,
375
+ rawValue,
376
+ refresh,
377
+ }: {
378
+ field: FieldDescriptor;
379
+ value?: string;
380
+ rawValue?: string | null;
381
+ refresh?: "none" | "immediate" | "delayed" | "waitForQuietPeriod";
382
+ }) => {
383
+ state.itemsRepository.updateFieldValue(
384
+ field,
385
+ state.user ?? { name: "unknown", ai: false },
386
+ true,
387
+ value,
388
+ rawValue,
389
+ );
390
+ return editFieldImmediate({ field, value, rawValue, refresh });
391
+ },
392
+ state.configuration.debounceFieldEditsInterval * 2,
393
+ { trailing: true },
394
+ );
395
+
396
+ async function getEditOP(
397
+ field: FieldDescriptor,
398
+ rawValue: string | null | undefined,
399
+ value: string | undefined,
400
+ ) {
401
+ const item = await state.itemsRepository.getItem(field.item);
402
+ if (!item) return;
403
+
404
+ const fieldItem = await state.itemsRepository.getItem({
405
+ id: field.fieldId,
406
+ language: "en",
407
+ version: 0,
408
+ });
409
+
410
+ if (!fieldItem) {
411
+ console.log("Could not fetch field item " + field.fieldId);
412
+ return;
413
+ }
414
+
415
+ let op = lastOp.current as EditFieldOperation;
416
+
417
+ if (
418
+ !op ||
419
+ op.undone ||
420
+ op.type !== "edit-field" ||
421
+ !op.itemId ||
422
+ op.itemId !== item.id ||
423
+ op.fieldId !== field.fieldId ||
424
+ op.mainItem.language !== item.language ||
425
+ op.mainItem.version !== item.version
426
+ ) {
427
+ op = {
428
+ type: "edit-field",
429
+ itemId: item.id,
430
+ fieldId: field.fieldId,
431
+ fieldName: fieldItem.name,
432
+ date: new Date().toISOString(),
433
+ id: uuid(),
434
+ description: "Edit " + item.name,
435
+ focus: item.id + ":" + field.fieldId,
436
+ canUndo: true,
437
+ mainItem: state.page?.item.descriptor || item.descriptor,
438
+ };
439
+ }
440
+
441
+ op.date = new Date().toISOString();
442
+ op.value = rawValue ?? value;
443
+
444
+ const firstEditInheritedComponent =
445
+ item.isInheritedFromMasterLanguage &&
446
+ op.mainItem.language != state.page?.item.language;
447
+
448
+ if (firstEditInheritedComponent)
449
+ op.mainItem.language = state.page!.item.language;
450
+
451
+ lastOp.current = op;
452
+ return op;
453
+ }
454
+
455
+ const undo = useCallback(
456
+ async (numberOfOperations?: number): Promise<boolean> => {
457
+ if (undoing.current) {
458
+ return false;
459
+ }
460
+ undoing.current = true;
461
+
462
+ try {
463
+ const item = state.contentEditorItem?.descriptor;
464
+ if (!item) return false;
465
+
466
+ const ops = state.editHistory
467
+ .filter((x) => x.canUndo && x.sessionId === state.sessionId)
468
+ .slice(0, numberOfOperations || 1);
469
+
470
+ if (!ops.length) return false;
471
+
472
+ // Mark operations as undone
473
+ ops.forEach((x) => {
474
+ x.undone = true;
475
+ x.canUndo = false;
476
+ });
477
+
478
+ const result = await executeUndo(
479
+ state.sessionId,
480
+ ops.map((x) => x.id),
481
+ );
482
+ handleResult(result, { refresh: "immediate" });
483
+
484
+ if (result.type === "success") {
485
+ const itemsToRefresh = result.data
486
+ .map((x: EditOperation) => {
487
+ if (x.type === "edit-field")
488
+ return {
489
+ ...(x as EditFieldOperation).mainItem,
490
+ id: (x as EditFieldOperation).itemId,
491
+ };
492
+ if (x.type === "add-component")
493
+ return {
494
+ id: (x as AddComponentOperation).componentId,
495
+ language: x.mainItem.language,
496
+ version: x.mainItem.version,
497
+ };
498
+ return undefined;
499
+ })
500
+ .filter(
501
+ (x: ItemDescriptor | undefined): x is ItemDescriptor => x != null,
502
+ );
503
+
504
+ await state.itemsRepository.refreshItems(itemsToRefresh);
505
+ await state.refreshHistory(item);
506
+ return true;
507
+ } else {
508
+ ui.showErrorToast(result);
509
+ return false;
510
+ }
511
+ } finally {
512
+ // Always reset the flag
513
+ undoing.current = false;
514
+ console.log("undoing flag reset");
515
+ }
516
+ },
517
+ [state, ui, undoing],
518
+ );
519
+
520
+ const redo = useCallback(async (): Promise<boolean> => {
521
+ if (undoing.current) return false;
522
+ undoing.current = true;
523
+
524
+ try {
525
+ const item = state.contentEditorItem?.descriptor;
526
+ if (!item) return false;
527
+
528
+ // Clone the array to avoid mutating the original state
529
+ const op = [...state.editHistory]
530
+ .reverse()
531
+ .find((x) => x.canRedo && x.sessionId === state.sessionId);
532
+ if (!op) return false;
533
+
534
+ op.canRedo = false;
535
+ op.undone = false;
536
+
537
+ const result = await executeRedo(state.sessionId, op.id);
538
+ handleResult(result, { refresh: "immediate" });
539
+
540
+ if (result.type === "success") {
541
+ const itemsToRefresh = result.data
542
+ .map((x: EditOperation) => {
543
+ if (x.type === "edit-field")
544
+ return {
545
+ ...(x as EditFieldOperation).mainItem,
546
+ id: (x as EditFieldOperation).itemId,
547
+ };
548
+ if (x.type === "add-component")
549
+ return {
550
+ id: (x as AddComponentOperation).componentId,
551
+ language: x.mainItem.language,
552
+ version: x.mainItem.version,
553
+ };
554
+ return undefined;
555
+ })
556
+ .filter(
557
+ (x: ItemDescriptor | undefined): x is ItemDescriptor => x != null,
558
+ );
559
+
560
+ await state.itemsRepository.refreshItems(itemsToRefresh);
561
+ await state.refreshHistory(item);
562
+ return true;
563
+ } else {
564
+ ui.showErrorToast(result);
565
+ return false;
566
+ }
567
+ } finally {
568
+ // Always reset the flag
569
+ undoing.current = false;
570
+ }
571
+ }, [state, ui, undoing]);
572
+
573
+ const deleteItems = useCallback(
574
+ async (items: ItemDescriptor[]) => {
575
+ const itemsToDelete = await state.itemsRepository.getItems(items);
576
+ await executeDeleteItems(items);
577
+ state.itemsRepository.onItemsDeleted(
578
+ itemsToDelete.map((x) => ({
579
+ item: x.descriptor,
580
+ parentId: x.parentId,
581
+ })),
582
+ );
583
+ state.requestRefresh("immediate");
584
+ },
585
+ [state.itemsRepository],
586
+ );
587
+
588
+ const moveItems = useCallback(
589
+ async (items: ItemDescriptor[], target: ItemDescriptor, index: number) => {
590
+ await executeMoveItems(items, target, index);
591
+ },
592
+ [state.itemsRepository],
593
+ );
594
+
595
+ const createItem = useCallback(
596
+ async (parent: ItemDescriptor, templateId: string, name: string) => {
597
+ const result = await executeCreateItem(parent, templateId, name);
598
+ if (handleErrorResult(result as ExecutionResult<unknown>, ui, state))
599
+ return;
600
+ state.itemsRepository.refreshItems([parent]);
601
+ return result.data as ItemDescriptor;
602
+ },
603
+ [state.itemsRepository],
604
+ );
605
+
606
+ const createVersion = useCallback(
607
+ async (item: ItemDescriptor) => {
608
+ await doCreateVersion(item, state.sessionId);
609
+ },
610
+ [state],
611
+ );
612
+
613
+ const copyItems = useCallback(
614
+ async (items: ItemDescriptor[], target: ItemDescriptor, index: number) => {
615
+ const result = await executeCopyItems(items, target, index);
616
+ return result.data as ItemDescriptor[];
617
+ },
618
+ [state.itemsRepository],
619
+ );
620
+
621
+ const duplicateItem = useCallback(
622
+ async (item: ItemDescriptor, target: ItemDescriptor, name: string) => {
623
+ const result = await executeDuplicateItem(item, target, name);
624
+ if (handleErrorResult(result as ExecutionResult<unknown>, ui, state))
625
+ return;
626
+ state.itemsRepository.refreshItems([target]);
627
+ return result.data as ItemDescriptor;
628
+ },
629
+ [state.itemsRepository],
630
+ );
631
+ const ops = useMemo(
632
+ () => ({
633
+ addComponent,
634
+ ensureLock,
635
+ lockItems: lockItemsAndRefresh,
636
+ unlockItems: unlockItemsAndRefresh,
637
+ executeWorkflowCommand: executeWorkflowCommandAndRefresh,
638
+ editField,
639
+ undo,
640
+ redo,
641
+ executeEditOperation: executeOp,
642
+ renameItem,
643
+ deleteItems,
644
+ createItem,
645
+ createVersion,
646
+ duplicateComponents,
647
+ undoing: undoing.current,
648
+ moveItems,
649
+ copyItems,
650
+ duplicateItem,
651
+ }),
652
+ [
653
+ addComponent,
654
+ ensureLock,
655
+ lockItemsAndRefresh,
656
+ unlockItemsAndRefresh,
657
+ executeWorkflowCommandAndRefresh,
658
+ editField,
659
+ undo,
660
+ redo,
661
+ executeOp,
662
+ renameItem,
663
+ deleteItems,
664
+ createItem,
665
+ createVersion,
666
+ duplicateComponents,
667
+ undoing.current,
668
+ moveItems,
669
+ state.mode,
670
+ state.page,
671
+ copyItems,
672
+ duplicateItem,
673
+ ],
674
+ );
675
+
676
+ return useMemo(
677
+ () => ({
678
+ ops,
679
+ context: {
680
+ editOperationExecuted,
681
+ executingEditOperations,
682
+ },
683
+ }),
684
+ [executingEditOperations, editOperationExecuted, ops, undoing],
685
+ );
686
+ }
687
+
688
+ async function getOrMergeSuggestedEditOp(
689
+ field: FieldDescriptor,
690
+ rawValue: string | null | undefined,
691
+ value: string | undefined,
692
+ state: {
693
+ page?: {
694
+ item: { descriptor: { id: string; language: string; version: number } };
695
+ };
696
+ user?: { name: string };
697
+ itemsRepository: { getItem: (descriptor: any) => Promise<any> };
698
+ // Assume you have access to current suggested edits in context or state:
699
+ suggestedEdits: SuggestedEdit[];
700
+ setSuggestedEdits: (suggestedEdits: SuggestedEdit[]) => void;
701
+ },
702
+ ): Promise<SuggestedEdit | undefined> {
703
+ const item = await state.itemsRepository.getItem(field.item);
704
+ if (!item) return;
705
+
706
+ const fieldItem = item.fields.find(
707
+ (f: { id: string; rawValue?: string }) => f.id === field.fieldId,
708
+ );
709
+ if (!fieldItem) {
710
+ console.log(
711
+ "Could not locate field in item for suggested edit:",
712
+ field.fieldId,
713
+ );
714
+ return;
715
+ }
716
+
717
+ const newVal = rawValue !== undefined ? rawValue : value;
718
+ if (newVal === undefined) return;
719
+
720
+ if (!state.page) {
721
+ console.log("ERROR: No page");
722
+ return;
723
+ }
724
+ const page = state.page;
725
+
726
+ // Attempt to find an existing suggested edit by the same user for this field.
727
+ const existing = state.suggestedEdits.find(
728
+ (edit) =>
729
+ edit.mainItemId === page.item.descriptor.id &&
730
+ edit.mainItemLanguage === page.item.descriptor.language &&
731
+ edit.mainItemVersion === page.item.descriptor.version &&
732
+ edit.itemId === item.id &&
733
+ edit.fieldId === field.fieldId &&
734
+ edit.author === (state.user?.name || "unknown") &&
735
+ edit.status === "Pending", // or any status that indicates it's open for further editing.
736
+ );
737
+
738
+ if (existing) {
739
+ // Update the existing suggestion.
740
+ existing.newValue = newVal || "";
741
+ existing.updated = new Date().toISOString();
742
+ existing.updatedBy = state.user?.name || "unknown";
743
+ // Optionally, you might update the comments or other fields.
744
+ state.setSuggestedEdits([...state.suggestedEdits]);
745
+ return existing;
746
+ } else {
747
+ // Create a new suggested edit if none exists.
748
+ const newValue = newVal || "";
749
+ const oldValue = fieldItem.rawValue || "";
750
+ if (decode(newValue) === decode(oldValue)) return;
751
+
752
+ const newEdit: SuggestedEdit = {
753
+ id: uuid(),
754
+ mainItemId: state.page?.item.descriptor.id,
755
+ mainItemLanguage: state.page?.item.descriptor.language,
756
+ mainItemVersion: state.page?.item.descriptor.version,
757
+ itemId: item.id,
758
+ fieldId: field.fieldId,
759
+ oldValue: fieldItem.rawValue || "",
760
+ newValue: newVal || "",
761
+ status: "Pending",
762
+ type: "FieldValue",
763
+ };
764
+
765
+ state.setSuggestedEdits([...state.suggestedEdits, newEdit]);
766
+ return newEdit;
767
+ }
768
+ }