strapi-plugin-navigation 1.1.3 → 2.0.0-beta.4

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 (149) hide show
  1. package/README.md +61 -87
  2. package/admin/src/components/EmptyView/index.js +7 -16
  3. package/admin/src/components/Item/ItemCardBadge/index.js +8 -0
  4. package/admin/src/components/Item/ItemCardHeader/Wrapper.js +21 -0
  5. package/admin/src/components/Item/ItemCardHeader/index.js +59 -0
  6. package/admin/src/components/Item/Wrapper.js +39 -0
  7. package/admin/src/components/Item/index.js +78 -124
  8. package/admin/src/components/NavigationItemList/Wrapper.js +22 -0
  9. package/admin/src/components/NavigationItemList/index.js +56 -0
  10. package/admin/src/components/PluginIcon/index.js +6 -0
  11. package/admin/src/components/Search/index.js +46 -83
  12. package/admin/src/index.js +49 -45
  13. package/admin/src/pages/App/index.js +31 -0
  14. package/admin/src/{containers → pages}/DataManagerProvider/actions.js +0 -0
  15. package/admin/src/{containers → pages}/DataManagerProvider/index.js +77 -84
  16. package/admin/src/{containers → pages}/DataManagerProvider/init.js +0 -0
  17. package/admin/src/pages/DataManagerProvider/reducer.js +125 -0
  18. package/admin/src/pages/View/components/NavigationContentHeader/index.js +18 -0
  19. package/admin/src/pages/View/components/NavigationHeader/index.js +60 -0
  20. package/admin/src/pages/View/components/NavigationItemForm/index.js +403 -0
  21. package/admin/src/{containers → pages}/View/components/NavigationItemForm/utils/form.js +2 -2
  22. package/admin/src/pages/View/components/NavigationItemPopup/NavigationItemPopupFooter.js +40 -0
  23. package/admin/src/pages/View/components/NavigationItemPopup/NavigationItemPopupHeader.js +20 -0
  24. package/admin/src/{containers → pages}/View/components/NavigationItemPopup/index.js +16 -16
  25. package/admin/src/pages/View/index.js +221 -0
  26. package/admin/src/{containers → pages}/View/utils/enums.js +0 -0
  27. package/admin/src/{containers → pages}/View/utils/form.js +1 -1
  28. package/admin/src/{containers → pages}/View/utils/index.js +0 -0
  29. package/admin/src/{containers → pages}/View/utils/parsers.js +13 -12
  30. package/admin/src/pluginId.js +3 -2
  31. package/admin/src/translations/en.json +47 -38
  32. package/admin/src/translations/fr.json +7 -1
  33. package/admin/src/utils/getTrad.js +2 -2
  34. package/package.json +18 -7
  35. package/server/bootstrap.js +41 -0
  36. package/server/config/index.js +8 -0
  37. package/server/config.js +8 -0
  38. package/server/content-types/audience/index.js +9 -0
  39. package/{models/audience.js → server/content-types/audience/lifecycle.js} +0 -0
  40. package/{models/audience.settings.json → server/content-types/audience/schema.json} +4 -2
  41. package/server/content-types/index.js +13 -0
  42. package/server/content-types/navigation/index.js +9 -0
  43. package/{models/navigation.js → server/content-types/navigation/lifecycle.js} +0 -0
  44. package/server/content-types/navigation/schema.js +45 -0
  45. package/server/content-types/navigation-item/index.js +9 -0
  46. package/{models/navigationItem.js → server/content-types/navigation-item/lifecycle.js} +0 -0
  47. package/{models/navigationItem.settings.json → server/content-types/navigation-item/schema.json} +16 -12
  48. package/server/content-types/navigations-items-related/index.js +9 -0
  49. package/{models/navigations_items_related.js → server/content-types/navigations-items-related/lifecycle.js} +0 -0
  50. package/{models/navigations_items_related.settings.json → server/content-types/navigations-items-related/schema.json} +4 -2
  51. package/server/controllers/index.js +7 -0
  52. package/{controllers → server/controllers}/navigation.js +20 -31
  53. package/server/graphql/index.js +23 -0
  54. package/server/graphql/queries/index.js +17 -0
  55. package/server/graphql/queries/render-navigation-child.js +16 -0
  56. package/server/graphql/queries/render-navigation.js +15 -0
  57. package/server/graphql/resolvers-config.js +4 -0
  58. package/server/graphql/types/content-types-name-fields.js +8 -0
  59. package/server/graphql/types/content-types.js +16 -0
  60. package/server/graphql/types/create-navigation-item.js +17 -0
  61. package/server/graphql/types/create-navigation-related.js +8 -0
  62. package/server/graphql/types/create-navigation.js +7 -0
  63. package/server/graphql/types/index.js +15 -0
  64. package/server/graphql/types/navigation-config.js +9 -0
  65. package/server/graphql/types/navigation-details.js +10 -0
  66. package/server/graphql/types/navigation-item.js +29 -0
  67. package/server/graphql/types/navigation-related.js +23 -0
  68. package/server/graphql/types/navigation-render-type.js +4 -0
  69. package/server/graphql/types/navigation.js +9 -0
  70. package/server/register.js +5 -0
  71. package/server/routes/admin.js +38 -0
  72. package/server/routes/client.js +21 -0
  73. package/server/routes/index.js +4 -0
  74. package/{services → server/services}/__tests__/navigation.test.js +0 -0
  75. package/server/services/index.js +7 -0
  76. package/server/services/navigation.js +729 -0
  77. package/{services → server/services}/utils/constant.js +3 -1
  78. package/server/services/utils/functions.js +185 -0
  79. package/strapi-admin.js +1 -0
  80. package/strapi-server.js +20 -0
  81. package/__mocks__/helpers/another-plugin/blog-post.settings.json +0 -31
  82. package/__mocks__/helpers/another-plugin/pages.settings.json +0 -28
  83. package/__mocks__/helpers/blog-post.settings.json +0 -31
  84. package/__mocks__/helpers/home-page.settings.json +0 -4
  85. package/__mocks__/helpers/my-homepage.settings.json +0 -27
  86. package/__mocks__/helpers/pages.settings.json +0 -27
  87. package/__mocks__/helpers/strapi.js +0 -101
  88. package/admin/src/assets/images/icon-cross-blue.svg +0 -1
  89. package/admin/src/assets/images/icon_remove.svg +0 -19
  90. package/admin/src/components/Container/index.js +0 -7
  91. package/admin/src/components/Input/index.js +0 -41
  92. package/admin/src/components/Item/CardItem.js +0 -46
  93. package/admin/src/components/Item/CardItemLevelAdd.js +0 -41
  94. package/admin/src/components/Item/CardItemLevelWrapper.js +0 -27
  95. package/admin/src/components/Item/CardItemPath.js +0 -9
  96. package/admin/src/components/Item/CardItemRestore.js +0 -19
  97. package/admin/src/components/Item/CardItemTitle.js +0 -5
  98. package/admin/src/components/Item/CardWrapper.js +0 -78
  99. package/admin/src/components/ItemFooter/CardItemError.js +0 -11
  100. package/admin/src/components/ItemFooter/CardItemRelation.js +0 -18
  101. package/admin/src/components/ItemFooter/CardItemRelationStatus.js +0 -17
  102. package/admin/src/components/ItemFooter/CardItemType.js +0 -18
  103. package/admin/src/components/ItemFooter/Wrapper.js +0 -26
  104. package/admin/src/components/ItemFooter/index.js +0 -66
  105. package/admin/src/components/ItemOrdering/CardOrderingButton.js +0 -24
  106. package/admin/src/components/ItemOrdering/Wrapper.js +0 -24
  107. package/admin/src/components/ItemOrdering/index.js +0 -36
  108. package/admin/src/components/List/Container.js +0 -34
  109. package/admin/src/components/List/ListLevelRoot.js +0 -18
  110. package/admin/src/components/List/index.js +0 -81
  111. package/admin/src/components/Option/OptionButton.js +0 -18
  112. package/admin/src/components/Option/OptionSet.js +0 -14
  113. package/admin/src/components/Option/Wrapper.js +0 -15
  114. package/admin/src/components/Option/index.js +0 -47
  115. package/admin/src/components/Select/ClearIndicator.js +0 -15
  116. package/admin/src/components/Select/DropdownIndicator.js +0 -39
  117. package/admin/src/components/Select/ErrorMessage.js +0 -10
  118. package/admin/src/components/Select/IndicatorSeparator.js +0 -3
  119. package/admin/src/components/Select/MultiValueContainer.js +0 -43
  120. package/admin/src/components/Select/StyledOption.js +0 -11
  121. package/admin/src/components/Select/index.js +0 -68
  122. package/admin/src/components/Select/utils/styles.js +0 -92
  123. package/admin/src/containers/App/Wrapper.js +0 -14
  124. package/admin/src/containers/App/index.js +0 -34
  125. package/admin/src/containers/DataManagerProvider/reducer.js +0 -136
  126. package/admin/src/containers/DetailsView/Wrapper.js +0 -21
  127. package/admin/src/containers/DetailsView/index.js +0 -111
  128. package/admin/src/containers/Initializer/index.js +0 -26
  129. package/admin/src/containers/ListView/Footer.js +0 -56
  130. package/admin/src/containers/ListView/components.js +0 -138
  131. package/admin/src/containers/ListView/index.js +0 -54
  132. package/admin/src/containers/View/FadedWrapper.js +0 -51
  133. package/admin/src/containers/View/HeaderForm.js +0 -9
  134. package/admin/src/containers/View/HeaderFormCell.js +0 -25
  135. package/admin/src/containers/View/Wrapper.js +0 -17
  136. package/admin/src/containers/View/components/NavigationItemForm/ModalFooter.js +0 -45
  137. package/admin/src/containers/View/components/NavigationItemForm/index.js +0 -427
  138. package/admin/src/containers/View/components/NavigationItemPopup/MediumPopup.js +0 -6
  139. package/admin/src/containers/View/index.js +0 -240
  140. package/admin/src/lifecycles.js +0 -3
  141. package/admin/src/permissions.js +0 -14
  142. package/config/functions/bootstrap.js +0 -138
  143. package/config/routes.json +0 -60
  144. package/config/schema.graphql.js +0 -209
  145. package/examples/audit-log-integrations.js.md +0 -38
  146. package/models/navigation.settings.json +0 -43
  147. package/public/assets/preview.png +0 -0
  148. package/services/navigation.js +0 -732
  149. package/services/utils/functions.js +0 -186
@@ -0,0 +1,729 @@
1
+ const pluralize = require('pluralize');
2
+ const {
3
+ find,
4
+ get,
5
+ isNil,
6
+ isObject,
7
+ isEmpty,
8
+ last,
9
+ upperFirst,
10
+ map,
11
+ toNumber,
12
+ isString,
13
+ first,
14
+
15
+ } = require('lodash');
16
+ const { validate: isUuid } = require('uuid');
17
+ const slugify = require('slugify');
18
+ const { KIND_TYPES } = require('./utils/constant');
19
+ const utilsFunctionsFactory = require('./utils/functions');
20
+ const { renderType } = require('../content-types/navigation/lifecycle');
21
+ const { type: itemType, additionalFields: configAdditionalFields } = require('../content-types/navigation-item').lifecycle;
22
+ const { NotFoundError } = require('@strapi/utils').errors
23
+ const excludedContentTypes = ['strapi::'];
24
+ const contentTypesNameFieldsDefaults = ['title', 'subject', 'name'];
25
+
26
+ module.exports = ({ strapi }) => {
27
+ const utilsFunctions = utilsFunctionsFactory(strapi);
28
+
29
+ return {
30
+ // Get all available navigations
31
+ async get() {
32
+ const { masterModel } = utilsFunctions.extractMeta(strapi.plugins);
33
+ const entities = await strapi
34
+ .query(masterModel.uid)
35
+ .findMany({
36
+ paggination: {
37
+ limit: -1,
38
+ }
39
+ });
40
+ return entities;
41
+ },
42
+
43
+ async getById(id) {
44
+ const { masterModel, itemModel } = utilsFunctions.extractMeta(strapi.plugins);
45
+ const entity = await strapi
46
+ .query(masterModel.uid)
47
+ .findOne({ where: { id } });
48
+
49
+ const entityItems = await strapi
50
+ .query(itemModel.uid)
51
+ .findMany({
52
+ where: {
53
+ master: id,
54
+ },
55
+ paggination: {
56
+ limit: -1,
57
+ },
58
+ sort: ['order:asc'],
59
+ populate: ['related', 'parent']
60
+ });
61
+ const entities = await this.getRelatedItems(entityItems);
62
+ return {
63
+ ...entity,
64
+ items: utilsFunctions.buildNestedStructure(entities),
65
+ };
66
+ },
67
+
68
+ // Get plugin config
69
+ async config() {
70
+ const { pluginName, audienceModel } = utilsFunctions.extractMeta(strapi.plugins);
71
+ const additionalFields = strapi.plugin(pluginName).config('additionalFields')
72
+ const contentTypesNameFields = strapi.plugin(pluginName).config('contentTypesNameFields');
73
+ const allowedLevels = strapi.plugin(pluginName).config('allowedLevels');
74
+
75
+ let extendedResult = {};
76
+ const result = {
77
+ contentTypes: await strapi.plugin(pluginName).service('navigation').configContentTypes(),
78
+ contentTypesNameFields: {
79
+ default: contentTypesNameFieldsDefaults,
80
+ ...(isObject(contentTypesNameFields) ? contentTypesNameFields : {}),
81
+ },
82
+ allowedLevels,
83
+ additionalFields,
84
+ };
85
+
86
+ if (additionalFields.includes(configAdditionalFields.AUDIENCE)) {
87
+ const audienceItems = await strapi
88
+ .query(`plugin::${pluginName}.${audienceModel.modelName}`)
89
+ .findMany({
90
+ paggination: {
91
+ limit: -1,
92
+ }
93
+ });
94
+ extendedResult = {
95
+ ...extendedResult,
96
+ availableAudience: audienceItems,
97
+ };
98
+ }
99
+ return {
100
+ ...result,
101
+ ...extendedResult,
102
+ };
103
+ },
104
+
105
+ async configContentTypes() {
106
+ const eligibleContentTypes =
107
+ await Promise.all(
108
+ strapi.plugin('navigation').config('contentTypes')
109
+ .filter(contentType => !!strapi.contentTypes[contentType])
110
+ .map(
111
+ async (key) => {
112
+ if (find(excludedContentTypes, name => key.includes(name))) { // exclude internal content types
113
+ return;
114
+ }
115
+ const item = strapi.contentTypes[key];
116
+ const { kind, options, uid } = item;
117
+ const { draftAndPublish } = options;
118
+
119
+ const isSingleType = kind === KIND_TYPES.SINGLE;
120
+ const isSingleTypeWithPublishFlow = isSingleType && draftAndPublish;
121
+
122
+ const returnType = (available) => ({
123
+ key,
124
+ available,
125
+ });
126
+
127
+ if (isSingleType) {
128
+ if (isSingleTypeWithPublishFlow) {
129
+ const itemsCountOrBypass = isSingleTypeWithPublishFlow ?
130
+ await strapi.query(uid).count({
131
+ publicationState: 'live',
132
+ }) :
133
+ true;
134
+ return returnType(itemsCountOrBypass !== 0);
135
+ }
136
+ const isAvailable = await strapi.query(uid).count();
137
+ return isAvailable === 1 ? returnType(true) : undefined;
138
+ }
139
+ return returnType(true);
140
+ },
141
+ ),
142
+ );
143
+ return eligibleContentTypes
144
+ .filter(key => key)
145
+ .map(({ key, available }) => {
146
+ const item = strapi.contentTypes[key];
147
+ const relatedField = (item.associations || []).find(_ => _.model === 'navigationitem');
148
+ const { uid, options, info, collectionName, modelName, apiName, plugin, kind } = item;
149
+ const { name, description } = info;
150
+ const { isManaged, hidden, templateName } = options;
151
+ const findRouteConfig = find(get(strapi.api, `[${modelName}].config.routes`, []),
152
+ route => route.handler.includes('.find'));
153
+ const findRoutePath = findRouteConfig && findRouteConfig.path.split('/')[1];
154
+ const apiPath = findRoutePath && (findRoutePath !== apiName) ? findRoutePath : apiName || modelName;
155
+ const isSingle = kind === KIND_TYPES.SINGLE;
156
+ const endpoint = isSingle ? apiPath : pluralize(apiPath);
157
+ const relationName = utilsFunctions.singularize(modelName);
158
+ const relationNameParts = last(uid.split('.')).split('-');
159
+ const contentTypeName = relationNameParts.length > 1 ? relationNameParts.reduce(
160
+ (prev, curr) => `${prev}${upperFirst(curr)}`, '') : upperFirst(modelName);
161
+ const labelSingular = name ||
162
+ (upperFirst(relationNameParts.length > 1 ? relationNameParts.join(' ') : relationName));
163
+ return {
164
+ uid,
165
+ name: relationName,
166
+ isSingle,
167
+ description,
168
+ collectionName,
169
+ contentTypeName,
170
+ label: isSingle ? labelSingular : pluralize(name || labelSingular),
171
+ relatedField: relatedField ? relatedField.alias : undefined,
172
+ labelSingular: utilsFunctions.singularize(labelSingular),
173
+ endpoint,
174
+ plugin,
175
+ available,
176
+ visible: (isManaged || isNil(isManaged)) && !hidden,
177
+ templateName,
178
+ };
179
+ })
180
+ .filter((item) => item && item.visible);
181
+ },
182
+
183
+ async getRelatedItems(entityItems) {
184
+ const relatedTypes = new Set(entityItems.flatMap((item) => get(item.related, 'related_type')));
185
+ const groupedItems = Array.from(relatedTypes).filter((relatedType) => relatedType).reduce(
186
+ (acc, relatedType) => Object.assign(acc, {
187
+ [relatedType]: [
188
+ ...(acc[relatedType] || []),
189
+ ...entityItems
190
+ .filter((item => item.related?.related_type === relatedType))
191
+ .flatMap((item) => Object.assign(item.related, { navigationItemId: item.id })),
192
+ ],
193
+ }),
194
+ {});
195
+
196
+ const data = new Map(
197
+ (
198
+ await Promise.all(
199
+ Object.entries(groupedItems)
200
+ .map(async ([model, related]) => {
201
+ const relationData = await strapi
202
+ .query(model)
203
+ .findMany({
204
+ where: {
205
+ id: { $in: map(related, 'related_id') }
206
+ }
207
+ });
208
+ return relationData
209
+ .flatMap(_ =>
210
+ Object.assign(
211
+ _,
212
+ {
213
+ __contentType: model,
214
+ navigationItemId: related.find(
215
+ ({ related_id }) => related_id === _.id.toString())?.navigationItemId,
216
+ },
217
+ ),
218
+ );
219
+ }),
220
+ )
221
+ )
222
+ .flat(1)
223
+ .map(_ => [_.navigationItemId, _]),
224
+ );
225
+ return entityItems
226
+ .map(({ related, ...item }) => {
227
+ const relatedData = data.get(item.id);
228
+ if (relatedData) {
229
+ return Object.assign(item, { related: [relatedData] });
230
+ }
231
+ return item;
232
+ });
233
+ },
234
+
235
+ async getContentTypeItems(model) {
236
+ try {
237
+ const contentTypeItems = await strapi.query(model).findMany()
238
+ return contentTypeItems;
239
+ } catch (err) {
240
+ return [];
241
+ }
242
+ },
243
+
244
+ async post(payload, auditLog) {
245
+ const { masterModel, service } = utilsFunctions.extractMeta(strapi.plugins);
246
+ const { name, visible } = payload;
247
+ const data = {
248
+ name,
249
+ slug: slugify(name).toLowerCase(),
250
+ visible,
251
+ }
252
+
253
+ const existingEntity = await strapi
254
+ .query(masterModel.uid)
255
+ .create({ data });
256
+
257
+ return service
258
+ .createBranch(payload.items, existingEntity, null)
259
+ .then(() => service.getById(existingEntity.id))
260
+ .then((newEntity) => {
261
+ utilsFunctions.sendAuditLog(auditLog, 'onChangeNavigation',
262
+ { actionType: 'CREATE', oldEntity: existingEntity, newEntity });
263
+ return newEntity;
264
+ });
265
+ },
266
+
267
+ async put(id, payload, auditLog) {
268
+ const { masterModel, service } = utilsFunctions.extractMeta(strapi.plugins);
269
+ const { name, visible } = payload;
270
+
271
+ const existingEntity = await service.getById(id);
272
+ const entityNameHasChanged = existingEntity.name !== name || existingEntity.visible !== visible;
273
+ if (entityNameHasChanged) {
274
+
275
+ await strapi.query(masterModel.uid).update({
276
+ where: { id },
277
+ data: {
278
+ name: entityNameHasChanged ? name : existingEntity.name,
279
+ slug: entityNameHasChanged ? slugify(name).toLowerCase() : existingEntity.slug,
280
+ visible,
281
+ },
282
+ });
283
+ }
284
+ return service
285
+ .analyzeBranch(payload.items, existingEntity, null)
286
+ .then((auditLogsOperations) =>
287
+ Promise.all([
288
+ auditLog ? utilsFunctions.prepareAuditLog((auditLogsOperations || []).flat(Number.MAX_SAFE_INTEGER)) : [],
289
+ service.getById(existingEntity.id)],
290
+ ))
291
+ .then(([actionType, newEntity]) => {
292
+ utilsFunctions.sendAuditLog(auditLog, 'onChangeNavigation',
293
+ { actionType, oldEntity: existingEntity, newEntity });
294
+ return newEntity;
295
+ });
296
+ },
297
+
298
+ async renderChildren(
299
+ idOrSlug,
300
+ childUIKey,
301
+ type = renderType.FLAT,
302
+ menuOnly = false,
303
+ ) {
304
+ const { service } = utilsFunctions.extractMeta(strapi.plugins);
305
+ const findById = !isNaN(toNumber(idOrSlug)) || isUuid(idOrSlug);
306
+ const criteria = findById ? { id: idOrSlug } : { slug: idOrSlug };
307
+ const filter = type === renderType.FLAT ? null : childUIKey;
308
+
309
+ const itemCriteria = {
310
+ ...(menuOnly && { menuAttached: true }),
311
+ ...(type === renderType.FLAT ? { uiRouterKey: childUIKey } : {}),
312
+ };
313
+
314
+ return service.renderType(type, criteria, itemCriteria, filter);
315
+ },
316
+
317
+ async render(idOrSlug, type = renderType.FLAT, menuOnly = false) {
318
+ const { service } = utilsFunctions.extractMeta(strapi.plugins);
319
+
320
+ const findById = !isNaN(toNumber(idOrSlug)) || isUuid(idOrSlug);
321
+ const criteria = findById ? { id: idOrSlug } : { slug: idOrSlug };
322
+ const itemCriteria = menuOnly ? { menuAttached: true } : {};
323
+
324
+ return service.renderType(type, criteria, itemCriteria);
325
+ },
326
+
327
+ async renderType(type = renderType.FLAT, criteria = {}, itemCriteria = {}, filter = null) {
328
+ const { pluginName, service, masterModel, itemModel } = utilsFunctions.extractMeta(
329
+ strapi.plugins,
330
+ );
331
+
332
+ const entity = await strapi
333
+ .query(masterModel.uid)
334
+ .findOne({
335
+ where: {
336
+ ...criteria,
337
+ visible: true,
338
+ }
339
+ });
340
+ if (entity && entity.id) {
341
+ const entities = await strapi.query(itemModel.uid).findMany({
342
+ where: {
343
+ master: entity.id,
344
+ ...itemCriteria,
345
+ },
346
+ paggination: {
347
+ limit: -1,
348
+ },
349
+ sort: ['order:asc'],
350
+ populate: ['related', 'audience', 'parent'],
351
+ });
352
+
353
+ if (!entities) {
354
+ return [];
355
+ }
356
+ const items = await this.getRelatedItems(entities);
357
+ const { contentTypes, contentTypesNameFields } = await service.config();
358
+
359
+ switch (type?.toLowerCase()) {
360
+ case renderType.TREE:
361
+ case renderType.RFR:
362
+ const getTemplateName = await utilsFunctions.templateNameFactory(items, strapi, contentTypes);
363
+ const itemParser = (item, path = '', field) => {
364
+ const isExternal = item.type === itemType.EXTERNAL;
365
+ const parentPath = isExternal ? undefined : `${path === '/' ? '' : path}/${item.path === '/'
366
+ ? ''
367
+ : item.path}`;
368
+ const slug = isString(parentPath) ? slugify(
369
+ (first(parentPath) === '/' ? parentPath.substring(1) : parentPath).replace(/\//g, '-')) : undefined;
370
+ const lastRelated = item.related ? last(item.related) : undefined;
371
+ return {
372
+ id: item.id,
373
+ title: utilsFunctions.composeItemTitle(item, contentTypesNameFields, contentTypes),
374
+ menuAttached: item.menuAttached,
375
+ path: isExternal ? item.externalPath : parentPath,
376
+ type: item.type,
377
+ uiRouterKey: item.uiRouterKey,
378
+ slug: !slug && item.uiRouterKey ? slugify(item.uiRouterKey) : slug,
379
+ external: isExternal,
380
+ related: isExternal || !lastRelated ? undefined : {
381
+ ...lastRelated,
382
+ __templateName: getTemplateName(lastRelated.relatedType || lastRelated.__contentType, lastRelated.id),
383
+ },
384
+ audience: !isEmpty(item.audience) ? item.audience.map(aItem => aItem.key) : undefined,
385
+ items: isExternal ? undefined : service.renderTree({
386
+ items,
387
+ id: item.id,
388
+ field,
389
+ path: parentPath,
390
+ itemParser,
391
+ }),
392
+ };
393
+ };
394
+ const treeStructure = service.renderTree({
395
+ items,
396
+ field: 'parent',
397
+ itemParser,
398
+ });
399
+
400
+ const filteredStructure = filter
401
+ ? treeStructure.filter((item) => item.uiRouterKey === filter)
402
+ : treeStructure;
403
+
404
+ if (type === renderType.RFR) {
405
+ return service.renderRFR({
406
+ items: filteredStructure,
407
+ contentTypes,
408
+ });
409
+ }
410
+ return filteredStructure;
411
+ default:
412
+ return items
413
+ .filter(utilsFunctions.filterOutUnpublished)
414
+ .map((item) => ({
415
+ ...item,
416
+ audience: item.audience?.map(_ => _.key),
417
+ title: utilsFunctions.composeItemTitle(item, contentTypesNameFields, contentTypes),
418
+ related: item.related?.map(({ localizations, ...item }) => item),
419
+ items: null,
420
+ }));
421
+ }
422
+ }
423
+ throw new NotFoundError();
424
+ },
425
+
426
+ renderTree({
427
+ items = [],
428
+ id = null,
429
+ field = 'parent',
430
+ path = '',
431
+ itemParser = (i) => i,
432
+ }) {
433
+ return items
434
+ .filter(
435
+ (item) => {
436
+ if (item[field] === null && id === null) {
437
+ return true;
438
+ }
439
+ let data = item[field];
440
+ if (data && typeof id === 'string') {
441
+ data = data.toString();
442
+ }
443
+ return (data && data === id) || (isObject(item[field]) && (item[field].id === id));
444
+ },
445
+ )
446
+ .filter(utilsFunctions.filterOutUnpublished)
447
+ .map(item => itemParser({
448
+ ...item,
449
+ }, path, field));
450
+ },
451
+
452
+ renderRFR({ items, parent = null, parentNavItem = null, contentTypes = [] }) {
453
+ const { service } = utilsFunctions.extractMeta(strapi.plugins);
454
+ let pages = {};
455
+ let nav = {};
456
+ let navItems = [];
457
+
458
+ items.forEach(item => {
459
+ const { items: itemChilds, ...itemProps } = item;
460
+ const itemNav = service.renderRFRNav(itemProps);
461
+ const itemPage = service.renderRFRPage({
462
+ item: itemProps,
463
+ parent,
464
+ });
465
+
466
+ if (item.type === itemType.INTERNAL) {
467
+ pages = {
468
+ ...pages,
469
+ [itemPage.id]: {
470
+ ...itemPage,
471
+ },
472
+ };
473
+ }
474
+
475
+ if (item.menuAttached) {
476
+ navItems.push(itemNav);
477
+ }
478
+
479
+ if (!parent) {
480
+ nav = {
481
+ ...nav,
482
+ root: navItems,
483
+ };
484
+ } else {
485
+ const navLevel = navItems
486
+ .filter(navItem => navItem.type === itemType.INTERNAL.toLowerCase());
487
+ if (!isEmpty(navLevel))
488
+ nav = {
489
+ ...nav,
490
+ [parent]: [].concat(parentNavItem ? parentNavItem : [], navLevel),
491
+ };
492
+ }
493
+
494
+ if (!isEmpty(itemChilds)) {
495
+ const { nav: nestedNavs } = service.renderRFR({
496
+ items: itemChilds,
497
+ parent: itemPage.id,
498
+ parentNavItem: itemNav,
499
+ contentTypes,
500
+ });
501
+ const { pages: nestedPages } = service.renderRFR({
502
+ items: itemChilds.filter(child => child.type === itemType.INTERNAL),
503
+ parent: itemPage.id,
504
+ parentNavItem: itemNav,
505
+ contentTypes,
506
+ });
507
+ pages = {
508
+ ...pages,
509
+ ...nestedPages,
510
+ };
511
+ nav = {
512
+ ...nav,
513
+ ...nestedNavs,
514
+ };
515
+ }
516
+ });
517
+
518
+ return {
519
+ pages,
520
+ nav,
521
+ };
522
+ },
523
+
524
+ renderRFRNav(item) {
525
+ const { uiRouterKey, title, path, type, audience } = item;
526
+ return {
527
+ label: title,
528
+ type: type.toLowerCase(),
529
+ page: type === itemType.INTERNAL ? uiRouterKey : undefined,
530
+ url: type === itemType.EXTERNAL ? path : undefined,
531
+ audience,
532
+ };
533
+ },
534
+
535
+ renderRFRPage({ item, parent }) {
536
+ const { uiRouterKey, title, path, slug, related, type, audience, menuAttached } = item;
537
+ const { __contentType, id, __templateName } = related || {};
538
+ const contentType = __contentType || '';
539
+ return {
540
+ id: uiRouterKey,
541
+ title,
542
+ templateName: __templateName,
543
+ related: type === itemType.INTERNAL ? {
544
+ contentType,
545
+ id,
546
+ } : undefined,
547
+ path,
548
+ slug,
549
+ parent,
550
+ audience,
551
+ menuAttached,
552
+ };
553
+ },
554
+
555
+ createBranch(items = [], masterEntity = null, parentItem = null, operations = {}) {
556
+ const { itemModel, service } = utilsFunctions.extractMeta(strapi.plugins);
557
+ return Promise.all(
558
+ items.map(async (item) => {
559
+ operations.create = true;
560
+ const { parent, master, related, ...params } = item;
561
+ const relatedItems = await this.getIdsRelated(related, master);
562
+ const data = {
563
+ ...params,
564
+ related: relatedItems,
565
+ master: masterEntity,
566
+ parent: parentItem ? { ...parentItem, _id: parentItem.id } : null,
567
+ }
568
+ const navigationItem = await strapi
569
+ .query(itemModel.uid)
570
+ .create({ data });
571
+ return !isEmpty(item.items)
572
+ ? service.createBranch(
573
+ item.items,
574
+ masterEntity,
575
+ navigationItem,
576
+ operations,
577
+ )
578
+ : operations;
579
+ }),
580
+ );
581
+ },
582
+
583
+ removeBranch(items = [], operations = {}) {
584
+ const { itemModel, service } = utilsFunctions.extractMeta(strapi.plugins);
585
+ return Promise.all(
586
+ items
587
+ .filter(item => item.id)
588
+ .map(async (item) => {
589
+ operations.remove = true;
590
+ const { id, related, master } = item;
591
+ await Promise.all([
592
+ strapi
593
+ .query(itemModel.uid)
594
+ .delete({ where: { id } }),
595
+ this.removeRelated(related, master),
596
+ ]);
597
+ return !isEmpty(item.items)
598
+ ? service.removeBranch(
599
+ item.items,
600
+ operations,
601
+ )
602
+ : operations;
603
+ }),
604
+ );
605
+ },
606
+
607
+ async updateBranch(toUpdate, masterEntity, parentItem, operations) {
608
+ const { itemModel, service } = utilsFunctions.extractMeta(strapi.plugins);
609
+ const databaseModel = strapi.query(itemModel.uid);
610
+ return Promise.all(
611
+ toUpdate.map(async (item) => {
612
+ operations.update = true;
613
+ const { id, updated, parent, master, related, items, ...params } = item;
614
+ let currentItem;
615
+ if (updated) {
616
+ const relatedItems = await this.getIdsRelated(related, master);
617
+ currentItem = await databaseModel
618
+ .update({
619
+ where: { id },
620
+ data: {
621
+ ...params,
622
+ related: relatedItems,
623
+ master: masterEntity,
624
+ parent: parentItem ? { ...parentItem, _id: parentItem.id } : null,
625
+ },
626
+ });
627
+ } else {
628
+ currentItem = item;
629
+ }
630
+ return !isEmpty(items)
631
+ ? service.analyzeBranch(
632
+ items,
633
+ masterEntity,
634
+ currentItem,
635
+ operations,
636
+ )
637
+ : operations;
638
+ }),
639
+ );
640
+ },
641
+
642
+ analyzeBranch(items = [], masterEntity = null, parentItem = null, prevOperations = {}) {
643
+ const { service } = utilsFunctions.extractMeta(strapi.plugins);
644
+ const { toCreate, toRemove, toUpdate } = items
645
+ .reduce((acc, _) => {
646
+ const branchName = service.getBranchName(_);
647
+ if (branchName) {
648
+ return { ...acc, [branchName]: [...acc[branchName], _] };
649
+ }
650
+ return acc;
651
+ },
652
+ { toRemove: [], toCreate: [], toUpdate: [] },
653
+ );
654
+ const operations = {
655
+ create: prevOperations.create || !!toCreate.length,
656
+ update: prevOperations.update || !!toUpdate.length,
657
+ remove: prevOperations.remove || !!toRemove.length,
658
+ };
659
+ return utilsFunctions.checkDuplicatePath(parentItem || masterEntity, toCreate.concat(toUpdate))
660
+ .then(() => Promise.all(
661
+ [
662
+ service.createBranch(toCreate, masterEntity, parentItem, operations),
663
+ service.removeBranch(toRemove, operations),
664
+ service.updateBranch(toUpdate, masterEntity, parentItem, operations),
665
+ ],
666
+ ));
667
+ },
668
+
669
+ getIdsRelated(relatedItems, master) {
670
+ if (relatedItems) {
671
+ return Promise.all(relatedItems.map(async relatedItem => {
672
+ try {
673
+
674
+ const model = strapi.query('plugin::navigation.navigations-items-related');
675
+ const entity = await model
676
+ .findOne({
677
+ where: {
678
+ related_id: relatedItem.refId,
679
+ related_type: relatedItem.ref,
680
+ field: relatedItem.field,
681
+ master,
682
+ }
683
+ });
684
+ if (!entity) {
685
+ const newEntity = {
686
+ master,
687
+ order: 1,
688
+ field: relatedItem.field,
689
+ related_id: relatedItem.refId,
690
+ related_type: relatedItem.ref,
691
+ };
692
+ return model.create({ data: newEntity }).then(({ id }) => id);
693
+ }
694
+ return entity.id;
695
+ } catch (e) {
696
+ console.error(e);
697
+ }
698
+ }));
699
+ }
700
+ },
701
+
702
+ removeRelated(relatedItems, master) {
703
+ return Promise.all(relatedItems.map(relatedItem => {
704
+ const model = strapi.query('plugin::navigation.navigations-items-related');
705
+ const entityToRemove = {
706
+ master,
707
+ field: relatedItem.field,
708
+ related_id: relatedItem.refId,
709
+ related_type: relatedItem.ref,
710
+ };
711
+ return model.delete({ where: entityToRemove }).then(({ id }) => id);
712
+ }));
713
+ },
714
+
715
+ getBranchName(item) {
716
+ const hasId = !isNil(item.id);
717
+ const toRemove = item.removed;
718
+ if (hasId && !toRemove) {
719
+ return 'toUpdate';
720
+ }
721
+ if (hasId && toRemove) {
722
+ return 'toRemove';
723
+ }
724
+ if (!hasId && !toRemove) {
725
+ return 'toCreate';
726
+ }
727
+ },
728
+ }
729
+ }