strapi-plugin-navigation 2.0.0-beta.1 → 2.0.0-beta.5
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/README.md +62 -104
- package/admin/src/components/Item/ItemCardHeader/index.js +2 -2
- package/admin/src/components/Item/index.js +3 -1
- package/admin/src/components/NavigationItemList/index.js +2 -0
- package/admin/src/components/Search/index.js +49 -0
- package/admin/src/pages/DataManagerProvider/index.js +6 -9
- package/admin/src/pages/DataManagerProvider/reducer.js +58 -69
- package/admin/src/pages/View/index.js +16 -4
- package/admin/src/pages/View/utils/parsers.js +1 -1
- package/admin/src/pluginId.js +2 -2
- package/package.json +7 -3
- package/server/config/index.js +8 -0
- package/server/controllers/navigation.js +21 -0
- package/server/graphql/index.js +23 -0
- package/server/graphql/queries/index.js +17 -0
- package/server/graphql/queries/render-navigation-child.js +16 -0
- package/server/graphql/queries/render-navigation.js +15 -0
- package/server/graphql/resolvers-config.js +4 -0
- package/server/graphql/types/content-types-name-fields.js +8 -0
- package/server/graphql/types/content-types.js +16 -0
- package/server/graphql/types/create-navigation-item.js +17 -0
- package/server/graphql/types/create-navigation-related.js +8 -0
- package/server/graphql/types/create-navigation.js +7 -0
- package/server/graphql/types/index.js +15 -0
- package/server/graphql/types/navigation-config.js +9 -0
- package/server/graphql/types/navigation-details.js +10 -0
- package/server/graphql/types/navigation-item.js +29 -0
- package/server/graphql/types/navigation-related.js +23 -0
- package/server/graphql/types/navigation-render-type.js +4 -0
- package/server/graphql/types/navigation.js +9 -0
- package/server/register.js +5 -0
- package/server/routes/client.js +21 -0
- package/server/routes/index.js +2 -1
- package/server/services/navigation.js +272 -6
- package/server/services/utils/functions.js +84 -2
- package/strapi-server.js +3 -1
|
@@ -8,11 +8,18 @@ const {
|
|
|
8
8
|
last,
|
|
9
9
|
upperFirst,
|
|
10
10
|
map,
|
|
11
|
+
toNumber,
|
|
12
|
+
isString,
|
|
13
|
+
first,
|
|
14
|
+
|
|
11
15
|
} = require('lodash');
|
|
16
|
+
const { validate: isUuid } = require('uuid');
|
|
17
|
+
const slugify = require('slugify');
|
|
12
18
|
const { KIND_TYPES } = require('./utils/constant');
|
|
13
19
|
const utilsFunctionsFactory = require('./utils/functions');
|
|
14
|
-
const {
|
|
15
|
-
|
|
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
|
|
16
23
|
const excludedContentTypes = ['strapi::'];
|
|
17
24
|
const contentTypesNameFieldsDefaults = ['title', 'subject', 'name'];
|
|
18
25
|
|
|
@@ -37,7 +44,7 @@ module.exports = ({ strapi }) => {
|
|
|
37
44
|
const { masterModel, itemModel } = utilsFunctions.extractMeta(strapi.plugins);
|
|
38
45
|
const entity = await strapi
|
|
39
46
|
.query(masterModel.uid)
|
|
40
|
-
.findOne({ where: { id }});
|
|
47
|
+
.findOne({ where: { id } });
|
|
41
48
|
|
|
42
49
|
const entityItems = await strapi
|
|
43
50
|
.query(itemModel.uid)
|
|
@@ -193,9 +200,11 @@ module.exports = ({ strapi }) => {
|
|
|
193
200
|
.map(async ([model, related]) => {
|
|
194
201
|
const relationData = await strapi
|
|
195
202
|
.query(model)
|
|
196
|
-
.findMany({
|
|
197
|
-
|
|
198
|
-
|
|
203
|
+
.findMany({
|
|
204
|
+
where: {
|
|
205
|
+
id: { $in: map(related, 'related_id') }
|
|
206
|
+
}
|
|
207
|
+
});
|
|
199
208
|
return relationData
|
|
200
209
|
.flatMap(_ =>
|
|
201
210
|
Object.assign(
|
|
@@ -286,6 +295,263 @@ module.exports = ({ strapi }) => {
|
|
|
286
295
|
});
|
|
287
296
|
},
|
|
288
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
|
+
|
|
289
555
|
createBranch(items = [], masterEntity = null, parentItem = null, operations = {}) {
|
|
290
556
|
const { itemModel, service } = utilsFunctions.extractMeta(strapi.plugins);
|
|
291
557
|
return Promise.all(
|
|
@@ -2,9 +2,13 @@ const {
|
|
|
2
2
|
last,
|
|
3
3
|
isObject,
|
|
4
4
|
isEmpty,
|
|
5
|
+
flatten,
|
|
6
|
+
find,
|
|
7
|
+
isString,
|
|
8
|
+
get,
|
|
5
9
|
} = require('lodash');
|
|
6
10
|
|
|
7
|
-
const { type: itemType } = require('../../content-types/navigation-item');
|
|
11
|
+
const { type: itemType } = require('../../content-types/navigation-item/lifecycle');
|
|
8
12
|
const { NavigationError } = require('../../../utils/NavigationError');
|
|
9
13
|
const { TEMPLATE_DEFAULT } = require('./constant');
|
|
10
14
|
|
|
@@ -13,7 +17,7 @@ module.exports = ({ strapi }) => {
|
|
|
13
17
|
singularize(value = '') {
|
|
14
18
|
return last(value) === 's' ? value.substr(0, value.length - 1) : value;
|
|
15
19
|
},
|
|
16
|
-
|
|
20
|
+
|
|
17
21
|
extractMeta(plugins) {
|
|
18
22
|
const { navigation: plugin } = plugins;
|
|
19
23
|
const { navigation: service } = plugin.services;
|
|
@@ -99,5 +103,83 @@ module.exports = ({ strapi }) => {
|
|
|
99
103
|
return resolve();
|
|
100
104
|
});
|
|
101
105
|
},
|
|
106
|
+
|
|
107
|
+
async templateNameFactory(items, strapi, contentTypes = []) {
|
|
108
|
+
const flatRelated = flatten(items.map(i => i.related)).filter(_ => !!_);
|
|
109
|
+
const relatedMap = flatRelated.reduce((acc, curr) => {
|
|
110
|
+
if (!acc[curr.__contentType]) {
|
|
111
|
+
acc[curr.__contentType] = [];
|
|
112
|
+
}
|
|
113
|
+
acc[curr.__contentType].push(curr.id);
|
|
114
|
+
return acc;
|
|
115
|
+
}, {});
|
|
116
|
+
const responses = await Promise.all(
|
|
117
|
+
Object.entries(relatedMap)
|
|
118
|
+
.map(
|
|
119
|
+
([contentType, ids]) => {
|
|
120
|
+
const contentTypeUid = get(find(contentTypes, cnt => cnt.uid === contentType), 'uid');
|
|
121
|
+
return strapi.query(contentTypeUid)
|
|
122
|
+
.findMany({ id_in: ids, _limit: -1 })
|
|
123
|
+
.then(res => ({ [contentType]: res }))
|
|
124
|
+
}),
|
|
125
|
+
);
|
|
126
|
+
const relatedResponseMap = responses.reduce((acc, curr) => ({ ...acc, ...curr }), {});
|
|
127
|
+
const singleTypes = new Map(
|
|
128
|
+
contentTypes
|
|
129
|
+
.filter(x => x.isSingle)
|
|
130
|
+
.map(({ contentTypeName, templateName }) => [contentTypeName, templateName || contentTypeName])
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
return (contentType, id) => {
|
|
134
|
+
const template = get(relatedResponseMap[contentType].find(data => data.id === id), 'template');
|
|
135
|
+
|
|
136
|
+
if (template) {
|
|
137
|
+
const templateComponent = this.getTemplateComponentFromTemplate(template);
|
|
138
|
+
return get(templateComponent, 'options.templateName', TEMPLATE_DEFAULT);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (singleTypes.get(contentType)) {
|
|
142
|
+
return singleTypes.get(contentType);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return TEMPLATE_DEFAULT;
|
|
146
|
+
};
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
getTemplateComponentFromTemplate(template = []) {
|
|
150
|
+
const componentName = get(first(template), '__component');
|
|
151
|
+
return componentName ? strapi.components[componentName] : null;
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
composeItemTitle(item = {}, fields = {}, contentTypes = []) {
|
|
155
|
+
const { title, related } = item;
|
|
156
|
+
if (title) {
|
|
157
|
+
return isString(title) && !isEmpty(title) ? title : undefined;
|
|
158
|
+
} else if (related) {
|
|
159
|
+
const relationTitle = this.extractItemRelationTitle(isArray(related) ? last(related) : related, fields, { contentTypes });
|
|
160
|
+
return isString(relationTitle) && !isEmpty(relationTitle) ? relationTitle : undefined;
|
|
161
|
+
}
|
|
162
|
+
return undefined;
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
extractItemRelationTitle(relatedItem = {}, fields = {}, contentTypes = []) {
|
|
166
|
+
const { __contentType } = relatedItem;
|
|
167
|
+
const contentType = find(contentTypes, _ => _.contentTypeName === __contentType);
|
|
168
|
+
const { default: defaultFields = [] } = fields;
|
|
169
|
+
return get(fields, `${contentType ? contentType.collectionName : ''}`, defaultFields).map((_) => relatedItem[_]).filter((_) => _)[0] || '';
|
|
170
|
+
},
|
|
171
|
+
|
|
172
|
+
filterOutUnpublished(item) {
|
|
173
|
+
const relatedItem = item.related && last(item.related);
|
|
174
|
+
const isHandledByPublshFlow = relatedItem ? 'published_at' in relatedItem : false;
|
|
175
|
+
|
|
176
|
+
if (isHandledByPublshFlow) {
|
|
177
|
+
const isRelatedDefinedAndPublished = relatedItem ?
|
|
178
|
+
isHandledByPublshFlow && get(relatedItem, 'published_at') :
|
|
179
|
+
false;
|
|
180
|
+
return item.type === itemType.INTERNAL ? isRelatedDefinedAndPublished : true;
|
|
181
|
+
}
|
|
182
|
+
return (item.type === itemType.EXTERNAL) || relatedItem;
|
|
183
|
+
},
|
|
102
184
|
};
|
|
103
185
|
}
|
package/strapi-server.js
CHANGED
|
@@ -4,6 +4,7 @@ const routes = require('./server/routes');
|
|
|
4
4
|
const controllers = require('./server/controllers');
|
|
5
5
|
const contentTypes = require('./server/content-types');
|
|
6
6
|
const config = require('./server/config');
|
|
7
|
+
const register = require('./server/register');
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
module.exports = () => {
|
|
@@ -13,6 +14,7 @@ module.exports = () => {
|
|
|
13
14
|
routes,
|
|
14
15
|
controllers,
|
|
15
16
|
services,
|
|
16
|
-
contentTypes
|
|
17
|
+
contentTypes,
|
|
18
|
+
register,
|
|
17
19
|
};
|
|
18
20
|
};
|