strapi-plugin-navigation 2.0.4 → 2.0.7
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 +2 -1
- package/admin/src/components/CollapseButton/index.js +31 -0
- package/admin/src/components/Item/ItemCardBadge/index.js +4 -4
- package/admin/src/components/Item/ItemCardHeader/Wrapper.js +0 -4
- package/admin/src/components/Item/ItemCardHeader/index.js +5 -4
- package/admin/src/components/Item/Wrapper.js +1 -1
- package/admin/src/components/Item/index.js +129 -66
- package/admin/src/components/NavigationItemList/Wrapper.js +1 -1
- package/admin/src/components/NavigationItemList/index.js +6 -0
- package/admin/src/components/Search/index.js +1 -1
- package/admin/src/pages/SettingsPage/index.js +95 -91
- package/admin/src/pages/View/components/NavigationItemForm/index.js +49 -30
- package/admin/src/pages/View/components/NavigationItemPopup/NavigationItemPopupHeader.js +3 -3
- package/admin/src/pages/View/index.js +81 -9
- package/admin/src/pages/View/utils/enums.js +1 -0
- package/admin/src/pages/View/utils/parsers.js +6 -3
- package/admin/src/translations/en.json +15 -3
- package/admin/src/utils/index.js +4 -2
- package/package.json +2 -3
- package/server/bootstrap.js +3 -22
- package/server/config/index.js +1 -0
- package/server/config.js +1 -0
- package/server/content-types/navigation-item/lifecycle.js +1 -0
- package/server/content-types/navigation-item/schema.json +7 -1
- package/server/graphql/types/content-types-name-fields.js +2 -2
- package/server/graphql/types/navigation-item.js +1 -1
- package/server/services/navigation.js +44 -19
- package/server/services/utils/functions.js +1 -1
- package/yarn-error.log +0 -5263
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React, { useState } from 'react';
|
|
2
2
|
import { Formik } from 'formik';
|
|
3
|
-
import { isEmpty, capitalize, isEqual } from 'lodash';
|
|
3
|
+
import { isEmpty, capitalize, isEqual, orderBy } from 'lodash';
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
CheckPermissions,
|
|
@@ -8,9 +8,11 @@ import {
|
|
|
8
8
|
Form,
|
|
9
9
|
useOverlayBlocker,
|
|
10
10
|
useAutoReloadOverlayBlocker,
|
|
11
|
+
SettingsPageTitle,
|
|
11
12
|
} from '@strapi/helper-plugin';
|
|
12
13
|
import { Main } from '@strapi/design-system/Main';
|
|
13
14
|
import { ContentLayout, HeaderLayout } from '@strapi/design-system/Layout';
|
|
15
|
+
import { Accordion, AccordionToggle, AccordionContent, AccordionGroup } from '@strapi/design-system/Accordion';
|
|
14
16
|
import { Button } from '@strapi/design-system/Button';
|
|
15
17
|
import { Box } from '@strapi/design-system/Box';
|
|
16
18
|
import { Stack } from '@strapi/design-system/Stack';
|
|
@@ -19,13 +21,8 @@ import { Grid, GridItem } from '@strapi/design-system/Grid';
|
|
|
19
21
|
import { ToggleInput } from '@strapi/design-system/ToggleInput';
|
|
20
22
|
import { NumberInput } from '@strapi/design-system/NumberInput';
|
|
21
23
|
import { Select, Option } from '@strapi/design-system/Select';
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
Card,
|
|
26
|
-
CardBody,
|
|
27
|
-
CardContent,
|
|
28
|
-
} from '@strapi/design-system/Card';
|
|
24
|
+
import { Tooltip } from '@strapi/design-system/Tooltip';
|
|
25
|
+
import { Check, Refresh, Play, Information } from '@strapi/icons';
|
|
29
26
|
|
|
30
27
|
import permissions from '../../permissions';
|
|
31
28
|
import useNavigationConfig from '../../hooks/useNavigationConfig';
|
|
@@ -40,20 +37,29 @@ const SettingsPage = () => {
|
|
|
40
37
|
const { lockAppWithAutoreload, unlockAppWithAutoreload } = useAutoReloadOverlayBlocker();
|
|
41
38
|
const [isRestorePopupOpen, setIsRestorePopupOpen] = useState(false);
|
|
42
39
|
const [isRestartRequired, setIsRestartRequired] = useState(false);
|
|
40
|
+
const [contentTypeExpanded, setContentTypeExpanded] = useState(undefined);
|
|
43
41
|
const { data: navigationConfigData, isLoading: isConfigLoading, err: configErr, submitMutation, restoreMutation, restartMutation } = useNavigationConfig();
|
|
44
42
|
const { data: allContentTypesData, isLoading: isContentTypesLoading, err: contentTypesErr } = useAllContentTypes();
|
|
45
43
|
const isLoading = isConfigLoading || isContentTypesLoading;
|
|
46
44
|
const isError = configErr || contentTypesErr;
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
const boxDefaultProps = {
|
|
46
|
+
background: "neutral0",
|
|
47
|
+
hasRadius: true,
|
|
48
|
+
shadow: "filterShadow",
|
|
49
|
+
padding: 6,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const preparePayload = ({ selectedContentTypes, nameFields, audienceFieldChecked, allowedLevels, populate }) => ({
|
|
49
53
|
contentTypes: selectedContentTypes,
|
|
50
54
|
contentTypesNameFields: nameFields,
|
|
55
|
+
contentTypesPopulate: populate,
|
|
51
56
|
additionalFields: audienceFieldChecked ? [navigationItemAdditionalFields.AUDIENCE] : [],
|
|
52
57
|
allowedLevels: allowedLevels,
|
|
53
58
|
gql: {
|
|
54
|
-
navigationItemRelated: selectedContentTypes.map(uid => allContentTypes.find(ct => ct.uid === uid).info.displayName)
|
|
59
|
+
navigationItemRelated: selectedContentTypes.map(uid => allContentTypes.find(ct => ct.uid === uid).info.displayName.replace(/\s+/g, ''))
|
|
55
60
|
}
|
|
56
|
-
})
|
|
61
|
+
});
|
|
62
|
+
|
|
57
63
|
const onSave = async (form) => {
|
|
58
64
|
lockApp();
|
|
59
65
|
const payload = preparePayload(form);
|
|
@@ -82,6 +88,7 @@ const SettingsPage = () => {
|
|
|
82
88
|
unlockAppWithAutoreload();
|
|
83
89
|
};
|
|
84
90
|
const handleRestartDiscard = () => setIsRestartRequired(false);
|
|
91
|
+
const handleSetContentTypeExpanded = key => setContentTypeExpanded(key === contentTypeExpanded ? undefined : key);
|
|
85
92
|
|
|
86
93
|
const prepareNameFieldFor = (uid, current, value) => ({
|
|
87
94
|
...current,
|
|
@@ -104,8 +111,9 @@ const SettingsPage = () => {
|
|
|
104
111
|
const allContentTypes = !isLoading && Object.values(allContentTypesData).filter(item => item.uid.includes('api::'));
|
|
105
112
|
const selectedContentTypes = navigationConfigData?.contentTypes.map(item => item.uid);
|
|
106
113
|
const audienceFieldChecked = navigationConfigData?.additionalFields.includes(navigationItemAdditionalFields.AUDIENCE);
|
|
107
|
-
const allowedLevels = navigationConfigData?.allowedLevels;
|
|
108
|
-
const nameFields = navigationConfigData?.contentTypesNameFields
|
|
114
|
+
const allowedLevels = navigationConfigData?.allowedLevels || 2;
|
|
115
|
+
const nameFields = navigationConfigData?.contentTypesNameFields || {}
|
|
116
|
+
const populate = navigationConfigData?.contentTypesPopulate || {}
|
|
109
117
|
|
|
110
118
|
return (
|
|
111
119
|
<>
|
|
@@ -119,6 +127,7 @@ const SettingsPage = () => {
|
|
|
119
127
|
audienceFieldChecked,
|
|
120
128
|
allowedLevels,
|
|
121
129
|
nameFields,
|
|
130
|
+
populate,
|
|
122
131
|
}}
|
|
123
132
|
onSubmit={onSave}
|
|
124
133
|
>
|
|
@@ -145,12 +154,7 @@ const SettingsPage = () => {
|
|
|
145
154
|
onClose={handleRestartDiscard}>
|
|
146
155
|
{getMessage('pages.settings.actions.restart.alert.description')}
|
|
147
156
|
</RestartAlert>)}
|
|
148
|
-
<Box
|
|
149
|
-
background="neutral0"
|
|
150
|
-
hasRadius
|
|
151
|
-
shadow="filterShadow"
|
|
152
|
-
padding={6}
|
|
153
|
-
>
|
|
157
|
+
<Box {...boxDefaultProps} >
|
|
154
158
|
<Stack size={4}>
|
|
155
159
|
<Typography variant="delta" as="h2">
|
|
156
160
|
{getMessage('pages.settings.general.title')}
|
|
@@ -172,6 +176,75 @@ const SettingsPage = () => {
|
|
|
172
176
|
{allContentTypes.map((item) => <Option key={item.uid} value={item.uid}>{item.info.displayName}</Option>)}
|
|
173
177
|
</Select>
|
|
174
178
|
</GridItem>
|
|
179
|
+
{!isEmpty(values.selectedContentTypes) && (
|
|
180
|
+
<GridItem col={12}>
|
|
181
|
+
<AccordionGroup
|
|
182
|
+
label={getMessage('pages.settings.form.contentTypesSettings.label')}
|
|
183
|
+
labelAction={<Tooltip description={getMessage('pages.settings.form.contentTypesSettings.tooltip')}>
|
|
184
|
+
<Information aria-hidden={true} />
|
|
185
|
+
</Tooltip>}>
|
|
186
|
+
{orderBy(values.selectedContentTypes).map(uid => {
|
|
187
|
+
const { attributes, info: { displayName } } = allContentTypes.find(item => item.uid == uid);
|
|
188
|
+
const stringAttributes = Object.keys(attributes).filter(_ => attributes[_].type === 'string');
|
|
189
|
+
const relationAttributes = Object.keys(attributes).filter(_ => attributes[_].type === 'relation');
|
|
190
|
+
const key = `collectionSettings-${uid}`;
|
|
191
|
+
return (<Accordion
|
|
192
|
+
expanded={contentTypeExpanded === key}
|
|
193
|
+
toggle={() => handleSetContentTypeExpanded(key)}
|
|
194
|
+
key={key}
|
|
195
|
+
id={key}
|
|
196
|
+
size="S">
|
|
197
|
+
<AccordionToggle title={displayName} togglePosition="left" />
|
|
198
|
+
<AccordionContent>
|
|
199
|
+
<Box padding={6}>
|
|
200
|
+
<Stack size={4}>
|
|
201
|
+
<Select
|
|
202
|
+
name={`collectionSettings-${uid}-entryLabel`}
|
|
203
|
+
label={getMessage('pages.settings.form.nameField.label')}
|
|
204
|
+
hint={getMessage(`pages.settings.form.populate.${isEmpty(stringAttributes) ? 'empty' : 'hint'}`)}
|
|
205
|
+
placeholder={getMessage('pages.settings.form.nameField.placeholder')}
|
|
206
|
+
onClear={() => null}
|
|
207
|
+
value={values.nameFields[uid] || []}
|
|
208
|
+
onChange={(value) => setFieldValue('nameFields', prepareNameFieldFor(uid, values.nameFields, value))}
|
|
209
|
+
multi
|
|
210
|
+
withTags
|
|
211
|
+
disabled={isRestartRequired || isEmpty(stringAttributes)}
|
|
212
|
+
>
|
|
213
|
+
{stringAttributes.map(key =>
|
|
214
|
+
(<Option key={uid + key} value={key}>{capitalize(key.split('_').join(' '))}</Option>))}
|
|
215
|
+
</Select>
|
|
216
|
+
<Select
|
|
217
|
+
name={`collectionSettings-${uid}-populate`}
|
|
218
|
+
label={getMessage('pages.settings.form.populate.label')}
|
|
219
|
+
hint={getMessage(`pages.settings.form.populate.${isEmpty(relationAttributes) ? 'empty' : 'hint'}`)}
|
|
220
|
+
placeholder={getMessage('pages.settings.form.populate.placeholder')}
|
|
221
|
+
onClear={() => null}
|
|
222
|
+
value={values.populate[uid] || []}
|
|
223
|
+
onChange={(value) => setFieldValue('populate', prepareNameFieldFor(uid, values.populate, value))}
|
|
224
|
+
multi
|
|
225
|
+
withTags
|
|
226
|
+
disabled={isRestartRequired || isEmpty(relationAttributes)}
|
|
227
|
+
>
|
|
228
|
+
{relationAttributes.map(key =>
|
|
229
|
+
(<Option key={uid + key} value={key}>{capitalize(key.split('_').join(' '))}</Option>))}
|
|
230
|
+
</Select>
|
|
231
|
+
</Stack>
|
|
232
|
+
</Box>
|
|
233
|
+
|
|
234
|
+
</AccordionContent>
|
|
235
|
+
</Accordion>);
|
|
236
|
+
})}
|
|
237
|
+
</AccordionGroup>
|
|
238
|
+
</GridItem>)}
|
|
239
|
+
</Grid>
|
|
240
|
+
</Stack>
|
|
241
|
+
</Box>
|
|
242
|
+
<Box {...boxDefaultProps} >
|
|
243
|
+
<Stack size={4}>
|
|
244
|
+
<Typography variant="delta" as="h2">
|
|
245
|
+
{getMessage('pages.settings.additional.title')}
|
|
246
|
+
</Typography>
|
|
247
|
+
<Grid gap={4}>
|
|
175
248
|
<GridItem col={3} s={6} xs={12}>
|
|
176
249
|
<NumberInput
|
|
177
250
|
name="allowedLevels"
|
|
@@ -183,20 +256,6 @@ const SettingsPage = () => {
|
|
|
183
256
|
disabled={isRestartRequired}
|
|
184
257
|
/>
|
|
185
258
|
</GridItem>
|
|
186
|
-
</Grid>
|
|
187
|
-
</Stack>
|
|
188
|
-
</Box>
|
|
189
|
-
<Box
|
|
190
|
-
background="neutral0"
|
|
191
|
-
hasRadius
|
|
192
|
-
shadow="filterShadow"
|
|
193
|
-
padding={6}
|
|
194
|
-
>
|
|
195
|
-
<Stack size={4}>
|
|
196
|
-
<Typography variant="delta" as="h2">
|
|
197
|
-
{getMessage('pages.settings.additional.title')}
|
|
198
|
-
</Typography>
|
|
199
|
-
<Grid gap={4}>
|
|
200
259
|
<GridItem col={6} s={12} xs={12}>
|
|
201
260
|
<ToggleInput
|
|
202
261
|
name="audienceFieldChecked"
|
|
@@ -212,62 +271,7 @@ const SettingsPage = () => {
|
|
|
212
271
|
</Grid>
|
|
213
272
|
</Stack>
|
|
214
273
|
</Box>
|
|
215
|
-
{
|
|
216
|
-
<Box
|
|
217
|
-
background="neutral0"
|
|
218
|
-
hasRadius
|
|
219
|
-
shadow="filterShadow"
|
|
220
|
-
padding={6}
|
|
221
|
-
>
|
|
222
|
-
<Stack size={4}>
|
|
223
|
-
<Typography variant="delta" as="h2">
|
|
224
|
-
{getMessage('pages.settings.nameField.title')}
|
|
225
|
-
</Typography>
|
|
226
|
-
<Grid gap={4}>
|
|
227
|
-
{values.selectedContentTypes.map(uid => {
|
|
228
|
-
const { attributes, info: { displayName } } = allContentTypes.find(item => item.uid == uid);
|
|
229
|
-
const stringAttributes = Object.keys(attributes).filter(_ => attributes[_].type === 'string');
|
|
230
|
-
|
|
231
|
-
return !isEmpty(stringAttributes) && (
|
|
232
|
-
<GridItem key={`collectionSettings-${uid}`} col={6} s={12} xs={12}>
|
|
233
|
-
<Card background="primary100" borderColor="primary200">
|
|
234
|
-
<CardBody>
|
|
235
|
-
<CardContent style={{ width: '100%' }}>
|
|
236
|
-
<Stack size={4}>
|
|
237
|
-
<Typography variant="epsilon" fontWeight="semibold" as="h3">{displayName}</Typography>
|
|
238
|
-
<Select
|
|
239
|
-
name={`collectionSettings-${uid}-entryLabel`}
|
|
240
|
-
label={getMessage('pages.settings.form.nameField.label')}
|
|
241
|
-
hint={getMessage('pages.settings.form.nameField.hint')}
|
|
242
|
-
placeholder={getMessage('pages.settings.form.nameField.placeholder')}
|
|
243
|
-
onClear={() => null}
|
|
244
|
-
value={values.nameFields[uid] || []}
|
|
245
|
-
onChange={(value) => setFieldValue('nameFields', prepareNameFieldFor(uid, values.nameFields, value))}
|
|
246
|
-
multi
|
|
247
|
-
withTags
|
|
248
|
-
disabled={isRestartRequired}
|
|
249
|
-
>
|
|
250
|
-
{stringAttributes.map(key =>
|
|
251
|
-
(<Option key={uid + key} value={key}>{capitalize(key.split('_').join(' '))}</Option>))}
|
|
252
|
-
</Select>
|
|
253
|
-
</Stack>
|
|
254
|
-
</CardContent>
|
|
255
|
-
</CardBody>
|
|
256
|
-
</Card>
|
|
257
|
-
</GridItem>
|
|
258
|
-
);
|
|
259
|
-
})
|
|
260
|
-
}
|
|
261
|
-
</Grid>
|
|
262
|
-
</Stack>
|
|
263
|
-
</Box>
|
|
264
|
-
)}
|
|
265
|
-
<Box
|
|
266
|
-
background="neutral0"
|
|
267
|
-
hasRadius
|
|
268
|
-
shadow="filterShadow"
|
|
269
|
-
padding={6}
|
|
270
|
-
>
|
|
274
|
+
<Box {...boxDefaultProps} >
|
|
271
275
|
<Stack size={4}>
|
|
272
276
|
<Typography variant="delta" as="h2">
|
|
273
277
|
{getMessage('pages.settings.restoring.title')}
|
|
@@ -308,4 +312,4 @@ const SettingsPage = () => {
|
|
|
308
312
|
}
|
|
309
313
|
|
|
310
314
|
|
|
311
|
-
export default SettingsPage;
|
|
315
|
+
export default SettingsPage;
|
|
@@ -2,6 +2,7 @@ import React, { useEffect, useMemo, useState, useCallback } from 'react';
|
|
|
2
2
|
import { debounce, find, get, isEmpty, isEqual, isNil, isString } from 'lodash';
|
|
3
3
|
import PropTypes from 'prop-types';
|
|
4
4
|
import { Formik } from 'formik'
|
|
5
|
+
import slugify from 'slugify';
|
|
5
6
|
|
|
6
7
|
// Design System
|
|
7
8
|
import { ModalBody } from '@strapi/design-system/ModalLayout';
|
|
@@ -10,10 +11,7 @@ import { Grid, GridItem } from '@strapi/design-system/Grid';
|
|
|
10
11
|
import { Form, GenericInput } from '@strapi/helper-plugin';
|
|
11
12
|
|
|
12
13
|
import { NavigationItemPopupFooter } from '../NavigationItemPopup/NavigationItemPopupFooter';
|
|
13
|
-
|
|
14
|
-
|
|
15
14
|
import { navigationItemAdditionalFields, navigationItemType } from '../../utils/enums';
|
|
16
|
-
import slugify from 'slugify';
|
|
17
15
|
import { extractRelatedItemLabel } from '../../utils/parsers';
|
|
18
16
|
import { form as formDefinition } from './utils/form';
|
|
19
17
|
import { checkFormValidity } from '../../utils/form';
|
|
@@ -76,18 +74,19 @@ const NavigationItemForm = ({
|
|
|
76
74
|
};
|
|
77
75
|
|
|
78
76
|
const sanitizePayload = (payload = {}) => {
|
|
79
|
-
const { onItemClick, onItemLevelAddClick, related, relatedType, menuAttached, ...purePayload } = payload;
|
|
80
|
-
const sanitizedType = purePayload.type || navigationItemType.INTERNAL;
|
|
77
|
+
const { onItemClick, onItemLevelAddClick, related, relatedType, menuAttached, type, ...purePayload } = payload;
|
|
81
78
|
const relatedId = related
|
|
82
79
|
const relatedCollectionType = relatedType;
|
|
80
|
+
const title = payload.title || relatedSelectOptions.find(v => v.key == relatedId)?.label
|
|
83
81
|
return {
|
|
84
82
|
...purePayload,
|
|
83
|
+
title,
|
|
84
|
+
type,
|
|
85
85
|
menuAttached: isNil(menuAttached) ? false : menuAttached,
|
|
86
|
-
type:
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
relatedType: relatedCollectionType,
|
|
86
|
+
path: type !== navigationItemType.EXTERNAL ? purePayload.path : undefined,
|
|
87
|
+
externalPath: type === navigationItemType.EXTERNAL ? purePayload.externalPath : undefined,
|
|
88
|
+
related: type === navigationItemType.INTERNAL ? relatedId : undefined,
|
|
89
|
+
relatedType: type === navigationItemType.INTERNAL ? relatedCollectionType : undefined,
|
|
91
90
|
isSingle: isSingleSelected,
|
|
92
91
|
uiRouterKey: generateUiRouterKey(purePayload.title, relatedId, relatedCollectionType),
|
|
93
92
|
};
|
|
@@ -107,11 +106,8 @@ const NavigationItemForm = ({
|
|
|
107
106
|
}
|
|
108
107
|
};
|
|
109
108
|
|
|
110
|
-
const onTypeChange = ({ target: { name, value } }) =>
|
|
111
|
-
onChange({ target: { name, value: value ? navigationItemType.INTERNAL : navigationItemType.EXTERNAL } });
|
|
112
|
-
|
|
113
109
|
const onAudienceChange = (value) => {
|
|
114
|
-
onChange({target: {name: `${inputsPrefix}audience`, value}});
|
|
110
|
+
onChange({ target: { name: `${inputsPrefix}audience`, value } });
|
|
115
111
|
}
|
|
116
112
|
|
|
117
113
|
const onChange = ({ target: { name, value } }) => {
|
|
@@ -146,6 +142,20 @@ const NavigationItemForm = ({
|
|
|
146
142
|
[relatedTypeSelectValue, contentTypes],
|
|
147
143
|
);
|
|
148
144
|
|
|
145
|
+
const navigationItemTypeOptions = Object.keys(navigationItemType).map(key => {
|
|
146
|
+
const value = navigationItemType[key].toLowerCase();
|
|
147
|
+
return {
|
|
148
|
+
key,
|
|
149
|
+
value: navigationItemType[key],
|
|
150
|
+
metadatas: {
|
|
151
|
+
intlLabel: {
|
|
152
|
+
id: getTradId(`popup.item.form.type.${value}.label`),
|
|
153
|
+
defaultMessage: getTradId(`popup.item.form.type.${value}.label`),
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
149
159
|
const relatedSelectOptions = contentTypeEntities
|
|
150
160
|
.filter((item) => {
|
|
151
161
|
const usedContentTypeEntitiesOfSameType = usedContentTypeEntities
|
|
@@ -176,7 +186,9 @@ const NavigationItemForm = ({
|
|
|
176
186
|
const isExternal = form.type === navigationItemType.EXTERNAL;
|
|
177
187
|
const pathSourceName = isExternal ? 'externalPath' : 'path';
|
|
178
188
|
|
|
179
|
-
const submitDisabled =
|
|
189
|
+
const submitDisabled =
|
|
190
|
+
(form.type === navigationItemType.INTERNAL && isNil(get(form, `${inputsPrefix}related`))) ||
|
|
191
|
+
(form.type === navigationItemType.WRAPPER && isNil(get(form, `${inputsPrefix}title`)));
|
|
180
192
|
|
|
181
193
|
const debouncedSearch = useCallback(
|
|
182
194
|
debounce(nextValue => setContentTypeSearchQuery(nextValue), 500),
|
|
@@ -283,7 +295,21 @@ const NavigationItemForm = ({
|
|
|
283
295
|
value={get(form, `${inputsPrefix}title`, '')}
|
|
284
296
|
/>
|
|
285
297
|
</GridItem>
|
|
286
|
-
<GridItem key={`${inputsPrefix}
|
|
298
|
+
<GridItem key={`${inputsPrefix}type`} col={4} lg={12}>
|
|
299
|
+
<GenericInput
|
|
300
|
+
intlLabel={{
|
|
301
|
+
id: getTradId('popup.item.form.type.label'),
|
|
302
|
+
defaultMessage: 'Internal link',
|
|
303
|
+
}}
|
|
304
|
+
name={`${inputsPrefix}type`}
|
|
305
|
+
options={navigationItemTypeOptions}
|
|
306
|
+
type='select'
|
|
307
|
+
error={get(formErrors, `${inputsPrefix}type.id`)}
|
|
308
|
+
onChange={onChange}
|
|
309
|
+
value={get(form, `${inputsPrefix}type`, '')}
|
|
310
|
+
/>
|
|
311
|
+
</GridItem>
|
|
312
|
+
<GridItem key={`${inputsPrefix}menuAttached`} col={4} lg={12}>
|
|
287
313
|
<GenericInput
|
|
288
314
|
intlLabel={{
|
|
289
315
|
id: getTradId('popup.item.form.menuAttached.label'),
|
|
@@ -297,19 +323,6 @@ const NavigationItemForm = ({
|
|
|
297
323
|
disabled={!(data.isMenuAllowedLevel && data.parentAttachedToMenu)}
|
|
298
324
|
/>
|
|
299
325
|
</GridItem>
|
|
300
|
-
<GridItem key={`${inputsPrefix}type`} col={6} lg={12}>
|
|
301
|
-
<GenericInput
|
|
302
|
-
intlLabel={{
|
|
303
|
-
id: getTradId('popup.item.form.type.label'),
|
|
304
|
-
defaultMessage: 'Internal link',
|
|
305
|
-
}}
|
|
306
|
-
name={`${inputsPrefix}type`}
|
|
307
|
-
type='bool'
|
|
308
|
-
error={get(formErrors, `${inputsPrefix}type.id`)}
|
|
309
|
-
onChange={onTypeChange}
|
|
310
|
-
value={get(form, `${inputsPrefix}type`, '') === navigationItemType.INTERNAL}
|
|
311
|
-
/>
|
|
312
|
-
</GridItem>
|
|
313
326
|
<GridItem key={`${inputsPrefix}path`} col={12}>
|
|
314
327
|
<GenericInput
|
|
315
328
|
intlLabel={{
|
|
@@ -328,7 +341,7 @@ const NavigationItemForm = ({
|
|
|
328
341
|
description={generatePreviewPath()}
|
|
329
342
|
/>
|
|
330
343
|
</GridItem>
|
|
331
|
-
{
|
|
344
|
+
{get(form, `${inputsPrefix}type`) === navigationItemType.INTERNAL && (
|
|
332
345
|
<>
|
|
333
346
|
<GridItem col={6} lg={12}>
|
|
334
347
|
<GenericInput
|
|
@@ -400,8 +413,14 @@ const NavigationItemForm = ({
|
|
|
400
413
|
label={getMessage('popup.item.form.audience.label')}
|
|
401
414
|
onChange={onAudienceChange}
|
|
402
415
|
value={audience}
|
|
416
|
+
hint={
|
|
417
|
+
!isLoading && isEmpty(audienceOptions)
|
|
418
|
+
? getMessage('popup.item.form.audience.empty', 'There are no more audiences')
|
|
419
|
+
: undefined
|
|
420
|
+
}
|
|
403
421
|
multi
|
|
404
422
|
withTags
|
|
423
|
+
disabled={isEmpty(audienceOptions)}
|
|
405
424
|
>
|
|
406
425
|
{audienceOptions.map(({ value, label }) => <Option key={value} value={value}>{label}</Option>)}
|
|
407
426
|
</Select>
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
|
|
2
2
|
|
|
3
3
|
import React from 'react';
|
|
4
|
-
import {
|
|
4
|
+
import { Typography } from '@strapi/design-system/Typography';
|
|
5
5
|
import { ModalHeader } from '@strapi/design-system/ModalLayout';
|
|
6
6
|
import { getMessage } from '../../../../utils';
|
|
7
7
|
|
|
8
8
|
export const NavigationItemPopupHeader = ({isNewItem}) => {
|
|
9
9
|
return (
|
|
10
10
|
<ModalHeader>
|
|
11
|
-
<
|
|
11
|
+
<Typography variant="omega" fontWeight="bold" textColor="neutral800" as="h2" id="asset-dialog-title">
|
|
12
12
|
{getMessage(`popup.item.header.${isNewItem ? 'new' : 'edit'}`)}
|
|
13
|
-
</
|
|
13
|
+
</Typography>
|
|
14
14
|
</ModalHeader>
|
|
15
15
|
);
|
|
16
16
|
};
|
|
@@ -120,6 +120,44 @@ const View = () => {
|
|
|
120
120
|
}, []);
|
|
121
121
|
const filteredList = !isSearchEmpty ? filteredListFactory(changedActiveNavigation.items, (item) => item?.title.includes(searchValue)) : [];
|
|
122
122
|
|
|
123
|
+
const changeCollapseItemDeep = (item, collapse) => {
|
|
124
|
+
if (item.collapsed !== collapse) {
|
|
125
|
+
return {
|
|
126
|
+
...item,
|
|
127
|
+
collapsed: collapse,
|
|
128
|
+
updated: true,
|
|
129
|
+
items: item.items?.map(el => changeCollapseItemDeep(el, collapse))
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return {
|
|
133
|
+
...item,
|
|
134
|
+
items: item.items?.map(el => changeCollapseItemDeep(el, collapse))
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const handleCollapseAll = () => {
|
|
139
|
+
handleChangeNavigationData({
|
|
140
|
+
...changedActiveNavigation,
|
|
141
|
+
items: changedActiveNavigation.items.map(item => changeCollapseItemDeep(item, true))
|
|
142
|
+
}, true);
|
|
143
|
+
setStructureChanged(true);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const handleExpandAll = () => {
|
|
147
|
+
handleChangeNavigationData({
|
|
148
|
+
...changedActiveNavigation,
|
|
149
|
+
items: changedActiveNavigation.items.map(item => changeCollapseItemDeep(item, false))
|
|
150
|
+
}, true);
|
|
151
|
+
setStructureChanged(true);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const handleItemReOrder = (item, newOrder) => {
|
|
155
|
+
handleSubmitNavigationItem({
|
|
156
|
+
...item,
|
|
157
|
+
order: newOrder,
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
|
|
123
161
|
const handleItemRemove = (item) => {
|
|
124
162
|
handleSubmitNavigationItem({
|
|
125
163
|
...item,
|
|
@@ -134,6 +172,14 @@ const View = () => {
|
|
|
134
172
|
});
|
|
135
173
|
};
|
|
136
174
|
|
|
175
|
+
const handleItemToggleCollapse = (item) => {
|
|
176
|
+
handleSubmitNavigationItem({
|
|
177
|
+
...item,
|
|
178
|
+
collapsed: !item.collapsed,
|
|
179
|
+
updated: true,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
137
183
|
const handleItemEdit = (
|
|
138
184
|
item,
|
|
139
185
|
levelPath = '',
|
|
@@ -157,6 +203,33 @@ const View = () => {
|
|
|
157
203
|
setSearchValue('');
|
|
158
204
|
}
|
|
159
205
|
|
|
206
|
+
const endActions = [
|
|
207
|
+
{
|
|
208
|
+
onClick: handleExpandAll,
|
|
209
|
+
disabled: isLoadingForSubmit,
|
|
210
|
+
type: "submit",
|
|
211
|
+
variant: 'tertiary',
|
|
212
|
+
tradId: 'header.action.expandAll',
|
|
213
|
+
margin: '8px',
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
onClick: handleCollapseAll,
|
|
217
|
+
disabled: isLoadingForSubmit,
|
|
218
|
+
type: "submit",
|
|
219
|
+
variant: 'tertiary',
|
|
220
|
+
tradId: 'header.action.collapseAll',
|
|
221
|
+
margin: '8px',
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
onClick: addNewNavigationItem,
|
|
225
|
+
startIcon: <PlusIcon />,
|
|
226
|
+
disabled: isLoadingForSubmit,
|
|
227
|
+
type: "submit",
|
|
228
|
+
tradId: 'header.action.newItem',
|
|
229
|
+
margin: '16px',
|
|
230
|
+
},
|
|
231
|
+
]
|
|
232
|
+
|
|
160
233
|
return (
|
|
161
234
|
<Main labelledBy="title" aria-busy={isLoadingForSubmit}>
|
|
162
235
|
<NavigationHeader
|
|
@@ -173,18 +246,15 @@ const View = () => {
|
|
|
173
246
|
<>
|
|
174
247
|
<NavigationContentHeader
|
|
175
248
|
startActions={<Search value={searchValue} setValue={setSearchValue} />}
|
|
176
|
-
endActions={
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
>
|
|
182
|
-
{formatMessage(getTrad('header.action.newItem'))}
|
|
183
|
-
</Button>}
|
|
249
|
+
endActions={endActions.map(({ tradId, margin, ...item }, i) =>
|
|
250
|
+
<Box marginLeft={margin} key={i}>
|
|
251
|
+
<Button {...item}> {formatMessage(getTrad(tradId))} </Button>
|
|
252
|
+
</Box>
|
|
253
|
+
)}
|
|
184
254
|
/>
|
|
185
255
|
{isEmpty(changedActiveNavigation.items || []) && (
|
|
186
256
|
<Flex direction="column" minHeight="400px" justifyContent="center">
|
|
187
|
-
<Icon as={EmptyDocumentsIcon} width="160px" height="88px" color=""/>
|
|
257
|
+
<Icon as={EmptyDocumentsIcon} width="160px" height="88px" color="" />
|
|
188
258
|
<Box padding={4}>
|
|
189
259
|
<Typography variant="beta" textColor="neutral600">{formatMessage(getTrad('empty'))}</Typography>
|
|
190
260
|
</Box>
|
|
@@ -206,6 +276,8 @@ const View = () => {
|
|
|
206
276
|
onItemRemove={handleItemRemove}
|
|
207
277
|
onItemEdit={handleItemEdit}
|
|
208
278
|
onItemRestore={handleItemRestore}
|
|
279
|
+
onItemReOrder={handleItemReOrder}
|
|
280
|
+
onItemToggleCollapse={handleItemToggleCollapse}
|
|
209
281
|
displayFlat={!isSearchEmpty}
|
|
210
282
|
root
|
|
211
283
|
error={error}
|
|
@@ -24,12 +24,14 @@ export const transformItemToRESTPayload = (
|
|
|
24
24
|
order,
|
|
25
25
|
audience = [],
|
|
26
26
|
items = [],
|
|
27
|
+
collapsed,
|
|
27
28
|
} = item;
|
|
28
29
|
const isExternal = type === navigationItemType.EXTERNAL;
|
|
30
|
+
const isWrapper = type === navigationItemType.WRAPPER;
|
|
29
31
|
const { contentTypes = [] } = config;
|
|
30
32
|
|
|
31
33
|
const parsedRelated = Number(related);
|
|
32
|
-
const relatedId = isExternal || isNaN(parsedRelated) ? related?.value || related : parsedRelated;
|
|
34
|
+
const relatedId = isExternal || isWrapper || isNaN(parsedRelated) ? related?.value || related : parsedRelated;
|
|
33
35
|
|
|
34
36
|
const relatedContentType = relatedType ?
|
|
35
37
|
find(contentTypes,
|
|
@@ -46,13 +48,14 @@ export const transformItemToRESTPayload = (
|
|
|
46
48
|
removed,
|
|
47
49
|
order,
|
|
48
50
|
uiRouterKey,
|
|
51
|
+
collapsed,
|
|
49
52
|
menuAttached: itemAttachedToMenu,
|
|
50
53
|
audience: audience.map((audienceItem) =>
|
|
51
54
|
isObject(audienceItem) ? audienceItem.value : audienceItem,
|
|
52
55
|
),
|
|
53
56
|
path: isExternal ? undefined : path,
|
|
54
57
|
externalPath: isExternal ? externalPath : undefined,
|
|
55
|
-
related: isExternal
|
|
58
|
+
related: isExternal || isWrapper
|
|
56
59
|
? undefined
|
|
57
60
|
: [
|
|
58
61
|
{
|
|
@@ -274,7 +277,7 @@ export const usedContentTypes = (items = []) => items.flatMap(
|
|
|
274
277
|
|
|
275
278
|
export const isRelationCorrect = ({ related, type }) => {
|
|
276
279
|
const isRelationDefined = !isNil(related);
|
|
277
|
-
return type
|
|
280
|
+
return type !== navigationItemType.INTERNAL || (type === navigationItemType.INTERNAL && isRelationDefined);
|
|
278
281
|
};
|
|
279
282
|
|
|
280
283
|
export const isRelationPublished = ({ relatedRef, relatedType = {}, type, isCollection }) => {
|