strapi-plugin-navigation 1.0.2 → 1.1.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/.circleci/config.yml +1 -1
- package/.github/stale.yml +15 -0
- package/.nvmrc +1 -1
- package/README.md +41 -31
- package/__mocks__/helpers/another-plugin/blog-post.settings.json +31 -0
- package/__mocks__/helpers/another-plugin/pages.settings.json +28 -0
- package/__mocks__/helpers/blog-post.settings.json +2 -1
- package/__mocks__/helpers/home-page.settings.json +4 -0
- package/__mocks__/helpers/my-homepage.settings.json +27 -0
- package/__mocks__/helpers/strapi.js +63 -4
- package/admin/src/containers/View/components/NavigationItemForm/utils/form.js +1 -1
- package/admin/src/containers/View/index.js +2 -2
- package/admin/src/containers/View/utils/parsers.js +11 -12
- package/admin/src/translations/en.json +2 -0
- package/admin/src/translations/fr.json +39 -0
- package/admin/src/translations/index.js +3 -1
- package/config/functions/bootstrap.js +107 -11
- package/config/routes.json +8 -0
- package/config/schema.graphql.js +204 -0
- package/controllers/navigation.js +18 -9
- package/models/navigation.settings.json +1 -1
- package/models/navigationItem.js +21 -3
- package/models/navigationItem.settings.json +15 -4
- package/models/navigations_items_related.js +19 -0
- package/models/navigations_items_related.settings.json +45 -0
- package/package.json +3 -3
- package/services/__tests__/navigation.test.js +75 -30
- package/services/navigation.js +224 -102
- package/services/utils/functions.js +10 -10
package/.circleci/config.yml
CHANGED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
daysUntilStale: 30
|
|
2
|
+
daysUntilClose: 7
|
|
3
|
+
exemptLabels:
|
|
4
|
+
- pinned
|
|
5
|
+
- security
|
|
6
|
+
- backlog
|
|
7
|
+
- enhancement
|
|
8
|
+
- feature request
|
|
9
|
+
staleLabel: wontfix
|
|
10
|
+
# Comment to post when marking an issue as stale. Set to `false` to disable
|
|
11
|
+
markComment: >
|
|
12
|
+
This issue has been automatically marked as stale because it has not had
|
|
13
|
+
recent activity. It will be closed if no further activity occurs. Thank you
|
|
14
|
+
for your contributions.
|
|
15
|
+
closeComment: false
|
package/.nvmrc
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
v14.17.3
|
package/README.md
CHANGED
|
@@ -55,7 +55,7 @@ Complete installation requirements are exact same as for Strapi itself and can b
|
|
|
55
55
|
|
|
56
56
|
**Supported Strapi versions**:
|
|
57
57
|
|
|
58
|
-
- Strapi v3.6.
|
|
58
|
+
- Strapi v3.6.8 (recently tested)
|
|
59
59
|
- Strapi v3.x
|
|
60
60
|
|
|
61
61
|
(This plugin may work with the older Strapi versions, but these are not tested nor officially supported at this time.)
|
|
@@ -73,40 +73,22 @@ Complete installation requirements are exact same as for Strapi itself and can b
|
|
|
73
73
|
|
|
74
74
|
## Content Type model relation to Navigation Item
|
|
75
75
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
}
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
inside the `attributes` section like in example below:
|
|
89
|
-
|
|
90
|
-
```
|
|
91
|
-
"attributes": {
|
|
92
|
-
...,
|
|
93
|
-
"navigation": {
|
|
94
|
-
"model": "navigationitem",
|
|
95
|
-
"plugin": "navigation",
|
|
96
|
-
"via": "related",
|
|
97
|
-
"configurable": false,
|
|
98
|
-
"hidden": true
|
|
99
|
-
},
|
|
100
|
-
...
|
|
101
|
-
},
|
|
76
|
+
We can define in `config/plugins.js`
|
|
77
|
+
```js
|
|
78
|
+
navigation: {
|
|
79
|
+
...
|
|
80
|
+
relatedContentTypes: [
|
|
81
|
+
'application::pages.pages'
|
|
82
|
+
],
|
|
83
|
+
...
|
|
84
|
+
},
|
|
102
85
|
```
|
|
103
86
|
|
|
104
87
|
## Configuration
|
|
105
|
-
To setup the plugin properly we recommend to put following snippet as part of `config/
|
|
88
|
+
To setup the plugin properly we recommend to put following snippet as part of `config/plugins.js` or `config/<env>/plugins.js` file. If you've got already configurations for other plugins stores by this way, use just the `navigation` part within exising `plugins` item.
|
|
106
89
|
|
|
107
|
-
```
|
|
90
|
+
```js
|
|
108
91
|
...
|
|
109
|
-
plugins: {
|
|
110
92
|
navigation: {
|
|
111
93
|
additionalFields: ['audience'],
|
|
112
94
|
allowedLevels: 2,
|
|
@@ -114,8 +96,8 @@ To setup the plugin properly we recommend to put following snippet as part of `c
|
|
|
114
96
|
'blog_posts': ['altTitle'],
|
|
115
97
|
'pages': ['title'],
|
|
116
98
|
},
|
|
99
|
+
gql: { ... }
|
|
117
100
|
},
|
|
118
|
-
},
|
|
119
101
|
...
|
|
120
102
|
```
|
|
121
103
|
|
|
@@ -123,6 +105,34 @@ To setup the plugin properly we recommend to put following snippet as part of `c
|
|
|
123
105
|
- `additionalFields` - Additional fields: 'audience', more in the future
|
|
124
106
|
- `allowedLevels` - Maximum level for which your're able to mark item as "Menu attached"
|
|
125
107
|
- `contentTypesNameFields` - Definition of content type title fields like `'content_type_name': ['field_name_1', 'field_name_2']`, if not set titles are pulled from fields like `['title', 'subject', 'name']`
|
|
108
|
+
- `gql` - If you're using GraphQL that's the right place to put all necessary settings. More **[ here ](#gql-configuration)**
|
|
109
|
+
|
|
110
|
+
## GQL Configuration
|
|
111
|
+
To properly configure GQL to work with navigation you should provide `gql` prop which should contain union types which will be used for define GQL response format for your data while fetching:
|
|
112
|
+
|
|
113
|
+
```gql
|
|
114
|
+
master: Int
|
|
115
|
+
items: [NavigationItem]
|
|
116
|
+
related: NavigationRelated
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
as follows:
|
|
120
|
+
|
|
121
|
+
```js
|
|
122
|
+
gql: {
|
|
123
|
+
navigationItemRelated: 'union NavigationRelated = <your GQL related entities>',
|
|
124
|
+
},
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
for example:
|
|
128
|
+
|
|
129
|
+
```js
|
|
130
|
+
gql: {
|
|
131
|
+
navigationItemRelated: 'union NavigationRelated = Pages | UploadFile',
|
|
132
|
+
},
|
|
133
|
+
```
|
|
134
|
+
where `Pages` and `UploadFile` are your types to the **Content Types** you're referring by navigation items relations.
|
|
135
|
+
|
|
126
136
|
|
|
127
137
|
## Public API Navigation Item model
|
|
128
138
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"uid": "plugins::another-plugin.blog-post",
|
|
3
|
+
"plugin": "another-plugin",
|
|
4
|
+
"kind": "collectionType",
|
|
5
|
+
"collectionName": "blog_posts",
|
|
6
|
+
"info": {
|
|
7
|
+
"name": "Blog post"
|
|
8
|
+
},
|
|
9
|
+
"options": {
|
|
10
|
+
"increments": true,
|
|
11
|
+
"timestamps": true,
|
|
12
|
+
"searchable": true,
|
|
13
|
+
"previewable": true
|
|
14
|
+
},
|
|
15
|
+
"attributes": {
|
|
16
|
+
"title": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"required": true
|
|
19
|
+
},
|
|
20
|
+
"altTitle": {
|
|
21
|
+
"type": "string"
|
|
22
|
+
},
|
|
23
|
+
"navigation": {
|
|
24
|
+
"model": "navigationitem",
|
|
25
|
+
"plugin": "navigation",
|
|
26
|
+
"via": "related",
|
|
27
|
+
"configurable": false,
|
|
28
|
+
"hidden": true
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"uid": "plugins::another-plugin.pages",
|
|
3
|
+
"plugin": "another-plugin",
|
|
4
|
+
"kind": "collectionType",
|
|
5
|
+
"collectionName": "pages",
|
|
6
|
+
"info": {
|
|
7
|
+
"name": "Pages"
|
|
8
|
+
},
|
|
9
|
+
"options": {
|
|
10
|
+
"increments": true,
|
|
11
|
+
"timestamps": true,
|
|
12
|
+
"searchable": true,
|
|
13
|
+
"previewable": true
|
|
14
|
+
},
|
|
15
|
+
"attributes": {
|
|
16
|
+
"title": {
|
|
17
|
+
"type": "string",
|
|
18
|
+
"required": true
|
|
19
|
+
},
|
|
20
|
+
"navigation": {
|
|
21
|
+
"model": "navigationitem",
|
|
22
|
+
"plugin": "navigation",
|
|
23
|
+
"via": "related",
|
|
24
|
+
"configurable": false,
|
|
25
|
+
"hidden": true
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
{
|
|
2
|
+
|
|
3
|
+
"uid": "application::page-homes.home-page",
|
|
4
|
+
"kind": "singleType", "collectionName": "page_homes", "info": { "name": "Page Home" }, "options": { "increments": true, "timestamps": true, "draftAndPublish": true }, "attributes": { "Subtitle": { "type": "string" }, "Title": { "type": "string" }, "Summary": { "type": "text" }, "Image": { "model": "file", "via": "related", "allowedTypes": [ "images" ], "plugin": "upload", "required": false }, "Components": { "type": "dynamiczone", "components": [] }, "navigation": { "model": "navigationitem", "plugin": "navigation", "via": "related", "configurable": false, "hidden": true } } }
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"uid": "application::my-homepages.my-homepage",
|
|
3
|
+
"kind": "singleType",
|
|
4
|
+
"collectionName": "my-homepages",
|
|
5
|
+
"info": {
|
|
6
|
+
"name": "My Homepage",
|
|
7
|
+
"description": ""
|
|
8
|
+
},
|
|
9
|
+
"options": {
|
|
10
|
+
"increments": true,
|
|
11
|
+
"timestamps": true,
|
|
12
|
+
"draftAndPublish": true,
|
|
13
|
+
"templateName": "AwesomeTemplate"
|
|
14
|
+
},
|
|
15
|
+
"attributes": {
|
|
16
|
+
"Welcome": {
|
|
17
|
+
"type": "string"
|
|
18
|
+
},
|
|
19
|
+
"navigation": {
|
|
20
|
+
"model": "navigationitem",
|
|
21
|
+
"plugin": "navigation",
|
|
22
|
+
"via": "related",
|
|
23
|
+
"configurable": false,
|
|
24
|
+
"hidden": true
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
const {get} = require('lodash');
|
|
1
2
|
function setupStrapi() {
|
|
2
3
|
Object.defineProperty(global, 'strapi', {
|
|
3
4
|
value: {
|
|
5
|
+
query: jest.fn().mockImplementation(() => ({
|
|
6
|
+
count: jest.fn().mockImplementation(),
|
|
7
|
+
})),
|
|
4
8
|
config: {
|
|
5
9
|
custom: {
|
|
6
10
|
plugins: {
|
|
@@ -11,27 +15,82 @@ function setupStrapi() {
|
|
|
11
15
|
},
|
|
12
16
|
},
|
|
13
17
|
},
|
|
18
|
+
get(path, defaultValue) {
|
|
19
|
+
return get(strapi, path, defaultValue);
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
api: {
|
|
23
|
+
'home-page': {
|
|
24
|
+
config: {
|
|
25
|
+
routes: [
|
|
26
|
+
{
|
|
27
|
+
method: 'GET',
|
|
28
|
+
path: '/custom-api',
|
|
29
|
+
handler: 'home-page.find',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
method: 'PUT',
|
|
33
|
+
path: '/custom-api',
|
|
34
|
+
handler: 'home-page.update',
|
|
35
|
+
},
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
}
|
|
14
39
|
},
|
|
15
40
|
contentTypes: {
|
|
16
|
-
'
|
|
41
|
+
'pages': {
|
|
17
42
|
...require('./pages.settings.json'),
|
|
18
43
|
apiName: 'pages',
|
|
44
|
+
modelName: 'pages',
|
|
19
45
|
associations: [{ model: 'navigationitem' }],
|
|
20
46
|
},
|
|
21
|
-
'blog-post': {
|
|
47
|
+
'application::blog-post.blog-post': {
|
|
22
48
|
...require('./blog-post.settings.json'),
|
|
23
49
|
apiName: 'blog-posts',
|
|
50
|
+
modelName: 'blog-posts',
|
|
51
|
+
associations: [{ model: 'navigationitem' }],
|
|
52
|
+
},
|
|
53
|
+
'application::my-homepages.my-homepage': {
|
|
54
|
+
...require('./my-homepage.settings.json'),
|
|
55
|
+
apiName: 'my-homepage',
|
|
56
|
+
modelName: 'my-homepage',
|
|
24
57
|
associations: [{ model: 'navigationitem' }],
|
|
25
58
|
},
|
|
59
|
+
'application::page-homes.home-page': {
|
|
60
|
+
...require('./home-page.settings.json'),
|
|
61
|
+
apiName: 'custom-api',
|
|
62
|
+
modelName: 'home-page',
|
|
63
|
+
associations: [{ model: 'navigationitem' }],
|
|
64
|
+
},
|
|
65
|
+
'plugins::another-plugin.pages': {
|
|
66
|
+
...require('./another-plugin/pages.settings.json'),
|
|
67
|
+
modelName: 'plugin-pages',
|
|
68
|
+
associations: [{ model: 'navigationitem' }],
|
|
69
|
+
},
|
|
70
|
+
'plugins::another-plugin.blog-post': {
|
|
71
|
+
...require('./another-plugin/blog-post.settings.json'),
|
|
72
|
+
modelName: 'plugin-blog-posts',
|
|
73
|
+
associations: [{ model: 'navigationitem' }],
|
|
74
|
+
}
|
|
26
75
|
},
|
|
27
76
|
plugins: {
|
|
28
77
|
navigation: {
|
|
29
78
|
services: {
|
|
30
79
|
navigation: jest.fn().mockImplementation(),
|
|
31
80
|
},
|
|
81
|
+
relatedContentTypes: [
|
|
82
|
+
'application::pages.pages',
|
|
83
|
+
'application::blog-post.blog-post',
|
|
84
|
+
'application::my-homepages.my-homepage',
|
|
85
|
+
'application::page-homes.home-page',
|
|
86
|
+
'plugins::another-plugin.pages',
|
|
87
|
+
'plugins::another-plugin.blog-post'
|
|
88
|
+
]
|
|
89
|
+
},
|
|
90
|
+
anotherPlugin: {
|
|
32
91
|
models: {
|
|
33
|
-
'pages': require('./pages.settings.json'),
|
|
34
|
-
'blog-post': require('./blog-post.settings.json'),
|
|
92
|
+
'plugin-pages': require('./another-plugin/pages.settings.json'),
|
|
93
|
+
'plugin-blog-post': require('./another-plugin/blog-post.settings.json'),
|
|
35
94
|
}
|
|
36
95
|
}
|
|
37
96
|
},
|
|
@@ -29,7 +29,7 @@ export const form = {
|
|
|
29
29
|
is: val => val === navigationItemType.EXTERNAL,
|
|
30
30
|
then: yup.string()
|
|
31
31
|
.required(translatedErrors.required)
|
|
32
|
-
.matches(/(#.*)|(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})/, {
|
|
32
|
+
.matches(/(#.*)|(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,}|mailto:.+@(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])\.)+[^.\s]{2,})/, {
|
|
33
33
|
excludeEmptyString: true,
|
|
34
34
|
message: `${pluginId}.popup.item.form.externalPath.validation.type`,
|
|
35
35
|
}),
|
|
@@ -58,13 +58,13 @@ const View = () => {
|
|
|
58
58
|
const navigationSelectValue = get(activeNavigation, "id", null);
|
|
59
59
|
const actions = [
|
|
60
60
|
{
|
|
61
|
-
label:
|
|
61
|
+
label: formatMessage(getTrad('submit.cta.cancel')),
|
|
62
62
|
onClick: () => isLoadingForSubmit ? null : handleResetNavigationData(),
|
|
63
63
|
color: "cancel",
|
|
64
64
|
type: "button",
|
|
65
65
|
},
|
|
66
66
|
{
|
|
67
|
-
label:
|
|
67
|
+
label: formatMessage(getTrad('submit.cta.save')),
|
|
68
68
|
onClick: () =>
|
|
69
69
|
isLoadingForSubmit || structureHasErrors ? null : handleSubmitNavigation(formatMessage, transformToRESTPayload(changedActiveNavigation, config)),
|
|
70
70
|
color: "success",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isUuid, uuid } from 'uuidv4';
|
|
2
|
-
import { find, get, isArray, isEmpty, isNil, isNumber, isObject, isString,
|
|
2
|
+
import { find, get, isArray, isEmpty, isNil, isNumber, isObject, isString, last, omit, orderBy } from 'lodash';
|
|
3
3
|
import { navigationItemType } from './enums';
|
|
4
4
|
|
|
5
5
|
export const transformItemToRESTPayload = (
|
|
@@ -46,8 +46,7 @@ export const transformItemToRESTPayload = (
|
|
|
46
46
|
order,
|
|
47
47
|
uiRouterKey,
|
|
48
48
|
menuAttached,
|
|
49
|
-
audience: audience.map((audienceItem) =>
|
|
50
|
-
isObject(audienceItem) ? audienceItem.value : audienceItem,
|
|
49
|
+
audience: audience.map((audienceItem) => isObject(audienceItem) ? audienceItem.value || audienceItem.id : audienceItem,
|
|
51
50
|
),
|
|
52
51
|
path: isExternal ? undefined : path,
|
|
53
52
|
externalPath: isExternal ? externalPath : undefined,
|
|
@@ -65,7 +64,7 @@ export const transformItemToRESTPayload = (
|
|
|
65
64
|
};
|
|
66
65
|
|
|
67
66
|
export const transformToRESTPayload = (payload, config = {}) => {
|
|
68
|
-
|
|
67
|
+
const { id, name, visible, items } = payload;
|
|
69
68
|
return {
|
|
70
69
|
id,
|
|
71
70
|
name,
|
|
@@ -120,7 +119,7 @@ const linkRelations = (item, config) => {
|
|
|
120
119
|
const shouldBuildRelated = !relatedRef || (relatedRef && (relatedRef.id !== relatedId));
|
|
121
120
|
if (shouldBuildRelated && !shouldFindRelated) {
|
|
122
121
|
const relatedContentType = find(contentTypes,
|
|
123
|
-
ct =>
|
|
122
|
+
ct => ct.uid === relatedItem.__contentType, {});
|
|
124
123
|
const { uid, labelSingular, isSingle } = relatedContentType;
|
|
125
124
|
relation = {
|
|
126
125
|
related: relatedItem.id,
|
|
@@ -262,7 +261,7 @@ export const usedContentTypes = (items = []) => items.flatMap(
|
|
|
262
261
|
if (item.relatedRef) {
|
|
263
262
|
return [item.relatedRef, ...used];
|
|
264
263
|
}
|
|
265
|
-
return used;
|
|
264
|
+
return used;
|
|
266
265
|
},
|
|
267
266
|
);
|
|
268
267
|
|
|
@@ -284,11 +283,11 @@ export const isRelationPublished = ({ relatedRef, relatedType = {}, type, isColl
|
|
|
284
283
|
return true;
|
|
285
284
|
};
|
|
286
285
|
|
|
287
|
-
export const validateNavigationStructure = (items = []) =>
|
|
288
|
-
items.map(item =>
|
|
289
|
-
(item.removed || isRelationCorrect({
|
|
290
|
-
related: item.related,
|
|
286
|
+
export const validateNavigationStructure = (items = []) =>
|
|
287
|
+
items.map(item =>
|
|
288
|
+
(item.removed || isRelationCorrect({
|
|
289
|
+
related: item.related,
|
|
291
290
|
type: item.type,
|
|
292
|
-
})) &&
|
|
291
|
+
})) &&
|
|
293
292
|
validateNavigationStructure(item.items)
|
|
294
|
-
).filter(item => !item).length === 0;
|
|
293
|
+
).filter(item => !item).length === 0;
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
"plugin.name": "UI Navigation",
|
|
3
3
|
"header.title": "Navigation",
|
|
4
4
|
"header.description": "Define your portal navigation",
|
|
5
|
+
"submit.cta.cancel": "Cancel",
|
|
6
|
+
"submit.cta.save": "Save",
|
|
5
7
|
"empty": "Navigation container is empty",
|
|
6
8
|
"empty.cta": "Create first item",
|
|
7
9
|
"popup.item.header": "Manage navigation item",
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"plugin.name": "UI Navigation",
|
|
3
|
+
"header.title": "Navigation",
|
|
4
|
+
"header.description": "Définisser votre menu de navigation",
|
|
5
|
+
"submit.cta.cancel": "Annuler",
|
|
6
|
+
"submit.cta.save": "Sauvegarder",
|
|
7
|
+
"empty": "Le menu de navigation est vide",
|
|
8
|
+
"empty.cta": "Créer un premier élément",
|
|
9
|
+
"popup.item.header": "Modifiez les éléments de navigation",
|
|
10
|
+
"popup.item.form.title.label": "Titre",
|
|
11
|
+
"popup.item.form.title.placeholder": "Saisissez le titre de l'élément ou laissez le champ vide pour utiliser celui de l'entité associée",
|
|
12
|
+
"popup.item.form.uiRouterKey.label": "Clé de routeur pour l'interface utilisateur",
|
|
13
|
+
"popup.item.form.uiRouterKey.placeholder": "Si vide, généré automatiquement par \"Title\"",
|
|
14
|
+
"popup.item.form.path.label": "URL",
|
|
15
|
+
"popup.item.form.path.placeholder": "Une partie d'URL unique identifie cet élément",
|
|
16
|
+
"popup.item.form.path.preview": "Aperçu:",
|
|
17
|
+
"popup.item.form.externalPath.label": "URL externe",
|
|
18
|
+
"popup.item.form.externalPath.placeholder": "Lien vers la source externe",
|
|
19
|
+
"popup.item.form.externalPath.validation.type": "Cette valeur d'URL n'est pas correcte.",
|
|
20
|
+
"popup.item.form.menuAttached.label": "Ajouter au menu",
|
|
21
|
+
"popup.item.form.type.label": "Type",
|
|
22
|
+
"popup.item.form.type.internal.label": "Interne",
|
|
23
|
+
"popup.item.form.type.external.label": "Externe",
|
|
24
|
+
"popup.item.form.audience.label": "Audience",
|
|
25
|
+
"popup.item.form.audience.placeholder": "Écrivez pour lancer la recherche...",
|
|
26
|
+
"popup.item.form.relatedSection.label": "Relation avec",
|
|
27
|
+
"popup.item.form.relatedType.label": "Type de contenu",
|
|
28
|
+
"popup.item.form.related.label": "Entitée",
|
|
29
|
+
"popup.item.form.related.empty": "Il n'y a plus d'entitée \"{ contentTypeName }\", que vous pouvez sélectionner",
|
|
30
|
+
"popup.item.form.button.create": "Créer un élément",
|
|
31
|
+
"popup.item.form.button.update": "Modifier l'élément",
|
|
32
|
+
"popup.item.form.button.restore": "Restaurer l'élément",
|
|
33
|
+
"popup.item.form.button.remove": "Supprimer",
|
|
34
|
+
"notification.navigation.submit": "Les modifications de navigation ont été enregistrées",
|
|
35
|
+
"notification.navigation.error": "Chemin indisponible: { path } dans le parent: { parentTitle } pour l'élément { errorTitles }",
|
|
36
|
+
"notification.navigation.item.relation": "L'entitée n'a pas de relations!",
|
|
37
|
+
"notification.navigation.item.relation.status.draft": "brouillon",
|
|
38
|
+
"notification.navigation.item.relation.status.published": "publié"
|
|
39
|
+
}
|
|
@@ -1,26 +1,112 @@
|
|
|
1
|
-
const { isEmpty } = require(
|
|
1
|
+
const { isEmpty, get, last } = require('lodash');
|
|
2
|
+
|
|
3
|
+
const saveJSONParse = (value) => {
|
|
4
|
+
try {
|
|
5
|
+
return JSON.parse(value).map((_) => ({ ..._, id: _._id }));
|
|
6
|
+
} catch (e) {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const getDefaultConnectionName = (strapi) => strapi.config.get('database.defaultConnection');
|
|
12
|
+
|
|
13
|
+
const isMongo = (strapi) => {
|
|
14
|
+
const connectionName = getDefaultConnectionName(strapi);
|
|
15
|
+
return strapi.config.get(`database.connections.${connectionName}.connector`).includes('mongo');
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const getNavigationMorphData = (strapi) => {
|
|
19
|
+
const connectionName = getDefaultConnectionName(strapi);
|
|
20
|
+
const { [connectionName]: knex } = strapi.connections;
|
|
21
|
+
return knex.schema.hasTable('navigations_items_morph').then((exist)=> exist ? knex('navigations_items_morph').select('*') : []);
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const getNavigationItemsModel = (strapi) => strapi.query('navigationitem', 'navigation');
|
|
25
|
+
|
|
26
|
+
const getRelatedModel = (strapi) => strapi.query('navigations_items_related', 'navigation');
|
|
27
|
+
|
|
28
|
+
const createRelatedData = (relatedModel, navigationItemsModel, items) => ({
|
|
29
|
+
field,
|
|
30
|
+
order,
|
|
31
|
+
related_id,
|
|
32
|
+
related_type,
|
|
33
|
+
navigations_items_id,
|
|
34
|
+
}) => {
|
|
35
|
+
const item = items.find(item => item.id === navigations_items_id);
|
|
36
|
+
const modelUID = get(strapi.query(related_type), 'model.uid');
|
|
37
|
+
if (item && modelUID) {
|
|
38
|
+
const relatedData = {
|
|
39
|
+
field,
|
|
40
|
+
order,
|
|
41
|
+
related_id,
|
|
42
|
+
related_type: modelUID,
|
|
43
|
+
master: get(item.master, 'id', item.master),
|
|
44
|
+
};
|
|
45
|
+
return relatedModel.create(relatedData)
|
|
46
|
+
.then(
|
|
47
|
+
({ id }) => navigationItemsModel.update({ id: navigations_items_id }, { related: id }),
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
return Promise.resolve();
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const migrateNavigationItemsSQL = async (strapi) => {
|
|
54
|
+
const morphData = await getNavigationMorphData(strapi);
|
|
55
|
+
if (morphData.length) {
|
|
56
|
+
const relatedModel = getRelatedModel(strapi);
|
|
57
|
+
const navigationItemsModel = getNavigationItemsModel(strapi);
|
|
58
|
+
const items = await navigationItemsModel.find({});
|
|
59
|
+
await Promise.all(morphData.map(createRelatedData(relatedModel, navigationItemsModel, items)));
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const migrateNavigationItemsMongo = async (strapi) => {
|
|
64
|
+
const navigationItemsModel = getNavigationItemsModel(strapi);
|
|
65
|
+
const connectionName = getDefaultConnectionName(strapi);
|
|
66
|
+
const models = strapi.connections[connectionName].models;
|
|
67
|
+
const items = (await models.NavigationNavigationitem.find({}))
|
|
68
|
+
// workaround to change type from object to int
|
|
69
|
+
.map(_ => ({ ..._.toObject(), related: last(saveJSONParse(get(_.errors, 'related.properties.value', null))) }))
|
|
70
|
+
.filter(_ => _.related);
|
|
71
|
+
|
|
72
|
+
if (items.length) {
|
|
73
|
+
await Promise.all(items.map(item => {
|
|
74
|
+
const data = {
|
|
75
|
+
related_id: item.related.ref,
|
|
76
|
+
related_type: models[item.related.kind].uid,
|
|
77
|
+
field: item.related.field,
|
|
78
|
+
order: 1,
|
|
79
|
+
master: item.master,
|
|
80
|
+
};
|
|
81
|
+
return getRelatedModel(strapi)
|
|
82
|
+
.create(data)
|
|
83
|
+
.then(result => navigationItemsModel.update({ id: item.id }, { related: [result.id] }));
|
|
84
|
+
}));
|
|
85
|
+
|
|
86
|
+
}
|
|
87
|
+
};
|
|
2
88
|
|
|
3
89
|
module.exports = async () => {
|
|
4
90
|
// Check if the plugin users-permissions is installed because the navigation needs it
|
|
5
|
-
if (Object.keys(strapi.plugins).indexOf(
|
|
91
|
+
if (Object.keys(strapi.plugins).indexOf('users-permissions') === -1) {
|
|
6
92
|
throw new Error(
|
|
7
|
-
|
|
93
|
+
'In order to make the navigation plugin work the users-permissions plugin is required',
|
|
8
94
|
);
|
|
9
95
|
}
|
|
10
96
|
|
|
11
97
|
// Add permissions
|
|
12
98
|
const actions = [
|
|
13
99
|
{
|
|
14
|
-
section:
|
|
15
|
-
displayName:
|
|
16
|
-
uid:
|
|
17
|
-
pluginName:
|
|
100
|
+
section: 'plugins',
|
|
101
|
+
displayName: 'Access the Navigation',
|
|
102
|
+
uid: 'read',
|
|
103
|
+
pluginName: 'navigation',
|
|
18
104
|
},
|
|
19
105
|
{
|
|
20
|
-
section:
|
|
21
|
-
displayName:
|
|
22
|
-
uid:
|
|
23
|
-
pluginName:
|
|
106
|
+
section: 'plugins',
|
|
107
|
+
displayName: 'Ability to change the Navigation',
|
|
108
|
+
uid: 'update',
|
|
109
|
+
pluginName: 'navigation',
|
|
24
110
|
},
|
|
25
111
|
];
|
|
26
112
|
|
|
@@ -36,6 +122,16 @@ module.exports = async () => {
|
|
|
36
122
|
visible: true,
|
|
37
123
|
});
|
|
38
124
|
}
|
|
125
|
+
const relatedModel = getRelatedModel(global.strapi);
|
|
126
|
+
const isMigrated = !!(await relatedModel.count({}));
|
|
127
|
+
if (!isMigrated) {
|
|
128
|
+
const isMongoDB = isMongo(global.strapi);
|
|
129
|
+
if (isMongoDB) {
|
|
130
|
+
await migrateNavigationItemsMongo(global.strapi);
|
|
131
|
+
} else {
|
|
132
|
+
await migrateNavigationItemsSQL(global.strapi);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
39
135
|
|
|
40
136
|
const { actionProvider } = strapi.admin.services.permission;
|
|
41
137
|
await actionProvider.registerMany(actions);
|