@strapi/content-type-builder 5.54.0 → 5.55.0

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 (208) hide show
  1. package/dist/admin/components/AIChat/hooks/useAIFetch.js +6 -2
  2. package/dist/admin/components/AIChat/hooks/useAIFetch.js.map +1 -1
  3. package/dist/admin/components/AIChat/hooks/useAIFetch.mjs +7 -3
  4. package/dist/admin/components/AIChat/hooks/useAIFetch.mjs.map +1 -1
  5. package/dist/admin/components/AIChat/hooks/usePrefetchAIToken.js +20 -0
  6. package/dist/admin/components/AIChat/hooks/usePrefetchAIToken.js.map +1 -0
  7. package/dist/admin/components/AIChat/hooks/usePrefetchAIToken.mjs +18 -0
  8. package/dist/admin/components/AIChat/hooks/usePrefetchAIToken.mjs.map +1 -0
  9. package/dist/admin/components/ContentTypeBuilderNav/ContentTypeBuilderNav.js +83 -59
  10. package/dist/admin/components/ContentTypeBuilderNav/ContentTypeBuilderNav.js.map +1 -1
  11. package/dist/admin/components/ContentTypeBuilderNav/ContentTypeBuilderNav.mjs +86 -62
  12. package/dist/admin/components/ContentTypeBuilderNav/ContentTypeBuilderNav.mjs.map +1 -1
  13. package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/DeleteFolderDialog.js +112 -0
  14. package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/DeleteFolderDialog.js.map +1 -0
  15. package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/DeleteFolderDialog.mjs +110 -0
  16. package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/DeleteFolderDialog.mjs.map +1 -0
  17. package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/FolderNameField.js +159 -0
  18. package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/FolderNameField.js.map +1 -0
  19. package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/FolderNameField.mjs +157 -0
  20. package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/FolderNameField.mjs.map +1 -0
  21. package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/FolderNavSection.js +311 -0
  22. package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/FolderNavSection.js.map +1 -0
  23. package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/FolderNavSection.mjs +309 -0
  24. package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/FolderNavSection.mjs.map +1 -0
  25. package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/SortableTreeRow.js +379 -0
  26. package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/SortableTreeRow.js.map +1 -0
  27. package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/SortableTreeRow.mjs +376 -0
  28. package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/SortableTreeRow.mjs.map +1 -0
  29. package/dist/admin/components/ContentTypeBuilderNav/hooks/useContentTypeBuilderMenu.js +172 -0
  30. package/dist/admin/components/ContentTypeBuilderNav/hooks/useContentTypeBuilderMenu.js.map +1 -0
  31. package/dist/admin/components/ContentTypeBuilderNav/hooks/useContentTypeBuilderMenu.mjs +166 -0
  32. package/dist/admin/components/ContentTypeBuilderNav/hooks/useContentTypeBuilderMenu.mjs.map +1 -0
  33. package/dist/admin/components/ContentTypeBuilderNav/hooks/useFolderActions.js +62 -0
  34. package/dist/admin/components/ContentTypeBuilderNav/hooks/useFolderActions.js.map +1 -0
  35. package/dist/admin/components/ContentTypeBuilderNav/hooks/useFolderActions.mjs +60 -0
  36. package/dist/admin/components/ContentTypeBuilderNav/hooks/useFolderActions.mjs.map +1 -0
  37. package/dist/admin/components/ContentTypeBuilderNav/hooks/useSortableTree.js +145 -0
  38. package/dist/admin/components/ContentTypeBuilderNav/hooks/useSortableTree.js.map +1 -0
  39. package/dist/admin/components/ContentTypeBuilderNav/hooks/useSortableTree.mjs +143 -0
  40. package/dist/admin/components/ContentTypeBuilderNav/hooks/useSortableTree.mjs.map +1 -0
  41. package/dist/admin/components/ContentTypeBuilderNav/lib/buildFolderTree.js +176 -0
  42. package/dist/admin/components/ContentTypeBuilderNav/lib/buildFolderTree.js.map +1 -0
  43. package/dist/admin/components/ContentTypeBuilderNav/lib/buildFolderTree.mjs +169 -0
  44. package/dist/admin/components/ContentTypeBuilderNav/lib/buildFolderTree.mjs.map +1 -0
  45. package/dist/admin/components/ContentTypeBuilderNav/lib/dropProjection.js +277 -0
  46. package/dist/admin/components/ContentTypeBuilderNav/lib/dropProjection.js.map +1 -0
  47. package/dist/admin/components/ContentTypeBuilderNav/lib/dropProjection.mjs +273 -0
  48. package/dist/admin/components/ContentTypeBuilderNav/lib/dropProjection.mjs.map +1 -0
  49. package/dist/admin/components/ContentTypeBuilderNav/lib/flatModel.js +62 -0
  50. package/dist/admin/components/ContentTypeBuilderNav/lib/flatModel.js.map +1 -0
  51. package/dist/admin/components/ContentTypeBuilderNav/lib/flatModel.mjs +58 -0
  52. package/dist/admin/components/ContentTypeBuilderNav/lib/flatModel.mjs.map +1 -0
  53. package/dist/admin/components/DataManager/DataManagerContext.js.map +1 -1
  54. package/dist/admin/components/DataManager/DataManagerContext.mjs.map +1 -1
  55. package/dist/admin/components/DataManager/DataManagerProvider.js +70 -8
  56. package/dist/admin/components/DataManager/DataManagerProvider.js.map +1 -1
  57. package/dist/admin/components/DataManager/DataManagerProvider.mjs +70 -8
  58. package/dist/admin/components/DataManager/DataManagerProvider.mjs.map +1 -1
  59. package/dist/admin/components/DataManager/reducer.js +411 -33
  60. package/dist/admin/components/DataManager/reducer.js.map +1 -1
  61. package/dist/admin/components/DataManager/reducer.mjs +411 -33
  62. package/dist/admin/components/DataManager/reducer.mjs.map +1 -1
  63. package/dist/admin/components/DataManager/utils/cleanData.js +11 -2
  64. package/dist/admin/components/DataManager/utils/cleanData.js.map +1 -1
  65. package/dist/admin/components/DataManager/utils/cleanData.mjs +11 -2
  66. package/dist/admin/components/DataManager/utils/cleanData.mjs.map +1 -1
  67. package/dist/admin/components/DataManager/utils/contentStructure.js +150 -0
  68. package/dist/admin/components/DataManager/utils/contentStructure.js.map +1 -0
  69. package/dist/admin/components/DataManager/utils/contentStructure.mjs +133 -0
  70. package/dist/admin/components/DataManager/utils/contentStructure.mjs.map +1 -0
  71. package/dist/admin/components/FolderSelect.js +200 -0
  72. package/dist/admin/components/FolderSelect.js.map +1 -0
  73. package/dist/admin/components/FolderSelect.mjs +198 -0
  74. package/dist/admin/components/FolderSelect.mjs.map +1 -0
  75. package/dist/admin/components/FormModal/FormModal.js +23 -2
  76. package/dist/admin/components/FormModal/FormModal.js.map +1 -1
  77. package/dist/admin/components/FormModal/FormModal.mjs +23 -2
  78. package/dist/admin/components/FormModal/FormModal.mjs.map +1 -1
  79. package/dist/admin/components/FormModal/contentType/contentTypeForm.js +15 -18
  80. package/dist/admin/components/FormModal/contentType/contentTypeForm.js.map +1 -1
  81. package/dist/admin/components/FormModal/contentType/contentTypeForm.mjs +15 -18
  82. package/dist/admin/components/FormModal/contentType/contentTypeForm.mjs.map +1 -1
  83. package/dist/admin/components/FormModal/reducer.js +6 -0
  84. package/dist/admin/components/FormModal/reducer.js.map +1 -1
  85. package/dist/admin/components/FormModal/reducer.mjs +6 -0
  86. package/dist/admin/components/FormModal/reducer.mjs.map +1 -1
  87. package/dist/admin/pages/App/index.js +2 -5
  88. package/dist/admin/pages/App/index.js.map +1 -1
  89. package/dist/admin/pages/App/index.mjs +2 -5
  90. package/dist/admin/pages/App/index.mjs.map +1 -1
  91. package/dist/admin/src/components/AIChat/hooks/usePrefetchAIToken.d.ts +1 -0
  92. package/dist/admin/src/components/ContentTypeBuilderNav/components/FolderNav/DeleteFolderDialog.d.ts +16 -0
  93. package/dist/admin/src/components/ContentTypeBuilderNav/components/FolderNav/FolderNameField.d.ts +12 -0
  94. package/dist/admin/src/components/ContentTypeBuilderNav/components/FolderNav/FolderNavSection.d.ts +14 -0
  95. package/dist/admin/src/components/ContentTypeBuilderNav/components/FolderNav/SortableTreeRow.d.ts +38 -0
  96. package/dist/admin/src/components/ContentTypeBuilderNav/{useContentTypeBuilderMenu.d.ts → hooks/useContentTypeBuilderMenu.d.ts} +17 -7
  97. package/dist/admin/src/components/ContentTypeBuilderNav/hooks/useFolderActions.d.ts +42 -0
  98. package/dist/admin/src/components/ContentTypeBuilderNav/hooks/useSortableTree.d.ts +53 -0
  99. package/dist/admin/src/components/ContentTypeBuilderNav/lib/buildFolderTree.d.ts +42 -0
  100. package/dist/admin/src/components/ContentTypeBuilderNav/lib/dropProjection.d.ts +58 -0
  101. package/dist/admin/src/components/ContentTypeBuilderNav/lib/flatModel.d.ts +18 -0
  102. package/dist/admin/src/components/DataManager/DataManagerContext.d.ts +41 -0
  103. package/dist/admin/src/components/DataManager/reducer.d.ts +105 -1
  104. package/dist/admin/src/components/DataManager/utils/cleanData.d.ts +5 -1
  105. package/dist/admin/src/components/DataManager/utils/contentStructure.d.ts +68 -0
  106. package/dist/admin/src/components/FolderSelect.d.ts +18 -0
  107. package/dist/admin/src/components/FormModal/contentType/contentTypeForm.d.ts +0 -21
  108. package/dist/admin/src/components/FormModal/forms/forms.d.ts +0 -10
  109. package/dist/admin/src/components/FormModal/reducer.d.ts +7 -0
  110. package/dist/admin/src/utils/folderTelemetry.d.ts +5 -0
  111. package/dist/admin/translations/en.json.js +23 -0
  112. package/dist/admin/translations/en.json.js.map +1 -1
  113. package/dist/admin/translations/en.json.mjs +23 -0
  114. package/dist/admin/translations/en.json.mjs.map +1 -1
  115. package/dist/admin/utils/folderTelemetry.js +19 -0
  116. package/dist/admin/utils/folderTelemetry.js.map +1 -0
  117. package/dist/admin/utils/folderTelemetry.mjs +16 -0
  118. package/dist/admin/utils/folderTelemetry.mjs.map +1 -0
  119. package/dist/server/controllers/schema.js +1 -1
  120. package/dist/server/controllers/schema.js.map +1 -1
  121. package/dist/server/controllers/schema.mjs +1 -1
  122. package/dist/server/controllers/schema.mjs.map +1 -1
  123. package/dist/server/controllers/validation/common.js +3 -0
  124. package/dist/server/controllers/validation/common.js.map +1 -1
  125. package/dist/server/controllers/validation/common.mjs +3 -1
  126. package/dist/server/controllers/validation/common.mjs.map +1 -1
  127. package/dist/server/controllers/validation/content-structure.js +177 -0
  128. package/dist/server/controllers/validation/content-structure.js.map +1 -0
  129. package/dist/server/controllers/validation/content-structure.mjs +173 -0
  130. package/dist/server/controllers/validation/content-structure.mjs.map +1 -0
  131. package/dist/server/controllers/validation/schema.js +3 -1
  132. package/dist/server/controllers/validation/schema.js.map +1 -1
  133. package/dist/server/controllers/validation/schema.mjs +3 -1
  134. package/dist/server/controllers/validation/schema.mjs.map +1 -1
  135. package/dist/server/register.js +1 -1
  136. package/dist/server/register.js.map +1 -1
  137. package/dist/server/register.mjs +1 -1
  138. package/dist/server/register.mjs.map +1 -1
  139. package/dist/server/routes/content-api.js +4 -4
  140. package/dist/server/routes/content-api.js.map +1 -1
  141. package/dist/server/routes/content-api.mjs +4 -4
  142. package/dist/server/routes/content-api.mjs.map +1 -1
  143. package/dist/server/services/api-handler.js +37 -4
  144. package/dist/server/services/api-handler.js.map +1 -1
  145. package/dist/server/services/api-handler.mjs +36 -5
  146. package/dist/server/services/api-handler.mjs.map +1 -1
  147. package/dist/server/services/content-structure.js +174 -0
  148. package/dist/server/services/content-structure.js.map +1 -0
  149. package/dist/server/services/content-structure.mjs +172 -0
  150. package/dist/server/services/content-structure.mjs.map +1 -0
  151. package/dist/server/services/content-types.js +196 -44
  152. package/dist/server/services/content-types.js.map +1 -1
  153. package/dist/server/services/content-types.mjs +195 -45
  154. package/dist/server/services/content-types.mjs.map +1 -1
  155. package/dist/server/services/index.js +3 -1
  156. package/dist/server/services/index.js.map +1 -1
  157. package/dist/server/services/index.mjs +3 -1
  158. package/dist/server/services/index.mjs.map +1 -1
  159. package/dist/server/services/schema-builder/index.js +4 -2
  160. package/dist/server/services/schema-builder/index.js.map +1 -1
  161. package/dist/server/services/schema-builder/index.mjs +4 -2
  162. package/dist/server/services/schema-builder/index.mjs.map +1 -1
  163. package/dist/server/services/schema-builder/schema-handler.js +16 -16
  164. package/dist/server/services/schema-builder/schema-handler.js.map +1 -1
  165. package/dist/server/services/schema-builder/schema-handler.mjs +16 -16
  166. package/dist/server/services/schema-builder/schema-handler.mjs.map +1 -1
  167. package/dist/server/services/schema-mutation.js +46 -0
  168. package/dist/server/services/schema-mutation.js.map +1 -0
  169. package/dist/server/services/schema-mutation.mjs +43 -0
  170. package/dist/server/services/schema-mutation.mjs.map +1 -0
  171. package/dist/server/services/schema.js +134 -79
  172. package/dist/server/services/schema.js.map +1 -1
  173. package/dist/server/services/schema.mjs +135 -80
  174. package/dist/server/services/schema.mjs.map +1 -1
  175. package/dist/server/src/controllers/schema.d.ts.map +1 -1
  176. package/dist/server/src/controllers/validation/common.d.ts +1 -0
  177. package/dist/server/src/controllers/validation/common.d.ts.map +1 -1
  178. package/dist/server/src/controllers/validation/content-structure.d.ts +56 -0
  179. package/dist/server/src/controllers/validation/content-structure.d.ts.map +1 -0
  180. package/dist/server/src/controllers/validation/schema.d.ts +35 -0
  181. package/dist/server/src/controllers/validation/schema.d.ts.map +1 -1
  182. package/dist/server/src/index.d.ts +3 -0
  183. package/dist/server/src/index.d.ts.map +1 -1
  184. package/dist/server/src/routes/content-api.d.ts.map +1 -1
  185. package/dist/server/src/services/api-handler.d.ts +13 -1
  186. package/dist/server/src/services/api-handler.d.ts.map +1 -1
  187. package/dist/server/src/services/content-structure.d.ts +37 -0
  188. package/dist/server/src/services/content-structure.d.ts.map +1 -0
  189. package/dist/server/src/services/content-types.d.ts +4 -0
  190. package/dist/server/src/services/content-types.d.ts.map +1 -1
  191. package/dist/server/src/services/index.d.ts +4 -0
  192. package/dist/server/src/services/index.d.ts.map +1 -1
  193. package/dist/server/src/services/schema-builder/index.d.ts +3 -1
  194. package/dist/server/src/services/schema-builder/index.d.ts.map +1 -1
  195. package/dist/server/src/services/schema-builder/schema-handler.d.ts.map +1 -1
  196. package/dist/server/src/services/schema-mutation.d.ts +26 -0
  197. package/dist/server/src/services/schema-mutation.d.ts.map +1 -0
  198. package/dist/server/src/services/schema.d.ts +1 -0
  199. package/dist/server/src/services/schema.d.ts.map +1 -1
  200. package/dist/server/src/utils/index.d.ts +2 -0
  201. package/dist/server/src/utils/index.d.ts.map +1 -1
  202. package/dist/server/utils/index.js.map +1 -1
  203. package/dist/server/utils/index.mjs.map +1 -1
  204. package/package.json +6 -6
  205. package/dist/admin/components/ContentTypeBuilderNav/useContentTypeBuilderMenu.js +0 -157
  206. package/dist/admin/components/ContentTypeBuilderNav/useContentTypeBuilderMenu.js.map +0 -1
  207. package/dist/admin/components/ContentTypeBuilderNav/useContentTypeBuilderMenu.mjs +0 -151
  208. package/dist/admin/components/ContentTypeBuilderNav/useContentTypeBuilderMenu.mjs.map +0 -1
@@ -0,0 +1,143 @@
1
+ import { useState, useRef, useCallback } from 'react';
2
+ import { pointerWithin, closestCenter, useSensors, useSensor, PointerSensor } from '@dnd-kit/core';
3
+ import { resolveDropTarget } from '../lib/dropProjection.mjs';
4
+ import { removeSubtree } from '../lib/flatModel.mjs';
5
+
6
+ // How long the pointer must dwell over a closed folder's nest zone before it
7
+ // springs open, letting you drop deeper.
8
+ const SPRING_OPEN_MS = 700;
9
+ const useHoverFolderOpen = (onExpandFolder)=>{
10
+ const springRef = useRef({
11
+ folderId: null
12
+ });
13
+ const cancelFolderOpen = ()=>{
14
+ clearTimeout(springRef.current.timer);
15
+ springRef.current = {
16
+ folderId: null
17
+ };
18
+ };
19
+ // Arm (once per folder) a timer that springs the closed folder open after a
20
+ // dwell, so the user can drop into a deeper level.
21
+ const scheduleFolderOpen = (folderId)=>{
22
+ if (springRef.current.folderId === folderId) {
23
+ return;
24
+ }
25
+ cancelFolderOpen();
26
+ springRef.current.folderId = folderId;
27
+ springRef.current.timer = setTimeout(()=>{
28
+ onExpandFolder(folderId);
29
+ springRef.current.folderId = null;
30
+ }, SPRING_OPEN_MS);
31
+ };
32
+ return {
33
+ scheduleFolderOpen,
34
+ cancelFolderOpen
35
+ };
36
+ };
37
+ /**
38
+ * The drag-and-drop engine for a flattened folder tree: it owns the drag state
39
+ * machine, pointer-vs-row geometry, spring-open timers and drop projection, and
40
+ * returns the props a `DndContext` + `SortableContext` need plus the resolved
41
+ * drop indicator. It carries no domain logic — nesting rules, the move itself,
42
+ * and expansion are supplied by the caller through {@link UseSortableTreeOptions}.
43
+ */ const useSortableTree = ({ onExpandFolder, collapsed, disabled, onDrop, canDrop = ()=>true, items })=>{
44
+ const [projected, setProjected] = useState(null);
45
+ const [activeId, setActiveId] = useState(null);
46
+ const pointerRef = useRef(null);
47
+ const { scheduleFolderOpen, cancelFolderOpen } = useHoverFolderOpen(onExpandFolder);
48
+ const collisionDetection = useCallback((args)=>{
49
+ // Captured here (onDragMove has no pointer position) to decide whether the
50
+ // pointer sits over a folder's nest zone (its middle third).
51
+ pointerRef.current = args.pointerCoordinates;
52
+ // Resolve `over` from the pointer, NOT the drag overlay's centre. dnd-kit
53
+ // keeps the grab point under the cursor, so the overlay centre sits up to
54
+ // half a row from the pointer depending on where the row was picked up; with
55
+ // closest-centre, `over` and the pointer-based nest-zone check then disagree
56
+ // and nesting into a folder misfires. Fall back to closest-centre only when
57
+ // the pointer is outside every row (the list's padding / edges). A static
58
+ // list keeps both results stable, so no hysteresis is needed.
59
+ const withinPointer = pointerWithin(args);
60
+ return withinPointer.length > 0 ? withinPointer : closestCenter(args);
61
+ }, []);
62
+ // While dragging a folder, its descendents are hidden so it cannot nest in itself.
63
+ const renderedItems = activeId ? removeSubtree(items, activeId) : items;
64
+ const activeItem = items.find((item)=>item.id === activeId) ?? null;
65
+ const sensors = useSensors(useSensor(PointerSensor, {
66
+ activationConstraint: {
67
+ distance: 8
68
+ }
69
+ }));
70
+ const onDragStart = (event)=>{
71
+ if (disabled) {
72
+ return;
73
+ }
74
+ cancelFolderOpen();
75
+ setActiveId(String(event.active.id));
76
+ setProjected(null);
77
+ };
78
+ const onDragCancel = ()=>{
79
+ resetDrag();
80
+ };
81
+ const onDragMove = (event)=>{
82
+ if (!activeId || !event.over) {
83
+ return;
84
+ }
85
+ const target = resolveDropTarget(renderedItems, {
86
+ pointer: pointerRef.current ?? undefined,
87
+ overId: String(event.over.id),
88
+ overRect: event.over.rect,
89
+ offsetX: event.delta.x,
90
+ activeId
91
+ });
92
+ if (target && activeItem && !canDrop(activeItem.node, target)) {
93
+ setProjected(null);
94
+ cancelFolderOpen();
95
+ return;
96
+ }
97
+ setProjected(target);
98
+ if (target?.kind === 'nest' && collapsed.has(target.folderId)) {
99
+ scheduleFolderOpen(target.folderId);
100
+ } else {
101
+ cancelFolderOpen();
102
+ }
103
+ };
104
+ const resetDrag = ()=>{
105
+ cancelFolderOpen();
106
+ setActiveId(null);
107
+ setProjected(null);
108
+ };
109
+ const onDragEnd = ()=>{
110
+ if (disabled) {
111
+ return;
112
+ }
113
+ const item = activeItem;
114
+ const target = projected;
115
+ resetDrag();
116
+ if (!item || !target) {
117
+ return;
118
+ }
119
+ onDrop(item.node, target);
120
+ };
121
+ const dropLine = projected?.kind === 'insert' && projected.line ? {
122
+ ...projected.line,
123
+ depth: projected.depth
124
+ } : null;
125
+ const dropTargetId = projected?.kind === 'nest' ? `folder:${projected.folderId}` : null;
126
+ const sortingStrategy = ()=>null;
127
+ return {
128
+ collisionDetection,
129
+ sortingStrategy,
130
+ renderedItems,
131
+ dropTargetId,
132
+ activeItem,
133
+ dropLine,
134
+ sensors,
135
+ onDragCancel,
136
+ onDragStart,
137
+ onDragMove,
138
+ onDragEnd
139
+ };
140
+ };
141
+
142
+ export { useSortableTree };
143
+ //# sourceMappingURL=useSortableTree.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useSortableTree.mjs","sources":["../../../../../admin/src/components/ContentTypeBuilderNav/hooks/useSortableTree.ts"],"sourcesContent":["import { useCallback, useRef, useState } from 'react';\n\nimport { closestCenter, PointerSensor, pointerWithin, useSensor, useSensors } from '@dnd-kit/core';\n\nimport { resolveDropTarget } from '../lib/dropProjection';\nimport { removeSubtree } from '../lib/flatModel';\n\nimport type { FolderTreeNode } from '../lib/buildFolderTree';\nimport type { DropTarget } from '../lib/dropProjection';\nimport type { FlatItem } from '../lib/flatModel';\nimport type {\n CollisionDetection,\n DragMoveEvent,\n DragStartEvent,\n SensorDescriptor,\n} from '@dnd-kit/core';\nimport type { SortingStrategy } from '@dnd-kit/sortable';\n\n// How long the pointer must dwell over a closed folder's nest zone before it\n// springs open, letting you drop deeper.\nconst SPRING_OPEN_MS = 700;\n\n/** The insertion line to paint, plus the depth its left edge indents to. */\nexport type DropLine = { anchorId: string; edge: 'top' | 'bottom'; depth: number };\n\ninterface UseSortableTreeOptions {\n /** Commit a drop. Called only for a real move, with the node and its resolved target. */\n onDrop: (activeNode: FolderTreeNode, target: DropTarget) => void;\n /**\n * Function which determines whether the dragged node may land on the resolved target.\n */\n canDrop?: (activeNode: FolderTreeNode, target: DropTarget) => boolean;\n /** Spring-open a closed folder held under the pointer, so the user can drop deeper. */\n onExpandFolder: (folderId: string) => void;\n /** Ids of collapsed folders — used to tell a closed folder (springs open) from an empty one. */\n collapsed: Set<string>;\n /** When true the contentStructure tree is read-only. */\n disabled: boolean;\n /** The full flattened, visible tree (descendants of collapsed folders omitted). */\n items: FlatItem[];\n}\n\nexport interface UseSortableTreeResult {\n collisionDetection: CollisionDetection;\n sensors: SensorDescriptor<object>[];\n sortingStrategy: SortingStrategy;\n /** The items to render (the dragged folder's descendants are hidden mid-drag). */\n renderedItems: FlatItem[];\n /** The row being dragged, or null. */\n activeItem: FlatItem | null;\n /** Folder row (by dnd id) to highlight as the nest target — no line is drawn for it. */\n dropTargetId: string | null;\n /** The insertion line to paint, or null when nesting into a highlighted folder. */\n dropLine: DropLine | null;\n onDragStart: (event: DragStartEvent) => void;\n onDragMove: (event: DragMoveEvent) => void;\n onDragCancel: () => void;\n onDragEnd: () => void;\n}\n\ntype SpringRef = { folderId: string | null; timer?: ReturnType<typeof setTimeout> };\n\nconst useHoverFolderOpen = (onExpandFolder: (folderId: string) => void) => {\n const springRef = useRef<SpringRef>({ folderId: null });\n\n const cancelFolderOpen = () => {\n clearTimeout(springRef.current.timer);\n springRef.current = { folderId: null };\n };\n\n // Arm (once per folder) a timer that springs the closed folder open after a\n // dwell, so the user can drop into a deeper level.\n const scheduleFolderOpen = (folderId: string) => {\n if (springRef.current.folderId === folderId) {\n return;\n }\n\n cancelFolderOpen();\n springRef.current.folderId = folderId;\n\n springRef.current.timer = setTimeout(() => {\n onExpandFolder(folderId);\n springRef.current.folderId = null;\n }, SPRING_OPEN_MS);\n };\n\n return { scheduleFolderOpen, cancelFolderOpen };\n};\n\n/**\n * The drag-and-drop engine for a flattened folder tree: it owns the drag state\n * machine, pointer-vs-row geometry, spring-open timers and drop projection, and\n * returns the props a `DndContext` + `SortableContext` need plus the resolved\n * drop indicator. It carries no domain logic — nesting rules, the move itself,\n * and expansion are supplied by the caller through {@link UseSortableTreeOptions}.\n */\nexport const useSortableTree = ({\n onExpandFolder,\n collapsed,\n disabled,\n onDrop,\n canDrop = () => true,\n items,\n}: UseSortableTreeOptions): UseSortableTreeResult => {\n const [projected, setProjected] = useState<DropTarget | null>(null);\n const [activeId, setActiveId] = useState<string | null>(null);\n\n const pointerRef = useRef<{ x: number; y: number } | null>(null);\n const { scheduleFolderOpen, cancelFolderOpen } = useHoverFolderOpen(onExpandFolder);\n\n const collisionDetection = useCallback<CollisionDetection>((args) => {\n // Captured here (onDragMove has no pointer position) to decide whether the\n // pointer sits over a folder's nest zone (its middle third).\n pointerRef.current = args.pointerCoordinates;\n\n // Resolve `over` from the pointer, NOT the drag overlay's centre. dnd-kit\n // keeps the grab point under the cursor, so the overlay centre sits up to\n // half a row from the pointer depending on where the row was picked up; with\n // closest-centre, `over` and the pointer-based nest-zone check then disagree\n // and nesting into a folder misfires. Fall back to closest-centre only when\n // the pointer is outside every row (the list's padding / edges). A static\n // list keeps both results stable, so no hysteresis is needed.\n const withinPointer = pointerWithin(args);\n return withinPointer.length > 0 ? withinPointer : closestCenter(args);\n }, []);\n\n // While dragging a folder, its descendents are hidden so it cannot nest in itself.\n const renderedItems = activeId ? removeSubtree(items, activeId) : items;\n const activeItem = items.find((item) => item.id === activeId) ?? null;\n\n const sensors = useSensors(useSensor(PointerSensor, { activationConstraint: { distance: 8 } }));\n\n const onDragStart = (event: DragStartEvent) => {\n if (disabled) {\n return;\n }\n\n cancelFolderOpen();\n setActiveId(String(event.active.id));\n setProjected(null);\n };\n\n const onDragCancel = () => {\n resetDrag();\n };\n\n const onDragMove = (event: DragMoveEvent) => {\n if (!activeId || !event.over) {\n return;\n }\n\n const target = resolveDropTarget(renderedItems, {\n pointer: pointerRef.current ?? undefined,\n overId: String(event.over.id),\n overRect: event.over.rect,\n offsetX: event.delta.x,\n activeId,\n });\n\n if (target && activeItem && !canDrop(activeItem.node, target)) {\n setProjected(null);\n cancelFolderOpen();\n return;\n }\n\n setProjected(target);\n\n if (target?.kind === 'nest' && collapsed.has(target.folderId)) {\n scheduleFolderOpen(target.folderId);\n } else {\n cancelFolderOpen();\n }\n };\n\n const resetDrag = () => {\n cancelFolderOpen();\n setActiveId(null);\n setProjected(null);\n };\n\n const onDragEnd = () => {\n if (disabled) {\n return;\n }\n\n const item = activeItem;\n const target = projected;\n\n resetDrag();\n\n if (!item || !target) {\n return;\n }\n\n onDrop(item.node, target);\n };\n\n const dropLine =\n projected?.kind === 'insert' && projected.line\n ? { ...projected.line, depth: projected.depth }\n : null;\n\n const dropTargetId = projected?.kind === 'nest' ? `folder:${projected.folderId}` : null;\n\n const sortingStrategy: SortingStrategy = () => null;\n\n return {\n collisionDetection,\n sortingStrategy,\n renderedItems,\n dropTargetId,\n activeItem,\n dropLine,\n sensors,\n onDragCancel,\n onDragStart,\n onDragMove,\n onDragEnd,\n };\n};\n"],"names":["SPRING_OPEN_MS","useHoverFolderOpen","onExpandFolder","springRef","useRef","folderId","cancelFolderOpen","clearTimeout","current","timer","scheduleFolderOpen","setTimeout","useSortableTree","collapsed","disabled","onDrop","canDrop","items","projected","setProjected","useState","activeId","setActiveId","pointerRef","collisionDetection","useCallback","args","pointerCoordinates","withinPointer","pointerWithin","length","closestCenter","renderedItems","removeSubtree","activeItem","find","item","id","sensors","useSensors","useSensor","PointerSensor","activationConstraint","distance","onDragStart","event","String","active","onDragCancel","resetDrag","onDragMove","over","target","resolveDropTarget","pointer","undefined","overId","overRect","rect","offsetX","delta","x","node","kind","has","onDragEnd","dropLine","line","depth","dropTargetId","sortingStrategy"],"mappings":"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"}
@@ -0,0 +1,176 @@
1
+ 'use strict';
2
+
3
+ var contentStructure = require('../../DataManager/utils/contentStructure.js');
4
+
5
+ function buildSectionTree(section, links, compareTitle) {
6
+ const groups = section.groups;
7
+ const groupById = new Map(groups.map((group)=>[
8
+ group.id,
9
+ group
10
+ ]));
11
+ const linkByUid = new Map(links.map((link)=>[
12
+ link.uid,
13
+ link
14
+ ]));
15
+ const childGroupIds = new Set();
16
+ for (const group of groups){
17
+ for (const child of group.children){
18
+ if (child.type === 'group') {
19
+ childGroupIds.add(child.id);
20
+ }
21
+ }
22
+ }
23
+ const grouped = new Set();
24
+ const buildFolder = (group, depth)=>{
25
+ const children = [];
26
+ for (const child of group.children){
27
+ if (child.type === 'group') {
28
+ const subGroup = groupById.get(child.id);
29
+ if (!subGroup) {
30
+ continue;
31
+ }
32
+ children.push(buildFolder(subGroup, depth + 1));
33
+ continue;
34
+ }
35
+ const link = linkByUid.get(child.uid);
36
+ if (!link) {
37
+ continue;
38
+ }
39
+ grouped.add(child.uid);
40
+ children.push({
41
+ type: 'contentType',
42
+ status: link.status,
43
+ parentId: group.id,
44
+ title: link.title,
45
+ depth: depth + 1,
46
+ uid: link.uid,
47
+ to: link.to
48
+ });
49
+ }
50
+ return {
51
+ type: 'folder',
52
+ parentId: group.parent,
53
+ status: group.status,
54
+ name: group.name,
55
+ id: group.id,
56
+ children,
57
+ depth
58
+ };
59
+ };
60
+ const rootFolders = groups.filter((group)=>!childGroupIds.has(group.id)).map((group)=>buildFolder(group, 0));
61
+ const ungrouped = links.filter((link)=>!grouped.has(link.uid)).sort((a, b)=>compareTitle(a.title, b.title)).map((link)=>({
62
+ type: 'contentType',
63
+ status: link.status,
64
+ title: link.title,
65
+ parentId: null,
66
+ uid: link.uid,
67
+ to: link.to,
68
+ depth: 0
69
+ }));
70
+ return [
71
+ ...ungrouped,
72
+ ...rootFolders
73
+ ];
74
+ }
75
+ function filterTree(nodes, matches) {
76
+ const result = [];
77
+ for (const node of nodes){
78
+ if (node.type === 'contentType') {
79
+ if (matches(node.title) || matches(node.uid)) {
80
+ result.push(node);
81
+ }
82
+ continue;
83
+ }
84
+ if (matches(node.name)) {
85
+ result.push(node);
86
+ continue;
87
+ }
88
+ const filteredChildren = filterTree(node.children, matches);
89
+ if (filteredChildren.length > 0) {
90
+ result.push({
91
+ ...node,
92
+ children: filteredChildren
93
+ });
94
+ }
95
+ }
96
+ return result;
97
+ }
98
+ function subtreeFolderHeight(folder) {
99
+ let deepestChild = 0;
100
+ for (const child of folder.children){
101
+ if (child.type === 'folder') {
102
+ deepestChild = Math.max(deepestChild, subtreeFolderHeight(child));
103
+ }
104
+ }
105
+ return 1 + deepestChild;
106
+ }
107
+ /**
108
+ * Whether moving `folder` under a target (a folder, or `null` for the section
109
+ * root) would keep the resulting subtree within {@link MAX_FOLDER_DEPTH}.
110
+ */ function canNestFolderAt(folder, target) {
111
+ const targetDomainDepth = target ? target.depth + 1 : 0;
112
+ const deepestResult = targetDomainDepth + subtreeFolderHeight(folder);
113
+ return deepestResult <= contentStructure.MAX_FOLDER_DEPTH;
114
+ }
115
+ function countSubtree(section, folderId, countableUids) {
116
+ const groupById = new Map(section.groups.map((group)=>[
117
+ group.id,
118
+ group
119
+ ]));
120
+ let contentTypes = 0;
121
+ let subfolders = 0;
122
+ const walk = (group)=>{
123
+ for (const child of group.children){
124
+ if (child.type === 'contentType') {
125
+ if (countableUids.has(child.uid)) {
126
+ contentTypes += 1;
127
+ }
128
+ continue;
129
+ }
130
+ const childGroup = groupById.get(child.id);
131
+ if (!childGroup) {
132
+ continue;
133
+ }
134
+ subfolders += 1;
135
+ walk(childGroup);
136
+ }
137
+ };
138
+ const root = groupById.get(folderId);
139
+ if (root) {
140
+ walk(root);
141
+ }
142
+ return {
143
+ contentTypes,
144
+ subfolders
145
+ };
146
+ }
147
+ function collectSubtreeContentTypeUids(section, folderId) {
148
+ const groupById = new Map(section.groups.map((group)=>[
149
+ group.id,
150
+ group
151
+ ]));
152
+ const uids = [];
153
+ const walk = (id)=>{
154
+ const group = groupById.get(id);
155
+ if (!group) {
156
+ return;
157
+ }
158
+ for (const child of group.children){
159
+ if (child.type === 'contentType') {
160
+ uids.push(child.uid);
161
+ continue;
162
+ }
163
+ walk(child.id);
164
+ }
165
+ };
166
+ walk(folderId);
167
+ return uids;
168
+ }
169
+
170
+ exports.buildSectionTree = buildSectionTree;
171
+ exports.canNestFolderAt = canNestFolderAt;
172
+ exports.collectSubtreeContentTypeUids = collectSubtreeContentTypeUids;
173
+ exports.countSubtree = countSubtree;
174
+ exports.filterTree = filterTree;
175
+ exports.subtreeFolderHeight = subtreeFolderHeight;
176
+ //# sourceMappingURL=buildFolderTree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildFolderTree.js","sources":["../../../../../admin/src/components/ContentTypeBuilderNav/lib/buildFolderTree.ts"],"sourcesContent":["import { MAX_FOLDER_DEPTH } from '../../DataManager/utils/contentStructure';\n\nimport type { Status } from '../../../types';\nimport type {\n ContentStructureSection,\n GroupStatus,\n} from '../../DataManager/utils/contentStructure';\nimport type { UID } from '@strapi/types';\n\nexport type ContentTypeLink = {\n uid: UID.ContentType;\n to: string;\n title: string;\n status: Status;\n};\n\nexport type FolderNode = {\n type: 'folder';\n id: string;\n name: string;\n status: GroupStatus;\n depth: number;\n parentId: string | null;\n children: FolderTreeNode[];\n};\n\nexport type ContentTypeNode = {\n type: 'contentType';\n uid: UID.ContentType;\n to: string;\n title: string;\n status: Status;\n depth: number;\n parentId: string | null;\n};\n\nexport type FolderTreeNode = FolderNode | ContentTypeNode;\n\nexport function buildSectionTree(\n section: ContentStructureSection,\n links: ContentTypeLink[],\n compareTitle: (a: string, b: string) => number\n): FolderTreeNode[] {\n const groups = section.groups;\n\n const groupById = new Map(groups.map((group) => [group.id, group]));\n const linkByUid = new Map(links.map((link) => [link.uid, link]));\n\n const childGroupIds = new Set<string>();\n\n for (const group of groups) {\n for (const child of group.children) {\n if (child.type === 'group') {\n childGroupIds.add(child.id);\n }\n }\n }\n\n const grouped = new Set<UID.ContentType>();\n\n const buildFolder = (\n group: ContentStructureSection['groups'][number],\n depth: number\n ): FolderNode => {\n const children: FolderTreeNode[] = [];\n\n for (const child of group.children) {\n if (child.type === 'group') {\n const subGroup = groupById.get(child.id);\n\n if (!subGroup) {\n continue;\n }\n\n children.push(buildFolder(subGroup, depth + 1));\n continue;\n }\n\n const link = linkByUid.get(child.uid);\n\n if (!link) {\n continue;\n }\n\n grouped.add(child.uid);\n\n children.push({\n type: 'contentType',\n status: link.status,\n parentId: group.id,\n title: link.title,\n depth: depth + 1,\n uid: link.uid,\n to: link.to,\n });\n }\n\n return {\n type: 'folder',\n parentId: group.parent,\n status: group.status,\n name: group.name,\n id: group.id,\n children,\n depth,\n };\n };\n\n const rootFolders = groups\n .filter((group) => !childGroupIds.has(group.id))\n .map((group) => buildFolder(group, 0));\n\n const ungrouped = links\n .filter((link) => !grouped.has(link.uid))\n .sort((a, b) => compareTitle(a.title, b.title))\n .map(\n (link): ContentTypeNode => ({\n type: 'contentType',\n status: link.status,\n title: link.title,\n parentId: null,\n uid: link.uid,\n to: link.to,\n depth: 0,\n })\n );\n\n return [...ungrouped, ...rootFolders];\n}\n\nexport function filterTree(\n nodes: FolderTreeNode[],\n matches: (text: string) => boolean\n): FolderTreeNode[] {\n const result: FolderTreeNode[] = [];\n\n for (const node of nodes) {\n if (node.type === 'contentType') {\n if (matches(node.title) || matches(node.uid)) {\n result.push(node);\n }\n\n continue;\n }\n\n if (matches(node.name)) {\n result.push(node);\n continue;\n }\n\n const filteredChildren = filterTree(node.children, matches);\n\n if (filteredChildren.length > 0) {\n result.push({ ...node, children: filteredChildren });\n }\n }\n\n return result;\n}\n\nexport function subtreeFolderHeight(folder: FolderNode): number {\n let deepestChild = 0;\n\n for (const child of folder.children) {\n if (child.type === 'folder') {\n deepestChild = Math.max(deepestChild, subtreeFolderHeight(child));\n }\n }\n\n return 1 + deepestChild;\n}\n\n/**\n * Whether moving `folder` under a target (a folder, or `null` for the section\n * root) would keep the resulting subtree within {@link MAX_FOLDER_DEPTH}.\n */\nexport function canNestFolderAt(folder: FolderNode, target: FolderNode | null): boolean {\n const targetDomainDepth = target ? target.depth + 1 : 0;\n const deepestResult = targetDomainDepth + subtreeFolderHeight(folder);\n\n return deepestResult <= MAX_FOLDER_DEPTH;\n}\n\nexport function countSubtree(\n section: ContentStructureSection,\n folderId: string,\n countableUids: ReadonlySet<UID.ContentType>\n): { contentTypes: number; subfolders: number } {\n const groupById = new Map(section.groups.map((group) => [group.id, group]));\n\n let contentTypes = 0;\n let subfolders = 0;\n\n const walk = (group: ContentStructureSection['groups'][number]) => {\n for (const child of group.children) {\n if (child.type === 'contentType') {\n if (countableUids.has(child.uid)) {\n contentTypes += 1;\n }\n\n continue;\n }\n\n const childGroup = groupById.get(child.id);\n\n if (!childGroup) {\n continue;\n }\n\n subfolders += 1;\n walk(childGroup);\n }\n };\n\n const root = groupById.get(folderId);\n\n if (root) {\n walk(root);\n }\n\n return { contentTypes, subfolders };\n}\n\nexport function collectSubtreeContentTypeUids(\n section: ContentStructureSection,\n folderId: string\n): UID.ContentType[] {\n const groupById = new Map(section.groups.map((group) => [group.id, group]));\n const uids: UID.ContentType[] = [];\n\n const walk = (id: string) => {\n const group = groupById.get(id);\n\n if (!group) {\n return;\n }\n\n for (const child of group.children) {\n if (child.type === 'contentType') {\n uids.push(child.uid);\n continue;\n }\n\n walk(child.id);\n }\n };\n\n walk(folderId);\n\n return uids;\n}\n\nexport function indexFolders(nodes: FolderTreeNode[]): Map<string, FolderNode> {\n const index = new Map<string, FolderNode>();\n\n const walk = (node: FolderTreeNode) => {\n if (node.type !== 'folder') {\n return;\n }\n\n index.set(node.id, node);\n node.children.forEach(walk);\n };\n\n nodes.forEach(walk);\n\n return index;\n}\n"],"names":["buildSectionTree","section","links","compareTitle","groups","groupById","Map","map","group","id","linkByUid","link","uid","childGroupIds","Set","child","children","type","add","grouped","buildFolder","depth","subGroup","get","push","status","parentId","title","to","parent","name","rootFolders","filter","has","ungrouped","sort","a","b","filterTree","nodes","matches","result","node","filteredChildren","length","subtreeFolderHeight","folder","deepestChild","Math","max","canNestFolderAt","target","targetDomainDepth","deepestResult","MAX_FOLDER_DEPTH","countSubtree","folderId","countableUids","contentTypes","subfolders","walk","childGroup","root","collectSubtreeContentTypeUids","uids"],"mappings":"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"}
@@ -0,0 +1,169 @@
1
+ import { MAX_FOLDER_DEPTH } from '../../DataManager/utils/contentStructure.mjs';
2
+
3
+ function buildSectionTree(section, links, compareTitle) {
4
+ const groups = section.groups;
5
+ const groupById = new Map(groups.map((group)=>[
6
+ group.id,
7
+ group
8
+ ]));
9
+ const linkByUid = new Map(links.map((link)=>[
10
+ link.uid,
11
+ link
12
+ ]));
13
+ const childGroupIds = new Set();
14
+ for (const group of groups){
15
+ for (const child of group.children){
16
+ if (child.type === 'group') {
17
+ childGroupIds.add(child.id);
18
+ }
19
+ }
20
+ }
21
+ const grouped = new Set();
22
+ const buildFolder = (group, depth)=>{
23
+ const children = [];
24
+ for (const child of group.children){
25
+ if (child.type === 'group') {
26
+ const subGroup = groupById.get(child.id);
27
+ if (!subGroup) {
28
+ continue;
29
+ }
30
+ children.push(buildFolder(subGroup, depth + 1));
31
+ continue;
32
+ }
33
+ const link = linkByUid.get(child.uid);
34
+ if (!link) {
35
+ continue;
36
+ }
37
+ grouped.add(child.uid);
38
+ children.push({
39
+ type: 'contentType',
40
+ status: link.status,
41
+ parentId: group.id,
42
+ title: link.title,
43
+ depth: depth + 1,
44
+ uid: link.uid,
45
+ to: link.to
46
+ });
47
+ }
48
+ return {
49
+ type: 'folder',
50
+ parentId: group.parent,
51
+ status: group.status,
52
+ name: group.name,
53
+ id: group.id,
54
+ children,
55
+ depth
56
+ };
57
+ };
58
+ const rootFolders = groups.filter((group)=>!childGroupIds.has(group.id)).map((group)=>buildFolder(group, 0));
59
+ const ungrouped = links.filter((link)=>!grouped.has(link.uid)).sort((a, b)=>compareTitle(a.title, b.title)).map((link)=>({
60
+ type: 'contentType',
61
+ status: link.status,
62
+ title: link.title,
63
+ parentId: null,
64
+ uid: link.uid,
65
+ to: link.to,
66
+ depth: 0
67
+ }));
68
+ return [
69
+ ...ungrouped,
70
+ ...rootFolders
71
+ ];
72
+ }
73
+ function filterTree(nodes, matches) {
74
+ const result = [];
75
+ for (const node of nodes){
76
+ if (node.type === 'contentType') {
77
+ if (matches(node.title) || matches(node.uid)) {
78
+ result.push(node);
79
+ }
80
+ continue;
81
+ }
82
+ if (matches(node.name)) {
83
+ result.push(node);
84
+ continue;
85
+ }
86
+ const filteredChildren = filterTree(node.children, matches);
87
+ if (filteredChildren.length > 0) {
88
+ result.push({
89
+ ...node,
90
+ children: filteredChildren
91
+ });
92
+ }
93
+ }
94
+ return result;
95
+ }
96
+ function subtreeFolderHeight(folder) {
97
+ let deepestChild = 0;
98
+ for (const child of folder.children){
99
+ if (child.type === 'folder') {
100
+ deepestChild = Math.max(deepestChild, subtreeFolderHeight(child));
101
+ }
102
+ }
103
+ return 1 + deepestChild;
104
+ }
105
+ /**
106
+ * Whether moving `folder` under a target (a folder, or `null` for the section
107
+ * root) would keep the resulting subtree within {@link MAX_FOLDER_DEPTH}.
108
+ */ function canNestFolderAt(folder, target) {
109
+ const targetDomainDepth = target ? target.depth + 1 : 0;
110
+ const deepestResult = targetDomainDepth + subtreeFolderHeight(folder);
111
+ return deepestResult <= MAX_FOLDER_DEPTH;
112
+ }
113
+ function countSubtree(section, folderId, countableUids) {
114
+ const groupById = new Map(section.groups.map((group)=>[
115
+ group.id,
116
+ group
117
+ ]));
118
+ let contentTypes = 0;
119
+ let subfolders = 0;
120
+ const walk = (group)=>{
121
+ for (const child of group.children){
122
+ if (child.type === 'contentType') {
123
+ if (countableUids.has(child.uid)) {
124
+ contentTypes += 1;
125
+ }
126
+ continue;
127
+ }
128
+ const childGroup = groupById.get(child.id);
129
+ if (!childGroup) {
130
+ continue;
131
+ }
132
+ subfolders += 1;
133
+ walk(childGroup);
134
+ }
135
+ };
136
+ const root = groupById.get(folderId);
137
+ if (root) {
138
+ walk(root);
139
+ }
140
+ return {
141
+ contentTypes,
142
+ subfolders
143
+ };
144
+ }
145
+ function collectSubtreeContentTypeUids(section, folderId) {
146
+ const groupById = new Map(section.groups.map((group)=>[
147
+ group.id,
148
+ group
149
+ ]));
150
+ const uids = [];
151
+ const walk = (id)=>{
152
+ const group = groupById.get(id);
153
+ if (!group) {
154
+ return;
155
+ }
156
+ for (const child of group.children){
157
+ if (child.type === 'contentType') {
158
+ uids.push(child.uid);
159
+ continue;
160
+ }
161
+ walk(child.id);
162
+ }
163
+ };
164
+ walk(folderId);
165
+ return uids;
166
+ }
167
+
168
+ export { buildSectionTree, canNestFolderAt, collectSubtreeContentTypeUids, countSubtree, filterTree, subtreeFolderHeight };
169
+ //# sourceMappingURL=buildFolderTree.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"buildFolderTree.mjs","sources":["../../../../../admin/src/components/ContentTypeBuilderNav/lib/buildFolderTree.ts"],"sourcesContent":["import { MAX_FOLDER_DEPTH } from '../../DataManager/utils/contentStructure';\n\nimport type { Status } from '../../../types';\nimport type {\n ContentStructureSection,\n GroupStatus,\n} from '../../DataManager/utils/contentStructure';\nimport type { UID } from '@strapi/types';\n\nexport type ContentTypeLink = {\n uid: UID.ContentType;\n to: string;\n title: string;\n status: Status;\n};\n\nexport type FolderNode = {\n type: 'folder';\n id: string;\n name: string;\n status: GroupStatus;\n depth: number;\n parentId: string | null;\n children: FolderTreeNode[];\n};\n\nexport type ContentTypeNode = {\n type: 'contentType';\n uid: UID.ContentType;\n to: string;\n title: string;\n status: Status;\n depth: number;\n parentId: string | null;\n};\n\nexport type FolderTreeNode = FolderNode | ContentTypeNode;\n\nexport function buildSectionTree(\n section: ContentStructureSection,\n links: ContentTypeLink[],\n compareTitle: (a: string, b: string) => number\n): FolderTreeNode[] {\n const groups = section.groups;\n\n const groupById = new Map(groups.map((group) => [group.id, group]));\n const linkByUid = new Map(links.map((link) => [link.uid, link]));\n\n const childGroupIds = new Set<string>();\n\n for (const group of groups) {\n for (const child of group.children) {\n if (child.type === 'group') {\n childGroupIds.add(child.id);\n }\n }\n }\n\n const grouped = new Set<UID.ContentType>();\n\n const buildFolder = (\n group: ContentStructureSection['groups'][number],\n depth: number\n ): FolderNode => {\n const children: FolderTreeNode[] = [];\n\n for (const child of group.children) {\n if (child.type === 'group') {\n const subGroup = groupById.get(child.id);\n\n if (!subGroup) {\n continue;\n }\n\n children.push(buildFolder(subGroup, depth + 1));\n continue;\n }\n\n const link = linkByUid.get(child.uid);\n\n if (!link) {\n continue;\n }\n\n grouped.add(child.uid);\n\n children.push({\n type: 'contentType',\n status: link.status,\n parentId: group.id,\n title: link.title,\n depth: depth + 1,\n uid: link.uid,\n to: link.to,\n });\n }\n\n return {\n type: 'folder',\n parentId: group.parent,\n status: group.status,\n name: group.name,\n id: group.id,\n children,\n depth,\n };\n };\n\n const rootFolders = groups\n .filter((group) => !childGroupIds.has(group.id))\n .map((group) => buildFolder(group, 0));\n\n const ungrouped = links\n .filter((link) => !grouped.has(link.uid))\n .sort((a, b) => compareTitle(a.title, b.title))\n .map(\n (link): ContentTypeNode => ({\n type: 'contentType',\n status: link.status,\n title: link.title,\n parentId: null,\n uid: link.uid,\n to: link.to,\n depth: 0,\n })\n );\n\n return [...ungrouped, ...rootFolders];\n}\n\nexport function filterTree(\n nodes: FolderTreeNode[],\n matches: (text: string) => boolean\n): FolderTreeNode[] {\n const result: FolderTreeNode[] = [];\n\n for (const node of nodes) {\n if (node.type === 'contentType') {\n if (matches(node.title) || matches(node.uid)) {\n result.push(node);\n }\n\n continue;\n }\n\n if (matches(node.name)) {\n result.push(node);\n continue;\n }\n\n const filteredChildren = filterTree(node.children, matches);\n\n if (filteredChildren.length > 0) {\n result.push({ ...node, children: filteredChildren });\n }\n }\n\n return result;\n}\n\nexport function subtreeFolderHeight(folder: FolderNode): number {\n let deepestChild = 0;\n\n for (const child of folder.children) {\n if (child.type === 'folder') {\n deepestChild = Math.max(deepestChild, subtreeFolderHeight(child));\n }\n }\n\n return 1 + deepestChild;\n}\n\n/**\n * Whether moving `folder` under a target (a folder, or `null` for the section\n * root) would keep the resulting subtree within {@link MAX_FOLDER_DEPTH}.\n */\nexport function canNestFolderAt(folder: FolderNode, target: FolderNode | null): boolean {\n const targetDomainDepth = target ? target.depth + 1 : 0;\n const deepestResult = targetDomainDepth + subtreeFolderHeight(folder);\n\n return deepestResult <= MAX_FOLDER_DEPTH;\n}\n\nexport function countSubtree(\n section: ContentStructureSection,\n folderId: string,\n countableUids: ReadonlySet<UID.ContentType>\n): { contentTypes: number; subfolders: number } {\n const groupById = new Map(section.groups.map((group) => [group.id, group]));\n\n let contentTypes = 0;\n let subfolders = 0;\n\n const walk = (group: ContentStructureSection['groups'][number]) => {\n for (const child of group.children) {\n if (child.type === 'contentType') {\n if (countableUids.has(child.uid)) {\n contentTypes += 1;\n }\n\n continue;\n }\n\n const childGroup = groupById.get(child.id);\n\n if (!childGroup) {\n continue;\n }\n\n subfolders += 1;\n walk(childGroup);\n }\n };\n\n const root = groupById.get(folderId);\n\n if (root) {\n walk(root);\n }\n\n return { contentTypes, subfolders };\n}\n\nexport function collectSubtreeContentTypeUids(\n section: ContentStructureSection,\n folderId: string\n): UID.ContentType[] {\n const groupById = new Map(section.groups.map((group) => [group.id, group]));\n const uids: UID.ContentType[] = [];\n\n const walk = (id: string) => {\n const group = groupById.get(id);\n\n if (!group) {\n return;\n }\n\n for (const child of group.children) {\n if (child.type === 'contentType') {\n uids.push(child.uid);\n continue;\n }\n\n walk(child.id);\n }\n };\n\n walk(folderId);\n\n return uids;\n}\n\nexport function indexFolders(nodes: FolderTreeNode[]): Map<string, FolderNode> {\n const index = new Map<string, FolderNode>();\n\n const walk = (node: FolderTreeNode) => {\n if (node.type !== 'folder') {\n return;\n }\n\n index.set(node.id, node);\n node.children.forEach(walk);\n };\n\n nodes.forEach(walk);\n\n return index;\n}\n"],"names":["buildSectionTree","section","links","compareTitle","groups","groupById","Map","map","group","id","linkByUid","link","uid","childGroupIds","Set","child","children","type","add","grouped","buildFolder","depth","subGroup","get","push","status","parentId","title","to","parent","name","rootFolders","filter","has","ungrouped","sort","a","b","filterTree","nodes","matches","result","node","filteredChildren","length","subtreeFolderHeight","folder","deepestChild","Math","max","canNestFolderAt","target","targetDomainDepth","deepestResult","MAX_FOLDER_DEPTH","countSubtree","folderId","countableUids","contentTypes","subfolders","walk","childGroup","root","collectSubtreeContentTypeUids","uids"],"mappings":"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"}