strapi-plugin-navigation 2.0.10 → 2.0.13
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 +179 -38
- package/admin/src/components/Alert/styles.js +8 -0
- package/admin/src/components/Item/ItemCardHeader/index.js +1 -1
- package/admin/src/components/Item/index.js +10 -3
- package/admin/src/pages/SettingsPage/index.js +28 -12
- package/admin/src/pages/SettingsPage/utils/functions.js +30 -0
- package/admin/src/pages/View/utils/parsers.js +3 -2
- package/admin/src/translations/en.json +4 -2
- package/package.json +2 -2
- package/server/controllers/navigation.js +11 -3
- package/server/graphql/types/navigation-item.js +1 -1
- package/server/services/navigation.js +31 -29
- package/server/services/utils/constant.js +10 -1
- package/server/services/utils/functions.js +7 -1
package/README.md
CHANGED
|
@@ -36,6 +36,21 @@ Strapi Navigation Plugin provides a website navigation / menu builder feature fo
|
|
|
36
36
|
- Tree (nested)
|
|
37
37
|
- RFR (ready for handling by Redux First Router)
|
|
38
38
|
|
|
39
|
+
### Table of Contents
|
|
40
|
+
1. [✨ Features](#-features)
|
|
41
|
+
2. [⏳ Installation](#-installation)
|
|
42
|
+
3. [🖐 Requirements](#-requirements)
|
|
43
|
+
4. [🔧 Basic Configuration](#-configuration)
|
|
44
|
+
- [Settings page](#in-v203-and-newer)
|
|
45
|
+
- [Plugin file](#in-v202-and-older--default-configuration-state-for-v203-and-newer)
|
|
46
|
+
5. [🔧 GraphQL Configuration](#-gql-configuration)
|
|
47
|
+
6. [🕸️ Public API specification](#%EF%B8%8F-public-api-specification)
|
|
48
|
+
- [REST API](#rest-api)
|
|
49
|
+
- [GraphQL API](#graphql-api)
|
|
50
|
+
8. [💬 FAQ](#-faq)
|
|
51
|
+
9. [🤝 Contributing](#-contributing)
|
|
52
|
+
10. [👨💻 Community support](#-community-support)
|
|
53
|
+
|
|
39
54
|
## ✨ Features
|
|
40
55
|
|
|
41
56
|
- **Navigation Public API:** Simple and ready for use API endpoint for consuming the navigation structure you've created
|
|
@@ -43,6 +58,7 @@ Strapi Navigation Plugin provides a website navigation / menu builder feature fo
|
|
|
43
58
|
- **Any Content Type relation:** Navigation can by linked to any of your Content Types by default. Simply, you're controlling it and also limiting available content types by configuration props
|
|
44
59
|
- **Different types of navigation items:** Create navigation with items linked to internal types, to external links or wrapper elements to keep structure clean
|
|
45
60
|
- **Multiple navigations:** Create as many Navigation containers as you want, setup them and use in the consumer application
|
|
61
|
+
- **Light / Dark mode compatible:** By design we're supporting Strapi ☀️ Light / 🌙 Dark modes
|
|
46
62
|
- **Customizable:** Possibility to customize the options like: available Content Types, Maximum level for "attach to menu", Additional fields (audience)
|
|
47
63
|
- **[Audit log](https://github.com/VirtusLab/strapi-molecules/tree/master/packages/strapi-plugin-audit-log):** integration with Strapi Molecules Audit Log plugin that provides changes track record
|
|
48
64
|
|
|
@@ -53,13 +69,23 @@ Strapi Navigation Plugin provides a website navigation / menu builder feature fo
|
|
|
53
69
|
|
|
54
70
|
## ⏳ Installation
|
|
55
71
|
|
|
72
|
+
### Via Strapi Markerplace
|
|
73
|
+
|
|
74
|
+
As a ✅ **verified** plugin by Strapi team we're available on the [**Strapi Marketplace**](https://market.strapi.io/plugins/strapi-plugin-navigation) as well as **In-App Marketplace** where you can follow the installation instructions.
|
|
75
|
+
|
|
76
|
+
<div style="margin: 20px 0" align="center">
|
|
77
|
+
<img style="width: 100%; height: auto;" src="public/assets/marketplace.png" alt="Strapi In-App Marketplace" />
|
|
78
|
+
</div>
|
|
79
|
+
|
|
80
|
+
### Via command line
|
|
81
|
+
|
|
56
82
|
It's recommended to use **yarn** to install this plugin within your Strapi project. [You can install yarn with these docs](https://yarnpkg.com/lang/en/docs/install/).
|
|
57
83
|
|
|
58
84
|
```bash
|
|
59
85
|
yarn add strapi-plugin-navigation@latest
|
|
60
86
|
```
|
|
61
87
|
|
|
62
|
-
After successful installation you've to build
|
|
88
|
+
After successful installation you've to re-build your Strapi instance. To archive that simply use:
|
|
63
89
|
|
|
64
90
|
```bash
|
|
65
91
|
yarn build
|
|
@@ -74,7 +100,9 @@ yarn develop --watch-admin
|
|
|
74
100
|
|
|
75
101
|
The **UI Navigation** plugin should appear in the **Plugins** section of Strapi sidebar after you run app again.
|
|
76
102
|
|
|
77
|
-
|
|
103
|
+
As a next step you must configure your the plugin by the way you want to. See [**Configuration**](#🔧-configuration) section.
|
|
104
|
+
|
|
105
|
+
All done. Enjoy 🎉
|
|
78
106
|
|
|
79
107
|
## 🖐 Requirements
|
|
80
108
|
|
|
@@ -82,7 +110,7 @@ Complete installation requirements are exact same as for Strapi itself and can b
|
|
|
82
110
|
|
|
83
111
|
**Supported Strapi versions**:
|
|
84
112
|
|
|
85
|
-
- Strapi v4.1.
|
|
113
|
+
- Strapi v4.1.8 (recently tested)
|
|
86
114
|
- Strapi v4.x
|
|
87
115
|
|
|
88
116
|
> 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).
|
|
@@ -91,9 +119,17 @@ Complete installation requirements are exact same as for Strapi itself and can b
|
|
|
91
119
|
|
|
92
120
|
## 🔧 Configuration
|
|
93
121
|
|
|
122
|
+
To start your journey with **Navigation plugin** you must first setup it using the dedicated Settings page (`v2.0.3` and newer) or for any version, put your configuration in `config/plugins.js`. Anyway we're recommending the click-through option where your configuration is going to be properly validated.
|
|
123
|
+
|
|
94
124
|
### In `v2.0.3` and newer
|
|
95
125
|
|
|
96
|
-
Version `2.0.3` introduces the intuitive **Settings** page which you can easily access via `Strapi Settings -> Section: Navigation Plugin -> Configuration`.
|
|
126
|
+
Version `2.0.3` introduces the intuitive **Settings** page which you can easily access via `Strapi Settings -> Section: Navigation Plugin -> Configuration`.
|
|
127
|
+
|
|
128
|
+
On the dedicated page, you will be able to set up all crucial properties which drive the plugin and customize each individual collection for which **Navigation plugin** should be enabled.
|
|
129
|
+
|
|
130
|
+
<div style="margin: 20px 0" align="center">
|
|
131
|
+
<img style="width: 100%; height: auto;" src="public/assets/configuration.png" alt="Plugin configuration" />
|
|
132
|
+
</div>
|
|
97
133
|
|
|
98
134
|
> *Note*
|
|
99
135
|
> The default configuration for your plugin is fetched from `config/plugins.js` or, if the file is not there, directly from the plugin itself. If you would like to customize the default state to which you might revert, please follow the next section.
|
|
@@ -169,7 +205,7 @@ For any role different than **Super Admin**, to access the **Navigation panel**
|
|
|
169
205
|
## Base Navigation Item model
|
|
170
206
|
|
|
171
207
|
### Flat
|
|
172
|
-
```
|
|
208
|
+
```json
|
|
173
209
|
{
|
|
174
210
|
"id": 1,
|
|
175
211
|
"title": "News",
|
|
@@ -182,13 +218,13 @@ For any role different than **Super Admin**, to access the **Navigation panel**
|
|
|
182
218
|
"master": 1, // Navigation 'id'
|
|
183
219
|
"createdAt": "2020-09-29T13:29:19.086Z",
|
|
184
220
|
"updatedAt": "2020-09-29T13:29:19.128Z",
|
|
185
|
-
"related":
|
|
221
|
+
"related": {/*<Content Type model >*/ },
|
|
186
222
|
"audience": []
|
|
187
223
|
}
|
|
188
224
|
```
|
|
189
225
|
|
|
190
226
|
### Tree
|
|
191
|
-
```
|
|
227
|
+
```json
|
|
192
228
|
{
|
|
193
229
|
"title": "News",
|
|
194
230
|
"menuAttached": true,
|
|
@@ -198,7 +234,7 @@ For any role different than **Super Admin**, to access the **Navigation panel**
|
|
|
198
234
|
"slug": "benefits",
|
|
199
235
|
"external": false,
|
|
200
236
|
"related": {
|
|
201
|
-
<Content Type model >
|
|
237
|
+
// <Content Type model >
|
|
202
238
|
},
|
|
203
239
|
"items": [
|
|
204
240
|
{
|
|
@@ -209,13 +245,13 @@ For any role different than **Super Admin**, to access the **Navigation panel**
|
|
|
209
245
|
"uiRouterKey": "generic",
|
|
210
246
|
"external": true
|
|
211
247
|
},
|
|
212
|
-
|
|
248
|
+
// < Tree Navigation Item models >
|
|
213
249
|
]
|
|
214
250
|
}
|
|
215
251
|
```
|
|
216
252
|
|
|
217
253
|
### RFR
|
|
218
|
-
```
|
|
254
|
+
```json
|
|
219
255
|
{
|
|
220
256
|
"id": "News",
|
|
221
257
|
"title": "News",
|
|
@@ -234,33 +270,41 @@ For any role different than **Super Admin**, to access the **Navigation panel**
|
|
|
234
270
|
|
|
235
271
|
## 🕸️ Public API specification
|
|
236
272
|
|
|
237
|
-
|
|
273
|
+
Plugin supports both **REST API** and **GraphQL API** exposed by Strapi.
|
|
238
274
|
|
|
239
|
-
|
|
275
|
+
**Query Params**
|
|
240
276
|
|
|
241
|
-
|
|
277
|
+
- `navigationIdOrSlug` - ID or slug for which your navigation structure is generated like for REST API:
|
|
242
278
|
|
|
243
|
-
|
|
279
|
+
> `https://localhost:1337/api/navigation/render/1`
|
|
280
|
+
> `https://localhost:1337/api/navigation/render/main-menu`
|
|
244
281
|
|
|
245
|
-
|
|
282
|
+
- `type` - Enum value representing structure type of returned navigation:
|
|
283
|
+
> `https://localhost:1337/api/navigation/render/1?type=FLAT`
|
|
246
284
|
|
|
247
|
-
- `
|
|
285
|
+
- `menu` (`menuOnly` for GQL) - Boolean value for querying only navigation items that are attached to menu should be rendered eg.
|
|
286
|
+
> `https://localhost:1337/api/navigation/render/1?menu=true`
|
|
248
287
|
|
|
249
|
-
|
|
288
|
+
- `path` - String value for querying navigation items by its path:
|
|
289
|
+
> `https://localhost:1337/api/navigation/render/1?path=/home/about-us`
|
|
250
290
|
|
|
251
|
-
###
|
|
291
|
+
### REST API
|
|
292
|
+
|
|
293
|
+
> **Important!**
|
|
294
|
+
> Version `v2.0.13` introduced breaking change!
|
|
295
|
+
> All responses have changed their structure. Related field will now be of type ContentType instead of Array\<ContentType\>
|
|
252
296
|
|
|
253
|
-
`GET <host>/api/navigation/render/<
|
|
297
|
+
`GET <host>/api/navigation/render/<navigationIdOrSlug>?type=<type>`
|
|
254
298
|
|
|
255
299
|
Return a rendered navigation structure depends on passed type (`tree`, `rfr` or nothing to render as `flat/raw`).
|
|
256
300
|
|
|
257
|
-
|
|
301
|
+
> The ID of navigation by default is `1`, if you've got defined multiple navigations you must work with their IDs or Slugs to fetch.
|
|
258
302
|
|
|
259
303
|
**Example URL**: `https://localhost:1337/api/navigation/render/1`
|
|
260
304
|
|
|
261
305
|
**Example response body**
|
|
262
306
|
|
|
263
|
-
```
|
|
307
|
+
```json
|
|
264
308
|
[
|
|
265
309
|
{
|
|
266
310
|
"id": 1,
|
|
@@ -274,14 +318,14 @@ Return a rendered navigation structure depends on passed type (`tree`, `rfr` or
|
|
|
274
318
|
"master": 1,
|
|
275
319
|
"created_at": "2020-09-29T13:29:19.086Z",
|
|
276
320
|
"updated_at": "2020-09-29T13:29:19.128Z",
|
|
277
|
-
"related":
|
|
321
|
+
"related": {
|
|
278
322
|
"__contentType": "Page",
|
|
279
323
|
"id": 1,
|
|
280
324
|
"title": "News",
|
|
281
|
-
...
|
|
282
|
-
}
|
|
325
|
+
// ...
|
|
326
|
+
}
|
|
283
327
|
},
|
|
284
|
-
...
|
|
328
|
+
// ...
|
|
285
329
|
]
|
|
286
330
|
```
|
|
287
331
|
|
|
@@ -289,7 +333,7 @@ Return a rendered navigation structure depends on passed type (`tree`, `rfr` or
|
|
|
289
333
|
|
|
290
334
|
**Example response body**
|
|
291
335
|
|
|
292
|
-
```
|
|
336
|
+
```json
|
|
293
337
|
[
|
|
294
338
|
{
|
|
295
339
|
"title": "News",
|
|
@@ -303,7 +347,7 @@ Return a rendered navigation structure depends on passed type (`tree`, `rfr` or
|
|
|
303
347
|
"__contentType": "Page",
|
|
304
348
|
"id": 1,
|
|
305
349
|
"title": "News",
|
|
306
|
-
...
|
|
350
|
+
// ...
|
|
307
351
|
},
|
|
308
352
|
"items": [
|
|
309
353
|
{
|
|
@@ -314,10 +358,10 @@ Return a rendered navigation structure depends on passed type (`tree`, `rfr` or
|
|
|
314
358
|
"uiRouterKey": "generic",
|
|
315
359
|
"external": true
|
|
316
360
|
},
|
|
317
|
-
...
|
|
361
|
+
// ...
|
|
318
362
|
]
|
|
319
363
|
},
|
|
320
|
-
...
|
|
364
|
+
// ...
|
|
321
365
|
]
|
|
322
366
|
```
|
|
323
367
|
|
|
@@ -325,7 +369,7 @@ Return a rendered navigation structure depends on passed type (`tree`, `rfr` or
|
|
|
325
369
|
|
|
326
370
|
**Example response body**
|
|
327
371
|
|
|
328
|
-
```
|
|
372
|
+
```json
|
|
329
373
|
{
|
|
330
374
|
"pages": {
|
|
331
375
|
"News": {
|
|
@@ -370,7 +414,7 @@ Return a rendered navigation structure depends on passed type (`tree`, `rfr` or
|
|
|
370
414
|
"parent": "Community",
|
|
371
415
|
"menuAttached": false
|
|
372
416
|
},
|
|
373
|
-
...
|
|
417
|
+
// ...
|
|
374
418
|
},
|
|
375
419
|
"nav": {
|
|
376
420
|
"root": [
|
|
@@ -389,7 +433,7 @@ Return a rendered navigation structure depends on passed type (`tree`, `rfr` or
|
|
|
389
433
|
"type": "external",
|
|
390
434
|
"url": "http://example.com"
|
|
391
435
|
},
|
|
392
|
-
...
|
|
436
|
+
// ...
|
|
393
437
|
],
|
|
394
438
|
"Community": [
|
|
395
439
|
{
|
|
@@ -397,10 +441,98 @@ Return a rendered navigation structure depends on passed type (`tree`, `rfr` or
|
|
|
397
441
|
"type": "internal",
|
|
398
442
|
"page": "Highlights"
|
|
399
443
|
},
|
|
400
|
-
...
|
|
444
|
+
// ...
|
|
401
445
|
],
|
|
402
|
-
...
|
|
446
|
+
// ...
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
### GraphQL API
|
|
452
|
+
|
|
453
|
+
Same as [**REST API**](#rest-api) returns a rendered navigation structure depends on passed type (`tree`, `rfr` or nothing to render as `flat/raw`).
|
|
454
|
+
|
|
455
|
+
**Example request**
|
|
456
|
+
|
|
457
|
+
```graphql
|
|
458
|
+
query {
|
|
459
|
+
renderNavigation(
|
|
460
|
+
navigationIdOrSlug: "main-navigation"
|
|
461
|
+
type: TREE
|
|
462
|
+
menuOnly: false
|
|
463
|
+
) {
|
|
464
|
+
id
|
|
465
|
+
title
|
|
466
|
+
path
|
|
467
|
+
related {
|
|
468
|
+
__typename
|
|
469
|
+
|
|
470
|
+
... on Page {
|
|
471
|
+
Title
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
... on WithFlowType {
|
|
475
|
+
Name
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
items {
|
|
479
|
+
id
|
|
480
|
+
title
|
|
481
|
+
path
|
|
482
|
+
related {
|
|
483
|
+
__typename
|
|
484
|
+
|
|
485
|
+
... on Page {
|
|
486
|
+
Title
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
... on WithFlowType {
|
|
490
|
+
Name
|
|
491
|
+
}
|
|
492
|
+
}
|
|
403
493
|
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
**Example response**
|
|
499
|
+
|
|
500
|
+
```json
|
|
501
|
+
{
|
|
502
|
+
"data": {
|
|
503
|
+
"renderNavigation": [
|
|
504
|
+
{
|
|
505
|
+
"id": 8,
|
|
506
|
+
"title": "Test page",
|
|
507
|
+
"path": "/test-path",
|
|
508
|
+
"related": {
|
|
509
|
+
"__typename": "WithFlowType",
|
|
510
|
+
"Name": "Test"
|
|
511
|
+
},
|
|
512
|
+
"items": [
|
|
513
|
+
{
|
|
514
|
+
"id": 11,
|
|
515
|
+
"title": "Nested",
|
|
516
|
+
"path": "/test-path/nested-one",
|
|
517
|
+
"related": {
|
|
518
|
+
"__typename": "Page",
|
|
519
|
+
"Title": "Eg. Page title"
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
]
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
"id": 10,
|
|
526
|
+
"title": "Another page",
|
|
527
|
+
"path": "/another",
|
|
528
|
+
"related": {
|
|
529
|
+
"__typename": "Page",
|
|
530
|
+
"Title": "dfdfdf"
|
|
531
|
+
},
|
|
532
|
+
"items": []
|
|
533
|
+
}
|
|
534
|
+
]
|
|
535
|
+
}
|
|
404
536
|
}
|
|
405
537
|
```
|
|
406
538
|
|
|
@@ -416,13 +548,22 @@ For single types a global name of this content type will be used as a template n
|
|
|
416
548
|
|
|
417
549
|
Live example of plugin usage can be found in the [VirtusLab Strapi Examples](https://github.com/VirtusLab/strapi-examples/tree/master/strapi-plugin-navigation) repository.
|
|
418
550
|
|
|
419
|
-
## 💬
|
|
551
|
+
## 💬 FAQ
|
|
552
|
+
|
|
553
|
+
### GraphQL tricks
|
|
420
554
|
|
|
421
|
-
|
|
555
|
+
**Q:** I would like to use GraphQL schemas but I'm not getting renderNavigation query or even proper types as Navigation, NavigationItem etc. What should I do?
|
|
422
556
|
|
|
423
|
-
**
|
|
557
|
+
**A:** There is a one trick you might try. Strapi by default is ordering plugins by the way which takes `strapi-plugin-graphql` to initialize earlier than other plugins so types might not be injected. If you don't have it yet, please create `config/plugins.js` file and put there at lease following lines:
|
|
558
|
+
|
|
559
|
+
```js
|
|
560
|
+
module.exports = {
|
|
561
|
+
'navigation': { enabled: true },
|
|
562
|
+
'graphql': { enabled: true },
|
|
563
|
+
};
|
|
564
|
+
```
|
|
424
565
|
|
|
425
|
-
|
|
566
|
+
If you already got it, make sure that `navigation` plugin is inserted before `graphql`. That should do the job.
|
|
426
567
|
|
|
427
568
|
## 🤝 Contributing
|
|
428
569
|
|
|
@@ -18,7 +18,7 @@ justify-content: center;
|
|
|
18
18
|
height: ${32 / 16}rem;
|
|
19
19
|
width: ${32 / 16}rem;
|
|
20
20
|
|
|
21
|
-
cursor:
|
|
21
|
+
cursor: move;
|
|
22
22
|
padding: ${({ theme }) => theme.spaces[2]};
|
|
23
23
|
border-radius: ${({ theme }) => theme.borderRadius};
|
|
24
24
|
background: ${({ theme }) => theme.colors.neutral0};
|
|
@@ -122,6 +122,13 @@ const Item = (props) => {
|
|
|
122
122
|
previewRef: dragPreview(previewRef),
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
const contentTypeUid = relatedRef?.__collectionUid;
|
|
126
|
+
const contentType = contentTypes.find(_ => _.uid === contentTypeUid) || {};
|
|
127
|
+
const generatePreviewUrl = entity => {
|
|
128
|
+
const { isSingle } = contentType;
|
|
129
|
+
return `/content-manager/${ isSingle ? 'singleType' : 'collectionType'}/${entity?.__collectionUid}${!isSingle ? '/' + entity?.id : ''}`
|
|
130
|
+
}
|
|
131
|
+
|
|
125
132
|
return (
|
|
126
133
|
<Wrapper level={level} isLast={isLast} style={{ opacity: isDragging ? 0.2 : 1 }} ref={refs ? refs.dropRef : null} >
|
|
127
134
|
<Card style={{ width: "728px", zIndex: 1, position: "relative", overflow: 'hidden' }}>
|
|
@@ -179,9 +186,9 @@ const Item = (props) => {
|
|
|
179
186
|
</ItemCardBadge>}
|
|
180
187
|
<Typography variant="omega" textColor='neutral600'>{relatedTypeLabel} / </Typography>
|
|
181
188
|
<Typography variant="omega" textColor='neutral800'>{relatedItemLabel}</Typography>
|
|
182
|
-
|
|
183
|
-
to={
|
|
184
|
-
endIcon={<ArrowRight />}> </Link>
|
|
189
|
+
{ contentType?.visible && (<Link
|
|
190
|
+
to={generatePreviewUrl(relatedRef)}
|
|
191
|
+
endIcon={<ArrowRight />}> </Link>) }
|
|
185
192
|
</Flex>)
|
|
186
193
|
}
|
|
187
194
|
</Flex>
|
|
@@ -22,7 +22,7 @@ import { ToggleInput } from '@strapi/design-system/ToggleInput';
|
|
|
22
22
|
import { NumberInput } from '@strapi/design-system/NumberInput';
|
|
23
23
|
import { Select, Option } from '@strapi/design-system/Select';
|
|
24
24
|
import { Tooltip } from '@strapi/design-system/Tooltip';
|
|
25
|
-
import { Check, Refresh, Play, Information } from '@strapi/icons';
|
|
25
|
+
import { Check, Refresh, Play, Information, ExclamationMarkCircle } from '@strapi/icons';
|
|
26
26
|
|
|
27
27
|
import permissions from '../../permissions';
|
|
28
28
|
import useNavigationConfig from '../../hooks/useNavigationConfig';
|
|
@@ -31,6 +31,8 @@ import { navigationItemAdditionalFields } from '../View/utils/enums';
|
|
|
31
31
|
import ConfirmationDialog from '../../components/ConfirmationDialog';
|
|
32
32
|
import RestartAlert from '../../components/RestartAlert';
|
|
33
33
|
import { getMessage } from '../../utils';
|
|
34
|
+
import { isContentTypeEligible, resolveGlobalLikeId } from './utils/functions';
|
|
35
|
+
import { PermanentAlert } from '../../components/Alert/styles';
|
|
34
36
|
|
|
35
37
|
const SettingsPage = () => {
|
|
36
38
|
const { lockApp, unlockApp } = useOverlayBlocker();
|
|
@@ -56,13 +58,7 @@ const SettingsPage = () => {
|
|
|
56
58
|
additionalFields: audienceFieldChecked ? [navigationItemAdditionalFields.AUDIENCE] : [],
|
|
57
59
|
allowedLevels: allowedLevels,
|
|
58
60
|
gql: {
|
|
59
|
-
navigationItemRelated: selectedContentTypes.map(uid =>
|
|
60
|
-
const singularName = allContentTypes.find(ct => ct.uid === uid).info.singularName;
|
|
61
|
-
const globalIdLike = singularName.split('-')
|
|
62
|
-
.map(_ => capitalize(_))
|
|
63
|
-
.join('')
|
|
64
|
-
return globalIdLike;
|
|
65
|
-
})
|
|
61
|
+
navigationItemRelated: selectedContentTypes.map(uid => resolveGlobalLikeId(uid)),
|
|
66
62
|
}
|
|
67
63
|
});
|
|
68
64
|
|
|
@@ -114,8 +110,24 @@ const SettingsPage = () => {
|
|
|
114
110
|
)
|
|
115
111
|
}
|
|
116
112
|
|
|
117
|
-
const
|
|
118
|
-
|
|
113
|
+
const configContentTypes = navigationConfigData?.contentTypes || [];
|
|
114
|
+
|
|
115
|
+
const allContentTypes = !isLoading && Object.values(allContentTypesData).filter(({ uid }) => isContentTypeEligible(uid, {
|
|
116
|
+
allowedContentTypes: navigationConfigData?.allowedContentTypes,
|
|
117
|
+
restrictedContentTypes: navigationConfigData?.restrictedContentTypes,
|
|
118
|
+
})).map(ct => {
|
|
119
|
+
const type = configContentTypes.find(_ => _.uid === ct.uid);
|
|
120
|
+
if (type) {
|
|
121
|
+
const { available, isSingle } = type;
|
|
122
|
+
return {
|
|
123
|
+
...ct,
|
|
124
|
+
available,
|
|
125
|
+
isSingle,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
return ct;
|
|
129
|
+
});
|
|
130
|
+
const selectedContentTypes = configContentTypes.map(item => item.uid);
|
|
119
131
|
const audienceFieldChecked = navigationConfigData?.additionalFields.includes(navigationItemAdditionalFields.AUDIENCE);
|
|
120
132
|
const allowedLevels = navigationConfigData?.allowedLevels || 2;
|
|
121
133
|
const nameFields = navigationConfigData?.contentTypesNameFields || {}
|
|
@@ -190,7 +202,7 @@ const SettingsPage = () => {
|
|
|
190
202
|
<Information aria-hidden={true} />
|
|
191
203
|
</Tooltip>}>
|
|
192
204
|
{orderBy(values.selectedContentTypes).map(uid => {
|
|
193
|
-
const { attributes, info: { displayName } } = allContentTypes.find(item => item.uid == uid);
|
|
205
|
+
const { attributes, info: { displayName }, available, isSingle } = allContentTypes.find(item => item.uid == uid);
|
|
194
206
|
const stringAttributes = Object.keys(attributes).filter(_ => attributes[_].type === 'string');
|
|
195
207
|
const relationAttributes = Object.keys(attributes).filter(_ => attributes[_].type === 'relation');
|
|
196
208
|
const key = `collectionSettings-${uid}`;
|
|
@@ -200,10 +212,14 @@ const SettingsPage = () => {
|
|
|
200
212
|
key={key}
|
|
201
213
|
id={key}
|
|
202
214
|
size="S">
|
|
203
|
-
<AccordionToggle title={displayName} togglePosition="left" />
|
|
215
|
+
<AccordionToggle title={displayName} togglePosition="left" startIcon={(isSingle && !available) && (<ExclamationMarkCircle aria-hidden={true} />)} />
|
|
204
216
|
<AccordionContent>
|
|
205
217
|
<Box padding={6}>
|
|
206
218
|
<Stack spacing={4}>
|
|
219
|
+
{ (isSingle && !available) && (
|
|
220
|
+
<PermanentAlert title={getMessage('pages.settings.form.contentTypesSettings.initializationWarning.title')} variant="danger" onClose={(e) => e.preventDefault()}>
|
|
221
|
+
{ getMessage('pages.settings.form.contentTypesSettings.initializationWarning.content') }
|
|
222
|
+
</PermanentAlert>)}
|
|
207
223
|
<Select
|
|
208
224
|
name={`collectionSettings-${uid}-entryLabel`}
|
|
209
225
|
label={getMessage('pages.settings.form.nameField.label')}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { capitalize } = require("lodash");
|
|
4
|
+
|
|
5
|
+
const UID_REGEX = /^(?<type>[a-z0-9-]+)\:{2}(?<api>[a-z0-9-]+)\.{1}(?<contentType>[a-z0-9-]+)$/i;
|
|
6
|
+
|
|
7
|
+
const splitTypeUid = (uid = '') => {
|
|
8
|
+
return uid.split(UID_REGEX).filter((s) => s && s.length > 0);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
module.exports = {
|
|
12
|
+
resolveGlobalLikeId(uid = '') {
|
|
13
|
+
const parse = (str) => str.split('-')
|
|
14
|
+
.map(_ => capitalize(_))
|
|
15
|
+
.join('');
|
|
16
|
+
|
|
17
|
+
const [type, scope, contentTypeName] = splitTypeUid(uid);
|
|
18
|
+
if (type === 'api') {
|
|
19
|
+
return parse(contentTypeName);
|
|
20
|
+
}
|
|
21
|
+
return `${parse(scope)}${parse(contentTypeName)}`;
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
isContentTypeEligible(uid = '', config = {}) {
|
|
25
|
+
const { allowedContentTypes = [], restrictedContentTypes = []} = config;
|
|
26
|
+
const isOneOfAllowedType = allowedContentTypes.filter(_ => uid.includes(_) || (uid === _)).length > 0;
|
|
27
|
+
const isNoneOfRestricted = restrictedContentTypes.filter(_ => uid.includes(_) || (uid === _)).length === 0;
|
|
28
|
+
return uid && isOneOfAllowedType && isNoneOfRestricted;
|
|
29
|
+
},
|
|
30
|
+
}
|
|
@@ -25,6 +25,7 @@ export const transformItemToRESTPayload = (
|
|
|
25
25
|
audience = [],
|
|
26
26
|
items = [],
|
|
27
27
|
collapsed,
|
|
28
|
+
isSingle
|
|
28
29
|
} = item;
|
|
29
30
|
const isExternal = type === navigationItemType.EXTERNAL;
|
|
30
31
|
const isWrapper = type === navigationItemType.WRAPPER;
|
|
@@ -37,7 +38,7 @@ export const transformItemToRESTPayload = (
|
|
|
37
38
|
find(contentTypes,
|
|
38
39
|
ct => ct.uid === relatedType) :
|
|
39
40
|
undefined;
|
|
40
|
-
const itemAttachedToMenu = menuAttached && parentAttachedToMenu
|
|
41
|
+
const itemAttachedToMenu = menuAttached && parentAttachedToMenu;
|
|
41
42
|
return {
|
|
42
43
|
id,
|
|
43
44
|
parent,
|
|
@@ -59,7 +60,7 @@ export const transformItemToRESTPayload = (
|
|
|
59
60
|
? undefined
|
|
60
61
|
: [
|
|
61
62
|
{
|
|
62
|
-
refId: relatedId,
|
|
63
|
+
refId: isSingle && !relatedId ? 1 : relatedId,
|
|
63
64
|
ref: relatedContentType ? relatedContentType.uid : relatedType,
|
|
64
65
|
field: relatedContentType && relatedContentType.relatedField ? relatedContentType.relatedField : 'navigation',
|
|
65
66
|
},
|
|
@@ -74,9 +74,9 @@
|
|
|
74
74
|
"pages.settings.notification.submit.error": "Config update has failed",
|
|
75
75
|
"pages.settings.notification.restore.error": "Config restore has failed",
|
|
76
76
|
"pages.settings.notification.restart.error": "Failed to restart your application. Try to do it manually.",
|
|
77
|
-
"pages.settings.form.contentTypes.label": "Enable for
|
|
77
|
+
"pages.settings.form.contentTypes.label": "Enable navigation for",
|
|
78
78
|
"pages.settings.form.contentTypes.placeholder": "eg. Pages, Posts",
|
|
79
|
-
"pages.settings.form.contentTypes.hint": "
|
|
79
|
+
"pages.settings.form.contentTypes.hint": "If none is selected, also none of the content types are enabled",
|
|
80
80
|
"pages.settings.form.allowedLevels.label": "Allowed levels",
|
|
81
81
|
"pages.settings.form.allowedLevels.placeholder": "eg. 2",
|
|
82
82
|
"pages.settings.form.allowedLevels.hint": "Maximum level for which you're able to mark item as \"Menu attached\"",
|
|
@@ -92,6 +92,8 @@
|
|
|
92
92
|
"pages.settings.form.populate.empty": "This content type doesn't have any relation fields",
|
|
93
93
|
"pages.settings.form.contentTypesSettings.label": "Content types",
|
|
94
94
|
"pages.settings.form.contentTypesSettings.tooltip": "Custom configuration per content type",
|
|
95
|
+
"pages.settings.form.contentTypesSettings.initializationWarning.title": "Warning",
|
|
96
|
+
"pages.settings.form.contentTypesSettings.initializationWarning.content": "- Content Type hasn't yet been initialized. Initialize it first to be able to use in a Visual Editor.",
|
|
95
97
|
"components.navigationItem.action.newItem": "Add nested item",
|
|
96
98
|
"components.navigationItem.badge.removed": "Removed",
|
|
97
99
|
"components.navigationItem.badge.draft": "Draft",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "strapi-plugin-navigation",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.13",
|
|
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.1.
|
|
20
|
+
"@strapi/utils": "^4.1.8",
|
|
21
21
|
"uuid": "^8.3.0",
|
|
22
22
|
"lodash": "^4.17.11",
|
|
23
23
|
"react": "^16.9.0",
|
|
@@ -25,12 +25,20 @@ module.exports = ({strapi}) => ({
|
|
|
25
25
|
},
|
|
26
26
|
|
|
27
27
|
async updateConfig(ctx) {
|
|
28
|
-
|
|
28
|
+
try {
|
|
29
|
+
await getService().updateConfig(ctx.request.body);
|
|
30
|
+
} catch (e) {
|
|
31
|
+
errorHandler(ctx)(e);
|
|
32
|
+
}
|
|
29
33
|
return ctx.send({ status: 200 });
|
|
30
34
|
},
|
|
31
35
|
|
|
32
36
|
async restoreConfig(ctx) {
|
|
33
|
-
|
|
37
|
+
try {
|
|
38
|
+
await getService().restoreConfig();
|
|
39
|
+
} catch (e) {
|
|
40
|
+
errorHandler(ctx)(e);
|
|
41
|
+
}
|
|
34
42
|
return ctx.send({ status: 200 })
|
|
35
43
|
},
|
|
36
44
|
|
|
@@ -43,7 +51,7 @@ module.exports = ({strapi}) => ({
|
|
|
43
51
|
await getService().restart();
|
|
44
52
|
return ctx.send({ status: 200 });
|
|
45
53
|
} catch (e) {
|
|
46
|
-
errorHandler(ctx
|
|
54
|
+
errorHandler(ctx)(e);
|
|
47
55
|
}
|
|
48
56
|
},
|
|
49
57
|
|
|
@@ -10,7 +10,7 @@ module.exports = ({ nexus }) =>
|
|
|
10
10
|
t.nonNull.string("uiRouterKey")
|
|
11
11
|
t.nonNull.boolean("menuAttached")
|
|
12
12
|
t.nonNull.int("order")
|
|
13
|
-
t.
|
|
13
|
+
t.field("parent", { type: "NavigationItem" })
|
|
14
14
|
t.int("master")
|
|
15
15
|
t.list.field("items", { type: 'NavigationItem' })
|
|
16
16
|
t.field("related", { type: 'NavigationRelated' })
|
|
@@ -11,16 +11,14 @@ const {
|
|
|
11
11
|
toNumber,
|
|
12
12
|
isString,
|
|
13
13
|
first,
|
|
14
|
-
|
|
15
14
|
} = require('lodash');
|
|
16
15
|
const { validate: isUuid } = require('uuid');
|
|
17
16
|
const slugify = require('slugify');
|
|
18
|
-
const { KIND_TYPES } = require('./utils/constant');
|
|
17
|
+
const { KIND_TYPES, ALLOWED_CONTENT_TYPES, RESTRICTED_CONTENT_TYPES } = require('./utils/constant');
|
|
19
18
|
const utilsFunctionsFactory = require('./utils/functions');
|
|
20
19
|
const { renderType } = require('../content-types/navigation/lifecycle');
|
|
21
20
|
const { type: itemType, additionalFields: configAdditionalFields } = require('../content-types/navigation-item').lifecycle;
|
|
22
21
|
const { NotFoundError } = require('@strapi/utils').errors
|
|
23
|
-
const excludedContentTypes = ['strapi::'];
|
|
24
22
|
const contentTypesNameFieldsDefaults = ['title', 'subject', 'name'];
|
|
25
23
|
|
|
26
24
|
module.exports = ({ strapi }) => {
|
|
@@ -33,7 +31,7 @@ module.exports = ({ strapi }) => {
|
|
|
33
31
|
const entities = await strapi
|
|
34
32
|
.query(masterModel.uid)
|
|
35
33
|
.findMany({
|
|
36
|
-
limit:
|
|
34
|
+
limit: Number.MAX_SAFE_INTEGER,
|
|
37
35
|
});
|
|
38
36
|
return entities;
|
|
39
37
|
},
|
|
@@ -50,7 +48,7 @@ module.exports = ({ strapi }) => {
|
|
|
50
48
|
where: {
|
|
51
49
|
master: id,
|
|
52
50
|
},
|
|
53
|
-
limit:
|
|
51
|
+
limit: Number.MAX_SAFE_INTEGER,
|
|
54
52
|
sort: ['order:asc'],
|
|
55
53
|
populate: ['related', 'parent', 'audience']
|
|
56
54
|
});
|
|
@@ -67,8 +65,8 @@ module.exports = ({ strapi }) => {
|
|
|
67
65
|
|
|
68
66
|
// Get plugin config
|
|
69
67
|
async config(viaSettingsPage = false) {
|
|
70
|
-
const { audienceModel
|
|
71
|
-
const pluginStore = await
|
|
68
|
+
const { audienceModel } = utilsFunctions.extractMeta(strapi.plugins);
|
|
69
|
+
const pluginStore = await this.getPluginStore()
|
|
72
70
|
const config = await pluginStore.get({ key: 'config' });
|
|
73
71
|
const additionalFields = config.additionalFields;
|
|
74
72
|
const contentTypesNameFields = config.contentTypesNameFields;
|
|
@@ -76,9 +74,12 @@ module.exports = ({ strapi }) => {
|
|
|
76
74
|
const allowedLevels = config.allowedLevels;
|
|
77
75
|
const isGQLPluginEnabled = !isNil(strapi.plugin('graphql'));
|
|
78
76
|
|
|
79
|
-
let extendedResult = {
|
|
77
|
+
let extendedResult = {
|
|
78
|
+
allowedContentTypes: ALLOWED_CONTENT_TYPES,
|
|
79
|
+
restrictedContentTypes: RESTRICTED_CONTENT_TYPES,
|
|
80
|
+
};
|
|
80
81
|
const result = {
|
|
81
|
-
contentTypes: await
|
|
82
|
+
contentTypes: await this.configContentTypes(viaSettingsPage),
|
|
82
83
|
contentTypesNameFields: {
|
|
83
84
|
default: contentTypesNameFieldsDefaults,
|
|
84
85
|
...(isObject(contentTypesNameFields) ? contentTypesNameFields : {}),
|
|
@@ -95,7 +96,7 @@ module.exports = ({ strapi }) => {
|
|
|
95
96
|
const audienceItems = await strapi
|
|
96
97
|
.query(audienceModel.uid)
|
|
97
98
|
.findMany({
|
|
98
|
-
limit:
|
|
99
|
+
limit: Number.MAX_SAFE_INTEGER,
|
|
99
100
|
});
|
|
100
101
|
extendedResult = {
|
|
101
102
|
...extendedResult,
|
|
@@ -109,7 +110,7 @@ module.exports = ({ strapi }) => {
|
|
|
109
110
|
},
|
|
110
111
|
|
|
111
112
|
async updateConfig(newConfig) {
|
|
112
|
-
const pluginStore = await
|
|
113
|
+
const pluginStore = await this.getPluginStore()
|
|
113
114
|
await pluginStore.set({ key: 'config', value: newConfig });
|
|
114
115
|
},
|
|
115
116
|
|
|
@@ -118,7 +119,7 @@ module.exports = ({ strapi }) => {
|
|
|
118
119
|
},
|
|
119
120
|
|
|
120
121
|
async setDefaultConfig() {
|
|
121
|
-
const pluginStore = await
|
|
122
|
+
const pluginStore = await this.getPluginStore()
|
|
122
123
|
const config = await pluginStore.get({ key: 'config' });
|
|
123
124
|
const pluginDefaultConfig = await strapi.plugin('navigation').config
|
|
124
125
|
|
|
@@ -138,24 +139,22 @@ module.exports = ({ strapi }) => {
|
|
|
138
139
|
},
|
|
139
140
|
|
|
140
141
|
async restoreConfig() {
|
|
141
|
-
const pluginStore = await
|
|
142
|
+
const pluginStore = await this.getPluginStore()
|
|
142
143
|
await pluginStore.delete({ key: 'config' });
|
|
143
144
|
await strapi.plugin('navigation').service('navigation').setDefaultConfig();
|
|
144
145
|
},
|
|
145
146
|
|
|
146
|
-
async configContentTypes() {
|
|
147
|
-
const pluginStore = await
|
|
147
|
+
async configContentTypes(viaSettingsPage = false) {
|
|
148
|
+
const pluginStore = await this.getPluginStore()
|
|
148
149
|
const config = await pluginStore.get({ key: 'config' });
|
|
149
150
|
const eligibleContentTypes =
|
|
150
151
|
await Promise.all(
|
|
151
152
|
config.contentTypes
|
|
152
|
-
.filter(contentType => !!strapi.contentTypes[contentType])
|
|
153
|
+
.filter(contentType => !!strapi.contentTypes[contentType] && utilsFunctions.isContentTypeEligible(contentType))
|
|
153
154
|
.map(
|
|
154
155
|
async (key) => {
|
|
155
|
-
if (find(excludedContentTypes, name => key.includes(name))) { // exclude internal content types
|
|
156
|
-
return;
|
|
157
|
-
}
|
|
158
156
|
const item = strapi.contentTypes[key];
|
|
157
|
+
|
|
159
158
|
const { kind, options, uid } = item;
|
|
160
159
|
const { draftAndPublish } = options;
|
|
161
160
|
|
|
@@ -177,7 +176,9 @@ module.exports = ({ strapi }) => {
|
|
|
177
176
|
return returnType(itemsCountOrBypass !== 0);
|
|
178
177
|
}
|
|
179
178
|
const isAvailable = await strapi.query(uid).count();
|
|
180
|
-
return isAvailable === 1 ?
|
|
179
|
+
return isAvailable === 1 ?
|
|
180
|
+
returnType(true) :
|
|
181
|
+
(viaSettingsPage ? returnType(false) : undefined);
|
|
181
182
|
}
|
|
182
183
|
return returnType(true);
|
|
183
184
|
},
|
|
@@ -188,9 +189,10 @@ module.exports = ({ strapi }) => {
|
|
|
188
189
|
.map(({ key, available }) => {
|
|
189
190
|
const item = strapi.contentTypes[key];
|
|
190
191
|
const relatedField = (item.associations || []).find(_ => _.model === 'navigationitem');
|
|
191
|
-
const { uid, options, info, collectionName, modelName, apiName, plugin, kind } = item;
|
|
192
|
+
const { uid, options, info, collectionName, modelName, apiName, plugin, kind, pluginOptions } = item;
|
|
193
|
+
const { visible = true } = pluginOptions['content-manager'] || {};
|
|
192
194
|
const { name, description } = info;
|
|
193
|
-
const {
|
|
195
|
+
const { hidden, templateName } = options;
|
|
194
196
|
const findRouteConfig = find(get(strapi.api, `[${modelName}].config.routes`, []),
|
|
195
197
|
route => route.handler.includes('.find'));
|
|
196
198
|
const findRoutePath = findRouteConfig && findRouteConfig.path.split('/')[1];
|
|
@@ -215,12 +217,12 @@ module.exports = ({ strapi }) => {
|
|
|
215
217
|
labelSingular: utilsFunctions.singularize(labelSingular),
|
|
216
218
|
endpoint,
|
|
217
219
|
plugin,
|
|
218
|
-
available,
|
|
219
|
-
visible
|
|
220
|
+
available: available && !hidden,
|
|
221
|
+
visible,
|
|
220
222
|
templateName,
|
|
221
223
|
};
|
|
222
224
|
})
|
|
223
|
-
.filter((item) => item && item.
|
|
225
|
+
.filter((item) => viaSettingsPage || (item && item.available));
|
|
224
226
|
},
|
|
225
227
|
|
|
226
228
|
async getRelatedItems(entityItems) {
|
|
@@ -393,7 +395,7 @@ module.exports = ({ strapi }) => {
|
|
|
393
395
|
master: entity.id,
|
|
394
396
|
...itemCriteria,
|
|
395
397
|
},
|
|
396
|
-
limit:
|
|
398
|
+
limit: Number.MAX_SAFE_INTEGER,
|
|
397
399
|
sort: ['order:asc'],
|
|
398
400
|
populate: ['related', 'audience', 'parent'],
|
|
399
401
|
});
|
|
@@ -472,10 +474,10 @@ module.exports = ({ strapi }) => {
|
|
|
472
474
|
...item,
|
|
473
475
|
audience: item.audience?.map(_ => _.key),
|
|
474
476
|
title: utilsFunctions.composeItemTitle(item, contentTypesNameFields, contentTypes),
|
|
475
|
-
related: item.related?.map(({ localizations, ...item }) => item),
|
|
477
|
+
related: last(item.related?.map(({ localizations, ...item }) => item)),
|
|
476
478
|
items: null,
|
|
477
479
|
}));
|
|
478
|
-
return isNil(rootPath) ?
|
|
480
|
+
return isNil(rootPath) ? publishedItems : utilsFunctions.filterByPath(publishedItems, rootPath).items;
|
|
479
481
|
}
|
|
480
482
|
}
|
|
481
483
|
throw new NotFoundError();
|
|
@@ -768,7 +770,7 @@ module.exports = ({ strapi }) => {
|
|
|
768
770
|
related_id: relatedItem.refId,
|
|
769
771
|
related_type: relatedItem.ref,
|
|
770
772
|
};
|
|
771
|
-
return model.delete({ where: entityToRemove })
|
|
773
|
+
return model.delete({ where: entityToRemove });
|
|
772
774
|
}));
|
|
773
775
|
},
|
|
774
776
|
|
|
@@ -8,5 +8,14 @@ module.exports = {
|
|
|
8
8
|
|
|
9
9
|
MODEL_TYPES: {
|
|
10
10
|
CONTENT_TYPE: 'contentType'
|
|
11
|
-
}
|
|
11
|
+
},
|
|
12
|
+
ALLOWED_CONTENT_TYPES: [
|
|
13
|
+
'api::',
|
|
14
|
+
'plugin::'
|
|
15
|
+
],
|
|
16
|
+
RESTRICTED_CONTENT_TYPES: [
|
|
17
|
+
'plugin::users-permissions',
|
|
18
|
+
'plugin::i18n.locale',
|
|
19
|
+
'plugin::navigation',
|
|
20
|
+
],
|
|
12
21
|
};
|
|
@@ -13,7 +13,7 @@ const {
|
|
|
13
13
|
|
|
14
14
|
const { type: itemType } = require('../../content-types/navigation-item/lifecycle');
|
|
15
15
|
const { NavigationError } = require('../../../utils/NavigationError');
|
|
16
|
-
const { TEMPLATE_DEFAULT } = require('./constant');
|
|
16
|
+
const { TEMPLATE_DEFAULT, ALLOWED_CONTENT_TYPES, RESTRICTED_CONTENT_TYPES } = require('./constant');
|
|
17
17
|
|
|
18
18
|
module.exports = ({ strapi }) => {
|
|
19
19
|
return {
|
|
@@ -216,5 +216,11 @@ module.exports = ({ strapi }) => {
|
|
|
216
216
|
}
|
|
217
217
|
return (item.type !== itemType.INTERNAL) || relatedItem;
|
|
218
218
|
},
|
|
219
|
+
|
|
220
|
+
isContentTypeEligible(uid = '') {
|
|
221
|
+
const isOneOfAllowedType = ALLOWED_CONTENT_TYPES.filter(_ => uid.includes(_)).length > 0;
|
|
222
|
+
const isNoneOfRestricted = RESTRICTED_CONTENT_TYPES.filter(_ => uid.includes(_) || (uid === _)).length === 0;
|
|
223
|
+
return uid && isOneOfAllowedType && isNoneOfRestricted;
|
|
224
|
+
},
|
|
219
225
|
};
|
|
220
226
|
}
|