@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.
- package/dist/admin/components/AIChat/hooks/useAIFetch.js +6 -2
- package/dist/admin/components/AIChat/hooks/useAIFetch.js.map +1 -1
- package/dist/admin/components/AIChat/hooks/useAIFetch.mjs +7 -3
- package/dist/admin/components/AIChat/hooks/useAIFetch.mjs.map +1 -1
- package/dist/admin/components/AIChat/hooks/usePrefetchAIToken.js +20 -0
- package/dist/admin/components/AIChat/hooks/usePrefetchAIToken.js.map +1 -0
- package/dist/admin/components/AIChat/hooks/usePrefetchAIToken.mjs +18 -0
- package/dist/admin/components/AIChat/hooks/usePrefetchAIToken.mjs.map +1 -0
- package/dist/admin/components/ContentTypeBuilderNav/ContentTypeBuilderNav.js +83 -59
- package/dist/admin/components/ContentTypeBuilderNav/ContentTypeBuilderNav.js.map +1 -1
- package/dist/admin/components/ContentTypeBuilderNav/ContentTypeBuilderNav.mjs +86 -62
- package/dist/admin/components/ContentTypeBuilderNav/ContentTypeBuilderNav.mjs.map +1 -1
- package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/DeleteFolderDialog.js +112 -0
- package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/DeleteFolderDialog.js.map +1 -0
- package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/DeleteFolderDialog.mjs +110 -0
- package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/DeleteFolderDialog.mjs.map +1 -0
- package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/FolderNameField.js +159 -0
- package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/FolderNameField.js.map +1 -0
- package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/FolderNameField.mjs +157 -0
- package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/FolderNameField.mjs.map +1 -0
- package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/FolderNavSection.js +311 -0
- package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/FolderNavSection.js.map +1 -0
- package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/FolderNavSection.mjs +309 -0
- package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/FolderNavSection.mjs.map +1 -0
- package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/SortableTreeRow.js +379 -0
- package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/SortableTreeRow.js.map +1 -0
- package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/SortableTreeRow.mjs +376 -0
- package/dist/admin/components/ContentTypeBuilderNav/components/FolderNav/SortableTreeRow.mjs.map +1 -0
- package/dist/admin/components/ContentTypeBuilderNav/hooks/useContentTypeBuilderMenu.js +172 -0
- package/dist/admin/components/ContentTypeBuilderNav/hooks/useContentTypeBuilderMenu.js.map +1 -0
- package/dist/admin/components/ContentTypeBuilderNav/hooks/useContentTypeBuilderMenu.mjs +166 -0
- package/dist/admin/components/ContentTypeBuilderNav/hooks/useContentTypeBuilderMenu.mjs.map +1 -0
- package/dist/admin/components/ContentTypeBuilderNav/hooks/useFolderActions.js +62 -0
- package/dist/admin/components/ContentTypeBuilderNav/hooks/useFolderActions.js.map +1 -0
- package/dist/admin/components/ContentTypeBuilderNav/hooks/useFolderActions.mjs +60 -0
- package/dist/admin/components/ContentTypeBuilderNav/hooks/useFolderActions.mjs.map +1 -0
- package/dist/admin/components/ContentTypeBuilderNav/hooks/useSortableTree.js +145 -0
- package/dist/admin/components/ContentTypeBuilderNav/hooks/useSortableTree.js.map +1 -0
- package/dist/admin/components/ContentTypeBuilderNav/hooks/useSortableTree.mjs +143 -0
- package/dist/admin/components/ContentTypeBuilderNav/hooks/useSortableTree.mjs.map +1 -0
- package/dist/admin/components/ContentTypeBuilderNav/lib/buildFolderTree.js +176 -0
- package/dist/admin/components/ContentTypeBuilderNav/lib/buildFolderTree.js.map +1 -0
- package/dist/admin/components/ContentTypeBuilderNav/lib/buildFolderTree.mjs +169 -0
- package/dist/admin/components/ContentTypeBuilderNav/lib/buildFolderTree.mjs.map +1 -0
- package/dist/admin/components/ContentTypeBuilderNav/lib/dropProjection.js +277 -0
- package/dist/admin/components/ContentTypeBuilderNav/lib/dropProjection.js.map +1 -0
- package/dist/admin/components/ContentTypeBuilderNav/lib/dropProjection.mjs +273 -0
- package/dist/admin/components/ContentTypeBuilderNav/lib/dropProjection.mjs.map +1 -0
- package/dist/admin/components/ContentTypeBuilderNav/lib/flatModel.js +62 -0
- package/dist/admin/components/ContentTypeBuilderNav/lib/flatModel.js.map +1 -0
- package/dist/admin/components/ContentTypeBuilderNav/lib/flatModel.mjs +58 -0
- package/dist/admin/components/ContentTypeBuilderNav/lib/flatModel.mjs.map +1 -0
- package/dist/admin/components/DataManager/DataManagerContext.js.map +1 -1
- package/dist/admin/components/DataManager/DataManagerContext.mjs.map +1 -1
- package/dist/admin/components/DataManager/DataManagerProvider.js +70 -8
- package/dist/admin/components/DataManager/DataManagerProvider.js.map +1 -1
- package/dist/admin/components/DataManager/DataManagerProvider.mjs +70 -8
- package/dist/admin/components/DataManager/DataManagerProvider.mjs.map +1 -1
- package/dist/admin/components/DataManager/reducer.js +411 -33
- package/dist/admin/components/DataManager/reducer.js.map +1 -1
- package/dist/admin/components/DataManager/reducer.mjs +411 -33
- package/dist/admin/components/DataManager/reducer.mjs.map +1 -1
- package/dist/admin/components/DataManager/utils/cleanData.js +11 -2
- package/dist/admin/components/DataManager/utils/cleanData.js.map +1 -1
- package/dist/admin/components/DataManager/utils/cleanData.mjs +11 -2
- package/dist/admin/components/DataManager/utils/cleanData.mjs.map +1 -1
- package/dist/admin/components/DataManager/utils/contentStructure.js +150 -0
- package/dist/admin/components/DataManager/utils/contentStructure.js.map +1 -0
- package/dist/admin/components/DataManager/utils/contentStructure.mjs +133 -0
- package/dist/admin/components/DataManager/utils/contentStructure.mjs.map +1 -0
- package/dist/admin/components/FolderSelect.js +200 -0
- package/dist/admin/components/FolderSelect.js.map +1 -0
- package/dist/admin/components/FolderSelect.mjs +198 -0
- package/dist/admin/components/FolderSelect.mjs.map +1 -0
- package/dist/admin/components/FormModal/FormModal.js +23 -2
- package/dist/admin/components/FormModal/FormModal.js.map +1 -1
- package/dist/admin/components/FormModal/FormModal.mjs +23 -2
- package/dist/admin/components/FormModal/FormModal.mjs.map +1 -1
- package/dist/admin/components/FormModal/contentType/contentTypeForm.js +15 -18
- package/dist/admin/components/FormModal/contentType/contentTypeForm.js.map +1 -1
- package/dist/admin/components/FormModal/contentType/contentTypeForm.mjs +15 -18
- package/dist/admin/components/FormModal/contentType/contentTypeForm.mjs.map +1 -1
- package/dist/admin/components/FormModal/reducer.js +6 -0
- package/dist/admin/components/FormModal/reducer.js.map +1 -1
- package/dist/admin/components/FormModal/reducer.mjs +6 -0
- package/dist/admin/components/FormModal/reducer.mjs.map +1 -1
- package/dist/admin/pages/App/index.js +2 -5
- package/dist/admin/pages/App/index.js.map +1 -1
- package/dist/admin/pages/App/index.mjs +2 -5
- package/dist/admin/pages/App/index.mjs.map +1 -1
- package/dist/admin/src/components/AIChat/hooks/usePrefetchAIToken.d.ts +1 -0
- package/dist/admin/src/components/ContentTypeBuilderNav/components/FolderNav/DeleteFolderDialog.d.ts +16 -0
- package/dist/admin/src/components/ContentTypeBuilderNav/components/FolderNav/FolderNameField.d.ts +12 -0
- package/dist/admin/src/components/ContentTypeBuilderNav/components/FolderNav/FolderNavSection.d.ts +14 -0
- package/dist/admin/src/components/ContentTypeBuilderNav/components/FolderNav/SortableTreeRow.d.ts +38 -0
- package/dist/admin/src/components/ContentTypeBuilderNav/{useContentTypeBuilderMenu.d.ts → hooks/useContentTypeBuilderMenu.d.ts} +17 -7
- package/dist/admin/src/components/ContentTypeBuilderNav/hooks/useFolderActions.d.ts +42 -0
- package/dist/admin/src/components/ContentTypeBuilderNav/hooks/useSortableTree.d.ts +53 -0
- package/dist/admin/src/components/ContentTypeBuilderNav/lib/buildFolderTree.d.ts +42 -0
- package/dist/admin/src/components/ContentTypeBuilderNav/lib/dropProjection.d.ts +58 -0
- package/dist/admin/src/components/ContentTypeBuilderNav/lib/flatModel.d.ts +18 -0
- package/dist/admin/src/components/DataManager/DataManagerContext.d.ts +41 -0
- package/dist/admin/src/components/DataManager/reducer.d.ts +105 -1
- package/dist/admin/src/components/DataManager/utils/cleanData.d.ts +5 -1
- package/dist/admin/src/components/DataManager/utils/contentStructure.d.ts +68 -0
- package/dist/admin/src/components/FolderSelect.d.ts +18 -0
- package/dist/admin/src/components/FormModal/contentType/contentTypeForm.d.ts +0 -21
- package/dist/admin/src/components/FormModal/forms/forms.d.ts +0 -10
- package/dist/admin/src/components/FormModal/reducer.d.ts +7 -0
- package/dist/admin/src/utils/folderTelemetry.d.ts +5 -0
- package/dist/admin/translations/en.json.js +23 -0
- package/dist/admin/translations/en.json.js.map +1 -1
- package/dist/admin/translations/en.json.mjs +23 -0
- package/dist/admin/translations/en.json.mjs.map +1 -1
- package/dist/admin/utils/folderTelemetry.js +19 -0
- package/dist/admin/utils/folderTelemetry.js.map +1 -0
- package/dist/admin/utils/folderTelemetry.mjs +16 -0
- package/dist/admin/utils/folderTelemetry.mjs.map +1 -0
- package/dist/server/controllers/schema.js +1 -1
- package/dist/server/controllers/schema.js.map +1 -1
- package/dist/server/controllers/schema.mjs +1 -1
- package/dist/server/controllers/schema.mjs.map +1 -1
- package/dist/server/controllers/validation/common.js +3 -0
- package/dist/server/controllers/validation/common.js.map +1 -1
- package/dist/server/controllers/validation/common.mjs +3 -1
- package/dist/server/controllers/validation/common.mjs.map +1 -1
- package/dist/server/controllers/validation/content-structure.js +177 -0
- package/dist/server/controllers/validation/content-structure.js.map +1 -0
- package/dist/server/controllers/validation/content-structure.mjs +173 -0
- package/dist/server/controllers/validation/content-structure.mjs.map +1 -0
- package/dist/server/controllers/validation/schema.js +3 -1
- package/dist/server/controllers/validation/schema.js.map +1 -1
- package/dist/server/controllers/validation/schema.mjs +3 -1
- package/dist/server/controllers/validation/schema.mjs.map +1 -1
- package/dist/server/register.js +1 -1
- package/dist/server/register.js.map +1 -1
- package/dist/server/register.mjs +1 -1
- package/dist/server/register.mjs.map +1 -1
- package/dist/server/routes/content-api.js +4 -4
- package/dist/server/routes/content-api.js.map +1 -1
- package/dist/server/routes/content-api.mjs +4 -4
- package/dist/server/routes/content-api.mjs.map +1 -1
- package/dist/server/services/api-handler.js +37 -4
- package/dist/server/services/api-handler.js.map +1 -1
- package/dist/server/services/api-handler.mjs +36 -5
- package/dist/server/services/api-handler.mjs.map +1 -1
- package/dist/server/services/content-structure.js +174 -0
- package/dist/server/services/content-structure.js.map +1 -0
- package/dist/server/services/content-structure.mjs +172 -0
- package/dist/server/services/content-structure.mjs.map +1 -0
- package/dist/server/services/content-types.js +196 -44
- package/dist/server/services/content-types.js.map +1 -1
- package/dist/server/services/content-types.mjs +195 -45
- package/dist/server/services/content-types.mjs.map +1 -1
- package/dist/server/services/index.js +3 -1
- package/dist/server/services/index.js.map +1 -1
- package/dist/server/services/index.mjs +3 -1
- package/dist/server/services/index.mjs.map +1 -1
- package/dist/server/services/schema-builder/index.js +4 -2
- package/dist/server/services/schema-builder/index.js.map +1 -1
- package/dist/server/services/schema-builder/index.mjs +4 -2
- package/dist/server/services/schema-builder/index.mjs.map +1 -1
- package/dist/server/services/schema-builder/schema-handler.js +16 -16
- package/dist/server/services/schema-builder/schema-handler.js.map +1 -1
- package/dist/server/services/schema-builder/schema-handler.mjs +16 -16
- package/dist/server/services/schema-builder/schema-handler.mjs.map +1 -1
- package/dist/server/services/schema-mutation.js +46 -0
- package/dist/server/services/schema-mutation.js.map +1 -0
- package/dist/server/services/schema-mutation.mjs +43 -0
- package/dist/server/services/schema-mutation.mjs.map +1 -0
- package/dist/server/services/schema.js +134 -79
- package/dist/server/services/schema.js.map +1 -1
- package/dist/server/services/schema.mjs +135 -80
- package/dist/server/services/schema.mjs.map +1 -1
- package/dist/server/src/controllers/schema.d.ts.map +1 -1
- package/dist/server/src/controllers/validation/common.d.ts +1 -0
- package/dist/server/src/controllers/validation/common.d.ts.map +1 -1
- package/dist/server/src/controllers/validation/content-structure.d.ts +56 -0
- package/dist/server/src/controllers/validation/content-structure.d.ts.map +1 -0
- package/dist/server/src/controllers/validation/schema.d.ts +35 -0
- package/dist/server/src/controllers/validation/schema.d.ts.map +1 -1
- package/dist/server/src/index.d.ts +3 -0
- package/dist/server/src/index.d.ts.map +1 -1
- package/dist/server/src/routes/content-api.d.ts.map +1 -1
- package/dist/server/src/services/api-handler.d.ts +13 -1
- package/dist/server/src/services/api-handler.d.ts.map +1 -1
- package/dist/server/src/services/content-structure.d.ts +37 -0
- package/dist/server/src/services/content-structure.d.ts.map +1 -0
- package/dist/server/src/services/content-types.d.ts +4 -0
- package/dist/server/src/services/content-types.d.ts.map +1 -1
- package/dist/server/src/services/index.d.ts +4 -0
- package/dist/server/src/services/index.d.ts.map +1 -1
- package/dist/server/src/services/schema-builder/index.d.ts +3 -1
- package/dist/server/src/services/schema-builder/index.d.ts.map +1 -1
- package/dist/server/src/services/schema-builder/schema-handler.d.ts.map +1 -1
- package/dist/server/src/services/schema-mutation.d.ts +26 -0
- package/dist/server/src/services/schema-mutation.d.ts.map +1 -0
- package/dist/server/src/services/schema.d.ts +1 -0
- package/dist/server/src/services/schema.d.ts.map +1 -1
- package/dist/server/src/utils/index.d.ts +2 -0
- package/dist/server/src/utils/index.d.ts.map +1 -1
- package/dist/server/utils/index.js.map +1 -1
- package/dist/server/utils/index.mjs.map +1 -1
- package/package.json +6 -6
- package/dist/admin/components/ContentTypeBuilderNav/useContentTypeBuilderMenu.js +0 -157
- package/dist/admin/components/ContentTypeBuilderNav/useContentTypeBuilderMenu.js.map +0 -1
- package/dist/admin/components/ContentTypeBuilderNav/useContentTypeBuilderMenu.mjs +0 -151
- package/dist/admin/components/ContentTypeBuilderNav/useContentTypeBuilderMenu.mjs.map +0 -1
|
@@ -4,12 +4,15 @@ import { applyPrivateSearchDefault } from '../../utils/applyPrivateSearchDefault
|
|
|
4
4
|
import { getRelationType } from '../../utils/getRelationType.mjs';
|
|
5
5
|
import { makeUnique } from '../../utils/makeUnique.mjs';
|
|
6
6
|
import { createUndoRedoSlice } from './undoRedo.mjs';
|
|
7
|
+
import { sectionKeyForKind, getDeleteFolderOnlySiblingNameCollisions, MAX_FOLDER_DEPTH, isFolderNameTakenBySibling, createEmptyContentStructure, findSiblingFolderByName } from './utils/contentStructure.mjs';
|
|
7
8
|
|
|
8
9
|
const initialState = {
|
|
9
10
|
components: {},
|
|
10
11
|
contentTypes: {},
|
|
11
12
|
initialComponents: {},
|
|
12
13
|
initialContentTypes: {},
|
|
14
|
+
contentStructure: createEmptyContentStructure(),
|
|
15
|
+
initialContentStructure: createEmptyContentStructure(),
|
|
13
16
|
reservedNames: {
|
|
14
17
|
models: [],
|
|
15
18
|
attributes: []
|
|
@@ -55,6 +58,12 @@ const getNewStatus = (oldStatus, newStatus)=>{
|
|
|
55
58
|
}
|
|
56
59
|
return newStatus;
|
|
57
60
|
};
|
|
61
|
+
const getNewGroupStatus = (oldStatus, newStatus)=>{
|
|
62
|
+
if (oldStatus === 'NEW') {
|
|
63
|
+
return oldStatus;
|
|
64
|
+
}
|
|
65
|
+
return newStatus;
|
|
66
|
+
};
|
|
58
67
|
const setAttributeStatus = (attribute, status)=>{
|
|
59
68
|
attribute.status = getNewStatus(attribute.status, status);
|
|
60
69
|
};
|
|
@@ -92,6 +101,9 @@ const replaceAttributeAt = (type, index, attribute)=>{
|
|
|
92
101
|
};
|
|
93
102
|
const removeAttributeByName = (type, name)=>{
|
|
94
103
|
const idx = type.attributes.findIndex((attr)=>attr.name === name);
|
|
104
|
+
if (idx === -1) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
95
107
|
const attr = type.attributes[idx];
|
|
96
108
|
setStatus(type, 'CHANGED');
|
|
97
109
|
if (attr.status === 'NEW') {
|
|
@@ -104,17 +116,156 @@ const updateType = (type, data)=>{
|
|
|
104
116
|
merge(type, data);
|
|
105
117
|
setStatus(type, 'CHANGED');
|
|
106
118
|
};
|
|
119
|
+
const findGroup = (groups, id)=>{
|
|
120
|
+
return groups.find((group)=>group.id === id);
|
|
121
|
+
};
|
|
122
|
+
const setGroupStatus = (group, status)=>{
|
|
123
|
+
group.status = getNewGroupStatus(group.status, status);
|
|
124
|
+
};
|
|
125
|
+
/**
|
|
126
|
+
* Remove a content type reference from every group of a section, mark the group as changed if a reference was removed.
|
|
127
|
+
* @returns true if a reference was removed, false otherwise.
|
|
128
|
+
*/ const removeContentTypeChild = (groups, uid)=>{
|
|
129
|
+
let removed = false;
|
|
130
|
+
for (const group of groups){
|
|
131
|
+
const groupIndex = group.children.findIndex((child)=>{
|
|
132
|
+
return child.type === 'contentType' && child.uid === uid;
|
|
133
|
+
});
|
|
134
|
+
if (groupIndex !== -1) {
|
|
135
|
+
group.children.splice(groupIndex, 1);
|
|
136
|
+
setGroupStatus(group, 'CHANGED');
|
|
137
|
+
removed = true;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
return removed;
|
|
141
|
+
};
|
|
142
|
+
/**
|
|
143
|
+
* Assign a content type to a folder within a single section, optionally creating
|
|
144
|
+
* a new root folder first.
|
|
145
|
+
*
|
|
146
|
+
* Detaches the content type from any current folder then files it under the target group.
|
|
147
|
+
* This is also used by the create/edit content-type modal's folder picker via
|
|
148
|
+
* createSchema/updateSchema.
|
|
149
|
+
*/ const applyFolderAssignment = (section, uid, folder)=>{
|
|
150
|
+
const { groups } = section;
|
|
151
|
+
removeContentTypeChild(groups, uid);
|
|
152
|
+
let targetGroupId;
|
|
153
|
+
if ('newFolderId' in folder) {
|
|
154
|
+
// Reuse an existing root folder of the same name instead of creating a
|
|
155
|
+
// duplicate. The modal picker can only ever makes root folders, so
|
|
156
|
+
// this makes the assumption that a same-named root folder means the
|
|
157
|
+
// user wants to insert into that folder.
|
|
158
|
+
const existing = findSiblingFolderByName(groups, null, folder.newFolderName);
|
|
159
|
+
if (existing) {
|
|
160
|
+
targetGroupId = existing.id;
|
|
161
|
+
} else {
|
|
162
|
+
groups.push({
|
|
163
|
+
name: folder.newFolderName.trim(),
|
|
164
|
+
id: folder.newFolderId,
|
|
165
|
+
parent: null,
|
|
166
|
+
children: [],
|
|
167
|
+
status: 'NEW'
|
|
168
|
+
});
|
|
169
|
+
targetGroupId = folder.newFolderId;
|
|
170
|
+
}
|
|
171
|
+
} else {
|
|
172
|
+
targetGroupId = folder.targetGroupId;
|
|
173
|
+
}
|
|
174
|
+
if (!targetGroupId) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const targetGroup = findGroup(groups, targetGroupId);
|
|
178
|
+
if (targetGroup) {
|
|
179
|
+
targetGroup.children.push({
|
|
180
|
+
type: 'contentType',
|
|
181
|
+
uid
|
|
182
|
+
});
|
|
183
|
+
setGroupStatus(targetGroup, 'CHANGED');
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
/**
|
|
187
|
+
* Determines whether `potentialDescendantId` is nested somewhere inside `ancestorId`'s subtree
|
|
188
|
+
*/ const isDescendantGroup = (groups, ancestorId, potentialDescendantId)=>{
|
|
189
|
+
let current = findGroup(groups, potentialDescendantId);
|
|
190
|
+
while(current && current.parent !== null){
|
|
191
|
+
if (current.parent === ancestorId) {
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
current = findGroup(groups, current.parent);
|
|
195
|
+
}
|
|
196
|
+
return false;
|
|
197
|
+
};
|
|
198
|
+
const getGroupDepth = (groups, id)=>{
|
|
199
|
+
let depth = 1;
|
|
200
|
+
let current = findGroup(groups, id);
|
|
201
|
+
while(current && current.parent !== null){
|
|
202
|
+
current = findGroup(groups, current.parent);
|
|
203
|
+
depth += 1;
|
|
204
|
+
}
|
|
205
|
+
return depth;
|
|
206
|
+
};
|
|
207
|
+
/**
|
|
208
|
+
* Number of folder levels below a group e.g. a group with no sub-folders has height 0.
|
|
209
|
+
*/ const getSubtreeHeight = (groups, id)=>{
|
|
210
|
+
const group = findGroup(groups, id);
|
|
211
|
+
if (!group) {
|
|
212
|
+
return 0;
|
|
213
|
+
}
|
|
214
|
+
let height = 0;
|
|
215
|
+
for (const child of group.children){
|
|
216
|
+
if (child.type !== 'group') continue;
|
|
217
|
+
height = Math.max(height, 1 + getSubtreeHeight(groups, child.id));
|
|
218
|
+
}
|
|
219
|
+
return height;
|
|
220
|
+
};
|
|
221
|
+
const applyDeleteContentType = (state, uid)=>{
|
|
222
|
+
const type = state.contentTypes[uid];
|
|
223
|
+
// Only application schemas belong to CTB. Folder actions can be crafted outside the UI,
|
|
224
|
+
// so this guard must live at the mutation boundary rather than in the dialog alone.
|
|
225
|
+
if (!type || !type.uid.startsWith('api::') || type.plugin) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
// just drop new content types
|
|
229
|
+
if (type.status === 'NEW') {
|
|
230
|
+
delete state.contentTypes[uid];
|
|
231
|
+
} else {
|
|
232
|
+
setStatus(type, 'REMOVED');
|
|
233
|
+
}
|
|
234
|
+
// remove the content type from the content structure tree
|
|
235
|
+
Object.values(state.contentStructure.sections).forEach((section)=>{
|
|
236
|
+
removeContentTypeChild(section.groups, uid);
|
|
237
|
+
});
|
|
238
|
+
// remove the content type from the components
|
|
239
|
+
Object.keys(state.components).forEach((componentUid)=>{
|
|
240
|
+
const component = state.components[componentUid];
|
|
241
|
+
const namesToRemove = component.attributes.filter((attribute)=>attribute.type === 'relation' && attribute.target === uid).map((attribute)=>attribute.name);
|
|
242
|
+
namesToRemove.forEach((name)=>{
|
|
243
|
+
removeAttributeByName(component, name);
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
// remove the content type from the content types
|
|
247
|
+
Object.keys(state.contentTypes).forEach((contentTypeUid)=>{
|
|
248
|
+
const contentType = state.contentTypes[contentTypeUid];
|
|
249
|
+
const namesToRemove = contentType.attributes.filter((attribute)=>attribute.type === 'relation' && attribute.target === uid).map((attribute)=>attribute.name);
|
|
250
|
+
namesToRemove.forEach((name)=>{
|
|
251
|
+
removeAttributeByName(contentType, name);
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
};
|
|
107
255
|
const slice = createUndoRedoSlice({
|
|
108
256
|
name: 'data-manager',
|
|
109
257
|
initialState,
|
|
110
258
|
reducers: {
|
|
111
259
|
init: (state, action)=>{
|
|
112
|
-
const { components, contentTypes, reservedNames } = action.payload;
|
|
260
|
+
const { components, contentTypes, reservedNames, contentStructure } = action.payload;
|
|
113
261
|
state.components = components;
|
|
114
262
|
state.initialComponents = components;
|
|
115
263
|
state.initialContentTypes = contentTypes;
|
|
116
264
|
state.contentTypes = contentTypes;
|
|
117
265
|
state.reservedNames = reservedNames;
|
|
266
|
+
const structure = contentStructure ?? createEmptyContentStructure();
|
|
267
|
+
state.initialContentStructure = structure;
|
|
268
|
+
state.contentStructure = structure;
|
|
118
269
|
state.isLoading = false;
|
|
119
270
|
},
|
|
120
271
|
createComponentSchema: (state, action)=>{
|
|
@@ -135,7 +286,7 @@ const slice = createUndoRedoSlice({
|
|
|
135
286
|
state.components[uid] = newSchema;
|
|
136
287
|
},
|
|
137
288
|
createSchema: (state, action)=>{
|
|
138
|
-
const { uid, data } = action.payload;
|
|
289
|
+
const { uid, data, folder } = action.payload;
|
|
139
290
|
const { displayName, singularName, pluralName, kind, draftAndPublish, pluginOptions } = data;
|
|
140
291
|
const newSchema = {
|
|
141
292
|
uid: uid,
|
|
@@ -158,6 +309,11 @@ const slice = createUndoRedoSlice({
|
|
|
158
309
|
pluginOptions
|
|
159
310
|
};
|
|
160
311
|
state.contentTypes[uid] = newSchema;
|
|
312
|
+
// File the new content type into a folder in the same transaction it is created,
|
|
313
|
+
// so both are subject to the same undo/redo operatiion.
|
|
314
|
+
if (folder) {
|
|
315
|
+
applyFolderAssignment(state.contentStructure.sections[sectionKeyForKind(kind)], uid, folder);
|
|
316
|
+
}
|
|
161
317
|
},
|
|
162
318
|
addAttribute: (state, action)=>{
|
|
163
319
|
const { attributeToSet, forTarget, targetUid } = action.payload;
|
|
@@ -419,12 +575,13 @@ const slice = createUndoRedoSlice({
|
|
|
419
575
|
}
|
|
420
576
|
},
|
|
421
577
|
updateSchema: (state, action)=>{
|
|
422
|
-
const { data, uid } = action.payload;
|
|
578
|
+
const { data, uid, folder } = action.payload;
|
|
423
579
|
const { displayName, kind, draftAndPublish, pluginOptions } = data;
|
|
424
580
|
const type = state.contentTypes[uid];
|
|
425
581
|
if (!type) {
|
|
426
582
|
return;
|
|
427
583
|
}
|
|
584
|
+
const previousKind = type.kind;
|
|
428
585
|
updateType(type, {
|
|
429
586
|
info: {
|
|
430
587
|
displayName
|
|
@@ -435,6 +592,15 @@ const slice = createUndoRedoSlice({
|
|
|
435
592
|
},
|
|
436
593
|
pluginOptions
|
|
437
594
|
});
|
|
595
|
+
if (kind && kind !== previousKind) {
|
|
596
|
+
const oldSection = state.contentStructure.sections[sectionKeyForKind(previousKind)];
|
|
597
|
+
removeContentTypeChild(oldSection.groups, uid);
|
|
598
|
+
}
|
|
599
|
+
// Apply the folder picker's choice after any kind relocation, so the
|
|
600
|
+
// assignment lands in the content type's (possibly new) section.
|
|
601
|
+
if (folder) {
|
|
602
|
+
applyFolderAssignment(state.contentStructure.sections[sectionKeyForKind(kind)], uid, folder);
|
|
603
|
+
}
|
|
438
604
|
},
|
|
439
605
|
deleteComponent: (state, action)=>{
|
|
440
606
|
const uid = action.payload;
|
|
@@ -454,49 +620,251 @@ const slice = createUndoRedoSlice({
|
|
|
454
620
|
attribute.components = newComponents;
|
|
455
621
|
}
|
|
456
622
|
});
|
|
457
|
-
contentType.attributes.
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
}
|
|
623
|
+
const namesToRemove = contentType.attributes.filter((attribute)=>attribute.type === 'component' && attribute.component === uid).map((attribute)=>attribute.name);
|
|
624
|
+
namesToRemove.forEach((name)=>{
|
|
625
|
+
removeAttributeByName(contentType, name);
|
|
461
626
|
});
|
|
462
627
|
});
|
|
463
628
|
// remove the compo from other components
|
|
464
629
|
Object.keys(state.components).forEach((componentUid)=>{
|
|
465
630
|
const component = state.components[componentUid];
|
|
466
|
-
component.attributes.
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
}
|
|
631
|
+
const namesToRemove = component.attributes.filter((attribute)=>attribute.type === 'component' && attribute.component === uid).map((attribute)=>attribute.name);
|
|
632
|
+
namesToRemove.forEach((name)=>{
|
|
633
|
+
removeAttributeByName(component, name);
|
|
470
634
|
});
|
|
471
635
|
});
|
|
472
636
|
},
|
|
473
637
|
deleteContentType: (state, action)=>{
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
}
|
|
480
|
-
|
|
638
|
+
applyDeleteContentType(state, action.payload);
|
|
639
|
+
},
|
|
640
|
+
createFolder: (state, action)=>{
|
|
641
|
+
const { section, id, name: rawName, parentId } = action.payload;
|
|
642
|
+
const name = rawName.trim();
|
|
643
|
+
const { groups } = state.contentStructure.sections[section];
|
|
644
|
+
if (parentId && getGroupDepth(groups, parentId) + 1 > MAX_FOLDER_DEPTH) {
|
|
645
|
+
return;
|
|
481
646
|
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
647
|
+
if (isFolderNameTakenBySibling(groups, parentId, name)) {
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
groups.push({
|
|
651
|
+
id,
|
|
652
|
+
name,
|
|
653
|
+
parent: parentId,
|
|
654
|
+
children: [],
|
|
655
|
+
status: 'NEW'
|
|
656
|
+
});
|
|
657
|
+
if (parentId) {
|
|
658
|
+
const parent = findGroup(groups, parentId);
|
|
659
|
+
if (parent) {
|
|
660
|
+
parent.children.push({
|
|
661
|
+
type: 'group',
|
|
662
|
+
id
|
|
663
|
+
});
|
|
664
|
+
setGroupStatus(parent, 'CHANGED');
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
},
|
|
668
|
+
renameFolder: (state, action)=>{
|
|
669
|
+
const { section, id, name: rawName } = action.payload;
|
|
670
|
+
const name = rawName.trim();
|
|
671
|
+
const { groups } = state.contentStructure.sections[section];
|
|
672
|
+
const group = findGroup(groups, id);
|
|
673
|
+
if (!group) {
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
if (group.name === name) {
|
|
677
|
+
return;
|
|
678
|
+
}
|
|
679
|
+
if (isFolderNameTakenBySibling(groups, group.parent, name, id)) {
|
|
680
|
+
return;
|
|
681
|
+
}
|
|
682
|
+
group.name = name;
|
|
683
|
+
setGroupStatus(group, 'CHANGED');
|
|
684
|
+
},
|
|
685
|
+
moveFolder: (state, action)=>{
|
|
686
|
+
const { section, id, newParentId, index } = action.payload;
|
|
687
|
+
const { groups } = state.contentStructure.sections[section];
|
|
688
|
+
const group = findGroup(groups, id);
|
|
689
|
+
if (!group) {
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
// Reject no-ops and cyclic moves
|
|
693
|
+
if (newParentId === id || newParentId && isDescendantGroup(groups, id, newParentId)) {
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
// Refuse moves that would exceed the maximum allowed depth.
|
|
697
|
+
const newTopDepth = newParentId ? getGroupDepth(groups, newParentId) + 1 : 1;
|
|
698
|
+
if (newTopDepth + getSubtreeHeight(groups, id) > MAX_FOLDER_DEPTH) {
|
|
699
|
+
return;
|
|
700
|
+
}
|
|
701
|
+
// Refuse a move that would collide with an identically named sibling.
|
|
702
|
+
if (isFolderNameTakenBySibling(groups, newParentId, group.name, id)) {
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
if (group.parent) {
|
|
706
|
+
const oldParent = findGroup(groups, group.parent);
|
|
707
|
+
if (oldParent) {
|
|
708
|
+
// Remove from the old parent's list of children.
|
|
709
|
+
const idx = oldParent.children.findIndex((child)=>{
|
|
710
|
+
return child.type === 'group' && child.id === id;
|
|
711
|
+
});
|
|
712
|
+
if (idx !== -1) {
|
|
713
|
+
oldParent.children.splice(idx, 1);
|
|
488
714
|
}
|
|
489
|
-
|
|
715
|
+
setGroupStatus(oldParent, 'CHANGED');
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
group.parent = newParentId;
|
|
719
|
+
setGroupStatus(group, 'CHANGED');
|
|
720
|
+
if (newParentId) {
|
|
721
|
+
const newParent = findGroup(groups, newParentId);
|
|
722
|
+
if (newParent) {
|
|
723
|
+
const entry = {
|
|
724
|
+
type: 'group',
|
|
725
|
+
id
|
|
726
|
+
};
|
|
727
|
+
if (typeof index === 'number' && index >= 0 && index <= newParent.children.length) {
|
|
728
|
+
newParent.children.splice(index, 0, entry);
|
|
729
|
+
} else {
|
|
730
|
+
newParent.children.push(entry);
|
|
731
|
+
}
|
|
732
|
+
setGroupStatus(newParent, 'CHANGED');
|
|
733
|
+
}
|
|
734
|
+
} else if (typeof index === 'number') {
|
|
735
|
+
// If a content type is dropped into the root:
|
|
736
|
+
// The root display order follows the array order of the
|
|
737
|
+
// section's root-level groups because there's no root level
|
|
738
|
+
// customization yet, so reposition the moved group among them.
|
|
739
|
+
const currentIndex = groups.findIndex((candidate)=>candidate.id === id);
|
|
740
|
+
if (currentIndex !== -1) {
|
|
741
|
+
const [moved] = groups.splice(currentIndex, 1);
|
|
742
|
+
const rootIds = groups.filter((candidate)=>candidate.parent === null).map((candidate)=>candidate.id);
|
|
743
|
+
const anchorId = rootIds[index];
|
|
744
|
+
const insertAt = anchorId ? groups.findIndex((candidate)=>candidate.id === anchorId) : groups.length;
|
|
745
|
+
groups.splice(insertAt === -1 ? groups.length : insertAt, 0, moved);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
},
|
|
749
|
+
deleteFolderOnly: (state, action)=>{
|
|
750
|
+
const { section, id: groupToDeleteId } = action.payload;
|
|
751
|
+
const { groups } = state.contentStructure.sections[section];
|
|
752
|
+
const groupToDelete = findGroup(groups, groupToDeleteId);
|
|
753
|
+
if (!groupToDelete) {
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
const groupToDeleteParentId = groupToDelete.parent;
|
|
757
|
+
// Reparenting a child folder into its grandparent must preserve the same sibling-name
|
|
758
|
+
// invariant as create, rename, and move. Do this before mutating any group state.
|
|
759
|
+
if (getDeleteFolderOnlySiblingNameCollisions(groups, groupToDeleteId).length > 0) {
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
// It is necessary to clone the children because we must subsequently mutate the original group.children array when we splice it out of its parent.
|
|
763
|
+
const children = groupToDelete.children.map((child)=>({
|
|
764
|
+
...child
|
|
765
|
+
}));
|
|
766
|
+
// Children of a deleted folder inherit the deleted folder's parent.
|
|
767
|
+
children.forEach((child)=>{
|
|
768
|
+
if (child.type === 'group') {
|
|
769
|
+
const childGroup = findGroup(groups, child.id);
|
|
770
|
+
if (childGroup) {
|
|
771
|
+
childGroup.parent = groupToDeleteParentId;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
490
774
|
});
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
775
|
+
if (groupToDeleteParentId) {
|
|
776
|
+
const groupToDeleteParent = findGroup(groups, groupToDeleteParentId);
|
|
777
|
+
if (groupToDeleteParent) {
|
|
778
|
+
// Splice the deleted folder's children of the deleted folder into their new parent at the position of the deleted folder.
|
|
779
|
+
const groupToDeleteIndex = groupToDeleteParent.children.findIndex((child)=>{
|
|
780
|
+
return child.type === 'group' && child.id === groupToDeleteId;
|
|
781
|
+
});
|
|
782
|
+
if (groupToDeleteIndex !== -1) {
|
|
783
|
+
groupToDeleteParent.children.splice(groupToDeleteIndex, 1, ...children);
|
|
784
|
+
} else {
|
|
785
|
+
groupToDeleteParent.children.push(...children);
|
|
497
786
|
}
|
|
498
|
-
|
|
787
|
+
setGroupStatus(groupToDeleteParent, 'CHANGED');
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
// Finally, remove the deleted folder from its section's list of groups.
|
|
791
|
+
const groupIndex = groups.findIndex((candidate)=>{
|
|
792
|
+
return candidate.id === groupToDeleteId;
|
|
499
793
|
});
|
|
794
|
+
if (groupIndex !== -1) {
|
|
795
|
+
groups.splice(groupIndex, 1);
|
|
796
|
+
}
|
|
797
|
+
},
|
|
798
|
+
deleteFolderAndContent: (state, action)=>{
|
|
799
|
+
const { section, id: groupToDeleteId, contentTypeUids } = action.payload;
|
|
800
|
+
contentTypeUids.forEach((uid)=>{
|
|
801
|
+
applyDeleteContentType(state, uid);
|
|
802
|
+
});
|
|
803
|
+
const { groups } = state.contentStructure.sections[section];
|
|
804
|
+
const groupToDelete = findGroup(groups, groupToDeleteId);
|
|
805
|
+
if (!groupToDelete) {
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
const toRemove = new Set();
|
|
809
|
+
const collect = (currentGroupId)=>{
|
|
810
|
+
toRemove.add(currentGroupId);
|
|
811
|
+
const currentGroup = findGroup(groups, currentGroupId);
|
|
812
|
+
for (const child of currentGroup?.children ?? []){
|
|
813
|
+
if (child.type !== 'group') continue;
|
|
814
|
+
collect(child.id);
|
|
815
|
+
}
|
|
816
|
+
};
|
|
817
|
+
collect(groupToDeleteId);
|
|
818
|
+
if (groupToDelete.parent) {
|
|
819
|
+
const groupToDeleteParent = findGroup(groups, groupToDelete.parent);
|
|
820
|
+
if (groupToDeleteParent) {
|
|
821
|
+
const groupToDeleteIndex = groupToDeleteParent.children.findIndex((child)=>{
|
|
822
|
+
return child.type === 'group' && child.id === groupToDeleteId;
|
|
823
|
+
});
|
|
824
|
+
if (groupToDeleteIndex !== -1) {
|
|
825
|
+
groupToDeleteParent.children.splice(groupToDeleteIndex, 1);
|
|
826
|
+
}
|
|
827
|
+
setGroupStatus(groupToDeleteParent, 'CHANGED');
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
// Removes the group entry and all its descendants from the contentStructure.
|
|
831
|
+
state.contentStructure.sections[section].groups = groups.filter((candidate)=>{
|
|
832
|
+
return !toRemove.has(candidate.id);
|
|
833
|
+
});
|
|
834
|
+
},
|
|
835
|
+
assignContentTypeToFolder: (state, action)=>{
|
|
836
|
+
const { section, uid, targetGroupId, index } = action.payload;
|
|
837
|
+
const { groups } = state.contentStructure.sections[section];
|
|
838
|
+
removeContentTypeChild(groups, uid);
|
|
839
|
+
if (targetGroupId) {
|
|
840
|
+
const targetGroup = findGroup(groups, targetGroupId);
|
|
841
|
+
if (targetGroup) {
|
|
842
|
+
const entry = {
|
|
843
|
+
type: 'contentType',
|
|
844
|
+
uid
|
|
845
|
+
};
|
|
846
|
+
if (typeof index === 'number' && index >= 0 && index <= targetGroup.children.length) {
|
|
847
|
+
targetGroup.children.splice(index, 0, entry);
|
|
848
|
+
} else {
|
|
849
|
+
targetGroup.children.push(entry);
|
|
850
|
+
}
|
|
851
|
+
setGroupStatus(targetGroup, 'CHANGED');
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
},
|
|
855
|
+
reorderFolderChildren: (state, action)=>{
|
|
856
|
+
const { section, groupId, from, to } = action.payload;
|
|
857
|
+
const group = findGroup(state.contentStructure.sections[section].groups, groupId);
|
|
858
|
+
if (!group) {
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
const child = group.children[from];
|
|
862
|
+
if (!child) {
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
group.children.splice(from, 1);
|
|
866
|
+
group.children.splice(to, 0, child);
|
|
867
|
+
setGroupStatus(group, 'CHANGED');
|
|
500
868
|
},
|
|
501
869
|
applyChange (state, reducerAction) {
|
|
502
870
|
const { action, schema } = reducerAction.payload;
|
|
@@ -523,10 +891,15 @@ const slice = createUndoRedoSlice({
|
|
|
523
891
|
};
|
|
524
892
|
} else {
|
|
525
893
|
const contentType = state.contentTypes[uid];
|
|
894
|
+
const previousKind = contentType?.kind;
|
|
526
895
|
state.contentTypes[uid] = {
|
|
527
896
|
...schema,
|
|
528
897
|
status: contentType?.status === 'NEW' ? 'NEW' : schema.status
|
|
529
898
|
};
|
|
899
|
+
if (previousKind && schema.kind && schema.kind !== previousKind) {
|
|
900
|
+
const oldSection = state.contentStructure.sections[sectionKeyForKind(previousKind)];
|
|
901
|
+
removeContentTypeChild(oldSection.groups, uid);
|
|
902
|
+
}
|
|
530
903
|
}
|
|
531
904
|
}
|
|
532
905
|
break;
|
|
@@ -557,6 +930,9 @@ const slice = createUndoRedoSlice({
|
|
|
557
930
|
} else {
|
|
558
931
|
state.contentTypes[uid].status = 'REMOVED';
|
|
559
932
|
}
|
|
933
|
+
Object.values(state.contentStructure.sections).forEach((section)=>{
|
|
934
|
+
removeContentTypeChild(section.groups, uid);
|
|
935
|
+
});
|
|
560
936
|
}
|
|
561
937
|
break;
|
|
562
938
|
}
|
|
@@ -575,12 +951,14 @@ const slice = createUndoRedoSlice({
|
|
|
575
951
|
}
|
|
576
952
|
return {
|
|
577
953
|
components: state.components,
|
|
578
|
-
contentTypes: state.contentTypes
|
|
954
|
+
contentTypes: state.contentTypes,
|
|
955
|
+
contentStructure: state.contentStructure
|
|
579
956
|
};
|
|
580
957
|
},
|
|
581
958
|
discard: (state)=>{
|
|
582
959
|
state.components = state.initialComponents;
|
|
583
960
|
state.contentTypes = state.initialContentTypes;
|
|
961
|
+
state.contentStructure = state.initialContentStructure;
|
|
584
962
|
}
|
|
585
963
|
});
|
|
586
964
|
const { reducer, actions } = slice;
|