@alpaca-editor/core 1.0.3942 → 1.0.3943

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (308) hide show
  1. package/.prettierrc +3 -0
  2. package/build.css +3 -0
  3. package/components.json +21 -0
  4. package/dist/editor/ContentTree.d.ts +2 -1
  5. package/dist/editor/ContentTree.js +23 -21
  6. package/dist/editor/ContentTree.js.map +1 -1
  7. package/dist/editor/FieldActionsOverlay.js +0 -2
  8. package/dist/editor/FieldActionsOverlay.js.map +1 -1
  9. package/dist/editor/ScrollingContentTree.js +1 -1
  10. package/dist/editor/ScrollingContentTree.js.map +1 -1
  11. package/dist/editor/Titlebar.js +1 -1
  12. package/dist/editor/Titlebar.js.map +1 -1
  13. package/dist/editor/ai/GhostWriter.js +24 -3
  14. package/dist/editor/ai/GhostWriter.js.map +1 -1
  15. package/dist/editor/client/EditorClient.js +7 -7
  16. package/dist/editor/client/EditorClient.js.map +1 -1
  17. package/dist/editor/field-types/InternalLinkFieldEditor.js +60 -10
  18. package/dist/editor/field-types/InternalLinkFieldEditor.js.map +1 -1
  19. package/dist/editor/media-selector/MediaFolderBrowser.js +48 -1
  20. package/dist/editor/media-selector/MediaFolderBrowser.js.map +1 -1
  21. package/dist/editor/menubar/PageSelector.js +116 -65
  22. package/dist/editor/menubar/PageSelector.js.map +1 -1
  23. package/dist/editor/page-viewer/EditorForm.js +5 -2
  24. package/dist/editor/page-viewer/EditorForm.js.map +1 -1
  25. package/dist/editor/ui/ItemSearch.js +14 -8
  26. package/dist/editor/ui/ItemSearch.js.map +1 -1
  27. package/dist/editor/ui/PerfectTree.d.ts +4 -2
  28. package/dist/editor/ui/PerfectTree.js +78 -4
  29. package/dist/editor/ui/PerfectTree.js.map +1 -1
  30. package/dist/editor/ui/Splitter.js +1 -1
  31. package/dist/revision.d.ts +2 -2
  32. package/dist/revision.js +2 -2
  33. package/dist/styles.css +8 -2
  34. package/eslint.config.mjs +4 -0
  35. package/images/bg-shape-black.webp +0 -0
  36. package/images/wizard-bg.png +0 -0
  37. package/images/wizard-tour.png +0 -0
  38. package/images/wizard.png +0 -0
  39. package/package.json +2 -8
  40. package/src/client-components/api.ts +6 -0
  41. package/src/client-components/index.ts +19 -0
  42. package/src/components/ActionButton.tsx +50 -0
  43. package/src/components/Error.tsx +57 -0
  44. package/src/components/ui/CardConnector.tsx +56 -0
  45. package/src/components/ui/button.tsx +62 -0
  46. package/src/components/ui/card.tsx +372 -0
  47. package/src/components/ui/context-menu.tsx +250 -0
  48. package/src/config/config.tsx +917 -0
  49. package/src/config/types.ts +286 -0
  50. package/src/editor/ComponentInfo.tsx +90 -0
  51. package/src/editor/ConfirmationDialog.tsx +103 -0
  52. package/src/editor/ContentTree.tsx +733 -0
  53. package/src/editor/ContextMenu.tsx +230 -0
  54. package/src/editor/Editor.tsx +90 -0
  55. package/src/editor/EditorWarning.tsx +34 -0
  56. package/src/editor/EditorWarnings.tsx +33 -0
  57. package/src/editor/FieldActionsOverlay.tsx +296 -0
  58. package/src/editor/FieldEditorPopup.tsx +65 -0
  59. package/src/editor/FieldHistory.tsx +75 -0
  60. package/src/editor/FieldList.tsx +190 -0
  61. package/src/editor/FieldListField.tsx +391 -0
  62. package/src/editor/FieldListFieldWithFallbacks.tsx +217 -0
  63. package/src/editor/FloatingToolbar.tsx +163 -0
  64. package/src/editor/ImageEditor.tsx +128 -0
  65. package/src/editor/ItemInfo.tsx +90 -0
  66. package/src/editor/LinkEditorDialog.tsx +196 -0
  67. package/src/editor/MainLayout.tsx +95 -0
  68. package/src/editor/MobileLayout.tsx +68 -0
  69. package/src/editor/NewEditorClient.tsx +11 -0
  70. package/src/editor/PictureCropper.tsx +568 -0
  71. package/src/editor/PictureEditor.tsx +301 -0
  72. package/src/editor/PictureEditorDialog.tsx +381 -0
  73. package/src/editor/PublishDialog.ignore +74 -0
  74. package/src/editor/ScrollingContentTree.tsx +68 -0
  75. package/src/editor/Terminal.tsx +227 -0
  76. package/src/editor/Titlebar.tsx +104 -0
  77. package/src/editor/ai/AiPopup.tsx +59 -0
  78. package/src/editor/ai/AiResponseMessage.tsx +106 -0
  79. package/src/editor/ai/AiTerminal.tsx +503 -0
  80. package/src/editor/ai/AiToolCall.tsx +61 -0
  81. package/src/editor/ai/EditorAiTerminal.tsx +20 -0
  82. package/src/editor/ai/GhostWriter.tsx +480 -0
  83. package/src/editor/ai/aiPageModel.ts +108 -0
  84. package/src/editor/ai/editorAiContext.ts +18 -0
  85. package/src/editor/client/AboutDialog.tsx +44 -0
  86. package/src/editor/client/EditorClient.tsx +2241 -0
  87. package/src/editor/client/GenericDialog.tsx +50 -0
  88. package/src/editor/client/editContext.ts +416 -0
  89. package/src/editor/client/helpers.ts +44 -0
  90. package/src/editor/client/itemsRepository.ts +574 -0
  91. package/src/editor/client/operations.ts +768 -0
  92. package/src/editor/client/pageModelBuilder.ts +219 -0
  93. package/src/editor/commands/commands.ts +22 -0
  94. package/src/editor/commands/componentCommands.tsx +431 -0
  95. package/src/editor/commands/createVersionCommand.ts +33 -0
  96. package/src/editor/commands/deleteVersionCommand.ts +71 -0
  97. package/src/editor/commands/itemCommands.tsx +351 -0
  98. package/src/editor/commands/localizeItem/LocalizeItemDialog.tsx +201 -0
  99. package/src/editor/commands/localizeItem/LocalizeItemUtils.ts +27 -0
  100. package/src/editor/commands/undo.ts +39 -0
  101. package/src/editor/component-designer/ComponentDesigner.tsx +70 -0
  102. package/src/editor/component-designer/ComponentDesignerAiTerminal.tsx +11 -0
  103. package/src/editor/component-designer/ComponentDesignerMenu.tsx +91 -0
  104. package/src/editor/component-designer/ComponentEditor.tsx +97 -0
  105. package/src/editor/component-designer/ComponentRenderingCodeEditor.tsx +31 -0
  106. package/src/editor/component-designer/ComponentRenderingEditor.tsx +104 -0
  107. package/src/editor/component-designer/ComponentsDropdown.tsx +39 -0
  108. package/src/editor/component-designer/PlaceholdersEditor.tsx +179 -0
  109. package/src/editor/component-designer/RenderingsDropdown.tsx +36 -0
  110. package/src/editor/component-designer/TemplateEditor.tsx +236 -0
  111. package/src/editor/component-designer/aiContext.ts +23 -0
  112. package/src/editor/componentTreeHelper.tsx +116 -0
  113. package/src/editor/context-menu/CopyMoveMenu.tsx +103 -0
  114. package/src/editor/context-menu/InsertMenu.tsx +347 -0
  115. package/src/editor/control-center/About.tsx +342 -0
  116. package/src/editor/control-center/ControlCenterMenu.tsx +76 -0
  117. package/src/editor/control-center/IndexOverview.tsx +50 -0
  118. package/src/editor/control-center/IndexSettings.tsx +266 -0
  119. package/src/editor/control-center/Info.tsx +104 -0
  120. package/src/editor/control-center/QuotaInfo.tsx +301 -0
  121. package/src/editor/control-center/Status.tsx +113 -0
  122. package/src/editor/control-center/WebSocketMessages.tsx +155 -0
  123. package/src/editor/editor-warnings/ItemLocked.tsx +63 -0
  124. package/src/editor/editor-warnings/NoLanguageWriteAccess.tsx +22 -0
  125. package/src/editor/editor-warnings/NoWorkflowWriteAccess.tsx +23 -0
  126. package/src/editor/editor-warnings/NoWriteAccess.tsx +16 -0
  127. package/src/editor/editor-warnings/ValidationErrors.tsx +54 -0
  128. package/src/editor/field-types/AttachmentEditor.tsx +9 -0
  129. package/src/editor/field-types/CheckboxEditor.tsx +47 -0
  130. package/src/editor/field-types/DropLinkEditor.tsx +80 -0
  131. package/src/editor/field-types/DropListEditor.tsx +84 -0
  132. package/src/editor/field-types/ImageFieldEditor.tsx +65 -0
  133. package/src/editor/field-types/InternalLinkFieldEditor.tsx +188 -0
  134. package/src/editor/field-types/LinkFieldEditor.tsx +85 -0
  135. package/src/editor/field-types/MultiLineText.tsx +82 -0
  136. package/src/editor/field-types/PictureFieldEditor.tsx +121 -0
  137. package/src/editor/field-types/RawEditor.tsx +53 -0
  138. package/src/editor/field-types/ReactQuill.tsx +580 -0
  139. package/src/editor/field-types/RichTextEditor.tsx +22 -0
  140. package/src/editor/field-types/RichTextEditorComponent.tsx +127 -0
  141. package/src/editor/field-types/SingleLineText.tsx +174 -0
  142. package/src/editor/field-types/TreeListEditor.tsx +261 -0
  143. package/src/editor/fieldTypes.ts +140 -0
  144. package/src/editor/media-selector/AiImageSearch.tsx +185 -0
  145. package/src/editor/media-selector/AiImageSearchPrompt.tsx +94 -0
  146. package/src/editor/media-selector/MediaFolderBrowser.tsx +321 -0
  147. package/src/editor/media-selector/MediaSelector.tsx +42 -0
  148. package/src/editor/media-selector/Preview.tsx +14 -0
  149. package/src/editor/media-selector/Thumbnails.tsx +48 -0
  150. package/src/editor/media-selector/TreeSelector.tsx +292 -0
  151. package/src/editor/media-selector/UploadZone.tsx +137 -0
  152. package/src/editor/media-selector/index.ts +8 -0
  153. package/src/editor/menubar/ActionsMenu.tsx +94 -0
  154. package/src/editor/menubar/ActiveUsers.tsx +17 -0
  155. package/src/editor/menubar/ApproveAndPublish.tsx +18 -0
  156. package/src/editor/menubar/BrowseHistory.tsx +28 -0
  157. package/src/editor/menubar/ItemLanguageVersion.tsx +76 -0
  158. package/src/editor/menubar/LanguageSelector.tsx +226 -0
  159. package/src/editor/menubar/Menu.tsx +83 -0
  160. package/src/editor/menubar/NavButtons.tsx +74 -0
  161. package/src/editor/menubar/PageSelector.tsx +278 -0
  162. package/src/editor/menubar/PageViewerControls.tsx +120 -0
  163. package/src/editor/menubar/PreviewSecondaryControls.tsx +18 -0
  164. package/src/editor/menubar/SecondaryControls.tsx +45 -0
  165. package/src/editor/menubar/Separator.tsx +12 -0
  166. package/src/editor/menubar/SiteInfo.tsx +53 -0
  167. package/src/editor/menubar/User.tsx +27 -0
  168. package/src/editor/menubar/VersionSelector.tsx +142 -0
  169. package/src/editor/page-editor-chrome/CommentHighlighting.tsx +307 -0
  170. package/src/editor/page-editor-chrome/CommentHighlightings.tsx +35 -0
  171. package/src/editor/page-editor-chrome/FieldActionIndicator.tsx +59 -0
  172. package/src/editor/page-editor-chrome/FieldActionIndicators.tsx +23 -0
  173. package/src/editor/page-editor-chrome/FieldEditedIndicator.tsx +64 -0
  174. package/src/editor/page-editor-chrome/FieldEditedIndicators.tsx +35 -0
  175. package/src/editor/page-editor-chrome/FrameMenu.tsx +338 -0
  176. package/src/editor/page-editor-chrome/FrameMenus.tsx +48 -0
  177. package/src/editor/page-editor-chrome/InlineEditor.tsx +765 -0
  178. package/src/editor/page-editor-chrome/LockedFieldIndicator.tsx +61 -0
  179. package/src/editor/page-editor-chrome/NoLayout.tsx +36 -0
  180. package/src/editor/page-editor-chrome/PageEditorChrome.tsx +122 -0
  181. package/src/editor/page-editor-chrome/PictureEditorOverlay.tsx +161 -0
  182. package/src/editor/page-editor-chrome/PlaceholderDropZone.tsx +169 -0
  183. package/src/editor/page-editor-chrome/PlaceholderDropZones.tsx +315 -0
  184. package/src/editor/page-editor-chrome/SuggestionHighlighting.tsx +300 -0
  185. package/src/editor/page-editor-chrome/SuggestionHighlightings.tsx +40 -0
  186. package/src/editor/page-editor-chrome/useInlineAICompletion.tsx +828 -0
  187. package/src/editor/page-viewer/DeviceToolbar.tsx +70 -0
  188. package/src/editor/page-viewer/EditorForm.tsx +262 -0
  189. package/src/editor/page-viewer/MiniMap.tsx +362 -0
  190. package/src/editor/page-viewer/PageViewer.tsx +169 -0
  191. package/src/editor/page-viewer/PageViewerFrame.tsx +1022 -0
  192. package/src/editor/page-viewer/pageModelSkeletonBuilder.ts +412 -0
  193. package/src/editor/page-viewer/pageViewContext.ts +186 -0
  194. package/src/editor/pageModel.ts +220 -0
  195. package/src/editor/picture-shared.tsx +53 -0
  196. package/src/editor/reviews/Comment.tsx +308 -0
  197. package/src/editor/reviews/Comments.tsx +125 -0
  198. package/src/editor/reviews/DiffView.tsx +109 -0
  199. package/src/editor/reviews/PreviewInfo.tsx +35 -0
  200. package/src/editor/reviews/Reviews.tsx +280 -0
  201. package/src/editor/reviews/SuggestedEdit.tsx +316 -0
  202. package/src/editor/reviews/reviewCommands.tsx +47 -0
  203. package/src/editor/reviews/useReviews.tsx +70 -0
  204. package/src/editor/services/aiService.ts +173 -0
  205. package/src/editor/services/componentDesignerService.ts +151 -0
  206. package/src/editor/services/contentService.ts +180 -0
  207. package/src/editor/services/editService.ts +488 -0
  208. package/src/editor/services/indexService.ts +24 -0
  209. package/src/editor/services/reviewsService.ts +53 -0
  210. package/src/editor/services/serviceHelper.ts +95 -0
  211. package/src/editor/services/suggestedEditsService.ts +39 -0
  212. package/src/editor/services/systemService.ts +5 -0
  213. package/src/editor/services/translationService.ts +21 -0
  214. package/src/editor/services-server/api.ts +150 -0
  215. package/src/editor/services-server/graphQL.ts +106 -0
  216. package/src/editor/sidebar/ComponentPalette.tsx +161 -0
  217. package/src/editor/sidebar/ComponentTree.tsx +549 -0
  218. package/src/editor/sidebar/Debug.tsx +111 -0
  219. package/src/editor/sidebar/DictionaryEditor.tsx +261 -0
  220. package/src/editor/sidebar/EditHistory.tsx +134 -0
  221. package/src/editor/sidebar/GraphQL.tsx +164 -0
  222. package/src/editor/sidebar/Insert.tsx +35 -0
  223. package/src/editor/sidebar/MainContentTree.tsx +102 -0
  224. package/src/editor/sidebar/Performance.tsx +53 -0
  225. package/src/editor/sidebar/Sessions.tsx +35 -0
  226. package/src/editor/sidebar/Sidebar.tsx +20 -0
  227. package/src/editor/sidebar/SidebarView.tsx +152 -0
  228. package/src/editor/sidebar/Translations.tsx +295 -0
  229. package/src/editor/sidebar/Validation.tsx +102 -0
  230. package/src/editor/sidebar/ViewSelector.tsx +60 -0
  231. package/src/editor/sidebar/Workbox.tsx +209 -0
  232. package/src/editor/ui/CenteredMessage.tsx +7 -0
  233. package/src/editor/ui/CopyMoveTargetSelectorDialog.tsx +81 -0
  234. package/src/editor/ui/CopyToClipboardButton.tsx +24 -0
  235. package/src/editor/ui/DialogButtons.tsx +11 -0
  236. package/src/editor/ui/Icons.tsx +709 -0
  237. package/src/editor/ui/ItemList.tsx +76 -0
  238. package/src/editor/ui/ItemNameDialogNew.tsx +118 -0
  239. package/src/editor/ui/ItemSearch.tsx +159 -0
  240. package/src/editor/ui/PerfectTree.tsx +676 -0
  241. package/src/editor/ui/Section.tsx +35 -0
  242. package/src/editor/ui/SimpleIconButton.tsx +54 -0
  243. package/src/editor/ui/SimpleMenu.tsx +40 -0
  244. package/src/editor/ui/SimpleTable.tsx +60 -0
  245. package/src/editor/ui/SimpleTabs.tsx +60 -0
  246. package/src/editor/ui/SimpleToolbar.tsx +7 -0
  247. package/src/editor/ui/Spinner.tsx +9 -0
  248. package/src/editor/ui/Splitter.tsx +420 -0
  249. package/src/editor/ui/StackedPanels.tsx +134 -0
  250. package/src/editor/ui/Toolbar.tsx +7 -0
  251. package/src/editor/utils/id-helper.ts +3 -0
  252. package/src/editor/utils/insertOptions.ts +69 -0
  253. package/src/editor/utils/itemutils.ts +29 -0
  254. package/src/editor/utils/useMemoDebug.ts +28 -0
  255. package/src/editor/utils.ts +486 -0
  256. package/src/editor/views/CompareView.tsx +245 -0
  257. package/src/editor/views/EditView.tsx +27 -0
  258. package/src/editor/views/ItemEditor.tsx +58 -0
  259. package/src/editor/views/MediaFolderEditView.tsx +66 -0
  260. package/src/editor/views/SingleEditView.tsx +57 -0
  261. package/src/fonts/Geist-Black.woff2 +0 -0
  262. package/src/fonts/Geist-Bold.woff2 +0 -0
  263. package/src/fonts/Geist-ExtraBold.woff2 +0 -0
  264. package/src/fonts/Geist-ExtraLight.woff2 +0 -0
  265. package/src/fonts/Geist-Light.woff2 +0 -0
  266. package/src/fonts/Geist-Medium.woff2 +0 -0
  267. package/src/fonts/Geist-Regular.woff2 +0 -0
  268. package/src/fonts/Geist-SemiBold.woff2 +0 -0
  269. package/src/fonts/Geist-Thin.woff2 +0 -0
  270. package/src/fonts/Geist[wght].woff2 +0 -0
  271. package/src/fonts/index.ts +10 -0
  272. package/src/index.ts +23 -0
  273. package/src/lib/safelist.tsx +16 -0
  274. package/src/lib/utils.ts +6 -0
  275. package/src/page-wizard/PageWizard.tsx +139 -0
  276. package/src/page-wizard/WizardBox.tsx +4 -0
  277. package/src/page-wizard/WizardBoxConnector.tsx +56 -0
  278. package/src/page-wizard/WizardSteps.tsx +458 -0
  279. package/src/page-wizard/service.ts +35 -0
  280. package/src/page-wizard/startPageWizardCommand.ts +26 -0
  281. package/src/page-wizard/steps/BuildPageStep.tsx +259 -0
  282. package/src/page-wizard/steps/CollectStep.tsx +296 -0
  283. package/src/page-wizard/steps/ComponentTypesSelector.tsx +454 -0
  284. package/src/page-wizard/steps/Components.tsx +193 -0
  285. package/src/page-wizard/steps/ContentStep.tsx +890 -0
  286. package/src/page-wizard/steps/EditButton.tsx +34 -0
  287. package/src/page-wizard/steps/FieldEditor.tsx +102 -0
  288. package/src/page-wizard/steps/Generate.tsx +60 -0
  289. package/src/page-wizard/steps/ImagesStep.tsx +382 -0
  290. package/src/page-wizard/steps/LayoutStep.tsx +227 -0
  291. package/src/page-wizard/steps/MetaDataStep.tsx +173 -0
  292. package/src/page-wizard/steps/SelectStep.tsx +281 -0
  293. package/src/page-wizard/steps/schema.ts +180 -0
  294. package/src/page-wizard/steps/usePageCreator.ts +325 -0
  295. package/src/page-wizard/usePageWizard.ts +79 -0
  296. package/src/revision.ts +2 -0
  297. package/src/splash-screen/NewPage.tsx +294 -0
  298. package/src/splash-screen/OpenPage.tsx +113 -0
  299. package/src/splash-screen/RecentPages.tsx +123 -0
  300. package/src/splash-screen/SectionHeadline.tsx +21 -0
  301. package/src/splash-screen/SplashScreen.tsx +195 -0
  302. package/src/tour/Tour.tsx +566 -0
  303. package/src/tour/default-tour.tsx +301 -0
  304. package/src/tour/preview-tour.tsx +128 -0
  305. package/src/types.ts +335 -0
  306. package/styles.css +765 -0
  307. package/tsconfig.build.json +31 -0
  308. package/tsconfig.json +14 -0
@@ -0,0 +1,733 @@
1
+ "use client";
2
+
3
+ import { useEditContext, useEditContextRef } from "./client/editContext";
4
+
5
+ import { useCallback, useEffect, useRef, useState, useMemo, memo } from "react";
6
+
7
+ import { ItemTreeNodeData, getChildren } from "./services/contentService";
8
+ import { ContextMenu } from "primereact/contextmenu";
9
+ import { MenuItem } from "primereact/menuitem";
10
+
11
+ import { getAbsoluteIconUrl } from "./utils";
12
+ import { FullItem, ItemDescriptor } from "./pageModel";
13
+ import { ItemCommand } from "./commands/itemCommands";
14
+ import { Spinner } from "./ui/Spinner";
15
+
16
+ import { PerfectTree, TreeNode } from "./ui/PerfectTree";
17
+ import { loadInsertOptions } from "./services/editService";
18
+
19
+ // Create a memoized version of PerfectTree
20
+ const MemoizedPerfectTree = memo(PerfectTree);
21
+
22
+ type CustomTreeNode = TreeNode & {
23
+ isDraggable?: boolean;
24
+ iconUrl?: string;
25
+ url?: string;
26
+ loading?: boolean;
27
+ parent?: CustomTreeNode;
28
+ selectable?: boolean;
29
+ className?: string;
30
+ };
31
+
32
+ export default function ContentTree({
33
+ isDraggable,
34
+ dragStart,
35
+ dragEnd,
36
+ expandIdPath,
37
+ selectionMode,
38
+ selectedItemIds,
39
+ rootItemId,
40
+ rootItemIds,
41
+ onSelectionChange,
42
+ filter,
43
+ templateIds,
44
+ className,
45
+ includeEmbeddedItems,
46
+ onDoubleClick,
47
+ selectPagesOnly,
48
+ renderNode,
49
+ onItemInserted,
50
+ language,
51
+ includeItemPath,
52
+ scrollToSelected,
53
+ }: {
54
+ isDraggable?: (item: ItemTreeNodeData) => boolean;
55
+ dragStart?: (item: ItemTreeNodeData) => void;
56
+ dragEnd?: () => void;
57
+ expandIdPath?: string;
58
+ selectionMode?: "single" | "multiple" | "none";
59
+ rootItemId?: string;
60
+ rootItemIds?: string[];
61
+ selectedItemIds?: string[];
62
+ onSelectionChange?: (itemIds: ItemTreeNodeData[]) => void;
63
+
64
+ filter?: (items: ItemTreeNodeData[]) => ItemTreeNodeData[];
65
+ templateIds?: string[];
66
+ className?: string;
67
+ includeEmbeddedItems?: boolean;
68
+ onDoubleClick?: (item: ItemTreeNodeData) => void;
69
+ selectPagesOnly?: boolean;
70
+ onItemInserted?: (item: ItemDescriptor) => void;
71
+ renderNode?: (
72
+ node: CustomTreeNode,
73
+ defaultRenderer: (node: CustomTreeNode) => React.ReactNode,
74
+ ) => React.ReactNode;
75
+ language: string;
76
+ includeItemPath?: boolean;
77
+ scrollToSelected?: boolean;
78
+ }) {
79
+ const [treeNodes, setTreeNodes] = useState<TreeNode[]>([]);
80
+
81
+ const [selectedKeys, setSelectedKeys] = useState<string[]>([]);
82
+ const [expandedKeys, setExpandedKeys] = useState<string[]>([]);
83
+ const treeContainerRef = useRef<HTMLDivElement>(null);
84
+ const editContext = useEditContext();
85
+
86
+ const nodeDictionary = useRef<{ [key: string]: CustomTreeNode }>({});
87
+ const cm = useRef<ContextMenu>(null);
88
+ const [menu, setMenu] = useState<MenuItem[]>([]);
89
+
90
+ const editContextRef = useEditContextRef();
91
+ const lastSelectedItemId = useRef<string | undefined>(undefined);
92
+ const [isInitialized, setIsInitialized] = useState(false);
93
+
94
+ const loadNodeChildren = useCallback(
95
+ async (node: CustomTreeNode): Promise<CustomTreeNode[]> => {
96
+ if (!editContext) return [];
97
+
98
+ let children = await getChildren(
99
+ node.key as string,
100
+ editContext.sessionId,
101
+ templateIds,
102
+ includeEmbeddedItems ?? false,
103
+ language,
104
+ includeItemPath ? "path,idPath" : undefined,
105
+ );
106
+
107
+ if (filter) children = filter(children);
108
+
109
+ return children.map((x) => ({
110
+ key: x.id,
111
+ label: x.name,
112
+ iconUrl: getAbsoluteIconUrl(x.icon),
113
+ isDraggable: isDraggable && isDraggable(x),
114
+ selectable: !selectPagesOnly || x.hasLayout,
115
+ data: x,
116
+ hasChildren: x.hasChildren,
117
+ parent: node,
118
+ }));
119
+ },
120
+ [
121
+ editContext,
122
+ filter,
123
+ includeEmbeddedItems,
124
+ isDraggable,
125
+ language,
126
+ selectPagesOnly,
127
+ templateIds,
128
+ includeItemPath,
129
+ ],
130
+ );
131
+
132
+ // const updateCorrespondingNode = useCallback(
133
+ // (x: FullItem, parent: CustomTreeNode) => {
134
+ // const node = nodeDictionary.current[x.id];
135
+ // if (!node) return;
136
+ // node.label = x.name;
137
+ // node.iconUrl = getAbsoluteIconUrl(x.icon);
138
+ // node.isDraggable =
139
+ // isDraggable &&
140
+ // isDraggable({
141
+ // ...x,
142
+ // isComponent: !x.hasLayout,
143
+ // thumbUrl: "",
144
+ // previewUrl: "",
145
+ // });
146
+ // node.selectable = !selectPagesOnly || x.hasLayout;
147
+ // node.data = x;
148
+ // node.hasChildren = x.hasChildren;
149
+ // node.parent = parent;
150
+ // },
151
+ // [isDraggable, selectPagesOnly],
152
+ // );
153
+
154
+ const loadNode = useCallback(
155
+ async (descriptor: ItemDescriptor) => {
156
+ const item = await editContext?.itemsRepository.getItem(descriptor);
157
+ if (!item) return;
158
+ return {
159
+ key: item.id,
160
+ label: item.name,
161
+ iconUrl: getAbsoluteIconUrl(item.icon),
162
+ isDraggable:
163
+ isDraggable &&
164
+ isDraggable({
165
+ ...item,
166
+ isComponent: !item.hasLayout,
167
+ thumbUrl: "",
168
+ previewUrl: "",
169
+ }),
170
+ selectable: !selectPagesOnly || item.hasLayout,
171
+ data: item,
172
+ hasChildren: item.hasChildren,
173
+ };
174
+ },
175
+ [editContext, isDraggable, selectPagesOnly],
176
+ );
177
+
178
+ const loadRootNodes = useCallback(
179
+ async (force: boolean = false) => {
180
+ if (!force && treeNodes.length > 0) return treeNodes;
181
+
182
+ if (rootItemId)
183
+ return await loadNodeChildren({ key: rootItemId, label: "" });
184
+
185
+ return await Promise.all(
186
+ rootItemIds
187
+ ?.map((rootItemId) =>
188
+ loadNode({
189
+ id: rootItemId,
190
+ language,
191
+ version: 0,
192
+ }),
193
+ )
194
+ .filter((x) => x) || [],
195
+ );
196
+ },
197
+ [loadNode, loadNodeChildren, rootItemId, rootItemIds, language, treeNodes],
198
+ );
199
+
200
+ const doPreloadNodes = useCallback(
201
+ async (expandedKeys: string[], treeNodes: TreeNode[]) => {
202
+ for (let i = 0; i < treeNodes.length; i++) {
203
+ const node = treeNodes[i];
204
+ if (!node) continue;
205
+ let children = node.children;
206
+ if (expandedKeys.includes(node.key as string) && !children) {
207
+ children = node.data.hasChildren ? await loadNodeChildren(node) : [];
208
+
209
+ node.children = children;
210
+ node.hasChildren = children.length > 0;
211
+ }
212
+ if (
213
+ expandedKeys.includes(node.key!) &&
214
+ children &&
215
+ children.length > 0
216
+ ) {
217
+ await doPreloadNodes(expandedKeys, children);
218
+ }
219
+ }
220
+ },
221
+ [loadNodeChildren],
222
+ );
223
+
224
+ const preLoadNodes = useCallback(
225
+ async (expandedKeys: string[], force: boolean) => {
226
+ const rootNodes = await loadRootNodes(force);
227
+ await doPreloadNodes(expandedKeys, rootNodes as TreeNode[]);
228
+
229
+ setTreeNodes([...(rootNodes.filter((x) => x) as TreeNode[])]);
230
+ },
231
+ [doPreloadNodes, loadRootNodes],
232
+ );
233
+
234
+ const refreshNode = useCallback(
235
+ async (node: TreeNode) => {
236
+ const item = await editContext?.itemsRepository.getItem(
237
+ node.data as ItemDescriptor,
238
+ );
239
+
240
+ if (item) {
241
+ // Update node properties directly without changing parent relationship
242
+ const customNode = node as CustomTreeNode;
243
+ customNode.label = item.name;
244
+ customNode.iconUrl = getAbsoluteIconUrl(item.icon);
245
+ customNode.isDraggable =
246
+ isDraggable &&
247
+ isDraggable({
248
+ ...item,
249
+ isComponent: !item.hasLayout,
250
+ thumbUrl: "",
251
+ previewUrl: "",
252
+ });
253
+ customNode.selectable = !selectPagesOnly || item.hasLayout;
254
+ customNode.data = item;
255
+ // Don't set hasChildren yet - we'll determine it after loading children
256
+
257
+ // Always reload children to get the current state, regardless of expand state
258
+ const newChildren = await loadNodeChildren(customNode);
259
+ customNode.children = newChildren;
260
+
261
+ // Update hasChildren based on actual children count
262
+ customNode.hasChildren = newChildren.length > 0;
263
+
264
+ // If node is not expanded, clear children to save memory but keep hasChildren correct
265
+ if (!expandedKeys?.includes(node.key as string)) {
266
+ customNode.children = undefined;
267
+ }
268
+ }
269
+ },
270
+ [
271
+ editContext?.itemsRepository,
272
+ expandedKeys,
273
+ loadNodeChildren,
274
+ isDraggable,
275
+ selectPagesOnly,
276
+ ],
277
+ );
278
+
279
+ // Handle root items or language changes
280
+ useEffect(() => {
281
+ setIsInitialized(true);
282
+ const load = async () => {
283
+ await preLoadNodes(expandedKeys || [], true);
284
+ };
285
+
286
+ load();
287
+ }, [language, rootItemId, rootItemIds]);
288
+
289
+ // Handle expanded keys changes without forcing reload
290
+ useEffect(() => {
291
+ if (isInitialized) {
292
+ preLoadNodes(expandedKeys || [], false);
293
+ }
294
+ }, [expandedKeys]);
295
+
296
+ useEffect(() => {
297
+ if (!editContext?.itemsRepository) return;
298
+ const unsubscribe = editContext.itemsRepository.subscribeItemsChanged(
299
+ async (items) => {
300
+ const nodes = items
301
+ .map((x) =>
302
+ x.action === "delete" && x.parentId
303
+ ? nodeDictionary.current[x.parentId]
304
+ : nodeDictionary.current[x.item.id],
305
+ )
306
+ .filter((x) => x);
307
+
308
+ if (nodes.length === 0) return;
309
+
310
+ await Promise.all(
311
+ nodes.map(async (x) => {
312
+ if (x) await refreshNode(x);
313
+ }),
314
+ );
315
+
316
+ setTreeNodes((prev) => [...prev]);
317
+ },
318
+ );
319
+ return () => {
320
+ unsubscribe();
321
+ };
322
+ }, [editContext?.itemsRepository, refreshNode]);
323
+
324
+ function getExpandedKeys() {
325
+ const newExpandedKeys: string[] = [];
326
+
327
+ if (rootItemId)
328
+ newExpandedKeys.push(rootItemId.toLowerCase().replace(/[{}]/g, ""));
329
+
330
+ if (!expandIdPath) return newExpandedKeys;
331
+
332
+ const idPathArray = expandIdPath.toLowerCase().split("/");
333
+ idPathArray.forEach((x) => {
334
+ if (x !== "") newExpandedKeys.push(x.substring(1, x.length - 1));
335
+ });
336
+ return newExpandedKeys;
337
+ }
338
+
339
+ useEffect(() => {
340
+ const newExpandedKeys = getExpandedKeys();
341
+
342
+ const hasNewKeys = newExpandedKeys.some(
343
+ (key) => !expandedKeys.includes(key),
344
+ );
345
+
346
+ if (hasNewKeys) {
347
+ setExpandedKeys([...new Set([...expandedKeys, ...newExpandedKeys])]);
348
+ }
349
+ }, [expandIdPath, rootItemId]);
350
+
351
+ useEffect(() => {
352
+ const newNodeDictionary: { [key: string]: CustomTreeNode } = {};
353
+ createMap(treeNodes, newNodeDictionary);
354
+ nodeDictionary.current = newNodeDictionary;
355
+ }, [treeNodes]);
356
+
357
+ function createMap(
358
+ treeNodes: CustomTreeNode[],
359
+ nodeDictionary: { [key: string]: CustomTreeNode },
360
+ ) {
361
+ treeNodes.forEach((n) => {
362
+ if (n.key) nodeDictionary[n.key] = n;
363
+ if (n.children) createMap(n.children, nodeDictionary);
364
+ });
365
+ }
366
+
367
+ const nodeTemplate = useCallback((node: CustomTreeNode) => {
368
+ let label = <span>{node.label}</span>;
369
+
370
+ if (node.url) {
371
+ label = (
372
+ <a
373
+ onDragStart={(e) => e.preventDefault()}
374
+ href={node.url}
375
+ className="text-primary font-semibold hover:underline"
376
+ >
377
+ {node.label}
378
+ </a>
379
+ );
380
+ }
381
+
382
+ const getNodeCursor = (node: CustomTreeNode) => {
383
+ if (node.isDraggable) return "cursor-grab";
384
+ return "";
385
+ };
386
+
387
+ return (
388
+ <span
389
+ // onDragStart={(event: React.DragEvent<HTMLDivElement>) => {
390
+ // handleDragStart(node, event);
391
+ // }}
392
+ // onDragEnd={handleDragEnd}
393
+ // draggable={node.isDraggable}
394
+ className={`flex items-center gap-2 text-xs ${getNodeCursor(node)} ${
395
+ node.className
396
+ }`}
397
+ >
398
+ <img
399
+ src={node.iconUrl}
400
+ style={{ height: "16px" }}
401
+ width="16"
402
+ height="16"
403
+ onDragStart={(e) => e.preventDefault()}
404
+ ></img>
405
+ {label}
406
+ </span>
407
+ );
408
+ }, []);
409
+
410
+ function handleDragStart(data: {
411
+ node: CustomTreeNode;
412
+ event: React.DragEvent;
413
+ isMultiSelect: boolean;
414
+ }): void {
415
+ const items = data.isMultiSelect
416
+ ? selectedItemIds
417
+ ?.map((x) => nodeDictionary.current[x]?.data)
418
+ .filter(Boolean)
419
+ : [data.node.data];
420
+
421
+ setTimeout(() => {
422
+ editContextRef.current?.dragStart({
423
+ type: "items",
424
+ typeId: data.node.data.templateId,
425
+ items: items,
426
+ });
427
+ }, 10);
428
+ //dragStart?.(data.node.data);
429
+ data.event.stopPropagation();
430
+ }
431
+
432
+ async function handleDrop(
433
+ parent: CustomTreeNode,
434
+ index: number,
435
+ event: React.DragEvent,
436
+ ): Promise<void> {
437
+ const object = editContextRef.current?.dragObject;
438
+ if (!object) return;
439
+
440
+ if (object.type === "items" && object.items) {
441
+ // Check insert options for validation
442
+ try {
443
+ const insertOptions = await loadInsertOptions({
444
+ id: parent.data.id,
445
+ language: parent.data.language || language,
446
+ version: parent.data.version || 0,
447
+ });
448
+
449
+ // Check if any of the dragged items have compatible insert options
450
+ const hasValidInsertOptions = object.items.some((item) =>
451
+ insertOptions.some(
452
+ (option) =>
453
+ option.id === (item as ItemTreeNodeData).templateId ||
454
+ option.id === object.typeId,
455
+ ),
456
+ );
457
+
458
+ if (!hasValidInsertOptions) {
459
+ const confirmed = await new Promise<boolean>((resolve) => {
460
+ editContext?.confirm({
461
+ message: (
462
+ <div>
463
+ The selected items may not be compatible with this location.
464
+ No matching insert options were found. Do you want to proceed
465
+ anyway?
466
+ </div>
467
+ ),
468
+ header: "Insert Options Warning",
469
+ icon: "pi pi-exclamation-triangle",
470
+ acceptLabel: "Proceed Anyway",
471
+ rejectLabel: "Cancel",
472
+ accept: () => resolve(true),
473
+ reject: () => resolve(false),
474
+ });
475
+ });
476
+
477
+ if (!confirmed) {
478
+ return;
479
+ }
480
+ }
481
+
482
+ // Proceed with the original drop logic
483
+ if (event.ctrlKey) {
484
+ editContext?.executeCommand({
485
+ command: editContext.configuration.commands.copyItems,
486
+ data: {
487
+ items: object.items as FullItem[],
488
+ targetItem: parent.data,
489
+ index,
490
+ },
491
+ });
492
+ } else {
493
+ editContext?.executeCommand({
494
+ command: editContext.configuration.commands.moveItems,
495
+ data: {
496
+ items: object.items as FullItem[],
497
+ targetItem: parent.data,
498
+ index,
499
+ },
500
+ });
501
+ }
502
+ } catch (error) {
503
+ console.error("Error validating insert options:", error);
504
+ editContext?.showToast({
505
+ severity: "error",
506
+ summary: "Validation Error",
507
+ detail: "Unable to validate insert options. Please try again.",
508
+ life: 5000,
509
+ });
510
+ return;
511
+ }
512
+ }
513
+ event.stopPropagation();
514
+ }
515
+
516
+ function handleDragEnd(): void {
517
+ editContextRef.current?.dragEnd();
518
+ if (dragEnd) dragEnd();
519
+ }
520
+
521
+ useEffect(() => {
522
+ setSelectedKeys(selectedItemIds || []);
523
+ }, [selectedItemIds]);
524
+
525
+ const itemCommands =
526
+ editContext?.configuration.commands.allItemCommands || [];
527
+
528
+ const onContextMenu = useCallback(
529
+ async (node: CustomTreeNode, originalEvent: React.MouseEvent) => {
530
+ const key = node.key;
531
+ let itemIds = selectedItemIds;
532
+
533
+ if (!itemIds || !itemIds.includes(key as string)) {
534
+ if (onSelectionChange && selectionMode === "single") {
535
+ onSelectionChange([node.data as ItemTreeNodeData]);
536
+ }
537
+ itemIds = [key as string];
538
+ }
539
+
540
+ const items = await editContext?.itemsRepository.getItems(
541
+ itemIds.map((x) => ({
542
+ id: x,
543
+ language: editContext.contentEditorItem?.language || "en",
544
+ version: 0,
545
+ })),
546
+ );
547
+
548
+ if (!items || items.length === 0) return;
549
+
550
+ const menuItems =
551
+ await editContext?.configuration.editor.contentTree.contextMenu.factory(
552
+ {
553
+ items: items,
554
+ editContext: editContext,
555
+ commandCallback: (command: ItemCommand, result: any) => {
556
+ cm.current?.hide(originalEvent);
557
+ if (command.id === "insertItem") {
558
+ const item = result as ItemDescriptor;
559
+ if (item) {
560
+ setExpandedKeys((keys) => [...keys, item.id]);
561
+ onItemInserted?.(item);
562
+ }
563
+ }
564
+ },
565
+ },
566
+ );
567
+
568
+ if (menuItems) setMenu(menuItems);
569
+
570
+ cm.current?.show(originalEvent);
571
+ },
572
+ [
573
+ itemCommands,
574
+ editContext,
575
+ selectedItemIds,
576
+ onSelectionChange,
577
+ selectionMode,
578
+ onItemInserted,
579
+ ],
580
+ );
581
+
582
+ // Memoize the toggle expand handler
583
+ const handleToggleExpand = useCallback((nodeKey: string) => {
584
+ setExpandedKeys((value) => {
585
+ if (value.includes(nodeKey)) return value.filter((x) => x !== nodeKey);
586
+ else return [...value, nodeKey];
587
+ });
588
+ }, []);
589
+
590
+ // Memoize the node selection handler
591
+ const handleNodeSelect = useCallback(
592
+ (nodeKey: string, ev: any) => {
593
+ let newSelection = [...selectedKeys];
594
+
595
+ if (
596
+ selectionMode === "multiple" &&
597
+ ev.shiftKey &&
598
+ lastSelectedItemId.current
599
+ ) {
600
+ // Get the nodes
601
+ const lastNode = nodeDictionary.current[lastSelectedItemId.current];
602
+ const currentNode = nodeDictionary.current[nodeKey];
603
+
604
+ // Check if both nodes exist and have the same parent
605
+ if (
606
+ lastNode &&
607
+ currentNode &&
608
+ lastNode.parent?.key === currentNode.parent?.key
609
+ ) {
610
+ const parentNode = lastNode.parent;
611
+
612
+ // Get all children of the parent
613
+ const siblings = parentNode?.children || [];
614
+
615
+ // Find indices of selected nodes
616
+ const siblingKeys = siblings.map((node) => node.key as string);
617
+ const lastNodeIndex = siblingKeys.indexOf(lastSelectedItemId.current);
618
+ const currentNodeIndex = siblingKeys.indexOf(nodeKey);
619
+
620
+ // Determine start and end indices
621
+ const startIdx = Math.min(lastNodeIndex, currentNodeIndex);
622
+ const endIdx = Math.max(lastNodeIndex, currentNodeIndex);
623
+
624
+ // Get all nodes between (inclusive)
625
+ const nodesToSelect = siblingKeys.slice(startIdx, endIdx + 1);
626
+
627
+ // Combine with existing selection
628
+ newSelection = [...new Set([...newSelection, ...nodesToSelect])];
629
+ } else {
630
+ // If not on same level, just select the current node
631
+ newSelection = [nodeKey];
632
+ }
633
+ } else if (selectionMode === "multiple" && ev.ctrlKey) {
634
+ newSelection = selectedKeys.includes(nodeKey)
635
+ ? selectedKeys.filter((x) => x !== nodeKey)
636
+ : [...selectedKeys, nodeKey];
637
+ } else {
638
+ lastSelectedItemId.current = nodeKey;
639
+ newSelection = [nodeKey];
640
+ }
641
+
642
+ setSelectedKeys(newSelection);
643
+ if (onSelectionChange) {
644
+ const selectedNodes = newSelection
645
+ .filter((key) => nodeDictionary.current[key])
646
+ .map((key) => nodeDictionary.current[key]);
647
+ onSelectionChange(selectedNodes.map((x) => x?.data));
648
+ }
649
+ },
650
+ [onSelectionChange, selectionMode, selectedKeys],
651
+ );
652
+
653
+ // Memoize the renderNode function
654
+ const memoizedRenderNode = useMemo(() => {
655
+ if (renderNode) {
656
+ return (node: CustomTreeNode) => renderNode(node, (n) => nodeTemplate(n));
657
+ }
658
+ return nodeTemplate;
659
+ }, [renderNode, nodeTemplate]);
660
+
661
+ // Memoize the double click handler
662
+ const handleDoubleClick = useCallback(
663
+ (node: CustomTreeNode) => {
664
+ onDoubleClick?.(node.data);
665
+ },
666
+ [onDoubleClick],
667
+ );
668
+
669
+ // Memoize treeNodes to prevent unnecessary re-renders
670
+ const memoizedTreeNodes = useMemo(() => treeNodes, [treeNodes]);
671
+
672
+ const loadChildren = useCallback(
673
+ async (node: CustomTreeNode, forceReload = false) => {
674
+ if (!node.children || forceReload) {
675
+ const nodes = await loadNodeChildren(node);
676
+ node.children = nodes;
677
+ setTreeNodes((prev) => [...prev]);
678
+ }
679
+ },
680
+ [loadNodeChildren],
681
+ );
682
+
683
+ const loadOnExpand = useCallback(
684
+ async (node: CustomTreeNode) => {
685
+ node.children = null;
686
+ setTreeNodes([...treeNodes]);
687
+ await loadChildren(node);
688
+ },
689
+ [loadChildren, treeNodes],
690
+ );
691
+
692
+ const handleDragOverZone = useCallback(
693
+ (node: CustomTreeNode | null, index: number, event: React.DragEvent) => {
694
+ event.dataTransfer.dropEffect = event.ctrlKey ? "copy" : "move";
695
+ return true;
696
+ },
697
+ [],
698
+ );
699
+ if (!memoizedTreeNodes.length)
700
+ return (
701
+ <div className="flex h-full items-center justify-center gap-2 bg-gray-50 text-xs text-gray-500">
702
+ <Spinner size="2xl" /> Loading...
703
+ </div>
704
+ );
705
+
706
+ return (
707
+ <>
708
+ <ContextMenu model={menu} ref={cm} className="text-sm" />
709
+ <div className={className} ref={treeContainerRef}>
710
+ <MemoizedPerfectTree
711
+ nodes={memoizedTreeNodes}
712
+ expandedKeys={expandedKeys}
713
+ onToggleExpand={handleToggleExpand}
714
+ renderNode={memoizedRenderNode}
715
+ onLazyLoad={loadOnExpand}
716
+ onDoubleClick={handleDoubleClick}
717
+ onSelect={handleNodeSelect}
718
+ selectedKeys={selectedKeys}
719
+ onContextMenu={onContextMenu}
720
+ enableDragAndDrop={true}
721
+ onStartDrag={handleDragStart}
722
+ onDragOverZone={handleDragOverZone}
723
+ onDragEnd={handleDragEnd}
724
+ isDragging={editContext?.dragObject !== undefined}
725
+ onDrop={(parent, index, event) => {
726
+ handleDrop(parent as CustomTreeNode, index, event);
727
+ }}
728
+ scrollToSelected={scrollToSelected}
729
+ />
730
+ </div>
731
+ </>
732
+ );
733
+ }