strapi-plugin-navigation 2.0.0 → 2.0.4
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 +50 -5
- package/__mocks__/strapi.js +30 -19
- package/admin/src/components/ConfirmationDialog/index.js +56 -0
- package/admin/src/components/Item/ItemCardBadge/index.js +1 -1
- package/admin/src/components/Item/ItemCardHeader/index.js +6 -12
- package/admin/src/components/Item/index.js +8 -10
- package/admin/src/components/RestartAlert/index.js +8 -0
- package/admin/src/hooks/useAllContentTypes.js +13 -0
- package/admin/src/hooks/useNavigationConfig.js +58 -0
- package/admin/src/index.js +24 -1
- package/admin/src/pages/SettingsPage/index.js +311 -0
- package/admin/src/pages/View/components/NavigationItemForm/index.js +36 -9
- package/admin/src/pages/View/components/NavigationItemPopup/NavigationItemPopupFooter.js +3 -6
- package/admin/src/pages/View/components/NavigationItemPopup/NavigationItemPopupHeader.js +2 -4
- package/admin/src/pages/View/components/NavigationItemPopup/index.js +2 -4
- package/admin/src/translations/en.json +48 -8
- package/admin/src/translations/fr.json +4 -4
- package/admin/src/utils/api.js +51 -0
- package/admin/src/utils/index.js +20 -0
- package/package.json +5 -5
- package/server/bootstrap.js +30 -1
- package/server/controllers/navigation.js +30 -5
- package/server/graphql/index.js +3 -4
- package/server/graphql/queries/render-navigation.js +4 -3
- package/server/graphql/types/content-types-name-fields.js +4 -2
- package/server/graphql/types/navigation-related.js +2 -2
- package/server/routes/admin.js +24 -1
- package/server/services/__tests__/functions.test.js +48 -0
- package/server/services/__tests__/navigation.test.js +26 -4
- package/server/services/navigation.js +56 -16
- package/server/services/utils/functions.js +41 -0
- package/strapi-server.js +0 -2
- package/yarn-error.log +5263 -0
- 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/codecov.yml +0 -3
- package/public/assets/logo.png +0 -0
- package/public/assets/preview.png +0 -0
- package/server/register.js +0 -5
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import React, { useState } from 'react';
|
|
2
|
+
import { Formik } from 'formik';
|
|
3
|
+
import { isEmpty, capitalize, isEqual } from 'lodash';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
CheckPermissions,
|
|
7
|
+
LoadingIndicatorPage,
|
|
8
|
+
Form,
|
|
9
|
+
useOverlayBlocker,
|
|
10
|
+
useAutoReloadOverlayBlocker,
|
|
11
|
+
} from '@strapi/helper-plugin';
|
|
12
|
+
import { Main } from '@strapi/design-system/Main';
|
|
13
|
+
import { ContentLayout, HeaderLayout } from '@strapi/design-system/Layout';
|
|
14
|
+
import { Button } from '@strapi/design-system/Button';
|
|
15
|
+
import { Box } from '@strapi/design-system/Box';
|
|
16
|
+
import { Stack } from '@strapi/design-system/Stack';
|
|
17
|
+
import { Typography } from '@strapi/design-system/Typography';
|
|
18
|
+
import { Grid, GridItem } from '@strapi/design-system/Grid';
|
|
19
|
+
import { ToggleInput } from '@strapi/design-system/ToggleInput';
|
|
20
|
+
import { NumberInput } from '@strapi/design-system/NumberInput';
|
|
21
|
+
import { Select, Option } from '@strapi/design-system/Select';
|
|
22
|
+
import { Check, Refresh, Play } from '@strapi/icons';
|
|
23
|
+
import { SettingsPageTitle } from '@strapi/helper-plugin';
|
|
24
|
+
import {
|
|
25
|
+
Card,
|
|
26
|
+
CardBody,
|
|
27
|
+
CardContent,
|
|
28
|
+
} from '@strapi/design-system/Card';
|
|
29
|
+
|
|
30
|
+
import permissions from '../../permissions';
|
|
31
|
+
import useNavigationConfig from '../../hooks/useNavigationConfig';
|
|
32
|
+
import useAllContentTypes from '../../hooks/useAllContentTypes';
|
|
33
|
+
import { navigationItemAdditionalFields } from '../View/utils/enums';
|
|
34
|
+
import ConfirmationDialog from '../../components/ConfirmationDialog';
|
|
35
|
+
import RestartAlert from '../../components/RestartAlert';
|
|
36
|
+
import { getMessage } from '../../utils';
|
|
37
|
+
|
|
38
|
+
const SettingsPage = () => {
|
|
39
|
+
const { lockApp, unlockApp } = useOverlayBlocker();
|
|
40
|
+
const { lockAppWithAutoreload, unlockAppWithAutoreload } = useAutoReloadOverlayBlocker();
|
|
41
|
+
const [isRestorePopupOpen, setIsRestorePopupOpen] = useState(false);
|
|
42
|
+
const [isRestartRequired, setIsRestartRequired] = useState(false);
|
|
43
|
+
const { data: navigationConfigData, isLoading: isConfigLoading, err: configErr, submitMutation, restoreMutation, restartMutation } = useNavigationConfig();
|
|
44
|
+
const { data: allContentTypesData, isLoading: isContentTypesLoading, err: contentTypesErr } = useAllContentTypes();
|
|
45
|
+
const isLoading = isConfigLoading || isContentTypesLoading;
|
|
46
|
+
const isError = configErr || contentTypesErr;
|
|
47
|
+
|
|
48
|
+
const preparePayload = ({ selectedContentTypes, nameFields, audienceFieldChecked, allowedLevels }) => ({
|
|
49
|
+
contentTypes: selectedContentTypes,
|
|
50
|
+
contentTypesNameFields: nameFields,
|
|
51
|
+
additionalFields: audienceFieldChecked ? [navigationItemAdditionalFields.AUDIENCE] : [],
|
|
52
|
+
allowedLevels: allowedLevels,
|
|
53
|
+
gql: {
|
|
54
|
+
navigationItemRelated: selectedContentTypes.map(uid => allContentTypes.find(ct => ct.uid === uid).info.displayName)
|
|
55
|
+
}
|
|
56
|
+
})
|
|
57
|
+
const onSave = async (form) => {
|
|
58
|
+
lockApp();
|
|
59
|
+
const payload = preparePayload(form);
|
|
60
|
+
await submitMutation({ body: payload });
|
|
61
|
+
const isContentTypesChanged = !isEqual(payload.contentTypes, navigationConfigData.contentTypes);
|
|
62
|
+
if (isContentTypesChanged && navigationConfigData.isGQLPluginEnabled) {
|
|
63
|
+
setIsRestartRequired(true);
|
|
64
|
+
}
|
|
65
|
+
unlockApp();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const onPopupClose = async (isConfirmed) => {
|
|
69
|
+
setIsRestorePopupOpen(false);
|
|
70
|
+
if (isConfirmed) {
|
|
71
|
+
lockApp();
|
|
72
|
+
await restoreMutation();
|
|
73
|
+
unlockApp();
|
|
74
|
+
setIsRestartRequired(true);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const handleRestart = async () => {
|
|
79
|
+
lockAppWithAutoreload();
|
|
80
|
+
await restartMutation();
|
|
81
|
+
setIsRestartRequired(false);
|
|
82
|
+
unlockAppWithAutoreload();
|
|
83
|
+
};
|
|
84
|
+
const handleRestartDiscard = () => setIsRestartRequired(false);
|
|
85
|
+
|
|
86
|
+
const prepareNameFieldFor = (uid, current, value) => ({
|
|
87
|
+
...current,
|
|
88
|
+
[uid]: value && !isEmpty(value) ? [...value] : undefined,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
if (isLoading || isError) {
|
|
92
|
+
return (
|
|
93
|
+
<>
|
|
94
|
+
<SettingsPageTitle
|
|
95
|
+
name={getMessage('Settings.email.plugin.title', 'Configuration')}
|
|
96
|
+
/>
|
|
97
|
+
<LoadingIndicatorPage>
|
|
98
|
+
Fetching plugin config...
|
|
99
|
+
</LoadingIndicatorPage>
|
|
100
|
+
</>
|
|
101
|
+
)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const allContentTypes = !isLoading && Object.values(allContentTypesData).filter(item => item.uid.includes('api::'));
|
|
105
|
+
const selectedContentTypes = navigationConfigData?.contentTypes.map(item => item.uid);
|
|
106
|
+
const audienceFieldChecked = navigationConfigData?.additionalFields.includes(navigationItemAdditionalFields.AUDIENCE);
|
|
107
|
+
const allowedLevels = navigationConfigData?.allowedLevels;
|
|
108
|
+
const nameFields = navigationConfigData?.contentTypesNameFields
|
|
109
|
+
|
|
110
|
+
return (
|
|
111
|
+
<>
|
|
112
|
+
<SettingsPageTitle
|
|
113
|
+
name={getMessage('Settings.email.plugin.title', 'Configuration')}
|
|
114
|
+
/>
|
|
115
|
+
<Main labelledBy="title">
|
|
116
|
+
<Formik
|
|
117
|
+
initialValues={{
|
|
118
|
+
selectedContentTypes,
|
|
119
|
+
audienceFieldChecked,
|
|
120
|
+
allowedLevels,
|
|
121
|
+
nameFields,
|
|
122
|
+
}}
|
|
123
|
+
onSubmit={onSave}
|
|
124
|
+
>
|
|
125
|
+
{({ handleSubmit, setFieldValue, values }) => (
|
|
126
|
+
<Form noValidate onSubmit={handleSubmit}>
|
|
127
|
+
<HeaderLayout
|
|
128
|
+
title={getMessage('pages.settings.header.title')}
|
|
129
|
+
subtitle={getMessage('pages.settings.header.description')}
|
|
130
|
+
primaryAction={
|
|
131
|
+
<CheckPermissions permissions={permissions.access}>
|
|
132
|
+
<Button type="submit" startIcon={<Check />} disabled={isRestartRequired}>
|
|
133
|
+
{getMessage('pages.settings.actions.submit')}
|
|
134
|
+
</Button>
|
|
135
|
+
</CheckPermissions>
|
|
136
|
+
}
|
|
137
|
+
/>
|
|
138
|
+
<ContentLayout>
|
|
139
|
+
<Stack size={7}>
|
|
140
|
+
{isRestartRequired && (
|
|
141
|
+
<RestartAlert
|
|
142
|
+
closeLabel={getMessage('pages.settings.actions.restart.alert.cancel')}
|
|
143
|
+
title={getMessage('pages.settings.actions.restart.alert.title')}
|
|
144
|
+
action={<Box><Button onClick={handleRestart} startIcon={<Play />}>{getMessage('pages.settings.actions.restart')}</Button></Box>}
|
|
145
|
+
onClose={handleRestartDiscard}>
|
|
146
|
+
{getMessage('pages.settings.actions.restart.alert.description')}
|
|
147
|
+
</RestartAlert>)}
|
|
148
|
+
<Box
|
|
149
|
+
background="neutral0"
|
|
150
|
+
hasRadius
|
|
151
|
+
shadow="filterShadow"
|
|
152
|
+
padding={6}
|
|
153
|
+
>
|
|
154
|
+
<Stack size={4}>
|
|
155
|
+
<Typography variant="delta" as="h2">
|
|
156
|
+
{getMessage('pages.settings.general.title')}
|
|
157
|
+
</Typography>
|
|
158
|
+
<Grid gap={4}>
|
|
159
|
+
<GridItem col={12} s={12} xs={12}>
|
|
160
|
+
<Select
|
|
161
|
+
name="selectedContentTypes"
|
|
162
|
+
label={getMessage('pages.settings.form.contentTypes.label')}
|
|
163
|
+
placeholder={getMessage('pages.settings.form.contentTypes.placeholder')}
|
|
164
|
+
hint={getMessage('pages.settings.form.contentTypes.hint')}
|
|
165
|
+
onClear={() => setFieldValue('selectedContentTypes', [], false)}
|
|
166
|
+
value={values.selectedContentTypes}
|
|
167
|
+
onChange={(value) => setFieldValue('selectedContentTypes', value, false)}
|
|
168
|
+
multi
|
|
169
|
+
withTags
|
|
170
|
+
disabled={isRestartRequired}
|
|
171
|
+
>
|
|
172
|
+
{allContentTypes.map((item) => <Option key={item.uid} value={item.uid}>{item.info.displayName}</Option>)}
|
|
173
|
+
</Select>
|
|
174
|
+
</GridItem>
|
|
175
|
+
<GridItem col={3} s={6} xs={12}>
|
|
176
|
+
<NumberInput
|
|
177
|
+
name="allowedLevels"
|
|
178
|
+
label={getMessage('pages.settings.form.allowedLevels.label')}
|
|
179
|
+
placeholder={getMessage('pages.settings.form.allowedLevels.placeholder')}
|
|
180
|
+
hint={getMessage('pages.settings.form.allowedLevels.hint')}
|
|
181
|
+
onValueChange={(value) => setFieldValue('allowedLevels', value, false)}
|
|
182
|
+
value={values.allowedLevels}
|
|
183
|
+
disabled={isRestartRequired}
|
|
184
|
+
/>
|
|
185
|
+
</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
|
+
<GridItem col={6} s={12} xs={12}>
|
|
201
|
+
<ToggleInput
|
|
202
|
+
name="audienceFieldChecked"
|
|
203
|
+
label={getMessage('pages.settings.form.audience.label')}
|
|
204
|
+
hint={getMessage('pages.settings.form.audience.hint')}
|
|
205
|
+
checked={values.audienceFieldChecked}
|
|
206
|
+
onChange={({ target: { checked } }) => setFieldValue('audienceFieldChecked', checked, false)}
|
|
207
|
+
onLabel="Enabled"
|
|
208
|
+
offLabel="Disabled"
|
|
209
|
+
disabled={isRestartRequired}
|
|
210
|
+
/>
|
|
211
|
+
</GridItem>
|
|
212
|
+
</Grid>
|
|
213
|
+
</Stack>
|
|
214
|
+
</Box>
|
|
215
|
+
{!isEmpty(values.selectedContentTypes) && (
|
|
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
|
+
>
|
|
271
|
+
<Stack size={4}>
|
|
272
|
+
<Typography variant="delta" as="h2">
|
|
273
|
+
{getMessage('pages.settings.restoring.title')}
|
|
274
|
+
</Typography>
|
|
275
|
+
<Grid gap={4}>
|
|
276
|
+
<GridItem col={12} s={12} xs={12}>
|
|
277
|
+
<Typography>
|
|
278
|
+
{getMessage('pages.settings.actions.restore.description')}
|
|
279
|
+
</Typography>
|
|
280
|
+
</GridItem>
|
|
281
|
+
<GridItem col={6} s={12} xs={12}>
|
|
282
|
+
<CheckPermissions permissions={permissions.access}>
|
|
283
|
+
<Button variant="danger-light" startIcon={<Refresh />} onClick={() => setIsRestorePopupOpen(true)}>
|
|
284
|
+
{getMessage('pages.settings.actions.restore')}
|
|
285
|
+
</Button>
|
|
286
|
+
</CheckPermissions>
|
|
287
|
+
<ConfirmationDialog
|
|
288
|
+
isVisible={isRestorePopupOpen}
|
|
289
|
+
header={getMessage('pages.settings.actions.restore.confirmation.header')}
|
|
290
|
+
labelConfirm={getMessage('pages.settings.actions.restore.confirmation.confirm')}
|
|
291
|
+
iconConfirm={<Refresh />}
|
|
292
|
+
onConfirm={() => onPopupClose(true)}
|
|
293
|
+
onCancel={() => onPopupClose(false)}>
|
|
294
|
+
{getMessage('pages.settings.actions.restore.confirmation.description')}
|
|
295
|
+
</ConfirmationDialog>
|
|
296
|
+
</GridItem>
|
|
297
|
+
</Grid>
|
|
298
|
+
</Stack>
|
|
299
|
+
</Box>
|
|
300
|
+
</Stack>
|
|
301
|
+
</ContentLayout>
|
|
302
|
+
</Form>
|
|
303
|
+
)}
|
|
304
|
+
</Formik>
|
|
305
|
+
</Main>
|
|
306
|
+
</>
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
export default SettingsPage;
|
|
@@ -1,23 +1,24 @@
|
|
|
1
1
|
import React, { useEffect, useMemo, useState, useCallback } from 'react';
|
|
2
|
-
import { useIntl } from 'react-intl';
|
|
3
2
|
import { debounce, find, get, isEmpty, isEqual, isNil, isString } from 'lodash';
|
|
4
3
|
import PropTypes from 'prop-types';
|
|
5
4
|
import { Formik } from 'formik'
|
|
6
5
|
|
|
7
6
|
// Design System
|
|
8
7
|
import { ModalBody } from '@strapi/design-system/ModalLayout';
|
|
8
|
+
import { Select, Option } from '@strapi/design-system/Select';
|
|
9
9
|
import { Grid, GridItem } from '@strapi/design-system/Grid';
|
|
10
10
|
import { Form, GenericInput } from '@strapi/helper-plugin';
|
|
11
11
|
|
|
12
12
|
import { NavigationItemPopupFooter } from '../NavigationItemPopup/NavigationItemPopupFooter';
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
import { navigationItemType } from '../../utils/enums';
|
|
15
|
+
import { navigationItemAdditionalFields, navigationItemType } from '../../utils/enums';
|
|
16
16
|
import slugify from 'slugify';
|
|
17
17
|
import { extractRelatedItemLabel } from '../../utils/parsers';
|
|
18
18
|
import { form as formDefinition } from './utils/form';
|
|
19
19
|
import { checkFormValidity } from '../../utils/form';
|
|
20
20
|
import { getTradId } from '../../../../translations';
|
|
21
|
+
import { getMessage } from '../../../../utils';
|
|
21
22
|
|
|
22
23
|
const NavigationItemForm = ({
|
|
23
24
|
isLoading,
|
|
@@ -42,7 +43,6 @@ const NavigationItemForm = ({
|
|
|
42
43
|
const [form, setFormState] = useState({});
|
|
43
44
|
const [formErrors, setFormErrorsState] = useState({});
|
|
44
45
|
const { relatedType } = form;
|
|
45
|
-
const { formatMessage } = useIntl();
|
|
46
46
|
|
|
47
47
|
const relatedFieldName = `${inputsPrefix}related`;
|
|
48
48
|
|
|
@@ -52,22 +52,29 @@ const NavigationItemForm = ({
|
|
|
52
52
|
...data,
|
|
53
53
|
type: data.type || navigationItemType.INTERNAL,
|
|
54
54
|
related: data.related?.value,
|
|
55
|
-
relatedType: data.relatedType?.value
|
|
55
|
+
relatedType: data.relatedType?.value,
|
|
56
|
+
audience: data.audience?.map(item => item.id),
|
|
56
57
|
});
|
|
57
58
|
}
|
|
58
59
|
|
|
60
|
+
const audience = get(form, `${inputsPrefix}audience`, []);
|
|
61
|
+
const audienceOptions = availableAudience.map((item) => ({
|
|
62
|
+
value: get(item, 'id', " "),
|
|
63
|
+
label: get(item, 'name', " "),
|
|
64
|
+
}));
|
|
65
|
+
|
|
59
66
|
const generatePreviewPath = () => {
|
|
60
67
|
if (!isExternal) {
|
|
61
68
|
const value = `${data.levelPath !== '/' ? `${data.levelPath}` : ''}/${form.path !== '/' ? form.path || '' : ''}`;
|
|
62
69
|
return {
|
|
63
70
|
id: getTradId('popup.item.form.type.external.description'),
|
|
64
71
|
defaultMessage: '',
|
|
65
|
-
values: { value
|
|
72
|
+
values: { value }
|
|
66
73
|
}
|
|
67
74
|
}
|
|
68
75
|
return null;
|
|
69
76
|
};
|
|
70
|
-
|
|
77
|
+
|
|
71
78
|
const sanitizePayload = (payload = {}) => {
|
|
72
79
|
const { onItemClick, onItemLevelAddClick, related, relatedType, menuAttached, ...purePayload } = payload;
|
|
73
80
|
const sanitizedType = purePayload.type || navigationItemType.INTERNAL;
|
|
@@ -103,6 +110,10 @@ const NavigationItemForm = ({
|
|
|
103
110
|
const onTypeChange = ({ target: { name, value } }) =>
|
|
104
111
|
onChange({ target: { name, value: value ? navigationItemType.INTERNAL : navigationItemType.EXTERNAL } });
|
|
105
112
|
|
|
113
|
+
const onAudienceChange = (value) => {
|
|
114
|
+
onChange({target: {name: `${inputsPrefix}audience`, value}});
|
|
115
|
+
}
|
|
116
|
+
|
|
106
117
|
const onChange = ({ target: { name, value } }) => {
|
|
107
118
|
setFormState(prevState => ({
|
|
108
119
|
...prevState,
|
|
@@ -139,7 +150,7 @@ const NavigationItemForm = ({
|
|
|
139
150
|
.filter((item) => {
|
|
140
151
|
const usedContentTypeEntitiesOfSameType = usedContentTypeEntities
|
|
141
152
|
.filter(uctItem => relatedTypeSelectValue === uctItem.__collectionUid);
|
|
142
|
-
return !find(usedContentTypeEntitiesOfSameType, uctItem => (item.id === uctItem.id && uctItem.id !== form.related));
|
|
153
|
+
return !find(usedContentTypeEntitiesOfSameType, uctItem => (item.id === uctItem.id && uctItem.id !== form.related));
|
|
143
154
|
})
|
|
144
155
|
.map((item) => {
|
|
145
156
|
const label = appendLabelPublicationStatus(
|
|
@@ -153,8 +164,8 @@ const NavigationItemForm = ({
|
|
|
153
164
|
key: get(item, 'id'),
|
|
154
165
|
metadatas: {
|
|
155
166
|
intlLabel: {
|
|
156
|
-
id: label ||
|
|
157
|
-
defaultMessage: label ||
|
|
167
|
+
id: label || `${item.__collectionUid} ${item.id}`,
|
|
168
|
+
defaultMessage: label || `${item.__collectionUid} ${item.id}`,
|
|
158
169
|
}
|
|
159
170
|
},
|
|
160
171
|
value: item.id,
|
|
@@ -380,6 +391,22 @@ const NavigationItemForm = ({
|
|
|
380
391
|
)}
|
|
381
392
|
</>
|
|
382
393
|
)}
|
|
394
|
+
|
|
395
|
+
{additionalFields.includes(navigationItemAdditionalFields.AUDIENCE) && (
|
|
396
|
+
<GridItem key={`${inputsPrefix}audience`} col={6} lg={12}>
|
|
397
|
+
<Select
|
|
398
|
+
id={`${inputsPrefix}audience`}
|
|
399
|
+
placeholder={getMessage('popup.item.form.audience.placeholder')}
|
|
400
|
+
label={getMessage('popup.item.form.audience.label')}
|
|
401
|
+
onChange={onAudienceChange}
|
|
402
|
+
value={audience}
|
|
403
|
+
multi
|
|
404
|
+
withTags
|
|
405
|
+
>
|
|
406
|
+
{audienceOptions.map(({ value, label }) => <Option key={value} value={value}>{label}</Option>)}
|
|
407
|
+
</Select>
|
|
408
|
+
</GridItem>
|
|
409
|
+
)}
|
|
383
410
|
</Grid>
|
|
384
411
|
</ModalBody>
|
|
385
412
|
</Form>
|
|
@@ -1,25 +1,22 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
|
3
|
-
import { useIntl } from 'react-intl';
|
|
4
3
|
|
|
5
4
|
import { Button } from '@strapi/design-system/Button';
|
|
6
5
|
import { ModalFooter } from '@strapi/design-system/ModalLayout';
|
|
7
|
-
|
|
8
|
-
import { getTrad } from '../../../../translations';
|
|
6
|
+
import { getMessage } from '../../../../utils';
|
|
9
7
|
|
|
10
8
|
export const NavigationItemPopupFooter = ({ handleCancel, handleSubmit, submitDisabled, formViewId }) => {
|
|
11
|
-
const { formatMessage } = useIntl();
|
|
12
9
|
|
|
13
10
|
return (
|
|
14
11
|
<ModalFooter
|
|
15
12
|
startActions={
|
|
16
13
|
<Button onClick={handleCancel} variant="tertiary">
|
|
17
|
-
{
|
|
14
|
+
{getMessage('popup.item.form.button.cancel')}
|
|
18
15
|
</Button>
|
|
19
16
|
}
|
|
20
17
|
endActions={
|
|
21
18
|
<Button onClick={handleSubmit} disabled={submitDisabled}>
|
|
22
|
-
{
|
|
19
|
+
{getMessage(`popup.item.form.button.save`)}
|
|
23
20
|
</Button>
|
|
24
21
|
}
|
|
25
22
|
/>
|
|
@@ -3,15 +3,13 @@
|
|
|
3
3
|
import React from 'react';
|
|
4
4
|
import { ButtonText } from '@strapi/design-system/Text';
|
|
5
5
|
import { ModalHeader } from '@strapi/design-system/ModalLayout';
|
|
6
|
-
import {
|
|
7
|
-
import { getTrad } from '../../../../translations';
|
|
6
|
+
import { getMessage } from '../../../../utils';
|
|
8
7
|
|
|
9
8
|
export const NavigationItemPopupHeader = ({isNewItem}) => {
|
|
10
|
-
const { formatMessage } = useIntl();
|
|
11
9
|
return (
|
|
12
10
|
<ModalHeader>
|
|
13
11
|
<ButtonText textColor="neutral800" as="h2" id="asset-dialog-title">
|
|
14
|
-
{
|
|
12
|
+
{getMessage(`popup.item.header.${isNewItem ? 'new' : 'edit'}`)}
|
|
15
13
|
</ButtonText>
|
|
16
14
|
</ModalHeader>
|
|
17
15
|
);
|
|
@@ -5,7 +5,6 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import React from 'react';
|
|
8
|
-
import { useIntl } from 'react-intl';
|
|
9
8
|
import PropTypes from 'prop-types';
|
|
10
9
|
import { find } from 'lodash';
|
|
11
10
|
|
|
@@ -15,8 +14,8 @@ import { ModalLayout } from '@strapi/design-system/ModalLayout';
|
|
|
15
14
|
import NavigationItemForm from '../NavigationItemForm';
|
|
16
15
|
import { extractRelatedItemLabel, isRelationCorrect, isRelationPublished } from '../../utils/parsers';
|
|
17
16
|
import { navigationItemType } from '../../utils/enums';
|
|
18
|
-
import { getTrad } from '../../../../translations';
|
|
19
17
|
import { NavigationItemPopupHeader } from './NavigationItemPopupHeader';
|
|
18
|
+
import { getMessage } from '../../../../utils';
|
|
20
19
|
|
|
21
20
|
const NavigationItemPopUp = ({
|
|
22
21
|
isOpen,
|
|
@@ -30,7 +29,6 @@ const NavigationItemPopUp = ({
|
|
|
30
29
|
usedContentTypesData,
|
|
31
30
|
}) => {
|
|
32
31
|
|
|
33
|
-
const { formatMessage } = useIntl();
|
|
34
32
|
|
|
35
33
|
const handleOnSubmit = (payload) => {
|
|
36
34
|
onSubmit(payload);
|
|
@@ -51,7 +49,7 @@ const NavigationItemPopUp = ({
|
|
|
51
49
|
relatedRef: item,
|
|
52
50
|
type: item.isSingle ? navigationItemType.INTERNAL : item.type,
|
|
53
51
|
isCollection,
|
|
54
|
-
}) ? '' : `[${
|
|
52
|
+
}) ? '' : `[${getMessage('notification.navigation.item.relation.status.draft')}] `.toUpperCase();
|
|
55
53
|
return `${appendix}${label}`;
|
|
56
54
|
};
|
|
57
55
|
|
|
@@ -18,14 +18,14 @@
|
|
|
18
18
|
"popup.item.form.path.preview": "Preview:",
|
|
19
19
|
"popup.item.form.externalPath.label": "External URL",
|
|
20
20
|
"popup.item.form.externalPath.placeholder": "Link to the external source",
|
|
21
|
-
"popup.item.form.externalPath.validation.type": "This value is not a proper url."
|
|
21
|
+
"popup.item.form.externalPath.validation.type": "This value is not a proper url.",
|
|
22
22
|
"popup.item.form.menuAttached.label": "Attach to menu",
|
|
23
23
|
"popup.item.form.type.label": "Internal link",
|
|
24
24
|
"popup.item.form.type.internal.label": "Internal source",
|
|
25
25
|
"popup.item.form.type.external.label": "External source",
|
|
26
26
|
"popup.item.form.type.external.description": "Output path: {value}",
|
|
27
27
|
"popup.item.form.audience.label": "Audience",
|
|
28
|
-
"popup.item.form.audience.placeholder": "
|
|
28
|
+
"popup.item.form.audience.placeholder": "Select audience...",
|
|
29
29
|
"popup.item.form.relatedSection.label": "Relation to",
|
|
30
30
|
"popup.item.form.relatedType.label": "Content Type",
|
|
31
31
|
"popup.item.form.relatedType.placeholder": "Select content type...",
|
|
@@ -43,9 +43,49 @@
|
|
|
43
43
|
"notification.navigation.item.relation": "Entity relation does not exist!",
|
|
44
44
|
"notification.navigation.item.relation.status.draft": "draft",
|
|
45
45
|
"notification.navigation.item.relation.status.published": "published",
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
|
|
51
|
-
|
|
46
|
+
"pages.settings.general.title": "General settings",
|
|
47
|
+
"pages.settings.additional.title": "Additional settings",
|
|
48
|
+
"pages.settings.nameField.title": "Content types settings",
|
|
49
|
+
"pages.settings.restoring.title": "Restoring",
|
|
50
|
+
"pages.settings.section.title": "Navigation Plugin",
|
|
51
|
+
"pages.settings.section.subtitle": "Configuration",
|
|
52
|
+
"pages.settings.header.title": "Navigation",
|
|
53
|
+
"pages.settings.header.description": "Configure the navigation plugin",
|
|
54
|
+
"pages.settings.actions.restart": "Restart Strapi",
|
|
55
|
+
"pages.settings.actions.submit": "Save configuration",
|
|
56
|
+
"pages.settings.actions.restore": "Restore configuration",
|
|
57
|
+
"pages.settings.actions.restore.confirmation.header": "Do you want to continue?",
|
|
58
|
+
"pages.settings.actions.restore.confirmation.confirm": "Restore",
|
|
59
|
+
"pages.settings.actions.restore.confirmation.description": "Plugin config will be restored from plugins.js file.",
|
|
60
|
+
"pages.settings.actions.restore.description": "Restoring the plugin configuration will cause it to be replaced with configuration saved inside 'plugins.js' file.",
|
|
61
|
+
"pages.settings.actions.restart.alert.title": "Strapi requires restart",
|
|
62
|
+
"pages.settings.actions.restart.alert.description": "You've made a configuration changes which requires your Strapi application to be restarted to take an effect in GraphQL schema. Do it manually or by using below trigger.",
|
|
63
|
+
"pages.settings.actions.restart.alert.close": "Discard",
|
|
64
|
+
"pages.settings.actions.restart.alert.cancel": "Cancel",
|
|
65
|
+
"pages.settings.notification.fetch.error": "Failed to fetch configuration. Retrying...",
|
|
66
|
+
"pages.settings.notification.submit.success": "Config has been updated successfully",
|
|
67
|
+
"pages.settings.notification.restore.success": "Config has been restored successfully",
|
|
68
|
+
"pages.settings.notification.restart.success": "Application has been restarted successfully",
|
|
69
|
+
"pages.settings.notification.submit.error": "Config update has failed",
|
|
70
|
+
"pages.settings.notification.restore.error": "Config restore has failed",
|
|
71
|
+
"pages.settings.notification.restart.error": "Failed to restart your application. Try to do it manually.",
|
|
72
|
+
"pages.settings.form.contentTypes.label": "Enable for Collection(s)",
|
|
73
|
+
"pages.settings.form.contentTypes.placeholder": "eg. Pages, Posts",
|
|
74
|
+
"pages.settings.form.contentTypes.hint": "Content types that can be related with navigation items. This configuration is applicable both for REST & GraphQL",
|
|
75
|
+
"pages.settings.form.allowedLevels.label": "Allowed levels",
|
|
76
|
+
"pages.settings.form.allowedLevels.placeholder": "eg. 2",
|
|
77
|
+
"pages.settings.form.allowedLevels.hint": "Maximum level for which you're able to mark item as \"Menu attached\"",
|
|
78
|
+
"pages.settings.form.audience.label": "Audience",
|
|
79
|
+
"pages.settings.form.audience.hint": "Enable audience field",
|
|
80
|
+
"pages.settings.form.nameField.label": "Name fields",
|
|
81
|
+
"pages.settings.form.nameField.placeholder": "Select at least one or leave empty to apply defaults",
|
|
82
|
+
"pages.settings.form.nameField.hint": "If left empty name field is going to take following ordered fields: \"title\", \"subject\" and \"name\"",
|
|
83
|
+
"components.navigationItem.action.newItem": "Add nested item",
|
|
84
|
+
"components.navigationItem.badge.removed": "Removed",
|
|
85
|
+
"components.navigationItem.badge.draft": "{type}: Draft",
|
|
86
|
+
"components.navigationItem.badge.published": "{type}: Published",
|
|
87
|
+
"components.confirmation.dialog.button.cancel": "Cancel",
|
|
88
|
+
"components.confirmation.dialog.button.confirm": "Confirm",
|
|
89
|
+
"components.confirmation.dialog.description": "Do you want to continue?",
|
|
90
|
+
"components.confirmation.dialog.header": "Confirmation"
|
|
91
|
+
}
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"notification.navigation.item.relation": "L'entitée n'a pas de relations!",
|
|
39
39
|
"notification.navigation.item.relation.status.draft": "brouillon",
|
|
40
40
|
"notification.navigation.item.relation.status.published": "publié",
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
44
|
-
"
|
|
41
|
+
"components.navigationItem.action.newItem": "Nouvel élément imbriqué",
|
|
42
|
+
"components.navigationItem.badge.removed": "Supprimé",
|
|
43
|
+
"components.navigationItem.badge.draft": "Brouillon",
|
|
44
|
+
"components.navigationItem.badge.published": "Publié"
|
|
45
45
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { request } from '@strapi/helper-plugin';
|
|
2
|
+
import pluginId from '../pluginId';
|
|
3
|
+
|
|
4
|
+
export const fetchNavigationConfig = async () => {
|
|
5
|
+
try {
|
|
6
|
+
const data = await request(`/${pluginId}/settings/config`, { method: 'GET' });
|
|
7
|
+
return data;
|
|
8
|
+
} catch (err) {
|
|
9
|
+
toggleNotification({
|
|
10
|
+
type: 'warning',
|
|
11
|
+
message: { id: 'notification.error' },
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
return { err };
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const updateNavigationConfig = async ({ body }) =>
|
|
19
|
+
request(`/${pluginId}/config`, { method: 'PUT', body }, true);
|
|
20
|
+
|
|
21
|
+
export const restoreNavigationConfig = async () =>
|
|
22
|
+
request(`/${pluginId}/config`, { method: 'DELETE' }, true);
|
|
23
|
+
|
|
24
|
+
export const fetchAllContentTypes = async () => {
|
|
25
|
+
try {
|
|
26
|
+
const { data } = await request('/content-manager/content-types');
|
|
27
|
+
return { ...data }
|
|
28
|
+
} catch (err) {
|
|
29
|
+
toggleNotification({
|
|
30
|
+
type: 'warning',
|
|
31
|
+
message: { id: 'notification.error' },
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
return { err };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const restartStrapi = async (toggleNotification) => {
|
|
39
|
+
try {
|
|
40
|
+
const { data } = await request(`/${pluginId}/settings/restart`);
|
|
41
|
+
|
|
42
|
+
return data;
|
|
43
|
+
} catch (err) {
|
|
44
|
+
toggleNotification({
|
|
45
|
+
type: 'warning',
|
|
46
|
+
message: { id: 'notification.error' },
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return { err };
|
|
50
|
+
}
|
|
51
|
+
};
|