strapi-plugin-navigation 2.0.0-beta.4 → 2.0.1
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 +46 -19
- package/__mocks__/pages.settings.json +25 -0
- package/__mocks__/strapi.js +196 -0
- package/admin/src/components/EmptyView/index.js +2 -1
- package/admin/src/components/Item/ItemCardBadge/index.js +15 -1
- package/admin/src/components/Item/ItemCardHeader/index.js +8 -15
- package/admin/src/components/Item/ItemCardRemovedOverlay/index.js +12 -0
- package/admin/src/components/Item/index.js +66 -23
- package/admin/src/components/NavigationItemList/index.js +8 -0
- package/admin/src/components/Search/index.js +21 -23
- package/admin/src/components/icons/navigation.js +14 -0
- package/admin/src/index.js +4 -3
- package/admin/src/pages/View/components/NavigationHeader/index.js +41 -35
- package/admin/src/pages/View/components/NavigationHeader/styles.js +13 -0
- package/admin/src/pages/View/components/NavigationItemForm/index.js +50 -7
- package/admin/src/pages/View/components/NavigationItemPopup/NavigationItemPopupHeader.js +2 -4
- package/admin/src/pages/View/components/NavigationItemPopup/index.js +1 -1
- package/admin/src/pages/View/index.js +31 -17
- package/admin/src/pages/View/utils/parsers.js +10 -4
- package/admin/src/permissions.js +8 -0
- package/admin/src/translations/en.json +9 -6
- package/package.json +5 -3
- package/permissions.js +11 -0
- package/server/bootstrap.js +5 -4
- package/server/content-types/navigation/schema.json +45 -0
- package/server/content-types/navigation-item/schema.json +1 -1
- package/server/graphql/types/navigation-related.js +1 -1
- package/server/services/__tests__/navigation.test.js +63 -78
- package/server/services/navigation.js +5 -5
- package/server/services/utils/functions.js +5 -12
- package/.circleci/config.yml +0 -48
- package/.eslintrc +0 -35
- package/.github/pull_request_template.md +0 -13
- package/.github/stale.yml +0 -15
- package/.nvmrc +0 -1
- package/admin/src/components/PluginIcon/index.js +0 -6
- package/codecov.yml +0 -3
- package/server/content-types/navigation/schema.js +0 -45
package/README.md
CHANGED
|
@@ -1,22 +1,35 @@
|
|
|
1
|
+
<div align="center" width="150px">
|
|
2
|
+
<img style="width: 150px; height: auto;" src="public/assets/logo.png" alt="Logo - Strapi Navigation plugin" />
|
|
3
|
+
</div>
|
|
1
4
|
<div align="center">
|
|
2
|
-
<h1>Strapi v4 - Navigation plugin
|
|
3
|
-
<p>Create consumable navigation with a simple and
|
|
5
|
+
<h1>Strapi v4 - Navigation plugin</h1>
|
|
6
|
+
<p>Create consumable navigation with a simple and straightforward visual builder</p>
|
|
4
7
|
<a href="https://www.npmjs.org/package/strapi-plugin-navigation">
|
|
5
|
-
<img src="https://img.shields.io/github/package-json/v/VirtusLab-Open-Source/strapi-plugin-navigation
|
|
8
|
+
<img alt="GitHub package.json version" src="https://img.shields.io/github/package-json/v/VirtusLab-Open-Source/strapi-plugin-navigation?label=npm&logo=npm">
|
|
6
9
|
</a>
|
|
7
10
|
<a href="https://www.npmjs.org/package/strapi-plugin-navigation">
|
|
8
11
|
<img src="https://img.shields.io/npm/dm/strapi-plugin-navigation.svg" alt="Monthly download on NPM" />
|
|
9
12
|
</a>
|
|
10
13
|
<a href="https://circleci.com/gh/VirtusLab/strapi-plugin-navigation">
|
|
11
|
-
<img src="https://circleci.com/gh/VirtusLab-Open-Source/strapi-plugin-navigation
|
|
14
|
+
<img src="https://circleci.com/gh/VirtusLab-Open-Source/strapi-plugin-navigation.svg?style=shield" alt="CircleCI" />
|
|
12
15
|
</a>
|
|
13
16
|
<a href="https://codecov.io/gh/VirtusLab/strapi-plugin-navigation">
|
|
14
|
-
<img src="https://codecov.io/gh/VirtusLab/strapi-plugin-navigation/coverage.svg?branch=
|
|
17
|
+
<img src="https://codecov.io/gh/VirtusLab/strapi-plugin-navigation/coverage.svg?branch=master" alt="codecov.io" />
|
|
18
|
+
</a>
|
|
19
|
+
<a href="https://sharing.clickup.com/tl/xhcmx-43/strapiv-4-navigation-roadmap">
|
|
20
|
+
<img src="https://img.shields.io/website?down_message=roadmap&label=product&up_message=roadmap&url=https%3A%2F%2Fsharing.clickup.com%2Ftl%2Fxhcmx-43%2Fstrapiv-4-navigation-roadmap" />
|
|
21
|
+
</a>
|
|
22
|
+
<a href="https://sharing.clickup.com/b/6-169004201-2/strapiv-4-navigation-board">
|
|
23
|
+
<img src="https://img.shields.io/website?down_message=board&label=product&up_color=blue&up_message=board&url=https%3A%2F%2Fsharing.clickup.com%2Fb%2F6-169004201-2%2Fstrapiv-4-navigation-board" />
|
|
15
24
|
</a>
|
|
16
25
|
</div>
|
|
17
26
|
|
|
18
27
|
---
|
|
19
28
|
|
|
29
|
+
<div style="margin: 20px 0" align="center">
|
|
30
|
+
<img style="width: 100%; height: auto;" src="public/assets/preview.png" alt="UI preview" />
|
|
31
|
+
</div>
|
|
32
|
+
|
|
20
33
|
Strapi Navigation Plugin provides a website navigation / menu builder feature for [Strapi Headless CMS](https://github.com/strapi/strapi) admin panel. Navigation has the possibility to control the audience and can be consumed by the website with different output structure renderers:
|
|
21
34
|
|
|
22
35
|
- Flat
|
|
@@ -28,14 +41,14 @@ Strapi Navigation Plugin provides a website navigation / menu builder feature fo
|
|
|
28
41
|
- **Navigation Public API:** Simple and ready for use API endpoint for consuming the navigation structure you've created
|
|
29
42
|
- **Visual builder:** Elegant and easy to use visual builder
|
|
30
43
|
- **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
|
+
- **Multiple navigations:** Create as many Navigation containers as you want, setup them and use in the consumer application
|
|
31
45
|
- **Customizable:** Possibility to customize the options like: available Content Types, Maximum level for "attach to menu", Additional fields (audience)
|
|
32
46
|
- **[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
|
|
33
47
|
|
|
34
|
-
|
|
35
48
|
## ⚙️ Versions
|
|
36
49
|
|
|
37
|
-
- **
|
|
38
|
-
- **
|
|
50
|
+
- **Strapi v4** - (current) - [v2.x](https://github.com/VirtusLab-Open-Source/strapi-plugin-navigation)
|
|
51
|
+
- **Strapi v3** - [v1.x](https://github.com/VirtusLab-Open-Source/strapi-plugin-navigation/tree/strapi-v3)
|
|
39
52
|
|
|
40
53
|
## ⏳ Installation
|
|
41
54
|
|
|
@@ -68,12 +81,10 @@ Complete installation requirements are exact same as for Strapi itself and can b
|
|
|
68
81
|
|
|
69
82
|
**Supported Strapi versions**:
|
|
70
83
|
|
|
71
|
-
- Strapi v4.0.
|
|
84
|
+
- Strapi v4.0.7 (recently tested)
|
|
72
85
|
- Strapi v4.x
|
|
73
86
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
It may or may not work with the older Strapi v4 versions, these are not tested nor officially supported at this time.
|
|
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).
|
|
77
88
|
|
|
78
89
|
**We recommend always using the latest version of Strapi to start your new projects**.
|
|
79
90
|
|
|
@@ -105,7 +116,7 @@ Config for this plugin is stored as a part of `config/plugins.js` or `config/<en
|
|
|
105
116
|
- `contentTypesNameFields` - Definition of content type title fields like `'api::<collection name>.<content type name>': ['field_name_1', 'field_name_2']`, if not set titles are pulled from fields like `['title', 'subject', 'name']`. **TIP** - Proper content type uid you can find in the URL of Content Manager where you're managing relevant entities like: `admin/content-manager/collectionType/< THE UID HERE >?page=1&pageSize=10&sort=Title:ASC&plugins[i18n][locale]=en`
|
|
106
117
|
- `gql` - If you're using GraphQL that's the right place to put all necessary settings. More **[ here ](#gql-configuration)**
|
|
107
118
|
|
|
108
|
-
## GQL Configuration
|
|
119
|
+
## 🔧 GQL Configuration
|
|
109
120
|
Using navigation with GraphQL requires both plugins to be installed and working. You can find instalation guide for GraphQL plugin **[here](https://docs.strapi.io/developer-docs/latest/plugins/graphql.html#graphql)**. To properly configure GQL to work with navigation you should provide `gql` prop. This should contain union types that will be used to define GQL response format for your data while fetching:
|
|
110
121
|
|
|
111
122
|
```gql
|
|
@@ -131,8 +142,14 @@ gql: {
|
|
|
131
142
|
```
|
|
132
143
|
where `Page` and `UploadFile` are your type names for the **Content Types** you're referring by navigation items relations.
|
|
133
144
|
|
|
145
|
+
## 👤 RBAC
|
|
146
|
+
Plugin provides granular permissions based on Strapi RBAC functionality.
|
|
147
|
+
|
|
148
|
+
### Mandatory permissions
|
|
149
|
+
For any role different than **Super Admin**, to access the **Navigation panel** you must set following permissions:
|
|
150
|
+
- _Plugins_ -> _Navigation_ -> _Read_ - gives you the access to **Navigation Panel**
|
|
134
151
|
|
|
135
|
-
##
|
|
152
|
+
## Base Navigation Item model
|
|
136
153
|
|
|
137
154
|
### Flat
|
|
138
155
|
```
|
|
@@ -146,8 +163,8 @@ where `Page` and `UploadFile` are your type names for the **Content Types** you'
|
|
|
146
163
|
"menuAttached": false,
|
|
147
164
|
"parent": 8, // Parent Navigation Item 'id', null in case of root level
|
|
148
165
|
"master": 1, // Navigation 'id'
|
|
149
|
-
"
|
|
150
|
-
"
|
|
166
|
+
"createdAt": "2020-09-29T13:29:19.086Z",
|
|
167
|
+
"updatedAt": "2020-09-29T13:29:19.128Z",
|
|
151
168
|
"related": [ <Content Type model > ],
|
|
152
169
|
"audience": []
|
|
153
170
|
}
|
|
@@ -198,7 +215,7 @@ where `Page` and `UploadFile` are your type names for the **Content Types** you'
|
|
|
198
215
|
}
|
|
199
216
|
```
|
|
200
217
|
|
|
201
|
-
## Public API specification
|
|
218
|
+
## 🕸️ Public API specification
|
|
202
219
|
|
|
203
220
|
### Render
|
|
204
221
|
|
|
@@ -378,16 +395,26 @@ Live example of plugin usage can be found in the [VirtusLab Strapi Examples](htt
|
|
|
378
395
|
|
|
379
396
|
## 🤝 Contributing
|
|
380
397
|
|
|
398
|
+
<div>
|
|
399
|
+
<a href="https://sharing.clickup.com/tl/xhcmx-43/strapiv-4-navigation-roadmap">
|
|
400
|
+
<img src="https://img.shields.io/website?down_message=roadmap&label=product&up_message=roadmap&url=https%3A%2F%2Fsharing.clickup.com%2Ftl%2Fxhcmx-43%2Fstrapiv-4-navigation-roadmap" />
|
|
401
|
+
</a>
|
|
402
|
+
<a href="https://sharing.clickup.com/b/6-169004201-2/strapiv-4-navigation-board">
|
|
403
|
+
<img src="https://img.shields.io/website?down_message=board&label=product&up_color=blue&up_message=board&url=https%3A%2F%2Fsharing.clickup.com%2Fb%2F6-169004201-2%2Fstrapiv-4-navigation-board" />
|
|
404
|
+
</a>
|
|
405
|
+
</div>
|
|
406
|
+
|
|
381
407
|
Feel free to fork and make a Pull Request to this plugin project. All the input is warmly welcome!
|
|
382
408
|
|
|
383
409
|
## 👨💻 Community support
|
|
384
410
|
|
|
385
411
|
For general help using Strapi, please refer to [the official Strapi documentation](https://strapi.io/documentation/). For additional help, you can use one of these channels to ask a question:
|
|
386
412
|
|
|
387
|
-
- [
|
|
413
|
+
- [Discord](https://discord.strapi.io/) We're present on official Strapi Discord workspace. Find us by `[VirtusLab]` prefix and DM.
|
|
388
414
|
- [Slack - VirtusLab Open Source](https://virtuslab-oss.slack.com) We're present on a public channel #strapi-molecules
|
|
389
415
|
- [GitHub](https://github.com/VirtusLab/strapi-plugin-navigation/issues) (Bug reports, Contributions, Questions and Discussions)
|
|
416
|
+
- [E-mail](mailto:strapi@virtuslab.com) - we will respond back as soon as possible
|
|
390
417
|
|
|
391
418
|
## 📝 License
|
|
392
419
|
|
|
393
|
-
[MIT License](LICENSE.md) Copyright (c)
|
|
420
|
+
[MIT License](LICENSE.md) Copyright (c) [VirtusLab Sp. z o.o.](https://virtuslab.com/) & [Strapi Solutions](https://strapi.io/).
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"kind": "collectionType",
|
|
3
|
+
"collectionName": "pages",
|
|
4
|
+
"info": {
|
|
5
|
+
"singularName": "page",
|
|
6
|
+
"pluralName": "pages",
|
|
7
|
+
"displayName": "Page",
|
|
8
|
+
"name": "page"
|
|
9
|
+
},
|
|
10
|
+
"options": {
|
|
11
|
+
"increments": true,
|
|
12
|
+
"timestamps": true,
|
|
13
|
+
"searchable": true,
|
|
14
|
+
"previewable": true,
|
|
15
|
+
"draftAndPublish": false
|
|
16
|
+
},
|
|
17
|
+
"pluginOptions": {},
|
|
18
|
+
"attributes": {
|
|
19
|
+
"title": {
|
|
20
|
+
"type": "string",
|
|
21
|
+
"required": true
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
const { isMatch } = require('lodash');
|
|
2
|
+
|
|
3
|
+
const masterModelMock = {
|
|
4
|
+
findOne: () => ({
|
|
5
|
+
id: 1,
|
|
6
|
+
name: "Main navigation",
|
|
7
|
+
slug: "main-navigation",
|
|
8
|
+
visible: true,
|
|
9
|
+
createdAt: "2021-12-30T14:05:50.276Z",
|
|
10
|
+
updatedAt: "2021-12-30T14:05:50.276Z",
|
|
11
|
+
}),
|
|
12
|
+
findMany: () => [{
|
|
13
|
+
id: 1,
|
|
14
|
+
name: "Main navigation",
|
|
15
|
+
slug: "main-navigation",
|
|
16
|
+
visible: true,
|
|
17
|
+
createdAt: "2021-12-30T14:05:50.276Z",
|
|
18
|
+
updatedAt: "2021-12-30T14:05:50.276Z",
|
|
19
|
+
}],
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const itemModelMock = {
|
|
23
|
+
findOne: async () => ({
|
|
24
|
+
id: 1,
|
|
25
|
+
title: "home",
|
|
26
|
+
type: "INTERNAL",
|
|
27
|
+
path: "home1",
|
|
28
|
+
externalPath: null,
|
|
29
|
+
uiRouterKey: "home",
|
|
30
|
+
menuAttached: true,
|
|
31
|
+
order: 1,
|
|
32
|
+
createdAt: "2021-12-31T10:04:54.812Z",
|
|
33
|
+
updatedAt: "2022-01-14T13:36:29.430Z",
|
|
34
|
+
related: {
|
|
35
|
+
id: 1,
|
|
36
|
+
related_id: "1",
|
|
37
|
+
related_type: "api::pages.pages",
|
|
38
|
+
field: "navigation",
|
|
39
|
+
order: 1,
|
|
40
|
+
master: "3",
|
|
41
|
+
createdAt: "2021-12-31T10:04:54.800Z",
|
|
42
|
+
updatedAt: "2021-12-31T10:04:54.800Z",
|
|
43
|
+
navigationItemId: 56,
|
|
44
|
+
},
|
|
45
|
+
parent: null,
|
|
46
|
+
}),
|
|
47
|
+
findMany: async ({where}) => [{
|
|
48
|
+
id: 1,
|
|
49
|
+
title: "home",
|
|
50
|
+
type: "INTERNAL",
|
|
51
|
+
path: "home1",
|
|
52
|
+
externalPath: null,
|
|
53
|
+
uiRouterKey: "home",
|
|
54
|
+
menuAttached: true,
|
|
55
|
+
order: 1,
|
|
56
|
+
createdAt: "2021-12-31T10:04:54.812Z",
|
|
57
|
+
updatedAt: "2022-01-14T13:36:29.430Z",
|
|
58
|
+
master: 1,
|
|
59
|
+
related: {
|
|
60
|
+
id: 1,
|
|
61
|
+
related_id: "1",
|
|
62
|
+
related_type: "api::pages.pages",
|
|
63
|
+
field: "navigation",
|
|
64
|
+
order: 1,
|
|
65
|
+
master: "3",
|
|
66
|
+
createdAt: "2021-12-31T10:04:54.800Z",
|
|
67
|
+
updatedAt: "2021-12-31T10:04:54.800Z",
|
|
68
|
+
navigationItemId: 56,
|
|
69
|
+
},
|
|
70
|
+
parent: null,
|
|
71
|
+
}, {
|
|
72
|
+
id: 2,
|
|
73
|
+
title: "side",
|
|
74
|
+
type: "INTERNAL",
|
|
75
|
+
path: "side",
|
|
76
|
+
externalPath: null,
|
|
77
|
+
uiRouterKey: "side",
|
|
78
|
+
menuAttached: false,
|
|
79
|
+
order: 1,
|
|
80
|
+
createdAt: "2021-12-31T10:04:54.824Z",
|
|
81
|
+
updatedAt: "2021-12-31T12:47:20.508Z",
|
|
82
|
+
master: 1,
|
|
83
|
+
related: {
|
|
84
|
+
id: 2,
|
|
85
|
+
related_id: "2",
|
|
86
|
+
related_type: "api::pages.pages",
|
|
87
|
+
field: "navigation",
|
|
88
|
+
order: 1,
|
|
89
|
+
master: "3",
|
|
90
|
+
createdAt: "2021-12-31T10:04:54.823Z",
|
|
91
|
+
updatedAt: "2021-12-31T10:04:54.823Z",
|
|
92
|
+
navigationItemId: 57,
|
|
93
|
+
},
|
|
94
|
+
parent: {
|
|
95
|
+
id: 1,
|
|
96
|
+
title: "home",
|
|
97
|
+
type: "INTERNAL",
|
|
98
|
+
path: "home1",
|
|
99
|
+
externalPath: null,
|
|
100
|
+
uiRouterKey: "home",
|
|
101
|
+
menuAttached: true,
|
|
102
|
+
order: 1,
|
|
103
|
+
createdAt: "2021-12-31T10:04:54.812Z",
|
|
104
|
+
updatedAt: "2022-01-14T13:36:29.430Z",
|
|
105
|
+
},
|
|
106
|
+
}].filter(item => isMatch(item, where)),
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const pageModelMock = {
|
|
110
|
+
findOne: async ({ where }) => ({
|
|
111
|
+
"id": 1,
|
|
112
|
+
"attributes": {
|
|
113
|
+
"title": "Page nr 1",
|
|
114
|
+
"createdAt": "2022-01-19T08:22:31.244Z",
|
|
115
|
+
"updatedAt": "2022-01-19T08:22:31.244Z",
|
|
116
|
+
"publishedAt": null
|
|
117
|
+
}
|
|
118
|
+
}),
|
|
119
|
+
findMany: async ({ where }) => [{
|
|
120
|
+
"id": 1,
|
|
121
|
+
"attributes": {
|
|
122
|
+
"title": "Page nr 1",
|
|
123
|
+
"createdAt": "2022-01-19T08:22:31.244Z",
|
|
124
|
+
"updatedAt": "2022-01-19T08:22:31.244Z",
|
|
125
|
+
"publishedAt": null
|
|
126
|
+
}
|
|
127
|
+
}, {
|
|
128
|
+
"id": 2,
|
|
129
|
+
"attributes": {
|
|
130
|
+
"title": "Page nr 2",
|
|
131
|
+
"createdAt": "2022-01-19T08:22:50.821Z",
|
|
132
|
+
"updatedAt": "2022-01-19T08:22:50.821Z",
|
|
133
|
+
"publishedAt": null
|
|
134
|
+
}
|
|
135
|
+
}]
|
|
136
|
+
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const plugins = (strapi) => ({
|
|
140
|
+
navigation: {
|
|
141
|
+
get services() { return require('../server/services') },
|
|
142
|
+
service: (key) => (require('../server/services'))[key]({ strapi }),
|
|
143
|
+
get contentTypes() { return require('../server/content-types') },
|
|
144
|
+
contentType: (key) => preparePluginContentType(require('../server/content-types')[key].schema, 'navigation'),
|
|
145
|
+
config: (key) => ({
|
|
146
|
+
...require('../server/config').default,
|
|
147
|
+
contentTypes: ['api::pages.pages'],
|
|
148
|
+
})[key],
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
const contentTypes = {
|
|
153
|
+
'api::pages.pages': {
|
|
154
|
+
...require('./pages.settings.json'),
|
|
155
|
+
uid: 'api::pages.pages',
|
|
156
|
+
modelName: 'page',
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const preparePluginContentType = (schema, plugin) => {
|
|
161
|
+
const { name } = schema.info;
|
|
162
|
+
|
|
163
|
+
return {
|
|
164
|
+
...schema,
|
|
165
|
+
uid: `plugin::${plugin}.${name}`,
|
|
166
|
+
modelName: name,
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const strapiFactory = (plugins, contentTypes) => ({
|
|
171
|
+
get plugins() { return plugins(strapi) },
|
|
172
|
+
plugin: (name) => plugins(strapi)[name],
|
|
173
|
+
get contentTypes() { return contentTypes },
|
|
174
|
+
contentType: (key) => contentTypes[key],
|
|
175
|
+
query: (model) => {
|
|
176
|
+
switch (model) {
|
|
177
|
+
case 'plugin::navigation.navigation':
|
|
178
|
+
return masterModelMock;
|
|
179
|
+
case 'plugin::navigation.navigation-item':
|
|
180
|
+
return itemModelMock;
|
|
181
|
+
case 'api::pages.pages':
|
|
182
|
+
return pageModelMock;
|
|
183
|
+
default:
|
|
184
|
+
return {
|
|
185
|
+
findOne: () => ({}),
|
|
186
|
+
findMany: () => [],
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const setupStrapi = () => {
|
|
193
|
+
Object.defineProperty(global, 'strapi', { value: strapiFactory(plugins, contentTypes) });
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
module.exports = { setupStrapi };
|
|
@@ -2,7 +2,21 @@ import styled from "styled-components";
|
|
|
2
2
|
import { Badge } from '@strapi/design-system/Badge';
|
|
3
3
|
|
|
4
4
|
const ItemCardBadge = styled(Badge)`
|
|
5
|
-
border: 1px solid ${({ theme, borderColor }) => theme.colors[borderColor]}
|
|
5
|
+
border: 1px solid ${({ theme, borderColor }) => theme.colors[borderColor]};
|
|
6
|
+
|
|
7
|
+
${ props => props.small && `
|
|
8
|
+
padding: ${props.theme.spaces[1]};
|
|
9
|
+
margin: 0px ${props.theme.spaces[3]};
|
|
10
|
+
vertical-align: middle;
|
|
11
|
+
|
|
12
|
+
cursor: default;
|
|
13
|
+
|
|
14
|
+
span {
|
|
15
|
+
font-size: .55rem;
|
|
16
|
+
line-height: 1;
|
|
17
|
+
vertical-align: middle;
|
|
18
|
+
}
|
|
19
|
+
`}
|
|
6
20
|
`;
|
|
7
21
|
|
|
8
22
|
export default ItemCardBadge;
|
|
@@ -4,6 +4,7 @@ import { useIntl } from 'react-intl';
|
|
|
4
4
|
import { Flex } from '@strapi/design-system/Flex';
|
|
5
5
|
import { IconButton } from '@strapi/design-system/IconButton';
|
|
6
6
|
import { Typography } from '@strapi/design-system/Typography';
|
|
7
|
+
import { Icon } from '@strapi/design-system/Icon';
|
|
7
8
|
import PencilIcon from '@strapi/icons/Pencil';
|
|
8
9
|
import TrashIcon from '@strapi/icons/Trash';
|
|
9
10
|
import RefreshIcon from '@strapi/icons/Refresh';
|
|
@@ -12,14 +13,14 @@ import Wrapper from './Wrapper';
|
|
|
12
13
|
import ItemCardBadge from '../ItemCardBadge';
|
|
13
14
|
import { getTrad } from "../../../translations";
|
|
14
15
|
|
|
15
|
-
const ItemCardHeader = ({ title, path, icon, removed,
|
|
16
|
-
|
|
16
|
+
const ItemCardHeader = ({ title, path, icon, removed, onItemRemove, onItemEdit, onItemRestore }) => {
|
|
17
|
+
|
|
17
18
|
const { formatMessage } = useIntl();
|
|
18
19
|
|
|
19
20
|
return (
|
|
20
21
|
<Wrapper>
|
|
21
22
|
<Flex alignItems="center">
|
|
22
|
-
{icon}
|
|
23
|
+
<Icon as={icon} />
|
|
23
24
|
<Typography variant="omega" fontWeight="bold">
|
|
24
25
|
{title}
|
|
25
26
|
</Typography>
|
|
@@ -27,23 +28,15 @@ const ItemCardHeader = ({ title, path, icon, removed, isExternal, isPublished, o
|
|
|
27
28
|
{path}
|
|
28
29
|
</Typography>
|
|
29
30
|
</Flex>
|
|
30
|
-
<Flex alignItems="center">
|
|
31
|
-
{removed
|
|
32
|
-
<ItemCardBadge
|
|
31
|
+
<Flex alignItems="center" style={{ zIndex: 2 }}>
|
|
32
|
+
{removed &&
|
|
33
|
+
(<ItemCardBadge
|
|
33
34
|
borderColor={`danger200`}
|
|
34
35
|
backgroundColor={`danger100`}
|
|
35
36
|
textColor={`danger600`}
|
|
36
37
|
>
|
|
37
38
|
{formatMessage(getTrad("navigation.item.badge.removed"))}
|
|
38
|
-
</ItemCardBadge>
|
|
39
|
-
: !isExternal && <ItemCardBadge
|
|
40
|
-
borderColor={`${badgeColor}200`}
|
|
41
|
-
backgroundColor={`${badgeColor}100`}
|
|
42
|
-
textColor={`${badgeColor}600`}
|
|
43
|
-
className="action"
|
|
44
|
-
>
|
|
45
|
-
{formatMessage(getTrad(`navigation.item.badge.${isPublished ? 'published' : 'draft'}`))}
|
|
46
|
-
</ItemCardBadge>
|
|
39
|
+
</ItemCardBadge>)
|
|
47
40
|
}
|
|
48
41
|
|
|
49
42
|
<IconButton disabled={removed} onClick={onItemEdit} label="Edit" icon={<PencilIcon />} />
|
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
import PropTypes from 'prop-types';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import { isEmpty, isNumber } from 'lodash';
|
|
3
|
+
import { isEmpty, isNumber, get } from 'lodash';
|
|
4
4
|
import { useIntl } from "react-intl";
|
|
5
5
|
|
|
6
|
+
import { Box } from '@strapi/design-system/Box';
|
|
6
7
|
import { Card, CardBody } from '@strapi/design-system/Card';
|
|
7
8
|
import { Divider } from '@strapi/design-system/Divider';
|
|
9
|
+
import { Flex } from '@strapi/design-system/Flex';
|
|
10
|
+
import { Link } from '@strapi/design-system/Link';
|
|
8
11
|
import { TextButton } from '@strapi/design-system/TextButton';
|
|
9
12
|
import { Typography } from '@strapi/design-system/Typography';
|
|
10
|
-
|
|
11
|
-
import
|
|
12
|
-
import LinkIcon from '@strapi/icons/Link';
|
|
13
|
+
|
|
14
|
+
import { ArrowRight, Link as LinkIcon, Earth, Plus } from '@strapi/icons';
|
|
13
15
|
|
|
14
16
|
import { navigationItemType } from '../../pages/View/utils/enums';
|
|
15
17
|
import ItemCardHeader from './ItemCardHeader';
|
|
16
18
|
import List from '../NavigationItemList';
|
|
17
19
|
import Wrapper from './Wrapper';
|
|
18
20
|
import { getTrad } from '../../translations';
|
|
21
|
+
import { extractRelatedItemLabel } from '../../pages/View/utils/parsers';
|
|
22
|
+
import ItemCardBadge from './ItemCardBadge';
|
|
23
|
+
import { ItemCardRemovedOverlay } from './ItemCardRemovedOverlay';
|
|
19
24
|
|
|
20
25
|
const Item = (props) => {
|
|
21
26
|
const {
|
|
@@ -32,6 +37,7 @@ const Item = (props) => {
|
|
|
32
37
|
onItemEdit,
|
|
33
38
|
error,
|
|
34
39
|
displayChildren,
|
|
40
|
+
config = {},
|
|
35
41
|
} = props;
|
|
36
42
|
|
|
37
43
|
const {
|
|
@@ -45,6 +51,7 @@ const Item = (props) => {
|
|
|
45
51
|
} = item;
|
|
46
52
|
|
|
47
53
|
const { formatMessage } = useIntl();
|
|
54
|
+
const { contentTypes, contentTypesNameFields } = config;
|
|
48
55
|
const isExternal = type === navigationItemType.EXTERNAL;
|
|
49
56
|
const isPublished = relatedRef && relatedRef?.publishedAt;
|
|
50
57
|
const isNextMenuAllowedLevel = isNumber(allowedLevels) ? level < (allowedLevels - 1) : true;
|
|
@@ -52,38 +59,69 @@ const Item = (props) => {
|
|
|
52
59
|
const hasChildren = !isEmpty(item.items) && !isExternal && !displayChildren;
|
|
53
60
|
const absolutePath = isExternal ? undefined : `${levelPath === '/' ? '' : levelPath}/${path === '/' ? '' : path}`;
|
|
54
61
|
|
|
62
|
+
const relatedItemLabel = !isExternal ? extractRelatedItemLabel(relatedRef, contentTypesNameFields, { contentTypes }) : '';
|
|
63
|
+
const relatedTypeLabel = relatedRef?.labelSingular;
|
|
64
|
+
const relatedBadgeColor = isPublished ? 'success' : 'secondary';
|
|
65
|
+
|
|
55
66
|
return (
|
|
56
67
|
<Wrapper level={level} isLast={isLast}>
|
|
57
|
-
<Card style={{ width: "728px", zIndex: 1, position: "relative" }}>
|
|
68
|
+
<Card style={{ width: "728px", zIndex: 1, position: "relative", overflow: 'hidden' }}>
|
|
69
|
+
{ removed && (<ItemCardRemovedOverlay />) }
|
|
58
70
|
<CardBody>
|
|
59
71
|
<ItemCardHeader
|
|
60
72
|
title={title}
|
|
61
73
|
path={isExternal ? externalPath : absolutePath}
|
|
62
|
-
icon={isExternal ?
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
74
|
+
icon={isExternal ? Earth : LinkIcon }
|
|
75
|
+
onItemRemove={() => onItemRemove({
|
|
76
|
+
...item,
|
|
77
|
+
relatedRef,
|
|
78
|
+
})}
|
|
66
79
|
onItemEdit={() => onItemEdit({
|
|
67
80
|
...item,
|
|
68
81
|
isMenuAllowedLevel,
|
|
69
82
|
isParentAttachedToMenu,
|
|
70
83
|
}, levelPath, isParentAttachedToMenu)}
|
|
71
|
-
onItemRestore={() => onItemRestore(
|
|
84
|
+
onItemRestore={() => onItemRestore({
|
|
85
|
+
...item,
|
|
86
|
+
relatedRef,
|
|
87
|
+
})}
|
|
72
88
|
removed={removed}
|
|
73
89
|
/>
|
|
74
90
|
</CardBody>
|
|
75
91
|
<Divider />
|
|
76
|
-
<CardBody style={{ margin: '8px' }}>
|
|
77
|
-
<
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
{
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
92
|
+
{ !isExternal && (<CardBody style={{ margin: '8px' }}>
|
|
93
|
+
<Flex style={{ width: '100%' }} direction="row" alignItems="center" justifyContent="space-between">
|
|
94
|
+
<TextButton
|
|
95
|
+
disabled={removed}
|
|
96
|
+
startIcon={<Plus />}
|
|
97
|
+
onClick={(e) => onItemLevelAdd(e, viewId, isNextMenuAllowedLevel, absolutePath, menuAttached)}
|
|
98
|
+
>
|
|
99
|
+
<Typography variant="pi" fontWeight="bold" textColor={removed ? "neutral600" : "primary600"}>
|
|
100
|
+
{formatMessage(getTrad("navigation.item.action.newItem"))}
|
|
101
|
+
</Typography>
|
|
102
|
+
</TextButton>
|
|
103
|
+
{ relatedItemLabel && (<Box>
|
|
104
|
+
<ItemCardBadge
|
|
105
|
+
borderColor={`${relatedBadgeColor}200`}
|
|
106
|
+
backgroundColor={`${relatedBadgeColor}100`}
|
|
107
|
+
textColor={`${relatedBadgeColor}600`}
|
|
108
|
+
className="action"
|
|
109
|
+
small
|
|
110
|
+
>
|
|
111
|
+
{formatMessage(getTrad(`navigation.item.badge.${isPublished ? 'published' : 'draft'}`), {
|
|
112
|
+
type: relatedTypeLabel
|
|
113
|
+
})}
|
|
114
|
+
</ItemCardBadge>
|
|
115
|
+
<Typography variant="pi" fontWeight="bold" textColor="neutral600">
|
|
116
|
+
{ relatedItemLabel }
|
|
117
|
+
<Link
|
|
118
|
+
to={`/content-manager/collectionType/${relatedRef?.__collectionUid}/${relatedRef?.id}`}
|
|
119
|
+
endIcon={<ArrowRight />}> </Link>
|
|
120
|
+
</Typography>
|
|
121
|
+
</Box>)
|
|
122
|
+
}
|
|
123
|
+
</Flex>
|
|
124
|
+
</CardBody>)}
|
|
87
125
|
</Card>
|
|
88
126
|
{hasChildren && !removed && <List
|
|
89
127
|
onItemLevelAdd={onItemLevelAdd}
|
|
@@ -92,10 +130,12 @@ const Item = (props) => {
|
|
|
92
130
|
onItemRestore={onItemRestore}
|
|
93
131
|
error={error}
|
|
94
132
|
allowedLevels={allowedLevels}
|
|
95
|
-
isParentAttachedToMenu={
|
|
133
|
+
isParentAttachedToMenu={menuAttached}
|
|
96
134
|
items={item.items}
|
|
97
135
|
level={level + 1}
|
|
98
136
|
levelPath={absolutePath}
|
|
137
|
+
contentTypes={contentTypes}
|
|
138
|
+
contentTypesNameFields={contentTypesNameFields}
|
|
99
139
|
/>
|
|
100
140
|
}
|
|
101
141
|
</Wrapper>
|
|
@@ -110,7 +150,6 @@ Item.propTypes = {
|
|
|
110
150
|
uiRouterKey: PropTypes.string,
|
|
111
151
|
path: PropTypes.string,
|
|
112
152
|
externalPath: PropTypes.string,
|
|
113
|
-
audience: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])),
|
|
114
153
|
related: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
|
115
154
|
menuAttached: PropTypes.bool
|
|
116
155
|
}).isRequired,
|
|
@@ -121,6 +160,10 @@ Item.propTypes = {
|
|
|
121
160
|
onItemRestore: PropTypes.func.isRequired,
|
|
122
161
|
onItemLevelAdd: PropTypes.func.isRequired,
|
|
123
162
|
onItemRemove: PropTypes.func.isRequired,
|
|
163
|
+
config: PropTypes.shape({
|
|
164
|
+
contentTypes: PropTypes.array.isRequired,
|
|
165
|
+
contentTypesNameFields: PropTypes.object.isRequired,
|
|
166
|
+
}).isRequired
|
|
124
167
|
};
|
|
125
168
|
|
|
126
169
|
export default Item;
|
|
@@ -16,6 +16,8 @@ const List = ({
|
|
|
16
16
|
onItemRemove,
|
|
17
17
|
onItemRestore,
|
|
18
18
|
displayFlat,
|
|
19
|
+
contentTypes,
|
|
20
|
+
contentTypesNameFields,
|
|
19
21
|
}) => (
|
|
20
22
|
<Wrapper level={level}>
|
|
21
23
|
{items.map((item, n) => {
|
|
@@ -36,6 +38,10 @@ const List = ({
|
|
|
36
38
|
onItemEdit={onItemEdit}
|
|
37
39
|
error={error}
|
|
38
40
|
displayChildren={displayFlat}
|
|
41
|
+
config={{
|
|
42
|
+
contentTypes,
|
|
43
|
+
contentTypesNameFields
|
|
44
|
+
}}
|
|
39
45
|
/>
|
|
40
46
|
);
|
|
41
47
|
})}
|
|
@@ -51,6 +57,8 @@ List.propTypes = {
|
|
|
51
57
|
onItemRemove: PropTypes.func.isRequired,
|
|
52
58
|
onItemRestore: PropTypes.func.isRequired,
|
|
53
59
|
onItemRestore: PropTypes.func.isRequired,
|
|
60
|
+
contentTypes: PropTypes.array.isRequired,
|
|
61
|
+
contentTypesNameFields: PropTypes.object.isRequired
|
|
54
62
|
};
|
|
55
63
|
|
|
56
64
|
export default List;
|