strapi-plugin-navigation 2.0.2 β†’ 2.0.3

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 CHANGED
@@ -81,7 +81,7 @@ Complete installation requirements are exact same as for Strapi itself and can b
81
81
 
82
82
  **Supported Strapi versions**:
83
83
 
84
- - Strapi v4.0.7 (recently tested)
84
+ - Strapi v4.1.0 (recently tested)
85
85
  - Strapi v4.x
86
86
 
87
87
  > This plugin is designed for **Strapi v4** and is not working with v3.x. To get version for **Strapi v3** install version [v1.x](https://github.com/VirtusLab-Open-Source/strapi-plugin-navigation/tree/strapi-v3).
@@ -233,6 +233,20 @@ For any role different than **Super Admin**, to access the **Navigation panel**
233
233
 
234
234
  ## πŸ•ΈοΈ Public API specification
235
235
 
236
+ ### Query Params
237
+
238
+ - `type` - Enum value representing structure type of returned navigation
239
+
240
+ **Example URL**: `https://localhost:1337/api/navigation/render/1?type=FLAT`
241
+
242
+ - `menu` - Boolean value for querying only navigation items that are attached to menu should be rendered eg.
243
+
244
+ **Example URL**: `https://localhost:1337/api/navigation/render/1?menu=true`
245
+
246
+ - `path` - String value for querying navigation items by its path
247
+
248
+ **Example URL**: `https://localhost:1337/api/navigation/render/1?path=/home/about-us`
249
+
236
250
  ### Render
237
251
 
238
252
  `GET <host>/api/navigation/render/<idOrSlug>?type=<type>`
@@ -48,7 +48,7 @@ const itemModelMock = {
48
48
  id: 1,
49
49
  title: "home",
50
50
  type: "INTERNAL",
51
- path: "home1",
51
+ path: "home",
52
52
  externalPath: null,
53
53
  uiRouterKey: "home",
54
54
  menuAttached: true,
@@ -164,8 +164,8 @@ const NavigationItemForm = ({
164
164
  key: get(item, 'id'),
165
165
  metadatas: {
166
166
  intlLabel: {
167
- id: label || " ",
168
- defaultMessage: label || " ",
167
+ id: label || `${item.__collectionUid} ${item.id}`,
168
+ defaultMessage: label || `${item.__collectionUid} ${item.id}`,
169
169
  }
170
170
  },
171
171
  value: item.id,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strapi-plugin-navigation",
3
- "version": "2.0.2",
3
+ "version": "2.0.3",
4
4
  "description": "Strapi - Navigation plugin",
5
5
  "strapi": {
6
6
  "name": "navigation",
@@ -17,7 +17,7 @@
17
17
  "test:unit": "jest --verbose --coverage"
18
18
  },
19
19
  "dependencies": {
20
- "@strapi/utils": "^4.0.7",
20
+ "@strapi/utils": "^4.1.0",
21
21
  "uuid": "^8.3.0",
22
22
  "bad-words": "^3.0.3",
23
23
  "lodash": "^4.17.11",
@@ -74,13 +74,14 @@ module.exports = ({strapi}) => ({
74
74
  },
75
75
  async render(ctx) {
76
76
  const { params, query = {} } = ctx;
77
- const { type, menu: menuOnly } = query;
77
+ const { type, menu: menuOnly, path: rootPath } = query;
78
78
  const { idOrSlug } = parseParams(params);
79
- return getService().render(
79
+ return getService().render({
80
80
  idOrSlug,
81
81
  type,
82
82
  menuOnly,
83
- );
83
+ rootPath
84
+ });
84
85
  },
85
86
  async renderChild(ctx) {
86
87
  const { params, query = {} } = ctx;
@@ -5,11 +5,12 @@ module.exports = ({ strapi, nexus }) => {
5
5
  args: {
6
6
  navigationIdOrSlug: nonNull(stringArg()),
7
7
  type: 'NavigationRenderType',
8
- menuOnly: booleanArg()
8
+ menuOnly: booleanArg(),
9
+ path: stringArg(),
9
10
  },
10
11
  resolve(obj, args) {
11
- const { navigationIdOrSlug, type, menuOnly } = args;
12
- return strapi.plugin('navigation').service('navigation').render(navigationIdOrSlug, type, menuOnly);
12
+ const { navigationIdOrSlug: idOrSlug, type, menuOnly, path: rootPath } = args;
13
+ return strapi.plugin('navigation').service('navigation').render({idOrSlug, type, menuOnly, rootPath});
13
14
  },
14
15
  };
15
16
  }
@@ -0,0 +1,48 @@
1
+ const { setupStrapi } = require('../../../__mocks__/strapi');
2
+ const utilsFunctionsFactory = require('../utils/functions');
3
+
4
+ describe('Utilities functions', () => {
5
+ beforeAll(async () => {
6
+ setupStrapi();
7
+ });
8
+
9
+ describe('Path rendering functions', () => {
10
+ it('Can build nested path structure', async () => {
11
+ const utilsFunctions = utilsFunctionsFactory({ strapi });
12
+ const { itemModel } = utilsFunctions.extractMeta(strapi.plugins);
13
+ const rootPath = '/home/side';
14
+ const entities = await strapi
15
+ .query(itemModel.uid)
16
+ .findMany({
17
+ where: {
18
+ master: 1
19
+ }
20
+ });
21
+ const nested = utilsFunctions.buildNestedPaths({ items: entities });
22
+
23
+ expect(nested.length).toBe(2);
24
+ expect(nested[1].path).toBe(rootPath);
25
+ });
26
+
27
+ it('Can filter items by path', async () => {
28
+ const utilsFunctions = utilsFunctionsFactory({ strapi });
29
+ const { itemModel } = utilsFunctions.extractMeta(strapi.plugins);
30
+ const rootPath = '/home/side';
31
+ const entities = await strapi
32
+ .query(itemModel.uid)
33
+ .findMany({
34
+ where: {
35
+ master: 1
36
+ }
37
+ });
38
+ const {
39
+ root,
40
+ items
41
+ } = utilsFunctions.filterByPath(entities, rootPath);
42
+
43
+ expect(root).toBeDefined();
44
+ expect(root.path).toBe(rootPath);
45
+ expect(items.length).toBe(1)
46
+ });
47
+ });
48
+ });
@@ -26,7 +26,7 @@ describe('Navigation services', () => {
26
26
  describe('Render navigation', () => {
27
27
  it('Can render branch in flat format', async () => {
28
28
  const service = strapi.plugin('navigation').service('navigation');
29
- const result = await service.render(1);
29
+ const result = await service.render({ idOrSlug: 1 });
30
30
 
31
31
  expect(result).toBeDefined()
32
32
  expect(result.length).toBe(2)
@@ -34,7 +34,10 @@ describe('Navigation services', () => {
34
34
 
35
35
  it('Can render branch in tree format', async () => {
36
36
  const service = strapi.plugin('navigation').service('navigation');
37
- const result = await service.render(1, "TREE");
37
+ const result = await service.render({
38
+ idOrSlug: 1,
39
+ type: "TREE"
40
+ });
38
41
 
39
42
  expect(result).toBeDefined()
40
43
  expect(result.length).toBeGreaterThan(0)
@@ -42,7 +45,10 @@ describe('Navigation services', () => {
42
45
 
43
46
  it('Can render branch in rfr format', async () => {
44
47
  const service = strapi.plugin('navigation').service('navigation');
45
- const result = await service.render(1, "RFR");
48
+ const result = await service.render({
49
+ idOrSlug: 1,
50
+ type: "RFR"
51
+ });
46
52
 
47
53
  expect(result).toBeDefined()
48
54
  expect(result.length).toBeGreaterThan(0)
@@ -50,11 +56,27 @@ describe('Navigation services', () => {
50
56
 
51
57
  it('Can render only menu attached elements', async () => {
52
58
  const service = strapi.plugin('navigation').service('navigation');
53
- const result = await service.render(1, "FLAT", true);
59
+ const result = await service.render({
60
+ idOrSlug: 1,
61
+ type: "FLAT",
62
+ menuOnly: true.valueOf,
63
+ });
54
64
 
55
65
  expect(result).toBeDefined()
56
66
  expect(result.length).toBe(1)
57
67
  });
68
+
69
+ it('Can render branch by path', async () => {
70
+ const service = strapi.plugin('navigation').service('navigation');
71
+ const result = await service.render({
72
+ idOrSlug: 1,
73
+ type: "FLAT",
74
+ rootPath: '/home/side'
75
+ });
76
+
77
+ expect(result).toBeDefined();
78
+ expect(result.length).toBe(1);
79
+ });
58
80
  });
59
81
 
60
82
  describe('Render child', () => {
@@ -66,7 +66,7 @@ module.exports = ({ strapi }) => {
66
66
  },
67
67
 
68
68
  async restart() {
69
- setImmediate(() => strapi.reload());
69
+ setImmediate(() => strapi.reload());
70
70
  },
71
71
 
72
72
  // Get plugin config
@@ -342,20 +342,20 @@ module.exports = ({ strapi }) => {
342
342
  ...(type === renderType.FLAT ? { uiRouterKey: childUIKey } : {}),
343
343
  };
344
344
 
345
- return service.renderType(type, criteria, itemCriteria, filter);
345
+ return service.renderType({ type, criteria, itemCriteria, filter });
346
346
  },
347
347
 
348
- async render(idOrSlug, type = renderType.FLAT, menuOnly = false) {
348
+ async render({ idOrSlug, type = renderType.FLAT, menuOnly = false, rootPath = null }) {
349
349
  const { service } = utilsFunctions.extractMeta(strapi.plugins);
350
350
 
351
351
  const findById = !isNaN(toNumber(idOrSlug)) || isUuid(idOrSlug);
352
352
  const criteria = findById ? { id: idOrSlug } : { slug: idOrSlug };
353
353
  const itemCriteria = menuOnly ? { menuAttached: true } : {};
354
354
 
355
- return service.renderType(type, criteria, itemCriteria);
355
+ return service.renderType({ type, criteria, itemCriteria, rootPath });
356
356
  },
357
357
 
358
- async renderType(type = renderType.FLAT, criteria = {}, itemCriteria = {}, filter = null) {
358
+ async renderType({ type = renderType.FLAT, criteria = {}, itemCriteria = {}, filter = null, rootPath = null }) {
359
359
  const { pluginName, service, masterModel, itemModel } = utilsFunctions.extractMeta(
360
360
  strapi.plugins,
361
361
  );
@@ -393,8 +393,8 @@ module.exports = ({ strapi }) => {
393
393
  const getTemplateName = await utilsFunctions.templateNameFactory(items, strapi, contentTypes);
394
394
  const itemParser = (item, path = '', field) => {
395
395
  const isExternal = item.type === itemType.EXTERNAL;
396
- const parentPath = isExternal ? undefined : `${path === '/' ? '' : path}/${item.path === '/'
397
- ? ''
396
+ const parentPath = isExternal ? undefined : `${path === '/' ? '' : path}/${first(item.path) === '/'
397
+ ? item.path.substring(1)
398
398
  : item.path}`;
399
399
  const slug = isString(parentPath) ? slugify(
400
400
  (first(parentPath) === '/' ? parentPath.substring(1) : parentPath).replace(/\//g, '-')) : undefined;
@@ -422,9 +422,17 @@ module.exports = ({ strapi }) => {
422
422
  }),
423
423
  };
424
424
  };
425
+
426
+ const {
427
+ items: itemsFilteredByPath,
428
+ root: rootElement,
429
+ } = utilsFunctions.filterByPath(items, rootPath);
430
+
425
431
  const treeStructure = service.renderTree({
426
- items,
432
+ items: isNil(rootPath) ? items : itemsFilteredByPath,
427
433
  field: 'parent',
434
+ id: get(rootElement, 'parent.id'),
435
+ path: get(rootElement, 'parent.path'),
428
436
  itemParser,
429
437
  });
430
438
 
@@ -440,7 +448,7 @@ module.exports = ({ strapi }) => {
440
448
  }
441
449
  return filteredStructure;
442
450
  default:
443
- return items
451
+ const publishedItems = items
444
452
  .filter(utilsFunctions.filterOutUnpublished)
445
453
  .map((item) => ({
446
454
  ...item,
@@ -449,6 +457,7 @@ module.exports = ({ strapi }) => {
449
457
  related: item.related?.map(({ localizations, ...item }) => item),
450
458
  items: null,
451
459
  }));
460
+ return isNil(rootPath) ? items : utilsFunctions.filterByPath(publishedItems, rootPath).items;
452
461
  }
453
462
  }
454
463
  throw new NotFoundError();
@@ -6,6 +6,7 @@ const {
6
6
  find,
7
7
  isString,
8
8
  get,
9
+ isNil,
9
10
  } = require('lodash');
10
11
 
11
12
  const { type: itemType } = require('../../content-types/navigation-item/lifecycle');
@@ -52,6 +53,45 @@ module.exports = ({ strapi }) => {
52
53
  });
53
54
  },
54
55
 
56
+ buildNestedPaths({items, id = null, field = 'parent', parentPath = null}){
57
+ return items
58
+ .filter(entity => {
59
+ if (entity[field] == null && id === null) {
60
+ return true;
61
+ }
62
+ let data = entity[field];
63
+ if (data && typeof id === 'string') {
64
+ data = data.toString();
65
+ }
66
+ return (data && data === id) || (isObject(entity[field]) && (entity[field].id === id));
67
+ })
68
+ .reduce((acc, entity) => {
69
+ const path = `${parentPath || ''}/${entity.path}`
70
+ return [
71
+ {
72
+ id: entity.id,
73
+ parent: parentPath && {
74
+ id: get(entity, 'parent.id'),
75
+ path: parentPath,
76
+ },
77
+ path
78
+ },
79
+ ...this.buildNestedPaths({items, id: entity.id, field, parentPath: path}),
80
+ ...acc,
81
+ ];
82
+ }, [])
83
+ },
84
+
85
+ filterByPath(items, path) {
86
+ const itemsWithPaths = this.buildNestedPaths({ items }).filter(({path: itemPath}) => itemPath.includes(path));
87
+ const root = itemsWithPaths.find(({ path: itemPath }) => itemPath === path);
88
+
89
+ return {
90
+ root,
91
+ items: isNil(root) ? [] : items.filter(({ id }) => (itemsWithPaths.find(v => v.id === id))),
92
+ }
93
+ },
94
+
55
95
  prepareAuditLog(actions) {
56
96
  return [
57
97
  ...new Set(